Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

加载中...
搜索中...
未找到匹配项
Boost.Redis

Boost.Redis 是一个基于 Redis 构建的高级客户端库,它构建于 Boost.Asio 之上,并实现了 Redis 协议 RESP3。使用 Boost.Redis 的要求是

  • Boost 1.84 或更高版本。
  • C++17 或更高版本。
  • Redis 6 或更高版本(必须支持 RESP3)。
  • GCC (11, 12)、Clang (11, 13, 14) 和 Visual Studio (16 2019, 17 2022)。
  • 具备关于 RedisBoost.Asio 的基础知识。

要使用该库,必须包含

#include <boost/redis/src.hpp>

在您的应用程序中不超过一个源文件中。要使用 cmake 构建示例和测试,请运行

# Linux
$ BOOST_ROOT=/opt/boost_1_84_0 cmake -S <source-dir> -B <binary-dir>
# Windows
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake

更多详情请参见 https://github.com/boostorg/cmake

连接

以下代码使用一个短连接来 ping Redis 服务器

auto co_main(config const& cfg) -> net::awaitable<void>
{
auto conn = std::make_shared<connection>(co_await net::this_coro::executor);
conn->async_run(cfg, {}, net::consign(net::detached, conn));
// 一个仅包含 ping 命令的请求。
request req;
req.push("PING", "Hello world");
// 响应对象。
response<std::string> resp;
// 执行请求。
co_await conn->async_exec(req, resp);
conn->cancel();
std::cout << "PING: " << std::get<0>(resp).value() << std::endl;
}

async_runasync_exec 函数扮演的角色是

  • async_exec:执行请求中包含的命令,并将各个响应存储在 resp 对象中。可以从代码中的多个位置并发调用。
  • async_run:解析、连接、ssl 握手、resp3 握手、健康检查、重新连接和协调底层读取和写入操作(以及其他事项)。

服务器推送

Redis 服务器还可以向客户端发送各种推送,其中一些是

连接类通过 boost::redis::connection::async_receive 函数支持服务器推送,该函数可以在用于执行命令的同一连接中调用。下面的协程展示了如何使用它

auto
receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
request req;
req.push("SUBSCRIBE", "channel");
generic_response resp;
conn->set_receive_response(resp);
// 在重新连接启用时循环
while (conn->will_reconnect()) {
// 重新连接到频道。
co_await conn->async_exec(req, ignore);
// 循环读取 Redis 推送。
for (;;) {
error_code ec;
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
if (ec)
break; // 连接丢失,中断以便我们可以重新连接到频道。
// 以某种方式使用响应 resp,然后清除它。
...
consume_one(resp);
}
}
}

请求

Redis 请求由一个或多个命令组成(在 Redis 文档中,它们被称为 管道)。例如

// 一些示例容器。
std::list<std::string> list {...};
std::map<std::string, mystruct> map { ...};
// 请求可以包含多个命令。
request req;
// 具有可变长度参数的命令。
req.push("SET", "key", "some value", "EX", "2");
// 推送一个列表。
req.push_range("SUBSCRIBE", list);
// 与上面相同,但作为迭代器范围。
req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
// 推送一个映射。
req.push_range("HSET", "key", map);

向 Redis 发送请求是通过已经声明的 boost::redis::connection::async_exec 执行的。请求内部的 boost::redis::request::config 对象决定了 boost::redis::connection 在某些情况下如何处理请求。建议读者仔细阅读它。

响应

Boost.Redis 使用以下策略来处理 Redis 响应

例如,以下请求有三个命令

request req;
req.push("PING");
req.push("INCR", "key");
req.push("QUIT");

因此,它的响应也将包含三个元素,可以在以下响应对象中读取

response<std::string, int, std::string>

响应的行为类似于元组,并且必须具有与请求命令数相同的元素(以下例外情况)。还需要每个元组元素都能够存储对其引用的命令的响应,否则将发生错误。要忽略请求中各个命令的响应,请使用标签 boost::redis::ignore_t,例如

