涉及到重複提交或交易的地方前端
用戶購買商品,下單時,有時不當心連續點擊屢次;
或者網絡很差,致使用戶覺得沒有提交,重複點擊提交按鈕;
網絡層面好比nginx的重發.java
對於分佈式系統,提交訂單的n個請求可能會被不一樣的服務單體消費,
那麼就會生成多個相同(除了訂單號,其餘購買信息徹底同樣)的訂單,nginx
後果:redis
有哪些解決方法呢?json
參考: 分佈式系統後臺如何防止重複提交緩存
流程以下:
代碼以下:服務器
/*** * 提交訂單 * @param model * @param request * @param response * @return */ @ResponseBody @RequestMapping(value = "/order/submit/json", produces = SystemHWUtil.RESPONSE_CONTENTTYPE_JSON_UTF) public String jsonSubmitOrder2(Model model, HttpServletRequest request, HttpServletResponse response , @RequestParam(required = false) Boolean del) { //能夠抽取出常量 String lockUniquePrefix = "lock"; Jedis jedis = getJedis(); //1. 獲取鎖 // key:"lock"+方法名,value:時間戳 //NX -- Only set the key if it does not already exist. String key = lockUniquePrefix + Thread.currentThread().getStackTrace()[1].getMethodName(); //"OK":成功;null:失敗 String result = jedis.set(key, "aa", "NX", "EX"/*seconds*/, 1000); Const.pool.returnResource(jedis); boolean success = "OK".equals(result); System.out.println("success :" + success); System.out.println("result :" + result); if (success) { //2. 執行具體業務邏輯 //... } //3. 業務邏輯執行完成以後,釋放鎖 jedis = getJedis(); jedis.del(key); Const.pool.returnResource(jedis); return BaseResponseDto.put2("result", result).put("success", success).toJson(); }
爲何jedis.set方法中要使用"NX"呢? 由於只有當key不存在時,操做纔會成功, 即key不存在時,jedis.set返回"OK",表示獲取鎖成功; key存在時,jedis.set返回null,表示獲取鎖失敗網絡
不一樣的兩個用戶同時提交訂單,是容許併發的,這種狀況不該該使用鎖機制來限制,
因此咱們使用分佈式鎖限制的只是 兩次請求信息徹底相同的兩次請求,
形成這種兩次徹底相同的請求的緣由,多是網絡卡頓致使用戶重複點擊,或者nginx 的重發併發
優化以後的代碼:app
//能夠抽取出常量 String lockUniquePrefix = "lock"; Jedis jedis = getJedis(); //1. 獲取鎖 // key:"lock"+方法名,value:時間戳 //NX -- Only set the key if it does not already exist. String key = lockUniquePrefix + Thread.currentThread().getStackTrace()[1].getMethodName(); Integer userId = 222; String conventionK = requestSafeThreadParamDto.getCid(); //不一樣的兩個用戶同時提交訂單,是容許併發的,這種狀況不該該使用鎖機制來限制, //因此咱們使用分佈式鎖限制的只是 兩次請求信息徹底相同的兩次請求, //形成這種兩次徹底相同的請求的緣由,多是網絡卡頓致使用戶重複點擊,或者nginx 的重發 String hashSource = WebServletUtil.buildHashSource(request, userId, conventionK); //對請求信息 作hash long crc32Long = EncryptionUtil.getHash(hashSource); //"OK":成功;null:失敗 String result = jedis.set(key + crc32Long, "aa", "NX", "EX"/*seconds*/, 1000); Const.pool.returnResource(jedis); boolean success = "OK".equals(result); System.out.println("success :" + success); System.out.println("result :" + result); try { if (success) { //2. 執行具體業務邏輯 //... } } catch (Exception e) { e.printStackTrace(); } finally { //3. 業務邏輯執行完成以後,釋放鎖 if (null != del && del) { jedis = getJedis(); jedis.del(key); Const.pool.returnResource(jedis); } } // return new BaseResponseDto(true).setValue(result).toJson(); return BaseResponseDto.put2("result", result).put("success", success).toJson();
由於關於鎖有兩個重要的操做:
在分佈式環境,必須保證這兩個操做是原子性的,
即不能把獲取鎖分爲兩步:先查詢,再add.
同時,獲取鎖時,可以設置有效期.
redis除了能夠實現分佈式鎖,還能做爲緩存服務器,
在實現需求中,我常常把一些容易變化的配置放在redis中, 這樣當產品經理需求變動時,我只需修改redis,即時生效,不用上線
redis 還能夠當作定時器