MongoDB 1.4新特性以及如何使用Icc编译 (build with Icc v11.x)

mongodb 1.4.0正式发布,将生产环境的1.2.x升级到1.4.0. 从1.3.2开始我已经在开发环境和个别slave服务器中部署,
没有什么大的问题。
1.4有不少值得称赞的改进,对于开发人员来说,以下是需要关注的几个:

对数组字段的支持明显增强了。

$set ,$unset 都支持数组角标了,也就是可以直接更新或删除数组中的子记录了。
$elemMatch
这个对于使用embbed object来说是必须的。1.2系列的如果要查找embbed object的数组记录,必须匹配全部的属性字段,造成检索
嵌入的数组记录成为一个及其困难的事情。$elemMatch弥补了这个缺陷,基本上打消了我使用embbed array的顾虑。

$addToSet
给数组加入一个唯一的值。1.2版本要使用push模拟实现这个,需要在条件中使用$not,但这样当需要upsert的时候会有问题。所以我的解决方案是先pull再push

$
新增的$数组操作符,用于代表“匹配的数组的记录的下标值。一般是用于配合更新。目前只能应用到结果中的首个记录。

在复制方面,增加了–fastsync选项,适合用于使用起一个slave来快速备份数据。(我经常这么干,比较省心省事)。

=========
使用ICC编译MongoDB是比较麻烦的,它使用scons来做为build系统,相对于传统的autoconfig,automake来说,需要
读懂scons的配置文件。
我直接修改了SConstruct, 加入了几个选项。我对server和client,tool分别选用了不同的编译参数,由于scons的install命令有些问题,
我直接用unix的install来安装的,不具通用性。

SConstruct的patch

[root@nightsailer.com mongodb-src-r1.4.0]# cat mongodb-1.4-icc-build.patch
==============
— SConstruct.1.4 2010-03-27 21:47:22.503284322 +0800
+++ SConstruct 2010-03-27 22:23:48.040272478 +0800
@@ -253,6 +253,32 @@
dest=”sharedclient”,
action=”store”,
help=”build a libmongoclient.so/.dll”)
+#hack for icc compiler by n.s.(nightsailer.com)
+AddOption( “–icc”,
+ dest=”icc”,
+ type=”string”,
+ nargs=0,
+ action=”store” )
+
+AddOption( “–icc-cxxflags”,
+ dest=”icc-cxxflags”,
+ type=”string”,
+ nargs=1,
+ action=”store”,
+ help=”cxxflags for icc” )
+
+AddOption( “–icc-cppflags”,
+ dest=”icc-cppflags”,
+ type=”string”,
+ nargs=1,
+ action=”store”,
+ help=”cppflags for icc” )
+AddOption( “–icc-ldflags”,
+ dest=”icc-ldflags”,
+ type=”string”,
+ nargs=1,
+ action=”store”,
+ help=”ldflags for icc ” )

# — environment setup —

@@ -303,7 +329,7 @@

justClientLib = (COMMAND_LINE_TARGETS == ['mongoclient'])

-env = Environment( MSVS_ARCH=msarch , tools = ["default", "gch"], toolpath = ‘.’ )
+env = Environment( MSVS_ARCH=msarch , tools = ["default","gch"], toolpath = ‘.’,ENV = os.environ )
if GetOption( “cxx” ) is not None:
env["CC"] = GetOption( “cxx” )
env["CXX"] = GetOption( “cxx” )
@@ -357,6 +383,8 @@
for x in GetOption( “extralib” ).split( “,” ):
env.Append( LIBS=[ x ] )

+#hack for icc compiler by n.s.(nightsailer.com)
+icc_build = not GetOption(“icc”) is None
# —— SOURCE FILE SETUP ———–

commonFiles = Split( “stdafx.cpp buildinfo.cpp db/common.cpp db/jsobj.cpp db/json.cpp db/lasterror.cpp db/nonce.cpp db/queryutil.cpp db/cmdline.cpp shell/mongo.cpp” )
@@ -689,6 +717,16 @@
if GetOption( “gdbserver” ) is not None:
env.Append( CPPDEFINES=["USE_GDBSERVER"] )

+ #hack for icc compiler by n.s.(nightsailer.com)
+ if icc_build:
+ print ‘***************WARN:This build only valid for ICC v11.x EM64T ****************’
+ if GetOption( “icc-cxxflags” ) is not None:
+ env['CXXFLAGS'] = GetOption(“icc-cxxflags”)
+ if GetOption( “icc-cppflags” ) is not None:
+ env['CPPFLAGS'] = GetOption(“icc-cppflags”)
+ if GetOption( “icc-ldflags” ) is not None:
+ env.Append( LINKFLAGS = GetOption(“icc-ldflags”) )
+
# pre-compiled headers
if False and ‘Gch’ in dir( env ):
print( “using precompiled headers” )
=============

