Archive for the '技术文章' Category
August 6th, 2010 by 张磊
最近在使用gmail时,会遇到原本登录的情况下还会提示输入用户名密码的情况。感觉蹊跷就检查了一下页面源代码,果然是钓鱼行为。源码如下:

我用gmail都是直接点击Google工具栏的按钮,但在家里和公司的电脑都会被劫持,特别地,家里在用Mac公司是Windows,不可能同时中了一样的木马。应该是运营商(两边的网络都是北京联通)做了手脚问题出在网络上。另外,通过搜索ndns01.com域名的相关信息,找到了今年7月的一篇帖子。
解决方案:坚持使用https访问Gmail,而且坚持手动输入URL。
如果已经在这样的钓鱼页面输入过密码,建议立即修改密码,然后检查Gmail账户的过滤器设置,看看有无转发邮件到外部陌生邮箱的过滤器。
UPDATE:看到下方有朋友的留言,说电信线路也有此问题。看起来不像是单个运营商的作为,也许错怪北京联通了。
July 11th, 2010 by 张磊
平时上网用ssh代理,但合上屏幕时就会断掉。用了Macbook之后,随时随地都有shell可以用,于是写了一段脚本来检测ssh tunnel是否还存活。如果没有就启动一个新的连接。
脚本内容:
curl -s -I http://www.google.com/ –socks5 localhost:7070 > /dev/null
[ $? -gt 0 ] && ssh -fN username@hostname -D 7070
脚本很简单,就是使用curl通代理访问一下google(这里也可以是任意别的网站)。如果访问失败了,就重开一个ssh进程监听7070端口。
要使用重连的机制,需要先建立远程服务器对本机的信任关系,这样才可以免输密码。如果觉得搭建信任关系过程太繁琐,可以使用ssh-copy-id这个小工具。
上述两行脚本可以加入到~/.profile(有的也叫.bash_profile)中,这样每次打开屏幕都会做检查,每次开机登录后也会自动连接。如果愿意,也可以加在Crontab中,每隔几分钟跑一下。
这样设置之后,平时基本上不用关心代理了,它会安静地在后台一直跑着。
UPDATE: 我就知道是我老土了。 @murj 推荐autossh,就是类似原理,大家可以关注一下。
July 11th, 2010 by 张磊
今天不小心伸腿时把Macbook电源线踢掉了,电源线和本本是通过磁性吸附在一起,因此本本毫发无损。我第一次体会到这种小设计的大用处,于是觉得是时候写篇文章,谈谈自己使用Macbook半月多来的感受。
4月去香港时通过子宁认识了一个在那边读书的姑娘,等到学生机开卖,就拜托她买了一台最低配的Macbook Pro。图片就不贴了,官网到处都是。随本本还赠送一个iPod Touch,后者已经成为我坐地铁、开会时的游戏机了。
总体来说,对Macbook的感觉是“也就那样”。如果你是个乐于尝鲜的人,那它值得拥有;不然还是别买了,会后悔的。到手的Macbook有太多优点,比如文章开头的电源线设计,比如支持多点触摸的触控板…最令我欣喜的是电池续航能力很棒。但也有太多细节,需要花时间去习惯。
中文输入
默认的中文输入法很差。请教传教士tinyfool之后,装了SunPinyin和FIT。目前在坚持用着FIT。但输入的流畅程度比Windows上的搜狗、谷歌拼音相差甚远。相信这种状况,短期内难以改变。
浏览器
默认的Safari用不习惯──总按错快捷键。后来装了Firefox,搭上熟悉的插件们,这才找回上网的感觉。有朋友总结说,现在操作系统的使用体验,很大程度上取决于浏览器的使用体验,深以为然。
系统相关
MacPorts是个好工具,虽然比不上apt-get那么犀利,但用它装点儿小软件不在话下。
缺少一个SecureCRT的替代品,后来直接用ssh+信任关系,也算好用。搭好了信任关系,想翻墙时、想上服务器时,都省得输密码。
窗口切换挺不习惯。一个应用程序的多个窗口没法用Command + Tab切,只能用Command + `来切换。其实如果窗口开得不多的话,没必要这样子。
键盘
键盘上没有Home/End/PgUp/PgDown/Insert/Delete这6个键,标着”delete”的键其实是”backspace”。当然,消失的按键都可以通过快捷键做到。比如 Home = Ctrl + A; End = Ctrl + E。多用快捷键可以提高效率,但把原来一个按键改成两个的组合,恐怕只会降低效率。
iPhoto
在使用iPhoto以前,我都是自己把照片从相机里复制出来归档。但这个软件改变了我的习惯,它自动同步,并且可以把照片发到Flickr,而且会把相机里的照片按照时间段记录为一个事件,很棒。为什么当初我用Picasa时,就没喜欢上它呢?
XCode
拿到Macbook和Touch之后,尝试写App。好歹也得写个”hello world”出来吧。XCode的代码提示、自动完成很棒,如果用vim来写我真不知道该如何记住那么多又臭又长的类名方法名。当然,我觉得有语义的类名和方法名还是很好的,用Ruby久了应该也会养成这习惯。
今天刚看完《iPhone 3开发基础教程》,这本书有很多推友推荐过。用来入门,值得一看。
买了Macbook,手头的T61还是没法被替代,而且T61确实也不错。世界杯期间一边用T61看球一边用Macbook上网,很有趣。
–
写完文章想起在杭州读大学时,一个用着Macbook的MM找我们宿舍一哥们去修电脑,众人口水不已,YY许久。现在自己买了一台,好像也没啥。当年去修电脑的兄弟,也没有然后。
June 29th, 2010 by 张磊
假如你在Linode入手了一个VPS,迅速地部署了Ubuntu,然后使用一大堆apt-get把LAMP服务都搭好了(这整个过程也就10分钟吧,可以更短)。此时可以算是“It works”,但还颇有一些地方需要调整。本文就在这种场景下,写一下此时可以做的最小优化,作用范围不仅限于Linode、Ubuntu,其他系统也可以参考。希望对一些朋友有帮助。
Linux
日志切分
如果没有日志切分,日志可能很快会把硬盘塞满,最后不得不手动清理。做日志切分推荐用logrotate,易于配置,一旦配置完成就会默默无闻地工作。
调整swapiness
swappiness用来控制使用系统swap的概率,ubuntu内核默认是60。建议修改为0,使系统尽可能使用物理内存而非swap。实际上,在上次Linode升级套餐后,我已经关掉了swap,系统运行得依然稳定。具体修改方法可以百度一下。
UPDATE: 经 @7id 提醒,swappiness参数更多的是降低磁盘io操作,对于内存不是特别小的情况,差别不大。详细请看下方留言。
Apache
启用压缩
启用压缩,可以减少传输的内容。对WebServer来说这几乎是必须的,但默认的Apache安装并未开启压缩。对于比较慢的连接,启用压缩会有更多好处。
调整MaxRequestsPerChild
MaxRequestsPerChild用于设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果这个参数为0,Apache进程占用的内存会只增不减。一些使用Apache的VPS经常遇到iorate很高,可以尝试调整一下这个参数(ref)。
Mysql
关掉InnoDB
如果服务器用来放blog或是论坛,多数时候MyISAM就足够用了。此时可以把InnoDB关掉。my.cnf中的注释说:”You might want to disable InnoDB to shrink the mysqld process by circa 100MB.”。实测在VPS关掉Innodb时虽然没节约了100MB,但50M还是有的,内存珍贵,能省则省吧。
只需在my.cnf加入一行 skip-innodb 就可以把InnoDB的功能关掉。
PHP
开启输出缓冲
在使用mod_php时,如果不开Output Buffering,每一个输出都会使Apache向客户端发送数据,导致效率很低。使用fastcgi时,由于WebServer本身有buffer,影响并不大。但默认的apt-get安装,使用的就是mod _php,因此建议在php.ini里把Output Buffering打开。
–
以上只是一些最初级的调整,叫做“优化”都显得有点夸大。但在初期遇到问题时,调整这些地方往往可以很快见效。
除此之外,强烈建议用nginx换掉Apache。
June 17th, 2010 by 张磊
在Twitter上看到盛大举办的校园牛人大赛,其中有“云计算脚本比赛”。看了下题目,开始悔恨大学里没好好学编译原理。但又手痒,就想用Racc试试。做题过程居然很顺利,从学习Racc到做完前三道题花了两三个小时,于是写一点有关Racc的示例,算是学习笔记。
以“云计算脚本比赛”第一题为例,要求执行如下脚本后,得到“Hello, 老赵”。
SET name = "老赵"
RETURN "Hello, " + name
简单来说,用Racc写成解释器,可以把上面的脚本,转化为Ruby代码并执行。
安装Racc:
gem install racc
创建一个level_1.y文件,定义一个JeffParser类。
class JeffParser
token T_SET T_RETURN T_STRING T_VAR
rule
lines:
| lines T_SET T_VAR '=' T_STRING {@local_vars[val[2]] = val[4] }
| lines T_RETURN exp {result = val[2]}
exp: exp '+' exp {result = val[0] + val[2] }
| T_STRING
| T_VAR {result = @local_vars[val[0]]}
end
先定义几个token。因为语法比较简单,只有 SET/RETURN/变量名/字符串四种。
在rule部分,格式是 token: token token … {action} 。中括号中的action 部分就是Ruby代码了,调试时可以把一些需要的变量打出来。val变量是由左边token的值构成的数组,0表示第一个token的值,依此类推。
之后是词法分析的部分。做的事儿是用正则表达式,把上面定义的4种token识别出来,并放到 @tokens 数组中。
---- header
require 'strscan'
---- inner
def parse(str)
@local_vars = {};
@tokens = []
s = StringScanner.new(str)
until s.eos?
case
when s.scan(/SET/)
@tokens << [:T_SET, s[0]]
when s.scan(/RETURN/)
@tokens << [:T_RETURN, s[0]]
when s.scan(/".+"/)
@tokens << [:T_STRING, eval(s[0])]
when s.scan(/[a-z0-9]+/)
@tokens << [:T_VAR, s[0]]
when s.skip(/[ \t\r\n]/)
else
@tokens << [s.getch, nil]
end
end
do_parse()
end
def next_token
@tokens.shift
end
保存后运行 `racc level_1.y ` 将生成一个名为level_1.tab.rb的文件,这就是我们想要的解释器了。
简单测试(假设脚本存放在level_1.file文件中):
ruby -r level_1.tab.rb -e \
'print JeffParser.new.parse(open("level_1.file").read())'
输出:
Hello, 老赵
当然,题目还只做了一半。题目要求:需要用http访问并得到结果。可以在前面挂一个Webrick来做到,就和Racc没关系了。
后面几道题,也可以如法泡制。
–广告时间–
赵姐夫(@jeffz_cn)说这次的题目是他经手的,后悔没把blog的链接放上去,于是我帮他打个广告,欢迎大家猛点此处。
March 2nd, 2010 by 张磊

前几天HipHop着实火了一把,我也第一时间参照Guide在Ubuntu Server上编译好了HipHop。
之后,又打算把我的blog使用的wordpress编译出来,历经艰难险阻,终于编译成功。在此把一些心得分享出来。
HipHop是什么?
HipHop是Facebook新近开源的一款软件,它可以把php代码转换成c++代码,并将其编译。据称,编译后在性能上会得到较大提升。
一、编译HipHop
- 建议使用ubuntu,参照这个文档,可以非常快地装好依赖的库。
- 别拿Linode去折腾,内存太小,吃不消的。如果手头没有合适的环境,建议在RackspaceCloud开一个Cloud Server来做。用完就关掉,估计也就一两块钱的事儿。我是在一个虚拟机上编的。
- 运行完make之后,可执行文件藏在 $
HPHP_HOME/src/hphp下面,一开始我居然没找到。
二、编译PHP项目
- 编译过程中遇到错误,只要进入/tmp/hphp_xxx这个临时目录,解决掉相应问题,在该目录重新运行make即可。
- 如果牵涉到修改php文件,则需要从头开始先生成cpp代码,再编译。
- 如果php项目中有重复的类定义,可能遇到“No rule to make target `cls/atomentry$1.h’,” 这样的错误。WordPress中就有好几处(>=3)。我的解决办法是,把重复定义的类去掉。
- 编译还会遇到类似“undefined variable eo_1”的错误。要解决此问题,可打开相应的cpp文件,在报错行的前一行加入:
Variant eo_1;
- 编译时的参数–cluster-count建议开大点,如果太小,会导致生成少数个大cpp文件,编译时非常占内存。
- WordPress中不需要的主题、插件都可以删掉。惭愧的是,很早以前我写的一个插件,会导致编译出错。
编译WordPress这个大玩意很需要耐心,我连续战斗了三个晚上,修改了多处代码,重编了无数次,终于泪流满面地看到编译成功的信息。
三、运行PHP项目
- 如果打算放到服务器上运行,还需要参考编译hiphop的教程把依赖的库先装好。可以用 ldd 命令查看依赖的库是否都满足了。
- scp到服务器之前,建议先压缩一下。我把WordPress编完将近80M,压缩之后只剩20M。
- 程序作为服务器启动后会有50多个线程,占用100M以上的内存。我没找到线程数这东西在哪里设置,小小一个blog,根本用不着这么多。
- 若打算长期使用而不是玩玩,可以参照这篇文章,使用nginx做一个反向代理。
值得一提的是,hiphop生成的中间代码(cpp代码),可读性相当好。
February 17th, 2010 by 张磊
昨晚不小心点了blog中某文章的“Compare Revisions”功能,深夜把我吓出一身冷汗。
Wake up, 张磊...
The Matrix has you...
Follow the white rabbit.
把这消息发到Twitter(@blogkid),大家都和我说“Knock Knock”。
我想我是遇到彩蛋了(若不是就惨了),于是今天打算寻找一下出处。在目录中grep Matrix、wake up、rabbit都未果,猜到可能是做了加密,于是去翻代码。在Wordpress 2.92版本的wp-admin/revision.php中找到了线索:
54 if ( $left_revision->ID == $right_revision->ID ) {
55 $redirect = get_edit_post_link( $left_revision->ID );
56 include( 'js/revisions-js.php' );
57 break;
58 }
大意是,如果两个相比较的文章ID一致,就会包含js/revision-js.php这个文件。
而wp-admin/js/revisions-js.php文件中,有一堆加密过的字符串。粗略判断,就是看到的这几行字了。下面可以自己触发这个彩蛋,只要构造一个这样的url:
http://you_blog_address/wp-admin/revision.php?action=diff&left=2414&right=2414
把后面的“2414”替换为blog上随便一个已有的文章ID,就能看到彩蛋出现啦。
PS:不知道是不是自己太没娱乐细胞了,初看到震惊了半晌。都是被逼的啊。
PS2:据说早在2.6版本就发现了这彩蛋。
February 16th, 2010 by 张磊
继续压缩话题。之前写了 Rails生成压缩的静态缓存,以及配置nginx以支持直接发送压缩文件两篇文章。今天谈的是在Rails中,将大段文本内容以压缩格式存在数据库。这也是从公司的一个项目中获得的灵感。
原始需求
手头有一个结构简单的文本库,可以看做是key=>value。约180万条数据,2.8G(确实不算大,见笑了)。
- 这个库查询频繁,CPU在IOWAIT耗时颇多。
- Linode服务器硬盘紧张,而CPU富裕。
- 若能通过压缩减小体积,备份/回导时也会更省时。
实施过程
整个实施过程做到了无缝转换,不需要停服务。
第一步:将文本字段类型由TEXT修改为BLOB;新增一个is_gzip字段用于标识是否经过压缩,默认为0。
* 需要注意,BLOB和TEXT最长支持65535字节。
第二步,修改Rails相应的Model。代码示例(压缩content字段):
class TextData < ActiveRecord::Model
def content
if self.is_gzip
Zlib::Inflate.inflate(super)
else
super
end
end
def content=(info_content)
super Zlib::Deflate.deflate(info_content, 9)
self.is_gzip = true
end
end
再一次感叹super的便捷。修改代码后,需要重新部署一下Rails应用。
第三步,创建一个rake任务,用来批量做转换。关键部位只消这么做:
text_data.content = text_data.content
text_data.save
第四步,在做过修改的表上做一次optimize table,以释放那些不需要的空间。
效果观察
- 经如上处理,2.8G数据只剩1.4G。如果用了Gzip而不用Deflate,有可能更小。
- IOWAIT未见明显下降。压缩前,单条记录理论上可以通过一次IO完成。
- 备份的速度确实有所提升,意料之中。
更多信息
- 这种思路同样适用于PHP、Python以及其他语言下的Web应用,只是在Rails中搞起来更为轻松。
- Mysql提供了Compress/Uncompress两个函数,但是不建议直接使用。一方面会增加数据库服务器计算的压力;另一方面,如果用了主从,每个服务器都得算一次。
- Zlib::Deflate.deflate 的第二个参数是压缩的level。我用随机数据测试,Level 9压缩后的体积比Level 1 小10%。
- 如果用了Sphinx之类的从数据库导出数据的全文检索引擎,此法需慎用。
February 15th, 2010 by 张磊
我的Linode十分繁忙,在跑一些密集操作数据库的Rake任务时尤其如此。但我观察发现,Linode服务器的4核CPU,只有第1个核心(CPU#0)非常忙,其他都处于idle状态。
不了解Linux是如何调度的,但目前显然有优化的余地。除了处理正常任务,CPU#0还需要处理每秒网卡中断。因此,若能将CPU#0分担的任务摊派到其他CPU核心上,可以预见,系统的处理能力将有更大的提升。
两个名词
SMP (Symmetrical Multi-Processing):指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。 [更多...]
CPU affinity:中文唤作“CPU亲和力”,是指在CMP架构下,能够将一个或多个进程绑定到一个或多个处理器上运行。[更多...]
一、在Linux上修改进程的“CPU亲和力”
在Linux上,可以通过 taskset 命令进行修改。以Ubuntu为例,运行如下命令可以安装taskset工具。
# apt-get install schedutils
对运行中的进程,文档上说可以用下面的命令,把CPU#1 #2 #3分配给PID为2345的进程:
# taskset -cp 1,2,3 2345
但我尝试没奏效,于是我关掉了MySQL,并用taskset将它启动:
# taskset -c 1,2,3 /etc/init.d/mysql start
对于其他进程,也可如此处理(nginx除外,详见下文)。之后用top查看CPU的使用情况,原来空闲的#1 #2 #3,已经在辛勤工作了。

二、配置nginx绑定CPU
刚才说nginx除外,是因为nginx提供了更精确的控制。
在conf/nginx.conf中,有如下一行:
worker_processes 1;
这是用来配置nginx启动几个工作进程的,默认为1。而nginx还支持一个名为worker_cpu_affinity的配置项,也就是说,nginx可以为每个工作进程绑定CPU。我做了如下配置:
worker_processes 3;
worker_cpu_affinity 0010 0100 1000;
这里0010 0100 1000是掩码,分别代表第2、3、4颗cpu核心。
重启nginx后,3个工作进程就可以各自用各自的CPU了。
三、刨根问底
- 如果自己写代码,要把进程绑定到CPU,该怎么做?可以用sched_setaffinity函数。在Linux上,这会触发一次系统调用。
- 如果父进程设置了affinity,之后其创建的子进程是否会有同样的属性?我发现子进程确实继承了父进程的affinity属性。
四、Windows?
在Windows上修改“CPU亲和力”,可以通过任务管理器搞定。


* 个人感觉,Windows系统中翻译的“处理器关系”比“CPU亲和力”容易理解点儿
—————–
进行了这样的修改后,即使系统负载达到3以上,不带缓存打开blogkid.net首页(有40多次查询)依然顺畅;以前一旦负载超过了1.5,响应就很慢了。效果很明显。
February 14th, 2010 by 张磊

cURL是我在Linux上经常用的一个小工具,我理解它是一个“客户端”。今天记录一下我的使用心得。达人请忽略。
cURL是一个利用URL语法在命令行方式下工作的文件传输工具。它支持很多协议:FTP, FTPS, HTTP, HTTPS, GOPHER等。[更多...]
场景一:测试域名绑定
我常需要在开发环境中,测试某台服务器上的Web Server是否正确绑定了域名。比如,我希望在服务器192.168.1.10上绑定www.blogkid.net。但需要修改hosts才能看到效果,这活儿很累人。
所谓“域名绑定”,就是把host映射到对应的目录。如果手头有cURL,可以使用 -H 参数,在请求头信息中多写一个 Host 字段。就可以测试是否配置正确了。
# curl -H "Host: www.blogkid.net" http://192.168.1.10/
场景二:查看头信息
响应头信息中包含了很多东西。除了HTTP版本和响应代码,还有Server、Content-Type、Content-Length等信息,如果有写入Cookie的操作,也会体现在头信息中。
使用cURL的 -I 参数,就可以看到这些头信息。比如淘宝的:
# curl -I http://www.taobao.com/
HTTP/1.1 200 OK
Date: Sun, 14 Feb 2010 08:57:35 GMT
Server: Apache
Set-Cookie: abt=b; expires=Sun, 28-Feb-2010 08:57:35 GMT; path=/; domain=www.taobao.com
at_catetype: b (咦,这是什么?)
Set-Cookie: _lang=zh_CN:GBK; Domain=.taobao.com; Path=/
Cache-Control: max-age=3600
Expires: Sun, 14 Feb 2010 09:57:35 GMT
Vary: Accept-Encoding
Content-Type: text/html; charset=GB2312
Content-Language: cn
我昨天也修改了一下我服务器的server信息,大家感兴趣可以 curl -I http://www.blogkid.net/ 看看。
这里插一句,不建议把使用Web服务器的版本暴露出来(其实服务器信息也最好隐藏掉,或者把Apache伪装成nginx什么的
)。免得突然爆出漏洞时,措手不及,被人利用。
场景三:跟踪URL跳转
如果遇到了一个多次跳转的URL,可以先用curl的 -L 参数看看,这个URL最终跳转到了什么地方。-L 参数最好配合 -I 使用,不然cURL会把最后一次请求获得的数据输出到控制台。
没有合适的URL拿来做例子,意会一下吧
场景四:发送压缩的请求
cURL提供了一个 –compress 参数,可以用来发送支持压缩的请求。但使用了–compress之后,虽然传输过程是压缩的,cURL的输出还是解压之后的,难以看到效果。
我一般用 -H 参数,自己写一个 Accept-Encoding 字段在头信息中。
curl -H "Accept-Encoding: gzip" http://www.blogkid.net/
如果直接运行上面的命令,会得到一堆乱码,因为cURL输出的内容,是压缩后的数据。不妨在后面接一个gunzip试试。
curl -H "Accept-Encoding: gzip" http://www.blogkid.net/ | gunzip
使用gunzip解压之后,信息又被还原了。前几天我写的压缩话题(1,2),就用了类似的方法来测试。
场景五:忽略证书错误
平日上网,遇到证书错误一定要小心。但我在工作中,经常需要用自签的假证书搭建开发环境。cURL在遇到证书错误时罢工,使用 -k 参数就可以让它不做证书校验。