前言:
本文讲述的是Redis使用过程中,往往容易出问题的场景案例,这里的🌟星级一共10个,星级越多,危险系数越大!!!
问题描述
(1)批量命令代替单个命令 (危险系数:🌟)
Set<String> noCacheSkuCodeSet = new HashSet<>();
for (String skuCode : skuCodeList) {
String key = KEY + skuCode;
// 每次使⽤单个Key获取单个缓存
String value = redisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(value)) {
Sku sku = JSON.parseObject(value, Sku.class);
skuList.add(sku);
} else {
noCacheSkuCodeSet.add(skuCode);
}
}
引发问题:每次单个获取缓存都是⼀次和Redis的IO消耗,当需要遍历的数量多了,会导致该请求响应⼗分缓慢
。
解决⽅法:使⽤MGET进⾏批量获取缓存
使⽤了批量命令代替单个命令后,可以减少客户端、服务端来回⽹络的IO次数,提升接⼝性能。
List<String> keys = new ArrayList();
for (String skuCode : skuCodeList) {
keys.add(KEY + skuCode);
}
// 使⽤MGet批量获取缓存
List<String> cacheValues = redisTemplate.opsForValue().multiGet(keys);
// 下面修改为for i循环即可,下标是一一对应的...
(2)过期时间丢失 (危险系数:🌟🌟)
测试代码中第一次设置key和设置5分钟的有效时间,第二次再对这个key修改值但是不设置过期时间
引发问题:会导致该key对缓存的过期时间丢失
,变为永不过期
String key = "test-key";
String value = "test-value";
// 设置缓存key和过期时间为5分钟后过期
redisTemplate.opsForValue().set(key, value, 300, TimeUnit.SECONDS);
// 查询缓存的过期时间
Long time = redisTemplate.opsForValue().getOperations().getExpire(key, TimeUnit.SECONDS);
// 第二次修改key的值但是不设置过期时间
String value2 = "test-value2";
// 设置缓存
redisTemplate.opsForValue().set(key, value2);
// 查询缓存的过期时间
Long time2 = redisTemplate.opsForValue().getOperations() .getExpire(key, TimeUnit.SECONDS);
解决⽅法:先获取其过期时间,再进⾏设置
// 查询缓存过期时间
Long time = redisTemplate.opsForValue().getOperations().getExpire(key, TimeUnit.SECONDS);
// 设置缓存,并设置过期时间
redisTemplate.opsForValue().set(key, value2, 300, TimeUnit.SECONDS);
(3)Redis 默认序列化 (危险系数:🌟🌟🌟🌟)
redis设置User对象存缓存,使用默认序列化方式会带class相关的信息(包路径等)
@Test
public void test() {
User user = new User();
user.setName("李白");
user.setAge(18);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
static class User implements Serializable {
private String name;
private Integer age;
}
引发问题:当取缓存信息时反序列化的类,和设置缓存是的类不⼀致时,会有类转换异常告警信息
解决⽅法:使⽤JSON序列化存储
// 使用json格式序列化对象
String userJsonStr = JSON.toJSONString(user);
redisTemplate.opsForValue().set("user", userJsonStr);
(4)Redission 线程并发时遇到的异常 (危险系数:🌟🌟🌟🌟)
当我们在使⽤Redission的lock.lock()⽅法时,且存在线程并发的情况时,可能会出现,线程一获取锁, 但没有释放锁;此时线程二解锁的时候,系统就会报出以下异常: java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id:
public void subProductStock() {
String key = "stock_lock";
RLock rlock = redission.getLock(key);
try {
rlock.lock();
//业务逻辑...
} catch (Exception e) {
e.printStackTrace();
} finally {
rlock.unlock();
}
}
解决⽅法:进⾏解锁的时候,判断该锁是否当前线程持有
因为redission有看门口狗机制,设置锁但是没有设置超时时间,会一直续期超时时间,但是获取锁的实例停机会自动释放锁
finally {
if (rlock.isHeldByCurrentThread()) {
rlock.unlock(); // 释放锁 }
}
(5)Redis集群环境中,执⾏Lua脚本批量设置 (危险系数:🌟🌟🌟🌟)
127.0.0.1:6379> MSET key1 '111' key2 '222' key3 '333'
(error) CROSSSLOT Keys in request don't hash to the same slot
引发问题:当执⾏批量设置缓存的时候,出现“请求的键没有落⼊同⼀个槽中”。
redis集群环境中,若总槽位数为16383,redis实例数为3,那么每一台机器负责的槽位就是(16383 / 3)
,设置值的时候是根据CRC16散列算法算出key再与总槽数取模就是所在槽位。
slot = CRC16(key) & 16383
解决⽅法:使用Hash Tag 落到同一slot
Hash Tag可以决定使⽤指定的Key计算hash值,使得此次批量操作的Key都可以落⼊同⼀个slot中。 上面写法改为:(Hash Tag 过多设置key到同一个节点可能会导致数据倾斜影响系统的吞吐量,小心使用)
127.0.0.1:6379> MSET {key1} '111' {key2} '222' {key3} '333'
(6)ZREM 批量删除缓存 (危险系数:🌟🌟🌟🌟🌟🌟🌟🌟)
Redis Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
当 key 存在但不是有序集类型时,返回一个错误。
注意: 在 Redis 2.4 版本以前, ZREM 每次只能删除一个元素。
redis 127.0.0.1:6379> ZREM key member1 [member ...]
注意:只要时间复杂度不是O(1)的操作,都需要注意操作的Key的数量。
时间复杂度:O(M*log(N)) (N是有序集合中的元素个数,M是被移除的元素个数)
在⼤批量的定时任务同⼀时间执⾏时,执⾏完之后需要对该缓存进⾏删除,则使⽤了该⽅法,但由于数 据量过多,会导致此次请求执⾏时间过⻓,有阻塞Redis的⻛险
。
解决⽅法:数据分批,分成多批次进行
(7)Keys 批量查询缓存 (危险系数:🌟🌟🌟🌟🌟🌟🌟🌟🌟🌟)
Redis Keys 命令用于查找所有符合给定模式 pattern 的 key
时间复杂度:O(N)
redis 127.0.0.1:6379>set sku1 商品1
redis 127.0.0.1:6379>set sku2 商品2
redis 127.0.0.1:6379>set sku3 商品3
redis 127.0.0.1:6379>keys sku*
sku2
sku1
sku3
有些特定的缓存,由于它们的Key前缀⼀致,所以使⽤了Keys命令,将这些⼀致前缀的缓存全部查询出来,但这个命令时间复杂度O(N)中的N,是Redis服务器中所有Key的数量,导致每个命令执⾏时⻓都⼗分之⻓,Redis直接阻塞
。
引发问题:⼀旦这些请求⼤量产⽣,会导致Redis被阻塞,影响其他使⽤Redis的业务系统。
解决方法:
1、代码层⾯优化,避免使⽤模糊查询的⽅式查询缓存
2、使⽤scan进⾏迭代获取(但要注意count的参数设置,⽐如当redis中的key有100万个时,设置count 为1000,则要与redis交互1000次,所以需要取舍,因此尽量避免模糊查询缓存的操作)
总结:
redis的使用在不同的场景一定要潜在的问题,以免发生重大生产事故。