2009.5.7

May 7th, 2009

此刻我正在实验室,背后是轰隆隆的服务器。看文档背单词灌水发呆等whisper回来吃饭。

此时whisper同学正在五道口某处辛勤的写书,我能想象他写程序的表情和玩戒指的动作,以及被人打断时痴呆的样子…… ——|||

看Google Reader, Space又抽风(告诉我这种团队是如何生存下去的啊啊啊啊啊啊啊),翻出来了好多女王姐姐的旧文,颇为感慨。

最近一直在问自己为什么要出国。后来发现这真是人生谜题之一,每个人境遇特色不同,选择太多其实是最令人心生恐惧的事情。控制欲望才能让心灵平静。然而我做不到。

昨天和whisper在清华散步,坐在图书馆憧憬未来。yy如果我在Stanford,我们的日子将会怎样怎样。想象mountain view的山有多高,天有多蓝,wifi有多快,白人有多少;Bay Area的建筑有多酷,程序员有多牛,生活有多便宜。我想憧憬是couple们在一起时最幸福的时刻之一,但是终究会有多少成分梦想照进现实?

然而我的神经实在是太粗了,纠结总是敌不过自我乐观的美好假象。合上单词书,一节Paganini过去,又觉得宁静幸福。不管前方怎样,要努力,要勇敢,不逃避,不放弃。十指相扣,两个人一个梦想,还不能实现吗?

强迫自己关掉牛校的网页,告诉自己NB的program看看就好,终究还是要回到这里脚踏实地,写两段程序背两页单词,做靠谱实际的工作。我想我还是有很多很美很不切实际的梦想,看来自己还没度过“蒙昧的少女时代”,然而年轻真好不是吗?有那么多可能,就算是泡沫,也耀眼美丽的一塌糊涂。

我确实是好久都没有博一下了。下笔生涩,语言乏力。

特别是当校内这个发文大玩具变成了工作场所之后,无法随心所欲的装酷,就越来越发现自己没有写作的动力了。(顺便鄙视自己做校内隐私5个月都没有把日志相册分组隐私设置做出来……)

我想要的幸福,似乎近在手边,明亮安静。希望可以无所畏惧,看时间流过。

Darkness Darkness Says

透明代理

April 24th, 2009

这两天没时间写spider,主要时间都花在弄透明代理上了。

问题是这样的,假设有两台WWW服务器,其中w1是主力,w2做冗余。后来,w2上部署了新的测试版服务,当服务稳定后,打算把w1也升级了。那么要升级就要满足一下几个条件:

  1. 直接该DNS记录不好,因为一旦w2正式上线失败,就需要2 * TTL左右的时间来切回w1
  2. 基于IP技术的机制不可行,包括换IP、ARP冗余和上级路由,我这是因为交换机锁MAC而我又没权限控制
  3. 没有第三台机器充当balancer

具体结构是:w1上运行apache;w2上运行nginx反向代理apache。为了保险,我首先采取的措施是,让w1上的apache不再处理请求,而是反向代理w2。但是如何让w2上的apache看到真实请求IP呢?方法是X-Forwarded-For

但问题紧跟着出现:如果请求是直接到w2/nginx上,那么nginx就需要使用:

proxy_set_header X-Forwarded-For $proxy_x_forwarded_for

如果请求是从w1/apache上反向代理过来的,那么nginx需要使用:

proxy_set_header X-Forwarded-For $http_x_forwarded_for

。如何协调呢?

这时就需要强大的 if 和 set 指令了:

1
2
3
4
5
set $real_ip $remote_addr;
if ($real_ip = 'address of w1') {
  set $real_ip $http_x_forwarded_for;
}
proxy_set_header X-Forwarded-For $real_ip;

有了上面的代码,就可以正式上线。首先不换DNS,如果有问题,只需要停止w1的反向代理即可;当测试没有问题之后,更换DNS,这时用户会随着DNS的更新逐步由w1迁移至w2,但整个过程不会感觉到任何迁移,因为请求最终都是w2响应的;最后,当确定w1上没有请求时,就可以对w1实施更新了。

还没完,下面介绍另一种方式,一种我没成功的方式,一种充分证明Linux脑残的方式:

  • request from any to w1:80 |-nat_as-> from any to w2:80 |-route_forward-> w2:80
  • reply from w2:80 to any |-route_to-> w1 |->nat_as-> from w1:80 to any |-normal_route-> any
    • 这就需要两次NAT和一次策略路由。
      w1上这样:

      iptables -t nat -A PREROUTING -p tcp -d w1_address --dport http -j DNAT --to-destination w2_address
      iptables -t nat -A POSTROUTING -p tcp -s w2_address --sport http -j SNAT --to-source w1_address

      w2上这样:

      iptables -t mangle -A OUTPUT -p tcp -s w2_address --sport http -j MARK --set-mark 80
      ip route add table 80 w1_network dev eth0 proto kernel scope link src
      ip route add table 80 default via w1_address dev eth0
      ip rule add fwmark 80 table 80

      前一步的NAT很成功,后面的策略路由就没成功过。

      whisper Whisper Says , , , , , , ,

