linux上利用rsync来实现跨服务器间的数据同步和备份

2条评论

2011 年 12 月 27 日 at 下午 1:59分类:Linux | 其他技术

今天要用到这个rsync是因为在工作中要用到数据跨服务器调用的问题,我这边的情况是这样子的:我的服务器两台(A,B),原先的的数据都是放在A上面的,之前在A上面安装了SVN的服务器,那么这样子,我们把数据都已版本控制的形式放在svn的版本控制中,那么在windows中获取这些数据就比较简单了,但是现在我们新增了B服务器,想把我们的项目app放到B这个web服务器上面,但是又不装svn服务器,所以我一开始的做法是在B服务器上面安装了svn的客户端,然后去checkout A 服务器上面的svn版本,但是我发现这样子操作,恨死,因为我们开发的人很多,不知道有谁在什么时候提交了,而且都还要人为的去操作,狠不方便。我现在只想实现的是,当我在windows向A的svn服务器提交数据的时候,A服务器会自动的向B服务器同步数据。我知道在svn的办版本库中有个hooks文件夹,这个文件夹里面可以写一些钩子文件。但是我发现在这个里面执行的svn update不能操作到另外一台机器,也就是说只能操作本机器(A)的。不知道用svn来实现这个哪位高人会,求指点?(这里面可以有一种方法:那就是通过脚本去请求:我在钩子里面写一个shell脚本去请求B服务器的一个php文件,然后通过这个文件去执行shell命令。最好是不要用类似于定时执行的例行性任务这种)
后来通过网上找了下,说是可以通过rsync来实现这个功能,经过测试,确实是可以的,把过程记录下:
这里讲实现两个过程:1.让服务器A自动把数据通过不到服务器B(我需要的),2.从服务器B自动从A服务器更新
1:讲服务器A 自动把数据上传到B服务器
首先不管哪台机器,首先要确保rsync这个有安装,可以敲命令:# rsync –version来查看情况,如果没有安装,请自行安装吧
如果安装了,那么就进行如下操作:
a:配置rsyncd.conf
/etc/rsyncd.conf文件的内容(实际上,当我以A向服务器B同步数据时,A是一个客户端,不需要这个配置文件,只需要一个rsyncd.secrets的密码文件就可以了,如果当我是一B座服务器向A服务器更新数据时就需要这个配置文件了)

    uid = nobody
    gid = nobody
    max connections = 5
    read only = true
    #hosts allow = 202.207.177.180
    hosts allow = *
    transfer logging = true
    log format = %h %o %f %l %b
    log file = /var/log/rsyncd.log
    slp refresh = 300
    log file = /var/log/rsyncd.log
    pid file = /var/run/rsyncd.pid
    lock file = /var/run/rsyncd.lock

    [dkcore]
    path = /home/web/www
    comment = DKCORE FIRST SERVER
    read only = false
    list = false
    auth users = fbbin
    setrcts file = /etc/rsyncd.secrets   #存放账号密码文件

b:配置密码文件/etc/rsyncd.secrets

binbin  #这是需要验证的密码

c:配置欢迎页面文件/etc/rsyncd.motd(实际上当A直接上传时,这个文件时不需要的,只有当A被当做服务器来请求连接时,才会用到)

Welcome to use the rsync services!

d:配置要上传的目标服务器的的/etc/rsyncd.conf文件
/etc/rsyncd.conf文件的内容

    uid = nobody
    gid = nobody
    max connections = 5
    read only = false
    hosts allow = *
    transfer logging = true
    log format = %h %o %f %l %b
    log file = /var/log/rsyncd.log
    slp refresh = 300
    log file = /var/log/rsyncd.log
    pid file = /var/run/rsyncd.pid
    lock file = /var/run/rsyncd.lock

    [dkcore]
    path = /home/web/www
    comment = DKCORE FIRST SERVER
    read only = false
    list = false
    auth users = fbbin
    setrcts file = /etc/rsyncd.secrets   #存放账号密码文件

e:配置密码文件/etc/rsyncd.secrets

fbbin:binbin  #这是需要验证的用户名和密码密码需要在A服务器上面的rsyncd.secrets文件中写好。

f:配置欢迎页面文件/etc/rsyncd.motd

Welcome to use the dkcore

然后我们在B服务器上面启动rsync(我们是要把数据让A服务器自动上传到B服务器)

# rsync --daemon --config=/etc/rsyncd.conf

这个时候我们在A机器中运行一下命令就可以把A机器中/home/dkcore同步到B机器中的dkcore模块也就是/home/web/www中了

# rsync -avzuCP --delete --password-file=/etc/rsyncd.secrets /home/dkcore fbbin@192.168.12.253::dkcore

上面的/home/dkcore是要同步的本地(A)数据地址,后面的IP是同步到的目标ip和用户名以及模块,这些都是在配置文件中定义的!
那么我现在只需要把这个命令写在post-commit文件中就可以了!

======================================================================================
2:让服务器B自己去A上面更新文件
这个时候A就是服务器了,那么只需要把上面B那个配置拿来用可以了,把里面的path改成我要同步的本机地址:/home/dkcore,然后把密码文件改成和上面的B一样的就行了,欢迎文件可要可不要,然后就是B上面的,这个时候B是客户端,配置文件其实可以不用要,只需要个密码文件。此时B不需要启动服务器,但是A就需要启动服务了,命令:

# rsync --daemon --config=/etc/rsyncd.conf

OK,这时我们在B机器上面用如下命令就可以把A上面的指定目录更新到B的机器上面了。

# rsync -vrztopg --delete --password-file=/etc/rsyncd.secrets rsync://fbbin@192.168.12.183/dkcore /home/wwwroot/dkcore

上面的/home/wwwroot/dkcore是把数据更新到的本地物理地址,rsync://fbbin@192.168.12.183/dkcore是指183这台机器(A)上面的dkcore模块更新到本地的/home/wwwroot/dkcore目录中,其中的fbbin是在A机器中的conf文件中配置的。
要是想执行备份的话,那么就可以添加个crontab任务来执行这个命令就OK了!
搞定。。。
参考资料:
1.http://blog.lixiphp.com/solve-rsync-auth-failed-on-module/
2.http://apps.hi.baidu.com/share/detail/16876083

