【巡检问题分析与最佳实践】Redis内存高问题

往期分享

RDS MySQL

RDS MySQL 实例空间问题

RDS MySQL 内存使用问题

RDS MySQL 活跃线程数高问题

RDS MySQL 慢SQL问题

RDS MySQL 实例IO高问题

RDS MySQL 小版本升级最佳实践

RDS PostgreSQL

RDS PostgreSQL 实例IO高问题

RDS PostgreSQL 慢SQL问题

RDS PostgreSQL CPU高问题

RDS SQL Server

RDS SQL Server 磁盘IO吞吐高问题

RDS SQL Server CPU高问题

RDS SQL Server 空间使用问题

Redis

Redis 流控问题

概述

作为内存型数据库,阿里云数据库Redis的内存使用率是一个非常重要的监控指标,当实例内存不足时会导致数据库响应缓慢,甚至导致写入错误等不利影响。

云数据库Redis具有内置保护功能,用户申请规格中的内存与配置文件中的maxmemory保持一致,即该实例可以使用的内存上限。如果达到此限制,Redis将开始以写命令的错误答复(但将继续接受只读命令),或者您可以将其配置为在达到最大内存限制时采用key逐出的方式以保证Redis的正常运行。

查看内存使用

部署架构为主备模式下,只提供当前主节点的内存使用。通过监控图查看内存的总体使用率非常方便,点击控制台"性能监控"后一目了然。

【巡检问题分析与最佳实践】Redis内存高问题

部署架构为Redis集群/读写分离模式下,由于多个物理节点组成了一个逻辑实例,且可能通过多个Proxy节点做路由转发和负载均衡,通过监控图查看内存时需要注意以下几点

    • 数据节点聚合指标:在集群模式下,Used Memory显示值为“该集群下所有子Shard的内存使用总和”,这里的子Shard内存不包括备用物理节点和只读实例;在读写分离模式下,Used Memory显示值为“该读写分离实例下各个物理节点内存的平均值”,基本上与单个物理节点内存使用量相同
    • 数据节点:单个Shard节点的内存使用与主备模式相同,但需要人为选择需要查看的物理节点。
    • Proxy节点和Proxy节点聚合:由于Proxy节点只做请求转发和请求的负载均衡,不会实际存储数据,所以基本上不会存在内存瓶颈,所以不提供Proxy组件的内存使用。

【巡检问题分析与最佳实践】Redis内存高问题

内存使用详细分析

   通过阿里云控制台提供的监控图可以大概确认当前实例的内存使用情况,在大部分场景下,绝大部分的内存均为实际的数据存储需求,当观察到内存超过一定阈值后扩容即可。然而在一些特定场景下,我们可能认为Redis的内存使用不符合预期,这时就需要对Redis实例的内存使用做详细的分析了。

   Redis官方提供有info memory和memory模块做更为精细的内存分析。相比较而言,memory模块的内存详情分析更为细致,本文以memory模块的结果解读作为Redis内存使用详情分析。memory模块一共提供有5个命令,通过memory help指令可以查看各命令得使用方式和概述,我们重点讲解memory stats命令。

r-bp17so24bq3wdor5i0.redis.rds.aliyuncs.com:6379> memory help
1) "MEMORY DOCTOR                        - Outputs memory problems report"
2) "MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key"
3) "MEMORY STATS                         - Show memory usage details"
4) "MEMORY PURGE                         - Ask the allocator to release memory"
5) "MEMORY MALLOC-STATS                  - Show allocator internal stats"

memory stats

   MEMORY STATS命令返回有关服务器内存使用情况详情,主要报告了以下指标内容,解读参考注释部分

r-bp17so24bq3wdor5i0.redis.rds.aliyuncs.com:6379> memory stats
 1) "peak.allocated" //Redis进程启动以来消耗的峰值内存(以字节为单位)
 2) (integer) 79492312
 3) "total.allocated" //Redis使用其分配器分配的总字节数,即当前的总内存使用量
 4) (integer) 79307776
 5) "startup.allocated" //Redis启动时消耗的初始内存量(以字节为单位)
 6) (integer) 45582592
 7) "replication.backlog" //复制积压缓冲区的大小(以字节为单位),在主备复制断开重连时使用
 8) (integer) 33554432
 9) "clients.slaves" //主从复制中所有slave的读写缓冲区大小(以字节为单位)
