OPPO缓存层6次版本迭代的异地多活实践

原题目:OPPO缓存层6次版本迭代的异地多活实践

前言

互联网后台服务的常用经典架构中,全部后台服务框架一般可以分四层:

接入层,一般用于恳求的安全校验、频率和流量把持 逻辑层,用于用户数据的恳求和返回逻辑把持 缓存层,为了加快拜访性能,cache用户数据,常见的redis、memcache等 存储层,落地了用户各种数据,比如mysql、leveldb等

关于异地多活斟酌这样一个常见问题,假设某当地机房的服务全体宕机(机房掉电或光纤挖断)了,这个时候怎么抢救?比如上海登陆服务宕机了,总不能让上海的用户无法登陆。很显然我们要把后台的恳求流量切换到别的机房,当然后台的4层架构每一层的切换场景都不太一样。

缓存层的作用是为了加速拜访性能,减轻对存储的拜访压力。如果直接把流量切换到别的机房可能会由于缓存命中率太低,把处于底层的存储层打爆,严重影响机房的服务。如何解决这些问题,这个就是本篇文章要讨论的话题。

本篇重要缭绕数据的一致性和数据的安全性等2个重要挑衅,通过版本迭代的方法来论述缓存层的多机房异地多活会遇到的一些问题。

1)一致性

在多机房中,机房之间的网络延时不断定性大;各个机房物理条件可能也不一样;多机房服务运行的状况也会不一样。如何保障在各种异常和不断定性的情形下还能保障数据的一致性是本篇重点要讨论的话题。

2)安全性

Google在GFS的论文开篇也说过相似的前提:“服务异常或者硬件故障有可能是常态”, 如何保障已经写入的数据在异常的常态下还能恢复也是本篇的另外一个重点。

来源版本

基于本篇要讨论的是缓存层异地多活问题,而redis目前比拟常用,所以我们这里以redis作为缓存层的例子来阐明问题。

内存数据是个断电易失的,我们很容易想到的第一个版本:我们先把写入数据(修正和删除统称为写入)写到磁盘,然后用另外一个过程读取磁盘数据发送到其它机房,如下图:

展开全文
第一个问题 “同步过程”有可能被重启(代码bug或者其它多方面的原因),重启后 “同步过程”过程从哪里开端重新同步呢? 第二个问题是数据可能不完全,比如像redis会有aof rewrite,删除本来的aof生成新的aof文件,老的aof文件会被删除而只保存新的aof,这样老aof的offset在新aof文件就无效了。

针对上面来源版本问题我们做一些解决措施。

可靠的DB

我们引入一个DB,把同步过程每次同步的地位写入到DB里面去,同步过程重启的时候首先从DB找到自己的offset,然后从offset后面开端数据同步就可以了。

流水记载

参考mysql的方法,把所有的写入数据依照时光的先后用流水日志的方法记载下来不能被修正,在没有同步到别的机房前也不能被删除。

经过上面的修正后我们得到修正版本一。

把修正版本一放到生产环境跑了几天后,又会发明一些问题:

性能低下

同步过程每次同步一条数据都要修正Mysql同步offset,导致同步过程性能低下

单机磁盘空间不够

因为物理机器上会混部多个服务,每个服务都在往磁盘写入数据 跨机房同步本身又比拟慢

可能导致单机磁盘空间被打爆,磁盘被打爆后数据再也无法写入了。

数据回环

Server A的数据被同步过程A同步到B机房以后, 被写入到数据流水B,然后同步过程B又把这个流水同步给了A机房,就这样形成了一个循环回路挥霍了网络带宽和资源。

如上图,A机房写入的数据同步到B机房后又回流到了A机房。

1、解决流水问题

针对版本一的2个问题,我们做一些方式来解决。

同步offset定时写入DB

同步过程每次同步了一跳数据后,不会立马把offset写入到mysql,而是一个时光段写一次。很显然性能问题解决了,但是同样带来了问题,如果同步过程重启了之后较大概率拿到的不是一个实时的offset。这样就会反复履行一部分流水日志。

幂等流水

为了到达重放了一部分日志也不影响数据的正确性。我们把所有的写入操作转换成内存镜像操作,比如string数据构造内存中cnt=100,那么履行inc cnt的可以转成set inc 101 这种操作。