// 忽略第二个和最后一个响应。
response<std::string, ignore_t, std::string, ignore_t>

下表提供了某些 Redis 命令返回的 resp3 类型

命令RESP3 类型文档
lpush数字https://redis.ac.cn/commands/lpush
lrange数组https://redis.ac.cn/commands/lrange
set简单字符串、null 或 blob 字符串https://redis.ac.cn/commands/set
getBlob 字符串https://redis.ac.cn/commands/get
smembers集合https://redis.ac.cn/commands/smembers
hgetall映射https://redis.ac.cn/commands/hgetall

要将这些 RESP3 类型映射到 C++ 数据结构,请使用下表

RESP3 类型可能的 C++ 类型类型
简单字符串std::string 简单
简单错误std::string 简单
Blob 字符串std::string, std::vector简单
Blob 错误std::string, std::vector简单
数字long long, int, std::size_t, std::string简单
双精度浮点数double, std::string简单
Nullstd::optional<T> 简单
数组std::vector, std::list, std::array, std::deque聚合
映射std::vector, std::map, std::unordered_map聚合
集合std::vector, std::set, std::unordered_set聚合
推送std::vector, std::map, std::unordered_map聚合

例如,对以下请求的响应

request req;
req.push("HELLO", 3);
req.push_range("RPUSH", "key1", vec);
req.push_range("HSET", "key2", map);
req.push("LRANGE", "key3", 0, -1);
req.push("HGETALL", "key4");
req.push("QUIT");

可以在下面的响应对象中读取

response<
redis::ignore_t, // hello
int, // rpush
int, // hset
std::vector<T>, // lrange
std::map<U, V>, // hgetall
std::string // quit
> resp;

然后,要执行请求并读取响应,请使用如下所示的 async_exec

co_await conn->async_exec(req, resp);

如果目的是完全忽略响应,请使用 ignore

// 忽略响应
co_await conn->async_exec(req, ignore);

包含嵌套聚合或异构数据类型的响应将在后面的“一般情况”中进行特殊处理。截至撰写本文时,并非所有 RESP3 类型都被 Redis 服务器使用,这意味着在实践中,用户将关注 RESP3 规范的简化子集。

推送

没有响应的命令,例如

  • "SUBSCRIBE"
  • "PSUBSCRIBE"
  • "UNSUBSCRIBE"

不应包含在响应元组中。例如,以下请求

request req;
req.push("PING");
req.push("SUBSCRIBE", "channel");
req.push("QUIT");

必须在响应对象 response<std::string, std::string> 中读取。

Null

应用程序访问 Redis 服务器中不存在或已过期的键是很常见的,要处理这些用例,请使用 std::optional 包装类型,如下所示

response<
std::optional<A>,
std::optional<B>,
...
> resp;

其他一切保持不变。

事务

要读取事务的响应,我们必须首先观察到 Redis 会将事务命令排队,并将它们的各个响应作为数组的元素发送,该数组本身就是对 EXEC 命令的响应。例如,要读取对此请求的响应

req.push("MULTI");
req.push("GET", "key1");
req.push("LRANGE", "key2", 0, -1);
req.push("HGETALL", "key3");
req.push("EXEC");

使用以下响应类型

response<
ignore_t, // multi
ignore_t, // QUEUED
ignore_t, // QUEUED
ignore_t, // QUEUED
response<
std::optional<std::string>, // get
std::optional<std::vector<std::string>>, // lrange
std::optional<std::map<std::string, std::string>> // hgetall
> // exec
> resp;
ignore_t ignore
全局忽略对象。

有关完整示例,请参见 cpp20_containers.cpp

一般情况

在某些情况下,Redis 命令的响应不适合上述模型,一些示例是

  • 响应类型不固定的命令(如 set)。期望 int 并接收 blob 字符串会导致错误。
  • 包含嵌套聚合的 RESP3 聚合无法在 STL 容器中读取。
  • 具有动态命令数的事务无法在 response 中读取。