linux下安装svn客户端

6条评论

2011 年 12 月 24 日 at 下午 2:31分类:Linux | WEB开发

网上找了下都是讲如何安装svn server的,我只需要一个支持http协议的客户端哈,不想装apache。安装所需软件有:apr,apr-util,sqlite,neon,subversion
1:下载相关软件:

# cd /usr/local/src
# wget http://labs.renren.com/apache-mirror/apr/apr-1.4.5.tar.gz
# wget http://labs.renren.com/apache-mirror/apr/apr-util-1.4.1.tar.gz
# wget http://www.sqlite.org/sqlite-amalgamation-3.6.16.tar.gz
# wget  http://www.webdav.org/neon/neon-0.28.4.tar.gz
# wget http://subversion.tigris.org/downloads/subversion-1.6.3.tar.bz2

2:安装apr:

# tar zxvf apr-1.4.5.tar.gz
# cd apr-1.4.5
# ./configure --prefix=/usr/local/apr
# make
# make install
# echo /usr/local/apr/lib >> /etc/ld.so.conf       #添加lib目录

3:安装apr-util:

# tar zxvf apr-util-1.4.1.tar.gz
# cd apr-util-.1.4.1
# ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr/
# make
# make install
# echo /usr/local/apr-util/lib >> /etc/ld.so.conf
# ldconfig -v

4:安装sqlite:

# tar zxvf sqlite-amalgamation-3.6.16.tar.gz
# cd sqlite-3.6.16/
# configure --prefix=/usr/local/sqlite
# make
# make install

5:安装neon(不需要支持http协议可以略掉安装)

# tar zxvf neon-0.28.4.tar.gz
# cd neon-0.28.4
# ./configure --prefix=/usr/local/neon --enable-shared
# make
# make install

6:安装subversion:

# tar -jxvf subversion-1.6.3.tar.bz2
# cd subversion-1.6.3
# ./configure --prefix=/usr/local/svn --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util --with-sqlite=/usr/local/sqlite --with-neon=/usr/local/neon
# make
# make install

如果中途没有出问题的话,那么就安装OK了
打开/etc/profile文件把/usr/local/svn/bin/svn目录添加到$PATH中,然后使用命令 # source /etc/profile 重新加载下

测试下svn:查看下版本信息

