技术

setproctitle

从 javaeye 的 robbin 那儿看到 rails 的这个技巧,觉得还是挺实用的,于是就下令在 web.py 上实现类似的功能。

经过 douyuan 同学的一番折腾,然后我也做了几个小实验,最后结论如下:

  • FreeBSD 下有 setproctitle(3) 函数来完成这个功能
  • Linux 对应的是系统调用 prctl(2), option 是 PR_SET_NAME, 宏定义是 15。注意 15 调用是从 2.6.9 引入的,虽然在 2.6.9 的内核头文件 linux/prctl.h 里没有 PR_SET_NAME 的宏定义,但仍然可以直接用 15 来成功调用... 换句话说,RHEL4/CentOS4的系统是可以利用这个特性的

对于 Linux 来说,prctl 并没有修改 argv/cmdline 的值,而是 /proc 文件系统里的内容(comm)。用 top 看是可以看到修改结果的,如果用 ps 命令,必须用参数 c 或者增加 output 格式 comm 才能看到修改结果,比如命令 ps axc (详情请参考 manual)

这样看在 FreeBSD or Linux 2.6.9+ 上用 ctypes 就可以搞定我想要的功能了,但对于更古老的 Linux 内核甚至别的操作系统怎么处理呢?看起来就只能去修改 argv[0] 的内容了,不过修改 argv[0] 也不是一件那么容易的事情,Linux 下运行例程:

  1. #include <unistd.h>
  2.  
  3. extern char **environ;
  4. int main(int argc, char *argv[])
  5. {
  6.     int i;
  7.     for (i = 0; i < argc; i++) {
  8.         printf("%p, %s\n", argv[i], argv[i]);
  9.     }
  10.     for (i = 0; environ[i]; i++) {
  11.         printf("%p, %s\n", environ[i], environ[i]);
  12.     }
  13. }

可以看到命令行参数,以及 environ 环境变量,是存储在一整块连续的内存上的(据说 Solaris 也是这样),如果向 argv[0] 里复制内容过长,有可能造成 environ 被破坏,进而对程序运行产生影响。正确的做法是先把 environ 内容复制出来,再修改 environ[] 数组里的指针地址到新的位置,然后就可以对 argv[0] 为所欲为了。nginx 的 os/unix/ngx_setproctitle.c 也是这么工作的。

换个角度,argv/environ 的这种存储方式使得如果知道所有 argv 的长度之和,从 environ[0] 向前偏移这个数字就是 argv[0] 的地址。这样看写一个 python extension 完成这个功能是可行的——虽然无法在模块里直接获得 argv[0] 地址,但可以取到 environ[0] 的地址,argv 的总长度则可以从 sys.argv 里计算出来然后作为参数传入... 这种方法潜在的问题也不少,比如在一个 python 进程里多次调用该怎么办,可似乎也只能这么来做了。

UPDATE: 发现 prctl 的一个问题是,它最多能设 15 字节长度的 comm, 这就导致可用性降低,也许最终还是要改 argv

Topic: 技术

前两天碰到 greader/gmail 暴慢的故障

前一分钟还好好的,突然 gmail 和 greader 就同时慢了下来

故障现象是加载页面(包括 js)速度还算可以接受,但 Ajax 开始运行后就没有数据返回了。在这个期间内其它同事的应用都很正常,可见不是网络问题。被迫用 gmail 的 BasicHTML 模式——不过也是很慢。持续了大约一天的时间,然后突然又同时恢复。

猜测在请求个人的数据的时候(mail & feed),google 的前端 web 服务器收到 request 后,首先要和某个服务交互一下,然后才能继续请求其它服务。好比以前 gmail 的 addressbook 服务故障后导致所有人登录不了邮箱一样,这次又是 reader 和 mail 同时依赖的一个服务出了故障.

下回再碰到这种情况应该就有经验了,耐心等待就是

Topic: 技术

关于 "#include"

我想写C程序都通常是把系统头文件放在最上面,然后是第三方库的,最后是我自己的,比如下面这样

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. #include <mysql.h>
  5.  
  6. #include <my_foo.h>
  7. ...

前天动手把我们的 apache module 移植到 nginx 上,里面自然是要 include 一堆 nginx 自己的头文件的。但它需要调 openssl 的库,那把 openssl 的头文件放哪里呢?我想了一下,觉得 openssl-devel 是系统自己 yum 安装上的,应该比 nginx 有更高的优先级,于是就是这么一个顺序

  1. #include <openssl/rsa.h>
  2. #include <openssl/pem.h>
  3.  
  4. #include <ngx_config.h>
  5. #include <ngx_core.h>
  6. #include <ngx_http.h>

(你看,我还是相当注重长幼尊卑的,正统思想啊~~)

这样程序似乎也没有问题,可就一个麻烦事情——调用 nginx 的 log 功能时就会 crash!用 gdb 跟踪,发现是使用 request->connection->log 的指针为空...

但就在 nginx 的代码里,向我的 handler 传入 request 之前,r->c->log 还是一个合法的指针,只要一进我的函数,这个就成了 NULL,而且一旦 handler 结束,指针值又非空了..

写到这里,聪明的读者就应该知道这是由于头文件预处理,导致两边结构体定义不一致。connection 这个结构体是如此定义的:

  1. struct ngx_connection_s {
  2. ...
  3. #if (NGX_SSL)
  4.     ngx_ssl_connection_t  *ssl;
  5. #endif
  6. ...
  7. };

就这么一个小小的 bug,足足花了2个小时才找到原因。

获得什么教训呢?

  1. 下回再用 gdb 跟踪到这种情况,应该能第一时间意识到结构体错误
  2. 如果要提供开发接口给第三方,那第三方可能直接使用的结构体里千万不要包括预处理代码
  3. 只要小心,是可以避免预处理的。但人总有大意的时候,那就养成一个好习惯,把预处理部分都放在结构体的最后,这就考验第三方开发者的人品了

最后是一篇新鲜热辣刚出炉的Nginx模块开发备忘:http://www.dup2.org/files/nginx_module_development.html
nginx 的中文 wiki 上有人写了一篇很好的翻译,我的备忘和其比起来,多了一些数据结构的分析。

Topic: 技术

发现 PyCrypto 不如 ctypes 来得方便

至少对于一个C程序员来说是这样..

有一段RSA签名的程序,原本是用 C 调 libcrypto 包装成 jni 由 java 使用的,现在打算在 python 里面实现一个类似的功能,这段 C 代码大概是这样:

int rsa_sign(char *private_keyfile, char *message, int message_len, char *buf, int buf_size)
{
RSA *key;
int malloc_size, rsa_size;

unsigned char *rsa;

BIO *bio;

bio = BIO_new_file(private_keyfile, "r");
if (NULL == bio) {
return 0;
}
key = (RSA *)PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
BIO_free(bio);
if (NULL == key) {
return 0;

}

malloc_size = RSA_size(key);
rsa = (unsigned char *)malloc(malloc_size);
if (NULL == rsa) {
RSA_free(key);
return 0;

}

if (0 == RSA_sign(NID_md5, message, message_len, rsa, &rsa_size, key)) {
free(rsa);
RSA_free(key);
return 0;
}

RSA_free(key);

if (rsa_size/3 * 4 >= buf_size) { /* 结果的 Base64 编码后长度大于 buf_size, 等于也错误(后面需要放 '\0' 的位置) */
free(rsa);
return 0;

}

b64_encode(rsa, rsa_size, buf);

free(rsa);
return 1;
}

可是找遍 PyCrypto, 楞是没有看到怎么实现 PEM_read_bio_RSAPrivateKey 这样从一个 PEM 格式的私钥文件里加载的功能。然后去看哪些基于 PyCrypto 包装的其他的包,比如 ezPyCrypto, Keyczar(这还是 google 发布的),包括 mail-list 上提供的 patch.... 这些似乎倒是可以 load key 了,可缺省好像也并没有对 NID_MD5 的支持, 绝望~~~~

于是抱着试试的心态用 ctypes 直接去调 libcrypto——以前只是在 PyS60 上玩票似的用过 ctypes,以及在 Win32 下用过,而且那时候 ctypes 还不太成熟——结果发现 python2.5 的 ctypes 竟如此的好用:

import ctypes

import base64

crypto_handler = ctypes.cdll.LoadLibrary("/usr/lib/libcrypto.so")
BIO_new_file = crypto_handler.BIO_new_file
PEM_read_bio_RSAPrivateKey = crypto_handler.PEM_read_bio_RSAPrivateKey
RSA_size = crypto_handler.RSA_size
RSA_sign = crypto_handler.RSA_sign
BIO_free = crypto_handler.BIO_free
RSA_free = crypto_handler.RSA_free

NID_md5 = 4

bio = BIO_new_file("/yourpathfile.key", "r")
key = PEM_read_bio_RSAPrivateKey(bio, 0, 0, 0)
r = BIO_free(bio)
if r != 1:
# break here

print 'BIO_free error'

rsa_size = RSA_size(key)
rsa = ctypes.create_string_buffer(rsa_size)

sign_size = ctypes.create_string_buffer(4)

message = "foobar"
if 1 != RSA_sign(NID_md5, message, len(message), rsa, sign_size, key):
print 'RSA_sign error'

RSA_free(key)

print base64.urlsafe_b64encode(rsa.raw)

Topic: 技术

Apache Module 之在各个 request 之间共享数据(资源)的方法

不论是1.3还是2.x,apache 的模式都包括一个进程服务好多次请求后再退出。现在有一个需求,在每个 request handler 里面希望保存一些数据,这些数据在以后该进程处理其他的 request 中可能还要用到。

最开始我从 request->server->process->pool 顺藤摸瓜看到一个貌似是和进程有关系的资源池。于是仿照 RUN_INIT_ONCE 的apr_pool_userdata_get/apr_pool_userdata_set 来访问资源。这样做的确可以在 request handler 之间共享数据,可是进程退出(apachectl stop)的时候不会去调 cleanup,非常之讨厌。

google 了半天,并参考 apache 自己的 LDAP 模块,发现官方解决方案好像是这样的:

  • 定义module_config结构体的时候,增加一个资源指针
  • 注册child_init的hook函数,从传入的第一个参数(pool指针)里面申请资源
  • 用apr_pool_cleanup_register注册cleanup函数在这个 pool 上(这个是cleanup的核心步骤,我感觉理论上完全可以自己重新创建一个pool,或哪怕是自己malloc,但关键是把cleanup函数注册到这个pool上。因为在进程退出的时候apache只会去销毁这个pool上的资源,而cleanup函数是"资源"之一)
  • ap_get_module_config, 把申请到的资源挂到自己定义的config结构体里

这样在进程退出的时候就自动 cleanup 了。这也是为什么到了 apache 2.0 后就不再提供 child_exit hook 的原因,因为开发者觉得由于有 cleanup 注册的机制,对应 child_init 的 child_exit 就不再有存在的必要。

唯一的疑问就是,request->server->process->pool 和在 child_init 时传入的 pool 是什么关系..... 不过懒得再去寻根究底了,先把需求实现了再说。


2008/11/25 UPDATE:上述的过程有一些问题,就是用 module_config 放资源指针,这样会导致虚拟主机的访问可能取不到 config。最简单粗暴的方法可能也是最有效的方法是用全局变量,当然别忘了 static 修饰。

Topic: 技术

取本机所有的 IP

代码修改自 qmail 的 ipme.c , 要点是对于 struct ifreq 里定义了 ifr_addr.sa_len 的系统(比如 FreeBSD),是和 Linux 不太一样的。
在 CentOS 3 和 FreeBSD 4.7 上测试通过

  1. /* ipme.c from qmail */
  2. #include "sys/types.h"
  3. #include "sys/ioctl.h"
  4. #include "sys/socket.h"
  5. #include "netinet/in.h"
  6. #include "net/if.h"
  7.  
  8. int main(int argc, char *argv[])
  9. {
  10.     struct ifconf ifc;
  11.     int sockfd;
  12.     char buf[20000];
  13.     char *ptr;
  14.  
  15.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  16.         return -1;
  17.     }
  18.  
  19.     ifc.ifc_buf = buf;
  20.     ifc.ifc_len = sizeof(buf);
  21.     if (ioctl(sockfd, SIOCGIFCONF, &ifc) == -1) {
  22.         return -1;
  23.     }
  24.  
  25.     ptr = buf;
  26.     while (ptr < buf + ifc.ifc_len) {
  27.         struct ifreq *ifr;
  28.         struct sockaddr_in *sin;
  29.         int len;
  30.  
  31.         ifr = (struct ifreq *)ptr;
  32. #ifdef __FreeBSD__
  33.         len = sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;
  34.  
  35.         if (ifr->ifr_addr.sa_family == AF_INET) {
  36.             printf("%s", ifr->ifr_name);
  37.             sin = (struct sockaddr_in *)&ifr->ifr_addr;
  38.             printf("\t%s", inet_ntoa(sin->sin_addr));
  39.             if (ioctl(sockfd, SIOCGIFFLAGS, ifr) == 0) {
  40.                 if (ifr->ifr_flags & IFF_UP) {
  41.                     printf("\tUP\n");
  42.                 } else {
  43.                     printf("\tDOWN\n");
  44.                 }
  45.             }
  46.         }
  47.  
  48.         // 这段代码来自 qmail 的 ipme.c, 在 FreeBSD 上实测中没有出现过 len 小的情况
  49.         if (len < sizeof(struct ifreq))
  50. #else
  51.         if (ioctl(sockfd, SIOCGIFFLAGS, ifr) == 0) {
  52.             if (ifr->ifr_flags & IFF_UP) {
  53.                 if (ioctl(sockfd, SIOCGIFADDR, ifr) == 0) {
  54.                     if (ifr->ifr_addr.sa_family == AF_INET) {
  55.                         sin = (struct sockaddr_in *)&ifr->ifr_addr;
  56.                         printf("%s\t%s\n", ifr->ifr_name, inet_ntoa(sin->sin_addr));
  57.                     }
  58.                 }
  59.             }
  60.         }
  61. #endif
  62.         len = sizeof(struct ifreq);
  63.         ptr += len;
  64.     }
  65. }
Topic: 技术

文本聚类研究

最近一直在研究文本分类。到今天,终于搞出一个收敛的单词聚类算法。

最主要的时间是在磨刀上——前前后后总共差不多有 1 周的时间在看 sogou 分词库的文档,以及给它开发一个 python-binding

然后是差不多花了半天的时间写爬虫,获得最初的文档库

利用"[PDF] WRM :一种基于单词相关度的文档聚类新方法"里介绍的概念计算单词两两之间的相关度

最麻烦的就是利用相关度对单词聚类,论文里介绍的方法好像并不好用。从周二到周日,都在不停的找论文,试验新算法,运行测试,修正... 的循环里度过

失败无数次后,最后在 Fuzzy C-Means Clustering 的指导下,投机取巧的设计了一个方法以及判断收敛的机制,今天下午已经成功的跑完一次聚类到 250 个集合的过程,另一个聚类到 100 个集合的过程虽然比较慢,但在 2 个小时前也已经结束。Oh, my god, 看起来是可以正常工作了。

感谢分词库开发者,感谢搜狗新闻、关键字广告 TEAM 曾给的帮助

【尤其是 sogou 的开发包,能返回词的词性... 虽然还有瑕疵,但已经对我帮助很大了】

感谢python, 没有它不可能短时间内把算法体无完肤的改上10几次

感谢《鬼吹灯》,周末的晚上是它让我保持清醒,时不时的去看一眼后台跑的计算过程.

感谢家里人忍受我最近的加班

Topic: 技术

在 PyS60 上使用 ctypes 修改 WIFI 的 MTU

自从换了 DELL 无线路由后,俺的 E61i 也出现无法正常连接 WLAN 上网的情况,而且和当初 Vista 的故障一样——可以访问 google,但大部分网站的连接不上,据以往经验,估计还是因为手机的 MTU 大于这个路由器最大支持的能力。

在网上搜了半天,发现在 S60 2nd 机型上,有一种改 genericnif.ini 的方法可以改变 MTU,但是到了 S60 3rd 机型,貌似就只能自己动手写程序了。

但 Symbian 下的 C++ 开发俺不熟啊,PyS60 没有接口啊,怎么办。正好在查相关开发信息的时候,发现 Symbian 出了一个 POSIX 兼容层(S60 平台上的称呼是 Open C),PyS60 社区有人据此移植了 libffi_ctypes 过去;又有人包装了兼容 Python2.5 的 ctypes 提供一些外围函数;wiki 上还有一个 open c 下执行 setsockopt 的例子

于是安装了 Open C 的 SDK, 然后在它的文档里找到这么一段:
The following flags will work only on hardware because it is supported by the underlying symbian API.

  • SIOCGIFMETRIC
  • SIOCGIFMTU
  • SIOCGIFNETMASK
  • SIOCGIFBRDADDR
  • SIOCGIFFLAGS
  • SIOCGIFDSTADDR
  • SIOCGIFPHYS
  • SIOCSIFMETRIC
  • SIOCSIFMTU
  • SIOCSIFFLAGS

只要确认 SIOCSIFMTU 是可以用的就好办了,对一个熟练的 C 程序员且有一定 ctypes 使用经验的人来说,除了平台移植问题外,开发是没有什么困难的。

开发过程如下

  1. 在手机上安装 libffi、Ctypes (_ctypes.pyd)、ctypes
  2. 在 Windows 下安装 S60 C++ SDK,以及 Open C plugin。可以找到 pips_nokia_1_3_SS.sis 等一共 4 个 .sis 文件,都安装到手机上
  3. 找到 winscw\udeb\libc.lib,然后用 dumpbin.exe /exports libc.lib 寻找函数名对应的序号,PyS60 的 ctypes 必须得用序号去定位函数,比如这样
    1. from _ctypes import *
    2. from ctypes import *
    3. libc = dlopen("libc.dll")
    4.  
    5. socket  = lambda x,y,z: call_function(dlsym(libc, "339"),   (x,y,z))
    6. ioctl   = lambda x,y,z: call_function(dlsym(libc, "180"),   (x,y,z))
    7. close   = lambda x:     call_function(dlsym(libc, "57"),    (x,))
    8. perror  = lambda x:     call_function(dlsym(libc, "255"),   (x,))
    9. dup     = lambda x:     call_function(dlsym(libc, "67"),    (x,))
    10. close(2)
    11. dup(1) #把标准错误重定向到标准输出,这样 perror 就输出到标准输出了
    12.  
    13. ntohl   = lambda x:     call_function(dlsym(libc, "168"),   (x,))
    14. ntohs   = lambda x:     call_function(dlsym(libc, "169"),   (x,))
    15. print ntohs(1), ntohl(256)  #简单的测试脚本
  4. 没有 dumpbin.exe 这个工具(VC++)的,也可以用 MinGW 的 nm.exe 来处理 libc.lib,只不过输出结果还需要再处理一下
    1. f = open("c:\libc.txt")
    2. idx = 1
    3. for i in f.readlines():
    4.     if i.startswith("00000000 T "):
    5.         if i[11] == '.':
    6.             continue
    7.         print idx, i[11:].strip()
    8.         idx += 1
  5. 根据 SDK 的头文件,找出 SIOCSIFMTU 这样的常量定义写成 python 语句
    1. IOCPARM_MASK = 0x1fff
    2. _IOC = lambda inout,g,n,l: (inout | (l & IOCPARM_MASK) << 16 | ord(g) << 8 | n)
    3. _IOR = lambda group, num, len: _IOC(0x40000000L, group, num, len)
    4. _IOW = lambda group, num, len: _IOC(0x80000000L, group, num, len)
    5. _IOWR = lambda group, num, len: _IOC(0x40000000L|0x80000000L, group, num, len)
    6.  
    7. SIOCGIFCONF     = _IOWR('s', 3, SIZEOF_IFCONF)
    8. SIOCGIFFLAGS    = _IOWR('i', 17, SIZEOF_IFREQ)
    9. SIOCGIFMTU      = _IOWR('i', 51, SIZEOF_IFREQ)
    10. SIOCSIFMTU      = _IOW('i', 52, SIZEOF_IFREQ)
    11.  
    12. IFF_UP          = 0x1
    13.  
    14. AF_INET         = 0x0800
    15. SOCK_DGRAM      = 2
    16. IPPROTO_UDP     = 17
  6. 还要去找 struct ifconf, struct ifreq 是怎样定义结构体的,结构体大小是多少
    1. SIZEOF_IFREQ = 84
    2. SIZEOF_IFCONF = 8
    3. IFREQ_IDXFMT = '52sH'
    4. IFREQ_MTUFMT = '52sI'
    5. IFREQ_FLAGSFMT = '52sBB'
    6. autopadding = lambda start, size: start + str(size - calcsize(start)) + 's'
    7. ifreq_idxfmt = autopadding(IFREQ_IDXFMT, SIZEOF_IFREQ)
    8. ifreq_mtufmt = autopadding(IFREQ_MTUFMT, SIZEOF_IFREQ)
    9. ifreq_flagsfmt = autopadding(IFREQ_FLAGSFMT, SIZEOF_IFREQ)
  7. 真正的操作很简单:
    1. sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    2.  
    3. result = create_string_buffer(SIZEOF_IFREQ * 20)
    4. ifc = create_string_buffer(pack("II", SIZEOF_IFREQ * 20, addressof(result)), SIZEOF_IFCONF)
    5.  
    6. ret = ioctl(sockfd, SIOCGIFCONF, ifc)
    7. ifc_len, ifc_buf = unpack("II", ifc.raw)
    8. count = ifc_len / SIZEOF_IFREQ
    9. for i in range(0, ifc_len, SIZEOF_IFREQ):
    10.     ifreq = result.raw[i:i+SIZEOF_IFREQ]
    11.     ifr_name, ifr_idx, padding = unpack(ifreq_idxfmt, ifreq)
    12.     ifr = create_string_buffer(ifr_name, SIZEOF_IFREQ)
    13.     ret = ioctl(sockfd, SIOCGIFFLAGS, ifr)
    14.     ifr_name, ifr_flags, ifr_flagshigh, padding = unpack(ifreq_flagsfmt, ifr)
    15.     if not (ifr_flags & IFF_UP):
    16.         print "%s: DOWN" % ifr_name
    17.         continue
    18.     ret = ioctl(sockfd, SIOCGIFMTU, ifr)
    19.     ifr_name, ifr_mtu, padding = unpack(ifreq_mtufmt, ifr)
    20.     print "%s: UP, MTU: %d" % (ifr_name, ifr_mtu)
    21.     if ifr_mtu > 1400: #凡是 MTU > 1400 的,都改成 1400
    22.         ifr = create_string_buffer(pack(ifreq_mtufmt, ifr_name, 1400, padding), SIZEOF_IFREQ)
    23.         if -1 == ioctl(sockfd, SIOCSIFMTU, ifr): foo = perror("why")
  8. 但...为什么 perror 会报 Permission denied 呢? 后来查了半天,发现是 SIOCSIFMTU 需要有 NetworkControl 权限。于是去 这里 申请一个自己手机 IMEI 对应的证书(免费的),证书对应的 key 在这里
  9. ensymble 这个工具来给 libffi/Ctypes/PythonScriptShell(unsigned_testrange) 重新签名,加上 NetworkControl 权限
    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --execaps="ALL-TCB-ALLFILES-DRM" PythonScriptShell_1_4_4_3rdEd_unsigned_testrange.SIS imei_PythonScriptShell_1_4_4_3rdEd.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" libffi.sisx imei_libffi.sis

    ensymble.py signsis --cert=20081008001.cer --privkey=dospy.key --passphrase="" --dllcaps="ALL-TCB-ALLFILES-DRM" ctypes.sisx imei_ctypes.sis

  10. 这下大功告成,我....我终于又可以在家里坐在马桶上用手机上无线了!!
Topic: 技术

给奥组委做的邮件系统容灾方案

据说一共有5家厂商提供了方案,俺代表搜狐提的方案最后得到采纳,日后要说起bj2008,我可以对达达说:当年你老爸也是为奥运建设出过一份力的...

现在残奥会也已经结束,按照当初商定的合同,搜狐的支持服务在一周后也就是9/30将正式终止,我想现在讲一下技术细节应该不会有什么影响了。

要解决的问题简单说就是:万一奥组委的邮件系统瘫痪,怎样才能让邮件系统尽快可用。

系统出问题有很多种,除了硬件故障、软件故障外,网络故障和机房故障也是需要考虑在内的。我根据搜狐邮件中心所能做的工作,提出的是一个异地容灾方案:

灾备系统在我们这里,把它设为低级别的 MX,平时不启动 25 端口,一旦奥组委确认需要启动该灾备系统,则启动 25 端口。将所有收到的邮件,比如说发给 foo@beijing2008.cn 的,利用 Milter 协议的 addRcpt 功能,增加一个 foo@vip.beijing2008.cn 的收件人。vip.beijing2008.cn 是本地 host 的一个邮件域,在紧急情况下,奥组委邮箱的原用户可以登录到这里查看并回复新收到的邮件。而发给 foo@beijing2008.cn 的邮件会在我们的 postfix 队列里等待,一旦奥组委的故障恢复,自然就能重新投递过去。

技术核心就是 Postfix 的 Milter 支持能力,以及针对 beijing2008.cn 的投递不是 DNS 查询而是 transport_table 指定。实施上最麻烦的是用户数据问题,和邮件没任何关系,却花了大量的时间在上面。

灾备准备好了,奥运期间也没有出现突发事件需要考验俺的这个方案。算是好消息,但也有点小遗憾。

最后要说的是,奥组委的IT部门,业务能力还是很不错的,比我以前做项目时接触的几个甲方单位强多了;还有一个首信的同仁,不知道是做项目监理还是负责整个的项目集成,几个电话打下来对他的敬业精神和认真态度也是很佩服。从小见大,奥运会本身怎么怎么样不去评论,但确实是把中国最优秀的人才集中在了一起去支持这个 big project。。。咳咳,上句所说的最优秀人才不包括我.

Topic: 技术
订阅 RSS - 技术 | BT的花