厦门印象-文字版

外面的雨沥沥拉拉,从下午两点开始我们就坐在机场的肯德基里等待晚上十点半离开的飞机。由于做的功课不够加上请假时间没多少可选择,很不幸地赶上了厦门的雨季。在厦门盘桓的五天中,有三天都在下雨,包括现在。天气预报说今天是全天暴雨,于是我们只好放弃白天拖着行李箱再转转的想法,一点钟退完房,早早地赶到机场闲坐。幸好这肯德基有插座又有网,我得空写写这几天的印象。

厦门的机场名字叫做高崎国际机场,这个颇具日本范儿的名字据说来自于地名高崎村,至于高崎村的由来是否与日本有关就不得而知了。高崎机场就在厦门本岛上,这倒让交通变得非常便利,除了出租车或机场大巴,还可以直接选择市内公交线路。

厦门的市内公交非常发达,而且颇具特色。公交车站的亭盖非常完善,几乎没有像北京那样只树个站牌了事的车站,这大概是由于本地雨水和太阳都比较厉害,等车时需要遮蔽。除了环岛路一带,厦门岛上很多公交车站是只有站名没有站牌的,根本没办法知道这个公交车站有几路公交,都到什么地方。于是我们在厦门只能靠百度地图来决定到哪里坐车,坐几路车,实在不知道厦门的普通市民平时是怎样出行的。BTW 幸好百度地图在厦门还是比较准的,不然我们只能两眼一抹黑了。厦门的公交线路极其多,曾经路过的每个站点,都至少有五六路公交车路过,多的甚至有十几路,大部分上车投币一元,到哪里都很方便。

说到公交车,不得不说一下刚到厦门的一个意外经历。下飞机后我们坐 27 路公交车去宾馆,到站下车后忽然发现有装笔记本电脑和一个钱包的书包忘到了公交车上。心想完了,于是急忙打车去追公交车,但是的士司机不了解公交线路,再加上夜间公交速度很快,到最后还是没追上。正在 27 路公交车的底站手足无措,准备接受物品丢失的现实时,接到了公交总站打来的电话。因为在出租车上曾经打过总站电话报失,正好公交司机打回电话说捡到了一个书包,就直接通知到我们了。第二天到机场附近的县后总站拿到了失而复得的书包,包里分文未失,而且总站的工作人员执意不收我用现金表达的谢意。我无以为报,只能用文字记录下来这件事,非常感谢 27 路公交司机肖福明以及厦门思明公交公司的好人们!

失而复得的书包仿佛把我们的好运气用光了,接下来厦门的行程就是各种不顺利。来之前我们曾经在网上预订了一个叫做“爱尚假日”的家庭旅馆,在网上各处是好评如潮,让人感觉好得令人发指那种。我老婆也因而心动,选择了这家比其它家庭旅馆价格高出一截的旅店。谁知道入住了以后发现,这只是一家很普通的家庭旅馆,条件也只是比京郊的农家乐好一些。卫生间马桶是蹲式的,没有抽风机,地板居然是抹得高低不平的水泥地;房间里倒是有两个窗户,一个对着走廊,一个对着外面高高低低的村屋,大床一坐就吱呀响,没有有线网络,无线网络不知道是多少人分享,慢得让人回忆起拨号上网的日子;柜子里放着两罐可乐,却没有标识说是否收费(当然是收费哒!),拖鞋不是一次性,有线电视收不到湖南江苏台。凭心而论,并不是说这家旅馆条件多差,可能它还比周围的某些旅馆强些,但那么多超现实的好评总让人感觉有托儿的嫌疑,心里不太舒服。

在爱尚假日住了两天后,换到一家号称是“快捷酒店”的家庭旅馆——摩登世家曾厝垵店。在环岛路一带,曾厝垵的位置还是要好于台湾民俗村一带。离鼓浪屿、中山路更近,交通更方便,旅馆、饭馆也多。跟爱尚假日相比,摩登世家还是要正规一些,毕竟挂着连锁酒店的牌子,装修服务还算比较标准。我们住了一个有超大卫生间的豪华大床房,本来一切都挺满意,但是——但是居然有超大的蟑螂!!!半夜起来发现三只蟑螂在行李箱附近爬,快给我恶心坏了。北京的德国小蠊跟南方的美洲大蠊比体型真是小太多,恶心程度也差很多啊!

总的来说,曾厝垵附近的环岛路一带几乎没什么正经的经济型酒店。除了个别带海景的大酒店之外,大部分本质上是渔村中村屋改建的家庭旅馆,规范和卫生条件都一般,而且特别绕特别难找,基本上也不可能有什么海景。我也对这种“家庭旅馆”失去了信心,也对住海边儿失去了幻想,以后如果不舍得住大酒店海景房,还是老老实实去住中心地带的经济酒店吧。当然,如果是单身背包穷游,这种年青人多的家庭旅馆、青年旅馆可能还挺热闹挺适合的。