其它的数据构造如果hash、set、zset都可以转换成这种操作,但是对于list队列这种数据就没措施支撑相似的幂等操作了,这时候可以先不用管。

参加新闻队列

因为单机磁盘究竟有限很容写满,而且如呈现磁盘破坏了数据还不能恢复。应用如kafka或者RocketMQ用多台机器做集群做数据的冗余保证了容量空间和数据安全。

流水格局参加idcid

如果同步过程可以辨认同步过来的数据源是来自哪里,那么就可以解决数据回环问题了,比如同步过程A发明从B同步的数据源是自己,那么A就可以过滤掉这条数据。

2、版本二的架构

经过了上面的几个版本迭代后,全部体系在生产环境跑起来了,但是经常一段时光后,很快你就会发明又出了很多新的问题:

机房A和机房B的数据呈现了大批的不一致

如果多机房写入一个同一个key时,就很容易呈现数据不一致。

如上图,机房A和机房B同时设置了 cnt,因为机房之间同步的时光不断定导致了机房A和机房B的数据完整不一样,这个问题如果没有后续的写入,基础不可修复。

更多机房数

不是所有的业务仅仅是2个机房相互同步,可能须要三个机房或者更多机房数,单纯的相互收发数据已经不能用了,须要修改整体架构。

持续针对版本二的2个问题我们逐一讨论解决。

1、解决数据不一致

上面的第一个问题呈现数据不一致的实质原因,在于在写入数据到cache时没有做统一的版本把持,如果我们能对数据做版本把持,不能随便写入,这样就可以到达数据的最终一致性了。作为多机房的数据版本把持用时光戳是我们很快的想到一个版本方式,如果依照写入绝对时光次序为准似乎可以解决,如下图:

我们以时光戳为版本,时光戳越大版本越大,优先级越高,优先级高的能笼罩小优先级别的版本数据。 机房A的写入时光戳是100,而机房B的时光戳为150,从机房A同步到机房B的数据是没法履行胜利的,因为机房B拥有更高优先级别的数据;而机房B同步到机房A的数据是可以同步胜利的,从而到达了数据一致。 这里有个问题如果时光戳一致,怎么解决呢?优先级都是一样的,2个人各执己见似乎又会不一致,这里可以人工设置一个优先级放到配置文件里面。比如机房A高于机房B的优先级,如果呈现版本优先级雷同的情形下,以机房A为准。

这样,通过上面的简略的版本我们暂以为解决了数据不一致的问题。

2、多机房支撑

上面讨论的都是在2个机房相互同步的情形下,还比拟简略,但如果是三个机房或者更多机房的情形下,就已经不能用了。面对多机房的数据要做同步这个时候可以有2个选择。

中心型:数据先同步到一个中心机房,然后由中心机房同步到其它的机房 两两相互型:机房之间数据相互同步,任意2个机房树立数据通道

针对上面的2个方法,我们各自讨论优毛病

中心型

长处

架构简略:所有的机房数据只须要同步到中心机房即可 解决数据一致性:上面有说到,须要设置一个优先级的机房到配置文件,如果有中心机房这样一个角色存在,那么都以中心机房为准,就可以顺带解决一致性问题里面的如果版本号一致以谁为准的问题

毛病

中心机房的流量大:因为所有的流量都经过中心机房中转,所以流量会比拟大 Failover:当中心机房呈现故障后,整体的同步通道都将被关闭

两两相互型

长处

去中心化:任何一个机房都是对等的,任何一个机房去掉,都不影响其它机房的数据同步,其它机房都可以照常同步,流量上也是比拟均等

毛病

过于庞杂:从上面的4个机房相互同步可以看到,树立的通道数据太多,架构层面也要同时斟酌与每一个机房的异常情形,会导致整领会非常庞杂 实际当中如果同步的机房个数不会超过3个,上面的2个类型都可以用,须要解决每个类型面临的问题就可以。

这里我们选择中心型来做多机房的数据同步,接下来要解决的就是中心型问题。

中心型的须要解决的几个问题

1)流量大

