Redis Cluster,不再是纸上谈兵。
我在 git 历史中能找到的关于 Redis Cluster 的第一个提交日期是 2011 年 3 月 29 日,但那是一次“复制并提交”的合并:cluster 分支的历史已被销毁,因为那完全是一堆进行中提交的大杂烩,只是为了勾勒出最初的 API 设想以及与系统其余部分交互的方式。
基本上,这是一个大约有 4 年历史的项目。这几乎相当于 Redis 项目整个历史的三分之二。然而直到今天,我才发布 Redis 3.0.0 的第一个 Release Candidate(候选版本),这是第一个支持 Cluster 的版本。
一段曲折的历程
要理解为什么花了这么长时间很简单:我是在非常仓促的情况下启动 cluster 项目的,当时看起来如果没有自动扩容的手段,Redis 就会变得毫无用处。那并不是启动 Cluster 项目的合适时机,原因很简单:Redis 本身还太不成熟,我们还没有一个扎实的“单实例”故事可以讲。
虽然我犯了在错误时机启动项目的错误,但至少我没有掉进无视社区需求的陷阱,所以这个项目被无数次地暂停,以便为其他更基础的功能腾出精力。持久化、复制、延迟、内省都得到了比 cluster 多得多的关注,仅仅因为它们对用户群体更重要。
这个项目的另一个局限是,当我启动它时,我对分布式编程一无所知。我做了一个糟糕透顶的初始设计,只很好地捕捉到了“产品”层面的需求:低延迟、线性可扩展性以及对小集群来说很小的开销。然而所有细节都是错的,它远比应有的复杂,所用的算法也不安全,等等。
在取得一些小进展的同时,我开始学习分布式编程的基础知识,重新设计了 Redis Cluster,并把同样的思路应用到了新版 Sentinel 中。这两个系统所使用的分布式编程算法仍然比较原始,因为它们是异步复制、最终一致的系统,所以我不需要处理共识(consensus)等非平凡的问题。不过,即使你面对的是一个相对简单的问题——至少与编写一个 CP 存储相比是这样——你也必须明白自己在做什么,否则 resulting 系统可能完全是错的。
尽管有这些问题,我还是继续推进这个项目,努力修复它、修复实现并使其走向成熟,因为有一个简单的事实,就像一头挤在小房间里的巨象,弥漫在整个 Redis 社区之中,那就是:人们一次又一次地用自己的努力在做两件事,而且很多时候做得千疮百孔:
- 把数据集分片到 N 个节点上。
- 一个响应迅速的故障转移流程,以便在某些故障下存活下来。
问题“2”糟糕到某个时候我决定在 Cluster 完成之前先启动 Redis Sentinel 项目,以便尽快提供一个高可用系统,而且对于大多数只需要“2”而不需要“1”的使用场景来说,它比 Redis Cluster 更合适。
终于,我开始看到这些努力的第一个真实成果,现在我们有了一个 release candidate,这是获得采用、修复剩余 bug 并以更渐进的方式改进系统所需的关键里程碑。
它到底做什么?
Redis Cluster 基本上是一种数据分片策略,能够在集群运行期间把键从一个节点重新分片到另一个节点,同时配备一个故障转移流程,确保系统能够在某些类型的故障中存活下来。
从分布式数据库的角度看,Redis Cluster 在分区期间提供有限程度的可用性,以及一种弱形式的一致性。基本上它既不是 CP 系统也不是 AP 系统。换句话说,Redis Cluster 没有去追求分布式系统理论上所能达到的极限,以换取某些现实世界的特性。
它的一致性模型就是著名的“最终一致性”模型。基本上,如果节点因分区而失去同步,那么可以保证当分区愈合时,所有服务某个给定键的节点都会就该键的值达成一致。
然而合并策略是“最后一次故障转移获胜”,因此在网络分区期间收到的写入可能会丢失。一个常见的例子是:如果一个 master 被划分到一个少数派分区中,而客户端仍在尝试向它写入;当分区愈合时,如果多数派一侧已经有一个 slave 被提升来替换这个 master,那么旧 master 收到的写入就会丢失。
这反过来意味着 Redis Cluster 不必为了尝试合并值而在数据结构中携带元数据,Redis 所支持的那些花哨的命令和数据结构同样被 Redis Cluster 支持。因此没有额外的内存开销,没有 API 限制,也没有对单个值可包含元素数量的限制,代价是分区期间的安全性较低。
很容易理解,在一个像 Redis Cluster 这样设计的系统中,节点发生分歧是不好的,所以系统试图通过限制两个节点产生分歧的概率(以及分歧的程度)来缓解自身的不足。这是通过几种方式实现的:
- 分区中的少数派一侧变为不可用。
- 复制的设计使得通常情况下,给客户端的回复和发往 slave 的复制流是同时发送的。
- 当有多个 slave 可用于对一个 master 进行故障转移时,系统会尽量挑选那个看起来与故障 master 分歧最小的。
这些策略不会改变系统的理论性质,但为常见的 Redis Cluster 故障模式增加了一些额外的现实世界保护。
就 Redis 的 API 和使用场景而言,我认为这个设计是合理的,但过去有许多人不认同。不过我的看法是,每个设计者都可以自由地按自己的意愿设计系统,只有一条规则:说实话。所以 Redis Cluster 在官方文档中清楚地记录了它的局限和故障模式。
决定一个系统有用与否的,是用户和手头的使用场景。我的感受是,六年来用户在没有集群支持的情况下依然持续使用 Redis,正是因为他们的使用场景允许这样做,而 Redis 提供的某些特定特性和性能使它非常适合解决某些问题。我希望 Redis Cluster 能改善其中许多用户的生活。
前方的路
终于我们有了一个可以交付的最小可行产品,它已经足够稳定,用户可以认真开始测试,某些情况下甚至可以直接采用。采用得越多,我们就越能改进它。这一点我从 Redis 和 Sentinel 身上有切身体会:现在有了这样一个渐进的过程,推动软件从可用走向成熟。倾听用户、修复 bug、用测试覆盖更多代码……
与此同时,我已经开始思考 Redis Cluster 的下一个版本,为 v1 补充许多现在无法加入的有用的东西,比如多数据中心支持、通过命令重放在少数派分区中获得更高的写安全性、自动的节点均衡(现在如果某些节点太空而另一些太满,需要手动 reshard),以及更多其他东西。
此外,我相信 Redis Cluster 可以从一种专门为缓存设计的特殊执行模式中受益:在这种模式下,节点接受针对它们并不负责的 hash slot 的写入,以便在处于少数派分区时保持可用。
总有时间去改进和修复我们的实现和设计,但如果过于纠结于我们希望某个软件应该是什么样子,就有风险让它停留在 vaporware(雾件)类别里远超必要的时间。是时候放手了。尽情享用 Redis Cluster 吧!
Redis Cluster RC1 既可以通过 Github 上的 '3.0.0-rc1' 标签获取,也可以在 Redis.io 下载页面 http://redis.io/download 以 tarball 形式获取。
随机一篇博客