Linux基础IO函数压力测试

Posted: 2010年6月10日星期四
做系统性能调优确实不好做,不仅要从算法上去解决,还有就是系统的一个库函数的IO能力到底怎么样,虽然有些资料提到,但到底差多少,心里还是没谱,下面是我做的一个linux系统的IO函数的处理效率的压力测试,各执行一百万次,看消耗的时间,详情请看代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/time.h>
#define LOOPNUM 1000000
#define IOSIZE 8

static double currtime(void){
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec + (double)tv.tv_usec / (1000 * 1000);
}

int main(int argc, char *argv[]){
int i;
double stime, etime;

// open and close
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);
close(fd);
}
etime = currtime() - stime;
printf("open&close: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// open and write and close
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);
write(fd, "hogehoge", IOSIZE);
close(fd);
}
etime = currtime() - stime;
printf("open&write&close: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// open and read and close
char buf[256];
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);
read(fd, buf, IOSIZE);
close(fd);
}
etime = currtime() - stime;
printf("open&read&close: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// lseek and write
int fd = open("TESTFILE", O_RDWR | O_CREAT, 00644);
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
lseek(fd, 0, SEEK_SET);
write(fd, "hogehoge", IOSIZE);
}
etime = currtime() - stime;
printf("lseek&write: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// lseek and read
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
lseek(fd, 0, SEEK_SET);
read(fd, buf, IOSIZE);
}
etime = currtime() - stime;
printf("lseek&read: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// pwrite
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
pwrite(fd, "hogehoge", IOSIZE, 0);
}
etime = currtime() - stime;
printf("pwrite: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// pwrite
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
pread(fd, buf, IOSIZE, 0);
}
etime = currtime() - stime;
printf("pread: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// mmap and output
void *map = mmap(NULL, IOSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
memcpy(map, "hogehoge", IOSIZE);
}
etime = currtime() - stime;
printf("mmap&output: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

// mmap and input
stime = currtime();
for(i = 0; i < LOOPNUM; i++){
memcpy(buf, map, IOSIZE);
}
etime = currtime() - stime;
printf("mmap&input: total=%.8f qps=%.0f\n", etime, LOOPNUM / etime);

munmap(map, IOSIZE);
close(fd);

return 0;
}



编译执行,在我笔记本上的执行结果如下:
open&close: total=2.59459996 qps=385416
open&write&close: total=4.47499609 qps=223464
open&read&close: total=3.04626703 qps=328271
lseek&write: total=1.35459495 qps=738228
lseek&read: total=0.51210380 qps=1952729
pwrite: total=1.15936184 qps=862543
pread: total=0.32837701 qps=3045280
mmap&output: total=0.00775003 qps=129031686
mmap&input: total=0.00818515 qps=122172497
从上面结果看,还是mmap速度快,跟其它的少一个数量级。

用C测试加减乘除压力测试

Posted: 2010年6月8日星期二
闲得无聊写了一个计算一百万次加减乘除所需要的时间,具体代码如下:

#include <stdio.h>
#include <sys/time.h>

#define MAX 1000000

static double currtime(void)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) == -1) return 0.0;
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
}

int main(int argc, char *argv[])
{
int i, j = 0;
double stime, etime;
stime = currtime();
for (i = 0; i < MAX; i++)
j += 0;
etime = currtime();
printf("%d time's plus exec time is:\t%f\n", MAX, etime - stime);

stime = currtime();
for (i = 0; i < MAX; i++)
j -= 0;
etime = currtime();
printf("%d time's subtracting exec time is:\t%f\n", MAX, etime - stime);

stime = currtime();
for (i = 0; i < MAX; i++)
j *= 1;
etime = currtime();
printf("%d time's multiplication exec time is:\t%f\n", MAX, etime - stime);

stime = currtime();
for (i = 0; i < MAX; i++)
j /= 1;
etime = currtime();
printf("%d time's division exec time is:\t%f\n", MAX, etime - stime);
return 0;
}

在我的笔记本上测试,测试出来的加减乘除执行一百万次所需要的时间基本差不多。

c语言获取今日,明日,昨日的方法

Posted: 2010年6月7日星期一
最近要处理大量的log,基本上按天保存log文件,这就牵涉到日期问题,本来采用shell写了一套程序,发现效率不是很高,所以改用c语言重新写了一份,下面是获取系统的今日日期和昨日的一个demo

#include <stdio.h>
#include <time.h>

int main(int argc, char *argv[])
{
time_t t;
struct tm *m;

t = time(NULL) - 24 * 3600;
m = localtime(&t);
printf("Yesterday is: %d%.2d%.2d\n", m->tm_year + 1900, m->tm_mon + 1, m->tm_mday);

t = time(NULL);
m = localtime(&t);
printf("Today is: %d%.2d%.2d\n", m->tm_year + 1900, m->tm_mon + 1, m->tm_mday);


return 0;
}

nginx论坛防刷贴的实例

Posted: 2010年6月6日星期日
最近我的一个朋友跟我抱怨他的论坛被一群无聊的人在刷帖,问我有什么好的方法避免这种情况的发生,由于他使用的web服务器是nginx,我就利用web站点的访问log和nginx的目录访问权限规则写了个脚本,实现上面的功能,具体脚本的设计如下:
每5分钟探测当前这一个小时单IP的访问量,如果达到设置的上限,直接返回403错误页面给他,禁止他在接下来的一个小时的访问,并记录下他的ip,若在接下来的一个小时访问量正常,再智能的把他的站点的访问权限放开,允许他继续使用论坛,这样就拒绝了这群无聊的人,当然记得排除自己的ip地址。
附shell脚本实例

#!/bin/sh

LANG=UTF-8
onehourago=`date -d "1 hour ago" +%d\/%b\/%Y:%H`
now=`date +%d\/%b\/%Y:%H`
grep -v 1.1.1.1 /usr/local/nginx/logs/access.log | awk '/$onehourago/{++S[$1]}END{for (a in S) print a, S[a]}' | awk '{if($2>5000) print }' > /tmp/onehoureagodenyip
grep -v 1.1.1.1 /usr/local/nginx/logs/access.log | awk '/$now/{++S[$1]}END{for (a in S) print a, S[a]}' |awk '{if($2>5000) print }' > /tmp/nowdenyip
cat /tmp/onehoureagodenyip >> /tmp/nowdenyip
awk '{print $1}' /tmp/nowdenyip | sort |uniq > /tmp/denyip
cat /dev/null > /usr/local/nginx/conf/acl.conf
if [ ! -z /tmp/denyip ] ; then
for i in `cat /tmp/denyip`
do
echo "deny $i;" >> /usr/local/nginx/conf/acl.conf
done
fi
echo "allow all;" >> /usr/local/nginx/conf/acl.conf
echo "location ~ .*\.php?$ {
include /usr/local/nginx/conf/enable_php5.conf;
}" >> /usr/local/nginx/conf/acl.conf
kill -HUP `ps -ef |grep "nginx: master" |grep -v grep |awk '{print $2}'`

脚本说明:
先在nginx的主配置文件中server断中加入如下的内容:
location ^~ / {
include /usr/local/nginx/conf/acl.conf;
}
排除1.1.1.1的ip,同时当单IP一个小时的访问量达到5000次就屏蔽它(要是你的log中记录的东西不多的话,90%的用户单IP访问量一小时不可能有这么大)。

实时监控demo

Posted: 2010年5月22日星期六
最近写了一个实时服务器资源监控系统的demo,具体URL详见:http://60.29.244.76/live/live.html
目前能监控cpu,内存,load,网络连接状态,带宽。每2秒刷新一次,每条线都有开关,实现在动态显示,
Y轴随关闭的线,动态显示大小,每个点都有显示当前的实时状态的点,鼠标移动到相应的点能显示出来,如果闲更新太快,显示看不清楚,可以点击暂停,等你看清楚了可再点击开启实现再监控的功能。
目前只是一个demo程序,很多东西还没有,后期完善后,准备开源。

c实现数据字典批量替换文本内容

Posted: 2010年3月7日星期日
最近工作中需要大量替换log日志中的IP地址,使之显示ip对应的地区信息,于是用C写了个程序实现上述功能。
具体代码如下:

#include <stdio.h>
#include <stdlib.h>
#define __USE_GNU //为使用strcasestr函数添加
#include <string.h>

void str_replace(char *buf, const char *pre, const char *aft)
{
char *p;
//不区分大小写
p = strcasestr(buf, pre);
if (p){
char *p_2 = p + strlen(pre);
str_replace(p_2, pre, aft);
memmove(p + strlen(aft), p_2, strlen(p_2)+1);
memcpy(p, aft, strlen(aft));
}
}

int main(int argc, char *argv[])
{
FILE *fp1, *fp2;
char buf1[1024] = {0},
buf2[1024] = {0},
id[20] = {0},
name[32] = {0};

if (argc != 3) {
printf("%s \n", argv[0]);
exit(-1);
}

fp1 = fopen(argv[1], "r");
if (fp1 == NULL) {
printf("Open file %s FAILE!", argv[1]);
exit(-1);
}

fp2 = fopen(argv[2], "r");
if (fp2 == NULL) {
printf("Open file %s FAILE!", argv[2]);
exit(-1);
}

while (fgets(buf1, sizeof(buf1), fp1)) {
fseek(fp2, 0, SEEK_SET);
while (fgets(buf2, sizeof(buf2), fp2)) {
sscanf(buf2, "%s %s", id, name);
str_replace(buf1, id, name);
}
printf("%s", buf1);
}

fclose(fp1);
fclose(fp2);

return 0;
}

编译:
gcc -o replace replace.c

使用:
./replace apache.log ip.txt
其中apahce.log文件可以为任何格式的文本文件,ip.txt文件具体格式如下所示:

1.1.1.1 北京电信
2.2.2.2 天津联通
3.3.3.3 广州移动

第一列为IP地址,第二列为ip地址对应的地区信息。
该工具同样适用于其它的类似替换,代码中已做了不区分大小写处理。

shell编程中文本处理中用到的几张图

Posted: 2009年11月10日星期二
在网上找到几个shell文本处理的几个常用命令的使用方法,觉得比较好,就贴在博客中,权当备忘,由于在google图片搜索出来的,不知道原载作者是谁,如知道,告诉我一声,加上原作者地址,下面的图直接看比较小,可以直接点击图,看大图,或者下载下来作为文件保存
正则表达式:

vi使用

sed使用

awk使用

分析the address already in use产生的原因

Posted: 2009年11月5日星期四
最近在维护一个别人写的程序时遇到一个它的程序启动不了的情况,具体情况如下,服务器端按照它的方式不能正常关闭,就用kill命令把它的程序结束掉了,再启动程序时报如下的错误:the address already in use,问了公司的同事,同事也告诉不可能啊,要不就是由什么链入了系统库了,当时我也不懂也没有在意,没有去查,这几天我刚好在学习linux网络编程,也碰到了这个问题,通过查找资料,了解到,并不是什么链入了系统库了,而是下面将要说明的情况:
一般情况认为,server socket已经关闭,理应释放了ip address及port资源,但是事实并不一定是这样的,因为大部分socket连接是基于tcp协议多次握手连接的基础上,以保证数据传输的完整性,我们都知道tcp协议是一种可靠的协议,它有3次握手,四次挥手过程,下面是4次挥手过程
(1)当服务器端处于ESTABLISHED状态时, 服务器欲主动关闭连线,发送FIN至客户端,进入到FIN-WAIT-1状态,等待客户端回应ACK,表示等待确认客户端得知服务器端要关闭连线
(2)当客户端收到服务器端的FIN时,立即回应ACK给服务器端,进入到CLOSE-WAIT状态,表示须等到应用程序没有任何资料要传送给服务器端, 客户端才决定关闭连线
(3)服务器端收到客户端回应的ACK,表示确认客户端得知服务器要关闭连线,此刻等待客户端发送FIN
(4)客户端决定关闭连线,发送FIN至服务器端,进入到LAST-ACK状态,等待服务器端回应ACK,表示等待确认服务器端得知客户端要关闭连线
(5)服务器端收到客户端的FIN后,随即回应ACK,进入TIME_WAIT状态,表示服务器端得知客户端要关闭连线,且等待2MSL时间,以防客户端再次发送FIN
(6)客户端收到服务器端回应的ACK后,进入CLOSED状态,表示客户端已确认服务器端已得知它要关闭连线,才进行关闭连线
(7)服务器端等待2MSL时间,才进入CLOSED状态,关闭连线,并自连线表中移除
在上述的第(5),(7)点,此刻TIME-WAIT的用意在于,虽服务器已确认客户端要关闭连线,且回应了ACK给客户端,但不保证客户端会收到ACK ,一旦ACK遗漏,
客户端会再次发送FIN给服务器端,再次进行确认,所以服务器端须进入TIME-WAIT状态,等待2MSL时间,预防客户端会再次发送FIN,进行连线关闭的确认。

从上面的4次挥手过程得知,我们直接结束游戏的服务器端,它并没有马上释放它的ip及port资源就在于服务器端进入了time_wait状态,有可能客户端没有
收到ack资源,导致不停的发送fin请求,导致直接结束服务器端,导致重启起不来,下面是我用C写的一个程序怎么重现上面所出现的实例,以及怎么避免上面所
出现的情况
1.客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
struct sockaddr_in server;
int sock;
char buf[32];
int n;

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}

server.sin_family = AF_INET;
server.sin_port = htons(1234);
server.sin_addr.s_addr = inet_addr("127.0.0.1");

if(connect(sock, (struct sockaddr *)&server, sizeof(server)) != 0){
perror("connetc");
exit(1);
}

n = read(sock, buf, sizeof(buf));
if(n < 0) {
perror("read");
return 1;
}

printf("%d, %s\n", n, buf);

close(sock);

return 0;
}

帮上面的代码保存为一个client.c文件,通过下面命令进行编译
gcc -o client client.c
2.重现出现情况的服务器端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
int sock0;
struct sockaddr_in addr;
struct sockaddr_in client;
int len;
int sock;

sock0 = socket(AF_INET, SOCK_STREAM, 0);
if (sock0 < 0) {
perror("socket");
return 1;
}

addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sock0, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("bind");
return 1;
}

if (listen(sock0, 5) !=0) {
perror("listen:");
return 1;
}
while (1) {
len = sizeof(client);
sock = accept(sock0, (struct sockaddr *)&client, &len);
if (sock < 0) {
perror("accept:");
break;
}

printf("accepted connection from %s\n",
inet_ntoa(client.sin_addr));
if(send(sock, "Hello", 5, 0) < 1) {
perror("write:");
return 1;
}

close(sock);
}
close(sock0);

return 0;
}

把上面的程序保存为一个server1.c文件,执行下面的命令进行编译
gcc -o server1 server1.c
3.避免上述情况发生的服务器端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
int sock0;
struct sockaddr_in addr;
struct sockaddr_in client;
int len;
int sock;
int yes = 1;

sock0 = socket(AF_INET, SOCK_STREAM, 0);
if (sock0 < 0) {
perror("socket");
return 1;
}

addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = INADDR_ANY;
setsockopt(sock0,
SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(yes));
if(bind(sock0, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
perror("bind");
return 1;
}

if (listen(sock0, 5) !=0) {
perror("listen:");
return 1;
}
while (1) {
len = sizeof(client);
sock = accept(sock0, (struct sockaddr *)&client, &len);
if (sock < 0) {
perror("accept:");
break;
}

printf("accepted connection from %s\n",
inet_ntoa(client.sin_addr));
if(send(sock, "Hello", 5, 0) < 1) {
perror("write:");
return 1;
}

close(sock);
}
close(sock0);

return 0;
}

把上面的程序保存为一个server2.c文件,执行下面的命令进行编译
gcc -o server2 server2.c

启动服务器端
./server1
在另外一个终端中启动客户端
./clinet
得到如下的内容
5, Hello
在服务器的终端通过ctrl + c结束刚刚起来的服务器端,在一分钟内再启动服务器端,你就可以发现出现下面的错误信息
bind: Address already in use
过1分钟后(Linux系统默认time_wait的时间为60秒)就可以发现可以起来了。
为规避上面的情况,再启动server2,再重复上面的步骤,发现已经没有这种情况了。

上面只是一个具体的实例,并不保证数据的完整性,具体到它们的程序中,具体进行分析。

c开发的中的man使用技巧

