灯泡接口

我以前是一个略具 geek 精神的人。现在不算了,写出来的好玩的计算机技术文章也没那么多了。虽然变无趣了,但我还有生活,所以我决定发掘一下其它的领域。生活中的琐事虽小,仔细琢磨下却有一些有趣的知识在里面。某些人从极小的时候就掌握的常识,对其他人来说可能到老都不明白。典型的例子有识别地图、指南针、手表或者分辨麦苗、韭菜等。

今天说的灯泡接口,也是类似。我从小到大,做过不少次爬上跳下换灯泡的活儿,本来觉得是一件很稀松平常的事情。但自食其力后才发现,原来也没那么简单,至少面临着一个复杂的问题:怎样选购正确的灯泡?

在我老家那个落后的小城镇里,很多东西都是二元的。免费电视信号只有两个:县台和县教育台;(铁路)地下道只有两个:东地下道和西地下道;灯泡也只有两种:(螺)丝口或者挂口。这样购买的风险很低。白炽灯泡一元一个,实在不行的话,买两个不同接口的灯泡就完了。反正在我小的时候有过买错灯泡的经历,算不得难堪。

长大后忽然发现,这世界不再是二元的了。典型的例子就是普通灯泡接口不再是两种,灯泡商品也不再是两种,当然价钱也不再是一元。去年年初,我老婆从公司带回来一个小台灯。它有一个圆盘形的底座,底座中央是一个笔直的灯杆,看起来像是一个倒立的图钉。在图钉的钉尖儿上是灯泡的接口,有一个可爱的圆柱状塑料灯罩可以把灯泡罩起来。灯泡的接口很奇怪,看起来是丝口,但又比丝口细。包装盒上的文字介绍极少,少到几乎无法阅读。在这些几乎无法阅读的文字中,我找到一个神奇的代码:E14。凭着直觉,我认出这应该是灯泡接口的型号。

我用来认知灯泡世界的模型改变了,只好重新建立模型。然后我才知道,原来我平常说的丝口,学名应该叫做“爱迪生螺旋(Edison Screw)接口” ,更确切一些,应该叫做“中型爱迪生螺旋(Medium Edison Screw)” 或者“E27”接口,即直径为 27 毫米的爱迪生螺旋接口。显而易见,除了E27,肯定还有其它的 E* 接口,例如上文提到的“E14”。此外,原来旧式手电筒上常见的小灯泡接口,也属于这一家族:“E10”。

从螺旋接口的型号上来看,中国普遍使用的是欧制接口。我不知道标准是如何制定的,但从查到的信息来看,中国最早的电灯公司是1861年英国商人办的“汉口电灯公司”,采用欧制接口可能跟我国 19 世纪首先被欧洲入侵的这一段历史有一定的关系。

与螺旋接口类似,卡口(或挂口,Bayonet Mount)也是一个家族 。我们通常家庭使用的,应该是“B22d”接口,即直径为 22 毫米,带双(double)接触点的卡口。此外还有射灯常用的“GU5.3”或者“GU10”接口,即插脚式U型接口,也属于卡口的一种。

在我看来,卡口要比丝口更安全,因为其露出的金属部分是不带电的,无意中摸到内部带电的弹簧突起比较困难。但奇怪的是周围的卡口灯座越来越少,我对这个现象非常好奇,却不知道其原因。

当然,除了上面说的这两个系列之外,还有其它的系列接口,可以参考这篇文档《灯头、灯座的型号命名方法及常用型号》 。不过其中一些,例如预聚焦式、凹点式或者汽车用灯接口,一般就只有专业人士才用得着了。

史蒂夫·乔布斯传

我一直羞于去崇拜某个人,死去的或者正在死去的。我总害怕他做了或者将要做一件我并不认同的事情,这就好比冬至吃饺子时吃到一块脏兮兮的硬币,不是每个人都能享受这种运气。所幸的是《史蒂夫·乔布斯传》的作者所持角度还算客观。读完这本书之后你不免去崇拜乔布斯,但又会有所保留。你会想:我真希望能达到他那样的成就,却不想成为他那样的人。

每个人做事都有自己的方法,但乔布斯采用的那种,恐怕要被归到异类中去。只有他那种天生的特质,结合独特的人生经历,还有过早成功的光环,才能造就那样强大的现实扭曲力场。跋扈如他那样,做一个打工仔的话,恐怕不出半月就要卷铺盖走人了。

在他的传奇一生中,有一股认真劲儿让人不能不佩服。包括他对事件策划、产品外观、字体、广告、包装,甚至于名片美学上的执念,无不让你觉得这家伙真倔,真难办。但当你转了一圈儿,看到那么多平庸的玩意儿时,你就会想:哦,这家伙还是有一定的道理的。

除了认真之外,他还有一点让我非常羡慕的,就是不屈于现实。软驱、吸入式光驱、玻璃屏幕、拉丝铝外框,到处都透着不妥协。但奇迹的是,那些硅谷工程师们居然办到了!这一点也被我归结为我们信息技术落后于硅谷的原因,奇思妙想得不到尊重,创新的阻力远大于推动力,有批判性,无建设性。

如果在这里赞扬他,能够列出的点还有很多。看完这本传记后,我甚至有去买苹果产品的冲动。但就像前面所说,我,一个平庸的家伙,对乔布斯的革命性的设计和产品,一直没有足够到转化为购买力的欣赏。创新是一样高投入的工作,必须有高回报的支持,而这依赖于掏腰包的人的欣赏和价值认可。对于某个产品来说是这样,对于一个公司来说,同样如此。可惜的是,很多公司的老板更像我,而不是乔布斯。

std::sort 的仿函数参数

因为习惯了 qsort 的函数指针参数,以前用 std::sort 的时候一般也是传函数指针而不是仿函数(functor)。从很多示例程序来看,貌似没有什么大的不同。但是直到今天我才醒悟,原来是示例太简单了啊!

具体来说,我今天遇到了一个问题:要对一个表进行排序,每个字段可能是升序,可能是降序,也有不同的类型,所以排序的时候需要根据这些信息进行比较。比较函数不能是类成员函数,但我又的确要用到类成员的信息,函数接口又不能变,着实发愁。愁了就只能 Google,发现原来仿函数可以轻松地搞定这件事情。

// 来自 http://stackoverflow.com/a/1902360
class MyClass{

   // ...
   struct doCompare
   {
       doCompare( const MyClass& info ) : m_info(info) { } // only if you really need the object state
       const MyClass& m_info;

       bool operator()( const int & i1, const int & i2  )
       {
            // comparison code using m_info
       }
   };