10) (integer) 17266
11) "clients.normal" //除slave外所有其他客户端的读写缓冲区(以字节为单位)
12) (integer) 119102
13) "aof.buffer" //aof持久化使用的缓存和aofrewrite时产生的缓存
14) (integer) 0
15) "db.0"  //有多少个业务db,这里就会展示多少个
16) 1) "overhead.hashtable.main" //当前db的hash链表开销内存总和,可以认为是元数据内存
    2) (integer) 144
    3) "overhead.hashtable.expires" //当前db存储“设置了key的expire时间”消耗的内存
    4) (integer) 0
    5) "overhead.hashtable.inmem_keys"
    6) (integer) 80
17) "overhead.total" //数值=startup.allocated+replication.backlog+clients.slaves+clients.normal+aof.buffer+db.X
18) (integer) 79273616
19) "keys.count" //当前Redis实例的key总数
20) (integer) 2
21) "keys.bytes-per-key" //当前Redis实例每个key的平均大小,数值=(total.allocated-startup.allocated)/keys.count
22) (integer) 16862592
23) "dataset.bytes" //纯业务数据占用的内存大小
24) (integer) 34160
25) "dataset.percentage" //纯业务数据占用的内存比例,数值=dataset.bytes*100/(total.allocated-startup.allocated)
26) "0.1012892946600914"
27) "peak.percentage" //当前总内存与历史峰值的比例,数值=total.allocated*100/peak.allocated
28) "99.767860412597656"
29) "fragmentation" //内存的碎片率
30) "0.45836541056632996"

从上述的指标解读可以看出,正常情况下消耗一个Redis实例内存的绝大部分是纯业务数据,但是也有一些其他方面的内存开销,比如主备复制的积压缓冲区,Redis进程初始化消耗的内存,Redis内部维护Key-Value链表消耗的内存等等。当这些非dataset消耗的内存不多时,我们建议您直接忽略,重点分析dataset的内存使用情况是否符合预期;如果非dataset消耗的内存也占据了相当大的比例,需要根据上述指标解读一一查看内存消耗的根因。

更多memory stats的内存请参考

https://redis.io/commands/memory-stats

memory doctor

   memory docto命令可以提供当前Redis实例关于内存使用方面的建议,在不同的允许状态下会有不同的分析结果,仅供用户参考。

r-bp17so24bq3wdor5i0.redis.rds.aliyuncs.com:6379> memory doctor
Hi Sam, I can't find any memory issue in your instance. I can only account for what occurs on this base

除了上述范例以外,memory doctor还会从其他多个维度给出当前Redis实例的内存诊断建议,主要包括以下方面,您可以根据指令结果制定相应的优化策略。

    int empty = 0;          /* Instance is empty or almost empty. */
    int big_peak = 0;       /* Memory peak is much larger than used mem. */
    int high_frag = 0;      /* High fragmentation. */
    int high_alloc_frag = 0;/* High allocator fragmentation. */
    int high_proc_rss = 0;  /* High process rss overhead. */
    int high_alloc_rss = 0; /* High rss overhead. */
    int big_slave_buf = 0;  /* Slave buffers are too big. */
    int big_client_buf = 0; /* Client buffers are too big. */
    int many_scripts = 0;   /* Script cache has too many scripts. */

memory usage

   MEMORY USAGE命令展示了指定Key消耗的内存,单位为Byte,表示在Redis中存储该Key所需的用于数据和管理开销的内存分配的总和。

   对于嵌套数据类型,提供可选的SAMPLES选项,其中count是采样的嵌套值的数量。默认情况下,此选项设置为5。要对所有嵌套值进行采样,使用SAMPLES 0。

r-bp17so24bq3wdor5i0.redis.rds.aliyuncs.com:6379> memory usage hudao
(integer) 252
r-bp17so24bq3wdor5i0.redis.rds.aliyuncs.com:6379> memory usage hudao_not_exit
(nil)

更多关于memory usage的内容参考

https://redis.io/commands/memory-usage

内存数据逐出和expire策略

   前文提到,当云数据库Redis内存不足时,如果用户配置了对应的数据逐出策略,则数据会做自动淘汰以保证Redis实例能够正常运行。在阿里云数据库Redis实例的默认逐出策略是volatile-lru, 如需修改,可以登录控制台在系统参数中修改。

