Redis-常见 Java 客户端

Redis-常见 Java 客户端

一、核心 Java 客户端对比

客户端 核心特点 适用场景
Jedis⭐ 以 Redis 命令为方法名,学习成本低;但线程不安全,需依赖连接池使用 简单场景、非高并发需求
Lettuce⭐ 基于 Netty 实现,支持同步 / 异步 / 响应式编程,线程安全;支持哨兵 / 集群 / 管道 高并发、分布式场景(Spring Boot 默认)
Redisson 基于 Redis 实现分布式数据结构(Lock/Queue/Map 等),功能强大,封装程度高 分布式锁、分布式集合等场景
SpringDataRedis⭐⭐⭐ 基于 Jedis/Lettuce 封装的 Spring 生态组件,简化操作,适配 Spring 全家桶 Spring/Spring Boot 项目(主流选择)
其他客户端 java-redis-client(轻量无依赖)、vertx-redis-client(Vert.x 异步) 极小项目、Vert.x 生态项目

二、SpringDataRedis 核心解析

1. 核心定位

SpringDataRedis 是 Spring Data 生态下的 Redis 操作组件,并非独立客户端,而是对 Jedis、Lettuce 等底层客户端的封装,提供统一、简洁的 API,适配 Spring 的依赖注入、事务管理等特性,是 Spring Boot 项目操作 Redis 的首选方案。

2. RedisTemplate 的操作 API 分类(按数据类型)

SpringDataRedis 的RedisTemplate将不同数据类型的操作封装为独立的 API 类,便于精准操作:

API 方法 返回值类型 说明
redisTemplate.opsForValue() ValueOperations 操作 String 类型数据
redisTemplate.opsForHash() HashOperations 操作 Hash 类型数据
redisTemplate.opsForList() ListOperations 操作 List 类型数据
redisTemplate.opsForSet() SetOperations 操作 Set 类型数据
redisTemplate.opsForZSet() ZSetOperations 操作 SortedSet 类型数据
redisTemplate本身 - 执行通用 Redis 命令(如过期时间、删除键)

3. 核心组件

组件 作用
RedisTemplate 通用模板类,支持任意类型 Key/Value(需指定序列化器)
StringRedisTemplate 专用模板类,Key/Value 均为 String 类型(默认 String 序列化,无需额外配置)
RedisConnectionFactory 连接工厂,适配 Jedis/Lettuce,管理 Redis 连接(Spring Boot 自动配置)
RedisSerializer 序列化器,解决对象与字节数组的转换(如 String、Jackson2Json、JDK 序列化)

4. 快速集成(Spring Boot)

(1)依赖引入(Maven)
xml 复制代码
<!-- SpringDataRedis核心依赖(自动引入Lettuce) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖(Lettuce默认用内置池,可选commons-pool2优化) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- Jackson核心依赖(支持JSON序列化,处理JDK8时间类型) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Lombok(可选,简化实体类编写) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
(2)配置(application.yml)
yaml 复制代码
spring:
  data:
    redis:
      host: 127.0.0.1       # Redis地址
      port: 6379            # 端口
      password: ""          # 密码(无则空)
      database: 0           # 数据库索引(默认0)
      timeout: 10000ms      # 连接超时时间
      lettuce:              # 底层客户端(默认Lettuce,可替换为jedis)
        pool:
          max-active: 8     # 最大连接数
          max-idle: 8       # 最大空闲连接
          min-idle: 0       # 最小空闲连接
          max-wait: -1ms    # 连接等待时间(-1表示无限制)
