分类目录归档:网站架构

Sphinx重建索引失效案例分析

重建Sphinx索引的时候,可以使用以下命令:

/sphinx/bin/indexer –rotate idx_name

但是这个命令并不是保证一定能重建成功的,有时候重建了之后,发现新的数据并没有加入到索引里,而从日志可以看到这样的记录:

WARNING: rotating index ‘idx_m_tag’: lock: failed to lock /sphinx/data/idx_m_tag.new.spl: Resource temporarily unavailable; using old index
WARNING: rotating index ‘idx_n_tag’: lock: failed to lock /sphinx/data/idx_n_tag.new.spl: Bad file descriptor; using old index
[Sun Mar 24 06:40:01.084 2013] [12413] rotating finished

日志记录了重建索引失败,继续使用旧的索引这一事实。

要分析这个原因,首先要知道sphinx的重建索引的流程,日志里记录的spl文件,实际是一个空文件,只是sphinx加锁的一个标记而已。重建索引时,会生成新的索引文件,即在原来的文件名字后面加上.new标记为新文件,旧文件不变。然后重启searchd进程,所以我们在执行rotate index的时候会看到这样的输出:

rotating indices: succesfully sent SIGHUP to searchd (pid=12413).

searchd进程启动后,会执行一个操作,把旧文件命名为.old文件,把.new文件更新为正常的索引文件名,如果有问题,会有 一个回滚的操作。在这个过程中,通过对spl这个文件的加锁和解锁来进行文件切换。

如果对spl的锁争用出现问题,就会导致新的索引文件不生效,这时候的解决办法是手动重启searchd进程。

如何避免这个问题,最简单的就是对sphinx日志进行监控,通过脚本捕捉到lock spl文件失败的情况,马上处理。

语音文件转码—适应手机和网页播放

移动互联网时代,语音越来越成为移动应用的主流,手机上一般是amr格式的文件,如果这些文件要在网页上播放,一般需要转换为mp3格式,如何转换?本文介绍一个开源工具——ffmpeg

要支持mp3输出,首先需要安装一个lame包,下载地址如下:http://lame.sourceforge.net/download.php

ffmpeg的安装也比较简单,首先下载源码包,然后在配置的时候指定如下参数:

./configure –enable-libmp3lame

接下来make & make install就可以了。

ffmpeg提供了一个命令行工具,可以直接转换:

ffmpeg -i 1.amr 1.mp3

应用程序可以调用命令行实现文件转换的功能,比如java里调用方式如下:

String command = “ffmpeg -i /tmp/1.amr /tmp/1.mp3″;

Process process = Runtime.getRuntime().exec(command);

如果文件存储在mogilefs分布式系统里,可以使用mogtool命令来实现文件的读取和写入,mogtool要运行需要以下两个包的支持:

MogileFS-Client-1.14

IO-stringy-2.110

读取和写入的命令如下:

mogtool --trackers=192.168.0.27:7001 --domain=voice --class=tui extract 1.amr /tmp/1.amr

mogtool --trackers=192.168.0.27:7001 --domain=voice --class=tui inject /tmp/1.mp3 1.mp3

提供一种思路,希望分享给需要的人。

使用Linux管道批量删除Redis的key

Redis并没有提供批量删除记录的方法,这有时候很不方便,特别是重新初始化数据的时候。一般有两种做法:

如果业务场景明确,可以通过DBID进行区分,Redis默认的DBID是0,默认的配置文件是有16个DB,可以在应用中通过select方法指定数据库的ID进行存取。然后通过FlushDB命令能清空某个ID的数据库,达到重新初始化的目的。

如果某个ID的数据库已经有很多类型的记录了,无法整个库清空,那么就需要批量的删除key值,这时可以采用Linux管道,比如我要删除DBID为1的test开头的key值:

[root@banping redis]# /banping/redis/src/redis-cli -n 1 keys “test*” | xargs /banping/redis/src/redis-cli -n 1 del
(integer) 5095

把keys的输出作为del的输入,这样就批量删除了。

附redis-cli 命令:

redis-cli 2.4.2

Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]
-h Server hostname (default: 127.0.0.1)
-p Server port (default: 6379)
-s Server socket (overrides hostname and port)
-a Password to use when connecting to the server
-r Execute specified command N times
-i When -r is used, waits seconds per command.
It is possible to specify sub-second times like -i 0.1.
-n Database number
-x Read last argument from STDIN
-d Multi-bulk delimiter in for raw formatting (default: n)
–raw Use raw formatting for replies (default when STDOUT is not a tty)
–latency Enter a special mode continuously sampling latency.
–help Output this help and exit
–version Output version and exit

如何使用Amazon的邮件服务SES

云服务提供商Amazon有一项邮件服务,叫做SES(Simple Email Service),如果不想搭建自己的邮件系统,使用这个服务是很不错的一个选择。本文记录如何使用这项服务。

首先需要去Amazon AWS注册一个账号,然后就可以使用这项服务了,首先需要验证几个邮箱进行测试用,可以是用PHP或者Java、Perl等语言编写程序发送邮件,测试完成后,就可以申请转到生产环境,审批申请的时间是一个工作日内,具体官网上都有介绍,比较简单。

SES提供了三种API方式进行交互,一种是SendMail,一种是SendRawMail,还有是传统的SMTP方式,可以按需使用。

如果使用JAVA和SES通信,需要使用官方的SDK,即aws-java-sdk-1.3.5.jar这个包,还需要Apache的httpclient包,也就是httpcore-4.1.4.jar和httpclient-4.1.3.jar这两个jar包。

在测试过程中发现SES代发的邮件会被一些邮件服务商评断为垃圾邮件,进入了垃圾箱,比如163。为了解决这个问题,可以给邮件服务增加SPF、SenderID和DKIM验证,测试证明前两者基本被163忽略,而能解决问题的就是DKIM,有了这个DKIM,Gmail在显示邮件信息的时候甚至会把默认出现的amazon代发的头部信息去掉,很棒。关于DKIM,我以前的一篇文章曾经介绍过,不过那是和Postfix整合的,这次是要通过应用程序来调用,要注意的是,使用DKIM功能必需使用SendRawMail接口来发邮件。

增加SPF和SenderID的方法就是在DNS上增加以下TXT记录:

v=spf1 include:amazonses.com ?all

spf2.0/pra include:amazonses.com ?all

增加DKIM功能,需要先生成密钥:

[root@dev banping]# openssl genrsa -out banping.private 1024

[root@dev banping]# openssl rsa -in banping.private -out banping.public -pubout -outform PEM

然后把public key添加到DNS记录里:

 主机记录:s1._domainkey.mail

内容:k=rsa;t=y;p=……(public key内容)

对于如何使用private key进行签名,可以使用Apache James项目提供的一个包JDKIM,需要apache-jdkim-library-0.2.jar、apache-jdkim-mailets-0.2.jar、apache-mime4j-core-0.7.jar和apache-mime4j-dom-0.7.jar四个JAR文件。

特别要注意的是,JDKIM不能使用原始的Private key,必需经过格式转换和编码,我今天大部分时间都花在了处理这个问题上,因为官方文档并没有说这些,或者说了我没发现,后来看JDKIM的源码才发现了问题所在:

[root@dev banping]# openssl pkcs8 -topk8 -inform PEM -in banping.private -outform DER -nocrypt -out rsapriv.der

[root@dev banping]# base64 rsapriv.der

把base64转码后的输出作为private key参数传给JDKIM的对应函数就可以了,同时,DNS里记录的信息要和这里加密的头信息保持一致,比如在DKIM_HEADER_TEMPLATE里记录:

s=s1; d=mail.banping.com

否则邮件接收方在验证DNS信息的时候不匹配,认证会无法通过。还有就是发件人无法显示中文,会乱码,邮件标题和内容里的中文乱码的问题,可以通过设置utf-8解决。

update 20120330 :如果你的用于发送邮箱配置了腾讯的企业邮箱,那么发送到QQ邮箱的邮件列表会显示Amazon的发件地址,同时会提示此邮件地址未验证之类的信息,解决的办法是不要使用腾讯的企业邮箱。

配置Postfix邮件系统的访问权限

Postfix邮件系统的配置主要通过main.cf文件,路径在/etc/postfix下。如果要允许其他服务器使用Postfix的服务,需要重点关注inet_interfaces和mynetworks这两个参数。

# The inet_interfaces parameter specifies the network interface
# addresses that this mail system receives mail on. By default,
# the software claims all active interfaces on the machine. The
# parameter also controls delivery of mail to user@[ip.address].
#
# See also the proxy_interfaces parameter, for network addresses that
# are forwarded to us via a proxy or network address translator.
#
# Note: you need to stop/start Postfix when this parameter changes.
#
inet_interfaces = all

要注意的是,修改这个参数必须要停止再启动Postfix,单纯的restart是不生效的。

# You can also specify the absolute pathname of a pattern file instead
# of listing the patterns here. Specify type:table for table-based lookups
# (the value on the table right-hand side is not used).
#
mynetworks = 11.12.13.14,127.0.0.0/8

这个参数指定了要使用Postfix的IP地址,如果不设定正确,会在maillog里看到类似如下的提示信息:

Dec 1 11:16:21 mail postfix/smtpd[21197]:warning: 11.12.13.14: address not listed for hostname banping.com
Dec 1 11:16:21 mail postfix/smtpd[21197]: connect from unknown[11.12.13.14]
Dec 1 11:16:21 mail postfix/smtpd[21197]: NOQUEUE: reject: RCPT from unknown[11.12.13.14]: 554 5.7.1: Relay access denied; from= to= proto=ESMTP helo=<banping.com>

自己搭建邮件系统还是比较折腾的,使用Amazon ses是比较便捷的做法。

Redis性能评测

引言

本篇要评测的NoSQL产品是Redis,可以把它的功能理解为一个Key-Value的数据结构操作,数据都保存在内存中定期刷新到磁盘,以极高的读写效率而备受关注。国内的新浪微博就大规模的使用了Redis来存储用户关系和计数。

介绍

按照官方的说法,Redis是用ANSI C开发的,能运行在大多数POSIX(Linux, *BSD, OS X 和Solaris等)系统上,官方没有支持Windows的版本。目前最新的版本是2.2.11,这个版本主要是修复了一个2.2.7版本中遍历方式优化带来的一个bug。

和普通的Key-Value结构不同,Redis的Key支持灵活的数据结构,除了strings,还有hashes、lists、 sets 和sorted sets等结构。正是这些灵活的数据结构,丰富了Redis的应用场景,能满足更多业务上的灵活存储需求。

Redis的数据都保存在内存中,而且底层实现上是自己写了epoll event loop部分,而没有采用开源的libevent等通用框架,所以读写效率很高。为了实现数据的持久化,Redis支持定期刷新(可通过配置实现)或写日志的方式来保存数据到磁盘。

安装和使用

由于没有第三方的包依赖关系,Redis的安装十分简单:

$ wget http://redis.googlecode.com/files/redis-2.2.11.tar.gz

$ tar xzf redis-2.2.11.tar.gz

$ cd redis-2.2.11

$ make

这样就安装完毕了,执行src目录下的redis-server可以启动Redis进程,不过最好先配置一下redis.conf文件,常用的几个要注意的参数如下:

daemonize yes

指定Redis以守护进程的方式运行。

pidfile /home/banping/redis/redis.pid