为了处理这些情况,Boost.Redis 提供了 boost::redis::resp3::node 类型抽象,它是响应中最通用形式的元素,无论是简单的 RESP3 类型还是聚合的元素。它的定义如下

template <class String>
struct basic_node {
// 此节点中数据的 RESP3 类型。
type data_type;
// 聚合元素的数量(对于简单数据,则为 1)。
std::size_t aggregate_size;
// 此节点在响应树中的深度。
std::size_t depth;
// 实际数据。对于聚合类型,这始终为空。
String value;
};

对 Redis 命令的任何响应都可以在 boost::redis::generic_response 中接收。该向量可以看作是响应树的预序视图。使用它与使用其他类型没有区别

// 接收任何 RESP3 简单或聚合数据类型。
co_await conn->async_exec(req, resp);
adapter::result< std::vector< resp3::node > > generic_response
对请求的通用响应。

例如,假设我们要使用 HGETALL 从 Redis 检索哈希数据结构,一些选项是

  • boost::redis::generic_response:始终有效。
  • std::vector<std::string>:高效且扁平,所有元素均为字符串。
  • std::map<std::string, std::string>:如果您需要将数据作为 std::map,则高效。
  • std::map<U, V>:如果您存储序列化数据,则高效。避免临时变量,并且 UV 需要 boost_redis_from_bulk

除了上述内容外,用户还可以使用容器的无序版本。相同的推理适用于集合,例如 SMEMBERS 和一般的其他数据结构。

序列化

Boost.Redis 通过以下自定义点支持用户定义类型的序列化

// 序列化。
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
// 反序列化
void boost_redis_from_bulk(mystruct& u, node_view const& node, boost::system::error_code&)

这些函数通过 ADL 访问,因此用户必须将它们导入全局命名空间。在“示例”部分,读者可以找到展示如何使用 json 和 protobuf 进行序列化的示例。

示例

以下示例展示了如何使用到目前为止讨论的功能

某些异步示例中使用的 main 函数已分解到 main.cpp 文件中。

回声服务器基准测试

本文档对我使用不同语言和不同 Redis 客户端实现的 TCP 回声服务器的性能进行了基准测试。选择回声服务器的主要动机是

  • 易于实现,并且在大多数语言中不需要专业知识。
  • I/O 绑定:回声服务器通常 CPU 消耗非常低,因此非常适合衡量程序如何处理并发请求。
  • 它在并发性方面很好地模拟了典型的后端。

我还对实现施加了一些约束

  • 它应该足够简单,不需要编写太多代码。
  • 倾向于使用标准习惯用法,避免需要专家级别的优化。
  • 避免使用连接池和线程池等复杂的东西。

要重现这些结果,请在一个终端中运行一个回声服务器程序,并在另一个终端中运行 echo-server-client

无 Redis

首先,我测试了一个纯 TCP 回声服务器,即直接将消息发送给客户端而不与 Redis 交互的服务器。结果如下所示

测试是在本地主机上使用 1000 个并发 TCP 连接执行的,在我的机器上平均延迟为 0.07 毫秒。在更高延迟的网络上,库之间的差异预计会减少。

  • 我预计 Libuv 的性能与 Asio 和 Tokio 相似。
  • 我确实预计 nodejs 会稍逊一筹,因为它毕竟是 javascript 代码。否则,我预计它的性能与 libuv 相似,因为它毕竟是它背后的框架。
  • Go 让我感到惊讶:比 nodejs 和 libuv 更快!

基准测试中使用的代码可以在以下位置找到

使用 Redis

这与上述回声服务器类似,但消息由 Redis 而不是回声服务器本身回显,回声服务器充当客户端和 Redis 服务器之间的代理。结果如下所示

测试是在平均延迟为 35 毫秒的网络上执行的,否则它使用与上一个示例相同数量的 TCP 连接。