    doSort()
    { std::sort( arr, arr+someSize, doCompare(*this) ); }
};

简单点儿说,因为仿函数是个类,也可以有成员变量,构造的时候可以传参进去初始化,这样就能实现更灵活的比较方法。这么简单的道理,为什么之前我就是想不到呢?

此外值得一提的是,std::sort 要求比较的结果是 strict weak order,就是说要严格小于才返回 true。这就意味着,仅仅对比较结果取反,是无法实现逆序的。因为小于的取反不是大于,而是大于等于。

我们有过经验,如果相等的时候也返回 true,可能会导致某些标准库实现的 sort 函数指针越界,导致程序出错。所以要千万避免犯这个错误。

第一次香山

下午在电视上看了个《李献计历险记》,直接没把我脑壳看坏掉。之后七荤八素地躺在床上,在似睡似醒中迷瞪了一个小时,睁开眼忽然看到一幅奇怪的景象,从窗帘缝隙中透过的一点亮光在三四秒内以肉眼可见的速度暗了下去。直到现在我也不确定那是现实还是梦中,但可以确定的一点是,我随后陷入了白日梦后的各种恶心、难受和不适中。

周末就是这样,可能安排个一到两件事,其它时间睡也不是,坐也不是。徒留些无聊烦躁心情在那,如此一来就特别羡慕那些把日程排得满满的人。为了不让自己烦躁,我也在尝试找一些不那么无聊的事情去做,比如骑车爬香山。

这个念头由来已久,但付诸实施却只是昨天的事。有各种原因,懒惰,周末不愿意动弹;住的太靠里,出城太远;没有人一起玩,不认识路之类的。真正促使我成行的是微博上的一句话,做好一件事的最好方法,就是去做这件事。

在我很久很久以前拟定计划时,最大的困难就是不认识路。值得一提的是,这个困难一部分来自于没有好的地图,另一部分来自于一些所谓老鸟的行话。在香山路线中,出现频率最高的有以下几个:海二招、鬼笑石、果快、茶棚、马道等等。理解这几个名字费了我不少劲,比如海二招原来是海军第二招待所的简称,而不是海淀区第二招待所;果快原来不是地名,是果园和快活林的简称。不过,惭愧地是,我现在还是不知道这几个地方在哪儿。

有人可能觉得很习惯,但我真厌倦这些个装逼的词儿。就比如拿 5D Mark II 相机非得叫“无敌兔”,佳能 EF 50/1.8 镜头叫“小痰盂”,还有各种“小白、小小白、爱死小白”等。唉,难道就不能正正经经地说话?我以后可能慢慢地会理解这些词的含义,但我也会尽力地不去使用这些词。

再说回来,在我很久很久以前拟定计划时,是希望从珠联大酒楼-海军第二招待所-松鹤山庄那个香山入口进去,骑防火道熟悉一下香山的地形。以后有机会了再跟着别人走山地。但没成想到的是,因为什么森林公园养护完成开放之类,除正门外的其它门大多关闭不让走了。有一个老大爷在那拦各种车,行人如果不是当地居民也会被拦下。

无奈只能另想它法,有人给我说珠联大酒楼南面不远有一个山地入口。于是我就战战兢兢地从一个挂着军事禁区牌子的路口进去了,进去之后右转走到尽头有个看起来像部队的院子。大门右侧有条小路,我就很冒失地骑了进去。刚开始没有什么坡,还能骑。

山路的开始阶段
山路的开始阶段

骑没多久就到了一个挺长的围墙边,从那儿开始就有些变态了。再加上从我家到香山路上已经花了 30 公里了,体力也跟不上,于是很多坡就是推过去的了。下面是一个特别变态的地方,下降约有一米,路外侧有个大石块,内侧小沟不到一尺宽,还拐个弯儿。我觉得如果不从大石头上飞过去,走内侧不倒也得磕牙盘,只好无奈地扛着车过去了。

变态小坡
变态小坡

实话说,走的这段山路我是又累又怕,我这种平原长大的孩子哪儿骑过这种路呀?左侧是石壁,右侧是山崖,掉下去即使有小树挡着,也好不到哪儿去。我是好不容易捱到了防火道的出口啊!这个出口外面应该是梅园。

防火道-梅园
防火道-梅园

回来后我回顾了一下这段上山线路,我估计我走的可能是果园的上山路线,但是后来可能走岔了,所以才从梅园上了防火道。中午到下午这段路上太阳不多,基本上都在阴影下,不会很热。虽然看到防火道很兴奋,但无奈地发现自己已经木有力气了,于是只稍往上爬了一点儿,就溜车下去了。

不知名建筑
不知名建筑

唉,本来想继续写点儿的,被一个工作电话给弄烦了,罢了。

TCP Fast Open by Google 浅析

Google 将在今年 12 月的 ACM CoNEXT 会议上发表他们在改善 Web 应用响应时延方面的一个工作,通过修改 TCP 协议利用三次握手时进行数据交换的“TCP Fast Open”。虽然 paper 是两天前才释出,但相关的 RFC 草案则早在 2011 年 3 月份就提交到了 IETF,并且在两周前进行了一次 UPDATE,这里是 DIFF

对于 TCP Fast Open 方案的内容,淘宝的一个朋友已经根据 RFC 草案进行了解读。我就不再赘述,感兴趣的朋友可以去看 paper 或者 RFC。我这里只是想讨论一下这个东西的应用前景。

由于对背景并没有做深入了解,我相信已经有很多人尝试去做过类似的工作,但我想类似的工作应该没有得到过大规模的应用。对于已经成型很久很久的 TCP 协议,让人很难有修改它的欲望,因为改那么底层的东西意味着很多很多的麻烦。

但是是否愿意付出代价,有一个前提是有没有足够的好处。TFO 给出的好处是:在 RTT (Round Trip Time) 比较低时,客户端页面加载时间优化大约在 4%~5%;RTT 越长,好处越大,平均大约在 25%。

Google TCP Fast Open Evaluation
Google TCP Fast Open Evaluation

除了页面加载变快改善了用户体验之外,TFO 给服务器端也带来了一些好处。由于每个请求都节省了一个 RTT,相应地也减少了服务器端的 CPU 消耗。paper 中给出的数据是每秒事务数由 2876.4 上升到 3548.7。

虽然 paper 中大部分时间在强调 TFO 对 web 页面加载的显著加速作用,但我认为即使 TFO 能成为互联网标准,它目前的状态离成为标准还有很长一段距离,因而在短期内它是无法影响到主流互联网世界的。但这并不意味着它没有机会,依小弟的愚见,目前它的推广应用可能有两个方向:

