关于数据库扩容,有一个苛刻的事实常常被忽视。
事实:扩大您的数据库读取是容易的。麻烦在于您想要扩大写入!
为什么扩大写入复杂呢?要扩大读取,您只需将数据复制到多个节点。
Leader节点处理写入请求,Followers节点处理读取。
确实,领导者和跟随者之间存在一些复制延迟,但你可以处理这个问题。
然而,扩大写入操作则是完全不同的比赛。
让我解释一下原因。
为了扩大写入,你允许多个节点处理写入请求。
这被称为 Active-Active 设置。
这是它的样子:
当一个节点接收到写请求时,它将会将变化传播到其他节点。
但是...这种安排可能会导致冲突。
例如:
✅设想我们有两个节点A和B运行在 Active-Active 设置中。
✅节点A接收到一个针对键'foo'的写请求,值为'123'。
✅它接受这个请求,但在确认客户端之后失败了。
✅现在,B接收到一个针对键'foo'的读取请求。因为找不到该键,所以返回 NULL。
✅接着,B接收到一个针对键'foo'的写请求,值为'456',并且接受这个请求。
✅现在A从死亡中恢复过来,试图与B相接。
✅但他们都看到键'foo'有冲突。系统中存在不一致性。
看看下面的插图,以便更清楚地了解:
在处理数据库冲突时,通常会使用以下几种冲突解决算法:1. 最后写入胜出(Last Write Wins):这是最简单也最常见的一种冲突解决策略。在存在冲突的情况下,直接选择最后写入的数据作为最终的数据。这种策略很简单,但是可能会导致数据丢失,尤其当两个并发的写操作对同一个数据项进行修改时。
2. 冲突可解决数据类型(Conflict-free Replicated Data Type, CRDTs):CRDTs 是一种特殊的数据类型,通过一些数学属性来保证在分布式系统中的最终一致性。维护一种无冲突的数据类型,即使在没有协调的情况下,各地的副本也能达到相同的状态。常见的有计数器 CRDT、Set CRDT 等。
3. 版本向量(Vector Clocks):版本向量实际上是对每个操作进行时间戳打标,通过这种方式来解决冲突。当数据库遇到冲突时,它可以通过比较版本向量来决定哪个版本更新,因此应该胜出。然而,这种方法在面临“并发更新”问题时可能仍然无法避免冲突。
4. 合并(Merging):对于某些种类的数据,比如文档或者其他复杂数据类型,通常使用的冲突解决策略是将冲突的数据合并起来。这种情况下,需要开发者定义自己的合并规则。
5. 操作转换(Operational Transformation):操作转换是一种用于实现实时协作系统的算法,它通过确保每个副本看到相同的操作执行的顺序来解决冲突。这在多人实时协作编辑同一份文档时非常有效。
选择哪种冲突解决策略依赖于您的具体情况和需求,包括您的数据模型、负载特性、一致性需求以及可接受的延迟等。在实际应用中,往往会结合使用多种策略。
这绝对不是一件简单的事情但是,关于这个问题的更多内容,我会在以后的帖子中介绍。
那么 - 在扩展您的数据库时,你更偏向于哪一种策略呢?欢迎大家留言沟通交流。
对我的文章感兴趣的朋友可以点击查看主页我的专栏,更多高质量内容都会收录专栏。