(3)各数据类型操作示例
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisTemplateDemo {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate; // 自定义序列化的模板
    @Autowired
    private StringRedisTemplate stringRedisTemplate;     // String专用模板

    // 1. String类型操作(ValueOperations)
    public void operateString() {
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        // 设置值(过期时间1小时)
        valueOps.set("user:name:1000", "张三", 3600, TimeUnit.SECONDS);
        // 获取值
        Object name = valueOps.get("user:name:1000");
        // 自增(计数器)
        valueOps.increment("user:login:count:1000");
    }

    // 2. Hash类型操作(HashOperations)
    public void operateHash() {
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        // 存储用户信息
        hashOps.put("user:info:1000", "age", "25");
        hashOps.put("user:info:1000", "phone", "13800000000");
        // 获取单个字段
        Object age = hashOps.get("user:info:1000", "age");
        // 获取所有字段
        Map<Object, Object> userInfo = hashOps.entries("user:info:1000");
    }

    // 3. List类型操作(ListOperations)
    public void operateList() {
        ListOperations<String, Object> listOps = redisTemplate.opsForList();
        // 左推元素(消息队列生产)
        listOps.leftPush("msg:queue", "消息1");
        listOps.leftPush("msg:queue", "消息2");
        // 右取元素(消息队列消费)
        Object msg = listOps.rightPop("msg:queue");
        // 获取列表范围
        listOps.range("msg:queue", 0, -1);
    }

    // 4. Set类型操作(SetOperations)
    public void operateSet() {
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();
        // 添加元素
        setOps.add("user:tags:1000", "学生", "程序员");
        // 判断元素是否存在
        boolean hasTag = setOps.isMember("user:tags:1000", "学生");
        // 获取所有元素
        Set<Object> tags = setOps.members("user:tags:1000");
    }

    // 5. SortedSet类型操作(ZSetOperations)
    public void operateZSet() {
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
        // 添加商品销量排行(分数=销量)
        zSetOps.add("goods:rank:hot", "商品A", 100);
        zSetOps.add("goods:rank:hot", "商品B", 200);
        // 获取前10名(降序)
        Set<Object> top10 = zSetOps.reverseRange("goods:rank:hot", 0, 9);
    }

    // 6. 通用命令(直接用RedisTemplate)
    public void operateCommon() {
        // 设置键过期时间
        redisTemplate.expire("user:name:1000", 30, TimeUnit.MINUTES);
        // 删除键
        redisTemplate.delete("user:name:1000");
        // 判断键是否存在
        boolean exists = redisTemplate.hasKey("user:name:1000");
    }
}

5. 高级配置(自定义序列化器)

(1)JDK 序列化的问题(深度解析)

RedisTemplate默认使用JdkSerializationRedisSerializer作为序列化器,该序列化方式基于 JDK 自带的ObjectOutputStream实现对象与字节数组的转换,存在以下核心问题:

  • 乱码问题 :序列化后的字节数组在 Redis 客户端(如 redis-cli)中查看时,会显示为\xAC\xED\x00\x05t\x00\x03张三这类不可读的十六进制乱码,无法直接通过 Redis 命令行排查数据内容,调试和运维成本极高;

  • 体积过大 :JDK 序列化会额外存储类的全限定名、字段描述符、继承关系等元数据,即使是简单对象(如仅包含 id 和 name 的 User 对象),序列化后的体积也比 JSON 格式大 3-5 倍;例如一个包含id=1000name="张三"的 User 对象,JDK 序列化后约占 200 字节,而 Jackson2Json 序列化仅占 40 字节左右;

  • 兼容性差 :JDK 序列化强依赖类的结构,若类的序列化 ID(serialVersionUID)未指定、字段增减或修饰符变更(如 public 改 private),反序列化时会直接抛出InvalidClassException,跨服务、跨版本部署时极易出现序列化异常;

  • 性能损耗:JDK 序列化的序列化 / 反序列化速度远低于 JSON 类序列化器,在高并发场景下,大量对象的序列化操作会显著增加 CPU 开销,降低 Redis 操作的整体吞吐量;

  • 无法跨语言解析:JDK 序列化是 Java 专属的格式,若其他语言(如 Python、Go)的服务需要读取 Redis 中的数据,无法解析 JDK 序列化后的字节数组,导致 Redis 数据无法跨语言复用。

(2)自定义 Jackson 序列化器(解决 JDK 序列化问题)

推荐使用Jackson2JsonRedisSerializer替代 JDK 序列化,该序列化器基于 JSON 格式,兼具可读性、轻量化、跨语言兼容的优势:

java 复制代码
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.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 1. Key/HashKey序列化:String(避免Key出现乱码)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        // 2. 对比:JDK序列化(默认,不推荐)
        // JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
        // template.setValueSerializer(jdkSerializer);
        // template.setHashValueSerializer(jdkSerializer);

        // 3. Value/HashValue序列化:Jackson2Json(解决JDK序列化的所有问题)
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new JavaTimeModule()); // 注册JDK8时间模块(支持LocalDateTime/LocalDate)
        om.findAndRegisterModules(); // 自动注册所有Jackson模块,兼容更多数据类型
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 保留类型信息,解决反序列化时的类型丢失问题
        jacksonSerializer.setObjectMapper(om);
        
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}
(3)Jackson2JsonRedisSerializer 使用示例(存储 / 读取复杂对象)
步骤 1:定义待序列化的实体类
java 复制代码
import lombok.Data;
import java.time.LocalDateTime;

@Data // 需引入lombok依赖,也可手动写get/set
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String phone;
    private LocalDateTime createTime; // JDK8时间类型
}
步骤 2:使用自定义 RedisTemplate 操作对象
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Component
public class JacksonRedisSerializerDemo {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate; // 注入自定义序列化的RedisTemplate