# svn --version
    /usr/local/svn/bin/svn --version
    svn,版本 1.6.3 (r38063)
       编译于 Jul 30 2009,14:31:41
     
    版权所有 (C) 2000-2009 CollabNet。
    Subversion 是开放源代码软件,请参阅 http://subversion.tigris.org/ 站点。
    此产品包含由 CollabNet(http://www.Collab.Net/) 开发的软件。
     
    可使用以下的版本库访问模块:
     
    * ra_neon : 通过 WebDAV 协议使用 neon 访问版本库的模块。
      - 处理“http”方案
    * ra_svn : 使用 svn 网络协议访问版本库的模块。  - 使用 Cyrus SASL 认证
      - 处理“svn”方案
    * ra_local : 访问本地磁盘的版本库模块。
      - 处理“file”方案

OK 搞定了!

MongoDB和php的mongo拓展的安装

一条评论

2011 年 12 月 19 日 at 下午 8:38分类:Cache | NoSQL | PHP

MongoDB的安装:

# wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.0.2.tgz
# tar zxvf mongodb-linux-x86_64-2.0.2.tgz
# mv mongodb-linux-x86_64-2.0.2 /usr/local/mongodb
# cd /data
# mkdir mongodb
# cd /usr/local/mongodb/
# touch logs

安装完毕!这个安装是史上最简单的了
启动MongoDB

# ./bin/mongod --dbpath=/data/mongodb/ --logpath=/usr/local/mongodb/logs.log --logappend  --port=27017 --fork --source=127.0.0.1

如果要是设置开机启动那么就把这个命令写到/etc/rc.local里面去吧!
另外这里启动有一个地方要说下,那就是关于验证的问题,这个不像Mysql任何时候都需要用户名和密码才能进入数据库,这个如果你在启动的时候,没有说明要验证的情况下,那么操作MongoDB是不需要验证的,但是如果在启动的时候设置了需要验证的话,那么在操作MongoDB就需要输入用户名和密码了。
如果需要用户的验证的那么就是在启动的命令中加入 –auth 参数,不需要的话就不用加,或者是加 –noauth
如果说开启了验证的,那么在操作MongoDB的时候,需要输入用户名密码,那么这个用户密码从哪里来呢?我一开始也纠结了一会,后来明白了,这个跟mysql数据库还是差不多的,存在一个用户表admin,这个表里面的用户就是MongoDB的管理用户列表,和mysql相似,同时,在操作具体的某个数据库的时候,还需要你去认证,而这个用户是针对当前这个数据库的,如果你先use admin,然后db.auth(‘name’,'passwd’)验证通过了,那么其他数据库就不用验证了,如果你你走这步,那么你就需要针对不同数据库,输入不同的用户名密码进行验证了,这点和mysql是一样的!
举个例子:我在MongoDB中有个数据库TEST,如果开启了验证模式,那么我想进入这个数据库进行操作,那么就需要进行db.auth(‘name’,'passwd’)进行验证,同时我可以先通过admin表的验证,那么也就通过其他所有表的验证了!增加用户的话,要先use dbname,然后在使用db.addUser(‘name’,'passwd’)就可以了。

简单的参数说明:
–logpath 日志文件路径
–master 指定为主机器
–slave 指定为从机器
–source 指定主机器的IP地址
–pologSize 指定日志文件大小不超过64M.因为resync是非常操作量大且耗时,最好通过设置一个足够大的oplogSize来避免resync(默认的 oplog大小是空闲磁盘大小的5%)。
–logappend 日志文件末尾添加
–port 启用端口号
–fork 在后台运行
–only 指定只复制哪一个数据库
–slavedelay 指从复制检测的时间间隔
–auth 是否需要验证权限登录(用户名和密码)
–noauth 不需要验证权限登录(用户名和密码)

PHP的MongoDB拓展的安装:

# wget http://pecl.php.net/get/mongo-1.2.6.tgz
# tar zxvf mongo-1.2.6.tgz
# cd mongo-1.2.6
# /usr/local/php/bin/phpize
# ./configure --enable-mongo=share --with-php-config=/usr/local/php/bin/php-config
# make && make installl 

将生成的拓展mongo.so文件添加到php.ini中,重启php-fpm,然后查看下phpinfo()

安装成功!之后就是测试了。。。

相关资料:
1:http://www.cnblogs.com/zengen/archive/2011/04/23/2025722.html
2:http://blog.csdn.net/liuyuanshijie/article/details/6735621

redis缓存的安装和使用

没有评论

2011 年 12 月 17 日 at 下午 3:03分类:Cache | Linux | NoSQL | PHP | WEB开发

首先说下什么事redis,之前在我的博客中降到很多的缓存诸如:memcache,memcached,memcachedb,dbcached等,今天讲的这个redis跟这几个差不多,redis是一个key-value存储系统。 和Memcached(这里指服务,客户端有memcache和memcached两种)类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集 合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis 支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改 操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
如果说把memcache,memcached,memcachedb,dbcached以及redis进行分类的话,我会把memcache和memcached分为一类,其实后者是前者的升级版,而memcachedb,dbcached和redis可以分为一类,因为他们在记住内存的同时会把数据写到磁盘文件中,达到持久性存储的目的,个人认为可以把这三者归结为Nosql的范畴。

在linux上面安装redis

# wget http://redis.googlecode.com/files/redis-2.4.4.tar.gz
# tar zxvf redis-2.4.4.tar.gz
# mv redis-2.4.4 /usr/local/redis
# cd /usr/local/redis
# make && make install

安装完成后,会自动copy可执行文件到环境变量中,不要自己去copy了。

# redis-
redis-benchmark   redis-check-dump  redis-server  
redis-check-aof   redis-cli

之后就是配置redis了,配置文件位于/usr/local/redis下面的redis.conf
配置项根据个人而定,一下是我的配置项:

#是否以后台进程运行,默认为no
daemonize yes
#如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid
pidfile /var/run/redis/redis.pid
#监听端口,默认为6379
port 6379
#绑定主机IP,默认值为127.0.0.1(注释)
bind 127.0.0.1
#超时时间,默认为300(秒)
timeout 300
#日志记录等级,有4个可选值,debug,verbose(默认值),notice,warning
loglevel verbose
#日志记录方式,默认值为stdout
logfile stdout
#可用数据库数,默认值为16,默认数据库为0
databases 16
#指出在多长时间内,有多少次更新操作,就将数据同步到数据文件。这个可以多个条件配合,比如默认配置文件中的设置,就设置了三个条件。
#900秒(15分钟)内至少有1个key被改变
save 900 1
#300秒(5分钟)内至少有10个key被改变
save 300 10
#存储至本地数据库时是否压缩数据,默认为yes
rdbcompression yes
#本地数据库文件名,默认值为dump.rdb
dbfilename redis.rdb
#本地数据库存放路径,默认值为 ./
dir /data/redis/
#当本机为从服务时,设置主服务的IP及端口(注释)
slaveof <masterip> <masterport>
#当本机为从服务时,设置主服务的连接密码(注释)
masterauth <master-password>
#连接密码(注释)
requirepass foobared
#最大客户端连接数,默认不限制(注释)
maxclients 128
#设置最大内存,达到最大内存设置后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,任到达最大内存设置,将无法再进行写入操作。(注释)
maxmemory <bytes>
#是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上#面#save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认值为no
appendonly yes
#更新日志文件名,默认值为appendonly.aof(注释)
appendfilename /root/redis_db/appendonly.aof
#更新日志条件,共有3个可选值。no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次(默认值)。
appendfsync everysec
#是否使用虚拟内存,默认值为no
vm-enabled yes
#虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
#将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的 (Redis的索引数据就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0。
vm-max-memory 0
#虚拟内存文件以块存储,每块32bytes
vm-page-size 32
2#虚拟内在文件的最大数
vm-pages 134217728
#可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.
vm-max-threads 4
#把小的输出缓存放在一起,以便能够在一个TCP packet中为客户端发送多个响应,具体原理和真实效果我不是很清楚。所以根据注释,你不是很确定的时候就设置成yes
glueoutputbuf yes
#在redis 2.0中引入了hash数据结构。当hash中包含超过指定元素个数并且最大的元素没有超过临界时,hash将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值
hash-max-zipmap-entries 64
#hash中一个元素的最大值
hash-max-zipmap-value 512
#开启之后,redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使 用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置 为yes,以便能够尽可能快的释放内存
activerehashing yes

配置好了之后来启动redis

# redis-server /usr/local/redis/redis.conf
# netstat -anpt | grep 6379   //查看进程是否启动

现在你可以用telnet来链接并进行数据的存储测试。
我们现在来安装php的redis拓展

# wget https://github.com/owlient/phpredis/tarball/master
# mv owlient-phpredis-2.1.1-1-g90ecd17.tar.gz phpredis-2.1.1-1.tar.gz
# tar zxvf phpredis-2.1.1-1.tar.gz
# mc owlient-phpredis-2.1.1-1-g90ecd17 phpredis-2.1.1
# cd  phpredis-2.1.1
# /usr/locla/php/bin/phpize
# ./configure --enable-redis=share --with-php-config=/usr/local/php/bin/php-config
# make && make install

之后在php的拓展目录下面会有redis.so文件生成,然后把这个加到php.ini中就行了,记得重启php-fpm哦。
PS:我们在安装php的拓展的时候,记得一定要加上–enable-theNameOfTheExtensions=share还有后面的php-config文件路径。虽然有些时候不会报错,但是尽量还是加上好,我曾经就因为装一个facedetect.so这个拓展,没有加那两句话,结果php一直不能认识这个拓展,然后phpinfo里面也没有这个拓展,害了我花了很长时间。
php的测试代码

<?php  
$redis = new Redis();  
$redis->connect('127.0.0.1', 6379);  
$redis->set('fbbin',serialize(array('中华人民共和国','美利坚合众国')));  
print_r(unserialize($redis->get("fbbin"))); 

能够正确输出信息,搞定。
上面的代码之所以要序列化,好像redis不支持Array类型的数据,我一开始并没有序列化,然后得出的结果就是“Array”,而不是我要的数组数据,所以我把它序列化了。到底是不支持还是我哪里的配置问题,还有待考究啊,希望哪位牛人能give me a hand。

一款不错的网站压力测试工具webbench

2条评论

2011 年 12 月 16 日 at 下午 5:49分类:WEB开发

原文地址:http://blog.s135.com/post/288/;

webbench最多可以模拟3万个并发连接去测试网站的负载能力,个人感觉要比Apache自带的ab压力测试工具好,安装使用也特别方便。
1、适用系统:Linux

2、编译安装:(需要先安装ctags)

# wget http://blog.s135.com/soft/linux/webbench/webbench-1.5.tar.gz
# tar zxvf webbench-1.5.tar.gz
# cd webbench-1.5
# make &amp;amp;&amp;amp; make install

3、使用:

# cp webbench /bin/webbench
# webbench -c 500 -t 30 http://127.0.0.1/

参数说明:-c表示并发数,-t表示时间(秒)
4、测试结果示例:

Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://127.0.0.1/test.jpg
500 clients, running 30 sec.

Speed=415536 pages/min, 2236968bytes/sec.
Requests: 207758 susceed, 0 failed.

memcached运行和内存使用情况的监控

2条评论

2011 年 12 月 16 日 at 下午 3:08分类:Cache | PHP | WEB开发

之前都是在讲关于memcache的一些操作啊和配置等得问题,但是我们安装好服务之后,并且服务端和客户端都能够正常工作了,那么这个时候我们怎么知道内存的使用的情况呢,以及memcache是否正常运行呢?
大概很多人都不知道在memcache的源码包里面有个文件叫做:memcache.php这个功能就为我们的监控做了完整的事情,我们只需要 借助它,那么一切就OK了。
只需要把这个文件copy到你的web根目录,然后修改一下代码:

define('ADMIN_USERNAME','fbbin');     // Admin Username
define('ADMIN_PASSWORD','binbin');      // Admin Password

$MEMCACHE_SERVERS[] = '127.0.0.1:11211'; // add more as an array

然后我们通过URL访问这个文件,如果正常的话,会得出如下界面,如果不正常的话,请自行找原因。

上图中的东西,我相信大家都能看懂
Hits: 1330 (90.9%) #点击了29
Misses: 133 (9.1%) #尚未使用的133
此外它还能对指定的key值进行管理。如下图

从上面可以看出,可以对KEY的值进行删除操作。

OK!

web做集群时,利用memcache来同步session

一条评论

2011 年 12 月 16 日 at 上午 11:09分类:Cache | PHP | WEB开发

记得以前做集群服务时,同步session的问题有些时候是借助COOKIE来使用的,有些时候是通过共享文件的方式来做,最多的还是通过memcache来做,不过之前做得跟以下讲得很不一样,以前的做法是完全不用PHP自带的session,而是借助memcache的缓存原理来实现一个memcache,这样子做当然是可以的!但是今天讲得是一种更简单的额方法。我个人觉得用memcache来同步session是最好的。
首先我模拟了一个web集群的,我启动两个memcached进程:

# /usr/local/bin/memcached -d -p 11211 -u root -m 64 -c 1024 -P /var/run/memcached/memcached1.pid
# /usr/local/bin/memcached -d -p 11212 -u root -m 64 -c 1024 -P /var/run/memcached/memcached2.pid

这样子服务算是简单的启动了,下面配置下php.ini这个文件

session.save_handler = "memcache"
memcache.hash_strategy = "consistent"  #这个memcache在php.ini中配置项,还有很多,可以根据phpinfo里面来看它的配置项
session.save_path = "tcp://127.0.0.1:11211?weight=1,tcp://127.0.0.1:11212?weight=2"

简单的说明:
第一行,session的储存方式是memcache;第二行,memcache的hash算法是consistent;第三行,session储存的位置;
然后重启php-fpm

#  /etc/rc.d/init.d/php-fpm stop
#  /etc/rc.d/init.d/php-fpm start

此时你查看phpinfo页面的关于session的信息如下:
Session Support enabled
Registered save handlers files user sqlite memcache
Registered serializer handlers php php_binary
紧接着下面是
session.save_path tcp://127.0.0.1:11211?weight=1,tcp://127.0.0.1:11212?weight=2 tcp://127.0.0.1:11211?weight=1,tcp://127.0.0.1:11212?weight=2

测试下成不成功:

<?php
session_start();
$_SESSION['sessionTestData'] = 'fbbin code!';
echo session_id();
?>

通过memcache来获取刚刚存得数据

<?php  
$mem = new Memcache;  
$mem->addServer("127.0.0.1",11212)or die ("Could not add server 11212");  
$mem->addServer("127.0.0.1",11211)or die ("Could not add server 11211");  
$val = $mem->get('ind40dbsjobtq2k8lnfmv5eps5');    //上面输出的session_id()
echo $val;  
?> 

得出的结果是:

sessionTestData|s:12:"fbbin code!";

测试成功!关于这个时间的有效性跟session和memcache有关系。

基于libmemcached,php扩展memcached的安装

一条评论

2011 年 12 月 15 日 at 下午 8:09分类:Cache | Linux | PHP | WEB开发

1:为什么要装memcached扩展
memcached的1.2.4及以上增加了CAS(Check and Set)协议,对于同一key的多进程的并发处理问题。这种情况其实根数据库很像,如果同时有几个进程对同一个表的同一数据进行更新的话,那会不会打架呢,哈哈。数据库里面可以锁定整张表,也可以锁定表里面一 行的功能,其实memcached加入的CAS根这个差不多。php的扩展memcache,不支持cas,而它的升级版memcached支持,所以我们要装memcached扩展,memcached扩展是基于libmemcached,所以要先安装libmemcached,php的memcache拓展的服务端是memcached这个服务,既然是它的升级版,那么php的memcached拓展的服务端同样也是memcached,只不过php的memcached拓展还需要在服务端安装libmemcached这个依赖条件,因此我们要先安装这个libmemcached。
2:安装libmemcached

#  wget http://launchpad.net/libmemcached/1.0/1.0.2/+download/libmemcached-1.0.2.tar.gz
#  tar zxvf libmemcached-1.0.2.tar.gz
#  cd  libmemcached-1.0.2
# ./configure --prefix=/usr/local/libmemcached --with-memcached
# make && make install

安装注意问题:
1: 安装过程中不要忘了,–with-memcached,不然会提示你
checking for memcached… no
configure: error: “could not find memcached binary”
2:你的memcached是不是1.2.4以上的,如果不是会提示你
collect2: ld returned 1 exit status
make[2]: *** [clients/memslap] Error 1
make[2]: Leaving directory `/home/zhangy/libmemcached-0.42′
解决办法是–disable-64bit CFLAGS=”-O3 -march=i686″,如果不用这个64位的long型数据,我想php扩展memcached,memcache也就没什么区别了,memcached也就没什么意思了。
3:安装PHP的memcached拓展

# wget http://pecl.php.net/get/memcached
# cd memcached-2.0.0b2
# /usr/lcoal/php/bin/phpize
#  ./configure --enable-memcached --with-php-config=/usr/local/php/bin/php-config --with-libmemcached-dir=/usr/local/libmemcached --with-memcached --with-zlib-dir=/usr/local/zlib --enable-memcached-igbinary
#  make && make install

上面的编译时的 “–with-zlib-dir=/usr/local/zlib”这句话, 如果说你要在缓存数据时用到压缩等函数的话,那么就需要安装zlib这个包,如果不需要压缩的话那就不需要安装了。安装方法如下

# wget http://download.chinaunix.net/down.php?id=24014&amp;amp;ResourceID=12241&amp;amp;site=1
# tar -xvzf zlib-1.2.3.tar.gz
# cd zlib-1.2.3.tar.gz
# ./configure  --prefix=/usr/local/zlib
# make && make install

上面编译时的–enable-memcached-igbinary这句话,igbinary是一个序列化工具,它将php的数据结构存储为紧密的二进制形式,在时间和空间上都有所改进,可以提高性能。要想带上这个参数那么就需要安装它 ,如下操作

# http://pecl.php.net/get/igbinary-1.1.1.tgz
# cd igbinary-1.1.1
# /usr/lcoal/php/bin/phpize
# ./configure
# make && make install

完成之后也是一个php的拓展,需要添加到php.ini中去,那么上面安装时携带的参数才有效!

上面的拓展安装好之后会
出现需要的memcached.so文件的地址,然后把这个拓展添加早php.ini中,然后重启下php-fpm就可以了!
测试的代码:

<?php
$m = new Memcached();
$m->addServer('localhost', 11211);
$items = array(
    'key1' => 'value1',
    'key2' => 'value2',
    'key3' => 'value3'
);
$m->setMulti($items);
$m->getDelayed(array('key1', 'key3'), true, 'result_cb');
function result_cb($memc, $item)
{
    var_dump($item);
}
echo "<hr />";
//$m->set('fbbin',date('Y-M-D H:I:S'));
echo $m->get('fbbin'); //return : 2011-Dec-Tue 06:0:th

数据库池连接代理服务器SQL Relay的安装配置使用

没有评论

2011 年 12 月 13 日 at 下午 7:50分类:Linux | MySQL | PHP | WEB开发

具体的SQL Relay是什么,我相信网上有很多很多的文章,你可以去百度或者是谷歌上面找。
不说废话了,开始安装SQL Relay,到 http://sqlrelay.sourceforge.net/ 去下载相应的软件
安装SQL Relay需要先安装Rudiments,至于为什么,因为这是它以来的gcc编译包。 在 http://sqlrelay.sourceforge.net/download.html 可以找到。
1、安装Rudiments:

# tar vxzf rudiments-0.34.tar.gz
# cd rudiments-0.34
# ./configure --prefix=/usr/local/rudiments
# make
# make install

至此,rudiments安装结束
2、安装SQL Relay:

# tar vxzf sqlrelay-0.43.tar.gz
# cd sqlrelay-0.43
# ./configure --prefix=/usr/local/sqlrelay --with-rudiments-prefix=/usr/local/rudiments --with-mysql-prefix=MySQL安装路径 --with-php-prefix=PHP安装路径
# make
# make install

安装结束,以上编译参数根据个人需要来设定,我用的PHP+MYSQL的环境,所以只需要这两个东西!
安装结束之后会再PHP的extension_dir目录生成一个sql_relay.so文件,这个就是编译生成的SQL Relay的php拓展,我们需要把它加到php.ini文件中。
然后重启php-fpm,查看下phpinfo,看下是否加载了sql_relay拓展!
安装结束之后,就是如何使用了,在我们安装的/usr/local/sqlrelay/share/doc目录下面,是关于sql_relay的PHP的API以及配置参数,全部都在这个目录下面,只不过全是英文的,看起来很吃力啊。
下面是我根据提供的API封装的一个CLASS,

<?php
/**
 *@desc 数据访问层的操作
 *@author fbbin
 */
define('DB_USER',               'fbbin' );
define('DB_PASSWD',             'binbin' );
define('DB_ID',                 'router' );
define('DB_PORT',               9090 );
define('DB_SOCKET',             '/tmp/router.socket' );
define('DB_RETRYTIME',          0 );
define('DB_TRIES',              1 );
define('DB_BUFFER_SIZE',        0 );
define('DB_TIMEOUT',            10 );
define('DB_TRANS_AUTO_COMMIT',  FALSE );
define('DB_OK',                 TRUE );
define('DB_FAIL',               FALSE );
define('DB_DEBUG',              true );
define('DB_CHARSET',            'utf8' );
define('DB_SAVEQUERY',          TRUE );
define('DB_CACHE',              false );
define('DB_CACHE_TTL',          600 );
define('DB_CACHE_PATH',         './' );

class SqlrelayAction
{
	var $connection = '';
	var $cursor = '';
	var $affectedrows = '';
	var $errorNum = -1;
	var $queries = array();
	var $queryCount = 0;
	var $transStatus = true;
	
	/**
	 * construct
	 */
	public function __construct()
	{
		if( !$this->isSupport() )
		{
			$this->errorNum = 4;
			if( DB_DEBUG )
			{
				exit($this->getError());
			}
			return DB_FAIL;
		}
		sqlrcon_setTimeout( DB_TIMEOUT );
		if( !is_resource($this->connection) || !is_object($this->connection) )
		{
			$this->_dbConnect();
		}
		return $this->_init();
	}
	
	/**
	 * @desc 应用初始化,检测数据库是否启动等
	 * @access protected
     * @return void
	 */
	protected function _init()
	{
		$_init = sqlrcon_ping( $this->connection );
		if ( !$_init )
		{
			$this->errorNum = 0;
			if( DB_DEBUG )
			{
				exit($this->getError());
			}
			return DB_FAIL;
		}
		$this->checkTrans();
		$this->_initCursor();
		$this->setCharset();
	}
	
	/**
	 * @desc 创建数据池链接
	 * @access protected
     * @return bool
	 */
	protected function _dbConnect()
	{
		$this->connection = sqlrcon_alloc(DB_ID, DB_PORT, DB_SOCKET, DB_USER, DB_PASSWD, DB_RETRYTIME, DB_TRIES);
		return true;
	}
	
	/**
	 * @desc 获取操作句柄
	 * @access protected
     * @return bool
	 */
	protected function _initCursor()
	{
		$this->cursor = sqlrcur_alloc( $this->connection );
		return true;
	}
	
	/**
	 * @desc 预查询准备
	 * @access protected
     * @return void
	 */
	protected function _preQuery()
	{
		if( DB_CACHE )
		{
			$this->cacheOn( $query );
		}
		//将结果集存入缓冲区
		sqlrcur_setResultSetBufferSize($this->cursor, DB_BUFFER_SIZE);
	}
	
	/**
	 * @desc 执行一条SQL语句
	 * @param  $query 查询的SQL
	 * @access public
     * @return void
	 */
	public function query( $query = '' )
	{
		if( empty($query) )
		{
			$this->errorNum = 1;
			return DB_FAIL;
		}
		$isSelect = $this->isSelect($query);
		if( $isSelect && DB_CACHE )
		{
			$tmp = sqlrcur_openCachedResultSet($this->cursor, MD5($query));
			if( $tmp )
			{
				return $this->fetchAssoc();
			}
		}
		//
		$this->_preQuery( $query );
		
		if( DB_SAVEQUERY )
		{
			$this->queries[] = $query;
		}
		$this->queryCount++ ;
		
		if ( !sqlrcur_sendQuery($this->cursor, $query) ) {
			$this->affectedrows = 0;
			if( DB_DEBUG )
			{
				echo sqlrcur_errorMessage( $this->cursor );
				sqlrcur_free( $this->cursor );exit;
			}
			$this->transStatus = false;
			return DB_FAIL;
		}
		$this->affectedrows = $isSelect ? 0 : sqlrcur_affectedRows( $this->cursor );
		if( DB_CACHE )
		{
			$this->cacheOff();
		}
		if ( $isSelect ) {
			return $this->fetchAssoc();
		}
		return $this->affectedrows;
	}
	
	/**
	 * @desc 遍历查询的结果集
	 * @access protected
     * @return array
	 */
	protected function fetchAssoc()
	{
		$tmp = array();
		for($row = 0, $count = sqlrcur_rowCount($this->cursor); $row < $count; $row++)
		{
			$tmp[] = sqlrcur_getRowAssoc($this->cursor, $row);
		}
		
		if( sqlrcon_getDebug($this->connection) )
		{
			sqlrcon_debugOff( $this->connection );
		}
		sqlrcur_free( $this->cursor );
		
		return $tmp;
	}
	
	/**
	 * @desc 启动事务检测
	 * @access protected
     * @return bool
	 */
	protected function checkTrans()
	{
		if ( DB_TRANS_AUTO_COMMIT ) 
		{
            sqlrcon_autoCommitOn( $this->connection );
        } 
        else 
        {
            //sqlrcon_autoCommitOff( $this->connection );
        }
        return DB_OK;
	}
	
	/**
	 * @desc 启动事务
	 * @access public
     * @return bool
	 */
	public function transStart()
	{
		$this->transStatus = true;
		return sqlrcon_autoCommitOff( $this->connection );;
	}
	
	/**
	 * @desc 提交事务
	 * @access public
     * @return bool
	 */
	public function transCommit()
	{
		if( sqlrcon_commit($this->connection) == 1 )
		{
			return DB_OK;
		}
		else
		{
			$this->errorNum = 2;
			return DB_FAIL;
		}
	}
	
	/**
	 * @desc 获取操作的事务状态
	 * @access public
     * @return bool
	 */
	public function transStatus()
	{
		return $this->transStatus;
	}
	
	/**
	 * @desc 事务回滚
	 * @access public
     * @return bool
	 */
	public function transRollback()
	{
		if( sqlrcon_rollback($this->connection) == 1 )
		{
			return DB_OK;
		}
		else 
		{
			$this->errorNum = 3;
			return DB_FAIL;
		}
	}
	
	/**
	 * @desc 开启SQL Relay缓冲
	 * @param  $query 查询的SQL
	 * @access protected
     * @return bool
	 */
	protected function cacheOn($query = '')
	{
		sqlrcur_cacheToFile($this->cursor, DB_CACHE_PATH.MD5($query));

		sqlrcur_setCacheTtl($this->cursor, DB_CACHE_TTL);
		
		return true;
	}
	
	/**
	 * @desc 关闭SQL Relay缓冲
	 * @access protected
     * @return void
	 */
	protected function cacheOff()
	{
		sqlrcur_cacheOff( $this->cursor );
	}
	
	/**
	 * @desc 设置查询编码
	 * @access protected
     * @return void
	 */
	protected function setCharset()
	{
		return sqlrcur_sendQuery($this->cursor, "set names " . DB_CHARSET);
	}
	
	/**
	 * @desc 检测系统是否支持SQL Relay
	 * @access protected
     * @return bool
	 */
	protected function isSupport()
	{
		if( extension_loaded('sql_relay') && function_exists( 'sqlrcon_alloc' ) )
		{
			return true;
		}
		else 
		{
			return false;
		}
	}
	
	/**
	 * @desc 挂起结果集,挂起会话
	 * @access public
     * @return resoure
	 */
	public function suspendResult()
	{
		sqlrcur_suspendResultSet($this->cursor);
		sqlrcon_suspendSession($this->connection);
		$return['ResultSetId'] = sqlrcur_getResultSetId($this->cursor);
		$return['PortId']      = sqlrcon_getConnectionPort($this->connection);
		$return['SocketId']    = sqlrcon_getConnectionSocket($this->connection);
        return $return;
	}
	
	/**
	 * @desc 恢复结果集,恢复会话
	 * @access public
     * @return bool
	 */
	public function resumeResult( $params )
	{
		if( !$this->connection )
		{
			$this->_dbConnect();
		}
		if( !$this->cursor )
		{
			$this->_initCursor();
		}
		//恢复会话
		sqlrcon_resumeSession($this->connection, $params['PortId'], $params['SocketId'] );
		return sqlrcur_resumeResultSet($this->connection, $params['ResultSetId']);
	}
	
	/**
	 * @desc 检测一条SQL是否是SELECT语句,并发送字段信息与否
	 * @access protected
     * @return bool
	 */
	protected function isSelect( $query = '' )
	{
		$is = ( preg_match('/^\s*SHOW TABLES\s*/si', $query) || preg_match('/^\s*\(?\s*SELECT\s+/si', $query) ) &&
                    !preg_match('/^\s*\(?\s*SELECT\s+INTO\s+/si', $query);
		//告诉服务器是否发送字段信息
		if( $is )
		{
			sqlrcur_getColumnInfo( $this->cursor );
		}
		else 
		{
			sqlrcur_dontGetColumnInfo( $this->cursor );
		}
		return $is;
	}
	
	/**
	 * @desc 获取SQL操作影响的行数
	 * @access public
     * @return intval
	 */
	public function affectedRows()
	{
		return $this->affectedrows;
	}
	
	/**
	 * @desc 结果集的列数
	 * @access public
     * @return intval
	 */
	public function colNums()
    {
        return sqlrcur_colCount($this->cursor);
    }
	
    /**
     * @desc 返回最后一条SQL
     * @access public
     * @return string
     */
    public function lastQuery()
    {
    	return end($this->queries);
    }
    
    /**
     * @desc 获取debug调试信息
     * @access public
     * @return string
     */
    public function showDebugInfo()
    {
    	return sqlrcon_debugOn($this->connection);
    }
    
    /**
     * @desc 返回数据操作的相关版本信息
     * @access public
     * @return array
     */
    public function version()
    {
    	$dbtype = sqlrcon_identify( $this->connection );
    	return array($dbtype.' version'=>sqlrcon_dbVersion( $this->connection ),
    				'SQL Relay server version'=>sqlrcon_serverVersion( $this->connection ),
    				'SQL Relay client version'=>sqlrcon_clientVersion( $this->connection )
    				);
    }
    
    /**
     * @desc 结果集的行数
     * @access public
     * @return intval
     */
	public function rowNums()
    {
        return sqlrcur_rowCount($this->cursor);
    }
    
	/**
	 * @desc 获取错误信息
	 * @access public
     * @return array
	 */
	public function getErrorInfo()
	{
		$errorInfo = array('The Database is down!',
						   'The Sql you commit was empty!',
						   'The Transaction commit fail!',
						   'The Transaction rollback fail!',
						   'The System do not support SQL Relay action!');
		if( $this->errorNum >=0 )
		{
			return $errorInfo[$this->errorNum];
		}
		else {
			return sqlrcur_errorMessage( $this->cursor );
		}
	}
	
	/**
	 * @desc 释放系统资源
	 */
	public function __destruct()
	{
		sqlrcon_endSession($this->connection);
		sqlrcon_free($this->connection);
        $this->connection = null;
        $this->queryCount = 0;
        return DB_OK;
	}
	
}

$obj = new SqlrelayAction();
//$obj->showDebugInfo();
$sel = "update dk_users set `usr_lastname`='fbbin001'";
$up = "select * from `dk_users`";
$arr = $obj->query( $up );

echo $obj->queryCount;

print_r($arr);

?>

这个些都是很简单的一些东西,但是由于是英文的文档,看得我相当吃力啊,花费了不少时间啊,其实主要的功能是在它的一个实现读写分离技术上面,这就需要看他额配置文件,配置文件位于/usr/lcoal/sqlrelay/etc/sqlrelay.conf,下面是我的配置文件:

<?xml version="1.0"?>
<!DOCTYPE instances SYSTEM "sqlrelay.dtd">
<instances>

	<!-- Regular SQL Relay Instance -->
	<instance id="reader" port="9091" socket="/tmp/reader.socket" dbase="mysql" connections="6" maxconnections="15" maxqueuelength="5" growby="1" ttl="60" maxsessioncount="1000" endofsession="commit" sessiontimeout="600" runasuser="nobody" runasgroup="nobody" cursors="5" authtier="listener" handoff="pass" deniedips="" allowedips="" debug="listener" maxquerysize="65536" maxstringbindvaluelength="4000" maxlobbindvaluelength="71680" idleclienttimeout="-1" maxlisteners="-1" listenertimeout="0" reloginatstart="no" timequeriessec="-1" timequeriesusec="-1">
		<users>
			<user user="fbbin" password="binbin"/>
		</users>
		<connections>
			<connection connectionid="reader_one" string="host=127.0.0.1;user=fbbin;password=binbin;db=slave;port=3306;charset=utf8" metric="1" behindloadbalancer="no"/>
			<connection connectionid="reader_two" string="host=127.0.0.1;user=fbbin;password=binbin;db=slave_two;port=3306;charset=utf8" metric="1" behindloadbalancer="no"/>
		</connections>
	</instance>

	<instance id="writer" port="9092" socket="/tmp/writer.socket" dbase="mysql" connections="4" maxconnections="15" maxqueuelength="5" growby="2" ttl="60" maxsessioncount="1000" endofsession="commit" sessiontimeout="600" runasuser="nobody" runasgroup="nobody" cursors="5" authtier="listener" handoff="pass" deniedips="" allowedips="" debug="listener" maxquerysize="65536" maxstringbindvaluelength="4000" maxlobbindvaluelength="71680" idleclienttimeout="-1" maxlisteners="-1" listenertimeout="0" reloginatstart="no" timequeriessec="-1" timequeriesusec="-1">
		<users>
			<user user="fbbin" password="binbin"/>
		</users>
		<connections>
			<connection connectionid="writer_one" string="host=127.0.0.1;user=fbbin;password=binbin;db=master;port=3306;charset=utf8" metric="1" behindloadbalancer="no"/>
			<connection connectionid="writer_two" string="host=127.0.0.1;user=fbbin;password=binbin;db=master_two;port=3306;charset=utf8" metric="1" behindloadbalancer="no"/>
		</connections>
	</instance>

	<instance id="router" port="9090" socket="/tmp/router.socket" dbase="router" connections="3" maxconnections="10" maxqueuelength="5" growby="2" ttl="60" maxsessioncount="1000" endofsession="commit" sessiontimeout="600" runasuser="nobody" runasgroup="nobody" cursors="5" authtier="listener" handoff="pass" deniedips="" allowedips="" debug="listener" maxquerysize="65536" maxstringbindvaluelength="4000" maxlobbindvaluelength="71680" idleclienttimeout="-1" maxlisteners="-1" listenertimeout="0" reloginatstart="no">
		<users>
			<user user="fbbin" password="binbin"/>
		</users>
		<router>
			<route host="127.0.0.1" port="9092" socket="/tmp/writer.socket" user="fbbin" password="binbin">
				<query pattern="^\s*insert\s+into\s+"/>
				<query pattern="^\s*INSERT\s+INTO\s+"/>
                                <query pattern="^\s*update\s+"/>
				<query pattern="^\s*UPDATE\s+"/>
                                <query pattern="^\s*delete\s+from\s+"/>
				<query pattern="^\s*DELETE\s+FROM\s+"/>
                                <query pattern="^\s*drop\s+table\s+"/>
				<query pattern="^\s*DROP\s+TABLE\s+"/>
                                <query pattern="^\s*create\s+table\s+"/>
				<query pattern="^\s*CREATE\s+TABLE\s+"/>
                        </route>
                        <route host="127.0.0.1" port="9091" socket="/tmp/reader.socket" user="fbbin" password="binbin">
				<query pattern="^\s*select\s+.*\s+from\s+"/>
				<query pattern="^\s*SELECT\s+.*\s+FROM\s+"/>
				<query pattern=".*"/>
                        </route>    
                </router>
	</instance>

</instances>

这里呢就模拟实现了数据的读写分离,至于这里面的XML的tag所对应的是什么意思,那么就需要去看看文档,我也是研究半天才弄出来的,不容易啊 !其实能写到这里很多都是参考了网上的一些资料,虽然很多资料不全,很多资料吵来吵去的,哎,这就需要自己去整理了。

参考资料:
1、http://www.linuxsir.org/main/?q=node/144;
2、http://www.ydmsh.com/www/Blog/Show/id/152/;
3、http://www.ydmsh.com/www/Blog/Show/id/149/
4、更多的是看/usr/local/sqlrelay/share/doc下面的英文文档了

新浪微博开放平台认证过程

4条评论

2011 年 12 月 02 日 at 下午 5:16分类:PHP

1:向新浪平台发布请求,获取未被授权的oauth_token和oauth_token_secret(每次请求到的都是不一样的)

请求:http://api.t.sina.com.cn/oauth/request_token?oauth_consumer_key=3013884794&oauth_nonce=6f04c63f12b12a2905ea5624e48beb57&oauth_signature=CRivWHNHQkl3Nv44ooInhH6ZZt8%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1322814877&oauth_version=1.0a

得到:oauth_token=92dd185616b77aae39d484c8812600c5&oauth_token_secret=e747515fe9db1fd77efb841e45333009

———————————————————————————————–

2:将上一步获得的 oauth_token 作为参数,引导用户浏览器跳至新浪微博的授权页面,用户进入这个页面登录新浪微博,进行Token的授权。如果在1中已经向服务器设置了浏览器的回调地址的话,则用户的浏览器将会被重定向至该地址,该地址将会新增一个参数:oauth_verifier

进入授权页面:http://api.t.sina.com.cn/oauth/authorize?oauth_token=ac0ae801cb73d856f9c968c088c9ebea&oauth_callback=http%3A%2F
%2Fwww.fbbin.com%2Fweibodemo%2Fcallback.php

———————————————————————————————-

3:在上一步设置的callback地址中,有向服务器请求真正的AccessToken,将1中得到的oauth_token ,oauth_token_secret和2中得到的oauth_verifier 作为参数,传递给新浪微博服务器,服务器将会返回真正的Access Token与Access Secret,还会返回用户在新浪微博中的user_id。有了用户的Access Token与Access Secret就可以自由便利地调用新浪微博的开放接口了,因此在你的APP中需要保存这两个值

授权之后执行页面:http://www.fbbin.com/weibodemo/callback.php?oauth_token=ac0ae801cb73d856f9c968c088c9ebea&oauth_verifier=182307

得到:oauth_token=988202bf0b2b68597e260016ac02268e&oauth_token_secret=9bd26dbb58cd6b59f639b04d3e5a95b3&user_id=2017141015

参考资料:
1·http://blog.yourtion.com/sina-oauth-verification-storage.html
2.http://www.fising.cn/2011/06/%E5%9F%BA%E4%BA%8Ephp%E7%9A%84oauth%E8%AE%A4%E8%AF%81%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E6%90%AD%E5%BB%BA.shtml
3.http://blog.csdn.net/youngerchen/article/details/6763045
4.http://www.u85.us/viewnews-1458.html