Redis
Redis(REmote DIctionary Server)是一個高性能的key-value數據庫。
Redis是一個開源的使用ANSI C語言編寫、遵守BSD協議、支持網絡、可基於內存亦可持久化的日志型、Key-Value數據庫,並提供多種語言的API。它通常被稱為數據結構服務器,因為值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等類型。
Redis的使用這里不做說明,請參考如下網址:
http://redisdoc.com/
http://www.runoob.com/redis/redis-tutorial.html
Hiredis
Hiredis是Redis數據庫的一個極簡C客戶端庫,只是對Redis協議的最小支持。
源碼地址:https://github.com/redis/hiredis
1. 同步接口
(1)建立連接:與Redis server建立連接,返回一個redisContext結構指針
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
(2)發送命令:向Redis server發送命令;成功:返回redisReply結構指針,失敗:返回NULL
// 同步執行redis命令,和printf()用法類似 void *redisCommand(redisContext *c, const char *format, ...); // argc:argv數組元素個數;argv:參數數組(指針數組);argvlen:數組首地址,每個元素是argv數組中相應參數的長度。 // 傳入命令是字符串形式時,argvlen可以指定為NULL,這個時候使用strlen()計算argv中每個字符串長度;傳入命令是二進制形式時,argvlen必須指定,用於指示argv中每個元素的長度 void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
(3)釋放redisReply結構指針
void freeReplyObject(void *reply);
(4)釋放redisContext結構指針
void redisFree(redisContext *c);
2. 主要數據結構
(1)redisContext:管理連接上下文的結構體,由redisConnect()函數創建並返回。
/* Context for a connection to Redis */ typedef struct redisContext { int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ int fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ enum redisConnectionType connection_type; struct timeval *timeout; struct { char *host; char *source_addr; int port; } tcp; struct { char *path; } unix_sock; /* For non-blocking connect */ struct sockadr *saddr; size_t addrlen; } redisContext;
(2)redisReply:響應結構體,由redisCommand()函數創建並返回。
/* This is the reply object returned by redisCommand() */ typedef struct redisReply { int type; /* REDIS_REPLY_* */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ size_t len; /* Length of string */ char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ } redisReply;
redisReply結構體中的type字段有如下值:
#define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_INTEGER 3 #define REDIS_REPLY_NIL 4 #define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_ERROR 6
3. 示例代碼
1. Hiredis源碼中提供的一個例子:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <hiredis.h> int main(int argc, char **argv) { unsigned int j, isunix = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; if (argc > 2) { if (*argv[2] == 'u' || *argv[2] == 'U') { isunix = 1; /* in this case, host is the path to the unix socket */ printf("Will connect to unix socket @%s\n", hostname); } } int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds if (isunix) { c = redisConnectUnixWithTimeout(hostname, timeout); } else { c = redisConnectWithTimeout(hostname, port, timeout); } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); redisFree(c); } else { printf("Connection error: can't allocate redis context\n"); } exit(1); } /* PING server */ reply = redisCommand(c,"PING"); printf("PING: %s\n", reply->str); freeReplyObject(reply); /* Set a key */ reply = redisCommand(c,"SET %s %s", "foo", "hello world"); printf("SET: %s\n", reply->str); freeReplyObject(reply); /* Set a key using binary safe API */ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); printf("SET (binary API): %s\n", reply->str); freeReplyObject(reply); /* Try a GET and two INCR */ reply = redisCommand(c,"GET foo"); printf("GET foo: %s\n", reply->str); freeReplyObject(reply); reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* again ... */ reply = redisCommand(c,"INCR counter"); printf("INCR counter: %lld\n", reply->integer); freeReplyObject(reply); /* Create a list of numbers, from 0 to 9 */ reply = redisCommand(c,"DEL mylist"); freeReplyObject(reply); for (j = 0; j < 10; j++) { char buf[64]; snprintf(buf,64,"%u",j); reply = redisCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } /* Let's check what we have inside the list */ reply = redisCommand(c,"LRANGE mylist 0 -1"); if (reply->type == REDIS_REPLY_ARRAY) { for (j = 0; j < reply->elements; j++) { printf("%u) %s\n", j, reply->element[j]->str); } } freeReplyObject(reply); /* Disconnects and frees the context */ redisFree(c); return 0; }
2. 一個C++封裝的示例

