【转】分布式场景下的资损防控

1 、资损的定义

资损是在指在业务活动中,业务规则和实际资金流动不一致,导致业务参与方中的任何一方或多方遭受了资金损失。简单理解就是系统的某个功能出现了BUG,导致用户或者公司出现了资金的损失,比如在营销过程中多给某个用户发了10元红包,或者是用户领取了10元红包无法使用,用户在支付时看到的订单金额是100元,结果支付了101元或99元,这些情况都属于资损,用户支付了101用户资损1元,用户实际支付了99元公司资损了1元。

为什么要重视资损防控?因为资金损失如果金额巨大会直接毁掉一个业务,比如在某个业务一次运营活动中把9折的优惠券配成1折,然后发给了几百万个用户,这里面可能涉及到几千万的资金损失,但是这个业务本身就没有盈利几千万,所以只要业务涉及到资金流动一定是非常重要的事情。

2、如何进行资损防控

资损防控首先要定一个目标,对于核心业务常用的目标是1-5-10,既1分钟发现、5分钟定位、10分钟解决。比如把发给用户的优惠券折扣配错了,我们能在一分钟之内发现这个错误,在5分钟内能定位是优惠券配置错了还是系统其他BUG,在10分钟内把优惠券发放停掉。

要做到1-5-10这个目标,事前规避问题、事中快速定位问题、事后最小成本解决问题,这三个目标称为资损防控的三道防线。

2.1、第一道防线-事前规避

第一道防线事前规避,主要是在通过建立编码规范和发布规范在事前规避各种可能出现的资损问题。

2.1.1、建立编码规范

你可以根据业务需要建设对应的编码规范,我这里举几个例子:

业务代码不允许使用ThreadLocal。如果你的系统使用线程池,线程池里的线程是共享的,使用ThreadLocal存放数据,如果因为异常等原因没有清楚掉原来存储的数据,会引发各种奇怪的问题且非常难排查,我们已经遇到过多次线上问题,所以统一规定业务代码不使用ThreadLocal。比如会出现用户A的状态和用户B的状态串掉,发给A的优惠券B可以使用。

幂等控制。所谓幂等就是第一次请求和第二次请求得到的结果是一样的,通常需要设计一个幂等号出来,一般使用业务阶段+唯一业务号来做幂等号,唯一业务号比如支付单和交易单等,业务阶段包括支付、打款、售中退款和售后退款等。因为在这几个业务阶段过程中支付单都是一样,需要增加业务阶段来让幂等号唯一。

无状态。服务器一定要无状态,既在分布式环境下,某个应用部署在多个服务器中,服务器A和服务器B不保存状态型数据,比如用户上传文件后,把文件保存在A服务器中,A服务器就有状态了,但是查询的时候请求到了B服务器,就查不到对应的文件,一般建议文件存储统一放在分布式存储中,保证服务器无状态。

2.1.2、建立发布规范

系统发布规范包含三要素,可灰度、可监控和可回滚

可灰度是在发布系统或发布活动的时候逐渐切流,灰度不只是按照系统进行灰度,还可以按照用户、商家或银行机构等维度进行灰度。在灰度的过程中观察系统是否有异常,比如有个运营活动要给几千万用户发放优惠券,千万不要一次性给几千万用户发放,一旦发错就是影响几千万用户,而是先发给几个内部用户,观察下看看有没有异常,再发给几百个用户,再观察下有没有异常,尔后发给几万个用户、几十万用户、几百万用户和全部用户。

可监控是有问题能通过系统监控到,监控通常分为业务监控和系统监控、拿支付业务举例说明,业务监控指标包括每秒的交易创建笔数、支付笔数、支付金额、退款笔数和退款金额等,监控这些业务指标有没有在短时间内增长或下跌10%,如果有陡增或陡降,很有可能是系统有变更导致了问题,这里需要注意的是业务暴增也可能是系统问题,比如不应该打开的交易场景打开了,引发了支付流量增加。系统监控需要关注的指标有LOAD、TPS、QPS、TPM、磁盘IO和磁盘容量等。

可回滚是所有的变更都可以回滚到最初的状态。

2.2、第二道防线-事中定位能力

事中定位能力包括找变更点和排查日志两种方法。

2.2.1、找变更点

大部分线上问题都和线上变更有关系,比如发布一个功能或推送一个配置上线,这些都属于线上变更。所以快速定位问题的关键是首先找到变更点是什么,然后评估下业务流量陡增是不是和这个变更点有关系。有时候一次发布可能包含多个变更点,你也可以用发布系统管理所有的变更点,当出现变更的时候,自动在群里同步变更内容。如果你没有发布系统,也可以让开发人员在变更前在群里同步所有变更内容,同步的变更内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

