Redis Java 集成到 Spring Boot:从单机到集群的使用样例
在 Java 项目里使用 Redis,除了直接使用 Jedis,还可以把 Redis 集成到 Spring Boot 中,通过 Spring Data Redis 提供的模板类来操作。本文按照文档顺序,整理 Spring Boot 连接 Redis 单机、编写 Controller 测试接口、操作常见 Redis 数据结构,以及连接 Redis 集群的完整流程。
本文示例主要使用StringRedisTemplate。它适合操作 key 和 value 都是字符串的 Redis 数据,接口风格和原始 Redis 命令略有不同,但整体思路是一致的。
1. 使用 Spring Boot 连接 Redis 单机
1.1 创建项目
创建 Spring Boot 项目时,依赖选择中勾选 NoSQL 分类下的:
Spring Data Redis为了方便写接口测试 Redis 操作,也建议同时勾选 Web 分类下的:
Spring Web这样项目创建完成后,就可以通过 Controller 暴露测试接口,在浏览器或接口测试工具里触发 Redis 操作。
1.2 配置 Redis 服务地址
在application.yml中配置 Redis 的主机和端口:
spring:redis:host:127.0.0.1port:8888这里的127.0.0.1:8888表示 Java 程序访问本地8888端口。如果 Redis 本身就在本机运行,并且使用默认端口,可以改成:
spring:redis:host:127.0.0.1port:6379如果 Redis 安装在云服务器上,也可以通过端口转发把远程 Redis 的6379映射到本地8888,这样 Spring Boot 项目仍然访问127.0.0.1:8888。
2. 创建 Controller
文档里的示例只是为了演示 Redis 的基本操作,所以没有做 Service、Mapper 等分层,直接创建一个简单的 Controller。
@RestControllerpublicclassMyController{@AutowiredprivateStringRedisTemplateredisTemplate;}这里注入的是StringRedisTemplate。它是 Spring Data Redis 提供的模板对象,里面封装了很多 Redis 操作方法。
常见操作入口如下:
opsForValue() -> 操作 String opsForList() -> 操作 List opsForHash() -> 操作 Hash opsForSet() -> 操作 Set opsForZSet() -> 操作 ZSet也就是说,在 Spring Boot 里不再直接调用set、lpush、hset这类命令,而是先通过opsForXxx()拿到对应数据结构的操作对象,再调用具体方法。
3. 使用 String
String 是 Redis 中最基础的数据结构。使用StringRedisTemplate操作 String 时,需要通过opsForValue()。
@GetMapping("/testString")@ResponseBodypublicStringtestString(){redisTemplate.opsForValue().set("key","value");Stringvalue=redisTemplate.opsForValue().get("key");System.out.println(value);redisTemplate.delete("key");return"OK";}这段代码的执行流程是:
- 向 Redis 写入
key -> value。 - 再根据
key读取 value。 - 把读取到的结果打印到控制台。
- 删除这个测试 key。
- 给浏览器或接口测试工具返回
OK。
它对应的 Redis 命令大致可以理解为:
SET key value GET key DEL key4. 使用 List
List 可以理解为 Redis 中的列表结构,支持从左侧或右侧插入元素,也支持按下标范围读取元素。使用StringRedisTemplate操作 List 时,通过opsForList()。
@GetMapping("/testList")@ResponseBodypublicStringtestList(){redisTemplate.opsForList().leftPush("key","a");redisTemplate.opsForList().leftPushAll("key","b","c","d");List<String>values=redisTemplate.opsForList().range("key",1,2);System.out.println(values);redisTemplate.delete("key");return"OK";}这里用到的几个方法含义是:
leftPush() -> 从列表左侧插入一个元素 leftPushAll() -> 从列表左侧批量插入多个元素 range() -> 按下标范围获取列表元素range("key", 1, 2)表示获取下标从1到2的元素,区间是闭区间。
因为示例使用的是leftPush,每次都从左侧插入,所以元素最终顺序和插入参数顺序不一定完全一样。学习 List 时,要特别注意“左侧插入”和“右侧插入”对顺序的影响。
5. 使用 Hash
Hash 适合保存对象结构,例如一个用户对象可以保存多个字段:name、age、email等。使用StringRedisTemplate操作 Hash 时,通过opsForHash()。
@GetMapping("/testHashmap")@ResponseBodypublicStringtestHashmap(){redisTemplate.opsForHash().put("key","name","zhangsan");Stringvalue=(String)redisTemplate.opsForHash().get("key","name");System.out.println(value);redisTemplate.opsForHash().delete("key","name");booleanok=redisTemplate.opsForHash().hasKey("key","name");System.out.println(ok);redisTemplate.delete("key");return"OK";}这段代码主要演示了四个动作:
put() -> 写入 hash 字段 get() -> 读取 hash 字段 delete() -> 删除 hash 字段 hasKey() -> 判断 hash 字段是否存在对应到 Redis 命令,大致可以理解为:
HSET key name zhangsan HGET key name HDEL key name HEXISTS key name需要注意,opsForHash().get()返回值类型是Object,如果确定里面存的是字符串,可以像示例中一样强制转换为String。
6. 使用 Set
Set 是无序、不重复集合。它适合表示“某个元素是否存在于集合中”这类问题。使用StringRedisTemplate操作 Set 时,通过opsForSet()。
@GetMapping("/testSet")@ResponseBodypublicStringtestSet(){redisTemplate.opsForSet().add("key","aaa","bbb","ccc");booleanok=redisTemplate.opsForSet().isMember("key","aaa");System.out.println(ok);redisTemplate.opsForSet().remove("key","aaa");longn=redisTemplate.opsForSet().size("key");System.out.println(n);redisTemplate.delete("key");return"OK";}这里用到的几个方法含义是:
add() -> 添加集合元素 isMember() -> 判断元素是否属于集合 remove() -> 删除集合元素 size() -> 获取集合元素数量对应到 Redis 命令,大致是:
SADD key aaa bbb ccc SISMEMBER key aaa SREM key aaa SCARD keySet 的元素没有固定顺序,因此如果后续打印集合内容,不要依赖它的输出顺序。
7. 使用 ZSet
ZSet 也叫有序集合。它和 Set 一样,member 不允许重复,但每个 member 都会关联一个 score,Redis 会按照 score 进行排序。使用StringRedisTemplate操作 ZSet 时,通过opsForZSet()。
@GetMapping("/testZSet")@ResponseBodypublicStringtestZSet(){redisTemplate.opsForZSet().add("key","吕布",100);redisTemplate.opsForZSet().add("key","赵云",98);redisTemplate.opsForZSet().add("key","典韦",95);Set<String>values=redisTemplate.opsForZSet().range("key",0,2);System.out.println(values);longn=redisTemplate.opsForZSet().count("key",95,100);System.out.println(n);redisTemplate.delete("key");return"OK";}这段代码主要演示:
add() -> 添加 member 和 score range() -> 按排名范围获取 member count() -> 统计指定 score 区间内的 member 数量默认情况下,range("key", 0, 2)会按照 score 从小到大返回指定范围内的元素。示例中典韦的分数是95,赵云是98,吕布是100。
count("key", 95, 100)统计分数在95到100之间的元素数量,区间包含两端。
8. 执行结果
运行 Spring Boot 程序后,分别访问上面的接口:
/testString /testList /testHashmap /testSet /testZSet每个接口都会向 Redis 写入测试数据,读取或判断结果,把结果打印到服务器控制台,最后删除测试 key,并返回:
OK如果浏览器页面返回OK,说明接口执行成功。具体 Redis 操作的输出结果,需要在 Spring Boot 启动窗口或控制台中查看。
9. 使用 Spring Boot 连接 Redis 集群
前面的配置连接的是 Redis 单机。如果要连接 Redis Cluster,需要把配置改成集群节点列表。
spring:redis:cluster:nodes:-172.30.0.101:6379-172.30.0.102:6379-172.30.0.103:6379-172.30.0.104:6379-172.30.0.105:6379-172.30.0.106:6379-172.30.0.107:6379-172.30.0.108:6379-172.30.0.109:6379lettuce:cluster:refresh:adaptive:trueperiod:2000这里配置了多个 Redis 集群节点地址。Spring Boot 会根据这些节点信息连接 Redis Cluster。
下面的 Lettuce 配置用于自动刷新集群拓扑结构:
lettuce:cluster:refresh:adaptive:trueperiod:2000它的作用是:当集群中有节点宕机、恢复,或者有新节点加入时,客户端能够感知集群结构的变化。这样应用程序不需要一直使用启动时拿到的旧拓扑信息。
改完配置后,上面 Controller 中的代码不需要调整,仍然可以继续使用StringRedisTemplate操作 Redis。
10. 集群访问注意事项
文档中的集群 IP 类似:
172.30.0.101 172.30.0.102 172.30.0.103这些通常是 Docker 容器内部 IP。在 Windows 主机上,Java 程序可能无法直接访问这些容器 IP。
因此,如果 Redis 集群运行在 Linux 的 Docker 环境中,更稳妥的方式是把 Spring Boot 项目打成 jar 包,上传到 Linux 环境中运行:
java-jarapp.jar这样 Spring Boot 程序和 Redis 集群处于同一个网络环境中,访问这些容器 IP 才更容易成功。
11. 小结
Spring Boot 2 系列默认使用 Lettuce 作为 Redis 客户端,它和 Jedis 的使用方式有一定差异。
Jedis 的方法名通常和 Redis 原生命令非常接近,例如set、get、lpush、hset等。而集成到 Spring Boot 之后,更常见的写法是通过StringRedisTemplate:
opsForValue() opsForList() opsForHash() opsForSet() opsForZSet()虽然接口形式不同,但底层操作的 Redis 数据结构仍然是一样的。只要熟悉 Redis 的基本数据类型和常见命令,就能比较容易地通过方法名理解 Spring Data Redis 的用法。
学习时可以按这个顺序练习:
- 先配置单机 Redis 地址,确认 Spring Boot 能连上 Redis。
- 再通过 Controller 分别练习 String、List、Hash、Set、ZSet。
- 最后把单机配置替换成 Redis Cluster 配置,理解 Lettuce 的集群拓扑刷新配置。
这样从单机到集群,Spring Boot 集成 Redis 的基本流程就比较完整了。