Gearmand异步处理就安全了吗?不!
前言
之前使用Gearman的时候,遇到过一个卡顿的问题。今天微博上又有人问我是否遇到过此类问题。这个问题,当时是伯诚老师解决的。我把他的文章搬过来。希望能给遇到此类问题的人一点参考。
问题
使用Gearman作为异步消息处理中间件是却没有想象中的顺利。我们多次发现Gearmand进程会将PHP的请求Hold住,不做任何响应,即便PHP在GearmanClient发起连接时设置了连接超时时间,也不会超时。这对于php的工作方式来说,是很危险的。
对于这个问题,我们问了Google许多次,自己也在服务器上跟踪了许久,终于将问题定位为在Gearman的worker进程在通过 addFunction 方法注册任务时,如果使用了timeout参数,那么就会复现这个问题,但这个问题的复现是随机发生的。
首先,Gearmand进程正常工作时,通过pstack查看其工作线程状态如下:
Thread 7 (Thread 0x40bc2940 (LWP 8627)): #0 0x00007fec366d1e46 in poll () from /lib64/libc.so.6 #1 0x00007fec386be7a7 in current_epoch_handler () from /proc/8626/exe #2 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #3 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 6 (Thread 0x415c3940 (LWP 8628)): #0 0x00007fec366db018 in epoll_wait () from /lib64/libc.so.6 #1 0x00007fec37767ff0 in epoll_dispatch () #2 0x00007fec3775b3e1 in event_base_loop () #3 0x00007fec386b7848 in _thread () from /proc/8626/exe #4 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #5 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 5 (Thread 0x41fc4940 (LWP 8629)): #0 0x00007fec37dc9b99 in pthread_cond_wait@@GLIBC_2.3.2 () #1 0x00007fec386b6269 in _proc () from /proc/8626/exe #2 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #3 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 4 (Thread 0x429c5940 (LWP 8630)): #0 0x00007fec366db018 in epoll_wait () from /lib64/libc.so.6 #1 0x00007fec37767ff0 in epoll_dispatch () #2 0x00007fec3775b3e1 in event_base_loop () #3 0x00007fec386b7848 in _thread () from /proc/8626/exe #4 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #5 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 3 (Thread 0x433c6940 (LWP 8631)): #0 0x00007fec366db018 in epoll_wait () from /lib64/libc.so.6 #1 0x00007fec37767ff0 in epoll_dispatch () #2 0x00007fec3775b3e1 in event_base_loop () #3 0x00007fec386b7848 in _thread () from /proc/8626/exe #4 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #5 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 2 (Thread 0x43dc7940 (LWP 8632)): #0 0x00007fec366db018 in epoll_wait () from /lib64/libc.so.6 #1 0x00007fec37767ff0 in epoll_dispatch () #2 0x00007fec3775b3e1 in event_base_loop () #3 0x00007fec386b7848 in _thread () from /proc/8626/exe #4 0x00007fec37dc54a7 in start_thread () from /lib64/libpthread.so.0 #5 0x00007fec366dac2d in clone () from /lib64/libc.so.6 Thread 1 (Thread 0x7fec3863e6f0 (LWP 8626)): #0 0x00007fec366db018 in epoll_wait () from /lib64/libc.so.6 #1 0x00007fec37767ff0 in epoll_dispatch () #2 0x00007fec3775b3e1 in event_base_loop () #3 0x00007fec386b4d5d in gearmand_run () from /proc/8626/exe #4 0x00007fec386a3307 in main () from /proc/8626/exe
可以看到,正常工作时,Gearmand进程中会有7个工作线程。而当工作不正常时,也就是PHP请求被Hold住时,可以看到这里面的线程数是少于7个的。
这个问题在起官方网站的issue中也有人提起。
原因与解决方法
使用的Gearmand的版本是1.1.8依然有这个问题。追溯到0.32版本发现无法重现这个问题,而0.33至1.1.8都存在此问题。后来阅读了源码以及查看了相关Changelog发现0.33之后的版本增加了对worker的timeout处理。而从上面官网的一些用户的反馈来看,也确实和addFunction的timeout参数有关。于是我们先将addFunction时的timeout参数设置为0。问题果然不在重现,那么这个timeout到底为什么会导致这个问题呢?这需要进一步分析Gearmand的源码,分析的重点如下:
首先是线程数为什么会减少?
libgearman-server/gearmand_thread.cc: _thread方法中
static void *_thread(void *data) { gearmand_thread_st *thread= (gearmand_thread_st *)data; char buffer[BUFSIZ]; int length= snprintf(buffer, sizeof(buffer), "[%6u ]", thread->count); if (length <= 0 or sizeof(length) >= sizeof(buffer)) { assert(0); buffer[0]= 0; } (void)gearmand_initialize_thread_logging(buffer); gearmand_debug("Entering thread event loop"); ex s; int r ; if ( (r = event_base_loop(thread->base, 0)) == -1) { gearmand_fatal("event_base_loop(-1)"); cout<<"event_base_loop(-1)"<<endl; Gearmand()->ret= GEARMAND_EVENT; } cout<<"r="<<r<<endl; cout<<"Exiting thread event loop"<<endl; gearmand_debug("Exiting thread event loop"); return NULL; }
在问题发生的时候,会导致event_base_loop执行完毕,导致退出。查看了libevent的相关资料,可以看到。libevent 1.4.x是非线程安全的,不能跨线程执行event_add。而libevent 2.0.x通过线程锁做到了线程安全,可以通过执行evthread_use_pthreads跨线程执行event_add。而Gearmand的代码中没有使用evthread_use_pthreads。而Gearmand的官方代码中,应该就是对于timeout特性进行处理时发生的问题。我们可以看到如下源码中
libgearman-server/connection.cc:gearman_server_con_add_job_timeout 方法中
if (con->timeout_event == NULL) { gearmand_con_st *dcon= con->con.context; con->timeout_event= (struct event *)malloc(sizeof(struct event)); // libevent POD if (con->timeout_event == NULL) { return gearmand_merror("malloc(sizeof(struct event)", struct event, 1); } timeout_set(con->timeout_event, _server_job_timeout, job); event_base_set(dcon->thread->base, con->timeout_event); }
gearman_server_con_add_job_timeout 这个方法,是在main thread中执行的,而下面这条命令
event_base_set(dcon->thread->base, con->timeout_event);操作的是dcon->thread 对象上的event base对象,因此属于跨线程操作了event_base对象。
我们随后对gearman_server_con_add_job_timeout这个方法中的event_base_set调用进行了修改。将dcon->thread->base对象改到了_global_gearmand->base。
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:信海龙 来源: 博学无忧
- 标签: Gearmand
- 发布时间:2015-11-02 22:33:32
- [54] IOS安全–浅谈关于IOS加固的几种方法
- [52] android 开发入门
- [52] 如何拿下简短的域名
- [51] 图书馆的世界纪录
- [49] Go Reflect 性能
- [49] Oracle MTS模式下 进程地址与会话信
- [47] 【社会化设计】自我(self)部分――欢迎区
- [46] 读书笔记-壹百度:百度十年千倍的29条法则
- [37] 程序员技术练级攻略
- [29] 视觉调整-设计师 vs. 逻辑