compile.sh
============
#!/bin/bash
#dirty,hard compiler for n.s.(nightsailer.com)
COMMON_CXXFLAGS=’-fp-model source -unroll2 -axSSE2,SSE3,SSE4.1,SSE4.2 -xSSE3 -static-intel -fpic -fno-strict-aliasing’
CPPFLAGS=’-I/opt/local/include -I.. -pthread’
CXX=icc
VERSION=1.4.0
DEST=/opt/mongodb-$VERSION
BIN_TOOLS=”mongodump mongorestore mongoimport mongoexport mongofiles mongosniff mongostat mongobridge”
BIN_SERVER=”mongod mongos”
BIN_SHELL=”mongo”
LIB_CLIENT=”libmongoclient.a”
build() {
CXXFLAGS=”-O3 -ip -fno-inline-functions $COMMON_CXXFLAGS”
CPPFLAGS=’-I/opt/local/include -I.. -pthread’
scons –release -Q –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” $*
}
build_tools() {
CXXFLAGS=”-O3 -ipo $COMMON_CXXFLAGS”
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” -c all
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” $BIN_TOOLS mongoclient
CXXFLAGS=”-O2 -ip $COMMON_CXXFLAGS”
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” mongosniff
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” mongostat
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” mongobridge
}
build_shell() {
CXXFLAGS=”-O2 -ip $COMMON_CXXFLAGS”
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” -c $BIN_SHELL
scons –release –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” $BIN_SHELL
}
build_server() {
CXXFLAGS=”-O3 -ipo -static-libgcc $COMMON_CXXFLAGS”
scons –release –static –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” -c $BIN_SERVER
scons –release –static –extrapath=/opt/local –cxx=$CXX –icc –extralib=’tcmalloc_minimal’ –icc-cxxflags=”$CXXFLAGS” –icc-cppflags=”$CPPFLAGS” $BIN_SERVER
}

install_mongodb() {
mkdir -p $DEST/bin
mkdir -p $DEST/include/mongo
mkdir -p $DEST/include/mongo/util
mkdir -p $DEST/include/mongo/db
mkdir -p $DEST/include/mongo/client
mkdir -p $DEST/lib64
install *h $DEST/include/mongo/
install -m 0644 util/*.h $DEST/include/mongo/util/
install -m 0644 db/*.h $DEST/include/mongo/db/
install -m 0644 client/*.h $DEST/include/mongo/client/
install -m 0755 -s $BIN_SERVER $BIN_TOOLS $BIN_SHELL $DEST/bin
install -m 0644 $LIB_CLIENT $DEST/lib64
install -m 0644 distsrc/* $DEST
}
build_tools
build_server
build_shell
install_mongodb
==================

在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,方便多了。

PHP:call_user_func_array 导致的诡异事件

最近,在升级原创榜时,发现了Doggy中Dt模版的一个诡异现象。当访问某些页面时,不定期出现Gateway timeout.
PHP没有报任何错误,也没有任何错误日志或者信息输出。我一度认为是模版的Tag有问题,因为当尝试
删除某些Tag后会解决这个问题。好景不长,更多的页面也冒出此类错误,然而当我把模版内部解析缓存关闭后,则不会出现此类错误。 于是转头跟踪Dt模版的内部调用。Dt模版的设计中,当模版第一次被解析后,将模版转换成Doggy_Dt_NodeList对象,并将它缓存到内部cache中。当模版下次被调用时,如果模版没有变动,则直接调用NodeList的render方法,这样可以减少模版解析的时间。然后,当从缓存中被反序列的Doggy_Dt_NodeList,却无法正确执行filter。看下面的一段代码:

if (isset(Doggy_Dt::$filters[$name])) {

foreach ($args as $i => $argument) {
# name args
if (is_array($argument)) {
foreach ($argument as $n => $arg) {
$args[$i][$n] = $this->resolve($arg);
}
}
else {
# resolve argument values
$args[$i] = $this->resolve($argument);
}
}
array_unshift($args, $object);
$object = call_user_func_array(Doggy_Dt::$filters[$name], $args);
}

罪魁祸首是:call_user_func_array(Doggy_Dt::$filters[$name], $args)。

此处调用的call_user_func_array导致无法autoload class。于是php就悄无声息的挂了。

说是诡异,就是因为,如果不是反序列出来的,则能够正常的调用。
此外,在官方手册中,对于call_user_func_array还有一段注释:

Note: Callbacks registered with functions such as call_user_func() and call_user_func_array() will not be called if there is an uncaught exception thrown in a previous callback.

也就是说,如果之前call_user_func_array()导致了未知的异常,那么后续的call_user_func_array则不会被调用。

后来检查,的确模版中也有错误的filter tag。 但,奇怪的是只有是反序列(unserialize)回来的才会重复此类
现象。 因此,未必是完全如注释所说的原因。
我猜测,有可能是call_user_func_array的错误导致了反序列后的对象无法正确再次调用call_user_func_array,至少在call_user_func_array中无法正确autoload class。

解决方法,其实也是修复上面代码的一个bug,在call_user_func_array前增加is_callable判断。
if (isset(Doggy_Dt::$filters[$name]) && is_callable(Doggy_Dt::$filters[$name])) {

}
else {
Doggy_Log_Helper::warn(‘unkown filter:’.$name);
}

于是,世界就太平了。