因为我们的流量是写入流量,也就是说读取的数据是没有流水的,对于cache来说一般写入的量不会很大,写入流量比拟大的场景可能就是在导入数据的时候那个短暂的瞬间。

2)Failover

中心机房如果呈现故障,或者中心机房网络成为孤岛,要做到:

① 不影响其他redis机房的正常写入和读取。

依据新闻队列的订阅和写入,我们把同步过程拆离开2个不同的单独的第三方过程,“订阅过程”和“同步扫描过程”。

redis本身还是不参与同步,由这2个第三方过程来做同步。

同步扫描过程:只负责扫描本地的server binlog,然后写入到本地新闻队列,停止返回。

订阅过程:中心机房要订阅所有其它非中心机房的新闻队列,汇总数据;非中心机房只要订阅中心机房的新闻队列就可以拿到全量数据了。

② 快速设置新的中心机房

当中心机房可能全部成为孤岛无法和别的机房通讯后,运维可以在DB设置一个新的中心机房idcid。订阅过程和扫描过程如果发明DB产生了变动会立马同步新的中心机房的idcid到所有的同步组件。

3、版本三的架构

解决了上面的2个问题后,我们的版本架构以三个机房为例子:

版本三看似已经解决了一些问题,可在生成环境跑了一段时光后,你就发明还是会呈现了越来越多的数据不一致key,甚至差别较大。看下面的图:

机房B,履行 “set cnt 200”, 时光戳是100,然后同步到机房A。

机房A,履行 “set cnt 300”,机房A的时光戳可能异常是58,因为是业务的正常写入,假设先不能谢绝,那么在此机会房A的内存状况是 cnt = 300,时光是58,当把这个状况同步到机房B的时候就会失败,因为A机房的时光58小于B机房的100。这个时候在A、B机房就存在雷同key但是不同value的情形。

1、持续解决数据不一致

如上图所示,机器A的时光戳因为比拟小,导致机器A时光后写入的数据无法同步到机器B, 导致机房A的数据和机房B的无法达成一致。这个时候有2个选择:

1) 必定条件下谢绝业务的正常写入

内存中数据有一个版本号,如果业务的正常写入此时获取的版本号比内存中的版本号还要小,那么就谢绝业务的正常写入。

比如上图中的场景:A机房的时光戳比拟小,小于此时cnt的版本号就谢绝写入。

这个措施是一个很简略的解决思路,但是如果某个机器的时光比拟大,会导致其他机器就无法写入数据,这显然是不可取的。

2)把这个艰难的义务交给运维或者运营

时刻监控每一个机器的时光戳,如果呈现了不一致就告警和主动修复,这个时候你的手机可能无时无刻的都在告警。

还会有另外一个问题 ?因为多机房之间网络通讯本身就须要较长时光,等服务去取到机器的时光戳本身就消耗较长时光,多个机房的时光戳也要保住一致也是不太现实的。

经过无数次的折腾,最后你只能面对现实:要保证所有机房的时光戳一致是不可能的。

2、散布式的逻辑时钟

回到问题本身,我们用时光戳是用来作为key版本号的作用保证数据的一致性,假设就算时光戳不一致也能到达数据的的最终一致性,那就不须要纠结“保证所有机器的时光戳一致”这个问题了!我们解决思路简略来说是:

时钟不可逆, 时钟版本号只能递增!

每一个key在写入时的时光戳版本都不能变小,只能变大。我们key的版本号不在是绝对的物理机器时光戳,而是一个逻辑时光钟,这个时光钟不能变小。

看看上面的问题在机器A设置数据“set cnt 300”时,因为本机A的机器时光比拟慢获取到的时光戳是58,但是cnt本身的时光戳是100,这样的话在机房A的写入操作版本号就变小了,确定无法同步到机房B的,如果这个时候代码发明cnt 的版本号大于机器的时光戳,就把版本号进行累增到101,这个时候就可以同步到机房B了。

散布式的逻辑时钟解决多机房一致性。

3、版本四最终形态

版本四和版本三的架构还是不变的,变动的是在于通过火布式逻辑时钟增这个方式,来做到不管多机房的机器时光戳如何不一致都可以做到最终一致性。

