一、幂等性概念
在数学里,幂等有两种主要的定义。
1、在某二元运算下,幂等元素是指被自己重复运算(或对于函数是为复合)的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1。
2、某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的。
3、一元运算的定义是二元运算定义的特例(详情请见下面)。
二、分布式系统 中的幂等性
就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
在编程中,一个幂等操作的特点是其任意多次执行所产生的影响与一次执行的影响相同。幂等函数或者幂等方法是指相同参数可以重复执行,并获得相同结果的函数。
这些函数不会影响系统的状态,也不用担心重复执行对系统造成改变。例如:getPolicyNo()就是一个幂等函数。更复杂的幂等操作是利用唯一交易号(流水号)或者主键来实现。
三、幂等场景
1、查询操作:
查询一次和查询多次,在数据不变的情况下,返回结果都是一样。查询操作具有天然的幂等性。
2、删除操作
删除操作也是幂等的,删除一次和多次最终的实现结果相同,只是返回有差异。(第一次删除返回 影响条数 1,后面几次返回影响条数 0)
3、唯一索引:防止新增脏数据
例如:支付宝的资金账户和用户账户,每个用户只能有一个资金账户,怎么防止给用户账户创建多个资金账户呢?可以考虑给资金账户表中的用户id创建唯一索引,所以对应一个 用户ID 只能有一条资金账户记录。
该设计下出现的问题就是,高并发下出现新增报错的情况,再查询一次数据就已经存在了。
4、token 机制 : 防止页面重复提交
原理上通过session token 实现的,当客户端请求时,服务器会随机生成一个Token,并且将Token发给客户端,下次客户端提交请求时,Token会随着表单一起提交到服务器端。
服务端第一次验证通过后,会随即更改Token 的数值,这样若用户重复提交,第二次的验证就会失败,即 服务端 Token数值已经改变,但用户提交的表单中Token数值并未改变。
5、悲观锁
获取数据的时候-加锁获取。
select * from table_xxx where id='xxx' for update;
注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的;悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;
6、乐观锁
乐观锁只是更新的时候同时带上 更新条件,其他时间不锁表,可以通过 version 或者其他状态条件。
7、分布式锁
如果分布式系统,构建全局唯一索引比较困难,唯一性字段没法确认,这个时候就需要引入第三方协调系统,比如 redis 、zookeeper 来实现全局锁功能。获取分布式锁然后做操作,之后释放锁,这其实就是多线程并发的思路。
8、select + insert
并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了。注意:核心高并发流程不要用这种方法;
9、状态机幂等
在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态机,对业务系统设计能力提高有很大帮助
10、对外提供接口的api如何保证幂等
如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号;source+seq在数据库里面做唯一索引,防止多次付款(并发时,只能处理一个请求) 。
重点:对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引,这样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处理结果;没有处理过,进行相应处理,返回结果。注意,为了幂等友好,一定要先查询一下,是否处理过该笔业务,不查询直接插入业务系统,会报错,但实际已经处理了。
四、总结
幂等与你是不是分布式高并发还有JavaEE都没有关系。关键是你的操作是不是幂等的。一个幂等的操作典型如:把编号为5的记录的A字段设置为0这种操作不管执行多少次都是幂等的。一个非幂等的操作典型如:把编号为5的记录的A字段增加1这种操作显然就不是幂等的。要做到幂等性,从接口设计上来说不设计任何非幂等的操作即可。譬如说需求是:当用户点击赞同时,将答案的赞同数量+1。改为:当用户点击赞同时,确保答案赞同表中存在一条记录,用户、答案。赞同数量由答案赞同表统计出来。总之幂等性应该是合格程序员的一个基因,在设计系统时,是首要考虑的问题,尤其是在像支付宝,银行,互联网金融公司等涉及的都是钱的系统,既要高效,数据也要准确,所以不能出现多扣款,多打款等问题,这样会很难处理,用户体验也不好。