Java 操作 Redis 技术怎么选?Jedis 真的过时了吗?

背景

某项目引入了一个新需求,需要根据某数据表的数据进行数据处理,这个数据存储在哪里呢?内存 VS 缓存组件呢?权衡之下选择使用 Redis 缓存。

调研 Java 操作 Redis 的客户端时,主要有 Jedis 和 lettuce 两种,而且各种资料显示后者比前者应用范围更广,效率更高,且是 spring-boot 生态默认支持的。

最开始我也是直接调研 lettuce 的用法的,部署测试时发现引用的 lettuce 依赖的 netty 版本和已有的 netty 版本不匹配,又继续调研 Jedis 。

lettuce 调研

lettuce 最新版本6.5.5 依赖的 netty 是4.1.65,我们那个老项目应用的 netty-all ,最后一次更新是在10年前,版本太低了,兼容不了 lettuce ,其中一些新版本的方法都没有,运行时会报异常。

Jedis 调研

Jedis 倒是一直在更新,依赖的包比较少,项目中都有,而且 json 这个包即使没有,API 也不会报错,可见它不是必须的。 此外,它依赖了连接池包 commons-pool2,注意,低版本的 jedis-2.9.0 依赖的连接池版本也低,高版本的连接池的构造函数语法有变动,需要匹配对应版本。

基于Jedis 的 Redis 操作

Redis 支持三种部署方式:单机、集群和哨兵。那么我们在编写 Redis 操作的时候就要考虑三种类型的客户端,通用的方法是使用连接池。这里可以简单实用工厂模式来实现 Redis 客户端对象的获取,定义顶层抽象类,包含公共的 Redis 的操作方法:

  1. ping()
  2. get(String key)
  3. set(String key, Object value)
  4. setBatch(List<Map> data)
  5. del(String key)

子类实现按三类部署方式对应的客户端API来处理。

第一类,单机连接池和批处理操作:

c 复制代码
// Part1:创建连接池对象
JedisPool jedisPool = new JedisPool(config, url, port, timeout, password, databaseIndex)

// Part2:批量写入,使用 Pipeline 对象
public void setBatch(Map<String, String> datas) {
    try (Jedis jedis = jedisPool.getResource()) {
        Pipeline pipelined = jedis.pipelined();
        datas.forEach((key, value) ->{
           pipelined.set(key, value);
        });
        
        // 一批次刷新
        pipelined.sync();

        // 必须关闭否则会资源泄漏
        pipelined.close();
    } catch (Exception e) {
        logger.error("Redis set data error", e);
    }
}

第二类,集群连接池和批处理操作:

c 复制代码
// Part1:创建连接池对象
Set<HostAndPort> jedisClusterNodes = new HashSet<>();
// TODO 添加集群节点到 jedisClusterNodes

// 创建集群连接池对象
JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes,
                connectTimeout,
                timeout,
                maxRetryCount,
                password,
                poolConfig);

// Part2:批量写入,使用 Pipeline 对象  
public void setBatch(Map<String, String> datas) {
    try (ClusterPipeline pipelined = jedisCluster.pipelined()) {
        datas.forEach((key, value) ->{
           pipelined.set(key, value);
        });
        
        // 一批次刷新
        pipelined.sync();
    } catch (Exception e) {
        logger.error("Redis set data error", e);
    }
}           

第三类,哨兵连接池操作和批处理操作:

c 复制代码
// Part1:创建连接池对象
Set<String> sentinels = new HashSet<>(Arrays.asList(urls.split(",")));
JedisSentinelPool jedisPool = new JedisSentinelPool(masterName,     sentinels,
                poolConfig,
                timeout,
                password,
                databaseIndex);

// Part2:批量写入,使用 Pipeline 对象
public void setBatch(Map<String, String> datas) {
    try (Jedis jedis = jedisPool.getResource()) {
        Pipeline pipelined = jedis.pipelined();
        datas.forEach((key, value) ->{
           pipelined.set(key, value);
        });
        
        // 一批次刷新
        pipelined.sync();

        // 必须关闭否则会资源泄漏
        pipelined.close();
    } catch (Exception e) {
        logger.error("Redis set data error", e);
    }
}

启示录

解决这个需求后,我想到一个问题技术框架也有高低优劣吗?Jedis 这个短小精悍的工具包,而且一直在保持更新,依赖少,新版本提供了 Pipeline 这个批处理的方法,可以将一批操作封装在一起一次提交,效率非常高。

我测试了50万条数据写入 Redis,用 Pipeline 后可以在5秒内完成,真正能达到10万每秒。但是如果单独一条条发送,新版本「5.2.0」的效率反而低于低版本「2.9.0」,集群效率又低于单机效率,高版本的单机在150秒左右,高版本的集群在 600 秒左右。

版本更迭,依赖变化,保持最小依赖不容易啊!

相关推荐
凤山老林7 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne8 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF8 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li9 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
upala9 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna9 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构
码事漫谈9 小时前
因为太想讨好你,所以AI会骗你
后端
吴凡wudianmei9 小时前
每调一次模型都花钱?如何用工程手段把调用压到最少?
前端·后端·架构
花间相见9 小时前
【计算基础|MySQL01】【上手】安装连接、数据类型与建库建表
数据库·后端
花间相见9 小时前
【计算基础|MySQL02】【上手】SQL 增删改查
数据库·后端