MyCat1.6.7.1单库分表的真实使用案例详解

前言

MyCat官网:http://mycat.io/
安装包库:http://dl.mycat.io/
Linux版本:CentOS7
JDK版本:1.8.0,因为MyCat使用Java写的,所以需要安装JDK
Docker版本:Docker version 1.13.1, build b2f74b2/1.13.1
本文所使用的MyCat版本:mycat1.6.7.1

主流分表中间件

中间件 优势 劣势 推荐星
Cobar 阿里最早起的数据库中间件,现无人维护 无人维护导致使用成本高,难度较大 2
MyCat 基于Cobar 进行二次开发,采用代理模式来进行数据库分库分表,也是目前国内较为受欢迎的中间件,低侵入性,高性能 对于复杂的SQL不支持,只支持基本函数,不支持复杂join等,具体可参考官方文档。http://www.mycat.io/document/mycat-definitive-guide.pdf 5
ShardingJDBC Sharding-Sphere 1.X,是由当当的应用框架dd-frame中的dd-rdb模块演进而来,最终抽离并开源,是完全由当当孵化的开源产品,采用Apache 2.0协议,著作版权归 http://dangdang.com 所有。侵入性较高,可进行灵活的配置 使用难度系数大,如果是分布式系统需在每个服务中进行配置,配置难度高 3
ShardingProxy Sharding-Sphere 2.X,原理与MyCat 一样,进过使用测试,在单库分表能力上比MyCat优秀,社区也更活跃。 对一些MySQL类型不支持,如bit(可用int替代);存在乱码问题,但通过源码编译使用,也可得到解决,一般公司使用都会使用源码编译方式,便于修改问题。详细请参考:https://shardingsphere.apache.org/document/current/cn/manual/sharding-proxy/,本文不做介绍。 6
Sharding-Sidecar Sharding-Sphere 3.X,目前仍然在规划中,据官方介绍,定位为Kubernetes或Mesos的云原生数据库代理,以DaemonSet的形式代理所有对数据库的访问,功能十分强大。 全新的数据库中间件概念,面临着诸多挑战,需要大量的完善维护,不稳定。应用面不适合小打小闹的项目 6
OneProxy 前支付宝数据库团队领导楼总开发,C语言开发,支持连接池、故障切换、读写分离、分库分表、SQL防火墙、SQL监控等多项实用功能,主打高性能和稳定性。 是商业化中间件,收费。 5

单库分表需求说明

项目初期,用户量小于500w,具备下单、撮合、频繁Job查询类业务,单表性能无法保证系统可用性。因为项目已上线,如果进行分库分表,对代码改动大,且就目前用户量而言无需进行分库,分表即可保证查询速度。

写在前面的关于数据分库分表的一些建议

  1. 达到一定数量级才拆分(800万);
  2. 不到800万但跟大表(超800万的表)有关联查询的表也要拆分,在此称为大表关联表;
  3. 大表关联表如何拆:小于100万的使用全局表;大于100万小于800万跟大表使用同样的拆分策略;无法跟大表使用相同规则的,可以考虑从java代码上分步骤查询,不用关联查询,或者破例使用全局表;
  4. 真实案例】破例的全局表:如item_sku表250万,跟大表关联了,又无法跟大表使用相同拆分策略,也做成了全局表。破例的全局表必须满足的条件:没有太激烈的并发update,如多线程同时update同一条id=1的记录。虽有多线程update,但不是操作同一行记录的不在此列。多线程update全局表的同一行记录会死锁。批量insert没问题;
  5. 拆分字段是不可修改的;
  6. 拆分字段只能是一个字段,如果想按照两个字段拆分,必须新建一个冗余字段,冗余字段的值使用两个字段的值拼接而成(如大区+年月拼成zone_yyyymm字段);
  7. 拆分算法的选择和合理性评判:按照选定的算法拆分后每个库中单表不得超过800万;
  8. 能不拆的就尽量不拆。如果某个表不跟其他表关联查询,数据量又少,直接不拆分,使用单库即可。

更多建议请参考:http://www.zhongruitech.com/65556184.html,http://www.unjeep.com/article/18497.html (备用)

MyCat 单库分表支持说明

