基于幂等表思想的幂等实践

news2024/11/20 7:09:07

一、为什么需要幂等

  • 分布式场景下,多个业务系统间实现强一致的协议是极其困难的。一个最简单和可实现的假设就是保证最终一致性,这要求服务端在处理一个重复的请求时需要给出相同的回应,同时不会对持久化数据产生副作用(即多次操作与单次操作的结果需要是业务角度一致的)。
  • 一个API拥有幂等能力的话,调用发起方就可以很安全的进行重试。这符合我们普遍的假设。提供幂等能力是服务提供方必须需要做的事。
  • 拥有幂等能力的话可以保证我们的接口不会被各种异常重试或恶意请求锁冲击。

二、幂等方式

不同的场景下(常见的是【界面和后端接口交互场景】和【接口于接口交互场景】),幂等方式有很多并且各不相同,都各有一些局限性和缺点!!!

  • 可以基于【业务key + 业务状态机 + 乐观锁】去做幂等实现(一般适用于比较简单的update场景)

    • 比如:更新订单状态为 finished 的场景中,先根据订单号查询订单,判断订单状态是否为finished,若不是则更新为finished
  • 可以基于【业务key + 分布式锁 + 业务状态机】去做幂等实现

    • 比如:新增用户信息场景中,执行方法前先加分布式锁(防并发),以用户身份证号为查询条件,查询用户,如果用户不存在则进行新增。如果用户存在则幂等处理
    • 这种方案一般有个固定的流程:【一锁、二判、三执行
  • 可以基于【业务key + 唯一索引】去做幂等实现(一般适用于新增数据场景)

    • 比如:新增用户信息场景中,以用户身份证号为唯一键,建立唯一索引,新增用户时通过捕获唯一键冲突异常(DuplicateKeyException)进行幂等控制
  • 可以基于【Redis + token模式】去做幂等实现(多用于界面和接口交互,接口于接口交互不太适用,该方案也是比较常见的方案,但不在本次讨论范围中)

    • 比如:用户提交填写好的表单信息,多次重复提交时保证仅仅真正执行一次,其余的都幂等返回相同结果
  • 可以基于【幂等表】去做幂等实现(比较通用的一种方案,具体效率取决于存储幂等记录的存储介质)

    • 比如:消费MQ消息时,为了避免消息重复消费,消费消息前可以先插入一条幂等记录,然后再执行消费逻辑,消费完成后修改幂等记录的幂等状态为消费成功!
    • 接口互调的情况类似

三、幂等设计原则

一个具有幂等性的服务,要求无论重复请求在多么极端的情况下发生,都要表里如一,此时必须满足:

  • 对外:返回完全相同的结果
  • 对内:自身状态不再发生任何改变
  • 对于服务提供方来说:严格来说需要请求中的字段完全一样,服务提供方才认为是重复请求。但是在实际环境中我们可能没有这么严格的要求,我们一般认为只要关键的业务参数相同,那么他就属于重复请求,应该被幂等处理。

  • 对于服务调用方来说:需要做好幂等结果处理,多次请求返回相同结果需要正确被处理

  • 幂等设计要尽量从简单、可靠、高效(过多的幂等逻辑会对可用性和性能造成影响)角度出发

    • 简单:幂等流程和逻辑要尽量简单
    • 可靠:不仅仅在正常运行的情况下要保证幂等的可靠性,在某些异常场景下也要尽量保证幂等的可靠性,否则该幂等设计的意义将大打折扣
    • 高效:幂等逻辑执行不能高耗时,针对于一些高并发的接口需要做到尽量减少幂等逻辑执行耗时
  • 通用幂等组件设计易用性和可扩展性也同样重要

四、常见幂等场景例子

  • MQ消息消费场景中】,由于MQ为了保证消息投递成功,可能会发起多次重试,那么消费者方便需要保证重复的消息能够被幂等处理(比如:监听用户支付成功消息进行生成支付单)
  • 界面和接口交互场景中】,前端重复提交数据,后台接口需要保证只执行一次,其余重复请求均幂等返回(比如:用户重复提交订单、重复提交录入的用户信息)
  • 接口互调的场景中】,调用方可能由于多种原因没能收到响应结果而发起重试(比如:数据同步、库存扣减等),此时被调用方需要保证重复调用幂等处理

