高并发下的接口幂等性如何实现

转自https://mp.weixin.qq.com/s/odRypb6YqF3xuRn-4YAwlg
https://blog.csdn.net/wanglei303707/article/details/88298211

幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的。

1、防止页面重复提交——token

1.1 场景

页面的数据只能被点击提交一次

1.2 发生原因

由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交

1.3场景:

场景一:在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交
场景二:表单提交后用户点击【刷新】按钮导致表单重复提交
场景三:用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交

1.4 解决办法

集群环境:采用token加redis(redis单线程的,处理需要排队)
单JVM环境:采用token加redis或token加jvm内存

1.5 处理流程:

1.5.1 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间;
1.5.2 前端提交后,后台校验token,同时删除token,执行业务生成新的token返回
token特点:要申请,一次有效性,可以限流

因为redis单线程的原因,当多次提交时,redis需要排队处理,所以只有第一次提交可以删除token成功,当删除成功代表token校验通过,删除失败,说明是重复提交。

image.png

2、防止新增脏数据(重复数据)——唯一索引

查询操作和删除操作天然就是幂等的:

  • 查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作;

  • 删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个)

  • 新增操作:有些服务是有重试机制的(ribbon),在这种情况下,就可能会出现新增两条一样的数据,这时候服务要支持幂等操作,否则会出问题,这时候可以通过数据库唯一索引来防止新增脏数据。
    比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录

2.1 唯一索引或唯一组合索引来防止新增数据存在脏数据

当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可

2.2 防重表

使用订单号orderNo做为去重表的唯一索引,每次请求都根据订单号向去重表中插入一条数据。第一次请求查询订单支付状态,当然订单没有支付,进行支付操作,无论成功与否,执行完后更新订单状态为成功或失败,删除去重表中的数据。后续的订单因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。可以看出防重表作用是加锁的功能。

image.png

防重表可以在一些主业务表不方便加唯一索引或者唯一组合索引时其作用,通过单独新建一张防重表来解决问题

3、对外提供接口的api如何保证幂等

如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号,source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)

重点: 对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引

这样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处理结果;没有处理过,进行相应处理,返回结果。

注意,为了幂等友好,一定要先查询一下,是否处理过该笔业务,不查询直接插入业务系统,会报错,但实际已经处理了。

这里使用的还是数据库唯一索引(联合唯一索引)。

对于一些有时效性的业务处理接口来说,事实上还有一种方法,就是使用redis。

  1. 当第三方调用时,先判断是否过期,如果过期了,就不处理,如果没有过期,则下一步;
  2. 判断redis中是否存在source_seq key,如果存在,说明已经处理过了,是重复调用,直接返回;如果不存在,则set source_seq key ,注意这里的存在则返回,不存在则set key,是需要保持原子操作的,可以通过lua脚本来提交;
  3. key有效期和业务有关系, 比如说我支付消息有效期只有1h,那么key 有效期也是1h;

4、同一时间只能完成一次请求——乐观锁、分布式锁

在分布式环境下,因为网络、重试等原因导致一个长流程请求经过不同的实例时,会发生重复操作,为了防止这种情况,可以采用乐观锁,或者分布式锁来解决。

image.png
image.png

使用乐观锁,或者分布式锁,其实就是在mysql中,同一时间只有一次请求。
在有些场景下,使用version乐观锁还是麻烦,其实可以使用主键id,或者唯一索引来锁定mysql数据,然后再操作,比如状态变更,如下

update tableA set status = 5 where id=4 and status=4;

就算同一时间有重复操作,但是因为mysql行锁的特性,同一时间只有一个操作能锁定id为4的这行数据,状态变更成功以后(返回值为1),后面的操作已经不满足status=4的条件了,利用这个特性,不止能防重复,对于一些并发开奖场景,可以直接替代乐观锁的使用。

5、总结

查询操作:select,查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作;
删除操作:delete,删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个);
新增操作:insert,业务逻辑上,先select再insert;数据库上尽量通过唯一索引或唯一组合索引来防止新增脏数据(重复数据);如果没有唯一索引,则再考虑分布式锁。
更新操作:update,对于状态操作,where条件中,一定要加上原本的状态值,即明确是由旧状态变更为新状态,这个过程是不可逆的。

如果上面的一般方法还是不能防重,则可以使用redis分布式锁。

使用token+redis的好处是,一个token只能使用一次,token使用或者过期后,就只能再次申请token,是严格防止重复的。但是会比较麻烦,一个请求,需要两次操作,一次获取token,一次执行业务。

其实可以简单的只用redis分布式锁来防止重复,把获取锁和释放锁的逻辑写在过滤器,或者拦截器中,只有获取到锁,才能执行业务逻辑。—— 对那些需要加防重的接口url,可以放在配置文件中,而不要放在redis中,因为绝对部分接口url是不需要防重的,如果放在redis中,每个请求都会去redis中获取url配置,太浪费redis了,如果赶上公司搞活动,很容易就报警了。

key的设置:可以先把请求参数中为空的去掉,然后拼接为String,并对其MD5加密,再加上userId,最后拼接成key。

至于redis分布式的实现,可以见redis系列文章。https://www.jianshu.com/nb/39445385

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容