需求背景:
入庫單號:L-ASN202502270728
入庫批次號:100025022800151
數量:500
理貨后,生成了一個500的已理貨數據,上架后,生成了兩個500已上架數據,重復了


以php項目為例 php-laravel框架
1,通過url+請求參數進行加鎖處理 可以組織90%以上的重復點擊帶來的重復數據的問題
PreventDupSubmit.php
<?php namespace App\Http\Middleware; use App\Exceptions\InvalidRequestException; use App\Http\ApiHelper\Response; use App\Http\Services\WmsLockService; use Closure; use Illuminate\Support\Facades\Redis; class PreventDupSubmit { /** * 簡單的防止前端重復提交數據 導致的數據異常 * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $requestParams = $request->all(); $requestParams["adminId"] = getAdminUserId(); $requestParams["uri"] = $request->getUri(); if(isset($requestParams["file"])){ unset($requestParams["file"]); } \Log::info(sprintf("并發請求url:%s,請求參數:%s",$request->getUri(),json_encode($requestParams))); ksort($requestParams); $key = md5(serialize($requestParams)); $getLock = WmsLockService::getLock($key,8); // $request->offsetSet("request_md5_key",$key); if(!$getLock){ return response()->json(json_decode(Response::setError("請勿重復請求,請5秒后再發起請求"), true)); } // $result = Redis::Connection("wms")->set('wms_' . $key, 1,"EX",5,"NX"); // if(!$result){ // throw new InvalidRequestException("請勿重復請求,請5秒后再發起請求"); // } $response = $next($request); WmsLockService::delLock($key); // Redis::Connection("wms")->del('wms_' . $key); return $response; } }
WmsLockService.php
<?php namespace App\Http\Services; use Illuminate\Support\Facades\Redis; class WmsLockService { const LOCK_KEY = "wms_lock_"; public static function getLock($Key="",$time=15){ $requestId = uniqid(); // 生成一個唯一的請求ID $expireTime = 1000 * intval($time); // 鎖的過期時間,單位毫秒 // Lua腳本 $luaScript = <<<LUA local lock_key = KEYS[1] local request_id = ARGV[1] local expire_time = ARGV[2] local result = redis.call('SET', lock_key, request_id, 'NX', 'PX', expire_time) if result then return 1 else return 0 end LUA; $lockKey = self::LOCK_KEY.$Key; $result = Redis::Connection("wms")->eval($luaScript, 1, $lockKey, $requestId, $expireTime); return $result; } public static function delLock($lockKey=""){ Redis::Connection("wms")->del(self::LOCK_KEY.$lockKey); } }
Route::match(['get', 'post'], '/stockIn/stockShelf/oneKeyPutawayAction', 'StockShelfController@oneKeyPutawayAction')->middleware('prevent_dup_submit')

2,假如業務中出現非重復點擊,或者由于事務中處理比較慢(請求上架或者理貨 請求參數類似于[1,3,5] [1,3,8,9]),兩次請求中都有請求參數id:1,3 會造成1和3的并發問題。
處理方法就是針對具體請求的函數對單個參數進行加鎖 當上架id:[1,3,5]請求的時候可以讓1,3,5分別進行加鎖,下一個1,3,8,9來請求的時候就會因為拿不到鎖而報錯

RedisLock
<?php namespace App\Http\Utils; use Illuminate\Support\Facades\Redis; class RedisLock { protected $systemName; protected $functionName; const KEY_PREFIX = 'lock'; const TTL = 60; protected $randNumMap = [];//直接存儲的,只有php-fpm才建議使用,否則會出現內存溢出問題 public function __construct($systemName, $functionName) { $this->systemName = $systemName; $this->functionName = $functionName; } public static function initInstance($systemName, $functionName) { return new self($systemName, $functionName); } public function lock($key, $ttl = null) { $redis = Redis::connection(); $randNum = mt_rand(10000000, 99999999); $ttl = $ttl ?? self::TTL; $redisKey = self::KEY_PREFIX . ":{$this->systemName}:{$this->functionName}:{$key}"; $result = $redis->setNx($redisKey, $randNum); if ($result) { $redis->expire($redisKey, $ttl); $this->randNumMap[$key] = $randNum; return $randNum; } else { return null; } } public function unlock($key, $randNum) { $redis = Redis::connection(); $redisKey = self::KEY_PREFIX . ":{$this->systemName}:{$this->functionName}:{$key}"; $lua = <<<LUA if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end LUA; $result = $redis->eval($lua, 1, $redisKey, $randNum); return $result; } public function lockFunction($key, callable $callback,callable $failCallback = null) { $random = $this->lock($key); if ($random !== null) { $result = call_user_func($callback); $delResult = $this->unlock($key, $random); if ($delResult==0){ if ($failCallback){ call_user_func($failCallback); } } return $result; } else { return false; } } public function lockBatchKey($keyArr, $ttl = null) { $lockResult = []; foreach ($keyArr as $key) { $result = $this->lock($key, $ttl); if ($result) { $lockResult[$key] = $result; } else { $lockResult[$key] = 0; } } return $lockResult; } public function unlockBatchKey($resultArr) { $unlockResult = []; foreach ($resultArr as $key => $random) { $random = $this->randNumMap[$key] ?? $random; $result = $this->unlock($key, $random); if ($result) { $unlockResult[$key] = true; } else { $unlockResult[$key] = false; } } return $unlockResult; } }
本文來自博客園,作者:孫龍-程序員,轉載請注明原文鏈接:http://www.rzrgm.cn/sunlong88/p/18750146
浙公網安備 33010602011771號