Mycat(基于HGDB的水平切分、主从复制、读写分离)

1.Mycat是什么?

Text
Mycat是一款基于阿里开源产品Cobar而研发的开源数据库分库分表中间件(基于Java语言开发)。官网所言:Mycat国内最活跃的、性能最好的开源数据库中间件!
一个彻底开源的,面向企业应用开发的大数据库集群。
支持事务、ACID、可以替代MySQL的加强版数据库。
一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群。
一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server。
结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品。
一个新颖的数据库中间件产品。

2.Mycat关键特性

Text
支持SQL92标准
支持MySQL、Oracle、DB2、SQL Server、PostgreSQL等DB的常见SQL语法
遵守Mysql原生协议,跨语言,跨平台,跨数据库的通用中间件代理
基于心跳的自动故障切换,支持读写分离,支持MySQL主从,以及galera cluster集群
支持Galera for MySQL集群,Percona Cluster或者MariaDB cluster
基于Nio实现,有效管理线程,解决高并发问题
支持数据的多片自动路由与聚合,支持sum,count,max等常用的聚合函数,支持跨库分页
支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join
支持通过全局表,ER关系的分片策略,实现了高效的多表join查询
支持多租户方案
支持分布式事务(弱xa)
支持XA分布式事务(1.6.5)
支持全局序列号,解决分布式下的主键生成问题
分片规则丰富,插件化开发,易于扩展
强大的web,命令行监控
支持前端作为MySQL通用代理,后端JDBC方式支持MySQL、PostgreSQL、Oracle、DB2、SQLServer、MongoDB、巨杉
支持密码加密
支持服务降级
支持IP白名单
支持SQL黑名单、sql注入攻击拦截
支持prepare预编译指令(1.6)
支持非堆内存(Direct Memory)聚合计算(1.6)
支持PostgreSQL的native协议(1.6)
支持mysql和oracle存储过程,out参数、多结果集返回(1.6)
支持zookeeper协调主从切换、zk序列、配置zk化(1.6)
支持库内分表(1.6)
集群基于ZooKeeper管理,在线升级,扩容,智能优化,大数据处理(2.0开发版

3.安装与使用

下载地址

https://github.com/MyCATApache/Mycat-download/tree/master

#Mycat是基于Java开发的,确保安装好了Java环境,可命令行输入:java -version 进行测试。
#Linux下还需配置Mycat的解压目录:vim /etc/profile,配置完成后使用:source /etc/profile
export MYCAT_HOME=/usr/local/mycat
export JAVA_HOME=/usr/local/java/jdk1.8.0_291
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JRE_HOME/lib
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$MYCAT_HOME/bin:$PATH

#安装好了jdk,并且配置了环境变量后进入​​/usr/local/mycat/conf​​​目录下设置jvm
vim wrapper.conf
wrapper.java.command=/usr/local/java/jdk1.8.0_291/bin/java
#运行(Linux)
mycat start #启动
mycat stop #停止
mycat console #前台运行
mycat restart #重启服务
mycat pause #暂停
mycat status #查看启动状态

4.Mycat配置

4.1.目录说明
bin:启动目录
conf:配置文件目录
server.xml:是Mycat服务器参数调整和用户授权的配置文件
schema.xml:是逻辑库定义和表以及分片定义的配置文件
rule.xml:是分片规则的配置文件,分片规则的具体一些参数信息单独存放为文件,也在这个目录下,配置文件修改 需要重启MyCAT
log4j.xml:日志存放在logs/log中,每天一个文件,日志的配置是在conf/log4j.xml中,根据自己的需要可以调整 输出级别为debug,debug级别下,会输出更多的信息,方便排查问题
autopartition-long.txt,partition-hash-int.txt,sequence_conf.properties, sequence_db_conf.properties 分片相关的id分片规则配置文件
lib:jar包目录
logs :日志目录
tmlogs:临时日志目录
4.2.配置文件server.xml
Text
包含了Mycat需要的系统配置信息,用户配置信息以及逻辑库配置信息,源代码中的映射类为:SystemConfig.class
添加如下配置:相当于建立了一个叫做mycat用户,对应密码为123456,该用户管理了mycats这个逻辑库。当然了,也可以为用户添加管理多个逻辑库,以,(英文逗号)分隔开即可
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="defaultSqlParser">druidparser</property>
<property name="mutiNodeLimitType">1</property>
<property name="serverPort">8066</property> <!--连接端口,替换连接mysql时的3306端口-->
<property name="managerPort">9066</property>
</system>
<!--用户配置节点-->
<user name="mycat"> <!-- 连接mycat的用户名-->
<property name="password">123456</property> <!-- 连接mycat的密码-->
<property name="schemas">mycats</property> <!-- 逻辑数据库名,这里会和schema.xml中的配置关联,多个用逗号分开 -->
<property name="readOnly">false</property>
</user>
</mycat:server>

4.3.配置文件schema.xml
Text
可以说是最重要的配置文件,管理着 MyCat 的逻辑库、表、分片规则、DataNode 以及 DataSource
schema是实际逻辑库的配置,多个schema代表多个逻辑库
dataNode是逻辑库对应的分片,如果配置多个分片则需要添加多个dataNode即可
dataHost是实际的物理库配置,可以根据业务需要配置多主、主从等其他配置,多个dataHost代表分片对应的物理库地址,下面的writeHost、readHost代表该分片是否配置多写,主从,读写分离等高级特性
#添加如下配置:水平切分,数据按Id取模均匀划分到两个数据库中
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑数据库名:mycatDatbase,与server.xml中对应;student 是物理数据库中的表,这里在两个库都有这个表 ; dataNode 是自己定义的,区别两个数据库-->
<schema name="mycats" checkSQLschema="false" sqlMaxLimit="100">
<table name="student" primaryKey="id" dataNode="masterNode1,masterNode2" rule="ruleById"/>
</schema>
<!-- 设置dataNode 对应的数据库,name 和schema中对应 ,dataHost:自己定义的host配置 ,database是物理数据库,master1和master2是两个物理数据库 -->
<dataNode name="masterNode1" dataHost="masterHost1" database="master1" />
<dataNode name="masterNode2" dataHost="masterHost2" database="master2" />
<!-- mycat 逻辑主机dataHost对应的物理主机.其中也设置对应的mysql登陆信息 balance 为1 表示读写分离-->
<dataHost name="masterHost1" maxCon="100" minCon="10" balance="3" writeType="0" dbType="postgresql" dbDriver="jdbc">
<heartbeat>select 1</heartbeat>
<writeHost host="hostM1" url="jdbc:highgo://192.168.100.10:5866/master1" user="sysdba" password="Qwer@1234">
<readHost host="hostS1" url="jdbc:highgo://192.168.100.11:5866/master1" user="sysdba" password="Qwer@1234"/>
</writeHost>
</dataHost>

<dataHost name="masterHost2" maxCon="100" minCon="10" balance="3" writeType="0" dbType="postgresql" dbDriver="jdbc">
<heartbeat>select 1</heartbeat>
<writeHost host="hostM2" url="jdbc:highgo://192.168.100.10:5866/master2" user="sysdba" password="Qwer@1234">
<readHost host="hostS2" url="jdbc:highgo://192.168.100.11:5866/master2" user="sysdba" password="Qwer@1234"/>
</writeHost>
</dataHost>
</mycat:schema>
4.4.配置文件rule.xml
Text
定义了表拆分所涉及到的规则定义。根据业务可以灵活的对表使用不同的分片算法(目前已实现十余种不同的分片规则,对应所在源码包为:io.mycat.route.function),或者对表使用相同的算法但具体的参数不同。
#添加如下配置:水平切分,数据按Id取模均匀划分到两个数据库中
<mycat:rule xmlns:mycat="http://io.mycat/">
<tableRule name="ruleById">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- 说明我们前边用多少个mode,数量要一致,不然会报错-->
<property name="count">2</property>
</function>
</mycat:rule>

5.Springboot项目通过Mycat连接HGDB

5.1.准备环境
1.瀚高数据库4.5.8搭建主从流复制集群,参考流复制搭建手册。
2.主节点创建数据库master1和master2,分别创建表student
CREATE TABLE public.student(
id integer NOT NULL,
name character varying(200),
age integer,
address character varying(200),
PRIMARY KEY (id)
);
3.放入驱动HgdbJdbc-6.2.3.jar,路径为/usr/local/mycat/lib
5.2.配置application.properties
#注意了,这里都是用连mysql的方式去配置,Mycat会在后端做好对其它数据库的连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=mycat
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://192.168.100.5:8066/mycats
5.3.添加实体Student.java
public class Student implements Serializable{
private int id;
private String name;
private int age;
private String address;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public Student(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}

public Student() {
}
public String toString(){
return "[id:"+this.id+",age:"+this.age+",name:"+this.name+",address:"+this.address+"]";
}

}
5.4.添加StundentMapper.java
public interface StudentMapper {

int getCount();

List<Student> getStuList();

int insert(Student student);

List<Student> getStuByAge(int age);

}

5.5.添加StudentService.java

@Service
public class StudentService implements StudentMapper {
@Autowired
private StudentMapper studentMapper;
@Override
public int getCount() {
return studentMapper.getCount();
}

@Override
public List<Student> getStuList() {
return studentMapper.getStuList();
}

@Override
public int insert(Student student) {
return studentMapper.insert(student);
}

@Override
public List<Student> getStuByAge(int age) {
return studentMapper.getStuByAge(age);
}

}
5.6.添加StudentController.java
@Api(value="StudentController",description = "学生信息相关接口,读写分离了")
@Controller
@RequestMapping("/stu")
public class StudentController {
private static final Logger log = LoggerFactory.getLogger(StudentController.class);
@Autowired
private StudentService studentService;

@RequestMapping(value = "/getCount",method = RequestMethod.GET)
@ResponseBody
@ApiOperation("查询所有学生数量")
public String getStuCount(){
System.out.print("==========");
int count = studentService.getCount();
String str = "总计数量:"+count;
return str;
}
@RequestMapping(value = "/getAllStudent",method = {RequestMethod.GET})
@ResponseBody
@ApiOperation("得到所有学生信息列表")
public List<Student> getAllStudent(){
log.info("======== getAllStudent start ==============");
List<Student> studentList = studentService.getStuList();
log.info("=============== studentList message ==============");
log.info("studentList: "+studentList);
log.info("======== getAllStudent end ==============");
return studentList;
}
@RequestMapping(value = "/getStuByAge/{age}",method = {RequestMethod.GET})
@ResponseBody
@ApiOperation(value="查询年龄小于age的学生信息",notes = "查询年龄大于age的学生信息 ")
public List<Student> getStuByAge(@PathVariable(value="age") Integer age){
log.info("======== getStuByAge start ==============");
log.info("=======================age:>"+age);
List<Student> studentList = studentService.getStuByAge(age);
return studentList;
}
@ApiOperation(value = "新增学生信息",notes = "")
@RequestMapping(value = "/insertStu",method = RequestMethod.GET)
@ResponseBody
public String insertStu(){
for (int i = 1; i <= 50; i++) {
Student student = new Student();
student.setId(i);
student.setName("lisi"+i);
student.setAge(18);
student.setAddress("山东");
studentService.insert(student);
}
System.out.println("=============insert success==================");
return "success";
}
}
5.7.启动项目
Text
http://localhost:8080/stu/insertStu #测试插入
#查看master1和master2,数据按id取模的方式划分到了两个数据库中,同时从库同步了主库的数据
master1=# select * from student;
id | name | age | address
----+--------+-----+---------
2 | lisi2 | 18 | 山东
4 | lisi4 | 18 | 山东
6 | lisi6 | 18 | 山东
8 | lisi8 | 18 | 山东
10 | lisi10 | 18 | 山东
12 | lisi12 | 18 | 山东
14 | lisi14 | 18 | 山东
16 | lisi16 | 18 | 山东
18 | lisi18 | 18 | 山东
20 | lisi20 | 18 | 山东
22 | lisi22 | 18 | 山东
24 | lisi24 | 18 | 山东
26 | lisi26 | 18 | 山东
28 | lisi28 | 18 | 山东
30 | lisi30 | 18 | 山东
32 | lisi32 | 18 | 山东
34 | lisi34 | 18 | 山东
36 | lisi36 | 18 | 山东
38 | lisi38 | 18 | 山东
40 | lisi40 | 18 | 山东
42 | lisi42 | 18 | 山东
44 | lisi44 | 18 | 山东
46 | lisi46 | 18 | 山东
48 | lisi48 | 18 | 山东
50 | lisi50 | 18 | 山东
(25 rows)
master2=# select * from student;
id | name | age | address
----+--------+-----+---------
1 | lisi1 | 18 | 山东
3 | lisi3 | 18 | 山东
5 | lisi5 | 18 | 山东
7 | lisi7 | 18 | 山东
9 | lisi9 | 18 | 山东
11 | lisi11 | 18 | 山东
13 | lisi13 | 18 | 山东
15 | lisi15 | 18 | 山东
17 | lisi17 | 18 | 山东
19 | lisi19 | 18 | 山东
21 | lisi21 | 18 | 山东
23 | lisi23 | 18 | 山东
25 | lisi25 | 18 | 山东
27 | lisi27 | 18 | 山东
29 | lisi29 | 18 | 山东
31 | lisi31 | 18 | 山东
33 | lisi33 | 18 | 山东
35 | lisi35 | 18 | 山东
37 | lisi37 | 18 | 山东
39 | lisi39 | 18 | 山东
41 | lisi41 | 18 | 山东
43 | lisi43 | 18 | 山东
45 | lisi45 | 18 | 山东
47 | lisi47 | 18 | 山东
49 | lisi49 | 18 | 山东
(25 rows)
Text
http://localhost:8080/stu/getAllStudent #测试查询
[{"id":1,"name":"lisi1","age":18,"address":"山东"},{"id":3,"name":"lisi3","age":18,"address":"山东"},{"id":5,"name":"lisi5","age":18,"address":"山东"},{"id":7,"name":"lisi7","age":18,"address":"山东"},{"id":9,"name":"lisi9","age":18,"address":"山东"},{"id":11,"name":"lisi11","age":18,"address":"山东"},{"id":13,"name":"lisi13","age":18,"address":"山东"},{"id":15,"name":"lisi15","age":18,"address":"山东"},{"id":17,"name":"lisi17","age":18,"address":"山东"},{"id":19,"name":"lisi19","age":18,"address":"山东"},{"id":21,"name":"lisi21","age":18,"address":"山东"},{"id":23,"name":"lisi23","age":18,"address":"山东"},{"id":2,"name":"lisi2","age":18,"address":"山东"},{"id":4,"name":"lisi4","age":18,"address":"山东"},{"id":6,"name":"lisi6","age":18,"address":"山东"},{"id":8,"name":"lisi8","age":18,"address":"山东"},{"id":10,"name":"lisi10","age":18,"address":"山东"},{"id":12,"name":"lisi12","age":18,"address":"山东"},{"id":14,"name":"lisi14","age":18,"address":"山东"},{"id":16,"name":"lisi16","age":18,"address":"山东"},{"id":18,"name":"lisi18","age":18,"address":"山东"},{"id":20,"name":"lisi20","age":18,"address":"山东"},{"id":22,"name":"lisi22","age":18,"address":"山东"},{"id":24,"name":"lisi24","age":18,"address":"山东"},{"id":26,"name":"lisi26","age":18,"address":"山东"},{"id":28,"name":"lisi28","age":18,"address":"山东"},{"id":30,"name":"lisi30","age":18,"address":"山东"},{"id":32,"name":"lisi32","age":18,"address":"山东"},{"id":34,"name":"lisi34","age":18,"address":"山东"},{"id":36,"name":"lisi36","age":18,"address":"山东"},{"id":38,"name":"lisi38","age":18,"address":"山东"},{"id":40,"name":"lisi40","age":18,"address":"山东"},{"id":42,"name":"lisi42","age":18,"address":"山东"},{"id":44,"name":"lisi44","age":18,"address":"山东"},{"id":46,"name":"lisi46","age":18,"address":"山东"},{"id":48,"name":"lisi48","age":18,"address":"山东"},{"id":50,"name":"lisi50","age":18,"address":"山东"},{"id":25,"name":"lisi25","age":18,"address":"山东"},{"id":27,"name":"lisi27","age":18,"address":"山东"},{"id":29,"name":"lisi29","age":18,"address":"山东"},{"id":31,"name":"lisi31","age":18,"address":"山东"},{"id":33,"name":"lisi33","age":18,"address":"山东"},{"id":35,"name":"lisi35","age":18,"address":"山东"},{"id":37,"name":"lisi37","age":18,"address":"山东"},{"id":39,"name":"lisi39","age":18,"address":"山东"},{"id":41,"name":"lisi41","age":18,"address":"山东"},{"id":43,"name":"lisi43","age":18,"address":"山东"},{"id":45,"name":"lisi45","age":18,"address":"山东"},{"id":47,"name":"lisi47","age":18,"address":"山东"},{"id":49,"name":"lisi49","age":18,"address":"山东"}]
Text
#读操作都走了从库
#从库日志
2023-03-09 04:49:04.072 EST,"sysdba","master1",1504,"192.168.100.5:44528",6409ab54.5e0,8,"SELECT",2023-03-09 04:48:04 EST,2/10,0,LOG,00000,"execute <unnamed>: SELECT *
FROM student
LIMIT 100",,,,,,,,,"PostgreSQL JDBC Driver"
2023-03-09 04:49:04.072 EST,"sysdba","master2",1502,"192.168.100.5:44524",6409ab54.5de,10,"SELECT",2023-03-09 04:48:04 EST,6/12,0,LOG,00000,"execute <unnamed>: SELECT *
FROM student
LIMIT 100",,,,,,,,,"PostgreSQL JDBC Driver"