不同数据中心同步处理

很久之前看过facebook关于不同数据中心之间的延迟处理。

目前我们也遇到类似的问题。这次我们使用的是mongodb。
由于国内恶劣的环境和昂贵的价格,我们被迫将一些大量消耗带宽的服务迁移到国外的主机。这导致了大量的网络延迟。
大概200-400ms。 这样,仅仅依靠mongodb的同步是无法解决数据的一致性了。

由于数据同步量很大,又通过VPN,主数据中心和国外同步的时间周期要以分钟甚至小时计算了。

没办法,只能土法了。

当前解决方案是:

1. 数据一律从国内dc进行更新,此时设置更新数据状态为未同步
2. 数据更新后,发送一个消息到国外dc的队列,对应的worker则开始监控此数据的同步情况
3. 数据未同步前,所有的数据均从国内的主数据进行分发处理
4. 同步完毕,国外dc通知国内dc,将数据状态更改为已同步
5. 一旦同步,后续的数据处理工作自动转发到国外,从而实现分流

缺点:

监控worker和队列的工作状态需要监控。否则会导致流量无法分发。

虽然是土法,但也能解决眼前的问题。

Non-blocking/Evented/IO 将会改变我们传统的编程模式

最近Node.js 很火, 我对此很有兴趣,我自己也在使用Perl的AnyEvent。
Event/IO的一个要点是non-blocking。 Nginx能够解决c10k的问题,一个关键是使用
Event Loop取代传统的thread/pre-fork。要想充分发挥nginx的优势,需要后端和对应
模块都能够non-blocking才行。

然而,现实多数的架构都是blocking的,比如PHP/FastCGI, 即便nginx的前端再好,后端的应用
依然是瓶颈。

所以,如何使用Event/IO实现non-blocking编程,是我们要关注的一个领域。

Ruby的EventMachine, Python的Twisted, Perl的AnyEvent都是不错的开始。

Node.js则是另一个选择。 Javascript似乎是天生的适合作Event/IO的编程语言。 上手轻松,简单。

对于我们来说,最困难的是开始,如何适应新的基于callback的处理模式,这是最大的挑战。

在未来一段时间内,我将把注意力集中到node.js和AnyEvent. 对于我目前正在规划的视觉中国C10平台中,这将是一个非常有趣解决方案。

推荐关于node.js的slide:

http://nodejs.org/cinco_de_node.pdf

Why You Should Pay Attention to Node.Js

ip_conntrack: table full, dropping packet.

流量比较大的服务器上经常出现 “ip_conntrack: table full, dropping packet.” 这类的信息。
由于CentOS/Redhat默认的ip conntrack参数值比较低,要做一些调整:

在/etc/sysctl.conf
net.ipv4.ip_conntrack_max = 131072
在/etc/rc.local 加入:
echo 16384 > /sys/module/ip_conntrack/parameters/hashsize

然后
sysctl -p
echo 16384 > /sys/module/ip_conntrack/parameters/hashsize

这样就调高了ip conntrack table的大小和对应优化ip conttrack模块的hash 大小,对于网络应用链接多的场合适用。

要查看当前链接数:

cat /proc/sys/net/ipv4/netfilter/ip_conntrack_count

在CentOS/Redhat 5.4上使用openswan搭建数据中心之间的VPN

最近由于主数据中心的流量过大,为了分流和成本,在国外购买了几个独立主机。这样就需要在2个数据中心之间进行
数据的同步,包括mysql,mongodb,以及反向代理等。通过在使用IPSec创建VPN从而将2个子网连接在一起是
最合适的方案。

在最初曾经考虑过使用OpenVPN,但是由于我们的应用涉及到较大的数据传输,OpenVPN的性能相对于IPSec的方案还是差,
我们也无法通过为openvpn部署硬件加速。出于成本考虑,最后选择纯软的vpn。

