【C++三方组件】redis-plus-plus:现代 Redis C++ 客户端

【C++三方组件】redis-plus-plus:现代 Redis C++ 客户端

【摘要】:redis-plus-plus 在 hiredis 之上盖了一层现代 C++:std::string 与 Optional 的参数返回、异常错误通道、开箱即用的连接池、对象化的 pipeline 与订阅回调,哨兵与集群也内置。本篇实测连接池四线程并发写、pipeline 的类型化取回、发布订阅回调与异常体系;两个坑位:consume 一次只处理一条已到达的回复、订阅确认本身就是 SUBSCRIBE 的回复。末尾给出与 hiredis 的选型分界。

【关键词】:redis-plus-plus、Redis 客户端、连接池、pipeline、发布订阅

【版本基准】:redis-plus-plus 1.3.15(Apache-2.0)+ hiredis 1.4.1|示例 C++17|实测环境同第 36 篇(Redis 5.0.14.1 @ 127.0.0.1:6379)

1. What:hiredis 的一整层工程外衣

第 36 篇结尾列过 hiredis 的缺口:没有连接池、没有自动重连、reply 手动解析、线程不安全。redis-plus-plus(sewenew/redis-plus-plus)就是补齐这四件事的 C++ 封装------协议层仍是 hiredis,你在工程里穿的是这层外衣:

  • Redis 对象:模板化的命令方法------redis.set(key, val)、redis.get(key)、redis.lpush(...),每个 Redis 命令一个同名方法 ,参数与返回值全是 std::string / std::optional / 整型;
  • 连接池 内置:Redis 对象线程安全地代理到池中连接;
  • 错误走异常 :sw::redis::Error 基类下分 ReplyError、ConnectionError、TimeoutError 等;
  • 哨兵 (Sentinel)与集群 (Cluster)模式内置,ConnectionOptions 一组字段切换。

支持 C++11/14/17,编译期选择;本篇示例按 C++17 用(Optional 返回正好衔接 std::optional)。

2. Why:裸 hiredis 之上的样板代码

用裸 hiredis 写应用,每个命令都是「格式串 + 类型分支 + freeReplyObject」三段式;GET 的 NIL 与 STRING 要分支处理;连接的线程模型要自己规划;断线检测与重连要自己写状态机。这些代码每个项目都要写一遍,而且写错的姿势基本相同 (忘了 free、判错顺序颠倒、重连后忘了重订阅)。redis-plus-plus 把这些样板收进库内:一行 redis.get(key) 背后是「借连接 → 发命令 → 解 reply → Optional 化 → 还连接」的完整路径。代价是引入一层模板编译时间与一个额外依赖(先装 hiredis),换来的是业务代码里没有一行协议细节。

3. How:接入与运行

  1. vcpkg :vcpkg install redis-plus-plus(自动带 hiredis),CMake 走 find_package(redis++ CONFIG REQUIRED),目标是 redis++::redis++;
  2. 源码 :先构建安装 hiredis,再构建安装 redis-plus-plus(它 find_package(hiredis)),共用一个安装前缀------示例工程的一键脚本与逐条命令见 README。

完整示例 redis_pp_demo.cpp:一个 main 串五节------基础读写、连接池并发、pipeline、发布订阅、异常体系。与第 36 篇共用同一台本机 Redis。

4. 基础:ConnectionOptions 与类型化读写

cpp 复制代码
sw::redis::ConnectionOptions opts;
opts.host = "127.0.0.1";
opts.port = 6379;
opts.socket_timeout = std::chrono::milliseconds(500);
// opts.password = "...";  opts.db = 1;  按需

sw::redis::Redis redis(opts);   // 单连接模式
redis.set("name", "redis-plus-plus");
if (auto v = redis.get("name"))  // Optional<string>
  // v->c_str() ...
if (!redis.get("missing"))
  // Redis 的 NIL 就是空 Optional

对照第 36 篇:GET 的 NIL 分支从「判 r->type」变成 if (auto v = ...)------六种 reply 类型全部消失在类型系统里 。⚠️ socket_timeout / connect_timeout 建议永远显式设置:不设的话,网络故障时读操作可能无限期挂起,这是新库最常见的「偶现卡死」来源。

5. 连接池:线程安全从这里开始

cpp 复制代码
sw::redis::ConnectionPoolOptions pool;
pool.size = 4;
sw::redis::Redis pooled(opts, pool);   // 池化模式

std::thread t([&pooled] {
  pooled.set("k", "v");   // 内部借还连接
});

池化与非池化的差别一句话:非池化的 Redis 对象一次只能跑一个操作(一条连接),池化的对象可以多线程并发调用,连接不够时等待归还。实测(4 线程 × 2500 条 SET,value 100 B,本机回环):

text 复制代码
4 线程 x 2500 条:3528.2 ms(0.3 万条/秒)

