幂等性的研究及后台验证短时间内同一申请是否重复提交的方案

来源:互联网 发布:淘宝代销管理软件 编辑:程序博客网 时间:2024/06/06 05:40

幂等性的研究及实例应用

引入:
这段时间在做新渠道的接入,把以前的核心拿过来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;    }
阅读全文
0 0
原创粉丝点击