【brpc学习实践十三】基于brpc的redis client的实现

2023-12-05 08:45

本文主要是介绍【brpc学习实践十三】基于brpc的redis client的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

brpc支持了redis协议,提供了相关redis访问接口,充分利用了bthread,可以坐到比hiredis更高效。

brpc redis与hiredis的对比

相比使用hiredis(官方client)的优势有:

  • 线程安全。用户不需要为每个线程建立独立的client。
  • 支持同步、异步、批量同步、批量异步等访问方式,能使用ParallelChannel等组合访问方式。
  • 支持多种连接方式。支持超时、backup request、取消、tracing、内置服务等一系列RPC基本福利。
  • 一个进程中的所有brpc client和一个redis-server只有一个连接。多个线程同时访问一个redis-server时更高效(见性能)。无论reply的组成多复杂,内存都会连续成块地分配,并支持短串优化(SSO)进一步提高性能。

像http一样,brpc保证在最差情况下解析redis reply的时间复杂度也是O(N),N是reply的字节数,而不是O( N 2 N^2 N2)。当reply是个较大的数组时,这是比较重要的。

加上-redis_verbose后会打印出所有的redis request和response供调试。

访问单台redis

创建一个访问redis的Channel:

#include <brpc/redis.h>
#include <brpc/channel.h>brpc::ChannelOptions options;
options.protocol = brpc::PROTOCOL_REDIS;
brpc::Channel redis_channel;
if (redis_channel.Init("0.0.0.0:6379", &options) != 0) {  // 6379是redis-server的默认端口LOG(ERROR) << "Fail to init channel to redis-server";return -1;
}
...

执行SET后再INCR:

std::string my_key = "my_key_1";
int my_number = 1;
...
// 执行"SET <my_key> <my_number>"
brpc::RedisRequest set_request;
brpc::RedisResponse response;
brpc::Controller cntl;
set_request.AddCommand("SET %s %d", my_key.c_str(), my_number);
redis_channel.CallMethod(NULL, &cntl, &set_request, &response, NULL/*done*/);
if (cntl.Failed()) {LOG(ERROR) << "Fail to access redis-server";return -1;
}
// 可以通过response.reply(i)访问某个reply
if (response.reply(0).is_error()) {LOG(ERROR) << "Fail to set";return -1;
}
// 可用多种方式打印reply
LOG(INFO) << response.reply(0).c_str()  // OK<< response.reply(0)          // OK<< response;                  // OK
...

执行INCR <my_key>

brpc::RedisRequest incr_request;
incr_request.AddCommand("INCR %s", my_key.c_str());
response.Clear();
cntl.Reset();
redis_channel.CallMethod(NULL, &cntl, &incr_request, &response, NULL/*done*/);
if (cntl.Failed()) {LOG(ERROR) << "Fail to access redis-server";return -1;
}
if (response.reply(0).is_error()) {LOG(ERROR) << "Fail to incr";return -1;
}
// 可用多种方式打印结果
LOG(INFO) << response.reply(0).integer()  // 2<< response.reply(0)            // (integer) 2<< response;                    // (integer) 2

批量执行incr或decr

brpc::RedisRequest request;
brpc::RedisResponse response;
brpc::Controller cntl;
request.AddCommand("INCR counter1");
request.AddCommand("DECR counter1");
request.AddCommand("INCRBY counter1 10");
request.AddCommand("DECRBY counter1 20");
redis_channel.CallMethod(NULL, &cntl, &request, &response, NULL/*done*/);
if (cntl.Failed()) {LOG(ERROR) << "Fail to access redis-server";return -1;
}
CHECK_EQ(4, response.reply_size());
for (int i = 0; i < 4; ++i) {CHECK(response.reply(i).is_integer());CHECK_EQ(brpc::REDIS_REPLY_INTEGER, response.reply(i).type());
}
CHECK_EQ(1, response.reply(0).integer());
CHECK_EQ(0, response.reply(1).integer());
CHECK_EQ(10, response.reply(2).integer());
CHECK_EQ(-10, response.reply(3).integer());

RedisRequest

一个RedisRequest可包含多个Command,调用AddCommand*增加命令,成功返回true,失败返回false并会打印调用处的栈

bool AddCommand(const char* fmt, ...);
bool AddCommandV(const char* fmt, va_list args);
bool AddCommandByComponents(const butil::StringPiece* components, size_t n);

格式和hiredis基本兼容:即%b对应二进制数据(指针+length),其他和printf的参数类似。对一些细节做了改进:当某个字段包含空格时,使用单引号或双引号包围起来会被视作一个字段。比如AddCommand(“Set ‘a key with space’ ‘a value with space as well’”)中的key是a key with space,value是a value with space as well。在hiredis中必须写成redisvCommand(…, “SET %s %s”, “a key with space”, “a value with space as well”);

