拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Redis实战速记:从安装部署到线上排错的完整指南

排查过一起线上事故活动页某个计数器key没有设置过期时间一年下来数据堆了快10GB直接把Redis内存打满随后OOM、持久化失败、连接数全线告警。翻代码才发现写逻辑的同事只是把Redis当成了带持久化的Map完全没做容量治理。这种问题在团队里太常见了所以我一直觉得像Redis这种长在业务底层的组件与其收藏一堆零散博客吃灰不如整理一份可以随手翻的速记。这份速记不打算搬运官方文档而是把我这些年从安装、数据类型、生产部署到线上排错的经验按实际使用顺序串起来。适合三类人刚上手Redis的后端开发者业务里已经在用Redis但经常被诡异问题绊住的人准备面试想系统性梳理一遍Redis知识块的求职者。看完谈不上成专家但日常开发和排查问题你心里会有一套完整的地图。1. Redis为什么值得一份速记场景、价值与上手路径先把Redis在技术栈里的位置说清楚。它是一个内存型键值数据库核心卖点总结下来就三个快、数据结构丰富、操作原子。快是因为数据常驻内存官方基准单实例轻松上10万QPS虽然实际值跟网络环境、key大小、并发模型都有关系但比传统数据库快一到两个数量级是事实。数据结构丰富是指它不只能存字符串还支持Hash、List、Set、ZSet甚至可以直接对结构做计算比如列表截断、集合交集。操作原子则是它的单线程事件模型决定的单个命令执行期间不会被其他命令插入这让它天然适合计数器、限流、分布式锁这类需要原子语义的场景。基于这三个特性Redis最常见的落地场景大概能分五类缓存热点数据加速也是绝大多数团队对Redis的第一认知。分布式锁借助SETNX和过期时间实现跨进程互斥。计数器与限流比如秒杀库存、接口频控、每日签到计数。排行榜与榜单ZSet的天然主场按分数排序随时取TopN。会话共享与轻量消息队列解决多实例登录态同步或做简单的发布订阅。什么场景别硬上如果你需要的是强事务、跨多个key的原子操作、SQL式复杂查询或者数据量远远超过内存预算且没有扩容条件Redis就不是最优解。很多人把大JSON整串往里塞内存打满以后抱怨Redis不行这本质上是把Redis当成了无限容量的HashMap在用。这里要强调一个认知Redis可以当主存储但那就必须认真考虑持久化策略、容量规划、备份恢复和故障转移。用途决定设计没有银弹。2. 环境准备Windows安装与Docker镜像两条路线开发机是Windows的同学第一个要接受的现实是Redis官方不提供Windows版本。官网下载页只挂Linux和macOS的源码包网上流传的Windows版Redis主要来自两个渠道一个是微软当年维护的老版本停留在3.x时代另一个是社区开发者tporadowski发布的5.0.x移植版。拿安装包练手可以但别拿它当生产环境参考。我的建议是开发期直接用Docker。原因有三个官方镜像redis:7.x-alpine体积小、启动快能保证开发环境和生产环境版本一致后面要搭主从、集群用docker compose一键拉起比在Windows上手动起一堆进程省事太多。2.1 Windows本机安装版本选择与坑如果确实需要在Windows本机装一个独立Redis优先选tporadowski的5.0.x版本或者用WSL2跑Linux版。装完以后必须检查两点。第一确认配置文件里的密码和持久化配置。很多Windows移植版默认不开启AOF执行CONFIG GET appendonly大概率返回no这意味着如果进程崩了或机器重启内存数据直接丢光。测试环境无所谓但如果你要在本地调试和生产一致的行为最好开上。第二搞清楚Redis是怎么启动的。手动双击redis-server.exe跑起来的前台进程关掉窗口就没了。想作为后台服务运行需要自己注册成Windows服务或者干脆用redis-server --service-install redis.windows.conf这种命令注册。2.2 Docker部署单节点快速起服务还是推荐走Docker路线。一条命令起一个带密码、开持久化的单节点docker run -d --name redis-dev \ -p 6379:6379 \ -v /data/redis:/data \ redis:7.2-alpine \ redis-server --appendonly yes --requirepass yourpassword参数拆开来说-d后台运行-p 6379:6379把容器内6379映射到宿主机-v /data/redis:/data挂载数据目录这是关键否则容器一删数据全没。redis-server --appendonly yes用命令行参数覆盖默认配置开启AOF持久化。--requirepass设置访问密码本地开发也建议加上避免同网段其他机器扫到6379端口直接连进来。验证服务是否正常先看日志docker logs redis-dev然后客户端连接测试redis-cli -a yourpassword ping返回PONG就说明服务没问题。2.3 基础配置项速查不管用哪种方式部署下面这些配置项最好心里有数它们决定了Redis的行为边界配置项默认值说明bind127.0.0.1监听地址生产按需开放别用0.0.0.0裸奔port6379默认端口生产可以改成非常规端口requirepass空访问密码生产必须设置maxmemory不限制最大内存超过后按淘汰策略处理maxmemory-policynoeviction内存淘汰策略见第6章面试题部分appendonlyno是否开启AOF持久化appendfsynceverysecAOF刷盘策略平衡性能与安全save900 1 等RDB快照触发条件timeout0空闲连接超时0表示不关闭loglevelnotice日志级别debug、verbose、notice、warning这里有个容易忽略的点maxmemory不设置Redis会一直往内存里写直到操作系统OOM。很多缓存key没有过期时间的事故根子就在这。生产中不管内存多大我都建议至少设置一个兜底上限配合合理的淘汰策略保证Redis进程本身不会先挂。3. 五种核心数据类型命令速查与选型判断这部分是速记里最值得反复翻阅的。你不需要背下所有命令但每种类型的核心命令、典型场景和选型边界必须清晰。类型底层实现典型场景核心命令StringSDS动态字符串缓存、计数器、分布式锁SET GET INCR SETNXHash哈希表/压缩列表对象缓存、用户属性HSET HGET HGETALLList双向链表/快速列表消息列表、时间线LPUSH RPUSH LPOP LRANGESet哈希表/整数集合去重、标签、抽奖SADD SMEMBERS SISMEMBERZSet跳表哈希表排行榜、延时队列ZADD ZRANGE ZRANGEBYSCORE3.1 String最常用但不是只有SET/GETString的类型里能存的不只是普通字符串Redis的INCR、DECR是对整数值的原子操作可以用于计数器、秒杀库存扣减。SET product:stock:1001 100 DECR product:stock:1001 SET login:token:u9527 abc123 EX 7200第二个例子常用做法是SET带EX开过期时间原子设置值和过期时间不需要再单独调EXPIRE。这比先SET再EXPIRE少一次网络往返也避免了EXPIRE失败导致永不过期的问题。还有一个容易踩坑的点INCR只支持64位有符号整数如果value是浮点数要用INCRBYFLOAT。如果value压根不是数字直接报ERR value is not an integer or out of range这个错误第5章会详细说。3.2 Hash对象缓存的首选Hash非常适合存对象本质是field-value的集合类似Map套Map。比如用户信息、商品详情都可以用一个key存多个字段而不是把整个对象序列化成一个String。HSET user:1001 name 张三 age 28 HGET user:1001 name HGETALL user:1001使用Hash比整串JSON的优势在于可以单独更新某个字段不用把整个对象读出来再序列化回去。字段非常多的时候HGETALL会拉全量数据生产里建议用HMGET指定字段或者拆分成多个小Hash控制单个key的大小避免大key成为性能隐患。3.3 List双向链表结构的消息列表List底层是双向链表左边进右边出天然适合做消息队列或时间线。LPUSH notify:user:1001 message1 RPOP notify:user:1001 LRANGE notify:user:1001 0 -1用LPUSH加RPOP实现先进先出队列多个消费者同时RPOP天然互斥。不过它只提供ACK机制外的简单功能如果业务需要消息确认、重试、延迟消息还是老老实实用专业MQ别用Redis List硬扛。另外LRANGE可以分页但大数据量下LIST的中间部分操作性能一般不适合存超长列表当数据库用。3.4 Set去重与集合运算Set是无序字符串集合自动去重支持交并补运算。适合做标签系统、共同好友、抽奖去重。SADD tags:article:1001 redis java devops SISMEMBER tags:article:1001 redis SINTER tag:java tag:redisSINTER求两个集合的交集典型应用是同时关注了A和B的用户。抽奖场景可以用SPOP随机弹出一个元素不需要先在代码里取全量再随机。需要注意Set的成员数量很大时SMEMBERS会拉全量如果有规模量级要求可以用SSCAN迭代。3.5 ZSet带权重的排序集合ZSet是Redis里最能打的数据结构之一每个成员带一个score浮点值按score排序底层是跳跃表加哈希表所以查成员和按分数范围查询都很快。排行榜是它的教科书级场景。ZADD leaderboard:game1 8000 player1 ZINCRBY leaderboard:game1 500 player1 ZREVRANGE leaderboard:game1 0 9 WITHSCORES ZRANGEBYSCORE order:delay:queue 0 1700000000 LIMIT 0 10第二个ZINCRBY适合游戏分数实时累加第三个T检索Top10第四个可以实现延时队列score存时间戳轮询时取出score小于当前时间的元素。选型时注意ZSet对写性能的开销比List高如果只是先进先出且不需要排序用List更省。4. 生产部署与日常运维从可视化客户端到Docker Compose开发调试和生产部署C位离不开两个工具链可视化客户端和编排方案。4.1 可视化客户端RDM、Another Redis Desktop Manager还是RedisInsight老牌工具Redis Desktop Manager也就是热词里频繁出现的RDM界面成熟但新版走向商用授权免费版功能有阉割旧版又要自己找渠道。我的建议是个人平时排障优先用官方出品的RedisInsight免费支持集群拓扑可视化、内存分析、慢日志查询还能直接看key的TTL和内存占用。团队里图简单可以用开源免费的Another Redis Desktop ManagerGitHub上维护活跃跨平台中文界面友好多标签连接很方便。不要陷入选择困难。工具的作用是快速看到数据、定位问题而不是越多越好。我自己电脑上只装一个RedisInsight偶尔用redis-cli配合命令做精确操作。4.2 连接配置与安全注意连接参数基本就是host、port、auth三件套。但在生产环境有几个安全细节必须注意不要用默认端口直接暴露公网经常有安全扫描脚本扫6379弱口令直接被打穿。Redis没有内建的用户权限体系别用root跑Redis进程给个独立用户。危险命令要重命名或禁用比如CONFIG、FLUSHALL、KEYS。生产环境误执行FLUSHALL的后果不用我多说。业务侧访问Redis要设置合理的超时和连接池上限防止Redis抖动时客户端无限等待连带打爆服务线程池。举一个真实场景某次线上Redis cpu升高排查发现是某位同事在客户端代码里用了KEYS user:*全量扫描。KEYS会阻塞Redis单线程几秒钟期间所有其他命令排队看起来就像Redis卡死了。这种问题的标准解法是用SCAN游标迭代或者直接维护一个索引key。4.3 生产环境部署Docker Compose搭建Redis主从从单机Redis跨到生产环境第一个该落地的是主从复制。主从的作用不只是读扩展更重要的是给数据加一层冗余。用Docker Compose搭主从非常直接。version: 3.8 services: redis-master: image: redis:7.2-alpine container_name: redis-master ports: - 6379:6379 command: redis-server --appendonly yes --requirepass masterpass volumes: - redis-master-data:/data networks: - redis-net redis-slave: image: redis:7.2-alpine container_name: redis-slave depends_on: - redis-master ports: - 6380:6379 command: redis-server --replicaof redis-master 6379 --masterauth masterpass --requirepass slavepass volumes: - redis-slave-data:/data networks: - redis-net volumes: redis-master-data: redis-slave-data: networks: redis-net: driver: bridge补充说明Redis 5.0之前配置项叫slaveof5.0之后更名为replicaof。如果你拉的是redis:6或7镜像用--replicaof。主从之间的鉴权通过--masterauth指定。业务连接从库时用--requirepass设置的独立密码。启动并验证docker compose up -d docker exec redis-slave redis-cli -a slavepass info replication输出里的role:slave、master_link_status:up说明主从复制已经建立。如果master_link_status:down优先检查masterauth是否配置以及主库requirepass和从库masterauth是否一致。需要明确一点主从复制解决的是数据冗余和高可用读取但主库宕机后从库不会自动顶上来。要实现自动故障转移还要部署Sentinel哨兵集群或者在生产体量较大时用Redis Cluster。从主从到哨兵再到Cluster是一条渐进的升级路径先把主从玩熟再碰哨兵节奏最稳。4.4 部署后的日志与健康检查日志是排查Redis问题的第一入口。容器内默认日志打到stdout直接docker logs redis-master查看。调整日志级别用loglevel配置生产环境建议notice排障时临时调成debug但别长期开着日志量会非常恐怖。日常运维里还要养成两个习惯。一是看慢日志slowlog get 10可以列出最近10条慢命令如果看到大批KEYS或者超大key的GET就要警惕了。二是看内存和连接数redis-cli info memory和redis-cli info clients。我通常定期跑一遍redis-cli --bigkeys它会用SCAN的方式找出大key分类提前治理而不是等内存告警才去救火。5. 实战高频坑点序列化、increment()报错与缓存治理这一章的内容全部来自线上踩坑现场按热词搜索热度来看也是大家问得最多的几个点。5.1 序列化方式汇总为什么RedisTemplate存进去是乱码Spring Boot项目里用RedisTemplate最常见的现象是数据明明存进去了用可视化工具一看key是\xac\xed\x00\x05t\x00\x0b...这种二进制乱码或者GET回来发现value和自己存的不一样。原因基本只有一个RedisTemplate默认的序列化器是JdkSerializationRedisSerializer它会把Java对象序列化成JDK二进制格式。这个格式对人类不可读而且存进去的key还带着类名前缀不同服务如果序列化器不一致会出现同一个业务key写入端和读取端互相看不到的灵异现象。解决办法是显式配置序列化方案。我的习惯是key用StringRedisSerializervalue用GenericJackson2JsonRedisSerializer这样key是纯字符串value是JSON兼顾可读性和反序列化灵活性。Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; }如果你根本不需要对象自动序列化所有value都是字符串那直接用StringRedisTemplate最省心它内部已经把key和value都设定为StringRedisSerializer基本不存在乱码问题。5.2 increment()报错排查链路错误信息ERR value is not an integer or out of range在Java里出现的完整姿势是这个redisTemplate.opsForValue().increment(stock:001); // 结果抛出RedisSystemException: // Error in execution; nested exception is // io.lettuce.core.RedisCommandExecutionException: // ERR value is not an integer or out of range很多人看到这个报错第一反应是我明明存的是数字啊跟着陷入迷茫。分享一个标准排查链路第一步确认这个key是不是第一次写入。INCR对不存在的key会当成0处理不存在因为key不存在所以报错的说法所以报错一定意味着key已经存在且value不是合法整数字符串。第二步用可视化工具或者redis-cli查看这个key的真实内容。重点是看它是什么类型TYPE stock:001 GET stock:001如果GET返回的是\xac\xed\x00\x05...这种二进制说明写入端用了JDK序列化器value根本不是可解析的整数。这通常是同一个key一个地方用StringRedisTemplate/new写入另一个地方又用RedisTemplate想incr两边序列化不一致导致。第三步检查业务代码里是否有混合写入。常见的一个坑是初始化库存时用普通SET存了一个字符串abc或者存了一个JSON对象之后再用increment自然报错。修复思路是给计数器类key单独使用StringRedisTemplate并且避免同一个key多用途。如果开发时无法立刻定位先用SCAN stock:*匹配出候选key再逐个GET看内容通常十秒内能锁定元凶。这里再提醒一个容易忽略的边界INCR只支持64位有符号整数如果要累加浮点数比如余额0.95加0.05直接用INCR也会报同样的错这种情况要改用INCRBYFLOAT。5.3 缓存治理三板斧穿透、击穿、雪崩缓存治理这些年几乎成了面试必考也是线上事故高发区。简单把三个概念和对应方案理清楚缓存穿透指的是查询一个必然不存在的数据缓存和数据库都没有请求直接打到DB恶意攻击可以靠这个打垮数据库。解法一般两种一是对空结果也做缓存比如查不到用户缓存一个空对象并设较短过期时间二是用布隆过滤器在缓存前把不可能存在的key直接过滤掉。缓存击穿指的是某个热点key恰好过期一瞬间大量请求全部打到DB。解法是互斥锁发现缓存没有时先获取分布式锁只允许一个线程去DB加载数据并回填缓存其他线程等待后直接读缓存。另一种是逻辑过期value里存一个过期时间戳拿到数据后发现逻辑过期先返回旧数据再异步更新缓存能做到不阻塞请求。缓存雪崩指的是大量key同时过期或者整个Redis宕机请求全部倾泻到DB。解法相对工程化过期时间加随机值比如基础过期时间300秒加一个0到60秒的随机数避免同一秒集体过期重要数据做多级缓存比如本地Caffeine做一级Redis做二级DB兜底如果Redis彻底不可用要有降级开关直接返回默认数据或提示而不是无限等待。三个问题并排放在一起容易记混。我的记忆方法是穿透是查了没有击穿是单点key过期的瞬间雪崩是大面积过期或节点宕机。治理思路本质都是让请求尽量别打到DB打到了也要有兜底。6. 分布式锁与面试高频题从原理到代码6.1 分布式锁的正确写法说到分布式锁网上能搜到大量不靠谱的写法。最典型的错误是分两步走先SETNX再EXPIRE。这两条命令不是原子的如果SETNX成功之后进程突然挂了锁永远不释放后续所有请求全部阻塞。正确的最小实现是一条原子命令SET lock:order:1001 uuid-vaule NX PX 30000NX保证key不存在时才设置PX 30000设置30秒过期。value用一个唯一标识比如UUID作用是释放锁时验证是不是自己的锁防止误删别人的锁。释放锁要用Lua脚本保证get和del的原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end不这么写的后果线程A持有锁因为某些原因执行时间超过了过期时间锁自动释放了线程B拿到锁开始执行此时A执行完直接DEL把B的锁删了。有了value校验就不会误删B的锁。不过在实际工程中我一般不会手写这套逻辑而是直接用Redisson。Redisson对分布式锁封装了看门狗机制默认30秒过期每10秒自动续期只要业务线程没结束锁就不会意外过期。它的release逻辑内部已经处理好了value校验。手写实现主要用于理解原理面试时能写出来已经超过很多人。另外有个更底层的认知Redis分布式锁属于AP模型极端情况下比如主从切换可能发生两个客户端同时拿到锁。如果业务对锁的正确性要求极高比如金融支付场景就要考虑ETCD或ZooKeeper这类CP模型实现。大多数互联网业务的库存扣减、防重复提交用Redis锁加合理的过期时间已经足够。6.2 高频面试题梳理这部分按热词中的redis面试题展开把高频题目和回答要点列出来方便面试前快速过一遍。问题一Redis为什么这么快回答要点纯内存访问避免磁盘IOIO多路复用单线程处理网络请求单线程避免了线程切换和锁竞争的开销底层数据结构设计高效比如SDS、跳表、压缩列表。注意要强调命令执行是单线程。问题二Redis为什么用单线程核心原因不是单线程一定快而是Redis的瓶颈通常不在CPU而在网络IO和内存。单线程模型简化了并发控制每个命令都是原子的不需要加锁。Redis 6.0之后引入多线程但只用于网络数据的读写命令执行仍然是单线程。问题三过期删除策略有哪些两种策略配合使用惰性删除访问key时才检查是否过期过期就删节省CPU但会残留过期数据定期删除每隔一段时间随机抽取一部分带过期时间的key检查删除过期key。两种情况都查不到的过期数据最终靠内存淘汰策略兜底。问题四内存淘汰策略有哪些Redis 4.0之后共有8种策略noeviction不淘汰直接报错allkeys-lru所有key按LRU淘汰volatile-lru只淘汰设置了过期时间的keyallkeys-lfu和volatile-lfu按LFU最小频率淘汰allkeys-random和volatile-random随机淘汰volatile-ttl淘汰剩余时间最短的key。生产环境常用allkeys-lru或allkeys-lfu保证进程不因内存耗尽崩溃。问题五RDB和AOF有什么区别RDB是内存快照恢复快、文件紧凑但两次快照之间的数据可能丢失。AOF记录写命令数据丢失少但文件大、恢复慢。生产推荐开启AOF并在Redis 4.0后用混合持久化RDB作为基础快照加上AOF增量命令兼顾恢复速度和数据安全。问题六主从复制的原理从节点初次连接主节点时触发全量同步主节点生成RDB快照发给从节点同时把缓冲区中的写命令发给从节点后续进入增量同步阶段通过repl_backlog缓冲和偏移量offset同步新写入。同步是异步的所以主从存在短暂数据不一致。问题七缓存一致性怎么保证没有完美的方案常见手段是延迟双删更新数据库后删除缓存隔一小段延迟再次删除用来解决并发读写下可能出现的旧缓存回填问题。更可靠的是用Canal订阅binlog异步删除缓存把更新操作从业务代码里解耦出来可靠性更高但架构变重。问题八Redis分布式锁失效怎么办我一般先反问你用的是哪种实现。如果是SETNX加手动EXPIRE问题和解法就是6.1里说的那套。如果说用了Redisson那就考察看门狗续期机制。如果追问极端场景落到RedLock算法的争议和CP模型选型能展开讲就已经是加分项了。顺便说一个面试的实用技巧回答Redis问题尽量从数据结构和单线程模型讲起而不是背结论。重点是把你实际写过的代码和踩过的坑讲清楚面试官更看重你踩过坑之后的思考。这份速记写下来有点像把自己这几年的Redis排错现场重新过了一遍边写边回忆那些半夜看告警群的日子。最深的体会是Redis本身不难难的是用的时候克制。缓存不是加得越多越好锁不是必须就别上key的命名和过期时间在一开始就要规划好。用之前多在纸上画画数据流向想清楚每个key的存活周期和大小上限很多线上事故根本不会发生。如果你也在维护一套Redis建议从今天开始做两件事检查所有key的过期时间和最大内存限制以及梳理一遍有没有跨服务乱用同一个key的情况。这两件事做完大概率能帮你躲掉今年最痛的几次告警。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门