brpc支持了redis协议,提供了相关redis访问接口,充分利用了bthread,可以坐到比hiredis更高效。
相比使用hiredis(官方client)的优势有:
像http一样,brpc保证在最差情况下解析redis reply的时间复杂度也是O(N),N是reply的字节数,而不是O( N 2 N^2 N2)。当reply是个较大的数组时,这是比较重要的。
加上-redis_verbose后会打印出所有的redis request和response供调试。
创建一个访问redis的Channel:
#include
#include
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;
}
...
std::string my_key = "my_key_1";
int my_number = 1;
...
// 执行"SET "
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
...
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
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可包含多个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可能包含一个或多个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可能是:
如果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可以重用。
建立一个使用一致性哈希负载均衡算法(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)变为空格,方便阅读。
Name | Value | Description | Defined At |
---|---|---|---|
redis_verbose | false | [DEBUG] Print EVERY redis request/response | src/brpc/policy/redis_protocol.cpp |
redis_verbose_crlf2space | false | [DEBUG] Show \r\n as a space | src/brpc/redis.cpp |
另外,brpc还提供了一个类似与redis-cli的访问工具
example/redis_c++/redis_cli是一个类似于官方CLI的命令行工具,以展示brpc对redis协议的处理能力。当使用brpc访问redis-server出现不符合预期的行为时,也可以使用这个CLI进行交互式的调试。
和官方CLI类似,redis_cli
也可以直接运行命令,-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