赞
踩
防重技术方案设计和幂等性设计是系统内部的设计,交易类跨系统接口设计是系统之间的状态设计。
为解决容易导致数据一致性的多次重复提交数据情况的出现,分别针对系统建设过程的三层提出不同的解决方案:
客户端:基于token的表单防重
应用层:接口和功能操作必须幂等
数据库:不进行分库分表,基于关键业务字段使用唯一索引约束
客户端使用token作为防止表单重复提交的关键事项。包括某些需要防止重复提交的插入操作,都可以在客户端使用这种方式。
1、不管是SPA(单页面应用)还是普通的页面,在打开表单的时候首先需要从服务端获取表单token。
2、服务端在生成token时需要将token存储到redis,并设置ttl时间,避免redis存放过期的数据内存撑爆。
3、每次提交表单时需要将表单token和页面数据一起提交。
4、服务端在接受到表单提交请求时先检查有没有表单token,如果没有表单token则拒绝处理。
5、服务端在收到表单token时检查redis里面的值是否有效。
目前全球资金目前没有使用redis的原子性操作[脚本],这是个很大的问题,但即使使用了redis就不是没有问题了,redis主从同步。
业务系统接口在实现时按照如下思路进行考虑,根据不同的业务逻辑采用不同的实现方式。
1.接口做幂等处理。如果没有幂等处理,采用接口表,通过业务ID来处理。建议对接口分等级,已经将分期的核心业务按照京东的接口分级方式进行分级,包括P0,P1,P2。
2.定时任务也要做幂等处理的要求。
1.select、delete(批量)id正常情况下是幂等操作,但是对于主从同步,出现从库同步延迟时,将会导致select从库时,出现select不幂等,最简单的处理方式是在第一次插入后,采用managerHint类进行强制读取主库返回参数。以后每次都将使用从库读取。但无论怎样,一定得通知接口掉用方,该查询接口是不幂等的。 在与某支付公司对接时,因在接口文档上并未写明该接口的不幂等,导致提交支付公司订单重复。当时开发人员是每次请求都会生成一个业务ID,导致支付公司认为是两次支付指令,出现了重复代扣的问题。当前另外一个问题是状态控制也不严谨。删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个) 。
2.update、insert需幂等处理,如果不能进行幂等处理,采用异步的方式进行处理。数据先放接口表。异步处理日志记录详尽。为什么update要做幂等处理,这是因为
1.对非幂等操作,尝试按照 《幂等设计》 的方案进行幂等转换。
2.【可选步骤】如果无法进行幂等转换的要尝试考虑能否将业务流程拆分出主业务操作,通过事务日志+事后补偿的机制解决之。
例如:从A账户扣款,B账户收款,可以将“从A账户扣款动作作为主业务操作”,这样即使“B账户收款出现异常”也可以通过事后补偿的机制促进事务向前推进。
该过程要求提供事务日志组件,详细跟踪各事务的推进情况,并在出现异常是及时由人工干预。
3. 如果不能拆分出主业务操作的,可以采用异步的方式进行。对于比较复杂的业务操作,可以先将业务请求记录下来,然后通过异步的方式,采用线程安全的方式解决。消费代扣流程就是采用这种方式。
a、接入层先将数据进行简单的校验后记录下来,然后返回调用方接受成功。
b、核心业务系统再对输入数据进行各种规则检查;根据业务规则进行相应的运算处理,详细记录各步骤处理过程【可参考步骤3】;将结果输出到输出层对应的存储表。
c、通过任务调度管理器将输出层数据输出到对应的业务系统中去。
为了防止业务层出现问题和通过导入的方式造成重复数据记录。在数据存储层一定做防重处理,这是持久化数据的最后一道防线。
1.一般互联网产品不用数据库的函数、触发器、存储过程等。
2.核心交易业务建议不用分库分表(像上面说的某支付公司,就是因为采用了分库分表,导致了select的不幂等)。
3.关键业务建唯一索引,这也是保证数据不重复的最后一道防线,二级索引的叶子节点不保存记录中的所有列,其叶子节点保存的是<健值,(记录)地址>,如果执行计划认定该索引有效,则会通过该二级索引查询到主键,通过主键查询该表的聚簇索引。
5.隔离级别为Serializable(不建议,采用默认隔离级别RR,或者降低为RC,但是一定要评估清楚降低RC能带来的好处及带来的影响(不能限制重复读的问题))
6.date guard设置为最大保护模式。oracle->数据复制机制。同步复制/半同步复制/全异步。目前mariaDB失败没有告警。
7.建议加入疑似重复交易。【根据业务场景,比如发工资,一个月一次|比如选择不同的银行,可以不用考虑疑似判重】
8.时间差保证处理【比如消费日终,在跑日终3分钟内不能进行任何下一步操作】
幂等本是个数学概念,但在编程中,一个幂等操作的特点是其任意多个词执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。
update ... id = 0 and status = Y(有弊端)、update ... id = 0 and version='1' -- 更新时如果能带ID则尽量带ID更新。
b.通过条件限制
update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-#subAmount# >= 0
要求:quality-#subQuality# >= ,这个情景适合不用版本号,只更新是做数据安全校验,适合库存模型,扣份额和回滚份额,性能更高。
注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁表,上面两个sql改成下面的两个更好。
update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#
update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0
要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供)
redis分布式锁超过这个时间锁会自动释放。分布式锁过期了,解决方案当然就是续期。以下是续期方式:
思路一:任务执行的时候,开辟一个守护线程,在守护线程中每隔一段时间重新设置过期时间。
思路二:通过Redisson中的看门狗来实现(建议)。
select + insert
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了。
注意:核心高并发流程不要用这种方法,存在多线程安全的问题
状态机幂等
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。
注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态机,对业务系统设计能力提高有很大帮助
对外提供接口的api如何保证幂等
如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号
source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)
重点:
对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引,这样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处理结果;没有处理过,进行相应处理,返回结果。注意,为了幂等友好,一定要先查询一下,是否处理过该笔业务, 注意,这里如果两个线程同时查询都没有被处理,都去发起第三方查询,如果前一个线程A返回失败,后一个线程B返程成功,且因网络原因B提前返回。更新时,一定要带状态或版本号更新保证幂等,否则刚才说的情况将会导致B线程成功的状态又被A线程置为失败。
总结:幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在金融相关业务系统建设过程中,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确。
交易类接口调用要详细记录好跨系统交互的原始数据记录,一是要打印日志,二是要记录到表,作为数据跟踪的基础。避免两边数据不一致等各种错误出现的情况下无法排查定位问题的情况出现。因此这类接口调用第一步要先将数据落地保存。
a、外围系统对本系统的调用数据(发送数据和返回数据)要写入接口表
其它系统调用本系统接口发送过来的数据需要进行处理的,如果业务逻辑比较复杂的,存在并发安全等问题的,先将数据保存入库后再异步进行处理。
注意:数据保存到接口表之后不得再对接口表的数据的业务数据进行修改。
b、本系统对外围系统的调用数据(发送数据和返回数据)要写入接口表
本系统需要发送给其它系统处理的数据,一律需要设计输出数据接口表,数据先写入到输出数据接口表,经过校验检查确认数据无误后再进行发送。
跟外围系统交互过程中必须先理解对方系统接收的数据要求,并且同步分析本系统需要发送给对方系统的数据业务关系,据此进行数据库表结构设计。
数据库表结构设计只需要考虑交互数据结构,不需要考虑算法实现过程中的中间数据存储,需要将过程数据和结果数据分离开来。
数据在发送给第三方系统进行处理之前必须先考虑如何对数据进行检查,检查确保数据无误之后才可以发送出去进行处理。
接口表数据检查规则应该不同于算法业务逻辑,需要从另外的角度考虑校验。如代扣拆单合单算法需要从不同业务逻辑校验。
算法应该尽量简单,需要重复考虑分布式锁、分布式事务(2PC等方案)、数据分布式一致性、数据重复提交等情况的应对措施。
对其它系统发送过来的数据处理之前必须先做相关检查校验,检查校验内容包括:
算法设计过程中必须做到如下几点:
接口表需要考虑如下几点:
考虑完接口表设计后要考虑清楚交易规则设计,校验规则可能需要检查项包括:
数据发送过程中需要考虑如下问题:
禁止:
循环调用dubbo、http调用。在分期中,因某个领导固执己见,将在业务代码中循环调用dubbo服务,导致长事务锁表,引发生产事故。
参考:
https://blog.csdn.net/m0_37337849/article/details/97755987
https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。