内容简介:这周对一个服务进行了升级,结果踩了一个不大不小的坑。先介绍下这个服务的背景:最开始的实现是会把接收到的数据保存在一个成员变量里,然后等积攒到一定数据量后,写入一个文件内。
这周对一个服务进行了升级,结果踩了一个不大不小的坑。
先介绍下这个服务的背景:
这是一个数据接收的服务,通过http协议接收到json数据之后进行解析,然后落地到本地文件; 之后再由其他服务读取这些文件,进行后续的处理。
最开始的实现是会把接收到的数据保存在一个成员变量里,然后等积攒到一定数据量后,写入一个文件内。
这样做最简单,但是有这么一个问题:
如果服务器重启,那么当前hold在内存中的这些数据,就永久的丢失了。
所以这一版呢,我就把它改成了实时append写入文件的方式,写够一定的条目数,就切换一个文件来写。
既然要实时落地,那么这里在接受数据的线程和落地文件的线程之间,就需要一个数据队列来作为缓冲。
针对这个数据队列的操作,则应该加锁避免竞态出现。
那么接受数据的伪代码如下:
// Thread receiver ConstructContentFromJson(json_value, &content_elem); { MutexLocker locker(&mutex_); contents_.emplace(std::move(content_elem)); }
而落地文件的伪代码则是:
// Thread writer while(!quit_) { TryWriteContent(); } //... int TryWriteContent() { ContentElem content_elem; { MutexLocker locker(&mutex_); if (contents_.empty) { usleep(1000); return ERROR_NO_ELEM; } content_elem = contents_.pop_front(); } return DoWriteContent(content_elem); }
看上去一切都很合理:
- 接受到数据,就在锁的保护下塞到队列中;
- 写数据时,则在锁的保护下拿出一个数据来拷贝到局部变量,然后就可以放心写入文件了;
- 如果队列中没数据可获取了,那就原地等待1ms。
临界区也基本控制的很小,应该也没有性能问题。
DUANG!!!
但是,当我升级完后台服务器群中的一台后,看监控,却发现请求量在更新服务器之后,暴跌了90%。
但是问题是,我这才只更新了一台服务器啊。还有好几台没更新,跑的也是旧版本程序,它们上面接收到的请求也暴跌了。
那一定不是我的锅!
于是我向前去查了nginx log,发现确实nginx接收到的请求数量就少了很多。
难道是服务的调用方也同时发了新版本?
于是赶紧电话联系千里之外的网友同事,查出来的结果是:
由于服务超时严重,所以调用方主动限流了。
那看来还是我的锅…… 是你的,总是逃不掉。 毕竟调用方看来,nginx代理屏蔽了后台所有服务器细节,所以后台一台服务器超时严重,调用方就会认为整个服务超时严重。
再回头去看上面的核心代码,问题很快就浮出水面了:
usleep放在了锁的临界区范围内!
这会导致写文件线程在没数据时,一直占据着锁。虽然这个线程在sleep,但是它却占据着锁,其他线程也没办法往队列里填充数据,等于这部分时间服务啥也干不了。多来几次,就大面积超时了。
问题找到了,解决方法也很简单,把usleep挪到锁外即可。
总结
- 锁的临界区内,一定不能出现sleep这种阻塞操作(包括但不限于文件IO、网络IO等)。
- 需要sleep时,可以考虑主动将线程的控制权让出,从而避免使用sleep。
转载请注明出处: http://blog.guoyb.com/2018/07/28/mutex-sleep/
欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 小心递归中内存泄漏
- 小心 getLaunchIntentForPackage() 方法
- 小心 !跨站点websocket劫持!
- 小心你的机器学习债
- 升级 Dubbo,小心 default.version
- 网络协议传奇(四):小心求变演进路
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。