Redis官方库hiredis封装
hiredis封装
hiredis基本用法
创建连接
redisContext *redisConnect(const char *ip, int port);
, 提供了最简单的创建方式, 返回一个redisContext指针。context中比较常用的是 err错误码、errstr错误信息、connect_timeout、 command_timeout两个超时时间。
typedef struct redisContext {
const redisContextFuncs *funcs; /* Function table */
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
redisFD fd;
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type;
struct timeval *connect_timeout;
struct timeval *command_timeout;
struct {
char *host;
char *source_addr;
int port;
} tcp;
struct {
char *path;
} unix_sock;
/* For non-blocking connect */
struct sockaddr *saddr;
size_t addrlen;
/* Optional data and corresponding destructor users can use to provide
* context to a given redisContext. Not used by hiredis. */
void *privdata;
void (*free_privdata)(void *);
/* Internal context pointer presently used by hiredis to manage
* SSL connections. */
void *privctx;
/* An optional RESP3 PUSH handler */
redisPushFn *push_cb;
} redisContext;
执行命令
void *redisCommand(redisContext *c, const char *format, ...);
,第一个参数是创建的context,后面的参数则是redis命令,返回的是redisReply指针。其中type表示的是返回的数据类型。
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
REDIS_REPLY_VERB, REDIS_REPLY_DOUBLE (in additional to dval),
and REDIS_REPLY_BIGNUM. */
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
封装后的类需要提供哪些功能
- 提供创建client,执行命令,解析回包的基本功能
- hiredis是一个C库,很多函数返回的是一个指针,需要手动进行释放,封装后不希望用户去主动释放
封装
封装redisReply
- 对redisReply RAII
- 提供一些便利的函数,能够对回包类型、是否成功进行判断,便于访问数据
enum Status {
kLoseConnection = 0, // 连接中断
kClientException, // 创建的客户端异常
kOk,
};
struct RedisReply {
virtual ~RedisReply() {
if (reply != nullptr) {
freeReplyObject(reply);
}
}
Status status= Status::kClientException;;
redisReply* reply = nullptr;
//
bool OK() const {
return status == Status::kOk && reply->type != REDIS_REPLY_ERROR;
}
std::string Message() const { return reply->str; }
// 数据包类型判断
bool IsInt() const { return reply->type == REDIS_REPLY_INTEGER; }
long long GetInt() const { return reply->integer; }
bool IsDouble() const { return reply->type == REDIS_REPLY_DOUBLE; }
double GetDouble() const { return reply->dval; }
bool IsString() const {
return reply->type == REDIS_REPLY_STRING ||
reply->type == REDIS_REPLY_STATUS;
}
std::string GetString() const { return reply->str; }
bool IsArray() const { return reply->type == REDIS_REPLY_ARRAY; }
size_t Size() const {
if (IsString())
return reply->len;
return reply->elements;
}
redisReply &operator[](size_t index) { return *(reply->element[index]); }
const redisReply &at(size_t index) const { return *(reply->element[index]); }
bool IsNil() const { return reply->type == REDIS_REPLY_NIL; }
};
封装redisContext
constexpr int kDefaultCommandTimeoutMs = 300;
constexpr int kDefaultConnectTimeoutS = 10;
constexpr size_t kDefaultPoolItems = 10;
class HiRedisClient {
public:
HiRedisClient(const std::string &ip, int port) {
client = redisConnect(ip.c_str(), port);
if (client != nullptr) {
client->command_timeout = (timeval *)hi_malloc(sizeof(timeval));
client->connect_timeout = (timeval *)hi_malloc(sizeof(timeval));
client->command_timeout->tv_sec = kDefaultConnectTimeoutS;
client->command_timeout->tv_usec = 0;
}
}
virtual ~HiRedisClient() {
if (client != nullptr) {
redisFree(client);
}
}
bool Health() { return client != nullptr && client->err == 0; }
RedisReply ExecCommand(const std::string &command) {
RedisReply reply;
auto *rsp = (redisReply *)redisCommand(client, command.c_str());
if (rsp == nullptr) {
// 断连接
redisReconnect(client);
reply.status = Status::kLoseConnection;
return reply;
}
reply.status = Status::kOk;
reply.reply = rsp;
return reply;
}
void SetCommandTimeOutMs(int timeout) {
client->command_timeout->tv_sec = 0;
client->command_timeout->tv_usec = timeout * 1000;
}
void SetConnectTimeOutMs(int timeout) {
int seconds = timeout / 1000;
int ms = timeout % 1000;
client->command_timeout->tv_sec = seconds;
client->command_timeout->tv_usec = ms * 1000;
}
private:
redisContext *client = nullptr; // redis context
};
封装一个简单的连接池
redisContext是非线程安全的,多数情况下需要我们自己封装一个连接池,对连接进行复用。
对于连接池往往存在Get, Put两个操作,为了避免用户忘记Put, 在获取一个连接时,对shared_ptr的析构进行指明,解决了这一问题。然后就是一个简单的锁+队列的多线程数据管理。
class HiRedisPool {
public:
static HiRedisPool* GetInstance(){
static HiRedisPool instance;
return &instance;
}
void Init(const std::string &ip, int port,
size_t max_connection = kDefaultPoolItems) {
ip_ = ip;
port_ = port;
max_connection_ = max_connection;
}
using RedisClientPtr = std::shared_ptr<HiRedisClient>;
bool GetRedisClient(RedisClientPtr &client_ptr) {
std::unique_lock<std::mutex> lock(list_mutex_);
if (!client_list_.empty()) {
client_ptr = client_list_.front();
client_list_.pop_front();
} else if (used < max_connection_) {
++used;
client_ptr = std::shared_ptr<HiRedisClient>(
new HiRedisClient(ip_, port_),
std::bind(&HiRedisPool::ReturnClient, this, std::placeholders::_1));
}
return client_ptr != nullptr;
}
void ReturnClient(HiRedisClient *client_ptr) {
std::unique_lock<std::mutex> lock(list_mutex_);
client_list_.push_back(std::shared_ptr<HiRedisClient>(client_ptr, std::bind(&HiRedisPool::ReturnClient, this, std::placeholders::_1)));
}
size_t Size(){return client_list_.size();}
size_t Used(){return used;}
private:
HiRedisPool() = default;
HiRedisPool(const HiRedisPool&) = delete;
HiRedisPool& operator=(const HiRedisPool&) = delete;
HiRedisPool(HiRedisPool&&) = delete;
HiRedisPool& operator=(HiRedisPool&&) = delete;
size_t max_connection_ = kDefaultPoolItems;
std::string ip_;
int port_;
size_t used = 0;
std::list<RedisClientPtr> client_list_;
std::mutex list_mutex_;
};
使用示例
上述封装包含的头文件如下:
#include <hiredis.h>
#include <memory>
#include <atomic>
#include <list>
#include <mutex>
#include <functional>
HiRedisPool* pool = HiRedisPool::GetInstance();
void PING_TEST() {
HiRedisPool::RedisClientPtr client;
if (!pool->GetRedisClient(client)) {
printf("[%d] get client failed", std::this_thread::get_id());
return;
}
auto reply = client->ExecCommand("PING");
if (reply.IsString()) {
printf("[%d] %d,%s\n", std::this_thread::get_id(), reply.status, reply.GetString().c_str());
}
printf("[%d] pool size:%d", std::this_thread::get_id(), pool->Size());
}
int main(int argc, char **argv) {
pool->Init("127.0.0.1", 6380);
std::list<std::thread> workers;
for(size_t index = 0; index < 20; ++index){
workers.push_back(std::thread(PING_TEST));
}
for(auto& worker : workers){
worker.join();
}
return 0;
}