大厂如何使用binlog解决多机房同步mysql数据(一)?

790 阅读10分钟

前言

小伙伴们是否经常听说多机房部署,异地容灾?什么两地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生成器来生成唯一索引,保证不会产生冲突。关于如何生成全局唯一分布式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的方案,以及同步方案遇到的一些问题,以及一些解决方案;但还遗留了数据回环问题,老顾将在下一篇文章中介绍解决方案。谢谢!!!