我们两个很懒,懒到每天都是睡到中午才起床。但得益于厦门本身景点不多,倒也没落下太多。环岛路逛了两次,从两个方向基本上把环岛路的木栈桥走遍了,风景的确不错。让我很感惊奇的是厦大白城附近的海水,胡里山炮台分开的两段海滩海水清洁程度有很大差距。厦大白城校门外面的海滩海水相当干净,直到岸边一两米才泛起黄沙;但是一岩之隔的东侧就离岸边老远就有一道明显的蓝黄海水分界线。

中山路没甚意思,就是一条商业街,民俗小吃、手工艺品与现代商场、专卖店和谐相处,与南京的夫子庙类似。厦门大学也没有想象中漂亮,唯一觉得有味道的是演武路校门进去后左手边的一排三层旧楼。鼓浪屿倒真是一个好地方,轮渡码头边的龙头路很多美食,摩肩接踵;再往里走却街巷交错,甚少人迹。很多漂亮的小地方,一下午时间却也没走完。鼓浪屿上的美食很平价,在岛上吃到最不值的大概就是20元一份的张三疯奶茶了。最赞的一个是牛肉香餐室的卤牛肉和沙茶面,另一个是马拉桑的果汁,现榨的100%橙汁最让人印象深刻,在厦门其它地方也未见到过。沙茶面在厦门各处的做法不同,但味道的确都不错。

本来计划在最后一天逛一逛岛上的内湖——筼筜湖,无奈天公不作美,看着阴沉的天气只好放弃。除了刻意去的景点外,还意外地去了一些地方。例如第一晚住的酒店附近的莲花公园,尝到了正宗的莲花公园煎蟹;有一天躲雨,还进到了整修中的启明寺,菩萨罗汉歪了一地。

----------------------------------------------------------------------------------------------------

在厦门机场,肯德基的免费 wifi 时断时续;中国电信 ChinaNet 连上却打不开验证界面,10 小时的免费时段无法使用;中国移动 CMCC 的收费 wifi 使用的确便利也飞快,40 小时赠送套餐总算有了用武之地。

春游东湖港

说起来已经是两周前的事情了,所在大组春游地点选在了京郊房山的十渡。可能对京郊大家兴趣都不是很大,算上家属才有不到四十人报名。

第一天去的是十渡还是十五渡已经搞不清了,都是些山寨的旅游项目:漂流、卡丁车和竹筏。以前有漂流的经验,怕进水所以根本就没带相机,只是偶尔用手机拍几张。可惜的照片放在内置 SD 卡上,被刷机给刷掉了。刷机前应该检查一下照片有没有被即时上传的,sigh~~~

晚上住的农家乐还可以,比去年春天去的农家乐好太多了。起码房间、被褥很干净,晚上睡觉不冷,吃的也还不错。

第二天去了一个比较正规的景点——东湖港,爬山。房山十渡一线的山和门头沟、怀柔的山区别比较大,别处的山远没有这里陡峭。东湖港整个上山的路虽然不算长,但都是陡峭的阶梯,而且很多地方是陡峭的栈道。从栈道上下,望见铁质楼梯缝下面就是空荡荡的绝壁,对胆识真的会有些考验。

上传几张同事拍的照片,留个纪念。

迁到丰台区

来北京已近六年,大小搬家经历了七八次,家当却是一次比一次多。实习和读书期间一直在海淀区晃悠。毕业以后在西二旗智学苑小住了3月,就因老婆来京工作地点交通不便搬到了曾经的宣武区牛街西里(现在属西城),北向一居开间。

都说牛街是穆斯林聚居区,美食众多。我虽在牛街住了一年半,老北京小吃却没尝到多少,比如说豆汁、焦圈就没有试过。其中大部分原因是懒得尝试,还有对太油太腻的食物不太热衷。最让我留恋的食物只有两样:一个是老城伊的羊蝎子,另一个是聚宝源的肉饼。大伙烤肉的牛肉面本来也颇合我口味,但自从涨价五块钱以后,就不甚满意了。

本来计划在牛街居住两年或更久,也习惯了在那里的生活。后来因房东的卖房而打断,很多朋友可能对这种情况都深有体会且深恶痛绝。但好歹我遇到了一位实诚的房东,不仅少收了我一个月的租金,还补偿了我一笔违约金,也算是好聚好散。当初这个房子是在豆瓣上找到的,这让我的很多同事都比较惊讶。他们不知道文艺青年聚集的豆瓣还能交换此类生活信息,但我想,关于豆瓣他们不知道的事情可能还有很多 :)。

再次找房,考虑到我们俩的公司位置,还是打算在牛街附近找。但是这次尝试了一个新的途径,在小区内部贴条找房。谁知本来目标是本小区的房子,却租到了一个本小区业主在其它小区的房子。房子在丰台区右安门附近,临着二环,还是一居开间,西向,包暖气。实话说,都是回迁小区,小区内部环境却比牛街西里差很多。但考虑到租房开支在生活支出里占大头,这个价格在周边也算便宜了,不能挑剔太多。