第一个Spider

April 22nd, 2009

开始写Spider,工具是perl + libcurl + SQLite。原理很简单,先从已知的链接抓起,然后从抓来的网页中提取链接,把新的链接放入数据库,然后从数据库取出要抓的链接。

SQLite采用最简单的结果,就一张表:

1
2
3
4
5
6
CREATE TABLE links (
  uri TEXT CONSTRAINT pk_uri PRIMARY KEY,
  code INTEGER NOT NULL,
  type TEXT,
  created_at INTEGER NOT NULL,
  fetched_at INTEGER NOT NULL);

分别记录链接地址、HTTP状态码(未抓取就是0)、Content-Type类型、入库时间和抓取时间

Perl本身有LWP但是速度比较慢,所以选取libcurl,此外使用URI规范链接地址,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use WWW::Curl;
use URI;
 
my $curl = new WWW::Curl::East;
$curl->setopt(CURLOPT_HEADER, 1);
$curl->setopt(CURLOPT_USERAGENT, 'mozillabot');
$curl->setopt(CURLOPT_ENCODING, 'identity,deflate,gzip');
$curl->setopt(CURLOPT_AUTOREFERER, 1);
$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
$curl->setopt(CURLOPT_MAXREDIRS, 5);
 
my $body;
open (my $fileb, '>', \$body);
$curl->setopt(CURLOPT_WRITEDATA, $fileb);
 
my $uri = new URI('http://www.mozillaonline.com');
$curl->setopt(CURLOPT_URL, $url->canonical);
 
my $ret = $curl->perform;
return unless $ret == 0;
my $httpcode = $curl->getinfo(CURLINFO_HTTP_CODE) + 0;
my $ctype = $curl->getinfo(CURLINFO_CONTENT_TYPE);

有了内容下一步就是提取链接地址,使用正则表达式匹配,并插入数据库:

1
2
3
4
5
6
7
8
9
10
my $ins = $dbh->prepare("insert or ignore into links values (?, 0, null, strftime('%s', 'now'), 0)");
my @re = ("<[^>\\n]+href=\"([^\"]+)\"", "<[^>\\n]+href='([^']+)'", "<[^>\\n]+href=([^'\" >]+)[ >]", "<[^>\\n]+src=\"([^\"]+)\"", "<[^>\\n]+src='([^']+)'>", "<[^>\\n]+src=([^'\" >]+)[ >]");
 
my @m;
push @m, map { &absolute($base, $_); } $response =~ m|$_|gi for @re;
 
$dbh->begin_work;
$ins->execute($_) for @m;
$ins->finish;
$dbh->commit;

这里提取的是所有href和src属性里的链接,并根据原始链接把相对地址处理成绝对地址。

关于并发。spider的特点就是要非常好并发,但存在这样的问题:如何保证各个spider之间不抓同样的地址呢?目前的方法是取链接在数据库中行号对spider总数的模,不同的spider使用不同的模。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use Getopt::Std;
 
our($opt_n, $opt_f);
getopt('n:f:');
 
&spawn;
&fetch_url;
 
sub spawn {
        my $pid;
        for (0 .. $opt_n - 1) {
                $serial = $_;
                $pid = fork;
                return if $pid == 0;
                warn 'Fail to fork child' if not defined $pid;
                push @pids, $pid;
        }
        wait for @pids;
}
 
sub fetch {
        while (1) {
                my $links = $dbh->selectall_arrayref("select uri from links where code = 0 and ROWID % $opt_n = $serial order by created_at asc limit 10");
                my @rows;
                push @rows, $_->[0] for @$links;
                sleep 5 unless @rows;
                &extract for @rows;
        }
}

性能问题。根据测量,抓取一个网页的速度在1s左右,虽然不是很快,但也算是能接受。但是经过16个小时10进程的抓取,发现提取出6M链接,但是只完成了44k的抓取。这个问题源于SQLite,但记录数达到M级别的时候,SQLite的查询性能会极度降低;同时作为嵌入式数据库,SQLite的锁是文件锁,性能也很差;而且数据库本身的设计也不是多好。下一步的工作是改用PostgreSQL,并进行能细致的测量。

whisper Spider Journal, Whisper Says , , , , , ,