MyCat自1.6版本后支持单库分表,网传只有1.6版本支持单库分表不可信,博主亲测MyCat1.6.5、1.6.7.1、1.7-BETA、2.0-dev都支持单库分表,但又有略微区别,其中推荐使用1.6.7.1版本,是目前的稳定版本。

MyCat Linux安装之Tar包解压

Yum安装JDK8(如果你安装的JDK非1.8建议升级到1.8,之前版本无效卸载,改变环境变量配置即可)
1、查看1.8版本安装包列表

yum search java-1.8		

2、yum安装指定版本JDK

yum -y install java-1.8.0-openjdk-devel.x86_64

安装完成 路径一般在 /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.el7_4.x86_64
3、查看是否安装成功

[root@localhost jvm]# java -version
openjdk version "1.8.0_151"
OpenJDK Runtime Environment (build 1.8.0_151-b12)
OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)
[root@localhost jvm]# javac -version
javac 1.8.0_151

4、修改环境变量

扫描二维码关注公众号,回复: 10244391 查看本文章

配置环境变量 vim /etc/profile, 在文件末尾加上:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.151-1.b12.el7_4.x86_64
export PATH=$JAVA_HOME/bin:$PATH 
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

5、执行 . /etc/profile 使用配置生效
安装MyCat1.6.7.1
在这里插入图片描述
在官方我们可以看到MyCat详细的版本更新迭代日志,以及个版本下载链接。
在这里插入图片描述
通过wget拉取tar包,wget命令不存在可通过 yum install wget进行在线安装其模块!

wget http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz

然后解压安装包

tar -zxvf Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz

MyCat 安装之制作Docker 镜像

安装Docker环境,此处不做详细介绍,参考:https://www.runoob.com/docker/docker-tutorial.html

1、任意路径,创建文件夹mycat

mkdir /home/mycat

2、拉取mycat压缩包

wget http://dl.mycat.io/1.6.7.1/Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz

3、重命名

mv Mycat-server-1.6.7.1-release-20190213150257-linux.tar.gz mycat1.6.7.1.tar

4、编写Dockerfile

vim Dockerfile

Dockerfile内容:

FROM openjdk:8
ADD mycat1.6.7.1.tar.gz /usr/local/
VOLUME ["/usr/local/mycat/conf","/usr/local/mycat/logs"]
ENV MYCAT_HOME=/home/mycat
EXPOSE 8066 8066
RUN chmod -R 777 /usr/local/mycat/bin
CMD ["./usr/local/mycat/bin/mycat", "console"]

说明:
FROM:指定基础环境镜像为openjdk:8,
ADD:将mycat解压到/usr/local目录中,得到 /usr/local
VOLUME:配置暴露出的映射地址,之后可将这两个文件夹在启动时直接映射宿主机的文件夹
ENV:设置MYCAT_HOME
EXPOSE:暴露出MyCat的所需端口
RUN:执行运行时特殊指令,此处只改变了文件权限
CMD:以前台进程的方式启动MyCat服务

5、打包镜像【注意最后的那个点】

docker build -t mycat:1.6.7.1 . 

6、启动容器

docker run -p 8066:8066 -d --name mycat_node1 --net host -v /home/mycat/conf/:/usr/local/mycat/conf -v /home/mycat/logs/:/usr/local/mycat/logs/ mycat:1.6.7.1

7、查看是否启动成功

cat wrapper.log

8、提供一下备用命令

netstat -ntpl					-----查看出站端口

MyCat配置文件说明

MyCat核心配置文件server.xml、schema.xml、rule.xml,两大日志文件wrapper.log、mycat.log。

文件 作用说明
schema.xml 管理MyCat的逻辑库、表、分片规则的配置文件
rule.xml 配置MyCat分片路由策略
server.xml 配置中定义用户以及系统相关变量,如端口等。
wrapper.log logs目录下wrapper.log文件记录MyCat启动日志文件,移动成功、异常信息。
mycat.log logs目录下mycat.log文件记录执行的SQL日志信息,运行过程中SQL错误信息、分表后的SQL语句日志。
partition-hash-int.txt 配置枚举分表的映射关系(分表列值&表名索引)的文件,参考下文理解。
sequence_db_conf.properties 配置唯一性自增ID数据库实现方式的序列映射关系(序列名&数据节点名)的文件,参考下文理解。

