Redis性能优化

本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别

文章目录


一、Bigkey问题

  Bigkey指的是,在Redis中,某个key对应的value

  • 字符串类型的value:单个大小大于10kb
  • 非字符串的value:集合中的元素过多,或者单个元素的key过大

  Bigkey可能会导致以下的问题:

  • 执行命令阻塞:因为Redis执行命令是单线程的,单个key对应的value过大,必然导致处理时间的增加,在处理该key时,其他的请求必须等待。
  • 网络拥塞:key的value过大,占用带宽过大,处理的请求数量也就越少。
  • 过期删除:如果大量的Bigkey同时到期,在删除时同样会面临执行命令阻塞的问题。

  造成Bigkey的原因,主要是针对业务场景,Redis存储粒度设计不当,可能包含以下的情况:

  • 将包含商品表中所有字段的对象,序列化作为String结构的value。
  • 将所有用户的信息,作为Hash结构的value存储。

  对于Bigkey的优化,可以从以下的方面入手:

  • 将key在业务的层面进行拆分,例如存储用户的信息,可以分key存储。
  • 进行存储时,仅仅存储业务上必要的字段数据。

  如果无法避免使用Bigkey,在查询时不要使用getAll将所有的数据都取出,仅仅通过get查询需要的部分。并且给key设置不同的过期时间,防止集中过期。

二、命令使用

  尽量不要使用查询所有的命令,例如hgetallsmemberskeys等。如果有需要进行遍历,推荐使用渐进式的命令,例如scan渐进式地遍历key,假设我先向Redis中存入10个key:

  使用scan命令,这里的count不是代表要扫描多少个精确的值。下一次的cursor应该是本次查询出的结果,cursor为0时代表扫描结束,语法如下:

SCAN cursor MATCH pattern COUNT count TYPE type

  后续的扫描:

  但是使用该命令可能存在查询重复的问题,需要用户在客户端自己进行防重复处理。

  同时在线上环境,一些危险操作应该禁用,例如flushall,flushdb等全量删除操作,可以使用rename机制修改这些命令。

  并且Redis的数据库功能,实际上对于并发提升没有帮助,因为默认的16个数据库都是存在同一个redis中的,用户访问0数据库的时候,对于1数据库的访问的请求实际是阻塞的。

  对于批量操作,可以使用pipeline提高效率,但是一次pipeline发送的命令数量同样不应过多,pipeline是Redis非原生的批量处理命令,相当于数据库的批量查询,省去了每次单条查询的IO操作。(十次单条查询,十次IO,将十次单条查询合并成一次批量查询,只需要一次IO),Redis处理数据的时间,远远小于每次数据发送,建立连接的时间。但是该操作不具有原子性,无法保证同一批的操作要么同时成功,要么同时失败。它允许一批次的命令,部分执行成功,部分执行失败。最后会把所有的结果都返回,包括成功和失败的。

三、连接池的设置

  对于连接的管理,Redis同样采用了和线程池类似的池化技术,其中有三个关键的参数,可以根据需要在客户端进行配置:

  • maxTotal:也称为maxActive,为整个连接池允许的最大连接数,不能超过redis的最大连接数(maxClient)10000,大于最大连接数的所有请求全部拒绝掉。客户端的个数 * maxTotal 不能大于maxClient。
  • maxIdle:连接池中允许最大空闲的连接数。
  • minIdle:连接池中确保最小空闲的连接数。

  假设我当前连接池中有50个最大的连接:

  • minIdle设置为10,表示整个连接池中最少有10个空闲的连接常驻。
  • maxidle设置为20,表示整个连接池中最多有20个空闲的连接常驻。

  高并发的场景下来了30个连接,在高并发时期过去之后,连接会释放到20(maxidle),这20个连接尽管没有使用,也会常驻在redis中。

  默认的空闲连接数,选择的是maxIdle ,如果修改配置,那连接数会释放到10(minIdle),剩下的10个连接尽管没有使用,也会常驻在redis中。

  maxidle和maxtotal设置的相同,则高并发场景下,每次拿连接都不用重新生成,连接不会频繁生成和销毁。

  Redis的连接池是懒加载的,所以在面对系统刚启动,就有大量请求访问Redis的场景下,需要进行连接池预热:可以创建一些redis的连接,然后执行一些简单的命令。需要等到所有的命令执行完成后,再将连接全部归还到连接池。

java 复制代码
List<Jedis> minIdleJedisList = new ArrayList<>(jedisPoolConfig.getMinIdle());

// 第一步:提前获取 minIdle 个 Jedis 连接,触发连接池初始化
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {
        jedis = pool.getResource();
        minIdleJedisList.add(jedis);
        jedis.ping(); // 触发连接
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    } finally {
        // 注意:此处不能立刻 close,否则连接立即归还,等于只初始化了一个连接
    }
}

// 第二步:统一归还连接到连接池
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {
        jedis = minIdleJedisList.get(i);
        if (jedis != null) {
            jedis.close(); // 归还连接
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
}

四、Redis的缓存过期策略

  Redis的缓存过期,主要可以分为:

  • 被动删除:给key设置过期时间的情况,设置了过期时间,到达过期时间后并不会立刻删除,而是需要用户再去访问一次才会删除(惰性删除)
  • 定期删除:定期寻找过期的key,直接删除,防止某个key一直没有被访问到,也就无法被删除。
  • 当前运用的内存超过限定时,触发主动清理策略
    • 设置了过期时间的key:LRU算法,淘汰最久没有使用的key。LFU算法,淘汰最近一段时间内访问最少的key,热点数据优先选用。
    • 针对所有的key:上述两种,加上一种随机删除策略。
    • 不处理,不会剔除任何数据,对于新的请求拒绝,并且只响应读取操作。

  针对热点缓存的淘汰,不应该按照时间(LRU),应该按照次数(LFU)。因为假设某个热点key,在1分钟之前被访问了100次,某个冷门的key,在1个小时之前被访问了1次,然后在10s前又被访问了一次,如果按照LRU,那么被访问了100次的热点key反而会被淘汰。

  在配置文件中可以设置内存上限触发淘汰策略的阈值 淘汰策略,默认的淘汰策略是noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息

补充:布隆过滤器

  解决缓存三大问题中的缓存穿透,最为普遍的操作是缓存空值。但是上一篇中提到,如果针对不同的不存在value的key进行恶意攻击,那么redis中会存在很多空值,对于内存的占用也是可观的。而布隆过滤器是解决缓存穿透的另一种方案,大量的不存在的数据,缓存空值在redis中,占用的空间远远大于布隆过滤器。

  开启布隆过滤器时,用户在set某个key时,除了向缓存中存,还要存到布隆过滤器中:进行多次hash运算(key的hash运算结果,对于bitmap数组的长度取模),每次运算出结果对应的位置,数组中对应下标的bit位从0改成1:

进行三次不同的Hash运算

  在get key时,执行和set key相同的hash运算。假设多次hash运算,有一次每次运算出结果对应的位置bit位为0,证明该key不存在。

  可以通过Redisson去实现布隆过滤器:

java 复制代码
public class RedissonBloomFilter {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L, 0.03);
        //将zhuge插入到布隆过滤器中
        bloomFilter.add("test1");

        //判断下面号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("test1"));//false
        System.out.println(bloomFilter.contains("test2"));//false
        System.out.println(bloomFilter.contains("test3"));//true
    }
}

总结

相关推荐
倔强的石头_7 小时前
《Kingbase护城河》——猎捕慢查询:执行计划的微观解析与索引调优实战
数据库
SelectDB9 小时前
Apache Doris Python UDF:让 SQL 直接调用 Python 生态,支撑 Agent 时代复杂业务逻辑
大数据·数据库·python
jiayou642 天前
KingbaseES 表级与列级加密完全指南
数据库·后端
用户3074596982072 天前
Redis 延时队列详解
redis
GBASE2 天前
G术时刻 |GBase 8s数据库事务并发控制之封锁技术介绍(下)
数据库
烤代码的吐司君2 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
RainCity3 天前
Java Swing 自定义组件库分享(十二)
java·笔记·后端
xiezhr3 天前
逛GitHub发现了一款免费的带AI功能的数据库管理工具
数据库·ai编程·dba
你听得到113 天前
用户说 App 卡,但说不清在哪?我把 Flutter 监控 SDK 升级成了链路观测工作台
前端·flutter·性能优化
吃糖的小孩4 天前
给 QQ AI 机器人设计“可控记忆”:会话摘要、手动长期记忆与角色卡边界
数据库