Posted: 2009年11月2日星期一
在c语言开发过程中,经常需要进行查找函数的说明,在linux下有man命令,比较容易获取c的标准库函数的说明,在ubuntu下若要查看C语言的函数说明,需要安装manpages-dev包,具体安装方法如下
sudo apt-get install manpages-dev
若对于英文不好的同学,可以再安装一个中文的语言包:manpages-zh,这个语言包并不一定包含所有的函数说明,只是其中的一部分,安装方法如下:
sudo apt-get install manpages-zh
通常对于一些不常用的函数可以直接通过man 函数名,可以得到所需的函数说明,对于函数名跟系统命令相同时,就不会得到你想要的函数说明,具体怎么区分怎么才能得到了我想要的函数说明呢?
通常做法如下所示
1. man -k 函数名 |grep ^函数名
2.通过查到的结果,一般会在后面加一个括号带一个数字,再通过man 刚刚得到的数字 函数名 得到我们想要的函数帮助文档
譬如查看c语言read函数说明
man read |grep ^read
得到的结果如下:
zhou@lingjie:~$ man -k read |grep ^read
read (2) - read from a file descriptor
readahead (2) - perform file readahead into page cache
readahead-list (8) - manual page for readahead-list: 0.20050517.0220
readahead-watch (8) - manual page for readahead-watch: 0.20050517.0220
readdir (2) - read directory entry
readdir (3) - read a directory
readdir_r (3) - read a directory
readelf (1) - Displays information about ELF files.
readline (3readline) - get a line from a user with editing
readlink (1) - display value of a symbolic link
readlink (2) - read value of a symbolic link
readlinkat (2) - read value of a symbolic link relative to a directory ...
readom (1) - read or write data Compact Discs
readprofile (1) - a tool to read kernel profiling information
readv (2) - read or write data into multiple buffers

那么我查看C的函数说明就是如下的命令
man 2 read
就可以得到我们想要的函数说明了。

利用iptables端口映射实现跨多机房内网访问

Posted: 2009年10月9日星期五
公司在外地的移动机房,由于公司采用的电信通网络,公司的机房跟外地的移动机房连接及其不稳定,丢包以及连接中断是家常便饭,由于需要程序需要数据交换,这种网络状况基本上不可能,公司放了一个机器放在一个双线机房用iptables端口转发实现数据交换,进行服务器维护,但是最近公司有个需求需要直接用公司的机器访问,外地移动机房内的内网DB,这台DB没有外网地址就得进行多次转发。由于放在移动机房的只有一个linux机器具有外网IP,而这台机器上也有mysql,占用了3306端口,这就需要把移动机房中的转发机器的其它端口转发到内网DB的3306上,同时为安全起见只允许机房的ip地址访问转发的端口,具体需求拓扑如下所示:

下面是实现过程
在中转机房的机器上执行

iptables -A INPUT -p tcp -s 219.x.x.x -d 220.x.x.x --dport 5300 -j ACCEPT
iptables -A PREROUTING -s 219.x.x.x -d 220.x.x.x -p tcp --dport 5300 -j DNAT --to-destination 221.x.x.x
iptables -A POSTROUTING -p tcp --dport 5300 -j SNAT --to-source 220.x.x.x


在移动机房的机器上执行下面的语句

iptables -A PREROUTING -d 221.x.x.x -p tcp --dport 5300 -j DNAT --to-destination 192.168.0.6:3306
iptables -A POSTROUTING -p tcp --dport 3306 -j SNAT --to-source 192.168.0.5


通过执行上面的语句,公司的机器可以通过直接访问中转机房的220.x.x.x的5300直接访问到移动机房内网的机器

搭建linux ris服务器批量在dell服务器上安装windows 2003

Posted: 2009年10月7日星期三
以前写了一个《使用kickstart批量安装Linux》对于sa来说节省了很大部分时间,现在又一批机器需要安装windows 2003由于有一台linux安装服务器,为节约服务器着想,决定在这台服务器上面安装一个linux ris服务器,其实在网上有一堆文章介绍搭建一个linux ris服务器批量安装windows,对于一般的pc来说照着他们的做也许没有问题很简单就能搭建出来,但是对于服务器而言就比较的麻烦了,首先我们的这批服务器是dell的服务器,安装windows 2003需要dell的安装引导盘,引导盘的作用就是用来加载驱动程序,而网上的文章基本没有介绍有关如何加载驱动?加载驱动有什么技巧以及注意的地方。我也问了我的一些sa的朋友,几个朋友都告诉我他们是一台台手动去安装,问他们原因,他们说配置驱动很麻烦。事实证明他们说的确实是这样,如果你安装的机器是10台以内,我建议你还是别照着我的做了,一台台手动安装比较的快。

本文的中演示的是安装windows 2003 i386版本。并不适用其它版本,对于window 2003之前的版本可以参照linux ris安装文档进行相应的更改,对于vista之后的windows更不适用,vista之后的版本都改成了wds进行批量部署。
实施方案的拓扑图如下所示
          ┌────────┐
          │   Boot Server   │
          │(DHCP TFTP samba)│
          └───┬────┘
                  │
          ┌───┴───┐
          │     Switch    │
          └───┬───┘
                   │
      ┌─────┴──────┐
      │                         │
┌──┴───┐        ┌───┴───┐
│Client(PxE)  │        │Client(PxE)    │
└──────┘        └───────┘
实施步骤
1.配置启动服务器
2.配置安装方案
3.启动待安装服务器,进行系统安装

启动服务器就一个普通的linux pc能够连接上网络,下面说说我要安装的服务器配置
dell 1950:
CPU: E5410*2(2330MHZ)
mem: 2G*4
raid控制卡: PERC 6/i
网卡: NetXtreme II Broadcom
配置启动服务器
启动服务器的目的是帮助待安装机器上启动Red Hat Linux安装程序。启动服务器上需要搭建一个DHCP 服务器和一个TFTP 服务器,一个nfs用来传输网络镜像文件。前者是为了给待安装机器分配IP地址,后者则是提供了一个让待安装机器下载启动镜像的途径。
首先配置DHCP服务
1) 安装DHCP服务器包(RPM包名:dhcpd)。
2)配置服务器IP地址
3) 编辑DHCP服务器配置文件 /etc/dhcp.conf。

ddns-update-style interim;
ignore client-updates;
allow booting;
allow bootp;

subnet 192.168.0.0 netmask 255.255.255.0 {
option routers 192.168.0.1;
option subnet-mask 255.255.255.0;
option domain-name-servers 192.168.0.1;
range dynamic-bootp 192.168.0.128 192.168.0.254;
default-lease-time 21600;
max-lease-time 43200;
next-server 192.168.0.4;
filename "pxelinux.0";
}
}

注:
一般参数基本一样,下面我说以下几个重要,平时作为dhcp服务器,不常用的几个参数
allow booting;allow bootp;这两句话的意思是能作为pxe的启动服务器,给他们分配ip地址,next-server作用是指定tftp服务器的地址,filename,找到tftp服务器后找下载那个启动文件,搭建tftp服务器
4) 给启动服务器配置一个IP,这个IP必须在DHCP服务器定义的子网内。上例中配置的启动服务器的ip地址为192.168.0.4。
5) 启动DHCP服务。
service dhcpd start
配置TFTP服务器(如没有安装xinetd软件包,先安装这个软件包)

1) 安装TFTP服务器包(RPM包名:tftpd)。
2) 编辑TFTP 服务器的配置文件 /etc/xinetd.d/tftp。配置文件如下:
service tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = dpan
server = /usr/sbin/in.tftpd
server_args = -s /tftpboot -m /tftpboot/rules -vv
disable = no
}

注:
这里选定了/tftpboot为TFTP 服务器的根目录位置。
-m /tftpboot/rules 这里指定一个规则文件,这个文件的作用下文中指出,
-vv的作用是把tftp的log详细输出,log文件为/var/log/message,作用为便于调试。
3)建立/tftpboot/rules文件,文件内容如下:
ri ^[a-z]: # Remove “drive letters”
rg \\ / # Convert backslashes to slashes
rg \# @ # Convert hash marks to @ signs
rg /../ /..no../ # Convert /../ to /..no../
rg A a
rg B b
rg C c
rg D d
rg E e
rg F f
rg G g
rg H h
rg I i
rg J j
rg K k
rg L l
rg M m
rg N n
rg O o
rg P p
rg Q q
rg R r
rg S s
rg T t
rg U u
rg V v
rg W w
rg X x
rg Y y
rg Z z
r ^/(.*) \1

这个文件的作用是把所有的查找大写的内容转为小写,由于windows不区分大小写,故统一改成小写。
4)启动TFTP服务
service xinetd start

由于windows的ris安装是通过windows共享文件的方式进行文件传输后,在linux下可以通过samba实现与windows进行文件共享,进而实现了ris安装
配置samba服务器
1) 安装samba服务器包(RPM包名:samba)。
2)配置服务器IP地址
3) 编辑samba服务器配置文件 /etc/samba/smb.conf

[global]
null passwords = true
workgroup = MYGROUP
server string = Samba Server
security = share
load printers = no
disable spoolss = yes
debug level = 2
log file = /var/log/samba/%m.log
max log size = 0
dns proxy = no
[wininstall]
path = /tftpboot
browsable = true
read only = No
writable = yes
guest ok = Yes

