1.1为什么需要分库分表
随着平台的发展,平台的数据会越来越多。当表中的数据量过多时,数据库的性能会下降严重,很有可能会把系统给拖垮。类似于分而治之的思想,将大的问题拆分成小的问题,从而提高效率。通过将数据分散在不同的数据库中,将一张表的数据,拆分成多张表。使得单一数据库和表的数据量变小,从而达到提升数据库操作性能的目的
1.2.1 垂直分表
用户在电商平台流览商品时,首先看到的是商品的基本信息,如果对该商品感兴趣时才会继续查看该商品的详细描述。因此,商品基本信息的访问频次要高于商品详细描述信息,商品基本信息的访问效率要高于商品详细描述信息(大字段)。 由于这两种数据的特性不一样,因此考虑将商品信息表拆分如下:
垂直分表定义:将一个表的字段分散到多个表中,每个表存储其中一部分字段。垂直分表带来的提升是:
- 减少IO争抢,减少锁表的几率,查看商品详情的与商品概述互不影响
- 充分发挥高频数据的操作效率,对商品概述数据操作的高效率不会被操作商品详情数据的低效率所拖累。
垂直拆分原则:
- 把不常用的字段单独放在一张表
- 把text,blob等大字段拆分出来单独放在一张表
- 经常组合查询的字段单独放在一张表中
1.2.2 垂直分表
对表进行垂直分表,数据库性能虽然得到了提升,但是所有数据还是放在一台服务器上,请求还是竞争的同一个物理机的CPU、内存、网络IO、磁盘。
以电商平台为例,可以把原有的SELLER_DB(卖家库),拆分为PRODUCT_DB(商品库)和STORE_DB(店铺库),并把这两个库分散到不同服务器上,如下图所示:
商品信息与商品描述业务耦合度较高,因此一起被存放在PRODUCT_DB(商品库);而店铺信息相对独立,因此单独被存放在STORE_DB(店铺库),这就叫垂直分库
垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,从而达到多个服务器共同分摊压力的效果。垂直分库带来的提升是:
- 解决业务层面的耦合,业务清晰
- 能对不同业务的数据进行分级管理、维护、监控、扩展等
- 高并发场景下,垂直分库在一定程度上可以提升IO、数据库连接数、单机硬件资源的性能
1.2.3 水平分库
当数据库的业务已经无法再拆分了,则可以进行水平拆分。比如通过判断商品ID是奇数还是偶数,然后把商品信息分别存放到两个数据库中。
水平分库是把同一个表的数据按一定规则拆分到不同的数据库中,每个库可以放在不同的服务器上。它带来的提升是:
- 解决了单库大数据,高并发的性能瓶颈。
- 按照合理拆分规则拆分,join操作基本避免跨库。
- 提高了系统的稳定性及可用性。
当一个应用难以再细粒度的垂直切分,或切分后数据量行数仍然巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平分库了,经过水平切分的优化,往往能解决单库存储量及性能瓶颈。但由于同一个表被分配在不同的数据库,需要额外进行数据操作的路由工作,因此大大增加了系统复杂度。
1.2.4 水平分表
同理,库能水平拆分,表也可以水平拆分
优点:
- 优化单一表数据量过大而产生的性能问题
- 避免IO争抢并减少锁表的几率
库内的水平分表,解决了单一表数据量过大的问题,分出来的小表中只包含一部分数据,从而使得单个表的数据量变小,提高检索性能。但由于同一个表的数据被拆分为多张表,也需要额外进行数据操作的路由工作,因此增加了系统复杂度。
总结:
垂直分表 | 可以把一个宽表的字段按访问频次、业务耦合松紧、是否是大字段的原则拆分为多个表,这样既能使业务清晰,还能提升部分性能。拆分后,尽量从业务角度避免联查,否则性能方面将得不偿失。 |
垂直分库 | 可以把多个表按业务耦合松紧归类,分别存放在不同的库,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能,同时能提高整体架构的业务清晰度,不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。 |
水平分库 | 可以把一个表的数据(按数据行)分到多个不同的库,每个库只有这个表的部分数据,这些库可以分布在不同服务器,从而使访问压力被多服务器负载,大大提升性能。它不仅需要解决跨库带来的所有复杂问题,还要解决数据路由的问题 |
水平分表 | 可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据,这样做能小幅提升性能,它仅仅作为水平分库的一个补充优化。 |
2.1Sharding-JDBC
Sharding-JDBC是一款开源分布式数据库中间件,定位为轻量级Java框架,在Java的JDBC层提供额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
2.2功能介绍
Sharding-JDBC可以进行分库分表,同时又可以解决分库分表带来的问题,它的核心功能是:数据分片和读写分离。
数据分片的有效手段是对关系型数据库进行分库和分表。在使用Sharding-JDBC进行数据分片前,需要了解以下概念:
逻辑表 | 水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是 t_order_0 到 t_order_9 ,他们的逻辑表名为 t_order 。 |
真实表 | 在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9 。 |
数据节点 | 数据分片的最小单元。由数据源名称和数据表组成,例: ds_0.t_order_0 。 |
分片键 | 用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。 |
自增主键生成策略 | 通过在客户端生成自增主键替换以数据库原生自增主键的方式,做到分布式全局主键无重复 |
绑定表 | 指分片规则一致的主表和子表。例如: 商品信息表 表和 商品描述 表,均按照 商品id 分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积,关联查询效率将大大提升( 注意:绑定表之间的分片键要完全相同。) |
读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库,它提供透明化读写分离,让使用方尽量像使用一个数据库一样进行读写分离操作。Sharding-JDBC不提供主从数据库的数据同步功能,需要采用其他机制支持。
2.3使用案例
1.依赖:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</dependency>
2.配置sharding-jdbc
只需要配置好就行了。
# 真实数据源定义
spring.shardingsphere.datasource.names = m0,m1,m2,s0,s1,s2
spring.shardingsphere.datasource.m0.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.url = jdbc:mysql://localhost:3306/store_db?useUnicode=true
spring.shardingsphere.datasource.m0.username = root
spring.shardingsphere.datasource.m0.password = 123spring.shardingsphere.datasource.m1.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url =jdbc:mysql://localhost:3306/product_db_1?useUnicode=true
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = 123
spring.shardingsphere.datasource.m2.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url =jdbc:mysql://localhost:3306/product_db_2?useUnicode=true
spring.shardingsphere.datasource.m2.username = root
spring.shardingsphere.datasource.m2.password = 123
spring.shardingsphere.datasource.s0.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.url = jdbc:mysql://localhost:3307/store_db?useUnicode=true
spring.shardingsphere.datasource.s0.username = root
spring.shardingsphere.datasource.s0.password = 123
spring.shardingsphere.datasource.s1.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s1.url =jdbc:mysql://localhost:3307/product_db_1?useUnicode=true
spring.shardingsphere.datasource.s1.username = root
spring.shardingsphere.datasource.s1.password = 123
spring.shardingsphere.datasource.s2.type =com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s2.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s2.url =jdbc:mysql://localhost:3307/product_db_2?useUnicode=true
spring.shardingsphere.datasource.s2.username = root
spring.shardingsphere.datasource.s2.password = 123# 主库从库逻辑数据源定义 ds0为store_db ds1为product_db_1 ds2为product_db_2
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s0
spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names=s1
spring.shardingsphere.sharding.master-slave-rules.ds2.master-data-source-name=m2
spring.shardingsphere.sharding.master-slave-rules.ds2.slave-data-source-names=s2
# 分库策略,以store_info_id为分片键,分片策略为store_info_id % 2+1
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column= store_info_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithmexpression = ds$->{store_info_id % 2+1}
# store_info分表策略,固定分配至ds0的store_info真实表
spring.shardingsphere.sharding.tables.store_info.actual-data-nodes = ds$->{0}.store_info
spring.shardingsphere.sharding.tables.store_info.table-strategy.inline.shardingcolumn = id
spring.shardingsphere.sharding.tables.store_info.tablestrategy.inline.algorithm-expression = store_info
# product_info分表策略,分布在ds1,ds2的product_info_1和product_info_2表 ,分片策略为
product_info_id % 2+1,product_info_id采用雪花算法
spring.shardingsphere.sharding.tables.product_info.actual-data-nodes = ds$->{1..2}.product_info_$->{1..2}
spring.shardingsphere.sharding.tables.product_info.tablestrategy.inline.sharding-column = product_info_id
spring.shardingsphere.sharding.tables.product_info.tablestrategy.inline.algorithm-expression = product_info_$->{product_info_id % 2+1}
spring.shardingsphere.sharding.tables.product_info.keygenerator.column=product_info_id
spring.shardingsphere.sharding.tables.product_info.key-generator.type=SNOWFLAKE
# product_descript分表策略,分布在ds1,ds2的product_descript_1和product_descript_2表,分片策略为product_info_id % 2+1,id采用雪花算法
spring.shardingsphere.sharding.tables.product_descript.actual-data-nodes = ds$->{1..2}.product_descript_$->{1..2}
spring.shardingsphere.sharding.tables.product_descript.tablestrategy.inline.sharding-column = product_info_id
spring.shardingsphere.sharding.tables.product_descript.tablestrategy.inline.algorithm-expression = product_descript_$->{product_info_id %2+1}
spring.shardingsphere.sharding.tables.product_descript.key-generator.column=id
spring.shardingsphere.sharding.tables.product_descript.keygenerator.type=SNOWFLAKE
# 设置product_info,product_descript为绑定表,这两张表中的分片键名字必须一致
spring.shardingsphere.sharding.binding-tables = product_info,product_descript
# 打开sql输出日志
spring.shardingsphere.props.sql.show = true
配置参考下图:
3MyCat
mycat是一个分布式数据库中间插件,是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。
3.1核心概念
Schema | 由它指定逻辑数据库(相当于MySQL的database数据库) |
Table | 逻辑表(相当于MySQL的table表) |
DataNode | 真正存储数据的物理节点 |
DataHost | 存储节点所在的数据库主机(指定MySQL数据库的连接信息) |
User | MyCat的用户(类似于MySQL的用户,支持多用户) |
3.1配置schema.xml
schema.xml作为Mycat中重要的配置文件之一,管理着Mycat的逻辑库、表、分片规则、DataNode以及DataHost之间的映射关系
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--
schema : 逻辑库 name:逻辑库名称
sqlMaxLimit:一次取多少条数据 要超过用limit xxx
table:逻辑表
dataNode:数据节点 对应datanode标签
rule:分片规则,对应rule.xml
subTables:子表
primaryKey:分片主键 可缓存
-->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
<!-- auto sharding by id (long) -->
<table name="item" dataNode="dn1,dn2,dn3" rule="mod-long"
primaryKey="ID"/>
</schema>
<!-- <dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743"
/> -->
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<!--
dataHost : 数据主机(节点主机)
balance:1 :读写分离 0 : 读写不分离
writeType:0 第一个writeHost写, 1 随机writeHost写
dbDriver: 数据库驱动 native:MySQL JDBC:Oracle、SQLServer
switchType: 是否主动读
1、主从自动切换 -1 不切换 2 当从机延时超过slaveThreshold值时切换为主读
-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.24.129:3306" user="root" password="root" >
</writeHost>
</dataHost>
</mycat:schema>
3.2配置Server.xml
server.xml几乎保存了所有mycat需要的系统配置信息。最常用的是在此配置用户名、密码及权限
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="defaultSqlParser">druidparser</property>
</system>
<user name="mycat">
<property name="password">mycat</property>
<property name="schemas">TESTDB</property>
</user>
</mycat:server>
3.2配置配置rule.xml
rule.xml里面就定义了我们对表进行拆分所涉及到的规则定义。我们可以灵活的对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同。这个文件里面主要有tableRule和function这两个标签。在具体使用过程中可以按照需求添加tableRule和function。此配置文件可以不用修改,使用默认即可。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat=”http://io.mycat/“ >
<!--
tableRule 标签配置说明:
name 属性指定唯一的名字,用于标识不同的表规则
rule 标签则指定对物理表中的哪一列进行拆分和使用什么路由算法。
columns 内指定要拆分的列名字。
algorithm 使用 function 标签中的 name 属性。连接表规则和具体路由算法。当然,多个表规则
可以连接到同一个路由算法上。 table 标签内使用。让逻辑表使用这个规则进行分片。
-->
<tableRule name="sharding-by-intfile">
<rule>
<columns>sharding_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<!--
function 标签配置说明:
name 指定算法的名字。
class 制定路由算法具体的类名字。
property 为具体算法需要用到的一些属性。
-->
<function name="hash-int"
class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
</function>
</mycat:rule>
4MyCat和Sharding-JDBC
数据库连接方式 | Mycat:作为数据库代理,需要将应用程序的数据库连接指向Mycat,由Mycat负责路由请求到后端的数据库节点。 |
数据库连接方式 | Sharding-JDBC:通过JDBC接口透明地将SQL路由到不同的数据库节点上,应用程序直接连接到Sharding-JDBC提供的数据源 |
配置和部署 | Mycat:需要额外部署Mycat服务器,并配置Mycat的路由规则、分片策略等,较为复杂。 |
配置和部署 | Sharding-JDBC:作为一个Java库,可以直接集成到应用程序中,配置相对简单,无需额外的中间件服务器。 |
SQL透明性 | Mycat:作为数据库代理,可以对SQL进行解析和路由,但需要将应用程序的数据库连接指向Mycat,对应用程序有一定的侵入性。 |
SQL透明性 | Sharding-JDBC:通过JDBC接口透明地将SQL路由到不同的数据库节点上,应用程序无需感知分片和路由的存在,对应用程序透明 |
功能扩展 | Mycat:提供了丰富的数据库中间件功能,如负载均衡、读写分离、缓存等,可以直接代理MySQL的连接。 |
功能扩展 | Sharding-JDBC:主要提供了数据分片和读写分离的功能,较为专注于分布式数据库的路由和分片。 |