    // 存储User对象到Redis
    public void saveUserToRedis() {
        // 1. 构建User对象
        User user = new User();
        user.setId(1000L);
        user.setName("张三");
        user.setAge(25);
        user.setPhone("13800000000");
        user.setCreateTime(LocalDateTime.now());

        // 2. 获取ValueOperations操作器
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        
        // 3. 存储对象(Jackson2Json序列化),设置过期时间2小时
        valueOps.set("user:detail:1000", user, 2, TimeUnit.HOURS);
        
        System.out.println("User对象已通过Jackson2Json序列化存储到Redis");
    }

    // 从Redis读取User对象
    public User getUserFromRedis() {
        // 1. 获取ValueOperations操作器
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        
        // 2. 读取对象(自动反序列化为User类型)
        Object result = valueOps.get("user:detail:1000");
        
        // 3. 类型转换(因配置了enableDefaultTyping,可直接强转)
        User user = (User) result;
        
        System.out.println("从Redis读取的User对象:" + user);
        return user;
    }
}
步骤 3:测试效果
java 复制代码
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class RedisTestRunner implements CommandLineRunner {
    @Autowired
    private JacksonRedisSerializerDemo jacksonDemo;

    @Override
    public void run(String... args) throws Exception {
        // 存储对象
        jacksonDemo.saveUserToRedis();
        
        // 读取对象
        User user = jacksonDemo.getUserFromRedis();
        System.out.println("用户ID:" + user.getId());
        System.out.println("用户姓名:" + user.getName());
        System.out.println("创建时间:" + user.getCreateTime());
    }
}
效果验证(redis-cli 中查看)
bash 复制代码
# 执行命令查看存储的内容
127.0.0.1:6379> GET user:detail:1000
# 输出(清晰的JSON格式,可直接阅读)
"{
    "@class":"com.example.demo.entity.User",
    "id":1000,"name":"张三",
    "age":25,
    "phone":"13800000000",
    "createTime":[2025,12,15,22,30,50,123456000]
}"
补充:JSON 序列化优化(StringRedisTemplate 手动序列化)
1. JSON 序列化的内存开销问题

Jackson2JsonRedisSerializer 会自动在 JSON 中写入 @class 字段(存储类全限定名),用于反序列化识别类型,但会产生额外内存开销:

json 复制代码
{
  "@class": "com.example.demo.entity.User",  // 冗余的类信息
  "id": 1000,
  "name": "张三",
  "age": 25,
  "phone": "13800000000",
  "createTime": [2025,12,15,22,30,50,123456000]
}

大量存储时,@class 字段会浪费 Redis 内存,推荐使用 StringRedisTemplate 手动序列化,剔除冗余字段。

2. 通用手动序列化工具类
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.util.concurrent.TimeUnit;

/**
 * StringRedisTemplate 手动序列化工具类(适配User实体,无冗余@class字段)
 */
@Component
public class RedisJsonUtil {
    // 注入StringRedisTemplate(默认String序列化,无乱码)
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 自定义ObjectMapper(适配LocalDateTime,与RedisConfig中一致)
    private static final ObjectMapper OBJECT_MAPPER;
    static {
        OBJECT_MAPPER = new ObjectMapper();
        OBJECT_MAPPER.registerModule(new JavaTimeModule()); // 支持JDK8时间类型
        OBJECT_MAPPER.findAndRegisterModules();
    }

    // ========== 通用序列化方法(存储对象) ==========
    public <T> void set(String key, T obj) {
        try {
            // 手动将对象转为JSON字符串(无@class字段)
            String json = OBJECT_MAPPER.writeValueAsString(obj);
            stringRedisTemplate.opsForValue().set(key, json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Redis序列化对象失败", e);
        }
    }

    // 重载:带过期时间的存储
    public <T> void set(String key, T obj, long timeout, TimeUnit unit) {
        try {
            String json = OBJECT_MAPPER.writeValueAsString(obj);
            stringRedisTemplate.opsForValue().set(key, json, timeout, unit);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Redis序列化对象失败(带过期时间)", e);
        }
    }

    // ========== 通用反序列化方法(读取对象) ==========
    public <T> T get(String key, Class<T> clazz) {
        String json = stringRedisTemplate.opsForValue().get(key);
        if (json == null || json.isEmpty()) {
            return null;
        }
        try {
            // 手动将JSON转为指定类型对象
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Redis反序列化对象失败", e);
        }
    }

    // ========== 复用原有通用命令 ==========
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.expire(key, timeout, unit);
    }

    public Boolean delete(String key) {
        return stringRedisTemplate.delete(key);
    }

    public Boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key);
    }
}
3. 工具类使用示例
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Component
public class StringRedisTemplateOptDemo {
    @Autowired
    private RedisJsonUtil redisJsonUtil;