因为重新装修过没几年,房子内部的大体状况还算可以。唯一不好的是卫浴管道、设施比较陈旧,不少问题,我还没来得及修理。这种活我从来不愿意麻烦房东,自己就能干,省时省事。新家也不尽是坏处,附近交通上反而比牛街方便一些,在立交桥边公交线路多,也没那么堵。小区周边的小饭馆和超市也比牛街西里更多更近,尤其到晚上,临街小摊如同五道口一样兴盛。

看看房子,愈发能明白北京的区域发展不平衡。在房租房价上,南城要差北城一个档次,北城尤以西北房租、房价最高。大致原因应该是一方面西北是海淀区教育资源好,另一方面高校吸引高科技企业聚集,居民平均收入更高。我倒庆幸能在南城找房子,虽然上班路上得花费一个半小时,但好歹我老婆不用那么辛苦,且房租支出能不那么肉疼些。

今天本想骑我那尘封已久的公爵 600 出去转转来着,无奈天公不做美,只好补一补欠下来的近况。

PS: 今天是南京大学 110 年校庆,祝母校生日快乐!

Python JSON模块解码中文的BUG

很多语言或协议选择使用 ASCII 字符 “\”(backslash,0x5c) 作为字符串的转义符,包括 JSON 中的字符串。一般来说,使用 Python 中的 JSON 模块编码英文,不会存在转义符的问题。但如果使用 JSON 模块编解码中文,就可能面临着中文字符包含转义符带来的 bug。本篇文章给出了一个 badcase。

中文解码错误

测试用例文件里面包含繁体的“運動”二字,使用 GB18030 编码。使用 json 解码的错误如下:

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> fp=open('decode.dat', 'r')
>>> json.load(fp, encoding='gb18030')
Traceback (most recent call last):
  File "", line 1, in 
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 278, in load
    **kw)
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 339, in loads
    return cls(encoding=encoding, **kw).decode(s)
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 360, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 376, in raw_decode
    obj, end = self.scan_once(s, idx)
UnicodeDecodeError: 'gb18030' codec can't decode byte 0xdf in position 0: incomplete
multibyte sequence

发生这个问题的原因,就存在于“運”字的编码之中。“運”的 GB18030 编码是 0xdf5c,由于第二个字符与转义符 “\” 编码相同,所以剩下的这个 0xdf 就被认为是一个 incomplete multibyte sequence。

我本来认为,既然已经提供了编码,json 模块就能够区分汉字与转义符(所以我觉得这应该是 json 的一个 bug)。但从实验来看,并非如此。对于一些不需提供字符编码的 JSON 解码器来说,我们倒可以用一种比较 tricky 的方法绕过上面这个问题,即在“運”字后面加一个额外的转义符:

{"a":"運\動"}

遗憾的是,这种方法对 Python 的 json 模块不适用。我仍不知道该如何解决这个解码问题。

中文编码——没错误!

对于相同的 case,Python 倒是能够编码成功:

$ cat in.dat
運動
$ python
>>> import json
>>> in_str = open('in.dat', 'r').read()
>>> out_f = open('out.dat', 'w', 0)
>>> dump_str = json.dumps({'a': in_str}, ensure_ascii=False, encoding='gb18030')
>>> out_f.write(dump_str.encode('gb18030'))
$ cat out.dat
{"a": "運動"}

所以这件事情就把我给搞糊涂了,Python 的 json 模块不能解码自己编码的 json 串。所以我觉得这可能是一个 bug,或者至少是 2.7.1 版本的 bug。

PS: 要仔细看文档

20120516:经网友 TreapDB 提醒,加载字符串时自己做 Unicode 转换,貌似能够解决这个问题。

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> in_str = open('decode.dat', 'r').read().decode('gb18030')
>>> json.loads(in_str)

回头仔细看了一下 json 的文档,其中有这么一段:

Encodings that are not ASCII based (such as UCS-2) are not allowed, and should be wrapped with codecs.getreader(encoding)(fp), or simply decoded to a unicode object and passed to loads().

已经注明了 encoding 不支持非 ASCII-based 编码的参数,所以应该使用 getreader 进行转码,而不是让 json 模块去转码。看来是我没读懂文档,大惊小怪了,回家面壁去!

>>> json.load(codecs.getreader('gb18030')(fp))

WP Super Cache插件带来的500错误

今天博客服务器(Hostmonster 主机)全站从中午开始出现 500 错误,然后我登陆进 CPanel 各种查看日志、进程、数据库、PHP 状态,均未发现异常。后来又清理 php.ini、.htaccess,重启 PHP,也没有任何改善。只好给客服投了个 Ticket,准备等待客服解决。

后来灵机一动,发现同一主机 host 的其它 WordPress,有的活得很好,有的也是挂掉了。于是用排除法清理 wp-config.php,最终确定是 wp-config.php 中的 WP_CACHE 配置项有问题,删掉之后访问就恢复正常。

define('WP_CACHE', true); //Added by WP-Cache Manager

