Redis系列1:深刻理解高性能Redis的本质
Redis系列2:数据持久化提高可用性
Redis系列3:高可用之主从架构
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 集群模式
追求性能极致:Redis6.0的多线程模型
追求性能极致:客户端缓存带来的革命
Redis系列8:Bitmap实现亿万级数据计算
Redis系列9:Geo 类型赋能亿级地图位置计算
Redis系列10:HyperLogLog实现海量数据基数统计
Redis系列11:内存淘汰策略
Redis系列12:Redis 的事务机制
Redis系列13:分布式锁实现
Redis系列14:使用List实现消息队列
Redis系列15:使用Stream实现消息队列
Redis系列16:聊聊布隆过滤器(原理篇)
Redis系列17:聊聊布隆过滤器(实践篇)
Redis系列18:过期数据的删除策略
Redis系列19:LRU内存淘汰算法分析
Redis系列20:LFU内存淘汰算法分析
Redis系列21:缓存与数据库的数据一致性讨论
Redis系列22:Redis 的Pub/Sub能力
Redis是我们在业务开发中很重要的一个辅助,能够极大提高我们系统的运行效率,为后端的存储服务减少压力,提升用户使用体验。
但是作为一个辅助提升速度的组件,如果自己存在请求延迟的情况,那将是一个巨大的灾难,可能引起整条业务链路的雪崩。
在我以往的博客里面,也有过相应的案例,比如 《架构与思维:一次缓存雪崩的灾难复盘》。
但在实际业务场景中,可能有更加复杂的原因导致Redis访问效率变慢,下面我们详细来分析下。
在之前的章节中,我们根据官网的资料有过这样的结论:
在较高的配置基准下(比如 8C 16G +),在连接数为0~10000的时候,最高QPS可达到120000。Redis以超过60000个连接为基准,仍然能够在这些条件下维持50000个q/s,体现了超高的性能。下图中横轴是连接数,纵轴是QPS。
可见,Redis的性能和流量可抗性是极其高的,从客户端request发出到接受到response,这个处理过程时间是极短的,一般是微秒级别。
而对比Redis出现性能瓶颈的时候,就会有比较异常的表现,如达到几秒甚至几十秒,这时候我们就可以认定 Redis 性能变差了,需要去优化。
感知到 Redis的变慢了,接下来我们要做的就是验证和确认,这样才能有针对性的进行优化。
我们通过以下方法进行验证和分析。
redis-cli 提供了一个指令选项 --intrinsic-latency
,用于监测和统计某个时间段内Redis的最大延迟。这个选项可以用来评估Redis本身的性能,并且可以作为Redis的基准性能。
使用--intrinsic-latency选项需要指定时长,例如120秒。在指定的时间段内,redis-cli会记录每个秒级的最大延迟。这个最大延迟可以反映出Redis本身的性能,不受网络或其他外部因素的影响。
你可以通过以下命令使用--intrinsic-latency选项:
redis-cli --intrinsic-latency 120
执行该命令后,redis-cli会输出在120秒内的最大延迟统计信息。
需要注意的是,--intrinsic-latency选项从Redis的2.8.7版本开始支持。如果你使用的是较早的版本,可能无法使用该选项。
redis-cli --intrinsic-latency 120
Max latency so far: 5 microseconds.
Max latency so far: 14 microseconds.
Max latency so far: 33 microseconds.
Max latency so far: 52 microseconds.
Max latency so far: 70 microseconds.
Max latency so far: 130 microseconds.
Max latency so far: 272 microseconds.
Max latency so far: 879 microseconds.
Max latency so far: 1079 microseconds.
Max latency so far: 1665 microseconds.
Max latency so far: 1665 microseconds.
可以看到,当前运行的最大延迟是 1665 微秒,所以基线延迟的性能是 1665 (约 1.6 毫秒)微秒。
可以在终端上连接Redis的服务端进行测试,避免客户端测试因为网络的影响导致差异较大。
可以通过 -h host -p port
来连接到服务端。
redis-cli --latency -h `host` -p `port`
在算法设计中,最优的时间复杂度是O(1) 和 O(log N)。
一样的 ,Redis官方也尽量自身的操作指令尽量的高效,尽量节省时间复杂度。但是涉及到集合操作、全量的复杂度一般为O(N),
排查是否使用了慢指令,可以用如下几种方式:
如果只是简单判断是否使用了慢速查询,还可以使用命令 top、htop、prstat 等检查 Redis 主进程的 CPU 消耗即可
在 Redis 中,slowlog 是一个用于查询和记录执行时间较长的命令的命令。它可以帮助你找出哪些命令的执行时间超过了设定的阈值,并且将这些命令记录下来,以便后续分析和优化。
默认情况下命令执行时间超过 10ms 就会被记录到日志,这个只记录执行时间,摒弃了 io 往返或者网络延迟引起的响应慢部分。
如果你想修改慢查询的时间阈值,自定义慢查询的耗时标准,可以执行如下命令:
redis-cli CONFIG SET slowlog-log-slower-than 3330
这边为什么使用 3330 ,是因为我们采用上面基线延迟测试值的double。
slowlog 命令的基本语法如下:
slowlog get [count]
其中,count 是一个可选参数,表示要返回的 slow log 的数量。如果不指定 count,则默认返回最近的 slow log。
当执行 slowlog get 命令时,Redis 会返回最近的一些 slow log,每个 slow log 包含以下信息:
# 举例:读取最近2个慢查询
127.0.0.1:6381> SLOWLOG get 2
1) 1) (integer) 17
2) (integer) 1693641198
3) (integer) 5427
4) 1) "hgetall"
2) "all.uer_info"
1) 1) (integer) 18
2) (integer) 1693641217
3) (string) "GET"
4) (integer) 3771
第一个 HGET 命令,共 4 个字段:
根据我们之前的知识,Redis 的数据读写这个主操作是由单线程执行,如果被影响,导致阻塞,那么性能就会大大降低。
所以,如何避免主线程阻塞,是我们解决Redis慢执行的一个关键因素。以下从一个方向
互联网时代,客户端访问Redis服务端的时候很可能回到网络延迟较高,这样,客户端连接并请求的的效率就会变低。
如果是多个数据中心或者多网络分区的场景(两地三中心、异地多活),这种情况就更明显,毕竟数据在网络中传输都是视距离时延的。
通过 TCP/IP 连接或 Unix 域连接连接到 Redis,1 Gbit/s 的网络延迟大约为 200 us。
从下面这个图看到往返时间RTT(Round trip time),执行过程为:
我们上面与分析过慢指令,在算法设计中,最优的时间复杂度是O(1) 和 O(log N)。所以当我们确认了有慢查询指令。可以通过以下两种方式解决:
我们在数据持久化的篇章中,说过数据持久化的一些办法,包括RDB内存快照和AOF日志。使用的不合理,也会照成Redis的性能大打折扣。
生成 RDB 快照(参考这篇 《Redis系列2:数据持久化提高可用性》),Redis 必须 fork 后台进程,
做了fork操作之后,实际是拆分了执行出去,因为在主线程中执行,所以会导致性能降低,操作延迟。
在Redis 中可以使用操作系统的 COW(Copy On Write,多进程写时复制技术) 来快速持久化,减少内存占用。
Redis在执行 bgsave 时,涉及到内存和磁盘的分配和复制,并且从库加载 RDB 期间无法提供读写服务,为了保障高效率,
主库的数据量大小尽量控制在 2~4G 左右,超过4G,会让从库加载效率变慢,从而影响业务操作。
Linux 2.6.38 版本之后开始支持内存大页,最大可以支持2MB的内存页分配,而之前的常规页是按照4KB来分配的。
这样的话,会导致Redis本身的一些持久化操作的问题,(参考这篇 《Redis系列2:数据持久化提高可用性》)。
可以使用指令来disable Linux 内存大页:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
Swap是操作系统中的一种虚拟内存技术,用于在物理内存不足时将一部分数据从物理内存中移动到硬盘上,以释放物理内存空间,避免系统因为内存不够而导致的 oom 情况。Swap操作包括“换出”(swap out)和“换入”(swap in)两个过程。
Swap操作可以帮助操作系统在物理内存不足时继续运行程序,但是过度的swap操作可能会导致系统性能下降,因为硬盘访问速度比物理内存慢得多。因此,合理配置和管理swap空间对于系统性能优化非常重要。
通常建议将swap空间设置为物理内存的1.5到2倍,并且应该避免在swap空间中频繁进行大量的读写操作。
既然swap的操作引发是由于内存空间不够导致的操作,那看看我们的Redis里面有哪些操作会引起这类行为:
# 获取Redis 实例的进程ID(pid):
$ redis-cli info | grep process_id
process_id:12893
# 根据进程进入 /proc 文件系统目录:
cd /proc/12893
# 打开 smaps 的文件,查找所有文件中所有的 swap 操作:
# 这边可以看到,当使用内存达到810554 kb 时,swap内存达到37kb。
$ cat smaps | egrep '^(Swap|Size)'
Size: 241 kB
Swap: 0 kB
Size: 172 kB
Swap: 0 kB
Size: 810554 kB
Swap: 37 kB
如果 Swap 是 0 kb,或者小量的kb,应该都是正常的。但当出现百 M,甚至 GB 级别的 swap 大小时,内存就明显吃紧了,大概率会导致线上请求变慢。
可以用如下办法进行解决:
为了保证数据可靠性,Redis 使用 AOF 和 RDB 快照实现快速恢复和持久化。
(参考这篇 《Redis系列2:数据持久化提高可用性》)
AOF(Append-Only File)作为Redis用于缓存持久化的一种方式,当你选择 "always" 作为 AOF 的写回策略时,这意味着每一个写操作都会被立即同步到磁盘中。
虽然这种策略可以最大限度地保证数据的安全性,但是它对性能的影响也是最大的,因为每次写操作都需要等待磁盘 I/O 完成。如果你的应用可以接受一点点数据丢失的风险,或者你的应用主要是读操作,你可能会想要调整 AOF 的写回策略以提高性能。
以下是几种可能的解决方案:
注意,调整持久化策略或使用缓存都可能会增加数据丢失的风险,因此你需要根据你的应用需求和风险承受能力来选择合适的策略。
参考这篇文章《Redis系列18:过期数据的删除策略》,我们有详细的描述
Redis 有两种方式淘汰过期数据:
定时任务的发起的频率由redis.conf配置文件中的hz来进行配置
# 代表每1s 运行 10次
hz 10
Redis 默认每 1 秒运行 10 次,也就是每 100 ms 执行一次,每次随机抽取一些设置了过期时间的 key(这边注意不是检查所有设置过期时间的key,而是随机抽取部分),检查是否过期,如果发现过期了就直接删除。
该定时任务的具体流程如下:
大家看最后一步,如果一致有超过25%的过期key,就会导致 Redis 一直去删除来释放内存,而删除是阻塞的。
所以,需要避免大量 key 过期早同一时期过期,这样可能需要重复删除多次才能降低到 25% 以下。
解决方案:
可以给缓存设置过期时间时加上一个随机值时间(在 EXPIREAT 和 EXPIRE 的过期时间参数上,加上一个一定大小范围内的随机数),使得每个key的过期时间分布开来,不会集中在同一时刻失效。
随机值我们团队的做法是:n * 3/4 + n * random() 。所以,比如你原本计划对一个缓存建立的过期时间为8小时,那就是6小时 + 0~2小时的随机值。
这样保证了均匀分布在 6~8小时之间。如图:
bigkey 是指含有较大数据或含有大量成员、列表数的 Key。以下是一些实际的例子:
bigkey 带来问题如下:
优化 Redis 的 bigkey 可以从以下几个方面入手:
优化步骤如下: