设为首页 收藏本站
查看: 823|回复: 0

[经验分享] 从hiredis使用出core谈谈redis多线程的使用

[复制链接]

尚未签到

发表于 2016-12-21 06:07:45 | 显示全部楼层 |阅读模式
  

从hiredis使用出core谈谈redis多线程的使用
发表于3个月前(2014-02-25 15:29) 阅读(607)|评论(311人收藏此文章,我要收藏
0


摘要hireedis多线程出core原因

目录[-]
1、情景描述1.1 使用场景1.2 初步实现方案1.3 结果2、线下复现2.1 代码2.2 编译执行2.3 原因分析3. 终极解决方案

  在实际工作中,我需要使用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#00x000000302af2e2ed in raise () from /lib64/tls/libc.so.6

03#10x000000302af2fa3e in abort () from /lib64/tls/libc.so.6

04#20x000000302af62db1 in __libc_message () from /lib64/tls/libc.so.6

05#30x000000302af6888e in _int_free () from /lib64/tls/libc.so.6

06#40x000000302af6a12d in _int_realloc () from /lib64/tls/libc.so.6

07#50x000000302af6b39c in realloc () from /lib64/tls/libc.so.6

08#60x0000000000dc2269 in sdscatlen (s=Variable "s" is not available.

09)at sds.c:97

10#70x0000000000dc1d40 in __redisAppendCommand (c=0x16fa1d0, cmd=Variable "cmd" is not available.

11)at hiredis.c:1186

12#80x0000000000dc1d97 in redisvAppendCommand (c=0x16fa1d0, format=Variable "format" is not available.

13)at hiredis.c:1206

14#90x0000000000dc1eed in redisvCommand (c=0x16fa1d0, format=Variable "format" is not available.

15)at hiredis.c:1267

16#100x0000000000dc1fb6 in redisCommand (c=Variable "c" is not available.

17)at hiredis.c:1276

18#110x0000002b1a8e6310 in Default_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"")at default_handler.cpp:659

20#120x0000002b1a9134df in Default_Ms_Handler::get_digest (this=0x1ff41f0) at default_ms_handler.cpp:646

21#130x000000000092910c in do_proc () at gss_work.cpp:1107

22#140x000000000091c91f in thread_main () at gss_net.cpp:188

23#150x0000000000bc10e9 in default_native () at ubserver_app.cpp:283

24#160x0000000000bbc676 in eppool_consume (pool=0x2230b90, data=0x22188f0) at eppool.cpp:649

25#170x0000000000bbc4d1 in _eppool_workers (param=0x22188f0) at eppool.cpp:604

26#180x000000302b80610a in start_thread () from /lib64/tls/libpthread.so.0

27#190x000000302afc6003 in clone () from /lib64/tls/libc.so.6

28#200x0000000000000000 in ?? ()



  当时经过多次尝试。把连接放入到了每个线程中。那么就不会出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) 2014 Baidu.com, Inc. All Rights Reserved

004*

005**************************************************************************/

006

007

008

009/**

010*@file redistest.cpp

011*@author liujun05(com@baidu.com)

012*@date 2014/02/25 10: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#defineuint32 unsigned int

027#endif

028

029#defineMAX_REDIS_SERVER_CNT 10

030#defineMAX_REDIS_IPS 1024

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 ,"SET%s %s","foo","helloworld");

089freeReplyObject(reply);

090}

091}

092

093intinit_data()

094{

095g_data.redis_num= 0;

096structtimevaltimeout = { 1, 500000 };//1.5 seconds

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] not equal redis_num[%d] or not vaild\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= redisConnectWithTimeout( g_cfg.redis_ip_array, g_cfg.redis_port , timeout);

118if(g_data.rc == NULL || g_data.rc->err)

119{

120printf("contentto redis server[%s:%u], error[%s]\n",

121g_cfg.redis_ip_array,g_cfg.redis_port, g_data.rc->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("thereids num[%u] is not vaild\n",g_cfg.redis_num);

165show_usage();

166return0;

167}

168

169intret= init_data();

170if(ret != 0)

171{

172printf("initnum fail\n");

173return-1;

174}

175

176

177pthread_tt[100];

178for(inti=0;i<100; i++)

179{

180pthread_create(&t,NULL, test_thread1, &g_data);

181}

182

183for(inti=0;i<100; i++)

184{

185pthread_join(t,NULL);

186}

187

188destory_data();

189return0;

190}

191

192

193

194/*vim: set expandtab ts=4 sw=4 sts=4 tw=100: */



