1. 为什么会突然想聊RedisRedis这个玩意儿做后端开发的几乎天天见但真要问你它到底干了些什么、为什么快、怎么用才不踩坑很多人其实是一知半解的。我最早接触Redis是好几年前接手一个用户会话管理的模块那时候项目里就用了Redis存登录态当时只觉得它像个超高速的字典往里面塞数据、取数据都特别快别的就没深究。后来踩的坑多了才慢慢把Redis的脾气摸清楚。键过期了但内存没释放、缓存雪崩把数据库打挂、明明用了分布式锁却还是出了并发问题这些场景我都遇到过。所以我想把这两年积累的Redis使用经验整理成一篇完整的文章从安装部署、基础数据类型到实际项目中的缓存策略、分布式锁、内存排查一次性讲透。这篇文章适合刚接触Redis的新手快速上手也适合用过一段时间但总是“知其然不知其所以然”的同学查漏补缺。先回答一个最基础的问题Redis到底解决什么问题简单说它就是一台跑在内存里的键值数据库读写速度极快单线程模型下轻松扛住每秒几万甚至十几万的读写请求。它跟传统数据库最大的区别是数据主要存在内存里所以它适合存那些需要高频访问、对延迟极度敏感的数据比如热点新闻、用户登录态、排行榜、计数器而不适合当唯一的数据存储去存那些必须永久落盘的业务数据。2. 下载安装这件事其实没有你想的那么麻烦2.1 Windows下快速搭建Redis环境很多人一说装Redis就头疼因为Redis官方其实不提供Windows版本但日常开发又离不开。最省事的方案是使用微软维护的Windows移植版或者用tporadowski维护的开源Windows版本直接在GitHub上下载zip包解压就能用。也可以使用Memurai这种兼容Redis协议的Windows原生服务但个人开发直接下载zip包就够了。下载解压之后打开目录你会看到这些关键文件redis-server.exe # 服务端 redis-cli.exe # 命令行客户端 redis.windows.conf # 配置文件启动方式非常简单命令行进入该目录后执行redis-server.exe默认端口6379启动成功后你会看到那个经典的大Redis logo。想换端口或者开启持久化就用配置文件启动redis-server.exe redis.windows.conf我建议在Windows上做本地测试时直接使用WSL2跑Linux版Redis这样和生产环境一致避免Windows移植版在部分命令行为上跟Linux原生版本有细微差异带来的坑。WSL2里执行sudo apt install redis一行命令就能装好非常顺手。2.2 Linux服务器上的一键部署服务器部署Redis一般不用自己编译源码直接用系统包管理器就行以Ubuntu/Debian为例sudo apt update sudo apt install redis-server -y装完默认就启动了查看状态systemctl status redis-serverCentOS系则是sudo yum install redis -y sudo systemctl start redis如果包管理器版本太老或者你想要特定大版本的新特性推荐用官方提供的编译安装方式也就三步wget https://download.redis.io/releases/redis-7.0.15.tar.gz tar xzf redis-7.0.15.tar.gz cd redis-7.0.15 make编译完成后src目录下就是编译好的redis-server和redis-cli。注意编译前确保系统有gcc和make缺了先apt install build-essential补上。这里有个细节很多人忽略用包管理器装的Redis不同系统的配置文件位置不一样。Ubuntu默认在/etc/redis/redis.conf编译安装则在你解压目录下的redis.conf。修改配置之前一定确认清楚路径不然改了半天发现服务根本没加载你改的那个文件。3. 五种基础数据类型用好了就是半套架构3.1 String不止是存字符串那么简单String是Redis最基础的类型value最大512MB可以存字符串、整数、浮点数甚至序列化后的JSON。看起来简单但它的实际应用远超“存个值”这么简单。常用命令SET key value GET key SETEX key seconds value # 设置值并指定过期时间 SETNX key value # 键不存在时才设置分布式锁的基础 INCR key # 原子自增 INCRBY key increment # 按指定步长自增INCR这个命令值得多说一句它是原子操作也就是说多个客户端同时执行INCR也不会出现数据错乱。我做活动系统的时候就用INCR实现过商品秒杀库存扣减配合在事务里判断返回值是否超过库存上限比在数据库里做行锁快了不止一个量级。还有个经验不要把大对象直接塞进String里。比如把一个几兆字节的图片base64存进去虽然能存但每次读写这个键都会引起明显的阻塞因为Redis是单线程的处理一个大键要消耗较长时间会影响同一实例上其他所有键的读写。所以大对象要么分片存要么改成存文件路径而不是文件内容。3.2 Hash适合存“对象”Hash类型特别适合表示一个对象因为它本身是字段和值的映射。比如用户信息用String存就要把整个对象序列化成JSON要改某个字段就得把整个对象取出来反序列化再写回去用Hash存就灵活得多可以直接改某个字段。常用命令HSET user:1001 name 张三 age 30 HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age 1实际上我会推荐在存“需要频繁修改部分字段”的业务对象时使用Hash。但注意Hash也有它的坑当字段特别多或者某个字段的值特别大时底层编码会从ziplist变成hashtable内存占用会明显上升。合理控制单个Hash的字段数量别一个Hash里塞几千个字段。3.3 List就是那个可以用来做消息队列的结构List是双向链表可以从头部或尾部推入弹出数据因此非常适合做简单的消息队列、最新消息列表、关注时间线之类的场景。常用命令LPUSH queue task_001 RPOP queue LRANGE list 0 10 LLEN list在实际项目里我用List做过异步通知队列生产者LPUSH任务消费者RPOP处理。不过要提醒想用List做队列的人如果消费端挂了任务会堆积在队列里没有重试机制也没有消息确认。这个场景建议升级到Redis Stream或者直接用专业的消息队列中间件如RocketMQ、KafkaList做队列更适合那种“丢了也无所谓”的轻量任务。3.4 Set去重和集合运算的高手Set是无序、不重复的字符串集合它的核心价值在于集合运算交集、并集、差集。SADD tags:article:1001 java redis SINTER tags:java tags:redis # 既打了java标签又打了redis标签的文章ID SUNION tag1 tag2 # 两个标签下的所有文章ID SMEMBERS key # 取出所有成员做社交类功能时Set几乎是标配。比如共同关注、好友推荐、抽奖去重、标签筛选一个SINTERSTORE命令就能把结果存到新键里比自己在业务代码里做集合运算快很多。有个性能要点必须注意SMEMBERS命令会一次性返回集合所有成员当集合很大比如几万以上时这个命令会阻塞Redis。线上环境要遍历大集合时用SSCAN替代它的游标式遍历不会阻塞服务。3.5 ZSet排行榜功能的绝对王者ZSet是有序集合每个成员关联一个分数scoreRedis按分数从小到大排序。它的底层实现是跳跃表加哈希表读写性能都非常优秀。命令示例ZADD leaderboard 100 player_1 98 player_2 ZREVRANGE leaderboard 0 9 # 获取分数最高的前10名 ZSCORE leaderboard player_1 # 查看某个成员分数 ZRANK leaderboard player_1 # 查看成员排名 ZINCRBY leaderboard 5 player_1 # 给某个成员加分游戏排行榜这种高频场景就不说了我用ZSet做过一个非常实用的功能——延迟队列。思路是score存任务的执行时间戳定期用ZRANGEBYSCORE key -inf 当前时间戳取出到期的任务处理完再删除。这个方案在任务量不大时几千到几万非常好用比引入专业的延迟消息中间件轻量得多。4. 缓存使用的实战策略与坑4.1 缓存失效的三种经典问题缓存穿透指的是请求查询一个必然不存在的数据缓存里没有数据库里也没有每次请求都打到数据库。解决方案有三种第一种是缓存空值即使查不到也缓存一份短期的空结果第二种是布隆过滤器把所有可能的合法ID先过滤一遍不存在的直接挡掉第三种是对同一参数的并发请求做互斥只放一个请求去查数据库。缓存击穿指某个热点key在过期瞬间大量请求同时打到数据库。解决办法是给热点key设置逻辑过期时间或者使用互斥锁Redis分布式锁保证只有一个线程去重建缓存。缓存雪崩是大量key在同一时段集中过期或者Redis实例宕机导致大量请求打到数据库。预防手段包括过期时间设置随机偏移量避免集体失效多级缓存本地缓存RedisRedis高可用架构。我处理过一个比较典型的案例某个首页聚合接口的缓存过期时间全部设成整点前的同一时刻某一刻全部过期数据库瞬间被压垮。后来把所有key的过期时间加上随机30到60秒的偏移问题立竿见影地解决。4.2 缓存一致性是用Redis做缓存绕不开的坎先给结论在绝大多数业务中先更新数据库再删除缓存是性价比最高的方案。为什么不是更新缓存因为更新缓存的成本高且容易出现并发下的脏数据而删除缓存配合“懒加载”策略下次请求重新查库回填缓存简单可靠。这里有个细微的bug要注意先更新数据库后删除缓存如果删除缓存失败怎么办一种补救方案是把删除失败的键名发给消息队列由消费者重试删除更简单的做法是用阿里云或者其他Redis客户端自带的延迟双删功能。我们内部用一个轻量的方式删除缓存时如果返回失败重试三次仍然失败就打日志由定时任务扫表处理。虽然不够优雅但非常实用。还有一种思路叫“旁路缓存模式”读的时候先读缓存缓存没有就查库并回填写的时候直接写库并删除缓存。这个模式已经被大量实践验证过适合大多数项目。4.3 键过期了为什么内存占用还在涨这是很多新手问的问题我明明给key设置了EXPIRE为什么used_memory还是一直涨答案是Redis的过期键删除策略并不是实时的。Redis默认使用惰性删除加定期删除结合的方式访问这个键的时候才检查它是否过期过期了就删另外后台每100ms随机抽取一批设置了过期时间的键做检查删掉其中过期的。如果过期键长时间没被访问又没被随机抽到它会一直占据内存直到内存达到maxmemory触发淘汰策略。所以要养成主动控制的习惯页面运营产生的临时缓存数据依赖过期时间的同时也要监控INFO memory里的过期键总量。键空间特别大时可以执行SCAN配合TYPE命令找到大键然后用UNLINK异步删除避免用DEL阻塞线程。5. 持久化Redis重启了数据还在吗5.1 RDB和AOF一对好搭档Redis提供了两种持久化方式RDB快照和AOF追加日志。RDB把某一时刻的全量数据写入磁盘二进制文件恢复速度快适合做备份和灾难恢复但它会丢失最后一次快照之后的写入。AOF记录的是每一条写命令持久化实时性更高按配置可以做到每秒同步甚至每个命令同步但文件持续增长恢复时需要回放所有命令恢复速度慢。生产环境我建议两者都开启用RDB做冷备用AOF做数据恢复。这里有个很容易踩的坑如果服务器突然断电AOF文件损坏了怎么办。Redis自带了修复工具redis-check-aof执行redis-check-aof --fix appendonly.aof可以尝试修复但修复过程会截断末尾损坏的命令。所以重要的Redis数据我建议每天定时把RDB快照备份到对象存储或者异地机器单纯依赖本机磁盘非常不保险。5.2 持久化对性能的影响很少有人深入研究开启AOF后每个写命令都会追加到日志这个操作占用的主要是磁盘IO。appendfsync配置项有三个值always每次写都刷盘、everysec每秒刷一次、no交给操作系统。always最安全但性能最差everysec性能明显提升且最多丢一秒数据是官方推荐的生产配置。注意往这里写一个真实的调优经验如果Redis实例的写并发非常高并且磁盘是机械硬盘或者IO能力一般的云盘appendfsync everysec也可能出现短时阻塞。有一个规律是要保证Redis性能磁盘IO能力比CPU更重要。同样的配置放在SSD和机械硬盘上延迟能差出好几倍。6. 分布式锁的实现细节比想象中复杂6.1 一个能用的分布式锁长什么样基于Redis的分布式锁最常用的是SETNX配合锁过期时间标准写法SET lock_key unique_value NX PX 30000这句命令的含义是仅当lock_key不存在时设置它值用unique_value标识锁的持有者同时设置30秒过期时间防止死锁。释放锁时要先判断unique_value是否对得上防止自己的锁被别人误删。所以释放锁要用Lua脚本保证判断和删除的原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这里很多人会踩一个坑分布式锁的过期时间设太短业务还没执行完锁就自动释放了其他线程就进来了。我见过的情况是某次并发任务执行时间超过预期锁到期后另一个线程拿到锁两个线程同时操作共享资源数据错乱。解决办法一是合理设置较大的过期时间并加上看门狗续期机制二是尽量不要在持锁期间做长事务操作。6.2 Redlock与集群版锁的问题Redis官方也提出过Redlock算法本质是向多个独立Redis节点同时申请锁超过半数成功才算获得锁。这个算法在业界争议很大尤其在集群模式下它的安全性并非无懈可击。我的实际建议是如果业务对锁的正确性要求极高优先考虑ZooKeeper或者etcd这类强一致性的协调服务而不是守着Redis硬抗如果只是防止重复提交这种容忍极小概率失误的场景Redis分布式锁完全够用。7. 常见问题速查表问题现象常见原因排查命令/解决思路连接超时业务量涨了内存不够触发swap或客户端连接数太多INFO stats看total_connections_received调大timeout和maxclients内存暴涨过期键堆积、大key存在、数据未设置过期时间INFO memory、redis-cli --bigkeys、SCAN配合TYPE找大键延迟突然升高有大key执行耗时命令、fork进程做RDB快照、AOF重写SLOWLOG GET查看慢命令用UNLINK替换DEL缓存和数据库不一致更新数据库和删除缓存顺序不对或删除失败强制走“先更新库再删缓存”流程并补重试机制主从切换后丢数据主节点宕机时部分命令未同步到从节点调整min-replicas-to-write参数必要时启用WAIT命令启动了但无法写键实例处于只读状态常见于主从模式的从节点INFO replication查看role确认是否连错了节点排查Redis问题我建议先打开慢日志看一把SLOWLOG GET 20能列出最近执行的耗时命令那些耗时几百毫秒的命令就是你要找的大头。很多时候延迟上升不是Redis本身有问题而是一条大命令拖慢了整个实例。记住Redis是单线程的单条命令耗时越长所有请求排队的时间就越长。8. 内存优化与性能调优的实战心得内存管理是Redis使用中最容易被忽视又最值得花时间的部分。先说一个小参数maxmemory-policy。默认的内存淘汰策略是noeviction也就是说内存满了之后写命令直接报错。生产环境通常都改成allkeys-lru所有键按LRU淘汰或volatile-lru只淘汰设置了过期时间的键没设置过期时间的不会被动删除。这个选择直接影响业务的可靠性如果你有重要的、绝不希望被淘汰的键就用volatile-lru并给这些键设置-1的过期时间永不过期。另一个内存优化的思路是数据结构的编码选择。Redis为了节省内存对小数据量做了特殊编码比如小List用quicklist、小Hash用listpack等等。可以通过OBJECT ENCODING key查看某个key内部用了什么编码但正常情况下你不需要手动干预Redis会自动选择。真正能人工介入的优化点在于控制键的数量而不是键的编码。还有一个场景值得提醒如果一个业务使用Redis存了大量短期热点数据比如每个用户一个Hash存购物车几百万用户就有几百万个键。这种场景下键名本身占用的内存被很多人忽视。键名每多一个字符全体键加起来就是巨大的内存开销。我之前把一个模块的键名从module:user:info:userid:123456缩短成mu:u:123456把同数量级数据的整体内存占用降了接近15%虽然可读性变差一点但在内存有限的环境下是值得的取舍。说到性能调优有三个内核参数值得设置尤其是处理高并发时# 关闭TCP的延迟合并算法降低网络延迟对Redis这种毫秒级应用有效 echo 1 /proc/sys/net/ipv4/tcp_tw_reuse # 提高文件描述符上限 ulimit -n 102400 # 关闭内存swap倾向避免Redis内存被换入换出 sysctl vm.swappiness1不要小看客户端连接参数连接池大小是很多人一上来就填得很大的地方。其实Redis每秒能处理的命令总量是有限的连接池过大会导致大量连接在服务端排队反向拖慢响应。我见过一个项目开了200个Jedis连接打到一个单线程Redis实例上最后延迟高得离谱。合理做法是压测找出阈值连接池大小控制在CPU核数附近的数量级比较稳妥。9. Redis和数据库组合使用时的一个建议最后说一个我反复强调的建议Redis不是用来替换数据库的它是在数据库前面加了一道快速通道。任何数据先想清楚它的生命周期、一致性要求和丢失容忍度再决定要不要进Redis。缓存的数据允许偶尔丢、允许短暂不一致订单、余额这类数据则必须以数据库为准Redis最多只做热点加速。在我自己的项目里一条数据的存储流程通常是写入时以数据库为准事务提交成功后再操作Redis删除或更新缓存读取时优先从Redis取取不到再查库回填。这套流程可能不是最高级的但它简单、可解释、容易排查问题。从实用角度看Redis值得投入时间学习的地方不在背命令而在理解它的单线程模型、内存管理机制和网络模型。命令只是工具真正让你在项目中把Redis用好用稳的是对这些底层机制的理解。这就是为什么我建议每个人在部署Redis后先花半小时跑一遍redis-cli --stat看全量信息再压测一下单机吞吐量用数据感知一下这台内存数据库真实的上限在哪里。