这个数字与第 36 篇逐条往返同量级------瓶颈在每条命令一次网络往返,加线程只摊薄了等待、没消除往返。真正提速的钥匙还是 pipeline。

6. pipeline:类型化取回

cpp 复制代码
auto pipe = pooled.pipeline();
auto replies = pipe.set("pipe:k", "v1")
                   .incr("pipe:cnt")
                   .incr("pipe:cnt")
                   .get("pipe:k")
                   .exec();          // 一次往返
replies.get<std::string>(0);   // "OK"
replies.get<long long>(1);     // 1

与第 36 篇的 redisAppendCommand / redisGetReply 对照:入队变成链式调用、取回变成按命令序号类型化 get ------freeReplyObject 与 type 分支都不存在了。⚠️ exec() 之前命令不会发出,忘了 exec 是新手的经典丢批量事故。

7. 发布订阅:回调注册 + consume 拉取

cpp 复制代码
Subscriber sub = pooled.subscriber();
sub.on_message([](std::string channel, std::string msg) {
  // 消息到达时被调用
});
sub.subscribe("pp:news");
sub.consume();   // 处理一条已到达的回复

实测输出与第 36 篇一致(收到 [pp:news] hello / world)。两个坑位要立牌:

  1. consume() 一次只处理一条已到达的回复 ------订阅确认占一条,每条消息各占一条;数错了次数,多调的那次会阻塞在没有消息的连接上;
  2. 确认与消息都在同一个订阅连接上串行到达,发布侧用另一条连接(示例用池化对象 publish)。

生产代码里订阅通常配一个专门线程循环 consume(),把阻塞变成「等下一条消息」的正向语义------示例为了输出确定,用 100 ms 小睡等消息到齐再精确消费,这是测试写法不是生产写法。

8. 异常体系与选型

错误从返回值搬进异常,层次清晰:

cpp 复制代码
try {
  pooled.incr("non-number-key");     // 执行层
} catch (const sw::redis::ReplyError& e) { ... }

try {
  sw::redis::Redis dead(bad_opts);   // 传输层
} catch (const sw::redis::Error& e) { ... }

实测输出:ReplyError: ERR value is not an integer...、连接错误: failed to connect to Redis (127.0.0.1:16379): timed out...。sw::redis::Error 是公共基类------只想记日志不想分类时 catch 它一个就够。对照第 36 篇的双通道:hiredis 里「先判 nullptr 再判 type」的纪律,在这里变成两层 catch 的选择。

选型分界一句话:要 C 兼容、要最小依赖、要自己封装,选 hiredis(第 36 篇);要连接池、类型化 API、异常与哨兵集群开箱即用,选 redis-plus-plus(本篇)。还有一个工程判断:重度依赖模板的 redis-plus-plus 编译更慢、二进制更大,与它省下的样板代码相比通常划算------除非你的项目对构建时间极度敏感。

〔关联〕连接池的借还语义与线程安全边界,模式语言里是对象池模式的直接应用,见 cpp-design-patterns。

9. 参考资料

  • 官方仓库 sewenew/redis-plus-plus:README 按命令字母序给全量方法签名,是最全的 API 参考;
  • 连接池 / 哨兵 / 集群配置文档同在仓库 wiki;
  • 完整构建与运行入口:examples/36-37/README(与第 36 篇共用);
  • 上一篇(第 36 篇):hiredis------本篇脚下的协议底座。

参考 :sewenew/redis-plus-plus 1.3.15(Apache-2.0),底层 hiredis 1.4.1。本篇全部输出与吞吐(Optional 读、连接池并发、pipeline 取回、订阅回调、两类异常)均为本机实测(MSVC v145/VS2026,Windows;Redis 5.0.14.1 @ 127.0.0.1:6379)。

相关推荐
SWAGGY..39 分钟前
【C++进阶】:(10)哈希表的原理与实现——从哈希函数到哈希桶
c++·哈希算法·散列表
ab12376841 分钟前
MQTT笔记
java·笔记·mqtt·学习
二宝哥1 小时前
【问题】代理转发到后端使用request.getRequestURL()地址没有端口
java
霸道流氓气质1 小时前
HashiCorp Vault 与云 KMS 入门实战:动态密钥、SecretLeaseContainer 与 Java 集成完整指南
java·开发语言
wuminyu1 小时前
stackChunkOop内部结构与GC屏障的配合工作原理剖析
java·linux·c语言·jvm·c++
xuxigifxfh1 小时前
牛客:HJ21 简单密码
java·开发语言·算法
小π军1 小时前
C 和 C++ 是否都支持函数的重载?
java·c语言·c++
sp42a1 小时前
简单的 Spring 文件上传与下载组件
java·后端·spring
步行cgn1 小时前
为什么 Spring 建议使用构造器注入?
java·后端·spring