前言
业务开发中,常常面临防止重复提交问题,当该情况发生往往会带来验证后果。前端操作抖动、快速操作、网络延迟以及后台处理慢等等都会增加后端重复处理的概率;
方案
- 前端提交之后,屏蔽提交按钮。该方案虽然可以启动一定作用,对于模拟接口请求就没有用。
- 提交表单跳转其他页面。该方案在极致情况下也是不安全的。
- 利用Session防止表单重复提交。客户端请求一个页面,服务端生成一个token(令牌)存在session中,并且把token放在页面中一起发给客户端,客户端提交请求时,服务端先验证token合法性,如果通过从session删除该值,继续处理业务;如果验证不通过返回错误,理论上接口并发请求还是可能会出现重复提交。
- 利用数据库字段设置唯一索引。可以有效避免表单重复提交,但增大服务器和数据库开销。
- 利用Redis加锁和删除锁。Redis中设置一个单据锁,利用set操作的原子性,只有获取该锁(未提交表单)才能提交表单,未获取(已提交表单)则直接返回客户端;这里单据锁的值过期时间一定要大于中间逻辑执行时间,不然就会出现重复提交。
<?php
// 加载redis组件
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 防止重复变量名称
$redis_name = 'lock';
// 模拟用户ID
$uid = 1;
$ok = $redis->rawCommand('set', $redis_name . '_' . $uid, $uid, 'EX', 5, 'NX');
if (!$ok) {
// 未获取到锁,表示已提交
// 其他处理逻辑...
write_log('test.log', ['status' => 0, 'msg' => '订单已提交,请勿重复提交', 'data' => ['uid' => $uid, 'ok' => $ok]]);
return false;
}
// 获取到锁,处理订单逻辑...
sleep(3);
// 其他逻辑处理完成,删除锁
$redis->del($redis_name . '_' . $uid);
write_log('test.log', ['status' => 1, 'msg' => '订单处理完成', 'data' => ['uid' => $uid, 'ok' => $ok]]);
$redis->close();
function write_log($filepath, $data) {
$dir_name = dirname($filepath);
if (!file_exists($dir_name)) { // 目录不存在就创建
// iconv防止中文名乱码
mkdir(iconv("UTF-8", "GBK", $dir_name), 0777, true);
}
$fp = fopen($filepath, "a+");// 打开文件资源通道 不存在则自动创建
fwrite($fp, date("Y-m-d H:i:s") . ":" . json_encode($data, JSON_UNESCAPED_UNICODE) . "\r\n");//写入文件
fclose($fp);// 关闭资源通道
}
---------------------------------------------------------------------------------------
模拟并发提交
/usr/local/wrk/wrk -t4 -c100 -d1s http://127.0.0.1:1010/queue_redis/avoid_duplicate.php
Running 1s test @ http://queue.babytime.vip/queue_redis/avoid_duplicate.php
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 131.30ms 136.14ms 663.84ms 89.58%
Req/Sec 168.42 146.36 500.00 84.62%
534 requests in 1.03s, 99.60KB read
Requests/sec: 518.12
Transfer/sec: 96.64KB
查看日志记录
[root@Lewis queue_redis]# tail -f test.log
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:19:{"status":0,"msg":"订单已提交,请勿重复提交","data":{"uid":1,"ok":false}}
2020-01-07 19:12:21:{"status":1,"msg":"订单处理完成","data":{"uid":1,"ok":true}}
总结
以上方案都有其优缺点,根据自己项目实际情况调整和搭配使用。 |