关于重复提交的问题

2022-11-10 03:08:16 +08:00
 iam

请教大家一个问题

最近在做一个项目,接口使用了腾讯云函数,昨天发现前端会有重复提交的问题。尤其是在插入性质的接口,因为上一次请求还没完成,数据库并没有数据,重复的判断不会生效,会造成多次插入的 bug ;有没有什么好的解决方案,Redis 锁、数据库锁或者网关限制那种更好一点呢。
3024 次点击
所在节点    程序员
29 条回复
MrHyde
2022-11-10 03:12:31 +08:00
queue
CEBBCAT
2022-11-10 03:55:28 +08:00
queue 相当于把请求排了队,但又导致了队列只能有一个消费者的问题,未见其明也

我经验不是很多,这种情况下不是默认用数据库唯一约束、RequestID 来解决吗?
opengps
2022-11-10 08:20:14 +08:00
数据库锁只是备用,redis 锁才是关键。不然你数据库没落盘的都判断失效了
打卡页面时候提前带上唯一参数,如果这个唯一参数重复,那就说明当前页面存在重复提交,用这个参数实现拦截,甚至可以用这个参数达到幂等效果。
xuanbg
2022-11-10 08:22:12 +08:00
RequestID 如果在请求时产生,那 P 用没有。如果在初始化页面时产生,真要重发请求的时候就 SB 了。最有效的办法就是按钮点击即 disable3-5 秒。这三五秒内,就无法重复请求。过了这三五秒还没成功或失败,OP 你自己看着延长时间吧。
ragnaroks
2022-11-10 08:35:58 +08:00
首先前端一定需要做 throttle ,倒不是说方法都要 throttle() 一次,像楼上写的点击按钮后使按钮处于不可响应状态就是简单有效的方式。

其次根据具体业务来选择队列、锁、唯一键,处理像站内信这样的发后不管就可以用队列,处理像提现这样的只需要知道是否执行成功而不管业务结果就可以用锁,处理像修改某实体数据这样的就用唯一键。
dengji85
2022-11-10 08:48:10 +08:00
前面说的按钮禁用是比较好用的方案
YepTen
2022-11-10 08:51:02 +08:00
对后端来说:
1. 单节点
可以试试 LRUCache ,key 为 hash ( RequestData ),value 为 systemtime 。
对 LRUCache 指定大小
对 systemtime 进行判断,比如重复提交的最小间隔为 3S 。

业务处理上:
先查数据库,没有就放在 LRUCache 中。
2. 多节点
redis 锁吧。
DreamStar
2022-11-10 09:28:35 +08:00
数据库层面
数据库上唯一限制, 并发更新上乐观锁字段.

代码层面
做个过滤器, 出个幂等接口返一个 token, 同 token 只有一次能成功, 多次就是重复请求
redis 锁
BugCry
2022-11-10 09:30:10 +08:00
加参数:timestamp + nonce ,每次请求生成一个随机数作为 nonce 。
请求进来先判断一下(timestamp, nonce)是否存在,存在说明是重复请求。

好处是灵活,依赖少,不用锁。单节点可以直接在内存判断,多节点存 redis
wolfie
2022-11-10 09:31:09 +08:00
技术层面做不到通用的幂等。
拿 redis 作为锁 double check 。
qrobot
2022-11-10 09:31:35 +08:00
@ragnaroks 首先 throttle 是一件非常错误的行为, 因为 throttle 的实现就是 setTimeout , 这个在 JS 的线程模型中, 部分场景中会有非常不可思议的结果, 其次 throttle 多少毫秒合适? 600 毫秒? 1000 毫秒? 比如点击了,没有反馈, 就算间隔 5 秒,用户也会进行不断的点击, throttle 无疑就是一种非常错误的设计, 也是非常错误的解决方案.


相对于 throttle 来说, 按钮上的 loading 或者 按钮上的 禁用 都是一个非常好的方案,
qrobot
2022-11-10 09:33:04 +08:00
@qrobot 用户多次点击的原因其实很简单

1. 设备原因导致误触
2. 点击提交后, 没有反馈,以为网络延迟.
swulling
2022-11-10 09:33:53 +08:00
重复提交直接做幂等设计不就完了。

你最终是要落数据库的,那你就需要准备一个唯一字段,比如增加一个 uuid 字段。

这个字段由客户端生成,发过去后如果重复提交落库就会失败。
xiang0818
2022-11-10 09:44:05 +08:00
首先让前端做防抖,避免因为网络问题等问题造成的重复提交,二就是后端做幂等设计。方案网上有很多。
iPisces77
2022-11-10 09:53:32 +08:00
最后还是数据库唯一性兜底的
ragnaroks
2022-11-10 09:59:08 +08:00
@qrobot 如果你看不懂我的第一句话可以不用回复的。
ragnaroks
2022-11-10 10:07:15 +08:00
@qrobot
翻了你的 github 发现你可能确实没有经验,那我就好心为你讲解下。

"throttle" 并不是说前端的 throttle() 或类似的方法,而是说对某一件事情进行节流控制,事实上在 SOF 更多的是数据库和 HTTP 服务器相关。4 楼和我写的,将按钮处于一个不可响应的状态,那对于用户多次点击按钮这个行为来说就是 throttle 。至于到底是加 disabled 属性,还是替换为 loading 组件,那是具体产品设计。

最后,throttle 从来不是"非常错误的行为",在没有异步完成端口之前,F5 就能刷死 HTTPd 。
facelezz
2022-11-10 10:15:34 +08:00
重复提交业界的方案:以 uber 为例子
1.返回一个 rid 给前端,前端带着这个参数请求
2.后端有一个 rid 的表( rid 唯一索引),执行业务时把插入 rid 表的语句和业务组合成一个事务
facelezz
2022-11-10 10:16:59 +08:00
不推荐 redis 的锁的原因,因为如果是支付相关的,最好不要使用这种效率锁(可以参考 Martin Kleppmann 和 redis 创始人的讨论)
pengtdyd
2022-11-10 11:11:25 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/894008

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX