单服务器Docker Redis缓存集群搭建及Spring Boot适配实践
缓存是微服务架构中提升性能、减轻数据库压力的核心,Redis凭借高性能成为主流方案。单节点Redis存在单点故障风险,主从+哨兵集群可实现读写分离与故障自动转移,保障高可用。
本文针对中小型项目单服务器场景,详细介绍Docker容器化搭建Redis主从+哨兵集群(统一目录运维)、Spring Boot用户缓存适配,补充缓存三大异常解决方案及故障排查,助力快速落地生产级缓存架构。
摘要:针对单服务器部署场景,提出Docker容器化Redis主从+哨兵集群方案,解决单点故障、IP动态变化、配置兼容等问题;实现Spring Boot缓存服务适配,提供缓存穿透、击穿、雪崩解决方案,通过验证确保集群高可用。方案部署简单、运维便捷,适用于中小型项目生产落地。
关键词:Docker;Redis主从集群;Redis哨兵;Spring Boot;缓存适配
一、单服务器Docker搭建Redis主从+哨兵集群(目录统一管理)
1. 集群设计思路
单服务器采用"不同端口模拟多节点",结合Docker Compose一键编排,所有集群目录统一至/docker/redis-ha-docker,简化运维。节点规划贴合生产端口规范,具体如下:
- 主节点(master):6379端口(核心写节点,同步数据至从节点,负责数据持久化)
- 从节点1(slave1):6380端口;从节点2(slave2):6381端口(均为读节点,分担并发、冗余备份)
- 哨兵1-3:端口26379、26380、26381(监控节点,协同判断故障、执行故障转移,满足quorum=2)
核心优势:哨兵冗余避免监控失效;主从读写分离提升并发;Docker环境隔离;目录统一便于运维;适配主节点IP动态变化,解决容器重启失联问题。
2. 环境准备
确保服务器满足以下要求,避免部署失败:
- 系统:CentOS 7/8或Ubuntu 20.04+,内存≥2GB;
- 组件:Docker≥20.10、Docker Compose≥2.10,启动并设置开机自启;
- 端口:放行6379、6380、6381、26379、26380、26381(防火墙、安全组同步配置);
- 目录:执行命令创建统一结构,路径可调整但需保持后续配置一致:
bash
mkdir -p /docker/redis-ha-docker/{master,slave1,slave2,sentinel1,sentinel2,sentinel3}
cd /docker/redis-ha-docker
3. 编写Docker Compose文件(核心编排)
在集群根目录创建docker-compose.yml,适配Redis 7.0.15,核心解决主节点IP动态变化问题,内容如下:
yaml
version: '3.8'
services:
# 主节点
redis-master:
image: redis:7.0-alpine
container_name: redis-master
ports:
- "6379:6379"
volumes:
- ./master/redis.conf:/etc/redis/redis.conf # 路径对应redis-ha-docker下的master目录
- ./master/data:/data
command: redis-server /etc/redis/redis.conf
restart: always
networks:
redis-network:
aliases:
- redis-master
# 从节点1
redis-slave1:
image: redis:7.0-alpine
container_name: redis-slave1
ports:
- "6380:6379"
volumes:
- ./slave1/redis.conf:/etc/redis/redis.conf # 路径对应redis-ha-docker下的slave1目录
- ./slave1/data:/data
command: redis-server /etc/redis/redis.conf
restart: always
depends_on:
- redis-master
networks:
- redis-network
# 从节点2
redis-slave2:
image: redis:7.0-alpine
container_name: redis-slave2
ports:
- "6381:6379"
volumes:
- ./slave2/redis.conf:/etc/redis/redis.conf # 路径对应redis-ha-docker下的slave2目录
- ./slave2/data:/data
command: redis-server /etc/redis/redis.conf
restart: always
depends_on:
- redis-master
networks:
- redis-network
# 哨兵1 核心逻辑:1个哨兵检测到主节点故障→标记主观下线→询问其他哨兵→≥2个哨兵确认→标记客观下线→选举1个主导哨兵执行故障转移
redis-sentinel1:
image: redis:7.0-alpine
container_name: redis-sentinel1
ports:
- "26379:26379"
volumes:
- ./sentinel1/sentinel.conf:/etc/redis/sentinel.conf # 路径对应redis-ha-docker下的sentinel1目录
command: redis-sentinel /etc/redis/sentinel.conf
restart: always
depends_on:
- redis-master
- redis-slave1
- redis-slave2
networks:
- redis-network
# 哨兵2(与哨兵1、3配置一致,参与投票协商,不单独触发故障转移)
redis-sentinel2:
image: redis:7.0-alpine
container_name: redis-sentinel2
ports:
- "26380:26379"
volumes:
- ./sentinel2/sentinel.conf:/etc/redis/sentinel.conf # 路径对应redis-ha-docker下的sentinel2目录
command: redis-sentinel /etc/redis/sentinel.conf
restart: always
depends_on:
- redis-master
- redis-slave1
- redis-slave2
networks:
- redis-network
# 哨兵3(与哨兵1、2配置一致,quorum=2,需至少2个哨兵确认主节点故障才触发转移)
redis-sentinel3:
image: redis:7.0-alpine
container_name: redis-sentinel3
ports:
- "26381:26379"
volumes:
- ./sentinel3/sentinel.conf:/etc/redis/sentinel.conf # 路径对应redis-ha-docker下的sentinel3目录
command: redis-sentinel /etc/redis/sentinel.conf
restart: always
depends_on:
- redis-master
- redis-slave1
- redis-slave2
networks:
- redis-network
networks:
redis-network:
driver: bridge
4. 编写各节点配置文件
所有配置文件对应挂载目录创建,适配Redis 7.0.15,删除无效配置,确保集群稳定运行。
(1)主节点配置(./master/redis.conf)
bash
bind 0.0.0.0
protected-mode no
port 6379
daemonize no # Docker环境必须设为no,避免容器退出
pidfile /var/run/redis_6379.pid
logfile "redis_6379.log"
dir /data
# 双重持久化,兼顾安全与性能
rdbcompression yes
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
replica-read-only yes # 从节点只读,保证数据一致
(2)从节点配置(./slave1/redis.conf、./slave2/redis.conf)
两者配置一致,仅目录不同,核心添加主节点指向:
bash
bind 0.0.0.0
protected-mode no
port 6379
daemonize no
pidfile /var/run/redis_6379.pid
logfile "redis_6379.log"
dir /data
replicaof redis-master 6379 # 指向主节点别名,适配IP动态变化
replica-read-only yes
# 持久化配置与主节点一致
rdbcompression yes
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
(3)哨兵配置(3个哨兵一致,以./sentinel1/sentinel.conf为例)
bash
port 26379
daemonize no
pidfile /var/run/redis-sentinel_26379.pid
logfile "sentinel_26379.log"
dir /data
# 监控主节点,quorum=2(至少2个哨兵确认故障)
sentinel monitor mymaster redis-master 6379 2
# 核心兼容配置(适配Redis 7.0.15,解决解析报错)
protected-mode no
sentinel resolve-hostnames yes # 开启主机名解析
# 超时与故障转移配置
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel down-after-milliseconds mymaster 30000 # 30秒无响应标记主观下线
sentinel failover-timeout mymaster 180000 # 故障转移超时3分钟
sentinel parallel-syncs mymaster 1 # 每次同步1个从节点,避免集群压力
说明:3个哨兵配置分别放入对应目录,无需修改IP,均沿用主节点别名,彻底规避配置兼容及解析报错。
5. 集群启动与验证
在/docker/redis-ha-docker目录执行以下命令,完成启动与验证,确保集群正常运行:
bash
# 一键启动所有节点
docker-compose up -d
# 查看容器状态(确保所有容器Up)
docker-compose ps
# 验证主从关系(主节点应显示2个从节点)
docker exec -it redis-master redis-cli info replication
# 验证哨兵状态(应显示监控1个主节点、2个从节点、3个哨兵)
docker exec -it redis-sentinel1 redis-cli -p 26379 info sentinel
# 验证主节点IP动态变化适配(重启主节点后重新验证哨兵状态)
docker restart redis-master
sleep 10
docker exec -it redis-sentinel1 redis-cli -p 26379 info sentinel
6. 故障转移测试
测试故障转移功能,确保集群高可用:
bash
# 停止主节点,模拟故障
docker stop redis-master
# 30秒后查询新主节点(应为slave1或slave2)
docker exec -it redis-sentinel1 redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
# 重启原主节点,验证其作为从节点重新加入集群
docker start redis-master
docker exec -it redis-master redis-cli info replication
二、Spring Boot用户缓存服务适配
1. 配置文件修改(application.yml)
核心配置哨兵节点地址,与Redis集群端口对应,确保服务正常连接集群:
yaml
spring:
redis:
sentinel:
master: mymaster # 与哨兵配置中主节点名称一致
nodes: 192.168.1.10:26379,192.168.1.10:26380,192.168.1.10:26381 # 替换为服务器IP
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
timeout: 2000ms
user:
cache:
expire: 3600 # 缓存默认过期时间1小时
hot-user-ids: 1,2,3 # 热点用户ID,用于缓存优化
2. Docker部署Spring Boot服务(可选)
容器化部署服务,与Redis集群协同工作,步骤如下:
(1)编写Dockerfile
sql
FROM openjdk:8-jre-alpine
WORKDIR /app
COPY target/redis-user-cache-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
(2)打包与启动
perl
# 打包Spring Boot项目
mvn clean package -DskipTests
# 构建镜像
docker build -t user-cache-service:1.0 .
# 启动容器,加入Redis集群网络
docker run -d --name user-cache -p 8080:8080 
 --network redis-network 
 -e SPRING_REDIS_SENTINEL_NODES=192.168.1.10:26379,192.168.1.10:26380,192.168.1.10:26381 
 user-cache-service:1.0