当Redis以守护进程方式运行时,把pid写入指定的文件。

port 6379

指定监听端口,默认端口为6379。

bind 192.168.0.35

绑定的主机IP地址。

logfile stdout

指定日志的记录方式,默认为标准输出。

databases 16

设置数据库的数量。

save <seconds> <changes>

Redis默认配置文件中提供了三个条件:

save 900 1

save 300 10

save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改的时候,同步数据到磁盘文件。

rdbcompression yes

指定存储至本地数据库时是否压缩数据,默认为yes。

dbfilename dump.rdb

指定本地数据库文件名。

dir /home/banping/redis/data

指定本地数据库存放目录。

requirepass foobared

设置Redis连接密码,默认关闭。

maxclients 128

设置最大客户端连接数,默认无限制。

maxmemory <bytes>

指定Redis能使用的最大内存。

其他更详细的参数说明请参见官方文档。修改完配置文件后,我们可以用指定的配置文件启动Redis服务:

[root@localhost redis-2.2.10]# src/redis-server /home/banping/redis/redis-2.2.11/redis.conf

这样一个redis服务进程就启动了,它监听6379端口来提供服务。

测试说明

一、测试环境

Redis部署在一台PC 服务器上,配置如下:

CPU为Xeon 2.80GHz *4

内存为4G

硬盘为一块400G SATA盘

操作系统为64位CentOS 5.3版本

二、测试方法

这里仍然采用PHP客户端进行测试,Redis官方推荐了两个PHP客户端,一个是PHP程序包Predis,另一个是C开发的扩展包phpredis,我们这里采用后者,网址为https://github.com/nicolasff/phpredis ,可以编译到PHP运行环境中来使用。

为了不对测试服务器产生额外的影响,测试客户端部署在另外一台独立的服务器上,运行的PHP的版本是5.3.5,web server是Nginx 0.8.54,通过fastcgi的方式调用PHP服务。使用apache ab工具实现多个请求和并发操作。

测试过程中就使用上文提到的已经启动的Redis服务,首先是进行写操作,通过500个请求,每个请求写入10000条记录,并发度为2来共写入500万条数据,key为数字1到5000000,value大小为100个字节。然后是读操作,也是用500个请求,每个请求随机根据key值读出10000条记录,并发度为10共读出500万条记录,评测的重点是写入和读出数据的时间,以及在此过程中服务器的资源使用情况。

测试结果及总结

一、写操作

成功写入500万条记录,共耗时524秒,平均每秒写入数据9542笔。磁盘上的数据文件大小134M。写入过程中,服务器内存、CPU和磁盘等资源使用情况如下图所示:

 

 

可见,内存使用平稳上升,最后占用140M左右,主要用来缓存数据,对比数据文件的大小可以设想,在操作系统内存可用的情况下,内存的分配和数据文件的大小是大致相当的。内存占用会有临时的超过实际占用的几个点,而且会马上释放掉,CPU和磁盘IO都表现出周期性的上下波动,估计这些和Redis刷新数据到磁盘的实现机制有关,在高点的时候正好是数据写入磁盘的过程。总体来说占用资源很少,表现也很平稳。

二、读操作

成功读出500万条记录,共耗时184秒,平均每秒读出数据27174笔。

读数据过程中没有发生磁盘IO。CPU有所消耗,Idle值稳定在77左右,等待CPU资源的进程一直是1个。内存表现平稳没有波动。

通过以上测试结果可以看出,Redis数据操作都在内存完成,定期刷新到磁盘,占用的磁盘空间和内存大小由实际的数据量决定,在内存可用的情况下二者是一致的。从功能角度看Redis高效的读写效率和丰富的数据结构能满足很多互联网应用场景的需求,而且在高负载的情况下,数据内存化是趋势,总体来说Redis虽然还在不断发展之中,代码量也不多,但是一个轻量级的很有潜力的NoSQL产品,肯定会有越来越多的成功案例。

注:本文首发在IT168网站,版权所有,转载请注明出处。

MongoDB性能评测

引言

本篇要评测的NoSQL产品是MongoDB,它是现在开源社区里越来越受到关注的一个NoSQL产品,按照官方的说法,是一个可扩展的、高性能的、开源的、面向文档的数据库。Craigslist、foursquare、国内的淘宝网等知名互联网公司都有在他们的生产环境部署了MongoDB。

介绍

MongoDB是用C++开发的面向文档的数据库,因为是面向文档的,也就是反传统的数据库范式来设计的,把相关的对象都记录到一个文档里,每个文档内是schema-free的,通俗的理解,也就是列名可以自由定义,比较灵活,特别是面对业务逻辑多变的应用场景十分给力。数据以BSON(二进制JSON)的格式存储。因为每个文档是一份完整的记录,所以MongoDB和传统的关系型数据库相比,可能带来一定的数据冗余和存储开销。

 

很明显,MongoDB这种面向文档的数据库和传统的关系型数据库的设计思路是差别很大的,因为每个文档都包含了所有信息,和其他文档是没有关联的,这样传统的Join操作就完全没必要了,也正是因为去除了这种“关系”,使得MongoDB的水平拆分更加容易,这也是面对海量数据的一个很好的处理思路。另外,MongoDB的索引机制和MySQL等数据库是一样的,可以利用传统的关系型数据库的经验来使用MongoDB的索引。

 

不像其他很多NoSQL产品由个别工程师根据应用场景开发出来,MongoDB是有一个专门的公司10gen来维护。有一点要注意的是,MongoDB自己是不管理内存的,无法指定内存大小,完全交给操作系统来管理,因此有时候是不可控的,在生产环境使用必须在OS层面监控内存使用情况。

安装和使用

mongodb目前最新的版本是1.8.2-rc3,其源码安装用了很多第三方的东西,比如JS引擎(目前官方推荐的是mozilla的Spider Monkey,以后可能改成google的V8)、正则表达式引擎(pcre)、安装构建工具scons(用python来安装)、boost C++库等等。下面是安装过程:

 

1,下载需要的源文件和相关软件包:

 

[root@localhost mongodb]# wget http://downloads.mongodb.org/src/mongodb-src-r1.8.2-rc3.tar.gz

[root@localhost mongodb]# wget http://sourceforge.net/projects/scons/files/scons/2.1.0.alpha.20101125/scons-2.1.0.alpha.20101125.tar.gz/download

[root@localhost mongodb]# wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.7.0.tar.gz

 

[root@localhost mongodb]# wget http://sourceforge.net/projects/pcre/files/pcre/7.4/pcre-7.4.tar.gz/download

 

2,安装scons:

 

[root@localhost mongodb]# tar zxvf scons-2.1.0.alpha.20101125.tar.gz

[root@localhost mongodb]# cd scons-2.1.0.alpha.20101125

[root@localhost scons-2.1.0.alpha.20101125]# python setup.py install

 

3,安装pcre:

 

[root@localhost mongodb]# tar zxvf pcre-7.4.tar.gz

[root@localhost mongodb]# cd pcre-7.4

[root@localhost pcre-7.4]# ./configure

[root@localhost pcre-7.4]# make

[root@localhost pcre-7.4]# make install

 

4,安装Spider Monkey:

 

[root@localhost mongodb]# tar zxvf js-1.7.0.tar.gz

[root@localhost mongodb]# cd js/src

[root@localhost src]# export CFLAGS=”-DJS_C_STRINGS_ARE_UTF8″

[root@localhost src]# make -f Makefile.ref

[root@localhost src]# JS_DIST=/usr make -f Makefile.ref export

 

5,yum方式安装boost:

 

[root@localhost src]# yum -y install boost boost-devel

 

6,安装mongodb:

 

[root@localhost mongodb]# tar zxvf mongodb-src-r1.8.2-rc3.tar.gz

[root@localhost mongodb]# cd mongodb-src-r1.8.2-rc3

[root@localhost mongodb-src-r1.8.2-rc3]# scons all

[root@localhost mongodb-src-r1.8.2-rc3]# scons –prefix=/usr/local/mongodb –full install

 

这样就安装完毕了,可以简单的启动mongod进程来验证一下:

 

[root@localhost bin]# ./mongod –dbpath /tmp

Wed Jun  8 11:57:38 [initandlisten] MongoDB starting : pid=29700 port=27017 dbpath=/tmp 64-bit

Wed Jun  8 11:57:38 [initandlisten] db version v1.8.2-rc3, pdfile version 4.5

Wed Jun  8 11:57:38 [initandlisten] git version: nogitversion

Wed Jun  8 11:57:38 [initandlisten] build sys info: Linux localhost.localdomain 2.6.18-128.el5 #1 SMP Wed Jan 21 10:41:14 EST 2009 x86_64 BOOST_LIB_VERSION=1_33_1

Wed Jun  8 11:57:38 [initandlisten] waiting for connections on port 27017

Wed Jun  8 11:57:38 [websvr] web admin interface listening on port 28017

可见mongod默认在27017端口监听,而28017端口是web管理的端口,可通过http方式来访问。为了规范,我们用以下命令启动一个mongod进程:

[root@localhost data]# /usr/local/mongodb/bin/mongod –fork –dbpath /home/mongo/data/ –logpath /home/mongo/mongo.log –logappend –directoryperdb –journal –rest

这样一个mongod进程就启动了,它监听27017端口来提供服务,可以在应用程序中进行建立数据库等操作,它不像传统的Oracle等关系数据库那样,建库是个很慎重的工作。要了解更详细的使用MongoDB的信息,读者可以参看官方文档,这里就不提及了。

 

要了解更详细的信息,读者可以参看官方文档。

测试说明

一、测试环境

MongoDB部署在一台PC 服务器上,配置如下:

CPU为Xeon 2.80GHz *4

内存为4G

硬盘为一块400G SATA盘

操作系统为64位CentOS 5.3版本

 

二、测试方法

 

这里仍然采用PHP客户端进行测试,MongoDB官方为PHP开发了一个扩展包可以操作MongoDB,网址为http://pecl.php.net/package/mongo,可以编译到PHP运行环境中来使用。

为了不对测试服务器产生额外的影响,测试客户端部署在另外一台独立的服务器上,运行的PHP的版本是5.3.5,web server是Nginx 0.8.54,通过fastcgi的方式调用PHP服务。使用apache ab工具实现多个请求和并发操作。

测试过程中就使用上文提到的已经启动的数据库实例,首先是进行写操作,通过500个请求,每个请求写入10000条记录,并发度为2来共写入500万条数据,MongoDB的数据格式是BSON方式,不同于其他的NoSQL是key value的方式,因此这里写入的数据格式为:{id,data}的形式,ID为数字1到5000000,data大小为100个字节。然后是读操作,也是用500个请求,每个请求随机根据ID值读出10000条记录,并发度为10共读出500万条记录,评测的重点是写入和读出数据的时间,以及在此过程中服务器的资源使用情况。

 

需要说明的是,MongoDB默认会为每个记录建立一个名字为_id的索引,在没有索引的情况下MongoDB读数据都要进行全表扫描,效率还是很低的,因此在写完500万条记录后,我在id字段上建立了一个索引来加快读的过程。

测试结果及总结

一、写操作

成功写入416万条记录,写入失败84万条记录,共耗时525秒,平均每秒写入数据7924笔。数据文件大小近4G。写入过程中,服务器内存、CPU和磁盘等资源使用情况如下图所示:

 

 

 


 

