lettuce + redisson
当你看到这篇文章的时候,心里会想同一个项目为啥要用2个不同的redis客户端工具,是不是有什么大病???
jedis:客户端实例不是线程安全的,需要借助连接池来管理和使用 Jedis,还有它是阻塞的I/O。不支持异步。
lettuce:首先它是异步和非阻塞的,还支持多种特性,例如:事务、流水线操作、发布/订阅、Lua 脚本等
redisson:对字符串的支持比较差,不支持排序、事务、管道、分区等
所以综上所述,lettuce + redisson 一起使用且不是美滋滋吗?
提前准备
- 使用的springboot版本3.2.7,引入依赖
<!-- redisson ,排除了lettuce,jedis客户端 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.32.0</version>
</dependency>
<!-- redis : ,spring boot 默认使用lettuce客户端 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置lettuce
配置yml
spring:
data:
redis:
host: localhost
port: 6379
timeout: 10s
password: redis@123
database: 12
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
编写redis配置文件 RedisConfig
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
redis的工具类 RedisCache
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
}
使用
@Resource
private RedisCache redisCache;
@Test
void redisUser() {
Long userId = 1813144155037069314L;
String key = "USER_KEY:";
UserEntity redisUserEntity = redisCache.getCacheObject(key + userId);
if (ObjectUtil.isNull(redisUserEntity)) {
UserEntity userEntity = userMapper.selectOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUserId, userId));
if (ObjectUtil.isNotNull(userEntity)) {
redisCache.setCacheObject(key + userEntity.getUserId(), userEntity);
log.info("设置缓存成功");
}
}
}
到此完成一半,
配置redisson
编写RedissonConfig配置文件
@Configuration
public class RedissonConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private String port;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.database}")
private Integer database;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port)
// 更多.set
.setPassword(password)
.setDatabase(database)
;
return Redisson.create(config);
}
编写RedisLock锁工具类
@Component
public class RedisLock {
@Autowired
private RedissonClient redissonClient;
/**
* 获取锁
*
* @param lockKey 锁实例key
* @return 锁信息
*/
public RLock getRedisLock(String lockKey)
{
return redissonClient.getLock(lockKey);
}
/**
* 加锁
*
* @param lockKey 锁实例key
* @return 锁信息
*/
public RLock lock(String lockKey)
{
RLock lock = getRedisLock(lockKey);
lock.lock(); // 阻塞式等待。默认加的锁是30s时间
return lock;
}
/**
* 加锁
*
* @param lockKey 锁实例key
* @param leaseTime 上锁后自动释放锁时间
* @return true=成功;false=失败
*/
public Boolean tryLock(String lockKey, long leaseTime)
{
return tryLock(lockKey, 0, leaseTime, TimeUnit.SECONDS);
}
/**
* 加锁
*
* @param lockKey 锁实例key
* @param leaseTime 上锁后自动释放锁时间
* @param unit 时间颗粒度
* @return true=加锁成功;false=加锁失败
*/
public Boolean tryLock(String lockKey, long leaseTime, TimeUnit unit)
{
return tryLock(lockKey, 0, leaseTime, unit);
}
/**
* 加锁
*
* @param lockKey 锁实例key
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @param unit 时间颗粒度
* @return true=加锁成功;false=加锁失败
*/
public Boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
{
RLock rLock = getRedisLock(lockKey);
boolean tryLock = false;
try
{
tryLock = rLock.tryLock(waitTime, leaseTime, unit);
}
catch (InterruptedException e)
{
return false;
}
return tryLock;
}
/**
* 释放锁
*
* @param lockKey 锁实例key
*/
public void unlock(String lockKey)
{
RLock lock = getRedisLock(lockKey);
lock.unlock();
}
/**
* 释放锁
*
* @param lock 锁信息
*/
public void unlock(RLock lock)
{
lock.unlock();
}
使用
@Resource
private RedisLock redisLock;
private static final String LOCK_USER_KEY = "queryUserKey";
int n = 500;
@Override
public Result<String> queryUserLock() throws InterruptedException {
//阻塞式等待。默认加的锁是30s时间
RLock lock = redisLock.lock(LOCK_USER_KEY);
try {
log.info("线程:" + Thread.currentThread().getName() + "获得了锁");
log.info("剩余数量:{}", --n);
} finally {
// 做释放锁操作的时候判断一下是否已成功加锁 && lock.isLocked()是判断自己lock对象声明的锁是否被锁定
判断是否有锁对象,以及是否是同一个锁
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
//释放锁
lock.unlock();
log.info("线程:" + Thread.currentThread().getName() + "已经开始释放锁了!!!");
}
}
return Result.success();
}
jmeter测试结果
并发5000,3秒,都完成ok,只截部分
还有一种tryLock方法,是非阻塞的,有兴趣的朋友可以去了解一下。