Redis分布式锁你会了吗?
java1234
共 5816字,需浏览 12分钟
·
2020-09-12 20:26
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
一、前言
这里我简单说一下,代码可能没那么完美。希望各位大佬可以多多指点!!!要是有测试能力的兄弟可以帮我测一下,我这个分段加锁我是全部都在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 keys = Arrays.asList(KUS_NAME);
List 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 keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
List 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 keys = Arrays.asList(KUS_NAME,KEY_ID,UNIQUE_ID);
List 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 redisTemplate;
public void setRedisTemplate(RedisTemplate 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 map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public Map clear() {
Map map = new HashMap<>();
try {
// 获取所有key
Set keys = redisTemplate.keys("*");
assert keys != null;
// 迭代
Iterator 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 keys, List args){
return redisTemplate.execute((RedisCallback
六、测试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 init(@RequestParam("kuname") String kuname,@RequestParam("allnum") Integer allnum,@RequestParam("subnum") Integer subnum){
ConcurrentHashMap 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 map1 = new HashMap<>();
map1.put("msg","成功");
return map1;
}
/**
*
* @param kuname 初始化时得Redis仓库名称
* @param timeout 超时时间
* @return
*/
@RequestMapping("/seckill")
public Map seckill(@RequestParam("kuname") String kuname,@RequestParam(value = "timeout",defaultValue = "5") String timeout){
Map 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;
}
}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:
http://blog.csdn.net/G3187576034/article/details/108482317
粉丝福利:108本java从入门到大神精选电子书领取
???
?长按上方锋哥微信二维码 2 秒 备注「1234」即可获取资料以及 可以进入java1234官方微信群
感谢点赞支持下哈
评论