3. 服务验证
访问接口验证缓存功能正常,确保适配无误:
- 缓存命中/未命中:访问
http://服务器IP:8080/user/1; - 空值缓存:访问
http://服务器IP:8080/user/999; - 缓存更新:调用更新接口,验证缓存同步更新。
4. 缓存三大异常解决方案(适配当前集群)
针对缓存穿透、击穿、雪崩,结合Redis集群特性,提供可落地、无侵入解决方案,与业务逻辑兼容。
(1)缓存穿透
问题:请求不存在的用户ID,穿透缓存直击数据库,导致数据库压力激增。
解决方案:空值缓存+布隆过滤器双兜底:
- 空值缓存:查询数据库无结果时,缓存"null"标记(过期30秒),避免重复穿透;
- 布隆过滤器:项目启动时加载所有有效用户ID至Redis Set,请求先校验ID是否存在,不存在直接返回404。
(2)缓存击穿
问题:热点用户缓存过期瞬间,大量并发请求穿透至数据库。
解决方案:互斥锁+热点数据永不过期:
- 互斥锁:缓存未命中时,通过Redis SetNx获取分布式锁,获取成功查询数据库更新缓存,失败则重试;
- 热点数据永不过期:针对配置中热点用户,后台定时(每1小时)刷新缓存,确保缓存始终有效。
(3)缓存雪崩
问题:大量缓存同时过期,或Redis集群故障,导致所有请求穿透至数据库,引发数据库雪崩。
解决方案:过期时间随机化+集群高可用+降级熔断:
- 过期时间随机化:缓存设置基础过期时间+随机偏移量(10-60秒),避免缓存集中过期;
- 集群高可用:依托Redis主从+哨兵集群,主节点故障时哨兵自动切换新主节点,避免缓存服务中断;
- 降级熔断:通过Sentinel等组件监控数据库压力,压力过高时熔断缓存穿透请求,返回默认数据。
三、常见故障排查与注意事项
1. 常见故障排查
- 哨兵启动报错:检查配置文件,确保无Redis 7.0.15不支持的配置(如resolve-ip、known-master);
- 主从同步失败:确认从节点配置中replicaof指向主节点别名,主从节点网络连通;
- 容器启动失败:查看容器日志(docker logs 容器名),排查配置挂载路径错误、端口冲突;
- 缓存适配失败:检查Spring Boot配置中哨兵节点地址、主节点名称,确保与集群一致。
2. 注意事项
- 所有哨兵配置需完全一致,同步更新,避免哨兵协同异常;
- 目录路径、端口、网络别名需保持统一,避免配置混乱;
- 生产环境需定期备份Redis数据(挂载目录备份),避免数据丢失;
- 根据业务压力调整Redis连接池、过期时间等参数,优化性能。