🚀 Spring Boot + Redis Sentinel 完整测试案例

🏷️ 标签:Redis 、Redis Sentinel、Spring Boot 实战


📚 目录导航

  1. 📝 前言
  2. 🏗️ Redis Sentinel 架构说明
  3. 📦 Docker Compose 搭建 Redis 哨兵环境
  4. ⚙️ Spring Boot 配置
    • 📌 Maven 依赖
    • 📝 application.yml 配置
    • 🔧 Redis 配置类
  5. 🧪 测试 Controller
  6. 🚀 运行测试
  7. 为什么这样配置
  8. 🏁 总结

📝 一、前言

在生产环境中,Redis 通常部署为 一主多从 + 哨兵(Sentinel) 架构,以保证高可用性和数据安全性。
使用 Spring Boot 连接 Redis 哨兵时,开发者可能会遇到以下问题:

  • 哨兵返回主节点名称(如 redis-master)无法被客户端解析
  • 数据序列化和反序列化不一致导致 StreamCorruptedException

本文演示如何通过 Docker Compose 搭建 Redis 哨兵环境,并使用 Spring Boot 完成数据写入和读取操作。


🏗️ 二、Redis Sentinel 架构说明

1. ASCII 拓扑示意

          ┌─────────────┐│ redis-master││    6379     │└─────┬───────┘│┌─────────┴─────────┐│                   │
┌─────────────┐     ┌─────────────┐
│ redis-slave1│     │ redis-slave2│
│    6380     │     │    6381     │
└─────────────┘     └─────────────┘▲                   ▲│                   │
┌─────┴─────┐       ┌─────┴─────┐
│ sentinel1 │       │ sentinel2 │
│  26379    │       │  26380    │
└───────────┘       └───────────┘▲│┌───────────┐│ sentinel3 ││  26381    │└───────────┘

2. Mermaid 彩色架构图

redis-master:6379
redis-slave1:6380
redis-slave2:6381
sentinel1:26379
sentinel2:26380
sentinel3:26381

🔹 主节点(红色)、从节点(绿色)、哨兵(蓝色),箭头表示数据同步和监控方向。


📦 三、Docker Compose 搭建 Redis 哨兵环境

Docker Compose 搭建Redis哨兵


⚙️ 四、Spring Boot 配置

📌 1. Maven 依赖

    <dependencies><!-- Web 模块 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Jackson --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

📝 2. application.yml 配置

spring:data:redis:sentinel:nodes:- 192.168.3.150:26379- 192.168.3.150:26380- 192.168.3.150:26381master: mymastertimeout: 3000mslettuce:shutdown-timeout: 100mspool:max-active: 8max-idle: 8min-idle: 0max-wait: -1
logging:level:io.lettuce.core: DEBUGorg.springframework.data.redis: DEBUG

🔧 3. Redis 配置类

package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(template.getStringSerializer());template.setHashKeySerializer(template.getStringSerializer());template.afterPropertiesSet();return template;}@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {return new StringRedisTemplate(factory);}
}

使用 StringRedisTemplate 避免 Java 默认序列化问题。


🧪 五、测试 Controller

package com.example.demo.controller;import com.example.demo.service.RedisService;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/redis")
public class RedisController {private final RedisService redisService;// 构造函数注入,Spring 会自动注入 RedisService Beanpublic RedisController(RedisService redisService) {this.redisService = redisService;}// String 操作示例@PostMapping("/string/set")public String setString(@RequestParam String key, @RequestParam String value) {redisService.set(key, value, 60L, TimeUnit.SECONDS);return "String set successfully";}// String获取Key@GetMapping("/string/get")public Object getString(@RequestParam String key) {return redisService.get(key);}
}

🚀 六、运行测试

1. 启动 Docker Compose:

docker compose up -d

2. 启动 Spring Boot 应用

3. 测试写入:

curl "http://localhost:9090/redis/set?key=test&value=HelloRedis"

在这里插入图片描述

4. 测试读取:

curl "http://localhost:9090/redis/get?key=test"

在这里插入图片描述

5. 验证主从同步:

docker exec -it redis-slave1 redis-cli GET test
docker exec -it redis-slave2 redis-cli GET test

在这里插入图片描述

数据应在主从节点一致。