可见,内存使用呈阶梯状上升,最后占用了近3.5个G,主要用来缓存数据,对比数据文件的大小可以设想,在操作系统内存可用的情况下,内存的分配和数据文件的大小是大致相当的。CPU和磁盘IO都表现出周期性的上下波动,估计和操作系统mmap缓存数据和刷新到磁盘的实现机制有关。从图表可见本次测试的瓶颈同样出现在磁盘IO上,磁盘的使用率最后达到100%,导致无法成功写入数据。

 

二、读操作

成功读出500万条记录,共耗时432秒,平均每秒读出数据11574笔。

读数据过程中没有发生磁盘IO。CPU较繁忙,Idle值稳定在27左右,等待CPU资源的进程一直在5到10个之间。内存表现平稳没有波动。

通过以上测试结果可以看出,MongoDB占用的磁盘空间很大,这是因为其占用的磁盘空间是预分配的,每次以上一个数据文件的两倍大小来预分配空间,并以0来填充,以避免在繁忙时期分配磁盘空间导致性能下降,因此我们看到的数据文件大小并不是实际占用的空间大小。另外其内存是由操作系统管理的,自身并不管理内存使用。从功能角度看MongoDB提供的查询等操作接口是最为丰富的,在面对海量数据的拆分等可扩展方面也有很好的设计思路,是一个很好的将数据模型从面向关系转向面向文档的NoSQL产品,相信将来会看到更多的线上产品选用MongoDB。

搭建mongodb的sharding cluster

Mongodb为了提高处理大数据量的性能,提供了分片集群的功能,sharding可以理解为传统数据库表分区的扩展,其实现架构图如下:

可见,这个架构由三部分进程组成:两组mongod进程(shard和config server)了一组mongos进程。

shard可以理解为每一个数据分片,也就是一个大数据量中的一部分分片数据。为了提高高可用性,一般在生产系统中每个shard分片都是一个replica set,也就是提供了主从复制的功能,同时一般有第三个节点作为仲裁。

config server顾名思义是配置服务,主要保存各个shard的元数据信息,为了实现高可用,config server一般也要配置多个,每个config server的数据完全一致,mongodb是通过两阶段提交来保证最终一致性的,并不是常用的日志同步或共享存储的方式。有点让人费解的是,在启用config server进程的时候并不需要指定其要管理的shard进程,而是通过下面要说的mongos进程将二者联系起来。

客户端和mongos进程进行交互,mongos提供了routing功能,它使得客户端以为是和一个单一简单的系统在打交道。在启动mongos的时候要指定config server,然后就可以把shard添加进来。

以下演示了建立一个三个shard的集群,同时每个shard由主、从、仲裁三部分组成一个replica set。
建立mongodb用户和组,安装mongdb程序,如果用源代码安装,可以参考我的另一篇文章

[root@localhost software]# groupadd -g 20001 mongodb
[root@localhost software]# useradd -u 20001 -g mongodb mongodb
[root@localhost software]# passwd mongodb
[root@localhost software]# su - mongodb
[mongodb@localhost ~]$ tar xf mongodb-linux-x86_64-1.8.2.tgz
[mongodb@localhost ~]$ tree mongodb-linux-x86_64-1.8.2
mongodb-linux-x86_64-1.8.2
|-- GNU-AGPL-3.0
|-- README
|-- THIRD-PARTY-NOTICES
`-- bin
    |-- bsondump
    |-- mongo
    |-- mongod
    |-- mongodump
    |-- mongoexport
    |-- mongofiles
    |-- mongoimport
    |-- mongorestore
    |-- mongos
    |-- mongosniff
    `-- mongostat

1 directory, 14 files

建立对应的数据文件目录,在三台服务器启动shard进程,当然在一台服务器上测试指定不同的端口也可以:

[mongodb@localhost ~]$ cd mongodb-linux-x86_64-1.8.2/bin/
[mongodb@localhost bin]$ ./mongod --shardsvr --replSet shard1 --port 27017
--dbpath /home/mongodb/data/shard11 --oplogSize 100
--logpath /home/mongodb/data/shard11.log --logappend --fork
[mongodb@localhost bin]$ ./mongod --shardsvr --replSet shard1 --port 27018
--dbpath /home/mongodb/data/shard12 --oplogSize 100
--logpath /home/mongodb/data/shard12.log --logappend --fork
[mongodb@localhost bin]$ ./mongod --shardsvr --replSet shard1 --port 27019
--dbpath /home/mongodb/data/shard13 --oplogSize 100
--logpath /home/mongodb/data/shard13.log --logappend --fork

连接到任意shard节点,把三个服务器上的shard组成一个replica set,这里指定第三个shard为仲裁节点:

[mongodb@localhost bin]$ ./mongo 192.168.0.29:27019
MongoDB shell version: 1.8.2
connecting to: 192.168.0.29:27019/test
>
> config = {_id:'shard1',members: [
...                           {_id: 0, host: '192.168.0.27:27017'},
...                           {_id: 1, host: '192.168.0.28:27018'},
...                           {_id: 2, host: '192.168.0.29:27019',arbiterOnly:true}]
...           }
{
        "_id" : "shard1",
        "members" : [
                {
                        "_id" : 0,
                        "host" : "192.168.0.27:27017"
                },
                {
                        "_id" : 1,
                        "host" : "192.168.0.28:27018"
                },
                {
                        "_id" : 2,
                        "host" : "192.168.0.29:27019"
                }
        ]
}
>
> rs.initiate(config);
{
        "info" : "Config now saved locally.  Should come online in about a minute.",
        "ok" : 1
}

等待几分钟可查看replica set已经建立完毕:

shard1:ARBITER> rs.status();
{
        "set" : "shard1",
        "date" : ISODate("2011-10-14T05:30:38Z"),
        "myState" : 7,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "192.168.0.27:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 160417,
                        "optime" : {
                                "t" : 1313413539000,
                                "i" : 1
                        },
                        "optimeDate" : ISODate("2011-08-15T13:05:39Z"),
                        "lastHeartbeat" : ISODate("2011-10-14T05:30:37Z")
                },
                {
                        "_id" : 1,
                        "name" : "192.168.0.28:27018",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 160403,
                        "optime" : {
                                "t" : 1313413539000,
                                "i" : 1
                        },
                        "optimeDate" : ISODate("2011-08-15T13:05:39Z"),
                        "lastHeartbeat" : ISODate("2011-10-14T05:30:37Z")
                },
                {
                        "_id" : 2,
                        "name" : "192.168.0.29:27019",
                        "health" : 1,
                        "state" : 7,
                        "stateStr" : "ARBITER",
                        "optime" : {
                                "t" : 0,
                                "i" : 0
                        },
                        "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
                        "self" : true
                }
        ],
        "ok" : 1
}
shard1:ARBITER>

在三台服务器上启动config server 进程:

[mongodb@localhost bin]$ ./mongod --configsvr --dbpath /home/mongodb/data/config --port 20000
--logpath /home/mongodb/data/config.log --logappend --fork

在三台服务器上启动mongos进程,这里要指定对应的config servers:

[mongodb@localhost bin]$ ./mongos --configdb 192.168.0.27:20000,192.168.0.28:20000,192.168.0.29:20000
--port 30000 --chunkSize 5 --logpath /home/mongodb/data/mongos.log --logappend --fork

连接到任意mongos进程,添加shard节点,要注意的是只需添加数据节点(主、从)就可以,仲裁节点不能添加:

[mongodb@localhost bin]$ ./mongo 192.168.0.29:30000/admin
> db.runCommand({addshard:"shard1/192.168.0.27:27017,192.168.0.28:27018,192.168.0.29:27019",name:"s1",maxsize:20480});
{
        "ok" : 0,
        "errmsg" : "host 192.168.0.29:27019 does not belong to replica set as a non-passive membershard1"
}
> db.runCommand({addshard:"shard1/192.168.0.27:27017,192.168.0.28:27018",name:"s3",maxsize:20480});
{ "shardAdded" : "s3", "ok" : 1 }
>
> db.runCommand({listshards:1})
{
        "shards" : [
                {
                        "_id" : "s3",
                        "host" : "shard1/192.168.0.27:27017,192.168.0.28:27018"
                }
        ],
        "ok" : 1
}
>
> db.stats();
{
        "raw" : {
                "192.168.0.27:20000,192.168.0.28:20000,192.168.0.29:20000" : {
                        "db" : "admin",
                        "collections" : 0,
                        "objects" : 0,
                        "avgObjSize" : 0,
                        "dataSize" : 0,
                        "storageSize" : 0,
                        "numExtents" : 0,
                        "indexes" : 0,
                        "indexSize" : 0,
                        "fileSize" : 0,
                        "ok" : 1
                }
        },
        "objects" : 0,
        "avgObjSize" : NaN,
        "dataSize" : 0,
        "storageSize" : 0,
        "numExtents" : 0,
        "indexes" : 0,
        "indexSize" : 0,
        "fileSize" : 0,
        "ok" : 1
}

这样一个mongodb sharding cluster就搭建完成了。

ttserver性能评测

引言

本篇要评测的NoSQL产品是Tokyo Cabinet和Tokyo Tyrant,Tokyo Cabinet是一个性能优秀的数据存储引擎,而Tokyo Tyrant则提供了访问Tokyo Cabinet数据的网络接口。这是一个很成熟的产品,在国内外也有众多的成功案例。

介绍

Tokyo Cabinet(简称TC)和Tokyo Tyrant(简称TT),顾名思义,是源自日本的开源项目。由FAL Labs维护,主要的开发人员是Mikio Hirabayashi。最早应用在日本最大的SNS网站mixi.jp上成功后而声名鹊起。

TC是一个用C写的数据存储引擎,以key-value的方式存储数据,支持Hash、B+ tree、Hash Table等多种数据结构。同时提供了C、 Perl、 Ruby、Java和Lua等多种语言的API支持,但是如果通过网络来访问,就需要用TT。TT同样是用C写的,支持从网络端高并发、多线程的访问TC。另外TC/TT支持master/slave架构,可以通过配置实现高可用性,这也是很不错的一个特性。

TC因为支持灵活的数据结构而倍受欢迎,特别是Hash Table类型,很像传统的关系型数据库,只是每条存储记录的列是自由定义的,可以通过列作为条件来查询,十分方便。这是很多key-value结构的NoSQL产品所不具备的特性。当然,Hash Table类型和Hash、B+ tree相比存取效率会低一些,任何事物都是有两面性的,在带来高度灵活度的同时,必然要牺牲部分的效率。

TC/TT是一个久经考验的很稳定的产品,在千万及以下数据量级别表现出色。但是开发者由于种种原因,已经很长时间没有更新版本了,而是推出了对应升级产品,叫做Kyoto Cabinet和Kyoto Tycoon,这也给TC/TT的前景带来了不明朗的因素,很明显作者是鼓励人们使用升级的产品,但是由于新产品没有更多的成功案例,在业界的影响力反而不如TC/TT,因此在现阶段,TC/TT仍然是一个不错的NoSQL选择。

安装和使用

一、下载相关软件的最新版本(TC、TT和TC的lua扩展):

[root@localhost tctt]# wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz

[root@localhost tctt]# wget http://fallabs.com/tokyotyrant/tokyotyrant-1.1.41.tar.gz

[root@localhost tctt]# wget http://fallabs.com/tokyocabinet/luapkg/tokyocabinet-lua-1.9.tar.gz