配置启动信息
在windows下可以通过expand解压缩光盘中的文件,由于linux下面没有这个工具,但是有人写了一个叫cabextract的工具可以解压windows光盘中的压缩文件,具体可以在http://www.cabextract.org.uk/下载到你所需的版本,
由于我采用的centos 5.2 32位版,为简单着想我安装的是rpm包,具体操作如下
1)下载
wget http://www.cabextract.org.uk/cabextract-1.2-1.i386.rpm
2)安装
rpm -ivh cabextract-1.2-1.i386.rpm
上传windows 2003的iso镜像或者光盘中的内容到服务器的一个目录,这里我采用上传一个windows 2003的iso镜像到dpan目录下。并把光盘挂载到/tftpboot的win2k3目录下
1)建立win2k3目录
mkdir /tftpboot/win2k3
2)挂载iso文件到/mnt目录下
mount -o loop /dpan/windows2003.iso /mnt
3)为方便我们往后面往里面添加驱动或者添加启动后的脚本文件,我们把光盘中的内容复制到/tftpboot下的win2k3目录下
cp -r /mnt/* /tftpboot/win2k3
4)进入/tftpboot目录,配置windows ris启动文件信息(每行为一个命令)

cd /tftpboot
cabextract win2k3/i386/startrom.n1_
sed -i -e 's/NTLDR/W2K3L/gi' startrom.n12
mv startrom.n12 /tftpdpan/w2k3.0
cabextract win2k3/i386/setupldr.ex_
sed -i -e 's/winnt\.sif/wi2k3\.sif/gi' setupldr.exe
sed -i -e 's/ntdetect\.com/ntdetect\.2k3/gi' setupldr.exe
mv setupldr.exe /tftpdpan/w2k3l
cp win2k3/i386/ntdetect.com /tftpdpan/ntdetect.2k3

5)复制pxe的启动镜像到/tftpboot目录
启动镜像pxelinux.0可以在syslinux安装包里获得。安装好syslinux安装包后,将pxelinux.0复制到/tftpboot
cp /usr/lib/syslinux/pxelinux.0 /tftpboot
6)启动镜像pxelinux.0文件在执行过程中,会读取同目录下pxelinux.cfg文件夹下的配置文件以确定它应该加载什么启动文件。
建立启动配置文件存放文件夹
mkdir /tftpboot/pxelinux.cfg
建立启动配置文件default
default win2k3
label win2k3
kernel w2k3.0

建立应答文件/tftpboot/wi2k3.sif,文件内容如下所示

[data]
AutoPartition = "0"
floppyless = "1"
msdosinitiated = "1"
UnattendedInstall = "Yes"
OriSrc = "\\192.168.0.4\wininstall\win2k3\i386"
OriTyp = "4"
LocalSourceOnCD = "1"
DisableAdminAccountOnDomainJoin = "1"

[SetupData]
OsLoadOptions = "/fastdetect"
SetupSourceDevice = "\Device\LanmanRedirector\192.168.0.4\wininstall\win2k3"

[Unattended]
UnattendMode = "FullUnattended"
FileSystem = "LeaveAlone"
NtUpgrade = "No"
OverwriteOemFilesOnUpgrade = "No"
DriverSigningPolicy = "Ignore"
UpdateInstalledDrivers = "Yes"
ConfirmHardware = "No"
ExtendOEMPartition = "0"
TargetPath = "\WINDOWS"
UnattendSwitch = "Yes"
WaitForReboot = "No"
CrashDumpSetting = "0"
OemSkipEula = "Yes"
OEMSkipWelcome = "1"
InstallFilesPath = "\\192.168.0.4\wininstall\win2k3\i386"
LegacyNIC = "1"

[UserData]
ProductKey = "11111-11111-11111-11111-11111"
FullName = "abc"
OrgName = "xyz"
ComputerName = *

[GuiUnattended]
AdminPassword = 123456
EncryptedAdminPassword = "No"
OEMSkipRegional = "1"
TimeZone = "220"
OemSkipWelcome = "1"

[Identification]
JoinWorkgroup = WORKGROUP

[RemoteInstall]
Repartition = "No"
UseWholeDisk = "No"

[Networking]
InstallDefaultComponents = "Yes"


完成后目录结构如下所示
|-- w2k3l
|-- ntdetect.2k3
|-- pxelinux.0
|-- pxelinux.cfg
|  `-- default
|-- win2k3
|  |-- i386
|  |-- autorun.inf
|  |-- bootfont.bin
|  |-- docs
|  |-- i386
|  |-- $oem$
|  |-- printers
|  |-- readme.htm
|  |-- setup.exe
|  |-- support
|  |-- win51
|  |-- win51ia
|  `-- win51ia.sp1
|-- rules
|-- w2k3.0
`-- wi2k3.sif

经过以上的操作基本可以把dell 1950起来到蓝色的安装界面,要是有不需要任何驱动的机器,基本可以完成安装。由于是服务器,有些特定的硬件需要提前加载好驱动,安装起来比较的麻烦,下面告诉你怎样加载驱动,
首先是NetXtreme II Broadcom网卡驱动,既然是网络安装,那么首先需要加载的一定是网卡驱动,由于Broadcom公司提供了ris的驱动(不是系统下的驱动),具体这可在Broadcom驱动下载页中下载到相应的驱动,我需要的驱动下载地址为:http://www.broadcom.com/docs/driver_download/NXII/win_2k3_RIS-5.0.0.zip
下载解压
wget http://www.broadcom.com/docs/driver_download/NXII/win_2k3_RIS-5.0.0.zip
mkdir -p /tftpdpan/nicdriver
unzip win_2k3_RIS-5.0.0.zip -d /tftpdpan/nicdriver


由于要驱动网卡,需要一个binlserver, BINL读取所有新的与网络接口卡相关的 .inf 文件,并在映像中创建 .pnf 文件。
配置binl server,用来加载网卡驱动
1)服务器下载
wget http://oss.netfarm.it/guides/ris-linux-0.4.tar.gz
2)解压
tar xf ris-linux-0.4.tar.gz
3)执行
cd ris-linux-0.4
./infparser.py /tftpdpan/nicdrivers/Server/W2K3_W2K8/RIS/
./binlsrv.py -d
cp /tftpdpan/nicdrivers/Server/W2K3_W2K8/RIS/b06nd51x.sys /tftpdpan/win2k3/i386
启动被安装服务器,现在就可以加载到网卡驱动了

加载完网卡驱动还有找不到磁盘,需要加载raid磁盘驱动:
通过光盘安装过windows的用户知道在开始时蓝色屏幕上有一个按F6的提示,这个提示是用来通过软盘加载磁盘驱动,这类驱动是windows系统中的驱动,可以下载好windows驱动,解压好驱动放在一个指定的目录,再通过应答文件,一般可以加载上这类驱动,这类驱动加载完毕后不必在系统中再安装这类驱动。加载这类驱动有注意点:文件要有正确的oem数字签名(一般提供驱动的厂商都带数字签名),乱改动这类文件,同时不在配置文件中写明去掉数字签名认证,是不能加载的。
下面通过一个实例说明怎么加载dell的PERC 6/i raid卡的驱动,完成ris的驱动加载
1)下载dell的perc 6/i驱动
wget ftp://ftp.us.dell.com/SAS-RAID/DELL_MULTI-DEVICE_A07_R211422.exe
2)建立加载驱动的目录
mkdir -p /tftpboot/win2k3/\$oem\$/textmode
3)解压驱动文件
unzip DELL_MULTI-DEVICE_A07_R211422.exe -d /tftpboot/win2k3/\$oem\$/textmode
4)配置自动应答文件wi2k3.sif,在文件的末尾加上如下内容
[MassStorageDrivers]
"DELL PERC5 and PERC6/CERC6 RAID Controller Driver (Server 2003 32 bit)"=OEM

[OEMBootFiles]
nodev.inf
oemsetup.inf
percsas.cat
percsas.sys
txtsetup.oem
同时在wi2k3.sif中的[Unattended]段添加如下字段
OemPreInstall = "Yes"
注意了[MassStorageDrivers]段中的字符是编辑txtsetup.oem文件查看PERC_32中后面的字符段得到的结果,[OEMBootFiles]段中下面的配置就是刚刚解压后的所有文件,这样就完成了对raid卡驱动的加载
完成后目录结构如下所示
|-- w2k3l
|-- ntdetect.2k3
|-- pxelinux.0
|-- pxelinux.cfg
|  `-- default
|-- win2k3
|  |-- i386
|  |-- autorun.inf
|  |-- bootfont.bin
|  |-- docs
|  |-- i386
|  |-- $oem$
|    |-- textmode
|    |-- nodev.inf
|    |-- oemsetup.inf
|    |-- percsas.cat
|    |-- percsas.sys
|    `-- txtsetup.oem
|  |-- printers
|  |-- readme.htm
|  |-- setup.exe
|  |-- support
|  |-- win51
|  |-- win51ia
|  `-- win51ia.sp1
|-- rules
|-- w2k3.0
`-- wi2k3.sif
以上就可以完成对raid卡为PERC 6/i 网卡为:NetXtreme II Broadcom的机器批量安装。但安装完成后还需手动打补丁包、安装简单的软件如rar,以及执行一写安全脚本命令等。这样如果一台台去操作,工作量大。
学习了以下某些盗版光盘的制作方法,完成该系统的优化操作(由于这部分已经快是一个成型的windows系统,故建立的这部分文件千万注意了为windows文件格式,最好用windows下的文本编辑器进行编辑操作,用vi编辑的话会有两个后果,一是文件保存是两者的换行符不一致,而如果写了中文的话,两者保存了文件的编码方式不一致,导致执行不了这部分的命令)。
CMDLINES.TXT 这个文件位于$OEM$目录下,安装是当进行到注册组件(也就安装到剩余13分钟)时这个特殊的脚本被安装程序调用
下面是一个典型的
[COMMANDS]
"install.cmd"
"REGEDIT32.EXE /S youhua.reg"
"update.cmd"
这个文件的内容总是以" [COMMANDS]"为起头;总是使用引号(")来标记下面需要执行的每条命令CMDLINES.TXT是一个比较受限制的命令行接口,这就意味着很多传统DOS命令将不能由它进行调用,但是外部命令(比如REGEDIT32.EXE)还是可以调用的.
注意路径问题,这里的youhua.reg、update.cmd和install.cmd的路径为cmdlines.txt的同级目录.在$oem$目录下建立一个youhua.reg和install.cmd文件,youhua.reg文件为注册表优化的一部分参数,install.cmd为打补丁和安装网卡驱动的一些操作,install.cmd为添加网卡驱动的操作,下载网卡驱动,放置在$oem$目录下的DrvInst下,下面为几个文件的具体实例
install.cmd

@ echo off
@ color 0a

@ echo 安装网卡驱动中。。。。。 2>>%HOMEDRIVE%/errlog.txt

@ cd DrvInst 2>>%HOMEDRIVE%/errlog.txt

BDrv5706.msi /qn 2>>%HOMEDRIVE%/errlog.txt

if %errorlevel% EQU 0 (
@ echo 网卡驱动安装成功 2>>%HOMEDRIVE%/errlog.txt
@ cd ..
@ goto win2k3sp2 2>>%HOMEDRIVE%/errlog.txt
) else (
@ echo 网卡驱动安装失败 继续安装WINDOWS_2003_SP2 补丁 2>>%HOMEDRIVE%/errlog.txt
@ cd ..
@ goto win2k3sp2 2>>%HOMEDRIVE%/errlog.txt
)

:win2k3sp2

@echo 安装WINDOWS_2003_SP2补丁中。。。。 2>>%HOMEDRIVE%/errlog.txt

sp2.exe /passive /norestart 2>>%HOMEDRIVE%/errlog.txt

if %errorlevel% EQU 0 (
@ echo WINDOWS_2003_SP2补丁安装成功 2>>%HOMEDRIVE%/errlog.txt
) else (
@ echo WINDOWS_2003_SP2补丁安装失败 详细见errlog日志 2>>%HOMEDRIVE%/errlog.txt
)

youhua.reg

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\netBT\Parameters]
"SMBDeviceEnabled"=dword:0
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa]
"RestrictAnonymous"=dword:1
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
"SynAttackProtect"=dword:1
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System]
"DisableCAD"=dword:1
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System]
"DontDisplayLastUserName"=dword:1
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System]
"DontDisplayLockedUserId"=dword:3
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]
"DontDisplayLastUserName"=dword:1
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp]
"PortNumber"=dword:12345
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
"PortNumber"=dword:12345

完成后目录结构如下所示
|-- w2k3l
|-- ntdetect.2k3
|-- pxelinux.0
|-- pxelinux.cfg
|  `-- default
|-- win2k3
|  |-- i386
|  |-- autorun.inf
|  |-- bootfont.bin
|  |-- docs
|  |-- i386
|  |-- $oem$
|    |-- install.cmd
|    |-- youhua.reg
|    |-- cmdlines.txt
|    |-- DrvInst
|      |-- BDrv5706.msi
|      |-- 0x0409.ini
|      |-- DrvFiles.cab
|      |-- instmsia.exe
|      |-- instmsiw.exe
|      |-- setup.exe
|      |-- Setup.ini
|  |-- textmode
|      |-- nodev.inf
|      |-- oemsetup.inf
|      |-- percsas.cat
|      |-- percsas.sys
|      `-- txtsetup.oem
|  |-- printers
|  |-- readme.htm
|  |-- setup.exe
|  |-- support
|  |-- win51
|  |-- win51ia
|  `-- win51ia.sp1
|-- rules
|-- w2k3.0
`-- wi2k3.sif
上一步部分不是说有一部分命令不能执行吗,还有办法在应答文件中添加即自动应答的GuiRunOnce段中添加执行脚本,具体就是在第一次登录系统时执行脚本,脚本放置在$oem$下的$1目录里,这个目录里的内容会放在系统安装盘下根目录下,由于windows自动安装中不能自动分区通过GuiRunOnce,使用系统自带的diskpart命令实现自动分区,使用系统自带的cacls等命令实现分区及系统安全
在wi2k3.sif文件中添加如下内容,实现分区和系统安全及其其它命令
[GuiRunOnce]
%systemdrive%\diskpart.cmd
%systemdrive%\setup.cmd
下面是实现后的目录结构
|-- w2k3l
|-- ntdetect.2k3
|-- pxelinux.0
|-- pxelinux.cfg
|  `-- default
|-- win2k3
|  |-- i386
|  |-- autorun.inf
|  |-- bootfont.bin
|  |-- docs
|  |-- i386
|  |-- $oem$
|    |-- install.cmd
|    |-- youhua.reg
|    |-- cmdlines.txt
|    |-- DrvInst
|      |-- BDrv5706.msi
|      |-- 0x0409.ini
|      |-- DrvFiles.cab
|      |-- instmsia.exe
|      |-- instmsiw.exe
|      |-- setup.exe
|      |-- Setup.ini
|  |-- textmode
|      |-- nodev.inf
|      |-- oemsetup.inf
|      |-- percsas.cat
|      |-- percsas.sys
|      `-- txtsetup.oem
|  |-- $1
|      |-- diskpart.cmd
|      |-- diskpart.txt
|      |-- setup.cmd
|  |-- printers
|  |-- readme.htm
|  |-- setup.exe
|  |-- support
|  |-- win51
|  |-- win51ia
|  `-- win51ia.sp1
|-- rules
|-- w2k3.0
`-- wi2k3.sif

下面是几个文件的具体内容
diskpart.cmd

@ echo off
for %%i in (d,e,f,g,h) do mountvol %%i: /D > nul
%windir%\system32\diskpart /s %systemdrive%\diskpart.txt
echo y | format D: /FS:NTFS /V:GAME /Q /X /A:4096
if %errorlevel% EQU 0 (
@ echo format D: success....
) else (
@ echo format D: fail....
)
echo y | format E: /FS:NTFS /V:OTHER /Q /X /A:4096
if %errorlevel% EQU 0 (
@ echo format E: success....
) else (
@ echo format E: fail....
)


diskpart.txt

select disk 0
create partition extended
create partition logical size=30800
assign letter=D
create partition logical
assign letter=E


setup.cmd

@ echo off

@del /f /q %WINDIR%\Web\printers
@del /f /q %WINDIR%\Web\printers\images
@del /f /q %WINDIR%\Web\printers\PrtCabs
@del /f /q %WINDIR%\Help\iisHelp

wmic useraccount where name='administrator' call Rename test
net user dpan 123456 /add /passwordchg:yes /expires:never
net localgroup "Remote Desktop Users" dpan /add
net user epan 654321 /add /passwordchg:yes /expires:never
net localgroup "Remote Desktop Users" epan /add
net localgroup Users dpan /del
Net user epan 654321

Cacls D:\ /e /c /d epan
Cacls C:\ /t /e /c /g epan:f
Cacls E:\ /t /e /c /g epan:f
Cacls C:\ /e /c /d dpan
Cacls D:\ /t /e /c /g dpan:f
Cacls E:\ /e /c /d dpan
Cacls C:\ /e /c /d everyone
Cacls D:\ /e /c /d everyone
Cacls E:\ /e /c /d everyone
Cacls C:\ /e /c /r everyone
Cacls D:\ /e /c /r everyone
Cacls E:\ /e /c /r everyone

del %systemdrive%\diskpart.cmd
del %systemdrive%\diskpart.txt
del %systemdrive%\%0

eaccelerator真的能加速你的php吗?

Posted: 2009年8月3日星期一
经常看到很多介绍安装php的文章中,提到安装一个eaccelerator加速php程序,我按照他们的需求安装了,确实能用了,可是经过我的压力测试,发现他确实没有实现那些文章所谓的加速功能,是我配置错了,还是具体哪有问题,下面介绍一下我的配置情况,对于php的安装,搜索引擎一搜索网上一大堆,我就不累赘了,现在说说我的安装和eaccelerator的过程
php安装软件及版本
nginx/0.8.7
php-5.2.8
eaccelerator-0.9.5.3
eaccelerator安装

/usr/local/bin/phpize
./configure --enable-eaccelerator=shared --with-php-config=/usr/local/php/bin/php-config
make
sudo make install

eaccelerator配置
分成两块,一块是采用eaccelerator加速的就在php.ini文件中加入如下内容

[eaccelerator]
extension_dir = "/usr/local/lib/php/extensions"
extension="/no-debug-non-zts-20060613/eaccelerator.so"
eaccelerator.shm_size="16"
eaccelerator.cache_dir="/tmp/eaccelerator_cache"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

添加cache目录

mkdir /tmp/eaccelerator_cache

另一块就是初始化安装不配置任何参数进行测试
eaccelerator加速测试
ab -c 200 -n 10000 http://www.test.com/phpinfo.php
测试结果

Server Software: nginx/0.8.7
Server Hostname: www.test.com
Server Port: 80

Document Path: /test.php
Document Length: 25 bytes

Concurrency Level: 200
Time taken for tests: 1.523543 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Non-2xx responses: 10000
Total transferred: 2160000 bytes
HTML transferred: 250000 bytes
Requests per second: 6563.65 [#/sec] (mean)
Time per request: 30.471 [ms] (mean)
Time per request: 0.152 [ms] (mean, across all concurrent requests)
Transfer rate: 1384.27 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.8 0 15
Processing: 4 22 7.9 24 67
Waiting: 1 18 9.7 20 66
Total: 6 23 7.4 24 67

Percentage of the requests served within a certain time (ms)
50% 24
66% 24
75% 24
80% 25
90% 31
95% 32
98% 41
99% 61
100% 67 (longest request)

不采用eaccelerator加速测试
ab -c 200 -n 10000 http://www.test.com/phpinfo.php
测试结果

Server Software: nginx/0.8.7
Server Hostname: www.test.com
Server Port: 80

Document Path: /test.php
Document Length: 25 bytes

Concurrency Level: 200
Time taken for tests: 1.373767 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Non-2xx responses: 10011
Total transferred: 2162376 bytes
HTML transferred: 250275 bytes
Requests per second: 7279.25 [#/sec] (mean)
Time per request: 27.475 [ms] (mean)
Time per request: 0.137 [ms] (mean, across all concurrent requests)
Transfer rate: 1536.65 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 2 3.0 2 21
Processing: 4 24 11.4 23 94
Waiting: 2 17 12.9 15 93
Total: 5 26 11.1 26 94

Percentage of the requests served within a certain time (ms)
50% 26
66% 29
75% 31
80% 31
90% 32
95% 34
98% 83
99% 92
100% 94 (longest request)


结果说明:
通过ab的测试,从结果来看(只需要看黑体部分的内容就OK了),Requests per second(每秒事务数),Time per request(平均事务响应时间),Time per request(每个请求实际运行时间的平均值),这3个值在不采用eaccelerator加速的情况下,性能更强
总结:
不知是我的配置和测试的问题?还是在新的php版本中eaccelerator确实出现兼容性问题?

mysql数据类型区别之BLOB与TEXT

Posted: 2009年7月17日星期五
当数据量比较小时我们会选择使用占用空间相对较小的char或varchar数据类型,当数据量相对较大时通常会选择blob或text数据类型来存储他们。他们都可以存储可变数量的文本数据,在TEXT或BLOB列的存储或检索过程中,不存在大小写转换。两者的区别在于,blob能保存存储二进制数据,比如相片,blob也没有字符集概念,并且排序和比较基于列值字节的数值值。TEXT列有一个字符集,并且根据字符集的校对规则对值进行排序和比较。有4种blob类型,分别为:TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。它们只是可容纳值的最大长度不同。text类型也有相对应的4种数据类型,分别为:TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT。在大多数方面,可以将BLOB列视为能够足够大的VARBINARY列。同样,可以将TEXT列视为VARCHAR列。BLOB和TEXT在以下几个方面不同于VARBINARY和VARCHAR:
1、当保存或检索BLOB和TEXT列的值时不删除尾部空格。(这与VARBINARY和VARCHAR列相同)。
2、请注意比较时将用空格对TEXT进行扩充以适合比较的对象,正如CHAR和VARCHAR。
3、对于BLOB和TEXT列的索引,必须指定索引前缀的长度。对于CHAR和VARCHAR,前缀长度是可选的。
4、BLOB和TEXT列不能有 默认值。
当对blob和text数据类型执行大量删除操作时存在很到的性能问题,删除操作能给数据表格留下很大的的空洞,以后填入这些空洞的插入操作存在性能问题,下面通过一个具体事例来说明这个空洞问题。

mysql> create table t(id varchar(100), context text);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into t values(1, repeat('beijing', 100)),(2, repeat('beijing', 100)),(3, repeat('beijing', 100));
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

再连续执行如下语句16次

mysql> insert into t select * from t;
Query OK, 196608 rows affected (1.20 sec)
Records: 196608 Duplicates: 0 Warnings: 0

退出mysql,在操作系统下查看mysql数据文件夹下的,t.MYD文件大小

[root@backup ~]# du -sh /usr/local/mysql/data/test/t*
12K /usr/local/mysql/data/test/t.frm
268M /usr/local/mysql/data/test/t.MYD
4.0K /usr/local/mysql/data/test/t.MYI

从上面看到数据文件大小为268M。
进入mysql中删除id为1的数据,通过以上数据插入操作,我们知道id为1的数据应该为总数据的1/3。

mysql> delete from t where id=1;
Query OK, 131072 rows affected (1.11 sec)

退出mysql,继续在操作系统下查看mysql数据文件夹下的,t.MYD文件大小

[root@backup ~]# du -sh /usr/local/mysql/data/test/t*
12K /usr/local/mysql/data/test/t.frm
268M /usr/local/mysql/data/test/t.MYD
4.0K /usr/local/mysql/data/test/t.MYI

从上面可以看到数据大小并没有发生变化,这就产生了上面所说的空洞,接下来通过optimize table对表进行碎片整理,避免空洞造成的性能问题

mysql> optimize table t;
+--------+----------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------+----------+----------+----------+
| test.t | optimize | status | OK |
+--------+----------+----------+----------+
1 row in set (0.99 sec)

再次退出mysql,在操作系统下查看mysql数据文件夹下的,t.MYD文件大小

[root@backup ~]# du -sh /usr/local/mysql/data/test/t*
12K /usr/local/mysql/data/test/t.frm
179M /usr/local/mysql/data/test/t.MYD
4.0K /usr/local/mysql/data/test/t.MYI

可以发现表的数据文件变小了,说明空洞所占领的空间已经被回收。对于采用了blob字段或text字段的数据表格,建议定期对表格进行optimize table操作,避免因空洞带来的性能问题。
由于text和blob数据量大,对于text和blob数据类型的检索,一般不建议直接采用mysql对他们进行检索。同时建议吧text和blob数据列分离到单独的表中,这样避免text或blob数据删除所带来的性能问题。

mysql数据类型区别之char与varchar

Posted: 2009年7月15日星期三
也许在使用mysql创建数据表时经常有这样的疑问,是选择char还是varchar,text还是blob数据类型呢?其实选择什么都行,都能完成我们的需求,但是,选择好合适的数据类型不仅能节约磁盘空间还能提高访问速度达到mysql调优的目的。
1、char和varchar
char和varchar都能存储字符串,其中char是固定长度,varchar是可变长度字符类型。下面通过一个实例来来说明两者的数据存储的区别

mysql> create table c(c char(4), v varchar(4));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into c values('ab ', 'ab ');
Query OK, 1 row affected(0.00 sec)

通过在mysql函数cancat,在字符的末尾添加字符的功能可以看出他的存储情况

mysql> select concat(c, '+'), concat(v, '+') from c;
+----------------+----------------+
| concat(c, '+') | concat(v, '+') |
+----------------+----------------+
| ab+ | ab + |
+----------------+----------------+
1 row in set (0.00 sec)

从上可以看出在mysql检索时通常会删除掉char数据类型的空格。
由于char是固定长度,所以他的处理速度比varchar要快得多,但是他比较消耗存储空间,程序还得考虑他末尾的空格问题对于那些长度变化不大,并且对查询速度有较高要求的数据可以考虑使用char数据类型来存储。
下面来说说在不同的存储引擎条件下两者的使用原则
1、myisam存储引擎:建议使用固定长度的char数据类型来代替可变长度的varchar数据类型
2、memory存储引擎:都使用固定长度的数据行存储,因此使用char或varchar都没有关系,两者都被当作char数据类型进行处理
3、innodb存储引擎:建议使用可变的varchar数据类型,理由:对于innodb数据表,内部的行存储格式没有区分固定长度和可变长度列(所有的数据行都指向数据列值的头指针),因此使用固定长度的char数据类型不一定比使用可变数据长度的数据类型性能好,因而,主要的性能因素是数据行使用的存储总量,由于char数据列占用的空间比varchar数据列要大,因此varchar存储的空间要小,对于磁盘io来说越小的空间,处理数据能力越强。

putty vs SecureCRT

Posted: 2009年5月15日星期五
作为一名linux的sa,每天都会跟ssh终端打交道,选择配置好一个优秀的ssh终端工具对提高工作效率有很大的帮助。一般情况下有两种选择,一种是免费的putty和收费的SecureCRT。最初我选择的是免费putty,当我管理几台服务器时,确实是比较好的一种选择putty,不会记住用户名,密码,安全性好。在没有配置的情况下,putty的显示效果要比SecureCRT好。再者文件小,功能齐全,而且不需要安装,可以随时的打开使用。当我管理的不是几台linux服务器,而是几十台服务器后发现就不好了,每次都要输入IP地址,打开多台服务器时,不能作为tab显示,结果显示的窗口过多,当打开多个窗口是在任务栏上显示的标题不能标示对应打开的服务器,而是需要打开每个窗口去确认,确实不方便。而采用SecureCRT就没有这些问题。同时通过配置SecureCRT能够基本达到putty的显示效果,下面是我的配置SecureCRT的参数
1,配置终端类型,显示颜色
Options->SessionOptions ->Emulation,然后把Terminal类型改成xterm,并点中ANSI Color复选框。
2,配置字体,编码方式
字体设置:Options->SessionOptions->Appearance->font然后改成新宋体,文字大小设置为11号。
character改成utf-8的方式。
3,去掉显示的下划线
Options->SessionOptions->Appearance->Current color选择编辑,在新打开的窗口去掉show underline
4,vi中颜色显示效果
在打开的终端中,编辑/etc/profile 在文末添加如下内容
export TERM=xterm-color
添加完毕后执行如下内容,使之生效
source /etc/profile
同时用vi打开文件转入命令形式,输入如下命令
syntax on
如果是c语言的文件就接着输入set filetype=c,如果是jsp的话就输入,set filetype=jsp,对shell语言的话,不设置也能颜色显示,其它同理。就完成终端vi的颜色显示,不过遗憾的是对vi颜色显示支持不是很好,需要配置很多的系统参数。

采用tokyo cabinet搭建表格型DBM

Posted: 2009年5月12日星期二
DBM(DataBase Manager)的特征是:按键-值对的方式进行存储,使用hash进行查找,由于没有提供更高级的sql功能,它的效率远远超过传统的关系型数据库。现主流的dbm有:Berkeley DB(现属于oracle公司,是互联网部署最多的一款dbm),Gdbm(GNU Database Manager) QDBM: Quicker Database Manager,Tokyo cabinet,CDB:Constant Databases,Tokyo cabinet是基于QDBM再开发的一款DBM,它本身也只提供数据库引擎,所谓数据库引擎是指它提供了除SQL处理层以外的所有关系数据库的功能(如事务性(ACID)支持,数据存储,数据恢复等)。前面提到dbm的特征是一些键-值对的形式出现,那么Tokyo Cabinet作为一款dbm,它也不例外,怎么样才能实现跟普通的关系数据库一样的表格功能呢?Tokyo Cabinet的作者开发了一款Tokyo Tyrant的工具,可以采用它调用Tokyo Cabinet数据库引擎,实现跟管理数据库一样的数据库表格的功能,下面通过一个普通的员工信息表进行演示说明
表格内容如下

在普通的关系数据库mysql中建立这个表格的sql语句如下
create table staffs (
id int primary key,
name varchar(255),
sex enum ( 'male', 'female' ),
birthday date,
div varchar(255)
);

而根据上面所说的按键-值对方式进行存储,那么这样想的话,那不是得分成,几个db,分别是名字db,性别db,出生年月db和职务db,那这样的话表格的逻辑不是乱了吗!如果这样的话,估计没有几个人用,下面介绍采用tokyo tyrant的命令就可以做成这个表格,实现与普通关系型数据库提供的sql使用方式。
建立数据库
tctmgr create casket
插入数据

tctmgr put casket 1 "name" "张三" "sex" "male" \
"birthday" "19900101" "div" "brd,dev"
tctmgr put casket 5 "name" "李四" "sex" "female" \
"birthday" "19850523" "div" "dev"
tctmgr put casket 23 "name" "王五" "sex" "male" \
"birthday" "19881013" "div" "hr"

查询所有数据
tctmgr list -pv casket
--------
1 name 张三 sex male birthday 19900101 div brd,dev
5 name 李四 sex female birthday 19850523 div dev
23 name 王五 sex male birthday 19881013 div hr

条件查询
通过主键进行查询,查询用户id为23的职员信息
tctmgr get casket 23
--------
name 李四
sex female
birthday 19850523
div dev

通过不是主键进行查询

#查询以王开头的用户
tctmgr search -pv casket name STRBW "王"
-------
23 name 王五 sex male birthday 19881013 div hr
#查询以85年出生以后的男职员
tctmgr search -pv casket birthday NUMGE 19850101 sex STREQ "male"
--------
1 name 张三 sex male birthday 19900101 div brd,dev
23 name 王五 sex male birthday 19881013 div hr

附查询条件表达式(前面加~,相当于标准sql中的非)
* STREQ : 完全包含字符串,相当于标准sql中的where 字段=‘字符串’
* STRINC : 包含此字符串,相当于标准sql中like ‘*字符串*’
* STRBW : 以此字符串开头的内容,相当于标准sql中like ‘字符串*’
* STREW : 以此字符串结尾的内容,相当于标准sql中like ‘*字符串’
* STRAND : 包含在某区间内的内容,如标准sql中like ‘a-z’,那么他只能是a到z的字母
* STROR : 不包含在某区间内的内容,如标准sql中like ‘!a-z’,那么他只能是数字或其他的符号
* STRRX : 组合式结构,如标准sql中like “a“!b-m”#”
* NUMEQ : 数值大小一样 相当于标准sql中 where a=‘1’
* NUMGT : 数值大于某一值 相当于标准sql中 where a>‘1’
* NUMGE : 数值大于等于某一值 相当于标准sql中 where a>=‘1’
* NUMLT : 数值小于某一值 相当于标准sql中 where a<‘1’
* NUMLE : 数值小于某一值 相当于标准sql中 where a<=‘1’
* NUMBT : 数值处于某一数值区间,相当于标准sql中 where a> 1 and a < 10
* NUMOREQ : 数值不处于某一数值的区间,如果是a大于1,小于10的话,那这个表达式右边的内容应该相当于标准sql中 where a< 1 or a > 10
附排序
* STRASC : 按字面大小的升序排序
* STRDESC : 按字面大小的降序排序
* STRASC : 按数字大小的升序排序
* STRDESC : 按数字大小的降序排序
删除操作

tctmgr out casket 23
#确认结果
tctmgr list -pv casket
--------
1 name 张三 sex male birthday 19900101 div brd,dev
5 name 李四 sex female birthday 19850523 div dev

服务器故障[原创]

Posted: 2009年5月11日星期一
今天下午放在idc机房的机器无缘无故的就down掉了,打电话给机房的人要求重启,并要求告知原因,机房人告知机器没有打开,我把服务器上的日志分析了半天,发现没有任何down掉了日志,排除了机器故障,而处在同一个机柜里的服务都是好的,排除了供电故障,那么只有一种可能就是有人人为的把我的服务器的电源给拔掉了。我严重鄙视做这种事情的人。

web站点优化的理解[原创]

Posted: 2009年5月10日星期日
在网上看到很多文章在介绍web站点的优化方案,确实有作用,但都是片面的单独从一个角度去写的一个优化方案,很少有人比较全面的写一个优化方案,几乎都是要不从web页面上讲优化的条条款款,要不单独的从服务器上讲优化的条条款款,要不从db上写优化的条条款款。但这些优化方案局限性很大,很少有文章能提到一个全面的优化方案,下面是我自己所理解的一点web站点优化方案,个人认为前端优化是最重要的。当你的一个页面很大,到了一个页面2M,你服务器端优化做得再好,也是空的,你能及时的响应用户的请求,但是国内的用户现在的一个带宽情况你应该知道,光打开一个也面要多久时间,同时你服务器端的带宽是多少,带宽能承受得多少个用户同时并发,算算就知道了。再如果一个页面链接请求几百个,你服务器端优化得再好,承受的并发单台也只能做到不到十万个并发连接,同时浏览器还有对同一域名的并发限制,多域名的话,查询dns也是需要时间的。而国内做前端做得好的不是很多,包括alex排名非常高的各大网站(去掉google,baidu,页面元素太少,它们也确实做得很好),几乎前端做得很烂,真正做得好的是美国yahoo网站,大家可以去看看他的做法。yahoo工程师写了web前端优化的34条建议可以去看看,同时通过yahoo工程师写的firefox插件yslow去分析优化结果及优化建议。
再一个就是后端db的优化,因为db的连接是有限的,当数据量达到一定得规模,及出现db大并发时,db应该也是一个最慢的,当一个不合理的sql,很有可能就导致你网站的性能大打折扣,前端的web服务器优化得再好,也是空的,后端的db反应不过来,前端永远等待后端的结果,最好的做法就是少用db,能静态化页面,尽量静态化,能把数据缓存到cache中尽量缓存到cache中,优化sql链接,优化sql语句,当数据达到一定得数量级就分库,分表等操作。
第三个就是服务器的优化,这个做好,能基本提高大的并发,优化的方法应该用压力工具压(如:ab,webbench)几个简单的测试页面,进行调优,不是水平特别菜的sa的话,通过搜索引擎去查找别人的方案,依葫芦画瓢,基本上能达到一个不是很坏的结果。
再往后优化的话就是从架构上去做服务器的优化,不在本文讨论范围之类,这基本是包括最基本的优化方案,也是大部分站点能做的事情。做了以上的优化为老板节省了服务器及带宽资源。同时也使sa的工作量变少,因为管理的服务器越少,花在服务器维护的时间就越少,节省出来的时间可以做更多有意义的事情。
附yahoo的调查结果,后台只占5%,而前端高达95%之多,其中有88%的东西是可以优化的。

yslow更新到2.0[原创]

Posted: 2009年5月8日星期五
今天早上启动firefox发现yslow更新了新的版本,做为一个前端页面分析的一个好工具,更新后,界面变了,功能也变强了,首先在grad选项卡上,优化的内容增多了,以前是13条,现在又增加了,增加到22个,以前只是一个简单的列表,现在对各个选项进行分类,这样的话,就能对开发人员更明确,要优化的地方在那。评分选项上,各个参数如果不是A的话,会列出各个选项中做得不好的内容,不像以前点击后直接到yahoo deverloper的建议页面,现在得出的结果更明确。componets选项上,以前只是做一个列表。能做各个选项进行一个排序,现改成了分类列出,对于css中的图片,以前也是作为一个图片列表,不能很好明白这个图片到底出现在那,现在就能更仔细的得出结果,同时多了cookie发送和接收大小的列,headers显示及expires的列,每个文件响应时间及etage,action选项,功能更强,显示更好。statistics变化不大,准确的说,到现在还没有发现有变化在那。对于tools选项功能不多,值得一提的是有一个叫all smush.it的小工具,它是一个在线批量压缩图片工具,支持批量压缩图片,在无损图片质量的前提下达到图片大小的最优化压缩。压缩完毕之后下载zip格式压缩包,解压后便可以使用图片了。

为php添加pdo_oci模块的几种方法[原创]

Posted: 2009年5月7日星期四
最近公司的一部分开发决定采用框架进行开发,需要php_oci的php扩展模块。比较遗憾的最新的pdo_oci的版本还是05年开发的,安装过程如不注意会有比较多的问题,本人总结了一下它的几种安装方法,写下来进行备忘。
第一种就是在编译时添加--with-pdo-oci参数,这种方式的话,如果是刚开始搭建php环境的话应该是一种比较好的一种方式,具体安装步骤如下
首先安装oracle客户端,安装方法请查看我以前写的"Linux下编译安装PHP的oracle扩展",
值得注意的是,如果你采用rpm包安装的是64位的客户端的话,必须得为oracle客户端的include建立一个软连接,具体建立方法如下

ln -s /usr/lib/oracle/10.2.0.4/client64 /usr/lib/oracle/10.2.0.4/client

ln -s /usr/include/oracle/10.2.0.4/client64 /usr/include/oracle/10.2.0.4/client

再就是在编译时添加如下编译选项进行编译安装
--with-pdo-oci=instantclient,/usr,10.2.0.4
具体说明一下,instantclient即oracle的instant client的意思,/usr 就是oracle instant client安装目录,10.2.0.4 是oracle instant client版本,默认情况下如果写成这样的话,它会自动到/usr/include/oracle/10.2.0.4/client和/usr/lib/oracle/10.2.0.4/client查找依赖库文件,如果安装的64位的oracle instant client的话就必须得建立上面的一个软链接,不然就找不到所依赖的oci.h头文件而报错。其它的也就是跟普通的php安装一样,不需要任何特别的操作就能安装好php的PDO_OCI的扩展。
第二种方式即通过php的pear执行安装,这种适合于已经部署好了php环境,需要添加一个pdo_oci的扩展
如果在编译过程中没有添加--enable-sigchild参数的话,安装起来应该是最快的,最简单,最灵活。如果编译时添加了此参数的话,这种方式就不适合你,就别浪费时间了,安装时会报错。安装方法如下
添加oracle环境
export ORACLE_HOME=/usr/lib/oracle/10.2.0.4/client64/lib
添加oracle instant client的头文件的软链接
for i in /usr/include/oracle/10.2.0.4/client64/*;do;ln -s $i /usr/include;done
下载PDO_OCI进行安装
curl -O http://pecl.php.net/get/PDO_OCI-1.0.tgz
pear install PDO_OCI-1.0.tgz

在php.ini文件当中修改extension的路径及添加pdo_oci的扩展,
将extension_dir = "./"修改为extension_dir = "/usr/local/webserver/php/lib/php/extensions/no-debug-non-zts-20060613/"同时在它后面添加如下内容
extension=pdo_oci.so
重新启动http服务器即完成pdo_oci的安装,如不需要了,可以通过直接执行
pear uninstall PDO_OCI进行卸载
第三种安装方法直接编译pdo_oci的扩展包
安装前,如果你采用rpm包安装的是64位的客户端的话,必须得为oracle客户端的include建立一个软连接,具体建立方法如下

ln -s /usr/lib/oracle/10.2.0.4/client64 /usr/lib/oracle/10.2.0.4/client

ln -s /usr/include/oracle/10.2.0.4/client64 /usr/include/oracle/10.2.0.4/client

下载PDO_OCI进行安装
curl -O http://pecl.php.net/get/PDO_OCI-1.0.tgz
tar xf PDO_OCI-1.0.tgz
cd PDO_OCI-1.0
/usr/local/php/bin/phpize
./configure --with-pdo-oci=instantclient,/usr,10.2.0.4 --with-php-config=/usr/local/php/bin/php-config
make
make install

在php.ini文件当中修改extension的路径及添加pdo_oci的扩展,
将extension_dir = "./"修改为extension_dir = "/usr/local/webserver/php/lib/php/extensions/no-debug-non-zts-20060613/"同时在它后面添加如下内容
extension=pdo_oci.so
重新启动http服务器即完成pdo_oci的安装.
以上几种安装方法,安装完成后通过以下两种方式可以检验是否安装成功
安装完成执行php -m应该可以查看到PDO_OCI这么一行文字,表示安装成功,或通过phpinfo可以看到如下图所示的内容


表示安装成功。至此就是我总结的几种安装方法。