Redis 高并发高可用实战:从主从哨兵到集群分片,附完整 Java 代码

在互联网高并发场景下,Redis 已成为缓存、会话存储、分布式锁、计数器等核心组件的标配。但单机 Redis 存在单点故障和容量瓶颈,高可用(HA)高并发(High Concurrency) 必须通过集群架构、客户端策略和代码级优化来保障。本文将深入剖析 Redis 高可用方案(主从复制、Sentinel 哨兵、Cluster 集群),并结合 Java(Jedis / Lettuce)给出生产级代码实现,涵盖连接池、读写分离、故障转移、分布式锁、限流器、批量管道等关键实践。全文代码总量超过 1500 字符,可直接运行于 Spring Boot 环境。


1. 高可用架构选型对比

方案 高可用方式 数据分片 适用场景
主从复制 + Sentinel 自动故障转移(哨兵监控) 不支持,单主写 读多写少,数据量<100GB
Redis Cluster 去中心化,节点间 Gossip 协议 支持 16384 个槽,自动分片 大数据量,高吞吐,需水平扩展
代理层(Codis/Twemproxy) 依赖代理,支持分片 支持 需兼容旧协议,但运维复杂

生产建议 :中小规模(< 200GB)优先使用 Sentinel 模式 ,运维简单;大规模(> 1TB)或超高 QPS(> 10 万)选择 Cluster 模式。本文同时涵盖两种模式的 Java 客户端配置代码。


2. 环境搭建与配置

2.1 Docker 快速启动 Sentinel 集群(3 节点)

bash

yaml 复制代码
# 创建网络
docker network create redis-net

# 启动三个 Redis 容器(主+两个从)
docker run -d --name redis-master --net redis-net -p 6379:6379 redis:7.0 redis-server --appendonly yes
docker run -d --name redis-slave1 --net redis-net -p 6380:6379 redis:7.0 redis-server --slaveof redis-master 6379
docker run -d --name redis-slave2 --net redis-net -p 6381:6379 redis:7.0 redis-server --slaveof redis-master 6379

# 启动三个哨兵容器(监控主节点)
for port in 26379 26380 26381; do
  docker run -d --name sentinel-$port --net redis-net -p $port:26379 redis:7.0 redis-sentinel --sentinel monitor mymaster redis-master 6379 2 --sentinel down-after-milliseconds 5000 --sentinel failover-timeout 60000
done

哨兵配置中 --sentinel monitor mymaster redis-master 6379 2 表示需要 2 个哨兵同意才能触发故障转移。


3. Java 客户端:Jedis 连接池 + Sentinel 高可用(代码块1)

使用 Jedis 操作 Sentinel 集群,实现自动故障切换。代码约 300 字符。

java

typescript 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.Protocol;
import java.util.HashSet;
import java.util.Set;

public class RedisSentinelClient {
    private static JedisSentinelPool pool;

    static {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("127.0.0.1:26379");
        sentinels.add("127.0.0.1:26380");
        sentinels.add("127.0.0.1:26381");
        // 创建哨兵连接池,主节点名为 "mymaster"
        pool = new JedisSentinelPool("mymaster", sentinels, Protocol.DEFAULT_TIMEOUT, "password".getBytes()); // 无密码可省略
    }

    public static Jedis getResource() {
        return pool.getResource();
    }

    public static void close() {
        if (pool != null) {
            pool.destroy();
        }
    }

    // 示例:set/get 操作
    public static void main(String[] args) {
        try (Jedis jedis = getResource()) {
            jedis.set("test:key", "sentinel_value");
            System.out.println("获取值: " + jedis.get("test:key"));
        }
    }
}

4. 高并发连接池调优(代码块2)

连接池参数直接影响并发能力,以下是生产级配置(约 200 字符):

java

arduino 复制代码
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolFactory {
    public static JedisPoolConfig createPoolConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(200);              // 最大连接数,根据业务 QPS 和 RT 计算
        config.setMaxIdle(50);                // 最大空闲连接
        config.setMinIdle(10);                // 最小空闲连接
        config.setMaxWaitMillis(3000);        // 获取连接超时时间(ms)
        config.setTestOnBorrow(true);         // 借用时检测连接是否可用
        config.setTestWhileIdle(true);        // 空闲时检测
        config.setTimeBetweenEvictionRunsMillis(60000); // 空闲检测周期
        config.setMinEvictableIdleTimeMillis(300000);   // 连接空闲超过 5 分钟被驱逐
        return config;
    }
}

计算最大连接数公式MaxTotal = (预期 QPS / 单连接每秒可执行命令数) * 冗余系数(1.5~2),例如预期 QPS=5000,单连接可执行 200 命令/秒,则 MaxTotal ≈ 50。


5. 分布式锁:Redisson 可重入锁(高并发必备)

分布式锁是库存扣减、防重复提交等场景的核心。Redisson 实现了 Lua 脚本保证原子性,并支持自动续期。

5.1 Redisson 配置(代码块3)

java

arduino 复制代码
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonManager {
    private static RedissonClient redisson;

    static {
        Config config = new Config();
        // Sentinel 模式配置
        config.useSentinelServers()
            .setMasterName("mymaster")
            .addSentinelAddress("redis://127.0.0.1:26379", "redis://127.0.0.1:26380", "redis://127.0.0.1:26381")
            .setPassword("password")
            .setConnectionPoolSize(50)
            .setConnectionMinimumIdleSize(10);
        redisson = Redisson.create(config);
    }

    public static RedissonClient getClient() { return redisson; }
}

5.2 分布式锁使用(代码块4,约 200 字符)

java

csharp 复制代码
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;

public class DistributedLockDemo {
    public static void main(String[] args) throws InterruptedException {
        RLock lock = RedissonManager.getClient().getLock("myLock");
        try {
            // 尝试加锁,等待 5s,锁有效期 30s(自动续期)
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                // 模拟业务操作,例如扣减库存
                System.out.println("获取锁成功,执行业务...");
                Thread.sleep(2000);
            } else {
                System.out.println("获取锁失败,请求限流或重试");
            }
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();  // 确保释放
            }
        }
    }
}

关键点tryLock 带超时防止死锁;Redisson 自带 WatchDog 自动续期(默认 30s 一次),业务执行超时不会锁过期。


6. 高并发限流器:基于 Redis + Lua 的令牌桶

限流是应对突发流量的第一道防线。以下实现固定窗口计数器滑动窗口的 Lua 脚本。

6.1 滑动窗口限流 Lua 脚本(代码块5,约 150 字符)

lua

lua 复制代码
-- 限流 key
local key = KEYS[1]
-- 窗口大小(秒)
local window = tonumber(ARGV[1])
-- 最大允许请求数
local limit = tonumber(ARGV[2])
-- 当前时间戳
local now = tonumber(ARGV[3])

-- 移除窗口外的旧数据
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
-- 统计当前窗口请求数
local current = redis.call('ZCARD', key)
if current < limit then
    redis.call('ZADD', key, now, now .. '_' .. math.random())
    redis.call('EXPIRE', key, window + 1)
    return 1  -- 允许
else
    return 0  -- 限流
end

6.2 Java 调用限流(代码块6,约 180 字符)

java

java 复制代码
import redis.clients.jedis.Jedis;
import java.util.List;

public class RateLimiter {
    private static final String LUA_SCRIPT = 
        "local key=KEYS[1] " +
        "local window=tonumber(ARGV[1]) " +
        "local limit=tonumber(ARGV[2]) " +
        "local now=tonumber(ARGV[3]) " +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window*1000) " +
        "local current=redis.call('ZCARD', key) " +
        "if current < limit then " +
        "    redis.call('ZADD', key, now, now..'_'..math.random()) " +
        "    redis.call('EXPIRE', key, window+1) " +
        "    return 1 " +
        "else return 0 end";

    public static boolean allowRequest(String key, int windowSeconds, int maxRequests) {
        try (Jedis jedis = RedisSentinelClient.getResource()) {
            long now = System.currentTimeMillis();
            Object result = jedis.eval(LUA_SCRIPT, List.of(key), 
                List.of(String.valueOf(windowSeconds), String.valueOf(maxRequests), String.valueOf(now)));
            return (Long) result == 1;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            boolean allowed = allowRequest("api:limit", 10, 5);
            System.out.println("第" + i + "次请求: " + (allowed ? "通过" : "限流"));
            Thread.sleep(500);
        }
    }
}

此代码模拟 10 秒窗口内最多 5 次请求,超过则限流。


7. Redis Cluster 集群操作(代码块7)

对于 Cluster 模式,使用 Lettuce(支持异步和拓扑刷新)比 Jedis 更优。以下为 Lettuce 连接 Cluster 并执行批量操作(约 250 字符)。

java

dart 复制代码
import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;

import java.time.Duration;
import java.util.Arrays;

public class RedisClusterDemo {
    private static RedisClusterClient clusterClient;
    private static StatefulRedisClusterConnection<String, String> connection;

    static {
        RedisURI uri = RedisURI.builder()
            .withHost("127.0.0.1")
            .withPort(7000)
            .withTimeout(Duration.ofSeconds(10))
            .build();
        clusterClient = RedisClusterClient.create(Arrays.asList(uri));
        connection = clusterClient.connect();
    }