五、幂等实践

  • 为了将幂等这个常见的通用需求尽量设计得通用化,我们这里采用【幂等表 + 幂等状态机】来实现,该方案可以适用于绝大部分的【界面 + 接口交互】和【接口 与 接口交互】模式
  • 如果项目仅仅是界面和接口交互模式,那么采用【Redis + token】方案也是一个不错的选择
  • 当然,软件工程中几乎没有银弹,很难有一种完美适用与所有场景的方案

1、设计流程

幂等组件方案.png

  1. 调用放发起请求,请求到达服务提供方

  2. 获取指定的业务key作为唯一的幂等键,构建幂等记录(此时幂等记录status为处理中(processing)),然后尝试将幂等记录写入存储介质(可以是Redis也可以是MySQL或其他存储介质)

  3. 如果幂等记录写入成功,则执行业务逻辑

  4. 业务逻辑执行完毕,通过唯一键修改幂等记录的status为成功(success

  5. 如果幂等记录写入失败,则说明幂等记录已存在(该业务key对应的数据,之前有被执行过),需要进行如下处理:

  6. 通过幂等唯一键查询幂等记录,并且判定幂等记录的status

    1. 如果status为成功(success),则说明上次已经执行过该业务了,本次无需再重复执行,获取上次执行的结果(如果有需要的话)幂等返回即可

    2. 如果status为处理中(processing),则说明已经有其他线程正在处理业务数据 或者是 极端情况下应用宕机导致的异常情况。此时需要判定【请求处于处理中(processing)状态的时长】,并且结合应用配置的【允许的最大业务执行时长】进行判断

      • 处于processing状态的时间已经超过配置的【允许的最大业务执行时长】,则尝试以乐观锁的方式重新修改幂等记录,如果修改成功则执行业务逻辑,反之则抛出并发异常。
      • 处于processing状态的时间没有超过配置的【允许的最大业务执行时长】,那么直接抛出并发请求异常

2、问题思考

关于上述的幂等实现流程中,极端情况下,有如下几点需要思考和注意的问题点

  • 极端情况下,如果插入幂等记录成功,并且正常执行了业务流程,此时更新幂等状态为success时出现异常(比如存储幂等记录的存储介质宕机了),此时是否需要处理该异常,还是说抛出异常中断流程???如果抛出异常会有什么影响?如果catch异常会有什么影响?

    • 方案一、抛出异常:如果抛出异常中断流程,那么调用方应该感知到调用失败了,但是实际上业务流程已经执行完毕,这种情况如果调用方发起重试,那么幂等便会失效(同一个业务code被执行了两次)
    • 方案二、不抛出异常:如果不抛出异常,接口会继续执行,然后返回数据给调用方,如果调用方收到了返回数据,那么便不会发起重试了,不会有幂等问题。但是此时幂等记录的状态仍然是处理中(processing),再指定了业务最大执行时间的情况下,如果调用方【超过指定的最大执行】时间再次发起重试,那么幂等仍然失效(当然我们可以不指定业务最大执行时间)
    • 经过上述两种情况的比较,我们一般倾向第二种方案,自己处理掉异常,并且做一层 【兜底策略】 (比如告警或记录该条幂等数据信息等,后续可以转人工核对该数据),这种方案更加稳定和适用
  • 如果业务逻辑执行失败,那么是否应该删除之前创建的幂等记录?

    • 按照严格的幂等含义来说,我们应该保留这条幂等记录,并且将幂等记录的状态修改为Exception或failed,后续有重试请求进来时执行返回failed给调用方即可(保证多次调用得到的结果相同)。

      • 但是在真实环境中业务执行异常有可能是数据校验失败、接口里调用外部系统失败(比如外部系统正在发版(没有做优雅发布)等)
      • 针对于这些情况可能调用方修改数据后进行重试或过一定时间后进行重试,那么此时最好有一定的自愈能力,而不是每次这种数据都转人工处理(一些场景中会加大人力成本,比如我之前涉及到的某个系统,经常有些调用方传递的业务参数有问题或接口里调用外部系统失败的情况)
      • 当然这两种策略需要根据具体的情况来选择,没有谁好谁坏之分。
  • 是否需要设定【最大的处理时间】,比如我们期望接口最大处理时间为1小时(也就是说幂等记录处理processing状态的时间最大为1h),如果超过这个时间,那么认为这不是一种正常的case,下次重试请求时应该尝试恢复业务执行。

    • 这也是个具有两面性的选择问题,需要根据实际项目情况权衡选择

3、幂等实现

基于幂等表的幂等方案实现:https://gitee.com/mr_wenpan/basis-enhance/tree/master/enhance-boot-idempotent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163072.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】Linux下调试器gdb的使用

👑作者主页:安 度 因 🏠学习社区:StackFrame 📖专栏链接:Linux 文章目录一、前言二、铺垫三、指令集和使用1、指令集2、演示四、结语如果无聊的话,就来逛逛 我的博客栈 吧! 🌹 一、前…

通信原理与MATLAB(十三):AMI的编解码

目录1.AMI的的编解码原理1.1 AMI编码原理1.2 AMI解码原理2.AMI编解码的代码3.AMI编解码结果图4.AMI的误码率曲线4.1 原理4.2 AMI的误码率曲线代码4.3 误码率曲线图1.AMI的的编解码原理 1.1 AMI编码原理 如下图所示,AMI的编码原理:将原始码元的1转换成1,0转换成-1。…

快过年了,用Python康康哪一家足浴店可以带朋友去玩.....

人生苦短,我用Python 首先肯定是去正经足浴店, 毕竟一年出差也不少, 大家都很辛苦, 好不容易放假了, 约上好兄弟一起去放松放松~ 所需环境 python 3.8 解释器pycharm 编辑器 所需模块 requests 数据来源分析 …

Silane-PEG-NH2 氨基聚乙二醇硅烷 NH2-PEG-Silane结构式

英文名称:Silane-PEG-NH2 Silane-PEG-Amine 中文名称:硅烷-聚乙二醇-氨基 分子量:1k,2k,3.4k,5k,10k,20k。。。 存储条件:-20C,避光,避湿 用 途…

2022年度总结,迎接2023

目录 我和CSDN的2022 初次见面: 你我的成长: 博客: 比赛: 我和CSDN的2023 我和CSDN的2022 初次见面: CSDN你好啊!我跟你的初次见面在于2022年4月2日!!! 这这半年内…

【算法5.1】背包问题 - 01背包 (至多最大价值、至少最小价值)

目录 至少模板和至多模板的两大区别 1、至多模板 2、至少模板 2. 01背包 - 至多模板 - 体积至多j,总价值最大 1、朴素做法 - 二维dp 2、优化 - 一维dp 4700. 何以包邮? - 至少模板 - 价值至少j,总价值最小 至少模板和至多模板的两大区…

list容器与vector容器的区别

vector与list都是STL中非常重要的序列式容器,它们都存放在namespace std命名空间中,由于俩个容器的底层结构不同,导致其特性不同 一、底层实现结构不同 vector本质是一段动态连续的顺序表,而list底层是一个双向循环链表 二、访…

Ubuntu多硬盘luks全盘加密自动解锁(硬件变更后失效)的方法

简介大家都知道,Linux现在用Luks全盘加密一直有一个痛点,就是每次开机都需要输入解密硬盘的密码,之后又要输入用户密码,非常的麻烦!本文正是为了解决这个问题诞生的!本文多硬盘加密带来的效果是&#xff0c…

Redis持久化——RDB机制详解

在运行情况下,Redis 以数据结构的形式将数据维持在内存中,为了让这些数据在 Redis 重启之后仍然可用,需要将数据写入持久存储 持久化是指将数据写入持久存储,例如固态磁盘(SSD) Redis 提供了一系列持久化选项。这些包括&#xff1…

Java-黑马Java学习作业-day07综合练习

学习视频链接:黑马Java学习视频 文章目录练习一:飞机票练习二:打印素数练习三:验证码练习四:复制数组练习五:评委打分练习六:数字加密练习七:数字解密练习八:抽奖解法一:…

【C++11】—— 可变参数模板

目录 一、可变参数模板概念以及定义方式 二、参数包的展开 1. 递归函数方式展开参数包 2. 逗号表达式展开参数包 三、STL容器中的empalce相关接口函数 一、可变参数模板概念以及定义方式 在c11之前,类模板和函数模板只能含有固定数量的模板参数,c11…

JavaScript高级 ES5 面向对象原型继承

原型以及ES5中实现继承1. 对象和函数的原型1. 普通对象的原型 [[prototype]]2. 函数的原型 prototype2. new、constructor1. new 操作符2. constructor属性3. 将方法放到原型上4. 创建对象的内存表现5. 重写原型对象3. 原型链的查找顺序4. 原型链实现的继承5. 借用构造函数继承…

深入URP之Shader篇10: 深度值专题(1)

之前研究Unlit shader的时候就遇到一些Z值相关的问题,一笔带过了,比如ComputeFogFactor中的UNITY_Z_0_FAR_FROM_CLIPSPACE。今天就把URP Shader中出现的Z相关的问题做一个专题一起研究下。 深度缓冲的方向和UNITY_REVERSED_Z 先说这个关于z的宏&#x…

nacos:服务注册与发现

导入SpringCloudAlibaba相关的依赖&#xff0c;并在父工程将依赖进行管理 <dependencyManagement> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-s…

Java EE|多线程代码实例之定时器与线程池

文章目录&#x1f534;定时器什么是定时器以及开发中的作用标准库中的定时器定时器的实现&#x1f534;线程池什么是线程池标准库中的线程池创建一个线程池ThreadPoolExecutor构造方法解析线程池的实现&#x1f534;定时器 什么是定时器以及开发中的作用 程序中的定时器功能与…

【互联网大厂机试真题 - 华为】九宫格

题目描述 九宫格是一款广为流传的游戏,起源于河图洛书。游戏规则是:1到9九个数字放在3x3的格子中,要求每行、每列以及两个对角线上的三数之和都等于15. 在金麻名著《射雕英雄传》中黃蓉曾给九宫格的一种解法,口诀:戴九恩一,左三右七,二四有肩,八六为足,五居中央。解法…

【云原生进阶之容器】第四章Operator原理4.3节--Operator模式

1 Operator概述 1.1 诞生背景 Kubernetes实际是期望状态管理器。先在Kubernetes中指定应用程序期望状态(实例数,磁盘空间,镜像等),然后它会尝试把应用维持在这种状态。Kubernetes的控制平面运行在Master节点上,它包含数个controller以调和应用达到期望状态: 检查当前的…

【阶段三】Python机器学习30篇:机器学习项目实战:智能推荐系统的基本原理与计算相似度的常用方法

本篇的思维导图: 智能推荐系统模型 智能推荐系统属于非监督式学习,是机器学习一个非常重要的应用领域,它能带来的经济价值往往是直接且非常可观的。 智能推荐系统的基本原理 智能推荐系统的应用场景 互联网每天都在产生海量信息,用户行为数据呈现爆发式增长…

PyTorch - 常见神经网络

文章目录LeNetAlexNetDropoutAlexNet 网络结构torchvision中的AlexNet的实现ZFNetVGG-NetsVGG 各网络VGG-16 网络结构GoogLeNet代码实现ResNetDenseNetRNNLSTMGRULeNet 1998年&#xff0c;由 LeCun 提出用于手写数字识别任务只有5层结构&#xff1b;目前看来不输入深度学习网络…

智能售货机系统帝可得

智能售货机 概述项目使用springcloudalibaba中提供的短信服务图形验证码生成多端登录/网关统一鉴权对象存储服务代码的自动填充微服务集成emq&#xff0c;发送emq工单业务流 接收工单 拒绝工单 运维工单补货工单使用xxl-job进行任务调度lkd集成xxl-job自动创建维修工单自动…