AddCommandByComponents类似hiredis中的redisCommandArgv,用户通过数组指定命令中的每一个部分。这个方法对AddCommand和AddCommandV可能发生的转义问题免疫,且效率最高。如果你在使用AddCommand和AddCommandV时出现了“Unmatched quote”,“无效格式”等问题且无法定位,可以试下这个方法。

如果AddCommand*失败,后续的AddCommand*和CallMethod都会失败。一般来说不用判AddCommand*的结果,失败后自然会通过RPC失败体现出来。

command_size()可获得(成功)加入的命令个数。

调用Clear()后可重用RedisRequest

RedisResponse

RedisResponse可能包含一个或多个RedisReply,reply_size()可获得reply的个数,reply(i)可获得第i个reply的引用(从0计数)。注意在hiredis中,如果请求包含了N个command,获取结果也要调用N次redisGetReply。但在brpc中这是不必要的,RedisResponse已经包含了N个reply,通过reply(i)获取就行了。只要RPC成功,response.reply_size()应与request.command_size()相等,除非redis-server有bug,redis-server工作的基本前提就是reply和command按序一一对应。

每个reply可能是:

  • REDIS_REPLY_NIL:redis中的NULL,代表值不存在。可通过is_nil()判定。
  • REDIS_REPLY_STATUS:在redis文档中称为Simple String。一般是操作的返回状态,比如SET返回的OK。可通过is_string()判定(和string相同),c_str()或data()获得值。
  • REDIS_REPLY_STRING:在redis文档中称为Bulk String。大多数值都是这个类型,包括incr返回的。可通过is_string()判定,c_str()或data()获得值。
  • REDIS_REPLY_ERROR:操作出错时的返回值,包含一段错误信息。可通过is_error()判定,error_message()获得错误信息。
  • REDIS_REPLY_INTEGER:一个64位有符号数。可通过is_integer()判定,integer()获得值。
  • REDIS_REPLY_ARRAY:另一些reply的数组。可通过is_array()判定,size()获得数组大小,[i]获得对应的子reply引用。

如果response包含三个reply,分别是integer,string和一个长度为2的array。那么可以分别这么获得值:response.reply(0).integer(),response.reply(1).c_str(), repsonse.reply(2)[0]和repsonse.reply(2)[1]。如果类型对不上,调用处的栈会被打印出来,并返回一个undefined的值。

response中的所有reply的ownership属于response。当response析构时,reply也析构了。

调用Clear()后RedisResponse可以重用。

访问redis集群

建立一个使用一致性哈希负载均衡算法(c_md5或c_murmurhash)的channel就能访问挂载在对应命名服务下的redis集群了。注意每个RedisRequest应只包含一个操作或确保所有的操作是同一个key。如果request包含了多个操作,在当前实现下这些操作总会送向同一个server,假如对应的key分布在多个server上,那么结果就不对了,这个情况下你必须把一个request分开为多个,每个包含一个操作。

或者你可以沿用常见的twemproxy方案。这个方案虽然需要额外部署proxy,还增加了延时,但client端仍可以像访问单点一样的访问它。

查看发出的请求和收到的回复

打开-redis_verbose即看到所有的redis request和response,注意这应该只用于线下调试,而不是线上程序。

打开-redis_verbose_crlf2space可让打印内容中的CRLF (\r\n)变为空格,方便阅读。

NameValueDescriptionDefined At
redis_verbosefalse[DEBUG] Print EVERY redis request/responsesrc/brpc/policy/redis_protocol.cpp
redis_verbose_crlf2spacefalse[DEBUG] Show \r\n as a spacesrc/brpc/redis.cpp

另外,brpc还提供了一个类似与redis-cli的访问工具

Command Line Interface

example/redis_c++/redis_cli是一个类似于官方CLI的命令行工具,以展示brpc对redis协议的处理能力。当使用brpc访问redis-server出现不符合预期的行为时,也可以使用这个CLI进行交互式的调试。

和官方CLI类似,redis_cli <command>也可以直接运行命令,-server参数可以指定redis-server的地址。

$ ./redis_cli __          _     __/ /_  ____ _(_)___/ /_  __      _________  _____/ __ \/ __ `/ / __  / / / /_____/ ___/ __ \/ ___// /_/ / /_/ / / /_/ / /_/ /_____/ /  / /_/ / /__  /_.___/\__,_/_/\__,_/\__,_/     /_/  / .___/\___/  /_/            
This command-line tool mimics the look-n-feel of official redis-cli, as a 
demostration of brpc's capability of talking to redis server. The 
o

这篇关于【brpc学习实践十三】基于brpc的redis client的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/456896

相关文章

全面掌握 SQL 中的 DATEDIFF函数及用法最佳实践

《全面掌握SQL中的DATEDIFF函数及用法最佳实践》本文解析DATEDIFF在不同数据库中的差异,强调其边界计算原理,探讨应用场景及陷阱,推荐根据需求选择TIMESTAMPDIFF或inte... 目录1. 核心概念:DATEDIFF 究竟在计算什么?2. 主流数据库中的 DATEDIFF 实现2.1

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert