干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

作者简介

 

Nick,携程软件技术专家,关注分布式数据存储以及操作系统内核。

前言

在《携程 Redis 跨 IDC 多向同步实践》一文曾和大家分享过携程在 Redis 双向同步方面的心得,简单介绍了实现一个 Redis 双向同步系统中可能面临的问题,以及其中一种问题(分布式一致性)的部分处理方案 — CRDT(Conflict-free ReplicatedData Types)。本文将进一步阐述在具体设计和落地过程中的一些细节, 希望对大家能够有所帮助。包括:

  • Cycle Break — 如何打破盗梦空间的无限循环 

  • Last Write Wins & Vector Clock — 冲突的解决既简单又复杂 

  • Tomstone — 忆往昔才能看今朝 

  • GC — CRDT 取经之路的通天河 

  • Expire — 一致 or 不一致, 这是个问题

相信通过对这些问题的描述和解答, 大家对于如何实现一个双向同步的 Redis 会有一幅清晰的构图。

一、Cycle Break — 如何打破盗梦空间的无限循环

1.1 复制回环

以下图为例,假设 A B C 三个 Redis 建立起了双向复制关系。现在客户端先向其中一个 Redis(假设 A)发送了命令,SET KEY VAL(将 key 的值,设置或更新为 val),那么大概率会发生以下这样的步骤: 

1)A 将 SET KEY VAL 同步至 B 和 C 

2)B 和 C 接收到操作后,又再次同步给其他两个 Redis 

3)如此循环往复 …

综上所述,复制回环所带来的问题结合普通的数据结构,会带来以下问题:

  • 网络风暴 

  • 数据不一致

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

所以,我们最终的处理方案是:Redis对数据源进行甄别,只有属于来自客户端的操作,才会被选择性地同步给 Peer Master。然而,对于传统的 Master-Slave 架构来讲,还是会把所有对数据库有变更的操作,都同步给 Slave。

二、Last Write Wins & Vector Clock — 冲突的解决既简单又复杂

这里以一对简单的 K/V 为例,介绍下是如何处理冲突的。

2.1 冲突是如何产生的

下面一幅图很好地诠释了,为什么会有冲突以及冲突的后果。

假设我们在同一时刻,分别在两个互相同步的 Redis 上更新了一个 Key,左边的试图将 Key 设置为 CAT,而后边的客户端试图将 Key 设置为 DOG。

那么总共会有以下 4 种结果,前两种虽然不尽如人意,但至少保证了数据的一致性。而后面两种则是大家不希望看到的,因为数据不一致对业务造成不可忽略的风险。

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

 

看到这里,大家也许会发现,原来冲突处理如此简单,那我也可以大展身手了。当然,大部分系统的实现,做到这一层,已经解决了分布式一致性的问题。但是,是不是这样就皆大欢喜了呢/p>

答案当然是否定的,继续往下看你就会发现,这小小的 K/V 一致性,只是分布式系统中的冰山一角。冰山的下面有着千奇百怪的洪水猛兽,一个没处理到,都会带来无可估量的业务损失。

2.3 时钟 — 分布式系统永远的痛

相信部分同学在上学阶段或是工作以后,拜读过分布式系统的经典书目 –Distributed System Concept and Design (如下图)。这本书在开篇就对分布式系统有了一个经典的定义:

  • Concurrency 

  • No Global Clock 

  • Independent Failures

 

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

2.4 Vector Clock

那么时钟快慢带来的问题,是否无可避免实未必。

以上面的问题为例,是不需要冲突处理的,只是单从 Wall Clock,我们无法判定逻辑操作的时间。所以引入了一个叫 Vector Clock 的逻辑时钟,来表示一个操作的发生时刻。

以下图为例,全局有两个点,我们通过两个向量来表示发生过的逻辑操作。

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

问题的根源在哪里呢于 Delete 操作,将 Redis-B 上的值删除了,当 SET KEY=VAL 的更新操作到达之时,便没有了可以比较的对象。

