分库分表,读写分离会带来哪些问题?
前面一篇文章图解分布式系统架构(看推荐阅读)大概讲了一下分库分表,以及读写分离出现的场景,分库分表为了解决高并发和海量数据的问题。
分库后会出现新的问题
1、跨库join问题
如有2个库,订单库,用户库,要查询买了某件商品的所有用户信息
2、事务问题
用户下订单的时候需要扣减商品库存,如果订单数据和商品数据在一个数据库中,我们可以使用事务来保证扣减商品库存和生成订单的操作要么都成功要么都失败,但分库后就无法使用数据库事务了,这时就要用到分布式事务了
分表后也会出现新的问题
1、join操作
水平分表后,数据分散在多个表中,如果需要与其他表进行join查询,需要在业务代码或数据库中间件中进行多次join查询,然后将结果合并
2、count()操作
业务代码或者数据库中间件对每个表进行count(*)操作,然后将结果相加。或者新建一张表,假如表名为“记录数表”,包含table_name和row_count两个字段,每次插入或删除子表数据成功后,都更新“记录数表”
3、order by操作
水平分表后,数据分散到多个字表中,排序操作无法再数据库中完成,只能由业务代码或数据库中间件分别查询每个子表中的数据,然后汇总进行排序
而高并发这个阶段,肯定是需要做读写分离的,啥意思?因为实际上大部分的互联网公司,一些网站,或者是 app,其实都是读多写少。所以针对这个情况,就是写一个主库,但是主库挂多个从库,然后从多个从库来读,那不就可以支撑更高的读并发压力了吗?
那么如何实现 MySQL 的读写分离?
其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后从库读取bin log进行重放,这样主库和从库数据就一样,只不过并发量比较高时,会有主从同步延时问题
放个图理解一下MySQL主从复制的原理,这块面试经常被问到
总的来说,MySQL复制有三个步骤
1、在主库上把数据更改记录到二进制日志中(Binary Log)中(这些记录被称为二进制日志事件)
2、备库将主库上的日志复制到自己的中继日志(Relay Log)中
3、备库读取中继日志中的事件,将其重放到备库数据之上
现在理论知识都有了,就剩怎么实现了?本来就是为了实现一个功能,现在好了,单写读写分离,跨库join,分布式事务,排序操作等就够你忙的了。
这时候你就应该想起数据库中间件了,它能帮你进行上述操作,把你从复杂的数据处理中解放出来,专注于开发业务代码。
数据库中间件能帮你做什么?
目前国内用的最多的中间件就是sharding-jdbc,mycat,别的用的很少,不再介绍
而数据库中间件针对数据源管理,目前主要有两种思路
1、客户端模式,在每个应用程序模块中配置管理自己需要的一个(或者多个)数据源,直接访问各个数据库,在模块内完成数据的整合,sharding-jdbc的实现方式
2、通过中间代理层来统一管理所有的数据源,后端数据库集群对前端应用程序透明,mycat的实现方式
放两张图就能理解区别了
一般的建议是小公司用sharding-jdbc,大公司用mycat,因为维护一套mycat集群也需要人力,物力。鉴于篇幅限制,本文就介绍一下mycat的基本使用
以一个最形象的例子,让你明白mycat到底帮你做了什么?
先介绍一下什么是分片?简单来说,就是通过某种特定的条件,将我们存放在同一个数据库中的数据,分散存放到多个数据库上面,以达到分散单台设备负载的效果
如上图所表示,数据被分到多个分片数据库后,应用如果需要读取数据,就要需要处理多个数据源的数据。如果没有数据库中间件,那么应用将直接面对分片集群,数据源切换、事务处理、数据聚合都需要应用直接处理,原本该是专注于业务的应用,将会花大量的工作来处理分片后的问题,最重要的是每个应用处理将是完全的重复造轮子。
所以有了数据库中间件,应用只需要集中与业务处理,大量的通用的数据聚合,事务,数据源切换都由中间件来处理。
那么数据库中间件是怎么做到的呢?
绿色的部分为mycat的逻辑节点,蓝色的部分为物理节点(即数据库的部署地址)
schema:逻辑库
通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道
数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库
table:逻辑表
既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成
datanode:分片节点
数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点
datahost:节点主机(上图蓝色节点)
数据切分后,每个分片节点(dataNode)不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点(dataNode)所在的机器就是节点主机(dataHost),为了规避单节点主机并发数限制,尽量将读写压力高的分片节点(dataNode)均衡的放在不同的节点主机(dataHost)。
rule:分片规则
前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。
实战Mycat
为了快速熟悉各种配置,一般直接从git上下载代码,本地用idea打开启动,方便练习一波,小编演示本文就是用的这种方法
mycat的配置其实是蛮简单的,最主要的是熟悉各配置文件的规则。如用户名,密码,分片规则,都是在配置文件中定义的
关于配置文件,conf目录下主要以下三个需要熟悉,要是本地测试用idea打开在resources目录下
小编演示一个最简单的映射配置,找一个数据库服务器,建立3个库,db1,db2,db3,把id为0-500 0000的数据放在db1,id为500 0001到1000 0000的数据放在db2,以此类推
server.xml是Mycat服务器参数调整和用户授权的配置文件(省略了一些配置,后面2个配置文件一样)
- <mycat:server xmlns:mycat="http://io.mycat/">
- <user name="root" defaultAccount="true">
- <property name="password">123456</property>
- <property name="schemas">TESTDB</property>
- </user>
- </mycat:server>
schema.xml是逻辑库,逻辑表定义以及分片定义的配置文件
- <mycat:schema xmlns:mycat="http://io.mycat/">
- <!--逻辑库名-->
- <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
- <!--rule的值和rule.xml的实现对应-->
- <table name="tb_test" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
- </schema>
- <!--dataHost可以配置不同主机上的数据库,这里为了演示就配置了一个主机上的不同数据库-->
- <dataNode name="dn1" dataHost="localhost1" database="db1" />
- <dataNode name="dn2" dataHost="localhost1" database="db2" />
- <dataNode name="dn3" dataHost="localhost1" database="db3" />
- <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
- writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
- <!--心跳语句-->
- <heartbeat>select user()</heartbeat>
- <!--这里我本地mycat配了一个远程mysql-->
- <writeHost host="hostM1" url="远程mysql的ip地址:3306" user="root"
- password="2014">
- </writeHost>
- </dataHost>
- </mycat:schema>
rule.xml是分片规则的配置文件
- <mycat:rule xmlns:mycat="http://io.mycat/">
- <tableRule name="auto-sharding-long">
- <rule>
- <!--根据哪个列进行分片-->
- <columns>id</columns>
- <!--分片规则,连续分片-->
- <algorithm>rang-long</algorithm>
- </rule>
- </tableRule>
- <function name="rang-long"
- <!--分片规则的实现类-->
- class="io.mycat.route.function.AutoPartitionByLong">
- <!--分片规则配置文件-->
- <property name="mapFile">autopartition-long.txt</property>
- </function>
- </mycat:rule>
autopartition-long.txt详细的分片策略
- # range start-end ,data node index
- # K=1000,M=10000.
- 00-500M=0
- 500M-1000M=1
- 1000M-1500M=2
这个配置的意思是,id在0到500w放在***个分片,以此类推
小编这里用Navicat(数据库连接工具)连接到本地的mycat
主机:localhost
端口:8066
用户名:root(server.xml中配置好的用户名密码)
密码:123456
看到有一个TestDB库,在这个库里面执行建表语句
- CREATE TABLE `tb_test` (
- `id` int(11) NOT NULL,
- `name` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后到对应的物理数据库db1,db2,db3上看,3个库都有了这个表。
在逻辑数据库中插入如下三条数据
- insert into tb_test (id, name) values (1, "1");
- insert into tb_test (id, name) values (5000001, "5000001");
- insert into tb_test (id, name) values (10000001, "10000001");
可以看到id为1的数据插入到物理数据库中的db1,id为5000001的数据插入到db2,id为10000001的数据插入到db3
在逻辑数据库中执行如下语句又能拿到这3条记录
- select id, name from tb_test
执行如下语句,可以看到mycat从三个数据库中取了记录,LIMIT 100是因为schema.xml中配置了sqlMaxLimit=“100”
- explain select id, name from tb_test
有了mycat以后,我们的数据库地址配置成mycat即可,它帮我们做了很多,其他各种分片规则,读写分离等的配置就不再演示,理解整个框架的大概运行流程就行
***再分享一个知识点,mycat1.5 开始会支持本地 xml 启动,以及从 zookeeper 加载配置转为本地 xml 的两种方式,即原来分享的zookeeper可以用作配置中心