6.可能出现的问题:

2025-08-14T15:51:45.540+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.DefaultEndpoint  : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2] closeAsync()
2025-08-14T15:51:45.543+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] io.lettuce.core.RedisClient              : Resolved SocketAddress redis-master/<unresolved>:6379 using redis-sentinel://192.168.3.150,192.168.3.150:26380,192.168.3.150:26381?sentinelMasterId=mymaster&timeout=3s
2025-08-14T15:51:45.543+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] io.lettuce.core.AbstractRedisClient      : Connecting to Redis at redis-master/<unresolved>:6379
2025-08-14T15:51:45.545+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelInactive()
2025-08-14T15:51:45.546+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.DefaultEndpoint  : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2] deactivating endpoint handler
2025-08-14T15:51:45.546+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelInactive() done
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.l.core.protocol.ConnectionWatchdog     : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, last known addr=/192.168.3.150:26379] channelInactive()
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.l.core.protocol.ConnectionWatchdog     : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, last known addr=/192.168.3.150:26379] Reconnect scheduling disabled
2025-08-14T15:51:45.547+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-1] i.lettuce.core.protocol.CommandHandler   : [channel=0xec254466, /192.168.3.36:49466 -> /192.168.3.150:26379, epid=0x2, chid=0x1] channelUnregistered()
2025-08-14T15:51:47.799+08:00 DEBUG 9436 --- [Spring-Redis-Sentinel] [ioEventLoop-4-2] io.lettuce.core.AbstractRedisClient      : Connecting to Redis at redis-master/<unresolved>:6379: {}java.net.UnknownHostException: 不知道这样的主机。 (redis-master)at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method) ~[na:na]at java.base/java.net.InetAddress$PlatformNameService.lookupAllHostAddr(InetAddress.java:933) ~[na:na]at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1543) ~[na:na]at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:852) ~[na:na]at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1532) ~[na:na]at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1384) ~[na:na]at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1305) ~[na:na]at java.base/java.net.InetAddress.getByName(InetAddress.java:1255) ~[na:na]at io.netty.util.internal.SocketUtils$8.run(SocketUtils.java:156) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.internal.SocketUtils$8.run(SocketUtils.java:153) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at java.base/java.security.AccessController.doPrivileged(AccessController.java:569) ~[na:na]at io.netty.util.internal.SocketUtils.addressByName(SocketUtils.java:153) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.DefaultNameResolver.doResolve(DefaultNameResolver.java:41) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:61) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.SimpleNameResolver.resolve(SimpleNameResolver.java:53) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:55) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.InetSocketAddressResolver.doResolve(InetSocketAddressResolver.java:31) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.resolver.AbstractAddressResolver.resolve(AbstractAddressResolver.java:106) ~[netty-resolver-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap.doResolveAndConnect0(Bootstrap.java:220) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap.access$000(Bootstrap.java:47) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap$1.operationComplete(Bootstrap.java:189) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.bootstrap.Bootstrap$1.operationComplete(Bootstrap.java:175) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:603) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:570) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:505) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:649) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:638) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:118) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.safeSetSuccess(AbstractChannel.java:988) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:515) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:428) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:485) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:173) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:166) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569) ~[netty-transport-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.123.Final.jar:4.1.123.Final]at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
1. 为什么会这样

Docker 内部可以通过容器名 redis-master 互相访问(因为有自定义网络和 DNS)。

但是你的 Spring Boot 是在宿主机运行(不是在 Docker 内部),宿主机默认并不认识 redis-master 这个名字。

哨兵返回的主节点地址是它内部配置的 redis-master(来自 sentinel.conf 或 docker-compose 服务名),但宿主机解析不了。

2. 解决方案

在SpringBoot 主机 C:\Windows\System32\drivers\etc 加映射

如果 redis-master 容器的 IP 是 192.168.3.150(或者你用的是桥接 IP):

在这里插入图片描述
这样宿主机就能解析 redis-master 了。


❓ 七、为什么这样配置

  1. 哨兵模式:自动故障转移,保证高可用
  2. announce-ip 配置 IP:避免容器名解析问题,防止 UnknownHostException
  3. StringRedisTemplate:避免序列化异常,方便开发调试
  4. Docker Compose:快速搭建一主两从 + 三哨兵环境,便于测试

