ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Redis核心技术与实战:缓存原理、数据类型与高并发治理

Redis核心技术与实战:缓存原理、数据类型与高并发治理 1. 缓存到底是什么先搞清楚Redis为什么值得学1.1 一个让数据库喘口气的中间层很多刚开始接触后端开发的朋友都会遇到同一个困惑明明数据库已经能存数据了为什么还要在它前面再塞一个Redis直接查MySQL不行吗这个问题当年我也纠结了很久直到第一次在线上看到MySQL的CPU被打到100%才真正明白缓存存在的意义。你想想看一个电商系统的商品详情页同一件商品在一秒内可能被几万人同时打开。如果每个人都去数据库里查一遍数据库得重复干几万次同样的活。这些查询结果一模一样纯属浪费资源。缓存做的事情很简单把热点数据放到一个更快的地方下次查的时候先来这里找找到了就直接返回数据库连请求都收不到自然就轻松了。Redis就是一种基于内存的键值对存储系统它把数据放在内存里而不是磁盘上。内存的读写速度比磁盘快几个数量级所以Redis读写的延迟通常在亚毫秒级别。这也是为什么几乎所有高并发项目里都能看到它的身影——要么当业务缓存要么当分布式锁、排行榜、消息队列的底层载体。但要注意引入Redis不是为了炫技而是为了解决大量请求重复访问同一份数据这个具体问题。如果业务本身读写量很小数据库完全扛得住硬上缓存只会增加维护成本。判断标准其实很朴素先问一句这个数据被读的次数远大于被写的次数吗如果是缓存就是合适的如果不是先别急着上。1.2 内存、单线程与IO多路复用Redis快在哪里Redis快第一靠内存第二靠设计。你可能会觉得内存数据库那么多为什么Redis成了事实标准这里值得聊一聊它的底层逻辑。数据存在内存里省去了磁盘寻址和机械运动的时间这当然是基础。但更关键的是Redis采用了单线程模型——主线程一次只处理一个命令不需要在多个线程之间切换上下文。很多人一听单线程就以为是性能瓶颈恰恰相反Redis的核心瓶颈从来不是CPU而是网络IO和内存大小。单线程避免了多线程并发访问共享数据的锁竞争反而让它在绝大多数场景下跑得更稳。再配合IO多路复用机制Redis可以在一个线程里同时监听成千上万个网络连接。用生活里的例子打比方传统的阻塞式IO就像银行只在每个窗口配一个柜员来一个客户就锁住一个窗口而IO多路复用像一个大堂经理他能同时留意所有排队的客户谁喊我要办业务他就过去处理谁。Redis就是这个高效的大堂经理。解决了单线程模型的困惑你就能理解Redis很多设计上的怪癖它执行的命令都是原子的不需要额外加锁它也严格禁止耗时操作比如在Redis里遍历几十万Key的KEYS命令因为一旦有命令耗时太长后面的所有命令都要排队等着整个服务就像卡住一样。1.3 哪些场景适合用Redis哪些别硬上学习Redis不能只学命令更得学会判断什么场景该用、什么场景不该用。这里我结合自己踩过的坑把典型场景分个类。适合用Redis的场景核心都指向读多写少共享热点数据缓存登录会话、用户资料、商品详情、配置信息。计数器点赞数、浏览量、库存扣减利用INCR命令的原子性。排行榜用ZSet按分数排序输出Top N。分布式锁多台机器抢同一份资源时借助Redis实现互斥。简单队列List的LPUSHBRPOP组合做异步解耦。不太适合的场景也有不少。比如复杂的关系查询、多表关联聚合这些交给关系型数据库更合适再比如数据量几十GB以上、又要求全部放进内存的成本会非常难看这时候可能得考虑冷热分离或者换存储方案。还有强事务、需要回滚的金融级业务Redis的机制决定它不是干这个的硬要用会把自己坑得很惨。一句话总结Redis是缓存和协同工具不是数据库的替代品。带着这个认知去学后面所有的命令和实操才会有方向感。2. 环境搭建不劝退最常见的安装方式和启动排错2.1 Windows、macOS、Linux的安装路线很多新手学Redis第一关就卡在安装上因为Redis官方其实并不支持Windows。Windows上的版本大多是开源社区移植的或者微软之前维护的旧分支。如果你只是本地学习在Windows上可以用以下两种方式之一一种方式是从Redis的GitHub仓库里找Windows移植版下载zip包直接解压双击redis-server.exe就能启动默认端口6379再开一个cmd窗口运行redis-cli.exe就能连上。另一种方式是用包管理工具比如winget install Redis或者用WSL跑Linux环境。如果在macOS上安装就顺畅得多直接一行命令的事brew install redis brew services start redisLinux服务器上最常见的是源码编译安装先去官网下载稳定版tar包然后依次执行make、make install。如果你用的是Ubuntu或者CentOS也可以直接用系统自带包管理器装比如apt install redis-server或者yum install redis。用包管理器装的好处是自动注册成系统服务省去手工维护进程的麻烦。装完后先别急着写代码做两件事第一跑一下redis-cli ping看到返回PONG说明服务正常第二了解一下Redis默认是无密码的如果端口暴露到公网分分钟会被扫到并被写入恶意数据后面我会专门讲怎么配密码和保护模式。2.2 Docker部署与redis.conf里值得动刀的配置如果你要部署到测试环境或生产环境我个人强烈推荐用Docker。它的好处是同一条命令在任何机器上跑出来的环境一模一样团队成员不用为我机器上怎么起不来扯皮。先看最基础的启动方式docker run -d --name redis \ -p 6379:6379 \ -v /opt/redis/data:/data \ -v /opt/redis/redis.conf:/etc/redis/redis.conf \ redis:7.2 redis-server /etc/redis/redis.conf这里把数据目录和配置文件都挂载到了宿主机避免容器一删数据全没。至于为什么要单独挂配置文件因为官方镜像默认配置太保守比如没有设置密码、没有限制内存上限直接裸奔风险很高。配置文件中我最建议你动手改的几个参数列在下面bind 0.0.0.0 # 允许外部访问生产环境建议改成内网IP protected-mode yes # 开启保护模式禁止无密码远程操作 requirepass 你的密码 # 设置访问密码 maxmemory 2gb # 限制最大内存防止Redis把服务器内存吃光 maxmemory-policy allkeys-lru # 内存满了之后按LRU策略淘汰旧数据 appendonly yes # 开启AOF持久化防止重启丢数据maxmemory和maxmemory-policy这两项特别重要。没有内存上限的Redis一旦缓存Key持续增长最终会把机器内存耗尽可能连运维都登不进机器。设置了淘汰策略之后内存满了它会自动清理不常用的Key系统才有自我保护能力。2.3 连上Redis可视化管理工具与连接配置命令行用久了还是想有个可视化界面看看当前有哪些Key、哪些Key占内存大。常用的工具我推荐两款Redis Desktop ManagerRDM和Another Redis Desktop Manager——后者是开源社区的轻量替代品界面清爽连接体验和收费版RDM差不太多。连接之前确保你的Redis已经设置了密码或者允许当前IP访问。如果连接不上优先检查几个地方服务器防火墙是否放行了6379端口Redis的bind配置是否允许对应IP访问如果开了Docker端口映射确认宿主机的端口真的映射到了容器里。有一种情况特别容易踩坑Redis部署在云服务器上安全组只放行了80端口Redis端口没放行结果程序能通、本地工具死活连不上。排查的时候不要只盯程序报错先把网络链路理清楚客户端 - 服务器安全组 - 宿主机端口 - Docker容器端口 - redis.conf的bind配置链路里任何一环有问题都不通。3. 五种核心数据类型的设计意图从命令到业务场景3.1 String最朴素的KV却是计数器的主力Redis最基础的数据类型就是String一个Key对应一个ValueValue最大能存512MB。它看起来简单但使用频率最高因为缓存、会话、验证码这些场景本质上都是存一段字符串再取出来。除了基础的SET和GETString类型里有几个命令值得单独留意SET key value EX 300 # 设置5分钟过期时间 SETNX lock 1 EX 10 # 只有Key不存在时才能设置成功 INCR page_view # 自增1原子操作 MSET a 1 b 2 c 3 # 一次性批量设置SETNX这个名字很关键它是后面做分布式锁的起点。INCR的原子性也要理解透彻它执行过程中不会被其他命令打断所以多个客户端并发执行INCR最终结果也不会错乱。很多面试官就爱问如何用Redis实现计数器其实答案就是这一条命令。实操时我建议给Key设计一套清晰的命名规则比如模块:业务:唯一标识。像user:info:12345、product:detail:67890这样一眼就能看出Key属于哪个业务排查问题时能省下大量时间。3.2 Hash与List对象存储和消息队列的原型Hash类型适合存对象它的结构像一个字段集合一个Key下面可以有很多field每个field都有自己的value。比如存用户信息不需要把整个对象序列化成一大串JSON而是把name、age、email分别作为field存进去。这样想更新某个字段时只需要HMSET改那一个field不用整条数据重写。List类型则是一个双向链表可以从左或右推入、弹出元素。它有两个非常经典的应用场景作为简单的消息队列生产者用LPUSH把消息推入队列消费者用BRPOP阻塞弹出。BRPOP在没有消息时会一直等待而不是轮询空转比RPOP更省CPU。作为时间线或最新列表比如用户发布动态后用LPUSH把动态ID塞进列表再配合LTRIM只保留最近100条实现一个轻量级的最近动态列表。很多新手容易把List当数组用用下标频繁LINDEX随机访问。这其实违背了它的设计初衷List的随机访问性能不高它是为头部尾部操作设计的。如果你需要按下标高频随机读取应该考虑别的结构。3.3 Set与ZSet去重、抽奖、排行榜一条龙Set是无序去重集合常用于标签、好友关系、点赞去重这类判断元素是否存在或两个集合之间求交集的场景。举例来说A用户点赞过文章ID100可以把like:100存成一个Set每次点赞前用SISMEMBER检查是否已经点过比查数据库高效得多。ZSet是Set的有序版本每个元素关联一个分数Redis按分数从低到高自动排序。它的用途非常出彩ZADD rank:2024 100 用户A ZADD rank:2024 98 用户B ZREVRANGE rank:2024 0 9 WITHSCORES # 取分数最高的前10名 ZSCORE rank:2024 用户A # 查看某个用户的分数排行榜、积分排名、延时队列都能用它实现。比如直播打赏榜每次有人刷礼物就ZINCRBY给对应主播加分然后ZREVRANGE取出前几名展示整个过程都是内存操作扛住高并发比数据库排序稳得多。ZSet底层用了跳表skip list和哈希表两种结构所以它既能按分数范围查找也能通过元素直接定位分数。这个有序能力是List给不了的选型时要想清楚你到底是要按插入顺序排列还是要按规则动态排序。3.4 过期策略与内存淘汰缓存不能只进不出缓存和普通存储最大的区别是数据会过期。Redis允许给每个Key设置过期时间到期后自动删除。设置过期时间可以显式用EXPIRE也可以在SET时带EX参数。Redis删除过期Key采用了两套机制配合一种叫惰性删除即访问到某个Key时才发现它过期了顺手删除另一种叫定期删除后台每隔一段时间抽查一批过期Key并清理。惰性删除保证了不到期就不占额外操作定期删除则防止那些一直没被访问的过期Key占着内存。你可能会问那如果过期Key还没来得及删内存又满了怎么办这就轮到maxmemory-policy出场了。常见的淘汰策略有策略含义适用场景allkeys-lru在所有Key中淘汰最久没被访问的大部分缓存场景最常用volatile-lru只在设置了过期时间的Key中淘汰LRU希望没有过期时间的Key永不被删allkeys-random随机淘汰任意Key访问分布非常均匀的场景noeviction内存满后拒绝写入返回错误不允许删数据的业务我个人的习惯是全库缓存场景用allkeys-lru既简单又有效如果有少量绝对不能丢的Key且没设过期时间才考虑volatile-lru。记住一个原则淘汰策略宁可激进也不能让Redis写不进去因为写入失败在生产环境往往会造成更严重的连锁故障。4. 在Java项目里跑通RedisSpring Boot集成与序列化排坑4.1 从客户端到Spring Data Redis的调用链路本地Redis跑通之后下一步就是让它和业务代码对接。Java生态里主流的选择是Spring Boot整合Spring Data Redis它封装了底层客户端让你用几个注解或者一个RedisTemplate就能操作缓存。底层连接Redis的客户端有两个主流实现Jedis和Lettuce。Spring Boot 2.x默认用Lettuce它是基于Netty的异步客户端连接复用能力更强适合高并发环境。Jedis则更直白每次操作都走独立的socket连接老项目里比较常见。引入依赖很简单在pom.xml里加上spring-boot-starter-data-redis就够了。然后在application.yml里配置连接信息spring: data: redis: host: 127.0.0.1 port: 6379 password: 你的密码 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0有几个连接池参数要解释一下max-active是最大活跃连接数理论上并发上限有关min-idle保持的最小空闲连接数太小了可能出现突发流量时频繁建连max-wait建议显式配置一个值默认-1表示无限等待在高并发下有可能把线程全堵在等待连接上。4.2 RedisTemplate序列化最常见的乱码噩梦我第一次用Spring Boot操作Redis时往缓存里写了个对象一查Redis里存的是一堆\xAC\xED\x00...开头的乱码吓得以为是数据坏了。后来才明白这根本不是乱码而是Java默认的JDK序列化结果人类当然看不懂。RedisTemplate默认使用JdkSerializationRedisSerializer做序列化它会把Java对象变成二进制字节流。这样做的问题有三个存储空间大序列化后很臃肿、效率低、可读性差——你在Redis客户端里完全没法直观看到存了什么。解决方法是手动指定序列化器。最常用的组合是Key用StringRedisSerializerValue用Jackson2JsonRedisSerializer这样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; }这段配置我很建议直接抄进项目里。还有一个更偏门的坑如果用JSON序列化Value对象里必须有无参构造方法否则反序列化时会直接报错。很多人踩了之后才发现加上无参构造就好了。4.3 缓存注解用起来很爽坑也藏得很深Spring Data Redis提供了三层封装底层是RedisTemplate上面是CacheManager再往上是Cacheable、CacheEvict、CachePut之类的注解。注解用起来确实方便一个注解就能缓存方法的返回值。Cacheable(value user, key #userId) public User getUserById(Long userId) { return userMapper.selectById(userId); }但注解用多了一定要留意几个问题。第一个坑是缓存穿透和空值问题。如果getUserById查到null注解默认不会缓存这个null结果每次请求都要查数据库。解决办法是让方法返回一个空对象占位或者直接抛异常让调用方提前处理。第二个坑是内部调用导致的注解失效。同一个类里的方法A调用方法B如果B上面标了Cacheable是失效的——因为Spring缓存是基于代理实现的内部方法调用走的是this而不是代理对象注解逻辑根本不会被触发。很多新手查了半天缓存不生效最后发现是这个原因。第三个坑和Spring三级缓存的概念容易混淆。网上常提的Spring三级缓存解决的是Bean循环依赖问题和Redis缓存完全是两码事。面试时如果聊到这里一定要把Bean对象缓存和业务数据缓存区分开否则真的会越说越乱。4.4 缓存一致性更新数据库后缓存怎么办写缓存容易难的是保证缓存和数据库数据一致。最常见的方案有三种先更新数据库再删除缓存。这是最推荐的做法因为即使删除缓存失败最坏的结果也只是多一次数据库请求把新数据缓存起来不会出现缓存里长期残留旧数据的问题。先删除缓存再更新数据库。这个顺序坑很大如果更新数据库失败缓存又已经删了下次请求发现缓存没有就会去数据库拿到旧数据并重新缓存造成数据不一致。先更新数据库再更新缓存。流程上可行但更新缓存本身有成本而且并发场景下两个线程交错更新容易把旧值覆盖正确答案。压垮缓存一致性的往往是并发。举个例子线程A先更新数据库为value1紧接着线程B更新数据库为value2并更新缓存为2但线程A网络抖动了一下缓存的更新操作反而在B之后执行把2覆盖成了1。这就是经典的后更新先落库问题。所以生产环境里我常用的策略是更新数据库 删除缓存并且删除缓存这一步最好做成可靠的重试机制——比如借助消息队列异步重试删除或者用延时双删。团队里如果实现了可以适当结合业务容忍度来选择。记住一个底线没有银弹最终一致性才是大多数业务的实际需求别为了绝对一致把自己绕进死胡同。5. 缓存穿透、击穿、雪崩三大事故的成因与治理方案5.1 穿透查了个不存在的数据也能把DB打挂缓存穿透是指请求的数据在缓存里没有在数据库里也没有每次请求都落到数据库。正常情况下缓存能把大部分查询挡在外面但如果有人恶意构造一批不存在的ID比如请求一个根本不存在的商品ID缓存永远没有数据库就被无限轰炸。判断穿透很简单看数据库连接的QPS是不是异常升高同时Redis里明明没这个Key请求却疯狂过来。最常见的手段是接口入参加校验不合法或明显不存在的ID直接拒绝。针对穿透业界有几种主流解法缓存空值查询结果为空时也往缓存里写一个短生命周期的空值占位比如过期时间设成60秒。缺点是空值很多时会占用内存。布隆过滤器把所有合法ID预先加入布隆过滤器请求来了先判断ID是否可能存在不存在的直接拦截。布隆过滤器允许可能存在的误判但绝不会错杀一定不存在的ID非常适合黑名单拦截。布隆过滤器的原理可以这样理解它用多个哈希函数把一个ID映射到位数组的多个位置全部命中时只能说可能见过但有一个位置为空就肯定没见过。代价是存在一定的误判率但误判只会让请求继续走不会拦掉合法请求。5.2 击穿热点Key在重建的瞬间被流量淹没击穿和穿透一字之差本质完全不同。击穿指的是某一个热点Key在过期瞬间大量并发请求同时发现缓存不在于是一起打到数据库去重建。比如某个明星突发新闻把一条详情数据顶上热搜正好这条数据的缓存过期了一瞬间几万人同时查库。系统表现数据库单Key相关的SQL QPS突然飙高而Redis里这个Key明明昨天还在正常命中。解决击穿的核心是同一时间只让一个请求去重建缓存其他请求要么等待要么拿到旧值。互斥锁是典型方案请求进来看不到缓存先尝试获取一个分布式锁比如SETNX lock_key只有拿到锁的请求才去查库并回写缓存其他请求稍作重试后再次读缓存。代价是多了锁竞争的开销但如果热点数据不常失效收益远大于成本。另一种方案是逻辑过期缓存里不设物理过期时间而是存一个该数据的过期时间戳。每次读取时判断逻辑时间是否过期过期了就让一个线程去异步刷新缓存当前请求先返回旧数据。好处是读请求永远不会被阻塞坏处是实现复杂度更高需要处理好旧数据短暂可接受的问题。5.3 雪崩大批Key同时失效后的连锁反应雪崩比击穿更可怕不是单个Key失效而是大量Key在同一时间段集体失效导致缓存命中率瞬间跌到谷底请求像洪水一样涌向数据库数据库扛不住就挂然后整个服务跟着挂。雪崩的常见成因有三个。第一大量Key设置了同一个过期时间比如零点统一过期第二Redis实例本身宕机所有缓存瞬间不可用第三缓存服务被重启时清空还没来得及回填就收到海量请求。治理雪崩的思路是多管齐下过期时间加随机值典型做法是在基础TTL上再加一个随机秒数比如300 random(0, 60)把集体失效分散开。缓存永不失效改为后台异步定时刷新热点数据。做多级缓存比如本地进程内缓存一层Redis再一层Redis宕机时本地缓存还能扛一阵子。给数据库设置连接池水位线和服务熔断底层扛不住时优先保命而不是硬撑。这里想多说一句雪崩治理不只是Redis侧的事网关限流、数据库连接池保护、服务降级全都得上。缓存只是第一道防线防线后面还得有第二道和第三道。5.4 治理方案对比布隆过滤器、互斥锁、逻辑过期三种方案选起来其实不困难核心取决于你的业务容忍度。先看一张对比表方案适用问题优点缺点缓存空值穿透实现简单空值也走缓存空值占内存可能放大存储量布隆过滤器穿透拦截效果彻底基本不占内存需要事先构建ID集合存在误判率互斥锁击穿保证只重建一次阻塞请求可能拖慢响应逻辑过期击穿请求不阻塞用户体验好实现复杂短暂读到旧数据结合我自己的项目经验穿透优先用参数校验缓存空值如果恶意流量非常严重再加布隆过滤器击穿优先用互斥锁因为热点Key失效的频率一般不高锁竞争的代价可以接受对缓存恢复速度要求极高的场景再用逻辑过期做异步重建。实际落地时很少只选一种方案。高并发系统通常是入口层拦截非法请求 缓存层防止击穿 异步补偿保证最终一致的组合拳而不是寄希望于某个单一方案解决问题。6. 从单机走向分布式主从复制、哨兵与分布式锁的落地建议6.1 主从复制与Docker部署主从集群单机Redis的容量和可用性都有上限。机器一挂整个缓存服务就没了这在高并发系统里是不可接受的。业界标准的起步方案是主从复制一个主节点负责写一个或多个从节点负责读主节点的数据会自动同步到从节点。主从复制的好处很直观读写分离降低单点压力主节点挂了还能让从节点顶上数据也有了一份实时备份。在Docker环境里部署一主一从的配置非常顺手version: 3 services: redis-master: image: redis:7.2 command: redis-server --appendonly yes ports: - 6379:6379 volumes: - ./master/data:/data redis-slave: image: redis:7.2 command: redis-server --slaveof redis-master 6379 --appendonly yes ports: - 6380:6379 volumes: - ./slave/data:/data depends_on: - redis-master启动后用docker compose up -d拉起来再进从节点执行INFO replication看到role:slave和master_link_status:up就说明复制关系建立成功。这里提醒一句--slaveof只是容器启动时指定的复制关系如果你以后改成哨兵模式这些静态配置都得重新梳理。6.2 哨兵让故障转移自动发生主从复制解决了数据冗余但没有解决主节点挂了谁来接管的问题。总不能让运维半夜爬起来手动切换主节点吧Redis官方给出的答案是哨兵Sentinel。哨兵是一组独立的Redis进程它监控主节点的健康状态一旦发现主节点客观下线会在从节点里选一个提升为新的主节点并通知其他从节点和客户端重新指向新的主节点。整个过程是自动的不需要人肉介入。部署哨兵时至少要三个以上实例才能避免脑裂——因为哨兵之间需要投票达成一致单个哨兵无法判断自己看到的状态是否真实。用Docker部署哨兵的坑主要是网络和配置sentinel monitor mymaster redis-master 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000解释一下mymaster是监控主节点的名字2表示至少两个哨兵同意主节点不可用才会触发故障转移down-after-milliseconds是判断主观下线的等待时间。故障转移耗时通常在几秒到十几秒之间这个窗口期写入会失败业务方要做好重试。我自己的建议是如果团队规模不大、Redis实例也不多哨兵模式完全够用等实例数量上到几十个再考虑集群模式不迟。不要一上来就追求最高级架构稳定优先。6.3 分布式锁别用setnx裸写这里有血的教训分布式锁是Redis高频面试题也是线上最容易出事故的地方。场景很典型多个服务实例同时处理同一个订单如果不加锁重复扣库存、重复发送消息就会发生。最基础的实现是SETNX加过期时间SET lock:order:1001 token_abc EX 30 NX拿到锁的执行业务业务结束后再删锁。但这里有个经典陷阱如果业务执行时间超过了锁的过期时间锁自动释放了另一个线程又抢到同一把锁第一个线程执行完如果直接DEL就把别人的锁删掉了。正确做法是在Value里放一个唯一标识删锁前先对比是不是自己的标识再执行删除。# 伪代码逻辑 if redis.get(lockKey) myToken: redis.del(lockKey)但即便这样仍然存在判断和删除不是原子操作的问题。所以生产环境我更推荐直接使用Redisson它把看门狗续期、原子释放锁、可重入这些细节全部封装好了。你不需要自己造轮子把实现细节交给成熟库比自己手写SETNX安全得多。我还真见过有团队手写的锁在压测下出过超时释放锁后重复执行的事故教训深刻。6.4 最后说几句心里话Redis这条路写到这里已经覆盖了从零基础到分布式部署的主线。回顾整套学习过程我最大的体会是Redis的命令本身不难难的是理解每个设计背后的为什么。为什么用单线程却还这么快为什么缓存会引发一致性问题为什么主从复制之外还要加哨兵这些问题比单纯背命令重要得多。面试官问你Redis其实想看到的不是你会用SET和GET而是遇到问题你能不能在原理层面判断归因和选型。如果你正在学我建议按这个节奏走先把单机开发用熟再花一周时间把三大缓存问题每个都做成一次回放演练再往分布式方向推进。每一步都实际动手敲一遍比看十篇教程都值。至于集群模式Redis Cluster和更多高级特性那是后话先把上面的基础打扎实后面自然水到渠成。
返回列表