您的位置:首页 > 数据库 > Redis

从hiredis使用出core谈谈redis多线程的使用【转】

2015-09-02 16:00 579 查看

在实际工作中,我需要使用redis的客户端去连接redis,于是选择了hiredis客户端(公司强推)。hiRedis是Redis官方指定的C语言客户端开发包,支持Redis完整的命令集、管线以及事件驱动编程。

1、情景描述

1.1使用场景

一个epool模型的服务器不断接受外界请求,这个服务器框架给用户预留一个回调函数(多线程),回调函数为用户自己去实现的业务逻辑,其中redis的使用就需要在这个回调函数内部实现。

1.2初步实现方案

在程序启动的时候,我就初始化redis的连接,获得hiredis句柄。然后把hiredis句柄传入到线程函数里面。让其做相应的业务逻辑。

1.3结果

很不幸,一次请求都没问题,做压力测试,同时开20个线程访问,程序立即出core。
线上出core如下:
01(gdb)bt
02#00x000000302af2e2edinraise()from/lib64/tls/libc.so.6
03#10x000000302af2fa3einabort()from/lib64/tls/libc.so.6
04#20x000000302af62db1in__libc_message()from/lib64/tls/libc.so.6
05#30x000000302af6888ein_int_free()from/lib64/tls/libc.so.6
06#40x000000302af6a12din_int_realloc()from/lib64/tls/libc.so.6
07#50x000000302af6b39cinrealloc()from/lib64/tls/libc.so.6
08#60x0000000000dc2269insdscatlen(s=Variable"s"isnotavailable.
09)atsds.c:97
10#70x0000000000dc1d40in__redisAppendCommand(c=0x16fa1d0,cmd=Variable"cmd"isnotavailable.
11)athiredis.c:1186
12#80x0000000000dc1d97inredisvAppendCommand(c=0x16fa1d0,format=Variable"format"isnotavailable.
13)athiredis.c:1206
14#90x0000000000dc1eedinredisvCommand(c=0x16fa1d0,format=Variable"format"isnotavailable.
15)athiredis.c:1267
16#100x0000000000dc1fb6inredisCommand(c=Variable"c"isnotavailable.
17)athiredis.c:1276
18#110x0000002b1a8e6310inDefault_Handler::get_batch_redis(this=0x1ff41f0,redis_ins=0x175a7d0,dataid=6202,buf_num=12,res_num=6,key_sign=0x2bd67cb3c8,
19res_lens=0x2bd5f54208,res_buf=0x2bd5f54398"")atdefault_handler.cpp:659
20#120x0000002b1a9134dfinDefault_Ms_Handler::get_digest(this=0x1ff41f0)atdefault_ms_handler.cpp:646
21#130x000000000092910cindo_proc()atgss_work.cpp:1107
22#140x000000000091c91finthread_main()atgss_net.cpp:188
23#150x0000000000bc10e9indefault_native()atubserver_app.cpp:283
24#160x0000000000bbc676ineppool_consume(pool=0x2230b90,data=0x22188f0)ateppool.cpp:649
25#170x0000000000bbc4d1in_eppool_workers(param=0x22188f0)ateppool.cpp:604
26#180x000000302b80610ainstart_thread()from/lib64/tls/libpthread.so.0
27#190x000000302afc6003inclone()from/lib64/tls/libc.so.6
28#200x0000000000000000in??()
当时经过多次尝试。把连接放入到了每个线程中。那么就不会出core了。

2、线下复现

因为不方便公开公司代码,所以我写一个类似的代码来复现这个case。

2.1代码

