幂等性验证思想

本文探讨了幂等性在接口设计中的重要性,通过对比分析不同幂等性验证方法,如select+insert、乐观锁和悲观锁,提出了在不涉及数据库操作场景下,使用缓存区(hashmap)进行幂等性校验的解决方案。

引入: 

这段时间在做新渠道的接入,把以前的核心拿过来copy一份进行改造,在进行代码重读的时候,发现了一个好玩的东西,在申请入件的时候,需要经过一步校验,注释上写的是,对于短时间重复提交的验证。当时我就很好奇点了进去,看一看到底是什么东西,然后点开之后大吃一惊,里面做的操作是:取到这笔件的身份证信息,使用其作为标识去查询有没有这个标识的线程,如果有则返回失败,这笔件短时间内重复提交了,如果没有返回成功,这笔件短时间内没有重复提交,并以身份证为标识创建一个线程然后挂起这个线程n毫秒(算作重复性提交的时间)。

这里问题就大了,意思是一笔件进入系统的时候,如果需要做重复申请验证,则会新开一个子线程挂起,我们都知道对于线程的操作,无论是创建挂起销毁都好,会需要进行OS切换操作,这种OS操作十分占用资源,在没有必要的时候尽量避免反复的创建线程,而且这个验证会在每一次入件时都会执行。这对我们写的程序是一个极大的负担,于是我向上级反映此问题并且接到了优化代码的任务。在查询资料之后,发现对于这种验证短时间内是否重复提交的操作有一个专用的名词去形容他,也就是我要去研究的内容:幂等性验证。

一、幂等性的概念

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 
在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。复杂的操作幂等保证是利用唯一交易号(流水号)实现.【百度百科】

数学中的幂等是指一个数对自己进行函数运算,其结果等于他自己。数学表达式为(f(x)) = f(x):也就是某个函数或者某个接口使用相同参数调用一次或者无限次,其造成的后果是一样的。

在业务层面上来说,由于我们是提供接口的一方供他人调用,别人在调用时完全可以发送无数笔相同的数据(由于bug之类)调用你的接口,这样你在提供服务的时候,必须进行幂等性运算来控制重复的申请。

实际上TCP传输协议是支持幂等的,他使用了一个唯一的序列号作为标示来保证数据的幂等性。我们在进行应用层面上的幂等性验证的时候也是同理,需要使用一个唯一的序列号进行验证,通常是我们与对接方约定的流水号来进行控制,其特点是唯一性。

二、幂等性的验证实验

在网上保证接口幂等的方法很多,最简单常用的方法时,select+insert 先查询有没有此笔数据,然后再进行数据变更操作,但是这种方法很明显效率比较低下,在高并发的程序中不建议使用。然后比较多的就是乐观锁和悲观锁,悲观锁的原理是使用select for update的形式,这种方式要求你select的条件必须是主键或者唯一索引,否则会导致锁表,在你更新时任何人做不了表格操作。乐观锁是只在更新的一瞬间进行锁表,效率上要高很多,但要通过辅助的状态参数来进行验证,如: 
  update table set name=’name’,version=version+1 where version=version 
由于进行更新的时候会使其版本+1如果是并发的两条信息同时对表格操作时,第二笔就会因为版本号不对更新失败,这样也能保证幂等性。当然,你使用其他状态进行控制也是可行的,如果可以最好加上唯一索引,能够大幅度提高效率。 
正常与其他机构进行交互的时候,往往对方会提供流水号并且保证流水号唯一,这样作为己方就可以使用数据来源+对方提供的流水号作为唯一索引来进行幂等性校验。

三、解决实际问题

虽然查了这么多资料,但是发现幂等性校验并没有解决我的实际问题,应需求(可能是为了分担我们后台的核心专家系统压力,也可能是以前出过什么大量重复bug),我需要在程序中不牵扯到数据库操作的情况下,控制同一个人(身份证号,这两笔申请可能是不同的,但是只要是一个人就不允许连续操作)在短时间内不能进行重复申请操作。于是我想到了,设置一个缓存区来解决这个问题,思路如下: 

  • 设置一个hashmap,key储存身份证信息,value储存申请时的时间信息
  • 在一笔申请提交进来的时候,将身份证信息和时间信息存入map
  • 在信息存入map之前进行判断,如果这个key在map中存在则判断其value与现在时间是否超过允许重复提交时长,如果是则更新数据,返回通过,否则返回不通过。
  • 由于入键数可能比较多,一直往map中增加值可能会导致map非常大,后入的键验证效率变差,于是在map达到一定大小时将开一个子线程,循环遍历map中的值,将与当前时间相差大于允许重复提交时间的键全部删除 
    实现代码如下:
