July 2009


beta技术沙龙越办越有意思了,上次错过了阙宏宇的mod_cache(还有关于线程进程的讨论)就很可惜,这次关于Lucene的演讲,是无论如何不应该错过了。

到目前为止,全文检索已经完全不算高技术门槛了,记得以前看过一本书里面写:“今天,任何程序员,都可以很容易地构造一个全文检索应用”。是的,全文检索的基本原理大家都知道差不多了,剩下的只是实践。我见过纯粹自己开发的,具有AS(Advanced Search)、BS(Basic Search)、DI(Digest)等结构,“像模像样”的全文检索架构,不过应用更多的,却是在开源项目上完善、定制而来的,Apache的Lucene就是众多开源全文检索项目中,名气最大、资格最老、应用也最广泛的一个。本期beta技术沙龙,讲的就是大型网站中lucene的应用,主讲人是手机之家团队的唐福林(“手机之家”总是有些东东来共享,比如上次的DAL,这真是不错)。

众所周知,用Lucene构造一个“索引-查询”的应用是非常简单的,搭好环境,参照(修改)示范代码,很容易就可以成功。但是,要构造一个真正大规模、稳定、可靠的应用,就不说这么简单。程序的编写、模块的分布、架构的设计,都有许多费心思的讲究。按照PPT提供的数据,手机之家目前的Lucene应用,采用的是Lucene 2.4.1 + JDK 1.6(64 bit)的组合,运行在8 CPU, 32G内存的机器上,数据量超过3300万条,原始数据文件超过14G,每天需要支持超过35万次的查询,高峰时期QPS超过20。单看这些数据可能并没有大的亮点,但它的重建和更新都是自动化完成,而且两项任务可以同时运行,另一方面,在不影响服务可靠性的前提下,尽可能快地更新数据(如果两者发生冲突,则优先保证可用性,延迟更新),其中的工作量还是非常大的。

演讲的主要内容都PPT里,非常丰富,我就不再赘述了。要补充的是,这份PPT做得非常清楚,需求-目标-进度-设计-上线-测试-上线,整个流程非常清楚,给出的数据同样非常精当,我想,这也反映了手机之家团队的开发规范。

因为对Lucene的使用稍微有些经验,我在这里补充几句,权当狗尾续貂:

  1. 在大规模的应用中,Lucene更适合用于狭义的“搜索”,而不应当负责数据的存储。我们看看Lucene的源代码也可以知道,Document和Field的存储效率是不够好看的。手机之家的团队也发现了这一点,他们的办法是,用Lucene存放索引,用Memcache + Berkeley DB(Java Edition)负责存储。这样有两个好处,一是减小了Lucene的数据规模,提高了程序的效率;另一方面,这套系统也可以提供某些类似SQL的查询功能。实际上,Lucene Project自己似乎也注意到了这个问题,在Store中新增了一个db选项,其实也是利用的Berkeley DB。如果仅仅用Lucene存放索引,而不存放Document,并且合理配置,一台机器可以支持几十G甚至上百G的索引;如果需要用Lucene存放索引,最好在读取时使用FieldSelector,只读取需要的Field,如果使用恰当,性能会有10%左右的提升。
  2. 在大规模应用中,Cache是非常重要的。PPT中也提到,可以在程序提供服务之前,进行几次”预热“搜索,填充Searcher的Cache。据我们(银杏搜索)的经验,也可以在应用程序中,再提供针对Document的Cache,这样对性能有较大的改善(同一个JVM内部的Cache,速度更快一些)。Lucene自己似乎也注意到了这个问题,在2.4版本中提供了Cache,并提供了一个LRU Cache实现。不过据我们测试,在极端情况下,这个Cache可能会突破大小限制,一路膨胀最后吃光内存,甚至从网络上找的许多LRU Cache实现在极端条件下都有可能出现这样的问题(这也是我们百思不得其解的地方:反复检查程序的逻辑都没有问题),最终自己写了一个LRU Cache,并修改多次,目前来看是稳定的。
  3. 在编写Java服务程序的时候,记得设置退出的钩子函数(RunTime.getRunTime.addShutdownHook)是一个非常好的习惯。许多Java程序员都没有这种意识,或者有,也只是写一个finalize函数,结果程序非正常退出时,可能造成某些外部资源的状态不稳定。拿Lucene来说,之前的IndexWriter是默认autoCommit的,这样每添加一条记录,就提交一次,好处是如果中断,则之前添加的记录都是可用的,坏处则是,索引的速度非常低。在新版本中autoCommit默认为False,速度提升明显(我们测试的结果是,提高了大约8倍),但如果中途异常退出,则前功尽弃。如果我们添加了退出的钩子函数,捕获到退出信号则自动调用writer.close()方法,就可以避免这个问题。
  4. 目前的Lucene是兼容JDK 1.4的,它的binary版本也是JDK1.4编译的,如果对性能要求比较高,可以自行下载Lucene Source Code,用更新版本的JDK编译出.jar文件,据我测试,速度大约有30%的提升。
  5. 如果对并发的要求较高,可以考虑采用多IndexSearcher的技术,也就是在一个应用服务中,开启多个IndexReader(可以对同样的索引开启多个),每个IndexReader再生成一个IndexSearcher,将这些Searcher放在一个“池”里头,给搜索请求调用。这样可以大幅度提高并发的性能,代价是在写程序的时候就要考虑到这一点,进行相应的调整。