代码主要有testredis.cpp和Makefile(自己指定hiredis目录)。用法是./redis-n[num]-h[host]-p[port],n为host数目,多个host用"-"进行分割。
testredis.cpp
001/***************************************************************************
002*
003*Copyright(c)2014Baidu.com,Inc.AllRightsReserved
004*
005**************************************************************************/
006
007
008
009/**
010*@fileredistest.cpp
011*@authorliujun05(com@baidu.com)
012*@date2014/02/2510:28:44
013*@brief
014*
015**/
016
017#include<unistd.h>
018#include<stdio.h>
019#include<hiredis.h>
020#include<stdlib.h>
021#include<string.h>
022#include<pthread.h>
023
024
025#ifndefuint32
026#defineuint32unsignedint
027#endif
028
029#defineMAX_REDIS_SERVER_CNT10
030#defineMAX_REDIS_IPS1024
031
032typedefstruct_redis_conf_t
033{
034uint32redis_num;
035charredis_ips[MAX_REDIS_IPS];
036charredis_ip_array[MAX_REDIS_SERVER_CNT][MAX_REDIS_IPS];
037uint32redis_port;
038
039}redis_conf;
040
041typedefstruct_redis_data_t
042{
043uint32redis_num;
044redisContext*rc[MAX_REDIS_SERVER_CNT];
045}redis_data;
046
047redis_confg_cfg;
048redis_datag_data;
049
050voidshow_usage()
051{
052printf("usage:./redis-n[num]-h[host]-p[port]\n");
053}
054
055/**解析参数*/
056intmain_parse_option(intargc,char**argv)
057{
058intc;
059//reset获取参数的位置,多次调用时这个会出现问题
060while((c=getopt(argc,argv,"h:p:n:"))!=-1)
061{
062switch(c)
063{
064case'h':
065sprintf(g_cfg.redis_ips,optarg);
066break;
067case'p':
068g_cfg.redis_port=atoi(optarg);
069break;
070case'n':
071g_cfg.redis_num=atoi(optarg);
072break;
073default:
074show_usage();
075fflush(stdout);
076return-1;
077}
078}
079return0;
080}
081
082void*test_thread1(void*data)
083{
084redis_data*redis_ins=(redis_data*)data;
085redisReply*reply;
086for(inti=0;i<redis_ins->redis_num;i++)
087{
088reply=(redisReply*)redisCommand(redis_ins->rc[i],"SET%s%s","foo","helloworld");
089freeReplyObject(reply);
090}
091}
092
093intinit_data()
094{
095g_data.redis_num=0;
096structtimevaltimeout={1,500000};//1.5seconds
097
098char*ptok=NULL;
099char*part=strtok_r(g_cfg.redis_ips,"-",&ptok);
100intnum=0;
101while(part)
102{
103strcpy(g_cfg.redis_ip_array[num++],part);
104part=strtok_r(NULL,"-",&ptok);
105}
106
107if(num!=g_cfg.redis_num||num>MAX_REDIS_SERVER_CNT)
108{
109printf("ipnum[%d]notequalredis_num[%d]ornotvaild\n",num,g_cfg.redis_num);
110}
111
112g_data.redis_num=(num>MAX_REDIS_SERVER_CNT)?MAX_REDIS_SERVER_CNT:num;
113inti=0;
114
115for(i=0;i<g_data.redis_num;i++)
116{
117g_data.rc[i]=redisConnectWithTimeout(g_cfg.redis_ip_array[i],g_cfg.redis_port,timeout);
118if(g_data.rc[i]==NULL||g_data.rc[i]->err)
119{
120printf("contenttoredisserver[%s:%u],error[%s]\n",
121g_cfg.redis_ip_array[i],g_cfg.redis_port,g_data.rc[i]->errstr
122);
123gotoexit;
124}
125}
126return0;
127
128exit:
129for(intj=0;j<i;j++)
130{
131if(g_data.rc[j]!=NULL)
132{
133redisFree(g_data.rc[j]);
134}
135}
136return-1;
137}
138
139
140intdestory_data()
141{
142for(intj=0;j<g_data.redis_num;j++)
143{
144if(g_data.rc[j]!=NULL)
145{
146redisFree(g_data.rc[j]);
147}
148}
149}
150
151intmain(intargc,char**argv)
152{
153g_cfg.redis_ips[0]='\0';
154g_cfg.redis_port=6379;
155g_cfg.redis_num=0;
156if(0!=main_parse_option(argc,argv))
157{
158show_usage();
159return-1;
160}
161
162if(0==g_cfg.redis_num||g_cfg.redis_num>MAX_REDIS_SERVER_CNT)
163{
164printf("thereidsnum[%u]isnotvaild\n",g_cfg.redis_num);
165show_usage();
166return0;
167}
168
169intret=init_data();
170if(ret!=0)
171{
172printf("initnumfail\n");
173return-1;
174}
175
176
177pthread_tt[100];
178for(inti=0;i<100;i++)
179{
180pthread_create(&t[i],NULL,test_thread1,&g_data);
181}
182
183for(inti=0;i<100;i++)
184{
185pthread_join(t[i],NULL);
186}
187
188destory_data();
189return0;
190}
191
192
193
194/*vim:setexpandtabts=4sw=4sts=4tw=100:*/
Makefile

1redis:testredis.cpp
2g++-gtestredis.cpp-I./hiredis-L./hiredis-lhiredis-lpthread-oredis
3
4clean:
5rmredis

2.2编译执行

