分库分表之第四篇

@TOC

4.水平分表

水平分表是在同一个数据库内,把同一个表的数据按照一定的规则拆到多个表中。前面以及介绍过来,这里不再重复介绍。

5.水平分库

水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
(1)将原来的order_db库拆分为order_db_1、order_db_2

在这里插入图片描述

(2)分片规则修改
由于数据库拆分来两个,这里需要配置两个数据源。
分库需要配置分库的策略,和分表策略的意义类似,通过分库策略实现数据操作针对分库的数据库进行操作。
在这里插入图片描述

分库策略定义方式如下 :

#分库策略,如何将一个逻辑表映射到多个数据源 spring.shardingsphere.sharding.tables.<逻辑表名称>.database‐strategy.<分片策略>.<分片策略属性名>= # 分片策略属性值
#分表策略,如何将一个逻辑表映射为多个实际表 spring.shardingsphere.sharding.tables.<逻辑表名称>.table‐strategy.<分片策略>.<分片策略属性名>= #分 片策略属性值

Sharding-JDBC支持以下几种分片策略 :
不管分库还是分表,策略基本一样。

  • standard : 标准分片策略,对应StandardShardingStrategy。提供对SQL语句中的=,IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
  • complex :复合分片策略,对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中=,IN和BWTWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
  • inline :行表达式分片策略,对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如 :t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。
  • hint :Hint分片策略,对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例如 :内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。
  • none :不分片策略,对应NoneShardingStrategy。不分片的策略。
    目前例子中都使用inline分片策略,若对其他分片策略细节若感兴趣,请查阅官方文档 :
    https://shardingsphere.apache.org
    (3)插入测试
    修改testInsertOrder方法,插入数据中包含不同的user_id

@Test
public void testInsertOrder(){ 
    for (int i = 0 ; i<10; i++){
        orderDao.insertOrder(new BigDecimal((i+1)*5),1L,"WAIT_PAY"); 
    }
    for (int i = 0 ; i<10; i++){
        orderDao.insertOrder(new BigDecimal((i+1)*10),2L,"WAIT_PAY");
    } 
}

执行testInsertOrder :


在这里插入图片描述

通过日志可以看出,根据user_id的奇偶不同,数据分别落在了不同数据源,达到目标。
(4)查询测试
调用快速入门的查询接口进行测试 :

List<Map> selectOrderbyIds(@Param("orderIds")List<Long> orderIds);

通过日志发现,sharding-jdbc将sql路由到m1和m2 :


在这里插入图片描述

问题分析 :
由于查询语句中没有使用分片键user_id,所以sharding-jdbc将广播路由到每个数据结点。
下边我们在sql中添加分片键进行查询。
在OrderDao中定义接口 :

@Select({"<script>", " select",
" * ",
" from t_order t ",
"where t.order_id in",
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>", "#{id}",
"</foreach>",
" and t.user_id = #{userId} ",
"</script>"
})
List<Map> selectOrderbyUserAndIds(@Param("userId") Integer userId,@Param("orderIds")List<Long> orderIds);

编写测试方法 :

 @Test
public void testSelectOrderbyUserAndIds(){ 
    List<Long> orderIds = new ArrayList<>();
    orderIds.add(373422416644276224L); 
    orderIds.add(373422415830581248L); 
    //查询条件中包括分库的键user_id
    int user_id = 1;
    List<Map> orders = orderDao.selectOrderbyUserAndIds(user_id,orderIds);
    JSONArray jsonOrders = new JSONArray(orders); 
    System.out.println(jsonOrders);
}

执行testSelectOrderByUserAndIds :


在这里插入图片描述

查询条件user_id为1,根据分片策略m$ -> {user_id % 2 + 1}计算得出m2,此sharding-jdbc将sql路由到m2,见上图日志。

6.垂直分库

垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。接下来看一下如何使用Sharding-JDBC实现垂直分库。
(1)创建数据库
创建数据库user_db

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

在user_db中创建t_user表

DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` (
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`fullname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户姓名', `user_type` char(1) DEFAULT NULL COMMENT '用户类型',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC规则中修改

# 新增m0数据源,对应user_db
spring.shardingsphere.datasource.names = m0,m1,m2
...
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/user_db?useUnicode=true spring.shardingsphere.datasource.m0.username = root spring.shardingsphere.datasource.m0.password = root
....
# t_user分表策略,固定分配至m0的t_user真实表 spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = m$‐>{0}.t_user spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.sharding‐column = user_id spring.shardingsphere.sharding.tables.t_user.table‐strategy.inline.algorithm‐expression = t_user

(3)数据操作
新增UserDao :

