1. 为什么Spring Boot项目里绕不开Redis先搞清楚它解决了什么问题先说一个我真实遇到过的场景。前几年做一个电商类后台项目用户登录后要展示购物车数量、商品分类、首页轮播图这些数据。刚开始没上缓存所有请求直接打到MySQL结果一到高峰期数据库连接池瞬间被打满接口响应时间从几十毫秒暴涨到两三秒页面转圈圈成了常态。后来引入Redis做缓存层热数据直接从内存里取QPS一下子从每秒几百上升到几千接口响应稳在几十毫秒以内。这就是Redis在Spring Boot项目里最常见、也是最有价值的用途——缓存。但Redis能干的事远不止这些缓存热数据把频繁查询且不经常变化的数据放进Redis减少数据库压力分布式会话管理多个应用实例共享用户登录状态解决集群环境下Session不共享的问题分布式锁在微服务场景下协调多个实例对共享资源的互斥访问防止并发问题排行榜/计数器利用Redis的ZSet和INCR命令轻松实现热度排行、点赞数、访问量统计消息队列利用List结构实现简单的生产者-消费者模式应对轻量级异步任务秒杀/限流结合原子操作实现库存扣减和接口防刷可以这么说只要你的项目需要处理高并发、需要共享状态、需要加速访问Redis几乎都是绕不开的选择。而Spring Boot作为目前Java后端最主流的开发框架对Redis有非常成熟的集成方案——spring-boot-starter-data-redis几行配置就能跑起来。这篇教程面向的是有一定Java基础、想在自己的Spring Boot项目里接入Redis的开发者。我会从环境准备、依赖引入、配置编写、代码实操到常见坑点排查一步步带你把整条链路走通。实践中遇到的那些文档里不会写的细节我也会一并分享出来。2. 环境准备Windows和Linux下装好单机Redis别在第一步卡住2.1 Windows下安装Redis很多初学者在Windows上装Redis会遇到一个尴尬问题——Redis官方其实不支持Windows官方下载页面只提供Linux版本。那Windows用户怎么办最常见的方案是使用微软维护的移植版或者从Redis官网的Windows移植仓库获取。简单说下步骤到GitHub上搜索microsoftarchive/redis仓库下载对应版本的zip压缩包常用版本比如3.2.100、5.0.14等解压到本地目录比如D:\redis打开命令行进入解压目录执行redis-server.exe redis.windows.conf看到启动日志输出端口6379并提示Ready to accept connections就说明启动成功了。另开一个命令行窗口执行redis-cli.exe -p 6379输入ping返回PONG即验证通过。需要说明的是这个Windows移植版实际用于开发测试完全够用但生产环境强烈建议部署在Linux服务器上性能和稳定性更有保障。我实际使用中遇到过Windows版在高并发下连接数异常、内存回收不及时的问题所以如果你的项目要上线还是老老实实用Linux。2.2 Linux下安装RedisLinux下的安装就很正规了。这里给出基于官方源码编译安装的方式适合CentOS 7/Ubuntu 20.04等主流发行版# 下载源码包 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 make install # 启动服务 redis-server如果想以后台守护进程方式运行需要修改配置文件redis.confdaemonize yes然后执行redis-server redis.conf使用redis-cli ping验证返回PONG表示正常。2.3 用Docker快速部署最适合本地开发如果你本地装了Docker这是我最推荐的一种方式干净利落、不用污染宿主机环境docker run -d --name redis-dev -p 6379:6379 redis:7.0这条命令会拉取Redis 7.0官方镜像并在后台运行宿主机6379端口映射到容器内6379。验证方式同上。如果要用Redis Desktop Manager或Another Redis Desktop Manager这类可视化客户端查看数据连接配置也简单服务器地址填localhost端口填6379没有密码的话留空即可。注意以上几种方式默认都没有设置密码6379端口直接暴露在网络上风险很高。本地开发无所谓但一旦部署到云服务器务必设置requirepass或用安全组限制访问来源IP。关于Redis 6.0之后版本的ACL机制还有更细的权限控制能力比如给不同业务配置不同库的读写权限生产环境值得花点时间研究这里先不展开。3. 整合Spring Boot依赖、配置、序列化器一个都不能马虎3.1 引入依赖创建一个Spring Boot项目这里以Spring Boot 2.7.x为例3.x的差异后面单独说在pom.xml中引入dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency这个Starter会自动引入Spring Data Redis和底层的连接池依赖。默认情况下Spring Boot 2.x使用Lettuce作为Redis客户端这也是官方推荐的客户端之一相比老牌的JedisLettuce基于Netty实现支持异步和响应式编程性能更好、线程安全。3.2 配置application.yml最小可用的配置只需要三行spring: data: redis: host: localhost port: 6379如果你的Redis设置了密码加上password字段使用了指定数据库加上database字段spring: data: redis: host: localhost port: 6379 password: yourpassword database: 0 timeout: 3000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0这里解释一下lettuce.pool的配置。Redis连接池的max-active表示连接池最大连接数max-idle是最大空闲连接数min-idle是最小空闲连接数。很多人不知道的是——在Spring Boot 2.x中如果你引入的是spring-boot-starter-data-redis默认其实不会创建连接池因为Lettuce本身是线程安全的可以不依赖池。只有当commons-pool2这个依赖存在时上面的lettuce.pool配置才生效。所以如果你确实需要用连接池比如高并发场景下控制连接数还要额外引入dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency这个细节很容易被忽略等到线上出现连接数异常时才发现配置没有生效。3.3 配置序列化器默认的JDK序列化是大坑Spring Boot自动配置的RedisTemplate默认使用JdkSerializationRedisSerializer来序列化key和value。这意味着存入Redis的key会带上类似\xac\xed\x00\x05t\x00\x05的前缀在Redis Desktop Manager里看数据全是乱码value是以二进制形式存储的跨语言平台比如用Python、Node.js读取完全不兼容序列化后的体积比JSON大得多浪费内存所以项目里我几乎总是会重写一个RedisTemplate配置类把key和value的序列化方式改成JSONConfiguration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // 使用GenericJackson2JsonRedisSerializer来序列化和反序列化value GenericJackson2JsonRedisSerializer jsonRedisSerializer new GenericJackson2JsonRedisSerializer(); // key和hashKey使用String序列化 StringRedisSerializer stringRedisSerializer new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); template.afterPropertiesSet(); return template; } }这里有个我踩过的坑GenericJackson2JsonRedisSerializer在反序列化时会把对象转换成LinkedHashMap如果直接强转成自定义对象运行时会报ClassCastException。解决办法是可以使用带类信息的序列化方式比如在ObjectMapper中启用activateDefaultTyping或者改用Jackson的PolymorphicTypeValidator配置。更推荐的做法是别把整个对象直接塞进Redis而是手动把对象转成JSON字符串存入读取时再JSON.parseObject回对象。虽然多了一步但可读性、可控性、跨语言兼容性都更好。3.4 用StringRedisTemplate还是RedisTemplateSpring Boot还提供了一个StringRedisTemplate它和RedisTemplate的区别就一点——StringRedisTemplate的key和value默认都使用String序列化器。如果你的业务数据都是简单字符串比如缓存短信验证码、计数器、Token直接用StringRedisTemplate是最省事的。如果涉及对象缓存再考虑自定义RedisTemplate或JSON转换方案。4. 核心API实操缓存、过期时间、分布式锁、事务照着写就能用4.1 基础操作字符串和Hash最简单的读写操作直接注入StringRedisTemplateService public class CacheService { Autowired private StringRedisTemplate redisTemplate; // 写入带过期时间的缓存 public void setWithExpire(String key, String value, long timeout, TimeUnit unit) { redisTemplate.opsForValue().set(key, value, timeout, unit); } // 读取缓存 public String get(String key) { return redisTemplate.opsForValue().get(key); } // 删除缓存 public void delete(String key) { redisTemplate.delete(key); } // 判断是否存在 public boolean hasKey(String key) { return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } }Hash结构适合存储对象比如用户信息、购物车等// 写入Hash redisTemplate.opsForHash().put(user:1001, name, 张三); redisTemplate.opsForHash().put(user:1001, age, 25); // 读取整个Hash MapObject, Object user redisTemplate.opsForHash().entries(user:1001); // 设置过期时间 redisTemplate.expire(user:1001, 30, TimeUnit.MINUTES);注意opsForValue().set()和expire()是两条命令如果希望在写入时同时设置过期时间一定要用带timeout参数的重载方法避免因分两步执行导致缓存永久有效的隐患。4.2 缓存穿透、缓存击穿、缓存雪崩的应对策略谈到Redis缓存这三个经典问题绕不开。我用自己的话给你捋一遍缓存穿透查询一个不存在的key缓存里没有数据库里也没有每次请求都打到数据库。大量恶意请求用不存在的ID攻击数据库直接被打崩。应对方案缓存空结果并设置较短的过期时间比如5分钟或者用布隆过滤器前置拦截。缓存击穿一个热点key突然过期恰逢高并发请求同时涌入所有请求全部打到数据库。应对方案用分布式锁保证只有一个线程去查询数据库并重建缓存其他线程等待。缓存雪崩大量key在同一时间段集中过期导致一大批请求同时越过缓存打到数据库。应对方案给过期时间加随机值避免同一时刻过期或者用多级缓存。代码层面一个带互斥锁的缓存查询模板大概长这样public String getFromCacheWithLock(String key) { // 先查缓存 String value redisTemplate.opsForValue().get(key); if (value ! null) { return value; } // 缓存未命中加锁 String lockKey lock: key; boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (locked) { try { // 双重检测第二个线程进入时可能已经重建完毕 value redisTemplate.opsForValue().get(key); if (value null) { // 查询数据库 value queryFromDatabase(key); // 回填缓存设置过期时间 redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES); } } finally { // 释放锁 redisTemplate.delete(lockKey); } } else { // 未获取到锁短暂休眠后重试或直接返回默认值 try { Thread.sleep(50); return getFromCacheWithLock(key); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return value; }这里用的是setIfAbsent实现简单锁setIfAbsent保证了原子性10秒过期时间防止锁忘记释放导致死锁。生产级应用建议使用Redisson框架它能自动续期锁、防止释放别人的锁后面会提到。4.3 分布式锁的正确使用姿势在单体时代Java的synchronized和ReentrantLock足够解决并发问题。但到了微服务架构多个实例部署在不同的JVM中JVM内部的锁无法跨进程生效这时候就需要分布式锁。Redis实现分布式锁的核心思路是利用SETNX命令的原子性多个实例同时尝试写入同一个key只有一个能成功。上面那段示例代码展示了最基础的自研写法但有几点容易出问题锁的过期时间设置太短业务还没执行完锁就自动过期了另一个线程拿到锁进来出现并发问题。太长则有锁释放不及时的风险。释放锁时没有校验归属锁过期后另一个线程拿到了锁前一个线程执行完delete把别人的锁释放了这比不设锁还危险。必须用Lua脚本保证判断和删除的原子性。没有看门狗自动续期Redisson内部有一个看门狗机制默认每10秒续期一次保证业务执行期间锁一直有效。如果是个人项目或学习用途手写一个锁没问题生产环境直接引入Redissondependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.27.2/version /dependency使用起来非常简单Autowired private RedissonClient redissonClient; public void doSomethingWithLock(String lockKey) { RLock lock redissonClient.getLock(lockKey); boolean acquired false; try { // 尝试获取锁最多等待3秒租约时间30秒看门狗会自动续期 acquired lock.tryLock(3, 30, TimeUnit.SECONDS); if (acquired) { // 业务逻辑 } else { // 未获取到锁的处理 } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (acquired lock.isHeldByCurrentThread()) { lock.unlock(); } } }用Redisson还有个隐藏好处它在Redis集群模式下使用红锁算法从多个Master节点获取锁比单点方案可靠得多。这个问题的讨论在Redis官方文档里都有专门的篇幅建议深入读一读。4.4 用Redis实现排行榜功能排行榜是Redis的经典应用场景ZSet有序集合结构天生适合这个需求。每个成员关联一个分数按分数自动排序。// 给用户增加积分 redisTemplate.opsForZSet().incrementScore(ranking:2024, user:1001, 10); // 获取前10名 SetString top10 redisTemplate.opsForZSet() .reverseRangeByScore(ranking:2024, 0, 100, 0, 10); // 获取某个用户的排名从0开始 Long rank redisTemplate.opsForZSet() .reverseRank(ranking:2024, user:1001);相比用Java代码在内存里排序再显示ZSet的优势在于排序和排名计算全部在Redis内部完成十几万用户的排行榜毫秒级返回。注意reverseRangeByScore和reverseRange的区别——前者按分数区间查询后者按下标区间查询用混了会得到意想不到的结果。4.5 Redis事务和流水线别把Lua脚本想得太复杂Redis的事务和关系型数据库事务不太一样它只保证多条命令在一个队列中顺序执行中间不会插入其他客户端的命令但不支持回滚。事务通过MULTI、EXEC、DISCARD和WATCH实现。在Spring Data Redis中这样使用// 事务操作 redisTemplate.execute(new SessionCallbackObject() { Override public Object execute(RedisOperations operations) { operations.multi(); operations.opsForValue().set(key1, value1); operations.opsForValue().set(key2, value2); return operations.exec(); } });更多时候我们用Lua脚本来保证多条命令的原子性。比如检查并删除、扣减库存并判断是否超卖这类场景DefaultRedisScriptLong script new DefaultRedisScript(); script.setScriptText( if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end ); script.setResultType(Long.class); Long result redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);Lua脚本在Redis服务端执行天然具备原子性是解决复杂校验和操作的首选方案。初学阶段不用过度钻研Lua语法掌握几个常用模板改改就能覆盖大多数场景。5. Spring Boot 3的整合差异jakarta坐标和更简洁的配置Spring Boot 3.x是较新的一个大版本内部做了不少调整。如果你打算用Spring Boot 3整合Redis时有几个点需要注意依赖坐标变化Spring Boot 3基于Spring Framework 6原来的javax.*包换成了jakarta.*包不过spring-boot-starter-data-redis的坐标没变还是同一个。配置属性变化早期版本的配置前缀是spring.redis.*Spring Boot 2.0之后改成了spring.redis.*到了2.4又改为spring.data.redis.*。如果你用的版本比较新注意配置前缀别写错。Lettuce和Jedis都支持默认还是Lettuce但如果你更习惯Jedis API也可以这样配置排除Lettuce、引入Jedisdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId exclusions exclusion groupIdio.lettuce/groupId artifactIdlettuce-core/artifactId /exclusion /exclusions /dependency dependency groupIdredis.clients/groupId artifactIdjedis/artifactId /dependencyspring.redis到spring.data.redis的迁移如果你老项目升级时发现Redis配置不生效大概率是配置前缀的问题。Spring Boot 2.4以后统一使用spring.data.redis前缀这个变化让很多从老版本升级上来的项目踩了坑。还有个小细节Spring Boot 3.0之后Java版本最低要求是17如果你的开发环境还停留在Java 8/11直接升级Spring Boot 3会面临编译问题需要先把Java环境切到17及以上。6. Spring Boot MyBatis的整合思路把Redis用在正确的位置虽然标题聚焦Redis但业务项目里Redis极少是孤立使用的。最常见的组合是Spring Boot MyBatis RedisRedis处理缓存MyBatis做数据库访问。整合思路大致是查询时先走Redis命中缓存直接返回未命中则查询MySQL并把结果写入Redis设置合适的过期时间变更时先写MySQL再淘汰缓存更新操作执行SQL后删除对应缓存保证缓存和数据库的一致性热点数据预热项目启动时用一个ApplicationRunner把热点数据加载进Redis社区里流行的自定义缓存注解RedisCache的思路就是基于AOP实现。给方法加上注解通过切面拦截方法调用方法执行前查缓存缓存未命中再执行方法体执行完回填缓存。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RedisCache { String key(); long expire() default 30; TimeUnit timeUnit() default TimeUnit.MINUTES; }AOP切面实现的核心要点是SpEL表达式解析方法参数生成动态key反射调用目标方法异常时直接降级为数据库查询而不影响业务流程。黑马点评这类教学项目也大量使用这套模式可见在企业级项目中这个方案的成熟度。不过缓存注解设计要谨慎处理key冲突和缓存过期时间避免出现注解得多了反而更难排查问题的情况。7. 实战案例用户信息缓存的完整链路设计下面我把一个完整的用户信息缓存模块端到端走一遍从表结构到业务代码都给你贴出来。7.1 需求说明用户登录后前端要频繁请求用户基本信息头像、昵称、等级。这个信息变化频率极低但读取频率极高非常适合做Redis缓存。7.2 数据库表和实体类CREATE TABLE user_profile ( id BIGINT PRIMARY KEY AUTO_INCREMENT, nickname VARCHAR(50) NOT NULL, avatar_url VARCHAR(255), user_level INT DEFAULT 1, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );对应实体类Data public class UserProfile { private Long id; private String nickname; private String avatarUrl; private Integer userLevel; private LocalDateTime updateTime; }7.3 业务代码实现Service public class UserProfileService { private static final String CACHE_KEY_PREFIX user:profile:; private static final long CACHE_EXPIRE_MINUTES 30; Autowired private StringRedisTemplate redisTemplate; Autowired private ObjectMapper objectMapper; Autowired private UserProfileMapper userProfileMapper; /** * 查询用户信息先缓存后数据库 */ public UserProfile getUserProfile(Long userId) { String cacheKey CACHE_KEY_PREFIX userId; String cachedJson redisTemplate.opsForValue().get(cacheKey); // 缓存命中 if (cachedJson ! null) { try { return objectMapper.readValue(cachedJson, UserProfile.class); } catch (JsonProcessingException e) { // 反序列化失败正常走数据库查询 } } // 缓存未命中查数据库 UserProfile profile userProfileMapper.selectById(userId); if (profile ! null) { try { String json objectMapper.writeValueAsString(profile); // 写入缓存并设置过期时间加上随机值避免雪崩 long expire CACHE_EXPIRE_MINUTES * 60 new Random().nextInt(120); redisTemplate.opsForValue().set(cacheKey, json, expire, TimeUnit.SECONDS); } catch (JsonProcessingException e) { // JSON序列化失败不影响主流程 } } return profile; } /** * 更新用户信息先更新数据库再删除缓存 */ public void updateUserProfile(UserProfile profile) { userProfileMapper.updateById(profile); String cacheKey CACHE_KEY_PREFIX profile.getId(); redisTemplate.delete(cacheKey); } }这段代码包含了几个值得注意的设计过期时间加随机值每次写入缓存的过期时间在30分钟基础上加0到2分钟的随机量避免同一批用户缓存同时过期形成雪崩。JSON序列化失败不影响主流程缓存只是优化手段即使序列化有问题也不能让主链路挂掉。更新时先写库再删缓存现实场景中先删缓存再写库存在短暂的不一致窗口更推荐更新后直接删缓存让下一次读请求重新回填。7.4 压测验证效果做一个简单压测对比用wrk模拟100并发查询同一用户信息场景平均响应时间QPS数据库查询次数无缓存220ms450450有缓存8ms120000全命中这个对比数字在实际业务里非常具有冲击力。当然这是纯缓存命中的理想场景真实项目要综合考虑缓存命中率、数据一致性要求等因素。8. 遇坑排查实录我踩过的Redis整合问题整合Spring Boot和Redis的过程中有几个典型报错和坑点几乎是每个人都会遇到的。我按排查链路写下来你照着对照就能少走弯路。8.1 连接不上Connection refused报错信息通常是Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/127.0.0.1:6379排查顺序Redis进程是否启动执行redis-cli ping看是否有PONG返回端口是否被占用或监听在非本机地址检查redis.conf的bind配置。默认Redis只绑定127.0.0.1如果应用部署在Docker容器里跨容器访问宿主机需要把bind改为0.0.0.0并做好防火墙限制是否有密码配置了requirepass后应用连接时没写password也会报认证失败Spring Boot配置前缀是否写对新老版本spring.redis和spring.data.redis的区别前面已经强调过8.2 命令超时RedisCommandTimeoutExceptionRedis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException这个报错常见于高并发或网络环境不稳定的场景。Lettuce默认命令超时时间是60秒如果瞬时请求量巨大可能导致某些命令长期排队。排查思路检查Redis服务端的maxclients是否打满检查网络链路是否有延迟或丢包在配置里适当增加超时时间spring.data.redis.timeout: 3000ms检查是否误用了连接池的max-active设置太小导致所有线程阻塞等待获取连接这类问题在高并发场景下往往不是单一原因需要结合Redis的INFO命令、慢日志SLOWLOG等排查工具综合确认。8.3 乱码和类型转换异常配置了RedisTemplate却没设置序列化器存进去再读出来直接强转大概率遇到下面这类问题java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.UserProfile这就是前面说到的GenericJackson2JsonRedisSerializer反序列化丢失类型信息导致的。解决办法以实用为主——要么配置Jackson的类型信息写入要么干脆用JSON字符串配合ObjectMapper手动转换。8.4 Windows版Redis连接增多就崩我用Windows版Redis遇到过一个真实情况压测到几百并发时Redis进程直接无响应日志里大量socket错误。后来查了源码issue才发现Windows移植版处理大量并发连接时存在系统级限制线程模型和Linux原版差异明显。最终结论Windows版只适合学习测试生产环境务必用Linux部署。8.5 Docker部署时的网络互通问题如果你用Docker跑Redis应用在宿主机上直接跑注意localhost指向的是宿主机可以通但如果你的Spring Boot应用也跑在Docker容器里localhost指向的是容器自身这时候必须使用宿主机IP或者用host网络模式或者通过docker-compose里的服务名来访问。8.6 缓存与数据库数据不一致这是最隐蔽也是最容易忽略的坑。典型场景管理员后台修改了商品信息直接改了数据库但Redis里还留着旧数据前端展示的还是旧值。解决思路更新数据库后主动删除缓存设置合理的过期时间让不一致窗口可控对实时性要求高的数据考虑使用Canal监听MySQL binlog变更后自动刷新缓存9. Redis常用命令速查日常开发高频使用的十几个命令不管用哪种客户端理解底层命令总能让排查问题更从容。我整理了一份日常最常用的命令清单覆盖String、Hash、List、Set、ZSet五大结构。9.1 String结构命令作用示例SET key value设置值SET username zhangsanGET key获取值GET usernameSETNX key value键不存在时设置分布式锁基础SETNX lock 1INCR key自增1计数器INCR visit_countINCRBY key increment自增指定值INCRBY user:1:score 10EXPIRE key seconds设置过期时间EXPIRE username 60TTL key查看剩余过期时间TTL usernameDEL key删除键DEL username9.2 Hash结构命令作用示例HSET key field value设置Hash字段HSET user:1 name zhangsanHGET key field获取Hash字段HGET user:1 nameHGETALL key获取所有字段和值HGETALL user:1HDEL key field删除Hash字段HDEL user:1 age9.3 List结构命令作用示例LPUSH key value从左侧推入LPUSH msg helloRPUSH key value从右侧推入RPUSH msg worldLPOP key从左侧弹出LPOP msgRPOP key从右侧弹出RPOP msgLRANGE key start stop获取列表范围LRANGE msg 0 99.4 ZSet结构命令作用示例ZADD key score member添加成员和分数ZADD ranking 100 zhangsanZINCRBY key increment member增加分数ZINCRBY ranking 10 zhangsanZREVRANGE key start stop按分数从高到低获取ZREVRANGE ranking 0 9ZRANK key member获取排名从低到高ZRANK ranking zhangsanZREVRANK key member获取排名从高到低ZREVRANK ranking zhangsan9.5 通用命令命令作用示例SELECT index切换数据库SELECT 1FLUSHDB清空当前数据库FLUSHDBFLUSHALL清空所有数据库慎用FLUSHALLINFO查看服务器信息INFO memory注意Redis默认有16个数据库0-15但生产环境强烈建议只用db0配合key前缀区分业务。多库模式在集群环境下不支持切换库还会带来运维复杂度。10. 项目监控与调优Spring Boot Admin和Redis可视化工具10.1 Spring Boot Admin监控Redis相关指标Spring Boot Admin可以监控Spring Boot应用的运行状态包括Redis的连接状态、健康检查信息。配置方式很简单dependency groupIdde.codecentric/groupId artifactIdspring-boot-admin-starter-server/artifactId version2.7.10/version /dependency启动类加EnableAdminServer注解再配置好被监控应用的spring.boot.admin.client.url就能在管理界面看到Redis的健康检查状态。这套组合在生产环境非常实用因为Redis不可用会直接拖垮整个应用的响应能力早发现早处理。10.2 Redis可视化客户端Redis Desktop Manager老牌工具功能全面但收费版本才支持完整功能Another Redis Desktop Manager免费开源我用得比较多支持SSH隧道、JSON格式化Windows和Mac都有Redis InsightRedis官方推出的可视化工具支持命令分析、内存分析、慢日志查看界面也现代日常调试数据用什么无所谓生产环境建议用Redis Insight它的性能分析和键空间统计功能对排查问题很有帮助。10.3 内存优化和淘汰策略Redis是内存数据库内存是稀缺资源。项目上线前必须规划好设置maxmemory给Redis限制最大内存比如maxmemory 2gb选择合适的淘汰策略maxmemory-policyallkeys-lru从所有key中按LRU淘汰最近最少使用的volatile-lru只从设置了过期时间的key中按LRU淘汰allkeys-random随机淘汰任意keynoeviction内存满时不淘汰写入直接报错具体选哪个策略取决于业务场景。缓存类数据用allkeys-lru居多因为热点数据自然会被频繁访问而留在内存中。再提一嘴Redis的线程IO模型。Redis 7.0是单线程处理命令的模式这是它简单高效的重要原因——没有线程切换的损耗没有锁竞争。IO多路复用机制让它能同时处理大量客户端连接。所以很多性能问题的根源往往不在Redis本身而在你的应用层使用方式。别一上来就怀疑Redis性能不够先检查是否有大key、是否用了低效命令、是否有不必要的序列化开销。10.4 大Key和热Key的治理这是运维层面的老生常谈但它实在太重要了不得不放在这里说。大Key指的是单个key的value过大比如一个Hash有几万个字段一个List几百万条数据。热Key则是被高频访问的key。这两类key都会导致Redis性能抖动大Key的读取会带来明显延迟占用的内存也不均衡热Key会导致某个Redis节点CPU打满集群模式下形成节点热点治理手段就三句话拆分大Key、给热Key加本地缓存Caffeine、必要时做缓存副本分散读压力。11. 关于Redis持久化和主从复制别让数据说丢就丢11.1 RDB和AOF两种持久化机制Redis默认的持久化方式是RDB快照定期的内存快照落盘。它的优点是恢复速度快、文件体积小缺点是两次快照之间的数据有丢失窗口。AOFAppend Only File则是把每一条写命令追加到日志文件重启时重放日志恢复数据。它的数据安全保障更强可以做到每秒同步everysec但文件体积相对较大恢复速度慢一些。生产环境的合理配置是两者同时开启RDB用于快速恢复AOF用于减少数据丢失。Spring Boot业务代码中不需要感知这些底层机制但你应该知道自己在用的Redis实例是哪种持久化策略避免在数据不允许丢失的业务上使用默认的纯RDB模式。11.2 主从复制和哨兵主从复制是Redis保障可用性的基础。一个主节点负责写多个从节点负责读。配置在redis.conf里简单一行就够replicaof 192.168.1.10 6379Spring Boot连接主从环境时如果用的是Lettuce可以配置读写分离spring: data: redis: host: 192.168.1.10 port: 6379 lettuce: pool: max-active: 16真正做高可用还需要Sentinel哨兵机制自动故障转移。Spring Boot通过配置哨兵地址连接集群业务代码完全无感知。这个进阶话题先留个入口等你的单机Redis方案跑顺畅后再去研究集群高可用的部署方案会水到渠成。用Docker部署Redis主从的常用套路就是起多个容器主节点映射一个端口两个从节点分别映射不同端口通过--link或自定义网络连通。具体yml配置网上很多核心原理就是主从之间的通信机制理解了这条主线容器编排只是体力活。12. Redis面试高频知识串讲理解原理后八股文自然能答既然说到Redis面试八股文也少不了。我这里不罗列题目而是把高频考点背后的核心逻辑讲清楚理解透了自然答得上来Redis为什么快内存操作 单线程避免竞争 IO多路复用为什么单线程还能这么快内存访问纳秒级、命令执行时间极短、IO操作被多路复用器托管Redis和Memcached的区别Redis支持丰富数据结构、支持持久化、支持主从复制缓存穿透、击穿、雪崩的区别及解决方案前面已经详细讲过了分布式锁的实现方案SETNX 过期时间 Lua脚本保证原子性Redisson的看门狗续期Redis线程模型从6.0开始Redis引入多线程处理网络IO但命令执行仍然单线程——这个细节很多面试者搞混把这一整套串起来你会发现在实战中积累的理解比纯背题深刻得多。13. 我的实操体会和几个能直接抄的优化建议最后分享几点我实际跑项目的心得不带任何官话每一条都是踩过坑换来的第一个感悟是连接池千万别瞎配。我之前在一个项目里把max-active配成200结果Redis连接数直接冲到上限服务端大量TIME_WAIT连接反而把Redis拖垮。后来压测发现80的并发业务其实只需要30个连接左右把连接池收敛后稳定多了。连接池大小由业务并发度和每个操作的耗时共同决定不是一个拍脑袋的数值。第二个是缓存key的命名规范。建议统一格式业务名:实体名:ID比如order:orderDetail:12345。一开始不注重规范等到缓存里的key多到分不清归属排查问题会非常痛苦。全项目必须统一一套规则并且写进团队开发手册。第三个是数据序列化方案尽量简单。我之前见过一个项目为了追求通用性给所有对象都挂上复杂的Jackson多态配置结果一次字段调整影响了一堆缓存读取反序列化报错满天飞。后来统一改成存JSON字符串读取时手动转换清晰易懂反而更稳定。第四Redis的版本选择不要盲目追新。生产环境我用7.0系列已经很成熟稳定8.0这种大版本让社区先跑一段时间再说。但本地学习和新项目选型完全可以跟上最新版本提前感受新特性和性能变化。第五监控一定要提前做。Redis刚上线的时候一切正常真出问题往往是大流量或者数据量增长后的某一天。提前接入Spring Boot Admin的Redis健康检查配合Redis Insight观察内存增长曲线至少能让你在故障发生前有迹可循。最后说个真实案例收尾。之前做过一个抢优惠券的活动8万张券两秒内被抢完数据库没有任何压力MySQL只承受了两千左右的写请求。背后的方案很简单用Redis的List预先生成券池抢券时LPOP原子弹出抢完自动结束。全程没有锁、没有事务、没有复杂的分布式协调就因为Redis单线程执行命令的天生原子性。有些时候解决高并发问题靠的不是更复杂的架构而是选择更合适的数据结构和工具。这才是Redis最迷人的地方。