Redis 实战指南

Redis 实战指南:高性能缓存与数据结构实践

目录

  • [1. Redis 简介](#1. Redis 简介 "#1-redis-%E7%AE%80%E4%BB%8B")
  • [2. Redis 数据结构](#2. Redis 数据结构 "#2-redis-%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84")
  • [3. Java 客户端集成](#3. Java 客户端集成 "#3-java-%E5%AE%A2%E6%88%B7%E7%AB%AF%E9%9B%86%E6%88%90")
  • [4. 缓存实战场景](#4. 缓存实战场景 "#4-%E7%BC%93%E5%AD%98%E5%AE%9E%E6%88%98%E5%9C%BA%E6%99%AF")
  • [5. 分布式锁实现](#5. 分布式锁实现 "#5-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%AE%9E%E7%8E%B0")
  • [6. 消息队列与发布订阅](#6. 消息队列与发布订阅 "#6-%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E4%B8%8E%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85")
  • [7. 持久化机制](#7. 持久化机制 "#7-%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6")
  • [8. 集群架构](#8. 集群架构 "#8-%E9%9B%86%E7%BE%A4%E6%9E%B6%E6%9E%84")
  • [9. 性能优化](#9. 性能优化 "#9-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96")
  • [10. 常见问题与解决方案](#10. 常见问题与解决方案 "#10-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88")
  • [11. Spring Boot 集成](#11. Spring Boot 集成 "#11-spring-boot-%E9%9B%86%E6%88%90")
  • [12. 监控与运维](#12. 监控与运维 "#12-%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%BF%90%E7%BB%B4")
  • [13. 总结](#13. 总结 "#13-%E6%80%BB%E7%BB%93")

1. Redis 简介

Redis (Remote Dictionary Server) 是一个开源的高性能键值对数据库,支持多种数据结构,广泛用于缓存、消息队列、分布式锁等场景。

1.1 核心特性

  • 高性能: 单机10万+ QPS,基于内存操作
  • 丰富数据结构: String、Hash、List、Set、ZSet等
  • 持久化: 支持RDB和AOF两种持久化方式
  • 高可用: 支持主从复制、哨兵、集群模式
  • 原子操作: 所有操作都是原子性的
  • Lua脚本: 支持服务端Lua脚本
  • 事务支持: MULTI/EXEC事务机制

1.2 应用场景

markdown 复制代码
Redis 典型应用场景

1. 缓存系统
   ┌────────┐     Miss    ┌────────┐
   │ Client │────────────▶│  Redis │
   └────────┘             └────────┘
       │                       │
       │ Hit                   │ Miss
       ▼                       ▼
   ┌────────┐             ┌────────┐
   │ Result │◀────────────│   DB   │
   └────────┘   Set Cache └────────┘

2. 分布式锁
   Thread A ──▶ SETNX lock ──▶ 获得锁 ──▶ 执行业务
   Thread B ──▶ SETNX lock ──▶ 等待

3. 计数器/限流
   INCR user:1001:requests

4. 排行榜
   ZADD leaderboard score user

5. 消息队列
   LPUSH / BRPOP

6. 会话存储
   Session ID ──▶ Redis ──▶ User Data

1.3 Redis vs 其他缓存

复制代码
性能与特性对比
┌─────────────────┬──────────┬──────────┬──────────┐
│   Feature       │  Redis   │ Memcached│  Ehcache │
├─────────────────┼──────────┼──────────┼──────────┤
│ 数据结构        │  丰富    │   简单   │   简单   │
├─────────────────┼──────────┼──────────┼──────────┤
│ 持久化          │  支持    │  不支持  │   支持   │
├─────────────────┼──────────┼──────────┼──────────┤
│ 集群            │  支持    │   支持   │   支持   │
├─────────────────┼──────────┼──────────┼──────────┤
│ 事务            │  支持    │  不支持  │  不支持  │
├─────────────────┼──────────┼──────────┼──────────┤
│ Lua脚本         │  支持    │  不支持  │  不支持  │
├─────────────────┼──────────┼──────────┼──────────┤
│ 发布订阅        │  支持    │  不支持  │  不支持  │
└─────────────────┴──────────┴──────────┴──────────┘

2. Redis 数据结构

2.1 数据结构概览

yaml 复制代码
Redis 数据结构

String (字符串)
├─ 二进制安全
├─ 最大512MB
└─ 应用: 缓存、计数器、分布式锁

Hash (哈希表)
├─ 键值对集合
├─ 适合存储对象
└─ 应用: 用户信息、商品详情

List (列表)
├─ 双向链表
├─ 有序可重复
└─ 应用: 消息队列、最新列表

Set (集合)
├─ 无序不重复
├─ 支持集合运算
└─ 应用: 标签、共同好友

ZSet (有序集合)
├─ 有序不重复
├─ 每个元素关联分数
└─ 应用: 排行榜、延迟队列

特殊类型:
├─ Bitmap: 位图,统计活跃用户
├─ HyperLogLog: 基数统计
├─ Geo: 地理位置
└─ Stream: 消息流(5.0+)

2.2 底层数据结构

yaml 复制代码
Redis 底层实现

String
├─ int: 整数值
├─ embstr: 短字符串(<=44字节)
└─ raw: 长字符串

Hash
├─ ziplist: 元素少且小
└─ hashtable: 元素多或大

List
├─ ziplist: 元素少且小
└─ quicklist: 默认实现

Set
├─ intset: 整数且元素少
└─ hashtable: 其他情况

ZSet
├─ ziplist: 元素少且小
└─ skiplist + hashtable: 其他情况

跳表结构 (ZSet):
Level 3:  1 ─────────────────────▶ 9
Level 2:  1 ────────▶ 5 ─────────▶ 9
Level 1:  1 ───▶ 3 ──▶ 5 ───▶ 7 ──▶ 9
Level 0:  1 ─▶ 2 ─▶ 3 ─▶ 4 ─▶ 5 ─▶ 6 ─▶ 7 ─▶ 8 ─▶ 9

3. Java 客户端集成

3.1 Maven 依赖

xml 复制代码
<!-- pom.xml -->
<properties>
    <jedis.version>5.0.2</jedis.version>
    <lettuce.version>6.3.0.RELEASE</lettuce.version>
    <redisson.version>3.24.3</redisson.version>
</properties>

<dependencies>
    <!-- Jedis 客户端 -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>${jedis.version}</version>
    </dependency>

    <!-- Lettuce 客户端 (Spring Boot 默认) -->
    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>${lettuce.version}</version>
    </dependency>

    <!-- Redisson 客户端 (功能丰富) -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>${redisson.version}</version>
    </dependency>

    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- 连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.12.0</version>
    </dependency>

    <!-- JSON 序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.3</version>
    </dependency>
</dependencies>

3.2 Jedis 基础使用

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;
import java.util.*;

/**
 * Jedis 基础操作示例
 */
public class JedisExample {

    private static JedisPool jedisPool;

    /**
     * 初始化连接池
     */
    public static void initPool() {
        JedisPoolConfig config = new JedisPoolConfig();

        // 连接池配置
        config.setMaxTotal(100);          // 最大连接数
        config.setMaxIdle(20);            // 最大空闲连接
        config.setMinIdle(5);             // 最小空闲连接
        config.setMaxWait(Duration.ofSeconds(10)); // 最大等待时间

        // 连接测试
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);
        config.setTestWhileIdle(true);

        jedisPool = new JedisPool(config, "localhost", 6379, 3000, null);
    }

    /**
     * 获取 Jedis 连接
     */
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * String 操作
     */
    public static void stringOperations() {
        try (Jedis jedis = getJedis()) {
            // 设置值
            jedis.set("name", "Redis");

            // 设置带过期时间
            jedis.setex("session:1001", 3600, "user_data");

            // NX: 不存在才设置, XX: 存在才设置
            jedis.set("lock:order", "1",
                     new redis.clients.jedis.params.SetParams()
                         .nx().ex(30));

            // 获取值
            String value = jedis.get("name");
            System.out.println("name = " + value);

            // 自增
            jedis.set("counter", "0");
            Long count = jedis.incr("counter");
            System.out.println("counter = " + count);

            // 批量操作
            jedis.mset("key1", "value1", "key2", "value2");
            List<String> values = jedis.mget("key1", "key2");
            System.out.println("mget = " + values);
        }
    }

    /**
     * Hash 操作
     */
    public static void hashOperations() {
        try (Jedis jedis = getJedis()) {
            String key = "user:1001";

            // 设置字段
            jedis.hset(key, "name", "张三");
            jedis.hset(key, "age", "25");
            jedis.hset(key, "city", "北京");

            // 批量设置
            Map<String, String> userMap = new HashMap<>();
            userMap.put("email", "zhangsan@example.com");
            userMap.put("phone", "13800138000");
            jedis.hset(key, userMap);

            // 获取字段
            String name = jedis.hget(key, "name");
            System.out.println("name = " + name);

            // 获取所有字段
            Map<String, String> user = jedis.hgetAll(key);
            System.out.println("user = " + user);

            // 字段自增
            jedis.hincrBy(key, "age", 1);

            // 判断字段是否存在
            boolean exists = jedis.hexists(key, "name");
            System.out.println("name exists = " + exists);
        }
    }

    /**
     * List 操作
     */
    public static void listOperations() {
        try (Jedis jedis = getJedis()) {
            String key = "queue:tasks";

            // 左侧插入
            jedis.lpush(key, "task1", "task2", "task3");

            // 右侧插入
            jedis.rpush(key, "task4", "task5");

            // 获取范围元素
            List<String> tasks = jedis.lrange(key, 0, -1);
            System.out.println("tasks = " + tasks);

            // 弹出元素
            String task = jedis.rpop(key);
            System.out.println("popped = " + task);

            // 阻塞弹出 (消息队列常用)
            // List<String> result = jedis.brpop(10, key);

            // 获取长度
            Long length = jedis.llen(key);
            System.out.println("length = " + length);

            // 裁剪列表 (保留最新N条)
            jedis.ltrim(key, 0, 99);
        }
    }

    /**
     * Set 操作
     */
    public static void setOperations() {
        try (Jedis jedis = getJedis()) {
            // 添加元素
            jedis.sadd("tags:1001", "java", "redis", "mysql");
            jedis.sadd("tags:1002", "java", "python", "kafka");

            // 获取所有元素
            Set<String> tags = jedis.smembers("tags:1001");
            System.out.println("tags = " + tags);

            // 判断是否存在
            boolean isMember = jedis.sismember("tags:1001", "java");
            System.out.println("is member = " + isMember);

            // 集合运算
            // 交集 (共同标签)
            Set<String> inter = jedis.sinter("tags:1001", "tags:1002");
            System.out.println("intersection = " + inter);

            // 并集
            Set<String> union = jedis.sunion("tags:1001", "tags:1002");
            System.out.println("union = " + union);

            // 差集
            Set<String> diff = jedis.sdiff("tags:1001", "tags:1002");
            System.out.println("difference = " + diff);

            // 随机获取元素
            String random = jedis.srandmember("tags:1001");
            System.out.println("random = " + random);
        }
    }

    /**
     * ZSet (有序集合) 操作
     */
    public static void zsetOperations() {
        try (Jedis jedis = getJedis()) {
            String key = "leaderboard";

            // 添加元素
            jedis.zadd(key, 100, "player1");
            jedis.zadd(key, 200, "player2");
            jedis.zadd(key, 150, "player3");

            // 批量添加
            Map<String, Double> scores = new HashMap<>();
            scores.put("player4", 180.0);
            scores.put("player5", 220.0);
            jedis.zadd(key, scores);

            // 获取排名 (从0开始,按分数升序)
            Long rank = jedis.zrank(key, "player2");
            System.out.println("player2 rank = " + rank);

            // 获取逆序排名 (分数高的排前面)
            Long revRank = jedis.zrevrank(key, "player2");
            System.out.println("player2 rev rank = " + revRank);

            // 获取分数
            Double score = jedis.zscore(key, "player2");
            System.out.println("player2 score = " + score);

            // 获取排行榜 (Top N)
            List<String> top3 = jedis.zrevrange(key, 0, 2);
            System.out.println("Top 3 = " + top3);

            // 带分数获取
            List<redis.clients.jedis.resps.Tuple> top3WithScores =
                jedis.zrevrangeWithScores(key, 0, 2);
            for (redis.clients.jedis.resps.Tuple tuple : top3WithScores) {
                System.out.printf("%s: %.0f\n",
                    tuple.getElement(), tuple.getScore());
            }

            // 增加分数
            jedis.zincrby(key, 50, "player1");

            // 按分数范围获取
            List<String> range = jedis.zrangeByScore(key, 100, 200);
            System.out.println("score 100-200 = " + range);
        }
    }

    public static void main(String[] args) {
        initPool();

        System.out.println("=== String Operations ===");
        stringOperations();

        System.out.println("\n=== Hash Operations ===");
        hashOperations();

        System.out.println("\n=== List Operations ===");
        listOperations();

        System.out.println("\n=== Set Operations ===");
        setOperations();

        System.out.println("\n=== ZSet Operations ===");
        zsetOperations();

        jedisPool.close();
    }
}

3.3 Lettuce 异步操作

java 复制代码
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;

import java.util.concurrent.CompletableFuture;

/**
 * Lettuce 客户端示例
 */
public class LettuceExample {

    /**
     * 同步操作
     */
    public static void syncOperations() {
        RedisClient client = RedisClient.create("redis://localhost:6379");
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisCommands<String, String> commands = connection.sync();

        // 同步操作
        commands.set("key", "value");
        String value = commands.get("key");
        System.out.println("value = " + value);

        connection.close();
        client.shutdown();
    }

    /**
     * 异步操作
     */
    public static void asyncOperations() {
        RedisClient client = RedisClient.create("redis://localhost:6379");
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisAsyncCommands<String, String> commands = connection.async();

        // 异步操作
        CompletableFuture<String> future = commands.set("async-key", "async-value")
            .toCompletableFuture()
            .thenCompose(result -> commands.get("async-key").toCompletableFuture());

        future.thenAccept(value -> {
            System.out.println("Async value = " + value);
        }).join();

        connection.close();
        client.shutdown();
    }

    /**
     * 响应式操作
     */
    public static void reactiveOperations() {
        RedisClient client = RedisClient.create("redis://localhost:6379");
        StatefulRedisConnection<String, String> connection = client.connect();
        var commands = connection.reactive();

        // 响应式操作
        commands.set("reactive-key", "reactive-value")
            .then(commands.get("reactive-key"))
            .subscribe(value -> {
                System.out.println("Reactive value = " + value);
            });

        // 等待完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        connection.close();
        client.shutdown();
    }

    public static void main(String[] args) {
        syncOperations();
        asyncOperations();
        reactiveOperations();
    }
}

4. 缓存实战场景

4.1 多级缓存架构

markdown 复制代码
多级缓存架构

        请求
          │
          ▼
    ┌──────────┐
    │ 本地缓存  │  L1 Cache (Caffeine/Guava)
    │ (进程内) │  延迟: <1ms
    └────┬─────┘
         │ Miss
         ▼
    ┌──────────┐
    │  Redis   │  L2 Cache (分布式)
    │ (分布式) │  延迟: 1-5ms
    └────┬─────┘
         │ Miss
         ▼
    ┌──────────┐
    │ Database │  数据源
    │  (MySQL) │  延迟: 10-100ms
    └──────────┘

优势:
1. 本地缓存减少网络开销
2. Redis 保证数据一致性
3. 多层防护,高可用
java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * 多级缓存实现
 */
public class MultiLevelCache {

    private final Cache<String, Object> localCache;
    private final JedisPool jedisPool;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public MultiLevelCache(JedisPool jedisPool) {
        this.jedisPool = jedisPool;

        // 本地缓存配置
        this.localCache = Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
    }

    /**
     * 获取缓存
     */
    public <T> T get(String key, Class<T> type,
                     java.util.function.Supplier<T> dbLoader) {
        // 1. 查询本地缓存
        T value = (T) localCache.getIfPresent(key);
        if (value != null) {
            System.out.println("L1 Cache Hit: " + key);
            return value;
        }

        // 2. 查询 Redis 缓存
        try (Jedis jedis = jedisPool.getResource()) {
            String json = jedis.get(key);
            if (json != null) {
                System.out.println("L2 Cache Hit: " + key);
                value = objectMapper.readValue(json, type);

                // 回填本地缓存
                localCache.put(key, value);
                return value;
            }
        } catch (Exception e) {
            System.err.println("Redis get error: " + e.getMessage());
        }

        // 3. 查询数据库
        System.out.println("Cache Miss, loading from DB: " + key);
        value = dbLoader.get();

        if (value != null) {
            // 回填缓存
            put(key, value, 3600);
        }

        return value;
    }

    /**
     * 设置缓存
     */
    public void put(String key, Object value, int expireSeconds) {
        // 写入本地缓存
        localCache.put(key, value);

        // 写入 Redis
        try (Jedis jedis = jedisPool.getResource()) {
            String json = objectMapper.writeValueAsString(value);
            jedis.setex(key, expireSeconds, json);
        } catch (Exception e) {
            System.err.println("Redis set error: " + e.getMessage());
        }
    }

    /**
     * 删除缓存
     */
    public void delete(String key) {
        // 删除本地缓存
        localCache.invalidate(key);

        // 删除 Redis
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.del(key);
        }
    }

    /**
     * 使用示例
     */
    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        MultiLevelCache cache = new MultiLevelCache(jedisPool);

        // 获取用户信息
        User user = cache.get("user:1001", User.class, () -> {
            // 模拟数据库查询
            return new User(1001L, "张三", "zhangsan@example.com");
        });

        System.out.println("User: " + user);

        jedisPool.close();
    }

    static class User {
        private Long id;
        private String name;
        private String email;

        public User() {}

        public User(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }

        @Override
        public String toString() {
            return String.format("User{id=%d, name='%s', email='%s'}",
                id, name, email);
        }

        // Getters and Setters
        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
    }
}

4.2 缓存更新策略

java 复制代码
/**
 * 缓存更新策略
 */
public class CacheUpdateStrategies {

    private final JedisPool jedisPool;

    public CacheUpdateStrategies(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 策略1: Cache Aside (旁路缓存)
     * 读: 先读缓存,没有则读数据库,回填缓存
     * 写: 先更新数据库,再删除缓存
     */
    public User getUserCacheAside(Long userId) {
        String key = "user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            // 1. 查询缓存
            String json = jedis.get(key);
            if (json != null) {
                return parseUser(json);
            }

            // 2. 查询数据库
            User user = getUserFromDB(userId);

            // 3. 回填缓存
            if (user != null) {
                jedis.setex(key, 3600, toJson(user));
            }

            return user;
        }
    }

    public void updateUserCacheAside(User user) {
        // 1. 先更新数据库
        updateUserInDB(user);

        // 2. 再删除缓存
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.del("user:" + user.getId());
        }

        // 注意: 删除缓存而非更新缓存
        // 避免并发写导致的数据不一致
    }

    /**
     * 策略2: Read/Write Through (读写穿透)
     * 缓存作为主要数据源,由缓存组件负责读写数据库
     */
    public User getUserReadThrough(Long userId) {
        String key = "user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            String json = jedis.get(key);

            if (json != null) {
                return parseUser(json);
            }

            // 缓存组件负责从数据库加载
            User user = getUserFromDB(userId);
            if (user != null) {
                jedis.setex(key, 3600, toJson(user));
            }
            return user;
        }
    }

    /**
     * 策略3: Write Behind (异步写回)
     * 写操作只更新缓存,异步批量写入数据库
     */
    public void updateUserWriteBehind(User user) {
        String key = "user:" + user.getId();

        try (Jedis jedis = jedisPool.getResource()) {
            // 1. 只更新缓存
            jedis.setex(key, 3600, toJson(user));

            // 2. 加入异步写队列
            jedis.lpush("write_back_queue", toJson(user));
        }

        // 异步任务定期处理队列,批量写入数据库
    }

    /**
     * 延迟双删策略
     * 解决数据库主从延迟导致的缓存不一致
     */
    public void updateUserDelayedDoubleDelete(User user) {
        String key = "user:" + user.getId();

        try (Jedis jedis = jedisPool.getResource()) {
            // 1. 先删除缓存
            jedis.del(key);

            // 2. 更新数据库
            updateUserInDB(user);

            // 3. 延迟再次删除缓存 (异步)
            CompletableFuture.runAsync(() -> {
                try {
                    // 等待主从同步
                    Thread.sleep(500);
                    jedis.del(key);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
    }

    // 辅助方法
    private User getUserFromDB(Long userId) {
        // 模拟数据库查询
        return new User(userId, "User" + userId, "user@example.com");
    }

    private void updateUserInDB(User user) {
        // 模拟数据库更新
        System.out.println("Updated user in DB: " + user.getId());
    }

    private User parseUser(String json) {
        // JSON 解析
        return new User();
    }

    private String toJson(User user) {
        // JSON 序列化
        return "{}";
    }

    static class User {
        private Long id;
        private String name;
        private String email;

        public User() {}

        public User(Long id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }

        public Long getId() { return id; }
        public void setId(Long id) { this.id = id; }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
    }
}

5. 分布式锁实现

5.1 基础分布式锁

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.UUID;

/**
 * Redis 分布式锁实现
 */
public class RedisDistributedLock {

    private final JedisPool jedisPool;
    private final String lockKey;
    private final String lockValue;
    private final int expireSeconds;

    public RedisDistributedLock(JedisPool jedisPool, String lockKey,
                                int expireSeconds) {
        this.jedisPool = jedisPool;
        this.lockKey = "lock:" + lockKey;
        this.lockValue = UUID.randomUUID().toString();
        this.expireSeconds = expireSeconds;
    }

    /**
     * 获取锁
     */
    public boolean tryLock() {
        try (Jedis jedis = jedisPool.getResource()) {
            // SET key value NX EX seconds
            String result = jedis.set(lockKey, lockValue,
                SetParams.setParams().nx().ex(expireSeconds));

            return "OK".equals(result);
        }
    }

    /**
     * 获取锁 (带重试)
     */
    public boolean tryLock(int retryTimes, long retryIntervalMs) {
        for (int i = 0; i < retryTimes; i++) {
            if (tryLock()) {
                return true;
            }

            try {
                Thread.sleep(retryIntervalMs);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false;
    }

    /**
     * 释放锁 (使用 Lua 脚本保证原子性)
     */
    public boolean unlock() {
        String luaScript =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";

        try (Jedis jedis = jedisPool.getResource()) {
            Object result = jedis.eval(luaScript,
                Collections.singletonList(lockKey),
                Collections.singletonList(lockValue));

            return Long.valueOf(1L).equals(result);
        }
    }

    /**
     * 使用示例
     */
    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("localhost", 6379);

        RedisDistributedLock lock =
            new RedisDistributedLock(jedisPool, "order:create", 30);

        try {
            if (lock.tryLock(3, 100)) {
                System.out.println("获取锁成功,执行业务逻辑...");

                // 执行业务逻辑
                Thread.sleep(1000);

                System.out.println("业务逻辑执行完成");
            } else {
                System.out.println("获取锁失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("锁已释放");
        }

        jedisPool.close();
    }
}

5.2 Redisson 分布式锁

java 复制代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

/**
 * Redisson 分布式锁
 * 支持: 可重入、公平锁、读写锁、红锁等
 */
public class RedissonLockExample {

    private static RedissonClient redisson;

    /**
     * 初始化 Redisson
     */
    public static void initRedisson() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setDatabase(0)
            .setConnectionPoolSize(10)
            .setConnectionMinimumIdleSize(5);

        redisson = Redisson.create(config);
    }

    /**
     * 基本锁操作
     */
    public static void basicLock() {
        RLock lock = redisson.getLock("myLock");

        try {
            // 获取锁,最多等待10秒,锁定后30秒自动释放
            boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);

            if (locked) {
                System.out.println("获取锁成功");

                // 执行业务逻辑
                Thread.sleep(5000);

            } else {
                System.out.println("获取锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 只有锁持有者才能释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("锁已释放");
            }
        }
    }

    /**
     * 可重入锁
     */
    public static void reentrantLock() {
        RLock lock = redisson.getLock("reentrantLock");

        try {
            lock.lock();
            System.out.println("第一次获取锁");

            // 可重入: 同一线程可以再次获取
            lock.lock();
            System.out.println("第二次获取锁 (可重入)");

            lock.unlock();
            System.out.println("第一次释放");

            lock.unlock();
            System.out.println("第二次释放");

        } finally {
            while (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 公平锁
     */
    public static void fairLock() {
        RLock fairLock = redisson.getFairLock("fairLock");

        try {
            // 按请求顺序获取锁
            fairLock.lock();
            System.out.println("获取公平锁成功");

            // 执行业务逻辑
            Thread.sleep(1000);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            fairLock.unlock();
        }
    }

    /**
     * 读写锁
     */
    public static void readWriteLock() {
        RReadWriteLock rwLock = redisson.getReadWriteLock("rwLock");

        // 读锁 (共享锁)
        RLock readLock = rwLock.readLock();
        // 写锁 (排他锁)
        RLock writeLock = rwLock.writeLock();

        // 读操作 (多个线程可以同时读)
        new Thread(() -> {
            try {
                readLock.lock();
                System.out.println("读线程1获取读锁");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                readLock.unlock();
                System.out.println("读线程1释放读锁");
            }
        }).start();

        // 写操作 (必须等所有读锁释放)
        new Thread(() -> {
            try {
                writeLock.lock();
                System.out.println("写线程获取写锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                writeLock.unlock();
                System.out.println("写线程释放写锁");
            }
        }).start();
    }

    /**
     * 看门狗自动续期
     */
    public static void watchdogDemo() {
        RLock lock = redisson.getLock("watchdogLock");

        try {
            // 不指定过期时间,看门狗会自动续期
            lock.lock();
            System.out.println("获取锁成功,看门狗自动续期");

            // 模拟长时间业务
            Thread.sleep(35000);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        initRedisson();

        basicLock();
        // reentrantLock();
        // fairLock();
        // readWriteLock();
        // watchdogDemo();

        redisson.shutdown();
    }
}

5.3 分布式锁最佳实践

markdown 复制代码
分布式锁注意事项

1. 锁超时问题
   ┌────────────┐
   │ 获取锁     │ 设置超时时间
   ├────────────┤
   │ 执行业务   │ 如果超时会自动释放
   ├────────────┤
   │ 释放锁     │ 可能释放了别人的锁
   └────────────┘

   解决:
   - 使用唯一标识
   - Redisson 看门狗自动续期

2. 锁不可重入
   Thread A: lock() -> lock() -> 死锁

   解决: 使用可重入锁

3. 单点故障
   Redis 主节点宕机,锁丢失

   解决:
   - Red Lock 算法
   - Redis 集群

4. 锁释放失败
   业务异常,锁未释放

   解决:
   - 设置过期时间
   - finally 中释放

6. 消息队列与发布订阅

6.1 List 实现消息队列

java 复制代码
/**
 * Redis List 实现简单消息队列
 */
public class RedisMessageQueue {

    private final JedisPool jedisPool;
    private final String queueName;

    public RedisMessageQueue(JedisPool jedisPool, String queueName) {
        this.jedisPool = jedisPool;
        this.queueName = queueName;
    }

    /**
     * 发送消息
     */
    public void send(String message) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.lpush(queueName, message);
            System.out.println("消息已发送: " + message);
        }
    }

    /**
     * 阻塞接收消息
     */
    public String receive(int timeoutSeconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            List<String> result = jedis.brpop(timeoutSeconds, queueName);

            if (result != null && result.size() > 1) {
                return result.get(1);
            }
            return null;
        }
    }

    /**
     * 可靠队列 (带确认)
     */
    public String receiveReliable(int timeoutSeconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 从队列取出并放入处理中队列
            String message = jedis.brpoplpush(
                queueName,
                queueName + ":processing",
                timeoutSeconds
            );
            return message;
        }
    }

    /**
     * 确认消息处理完成
     */
    public void ack(String message) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.lrem(queueName + ":processing", 1, message);
        }
    }

    /**
     * 延迟队列 (使用 ZSet)
     */
    public void sendDelayed(String message, long delaySeconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            double score = System.currentTimeMillis() / 1000.0 + delaySeconds;
            jedis.zadd(queueName + ":delayed", score, message);
            System.out.println("延迟消息已发送,延迟: " + delaySeconds + "秒");
        }
    }

    /**
     * 处理延迟消息
     */
    public void processDelayed() {
        try (Jedis jedis = jedisPool.getResource()) {
            while (true) {
                double now = System.currentTimeMillis() / 1000.0;

                // 获取到期的消息
                List<String> messages = jedis.zrangeByScore(
                    queueName + ":delayed", 0, now, 0, 10);

                if (messages.isEmpty()) {
                    Thread.sleep(100);
                    continue;
                }

                for (String message : messages) {
                    // 移动到主队列
                    Long removed = jedis.zrem(queueName + ":delayed", message);
                    if (removed > 0) {
                        jedis.lpush(queueName, message);
                        System.out.println("延迟消息已到期: " + message);
                    }
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) throws Exception {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        RedisMessageQueue queue = new RedisMessageQueue(jedisPool, "task_queue");

        // 生产者
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                queue.send("Task " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();

        // 消费者
        new Thread(() -> {
            while (true) {
                String message = queue.receive(5);
                if (message != null) {
                    System.out.println("处理消息: " + message);
                }
            }
        }).start();

        Thread.sleep(10000);
        jedisPool.close();
    }
}

6.2 发布订阅

java 复制代码
import redis.clients.jedis.JedisPubSub;

/**
 * Redis 发布订阅
 */
public class RedisPubSub {

    private final JedisPool jedisPool;

    public RedisPubSub(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 发布消息
     */
    public void publish(String channel, String message) {
        try (Jedis jedis = jedisPool.getResource()) {
            Long receivers = jedis.publish(channel, message);
            System.out.printf("消息发布到 %s,接收者: %d\n", channel, receivers);
        }
    }

    /**
     * 订阅频道
     */
    public void subscribe(String... channels) {
        new Thread(() -> {
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.subscribe(new JedisPubSub() {
                    @Override
                    public void onMessage(String channel, String message) {
                        System.out.printf("收到消息 [%s]: %s\n", channel, message);

                        // 处理消息
                        processMessage(channel, message);
                    }

                    @Override
                    public void onSubscribe(String channel, int subscribedChannels) {
                        System.out.printf("订阅成功: %s,当前订阅数: %d\n",
                            channel, subscribedChannels);
                    }

                    @Override
                    public void onUnsubscribe(String channel, int subscribedChannels) {
                        System.out.printf("取消订阅: %s,当前订阅数: %d\n",
                            channel, subscribedChannels);
                    }
                }, channels);
            }
        }).start();
    }

    /**
     * 模式订阅
     */
    public void psubscribe(String... patterns) {
        new Thread(() -> {
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.psubscribe(new JedisPubSub() {
                    @Override
                    public void onPMessage(String pattern, String channel,
                                          String message) {
                        System.out.printf("模式匹配 [%s] 频道 [%s]: %s\n",
                            pattern, channel, message);
                    }
                }, patterns);
            }
        }).start();
    }

    private void processMessage(String channel, String message) {
        // 业务处理
        System.out.println("处理消息: " + message);
    }

    public static void main(String[] args) throws Exception {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        RedisPubSub pubSub = new RedisPubSub(jedisPool);

        // 订阅频道
        pubSub.subscribe("news", "events");

        // 模式订阅
        pubSub.psubscribe("order:*");

        Thread.sleep(1000);

        // 发布消息
        pubSub.publish("news", "今日新闻");
        pubSub.publish("events", "系统事件");
        pubSub.publish("order:created", "订单创建");
        pubSub.publish("order:paid", "订单支付");

        Thread.sleep(2000);
        jedisPool.close();
    }
}

6.3 Stream 消息队列 (Redis 5.0+)

java 复制代码
import redis.clients.jedis.StreamEntry;
import redis.clients.jedis.StreamEntryID;
import redis.clients.jedis.params.XAddParams;
import redis.clients.jedis.params.XReadGroupParams;
import redis.clients.jedis.resps.StreamConsumersInfo;
import redis.clients.jedis.resps.StreamGroupInfo;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Redis Stream 消息队列
 * 特点: 持久化、消费者组、消息确认
 */
public class RedisStreamQueue {

    private final JedisPool jedisPool;
    private final String streamKey;

    public RedisStreamQueue(JedisPool jedisPool, String streamKey) {
        this.jedisPool = jedisPool;
        this.streamKey = streamKey;
    }

    /**
     * 创建消费者组
     */
    public void createConsumerGroup(String groupName) {
        try (Jedis jedis = jedisPool.getResource()) {
            try {
                jedis.xgroupCreate(streamKey, groupName,
                    StreamEntryID.LAST_ENTRY, true);
                System.out.println("消费者组创建成功: " + groupName);
            } catch (Exception e) {
                if (e.getMessage().contains("BUSYGROUP")) {
                    System.out.println("消费者组已存在: " + groupName);
                } else {
                    throw e;
                }
            }
        }
    }

    /**
     * 发送消息
     */
    public String send(Map<String, String> message) {
        try (Jedis jedis = jedisPool.getResource()) {
            StreamEntryID id = jedis.xadd(streamKey,
                XAddParams.xAddParams().maxLen(10000), message);
            System.out.println("消息已发送,ID: " + id);
            return id.toString();
        }
    }

    /**
     * 消费者组消费消息
     */
    public List<Map.Entry<String, List<StreamEntry>>> consume(
            String groupName, String consumerName, int count, long blockMs) {

        try (Jedis jedis = jedisPool.getResource()) {
            Map<String, StreamEntryID> streams = new HashMap<>();
            streams.put(streamKey, StreamEntryID.UNRECEIVED_ENTRY);

            return jedis.xreadGroup(groupName, consumerName,
                XReadGroupParams.xReadGroupParams()
                    .count(count)
                    .block((int) blockMs),
                streams);
        }
    }

    /**
     * 确认消息
     */
    public void ack(String groupName, String... messageIds) {
        try (Jedis jedis = jedisPool.getResource()) {
            StreamEntryID[] ids = new StreamEntryID[messageIds.length];
            for (int i = 0; i < messageIds.length; i++) {
                ids[i] = new StreamEntryID(messageIds[i]);
            }

            long acked = jedis.xack(streamKey, groupName, ids);
            System.out.println("确认消息数: " + acked);
        }
    }

    /**
     * 获取未确认消息 (用于重试)
     */
    public List<StreamEntry> getPendingMessages(String groupName,
                                                String consumerName,
                                                int count) {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.xclaim(streamKey, groupName, consumerName,
                60000, // 最小空闲时间
                XClaimParams.xClaimParams().count(count),
                StreamEntryID.MINIMUM_ID);
        }
    }

    /**
     * 查看 Stream 信息
     */
    public void printStreamInfo() {
        try (Jedis jedis = jedisPool.getResource()) {
            // 消费者组信息
            List<StreamGroupInfo> groups = jedis.xinfoGroups(streamKey);
            System.out.println("\n消费者组:");
            for (StreamGroupInfo group : groups) {
                System.out.printf("  组: %s, 消费者: %d, 待处理: %d\n",
                    group.getName(),
                    group.getConsumers(),
                    group.getPending());
            }
        }
    }

    public static void main(String[] args) throws Exception {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        RedisStreamQueue queue = new RedisStreamQueue(jedisPool, "orders");

        // 创建消费者组
        queue.createConsumerGroup("order-processor");

        // 生产者发送消息
        for (int i = 0; i < 5; i++) {
            Map<String, String> message = new HashMap<>();
            message.put("orderId", "ORDER_" + i);
            message.put("userId", "USER_" + (i % 3));
            message.put("amount", String.valueOf(100 + i * 10));

            queue.send(message);
        }

        // 消费者消费消息
        new Thread(() -> {
            while (true) {
                var results = queue.consume("order-processor",
                    "consumer-1", 10, 5000);

                if (results == null || results.isEmpty()) {
                    continue;
                }

                for (var entry : results) {
                    List<StreamEntry> messages = entry.getValue();
                    for (StreamEntry message : messages) {
                        System.out.printf("处理消息 [%s]: %s\n",
                            message.getID(), message.getFields());

                        // 处理完成后确认
                        queue.ack("order-processor",
                            message.getID().toString());
                    }
                }
            }
        }).start();

        Thread.sleep(5000);
        queue.printStreamInfo();

        jedisPool.close();
    }
}

7. 持久化机制

7.1 RDB 与 AOF

yaml 复制代码
持久化机制对比

RDB (Redis Database)
┌──────────────────────────────────────┐
│ 特点:                                │
│ - 二进制快照文件                     │
│ - 文件小,恢复快                      │
│ - fork 子进程,可能阻塞               │
│ - 可能丢失最后一次快照后的数据       │
│                                      │
│ 触发条件:                            │
│ - save 900 1    # 900秒内1次修改     │
│ - save 300 10   # 300秒内10次修改    │
│ - save 60 10000 # 60秒内10000次修改  │
│ - 执行 BGSAVE 命令                   │
│ - 主从复制时                         │
└──────────────────────────────────────┘

AOF (Append Only File)
┌──────────────────────────────────────┐
│ 特点:                                │
│ - 追加写入命令日志                   │
│ - 数据安全性高                       │
│ - 文件大,恢复慢                      │
│ - 支持重写压缩                       │
│                                      │
│ 同步策略:                            │
│ - always: 每条命令都同步             │
│ - everysec: 每秒同步 (推荐)          │
│ - no: 操作系统决定                   │
│                                      │
│ 重写触发:                            │
│ - auto-aof-rewrite-percentage 100    │
│ - auto-aof-rewrite-min-size 64mb     │
└──────────────────────────────────────┘

混合持久化 (Redis 4.0+)
┌──────────────────────────────────────┐
│ RDB 头 (全量) + AOF 尾 (增量)        │
│                                      │
│ 优势:                                │
│ - 快速加载 RDB 部分                  │
│ - AOF 保证数据完整                   │
│                                      │
│ 配置:                                │
│ aof-use-rdb-preamble yes             │
└──────────────────────────────────────┘

7.2 持久化配置

java 复制代码
/**
 * Redis 持久化配置示例
 */
public class RedisPersistenceConfig {

    /**
     * RDB 配置
     */
    public static String getRDBConfig() {
        return """
            # RDB 配置
            save 900 1
            save 300 10
            save 60 10000

            # RDB 文件名
            dbfilename dump.rdb

            # RDB 文件目录
            dir /var/lib/redis

            # 压缩 RDB 文件
            rdbcompression yes

            # RDB 校验
            rdbchecksum yes

            # 后台保存失败时停止写入
            stop-writes-on-bgsave-error yes
            """;
    }

    /**
     * AOF 配置
     */
    public static String getAOFConfig() {
        return """
            # 启用 AOF
            appendonly yes

            # AOF 文件名
            appendfilename "appendonly.aof"

            # 同步策略
            appendfsync everysec

            # 重写时是否同步
            no-appendfsync-on-rewrite no

            # 自动重写条件
            auto-aof-rewrite-percentage 100
            auto-aof-rewrite-min-size 64mb

            # 加载时忽略错误
            aof-load-truncated yes

            # 混合持久化
            aof-use-rdb-preamble yes
            """;
    }

    /**
     * 手动触发持久化
     */
    public static void triggerPersistence(Jedis jedis) {
        // 异步 RDB 快照
        String result = jedis.bgsave();
        System.out.println("BGSAVE: " + result);

        // 同步 RDB 快照 (阻塞)
        // jedis.save();

        // 异步 AOF 重写
        jedis.bgrewriteaof();
        System.out.println("BGREWRITEAOF started");
    }

    /**
     * 检查持久化状态
     */
    public static void checkPersistenceStatus(Jedis jedis) {
        String info = jedis.info("persistence");
        System.out.println("持久化状态:\n" + info);
    }
}

8. 集群架构

8.1 主从复制

markdown 复制代码
主从复制架构

        ┌──────────┐
        │  Master  │
        │  (写入)  │
        └────┬─────┘
             │
      ┌──────┼──────┐
      │      │      │
      ▼      ▼      ▼
┌──────┐ ┌──────┐ ┌──────┐
│Slave1│ │Slave2│ │Slave3│
│(读取)│ │(读取)│ │(读取)│
└──────┘ └──────┘ └──────┘

特点:
1. 异步复制
2. 读写分离
3. 从节点只读
4. 全量同步 + 增量同步

8.2 哨兵模式

markdown 复制代码
哨兵模式架构

    ┌──────────┐     ┌──────────┐     ┌──────────┐
    │Sentinel 1│     │Sentinel 2│     │Sentinel 3│
    └────┬─────┘     └────┬─────┘     └────┬─────┘
         │                │                │
         └────────────────┼────────────────┘
                          │
                          ▼
                   ┌──────────┐
                   │  Master  │
                   └────┬─────┘
                        │
              ┌─────────┼─────────┐
              │         │         │
              ▼         ▼         ▼
         ┌──────┐  ┌──────┐  ┌──────┐
         │Slave1│  │Slave2│  │Slave3│
         └──────┘  └──────┘  └──────┘

功能:
1. 监控: 检查节点状态
2. 通知: 发送故障通知
3. 自动故障转移: 主节点故障时自动选举
4. 配置提供: 客户端获取主节点地址
java 复制代码
import redis.clients.jedis.JedisSentinelPool;

/**
 * 哨兵模式连接
 */
public class SentinelExample {

    public static JedisSentinelPool createSentinelPool() {
        Set<String> sentinels = new HashSet<>();
        sentinels.add("sentinel1:26379");
        sentinels.add("sentinel2:26379");
        sentinels.add("sentinel3:26379");

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(20);
        config.setMinIdle(5);

        return new JedisSentinelPool(
            "mymaster",  // 主节点名称
            sentinels,
            config,
            3000,        // 超时时间
            null         // 密码
        );
    }

    public static void main(String[] args) {
        JedisSentinelPool pool = createSentinelPool();

        try (Jedis jedis = pool.getResource()) {
            jedis.set("key", "value");
            String value = jedis.get("key");
            System.out.println("value = " + value);
        }

        pool.close();
    }
}

8.3 Cluster 集群

markdown 复制代码
Redis Cluster 架构

┌─────────────────────────────────────────────────┐
│              Slot: 0 - 16383                    │
├─────────────┬─────────────┬─────────────────────┤
│  0 - 5460   │ 5461-10922  │   10923 - 16383     │
│             │             │                     │
│ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐        │
│ │ Master1 │ │ │ Master2 │ │ │ Master3 │        │
│ └────┬────┘ │ └────┬────┘ │ └────┬────┘        │
│      │      │      │      │      │              │
│ ┌────▼────┐ │ ┌────▼────┐ │ ┌────▼────┐        │
│ │ Slave1  │ │ │ Slave2  │ │ │ Slave3  │        │
│ └─────────┘ │ └─────────┘ │ └─────────┘        │
└─────────────┴─────────────┴─────────────────────┘

特点:
1. 数据分片: 16384 个槽
2. 去中心化: 无需哨兵
3. 高可用: 节点故障自动转移
4. 线性扩展: 添加节点自动迁移
java 复制代码
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;

/**
 * Cluster 集群连接
 */
public class ClusterExample {

    public static JedisCluster createCluster() {
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("node1", 6379));
        nodes.add(new HostAndPort("node2", 6379));
        nodes.add(new HostAndPort("node3", 6379));

        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(20);

        return new JedisCluster(
            nodes,
            3000,    // 连接超时
            3000,    // 读取超时
            5,       // 最大重试次数
            null,    // 密码
            config
        );
    }

    public static void main(String[] args) {
        try (JedisCluster cluster = createCluster()) {
            // 操作与单机相同
            cluster.set("key", "value");
            String value = cluster.get("key");
            System.out.println("value = " + value);

            // 批量操作需要使用 {} 指定同一个槽
            cluster.mset("{user}:1:name", "张三",
                        "{user}:1:age", "25");
        }
    }
}

9. 性能优化

9.1 优化建议

markdown 复制代码
Redis 性能优化清单

1. 内存优化
   ┌─────────────────────────────────┐
   │ - 选择合适的数据结构            │
   │ - 设置合理的过期时间            │
   │ - 启用内存淘汰策略              │
   │ - 使用压缩数据                  │
   └─────────────────────────────────┘

2. 网络优化
   ┌─────────────────────────────────┐
   │ - 使用 Pipeline 批量操作        │
   │ - 减少网络往返                  │
   │ - 使用连接池                    │
   │ - 启用 TCP_NODELAY              │
   └─────────────────────────────────┘

3. 命令优化
   ┌─────────────────────────────────┐
   │ - 避免使用 KEYS 命令            │
   │ - 使用 SCAN 替代                │
   │ - 避免大 Key                    │
   │ - 避免热点 Key                  │
   └─────────────────────────────────┘

4. 持久化优化
   ┌─────────────────────────────────┐
   │ - 根据场景选择 RDB/AOF          │
   │ - 关闭不必要的持久化            │
   │ - 使用 SSD 磁盘                 │
   └─────────────────────────────────┘

9.2 Pipeline 批量操作

java 复制代码
/**
 * Pipeline 批量操作
 */
public class PipelineExample {

    /**
     * 普通方式 vs Pipeline
     */
    public static void comparePerformance(JedisPool jedisPool) {
        int count = 10000;

        // 普通方式
        long start = System.currentTimeMillis();
        try (Jedis jedis = jedisPool.getResource()) {
            for (int i = 0; i < count; i++) {
                jedis.set("key:" + i, "value:" + i);
            }
        }
        long normalTime = System.currentTimeMillis() - start;
        System.out.println("普通方式耗时: " + normalTime + "ms");

        // Pipeline 方式
        start = System.currentTimeMillis();
        try (Jedis jedis = jedisPool.getResource()) {
            Pipeline pipeline = jedis.pipelined();

            for (int i = 0; i < count; i++) {
                pipeline.set("pipe:" + i, "value:" + i);
            }

            // 执行并获取结果
            List<Object> results = pipeline.syncAndReturnAll();
        }
        long pipelineTime = System.currentTimeMillis() - start;
        System.out.println("Pipeline 方式耗时: " + pipelineTime + "ms");

        System.out.printf("性能提升: %.2f 倍\n",
            (double) normalTime / pipelineTime);
    }

    /**
     * Pipeline 批量读取
     */
    public static void pipelineRead(JedisPool jedisPool, List<String> keys) {
        try (Jedis jedis = jedisPool.getResource()) {
            Pipeline pipeline = jedis.pipelined();

            for (String key : keys) {
                pipeline.get(key);
            }

            List<Object> results = pipeline.syncAndReturnAll();

            for (int i = 0; i < keys.size(); i++) {
                System.out.printf("%s = %s\n", keys.get(i), results.get(i));
            }
        }
    }

    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        comparePerformance(jedisPool);
        jedisPool.close();
    }
}

9.3 Lua 脚本

java 复制代码
/**
 * Lua 脚本示例
 */
public class LuaScriptExample {

    /**
     * 原子性计数器 (带上限)
     */
    public static Long atomicCounter(Jedis jedis, String key,
                                     int increment, int maxValue) {
        String script =
            "local current = redis.call('get', KEYS[1])\n" +
            "if not current then\n" +
            "    current = 0\n" +
            "else\n" +
            "    current = tonumber(current)\n" +
            "end\n" +
            "local new_value = current + tonumber(ARGV[1])\n" +
            "if new_value > tonumber(ARGV[2]) then\n" +
            "    return -1\n" +
            "end\n" +
            "redis.call('set', KEYS[1], new_value)\n" +
            "return new_value";

        Object result = jedis.eval(script,
            Collections.singletonList(key),
            Arrays.asList(String.valueOf(increment),
                         String.valueOf(maxValue)));

        return (Long) result;
    }

    /**
     * 限流器 (滑动窗口)
     */
    public static boolean slidingWindowRateLimiter(
            Jedis jedis, String key, int maxRequests, int windowSeconds) {

        String script =
            "local key = KEYS[1]\n" +
            "local now = tonumber(ARGV[1])\n" +
            "local window = tonumber(ARGV[2])\n" +
            "local max_requests = tonumber(ARGV[3])\n" +
            "\n" +
            "-- 移除窗口外的请求\n" +
            "redis.call('zremrangebyscore', key, 0, now - window * 1000)\n" +
            "\n" +
            "-- 获取当前请求数\n" +
            "local current = redis.call('zcard', key)\n" +
            "\n" +
            "if current < max_requests then\n" +
            "    -- 添加当前请求\n" +
            "    redis.call('zadd', key, now, now)\n" +
            "    -- 设置过期时间\n" +
            "    redis.call('expire', key, window)\n" +
            "    return 1\n" +
            "else\n" +
            "    return 0\n" +
            "end";

        Object result = jedis.eval(script,
            Collections.singletonList(key),
            Arrays.asList(
                String.valueOf(System.currentTimeMillis()),
                String.valueOf(windowSeconds),
                String.valueOf(maxRequests)
            ));

        return Long.valueOf(1L).equals(result);
    }

    public static void main(String[] args) {
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            // 测试计数器
            for (int i = 0; i < 5; i++) {
                Long count = atomicCounter(jedis, "counter", 10, 30);
                System.out.println("Counter: " + count);
            }

            // 测试限流器
            String userId = "user:1001";
            for (int i = 0; i < 15; i++) {
                boolean allowed = slidingWindowRateLimiter(
                    jedis, "rate:" + userId, 10, 60);
                System.out.println("Request " + i + ": " +
                    (allowed ? "允许" : "拒绝"));
            }
        }
    }
}

10. 常见问题与解决方案

10.1 缓存穿透

makefile 复制代码
缓存穿透

请求 ──▶ 缓存 (Miss) ──▶ 数据库 (Not Found)

场景: 查询不存在的数据,每次都穿透到数据库
危害: 数据库压力大,可能被恶意攻击

解决方案:
1. 缓存空值
2. 布隆过滤器
java 复制代码
/**
 * 缓存穿透解决方案
 */
public class CachePenetrationSolution {

    private final JedisPool jedisPool;

    // 布隆过滤器 (使用 Redisson)
    private RBloomFilter<Long> bloomFilter;

    public CachePenetrationSolution(JedisPool jedisPool,
                                   RedissonClient redisson) {
        this.jedisPool = jedisPool;

        // 初始化布隆过滤器
        this.bloomFilter = redisson.getBloomFilter("user:bloom");
        this.bloomFilter.tryInit(1000000, 0.01);
    }

    /**
     * 方案1: 缓存空值
     */
    public User getUserCacheNull(Long userId) {
        String key = "user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            String value = jedis.get(key);

            if (value != null) {
                if ("NULL".equals(value)) {
                    return null; // 缓存的空值
                }
                return parseUser(value);
            }

            // 查询数据库
            User user = getUserFromDB(userId);

            if (user == null) {
                // 缓存空值,设置较短过期时间
                jedis.setex(key, 300, "NULL");
            } else {
                jedis.setex(key, 3600, toJson(user));
            }

            return user;
        }
    }

    /**
     * 方案2: 布隆过滤器
     */
    public User getUserBloomFilter(Long userId) {
        // 先检查布隆过滤器
        if (!bloomFilter.contains(userId)) {
            return null; // 一定不存在
        }

        // 可能存在,查询缓存和数据库
        String key = "user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            String value = jedis.get(key);

            if (value != null) {
                return parseUser(value);
            }

            User user = getUserFromDB(userId);

            if (user != null) {
                jedis.setex(key, 3600, toJson(user));
            }

            return user;
        }
    }

    /**
     * 初始化布隆过滤器
     */
    public void initBloomFilter(List<Long> userIds) {
        for (Long userId : userIds) {
            bloomFilter.add(userId);
        }
        System.out.println("布隆过滤器初始化完成: " + userIds.size());
    }

    private User getUserFromDB(Long userId) {
        return null; // 模拟
    }

    private User parseUser(String json) {
        return null; // 模拟
    }

    private String toJson(User user) {
        return "{}"; // 模拟
    }

    static class User {
        private Long id;
        private String name;
    }
}

10.2 缓存击穿

makefile 复制代码
缓存击穿

热点Key ──▶ 过期 ──▶ 大量请求同时访问数据库

场景: 热点Key过期瞬间,大量请求打到数据库
危害: 数据库瞬时压力大

解决方案:
1. 互斥锁
2. 逻辑过期
3. 热点数据不过期
java 复制代码
/**
 * 缓存击穿解决方案
 */
public class CacheBreakdownSolution {

    private final JedisPool jedisPool;

    public CacheBreakdownSolution(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 方案1: 互斥锁
     */
    public User getUserWithMutex(Long userId) {
        String key = "user:" + userId;
        String lockKey = "lock:user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            String value = jedis.get(key);

            if (value != null) {
                return parseUser(value);
            }

            // 尝试获取锁
            String lockResult = jedis.set(lockKey, "1",
                new SetParams().nx().ex(10));

            if ("OK".equals(lockResult)) {
                try {
                    // 双重检查
                    value = jedis.get(key);
                    if (value != null) {
                        return parseUser(value);
                    }

                    // 查询数据库
                    User user = getUserFromDB(userId);

                    if (user != null) {
                        jedis.setex(key, 3600, toJson(user));
                    }

                    return user;
                } finally {
                    // 释放锁
                    jedis.del(lockKey);
                }
            } else {
                // 等待重试
                Thread.sleep(50);
                return getUserWithMutex(userId);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }

    /**
     * 方案2: 逻辑过期
     */
    public User getUserWithLogicalExpire(Long userId) {
        String key = "user:" + userId;

        try (Jedis jedis = jedisPool.getResource()) {
            String value = jedis.get(key);

            if (value == null) {
                return null;
            }

            CacheData cacheData = parseCacheData(value);

            // 检查是否过期
            if (System.currentTimeMillis() < cacheData.expireTime) {
                return cacheData.user;
            }

            // 已过期,尝试获取锁更新缓存
            String lockKey = "lock:user:" + userId;
            String lockResult = jedis.set(lockKey, "1",
                new SetParams().nx().ex(10));

            if ("OK".equals(lockResult)) {
                // 异步更新缓存
                CompletableFuture.runAsync(() -> {
                    try {
                        User user = getUserFromDB(userId);
                        if (user != null) {
                            CacheData newData = new CacheData();
                            newData.user = user;
                            newData.expireTime = System.currentTimeMillis()
                                + 3600000;
                            jedis.set(key, toJson(newData));
                        }
                    } finally {
                        jedis.del(lockKey);
                    }
                });
            }

            // 返回旧数据
            return cacheData.user;
        }
    }

    private User getUserFromDB(Long userId) {
        return null;
    }

    private User parseUser(String json) {
        return null;
    }

    private String toJson(Object obj) {
        return "{}";
    }

    private CacheData parseCacheData(String json) {
        return null;
    }

    static class CacheData {
        User user;
        long expireTime;
    }

    static class User {
        private Long id;
        private String name;
    }
}

10.3 缓存雪崩

markdown 复制代码
缓存雪崩

大量Key ──▶ 同时过期 ──▶ 数据库压力暴增
   或
Redis宕机 ──▶ 所有请求打到数据库

解决方案:
1. 过期时间随机化
2. 集群高可用
3. 熔断降级
4. 多级缓存
java 复制代码
/**
 * 缓存雪崩解决方案
 */
public class CacheAvalancheSolution {

    private final JedisPool jedisPool;

    public CacheAvalancheSolution(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * 方案1: 过期时间随机化
     */
    public void setWithRandomExpire(String key, String value,
                                    int baseSeconds) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 基础过期时间 + 随机时间 (避免同时过期)
            int randomSeconds = (int) (Math.random() * 300);
            int expireSeconds = baseSeconds + randomSeconds;

            jedis.setex(key, expireSeconds, value);
        }
    }

    /**
     * 方案2: 熔断降级
     */
    public User getUserWithCircuitBreaker(Long userId) {
        // 使用 Resilience4j 或 Sentinel 实现熔断
        try {
            return getUserFromCache(userId);
        } catch (Exception e) {
            // 熔断后返回默认值或降级逻辑
            System.out.println("熔断降级: " + e.getMessage());
            return getDefaultUser();
        }
    }

    /**
     * 方案3: 预热缓存
     */
    public void warmUpCache(List<Long> hotUserIds) {
        try (Jedis jedis = jedisPool.getResource()) {
            Pipeline pipeline = jedis.pipelined();

            for (Long userId : hotUserIds) {
                User user = getUserFromDB(userId);
                if (user != null) {
                    int expire = 3600 + (int) (Math.random() * 300);
                    pipeline.setex("user:" + userId, expire, toJson(user));
                }
            }

            pipeline.sync();
            System.out.println("缓存预热完成: " + hotUserIds.size());
        }
    }

    private User getUserFromCache(Long userId) {
        return null;
    }

    private User getUserFromDB(Long userId) {
        return null;
    }

    private User getDefaultUser() {
        return null;
    }

    private String toJson(User user) {
        return "{}";
    }

    static class User {
        private Long id;
        private String name;
    }
}

11. Spring Boot 集成

11.1 配置文件

yaml 复制代码
# application.yml
spring:
  redis:
    # 单机模式
    host: localhost
    port: 6379
    password:
    database: 0

    # 连接池配置
    lettuce:
      pool:
        max-active: 100
        max-idle: 20
        min-idle: 5
        max-wait: 10s

    # 超时配置
    timeout: 3s
    connect-timeout: 5s

    # 哨兵模式
    # sentinel:
    #   master: mymaster
    #   nodes:
    #     - sentinel1:26379
    #     - sentinel2:26379
    #     - sentinel3:26379

    # 集群模式
    # cluster:
    #   nodes:
    #     - node1:6379
    #     - node2:6379
    #     - node3:6379
    #   max-redirects: 3

11.2 配置类

java 复制代码
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * Redis 配置类
 */
@Configuration
@EnableCaching
public class RedisConfig {

    /**
     * 配置 RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // Key 序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // Value 序列化 (JSON)
        Jackson2JsonRedisSerializer<Object> jsonSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL,
            JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        jsonSerializer.setObjectMapper(objectMapper);

        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }

    /**
     * 配置缓存管理器
     */
    @Bean
    public RedisCacheManager cacheManager(
            RedisConnectionFactory connectionFactory) {

        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration
            .defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))
            .serializeKeysWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();

        // 针对不同缓存设置不同过期时间
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("users", defaultConfig.entryTtl(Duration.ofMinutes(30)));
        configMap.put("products", defaultConfig.entryTtl(Duration.ofHours(2)));

        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(configMap)
            .transactionAware()
            .build();
    }
}

11.3 使用示例

java 复制代码
import org.springframework.cache.annotation.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * Redis 使用示例
 */
@Service
public class UserService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final UserRepository userRepository;

    public UserService(RedisTemplate<String, Object> redisTemplate,
                      UserRepository userRepository) {
        this.redisTemplate = redisTemplate;
        this.userRepository = userRepository;
    }

    /**
     * 方式1: 使用注解缓存
     */
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        System.out.println("从数据库加载用户: " + id);
        return userRepository.findById(id).orElse(null);
    }

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    @CacheEvict(value = "users", allEntries = true)
    public void clearAllUsers() {
        System.out.println("清空所有用户缓存");
    }

    /**
     * 方式2: 使用 RedisTemplate
     */
    public User getUserWithTemplate(Long id) {
        String key = "user:" + id;

        // 查询缓存
        User user = (User) redisTemplate.opsForValue().get(key);

        if (user != null) {
            return user;
        }

        // 查询数据库
        user = userRepository.findById(id).orElse(null);

        if (user != null) {
            // 写入缓存
            redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
        }

        return user;
    }

    /**
     * Hash 操作
     */
    public void saveUserToHash(User user) {
        String key = "user:hash:" + user.getId();

        redisTemplate.opsForHash().put(key, "name", user.getName());
        redisTemplate.opsForHash().put(key, "email", user.getEmail());
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
    }

    /**
     * List 操作
     */
    public void addToRecentViews(Long userId, Long productId) {
        String key = "recent:views:" + userId;

        redisTemplate.opsForList().leftPush(key, productId);
        redisTemplate.opsForList().trim(key, 0, 99);
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    /**
     * Set 操作
     */
    public void addToFavorites(Long userId, Long productId) {
        String key = "favorites:" + userId;
        redisTemplate.opsForSet().add(key, productId);
    }

    /**
     * ZSet 操作 (排行榜)
     */
    public void updateLeaderboard(String userId, double score) {
        redisTemplate.opsForZSet().add("leaderboard", userId, score);
    }

    public Set<Object> getTopUsers(int n) {
        return redisTemplate.opsForZSet()
            .reverseRange("leaderboard", 0, n - 1);
    }
}

/**
 * 用户实体
 */
class User {
    private Long id;
    private String name;
    private String email;

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

12. 监控与运维

12.1 监控指标

yaml 复制代码
Redis 关键监控指标

内存指标:
├─ used_memory: 已用内存
├─ used_memory_rss: 物理内存
├─ mem_fragmentation_ratio: 内存碎片率
└─ maxmemory: 最大内存限制

性能指标:
├─ instantaneous_ops_per_sec: 每秒操作数
├─ hit_rate: 缓存命中率
├─ connected_clients: 连接数
└─ blocked_clients: 阻塞客户端数

持久化指标:
├─ rdb_last_bgsave_status: 最后RDB状态
├─ aof_last_rewrite_status: 最后AOF重写状态
└─ aof_current_size: 当前AOF大小

复制指标:
├─ role: 角色 (master/slave)
├─ connected_slaves: 从节点数
└─ master_repl_offset: 复制偏移量

告警阈值:
┌──────────────────────┬──────────────┐
│ Metric               │  Threshold   │
├──────────────────────┼──────────────┤
│ memory usage         │   > 80%      │
│ hit rate             │   < 90%      │
│ connected_clients    │   > 5000     │
│ blocked_clients      │   > 10       │
│ fragmentation_ratio  │   > 1.5      │
└──────────────────────┴──────────────┘

12.2 监控代码

java 复制代码
/**
 * Redis 监控
 */
public class RedisMonitoring {

    /**
     * 获取 Redis 信息
     */
    public static void printRedisInfo(Jedis jedis) {
        // 获取所有信息
        String info = jedis.info();
        System.out.println(info);

        // 获取特定部分
        System.out.println("\n=== Memory ===");
        System.out.println(jedis.info("memory"));

        System.out.println("\n=== Stats ===");
        System.out.println(jedis.info("stats"));

        System.out.println("\n=== Clients ===");
        System.out.println(jedis.info("clients"));
    }

    /**
     * 获取慢查询日志
     */
    public static void getSlowLog(Jedis jedis) {
        List<Slowlog> slowlogs = jedis.slowlogGet(10);

        System.out.println("慢查询日志:");
        for (Slowlog log : slowlogs) {
            System.out.printf("ID: %d, 耗时: %dμs, 命令: %s\n",
                log.getId(),
                log.getExecutionTime(),
                log.getArgs()
            );
        }
    }

    /**
     * 计算命中率
     */
    public static void calculateHitRate(Jedis jedis) {
        String info = jedis.info("stats");

        // 解析 keyspace_hits 和 keyspace_misses
        long hits = 0, misses = 0;
        for (String line : info.split("\r\n")) {
            if (line.startsWith("keyspace_hits:")) {
                hits = Long.parseLong(line.split(":")[1]);
            }
            if (line.startsWith("keyspace_misses:")) {
                misses = Long.parseLong(line.split(":")[1]);
            }
        }

        if (hits + misses > 0) {
            double hitRate = (double) hits / (hits + misses) * 100;
            System.out.printf("缓存命中率: %.2f%%\n", hitRate);
        }
    }

    /**
     * 获取大 Key
     */
    public static void findBigKeys(Jedis jedis) {
        // 使用 SCAN 遍历所有 key
        String cursor = "0";
        int count = 0;

        do {
            ScanResult<String> result = jedis.scan(cursor,
                new ScanParams().count(1000));

            for (String key : result.getResult()) {
                // 检查 key 大小
                Long size = jedis.objectEncodingLength(key);
                if (size != null && size > 10240) { // 10KB
                    String type = jedis.type(key);
                    System.out.printf("大Key: %s, 类型: %s, 大小: %d bytes\n",
                        key, type, size);
                    count++;
                }
            }

            cursor = result.getCursor();
        } while (!cursor.equals("0"));

        System.out.println("共发现大Key: " + count);
    }

    public static void main(String[] args) {
        try (Jedis jedis = new Jedis("localhost", 6379)) {
            printRedisInfo(jedis);
            getSlowLog(jedis);
            calculateHitRate(jedis);
            // findBigKeys(jedis); // 生产环境谨慎使用
        }
    }
}

13. 总结

13.1 Redis 核心优势

markdown 复制代码
Redis 核心优势

1. 性能 ★★★★★
   - 基于内存,读写速度快
   - 单线程避免锁竞争
   - IO 多路复用

2. 数据结构 ★★★★★
   - 丰富的数据类型
   - 灵活满足各种场景
   - 原子操作

3. 可用性 ★★★★★
   - 主从复制
   - 哨兵模式
   - 集群模式

4. 持久化 ★★★★☆
   - RDB 快照
   - AOF 日志
   - 混合持久化

5. 生态 ★★★★★
   - 客户端丰富
   - 社区活跃
   - 文档完善

13.2 最佳实践总结

  1. 数据结构选择

    • 简单键值用 String
    • 对象数据用 Hash
    • 队列用 List
    • 去重用 Set
    • 排序用 ZSet
  2. Key 设计规范

    • 使用统一前缀
    • 避免过长 Key
    • 设置合理过期时间
  3. 性能优化

    • 使用连接池
    • Pipeline 批量操作
    • 避免大 Key
    • 避免热点 Key
  4. 高可用部署

    • 主从复制
    • 哨兵监控
    • 集群分片
    • 定期备份
  5. 安全配置

    • 设置密码
    • 限制访问 IP
    • 禁用危险命令
    • 定期更新版本
相关推荐
李慕婉学姐5 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆6 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin7 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20057 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉7 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国7 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882487 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈8 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_998 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹8 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理