注意这里的lua扩展是可选的,如果不需要使用lua接口,可以不必安装。

二、安装lua脚本,注意这里不能通过yum的方式安装,否则后面装tt时会提示找不到lua.h文件:

wget http://www.lua.org/ftp/lua-5.1.4.tar.gz

make linux

make install

安装过程中一般会报错如下:

luaconf.h:275:31: error: readline/readline.h: No such file or directory

luaconf.h:276:30: error: readline/history.h: No such file or directory

通过yum安装readline-devel即可:

yum install readline-devel

三、安装TC、TT和TC的lua扩展,注意的是如果要使用LUA接口,那么编译TT的时候要加–enable-lua参数:

[root@localhost tctt]# tar zxvf tokyocabinet-1.4.47.tar.gz

[root@localhost tctt]# cd tokyocabinet-1.4.47

[root@localhost tokyocabinet-1.4.47]# ./configure

[root@localhost tokyocabinet-1.4.47]# make

[root@localhost tokyocabinet-1.4.47]# make install

[root@localhost tctt]# tar zxvf tokyocabinet-lua-1.9.tar.gz

[root@localhost tokyocabinet-lua-1.9]# make

[root@localhost tokyocabinet-lua-1.9]# make install

[root@localhost tctt]# tar zxvf tokyotyrant-1.1.41.tar.gz

[root@localhost tctt]# cd tokyotyrant-1.1.41

[root@localhost tokyotyrant-1.1.41]# ./configure –enable-lua

[root@localhost tokyotyrant-1.1.41]# make

[root@localhost tokyotyrant-1.1.41]# make install

至此就安装完成了,整个过程很简单,当然安装过程可能需要一些其他的程序包,可以根据提示进行安装即可。

TT提供了很多命令行工具来管理数据库,比较常用的两个是ttserver和tcrmgr。

Ttserver的用法如下:

ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn] [-pid path] [-kl] [-log path] [-ld|-le] [-ulog path] [-ulim num] [-uas] [-sid num] [-mhost name] [-mport num] [-rts path] [-rcc] [-skel name] [-mul num] [-ext path] [-extpc name period] [-mask expr] [-unmask expr] [dbname]

各个参数的说明可查看官方文档,这里就不一一列举了。我们可以建立一个hash table类型和一个B+ tree类型的数据库:

ttserver -host 192.168.0.35 -port 11301 -thnum 8 -dmn -pid /home/tc/test_data/test_data_b.pid -log /home/tc/test_data/test_data_b.log -le -ulog /home/tc/test_data/ -ulim 128m -sid 1 -rts /home/tc/test_data/test_data_b.rts /home/tc/test_data/test_data.tcb

ttserver -host 192.168.0.35 -port 11302 -thnum 8 -dmn -pid /home/tc/test_data/test_data_t.pid -log /home/tc/test_data/test_data_t.log -le -ulog /home/tc/test_data/ -ulim 128m -sid 1 -rts /home/tc/test_data/test_data_t.rts /home/tc/test_data/test_data.tct

注意最后一个参数的扩展名tcb和tct决定了数据库的类型分别是B+ tree和table。

通过tcrmgr命令行工具可以管理远程数据库,比如我们要查看key为1的记录可以这样操作:

[root@localhost tc]# tcrmgr get -port 11301 192.168.0.35 1

要了解更详细的信息,读者可以参看官方文档。

测试说明

一、测试环境

TC/TT部署在一台PC 服务器上,配置如下:

CPU为Xeon 2.80GHz *4

内存为4G

硬盘为一块400G SATA盘

操作系统为64位CentOS 5.3版本

二、测试方法

这里仍然采用第三方实现的PHP客户端进行测试,网址为http://pecl.php.net/package/tokyo_tyrant,这是一个标准的PHP扩展程序,可以编译到PHP运行环境中。要说明的是,这个PHP客户端实现的是TT接口,肯定比使TT自带的tcrtest效率要低一些,但是我们的测试要尽量模拟实际的生产环境,所以这里使用了第三方的PHP客户端。

为了不对测试服务器产生额外的影响,测试客户端部署在另外一台独立的服务器上,运行的PHP的版本是5.3.5,web server是Nginx 0.8.54,通过fastcgi的方式调用PHP服务。使用apache ab工具实现多个请求和并发操作。

为了更全面的反应TC/TT的性能,我对B+ tree和Hash Table两种数据库类型分别进行了测试,就使用上文提到的ttserver示例语句来建立测试数据库,每个类型的测试又分为两个步骤,首先是写操作,通过500个请求,每个请求写入10000条记录,并发度为2来共写入500万条数据,数据的key为数字1到5000000,value大小为100个字节。然后是读操作,也是用500个请求,每个请求随机根据key值读出10000条记录,并发度为10共读出500万条记录,评测的重点是写入和读出数据的时间,以及在此过程中服务器的资源使用情况。

测试结果及总结

一、B+tree类型写操作

成功写入500万条记录,共耗时739秒,平均每秒写入数据6766笔。数据文件大小137M。写入过程中,服务器内存、CPU和磁盘等资源使用情况如下图所示:

image001

image003

image005

可见,CPU使用率平稳,Idle值稳定在73到83之间,wait值稳定在7到12之间。内存分配上的变化较大,主要用来缓存数据,cache部分上升了1.1G,但是没有交换区到内存的换入换出。磁盘IO表现有周期性的上下波动,估计和TC的实现机制有关,数据先写入内存缓冲区,然后定期的刷新到磁盘。

二、B+tree类型读操作

成功读出500万条记录,共耗时1171秒,平均每秒读出数据4270笔。

读数据过程中没有发生磁盘IO。CPU较繁忙,Idle值稳定在38左右,等待CPU资源的进程一直在1到6个之间。内存表现平稳没有波动。

image007

三、Hash Table类型写操作

成功写入445万条记录,写入失败55万条记录,共耗时1560秒,平均每秒写入数据2853笔。数据文件大小538M。写入过程中,服务器内存、CPU和磁盘等资源使用情况如下图所示:

image009

image011

image013

CPU使用率较平稳,Idle值稳定在60到80之间,wait值最高在50左右,稳定在20到40之间。内存cache部分上升了0.7G用于缓存数据。磁盘IO表现有呈阶梯状的上升,最后达到100%,导致无法成功写入数据。

四、Hash Table类型读操作

成功读出500万条记录,共耗时175秒,平均每秒读出数据28571笔。

读数据过程中没有发生磁盘IO。CPU较繁忙,Idle值稳定在37到41之间,等待CPU资源的进程一直在1到9个之间。内存表现平稳没有波动。

image015

通过以上测试结果可以说明,TC/TT写入的数据的时候,先缓冲到内存中,然后通过一定的机制写入磁盘,这也是写的效率较高的原因,但是由此带来了周期性的磁盘繁忙,也可能有丢失数据的风险。写入的数据完全缓存到了文件系统中,所以cache部分占用的内存大量增加,这也是读取数据的时候没有发生磁盘IO的原因。B+ tree类型写入性能十分优异,令人惊讶的是读出的速度反而慢于写入的速度。Hash Table类型写入性能一般,但读取性能良好。总体上来说TC/TT在非海量数据的情况下表现不错,服务器资源占用稳定,读写效率较高。

备注:本文发表在IT168网站上,版权所有,转载请注明出处。

为Postfix增加DKIM功能

为了防止邮件被判定为垃圾邮件,可以给邮件系统增加DKIM(DomainKeys Identified Mail)功能,其主要的原理通俗的说,就是在发送邮件的时候通过私钥在邮件头写一段加密信息,然后公钥放到DNS服务器上,邮件的接收方通过邮件头的加密信息来和DNS上的公钥比对来判定邮件来源是否合法。这个计数貌似是雅虎提出的,被Yahoo! Mail和Gmail等厂商采用。

可以在http://sourceforge.net/projects/dkim-milter/ 下载源代码:

[root@mail dkim]# ll
total 736
-rw-r–r– 1 root root 748415 Jul 13 2011 dkim-milter-2.8.3.tar.gz
[root@mail dkim]# tar zxvf dkim-milter-2.8.3.tar.gz

也可以通过下载RPM安装包安装,比较简单:

[root@mail dkim]# wget http://download.fedora.redhat.com/pub/epel/5/i386/dkim-milter-2.8.3-4.el5.i386.rpm
[root@mail dkim]# rpm -ivh dkim-milter-2.8.3-4.el5.i386.rpm

安装完成后,首先要生成公钥和私钥两个文件,可以使用源码包里的一个脚本,其路径在:

dkim-milter-2.8.3/dkim-filter/dkim-genkey.sh

参数为要使用发信的域名:

[root@mail dkim]# ./dkim-genkey.sh -r -d mail.banping.com
[root@mail dkim]# ll
total 1024
-rw——- 1 root root 887 Jul 13 12:47 default.private
-rw——- 1 root root 308 Jul 13 12:47 default.txt

然后把这个default.txt的内容组织一下放到DNS上,增加一条domain=default._domainkey.mail.banping.com的txt记录,内容类似这样:

v=DKIM1;p=MIGfMA0GCSqGSIb898L9LKJ7dDFGNADCBiQKBgQCU1iD47S+n92ZeXKL444Kg7VzkczqN5xZnx6px1C+

/hImMNoQvF3X6HXLG1+OzO7s8Odf3lhpqgGWq+atFKT3YUZUY3vAL983LIKJIWo+

988QIB5iw1cotBretF0TFWVdf4weNyPrC1Qtvm8kQswIDAQAB” ;

把私钥放到想要的位置:

[root@mail dkim]# mv default.private /etc/mail/dkim-milter/keys/default

修改keylist配置文件:

[root@mail dkim]# vi /etc/mail/dkim-milter/keys/keylist
*@mail.rtmail.cn:mail.rtmail.cn:/etc/mail/dkim-milter/keys/default

也可以不动这个文件,把私钥位置配置在dkim-filter.conf文件中,由KeyFile参数指定:

[root@mail dkim]# vi /etc/mail/dkim-milter/dkim-filter.conf
AutoRestart yes
Domain mail.banping.com
Selector default
Socket inet:20118@localhost
Syslog Yes
X-Header Yes
KeyFile /etc/mail/dkim-milter/keys/default

至此DKIM配置完成,再修改postfix中的配置,在发信的时候启用加密功能:

[root@mail dkim]# vi /etc/postfix/main.cn
smtpd_milters = inet:localhost:20118
non_smtpd_milters = inet:localhost:20118
milter_protocol = 2
milter_default_action = accept

OK,现在启动DKIM和PostFix服务就可以了:

[root@mail dkim]# service dkim-milter start
[root@mail keys]# service postfix reload
Reloading postfix: [ OK ]

除了DKIM,还可以在DNS上增加SPF配置来提高邮件发送成功的几率,比如:

v=spf1 ip4:110.120.130.140 ~all

可以查看DNS上的配置是否成功:

[root@mail postfix]# dig -t txt mail.banping.com

; QUESTION SECTION:

;mail.banping.com. IN TXT

;; ANSWER SECTION:
mail.banping.com. 600 IN TXT “v=spf1 ip4:110.120.130.140 ~all”

可以到以下地址来测试DKIM和SPF服务是否正常:

http://www.brandonchecketts.com/emailtest.php

http://www.openspf.org/Why?show-form=1

搭建HBase+thrift+php环境

