前两篇文章(redis安装配置与测试、redis的数据类型)总计了redis的一些基本知识,这里要进行一下redis的实践,先介绍一个实际应用场景,对于客户频繁需要查询的信息,可以将其放在redis内存数据库中,相当于一个缓存,每次查的时候先去redis内存数据库中去查询,如果查询不到再去oracle数据库中查询,这样提高了效率。
本文,使用redis的C与语操作接口hiredis包装了redis数据库的增删改查以及连接和断开连接接口,可以用在实际应用中。
(一)hiredis介绍
hiredis提供了一linux下访问redis的接口,比如连接和操作redis数据库。这里介绍本文要使用的几个api,其他的可参考官网介绍
redisContext *redisConnect(const char *ip, int port); /*连接redis*/
void *redisCommand(redisContext *c, const char *format, ...); /*redis数据库操作 如SET GET AUTH等*/
void freeReplyObject(void *reply); /*释放redisCommand返回得到的结果结构体*
void redisFree(redisContext *c); /*释放连接上下文断开连接*/
hiredis中两种比较重要的结构体定义如下:
typedef struct redisContext
{
int err;
char errstr[128];
int fd;
int flags;
char *obuf;
redisReader *reader;
} redisContext;
typedef struct redisReply
{
int type;
long long integer;
int len;
char *str;
size_t elements;
struct redisReply **element;
} redisReply;
在调用redisConnect连接数据库时候,会返回一个redisContext结构体指针,可以看做是一个连接句柄,redisCommand需要用到该句柄。调用redisCommand返回一个redisReply指针(实际返回的类型是void*,需要显示的转换为redisReply*指针),指向的结构体包含了一些返回信息,如返回类型、返回值和长度等。
(二)hiredis使用
(1)连接redis
调用redisConnect连接redis数据库,得到一个redisContext*指针,如果连接失败,则结构体中的err和errstr被置值。连接成功则err为0。使用示例如下:
redisContext *c = NULL;
/*connect*/
c = redisConnect(ip, port);
(2)操作redis
使用redisCommand,该接口返回一个void*指针,显式转换为redisReply*指针,指向的结构中包含了一些返回信息,比较重要的是返回类型,代表了返回结果的类型。使用示例如下:
redisReply *reply = NULL;
reply = (redisReply *)redisCommand(c, "GET %s", key);
reply->type表明了返回的类型,常见的类型如下:
REDIS_REPLY_STATUS:
返回了命令执行状态,状态信息可以由reply-> str获得。AUTH命令和SET命令正常情况下返回该值, reply-> str为OK。
REDIS_REPLY_ERROR:
表示执行出现了一个错误
REDIS_REPLY_INTEGER:
返回了一个整数,整数值由eply->integer表示。例如DEL操作,如果key存在,正常情况下返回该类型,表示键的个数。
REDIS_REPLY_NIL:
返回了nil。 表示无相应数据,例如GET不存在的key。
REDIS_REPLY_STRING:
表示命令执行后返回了一个字符串。 返回的字符串使用reply-> str来获取。 这个字 符串的长度为reply-> len。例如GET,键存在的情况下,返回一个字符串。
(3)释放返回结果机构体
freeReplyObject(reply)
使用完毕后需要释放reply机构体。
(4)断开连接,清空redisContext
redisFree(c);
三、hiredis包装redis数据库的增删改查、连接和断开连接接口函数,接口函数如下:
redisContext *redis_connect(char *ip, unsigned int port, char *passwd);
void redis_disconnect(redisContext *c);
void redis_disconnect(redisContext *c);
int redis_insert(redisContext *c, char *key, char *value, int ex);
int redis_delete(redisContext *c, char *key);
int redis_select(redisContext *c, char *key, char *value);
int redis_update(redisContext *c, char *key, char *value, int ex);
具体实现:
1.redisOpr.c
/********************************
* redis opration
* created time: 2018-01-01
* created by poetteaes
*******************************/
# include "hiredis.h"
# include
# include
# define NOT_FOUND -1403
/********************************
* redis connect
* return a pointer if success;
* return NULL if failed
*******************************/
redisContext *redis_connect(char *ip, unsigned int port, char *passwd)
{
redisContext *c = NULL;
redisReply *replay = NULL;
/*connect*/
c = redisConnect(ip, port);
if(c==NULL)
{
printf("Error: redisConnect() error!\n");
return NULL;
}
if(c->err != 0)
{
printf("Error: %s\n", c->errstr);
redisFree(c);
}
/*auth if passwd is not NULL*/
if(passwd != NULL)
{
replay = (redisReply *)redisCommand(c, "AUTH %s", passwd);
if( replay == NULL)
{
printf("Error: AUTH error!\n");
redisFree(c);
printf("redisFree\n");
return NULL;
}
if( !(replay->type==REDIS_REPLY_STATUS && memcmp(replay->str, "OK", 2)==0) )
{
printf("Error: AUTH error!\n");
freeReplyObject(replay);
redisFree(c);
printf("redisFree\n");
return NULL;
}
}
return c; /*connect success*/
}
/********************************
* redis select
* return length of value if success;
* return NOT_FOUND if not found
* return -1 if failed
*******************************/
int redis_select(redisContext *c, char *key, char *value)
{
redisReply *reply = NULL;
if(value == NULL)
{
printf("Error: Invalid output argument!\n");
return -1;
}
reply = (redisReply *)redisCommand(c, "GET %s", key);
if( reply == NULL)
{
printf("Error: GET error!\n");
return -1;
}
if(reply->type != REDIS_REPLY_STRING && reply->type!=REDIS_REPLY_NIL)
{
printf("Error: GET error!\n");
freeReplyObject(reply);
return -1;
}
if(reply->type==REDIS_REPLY_NIL)
{
printf("Not found\n");
freeReplyObject(reply);
return NOT_FOUND;
}
memcpy(value, reply->str, reply->len);
freeReplyObject(reply);
return (reply->len);
}
/********************************
* redis insert
* return 0 if success;
* return -1 if failed
*******************************/
int redis_insert(redisContext *c, char *key, char *value, int ex)
{
redisReply *reply = NULL;
if(ex<0)
{
printf("Error: Invalid input argument ex[%d]", ex);
return -1;
}
/*test if the key has been existed*/
reply = (redisReply *)redisCommand(c, "GET %s", key);
if( reply == NULL)
{
printf("Error: GET error!\n");
return -1;
}
if(reply->type != REDIS_REPLY_STRING && reply->type!=REDIS_REPLY_NIL)
{
printf("Error: GET error!\n");
freeReplyObject(reply);
return -1;
}
if(reply->type==REDIS_REPLY_STRING)
{
printf("Error: The key has existed.\n");
freeReplyObject(reply);
return -1;
}
freeReplyObject(reply);
/*insert*/
reply = NULL;
if(ex !=0 )
{
reply = (redisReply *)redisCommand(c, "SET %s %s EX %d", key, value, ex);
}
else
{
reply = (redisReply *)redisCommand(c, "SET %s %s", key, value); /*without ex*/
}
if( reply == NULL)
{
printf("Error: SET error!\n");
return -1;
}
if( !(reply->type==REDIS_REPLY_STATUS && memcmp(reply->str, "OK", 2)==0) )
{
printf("Error: SET error!\n");
freeReplyObject(reply);
return -1;
}
freeReplyObject(reply);
return 0;
}
/********************************
* redis delete
* return 0 if success;
* return NOT_FOUND if not found
* return -1 if failed
*******************************/
int redis_delete(redisContext *c, char *key)
{
redisReply *reply = NULL;
reply = (redisReply *)redisCommand(c, "DEL %s", key);
if(reply==NULL || reply->type!=REDIS_REPLY_INTEGER)
{
printf("Error: The key doesn't exist.\n");
freeReplyObject(reply);
return -1;
}
freeReplyObject(reply);
if(reply->integer==0)
return NOT_FOUND;
else
return (reply->integer - 1);
}
/********************************
* redis delete
* return 0 if success;
* return NOT_FOUND if not found
* return -1 if failed
*******************************/
int redis_update(redisContext *c, char *key, char *value, int ex)
{
redisReply *reply = NULL;
if(ex<0)
{
printf("Error: Invalid input argument ex[%d]", ex);
return -1;
}
/*test if the key has been existed*/
reply = (redisReply *)redisCommand(c, "GET %s", key);
if( reply == NULL)
{
printf("Error: GET error!\n");
return -1;
}
if(reply->type != REDIS_REPLY_STRING && reply->type!=REDIS_REPLY_NIL)
{
printf("Error: GET error!\n");
freeReplyObject(reply);
return -1;
}
if(reply->type==REDIS_REPLY_NIL)
{
printf("The key doesn't exist.\n");
freeReplyObject(reply);
return NOT_FOUND;
}
freeReplyObject(reply);
/*update*/
reply = NULL;
if(ex !=0 )
{
reply = (redisReply *)redisCommand(c, "SET %s %s EX %d", key, value, ex);
}
else
{
reply = (redisReply *)redisCommand(c, "SET %s %s", key, value); /*without ex*/
}
if( reply == NULL)
{
printf("Error: SET error!\n");
return -1;
}
if( !(reply->type==REDIS_REPLY_STATUS && memcmp(reply->str, "OK", 2)==0) )
{
printf("Error: SET error!\n");
freeReplyObject(reply);
return -1;
}
freeReplyObject(reply);
return 0;
}
/*redis disconnect*/
void redis_disconnect(redisContext *c)
{
redisFree(c);
}
2. redisTest.c
测试程序如下:
/********************************
* redis opration test
* created time: 2018-01-01
* created by poetteaes
*******************************/
# include "hiredis.h"
# include
# include
# define EX_TIME 3600
# define NOT_FOUND -1403
redisContext *redis_connect(char *ip, unsigned int port, char *passwd);
void redis_disconnect(redisContext *c);
void redis_disconnect(redisContext *c);
int redis_insert(redisContext *c, char *key, char *value, int ex);
int redis_delete(redisContext *c, char *key);
int redis_select(redisContext *c, char *key, char *value);
int redis_update(redisContext *c, char *key, char *value, int ex);
int main(void)
{
int ret;
char ip[] = "127.0.0.1";
unsigned int port = 6379;
redisContext *c = NULL;
char key[128] = {0};
char value[128] ={0};
memcpy(key, "key1", 4);
memcpy(value, "value1", 6);
/*connect*/
c = redis_connect(ip, port, NULL);
if(c == NULL || c->err!=0)
{
printf("Error: redis_connect() error!\n");
if(c != NULL )
redis_disconnect(c);
return -1;
}
else
{
printf("redis connected.\n\n");
}
/*insert*/
ret = redis_insert(c, key, value, EX_TIME);
if(ret < 0)
{
printf("Error: redis_insert() error!\n\n");
/*redis_disconnect(c);
return -1;*/
}
else
{
printf("\nredis_insert() success.\n");
}
/*update*/
ret = redis_update(c, key, "value1update", EX_TIME);
if(ret !=0 && ret!=NOT_FOUND)
{
printf("\nError: redis_update() error!\n");
/*redis_disconnect(c);
return -1;*/
}
else if(ret==NOT_FOUND)
{
printf("key is not found!\n");
}
else
{
printf("\nredis_update() success.\n\n");
}
/*select*/
ret = redis_select(c, key, value);
if(ret < 0 && ret!=NOT_FOUND)
{
printf("\nError: redis_select() error!\n");
/*redis_disconnect(c);
return -1;*/
}
else if(ret==NOT_FOUND)
{
printf("key is not found!\n");
}
else
{
printf("\nredis_select() success.\n");
printf("value[%s]\n\n",value);
}
/*delete*/
ret = redis_delete(c, key);
if(ret < 0 && ret!=NOT_FOUND)
{
printf("\nError: redis_delete() error!\n");
/*redis_disconnect(c);
return -1;*/
}
else if(ret==NOT_FOUND)
{
printf("key is not found!\n");
}
else
{
printf("\nredis_delete() success.\n");
}
/*disconnect*/
redis_disconnect(c);
printf("\nredis disconnected.\n");
return 0;
}
3. makefile
CC = gcc
CFLAG = -g -c -Wall
INCLUDE_PATH=-I/usr/local/include/hiredis/
LIB_PATH=-L/usr/local/lib -lhiredis -L/media/sf_share/git/project/redis
all: libredisopr.so redisTest
%.o:%.c
$(CC) $(CFLAG) -fPIC $(INCLUDE_PATH) $(LIB_PATH) -c $*.c
libredisopr.so:redisOpr.o
$(CC) -o $@ $< -fPIC -shared $(INCLUDE_PATH) $(LIB_PATH)
redisTest:redisTest.o
$(CC) -o $@ $< $(INCLUDE_PATH) $(LIB_PATH) -lredisopr
clean:
rm -f *.o
redisOpr编译成libredisopr.so动态库,redisTest引用动态库中的增删改查等函数。测试结果如下:
包装的接口支持对插入时主键重复(报错)以及更新和查找时键不存在的情况进行了判断。例如键已经存在的情况下执行测试程序,结果如下:
(四)接口的实际应用
这里只采用对一个键进行操作的API来包装接口,实际上利用hiredis的API,也可以对多个键操作。但是一个键操作包装出来的增删改查在一些应用场景下已足矣,因为多个键实际上可以拼成一个键,比如定长格式的字符串或者JSON字符串,或者分割符字符串,同样结果的字符串也可以拼接,作为拼接key的拼接value,实际使用的时候,对结果再进行解析即可。
再回到开头说的应用场景,设想一个oracle数据库表,为订单表,主键为date和orderid,非主键字段为price何goodsname,交易发生时数据存储到oracle数据库,同时也存储在redis内存数据库中(超时清理),客户查询时候先查redis内存数据库,查不到再查oracle数据库。尽管主键和查询结果都有两个字段,但是完全可以用|分割符将主键的两个字段进行拼接(当然也可以使用JSON等格式封装),作为redis的键,同样结果字段也可以这样拼接,这样表中的每一行数据都可以在redis中由单一的key和value表示。插入redis数据库的时候,把date和orderid用|拼接成键,price何goodsname用|拼接成value,然后调用上述包装的redis_insert函数进行插入;查询redis数据库的时候,把查询字段date和orderid用|拼接为键,使用包装的redis_select去查询,得到拼装的value结果,从中解析出price何goodsname记得到了各个结果字段。