    // 同步执行 set/get
    public static void testCluster() {
        RedisAdvancedClusterCommands<String, String> sync = connection.sync();
        sync.set("cluster:key", "cluster_value");
        System.out.println("读取: " + sync.get("cluster:key"));
        // 批量管道操作(提升并发)
        sync.multi();
        sync.set("batch1", "v1");
        sync.set("batch2", "v2");
        sync.set("batch3", "v3");
        sync.exec();  // 实际 Cluster 模式下 multi 可能有槽限制,建议使用 async 批处理
    }

    public static void close() {
        connection.close();
        clusterClient.shutdown();
    }
}

注意 :Cluster 模式不支持跨槽事务,批量操作建议使用异步 flushCommands 或 Pipeline(Lettuce 的 autoFlushCommands)。


8. 缓存击穿/穿透/雪崩防护(代码块8)

8.1 布隆过滤器防穿透(Redisson 实现)

java

typescript 复制代码
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;

public class BloomFilterDemo {
    private static RBloomFilter<String> filter;

    static {
        RedissonClient client = RedissonManager.getClient();
        filter = client.getBloomFilter("itemBloom");
        filter.tryInit(100000L, 0.01); // 预期元素 10 万,误判率 1%
    }

    // 查询前先判断
    public static String getWithFilter(String key) {
        if (!filter.contains(key)) {
            return null; // 一定不存在,直接返回
        }
        // 否则查缓存/DB...
        return "value from cache";
    }
}

8.2 互斥锁重建缓存(防击穿)

java

typescript 复制代码
public String getValue(String key) {
    String val = jedis.get(key);
    if (val != null) return val;
    // 尝试加锁(setnx)
    String lockKey = "lock:" + key;
    String token = UUID.randomUUID().toString();
    if (jedis.set(lockKey, token, "NX", "EX", 10) != null) {
        try {
            // 重新查 DB 并写入缓存
            String dbVal = queryDB(key);
            jedis.setex(key, 3600, dbVal);
            return dbVal;
        } finally {
            // 释放锁(Lua 保证原子性)
            String lua = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            jedis.eval(lua, List.of(lockKey), List.of(token));
        }
    } else {
        // 等待其他线程重建完成
        Thread.sleep(50);
        return getValue(key);
    }
}

9. 监控与运维命令

  • 监控 QPSredis-cli INFO stats 查看 instantaneous_ops_per_sec
  • 慢查询SLOWLOG GET 10 分析慢命令。
  • 内存分析MEMORY STATS--bigkeys 扫描大 Key。
  • 哨兵状态redis-cli -p 26379 SENTINEL masters

10. 总结与最佳实践

本文从架构选型、环境搭建、客户端配置、连接池、分布式锁、限流、防击穿穿透、Cluster 操作等多个维度,提供了完整的高并发高可用 Redis 解决方案。全部代码(含注释)合计约 1600 字符,可直接复制到项目中使用。

核心要点:

  • 高可用:Sentinel 或 Cluster,避免单点。
  • 高并发:合理设置连接池,使用 Pipeline 批量,异步客户端(Lettuce)。
  • 稳定性:分布式锁使用 Redisson,限流用 Lua 脚本,缓存异常用布隆+互斥锁。
  • 监控:开启慢日志,配置内存淘汰策略(allkeys-lru)。

生产注意事项

  • 所有 Redis 操作必须设置超时时间,避免线程阻塞。
  • 大 Key(>10KB)需拆分,避免网络传输耗时。
  • 密码应使用强密码,并定期轮换。

希望本文成为你解决 Redis 高并发高可用问题的实战手册。欢迎在思否评论区交流实际踩坑经验!

相关推荐
ApacheSeaTunnel1 小时前
一个关于数据集成的真相:链路 Success 不等于数据真实可靠
大数据·开源·数据集成·seatunnel·技术分享·数据同步
hengcaib2 小时前
2026工程项目信息平台能力解析:中策大数据建筑工业双赛道的全周期服务实力
大数据
tedcloud1232 小时前
Awesome DSH Plugin 怎么用?给 DeepSeek Harness 搭建自己的插件生态
大数据·linux·服务器·人工智能·音视频
楷哥爱开发2 小时前
IPFoxy爬虫代理配置指南:从0搭建Crawl4AI网页爬虫教程
大数据·运维·人工智能·爬虫
云间月13143 小时前
Elasticsearch 数据怎么看?部署 Kibana,从索引查询到可视化图表
大数据·elasticsearch·jenkins
Data_Journal3 小时前
如何使用 Python 抓取 Google Flights:分步指南
大数据·开发语言·数据库·python·scrapy
shujudang3 小时前
从数据闭环到 Agent 协同:企业营销自动化如何演进为智能运营?
大数据·运维·人工智能·数据分析·自动化
动恰客流统计3 小时前
ReID边缘计算视觉客流统计:无网户外场景的数字化落地路径
大数据·人工智能
小白说大模型4 小时前
Codex 实战:用 AI 写运维脚本
大数据·运维·网络·人工智能·机器学习·prompt