2016年12月

linux IO编程基本知识

一、标准C的I/O和

FILE结构体

typedef struct iobuf{
int cnt; /*剩余的字节数*/
char *ptr; /*下一个字符的位置*/
char *base; /*缓冲区的位置*/
int flag; /*文件访问模式*/
int fd; /*文件描述符*/
}FILE;

便准c的IO都是有缓存的,先把要操作的内容放到缓存中,最后在进行io的操作,譬如把某个字串读取到变量中,或者写数据到文件,可以查看 http://jinblog.com/archives/810.html 这篇文章的详细介绍

标准C的IO缓存类型

1、全缓存

要求填满整个缓存区后才进行I/O 系统调用操作。对于磁盘文件通常使用全缓存访问。

2、 行缓存

1)涉及一个终端时(例如标准输入和标准输出),使用行缓存。
2)行缓存满自动输出
3)碰到换行符自动输出

3、 无缓存

标准错误流stderr 通常是不带缓存区的,这使得错误信息能够尽快地显示出来。

下面的例子简单说明

#include <stdio.h>

int main()
{
        // 如下,执行程序是看不到输出内容的
        printf("Hello world!!!");
        // 加入 \n 是能看到内容的,因为标准输出是行级缓存的
        // printf("Hello world!!!\n");
        // 也可以使用fflush函数刷新输出缓冲,也能看到输出内容
        //fflush(stdout);
        while(1)
        {   
                sleep(1);
        }   
        return 0;
}

linux系统编程之基础必备(四):C 标准库IO缓冲区和内核缓冲区的区别

1.C标准库的I/O缓冲区
   UNIX的传统 是Everything is a file,键盘、显示器、串口、磁盘等设备在/dev 目录下都有一个特殊的设备文件与之对应,这些设备文件也可以像普通文件(保存在磁盘上的文件)一样打开、读、写和关闭,使用的函数接口是相同的。用户程序调用C标准I/O库函数读写普通文件或设备,而这些库函数要通过系统调用把读写请求传给内核 ,最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE 结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc / fputc 为例,当用户程序第一次调用fgetc 读一个字节时,fgetc 函数可能通过系统调用 进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指 向I/O缓冲区中的第二个字符,以后用户再调fgetc ,就直接从I/O缓冲区中读取,而不需要进内核 了,当用户把这1K字节都读完之后,再次调用fgetc 时,fgetc 函数会再次进入内核读1K字节 到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在“Memory Hierarchy”中 CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放 在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接 从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc 通常只是写到I/O缓 冲区中,这样fputc 函数可以很快地返回,如果I/O缓冲区写满了,fputc 就通过系统调用把I/O缓冲 区中的数据传给内核,内核最终把数据写回磁盘或设备。有时候用户程序希望把I/O缓冲区中的数据立刻 传给内核,让内核写回设备或磁盘,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件 之前也会做Flush操作。

我们知道main 函数被启动代码这样调用:exit(main(argc, argv));。

main 函数return时启动代码会 调用exit ,exit 函数首先关闭所有尚未关闭的FILE *指针(关闭之前要做Flush操作),然后通 过_exit 系统调用进入内核退出当前进程.

C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时, 不同类型的缓冲区具有不同特性。

  • 全缓冲

如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。

  • 行缓冲

如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内 核。标准输入和标准输出对应终端设备时通常是行缓冲的。

  • 无缓冲

用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。

   除了写满缓冲区、写入换行符之外,行缓冲还有两种情况会自动做Flush操作。如果: 

用户程序调用库函数从无缓冲的文件中读取
或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据

  如果用户程序不想完全依赖于自动的Flush操作,可以调fflush函数手动做Flush操作。 

#include <stdio.h>
int fflush(FILE *stream);
返回值:成功返回0,出错返回EOF并设置errno
fflush函数用于确保数据写回了内核,以免进程异常终止时丢失数据,如fflush(stdout); 作为一个特例,调 用fflush(NULL)可以对所有打开文件的I/O缓冲区做Flush操作。

2. 用户程序的缓冲区
   在函数栈上分配的如char buf[10];之类的缓冲区, strcpy(buf, str);  str 所指向的字符串有可能超过10个字符而导致写越界,这种写越界可能当时不出错, 而在函数返回时出现段错误,原因是写越界覆盖了保存在栈帧上的返回地址, 函数返回时跳转到非法地址,因而出错。像buf 这种由调用者分配并传给函数读或写的一段内存通 常称为缓冲区(Buffer),缓冲区写越界的错误称为缓冲区溢出(Buffer Overflow)。如果只是出 现段错误那还不算严重,更严重的是缓冲区溢出Bug经常被恶意用户利用,使函数返回时跳转到一 个事先设好的地址,执行事先设好的指令,如果设计得巧妙甚至可以启动一个Shell,然后随心所欲 执行任何命令,可想而知,如果一个用root 权限执行的程序存在这样的Bug,被攻陷了,后果将很 严重。

  下图以fgets / fputs 示意了I/O缓冲区的作用,使用fgets / fputs 函数时在用户程序中也需要分配缓冲 区(图中的buf1 和buf2 ),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。
3.内核缓冲区
  • 终端缓冲

终端设备有输入和输出队列缓冲区,如下图所示

以输入队列为例,从键盘输入的字符经线路规程过滤后进入输入队列,用户程序以先进先出的顺序 从队列中读取字符,一般情况下,当输入队列满的时候再输入字符会丢失,同时系统会响铃警报。 终端可以配置成回显(Echo)模式,在这种模式下,输入队列中的每个字符既送给用户程序也送给 输出队列,因此我们在命令行键入字符时,该字符不仅可以被程序读取,我们也可以同时在屏幕上 看到该字符的回显。
注意上述情况是用户进程(shell进程也是)调用read/write等unbuffer I/O函数的情况,当调用printf/scanf(底层实现也是read/write)等C标准I/O库函数时,当用户程序调用scanf读取键盘输入时,开始输入的字符都存到C标准库的I/O缓冲区,直到我们遇到换行符(标准输入和标准输出都是行缓冲的)时,系统调用read将缓冲区的内容读到内核的终端输入队列;当调用printf打印一个字符串时,如果语句中带换行符,则立刻将放在I/O缓冲区的字符串调用write写到内核的输出队列,打印到屏幕上,如果printf语句没带换行符,则由上面的讨论可知,程序退出时会做fflush操作.

  • 虽然write 系统调用位于C标准库I/O缓冲区的底 层,被称为Unbuffered I/O函数,但在write 的底层也可以分配一个内核I/O缓冲区,所以write 也不一定是直接写到文件的,也 可能写到内核I/O缓冲区中,可以使用fsync函数同步至磁盘文件,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别 的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的, 而c标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的.

  • 为了减少读盘次数,内核缓存了目录的树状结构,称为dentry(directory entry(目录下项) cache

  • FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道,各进程可以打开这个文件进行read / write ,实际上是在读写内核通道(根本原因在于这个file 结构体所指向的read 、write 函数 和常规文件不一样),这样就实现了进程间通信。UNIX Domain Socket和FIFO的原理类似,也需 要一个特殊的socket文件来标识内核中的通道,文件类型s表示socket,这些文件在磁盘上也没有数据块。UNIX Domain Socket是目前最广泛使用 的IPC机制.如下图:

  • stack overflow 无穷递归或者定义的极大数组都可能导致操作系统为程序预留的栈空间耗尽 程序崩溃(段错误)

转载自 http://www.educity.cn/linux/1241891.html

Linux操作系统的构成

一、Linux操作系统的构成

####### 1、内核
1) 操作系统的核心,负责管理系统的进程、内存、设备驱动程序、文件和网络系统
2) 控制系统和硬件之间的相互通信
3) 决定着系统的性能和稳定性。
####### 2、Shell
####### 3、文件系统
####### 4、应用程序

二、Linux操作系统的基本概念

####### 1、文件和文件系统
1) 文件:数据或设备的一种逻辑组织
2) 文件系统:文件间关系管理的一种逻辑组织
####### 2、程序和进程
1) 程序: 计算机执行的指令集和
2) 进程: 程序的一个运行实例,操作系统资源分配的最小单位
####### 3、线程(轻量级进程)
程序运行的基本单位,一个进程内部可以有一或若干线程同时运行
####### 4、信号
Linux系统中进程 通信的一种技术,异步程序设计的基础

三、Linux操作系统的启动流程
  1. 芯片和部分外围电路的初始化
  2. 加载内核
[[email protected] ~]# file /boot/vm*
/boot/vmlinuz-2.6.32-504.el6.x86_64:     Linux kernel x86 boot executable bzImage, version 2.6.32-504.el6.x86_64 (mockbuil, RO-rootFS, swap_dev 0x3, Normal VGA
  1. 加载最小文件系统
  2. 加载硬盘上的根文件系统
  3. 启动1号进程/sbin/init(0号为内核进程swapper进程),处理如下工作:
  4. 执行/etc/init.d目录中的所有脚本文件,启动某些系统的服务
  5. 执行/sbin/getty 初始化0、1和2(标准输入、标准输出和标准错误)
  6. 执行/bin/login启动用户登录程序
  7. 管理孤儿进程
四、BootLoader(uboot)
  • BootLoader就是在操作系统内核运行之前运行的一段小程序
  • 初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
  • 严重依赖于硬件,在嵌入式开发中里建立一个通用的BootLoader几乎是不可能的,在Linux中称为grub。

Linux内存管理

一、Linux内存管理

程序所操作的都是虚拟内存,系统分配的都是虚拟内存空间
####### 1、进程隔离
保护独立的进程,防止互相干涉数据和存储空间
####### 2、自动分配和管理
动态地分配,分配对程序员是透明的
####### 3、支持模块化的程序设计
能够定义程序模块,并且动态地创建、销毁模块,改变模块大小
####### 4、保护和访问控制
允许一部分内存可以由各种用户以各种方式进行访问
####### 5、长期存储
关机后长时间保存信息

二、段页式内存管理

####### 1、进程在虚拟内存中分为代码段、数据段和堆栈段
数据段分为
+ 静态存储区,分为初始化区和为初始化区(bss),存放全局变量和局部变量
+ 常量区,常量
####### 2、进程在段中有许多固定大小的块组成,这些块成为页
####### 3、虚拟地址由段号、页号和页中偏移量构成
####### 4、虚地址和贮存中实地址(物理地址)的动态映射。
####### 5、缺页
1)消除了进程全部载入内存中
2)按需调页

系统调用

一、系统调用

所谓系统调用是指操作系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务。

二、Linux进程的运行状态

####### 1、内核态
进程运行在内核空间
####### 2、用户态
进程运行在用户空间

三、IEEE POSIX标准
  1. 美国电气和电子工程师协会(IEEE)是一个国际性的电子技术与信息科学工程师的协会
  2. 在Linux中用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准—POSIX标准。这些系统调用编程接口主要通过C库(libc)实现的。

经常做一些linux下面的工作,想更深入的了解下linux下程序是怎么运行的,通过这个教程了解下

通向架构师的道路(第三天)之apache性能调优

总结前一天的学习

在前两天的学习中我们知道、了解并掌握了Web Server结合App Server实现单向Https的这样的一个架构。这个架构是一个非常基础的J2ee工程上线布署时的一种架构。在前两天的教程中,还讲述了Http服务器、App Server的最基本安全配置(包括单向https的实现), 它只是避免了用户可以通过浏览器侵入我们的Web访问器或者能够通过Web浏览器来查询我们的Web目录结构及其目录内的文件与相关内容,这种入侵我们把它称为:
Directory traversal,当然我们只是实现了最基本的防范Directory traversal的手段,在日后的Security课程中将会详细地去擅述完整的Web Security的相关理论。
从今天起我们将继续在原有的这种Apache+Tomat的架构上,去论述如何在性能及Performance上优化这个架构,因此这两天的课程在有些人看来,可能会有些“枯燥”,所以我在此给大家打个招呼:
这两天的课程论述的是如何在不改动代码与SQL语句的前提下,如何去改善和提高web server与app server的性能,千万不要小觑这一内容,它可以让你在不改动代码的情况下得到10-20倍以上的性能提高,网上有其它的大牛们写过一篇文章叫“Tomcat如何支持到1000个用户”,经本人经过几个重大工程的实践,Opensource的Tomcat如果调优的好不只可以支持者1000个用户,尤其当你的布署环境是64位操作系统的情况下,可能能够支持更大更高的并发性能,最后本节内容将会以Tomcat集群来做收场,在将来的课程中还会进一步详细讲述Weblogic的集群配置与IBM WASND的集群配置。

二、从性能测试谈起

2.1 性能测试简介

即压力测试,就是根据一定数量的VU(Virtual Users)我称为并发用户操作核心交易后,系统所能达到的最大瓶劲,以便于发现系统的极限、有没有Outof memory这样的问题存在以及相关的系统设置、配置是否搭挡的合理的一种测试。
一般商业的比较好的用LoaderRunner,如果没钱的就用Opensource的Jmeter来模拟这个VU的操作。
压力测试,存在几个误区,需要小心。
1) 无限大的拼命增加VU的数量
系统再完美,硬件配置再高,也经不住没有经过合理运算的VU的压力呀。
2) 偏执的用一定的数据量的VU,跑7

24小时 不是说这个没必要,很有必要,小日本的电视为什么寿命敢说比中国人生产的电视机寿命长?因为它用一个机械臂就对着电视机的按钮不断的点点点。 我们说的压力测试要测试多长时间,关键是要看经过科学计算的VU的数量以及核心交易数有多少,不是说我拿250个VU跑24

7如果没有问题我这个系统就没有问题了,这样的说法是不对的,错误的。随便举个例子就能把你推倒。
假设我有250个VU,同时跑上万笔交易,每个VU都有上万笔交易,250个VU一次跑下来可能就要数个小时,你又怎么能断定250个VU对于这样的系统我跑24

7小时就能真的达到上万笔交易在250个VU的并发操作下能够真的跑完7天的全部交易?可能需要一周半或者两周呢?对吧? 我还看到过有人拿500个VU对着一条交易反复跑24

7小时。。。这样的测试有意义吗?你系统就仅仅只有一条交易?你怎么能够判断这条交易涉及到的数据量最大?更不用说交易是彼此间有依赖的,可能a+b+c+d的交易的一个混合组织就能够超出你单笔交易所涉及到的数据量了呢!

2.2 合理的制定系统最大用户、并发用户

提供下面这个公式,以供大家在平时或者日常需要进行的性能测试中作为一个参考。
(1) 计算平均的并发用户数:C = nL/T
公式(1)中,C是平均的并发用户数;n是login session的数量;L是login session的平均长度;T指考察的时间段长度。
(2) 并发用户数峰值:C’ ≈ C+3根号C
公式(2)则给出了并发用户数峰值的计算方式中,其中,C’指并发用户数的峰值,C就是公式(1)中得到的平均的并发用户数。该公式的得出是假设用户的loginsession产生符合泊松分布而估算得到的。
实例:
假设有一个OA系统,该系统有3000个用户,平均每天大约有400个用户要访问该系统,对一个典型用户来说,一天之内用户从登录到退出该系统的平均时间为4小时,在一天的时间内,用户只在8小时内使用该系统。
则根据公式(1)和公式(2),可以得到:
C = 400

4/8 = 200 C’≈200+3

根号200 = 242
F=VU * R / T
其中F为吞吐量,VU表示虚拟用户个数,R表示每个虚拟用户发出的请求数,T表示性能测试所用的时间
R = T / TS。

2.3 影响和评估性能的几个关键指标

从上面的公式一节中我们还得到了一个名词“吐吞量”。和吞吐量相关的有下面这些概念,记录下来以供参考。

吞吐量

指在一次性能测试过程中网络上传输的数据量的总和。
对于交互式应用来说,吞吐量指标反映的是服务器承受的压力,在容量规划的测试中,吞吐量是一个重点关注的指标,因为它能够说明系统级别的负载能力,另外,在性能调优过程中,吞吐量指标也有重要的价值。

吞吐率

单位时间内网络上传输的数据量,也可以指单位时间内处理客户请求数量。它是衡量网络性能的重要指标,通常情况下,吞吐率用“字节数/秒”来衡量,当然,你可以用“请求数/秒”和“页面数/秒”来衡量。其实,不管是一个请求还是一个页面,它的本质都是在网络上传输的数据,那么来表示数据的单位就是字节数。

事务

就是用户某一步或几步操作的集合。不过,我们要保证它有一个完整意义。比如用户对某一个页面的一次请求,用户对某系统的一次登录,淘宝用户对商品的一次确认支付过程。这些我们都可以看作一个事务。那么如何衡量服务器对事务的处理能力。又引出一个概念----TPS

TPS (Transaction Per second)

每秒钟系统能够处理事务或交易的数量,它是衡量系统处理能力的重要指标。

点击率(Hit Per Second)

点击率可以看做是TPS的一种特定情况。点击率更能体现用户端对服务器的压力。TPS更能体现服务器对客户请求的处理能力。
每秒钟用户向web服务器提交的HTTP请求数。这个指标是web 应用特有的一个指标;web应用是“请求-响应”模式,用户发一个申请,服务器就要处理一次,所以点击是web应用能够处理的交易的最小单位。如果把每次点击定义为一个交易,点击率和TPS就是一个概念。容易看出,点击率越大。对服务器的压力也越大,点击率只是一个性能参考指标,重要的是分析点击时产生的影响。
需要注意的是,这里的点击不是指鼠标的一次“单击”操作,因为一次“单击”操作中,客户端可能向服务器发现多个HTTP请求。

吞吐量指标的作用:

+ 用户协助设计性能测试场景,以及衡量性能测试场景是否达到了预期的设计目标:在设计性能测试场景时,吞吐量可被用户协助设计性能测试场景,根据估算的吞吐量数据,可以对应到测试场景的事务发生频率,事务发生次数等;另外,在测试完成后,根据实际的吞吐量可以衡量测试是否达到了预期的目标。
+ 用于协助分析性能瓶颈:吞吐量的限制是性能瓶颈的一种重要表现形式,因此,有针对性地对吞吐量设计测试,可以协助尽快定位到性能冰晶所在位置。

平均相应时间

也称为系统响应时间,它一般指在指定数量的VU情况下,每笔交易从mouse 的click到IE的数据刷新与展示之间的间隔,比如说:250个VU下每笔交易的响应时间不超过2秒。
当然,响应时间也不能一概而论,对于实时交易如果银行柜台操作、超市收银员(邪恶的笑。。。)的操作、证交所交易员的操作来说这些操作的响应时间当然是越快越好,而对于一些企业级的如:
与银行T+1交易间的数据跑批、延时交易、T+1报表等,你要求它在2秒内响应,它也做不到啊。就好比你有个1MB的带宽,你传的东西是超过4MB,你要它在2秒内跑完理论速度也做不到啊,对吧,所以有些报表或者数据,光前面传输时间就不止两秒了。。。一口咬死说我所有的交易平均相应时间要2秒,真的是不科学的!

2.4 合理的性能测试
VU数量的增加

一个合理的性能测试除了需要合理的计算VU的数量、合理的设置系统平均响应时间外还需要合理的在测试时去规划发起VU的时间,比如说,我看到有人喜欢这样做压力测试。
第一秒时间,500个并发用户全部发起了。。。结果导致系统没多久就崩了,然后说系统没有满足设计要求。
为什么说上述这样的做法是不对的?我们说不是完全不对,只能说这样的测试已经超过了500个VU的并发的设计指标了。
合理的并发应该是如下这样的:
有25-50个VU开始起交易了,然后过一段时间又有25-50个用户,过一段时间又增加一些VU,当所有的设计VU都发起交易了,此时,再让压力测试跑一段时间比如说:24*7是比较合理的。所以VU数量不是一上手就500个在一秒内发起的,VU数量的增加应该如下面这张趋势图:

这是一个阶梯状的梯型图,可以看到VU的发起是逐渐逐渐增多的,以下两种情况如果发生需要检查你的系统是否在原有设置上存在问题:
+ VU数量上升阶段时崩溃
有时仅仅在VU数量上升阶段,系统就会了现各种各样的错误,甚至有崩溃者,这时就有重新考虑你的系统是否有设置不合理的地方了。
+ VU全部发起后没多久系统崩溃
VU在达到最高值时即所有的VU都已经发起了,此时它是以一条直的水平线随着系统运行而向前延伸着的,但过不了多久,比如说:运行24*7小时,运行了没一、两天,系统崩溃了,也需要做检查。
所以,理想的性能测试应该是VU数量上升到最终VU从发起开始到最后所有VU把交易做完后,VU数量落回零为止。

吐吞量的变化

从2.3节我们可以知道,吞吐量是随着压力/性能测试的时间而逐渐增大的,因此你的吞吐量指示应该如下图所示:
肯定是这样,你的吞吐量因该是积累的,如果你的吞吐量在上升了一段时间后突然下落,而此时你的性能测试还在跑着,如下图所示:

那么,此时代表什么事情发生了?你可以查一下你的loaderrunner或者jmeter里对于这段吞吐量回落期间的交易的response的状态进行查看,你将会发现大量的error已经产生,因为产生了error,所以你的交易其实已经出错了,因此每次运行的数据量越来越小,这也就意味着你的压力测试没有过关,系统被你压崩了!

平均响应时间

平均响应时间如VU的数量增加趋势图一样,一定是一开始响应时间最短,然后一点点增高,当增高到一定的程度后,即所有的VU都发起交易时,你的响应时间应该维持在一个水平值,然后随着VU将交易都一笔笔做完后,这个响应时间就会落下来,这段时间内的平均值就是你的系统平均响应时间。看看它,有没有符合设计标准?

内存监控

我们就来说AppServer,我们这边用的是Tomcat即SUN的JVM的内存变化,我们就用两张图例来讲解吧:
理想状态情况下的JVM内存使用趋势:
这是一个波浪型的(或者也可以说是锯齿型的)趋势图,随着VU数量的一点点增加,我们的内存使用数会不断的增加,但是JVM的垃圾回收是自动回收机制的,因此如果你的JVM如上述样的趋势,内存上涨一段时间,随即会一点点下落,然后再上涨一点,涨到快到头了又开始下落,直到最后你的VU数量全部下降下来时,你的JVM的内存使用也会一点点的下降。
非理想状态情况下的JVM内存使用趋势:

嘿嘿嘿,看到了吗?你的JVM随着VU 数量的上升,而直线上升,然后到了一定的点后,即到了Java –Xmx后的那个值后,突然直线回落,而此时你的交易还在进行,压力测试也还在进行,可是内存突然回落了。。。因为你的JVM已经crash了,即OUT OF MEMORY鸟。

CPU Load

我们来看一份测试人员提交上来CPU得用率的报告:

Web Server App Server DB Server
60% 98% =_=!(oh my god)6%囧

同时平均响应时间好慢啊。
拿过来看了一下代码与设计。。。Struts+spring+JDBC的一个框架,没啥花头的,再仔细一看Service层。
大量的复杂业务逻辑甚至报表的产生全部用的是javaobject如:List, Hashmap等操作,甚至还有在Service层进行排序、复杂查询等操作。
一看DB层的CPU利用率才6%,将一部分最复杂的业务拿出去做成Store Procedure(存储过程后),再重新运行压力测试。

Web Server App Server DB Server
60% 57% =_=!(oh my god) 26%


同时平均响应时间比原来快了15-16倍。
为什么??
看看第一份报告,我们当时还查看了数据库服务器的配置,和APPServer的配置是一个级别的,而利用率才6%。。。
数据库,至所以是大型的商用的关系型数据库,你只拿它做一个存储介质,你这不是浪费吗?人家里面设置的这个StoreProcedure的处理能力,索引效率,数据分块等功能都没有去利用,而用你的代码去实现那么多复杂业务比如说多表关联、嵌套等操作,用必要吗?那要数据库干什么用呢?
是啊,我承认,原有这样的代码,跨平台能力强一点,可付出的代价是什么呢?
用户在乎你所谓的跨平台的理论还是在乎的是你系统的效率?一个系统定好了用DB2或者是SQL SERVER,你觉得过一年它会换成Oracle或者MySQL吗?如果1年一换,那你做的系统也只能让用户勉强使用一年,我劝你还是不要去做了。在中国,有人统计过5年左右会有一次系统的更换,而一些银行、保险、金融行业的系统一旦采用了哪个数据库,除非这个系统彻底出了问题,负责是不会轻意换数据库的,因此不要拿所谓的纯JAVA代码或者说我用的是hibernate,ejb实现可以跨数据库这套来说事,效率低下的系统可以否定你所做的一切,一切!

三、Apache服务器的优化

上面两节,讲了大量的理论与实际工作中碰到的相关案例,现在就来讲一下在我们第一天和第二天中的ApacheHttp Server + Tomcat这样的架构,怎么来做优化吧。

3.1 Linux/UnixLinux系统下Apache 并发数的优化

Apache Http Server在刚安装完后是没有并发数的控制的,它采用一个默认的值,那么我们的Web Server硬件很好,允许我们撑到1000个并发即VU,而因为我们没有去配置导致我们的WebServer连300个并发都撑不到,你们认为,这是谁的责任?
Apache Http服务器采用prefork或者是worker两种并发控制模式。

preforkMPM

使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接。在大多数平台上,PreforkMPM在效率上要比Worker MPM要高,但是内存使用大得多。prefork的无线程设计在某些情况下将比worker更有优势:它可以使用那些没有处理好线程安全的第三方模块,并且对于那些线程调试困难的平台而言,它也更容易调试一些。

workerMPM 使用多个子进程,每个子进程有多个线程。每个线程在某个确定的时间只能维持一个连接。通常来说,在一个高流量的HTTP服务器上,Worker MPM是个比较好的选择,因为Worker MPM的内存使用比PreforkMPM要低得多。但worker MPM也由不完善的地方,如果一个线程崩溃,整个进程就会连同其所有线程一起"死掉".由于线程共享内存空间,所以一个程序在运行时必须被系统识别为"每个线程都是安全的"。

一般来说我们的ApacheHttp Server都是装在Unix/Linux下的,而且是采用源码编译的方式来安装的,我们能够指定在编译时Apache就采用哪种模式,为了明确我们目前的Apache采用的是哪种模式在工作,我们还可以使用httpd –l命令即在Apache的bin目录下执行httpd –l,来确认我们使用的是哪种模式。
这边,我们使用Apache配置语言中的” IfModule”来自动选择模式的配置。
我们的ApacheHttp Server在配完后一般是没有这样的配置的,是需要你手动的添加如下这样的一块内容的,我们来看,在httpd.conf文件中定位到最后一行LoadModule,敲入回车,加入如下内容:

<IfModule prefork.c>
ServerLimit  20000
StartServers  5
MinSpareServers  5
MaxSpareServers  10
MaxClients  1000
MaxRequestsPerChild 0
</IfModule>

上述参数解释:
+ ServerLimit 20000
默认的MaxClient最大是256个线程,如果想设置更大的值,就的加上ServerLimit这个参数。20000是ServerLimit这个参数的最大值。如果需要更大,则必须编译apache,此前都是不需要重新编译Apache。
生效前提:必须放在其他指令的前面
+ StartServers 5
指定服务器启动时建立的子进程数量,prefork默认为5。

  • MinSpareServers 5
    指定空闲子进程的最小数量,默认为5。如果当前空闲子进程数少于MinSpareServers ,那么Apache将以最大每秒一个的速度产生新的子进程。此参数不要设的太大。
  • MaxSpareServers 10
    设置空闲子进程的最大数量,默认为10。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。此参数不要设的太大。如果你将该指令的值设置为比MinSpareServers小,Apache将会自动将其修改成"MinSpareServers+1"。
  • MaxClients 256
    限定同一时间客户端最大接入请求的数量(单个进程并发线程数),默认为256。任何超过MaxClients限制的请求都将进入等候队列,一旦一个链接被释放,队列中的请求将得到服务。要增大这个值,你必须同时增大ServerLimit。
  • MaxRequestsPerChild10000
    每个子进程在其生存期内允许伺服的最大请求数量,默认为10000.到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild为"0",子进程将永远不会结束。
    将MaxRequestsPerChild设置成非零值有两个好处:
    1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
    2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
Prefork.c的工作方式:

一个单独的控制进程(父进程)负责产生子进程,这些子进程用于监听请求并作出应答。Apache总是试图保持一些备用的(spare)或者是空闲的子进程用于迎接即将到来的请求。这样客户端就不需要在得到服务前等候子进程的产生。在Unix系统中,父进程通常以root身份运行以便邦定80端口,而Apache产生的子进程通常以一个低特权的用户运行。User和Group指令用于设置子进程的低特权用户。运行子进程的用户必须要对它所服务的内容有读取的权限,但是对服务内容之外的其他资源必须拥有尽可能少的权限。
在上述的后再加入一个””如下红色加粗(大又粗)内容:

<IfModule prefork.c>
ServerLimit  20000
StartServers  5
MinSpareServers  5
MaxSpareServers  10
MaxClients  1000
MaxRequestsPerChild 0
</IfModule>
<IfModule worker.c>
ServerLimit  50
ThreadLimit  200
StartServers  5
MaxClients  5000
MinSpareThreads  25
MaxSpareThreads  500
ThreadsPerChild  100
MaxRequestsPerChild 0
</IfModule>

上述参数解释:
+ ServerLimit 16
服务器允许配置的进程数上限。这个指令和ThreadLimit结合使用设置了MaxClients最大允许配置的数值。任何在重启期间对这个指令的改变都将被忽略,但对MaxClients的修改却会生效。
+ ThreadLimit 64
每个子进程可配置的线程数上限。这个指令设置了每个子进程可配置的线程数ThreadsPerChild上限。任何在重启期间对这个指令的改变都将被忽略,但对ThreadsPerChild的修改却会生效。默认值是"64".
+ StartServers3
服务器启动时建立的子进程数,默认值是"3"。
+ MinSpareThreads75
最小空闲线程数,默认值是"75"。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太少,子进程将产生新的空闲线程。
+ MaxSpareThreads250
设置最大空闲线程数。默认值是"250"。这个MPM将基于整个服务器监视空闲线程数。如果服务器中总的空闲线程数太多,子进程将杀死多余的空闲线程。MaxSpareThreads的取值范围是有限制的。Apache将按照如下限制自动修正你设置的值:worker要求其大于等于MinSpareThreads加上ThreadsPerChild的和
+ MaxClients400
允许同时伺服的最大接入请求数量(最大线程数量)。任何超过MaxClients限制的请求都将进入等候队列。默认值是"400",16(ServerLimit)乘以25(ThreadsPerChild)的结果。因此要增加MaxClients的时候,你必须同时增加ServerLimit的值。
+ ThreadsPerChild25
每个子进程建立的常驻的执行线程数。默认值是25。子进程在启动时建立这些线程后就不再建立新的线程了。
+ MaxRequestsPerChild 0
设置每个子进程在其生存期内允许伺服的最大请求数量。到达MaxRequestsPerChild的限制后,子进程将会结束。如果MaxRequestsPerChild为"0",子进程将永远不会结束。
将MaxRequestsPerChild设置成非零值有两个好处:
1.可以防止(偶然的)内存泄漏无限进行,从而耗尽内存。
2.给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
注意
对于KeepAlive链接,只有第一个请求会被计数。事实上,它改变了每个子进程限制最大链接数量的行为。

Worker.c的工作方式:

每个进程可以拥有的线程数量是固定的。服务器会根据负载情况增加或减少进程数量。一个单独的控制进程(父进程)负责子进程的建立。每个子进程可以建立ThreadsPerChild数量的服务线程和一个监听线程,该监听线程监听接入请求并将其传递给服务线程处理和应答。Apache总是试图维持一个备用(spare)或是空闲的服务线程池。这样,客户端无须等待新线程或新进程的建立即可得到处理。在Unix中,为了能够绑定80端口,父进程一般都是以root身份启动,随后,Apache以较低权限的用户建立子进程和线程。User和Group指令用于设置Apache子进程的权限。虽然子进程必须对其提供的内容拥有读权限,但应该尽可能给予它较少的特权。另外,除非使用了suexec,否则,这些指令设置的权限将被CGI脚本所继承。
公式:
ThreadLimit>= ThreadsPerChild
MaxClients <= ServerLimit * ThreadsPerChild 必须是ThreadsPerChild的倍数
MaxSpareThreads>= MinSpareThreads+ThreadsPerChild
硬限制:
ServerLimi和ThreadLimit这两个指令决定了活动子进程数量和每个子进程中线程数量的硬限制。要想改变这个硬限制必须完全停止服务器然后再启动服务器(直接重启是不行的)。
Apache在编译ServerLimit时内部有一个硬性的限制,你不能超越这个限制。
preforkMPM最大为"ServerLimit200000"
其它MPM(包括work MPM)最大为"ServerLimit 20000
Apache在编译ThreadLimit时内部有一个硬性的限制,你不能超越这个限制。
mpm_winnt是"ThreadLimit 15000"
其它MPM(包括work prefork)为"ThreadLimit 20000
注意
使用ServerLimit和ThreadLimit时要特别当心。如果将ServerLimit和ThreadLimit设置成一个高出实际需要许多的值,将会有过多的共享内存被分配。当设置成超过系统的处理能力,Apache可能无法启动,或者系统将变得不稳定。

3.2 WindowsWindows系统下Apache 并发数的优化

以上是Linux/Unix下的Apache的并发数优化配置,如果我们打入了httpd –l如下显示:
怎么办?
+ 步骤一
先修改/path/apache/conf/httpd.conf文件。
httpd.conf
将“#Includeconf/extra/httpd-mpm.conf”前面的 “#” 去掉,保存。
+ 步骤二
再修改/apache安装目录/conf/extra/httpd-mpm.conf文件。
在mpm_winnt模式下,Apache不使用prefork也不使用work工作模式,切记!
因此,我们只要找到原文件中:

<IfModule mpm_winnt_module>
    ThreadsPerChild      150
    MaxRequestsPerChild    0
</IfModule>

修改后

<IfModule mpm_winnt_module>
    ThreadsPerChild      500
    MaxRequestsPerChild    5000
</IfModule>

上述参数解释:
+ ThreadsPerChild
是指一个进程最多拥有的线程数(Windows版本,貌似不可以开启多个进程),一般100-500就可以,根据服务器的具体性能来决定。
+ MaxRequestsPerChild
是指一个线程最多可以接受的连接数,默认是0,就是不限制的意思,
0极有可能会导致内存泄露。所以,可以根据实际情况,配置一个比较大的值。Apache会在几个线程之间进行轮询,找到负载最轻的一个线程来接受新的连接。
注意:
修改后,一定不要apacherestart,而是先 apache stop 然后再 apache start才可以。

3.3 启用服务端图片压缩

对于静态的html 文件,在apache 可加载mod_deflate.so 模块,把内容压缩后输出,可节约大量的传输带宽。
打开httpd.conf文件,找到:

#LoadModule deflate_module modules/mod_deflate.so

将前面的“#”去掉,变成:

LoadModule deflate_module modules/mod_deflate.so

然后在最后一行的LoadModule处,加入如下的几行:

<IfModule mod_deflate.c>
 DeflateCompressionLevel 7
 AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-PHP
 AddOutputFilter DEFLATE css js
</IfModule>

注意:
默认等级是6,而且9级需要更多的CPU时间,用默认的6级就可以了。
要注意的是,在apache 2.2.15中,我用httpd -l看,居然发现mod_deflat已经内置了,所以其实就不用再在httpd.conf中增加loadmodule了,否则会说出错的

3.4 Apache中将MS办公文档自动关联客户端的MS-Office
我们经常会在web页的一个超链接上点一个指向物理文件的文档,我们一般会得到“保存,另存为,打开”,3个选项,当我们打开的如果是一个MS文档,在选“打开”选项时IE会自动启用客户端上装有的word或者是excel等相关MS办公工具去打开,这个怎么做呢?很简单。
打开httpd.conf,找到:

    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz

在其后敲入一个回车,加入:

AddType application/vnd.openxmlformats  docx pptx xlsx doc xls ppt txt

重启Apache服务即可。

3.5防止DDOS攻击

DDOS攻击即采用自动点击机器人或者连续点击工具不断的刷新某一个网址或者网页上的按钮,造成网站在一时间收到大量的HTTP请求,进而阻塞网站正常的HTTP通道甚至造成网站瘫痪。

为了防止这一形式的攻击,我们一般把在一个按钮或者是一个请求在一秒内连续执行如:100次,可以认为是一种攻击(比如说你打开一个网页,点一下提交按钮,然后按住F5键不松开)。
在Linux下的Apache HttpServer安装后会提供一个mod_evasive20的模块,用于防止这一形式的攻击,它的做法是:
如果认为是一个DDOS攻击,它的防范手段采用如下两种形势:
+ 把这个请求相关联的IP,封锁30分钟
+ 直接把相关的IP踢入黑名单,让其永不翻身
设置:
在你的Apache的httpd.conf文件中的最后一行“LoadModule”加入如下这句:

LoadModule evasive20_module   /usr/lib/httpd/modules/mod_evasive20.so

然后加入下面这几行

<IfModule mod_evasive20.c>
DOSHashTableSize 3097
DOSPageCount 15
DOSSiteCount 100
DOSPageInterval 1
DOSSiteInterval 1
DOSBlockingPeriod 36000
DOSEmailNotify 网站超级管理员@xxx.com
DOSLogDir "logs/mod_evasive"
</IfModule>

核心参数解释:
+ DOSHashTableSize 3097 记录黑名单的尺寸
+ DOSPageCount 每个页面被判断为dos攻击的读取次数
+ DOSSiteCount 每个站点被判断为dos攻击的读取部件(object)的个数
+ DOSPageInterval 读取页面间隔秒
+ DOSSiteInterval 读取站点间隔秒
+ DOSBlockingPeriod 被封时间间隔秒
注意:
上述设置是针对Linux/Unix下的Apache Server,相关的Windows下的Apache见如下设置:

为Windows下的Apache加载mod_evasive模块
  1. 下载附件中的压缩包,解压并拷贝mod_dosevasive22.dll到Apache安装目录下的modules目录(当然也可以是其他目录,需要自己修改路径)。
  2. 修改Apache的配置文件http.conf。
    添加以下内容
LoadModule dosevasive22_module modules/mod_dosevasive22.dll
DOSHashTableSize 3097
DOSPageCount 3
DOSSiteCount 50
DOSPageInterval 1
DOSSiteInterval 1
DOSBlockingPeriod 10
3.6 Apache中设置URL含中文附件的下载/打开的方法(仅限Linux系统下)

这个话题很有趣,起因是我们在工程中碰到了客户这样的一个需求:

<a href=”xxx.xxx.xx/xx/xxx/轮胎损坏情况2007-05-05.jpg”>损坏部件</a>

看看好像没啥问题,一点这个超链接,因该是在IE中打开一个叫” 轮胎损坏情况2007-05-05.jpg”,嘿嘿,大家自己动手放一个带有中文名的这样的一个图片,看看能否被解析,解析不了。
所以我们就说,真奇怪,我们上传图片都是上传时的图片名经上传组件解析过以后变成一个UUID或者是GUID一类的文件名如:gb19070122abcxd.jpg这样一种英文加数字组合的文件名,这样的文件名,Apache当然是可以解析的,客户坚持一定我上传的图片是中文名(连中文描述都不行),因为,客户说:我们是中国人,当然用中文图片名。。。
没办法,找了半天,找到一篇日文的教程,还好还好,N年前学过一点点日语,照着教程把它啃下来了。
这是一个日本人写的关于在Apache中支持以亚州文字命名文件名的一个“补丁”,叫“mod_encoding”。
相关配置:
1. 下载完后是一个这样的压缩包:mod_encoding-20021209.tar.gz
2. 解压后使用:

configure
make
make install

在make这一行时,编译出错,报“make: *** [mod_encoding.so] Error 1”这样的错
原因很明显,是regex.h未包含进来,解决办法也很简单:
+ 用vi打开mod_encoding.c,
+ 在#include <httpd.h>那一段的前面加上如下一行:

#include <regex.h>

然后:重新make再make install 搞定,CALL!!!
3. 编译后得到一个:mod_encoding.so的文件,然后在httpd.conf文件中加入下面这几行:

LoadModule encoding_module modules/mod_encoding.so
Header add MS-Author-Via "DAV"
<IfModule mod_encoding.c>
  EncodingEngine    on
  NormalizeUsername on
  SetServerEncoding GBK
  DefaultClientEncoding UTF-8 GBK GB2312
  AddClientEncoding "(Microsoft .* DAV $)" UTF-8 GBK GB2312
  AddClientEncoding "Microsoft .* DAV" UTF-8 GBK GB2312
  AddClientEncoding "Microsoft-WebDAV*" UTF-8 GBK GB2312
</IfModule>
  1. 重启Apache,搞定,在apache中我们的url可以是中文名的附件了。
3.7 不可忽视的keepalive选项

在Apache 服务器中,KeepAlive是一个布尔值,On 代表打开,Off 代表关闭,这个指令在其他众多的 HTTPD 服务器中都是存在的。
KeepAlive 配置指令决定当处理完用户发起的 HTTP 请求后是否立即关闭 TCP 连接,如果 KeepAlive 设置为On,那么用户完成一次访问后,不会立即断开连接,如果还有请求,那么会继续在这一次 TCP 连接中完成,而不用重复建立新的 TCP 连接和关闭TCP 连接,可以提高用户访问速度。
那么我们考虑3种情况:
1.用户浏览一个网页时,除了网页本身外,还引用了多个JavaScript 文件,多个css 文件,多个图片文件,并且这些文件都在同一个HTTP 服务器上。
2.用户浏览一个网页时,除了网页本身外,还引用一个javascript 文件,一个图片文件。
3.用户浏览的是一个动态网页,由程序即时生成内容,并且不引用其他内容。
对于上面3中情况,我认为:1 最适合打开 KeepAlive ,2 随意,3 最适合关闭 KeepAlive
下面我来分析一下原因。
在 Apache 中,打开和关闭 KeepAlive 功能,服务器端会有什么异同呢?
先看看理论分析。
打开KeepAlive 后,意味着每次用户完成全部访问后,都要保持一定时间后才关闭会关闭TCP 连接,那么在关闭连接之前,必然会有一个Apache进程对应于该用户而不能处理其他用户,假设KeepAlive 的超时时间为10 秒种,服务器每秒处理 50个独立用户访问,那么系统中 Apache 的总进程数就是 10 * 50 = 500 个,如果一个进程占用 4M 内存,那么总共会消耗 2G内存,所以可以看出,在这种配置中,相当消耗内存,但好处是系统只处理了 50次 TCP 的握手和关闭操作。

如果关闭KeepAlive,如果还是每秒50个用户访问,如果用户每次连续的请求数为3个,那么 Apache 的总进程数就是 50 * 3= 150 个,如果还是每个进程占用 4M 内存,那么总的内存消耗为 600M,这种配置能节省大量内存,但是,系统处理了 150 次 TCP的握手和关闭的操作,因此又会多消耗一些 CPU 资源。
再看看实践的观察。
我在一组大量处理动态网页内容的服务器中,起初打开KeepAlive功能,经常观察到用户访问量大时Apache进程数也非常多,系统频繁使用交换内存,系统不稳定,有时负载会出现较大波动。关闭了KeepAlive功能后,看到明显的变化是:Apache 的进程数减少了,空闲内存增加了,用于文件系统Cache的内存也增加了,CPU的开销增加了,但是服务更稳定了,系统负载也比较稳定,很少有负载大范围波动的情况,负载有一定程度的降低;变化不明显的是:访问量较少的时候,系统平均负载没有明显变化。

总结一下:
在内存非常充足的服务器上,不管是否关闭KeepAlive 功能,服务器性能不会有明显变化;
如果服务器内存较少,或者服务器有非常大量的文件系统访问时,或者主要处理动态网页服务,关闭KeepAlive 后可以节省很多内存,而节省出来的内存用于文件系统Cache,可以提高文件系统访问的性能,并且系统会更加稳定。
+ 补充1
关于是否应该关闭 KeepAlive 选项,我觉得可以基于下面的一个公式来判断。
在理想的网络连接状况下,系统的Apache 进程数和内存使用可以用如下公式表达:

HttpdProcessNumber= KeepAliveTimeout * TotalRequestPerSecond / Average(KeepAliveRequests)
HttpdUsedMemory= HttpdProcessNumber * MemoryPerHttpdProcess

换成中文意思:

总Apache进程数 = KeepAliveTimeout * 每秒种HTTP请求数 / 平均KeepAlive请求
Apache占用内存 = 总Apache进程数 * 平均每进程占用内存数

需要特别说明的是:
[平均KeepAlive请求] 数,是指每个用户连接上服务器后,持续发出的 HTTP 请求数。当 KeepAliveTimeout 等 0或者 KeepAlive 关闭时,KeepAliveTimeout 不参与乘的运算从上面的公式看,如果 [每秒用户请求]多,[KeepAliveTimeout] 的值大,[平均KeepAlive请求] 的值小,都会造成 [Apache进程数] 多和 [内存]多,但是当 [平均KeepAlive请求] 的值越大时,[Apache进程数] 和 [内存] 都是趋向于减少的。
基于上面的公式,我们就可以推算出当 平均KeepAlive请求 <= KeepAliveTimeout 时,关闭 KeepAlive 选项是划算的,否则就可以考虑打开。
+ 补充2
KeepAlive 该参数控制Apache是否允许在一个连接中有多个请求,默认打开。但对于大多数论坛类型站点来说,通常设置为off以关闭该支持。
+ 补充3
如果服务器前跑有应用squid服务,或者其它七层设备,KeepAlive On 设定要开启持续长连接
实际在 前端有squid 的情况下,KeepAlive 很关键。记得On。
Keepalive不能随心所欲设置,而是需要根据实际情况,我们来看一个真实的在我工作中发生的搞笑一次事件:
当时我已经离开该项目了,该项目的TeamLeader看到了keepalive的概念,他只看到了关闭keeyalive可以节省web服务器的内存,当时我们的web服务器只有4gb内存,而并发请求的量很大,因此他就把这个keepalive设成了off。
然后直接导致脱机客户端(脱机客户端用的是.net然后webservice连接)的“login”每次都显示“出错”。
一查代码才知道,由于这个脱机客户端使用的是webservice访问,.net开发团队在login功能中设了一个超时,30秒,30秒timeout后就认为服务器没有开启,结果呢由于原来的apache设的是keeyalive和timeout 15秒,现在被改成了off,好家伙,根本就没有了这个timeout概念,因此每次.net登录直接被apache弹回来,因为没有了这个timeout的接口了。
由此可见,学东西。。。不能一知半解,务必求全面了解哈。

3.8 HostnameLookups设置为off

尽量较少DNS查询的次数。如果你使用了任何”Allow fromdomain”或”Denyfrom domain”指令(也就是domain使用的是主机名而不是IP地址),则代价是要进行两次DNS查询(一次正向和一次反向,以确认没有作假)。所以,为了得到最高的性能,应该避免使用这些指令(不用域名而用IP地址也是可以的)。

bash shell基本知识

bash shell执行命令前的动作
  1. 分割管道前后的命令
  2. 分割命令中的单词
  3. 替换别名命令
  4. 将括号扩展为指代字符串
  5. "~"符号扩展为实际路径
  6. 替换变量
  7. 替换"$()"和"``"形式的命令
  8. 替换运算符"$(())"为其结果
  9. 替换通配符"*"和"?"为指代本地文件或目录
  10. 根据"函数","内部命令","外部程序"的顺序搜索路径找到命令
  11. 运行替换后的整体程序
bash shell脚本程序变量
  • 参数位置变量
    $0-$9,${10}-${n}
  • 所有参数位置视为一个字符串
    $*
  • 所有参数位置视为一个串行多个字符串
    [email protected]
  • 参数个数
    $#
特殊的内置变量
  • 上一条命令执行结束后的返回值
    $?
  • 目前bash shell的进程号
    $$
  • 上一个后台程序的进程号
    $!
测试变量存在性及空值

${变量名称:-默认值}变量为空时回传默认值

echo ${S1:-is empty!!!}
echo ${S1}
echo 111

输出

[[email protected] test]# bash test.sh 
is empty!!!

111

${变量名称:+默认值}变量为空时设置成默认值

#!/bin/bash
echo ${S1:=is empty!!!}
echo ${S1}
echo 111

输出

[[email protected] test]# bash test.sh 
is empty!!!
is empty!!!
111

${变量名称:?提示信息}变量为空时停止显示程序显示提示信息

echo ${S1:?is empty!!!}
echo ${S1}
echo 111

输出

[[email protected] test]# bash test.sh 
test.sh: line 2: S1: is empty!!!
字符串切片,长度

${变量:位置起点},由指定的位置开始截取字符串到结尾
${变量:位置起点:长度},由指定的位置开始截取指定长度的子字符串
${#变量},传回子变量的长度
例子
逐个输出字符串

#!/bin/bash
STRING="Three words,eight letters."
echo ${STRING:0};
echo ${STRING:0:11};
echo ${#STRING};
# 逐字输出
for ((i=0;i<${#STRING};i++))
do 
        echo ${STRING:${i}:1};
done
对比样式

${变量#样式},从左开始对比变量,删除最短相符字串
${变量##样式},从左开始对比变量,删除最长相符字串
${变量%样式},从右开始对比变量,删除最短相符字串
${变量%%样式},从右开始对比变量,删除最长相符字串
${变量/样式/替换字串},如果变量中右匹配样式(取最长),则使用替代字串替换第一个匹配
${变量//样式/替换字串},如果变量中右匹配样式(取最长),则使用替代字串替换所有匹配
例子

#!/bin/bash
STRING="Three words,eight letters."
echo ${STRING#T*e};
echo ${STRING##T*e};
echo ${STRING%e*.};
echo ${STRING%%e*.};
echo ${STRING/s/ss};
echo ${STRING//s/ss};

输出

[[email protected] test]# bash test.sh 
e words,eight letters.
rs.
Three words,eight lett
Thr
Three wordss,eight letters.
Three wordss,eight letterss.
取变量名列表,数组索引列表

${!开头字串@}或者${!开头字串

},取当前匹配的变量名列表,各变量之前用$IFS第一个分割字符(默认为空格)隔开 ${!数组变量名[@]}或${!数组变量名[

]},取出数组索引列表,各索引之间用$IFS第一个分隔符(默认为空格)隔开

算术运算
  • 算数扩展 $((算数式))
  • 外部命令 expr 算数式
  • 内置命令 declare -i 变量=算数式
  • 内置命令 let 算数式
  • $[]方式 $[算数式]
  • 浮点运算用bc命令,最慢

例子

#!/bin/bash
echo $(( 1 + 1));
expr 1 + 1;
declare -i A=1+1;
echo A;
let B=1+1;
echo B;
echo $[ 1 + 1]
echo "scale=22;2.2+6.6" | bc

输出

[[email protected] test]# bash test.sh 
2
2
A
B
2
8.8
流程控制
 #!/bin/bash
STRING=1;
# if基本用法
if [ ${STRING} = "1" ];
then
        echo "\$STRING='1'"
elif [ ${STRING} -eq 2 ];
then
        echo "\$STRING=1"
fi

# case 基本用法
case $1 in
        1)
                echo "\$1=1";;
        2)
                echo "\$1=2";;
        *)
                echo "\$1i!=1 or 2";;

esac;
# for 基本使用
MAX_NUM=10;
# 使用 {1..n} 的形式时不能使用变量
#for i in {1..10}
# 下面两种方式能使用变量
#for i in `seq 0 ${MAX_NUM}`
for ((i=0;i<=${MAX_NUM};i++))
do
        echo ${i};
done;

# while 基本使用
while [ ${MAX_NUM} -gt 0 ] 
do
        # break使用
        break 1;
        echo ${MAX_NUM};
        MAX_NUM=$[MAX_NUM - 1]
done;
# 逐行读取
while read line
do
        # continue使用
        continue 1;
        sleep 1;
        echo ${line};
done < /etc/passwd;

# until 当条件表达式为假才会运行
until [ $MAX_NUM -gt 10 ]
do
        echo ${MAX_NUM};
        MAX_NUM=$((MAX_NUM+1));
done;
函数的基本使用
 #!/bin/bash
function fun1()
{
        # 使用local关键字避免和函数外的变量冲突
        local STRING=string
        echo "func1 \$1=${1}";
        # 最大返回255
        return 25;
}
fun2()
{
        echo fun2;
}
# 传参
fun1 aaa;
# 上个函数的返回值
echo $?;
# 包含其他文件脚本
#source ./test2.sh
. ./test2.sh
fun3;
文件的重定向
# 获取当前进程pid
[[email protected] test]# echo $$
8897
# 进入proc下对应pid的目录,里面有进程的相关信息
[[email protected] test]# cd /proc/8897/
[[email protected] 8897]# ls
attr       clear_refs       cpuset   fd      loginuid   mounts      numa_maps      pagemap      schedstat  stat     task
autogroup  cmdline          cwd      fdinfo  maps       mountstats  oom_adj        personality  sessionid  statm    wchan
auxv       comm             environ  io      mem        net         oom_score      root         smaps      status
cgroup     coredump_filter  exe      limits  mountinfo  ns          oom_score_adj  sched        stack      syscall
# fd目录下为文件的描述符
[[email protected] 8897]# cd fd
# 文件句柄,为正整数
[[email protected] fd]# ls -l
总用量 0
lrwx------ 1 root root 64 12月  9 15:19 0 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:19 1 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:19 2 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:19 255 -> /dev/pts/2

文件描述符
一个非负整数,用来索引和追踪文件,可以认为是打开文件的编号
在/proc/PID/fd中标识

# 创建文件
[[email protected] fd]# echo aaa > ~/test_file
# 打开文件句柄
[[email protected] fd]# exec 6<>~/test_file 
# 在fd目录下会生成一个文件,操作这个文件和直接操作那个文件一样
[[email protected] fd]# ls
0  1  2  255  6
[[email protected] fd]# cat 6
aaa
[roo[email protected] fd]# cat ~/test_file 
aaa
[[email protected] fd]# echo bbb > 6
[[email protected] fd]# cat ~/test_file 
bbb
[[email protected] fd]# cat 6
bbb
# 把文件删除,还可以把文件直接copy回去还原
[[email protected] fd]# \rm ~/test_file 
[[email protected] fd]# file ~/test_file
/root/test_file: cannot open `/root/test_file' (No such file or directory)
[[email protected] fd]# ls -l
总用量 0
lrwx------ 1 root root 64 12月  9 15:32 0 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:32 1 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:32 2 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:32 255 -> /dev/pts/2
lrwx------ 1 root root 64 12月  9 15:32 6 -> /root/test_file (deleted)
[[email protected] fd]# cp 6 ~/test_file
# 虽然文件还原了,但是文件其实已经不是之前的文件了
[[email protected] fd]# file ~/test_file
/root/test_file: ASCII text
[[email protected] fd]# echo ccc > 6
[[email protected] fd]# cat 6
ccc
[[email protected] fd]# cat ~/test_file 
bbb
# 关闭
exec 6<&-

打开文件

exec 6<>~/test_file

关闭文件
fd<&-或者fd>&-

exec 6<&-

作用
如下情况,逐行读取会跳过一行

#!/bin/bash
FILE_PATH=/etc/passwd
while read line
do
        sleep 1;
        read ;
        echo ${line};
done < ${FILE_PATH}

如下就可避免这种情况

 #!/bin/bash
FILE_PATH=/etc/passwd
exec 6<>${FILE_PATH}
while read -u 6 line
do
        sleep 1;
        read ;
        echo ${line};
done < ${FILE_PATH}
exec 6<&-;
限制程序指定进程数运行
  • 使用read -u文件描述符方式读取字符串内容
  • 设置文件描述符中的回车符号个数为预设的进程数
  • 通过循环完成进程的创建

例子

 #!/bin/bash
sub()
{
        echo "------${$}------begin------";
        SLEEP_TIME=$[ 1 % 7 ];
        sleep ${SLEEP_TIME};
        echo "------${$}------end------";
}
# 总执行次数
TOTAL_NUMBER=88;
# 同时最多执行次数
MAX_THREAD=8;
# 临时文件地址
TEMP_FILE="/tmp/.fifo-${$}";
# 创建管道文件,管道文件在内存中,更快
mkfifo ${TEMP_FILE};
# 打开文件句柄
exec 6<>${TEMP_FILE}
# 删除临时文件
rm -f ${TEMP_FILE};
# 填充回车到文件句柄使程序能执行
for((i=0;i<${MAX_THREAD};i++))
do
        echo
done >&6;

# 循环把程序执行完
for((i=0;i<${TOTAL_NUMBER};i++))
do
        # 从文件句柄中读取,遇到回车才会执行下一步
        read -u 6
        # 定义程序块,在程序块中执行要执行的操作,把程序块放到后台执行
        {   
                sub ${i};
                echo >&6;
        }&
        # 查看后台运行的程序
        jobs -r;
        jobs -r | wc -l;
done;


# 等到所有程序执行完
wait;
# 关闭文件句柄
exec 6<&-;
exit 0;