3.2 Tomstone

这个问题该如何处理然是没有对象可比,我们创造一个对象不就可以了吗是诞生了 Tomstone —— 被删除对象的栖身地。对象的删除,我们只做逻辑删除,并不会将对象真正地从内存中抹去,而是放置在一个叫做 Tomstone 的地方,让其他后续的命令,能够和之前的命令有一个对比。数据的存留与否也就有了判定的依据。

四、GC — CRDT 取经之路的通天河

GC — Garbage Collection,很多语言都有这个特性,像 Java,Go。无独有偶,我们这里所说的 GC,原则和这些语言无异,都是为了处理一类不再使用,但是又占有资源(通常是内存资源)的一些数据的回收。

4.1 GC 的痛点

上一小节,我们简单介绍了 Tomstone 的概念,GC 也是由于 Tomstone 的引入而带来的在实践中不得不面对的问题,如下图所示:

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

五、Expire — 一致 or 不一致,这是个问题

作为缓存来说,比较常见的是配置一定的缓存过期策略。一方面,可以保障数据的新鲜程度,另一方面无限制地将数据存入缓存,不仅不利于缓存的查询速度,对于资源来说也是不小的开销。所以,Redis 中引入了 Expire 的过期机制,给每一个缓存的 Key 设定一个过期时间是一个良好的习惯。

但是,在加入双向同步的架构之后,expire 似乎成为了一个问题,要不要将过期时间保持一致果保持一致的话,应该采取怎样的数据结构/p>

首先,我们应该确认一个问题,缓存的过期时间不一致,会不会导致数据一致性的问题合 Redis 的实现来说,缓存过期时间不一致,不会带来数据一致性的问题(这个数据特指除过期时间之外的用户数据)。要说明白这个道理,我们先来看一下 Redis 是如何过期数据的。

Redis 的过期策略简单来说分为两种,一种是主动过期,以一个固定的频率轮询存储过期时间的字典,发现有 key 过期就执行删除操作;另一种是被动过期,在用户对 key 操作时,同时判定一下 key 的过期时间,是否需要过期掉。

两种过期策略,都由 master 发起,slave 本身通过被动接受 master 同步过来的 delete 操作,来达到数据一致性(这里我们忽略 slave-read-only 为 false,且有客户端过期 key 写入的场景)。其实这个状态下,是存在已经过期,但是在内存中没有被删除的 key,这个时候访问 Redis,外在的表象为 key 不存在。那么对于客户端来说,数据是一致的,过期的 key 确实拿不到了(虽然 Redis 内存中可能还有)。

对于双向同步来说,如果并发地在两端的 Redis 执行 expire 操作,就会发生冲突,是否处理冲突,如何处理冲突,是我们这里想要讨论的点。

在我们实际实现的过程中,曾经有一个版本确实实现了 expire 多个 Redis 之间的一致性,但是这样做,引入了更多的数据结构来解决冲突处理问题。对比普通版本的 Redis,同样大小的 expire 数据量,内存要多出一倍。对于携程这样 Redis 重度依赖的用户来说,内存的增加无疑伴随着大量费用的上升。所以最终的实现上,我们并没有采取 expire 时间一致性的策略。

那么是不是 expire 时间不一致,数据就有问题了呢然不是。举例来说,有 A/B 两个 Redis 建立了双向同步。A/B 在同一时间点,分别对同样的 key 设置了不同的过期时间(如下图)。

一边设置过期时间为 30s,另外一边设置为 60s,互相同步之后,时间进行了对调。30s 以后 Redis-A 上面的 key 过期,触发了 del 操作,同时把这个操作传播给 Redis-B,因为 delete 机制的存在,两边的数据是一致的。

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

《携程架构实践》

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

当当

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

京东

干货 | 五大实例详解,携程 Redis 跨机房双向同步实践

 “携程技术”公众号

  分享,交流,成长

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91536 人正在系统学习中

来源:携程技术

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年6月14日
下一篇 2020年6月14日

相关推荐