2012年5月24日星期四
仰视皇天白日速
这段时间也随手总结加强了几条在后台C/C++开发上的经验/教训:
1. 尽量用进程协作方式,而非多线程
2. 设计实现中,考虑对栈对象友好的方式而非堆对象
3. 异步协议,而非同步协议
4. 尽量避免对象池的应用,用优化的malloc实现代替,如tcmalloc,jemalloc等。(Boost的对象池在大量小对象(10w级)性能糟糕)
5. 通用的功能考虑拆分到独立进程中实现,其它组件通过协议/进程间调用来访问。
6. std::list的size方法不是常量时间,随list长度增加(这倒是以前没想到过的)
7. 默认的G++编译器4.0之后已经提供了部分tr1实现,如hashmap->unordered_map等。
凑足七条,算作“呜呼七歌兮悄终曲”吧。
后记:
1. 这两天读了几篇李商隐和杜诗,愈读愈发觉得积聚了不少沉郁之气。
2. 联想到当下及数月之后,竟隐有“筵席渐散”之感。
2012年5月21日星期一
尼龙,鱼玄机与薛涛
今日继续看一本介绍路易十四时代的书,读到一处介绍一个当时有名的女贵妇,甚至吸引了国王路易十四的沙龙主持者尼龙时,联想到的第一个中国类似的人物是唐代的鱼玄机,出身乐籍,交际广泛。不过却是早夭,不得善终。
回头又想起唐代的另一个女诗人薛涛,应该是更类此人。薛涛亦是出身乐籍,也和当时名流颇多唱酬应和,年高七八十才辞世。《红楼梦》中有几节提到“薛涛笺”,典故即是出自此人。
唐代还有几个类似的人物,唐末宋初似乎还有个花蕊夫人比较有名,不过过了宋代李清照之后,女性中便很少有这类才华横溢的人物出现了,在元明清几乎是绝迹的,这当和逐渐禁锢的社会思想有关。
不过,又想到《红楼梦》中描写的却是另外一番景象。难道竟是明清之时,易安居士之流皆不出深闺?
2012年5月20日星期日
风干的豹尸
为何自杀?论者纷纭,探幽者众。不过据自己观察,海明威是把答案放到了《老人与海》,《乞力马扎罗的雪》,还有另外一篇记斗牛士之死的短篇等作品中。杰克.伦敦则在半自传《马丁.伊登》里回答。
雄奇者自绝于世俗,或可言以激烈的方式超脱于俗世。
附记:
摘抄《乞力马扎罗的雪》的开头如下,一个非常出色的开头,尚还没有像《双城记》的开头那样被用至滥俗:
"乞力马扎罗是一座海拔19710英尺的长年积雪的高山,据说它是非洲最高的一座山。马基人称西高峰为'鄂阿奇-鄂阿伊',意为上帝的庙殿。在西高峰的近旁,有一具已经风干冻僵的豹子尸体。豹子到这样高寒的地方来寻找什么,没有人作过解释。"
2012年5月19日星期六
“活剥”《诗经》一句
默诵既多,兼被当天Twitter的“反动”信息浸染了一次,遂活剥生吞成“既为愤青,云胡不忿”。看起来似乎颇符一般愤青心态,兼志吾心。
最后,仍然想不起为何无由来想起《诗经》中的这一句话,颇觉奇怪。
——附注:昨天特意Google了下,发现知道此句当是从《神雕》一书中得来,而非《诗经》原文。想起此句之因仍是模糊难定。
2012年5月1日星期二
一种代替dynamic_cast的模版RTTI实现
最初的实现是模仿Netty中Java的实现,用dynamic_cast判断(Java中是InstanceOf判断),但用dynamic_cast总觉得不太优雅,性能可能也存在问题(参考附注)。
最近有些空闲时间,重新思考了下这个问题,用了另外一种方法代替dynamic_cast实现
1. 注册handler接口方法用模版方法代替,将该携带类型信息的handler注册到一个和该类型相关的模版类中,如红色框部分。当然,在handler析构时需要去注册
2. 和该类型相关的模版类用静态set保存注册信息;
3. 判断事件类型与handler相关联信息,则可调用该模版类的静态方法。时间复杂度为set的实现决定。这里用tr1::unordered_set保存指针,时间复杂度为O(1)。 从理论上应当比用dynamic_cast快的多。(测试发现新方案较dynamic_cast实现快60%以上)
附注:
1. Google的编程规范强烈建议不要在产品代码中使用dynamic_cast。
2. 据一份评测报告显示,一次dynamic_cast可能消耗数十微秒,这个评测待验证。数十微秒显然太慢了。
2012年4月22日星期日
2012年4月7日星期六
大量短连接的Server中的惊群问题
最近继续完善自己的多进程非阻塞网络服务框架arch,对于所谓的惊群(Thundering herd)的问题,查看了在这个问题上nginx源码以及其它一些非开源代码,公司内部代码中的处理方式,有以下的思考:
惊群问题(Thundering herd problem),wiki上的解释简单说是“大量处理进程/CPU阻塞在一个调用上,由于某个触发条件导致都被唤醒,但只有一个进程能继续处理,其它均失败”。惊群现象会浪费较多的CPU。这种现象可以出现在很多场景中,以下讨论的具体场景为处理大量短连接的Server中的惊群。
这种场景以Web Server为典型,一般有多个进程/线程同时监听80端口(poll非阻塞IO方式),则当有一个客户端连接上来时,所有的等待者均被唤醒,继而都调用accept处理,但只有一个能成功,其它均失败。
目前较多的实现是基本不考虑惊群问题,如检查的第一个公司内部实现代码,lighthttpd。Redis因为是单进程单线程的实现,故没有考虑此问题。
Nginx是用进程锁的方式解决该问题,即确保在某一个时刻,只有一个进程监听端口处理接收的连接;后续通过比较复杂的手段在多个进程间切换监听工作达到负载均衡的效果,仍然用到进程锁。
Nginx的实现对于我来说过复杂了。我在arch中用了一个较为简单方式解决这个问题。
arch是一个多进程的框架实现,故这里先讨论多进程模型下的一种较简单的解决方法:
1. 一个主控进程(Manager)监听指定端口,将FD加入到非阻塞IO集合中,用类poll方式监听(select/poll/epoll)
2. 启动N(N=CPU个数)个工作IO进程(Worker),建立Worker和Manager之间的通信连接(Linux下只能用UnixDomainSocket, 某些Unix可用Stream管道)
3. 以上是进程初始化时的工作,当一个连接发生时,Manager接受此链接,马上将该连接的文件描述符发送到一个Worker进程(同时关闭文件描述符),马上返回到自己的等待状态等待处理下一个连接;(这里的选择Worker进程即是复杂均衡的判断逻辑,一般来说轮询选择就可以达到较好的平衡状态)
4. Worker进程接收到文件描述符后,加入到自己的非阻塞IO集合,读取数据处理业务逻辑
同Nginx的实现一样,arch也避免了惊群现象,不同之处在于arch用到了进程间文件描述符传送,避免了复杂的锁实现;而Nginx用了进程锁,避免了文件描述符传送的系统调用;后续会比较一下两者性能区别。
多线程模型下较为简单,只需要采用类似Nginx的锁方式,结合arch的manager+worker模型:
1. 启动Manager线程,监听定端口,将其加入到非阻塞IO集合中,用类poll方式监听(select/poll/epoll)
2. 启动N(N=CPU个数)个工作IO线程(Worker)
3. 当一个连接发生时,Manager接受此链接,马上将该连接的文件描述符交给一个Worker线程马上返回到自己的等待状态处理下一个连接;(这里的选择Worker进程即是复杂均衡的判断逻辑,一般来说轮询选择就可以达到较好的平衡状态)
4. Worker线程接收到文件描述符后,加入到自己的非阻塞IO集合,读取数据处理业务逻辑。
不过,对于编写C/C++的Server程序来说,多线程应当是尽量避免的。这倒是题外话了。