Makefile
1redis:testredis.cpp

2g++-g testredis.cpp -I./hiredis -L./hiredis -lhiredis -lpthread -o redis

3

4clean:

5rmredis



2.2 编译执行
1liujun05@cq01-rdqa-dev012.cq01:~/test/hiredis$./redis -n2 -h10.48.46.26-10.46.175.102

2***glibc detected *** doublefreeorcorruption (!prev): 0x000000000050aa80 ***

3Aborted(core dumped)



  可以看到出core了
01(gdb)bt

02#00x000000302af2e2ed in raise () from /lib64/tls/libc.so.6

03#10x000000302af2fa3e in abort () from /lib64/tls/libc.so.6

04#20x000000302af62db1 in __libc_message () from /lib64/tls/libc.so.6

05#30x000000302af6888e in _int_free () from /lib64/tls/libc.so.6

06#40x000000302af68bd6 in free () from /lib64/tls/libc.so.6

07#50x0000000000403c75 in redisBufferWrite (c=0x50a010, done=0x571c008c) at hiredis.c:1162

08#60x0000000000403d3e in redisGetReply (c=0x50a010, reply=0x571c00b8) at hiredis.c:1195

09#70x0000000000403f62 in redisvCommand (c=0x50a010, format=Variable "format" is not available.

10)at hiredis.c:1296

11#80x0000000000404006 in redisCommand (c=Variable "c" is not available.

12)at hiredis.c:1313

13#90x00000000004013e7 in test_thread1 (data=0x509ba0) at testredis.cpp:88

14#100x000000302b80610a in start_thread () from /lib64/tls/libpthread.so.0

15#110x000000302afc6003 in clone () from /lib64/tls/libc.so.6

16#120x0000000000000000 in ?? ()



  虽然出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,"Outof memory");

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){

021221sds newbuf;

031222

041223newbuf = sdscatlen(c->obuf,cmd,len);

051224if(newbuf== NULL) {

061225__redisSetError(c,REDIS_ERR_OOM,"Outof memory");

071226returnREDIS_ERR;

081227}

091228

101229c->obuf = newbuf;

111230returnREDIS_OK;

121231}



  问题出现了。
  对于任意一个多线程,他传入的redisContext* c都是一个,那么他们也公用同一个c->obuf,这里很明显,线程数据是耦合的。
  当一个线程调用sdsfree c->obuf,其他任意一个线程使用c->obuf都会导致出core.这也是我所谓的hiredis对多线程支持的不好的地方。
3. 终极解决方案
  那么,如果我一定要在多线程中通过hiredis客户端调用redis呢。有没有方案了,答案肯定是有,只不过性能稍差。
  原先的做法是先获得hiredis连接句柄,然后把句柄传入到多线程中,让多线程使用。现在改成在线程里面连接获得hiredis句柄,然后再进行使用。当然,代价是对于每个请求,都需要去连接redis服务器,加大了网络开销的同时还加大了redis的请求。
  redis是单线程异步模型,hiredis这个客户端看来也只支持单线程。希望后续有redis的相关程序猿来改进相应问题,在hiredis使用多线程需要慎重。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-316998-1-1.html 上篇帖子: Redis 下篇帖子: 翻译:redis-py 说明文件 (2012-05-30 17:55:52)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表