CAP理论相信做分布式系统的朋友都不陌生它是分布式系统中最经典的理论之一说的是一个分布式系统不可能,同时满足一致性(Consistency)可用性(Availability)和分区容错性(Partition tolerance)这三个条件最多只能,同时满足两个。
很多人都在课本上学过这个理论,但是真正在实际项目中用起来才发现理论和实战差距很大很多坑不踩过真的不知道我做分布式系统这些年在CAP相关的问题上踩了不少坑也交了不少学费今天想结合实际案例分享一下CAP理论在实战中的应用以及那些年我踩过的坑希望能帮大家少走弯路。
一、先回顾一下CAP理论
在说实战和踩坑之前,先简单回顾一下CAP理论的基本概念方便不太熟悉的朋友理解也方便后面的讨论。
CAP理论是由计算机科学家埃里克·布鲁尔(Eric Brewer)在2000年提出的,所以也叫布鲁尔定理(Brewer's theorem)它指出一个分布式计算系统不可能,同时满足以下三个特性最多只能,同时满足两个:
C - Consistency(一致性): 所有节点在同一时刻看到的数据是一致的,也就是说写操作成功后后续的读操作都能读到这个最新的值用户,不管访问哪个节点都能得到最新的数据不会出现读到旧数据的情况注意这里说的是强一致性(Strong Consistency)不是最终一致性(Eventual Consistency)。
A - Availability(可用性): 每个请求都能在有限的时间内得到响应(不管是成功还是失败)也就是说服务一直是可用的用户发请求过来系统能正常响应不会出现超时,或者无响应的情况注意可用性,只要求响应不要求返回的是最新数据返回旧数据也算可用。
P - Partition tolerance(分区容错性): 分布式系统在遇到网络分区(也就是节点之间,的网络断了节点之间,无法通信)的时候,仍然能继续工作不会,因为网络分区而整个系统崩溃,或者不可用在分布式系统中网络分区是不可避免的,因为网络总会出问题网线会断交换机会挂机房之间,的专线会中断等等,所以分布式系统必须考虑分区容错性。
CAP理论的核心结论是,因为网络分区在分布式系统中不可避免,所以P是必须的,那么在P存在的前提下C和A就不能,同时满足只能二选一,也就是说分布式系统要么是CP系统(保证一致性和分区容错牺牲可用性)要么是AP系统(保证可用性和分区容错牺牲一致性)没有系统能,同时满足CAP三者这就是CAP理论的核心。
常见的CP系统有ZooKeeperHBaseMongoDB(强一致性模式)等它们在网络分区的时候,会牺牲可用性保证数据一致性常见的AP系统有CassandraDynamoDBRedis Cluster(部分模式)等它们在网络分区的时候,会牺牲一致性保证可用性允许短时间内数据不一致最终达到一致。
理论说起来简单,但是真正在实际项目中应用就会遇到各种问题和坑下面就结合我的实际经历说说那些年我在CAP上踩过的坑。
二、坑1:以为选了CP就万事大吉结果可用性出大问题
我第一次真正接触CAP相关的问题是在做一个分布式配置中心的项目的时候,那时候我们需要一个配置中心来管理各个服务的配置要求配置数据不能错,因为配置错了可能导致服务出问题,所以我们觉得一致性很重要不能出现配置不一致的情况,于是我们就选了ZooKeeper来做配置中心的存储,因为ZooKeeper是典型的CP系统保证强一致性我们觉得选了CP就万事大吉了数据肯定一致不会出问题。
结果上线后没多久就出问题了有一次机房的网络出了点问题ZooKeeper集群的几个节点之间,网络抖动出现了短暂的网络分区ZooKeeper为了保证一致性就进行了Leader选举在选举过程中ZooKeeper是不可用的不能处理读写请求,虽然网络分区只持续了十几秒,但是ZooKeeper的Leader选举加上恢复花了差不多一分钟这一分钟里所有依赖配置中心的服务都拿不到配置有的服务启动失败有的服务,因为拿不到最新配置用了旧配置出了问题影响了不少服务那次故障让我们很被动也让我第一次深刻体会到CAP的代价选了CP就意味着在网络分区的时候,要牺牲可用性而可用性出问题对业务的影响可能很大。
后来我们复盘这次故障发现我们对配置中心的需求其实不需要,那么强的一致性配置数据短时间不一致(比如几秒到几十秒)其实是可以接受的,因为配置变更本来就不是频繁的,而且,即使个别节点暂时用旧配置也不会出大问题反而配置中心的可用性更重要,如果配置中心不可用服务拿不到配置直接就启动不了,或者运行出问题影响更大,所以我们其实应该选AP系统而不是CP系统我们当初只看到了一致性的重要性没充分评估可用性的重要性也没考虑网络分区的场景下CP系统不可用的代价导致选错了方案。
后来我们把配置中心的存储从ZooKeeper换成了一个AP的方案(基于最终一致性的分布式存储)并且在客户端加了本地缓存,即使配置中心不可用客户端也能用本地缓存的配置继续工作不会,因为配置中心不可用而影响服务改完后系统的可用性大大提升再也没,因为配置中心的问题导致大面积服务故障这次经历给我的教训是选CP还是AP不能拍脑袋要根据业务场景仔细评估一致性和可用性哪个更重要以及网络分区场景下的代价不要以为选了CP就万事大吉CP的代价是可用性这个代价可能很大。
三、坑2:以为AP就是数据随便不一致结果业务出问题
有了上一次的教训后我对AP系统有了好感觉得AP好可用性高不会,因为网络分区就不可用后来做一个用户评论系统的时候,我们就选了Cassandra作为存储,因为Cassandra是典型的AP系统高可用高吞吐适合评论这种写多读少的场景我们觉得评论数据短时间不一致没关系用户发了评论晚个几秒看到也无所谓AP正合适。
结果上线后又出问题了有用户反馈说自己发了评论,但是刷新后看不到,或者看到的评论数不对有时候评论发了两次才显示我们排查后发现是,因为Cassandra的最终一致性导致的用户发评论的时候,写到了一个节点,但是读的时候,读到了另一个还没同步到数据的节点,所以读不到刚发的评论,而且,因为我们设置的读写一致性级别比较低(写ONE读ONE)所以不一致的概率比较高用户体验很不好很多用户以为自己评论没发成功就反复发导致出现很多重复评论业务出了问题。
这次问题给我的教训是AP系统的最终一致性不是说数据可以随便不一致也不是说业务完全不用管一致性最终一致性只是说系统会在一段时间后达到一致,但是在达到一致之前,数据是可能不一致的而这个不一致的时间窗口和概率对业务的影响需要仔细评估不是所有业务都能接受的我们当初以为评论晚个几秒看到没关系,但是实际上,用户发了评论立刻刷新看不到就会觉得系统有问题体验很差,而且会导致重复提交等业务问题这个影响比我们预想的大。
后来我们调整了Cassandra的读写一致性级别把写和读的一致性级别调高(写QUORUM读QUORUM)这样能保证读到最新的数据(因为写了大多数节点读也读大多数节点一定能读到最新的)牺牲一点性能和可用性换取更好的一致性,同时我们在客户端加了写后读的处理用户发了评论后立刻读的时候,优先从刚写的节点读,或者加个短暂的延迟再读避免读到旧数据,另外我们还加了防重复提交的机制避免用户,因为看不到评论而反复提交改完后用户体验好了很多再也没出现发了评论看不到的问题。
这次经历给我的教训是AP不是银弹最终一致性不是不用管一致性而是要在可用性和一致性之间,做权衡根据业务需求调整一致性级别和处理策略把不一致的影响控制在业务可接受的范围内不要以为选了AP就可以,不管一致性AP的代价是一致性这个代价也可能导致业务问题需要仔细处理。
四、坑3:忽略了网络分区的真实场景以为P离我们很远
很多人在学CAP的时候,都觉得网络分区是一个理论上的场景离实际生产环境很远我们的网络很稳定不会出分区,所以不用太在意P我以前也是这么想的直到经历了几次真实的网络分区故障才改变了这个想法。
第一次遇到真实的网络分区是在一个双机房部署的项目我们的服务部署在两个机房A和B之间,用专线连接平时专线很稳定没出过问题我们也没太在意机房之间,的网络分区问题结果有一次施工把两个机房之间,的专线挖断了两个机房之间,完全无法通信出现了网络分区这时候我们的分布式系统就出问题了我们用的一个分布式数据库是CP的网络分区后为了保证一致性少数派的那个机房的节点就不可用了只能多数派的机房能正常服务而我们的流量是两个机房都有的结果少数派机房的用户就完全无法访问服务了影响了不少用户那次故障持续了几个小时直到专线修好才恢复影响很大。
那次故障让我深刻认识到网络分区不是理论场景是真实会发生的,而且一旦发生影响很大专线会被挖断交换机会挂路由器会出问题防火墙配置会错DDoS攻击会导致网络拥塞这些都可能导致网络分区在生产环境中网络分区,虽然不是天天发生,但是一年遇到个一两次很正常而每次发生都可能导致大故障,所以设计分布式系统的时候,必须考虑网络分区的场景不能假设网络永远稳定P是必须考虑的。
后来我们针对网络分区做了很多改进,比如在双机房部署的时候,采用AP的分布式存储允许网络分区时两个机房都能独立工作牺牲短时间的一致性保证可用性等网络恢复后再做数据同步和冲突解决,同时我们也做了流量调度网络分区时能把少数派机房的流量切到多数派机房减少影响,另外我们还定期做网络分区的混沌工程演练(之前,文章提过的混沌工程)主动模拟网络分区看看系统的反应发现问题提前修复这些改进大大提升了系统应对网络分区的能力后来再遇到网络问题影响就小很多了。
这次经历给我的教训是不要忽略网络分区的真实场景不要以为P离我们很远在分布式系统中网络分区是必然会发生的只是时间问题设计系统的时候,必须考虑网络分区下的行为是保C还是保A要根据业务需求做选择,并且要有应对网络分区的预案和演练不要等网络分区真的发生了才发现系统扛不住那就晚了。
五、坑4:混淆了强一致性和最终一致性用错场景
还有一个常见的坑就是混淆强一致性和最终一致性用错场景很多人对一致性的理解比较模糊以为,只要数据最终会一致就没问题,但是实际上,不同业务场景对一致性的要求差别很大有的场景必须强一致性有的场景最终一致性就够了,如果搞混了用错了就会出问题。
我之前,做一个电商系统的时候,就犯过这个错我们的商品库存系统用了一个最终一致性的方案,因为觉得库存数据短时间不一致没关系反正最终会一致结果上线后就出问题了大促的时候,用户抢购商品,因为库存数据在不同节点不一致有的节点显示,还有库存用户下单了,但是实际库存已经没了导致超卖很多用户付了钱,但是没货发不了只能退款补偿影响很不好那次问题就是,因为我们把必须强一致性的库存场景用了最终一致性的方案导致超卖。
库存这种场景对一致性要求非常高必须强一致性,因为涉及到钱和用户的核心利益超卖了就是事故必须避免而像商品评论用户头像浏览记录这种场景对一致性要求就低很多短时间不一致没关系最终一致就够了不同场景要区别对待不能一刀切都用最终一致性也不能都用强一致性(那样性能和可用性代价太大)。
后来我们把库存系统改成了强一致性的方案用分布式锁,或者原子操作保证库存扣减的一致性避免超卖,同时我们也梳理了整个电商系统的各个业务场景对一致性的要求分成了几个等级,比如核心交易(库存订单支付)必须强一致性重要数据(用户信息商品信息)可以短时间最终一致非核心数据(评论浏览记录推荐)可以最终一致性,然后根据不同等级选择不同的存储和一致性方案这样既保证了核心场景的一致性又在非核心场景兼顾了性能和可用性比较合理。
这次经历给我的教训是要清楚区分强一致性和最终一致性的适用场景根据业务需求选择合适的一致性级别不要搞混核心业务涉及钱和用户核心利益的必须强一致性非核心业务能接受短时间不一致的可以用最终一致性兼顾性能和可用性不要一刀切也不要想,当然要仔细分析每个场景的业务需求和不一致的代价再做选择。
六、坑5:只关注理论忽略了工程实现的细节
还有一个坑就是只关注CAP的理论忽略了工程实现的细节很多人觉得我知道CAP了选了CP或者AP就没问题了,但是实际上,工程实现的细节非常重要同样是CP系统,或者AP系统不同的实现不同的配置表现可能差别很大,如果工程实现没做好,即使理论上选对了也会出问题。
我之前,用Redis Cluster的时候,就踩过这个坑Redis Cluster在默认配置下是偏向AP的网络分区的时候,会牺牲一致性保证可用性,但是它也可以通过配置调整一致性级别我们当时用Redis Cluster做缓存觉得缓存嘛AP就行数据不一致没关系大不了缓存失效重新从数据库加载结果有一次Redis Cluster的某个节点出问题发生了主从切换在切换过程中有短暂的数据不一致我们的应用读到了旧的缓存数据而这个缓存存的是用户的余额(我们当时图方便把用户余额也放缓存了)结果导致用户看到的余额不对用旧余额做了交易出了问题,虽然最后通过对账修正了,但是影响很不好。
这次问题表面上看是缓存数据不一致导致的,但是实际上,是我们工程实现没做好第一我们不应该把用户余额这种核心数据放缓存还允许不一致第二我们对Redis Cluster的配置和主从切换的细节了解不够没做相应的处理第三我们的应用读缓存的时候,没做数据校验和降级直接就用了缓存数据这些都是工程实现的细节不是CAP理论能覆盖的,但是,恰恰是这些细节决定了系统实际的表现。
后来我们做了很多改进把用户余额这种核心数据从缓存移除直接读数据库,或者用强一致性的缓存方案,同时我们深入研究了Redis Cluster的配置和主从切换机制调整了相关配置,并且在应用层加了缓存数据的校验和降级逻辑读到可能不一致的缓存数据时能识别,并且降级到读数据库避免用错数据,另外我们也建立了缓存数据的对账机制定期检查缓存和数据库的一致性发现不一致及时修复这些工程细节的改进大大提升了系统的可靠性。
这次经历给我的教训是CAP理论只是指导工程实现的细节更重要同样的理论选择不同的工程实现和配置系统的实际表现可能天差地别不要以为懂了CAP理论就能做好分布式系统要深入了解你用的分布式组件的实现细节和配置做好应用层的处理和兜底才能真正保证系统的可靠性理论结合实践才是王道。
七、CAP实战的一些心得和建议
踩了这么多坑也总结了一些CAP实战的心得和建议分享给大家希望能帮大家少走弯路。
1. 没有完美的选择,只有合适的权衡
CAP理论告诉我们没有系统能,同时满足CAP三者,所以不要追求完美的系统什么都想要要根据业务需求做权衡选CP还是AP取决于你的业务更看重一致性还是可用性以及网络分区时哪个代价更小没有绝对的对错,只有合适不合适要结合业务场景仔细分析再做选择。
2. 网络分区是必然的必须考虑
不要假设网络永远稳定网络分区在分布式系统中是必然会发生的只是时间问题设计系统的时候,必须考虑网络分区下的行为是保C还是保A要有明确的策略和预案,并且要定期做网络分区的演练(混沌工程)验证系统在网络分区下的表现发现问题提前修复不要等网络分区真的发生了才发现系统扛不住。
3. 区分业务场景不要一刀切
不同业务场景对一致性和可用性的要求差别很大要区分对待不要整个系统都用同一种一致性级别核心业务(涉及钱用户核心利益)必须强一致性重要业务可以短时间最终一致非核心业务可以最终一致性根据不同场景选择不同的存储和一致性方案这样既保证核心又兼顾性能和可用性比较合理。
4. 最终一致性不是不用管一致性要处理好不一致窗口
选了AP最终一致性不代表不用管一致性了要清楚不一致的时间窗口有多长概率有多大对业务的影响是什么,并且要做相应的处理把影响控制在可接受范围内,比如调整读写一致性级别加写后读处理防重复提交数据对账和修复等等不要以为最终一致就万事大吉不一致窗口内的问题可能导致业务故障要重视。
5. 工程实现细节比理论更重要
CAP理论只是基础工程实现的细节更重要要深入了解你用的分布式组件的实现原理配置参数和已知问题做好应用层的处理和兜底,比如超时重试降级熔断数据校验对账等等这些工程细节决定了系统实际的可靠性不要只停留在理论层面要深入实践把细节做好。
6. 定期演练和验证不要想,当然
分布式系统很复杂很多问题不真正遇到是发现不了的不要想,当然地以为系统在网络分区下会怎么怎么样要定期做演练和验证,比如混沌工程主动注入网络分区节点故障等看看系统实际的表现是不是符合预期发现问题及时修复这样才能真正保证系统在异常场景下的可靠性。
八、写在最后
以上就是我这些年在CAP理论实战中踩过的坑和总结的心得从最开始以为选了CP就万事大吉到后来发现CP的可用性代价很大从以为AP就是随便不一致到后来发现AP的一致性问题也会导致业务故障从以为网络分区离我们很远到亲身经历真实的网络分区故障从混淆强一致性和最终一致性到学会区分业务场景选择合适的一致性级别从只关注理论到重视工程实现细节每一个坑都是交了学费才学到的经验。
CAP理论是分布式系统最基础也最重要的理论之一,但是理论和实战差距很大很多东西,只有真正在项目中用过踩过坑才能深刻理解希望我的这些踩坑经历和心得能帮大家对CAP理论有更深入的理解在实际项目中少走弯路做出更可靠的分布式系统。
最后用一句话结束这篇文章:"CAP理论不是选择题而是权衡术懂理论更要懂实战踩过坑才能真正掌握分布式系统的精髓。"
愿大家的分布式系统都能稳定运行少出故障用户体验越来越好。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录