//RedisClient.h #pragma once #include <string> #include <queue> #include <vector> #include <hiredis/hiredis.h> #include <mutex> #include <boost/shared_ptr.hpp> class RedisClient { public: RedisClient(std::string ip, int port, int timeout = 2000); virtual ~RedisClient(); bool set(const std::string &key, const std::string& value); bool get(const std::string &key, std::string& value); bool rpush(const std::string &key, const std::string& value); bool lpop(const std::string &key, std::string& value); bool hget(const std::string &key, const std::string& field, std::string& value); bool hget(const std::string &key, int field, std::string& value); bool lrange(const std::string& key, int start, int end, std::vector<std::string>& values); bool zrange(const std::string& key, int start, int end, std::vector<std::string>& values); private: int m_timeout; int m_serverPort; std::string m_setverIp; std::mutex m_mutex; std::queue<redisContext *> m_clients; time_t m_beginInvalidTime; static const int m_maxReconnectInterval = 3; redisContext* CreateContext(); void ReleaseContext(redisContext *ctx, bool active); bool CheckStatus(redisContext *ctx); }; typedef boost::shared_ptr<RedisClient> RedisClientPtr; typedef boost::shared_ptr<const RedisClient> const_RedisClientPtr; //RedisClient.cpp #include "RedisClient.h" #include <memory> #include <string.h> #include <iostream> #include <vector> using namespace std; RedisClient::RedisClient(string ip, int port, int timeout) { m_timeout = timeout; m_serverPort = port; m_setverIp = ip; m_beginInvalidTime = 0; } RedisClient::~RedisClient() { lock_guard<mutex> lock(m_mutex); while(!m_clients.empty()) { redisContext *ctx = m_clients.front(); redisFree(ctx); m_clients.pop(); } } bool RedisClient::get(const string& key, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "GET %s", key.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::set(const string& key, const string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "SET %s %s", key.c_str(), value.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if( !(r->type == REDIS_REPLY_STATUS && strcasecmp(r->str,"OK")==0)) { return false; } return true; } bool RedisClient::rpush(const string& key, const string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "RPUSH %s %s", key.c_str(), value.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if(r->type != REDIS_REPLY_INTEGER) { return false; } return true; } bool RedisClient::lpop(const string& key, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "LPOP %s", key.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::lrange(const std::string& key, int start, int end, vector<string>& values) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "LRANGE %s %d %d", key.c_str(), start, end); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_ARRAY) { return false; } for (int i = 0; i < (int)r->elements; i++) { redisReply *cur = r->element[i]; if (cur->type != REDIS_REPLY_STRING) { return false; } values.push_back(cur->str); } return true; } redisContext* RedisClient::CreateContext() { { lock_guard<mutex> lock(m_mutex); if(!m_clients.empty()) { redisContext *ctx = m_clients.front(); m_clients.pop(); return ctx; } } time_t now = time(NULL); if(now < m_beginInvalidTime + m_maxReconnectInterval) return NULL; struct timeval tv; tv.tv_sec = m_timeout / 1000; tv.tv_usec = (m_timeout % 1000) * 1000;; redisContext *ctx = redisConnectWithTimeout(m_setverIp.c_str(), m_serverPort, tv); if(ctx == NULL || ctx->err != 0) { if(ctx != NULL) redisFree(ctx); m_beginInvalidTime = time(NULL); return NULL; } return ctx; } void RedisClient::ReleaseContext(redisContext *ctx, bool active) { if(ctx == NULL) return; if(!active) {redisFree(ctx); return;} lock_guard<mutex> lock(m_mutex); m_clients.push(ctx); } bool RedisClient::CheckStatus(redisContext *ctx) { redisReply *reply = (redisReply*)redisCommand(ctx, "ping"); if(reply == NULL) return false; shared_ptr<redisReply> autoFree(reply, freeReplyObject); if(reply->type != REDIS_REPLY_STATUS) return false; if(strcasecmp(reply->str,"PONG") != 0) return false; return true; } bool RedisClient::hget(const string& key, int field, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "HGET %s %d", key.c_str(), field); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::hget(const string& key, const string& field, string& value) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "HGET %s %s", key.c_str(), field.c_str()); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_STRING) { return false; } value = string(r->str); return true; } bool RedisClient::zrange(const std::string& key, int start, int end, vector<string>& values) { redisContext *ctx = CreateContext(); if(ctx == NULL) { return false; } redisReply *r = (redisReply*)redisCommand(ctx, "ZRANGE %s %d %d", key.c_str(), start, end); shared_ptr<redisReply> autoFree(r, freeReplyObject); ReleaseContext(ctx, r != NULL); if (r == NULL) { return false; } if (r->type != REDIS_REPLY_ARRAY) { return false; } for (int i = 0; i < (int)r->elements; i++) { redisReply *cur = r->element[i]; if (cur->type != REDIS_REPLY_STRING) { return false; } values.push_back(cur->str); } return true; }