HiRedis库封装

Redis官方库hiredis封装

hiredis基本用法

hiredis github

创建连接

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;

封装后的类需要提供哪些功能

  1. 提供创建client,执行命令,解析回包的基本功能
  2. hiredis是一个C库,很多函数返回的是一个指针,需要手动进行释放,封装后不希望用户去主动释放

封装

封装redisReply

  1. 对redisReply RAII
  2. 提供一些便利的函数,能够对回包类型、是否成功进行判断,便于访问数据
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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值