欢迎光临
我们一直在努力

Java 是如何操作Redis的?从 Spring Data Redis中RedisTemplate 源码分析 ZSet 调用链

刚开始学习 Redis 时,我一直有一个疑问:

MySQL 可以通过 JDBC 操作,那么 Java 又是怎样把 ZADD、ZRANGE 等命令发送给 Redis 的?

在 Spring Data Redis 中,我们通常通过 RedisTemplate 操作 Redis:

ZSetOperations zSetOperations = redisTemplate.opsForZSet();

//zadd
zSetOperations.add("zset1","a",10);
zSetOperations.add("zset1","b",12);
zSetOperations.add("zset1","c",7);
//当在redis库中有对应的value值时,再添加score时会进行覆盖

//zrange
Set zset1 = zSetOperations.range("zset1", 0, 1);
System.out.println(zset1);
//[c, a, b] 按照分数从小到大排列

看起来只有几行代码,背后却经历了完整的调用过程:

RedisTemplate.opsForZSet()
→ DefaultZSetOperations
→ 序列化 key 和 value
→ RedisTemplate.execute()
→ RedisConnection
→ Lettuce 或 Jedis
→ Redis Server
→ 反序列化结果

接下来我们以 opsForZSet() 和 range() 为例,分析 Spring Data Redis 操作 Redis 的核心原理。


一、opsForZSet() 返回的是什么?

RedisTemplate 本身没有把所有 ZSet 方法都直接暴露出来,而是提供了一个专门操作 ZSet 的接口:

ZSetOperations<K, V>

它的默认实现类是:

DefaultZSetOperations<K, V>

RedisTemplate 中的源码结构可以简化为:

public class RedisTemplate<K, V> {

private final ZSetOperations<K, V> zSetOps =
new DefaultZSetOperations<>(this);

public ZSetOperations<K, V> opsForZSet() {
return this.zSetOps;
}
}

如图
在这里插入图片描述

RedisTemplate 创建并保存 DefaultZSetOperations,
调用 opsForZSet() 时直接返回已有的 zSetOps 对象。