首先尝试使用CentOS自带的IPSec进行配置,不过很快失败。RedHat的文档描述的很不清楚,此外,配置比较繁琐,
按照其说明配置也无法连通,实在没有耐心,于是改用openswan来部署。

在部署openswan之前,先要简单画出2个子网的拓扑图,只要分出“左”《-》“右” 即可:

A(192.168.8.x/211.x.x.x) +————+ B(192.168.9.x/174.xx.xx.xx)

按照上图,A为left 节点,B 为 right节电。192.168.8.x和 192.168.9.x分别为2个网络的网段。
在2个网段分别选择一个主机作为网关,这样分别设置这个2个网关就可以了。
实际为:
gate_a(192.168.8.70/211.x.x.x) , gate_b(192.168.9.1/174.x.x.x)

这2个host都需要有2个网卡,分别是链接内网和公网,记住上面的内网和公网ip。

下面开始安装:(gate_a)

1. 安装openswan
gate_a.nightsailer.com> yum install ipsec-tools
gate_a.nightsailer.com> yum install openswan

注意:下面这步很关键,CentOS-5.4有个bug,安装openswan时自动创建cert db并不正确,所以首先需要
重新创建,否则下一步生成key的时候就会报错。

gate_a.nightsailer.com>certutil -N -d /etc/ipsec.d

2. 现在,为gate_a生成密钥:
gate_a.nightsailer.com>ipsec newhostkey –configdir /etc/ipsec.d/ –output /etc/ipsec.d/keys.secrets

3.显示输出gate_a 作为left的公钥:
gate_a.nightsailer.com>ipsec showhostkey –left
ipsec showhostkey nss directory showhostkey: /etc/ipsec.d
# rsakey Axxxx
leftrsasigkey=0sA………

将eftrsasigkey=0sA….. 这行输出记下,后面备用

4. 修改/etc/ip_sec.conf

# /etc/ipsec.conf – Openswan IPsec configuration file
#
# Manual: ipsec.conf.5
#
# Please place your own config files in /etc/ipsec.d/ ending in .conf

version 2.0 # conforms to second version of ipsec.conf specification

# basic configuration
config setup
# Debug-logging controls: “none” for (almost) none, “all” for lots.
# klipsdebug=none
# plutodebug=”control parsing”
# For Red Hat Enterprise Linux and Fedora, leave protostack=netkey
protostack=netkey
nat_traversal=yes
virtual_private=
oe=off
# Enable this if you see “failed to find any available worker”
nhelpers=0

