后端接口如何实现防重复提交?
在平时工作中,我们经常会碰到一些应用场景,需要我们去做防重复提交或防重复点击处理,比如像投票、抽奖等等。正常情况下,前后端的都要去做防重复点击处理。今天我们则重点介绍一下服务器端如何实现对接口的防重复提交。
比较常见的一种方案是,我们首先对请求生成一个唯一的标识,然后针对该标识去做防重复提交判断。
废话不多说,直接上代码:
// 示例代码,仅供参考
$request_signature = sha1(
$request->method() .
'|' . $request->server('SERVER_NAME') .
'|' . $request->path() .
'|' . $request->ip() .
'|' . $authInfo['user_id']
);
$redis = app('redis')->connection('default');
if ($redis->get($request_signature)) {
throw new Exception('请勿重复提交。');
}
$expire_time = 10; // 10 秒内只能发起一次请求
$redis->set($request_signature, 1, 'EX', $expire_time);
echo "请求成功" . PHP_EOL;
上面这种情况可能是大家比较常见的吧,但是这里面有一个很严重的问题,正常情况下,我们的接口运行良好,也不存在重复提交的问题。但是当遇到并发请求的时候,我们就会发现,这种方法根本起不到防止重复提交的作用。
下面这张截图是我在本地模拟并发请求的测试结果,可以看到,我们在 8 秒内发起了 100 次请求,结果居然有 3 次请求成功:
那如何解决这种情况呢?大家可能还记得我们前面在写互斥锁的时候用到了 Redis 的 setnx
命令。下面我们给出改进版的方案:
// 示例代码,仅供参考
$request_signature = sha1(
$request->method() .
'|' . $request->server('SERVER_NAME') .
'|' . $request->path() .
'|' . $request->ip() .
'|' . $authInfo['user_id']
);
$redis = app('redis')->connection('default');
if ($redis->setnx($request_signature, 1) !== 1) {
throw new Exception('请勿重复提交。');
}
$expire_time = 10; // 10 秒内只能发起一次请求
$redis->expire($request_signature, $expire_time);
echo "请求成功" . PHP_EOL;
同样,我们在本地模拟下并发请求测试,结果如下:
可以看到,同样是在 8 秒内发起了 100 次请求,结果只有 1 次请求成功。
写到这里,我们就已经能够实现后端接口的防重复提交了。
我们知道 setnx
之所以能成功做到在高并发下限制接口的重复提交,完全是基于 Redis 本身的原子性操作。想要在 Redis 中实现原子性操作,还有一种办法,那就是利用 Lua 脚本。
下面我再贴出另外一种实现方案:
// 示例代码,仅供参考
$request_signature = sha1(
$request->method() .
'|' . $request->server('SERVER_NAME') .
'|' . $request->path() .
'|' . $request->ip() .
'|' . $authInfo['user_id']
);
$lua_script = <<<LUA
local cmd = redis.call
local req_uuid = KEYS[1]
local expire = ARGV[1]
if (cmd('EXISTS', req_uuid) == 1) then
return cmd('INCR', req_uuid)
else
cmd('SETEX', req_uuid, expire, 1)
return 1
end
LUA;
$expire_time = 10; // 10 秒内只能发起一次请求
$req_count = app('redis')->eval($lua_script, 1, $request_signature, $expire_time);
if ($req_count > 1) {
throw new Exception('请勿重复提交。');
} else {
echo "请求成功-->{$req_count}" . PHP_EOL;
}
同样,我们拿到并发下进行测试,结果如下:
效果同 setnx
一样,同样在 8 秒内发起了 100 次请求,只有 1 次请求成功。但是,这种方案还有另外一个好处,那就是我不仅能做到接口的防重复提交,还能做到指定时间窗口内接口访问频率的限制。关于接口限流,我们这里就不展开说了,后面我会单独写一篇文章来专门介绍一些常见的接口限流的方案。
好了,到这里关于后端接口防重复提交的办法我们就介绍完了。如果您有什么其他更好的方案,欢迎评论区留言,大家相互学习~
写在最后
如果本文对您有所帮助或者有所启发,请帮忙扫描下方二维码或微信搜索 「自在牛马」 关注一下我的公众号,您的支持是我最大的写作动力。感谢~
拒绝白嫖,转载请注明出处。
本作品采用《CC 协议》,转载必须注明作者和本文链接
Redis Lua 维护都异常复杂, 不宜这样搭桥实现, 不为跳坑而要埋坑. 如果我没猜错的话, 可以结合mysql来实现.