兼济天下则达 独善其身则穷
不知不觉中,已经开始坚持跑步三周年了,现在跑步已经成为了我生活中的一部分了,几天不跑步,腿就痒了,要的就是这种效果。
翻开日记,才 发现记录的很乱,已经不清楚哪一天有跑步了,跑的距离和时间,混乱的生活啊。研三上学期,正是找工作的混乱时期,没想到找一份工作就把我整的这么惨,连记 日志的方式都改变了,不再是那么密密麻麻的记录每天干了什么,而是每天就几句话,一笔带过。因为如此,已经不知跑步的里程,可是有一点可以肯定的是,我已 经离不开跑步了。研三下学期,生活更加混乱了,找完工作,还要写毕业论文,这更加痛苦,生活也更加混乱,导致我的胃一度犯病,胃病和阑尾炎一并来犯,把我 给吓坏了,还好扛过来了。
真正有计划的跑步,还是在参加工作之后。因为心中一直有参加马拉松的梦想,这个梦想是由三年前的一个小伙子点燃 的。三年前,那时我还在扬州。一个跑步爱好者对我说,看你双目无神,实则对生活充满了渴望,眉宇间散发着一股孤独的气息,再加上你体格健壮,极为适合跑马 拉松。瞬时感觉遇到知音,为报答知遇之恩,三年间从未间断跑马拉松的想法,参加工作后,终于有机会完成了。我住的地方正好在主江边上,而公司的时间很弹 性,所以工作之余,有足够的时间来实施我的跑步计划。
从7月8号入职,每个星期都会去跑上三次。刚开始的时候,因为体力已经退化,只好在 中大校园的操场上练习,后来由于操场是晚上9:30就关门了,而我的体力也逐渐增加,于是改道去珠江边跑。老实说,珠江边的空气还是极为不好的,时不时传 来一股鱼腥味,偶尔还有一股尿骚味,只是没有更加合适的地方。刚开始是往广州塔这个方向跑,发现这里人太多,于是改为往人民桥方向。毕竟还是有基础的,很 快速度和体力又回到了10KM/h,隔天跑上个10KM,呼吸非常自如。后来因为报名了深圳的山地马拉松,于是和公司的同事胡志容一起去跑白云山。志容已 经参加过三次马拉松了,在他的带领下和鼓舞下,跑了六次白云山,我的体力也有了明显的进步。
骑行也有助于提高耐力。用第一个月工资买了勇 士500后,到处骑行。在单骑走从化凤凰水库后,我的耐力更上一层楼了。可是马拉松的距离对于这平时的训练还是太远了,于是得跑更远的距离。周末的时候, 往更远的琶洲大桥进发,虽然很累,但还是坚持下来了。慢慢的20KM,25KM,30KM,已经具备了跑完马拉松的实力了。果然,顺利的完成了广州马拉 松。
用了三年的时间,完成了人生的第一个马拉松,也算是对那个小伙子的一个回报。老实说,还真得感谢他,要不然我不知道还要多久才能发现自己跑马拉松能力。
现在跑步已经融入我的生活了,也难怪在日记不再会记录跑到哪里,不再记录时间和距离。因为跑步对于我来说已经像吃饭一样了,试问,有谁会在日记里几下今天吃了什么饭,吃了多少,用多少时间呢?
在前面的介绍中,都没有处理更新和删除问题,这里有必要说说。在关于sphinx引擎的一些想法中说过公司所用的引擎中,处理更新和删除的办法是在索引中增加一个属性来标志这条记录是否失效,每次做增量时,就要去主索引和增量索引中更改相应id的属性值,这确实可以解决问题。不过并不是一个很好的解决办法,Sphinx的作者也说过这种方法既麻烦又容易出错。既然有更新和删除这个需求,必然会提供解决的办法,这个办法就是kilst。所谓的klist,就是kill list,按照字面理解,就是删除列表。我们只需要在增量索引中保存一个id列表,搜索时,如果在主索引中搜到相关文档,而文档的id存在于增量索引的id列表中,则这个文档将被丢弃。
这里有一个需要注意的是,当文章被删除时,仅仅通过增量抓取,在增量索引中并不能知道主索引中哪一个文档被删除了,所以这就必须在表中文档被删除时,能够记录下被删除的id,这就需要用到触发器,也需要建立一个辅助表来保存这些id。辅助表的建立如下:
create table sphinxklist( id integer not null, ts timestamp not null );
触发器的建立如下:
DELIMITER // CREATE TRIGGER sphinx_kill AFTER DELETE ON wp_posts FOR EACH ROW BEGIN INSERT INTO sphinxklist VALUES (OLD.ID, NOW()); END //
有了这些准备工作后,我们就可以使用klist了,事实上在之前的配置文件的基础上,只需要修改一点点内容就好了。首先修改主索引
source srcmain : base{ sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = UPDATE sphinx_helper SET main_tmp_maxts=NOW() WHERE appid='blog_search'; sql_query = \ SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified < (SELECT main_tmp_maxts FROM sphinx_helper WHERE appid='blog_search'); sql_query_post_index = UPDATE sphinx_helper SET main_maxts=main_tmp_maxts WHERE appid='blog_search'; sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search'); sql_attr_timestamp = post_modified sql_field_string = post_title }
可以看到,相对于之前的配置,这里只添加了一行
sql_query_post_index = DELETE FROM sphinxklist WHERE ts < (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search');
添加这行是为了防止之前运行引擎时留下的id再次被使用。
之后修改临时索引:
source srcdelta_temp : srcmain { sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = SET @maxtsdelta:=NOW(); sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search'; sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\ AND post_modified < @maxtsdelta; sql_query_killlist = SELECT ID FROM wp_posts WHERE post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE \ appid='blog_search') AND post_modified < @maxtsdelta UNION SELECT id FROM sphinxklist; sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search'; }
也只是添加了一行,也就是将这次抓取的id与sphinxlist中的id合并。 之后还需要修改Shell脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/bin/bash baseDir=/home/long/sphinxforchinese/blog_search conf=$baseDir/etc/blog_search.conf binDir=$baseDir/bin cd $binDir while [ true ] do #./indexer -c $conf --merge-klists --rotate --merge delta deltaTemp ./indexer -c $conf --merge-klists --rotate --merge delta delta_temp if [ "$?" -eq "0" ]; then cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog ./indexer -c $conf --rotate delta_temp fi sleep 60 done |
这个脚本相对于原来的只增加了--merge-klists这个参数,这个参数的意义是,将delta_temp合并到delta时,并不会删除delta的klist,而是将delta_temp的klist和delta的klist合并,这正是我们想要的。经过这样的变化,一个可以处理更新和删除的main+delta索引就建好了。
感谢Sphinx团队,感谢Sphinx-for-chinese团队,给我们提供了一个这么好用的开源引擎。
在上篇中,我们介绍了一种建立主索引和增量索引的方法,这种方法有一种不足之处就是会改变主索引,因为每次增量索引都会与主索引合并成新的主索引。为此,我们可以想出另一种解决的办法,每次只改变增量索引,这就需要另外再建立一个临时索引。
这里只需要改变少量地方,一个是增量索引,另外还需新增一个临时索引,具体配置如下:
source srcdelta : srcmain{ sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' limit 0; sql_query_post_index = } source srcdelta_temp : srcmain { sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = SET @maxtsdelta:=NOW(); sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search'; sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\ AND post_modified < @maxtsdelta; sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search'; } index delta_temp : main{ source = srcdelta_temp path = /home/long/sphinxforchinese/blog_search/var/data/delta_temp }
实际上,我们是先建立一个空的增量索引,之后临时索引中的数据慢慢合并到增量索引中。在这里,增量索引很像上篇中的主索引,而临时索引则像上篇中的增量索引。 此时我们需要修改dist_blog_search,即增加临时索引
index dist_blog_search { type = distributed local = main local = delta local = delta_temp agent_connect_timeout = 1000 agent_query_timeout = 3000 }
此后还需改变Shell脚本的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash baseDir=/home/long/sphinxforchinese/blog_search conf=$baseDir/etc/main_delta_temp.conf binDir=$baseDir/bin cd $binDir while [ true ] do ./indexer -c $conf --rotate --merge delta delta_temp if [ "$?" -eq "0" ]; then cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog ./indexer -c $conf --rotate delta_temp fi sleep 60 done |
事实上,改变的内容还是很少的。经过这样的改变,我们就无需再改变主索引了。第一次建立主索引后,就一直保持不变,变化的是增量索引。
虽然只建立主索引就可以满足许多应用,但当数据非常多时,每次都重建索引是一件非常耗时的事情,而且每次重建都会浪费CPU,这也是极为不好的。考虑这样一种情况,在数据库中一共有1千万个文档,而每天只新增一万个文档,如果每次都要重建索引,则第一次重建时,是1001万个文档,第二次时是1002万个文档,这都非常耗时的。如果建好主索引后,只对这些新增的一万个数据建一个增量索引,之后把它合并到主索引中,所需的时间将缩短。所以建立main+delta索引是一个不错的选择。
这里依然以之前的博客搜索为例。为了便于做增量,我们需要记录每次抓取的时间,而为了持久保存这个时间,我们需要在数据中建立一个辅助表,建表语句如下
create table sphinx_helper( appid varchar(300) not null primary key, main_maxts datetime, main_tmp_maxts datetime, delta_maxts datetime, delta_tmp_maxts datetime ); insert into sphinx_helper (appid) values ('blog_search');
在wp_posts表中, post_modified这个时间字段是随着每次文章的更新而自动变化的,所以可以使用它来做增量。主要思路就是用一个值来保存上次增量索引的时间,当需要再做增量索引时,则只需索引从这个保存的时间到现在这段时间里的数据。在sphinx_helper中,这个值用main_maxts来标示。对于主索引,写成配置文件如下,
source base{ type = mysql sql_host = localhost sql_user = root sql_pass = 123456 sql_db = blog sql_port = 3306 } source srcmain : base{ sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = UPDATE sphinx_helper SET main_tmp_maxts=NOW() WHERE appid='blog_search'; sql_query = \ SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified < (SELECT main_tmp_maxts FROM sphinx_helper WHERE appid='blog_search'); sql_query_post_index = UPDATE sphinx_helper SET main_maxts=main_tmp_maxts WHERE appid='blog_search'; sql_attr_timestamp = post_modified sql_field_string = post_title }
以上就是主索引的配置,之所以需要将NOW()得到的时间保存到数据库中,之后在sql_query_post_index中取出来用,是因为sql_query_post_index和sql_query不是用一个数据连接。而之所以在sql_query_post_index里才更新main_maxts,是为了保证只有在索引成功建立后才更新这个值。而对于增量索引的配置,则如下:
source srcdelta : srcmain { sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query_pre = SET @maxtsdelta:=NOW(); sql_query_pre = UPDATE sphinx_helper SET delta_tmp_maxts=@maxtsdelta WHERE appid='blog_search'; sql_query = SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified >= (SELECT main_maxts FROM sphinx_helper WHERE appid='blog_search')\ AND post_modified < @maxtsdelta; sql_query_post_index = UPDATE sphinx_helper SET delta_maxts=delta_tmp_maxts WHERE appid='blog_search'; }
在sql_query中可以看到,每次增量索引的数据都是在[max_maxts, NOW()]之间,而只在sql_query_post_index中更新delta_maxts也是基于上述理由。剩下的配置如下:
index main { source = srcmain path = /home/long/sphinxforchinese/blog_search/var/data/main docinfo = extern charset_type = utf-8 chinese_dictionary = /home/long/sphinxforchinese/blog_search/etc/xdict } index delta : main { source = srcdelta path = /home/long/sphinxforchinese/blog_search/var/data/delta } index dist_blog_search { type = distributed local = main local = delta agent_connect_timeout = 1000 agent_query_timeout = 3000 }
这里我们多了一个dist_blog_search,它是结合main和delta的搜索结果,在客户端中搜索时,我们使用dist_blog_search这个索引的结果。剩下的配置与只有主索引时相同,这里就不累述了。
写好配置文件后,还需要有一个步骤。因为我们的策略是每隔一段时间将增量索引与主索引合并,当合并之后,我们需要更新main_maxts这个值。如果我们是每个60秒做一次增量索引,这需要写一个shell脚本,脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash baseDir=/home/long/sphinxforchinese/blog_search conf=$baseDir/etc/main_delta.conf binDir=$baseDir/bin cd $binDir while [ true ] do ./indexer -c $conf --rotate --merge main delta if [ "$?" -eq "0" ]; then cat $baseDir/script/post_merge.sql | mysql -u root --password=123456 blog ./indexer -c $conf --rotate delta fi sleep 60 done |
先执行./indexer -c $conf --rotate --merge main delta
,这句是将主索引和增量索引合并,当合并成功时,则需要到数据库中修改main_maxts这个值,这个句子在post_merge.sql中,post_merge.sql的内容如下:
UPDATE sphinx_helper SET main_maxts=delta_maxts\ WHERE appid='blog_search';
之后进行增量抓取 ./indexer -c $conf --rotate delta
, 这里说说--rotate这个选项,这个选项非常有用。在主索引和增量索引合并时,indexer程序会将这两个索引合并成一个索引,当合并成功后,程序会发送一个SIGHUP信号给searchd,之后searchd就好去加载这个新的索引。
到这里,一个main+delta的索引就完成了。
记得一年前找工作时,阿里巴巴的面试官问了这么一道题,汉字在C语言中是怎么存储的,或者说C语言是如何处理汉字的,对于那时的我是一脸茫然,因为实战经验太少了。前不久,在处理中文标题时出现了乱码问题,组长只在一段代码中改了一句话就解决了问题,觉得非常神奇,于是看这段代码,原来就是关于编码处理的,于是认真的看了这段代码。
通过努力,算是明白了这段代码,其实也就是用了iconv函数族来进行编码之间的转化,再进行特殊处理。例如如果最终目标语言是GBK,可能还需要进行全角和半角之间的转化,繁体到简体的转换。事实上,我们处理时出现了乱码问题正是在繁体到简体的转换时写错了,导致出现乱码。
这里只想写写我自己的理解,在我看来,任何编码保存的文件最后都是字节流,在读取这些字节流时,你只有采用保存文件时采用的编码才不会出现乱码。这就好像加密与解密一样,例如如果一个文件是用utf-8编码的,但你是用GBK打开,此时就会出现乱码了,因为两种编码的编码方式不同。当然还是有一些例外情况,例如如果你是用GB2312保存的文件,用GBK打开是不会出现乱码,因为GBK是兼容GB2312的,GB2312的所有编码的值在GBK中是一样的,只是GBK还利用了GB2312没有用的一些空间来增加了一些编码。而GB18030则又扩充了GBK,包含了更多的字符。
在C语言中,用字符串来保存文本,这些文本都是字节流,只是每种编码占用的空间不同。例如“我是中国人”这5个字,如果是utf-8编码的,因为每个汉字占用3个字节,则要占用15个字节,用strlen可以测出来。如果是GBK编码的,因为每个汉字占用2个字节,则要占用10个字节。对于这样的回答,不知道那个面试官会不会满意呢?
所以这里有一个问题是,给你一个字符串,如果没告诉你用什么编码的,那么要怎么判断它是什么编码的。
因为一直都对Wordpress自带的搜索功能略有微词,可是又不想去改它,想想自己的博客一天都没有一个人会访问,更不用说这个搜索功能了。因为现在学习使用Sphinx-for-chinese,拿博客的数据来练练手。
先从最简单的情况开始,以后再一步一步的完善功能,这样才符合学习的线路,从易到难,而不是一开始就给你一个很完善的模型,然后改改路径就好了。最简单的情况就是只有一个主索引,然后隔一段时间重建索引。得益于Sphinx的高效,建索引的速度非常快,在文档中说达到了10M/s, 按照一篇文章为4KB计算,一秒钟可以给250篇文章建索引了,对于博客来说,已经足够了。对于其它的应用,当数据不多时,只有一个主索引也是可以的。
这里只使用了wp_posts表中的数据,只是用了ID, post_title, post_content, post_modified四个字段,所以非常的简单,直接上配置文件
source base{ type = mysql sql_host = localhost sql_user = root sql_pass = 123456 sql_db = blog sql_port = 3306 } source srcmain : base{ sql_query_pre = SET NAMES utf8 sql_query_pre = SET SESSION query_cache_type=OFF sql_query = \ SELECT ID, post_title, post_content, UNIX_TIMESTAMP(post_modified) AS post_modified FROM wp_posts WHERE \ post_status='publish' AND post_modified < NOW(); sql_attr_timestamp = post_modified sql_field_string = post_title } index main { source = srcmain path = /home/long/sphinxforchinese/blog_search/var/data/main docinfo = extern charset_type = utf-8 chinese_dictionary = /home/long/sphinxforchinese/blog_search/etc/xdict } indexer { mem_limit = 32M } searchd { listen = 9300 log = /home/long/sphinxforchinese/blog_search/var/log/searchd.log query_log = /home/long/sphinxforchinese/var/log/query.log read_timeout = 5 max_children = 30 pid_file = /home/long/sphinxforchinese/var/log/searchd.pid max_matches = 1000 seamless_rotate = 1 preopen_indexes = 1 unlink_old = 1 workers = threads binlog_path = /home/long/sphinxforchinese/var/data }
相关配置选项的意义可以查看示例,写的非常的详细。这里没有对post_content进行定义,因为只想对这个字段建索引,并不想保存它的原始内容,所以这里使用了默认行为,也就是只建索引。
建好索引,搜索跑步的相关文章,得到如下结果
搜索结果还行吧。
在 关于sphinx引擎的一些想法说过用Sphinx给同事搭引擎,可是那是建立在之前的配置文件之上,我只要依葫芦画瓢,改一改路径以及查询语句就搞定了,实质上没学到什么东西。在我看来,要想真正了解它,还是得重新造轮子,从头到尾自己搭一遍,在这个过程中出现了许多奇怪的错误,在这里记录一下。
1.checking for clock_gettime in -lrt... 这是我遇到的第一个问题,事实证明,这根本不是问题。到Sphinx-for-chinese下载了编译包,开始编译,之后就卡在了这里。刚开始以为是缺少librt,然而我在lib中找到了这个链接库。将编译包放在其它机器上编译,又是可以通过的,百思不得其解。只好到Sphinx-fro-chinese的QQ群里发问,黑猫给出解答是要将librt所在路径加入到etc/ld.so.conf,并运行ldconfig命令。按照他的办法,结果运行ldconfig命令时卡住了,于是可以断定是机器的问题。
2.ERROR: cannot find MySQL include files.
这个问题比较好解决,就是缺少MySQL的库文件。因为虚拟机装的是Ubuntu,只要运行以下命令就好了。
sudo apt-get install libmysql++-dev libmysqlclient15-dev checkinstall
如果是其它系统,相信也是类似的方法。如果已经有库文件了,则只需要将路径加入到/etc/ld.so.conf中,并执行ldconfig命令
3.index 'test1': search error: query too complex, not enough stack (thread_stack=1217498K or higher required).
这也是一个很奇怪的错误。我是按照文档中给出的例子建好索引,之后用命令行工具,也就是search要搜索的,结果就出现了这个错误。在网上搜索这个错误,没找到有用的信息,于是又求助于Sphinx-for-chinese群,群里的人说是因为命令行存在问题,用客户端搜就没问题。于是用客户端搜果然没问题,可是我还是无法释怀,因为之前公司的引擎中,用命令行是没有问题的。于是对照着公司用的引擎中的配置文件,发现配置文件中没有这一行,在自己的配置文件中注释掉这行后,果然没问题了。
所以对于这个错误的解决办法就是,将sql_query_info = SELECT * FROM documents WHERE id=$id这行注释掉.
这个确实太坑人了,连官方的配置文件都会出错,得浪费多少人的时间。
4.ERROR: index 'main': No fields in schema - will not index.
光运行例子是不行的,还是得自己写一些东西,于是将自己的博客文章来搜索。用了Wordpress中wp_posts表中的数据,我只用的四个字段ID,post_title,post_content,post_modified,将post_title,post_content定义成sql_attr_string,sql_attr_timestamp,结果就出现了这个错误。在网上找了,发现在官方bug报告中有提到这个问题
http://sphinxsearch.com/bugs/view.php?id=1632
管理员说,引擎中需要一个全文索引字段,否则就没有东西需要索引了,这样它就不会建索引。管理员建议定义为sql_field_string,这样就会对这个字段既索引又保存内容。对于我的配置,我并不想保存post_content这个字段,所以不想将它定义为sql_field_string,那怎样才能让它只被所以呢?看过文档之后,才知道默认情况下,是会被索引。这也是为什么,在上面的帖子中,将sql_attr_string = text注释掉就可以建索引了。所以我只能说管理员也没有真正理解这个错误的原因,看来不能迷信权威啊。
5.FATAL: there must be 2 indexes to merge specified
这个是在测试Klist的时,出现的。文档中说,当合并两个索引时,使用--merge-klists就可以将两个索引的klist合并,于是我在合并时加上了这个参数。具体如下:
./indexer -c $conf --rotate --merge --merge-klists delta deltaTemp
运行时就出现这个错误,我纳闷了,明明官方文档中说加入这个参数是没问题的。到网上找资料,有人是用--merge-killlists这个参数,试过之后,同样报这个错误。无奈之际,将--merge-klist参数放到--rotate前面,
./indexer -c $conf --merge-klists --rotate --merge delta deltaTemp
奇迹出现了,这次没有报错。我只能说,这真是个坑。
《Introduction to Search with Sphinx》写的还是非常不错的,毕竟是Sphinx的作者,表达能力和写作能力自然非同凡响,关于Sphinx的知识,许多都来自本书。等有时间了,可以将引擎的搭建过程写一写,应该可以帮助一些人。这次搭建过程,我学到了许多,虽然用的是开源的引擎,但真要从头到尾搭建一个引擎,并提供可靠的服务,并不是那么容易的,还是得多实践才行。
经过了四个多月的准备,昨天参加了广州马拉松,并顺利跑完了全程,真心不容易,为此应该好好纪念一下。
前天晚上,很早就上床睡觉了,因为 第二天要早起。可事实上根本睡不着,因为太兴奋了,辗转反侧了很久,不知什么时候睡着了。第二天很早就醒了,迷迷糊糊中还做梦,梦到自己迟到了,吓了一身 汗,突然醒来,才知道是在做梦。继续睡觉,直到5:30分的闹钟响,于是起床,喝了杯水,煮鸡蛋,刷牙,洗脸,吃前一天买的面包和刚煮好的鸡蛋,收拾东 西,出发。走在路上才知道起的还是挺早的,天都还没亮,路上也没行人。和海峰约好在地铁站等的,而小西门也是他的必经之路,于是在那里等他。之后一起去中 大地铁站坐地铁,路上也见到很多人是去参加马拉松的。
到了花城广场,已经很多人了,看到这么多人,顿时心情高涨,将行李包寄存后,做准备 活动,免得跑步时受伤。7:15时,和海峰分开,到各自的集结区,准备开跑。7:30分,开跑。刚开始时,人特别多,无法按照正常的速度跑,而且今天和海 峰约好,要等他追上来和他一起跑,可是过了15分钟后,海峰还没追上来。于是我无聊了,找点乐子吧,看看有没有身材娇好的小美女,因为我认为经常跑步的女 生会有非常不错的身材,果然,不久就被我发现一个。小美女一头长发,颇为专业的跑步套装,红色的跑鞋,极有活力,于是尾随她,即使她身边的那个男的明显是 他男朋友。很不幸,我跑得如此之慢,还是在26分钟时超过了小美女,也罢,继续跑。几分钟后就看到第一梯队的黑人运动员们已经在马路的另一半往回跑了,很 显然冠军会从他们中产生,我辈只能望黑兴叹,跑自己的步。
33分钟时,跑完第一个5公里,速度挺慢的。为了等海峰同学,再降速度,50分钟时,小美女又超过我了,可海峰还没追上来,算了不等他了,按自己的节奏跑吧。于是再次超过小美女,往10公里跑。还好之前一直看到的联合国儿童基金会还在前面,这说明还不算太慢。
59 分钟时,跑完10公里,比第一个5公里快了一些。按照自己的节奏跑起来果然畅快许多,到了猎德大桥时,我已经将联合国儿童基金会抛在后面了,可是下猎德大 桥时,也就是1:20分时,左腹和右腹开始阵痛,这是我始料未及的,平时练习时,跑30公里都没有腹痛过,难道是跑步紧张了,于是只好放慢脚步,很快就被 许多人给超过去了,奇怪的是竟然没看到小美女。实在疼的厉害,只好停下来走路了。
1:30分,在15公里处,喝了一杯水后,右腹的疼痛减轻了许多,难道是因为肚子饿?左腹阵痛不时袭来,只好慢吞吞的继续跑着。
2:07分,跑完半程马拉松,不久,联合国儿童基金会就把我超过去了,而现在我是没这个体力超过她了。在艺洲路上,怡宝的服务团队提供了一根香蕉,吃过之后,阵痛稍微缓解了一些,可以稍微加速了,很快到了滨江东路,
25KM 处,一个小美女把我超过去了,这令我非常吃惊,竟然被一个小女生给超过了,野性的呼唤激起我的斗志,于是超过她成了我的目标,连时间也忘记了。不久另一个 小美女也把我超过去了,很快她也把之前的小美女超过了,不是一般的厉害。不管她,先超过第一个小美女再说,然而要超过她并没那么容易,只是慢慢发现离她越 来越近,超过她只是时间的问题了。
31KM处,终于超过了小美女,近距离看,果然是个小美女,皮肤健康,明显是经常运动的,腿部肌肉协调,令人赏心悦目。从这里开始,一路畅快,平时训练的结果体现出来了,眼见一个一个老弱病残的被我超过去了,心里一阵欢喜。然而,那个超过小美女的小美女依然奔跑在前面,我总差她一百多米。
35KM,这是离小美女最近的一次了,如果我没停下来喝水,我就能超过她了。可是饥渴还是难以抗拒,吃香蕉,喝水之后,小美女又跑远了,而我吃饱了,喝足后,也丧失了超过她的能力,只能继续欺负老弱病残了。
37KM处,那个在31KM处被我超过的小美女又超过我了,于是再次奋起直追。
39KM处,再次超过小美女,看了一下时间,4:05分,只剩下3公里了,肯定可以在4:30内完成了,于是尽自己的力量慢慢跑,这次小美女不要再想超过我了。
4:26到达终点。
跑完之后,在边上休息,小美女也跑完了,和她说起追赶她的事,她笑笑说:你是不是因为被一个女的超过觉得很不爽。我哈哈笑了笑,其实小美女不知道,一路跑来,正是在追逐她的过程中,我的腹痛才消失的,之后才可以发挥正常水平。
这 次马拉松跑成这样,是我未预料到的,竟然腹痛到30KM后才消息,一度都想走下去了,只是我知道一旦我停止跑动的步伐,很容易就会放弃了,因为走路相对于 跑,实在是太舒服了。事实证明,只要坚持,最终会看到好的结果。从31KM处开始,我就一个一个的超过老弱病残,心情极为畅快,直到跑到终点,也只有4个 人从后超过我。
应该说,饼干同学赞助的报名费还是有些效果的,看在她出报名费的份上,跑的还是相当卖力的,奖品也如预期一样,有一条大浴巾,外加一条小毛巾。
这 次对于广州马拉松组委会有两点需要批评的。第一点是,在领取芯片时,竟不提供复印证书这个服务,导致许多忘记带复印件的人要到旁边的中国人寿复印,而复印 费竟要1块钱,商业真是无孔不入。第二点是,换取芯片押金时,竟然只有一个服务窗口,导致排队还押金的队伍异常之长,而当天太阳还是很强烈的,跑完步后, 还要在太阳下暴晒,心情异常不悦。有一点比较好的是,医疗志愿者们的素质相当不错,小白帽,小白挂,绿上衣,看了相当舒服,成为一道亮丽的风景。
以前问过余晟老师,Web开发应该从哪里开始做起,当时余老师回答,先学PHP吧,从一个简单的网站开始做起,前台,后台,功能,设计都走一遍,这样就能 学到很多东西了。可是一直以来都没有动手,临毕业时,因为时间挺多的,于是学着用PHP做一个网站,因为之前有做过欧拉工程的题目,所以打算做一个欧拉工 程的中文翻译网站。真正动手做起来,才知道自己很多都不会。在许多个文件里都有数据库连接的语句,写的乱七八糟的,界面丑陋,不懂设计,不过没关系反正是 自己玩的。
最致命的是我遇到一个问题,那就是在后台输入数据时,内容是放在一个testarea标签里,提交到数据库后,再显示出 来,内容就乱成一团了,原先的段落就没有了。可是去数据库里看,内容还是整整齐齐的分段的,问过同学,一个解决办法是自己在内容里写上标签,然后保存到数 据库,这样从数据库里读出的数据就会带标签的,显示就会正常。这确实可以解决,可是那得多麻烦,我在Wordpress的后台里输数据就不需要这样。最后 毕业了,也没时间去做这个,于是不了了之。
前段时间,志容同学怂恿我去学Django,说用这个开发网站很快,后台基本上不需要自己写。于是又想起以前做欧拉工程中文站的念头,开始做吧。 Django果然强大,如果之前有Web开发经验,看着教程,一步步做,很快就能做好一个网站,主要是它的后台管理做的太好了。可是还是遇到了之前那个内 容乱成一团的问题。问过志容后,说是安装一个富文本编辑器,这样就会将文本转化为HTML,于是照着安装,可是这次是将HTML的源码显示出来,上网查了 之后才知道是经过HTML转义,之需要开启safe就好了。这次显示是正常的,可是确发现从有道云笔记中拷贝过去的文本里,显示出来会多了一些 head,body标签,也就是它将整个文本转化成一个页面了。上网查找不到解决的办法,第二天问志容,他说要看了才知道,问了海峰之后,才知道从网页上 拷贝过去的会显示成一个网页的,要先将文本拷贝到记事本,之后再拷贝到编辑器中就好了。
可是按照这样的方法,结果依然有那些多余的标签,于是我不知道如何做了。
大学时,我的下铺是一个来自陕西的小伙,他的名字叫老大。刚开始的两个月,我们宿舍是非常和谐的,还记得一起做高等代数的日子,整个宿舍都在思考同一到数学题,多么和谐的画面。而我和老大同学则更是好基友了,我们一起去买鞋子,一起去图书馆借书,直到寒冷的日子来临。
那时我对于日光灯还特别畏惧,于是只好把脸朝里面,而我习惯右侧睡觉,于是,我的枕头下面就是老大同学的脚。因为我是睡的这头,所以同睡上铺的张光华同学也 只好和我一起头对着头。最终,秋天来临了,一股味道不时从下面涌下来,我们一致要求老大同学要注意个人卫生,为了此时,我们宿舍展开了激烈的讨论。而在这 个过程中,老大同学撒谎了,本来还是接受他是舍友的,可是他竟然对我们撒谎,我懒得拆穿他的谎言,只是在心里把他加到了黑名单。之后和张光华同学就只好换另一头睡觉了,忍受一下日光灯。
愤怒之余,创作了一首打油诗,聊以自慰。
睡在我下铺的兄弟
你无声地睡在下面
你散发的芬芳让我知道你的存在
睡在我下铺的兄弟
那芬芳沁人心脾
伴随着我度过每个漫漫冬夜
温暖着我的心
多年以后
也许我会忘记你的名字
但忘不了你的芬芳