1liujun05@cq01-rdqa-dev012.cq01:~/test/hiredis$./redis-n2-h10.48.46.26-10.46.175.102
2***glibcdetected***doublefreeorcorruption(!prev):0x000000000050aa80***
3Aborted(coredumped)
可以看到出core了

01(gdb)bt
02#00x000000302af2e2edinraise()from/lib64/tls/libc.so.6
03#10x000000302af2fa3einabort()from/lib64/tls/libc.so.6
04#20x000000302af62db1in__libc_message()from/lib64/tls/libc.so.6
05#30x000000302af6888ein_int_free()from/lib64/tls/libc.so.6
06#40x000000302af68bd6infree()from/lib64/tls/libc.so.6
07#50x0000000000403c75inredisBufferWrite(c=0x50a010,done=0x571c008c)athiredis.c:1162
08#60x0000000000403d3einredisGetReply(c=0x50a010,reply=0x571c00b8)athiredis.c:1195
09#70x0000000000403f62inredisvCommand(c=0x50a010,format=Variable"format"isnotavailable.
10)athiredis.c:1296
11#80x0000000000404006inredisCommand(c=Variable"c"isnotavailable.
12)athiredis.c:1313
13#90x00000000004013e7intest_thread1(data=0x509ba0)attestredis.cpp:88
14#100x000000302b80610ainstart_thread()from/lib64/tls/libpthread.so.0
15#110x000000302afc6003inclone()from/lib64/tls/libc.so.6
16#120x0000000000000000in??()
虽然出core位置不一致,但是经过查看代码,出core的原因应该是一致的。

2.3原因分析

从堆栈5可以看到hiredis.c的1162行出的core,打开hiredis.c
11160}elseif(nwritten>0){
21161if(nwritten==(signed)sdslen(c->obuf)){
31162sdsfree(c->obuf);
41163c->obuf=sdsempty();
51164}else{
61165c->obuf=sdsrange(c->obuf,nwritten,-1);
71166}
可以看到的确在1152行对c->obuf进行了一次free导致出core。

我们分析下调用关系,首先调用redisCommand.
11309void*redisCommand(redisContext*c,constchar*format,...){
21310va_listap;
31311void*reply=NULL;
41312va_start(ap,format);
51313reply=redisvCommand(c,format,ap);
61314va_end(ap);
71315returnreply;
81316}
然后调用redisvCommand

11303void*redisvCommand(redisContext*c,constchar*format,va_listap){
21304if(redisvAppendCommand(c,format,ap)!=REDIS_OK)
31305returnNULL;
41306return__redisBlockForReply(c);
51307}
接着调用redisvAppendCommand
01<span></span>1233intredisvAppendCommand(redisContext*c,constchar*format,va_listap){
021234char*cmd;
031235intlen;
041236
051237len=redisvFormatCommand(&cmd,format,ap);
061238if(len==-1){
071239__redisSetError(c,REDIS_ERR_OOM,"Outofmemory");
081240returnREDIS_ERR;
091241}
101242
111243if(__redisAppendCommand(c,cmd,len)!=REDIS_OK){
121244free(cmd);
131245returnREDIS_ERR;
141246}
151247
161248free(cmd);
171249returnREDIS_OK;
181250}
这里,我们需要care调用__redisAppendCommand.
011220int__redisAppendCommand(redisContext*c,char*cmd,size_tlen){
021221sdsnewbuf;
031222
041223newbuf=sdscatlen(c->obuf,cmd,len);
051224if(newbuf==NULL){
061225__redisSetError(c,REDIS_ERR_OOM,"Outofmemory");
071226returnREDIS_ERR;
081227}
091228
101229c->obuf=newbuf;
111230returnREDIS_OK;
121231}
问题出现了。
对于任意一个多线程,他传入的redisContext*c都是一个,那么他们也公用同一个c->obuf,这里很明显,线程数据是耦合的。
当一个线程调用sdsfreec->obuf,其他任意一个线程使用c->obuf都会导致出core.这也是我所谓的hiredis对多线程支持的不好的地方。

3.终极解决方案

那么,如果我一定要在多线程中通过hiredis客户端调用redis呢。有没有方案了,答案肯定是有,只不过性能稍差。
原先的做法是先获得hiredis连接句柄,然后把句柄传入到多线程中,让多线程使用。现在改成在线程里面连接获得hiredis句柄,然后再进行使用。当然,代价是对于每个请求,都需要去连接redis服务器,加大了网络开销的同时还加大了redis的请求。
redis是单线程异步模型,hiredis这个客户端看来也只支持单线程。希望后续有redis的相关程序猿来改进相应问题,在hiredis使用多线程需要慎重。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: