Redisson读写锁
在看Redis和mysql双写一致性时看到了使用Redisson读写锁达到强一致性。
介绍
双写一致性流程:
线程T1,T2。写锁:wLock;读锁:rLock。
T1修改数据库:先获取wLock,然后修改数据库数据,修改之后删除Redis中缓存。
T2读取缓存内容:先获取rLock,由于T1未释放wLock,获取锁失败,被阻塞。阻塞直至T1释放wLock,T2获取到rLock【此时任何线程想获取同一把写锁将被阻塞】,T2读取Redis发现没有数据,于是去数据库中读取最新数据。
使用读写锁虽然能达到一致性,但是性能较差。如果一致性要求不高,可以通过延时双删、MQ异步删除Redis数据、Canal方式实现
Canal 是阿里巴巴开源的一款基于数据库日志增量订阅&消费的解决方案,主要用于实时同步数据库变更到下游存储(如缓存、搜索引擎、数据仓库等)。它基于数据库的日志实现了增量数据订阅和消费,可以将数据库的变更操作(如插入、更新、删除)实时推送给其他数据存储或应用系统,实现数据的实时同步和消息传递。
SpringBoot项目中引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
说明:
读写锁使用Redisson
设置和获取键值使用springboot提供的RedisTemplate
流程:
使用两个线程分别执行读和写操作,调整线程优先级:
如果先执行写,写锁是排他锁,其余线程不能进行读写操作。写方法内部在设置key-value之后会等待1秒,观察这1秒内,读线程不能执行。
如果先执行读,读锁是共享锁,其余线程可读,但不能写。读方法内部在读取值之后,等待1秒,观察这1秒内,写线程不能执行。
当读,写线程都执行完毕之后,关闭Redisson客户端。(CountDownLatch)
具体代码实现
public class RedissonReadWriteLockExample {
static RedissonClient redissonClient = getRedissonClient();
static CountDownLatch countDownLatch = new CountDownLatch(2);//等待读、写线程执行完毕后,关闭redis客户端
RedisTemplate<String, String> redisTemplate = getRedisTemplate();//用于进行key的设置和读取
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");//获取redisson读写锁;
RLock readLock = readWriteLock.readLock();
RLock writeLock = readWriteLock.writeLock();
/**
* 获取redisson客户端
* @return
*/
private static RedissonClient getRedissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
/**
* 获取redisTemplate
*
* @return
*/
private static RedisTemplate<String, String> getRedisTemplate() {
//LettuceConnectionFactory使用此配置类
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
lettuceConnectionFactory.afterPropertiesSet();
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 读操作
*/
public void getKey() {
try {
readLock.lock();
String name = redisTemplate.opsForValue().get("name");
System.out.println("READ_KEY:" + name);
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readLock.unlock();
countDownLatch.countDown();
}
}
/**
* 写操作
*/
public void setKey() {
try {
writeLock.lock();
redisTemplate.opsForValue().set("name", "zhangsan");
System.out.println("SET_KEY");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
writeLock.unlock();
countDownLatch.countDown();
}
}
public static void main(String[] args) {
RedissonReadWriteLockExample example = new RedissonReadWriteLockExample();
Thread thread = new Thread(example::getKey);
Thread thread1 = new Thread(example::setKey);
/**
* 控制执行优先级,1代表最低优先级,10代表最高优先级。
* 但不一定按照优先级顺序执行,看操作系统调度
*/
thread.setPriority(10); //先读
thread1.setPriority(1); //后写
// thread.setPriority(1); //后读
// thread1.setPriority(10); //先写
thread.start();
thread1.start();
/**
* 在使用完 Redisson 客户端后,需要显式地关闭它,以确保相关的线程资源被释放
* 否则程序会一直显示在运行
*/
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
redissonClient.shutdown();
}
}