三个配置文件的详细说明请参考:http://www.cnblogs.com/wuzhenzhao/p/10188973.html 一文。或者官方文档:http://www.mycat.io/document/mycat-definitive-guide.pdf

单库分表之取模分片算法

数据库表结构
分表为:t_user_00、t_user_01、t_user_02,非分表:t_stu、其余三表请无视。

DROP TABLE IF EXISTS `t_user_00`;
CREATE TABLE `t_user_00`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(11) NOT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

在这里插入图片描述
server.xml文件:

<mycat:server xmlns:mycat="http://io.mycat/">
    <system>
    			<!-- 配置MyCat ID自增方式,0 为本地文件方式,1 为数据库方式,2 为时间戳序列方式,3 为分布式 ZK ID 生成器,4 为 zk 递增 id 生成。 从 1.6 增加 两种 ZK 的全局 ID 生成算法。详细请参考:http://www.mycat.io/document/mycat-definitive-guide.pdf -->
                <property name="sequnceHandlerType">1</property>
     </system>
   <!-- 配置逻辑数据库的读写都可用的用户,如果你是使用的1.7以后版本,请去掉defaultAccount属性 -->
    <user name="root" defaultAccount="true">
        <property name="password">123456</property>
        <!-- 指定逻辑数据库-->
        <property name="schemas">mycat_testdb</property>
    </user>

    <!-- 只读用户 -->
    <user name="user">
        <property name="password">user</property>
        <property name="schemas">mycat_testdb</property>
        <!-- 是否只读,用于实现读写分离 -->
        <property name="readOnly">true</property>
    </user>
</mycat:server>

rule.xml文件:

<mycat:rule xmlns:mycat="http://io.mycat/">
    <!-- 定义一个表路由规则 -->
    <tableRule name="rule1">
          <rule>
          		<!-- id为真实分表字段,这里为路由字段 -->
                <columns>id</columns>
                <!-- 指定路由则规则所使用的算法 -->
                <algorithm>rang-mod</algorithm>
          </rule>
    </tableRule>
	<!-- 定义取模算法,name表示名称,class表示所使用的算法类 -->
    <function name="rang-mod" class="io.mycat.route.function.PartitionByMod">
          <!-- count表示分表的个数 -->
          <property name="count">3</property>
    </function>
</mycat:rule>

schema.xml文件

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
    <!-- name:是mycat的逻辑库名称,必须保证和server.xml文件中配置的schemas一致,链接需要用的
          checkSQLschema:是否检测SQL语句中的MyCat特殊语义,比如如果采用数据库方式实现自增ID,则SQL语句中ID字段的值需改成next value for MYCATSEQ_TEST,如果checkSQLschema为false则不会检测,按字段串处理,开启则会检测为MyCat语义进行ID生成。
     -->
    <schema name="mycat_testdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<!-- name:逻辑表名
			primaryKey:真实主键
			autoIncrement:是否开启主键自增
			dataNode:指定数据节点(绑定真实数据库)
			subTables:指定所有真实分表,t_user_0$0-2为缩写,最终会被解析成t_user_00,t_user_01,t_user_02
			role:指定路由策略
		-->
		<!--<table name="t_user"  primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule1"/> -->
		<table name="t_user"  primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_0$0-2" rule="rule1"/> 
	
		<!-- 以下为不进行分表的表配置 -->
		<table name="t_no_database" dataNode="dn1" />
		<table name="t_order_201708" dataNode="dn1" />
		<table name="t_order_201709" dataNode="dn1" />
		<table name="t_stu" dataNode="dn1" />
	</schema>
        <!-- database 是MySQL数据库的库名 -->
    <dataNode name="dn1" dataHost="localhost1" database="sharding_0" />
    <!--
    dataNode节点中各属性说明:
    name:指定逻辑数据节点名称;
    dataHost:指定逻辑数据节点物理主机节点名称;
    database:指定物理主机节点上。如果一个节点上有多个库,可使用表达式db$0-99,     表示指定0-99这100个数据库;

    dataHost 节点中各属性说明:
        name:物理主机节点名称;
        maxCon:指定物理主机服务最大支持1000个连接;
        minCon:指定物理主机服务最小保持10个连接;
        writeType:指定写入类型;
            0,只在writeHost节点写入;
            1,在所有节点都写入。慎重开启,多节点写入顺序为默认写入根据配置顺序,第一个挂掉切换另一个;
        dbType:指定数据库类型;
        dbDriver:指定数据库驱动;
        balance:指定物理主机服务的负载模式。
            0,不开启读写分离机制;
            1,全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡;
            2,所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡;
-->
    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <!-- 可以配置多个主从 -->
        <writeHost host="hostM1" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg">
            <!-- 可以配置多个从库 -->
            <readHost host="hostS2" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg" />
        </writeHost>
    </dataHost>
</mycat:schema>

关于1.7+版本defaultAccount属性报错(Attribute “defaultAccount” must be declared for element type “user”.):

tail -1000f logs/wrapper.log

在这里插入图片描述
正确启动:
在这里插入图片描述
测试取模分片算法
连接MyCat,,分别新增三条数据:

INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (1, 10001, '张三', 21, '2019-04-16 17:31:17');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (2, 10002, '李四', 22, '2019-04-17 17:32:27');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`) VALUES (3, 10003, '王五', 23, '2019-04-18 17:34:47');

在这里插入图片描述
结果发现,名称为“张三”、“李四”、“王五”的数据分别在t_user_01、t_user_02、t_user_00,按照取模算法,1%3=1,2%3=2,3%3=0,所以没问题。再进行查询测试,查询所有即可,如果能查询到三条语句则没问题。

单库分表之枚举分片算法【本文核心】

修改t_user_01、t_user_02、t_user_00表结构,新增city(城市)字段。server.xml文件不变。

DROP TABLE IF EXISTS `t_user_00`;
CREATE TABLE `t_user_00`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(11) NOT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `city` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 35 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

rule.xml文件【注意所有tableRule必须在function 之前 】:

<mycat:rule xmlns:mycat="http://io.mycat/">
    <!-- 定义一个表路由规则 -->
    <tableRule name="rule1">
          <rule>
          		<!-- name为真实分表字段,这里为路由字段 -->
                <columns>id</columns>
                <!-- 指定路由则规则所使用的算法 -->
                <algorithm>rang-mod</algorithm>
          </rule>
    </tableRule>
	
	<!-- 枚举规则 -->
	<tableRule name="rule2">
		<rule>
		   <columns>city</columns>
		   <algorithm>hash-int</algorithm>
		</rule>
    </tableRule>
	
	<!-- 定义取模算法,name表示名称,class表示所使用的算法类 -->
    <function name="rang-mod" class="io.mycat.route.function.PartitionByMod">
          <!-- count表示分表的个数 -->
          <property name="count">3</property>
    </function>
	
	<!-- 定义枚举算法,
		mapFile:指定枚举映射文件
		type:字段属性类型,默认值为0,0表示Integer,非零表示String
		defaultNode:指定默认节点,如果city值没有在partition-hash-int.txt中配置枚举映射,则会被映射到默认的节点上,
			其值从0开始,对应了schema.xml文件<table>的subTables属性值
	-->
    <function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
		<property name="mapFile">partition-hash-int.txt</property>
		<property name="type">1</property>
		<property name="defaultNode">1</property>
    </function>
</mycat:rule>

schema.xml文件:

<mycat:schema xmlns:mycat="http://io.mycat/">
    <!-- mycat_testdb 是mycat的逻辑库名称,必须保证和server.xml文件中配置的schemas一致,链接需要用的 -->
    <schema name="mycat_testdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
		<!-- name:逻辑表名
			primaryKey:真实主键
			autoIncrement:是否开启主键自增
			dataNode:指定数据节点(绑定真实数据库)
			subTables:指定所有真实分表,t_user_0$0-2为缩写,最终会被解析成t_user_00,t_user_01,t_user_02
			role:指定路由策略
		-->
		<table name="t_user"  primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule2"/> 
		<!--<table name="t_user"  primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_0$0-2" rule="rule2"/> -->
	
		<!-- 以下为不进行分表的表配置 -->
		<table name="t_no_database" dataNode="dn1" />
		<table name="t_order_201708" dataNode="dn1" />
		<table name="t_order_201709" dataNode="dn1" />
		<table name="t_stu" dataNode="dn1" />
	</schema>
        <!-- database 是MySQL数据库的库名 -->
    <dataNode name="dn1" dataHost="localhost1" database="sharding_0" />
	
    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <!-- 可以配置多个主从 -->
        <writeHost host="hostM1" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg">
            <!-- 可以配置多个从库 -->
            <readHost host="hostS2" url="192.168.1.186:3306" user="root" password="jxmQblOefYV839mg" />
        </writeHost>
    </dataHost>
</mycat:schema>

partition-hash-int.txt 配置
其中岳阳、深圳等为分表列值(应当包括所有可取值),0为MyCat subTables属性解析后得到的表名数组的索引值。岳阳映射0,0映射到subTables[0],最终将映射到表名t_user_00,所以必须保证无误!

岳阳=0
深圳=1
其他=2

分片枚举算法测试与原理分析

INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (1, 10001, '张三', 21, '2019-04-16 17:31:17','岳阳');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (2, 10002, '李四', 22, '2019-04-17 17:32:27','深圳');
INSERT INTO t_user(`id`, `user_id`, `name`, `age`, `create_time`,`city`) VALUES (3, 10003, '王五', 23, '2019-04-18 17:34:47','beijing');

我们新增三条数据,然后通过观察查询所有和根据city条件查询时,MyCat打印的日志分析【分片枚举算法】怎么进行的SQL优化,以及如何定位的分表:

select * from t_user;
select * from t_user where city = '岳阳';

查询mycat.log日志:

tail -1000f mycat.log

在这里插入图片描述在这里插入图片描述
通过对比两个SQL语句执行时日志,不难看出,当查询不带city条件时,会进行全分表查询,显而易见,MyCat会根据我们设定的city字段进行分表查询,那它又是如何定位分表的呢?下面我们来看一下枚举算法路由的过程:

select * from t_user where city = '岳阳';

首先MyCat启动时第一步会解析schema.xml文件中<table>标签的subTables属性(我们配置的是subTables=“t_user_00,t_user_01,t_user_02”)得到一个String数组[“t_user_00”,“t_user_01”,“t_user_02”];第二部根据配置的rule属性,判定采用枚举分片算法,从而会解析rule.xml和partition-hash-int.txt文件,生成一个Map集合(key:枚举值,value:数组索引);当执行上面的SQL语句时,MyCat根据rule.xml文件中配置的枚举策略的columns,解析到city字段,也就得到了“岳阳”这个值,根据这个值从Map中Get到数组索引,最后从String数组中获取到表名,然后重新组装SQL语句,调用JDBC连接进行查询。

关于The content of element type “mycat:rule” must match “(tableRule*,function*)”. 报错的说明:
MyCat的rule.xml配置文件里不支持tableRule/function/tableRule/function交叉的方式配置。只能是tableRule/function顺序配置。

单库分表之全局唯一性自增长ID

由于项目使用的分表字段为varchar类型,因此选择了采用枚举分表算法,还有一个重要的问题没有解决,那就是ID自增长问题和ID唯一性问题,那么到底如何解决呢?

其实,MyCat为我们提供了5种方式,各有优缺点:

生成方式 优点 缺点
本地文件 本地加载,读取速度较快 当 MyCAT 重新发布后,配置文件中的 sequence 会恢复到初始值,高并发量请求存在瓶颈。
数据库 可以实现预读到MyCat内存,速度较之本地文件方式更快。并且ID是趋势递增的 存在临界值并发问题
本地时间戳 速度快,使用简单,ID趋势递增 并发量高时,存在问题,分布式集群时不可用
ZK ID生成器 具备速度、稳定性、安全性的解决方案,能抗并发 ZK需要集群,实现复杂,需要花费额外成本维护Zookeeper。不支持ID递增
ZK 递增 同上+ID自增 同上

数据库方式原理

在数据库中建立一张表,存放 sequence 名称(name),sequence 当前值(current_value),步长(increment
int 类型每次读取多少个 sequence,假设为 K)等信息;

在这里说明一下临界值问题
MyCat数据库ID自增长方式,具备预读功能,基于上述数据库方式实现原理,每次读取数据库都会读取K个ID存入MyCat缓存中,当MyCat缓存中的ID使用到最后一个时,出现的并发情况则称为“临界值并发问题”。但非亿级流量(实际情况低于这个数值,具体需根据业务而论)难以满足出现此问题的条件。越复杂的业务,就需要越稳定的系统,就需要采用越复杂的技术实现,ZK才是王道。复杂唯一性自增ID方式可参考美团实现方案:https://tech.meituan.com/2019/03/07/open-source-project-leaf.html

数据库方式实现以及遇到的坑
之前说过公司网站流量并不高,因此经过对比采用了数据库方式实现。其他四种方式可参考MyCat官方进行使用:http://www.mycat.io/document/mycat-definitive-guide.pdf

1、首先server.xml文件新增sequnceHandlerType配置:

    <system>
		<property name="sequnceHandlerType">1</property>
	</system>

2、创建mycat_sequence表

DROP TABLE IF EXISTS `mycat_sequence`;
CREATE TABLE `mycat_sequence`  (
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'sequence名称',
  `current_value` int(11) NOT NULL COMMENT '当前 value',
  `increment` int(11) NOT NULL DEFAULT 100 COMMENT '增长步长! mycat 在数据库中一次读取多少个 sequence',
  `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '存放 sequence 的表' ROW_FORMAT = Dynamic;

