前言
業務開發中,常常面臨防止重複送出問題,當該情況發生往往會帶來驗證後果。前端操作抖動、快速操作、網絡延遲以及背景處理慢等等都會增加後端重複處理的機率;
方案
- 前端送出之後,屏蔽送出按鈕。該方案雖然可以啟動一定作用,對于模拟接口請求就沒有用。
- 送出表單跳轉其他頁面。該方案在極緻情況下也是不安全的。
- 利用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 檢視日志記錄 [[email protected] 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}}
總結
以上方案都有其優缺點,根據自己項目實際情況調整和搭配使用。