Java进阶知识点:接口幂等性
幂等概念
在计算机中,表示对同一个过程应用相同的参数多次和应用一次产生的效果是一样,这样的过程即被称为满足幂等性。
也可以进行如下表述:一个HTTP请求方法,如果被请求多次和被请求一次效果相同,被认为是幂等的,比如PUT、DELETE和其他安全的请求方法都是幂等的。
幂等:
update test_user set user_age = 25 where user_id = 2
这种情况无论执行多少次,结果都不受影响,所以是幂等的。
非幂等:
update test_user set user_times = user_times + 1 where user_id = 2
这样的更新语句每执行一次,结果都会不一样,所以是非幂等的。
幂等实现方法
1 全局唯一ID
如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID存储到存储系统中,比如数据库、Redis等;否则,表示该方法已经执行。
使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。比较适用于有限制导入导出操作,比如:希望一个账号在同一时间内只能导入或导出一次数据,这个时候就可以在导入前根据账号建立一个对账号唯一的key值放入redis中,执行业务方法时判断是否存在,最后执行成功或失败后删除redis的Key。
但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。
2 去重表
这种方法适用于在业务中有唯一标的插入场景中。
比如在以上的支付场景中,如果一个订单只会支付一次,那么订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,用以记录订单支付信息。在实现时,把创建支付单据和写入去重表放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。这个方法其实也是用到唯一ID,与上面全局唯一ID不同的是,它是针对具体单个业务流程的,实现起来相对简单。
3 插入或更新
这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:
insert into goods_category
(goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE update_time=now()
4 多版本控制
这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等:
boolean updateGoodsName(int id,String newName,int version);
在实现时可以如下:
update goods set name=#{newName},version=#{version} where
id=#{id} and version<$
5 状态机控制
这种方法适合在有状态机流转的情况下,比如订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100,付款失败为99。在做状态机更新时,我们就这可以这样控制:
update goods_order set status=#{status} where id=#{id} and
status<#
以上就是保证接口幂等性的一些方法。
小结
幂等性设计不能脱离业务来讨论,一般情况下,去重表同时也是业务数据表,而针对分布式的去重ID,可以参考以下几种方式:
- UUID
- Snowflake
- 数据库自增ID
- 业务本身的唯一约束
- 业务字段+时间戳拼接