文章目录
- 0 环境
- 1 前言
- 2 准备
- 3 基本使用
- 4 超时时间解决
- 5 小结
0 环境
- 系统环境: centos7
- 编辑器: xshell和IDEA
1 前言
常见场景:
在单线程中 用户操作 一个线程修改用户状态 1 从数据库中读取用户状态 2 在内存中进行修改 3 修改好后 在重新写入 但在多线程中 读 改 写是三个操作 非原子操作 会出现问题
2 准备
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
代码结构 参考3-3节对连接池强约束
3 基本使用
- 一路畅通
先进来一个线程抢占位置 当其他线程进来操作时 发现已经有人占位了 放弃/等待(古语说的好 占坑那啥的 很贴切 setnx结束了 用del释放)
/**
* @Description: v1版本 --> 分布式锁 先来一个人占位 其他人 等着吧 直到这个人走了
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test() {
CallRedisDemo redisDemo = new CallRedisDemo();
redisDemo.execute(jedis -> {
// redis中setnx方法 当key为空时 创建 1
Long k = jedis.setnx("k", "123");
if (1 == k) {
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
jedis.del("k");
System.out.println("删除key:" + jedis.exists("k"));
}else {
// 有人占位 等待
}
});
}
注销
jedis.del("k");
或是添加休眠时间 就可以看到k是存到redis中的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okbGuk0L-1586332792927)()]
- 添加过期时间
若是在执行时 阻塞了 添加一个过期时间 哪怕阻塞 过期释放
/**
* @Description: v2版本 --> v1版本(中间无问题 一路畅通) 但总会有点跌跌撞撞的
* 假若出现异常 到不了删除key/执行错误等 key无法释放 请求阻塞
* 需要一个过期命令 例如食物有过期时间一样
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test1() {
CallRedisDemo redisDemo = new CallRedisDemo();
redisDemo.execute(jedis -> {
// redis中setnx方法 当key为空时 创建 1
Long k = jedis.setnx("k", "123");
if (1 == k) {
// 设置过期时间
jedis.expire("k", 6);
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
// jedis.del("k");
System.out.println("删除key:" + jedis.exists("k"));
}else {
// 有人占位 等待
}
});
}
/**
* @Description: v2.1版本 --> v2版本 还是会有问题 若是在shenx和设置过期时间(两个操作)之间出现异常
* 假如服务器挂了等 锁还在 未释放资源 解决问题(将2个操作合为1个操作 从redis2.8开始)
*
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test2() {
CallRedisDemo redisDemo = new CallRedisDemo();
redisDemo.execute(jedis -> {
// redis中setnx方法 当key为空时 创建 1 设置过期时间
String k = jedis.set("k", "123", new SetParams().nx().ex(9));
if (null != k && k.equals("OK")) {
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
jedis.del("k");
System.out.println("删除key:" + jedis.exists("k"));
}else {
// 有人占位 等待
}
});
}
4 超时时间解决
- 在centos7中 新建个目录 创建xxx.lua脚本 例如
mkdir redis-lua
vim test.lua
- 编写调用方式(test.lua)好后 wq保存
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
- 将lua脚本加载到redis中
cat springcloud/lua/redis-lua/test.lua | redis-cli -a 123456 script load --pipe
script load缓存lua脚本 并将返回SHA1校验和 复制到java中evalsha参数里
/**
* @Description: v2.2版本 --> 超时问题
* 假如A获得锁 --> 由于处理时间长 需要13s 导致在未完成的情况下 释放了A锁
* 这时B拿到了锁 刚执行5s A完成操作 释放B锁
* C拿到锁 运行 B完成操作 释放C锁
*
* 思路:
* 1 对于耗时业务 避免使用锁
* 2 在锁上下功夫了 给锁加一个保安(value设置随机字符串) 你要出去是吧
* 说一下你的 对比一下(随机字符串与redis中的对比) 一致你出去吧
* 否则 继续在家呆着 瞎跑啥
*
* 方案:选择redis中的lua脚本 其原子性 缓存特性 操作性 使用方便 内置包不错
*
* lua优点:
* 1 使用方便
* 2 其原子性可执行多个redis命令
* 3 对于网络性能 一次多个命令一起执行 更好 类似cloud中的 请求合并
*
* 使用lua脚本:
* 1 在服务端写好lua脚本 客户端调用
* 2 在客户端编写lua脚本 执行时发送给服务器端的redis上执行
*
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test3() {
CallRedisDemo redisDemo = new CallRedisDemo();
redisDemo.execute(jedis -> {
// 设置随机数
String randVal = UUID.randomUUID().toString();
// redis中setnx方法 当key为空时 创建 1 设置过期时间
String k = jedis.set("k", randVal, new SetParams().nx().ex(9));
if (null != k && k.equals("OK")) {
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
// jedis.del("k");
// System.out.println("删除key:" + jedis.exists("k"));
// 释放锁 随机数与redis中的比对
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k"), Arrays.asList(randVal));
System.out.println("通过");
}else {
System.out.println("没有拿到锁");
}
});
}
- 加个for循环呢
/**
* @Description: v2.2版本 --> 超时问题
* 假如A获得锁 --> 由于处理时间长 需要13s 导致在未完成的情况下 释放了A锁
* 这时B拿到了锁 刚执行5s A完成操作 释放B锁
* C拿到锁 运行 B完成操作 释放C锁
*
* 思路:
* 1 对于耗时业务 避免使用锁
* 2 在锁上下功夫了 给锁加一个保安(value设置随机字符串) 你要出去是吧
* 说一下你的 对比一下(随机字符串与redis中的对比) 一致你出去吧
* 否则 继续在家呆着 瞎跑啥
*
* 方案:选择redis中的lua脚本 其原子性 缓存特性 操作性 使用方便 内置包不错
*
* lua优点:
* 1 使用方便
* 2 其原子性可执行多个redis命令
* 3 对于网络性能 一次多个命令一起执行 更好 类似cloud中的 请求合并
*
* 使用lua脚本:
* 1 在服务端写好lua脚本 客户端调用
* 2 在客户端编写lua脚本 执行时发送给服务器端的redis上执行
*
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test3() {
CallRedisDemo redisDemo = new CallRedisDemo();
int count = 1;
for (int i = 0; i < 2; i++) {
System.out.println(count++);
redisDemo.execute(jedis -> {
// 设置随机数
String randVal = UUID.randomUUID().toString();
// redis中setnx方法 当key为空时 创建 1 设置过期时间
String k = jedis.set("k", randVal, new SetParams().nx().ex(9));
if (null != k && k.equals("OK")) {
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
// jedis.del("k");
// System.out.println("删除key:" + jedis.exists("k"));
// 释放锁 随机数与redis中的比对
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k"), Arrays.asList(randVal));
System.out.println("通过");
} else {
System.out.println("没有拿到锁");
}
});
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ATAzC72z-1586332792929)(https://imgkr.cn-bj.ufileos.com/cc6c8689-c185-435e-98f2-06316c911776.png)]
- 验证释放
/**
* @Description: v2.2版本 --> 超时问题
* 假如A获得锁 --> 由于处理时间长 需要13s 导致在未完成的情况下 释放了A锁
* 这时B拿到了锁 刚执行5s A完成操作 释放B锁
* C拿到锁 运行 B完成操作 释放C锁
*
* 思路:
* 1 对于耗时业务 避免使用锁
* 2 在锁上下功夫了 给锁加一个保安(value设置随机字符串) 你要出去是吧
* 说一下你的 对比一下(随机字符串与redis中的对比) 一致你出去吧
* 否则 继续在家呆着 瞎跑啥
*
* 方案:选择redis中的lua脚本 其原子性 缓存特性 操作性 使用方便 内置包不错
*
* lua优点:
* 1 使用方便
* 2 其原子性可执行多个redis命令
* 3 对于网络性能 一次多个命令一起执行 更好 类似cloud中的 请求合并
*
* 使用lua脚本:
* 1 在服务端写好lua脚本 客户端调用
* 2 在客户端编写lua脚本 执行时发送给服务器端的redis上执行
*
* @Param:
* @return:
* @Author: 水面行走
* @Date: 2020/4/6
*/
private static void test3() throws InterruptedException {
CallRedisDemo redisDemo = new CallRedisDemo();
int count = 1;
for (int i = 0; i < 2; i++) {
System.out.println(count++);
redisDemo.execute(jedis -> {
// 设置随机数
String randVal = UUID.randomUUID().toString();
// redis中setnx方法 当key为空时 创建 1 设置过期时间
String k = jedis.set("k", randVal, new SetParams().nx().ex(9));
if (null != k && k.equals("OK")) {
System.out.println("新建key:" + jedis.exists("k"));
// 赋值
jedis.set("age", "15");
System.out.println("获取age:" + jedis.get("age"));
// 使命完成 销毁
// jedis.del("k");
// System.out.println("删除key:" + jedis.exists("k"));
// 释放锁 随机数与redis中的比对
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k"), Arrays.asList(randVal));
System.out.println("通过");
} else {
System.out.println("没有拿到锁");
}
});
// 添加休眠时间 让其真正释放
Thread.sleep(9000);
}
}
5 小结
从开始的setnx 到过期时间的配置 更换为具体原子性的lua脚本