五大实例详解,携程 Redis 跨机房双向同步实践( 三 )


当然,整个过程也并非上面说起来那么简单 。
比如,GC 的策略选择,是主动 GC 还是被动 GC,抑或是两者的结合?
单次 GC 时间长短的控制?如果 GC 时间过长,必然会影响 Redis 的响应速度;过短的 GC,则会导致对象一直堆积在 Tombstone 中,内存得不到释放 。

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

文章插图
 
五、Expire -- 一致 or 不一致,这是个问题作为缓存来说,比较常见的是配置一定的缓存过期策略 。一方面,可以保障数据的新鲜程度,另一方面无限制地将数据存入缓存,不仅不利于缓存的查询速度,对于资源来说也是不小的开销 。所以,Redis 中引入了 Expire 的过期机制,给每一个缓存的 Key 设定一个过期时间是一个良好的习惯 。
但是,在加入双向同步的架构之后,expire 似乎成为了一个问题,要不要将过期时间保持一致?如果保持一致的话,应该采取怎样的数据结构?
首先,我们应该确认一个问题,缓存的过期时间不一致,会不会导致数据一致性的问题?结合 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 跨机房双向同步实践

文章插图
 
缓存过期(Expire)这一小节,先分享到这里 。实现的过程中,我们还对过期策略进行了优化,防止并发地过期删除操作,造成不必要的网络开销 。
六、总结本文试着从一个个具体的小例子出发,带大家 review 一个分布式的 K/V 系统是如何搭建起来的 。后续还将带来内存优化篇 -- ZGC 的 colored pointer 在携程 Redis 的灵活运用 。
【作者简介】Nick,携程软件技术专家,关注分布式数据存储以及操作系统内核 。
更多携程技术人一手干货文章,请关注“携程技术”微信公众号 。

【五大实例详解,携程 Redis 跨机房双向同步实践】


推荐阅读