仓颉原生Redis客户端探索与实践
关于仓颉Redis客户端 仓颉编程语言是一个支持高效、安全、全场景应用开发的现代编程语言,具有强安全、高性能、高效率、全场景等特点,同时具备以下现代编程语言的核心特性: 支持自动内存管理 支持完备的类型系统 支持扩展、类型推断、元编程 支持过程式、函数式和面向对象多种编程范式 支持轻量级线程模型 支持自动微分以及AI编程 支持多种运行模式,满足不同场景应用开发诉求 内置完整的工具链,包括包管理、代码格式化、构建和测试等 当前,大量在线业务系统需要使用Redis作为企业应用数据缓存来提升业务处理性能,提供一个仓颉原生的高性能Redis客户端SDK,支持Redis单机模式、哨兵模式、集群模式的客户端成为当务之急。 仓颉Redis客户端redis-sdk项目已经开源并发布到仓颉社区,特性为: 支持RESP2和RESP3协议 接口设计兼容jedis接口语义 支持哨兵模式和集群模式 支持发布订阅模式 支持单连接多线程模式 丰富的管理命令支持 完备的单元测试覆盖 架构简洁,易于扩展 总体设计 一、仓颉redis-sdk项目的总体设计如下: 1、接口层 提供创建到Redis单实例服务端、哨兵模式高可用服务端、集群模式高可用服务端的连接,提供执行Redis命令的接口方法。 2、服务层 命令模块:发送Redis命令,并收取命令对应的响应返回给调用方。 订阅模块:发动订阅命令或者模式匹配的订阅命令,订阅相应频道,并收取服务端推送的订阅消息。支持阻塞订阅和非阻塞订阅,阻塞订阅等到服务端返回的订阅频道数为0,或者订阅客户端关闭时退出;非阻塞客户端发送订阅命令后立即返回。 哨兵模块:记录哨兵模式的高可用集群的状态,连接到多个哨兵实例并监听主实例的变化,当主实例发生变化时,后续命令切换到新的主实例上执行。 集群模块:记录集群实例的状态,根据命令的参数计算出对应的槽位后,将命令发送到该槽位对应的集群实例执行。 3、编解码层 将Redis命令编码为二进制数组,用于在网络上传输。将从网络上收到的数据进行解码,转换为Redis命令对应的响应。支持RESP2和RESP3协议的编解码。 4、通信层 创建TCP连接,使用TCP连接发送请求报文和收取响应报文。支持连接空闲超时和连接重建。支持TLS通信。 二、Redis命令的处理流程如下: 发送命令的流程如下: 客户端执行命令时,将命令名称和命令对应的参数封装为RedisCommand类。 RedisCommandToByteEncoder将RedisCommand编码为RESP协议的二进制数据,最终由SocketConnection将二进制数据发送给Redis服务端。 客户端调用RedisComand的waitForResponse等待响应。 接收命令响应的流程如下: SocketConnection在读取到二进制数据后,将二进制数据交ByetToRedisMessageDecoder解码。 ByetToRedisMessageDecoder将解码结果RedisMessage交给RedisCommandHandler进行下一步处理。 RedisCommandHandler将RedisMesssage设置到RedisCommand中,并通知等待响应的客户端。 三、哨兵模式执行命令的流程如下: 客户端执行命令时,会通过SentinelCommandExectutor从SentinelConnectionProvider中获取到master实例的连接; SentinelManager负责维护哨兵实例的高可用集群的状态,会创建连接到每一个哨兵实例的订阅器RedisSubscriber; RedisSubscriber从哨兵实例订阅switch-master频道; SentinelManager在收到RedisSubscriber的master实例发生变化的通知后,会通知SentinelConnectionProvider更新连接到新的master实例。 四、集群模式执行命令的流程如下: 客户端执行命令时,会先计算命令的Key对应的槽位; ClusterCommandExectutor从ClusterConnectionProvider获取槽位对应的集群实例的连接用于执行命令; ClusterInfoCache负责维护集群模式的高可用集群的状态,会缓存所有集群实例的槽位信息; 当集群实例的槽位信息发生变化导致命令执行失败时,ClusterInfoCache会重新更新槽位信息,并在更新完成后再次执行命令; TopologyRefreshExecutor会定期更新ClusterInfoCache中缓存的槽位信息。 五、发布订阅模块的处理流程如下: 使用RedisSubscriber类向Redis服务端订阅频道; 使用RedisClient或者redis-cli向Redis服务端发布消息; Redis服务端收到消息后,会将消息推送给订阅的客户端,由RedisSubscriberHandler进行处理; RedisSubscriberHandler回调RedisSubscriberListener的onMessage方法处理收到的订阅消息。 客户端的使用 单实例的客户端 创建单实例的Redis客户端需要使用RedisClientBuilder构建器,并提供Redis服务的主机名和监听端口: 使用RedisClient执行Redis命令操作: 哨兵模式的客户端 创建哨兵模式的Redis客户端需要使用SentinelRedisClientBuilder构建器,并提供哨兵监控的主从集群名称masterName和哨兵实例的地址列表: 集群模式的客户端 创建集群模式的Redis客户端需要使用ClusterRedisClientBuilder构建器,并提供集群实例的地址列表: 性能测试 测试用例 为了便于对比性能,仓颉redis-sdk项目提供了压测客户端,位于项目根目录下的benchmark/mutli_thread目录。使用方式如下: export cjHeapSize=2048mb benchmark/build/release/bin/main --host=Redis服务的主机名 --port=Redis服务的端口 --maxConnections=Socket连接数 --password=Redis服务的密码 --threadCount=客户端并发的线程数 --totalRequestCount=压测总请求数 测试环境 性能测试时使用两台PC机器,一台运行Redis服务,一台运行压测客户端,两台机器的配置如下: 操作系统:CentOS 8.4 CPU:Intel(R) Core(TM) i7-8700 @ 3.20GHz 内存:16GB Redis版本:7.2.3 仓颉SDK版本:0.51.4 测试数据 为了对比仓颉redis-sdk客户端和Java Jedis客户端的性能,选取如下两个经典场景: 场景一:200个客户端线程/协程,1个Socket连接 场景二:200个客户端线程/协程,3个Socket连接 其余场景,感兴趣的读者可以下载压测用例自行对比。 1、200个客户端线程/协程,1个Socket连接 2、200个客户端线程/协程,3个Socket连接 3、测试结论 综合以上测试数据TPS的平均值,对比如下: 在多线程1条Socket连接的场景,仓颉redis-sdk客户端比Java Jedis客户端TPS高29.21倍;多线程3条Socket连接的场景,仓颉redis-sdk客户端比Java Jedis客户端TPS高15.87倍。仓颉redis-sdk客户端使用单条Socket连接就能达到较高的TPS。 造成这种差距的主要原因一方面是仓颉语言本身的高性能优势,另外一方面是客户端在设计和实现上的差异:Jedis客户端默认采用同步模式,每次只能发送一个请求并等待其响应;并且Jedis的客户端连接不支持多线程同时使用,需要使用连接池来管理Jedis连接以应对多线程环境。仓颉Redis客户端默认使用Pipeline机制,支持在一个连接上串行发送多个请求并异步接收响应,能有效减少线程之间的锁的竞争并提高吞吐率;并且仓颉Redis客户端支持多线程共享使用,无需引入连接池来协调多个线程对连接资源的访问控制,规避了由连接池引入的线程同步开销及潜在的锁竞争问题,提升了系统效率和响应速度。 共建仓颉生态 基于仓颉语言完备的产品文档和先进的开发工具链,我们团队迅速完成了仓颉Redis客户端的开发和测试工作,达到了Java语言Redis客户端的同等能力。通过此次为社区贡献仓颉原生Redis客户端的探索与实践,我们深刻体会到了仓颉编程语言的多重优势: 拥有现代编程语言的核心特性,如类型推断、自动内存管理以及元编程等,使得编程更加高效和灵活。 强大的运行时安全性,从根本上消除了空指针的隐患,保障了代码的稳健性。 易于扩展,仓颉的扩展功能(Extensions)允许为现有类添加新功能,无需继承或使用装饰模式,使代码简洁、易读,且维护性更好。 支持轻量级线程模型,多线程高IO场景具有较好的性能表现。 内置单元测试支持,易于开发高效稳定的代码。 内置诊断工具,极大地方便了性能优化和问题排查。 仓颉生态的持续发展仍需我们共同努力,我们诚挚地呼吁更多的开发者加入仓颉社区,共同为仓颉生态的建设贡献自己的力量。














