杰瑞科技汇

Java Redis教程该怎么学?

Java Redis 完全教程:从入门到精通

目录

  1. 引言:什么是 Redis?
  2. 环境准备
    • 安装并启动 Redis 服务器
    • 安装 Redis 客户端(推荐)
    • 配置 Java 开发环境
  3. Java Redis 客户端选择
    • Jedis (经典,轻量级)
    • Lettuce (推荐,高性能,异步支持)
    • Spring Data Redis (Spring 生态首选)
  4. 第一个 Java Redis 程序 (使用 Jedis)
  5. Redis 数据类型与 Java 操作详解
    • String
    • Hash
    • List
    • Set
    • Sorted Set (ZSet)
    • Key 操作
  6. 高级应用
    • 使用 Redis 实现缓存

      缓存穿透、击穿、雪崩及解决方案

      Java Redis教程该怎么学?-图1
      (图片来源网络,侵删)
    • 使用 Redis 实现分布式锁
      • 简单实现与问题
      • 使用 Redisson 实现健壮的分布式锁
    • 使用 Redis 实现消息队列
  7. 使用 Spring Boot 集成 Redis (最佳实践)
    • 项目配置
    • 使用 StringRedisTemplateRedisTemplate
    • 自定义序列化方式
  8. 总结与最佳实践

引言:什么是 Redis?

Redis (Remote Dictionary Server) 是一个开源的、高性能的、基于内存的键值数据库,它通常被用作数据库、缓存和消息中间件。

核心特点:

  • 速度快:数据存储在内存中,避免了磁盘 I/O,读写速度极快。
  • 丰富的数据结构:支持 String、Hash、List、Set、Sorted Set 等多种数据类型。
  • 原子性操作:所有操作都是原子性的,适合用于计数器、分布式锁等场景。
  • 持久化:支持 RDB (快照) 和 AOF (日志) 两种持久化方式,保证数据安全。
  • 多功能:除了缓存,还可以实现分布式锁、消息队列、排行榜等。

环境准备

a. 安装并启动 Redis 服务器

  1. 下载:从 Redis 官网 下载最新稳定版。

  2. 编译安装 (Linux/macOS):

    Java Redis教程该怎么学?-图2
    (图片来源网络,侵删)
    # 解压
    tar xzf redis-x.x.x.tar.gz
    cd redis-x.x.x
    # 编译
    make
    # 启动 Redis 服务器 (默认端口 6379)
    src/redis-server
  3. Windows: 可以使用 WSL (Windows Subsystem for Linux) 或下载预编译版本。

b. 安装 Redis 客户端

为了方便调试,强烈建议安装一个图形化客户端。

  • RedisInsight: 官方出品的免费桌面客户端,功能强大,强烈推荐。
  • AnotherRedisDesktopManager: 开源的跨平台客户端。

c. 配置 Java 开发环境

确保你已经安装了 JDK 8 或更高版本和 Maven。

Java Redis 客户端选择

在 Java 中操作 Redis,主要有以下几种客户端:

Java Redis教程该怎么学?-图3
(图片来源网络,侵删)
客户端 特点 适用场景
Jedis 最经典、最广为人知的客户端,API 直观,类似于 Redis 命令。 轻量级应用,对异步要求不高的项目。
Lettuce 基于 Netty,高性能,支持同步、异步和响应式编程,是 Spring Boot 2.x 的默认客户端。 新项目,特别是需要高性能或异步支持的项目。
Spring Data Redis Spring 生态的一部分,对 Redis 客户端(Jedis/Lettuce)进行了高级封装,提供了 RedisTemplate 等工具类,极大地简化了开发。 基于 Spring/Spring Boot 的项目,是 企业级应用的首选

本教程将首先使用 Jedis 讲解基础操作,然后重点介绍 Spring Boot 集成 Redis 的最佳实践。

第一个 Java Redis 程序 (使用 Jedis)

  1. 创建 Maven 项目,并添加 Jedis 依赖:

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.3.1</version> <!-- 使用较新版本 -->
    </dependency>
  2. 编写 Java 代码

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.params.SetParams;
    public class JedisDemo {
        public static void main(String[] args) {
            // 1. 创建 Jedis 客户端,连接到 Redis 服务器
            // 默认 host 是 "localhost", port 是 6379
            Jedis jedis = new Jedis("localhost", 6379);
            // 2. 测试连接是否成功
            String pingResponse = jedis.ping();
            System.out.println("连接成功: " + pingResponse); // 输出: PONG
            // 3. 设置一个 String 类型的键值对
            jedis.set("name", "张三");
            System.out.println("设置 name: " + jedis.get("name")); // 输出: 张三
            // 4. 设置一个有过期时间的键 (单位:秒)
            jedis.setex("temp_key", 10, "这是一个临时数据");
            System.out.println("设置 temp_key (10秒后过期): " + jedis.get("temp_key"));
            // 5. 使用 SET 命令的参数 (NX: 不存在才设置, EX: 设置过期时间)
            SetParams params = SetParams.setParams().nx().ex(5); // 5秒内不存在才设置
            String setResult = jedis.set("new_key", "new_value", params);
            System.out.println("NX/EX 设置结果: " + setResult); // 输出: OK 或 null
            // 6. 关闭连接
            jedis.close();
        }
    }

    运行此代码,如果一切正常,你将在控制台看到输出。

Redis 数据类型与 Java 操作详解

Jedis 客户端的方法名与 Redis 命令名基本一致,非常容易上手。

a. String

最基础的数据类型,可以存储文本、JSON、序列化对象等。

jedis.set("user:1001:name", "李四");
jedis.set("user:1001:age", "25");
// 批量设置
jedis.mset("key1", "value1", "key2", "value2");
// 获取
String name = jedis.get("user:1001:name");
// 数值原子递增
jedis.set("counter", "10");
jedis.incr("counter"); // 变为 11
jedis.incrBy("counter", 5); // 变为 16
// 数值原子递减
jedis.decr("counter"); // 变为 15

b. Hash

用于存储对象,一个键对应多个字段-值对,类似 Java 中的 Map<String, String>

// 存储一个用户对象
jedis.hset("user:1001", "name", "王五");
jedis.hset("user:1001", "email", "wangwu@example.com");
jedis.hset("user:1001", "age", "30");
// 批量设置
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "赵六");
userMap.put("email", "zhaoliu@example.com");
jedis.hmset("user:1002", userMap);
// 获取单个字段
String email = jedis.hget("user:1001", "email");
// 获取所有字段和值
Map<String, String> allFields = jedis.hgetAll("user:1001");
// 删除字段
jedis.hdel("user:1001", "age");

c. List

有序、可重复的列表,常作消息队列或文章列表。

// 从左侧推入
jedis.lpush("messages", "msg1", "msg2"); // 列表变为: [msg2, msg1]
// 从右侧推入
jedis.rpush("messages", "msg3"); // 列表变为: [msg2, msg1, msg3]
// 获取指定范围 (0到-1表示全部)
List<String> allMessages = jedis.lrange("messages", 0, -1); // [msg2, msg1, msg3]
// 获取长度
long len = jedis.llen("messages");
// 弹出元素 (从左侧)
String leftMessage = jedis.lpop("messages"); // 返回 "msg2"

d. Set

无序、不重复的集合,常用于标签、共同好友等。

// 添加元素
jedis.sadd("tags:java", "spring", "redis", "mysql");
jedis.sadd("tags:java", "spring"); // 重复添加,无效
// 获取所有成员
Set<String> tags = jedis.smembers("tags:java"); // 顺序不定
// 检查元素是否存在
boolean exists = jedis.sismember("tags:java", "redis");
// 计算数量
long count = jedis.scard("tags:java");
// 删除元素
jedis.srem("tags:java", "mysql");

e. Sorted Set (ZSet)

有序集合,每个元素都会关联一个 double 类型的分数,通过分数来排序。

// 添加元素,同时指定分数
jedis.zadd("leaderboard", 100, "userA");
jedis.zadd("leaderboard", 200, "userB");
jedis.zadd("leaderboard", 150, "userC");
// 按分数从小到大排序
Set<String> ascRanking = jedis.zrange("leaderboard", 0, -1); // [userA, userC, userB]
// 按分数从大到小排序
Set<String> descRanking = jedis.zrevrange("leaderboard", 0, -1); // [userB, userC, userA]
// 获取元素的分数
double scoreB = jedis.zscore("leaderboard", "userB");
// 获取元素的排名 (从0开始)
long rankA = jedis.zrank("leaderboard", "userA"); // 0

f. Key 操作

// 检查键是否存在
boolean exists = jedis.exists("name");
// 删除键
jedis.del("temp_key");
// 设置过期时间 (秒)
jedis.expire("name", 60);
// 获取键的剩余生存时间 (秒)
long ttl = jedis.ttl("name"); // 如果键不存在或已过期,返回 -2
// 获取键的数据类型
String type = jedis.type("name"); // 返回 "string", "hash", "list" 等

高级应用

使用 Redis 实现缓存

缓存是 Redis 最常见的用途,我们以查询用户信息为例。

// 模拟从数据库查询用户
public User getUserFromDB(long userId) {
    System.out.println("正在从数据库查询用户 ID: " + userId);
    // 模拟耗时
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    return new User(userId, "UserFromDB");
}
// 使用缓存的查询方法
public User getUserWithCache(long userId, Jedis jedis) {
    String key = "user:" + userId;
    // 1. 先从缓存查询
    String cachedUserJson = jedis.get(key);
    if (cachedUserJson != null) {
        System.out.println("缓存命中!");
        // 这里需要反序列化,实际项目中常用 JSON 库如 Jackson/Gson
        return new User(userId, cachedUserJson); // 简化处理
    }
    // 2. 缓存未命中,从数据库查询
    User user = getUserFromDB(userId);
    // 3. 将查询结果存入缓存,并设置过期时间 (30 分钟)
    // 这里需要序列化,简化处理
    jedis.setex(key, 1800, user.getName());
    return user;
}

缓存问题与解决方案:

  • 缓存穿透:查询一个不存在的数据,请求直接打到数据库。
    • 解决方案:如果查询结果为空,也将其缓存起来,并设置较短的过期时间(1 分钟)。
  • 缓存击穿:一个热点 key 在某一刻突然失效,大量请求直接打到数据库。
    • 解决方案
      1. 互斥锁/分布式锁:只允许一个线程去查询数据库,其他线程等待。
      2. 热点数据永不过期:逻辑上设置过期时间,但后台异步刷新。
  • 缓存雪崩:大量 key 在同一时间集体失效,导致请求全部打到数据库。
    • 解决方案:给 key 的过期时间加上一个随机值,避免同时失效。

使用 Redis 实现分布式锁

在分布式系统中,为了保证多个服务实例对共享资源的互斥访问,需要分布式锁。

简单实现 (有问题版):

public boolean tryLock(String lockKey, String requestId, int expireTime) {
    // set key value nx ex timeout
    String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
    return "OK".equals(result);
}
public void releaseLock(String lockKey, String requestId) {
    // 使用 Lua 脚本保证原子性
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) " +
                       "else " +
                       "return 0 " +
                       "end";
    jedis.eval(luaScript, 1, lockKey, requestId);
}

问题jedis.set() 成功,但客户端在执行业务逻辑前崩溃了,锁永远不会被释放,导致死锁。

健壮的实现:使用 Redisson