新增数据

INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('GLOBAL', 1, 100, 'MyCat默认');
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('COMPANY', 1, 100, NULL);
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('CUSTOMER', 1, 100, NULL);
INSERT INTO `mycat_sequence`(`name`, `current_value`, `increment`, `remarks`) VALUES ('ORDERS', 1, 100, NULL);

3、创建相关函数(三个)
这里应该使用项目中jdbc连接配置登陆的用户进行函数创建,此用户一般为非root用户。
在这里插入图片描述

  • 用于获取当前 sequence 值的函数
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_currval`(`seq_name` varchar(50)) RETURNS varchar(64) CHARSET utf8
    DETERMINISTIC
BEGIN
	#– 获取当前 sequence 的值 (返回当前值,增量)
	DECLARE retval VARCHAR(64);
	SET retval="-999999999,null";
	SELECT concat(CAST(current_value AS CHAR),",",CAST(increment AS CHAR)) INTO retval FROM
     MYCAT_SEQUENCE WHERE name = seq_name;
	RETURN retval;
END
  • 获取下一个 sequence 值
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_nextval`(`seq_name` varchar(50)) RETURNS varchar(64) CHARSET utf8
    DETERMINISTIC
BEGIN
	# 获取下一个 sequence 值
		UPDATE MYCAT_SEQUENCE SET current_value = current_value + increment
		WHERE NAME = seq_name;
		RETURN mycat_seq_currval(seq_name);