正如读者所见,Libuv 和 Rust 测试未在图中描述,原因是

  • redis-rs:此客户端落后太多,以至于无法与其他基准测试一起表示,而不会使其他基准测试显得微不足道。我不确定为什么它如此缓慢,我猜这与它缺乏自动 管道 支持有关。事实上,我启动的 TCP 连接越多,其性能就越差。
  • Libuv:我将其排除在外,因为它需要我编写太多的 c 代码。更具体地说,我必须使用 hiredis 并手动实现对管道的支持。

基准测试中使用的代码可以在以下位置找到

结论

Redis 客户端必须支持自动管道才能具有竞争力的性能。有关本文档的更新,请关注 https://github.com/boostorg/redis

比较

我开始编写 Boost.Redis 的主要原因是为了拥有一个与 Asio 异步模型兼容的客户端。随着我的进展,我还可以解决我认为其他库中的弱点。由于时间限制,我无法与 官方 列表中列出的每个客户端进行详细比较,相反,我将重点关注 github 上星数最多的最受欢迎的 C++ 客户端,即

Boost.Redis 与 Redis-plus-plus

在我们开始之前,重要的是要提及 redis-plus-plus 不支持的一些内容

  • 最新版本的通信协议 RESP3。没有它,就不可能支持一些重要的 Redis 功能,例如客户端缓存等。
  • 协程。
  • 直接在用户数据结构中读取响应,以避免创建临时变量。
  • 带有错误代码支持的错误处理。
  • 取消。

其余要点将单独讨论。让我们首先看看发送命令、管道和事务是什么样的

auto redis = Redis("tcp://127.0.0.1:6379");
// 发送命令
redis.set("key", "val");
auto val = redis.get("key"); // val 的类型为 OptionalString。
if (val)
std::cout << *val << std::endl;
// 发送管道
auto pipe = redis.pipeline();
auto pipe_replies = pipe.set("key", "value")
.get("key")
.rename("key", "new-key")
.rpush("list", {"a", "b", "c"})
.lrange("list", 0, -1)
.exec();
// 使用回复类型和索引解析回复。
auto set_cmd_result = pipe_replies.get<bool>(0);
// ...
// 发送事务
auto tx = redis.transaction();
auto tx_replies = tx.incr("num0")
.incr("num1")
.mget({"num0", "num1"})
.exec();
auto incr_result0 = tx_replies.get<long long>(0);
// ...
@ exec
引用 connection::async_exec 操作。

此 API 的一些问题是

  • 对命令、管道和事务的异构处理。这使得自动管道化成为不可能。
  • 任何发送单个命令的 Api 都具有非常有限的可用范围,并且出于性能原因应避免使用。
  • API 将异常强加给用户,未提供错误代码重载。
  • 无法重用缓冲区进行新的调用,例如 redis.get,以避免进一步的动态内存分配。
  • 解析和连接的错误处理不明确。

根据文档,redis-plus-plus 中的管道具有以下特征

‍注意:默认情况下,创建 Pipeline 对象并不便宜,因为它会创建一个新连接。

这显然是 API 的一个缺点,因为管道应该是默认的通信方式,而不是例外,为每个管道付出如此高的代价会对性能造成严重影响。事务也遭受着非常相同的问题。

‍注意:创建 Transaction 对象并不便宜,因为它会创建一个新连接。

在 Boost.Redis 中,发送一个命令、管道或事务之间没有区别,因为请求与 IO 对象是解耦的。

‍redis-plus-plus 还支持异步接口,但是,事务和订阅者的异步支持仍在进行中。

异步接口依赖于第三方事件库,到目前为止,仅支持 libuv。

redis-plus-plus 中的异步代码如下所示

auto async_redis = AsyncRedis(opts, pool_opts);
Future<string> ping_res = async_redis.ping();
cout << ping_res.get() << endl;

正如读者所见,异步接口基于 future,这也以性能不佳而闻名。然而,这种异步设计最大的问题是,它使得正确编写异步程序成为不可能,因为它在发送的每个命令上启动一个异步操作,而不是将消息排队并在可以发送时触发写入。也不清楚如何使用这种设计来实现管道(如果可以实现的话)。

参考

高级 页面记录了所有公共类型。

致谢

感谢帮助塑造 Boost.Redis 的人员

  • Richard Hodges (madmongo1):感谢他对 Asio、异步程序设计等方面的非常有用的支持。
  • Vinícius dos Santos Oliveira (vinipsmaker):感谢关于 Boost.Redis 如何在读取操作中消耗缓冲区的有益讨论。
  • Petr Dannhofer (Eddie-cz):感谢帮助我理解 AUTHHELLO 命令如何相互影响。
  • Mohammad Nejati (ashtum):感谢指出在连接丢失时 async_exec 的调用应失败的场景。
  • Klemens Morgenstern (klemens-morgenstern):感谢关于超时、取消、同步接口以及 Asio 的一般帮助的有益讨论。
  • Vinnie Falco (vinniefalco):感谢关于如何改进代码和文档的一般性建议。
  • Bram Veldhoen (bveldhoen):感谢贡献 Redis-streams 示例。

还要非常感谢所有参与 Boost 评审的个人

评审可以在这里找到:https://lists.boost.org/Archives/boost/2023/01/date.php。来自评审管理器的 ACCEPT 线程可以在这里找到:https://lists.boost.org/Archives/boost/2023/01/253944.php

更新日志

Boost 1.88

  • (问题 233)为了处理可能不存在于 Redis 服务器中的键,该库支持 std::optional,例如 response<std::optional<std::vector<std::string>>>。但在某些情况下,例如 MGET 命令,向量中的每个元素都可能不存在,现在可以将响应指定为 response<std::optional<std::vector<std::optional<std::string>>>>
  • (问题 225)使用 deferred 作为连接默认完成令牌。
  • (问题 128)添加了一个新的 async_exec 重载,允许传递响应适配器。这使得可以直接在自定义数据结构中接收 Redis 响应,从而避免不必要的数据复制。感谢 Ruben Perez (@anarthal) 实现了此功能。
  • 此版本还有其他多项小的改进,用户可以参考 git 历史记录了解更多详情。

Boost 1.87

  • (问题 205)通过使用 wait_for_one_error 而不是 wait_for_all,提高了对断开连接的反应时间。函数 connection::async_run 也已更改为在从服务器接收到该错误时向用户返回 EOF。这是一个重大更改。
  • (问题 210)修复了空嵌套响应的适配器。
  • (问题 211212)修复了在某些条件下会挂起的重新连接循环,有关更多详细信息,请参阅链接的问题。
  • (问题 219)将默认日志级别从 disabled 更改为 debug

Boost 1.85

  • (问题 170) 在高负载和低延迟网络下,有可能在写入操作完成之前,并且在请求仍然标记为暂存且未写入时,就开始接收响应。这会扰乱将响应分类为非请求或非请求的启发式方法。
  • (问题 168)。提供了一种将自定义 SSL 上下文传递给连接的方法。此处的设计与 Boost.Beast 和 Boost.MySql 的设计不同,因为在 Boost.Redis 中,连接拥有上下文,而不仅仅是存储对用户提供的上下文的引用。这没问题,因为应用程序的整个应用程序只需要一个连接,这使得每个连接的 ssl 上下文开销可以忽略不计。
  • (问题 181)。请参阅 评论中此错误的详细描述。
  • (问题 182)。将 config::username 的默认值设置为 "default"。这使得在 Redis 中使用 requirepass 配置更加简单。
  • (问题 189)。通过对 bulk 和 aggregate 的大小使用 std::size_t 而不是 std::uint64_t 来修复窄化转换。现在的代码依赖于 std::from_chars,如果平台上 std::size_t 的大小为 32,则当接收到的值大于 32 时,返回错误。

