上一章写了使用Redis来简单的实现的分布式锁,但是实际上官方是不推荐这种方式,官方推荐我们使用一种叫the Redlock algorithm的算法来实现,能保证更好的使用效果,并且这个框架适配了很多种语言,更加的强大,虽然会难一点,而我使用Java开发,当然是使用Redisson
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
接下来就开始使用Redisson
导入jar包,Redisson作为一个队里的框架,在使用时需要导入它的jar包,首先是在普通项目中
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.1</version>
</dependency>
配置Redisson,Redisson的配置有两种方式,一种是通过程序配置,另一种是配置文件
@Configuration
public class RedissonConfig {
/**
* 将Redisson配置到容器中
*/
@Bean(destroyMethod = "shutdown")//服务停止后销毁
public RedissonClient redisson() throws IOException {
Config config = new Config();
//集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
//单节点模式
config.useSingleServer().setAddress("redis://192.168.0.109:6379").setPassword("没密码可以不配置此项");
return Redisson.create(config);
}
}
接下来就测试关于分布式锁的使用
可重入锁
如果有A、B两个方法,A去调用B方法,A和B方法都要加锁并且加的是同一把锁,如果是可重入的,当执行A方法去调用B方法,A方法已经加锁了,B检测到A方法已经加锁了就直接用这把锁,执行B的方法,执行完后A释放锁,如果设计为不可重入锁,A和B都需要持有同一把锁,当A占有了锁,B只能等A释放锁才能执行,而A在等B执行完才释放锁,这样就造成了死锁,所以所有的锁都应该设计为可重入锁避免死锁问题
@Autowired
private RedissonClient redissonClient;
public void redsissonDemo() {
//获取一把锁,只要锁名字一样就是同一把锁
RLock lock = redissonClient.getLock("lock");
// 加锁
/*
* 方式一 默认过期时间
* 加锁方法有两个特点
* 1. 看门狗机制,在业务逻辑执行期间自动给锁续期
* 2. 默认锁30S后过期,即使业务宕机没有解锁
*/
lock.lock();//阻塞式等待
/*
* 方式二 指定过期时间
* 通过源码,此方法传递的超时时间,超时后就删除锁,不会自动续期
*/
lock.lock(10,TimeUnit.SECONDS);
try {
System.out.println("执行业务略及");
} finally {
// 解锁
lock.unlock();
}
}
读写锁
读写锁分为读锁和写锁,这两个是成对出现,例如当一个服务正在修改一个数据,而其他的服务需要读取这个数据,就必须要等待写锁释放才能读取,如果都是读就互不影响,就是说当写锁存在,读锁就只能等待。加读写锁的好处就在于读取到的数据一定是最新的
@Autowired
private RedissonClient redissonClient;
public void writeLock() {
//读写锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
//加写锁
RLock rLock = lock.writeLock();
rLock.lock();
try {
System.out.println("执行修改数据");
} finally {
rLock.unlock();
}
}
public void readLock() {
//读写锁
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
RLock rLock = lock.readLock();
rLock.lock();
try {
System.out.println("执行读取数据");
} finally {
rLock.unlock();
}
}
信号量
在我们的业务中可能有这样的场景,在某些时刻需要对某些接口进行限流,就可以使用信号量来解决,每个线程执行时,先去获取一个信号量,当信号量全部被获取后,如果还有请求,这些请求都会被阻塞等待,直到获取到信号量,当然也可以提前结束
public void getSemaphore() throws InterruptedException {
RSemaphore sem = redissonClient.getSemaphore("sem");
// sem.acquire();//获取一个信号量(阻塞式获取)
boolean b = sem.tryAcquire();//尝试获取信号量
if (b) {
System.out.println("执行业务逻辑");
}
//没获取到直接结束
}
public void releaseSemaphore() {
RSemaphore sem = redissonClient.getSemaphore("sem");
sem.release();//释放信号量
System.out.println("执行业务逻辑");
}
以上是一些我们常用的锁,当然官方提供的可不仅仅只有这几个,详细文档请参考官方文档(传送门)
Redisson底层都是使用lua脚本保证了所以它的所有锁都保证了原子性,并且Redisson基于它的看门狗机制,解决了死锁问题并且使用且来也比Redis更加简单强大,推荐在项目中使用Redisson