1.需求分析
筆者的項目中有個簽到功能:一個使用者每天只能簽到一次;但是在並發的情況下,一個使用者可能一天能簽到數次;在這種情況下,筆者最先想到的就是運用redis的分布式鎖解決該問題 2.redis分布式鎖
①redis中的setnx具備天然的鎖機制;如果redis中存在key,則返回0,儲存失敗;不存在,則返回1,儲存成功;
②擷取redis分布式鎖的正確姿勢
private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";private static final Long RELEASE_SUCCESS = 1L;
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime){ String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); System.out.println("=============擷取分布式鎖結果:"+result); if (LOCK_SUCCESS.equals(result)) { return true; } return false;}
這裡還設定了redis的expire時間,當linux伺服器崩潰了或者redis崩了,不至於發生死結的情況,key在超過到期時間後自動刪除的。這種情況下key的儲存與到期時間的設定是原子性的操作,有效避免了前面所說的死結。
③擷取分布式鎖的錯誤方式
ShardedJedis shardedJedis = RedisClientUtil.getShardedJedis();long update = shardedJedis.setnx("goods","66");System.out.println("是否儲存成功:"+update);shardedJedis.expire("goods",30); //30秒的到期時間shardedJedis.close();
在這種情況下,如果系統發生宕機或者崩潰,那麼分布式鎖很可能成為死結,增添redis的壓力;畢竟這種情況下key的儲存與到期時間的設定是非原子性的操作;
④釋放分布式鎖的正確姿勢
/** * 釋放分布式鎖 * @param jedis * @param lockKey * @param requestId * @return */public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); if (RELEASE_SUCCESS.equals(result)) { return true; } return false;}
2.執行個體運用
①在並發的情況下,簽到重複
②分布式鎖正確解決該並發問題
//redis分布式鎖Jedis jedis = RedisClientUtil.getJedis();boolean flag = RedisClientUtil.tryGetDistributedLock(jedis,"singLock:"+userId,userId,1 * 60 * 1000);jedis.close();if(flag){ //擷取分布式鎖成功String registrationId = this.equipmentMapper.findRegistrationId(params.get("userId").toString());//簽到隨機紅包BigDecimal signRedPacketNum = new BigDecimal(RedPacketUtil.randomPacketBySign());//滿勤隨機紅包BigDecimal signFullRedPacket = new BigDecimal(RedPacketUtil.randomPacketBySignFull());int totalDays = DateUtil.getToalDayNow();Integer signDayNum = this.signMapper.findsignDayNum(params);Sign todaySign = this.signMapper.findTodaySign(params);//簽到紅包Map<String,String> pushMap=new HashMap<>();pushMap.put("redpactetId",params.get("uuid"));pushMap.put("packetAmount",String.valueOf(signRedPacketNum));pushMap.put("type", "2018");pushMap.put("packetType","3");pushMap.put("packetBody","簽到紅包");//滿勤紅包Map<String,Object> redpacketFullMaps = new HashMap<>();redpacketFullMaps.put("uuid",params.get("uuid"));redpacketFullMaps.put("userId",params.get("userId"));redpacketFullMaps.put("packetAmount",signFullRedPacket);redpacketFullMaps.put("packetType", 4);redpacketFullMaps.put("packetBody","恭喜你獲得一個簽到滿勤紅包");Map<String,String> pushSignMap=new HashMap<>();pushSignMap.put("redpactetId",params.get("uuid"));pushSignMap.put("packetAmount",Double.toString(signFullRedPacket.doubleValue()));pushSignMap.put("type", "2019");pushSignMap.put("packetType","4");pushSignMap.put("packetBody","滿勤紅包");HashSet<String> registrationIDs = new HashSet<>();registrationIDs.add(registrationId); if (todaySign != null) { return ResultJsonUtil.toFailureJSONString(TODAY_IS_SIGNED); } //補簽 if (params.get("type").equals("1")) { Integer repairSignNum = this.signMapper.findRepairSignNum(params); if (repairSignNum < 2) { this.signMapper.userSign(params); return ResultJsonUtil.toSuccessJSONString(uuid); } else { return ResultJsonUtil.toFailureJSONString(MONTH_TWO); } } //簽到 String signOrderCode = RandomCodeUtil.getOrderCode(PayTypeConfig.YIVI_USER_SIGN); //滿勤 String fullOrderCode = RandomCodeUtil.getOrderCode(PayTypeConfig.YIVI_USER_FULL_SIGN); if (params.get("type").equals("0") && todaySign == null) { this.signMapper.userSign(params); this.redPacketMapper.putRedPacket(redpacketMaps); // 添加系統錢包流水記錄 Double sysRemainAmount = this.accountMapper.findSysAmountByUserId(AlipayUtil.YIVI_SYSTEM_ACCOUNT); AccountFlow sysFlow = AccountFlow.newBuilder().setUserId(AlipayUtil.YIVI_SYSTEM_ACCOUNT).setOrderCode(signOrderCode) .setBody("紅包支出").setIsInflow("0").setType("3").setTotal_amount(signRedPacketNum.toString()) .setRemainAmount(sysRemainAmount.toString()).build(); Integer insertSysAccountFlow = this.accountFlowMapper.insertSysFlow(sysFlow.toMap()); try { PushThreadPool.getInstance().push(PushDataModel.redPacketSend(pushMap, registrationIDs), channal); } catch (Exception e) { e.printStackTrace(); } if (totalDays - signDayNum.intValue() == 1) { redPacketMapper.putRedPacket(redpacketFullMaps); AccountFlow sysFlowFull = AccountFlow.newBuilder().setUserId(AlipayUtil.YIVI_SYSTEM_ACCOUNT).setOrderCode(fullOrderCode) .setBody("紅包支出").setIsInflow("0").setType("3").setTotal_amount(signFullRedPacket.toString()) .setRemainAmount(sysRemainAmount.toString()).build(); this.accountFlowMapper.insertSysFlow(sysFlowFull.toMap()); PushThreadPool.getInstance().push(PushDataModel.redPacketSend(pushSignMap, registrationIDs), channal); } }}
筆者將使用者的userId作為key, 每個使用者都用一個屬於自己的分布式鎖的key,而且key的到期時間設定為1分鐘,這樣在一分鐘的時間內,並發是不可能發生的,等key失效再進行訪問,系統此時會拋出“今天您已經簽到過”的溫馨提醒
好了,我是張星,歡迎加入博主技術交流群,群號:313145288