redis与连接
Redis处理命令
connection主要方法及与reply关系
connection只支持移动语义,不支持拷贝和赋值
recv使用ReplyUPtr,即unique_ptr<redisReply, ReplyDeleter>,其中ReplyDeleter定义如下
struct ReplyDeleter {
void operator()(redisReply *reply) const {
if (reply != nullptr) {
freeReplyObject(reply);
}
}
};
其调用redisGetReply获取响应,当中会处理以下几种异常情况
- redisGetReply返回不是REDIS_OK,抛出异常
- 调用broken判断连接是否断了
- 在handle_error_reply标识为true,并且响应的type值为REDIS_REPLY_ERROR,抛出异常
send方法主要是调用redisAppendCommandArgv,会处理下面情况
- redisAppendCommandArgv如果失败,会抛出异常
- 调用broken()看连接是否断了
bool broken() const noexcept {
return !_ctx || _ctx->err != REDIS_OK;
}
连接池及其管理
SafeConnection:包含连接池的引用,构造函数负责从连接池中得到可用的连接,析构函数中将连接还到连接池中
GuardedConnection:使用ConnectionPoolSPtr _pool指向连接池的指针
解析响应
通过模板函数来作具体类型的的解析转发
template <typename T>
inline T parse(redisReply &reply) {
return parse(ParseTag<T>(), reply);
}
optional解析
template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
if (reply::is_nil(reply)) {
// Because of a GCC bug, we cannot return {} for -std=c++17
// Refer to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86465
#if defined REDIS_PLUS_PLUS_HAS_OPTIONAL
return std::nullopt;
#else
return {};
#endif
}
return Optional<T>(parse<T>(reply));
}
字符串解析
std::string parse(ParseTag<std::string>, redisReply &reply) {
#ifdef REDIS_PLUS_PLUS_RESP_VERSION_3
if (!reply::is_string(reply) && !reply::is_status(reply)
&& !reply::is_verb(reply) && !reply::is_bignum(reply)) {
throw ParseError("STRING or STATUS or VERB or BIGNUM", reply);
}
#else
if (!reply::is_string(reply) && !reply::is_status(reply)) {
throw ParseError("STRING or STATUS", reply);
}
#endif
if (reply.str == nullptr) {
throw ProtoError("A null string reply");
}
// Old version hiredis' *redisReply::len* is of type int.
// So we CANNOT have something like: *return {reply.str, reply.len}*.
return std::string(reply.str, reply.len);
}
设计点
- 在redis层使用函数作为模板类型参数,也就是command层定义的函数。
template <typename Cmd, typename ...Args>
auto Redis::command(Cmd cmd, Args &&...args)
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type {
if (_connection) {
// Single Connection Mode.
// TODO: In this case, should we reconnect?
auto &connection = _connection->connection();
if (connection.broken()) {
throw Error("Connection is broken");
}
return _command(connection, cmd, std::forward<Args>(args)...);
} else {
assert(_pool);
// Pool Mode, i.e. get connection from pool.
SafeConnection connection(*_pool);
return _command(connection.connection(), cmd, std::forward<Args>(args)...);
}
}
template <typename ...Args>
auto Redis::command(const StringView &cmd_name, Args &&...args)
-> typename std::enable_if<!IsIter<typename LastType<Args...>::type>::value, ReplyUPtr>::type {
auto cmd = [](Connection &connection, const StringView &name, Args &&...params) {
CmdArgs cmd_args;
cmd_args.append(name, std::forward<Args>(params)...);
connection.send(cmd_args);
};
return command(cmd, cmd_name, std::forward<Args>(args)...);
}
template <typename Input>
auto Redis::command(Input first, Input last)
-> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type {
range_check("command", first, last);
auto cmd = [](Connection &connection, Input start, Input stop) {
CmdArgs cmd_args;
while (start != stop) {
cmd_args.append(*start);
++start;
}
connection.send(cmd_args);
};
return command(cmd, first, last);
}
- 使用可变参数模板
template <typename Cmd, typename ...Args>
ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args);
//command系列
template <typename Cmd, typename ...Args>
auto command(Cmd cmd, Args &&...args)
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;
- 在redis层最终会调用不带连接的command方法,调用带连接参数的_command方法(调用command层具体的函数,然后接收响应)
template <typename Cmd, typename ...Args>
ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) {
assert(!connection.broken());
cmd(connection, std::forward<Args>(args)...);
auto reply = connection.recv();
return reply;
}