变更点:1002XX功能上线

变更类型:系统+SQL

变更系统:XX系统

变更人:张三

变更环境:生产

变更开始时间:10月2日 12点00分

变更结束时间:10月2日 14点00分

2.2.2、排查日志

快速排查日志要做到两点,管理日志级别和日志要打全。

管理日志级别。线上有问题首先是排查系统错误日志和系统指标是否异常,分析这些错误和异常是否和线上问题有关联,要提高排查日志的效率,就必须保证线上的错误日志尽量少,这个需要你日常就做好日志维护,我的要求是线上只要有错误日志就必须排查,如果确认不是线上问题也打了很多ERROR日志,那么出现真正线上问题的时候就会被这些错误日志淹没,非线上问题可以把日志级别从ERROR改成WARN级别,这样做到好处是只要线上有错误日志就很有可能是线上问题,增加大家运维保障的敏感度,如果线上全是错误日志,就像“狼来了”的故事一样,没有人相信这些错误是有线上问题。

日志要打全。要做到通过日志排查问题,代码中很多关键的地方必须打印日志,比如每个关键方法的入参、异常和出参等。所有方法的入参必须增加强制校验,参数有问题直接异常,避免异常扩散,导致定位问题困难。没有日志排查问题,你就必须通过看代码一行一行的分析和推理,这个效率和准确度都非常低,而且当出现线上问题时压力也非常大。多打点日志没有坏处,现在的存储成本也相对不高,如果遇到大促流量非常大时,可以关掉细节日志打印,保留关键日志打印。

2.3、第三道防线-事后应急能力

事后应急的核心目标是最小成本解决问题,包括回滚应急和降级应急能力。

2.3.1、回滚应急

回滚应急包含回滚管理和回滚效率两部分。

回滚管理包含回滚内容管理和回滚顺序管理。回滚内容包括代码回滚、缓存回滚、配置回滚和数据库回滚等。一次回滚可能涉及到多个系统的代码回归、多个SQL回滚,且各种回滚之间存在先后顺序关系,必须按照一定的顺序逐个回滚,如果回滚顺序搞错了,有可能引发新的故障。

回滚效率就是回滚的速度,比如我在提交变更SQL的时候,也会同时提交一份回滚SQL,保障这个数据能快速回滚,否则你把数据库里的数据更新错了,也不知道原来的数据是什么,这个就非常麻烦了。我记得我前公司的一位同事有个非常好的习惯,就是在他电脑上的文件基本上只新增不删除,不用的文件统一归档到一个地方,他的这个习惯影响了我很多年,我现在写文档都会保存多个版本,在关键时候的确救了几次场,我想这也是在建设一种回滚能力。

2.3.2、降级应急

降级能力就是通过推送一个配置项把某个功能关闭,或把某个场景流量直接关闭。降级应急分为无损降级和有损降级。

无损降级,就是降级之后对业务无任何影响。假如你做了一次架构升级,把流量引入到了新链路,但是运行了几个小时在新链路上发现了一个BUG,这个时候你通过降级开关,降级了新链路,把流量导入到了老链路,无论新老链路都能支持业务。

有损降级,就是降级之后对业务有影响。你推送了一个降级开关把某个场景的流量关掉了,那么对业务来说交易量就变少了,这就是有损降级,但是为什么要执行有损降级?因为这个场景如果不关闭会导致更大的资损,资损和流量减少相比影响更大,所以必须通过降级关闭这个场景。

3、资损演练

资损类的问题一年也很难遇到几次,但是遇到了一次可能就是致命的。如果我们把三道防线都建立好了,我怎么知道三道防线的有效性和正确性,怎么能检测出我的三道防线是否能实现1-5-10的目标呢?

我们需要模拟真实资损场景进行演练,定期组织资损演练主动对我们系统进行资损攻击和错误注入,比如在线上故意配置几张错误折扣的优惠券发给内部用户,检测下系统是否能做到1分钟发现这个资损问题,5分钟定位出问题,10分钟内将这个问题止血掉。如果做不到,再看看是哪些防线有问题。进行资损演练的人最好和布控资损防线的人分开,因为这样他就能天马行空的进行攻击,从而增加未知错误的概率,能更好的检测三道防线的有效性。

这个世界上最难防御的是未知的错误,如果你已经知道会出现某个错误,那么你可以使用监控和核对等手段防止错误发生。所以我们需要通过演练来创造未知错误