做后端开发的这几年,Redis数据库基本是每套系统里都绕不开的组件。有人拿它当缓存用,有人用它做分布式锁、排行榜、消息队列,甚至有人把热点数据全塞进去,让它直接扛住核心流量。它的全名是Remote Dictionary Server,早期就是一个内存字典服务,但发展到今天,它的定位早就超出了“缓存”这两个字。这篇文章不打算按官方文档的方式罗列命令,而是把我从单体项目到集群环境里反复验证过的经验整理出来,覆盖的数据类型实战、持久化选型、主从与Cluster部署、缓存三大问题、分布式锁都是日常高频场景。适合刚接触Redis想少踩坑的开发者,也适合有一定使用经验但想系统梳理一遍的同行。
1. 先搞清楚Redis到底解决了什么问题
1.1 Redis数据库与传统关系型数据库的定位差异
很多初学者会把Redis和MySQL、Oracle放在一起比较,觉得它们都是“数据库”。其实这两类东西解决问题的维度完全不一样。MySQL这类关系型数据库的核心是持久化、事务、ACID、SQL查询能力,数据最终落在磁盘上,即使断电也不会丢。Redis的核心是内存存储、极低延迟、丰富的数据结构,数据读写都在内存里完成,单机QPS可以轻松到十万级别,这在MySQL上是很难想象的。
可以打个比方:MySQL是公司的财务账本,每一笔流水都记录得清清楚楚,能查能核对;Redis是前台放着的速记本,高频的、临时的、需要快速响应的事情先记在速记本上,等忙完再回到账本里落账。日常开发中两者配合,MySQL存权威数据,Redis扛热点流量,这才是一个健康系统的常态。
所以当你评估一个项目要不要上Redis时,第一件事不是“Redis能做什么”,而是“我的场景是不是真的需要这种速度”。如果数据量不大、并发不高、查询只是偶发几次,那Redis给系统带来的复杂度,可能比它解决的问题还要多。
1.2 什么场景适合上Redis,什么场景别硬上
从我实际接触过的项目来看,适合Redis的场景有这几种:热点数据缓存、计数器(浏览数、点赞数、库存)、排行榜与Top N、分布式锁、Session共享、限流、简单消息队列、UV统计、地理位置检索、布隆过滤器。这些场景的共同点是:读多写少、延迟敏感、并发高、数据结构相对简单。只要占到两三条,Redis大概率能帮上忙。
不适合的场景也很明显。比如复杂SQL查询、聚合分析、多表关联,这些东西Redis做不了也做不好;强事务一致性、需要回滚的业务操作,Redis的MULTI/EXEC只是乐观执行,不具备真正的事务语义;数据量大到超过内存规模,再靠集群扩展成本会很高;需要持久保存且不允许丢数据,Redis即使开了AOF也可能在极端情况下丢几秒数据,这个风险必须心里有数。
我见过最多的翻车案例有两种:一种是把所有业务数据都塞进Redis,内存成本爆炸;另一种是把Redis当唯一存储却不开持久化,重启之后缓存里的数据全丢,下游业务直接雪崩。结论很简单:先想清楚定位,再选型。
2. 六大核心数据类型:一张表讲透每个命令的坑
2.1 String与Hash:缓存、计数与对象存储的最优解
String是Redis里最基础的数据类型,几乎所有的缓存场景都从它开始。SET key value、GET key是最常见的操作,配合EXPIRE可以设置过期时间。实际工作中我常用到的是SETEX、SETNX和INCR这几个。比如验证码场景,SETEX code 300 "123456" 表示5分钟内有效;库存扣减用DECR,浏览数用INCR,这些都是原子操作,不需要外加重入锁。INCR的原子性是面试里反复问的点,Redis是单线程执行命令,所以同一时刻不会有第二个命令插进来,这才是它并发安全的根本原因。
Hash类型用来存对象非常合适。用户信息、商品详情这类字段多且只改其中某几个字段的数据,用HSET user:info:1001 name "张三" age 25,之后单独改一个字段就执行HSET user:info:1001 age 26,不需要像String那样把整个JSON序列化后整体覆盖。这里有个很常见的坑:新手喜欢把整个对象序列化成JSON字符串塞进String key,改一个字段也要全量GET、反序列化、改字段、再序列化、SET,一次更新等于读写一整块数据。用Hash的话,字段粒度操作要优雅得多。
另外一个序列化的坑,很多人用Spring RedisTemplate操作Redis后发现key变成类似 \xac\xed\x00\x05t\x00\x04name 这种乱码,这就是JDK默认序列化器干的。Redis Desktop Manager里看到一堆乱码,第一反应不是Redis坏了,而是序列化方式没配对。建议用StringRedisSerializer做key序列化,value用GenericJackson2JsonRedisSerializer或者统一走JSON序列化,内存占用和可读性都会好很多。
2.2 List、Set与ZSet:队列、标签与排行榜的实战姿势
List类型的语义是链表,LPUSH/RPUSH从两端推入,LPOP/RPOP从两端弹出。最经典的用法是“最新列表”,比如用户动态时间线,每来一条新消息LPUSH到一个固定key,然后用LRANGE key 0 99取最近100条。另一个使用姿势是消息队列,生产者LPUSH,消费者RPOP,但这里有个严重的坑:如果没有阻塞等待,消费者要不断轮询,浪费CPU。改进方案是BRPOP key 0,这个命令会一直阻塞到有新数据进来,相当于一个简易的发布订阅。但要注意,List做消息队列时,消费者拿到消息处理失败,这条消息如果没有重新推回队列就会丢失,所以只适合能接受偶发丢失的业务。
Set类型的语义是去重和集合运算。SADD添加成员,SISMEMBER判断是否存在,SINTER取交集。实际场景里我会用它做标签系统、用户共同关注、抽奖。抽奖场景特别适合用SPOP:从集合里随机弹出一个成员,天然避免重复中奖。还有个冷门用法是做“去重队列的中间态”,比如已处理订单ID集合,用来拦截重复请求。
ZSet是Redis里数据结构最复杂也最值钱的一个类型。每个成员关联一个score,按score排序。排行榜是它的经典场景:ZADD leaderboard:2024 user_1001 100,用户积分变化就ZINCRBY,取排名用ZREVRANK,取榜单用ZREVRANGE。ZSet还能用来做延迟队列,score存未来执行时间戳,用一个线程轮询ZRANGEBYSCORE key 0 当前时间 LIMIT 1,取到就ZREM删除再执行。这种方案要注意多消费者并发时,必须先取再删两步操作不是原子性的,要用Lua脚本打包,否则同一任务可能被多个消费者取到。
2.3 容易被忽略的扩展类型与使用禁忌
除了五大基本类型,Redis还有几个平时容易被忽略但实战价值很高的类型。Bitmap适合做签到和在线状态,每天一个bit位,一个月就30个bit,几百万用户也才几十MB内存。HyperLogLog做UV统计非常省内存,几百万独立访客误差也就在0.81%左右,但它是近似算法,不能用于精确计数。Geo类型基于ZSet实现,GEOADD往里面存经纬度,GEOSEARCH查附近的人,做LBS功能时能省去自己算距离的逻辑。Stream是Redis 5.0引入的消息队列模型,比List队列多了消费组和消息确认机制,XADD写消息,XREADGROUP消费,XACK确认,适合需要可靠消息的轻量场景。
使用禁忌这块,我列几个自己踩过的。第一,线上环境绝对不要用KEYS * 遍历key,这个命令会阻塞Redis单线程,数据量一大整个实例直接卡死,正确做法是用SCAN游标遍历或者redis-cli --scan。第二,key命名一定要有规范,推荐 业务:模块:唯一标识 的格式,比如 user:info:1001,否则到后期Redis里几千个无规律key,排查问题能查哭你。第三,Redis为什么快的底层逻辑要能说清楚:纯内存、IO多路复用、单线程避免上下文切换和锁竞争、底层SDS和跳表这类高效数据结构,这几个点也是面试里问得最多的地方。
3. 持久化机制:RDB和AOF怎么选才不丢数据
3.1 RDB快照与AOF日志的差异和选择
Redis的持久化方案主要有两种。RDB是内存数据的二进制快照,手动执行SAVE或BGSAVE生成dump.rdb,也可以按配置自动触发。BGSAVE会fork一个子进程,子进程利用写时复制(COW)机制生成快照,主进程继续服务。RDB的优点是文件紧凑、恢复速度快,缺点是两次快照之间的数据会丢,而且实例内存特别大时fork过程会阻塞主线程。
AOF是追加命令日志,把每一条写命令按协议记录到文件里。appendfsync的配置决定了刷盘策略:always每条命令都刷盘,最安全但性能最差;everysec每秒刷一次盘,性能和数据安全比较均衡,最多丢一秒数据;no让操作系统决定,性能最好但可能丢的数据最多。AOF的缺点是文件会持续增大,所以Redis提供了AOF重写机制(BGREWRITEAOF),把当前数据状态压缩成恢复所需的最小命令集合。Redis 4.0之后还支持混合持久化,开aof-use-rdb-preamble之后,AOF重写文件前半段是RDB快照、后半段是增量命令,兼顾了恢复速度和数据安全。
我怎么选呢?如果Redis只用来做纯缓存,丢了数据可以从DB找回来,那可以只开RDB,甚至不开持久化都行。只要Redis里存了不能从其他系统重建的数据,就必须开AOF并设置appendfsync everysec。我个人很推荐混合持久化方案,恢复速度快还能控制数据丢失窗口。
3.2 我在生产环境踩过的持久化坑
第一个坑是默认RDB配置在低频写入场景下等于没持久化。Redis默认的save规则是900秒内1次修改、300秒内10次修改、60秒内10000次修改。如果你的业务写入量不大,比如几小时才写了二十几条数据,Redis可能一直没到触发条件,一旦物理机断电,这半小时甚至几小时的增量全部没落盘。我当时就是信了“配置了RDB就安全”,等服务器意外断电后才发现损失比预想大得多。改成AOF everysec之后,这个隐患才真正解决。
第二个坑是fork对内存峰值的影响。有一次实例内存已经跑到16GB,开BGSAVE之后瞬间内存报警,原因是fork子进程时写时复制,父进程部分内存页被修改后需要复制一份,RSS会膨胀,峰值可能接近原内存的1.5倍。所以大内存实例要做好内存余量规划,内存吃紧的机器上宁可调高save阈值,减少快照频率,也不能让系统被fork拖垮。
第三个坑是AOF文件无限增长。没有配置重写阈值的实例,AOF文件会一直涨,直到磁盘报警。auto-aof-rewrite-percentage默认100,auto-aof-rewrite-min-size默认64MB,达到条件会自动重写。前期不知道有重写机制,手工删AOF文件还直接把表删坏了,后来老老实实配置好自动重写,再定期用redis-check-aof检查文件健康度,就再没出过问题。另外一定要记住,持久化文件不代表备份,dump.rdb和appendonly.aof都要定期备份到独立存储,同机同磁盘的备份在整机损坏时一个都救不了。
4. 高可用与集群:主从、哨兵、Cluster的选型思路
4.1 主从复制原理与增量同步的边界
单机Redis总有挂掉的时候,最常见的高可用方案是从“一主一从”开始。主从复制命令是REPLICAOF(老版本叫SLAVEOF),也可以直接在配置文件里配 replicaof 主库IP 主库端口。从库第一次连接主库时,会发PSYNC命令请求同步,主库生成RDB快照发送给从库,从库加载完快照后再持续接收主库推送的写命令,这是全量复制。复制建立后,如果从库短时间断开重连,主库会从repl_backlog积压缓冲区里找到断点,只推送缺失的命令,这就是增量复制。
这里有一个很多人不知道的边界:repl_backlog默认大小只有1MB,如果从库断线时间太长,主库积压的命令超出了这个缓冲区的范围,从库的offset就失效了,复制会自动退化成全量同步。大实例发生全量同步时,主库要生成RDB、传输文件、写磁盘,IO和带宽压力会非常大。我习惯把repl-backlog-size调大到实例内存的1%-2%,比如实例4GB就拉到64MB以上,能有效减少不必要的全量同步。
密码场景下有个容易翻车的点:主库配置了requirepass,从库必须配置masterauth,否则复制连接会一直建立不上,日志里刷一堆认证失败。排查时先看从库的info replication,确认master_link_status是不是up,如果不是,优先检查密码和网络连通性,不要上来就怀疑配置写错。
4.2 哨兵与Cluster:什么时候用哪个
主从架构做好了,还需要解决“主库挂了怎么自动切换”的问题,这就轮到Sentinel(哨兵)出场。哨兵负责监控主库和从库的状态,主库挂了之后,多个哨兵投票确认客观下线,然后从从库里选一个提升为主库,再通知客户端更新连接地址。哨兵高可用至少部署三个实例,用奇数避免投票平局,这是行业里反复验证过的底线。如果项目数据量不大、单主库写性能够用,只需要“主挂了能自动恢复”,哨兵就足够了。
如果单台Redis的内存和写入量已经到了瓶颈,就需要Cluster(集群)方案。Redis Cluster按照16384个槽位做数据分片,每个key通过CRC16(key)%16384计算出归属的槽,由不同节点负责处理。客户端直接连接集群节点,遇到MOVED或ASK重定向就路由到对应节点。集群最少三主三从,既能水平扩展容量,又能通过从节点高可用。但代价是运维复杂度明显上升,而且跨key操作受限制——在集群里执行MGET、事务、Lua脚本时,参与的key必须在同一个槽,否则直接报错,解决办法是用hash tag把相关key绑到同一个槽,比如 {user:1001}:info 和 {user:1001}:profile。
我在选型时的判断逻辑是:数据量小、容量不是瓶颈,选哨兵;数据量大、需要扩容和横向扩展,选Cluster。另外要清醒认知,Cluster解决的是容量和吞吐问题,不是一致性问题的万能药,主从之间的数据复制仍然是异步的,极端情况下从库提升时会丢少量数据。
4.3 Docker快速部署Redis主从的参考步骤
平时做测试和预发验证,用Docker部署Redis主从非常方便,几分钟就能拉一套环境出来。先准备一个docker-compose.yml,我经常用的配置大概是这样的:
version: "3" services: redis-master: image: redis:7.0 container_name: redis-master ports: - "6379:6379" command: redis-server --appendonly yes --requirepass 123456 redis-slave1: image: redis:7.0 container_name: redis-slave1 ports: - "6380:6379" command: redis-server --appendonly yes --slaveof redis-master 6379 --masterauth 123456 --requirepass 123456 redis-slave2: image: redis:7.0 container_name: redis-slave2 ports: - "6381:6379" command: redis-server --appendonly yes --slaveof redis-master 6379 --masterauth 123456 --requirepass 123456在docker-compose.yml所在目录执行 docker-compose up -d 启动,三个容器起来后用 docker exec -it redis-slave1 redis-cli -a 123456 info replication,看到 master_link_status:up 就说明从库已经连上主库了。有几个细节要记牢:容器里redis-server必须前台运行,所以不要配daemonize yes;主从之间通过容器网络用服务名互相访问,所以配置里写的是redis-master而不是IP;设置了密码后从库的masterauth不能漏,否则复制建不起来。
这套方案强烈不建议直接搬上生产。生产环境首选裸机/虚机上的官方二进制,或者K8s里的Redis Operator,容器只适合开发联调和快速验证。
5. 缓存治理实战:穿透、击穿、雪崩与热点大Key
5.1 穿透、击穿、雪崩的识别与应对
缓存穿透是指请求了一个缓存和数据库里都不存在的数据,每次请求都会直接打到数据库。最典型的例子是恶意攻击者用不存在的ID刷接口,Redis拦不住,数据库被拖垮。应对方案有两种,第一种是缓存空值,把查不到的结果也缓存起来,设置一个较短的TTL比如60秒;第二种是布隆过滤器,在请求进缓存之前先判断key是否可能存在,布隆过滤器会告诉你不存在就一定不存在,存在则不一定存在,能挡掉绝大部分无效请求。
缓存击穿是指某个热点key的过期瞬间,大量并发请求同时穿透到数据库。这个和穿透的区别在于,key本身是真实存在且非常高热的,只是恰好在高并发的一瞬间过期了。最常用的方案是互斥锁:当缓存过期后,第一个线程尝试用SET NX拿锁重建缓存,其他线程拿不到锁就短暂等待或返回旧值,缓存重建完成后其他线程直接读新值。另一个方案是逻辑过期:value里存一份过期时间,线程发现逻辑过期后先返回旧值,同时启动一个后台线程去刷新缓存,本质是利用“脏读”换性能。
缓存雪崩是更大范围的击穿:大量key在同一时间过期,或者Redis实例整体宕机,导致所有请求都打到数据库。大量key同时过期很好解决,给TTL加一个随机数就行,比如基础时间60秒加随机0到300秒的偏移,让过期时间错开。Redis本身宕机就要靠主从+哨兵+Cluster这类高可用架构解决,同时业务侧要准备多级缓存和限流熔断,不能把所有鸡蛋放在一个篮子里。
三类问题的表现很像,但解法完全不同,我整理成一个小表方便对比:
| 问题 | 核心特征 | 主要解法 | 适用场景 |
|---|---|---|---|
| 缓存穿透 | 查不存在的数据 | 空值缓存、布隆过滤器 | 非法ID、恶意刷接口 |
| 缓存击穿 | 热点key过期瞬间 | 互斥锁、逻辑过期 | 热点资讯、秒杀商品 |
| 缓存雪崩 | 大量key同时失效/实例挂 | TTL随机、多级缓存、高可用 | 大促活动、集中缓存项 |
5.2 缓存与数据库的一致性:删缓存比更新缓存更安全
缓存和数据库双写时,最推荐用的模式是Cache Aside:读请求先查缓存,没有就查数据库再回写缓存;写请求直接更新数据库,然后删除缓存,等下一次读的时候再回填。这里有个关键原则:删除缓存而不是更新缓存。原因很简单,更新缓存存在并发时序问题,两个线程同时写同一个key,数据库里可能是A的值,缓存里却残留了B的值,对不上;删除掉之后,读请求自然会把最新数据回填进去,天然规避了这个问题。
但删除缓存也不是没有坑。先更新库再删缓存时,如果删缓存失败,老数据还是会留在缓存里继续被读到。先删缓存再更新库时,如果更新库失败,下一次读又会把旧数据写回缓存,一样脏。我常用的兜底方案是延迟双删:更新数据库后,先删一次缓存,等待几百毫秒,再删一次,这样可以把第一个删完到第二个删之间可能出现的脏回填补掉。更稳妥的方案是订阅数据库的binlog变更,异步删缓存,删除失败就重试,最终一致。
对一致性要求极高的场景,比如支付金额、库存这类,不要依赖纯缓存方案,直接走数据库并在业务层加锁,缓存只用来做读取加速。物理世界里没有免费的午餐,缓存提速本身就是拿“允许短暂不一致”换来的,这个预期要提前和业务对齐。
5.3 热点Key与大Key的治理策略
热点Key是某个key的访问量异常高,比如明星八卦内容的id、双11秒杀的商品id,单个key每秒可能几万次请求。热点key直接拖垮单个Redis节点的网络或CPU。治理思路一个是本地缓存,在应用服务器上把热点数据缓存一份,减少对Redis的请求量;另一个是给key拆副本,比如把 hot:goods:1001 复制成 hot:goods:1001#0、#1、#2等多个副本,请求按照取模分散到不同副本上,Redis的读压力就摊开了。识别热点key可以用 redis-cli --hotkeys,但前提是开启了LFU淘汰策略。
大Key就是value特别大或者集合元素特别多的情况,比如一个String value超过10KB,或一个Hash有几十万个字段。大Key的危害包括:网络传输慢导致其他请求变慢、删除大Key时阻塞Redis单线程、主从复制时传输耗时增加内存消耗。排查用 redis-cli --bigkeys,它会扫描整个实例并输出不同类型里最大的key。治理分两种思路:一种是拆,把大String按业务字段拆分成多个Hash或小String;另一种是删,删除大Key用UNLINK而不是DEL,UNLINK是异步释放内存,不会阻塞主线程。
内存淘汰策略也要一起规划。maxmemory必须设值,否则实例内存被写满后有OOM风险。淘汰策略推荐缓存类场景用allkeys-lru或者allkeys-lfu,LFU对热点数据更友好;如果有不能淘汰的业务数据,就要选volatile系列并且保证这些key都设置了过期时间。实际调优时我一般先用 allkeys-lfu,热点命中和内存占用都能兼顾,效果不好再回退lru。
6. 分布式锁的正确写法与常见翻车现场
6.1 从SETNX开始的三步演进
很多项目里的第一个分布式锁都是用SETNX写的:SETNX lock 1,返回1代表拿到锁,返回0代表别人持有。如果就这么完事,坑非常大。第一个问题是没有过期时间,客户端拿到锁后崩溃或网络断开,锁永远不释放,后面的请求全部卡死。第二个问题是忘了释放,就算写了DEL释放锁,如果业务执行时间超过锁的持有时间,锁已经被自动清掉,另一个线程拿到锁,这时第一个线程执行完DEL,把别人的锁误删了。
所以正确写法要统一走这三步:第一,用 SET lock_key unique_value NX PX 30000 同时设置过期时间和独占条件,unique_value是每个线程的唯一标识;第二,释放锁之前必须校验unique_value是不是自己;第三,校验和删除必须是原子操作,不能分两条命令。删除锁的Lua脚本可以这样写:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end这段脚本用redis-cli执行时,EVAL配合KEYS[1]和ARGV[1]传参,校验通过才删除,从根上解决误删问题。我见过太多项目卡在“没设过期时间”和“先GET再DEL两步操作”这两个阶段,踩坑之后才老老实实补上。
6.2 Redisson与看门狗机制
手动写Lua脚本当然能跑,但真正的生产项目我更推荐直接用Redisson。Redisson封装了分布式锁的完整实现,RLock支持可重入。最关键的是它内置看门狗机制,默认锁的leaseTime是30秒,看门狗每10秒检测一次,只要持有锁的线程还活着就自动续期,避免业务没执行完锁就过期。这个机制解决了我上面说的“锁过期而业务还在跑”的经典问题。
用Redisson的代码模式大概是这样:
RLock lock = redissonClient.getLock("order:pay:1001"); boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS); if (locked) { try { // 业务逻辑 } finally { lock.unlock(); } }注意tryLock的参数:第一个是等待时间,最多等3秒;第二个是锁持有的时间,传-1时走看门狗自动续期。unlock一定要放在finally里,否则业务抛异常时锁不会释放。我对锁粒度的建议是能细则细,锁的范围越小越好,比如订单支付就锁order:pay:1001,不要锁整张订单表。
6.3 分布式锁的边界与替代方案
主从架构下有一个绕不开的问题:锁是写入主节点的,如果主节点还没把锁信息复制到从库就挂了,从库被提升为主库后,锁信息就丢了,另一个线程可能拿到同一把锁。RedLock试图通过向多个独立节点同时加锁来解决,但业内对这个方案一直有争议,在极端场景下它也不是绝对安全。我个人的建议是,大部分业务场景不需要追求理论上的绝对正确,用单点Redis加锁、配合看门狗续期和集群高可用保证锁所在节点尽量不挂,对业务来说已经足够;真到对一致性要求特别变态的场景,应该直接考虑ZooKeeper或etcd这类强一致协调服务,而不是在Redis上死磕。
还有一个更根本的问题:能不用锁就别用锁。分布式锁是串行化手段,会降低系统吞吐。很多所谓的“要加锁”场景,其实用原子操作、幂等设计、乐观锁版本号就能解决。比如库存扣减,直接用Redis的DECR命令原子扣减;比如防重复提交,直接利用数据库唯一索引做幂等。锁的使用场景确实存在,但它永远是最后的选择,不是第一选择。
7. 日常工具链:安装、可视化客户端与运维小抄
7.1 Windows与Linux环境的安装和基础配置
Redis官方并不支持Windows,官方文档里也没有Windows版本。网上流传的Windows安装包大多来自第三方移植,比如tporadowski/redis项目提供的5.0.14版本,或者微软早期存档的3.2.100版本。这些拿来本地学习、开发联调完全够用,但如果要上生产,直接放弃Windows方案,用Linux裸机或者Docker。Windows安装包使用很简单,下载解压后运行redis-server.exe就能启动,双击之后终端窗口不要关,它就是前台进程。
Linux上安装Redis就两条路:发行版包管理器一条命令搞定,CentOS用 yum install redis,Debian/Ubuntu用 apt install redis-server;想要新版本去redis官网下载源码,make && make install。编译依赖gcc和make,没有就提前装上。安装好后,核心配置这几个点:daemonize yes让Redis后台运行;requirepass设置访问密码;bind按实际环境填写;maxmemory设置内存上限;appendonly开启AOF持久化。
启动验证流程我一般这么走:redis-server /etc/redis.conf 启动服务,redis-cli -a 密码 ping,返回PONG就说明通了。systemd里现在就一个systemctl enable redis再systemctl start redis的事。还有个小细节,客户端连Redis超时大概率不是Redis慢,而是没走内网、防火墙挡了6379端口、或者安全组没放行,排查顺序千万别搞反。
7.2 可视化客户端:太多人选错了工具
可视化客户端这块,市场上有三个经常被混淆的名字。RedisInsight是Redis官方出品的客户端,支持Redis 7的新特性,自带内存分析、慢日志、命令面板,连接管理也做得干净,是我现在最推荐的工具。Another Redis Desktop Manager是老牌开源项目,跨平台,支持Windows、Mac、Linux,日常增删改查看数据和执行命令都很顺手,开发者很活跃,基本每个月都有更新。Redisson Desktop Manager这个名字已经停更很久了,免费版功能落后,很多下载站还捆绑垃圾软件,不建议再去踩坑。
下载地址有一个原则:去GitHub官方Releases页下载,不要从任何第三方下载站下“Redis安装包”,那些站点的绿色安装包里十有八九带一堆全家桶。连接到生产环境时要格外小心,不要在GUI里直接执行FLUSHALL,也不要用图形界面的KEYS *做全量扫描,浏览器模式只做小范围查询,大范围扫描老老实实用redis-cli的--scan命令。
7.3 运维小抄:常用命令与连接池参数
日常运维和排查问题时,我最常用到的命令就这么几个。info memory看内存使用量和碎片率,mem_fragmentation_ratio超过1.5说明内存碎片严重,可以考虑重启或者开启碎片整理;slowlog get 拉最近慢命令,注意Redis的慢日志阈值默认10毫秒,对这个级别以上的命令做优化;CLIENT LIST看当前连接数;MONITOR命令虽然能实时看命令流,但在高并发生产环境会带来明显性能损耗,我基本只会拿来临时抓几秒,用完整一个命令就立刻断开。
连接池参数也经常被忽略。Java程序用Jedis或Lettuce连接Redis时,maxTotal代表连接池最大连接数,maxIdle代表最大空闲连接数,minIdle是最小空闲数。这些参数不是越大越好,要根据预估QPS和单连接能力算,比如单连接能扛5000 QPS,要扛5万QPS就需要10个左右活跃连接,连接池的maxTotal大概设置到20-30比较稳妥。连接池太小会表现为获取连接超时而不是Redis本身慢,很多人排查半天Redis配置,最后发现问题在连接池连不上,这种案例我见得太多了。
还有一点和数据库同步相关,如果你想把Redis的数据同步或迁移到另一个Redis实例,开源工具里RedisShake很成熟,支持全量+增量同步。做集群扩容、机房迁移、数据备份时,比手动导出导入命令要靠谱得多。
最后,说一个我自己的排查习惯。线上Redis出问题,我第一反应不是看慢日志,而是先看info memory里的used_memory_human和mem_fragmentation_ratio,再看info stats里的瞬时ops,最后才决定是不是要动数据。很多看起来是“Redis慢”的问题,其实是连接池不够、key设计不合理、或者隔壁大key把IO打满了。Redis本身很快,快到你得先怀疑自己,而不是怀疑它。
还有一点,配置别图省事。我见过不少人maxmemory保持默认0,结果线上内存直接打满OOM;也见过AOF和RDB贪方便全开,存储空间小的机器天天报警。动手之前先把场景想清楚,再决定数据结构和持久化方案,这个顺序一旦反过来,后面就要用加班来还。希望这篇内容能帮你把Redis用得更顺手,也让那些被Redis问住的时刻少一点。