从源码可以得到两个结论:

  • opsForZSet() 返回的实际对象是 DefaultZSetOperations;
  • zSetOps 是成员变量,并不是每次调用都重新创建。

  • 二、为什么 Operations 中保存的是同一个 RedisTemplate?

    new DefaultZSetOperations<>(this)

    这段代码位于 RedisTemplate 类中,因此这里的 this 指向当前的 RedisTemplate 对象。

    DefaultZSetOperations 会把这个对象继续传给父类 AbstractOperations:

    class AbstractOperations<K, V> {

    final RedisTemplate<K, V> template;

    AbstractOperations(RedisTemplate<K, V> template) {
    this.template = template;
    }
    }

    对象关系可以理解为:

    RedisTemplate@10175
    └── zSetOps → DefaultZSetOperations@10223
    └── template → RedisTemplate@10175

    内存中只有一个 RedisTemplate 对象,只是多个变量保存了它的引用。

    但需要注意,DefaultZSetOperations 本身并不是 RedisTemplate,它只是持有了模板对象的引用。


    三、ZADD 是如何执行的?

    zSetOps.add("zset1", "a", 10);

    最终会进入 DefaultZSetOperations.add(),核心逻辑可以简化为:

    public Boolean add(K key, V value, double score) {
    byte[] rawKey = rawKey(key);
    byte[] rawValue = rawValue(value);

    return execute(connection ->
    connection.zAdd(rawKey, score, rawValue));
    }

    这个方法完成了三件事:

    key → 序列化为 byte[]
    value → 序列化为 byte[]
    score → 直接作为 double 传递

    然后通过:

    connection.zAdd(rawKey, score, rawValue)

    执行 Redis 的:

    ZADD zset1 10 a

    需要注意,Java 泛型并不决定 Redis 中真正保存的数据格式:

    RedisTemplate<String, String>

    这里只表示 Java 编译阶段 key 和 value 应该是 String。

    真正决定数据如何转换成字节的是:

    keySerializer
    valueSerializer


    四、range() 的完整调用链

    Set<String> values =
    zSetOps.range("zset1", 0, 1);

    其中:

    0 表示第一个成员
    -1 表示最后一个成员

    range(key, 0, 1)

    它的整体调用链为:

    DefaultZSetOperations.range()
    → rawKey()
    → RedisTemplate.execute()
    → RedisConnection.zRange()
    → 返回 Set<byte[]>
    → deserializeValues()
    → 返回 Set<V>

    如图: 我把range方法的调用过程截图展示出来

    range() 先序列化 key,再通过 RedisTemplate 获取连接并执行
    zRange,最后把 Redis 返回的字节数据反序列化为 Java 对象。


    五、range() 源码做了什么?

    核心源码可以简化为:

    public Set<V> range(K key, long start, long end) {
    byte[] rawKey = rawKey(key);

    Set<byte[]> rawValues =
    execute(connection ->
    connection.zRange(rawKey, start, end));

    return deserializeValues(rawValues);
    }

    整个方法分为三个阶段。

    1. rawKey() 序列化 key

    Redis 服务器不认识 Java 中的 String、实体类等对象。

    因此:

    String key = "zset1";

    需要先转换为:

    byte[]

    rawKey() 的核心逻辑为:

    byte[] rawKey(Object key) {
    Assert.notNull(key, "non null key required");

    return keySerializer.serialize(key);
    }

    如果没有配置序列化器,并且 key 本身已经是 byte[],则可以直接使用;否则会调用 key serializer 完成序列化。

    2. execute() 获取连接并执行命令

    DefaultZSetOperations 自己不负责创建 Redis 连接。

    它会通过父类中保存的 template 调用:

    template.execute(callback);

    RedisTemplate.execute() 主要负责:

    通过 RedisConnectionFactory 获取连接
    → 执行回调中的 Redis 命令
    → 处理异常
    → 释放连接

    回调代码是:

    connection ->
    connection.zRange(rawKey, start, end)

    可以理解为:

    当 RedisTemplate 给我一个 RedisConnection 后,就调用它的 zRange() 方法。

    通过连接工厂获取连接。

    是否使用连接池,取决于项目使用的是 Lettuce 还是 Jedis,以及具体的连接配置。

    3. deserializeValues() 反序列化结果

    Redis 返回的成员在底层表现为:

    Set<byte[]>

    例如:

    byte[]("a")
    byte[]("b")

    Spring Data Redis 还需要调用:

    deserializeValues(rawValues)

    使用 value serializer 把每个字节数组恢复为 Java 对象:

    Set<byte[]>
    → 逐个反序列化
    → Set<String>

    最终业务代码拿到的才是:

    Set<String>


    六、RedisConnection 之后发生了什么?

    RedisConnection 是 Spring Data Redis 定义的统一连接接口。

    如果项目使用 Lettuce,大致调用过程为:

    RedisConnection.zRange()
    → LettuceZSetCommands.zRange()
    → Lettuce客户端
    → 编码Redis命令
    → 通过网络发送给Redis Server

    如果使用 Jedis,则会进入对应的 Jedis 实现。

    这样设计的好处是,上层的 RedisTemplate 和业务代码不需要关心底层到底使用哪个 Redis 客户端。

    完整流程可以表示为:

    Java代码
    → ZSetOperations
    → RedisTemplate
    → RedisConnection
    → Lettuce或Jedis
    → Redis Server


    总结

    通过 ZSetOperations.range() 的源码,可以得到以下结论:

  • RedisTemplate 会提前创建并保存 DefaultZSetOperations 对象。
  • opsForZSet() 只是返回已有对象,并不会每次重新创建。
  • DefaultZSetOperations 持有原 RedisTemplate 的引用。
  • Java 中的 key 和 value 需要先通过序列化器转换为 byte[]。
  • Operations 将具体操作交给 RedisTemplate.execute()。
  • RedisTemplate 负责获取连接、执行回调和释放连接。
  • RedisConnection 最终通过 Lettuce 或 Jedis 将命令发送给 Redis。
  • Redis 返回的字节数据还需要经过反序列化,才能变成 Java 对象。
  • 整个 range() 调用可以概括为:

    Java key
    → 序列化为 byte[]
    → 执行 ZRANGE
    → 获得 Set<byte[]>
    → 反序列化
    → 返回 Set<V>

    ZSetOperations 是面向 ZSet 的操作入口,而序列化、连接管理和命令执行等基础能力,仍然由 RedisTemplate 统一提供。

    赞(0)
    未经允许不得转载:171主机测评 » Java 是如何操作Redis的?从 Spring Data Redis中RedisTemplate 源码分析 ZSet 调用链
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址