@Mapper
@Component
public interface UserDao {
    /**
    * 新增用户
    * @param userId 用户id
    * @param fullname 用户姓名 * @return
    */
    @Insert("insert into t_user(user_id, fullname) value(#{userId},#{fullname})") int insertUser(@Param("userId")Long userId,@Param("fullname")String fullname);
    /**
    * 根据id列表查询多个用户
    * @param userIds 用户id列表 * @return
    */
    @Select({"<script>", " select",
    " * ",
    " from t_user t ",
    " where t.user_id in",
    "<foreach collection='userIds' item='id' open='(' separator=',' close=')'>", "#{id}",
    "</foreach>",
    "</script>"
    })
    List<Map> selectUserbyIds(@Param("userIds")List<Long> userIds);
}

(4)测试
新增单元测试方法 :

 @Test
  public void testInsertUser(){ 
      for (int i = 0 ; i<10; i++){
    Long id = i + 1L;
    userDao.insertUser(id,"姓名"+ id ); 
        }
}
@Test
public void testSelectUserbyIds(){
    List<Long> userIds = new ArrayList<>(); userIds.add(1L);
    userIds.add(2L);
    List<Map> users = userDao.selectUserbyIds(userIds); System.out.println(users);
}

执行testInsertUser:


在这里插入图片描述

通过日志可以看出t_user表的数据被落在了m0数据源,达到目标。 执行testSelectUserbyIds:


在这里插入图片描述

通过日志可以看出t_user表的查询操作被落在了m0数据源,达到目标。

7.公共表

公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。可以将这类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行。接下来看一下如何使用Sharding-JDBC实现公共表。
(1)创建数据库
分别在user_db、order_db_1、order_db_2中创建t_dict表 :

 CREATE TABLE `t_dict` (
`dict_id` bigint(20) NOT NULL COMMENT '字典id',
`type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典类型', `code` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典编码', `value` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '字典值', PRIMARY KEY (`dict_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)在Sharding-JDBC规则中修改

# 指定t_dict为公共表
spring.shardingsphere.sharding.broadcast‐tables=t_dict

(3)数据操作
新增DictDao :

@Mapper
@Component
public interface DictDao {
    /**
    * 新增字典
    * @param type 字典类型 * @param code 字典编码 * @param value 字典值 * @return
    */
    @Insert("insert into t_dict(dict_id,type,code,value) value(#{dictId},#{type},#{code},# {value})")
    int insertDict(@Param("dictId") Long dictId,@Param("type") String type, @Param("code")String code, @Param("value")String value);
    /**
    * 删除字典
    * @param dictId 字典id * @return
    */
    @Delete("delete from t_dict where dict_id = #{dictId}") 
    int deleteDict(@Param("dictId") Long dictId);
}

(4)字典操作测试
新增单元测试方法 :

@Test
public void testInsertDict(){
    dictDao.insertDict(1L,"user_type","0","管理员");
    dictDao.insertDict(2L,"user_type","1","操作员"); 
}
@Test
public void testDeleteDict(){
    dictDao.deleteDict(1L);
    dictDao.deleteDict(2L); 
}

执行testInsertDict :


在这里插入图片描述

通过日志可以看出,对t_dict的表的操作被广播至所有数据源。
测试删除字典,观察是否把所有数据源中该公共表的记录删除。
(5)字典关联查询测试
字典表已在各各分库存在,各业务表即可和字典表关联查询。
定义用户关联查询dao :
在UserDao中定义 :

/**
* 根据id列表查询多个用户,关联查询字典表 * @param userIds 用户id列表
* @return
*/
@Select({"<script>", " select",
" * ",
" from t_user t ,t_dict b",
" where t.user_type = b.code and t.user_id in",
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>", "#{id}",
"</foreach>",
"</script>"
})
List<Map> selectUserInfobyIds(@Param("userIds")List<Long> userIds);

定义测试方法 :

 @Test
public void testSelectUserInfobyIds(){ 
    List<Long> userIds = new ArrayList<>(); 
    userIds.add(1L);
    userIds.add(2L);
    List<Map> users = userDao.selectUserInfobyIds(userIds); 
    JSONArray jsonUsers = new JSONArray(users); 
    System.out.println(jsonUsers);
}

执行测试方法,查看日志,成功关联查询字典表 :


在这里插入图片描述

8.读写分离

8.1 理解读写分离

面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。

在这里插入图片描述

通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进行一步的提升系统的处理能力。使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。
在这里插入图片描述

读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统的性能。
Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。它提供透明化读写分离,让使用方尽量像使用一个数据库一个使用主从数据库集群。
在这里插入图片描述