Redisson 是一个强大的 Java Redis 客户端,它提供了开箱即用的分布式锁实现。

  1. 添加依赖:

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.23.4</version>
    </dependency>
  2. 使用代码:

    import org.redisson.Redisson;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    public class RedissonLockDemo {
        public static void main(String[] args) {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://localhost:6379");
            RedissonClient redisson = Redisson.create(config);
            RLock lock = redisson.getLock("myLock");
            try {
                // 1. 尝试获取锁,最多等待 10 秒,锁自动释放 30 秒
                boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
                if (isLocked) {
                    System.out.println("获取锁成功,执行业务逻辑...");
                    // 模拟业务处理
                    Thread.sleep(15000);
                } else {
                    System.out.println("获取锁失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 2. 释放锁
                if (lock.isHeldByCurrentThread()) {
                    System.out.println("释放锁");
                    lock.unlock();
                }
            }
            redisson.shutdown();
        }
    }

    Redisson 的锁实现了 可重入锁续期(看门狗机制)公平锁/非公平锁 等复杂逻辑,非常健壮。

使用 Redis 实现消息队列

虽然 Redis 不是专业的消息队列,但可以使用 ListStream 来实现简单的消息队列功能。

基于 List 的发布/订阅 (简单版):

  • 生产者LPUSHRPUSH 将消息推入列表。
  • 消费者RPOPBLPOP (阻塞式弹出) 从列表中取出消息。

基于 Stream (功能更强大):

Redis 5.0 引入了 Stream 数据结构,提供了更专业的消息队列功能,如消费者组、消息确认等。

使用 Spring Boot 集成 Redis (最佳实践)

这是目前企业级开发中最主流的方式。

  1. 创建 Spring Boot 项目,并添加依赖:

    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 使用 Lettuce 客户端,Spring Boot 2.x 默认 -->
    <!-- Spring Boot 会自动引入它 -->
    <!-- 为了方便操作 JSON,添加 Jackson 依赖 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
  2. 配置 application.yml:

    spring:
      redis:
        host: localhost
        port: 6379
        # password: yourpassword # 如果有密码
        database: 0 # 使用 0 号数据库
        lettuce:
          pool:
            max-active: 8 # 连接池最大连接数
            max-idle: 8   # 连接池最大空闲连接数
            min-idle: 0   # 连接池最小空闲连接数
            max-wait: -1ms # 连接池获取连接最大等待时间
  3. 使用 StringRedisTemplate

StringRedisTemplateRedisTemplate 的一个特化版本,它的 key 和 value 都被强制为 String 类型,在大多数缓存场景下,我们推荐将对象序列化为 JSON 字符串进行存储,StringRedisTemplate 是最常用、最方便的选择。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    public User getUserById(Long id) {
        String key = "user:" + id;
        // 1. 从缓存查询
        String cachedUserJson = stringRedisTemplate.opsForValue().get(key);
        if (cachedUserJson != null) {
            System.out.println("缓存命中!");
            // 使用 Jackson 反序列化
            return JSON.parseObject(cachedUserJson, User.class);
        }
        // 2. 缓存未命中,从数据库查询 (模拟)
        User user = getUserFromDB(id);
        // 3. 存入缓存
        if (user != null) {
            // 使用 Jackson 序列化
            String userJson = JSON.toJSONString(user);
            stringRedisTemplate.opsForValue().set(key, userJson, 30, TimeUnit.MINUTES);
        }
        return user;
    }
    // ... 其他方法
}

自定义序列化方式 (可选)

默认情况下,RedisTemplate 使用的是 JdkSerializationRedisSerializer,它会产生二进制数据,不便于阅读,我们可以将其替换为 Jackson2JsonRedisSerializer

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 1. 创建 RedisTemplate 对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 2. 创建 JSON 序列化器配置
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(om);
        // 3. 设置 key 和 hashKey 的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        // 4. 设置 value 和 hashValue 的序列化器
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        // 5. 初始化 RedisTemplate
        template.afterPropertiesSet();
        return template;
    }
}

配置完成后,你就可以直接在 RedisTemplate 中存取 Java 对象了,它会自动完成 JSON 的序列化和反序列化。

总结与最佳实践

  • 选择合适的客户端:新项目优先选择 Lettuce,Spring 项目优先选择 Spring Data Redis
  • 优先使用 StringRedisTemplate:在缓存场景下,将对象序列化为 JSON 字符串存储是最佳实践,简单、高效、可读性好。
  • 自定义序列化器:如果必须使用 RedisTemplate,请务必将默认的 JDK 序列化器替换为 JSON 序列化器 (Jackson2JsonRedisSerializer)。
  • 为所有缓存数据设置过期时间:这是防止缓存雪崩和避免数据不一致的基本要求。
  • 善用高级数据结构:不要只用 String,Hash、List、ZSet 等能解决很多实际问题。
  • 复杂功能使用成熟库:对于分布式锁、消息队列等复杂场景,直接使用 Redisson 等成熟库,避免重复造轮子和踩坑。
  • 注意连接池配置:在生产环境中,务必配置好 Redis 客户端的连接池参数,以获得最佳性能和稳定性。

希望这份详尽的教程能帮助你从零开始掌握 Java Redis 开发!

分享:
扫描分享到社交APP
上一篇
下一篇