RedHat的yum无法使用的解决办法

最近弄了一台阿里云服务器,安装的是RedHat5.4 64位的系统。有点奇怪的是阿里云只提供CentOS 32位的系统,RH才有64位的。但是在使用yum的过程中报错:

[root@banping mysql-5.5.10]# yum install libaio-devel

Loaded plugins: rhnplugin, security

This system is not registered with RHN.

RHN support will be disabled.

Setting up Install Process

No package libaio-devel available.

Nothing to do

意思是这套RH没有在官网上注册,不能下载RH的软件包。后来我把yum替换成了CentOS的版本,过程如下:

首先看看有哪些yum包,然后卸载掉:

[root@banping mysql-5.5.10]# rpm -qa|grep yum

yum-rhn-plugin-0.5.4-13.el5

yum-3.2.22-20.el5

yum-metadata-parser-1.1.2-3.el5

yum-security-1.1.16-13.el5

yum-updatesd-0.9-2.el5

卸载要使用nodeps参数,否则由于依赖关系是无法卸载成功的:

rpm -e –nodeps yum-rhn-plugin-0.5.4-13.el5

……

然后可以从163的镜像下载CentOS的yum包:

[root@banping centos]# wget http://mirrors.163.com/centos/5/os/x86_64/CentOS/yum-3.2.22-37.el5.centos.noarch.rpm

[root@banping centos]# wget http://mirrors.163.com/centos/5/os/x86_64/CentOS/yum-fastestmirror-1.1.16-16.el5.centos.noarch.rpm

[root@banping centos]# wget http://mirrors.163.com/centos/5/os/x86_64/CentOS/yum-metadata-parser-1.1.2-3.el5.centos.x86_64.rpm

安装新下载的包:

[root@banping centos]# rpm -ivh yum-*

找一个CentOS的包资源配置库,名为CentOS-Base.repo,放到/etc/yum.repos.d/路径:

wget http://www.linuxidc.com/files/2011/05/06/CentOS-Base.repo

生成缓存文件到/var/cache/yum路径:

[root@banping yum.repos.d]# yum makecache

这样就可以使用CentOS的yum了。

update 2012-05-04:
如果报错GPG key retrieval failed,可重新导入KEY:

# wget http://centos.ustc.edu.cn/centos/RPM-GPG-KEY-CentOS-5
# rpm –import RPM-GPG-KEY-CentOS-5

如何在Linux服务器之间ssh不需要密码?

要在一台Linux上ssh到另外一台Linux服务器,而不需要每次都输入密码,其实很简单。比如要从A服务器登录到B服务器,那么首先在A服务器上生成公钥和私钥,用RSA加密算法:

[root@test awstats]# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
9c:74:d0:05:48:c8:43:84:a8:44:8f:b6:a1:2f:8c:91 root@test.banping.com
[root@test awstats]# cd /root/.ssh/
[root@test .ssh]# ll
-rw------- 1 root root 1675 10-12 10:15 id_rsa
-rw-r--r-- 1 root root  397 10-12 10:15 id_rsa.pub
-rw-r--r-- 1 root root 1972 08-15 19:50 known_hosts

可见这里的id_rsa和id_rsa.pub就是私钥和公钥了,把公钥的内容放到B服务器的指定位置就可以了。

放到哪里呢?对应登录用户的home的.ssh目录下,文件名一般为authorized_keys,其实这个名字是任意的,是写在sshd_config文件里的:

[root@web-04 .ssh]# cd /etc/ssh

[root@web-04 ssh]# vi sshd_config

AuthorizedKeysFile      .ssh/authorized_keys

这个authorized_keys文件可以放置多个其他服务器的公钥,如果你想让多台服务器都能无需输入密码就登录上来,把它们的公钥放这里就行了。不过安全第一,切不可为了省事而忽略了安全的问题。

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。

网站的消息通知产品设计漫谈

现在的很多网站都有消息通知系统,比如新浪微博页面右上角的小黄签,比如Facebook页面左上角的Notifications。但是消息通知系统的说法是个笼统的概念,我理解的其本质功能是网站把某些对用户有价值的信息及时告知用户。比如常见的SNS关系中谁关注了你,谁评价了你发布的内容,谁邀请你加入某个小组等。