P.S. 据我观察,国内公司内部的项目,一般取的名字都中规中矩,以’er’结尾的比较多,多是Indexer, Crawler, Layer之类。好像很少有外国那种“天马行空”的奇特名字,譬如Hadoop(这是一个“没来由”的名字)、Lucene(这是个少见的姓)。国内我接触过不多,以前抓虾有个重要的DB叫tudui(“土堆”),目前银杏有个项目叫LaserTank,都是跟实际用途毫不相关的,印象反而深刻。

我第一次吃香蕉的时候,顺手把蕉把(也就是互相连结的那一头)一揪,就开始剥皮。父亲看了说:怎么能这样剥香蕉呢,一定要从另外一头开始剥的。在父母面前,小孩子当然不敢当面顶撞,而且,我并没有尝试过从另一头开始剥。可是比较两种方法之后,我觉得还是自己的方法更好,于是去找父亲理论。父亲又试了试,说:“真怪,从这一头来剥,果然是要省事一些,可是我们从小到大,都是从那头开始剥皮的呀。”

这件事过去已经有二十多年了,我却时常想起它,开始只是告诉自己“不要迷信权威”,到后来,我逐渐发现,在生活中,一些看似简单的事情,似乎有“想当然(甚至都不用‘想’)”的做法,可是这做法未必好,许多时候,我们甚至需要意识到“我这是在想当然”、“想当然其实并不是好的办法”,于是或者另辟蹊径别出心裁,或者放下身段重新学习,结果要么遇见别样洞天,要么由此再上层楼;这样的事情,在我的生活中,一次又一次地被验证。

最近一次的经验,来自读书。
我最开始读书,主要看重的就是故事情节,哪几个人,发生了一回怎样的事情,了解了这些,就差不多够了。那时候,除了情节,学校语文课顶多教教“拿个小本子,把优美词句、名人名言记录下来”。这种做法,我从来没有尝试过,所以它有什么效果,我也不得而知。“读书这样简单的事情,还有什么道理可言吗?”许多年里,这就是我“想当然”的观点。
印象里,第一次启蒙来自在大学学到姚斯的“接受美学理论”:审美经验原来分为三个层次!原来普通的“愉悦”只停留在最表面的层次!这可以算“歪打正着”吧,姚斯说的本是对于文艺作品的接受,可是我却想到了自己读书的方法原来那么“原始”,并一下子明白了,为什么同样读一本书,不同的人能谈出来的内容完全不一样。
原来,这道理就和剥香蕉一样,即便是“读书”这样“再简单不过”、“人人都会”的事情,居然也可以有门道可言,居然也有规矩可讲。在这些门道和规矩面前,我这个“读”了许多年书的人,似乎根本“没有上道”!于是我开始有意识地学习和锻炼读书的方法:记住作者的名字,了解作品的背景,寻找文字的特点,并且,要针对不同的文本,选择不同的方法和侧重点……这样下来,成效大增(去年有位朋友与我谈及读书,最后问我:你这样的阅读境界,是怎样来的呢?)
读书这样简单的事情,自己探索和反思都可以收到这样的成果,那么已经成型的“门道”和“规矩”,岂不是帮助更大?可是,这些“门道”和“规矩”,究竟在哪里呢,是不是只能自己反省、参悟?这个问题我一直不很清楚,直到最近接触了《如何阅读一本书》才想到,所谓“门道”和“规矩”,大概就存在于这样的书里头罢。

