Ubuntu 22.04 编译安装与实战 redis-plus-plus C++ 客户端 1. 项目概述为什么选择 redis-plus-plus如果你正在用 C 开发一个需要高性能数据缓存、消息队列或者会话存储的后端服务那么 Redis 大概率是你的技术栈选项之一。作为一个 C 开发者直接去解析 Redis 的 RESP 协议或者用裸的 socket 去通信显然不是明智之举我们需要一个成熟、高效且符合 C 习惯的客户端库。市面上选择不少比如hiredis它是一个轻量级的 C 库很多 C 封装都基于它。但今天要聊的redis-plus-plus在我看来是目前 C 生态中连接 Redis 的“瑞士军刀”。简单说redis-plus-plus是一个基于hiredis的 C17 客户端库。它的设计哲学很明确提供类型安全、直观易用、高性能且功能完整的接口。相比于直接使用hiredis它帮你处理了连接池、重连、命令管道pipelining、事务、发布订阅等复杂逻辑让你能像调用本地 STL 容器一样去操作 Redis。项目作者是 sewenew在 GitHub 上非常活跃社区和文档也都相当不错。这次我们就以 Ubuntu 22.04 LTS 这个目前非常稳定且流行的服务器操作系统为例从头到尾走一遍redis-plus-plus的安装、配置和基础使用。无论你是要在云服务器上部署服务还是在本地开发环境搭建测试这套流程都适用。我会把过程中容易踩的坑、关键的配置参数以及我实际项目中的使用心得都揉进去目标是让你看完就能在自己的项目里用起来。2. 环境准备与依赖安装在开始编译安装redis-plus-plus之前我们需要确保系统环境是干净的并且所有必要的依赖都已就位。Ubuntu 22.04 默认的软件源已经比较新了这为我们省去了不少麻烦。2.1 系统更新与基础工具首先打开终端更新一下包列表并升级现有软件包。这是一个好习惯能避免一些因基础库版本过旧导致的编译问题。sudo apt update sudo apt upgrade -y接着安装编译所需的工具链包括g、cmake和make。redis-plus-plus使用 CMake 作为构建系统所以cmake是必须的。sudo apt install -y build-essential cmake pkg-config注意pkg-config是一个用来帮助在编译时查找库文件和头文件的小工具虽然redis-plus-plus的 CMake 脚本可能不直接依赖它但安装上可以避免一些潜在的、其他依赖库的查找问题。2.2 安装核心依赖hiredisredis-plus-plus底层通信依赖于hiredis。虽然它的源码包自带了一个hiredis子模块但为了系统管理的统一性和避免版本冲突我强烈建议通过系统包管理器安装开发版。sudo apt install -y libhiredis-dev安装完成后你可以通过dpkg -L libhiredis-dev查看头文件和库被安装到了哪里通常是/usr/include/hiredis和/usr/lib/x86_64-linux-gnu/。确保它能被找到是后续编译成功的关键。2.3 可选但推荐的依赖OpenSSL 和 TLS/SSL 支持如果你的 Redis 服务器启用了 TLS/SSL 加密连接特别是在云服务或生产环境中很常见那么你需要安装 OpenSSL 的开发库。sudo apt install -y libssl-dev即使暂时不用 SSL先安装上也无妨。redis-plus-plus在编译时会自动检测是否存在 OpenSSL并决定是否编译 SSL 支持。有了这个你的客户端就具备了连接加密 Redis 服务的能力为未来留好了扩展性。3. 编译与安装 redis-plus-plus依赖就绪后我们就可以开始编译redis-plus-plus本身了。这里我推荐从 GitHub 拉取最新的稳定版本源码进行编译这样能获得最新的特性和修复。3.1 获取源代码首先找一个合适的目录比如~/workspace然后克隆仓库。使用--recursive参数确保子模块它自带的hiredis备用源码也被拉取下来。cd ~ git clone --recursive https://github.com/sewenew/redis-plus-plus.git cd redis-plus-plus实操心得虽然我们通过系统包安装了hiredis但拉取子模块是个好习惯。万一系统版本不兼容我们可以选择编译并使用源码包内的hiredis。不过在 Ubuntu 22.04 上APT 提供的版本通常足够新且稳定。3.2 创建构建目录并运行 CMake遵循 CMake 的“外部构建”最佳实践我们在源码目录外创建一个单独的构建目录。mkdir build cd build接下来是关键的 CMake 配置步骤。这里有几个重要的参数需要关注cmake .. -DCMAKE_BUILD_TYPERelease -DREDIS_PLUS_PLUS_CXX_STANDARD17 -DREDIS_PLUS_PLUS_BUILD_TESTOFF让我解释一下这几个参数-DCMAKE_BUILD_TYPERelease指定构建类型为发布模式。这会开启编译器优化如-O3移除调试信息生成性能最优的库文件。如果是开发调试可以设为Debug。-DREDIS_PLUS_PLUS_CXX_STANDARD17明确指定使用 C17 标准。redis-plus-plus需要 C11 及以上但使用 C17 能确保所有现代特性可用也是作者推荐的标准。-DREDIS_PLUS_PLUS_BUILD_TESTOFF禁用测试用例的编译。这能显著加快编译速度因为我们只需要库文件本身不需要运行它的单元测试。如果 CMake 运行成功你会看到输出中包含了关键的总结信息例如-- Build type: Release -- C standard: 17 -- hiredis lib: /usr/lib/x86_64-linux-gnu/libhiredis.so -- TLS/SSL support: ON (found OpenSSL) -- ... -- Configuring done -- Generating done请特别留意hiredis lib和TLS/SSL support这两行确认它找到了我们系统安装的hiredis并且 SSL 支持已开启。3.3 编译与安装配置完成后使用make进行编译。为了加快速度可以使用-j参数指定并行编译的作业数通常设置为 CPU 核心数。make -j$(nproc)编译过程可能需要一两分钟。成功后就可以将库安装到系统目录了sudo make install默认情况下这会将头文件安装到/usr/local/include/sw/将库文件libredis.so和libredis.a安装到/usr/local/lib/。/usr/local是 Linux 系统存放本地编译软件的标准位置。3.4 验证安装与系统配置安装完成后我们需要让系统知道新库的位置。更新动态链接库缓存运行以下命令让系统载入/usr/local/lib下的新库。sudo ldconfig验证头文件和库你可以快速检查一下文件是否存在。ls /usr/local/include/sw/redis/ # 应该看到一堆 .h 头文件 ls /usr/local/lib/libredis.so # 应该看到共享库文件至此redis-plus-plus库本身就已经在你的 Ubuntu 22.04 系统上安装完毕了。4. 基础使用从连接到第一个命令理论说再多不如动手试一下。我们来创建一个最简单的 C 程序连接 Redis 并执行几个基本命令。4.1 准备一个测试用的 Redis 服务器首先确保你有一个可以连接的 Redis 服务器。如果你还没有可以在同一台机器上快速安装一个用于测试sudo apt install -y redis-server sudo systemctl start redis-server sudo systemctl enable redis-server默认情况下Redis 服务会监听127.0.0.1:6379且没有密码。这正好用于我们的本地测试。4.2 编写第一个 C 程序创建一个新的目录和源文件例如~/test_redispp/。mkdir ~/test_redispp cd ~/test_redispp touch test_basic.cpp用你喜欢的编辑器如vim,nano, 或 VSCode打开test_basic.cpp输入以下代码#include sw/redis/redis.h #include iostream int main() { try { // 1. 创建连接选项连接到本地默认Redis sw::redis::ConnectionOptions connection_opts; connection_opts.host 127.0.0.1; // Redis服务器地址 connection_opts.port 6379; // Redis端口 connection_opts.socket_timeout std::chrono::milliseconds(200); // 读写超时 // 2. 创建连接池选项可选但对于生产环境推荐 sw::redis::ConnectionPoolOptions pool_opts; pool_opts.size 3; // 连接池大小 // 3. 创建Redis客户端对象 sw::redis::Redis redis(connection_opts, pool_opts); // 4. 执行命令设置一个键值对 redis.set(my_key, Hello, redis-plus-plus!); // 5. 执行命令获取键值 auto val redis.get(my_key); if (val) { std::cout The value of my_key is: *val std::endl; } else { std::cout my_key does not exist. std::endl; } // 6. 执行命令判断键是否存在 if (redis.exists(my_key)) { std::cout my_key exists in Redis. std::endl; } // 7. 执行命令删除键 redis.del(my_key); std::cout my_key has been deleted. std::endl; } catch (const sw::redis::Error e) { // 捕获所有redis-plus-plus抛出的异常 std::cerr Redis error: e.what() std::endl; return 1; } catch (const std::exception e) { // 捕获其他标准异常 std::cerr Standard error: e.what() std::endl; return 1; } return 0; }4.3 编译并运行测试程序现在来编译这个程序。你需要链接redis.so和hiredis.so这两个库。g -stdc17 -o test_basic test_basic.cpp -lredis -lhiredis -pthread参数解析-stdc17指定 C 语言标准必须与编译库时的一致。-o test_basic指定输出可执行文件名。-lredis -lhiredis链接redis-plus-plus和hiredis库。编译器会在默认库路径如/usr/local/lib,/usr/lib中查找libredis.so和libhiredis.so。-pthread因为redis-plus-plus内部可能使用了线程所以需要链接 POSIX 线程库。如果编译成功运行它./test_basic你应该能看到如下输出The value of my_key is: Hello, redis-plus-plus! my_key exists in Redis. my_key has been deleted.恭喜你已经成功使用redis-plus-plus完成了与 Redis 的第一次交互。这个简单的例子涵盖了连接建立、基本的 SET/GET/EXISTS/DEL 命令以及异常处理。5. 核心功能深度解析与实战掌握了基础连接和命令后我们来深入探讨redis-plus-plus的一些核心特性和高级用法。这些是你在实际项目中必然会用到的。5.1 连接管理与连接池在上面的例子中我们隐式地使用了连接池。ConnectionPoolOptions允许你精细控制连接行为。pool_opts.size连接池大小。这并非越大越好。设置过大会浪费服务器资源过小则可能在并发高时成为瓶颈。一个经验值是设置为你的应用服务器线程数或稍多一点。对于我们的测试3个足够了。pool_opts.wait_timeout当连接池耗尽时客户端等待一个可用连接的最长时间。默认是0ms一直等待。在生产环境中建议设置一个合理的超时如100ms避免线程被无限期挂起。pool_opts.connection_lifetime一个连接在池中的最长存活时间。超过这个时间即使连接是好的也会被关闭并创建新连接。这有助于缓解一些网络中间件如代理或 Redis 服务器端的连接空闲超时问题。可以设置为几分钟到几十分钟。sw::redis::ConnectionPoolOptions pool_opts; pool_opts.size 5; pool_opts.wait_timeout std::chrono::milliseconds(100); pool_opts.connection_lifetime std::chrono::minutes(10);注意事项Redis对象是线程安全的吗是的sw::redis::Redis对象是线程安全的因为它内部通过连接池来管理连接。多个线程可以安全地调用同一个Redis对象的方法。但是Pipeline和Transaction对象不是线程安全的它们应该在单个线程内创建和使用。5.2 丰富的数据类型支持redis-plus-plus对 Redis 的各种数据类型提供了原生、类型安全的接口用起来非常直观。字符串 (String):redis.set(counter, 100); redis.incr(counter); // 自增结果变为101 auto num redis.getlong long(counter); // 明确获取为long long类型哈希 (Hash):std::unordered_mapstd::string, std::string user {{name, Alice}, {age, 30}}; redis.hset(user:1000, user.begin(), user.end()); // 批量设置字段 auto name redis.hget(user:1000, name); // 获取单个字段 std::unordered_mapstd::string, std::string all_fields; redis.hgetall(user:1000, std::inserter(all_fields, all_fields.begin())); // 获取所有字段列表 (List):redis.rpush(task_queue, task1); // 右侧推入 redis.rpush(task_queue, {task2, task3}); // 批量推入 auto task redis.lpop(task_queue); // 左侧弹出实现队列集合 (Set) 与有序集合 (Sorted Set):redis.sadd(tags, {cpp, redis, database}); if (redis.sismember(tags, cpp)) { // ... 判断成员是否存在 } redis.zadd(leaderboard, {{player1, 100.5}, {player2, 89.0}}); // 带分数添加 std::vectorstd::pairstd::string, double top3; redis.zrevrange(leaderboard, 0, 2, std::back_inserter(top3)); // 获取前三名地理空间 (Geospatial):redis.geoadd(cities, {{13.361389, 38.115556, Palermo}, {15.087269, 37.502669, Catania}}); auto dist redis.geodist(cities, Palermo, Catania, sw::redis::GeoUnit::KM); // 计算距离5.3 管道 (Pipeline) 与事务 (Transaction)这是提升性能的两个利器。管道 (Pipeline)将多个命令一次性发送给服务器减少网络往返延迟 (RTT)。适用于需要连续执行多个不依赖中间结果的命令场景。auto pipe redis.pipeline(); // 创建一个管道对象 auto replies pipe.set(key1, val1) .get(key2) .incr(counter) .exec(); // 一次性发送所有命令返回一个回复数组 // replies 是一个 vectorReplyUPtr // 你需要按照命令发送的顺序来解析回复 std::cout SET reply: replies[0]-str() std::endl; // 通常是OK if (replies[1]) { std::cout GET reply: replies[1]-str() std::endl; }事务 (Transaction)通过MULTI/EXEC确保一系列命令的原子性执行。redis-plus-plus的接口让它用起来很像管道。auto tx redis.transaction(); // 创建一个事务对象 auto tx_replies tx.set(tx_key1, 100) .incr(tx_key1) // 在事务内这个incr看到的是事务开始时的值吗不它看到的是事务队列中的命令顺序。 .get(tx_key1) .exec(); // 事务内的命令要么全部成功要么全部失败 // 注意事务内命令的回复在 EXEC 前不会返回EXEC 后返回一个数组。 long long final_val tx_replies[2]-parselong long(); std::cout Value after transaction: final_val std::endl; // 输出 101重要区别管道是为了性能不保证原子性事务是为了原子性但会消耗更多资源因为需要等待 EXEC。你可以将管道和事务结合使用auto tx redis.transaction(true);这个true参数表示在事务中使用管道能进一步提升事务内命令的网络效率。5.4 发布订阅 (Pub/Sub)redis-plus-plus提供了同步和异步两种方式的发布订阅接口。这里展示同步接口它更简单直观。// 订阅者 sw::redis::Subscriber sub redis.subscriber(); sub.on_message([](std::string channel, std::string msg) { std::cout Received message from channel channel : msg std::endl; }); sub.subscribe(news); // 订阅频道 sub.subscribe(sports); // 进入监听循环通常在一个独立线程中运行 // 这里为了示例我们只监听一小段时间 sub.consume_for(std::chrono::seconds(10)); // 在另一个线程或进程中发布者可以这样做 redis.publish(news, Breaking: C23 is finalized!); redis.publish(sports, Game score: 5-3);注意事项同步的consume()或consume_for()是阻塞调用。在实际应用中你通常需要在一个专门的线程中运行订阅者的消费循环以免阻塞主线程。redis-plus-plus也提供了基于std::future的异步接口适合集成到事件驱动框架中。6. 高级配置与生产环境考量当你的应用从开发测试走向生产环境时一些配置和最佳实践就显得尤为重要。6.1 连接加密与认证密码认证如果 Redis 服务器配置了密码通过requirepass指令你需要在连接选项中设置。connection_opts.password your_strong_password_here; // 设置密码TLS/SSL 连接如果 Redis 服务器启用了 TLS例如云数据库服务你需要配置相关选项。这要求你在编译redis-plus-plus时启用了 OpenSSL 支持。connection_opts.tls.enabled true; // 启用 TLS // connection_opts.tls.cacert /path/to/ca.crt; // 如果需要验证服务器证书指定CA证书路径 // connection_opts.tls.cert /path/to/client.crt; // 如果需要客户端证书 // connection_opts.tls.key /path/to/client.key;6.2 重连与高可用策略网络是不稳定的。一个健壮的客户端必须具备自动重连能力。connection_opts.connect_timeout std::chrono::milliseconds(500); // 连接超时 connection_opts.socket_timeout std::chrono::milliseconds(200); // 读写超时 // 连接池选项中的重试间隔默认已有可调整 // pool_opts.connection_lifetime 和 pool_opts.wait_timeout 也间接影响连接健康度redis-plus-plus底层使用的hiredis在连接断开后当下次执行命令时会尝试自动重连。但是在极端高并发下瞬间大量重连可能对服务器造成压力。更高级的策略是结合哨兵 (Sentinel)或集群 (Cluster)模式。6.3 使用 Redis 哨兵模式对于高可用部署Redis 哨兵模式是常见选择。redis-plus-plus提供了直接连接哨兵获取主节点地址的方式。sw::redis::ConnectionOptions connection_opts; connection_opts.type sw::redis::ConnectionType::SENTINEL; // 指定为哨兵模式 connection_opts.host sentinel-host; // 哨兵服务器地址 connection_opts.port 26379; // 哨兵端口 connection_opts.password sentinel_password_if_any; // 哨兵密码如果有 connection_opts.sentinel_master mymaster; // 主服务器名称需与哨兵配置一致 // 连接池选项同样适用 sw::redis::Redis redis(connection_opts, pool_opts);这样创建的Redis对象会自动通过哨兵发现当前的主节点并在主节点切换时自动更新连接。你无需在代码中关心主从切换的细节。6.4 使用 Redis 集群模式对于海量数据Redis 集群是解决方案。redis-plus-plus有专门的RedisCluster类来处理集群连接。sw::redis::ConnectionOptions cluster_connection_opts; cluster_connection_opts.host cluster-node-host; // 集群中任意一个节点地址 cluster_connection_opts.port 6379; sw::redis::ConnectionPoolOptions cluster_pool_opts; cluster_pool_opts.size 3; // 创建集群客户端 sw::redis::RedisCluster redis_cluster(cluster_connection_opts, cluster_pool_opts); // 使用方式与单机 Redis 几乎相同库会自动处理 key 的槽位计算和路由。 redis_cluster.set(user:{1000}:name, Alice); // 注意key中包含哈希标签{}用于确保相关key落在同一节点使用集群时要特别注意key 的哈希标签。对于需要跨多个 key 的操作如事务、Lua脚本这些 key 必须位于同一个集群节点上通过{...}哈希标签可以控制这一点。7. 常见问题排查与性能调优即使一切配置正确在实际运行中也可能遇到各种问题。这里记录一些我踩过的坑和解决方法。7.1 编译与链接问题问题1编译时找不到sw/redis/redis.h头文件。原因编译器在默认包含路径中找不到头文件。make install将头文件装在了/usr/local/include下。解决确保你的编译命令正确。如果使用 CMake在CMakeLists.txt中添加include_directories(/usr/local/include)和link_directories(/usr/local/lib)并使用target_link_libraries(your_target redis hiredis pthread)。问题2链接时找不到libredis.so。原因链接器路径问题或ldconfig未更新。解决运行sudo ldconfig更新缓存。在编译命令中显式指定库路径-L/usr/local/lib -lredis -lhiredis。检查库文件是否存在ls -la /usr/local/lib/libredis.so*。问题3运行时出现GLIBCXX_3.4.29‘ not found等动态链接错误。原因编译redis-plus-plus使用的 GCC 版本高于你运行环境中的版本。解决在开发和生产环境尽量保持 GCC 版本一致。或者在较低版本的系统上使用静态链接编译redis-plus-plus时加-DREDIS_PLUS_PLUS_BUILD_STATICON链接时使用-lredis-static但静态链接会增大二进制体积。7.2 运行时连接与性能问题问题1连接超时或读写超时。排查检查 Redis 服务器是否运行redis-cli ping。检查防火墙/安全组规则是否放行了6379端口。检查connection_opts中的host、port是否正确。适当增加connect_timeout和socket_timeout的值特别是在网络延迟较高的环境中。注意超时设置太短会导致频繁超时失败太长则会在网络故障时导致线程长时间阻塞。需要根据网络状况调整。问题2Broken pipe或Connection reset by peer错误。原因服务器端主动关闭了连接可能由于空闲超时、重启或客户端异常。解决redis-plus-plus的连接池本身具备一定的重连能力。确保设置了合理的pool_opts.connection_lifetime短于服务器的timeout配置让连接定期刷新。对于长时间空闲的连接可以启用 TCP keepalive需要在系统层面或 hiredis 底层配置redis-plus-plus接口未直接暴露。问题3在高并发下出现性能瓶颈或连接数过多。调优连接池大小pool_opts.size不是越大越好。一个经验法则是设置为应用服务器工作线程数的 1-2 倍。可以通过监控 Redis 的connected_clients指标来调整。使用管道对于批量操作务必使用 Pipeline能极大减少 RTT提升吞吐量。避免大 Key 和大 ValueRedis 是单线程的操作一个大 Key如包含百万成员的 Hash会阻塞其他命令。设计数据结构时要进行拆分。合理使用异步接口对于非即时响应的操作可以考虑使用redis-plus-plus的异步接口返回std::future避免阻塞调用线程。7.3 内存与资源管理问题内存泄漏。排查redis-plus-plus本身经过良好测试内存管理是可靠的。泄漏通常发生在使用方式上。注意确保Pipeline和Transaction对象在合适的作用域内创建和销毁不要长时间持有。对于订阅模式如果在一个长期运行的线程中使用Subscriber要确保在程序退出或不再需要时正确调用sub.unsubscribe()并结束消费循环。使用 Valgrind 等工具进行内存检查valgrind --leak-checkfull ./your_redis_program。8. 集成到 CMake 项目的最佳实践在真实的 C 项目中我们很少直接用命令行g编译而是使用 CMake 这样的构建系统。下面是一个完整的CMakeLists.txt示例展示如何将redis-plus-plus集成到你的项目中。cmake_minimum_required(VERSION 3.10) project(MyRedisApp LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找 redis-plus-plus 库 find_package(redis REQUIRED) # 查找 hiredis 库 find_package(hiredis REQUIRED) # 如果你的系统安装路径非标准可以手动指定路径 # include_directories(/usr/local/include) # link_directories(/usr/local/lib) add_executable(my_app main.cpp src/redis_client.cpp) # 链接库 target_link_libraries(my_app PRIVATE redis::redis hiredis::hiredis pthread ) # 或者如果 find_package 失败可以使用 pkg-config需先安装 pkg-config # find_package(PkgConfig REQUIRED) # pkg_check_modules(REDISPP REQUIRED IMPORTED_TARGET redis) # pkg_check_modules(HIREDIS REQUIRED IMPORTED_TARGET hiredis) # target_link_libraries(my_app PRIVATE PkgConfig::REDISPP PkgConfig::HIREDIS pthread)为了让find_package正常工作redis-plus-plus的安装需要生成 CMake 配置文件。幸运的是我们之前通过make install安装的版本1.3.0 之后通常已经包含了这些文件位于/usr/local/lib/cmake/redis/。如果 CMake 找不到你可能需要检查该路径是否存在或者将CMAKE_PREFIX_PATH设置为/usr/local。最后我想分享一个在微服务架构中使用redis-plus-plus的心得将 Redis 客户端封装成一个单例或依赖注入的服务。这样可以在整个应用中统一管理连接池配置、重试逻辑和监控指标。例如你可以创建一个RedisService类在构造函数中初始化sw::redis::Redis或sw::redis::RedisCluster对象并提供一系列业务相关的原子操作方法如getUserSession、cacheProductInfo。这不仅能提高代码复用性也使得后续切换 Redis 部署模式单机-哨兵-集群或进行 A/B 测试时变更范围最小化。