HBase是一个开源的NoSQL产品,它是实现了Google BigTable论文的一个开源产品,和Hadoop和HDFS一起,可用来存储和处理海量column family的数据。官方网址是:http://hbase.apache.org。Hbase的体系结构比较复杂,本文只探讨基本的安装测试问题,首先从镜像下载最新版的HBase:

[root@localhost hbase]# wget http://mirror.bjtu.edu.cn/apache/hbase/stable/hbase-0.90.3.tar.gz

[root@localhost hbase]# java -version
java version “1.6.0_24″

Hbase是Java开发的,先检查下本地的JDK版本是否在1.6以上:

[root@localhost hbase]# java -version

java version “1.6.0_24″

解压并配置数据文件的路径:

[root@localhost hbase]# tar zxvf hbase-0.90.3.tar.gz

[root@localhost hbase]# cd hbase-0.90.3/conf

[root@localhost conf]# vi hbase-site.xml

</configuration>
<configuration>

<property>

<name>hbase.rootdir</name>

<value>/home/banping/hbase/data</value>

</property>

Hbase依赖于Hadoop来运行,它有两种配置方式,一种是比较简单的单实例方式,它已经内置了0.20版本的Hadoop包和一个本地Zookeeper,它们运行在同一个JVM之下,可以用本地文件系统而不用HDFS。另外一种比较复杂的分布式部署,需要在每一个节点替换自带的Hadoop包以免有版本问题,而且必须有一个HDFS实例,同时需要独立的ZooKeeper集群。为简单起见,我们选择单实例部署的方式,启动Hbase进程:

[root@localhost hbase-0.90.3]# bin/start-hbase.sh

starting master, logging to /home/banping/hbase/hbase-0.90.3/bin/../logs/hbase-root-master-localhost.localdomain.out

[root@localhost webtest]# netstat -tnlp|grep java

tcp        0      0 :::2181                     :::*                        LISTEN      9880/java

tcp        0      0 :::60010                    :::*                        LISTEN      9880/java

tcp        0      0 ::ffff:127.0.0.1:63148      :::*                        LISTEN      9880/java

tcp        0      0 ::ffff:127.0.0.1:35539      :::*                        LISTEN      9880/java

tcp        0      0 :::60030                    :::*                        LISTEN      9880/java

可以看到启动了5个端口,可以通过自带的shell命令来进行基本的操作:

[root@localhost hbase-0.90.3]# bin/hbase shell

hbase(main):002:0> create ‘test’,'cf’

0 row(s) in 0.9940 seconds

hbase(main):019:0> list

TABLE

test

1 row(s) in 0.0290 seconds

hbase(main):022:0> put ‘test’, ‘row1′, ‘cf:a’, ‘value1′

0 row(s) in 0.2130 seconds

hbase(main):023:0> put ‘test’, ‘row2′, ‘cf:b’, ‘value2′

0 row(s) in 0.0120 seconds

hbase(main):024:0> put ‘test’, ‘row3′, ‘cf:c’, ‘value3′

0 row(s) in 0.0130 seconds

hbase(main):025:0> scan ‘test’

ROW                                         COLUMN+CELL

row1                                       column=cf:a, timestamp=1310027460885, value=value1

row2                                       column=cf:b, timestamp=1310027469458, value=value2

row3                                       column=cf:c, timestamp=1310027476262, value=value3

3 row(s) in 0.0870 seconds

hbase(main):026:0> get ‘test’, ‘row1′

COLUMN                                      CELL

cf:a                                       timestamp=1310027460885, value=value1

1 row(s) in 0.0250 seconds

hbase(main):027:0>  disable ‘test’

0 row(s) in 2.0920 seconds

hbase(main):029:0> drop ‘test’

0 row(s) in 1.1440 seconds

hbase(main):030:0> exit

停止Hbase实例:

[root@localhost hbase-0.90.3]# ./bin/stop-hbase.sh

stopping hbase……

如果使用PHP操作Hbase,一种途径是使用Facebook开源出来的thrift,官网是:http://thrift.apache.org/ ,它是一个类似ice的中间件,用于不同系统语言间信息交换。首先下载最新的版本:

[root@localhost hbase]# wget http://mirror.bjtu.edu.cn/apache//thrift/0.6.1/thrift-0.6.1.tar.gz

安装需要的依赖包:

[root@localhost thrift-0.6.1]# sudo yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel

编译安装:
[root@localhost thrift-0.6.1]# ./configure –prefix=/home/banping/hbase/thrift –with-php-config=/usr/local/php/bin/
[root@localhost thrift-0.6.1]# make
[root@localhost thrift-0.6.1]# make install
生成php和hbase的接口文件:
[root@localhost hbase]# thrift/bin/thrift –gen php /home/banping/hbase/hbase-0.90.3/src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thrift
[root@localhost hbase]# cd gen-php/Hbase
[root@localhost Hbase]# ll
total 320
-rw-r–r– 1 root root 285433 Jul  7 19:22 Hbase.php
-rw-r–r– 1 root root  27426 Jul  7 19:22 Hbase_types.php
把PHP客户端需要的包及刚才生成的接口文件复制出来供php程序调用:
[root@localhost Hbase]# cp -a /home/banping/hbase/thrift-0.6.1/lib/php /home/webtest/thrift/
[root@localhost Hbase]# cd /home/webtest/thrift/
[root@localhost thrift]# mkdir packages
[root@localhost thrift]# cp -a /home/banping/hbase/gen-php/Hbase /home/webtest/thrift/packages

据一些资料说也可以使用PHP扩展的方式来使用thrift(php和HBase接口文件仍然是必须的),但是我测试没有通过,做法如下:

[root@localhost thrift]# cd ext/thrift_protocol
[root@localhost thrift_protocol]# /usr/local/php/bin/phpize
[root@localhost thrift_protocol]# ./configure –with-php-config=/usr/local/php/bin/php-config –enable-thrift_protocol
[root@localhost thrift_protocol]# make
[root@localhost thrift_protocol]# make install
然后把生成的thrift_protocol.so文件配置到php.ini并重启php-fpm服务。
启动hbase和thrift进程:
[root@localhost hbase-0.90.3]# ./bin/start-hbase.sh
[root@localhost hbase-0.90.3]# ./bin/hbase-daemon.sh start thrift
starting thrift, logging to /home/banping/hbase/hbase-0.90.3/bin/../logs/hbase-root-thrift-localhost.localdomain.out

thrift默认监听的端口是9090

[root@localhost webtest]# netstat -tnlp|grep java

tcp        0      0 ::ffff:127.0.0.1:9090       :::*                        LISTEN      10069/java

tcp        0      0 :::2181                     :::*                        LISTEN      9880/java

tcp        0      0 :::60010                    :::*                        LISTEN      9880/java

tcp        0      0 ::ffff:127.0.0.1:63148      :::*                        LISTEN      9880/java

tcp        0      0 ::ffff:127.0.0.1:35539      :::*                        LISTEN      9880/java

tcp        0      0 :::60030                    :::*                        LISTEN      9880/java

写一个PHP文件来测试:

[root@localhost webtest]# vi hbase.php
<?php
$GLOBALS['THRIFT_ROOT'] = ‘/home/webtest/thrift/src’;
require_once( $GLOBALS['THRIFT_ROOT'].’/Thrift.php’ );
require_once( $GLOBALS['THRIFT_ROOT'].’/transport/TSocket.php’ );
require_once( $GLOBALS['THRIFT_ROOT'].’/transport/TBufferedTransport.php’ );
require_once( $GLOBALS['THRIFT_ROOT'].’/protocol/TBinaryProtocol.php’ );
require_once( $GLOBALS['THRIFT_ROOT'].’/packages/Hbase/Hbase.php’ );
$socket = new TSocket( ‘localhost’, 9090 );
$socket->setSendTimeout( 10000 ); // Ten seconds (too long for production, but this is just a demo ;)
$socket->setRecvTimeout( 20000 ); // Twenty seconds
$transport = new TBufferedTransport( $socket );
$protocol = new TBinaryProtocol( $transport );
$client = new HbaseClient( $protocol );
$transport->open();
echo( “listing tables…n” );
$tables = $client->getTableNames();
sort( $tables );
foreach ( $tables as $name ) {
echo( ”  found: {$name}n” );
}
$columns = array(
new ColumnDescriptor( array(
‘name’ => ‘entry:’,
‘maxVersions’ => 10
) ),
new ColumnDescriptor( array(
‘name’ => ‘unused:’
) )
);
$t = “table1″;
echo( “creating table: {$t}n” );
try {
$client->createTable( $t, $columns );
} catch ( AlreadyExists $ae ) {
echo( “WARN: {$ae->message}n” );
}
$t = “test”;
echo( “column families in {$t}:n” );
$descriptors = $client->getColumnDescriptors( $t );
asort( $descriptors );
foreach ( $descriptors as $col ) {
echo( ”  column: {$col->name}, maxVer: {$col->maxVersions}n” );
}
$t = “table1″;
echo( “column families in {$t}:n” );
$descriptors = $client->getColumnDescriptors( $t );
asort( $descriptors );
foreach ( $descriptors as $col ) {
echo( ”  column: {$col->name}, maxVer: {$col->maxVersions}n” );
}
$t = “table1″;
$row = “row_name”;
$valid = “foobar-xE7x94x9FxE3x83x93″;
$mutations = array(
new Mutation( array(
‘column’ => ‘entry:foo’,
‘value’ => $valid
) ),
);
$client->mutateRow( $t, $row, $mutations );
$table_name = “table1″;
$row_name = ‘row_name’;
$fam_col_name = ‘entry:foo’;
$arr = $client->get($table_name, $row_name , $fam_col_name);
// $arr = array
foreach ( $arr as $k=>$v  ) {
// $k = TCell
echo (“value = {$v->value} , <br>  ”);
echo (“timestamp = {$v->timestamp}  <br>”);
}
$table_name = “table1″;
$row_name = “row_name”;
$arr = $client->getRow($table_name, $row_name);
// $client->getRow return a array
foreach ( $arr as $k=>$TRowResult  ) {
// $k = 0 ; non-use
// $TRowResult = TRowResult
var_dump($TRowResult);
}
$transport->close();
?>
通过浏览器查看即可看到结果。这样PHP通过thrift访问HBase就可以了。

HandlerSocket客户端端口大量被占用的案例

HandlerSocket是通过socket和innodb通信的,但是网络通信如果双方连接没有按照规则正常关闭,可能导致wati_close状态的TCP连接大量存在,占用大量端口资源,需要慎重处理。

最近碰到这样的一个案例,检查一个服务器上的php fastcgi的连接情况,发现一些异常:

[root@mail ~]# netstat -anpo | grep php-fpm|wc -l
157

[root@banping~]# netstat -anpo | grep php-fpm|wc -l

517

而实际是没有这么多连接的,进一步检查详细信息,发现很多wati_close状态的TCP连接,占用了大量端口,而服务器端对应的是HandlerSocket插件的9998端口,初步判断是socket连接没有正常关闭导致的问题:

[root@banping ~]# netstat -anpo | grep php-fpm

tcp        1      0 10.0.0.1:43635            10.0.0.2:9998               CLOSE_WAIT  3592/php-fpm: pool  off (0.00/0/0)

tcp        1      0 10.0.0.1:43699            10.0.0.2:9998               CLOSE_WAIT  3592/php-fpm: pool  off (0.00/0/0)

