Redis分布式鎖 分段加鎖思想實作 分段鎖
一、前言
? 這里我簡單說一下,代碼可能沒那么完美,希望各位大佬可以多多指點!!!要是有測驗能力的兄弟可以幫我測一下,我這個分段加鎖我是全部都在Redis中實作主要用到的技術就是Lua腳本,
二、建議
? 建議搭配訊息佇列使用,防止一下進來太多請求,達到一個削峰的效果,
三、簡述
? 嘗試加鎖: 首先當一個請求發送過來了,嘗試獲取分段鎖得時候會查出所有的分段倉庫的key并嘗試創建標識如果成功回傳倉庫的KEY不成功就進行下個倉庫KEY的如果成功了會使用倉庫的KEY創建一個標識來鎖定這個庫存并添加過期時間然后回傳一個 倉庫的KEY,
? 解鎖:根據之前那個 倉庫的KEY 查出用戶唯一標識然后和我們解鎖傳入的用戶id進行比較看看是否相對再決定是否釋放這個鎖,釋放鎖的時候我們會檢查庫存如果為0那么順便洗掉庫存提高下次鎖獲取的效率,


四、流程圖

五、核心代碼
Lua腳本部分為了方便各位大神查看我單獨寫一個代碼塊
//TRY_LOCK
if redis.call("EXISTS",KEYS[1]) > 0 then
local kus = redis.call("HKEYS",KEYS[1]);
for i = 1, #kus do
if redis.call('setnx',kus[i],ARGV[1]) > 0 then
redis.call('expire', kus[i], ARGV[2])
return kus[i];
end
end
else
return nil;
end
//DECR
local kusName = KEYS[1]; local key = KEYS[2];
local uid = redis.call('get',key);
local kuName = redis.call('HGET',kusName,key);
local kuNum = redis.call('get',kuName);
if redis.call("EXISTS",kusName) > 0 then
if KEYS[3] == uid then
if kuNum ~= nil and tonumber(kuNum) <= 1 then
return redis.call('DECR',kuName);
else
return null;
end
end
else
return -1;
end
//UN_LOCK
local kusName = KEYS[1];
local key = KEYS[2];
local uid = redis.call('get',key);
local kuName = redis.call('HGET',kusName,key);
local kuNum = redis.call('get',kuName);
if uid == KEYS[3] then
if kuNum ~=nil and tonumber(kuNum) <= 0 then
redis.call('hdel',kusName,key);
redis.call('del',kuName);
end
return redis.call('del',key);
end
return false;
package com.suoxin.core.lock;
import com.suoxin.utils.RedisTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
/**
* @author 所心
* @create 2020-09-01 下午 5:11
* @description Redis分段鎖
*/
public class RedisDistributedLock {
Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);
@Resource
private RedisTools tools;
//這里我使用得是lua得方式
private static final String TRY_LOCK = "if redis.call('EXISTS',KEYS[1]) > 0 then local kus = redis.call('HKEYS',KEYS[1]); for i = 1, #kus do if redis.call('setnx',kus[i],ARGV[1]) > 0 then redis.call('expire', kus[i], ARGV[2]) return kus[i]; end end else return nil; end";
private static final String DECR = "local kusName = KEYS[1]; local key = KEYS[2]; local uid = redis.call('get',key); local kuName = redis.call('HGET',kusName,key); local kuNum = redis.call('get',kuName); if KEYS[3] == uid then if kuNum ~= nil and tonumber(kuNum) >= 1 then return redis.call('DECR',kuName); else return nil; end end";
private static final String UN_LOCK = "local kusName = KEYS[1]; local key = KEYS[2]; local uid = redis.call('get',key); local kuName = redis.call('HGET',kusName,key); local kuNum = redis.call('get',kuName); if uid == KEYS[3] then if kuNum ~=nil and tonumber(kuNum) <= 0 then redis.call('hdel',kusName,key); redis.call('del',kuName); end return redis.call('del',key); end return false;";
private String KUS_NAME = null;
private String UNIQUE_ID = null;
private String EXPIRE = "60";
private String KEY_ID = null;
/**
*
* @param KUS_NAME 分段庫名稱
* @param UNIQUE_ID 唯一標識
* @param EXPIRE 過期時間 以秒為單位
*/
public RedisDistributedLock(String KUS_NAME, String UNIQUE_ID, String EXPIRE) {
this.KUS_NAME = KUS_NAME;
this.UNIQUE_ID = UNIQUE_ID;
this.EXPIRE = EXPIRE;
//log.info("引數初始化:===>總倉庫名稱: {}, 唯一標識: {}, 過期時間: {}",KUS_NAME,UNIQUE_ID,EXPIRE);
}
/**
* 初始化工具類
* @param tools
*/
public void setTools(RedisTools tools) {
this.tools = tools;
}
/**
* 獲取鎖 前面設定的時間就是鎖得過期時間
* @return
*/
public boolean tryLock(){
List<String> keys = Arrays.asList(KUS_NAME);
List<String> args = Arrays.asList(UNIQUE_ID,EXPIRE);
Object result = tools.luaScript(TRY_LOCK, keys, args);
if (result != null && (result.toString().length() > 3)){
this.KEY_ID = result.toString();
//log.info("倉庫ID: {} 回傳結果: {} 獲取鎖成功",KEY_ID,result);
return true;
} else {
//log.info("倉庫ID: {} 回傳結果: {} 獲取鎖失敗",KEY_ID,result);
return false;
}
}
/**
* 原子性扣減
* @return
*/
public Integer decr(){
Object result = null;
if (KEY_ID != null){
List<String> keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
List<String> args = Arrays.asList();
result = tools.luaScript(DECR,keys,args);
if (result != null && !result.toString().isEmpty()){
log.info("消費成功 回傳結果: {}",result);
return 1;
}else {
//log.info("消費失敗 回傳結果: {}",result);
return 0;
}
}
log.info("已售空...{}"+ result);
return -1;
}
/**
* 釋放鎖
*/
public void unLock(){
if (KEY_ID != null){
List<String> keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
List<String> args = Arrays.asList();
Object result = tools.luaScript(UN_LOCK, keys, args);
//log.info("釋放鎖 回傳結果: {}",result);
}
}
}
工具類代碼:
package com.suoxin.utils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author 所心
* @create 2020-08-06 下午 11:01
* @description RedisUtils 工具類
*/
public class RedisTools {
private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* @param key 鍵
* @param value 值
* @return true成功 false失敗
* 普通快取放入
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* @param key 鍵
* @param map 對應多個鍵值
* @return true 成功 false 失敗
*
* HashSet
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Map<String, Object> clear() {
Map<String, Object> map = new HashMap<>();
try {
// 獲取所有key
Set<String> keys = redisTemplate.keys("*");
assert keys != null;
// 迭代
Iterator<String> it1 = keys.iterator();
while (it1.hasNext()) {
// 回圈洗掉
redisTemplate.delete(it1.next());
}
map.put("code", 1);
map.put("msg", "清理全域快取成功");
return map;
} catch (Exception e) {
map.put("code", -1);
map.put("msg", "清理全域快取失敗");
return map;
}
}
public Object luaScript(String script, List<String> keys, List<String> args){
return redisTemplate.execute((RedisCallback<Object>) connection -> {
Object nativeConnection = connection.getNativeConnection();
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return ((JedisCluster) nativeConnection).eval(script, keys, args);
}
// 單機模式
else if (nativeConnection instanceof Jedis) {
return ((Jedis) nativeConnection).eval(script, keys, args);
}
return false;
});
}
}
六、測驗Controller
package com.suoxin.controller;
import com.suoxin.core.lock.RedisDistributedLock;
import com.suoxin.utils.RedisTools;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 所心
* @create 2020-09-03 下午 1:22
* @description 測驗Controller
*/
@RestController
public class SeckillController {
@Resource
private RedisTools tools;
private RedisDistributedLock lock = null;
/**
*
* @param kuname Redis初始化得Redis庫名稱
* @param allnum 庫存總數
* @param subnum 分段數
* @return 僅是測驗沒有寫太完美但是核心代碼還是五臟俱全得
*/
@RequestMapping("/init")
public Map<String,Object> init(@RequestParam("kuname") String kuname,@RequestParam("allnum") Integer allnum,@RequestParam("subnum") Integer subnum){
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();
int max=100000,min=1000;
for (int i = 0; i < (allnum/subnum); i++) {
long nowtime = System.currentTimeMillis();
int ran2 = (int) (Math.random()*(max-min)+min);
map.put(UUID.randomUUID().toString(), Long.parseLong(nowtime+""+ran2));
tools.set(nowtime+""+ran2, subnum);
}
tools.hmset(kuname, map);
Map<String, Object> map1 = new HashMap<>();
map1.put("msg","成功");
return map1;
}
/**
*
* @param kuname 初始化時得Redis倉庫名稱
* @param timeout 超時時間
* @return
*/
@RequestMapping("/seckill")
public Map<String, Object> seckill(@RequestParam("kuname") String kuname,@RequestParam(value = "timeout",defaultValue = "5") String timeout){
Map<String, Object> map1 = new HashMap<>();
long run = System.currentTimeMillis();
//初始化鎖
lock = new RedisDistributedLock(kuname, UUID.randomUUID().toString(), timeout);
lock.setTools(tools);
try {
//獲取鎖
if (lock.tryLock()) {
map1.put("msg","恭喜您搶到了!!!:" + lock.decr());
} else {
map1.put("msg","很遺憾您沒有搶到 ╥﹏╥...");
}
} finally {
//計算時間
long end = System.currentTimeMillis();
map1.put("elapsedTime","耗時:===>"+ (end-run) +"ms");
//釋放鎖
lock.unLock();
return map1;
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/2841.html
標籤:其他