这类消息可以大体上分为两类,一种是告知性质的,就是用户知道有这么回事就行了,最多是具体看一下内容,比如其他用户对你发布的内容做了评论。另外一种是需要用户处理的,用户必须做出某种动作来回应,比如好友请求,你是接受、拒绝还是忽略。

纵观现在一些网站的消息通知产品设计,可以分为两种实现方式,一种是把各个功能模块的消息分类,然后每类有多少数字告知用户,用户需要到具体的功能模块页面查看同类的内容,典型的是新浪微博的设计。如下图所示:

其按功能分类通知每类新消息的数目,然后可以点击链接到某个功能模块查看同类消息。对应的,在功能设计上就有专门的评论汇总地方,有@我汇总的地方。这样的好处在同类消息很多的时候,比如收到几十条新评论的时候,用户不必频繁的在消息通知页面和具体评论页面来回切换,因为所有的评论在一个页面都能查看了。不好的地方就是不够直观,需要再次点击才能查看用户是对你的哪些内容发布了评论。另外,新浪微博中并没有用户必须处理的操作,用户之间是以关注为表现形式的弱关系,不需要确认就能关注一个人。

相对的,Facebook是所谓的强关系,就是用户加一个人为好友,必须得到对方的确认,为了处理好告知性质和操作性质两方面的消息通知,Facebook把好友请求部分独立出来了,可以理解为是一种比较复杂的消息通知。其界面如下:

用户必须在这个界面进行确认才能真正成为朋友,但是在消息通知里告知用户并能马上确认,对用户操作来说是很方便的。Facebook传统的消息通知和新浪微博不同,它可以在消息里显示具体的内容,而不是单纯的数量提示:

这样做的好处就是,不必设计一个单独的功能汇总某一类的消息,不好的地方就是在消息很多的时候,用户需要频繁的在消息通知界面和具体的内容界面切换来查看未读的内容,比较麻烦。国内模仿quora的知乎也是这样设计消息通知功能的:

那么有没有更好的方式来展现消息通知及其具体内容呢,Google Plus做成了更好的尝试,首先在消息通知的小窗口就能查看某一条具体消息的内容:

以下是在小窗口查看具体消息内容的情况,在这个页面可以进行消息详细内容的前后切换:

然后在完整的消息列表页,是直接显示了消息的详细内容:

正如你看到的,前两条消息就要占用一屏以上的空间,这在消息很多的情况下,是很不方便的。那么有没有更好的展现方式呢,我认为Twitter的界面风格就是最佳的方式:

在左边展示完整的消息列表,右边展示某个消息的具体内容及操作动作,用户可以很清晰的知道自己当前查看或处理的消息,并且不需要界面切换,perfect!

消息通知的合并也很重要,可以避免大量同样的消息对用户造成干扰,新浪微博的通知数目的方式本身就是一种合并,Facebook和G+也都对合并做的很好。还要注意的是,Facebook对于一段时间以前的历史消息就不予显示了,这无论从消息通知的功能本质来说,还是系统的性能方面考虑,都是可以理解的。

 

 

搭建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网站上,版权所有,转载请注明出处。

SELinux给vsftpd配置带来的问题

今天在centos 5.6 x86_64的服务器上配置一个vsftpd的时候碰到了一些波折,系统用户登录后死活都不能切换目录,也无法上传文件:

C:Usersrt8>ftp 192.168.0.26
连接到 192.168.0.26。
220 (vsFTPd 2.0.5)
用户(192.168.0.26:(none)): dev
331 Please specify the password.
密码:
230 Login successful.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
admins
api_www
backend_templates
backend_templates_c
bt.tar.gz
command
config
home
icon
lib
mobile
mobile_templates
mobile_templates_c
rt.cn
rtweb.tar.gz
script
site_api
temp
templates
templates_c
test
trunk
www
226 Directory send OK.
ftp: 收到 235 字节,用时 0.01秒 23.50千字节/秒。
ftp> cd test
550 Failed to change directory.
ftp> bye
221 Goodbye.