END
  • 设置 sequence 值
CREATE DEFINER=`root`@`%` FUNCTION `mycat_seq_setval`(`seq_name` varchar(50),`value` integer) RETURNS varchar(64) CHARSET utf8
    DETERMINISTIC
BEGIN
	#设置 sequence 值
  UPDATE MYCAT_SEQUENCE
		SET current_value = value
	WHERE name = seq_name;
	RETURN mycat_seq_currval(seq_name);
END

4、配置sequence_db_conf.properties
其中GLOBAL、COMPANY都是定义的列名,必须保证与mycat_sequence表name名称一致,dn1为schema.xml中的<dataNode>的name。

#sequence stored in datanode
GLOBAL=dn1
COMPANY=dn1
CUSTOMER=dn1
ORDERS=dn1

5、使用数据库方式后的insert语句

INSERT INTO  `t_user_00`(`id`, `user_id`, `name`, `age`, `create_time`, `city`) VALUES (next value for MYCATSEQ_GLOBAL, 10001, '张三', 21, '2019-04-16 17:31:17', '岳阳');

next value for MYCATSEQ_GLOBAL表示为使用 名称为 GLOBAL的ID序列进行新增,对于不同表需要创建不同的序列,以用来防止ID冲突,保证唯一性。定义不同的序列只需要在mycat_sequence表添加一条数据,并在sequence_db_conf.properties配置文件中添加一个映射即可。

