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;
}
View Code