C:Usersbanping>ftp 192.168.0.26

连接到 192.168.0.26。

220 (vsFTPd 2.0.5)

用户(192.168.0.26:(none)): banping

331 Please specify the password.

密码:

230 Login successful.

ftp> ls

200 PORT command successful. Consider using PASV.

150 Here comes the directory listing.

temp

test

226 Directory send OK.

ftp: 收到 235 字节,用时 0.01秒 23.50千字节/秒。

ftp> cd test

550 Failed to change directory.

ftp> put d:/1.txt

200 PORT command successful. Consider using PASV.

553 Could not create file.

ftp> bye

221 Goodbye.

经确认不是配置的问题,后来才发现是SELinux导致的。SELinux全称是Security Enhanced Linux,是增强Linux安全性的一个软件包,默认配置在/etc/selinux目录,可以关掉它对FTP的安全审核功能来解决这个问题:

[root@dev dev]# setsebool -P ftpd_disable_trans on

[root@dev dev]# service vsftpd restart

然后再试就OK了。记录一下备忘。

HandlerSocket性能测试

引言

NoSQL是伴随着web2.0的迅猛发展而在2009年被提出的一个概念,一般可以通俗的理解为高性能的Key Value存储结构的数据库,当然也有其他更广泛的类型。它基于CAP和BASE理论,强调最终一致性,具有数据结构灵活、扩展方便、大数据量下读写性能高效等特点,在互联网行业被广泛采用。本系列文章将评测广受关注的几个NoSQL数据库产品。本文关注的是HandlerSocket Plugin for MySQL。

介绍

HandlerSocket是日本DeNA公司的架构师Yoshinori开发的一个NoSQL产品,以MySQL Plugin的形式运行。其主要的思路是在MySQL的体系架构中绕开SQL解析这层,使得应用程序直接和Innodb存储引擎交互,通过合并写入、协议简单等手段提高了数据访问的性能,在CPU密集型的应用中这一优势尤其明显。其架构图如下:

image001

图片来源:HandlerSocket作者博客

另外,HandlerSocket还帮我们解决了缓存的问题,因为Innodb已经有了成熟的解决方案,通过参数可以配置用于缓存数据的内存大小,这样只要我们分配合理的参数,就能在应用程序无需干涉的情况下实现热点数据的缓存,降低缓存维护的开发成本。

因为HandlerSocket是MySQL的一个Plugin,集成在mysqld进程中,对于NoSQL无法实现的复杂查询等操作,仍然可以使用MySQL自身的关系型数据库功能来实现。在运维层面,原来广泛使用的MySQL主从复制等经验可以继续发挥作用,相比其他或多或少存在一些bug的NoSQL产品,数据安全性更有保障。

可以说这是一个很有创意的产品,因此HandlerSocket的作者在2010年10月在博客上宣称这一产品能达到75K QPS的时候,在业界引起了广泛的关注,包括MySQL官方在新的5.6版本中推出的Memcached API,相信也是受了HandlerSocket的启发。

安装

一、安装MySQL

通过编译源码的方式安装MySQL,这里选择的版本是5.5.8版本。由于这不是本文的重点,这里只简单的提及一下要点。

首先到MySQL的官网http://dev.mysql.com/downloads/mysql/下载源码,要注意的是从MySQL 5.5版本开始需要Cmake编译工具和bison。然后开始安装:

[root@localhost handlersocket]# tar zxvf mysql-5.5.8.tar.gz

[root@localhost handlersocket]# cd mysql-5.5.8

[root@localhost mysql-5.5.8]#  cmake . -DBUILD_CONFIG=mysql_release -DCMAKE_INSTALL_PREFIX=/home/handlersocket/mysql558/ -DSYSCONFDIR=/home/handlersocket/mysql558/ -DWITH_EMBEDDED_SERVER=on -DWITH_READLINE=on -DWITH_SSL=yes -DENABLED_LOCAL_INFILE=on -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITH_PERFSCHEMA_STORAGE_ENGINE=1 -DWITH_PARTITION_STORAGE_ENGINE=1