Boost 1.84 (Boost 中的首次发布)

  • 弃用接受 response 的 async_receive 重载。用户现在应该首先调用 set_receive_response 以避免不断地和不必要地设置相同的 response。
  • 使用 std::function 来类型擦除 response 适配器。此更改不应以任何方式影响用户,但允许连接内部结构的重要简化。这导致了巨大的性能改进。
  • 连接有一个新的成员 get_usage(),它返回连接使用信息,例如写入、接收的字节数等。
  • 服务器推送的消费方面有巨大的性能改进,现在通过 asio::channel 进行通信,因此可以缓冲,从而避免阻塞套接字读取循环。批量读取也通过 channel.try_send 支持,缓冲的消息可以使用 connection::receive 同步消费。已添加函数 boost::redis::cancel_one 以简化处理同一 generic_response 中包含的多个服务器推送。重要提示:当 connection::async_receive 恢复时,这些更改可能会导致响应中出现多个推送。因此,用户在调用 resp.clear() 时必须小心:要么确保所有消息都已处理,要么只使用 consume_one

v1.4.2 (包含符合 boost 审查和更多内容的更改)

  • 添加 boost::redis::config::database_index,以便可以在启动运行命令之前选择数据库,例如在自动重新连接之后。
  • 巨大的性能提升。我的一个测试从 140k req/s 提升到 390k/s。这在解析器简化之后成为可能,解析器简化减少了重新调度和缓冲区轮换的次数。
  • 添加 Redis stream 示例。
  • 将项目重命名为 Boost.Redis,并将代码移动到命名空间 boost::redis 中。
  • 正如审查中指出的那样,to_bulkfrom_bulk 名称对于 ADL 自定义点来说太通用了。它们获得了前缀 boost_redis_
  • boost::redis::resp3::request 移动到 boost::redis::request
  • 添加新的 typedef boost::redis::response,应该使用它来代替 std::tuple
  • 添加新的 typedef boost::redis::generic_response,应该使用它来代替 std::vector<resp3::node<std::string>>
  • redis::ignore 重命名为 redis::ignore_t
  • 更改 async_exec 以接收 redis::response 而不是适配器,即,用户应该直接传递 resp 而不是传递 adapt(resp)
  • 引入 boost::redis::adapter::result 以存储对命令的响应,包括可能的 resp3 错误,而不会丢失错误诊断部分。要访问值,现在使用 std::get<N>(resp).value() 而不是 std::get<N>(resp)
  • 实现全双工通信。在这些更改之前,连接将等待响应到达后再发送下一个响应。现在,请求被持续合并并写入套接字。request::coalesce 变得不必要并被删除。我可以使用这些更改测量到显著的性能提升。
  • 改进使用 Boost.Describe 序列化为 JSON 和 protobuf 的序列化示例。有关更多详细信息,请参阅 cpp20_json.cppcpp20_protobuf.cpp
  • 升级到 Boost 1.81.0。
  • 修复了使用 libc++ 的构建。
  • 向连接类添加高级功能。例如,boost::redis::connection::async_run 将自动解析、连接、重新连接并执行健康检查。

v1.4.0-1

  • retry_on_connection_lost 重命名为 cancel_if_unresponded。(v1.4.1)
  • 删除对 Boost.Hana、boost::string_view、Boost.Variant2 和 Boost.Spirit 的依赖。
  • 修复了 windows 上的构建并设置 CI。

v1.3.0-1

  • 升级到 Boost 1.80.0
  • 删除自动发送 HELLO 命令。如果不膨胀连接类,则无法正确实现此功能。现在,用户有责任发送 HELLO。包含它的请求优先于其他请求,并将移动到队列的前面,请参阅 aedis::request::config
  • 自动名称解析和连接已从 aedis::connection::async_run 中删除。用户现在必须手动执行此步骤。此更改的原因是内置它们无法提供 boost 用户所需的足够灵活性。
  • 删除健康检查和空闲超时。此功能现在必须由用户实现,请参阅示例。这是使 Aedis 对更广泛的受众有用并适合 Boost 审查过程的一部分。
  • aedis::connection 现在使用 typedef 到 net::ip::tcp::socketaedis::ssl::connectionnet::ssl::stream<net::ip::tcp::socket>。需要使用其他流类型的用户现在必须专门化 aedis::basic_connection
  • 添加异步代码的低级示例。