Sharding-JDBC提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用,同一线程且同一数据库连接内,如有写操作,以后的读操作均从主库读取,用于保证数据一致性。Sharding-JDBC不提供主从数据库的数据同步功能,需要采用其他机制支持。
在这里插入图片描述

接下来,对上面例子中user_db进行读写分离实现。为了实现Sharding-JDBC的读写分离,首先,要进行mysql的主从同步配置。

8.2.mysql主从同步(windows)

一,新增mysql实例
复制原有mysql如 :D:\mysql-5.7.25(作为主库)-> D:\mysql-5.7.25-s1(作为从库),并修改以下从库的my.ini:

[mysqld]
#设置3307端口
port = 3307
# 设置mysql的安装目录 basedir=D:\mysql‐5.7.25‐s1
# 设置mysql数据库的数据的存放目录 datadir=D:\mysql‐5.7.25‐s1\data

然后将从库安装为windows服务,注意配置文件位置 :

D:\mysql‐5.7.25‐s1\bin>mysqld install mysqls1 ‐‐defaults‐file="D:\mysql‐5.7.25‐s1\my.ini"

由于从库是从主库复制过来的,因此里面的数据完全一致,可使用原来的账号、密码登录。
二、修改主、从库的配置文件(my.ini),新增内容如下 :
主库 :

[mysqld]
#开启日志
log‐bin = mysql‐bin #设置服务id,主从不能一致
server‐id = 1
#设置需要同步的数据库 binlog‐do‐db=user_db
#屏蔽系统库同步 
binlog‐ignore‐db=mysql 
binlog‐ignore‐db=information_schema 
binlog‐ignore‐db=performance_schema

从库 :

[mysqld]
#开启日志
log‐bin = mysql‐bin
#设置服务id,主从不能一致
server‐id = 2
#设置需要同步的数据库 
replicate_wild_do_table=user_db.%
#屏蔽系统库同步 
replicate_wild_ignore_table=mysql.% replicate_wild_ignore_table=information_schema.% replicate_wild_ignore_table=performance_schema.%

重启主库和从库 :

net start [主库服务名]
net start [从库服务名mysqls1]

请注意,主从MySQL下的数据(data)目录下有个文件auto.cnf,文件中定义了uuid,要保证主从数据库实例的uuid不一样,建议直接删除掉,重启服务后将会重新生成。
三、授权主从复制专用账号

#切换至主库bin目录,登录主库
mysql ‐h localhost ‐uroot ‐p
#授权主备复制专用账号
GRANT REPLICATION SLAVE ON *.* TO 'db_sync'@'%' IDENTIFIED BY 'db_sync'; 
#刷新权限
FLUSH PRIVILEGES;
#确认位点 记录下文件名以及位点 
show master status;
在这里插入图片描述

四、设置从库向主库同步数据、并检查链路

#切换至从库bin目录,登录从库
mysql ‐h localhost ‐P3307 ‐uroot ‐p #先停止同步
STOP SLAVE;
#修改从库指向到主库,使用上一步记录的文件名以及位点 CHANGE MASTER TO
master_host = 'localhost', master_user = 'db_sync', master_password = 'db_sync', master_log_file = 'mysql‐bin.000002', master_log_pos = 154;
#启动同步
START SLAVE; #查看从库状态Slave_IO_Runing和Slave_SQL_Runing都为Yes说明同步成功,如果不为Yes,请检查error_log,然后 排查相关异常。
show slave status\G
#注意 如果之前此备库已有主库指向 需要先执行以下命令清空 STOP SLAVE IO_THREAD FOR CHANNEL '';
reset slave all;

最后测试在主库修改数据库,看从库是否能够同步成功。

8.3.实现sharding-jdbc读写分离

(1)在Sharding-JDBC规则中修改

# 增加数据源s0,使用上面主从同步配置的从库。
spring.shardingsphere.datasource.names = m0,m1,m2,s0
...
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/user_db?useUnicode=true spring.shardingsphere.datasource.s0.username = root spring.shardingsphere.datasource.s0.password = root
....
# 主库从库逻辑数据源定义 ds0为user_db 
spring.shardingsphere.sharding.master‐slave‐rules.ds0.master‐data‐source‐name=m0 
spring.shardingsphere.sharding.master‐slave‐rules.ds0.slave‐data‐source‐names=s0
# t_user分表策略,固定分配至ds0的t_user真实表 spring.shardingsphere.sharding.tables.t_user.actual‐data‐nodes = ds0.t_user ....

(2)测试
执行testInsertUser单元测试 :


在这里插入图片描述

通过日志可以看出,所有写操作落入m0数据源。
执行testSelectUserByIds单元测试 :


在这里插入图片描述

通过日志可以看出,所有写操作落入s0数据源,达到目标。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容