[root@localhost mysql-5.5.8]# make

[root@localhost mysql-5.5.8]# make install

安装完成后配置my.cnf文件,最重要的参数是分配给innodb存储引擎的内存大小,也就是innodb_buffer_pool_size参数,这里设置为2G。

[root@localhost mysql558]# vi my.cnf

……

innodb_buffer_pool_size = 2G

……

二、安装HandlerSocket

首先到以下地址下载代码:

https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL

开始安装HandlerSocket:

[root@localhost handlersocket]# tar -zxvf ahiguti-HandlerSocket-Plugin-for-MySQL-1.0.6-67-g25f4957.tar.gz

[root@localhost handlersocket]# cd handlersocket/

[root@localhost handlersocket]# ./autogen.sh

[root@localhost handlersocket]# ./configure –with-mysql-source=/home/handlersocket/mysql-5.5.8 –with-mysql-bindir=/home/handlersocket/mysql558/bin  –with-mysql-plugindir=/home/handlersocket/mysql558/lib/plugin

[root@localhost handlersocket]# make

[root@localhost handlersocket]# make install

三、修改MySQL配置

在my.cnf配置文件增加以下内容:

[root@localhost mysql558]# vi my.cnf

[mysqld]

……

loose_handlersocket_port = 9998

# the port number to bind to (for read requests)

loose_handlersocket_port_wr = 9999

# the port number to bind to (for write requests)

loose_handlersocket_threads = 16

# the number of worker threads (for read requests)

loose_handlersocket_threads_wr = 16

# the number of worker threads (for write requests)

open_files_limit = 65535

# to allow handlersocket accept many concurrent connections

#make open_files_limit as large as possible.

这里增加的这些主要是针对handlersocket的配置,它有2个端口,9998用来读数据,9999读写均可,但是通过9998来读的效率更高。这里我们设置处理读写的线程数均为16个,另外为了处理更多的并发连接,设置能打开的文件描述符个数为65535。

四、安装MySQL授权表并启动数据库

[root@localhost mysql558]# scripts/mysql_install_db –user=mysql

[root@localhost mysql558]# bin/mysqld_safe –user=mysql &

五、登录MySQL安装HandlerSocket Plugin:

[root@localhost mysql558]# bin/mysql -u root -p

mysql> install plugin handlersocket soname ‘handlersocket.so’;

Query OK, 0 rows affected (0.01 sec)

通过以下命令查看也可见handlersocket已经成功安装到MySQL中:

mysql> show processlist;

mysql> show plugins;

测试说明

一、测试环境

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

CPU为Xeon 2.80GHz *4

内存为4G

硬盘为一块400G SATA盘

操作系统为64位CentOS 5.3版本

二、测试方法

Handlersocket的客户端几乎涵盖了各种语言,都实现了通过以上两个端口对数据读写的操作,其中查询操作必须通过建立有索引的列来进行。

考虑到NoSQL在互联网行业应用较为广泛,采用PHP实现客户端程序的做法,通过一定的并发去读写数据,观测每秒读写的记录数作为主要的衡量指标。当然也可以利用Java的多线程进行并发测试,但由于篇幅和时间所限,本文不再涉及此方面的内容。

这里采用第三方实现的一个PHP客户端,网址为http://code.google.com/p/php-handlersocket/,可以编译到PHP运行环境中。

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

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

测试结果及总结

一、写操作

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

image003

image005

image007

可见,CPU使用率平稳,Idle值稳定在69到74之间,wait值稳定在15到19之间。内存分配上的变化较大,主要用来缓存数据,cache部分上升了1.4G,但是没有交换区到内存的换入换出。磁盘IO表现平稳,每秒有70%到80%的时间用于处理IO操作。

二、读操作

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

image009

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

通过以上测试结果可以说明,写入的数据完全缓存到了文件系统中,所以cache部分占用的内存大量增加,这也是读取数据的时候没有发生磁盘IO的原因。