🏁 八、总结

  • Redis Sentinel + Spring Boot 可以轻松实现高可用读写

  • 注意:

    • 哨兵返回 IP 避免主机名解析问题
    • 数据序列化需与存储类型匹配
  • 本方案适合开发、测试和小型生产环境

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918798.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918798.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918798.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣-295.数据流的中位数

题目链接 295.数据流的中位数 class MedianFinder {PriorityQueue<Integer> left;//队头最大PriorityQueue<Integer> right;//队头最小public MedianFinder() {left new PriorityQueue<>(new Comparator<Integer>() {Overridepublic int compare(In…

【数据分享】2014-2023年长江流域 (0.05度)5.5km分辨率的每小时日光诱导叶绿素荧光SIF数据

而今天要说明数据就是2014-2023年长江流域 &#xff08;0.05度&#xff09;5.5km分辨率的每小时日光诱导叶绿素荧光SIF数据。数据介绍一、数据集概况&#xff1a;长江流域植被动态的 “每小时快照”本文分享的核心数据集为2014 年 9 月至 2023 年 9 月长江流域日光诱导叶绿素荧…

计算机二级 Web —— HTML 全面精讲(含真题实战)

例题来源: web.code2ji.cn 0. HTML 基础与全局常识 0.1 HTML 是什么 HTML&#xff08;HyperText Markup Language&#xff09;是网页结构语言&#xff0c;用“标签”描述内容、层次与含义。 0.2 基本文档骨架&#xff08;必须熟练&#xff09; <!DOCTYPE html> <…

Linux中的日志管理

注&#xff1a;在 centos7/Rocky9 中&#xff0c;系统日志消息由两个服务负责处理&#xff1a;systemd-journald 和 rsyslog一、常见日志文件的作用实验一&#xff1a;测试查看暴力破解系统密码的IP地址步骤一&#xff1a;故意输错密码3次&#xff0c;在日志文件中查看步骤二&a…

C++ 性能优化擂台:挑战与突破之路

一、引言&#xff08;一&#xff09;C 在性能关键领域的地位在当今数字化时代&#xff0c;C 语言凭借其高效性、灵活性和对硬件的直接操控能力&#xff0c;在众多对性能要求极高的领域中占据着举足轻重的地位。无论是构建高性能的游戏引擎&#xff0c;实现金融领域毫秒级响应的…

五、Elasticsearch在Linux的安装部署

五、Elasticsearch在Linux的安装部署 文章目录五、Elasticsearch在Linux的安装部署1.Elasticsearch的作用2.安装0. 安装前准备1.使用包管理器安装&#xff08;推荐&#xff0c;自动服务化&#xff09;Ubuntu / DebianRHEL / CentOS / Rocky / Alma2. 使用 tar.gz 安装&#xff…

Kubernetes集群部署全攻略

目录 一、 服务器环境及初始化 1、架构分析 2、初始化 2.1、清空Iptales默认规则及关闭防火墙 2.2、关闭SELINUX 2.3、关闭Swap交换空间 2.4、设置主机名 2.5、编写hosts文件 2.6、设置内核参数 二、安装Docker环境 1、安装Docker 1.1、配置阿里源 1.2、安装docke…

Ceph存储池详解

Ceph 存储池&#xff08;Pool&#xff09;详解 Ceph 的 存储池&#xff08;Pool&#xff09; 是逻辑存储单元&#xff0c;用于管理数据的分布、冗余和访问策略。它是 Ceph 存储集群的核心抽象&#xff0c;支持 对象存储&#xff08;RGW&#xff09;、块存储&#xff08;RBD&…

使用 Docker 部署 PostgreSQL

通过 Docker 部署 PostgreSQL 是一种快速、高效的方式&#xff0c;适用于开发和测试环境。 步骤 1&#xff1a;拉取 PostgreSQL 镜像 运行以下命令从 Docker Hub 拉取最新的 PostgreSQL 镜像&#xff1a; docker pull postgres 如果需要其他的镜像&#xff0c;可以指定版本…

P1886 滑动窗口 /【模板】单调队列【题解】

P1886 滑动窗口 /【模板】单调队列 题目描述 有一个长为 nnn 的序列 aaa&#xff0c;以及一个大小为 kkk 的窗口。现在这个窗口从左边开始向右滑动&#xff0c;每次滑动一个单位&#xff0c;求出每次滑动后窗口中的最小值和最大值。 例如&#xff0c;对于序列 [1,3,−1,−3,5,3…

河南萌新联赛2025第(五)场:信息工程大学补题

文章目录[TOC](文章目录)前言A.宇宙终极能量调和与多维时空稳定性验证下的基础算术可行性研究B.中位数C.中位数1F.中位数4G.简单题H.简单题I.Re:从零开始的近世代数复习&#xff08;easy&#xff09;K.狂飙追击L.防k题前言 这次萌新联赛考到了很多数学知识 A.宇宙终极能量调和…

SuperMap GIS基础产品FAQ集锦(20250804)

一、SuperMap iServer 问题1&#xff1a;iServer的名称和logo怎么自定义&#xff1f; 11.3.0 【解决办法】参考&#xff1a;https://blog.csdn.net/supermapsupport/article/details/144744640 问题2&#xff1a;iServer 刷新工作空间&#xff0c;当数据库是 PostGIS 时&#x…

AWS CloudFormation批量删除指南:清理Clickstream Analytics堆栈

概述 在AWS环境管理中,经常会遇到需要批量删除CloudFormation堆栈的情况。本文记录了一次完整的Clickstream Analytics堆栈清理过程,包括遇到的问题和解决方案,希望能为其他开发者提供参考。 背景 我们的AWS账户中部署了多个Clickstream Analytics解决方案的CloudFormati…

redis中分布式锁的应用

我们之前讲了秒杀模块的实现&#xff0c;使用了sychronized互斥锁&#xff0c;但是在集群模式下因为不同服务器有不同jvm&#xff0c;所以synchronized互斥锁失效了。 redis实现秒杀超卖问题的解决方案&#xff1a;(仅限于单体项目)-CSDN博客 这时就要找到一个多台服务器都能…

【科研绘图系列】R语言绘制微生物丰度和基因表达值的相关性网络图

文章目录 介绍 加载R包 数据下载 导入数据 数据预处理 画图 系统信息 参考 介绍 【科研绘图系列】R语言绘制微生物丰度和基因表达值的相关性网络图 加载R包 library(tidyverse) library(ggsignif) library(RColorBrewer) library(dplyr) library(reshape2) library(grid

Pycharm现有conda环境有对应env,但是添加后没反应

一、系统环境 二、异常现象 Pycharm现有conda环境有对应env&#xff1a; anaconda3的envs下也确实存在这个环境&#xff1a; 但是添加后没反应&#xff08;点击确认后&#xff0c;yolov7环境没有出现在列表中&#xff09;&#xff1a; 但是我之前在别的机子添加是没问题的。 …

Git常用指令大全:从入门到精通

Git 的常用指令&#xff0c;分为基础操作、分支管理、远程协作、撤销操作和高级功能五个部分&#xff0c;并附上实用示例&#xff1a;一、基础操作&#xff08;必会&#xff09;初始化仓库 git init # 在当前目录创建新仓库克隆远程仓库 git clone https://github.com/user/rep…

Redis (REmote DIctionary Server) 高性能数据库

Redis {REmote DIctionary Server} 高性能数据库1. What is Redis?1.1. 基于内存的数据存储2. Install Redis on Linux3. Starting and stopping Redis in the background3.1. systemctl3.2. service 4. Connect to Redis5. 退出 Redis 的命令行界面 (redis-cli)6. redis-serv…

MySQL中的DML(二)

DML(Data Manipulation Language) : 数据库操作语言&#xff0c;对数据库中表的数据进行增删改操作。 创建student表&#xff1a; CREATE DATABASE test; use test; CREATE TABLE student (id int,name varchar(255),address varchar(255),city varchar(255) );INSERT INTO stu…

linux 主机驱动(SPI)与外设驱动分离的设计思想

一、 主机驱动与外设驱动分离Linux中的SPI、I2c、USB等子系统都利用了典型的把主机驱动和外设驱动分离的想法&#xff0c;让主机端负责产生总线上的传输波形&#xff0c;而外设端只是通过标准的API来让主机端以适当的波形访问自身。因此这里涉及了4个软件模块&#xff1…