《如何阅读一本书》
,就是一本关于“读书”的书(是的,它就好像一口咬住自己尾巴的蛇)。这本初版六十多年来不断重印、再版的书,就是要教会读者读书的方法。大致来说,整本书分为三个部分:第一部分可以视作纵向探究,主要向读者介绍阅读层次的概念,由下而上依次介绍了基础阅读(也就是初步的,毫无结构的阅读)和检视阅读(带有结构意识,能够抓住主题的阅读)。第二部分则着重讲解阅读的第三层次,也就是分析阅读,即如何“通透”——紧紧抓住这本书,做到“我注六经”,读到这本书“真正属于自己”为止。有过亲身经历的人都知道,这并非易事,大学里的一些课程,老师会带领学生,把大量的精力倾注到少数的经典上——可惜,许多人即便花了大量的时间精力,因为没有掌握合理的阅读方法和技巧,也无法达到“通透”的境界;我想,作者之所以愿意花三分之一的篇幅来讲分析阅读,原因也部分在于分析阅读是如此重要,而它本身又需要大量的技巧和锻炼。全书的第三部分则可以视为横向解剖,在这一部分,作者分门别类地讲解了各类书籍的阅读方法:实用型的书籍要如何阅读,想象文学要如何阅读,历史、科学、数学、哲学类书籍,又应当怎样阅读……虽然我们未必要完全遵照其中的方法去阅读各类文本,但是相比自己从头开始积累,书中提到的各种办法,无疑是具有相当价值的,不容错过。
另外,这本书也有一些总览性建议,我觉得很受用,譬如阅读书籍时,要仔细观察目录,要读一读作者的前言,要仔细想想每一部分的标题……这些都是我之前读书不太重视,其实又非常重要的技巧(当然,这也可能与我国特色的前言、序言有关,不信,看看《中华人民共和国宪法》),以后应当努力改进。

也许有人会说:读书就是读书,要那么多条条框框干什么,太累了。
没错,一千个读者就有一千个汉姆雷特,极端地说,作品的意义完全存在于“接受”的过程之中。可是,我又分明看到,同样的一本书,同样的时间,有人还在刀耕火种,也有人读书却已经开上了联合收割机——虽说都是“接受”,可是接受的深度和广度,却有如此的差别。造成这种差别的,恐怕并非悟性,而是清晰的思维,和长期有意识的锻炼。

朋友,你的香蕉是怎样剥的?你的书,又是怎样读的呢?

2009年4月25日,周六。受李笑来老师检出糖尿病的消息影响,我来到新街口的慈铭做体检。虽然有一年多没做体检了,我还是很有些信心的,也一直坚持锻炼,历次检查都是正常,“不可能有问题吧”。
果然,事情不出我的所料,除去那些不能现场出结果的项目,其它都是一次通过,毫无问题。不多长时间,就只剩胸透没做了。
“照完就完事”,我这么想着,走进了检查室,像其他人一样,站好。不料,检查的过程似乎要更长一点;更让人不解的是,年轻的检查员起身,到旁边叫来一位年长的大夫。隔着玻璃,我听不到他们说了什么,只看到他们在屏幕上指指点点,一旁的父亲也凑上去询问,脸色隐约有变。
终于结束了,我忐忑不安地走出来,只见体检单上写着:左上肺叶有不规则片状阴影,建议确证。大夫说:看症状像肺结核,不过我们只管体检,要确诊,请去大医院,或者专业医院。
我连忙对父亲说,没事,没事,还不知道呢,等明天去医院看看再说。心里却收得有些紧:肺结核,就是古代说的“痨病”吧,是不是还得吃“人血馒头”~~。

  • 背景:肺结核是一种常见的疾病,在发展中国家,发病率尤其高。空气中到处都有结核杆菌,在个人免疫力下降,或者过度劳累时,病菌容易趁虚而入。患病之后可能并不会长期咳嗽,低热(尤其是午后或傍晚)、盗汗(睡觉醒来全身湿透)、精力严重下降,也是肺结核病的症状(在我身上,这三点体现的很明显,尤其是精力,我有时候百思不得其解:为什么我早上锻炼了,全天的精神反而更差了,也有朋友抱怨“你总是很累”)。

