本文简要分享Linearizability(线性化)和Serializability(串行化)这两个概念的区别和联系。
Serializability(串行化)
串行化解决的是数据库中多个事务的隔离性问题。在一个事务内,针对同一个对象读到的永远是同一个值,哪怕其它事务已经提交了新值。所以显然,有可能读到的是旧值。
在不同的事务之间,互相不干扰。每个事务都认为自己就是唯一的事务。实际上是多个事务并发在执行。串行化核心是要保证并发执行的多个事务最终的效果与按某个特性顺序串行化地执行这些事务的效果一致。但至于与什么特定顺序一致, 则不保证。
例如,有三个事务T1, T2和T3并发在执行。如果串行化顺序地执行这三个事务,则可能的组合有6种。串行化只会保证这三个事务并发执行的最终结果肯定与其中一种组合的结果一致,但具体是哪种组合,则不保证。这种跨事务的正确性,则需要应用层自己来解决。
从数据库的角度来看,能做到串行化的隔离级别,已经是级别非常高的要求了。本质上其解决的是数据的正确性的问题,保证不会由于多个事务并发执行,而导致出现不正确的数据。
解决串行化的核心技术包括两阶段锁(2PL)以及多版本并发控制(MVCC)。
Linearizability(线性化)
线性化则是要解决分布式系统中多副本的一致性读写问题。为了高可用性,一般数据都会有多个副本,分布在多个节点中。这就造成一个潜在的问题,针对某个对象,有可能刚刚写入新值,而紧接着读取该对象时,却读到了一个旧值。
线性化核心就是要解决这个问题,一旦某个对象写入了新值,之后读取这个对象时肯定会读取到最新的值。对于用户来说,仿佛系统中只有一个副本。所以线性化本质上实现的是强一致性。
数据库可以同时实现串行化和线性化,那就是严格的串行化。
etcd中的线性化读
etcd默认实现的就是linearizable read。
读请求可以发送给etcd cluster中的任何一个member,如果当前节点是leader,则可以直接处理client的读请求。
但如果当前节点不是leader。那么会向leader询问当前最新的commitId,只有本地的applyId不小于leader的commitId时,才能立即处理client的请求;否则就会等待leader的数据同步到本地之后才能处理,当然也可能超时失败。
client如果不想使用linearizable read,则可以带一个参数serializable(true),来关闭之。对于etcdctl来说,就是通过一个命令行参数"--consistency",将其设置成"s"即可,就是告诉etcdserver使用serializable的方式,而不是lineariable的方式来处理读请求。
小结
本文从概念上区分了Linearizability和Serializability这两个概念的区别,并结合etcd的实际实现讲述了etcd是如何实现linearizability read。
在实际的系统中,不管是实现Linearizability,还是实现Serializability,对性能的影响都是很大的。所以数据库的默认隔离级别一般不是Serializability;对于分布式系统,也不是所有的系统都支持Linearizability,要根据实际需求来选择。
Reference
<Designing Data-Intensive Application>
https://github.com/etcd-io/etcd
http://www.bailis.org/blog/linearizability-versus-serializability/
--END--