分表分库原则
分表分库虽然能解决大表对数据库系统的压力,但它并不是万能的,也有一些不利之处,因此首要问题是分不分库,分哪些库,什么规则分,分多少分片,一般而言有以下原则:
- 能不分就不分,1000万以内的表,不建议分片,通过合适的索引,读写分离等方式,可以很好的解决性能问题。
- 分片数量尽量少,分片尽量均匀分布在多个DataHost上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量。
- 分片规则需要慎重选择,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,常用的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容。
- 尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题。
- 查询条件尽量优化,尽量避免select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。
总体上来说,分片的选择是取决于频繁的查询SQL 的条件,因为不带任何Where 语句的查询 SQL,会便利所有的分片,性能相对差,因此这种SQL 越多,对系统的影响越大,所以我们要尽量避免这种SQL 的产生。
ER表
Mycat 中的ER 表是基于E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,保证数据Join 不会跨库操作。
ER分片是解决跨分片数据join 的一种很好的思路,也是数据切分规划的一条重要规则。
<table name="customer" primaryKey="customer_id" dataNode="dn1,dn2" rule="shardingby-intfile">
<childTable name="orders" primaryKey="order_id" joinKey="customer_id" parentKey="customer_id" />
</table>
全局表
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动。
业务表往往需要和字典表Join查询,当业务表因为规模而进行分片以后,业务表与字典表之间的关联跨库了,在Mycat中通过表冗余来解决这类表的join,即它的定义中指定的dataNode上都有一份该表的拷贝(即全局表)
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
分片枚举
通过在配置文件中配置可能的枚举id,自己配置分片,本规则适用于特定的场景,比如有些业务需要按照省份或区县来做保存,而全国省份区县固定的,这类业务使用本条规则,配置如下:
<tableRule name="sharding-by-enum">
<rule>
<columns>id</columns>
<algorithm>enum_func</algorithm>
</rule>
</tableRule>
<function name="enum_func" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">sharding-by-enum.txt</property>
<property name="type">0</property>
<property name="defaultNode">0</property>
</function>
- 算法实现类为:io.mycat.route.function.PartitionByFileMap
- mapFile 标识配置文件名称;
- type 默认值为0,0 表示Integer,非零表示String;
- defaultNode defaultNode 默认节点:小于0 表示不设置默认节点,大于等于0 表示设置默 认节点为第几个数据节点。 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点如果不配置默认节点(defaultNode 值小于0 表示不配置默认节点),碰到不识别的枚举值就会报错。
sharding-by-enum.txt 放置在conf/下,配置内容示例
#字段值为10000的放到0号数据节点
10000=0
10010=1
范围分片
<tableRule name="range-sharding">
<rule>
<columns>id</columns>
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">range-partition.txt</property>
<property name="defaultNode">0</property>
</function>
mapFile 代表配置文件,在conf目录下
defaultNode 超过范围后的默认节点。所有的节点配置都是从0 开始,及0 代表节点1。
mapFile中的定义示例
0-500M=0
500M-1000M=1
1000M-1500M=2
或者
0-10000000=0
10000001-20000000=1
按日期范围分片
<tableRule name="sharding-by-date">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-date</algorithm>
</rule>
</tableRule>
<function name="sharding-by-date" class="io.mycat.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2020-01-01</property>
<property name="sEndDate">2021-01-02</property>
<property name="sPartionDay">10</property>
</function>
columns :标识将要分片的表字段
algorithm :分片函数
dateFormat :日期格式
sBeginDate :开始日期
sEndDate:结束日期
sPartionDay :分区天数,即默认从开始日期算起,分隔10 天一个分区
注意:
当sBeginDate,sEndDate 都有指定时,此时表的dataNode 数量的>=这个时间段算出的分片数,否则启动时会异常。如果配置了sEndDate 则代表数据达到了这个日期的分片后循环从开始分片插入
没有指定 sEndDate 的情况 数据分片将依次存储到dataNode上,数据分片随时间增长,所需的dataNode数也随之增 长,当超出了为该表配置的dataNode数时,也会出现异常。
自然月分片
按月份列分区,每个自然月一个分片
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-month</algorithm>
</rule>
</tableRule>
<function name="sharding-by-month" class="io.mycat.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2014-01-01</property>
</function>
columns: 分片字段,字符串类型
dateFormat : 日期字符串格式,默认为yyyy-MM-dd
sBeginDate : 开始日期,无默认值
sEndDate:结束日期,无默认值
节点从0 开始分片
注意:
不指定sBeginDate、sEndDate时节点数量必须是12 个,对应1 月~12 月
仅指定sBeginDate时从该时间按月递增,无最大节点
取模分片
此规则为对分片字段进行十进制运算,来分片数据
<tableRule name="mod-sharding">
<rule>
<columns>id</columns>
<algorithm>mod-fun</algorithm>
</rule>
</tableRule>
<function name="mod-fun" class="io.mycat.route.function.PartitionByMod">
<property name="count">3</property>
</function>
count 指明dataNode 的数量,是求模的基数
取模范围分片
此种规则是取模运算与范围约束的结合,主要为了后续数据迁移做准备,即可以自主决定取模 后数据的节点 分布
<tableRule name="sharding-by-pattern">
<rule>
<columns>id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
<property name="patternValue">256</property>
<property name="defaultNode">2</property>
<property name="mapFile">partition-pattern.txt</property> </function>
partition-pattern.txt
#余数为1-32的放到数据节点0上
1-32=0
33-64=1
65-96=2
97-128=3
129-160=4
161-192=5
193-224=6
225-256=7
范围取模分片
先进行范围分片计算出分片组,组内再求模。
优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题。综合了范围分片和求模分片的优点,分片组内使用求模可以保证组内数据比较均匀,分片组之间是范围分片 可以兼顾范围查询。
好事先规划好分片的数量,数据扩容时按分片组扩容,则原有分片组的数据不需要迁移。由 于分片组内数据比 较均匀,所以分片组内可以避免热点数据问题。
<tableRule name="auto-sharding-rang-mod">
<rule>
<columns>id</columns>
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>
<function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property> <property name="defaultNode">21</property>
</function>
partition-range-mod.txt 以下配置一个范围代表一个分片组,=号后面的数字代表该分片组所拥有的分片的数量
//代表有5个分片节点
0-200M=5
200M1-400M=1
400M1-600M=4
600M1-800M=4
800M1-1000M=6
一致性hash
一致性hash 算法有效解决了分布式数据的扩容问题
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<!-- 默认是0-->
<property name="seed">0</property>
<!-- 要分片的数据库节点数量,必须指定,否则没法分片-->
<property name="count">2</property>
<!-- 一个实际的数据库节点被映射为多少个虚拟节点,默认是160 -->
<property name="virtualBucketTimes">160</property>
<!--
<property name="weightMapFile">weightMapFile</property>
节点的权重,没有指定权重的节点默认是1。以properties 文件的格式填写,以从0 开始到 count-1 的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1 代替
-->
<!--
<property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur hash 值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西
-->
</function>