(2) 订单号机制。用户的每一次符合资格的发货请求,都生成一个订单号与之对应,通过它来确保1个订单号,只发货1次。这个方案虽然比较完善,但是,它是依赖于发货服务方配合做“订单号发货状态更新“的,而我们的发货业务方众多,并非每一个都能支持”订单号更新“的场景。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
(3) 自动重试的异步发货模式。用户点击领取礼包按钮后,Web端直接返回成功,并且提示礼包在30分钟内到账。对于后台,则将该发货录入到发货队列或者存储中,等待发货服务异步发货。因为是异步处理,可以多次执行发货重试操作,直到发货成功为止。同时,异步发货是可以设置一个比较长的超时等待时间,通常不会出现“超时成功”的场景,并且对于前端响应来说,不需要等待后台发货状态的返回。但是,这种模式,会给用户带来比较不好的体验,就是没有实时反馈,无法立刻告诉用户,礼包是否到账。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
4. 非订单号的特殊防刷机制某些特殊的合作场景,我们无法使用双方约定订单号方式,例如一个完全隔离独立的外部发货接口,不能和我们做订单号的约定。基于这种场景,我们AMS专门做了一种防刷的机制,就是通过限制read超时的次数。但是,这种方案并非完美解决重复发货问题,只是能起到够尽可能减少避免被刷的作用。一次网络通信,通常包含:建立连接(connect),写入数据发包(write),等待并且读取回包(read),断开连接(close)。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
通常一个发货服务如果出现异常,大多数情况,在connect步骤就是失败或者超时,而如果一个请求走到等待回包(read)时超时,那么发货服务另外一边就有可能发生了“超时但发货成功”的场景。这个时候,我们将read超时的发生次数记录起来,然后提供了一个配置限制次数的能力。假如设置为2次,那么当一个用户第一次领取礼包,遇到read超时,我们就允许它重试,当还遇到第二次read超时,就达到我们之前设置的阀值2,我们就认为它可能发货成功,拒绝用户的第三次领取请求。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
这种做法,假设发货服务真的出现很多超时成功,那么用户也最多只能刷到2次礼包(次数可配置),而避免发生礼包无限制被刷的场景。但是,这种方案并不完全可靠,谨慎使用。
在发货场景,还会涉及分布式场景下的CAP(一致性、可用性、分区容错性)问题,不过,我们的系统并非是一个电商服务,大部分的发货并没有强烈的一致性要求。因此,总体而言,我们是弱化了一致性问题(核心服务,通过异步重试的方式,达到最终一致性),以追求可用性和分区容错性的保证。
四、 服务降级,自动屏蔽非核心分支异常对于一次礼包领取请求,在我们的后端CGI会经过10多个环节和服务的逻辑判断,包括礼包配置读取、礼包限量检查、登陆态校验、安全保护等等。而这些服务中,就有不可以跳过的核心环节,例如读取礼包配置的服务,也有非核心环节,例如数据上报。对于非核心环节,我们的做法,就是设置一个比较低的超时时间。
例如我们其中一个统计上报服务,平均耗时是3ms,那么我们就将超时时间设置为20ms,一旦超时则旁路掉,继续按照正常逻辑走业务流程。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
五、 服务解耦、物理隔离虽然,大家都知道一个服务的设计,要尽可能小和分离部署,如此,服务之间的耦合会比较小,一旦某个模块出问题,受到影响的模块就比较少,容错能力就会更强。可是,从设计之初,就将每一个服务有序的切割地很小,这个需要设计者具备超前的意识,能够提前意识到业务和系统的发展形态,而实际上,业务的发展往往是比较难以预知的,因为业务的形态会随着产品的策略的改变而变化。在业务早期流量比较小的时候,通常也没有足够的人力和资源,将服务细细的切分。AMS从日请求百万级的Web系统,逐渐成长为亿级,在这个过程中,流量规模增长了100倍,我们经历了不少服务耦合带来的阵痛。
亿级Web系统的容错性建设实践 - 徐汉彬Hansion - 技术行者
1. 服务分离,大服务变成多个小服务