尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redis从入门到实战:缓存、持久化、分布式锁与高可用架构全解析

Redis从入门到实战:缓存、持久化、分布式锁与高可用架构全解析 1. 从零开始认识Redis它到底是什么能解决什么问题先聊一个很多新手都会问的问题Redis到底是什么网上资料一大堆但大部分要么太学术要么太零散。我用最简单的一句话概括Redis是一个基于内存的键值对存储系统你可以把它理解成一个跑在服务器上的超级字典——你给它一个key它立刻还给你对应的value速度极快快到什么程度呢官方数据是单机可以支撑十万甚至更高的QPS每秒查询次数。我第一次接触Redis是在做一个高并发的商品秒杀项目数据库扛不住瞬间流量服务器CPU直接打满页面响应时间从几十毫秒飙升到好几秒。后来把热点数据接入Redis做缓存瞬间压力就降下来了。从那以后我就意识到Redis不是“用不用”的问题而是“怎么用好”的问题。它到底能做什么核心场景我总结成四类缓存把热点数据、高频查询结果放在内存里减少数据库压力这是最普遍的使用方式。分布式锁多个服务实例同时操作同一份资源时用Redis保证只有一个实例能拿到执行权避免并发冲突。消息队列利用它的List结构或者Stream类型做简单的消息发布订阅很多轻量级场景不必引入重型MQ。计数器/排行榜INCR、ZADD这些命令天然适合做点赞数、播放量、实时排行榜一类的统计功能。这篇文章面向三类读者刚接触Redis、急着做技术选型和架构设计的人已经在项目里用了Redis但停留在“存进去、取出来”层面的开发人员以及准备面试、需要系统梳理Redis知识体系的人。我会结合自己踩过的坑和实战代码把安装、数据类型、持久化、主从哨兵、集群、分布式锁、可视化工具这些内容从头到尾捋一遍。2. 安装Redis的完整实践Windows、Linux和Docker三种路线2.1 Windows下安装Redis的两种正确姿势Redis官方并不原生支持Windows官网下载页面通常只提供Linux版本。但国内很多开发者本地开发机就是Windows所以这里有两种常见的解决路线。第一种使用微软维护的Windows移植版。这个方案的问题是版本较老官方已停止维护功能上缺少Redis 6.0以后的一些新特性比如多线程IO、ACL权限控制。如果你只是本地写写简单Demo拿来做测试上面能找到zip压缩包解压后直接运行redis-server.exe即可。但要注意这种老版本在Windows上兼容性一般我第一次用时遇到过程序崩溃后来就转向了第二种方案。第二种用WSL2装Linux子系统然后在里面安装官方正版Redis。这种方式更接近生产环境我个人比较推荐。具体流程是先打开Windows的“适用于Linux的Windows子系统”功能然后从微软商店安装Ubuntu进入Ubuntu终端后执行sudo apt update sudo apt install redis-server安装完成后先修改配置再启动sudo vim /etc/redis/redis.conf # 将 supervised no 改为 supervised systemd # 将 bind 127.0.0.1 ::1 保留本地访问足够 sudo systemctl start redis-server redis-cli ping # 如果返回 PONG说明安装成功第三种是Windows上使用Docker Desktop跑Redis镜像这个放到下一节一起讲因为它和Linux版的Docker命令完全一致。2.2 Docker镜像安装Redis最省心的方式我在多台服务器上部署Redis的经验告诉我用Docker安装Redis是所有方式中最省心、最不易出错的。不管你是Ubuntu、CentOS还是Windows只要装了Docker命令基本统一。拉取官方镜像docker pull redis:7.0运行一个带持久化和密码的实例docker run -d \ --name redis-server \ -p 6379:6379 \ -v /data/redis/conf/redis.conf:/etc/redis/redis.conf \ -v /data/redis/data:/data \ redis:7.0 redis-server /etc/redis/redis.conf解释一下这几个参数的含义-d表示后台运行--name给容器命名-p把宿主机的6379端口映射到容器的6379端口-v做目录挂载。这里有个关键细节容器内的Redis默认是没有任何持久化配置的如果直接docker run redis容器删掉数据就全丢了。所以生产环境一定要挂载配置文件和data目录。如果你是快速测试不想写配置文件可以这样简化docker run -d --name redis-test -p 6379:6379 redis:7.0然后进入容器查看日志docker logs -f redis-test注意docker run时如果没加--restartalways服务器重启后容器不会自动拉起生产环境建议加上。2.3 Linux环境CentOS/Ubuntu源码编译安装生产服务器上如果不想用Docker想直接装原生Redis常见有两种方式包管理器安装和源码编译安装。Ubuntu/Debian直接apt install redis-server就行但CentOS默认仓库里的Redis版本可能比较旧。这里我推荐源码编译安装因为可以精确控制版本。Redis 7.0版本的源码编译步骤如下wget https://download.redis.io/releases/redis-7.0.12.tar.gz tar -zxvf redis-7.0.12.tar.gz cd redis-7.0.12 make -j4 make installmake install默认会把可执行文件放到/usr/local/bin目录下包括redis-server、redis-cli、redis-sentinel等。编译完成后创建配置目录并拷贝配置mkdir -p /etc/redis cp redis.conf /etc/redis/然后修改几个关键配置项daemonize yes requirepass yourpassword appendonly yes启动并验证redis-server /etc/redis/redis.conf redis-cli -a yourpassword ping提示源码编译时建议先安装gcc和makeCentOS执行yum install -y gcc makeUbuntu执行apt install -y build-essential否则make会报错。3. Redis核心数据类型详解不只是存String3.1 五种基础数据类型的使用场景与实操命令Redis的数据类型是它的灵魂面试必问实际开发中也最常踩坑。我按使用频率逐个讲清楚。String字符串类型这是最基础的类型可以存任意字符串包括数字和二进制数据。典型用途是缓存用户信息、商品详情、Session共享。命令包括SET、GET、INCR、DECR、EXPIRE。举个例子SET user:1001 {name:张三,age:18} EXPIRE user:1001 3600 GET user:1001 INCR page:viewHash哈希类型适合存对象一个key对应一个field-value映射表。比如用户信息如果用String存JSON改一个字段得整体重新写入用Hash的话可以单独更新某个字段。命令有HSET、HGET、HGETALL、HDEL。HSET user:1001 name 张三 age 18 city 北京 HGET user:1001 name HINCRBY user:1001 age 1List列表类型底层是双向链表支持从两头操作适合做消息队列、最新消息列表。常用命令有LPUSH、RPUSH、LPOP、RPOP、LRANGE。我用它做过一个简单的站内信功能新消息LPUSH进去用户读取时LRANGE拉取。LPUSH message:1001 msg1 msg2 LRANGE message:1001 0 -1Set集合类型元素无序且不可重复非常适合做去重、共同好友、标签系统。命令有SADD、SMEMBERS、SINTER交集、SUNION并集。SADD user:1001:tags java redis python SADD user:1002:tags redis mysql SINTER user:1001:tags user:1002:tags # 返回交集即两个用户共同关注的标签ZSet有序集合在Set的基础上增加了score打分字段Redis会按score自动排序。典型应用是排行榜、延时队列。命令有ZADD、ZRANGE、ZREVRANGE其中ZREVRANGE按分数从高到低取排名。ZADD leaderboard 89 player1 95 player2 78 player3 ZREVRANGE leaderboard 0 -1 WITHSCORES3.2 Java中RedisTemplate操作数据类型的坑与正确姿势实际Java项目里我们不会直接敲Redis命令而是用Spring Data Redis封装的RedisTemplate。这里有一个经典大坑默认序列化方式导致存入Redis的key和value带着奇怪的前缀。我第一次用RedisTemplate存数据打开可视化工具一看key变成了一串\xac\xed\x00\x05t\x00的乱码。这是因为默认用的JDK序列化将对象转成了二进制格式。解决办法是自定义一个RedisTemplate把key的序列化器改成StringRedisSerializervalue则根据需要选择GenericJackson2JsonRedisSerializer或自定义的Jackson序列化器。配置代码如下Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer keySerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); return template; } }搞清楚序列化问题之后还有一个使用上的坑很多人不知道RedisTemplate和StringRedisTemplate是两回事。StringRedisTemplate默认所有key和value都按字符串处理适合纯字符串场景。而自定义的RedisTemplateString, Object可以存对象。两者序列化方案不一致混用会导致数据读出乱码实际项目中要统一。3.3 踩坑实录increment()报错not integer or out of range的排查过程这是我接手一个项目时真实遇到的问题。线上日志里一直在报Redis执行redisTemplate.opsForValue().increment(key)时出现异常提示value不是整数或超出范围。刚开始我没当回事以为是某个脏数据把这个key写入了非数字字符串。但后来发现告警频率越来越高才意识到问题的严重性。排查思路是这样一步步展开的第一步确认异常来自哪一行代码。用分布式ID生成器的场景有一个方法是用increment()实现每天自增IDkey的格式是biz:serial:20240601。这一步基本可以定位到业务含义。第二步连接Redis查看这个key当前的值。用redis-cli执行GET biz:serial:20240601发现返回结果是一个很长的JSON字符串里面还有一个用户昵称字段。看到这个我基本就明白了这个key在别的地方被复用了其他地方向同一个key写入了对象类型的值序列化后是一串字符串导致increment()执行时报错。第三步检查代码中所有使用到这个key的位置。最后发现是一个缓存用户资料的逻辑用了同样的key前缀但业务含义完全不同。解决方法是把各业务线的key前缀彻底分离并加上了项目名和业务模块名。我习惯的key命名格式是项目名:业务模块:业务标识:唯一ID比如user-center:profile:1001和user-center:serial:20240601。这个问题的根因其实是key命名不规范。Redis中所有类型的key共用一个全局命名空间不同业务之间如果前缀没有隔离出现这种冲突是必然的。排查这个问题的过程中我也养成了一个习惯在代码注释里写明每个key的数据类型和用途同时每次打印日志时尽量带上key。经验Redis的increment()只能操作整数值。如果value是浮点数可以改用incrbyfloat如果value里带引号或空格执行也会失败。防患于未然的最佳做法是SET key 0初始化确保value是整数类型。4. Redis持久化机制AOF和RDB怎么选4.1 RDB快照的原理与配置Redis是内存数据库数据都在内存里如果机器断电或进程崩溃内存中的数据全部丢失。持久化就是为了解决这个问题。Redis提供两种持久化方案RDBRedis DataBase和AOFAppend Only File。RDB的原理我打一个比方它就是把当前内存中所有数据拍一张“快照”写到磁盘上。恢复的时候直接把这张快照加载回内存启动速度快文件小适合做备份和灾备。RDB的触发方式有三种手动执行SAVE命令阻塞或BGSAVE命令后台fork子进程执行不阻塞配置文件中设置自动快照条件比如save 900 1表示900秒内至少1次修改就自动触发关闭Redis时自动保存常见配置项如下save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes dbfilename dump.rdb dir /var/lib/redis需要注意RDB的缺点是数据丢失窗口较大。假如你配置了60秒保存一次那么在最后一次保存到故障之间这60秒内修改的数据重启后就没了。对数据一致性要求高的场景单靠RDB不够。4.2 AOF日志的原理与三种写回策略AOF的思路和RDB完全不同它记录的是Redis收到的每一条写命令追加到日志文件末尾。恢复数据时把文件里的命令重放一遍即可。这就好比记账明细RDB只记录最终余额AOF记下每一笔流水。AOF的核心配置项是appendfsync它决定日志刷盘的频率有三种策略策略行为优点缺点always每次写命令都同步刷盘数据最安全最多丢1条命令性能最差频繁IOeverysec每秒刷一次盘性能和安全性平衡极端情况下丢1秒数据no由操作系统决定刷盘时机性能最好数据丢失不可控生产环境我一般推荐everysec这也是Redis默认配置。它在性能和安全性之间取得平衡绝大多数业务场景都能接受丢1秒以内的数据。AOF还有压缩重写机制BGREWRITEAOF它会根据当前数据集生成最精简的写命令集合避免AOF文件无限膨胀。比如你对同一个key执行了100次INCR重写后只会记录一条SET key 100。4.3 混合持久化生产环境的最优解Redis 4.0以后提供了混合持久化方案aof-use-rdb-preamble yes。它的思路是使用AOF日志作为主持久化方式在AOF重写时先写入一个RDB格式的快照作为文件头部再继续追加后续的写命令。这套方案的好处是RDB格式加载速度快AOF命令保证数据完整。宕机恢复时先加载RDB快照再重放增量命令兼顾了启动速度和数据安全性。我在生产环境基本都采用这个配置。我的配置文件核心片段供参考appendonly yes appendfilename appendonly.aof appendfsync everysec aof-use-rdb-preamble yes经验开启AOF之后如果你改过密码或者做数据迁移一定要先确认AOF文件完整。我遇到过AOF文件因为磁盘写满而损坏导致Redis启动失败的情况排查命令是redis-check-aof --fix appendonly.aof。5. Redis主从复制、哨兵与集群架构5.1 主从复制读写分离怎么配置单机Redis有一个致命问题一旦宕机所有依赖它的服务全部瘫痪。解决高可用的第一步是配置主从复制。主从复制的原理一句话主节点负责写从节点负责读主节点把写操作同步到从节点。这样就实现了读写分离和数据冗余。配置从节点非常灵活可以在配置文件里写replicaof 192.168.1.100 6379 replica-read-only yes也可以在命令行动态执行redis-cli REPLICAOF 192.168.1.100 6379配置完成后用INFO replication确认主从状态。如果看到role:slave且master_link_status:up说明同步正常。在实际项目中我遇到过主从数据延迟的问题。原因是Redis的复制默认是异步的主节点执行写命令后立即返回然后再异步同步给从节点。如果主从之间网络延迟大或者从节点执行同步命令较慢从节点读到的数据可能是旧的。对一致性要求高的读操作建议直接读主节点从节点只承担不敏感数据的查询。5.2 哨兵模式自动故障转移的关键主从复制解决了数据冗余但解决不了“主节点挂了怎么办”的问题。如果主节点宕机整个系统还是无法写入。哨兵Sentinel就是为此设计的它会持续监控主从节点的健康状态当主节点不可用时自动从从节点中选举出一个新的主节点。用Docker快速搭建一套哨兵模式的完整流程# 创建目录 mkdir -p /data/redis/{master,slave1,slave2,sentinel1,sentinel2,sentinel3}主节点/data/redis/master/redis.conf配置port 6379 bind 0.0.0.0 protected-mode no daemonize yes appendonly yes从节点/data/redis/slave1/redis.conf配置port 6380 bind 0.0.0.0 protected-mode no daemonize yes replicaof 主节点IP 6379 appendonly yes另一个从节点配置类似端口改成6381。这里有个关键点从节点配置replicaof时要指向主节点的实际IP不能写127.0.0.1否则容器内访问会出问题。哨兵节点配置port 26379 daemonize yes protected-mode no sentinel monitor mymaster 主节点IP 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000三个哨兵的sentinel monitor配置尽量一致。其中最后的数字2表示“至少2个哨兵同意主节点不可用才触发故障转移”。如果哨兵数量少于3个这个数字要相应调整否则永远不会触发切换。启动哨兵redis-sentinel /data/redis/sentinel/sentinel.conf验证哨兵是否正常工作可以手动kill掉主节点进程观察哨兵日志大约几秒后它应该会重新选举出新的主节点。经验配置文件里daemonize yes和Docker的-d参数可能会冲突在容器里运行建议改为daemonize no让Redis进程在前台运行由Docker管理。5.3 Redis Cluster集群数据分片与高可用当数据量超过单机内存就需要集群了。Redis Cluster通过数据分片Sharding把数据分散到多个节点上总共16384个哈希槽hash slot每个节点负责其中一部分槽。写入一个key时Redis计算CRC16(key) % 16384得到槽位再把数据路由到对应节点。搭建集群的要点# 假设有6个节点端口6379-6384 redis-cli --cluster create 192.168.1.101:6379 192.168.1.102:6379 \ 192.168.1.103:6379 192.168.1.104:6380 \ 192.168.1.105:6380 192.168.1.106:6380 \ --cluster-replicas 1--cluster-replicas 1表示每个主节点带一个从节点。这样一个3主3从的集群就创建好了。验证方式redis-cli -c -p 6379 CLUSTER INFO redis-cli -c -p 6379 CLUSTER NODES使用集群时有一个必须注意的坑多key操作需要所有key落在同一个槽里。比如MSET、MGET、事务、Lua脚本如果涉及的key分散在不同节点上Redis会直接报CROSSSLOT错误。解决办法是用{}指定hash tag# 这样的两个key会落在同一个槽 MSET user:{1001}:name 张三 user:{1001}:age 18总结下来单机是入门主从是备份哨兵是高可用集群是海量数据扩展。前面几步做扎实了再上集群才不容易出问题。6. Redis分布式锁从简单SETNX到Redisson6.1 分布式锁的经典实现与常见问题分布式锁解决的痛点很典型多个服务实例同时操作同一个共享资源比如扣库存、发优惠券本地语言层面的锁如synchronized、Lock只能锁住当前进程无法跨服务互斥。这时候就需要一个公共的第三方来协调Redis正是最常用的实现方案。最原始的实现是SETNX key value意思是“如果key不存在则设置”返回1表示获取锁成功返回0表示获取失败。释放锁时调用DEL key。但直接用SETNX有几个严重问题。第一个问题是死锁如果获取锁的进程在释放锁之前崩溃key永远不会被删除其他进程永远拿不到锁。解决办法是给key设置过期时间比如SET key value EX 30 NX这样即使进程崩溃锁也会在30秒后自动释放。第二个问题是误删锁假设A进程持有锁由于执行时间超过了过期时间锁自动释放了。此时B进程获取到同一个锁。等A执行完要释放锁时如果直接DEL就会把B持有的锁删掉。正确做法是在value里存一个唯一标识比如UUID释放锁之前先用GET判断value是否等于自己的唯一标识相等才删除。这个判断和删除必须保证原子性不能用两步操作最好用Lua脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end第三个问题是过期时间设置多久合理。设短了业务没执行完锁就过期了设长了如果进程崩溃其他进程等待太久。比较稳妥的方案是在获取锁之后启动一个后台线程定期给锁续期这个机制叫“看门狗”。但自己写这个逻辑非常容易出Bug所以实际工程中我更推荐直接用Redisson框架。6.2 Redisson实现分布式锁的实战案例Redisson是Redis官方推荐的Java客户端之一它把分布式锁封装成了类似ReentrantLock的使用方式且内置了看门狗续期机制默认过期时间30秒每10秒自动续期一次。先引入依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.22.1/version /dependency然后配置连接spring: redis: redisson: config: | singleServerConfig: address: redis://127.0.0.1:6379 password: yourpassword使用锁的代码Autowired private RedissonClient redissonClient; public void deductStock(String productId, int count) { RLock lock redissonClient.getLock(lock:product: productId); try { // 尝试获取锁最多等待5秒锁自动释放时间30秒看门狗会自动续期 boolean isLocked lock.tryLock(5, TimeUnit.SECONDS); if (!isLocked) { throw new RuntimeException(系统繁忙请稍后重试); } // 业务逻辑扣减库存 int stock getStockFromRedis(productId); if (stock count) { throw new RuntimeException(库存不足); } setStockToRedis(productId, stock - count); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }这段代码有几个细节值得注意tryLock(5, TimeUnit.SECONDS)表示最多等待5秒获取锁超过就直接放弃避免了线程无限阻塞。finally里释放锁之前要判断isHeldByCurrentThread()防止在等待获取锁失败后误释放别人的锁。Redisson还提供了公平锁getFairLock、读写锁getReadWriteLock以及RSemaphore信号量满足不同业务需求。没有特殊要求的话默认的getLock就够用了。7. Redis可视化工具推荐与对比7.1 Redis Desktop Manager和Another Redis Desktop Manager没有可视化工具之前我调试Redis全靠命令行key一多就很不直观。后来用上可视化工具管理、排查数据确实方便不少。先说经典工具Redis Desktop Manager简称RDM它是较早流行的Redis图形化客户端支持Windows、macOS、Linux。连接Redis时需要填写host、port、auth信息支持SSH隧道连接远程服务器。但RDM在2022年之后变成了商业软件免费版功能受限。社区随之推出了Another Redis Desktop Manager简称ARDMUI风格清爽安装包体积小支持key的模糊搜索、查看TTL、逐行编辑JSON数据还支持多标签页连接多个Redis实例。我现在的主力工具就是ARDM跨平台体验不错GitHub上开源可以直接下载。这两个工具的适用场景有区别。如果你只需要偶尔看看key、清空某个库、手动执行几条命令ARDM免费版完全够用。如果团队里有人已经有了RDM的授权用它的专业SSH通道管理线上服务器也很顺手。选型建议是团队统一避免有人用命令行有人用不同工具排查问题时协作效率更高。7.2 通过Docker搭建Web版可视化工具有些生产环境不允许开发人员直接连Redis端口这时候可以部署一个Web版的可视化工具。我常用的方案是redisinsight这是Redis官方出的可视化界面功能最强支持查看内存分析、慢日志、命令监控、CRDT。用Docker一键启动docker run -d \ --name redis-insight \ -p 8001:8001 \ -v redisinsight:/db \ redis/redisinsight:latest启动后浏览器访问http://服务器IP:8001界面里就可以添加Redis连接填写IP、端口和密码即可在线管理数据。RedisInsight对Redis 6.0的支持非常完善可以查看每个key的内存占用、执行命令的耗时分析做性能调优时很实用。7.3 快速查看Redis日志的几种方式排查Redis问题日志是第一步。日志的位置取决于你的启动方式和挂载路径。直接运行redis-server时日志默认输出到stdout标准输出。如果配置了logfile /var/log/redis/redis.log则写入指定文件。用Docker部署时可以用docker logs redis-server查看容器标准输出日志。如果挂载了配置文件并指定了logfile日志会写到宿主机挂载目录中。查看正在执行的命令的耗时可以通过慢日志redis-cli SLOWLOG GET 10 redis-cli CONFIG SET slowlog-log-slower-than 10000第一条命令获取最近10条慢日志第二条命令把慢日志阈值设置为10毫秒。生产环境我会把阈值设置成20毫秒只关注明显拖慢系统的命令。通过慢日志配合MONITOR命令调试用生产慎用会拖垮性能基本能定位大多数性能问题。8. 高频Redis面试题与避坑清单8.1 和Redis相关的面试核心考点最近几年面试基本绕不开Redis我结合自己做面试官的经验把考察频率高的几个问题整理成速查表考点核心回答要点易错点Redis为什么快纯内存操作、单线程避免上下文切换和锁竞争、IO多路复用、高效的数据结构不要只说“单线程”要提到Redis 6.0的多线程IO缓存穿透查询一个不存在的数据请求穿透到数据库解决方式缓存空值、布隆过滤器缓存击穿某个热点key过期瞬间大量请求同时打到数据库解决方式互斥锁、逻辑过期缓存雪崩大量key同时过期或Redis宕机导致数据库被打垮解决方式过期时间加随机值、集群高可用持久化区别RDB快照 vs AOF日志回答完区别后补充混合持久化数据淘汰策略LRU、LFU、随机淘汰、不淘汰八种策略要能说清楚结合场景分布式锁SETNX 过期时间 唯一标识 Lua脚本必须讲清楚误删锁和死锁问题主从复制原理全量复制、增量复制、复制积压缓冲区别漏了复制风暴的场景回答这些问题时单纯的背定义已经不够了。面试官更看重你能不能结合场景说明“为什么这么设计”。比如聊缓存穿透时你可以补一句“如果业务数据本身就可能不存在建议先用布隆过滤器过滤一定不存在的key再让真实查询落到数据库”这比背概念要加分。8.2 日常开发必须养成的Redis好习惯踩过的坑多了自然就总结出一套使用规范。这里分享几个我一直在执行的Redis使用习惯可以直接套用到自己项目里第一key命名必须统一带前缀。格式建议是项目名:业务模块:业务标识:唯一ID例如shop:cart:1001:SKU88321。好处是方便排查问题、按前缀批量清理、避免不同业务key冲突。第二给key设置过期时间。很多新手存数据后忘记设置TTL导致Redis内存只增不减最终触发内存淘汰策略影响线上数据。除了少数确实需要常驻的配置数据其他缓存数据都应该设置合理的TTL。第三大key和热key必须治理。大key指单个key的value过大比如超过几MB会导致Redis执行命令时阻塞其他请求热key指单个key被高频访问可能把某个分片打满。排查命令是redis-cli --bigkeys它会扫描大key并给出统计。第四禁止在生产环境用KEYS命令。KEYS *会阻塞Redis主线程数据量大时直接造成服务不可用要用SCAN命令代替游标式遍历不会阻塞。第五监控和告警要前置。建议给Redis加上内存使用率、连接数、命中率、主从同步延迟这几个监控指标并设置告警阈值。内存涨到80%就要告警而不是等内存满了才被动处理。8.3 Redis缓存治理的方向缓存治理不是一次性的事而是一个持续过程。我们项目里遇到过几次和缓存相关的线上事故具体表现都是数据库被打崩事后复盘基本都能归到缓存穿透、缓存击穿、缓存雪崩这三类。针对这三类问题我整理了一套完整的治理方案缓存穿透引入布隆过滤器拦截不存在的key查询结果为空时也缓存一个空值过期时间设短一点比如30秒避免脏key堆积缓存击穿热点数据可以考虑逻辑过期或者获取缓存时加互斥锁保证只有一个线程去查数据库重建缓存缓存雪崩过期时间统一加随机值比如baseTime Random.nextInt(300)Redis一定要部署成高可用集群主从备份做到位另外还有一个经常被忽视的细节尽量减少缓存和数据库的数据不一致窗口。常见的做法是“先更新数据库再删除缓存”而不是“先删缓存再更新数据库”。因为后者在更新数据库失败时缓存已经被删了下一次请求就会直接打到数据库。而前者即使删除缓存失败也只会短暂出现脏数据影响小得多。如果对一致性要求很高可以配合监听数据库的binlog异步更新缓存。9. 一些压箱底的经验和最后的建议写这篇文章的时候我特意把这些年用Redis踩过的大坑从头到尾回忆了一遍。每次线上出问题最后定位到根因时都会发现不是Redis本身不够稳定而是我们对它的理解不够深使用姿势不够规范。我最想强调的一点是不管你的项目是单体架构还是微服务架构Redis的key设计和序列化方案一定要在项目初期就定好规范。因为这个东西一但铺开后期改起来成本极高。我们项目组后来重新梳理缓存规范时光迁移线上存量key就花了好几周的时间中间还出过一次读不到数据的事故。前车之鉴提醒大家注意。如果你想尽快掌握Redis我的建议是先用Docker跑一个实例把命令行的增删改查摸一遍再装一个可视化客户端熟悉数据结构的变化过程。然后试着用Java或你熟悉的语言写一个RedisTemplate操作Demo把String、Hash、List、Set、ZSet都过一遍。接着搭建一套主从加哨兵的最小环境手动kill主节点观察哨兵怎么切换。等你亲手操作完这四步你对Redis的掌握程度就已经超过很多只背面试题的人了。如果在实际操作中遇到问题优先看redis.log日志日志里找不到线索就看SLOWLOG这两个工具能帮你解决90%的疑问。最后再分享一个小技巧生产服务器的Redis配置里我一般会在redis.conf末尾加上一段注释记录当前实例负责哪些业务、主从拓扑关系、负责人联系方式。下次接手的人或者出问题时的值班人员看到这段注释能少走很多弯路。
返回列表