v1.2.0

  • aedis::adapt 现在支持使用 std::tie 创建的元组。aedis::ignore 现在是 std::ignore 类型的别名。
  • aedis::connection 类中使用的内部队列提供分配器支持。
  • 更改 async_run 的行为,以便在收到 asio::error::eof 时完成并成功。这使得使用 awaitable 运算符编写组合操作更容易。
  • aedis::request 中添加分配器支持 (来自 Klemens Morgenstern 的贡献)。
  • aedis::request::push_range2 重命名为 push_range。后缀 2 用于消除歧义。Klemens 使用 SFINAE 修复了它。
  • fail_on_connection_lost 重命名为 aedis::request::config::cancel_on_connection_lost。现在,只有当 async_run 完成时,它才会导致连接被取消。
  • 引入 aedis::request::config::cancel_if_not_connected,如果 async_exec 在连接建立之前被调用,这将导致请求被取消。
  • 引入新的请求标志 aedis::request::config::retry,如果设置为 true,则会导致请求在发送到 Redis 后,但在 async_run 完成后仍然未响应时,不会被取消。它提供了一种避免重复执行命令的方法。
  • 删除接受请求和适配器作为参数的 aedis::connection::async_run 重载。
  • 更改 aedis::adapt()std::vector<aedis::resp3::node<T>> 一起使用的行为。接收 RESP3 简单错误、blob 错误或 null 不会导致错误,但将被视为正常响应。用户有责任检查向量中的内容。
  • 修复了 connection::cancel(operation::exec) 中的错误。现在,此调用将仅取消未写入的请求。
  • aedis::connection::async_exec 实现每个操作的隐式取消支持。以下调用将 co_await (conn.async_exec(...) || timer.async_wait(...)) 取消请求,只要它尚未写入。
  • aedis::connection::async_run 完成签名更改为 f(error_code)。过去就是这样,第二个参数没有帮助。
  • operation::receive_push 重命名为 aedis::operation::receive

v1.1.0-1

  • aedis::connection::config 中删除 coalesce_requests,它现在成为请求属性,请参阅 aedis::request::config::coalesce
  • aedis::connection::config 中删除 max_read_size。最大读取大小现在可以指定为 aedis::adapt() 函数的参数。
  • 删除 aedis::sync 类,有关如何执行同步和线程安全调用的信息,请参阅 intro_sync.cpp。这在 Boost. 1.80 中是可能的,因为它需要 boost::asio::deferred
  • boost::optional 移动到 std::optional。这是迁移到 C++17 的一部分。
  • 更改第二个 aedis::connection::async_run 重载的行为,以便在连接丢失时始终返回错误。
  • 添加 TLS 支持,请参阅 intro_tls.cpp。
  • 添加一个示例,展示如何通过 sentinels 解析地址,请参阅 subscriber_sentinel.cpp。
  • 添加 aedis::connection::timeouts::resp3_handshake_timeout。这是用于发送 HELLO 命令的超时。
  • 添加 aedis::endpoint,除了主机和端口之外,用户还可以选择性地提供用户名、密码和预期的服务器角色 (请参阅 aedis::error::unexpected_server_role)。
  • aedis::connection::async_run 检查 hello 命令中接收到的服务器角色是否等于 aedis::endpoint 中指定的预期服务器角色。要跳过此检查,请将 role 变量留空。
  • aedis::connection 中删除重新连接功能。在简单的重新连接策略中这是可能的,但在更复杂的场景中会使类膨胀,例如,使用 sentinel、身份验证和 TLS。这在单独的协程中实现起来很简单。因此,enum eventasync_receive_event 也已从类中删除。
  • 修复了 connection::async_receive_push 中的错误,该错误阻止传递除 adapt(std::vector<node>) 之外的任何响应适配器。
  • 更改了 aedis::adapt() 的行为,该行为导致 RESP3 错误被忽略。其结果之一是,在需要身份验证的服务器中,connection::async_run 不会失败退出。
  • 更改了 connection::async_run 的行为,该行为会导致在 connection::async_exec 中发生错误时完成并成功。
  • 将构建系统从 autotools 移植到 CMake。