但由于 WP_CACHE 配置项是 WP Super Cache 自动增加的,一旦登陆进后台,WP Super Cache 就会自动把它再加上,后台页面又会出现 500 错误。于是乎我只好将整个 WP Super Cache 插件干掉(包括 wp-content 下的 php 脚本),终于一切恢复了正常。印象里删掉的 WP Super Cache 的版本是 0.9.9.*。

rm advanced-cache.php backup-*  cache/  wp-cache-config.php plugins/wp-super-cache/ -rf

考虑到 WP Super Cache 还是对性能有一定改善,又看了一下最新版的 WP Super Cache 是 1.0 版,我怀疑是 WP Super Cache 版本较旧造成的问题。虽然该版本已经使用了很长时间,不明白为什么今天才会爆出来 500 错误(也许 Hostmonster 主机程序进行了升级?),我还是装上了最新版本 WP Super Cache 插件。期望它不要再出现类似问题,否则只能弃用了。

既然我的博客不是同一主机上的个例,我想可能在 Hostmonster 上的其它主机也可能会遇到此类问题,特记录下来供参考。

警惕程序日志对性能的影响

做后台系统比做客户端软件的辛苦的地方,就是不能让程序轻易地挂掉。因为在生产环境中无法容易地复现或调试 bug,很多时候需要程序日志提供足够的信息,所以一个后台系统的程序员必须要明白该如何打日志(logging)。

很多语言都有自己现成的 logging 库,比如 Python 标准库中的 logging 模块,Apache 的 log4cxx(C++), log4j(Java)。如果你愿意找,很容易能找到基本满足自己需求的日志程序库。当然,自己实现一个也不是很困难。难点不在于写这些库,而是如何去使用它们。

大部分情况下,我们关注的都是日志的级别和内容。即哪些情况下,该打哪个级别的日志,日志语句中,该怎么写。

在程序开发的过程中,我们需要很多的日志协助分析程序问题;但在生产环境中,我们没有那么多的空间存储丰富的日志,而且日志量太大对于问题排查反而是累赘。有些人使用预处理解决这个问题,在 debug 版本和 release 版本中编译进不同的日志语句。这样能够解决一些问题,但却使得在生产环境中无法轻易地打印更多的日志。大部分人更接受的做法是,使用配置(参数)控制日志的打印级别,在需要更多日志的时候,可以随时打开它们。为了实现日志“少但是足够”的目标,开发人员必须明白日志信息的价值,即哪些日志应该属于哪个级别。

日志的作用是提供信息,但不同的日志语句,提供的信息量却是不一样的。有的日志里会写“Failed to get sth..”,但却忘记加上失败调用的返回值。同程序一样,日志语句中有的是变量(某个变量内容),有的是常量(提示信息)。常量你总能从程序源代码中获得,但变量不行。所以在一条日志中,信息量最大的是变量,是函数返回值/字符串内容/错误码,因而变量应该尽量放在靠前的位置。常量也不是一点价值没有,写得好的提示语句,会使问题一目了然,可以免去你到代码中 grep,然后重读代码的麻烦。

上面这两点,几乎所有知道 logging 重要性的同学都会了解。但关于 logging 对性能的影响,很多人没有足够的警惕心。例如有人会在一个按行解析文件的函数中写下这样的日志:

int parseline(...)
{
log_trace("Enter parseline with ...");
DO_SOMETHING;
log_trace("Exit parseline with ...");
return 0;
}

乍一看,由于 log_trace 级别不高,在生产环境中肯定会关闭,那么这样做看起来对性能没太大影响。但实际上 log_trace 可能是这样实现的:

#define log_trace(fmt, arg...) \
    xx_log(LVL_TRACE, "[%s:%d][time:%uus]" fmt, __FILE__, __LINE__,\
           log_getussecond(), ## arg)
#endif

可以看到 log_trace 宏中自动添加了很多信息,值得注意的是时间参数 log_getussecond()。大家都知道统计时间需要系统调用,那么无论 log_getussecond() 函数是如何实现的,它的代价肯定是高于一般的简单函数。

我们本以为 log_trace 在 LVL_TRACE 级别被关闭的情况下,消耗的代价仅仅是一个函数调用和分支判断,却没有发现宏参数中还隐藏着一个需要调用系统调用的函数。当文件不大是还算能够忍受,但当这个文件是一个数据库,扫描每一行都要执行两次 log_trace() 时,它对系统性能的影响就绝不可忽视了。

所以,最佳的做法还是,在性能攸关的代码中,使用可被预处理掉的 logging 语句,仅仅在 debug 发布中才能见到这些日志,release 版本中不把它们编译进来。

此外,上面这个 log_trace,是一个糟糕的设计。logging 模块只应该干 logging 的事情,开发人员需要时间统计时会自己完成。

64位Ubuntu上使用Network Connect

Network Connect 是 Juniper 公司出品的配合其安全硬件 VPN 解决方案的软件包,很多公司使用这个 VPN 解决方案,一般需要使用 RSA Token 动态密码登录。Network Connect 支持 Windows 和 Linux 操作系统,但很遗憾的是,它只支持 32 位 Linux。下面以 Ubuntu 为例,介绍如何

在 64 位 Linux 上安装并使用 Juniper Network Connect

1. 安装浏览器 Java 插件(64位);

$ sudo apt-get install icedtea-plugin

虽然看起来只安装了一个软件包,但实际上可能会下载依赖的 OpenJDK 的一系列软件包。这是为了在 Firefox 下能够正确启动 Network Connect 的安装过程。

2. 修改 root 密码;

$ sudo passwd

由于 Ubuntu 默认不使用 root 用户,下面自动安装 Network Connect 软件时候又必须提供 root 密码,所以这里必须先初始化 root 的密码。

3. 打开 Firefox,访问 VPN 网站。

像在 Windows 下那样,先登录进入 VPN 页面,再点击 start,启动 Network Connect 的自动安装过程。过程中会弹出一个很丑的终端,安装时需要输入 root 密码,但是最终必定是无法弹出 Network Connect 小图标,也连接不上。

4. 下载脚本 junipernc[1],并且安装到执行目录 /usr/bin 中;

$ wget http://mad-scientist.net/junipernc -O junipernc 
$ chmod 755 junipernc
$ sudo mv junipernc /usr/bin

5. 安装 Network Connect 二进制程序依赖的 32 位动态链接库;

NC 具体的可执行程序是 ~/.juniper_networks/network_connect/ncsv ,是 32 位的可执行程序。如果不安装它依赖的 32 位动态链接库[2],该程序是执行不了的。

$ sudo apt-get install libc6-i386 lib32z1 lib32nss-mdns

6. 执行 junipernc 脚本,会跳出各种对话框,对应填入各种参数;

$ junipernc --nojava

URL 就填入 VPN 网站的域名,USER 就是自己的用户名,REALM 比较麻烦,需要自己查看 VPN 网站登录页面的源代码,看对应 REALM 域实际表单提交的 value 是什么,填进去即可。

--nojava 的意思是,只执行 VPN 连接,不启动 Network Connect 小锁图标的 Java 程序。因为该 Java 程序要求 32 位 Java 环境。

连接失败会有提示;连接成功后,junipernc 会一直停在那里,终止连接可以使用 Ctrl-C 命令行,或者 sudo killall -9 ncsv。

6-1. 修改 junipernc 配置;

junipernc 有两个配置文件,一个是 ~/.vpn.default.cfg,保存着用户手工输入的配置;一个是 ~/.vpn.default.crt,这个是从网站上下载下来的证书。

这样,一般的 VPN 连接功能就实现了。如果希望启动 Network Connect 小锁图标并监控 VPN 的流量信息,就需要

在 64 位 Ubuntu 上安装 32 位 Java 环境[3]

如果不是特别需要,不建议折腾下面这套东西。

a. 到 Oracle 网站上下载 32位 Java 的 tar 包;

到这个地址:http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html#javasejdk下载例如 jre-7u3-linux-i586.tar.gz 的 Linux JRE 包。重要的特征是那个 i586。

b. 解压并安装到 jvm 目录,调整默认 java 的链接到 32位 JRE。

$ tar xzvf jre-7u3-linux-i586.tar.gz
$ sudo mv jre1.7.0_03 /usr/lib/jvm
$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jre1.7.0_03/bin/
java 73
$ sudo update-alternatives --config java

最后一个命令会给出一个 Java 的列表,如下所示,选择 jre1.7.0_03 对应的编号即可。

There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                      Priority   Status
------------------------------------------------------------
  0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      auto mode
  1            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      manual mode
* 2            /usr/lib/jvm/jre1.7.0_03/bin/java          73        manual mode

Press enter to keep the current choice[*], or type selection number: 2

c. 验证 java 可以执行且版本正确(其实依赖上面的第 5 步);

$ java -version
java version "1.7.0_03"
Java(TM) SE Runtime Environment (build 1.7.0_03-b04)
Java HotSpot(TM) Server VM (build 22.1-b02, mixed mode)

d. 安装 32位 JRE 可能依赖的 32位动态链接库。

$ sudo apt-get install ia32-libs

这个包会依赖非常多的 32位链接库,安装过程会比较漫长。

参考文章:

[1] Using Juniper Network Connect on Ubuntu
[2] Debian6(64位)搞掂Juniper VPN
[3] Installing 32 bit java now that ia32-sun-java6-bin is not available

修改exvim目录过滤逻辑为匹配拒绝

exVim 是一个非常优秀的 Vim 环境,通过它能够省去很多 Vim 插件的配置工作。自从使用上 exVim 后,我基本没有再自定义 Vim 插件,完全依赖 exVim 打包的辅助功能。

最近让我略有不爽的使用问题是:exVim 默认的 file filter 和 dir filter 都是匹配通过的,即“匹配 filter 过滤条件的目录和文件被通过,列入项目目录、文件列表中”。

exVim 的 dir filter

对于文件来说,设置匹配通过毫无问题。因为我也想要项目中仅包含 “.cpp,.c,.h,.py” 这样的源代码文件,选出来匹配这些模式的文件就是我希望的结果。

但是对于目录来说,设置匹配通过就与我通常的需求相悖了。一般情况下,项目目录下的所有目录都是程序需要的。但是一些专门存放测试程序、测试框架、输出文件的目录,我其实不希望显示在我的项目中。而且 exVim 中的目录过滤貌似仅限在项目顶层目录中,过滤的意义不大。

所以我修改了一下 exVim 的代码,将默认的 dir filter 含义修改为匹配拒绝,即:“匹配 dir filter 的目录被拒绝(被过滤掉),无论它在哪一级。"例如,我将 dir filter 设置为 “test,output”,那么我项目目录下所有叫做 test 或者 output 的子目录都不会显示到项目目录列表中,而不妨碍其它名称目录的通过。

可以想见两个 filter 采用不同的通过逻辑并不是 exVim 开发者希望看到的,所以我想这个修改也没必要提交给开发者。不过我仍然觉得这是很有用的一个修改,所以拿出来分享一下。修改的补丁文件见:http://share.solrex.org/ibuild/exvim-dir_filter-8.05_b2.patch

PS: patch 文件中还有一个改动是将 quick_gen_project_PROJECT_autogen.sh 文件从项目目录下,移动到项目目录下的 .vimfiles.PROJECT/ 目录中,原因是看起来碍眼 :)