要说明的是,虽然这是一份真实的测试数据,但是并不一定具有普遍意义。因为应用场景是千差万别的,服务器的资源配置、数据记录的多少、单条数据的大小、读写的比例、客户端程序的质量等因素都会影响测试结果,甚至差别会非常大,对我们来说,更重要的是了解这个NoSQL产品的特性,知道它的适用场景,并且能够根据自己实际的应用场景针对性的进行测试,这样才能做到针对性的选型,只有最适合自己需求的产品才是最好的产品。

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

TechClub技术沙龙及第一次活动

一直以来北京等一线城市的技术交流活动都有很多,而厦门虽然互联网公司也不少,但大多规模都不大,交流的气氛也不够浓厚。因此我和几位朋友发起了一个TechClub技术沙龙,希望能有更多喜欢技术、热爱分享的朋友加入进来,一起推动厦门及周边地区的IT技术沙龙活动,在交流中结识朋友,共同提高。我们的网站是http://www.tech-club.org

2011年7月30日,我们的第一次地面活动成功举行了,主题是《互联网数据库应用实践》,大概有70位朋友参与,还有很多朋友冒着极大的危险(原因你懂的)从福州乘动车赶过来参加,非常让人感动,这也促使我努力的把这个活动办下去,让更多的朋友能从中受益。意外的是还有一位北京MySQL公司的朋友顺路过来参与,技术无界限啊。

banping@TechClub

我在本次活动中分享了一个主题,叫《设计高性能MySQL应用》,以下是PPT内容:

为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

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

使用XtraBackup备份MySQL的一个经典错误

本文开头提到的问题就是在这个锁表的过程中不成功,造成等待而最终超时退出。
今天凌晨2点50分,一台MySQL服务器的备份遭遇了一个错误,信息如下:
innobackupex: Error: Connection to mysql child process (pid=20080) timedout. (Time limit of 900 seconds exceeded. You may adjust time limit by editing the value of parameter “$mysql_response_timeout” in this script.) while waiting for reply to MySQL request: ‘FLUSH TABLES WITH READ LOCK;’ at /home/mysql/backup/xtrabackup-1.6/innobackupex line 336.
这是使用XtraBackup容易引起的一个经典错误,引起这个错误的原因也很简单,就是XtraBackup在备完innodb表的时候,要获得对所有表的锁定来备份MyISAM等其他表,来保证数据的一致性,如果数据库不断的有DML操作,XtraBackup就可能迟迟无法获得对所有表的锁定,最后超时。
让我们先了解一下XtraBackup备份MySQL的原理和过程。

前几天一台MySQL服务器的备份遭遇了一个错误,信息如下:

innobackupex: Error: Connection to mysql child process (pid=20080) timedout. (Time limit of 900 seconds exceeded. You may adjust time limit by editing the value of parameter “$mysql_response_timeout” in this script.) while waiting for reply to MySQL request: ‘FLUSH TABLES WITH READ LOCK;’ at /home/mysql/backup/xtrabackup-1.6/innobackupex line 336.

这是使用XtraBackup容易引起的一个经典错误,引起这个错误的原因也很简单,就是XtraBackup在备完innodb表的时候,要获得对所有表的锁定来备份MyISAM表和其他文件,来保证数据的一致性,如果在此过程中数据库不断的有DML操作,XtraBackup就可能迟迟无法获得对所有表的锁定,最后超时。

要避免这个问题,一个是加大超时的时间限制,但这显然不是一个好的解决办法,另外就是在备份的时候加上–no-lock参数,就是在复制MyISAM表和.frm等文件的时候不锁表,但要保证这时没有对MyISAM表的DML操作和Innodb表的DDL操作,这一般通过应用端权衡时间是容易办到的。

但是要注意的是,如果备份出来的文件要用于主从复制,那么不锁表会导致没有输出 slave_info文件和binlog_info文件,这对于一些人的应用场景可能会有影响。这时可以加一个–safe-slave-backup参数,使得在从库上备份的时候停止SQL THRED,这样即使从库能从主库接收binlog文件,但是不会应用,relay log position就不会移动了。但是单纯的停掉slave SQL Thread是不会影响binlog position的。