1. 移动互联网。移动互联网的 RTT 目前远大于传统互联网(常理推测,需数据支撑),因而一个 RTT 节省的效果无法被忽视;另外移动互联网终端操作系统多样化,不像桌面终端系统那么单一。 Google 自己就掌握着其中一个很重要的 android,百度也计划推出自己的“易平台”。这些互联网公司有动力去改善移动用户访问自身网站的用户体验。

2. 互联网企业数据中心。虽然数据中心内部访问时延很低,但对于典型的请求/响应的服务而言,减少一次 RTT 带来的好处还是有吸引力的,最起码能减少计算能力浪费和增加吞吐吧。再加上很多企业内部使用的都是定制的开源操作系统和定制的网络库,升级的代价并不是那么高。如果我是企业基础设施的负责人,我想我会很慎重地考虑这个方案的。

代码行统计工具-CLOC

在工作中有时会有需要统计代码的行数,一般会用 wc 给出一个大致的结果。只不过在源代码文件分布比较分散,且存在多种不同类型语言的源代码时,wc 就不是特别适合了。

在公司内部也见过一些同事实现类似功能的脚本,但我想这应该是一个通用的需求,于是就找到了这个工具 - CLOC。其实就是一个 perl 脚本,很好用,统计报告也很清晰。在这里推荐一下。下面是一个统计 leveldb 源代码行数的例子。

$ cloc .
     128 text files.
     123 unique files.                                          
     353 files ignored.

http://cloc.sourceforge.net v 1.55  T=0.5 s (238.0 files/s, 46718.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C++                             60           2012           1258          13124
C/C++ Header                    52            968           1458           2690
HTML                             3             84              0           1094
C                                1             33              7            255
make                             1             43             17            153
CSS                              1             10              1             78
Bourne Shell                     1              9             19             46
-------------------------------------------------------------------------------
SUM:                           119           3159           2760          17440
-------------------------------------------------------------------------------

关于自动分裂的思考

自动分裂是分布式系统中的一项重要技术,通常与自动迁移和负载均衡一起考虑,提供了系统的可扩展性和良好的性能。例如 Google 的 BigTable 和 Yahoo 的 PNUTS 都实现了类似的功能,我之前也认为这应该是一个好的分布式系统标配。

读了 Facebook 关于实时 Hadoop 的文章后,结合我自己在工程上的实践,我开始反思这一想法,认识到了这个功能的一些局限性。

Facebook 在打造实时 HBase 系统时,放弃了 HBase 提供的自动分裂,而专门开发了手工分裂功能。对此, Facebook 的解释是:

  1. 由于业务数据的均匀增长性,所有子表可能在相近的时间触发自动分裂,导致分裂风暴;合理安排的手工分裂可以避免这一情况,减少对生产环境的影响。
  2. 手工分裂时在某个时间,子表的数目是稳定的,有利于进行调试和调优;自动分裂时很难把握住系统中子表的变化。
  3. 在对日志文件问题进行后期处理时,子表没有分裂比有分裂要容易处理很多。因为应用日志到子表上时不用考虑是否已经分裂。

Facebook 给出的三个原因是非常合理的,我也很赞同,但我想补充一下我对自动分裂局限性的两个考虑:

  1. 较难进行事故影响评估。对于一个严肃服务来说,发生系统事故时不仅要求尽快恢复,更为紧迫的要求是迅速给出影响评估。手工分裂时运维人员对系统中子表的分布情况有着更好的了解,能够更快地做出评估(而且一般影响面也可控一些)。
  2. 较难进行数据恢复。当子表数据出现问题,或者数据源本身就有问题,要进行数据恢复时,手工分裂一方面能够准确地定位错误数据的位置,另一方面便于进行错误数据的处理(后台直接替换错误文件等,不单指 HBase)。而自动分裂时寻找错误数据位置本身就比较麻烦,由于子表可能一直在变动中,对错误数据进行处理也不容易。

从上面列出的几点来看,使用、改造或者实现一个分布式系统时,不能仅仅考虑方案是否漂亮,还要考虑到该系统的具体应用场景。脱离了应用场景的系统实现,如同漂亮的水果,吃起来不一定甜。但令人感到讽刺的是,漂亮的水果一般比较贵。

青岛行记

在种种客观、主观条件下,我和媳妇儿同时决定上周休假一周散散心。选在十一之后,主要是为了人少点儿、住店便宜点儿和玩的舒服点儿。玩的舒服点儿主要是指,十一我还得值班处理服务器告警,但十一之后别人都在上班,我就可以放心地玩了。

之所以选择去青岛,主要是因为某个人太懒,对爬山和走远路很抵触,只好到海边溜达。我在去哪儿上团购了个旅馆,一天才七十块,这样能够比较放心地多住几天。

在青岛的日子是比较闲适的,每天睡到中午才起床。起来后就背个小包去逛,逛到哪儿算哪儿。没有奢求逛完所有的地方,但是由于时间充裕,基本上有意思的地方也都去看了下,唯一没去的就是崂山...

上班以后,工作赚钱成了主要目的,即使是下班回到家,也时不时地要处理一些工作上的事情,因而平时感觉生活完全被工作给充斥了。真正抛开所有事情,手机调成静音,才回归到了比较简单的状态,心情畅快不少。

我从青岛回来之后,看了一个名为《硬汉2》的电影。整个故事是在青岛取景拍摄的,一开头就是青岛天主教堂,熟悉的画面让我感觉好生激动。想要休闲的话,在家里宅着也未尝不可。但到一个陌生的地方总是一个更优的选择。在那里,困扰你的不再是日常的琐碎小事,而是完全不同的问题域。除此之外,还有增长见闻的好处,这一点对我是颇有吸引力的。特别是在当地能够发生一些有趣的、甜蜜的、或者忧伤的故事,就再好不过了。

我不太善于描述旅行中的人和事,再加上到青岛玩纯粹是休息,没办法作为攻略分享。只好挑几张图片出来,随便看看吧。

《三体三部曲》赠书计划

上周我又重读了一遍《三体》系列,在路上,用 Kindle。

还记得原来《三体I》好像是在《科幻世界》上连载读完的;《三体Ⅱ》曾经买过一本书,不知道放哪里了;《三体Ⅲ》是读的 txt 电子版。这次重读,看的是网友精心排版过的三体系列 PDF 电子版。

第一次读时主要是跟着情节往前追,急切地想知道后面会发生什么,看重的是酣畅淋漓。这次重读,更注意细节和推理,故事情节发展的脉络,人物感情和心理的变化。虽然重读时对书中一些地方产生了疑惑,但我觉得和整个故事的图景来比瑕不掩瑜,况且还有可能是我没有想透呢?

《三体》三部曲是一个好的科幻故事,好到当想到它描述的宇宙有可能是真实的时,忍不住会打个冷战。刘慈欣是一个非常杰出的科幻小说家。在我眼里,相信也是在很多科幻迷眼里,他代表了中国当代长篇科幻小说的最高水平。我很惭愧阅读电子版小说侵犯了大刘的权益,但我实在对收藏实体书无爱,书架已经够沉了。因而我想赠出一套《三体三部曲》给我的博客读者,一来我的良心能够稍觉安稳,二来希望对扩大国产科幻的影响力有所助益。

获得这套赠书的方法很简单,你只需要在评论中留言说明你想要这套书的理由:

我想要《三体三部曲》,因为...

我会选择喜欢的一个理由,然后根据你留下的电子信箱联系你,通过某个电商(卓越、京东之类)直接发货给你。这不是广告,不是福利,也不是读后感评比,没有任何客观性,一切都凭我的主观感觉,所以对于没拿到的同学我最后只能表示遗憾。

理由可以随便写,我相信应该不会有人为了这几十块钱的书而费劲脑筋来编故事。此外,我不会考虑认识的朋友,世界已经很小了,我们有必要把它搞得更小吗?还有,仅限大陆的朋友,不然运费都可以买好几套了...

考虑到我博客的影响力,应该不会有太多理由让我挑选,那么等待的时间就稍微长一点吧。最后的时间放到 2011 年 10 月 25 日,如果到时还没有我喜欢的理由出现,我就从已有的留言中随机挑选一个送出。

★★★★★
书已经送给来自浙江杭州的 Arcthur 同学
★★★★★

Leveldb 编译错误背后的C++标准变化

在编译 Levedb 时,我遇到了这个错误:

g++ -c -I. -I./include -fno-builtin-memcmp -DLEVELDB_PLATFORM_POSIX -pthread -DOS_LINUX -O2 -DNDEBUG db/version_set.cc -o db/version_set.o
db/version_set.cc: In member function `void leveldb::VersionSet::Builder::Apply(leveldb::VersionEdit*)':
./db/version_edit.h:100: error: `std::vector, std::allocator > > leveldb::VersionEdit::compact_pointers_' is private
db/version_set.cc:461: error: within this context
...

在网上容易搜到解决方案,由于归根结底是访问控制问题,方法是把所有涉及到的的 private 变量或类型修改为 public。由于不是所有的编译器都会报错,我就很好奇产生这个错误的根本原因。

BTW: 一种不修改代码的 work around 方法是,在编译这个文件时加上 -fno-access-control 参数,这样 g++ 就不会进行访问控制检查,自然也就没问题了。这个参数同样可以用于对 private 成员函数进行单元测试。

简单地分析一下这个错误。发生错误的地方是在 VersionSet::Builder 这个类的成员函数中,而错误则是其成员函数无法访问 VersionEdit 和 Version 类的私有成员变量。VersionSet 是 VersionEdit 和 Version 类的友元类,Builder 是 VersionSet 的嵌套类。简化一下,代码如下所示:

class VersionSet;

class VersionEdit {
    friend class VersionSet;
    static int compact_pointers_;
};

class VersionSet {
    class Builder {
        int foo()
        {  
            return VersionEdit::compact_pointers_;
        }  
    }; 
};

把这段代码拿给编译器去编译,g++ 3.4.4/5 会报类似的 `int VersionEdit::compact_pointers_' is private 错误,但是 g++ 4.5.3 则能够编译通过。

由于 VersionSet 是 VersionEdit 的友元类,那么 VersionSet 是能够访问 VersionEdit 私有成员的,这样问题就集中在 Builder 是否能够获得与 VersionEdit 的友元关系。如果语法规定嵌套类 Builder 能够从 VersionSet “获得”友元关系,那么 Builder就能够访问 VersionEdit::compact_pointers_,反之就不能访问。

在 C++98 标准中,关于嵌套类的权限有如下描述:

$11.8/1 [class.access.nest],

The members of a nested class have no special access to members of an enclosing class, nor to classes or functions that have granted friendship to an enclosing class; the usual access rules (clause 11) shall be obeyed. The members of an enclosing class have no special access to members of a nested class; the usual access rules (clause 11) shall be obeyed.

Example:

class E {
    int x;
    class B { };
    class I {
        B b;                 // error: E::B is private
        int y;
        void f(E* p, int i) {
           p->x = i;         // error: E::x is private
        }
   };
   int g(I* p)
   {
       return p->y;          // error: I::y is private
   }
};

但是在 C++11 中,这段描述变更为:

$11.7/1 Nested classes [class.access.nest]

A nested class is a member and as such has the same access rights as any other member. The members of an enclosing class have no special access to members of a nested class; the usual access rules (Clause 11) shall be obeyed.

Example:

class E {
    int x;
    class B { };
    class I {
        B b;                  // OK: E::I can access E::B
        int y;
        void f(E* p, int i) {
            p->x = i;         // OK: E::I can access E::x
        }  
    }; 
    int g(I* p) {
        return p->y;          // error: I::y is private
    }  
};

从上面的描述和示例代码对比中我们可以明显看出,在旧标准中嵌套类和“被嵌套类”没有什么特殊的关系,就像两个普通类一样;但是在新标准中嵌套类已经完全视为“被嵌套类”的成员,那么自然也获得了“被嵌套类”成员应该有的访问控制权限。这也就意味着“被嵌套类”的普通成员拥有的访问“被友元类”私有成员变量的权限,嵌套类也能够获得,那么 Leveldb 在新版本的编译器下能够编译通过也不足为奇了。

不过 gcc3.4 的编译错误问题还不能单单归究于标准的变化。因为 gcc3.4 已经能够支持嵌套类访问“被嵌套类”的私有成员(因为在很早以前这就被确认为一个缺陷),只是不能够支持友元关系到嵌套类的传递。友元关系的传递可能是在 4.1 或者 4.2 版本中实现的,应该属于上述标准变化的衍生特性。

通过科目三路考

前天晚上通过科目三路考,我为期半年的驾校学习总算结束了。

想起来这半年也是折腾不断。我是 2 月底参加水木团购版的东方时尚驾校报名团购,3 月中旬考科目一。之后因为懒得约模拟机,光模拟机 6 个小时花了三周时间才上完。后来从淘宝上买了个约车软件,专门用来约周末的散段,效率还挺高的,也就三周就把散段上完了。后来科目二考试也算挺顺利。只是5月该考科目三的时候忽然忙起来了,工作上各种事情,再加上天气热,就把科目三考试拖了下来,一直拖到 9 月。

工作以后考驾照呢,我觉得主要还是得看执行力。执行力高的,两个月以内就能搞定,像我这样执行力差的,光拖就能拖四个月。我自己的一个特性是有压力的时候不希望分心。前段时间在做一个比较大的升级,对我个人的设计能力、架构把握和编码能力都是一个挑战。再加上合作半年的一个同事离职,新加入的同事也不太熟悉,我只好一个人吭哧吭哧把剩下的事情做完。

不过这个项目让我很有成就感!重构了一个 2 万多行的模块,精简到不到 1 万行;重整了 4 万多行代码,架构上进行了很大的调整,脉络上清晰很多。另外也加了些新功能。但是由于我负责的系统涉及产品线太多,平时工作中新需求、沟通、线上问题处理等日常事务占到一大半,很多时候只能加班写代码。这样大的工作量让人有些筋疲力尽。现在项目完成了,我也想休息休息调整一下,就把后续的编码工作让给同事去做,我下面这个月主要任务就是跟进测试和校园招聘的工作了。

算了,又跑题了,最后做个广告吧:如果 2012 应届生同学对百度提供的工作机会感兴趣的话,欢迎邮件简历给我!优秀的朋友我可以帮助内部推荐,相信你知道在哪里能找到我的邮箱地址 :)

双人桌游体验

比较惭愧地说,最近有点儿迷上了玩桌游。上下班地铁上有时候都会一直玩手机上的安卓游戏《Catan》(卡坦岛)。xixi 自从体验过桌游《三国杀》、《只言片语》后,也成天嚷嚷着要参加我们的同事聚会玩游戏。只是从家到单位的距离实在太远,只有进城 building 的时候才有机会一起玩玩游戏。

由于比较经典的桌游大部分需要三人或以上才可以玩,除非是合租的朋友,不然在家很难凑齐人。于是我就略微探索了一下适合双人玩的游戏,淘宝买了三款,《Lost Cities》(失落的城市、遗失的古迹)、《Carcassonne》(卡卡颂、卡卡城)、《Hailli Galli》(德国心脏病)。下面简单介绍一下初步的体验,供同样喜欢桌游的小情侣儿们参考 :)

《Lost Cities》是一款卡牌游戏,主线是两个人去竞争开发五座古城,只能两人玩。卡牌分两种,一种是赌注牌,每个古城对应三张相同的赌注牌,以颜色区分;一种是开发牌,每个古城对应 9 张从 2~10 的开发牌,以颜色区分。限制有 3 个:1. 一旦决定开发某座古城,首先付出 20 点的成本;2. 每张赌注牌能够提高一倍的收入/损失,但是赌注牌必须先于开发牌去开发古城;3. 开发牌必须按照点数从小到大用于开发某座古城。

规则就不详细描述了,主要谈谈体验。《Lost Cities》的双人对抗性还是很强的,每个人必须规划好自己的出牌顺序。如果刚开始出的点数大,那么后面点数小的牌只能弃掉了,弃牌可能被对方拿走;赌注牌也是这样,如果先出开发牌,赌注牌也只能弃掉;牌不好的话,开发的城市还不能太多,否则可能会亏本;追求的有一个目标是某座城的赌注牌+开发牌数大于等于 8 张,这样可以有 20 点的奖励。可能对结局影响比较大的状态是摸到开发牌点数的大小,如果某个人摸到的大部分是大于 5 的开发牌,那么赢的概率就会高很多。总的来讲,这个游戏的可玩性还是很强的,看似强势的一方到最后结算时发现分数反而更少也是有可能的。

《Carcassonne》是一款纸牌建造游戏,和拼图类似,适合2~6人。所有的纸牌都是正方形硬纸板,上面有河流、城市、寺庙、草原等场景。两个人轮流抽牌进行拼图,每个人有 7 个人偶(Meeple)。人偶可以放置在未建造成功的场景上,一旦建造成功,就可以根据场景不同结算分数,人偶被释放走去占领其它未建造成功的场景。

这款游戏的计分规则还是比较复杂的。如果多人玩,由于人偶太多,可能必须考虑复杂规则。但是两人玩的时候我觉得可以简化一些,比如我们玩的时候根本没有考虑农场场景和不允许二人占领同一场景。除了进展比较慢,游戏还是挺有意思的,而且值得一提的是,这种拼图类的游戏一般比较讨女孩子喜欢...

《Hailli Galli》也是一款纸牌游戏,规则更简单,适合2~4人。先均分所有纸牌,每个人轮流出牌,放到面前的自己弃牌堆上。一旦某个出牌触发了某个规则,所有人去抢按中间的铃铛,先抢到的就拥有所有人的弃牌堆,最终赢光所有人牌的人获胜。规则简单到什么程度呢,纸牌内容就是水果组合和动物,两张花色一样的牌需要抢铃、出现猴子时,台面上没有柠檬时抢铃、出现大象时台面上没有草莓时抢铃等等。好幼稚啊!我觉得把它称为家庭桌游更合适。

不过,话说回来,如果你想光明正大地摸摸姑娘的小手,强烈推荐这款游戏!因为大家都去抢按一个铃的时候,嘿嘿,你懂的!此外,估计也适合同事之间的破冰活动,大家有了肌肤之亲,自然距离感就会少一点儿~~~

承德行记

来北京几年,周边去的地方也不多。今年大组 building 选择了承德,那么自然愿意去到彼一游了。由于是跟团去,游记攻略可说的不多,就以照片为引,流水一番吧。

这是避暑山庄大门,从这样的景象中可以看到暑假里的承德是多么的人山人海,据导游介绍说避暑山庄每天要接待 10 万人。反正心理预期是早就有的,对这样的情况也没什么可抱怨的。

避暑山庄大门
避暑山庄大门

下面这张应该是正门,康熙爷写的“避”字多了一横。

避暑山庄牌匾
避暑山庄牌匾

在纷纷扰扰之中,偶尔也会有几片宁静。

小伙子你肿么了,是在练习蹲坑吗?

等待游览车中
等待游览车中
二马道上遥望小布达拉宫

烟雨楼附近避暑山庄的湖光山色真是太漂亮了,即使在那么多游客的情况下也掩饰不住它的美丽。什么故宫、北海、颐和园都是浮云啊!遥想当年,我不禁发出一句响亮的评论:“操!”

避暑山庄烟雨楼

小布达拉宫地方小,游客密度较避暑山庄更胜一筹。拍出来能看的照片不多,下面这个是大红台的近景。

普陀宗乘之庙(小布达拉宫)大红台近景

从外面看大红台和一堵墙似的,以为里面会是很窄小的空间,但事实上却是别有洞天。

大红台内院西北最高处
一窗一世界

虽然与小布达拉宫相比略小,但我觉得班禅行宫的金顶还是挺霸气的,因为上面有八条龙!

须弥福寿之寺大殿金顶
须弥福寿之寺红墙一角

最后再从另一个角度看看小布达拉宫,看看你能发现什么!

照片正中那棵树,放大了看,有没有觉得很眼熟?哈哈 :)

虽然被赶着只能走马观花地看,虽然游人多到恨不得马上买票回家,但总的来说,这次承德游还是长了不少见识。我自己的体悟主要在园林和建筑上,皇家园林和寺庙的确是费了不少的心机在里面。廊庑婉转、雉堞衔连,起承转合,相当精妙。很多地方都有让人柳暗花明、豁然开朗的感觉。风景实在是太好了!若有机会穿越回清朝,在里面倘佯几回,岂不快哉?

技术人员的眼界

意识到眼界的重要性,最初是在大学时学长的交流会上。南大数学系有一个传统,每年总有那么两三次组织高年级的同学开经验交流会。这些交流会可能有明确主题,例如留学或是找工作,也可能没有明确主题。幼稚如我,在大一阶段拒绝参加任何形式的社团或者活动,认为踏踏实实做好眼前的事情足矣,闲暇时间基本花费在小说上。后来的种种经验证明,这是多么傻的一种做法!

我在数学方面是一个资质一般的学生,是几位师长和朋友打开了我在计算机行业的眼界,得以投身到信息技术的洪流中。写到这里忽然觉得有变成回忆长文的迹象,就此打住,几位是谁就不介绍了。唯一值得一提的是,李开复先生也是其中之一,这也是我从不参于对他的争议和讽刺话题的原因。在帮助和启发中国学生方面,我觉得他是一个值得尊敬的人。

但是在进研究生院之后,我自己闭塞了眼界的发展。一方面有当时身处的环境使然的原因,另一方面有成绩羞见江东父老的自闭自卑成分。非常后悔的有两点,一是虽然在某个研究专题方面有所收获,但在计算机科学系统性知识方面的进展不够大;二是身处远离实业的象牙塔,虽然亲身经历了一次严重的经济危机,却没能趁机深入地去学习和观察隐藏在这场危机之后的经济和科技发展规律。

我用来勉励自己,有时候也用来鼓励他人的一句话是:“只要你读的是自己不知道的东西,那就是在进步。”因而我经常不择食地去读一些书,了解一些知识。但渐渐地认识到了一点,当你着手去学习时,眼界决定了你汲取知识的效率和效果。具体到计算机科学,眼界可能决定了你读的书是经典还是垃圾,花时间在过时的还是新兴的技术上。

这个周末在家看吴军先生的《浪潮之巅》,爱不释手。作为一个曾经拜读过 Google 中国黑板报上“浪潮之巅”系列连载的读者,我本以为这会是本信息企业史,但阅读后发现新增的三分之一内容更精彩。我不曾想过一个技术人员的眼界会如此之广,能够从各个角度去观察和分析身处的这个行业、这个时代。我不否认对于书中的一些观点我并不完全赞同,但作者的眼界和思考能力着实让我佩服。

现在我已经初步开始自己的职业生涯,未来该何去何从,心中有一些惶恐。该打造哪方面的能力,亦不知该如何着手。有一些短期的规划,但并无具体的长远目标,我知道自己又到了一个眼界的瓶颈期。都说中国的年青人被房子票子绑架了,我希望自己能在忧愁这些之外,分些时间来读读好书,交交优秀的朋友,想想深刻的问题,聊以自勉。

2011年13月32日24点60分60秒

这是一个测试时候发现的问题,2011 年 13 月 32 日 24 点 60 分 60 秒,这个时间存在吗?

我觉得这应该是个错误的时间,但很不幸地是,如果往 struct tm 中填入这个时间,mktime() 时并不会报错。下面是一段简单的测试代码:

#include <stdio.h>
#include <string.h>
#include <time.h>

int main()
{
    struct tm t;
    memset(&t, 0, sizeof(t));
    t.tm_year = 2011-1900;
    t.tm_mon = 13-1;
    t.tm_mday = 32;
    t.tm_hour = 24;
    t.tm_min = 60;
    t.tm_sec = 60;
    printf("tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, tm_sec=%d\n",
            t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
    time_t tt = mktime(&t);
    printf("%s", ctime(&tt));
    printf("tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, tm_sec=%d\n",
            t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
}

这个程序执行的结果是:

$ ./a.out  
tm_mon=12, tm_mday=32, tm_hour=24, tm_min=60, tm_sec=60
Thu Feb  2 01:01:00 2012
tm_mon=1, tm_mday=2, tm_hour=1, tm_min=1, tm_sec=0

查看 man mktime,的确有对这个特性的解释:

...if structure members are outside their valid interval, they will be normalized
 (so that, for example,  40  October  is  changed into  9  November);...

这就意味着,对输入时间做严格的检查无法依赖于标准库中的 mktime() 函数,只能自己来进行。这应该也是 date 命令曾经改进过的地方。

$ date -d "2011-13-32" # date (coreutils) 5.2.1
Wed Feb  1 00:00:00 CST 2012
$ date -d "2011-13-32" # date (GNU coreutils) 8.5
date: invalid date `2011-13-32' 

