FEATURED · 精选文章

Redis高性能原理:内存、单线程与I/O多路复用

发布时间 / 2026/9/17 22:46:27
来源 / 创域科博编辑部
栏目 / 资讯中心
Redis高性能原理:内存、单线程与I/O多路复用 1. 先聊两句Redis凭什么被称作“高性能缓存之王”我第一次在生产环境里认真用Redis是给一套高并发的订单系统做缓存层。那时候MySQL扛不住突发流量业务高峰期接口平均响应时间直接从40毫秒飙到800毫秒数据库连接池经常被打满。后来我们引入Redis做了热点数据缓存接口响应时间回到30毫秒左右数据库压力降了70%以上。从那以后我就觉得Redis在互联网技术栈里的地位真的是不可替代的。Redis全称是Remote Dictionary Server也就是远程字典服务开源作者是意大利人Salvatore Sanfilippo2009年首次发布。别看它名字里带“字典”实际能力远远不只是KV存储。你可以把它当缓存也可以拿它做分布式锁、消息队列、排行榜、自动补全、布隆过滤器甚至用它来跑简单的业务逻辑。这些全都建立在同一个基础能力之上单线程却能扛住每秒十万级别的读写请求。这听起来很不可思议。常规认知里多线程才能最大化利用CPU单线程不是应该很慢吗Redis偏偏用单线程做出了极高的性能这里面的门道就是本篇文章的重点Redis的高性能原理到底是什么底层有哪些设计让它在海量读写面前依然游刃有余。我尽量用通俗的比喻加实际场景把这些说清楚。不管你是刚接触Redis的新手还是已经用它写过不少业务的开发只要你想搞懂Redis为什么值得用、怎么用才高效、遇到性能瓶颈时怎么排查这篇文章都适合你。2. 高性能的核心底盘纯内存、单线程、I/O多路复用2.1 纯内存存储数据就在“内存条”里很多人在聊Redis高性能时第一反应就是“因为它把数据放在内存里”这个说法没错但只说对了表象。所有数据库性能的起点本质上都是存储介质的物理速度差异。内存随机读写延迟大约是几十纳秒到一百纳秒级别而普通SSD的随机读写延迟是几十微秒到几百微秒机械硬盘更是直接到毫秒级。这么一对比内存比磁盘快了两到三个数量级。Redis把所有数据都放在内存读写过程不需要经过磁盘I/O这是它能做到微秒级响应最根本的物质基础。MySQL之所以慢不是因为SQL引擎差而是它必须定期把数据落盘每次读取也可能涉及磁盘页的查找。Redis没有这层负担数据就在内存里直接拿着指针找就行了。但这带来了一个非常现实的问题内存是稀缺资源。32G内存的服务器Redis最多也就用32G数据还得留一部分给操作系统。所以你在设计Redis缓存时要控制key的总量、善用过期时间、设计好淘汰策略。我用Redis这么多年踩过最痛的一个坑就是缓存数据无限增长最后把服务器内存吃满操作系统直接OOM杀掉了Redis进程。后面我会专门讲内存淘汰策略。2.2 单线程是故意的而且收益很大Redis在6.0之前整个数据读写执行链路是严格单线程的。很多人第一反应是“浪费CPU”但Redis的作者给过非常清楚的解释Redis的瓶颈从来不是CPU而是网络I/O和内存速度。因为所有操作都是纯内存级别CPU计算时间极短加锁的成本反而可能超过计算本身。单线程带来了三个巨大优势没有线程切换开销。多线程程序在线程切换时需要保存和恢复上下文这个成本在频繁切换时非常可观。Redis单线程从头跑到尾完全没有这层消耗。没有竞争条件不需要加锁。多线程环境下两个线程同时修改一个共享变量很容易出问题必须通过锁、原子操作等手段来保证线程安全。Redis单线程天然串行执行命令所有操作都是原子的不需要锁机制。可预测性和简单性。调试多线程程序的并发问题非常痛苦单线程让逻辑变得简单直接也方便维护。这里要特别说明一个细节Redis的“单线程”指的是命令执行线程只有一个。但是Redis进程内部并不是只有一个线程比如持久化时用的fork子线程、异步删除大key用的后台线程、6.0之后的网络I/O多线程这些都是独立存在的。我们说的单线程专指所有接收到的读写命令在同一个主线程里顺序执行。2.3 I/O多路复用用一套机制管所有连接单线程处理命令听起来没问题但它怎么同时处理成千上万个客户端连接呢总不可能来一个连接就开一个线程那样单线程就名存实亡了。这里的关键就是I/O多路复用机制。I/O多路复用是指用一个线程同时监听多个文件描述符就是socket连接当某个连接有数据到达时内核通知应用程序去处理。Linux下的实现主要有select、poll、epoll三种Redis使用的是基于epoll的复用模型在Linux平台同时允许使用kqueue等机制适配其他操作系统。用生活场景来打比方想象你是一个餐厅服务员只服务一桌客人肯定浪费但如果你同时服务十桌就需要时刻关注每桌客人是不是在叫人。一种方式是轮询不断去每桌看有没有需求这就是select模型连接多了效率低下。另一种方式是给每桌装一个呼叫铃谁按铃你就去处理谁这就是epoll模型。Redis就是这个装呼叫铃的餐厅海量连接在等待但只要有人真正“按铃”数据可读可写内核就会通知Redis去处理。这个机制让Redis可以轻松管理上万个连接而不会因为连接数增加显著降低性能。I/O多路复用解决的是“如何高效等待事件”的问题而单线程解决了“如何处理事件不冲突”的问题两者配合起来形成了Redis高性能的网络基石。2.4 一次完整的Redis请求是怎么被处理的为了让你理解得更透彻我画一条完整链路来说明一个简单的get命令经历的旅程客户端发送TCP数据包到Redis监听的端口默认6379。内核网络栈收到数据包放入socket接收缓冲区。Redis的epoll模型监听到这个连接可读产生一个文件事件。Redis主线程从epoll中取出这个事件读取socket缓冲区的数据。Redis解析RESP协议命令得到这是一个GET操作取出对应的key。Redis在内存中的全局哈希表里查找key对应的值。Redis把结果按RESP协议格式写入socket发送缓冲区。数据包通过网络返回客户端整个流程结束。整个链路中Redis主线程的CPU耗时主要是解析协议和哈希查找这两步都是纳秒级或微秒级操作所以单线程一样能支撑高并发。从宏观上看也就是一次网络往返加一次内存查询的时间自然能做到单机十万级QPS。3. 高效数据结构Redis凭什么“快”得这么细节3.1 全局哈希表与渐进式rehashRedis的高性能不只来自网络模型存储层的数据结构设计也非常讲究。Redis把所有key-value都保存在一个全局哈希表里这是一个经典的数组加链表结构每个key经过哈希函数计算后落到数组的某个桶位冲突的key用链表串起来。哈希表的查询时间复杂度是O(1)这也是Redis读写快的一个重要原因。但哈希表在数据量逐渐增大后哈希冲突会变多链表变长查询性能下降。这时候就需要扩容扩大数组长度并重新计算每个key的位置这个过程叫rehash。Redis做rehash的方式很聪明叫渐进式rehash。它不会一次性把所有数据重新映射因为如果数据量有几百万个key一次性rehash会造成明显的停顿。相反Redis把rehash的动作拆散成多次执行扩容开始后新数组和旧数组同时存在每次处理命令时顺便迁移一小部分数据直到旧数组完全清空才释放旧空间。这样就把一个大操作分摊到了多个请求周期里保证了服务的平滑性。3.2 String、List、Hash、Set、ZSet的底层设计Redis提供了五种基础数据类型每种底层还准备了多个内部编码目的就是在不同场景下都能保持最优性能。String是Redis最常用的类型底层实现是SDSSimple Dynamic String简单动态字符串。SDS和普通C字符串相比最重要的是记录了字符串长度获取长度是O(1)操作而不需要遍历同时它有预分配和惰性释放机制减少频繁申请内存带来的系统调用开销。List在数据量小时用压缩列表ziplist存储数据量和元素大小超过阈值后转为quicklist或linkedlist。ziplist在内存上是连续的非常节省空间而且对少量元素遍历很快。quicklist由多个ziplist组成在内存占用和读写性能之间做平衡。最新版本还引入了listpack更进一步的优化。Hash在元素少时用ziplist元素多时转为哈希表加SDS的组合。这里有个常见的坑如果你用Hash存储大量小字段但字段数量超过hash-max-listpack-entries配置默认128内部就会转为真正的哈希表内存占用会显著上升性能也会有变化。生产环境要结合数据规模调整这些参数。Set的底层实现是intset整数集合或哈希表。当所有元素都是整数且数量不多时intset采用有序数组存储内存紧凑元素不再是整数或数量超过阈值就转为哈希表。ZSet是最具特色的类型底层是跳表skiplist加哈希表的组合。跳表是一种多层链表的变种每一层链表作为下一层的索引查找元素时从高层往低层跳跃前进平均查找复杂度是O(log n)。它和红黑树相比实现更简单而且区间遍历非常方便所以ZSet既能精确查找成员又能按分数范围取数据像排行榜这种场景就是它的主场。3.3 为什么跳表在ZSet里这么关键为了让你理解跳表的价值我用一个更直观的比喻。假设有一串有序的数字1、3、4、7、9、12、14、18普通链表找14需要从头遍历7次。跳表会在原始链表上面抽出一些节点组成“快速通道”比如1、4、9、14这一层找14时先在快速通道定位到14然后再下钻到原始链表只需要3次跳跃。在ZSet里每个成员除了有value还有score分数。跳表按照score排序同时每个节点带一个后退指针支持从小到大的正向范围和从大到小的反向范围便利。排行榜功能其实是天生为ZSet设计的比如我们要展示一个游戏的战力排行直接ZREVRANGE rank 0 99就能拿到前100名后端心都不用操。Redis之所以用跳表而不是红黑树除了实现简单之外还有一个重要原因跳表的区间查找能力强。红黑树做精确查找很快但你要找“分数在80到90之间的所有成员”红黑树得中序遍历跳表则可以在O(log n)定位起点后顺序遍历效率和代码复杂度都占优势。3.4 数据结构的编码转换配置Redis的很多底层编码是可以在配置文件中指定的比如hash-max-listpack-entries、set-max-intset-entries、zset-max-listpack-entries这些参数。生产环境里如果预估数据量较大默认值可能需要调整。我举个例子有一张用户购物车表用Hash存储用户和商品清单一个用户可能有三五百个商品字段。默认情况下超过128个字段Hash就从listpack转为真正的哈希表而转换之后内存占用会高不少。如果你明确知道自己的场景是“字段数多但字段值小”可以适当调大阈值让更多数据停留在listpack编码减少内存碎片和指针开销。不过要注意listpack里查找是线性扫描而不是哈希查找元素多了性能反而可能下降所以阈值也不是越大越好。你需要根据实际业务在内存占用和查询速度之间做权衡。4. 网络协议与并发模型的再次进化4.1 RESP协议极致的简洁带来极致的高效Redis客户端和服务端通信使用的协议叫RESPREdis Serialization Protocol全称是REdis Serialization Protocol。它的设计原则是极致的简洁没有像HTTP那样复杂的头部和状态字段就是简单的文本加二进制混编格式。一个普通的SET命令发送出来大概是这样的*3 $3 SET $5 mykey $7 myvalue第一行*3表示这个命令有3部分然后依次是每个部分的长度和内容。这样设计的好处是Redis解析协议时不需要复杂的字符串处理直接读长度再读内容即可解析速度非常快。这在高性能服务里很关键因为单线程模型下协议解析也算在主线程的CPU时间里协议越简单单条命令的CPU成本就越低。RESP3是RESP协议的新版本在旧版的基础上增加了很多丰富的数据类型比如Map、Set、Push等目的是让客户端能更直接地映射服务器返回的数据结构减少客户端解析逻辑。同时RESP3支持服务端主动推送比如客户端可以订阅某个key的变化这在一些实时场景里特别好用。4.2 为什么Redis 6.0要引入多线程I/O前面我强调Redis是单线程模型到了6.0版本作者引入了多线程I/O但这是有严格的边界的。开启多线程之后网络读写I/O这一层可以多线程并行但命令的执行仍在单线程里完成。为什么要这么做因为当Redis跑在万兆网卡这种网络环境下时单线程处理网络I/O会成为瓶颈。网络数据包的收发、协议字节流的编解码这些操作如果用单线程来做在千万级包量面前CPU会吃满而真正执行命令的CPU时间反而没有充分释放。通过把“读包、解析请求、写回结果”这部分工作分流到多个线程Redis主线程就能腾出更多CPU时间来处理命令整体QPS在特定网络场景下能提升几十个百分点。这里最容易被误解的是多线程I/O不等于多线程执行命令也不是所有命令都能并发执行。Redis的原子性、简单性并没有被破坏所有写操作仍然是依次执行。如果你在面试中讲“Redis 6.0支持多线程了”要赶紧补一句“是多线程处理网络I/O命令执行依然是单线程”否则容易被追问到底。4.3 原子性、管道和批量操作因为单线程执行命令Redis天然具备原子性。INCR命令之所以能安全完成“读取、加一、写回”三步正是因为这三步在单线程里不会被其他命令打断。这一点在做分布式计数器、生成唯一序列号时非常有用省掉了加锁的烦恼。同时Redis支持管道Pipeline它允许客户端把多条命令一次性发送给服务器服务器执行完统一返回结果。管道最直接的收益是减少了网络往返次数。比如你要执行1000条SET命令如果一条一条发就是1000次RTT往返时延用管道批量发送可能只需要一次或几次RTT性能差距可能是几十倍。管道还有个兄弟叫事务MULTI/EXEC区别在于管道只是“批量提交命令”但不承诺原子性事务则是“一组命令串行执行中间不会插入别人的命令”。在实际使用中如果只是批量写入且不要求原子性用管道就够了如果要求多个操作作为一个整体要么都执行要么都不执行就得用事务或Lua脚本。Redis的Lua脚本可以一次执行多条命令并且保证原子性特别适合组合型逻辑。4.4 持久化机制怎么做到“快又不爱丢数据”Redis把数据放在内存里带来高性能但内存断电就没了。所以持久化是Redis生产环境里绕不开的话题。Redis提供两种持久化方式RDB快照和AOF日志。RDB是定期的内存数据快照生成一个二进制文件加载速度快适合做备份和灾难恢复。缺点是如果两次快照之间发生了数据写入这部分数据可能在宕机时丢失。RDB的生成方式很巧妙通过fork子进程让子进程快照数据主进程继续对外提供服务这就是写时复制Copy-On-Write机制的应用。AOF是追加写日志每执行一条写命令就把命令追加到AOF文件末尾。因为顺序写入磁盘性能比随机写入高很多但仍然需要保证每次命令都fsync到磁盘否则操作系统缓冲区里的数据在断电时还是会丢。fsync频率可以在配置里调整最安全的是每次写入都fsync性能最差也可以每秒fsync一次性能和安全性折中也可以完全交给操作系统决定性能最好但丢失窗口最大。在实际线上环境我的建议是开启AOF并且设置appendfsync everysec这种配置在性能和安全性之间最平衡。同时开启RDB作为全量备份当AOF文件损坏或需要重建时就拿RDB打底。还有一个很实用的运维手段用一个从节点专门做持久化主节点完全关闭持久化把性能全部投入到业务读写上从节点则负责生成备份这样主节点既不损失性能又保障了数据安全。5. 高并发架构中的Redis缓存之外的价值5.1 缓存三兄弟穿透、击穿、雪崩高并发场景下用Redis最常见的就是做缓存。但缓存用不好反而容易酿成事故。我这些年处理过的线上问题翻来覆去就是这三个缓存穿透、缓存击穿、缓存雪崩。缓存穿透是指请求的数据在缓存里没有在数据库里也没有于是每次请求都直接打到数据库。比如一个不存在的ID攻击者反复请求数据库压力会瞬间飙升。解决办法有三个对不存在的key也设置空值缓存设置短过期时间在Redis前面加布隆过滤器把不存在的key直接过滤掉限流和参数校验把明显非法的请求提前拦掉。缓存击穿是指某个热点key突然过期导致大量请求同时去数据库查数据库被打爆。这和穿透不一样穿透是“根本没有这个数据”击穿是“有数据但缓存刚好失效了”。更常见的场景是双11这种大促前预热的商品详情到期后瞬间涌入大量并发。解决思路是热点数据尽量不设置过期时间或者把过期时间分散开不要在同一个点失效用互斥锁让单个请求去数据库重建缓存其他请求等锁。缓存雪崩是指大量key在同一时间集体过期或者Redis实例整体宕机请求全部打到数据库。这个比击穿更严重因为影响的不是一两个key而是整个系统的缓存层。解决办法包括过期时间加随机偏移量避免集体失效做高可用主从加哨兵或者Redis Cluster保证Redis不宕机数据库侧做熔断降级和限流把影响控制在一定范围。5.2 内存淘汰策略对性能的影响Redis在使用过程中如果内存满了怎么处理默认是noeviction也就是写不进去新数据直接报错。实际生产里我们通常要配置淘汰策略常见的有几种volatile-lru从已设置过期时间的key中淘汰最近最少使用的。allkeys-lru从所有key中淘汰最近最少使用的。volatile-lfu从已设置过期时间的key中淘汰使用频率最低的。allkeys-lfu从所有key中淘汰使用频率最低的。volatile-random从已设置过期时间的key中随机淘汰。allkeys-random从所有key中随机淘汰。选择策略要看业务。如果缓存的数据本身没有持久化价值allkeys-lru是最通用的如果有些key需要保留到过期自动清理就选volatile-lru确保没设置过期时间的核心数据不被淘汰。LFU和LRU的区别在于LRU看的是“多久没访问”LFU看的是“一段时间内访问频率”像那种偶尔集中访问一次的历史数据LRU可能因为一次访问就被保留很久LFU则能更准确地保留真正高热的数据。内存淘汰策略对性能的影响是全局性的一旦触发了批量淘汰主线程会花额外的时间扫描并删除key这在单线程模型下会阻塞正常的读写命令。所以内存规划要前置宁可淘汰策略设好也别等内存满了才被动应对。5.3 分布式锁的正确打开方式Redis做分布式锁是高频使用场景但很多人第一次实现时都写错过。分布式锁的核心要求是“互斥、不产生死锁、可重入、锁超时自动释放、能确保删除的是自己的锁”。最经典的SET NX EX实现SET lock_key unique_value NX EX 10这行命令的意思是只有lock_key不存在时才设置成功NX同时设置10秒自动过期EXvalue用唯一标识比如UUID。释放锁时不能用简单的DEL必须先用Lua脚本判断value是否等于自己的标识确认是本人持有锁再删除否则可能因为锁自动过期把别人的锁删掉。实际业务中锁的自动过期时间需要根据业务复杂度合理设置。如果业务执行时间可能超过锁过期时间可以用Redisson这样的客户端实现看门狗自动续期机制。在Redisson里默认锁的超时时间是30秒如果业务还没结束它会自动每10秒续期一次直到任务完成释放锁。千万不要用固定过期时间处理超长业务这个坑我吃过亏有一次我们做数据迁移任务任务跑了70多秒锁10秒就过期了另一个服务实例抢到了锁两边同时写数据最后对账对了好几天才把数据修正过来。5.4 主从复制、哨兵和集群为什么重要Redis的高性能离不开高可用底座。主从复制是最基础的方案一个主节点负责写多个从节点负责读读写分离可以显著扩大读能力。复制的过程是异步的主节点写完就返回通过传播命令到从节点所以从节点可能会比主节点落后一点点这在读一致性要求不高的场景里没问题。哨兵是负责监控和自动故障转移的进程。当主节点挂了哨兵会从从节点里选举出新的主节点实现无人工干预的故障切换。集群模式则解决了单节点容量和性能的上限问题通过哈希槽把数据分布到多个节点上每个节点处理一部分数据支持横向扩展。在容器化部署场景下用Docker Compose搭建主从加哨兵的流程很有实操价值。需要注意的一点是普通Redis主从复制基于IP和端口在Docker网络里要绑定好固定IP否则容器重启后IP变化从节点就找不到主节点了。另外Redis Cluster需要至少三个主节点才能形成完整集群生产环境的网络延时和数据迁移都要提前规划。6. 生产环境实操从部署到性能排查的亲身经历6.1 Windows下的Redis安装和可视化工具选择Windows开发和测试环境官方其实不直接支持Redis。热词里出现“redis windows 下载”的频率很高大家通常的做法是用微软的移植版本或者用WSL装Linux版Redis。WSL是目前最推荐的方式你在WSL里安装的Redis和Linux生产环境表现完全一致不会遇到Windows移植版的兼容性问题。图形化客户端方面大家问得最多的是Redis Desktop Manager和Another Redis Desktop Manager。RDM早期版本对Redis 6的ACL认证支持不好经常出现连不上的问题。Another Redis Desktop Manager是开源社区基于RDM的升级版本界面简洁支持暗黑模式连接配置也很直观我个人的日常操作基本用这个。它还有一个特色功能是可以直接查看key在Redis里的底层编码类型做性能分析时会用到。6.2 Docker Compose快速部署一个带密码的Redis实例这里分享一个我在生产环境里使用的Docker Compose配置简单但足够规范version: 3.8 services: redis: image: redis:7.0-alpine container_name: redis-app restart: always ports: - 6379:6379 command: [redis-server, --requirepass, yourpassword, --appendonly, yes] volumes: - redis-data:/data volumes: redis-data:解释一下几个关键点redis:7.0-alpine是官方精简版镜像体积小适合容器环境requirepass设置密码防止未授权访问appendonly yes开启AOF持久化数据目录挂载到命名volume里容器销毁重启数据不丢。生产环境一定要设置密码并限制访问来源我见过一些配置了公网6379端口且无密码的Redis结果是直接被挖矿程序入侵整个数据库清空加密数据全部丢失。和这台部署方式关联的还有docker安装redis主从这个热搜词。主从复制的Docker Compose部署大概是两个Redis服务从节点启动时用--replicaof指定主节点的服务名和端口。在同一Docker网络中服务名可以直接当IP用这也是容器化部署相比传统部署更方便的地方。6.3 热词里那些“Redis面试题”背后真正考的东西看过热词列表里面有一堆“redis面试题”“分布式锁”“缓存治理”相关词条。面试官问Redis本质上就是想确认你懂不懂“为什么快”和“怎么用对”。我用这些年面试和被面试的经验帮你梳理几个高频问题背后的考点。“Redis为什么快”要回答到纯内存、单线程、I/O多路复用、高效数据结构这四层缺一不可。“单线程为什么还快”考点是网络I/O和内存操作不受制于CPU而不是真的“不耗CPU”。“Redis会丢数据吗”这问你持久化机制之间的区别以及不同AOF刷盘策略的数据安全窗口。“缓存穿透、击穿、雪崩怎么解决”这是实际高并发场景的经验判断光背概念没用要能结合业务说清具体怎么避免。“Redis的分布式锁怎么做”最简单的是SET NX EX加Lua脚本删除进阶一点是Redisson看门狗再深一点是RedLock的利弊。“ZSet的底层实现”要能说“跳表哈希表跳表为了排序和区间哈希表为了精确查找”再解释跳表的时间复杂度。你会发现所有问题都能回溯到“高性能原理”这条主线上来。把这篇文章里的内容吃透Redis面试基本上没什么难点了。6.4 性能瓶颈排查从哪几个维度入手线上Redis突然变慢了怎么办这是我被问到最多的实际问题。我一般按这个顺序排查。第一看慢查询日志。Redis有slowlog配置可以记录执行时间超过阈值的命令。执行SLOWLOG GET就能看到最近哪些命令耗时高。常见元凶是大key操作比如一次性LRANGE一个几十万元素的List或者HGETALL一个大Hash单条命令阻塞主线程几百毫秒整个服务跟着卡顿。第二看内存碎片率。执行INFO memory看mem_fragmentation_ratio这个指标。碎片率如果长期大于1.5说明内存碎片严重大概率是频繁修改数据或者删除大key导致的。碎片率高会降低内存分配效率也可能拖慢整个进程。这是在线调优时容易被忽略的问题。第三检查Redis连接数和客户端行为。有时候慢不是Redis慢而是业务代码里的连接池配置不合理。默认的Jedis连接池如果maxTotal设得太小高并发下会出现大量等待获取连接的超时如果设得太大又可能占用过多系统资源。合理设置连接池大小是高性能使用Redis的前提条件之一。第四关注网络和带宽。Redis的响应很依赖网络RTT跨机房读写和应用同机房的延迟差距非常明显。开启pipeline后即使RTT再高批量命令的吞吐量也能显著提升。线上如果发现单条命令耗时正常但整体吞吐不行优先检查客户端是不是没用pipeline。7. 一些藏在操作细节里的经验技巧聊到这儿高性能的大框架已经讲得差不多了。最后再分享几个实际操作中特别容易踩坑却又经常被忽略的细节。第一个是避免大key。一个String值几十MB、一个List有几十万个元素、一个Hash有几十万字段都算大key。大key在读取、删除、迁移时都很危险删除一个大key会短暂阻塞主线程导致出现明显的请求毛刺。建议在做缓存设计时就预估单key数据量超过一定阈值就拆分存储。我线上处理过一个案例用户为了省事把整个订单列表序列化成一个JSON字符串放进一个String key里这个key有20多MB每天凌晨的清理任务一碰到它接口QPS就掉一大截后来改成Hash按天分片存储问题才消失。第二个是合理使用序列化。热词里反复出现“redis序列化”很多业务用Redis存储对象时会直接Java序列化或者JSON序列化。Java原生序列化生成的字节数组很大存取效率也差用JSON序列化虽然可读性好但字符串长度依然不短。最高效的方式是根据数据格式选择合适的数据结构而不是把对象整个塞进去。比如一个用户的基本信息只有十几个字段完全可以用Hash存储字段名做key省去了序列化反序列化的CPU和内存开销。如果确实只能存字符串优先考虑Protobuf这类高效的序列化方式性能和体积会好很多。第三个是过期时间的设置要加随机偏移。这个我在缓存雪崩那段提过这里再强调一次大批量key的过期时间如果完全相同到期的那一瞬间会同时触发大量过期删除主线程的CPU占用率会突然飙升影响所有请求。建议在基础过期时间上加上一个随机值让key“错峰过期”这个简单的习惯能避免大量线上事故。第四个是定期做数据治理。Redis作为一个内存系统数据不能只增不减。我每个季度都会扫描一遍Redis里的key找出那些长期不访问、没有设置过期时间、或者明显可以合并的key把它们清理掉。Redis提供了OBJECT IDLETIME可以查看key的空转时间配合SCAN命令就能在不阻塞服务的情况下摸清整体数据分布。这一点在“Redis缓存治理”这个热搜词底下尤其值得展开缓存不只是写入数据清理和逻辑优化才是长期职场里拉开差距的部分。Redis最迷人的地方就在于它用简单的原理支撑起极致的性能用少数几个设计决策解决了一大批实际问题。每次深入研究我都会发现它把一个复杂系统做得极其克制和优雅。你在接到一个高并发项目时思考的不只是“用Redis”而是“怎么用Redis才能又稳又快又不出事故”这种设计层面的经验才是比任何API熟练度都值钱的东西。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