4月27日,周一。早上来到新街口的北京市结核病防治医院,医生问了问状况,让去拍个片子。看了看,确实有阴影,但还不能确诊,需要进行痰检、皮试(检查血液是否感染),才能确认。血检是当时就能出结果,痰检必须连续三天送样本。

  • 背景:在确诊之前,需要检查血液、痰液里是否存在结核杆菌。血液状态可以通过皮试检查,若发红,则表示已感染。痰液检验结果若呈阳性,则处于开放期(也就是可能传染他人),应隔离治疗。痰检必须是每天清晨漱口之后马上采样送检,涂片结果当时可出,培养的结果则要两个月之后才可见到。另外,在采样时,一定注意牙龈不要出血,否则容易被认定为血痰,影响结果。

4月30日,周四。最后一次送痰样本。医生检查了皮试结果,确认已经感染结核杆菌。因为五一劳动节医院休息,痰检结果要等五一之后才能知道。

5月6日,周三。再次去医院,挂了号,医生说,你这个情况真奇怪,看片子是有,皮试也是阳性,但怎么痰检都是阴性(也就是不传染他人),再去做血液抗体检查,看看是否有抗体。等了半个小时,结果出来了,没有抗体。“这个情况确实比较少见,你等等吧,我们研究研究,你过一周再来好了。”

5月13日,周三。去医院取了会诊结果,医生说,已经开会研究过了,判断还是感染了肺结核,就是不排菌,不传染,开始服药治疗吧。于是登记了相关信息,签了一份“同意治疗”的文件,领了药。医生嘱咐说:一定要注意休息,晚上不能睡太晚(我习惯12点睡觉),11点之前,必须睡觉。

  • 背景:按照政府规定,对于肺结核病人,采取免费治疗政策。据我所知,“免费”的范围包括:在治疗周期内(一般为半年到九个月),免费提供抗结核病药物,两次免费胸片(分别在治疗的第二个月和第六个月),而且,这项政策不受户口所在地的影响,在当地发现,就可以在当地享受免费待遇。
    一般来说,治疗结核病常用的药物有:利福平、异烟肼、乙胺丁醇、吡嗪酰胺和链霉素。这五种药物都属于抗生素,其中的每一种都可以杀死几乎所有细菌,但因为患者体内可能有大量细菌,单独用药总可能要留下死角(产生抗药性之后情况会非常麻烦),所以一般采取混合使用的办法,并至少治疗六个月,这样可以保证不复发。
    利福平、异烟肼和吡嗪酰胺,这三种药可能导致恶心和呕吐,并对肝功能有影响,作为辅助,可以服用保肝药(西药有肌苷片,辅助治疗肝炎的,便宜而且效果好,如果想吃中成药,一般服用护肝片,注意,保肝药不在免费范围之内),外加维生素B和维生素C。另外,服用利福平之后,汗液、尿液等可能呈橘红色,并有特殊气味,这是正常反应,不必惊慌。
    乙胺丁醇一般在治疗初期大剂量服用(剂量是其它药物的两倍),效果明显,但两个月后应减少,因为它会影响视力。链霉素是最先发现的能够治疗结核的药物,一般在结核病晚期注射使用,它会影响听力和人体平衡。

6月11日,周四。到现在为止,已经治疗了一个月了。按照规定,必须检查肝功能,并查痰。因为来了才知道要查肝功能,早上又吃了饭,只能下周二再来。医生并且嘱咐:查痰前三天,必须停药。
回想起来,治疗第一个月的反应真是很大,视力、精力都受到很大影响。眼睛特别容易疲劳,人也特别容易累——本身患病精神就不好,吃药以后更加厉害了,许多时候在公司就呼呼大睡起来,而且有好几天,我下班的时候感觉人要虚脱了,都不知道自己怎么骑车回去的(我一直觉得,在北京,除非下雨雪,否则上下班开车不如骑车),手上的许多工作更是搁置下来,包括年初制定的写书计划,答应帮派朋友做的事情,等等,外出活动更是难得了(只有一个周末去国图看了书,而且只看了半天左右)。

6月16日,周二。去医院查肝功能,并送了查痰样本。上班的时候,有个电话没接到,看号码,是医院打来的,打回去,因为是总机,只得作罢。

6月17日,周三。再次去医院,被告知,查痰样本仍然是阴性,只是转氨酶水平高了一些,超过了正常限度,暂时不用中断治疗,过两周再来查。

  • 背景:在治疗初期,因为服用抗生素的剂量比较大,副作用明显,肝功能的某些指标可能出现异常,这有可能是正常反应,不必紧张,应当判明原因之后再做决定。

7月2日,周四。去医院查血常规、肝功能。血常规的结果一切正常,肝功能显示转氨酶水平已经降下来了,但还是超过正常水平一点点,负责通知的医生说:你改天再来医院找大夫看看吧,到底要怎么办。

7月7日,周二(读者可能注意到了,我一般在周二或者周四去医院,因为该院只有周二和周四才能做血液检查,为了免除多跑一趟的可能,我尽量选择这两天去)。我又一次来到医院,医生说,转氨酶水平已经降下来了,目前稍微高一点,不是问题。从开始治疗到现在,已经两个月了,需要拍片比较(这一次是免费的)。把两张X光片放在一起比较,可以看出病灶处的阴影已经减少了很多,效果比较明显。“从现在的状况看,可以停用乙胺丁醇和吡嗪酰胺了,你只需要坚持服用异烟肼和利福平,坚持四个月,应该就可以痊愈了。”
这真是一个好消息!

总结这几个月的经历,有几条经验:

  1. 定期体检是一个好的习惯。有人觉得自己“身体好”,也有人怕检出问题心慌,但是,这都不应该成为拒绝体检的理由。许多时候,细密客观的医学指标,比笼统的生理感觉可靠得多。
  2. 多了解一些疾病的早期症状是非常有利的。李笑来老师曾说,如果他明白自己的许多反应是糖尿病的“征兆”,就会更早地发现问题所在。我自己的情况也是,虽然我发现的时候不算晚,但如果知道盗汗、低热和精力下降可能是肺结核的反应,我也会更早地开始治疗。这方面,我想到的一个好办法是阅读医学手册,看过一遍,就能大致知道什么病有什么症状。推荐默沙东制药《默克家庭诊疗手册》《默克诊疗手册》,有纸版书,默沙东中国网站还可心地提供了中文版在线阅读和查询
  3. 治病的道理,同其它许多事情一样。如今医学已经非常昌明了,对许多疾病都有可靠的疗法,但这并不是说患者就可以高枕无忧:一方面要充分了解、摆正心态,既不能恐慌,也不能无视;另一方面,也需要遵照医嘱,配合治疗,定时定量服药。有句老话说:“持之以恒,万事必成”。治病也是这样。
  4. 结核病治疗期间,应当尽量保证营养(肉类、蛋类、蔬菜类),并注意休息。照中医的说法,必须“静养”,而且身边人确实有“静养”之后迅速康复的例子,但照我的经验,减少劳作,按时作息,也可以保证疗效。
  5. 生病期间,心态难免会有变化,这时候,亲人和朋友的支持是很重要的;如果自己生病了,应当感激他们的每一点支持和关爱,如果亲人或朋友生病了,不妨多尽一份力量去关爱他们吧。

出版两年之后,《精通正则表达式》马上要第四次重印了,这个消息很是让我兴奋。

我读大学的时候,有幸接触到侯捷老师的许多文章,尤其是他谈关于选择技术书籍的言论,感觉受益匪浅——正是从此,我深刻认识到,“学习”的宾语不应该是“教材”,而是“知识”。认识到这一点,就豁然开朗了;当然,也无比真切地知道了好的书籍是多么重要。

另一方面,我也深信,总的来说,知识的价值是在传播中实现的。我经历过“有了新的收获自己保密,一人独享”,也经历过众人把自己的心得拿出来分享、彼此协作的环境,两厢对比,后者提供的满足感远远超越前者。因此,有更多的人迅速学会“卑之无甚高论”的正则表达式,不需要重走我自己当初学习的弯路,对我来说,也是一种不小的满足(相比之下,帮人写各种表达式所得到的满足,实在是“很小很小”)。

在这里还要感谢博文视点的编辑许莹,她细心地把目前勘误列表列出的所有错误都做了订正。因为《精通》一书中存在的错误,始终是我的一块心病。

另外,要兑现我年初的计划,今年要写一本关于正则表达式的书,正好在这里征集大家的意见:你们是期望它更加“下里巴人”,包括Word, EditPlus等等常用软件的应用例子,以应付更广泛的工作呢;还是希望更加“阳春白雪”,与狭义的“IT行业”(也就是开发)靠的更近呢? 或者有什么别的想法,还请不吝赐教。