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 前缀区分,避免数据污染。

相关推荐
程序员ggbond6 小时前
springboot的一些应用总结
后端
程序员爱钓鱼6 小时前
Node.js 编程实战:MySQL PostgreSQL数据库操作详解
后端·node.js·trae
Ryana6 小时前
协程不是银弹:历时半年,终于搞清了每分钟120次YGC的真相
jvm·后端
Sammyyyyy6 小时前
Django 6.0 发布,新增原生任务队列与 CSP 支持
数据库·后端·python·django·sqlite·servbay
雨雨雨雨雨别下啦6 小时前
SSM+Spring Boot+Vue.js3期末复习
vue.js·spring boot·后端
honder试试6 小时前
Springboot实现Clickhouse连接池的配置和接口查询
spring boot·后端·clickhouse
武子康6 小时前
大数据-185 Logstash 7 入门实战:stdin/file 采集、sincedb/start_position 机制与排障
大数据·后端·logstash
DemonAvenger6 小时前
Redis内存管理与优化策略:避免OOM的最佳实践
数据库·redis·性能优化
想搞艺术的程序员6 小时前
Go语言环形队列:原理剖析、编程技巧与核心优势
后端·缓存·golang