注意

  • sequence_db_conf.properties文件中的值必须保证与mycat_sequence表中的name一致,且不能存在特殊字符,如“-”,“_”之类。
  • schema.xml文件中<table>标签必须具备primaryKey属性,且值必须为自增列名,如果需要得到自增后的ID则需要开启autoIncrement属性。敲黑板,划重点,primaryKey属性对应的MySQL数据列一定得是标有AUTO_INCREMENT的列,不然任会获取不到自增后的ID,原因是MyCat通过SELECT LAST_INSERT_ID();获取,而LAST_INSERT_ID();函数依赖于AUTO_INCREMENT标识。
<table name="t_user"  primaryKey="id" autoIncrement="true" dataNode="dn1" subTables="t_user_00,t_user_01,t_user_02" rule="rule2"/> 
<table name="t_no_database" dataNode="dn1" />
  • 没有用到自增列属性的表则需要去掉primaryKey和autoIncrement属性,可在一定程度上提升性能
  • schema.xml中的<schema>属性checkSQLschema必须设为false,设置为true表示不处理MyCat schema设定字符,将忽略SQL中的next value for MYCATSEQ_GLOBAL不进行处理。

单库分表踩过的坑总结

1、分表之间不能进行子查询、关联查询、in查询,分表可与非分表间进行简单的子查询、in查询、关联查询。

2、不要在schema 标签中添加 primaryKey=“ID” autoIncrement=“true” 否则会插入很慢

3、自增ID采用数据库实现方案时,新增需将自增列的值改为 next value for MYCATSEQ_对应的sequence,需添加是哪个指定的函数,需配置sequence_db_conf.properties

4、查询排序时,order by所使用到的排序字段必须在select中出现,否则将会报错:all columns in order by clause should be in the selected column list!createTime

5、MyCat的一些自带函数 sum,min,max,count等可以正确使用,但多分片执行的avg有bug,执行的结果是错误的

6、复杂删除 delete a from a join b on a.id=b.id; 支持子查询方式 delete from a where a.id in (select id from b), 但表不能起别名

7、在进行单表操作时,不能对表取别名,应遵循标准语法,否则MyCat在进行SQL的截取时会出现异常。

8、新增、修改mycat_sequence表数据后需要重启MyCat

9、<table>的primaryKey="id"指定主键,autoIncrement="true"开启新增返回自增主键,如非必要请勿开启,会影响性能

10、MyCat 对于表名大小写敏感,因此需注意MyCat配置中的表名与数据库表名必须统一大小写【schema.xml subTables属性】

11、对于第10点的补充,MyCat1.7版本,使用分片枚举进行单库分表【新增】时,如果分表字段未被匹配到对应的表名(注意是区分大小写的匹配),则会根据partition-hash-int.txt文件配置中的值为1的表中

12、(这个坑还得博主我花费了来调试,主要是问题产生于多线程中,且异常信息不明确)MyCat对 修改SQL语句中不支持 BigDecimal类型的0E-8【MathUtil.accuracyRoundDown工具类使用导致】(原值为0.00000000小数点后面8个0)遇到的时候加个判断,不然会出现mysql lock锁超时,当将timeout调整为>300s时会出现,MyCat事务不提交问题(MyCat默认数据库连接300s超时)

mycat.log日志输出异常:found backend connection SQL timeout ,close it MySQLConnection,表现症状为,数据库lock锁超时:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,尝试调整MySQL超时时间,修改my.cnf文件添加innodb_lock_wait_timeout=500,配置等待锁超时时间为500秒,
此时出现回滚异常,异常信息提示说:连接超时,系统尝试回滚时,连接已被断开!这是MyCat的异常,MyCat默认一个数据库连接的超时时间为300s,超过就会断开连接。

补充两条WebSocket需具备的机制

1、WebSocket前后端需具备心跳消息重连机制

2、客户端心跳,用于客户端重连;服务端心跳,用于维护session集合,无响应的客户端提出;默认三次,超过就踢出。

总结

根据目前业务需求,我们选择了枚举算法,基于在原有的代码改动最小的基础上优化交易所撮合性能,ShardingJDBC侵入性太强,对原有代码改动大,最重要的是使用复杂,配置复杂,成本高,而MyCat代理的实现方式更符合要求。至于为什么选择枚举模式,首要原因是分表字段是varchar类型。

发布了17 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_38652136/article/details/90374535