本文始发于我的公众号:TechFlow数据库
在计算机系统的领域,一致性能够说是一个高频词,可能出现的场景不少。从分布式系统到数据库的事务,都有它的身影。网络
以前咱们在介绍数据库事务的时候,谈到过事务的一致性。在数据库当中,一致性是一种目的,不是一种手段。数据库但愿控制事务的原子性、隔离性和持久性来保证数据的一致性。这里的一致性更多的指的是实际和咱们观念的一致。也就是说结果都在咱们预期以内。而在分布式系统当中,一致性有另外的含义,一个是多份数据副本之间的一致性问题,另外一个是多阶段提交的一致性问题。咱们今天先来聊聊副本一致性问题。并发
这个问题出现的缘由很简单,由于分布式系统当中,数据每每会有多个副本。若是是一台数据库处理全部的数据请求,那么经过ACID四原则,基本能够保证数据的一致性。而多个副本就须要保证数据会有多份拷贝。这就带来了同步的问题,由于咱们几乎没有办法保证能够同时更新全部机器当中的包括备份全部数据。尤为是当这些机器分布在全国各地甚至是世界各地的时候,因为网络延迟,即便我在同一时间给全部机器发送了更新数据的请求,也不能保证这些请求被响应的时间保持一致。只要存在时间差,就会存在某些机器之间的数据不一致的状况。也就是说,在分布式系统当中的一致性,指的是数据一致性。异步
这实际上是一个两难问题,为了解决流量过大的压力问题,咱们设计了分布式系统。可是分布式系统又会带来数据多份拷贝不一样步违反一致性的问题,咱们既不能容忍数据出错,也不能放弃分布式系统,惟一的办法就是采起一些措施,来最大可能地下降这个问题的影响力。分布式
多种多样的一致性模型,就是这些措施的体现。让咱们从最简单的严格一致性提及。高并发
### 严格一致性
性能
严格一致性是最理想的状况,若是咱们每次请求一个数据,无论什么状况下,咱们都能得到它的最后一次改动的结果。很遗憾的是,严格一致性是不可能实现的。优化
不可能实现的缘由很简单,由于多台机器之间的数据同步须要时间,不管这个时间多小,它都是肯定存在的。只要存在,就不可能实现严格一致性。举个简单的例子,咱们有A和B两台机器。在t时刻,A机器修改了某条数据,在1毫秒以前,B机器收到了一条查询该数据的请求。当B执行这个查询的时候,A机器已经修改完成,那么究竟B查询到的值应该是什么呢?是A修改以前的仍是修改以后的呢?在A机器看来,B的查询发生在它修改以后,但是B机器看来却偏偏相反,A修改值发生了在它收到请求以后。若是咱们要保证严格一致性,那么究竟什么结果才是对的呢?设计
固然上面这个例子只是最极端的状况,通常只会在理论上发生,可是经过对极端状况的分析,咱们也能够看得出来,严格一致性是不可能实现的。3d
数据不一致出现的根本缘由在于多台机器更新数据的时间差,咱们更新多台机器,总有先有后,很难保证彻底同步。根据同步数据时采用同步仍是异步策略,又能够将一致性分为强一致性与弱一致性。
使用同步策略更新数据时,咱们每次请求发给主节点,主节点收到数据以后使用同步更新的策略将数据发送给从节点。当全部的从节点更新成功以后,主节点会更新数据的状态,使它生效,以后返回response给用户,告知更新成功。
显然,经过同步更新数据的策略下,一致性的保障更强。若是咱们在主机上作好隔离措施,好比在更新结束以前,用户不能发起下一次更新,那么尽量地保证数据一致性不出问题。可是这种作法也有很大的弊端,最大的弊端就在于使用同步更新的操做,并且要全部从库都更新成功才能返回,这样的时间开销很是大。最关键的一个缺点在于,若是一个从库宕机,那么主库就不可能更新全部的从库,那么新来的请求永远不会更新,这显然是不能接受的。
和强一致性对应的是弱一致性,咱们不采用同步策略来更新数据,而采用异步更新的方式。好处也很明显,同步改为了异步,时间消耗大大缩减。可是问题也很明显,除了异步自己带来的问题以外,因为多个副本之间的数据更新发生不一样时,若是连续屡次访问落到了不一样的副本上,就会出现屡次访问的结果不一致的状况。
本质上来讲分布式系统的一致性模型,只有强弱两种。只不过在这两种基础的模型上,针对许多可能出现的问题还会进行相应的优化。整体上而言,分布式系统对于性能的要求要高于一致性,因此大多分布式系统的一致性模型,仍是基于弱一致性设计的。下面就来列举几种,比较经典的弱一致性模型的优化方案。
读写一致性在平常中常常碰见,好比在某论坛当中,用户回复了某个帖子。可是当用户刷新的时候,可能会出现这个回复消失的状况。用户会陷入困扰,不知道这个回复究竟有没有成功。
会发生这种状况的缘由也很简单,由于用户刷新的时候,访问的从库可能尚未获取到用户回复的数据,因此显示的结果当中天然就没有用户刚刚回复的内容。在这种状况下,咱们须要保证读写一致性。也就是说用户读取本身写入结果的一致性,保证用户永远可以第一时间看到本身更新的内容。好比咱们发一条朋友圈,朋友圈的内容是否是第一时间被朋友看见不重要,可是必定要显示在本身的列表上。
那如何实现呢?
方案有好几种,一种方案是对于一些特定的内容咱们每次都去主库读取。好比咱们读取帖子当中回复信息的时候,永远都去主库读取。可是这样的问题也很明显,可能会致使主库的压力过大。另外一种方案是咱们设置一个更新时间窗口,在刚刚更新的一段时间内,咱们默认都从主库读取,过了这个窗口以后,咱们会挑选最近有过更新的从库进行读取。
还有一种更好的方案是咱们直接记录用户更新的时间戳,在请求的时候把这个时间戳带上,凡是最后更新时间小于这个时间戳的从库都不予以响应。也就是说只有包含用户写入这个更新的库能够响应这个请求,就能够保证明现用户端的读写性一致了。
单调读解决的是最经典的弱一致性的不一致问题,出现的场景也很简单。因为主从节点更新数据的时间不一致,致使用户在不停地刷新的时候,有时候能刷出来,再次刷新以后会发现数据不见了,再刷新又可能再刷出来,就好像碰见灵异事件同样。我记得之前微博或者人人就存在这个问题,单调读就是针对的这个场景,能够保证不会出现这种状况。
解决的方法其实很简单,就是根据用户ID计算一个hash值,再经过hash值映射到机器。同一个用户无论怎么刷新,都只会被映射到同一台机器上。这样就保证了不会读到其余从库的内容,带来用户体验很差的影响。固然,这只是一种解决方案,其余的解决方法还有不少,这里不一一讨论。
因果一致性针对的数据之间逻辑上的因果问题,举个例子,好比说用户在知乎里提问题和回答问题。想要回答问题,必需要保证有对应的问题。也就是说必定是先有问题,再有的回答。但是问题和回答并不必定存储在同一个节点上,颇有可能出现问题存入节点A,回答存入节点B的状况。由于存在同步延迟,因此就可能出现查询的用户只看获得回答,却找不到对应问题的状况,违反了事物之间的因果性。
为了解决这个问题,一种方案是在写入的时候遵循某种逻辑顺序,那么在读取的时候,就能够保证不会出现因果错乱的状况。但问题是,不少因果性并不想问题和回答这么明显,一些隐藏的因果性可能很难被轻易判断,就须要引入更高深的技术,感兴趣的同窗能够去搜索一下向量时钟深刻了解。
听名字这种方案彷佛很厉害,其实最终一致性是全部分布式一致性模型当中最弱的。能够认为是没有任何优化的“最”弱一致性,它的意思是说,我不考虑全部的中间状态的影响,只保证当没有新的更新以后,通过一段时间以后,最终系统内全部副本的数据是正确的。
有大神打了这么一个比方,就好像你去点星巴克。你并不知道星巴克何时作好,你在作好以前去拿,拿到的结果是错的。可是你知道,通过一段时间以后,你必定能够拿到你想要的。至于星巴克作好须要多久,每每没有定论,能够是几百分之一秒,也能够是几个小时。
听起来这个方案很不靠谱,在肯定它可行以前,咱们还想问几个问题,首先,系统能不能保证一段时间是多久?若是天荒地老怎么办?其次,由于最终才能收敛,那么在收敛以前,多个副本之间的值可能都不一样,究竟又该以哪一个为准?
好在,这两个问题都能回答。对于第一个问题,答案是系统没办法肯定究竟须要多久收敛,可是能够肯定最大的收敛时间。有点像是物理学上的半衰期,咱们不知道一个粒子究竟须要多久衰变,可是能够肯定足够多的粒子当中的一半衰变所须要的时间。第二个问题更好回答,当有多份数据出现的时候,一般的作法是选择其中时间戳较大的,也就是说出现较晚的值。
虽然最终一致性看起来很不靠谱,可是它最大程度上保证了系统的并发能力,也所以,在高并发的场景下,它也是使用最广的一致性模型。
到这里为止,分布式系统当中常见的一致性模型就介绍完了。分布式系统有一个很大的特色,就是咱们看专业名词的时候每每云里雾里,不知所云。可是当咱们去了解它背后的设计理念与出现的缘由,就能发现它的有趣。衷心但愿你们都能从中发现本身的乐趣,都有学有收获。
若是喜欢本文,请顺手点个关注吧,大家的支持是我最大的动力。