什么是资损
资损通常来讲是指领取场景下的资金损失,这里能够从两个维度看 :
- 用户角度:多扣用户款导致用户资金损失,此问题个别须要通过客服等渠道反馈,能够把多的钱退给用户,然而很大水平上损失了用户体验;
- 公司角度:次要是多出金、多出货、多充值等状况,个别这种损失很难追回,这就是实打实的产生了资产损失。
比方对于一个电商业务可能波及到上图各种业务,业务之间都存在下发、回调、消息传递等各种逻辑或者状态同步,如果其中因为一些交互操作失落导致库存异样、资金结算换算异样、单据流程未完结异样、逻辑触发反复申请,并发管制处理不当等等,最终导致资产或资金的损失。
对于状况预防伎俩除了事先的一些列严格测试,预先剖析的优化补救,也能够通过事中进行监控,得物在这方面就是通过自研的DCheck进行防控。
初识DCheck
此零碎由“交易&稳定性”团队主导的,次要是心愿及时的发现数据的问题,保障数据稳固的运行,尤其是波及到资损的场景,为了做到实时无效的监控,在此背景下搭建了准实时核查零碎DCheck,此平台基于Mysql的binglog监控,以及MQ的订阅的信息流技术手段,通过配置触发条件、规定和工作运行以及告警,来确保各个业务上下游业务间的状态一致性,进出扣款金额计算后的准确性。
深刻DCheck
性能层级
架构逻辑
概念
- 主题:逻辑数据库 或 MQ订阅
- 事件:Update/ Insert类型操作 或 MQ自定义消
- 子事件:事件所返回的数据的第一层过滤
- 脚本池:有filter和check两个继承办法,filter解决二层数据过滤,check业务上下游逻辑判断
- 规定:触发和检测外围执行局部
DCheck
对于此零碎的性能和应用简略演示如下:
核查配置
主题治理
- TOPIC:库名
- 主题编码:表名
- 主题名称:中文的表名
- MQ实例地址: 对于binlog填*
事件治理
对于binlog而言,分成了INSERT和UPDATE两种,依据上主题配置主动生成无需创立。
子事件治理
荡涤的数据的过滤层,此处在规定配置中,返回‘TRUE’的才会进入下一层,如:
if( obj.status.toInteger() == 10000 && (obj.type.toInteger() ==101 || obj.type.toInteger() ==301) ) return 'TRUE';
如果想所有都返回,就间接 return 'TRUE'。
脚本池治理
所有被执行脚本应用的是groovy进行编写的,次要是BaseScript实现filter和check两个办法,外部能够参考脚本库:
- Filter通过事件过滤后须要脚本再进行二次过滤的操作,次要是一些不能简略从donCleanData进行判断,须要进行正向逆向获取其余数据,或是一些早为简单的逻辑。
Check验证逻辑解决办法,次要是就是进行上下游同步状态验证,简单后果计算(尤其是进出帐扣款等方面)比拟。
代码例子:import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import groovy.util.logging.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * DCheck:沉着期内-平台客服勾销订单-退买家领取金额 */ @Slf4j @Service class CheckRefundPayForLess30min implements BaseScript { @Resource private OrderDevOpsApi orderDevOpsApi; @Resource DCheckApi dCheckApi; @Resource private PayServiceFeignClient payServiceFeignClient String logTag = "TAG_CheckCrossAndOverSeaRefundPayForLess30min:{}" // 1.关单工夫-领取工夫<30分钟 @Override boolean filter(JSONObject doneCleanData) { // 查问领取工夫 String unionId = doneCleanData.getString("order_no"); String payTime = getOrderData(unionId,"payTime", DevOpsSceneEnum.FORWARD_PAY); long modifyTime = doneCleanData.getDate("modify_time").getTime(); long diffTime = modifyTime - Long.valueOf(payTime) if (diffTime < 30 * 60 * 1000){ return true; } log.info(logTag,"===>有合乎数据进入Check") return false; } @Override String check(JSONObject doneCleanData) { String subOrderNo = doneCleanData.getString("sub_order_no"); Result<List<String>> listResult = dCheckApi.queryPayNoBySubOrderNo(subOrderNo); if(listResult == null || listResult.getData() == null) { return "依据正向查问接口通过子订单号查问领取流水号数据为空"; } if(listResult.getData().size() > 1){ return "依据正向查问接口通过子订单号查问领取流水号多条数据,请查看是否须要优化逻辑"; } String outPayNo = listResult.getData().get(0); RefundQueryRequest refundQueryRequest = new RefundQueryRequest(); refundQueryRequest.setPayLogNum(outPayNo); Result<List<RefundBillDTO>> resp = payServiceFeignClient.queryRefundsByPayLogNum(refundQueryRequest); // 判断领取查问数据是否为空,如果为空间接报数据谬误,以及是否查问到了多条数据 if (resp == null || resp.getData() == null) { return "上游数据为空:领取退款查问(依据领取流水号)"; } else if (resp.getData().size() != 1) { return "上游数据为多条请确认逻辑:领取退款查问(依据领取流水号)"; } // 检查点逻辑判断1: 状态为打款胜利 if (resp.getData().get(0).getStatus() !=2 ){ return "校验领取打款状态非2"; } // 逻辑校验点2:交易退款和RPC查问的金额统一,否则告警 if (resp.getData().get(0).getAmount() != doneCleanData.getLong("amount")) { return "校验交易退款金额和领取打款金额不统一"; } return "SUCCESS"; } // 数据库查问对应字段值 String getOrderData(String unionNo,String key,DevOpsSceneEnum devOpsSceneEnum){ // 外部办法省略.... return value; } }
规定配置
规定配置为上述所有根底配置的组合以及真正的运行外围,次要两大块,根底信息和降级策略:
根底信息:子事件(反对搜寻和多个选中)+ 脚本类(搜寻抉择) = 触发和执行逻辑,其余为辅助配置信息依照各自域和需要配置
降级策略:
- 采样百分比:线上流量采样百分比,后期测试或者对业务有很大影响的须要管制流量,不能为100%。
- 首次延迟时间:触发执行延迟时间,在业务流程,数据同步有些会有些提早,为了防止因为提早导致的状态不同步问题,倡议设置肯定的提早比例,个别10秒左右。
- 最大超时工夫和失效工夫:规定无效工夫配置。
工具应用
核查异样
针对check异样的数据,个别首先会发到配置的告警飞书群和配置的集体,点击即可跳转到此页面,次要看谬误的具体数据,经确认后是脚本或者局部数据问题的,优化后“重发”能够标记为解决,如果是确定是问题的,则定位“资损”问题。
Mock
因为本地脚本调用一些RPC接口,目前还没有好的方法能在本地进行debug,所以就须要通过先配置规定后,应用mock进行调试,次要用到的是规定调试,抉择指定的规定,搜寻或者发明合乎dcheck场景的json格局的申请参数,提交申请查看响应后果即可。
这里有个问题,因为dcheck外部逻辑对一些零碎异样脚本做了对立解决,很多时候没法看到具体的起因,就是失败或者逻辑外通过,这就须要须要在脚本中多加些打印日志,而后通过日志平台去查看具体逻辑问题。
一些应用技巧
规定配置技巧
- 规定中事件是能够多选的,所以对于事件不同,但check雷同或者类似的脚本解决逻辑能够归并,缩小规定保护量。
- 触发数据能够在事件进行配置的,尽量少用脚本filter解决,代码个别解决须要非触发数据外逻辑。
Groovy的闭包应用
脚本数据处理有会有很多list,key-value的解决,能够充分利用groovy的闭包个性,大大简化java语言的简单解决逻辑。
举例:如果如下数据格式的后果返回objectList:
[{id=10086, refundNo=RE10086, orderNo=100888, userId=15206, bizType=110, payTool=0, payStatus=404,amount=100, feature=, isDel=0, createTime=2021-05-11 21:39:34.000, modifyTime=2021-05-11 21:39:34.000, moneyFrom=1, currency=, countryCode=},{id=10087, refundNo=RE10087, orderNo=100999, userId=15206, bizType=202, payTool=0, payStatus=404, amount=400, feature=, isDel=0, createTime=2021-05-11 21:39:34.000, modifyTime=2021-05-11 21:39:34.000, moneyFrom=1, countryCode=}]
- filter进行条件判断,能够应用any
def filterResult = objectList.any{it.bizType in [110,119] && it.payStatus == 404};
return filterResult - check获取某个符合条件的值,能够应用find
def mount = objectList.find{ it.type=5}.amount
更多groovy个性能够参考文章:https://www.jianshu.com/p/5d3...
一些平台有余
没有比拟不便的调试形式
目前本地脚本库没有能够运行调试的环境,尽管有Mock工具,但也是须要先配置事件,上传脚本配置后能力进行调试,同时脚本逻辑问题也须要去日志一直改脚本的加日志的形式去查看,另外最初如果在测试环境了进行了调试通过,还要去线环境再反复配置一遍流程。
倡议:线上在子事件、脚本、规定减少的页面就减少一个Debug按钮,能够间接通过给定参数或者抓取符合条件的一条数据进行调试给出后果,最好也能给出这部分日志。
脚本池Filter和check办法放在一起会有很多冗余
在开发脚本和配置中发现其实好多filter逻辑雷同,抑或check逻辑雷同,但因为脚本中放在一起,就会产生穿插的逻辑编写,无奈做到无效的公共剥离。
倡议:Filter 和 check 拆分,并且作为公共的池,规定中独自配置,更能够抉择模式为援用或者导入,导入的反对次要不便逻辑大部分雷同,个别参数不雷同下疾速批改配置规定上线。
真的问题呈现时没有熔断机制
尽管目前整个平台的理论还待察看,但其中后续如果平台成果是很好的,真的产生大批量数据问题或者资金损失问题,平台的机制也只是产生问题的正告,而后技术染指,实质上还是后置的解决的形式,如一开始讲的这种后置的解决,并不能达到及时止损的目标。
倡议:后续在平台更加精确时,应该思考与要害域熔断联动,及时禁止损失的产生。
平台应该增高低线开关
目前规定的执行不执行,只能通过编辑管制流量0,或是执行工夫来敞开,也没有批量的,操作上有些不便。
倡议:减少高低线开关,减少批量操作如:流量大小,开关,正告人等
平台能够思考减少流量的动静判断机制
因为好多check点是通过各个域的接口进行操作,在流量大的时候,尤其是要害的业务,可能对业务产品很大影响,或者在某接口兼容不好,零碎异样导致大量非常规一场报错的时候,各方人会炸掉。
倡议:规定分等级,外围业务能够做高级配置,遇到上述问题触发主动调节降流量,对于问题已复原可主动减少流量。
文|大奇
关注得物技术,携手走向技术的云端