当前位置: 首页 > news >正文

Redis性能优化

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

文章目录

  • 一、Bigkey问题
  • 二、命令使用
  • 三、连接池的设置
  • 四、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的连接,然后执行一些简单的命令。需要等到所有的命令执行完成后,再将连接全部归还到连接池。

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去实现布隆过滤器:

public class RedissonBloomFilter {public static void main(String[] args) {Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379");//构造RedissonRedissonClient 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"));//falseSystem.out.println(bloomFilter.contains("test2"));//falseSystem.out.println(bloomFilter.contains("test3"));//true}
}

总结

在这里插入图片描述

http://www.dtcms.com/a/267709.html

相关文章:

  • 五、Python新特性指定类型用法
  • AI大模型(六)Langchain核心模块与实战(一)
  • 使用LVM和扩展文件系统增加root分区存储容量
  • 信号和槽(4)
  • 3dmax物理材质转换标准材质,物理材质转VR材质,VR材质转标准材质3dmax物理材质转标准材质插件
  • 98.验证二叉搜索树
  • python实现简单的地图绘制与标记20250705
  • 【每天一个知识点】子空间聚类(Subspace Clustering)
  • 零基础保姆级本地化部署文心大模型4.5开源系列
  • Unity文件夹标签 —— FolderTag
  • SpinLock (TTAS) C-A-S 自旋锁实现原理
  • 人工智能学习60-Yolo
  • TCP协议概念和特性
  • cmd 的sftp传输;Conda出现环境问题: error: invalid value for --gpu-architecture (-arch)
  • Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
  • 《自然语言处理:基于预训练模型的方法》-笔记
  • Java教程——深入学习guava之并发编程
  • 基于matlab的二连杆机械臂PD控制的仿真
  • 2025年5月-植物水分吸收和运输优化算法water uptake and transport-附Matlab免费代码
  • 数字地和模拟地
  • 使用 C++/OpenCV 和 MFCC 构建双重认证智能门禁系统
  • 白皮解读——173页数据安全治理白皮书5.0【附全文阅读】
  • 灵活应对市场波动的智能运营中枢​
  • leetcode918.环形子数组的最大和
  • Spark SQL架构及高级用法
  • Java Go SDK 管理工具与最佳实践
  • 【汇编逆向系列】九、函数传参之结构体 - SHL、SHR指令,小型结构体参数和返回值
  • 《深度剖析:5G网络切片如何精准保障不同业务QoS需求》
  • PHP语法基础篇(九):正则表达式
  • 本机上搭一个HTTPS网站用什么工具?.NET self host支持吗?