    // 存储User对象(手动序列化,无@class冗余)
    public void saveUserWithOpt() {
        // 构建User对象
        User user = new User();
        user.setId(1000L);
        user.setName("张三");
        user.setAge(25);
        user.setPhone("13800000000");
        user.setCreateTime(LocalDateTime.now());

        // 调用工具类存储(2小时过期)
        redisJsonUtil.set("user:detail:1000", user, 2, TimeUnit.HOURS);
        System.out.println("User对象已通过手动JSON序列化存储(无@class字段)");
    }

    // 读取User对象(手动反序列化)
    public User getUserWithOpt() {
        // 调用工具类读取,指定User类型
        User user = redisJsonUtil.get("user:detail:1000", User.class);
        System.out.println("手动反序列化读取的User:" + user);
        return user;
    }
}
4. 效果验证(redis-cli 查看)
bash 复制代码
# 执行命令查看
127.0.0.1:6379> GET user:detail:1000
# 输出(无@class字段,节省内存)
"{
    "id":1000,"name":"张三",
    "age":25,
    "phone":"13800000000",
    "createTime":"2025-12-15T22:45:30.123456"
}"
5. 对比总结
对比维度 JDK 序列化 Jackson2Json(自动) StringRedisTemplate(手动)
可读性 乱码不可读 JSON 可读(含 @class) JSON 可读(无冗余)
数据体积 大(元数据多) 中(含 @class) 小(纯数据)
序列化速度 较快 最快
跨语言兼容 仅 Java 支持多语言 支持多语言
版本兼容性
调试成本
内存开销 极大 中等 极小
  1. 序列化选型原则

    • 简单字符串场景:直接用 StringRedisTemplate(原生高效);

    • 复杂对象场景:优先用 StringRedisTemplate + 手动JSON序列化(节省内存);

    • 多类型混合场景(如 Set/List 存不同对象):用自定义 RedisTemplate + Jackson2Json(保留 @class,牺牲少量内存换便捷性)。

6. SpringDataRedis 最佳实践
  1. 优先用 StringRedisTemplate:大部分场景下 Key/Value 为字符串,避免序列化问题;仅需存储对象时用自定义 RedisTemplate。

  2. 避免频繁创建模板实例:RedisTemplate 是线程安全的,注入后复用即可,无需每次操作新建。

  3. 批量操作优化 :使用executePipelined()实现管道操作,减少网络往返:

    java 复制代码
    // 批量插入数据(管道)
    stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        for (int i = 0; i < 1000; i++) {
            connection.stringCommands().set(("key:" + i).getBytes(), ("value:" + i).getBytes());
        }
        return null;
    });
  4. 事务支持 :通过multi()/exec()实现 Redis 事务,但需注意 Redis 事务仅保证原子性,不支持回滚:

    java 复制代码
    stringRedisTemplate.multi(); // 开启事务
    stringRedisTemplate.opsForValue().set("key1", "value1");
    stringRedisTemplate.opsForValue().set("key2", "value2");
    stringRedisTemplate.exec();  // 执行事务
  5. 缓存注解结合 :搭配@Cacheable/@CachePut/@CacheEvict简化缓存操作(Spring Cache):

    java 复制代码
    @Service
    public class UserService {
        // 查询时缓存,key为user:info:id
        @Cacheable(value = "user", key = "'info:' + #id")
        public User getUserById(Long id) {
            // 从数据库查询用户逻辑
            return new User(id, "张三", 25);
        }
    }

三、其他客户端核心实践(精简版)

1. Jedis

java 复制代码
// 连接池配置 + 资源自动归还
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(50);
try (Jedis jedis = new JedisPool(poolConfig, "127.0.0.1", 6379).getResource()) {
    jedis.set("key", "value");
}

2. Redisson(分布式锁核心)

java 复制代码
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("distributed-lock");
try {
    if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
        // 业务逻辑
    }
} finally {
    lock.unlock();
}

四、通用最佳实践

  1. 连接池配置 :根据并发量设置max-active(50-200)、max-idle(20-50),避免连接耗尽。

  2. 异常处理 :捕获RedisConnectionFailureException,实现 3 次以内重试(避免网络波动导致失败)。

  3. 大 Key 拆分:单个 Key 值超过 10KB 时拆分,避免序列化 / 传输耗时。

  4. 环境隔离 :测试 / 生产环境通过database索引(如测试用 1,生产用 0)或 Key 前缀区分,避免数据污染。

相关推荐
止语Lab4 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活4 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困4 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn4 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530144 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908404 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金4 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
X-⃢_⃢-X4 小时前
十、Redis之布隆过滤器
数据库·redis·缓存
人间凡尔赛5 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒5 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端