因为临时表的原因,–safe-slave-backup需要在SHOW STATUS的输出中slave_open_temp_tables为0的时候才停止slave SQL Thread,否则会导致复制出问题。因为临时表是基于用户session的,因此如果正在操作临时表的数据的时候停止slave SQL Thread,会导致可能后续的数据不一致,在其他停用slave SQL Thread的场景也要注意这个问题。

解析XtraBackup备份MySQL的原理和过程

XtraBackup是percona公司提供的开源工具,以热备Innodb表著称而被广泛采用。

XtraBackup对Innodb的备份之所以是热备,无需锁表,是基于Innodb自身的崩溃恢复机制,它首先复制所有的Innodb数据文件,这样复制出来的文件肯定是不一致的,然后对每个文件进行崩溃恢复处理,最终达到一致。就和MySQL在启动Innodb的时候一样,会通过比较数据文件头和redo log文件头信息来检查数据是否是一致的,如果不一致就尝试通过前滚(把redo log中所有提交的事务写入数据文件)和回滚(从数据文件中撤销所有redo log中未提交的事务引起的修改)来使数据达到最终一致。

This works because InnoDB maintains a redo log, also called the transaction log. This contains a record of every change to InnoDB’s data. When InnoDB starts, it inspects the data files and the transaction log, and performs two steps. It applies committed transaction log entries to the data files, and it performs an undo operation on any transactions that modified data but did not commit.
XtraBackup works by remembering the log sequence number (LSN) when it starts, and then copying away the data files. It takes some time to do this, so if the files are changing, then they reflect the state of the database at different points in time. At the same time, XtraBackup runs a background process that watches the transaction log files, and copies changes from it. XtraBackup needs to do this continually because the transaction logs are written in a round-robin fashion, and can be reused after a while. XtraBackup needs the transaction log records for every change to the data files since it began execution.
The above is the backup process. Next is the prepare process. During this step, XtraBackup performs crash recovery against the copied data files, using the copied transaction log file. After this is done, the database is ready to restore and use.
The above process is implemented in the xtrabackup compiled binary program. The innobackupex program adds more convenience and functionality by also permitting you to back up MyISAM tables and .frm files. It starts xtrabackup, waits until it finishes copying files, and then issues FLUSH TABLES WITH READ LOCK to prevent further changes to MySQL’s data and flush all MyISAM tables to disk. It holds this lock, copies the MyISAM files, and then releases the lock.
The backed-up MyISAM and InnoDB tables will eventually be consistent with each other, because after the prepare (recovery) process, InnoDB’s data is rolled forward to the point at which the backup completed, not rolled back to the point at which it started. This point in time matches where the FLUSH TABLES WITH READ LOCK was taken, so the MyISAM data and the prepared InnoDB data are in sync.
The xtrabackup and innobackupex tools both offer many features not mentioned in the preceding explanation. Each tool’s functionality is explained in more detail on its manual page. In brief, though, the tools permit you to do operations such as streaming and incremental backups with various combinations of copying the data files, copying the log files, and applying the logs to the data.

XtraBackup在启动的时候会记录一个LSN(log sequence number),然后就把所有的Innodb数据文件复制出来,这样复制出来的数据文件是不一致的,但是XtraBackup会在后台运行一个进程把所有对redo log file的修改记录下来,只要有了这个数据,就能进行崩溃恢复。只所以要额外记录下来,是因为MySQL自身的redo log file是可重用的。

以上的操作是由xtrabackup二进制程序(比如xtrabackup_55)完成的,如果使用innobackupex 脚本,刚才的步骤完成以后,innobackupex就会去备份MyISAM表和.frm文件,这时要保证数据的一致性就会先锁表了,通过FLUSH TABLES WITH READ LOCK命令锁表然后把文件复制出来,再释放掉这个锁。

在恢复数据的时候,要经过prepare(recovery)和restore两个步骤。在prepare结束以后,Innodb的表恢复到了复制Innodb文件结束的时间点,这个时间点也就是锁表复制MyISAM表的起点,所以最终数据是一致的。一般我们在恢复的时候执行两次prepare,是因为第二次prepare会帮助我们生成redo log文件,从而加快MySQL数据库启动的速度。

我们再来看一下实际备份的日志来理解这个过程:

……

110701 03:29:13  innobackupex: Starting ibbackup with command: xtrabackup_55  –defaults-file=”/home/mysql/3306/my.cnf” –backup –suspend-at-end –log-stream –target-dir=./

innobackupex: Waiting for ibbackup (pid=22334) to suspend

innobackupex: Suspend file ‘/home/mysql/3306/data/xtrabackup_suspended’

xtrabackup: suspend-at-end is enabled.

xtrabackup: uses posix_fadvise().

xtrabackup: cd to /home/mysql/3306/data

xtrabackup: Target instance is assumed as followings.

xtrabackup:   innodb_data_home_dir = /home/mysql/3306/data

xtrabackup:   innodb_data_file_path = ibdata1:512M:autoextend

xtrabackup:   innodb_log_group_home_dir = /home/mysql/3306/redolog

xtrabackup:   innodb_log_files_in_group = 3

xtrabackup:   innodb_log_file_size = 134217728

110701  3:29:13 InnoDB: Using Linux native AIO

110701  3:29:13  InnoDB: Warning: allocated tablespace 268, old maximum was 0

xtrabackup: Stream mode.

>> log scanned up to (2371741708)

110701 03:29:15  innobackupex: Continuing after ibbackup has suspended

innobackupex: Starting to backup InnoDB tables and indexes

innobackupex: from original InnoDB data directory ‘/home/mysql/3306/data’

innobackupex: Backing up as tar stream ‘ibdata1′

>> log scanned up to (2371741708)

>> log scanned up to (2371742105)

>> log scanned up to (2371742105)

innobackupex: Backing up file ‘/home/mysql/3306/data/test/t.ibd’

>> log scanned up to (2371742115)

innobackupex: Backing up files ‘/home/mysql/3306/data/banping/*.ibd’ (16 files)

……

110701 03:29:35  innobackupex: Connected to database with mysql child process (pid=22630)

>> log scanned up to (2371742526)

110701 03:29:39  innobackupex: Starting to lock all tables…

>> log scanned up to (2371742526)

>> log scanned up to (2371742526)

110701 03:29:51  innobackupex: All tables locked and flushed to disk

110701 03:29:51  innobackupex: Starting to backup .frm, .MRG, .MYD, .MYI,

innobackupex: .TRG, .TRN, .ARM, .ARZ, .CSM, .CSV and .opt files in

innobackupex: subdirectories of ‘/home/mysql/3306/data’

innobackupex: Backing up files ‘/home/mysql/3306/data/banping/*.{frm,MYD,MYI,MRG,TRG,TRN,ARM,ARZ,CSM,CSV,opt,par}’ (17 files)

innobackupex: Backing up file ‘/home/mysql/3306/data/test/t.frm’

……

110701 03:29:53  innobackupex: Finished backing up .frm, .MRG, .MYD, .MYI, .TRG, .TRN, .ARM, .ARZ, .CSV, .CSM and .opt files

innobackupex: Resuming ibbackup

xtrabackup: The latest check point (for incremental): ’2371742526′

>> log scanned up to (2371742526)

xtrabackup: Transaction log of lsn (2371741708) to (2371742526) was copied.

110701 03:29:55  innobackupex: All tables unlocked

110701 03:29:55  innobackupex: Connection to database server closed

innobackupex: Backup created in directory ‘/home/mysql/backup/data/3306′

innobackupex: MySQL binlog position: filename ‘bin.000014′, position 309836330 mysql,information_schema,performance_schema

innobackupex: MySQL slave binlog position: master host ”, filename ”, position

innobackupex: You must use -i (–ignore-zeros) option for extraction of the tar stream.

110701 03:29:55  innobackupex: completed OK!

网站性能调试案例一则

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

怎么看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