经过了前面多版本的迭代了,这个时候应当是没有什么问题了,在生产环境跑了好久也没呈现啥问题。但是随着接入的业务越来越多,会发明又会偶尔呈现数据不一致的情形,你不得已登陆到机器看到发明server产生主备切换了,对于数据的一致性你做的还不够。

Master写入了a、b、c三个数据,同步过程也已经同步到了别的机房,如这个时候master宕机了,但是b、c还没有同步到Replication备机,就产生了主备切换,Replication成为新的master,但是没有b、c这2个数据,而别的机房还存在这2个数据。

1、解决主备切换数据不一致

这个问题的基本原因在于主机和备机的数据不一致,但是同步过程却把不一致的数据同步到了别的机房,如果每一个数据都在replica都存在那同步到别的机房就不会有问题了。这个时候可以有2个解决思路:

查询备机

扫描过程每次同步数据都去查询一下备机,如果数据一致就同步别的机房。显然这样做的成本太高,会导致同步非常慢,如果查询呈现延迟,扫描同步过程很容易卡顿,导致吞吐量大大降落。

写入seq

用一个整型值来累计写入操作,每次写入操作加1,主备同时累增,同时binlog流水里面也加上这个seq,每一个写入流水对应一个seq。

比如master的seq是100,但是replica的seq是80,这个时候我们的扫描同步过程只要同步80之前的binlog流水同步到别的机房就没有问题,而80~100的binlog流水则先不同步。

2、修正版本五最终形态

版本五和版本四的架构还是不变的,变动的是在于通过参加seq这个机制,来保证server在做主备切换的时候也可以保证数据的一致性。

既然多个机房数据同步了,如果多个机房同时做累加操作的话,比如inc cnt这种操作,因为数据同步都是内存镜像操作。

比如cnt初始值为0, A机房inc cnt,B机房也inc cnt,通过上面的同步机制最终得出的值可能是1也可能是2,而用户须要的是2这个值。

1、新增多机房的数值统计功效

上面统计呈现问题的原因在于多个机房对同一个key进行累增或者累减,然后同步的又是内存镜像就会导致数据相互笼罩。

所以我们的解决措施是把每个机房的数值进行单独统计,比如用一个hash构造,cnt为key名作为主key,每个机房的名字id做为子key。每个机房都单独累增或者累减相互不影响,这样读取的时候就可以得到一个准确的多方值了。

2、版本的形态

这个版本我们参加的全球多机房的数值统计功效,整体架构不变。

RedisPlus

上面的所有功效,我们的中间件RedisPlus都已经实现好了,即时可用。

作者丨OPPO互联网技巧

起源丨OPPO互联网技巧(ID:OPPO_tech)

dbaplus社群欢迎宽大技巧人员投稿,投稿邮箱:editor@dbaplus.cn

在技巧的快速迭代中,从来没有最好的架构,只有不断演进和完美的更好的架构,来Gdevops全球迅速运维峰会北京站看看业内风行的架构趋势吧:

《银行数字化转型战略剖析、要害技巧及未来架构趋势》建信金科 资深业务架构师/《企业级业务架构作者》/《银行数字化转型》作者 付晓岩 《建设迅速型花费金融中台及云原生下的DevOps实践》中邮花费金融 总经理助理 李远鑫 《平安银行“传统+互联网”混杂CMDB及运营中台实践》平安银行 运营开发负责人 徐大蔚 《中国农业银行信贷中台及数据中台建设实践》中国农业银行 研发中心资深架构专家 张亮 《技巧系统建设:架构、质量、中台、后端的战略落地与抵触破解》58到家团体/快狗打车 技巧VP/CTO 沈剑 《微服务技巧系统落地:散布式调用链追踪体系实战》58到家团体/快狗打车 技巧委员会成员/架构部负责人 王昊 《揭穿现象看实质:到家、货运O2O业务试金石基本-中台建设》58到家团体/快狗打车 业务服务部负责人/中台技巧部负责人 李洪英

9月11日,让我们一起用更多元的视角察看问题实质,寻找架构发展的轨迹和趋势。

Gdevops全球迅速运维峰会北京站:https://www.bagevent.com/event/6243820?bag_track=dbaplus返回搜狐,查看更多

义务编纂: