Redis 性能调优
设置键值的过期时间(惰性删除)
- 应该根据实际的业务情况,对键值设置合理的过期时间,这样 Redis 会帮你自动清除过期的键值对,以节约对内存的占用,以避免键值过多的堆积,频繁的触发内存淘汰策略。
Redis 有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除)。
- expire:命令用于将键key 的生存时间设置为ttl 秒。
- pexpire:命令用于将键key 的生存时间设置为ttl 毫秒。
- pexpireat< timestamp>:命令用于将键key 的过期时间设置为timestamp所指定的秒数时间戳。
- pexpireat < timestamp >: 命令用于将键key 的过期时间设置为timestamp所指定的毫秒数时间戳。
使用 lazy free 特性
lazy free 特性是 Redis 4.0 新增的一个非常使用的功能,它可以理解为惰性删除或延迟删除。意思是在删除的时候提供异步延时释放键值的功能,把键值释放操作放在 BIO(Background I/O) 单独的子线程处理中,以减少删除删除对 Redis 主线程的阻塞,可以有效地避免删除 big key 时带来的性能和可用性问题。
lazy free 对应了 4 种场景,默认都是关闭的:
#表示当 Redis 运行内存超过最大内存时,是否开启 lazy free 机制删除
lazyfree-lazy-eviction no
#表示设置了过期时间的键值,当过期之后是否开启 lazy free 机制删除
lazyfree-lazy-expire no
#有些指令在处理已存在的键时,会带有一个隐式的 del 键的操作,比如rename(修改key的名称) 命令,当目标键已存在,Redis 会先删除目标键,如果这些目标键是一个 big key,就会造成阻塞删除的问题,此配置表示在这种场景中是否开启 lazy free 机制删除
lazyfree-lazy-server-del no
#针对 slave(从节点) 进行全量数据同步,slave 在加载 master 的 RDB 文件前,会运行 flushall 来清理自己的数据,它表示此时是否开启 lazy free 机制删除
slave-lazy-flush no
#建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del 等配置,这样就可以有效的提高主线程的执行效率。
限制 Redis 内存大小,设置内存淘汰策略
#当达到该值时,将会执行配置的缓存淘汰策略(maxmemory的单位为字节)
maxmemory <bytes>
没有指定最大缓存,如果有新的数据添加,超过最大内存,则32位会使redis崩溃,所以一定要设置。最佳设置是物理内存的75% ,写操作比较多 60%。
在 64 位操作系统中 Redis 的内存大小是没有限制的,也就是配置项 maxmemory 是被注释掉的,这样就会导致在物理内存不足时,使用 swap 空间既交换空间,而当操作系统将 Redis 所用的内存分页移至 swap 空间时,将会阻塞 Redis 进程,导致 Redis 出现延迟,从而影响 Redis 的整体性能。因此我们需要限制 Redis 的内存大小为一个固定的值,当 Redis 的运行到达此值时会触发内存淘汰策略,内存淘汰策略在 Redis 4.0 之后有 8 种,整体分为三大类:
- FIFO:先进先出淘汰策略
- LRU:最少使用淘汰策略
- LFU:使用最少淘汰策略
FIFO
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
1.新数据插入到链表头部。
2.每当缓存命中(即缓存数据被访问),则将数据移到链表头部。
3.当链表满的时候,将链表尾部的数据丢弃。
LFU
最不经常使用策略,在一段时间内,数据被使用频次最少的,优先被淘汰。最少使用(LFU)是一种用于管理计算机内存的缓存算法。主要是记录和追踪内存块的使用次数,当缓存已满并且需要更多空间时,系统将以最低内存块使用频率清除内存.采用LFU算法的最简单方法是为每个加载到缓存的块分配一个计数器。每次引用该块时,计数器将增加一。当缓存达到容量并有一个新的内存块等待插入时,系统将搜索计数器最低的块并将其从缓存中删除(本段摘自维基百科)
LRU和LFU侧重点不同,LRU主要体现在对元素的使用时间上,而LFU主要体现在对元素的使用频次上。
LFU的缺陷是:在短期的时间内,对某些缓存的访问频次很高,这些缓存会立刻晋升为热点数据,而保证不会淘汰,这样会驻留在系统内存里面。而实际上,这部分数据只是短暂的高频率访问,之后将会长期不访问,瞬时的高频访问将会造成这部分数据的引用频率加快,而一些新加入的缓存很容易被快速删除,因为它们的引用频率很低。
Redis缓存淘汰策略
redis 内存数据集大小上升到配置值的时候,就会实行数据淘汰策略。
#支持热配置 内存淘汰策略在 Redis 4.0 之后有 8 种
maxmemory-policy voltile-lru
- noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略。
- allkeys-lru:淘汰整个键值中最久未使用的键值。
- allkeys-random:随机淘汰任意键值;。
- volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值。
- volatile-random:随机淘汰设置了过期时间的任意键值。
- volatile-ttl:优先淘汰更早过期的键值。
- volatile-lfu:淘汰所有设置了过期时间的键值中,最少使用的键值。
- allkeys-lfu:淘汰整个键值中最少使用的键值。
其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。
禁止使用长耗时的命令
Redis 绝大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,在官方文档对每命令都有时间复杂度说
明,如下图
其中 O(1) 表示可以安全使用的,而 O(N) 就应该当心了,N 表示不确定,数据越大查询的速度可能会越慢。因为 Redis 只用一个线程来做数据查询,因为Redis执行命令是单线程的,如果这些指令耗时很长,就会阻塞 Redis,造成大量延时。
- 禁止使用keys命令
- 避免一次查询所有成员,使用scan命令进行分批,游标式遍历
- 控制Hash,Set等数据结构的大小
- 在某一些场景,将并集,交集,差集放在客户端处理,以减少Redis服务器的运行压力
- 删除(del)大数据时,可以采用异步删除方式unlink,它会启动一个新的异步线程来删除数据,而不阻塞Redis继续接收处理其它请求
Redis6.0 引入了多线程
为什么 Redis 一开始选择单线程模型(单线程的好处)?
- IO多路复用
FD是一个文件描述符,意思是表示当前文件处于可读、可写还是异常状态。使用 I/O 多路复用机制同时监听多个文件描述符的可读和可写状态。理解为具有了多线程的特点。
一旦受到网络请求就会在内存中快速处理,由于绝大多数的操作都是纯内存的,所以处理的速度会非常地快。也就是说在单线程模式下,即使连接的网络处理很多,因为有IO多路复用,依然可以在高速的内存处理中得到忽略。
- 可维护性高
多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题。单线程模式下,可以方便地进行调试和测试。
- 基于内存
单线程状态下效率依然高 读请求11W+ 写请求8.7W+
多线程能够充分利用CPU的资源,但对于Redis来说,由于基于内存速度那是相当的高,能达到在一秒内处理10万个用户请求,如果一秒十万还不能满足,那我们就可以使用Redis分片的技术来交给不同的Redis服务器。这样的做法避免了在同一个 Redis 服务中引入大量的多线程操作。而且基于内存,除非是要进行AOF备份,否则基本上不会涉及任何的 I/O 操作。这些数据的读写由于只发生在内存中,所以处理速度是非常快的;用多线程模型处理全部的外部请求可能不是一个好的方案。
总结成两句话,基于内存而且使用多路复用技术,单线程速度很快,又保证了多线程的特点。因为没有必要使用多线程。
为什么 Redis 在 6.0 之后加入了多线程
因为读写网络的read/write系统调用在Redis执行期间占用了大部分CPU时间,如果把网络读写做成多线程的方式对性能会有很大提升。
Redis可以使用del命令删除一个元素,如果这个元素非常大,可能占据了几十兆或者是几百兆,那么在短时间内是不能完成的,这样一来就需要多线程的异步支持。
总结:Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率**。
使用 slowlog 优化耗时命令
使用 slowlog 功能找出最耗时的 Redis 命令进行相关的优化,以提升 Redis 的运行速度,慢查询有两个重要的配置项:
#用于设置慢查询的评定时间,也就是说超过此配置项的命令,将会被当成慢操作记录在慢查询日志中,它执行单位是微秒 (1 秒等于 1000000 微秒)
slowlog-log-slower-than:100
#用来配置慢查询日志的最大记录数
slowlog-max-len:100
避免大量数据同时失效
Redis 过期键值删除使用的是贪心策略,它每秒会进行 10 次过期扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10 ,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如果过期 key 的比例超过 25% ,重复执行此流程,如下图所示:
如果在大型系统中有大量缓存在同一时间同时过期,那么会导致 Redis 循环多次持续扫描删除过期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程会导致 Redis 的读写出现明显的卡顿,卡顿的另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的 CPU。为了避免这种卡顿现象的产生,我们需要预防大量的缓存在同一时刻一起过期,就简单的解决方案就是
在过期时间的基础上添加一个指定范围的随机数。
使用 Pipeline 批量操作数据
Pipeline (管道技术) 是客户端提供的一种批处理技术
可以批量执行一组指令,一次性返回全部结果,
可以减少频繁的请求应答
客户端使用优化
在客户端的使用上我们除了要尽量使用 Pipeline 的技术外,还需要注意要尽量使用 Redis 连接池,而不是频繁创建销毁 Redis 连接,这样就可以减少网络传输次数和减少了非必要调用指令。
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
使用分布式架构来增加读写速度
- 主从同步
使用主从同步功能我们可以把写入放到主库上执行,把读功能转移到从服务上,因此就可以在单位时间内处理更多的请求,从而提升的 Redis 整体的运行速度。
- 哨兵模式
哨兵模式是对于主从功能的升级,但当主节点奔溃之后,无需人工干预就能自动恢复 Redis 的正常使用。
- 集群
Redis Cluster 是 Redis 3.0 正式推出的,Redis 集群是通过将数据库分散存储到多个节点上来平衡各个节点的负载压力。
Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:
slot = CRC16(key) & 16383
每一个节点负责维护一部分槽以及槽所映射的键值数据。这样 Redis 就可以把读写压力从一台服务器,分散给多台服务器了,因此性能会有很大的提升。
Redis Cluster 应该是首选的实现方案,它可以把读写压力自动的分担给更多的服务器,并且拥有自动容灾的能力。
数据量不多,并发也不多 我们就用单机
数据量不是很多,并发很多(超过10万的读) 我们一般是使用主从+sentinel 高可用 写是主库,读是从库 每增加10万并发读 加一个从机
数据量大, 我们都是采用cluster 多读写。10个 4G内存 肯定比一个40G 便宜。
cluster 把sentinel的功能分摊到主从节点了 监控:master ,主节点是否fail 是主节点选择 选择新的主节点 是从节点决定。
cluster 的从机 默认是不读不写,就是备份和容灾
缓存常见问题
缓存预热
- 直接写个缓存刷新页面,上线前手工操作一下
- 数据量不大的时候,可以在项目启动的时候自动加载
- 定时刷新缓存
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB) 带来很大压力。
解决方案
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
setRedis(Key,value,time + Math.random() * 10000) 代码
- 做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热 点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对 某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存
过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数 据库。
if(redis.sexnx()==1){ //先查询缓存 //查询数据库 //加入缓存 }
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如 DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。 也就是说,对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】
解决方案
- 在服务器端,接收参数时业务接口中过滤不合法的值,null,负值,和空值进行检测和空值。
- bloom filter:类似于哈希表的一种算法,用所有可能的查询条件生成一个bitmap,在进行数据库 查询之前会使用这个bitmap进行过滤,如果不在其中则直接过滤,从而减轻数据库层面的压力。 采用的是一票否决 只要有一个认为你不存在 就认为你是不存在的
- 空值缓存:一种比较简单的解决办法,在第一次查询完不存在的数据后,将该key与对应的空值也放入 缓存中,只不过设定为较短的失效时间,例如几分钟,这样则可以应对短时间的大量的该key攻击,设置 为较短的失效时间是因为该值可能业务无关,存在意义不大,且该次的查询也未必是攻击者发起,无过久 存储的必要,故可以早点失效
缓存降级
当访问量出现剧增、服务出现问题(相应时间慢或者不响应) 或非核心业务影响到核心流程的性能,还需要保证服务的可用性,即便有损服务。
双11的时候 一般会对 退款 降级。
方式:
系统根据一些关键数据进行降级
配置开关实现人工降级
有些服务时无法降级(加入购物车,结算)
参考日志级别: 一般:ex有些服务偶尔网络抖动或者服务正在上线超时,可以自定降级
警告:有些服务在一端时间内有波动(95%-100%),可以自定降级或人工降级,还有发送
告警
错误:可利用率低于90%,redis连接池被打爆了,数据库连接池被打爆,或者访问量突然猛
增到系统能承受的最大阈值,这时候根据情况自动降级或人工降级
严重错误:比如因为特殊原因数据错误了,需要紧急人工降级。
redis服务出问题了, 不去查数据库,而是直接返回一个默认值(自定义一些随机值)
缓存更新
自定义的缓存淘汰策略:
- 定期去清理过期的缓存
- 当有用户请求过来时,先判断这个请求用到的缓存是否过期,过期的话就去底层系统得到新数据进行缓存更新
双写一致性
先写DB在写缓存(常规业务场景)
先写DB再删缓存,读的时候再存缓存(高并发场景)在高并发场景下要求双写一致性可使用双重检查锁
Redis 5.0 新特性 12个
- 新增加的Stream(流)数据类型
- 新的Redis模块api : Times and Cluster api,是一个抽象的集群消息总线,用于方便开发分布式系统。
- RDB(redis datebase)现在用于存储 LRU(最近最少使用淘汰算法) 和 LFU(最近不经常使用淘汰算法)元数据信息。
- 集群管理器从ruby(redis-trib.rb)移植到c代码。以前创建集群时候需要通过ruby脚本来创建,现在用c代码重新编写,不用在额外按装ruby了。
- 新增加有序集合的sorted set4个命令:
- ZPOPMAX
作用:删除返回集合中分值最高的元素
用法:ZPOPMAX key [count]
- ZPOPMIN
作用:删除返回集合中分值最小的元素
用法:ZPOPMIN key [count]
- BZPOPMAX
作用:ZPOPMAX的阻塞版
用法:BZPOPMAX key [key ...] timeout
- BZPOPMIN
作用:ZPOPMIN的阻塞版
用法:BZPOPMIN key [key ...] timeout
- 主动内存碎片整理功能version2版本,依赖于Jemalloc内存分配器。
- 增强HyperLogLog实现,这个功能是估算集合基数,redis5优化这个算法来节省空间
- 更好的内存统计报告(碎片整理和内存报告)。
当我们在redis里存一个key时候,redis会给这个key分配一个存储空间,但是当把这个key删了,
redis是不会立即回收这个已经删除key所占用的空间。因此如果反复增加删除key的话,会产生很
多内存碎片。这就会影响之后申请大块连续的内存空间,所以进行内存碎片整理很有必要。
在redis4时候已经有自动整理内存碎片的功能了,不过那时候功能还属于实验阶段。
redis5是在redis4的基础上将内存碎片自动清理功能进行了完善,现在该功能已经成熟。
那么这个功能有如下作用:
1.在redis运行期间自动进行内存碎片清理,可以实时释放内存空间。
2.通过内存报告来了解整个系统的内存使用情况。
在redis配置文件中查看内存碎片控制相关参数
参数说明:
1) activedefrag:内存碎片功能启动配置项,当为yes就表示开启该功能。
2)active-defrag-ignore-bytes:当内存浪费小于100M就忽略,大于100M就启动内存碎片整理,这个值可以设置的。
3)active-defrag-threshold-lower:当内存浪费小于10%就暂时忽略,大于10%就启动内存碎片整理,这个值可以设置的。
9.许多带有子命令的命令现在都有一个help子命令。
10.客户端断开和连接时候性能更好。
11.错误修复和改进。
12.Jemalloc升级到5.1版本。
Redis 6.0 新特性
2020年5月2号 形成了稳定版本发布
- 多线程IO
Redis 6引入多线程IO,但多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线
程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等
的并发问题。
- 重新设计了客户端缓存功能
实现了Client-side-caching(客户端缓存)功能。放弃了caching slot,而只使用key names。
Redis server-assisted client side caching
- RESP3协议
RESP(Redis Serialization Protocol)是 Redis 服务端与客户端之间通信的协议。Redis 5 使用的是
RESP2,而 Redis 6 开始在兼容 RESP2 的基础上,开始支持 RESP3。
推出RESP3的目的:一是因为希望能为客户端提供更多的语义化响应,以开发使用旧协议难以实现的功
能;另一个原因是实现 Client-side-caching(客户端缓存)功能。
RESP3
- 支持SSL
- ACL权限控制
- 支持对客户端的权限控制,实现对不同的key授予不同的操作权限。
- 有一个新的ACL日志命令,允许查看所有违反ACL的客户机、访问不应该访问的命令、访问不应该
访问的密钥,或者验证尝试失败。这对于调试ACL问题非常有用。
- 提升了RDB日志加载速度
根据文件的实际组成(较大或较小的值),可以预期20/30%的改进。当有很多客户机连接时,信息也
更快了,这是一个老问题,现在终于解决了。
- 提供了众多的新模块(modules)API
- 发布官方的Redis集群代理模块 Redis Cluster proxy**
在 Redis 集群中,客户端会非常分散,现在为此引入了一个集群代理,可以为客户端抽象 Redis 群集,
使其像正在与单个实例进行对话一样。同时在简单且客户端仅使用简单命令和功能时执行多路复用。
Redis 常见面试问题
- Redis 有哪些数据类型. 8-9
- 能说一下他们的特性,还有分别的使用场景么?day 03
- 单机会有瓶颈,怎么解决这个瓶颈?集群
- 哪他们之间是如何进行数据交互的?Redis是如何进行持久化的?Redis数据都在内存中,一断电
或重启不就没有了吗?主从复制-RDB 持久化方式 :RDB AOF 4.0后增加了混合持久化。 做了
持久化机制
- 哪你是如何选择持久方式的?4.0之前 只是做缓存 RDB 其他是AOF和RDB都打开,不建议只使
用AOF 4.0之后需要使用 就开启混合持久化 。混合持久化是对AOF的一个补充,主要是重写不一
样
- Redis还有其他保证集群高可用的方式吗?主从+哨兵
- 数据传输的时候网络断了或者服务器断了,怎么办?增量同步 全量同步
- 能说一下Redis的内存淘汰机制么? LRU LFU day 04
- 如果的如果,定期没删,我也没查询,那可咋整 ?设置最大内存,还要设置缓存淘汰策略
- 哨兵机制的原理是什么? 监控-3个任务 判断下线-SDOWN和ODWON 选举 leader 自动故障迁移 day02
- 哨兵组件的主要功能是什么?监控、通知(告警)、自动故障迁移
- Redis的事务原理是什么?内存队列,批量处理,具有隔离性
- Redis事务为什么是“弱”事务?days03 1.不是原子操作 2.不支持回滚
- Redis为什么没有回滚操作?days03 1.大多数错误都是代码 和数据的问题 ,可以预判断 2.性能考虑
- 在Redis中整合lua有几种方式,你认为哪种更好,为什么?写lua文件 redis.call()
- lua如何解决Redis并发问题?原子性(隔离性)
- 介绍Redis下的分布式锁实现原理、优势劣势和使用场景? days03
- Redis-Cluster和Redis主从+哨兵的区别,你认为哪个更好,为什么。看业务如果我们读多写少 并发量大 数据只有2G 100W读 主从+哨兵如果并发写多 数据量大 40G数据,10W并发 Rediscluster
- 什么情况下会造成缓存穿透,如何解决?缓存没数据,所有操作访问数据库。 服务器过滤 缓存降级 布隆过滤器 days04
- 什么情况下会造成缓存雪崩,如何解决?大量key同时失效,又有大量并发访问。 主要两种:一种
是过期时间+随机值 多级缓存,CDN和Nginx
- 什么是缓存击穿,如何解决 热点key失效。持久化 二级缓存 days04
- 什么情况下会造成数据库与Redis缓存数据不一致,如何解决? 只要双写就会不一致。
一种最终一致性: 延迟删除 mysql binlog MQ 异步更新KV
还有一种强一致性:KV-DB 必须保持一致 读请求和写请求串行化,传到内存队列。 比正常情况
服务器多使用5.6-5.7倍
- 那你了解的最经典的KV,DB读写模式什么样
CAP模式
读的时候 先读缓存,缓存没有的话 就读数据库,然后把取出的数据放入到缓存 ,同时返回响应
更新的时候,先更新数据库,然后再删除缓存
- 为什么是删除缓存,而不是更新缓存?
用到缓存的时候再去算缓存
Lazy 思想
- Redis的线程模型你了解吗?
文件处理器 由于文件处理器是单线程的 所以我们说redis是单线程。Redis的核心组件:
Socket
多路复用程序
文件命令分配(分派)器
文件处理器(命令接受器,命令处理器,命令响应处理器)Redis调优机制