【警告】别被我看到你再这样用Redis!!!

本文列举了Redis在实际应用中容易出现问题的七个场景,并提供了相应的解决方案。从批量命令使用、过期时间管理到集群环境下的数据操作,每种情况都可能导致性能下降甚至系统故障。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

本文讲述的是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的使用在不同的场景一定要潜在的问题,以免发生重大生产事故。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农阿福

看明白的同学,别忘请阿福喝奶茶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值