电子商务网站功能页面/个人网站怎么建立
对象共享、内存优化
- 对象的引用计数
- 对象共享
- 为什么开启maxmemory和LRU淘汰策略后对象池无效?(面试题)
- 为什么Redis不共享包含字符串的对象?(面试题)
- 对象的空转时长
- 内存消耗
- 内存使用统计
- 内存消耗划分
- 对象内存
- 缓冲内存
- 内存碎片
- 子进程内存消耗
- 内存管理
- 设置内存上限
- 动态调整内存上限
- 内存回收策略(重要!!!)
- 删除过期键对象
- 内存溢出控制策略
- 内存优化
- 了解redisObjec对象
- 缩减键值对象(长度)
- 共享对象池
- 字符串优化(embstr编码)
- 编码(类型)优化(重要!!!)
- Redis为什么对一种数据结构实现多种编码方式?(面试题)
- 短(数据)结构
- 控制键的数量(选择数据类型同时也牵扯编码类型)
- hash键和field键的设计
- 分片结构(待完善)
对象的引用计数
因为C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)技术实现的内存回收机制
通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收
每个对象的引用计数信息由redisObject结构的refcount属性记录:
typedef struct redisObject {// ...// 引用计数int refcount;// ...
} robj;
对象的引用计数信息 会随着 对象的使用状态而不断变化
- 在创建一个新对象时,引用计数的值会被初始化为1
- 当对象被一个新程序使用时,它的引用计数值会被增一
- 当对象不再被一个程序使用时,它的引用计数值会被减一
- 当对象的引用计数值变为0时,对象所占用的内存会被释放
对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段
对象共享
除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用
假设键A创建了一个包含整数值100的字符串对象作为值对象
如果这时键B也要创建一个同样保存了整数值100的字符串对象作为值对象,那么服务器有以下两种做法:
- 为键B新创建一个包含整数值100的字符串对象
- 让键A和键B共享同一个字符串对象
以上两种方法很明显是第二种方法更节约内存
在Redis中,让多个键共享同一个值对象需要执行以下两个步骤:
- 将 数据库键 的 值指针 指向 一个现有的值对象
- 将 被共享的值对象 的 引用计数 增一
目前来说,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象
创建共享字符串对象的数量可以通过修改redis.h/REDIS_SHARED_INTEGERS
常量来修改
可以通过object refcount
命令查看对象引用数验证是否启用整数对象池技术
这些共享对象 不单单只有 字符串键可以使用,那些 在数据结构中 嵌套了 字符串对象的对象(linkedlist编码的列表对象、hashtable编码的哈希对象、hashtable编码的集合对象,以及zset编码的有序集合对象)都可以使用这些共享对象
对于ziplist编码的值对象,即使内部数据为整数也无法使用共享对象池,因为ziplist使用压缩且内存连续的结构,对象共享判断成本过高
当设置maxmemory
并启用LRU相关淘汰策略如:volatile-lru,allkeys-lru时,Redis禁止使用共享对象池
为什么开启maxmemory和LRU淘汰策略后对象池无效?(面试题)
LRU算法 需要获取 对象最后被访问时间,以便淘汰最长未访问数据,每个对象最后访问时间存储在redisObject对象的lru字段
对象共享意味着多个引用共享同一个redisObject,这时lru字段也会被共享,导致无法获取每个对象的最后访问时间
如果没有设置maxmemory
,直到内存被用尽Redis也不会触发内存回收,所以共享对象池可以正常工作
为什么Redis不共享包含字符串的对象?(面试题)
当服务器 考虑 将一个 共享对象 设置为 键的值对象时,程序需要先检查 给定的共享对象 和 键想创建的目标对象 是否完全相同
只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象
而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度就会越高,消耗的CPU时间也会越多
- 如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1)
- 如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N)
- 如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N 2)
因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享
对象的空转时长
除了type、encoding、ptr和refcount四个属性之外,redisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间
typedef struct redisObject {// ...unsigned lru:22;// ...
} robj;
OBJECT IDLETIME命令可以打印出给定键的空转时长,这一空转时长 就是通过 将当前时间 减去 键的值对象的lru时间计算得出的
redis> SET msg "hello world"
OK
# 等待一小段时间
redis> OBJECT IDLETIME msg
(integer) 20
# 等待一阵子
redis> OBJECT IDLETIME msg
(integer) 180
#访问msg键的值
redis> GET msg
"hello world"
# 键处于活跃状态,空转时长为0
redis> OBJECT IDLETIME msg
(integer) 0
OBJECT IDLETIME命令的实现是特殊的,这个命令在访问键的值对象时,不会修改值对象的lru属性
除了可以被OBJECT IDLETIME命令打印出来之外,键的空转时长还有另外一项作用:如果服务器打开了maxmemory
选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory
选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存
内存消耗
内存使用统计
Redis自身使用内存的统计数据可通过执行info memory
命令获取内存相关指标
需要重点关注的指标有used_memory_rss
和used_memory
以及它们的比值mem_fragmentation_ratio
- 当
mem_fragmentation_ratio
>1时,说明used_memory_rss - used_memory多出的部分内存 并没有用于 数据存储,而是被内存碎片所消耗,如果两者相差很大,说明碎片率严重 - 当
mem_fragmentation_ratio<1
时,这种情况一般出现在 操作系统 把Redis内存交换(Swap)到硬盘导致,出现这种情况时要格外关注,由于硬盘速度远远慢于内存,Redis性能会变得很差,甚至僵死
内存消耗划分
Redis进程内消耗主要包括:自身内存 + 对象内存 + 缓冲内存 + 内存碎片
其中Redis空进程自身内存消耗非常少,通常used_memory_rss
在3MB左右,used_memory
在800KB左右,一个空的Redis进程消耗内存可以忽略不计
对象内存
对象内存是Redis内存占用最大的一块,存储着用户所有的数据
Redis所有的数据都采用key-value数据类型,每次创建键值对时,至少创建两个类型对象:key对象和value对象
对象内存消耗可以简单理解为sizeof(keys)+sizeof(values)
键对象都是字符串,在使用Redis时很容易忽略键对内存消耗的影响,应当避免使用过长的键
value对象更复杂些,主要包含5种基本数据类型:字符串、列表、哈希、集合、有序集合
其他数据类型都是建立在这5种数据结构之上实现的,如:Bitmaps和HyperLogLog使用字符串实现,GEO使用有序集合实现等
每种value对象类型根据使用规模不同,占用内存不同。
使用时一定要合理预估并监控value对象占用情况,避免内存溢出
缓冲内存
缓冲内存主要包括:客户端缓冲、复制积压缓冲区、AOF缓冲区
客户端缓冲 指的是 所有接入到Redis服务器TCP连接 的 输入输出缓冲
输入缓冲无法控制,最大空间为1G,如果超过将断开连接
输出缓冲通过参数client-output-buffer-limit
控制,有以下几种客户端:
- 普通客户端:除了复制和订阅的客户端之外的所有连接,Redis的默认配置是:
client-output-buffer-limit normal 0 0 0
,Redis并没有对普通客户端的输出缓冲区做限制,一般普通客户端的内存消耗可以忽略不计,但是当有大量慢连接客户端接入时这部分内存消耗就不能忽略了,可以设置maxclients
做限制。特别是 当使用 大量数据输出的命令 且 数据无法及时推送给客户端时,如monitor命令,容易造成Redis服务器内存突然飙升 - 从客户端:主节点会为每个从节点单独建立一条连接用于命令复制,默认配置是:
client-output-buffer-limit slave 256mb 64mb 60
。当主从节点之间网络延迟较高 或 主节点挂载大量从节点时 这部分内存消耗将 占用很大一部分,建议主节点挂载的从节点不要多于2个,主从节点不要部署在较差的网络环境下,如异地跨机房环境,防止复制客户端连接缓慢造成溢出 - 订阅客户端:当使用发布订阅功能时,连接客户端 使用 单独的输出缓冲区,默认配置为:
client-output-buffer-limit pubsub 32mb 8mb 60
,当订阅服务的消息生产快于消费速度时,输出缓冲区会产生积压造成输出缓冲区空间溢出
复制积压缓冲区:Redis在2.8版本之后提供了一个 可重用的 固定大小缓冲区 用于实现 部分复制功能,根据repl-backlog-size
参数控制,默认1MB
对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区,因此可以设置较大的缓冲区空间,如100MB,这部分内存投入是有价值的,可以有效避免全量复制
AOF缓冲区:这部分空间用于 在Redis重写期间 保存 最近的写入命令,AOF缓冲区空间消耗用户无法控制,消耗的内存取决于AOF重写时间和写入命令量,这部分空间占用通常很小
内存碎片
Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc
内存分配器为了更好地管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配
内存碎片问题虽然是所有内存服务的通病,但是jemalloc针对碎片化问题专门做了优化,一般不会存在过度碎片化的问题,正常的碎片率(mem_fragmentation_ratio
)在1.03左右
但是当存储的数据长短差异较大时,以下场景容易出现高内存碎片问题:
- 频繁做更新操作,例如频繁对已存在的键执行append、setrange等更新操作
- 大量过期键删除,键对象过期删除后,释放的空间无法得到充分利用,导致碎片率上升
出现高内存碎片问题时常见的解决方式如下:
- 数据对齐:在条件允许的情况下尽量做数据对齐,比如数据尽量采用数字类型或者固定长度字符串等,但是这要视具体的业务而定,有些场景无法做到
- 安全重启:重启节点可以做到内存碎片重新整理,因此可以利用高可用架构,如Sentinel或Cluster,将碎片率过高的主节点转换为从节点,进行安全重启
子进程内存消耗
子进程内存消耗 主要指 执行AOF/RDB重写时 Redis创建的子进程 内存消耗
Redis执行fork操作 产生的子进程内存占用量 对外表现为 与父进程相同,理论上需要一倍的物理内存来完成重写操作
但Linux具有写时复制技术(copy-on-write),父子进程会共享相同的物理内存页
当 父进程 处理写请求时 会对 需要修改的页 复制出一份 副本完成写操作,而 子进程依然读取 fork时 整个父进程的内存快照
子进程内存消耗总结如下:
- Redis产生的子进程并不需要消耗1倍的父进程内存,实际消耗根据期间写入命令量决定,但是依然要预留出一些内存防止溢出
- 需要设置
sysctl vm.overcommit_memory=1
允许内核可以分配所有的物理内存,防止Redis进程执行fork时因系统剩余内存不足而失败 - 排查当前系统是否支持并开启THP,如果开启建议关闭,防止copy-on-write期间内存过度消耗
内存管理
设置内存上限
Redis使用maxmemory
参数限制最大可用内存。限制内存的目的主要有:
- 用于缓存场景,当超出内存上限
maxmemory
时使用LRU等删除策略释放空间 - 防止所用内存超过服务器物理内存
需要注意,maxmemory
限制的是 Redis实际使用的 内存量,也就是used_memory
统计项对应的内存
由于内存碎片率的存在,实际消耗的内存可能会比maxmemory
设置的更大,实际使用时要小心这部分内存溢出
通过设置内存上限 可以非常方便地实现 一台服务器部署多个Redis进程的内存控制
比如一台24GB内存的服务器,为系统预留4GB内存,预留4GB空闲内存给其他进程或Redis fork进程,留给Redis16GB内存,这样可以部署4个maxmemory=4GB的Redis进程。得益于Redis单线程架构和内存限制机制,即使没有采用虚拟化,不同的Redis进程之间也可以很好地实现CPU和内存的隔离性
动态调整内存上限
Redis的内存上限可以通过config set maxmemory
进行动态修改,即修改最大可用内存
Redis-1>config set maxmemory 6GB
Redis-2>config set maxmemory 2GB
通过动态修改maxmemory,可以实现在当前服务器下动态伸缩Redis内存的目的
Redis默认无限使用服务器内存,为防止极端情况下导致系统内存耗尽,建议所有的Redis进程都要配置maxmemory
在保证物理内存可用的情况下,系统中所有Redis实例可以调整maxmemory
参数来达到自由伸缩内存的目的
内存回收策略(重要!!!)
Redis的内存回收机制主要体现在以下两个方面:
- 删除到达过期时间的键对象
- 内存使用达到
maxmemory
上限时触发内存溢出控制策略
删除过期键对象
Redis所有的键都可以设置过期属性,内部保存在过期字典中
由于进程内保存大量的键,维护每个键精准的过期删除机制会导致消耗大量的CPU,对于单线程的Redis来说成本过高,因此Redis采用惰性删除和定时任务删除机制实现过期键的内存回收
- 惰性删除:惰性删除 用于 当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空,这种策略是出于节省CPU成本考虑,不需要单独维护TTL链表来处理过期键的删除。但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。正因为如此,Redis还提供另一种定时任务删除机制作为惰性删除的补充
- 定时任务删除:Redis内部维护一个定时任务,默认每秒运行10次(通过配置hz控制)。定时任务中 删除过期键逻辑 采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键
阅读参考:Redis的过期键删除策略(惰性删除、定时删除、 定期删除)及备份和复制对于过期键处理
内存溢出控制策略
当Redis所用内存达到maxmemory
上限时会触发相应的溢出控制策略
具体策略受maxmemory-policy
参数控制,Redis支持6种策略,如下所示:
- noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作
- volatile-lru:根据LRU算法 删除 设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略
- allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止
- allkeys-random:随机删除所有键,直到腾出足够空间为止
- volatile-random:随机删除过期键,直到腾出足够空间为止
- volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略
内存溢出控制策略可以采用config set maxmemory-policy{policy}
动态配置
Redis支持丰富的内存溢出应对策略,可以根据实际需求灵活定制,比如当设置volatile-lru策略时,保证具有过期属性的键可以根据LRU剔除,而未设置超时的键可以永久保留
还可以采用allkeys-lru策略把Redis变为纯缓存服务器使用
当Redis因为内存溢出删除键时,可以通过执行info stats
命令查看evicted_keys
指标找出当前Redis服务器已剔除的键数量
每次Redis执行命令时如果设置了maxmemory
参数,都会尝试执行回收内存操作
当Redis一直工作在**内存溢出(used_memory > maxmemory)**的状态下 且 设置非noeviction策略时,会频繁地触发回收内存的操作,影响Redis服务器的性能
频繁执行回收内存成本很高,主要包括查找可回收键和删除键的开销,如果当前Redis有从节点,回收内存操作对应的删除命令会同步到从节点,导致写放大的问题,如下所示:
建议线上Redis内存工作在maxmemory > used_memory状态下,避免频繁内存回收开销
对于需要收缩Redis内存的场景,可以通过调小maxmemory
来实现快速回收
比如对一个实际占用6GB内存的进程设置maxmemory=4GB,之后第一次执行命令时,如果使用非noeviction策略,它会一次性回收到maxmemory指定的内存量,从而达到快速回收内存的目的
注意,此操作会导致数据丢失和短暂的阻塞问题,一般在缓存场景下使用
内存优化
了解redisObjec对象
Redis存储的数据都使用redisObject来封装,包括string、hash、list、set、zset在内的所有数据类型。理解redisObject对内存优化非常有帮助
-
type字段:表示当前对象使用的数据类型,Redis主要支持5种数据类型:string、hash、list、set、zset。可以使用
type {key}
命令查看对象所属类型,type命令返回的是值对象类型,键都是string类型 -
encoding字段:表示Redis内部编码类型,encoding在Redis内部使用,代表 当前对象内部 采用哪种数据结构实现。理解Redis内部编码方式对于优化内存非常重要,同一个对象采用不同的编码实现内存占用存在明显差异
-
lru字段:记录对象最后一次被访问的时间,当配置了
maxmemory
和maxmemory-policy=volatile-lru
或者allkeys-lru
时,用于辅助LRU算法删除键数据。可以使用object idletime {key}
命令在不更新lru字段情况下查看当前键的空闲时间可以使用
scan + object idletime
命令 批量查询 哪些键 长时间未被访问,找出长时间不访问的键进行清理,可降低内存占用。 -
refcount字段:记录 当前对象 被引用的次数,用于通过引用次数回收内存,当refcount=0时,可以安全回收当前对象空间。使用
object refcount {key}
获取当前对象引用。当对象为整数且范围在[0-9999]时,Redis可以使用共享对象的方式来节省内存 -
*ptr字段:与对象的数据内容相关,如果是整数,直接存储数据;否则 表示 指向数据的指针。Redis在3.0之后 对 值对象 是 字符串且长度<=39字节的数据,内部编码为embstr类型,字符串sds和redisObject一起分配,从而只要一次内存操作即可
高并发写入场景中,在条件允许的情况下,建议字符串长度控制在39字节以内,减少创建redisObject内存分配次数,从而提高性能,阅读参考:Redis数据类型(二)-字符串对象
缩减键值对象(长度)
降低Redis内存使用最直接的方式就是缩减键和值的长度
-
key长度:如在设计键时,在完整描述业务情况下,键值越短越好
-
value长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入Redis
首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据
其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小
以Java为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如:protostuff、kryo等值对象除了存储二进制数据之外,通常还会使用通用格式存储数据比如:json、xml等作为字符串存储在Redis中
这种方式优点是方便调试和跨语言,但是同样的数据相比字节数组所需的空间更大,在内存紧张的情况下,可以使用通用压缩算法压缩json、xml后再存入Redis,从而降低内存占用,例如使用GZIP压缩后的json可降低约60%的空间(面试可能会被问到如何优化json保存redis)
共享对象池
参考上面的对象共享
字符串优化(embstr编码)
首先要了解字符串数据结构,阅读参考:
- Redis数据结构(一)-简单动态字符串(simple dynamic string,SDS)
- Redis数据类型(二)-字符串对象
因为字符串(SDS)存在预分配机制,日常开发中要小心预分配带来的内存浪费
字符串 之所以采用 预分配的方式 是防止 修改操作 需要 不断重分配内存 和 字节数据拷贝
字符串预分配每次并不都是翻倍扩容,空间预分配规则如下:
- 第一次创建len属性等于数据实际大小,free等于0,不做预分配
- 修改后如果已有free空间不够且数据小于1M,每次预分配一倍容量。如原有len=60byte,free=0,再追加60byte,预分配120byte,总占用空间:60byte+60byte+120byte+1byte
- 修改后如果已有free空间不够且数据大于1MB,每次预分配1MB数据。如原有len=30MB,free=0,当再追加100byte,预分配1MB,总占用空间:1MB+100byte+1MB+1byte
尽量减少字符串频繁修改操作如append、setrange,改为直接使用set修改字符串,降低 预分配 带来的内存浪费 和 内存碎片化
编码(类型)优化(重要!!!)
首先要了解对象编码类型,阅读参考:Redis数据类型(一)-对象(常说的数据类型)
在这里再次回顾一下,数据类型 和 编码方式 关系如下:
编码类型转换 在 Redis写入数据时 自动完成,这个转换过程是不可逆的
转换规则只能从小内存编码向大内存编码转换
Redis之所以不支持编码回退,主要是数据增删频繁时,数据向压缩编码转换非常消耗CPU,得不偿失
具体编码配置如下:
理解编码转换流程和相关配置之后,可以使用
config set
命令 设置 编码相关参数 来满足 使用压缩编码的条件
对于已经采用 非压缩编码类型的数据 如hashtable、linkedlist等,设置参数后 即使 数据 满足 压缩编码条件,Redis也不会做转换,需要重启Redis重新加载数据才能完成转换
Redis为什么对一种数据结构实现多种编码方式?(面试题)
主要原因是Redis作者 想通过 不同编码 实现 效率和空间的平衡
比如当存储只有10个元素的列表,当使用双向链表数据结构时,必然需要维护大量的内部字段如每个元素需要:前置指针,后置指针,数据指针等,造成空间浪费,如果采用连续内存结构的压缩列表(ziplist),将会节省大量内存,而由于数据长度较小,存取操作时间复杂度即使为O(n2)性能也可满足需求
短(数据)结构
在列表、散列和有序集合的长度较短或者体积较小的时候,Redis可以选择使用压缩列表(ziplist)的紧凑存储方式来存储这些结构
压缩列表是列表、散列和有序集合这3种不同类型的对象的一种非结构化(unstructured)表示
与Redis在通常情况下使用双链表表示列表、使用散列表表示散列、使用散列表加上跳跃表(skiplist)表示有序集合的做法不同
压缩列表会以序列化的方式存储数据,这些序列化数据每次被读取的时候都要进行解码,每次被写入的时候也要进行局部的重新编码,并且可能需要对内存里面的数据进行移动
控制键的数量(选择数据类型同时也牵扯编码类型)
相较于使用字符串作为key,hash结构可以降低键数量
hash的field可用于记录原始key字符串,方便哈希查找
hash的value保存原始值对象,确保不要超过hash-max-ziplist-value
限制
同样的数据使用ziplist编码的hash(数据)类型存储 比 string(数据)类型节约内存
- 节省内存量 随着 value空间(值大小)的减少越来越明显
- hash(数据类型)-ziplist(编码类型)类型 比 string类型写入耗时,但随着value空间(值大小)的减少,耗时逐渐降低
使用hash重构后节省内存量效果非常明显,特别对于存储小对象的场景,内存只有不到原来的1/5
这种内存优化技巧的关键点如下:
- hash类型节省内存的原理 是 使用ziplist编码,如果使用hashtable编码方式反而会增加内存消耗
- ziplist长度需要控制在1000以内,否则由于存取操作时间复杂度在O(n)到O(n2)之间,长列表会导致CPU消耗严重,得不偿失
- ziplist适合存储小对象,对于大对象 不但 内存优化效果不明显 还会增加 命令操作耗时
- 需要预估 键的规模,从而确定每个hash结构需要存储的元素数量
- 根据hash长度和元素大小,调整
hash-max-ziplist-entries
和hash-max-ziplist-value
参数,确保hash类型使用ziplist编码
hash键和field键的设计
- 当键离散度较高时,可以按字符串位截取,把后三位作为哈希的field,之前部分作为哈希的键
- 当键离散度较低时,可以使用哈希算法打散键,如:使用crc32(key)&10000函数把所有的键映射到“0-9999”整数范围内,哈希field存储键的原始值
- 尽量减少hash键和field的长度,如使用部分键内容
使用hash结构控制键的规模虽然可以大幅降低内存,但同样会带来问题,需要提前做好规避处理。如下所示:
- 客户端 需要预估 键的规模并设计hash分组规则,加重客户端开发成本
- hash重构后 所有的键 无法再使用超时(expire) 和 LRU淘汰机制自动删除,需要手动维护删除
- 对于大对象,如1KB以上的对象,使用hash-ziplist结构控制键数量反而得不偿失