v1.0.0

  • 为 windows 用户添加实验性 cmake 支持。
  • 添加新类 aedis::sync,它在线程安全和同步 API 中包装 aedis::connectionsync.hpp 中的所有自由函数现在都是 aedis::sync 的成员函数。
  • aedis::connection::async_receive_event 拆分为两个函数,一个用于接收事件,另一个用于服务器端推送,请参阅 aedis::connection::async_receive_push
  • 消除 aedis::adapter::adaptaedis::adapt 之间的冲突。
  • 添加 connection::operation 枚举以替换 cancel_* 成员函数,使用单个 cancel 函数,该函数获取应取消的操作作为参数。
  • Bugfix: 从 connection 对象具有未发送命令的状态重新连接时出现的错误。在某些情况下,可能导致 async_exec 永远无法完成。
  • Bugfix: Doxygen 中缺少 adapt() 函数的文档。

v0.3.0

  • 添加 experimental::execreceive_event 函数,以提供跨线程执行请求的线程安全和同步方式。有关示例,请参阅 intro_sync.cppsubscriber_sync.cpp
  • connection::async_read_push 已重命名为 async_receive_event
  • connection::async_receive_event 现在用于向用户传达内部事件,例如 resolve、connect、push 等。有关示例,请参阅 cpp20_subscriber.cppconnection::event
  • aedis 目录已移动到 include 以使其看起来更像 Boost 库。用户现在应该在编译器标志中将 -I/aedis-path 替换为 -I/aedis-path/include
  • AUTHHELLO 命令现在自动发送。此更改对于实现重新连接是必要的。AUTH 中使用的用户名和密码应由用户在 connection::config 上提供。
  • 添加对重新连接的支持。请参阅 connection::enable_reconnect
  • 修复了 connection::async_run(host, port) 重载中的错误,该错误导致重新连接时崩溃。
  • 修复了连接类中的执行器用法。在这些更改之前,它对用户施加了 any_io_executor
  • connection::async_run 退出时,connection::async_receiver_event 不再被取消。此更改使用户代码更简单。
  • 带有主机和端口重载的 connection::async_exec 已被删除。请使用其他 connection::async_run 重载。
  • connection::async_run 中的主机和端口参数已移动到 connection::config,以更好地支持身份验证和故障转移。
  • chat_room 示例中的许多简化。
  • 修复了 clang 编译器中的构建,并对文档进行了一些改进。

v0.2.0-1

  • 修复了在高负载下发生的错误。(v0.2.1)
  • 对高级 API 进行重大重写。不再需要使用低级 API。
  • 不再有回调:发送请求遵循 ASIO 异步模型。
  • 支持重新连接:当连接丢失时,挂起的请求不会被取消,并在建立新连接时重新发送。
  • 该库不再代表用户发送 HELLO-3。这对于正确支持 AUTH 非常重要。

v0.1.0-2

  • echo_server 示例中添加重新连接协程。(v0.1.2)
  • 使用 make_parallel_group 修正 client::async_wait_for_data 以启动操作。(v0.1.2)
  • 文档中的改进。(v0.1.2)
  • 避免在重新连接后在客户端类中进行动态内存分配。(v0.1.2)
  • 改进了文档并为高级客户端添加了一些功能。(v.0.1.1)
  • 设计和文档方面的改进。

v0.0.1

  • 首次发布以收集设计反馈。