前语
幂等是分布式系统中确保数据一致性和安全性的重要保证之一,尤其是在金融、付出领域,其作为资损防控的硬性指标体现在系统架构规划中。今日咱们就来浅谈一下幂等相关的规划。
幂等的界说
幂等( idempotent、idempotence )的概念来源于数学,并被广泛使用于计算机科学。在数学中,其语意是 f ( x ) = f ( f ( x )),比方求取绝对值,abs ( x ) = abs ( abs ( x )),便是幂等的。
在计算机科学中,幂等即相同的恳求调用一次和调用屡次,服务端处理的的成果相同,而且最多受理一次。
幂等的重要性
咱们就拿付出公司的资金调拨举个例子。一般的,第三方付出公司需求借助清算公司(如网联)供给的付出通道进行备付金账户资金调拨,以确保资金池足够可用。当第三方付出公司建议资金调拨恳求时,假如清算公司的回来成果丢掉,这时,付出公司是否能够重试?假如重试,是否会产生资金的重复调拨?
互联网公司的使用间存在物理鸿沟,恳求和响应信息会经过网络进行传递。咱们说远程调用的成果会有三个状况:成功,失败,未知。前两者都是清晰的状况,而未知具有不确定性,一般都是由网络超时、丢包引起的。如上例中,假如呈现了超时,其实有两种方案,咱们能够建立查询补偿机制,来研判是否要从头建议资金调拨。或许,清算公司做好幂等控制,付出公司能够无脑重试,既能够确保资金调拨事务的正常,又能确保不会产生屡次调拨。
在架构规划中,幂等的使用面非常广泛,比方 MQ 躲避重复消费、表单躲避重复提交等。
幂等规划
幂等两大要素
幂等包括两大要素,幂等标记和要害恳求参数。
幂等号:它对应服务端的仅有束缚,在规划上,它一般由上游的幂等单号和来源组成。服务端的接口文档中,需求清晰指出幂等号的信息组成,它的作用是对恳求信息进行身份标识,相同幂等号的恳求将被服务端识别为同一恳求。
要害恳求信息:接收的中心事务信息,常见的如收款账户、打款账户,打款金额、币种、商品数量等等。相同的恳求中,调用方需求确保要害恳求信息不变,一旦信息产生改动,则需求替换幂等号。
幂等原则
调用方有必要确保幂等号的仅有性、不变性
阐明
调用方需求确保幂等号不重复,且对同一事务单据的同一次操作,不管恳求多少次,都要确保幂等号不变。
反例
幂等号重复,原因根本如下
- sequence cycle 问题,未评价好事务量同 sequence 增长速度,导致幂等号重复。
- sequence 步长、分段设置问题,导致跨区域/单元/库/表幂等号重复;
幂等号改动,原因根本如下
- 事务中生成幂等号,并建议远程调用,调用超时本地事务回滚,第2次恳求又会生成新的幂等号。
调用方有必要确保要害事务恳求参数的不变性
阐明
当服务端没有回来成果时,调用方要害事务恳求参数不允许变更。
反例
初度恳求,由于网络反常导致 timeout 调用方没有拿到成果,而服务端受理成功。客户端修改单据金额,恳求信息产生改动,调用方与服务端处理犯错。
调用方制止幂等号纯内存拼接,不进行耐久化
阐明
幂等号不耐久化,对于异步回执处理,上下游数据稽核带来困难,所以幂等号耐久化是一个根本要求。
反例
RPC 调用,调用方的幂等号,是内存中依据事务映射拼接得来,不做耐久化。
//内存中拼接幂等号
request.setRequestId(BizTypeEnum.getPrefix(xxDO.getBizType()) + xxxDO.getId()):
调用方幂等号生成事务内制止包括 RPC
反例
transactionTemplate.execute (status ->
//生成流水号 xxx
SerialDO serialDO = buildSerialDO();
//播入 aaa 表
serialDAO.insert(serialDO);
someDAO.update (someDO) ;
// dubbo 调用 rpc,流水号 xxxId 作为幂等号
invokeRpc(request);
return true,
));
正例
- RPC 放在事务外面
transactionTemplate.execute (status ->
//生成流水号 xxx
SerialDO serialDO = buildSerialDO();
//播入 aaa 表
serialDAO.insert(serialDO);
someDAO.update (someDO) ;
return true;
));
// dubbo 调用 rpc,流水号 xxxId 作为幂等号
invokeRpc(request);
- 运用事务同步器:假如事务在外层开启,为了不损坏代码结构,运用事务同步器,事务提交后建议 RPC 调用,调用反常后使用需求做康复。
/**
* 外层已开启事务
*/
public static void execute (){
//更新单据状况
Runnable runnable = () -> {
response = dubboService.call(request);
};
register(runnable);
}
public static void register (Runnable runnable) {
if (TransactionSynchronizationManager.isActualTrangactionActive()) {
TransactionSynchronizationManager.registersynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit () {
runnable.run();
}
}
);
} else {
LOGGER.debug( "No active transaction.");
runnable.run();
}
}
- 事务自研组件:事务中插入本地任务,统一康复执行。
服务端不能单纯依赖查询做幂等
阐明
分布式下并发场景,并不能单纯的依赖查询做到插入 幂等。常见仅有性保证方法:
- DB 束缚:对插入流水的幂等号建 DB 仅有索引束缚
- 分布式锁:如 redis、 zookeeper 等。若耐久层在 DB,不推存运用(依赖外部存储做幂等控制,与 DB 的强一致性无法确保),触及资金等强一致性场景不引荐。
反例
RPC 调用超时,本地事务回滚。下次重试,会生成新的幂等号,导致资损。
服务端有必要确保受理成果一致性
阐明
针对相同恳求,不论调用方恳求多少次,服务端仅受理一次,且受理成果相同。
反例
售中退款的场景中,第一次服务端正常受理调用方恳求,但调用方由于超时丢掉响应;当第2次调用方重试,服务端发现退款金额不足,回来受理失败,导致毛病。
//1、根本校验
//2、失望锁内,可退款金额判别;
Assert.isTrue(refundable(xxx), "cannot refund");
//3、逻辑处理
try {
process(xxx);
} catch (Exception e) {
//幂等判别处理
}
调用方收到服务端幂等成果后,比对要害事务参数
阐明
客户端收到服务端成果后,本着不信任的原则,针对要害事务恳求参数如账户、 金额同服务端受理内容对比。
反例
服务端做幂等判别时,只看幂等号,尽管第2次恳求幂等号不变,但是金额又或许被篡改,假如服务端直接回来成功,将导致资金丢失。
正例
- 服务端:依据幂等号查询 DB 流水,回来现已受理的要害事务信息。
- 调用方:对服务方回来的幂等内容做校验,确保与预期一致。
总结
以上规则是借鉴历史项目和互联网经验总结而成,主要侧重于幂等规划的原则,幂等的落地方案有许多,比方幂等表、乐观锁、失望锁等,这儿就不赘述。
引荐阅读
政采云 Flutter 单元测试实践
音视频技能助力政府采购之音视频编码采集(一)
以dubbo源码为例-运用lambda重构面向对象模块
万字长文解读 Linux 内核追踪机制
spring-retry详解
招贤纳士
政采云技能团队(Zero),Base 杭州,一个赋有激情和技能匠心精神的成长型团队。规划 500 人左右,在日常事务开发之外,还分别在云原生、区块链、人工智能、低代码渠道、中间件、大数据、物料系统、工程渠道、性能体验、可视化等领域进行技能探索和实践,推进并落地了一系列的内部技能产品,继续探索技能的新鸿沟。此外,团队还纷纷投身社区建造,目前现已是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等很多优秀开源社区的贡献者。
假如你想改动一向被事折腾,期望开端折腾事;假如你想改动一向被劝诫需求多些主意,却无从破局;假如你想改动你有才能去做成那个成果,却不需求你;假如你想改动你想做成的事需求一个团队去支撑,但没你带人的方位;假如你想改动本来领悟不错,但总是有那一层窗户纸的模糊……假如你信任信任的力量,信任平凡人能成就非凡事,信任能遇到更好的自己。假如你期望参与到随着事务腾飞的进程,亲手推进一个有着深化的事务了解、完善的技能系统、技能发明价值、影响力外溢的技能团队的成长进程,我觉得咱们该聊聊。任何时刻,等着你写点什么,发给 zcy-tc@cai-inc.com
微信大众号
文章同步发布,政采云技能团队大众号,欢迎重视