#You may put your configuration (.conf) file in the “/etc/ipsec.d/” and uncomment this.
include /etc/ipsec.d/*.conf

5. 现在为这个vpn单独创建一个conf文件,放到/etc/ipsec.d/nightsailer.com_vpn.conf

conn nightsailer_vpn
#左节点的公网ip(gate_a)
left=211.x.x.x
#左节点的内网网段
leftsubnet=192.168.8.0/24
#左节点的网关的内网ip(可选)
leftsourceip=192.168.8.70
#左节点的id,可以是ip,也可以是域名:
# leftid=@gate_a.chinavisual.com
# 建议最好用ip,域名需要反向解析,dns没配好很容易出问题
leftid=211.x.x.x
# 上一步显示的gate_a作为left的公钥
leftrsasigkey=0sAQO…
leftnexthop=%defaultroute
#以下参数含义和上面的一样
right=174.x.x.x
rightsubnet=192.168.9.0/24
rightsourceip=192.168.9.1
rightid=174.x.x.x
rightrsasigkey=0sAQOp….
rightnexthop=%defaultroute
#是否在ipsec启动时自动启用这个链接
#auto = add ( 若这个选项,则需要手动up vpn链接)
auto=start

现在,登录到gate_b,重复上面的步骤1-5。

注意,第3步略有不同,因为gate_b是right节点,所以要显示它的right 密钥:
gate_b.nightsailer.com>ipsec showhostkey –right
ipsec showhostkey nss directory showhostkey: /etc/ipsec.d
# rsakey Axxxx
rightrsasigkey=0sA………

将rightrsasigkey=0sA….. 这行输出更新/etc/ipsec.d/nightsailer.com_vpn.conf(包括gate_a上的)

现在分别启动ipsec
gate_a.nightsailer.com>/etc/init.d/ipsec start
gate_b.nightsailer.com>/etc/init.d/ipsec start

好了,现在在gate_a和gate_b上相互ping,就可以ping通了。

网关通了,那么还需要再各自子网添加一个路由:
A网段添加:
route add -net 192.168.9.0 netmask 255.255.255.0 gw 192.168.8.70 eth1
#添加静态路由
在 /etc/sysconfig/network-scripts/route-eth1 追加:
192.168.9.0/24 via 192.168.8.70 dev eth1

B网段添加:
route add -net 192.168.8.0 netmask 255.255.255.0 gw 192.168.9.1 eth1
在 /etc/sysconfig/network-scripts/route-eth1 追加:
192.168.8.0/24 via 192.168.9.1 dev eth1

一切就大功告成了。

快速恢复mysql master-master同步

系统出现错误导致2个Master之间出现不一致。需要立刻同步。
由于错误出现的时间超过了binlog的保留范围(已经被清除),所以只能重新做。

1. 用mmm_control 关闭对应节点
mmm_control set_offline db11
mmm_control set_offline db2

2. 重置master/slave(db2,db11)
reset master;
reset slave;
slave stop

3. 复制最新的master到后备master(from db2 to db11)
使用 mk-parallel-dump和mk-parallel-restore 安全,快速。
xtrabackup 也可以,但不够快速和安全,且需要各个节点配置完全一样。

4. 重新启用replication
db2:
change master to master_host=’192.168.8.11′,master_user=’xxxxxx’,master_password=’xxxx’, master_log_file=’mysql-bin.000001′,master_log_pos=0;
db11:
change master to master_host=’192.168.8.2′,master_user=’xxxxx’,master_password=’xxxx’, master_log_file=’mysql-bin.000001′,master_log_pos=0;

both:
slave start
show master status\G
show slave status\G

5. set db online
mmm_control set_online db2
mmm_control set_online db11

完毕。

令我发狂IE cookie的问题

这个问题基本上可以令人发狂。在 6.0 sp3 091208-2036以后的版本(其他版本也可能有问题)。
如果使用php的set_cookie 如果设置的过期时间比较短,比如180秒后过期,那么cookie将无法创建。
而之前的IE版本以及如Firefox,Chrome,Safari等均没有这个现象。

发现这个原因,是我折腾了一晚上,并且特意弄了台windows,用QQ远程在一个网友的机器上反复折腾,
当临近崩溃的最后一秒发现的。

血泪的教训。 cookie的过期时间最好在1个小时以上,通过将过期时间写入值存入cookie,然后判断,不要依赖浏览器,尤其是
狗屎的IE6.

听说国外有人给IE6举办了葬礼,什么时候国内能有呢,那对于web开发真是个福音。不过,IE7/8对于网银的支持实在
是糟糕透顶,对于我这种mac用户,IE乃至windows的唯一用途就是上网银。。。所以,我的虚拟机用的还是IE6.。。
天大的讽刺!

还好,招商银行支持iphone,方便多了。

Notes: CentOS5.4编译Perl IO::Tty的处理

直接编译IO::Tty,会导致:

Can’t load ‘/root/.cpan/build/IO-Tty-1.08-PWZkbn/blib/arch/auto/IO/Tty/Tty.so’ for module IO::Tty:
undefined symbol: strlcpy at …..

快速修正, 打开Makefile
DEFINE = -DHAVE_DEV_PTMX -DHAVE_GETPT -DHAVE_GRANTPT -DHAVE_OPENPTY -DHAVE_POSIX_OPENPT -DHAVE_PTSNAME -DHAVE_PTSNAME_R -DHAVE_PTY_H -DHAV
E_SIGACTION -DHAVE_STRLCPY -DHAVE_SYS_STROPTS_H -DHAVE_TERMIOS_H -DHAVE_TERMIO_H -DHAVE_TTYNAME -DHAVE_UNLOCKPT -DHAVE__GETPTY

去掉 中间的-DHAVE_STRLCPY和最后的-DHAVE__GETPTY

重新编译, make test.

ok!

技巧:用ETag产生更为有效的304 Not Modified

对于大流量的网站, 尽可能使用304 Not Modified, 这将大大节约带宽的使用,也节省你的资金投入.
通常,对于静态文件, 一般都无须配置, 多数的http server能够产生有效的last-modifed-time和Expires header,
同时下次请求的时候自动判断并输出304.
但是对于动态输出的页面, 依赖时间戳并不是一个有效的方式. 很多时候, 我们需要频繁更新文件,但是
文件内容并没有改变. 这是就要用到ETag了.

关于ETag即Entity Tag是W3C在HTTP 1.1中定义的. 对于动态输出,我们可以使用md5值作为etag的值, 这样
可以当内容没有变更,那么就可以直接返回304了.

结合MongoDB的GridFS,ETag更加有用. 当文件被存储到db中的时候, 计算文件的md5值,保存. 当输出gridfs中的文件时候,
同时输出日期和etag.
以下是一个简单的PHP类, 用于检查是否刷新, 这个类首先检查过期时间, 同时检查etag, 并且etag的优先级高于日期检查:

/**
* Cache Validator
*
* A helper class to mainipulate/validate cache.
*
* The code is ported from my oooold project(eps2004),but works!
* @author night
*/
class CZone_Core_Util_HttpCacheValidator {
/**
* Validate client headers and check wheather to send 304 header.
*
* @param int $lastModified timestamp to validate
* @param string $tag ETag to validate
*/
public static function is_expired($lastModified,$tag,$headers){
// $now = time();
// $headers = getallheaders();
$refresh=TRUE;
if(isset($headers["If-Modified-Since"])) {
$arraySince = explode(";", $headers["If-Modified-Since"]);
$since = strtotime($arraySince[0]);
if($since >= $lastModified) $refresh=FALSE;
}
/**
* Check Entity Tag(ETag)
*
* Entity tags are used for comparing two or more entities from the same requested resource.
* HTTP/1.1 uses entity tags in the ETag (section 14.19), If-Match (section 14.24),
* If-None-Match (section 14.26), and If-Range (section 14.27) header
* fields. The definition of how they are used and compared as cache
* validators is in section 13.3.3. An entity tag consists of an opaque
* quoted string, possibly prefixed by a weakness indicator.
*
* entity-tag = [ weak ]
* opaque-tag weak = “W/”
* opaque-tag =quoted-string
*
* A “strong entity tag” MAY be shared by two entities of
* a resource only if they are equivalent by octet equality. A “weak
* entity tag,” indicated by the “W/” prefix, MAY be shared by two
* entities of a resource only if the entities are equivalent and could
* be substituted for each other with no significant change in
* semantics. A weak entity tag can only be used for weak comparison.An
* entity tag MUST be unique across all versions of all entities
* associated with a particular resource. A given entity tag value MAY
* be used for entities obtained by requests on different URIs. The use
* of the same entity tag value in conjunction with entities obtained by
* requests on different URIs does not imply the equivalence of those
* entities.
*
* See HTTP/1.1(W3C)
*
*/
if(isset($headers["If-None-Match"])) { // check ETag
if(strcmp($headers["If-None-Match"], $tag) == 0 ){
$refresh=FALSE;
}
else {
$refresh=TRUE;
}

}
if(isset($headers["If-Match"])) { // check ETag
if(strcmp($headers["If-Match"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
//firefox style,resume download
if(isset($headers["If-Range"])) { // check ETag
if(strcmp($headers["If-Range"], $tag) == 0 )
$refresh=FALSE;
else
$refresh=TRUE;
}
return $refresh;
}
?>

Patch for build gmagick on mac osx 10.6(snow leopard)

在mac osx 10.6.2(snow leopard)编译gmagick失败. 错误如下:

ld: duplicate symbol _php_gmagick_sc_entry in .libs/gmagick_methods.o and .libs/gmagick_helpers.o
collect2: ld returned 1 exit status
make: *** [gmagick.la] Error 1

感觉很奇怪,因为在centos上没问题. 检查了下gmagick_methods.c和gmagic_helpers.c 也没有重复定义啊.

$ nm .libs/gmagick_methods.o |grep _php_gmagick_sc_entry
000000000000f570 S _php_gmagick_sc_entry
$ nm .libs/gmagick_helpers.o |grep _php_gmagick_sc_entry
00000000000034b8 S _php_gmagick_sc_entry

Oh, ld没错, 的确是重复定义了,由于类型是S,那么还是php_gmagick_sc_entry的声明有问题.
再仔细查看了一下,果然. 由于php_gmagick_sc_entry是在php_gmagick.h中声明,而在gmagick_methods.c
和gmagic_helpers.c中都include了这个文件. 由于没有显示声明为exten导致了问题. 重新加入exten修饰符,
ok.

===========Patch=============
— php_gmagick.h 1970-01-01 17:13:08.000000000 +0800
+++ php_gmagick.h.ns 2009-12-03 01:17:52.000000000 +0800
@@ -18,7 +18,7 @@
*/

#ifndef HAVE_PHP_GMAGICK_H
-# define HAVE_PHP_GMAGICK_H
+#define HAVE_PHP_GMAGICK_H

/* Define Extension Properties */
#define PHP_GMAGICK_EXTNAME “gmagick”
@@ -107,12 +107,12 @@
#endif

/* Class entries */
-zend_class_entry *php_gmagick_sc_entry;
-zend_class_entry *php_gmagickdraw_sc_entry;
-zend_class_entry *php_gmagickpixel_sc_entry;
-zend_class_entry *php_gmagick_exception_class_entry;
-zend_class_entry *php_gmagickdraw_exception_class_entry;
-zend_class_entry *php_gmagickpixel_exception_class_entry;
+extern zend_class_entry *php_gmagick_sc_entry;
+extern zend_class_entry *php_gmagickdraw_sc_entry;
+extern zend_class_entry *php_gmagickpixel_sc_entry;
+extern zend_class_entry *php_gmagick_exception_class_entry;
+extern zend_class_entry *php_gmagickdraw_exception_class_entry;
+extern zend_class_entry *php_gmagickpixel_exception_class_entry;

/* Forward declarations */
PHP_METHOD(gmagick, __construct);
— gmagick.c 1970-01-01 17:13:08.000000000 +0800
+++ gmagick.c.ns 2009-12-03 01:44:20.000000000 +0800
@@ -27,6 +27,13 @@
static zend_object_handlers gmagickdraw_object_handlers;
static zend_object_handlers gmagickpixel_object_handlers;

+zend_class_entry *php_gmagick_sc_entry;
+zend_class_entry *php_gmagickdraw_sc_entry;
+zend_class_entry *php_gmagickpixel_sc_entry;
+zend_class_entry *php_gmagick_exception_class_entry;
+zend_class_entry *php_gmagickdraw_exception_class_entry;
+zend_class_entry *php_gmagickpixel_exception_class_entry;
+
/* {{{ static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
*/
static void php_gmagick_object_free_storage(void *object TSRMLS_DC)
===========END PATCH============

UPDATE: 作者Vito回信很迅速啊, 他只使用Linux,patch已经被采纳了.

解决GraphicsMagick 和 ImageMagick冲突(PHP imagick and gmagick extension)

发现PHP imagick or magickwand无法正确加载. 经过测试发现是由于和gmagick冲突. 解决, 在编译GraphicsMagick时候加入:

–enable-symbol-prefix

重新编译后正常.

Next Page »