携友闲逛798

上周末正好有事到望京附近,顺便带老婆和朋友一起逛了逛 798 艺术中心,照了几张照片。我在里面逛得倒是依然自得,可惜同行的两位女同学兴致寥寥,于是没待太久就散了。

MIUI的贴心功能

预先声明:这不是一篇软文,我真的是出于对 MIUI 系统的喜爱才写的。

我的手机型号是华为 U8800,联通定制机,自带的 ROM 里太多乱七八糟的软件,后来就刷了华为官版的海外 ROM。这个 ROM 是比较干净的版本,但不知道为何老是黑屏重启,基本上每天会重启了一两次。毕竟追求的是性价比,这点儿缺陷我就忍了。

不过基于安卓用户的刷机习惯,后来我还是忍不住刷了修改的 MIUI ROM——因为我的手机型号不在 MIUI 的支持列表里。第一次刷的是 2011 光棍节版,前两天又更新到 2.3.2 版。MIUI 很多贴心改进让我爱不释手,下面我会列举一下。

  1. 菜单键+音量- 执行屏幕截图,下面的图片有些是用这个功能截的。

  2. 拨号键盘使用 T9 拼音直接搜索联系人,支持模糊查询。

    拨号键盘

  3. 通知栏内置很多有用的功能快捷开关。

    通知栏

  4. 系统自带防打扰过滤器功能,可以按照规则过滤垃圾短信和电话。

    防打扰

  5. 可在联系人列表中直接查看到手机号码的归属地,这样当某些朋友有多个手机号码时,容易判断该打哪一个。

    联系人
  6. 陌生人的未接电话可以显示号码归属地和响铃次数,而且第一声响铃静音。这对判断来电是否是垃圾电话非常有帮助。

    陌生人来电
  7. 内置流量监控和防火墙功能,可以为每个应用程序设置网络访问权限。

    流量监控和防火墙
  8. 内置音乐播放器与百度合作,提供在线音乐功能。

    音乐播放器
  9. 内置手电筒功能,且在锁屏时可以长按 Home 键打开手电筒。这个功能实在是太有用了!

    手电筒
  10. 电话功能设置很多贴心小功能:自动录音、接通挂断时振动、来电挂断时提供短信回复、通话记录点击显示联系人信息等等。

    电话设置
  11. 最后值得一提的是,虽然不算个功能,但刷了 MIUI 之后,每天重启的现象居然没了!我完全没想到第三方修改的第三方 ROM 居然比官版 ROM 还稳定,这个出乎了我的意料。

上海流水

我上次去上海是 8 年前,记忆都已经模糊了,只剩下几个场景比较清晰。与吴诗涛坐在沪宁双层城际列车的楼梯间地板上享受列车空调,路过华东理工收费的小足球场和封闭的草坪足球场,以及在臭水沟边秦嘉诚宿舍里塞紧蚊帐捉蚊子。在豫园吃过什么已经忘了,只记得自动售货机的可乐很贵,地铁也很贵。逛南京路步行街的时候在下雨,走到外滩雨更大。在雨中仰望了一下金茂大厦和东方明珠电视塔,不记得那时有没有环球金融中心。

上周因公差有幸免费重游上海。之所以说有幸是因为互联网公司不比其它行业,即使是大公司,出差机会也很少。对于华为、宝洁这样的公司来说,恐怕不出差反而是有幸了。我也是第一次坐飞机——不要笑我。通过飞机相连,两个城市间的距离变得好小!

这次到上海的第一印象是厚厚的云层,以及落地前虹桥机场附近的繁华,飞机就好像贴着闹市区的头顶飞过,宾馆饭店的霓虹灯广告牌都清楚可辨。出了航站楼,就闻到一股久违了的江南的潮湿空气,更重要的是,空气中没有煤烟味。经过北京漫长的冬天,这种感觉给人的冲击感太强烈了。

第一天是在张江工作,所以住在了锦江之星的张江店。我厂的上海研发中心跟安捷伦在同一幢楼里,很不起眼。室内装修跟大厦、奎科没有什么太大不同,哦,跟奎科没有什么太大不同。跟奎科一样,会议室也比较容易预定。开了一天会,不提。

由于次日是周末,所以当天晚上就坐地铁 2 号线进城住了,订的是汉庭南京西路店。地铁直达,通勤高峰期会比地面交通快一些。不得不说,2 号线人真多啊!跟北京的 1 号线类似,只要有很多人下的地方,总有很多人上,拥挤程度一路不见减轻。汉庭的大床房比锦江之星略好一些,当然价格也贵一些。

因为一个人吃晚饭无聊,约了三个朋友。一个是老同学翟效华,一个是学弟王信文,还有一个我内推入职的朋友钟云龙。地点是信文找的,在徐汇区的一个叫做厚味香辣馆的店,席间言谈尽欢,只怕是我说的太多,略显聒噪。

第二天本想早起逛逛,可起来发现外面淅淅沥沥的,只好放弃,等待中午与老同学的聚会。午饭是在宾馆附近的蝶园吃的,上海菜,味道还不错。桌上聊了聊各自的近况,还给余盈丰打了个电话。之后吴诗涛和谢成晟陪我逛了逛南京路,半路上老谢被夫人叫回家了,只剩阿涛这个单身贵族陪我逛到外滩,然后打车去机场。

妄谈时间序列表格型大数据系统设计

一直在特定领域的分布式系统一线摸爬滚打,曾取得一些微不足道的成绩,也犯过一些相当低级的错误。回头一看,每一个成绩和错误都是醉人的一课,让我在兴奋和懊恼的沉迷中成长。自己是个幸运儿,作为一个 freshman 就能够有机会承担许多 old guy 才能够有的职责。战战兢兢、如履薄冰的同时,在一线的实作和思考也让我获得了一些珍贵的经验,却直至今日才够胆量写出来一晒。这篇文章标题前面是“妄谈”两字,所持观点未必被所有人认可,我姑妄言之,有心之人姑听之。若有些友好的讨论,亦我所愿也。

我做的虽然也是分布式系统,却不够胆去讨论通用分布式系统的设计原则。因而这篇文章的主题限定到一个特定领域的分布式系统设计,这样即使别人有疑惑,我也可以把 TA 拖到我擅长的领域打败 TA :)

既然要限定,我们需要给这个系统下个定义,就有必要解释一下标题。

大数据(Big Data),这是由于分布式系统和云计算的风靡而变得很火的一个词。那么多大的规模才算大数据呢?目前没有定义,但要讨论这个问题,就必须给个确定的范围。在本文中,这个范围暂时定义为 10TB~1PB 的数据量。为什么是这个范围?我的理由是,小于 10TB 的数据规模有比较多的可选方案;大于 1PB 的数据规模,讨论的意义不大,下面会谈到。

表格型数据,是指数据是有结构的,类似于关系型数据库中的表,但不是关系型,至少不是完整的关系型。在大数据的范围内,不能说完全没有关系型的需求,但这个需求实际上是很小的。因为关系操作的复杂性,使得其在大数据上的性能非常差,此类的需求往往使用数据冗余等其它方式来实现。是性能原因,而不仅是实现难度导致它不被需求。

时间序列数据,是指数据是按照时间产生的,跟随时间而变化的分析型数据。其实分析型数据一般都是时间序列的。与操作型数据不同,在分析型数据中单单一条记录的信息是很小的,只有与其它数据进行对比、组合、分解,这条记录才会体现出其价值。

在这些限定词下,这个系统的用途就比较清楚了。它可以被用到很多地方:比如网站访问统计(Google Analytics 和百度统计)、APP 的数据统计、集群服务器状态收集、在线广告的展现和点击量等等。它是一个数据仓库,但庞大于一般的数据仓库,功能需求却少于一般的数据仓库,而且很强调性能。在这个级别上,我还没看到成熟的开放系统解决这个问题(也许我是孤陋寡闻),基本上每家都是自己实现,所以它也更值得讨论。

由于不知该如何系统地探讨,我下面只能把自己发散的思维整理为一条条简单的原则,可能会有很大的跳跃性。但是,谁在乎它连不连贯呢?

latency 对你很重要时,不要采用分层设计,优化做得越底层越好

事实上,对于有兴趣做这样一套系统的公司,latency 都很重要。因为 latency 不重要时它们完全可以使用 HBase。而且,当你有超过 1PB 数据时,你会发现其中很大一部分的 latency 不重要,那剥离出来这部分,用 HBase 吧。