public static final Map<String,Long> MAP = new HashMap<String,Long>();
    public static final long OUT_TIME=1000;
    public static final int OUT_SIZE=100;
    //验证是否短时间重复申请
    public static boolean isRepeat(String idNum){
        boolean flag =false;
        //检查是否存在key
        if(MAP.containsKey(idNum)){
            if(System.currentTimeMillis()-MAP.get(idNum)>OUT_TIME){
                flag= true;
            }else{
                flag= false;
            }
        }else{
            flag= true;
        }
        MAP.put(idNum, System.currentTimeMillis());
        //当数量过多做一次清除
        if(MAP.size()>OUT_SIZE){
            new Thread(){
                @Override
                public void run() {
                    for (Entry<String, Long> entry :MAP.entrySet() ) {
                        if(System.currentTimeMillis()-entry.getValue()>OUT_TIME){
                            MAP.remove(entry.getKey());
                        }       
                    }
                }
            }.start();
        }
        return flag;
    }

 

### 幂等性解决方案概述 幂等性是分布式系统和微服务架构中非常重要的特性之一,尤其是在高并发环境下。为了确保系统的稳定性和一致性,通常需要采用一些特定的技术手段来实现幂等性。 以下是几种常见的幂等性解决方案: #### 1. **基于唯一标识符的去重机制** 这种方法的核心思想是在每次请求时生成一个全局唯一的标识符(如 UUID),并将该标识符作为键存储到数据库或缓存中。在后续接收到相同标识符的请求时,可以直接忽略掉重复的请求[^3]。 ```python import uuid unique_id = str(uuid.uuid4()) cache.set(unique_id, "processed", ex=60) # 将唯一ID存入缓存,设置过期时间为60秒 ``` #### 2. **使用分布式锁** 在某些场景下,可以通过加锁的方式来防止同一笔事务被多次处理。例如,在 Redis 中可以利用 `SETNX` 命令创建分布式锁,只有第一个获取锁成功的请求才能被执行,其他请求会被阻塞直到超时[^2]。 ```lua -- 使用Lua脚本保证原子性操作 local key = KEYS[1] local value = ARGV[1] if redis.call("get", key) ~= nil then return 0 -- 锁已存在,返回失败 end redis.call("set", key, value) redis.call("expire", key, 60) -- 设置锁的有效时间 return 1 -- 返回成功标志 ``` #### 3. **Token校验法** Token校验是一种较为复杂的幂等控制方式,适用于支付类业务场景。具体做法分为两步:第一步由服务器生成一个临时 token 并将其保存至缓存;第二步客户端携带此 token 发起实际业务请求,服务器验证 token 后再执行相应逻辑并清除 token[^4]。 #### 4. **补偿机制** 对于那些难以完全避免重复执行的操作,则可通过设计对应的补偿策略来进行事后修正。比如记录每条交易的状态变化历史,一旦发现异常情况即可触发回滚或其他补救措施[^3]。 --- ### 示例代码展示 以下提供了一个简单的 Python 实现案例,演示如何借助 Redis 来达成基本的幂等功能: ```python import redis r = redis.Redis(host='localhost', port=6379, decode_responses=True) def process_request(request_id): """ 处理带有幂等特性的请求 :param request_id: 请求唯一标识 :return: 是否允许继续处理当前请求 """ if r.exists(request_id): print(f"Request {request_id} has been processed before.") return False r.setex(request_id, time=60, value="done") # 存储请求ID,并设定有效期为60秒 print(f"Processing new request with ID={request_id}.") return True if __name__ == "__main__": test_ids = ["req_001", "req_002", "req_001"] for req_id in test_ids: result = process_request(req_id) if not result: continue # 执行具体的业务逻辑... ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值