【巡检问题分析与最佳实践】Redis内存高问题

阿里云Redis支持的数据逐出策略

  • volatile-lru
    按照LRU算法逐出原有数据,但仅逐出设置了过期时间的数据。
  • volatile-ttl
    仅逐出设置了过期时间的数据,并且是按照TTL由小到大的顺序进行逐出。
  • allkeys-lru
    按照LRU算法逐出原有数据。
  • volatile-random
    随机逐出原有数据,但仅逐出设置了过期时间的数据。
  • allkeys-random
    随机逐出原有数据。
  • noeviction
    不逐出任何数据,当内存已满时新数据的写入会得到一个错误信息(DEL和某些其他的命令除外)。
  • volatile-lfu
    按照LFU算法逐出原有数据,只从设置了过期时间的key中选择最不常用的key进行删除。
  • allkeys-lfu
    按照LFU算法优先逐出最不常用的key。

阿里云Redis的expire策略

针对设置了expire属性的key,Redis提供有两种过期删除策略,一种为惰性删除,一种为主动删除。惰性删除即当key过期时并不删除,每次从数据库获取该key的时候再去检查是否过期,如果过期则删除,并返回NULL。主动删除指的是通过修改hz参数的值,您可以调整Redis执行定期任务的频率,从而改变Redis清除过期key、清理超时连接等操作的效率,执行过程如下:

  1. 从设置了过期时间的key的集合中随机检查20个key。
  2. 删除检查中发现的所有过期key。
  3. 如果检查结果中25%以上的key已过期,则开始新一轮任务。

如果过期key数量很多或者增加速度很快,而Redis的主动清除频率较低,过期key将占用大量的内存空间,可能会影响Redis服务的性能。适当调整hz参数的值,提高清除频率,能够很好地解决这个问题。

相关自定义参数解读

  • hz:设置 Redis 后台任务执行频率,比如清除过期键任务,设置范围为1到500,默认为10。数值越大 CPU 消耗越大,延迟越小,建议不要超过100
  • dynamic-hz:根据客户端连接数动态调整hz的值,从而实现Redis定期任务执行频率的自动调整,Redis 5.0以后版本支持
  • azyfree-lazy-eviction:是否使用lazy free的方式驱逐key,当内存达到上限,分配失败后
  • lazyfree-lazy-expire:是否使用lazy free的方式删除设置了expire且到期的key
  • lazyfree-lazy-server-del:在隐式数据删除时,是否使用lazy free

更多关于过期key的清理策略可参考

https://help.aliyun.com/document_detail/142171.html

https://help.aliyun.com/document_detail/142379.html

https://redis.io/commands/expire

Redis内存使用最佳实践

一般来说,dataset占用了Redis实例中的绝大部分内存,所以优化dataset的大小至关重要。一般有以下的优化思路

  • 不要放垃圾数据
    • 测试数据,下线业务的数据需要及时清理
  • 给key设置合理的过期时间和清理策略
    • 对具有时效性的数据设置TTL,通过Redis自身的过期key清理机制降低过期key对内存的占用
    • 建议结合惰性删除和主动删除,惰性删除容易造成大量不被访问的过期key占用内存的问题,主动删除需要额外消耗CPU资源
  • 不同业务使用不同的逻辑db
    • 不同业务使用同一个Redis实例时,存放在不同的逻辑db上有助于业务梳理,无用业务的数据清空等
    • 过期key的主动清理会依次遍历所有db,存放不同的逻辑db更有助于提高过期数据清理效率
  • 避免出现大Key
    • Key过大会导致网络传输时延比较大,需要的输出缓冲区也更大,在定时清理过程中也容易造成比较高的延迟
    • 建议通过业务拆分,数据压缩等方式避免大Key的产生
    • 通过使用阿里云Redis控制台提供的“缓存分析”功能分析大Key,步骤如下图所示
      • 【巡检问题分析与最佳实践】Redis内存高问题
  • 配置升级
    • 主备架构的云数据库Redis最大支持64G内存,集群版本最大可支持4T内存

更多关于Redis的内存使用优化参考

https://redis.io/topics/memory-optimization





上一篇:【巡检问题分析与最佳实践】MongoDB 磁盘IO高问题


下一篇:【巡检问题分析与最佳实践】MongoDB 内存高问题