在这个数据量上,必须采用分布式的实现方案。但不要为了系统逻辑的清晰而做存储层与应用层分离的实现,像 BigTable 那样。因为 locality 可以显著地降低 latency,做了存储层和应用层的分离,那你就放弃了很多可以优化的地方。否则你必须破坏分层的封装性,像 Facebook 对 HBase 做的那样。

MySQL 不是一个选项,分布式 MySQL 也不是,分布式 KV 也不是,做自己的系统吧

总会有人问这些问题:为什么数据库(分布式数据库、分布式 KV 存储)不能用于这样的场景?我只能说,原因关键是上面三个形容词:时间序列数据、表格型数据、大数据。此外可能还要加上性能、成本等其它因素。

问出上面这个问题的人,其实都可以去用数据库或者 KV 系统,大部分情况下他们的需求会被满足。因为实践过且不满足需求的人,不会问上面这个问题,所以自己找出为什么吧,更容易些。

索引很重要,但要注意控制粒度

上面说过,对于分析型数据而言,单条记录没那么重要,所以快速地获取一条记录不会成为此类系统的目标,而且索引会降低数据更新的性能。但是能不要索引吗?开玩笑,那你怎么查询!索引必须要有,但要考虑到业务场景,做到合适的粒度。所谓合适的粒度,就是能快速获得目标数据而又不至于影响数据更新的性能。

内存很重要,能省则省,能用就用完

内存的重要性大家都明白,但很少人能真正理解。能省则省——说的是不要用浪费空间的数据结构;能用就用完——说的是在保证服务器能正常工作的前提下,使用最多的内存。

IO 很重要,做任何能减少 IO 次数和数据量的事,如果要折衷,选择优化次数

对于分析型数据而言,CPU 向来不是瓶颈,IO 才是。做任何能减少 IO 次数和数据量的事,比如各种缓存(块缓存、索引缓存、请求结果缓存),比如数据压缩。如果在减少 IO 次数和减少数据量上做折衷,选择减少 IO 次数,除非这会导致数据量爆炸。

即使没分层,也不要随机写

即使能直接访问到本地文件系统,也不要使用随机写,不要向一个文件中插入内容,而是将更新与基准合并写入另一个文件。这样性能更高,真的。

支持 CRUD?不,只支持 CRA,A for aggregate

其实很多数据都可以表示成时间序列型数据,例如 MySQL 的数据表内容完全可以用时间序列的操作日志来表示,这也是 Twitter 首席工程师 Nathan Marz 提倡的,他说有 CR 就够了。虽然我没有那么极端,但是朋友,我们处理的就是时间序列数据啊,所以我们完全不需要 UD。增加 A 的原因是,聚合会减少数据量,聚合会提升查询性能。

一定要压缩数据,选择一个合适的压缩算法

原因很简单,这能够减少 IO 数据量。但不要傻乎乎地压缩整个文件,跟 BigTable 学,分块压缩。考虑到对数据更新和读取的性能偏重不同,选择对自己合适的压缩算法。因为列存储的压缩比一般而言更高,所以

如果能做列存储,就做吧

尽量分离更新和读取的压力

如果数据需要做清洗,可以聚合,那么在导入系统前做这件事,而不是让承担查询压力的系统做这件事。

实时性没那么重要,批量更新会让你更轻松

如果能接受一天的延迟,就每天一批;能接受一个小时的延迟,就不做分钟级更新。更新次数越少,预聚合效果越好,数据量越小;更新次数越少,一致性越容易保证;更新次数越少,事故处理越从容。实时更新的话,很多事情会变得非常复杂,尤其是故障处理。

用数据冗余实现关系型需求或者高性能需求

如果有关系型运算需求,一定要逼 PM 改掉。实在改不掉,在导入系统前(或者过一段时间后)计算得到结果,直接导入到系统中。高性能需求也是这样,提前在系统外聚合好再导入,让系统做最少的事情它才能更快。

分布式架构?不重要,重要的是可靠性

至于采取什么样的分布式架构,其实不重要。只要它能实现 IO 的(大致)负载均衡,并且可靠就够了。另外,值得一提的是,如果想实现中心机,选举,分片自动分裂、合并、迁移等 fancy 分布式技术,首先想想自己公司是不是行业领导者。Perfect is the enemy of good. 对于很多人来说,Zookeeper 足够了。

好的运维工具,比完美的设计更靠谱[20110222]

在完成一个大规模系统时,往往很难做到完美,尤其是当这个完美设计很复杂时。事实上 fancy 的功能也不是不能折中,例如可伸缩性通过运维工具而不是内建于系统中实现,其复杂度会大大下降,稳定性会大大提高。所以如果没有足够的能力或者时间去实现一个完美的系统,不如好好地去做一些简洁方便的运维工具。

借鉴别人经验

这个不用我解释了吧。找一切可利用的信息,和一些人讨论,自己做决定。 :)

(暂时写到这里,但我可能会更新这篇文章,当我想到更多时。)

灯泡接口

我以前是一个略具 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
-------------------------------------------------------------------------------