tcp        1      0 10.0.0.1:43335            10.0.0.2:9998               CLOSE_WAIT  3592/php-fpm: pool  off (0.00/0/0)

tcp        1      0 10.0.0.1:43306            10.0.0.2:9998               CLOSE_WAIT  2894/php-fpm: pool  off (0.00/0/0)

……

但是很难定位在客户端的哪个具体操作会导致这个问题,或许是在有一定负载压力的情况下才会出来这种死连接。

估计的原因有几种可能,一是HandlerSocket自身代码的问题,二是PHP客户端的实现问题,这个客户端并没有显示的断开socket连接的接口提供,三是自身代码问题或压力负载导致的。

不好定位问题,可以先通过Linux的keepalive参数来控制TCP的超时来释放CLOSE_WAIT的连接:

[root@banping ~]# vi /etc/sysctl.conf

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_keepalive_probes = 2

net.ipv4.tcp_keepalive_intvl = 2

[root@banping ~]# sysctl -p

把超时时间设为300秒,这个默认是7200秒,把tcp_keepalive_intvl(keepalive探测包的发送间隔)和tcp_keepalive_probes (如果对方不予应答,探测包的发送次数)都设为2秒,这样就能很快的释放连接。

默认参数可以这样查看:

[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_time

7200

[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_probes

9

[root@banping ~]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl

75

然后过一段时间我们再观察CLOSE_WAIT连接数:

[root@banping ~]# netstat -anpo | grep php-fpm|grep CLOSE_WAIT|grep 9998|wc -l

203

这没有解决程序上的本质问题,需要继续观察评估效果。

网站性能调试案例一则

前几天网上一个朋友反映碰到了网站性能的问题:

怎么看LINUX服务器是不是假死啊 我的网站老是出现可以PING 也可以登录SSH 就是网站不能访问啊 重启了 又可以了

free

从这个free的输出看内存有很多空闲,我请他输出vmstat 5的结果如下所示:

vmstat

从这个输出可见内存使用较少,CPU有一定的使用,但并不高,IO有等待,初步判断瓶颈在IO上,后来登录到服务器查看,主要发现了三个问题,一是MySQL都用默认的参数在跑,内存给的是8M,有30多个schema。二是apache连接数配置的太少,很容易就溢出,三是文件系统有问题,需要用fsck来修复,不排除是磁盘故障。

于是我做了以下动作:

一是调整MySQL的配置,由于他的大部分表是MyISAM引擎,部分memory引擎,加大key_buffer_size到1G,毫无疑问这是MyISAM引擎最重要的参数,如果只能调整一个参数,那么就要增大这个值来给MySQL使用更多的内存而不是空闲在那里。另外我调整了两个参数如下:

#add by banping

max_connections = 1000

key_buffer_size = 1G

myisam_sort_buffer_size = 128M

实际上这个myisam_sort_buffer_size参数意义不大,这是个字面上蒙人的参数,它用于ALTER TABLE, OPTIMIZE TABLE, REPAIR TABLE 等命令时需要的内存。如果真的要提高排序用的内存,是要调整sort_buffer_size参数,但这个参数是为每个session分配的,要慎用。

二是增加了apache连接数的大小,通过ps -ef|grep httpd|wc -l命令查看,挂死的时候连接数达到了上限,它是使用prefork模式的,我修改了配置文件:

[root@centos conf]# vi extra/httpd-mpm.conf

<IfModule mpm_prefork_module>

StartServers          5

MinSpareServers       5

MaxSpareServers      10

ServerLimit         1000

MaxClients          350

MaxRequestsPerChild   0

</IfModule>

通过top命令可见一些httpd进程占用17M左右的内存,按照平均10M计算,那么350*10就是3.4G的内存,而服务器的内存配置是4G。可以估算他这个服务器目前最多也就能撑500个以下的连接,这时还要减少分配给MySQL的内存。

三是文件系统的问题,发现有些文件虽然是rw的属性,但是操作的时候是read only的,通过以下命令发现了一堆错误:

[root@centos ~]# dmesg |grep error

EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 110350817 in dir #84690004

EXT3-fs error (device sdb1): ext3_journal_start_sb: Detected aborted journal

EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 97047037 in dir #84689192

EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 95801904 in dir #84689116

EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 101257606 in dir #84689449

EXT3-fs error (device sdb1): ext3_lookup: unlinked inode 110563813 in dir #84690017

检查fstab设置是没问题的:
[root@centos ~]# cat /etc/fstab
LABEL=/                 /                       ext3    defaults        1 1
LABEL=/home             /home                   ext3    defaults        1 2
LABEL=/var              /var                    ext3    defaults        1 2
LABEL=/boot             /boot                   ext3    defaults        1 2
/dev/sdb1               /web                    ext3    defaults        1 2
tmpfs                   /dev/shm                tmpfs   defaults        0 0
devpts                  /dev/pts                devpts  gid=5,mode=620  0 0
sysfs                   /sys                    sysfs   defaults        0 0
proc                    /proc                   proc    defaults        0 0
LABEL=SWAP-sda5         swap                    swap    defaults        0 0
你这台服务器不只是web server吧 是不是数据库也在,有没有很多写入?
新叶子-陈志鑫(77008008) 10:12:19
有啊 好像有很多写入 我就是搞不懂啊
半瓶(9155091) 10:12:48
估计你的瓶颈在

这时可做的操作是先umount文件系统后执行fsck检查和修复再mount,再有问题可能就是磁盘的硬件故障了,但是考虑到风险我并没有做这个操作,而且要做必须先要停掉应用,否则是无法umount的。

之后的观察了几天发现在apache连接数超过256的时候仍然会挂掉,这时我怀疑是ServerLimit参数没生效,这个ServerLimit参数对应的是src/include/httpd.h文件中的#define HARD_SERVER_LIMIT 256这个变量值,这个值会限制连接数的大小,而不管你的MaxClients参数大小是多少。我修改了apache的配置后是restart而不是stop再start的,估计这样的操作由问题。

后来caoz知道了这个问题,他说apache连接溢出有多种可能,上去看了这台服务器,找到了连接数增长的原因是因为默认的KeepAlive On和Timeout 300导致连接没有及时的释放。改变这两个参数的值,连接数的压力就下来了。高人啊,一下就找到了本质的原因。然后他通过fsck修复了文件系统的问题。

通过这个事例我们可以发现很多事情的产生必然有背后的原因,做决定要有充足的依据做支撑,解决是表象背后的本质问题才是真正的解决问题。

HandlerSocket协议及PHP客户端应用

HandlerSocket Plugin for MySQL的协议提供了比较丰富的功能,但是第三方开发者提供的客户端参差不齐,文档也大多不完善,所以应用起来还是有些累。

简单分析下HandlerSocket的协议:

1、OpenIndex

<indexid> <dbname> <tablename> <indexname> <columns> [<fcolumns>]

<indexid>编号(后续操作都会用到)

<dbname> 数据库名

<tablename> 表名

<indexname>索引名

<columns>需要返回结果的列名列表

[<fcolumns>]可选,用于过滤条件的列名列表,这个f是filter的意思

2、get data

<indexid> <op> <vlen> <v1> … <vn> [LIM] [IN] [FILTER ...]

<indexid> opened index id

<op> 操作符 ‘=’, ‘>’, ‘>=’, ‘<’, and ‘<=’

<vlen> 参数中提供的索引包含的列的个数

<v1> … <vn>索引中包含的列,这个参数可以比实际索引中的列少

[LIM] <limit> <offset>

[IN] <icol> <ivlen> <iv1> … <ivn> 列名,长度,值

[FILTER ...] <ftyp> <fop> <fcol> <fval> 过滤类型F或W(条件或循环),操作符,列名,值。

3、Updating/Deleting data

<indexid> <op> <vlen> <v1> … <vn> [LIM] [IN] [FILTER ...] MOD

MOD <mop> <m1> … <mk>

<mop> is ‘U’ (update), ‘+’ (increment), ‘-’ (decrement), ‘D’ (delete),

4、Inserting data

<indexid> + <vlen> <v1> … <vn>

5、Authentication  这个是后来加上的,以前的旧版本没有权限控制

A <atyp> <akey>

详情参见官方文档:https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/protocol.en.txt

对于PHP客户端,有个开源的项目在以下地址:http://code.google.com/p/php-handlersocket/

$hs = new HandlerSocket($hs_host, $hs_port);
$dbname = ‘rtindex’;
$table = ‘friendfeed’;
if (!($hs->openIndex(23, $dbname, $table, ‘user_id’, ‘post_id’,'post_id’)))
{
echo $hs->getError(), PHP_EOL;
die();
}
if ($maxid==0){
$value = $hs->executeSingle(23, ‘=’, array($user_id), 9999999, 0);
}else{
//int $limit, int $skip, strint $modop, array $values, array $filters, int $invalues_key, array $invalues ] )
$value = $hs->executeSingle(23, ‘=’, array($user_id), 9999999, 0,null,null,array(array(‘F’,'<’,0,$maxid)));

常见的调用方法如下:

1、新建一个对象

$hs = new HandlerSocket($hs_host, $hs_port);

2、打开索引

$hs->openIndex(23, $dbname, $table, ‘index_id’, ‘return_column’,'filter_column’);

3、简单查询

$value = $hs->executeSingle(23, ‘=’, array($user_id), 9999999, 0);

4、复杂查询,含过滤条件

$value = $hs->executeSingle(23, ‘=’, array($user_id), 9999999, 0,null,null,array(array(‘F’,'<’,0,$maxid)));

以上是php-handlersocket 0.1.0版本实现的接口,0.2.0增加了IN过滤方式。

HandlerSocket查询速度还是很快的,而且比传统的NoSQL通过key get data的方式有更丰富的过滤条件。唯一的遗憾是没有提供排序功能。排序就要占用CPU资源,这对于HandlerSocket的设计初衷是违背的,所以也无可厚非。

使用rsync同步文件

rsync是一个同步文件算法,用于批量同步根据时间修改的小文件比较合适,它主要包含以下概念:

client role The client initiates the synchronisation.
server role The remote rsync process or system to which the client connects either within a local transfer, via a remote shell or via a network socket.
This is a general term and should not be confused with the daemon.
Once the connection between the client and server is established the distinction between them is superseded by the sender and receiver roles.
daemon Role and process An Rsync process that awaits connections from clients. On a certain platform this would be called a service.
remote shell role and set of processes One or more processes that provide connectivity between an Rsync client and an Rsync server on a remote system.
sender role and process The Rsync process that has access to the source files being synchronised.
receiver role and process As a role the receiver is the destination system. As a process the receiver is the process that receives update data and writes it to disk.
generator process The generator process identifies changed files and manages the file level logic.

client role The client initiates the synchronisation.

server role The remote rsync process or system to which the client connects either within a local transfer, via a remote shell or via a network socket.

This is a general term and should not be confused with the daemon.

Once the connection between the client and server is established the distinction between them is superseded by the sender and receiver roles.

daemon Role and process An Rsync process that awaits connections from clients. On a certain platform this would be called a service.

remote shell role and set of processes One or more processes that provide connectivity between an Rsync client and an Rsync server on a remote system.

sender role and process The Rsync process that has access to the source files being synchronised.

receiver role and process As a role the receiver is the destination system. As a process the receiver is the process that receives update data and writes it to disk.

generator process The generator process identifies changed files and manages the file level logic.

更详细的说明可参见这个文档:http://rsync.samba.org/how-rsync-works.html

简单的说,它有一个自身的同步文件算法,首先建立连接的时候,有客户端和服务端的概念,如果服务端安装了rsync的daemon服务,客户端可通过socket方式直接和它通信,如果服务端没有rsync daemon进程,可以用ssh或rsh等remote shell的方式通信。连接建立以后,就有个发送者和接收者的概念。客户端和服务端都可接收也可发送。

比如从A服务器(10.0.0.1)同步文件到B服务器(10.0.0.2),可以在A服务器上用以下命令:

rsync -e ssh -tpogrv –exclude=”config/config.php” –exclude=”/temp” /hosta/www/ root@10.0.0.2:/hostb/www/

说明如下:

–exclude 可用来排除文件,这里排除掉一个文件和一个目录。

/hosta/www/ 是A服务器本地目录

root@10.0.0.2:/hostb/www/ 是目标服务器,即B服务器目录。注意这里的冒号的作用。冒号位置所在的服务器是接收文件的服务器。

如果是用rsync daemon方式,则用两个冒号来标记位置。

SVN使用小记

SVN是个不错的版本控制工具,官网是:http://subversion.apache.org/

简单记录下使用过程,首先下载二进制包,我选择的是这个版本:

http://www.wandisco.com/subversion/download#centos

需要注册一下email.下来的是一个svninstall_centos5_wandisco.sh文件。

[root@localhost svn]# ll
-rw-r–r– 1 root root 5952 Jun 20 09:45 svninstall_centos5_wandisco.sh
[root@localhost svn]# chmod +x svninstall_centos5_wandisco.sh
[root@localhost svn]# ./svninstall_centos5_wandisco.sh

安装过程中可选择是否安装apache and subversion modules。

安装完成后建立版本库:

[root@localhost svn]# svnadmin create /home/svn/repository
[root@localhost svn]# cd repository/conf
[root@localhost conf]# ll
-rw-r–r– 1 root root 1080 Jun 20 11:14 authz
-rw-r–r– 1 root root 309 Jun 20 11:14 passwd
-rw-r–r– 1 root root 2279 Jun 20 11:14 svnserve.conf

主要的配置文件就是这3个,其中修改的要点如下:

[root@localhost conf]# vi svnserve.conf
[general]
anon-access = none
auth-access = read
auth-access = write
password-db = passwd
authz-db = authz

[root@localhost conf]# vi passwd
[users]
banping1 = pwd1
banping2 = pwd2

[root@localhost conf]# vi authz
[groups]
u = banping1,banping2
[/]
@u = rw

然后以daemon方式启动即可:

[root@localhost conf]# svnserve -d -r /home/svn/repository/

默认监听的端口是3690.

如果需要使用户必须lock文件才能编辑,可以修改客户端的subversion config文件属性:

[miscellany]
enable-auto-props = yes
[auto-props]
* = svn:needs-lock

记录一下源码安装MongoDB的过程

本文简单记录在64位centos上安装mongodb的过程。mongodb是用C++开发的面向文档的数据库,也就是反传统的数据库范式来设计的,把相关的对象都记录到一个文档里,每个文档内是schema-free的,也就是列名可以自由定义,比较灵活,特别是面对业务逻辑多变的应用场景十分给力。数据以BSON(类似JSON)的格式二进制存储。不好的地方就是可能带来一定的数据冗余和存储开销。

mongodb目前最新的版本是.8.2-rc3,其源码安装用了很多第三方的东西,比如JS引擎(目前官方推荐的是mozilla的Spider Monkey,以后可能改成google的V8,和node.js一样,呵呵)、正则表达式引擎(pcre)、安装构建工具scons(这东西还要用python来安装)、boost C++库等等。下面是安装实录:

1,下载需要的源文件和相关软件包:

[root@localhost mongodb]# wget http://downloads.mongodb.org/src/mongodb-src-r1.8.2-rc3.tar.gz

[root@localhost mongodb]# wget http://sourceforge.net/projects/scons/files/scons/2.1.0.alpha.20101125/scons-2.1.0.alpha.20101125.tar.gz/download

[root@localhost mongodb]# wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.7.0.tar.gz

[root@localhost mongodb]# wget http://sourceforge.net/projects/pcre/files/pcre/7.4/pcre-7.4.tar.gz/download

2,安装scons:

[root@localhost mongodb]# tar zxvf scons-2.1.0.alpha.20101125.tar.gz

[root@localhost mongodb]# cd scons-2.1.0.alpha.20101125

[root@localhost scons-2.1.0.alpha.20101125]# python setup.py install

3,安装pcre:

[root@localhost mongodb]# tar zxvf pcre-7.4.tar.gz

[root@localhost mongodb]# cd pcre-7.4

[root@localhost pcre-7.4]# ./configure

[root@localhost pcre-7.4]# make

[root@localhost pcre-7.4]# make install

4,安装Spider Monkey:

[root@localhost mongodb]# tar zxvf js-1.7.0.tar.gz

[root@localhost mongodb]# cd js/src

[root@localhost src]# export CFLAGS=”-DJS_C_STRINGS_ARE_UTF8″

[root@localhost src]# make -f Makefile.ref

[root@localhost src]# JS_DIST=/usr make -f Makefile.ref export

5,安装boost,yum方式比较偷懒:

[root@localhost src]# yum -y install boost boost-devel

6,安装mongodb:

[root@localhost mongodb]# tar zxvf mongodb-src-r1.8.2-rc3.tar.gz

[root@localhost mongodb]# cd mongodb-src-r1.8.2-rc3

[root@localhost mongodb-src-r1.8.2-rc3]# scons all

[root@localhost mongodb-src-r1.8.2-rc3]# scons –prefix=/usr/local/mongodb –full install

7,启动mongodb来测试:

[root@localhost bin]# ./mongod –dbpath /tmp
./mongod: error while loading shared libraries: libpcrecpp.so.0: cannot open shared object file: No such file or directory

[root@localhost bin]# ./mongod –dbpath /tmp

./mongod: error while loading shared libraries: libpcrecpp.so.0: cannot open shared object file: No such file or directory

[root@localhost bin]# LD_DEBUG=libs ./mongod -v

……

29656:       trying file=/lib64/tls/x86_64/libpcrecpp.so.0

29656:       trying file=/lib64/tls/libpcrecpp.so.0

29656:       trying file=/lib64/x86_64/libpcrecpp.so.0

29656:       trying file=/lib64/libpcrecpp.so.0

29656:       trying file=/usr/lib64/tls/x86_64/libpcrecpp.so.0

29656:       trying file=/usr/lib64/tls/libpcrecpp.so.0

29656:       trying file=/usr/lib64/x86_64/libpcrecpp.so.0

29656:       trying file=/usr/lib64/libpcrecpp.so.0

29656:

./mongod: error while loading shared libraries: libpcrecpp.so.0: cannot open shared object file: No such file or directory

[root@localhost bin]# ln -s /usr/local/lib/libpcrecpp.so.0 /usr/lib64/libpcrecpp.so.0

[root@localhost bin]# ll /usr/lib64/libpcrecpp.so.0

lrwxrwxrwx 1 root root 30 Jun  8 11:57 /usr/lib64/libpcrecpp.so.0 -> /usr/local/lib/libpcrecpp.so.0

[root@localhost bin]# ./mongod –dbpath /tmp

Wed Jun  8 11:57:38 [initandlisten] MongoDB starting : pid=29700 port=27017 dbpath=/tmp 64-bit

Wed Jun  8 11:57:38 [initandlisten] db version v1.8.2-rc3, pdfile version 4.5

Wed Jun  8 11:57:38 [initandlisten] git version: nogitversion

Wed Jun  8 11:57:38 [initandlisten] build sys info: Linux localhost.localdomain 2.6.18-128.el5 #1 SMP Wed Jan 21 10:41:14 EST 2009 x86_64 BOOST_LIB_VERSION=1_33_1

Wed Jun  8 11:57:38 [initandlisten] waiting for connections on port 27017

Wed Jun  8 11:57:38 [websvr] web admin interface listening on port 28017

为已经编译安装的PHP环境增加mysql模块

PHP编译安装完成后,可能后来又需要某些当时未编译进去的功能模块,这时如果重新编译安装又比较麻烦,这时可以通过安装SO扩展的方式来安装新的模块,需要PHP的源码。本文讲述安装mysql连接模块的过程。

首先进入PHP的源码目录,到ext子目录下进入相应的模块目录:

[root@localhost banping]# cd php-5.3.5/ext/mysql

[root@localhost mysql]# ll
total 184
-rw-r–r– 1 1005 1005  4560 Nov 30  2009 config.m4
-rw-r–r– 1 1005 1005   974 Jun 24  2008 config.w32
-rw-r–r– 1 1005 1005    46 Aug 21  2002 CREDITS
-rw-r–r– 1 1005 1005  8617 Jan 17  2004 mysql.dsp
-rw-r–r– 1 1005 1005  4438 Jan  6  2003 mysql.mak
-rw-r–r– 1 1005 1005  1505 Dec 31  2008 mysql_mysqlnd.h
-rw-r–r– 1 1005 1005  1904 Jul 15  2009 package.xml
-rw-r–r– 1 1005 1005 76369 Oct  5  2010 php_mysql.c
-rw-r–r– 1 1005 1005  1472 Jan  3  2010 php_mysql.h
-rw-r–r– 1 1005 1005  4138 Jan  3  2010 php_mysql_structs.h
drwxr-xr-x 2 1005 1005  4096 Jan  6 00:40 tests
[root@localhost mysql]# /usr/local/php/bin/phpize
Configuring for:
PHP Api Version:         20090626
Zend Module Api No:      20090626
Zend Extension Api No:   220090626

[root@localhost mysql]# ll

total 184

-rw-r–r– 1 1005 1005  4560 Nov 30  2009 config.m4

-rw-r–r– 1 1005 1005   974 Jun 24  2008 config.w32

-rw-r–r– 1 1005 1005    46 Aug 21  2002 CREDITS

-rw-r–r– 1 1005 1005  8617 Jan 17  2004 mysql.dsp

-rw-r–r– 1 1005 1005  4438 Jan  6  2003 mysql.mak

-rw-r–r– 1 1005 1005  1505 Dec 31  2008 mysql_mysqlnd.h

-rw-r–r– 1 1005 1005  1904 Jul 15  2009 package.xml

-rw-r–r– 1 1005 1005 76369 Oct  5  2010 php_mysql.c

-rw-r–r– 1 1005 1005  1472 Jan  3  2010 php_mysql.h

-rw-r–r– 1 1005 1005  4138 Jan  3  2010 php_mysql_structs.h

然后和正常安装其他扩展一样:

[root@localhost mysql]# /usr/local/php/bin/phpize

Configuring for:

PHP Api Version:         20090626

Zend Module Api No:      20090626

Zend Extension Api No:   220090626

[root@localhost mysql]# ./configure –with-php-config=/usr/local/php/bin/php-config

[root@localhost mysql]# make

[root@localhost mysql]# make install

Installing shared extensions:     /usr/local/php/lib/php/extensions/no-debug-non-zts-20090626/

[root@localhost mysql]# ll /usr/local/php/lib/php/extensions/no-debug-non-zts-20090626/

-rwxr-xr-x 1 root root  145402 Apr 27 18:14 mysql.so

然后把这个mysql.so添加到php.ini文件就可以了。

邮件系统那点事

大多数Linux发行版都带了sendmail程序用于发送邮件,但是由于安全性、效率和易用性的问题,目前大多数邮件系统都是用postfix搭建。安装postfix的过程很简单,首先停掉原来的sendmail服务:

[root@mail ~]# /etc/init.d/sendmail stop
Shutting down sm-client: [  OK  ]
Shutting down sendmail: [  OK  ]

[root@mail ~]# /etc/init.d/sendmail stop

Shutting down sm-client: [  OK  ]

Shutting down sendmail: [  OK  ]

然后通过yum安装postfix:

[root@mail ~]# yum install postfix

Loaded plugins: fastestmirror

Determining fastest mirrors

* addons: centos.ustc.edu.cn

* base: centos.ustc.edu.cn

* extras: centos.ustc.edu.cn

* updates: centos.ustc.edu.cn

addons                                                                                                                                           |  951 B     00:00

base                                                                                                                                             | 1.1 kB     00:00

base/primary                                                                                                                                     | 954 kB     00:00

base                                                                                                                                                          2683/2683

extras                                                                                                                                           | 2.1 kB     00:00

updates                                                                                                                                          | 1.9 kB     00:00

updates/primary_db                                                                                                                               | 293 kB     00:00

Setting up Install Process

Resolving Dependencies

–> Running transaction check

—> Package postfix.i386 2:2.3.3-2.2.el5_6 set to be updated

–> Finished Dependency Resolution

Dependencies Resolved

===================================================

Package                               Arch                               Version                                           Repository                             Size

===================================================

Installing:

postfix                               i386                               2:2.3.3-2.2.el5_6                                 updates                               3.5 M

Transaction Summary

===================================================

Install       1 Package(s)

Upgrade       0 Package(s)

Total download size: 3.5 M

Is this ok [y/N]: y

Downloading Packages:

postfix-2.3.3-2.2.el5_6.i386.rpm                                                                                                                 | 3.5 MB     00:00

Running rpm_check_debug

Running Transaction Test

Finished Transaction Test

Transaction Test Succeeded

Running Transaction

Installing     : postfix                                                                                                                                          1/1

Installed:

postfix.i386 2:2.3.3-2.2.el5_6

Complete!

[root@mail ~]# /etc/init.d/postfix –help

Usage: /etc/init.d/postfix {start|stop|restart|reload|abort|flush|check|status|condrestart}

[root@mail ~]#

[root@mail ~]# cd /etc/postfix/

[root@mail postfix]# ll

total 228

-rw-r–r– 1 root root 17832 Apr 14 06:59 access

-rw-r–r– 1 root root  3550 Apr 14 06:59 bounce.cf.default

-rw-r–r– 1 root root 11175 Apr 14 06:59 canonical

-rw-r–r– 1 root root  9920 Apr 14 06:59 generic

-rw-r–r– 1 root root 16838 Apr 14 06:59 header_checks

-rw-r–r– 1 root root 11942 Apr 14 06:59 LICENSE

-rw-r–r– 1 root root 26737 Apr 14 06:59 main.cf

-rw-r–r– 1 root root 17981 Apr 14 06:59 main.cf.default

-rw-r–r– 1 root root   958 Apr 14 06:59 makedefs.out

-rw-r–r– 1 root root  4137 Apr 14 06:59 master.cf

-rw-r–r– 1 root root 17639 Apr 14 06:59 postfix-files

-rwxr-xr-x 1 root root  6366 Apr 14 06:59 postfix-script

-rwxr-xr-x 1 root root 22564 Apr 14 06:59 post-install

-rw-r–r– 1 root root  6805 Apr 14 06:59 relocated

-rw-r–r– 1 root root  1629 Apr 14 06:59 TLS_LICENSE

-rw-r–r– 1 root root 12081 Apr 14 06:59 transport

-rw-r–r– 1 root root 11984 Apr 14 06:59 virtual

[root@mail postfix]#

配置postfix主要是通过main.cn文件,其中最主要的参数是myhostname,很多其他配置都是默认基于这个参数的。

要配置好一个邮件系统,需要对其原理有个基本了解,特别是DNS、MX、反解析、MTA、SMTP、POP3、IMAP等概念要有个了解。

检查配置的正确性,然后启动postfix服务:

[root@mail postfix]# postfix check

[root@mail postfix]# /etc/init.d/postfix start

Starting postfix: [  OK  ]

测试发送邮件:

[root@mail postfix]# mail yourgoogleaccount@gmail.com

Subject: test postfix

this is mail content

.

Cc:

可查看日志,看看启动进程的过程,和邮件发送的步骤,以下是启动postfix进程的日志:

Apr 17 17:18:23 web4 sendmail[5354]: alias database /etc/aliases rebuilt by root

Apr 17 17:18:23 web4 sendmail[5354]: /etc/aliases: 76 aliases, longest 10 bytes, 765 bytes total

Apr 17 17:18:23 web4 postfix/postfix-script: starting the Postfix mail system

Apr 17 17:18:23 web4 postfix/master[5400]: daemon started — version 2.3.3, configuration /etc/postfix

发送邮件,首先要一个客户端投递信件给postfix MTA,这里的客户端就是操作系统的这个mail命令:

Apr 17 17:22:45 web4 sendmail[5443]: p3H9MjmS005443: from=root, size=94, class=0, nrcpts=2, msgid=<201104170922.p3H9MjmS005443@mail.banping.com>, relay=root@localhost

Apr 17 17:22:45 web4 postfix/smtpd[5444]: connect from localhost[127.0.0.1]

Apr 17 17:22:45 web4 postfix/smtpd[5444]: 2424B138042: client=localhost[127.0.0.1]

Apr 17 17:22:45 web4 postfix/cleanup[5447]: 2424B138042: message-id=<201104170922.p3H9MjmS005443@mail.banping.com>

Apr 17 17:22:45 web4 sendmail[5443]: p3H9MjmS005443: to=yourgoogleaccount@gmail.com, ctladdr=root (0/0), delay=00:00:00, xdelay=00:00:00, mailer=relay, pri=60094, relay=[127.0.0.1] [127.0.0.1], dsn=2.0.0, stat=Sent (Ok: queued as 2424B138042)

Apr 17 17:22:45 web4 postfix/qmgr[5403]: 2424B138042: from=<root@mail.banping.com>, size=491, nrcpt=2 (queue active)

Apr 17 17:22:45 web4 postfix/smtpd[5444]: disconnect from localhost[127.0.0.1]

postfix成功接收后,发现不是发给本地用户的邮件,就开始relay邮件,成功后删除,如果失败则会隔一段时间重试:

Apr 17 17:22:52 web4 postfix/smtp[5448]: 2424B138042: to=<yourgoogleaccount@gmail.com>, relay=gmail-smtp-in.l.google.com[74.125.53.27]:25, delay=7.1, delays=0.07/0/2.6/4.4, dsn=2.0.0, status=sent (250 2.0.0 OK 1303032313 r10si6992085pbr.25)

Apr 17 17:22:52 web4 postfix/qmgr[5403]: 2424B138042: removed

Yahoo邮箱比较难搞定,会出现多次重发的情况,可以通过官方的反馈渠道沟通解决。

如果方便程序使用邮件系统,可以自己实现一个客户端来和postfix交互,相信大多数邮件系统就是这样实现的。

如何给生产环境的TC/TT部署一个从库

[root@web-04 tc]# mkdir backup
[root@web-04 tc]# cd backup/
[root@web-04 backup]# ll
total 0
You have new mail in /var/spool/mail/root
[root@web-04 backup]# vi backup.sh
#! /bin/sh
srcpath=”$1″
destpath=”$1.$2″
rm -f “$destpath”
cp -f “$srcpath” “$destpath”

要备份TC/TT数据库比较简单,用tcrmgr copy命令即可,如果要给一个在生产环境上运行的TC/TT加一个从库,就要考虑数据的一致性问题。首先就是要获取一个开始同步的时间戳:

[root@web-04 tc]# mkdir backup

[root@web-04 tc]# cd backup/

[root@web-04 backup]# vi backup.sh

#! /bin/sh

srcpath=”$1″

destpath=”$1.$2″

rm -f “$destpath”

cp -f “$srcpath” “$destpath”

[root@web-04 tc]# chmod +x backup/backup.sh

[root@web-04 tc]# tcrmgr copy -port 11216 10.0.0.2 ‘@/home/tc/backup/backup.sh’

[root@web-04 tc]# ll livefeed/

total 1244

-rw-r–r– 1 root root 272365 Apr 13 17:21 00000001.ulog

-rw-r–r– 1 root root    702 Apr 13 17:45 livefeed.log

-rw-r–r– 1 root root      6 Mar 31 19:31 livefeed.pid

-rw-r–r– 1 root root 731680 Apr 13 17:45 livefeed.tct

-rw-r–r– 1 root root 731680 Apr 13 17:46 livefeed.tct.1302687980238123

-rw-r–r– 1 root root  74496 Apr 13 17:45 livefeed.tct.idx.user_id.dec

-rw-r–r– 1 root root  74496 Apr 13 17:46 livefeed.tct.idx.user_id.dec.1302687980245858

-rw-r–r– 1 root root      0 Apr 13 17:46 livefeed.tct.idx.user_id.dec.wal

可以看到生成了两个13026879802数字开头的文件,这分别是数据文件和索引文件的时间戳。因为这是一个有索引的数据库,而搭建从库的时候我们是不必考虑索引的,启动ttserver的时候会根据参数自动建立索引。

接下来把数据文件复制到从库所在的服务器:

[root@web-04 livefeed]# scp livefeed.tct.1302687980238123 root@10.0.0.215:/home/tc/livefeed/

在从库生成一个同步时间戳用的rts文件:

[root@mail livefeed]# ll

total 720

-rw-r–r– 1 root root 731680 Apr 13 17:48 livefeed.tct.1302687980238123

[root@mail livefeed]# echo 1302687980238123 > livefeed.rts

[root@mail livefeed]# mv livefeed.tct.1302687980238123 livefeed.tct

[root@mail livefeed]# ll

total 724

-rw-r–r– 1 root root     17 Apr 13 17:50 livefeed.rts

-rw-r–r– 1 root root 731680 Apr 13 17:48 livefeed.tct

然后在从库启动ttserver实例,指定主库的地址和端口号,注意需要rcc和rts参数:

[root@mail livefeed]# ttserver -mhost 10.0.0.2 -mport 11216 -host 10.0.0.215 -port 11216 -thnum 8 -dmn -pid /home/tc/livefeed/livefeed.pid -log /home/tc/livefeed/livefeed.log -le -ulog /home/tc/livefeed/ -ulim 128m -sid 22 -rcc -rts /home/tc/livefeed/livefeed.rts /home/tc/livefeed/livefeed.tct#idx=user_id:dec

[root@mail livefeed]# ll

total 776

-rw-r–r– 1 root root    737 Apr 13 17:54 livefeed.log

-rw-r–r– 1 root root      5 Apr 13 17:54 livefeed.pid

-rw-r–r– 1 root root     17 Apr 13 17:50 livefeed.rts

-rw-r–r– 1 root root 731680 Apr 13 17:54 livefeed.tct

-rw-r–r– 1 root root  74496 Apr 13 17:54 livefeed.tct.idx.user_id.dec

可以在主库上插入数据测试,从库这边会同步insert ,同时生成自己的ulog文件。