目录
- 前言
- 单一IDC
- 多IDC
- mysql主从同步
- 数据同步方案
- 多机房mysql同步方案
- 优化同步方案
- 同步方案的问题
- 如何解决重复插入
- 对于DDL语句处理
- 如何解决唯一索引冲突
- 如何解决数据回环问题
- 总结
前言
小伙伴们是否经常听说多机房部署,异地容灾?什么两地3中心,三地5中心?是否好奇多机房部署,数据之间是如何共享的呢?
今天老顾就来尝试着给大家解惑解惑,并详细介绍一下数据同步的问题。
单一IDC
上图的架构,是一个IDC机房中,部署了一主两从mysql数据库集群,大多数据中小型互联网公司采用的方案。
上面的方案存在一些问题:
1)不同地区的用户体验速度不同。一个IDC必然只能部署在一个地区,例如部署在北京,那么北京的用户访问将会得到快速响应;但是对于上海的用户,访问延迟一般就会大一点。
上海到北京的一个RTT可能有20ms左右。
2)容灾问题。这里容灾不是单台机器故障,而是指机房断电,自然灾害,或者光纤被挖断等重大灾害。一旦出现这种问题,将无法正常为用户提供访问,甚至出现数据丢失的情况。
某年,支付宝杭州某数据中心的光缆就被挖断过
多IDC
为了解决这些问题,我们可以将服务部署到多个不同的IDC中,不同IDC之间的数据互相进行同步。如下图
通过这种方式,我们可以解决单机房遇到的问题:
1)用户体验。不同的用户可以选择离自己最近的机房进行访问
2)容灾问题。当一个机房挂了之后,我们可以将这个机房用户的流量调度到另外一个正常的机房,由于不同机房之间的数据是实时同步的,用户流量调度过去后,也可以正常访问数据
故障发生那一刻的少部分数据可能会丢失
关于流量的调度问题,本文就不介绍,以后老顾会单独介绍流量、灰度发布的问题。本文主要介绍数据同步。
容灾补充
- 机房容灾 : 上面的案例中,我们使用了2个IDC,但是2个IDC并不能具备机房容灾能力。至少需要3个IDC,例如,一些基于多数派协议的一致性组件,如zookeeper,redis、etcd、consul等,需要得到大部分节点的同意。例如我们部署了3个节点,在只有2个机房的情况下, 必然是一个机房部署2个节点,一个机房部署一个节点。当部署了2个节点的机房挂了之后,只剩下一个节点,无法形成多数派。在3机房的情况下,每个机房部署一个节点,任意一个机房挂了,还剩2个节点,还是可以形成多数派。这也就是我们常说的"两地三中心”。
- 城市级容灾:在发生重大自然灾害的情况下,可能整个城市的机房都无法访问。为了达到城市级容灾的能力,使用的是"三地五中心"的方案。这种情况下,3个城市分别拥有2、2、1个机房。当整个城市发生灾难时,其他两个城市依然至少可以保证有3个机房依然是存活的,同样可以形成多数派。
Mysql主从同步
小伙伴们应该知道mysql的主从架构的数据复制的基本原理
通常一个mysql集群有一主多从构成。用户的数据都是写入主库Master,Master将数据写入到本地二进制日志binary log中。从库Slave启动一个IO线程(I/O Thread)从主从同步binlog,写入到本地的relay log中,同时slave还会启动一个SQL Thread,读取本地的relay log,写入到本地,从而实现数据同步。
数据同步方案
根据上面的mysql主从数据复制方案,那我们是不是可以自己写个组件,也读取binlog日志,解析出sql语句;然后同步到另一个mysql集群呢?
这样就可以实现了一个集群的数据,同步到另一个集群中。
那这个组件需要我们自己写吗?这个组件可以参考binlog的协议,只要有资深的网络编程知识,是能够实现的。
当然现在也不需要我们自己编写,现在市面上有成熟开源的
- 阿里巴巴开源的canal
- 美团开源的puma
- linkedin开源的databus
我们可以利用这些开源组件订阅binlog日志,解析到变化数据同步到目标库中。整个过程可以分为2步,第一步订阅获得变化的数据,第二步是把变化数据更新到其他目标库。
这边所说的目标库,不单单为mysql集群,也可以为redis,es等
上图我们通过订阅binlog,完成比较有代表性的数据同步
多机房Mysql同步
根据上面的知识,多机房的mysql的数据同步,可以也采用binlog方案
北京用户的数据不断写入离自己最近的机房的DB,通过binlog订阅组件订阅这个库binlog,然后下游的更新组件将binlog转换成SQL,插入到目标库。上海用户类似,只不过方向相反,不再赘述。通过这种方式,我们可以实时的将两个库的数据同步到对端。
上面的方案面对binlog更新不频繁的场景,应该问题不大;但是如果更新很频繁,那么binlog日志量会很大,处理更新数据的组件很有可能会顶不住,那如何处理?
优化同步方案
为了解决binlog量过大,更新数据组件处理不过来,可以在此方案中加入MQ进行削峰,如下图
同步方案的问题
我们看到上面的架构,主要是针对增量数据的同步;但一开始项目上线的时候,全量数据怎么处理呢?这个一般的处理策略是DBA先dump一份源库完整的数据快照;目标库导入快照即可。
下面我们看看增量数据同步,仔细的小伙伴们应该会看到北京IDC和上海IDC之间的数据是双向的,因为北京用户的数据是更新到北京DB的,上海用户的数据是更新到上海DB的,所以业务上面也是必须是双向的。
整个数据同步的过程会出现几个问题:
如何解决重复插入?
考虑以下情况下,源库中的一条记录没有唯一索引。对于这个记录的binlog,通过更新组件将binlog转换成sql插入目标库时,抛出了异常,此时我们并不知道知道是否插入成功了,则需要进行重试。如果之前已经是插入目标库成功,只是目标库响应时网络超时(socket timeout)了,导致的异常,这个时候重试插入,就会存在多条记录,造成数据不一致。
因此,通常,在数据同步时,通常会限制记录必须有要有主键或者唯一索引。
对于DDL语句如何处理?
如果数据库表中已经有大量数据,例如千万级别、或者上亿,这个时候对于这个表的DDL变更,将会变得非常慢,可能会需要几分钟甚至更长时间,而DDL操作是会锁表的,这必然会对业务造成极大的影响。
因此,同步组件通常会对DDL语句进行过滤,不进行同步。DBA在不同的数据库集群上,通过一些在线DDL工具进行表结构变更。
如何解决唯一索引冲突?
由于两边的库都存在数据插入,如果都使用了同一个唯一索引,那么在同步到对端时,将会产生唯一索引冲突。对于这种情况,通常建议是使用一个全局唯一的分布式ID生成器来生成唯一索引,保证不会产生冲突。
另外,如果真的产生冲突了,同步组件应该将冲突的记录保存下来,以便之后的问题排查。
如何解决数据回环问题?
此问题是数据同步经常出现的,也是必须需要解决的。最重要的问题。我们针对INSERT、UPDATE、DELETE三个操作来分别进行说明:
INSERT操作
假设在A库插入数据,A库产生binlog,之后同步到B库,B库同样也会产生binlog。由于是双向同步,这条记录,又会被重新同步回A库。由于A库本来就存在这条记录了,产生冲突。
UPDATE操作
先考虑针对A库某条记录R只有一次更新的情况,将R更新成R1,之后R1这个binlog会被同步到B库,B库又将R1同步会A库。对于这种情况下,A库将不会产生binlog。因为A库记录当前是R1,B库同步回来的还是R1,意味着值没有变。
在一个更新操作并没有改变某条记录值的情况下,mysql是不会产生binlog,相当于同步终止。下图演示了当更新的值没有变时,mysql实际上不会做任何操作:
上图演示了,数据中原本有一条记录(1,"tianshouzhi”),之后执行一个update语句,将id=1的记录的name值再次更新为”tianshouzhi”,意味着值并没有变更。这个时候,我们看到mysql 返回的影响的记录函数为0,也就是说,并不会产生的更新操作。
小伙伴们是不是以为,update操作不会有回环问题了;事实上并不是,我们看一些场景:
考虑A库的记录R被连续更新了2次,第一次更新成R1,第二次被更新成R2;这两条记录变更信息都被同步到B库,B也产生了R1和R2。由于B的数据也在往A同步,B的R1会被先同步到A,而A现在的值是R2,由于值不一样,将会被更新成R1,并产生新的binlog;此时B的R2再同步会A,发现A的值是R1,又更新成R2,也产生binlog。由于B同步回A的操作,让A又产生了新的binlog,A又要同步到B,如此反复,陷入无限循环中。
这个后果将会进入死循环。
DELETE操作
同样存在先后顺序问题。例如先插入一条记录,再删除。B在A删除后,又将插入的数据同步回A,接着再将A的删除操作也同步回A,每次都会产生binlog,陷入无限回环。
总结
今天老顾介绍了基本的多机房同步mysql的方案,以及同步方案遇到的一些问题,以及一些解决方案;但还遗留了数据回环问题,老顾将在下一篇文章中介绍解决方案。谢谢!!!
---End---