日志:分布式系统的核心
日志是什么?
提起日志,可能你最先想到的是日常开发中写入到某个文件的信息,例如:
log.Println("start ...")
除此之外,不难想到在数据复制、版本控制等概念中,也有日志两个字的身影,而本文讨论的正是这一种日志。
日志,可以理解为一种存储结构,日志项按时间有序。日志记录了不同事件发生的先后次序,这对于分布式系统有着重要意义。
数据库中的日志
为了保证事务的原子性和持久性,在更改数据前,会将要做的更改写入到日志中,日志记录了每一次更改的数据变化和时间。由于日志是立即持久化的,因此可以通过日志,恢复数据库到任意历史时间的状态。
日志除了用于实现事务 ACID 的细节外,还被用于数据库间的数据复制。备库通过执行和主库一样的更改序列即可做到与主库数据同步,而更改序列即主库的日志,主库要做的就是将日志传输到备库。
分布式系统中的日志
上一节中,日志解决了数据库中,更改行为的排序和数据的传输两个问题。而在分布式系统中,这两个问题更是重要。分布式系统通过日志,让多个副本达成一致的行为顺序,该方案称之为状态机复制原理:
如果两个相同的,确定性的进程从同一状态开始,且以相同顺序获得相同输入,则这两个进程会产生相同的输出,并结束在相同的状态。
分布式系统的日志复制方案通常有两种:
- Peer & Peer
- Primary & Secondary
这里讲 Primary & Secondary,Leader & Worker 或者 Master & Slave 等都是一样的概念。
日志和数据的关系
如果把日志比作账单,那么数据就是余额。日志和数据的关系就可以这样理解:数据是静态的,日志是数据的变更记录,是每个历史时间点的数据备份。
数据的统一管理
举个例子,用户点击了某条广告,该事件需要记录到系统中,此时写一条事件数据到日志中,数据库、缓存等不同的系统都从日志读取日志项并应用。
这是一种基于日志订阅的系统模型,日志项的索引作为逻辑时钟,极大的简化了不同的订阅方的系统状态是否一致的问题。
举个例子,假设写了一条数据,对应日志项索引为 9,如上图所示,数据库已经更新自身状态到 11,而缓存则是更新自身的状态到 7,如果我们想要读取刚刚写入的数据,就只能在数据库中读,而不是通过缓存读取。
在该系统模型中,日志还起到了缓冲的作用,数据的生产和消费是异步的,即使某一时刻有大量的写请求,数据库也不会感受到压力。除此之外,无状态的订阅方被允许宕机或下线维护,重新上线后依然可以继续处理数据。
日志与流处理
通过一张图来了解什么是流处理:
- 系统 1 订阅了 Log A 和 Log B,系统 2 订阅了 Log B 和 Log C
- 系统 1 计算并发布数据到 Log D 和 Log E,系统 2 计算并发布数据到 Log E 和 Log F
- 系统 3 订阅了 Log D 和 Log E
- 系统 3 计算并发布数据到 Log F
在如图所示的流处理中,日志是其中的核心。
有状态的流处理
上文说过,无状态的订阅方被允许宕机或下线维护,重新上线后依然可以继续处理数据。那么对于有状态的订阅方,在可能宕机的情况下,如何维护正确的状态?
什么是无状态和有状态?例如同样是提供计算服务,一个是每次输入 2 个值,输出这两个值的和;另一个是每次输入 1 个值,输出所有历史输入的累加值和这一次输入值的和。前者是无状态服务,后者是有状态服务,需要记录到历史输入的累加值。
回顾日志和数据的关系的讨论,订阅系统可以将输入流(日志)转换为数据,并保存在本地的数据库中。
还是以上面提到的这个有状态计算服务为例,日志中记录可以这样表示:
+1, +2, +3, +4, +5
该日志的订阅方已经消费到第四个位置了,并输出 10,此时应该继续向后消费 +5,但是由于某些原因宕机了,如果恢复后本地没有记录 10 这个状态,再次消费 +5 就会输出 5,而记录了状态,就是输出 15。
本例中,订阅系统将输入流:“+1,+2,+3,+4”看作变更日志,对应的数据就是 10 记录在本地数据库,在消费了 +5 这一日志项后,数据库中的数据就变成了 15。
需要注意的是,本例中订阅方宕机恢复后消费的起始位置并不归类于状态中。此外,日志的增长不该是无限的,可以使用日志合并来收缩空间,例如本例中可以将日志:“+1,+2,+3,+4”合并为:”+10“。
参考资料
转载自:https://juejin.cn/post/7126111208125497375