前言
之前复习和记录了一下redis的基础数据类型
今天看了一下 这本书对于redis实现分布式锁的内容
感觉差不多 也是一样的操作 利用redis本身的特性做的单节点分布式锁 和多节点分布式锁的处理
分布式锁:在分布式环境中 对于资源锁定需要一个能共享的变量区域来存储资源的锁定状态 从而处理相关资源
常用实现分布式锁的中间件有 redis、zk、etcd等等 只要是能共享存储的可并发访问使用的都可以用来实现分布式锁 redis只是其中一种方案
实现分布式锁
一般项目中 单机redis 即可满足使用要求
但是也存在 分布式情况 这里只考虑官方cluster分布式方案的分布式锁实现
只需要 使用set 来加锁和设置时间 exists判断是否锁定 del主动删除锁
指令演示
1 2 3 4 5 6 7
|
set name value ex 5 nx
exists name
del name
|
单机模式
java实现代码
直接使用 redisTemplate来操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| package com.ming.base;
import com.ming.core.utils.SpringBeanManager; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Objects; import java.util.concurrent.TimeUnit;
@Slf4j public class RedisLock {
private static final long DEFAULT_EXPIRE = 10;
public static void lock(String key) { lock(key, System.currentTimeMillis() + "", DEFAULT_EXPIRE); }
public static void lock(String key, Long expire) { lock(key, System.currentTimeMillis() + "", expire); }
public static void lock(String key, String value) { lock(key, value, DEFAULT_EXPIRE); }
public static void lock(String key, String value, Long expire) { log.debug("加锁,key{},value{},超时时间:{}s", key, value, expire); if (!Objects.requireNonNull(getClient().opsForValue().setIfAbsent(key, value, expire, TimeUnit.SECONDS), "加锁返回异常")) { throw new RuntimeException("加锁失败、已经锁定"); } }
public static void unLock(String key) { log.debug("释放锁,key{}", key); getClient().delete(key); }
private static StringRedisTemplate getClient() { return SpringBeanManager.getBean(StringRedisTemplate.class); } }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.ming;
import com.ming.base.RedisLock; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) @SpringBootTest(classes = Start.class) public class RedisLockTest { @Test public void test(){ RedisLock.lock("ming"); try{ RedisLock.lock("ming"); }catch (RuntimeException e){ e.printStackTrace(); } RedisLock.unLock("ming"); RedisLock.lock("ming"); RedisLock.unLock("ming"); }
}
|
cluster集群模式
分布式情况下 使用 redisson来实现分布式锁 避免由于集群的同步、故障等问题引起分布式锁异常
参考文档: https://github.com/redisson/redisson
大佬解析隐含的坑博客:https://www.jianshu.com/p/b12e1c0b3917
maven配置
1 2 3 4 5
| <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.0</version> </dependency>
|
java实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.ming.base;
import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config;
@Slf4j public class RedissonLock {
public static final long DEFAULT_EXPIRE = 100;
public static RedissonClient getClient() { Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379"); return Redisson.create(config);
} }
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.ming;
import com.ming.base.RedissonLock; import org.junit.Test; import org.junit.runner.RunWith; import org.redisson.api.RLock; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.TimeUnit;
@RunWith(SpringRunner.class) @SpringBootTest(classes = Start.class) public class RedissonLockTest { @Test public void test() throws InterruptedException { String key = "ming"; RLock lock = RedissonLock.getClient().getLock(key); lock.lock(RedissonLock.DEFAULT_EXPIRE, TimeUnit.SECONDS);
System.out.println(System.currentTimeMillis()); }
}
|
注意
超时
超时时间 一定要根据生产运行情况 进行预估配置
个人习惯配置为: max(资源执行最大耗时)*130%
锁的超时时间:
最小值:这个资源最大操作时间
如果预估不准 建议初始设置长一点 根据生产运行情况进行合适的调整
最大值:业务允许的最大超时时间或者硬件能支撑的最大时间
一般会有操作此项业务最大超时时间 如果没有可以根据硬件的资源情况估计
可重入性
一般来说 锁肯定需要考虑重入性
但是分布式锁这种本身就比较麻烦的东西 一般建议从业务上考虑避开重入锁
当然如果无法避开 就只能在key上加上计数器来做重入性锁了
然后由于 redis本身单个命令是原子操作 但是组合使用就gg了
如果非要需要 自定义lua脚本 实现redis定制命令来配合实现可重入的锁
redis在执行lua脚本的时候 会当成一个整体执行 不存在竞态条件
或者直接使用redisson 的RedissonLock 来做
总结
redis 本身性能强劲 不考虑高可用 大多数情况下单机够用
redis 用来共享数据 做分布式锁也是很适合 性能高、单线程执行、可用lua脚本扩充功能
分布式锁 本身没有什么特殊性 就是需要一个性能好、可控制并发访问的地方存储就行
redis、zk、etcd 等等 只要能存数据并且有良好的并发访问控制的中间件都可以实现
redisson 用起来麻烦点 但是胜在用的人多 考虑的完全 适配单机、分布式各种环境
手写的话 只适合自嗨 其实并没有什么卵用