数据库的切分
垂直切分
垂直切分是指安装业务将表进行分类,分布到不同的数据库上面,这样就将数据或着说压力分担到不同库上面。
一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能模块相互之间的交互点约统一数量就越少,系统的耦合度就越低,系统各个模块的维护以及扩展性也就越好。这样的系统,实现数据的垂直切分也就越容易。
一般来讲业务存在着复杂join的场景是难以切分的,往往业务独立的易于切分。如何切分,切分到何种程度是考验技术架构的一个难题。
由于垂直切分是安装业务分类将表分散到不同的库,所以有些业务表会过于庞大,存在单表读写与存储瓶颈,所以就需要水平拆分来解决。
下面来分析下垂直切分的优缺点:
优点:
- 拆分后业务清晰,拆分规则明确。
- 系统之间整合或扩展容易。
- 数据维护简单。
缺点:
- 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度。
- 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
- 事务处理复杂。
水平切分
水平切分不是将表做分类,而是安装某个字段的某种规则来分散到多个库中,每个表包含一部分数据。简单来说,我们可以将数据水平切分理解为是按照数据行的切分,就是将表中的某些行切分到一个数据库中,而另外一些行又切分到其他数据库中。
拆分数据就需要定义分片规则。关系型数据库是行列的二维模型,拆分的第一原则是找到拆分维度。比如:从会员的角度来分析,商户订单交易类系统中查询会员某天某月某个订单,那么就需要按照会员结合日期来拆分,不同的数据按照会员 ID做分组,这样所有的数据查询 join 都会在单库内解决;如果从商户的角度来讲,要查询某个商家某天所有的订单数,就需要按照商户ID做拆分;但是如果系统既想按会员拆分,又想按商家数据,则会有一定的困难。如何找到合适的分片规则需要综合考虑衡量。
几种典型的分片规则包括:
- 按照用户ID求模,将数据分散到不同的数据库,具有相同用户的数据被分散到一个库中。
- 按照日期,将不同月设置日的数据分散到不同的库中。
- 按照某个特定的字段求模,或者根据特定范围段分散到不同的库中。
数据拆分的优缺点:
优点:
- 拆分规则抽象好,join操作基本可以让数据库做
- 不存在单库大数据,高并发的性能瓶颈
- 应用端改造较少
- 提高了系统的稳定性跟负载能力
缺点:
- 拆分规则难以抽象
- 分片事务一致性难以解决
- 数据多次扩展难度跟维护量极大
- 跨库join性能较差
垂直切分和水平切分共同的缺点:
- 引入分布式事务的问题
- 跨节点join问题
- 跨节点合并排序分页问题
- 多数据源管理问题
针对数据源管理,目前主要有两种思路:
- 客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个数据库,在模块内完成数据的整合;
- 通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明;
Mycat由于数据切分后数据Join的难度,下面是对数据切分的经验原则:
- 第一原则:能不切分尽量不要切分。
- 第二原则:如果要切分一定要选择合适的切分规则,提前规划好。
- 第三原则:数据切分尽量通过数据冗余或表分组(Table Group)来降低跨库Join的可能。
- 第四原则:由于数据库中间件对数据Join实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 Join。
mycat的应用场景
- 单纯的读写分离,此时配置最为简单,支持读写分离,主从切换。
- 分表分库,对于超过1000万的表进行分片,最大支持1000亿单表分片。
- 多租户应用,每个应用一个库,但应用程序只连接mycat,从而不改造程序本身,实现多租户化。
- 报表系统,借助于Mycat的分表能力,处理大规模报表统计。
- 提单hbase,分析大数据
- 作为海量数据实时查询的一种简单有效方案。比如100亿条频繁查询记录需要在3秒内查询出结果,除了基于主键哦查询,还可能存在范围查询或其他属性查询,此时mycat可能就是最简单有效的选择。
相关概念
数据库中间件
Mycat 是数据库中间件,就是介于数据库与应用之间,进行数据处理与交互的中间服务。由于前面讲的对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构成了整个完整的数据库存储。
逻辑库(Schema)
通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。
逻辑表(table)
逻辑表
在分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表结构。
分片表
分片表,是指那些原有的很大数据的表,需要切分到多个数据库表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。例如在 mycat 配置中的 t_node 就属于分片表,数据按照规则被分到 dn1,dn2 两个分片节点(dataNode)上。
<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1,dn2" rule="rule1" />
非分片表
一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片表是相对分片表来说的,就是那些不需要进行数据切分的表。如下配置中 t_node,只存在于分片节点( dataNode) dn1 上。
<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1" />
ER表
关系型数据库是基于实体关系模型(Entity-Relationship Model)之上,通过其描述了真实世界中事物与关系,Mycat中的ER表计时来源于此。根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(table group)保证数据join不会跨库操作。表分组(table group)是解决跨库分片数据join的一种很好思路,也是数据切分规则的重要一条。
对于可以抽象出父子关系的表,都适用于ER分片表,子表的记录与所关联的父表记录存放在同一个数据分片上,避免数据的Join跨库操作。
全局表
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几个特性:
- 变动不频繁
- 数据量总体变化不大
- 数据规模不大,很少有超过数十万条记录
对于这类表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属字典表之间的关联,就成了比较棘手的问题。所以mycat中通过数据冗余来解决这类表的join,即所有的分片都有一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。数据冗余是解决跨分片数据join的一种很好的思路,也是数据切分规则的另外一条重要规则。
Mycat全局表的特性:
- 全局表的插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
- 全局表的查询操作,只从一个节点获取
- 全局表可以跟任何一个表进行 JOIN 操作
全局表配置比较简单,不用写 Rule 规则,如下配置即可:
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
需要注意的是,全局表每个分片节点上都要有运行创建表的 DDL 语句。
分片节点(dataNode)
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。
节点主机(dataHost)
数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一台机器上面可以有多个分片数据库,这样一个多多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。
分片规则(rule)
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则八数据分到某个分片的规则就是分片规则,数据切分选择适合的分片规则非常重要,将极大的避免后续数据处理的难度。
全局序列号(sequence)
数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制来保证数据唯一标识,这种保证全局性的数据唯一标识的机制就是全局序列号(sequence)。
多租户
多租户技术或称为多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保个用户间数据的隔离性。在云计算时代,多租户技术在共用的数据中心以单一系统架构与服务提供多数客户端相同设置可定制化的服务,并且仍然可以保障客户的数据隔离。目前各种各样的云计算服务就是这种技术范畴,例如阿里的云数据库服务(RDS)、阿里的云服务器的等等。多租户在数据存储上存在三种主要的方案,分别是:
1. 独立数据库
这是第一种方案,即一个租户一个数据库,这种方案的用户数据隔离级别最高,安全性最好,但成本也高。
优点:
- 为不同的租户提供独立的数据库,有助于简化数据模型的扩展设计,满足不同租户的独特需求;
- 如果出现故障,恢复数据比较简单。
缺点:
- 增大了数据库的安装数量,随之带来维护成本和购置成本的增加。
- 这种方案与传统的一个客户、一套数据、一套部署类似,差别只在于软件统一部署在运营商那里。如果面对的是银行、医院等需要非常高数据隔离级别的租户,可以选择这种模式,提高租用的定价。如果定价较低,产品走低价路线,这种方案一般对运营商来说是无法承受的。
2. 共享数据库,隔离数据架构
这是第二种方案,即多个或所有租户共享 Database,但是每个租户一个 Schema。
优点:
- 为安全性要求较高的租户提供了一定程度的逻辑数据隔离,并不是完全隔离;每个数据库可以支持更多的租户数量。
缺点:
- 如果出现故障,数据恢复比较困难,因为恢复数据库将牵扯到其他租户的数据;
- 如果需要跨租户统计数据,存在一定困难。
3. 共享数据库,共享数据架构
这是第三种方案,即租户共享同一个Database、同一个Schema,但在表中通过TenantID区分租户的数据。这是共享程度最高、隔离级别最低的模式。
优点:
- 三种方案比较,第三种方案的维护和购置成本最低,允许每个数据库支持的租户数量最多。
缺点:
- 隔离级别最低,安全性最低,需要在设计开发时加大对安全的开发量;
- 数据备份和恢复最困难,需要逐表逐条备份和还原。
- 如果希望以最少的服务器为最多的租户提供服务,并且租户接受以牺牲隔离级别换取降低成本,这种方案最适合。
Mycat的分片join
1. ER join
MyCAT 借鉴了 NewSQL 领域的新秀 Foundation DB 的设计思路, Foundation DB 创新性的提出了 Table Group 的概念,其将子表的存储位置依赖于主表,并且物理上紧邻存放,因此彻底解决了 JION 的效率和性能问题,根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上。
customer采用sharding-by-intfile这个分片策略,分片在dn1,dn2上, orders依赖父表进行分片,两个表的关联关系为orders.customer_id=customer.id
。这样一来,分片Dn1上的的customer与Dn1上的orders就可以进行局部的JOIN联合,Dn2上也如此,再合并两个节点的数据即可完成整体的 JOIN,试想一下,每个分片上 orders表有100万条,则10个分片就有1个亿,基于E-R映射的数据分片模式,基本上解决了80%以上的企业应用所面临的问题。
以上述例子为例, schema.xml 中定义如下的分片配置:
<table name="customer" dataNode="dn1,dn2" rule="sharding-by-intfile">
<childTable name="orders" joinKey="customer_id" parentKey="id"/>
</table>
2. Share join
ShareJoin是一个简单的跨分片Join,基于HBT的方式实现。目前支持2个表的 join,原理就是解析SQL语句,拆分成单表的SQL语句执行,然后把各个节点的数据汇集。
支持任意配置的 A,B 表如:
- A,B 的 dataNode 相同
<table name="A" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
- A,B 的 dataNode 不同
<table name="A" dataNode="dn1,dn2 " rule="auto-sharding-long" />
<table name="B" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
或者
<table name="A" dataNode="dn1 " rule="auto-sharding-long" />
<table name="B" dataNode=" dn2,dn3" rule="auto-sharding-long" />
3. catlet(人工智能)
解决跨分片的 SQL JOIN 的问题,远比想象的复杂,而且往往无法实现高效的处理,既然如此,就依靠人工的智力,去编程解决业务系统中特定几个必须跨分片的 SQL 的 JOIN 逻辑, MyCAT 提供特定的 API 供程序员调用,这就是 MyCAT 创新性的思路——人工智能。
Mycat对多租户的支持
单租户就是传统的给每个租户独立部署一套web+db。由于租户越来越多,整个web部分的机器和运维成本都非常高,因此需要改进为所有租户共享一套web的模式(db部分暂不改变)。
基于此需求,Mycat对单租户的程序做了简单的改造实现web多租户共享。具体改造如下:
1. web部分修改
- 在用户登录时,在线程变量(ThreadLocal)中记录租户的ID
- 修改jdbc的实现,在提交sql时,从ThreadLocal中获取租户的ID,添加到sql注释,把租户的schema放到注释中。例如:/*!mycat : schema = test_01 */ sql ;
2. 在db前面建立proxy层,代理所有web过来的数据库请求。proxy层是用mycat实现的,web提交的sql过来时在注释中指定schema,proxy层根据指定的schema转发sql请求。
3. Mycat的配置
<user name="mycat">
<property name="password">mycat</property>
<property name="schemas">order</property>
<property name="readOnly">true</property>
</user>
<user name="mycat2">
<property name="password">mycat</property>
<property name="schemas">order</property>
</user>
Mycat分片规则
在数据切分处理中,特别是水平切分中,中间件最重要的两个处理过程就是数据的切分和数据的聚合。选择合适的切分规则,至关重要。因为切分规则决定了后续数据聚合的难以程度,设置可以避免跨库的数据集合处理。前面提到的数据切分规则中的,数据冗余,表分组(table group)都是在业务上规避跨库join的很好方式,但不是所有业务场景都适用。
对于多对多关联,目前总的原则是需要从业务角度来看,关系表更偏向那个表,即“A的关系”还是“B的关系”,来决定关系表跟从那个方向存储,未来Mycat版本中将考虑将中间表进行双向复制,以实现从A-关系表 以及 B-关系表的双向关联查询。
主键分片和非主键分片,当你没有任何字段可以作为分片字段的时候,主键分片就是唯一选择,其优点是按照主键的查询最快,当采用自动增长的序列号作为主键时,还能比较均匀的将数据分片在不同的节点上。若有某个合适的业务字段比较适合做为分片字段,则建议采用此业务字段分片。选择分片字段的条件如下:
- 尽可能的比较均匀地分布数据到各个节点上
- 该业务字段是使用最频繁或最重要的查询条件
Mycat提供了“主键到分片”的内存缓存机制,热点数据按照主键查询,丝毫不损失性能。
<table name="t_user" primaryKey="user_id" dataNode="dn$1 -32" rule="mod-long">
<childTable name="t_user_detail" primaryKey="id" joinKey="user_id" parentKey="user_id" />
</table>
对于非主键分片的table,填写属性primaryKey,此时Mycat会将你根据主键查询的SQL语句的第一次执行结果进行分析,确定该table的某个业务主键在什么分片上,并进行主键到分片ID的缓存。第二次或后续查询mycat会优先从缓存中查询是否有id->node即主键到分片的映射;如果有直接查询,通过此种方法提高了非主键分片的查询性能。
如何去分片和如何选择合适分片规则的最重要原则就是——尽量避免跨库join。
分片枚举
通过在配置文件中配置可能的枚举ID,自己配置分片,本规则适用于特定场景;比如有些业务需要安装省份或区县来保存,而全国省份区县是固定的,这类业务使用本条规则。
<tableRule name="sharding-by-intfile">
<rule>
<columns>user_id</columns><!-- 分片表字段 -->
<algorithm>hash-int</algorithm><!-- 分片函数 -->
</rule>
</tableRule>
<function name="hash-int" class="org.opencloudb.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property><!-- 标识配置文件名称 -->
<property name="type">0</property><!-- 默认值为0,0表示Integer,非零表示String -->
<property name="defaultNode">0</property><!-- 所有的节点配置都是从0开始,即0代表节点1 -->
</function>
partition-hash-int.txt 配置:
10000=0
10010=1
DEFAULT_NODE=1
/**
* defaultNode 默认节点:小于 0 表示不设置默认节点,大于等于 0 表示设置默认节点
* 默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点
* 如果不配置默认节点( defaultNode 值小于 0 表示不配置默认节点),碰到
* 不识别的枚举值就会报错,
* like this: can’ t find datanode for sharding column:column_name val:ffffffff
*/
固定分片hash算法
本条规则类似于十进制的求模运算,区别在于是二进制的操作,是去ID的二进制低10位,即ID的二进制&1111111111。此算法的优点在于如果按照10进制取模运算,在练习插入1-10的时候1-10会被分到1-10的分片,增加了插入的事务控制难度,而此算法根据二进制则可能会分到连续的分片,减少了插入事务的控制难度。
<tableRule name="rule1">
<rule>
<columns>user_id</columns><!-- 分片表字段 -->
<algorithm>func1</algorithm><!-- 分片算法 -->
</rule>
</tableRule>
<function name="func1" class="org.opencloudb.route.function.PartitionByLong">
<property name="partitionCount">2,1</property><!-- 分片个数列表 -->
<property name="partitionLength">256,512</property><!-- 分片范围列表分区长度,最大分区数2^10=1024 -->
</function>
约束:count,length两个数组的长度必须是一致的。1024=sum((count[i]*length[i])). count和length两个向量的点积恒等于1024
范围约定
此分片适用于,提前规划好分片字段的某个范围属于哪个分片,
start<=range<=end.
range start-end,data node index
K=1000,M=10000.
<tableRule name="auto-sharding-long">
<rule>
<columns>user_id</columns><!-- 分片字段 -->
<algorithm>rang-long</algorithm><!-- 分片函数 -->
</rule>
</tableRule>
<function name="rang-long" class="org.opencloudb.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property><!-- 配置文件路径 -->
<property name="defaultNode">0</property><!-- 超过范围后的默认节点. -->
</function>
所有的节点配置都是从 0 开始,及 0 代表节点 1 ,此配置非常简单,即预先制定可能的 id 范围到某个分片
0-500M=0
500M-1000M=1
1000M-1500M=2
或
0-10000000=0
10000001 -20000000=1
取模
此规则对分片字段求模运算
<tableRule name="mod-long">
<rule>
<columns>user_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="org.opencloudb.route.function.PartitionByMod">
<! -- how many data nodes -->
<property name="count">3</property>
</function>
此种配置非常明确即根据id进行十进制求模预算,相比固定分片hash,此种在批量插入时可能存在批量插入单事务插入多数据分片,增大事务一致性难度。
按日期(天)分片
此规则为按天分片
<tableRule name="sharding-by-date">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-date</algorithm>
</rule>
</tableRule>
<function name="sharding-by-date"
class="org.opencloudb.route.function.PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2014 -01-01</property>
<property name="sEndDate">2014 -01-02</property>
<property name="sPartionDay">10</property>
</function>
配置说明:
columns :标识将要分片的表字段
algorithm :分片函数
dateFormat :日期格式
sBeginDate :开始日期
sEndDate:结束日期
sPartionDay :分区天数,即默认从开始日期算起,分隔 10 天一个分区
如果配置了 sEndDate 则代表数据达到了这个日期的分片后后循环从开始分片插
取模范围约束
此种规则是取模运算与范围约束的结合,主要为了后续数据迁移做准备,即可以自主决定取模后数据的节点分布。
<tableRule name="sharding-by-pattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="org.opencloudb.route.function.PartitionByPattern">
<property name="patternValue">256</property>
<property name="defaultNode">2</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
partition-pattern.txt
# id partition range start-end ,data node index
###### first host configuration
1-32=0
33-64=1
65-96=2
97-128=3
######## second host configuration
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7
配置说明:
上面 columns 标识将要分片的表字段, algorithm 分片函数, patternValue 即求模基数, defaoultNode默认节点,如果配置了默认,则不会按照求模运算
mapFile 配置文件路径
配置文件中, 1 -32 即代表 id%256 后分布的范围,如果在 1 -32 则在分区 1 ,其他类推,如果 id 非数据,则
会分配在 defaoultNode 默认节点
String idVal = “0” ;
Assert.assertEquals(true, 7 == autoPartition.calculate(idVal));
idVal = “45a” ;
Assert.assertEquals(true, 2 == autoPartition.calculate(idVal));
截取数字做hash求模范围约束
此种规则类似于取模范围约束,此规则支持数据符号字母取模。
<tableRule name="sharding-by-prefixpattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-prefixpattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="org.opencloudb.route.function.PartitionByPrefixPattern">
<property name="patternValue">256</property>
<property name="prefixLength">5</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
partition-pattern.txt
# range start-end ,data node index
# ASCII
# 8-57=0-9 阿拉伯数字
# 64、 65-90=@、 A-Z
# 97-122=a-z
###### first host configuration
1-4=0
5-8=1
9-12=2
13-16=3
###### second host configuration
17-20=4
21-24=5
25-28=6
29-32=7
0-0=7
配置说明:
上面 columns 标识将要分片的表字段, algorithm 分片函数, patternValue 即求模基数, prefixLength ASCII截取的位数
mapFile 配置文件路径
配置文件中, 1 -32 即代表 id%256 后分布的范围,如果在 1 -32 则在分区 1 ,其他类推
此种方式类似方式 6 只不过采取的是将列种获取前 prefixLength 位列所有 ASCII 码的和进行求模sum%patternValue ,获取的值,在范围内的分片数,
String idVal= “gf89f9a” ;
Assert.assertEquals(true, 0==autoPartition.calculate(idVal));
idVal=“8df99a” ;
Assert.assertEquals(true, 4==autoPartition.calculate(idVal));
idVal=“8dhdf99a” ;
Assert.assertEquals(true, 3==autoPartition.calculate(idVal));
应用指定
此规则是在运行阶段有应用自主决定路由到那个分片。
<tableRule name="sharding-by-substring">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-substring</algorithm>
</rule>
</tableRule>
<function name="sharding-by-substring" class="org.opencloudb.route.function.PartitionDirectBySubString">
<property name="startIndex">0</property><! -- zero-based -->
<property name="size">2</property>
<property name="partitionCount">8</property>
<property name="defaultPartition">0</property>
</function>
配置说明:
上面 columns 标识将要分片的表字段, algorithm 分片函数 此方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。
例如 id=05-100000002
在此配置中代表根据 id 中从 startIndex=0,开始,截取 siz=2 位数字即 05, 05 就是获取的分区,如果没传默认分配到 defaultPartition
截取数字hash解析
此规则是截取字符串中的 int 数值 hash 分片。
<tableRule name="sharding-by-stringhash">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-stringhash</algorithm>
</rule>
</tableRule>
<function name="sharding-by-stringhash" class="org.opencloudb.route.function.PartitionByString">
<property name="partitionLength">512</property><! -- zero-based -->
<property name="partitionCount">2</property>
<property name="hashSlice">0:2</property>
</function>
配置说明:
上面 columns 标识将要分片的表字段, algorithm 分片函数 函数中 partitionLength 代表字符串 hash 求模基数,partitionCount 分区数,hashSlice hash 预算位,即根据子字符串中 int 值 hash 运算hashSlice : 0 means str.length(), -1 means str.length()-1
/**
* “2” -> (0,2)
* “1:2” -> (1,2)
* “1:” -> (1,0)
* “-1:” -> (-1,0)
* “:-1 ” -> (0,-1)
125
* “:” -> (0,0)
*/
一致性hash
一致性 hash 预算有效解决了分布式数据的扩容问题。
<tableRule name="sharding-by-murmur">
<rule>
<columns>user_id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur" class="org.opencloudb.route.function.PartitionByMurmurHash">
<property name="seed">0</property><! -- 默认是 0-->
<property name="count">2</property><! -- 要分片的数据库节点数量,必须指定,否则没法分片-->
<property name="virtualBucketTimes">160</property><! -- 一个实际的数据库节点被映射为这么多虚拟
节点,默认是 160 倍,也就是虚拟节点数是物理节点数的 160 倍-->
<! -- <property name="weightMapFile">weightMapFile</property>
节点的权重,没有指定权重的节点默认是 1。以 properties 文件的格式填写,以从 0 开始到 count-1 的整数值也就
是节点索引为 key,以节点权重值为值。所有权重值必须是正整数,否则以 1 代替 -->
<! -- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的 murmur hash 值与物理节
点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
</function>
按单月小时拆分
此规则是单月内按照小时拆分,最小粒度是小时,可以一天最多 24 个分片,最少 1 个分片,一个月完后下月从头开始循环。每个月月尾,需要手工清理数据。
<tableRule name="sharding-by-hour">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hour</algorithm>
</rule>
</tableRule>
<function name="sharding-by-hour" class="org.opencloudb.route.function.LatestMonthPartion">
<property name="splitOneDay">24</property>
</function>
配置说明:
columns: 拆分字段,字符串类型( yyyymmddHH)
splitOneDay : 一天切分的分片数
范围求模分片
先进行范围分片计算出分片组,组内再求模. 优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题.综合了范围分片和求模分片的优点,分片组内使用求模可以保证组内数据比较均匀,分片组之间是范围分片可以兼顾范围查询。最好事先规划好分片的数量,数据扩容时按分片组扩容,则原有分片组的数据不需要迁移。由于分片组内数据比较均匀,所以分片组内可以避免热点数据问题。
<tableRule name="auto-sharding-rang-mod">
<rule>
<columns>id</columns>
<algorithm>rang-mod</algorithm>
</rule>
</tableRule>
<function name="rang-mod" class="org.opencloudb.route.function.PartitionByRangeMod">
<property name="mapFile">partition-range-mod.txt</property>
<property name="defaultNode">21</property>
</function>
配置说明:
上面 columns 标识将要分片的表字段, algorithm 分片函数,
rang-mod 函数中 mapFile 代表配置文件路径
defaultNode 超过范围后的默认节点顺序号,节点从 0 开始。
partition-range-mod.txt
range start-end ,data node group size
以下配置一个范围代表一个分片组, =号后面的数字代表该分片组所拥有的分片的数量。
0-200M=5 //代表有 5 个分片节点
200M1 -400M=1
400M1 -600M=4
600M1 -800M=4
800M1 -1000M=6
日期范围hash分片
思想与范围求模一致,当由于日期在取模会有数据集中问题,所以改成 hash 方法。先根据日期分组,再根据时间 hash 使得短期内数据分布的更均匀.优点可以避免扩容时的数据迁移,又可以一定程度上避免范围分片的热点问题.要求日期格式尽量精确些,不然达不到局部均匀的目的
<tableRule name="rangeDateHash">
<rule>
<columns>col_date</columns>
<algorithm>range-date-hash</algorithm>
</rule>
</tableRule>
<function name="range-date-hash" class="org.opencloudb.route.function.PartitionByRangeDateHash">
<property name="sBeginDate">2014-01-01 00:00:00</property>
<property name="sPartionDay">3</property> <!-- 代表多少天分一个分片-->
<property name="dateFormat">yyyy-MM-dd HH:mm:ss</property> <!--代表分片组的大小-->
<property name="groupPartionSize">6</property>
</function>
冷热数据分片
根据日期查询日志数据 冷热数据分布 ,最近 n 个月的到实时交易库查询,超过 n 个月的按照 m 天分片。
<tableRule name="sharding-by-date">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-hotdate</algorithm>
</rule>
</tableRule>
<function name="sharding-by-hotdate" class="org.opencloudb.route.function.PartitionByHotDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sLastDay">10</property>
<property name="sPartionDay">30</property>
</function>
自然月分片
按月份列分区 ,每个自然月一个分片,格式 between 操作解析的范例。
<tableRule name="sharding-by-month">
<rule>
<columns>create_time</columns><!--分片字段,字符串类型-->
<algorithm>sharding-by-month</algorithm>
</rule>
</tableRule>
<function name="sharding-by-month" class="org.opencloudb.route.function.PartitionByMonth">
<property name="dateFormat">yyyy-MM-dd</property><!--日期字符串格式-->
<property name="sBeginDate">2014-01-01</property><!--开始日期-->
</function>
Mycat防火墙的配置
白名单和SQL黑名单说明(在server.xml中配置)
<firewall>
<whitehost>
<host user="mycat" host="127.0.0.1"></host> ip <!-- 白名单 用户对应的可以访问的 ip 地址-->
</whitehost>
<blacklist check="true">
<property name="selelctAllow">false</property> <!-- 黑名单允许的 权限 后面为默认-->
</blacklist>
</firewall>
黑名单拦截明显配置
配置项 | 缺省值 | 描述 |
---|---|---|
selelctAllow | true | 是否允许执行 SELECT 语句 |
selectAllColumnAllow | true | 是否允许执行SELECT * FROM T 这样的语句。如果设置为false,不允许执行select * from t,但 select * from (select id, name from t) a 。这个选项是防御程序通过调用select* 获得数据表的结构信息。 |
selectIntoAllow | true | SELECT 查询中是否允许 INTO 字句 |
deleteAllow | true | 是否允许执行 DELETE 语句 |
updateAllow | true | 是否允许执行 UPDATE 语句 |
insertAllow | true | 是否允许执行 INSERT 语句 |
replaceAllow | true | 是否允许执行 REPLACE 语句 |
mergeAllow | true | 是否允许执行 MERGE 语句,这个只在 Oracle 中有用 |
callAllow | true | 是否允许通过 jdbc 的 call 语法调用存储过程 |
setAllow | true | 是否允许使用 SET 语法 |
truncateAllow | true | truncate 语句是危险,缺省打开,若需要自行关闭 |
createTableAllow | true | 是否允许创建表 |
alterTableAllow | true | 是否允许执行 Alter Table 语句 |
dropTableAllow | true | 是否允许修改表 |
commentAllow | false | 是否允许语句中存在注释, Oracle 的用户不用担心, Wall 能够识别 hints和注释的区别 |
noneBaseStatementAllow | false | 是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽 DDL。 |
multiStatementAllow | false | 是否允许一次执行多条语句,缺省关闭 |
useAllow | true | 是否允许执行 mysql 的 use 语句,缺省打开 |
describeAllow | true | 是否允许执行 mysql 的 describe 语句,缺省打开 |
showAllow | true | 是否允许执行 mysql 的 show 语句,缺省打开 |
commitAllow | true | 是否允许执行 commit 操作 |
rollbackAllow | true | 是否允许执行 roll back 操作 |
如果把 selectIntoAllow、 deleteAllow、 updateAllow、 insertAllow、 mergeAllow 都设置为 false,这就是一个只读数据源了。
拦截配置-永真条件
配置项 | 缺省值 | 描述 |
---|---|---|
selectWhereAlwayTrueCheck | true | 检查 SELECT 语句的 WHERE 子句是否是一个永真条件 |
selectHavingAlwayTrueCheck | true | 检查 SELECT 语句的 HAVING 子句是否是一个永真条件 |
deleteWhereAlwayTrueCheck | true | 检查 DELETE 语句的 WHERE 子句是否是一个永真条件 |
deleteWhereNoneCheck | false | 检查 DELETE 语句是否无 where 条件,这是有风险的,但不是SQL注入类型的风险 |
updateWhereAlayTrueCheck | true | 检查 UPDATE 语句的 WHERE 子句是否是一个永真条件 |
updateWhereNoneCheck | false | 检查 UPDATE 语句是否无 where 条件,这是有风险的,但不是SQL注入类型的风险 |
conditionAndAlwayTrueAllow | false | 检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永真条件 |
conditionAndAlwayFalseAllow | false | 检查查询条件(WHERE/HAVING 子句)中是否包含 AND 永假条件 |
conditionLikeTrueAllow | true | 检查查询条件(WHERE/HAVING 子句)中是否包含 LIKE 永真条件 |
其他拦截配置
配置项 | 缺省值 | 描述 |
---|---|---|
selectIntoOutfileAllow | false | SELECT ... INTO OUTFILE 是否允许,这个是 mysql 注入攻击的常见手段,缺省是禁止的 |
selectUnionCheck | true | 检测 SELECT UNION |
selectMinusCheck | true | 检测 SELECT MINUS |
selectExceptCheck | true | 检测 SELECT EXCEPT |
selectIntersectCheck | true | 检测 SELECT INTERSECT |
mustParameterized | false | 是否必须参数化,如果为 True,则不允许类似 WHERE ID = 1 这种不参数化的 SQL |
strictSyntaxCheck | true | 是否进行严格的语法检测, Druid SQL Parser 在某些场景不能覆盖所有的SQL 语法,出现解析 SQL 出错,可以临时把这个选项设置为 false,同时把 SQL 反馈给 Druid 的开发者。 |
conditionOpXorAllow | false | 查询条件中是否允许有 XOR 条件。 XOR 不常用,很难判断永真或者永假,缺省不允许。 |
conditionOpBitwseAllow | true | 查询条件中是否允许有”&”、 “~”、 “ |
conditionDoubleConstAllow | false | 查询条件中是否允许连续两个常量运算表达式 |
minusAllow | true | 是否允许 SELECT * FROM A MINUS SELECT * FROM B 这样的语句 |
intersectAllow | true | 是否允许 SELECT * FROM A INTERSECT SELECT * FROM B 这样的语句 |
constArithmeticAllow | true | 拦截常量运算的条件,比如说 WHERE FID = 3 - 1 ,其中”3 - 1”是常量运算表达式。 |
limitZeroAllow | false | 是否允许 limit 0 这样的语句 |
禁用对象检测配置
配置项 | 缺省值 | 描述 |
---|---|---|
tableCheck | true | 检测是否使用了禁用的表 |
schemaCheck | true | 检测是否使用了禁用的 Schema |
functionCheck | true | 检测是否使用了禁用的函数 |
objectCheck | true | 检测是否使用了“禁用对对象” |
variantCheck | true | 检测是否使用了“禁用的变量” |
readOnlyTables | 空 | 指定的表只读,不能够在 SELECT INTO、 DELETE、 UPDATE、 INSERT、MERGE 中作为”被修改表”出现 |