也谈地铁迷药

这是篇临时起意的文章。最近经常看到有人讨论“地铁迷药”或者“地铁迷药辟谣”,很多人抱着宁可信其有,不可信其无的态度。而且还有人在微博上、论坛上吐槽,怀疑是被下迷药了,吐槽当时怎样怎样头晕,怎样怎样难受。

简单来说,我的基本态度是“信其无”,但却无法证明“其无”。所以我不讨论它到底有没有,只是就我自己的“丰富”经历,谈谈头晕、恶心、难受、两眼发黑和迷药不一定有关系。

我站着不动久了容易头晕,这是小时候就知道的事情,但至今不晓得是什么原因导致的。高中之前都是我父亲用推子给我理发,他个子比较高,所以我必须得站着让他理发才舒服。我一般站个二十分钟上下,就会头晕恶心,必须得蹲一会儿才能继续站。因而我从小怀疑自己心脏有问题,但后来发现自己足球、篮球、羽毛球各种剧烈运动都不怕,检查也没出过问题,这个怀疑只能不了了之。

长大之后,好了一些。不过大一结束时军训站军姿的时候,我晕倒过两次。晕倒的过程很奇妙,最开始是胃疼,但不是疼得受不了那种,就是悠悠地疼,然后头开始晕,觉得四周景物有点儿晃,然后觉得呼吸困难,接着是两眼开始冒金星(很奇妙,真的!),最后一黑就倒了。值得一提的是这种晕倒不是毫无知觉那种,即使是倒地,仍然有意识觉得自己要倒了,因而没有发生“咚”的一声以头抢地的情况。晕倒以后被同学扶到树荫下,喝点儿水,大概十几分钟就能缓过劲儿来。

来北京之后的第一个夏天,在 619 路玉泉路到中关村的公交车上晕过一次。当年的 619 是很破的公交车,而且很难占到座位。我站着站着忽然觉得军训时候晕倒的症状来了,而且还伴有恶心的症状,久病成良医,马上蹲下靠着椅子大口喘气。坐在椅子上的女孩儿看我状况不对,连忙把座位让给了我,我坐在那趴了好大一会儿才缓过来。

后来还有一次,是和女友从北海公园回来的公交车上晕的。几路不记得了,应该是北海公园西面某站到知春里东站的线路,过新街口的。那辆车也是那种两节的老车,大热天的闷得透不过气,我就站在两节车厢的连接处。上车感觉还挺正常的,大半程没啥问题,快到了开始头晕。也是赶紧蹲下,女友扶着,忍到下车。下车后直奔中关村海关旁边的麦当劳去买冷饮,在里面坐了好久才敢出来。

前面这几次都应该是天热闹的,后来有一次估计是因为空调太冷导致的。研究生毕业最后一天搬家,天很热,把行李搬到西二旗智学苑租的房子后,到中科院奥运园区找同学吃饭。吃了驴肉火锅,喝了啤酒,略微有点儿上头,就直接去坐地铁回家了。8号线大家应该知道,奥运支线,人特别少。一进地铁我就觉得冷风呼呼灌得我不舒服,等倒了两趟到知春路换乘,两腿都已经软了。我忍着头晕难受坐到西二旗站,头重脚轻,两腿飘飘回到家中,闷头躺床上就睡,第二天一点事儿都没有。

我的这几次遭遇里面,真正晕倒的是在军训场上,因为没法采取什么措施。其它情况下都是蹲下或者坐下,扶着什么东西尽量让自己舒服一些。

我仔细回想过,我头晕基本发生在暑期,或者太阳直射的情况下,因而我认为是体质不耐热导致的中暑症状。至于迷药,虽然好多次头晕发生在公共交通工具上,但我从来没怀疑过,因为根据我的经历根本不可能想到迷药上去。

Facebook的实时Hadoop系统

Facebook 在今年六月 SIGMOD 2011 上发表了一篇名为“Apache Hadoop Goes Realtime at Facebook”的会议论文 (pdf),介绍了 Facebook 为了打造一个实时的 HBase 系统使用到的独门秘技。由于该论文提到的应用场景与小弟负责的系统要解决的问题域有相似之处,因而抽时间仔细阅读了这篇论文。下面便是结合论文的内容,谈一谈我的一些看法和感想,如有谬误,敬请指正。

这篇 10 页的长文主要的内容是 Facebook 在 Hadoop 系统上的工程实践,这些工程实践的目标则是题目所点出的——实时。虽然缺乏 Hadoop 系统的开发或使用经验,但是我觉得并没有妨碍我对这篇论文的理解。在我的脑子里,HDFS 就是 GFS,HBase 就是 BigTable。它们实现上可能有差异之处,但主要的思想应该是相通的。如果熟悉 GFS 和 BigTable 那两篇文章,这篇文章就可以视为 GFS 和 BigTable “进阶”。

1. 应用场景和需求

文章的最初是一些背景介绍,主要给出了三类应用场景:Facebook Messaging、Facebook Insight 和 Facebook Metrics System(ODS)。Messaging 就是 Facebook 的新型消息服务,Insight 是提供给开发者和网站主的数据分析工具,ODS 则是 Facebook 内部的软硬件状态统计系统。这三个应用场景都有各自的特色,但简单地来说,面临的问题是同样的:单机或者拆分的关系型数据库无法满足需求。

基于应用场景的数据特征,Facebook 抽象出了几个对存储系统的需求。由于描述起来有些复杂,例如 Efficient and low-latency strong consistency semantics within a data center,这些需求就不一一列举了。相比需求,更让人感兴趣的是它的那些“非需求”,总共有三条:

  1. 容忍单数据中心内部的网络分化,Facebook 认为这个问题应该从网络硬件层面(做冗余设计)而不是软件层面去解决;
  2. 单个数据中心宕机不影响服务,Facebook 认为这种灾难很难发生,因而愿意接受这种风险;
  3. 跨数据中心的数据热备服务能力,Facebook 假设用户数据是分配到固定的数据中心的,可能带来的响应延迟问题应该通过缓存来解决。

从这些“非需求”上可以看出,Facebook 考虑的是更实际的情况,而不是一个理想中的分布式系统,在这点上有一定的借鉴意义。

根据以上的需求和非需求,Facebook 自然而然地给出选择 Apache Hadoop 这套系统的理由,其中有社区的成熟度、Hadoop 在一致性、扩展性、可用性、故障容忍、读写效率等等的各项优点,这些方面的优点也是有目共睹的。

2. 打造实时的 HDFS

HDFS 本身设计来支持离线 MapReduce 计算的分布式文件系统,虽然在扩展性和吞吐上有很好的表现,但在实时性方面表现并不好。如果想让基于 HDFS 的 HBase 有更好的性能,HDFS 层的优化是不可避免的。为了把 HDFS 打造成一个通用的低时延文件系统,Facebook 主要做了以下一些优化。

2.1 实现 NameNode 的高可用——AvatarNode

HDFS 的 NameNode 是系统单点,就意味着 NameNode 挂掉会导致系统的不可用。NameNode 重启时加载内存快照、应用log和收集 DataNode 的数据块信息报告大概需要 45 分钟。即便使用了 BackupNode,仍然需要收集数据块信息报告,切换的时间仍然可能大于 20 分钟。但有实时性需求的系统一般都会要求系统 24x7 的可用性,因而 Facebook 对单点的 NameNode 进行了改进,实现了 NameNode 的双节点热备,称为 AvatarNode,如下图所示:

AvatarNode
AvatarNode

简单地来说,备份 AvatarNode 通过 NFS 读取并回放主 AvatarNode 的事务日志来保持数据的同步,并同时接收 DataNode 的数据块信息报告,这保证了主备 AvatarNode 的数据差距尽可能地小,使得备份 AvatarNode 能够很快地切换为主节点的角色。主备 AvatarNode 的角色是注册到 ZooKeeper 中的,DataNode 可以根据 ZooKeeper 中信息判断需要服从哪个 AvatarNode 节点的指令。

为了实现热备 AvatarNode 的数据同步和易用性,Facebook 还改进了 NameNode 事务日志,并部署了 DAFS (Distributed Avatar File System) 屏蔽了 AvatarNode 的故障切换,使得这些改变对客户端透明。文中并没有提到 AvatarNode 的切换是手工还是自动进行的,但是考虑到 ZooKeeper 的 lease 机制,自动切换应该不难实现。

2.2 Hadoop RPC 兼容性和数据块可用性

在之前的系统需求中,有提到一点是 Fault Isolation,并且 Facebook 的 Hadoop 系统是在单机房部署的,因而同一个服务必然会使用多套 Hadoop 系统。为了系统升级独立方便,使客户端兼容不同版本的 Hadoop RPC 是自然而然的事情。

HDFS 在分配副本数据块位置时,虽然会考虑到机架位,但整体来说仍然是相当随机的。其实我以前也曾经与同事讨论过类似的问题,到底是选择随机分配副本位置,还是使用一定的组策略去分配。随机分配的好处是简单均衡,坏处是一旦发生多台宕机,由于副本随机分布,导致某块数据副本全部丢失概率很大;用一定的组策略去分配的好处是多台宕机如果不发生在同一组里,不会丢数据,但是一旦多台宕机发生在同一组,会丢很多数据。看来 Facebook 是选用了组策略分配的方法,认为多台宕机发生在同一组的概率不大。

但这样做是否正确,我是有疑问的。同一个机架或相邻机架上的服务器一般上架时间、硬件型号等都相同,那么同时发生故障的事件不是完全独立的,其概率是要大于理想故障分布情况下概率的。我想这也是为什么 Facebook 最终方案中一组机器是 (2, 5),2 个机架,5 台服务器。这两个机架的选择,如果很谨慎的话,能够尽量避免我说的这种情况。不过,凡事还得看执行力,如果不了解部署情况去选择机架的话,不一定能够达到预期效果。

2.3 实时负载的性能优化

除了上面的改动之外,Facebook 还对客户端的 RPC 过程进行了优化。为 RPC 添加超时机制,加快文件 lease 的撤销速度(由于对 HDFS 文件操作不了解,我没明白为什么要加快 lease 撤销)。

此外,还提到了最重要的一点:局部性!Facebook 增加了一个检查文件块是否在本机的功能,如果在本机就直接读取。不知道它具体实现方式是怎样的,但我觉得这个做法其实是“很黄很暴力”的,不知道会不会破坏数据一致性。

2.4 HDFS sync 优化和并发读

为了提高写性能,Facebook 允许不等待 sync 结束就继续写,这一点看起来也很暴力,不知道会不会影响数据正确性。

为了能够读到最新数据,Facebook 允许客户端读一个还未写完的数据文件。如果读到正在写入的最后一个块,就重新计算 checksum。

3. 打造实时生产坏境的 HBase

3.1 行级别原子性和一致性

虽然 HBase 已经保证了行级别的原子性,但节点宕机可能导致最后一条更新日志不完整。Facebook 不够满意,引入了 WALEdit,一个日志事务概念来保证每条更新日志的完整性。

一致性方面,看来 HBase 能够满足需求。不过对于 3 个副本同时校验失败导致数据块不可用的情况,Facebook 增加了事后分析的机制,而不是简单丢弃。

3.2 可用性

为了提高 HBase 的可用性,Facebook 对其进行了完善的测试,并解决了以下几个问题:

  1. 重写 HBase Master,将 ragion 分配信息存储到 ZooKeeper 中以保证宕机切换正确完成。
  2. 使得 compaction 可以中断以加速 RegionServer 的正常退出速度,并实现 rolling restarts(就是逐台升级),降低程序升级对服务的影响。
  3. 将宕机 RegionServer 的日志拆分功能从 Master 中拆离,由多个 RegionServer 进行拆分,以提高 RegionServer 故障恢复效率。

这几个问题的解决倒是有通用的用途,我想不久以后很有可能会合并到 Hadoop 的代码中。

3.3 性能优化

性能优化主要从两点进行,一个是 compaction 性能,另一个是读性能。

读过 BigTable 论文的应该对其 memtable 和 compaction 的特性比较熟悉。这里主要讨论了让 minor compaction 也删除数据的好处,以及如何做 major compaction 能够提高合并的性能。

在数据读性能方面,文章里主要讨论了减少 IO 操作的方法,其中包括 bloom filter 和特定类型 meta 信息(时间戳)的使用。还有很重要的一点,在部署上保持 RegionServer 和物理文件的局部性!

文章后面还给出了 Facebook 在部署和运维方面的一些经验,其中有一些有趣的点,我后续可能会写篇文章专门讨论,这里就不详细说明了。

4. 总结

以前我们也曾经讨论过如何在分布式文件系统的基础上搭建一套实时数据分析系统,当时认为如果有成熟的 GFS 可用的话,这个工作会比较简单。现在读到 Facebook 的这篇文章,才发现当初想法的幼稚。仅仅从这篇文章中的技术点体现出的工作量来看,文中说这个系统是多年持续工作的结晶是令人信服的。当然,这也意味着想复制一套这样的系统并不是件轻松容易的事。

从系统设计的成果来看,这个系统应该能达到文章开头制定的需求目标,并也能够满足大部分应用场景的需要。不过有一点,我存在疑问,即是为 Insights 提供的 Realtime Analytics 功能。Realtime 没问题,但使用 HBase, Analytics 究竟能支持多好呢?可能还需要再去了解 HBase 的功能才能有答案。

从这个系统的很多细节可以发现,有不少折中和 trick。我想这就是现实世界,凡事很难做到尽善尽美,工程也一样。在设计系统时追求完美没有错,但是需要考虑代价和可行性,不要忘记满足需求才是最重要的目标。除此之外,也不妨再列出一些“非需求”,排除这些限制可能会降低不少的系统复杂度。