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
    • 禁用危险命令
    • 定期更新版本
相关推荐
靠沿4 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
qq_12498707534 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
一 乐5 小时前
宠物管理|宠物共享|基于Java+vue的宠物共享管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·springboot·宠物
a crazy day5 小时前
Spring相关知识点【详细版】
java·spring·rpc
白露与泡影5 小时前
MySQL中的12个良好SQL编写习惯
java·数据库·面试
foundbug9995 小时前
配置Spring框架以连接SQL Server数据库
java·数据库·spring
凯酱5 小时前
@JsonSerialize
java
悦悦子a啊5 小时前
项目案例作业(选做):使用文件改造已有信息系统
java·开发语言·算法
lkbhua莱克瓦245 小时前
Java项目——斗地主小游戏(控制台版)
java·开发语言·windows·斗地主项目