先给还在观望的读者说句实在话Redis这套东西只要你写后端迟早会碰上。有些项目文档里写着缓存用Redis有些面试题里问着缓存穿透怎么解决还有些老系统里缓存和数据库不一致搞得人焦头烂额。Redis入门这件事不是背几个命令就算完你得把它当成一个内存里的数据结构服务器来理解知道它擅长干什么、不擅长干什么才能在项目里用得顺手。这篇文章我从实际使用的角度把这几年积累的关键点、踩过的坑、排查过的线上问题都梳理一遍新手照着走能少花很多时间有基础的也可以翻翻常见问题那一章多少有点收获。1. Redis是什么为什么大家都在学1.1 先搞清楚它到底解决什么问题Redis全称是Remote Dictionary Server翻译过来就是远程字典服务。名字听着绕本质上你就把它理解成一个跑在内存里的键值数据库。数据以key-value的形式存储value可以是一个字符串、一个对象、一个列表甚至一个有序集合。Redis能火这么多年核心就一个词快。它把数据存在内存里读写速度能达到每秒十万次以上而传统关系型数据库比如MySQL因为要落盘还要处理事务和行锁同样一台机器上能支撑的并发量完全不在一个量级。在实际项目里MySQL扛不住的高频读请求就是Redis发挥价值的地方。很多人会把它和Memcached放在一起对比。Memcached更单纯只会存字符串数据结构支持非常有限。Redis则提供了丰富的数据类型尤其是有序集合ZSet这种直接催生了一大批基于本机的排行榜、延迟队列、优先队列实现。更关键的是Redis支持持久化重启后数据可以不丢这是Memcached做不了的。所以后来很多团队干脆把Redis从缓存位置扶正当成一个独立的高性能存储层来用。我个人给初学者画清一条界线Redis适合放访问频率极高但变更不太频繁的数据以及多台机器需要同步共享的数据。它不适合当主存储去存业务核心数据原因后面聊持久化的时候细说。记住这条界线几乎可以避开绝大多数使用误区。1.2 Redis到底快在哪里要理解Redis为什么快不能只说它用内存所以快。内存快没错但其他数据库也可以把热数据放内存比如MySQL的Buffer Pool。Redis真正快的秘密有几个层面。第一IO模型极其简单高效。Redis主流程是单线程的指处理命令的线程只有一个配合多路复用机制一个线程可以同时管理成百上千个客户端连接。因为单线程就没有线程切换的开销没有锁竞争也没有死锁的可能。加上纯内存访问一次命令处理基本就在微秒级别。第二数据结构经过高度优化。Redis内部没有直接用简单的双向链表或者C字符串而是针对每种使用场景做了定制比如字符串的SDS简单动态字符串可以安全地做追加操作而不用担心缓冲区溢出比如ZSet用了跳表让有序集合的插入和查询都可以在对数时间内完成。这些细节普通使用者可能感知不到但在大数据量下差距就是这么一点一点拉开的。第三IO多路复用。Redis用单线程同时监听多个socket当某个socket有数据可读或可写时才去处理对应的事件其他时间线程可以去干别的事情或者休眠等待。从系统调用级别来看它避免了为每个连接开一个线程的笨重做法性能自然就上来了。但这也有一个副作用需要特别注意既然Redis主流程是单线程就要求每个命令的执行时间都尽可能短。如果某个命令耗时很长比如遍历所有key它就会把后面的请求全部堵住。这一点在后面的慢查询排查章节我会展开讲。1.3 什么时候适合用Redis什么时候不该用它很多初学者容易走到两个极端。一个极端是什么都往Redis里塞觉得读写快就万事大吉另一个极端是觉得Redis只是缓存挂了也无所谓从不考虑数据一致性和备份。这两种都是要吃亏的。适合用Redis的场景我列几个最典型的高并发读缓存商品详情、热帖内容、用户基础信息这类读多写少的数据先读Redis没命中再查数据库。分布式环境下的共享数据多台应用服务器之间需要共享会话、共享计数、共享锁状态。进程内的变量没法跨机器Redis正好补上这个空。频率限制短信验证码、登录失败次数、接口限流Redis的INCR加过期时间可以很轻松地实现。排行榜/最新列表ZSet天然支持按分数排序和范围查询List支持头尾插入这些场景用关系型数据库反而要写复杂的SQL。异步队列List的BLPOP配合Lua脚本可以做一个简单的可靠消息队列虽然功能比不上专门的消息中间件但在体量不大时非常实用。不适合用Redis的场景也很明显复杂关系查询比如多表关联、条件组合查询、事务性强的写操作这些是关系型数据库的强项硬搬到Redis只会把业务逻辑搞得一团糟。数据量远超内存容量Redis的瓶颈是内存如果生产数据几百个G缓存全放不下还得自己做淘汰策略成本和复杂度都上去了。需要强事务和复杂回滚Redis的事务说白了就是批量执行一批命令中间出错不会自动回滚要求系统支持强一致性的金融核心交易场景用它当主库风险很大。2. 五种核心数据类型用对了才是真的入门2.1 String最简单的才是用得最多的Redis入门第一个要掌握的就是String类型。它是最基础的也是在实际项目里用得最多的。一个key对应一个字符串value最大能存512MB。String看似简单但真正用好的人不多。几个高频命令SET key value设置值可以带EX参数指定过期时间比如SET login:token:u1001 abc123 EX 3600。GET key取值。INCR/DECR自增/自减这个命令是原子操作非常适合做计数器。SETNX key value只有在key不存在时才设置成功这是实现分布式锁的基石。GETSET取旧值并写新值某些场景下可以避免两次请求。实际项目中我建议记住几个标准用法。第一个是做对象缓存用JSON序列化整个对象存进一个key适合读取频率特别高、字段不太变化的场景。第二个是用INCR做流量计数器比如记录用户每天访问次数key设计成visit:count:20250601:u1001配合EXPIRE设置当天过期第二天自动归零省心得很。第三个是SETNX做全局唯一值生成多个服务同时抢一个key抢到的人才有资格继续操作。这里有个细节容易被忽略Redis里所有key和value都是字符串如果你存了一个数字进去怎么保证INCR还是原子操作实际上INCR命令在Redis内部会尝试把value解析成整数再自增如果解析失败会返回错误。所以用INCR的key一定不要往里存非数字字符串否则线上会报错。2.2 Hash一个key下面挂一堆字段Hash类型解决的是对象存一个key还是存多个key的问题。存多个key的缺点很明显对象有多少个字段就要生成多少个key内存开销大、管理困难而且取整个对象要发多次请求。存成一个JSON字符串也有问题修改一个字段需要先GET再反序列化修改后整个覆盖并发下容易丢失更新。Hash用起来很直观HSET key field value设置对象里的字段。HGET key field取单个字段。HMGET key field1 field2一次取多个字段。HGETALL key取所有字段和值。HINCRBY key field increment给对象中某个数字字段自增。举个实际例子用户资料存在user:info:1001这个key下field分别是name、age、vip_level。要修改vip_level一条HSET user:info:1001 vip_level 2就完事不用拿整个JSON来回倒腾。这在频繁更新部分字段的场景下优势极其明显。不过Hash有一个天生的坑如果一个key下面的字段特别多比如几万个HGETALL一次会把所有数据拉出来网络传输和内存占用都会上去。设计的时候要想清楚一个Hash key不应该无限膨胀。如果真有这种一个大key里塞了一堆独立小对象的需求更合理的做法是把大key拆小按业务维度分拆成多个key。2.3 List排队这件事Redis也管List类型就是一个双向链表实际上Redis新版本用quicklist实现。支持从头部或者尾部插入、弹出元素所以天然适合做消息队列、时间线列表、最新动态这类场景。常用命令LPUSH/RPUSH从左边/右边推入元素。LPOP/RPOP从左边/右边弹出元素。LRANGE key start stop取范围内的元素。LLEN获取列表长度。BLPOP/BRPOP阻塞式弹出如果列表为空会一直等直到有元素进来或超时。我用List做过一个比较有意思的东西是用户最近浏览记录。用户每次浏览商品就往history:u1001这个List用LPUSH压入商品ID然后用LTRIM只保留最近50条读取的时候LRANGE取全部。这个方案不需要数据库性能也好唯一要注意的是List会保存重复元素如果你要去重得自己再拿一个Set配合着记。阻塞式弹出命令BRPOP是很多人忽略的好东西。做简单的任务队列时Worker线程可以用BRPOP task:queue 0去死等新任务0表示永不超时。相比轮询这种方式既省CPU又实现了实时性比定时扫描数据库的方案优雅很多。2.4 Set与ZSet去重和排名的利器Set类型是字符串的无序集合特点就是自动去重还支持集合运算交集、并集、差集。这个特性在做标签系统、好友关系、粉丝关注时非常方便。命令就那几个SADD、SREM、SMEMBERS、SISMEMBER、SINTER、SUNION。比如推荐你可能认识的人传统SQL要写JOIN用Redis可以先拿到你的好友集合再对你的好友的好友集合做SINTER交集结果按分数排序一套组合拳比数据库快得多。ZSet是Redis里最独特的数据结构成员是唯一的但每个成员都可以带一个double类型的分数。Redis按分数从小到大排序分数相同按成员字典序排。底层结构是跳表加哈希表所以插入、查找、按分数区间查询都非常快。ZADD key score member添加成员并设定分数。ZINCRBY key increment member给成员加分。ZRANGE key start stop按分数顺序取排名区间。ZREVRANGE key start stop按分数倒序取排名区间排行榜必备。ZSCORE key member获取某成员的分数。ZRANK key member获取成员的排名。我强烈建议新手把ZSet玩熟它的场景太广了排行榜按分数排、延迟队列把执行时间当作分数轮询时拿当前时间戳作为区间上限、计数器排名用ZINCRBY做累加更新。2.5 选型思路别一上来就堆高级特性数据类型选型有一个很重要的原则能用String解决的别用Hash能用Hash解决的就别用ZSet。我见过不少新手的代码什么数据都往ZSet里塞理由是要排序。可如果你只需要全局唯一去重一个Set就够了ZSet跳表维护的成本并不低。另一个原则是越简单的结构越不容易出错。String只需要管一个valueHash需要管理多个field的一致性List需要注意阻塞问题ZSet还要关注分数精度。在没有明确收益的情况下不要为了炫技引入复杂度。我还见过有人把用户ID拼成字符串用String存然后需求改成排行榜后又全部推到重建这种前期选型拍脑袋导致的返工实在没必要。如果真的拿不准你可以反过来想这个数据我是要精确查询、范围查询、排序查询还是只要去重标记?明确查询模式再定类型数据模型就不会跑偏。3. 环境准备与基础操作半小时跑起来3.1 安装与启动Redis安装有两个主流路径。第一个是在Linux服务器上直接编译安装适合生产环境。下载源码包解压后执行make然后make installRedis的两个主要二进制文件redis-server和redis-cli会装到/usr/local/bin下。第二个是在本地开发环境用Docker一键拉起特别适合入门阶段不想折腾系统环境的同学docker run -d --name redis-dev -p 6379:6379 redis:7.2如果是在Windows上开发我建议还是通过Docker或者WSL来跑因为Redis官方并不支持Windows。网上有一些非官方移植版本生产环境用起来风险大不建议深入。装好后启动服务端redis-server /path/to/redis.conf如果没指定配置文件Redis会用默认配置在6379端口监听。然后另开一个终端启动客户端redis-cli -h 127.0.0.1 -p 6379连接到客户端后试试最简单的命令127.0.0.1:6379 SET hello world OK 127.0.0.1:6379 GET hello world到这里你的Redis已经能用了。生产环境部署时有几个配置文件参数必须改。bind默认是127.0.0.1只允许本机访问如果你有多台服务器需要访问Redis要把bind改成内网IP或者0.0.0.0不建议除非防火墙控制得很严。requirepass设置访问密码格式是requirepass 你的密码。protected-mode建议保持yes开启它会在没有密码且只监听本地的情况下默认保护Redis防止暴露在公网被扫描攻击。提示没设密码的Redis如果暴露到公网会成为肉鸡被入侵挖矿这是真实发生过的事。我见过某公司测试环境Redis裸奔结果被刷了一堆恶意keyCPU飙升到100%最后只能清库重启。安全底线一定要守住。3.2 基础命令与常用运维指令命令不少但入门阶段真正高频的其实就十几个。我按功能归个类操作类SET / GET / DEL设置、读取、删除。EXISTS判断key是否存在。EXPIRE key seconds设置过期时间。TTL key查看剩余存活时间-1表示永久-2表示key不存在。TYPE key查看key的类型。KEYS pattern模糊匹配key列表比如KEYS user:*。需要特别提醒生产环境千万慎用KEYS命令。它内部会遍历整个键空间线上如果数据量有几十万个key执行KEYS会把单线程的Redis卡住影响所有请求。有这类需求应该用SCAN命令SCAN是游标式逐渐扫描每次返回少量数据不会卡服务。命令格式是SCAN cursor MATCH pattern COUNT count需要循环取完。批量设置和读取MSET key1 value1 key2 value2批量设置。MGET key1 key2批量读取。这两个命令能显著减少网络RTT在需要多次操作同一个key时可以考虑用Pipeline流水线把它们打包成一次网络请求发送性能提升非常明显。3.3 key命名规范与生命周期管理命名这件事看起来不起眼实际在工程里是大事。一套好的key命名规范让后续排查问题、维护数据事半功倍。我常用的规范是业务名:对象名:id[:子对象]。比如用户信息user:info:1001商品详情缓存product:detail:2001用户购物车cart:1001每日登录次数login:count:20250601:1001这样一个冒号分隔不仅好读还方便用Scan按前缀扫描也方便通过key前缀快速判断这个key属于哪个业务线。相反如果起了abc123、data1这种名字过两周你自己都忘了这key存的啥。生命周期管理一定要记住能设过期时间的一定要设。缓存类数据设置合理的TTL可以在数据变冷后自动清理内存防止Redis无限膨胀。但要注意不是所有key都适合设过期。比如排行榜数据如果设了过期排行榜就凭空消失了这是业务事故。所以设TTL之前要问自己这个key过期后业务数据能容忍丢失吗如果不能要么不设过期要么用持久化方案兜底。4. 持久化重启后数据还在吗4.1 RDB快照与AOF日志很多初学者用Redis当缓存用觉得丢了数据无所谓。但一旦用Redis存储了有业务意义的数据比如购物车、计数、分布式锁状态持久化就不得不考虑。Redis提供两种持久化机制RDB和AOF两者相辅相成。RDBRedis DataBase是内存快照。Redis会把当前全部数据以二进制格式dump到磁盘的.rdb文件里。你可以配置触发条件比如60秒内如果有1000次写操作就自动快照一次。RDB的优点是文件紧凑、恢复速度快非常适合做备份和灾难恢复缺点也很明显快照之间如果发生写入这部分数据会丢失因为RDB不是实时的。AOFAppend Only File则像MySQL的binlog把每次写操作以协议文本形式追加到日志文件末尾。AOF的特点是数据安全性高你可以配置everysec每秒刷盘一次或者always每条命令都刷盘。everysec模式下最多丢失一秒的数据对绝大多数业务来说足够安全。AOF的缺点是文件体积会越来越大需要定期做AOF重写来压缩。Redis 4.0之后有了混合持久化。开启后AOF重写时直接生成一份RDB格式的快照作为基底后续增量命令再用AOF格式追加。这样兼顾了RDB恢复快和AOF数据安全的优点是我现在建议的首选方案。4.2 我应该怎么选简单粗暴的选型建议如果Redis纯做缓存丢了能接受RDB就够甚至不持久化也行。如果Redis存了业务数据必须开AOF且配置为everysec。如果对恢复时间有要求生产环境建议开混合持久化。配置文件里对应的参数大概是appendonly yes appendfsync everysec aof-use-rdb-preamble yes save 900 1 save 300 104.3 重启后数据没了的真实教训讲一个我实际踩过的坑。有一回某业务线的排行榜数据存在Redis里当时只开了RDB而且快照条件配的是save 900 1900秒内1次写才存。某天服务被重启结果排行榜丢了接近十分钟的增量数据用户发现自己的排名突然掉了好几万反馈一大片。排查后发现问题就出在RDB的触发条件上那段时间写入量不大没达到快照触发阈值所以崩溃前的数据根本没落盘。后来改成AOF everysec这种问题再没出现过。这事的教训是持久化配置不是开了就行要理解触发条件再对照业务对数据丢失的容忍度来做取舍。如果业务数据完全不能丢老老实实AOF always也不是不行就是写性能会打折要看你能接受多大的写入吞吐。5. 常见应用场景从理论到能落地5.1 缓存热数据扛住高并发Redis最核心最常见的应用就是缓存。经典的缓存策略是Cache Aside旁路缓存读请求先查Redis命中就直接返回没命中就查数据库查完把结果写回Redis并设置过期时间写请求先更新数据库再删除Redis里的旧缓存等下次读的时候再回填。为什么更新缓存是删除缓存而不是更新缓存因为更新缓存要考虑并发写的问题多个线程同时改数据缓存里的值很难保证和数据库一致。而删除缓存则很简单下次读请求发现没命中再从数据库拉最新值回填自然就是最新的。这种策略下缓存和数据库的一致性窗口很小实际用起来很省心。做缓存还有一个重要决策过期时间设多久。设短了命中率低数据库压力大设长了缓存和数据库不一致的时间变长。我的经验是业务维度区分对待商品详情这种不太变化的可以设24小时用户库存这种实时性要求高的设几十秒甚至不缓存。5.2 分布式锁多台机器抢同一份资源一个进程内的锁比如Java的synchronized只能在单机内有效。一旦服务部署了多个实例多个进程同时操作共享资源就必须用分布式锁。Redis做分布式锁是常见的方案核心就是利用SETNX的原子性。最简单的实现SETNX lock:order:3001 unique_value EX 30如果返回OK说明抢到了锁如果返回空说明锁被别人持有。释放锁时需要先判断unique_value是不是自己的防止误删别人的锁再执行DEL删除。注意判断和删除两个动作需要保持原子性所以通常用Lua脚本来做if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end用唯一值作为锁的value是关键否则可能出现线程A执行时间过长锁过期被线程B拿到A执行完后DEL把B的锁删掉的情况。加锁时设置过期时间也是必须的防止持有锁的线程崩溃导致锁永不释放。这里要说明的是单机Redis实现的分布式锁存在一个固有的问题如果Redis主节点故障主从切换期间锁信息可能丢失。严格要求的场景要用更完善的方案比如带RedLock算法或者引入其他组件但对于绝大多数内部系统简单实现加过期时间兜底已经足够。5.3 排行榜与计数器ZSet和INCR的实战排行榜是ZSet的经典场景。以日榜、周榜、总榜为例key可以分别是rank:day:20250601、rank:day:week12、rank:total。每次用户获得分数比如积分变化调用ZINCRBY rank:day:20250601 10 user_1001Redis就会自动更新分数并重新排序。查看排行前10名ZREVRANGE rank:day:20250601 0 9 WITHSCORES查看某用户当前排名ZREVRANK rank:day:20250601 user_1001计数器用INCR就更简单了。文章的阅读数、视频的播放数、接口的调用次数都不需要事务INCR天然原子。但有一个问题要提前想好计数器的高频更新应该先更新Redis然后通过异步任务把Redis里的值定期刷到数据库避免每次都打数据库。这个同步过程可以用定时任务也可以每次INCR后把key丢进一个待同步队列由消费端批量落库。5.4 简单消息队列与延时任务List的LPUSH加BRPOP组合可以做一个轻量级的任务队列。生产者LPUSH任务消费者BRPOP阻塞获取天然支持多消费者竞争消费。延时任务稍微绕一点用ZSet实现很优雅任务的执行时间戳作为分数任务内容作为成员用一个后台轮询线程不断拿ZRANGEBYSCORE task:delay 0 当前时间戳取出所有到期的任务执行然后ZREM删除。这样做的优势是完全基于Redis本身不需要额外引入延迟队列组件。当然它能实现的可靠性有限任务执行失败得自己处理重试。如果业务要求消息不丢失、支持事务消息老老实实上专业消息队列才是正道。6. 常见问题与排查技巧实录6.1 缓存雪崩、缓存击穿、缓存穿透这三个概念是Redis使用中最高频的坑我分开讲它们其实对应三种完全不同的场景。缓存穿透查询一个根本不存在的数据。缓存里没有数据库里也没有每次请求都直接打到数据库白白承受压力。攻击者可以利用这个特点大量请求不存在的ID打垮数据库。最简单的应对是缓存空值查不到数据库时也写一个空缓存TTL设短一点比如60秒。更彻底的方式是布隆过滤器把所有存在的主键提前过滤一遍但实现成本稍高。缓存击穿某个非常热的key比如某款秒杀商品的详情刚好在某个时刻缓存过期同时大量请求一起涌入全部打到数据库上。应对办法有几种用互斥锁让只有一个请求去回源数据库其他请求等待或直接返回旧值。更实用的做法是逻辑过期缓存不设置物理过期时间存一个逻辑过期字段读的时候发现逻辑过期后异步回源更新请求还能读到旧值体验上几乎无缝。缓存雪崩大量key同时过期加上流量高峰期数据库被打垮。常见原因是同一批数据设置了相同的TTL比如当天零点缓存清空。应对办法过期时间加随机数比如TTL 基础时间 随机0到300秒错开过期时间或者做多级缓存本地缓存Redis或者用高可用架构保证Redis本身不挂。6.2 慢查询排查单线程的Redis最怕什么前面说过Redis是单线程处理命令任何一条命令执行时间过长后面的所有请求都得排队等待。所以Redis的监控里慢查询日志是必看的指标。查看慢查询SLOWLOG GET默认慢查询阈值是10毫秒也就是说执行时间超过10ms的命令会被记录下来。你可以用CONFIG SET调整CONFIG SET slowlog-log-slower-than 5000造成慢查询的常见元凶KEYS、SMEMBERS这种O(N)命令数据量大时直接卡死。一次性取大量数据的命令比如LRANGE一个几十万元素的List、HGETALL一个超大Hash。复杂的Lua脚本执行时间过长。大key的删除操作。删除一个几百MB的key时内存回收过程会阻塞服务。针对大key删除Redis 4.0引入了UNLINK命令它和DEL的区别是异步释放内存不会阻塞主线程。碰到大key要删除用UNLINK而不是DEL。排查慢查询的流程先SLOWLOG GET看有没有异常命令再看慢命令对应的key是不是大key再检查这条命令是否在高并发路径上。如果确实无法避免大数据量操作考虑拆分数据、换数据结构、或者把这类操作放到业务低峰期执行。6.3 big key与hot key两个必须知道的概念big key指单个key的value特别大比如一个List里有几十万个元素、一个Hash有上万个字段或者一个字符串值有几MB。big key的危害是读写时网络传输时间长、内存占用高、删除时阻塞主线程。判断big key可以用MEMORY USAGE key查看内存占用也可以用redis-cli --bigkeys扫描注意这个命令对线上有影响谨慎使用。hot key指某个key被极高频率访问比如双十一某爆款商品的详情其他key访问量是几百它是每秒几万。hot key会把流量压倒单台Redis节点上造成该节点CPU飙升、带宽打满。应对思路本地缓存在应用服务器进程内缓存一份hot key的数据虽然分布式一致性会有偏差但能极大减轻Redis压力。热点key打散把一个key复制成多个带后缀的keykey:1、key:2读请求随机访问其中一个副本。缺点是数据更新时要同步更新所有副本。读写分离虽然有局限但至少可以让部分读请求分流到副本节点。6.4 缓存一致性删缓存和更新数据库的先后问题缓存和数据库一致性是后端面试高频题也是线上最容易出问题的地方。最经典的问题是先更新数据库还是先删除缓存先说先删除缓存再更新数据库。如果更新数据库失败缓存已经被删掉了下一个读请求重新拉数据库旧值回填问题不大。但如果更新数据库成功前有一个读请求刚查完数据库旧值回填了缓存而之后数据库更新完成缓存里就留着旧值了。等这个TTL到期后才会更新这段时间内数据就是不一致的。先更新数据库再删除缓存是主流做法。这样即使删除缓存失败最坏的情况也就是缓存中还是旧值和数据库刚刚写的新值不一致但通常删除操作失败的概率低。如果想进一步兜底可以引入延迟双删更新数据库后先删一次缓存隔几百毫秒再删一次保证第一次删除期间可能回填的旧缓存也会被第二次删掉。这些方案都不是100%强一致的但加上过期时间兜底可以让数据不一致的窗口缩小到几乎无感知。对大多数互联网业务来说这个程度完全够用。真要强一致就别用缓存直接用数据库扛这是架构取舍的问题。7. 内存管理防止Redis被撑爆7.1 过期策略与内存淘汰机制Redis内存有限但数据可以不断写入所以必须管理内存。首先要理解Redis的key过期策略它有两种惰性删除和定期删除。惰性删除是当key被访问时发现已过期就删掉。问题在于如果过期key一直没被访问就永远占着内存。所以还要有定期删除Redis每隔一段时间随机抽查一批设置了过期时间的key删除其中已过期的直到命中的过期key比例低到一定程度。注意它是抽样不是全量扫描所以有些过期key依然可能残留。当内存真的满了Redis会根据配置的maxmemory-policy执行淘汰策略。常用策略noeviction不淘汰写命令直接报错。allkeys-lru对所有key执行LRU淘汰最久没访问的先淘汰。volatile-lru只对设置了过期时间的key进行LRU淘汰。allkeys-random随机驱逐。volatile-ttl优先淘汰剩余存活时间最短的key。我的建议是如果Redis定位是缓存用allkeys-lru基本合理如果Redis里混存了不能丢的数据用volatile-*系列会更安全只淘汰可丢失的缓存数据。但说句实在话生产环境我更倾向于通过运维手段保证内存不被打满比如给不同业务设置不同的key前缀和配额再配合监控预警而不是指望淘汰策略兜底。7.2 内存碎片与优化实践内存碎片是Redis内存管理中容易被忽视的问题。频繁的写入和删除会造成内存碎片率升高表现为used_memory不高但used_memory_rss很高。可以通过INFO memory查看mem_fragmentation_ratio碎片率如果长期大于1.5说明碎片较多可以执行MEMORY PURGE尝试整理在某些版本该命令可能不起作用更稳妥的方式是重启或者迁移数据。开发阶段的几条内存优化建议用小对象共享整数Redis内部对0到9999的整数做了共享用Redis存储这些值时不会新建对象。不过这个范围外的就没办法了。合理选择数据结构几十个字段的对象用Hash比存JSON字符串更省内存因为Hash有压缩编码ziplist编码下内存占用很低这点在数据量大时差异会被放大。避免存储大量无意义的小key每个key本身有固定开销key对象、value对象、字典结构几百万个小key会白白消耗几百兆内存。8. 一些值得尽早养成的使用习惯8.1 工具与监控配置工欲善其事必先利其器。redis-cli只是基础实际排查问题我离不开几个辅助工具。一是Redis自带的INFO命令能输出大量关键指标内存使用、连接数、命中率、持久化状态、复制状态。建议定时把INFO输出采集到监控系统关键指标设告警。比如命中率突然下降、内存增长曲线异常、rejected_connections连接被拒绝不为0这些都要第一时间被感知。二是SLOWLOG做慢查询采集配合业务的时间线可以定位某次接口超时是不是Redis慢命令引起的。三是Redisson这种客户端库Java环境封装了很多高级功能分布式锁、布隆过滤器、延迟队列比直接用裸命令更易用、更安全。不过要用它的前提是你已经理解了底层的原理否则出了问题一样抓瞎。8.2 上线前的Redis Checklist根据我踩过的坑整理了一个上线前自检清单照着走一遍能规避掉大部分问题key命名是否统一带了业务前缀方便排查和维护。是否需要设置过期时间所有缓存类key都必须有TTL。是否有大key隐患比如Hash或者List会不会在运行中无限膨胀。持久化配置是否匹配数据丢失容忍度Redis内存上限是否设置淘汰策略是否符合预期主从部署是否开启故障转移方案是否验证过密码和网络策略是否配置不能裸奔到公网。是否接入了监控和告警这套清单看着简单但每一条背后都是线上事故换来教训。我当年因为没给缓存key设置过期时间Redis内存直接被打满随后淘汰策略触发把所有数据清掉整个服务雪崩了一个多小时。从那之后清单上的每一项我都在每次上线前认认真真过一遍。8.3 结语一个建议和一个体会最后分享一点个人体会。刚开始学Redis的人很容易陷入一天学完所有命令的误区实际上真正能在项目里滚动起来的永远就那么几个基础命令加两三个高级数据结构。先把String、Hash、List、Set、ZSet在生产环境用顺手再把持久化、过期策略、内存淘汰、分布式锁这些机制吃透遇到线上问题就不会慌。我见过不少开发者Redis用了两三年还是只知道get set一遇到缓存穿透、大key清理就手足无措。原因很简单没有系统性地把Redis当成一个有生命周期的存储系统来理解。它会满、它会卡、它会丢数据、它会淘汰数据只有把这些行为都摸清楚了Redis才算真正入了门。希望这篇整理能帮你在入门这条路上少踩几个坑后面的路自然会越走越顺。
阅读完成 · 觉得有帮助?