一、为什么需要关注Redis连接管理

Redis作为高性能的内存数据库,在Java项目中经常被用作缓存或消息队列。但如果不注意连接管理,很容易出现连接泄漏、性能瓶颈等问题。想象一下,你的应用在高并发时突然变慢,排查半天发现是Redis连接池耗尽了,这种场景是不是很熟悉?

Java操作Redis主要有两种方式:

  1. 使用Jedis这样的客户端库直接连接
  2. 通过Spring Data Redis这样的框架封装

无论哪种方式,连接管理都是核心问题。比如下面这个典型的错误示例(技术栈:Java + Jedis):

// 反例:没有正确关闭的连接
public void wrongUsage() {
    Jedis jedis = new Jedis("localhost", 6379);  // 创建连接
    jedis.set("key", "value");  // 执行操作
    // 忘记调用jedis.close()!
}

这个代码跑几次可能没问题,但在生产环境运行一段时间后,Redis服务器就会因为连接数过多而拒绝服务。正确的做法应该是使用try-with-resources:

// 正例:自动关闭连接
public void correctUsage() {
    try (Jedis jedis = new Jedis("localhost", 6379)) {
        jedis.set("key", "value");
    }  // 自动调用close()
}

二、连接池的最佳实践

直接创建和销毁连接代价很高,所以生产环境一定要用连接池。以JedisPool为例:

// 初始化连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(50);          // 最大连接数
poolConfig.setMaxIdle(10);          // 最大空闲连接
poolConfig.setMinIdle(5);           // 最小空闲连接
poolConfig.setTestOnBorrow(true);   // 取连接时测试连通性

// 创建连接池
try (JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379)) {
    try (Jedis jedis = jedisPool.getResource()) {
        jedis.set("pooledKey", "value");
    }
}

关键参数说明:

  • MaxTotal:根据业务QPS调整,建议500QPS配20-30个连接
  • TestOnBorrow:能避免拿到已失效的连接,但会有轻微性能损耗
  • 连接池应该作为单例全局使用,不要频繁创建销毁

三、序列化方案的选型

当Java对象需要存入Redis时,序列化方式直接影响性能和可维护性。常见的方案有:

  1. JDK序列化:兼容性好但性能差
  2. JSON:可读性好但占用空间大
  3. Protobuf:高效但需要预定义Schema
  4. Kryo:性能极致但兼容性差

以Spring Data Redis配置JSON序列化为例:

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer替换默认序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        template.setDefaultSerializer(serializer);
        
        return template;
    }
}

存储复杂对象时的对比示例:

// 用户对象
public class User implements Serializable {
    private Long id;
    private String name;
    // 省略getter/setter
}

// 测试不同序列化
public void testSerialization() {
    User user = new User(1L, "张三");
    
    // JDK序列化:产生不可读的二进制
    redisTemplate.opsForValue().set("user:jdk", user);
    
    // JSON序列化:存储为可读字符串
    redisTemplate.opsForValue().set("user:json", user);
}

四、实战中的避坑指南

  1. 连接泄漏排查
    在Redis客户端执行CLIENT LIST命令,观察idle时间过长的连接

  2. 序列化兼容性
    修改类结构后,旧的序列化数据可能无法反序列化。建议:

    • 添加serialVersionUID
    • 或者采用向前兼容的序列化方案如Protobuf
  3. 大Key问题
    单个Value不宜超过1MB,否则会影响Redis性能。解决方案:

    • 拆分为多个Key
    • 使用Hash结构存储
// 大Key拆分示例
public void saveLargeData() {
    // 反例:直接存储10MB的字符串
    // jedis.set("hugeKey", giantString);
    
    // 正例:拆分为100个100KB的块
    for (int i = 0; i < 100; i++) {
        String chunk = giantString.substring(i*100000, (i+1)*100000);
        jedis.hset("hugeKey:chunks", "chunk_" + i, chunk);
    }
}
  1. Pipeline批量操作
    当需要执行多个命令时,使用Pipeline能显著减少网络开销:
public void usePipeline() {
    try (Jedis jedis = jedisPool.getResource()) {
        Pipeline pipeline = jedis.pipelined();
        for (int i = 0; i < 1000; i++) {
            pipeline.set("pipeline:" + i, "value");
        }
        pipeline.sync();  // 一次性发送所有命令
    }
}

五、总结与选型建议

对于大多数Java项目,推荐的技术组合是:

  • 连接管理:使用JedisPool或Lettuce连接池
  • 序列化:Spring Data Redis + Jackson JSON
  • 监控:通过Redis的INFO命令定期检查连接数

特殊场景下的优化方向:

  • 超高并发:考虑Lettuce的异步API
  • 跨语言需求:改用MessagePack等格式
  • 极端性能:评估Kryo或FST序列化

记住,没有银弹方案,要根据你的具体业务特点做技术选型。比如一个简单的电商系统可能用JSON序列化就够了,而金融级的交易系统可能需要Protobuf带来的强类型保障。