此文已由做者肖凡受权网易云社区发布。
html
欢迎访问网易云社区,了解更多网易技术产品运营经验。数据库
最近接了一个看上去很小,很容易实现的需求,然而在作的过程当中发现有些问题若是思考不全就很容易致使问题,这里给你们分享下。缓存
需求背景安全
考拉的订单风控主要是对接杭研的反垃圾服务,订单支付完成后会异步抄送给反垃圾那边。若是订单的支付方式是网易宝支付,则网易宝也会作风控检测。那么就须要一个地方来聚合反垃圾和网易宝的风控结果,当两方都认为经过时才通知主站订单放行,不然要等待另外一方的通知。并发
需求描述异步
聚合反垃圾和网易宝风控结果,将最终结果通知主站。分布式
这个需求看上去是很是简单的,不就是聚合下两边的通知么,找个地方存下来不就行了嘛,so easy!而后开始码代码了。首先想到要在当前的风控系统的通知接口上增长两个字段,一个用来表示调用方,1表示反垃圾,2表示网易宝;一个是用来表示支付方式,1表示网易宝支付,2表示非网易宝支付,而后就是要须要将这个通知存下来,缓存确定是不行的,数据可能丢掉,那就放在rds里面,新建一张表,包括订单id、反垃圾通知状态、网易宝通知状态、是否已通知主站等字段,而后风控通知来了以后就去表里面看看是否另外一方的通知已经来过,若是没有就新加一条记录,最后根据逻辑决定通知主站订单放心/拦截/关单。整个代码逻辑大概就是这样了,可是这里面会有不少问题,下面我深刻这个需求给你们分享下会碰到的问题。单元测试
问题1:若是两个通知同时到来怎么办?测试
初始的代码逻辑是这样:来了一个通知以后,根据订单id去表里查数据,若是不存在,则增长一条;若是存在,则判断另外一方的通知是否已经到达,已到达则更新数据,标记该订单已通知主站。这里很明显有一个竞态条件:若是两个通知并发的到达,都会发现数据库里面没有数据,都会去插入数据,而订单id是惟一索引,那么这里就会报错了。如何解决呢?常见的方式有分布式锁、额外的引入队列,简单点的可使用nkv的原子操做。考虑到这里并发的可能性不大,直接catch了重复插入的exception,而后catch块里面进行更新操做。.net
问题2:若是有一个通知一直不来怎么办?或者两个通知都不来呢?
任何第三方系统对于咱们来讲都是不可靠的,设计代码逻辑的时候是必定须要考虑第三方系统异常的状况的。针对这种状况,须要根据订单支付时的落库信息去定时反查反垃圾的订单状态而后通知主站。这里引入的定时任务也是带来额外的风险,好比如何保证定时任务必定执行?目前是使用考拉定时任务中间件kschedule来保证的。
问题3:若是通知重复了怎么办?
好比同一个订单,反垃圾风控结果是要关单,若是并发的通知了两次,程序会不会出问题?这里就须要考虑接口自己的幂等性。若是一个订单已经关单,再次调用关单时,必须返回关单成功,返回的结果和首次关单时的结果严格一致,不然可能就会致使各类数据不一致状况出现
问题4:若是消息乱序了怎么办?
好比正常状况是,一笔订单先通知拦截,订单进入审核状态,再通知订单审核经过,订单放行。假如如今收到的通知顺序是先收到审核经过,再收到拦截,那怎么处理呢?这里可使用状态机来保证状态的流转,像前面提到的这种状况,预先定义好已审核经过的订单,再次收到拦截请求时不予处理。
问题5:数据库的操做和dubbo接口的调用怎么保证事务完整性?
当考拉风控系统收到反垃圾通知时,须要通知主站,同时要更新数据库,表示该订单已通知,那如何保证通知主站和数据库的更新在一个事务里面呢?这里可使用考拉的分布式事务中间件DTS,不过有点过重了。考虑到第三方的通知是会有重试的,而且咱们的接口都是幂等的,能够先通知主站,再更新数据库,若是都成功则返回反垃圾一个正确的code,不然有任何异常就返回一个error值。反垃圾或网易宝那边发现是error的就重试,这样就能够达到最终一致状态。
问题6:要是反垃圾一直挂了咋办?要是反垃圾疯狂的调用你的接口怎么办?怎么快速发现第三方系统的问题?
代码逻辑是写完了,可是若是反垃圾因为本身的bug挂了,致使有风险的订单一直没法关闭,如何快速发现呢?那就要加监控了。除了监控异常的状况之外,正常的调用量也须要监控。什么意思呢?咱们通常监控会覆盖到一些程序中的异常,好比调用主站关单接口失败了,会在catch里面捕获到以后给哨兵发一个监控信息,可是这样会漏掉一些状况,好比反垃圾挂了,一成天一个通知都没有,或者反垃圾逻辑出错,疯狂的关闭大量的订单,这些调用量的异常也须要及时的发现。进一步的,接口要作好保护,防止被第三方调挂了。
问题7:如何平滑的上线?
这是一个兼容性问题,也是比较容易忽视的一个问题。举个例子:关单接口是以前就存在的,一个http接口,提供给反垃圾使用,接口的参数包括订单id和关单缘由等。如今该接口加了一个参数payType用来表示支付类型,Integer类型。根据约定,这个参数是必传的,因此代码里面会判断,若是该字段为空,则直接返回错误码。这么作是没有什么问题的,也是一种保护接口的方式,可是这里就有一个兼容性问题了:当你的代码先上线后,该字段确定是空的,这会致使全部的关单所有异常,这一点不能忽视了。
一个小需求,看似很简单,可是想要作到没啥问题仍是须要深刻的思考的,业务逻辑代码只是一部分。特别是和第三方有交互的时候,必定要记住:全部的第三方都是不可控的,它们有可能挂掉、有可能不停的调用、有可能不按照约定来,任什么时候候你都要保证本身的系统处于一个安全的环境中。这些逻辑有些是能够在统一网关中解决掉,有些只能靠本身的接口解决。
3天后。。。
说了这么多,然而并无什么卵用,仍是出了问题。。。
什么问题呢?反垃圾调用关单接口失败。如何致使的呢?不是思考了这么多么,为什么毛用没有?那只能说明我思考的还不够多。。
这个问题致使的缘由后来也查清楚了,是反垃圾在获取payType这个参数的时候报了空指针异常,致使请求根本就发到我这边。考拉这边的商户类型有新增,而反垃圾那边没有同步最新的商户类型。此次作payType映射的时候,碰到新的商户类型就报错了。以前没问题是由于没有使用到这个payType。那说好的监控呢?为毛没监控到?额,这是由于此次上线是反垃圾先上线,咱们尚未上线。。。
那么最后再加一个思考:对于接口修改的内容,双方必定要多沟通肯定清楚,任何一个小问题均可能会致使线上事故。联调的时候须要覆盖尽量多的场景,而后这始终是会有遗漏的,因此单元测试赶忙跟上吧。
网易云免费体验馆,0成本体验20+款云产品!
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 知物由学 | 广告欺诈:如何应对数字广告里分羹者?
【推荐】 从DevOps到CloudNative,应用上云姿式全解锁
【推荐】 一个内部增加案例的分享