最佳实战-分布式一致性解决方案
接触了业界一些针对于分布式服务的一致性解决方案后,接下来总结下自己在面对一致性问题的一些方案和思路,其实很多时候我理解分布式服务的一致性一定要以可靠性为基础、简洁性为目标去考量方案,当然前提是满足我们的分布式需求、高并发高性能以及吞吐量为前提。总结了几种比较高效的处理模式。
•主动查询模式:所有的操作都提供一个查询接口,用于向外部输出操作执行的状态,服务操作的使用方可以通过查询接口而得知服务操作执行的状态。然后根据不同的状态来做不同的处理操作。那么这种模式通常可以针对于我们Q2的问题(同步调用超时),也就是同步调用超时的场景下使用 。
策略:ServiceA在请求ServiceB,execute(params..)进行写操作的同时,要带着status状态来标示操作是否成功,保障在出现超时熔断的时候,ServiceA可以启动主动查询ServiceB的操作status值来判断结果;
1.性能要求高,即时性要求不高,可以使用数据库来进行status状态的存储及主动查询的目标;
2.性能要求高,即时性要求也高,那么就不能使用数据库来作为主动查询方案的目标,写库的同时把status放入内存中,按照时间持久化原则后期在使用读库;
实际场景解决方案:一个操作流程中包含三个关联操作,ServiceA 操作本地数据库table1做持久化操作,成功后请求ServiceB对数据库table做持久化操作,请求Timeout100MS,如果ServiceB操作成功并返回结果那么ServiceA 在根据ServiceB返回的数据对table2做持久化操作;
比如系统A调用系统B但是出现系统A超时的情况,这个时候我们的系统A主动调用发起查询接口,去确认当前调用的处理方(也就是系统B)的真实状态,如果系统B执行的预设任务返回成功,则系统A可以继续执行其他后续逻辑;如果系统B执行的预设任务返回失败,我们可以根据需求进行重试和直接对上游(也就是使用方)返回失败等可确定性的操作。如果系统B执行的预设任务返回未知状态,我们也可以根据实际情况,具体需求去判断到底需不需要重试,重试的话系统B有没有做到幂等,如果调用时间不可容忍也可以采用快速失败策略,然后对系统B去调用业务的逆向操作(或者记录逆向操作日志),然后后续进行回滚和补偿系统B的执行结果,最终达到双方的一致状态。
•补偿模式:通常与主动查询模式结合使用,目的都是为了实现上下游的服务最终一致性的努力确保机制。
•异步确保模式、可靠性消息模式:(对即时性和实时性要求不高的时候会采用)
异步确保模式、可靠消息模式:这两种模式也是互联网行业中经常需要使用的经典模式,很多时候我们的使用方对响应时间 要求不太高、或者说不需要特别强调实时性的场景,这一类的操作我们经常采用异步化、或者解耦的方式,把其从主流程(核心链路上摘除) ,或者说我们划分好业务边界,然后再可以容忍的窗口期内做异步确保和发送可靠性消息模式。这个方案最大的好处是能够对高并发流量进行削峰,从而对服务上能够提供解耦。比如我们电商系统中的物流、配送等,金融系统中的支付、计费入账等等。
当下单的主信息落库成功后就返回下单成功的消息给用户,将其他消息出入JOSN中,异步分别落库;注意要在给ServiceB传递消息的是要做到可靠性消息传递模式,具体ServiceB可能收到很多次消息那么ServiceB自己做幂等性来保障;
•定期校对模式 :这种方式多用于互联网金融行业,一般都是针对商户与平台与银行等第三方金融支付平台之间的一个经典场景,因为涉及资金安全,所以对于互联网金融行业会对其进行多重的一致性保证机制,比如商户交易对账、系统间一致性对账、财务对账等等。这种也是针对于场景而言的。一般对实时性要求最低,但是对准确性、一致性要求最高。
理论上来讲Q2(同步)、Q4(掉单场景)都可以通过异步确保、可靠性消息、定期校队模式去解决,但是具体实际问题还得具体分析和评估合理性。
缓存一致性的问题
•在大规模分布式的系统中,一个场景的核心需求就是亿级别的读需求,显然关系型数据库不是解决高并发的最佳方案,互联网行业通常的做法就是使用缓存来抗住读流量,那么如何保障缓存和数据库的一致性,我也大概汇总和划分几点必须要做和清楚的事情:
•如果性能要求不是非常高,则尽量使用分布式缓存(reids),不要使用本地缓存。
•写缓存时数据一定要完整,如果缓存数据一部分有效,另一部分无效,则宁可在需要时回源数据库抽取,也不要把部分数据放入缓存。
•使用缓存就牺牲了一致性,为了提高性能,数据库与缓存只能保证且只需要保持弱一致性即可,否则其他的做法即违背了缓存的目的也浪费了太多的系统性能。
•读的顺序是先读缓存再读数据库,写的顺序一定要先写数据库再写缓存。
在互联网企业不会使用分布式事务(使用策略解决),也不会使用事务。