
1. 入门认知Redis到底是什么为什么值得花时间系统性学一遍我最早接触Redis是在做用户会话缓存的时候当时项目里Session暴增MySQL扛不住团队连夜把热点数据往Redis里塞。那时候我对Redis的理解就停留在“一个很快的KV数据库”直到后来踩了缓存雪崩、主从切换丢数据、分布式锁失效这些坑才意识到Redis不是“会用几个命令”就够的。它背后涉及的缓存设计、持久化机制、集群架构、内存淘汰策略每一个环节都能单独写一篇长文。这篇文章打算围绕“Redis最全详细学习”这个主题把一套我自己验证过、也带过新人走通的学习路径整理出来。内容不只讲命令还会覆盖安装、数据类型选型、持久化、主从哨兵、集群、分布式锁、缓存治理、可视化客户端、日志排查以及面试里高频出现的原理题。适合谁看准备系统学习Redis的开发者、马上要面试后端岗位的候选人、以及对现有缓存方案不放心想重新梳理一遍的工程师。先给一个结论Redis值得学而且值得按体系学。它不是那种“会用就行”的工具因为它的高性能来自一系列精妙的设计——单线程事件循环、IO多路复用、内存数据结构、渐进式rehash。你多理解一层设计线上出问题时就能多一分从容。下面按我的学习路径一步步展开。我见过太多人一开始就背命令背完就忘遇到问题还是一头雾水。正确的打开方式应该是先搞清楚Redis解决什么问题再动手装一个实例接着把数据类型逐个玩明白然后再往深了走——持久化怎么选、主从怎么搭、哨兵怎么工作、集群怎么分片、分布式锁怎么实现才安全。每走一步都带着问题去学效率会高很多。还有一点很关键Redis的学习工具链也很重要。命令行客户端redis-cli、可视化工具Redis Desktop Manager以及它的开源替代Another Redis Desktop Manager、日志分析、监控面板这些工具用好了排查问题的速度能翻倍。后面我会逐一分享。2. 环境与工具从零装好Redis并配齐常用客户端2.1 Windows安装Redis的几个靠谱途径很多人第一步就卡在安装上。Redis官方其实不提供Windows版本官方文档里明确说了Windows支持是社区维护的。但这不代表Windows上没法用常见的有三条路。第一条路是用微软维护的旧版Redis for WindowsGitHub上能搜到tpemartin/redis或microsoftarchive/redis一般停留在3.x或5.x版本。这个适合本地快速验证命令缺点是版本老很多新特性比如Stream类型、ACL权限控制用不了。第二条路是用Memurai或者国产的某些发行版本质上是Redis的Windows移植兼容性不错但同样存在版本滞后问题。第三条路是我推荐的方式用Docker跑Redis。Windows上装好Docker Desktop然后一条命令就能拉起最新版Redis容器docker run -d --name redis \ -p 6379:6379 \ -v /data/redis:/data \ redis:7.2这样做的好处是版本跟得上、环境干净、删了重建也方便而且和Linux生产环境的差异最小。如果公司服务器是Linux本地用Docker模拟最贴近真实部署。Windows下还有一种把Redis注册为系统服务的方式方便开机自启。如果你用的是redis-windows版本可以在安装目录执行redis-server --service-install redis.windows.conf --service-name Redis redis-server --service-start --service-name Redis这样Redis就会作为Windows服务运行不占用前台窗口。不过这套方式只适用于Windows移植版Docker方案则不需要关心服务注册问题Docker Desktop自身管理容器生命周期。2.2 Linux环境下的编译安装与配置生产环境大部分是Linux。Linux装Redis有两种主流方式包管理器安装和源码编译安装。包管理器最省事# Ubuntu / Debian apt-get update apt-get install redis-server -y # CentOS / RHEL 7 yum install epel-release -y yum install redis -y但包管理器装的版本可能不是你想要的比如CentOS默认源里可能是3.2甚至更低这时候就要考虑源码编译。我的习惯是到Redis官网redis.io查最新稳定版然后下载源码编译wget https://download.redis.io/releases/redis-7.2.4.tar.gz tar xzf redis-7.2.4.tar.gz cd redis-7.2.4 make make install编译之前确保系统有gcc和相关依赖yum install -y gcc gcc-c makemake install会把redis-server、redis-cli这些二进制装到/usr/local/bin。装完之后还需要一个配置目录和日志目录mkdir -p /etc/redis /var/lib/redis /var/log/redis cp redis.conf /etc/redis/然后修改配置文件里的daemonize、pidfile、logfile、dir这些参数再用redis-server /etc/redis/redis.conf启动。这里有个新人常犯的错直接敲redis-server不带配置文件启动结果用默认配置跑起来很多参数和预期不一样。养成“启动必带配置文件”的习惯后面会少很多麻烦。2.3 可视化客户端Redis Desktop Manager和它的替代品命令行用久了其实很顺手但看Key列表、查某个Key的TTL、可视化地浏览Hash结构里的字段还是图形界面更直观。这里说两个常用的。Redis Desktop Manager简称RDM是老牌工具支持Windows、macOS、Linux。网上很多人找它的开源旧版因为新版变成了商业订阅模式。如果你是个人使用找一个较早的开源版本比如2020年之前的版本完全够用。商业版多的那些功能对大多数人来说用不上。另一个是Another Redis Desktop Manager简称ARDM完全开源免费界面比RDM更现代支持暗色主题连接Redis Cluster也能自动识别槽位分布。我现在主力就是在用ARDM。连接的时候注意几个参数地址填Redis所在服务器IP或域名端口默认6379改了就在配置文件里对应调整密码如果redis.conf里设置了requirepass这里必须填对SSH隧道Redis没开公网访问时可以用跳板机转发工具只是辅助别忘了掌握redis-cli。ssh到服务器执行redis-cli -a 密码也能做所有操作而且排障时往往比GUI更快。2.4 图形客户端之外命令行的高频操作速览redis-cli里我最常用的几类命令# 连接与基础操作 redis-cli -h 127.0.0.1 -p 6379 -a yourpassword ping # 返回PONG说明连接正常 select 1 # 切换数据库默认16个库(0-15) dbsize # 查看当前库key数量 # 热Key和Big Key初步排查 redis-cli --bigkeys # 扫描大key redis-cli --hotkeys # 需要开启maxmemory-policy才能用 redis-cli --scan --pattern user:* # 按模式匹配key需要提醒的是-a传密码会把密码暴露在shell历史记录里生产环境建议用REDISCLI_AUTH环境变量或者进入redis-cli后用auth命令验证。3. 数据类型深挖五大基础类型加三种高级结构3.1 String类型不只是简单的KVString是Redis最基础的类型底层是SDSSimple Dynamic String不是C语言的char数组。SDS的设计解决了C字符串的很多痛点获取长度O(1)、二进制安全、预分配空间减少内存重新分配次数。你往Redis里存序列化后的JSON、计数器、验证码本质都是String。String的典型业务场景里有两个容易被忽略的第一个是计数器的原子性。INCR、DECR、INCRBY这些命令是原子操作适合做秒杀库存、点赞数、访问量。很多新人会先GET再SET这在并发下必然出错。必须用INCR这类自带原子性的命令。面试里问到“Redis的INCR为什么准”要答出两点单线程执行命令天然串行加上命令本身原子所以并发递增不会丢更新。第二个是分布式ID或流水号的生成。比如每天生成订单号用INCR配合日期前缀就能得到一个趋势递增的序列。String还有一个需要留意的点单个Value最大512MB但别真往里面塞大对象。一个Key存10MB以上的大JSON会导致网络传输慢、序列化耗时、内存碎片增多这就是所谓的Big Key问题。3.2 Hash类型对象存储的正确姿势Hash是一个string field到string value的映射表适合存对象。比如用户信息用HSET user:1001 name 张三 age 30这样存下来比序列化成JSON塞String更灵活想改某个字段就改某个字段不用整条覆盖。Hash底层的编码有两种ziplist和hashtable。当字段数少且值都比较短时用ziplist紧凑存储省内存当字段多了或值大了自动转成hashtable。这个转换阈值由hash-max-ziplist-entries和hash-max-ziplist-value控制。实际开发中Hash用来做购物车、商品信息、配置项聚合都很合适。一个容易踩坑的地方是不要把Hash搞得特别大一个Hash里有上万字段同样属于Big Key操作时hgetall会一次性把所有字段拉出来网络开销巨大。解决方案是分片按业务维度拆成多个小Hash。3.3 List类型队列与栈的基石List底层是quicklist由多个ziplist节点组成双向链表。它支持从头部或尾部压入弹出元素LPUSH、RPUSH、LPOP、RPOP这些命令组合起来可以实现栈、队列、阻塞队列。在生产者消费者场景里BRPOP阻塞式弹出非常实用它会让客户端一直等待直到队列里有数据减少无效轮询。我做过一个异步任务队列就是用List存待处理的消息ID消费者用BRPOP阻塞读取配合超时时间防止永久阻塞。List还有两个业务细节值得写进笔记LTRIM可以裁剪List长度比如只保留最近100条日志或通知。分页读取用LRANGE但要注意大List的LRANGE是全量遍历性能随范围增大而下降。List适合做“消息流”这种天然按时间排序的数据。3.4 Set类型和ZSet类型去重与排序的利器Set是无序集合基于哈希表实现支持交、并、差运算。典型场景抽奖去重一个用户只能中奖一次用SADD把userId加进集合抽中的用SPOP弹出一个。社交关系我的好友集合A、你的好友集合BSINTER直接算出共同好友。在线状态在线用户ID进集合掉线移除SCARD就是在线人数。ZSet有序集合在Set基础上多了一个score字段按score排序。底层是跳表加哈希表所以ZRANGE、ZREVRANGE能高效拿到排名区间。身边的场景太多了排行榜写入ZADD rank:2024 100 user:1用ZREVRANGE取前100名。延迟队列score用时间戳表示不断地ZRANGEBYSCORE取出到期任务。热点数据统计文章热度置顶score就是点击数或综合权重。ZSet容易忽略的是分数精度问题。score是double类型如果业务上对精度敏感比如金额排序建议乘以10的幂次转成整数再存避免浮点计算误差导致排序诡异。3.5 Bitmap、HyperLogLog与Geo高级数据结构的降维打击这几个类型在业务里用得好能大幅节省内存。Bitmap本质是String的位操作每个bit表示一个状态。比如用户签到一年365天一个用户一年的签到记录只需要365bit约46字节。SETBIT和GETBIT配合就能精确索引每一天。日活统计也可以借鉴用户ID映射到位偏移每天一个bitmap做位与运算就能算出N天活跃用户。HyperLogLog用来做基数统计就是“有多少个不同元素”这个问题。它基于概率算法标准误差0.81%但内存固定只有12KB左右。统计UV独立访客用它非常合适虽然不能拿到精确值但对绝大多数运营报表完全够用。PFADD、PFCOUNT、PFMERGE三条命令覆盖日常所有操作。Geo是地理位置类型底层也是ZSet的封装把经纬度编码成score。附近的人、门店距离计算、外卖配送范围判断GEOADD写入坐标GEOSEARCH查询附近N米内的点GEODIST算两点距离。做LBS相关功能时别傻傻地用MySQL算距离了Redis这一套好使得多。4. 高级特性解析持久化、淘汰策略与事务管道4.1 RDB与AOF的取舍以及实际推荐组合Redis持久化有两种方式RDB快照和AOF日志。RDB是某个时间点的全量数据快照文件紧凑、恢复快适合做备份和灾难恢复。缺点是周期性的崩了会丢最后一次快照之后的数据。AOF记录每次写命令可配置每秒刷盘或每次写刷盘数据安全性更高但文件大、恢复慢。Redis 4.0之后引入了混合持久化RDB做全量快照中间的增量用AOF小日志保存。加载时先加载RDB再回放增量兼顾恢复速度和数据安全。我的生产推荐配置是appendonly yes appendfilename appendonly.aof appendfsync everysec aof-use-rdb-preamble yesappendfsync everysec意味着最多丢一秒数据这在绝大多数业务里可接受。如果连一秒都丢不起有极端场景用always但要承受性能损耗。AOF还有几个运维细节AOF文件膨胀时Redis会自动重写bgrewriteaof通常不需要人为干预但如果在AOF重写过程中Redis崩溃重启后可能加载失败这时候需要用redis-check-aof修复redis-check-aof --fix appendonly.aofRDB的触发条件由配置save控制默认是save 900 1、save 300 10、save 60 10000意思是900秒内有1次写、300秒内有10次写、60秒内有10000次写就生成快照。生产环境我一般会调高阈值避免频繁fork子进程打满磁盘IO。4.2 内存淘汰策略8种策略怎么选Redis内存写满之后不会崩溃而是根据maxmemory-policy决定怎么处理新写入。内存淘汰策略一共有8种按大类分noeviction不淘汰写不进去直接报错。allkeys-lru / allkeys-lfu对所有Key按LRU、LFU算法淘汰。volatile-lru / volatile-lfu只对设置了过期时间的Key按LRU、LFU淘汰。allkeys-random / volatile-random随机淘汰。volatile-ttl淘汰剩余TTL最短的Key。选型建议很简单如果Redis里允许任何Key被淘汰用allkeys-lru如果只有带过期时间的缓存数据可以被淘汰用volatile-lru。LFU适合有“冷热不均”明显的数据LFU能更精确地区分访问频率LRU受“一次性大量访问”干扰比较大。实际项目里我倾向allkeys-lfu前提是业务能接受某些非热点Key被淘汰。还有一个面试高频配置项maxmemory-policy配合maxmemory一起设置别忘记设置maxmemory本身否则淘汰策略不会生效。遇到OOM command not allowed when used memory maxmemory时先看是不是配了noeviction。4.3 事务、Pipeline与Lua脚本的边界Redis事务用MULTI、EXEC、DISCARD、WATCH实现。MULTI开始收集命令EXEC一次性执行。注意Redis事务和MySQL事务有本质区别Redis不支持回滚事务中某个命令执行失败其他命令照常执行事务内所有命令也不是“并发隔离”意义上的原子只是“连续执行不被其他命令插入”。真正的原子性操作推荐用Lua脚本。Redis内置Lua解释器脚本里多条命令作为一个整体执行中间不会被其他客户端命令插入。秒杀扣库存、限制频率、原子性比较并交换这些场景写Lua脚本是最稳的做法。示例原子扣减库存并返回结果local stock tonumber(redis.call(GET, KEYS[1])) local num tonumber(ARGV[1]) if stock num then redis.call(DECRBY, KEYS[1], num) return 1 end return 0在redis-cli里执行redis-cli --eval stock.lua stock:1001 , 1注意--eval传入的key和argv用逗号分隔注意空格规则。这里有个我踩过的坑Lua脚本里如果使用随机命令比如TIME、SPOP在Redis Cluster模式下会报错因为脚本复制到从节点时要求确定性。Cluster模式下需要保证脚本操作的所有key在同一个slot里可以用Hash Tag实现。Pipeline和Lua的区别也值得说清楚。Pipeline是客户端批量发送命令、一次性接收结果减少RTT但不保证原子性一组命令中间可能插入其他客户端的命令。要批量导入数据或降低延迟用Pipeline要保证多条命令执行期间不被别人插队用Lua。4.4 慢查询日志与监控性能排查第一步Redis有没有慢命令直接执行SLOWLOG GET就能看到最近执行的慢查询记录。慢查询阈值由slowlog-log-slower-than配置单位微秒默认1000010毫秒。还有slowlog-max-len控制保留条数。线上排查性能问题第一步永远是慢查询日志。一次查询超过10毫秒对Redis这种内存数据库来说已经很慢了。常见的慢命令包括KEYS全库遍历、HGETALL大Hash全量拉取、SORT大集合排序、ZRANGEBYSCORE大范围、以及各类O(N)操作。生产环境严禁使用KEYS要用SCAN替代。SCAN是游标式遍历每次返回少量数据不会阻塞Redisredis-cli --scan --pattern user:* --count 1000配合redis-cli --bigkeys可以快速发现大Key再针对性地拆分或压缩。5. 高可用架构主从复制、哨兵与集群的完整落地5.1 主从复制原理与配置主从复制解决的是单点故障和数据冗余。一个Master挂多个SlaveMaster处理写请求Slave只读同步数据。配置一个从节点非常简单在从节点redis.conf里加replicaof 192.168.1.10 6379或者运行时用命令redis-cli REPLICAOF 192.168.1.10 6379从节点同步的原理是首次连接全量同步Master生成RDB快照发给Slave后续增量同步基于replication backlog缓冲区的命令传播。这里有个关键参数repl-backlog-size默认1MB如果从节点断连时间太长backlog被覆盖就只能重新全量同步。大实例全量同步很耗资源所以backlog调大一点比如64MB或128MB可以有效避免频繁全量同步。另一个参数是replica-read-only yes从节点默认只读别在上面写数据否则数据不一致问题会让你欲哭无泪。用Docker搭建主从的典型命令如下# 主节点 docker run -d --name redis-master -p 6379:6379 redis:7.2 redis-server --appendonly yes # 从节点 docker run -d --name redis-slave -p 6380:6379 redis:7.2 \ redis-server --replicaof 172.17.0.2 6379Slave的IP要换成Master容器的实际IP可以用docker inspect查看。主从搭好之后在Master上执行SET在Slave上GET能读到就说明同步正常。测试时也可以执行INFO replication查看连接状态重点关注master_link_status:up和slave0的数量。5.2 哨兵架构自动故障转移是怎么工作的主从复制本身不能自动切换。Master挂了写服务就断了需要人工把某个Slave提升为Master。哨兵Sentinel解决的就是这个问题监控、通知、自动故障转移。Sentinel本身是一个独立进程通常部署奇数个3个或5个通过流言协议和投票协议选出一个Leader来执行故障转移。配置哨兵的核心文件是sentinel.confsentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 12表示至少2个哨兵认为Master不可达才判定客观下线并触发切换。down-after-milliseconds是主观下线判定时长。5秒内没收到心跳哨兵就认为这个节点挂了。parallel-syncs控制在故障转移后同时有多少个从节点去同步新Master设1避免所有从节点同时全量同步造成压力。启动哨兵redis-sentinel /etc/redis/sentinel.conf哨兵主要的工作逻辑是检测Master是否主观下线、通过投票达成客观下线、选出一个从节点作为新Master、通知其他从节点和新Master建立主从关系。整个过程不需要人工干预但客户端也要配合像Lettuce、Jedis这些Java客户端支持配置多个哨兵地址Master切换后能自动更新连接。有一个容易忽略的点哨兵配置文件在运行过程中会被重写记录Master地址的变化。所以不要把sentinel.conf设置为只读否则故障转移后无法写回状态。5.3 Redis Cluster数据分片与多节点扩容哨兵解决的是高可用但数据量超过单机内存时就需要Cluster模式了。Redis Cluster把数据分到16384个哈希槽slot里每个节点负责一部分槽位。写入某个Key时对Key做CRC16计算再对16384取模定位到具体槽位请求会被路由到对应节点。Cluster至少需要3个Master每个Master建议配一个Replica。用命令创建集群redis-cli --cluster create \ 192.168.1.10:7000 192.168.1.11:7000 192.168.1.12:7000 \ --cluster-replicas 1--cluster-replicas 1表示为每个Master创建一个从节点。自动分配槽位。Router的使用上有个经典问题客户端直接连Cluster不指定槽位请求一个Key时如果Key的槽位不在当前节点Redis会返回MOVED错误客户端需要根据返回的槽位跳转。像Lettuce、Redisson这些客户端封装好了槽位路由逻辑对开发者透明。Cluster模式下禁止使用MULTI操作跨槽位的KeyLua脚本也要保证所有Key在同一个槽位。想让某些Key一定落在同一槽位用Hash Tag比如{user}:1001和{user}:1002花括号部分参与哈希计算两个Key就落在同一槽位了。扩容或缩容时用redis-cli --cluster reshard 192.168.1.10:7000交互式指定要迁移的槽位数量和目标节点。这个过程对线上基本无感但最好在低峰期执行。还有一个注意点Cluster模式下maxmemory是每个节点分别设置的别在总内存上做文章。5.4 用Docker快速搭建完整主从哨兵环境我平时验证架构方案最喜欢用Docker Compose一键拉起整套环境。贴一个最小可用的主从加哨兵配置version: 3 services: master: image: redis:7.2 container_name: redis-master command: redis-server --appendonly yes ports: - 6379:6379 slave1: image: redis:7.2 container_name: redis-slave1 command: redis-server --replicaof master 6379 depends_on: - master slave2: image: redis:7.2 container_name: redis-slave2 command: redis-server --replicaof master 6379 depends_on: - master sentinel1: image: bitnami/redis-sentinel:latest container_name: redis-sentinel1 environment: - REDIS_MASTER_HOSTmaster - REDIS_MASTER_PORT_NUMBER6379 - REDIS_SENTINEL_QUORUM2 depends_on: - master部署后可以用docker-compose up -d启动再进到sentinel容器执行redis-cli -p 26379 sentinel masters查看Leader信息手动docker stop master模拟Master宕机观察哨兵是否把某个从节点提升为新的Master。这套环境对我学习主从切换的细节帮助非常大。6. 高并发场景实践分布式锁、缓存治理与序列化6.1 分布式锁的正确写法与常见坑在分布式系统里多个节点同时操作同一份资源光靠本地锁不够需要一个跨进程的互斥机制。Redis做分布式锁是最常见的方案之一但很多人写出来的锁是有问题的。最简单的错误版本是SETNX加锁、DEL释放SETNX lock:order 1 # 业务操作 DEL lock:order问题很明显如果业务执行过程中抛异常锁永远不会释放如果持有锁的线程卡了很久锁自动过期另一个线程拿到锁这时前一个线程执行完DEL把后者的锁也删了。这两种情况都会导致锁失效。正确写法需要考虑三点加锁要设置过期时间、解锁要校验持有者身份、身份用唯一随机值。加锁SET lock:order 123456789 NX PX 30000NX表示不存在时才设置PX表示过期时间30秒。唯一值123456789是当前线程生成的UUID或雪花ID。解锁要用Lua脚本保证原子性if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end先比较再删除两步操作必须原子否则还是有先GET后DEL之间的竞争窗口。还有一个事锁的过期时间怎么定如果业务执行平均耗时2秒但偶尔慢到10秒过期时间设多少都会尴尬。这时候可以用Redisson的看门狗机制它会在锁过期前自动续期业务执行完显式释放锁就不会出错。它的原理是后台定时任务每隔10秒检查一下如果锁还在就把TTL重置到30秒。不只在Java里能用Redisson也支持其他语言但注意别自己重复造轮子轮子容易造歪。6.2 缓存穿透、击穿、雪崩的治理方案这三个问题几乎是面试必问也是线上必须处理的。缓存穿透是查询一个根本不存在的Key缓存和数据库都没有每次请求都打到数据库。解决思路缓存空值查不到数据时把空值也缓存起来设置短TTL比如60秒。布隆过滤器启动时把所有合法Key加载到布隆过滤器查询前先过滤不存在直接返回。布隆过滤器有误判率但能拦住绝大部分非法Key代价是多维护一份过滤器数据。Redis 4.0后可以用RedisBloom模块直接操作BF.ADD、BF.EXISTS。缓存击穿是指某个热点Key过期瞬间大量请求冲进数据库。解决思路互斥锁重建缓存时先拿锁拿到锁的线程去查库回填缓存其他线程等待或直接返回旧缓存。逻辑过期缓存里存一个过期时间戳读到时发现超时先返回旧数据再异步重建缓存。这个方案的优点是不会阻塞请求缺点是有短暂的数据不一致。缓存雪崩是大批量Key同时过期或者Redis整体不可用导致大量请求涌入数据库。解决思路过期时间加随机值比如基础TTL上加0到300秒的随机数打散过期时间。缓存集群高可用用哨兵或Cluster避免单点。数据库层做限流降级比如把热点读请求打到备用缓存或静态数据上。我见过最经典的雪崩案例运营后台一次性上架数千个商品所有商品缓存都设置了相同的过期时间结果凌晨全部一起过期数据库QPS瞬间暴涨。后来在写入缓存时做随机过期时间数据库压力立刻降下来了。6.3 序列化方案选择为什么Jdk序列化不推荐Java生态里用Redis存对象序列化方案直接影响内存占用和兼容性。默认的Jdk序列化比如GenericJackson2JsonRedisSerializer或JdkSerializationRedisSerializer有一个致命问题序列化后的内容非常大而且带有类结构信息兼容性差升级类名或字段后反序列化容易炸。主流方案是JSON序列化比如Fastjson2、Jackson把对象转成JSON字符串再存。人可读、体积小、跨语言。还有更极致的方案是用Protobuf或Kryo性能好、体积更小但代价是调试不方便、依赖Schema。我的建议是绝大多数业务用JSON序列化就够了别为了那一点点性能优化牺牲可读性。踩过一次坑的人都会认同线上排查数据时打开可视化工具看到一堆二进制乱码和看到一段清晰JSON体验天差地别。如果用了Spring Data Redis注意配置RedisTemplate时不要使用默认的Jdk序列化器改成StringRedisSerializer配合Jackson能少很多心病Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; }6.4 缓存治理实战一致性、监控与运维缓存和数据库的一致性是缓存治理里最复杂的问题。先记住一个结论强一致很难绝大多数业务只需要“最终一致”。写操作的一致性方案业界比较流行的是Cache Aside Pattern读请求先读缓存缓存没有就读数据库回填缓存。写请求先更新数据库再删除缓存。为什么是删除而不是更新缓存因为更新缓存有并发时序问题线程A和线程B同时写A先写完库又写缓存B后写完库又写缓存最后缓存里可能是旧值取决于写缓存顺序。删除缓存则更安全下一次读请求会自然触发缓存重建。但Cache Aside也有一个准问题更新数据库和删除缓存之间如果删除失败怎么办这时候需要一个可靠的手段补删比如引入消息队列把删除Key的操作发到MQ消费者重试执行DEL。也可以先删缓存再更新库但那个方案的问题更多不推荐。真正操作的时候别忘了给缓存设置合理的TTL。一个永远不过期的缓存数据变更后只能等人工清风险极大。设置TTL是兜底方案即使某次删除缓存失败最坏情况下几小时后也能恢复一致。监控维度上我建议至少盯住这几个指标命中率hit rate、内存使用量、Key过期数量、阻塞耗时、主从复制延迟。命中率越低说明缓存效果越差数据库压力越大。内存使用量增长过快要考虑是不是有Key没设TTL或者大Key堆积。主从复制延迟过高要考虑网络问题和big key写入导致的同步阻塞。7. 面试与扩展高频原理题和常见踩坑大全7.1 面试必背的Redis核心原理把面试里最高频的Redis题归拢一下每个用一两句话说透为什么Redis快内存访问、单线程避免锁竞争、IO多路复用epoll事件驱动、高效的数据结构设计SDS、跳表、quicklist。Redis单线程还能处理高并发Redis的瓶颈通常在IO网络和内存不在CPU单线程反而避免了线程切换和锁开销。注意Redis 6.0引入了多线程IO但命令执行仍是单线程。Redis事务理解用MULTI/EXEC将多条命令打包执行不支持回滚不保证原子性回滚只保证连续执行不被插入。缓存和数据库一致性问题Cache Aside模式更新数据库后删缓存用消息队列兜底重试。哨兵和Cluster的区别哨兵解决高可用Cluster解决扩展性和高可用并存。Redis分布式锁怎么实现SET NX PX加锁Lua脚本解锁看门狗续期。为什么用跳表实现ZSet不用平衡树跳表实现简单、范围查询方便内存效率也可以接受且在并发环境下调整指针结构更容易。7.2 日志排查从启动日志到慢查询的完整链路Redis日志的level有debug、verbose、notice、warning四个级别生产一般设notice或warning避免日志刷屏。配置文件里loglevel notice logfile /var/log/redis/redis.log启动后先看日志确认是否加载了正确的配置文件tail -f /var/log/redis/redis.log常见的日志关键字有Ready to accept connections tcp启动成功可以接受连接。Cant open the log file日志目录不存在或无权限。# Background saving terminated by signalRDB导出失败检查磁盘空间。MASTER - REPLICA sync started主从同步开始。Connection reset by peer客户端异常断开可能是超时或网络问题。慢查询日志属于运行时排查工具不要和系统日志混为一谈。SLOWLOG GET 20能看到最近20条慢命令字段分别记录id、时间戳、执行时长、命令内容。一个我常用的排查套路是先看慢查询有没有O(N)命令再看bigkeys有没有超大Key然后用redis-cli monitor观察实时命令流量注意monitor在高流量下非常消耗性能只能在低峰期短时间开启。7.3 Docker一键搭建Redis全部学习环境最后把整个学习环境用Docker方案统一收拢一下避免每次换机器重装环境浪费时间。一个Compose文件搞定主从、哨兵、集群需要的所有节点已经在上文分享过。如果只想快速起一个带密码的Redis一条命令docker run -d --name redis-learn -p 6379:6379 \ redis:7.2 redis-server --requirepass learn123 --appendonly yes连接的时候用redis-cli -a learn123验证。想要可视化客户端连远端记住默认没有密码如果redis.conf里没配置requirepassRedis允许无密码访问生产环境下一定要设置强密码并限制bind地址。还有一个值得养成的习惯学习阶段每改一个配置用redis-cli CONFIG GET验证是否生效。比如redis-cli -a learn123 CONFIG GET maxmemory redis-cli -a learn123 CONFIG GET appendonlyCONFIG SET可以热修改一些参数但像appendonly这种需要重启的配置改完记得持久化到配置文件。7.4 一个实战小项目用Redis构建简单的社交网站学习Redis最好的方式是拿一个完整的业务场景把各种特性串起来。我建议新人都做一遍“用Redis构建简单社交网站”这个练习。它覆盖面足够广又不需要真实建站只要把核心接口逻辑想清楚、用命令验证数据变化就够了。整个练习可以拆成这些模块用户关系用Set存储好友关系SADD friends:1001 1002SINTER算共同好友。用户时间线Timeline用List或ZSet存储发帖ID发帖时LPUSH到个人时间线关注的人发帖时也推送到自己的聚合时间线里。点赞与热度排行文章的点赞数为String自增热度用ZSet按score排序定时任务把点赞数同步进去。站内信/通知List做消息队列消费者BRPOP读取。在线状态统计Bitmap记录用户当日在线日活统计一天一个bitmap按天做位运算。这个项目做完等于把String、Hash、List、Set、ZSet、Bitmap、事务、Lua脚本、过期策略全练了一遍。再配合主从哨兵环境理解高可用设计整个Redis知识体系就闭合了。我在实际练习过程中最大的收获是命令本身不难背难的是判断“什么场景用什么结构”。比如好友关系用Set不用List是因为要去重和交集运算时间线用ZSet不用List是因为要支持按时间分页和去重在线状态用Bitmap不用Set是因为1亿用户每日在线状态只占十几MB内存Set会多消耗几个数量级。我自己带新人做这个练习时通常还会加一个要求给每个模块写一遍Lua脚本版本把多步操作原子化。比如点赞操作要同时更新文章点赞数和个人通知计数用一条脚本就避免了中间状态被读到。能独立完成这一套Redis基本就过关了。8. 写在最后的实操体会学Redis最好的方式就是动手。别再只看命令文档了先把Docker拉起来把一个实例跑起来用客户端连上亲手把String、Hash、List、Set、ZSet挨个存一遍取一遍再把主从和哨兵环境搭起来手动杀掉Master看它怎么切换。这些动作做完一遍你对Redis的理解会超过很多人。我在实际使用中最深的感触是Redis的能力不在单点而在设计。单点是缓存加主从是容灾加哨兵是自动切换加集群是水平扩展加Lua是原子操作加合理淘汰策略是稳定可控。一层层叠上去它就能支撑非常大的业务量。你在学习时如果心里一直有这条主线就不会越学越乱。最后分享一个小技巧给自己维护一份Redis学习笔记不用写得多华丽但每学一个命令或原理都配上自己在实验里跑过的输出和踩过的坑。这份笔记后续会成为你排查线上问题最快的索引。等你回头看时会发现所有的坑和心得都是最有价值的资产。