
新浪高级研发工程师,技术方向 Linux C 、Python。
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
其实输入与输出对于不管什么系统的设计都是异常重要的,比如设计 C 接口函数,首先要设计好输入参数、输出参数和返回值,接下来才能开始设计具体的实现过程。C 语言标准库提供的接口功能很有限,不像 Python 库。不过想把它用好也不容易,本文总结 C 标准库基础 IO 的常见操作和一些特别需要注意的问题,如果你觉着自己还不是大神,那么请相信我,读完全文后你肯定会有不少收获。 一、操作句柄 打开文件其实就是在操作系统中分配一些资源用于保存该文件的状态信息及文件的标识,以后用户程序可以用这个标识做各种读写操作,关闭文件则释放占用的资源。 打开文件的函数: #include <stdio.h> FILE *fopen(const char *path, const char *mode); FILE 是 C 标准库定义的结构体类型,其包含文件在内核中的标识(文件描述符)、I/O 缓冲区和当前读写位置信息,调用者不需知道 FILE 的具体成员,由库函数内部维护,调用者不应该直接访问这些成员。像 FILE* 这样的文件指针称为句柄(Handle)。 打开文件操作是对文件资源进行操作的,所以有可能打开文件失败,所以在打开函数时一定要判断返回值,如果失败则返回错误信息,以方便快速定位错误。 打开文件应该与关闭文件成对存在,虽然程序在退出时会释放相应的资源,但是对于一个长时间运行服务程序来说,经常打开而不关闭文件是会造成进程资源耗尽的,因为进程的文件描述符个数是有限的,及时关闭文件是个好习惯。 关闭文件的函数: #include <stdio.h> int fclose(FILE *fp); fopen 函数参数 mode 总结: "r":只读,文件必须存在。 "w":只写,如果不存在则创建,存在则覆盖。 "a":追加,如果不存在则创建。 "r+":允许读和写,文件必须存在。 "w+":允许读和写,文件不存在则创建,存在则覆盖。 "a+":允许读和追加,文件不存在则创建。 二、关于stdin/stdout/stderr 在用户程序启动时,main 函数还没开始执行之前,会自动打开三个 FILE* 指针分别是:stdin、stdout、stderr,这三个文件指针是 libc 中定义的全局变量,在 stdio.h 中声明,printf 向 stdout 写,而 scanf 从 stdin 读,用户程序也可以直接使用这三个文件指针。 stdin 只用于读操作,称为标准输入 stdout 只用于写操作,称为标准输出 stderr 也用于写操作,称为标准错误输出 通常程序的运行结果打印到标准输出,而错误提示打印到标准错误输出,一般标准输出和标准错误都是屏幕。通常可以标准输出重定向到一个常规文件,而标准错误输出仍然对应终端设备,这样就可以将运行结果与错误信息分开。 三、以字节为单位的IO函数 fgetc 函数从指定的文件中读一个字节,getchar从标准输入读一个字节,调用 getchar() 相当于 fgetc(stdin) #include <stdio.h> int fgetc(FILE *stream); int getchar(void); fputc 函数向指定的文件写入一个字节,putchar 向标准输出写一个字节,调用 putchar() 相当于调用 fputc(c, stdout)。 #include <stdio.h> int fputc(int c, FILE *stream); int putchar(int c); 参数和返回值类型为什么使用 int 类型?可以看到这几个函数的参数和返回值类型都是 int,而非 unsigned char 型。因为错误或读到文件末尾时将返回 EOF,即 -1,如果返回值是 unsigned char(0xff),与实际读到字节 0xff 无法区分,如果使用 int 就可以避免这个问题。 四、操作读写位置函数 当我们在操作文件时,有一个叫「文件指针」的家伙来记录当前操作的文件位置,比如刚打开文件,调用了 1 次 fgetc 后,此时文件指针指向了第 1 个字节后边,注意是以字节为单位记录的。 改变文件指针位置的函数: #include <stdio.h> int fseek(FILE *stream, long offset, int whence); whence:从何处开始移动,取值:SEEK_SET | SEEK_CUR | SEEK_END offset:移动偏移量,取值:可取正 | 负 void rewind(FILE *stream); 举几个简单例子: fseek(fp, 5, SEEK_SET); // 从文件头向后移动5个字节 fseek(fp, 6, SEEK_CUR); // 从当前位置向后移动6个字节 fseek(fp, -3, SEEK_END); // 从文件尾向前移动3个字节 offset 可正可负,负值表示向文件开头的方向移动,正值表示向文件尾方向移动,如果向前移动的字节数超过文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入会增加文件尺寸,文件空洞字节都是 0 $ echo "5678" > file.txt fp = fopen("file.txt", "r+"); fseek(fp, 10, SEEK_SET); fputc('K', fp) fclose(fp) // 通过结果可以看出字母K是从第10个位置开始写的 liwei:/tmp$ od -tx1 -tc -Ax file.txt 0000000 35 36 37 38 0a 00 00 00 00 00 4b 5 6 7 8 \n \0 \0 \0 \0 \0 K rewind(fp) 等价于 fseek(fp, 0, SEEK_SET) ftell(fp) 函数比较简单,直接返回当前文件指针在文件中的位置 // 实现计算文件字节数的功能 fseek(fp, 0, SEEK_END); ftell(fp); 五、以字符串为单位的IO函数 fgets 从指定的文件中读一行字符到调用者提供的缓冲区,读入内容不超过 size 。 char *fgets(char *s, int size, FILE *stream); char *gets(char *s); 首先要说明 gets() 函数强烈不推荐使用,类似 strcpy 函数,用户不可以指定缓冲区大小,很容易造成缓冲区溢出错误。不过 strcpy 程序员还是可以避免,而 gets 的输入用户可以提供任意长的字符串,唯一避免方法就是不使用 gets,而使用 fgets(buf, size, stdin) fgets 函数从 stream 所指文件读取以 '\n' 结尾的一行,包括 '\n' 在内,存到缓冲区中,并在该行结尾添加一个 '\0' 组成完整的字符串。如果文件一行太长,fgets 从文件中读了 size-1 个字符还没有读到 '\n',就把已经读到的 size-1 个字符和一个 '\0' 字符存入缓冲区,文件行剩余的内容可以在下次调用 fgets 时继续读。 若一次 fgets 调用在读入若干字符后到达文件末尾,则将已读到的字符加上 '\0' 存入缓冲区并返回,如果再次调用则返回 NULL,可以据此判断是否读到文件末尾。 fputs 向指定文件写入一个字符串,缓冲区保存的是以 '\0' 结尾的字符串,与 fgets 不同的是,fputs 不关心字符串中的 '\n' 字符。 int fputs(const char *s, FILE *stream); int puts(const char *s); 六、以记录为单位的IO函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); fread 和 fwrite 用于读写记录,这里的记录是指一串固定长度的字节,比如一个 int、一个结构体货或一个定长数组。 参数 size 指出一条记录的长度,nmemb 指出要读或写多少条记录,这些记录在 ptr 所指内存空间连续存放,共占 size * nmemb 个字节。 fread 和 fwrite 返回的记录数有可能小于 nmemb 指定的记录数。例如当读写位置距文件末尾只有一条记录长度,调用 fread 指定 nmemb 为 2,则返回值为 1。如果写文件时出错,则 fwrite 的返回值小于 nmemb 指定的值。 struct t{ int a; short b; }; struct t val = {1, 2}; FILE *fp = fopen("file.txt", "w"); fwrite(&val, sizeof(val), 1, fp); fclose(fp); liwei:/tmp$ od -tx1 -tc -Ax file.txt 0000000 01 00 00 00 02 00 00 00 001 \0 \0 \0 002 \0 \0 \0 从结果可以看出,写入的是 8 个字节,有兴趣的同学可以就此分析下系统的「大小端」和结构体的「对齐补齐」问题。 七、格式化IO函数 (1). printf / scanf int printf(const char *format, ...); int scanf(const char *format, ...); 这两个函数是我们学习 C 语言最早接触,可能也是接触比较多的了,没什么特别要说的。printf 就是格式化打印到标准输出。下面总结下 printf 常用的方式。 printf("%d\n", 5); // 打印整数 5 printf("-%10s-\n", "hello") // 设置显示宽度并左对齐:- hello- printf("-%-10s-\n", "hello") // 设置显示宽度并右对齐:- hello- printf("%#x\n", 0xff); // 0xff 不加#则显示ff printf("%p\n", main); // 打印 main 函数首地址 printf("%%\n"); // 打印一个 % scanf 就是从标准输入中读取格式化数据,简单举个例子: int year, month, day; scanf("%d/%d/%d", &year, &month, &day); printf("year = %d, month = %d, day = %d\n", year, month, day); (2). sprintf / sscanf / snprintf sprintf 并不打印到文件,而是打印到用户提供的缓冲区中并在末尾加 '\0',由于格式化后的字符串长度很难预计,所以很可能造成缓冲区溢出,强烈推荐 snprintf 更好一些,参数 size 指定了缓冲区长度,如果格式化后的字符串超过缓冲区长度,snprintf 就把字符串截断到 size - 1 字节,再加上一个 '\0',保证字符串以 '\0' 结尾。如果发生截断,返回值是截断之前的长度,通过对比返回值与缓冲区实际长度对比就知道是否发生截断。 int sscanf(const char *str, const char *format, ...); int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...); sscanf 是从输入字符串中按照指定的格式去读取相应的数据,函数功能非常的强大,支持类似正则表达式匹配的功能。具体的使用格式请自行查询官方手册,这里总结出最常用、最重要的几种使用场景和方式。 最基本的用法 char buf[1024] = 0; sscanf("123456", "%s", buf); printf("%s\n", buf); // 结果为:123456 取指定长度的字符串 sscanf("123456", "%4s", buf); printf("%s\n", buf); // 结果为:1234 取第1个字符串 sscanf("hello world", "%s", buf); printf("%s\n", buf); // 结果为:hello 因为默认是以空格来分割字符串的,%s读取第一个字符串hello 读取到指定字符为止的字符串 sscanf("123456#abcdef", "%[^#]", buf); // 结果为:123456 // %[^#]表示读取到#符号停止,不包括# 读取仅包含指定字符集的字符串 sscanf("123456abcdefBCDEF", "%[1-9a-z]", buf); // 结果为:123456abcdef // 表达式是要匹配数字和小写字母,匹配到大写字母就停止匹配了。 读取指定字符集为止的字符串 sscanf("123456abcdefBCDEF", "%[^A-Z]", buf); // 结果为:123456abcdef 读取两个符号之间的内容(@和.之间的内容) sscanf("liwei0526vip@linuxblogs.cn", "%*[^@]@%[^.]", buf); // 结果为:linuxblogs // 先读取@符号前边内容并丢弃,然后读@,接着读取.符号之前的内容linuxblogs,不包含字符. 给一个字符串 sscanf("hello, world", "%*s%s", buf); // 结果为:world // 先忽略一个字符串"hello,",遇到空格直接跳过,匹配%s,保存 world 到 buf // %*s 表示第 1 个匹配到的被过滤掉,即跳过"hello,",如果没有空格,则结果为 NULL 稍微复杂点的 sscanf("ABCabcAB=", "%*[A-Z]%*[a-z]%[^a-z=]", buf); // 结果为:AB 自己尝试分析哈 包含特殊字符处理 sscanf("201*1b_-cdZA&", "%[0-9|_|--|a-z|A-Z|&|*]", buf); // 结果为:201*1b_-cdZA& 如果能将上述几个例子搞明白,相信基本上已经掌握了 sscanf 的用法,实践才是检验真理的唯一标准,只有多使用,多思考才能真正理解它的用法。 (3). fprintf / fscanf fprintf 打印到指定的文件 stream 中,fscanf 从文件中格式化读取数据,类似 scanf 函数。相关函数的声明如下: int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...); 还是通过简单实例来说明基本用法。 FILE *fp = fopen("file.txt", "w"); fprintf(fp, "%d-%s-%f\n", 32, "hello", 0.12); fclose(fp); liwei:/tmp$ cat file.txt 32-hello-0.120000 而 fscanf 函数的使用基本上与 sscanf 函数使用方式相同。 八、IO缓冲区 还有个关于 IO 非常重要的概念,就是 IO 缓冲区。 C 标准库为每个打开的文件分配一个 I/O 缓冲区,用户调用读写函数大多数都在 I/O 缓冲区中读写,只有少数请求传递给内核。 以 fgetc/fputc 为例,当第一次调用 fgetc 读一个字节时,fgetc 函数可能通过系统调用进入内核读 1k 字节到缓冲区,然后返回缓冲区中第一个字节给用户,以后用户再调用 fgetc,就直接从缓冲区读取。 另一方面,fputc 通常只是写到缓冲区中,如果缓冲区满了,fputc 就通过系统调用把缓冲区数据传递给内核,内核将数据写回磁盘。如果希望把缓冲区数据立即写入磁盘,可以调用 fflush 函数。 C 标准库 IO 缓冲区有三种类型:全缓冲、行缓冲和无缓冲区,不同类型的缓冲区具有不同的特性。 全缓冲:如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。 行缓冲:如果程序写的数据中有换行符就把这一行写回内核,或者缓冲区满就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。 无缓冲:用户程序每次调用库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,用户程序的错误信息可以尽快输出到设备。 printf("hello world"); while(1); // 运行程序会发现屏幕并没有打印hello world // 因为缓冲区没满,且没有\n符号 除了写满缓冲区、写入换行符之外,行缓冲还有一种情况会自动做 flush 操作,如果: 用户程序调用库函数从无缓冲的文件中读取 或从行缓冲的文件中读取,且这次读操作会引发系统调用从内核读取数据,那么会读之前自动 flush 所有行缓冲 程序退出时通常也会自动 flush 缓冲区 如果不想完全依赖自动的 flush 操作,可以调用 fflush 函数手动操作。若调用 fflush(NULL) 可以对所有打开文件的 IO 缓冲区做 flush 操作。缓冲区大小也可以自定义设置,一般情况无需设置,默认即可。
写在前面 今天下午一个同事问「register」关键字是什么作用?噢,你说的是「register」啊,它的作用是……脑袋突然断片儿,我擦,啥意思来着,这么熟悉的陌生感。做C语言开发时间也不短了,不过好像没有用到过「register」,但作用还是知道的,一下子想不起来了,一万个草泥马飞奔过来。 其实C语言中除了register外,还包含常见的const、static、volatile、auto、extern等修饰符,现在一起再总结一下好了。 register 修饰符 register,寄存器变量,告诉编译器它所声明的变量在程序中使用的频率非常高,请编译器尽量将此变量放在寄存器中,这样程序执行速度更快。但实际上编译器不一定这么做,可以忽略此选项。 register 修饰符的几点注意点: 变量必须是 CPU 接受的类型,单个值,长度小于等于整数的长度 只能使用于局部变量和函数形参,全局(register)变量是非法的 无论寄存器变量是否存放在寄存器中,它地址都是不能访问的(取&) 其实过量的寄存器声明并没有什么坏处,寄存器可以忽略 const 修饰符 const修饰普通变量 有时候我们希望定义一个变量,它的值在整个作用域都不能变,比如定义缓冲区大小等,可以用 const 来修饰。 // 定义常量 strlenconst int strlen = 4096; // 试图修改 strlen 变量,编译器会报错strlen = 2048; 一般常量在定义时同时进行初始化,否则在定义完之后不能对其进行赋值操作,常见的初始化方式有: const int num = get_num(); // 运行时初始化const int num = n; // 运行时初始化const int num = 10; // 编译时初始化 const 变量真的就不能修改吗?看个例子: const int bufsize = 1024;int *p = &bufsize;*p = 2048;printf("bufsize = %d\n", bufsize); 打印结果是2048。其实 const 修饰的变量不变的本质含义是程序中通过引用变量符号 bufsize 时不能够进行修改,而不是 bufsize 变量所指向那段内存数据不能修改。 const修改指针变量 const 可以与指针变量一起使用,可以限制指针变量,也可以限制指针变量指向的内容。 const int *ptr; // 指针指向内容不能修改int const *ptr; // 与第1种等价int* const ptr; // 指针ptr变量本身不能修改const int* const ptr; // 指针变量和指针变量指向内容都不能修改 const修改函数参数 其实C语言中使用 const 定义常量并没有什么优势,完全可以使用#define来替代。const 通常用在函数形参中,当形参是一个指针,为了防止函数内部修改指针指向的内容时,就可以用 const 限制。 size_t strlen(const char *s);int strcmp(const char *s1, const char *s2); 常见C语言标准库中都有const限制,在我们自定义函数中也可以适当使用 const 来保证程序的健壮性。 const 类型与非 const 类型转换 当一个指针类似const char *str1,表示str1指针指向内容不能修改;但如果将 str1 赋值给 str2,这时 str2 没有通过 const 限制,通过 str2 就可以修改指针指向内容,这就失去了 const 的意义,编译器是不提倡这么做的。 const 与非 const 是两种类型,将非 const 指针赋值给 const 指针,编译器接受;如果将 const 指针赋值给非 const 指针,这样将增加指针变量的权限,不安全,有可能发生写入的危险。所以我们在写程序时遵守,对指针类型尽可能加 const 修饰;不能将 const 指针赋值给 const 指针。 static 修饰符 static 修饰符在程序中使用最为广泛,它大概有如下几种用法: 修饰局部变量:增加了局部变量的生命周期,若定义未初始化,则默认初始化为0 修饰全局变量:缩小了全局变量的作用域,限制在本模块(文件)中访问 修饰函数:缩小了函数的作用于,限制函数只能被本模块调用 volatile 修饰符 关键字 volatile 感觉是和 register 有点相反的意思,表示变量随时可能被修改,且系统对实时性要求很高,请一定从内存中读取内容,不要直接拷贝寄存器中的数据,有可能数据老旧。常见的使用场合包括中断服务程序和嵌入式系统的寄存器相关操作。 extern 修饰符 关键字 extern 常用在变量和函数声明前,用来说明此变量或函数是在别处定义过的,要在此处引用。在 hello.c 中: void hello(){printf("Hello.\n");} 在 main.c 文件中: extern void hello();hello(); // 声明之后调用 hello 函数 在 main.c 文件被编译时,告诉编译器hello()在别的地方定义过了,这里只是引用一下,放心编译好了,在程序最后链接的时候会去找hello实际定义的函数。 auto 修饰符 关键字 auto 其实可以理解为就是局部变量的显示说明,程序中很少去显示声明某个变量为 auto 的。
0、写在前面 作为一名开发者,熟悉使用 git 代码管理工具是一项必备的基本技能。git 相较 SVN 而言,其优点不言而喻。git 的功能非常强大,其包括的操作命令也非常的多,但是从实用性而言,很多命令可能我们一辈子也用不到,这里我只记录一下自己经常使用的 git 命令,熟练使用了这些命令,其实已经可以完全得心应手的使用 git 工具了。我所使用的开发环境是在 CentOS6.5 系统,下边的操作命令都是在 CentOS6.5 上进行实验通过的。 一、GIT的初始化及配置 1.git安装 yum方式安装: $ yum install git -y apt方式安装: $ apt install git -y 源码方式安装: $ ./configure && make && make install 2.git全局配置 $ git config --global user.name "liwei0526vip" $ git config --global user.email "liwei0526vip@163.com" 通过git config命令的--global参数设置了git的用户名和用户邮箱,默认情况下这台机器上所有的git仓库都会使用这个配置,当然也可以对某个仓库指定具体不同的用户名和用户邮箱 3.初始化仓库 仓库,英文名repository。可以简单理解成一个目录,这个目录里面的所有文件都可以被git管理起来,每个文件的修改、删除,git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以"还原"。 $ mkdir learngit $ cd learngit $ git init Initialized empty Git repository in /root/learngit/.git/ $ ls -a . .. .git 可以发现当前目录下多了一个.git的目录,这个目录是git来跟踪管理版本库的,千万不要手动修改这个目录里面的文件,否则就把git仓库给破坏了 4.关于文件变动跟踪 首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如 TXT 文件,网页,所有的程序代码等等, Git 也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词 "Linux" ,在第8行删了一个单词"Windows"。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了什么,版本控制系统不知道,也没法知道。 5.关于编码 因为文本是有编码的,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。 二、代码提交 1.查看repo状态 $ git status 2.添加文件到缓存区 $ git add file1.txt $ git add file2.txt 3.提交到版本库 $ git commit -m "message" # 一定要写提交信息, 便于后续查看版本信息 三、管理修改 1.丢弃工作区的修改 $ git checkout -- file.txt 2.丢弃暂存区的修改 $ git reset HEAD file.txt # 丢弃暂存区的修改, 完毕后工作区内容未撤销 3.撤销提交(版本回退) $ git reset --hard HEAD^ 4.管理修改的举例 场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。 场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了场景1,第二步按场景1操作。 场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。 5.查看工作区与版本库的差异 $ git diff HEAD -- file.txt 6.查看提交版本日志 $ git log --pretty=oneline # --pretty选项更清晰显示 四、远程库操作 1.克隆远程库 $ git clone http://gitlab.staff.sina.com.cn/liwei42/gitver 2.添加远程库(给本地库) $ git remote add origin git@gitlab.staff.sina.com.cn:liwei42/gitver.git 3.推送远程库 $ git push -u origin master # 第一次推送加'-u'选项, 关联本地与origin的master分支 4.拉取远程库 $ git pull <远程主机名> <远程分支名>:<本地分支名> 在默认模式下,取回远程主机某个分支的更新,再与本地的指定分支合并。git pull是git fetch后跟git merge的缩写。 比如,要取回origin主机的next分支,与本地的master分支合并,需要写成下面这样: $ git pull origin next:master # 如果要与当前分支合并, 则冒号后面的部分可以省略 相当于: $ git fetch origin $ git merge origin/next 手动建立追踪关系: $ git branch --set-upstream master origin/next 上面命令指定master分支追踪origin/next分支。如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名 $ git pull origin # 如果当前分支只有一个追踪分支,连远程主机名都可以省略 五、分支管理 1.创建分支 $ git branch b1 2.切换分支 $ git checkout b1 # 切换到b1分支 $ git checkout -b b2 # 创建并切换到b2分支 3.合并分支 $ git merge dev # 合并dev分支到当前分支 $ git merge dev master # 合并dev分支到master分支(可能当前分支并没有在master上) $ git --no-ff merge dev # 合并时不使用ff方式, 这样会在合并分支上保留提交的版本信息 六、分支策略 1.主分支master 代码库应该有一个、且仅有一个主分支。所有提供给用户使用的正式版本,都在这个主分支上发布。 2.开发分支develop 主分支只用来分布重大版本,日常开发应该在另一条分支上完成。这个分支可以用来生成代码的最新隔夜版本(nightly)。如果想正式对外发布,就在Master分支上,对Develop分支进行"合并" 3.临时性分支 功能(feature)分支 预发布(release)分支 修补bug(fixbug)分支 4.功能分支 它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。 5.预发布分支 它是指发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从Develop分支上面分出来的,预发布结束以后,必须合并进Develop和Master分支。它的命名,可以采用release-*的形式。 6.修补bug分支 软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。它的命名,可以采用fixbug-*的形式
有一天,我突发奇想创建了一个站点,基于 LNMP 架构,起初只有我自己访问,后来因为我点儿正,访问量越来越大,所以最终导致下面的架构演变。 1、单台机器 单台机器因为只是一个小站,访问量一天也没有多少uv(100以内),所以用一台1核1g的机器足够了。机器上安装的是 CentOS 系统,然后搭建了 nginx+php-fpm+mysql 的环境。 2、一台变两台 访问量越来越大,日uv突破5000,单台机器不够了,本可以增加机器配置编程4核8G,但是考虑到还要换机器,所以直接添置一台 DB 服务器单独跑 MySQL服务。原来的服务器只需要跑 nginx+php-fpm,新加服务器跑 MySQL 服务。 在这里,往往会遇到一个问题,就是如何在多台机器上编译安装 LAMP 环境,在单台机器上编译都没有问题,PHP 放在最后,因为它依赖 MySQL,但我们这里需要把 MySQL 放到另一台机器,所以编译肯定会报错。解决这个问题,其实很简单,即使 WEB 上不需要 MySQL,我们也要安装一下,因为编译 PHP 的时候依赖它。 3、增加memcached 访问量持续增加,uv上w了,DB 服务器和 WEB 服务器压力越来越大,这时候我们需要加一个缓存来缓解 DB 服务器的压力。同样是两台机器,只不过 WEB机器配置需要升级了,原来的1核1g不够用了,不仅要加 cpu 还要加内存,因为在 WEB 上我们需要运行 memcached 服务,同时 php 也需要安装memcache 扩展。 4、增加WEB并做MySQL主从 访问量又扩大了,uv到了5w,数据库服务器因为一开始配置就挺高,所以没有压力,但是 WEB 服务器负载有点高了,在高峰期可以感觉到网站访问变慢。所以,这时候不得不考虑要加一台 WEB 服务器。另外,数据库是单点,如果磁盘损坏,可能会带来意想不到的后果,所以我们有必要加一台从 DB 服务器,作为数据的备份。 在这里,两台 WEB 服务器我们并没有做负载均衡,因为为了节省资源,暂时先不去购买服务器做负载均衡,我们使用 DNS 轮询的方法来把用户的请求发到两台机器上,但这种该架构有个问题,一旦一台 WEB 机器宕机,将会有一半的用户访问不到业务。还有一个问题,我们也需要考虑到,如何保证 WEB 服务器上的数据一致,比如用户可能会上传图片到 WEB 服务器上,假如他上传到了 WEB1 上,那 WEB2 是不存在这个图片的。所以我们需要做一个共享存储让 WEB1 和 WEB2 同时可以访问,所以在这里我把 WEB1 的一个目录使用 NFS 共享出来,让 WEB2 去挂载。还有一个问题就是memcached服务如何分配,在这里,我是把 memcaced 服务分别安装到两台 WEB 上的,自己用自己的 memcached 服务。 5、MySQL读写分离 访问量持续上升,uv 已经到了数十万。网站在高峰期总是会卡顿那么一段时间。经排查,发现在 MySQL 服务器上有很多慢查询,经过各种调优依然没有太明显效果,最后决定做读写分离。 做读写分离有两种方案,第一可以借助程序来实现,把所有的写操作指向到主 MySQL ,所有的读操作指向到从 MySQL。对于这种方案,机器数量和环境不用做任何调整,唯一要做的是程序代码要改一下。第二可以借助 mysql-proxy 来实现,不用修改代码,节省开发成本,但需要增加一个角色。架构是这样的。 6、避免单点引入负载均衡 两台 WEB 服务器因为有一台比较老,所以在高峰期时,终究是没有能扛住而挂掉。结果影响了一半的用户访问不到网站了。经过此次事故,我不得不修改架构,尽量避免单点,于是在 WEB 前端设置了负载均衡器,并且做了高可用。 在这里我拿 nginx 做了负载均衡器,并没有使用 lvs,因为我觉得 nginx 更容易操作,更好控制。为了节省成本,我并没有单独把 mysql-proxy 摘出来作为独立服务器,因为那样的话,也得为它考虑单点问题。在这个架构中,其实还有一个缺陷,就是 NFS 服务端也是有风险的,更加保险的做法是单独搞一台服务器做NFS服务。 7、继续扩充 uv上升到100w,两台 WEB 服务器明显不够用了,而瓶颈并不在 MySQL 上。所以,只增加 WEB,同时把 NFS 服务器单独摘出来,并做一个备用 NFS 服务器。 8、引入NoSQL uv近1000w,三台 WEB 服务器也早已不够,增加到5台,而 MySQL 服务器压力逐渐变大,针对 MySQL 的慢查询,发现压力主要体现在个别 SQL 语句上,该优化的已经优化到极致,对于这几个查询,其实是可以使用 NoSQL 的。于是,我找懂php开发的朋友帮我修改了程序,把一些访问量大的数据存储到redis,从而减少了对 MySQL 服务器的压力。 而 Redis 为了防止单点也做了主从。 9、MySQL架构演变 http://www.cnblogs.com/liwei0526vip/p/6424605.html
和LAMP不同的是LNMP中的N指的是Nginx(类似于Apache的一种web服务软件)其他都一样。目前这种环境应用的也是非常之多。Nginx设计的初衷是提供一种快速高效多并发的web服务软件。在静态页面的处理上Nginx的确胜Apache一筹,然而在动态页面的处理上Nginx并不比Apache有多少优势。但是,目前还是有很多爱好者对Nginx比较热衷,随着Nginx的技术逐渐成熟,它在web服务软件领域的地位越来越高。下边记录一下LNMP架构搭建的详细过程。 一、MySQL数据库的安装 1. 系统环境 CentOS 6.4 x86_64 Mini 版本安装 2. 基础软件包安装 [root@vip ~]# yum install gcc vim make wget -y 3. 下载 # 进入源码存放目录 [root@vip ~]# cd /usr/local/src # 下载MySQL安装包 [root@vip src]# wget downloads.mysql.com/archives/get/file/mysql-5.5.40-linux2.6-x86_64.tar.gz 4. 解压安装 # 解压 [root@vip src]# tar -zxf mysql-5.5.40-linux2.6-x86_64.tar.gz # 设置安装路径 [root@vip src]# mv mysql-5.5.40-linux2.6-x86_64 /usr/local/mysql 5. 建立MySQL用户 [root@vip src]# useradd -s /sbin/nologin -M mysql 6. 准备数据目录 # 进入MySQL安装目录 [root@vip src]# cd /usr/local/mysql # 创建MySQL数据目录 [root@vip mysql]# mkdir -p /var/lib/mysql # 设置目录权限 [root@vip mysql]# chown -R mysql:mysql /var/lib/mysql 7. 初始化数据库 [root@vip mysql]# ./scripts/mysql_install_db --user=mysql --datadir=/var/lib/mysql when specifying MySQL privileges ! Installing MySQL system tables... OK Filling help tables... OK #看到2个OK说明初始化成功 8. 拷贝配置文件 [root@vip mysql]# /bin/cp support-files/my-large.cnf /etc/my.cnf 9. 拷贝启动脚本 # 拷贝启动脚本 [root@vip mysql]# /bin/cp support-files/mysql.server /etc/init.d/mysqld # 赋予可执行权限 [root@vip mysql]# chmod 755 /etc/init.d/mysqld 10. 修改启动脚本 [root@vip mysql]# vim /etc/init.d/mysqld # 修改设置内容如下 basedir=/usr/local/mysql datadir=/var/lib/mysql 11. 把MySQL添加到服务 # 添加到service列表 [root@vip mysql]# chkconfig --add mysqld # 设置开机启动 [root@vip mysql]# chkconfig mysqld on 12. 启动MySQL服务 [root@vip mysql]# service mysqld start Starting MySQL... SUCCESS! 13. 查看验证MySQL启动进程 [root@vip mysql]# ps -e | grep mysql 1830 pts/1 00:00:00 mysqld_safe 2121 pts/1 00:00:00 mysqld 14. 配置MySQL环境变量 将 MySQL 客户端命令路径加入 PATH 环境变量中去。 # 设置PATH环境变量 [root@vip mysql]# echo 'export PATH=$PATH:/usr/local/mysql/bin' > /etc/profile.d/mysql.sh [root@vip mysql]# source /etc/profile.d/mysql.sh 15. 登录MySQL测试 [root@vip mysql]# mysql # 默认没有密码 Your MySQL connection id is 1 Server version: 5.5.40-log MySQL Community Server (GPL) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. ... ... mysql> 二、PHP服务安装 说明下,针对Nginx的php安装和针对apache的php安装是有区别的,因为Nginx中的php是以fastcgi的方式结合nginx的。可以理解为nginx代理了php的fastcgi,而apache是把php最为自己的模块来调用的。 1、下载解压php压缩包 cd /usr/local/src wget http://cn2.php.net/get/php-5.5.38.tar.gz/from/this/mirror mv mirror php-5.5.38.tar.gz tar zxf php-5.5.38.tar.gz 2、创建php账号 useradd -s /sbin/nologin php-fpm -M 该账号用来运行php-fpm服务,在LNMP环境中,php是以一个服务来提供服务的。 3、配置编译选项 yum install -y libcurl-devel libtool-ltdl-devel yum install -y pcre pcre-devel apr apr-devel zlib-devel gcc make 配置编译选项: cd php-5.5.38 ./configure --prefix=/usr/local/php \ --with-config-file-path=/usr/local/php/etc \ --enable-fpm --with-fpm-user=php-fpm \ --with-fpm-group=php-fpm --with-mysql=/usr/local/mysql \ --with-mysql-sock=/tmp/mysql.sock --with-libxml-dir \ --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir \ --with-iconv-dir --with-zlib-dir --with-mcrypt --enable-soap \ --enable-gd-native-ttf --enable-ftp --enable-mbstring \ --enable-exif --enable-zend-multibyte --disable-ipv6 \ --with-pear --with-curl --with-openssl 4、编译安装PHP make make install 5、修改配置文件 cp php.ini-production /usr/local/php/etc/php.ini cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf vim /usr/local/php/etc/php-fpm.conf 把如下内容写入该文件: [global] pid = /usr/local/php/var/run/php-fpm.pid error_log = /usr/local/php/var/log/php-fpm.log [www] listen = 127.0.0.1:9000 user = php-fpm group = php-fpm pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.max_requests = 500 rlimit_files = 1024 保存配置文件后,检验配置是否正确的方法为: /usr/local/php/sbin/php-fpm -t 6、启动php-fpm 首先要拷贝一个启动脚本到/etc/init.d/下: cp /usr/local/src/php-5.3.27/sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm 给它更改权限为755 chmod 755 /etc/init.d/php-fpm chkconfig --add php-fpm service php-fpm start 设置开机启动,执行: chkconfig php-fpm on 检测是否启动 ps aux | grep php-fpm 三、Nginx编译安装 1、下载、解压Nginx cd /usr/local/src wget http://nginx.org/download/nginx-1.8.0.tar.gz tar zxvf nginx-1.8.0.tar.gz 2、配置编译选项 cd nginx-1.8.0 ./configure --prefix=/usr/local/nginx \ --with-http_realip_module --with-http_sub_module \ --with-http_gzip_static_module \ --with-http_stub_status_module --with-pcre 3、编译安装Nginx make make install 4、启动nginx /usr/local/nginx/sbin/nginx 检查nginx是否启动: ps aux | grep nginx 四、测试PHP解析 首先配置 nginx 配置文件,使其能够支持php。 vim /usr/local/nginx/conf/nginx.conf 找到: location = /50x.html { root html; } 在其后面增加如下配置: location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name; include fastcgi_params; } 重新加载: /usr/local/nginx/sbin/nginx -s reload 创建测试文件: vim /usr/local/nginx/html/2.php <?php echo "test php scripts."; ?> 测试: curl localhost/2.php test php scripts. 显示成这样,才说明 PHP 解析正常。 五、Nginx启动脚本和配置文件 1、编写启动脚本并加入系统服务 vim /etc/init.d/nginx 写入如下内容: #!/bin/bash # chkconfig: - 30 21 # description: http service. # Source Function Library . /etc/init.d/functions # Nginx Settings NGINX_SBIN="/usr/local/nginx/sbin/nginx" NGINX_CONF="/usr/local/nginx/conf/nginx.conf" NGINX_PID="/usr/local/nginx/logs/nginx.pid" RETVAL=0 prog="Nginx" start() { echo -n $"Starting $prog: " mkdir -p /dev/shm/nginx_temp daemon $NGINX_SBIN -c $NGINX_CONF RETVAL=$? echo return $RETVAL } stop() { echo -n $"Stopping $prog: " killproc -p $NGINX_PID $NGINX_SBIN -TERM rm -rf /dev/shm/nginx_temp RETVAL=$? echo return $RETVAL } reload(){ echo -n $"Reloading $prog: " killproc -p $NGINX_PID $NGINX_SBIN -HUP RETVAL=$? echo return $RETVAL } restart(){ stop start } configtest(){ $NGINX_SBIN -c $NGINX_CONF -t return 0 } case "$1" in start) start ;; stop) stop ;; reload) reload ;; restart) restart ;; configtest) configtest ;; *) echo $"Usage: $0 {start|stop|reload|restart|configtest}" RETVAL=1 esac exit $RETVAL 保存后,更改权限: chmod 755 /etc/init.d/nginx chkconfig --add nginx chkconfig nginx on 2、更改整理nginx配置 首先清空原来的配置文件: > /usr/local/nginx/conf/nginx.conf 快速清空文档。 编辑文件写入如下内容: # vim /usr/local/nginx/conf/nginx.conf user nobody nobody; worker_processes 2; error_log /usr/local/nginx/logs/nginx_error.log crit; pid /usr/local/nginx/logs/nginx.pid; worker_rlimit_nofile 51200; events { use epoll; worker_connections 6000; } http { include mime.types; default_type application/octet-stream; server_names_hash_bucket_size 3526; server_names_hash_max_size 4096; log_format combined_realip '$remote_addr $http_x_forwarded_for [$time_local]' '$host "$request_uri" $status' '"$http_referer" "$http_user_agent"'; sendfile on; tcp_nopush on; keepalive_timeout 30; client_header_timeout 3m; client_body_timeout 3m; send_timeout 3m; connection_pool_size 256; client_header_buffer_size 1k; large_client_header_buffers 8 4k; request_pool_size 4k; output_buffers 4 32k; postpone_output 1460; client_max_body_size 10m; client_body_buffer_size 256k; client_body_temp_path /usr/local/nginx/client_body_temp; proxy_temp_path /usr/local/nginx/proxy_temp; fastcgi_temp_path /usr/local/nginx/fastcgi_temp; fastcgi_intercept_errors on; tcp_nodelay on; gzip on; gzip_min_length 1k; gzip_buffers 4 8k; gzip_comp_level 5; gzip_http_version 1.1; gzip_types text/plain application/x-javascript text/css text/htm application/xml; include vhosts/*.conf; } 继续如下操作: cd /usr/local/nginx/conf mkdir vhosts cd vhosts vim default.conf 写入如下内容: server { listen 80 default; server_name localhost; index index.html index.htm index.php; root /tmp/1233; deny all; } 编辑www.123.com.conf文件,写入如下内容: server { listen 80; server_name www.123.com; index index.html index.htm index.php; root /usr/local/apache2/htdocs; location ~ \.php$ { include fastcgi_params; #fastcgi_pass unix:/tmp/php-fcgi.sock; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /usr/local/apache2/htdocs$fastcgi_script_name; } } 保存配置后,先检验一下配置文件是否有错误存在: /usr/local/nginx/sbin/nginx -t 启动nginx服务: service nginx start 查看是否有进程: ps aux | grep nginx 六、php-fpm配置文件 清空原始配置文件: > /usr/local/php/etc/php-fpm.conf 编辑配置文件: vim /usr/local/php/etc/php-fpm.conf 写入如下内容: [global] pid = /usr/local/php/var/run/php-fpm.pid error_log = /usr/local/php/var/log/php-fpm.log [www] listen = 127.0.0.1:9000 user = php-fpm group = php-fpm listen.owner = nobody listen.group = nobody pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.max_requests = 500 rlimit_files = 1024 配置说明: global部分是全局配置,指定pid文件路径以及error_log路径。[www]是一个pool,其实还可以再写第二个pool,第二个pool和第一个不一样的地方,首先pool的name,比如叫做[www2]。然后listen肯定就不能一样了,比如可以listen=/tmp/php-fcgi2.sock。而user,group也可以和[www]中定义的不一样。listen.owner这个是定义/tmp/php-fcgi.sock这个文件的所有者是谁,在php5.4版本之后监听的socket文件权限默认变成了 rw-------,如果不定义listen.owner那么nginx调用这个socket的时候就没有权限了,故在这里我们定义listen.owner为nginx的子进程监听用户。 pm = dynamic 表示以动态的形式启动,在php5.3 版本以后它可以支持动态和静态了,如果是静态,即 pm=static时,下面的配置只有pm.max_children管用。pm.max_children 表示启动几个 php-fpm 的子进程。如果是 dynamic,下面的配置会生效,pm.max_children 表示最大可以启动几个子进程。pm.start_servers 表示一开始启动几个子进程。pm.min_spare_servers 表示当 php-fpm 空闲时最少要有几个子进程,即如果空闲进程小于此值,则创建新的子进程。pm.max_spare_server 表示当 php-fpm 空闲时最多有几个子进程,即如果空闲进程大于此值,则会进行清理。pm.max_requests 表示一个子进程最多可以接受多少个请求,比如设置为 500 那么一个子进程受理 500 个请求后自动销毁。rlimit_files 表示每个子进程打开的多少个文件句柄。 七、常见的502错误问题 1、配置错误 因为nginx找不到php-fpm了,所以报错,一般是fastcgi_pass后面的路径配置错误了,后面可以是socket或者是127.0.0.1:900。要注意与php-fpm.conf配置文件中[listen]字段值保持一致。 2、资源耗尽 lnmp架构在处理php时,nginx直接调取后端的php-fpm服务,如果nginx的请求量偏高,我们又没有给php-fpm配置足够的子进程,那么php-fpm就会资源耗尽,一旦资源耗尽nginx找不到php-fpm就会出现502错误?? 解决方案:去调整php-fpm.conf中的pm.max_children数值,使其增加,但是也不能无限增加,毕竟资源有限,一般4G内存机器如果跑php-fpm和nginx,不跑mysql可以设置为150,8G为300以此类推 3、其它原因 除了上面的两种错误还有其他的原因,很少有,我们可以借助nginx的错误日志来进行排查vim /usr/local/nginx/logs/nginx_error.log 我们也可以给日志定义级别vim/usr/local/nginx/conf/nginx.conf 找到error_log,默认是crit最严谨的就行,也可以改成debug显示的信息最全面,但是很容易撑爆我们的磁盘。检查nginx是以哪个用户身份运行 ps aux | grep nginx vim /usr/local/php/etc/php-fpm.conf [www] listen.owner = nobody listen.group = nobody 使用版本高于5.4(含5.4),默认监听的sock文件权限是所有者只读,用户组和其它用户没有任何权限。所以nginx的启动用户nobody没有办法读这个socket文件,最终导致502,这个问题可以在nginx的错误日志中发现。解决办法很简单,上面给出的配置文件就有避免这个问题的配置。 listen.owner = nobody //定义属主 listen.group = nobody //定义属组 这两个配置就是定义socket的属主和属组是谁。除了这个还有一种方法这样nobody也可以有读取权限了。 listen.mode = 777
MySQL 主从(MySQL Replication),主要用于 MySQL 的实时备份、高可用HA、读写分离。在配置主从复制之前需要先准备 2 台 MySQL 服务器。 一、MySQL主从原理 1. 每个从仅可以设置一个主。2. 主在执行 SQL 之后,记录二进制 LOG 文件(bin-log)。3. 从连接主,并从主获取 binlog,存于本地 relay-log,并从上次记住的位置起执行 SQL,一旦遇到错误则停止同步。 二、Replication原理推论 1. 主从间的数据库不是实时同步,就算网络连接正常,也存在瞬间,主从数据不一致。2. 如果主从的网络断开,从会在网络正常后,批量同步。3. 如果对从进行修改数据,很可能从在执行主的bin-log出错而停止同步,一般不会修改从的数据。4. 一个衍生的配置是双主,互为主从配置,只要双方的修改不冲突,可以工作良好。5. 如果需要多主的话,可以用环形配置,这样任意一个节点的修改都可以同步到所有节点。6. 可以应用在读写分离的场景中,用以降低单台 MySQL 服务器的 I/O。7. 可以实现 MySQL 服务的 HA 集群。8. 可以是一主多从,也可以是相互主从(主主)。 三、实验环境 操作系统:CentOS 6.8_x64Mysql版本:5.1.73(主从版本要一致)Mysql安装:yum安装的方式主 IP 地址:192.168.0.8(master)从 IP 地址:192.168.0.18(slave) 四、主从的基本配置 1、对master的设置 修改 master 数据库的配置文件,vim /etc/my.cnf [mysqld] ... ... ... ... log-bin=mysql-bin # 二进制日志名称,开启bin-log server-id=8 # 为服务器设置一个独一无二的id,这里用IP的最后一位。 重启 master 数据库服务: service mysqld restart 2、对slave的设置 对于 slave 的设置,不需要开启二进制日志,仅需要设置以下 server-id 即可。 server-id=18 重启从服务区器。 五、创建主从复制账号 为了让 slave 能够通过 master 来获取二进制日志,需要专门给 slave 创建一个用户 repl,在主上操作。 mysql> grant replication slave on *.* to 'repl'@'192.168.0.18' identified by '123456'; Query OK, 0 rows affected (0.00 sec) 六、查看主服务器BIN日志的信息 执行完之后记录下这两值,然后在配置完从服务器之前不要对主服务器进行任何操作,因为每次操作数据库时这两值会发生改变。 mysql> show master status; +------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000001 | 259 | | | +------------------+----------+--------------+------------------+ 七、设置从服务器并启用slave 从上执行如下代码: mysql> change master to -> master_host="192.168.0.8", -> master_user="repl", -> master_password="123456", -> master_log_file="mysql-bin.000001", -> master_log_pos=248; 在从服务器配置完成,启动从服务器: mysql> start slave; Query OK, 0 rows affected (0.00 sec) 查看主从设置是否成功: mysql> show slave status\G; ... ... ... ... Slave_IO_Running: Yes Slave_SQL_Running: Yes 上面两项均为yes,说明配置成功。 八、测试主从 在主节点上创建一个数据库test或一张表table,然后在从节点上查看是否有test数据库或table表的创建。
假设一个网站(discuz)从最开始访问量很小做到日pv千万,我们来推测一下它的mysql服务器架构演变过程。第一阶段网站访问量日pv量级在1w以下。单台机器跑web和db,不需要做架构层调优(比如,不需要增加memcached缓存)。此时,数据往往都是每日冷备份的,但有时候如果考虑数据安全性,会搭建一个mysql主从。第二阶段网站访问量日pv达到几万。此时单台机器已经有点负载,需要我们把web和db分开,需要搭建memcached服务作为缓存。也就是说,在这个阶段,我们还可以使用单台机器跑mysql去承担整个网站的数据存储和查询。如果做 MySQL 主从,目的也是为了数据安全性。第三阶段网站访问量日pv达到几十万。单台机器虽然也可以支撑,但是需要的机器配置要比之前的机器好很多。如果经费允许,可以购买配置很高的机器来跑mysql服务,但是并不是说,配置翻倍,性能也翻倍,到了一定阶段配置增加已经不能带来性能的增加。所以,此阶段,我们会想到做mysql服务的集群,也就是说我们可以拿多台机器跑MySQL。但,MySQL的集群和web集群是不一样的,我们需要考虑数据的一致性,所以不能简单套用做web集群的方式(lvs,nginx代理)。可以做的架构是,mysql主从,一主多从。为了保证架构的健壮和数据完整,主只能是一个,从可以是多个。还有一个问题,我们需要想到,就是在前端web层,我们的程序里面指定了MySQL机器的ip,那么当mysql机器有多台时,程序里面如何去配置?discuz,其实有一个功能,支持MySQL读写分离。即,我们可以拿多台机器跑MySQL,其中一台写,其他多台是读,我们只需要把读和写的 IP 分别配置到程序中,程序自动会去区分机器。当然,如果不使用 discuz 自带的配置,我们还可以引用一个软件叫做 mysql-proxy, 使用他来实现读写分离。它支持一主多从的模式。第四阶段网站访问量日pv到几百万。之前的一主多从模式已经遇到瓶颈,因为当网站访问量变大,读数据库的量也会越来越大,我们需要多加一些从进来,但是从的数量增加到数十台时,由于主需要把bin-log全部分发到所有从上,那么这个过程本身就是一件很繁琐的事情,再加上频繁读取,势必会造成从上同步过来的数据有很大延迟。所以,我们可以做一个优化,把mysql原来的一主多从变为一主一从,然后从作为其他从的主,而前面的主只负责网站业务的写入,而后面的从不负责网站任何业务,只负责给其他从同步bin-log。这样还可以继续多叠加几个从库。第五阶段网站访问量日pv到1千万的时候,我们发现,网站的写入量非常大,我们之前架构中只有一个主,这里的主已经成为瓶颈了。所以,需要再近一步做出调整。比如,我们可以把业务分模块,把用户相关的单独分离出来,把权限、积分等也可以分离出来单独跑一个库,然后再做主从,也就是所谓的分库。当然也可以换一个纬度,把访问量或者写入量大的表单独分离出来,跑在一台服务器上,也可以把一个表分成多个小表。这一步操作,涉及到一些程序上的改动,所以需要事先和开发同事做好沟通和设计。总之,这一步要做的就是分库分表。写在后面再往后发展,继续把大表分小表即可。 而国内阿里淘宝网站的数据量是巨量的,他们的数据库全部都是 MySQL,他们的 MySQL 架构就是遵循分库分表这个原则的,只不过他们划分规则会有很多纬度,比如可以根据地域划分,可以根据买家、卖家划分,可以根据时间划分等等。
前边写了一篇使用 PXE 的方式批量安装操作系统,不是任何时候任何地方都有环境来通过 PXE 方式来进行安装。如果此时需要通过光盘安装,默认的情况下是通过交互式方式进行安装,其实也可以通过 kickstart 的方式来实现自动化安装部署。光盘通过 ks.cfg 进行安装的实现方式比较简单,下边简单的进行总结。 一、实现原理 光盘通过读取 ks.cfg 文件来实现安装操作系统,ks.cfg 配置文件放在光盘的根目录即可,然后修改 isolinux/isolinux.cfg 文件,设置内核参数,指定 ks.cfg 文件的位置即可。由于原始 iso 镜像文件是只读的,不能直接在 iso 光盘目录文件内进行修改,需要拷贝到一个临时目录,修改完后在封装为 iso 镜像文件。 二、拷贝镜像临时目录 mkdir /mnt/cdrom mount -o loop CentOS-6.8-x86_64-minimal.iso /mnt/cdrom cp -ar /mnt/cdrom/ /root/iso # 原来root下没有iso目录,拷贝过来重命名为iso 三、生成 ks.cfg 文件 生成 ks.cfg 文件的方式大概有2种,一是可以通过图形工具 system-config-kickstart 来定制生成指定的 ks.cfg 文件,二是对于熟悉 kickstart 语法的可以直接编写 ks.cfg 配置文件。本次实验用的 ks.cfg 配置比较简单,是通过工具生成的,下边给出本次的 ks.cfg 文件。 #platform=x86, AMD64, or Intel EM64T #version=DEVEL # Firewall configuration firewall --disabled # Install OS instead of upgrade install # Use CDROM installation media cdrom # Root password rootpw --iscrypted $1$p6oEoqGo$UDHZdzw56Rl6Rt5oi1A0Q1 # System authorization information auth --useshadow --passalgo=sha512 # Use graphical install graphical # System keyboard keyboard us # System language lang en_US # SELinux configuration selinux --disabled # Do not configure the X Window System skipx # Installation logging level logging --level=info # Reboot after installation #reboot # System timezone timezone --isUtc Asia/Shanghai # Network information network --bootproto=dhcp --device=eth0 --onboot=on # System bootloader configuration bootloader --location=mbr # Clear the Master Boot Record zerombr # Partition clearing information clearpart --all --initlabel # Disk partitioning information part /boot --asprimary --fstype="ext4" --ondisk=sda --size=200 part swap --asprimary --fstype="swap" --ondisk=sda --size=4096 part / --asprimary --fstype="ext4" --grow --ondisk=sda --size=1 拷贝到光盘镜像根目录: /bin/cp ks.cfg /root/iso/ 四、修改启动项菜单内核参数 修改菜单项配置文件 isolinux/isolinux.cfg: default vesamenu.c32 #prompt 1 timeout 1 # 超时自动选择菜单时间设置,设置为1时,即一闪而过,设置3秒为好。 修改内核参数,指定 ks.cfg 位置: label linux menu label ^Install CentOS 6.8 x64 System. # 自定义了菜单 menu default kernel vmlinuz append initrd=initrd.img ks=cdrom:/ks.cfg # 添加了ks文件的位置:光盘的根目录 五、封装iso镜像文件 cd /root/iso/ # 进入镜像制作目录 如果没有 mkisofs 命令,执行安装: yum install mkisofs -y 执行封装镜像的命令: mkisofs -o /root/CentOS6.8_x64.iso \ -V centos6 -b isolinux/isolinux.bin \ -c isolinux/boot.cat \ -no-emul-boot -boot-load-size 4 \ -boot-info-table -R -J -T -v . 校验并写入 md5 值(可选): implantisomd5 /root/CentOS6.8_x64.iso 通过光盘实现自动化安装已经完成制作,接下来测试可以通过虚拟机,导入 iso 镜像来做测试。
MySQL 数据库的使用是非常的广泛,稳定性和安全性也非常好,经历了无数大小公司的验证。仅能够安装使用是远远不够的,MySQL 在使用中需要进行不断的调整参数或优化设置,才能够发挥 MySQL 的最大作用。下边的内容是我在工作中经验的总结,也作为自己的工作笔记,如果能够帮助到有需要的同志就更好了。MySQL 的优化可以从个方面来做: 一、架构层面 1、做主从复制。2、实现读写分离。3、分库分表。 二、系统层面 1、增加内存。2、硬盘使用固态硬盘 SSD。3、给磁盘做 raid0 或者 raid5 以增加磁盘的读写速度。4、可以重新挂载磁盘,并加上 noatime 参数,这样可以减少磁盘的 I/O。 三、MySQL本身的优化 1、如果未配置主从同步,可以把 bin-log 功能关闭,减少磁盘 I/O。2、在 my.cnf 中加上 skip-name-resolve ,这样可以避免由于解析主机名延迟造成 M有SQL 执行慢。3、调整几个关键的 buffer 和 cache。调整的依据,主要根据数据库的状态来调试。如何调优可以参考五。4、根据具体的使用场景,选择合适的存储引擎。 四、应用层次 查看慢查询日志,根据慢查询日志优化程序中的 SQL 语句,比如增加索引 五、调整关键的buffer和cache 1、key_buffer_size 首先可以根据系统的内存大小设定它,大概的一个参考值:1G以下内存设定 128M;2G/256M; 4G/384M; 8G/1024M;16G/2048M。这个值可以通过检查状态值 Key_read_requests 和 Key_reads,可以知道 key_buffer_size 设置是否合理。比例 key_reads / key_read_requests 应该尽可能的低,至少是 1:100,1:1000更好(上述状态值可以使用 SHOW STATUS LIKE 'key_read%' 获得)。注意:该参数值设置的过大反而会是服务器整体效率降低! 2、table_open_cache 打开一个表的时候,会临时把表里面的数据放到这部分内存中,一般设置成 1024 就够了,它的大小我们可以通过这样的方法来衡量: 如果你发现 open_tables 等于 table_cache,并且 opened_tables 在不断增长,那么你就需要增加 table_cache 的值了(上述状态值可以使用 SHOW STATUS LIKE 'Open%tables' 获得)。注意,不能盲目地把 table_cache 设置成很大的值。如果设置得太高,可能会造成文件描述符不足,从而造成性能不稳定或者连接失败。 3、sort_buffer_size 查询排序时所能使用的缓冲区大小,该参数对应的分配内存是每连接独占! 如果有 100 个连接,那么实际分配的总共排序缓冲区大小为100 × 4 = 400MB。所以,对于内存在 4GB 左右的服务器推荐设置为:4-8M。 4、read_buffer_size 读查询操作所能使用的缓冲区大小。和 sort_buffer_size 一样,该参数对应的分配内存也是每连接独享! 5、join_buffer_size 联合查询操作所能使用的缓冲区大小,和 sort_buffer_size 一样,该参数对应的分配内存也是每连接独享! 6、myisam_sort_buffer_size 这个缓冲区主要用于修复表过程中排序索引使用的内存或者是建立索引时排序索引用到的内存大小,一般 4G 内存给 64M 即可。 7、query_cache_size MySQL查询操作缓冲区的大小,通过以下做法调整:SHOW STATUS LIKE ‘Qcache%’; 如果Qcache_lowmem_prunes该参数记录有多少条查询因为内存不足而被移除出查询缓存。通过这个值,用户可以适当的调整缓存大小。如果该值非常大,则表明经常出现缓冲不够的情况,需要增加缓存大小Qcache_free_memory:查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,是多了,还是不够用,我们可以根据实际情况做出调整。一般情况下 4G 内存设置 64M 足够了。 8、thread_cache_size 表示可以重新利用保存在缓存中线程的数,参考如下值:1G —> 8; 2G —> 16; 3G —> 32; 3G —> 64除此之外,还有几个比较关键的参数 9、thread_concurrency 这个值设置为 CPU 核数的2倍即可。 10、wait_timeout 表示空闲的连接超时时间,默认是:28800s,这个参数是和 interactive_timeout 一起使用的,也就是说要想让 wait_timeout 生效,必须同时设置 interactive_timeout,建议他们两个都设置为10。 11、max_connect_errors 是一个 MySQL 中与安全有关的计数器值,它负责阻止过多尝试失败的客户端以防止暴力破解密码的情况。与性能并无太大关系。为了避免一些错误我们一般都设置比较大,比如说10000。 12、max_connections 最大的连接数,根据业务请求量适当调整,设置 500 足够。 13、max_user_connections 是指同一个账号能够同时连接到 mysql 服务的最大连接数。设置为 0 表示不限制。通常我们设置为 100 足够。 ----- 待更新 -----
Apache 是一款使用量排名第一的 web 服务器,LAMP 中的 A 指的就是它。由于其开源、稳定、安全等特性而被广泛使用。前边的一篇文章中已经记录过如何搭建 LAMP 架构,搭建仅是第一步,其中最为重要的就是 Apache 服务,也是 LAMP 的核心。下边记录了使用 Apache 以来经常用到的功能。 一、Apache的三种工作模式 Apache 一共有3种稳定的 MPM 模式(多进程处理模块),它们分别是 prefork、worker、event。http-2.2版本的httpd默认的mpm工作模式为prefork,2.4版本的httpd默认是event工作模式。可以通过 httpd -V 来查看。 [root@linuxblogs ~]# httpd -V | grep -i "server mpm" Server MPM: Prefork 编译的时候,可以通过 configure 的参数来指定: --with-mpm=prefork|worker|event 1、prefork 工作模式 Apache在启动之初,就预先fork一些子进程,然后等待请求进来。之所以这样做,是为了减少频繁创建和销毁进程的开销。每个子进程只有一个线程,在一个时间点内,只能处理一个请求。 优点:成熟稳定,兼容所有新老模块。同时,不需要担心线程安全的问题。缺点:一个进程相对占用更多的系统资源,消耗更多的内存。而且,它并不擅长处理高并发请求。 2、worker 工作模式 使用了多进程和多线程的混合模式。它也预先fork了几个子进程(数量比较少),然后每个子进程创建一些线程,同时包括一个监听线程。每个请求过来,会被分配到1个线程来服务。线程比起进程会更轻量,因为线程通常会共享父进程的内存空间,因此,内存的占用会减少一些。在高并发的场景下,因为比起prefork有更多的可用线程,表现会更优秀一些。 优点:占据更少的内存,高并发下表现更优秀。缺点:必须考虑线程安全的问题。 3、event 工作模式 它和worker模式很像,最大的区别在于,它解决了keep-alive场景下,长期被占用的线程的资源浪费问题。event MPM中,会有一个专门的线程来管理这些keep-alive类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。这样增强了高并发场景下的请求处理能力。 HTTP采用keepalive方式减少TCP连接数量,但是由于需要与服务器线程或进程进行绑定,导致一个繁忙的服务器会消耗完所有的线程。Event MPM是解决这个问题的一种新模型,它把服务进程从连接中分离出来。在服务器处理速度很快,同时具有非常高的点击率时,可用的线程数量就是关键的资源限 制,此时Event MPM方式是最有效的,但不能在HTTPS访问下工作。 二、Apache的用户认证 有时候,我们需要给一些特殊的访问设置一个用户认证机制,增加安全。比如我们的个人网站,一般都是有一个管理后台的,虽然管理后台本身就有密码,但我们为了更加安全,可以再设置一层用户认证。 1、编辑配置文件 vim /usr/local/apache2/conf/extra/httpd-vhosts.conf 在对应的虚拟主机配置中加入如下配置:(加粗部分是添加内容) <VirtualHost *:80> DocumentRoot "/usr/local/apache2/htdocs" ServerName www.123.com ServerAlias www.abc.com <Directory /usr/local/apache2/htdocs/admin.php> AllowOverride AuthConfig AuthName "Please input you acount." AuthType Basic AuthUserFile /usr/local/apache2/htdocs/.htpasswd require valid-user </Directory> </VirtualHost> 说明:首先指定要对哪个目录进行验证,AuthName自定义,AuthUserFile指定用户密码文件在哪里。 2、创建加密用的用户名和密码文件 htpasswd -c /usr/local/apache2/htdocs/.htpasswd liwei htpasswd -m /usr/local/apache2/htdocs/.htpasswd admin 创建第一个用户时-c选项创建.htpasswd文件,-m选项增加用户,根据提示输入密码。 3、重启apache服务 apachectl -t apachectl graceful 先检查配置是否正确,然后用graceful相当于是reload配置,不用重启apache服务,效果一样。测试,通过浏览器输入 www.123.com/admin.php 提示输入密码。 三、设置默认虚拟主机 默认虚拟主机就是配置文件里的第一个虚拟主机。关于默认虚拟主机有个特点,凡是解析到这台服务器的域名,不管是什么域名,只要在配置文件中没有配置,那么都会访问到这个主机上来。如果我们直接用IP访问,会访问到这个站点上来。为了避免别人乱解析,所以应该把默认也就是第一个虚拟主机给禁止掉。我们使用allow,deny语句,已经禁止掉了。 1、配置默认虚拟主机 vim /usr/local/apache2/conf/extra/httpd-vhosts.conf 添加一个虚拟主机的记录: <VirtualHost *:80> DocumentRoot "/var/123" ServerName xxxxx.com.cn <Directory /var/123> Order allow,deny Deny from all </Directory> </VirtualHost> 创建/var/123目录,并且设置600权限,daemon用户无法访问: mkdir /var/123 chmod -R 600 /var/123 2、重启apache服务器 apachectl -t apachectl graceful 如果用IP或其它解析的域名访问,发现提示: Forbidden You don't have permission to access / on this server. 四、域名301跳转 一个站点难免会有多个域名,而多个域名总得有一个主次,比如我的网站可以用两个域名访问:www.itepub.cn 和 www.linuxblogs.cn 但大家发现不管我用哪个域名访问,最终都会跳转到www.linuxblogs.con 上来。这个行为就叫做域名跳转,这里的301只是一个状态码,跳转除了301还有302,301是永久跳转,302是临时跳转,网站上一定要设置为301,这样对搜索引擎是比较友好的。 1、配置域名跳转 # vim /usr/local/apache2/conf/extra/httpd-vhosts.conf <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP_HOST} ^www.abc.com$ RewriteRule ^/(.*)$ http://www.123.com/$1 [R=301,L] </IfModule> 配置为:当访问aaa时,跳转到123的网站。 2、配置多个域名跳转 <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP_HOST} ^www.abc.com$ [OR] RewriteCond %{HTTP_HOST} ^www.abcd.com$ RewriteRule ^/(.*)$ http://www.123.com/$1 [R=301,L] </IfModule> 3、重启服务器并测试 apachectl -t apachectl graceful 测试: # curl -x192.168.0.8:80 www.abc.com -I HTTP/1.1 301 Moved Permanently Date: Tue, 25 Oct 2016 15:48:10 GMT Server: Apache/2.2.31 (Unix) PHP/5.5.38 Location: http://www.123.com/ Content-Type: text/html; charset=iso-8859-1 # curl -x192.168.0.8:80 www.abcd.com -I HTTP/1.1 301 Moved Permanently Date: Tue, 25 Oct 2016 15:48:49 GMT Server: Apache/2.2.31 (Unix) PHP/5.5.38 Location: http://www.123.com/ Content-Type: text/html; charset=iso-8859-1 通过上述测试,发现无论是abc或abcd都可以跳转到 www.123.com 域名上来,通过浏览器访问也一样。 五、Apache日志切割 我们每访问一次网站,那么就会记录若干条日志。当然前提是已经设置了日志,日志不去管理,时间长了日志文件会越来越大,如何避免产生这么大的日志文件?其实apache有相关的配置,使日志按照我们的需求进行归档,比如每天一个新日志,或者每小时一个新的日志。 1、首先简单设置日志的路径名称 vim /usr/local/apache2/conf/extra/httpd-vhosts.conf 编辑添加内容如下: ErrorLog "logs/error.log" CustomLog "logs/access.log" combined 指定了日志存放在/usr/local/apache2/logs目录下分别为error.log和access.log,combined为日志显示的格式,日志格式可以参考配置文件httpd.conf中格式的指定,如下: LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common 2、设置apache日志分割 同样编辑配置文件httpd-vhosts.conf ErrorLog "|/usr/local/apache2/bin/rotatelogs -l /usr/local/apache2/logs/aaa-error_%Y%m%d.log 86400" CustomLog "|/usr/local/apache2/bin/rotatelogs -l /usr/local/apache2/logs/aaa-access_%Y%m%d.log 86400" combined ErrorLog是错误日志,CustomLog是访问日志。|就是管道符,意思是把产生的日志交给rotatelog这个工具,而这个工具就是apache自带的切割日志的工具。-l的作用是校准时区为UTC,也就是北京时间。86400,单位是秒,正好是一天,那么日志会每天切割一次。而最后面的combined为日志的格式,在httpd.conf中有定义。 六、不记录指定文件类型的日志 如果一个网站访问量特别大,那么访问日志就会很多,但有一些访问日志我们其实是可以忽略掉的,比如网站的一些图片,还有js、css等静态对象。而这些文件的访问往往是巨量的,而且即使记录这些日志也没有什么用,那么如何忽略不记录这些日志呢? 1、配置日志不记录图片的访问 vim /usr/local/apache2/conf/extra/httpd-vhosts.conf 相关配置为: SetEnvIf Request_URI ".*\.gif$" image-request SetEnvIf Request_URI ".*\.jpg$" image-request SetEnvIf Request_URI ".*\.png$" image-request SetEnvIf Request_URI ".*\.bmp$" image-request SetEnvIf Request_URI ".*\.swf$" image-request SetEnvIf Request_URI ".*\.js$" image-request SetEnvIf Request_URI ".*\.css$" image-request CustomLog "|/usr/local ... _%Y%m%d.log 86400" combined env=!image-request 说明:在原来日志配置基础上,增加了一些image-request的定义,比如把gif、jpg、bmp、swf、js、css等结尾的全标记为image-request,然后在配置日志后加一个标记env=!image-request,表示取反。 七、Apache配置静态缓存 所说的静态文件指的是图片、js、css等文件,用户访问一个站点,其实大多数元素都是图片、js、css等,这些静态文件其实是会被客户端的浏览器缓存到本地电脑上的,目的就是为了下次再请求时不再去服务器上下载,这样就加快了速度,提高了用户体验。但这些静态文件总不能一直缓存,它总有一些时效性,那么就得设置这个过期时间。 1、配置静态缓存 # vim /usr/local/apache2/conf/extra/httpd-vhosts.conf <IfModule mod_expires.c> ExpiresActive on ExpiresByType image/gif "access plus 1 days" ExpiresByType image/jpeg "access plus 24 hours" ExpiresByType image/png "access plus 24 hours" ExpiresByType text/css "now plus 2 hour" ExpiresByType application/x-javascript "now plus 2 hours" ExpiresByType application/javascript "now plus 2 hours" ExpiresByType application/x-shockwave-flash "now plus 2 hours" ExpiresDefault "now plus 0 min" </IfModule> 或者使用 mod_headers 模块实现: <IfModule mod_headers.c> # htm,html,txt 类的文件缓存一个小时 <filesmatch "\.(html|htm|txt)$"> header set cache-control "max-age=3600" </filesmatch> # css, js, swf 类的文件缓存一个星期 <filesmatch "\.(css|js|swf)$"> header set cache-control "max-age=604800" </filesmatch> # jpg,gif,jpeg,png,ico,flv,pdf 等文件缓存一年 <filesmatch "\.(ico|gif|jpg|jpeg|png|flv|pdf)$"> header set cache-control "max-age=29030400" </filesmatch> </IfModule> 说明:这里的时间单位可以 days、 hours 甚至是 min,两种不同的方法,上面使用的是mod_expires,而下面用的是 mod_headers,要想使用这些模块,必须要事先已经支持。如何查看是否支持,使用命令: # /usr/local/apache2/bin/apachectl -M 2、重启服务器并验证 apachectl -t apachectl graceful 验证: # curl -x127.0.0.1:80 'http://www.123.com/static/image/common/online_admin.gif' -I HTTP/1.1 200 OK Date: Wed, 26 Oct 2016 03:51:26 GMT Server: Apache/2.2.31 (Unix) PHP/5.5.38 Last-Modified: Tue, 31 May 2016 03:08:36 GMT ETag: "46891b-16b-5341ab0597500" Accept-Ranges: bytes Content-Length: 363 Cache-Control: max-age=86400 Expires: Thu, 27 Oct 2016 03:51:26 GMT Content-Type: image/gif 八、Apache配置防盗链 如果你的网站有很多漂亮的图片,比如你网站域名 www.123.com,图片地址为 www.123.com/image/111.jpg,那么其它人就可以直接把这个地址放到他自己的网站上,他的用户可以直接从他网站查看这张图片,而实际图片是从你的网站访问的,所产生的带宽消耗对你没有任何意义,应该对这些图片限制一下,凡是在第三方站点上,严禁访问你站点的图片,如何配置呢? 1、配置防盗链 # vim /usr/local/apache2/conf/extra/httpd-vhosts.conf SetEnvIfNoCase Referer "^http://.*\.123\.com" local_ref SetEnvIfNoCase Referer ".*\.abc\.com" local_ref SetEnvIfNoCase Referer "^$" local_ref <filesmatch "\.(txt|doc|mp3|zip|rar|jpg|gif)"> Order Allow,Deny Allow from env=local_ref </filesmatch> 说明:在这段配置中涉及到一个名词referer,其实就是上次访问的网站链接。配置referer是根据来源链接做限制的,如果来源链接不是我们想要的,就直接拒绝,这就是防盗链的原理。当然不止是图片,mp3、rar、zip等文件同样支持。上述配置中默认是除了定义的列表中的referer,其它都拒绝。 九、Apache访问控制 其实我们可以对apache的访问进行控制,可以设置白名单或黑名单。前面更改httpd.conf时候就已经看到了allow,deny这两个关键词,先来看看allow和deny的规则。 1、举例1 Order deny,allow deny from all allow from 127.0.0.1 我们的判断依据是这样的: 看Order后面的,哪个在前,哪个在后 如果deny在前,那么就需要看deny from这句,然后看allow from这句 规则是一条一条匹配的,不管是deny在前还是allow在前,都会生效的。 2、举例2 Order allow,deny deny from all allow from 127.0.0.1 这个就会deny所有了,127.0.0.1也会被deny。因为顺序是先allow然后deny,虽然开始allow了127,但是后面又拒绝了它。 3、举例3 Order allow,deny deny from all 上面的规则就表示,全部都不能通过。 4、举例4 Order deny,allow deny from all 上面的规则表示,全部都不能通。 Order deny,allow 只有顺序,没有具体规则,表示全部都可以通行(默认的),因为allow在最后了。 Order allow,deny 这个表示,全部都不能通行(默认的),因为deny在最后。 5、针对某个目录限制 比如这个目录很重要,只允许我们公司的IP访问,当然这个目录可以是网站根目录,也就是整个站点。 <Directory /usr/local/apache2/htdocs> Order deny,allow Deny from all Allow from 127.0.0.1 </Directory> 6、针对请求的URL去限制 <filesmatch "(.*)admin(.*)"> Order deny,allow Deny from all Allow from 127.0.0.1 </filesmatch> 这里用到了filesmatch语法,表示匹配的意思。 7、验证 # curl -x192.168.0.8:80 www.123.com/admin.php -I HTTP/1.1 403 Forbidden Date: Wed, 26 Oct 2016 06:24:54 GMT Server: Apache/2.2.31 (Unix) PHP/5.5.38 Content-Type: text/html; charset=iso-8859-1 # curl -x127.0.0.1:80 www.123.com/admin.php -I HTTP/1.1 401 Authorization Required Date: Wed, 26 Oct 2016 06:25:03 GMT Server: Apache/2.2.31 (Unix) PHP/5.5.38 WWW-Authenticate: Basic realm="Please input you acount." Content-Type: text/html; charset=iso-8859-1 十、禁止解析PHP 某个目录下禁止解析PHP,这个很有作用,我们做网站安全的时候,这个用的很多,比如某些目录可以上传文件,为了避免上传的文件有木马,所以我们禁止这个目录下面的访问解析PHP。 1、配置禁止解析php <Directory /usr/local/apache2/htdocs/data> php_admin_flag engine off <filesmatch "(.*)php"> Order deny,allow Deny from all </filesmatch> </Directory> 说明:php_admin_flag engine off这个语句就是禁止解析php的控制语句,但只这样配置还不够,因为这样配置后用户依然可以访问php文件,只不过不解析了,但可以下载,用户下载php文件也是不合适的,所以有必要再禁止一下。 十一、禁止指定user_agent user_agent叫做浏览器标识,目前主流的浏览器有IE、chrome、Firefox、360、iphone的Safari、Android手机上的、百度搜索引擎、google搜索引擎等很多,每一种浏览器都有对应的user_agent。为了避免一些无用的搜索引擎或机器爬虫之类引起的带宽的无辜消耗。 <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP_HOST} ^www.abc.com$ [OR] RewriteCond %{HTTP_HOST} ^www.abcd.com$ RewriteRule ^/(.*)$ http://www.123.com/$1 [R=301,L] RewriteCond %{HTTP_USER_AGENT} ".*Firefox.*" [NC,OR] RewriteCond %{HTTP_USER_AGENT} ".*Tomato Bot.*" [NC] RewriteRule .* - [F] </IfModule> 同样是使用rewrite模块来实现限制指定user_agent。本例中,RewriteRule .* - [F]可以直接禁止访问,rewritecond用user_agent来匹配,NC表示不区分大小写,OR表示或者,连接下一个条件。假如我们要把百度的搜索引擎限制掉,可以加一条这样的规则: RewriteCond %{HTTP_USER_AGENT} ^.*Baiduspider/2.0.* [NC] RewriteRule .* - [F] 十二、限制某个目录 我们可以allow和deny去现在网站根目录下的某个子目录,当然这个rewrite也可以实现,配置如下: <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{REQUEST_URI} ^.*/tmp/* [NC] RewriteRule .* - [F] </IfModule> 这段配置,会把只要是包含 /tmp/ 字样的请求都限制了。 ^_^...... 差不多到这里就要结束了,以后有新的常用功能再来更新。
一、写在前面 HA即(high available)高可用,又被叫做双机热备,用于关键性业务。简单理解就是,有2台机器 A 和 B,正常是 A 提供服务,B 待命闲置,当 A 宕机或服务宕掉,会切换至B机器继续提供服务。常见的实现高可用的开源软件有 heartbeat 和 keepalived。 这样,一台 web 服务器一天24小时提供web服务,难免会存在 web 服务挂掉或服务器宕机宕机的情况,那么用户就访问不了服务了,这当然不是我们期望的。如果这样,有2台服务器,A对外提供 web 服务,B作为备用,如果A挂掉,那么B立刻替代A的位置去提供 web 服务,这样对用户来说是透明的。但是有个问题,服务器A的 ip 是 10.0.0.100,服务器B的 ip 是 10.0.0.101,显然向用户提供A或B的ip地址是不可行的,因为用户总不能去切换ip来访问的吧。这时heartbeat或keepalived可以提供一个虚拟IP:10.0.0.102,用户只需要访问 10.0.0.102,当A提供服务时,VIP 会设置在A服务器上,当B提供服务时,VIP会设置在B服务器上,这样就可以让用户通过访问 10.0.0.102 来获取web服务,即使A或B服务器切换也不影响用户的正常访问。 下面我们使用 heartbeat 来做 HA 集群,并且把 nginx 服务作为 HA 对应的服务。 二、准备实验环境 服务器A:主机名:master操作系统:CentOS6.8 64位eth0网卡地址:192.168.0.18eth1网卡地址:172.16.254.18 服务器B:主机名:slave操作系统:CentOS6.8 64位eth0网卡地址:192.168.0.28eth1网卡地址:172.16.254.28 虚拟VIP:VIP:192.168.0.38 三、设置主机名 master节点设置hostname hostname master vim /etc/sysconfig/network 编辑配置文件: HOSTNAME=master slave节点设置hostname # hostname slave # vim /etc/sysconfig/network 编辑配置文件: HOSTNAME=slave 四、关闭防火墙和selinux(2台节点都要操作) 关闭iptables # iptables -F # service iptables save # service iptables stop 关闭selinux: # setenforce 0 # sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config 五、配置hosts文件(2台节点都操作) # vim /etc/hosts 增加内容如下: 192.168.0.18 master 192.168.0.28 slave 六、安装epel扩展源 (2台都操作) # yum install -y epel-release 七、安装heartbeat (2台都操作) # yum install -y heartbeat* libnet nginx 八、主master节点配置 1、拷贝配置文件: # cd /usr/share/doc/heartbeat-3.0.4/ # cp authkeys ha.cf haresources /etc/ha.d/ # cd /etc/ha.d 2、修改authkeys # vim authkeys 更改或增加如下内容: auth 3 3 md5 Hello! 然后修改其权限 # chmod 600 authkeys 3、编辑haresources文件 # vim haresources 加入下面一行: master 192.168.0.38/24/eth0:0 nginx 说明:master为主节点hostname,192.168.0.38为vip,/24为掩码为24的网段,eth0:0为vip的设备名,nginx为heartbeat监控的服务,也是两台机器对外提供的核心服务。 4、编辑ha.cf # vim ha.cf 修改为如下内容: debugfile /var/log/ha-debug logfile /var/log/ha-log logfacility local0 keepalive 2 deadtime 30 warntime 10 initdead 60 udpport 694 ucast eth1 172.16.254.28 auto_failback on node master node slave ping 172.16.254.1 respawn hacluster /usr/lib64/heartbeat/ipfail 5、配置说明: debugfile /var/log/ha-debug:该文件保存heartbeat的调试信息。logfile /var/log/ha-log:heartbeat的日志文件。keepalive 2:心跳的时间间隔,默认时间单位为秒s。deadtime 30:超出该时间间隔未收到对方节点的心跳,则认为对方已经死亡。warntime 10:超出该时间间隔未收到对方节点的心跳,则发出警告并记录到日志中。initdead 60:在某系统上,系统启动或重启之后需要经过一段时间网络才能正常工作,该选项用于解决这种情况产生的时间间隔,取值至少为deadtime的2倍。udpport 694:设置广播通信使用的端口,694为默认使用的端口号。ucast eth1 172.16.254.28:设置对方机器心跳检测的网卡和IP。auto_failback on:heartbeat的两台主机分别为主节点和从节点。主节点在正常情况下占用资源并运行所有的服务,遇到故障时把资源交给从节点由从节点运行服务。在该选项设为on的情况下,一旦主节点恢复运行,则自动获取资源并取代从节点,否则不取代从节点。respawn heartbeat /usr/lib/heartbeat/ipfail:指定与heartbeat一同启动和关闭的进程,该进程被自动监视,遇到故障则重新启动。最常用的进程是ipfail,该进程用于检测和处理网络故障,需要配合ping语句指定的ping node来检测网络连接。如果你的系统是64bit,请注意该文件的路径。 九、把主节点上的三个配置文件拷贝到从节点 # cd /etc/ha.d # scp authkeys ha.cf haresources slave:/etc/ha.d 十、从节点slave编辑ha.cf # vim /etc/ha.d/ha.cf 只需要更改一个地方如下: ucast eth1 172.16.254.28改为ucast eth1 172.16.254.18 十一、启动heartbeat服务 配置完毕后,先master启动,后slave启动。 # service heartbeat start 十二、检查测试 # ifconfig 看是否有接口 eth0:0 # ps aux | grep nginx 看是否有nginx进程 十三、测试方式1 主节点上故意禁ping # iptables -I INPUT -p icmp -j DROP 十四、测试方式2 主节点停止heartbeat服务 # service heartbeat stop 十五、测试脑裂 主节点master和从节点slave都down掉eth1网卡 # ifdown eth1
Nginx 的负载均衡功能,其实实际上和 nginx 的代理是同一个功能,只是把代理一台机器改为多台机器而已。 Nginx 的负载均衡和 lvs 相比,nginx属于更高级的应用层,不牵扯到 ip 和内核的修改,它只是单纯地把用户的请求转发到后面的机器上。这就意味着,后端的 RS 不需要配置公网。 一、实验环境 Nginx 调度器 (public 172.16.254.200 privite 192.168.0.48)RS1只有内网IP (192.168.0.18)RS2只有外网IP (192.168.0.28) 二、配置文件 在nginx调度器上编辑配置文件 # vim /usr/local/nginx/conf/vhosts/lb.conf 添加如下内容: upstream test { ip_hash; server 192.168.0.18; server 192.168.0.28; } server { listen 80;150 server_name www.aminglinux.com; location / { proxy_pass http://test/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } 三、配置说明 你会发现这个配置和我们之前讲的 nginx 代理配置如出一辙,只是多了一个upstream,这个 upstream 用来定义后端的 RS,可以只写一个。 ip_hash 为 nginx 的一种调度算法,加上这一行后会达到这样的效果,即一个用户的请求会适中被分发到固定的一个 RS上。这样的好处是,可以避免把同一个用户的请求分发到不同的机器上而导致 session 丢失的情况。 upstream 里面, RS 后面的 ip 后面还可以加权重,比如"server 192.168.31.100 weight=100;"。还有一点要注意, upstream 后面的 test 是自定义的一个名字,可以随便写,唯一的要求是要和 proxy_pass 后面保持一致。
负载均衡集群是 load balance 集群的简写,翻译成中文就是负载均衡集群。常用的负载均衡开源软件有nginx、lvs、haproxy,商业的硬件负载均衡设备F5、Netscale。这里主要是学习 LVS 并对其进行了详细的总结记录。 一、负载均衡LVS基本介绍 LB集群的架构和原理很简单,就是当用户的请求过来时,会直接分发到Director Server上,然后它把用户的请求根据设置好的调度算法,智能均衡地分发到后端真正服务器(real server)上。为了避免不同机器上用户请求得到的数据不一样,需要用到了共享存储,这样保证所有用户请求的数据是一样的。 LVS是 Linux Virtual Server 的简称,也就是Linux虚拟服务器。这是一个由章文嵩博士发起的一个开源项目,它的官方网站是 http://www.linuxvirtualserver.org 现在 LVS 已经是 Linux 内核标准的一部分。使用 LVS 可以达到的技术目标是:通过 LVS 达到的负载均衡技术和 Linux 操作系统实现一个高性能高可用的 Linux 服务器集群,它具有良好的可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的性能。LVS 是一个实现负载均衡集群的开源软件项目,LVS架构从逻辑上可分为调度层、Server集群层和共享存储。 二、LVS的基本工作原理 1. 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间2. PREROUTING链首先会接收到用户请求,判断目标IP确定是本机IP,将数据包发往INPUT链3. IPVS是工作在INPUT链上的,当用户请求到达INPUT时,IPVS会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时IPVS会强行修改数据包里的目标IP地址及端口,并将新的数据包发往POSTROUTING链4. POSTROUTING链接收数据包后发现目标IP地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器 三、LVS的组成 LVS 由2部分程序组成,包括 ipvs 和 ipvsadm。 1. ipvs(ip virtual server):一段代码工作在内核空间,叫ipvs,是真正生效实现调度的代码。2. ipvsadm:另外一段是工作在用户空间,叫ipvsadm,负责为ipvs内核框架编写规则,定义谁是集群服务,而谁是后端真实的服务器(Real Server) 四、LVS相关术语 1. DS:Director Server。指的是前端负载均衡器节点。2. RS:Real Server。后端真实的工作服务器。3. VIP:向外部直接面向用户请求,作为用户请求的目标的IP地址。4. DIP:Director Server IP,主要用于和内部主机通讯的IP地址。5. RIP:Real Server IP,后端服务器的IP地址。6. CIP:Client IP,访问客户端的IP地址。 下边是三种工作模式的原理和特点总结。 五、LVS/NAT原理和特点 1. 重点理解NAT方式的实现原理和数据包的改变。 (a). 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP (b). PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链(c). IPVS比对数据包请求的服务是否为集群服务,若是,修改数据包的目标IP地址为后端服务器IP,然后将数据包发至POSTROUTING链。 此时报文的源IP为CIP,目标IP为RIP (d). POSTROUTING链通过选路,将数据包发送给Real Server(e). Real Server比对发现目标为自己的IP,开始构建响应报文发回给Director Server。 此时报文的源IP为RIP,目标IP为CIP (f). Director Server在响应客户端前,此时会将源IP地址修改为自己的VIP地址,然后响应给客户端。 此时报文的源IP为VIP,目标IP为CIP 2. LVS-NAT模型的特性 RS应该使用私有地址,RS的网关必须指向DIP DIP和RIP必须在同一个网段内 请求和响应报文都需要经过Director Server,高负载场景中,Director Server易成为性能瓶颈 支持端口映射 RS可以使用任意操作系统 缺陷:对Director Server压力会比较大,请求和响应都需经过director server 六、LVS/DR原理和特点 1. 重将请求报文的目标MAC地址设定为挑选出的RS的MAC地址 (a) 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP(b) PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链(c) IPVS比对数据包请求的服务是否为集群服务,若是,将请求报文中的源MAC地址修改为DIP的MAC地址,将目标MAC地址修改RIP的MAC地址,然后将数据包发至POSTROUTING链。 此时的源IP和目的IP均未修改,仅修改了源MAC地址为DIP的MAC地址,目标MAC地址为RIP的MAC地址 (d) 由于DS和RS在同一个网络中,所以是通过二层来传输。POSTROUTING链检查目标MAC地址为RIP的MAC地址,那么此时数据包将会发至Real Server。(e) RS发现请求报文的MAC地址是自己的MAC地址,就接收此报文。处理完成之后,将响应报文通过lo接口传送给eth0网卡然后向外发出。 此时的源IP地址为VIP,目标IP为CIP (f) 响应报文最终送达至客户端 2. LVS-DR模型的特性 特点1:保证前端路由将目标地址为VIP报文统统发给Director Server,而不是RS RS可以使用私有地址;也可以是公网地址,如果使用公网地址,此时可以通过互联网对RIP进行直接访问 RS跟Director Server必须在同一个物理网络中 所有的请求报文经由Director Server,但响应报文必须不能进过Director Server 不支持地址转换,也不支持端口映射 RS可以是大多数常见的操作系统 RS的网关绝不允许指向DIP(因为我们不允许他经过director) RS上的lo接口配置VIP的IP地址 缺陷:RS和DS必须在同一机房中 3. 特点1的解决方案: 在前端路由器做静态地址路由绑定,将对于VIP的地址仅路由到Director Server 存在问题:用户未必有路由操作权限,因为有可能是运营商提供的,所以这个方法未必实用 arptables:在arp的层次上实现在ARP解析时做防火墙规则,过滤RS响应ARP请求。这是由iptables提供的 修改RS上内核参数(arp_ignore和arp_announce)将RS上的VIP配置在lo接口的别名上,并限制其不能响应对VIP地址解析请求。 七、LVS/Tun原理和特点 在原有的IP报文外再次封装多一层IP首部,内部IP首部(源地址为CIP,目标IIP为VIP),外层IP首部(源地址为DIP,目标IP为RIP) (a) 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP 。(b) PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链(c) IPVS比对数据包请求的服务是否为集群服务,若是,在请求报文的首部再次封装一层IP报文,封装源IP为为DIP,目标IP为RIP。然后发至POSTROUTING链。 此时源IP为DIP,目标IP为RIP (d) POSTROUTING链根据最新封装的IP报文,将数据包发至RS(因为在外层封装多了一层IP首部,所以可以理解为此时通过隧道传输)。 此时源IP为DIP,目标IP为RIP(e) RS接收到报文后发现是自己的IP地址,就将报文接收下来,拆除掉最外层的IP后,会发现里面还有一层IP首部,而且目标是自己的lo接口VIP,那么此时RS开始处理此请求,处理完成之后,通过lo接口送给eth0网卡,然后向外传递。 此时的源IP地址为VIP,目标IP为CIP(f) 响应报文最终送达至客户端 LVS-Tun模型特性 RIP、VIP、DIP全是公网地址 RS的网关不会也不可能指向DIP 所有的请求报文经由Director Server,但响应报文必须不能进过Director Server 不支持端口映射 RS的系统必须支持隧道 其实企业中最常用的是 DR 实现方式,而 NAT 配置上比较简单和方便,后边实践中会总结 DR 和 NAT 具体使用配置过程。 八、LVS的八种调度算法 1. 轮叫调度 rr这种算法是最简单的,就是按依次循环的方式将请求调度到不同的服务器上,该算法最大的特点就是简单。轮询算法假设所有的服务器处理请求的能力都是一样的,调度器会将所有的请求平均分配给每个真实服务器,不管后端 RS 配置和处理能力,非常均衡地分发下去。 2. 加权轮叫 wrr这种算法比 rr 的算法多了一个权重的概念,可以给 RS 设置权重,权重越高,那么分发的请求数越多,权重的取值范围 0 – 100。主要是对rr算法的一种优化和补充, LVS 会考虑每台服务器的性能,并给每台服务器添加要给权值,如果服务器A的权值为1,服务器B的权值为2,则调度到服务器B的请求会是服务器A的2倍。权值越高的服务器,处理的请求越多。 3. 最少链接 lc这个算法会根据后端 RS 的连接数来决定把请求分发给谁,比如 RS1 连接数比 RS2 连接数少,那么请求就优先发给 RS1 4. 加权最少链接 wlc这个算法比 lc 多了一个权重的概念。 5. 基于局部性的最少连接调度算法 lblc这个算法是请求数据包的目标 IP 地址的一种调度算法,该算法先根据请求的目标 IP 地址寻找最近的该目标 IP 地址所有使用的服务器,如果这台服务器依然可用,并且有能力处理该请求,调度器会尽量选择相同的服务器,否则会继续选择其它可行的服务器 6. 复杂的基于局部性最少的连接算法 lblcr记录的不是要给目标 IP 与一台服务器之间的连接记录,它会维护一个目标 IP 到一组服务器之间的映射关系,防止单点服务器负载过高。 7. 目标地址散列调度算法 dh该算法是根据目标 IP 地址通过散列函数将目标 IP 与服务器建立映射关系,出现服务器不可用或负载过高的情况下,发往该目标 IP 的请求会固定发给该服务器。 8. 源地址散列调度算法 sh与目标地址散列调度算法类似,但它是根据源地址散列算法进行静态分配固定的服务器资源。 九、实践LVS的NAT模式 1、实验环境 三台服务器,一台作为 director,两台作为 real server,director 有一个外网网卡(172.16.254.200) 和一个内网ip(192.168.0.8),两个 real server 上只有内网 ip (192.168.0.18) 和 (192.168.0.28),并且需要把两个 real server 的内网网关设置为 director 的内网 ip(192.168.0.8) 2、安装和配置 两个 real server 上都安装 nginx 服务 # yum install -y nginx Director 上安装 ipvsadm # yum install -y ipvsadm Director 上编辑 nat 实现脚本 # vim /usr/local/sbin/lvs_nat.sh # 编辑写入如下内容: #! /bin/bash # director服务器上开启路由转发功能: echo 1 > /proc/sys/net/ipv4/ip_forward # 关闭 icmp 的重定向 echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects echo 0 > /proc/sys/net/ipv4/conf/eth1/send_redirects # director设置 nat 防火墙 iptables -t nat -F iptables -t nat -X iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE # director设置 ipvsadm IPVSADM='/sbin/ipvsadm' $IPVSADM -C $IPVSADM -A -t 172.16.254.200:80 -s wrr $IPVSADM -a -t 172.16.254.200:80 -r 192.168.0.18:80 -m -w 1 $IPVSADM -a -t 172.16.254.200:80 -r 192.168.0.28:80 -m -w 1 保存后,在 Director 上直接运行这个脚本就可以完成 lvs/nat 的配置 /bin/bash /usr/local/sbin/lvs_nat.sh 查看ipvsadm设置的规则 ipvsadm -ln 3、测试LVS的效果 通过浏览器测试2台机器上的web内容 http://172.16.254.200 。为了区分开,我们可以把 nginx 的默认页修改一下: 在 RS1 上执行 # echo "rs1rs1" >/usr/share/nginx/html/index.html 在 RS2 上执行 # echo "rs2rs2" >/usr/share/nginx/html/index.html 注意,切记一定要在两台 RS 上设置网关的 IP 为 director 的内网 IP。 十、实践LVS的DR模式 1、实验环境 三台机器: Director节点: (eth0 192.168.0.8 vip eth0:0 192.168.0.38) Real server1: (eth0 192.168.0.18 vip lo:0 192.168.0.38) Real server2: (eth0 192.168.0.28 vip lo:0 192.168.0.38) 2、安装 两个 real server 上都安装 nginx 服务 # yum install -y nginx Director 上安装 ipvsadm # yum install -y ipvsadm 3、Director 上配置脚本 # vim /usr/local/sbin/lvs_dr.sh #! /bin/bash echo 1 > /proc/sys/net/ipv4/ip_forward ipv=/sbin/ipvsadm vip=192.168.0.38 rs1=192.168.0.18 rs2=192.168.0.28 ifconfig eth0:0 down ifconfig eth0:0 $vip broadcast $vip netmask 255.255.255.255 up route add -host $vip dev eth0:0 $ipv -C $ipv -A -t $vip:80 -s wrr $ipv -a -t $vip:80 -r $rs1:80 -g -w 3 $ipv -a -t $vip:80 -r $rs2:80 -g -w 1 执行脚本: # bash /usr/local/sbin/lvs_dr.sh 4、在2台 rs 上配置脚本: # vim /usr/local/sbin/lvs_dr_rs.sh #! /bin/bash vip=192.168.0.38 ifconfig lo:0 $vip broadcast $vip netmask 255.255.255.255 up route add -host $vip lo:0 echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce rs 上分别执行脚本: bash /usr/local/sbin/lvs_dr_rs.sh 5、实验测试 测试方式同上,浏览器访问 http://192.168.0.38 注意:在 DR 模式下,2台 rs 节点的 gateway 不需要设置成 dir 节点的 IP 。 参考链接地址:http://www.cnblogs.com/lgfeng/archive/2012/10/16/2726308.html 十一、LVS结合keepalive LVS可以实现负载均衡,但是不能够进行健康检查,比如一个rs出现故障,LVS 仍然会把请求转发给故障的rs服务器,这样就会导致请求的无效性。keepalive 软件可以进行健康检查,而且能同时实现 LVS 的高可用性,解决 LVS 单点故障的问题,其实 keepalive 就是为 LVS 而生的。 1、实验环境 4台节点 Keepalived1 + lvs1(Director1):192.168.0.48 Keepalived2 + lvs2(Director2):192.168.0.58 Real server1:192.168.0.18 Real server2:192.168.0.28 IP: 192.168.0.38 2、安装系统软件 Lvs + keepalived的2个节点安装 # yum install ipvsadm keepalived -y Real server + nginx服务的2个节点安装 # yum install epel-release -y # yum install nginx -y 3、设置配置脚本 Real server节点2台配置脚本: # vim /usr/local/sbin/lvs_dr_rs.sh #! /bin/bash vip=192.168.0.38 ifconfig lo:0 $vip broadcast $vip netmask 255.255.255.255 up route add -host $vip lo:0 echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce 2节点rs 上分别执行脚本: bash /usr/local/sbin/lvs_dr_rs.sh keepalived节点配置(2节点): 主节点( MASTER )配置文件 vim /etc/keepalived/keepalived.conf vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.0.38 } } virtual_server 192.168.0.38 80 { delay_loop 6 lb_algo rr lb_kind DR persistence_timeout 0 protocol TCP real_server 192.168.0.18 80 { weight 1 TCP_CHECK { connect_timeout 10 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } real_server 192.168.0.28 80 { weight 1 TCP_CHECK { connect_timeout 10 nb_get_retry 3 delay_before_retry 3 connect_port 80 } } } 从节点( BACKUP )配置文件 拷贝主节点的配置文件keepalived.conf,然后修改如下内容: state MASTER -> state BACKUP priority 100 -> priority 90 keepalived的2个节点执行如下命令,开启转发功能: # echo 1 > /proc/sys/net/ipv4/ip_forward 4、启动keepalive 先主后从分别启动keepalive service keepalived start 5、验证结果 实验1 手动关闭192.168.0.18节点的nginx,service nginx stop 在客户端上去测试访问 http://192.168.0.38 结果正常,不会出现访问18节点,一直访问的是28节点的内容。 实验2 手动重新开启 192.168.0.18 节点的nginx, service nginx start 在客户端上去测试访问 http://192.168.0.38 结果正常,按照 rr 调度算法访问18节点和28节点。 实验3 测试 keepalived 的HA特性,首先在master上执行命令 ip addr ,可以看到38的vip在master节点上的;这时如果在master上执行 service keepalived stop 命令,这时vip已经不再master上,在slave节点上执行 ip addr 命令可以看到 vip 已经正确漂到slave节点,这时客户端去访问 http://192.168.0.38 访问依然正常,验证了 keepalived的HA特性。 lvs 介绍:http://www.it165.net/admin/html/201401/2248.html
通过传统的方式安装和部署计算机时,都需要人工干预的方式完成安装。如果需要部署大量的类似功能的工作站或服务器,则需要耗费大量的时间。同时传统的安装方式,每台计算机都需要光驱设备及安装光盘等介质,会额外增加部署成本。因此,许多系统管理员都希望能够通过一种网络化的无人值守的自动安装方式将操作系统部署到目标计算机中。 一、相关服务和工具 1、PXE协议 PXE 是由 Intel 设计的协议,计算机可以通过 PXE 协议从网络引导启动。PXE 协议在启动过程分为 client 和 server 端,PXE 协议运行过程主要解决两个问题:首先解决 IP 地址的问题,然后解决如何传输操作系统启动文件和安装文件的问题。对于第一个问题,可以通过 DHCP Server 解决,通常情况下 DHCP 服务器主要用于分配 IP 地址给客户端。但在 PXE 环境下,DHCP 服务器需要额外加载 PXE 的相关配置。针对第二个问题,在启动初期因为 PXE client 中有相应的 TFTP 客户端,可以通过 TFTP 协议到 TFTP 服务器中下载相关文件启动计算机。后期在安装过程中,则通过 FTP 或 NFS 协议提供大量的操作系统安装文件的下载。 2、Kickstart 通过传统的方式安装和部署计算机时,都会要求通过交互的方式,回答各类问题,以完成安装和部署任务,过程繁琐,且无法实现自动化。红帽公司开发了 Kickstart 的安装方法,通过 ks 文件可以解决所有普通安装方式中需要回答的问题。可以通过 system-config-kickstart 工具定制 ks 文件,也可以通过相关语法来手工编写安装脚本。 3、CentOS操作系统 本次实验中所使用和安装的操作系统为CentOS 7,理论上 CentOS 6也是适用的。 4、DHCP 动态主机配置协议,主要用于给 DHCP 客户端自动分配 IP 地址,便于用户管理网络内部的计算机。针对 PXE 环境下,DHCP 服务器除分配 IP 地址外,还需要额外配置"next-server"选项定义 TFTP 服务器的地址,设置"filename"选项定义启动文件的名称。并且启动"booting"与"bootp"的支持。 5、TFTP与FTP 简单文件传输协议(TFTP)主要用于为客户机与服务器之间进行简单文件传输的协议。在 PXE 早期启动过程中,主要通过 TFTP 协议传输"pxelinux.0"。文件传输协议(FTP),适用于大量文件传输的情形,在后期安装过程,主要通过 FTP 协议传输 Linux 操作系统的安装包。 二、安装配置FTP服务 FTP 服务主要是下载 ks.cfg 文件和 操作系统文件的,也可以用 HTTP 或 NFS 来代替。 1、安装vsftpd服务 [root@localhost ~]# yum install -y vsftpd 2、提供操作系统镜像文件 FTP 默认配置即可,我们需要适用匿名用户。通过ftp安装操作系统,我们需要把操作系统镜像文件拷贝到这个匿名用户目录[root@localhost ~]# mount /dev/cdrom /var/ftp/pub/ # /var/ftp/pub是ftp的匿名用户目录 3、启动ftp服务 [root@localhost ~]# systemctl start vsftpd # 启动ftp服务 [root@localhost ~]# systemctl enable vsftpd # 设置开机启动 三、安装dhcp和tftp DHCP 和 TFTP 服务可以选择单独分别去安装,也可以通过安装 dnsmasq 服务,来实现 DHCP 和 TFTP 的功能。 1、安装dnsmasq软件包 [root@localhost ~]# yum install -y dnsmasq 2、配置dnsmasq dnsmasq 的配置文件是 /etc/dnsmasq.conf,主要是去掉以下相关的注释,并设置修改 DHCP 的范围和 TFTP 的根目录。 bogus-priv filterwin2k interface=eth0 dhcp-range=192.168.0.50,192.168.0.100,12h dhcp-boot=pxelinux.0 enable-tftp tftp-root=/var/tftp # tftp目录默认是没有的,需要手动创建 dhcp-authoritative 3、创建tftp根目录 [root@localhost ~]# mkdir /var/tftp 4、启动dnsmasq [root@localhost ~]# systemctl start dnsmasq [root@localhost ~]# systemctl enable dnsmasq 四、拷贝和准备相关文件 1、从iso中拷贝内核镜像和文件系统镜像 cp /var/ftp/pub/images/pxeboot/initrd.img /var/tftp/ # 拷贝文件系统镜像 cp /var/ftp/pub/images/pxeboot/vmlinuz /var/tftp/ # 拷贝内核镜像文件 2、生成pxe启动文件pxelinux.0 yum install -y syslinux # 安装pxelinux.0所需要的包 rpm -ql syslinux | grep "pxelinux.0" # 查询文件所在目录 cp /usr/share/syslinux/pxelinux.0 /var/tftp/ # 拷贝pxelinux.0文件到tftp根目录 3、准备默认的菜单配置文件 mkdir /var/tftp/pxelinux.cfg/ # 创建pxelinux.cfg目录,固定目录名称 vim /var/tftp/pxelinux.cfg/default # default文件,必须为这个名称 # 编辑内容如下 default linux prompt 1 timeout 60 display boot.msg label linux kernel vmlinuz append initrd=initrd.img text ks=ftp://192.168.0.3/ks.cfg # 这个地方指定了ks.cfg文件下载路径,后边会生成该文件 4、生成kickstart文件 kickstart 文件可以通过 system-config-kickstart 可视化工具来进行配置,生成 ks.cfg 文件;也可以通过已经安装好的操作系统的模板文件 anaconda-ks.cfg 来稍加修改即可。下边的 ks.cfg 文件是做实验时的样本,内容如下(加粗为修改部分): #version=DEVEL # System authorization information auth --enableshadow --passalgo=sha512 # Use CDROM installation media install url --url=ftp://192.168.0.3/pub/ # 需要指定安装方式通过ftp来下载安装操作系统 # Use graphical install graphical # Run the Setup Agent on first boot firstboot --enable ignoredisk --only-use=sda # Keyboard layouts keyboard --vckeymap=us --xlayouts='us' # System language lang en_US.UTF-8 # Network information network --bootproto=dhcp --device=ens33 --onboot=off --ipv6=auto --no-activate network --hostname=localhost.localdomain # Root password rootpw --iscrypted $6$LK7yftVlSa2zcGia$4loHYYWZUosdWvZA7Qzf.0lhmrcD5n26BK1xWm7QCNBdbBSjC7MK7yAYRvmIXGI8wu.t96jo6m8RRmNyjsKY60 # System services services --disabled="chronyd" # System timezone timezone Asia/Shanghai --isUtc --nontp # System bootloader configuration bootloader --append=" crashkernel=auto" --location=mbr --boot-drive=sda autopart --type=lvm # Partition clearing information clearpart --all --initlabel --drives=sda... ... ... ... # 还有很多内容 拷贝 ks.cfg 文件到 FTP 目录 [root@localhost ~]# cp /root/anaconda-ks.cfg /var/ftp/ks.cfg[root@localhost ~]# chmod +r /var/ftp/ks.cfg 五、客户端安装操作系统验证 以上工作完成之后,就可以开始安装操作系统了: 1、准备一台适当配置的物理机2、连接网线,与服务器在同一个局域网内3、设置 BIOS 从网卡启动4、等待安装...... 遇到的问题,有的主机即使设置了 BIOS 从 network 启动,仍然不能正常从网络来启动安装,需要仔细查找到 BISO 的关于 PXE 的开关设置,然后将其打开,每个主机的 BIOS 设置方式都不同,需要自己根据具体的硬件来设置。
Linux 操作系统的网卡设备的传统命名方式是 eth0、eth1、eth2等,而 CentOS7 提供了不同的命名规则,默认是基于固件、拓扑、位置信息来分配。这样做的优点是命名全自动的、可预知的,缺点是比 eth0、wlan0 更难读,比如 ens33 。 一、命名规则策略 规则1: 对于板载设备命名合并固件或 BIOS 提供的索引号,如果来自固件或 BIOS 的信息可读就命名,比如eno1,这种命名是比较常见的,否则使用规则2。 规则2: 命名合并固件或 BIOS 提供的 PCI-E 热插拔口索引号,比如 ens1,如果信息可读就使用,否则使用规则3。 规则3: 命名合并硬件接口的物理位置,比如 enp2s0,可用就命名,失败直接到方案5。 规则4: 命名合并接口的 MAC 地址,比如 enx78e7d1ea46da,默认不使用,除非用户选择使用此方案。 规则5: 使用传统的方案,如果所有的方案都失败,使用类似 eth0 这样的样式。 二、网卡名称字符含义 1、前2个字符的含义 en 以太网 Ethernet wl 无线局域网 WLAN ww 无线广域网 WWAN 2、第3个字符根据设备类型选择 o<index> on-board device index number s<slot> hotplug slot index number x<MAC> MAC address p<bus>s<slot> PCI geographical location p<bus>s<slot> USB port number chain 三、修改网卡名称样式为ethx 如果不习惯使用新的命名规则,可以恢复使用传统的方式命名,编辑 grub 文件,增加两个变量,再使用 grub2-mkconfig 重新生成 grub 配置文件即可。 1、编辑 grub 配置文件 vim /etc/sysconfig/grub # 其实是/etc/default/grub的软连接 # 为GRUB_CMDLINE_LINUX变量增加2个参数,具体内容如下(加粗): GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=cl/root rd.lvm.lv=cl/swap net.ifnames=0 biosdevname=0 rhgb quiet" 2、重新生成 grub 配置文件 grub2-mkconfig -o /boot/grub2/grub.cfg 然后重新启动 Linux 操作系统,通过 ip addr 可以看到网卡名称已经变为 eth0 。 3、修改网卡配置文件 原来网卡配置文件名称为 ifcfg-ens33,这里需要修改为 ethx 的格式,并适当调整网卡配置文件。 mv /etc/sysconfig/network-scripts/ifcfg-ens33 /etc/sysconfig/network-scripts/ifcfg-eth0 # 修改ifcfg-eth0文件如下内容(其它内容不变) NAME=eth0 DEVICE=eth0[root@localhost ~]# systemctl restart network.service # 重启网络服务 注意:ifcfg-ens33 文件最好删除掉,否则重启 network 服务时候会报错。
Nagios 是一款免费的开源 IT 基础设施监控系统,功能强大,灵活性强,能有效监控 Windows、Linux、VMware 和 Unix 主机状态,交换机、路由器等网络设置等。一旦主机或服务状态出现异常时,会发出邮件或短信报警第一时间通知 IT 运营人员,在恢复后发出正常的邮件或短信。Nagios 结构简单,可维护性强,提供一个可选的基于浏览器的 Web 界面,方便管理人员查看系统的运行状态,网络状态、服务状态、日志信息,以及其它异常现象。 一、Nagios 结构简单说明 Nagios 结构上来说,可分为核心和插件两个部分。Nagios 的核心部分只提供了很少的监控功能,因此要搭建一个完善的 IT 监控管理系统,用户还需要在 Nagios 服务器安装相应的插件,插件可以从 Nagios 官方网站下载 http://www.nagios.org/,也可以根据实际要求自己编写所需的插件。 二、Nagios 可实现的功能特性 监控网络服务(SMTP、POP3、HTTP、FTP、PING等) 监控本机及远程主机资源(CPU负荷、磁盘使用率、进程等) 允许用户编写自己的插件来监控特定的服务,支持多种开发语言(Shell、Perl、Python、PHP、C等) 具备定义网络分层结构的能力 当服务或主机问题产生与解决时将告警发送给联系人(通过Email、短信、自定义方式) 可以支持并实现对主机的冗余监控 可用 web 界面用于查看当前的网络状态、通知和故障历史、日志文件等 三、Nagios 监控实现原理 Nagios 软件需安装在一台独立的服务器上运行,这台服务器称为监控中心,监控中心服务器可以采用 Linux 或 Unix 操作系统;每一台被监视的硬件主机或服务都运行一个与监控中心服务器进行通信的 Nagios 软件后台程序,也可以理解为 Agent 或插件均可。监控中心服务器读取配置文件中的指令与远程的守护程序进行通信,并且指示远程的守护程序进行必要的检查。虽然 Nagios 软件必须在 Linux 或 Unix 操作系统上运行,但是远程被监控的机器可以是任何能够与其进行通信的主机,根据远程主机返回的应答,Naigos 将依据配置进行回应;接着 Nagios 将通过本地的机器进行测试,如果检测返回值不正确,Nagios 将通过一种或多种方式报警。 四、监控中心服务器安装配置 1. 中心服务器基本环境 操作系统:CentOS 6.4 x86_64IP 地址:192.168.0.8 2. 安装epel扩展源(nagios) [root@nagios ~]# yum install epel-release -y 3. 安装nagios系列软件 [root@nagios ~]# yum install httpd nagios nagios-plugins-all nagios-plugins-nrpe 4. 设置修改后台用户密码 [root@nagios ~]# htpasswd -c /etc/nagios/passwd nagiosadmin 默认用户名:nagiosadmin 密码:nagiosadmin 通过此命令可以修改密码。 5. 配置文件修改 [root@nagios ~]# vim /etc/nagios/nagios.cfg # 配置文件基本不用修改 检查配置文件是否有语法错误: [root@nagios ~]# nagios -v /etc/nagios/nagios.cfg 6. 启动nagios相关服务 [root@nagios ~]# service nagios start [root@nagios ~]# service httpd start 7. 浏览器访问 http://192.168.0.8/nagios # 注意最好要关闭iptables和selinux 通过浏览器访问上述的地址,输入后台用户名和密码。默认:nagiosadmin nagiosadmin 五、监控客户端基本网络服务 1. 说明 对于客户端类似ping、ssh、http等基础网络服务,只需要在服务器端执行简单网络探测的命令即可查看相应的服务是否正常运行,因此对于这些服务的监控在客户端不需要安装任何的插件及服务。 2. 客户端环境 操作系统:CentOS 6.4 x86_64IP 地址:192.168.0.28 3. 添加基本服务 在服务器端添加配置文件:vim /etc/nagios/conf.d/192.168.0.28.cfg # 添加如下内容 define host{ # 定义主机:192.168.0.28 use linux-server host_name 192.168.0.28 alias 0.28 address 192.168.0.28 } define service{ # 添加 ping 监控服务 use generic-service host_name 192.168.0.28 service_description check_ping check_command check_ping!100.0,20%!200.0,50% max_check_attempts 5 normal_check_interval 1 } define service{ # 添加 ssh 监控服务 use generic-service host_name 192.168.0.28 service_description check_ssh check_command check_ssh max_check_attempts 5 normal_check_interval 1 } define service{ # 添加 http 监控服务 use generic-service host_name 192.168.0.28 service_description check_http check_command check_http max_check_attempts 5 normal_check_interval 1 } 4. 重新加载nagios配置文件 service nagios reload 通过浏览器访问 http://192.168.0.8/nagios 通过 Current Status --> Service可以看到添加的主机 192.168.0.28 对应的3个服务的列表,刚开始都是 pending 状态,过一段时间 Status 会变成 OK 字样。 5. 配置文件解释说明 我们定义的配置文件中一共监控了三个 service: ssh ping http 这三个项目是使用本地的 nagios 工具去连接远程机器,及时没有安装任何的插件也是可以监测到的。 max_check_attempts 5:尝试检测到5次有问题才会告警,如果设置为1,一旦检测到问题立马告警normal_check_interval 1:重新检测的时间间隔,单位是分钟,默认是 3 分钟。notification_interval 60:出现异常后故障一直没有解决,nagios再次对使用者发出通知的时间。设置为0,仅通知一次。 六、监控客户端本地系统服务 其它的一些 service 诸如负载、磁盘使用等需要服务端通过 nrpe 去连接到远程主机获得信息,所以需要远程主机安装 nrpe 服务以及相应的插件程序。 1. 监控客户端 IP 地址:192.168.0.28 2. 客户端需要安装的软件 [root@nagios ~]# yum install epel-release -y # 安装epel源 [root@nagios ~]# yum install nrpe -y # 安装nrpe服务软件 [root@nagios ~]# yum install nagios-plugins-all -y # 安装所有nagios插件 3. 配置客户端nrpe服务能被监控 [root@nagios ~]# vim /etc/nagios/nrpe.cfg # 修改如下内容 allowed_hosts=127.0.0.1,192.168.0.8 # 添加192.168.0.8来监控 dont_blame_nrpe=1 # 修改0为1,设置可以传递参数command[check_hda1]=/usr/lib64/nagios/plugins/check_disk -w 20% -c 10% -p /dev/sda1 4. 客户端启动nrpe服务 [root@nagios ~]# service nrpe start Starting nrpe: [确定] 5. 服务器端配置来监控客户端 [root@vip ~]# vim /etc/nagios/objects/commands.cfg 在最后面增加如下: define command{ command_name check_nrpe command_line $USER1$/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ } [root@vip ~]# vim /etc/nagios/conf.d/192.168.0.28.cfg增加如下内容: define service{ # 添加监控负载 use generic-service host_name 192.168.0.28 service_description check_load check_command check_nrpe!check_load max_check_attempts 5 normal_check_interval 1 } define service{ # 添加监控sda1磁盘 use generic-service host_name 192.168.0.28 service_description check_disk_hda1 check_command check_nrpe!check_hda1 max_check_attempts 5 normal_check_interval 1 } 6. 重新加载服务器配置 [root@vip ~]# service nagios reload 通过浏览器访问 http://192.168.0.8/nagios 正常显示。 七、Nagios 配置图形显示 Nagios 对服务或主机监控的是一个瞬时状态,有时候系统管理员需要了解主机在一段时间内的性能及服务的响应状态,并且形成图表,这就需要通过查看日志数据来分析。但是这种方式不仅烦琐,而且抽象。为了能更直观的查看主机运行状态,这里采用 PNP 来实现此功能。PNP 是一个小巧的开源软件包,它是基于 PHP 和 Perl 脚本编写,PNP 可以利用 rrdtoul 工具将 Nagios 采集的数据绘制成图表,然后显示主机或者服务在一段时间内运行的状况。注:以下操作都是针对中心服务器的。 1. 安装pnp4nagios软件 [root@vip ~]# yum install -y pnp4nagios rrdtool 2. 修改主配置文件 [root@vip ~]# vim /etc/nagios/nagios.cfg 修改如下配置: process_performance_data=1 host_perfdata_command=process-host-perfdata service_perfdata_command=process-service-perfdata enable_environment_macros=1 3. 修改command.cfg配置文件 [root@vip ~]# vim /etc/nagios/objects/commands.cfg注释掉原有对process-host-perfdata和process-service-perfdata重新定义 define command { command_name process-service-perfdata command_line /usr/bin/perl /usr/libexec/pnp4nagios/process_perfdata.pl } define command { command_name process-host-perfdata command_line /usr/bin/perl /usr/libexec/pnp4nagios/process_perfdata.pl -d HOSTPERFDATA } 4. 修改templates.cfg配置文件 vim /etc/nagios/objects/templates.cfg define host { name hosts-pnp register 0 action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_ process_perf_data 1 } define service { name srv-pnp register 0 action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$ process_perf_data 1 } 5. 修改host和service的配置 [root@vip ~]# vim /etc/nagios/conf.d/192.168.0.28.cfg 所有主机使用的模板后边添加hosts-pnp: define host{ use linux-server,hosts-pnp 所有服务使用的模板后边添加srv-pnp: define service{ use generic-service,srv-pnp host_name 192.168.0.48 service_description check_disk_hda1 check_command check_nrpe!check_hda1 } 6. 重启服务,再次访问 [root@vip conf.d]# service nagios restart [root@vip conf.d]# service httpd restart [root@vip conf.d]# service npcd start 浏览器访问:http://192.168.0.8/nagios 点击 Current Status -> service 即可显示出"曲线"图标,等待一段时间,点击图标即可看到数据。 八、配置邮件告警 1. 增加联系人组 vim /etc/nagios/objects/contacts.cfg 增加如下内容: define contact{ contact_name 123 use generic-contact alias aming email liwei0526vip@163.com } define contact{ contact_name 456 use generic-contact alias aaa email liwei0526vip@163.com } define contactgroup{ contactgroup_name common alias common members 123,456 } 2. 在需要告警的相关服务中添加告警联系人 然后在要需要告警的服务里面加上contactgroup ,这里示例中以 http 服务为例: define service{ use generic-service,srv-pnp host_name 192.168.0.48 service_description check_http check_command check_http max_check_attempts 5 normal_check_interval 1 contact_groups common # 添加告警联系人 } 重启服务,ok。
LAMP 架构在企业里用得非常广泛,目前很多电商公司、游戏公司、移动互联网公司大多都采用这种架构。LAMP指的是Linux、Apache、MySQL、PHP。下面记录了 LAMP 架构系统服务的搭建过程。 一、MySQL数据库安装 1. 系统环境 CentOS 6.4 x86_64 Mini 版本安装 2. 基础软件包安装 [root@vip ~]# yum install gcc vim make wget -y 3. 下载 # 进入源码存放目录 [root@vip ~]# cd /usr/local/src # 下载MySQL安装包 [root@vip src]# wget downloads.mysql.com/archives/get/file/mysql-5.5.40-linux2.6-x86_64.tar.gz 4. 解压安装 # 解压 [root@vip src]# tar -zxf mysql-5.5.40-linux2.6-x86_64.tar.gz # 设置安装路径 [root@vip src]# mv mysql-5.5.40-linux2.6-x86_64 /usr/local/mysql 5. 建立MySQL用户 [root@vip src]# useradd -s /sbin/nologin -M mysql 6. 准备数据目录 # 进入MySQL安装目录 [root@vip src]# cd /usr/local/mysql # 创建MySQL数据目录 [root@vip mysql]# mkdir -p /var/lib/mysql # 设置目录权限 [root@vip mysql]# chown -R mysql:mysql /var/lib/mysql 7. 初始化数据库 [root@vip mysql]# ./scripts/mysql_install_db --user=mysql --datadir=/var/lib/mysql when specifying MySQL privileges ! Installing MySQL system tables... OK Filling help tables... OK #看到2个OK说明初始化成功 8. 拷贝配置文件 [root@vip mysql]# /bin/cp support-files/my-large.cnf /etc/my.cnf 9. 拷贝启动脚本 # 拷贝启动脚本 [root@vip mysql]# /bin/cp support-files/mysql.server /etc/init.d/mysqld # 赋予可执行权限 [root@vip mysql]# chmod 755 /etc/init.d/mysqld 10. 修改启动脚本 [root@vip mysql]# vim /etc/init.d/mysqld # 修改设置内容如下 basedir=/usr/local/mysql datadir=/var/lib/mysql 11. 把MySQL添加到服务 # 添加到service列表 [root@vip mysql]# chkconfig --add mysqld # 设置开机启动 [root@vip mysql]# chkconfig mysqld on 12. 启动MySQL服务 [root@vip mysql]# service mysqld start Starting MySQL... SUCCESS! 13. 查看验证MySQL启动进程 [root@vip mysql]# ps -e | grep mysql 1830 pts/1 00:00:00 mysqld_safe 2121 pts/1 00:00:00 mysqld 14. 配置MySQL环境变量 将 MySQL 客户端命令路径加入 PATH 环境变量中去。 # 设置PATH环境变量 [root@vip mysql]# echo 'export PATH=$PATH:/usr/local/mysql/bin' > /etc/profile.d/mysql.sh [root@vip mysql]# source /etc/profile.d/mysql.sh 15. 登录MySQL测试 [root@vip mysql]# mysql # 默认没有密码 Your MySQL connection id is 1 Server version: 5.5.40-log MySQL Community Server (GPL) Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. ... ... mysql> 二、Apache服务安装 apache可以通过yum的方式来安装,也可以通过源码编译的方式来安装,这里采用编译源码的方式进行安装。 1. 系统环境 CentOS 6.4 x86_64 Mini 版本安装 2. 下载解压apache安装包 [root@vip src]# cd /usr/local/src [root@vip src]# wget http://mirror.bit.edu.cn/apache/httpd/httpd-2.2.31.tar.gz [root@vip src]# tar zxf httpd-2.2.31.tar.gz 3. 安装必要的库和工具 [root@vip src]# yum install -y pcre pcre-devel apr apr-devel zlib-devel gcc make 4. 配置编译参数 [root@vip httpd-2.2.31]# cd httpd-2.2.31 [root@vip httpd-2.2.31]# ./configure \ --prefix=/usr/local/apache2 \ --with-included-apr \ --enable-so \ --enable-deflate=shared \ --enable-expires=shared \ --enable-rewrite=shared \ --with-pcre [root@vip httpd-2.2.31]# echo $? 5. 编译安装 [root@vip httpd-2.2.31]# make && make install [root@vip httpd-2.2.31]# echo $? 6. 配置apache环境变量 [root@vip httpd-2.2.31]# echo 'export PATH=$PATH:/usr/local/apache2/bin' > /etc/profile.d/http.sh [root@vip httpd-2.2.31]# source /etc/profile.d/http.sh 7. apache的启动和停止 apachectl start # 启动 apachectl stop # 停止 apachectl restart # 重启apachectl -t # 检查语法apachectl -M # 查看加载模块 8. 将apache加入系统服务 # 拷贝服务脚本 [root@vip httpd-2.2.31]# cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd [root@vip httpd-2.2.31]# vim /etc/init.d/httpd # 第一行下边添加2行内容 #!/bin/sh # chkconfig: 2345 61 61 # description: Apache # 添加到系统服务 并设置开机启动 [root@vip httpd-2.2.31]# chkconfig --add httpd [root@vip httpd-2.2.31]# chkconfig httpd on 9. 验证服务是否正常 [root@vip httpd-2.2.31]# service httpd start httpd: apr_sockaddr_info_get() failed for vip # 出现警告信息 httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName 解决警告信息的方法:去掉 ServerName www.example.com:80 行的注释#。 三、PHP系统安装 1. 下载解压安装包 [root@vip ~]# cd /usr/local/src [root@vip src]# wget http://cn2.php.net/get/php-5.5.38.tar.gz/from/this/mirror -O php-5.5.38.tar.gz [root@vip src]# tar zxf php-5.5.38.tar.gz 2. 下载依赖库和工具 [root@vip src]# yum install -y epel-release[root@vip src]# yum install -y libxml2-devel openssl openssl-devel bzip2 bzip2-devel \ libjpeg-devel libpng libpng-devel freetype freetype-devel libmcrypt-devel 3. 编译配置安装选项 [root@vip php-5.5.38]# cd php-5.5.38 [root@vip php-5.5.38]# ./configure \ --prefix=/usr/local/php \ --with-apxs2=/usr/local/apache2/bin/apxs \ --with-config-file-path=/usr/local/php/etc \ --with-mysql=/usr/local/mysql \ --with-libxml-dir \ --with-gd \ --with-jpeg-dir \ --with-png-dir \ --with-freetype-dir \ --with-iconv-dir \ --with-zlib-dir \ --with-bz2 \ --with-openssl \ --with-mcrypt \ --enable-soap \ --enable-gd-native-ttf \ --enable-mbstring \ --enable-sockets \ --enable-exif \ --disable-ipv6 4. 编译安装 [root@vip php-5.5.38]# make && make install [root@vip php-5.5.38]# echo $? 5. 拷贝php配置文件 [root@vip php-5.5.38]# cp php.ini-production /usr/local/php/etc/php.ini 6. 配置php环境变量 [root@vip php-5.5.38]# echo 'export PATH=$PATH:/usr/local/php/bin' > /etc/profile.d/php.sh [root@vip php-5.5.38]# source /etc/profile.d/php.sh 到此为止,php完成基本的编译安装,后续解析php还得另外配置。 四、配置支持php解析 1. 修改apache配置文件 <Directory /> Options FollowSymLinks AllowOverride None Order deny,allow Allow from all # Deny修改为Allow </Directory> 2. 支持php脚本解析 AddType application/x-compress .Z AddType application/x-gzip .gz .tgz AddType application/x-httpd-php .php # 添加这1行 3. 添加php索引支持 <IfModule dir_module> DirectoryIndex index.html index.htm index.php # 添加php索引 </IfModule> 4. 测试配置语法和重启apache服务 [root@vip ~]# apachectl -t Syntax OK [root@vip ~]# service httpd restart 5. 测试php解析 编写测试文件:/usr/local/apache2/htdocs/index.php <?php echo "hello php!" ?> 命令行curl测试: [root@vip htdocs]# curl localhost/index.php hello php![root@vip htdocs]# 解析 php 成功!
这篇博文接着上篇文章《使用 python 管理 mysql 开发工具箱 - 1》,继续写下自己学习 python 管理 MySQL 中的知识记录。 一、MySQL 的读写分离 学习完 MySQL 主从复制之后,可以考虑实现 MySQL 的读写分离,从而提高 MySQL 系统的整体性能。具体控制读写的路由功能可以交给应用程序或者MySQL-Proxy 程序来实现。读写分离其实就是让 Client 写入 master,而读数据从 slave 节点,这样减少了 master 既写又读的压力。这里没有具体介绍如何实现读写分离的功能,后续研究一下 MySQL Proxy 程序,这是 MySQL 官方提供的实现读写分离的程序。 二、slave 节点的负载均衡 1. 使用 DNS 来实现负载均衡 往往 slave 节点是多个,实现 slave 节点的负载均衡是非常重要的。其实可以采用 dns 的功能,一个域名指向多个 slave 的 IP 地址,这样 Client 每次解析到的 slave 地址都是平均分布的,简单的实现了负载均衡的功能。 2. 健康检查监控 我们自己需要实现一个监控程序,检查 slave 的健康情况,包括如下几个方面: 是否能连接 slave 节点,判断 timeout 是否会超时即可 检查 slave 状态,是否能正常工作。执行 show slave status\G; 查看 IO/SQL Running 是否正常。 主从同步时间间隔是否过长。如果 Second_behind_master > 2 认为太慢了 监控程序扫描所有 slave 节点,判断上述指标,把健康的 slave 节点加入到 DNS 解析记录里边,有问题的剔除出去。 三、DNS 基本安装和配置 1. 安装 rpm 包 [root@vip ~]# yum install bind -y 2. 修改配置文件named.conf options { listen-on port 53 { any; }; # 修改为any listen-on-v6 port 53 { ::1; }; ... ... ... ... allow-query { any; }; # 修改为any 添加内容: zone "example.com" IN { type master; file "example.com.zone"; }; 3. 添加设置区域zone文件 [root@vip ~]# vim /var/named/example.com.zone # 添加如下内容 $TTL 1D @ IN SOA ns.example.com. root.example.com. ( 0 ; serial 1D ; refresh 1H ; retry 1W ; expire 3H ); minimum NS ns.example.com. ns A 192.168.0.8 www A 192.168.0.2 4. 启动named服务 [root@vip ~]# service named start 5. 测试dns解析 [root@vip ~]# host www.example.com. localhost Using domain server: Name: localhost Address: :: 1 #53 Aliases: # 成功解析OK。 www.example.com has address 192.168.0.2 四、DNS 实现动态记录更新 DNS动态更新必要性: 某个slave出现故障,DNS应该将该slave剔除,不要解析这个slave节点 复制比较慢,拖后腿的slave节点也应该剔除出去。 考虑:类似keepalived的健康检查。 1. 生成key文件 [root@vip ~]# dnssec-keygen -a HMAC-MD5 -b 256 -n HOST -r /dev/urandom dnskey 生成 2 个文件: [root@vip ~]# ls Kexample.com.+157+46922.* Kexample.com.+157+46922.key Kexample.com.+157+46922.private 2. 修改配置文件named.conf,让dns支持更新:添加如下代码 key "example.com" { # 该key为新增加内容 algorithm HMAC-MD5; secret "25z/5wjwD4GsMgQluWagfkQ9TSqpoJzYbh/I/QEZo2M="; # secret内容参考Kexample.com.+157+46922.key文件内容 }; zone "example.com" IN { type master; file "example.com.zone"; allow-update { key "example.com"; }; # 增加一行 }; 3. 创建update.txt文件 使用nsupdate前需要创建个文件,告诉nsupdate怎么样去更新update.txt,内容如下: server 127.0.0.1 debug yes zone example.com. update delete s.db.example.com. A update add s.db.example.com. 86499 A 192.168.0.1 update add s.db.example.com. 86499 A 192.168.0.2 update add s.db.example.com. 86499 A 192.168.0.8 update add s.db.example.com. 86499 A 127.0.0.1 show send 4. 赋予/var/named目录写权限 chmod g+w /var/named 5. 手动更新dns记录 [root@vip ~]# nsupdate -k Kdnskey.+157+42183.key update.txt 6. 验证 [root@vip ~]# host s.db.example.com localhost Using domain server: Name: localhost Address: ::1#53 Aliases: s.db.example.com has address 192.168.0.1 s.db.example.com has address 192.168.0.2 s.db.example.com has address 192.168.0.8 s.db.example.com has address 127.0.0.1 7. 问题总结 1. 看日志文件log 2. 看权限错误 3. 看程序的用户 ps -ef | grep named 4. 看相关配置文件的权限 5. iptables和selinux是否关闭 五、Python 实现 DNS 查询 需要使用到 dnspython 模块,需要执行 pip install dnspython 安装此模块。 参考:http://blog.chinaunix.net/uid-24690947-id-1747409.html 六、Python 实现 DNS 动态更新 代码参考: # 动态更新dns记录 def dnsUpdate(zone, name, rdlist): key = dns.tsigkeyring.from_text({zone:keyring}) up = dns.update.Update(zone, keyring=key) rdata_list = [dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, i) for i in rdlist] ttl = 60 rdata_set = dns.rdataset.from_rdata_list(ttl, rdata_list) up.replace(name, rdata_set) q = dns.query.tcp(up, '127.0.0.1') # 调用 dnsUpdate('example.com', 's.db', alive) 七、MySQL 从服务器状态检查 按照检查的要求,对 slave 进行健康检查,代码如下: #!/usr/bin/env python #encoding: utf-8 import MySQLdb # 通过shell命令获取key列表格式 # mysql -S /tmp/slave01.sock -e "show slave status\G" | awk -F: 'NR!=1{print $1}' | awk '{printf "\""$1"\",\n"}' > a.txt keys = ( "Slave_IO_State", "Master_Host", "Master_User", "Master_Port", "Connect_Retry", "Master_Log_File", "Read_Master_Log_Pos", "Relay_Log_File", "Relay_Log_Pos", "Relay_Master_Log_File", "Slave_IO_Running", "Slave_SQL_Running", "Replicate_Do_DB", "Replicate_Ignore_DB", "Replicate_Do_Table", "Replicate_Ignore_Table", "Replicate_Wild_Do_Table", "Replicate_Wild_Ignore_Table", "Last_Errno", "Last_Error", "Skip_Counter", "Exec_Master_Log_Pos", "Relay_Log_Space", "Until_Condition", "Until_Log_File", "Until_Log_Pos", "Master_SSL_Allowed", "Master_SSL_CA_File", "Master_SSL_CA_Path", "Master_SSL_Cert", "Master_SSL_Cipher", "Master_SSL_Key", "Seconds_Behind_Master", "Master_SSL_Verify_Server_Cert", "Last_IO_Errno", "Last_IO_Error", "Last_SQL_Errno", "Last_SQL_Error", ) # 模拟一下slave节点列表, 设置注意实验时设置某些实例为不健康状态 conf = { 'master':'127.0.0.1:3306', 'slave':[ '127.0.0.1:3307', '192.168.0.8:3307', '127.0.0.1:3308', '192.168.0.8:3308', '127.0.0.1:3309', '192.168.0.8:3309', ] } # 检查slave节点的状态是否正常 def checkSlaveStatus(host, port): try: conn = MySQLdb.connect(host=host, port=port, user='root', connect_timeout=1) except Exception, e: print e return False cur = conn.cursor() cur.execute('show slave status') data = cur.fetchall() # 只获取到了冒号后边的value, key没有获取到, 和sql shell显示不同. # 将keys和data组合为字典的结构 data = dict(zip(keys, data[0])) # IO/SQL Running 是否正常 if data['Slave_IO_Running'] == 'No' or data['Slave_SQL_Running'] == 'No': return False elif data['Seconds_Behind_Master'] > 2: # 主从复制时间持续超过2秒, 太慢了 return False # 到这里肯定是没问题的了 return True # 将ip:port解析为主机+端口 def parseIP(s): host, port = s.split(':') return host, int(port) if __name__ == '__main__': #host = '127.0.0.1' # 写IP好像连不上, 需要授权相应的主机 #port = 3307 alive = [] for ip in conf['slave']: host, port = parseIP(ip) print checkSlaveStatus(host, port) 八、MySQL 从服务器状态更新 对 slave 健康状态检查后,将健康的节点列表记录,更新到 DNS 记录中。代码如下: #!/usr/bin/env python #encoding: utf-8 import MySQLdb import dns.query import dns.update import dns.tsigkeyring # 通过shell命令获取key列表格式 # mysql -S /tmp/slave01.sock -e "show slave status\G" | awk -F: 'NR!=1{print $1}' | awk '{printf "\""$1"\",\n"}' > a.txt keys = ( "Slave_IO_State", "Master_Host", "Master_User", "Master_Port", "Connect_Retry", "Master_Log_File", "Read_Master_Log_Pos", "Relay_Log_File", "Relay_Log_Pos", "Relay_Master_Log_File", "Slave_IO_Running", "Slave_SQL_Running", "Replicate_Do_DB", "Replicate_Ignore_DB", "Replicate_Do_Table", "Replicate_Ignore_Table", "Replicate_Wild_Do_Table", "Replicate_Wild_Ignore_Table", "Last_Errno", "Last_Error", "Skip_Counter", "Exec_Master_Log_Pos", "Relay_Log_Space", "Until_Condition", "Until_Log_File", "Until_Log_Pos", "Master_SSL_Allowed", "Master_SSL_CA_File", "Master_SSL_CA_Path", "Master_SSL_Cert", "Master_SSL_Cipher", "Master_SSL_Key", "Seconds_Behind_Master", "Master_SSL_Verify_Server_Cert", "Last_IO_Errno", "Last_IO_Error", "Last_SQL_Errno", "Last_SQL_Error", ) # 模拟一下slave节点列表, 设置注意实验时设置某些实例为不健康状态 conf = { 'master':'127.0.0.1:3306', 'slave':[ '127.0.0.1:3307', '192.168.0.8:3307', '127.0.0.1:3308', '192.168.0.8:3308', '127.0.0.1:3309', '192.168.0.8:3309', ] } keyring = '25z/5wjwD4GsMgQluWagfkQ9TSqpoJzYbh/I/QEZo2M=' # 检查slave节点的状态是否正常 def checkSlaveStatus(host, port): try: conn = MySQLdb.connect(host=host, port=port, user='root', connect_timeout=1) except Exception, e: print e return False cur = conn.cursor() cur.execute('show slave status') data = cur.fetchall() # 只获取到了冒号后边的value, key没有获取到, 和sql shell显示不同. # 将keys和data组合为字典的结构 data = dict(zip(keys, data[0])) # IO/SQL Running 是否正常 if data['Slave_IO_Running'] == 'No' or data['Slave_SQL_Running'] == 'No': return False elif data['Seconds_Behind_Master'] > 2: # 主从复制时间持续超过2秒, 太慢了 return False # 到这里肯定是没问题的了 return True # 将ip:port解析为主机+端口 def parseIP(s): host, port = s.split(':') return host, int(port) # 动态更新dns记录 def dnsUpdate(zone, name, rdlist): key = dns.tsigkeyring.from_text({zone:keyring}) up = dns.update.Update(zone, keyring=key) rdata_list = [dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, i) for i in rdlist] ttl = 60 rdata_set = dns.rdataset.from_rdata_list(ttl, rdata_list) up.replace(name, rdata_set) q = dns.query.tcp(up, '127.0.0.1') #print q if __name__ == '__main__': #host = '127.0.0.1' # 写IP好像连不上, 需要授权相应的主机 #port = 3307 alive = [] for ip in conf['slave']: host, port = parseIP(ip) if checkSlaveStatus(host, port): alive.append(host) # 解释下这里为什么要设置slave的alive集群阈值 # 如果不设置阈值, 那么存在健康的slave过少, 会导致slave的雪崩现象 # 反而会影响服务的正常运行, 保证只有在一定数量情况下才更新dns记录. if float(len(alive))/len(conf['slave']) > 0.6: dnsUpdate('example.com', 's.db', alive) # 注意: # 1. dns服务一定要保证/var/named目录组用户有写的权限 # 2. iptables 和 selinux 一定要设置好, 最好设置为关闭状态. 九、MySQL 监控测试 通过上边的代码已经实现了 slave 的健康检查,DNS 的动态更新。现在可以做一下测试: > 执行: [root@vip mysqlmanager]# python mysql_dns_monitor.py > 结果: [root@vip mysqlmanager]# host s.db.example.com localhost Using domain server: Name: localhost Address: ::1#53 s.db.example.com has address 127.0.0.1 # 已经更新了记录 s.db.example.com has address 192.168.0.8 # 更新了记录,并解析到ip地址,表明已经成功OK. > 扩展:其实可以准备几台独立的虚拟机来做测试,每台虚拟机作为要给 slave 节点,模拟一些健康问题,看是否能够正确检测并更新到。 十、MySQL 从服务器信息来自CMDB 待更新。。。。
一、MySQL 常见的备份方式 1. 直接拷贝数据库文件(物理拷贝) 2. 使用 mysqldump 工具备份 3. 使用 mysqlhotcopy 工具备份 4. 使用 mysql 的主从同步复制,实现数据实时同步备份 二、MySQL 物理数据文件结构介绍 1. 日志文件 错误日志 err log 二进制日志 binary log 更新日志 update log 查询日志 query log 慢查询日志 slow query log innodb 的 redo 日志 2. 数据文件 >>>对于 myisam 来说: 表结构信息:.frm 数据信息:.myd 数据索引信息;.myi >>>对于 Innodb 来说: 独享表空间:.ibd 共享表空间:.ibdata 3. 系统文件 配置文件:my.cnf 进程文件:xxx.pid socket文件:xxx.sock 4. replication 文件 master.info:存储在 slave 端目录下,关于 master 和 slave 相关信息 relay log:存储 I/O 进程从 master 读取的 bin-log 信息,然后由 slave 端的 SQL 线程从该 binary log 中读取解析过的日志信息,转化成 slave 所能执行的 query 语句 index:则是存放 binary log 的路径,也就是目录文件 三、使用 mysqldump 备份与恢复 1. 备份原理 mysqldump 备份原理比较简单,先查出需要备份的表结构,在文本文件中生成一个 create 语句;然后将表中的所有数据记录转换成一条 insert 语句;通过这些语句就能够创建表并插入数据。 2. 备份一个数据库 基本语法: >>> mysqldump -u username -p dbname table1 table2 ... > BackupName.sql 实例说明: mysqldump -u root -p test person > /tmp/backup.sql 3. 备份多个数据库 基本语法: mysqldump -u username -p --databases dbname2 dbname2 > BackupName.sql 实例说明: mysqldump -u root -p --databases test mysql > /tmp/backup.sql 4. 备份所有数据库 基本语法: mysqldump -u username -p -all-databases > BackupName.sql 实例说明: mysqldump -u -root -p -all-databases > /tmp/all.sql 5. 数据恢复 基本语法: mysql -u root -p [dbname] < backup.sql 实例说明: mysql -u root -p < /tmp/backup.sql 四、直接复制数据库目录 MySQL 有一种非常简单的备份方法,就是将 MySQL 中的数据库文件直接复制出来。这是最简单,速度最快的方法。不过在此之前,要先将服务器停止,这样才可以保证在复制期间数据库的数据不会发生变化。如果在复制数据库的过程中还有数据写入,就会造成数据不一致。这种情况在开发环境可以,但是在生产环境中很难允许备份服务器。 注意:这种方法不适用于 InnoDB 存储引擎的表,而对于 MyISAM 存储引擎的表很方便。同时,还原时 MySQL 的版本最好相同。 五、使用 mysqlhotcopy 快速备份 1. mysqlhotcopy 安装 2. 语法和常用选项 3. 实例备份 4. 实例恢复 六、备份策略 待续。 ---------- 本文结束 ----------
1. 安装制作工具 mkisofs yum install mkisofs -y 2. Linux 操作系统镜像 iso 打包 mkisofs -o /root/1229.iso \ -V mini7 -b isolinux/isolinux.bin \ -c isolinux/boot.cat \ -no-emul-boot -boot-load-size 4 \ -boot-info-table -R -J -T -v . 3. 校验并写入 md5 值 implantisomd5 /root/1229.iso
Mysql 是一个比较优秀的开源的数据库,很多公司都在使用。作为运维人员,经常做着一些重复性的工作,比如创建数据库实例,数据库备份等,完全都可以使用 python 编写一个工具来实现。 一、模块 ConfigParser 学习 ConfigParser 模块可以解析类似 MySQL 配置文件 my.cnf 和 windows 下的 ini 配置文件。使用 ConfigParser 模块解析这类配置文件非常方便,而且可以增加、设置或删除配置文件的 section option 等,如果你使用 C 语言解析过配置文件,你就能明白 ConfigParser 模块的高效。 1. 使用前需要注意的 ConfigParser 模块在 python 2.6 版本中不支持没有 value 的 option 解析,解决的方法有 2 种: (a) 升级 python 2.6 到 python 2.7。(b) 拷贝一个 python 2.7 标准库的 ConfigParser.py 文件到 2.6 的标准库覆盖原来的 ConfigParser.py 文件。 推荐使用第一种方式,直接,一劳永逸;不过第二种方式比较简单。我用的是 CentOS 6,默认 python2.6.6,我升级 python 版本为 2.7.10。 参考我的一篇博文:http://www.cnblogs.com/liwei0526vip/p/6219998.html 2. ConfigParser 基本使用 导入模块并实例化 import ConfigParser conf = ConfigParser.ConfigParser(allow_no_value=True) 读配置文件 read/readfp In [3]: conf.read('/root/my.cnf') Out[3]: ['/root/my.cnf'] # 返回配置文件列表,其实可以读取多个配置文件的列表,实际应用中没有意义,一般都只读取一个配置文件 # readfp() 函数可以读取 open() 函数返回的文件句柄 fp section 相关操作 # 配置文件中有哪些 section In [4]: conf.sections() Out[4]: ['client', 'mysqld', 'mysqldump', 'mysql', 'myisamchk', 'mysqlhotcopy'] # 添加 section [user] In [5]: conf.add_section('user') In [6]: conf.sections() Out[6]: ['client', 'mysqld', 'mysqldump', 'mysql', 'myisamchk', 'mysqlhotcopy', 'user'] # 删除 section [client] 注意:会把该section下的option全部删除 In [10]: conf.remove_section('client') Out[10]: True # 判断是否有section [user] In [15]: conf.has_section('user') Out[15]: True options 相关操作 In [16]: conf.options('mysqld') # 列出options In [17]: conf.set('mysqld','key','value') # 添加options [key] 同样可以修改options In [18]: conf.remove_option('mysqld','key') # 删除options In [23]: conf.has_option('mysqld', 'key') # 判断是否有[key] 获取 get 值 value In [24]: conf.get('mysqld', 'port') Out[24]: '3306' # 返回value字符串格式 In [25]: conf.getint('mysqld', 'port') Out[25]: 3306 # 返回value整数格式 In [26]: conf.getfloat('mysqld', 'xxxx') # 返回value浮点数格式 In [27]: conf.getboolean('mysqld', 'xxxx') # 返回value布尔型格式 In [28]: conf.items('mysqld') Out[28]: [('port', '3306'), ('query_cache_size', '16M'), ... ... ... ... ('server-id', '1')] 写入配置文件 # 上述的设置是对内存中数据进行操作,并非实际写入配置文件,通过write写入配置文件 In [29]: fp = open('/tmp/my.cnf', 'w') In [30]: conf.write(fp) In [31]: fp.close() 3. 高级功能项 ConfigParser 模块中定义了3个类对配置文件进行操作。分别是 RawConfigParser、ConfigParser、SafeConfigParser。RawCnfigParser 是最基础的INI文件读取类,ConfigParser、SafeConfigParser支持对%(value)s变量的解析 >>> 设定配置文件 test.conf [test] url = http://%(host)s:%(port)s/Portal host = localhost port = 8080 >>> 代码中使用ConfigParser解析: import ConfigParser conf = ConfigParser.ConfigParser() conf.read("test.conf") print conf.get("test", "url") conf.set("test", "url2", "%(host)s:%(port)s") print conf.get("test", "url2") >>> 终端输出内容: http://localhost:8080/Portal localhost:8080 SafeConfigParser 可以实现 ConfigParser 同样的功能。 二、生成 MySQL 配置文件模板 通过借助于 ConfigParser 模块,写了一个 MySQLDConfig 的工具类,使用这个类可以对 MySQL 配置文件进行一些列的操作:生成模板、添加选项、修改选项等,非常方便,作为后续开发工具箱的一个重要工具库。代码 mysql.py 如下: 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from ConfigParser import ConfigParser 5 import sys,os 6 7 # 定义MySQLConfig类 继承ConfigParser类 8 class MySQLDConfig(ConfigParser): 9 def __init__(self, config, **kw): 10 ConfigParser.__init__(self, allow_no_value=True) 11 # must support new style: object++ modify source code 12 #super(ConfigParser, self).__init__(allow_no_value=True) 13 # 所有选项都会汇聚到该字典中,统一写入配置文件 14 self.mysqld_vars = {} 15 self.config = config 16 if os.path.exists(self.config): 17 self.read(self.config) 18 self.get_mysqld_vars() 19 else: 20 self.get_defaults_mysqld_vars() 21 self.set_mysqld_vars(kw) 22 23 # 统一的接口:将指定的kw写入setattr和全局字典 24 def set_mysqld_vars(self, kw): 25 for k,v in kw.items(): 26 setattr(self, k, v) 27 self.mysqld_vars[k] = v 28 29 # 读取配置文件[mysqld]段的所有选项并set 30 def get_mysqld_vars(self): 31 rst = {} 32 options = self.options('mysqld') 33 for o in options: 34 rst[o] = self.get('mysqld', o) 35 self.set_mysqld_vars(rst) 36 37 # 设置默认的mysql选项, 如果没有配置文件,则使用默认配置项 38 def get_defaults_mysqld_vars(self): 39 defaults = { 40 'port':'3306', 41 'socket':'/var/lib/mysql/mysql.sock', 42 'key_buffer_size':'256M', 43 'max_allowed_packet':'1M', 44 'table_open_cache':'256', 45 'sort_buffer_size':'1M', 46 'read_buffer_size':'1M', 47 'read_rnd_buffer_size':'4M', 48 'myisam_sort_buffer_size':'64M', 49 'thread_cache_size':'8', 50 'query_cache_size':'16M', 51 'thread_concurrency':'8', 52 'user': 'mysql', 53 } 54 self.set_mysqld_vars(defaults) 55 56 # 特殊的接口 单独去设置/添加变量 比如skip-salve-start等不能通过变量传递的选项 57 def set_vars(self, k, v): 58 self.mysqld_vars[k] = v 59 60 # 将所有的选项保存到配置文件,包括:新添加和更新的 61 def save(self): 62 if not self.has_section('mysqld'): 63 self.add_section('mysqld') 64 for k, v in self.mysqld_vars.items(): 65 self.set('mysqld', k, v) 66 with open(self.config, 'w') as fd: 67 self.write(fd) 68 69 if __name__ == '__main__': 70 mc = MySQLDConfig('/root/my1.cnf', max_connections=200, port=3307) 71 #print mc.get('mysqld','port') 72 #print mc.max_connections 73 #print mc.port 74 #print mc.socket 75 76 #mc.set_vars('skip-slave-start', None) 77 mc.save() 78 print mc.port 三、创建 MySQL 实例 借助于上一章节编写的 MySQLDConfig 类,来创建 MySQL 实例,大概如下几个步骤: 生成配置文件 安装初始化 MySQL 实例 设置属主属组 启动 MySQL 运行 此时的代码结构: ├── library │ ├── __init__.py │ └── mysql.py └── mysqlmanager └── myman.py 2 directories, 3 files 具体的主代码 myman.py 如下: 1 #!/usr/bin/env python 2 #encoding: utf-8 3 4 import os, sys, time 5 from subprocess import Popen, PIPE 6 import shlex 7 from optparse import OptionParser 8 DIRNAME = os.path.dirname(__file__) 9 OPSTOOLS = os.path.abspath(os.path.join(DIRNAME, '..')) 10 sys.path.append(OPSTOOLS) 11 from library.mysql import MySQLDConfig 12 13 # MySQL实例的数据目录(会以name为子目录) 14 MYSQL_DATA_DIR = '/var/mysqlmanager/data' 15 # MySQL实例的配置文件目录 16 MYSQL_CONF_DIR = '/var/mysqlmanager/conf' 17 18 # 设置选项参数 19 def opt(): 20 parser = OptionParser() 21 parser.add_option( 22 '-n', '--name', # 指定实例的name 23 dest = 'name', 24 action = 'store', 25 default = 'myinstance' 26 ) 27 parser.add_option( 28 '-p', '--port', # 指定端口 29 dest = 'port', 30 action = 'store', 31 default = '3306' 32 ) 33 parser.add_option( 34 '-c', '--command', # create check and so on... 35 dest = 'command', 36 action = 'store', 37 default = 'check' 38 ) 39 options, args = parser.parse_args() 40 return options, args 41 42 # 初始化目录(如果不存在则创建) 43 def _init(): 44 if not os.path.exists(MYSQL_DATA_DIR): 45 os.makedirs(MYSQL_DATA_DIR) 46 if not os.path.exists(MYSQL_CONF_DIR): 47 os.makedirs(MYSQL_CONF_DIR) 48 49 # 使用glob模块读取配置文件,返回配置文件列表(绝对路径) 50 def readConfs(): 51 import glob 52 confs = glob.glob(MYSQL_CONF_DIR + '/*.cnf') 53 return confs 54 55 # 检查指定的配置文件中端口号是否与指定端口相同 56 def checkPort(conf_file, port): 57 mc = MySQLDConfig(conf_file) 58 if mc.mysqld_vars['port'] == port: 59 return True 60 else: 61 return False 62 63 # 有些配置项的值由具体示例而定,将这些不定项设置为字典 64 def _genDict(name, port): 65 return { 66 'pid-file': os.path.join(MYSQL_DATA_DIR, name,'%s.pid' % name), 67 'socket': '/tmp/%s.sock' % name, 68 'port': port, 69 'datadir': os.path.join(MYSQL_DATA_DIR, name), 70 'log-error': os.path.join(MYSQL_DATA_DIR, name, '%s.log' % name), 71 } 72 73 # 通过配置文件名称->配置文件的绝对路径 74 def getCNF(name): 75 cnf = os.path.join(MYSQL_CONF_DIR, '%s.cnf' % name) 76 return cnf 77 78 # MySQL执行安装过程 79 def mysql_install(name): 80 cnf = getCNF(name) 81 cmd = 'mysql_install_db --defaults-file=%s' % cnf 82 p = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE) 83 p.communicate() 84 p.returncode 85 86 # 为保险起见,设置MySQL实例数据目录的属主数组 87 def setOwner(datadir): 88 os.system('chown mysql:mysql %s -R' % datadir) 89 90 # 运行MySQL实例 91 def mysql_run(name): 92 cnf = getCNF(name) 93 cmd = 'mysqld_safe --defaults-file=%s &' % cnf 94 p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) 95 #time.sleep(2) 96 p.returncode 97 98 # 创建MySQL实例主函数, 由create子命令触发调用 99 def createInstance(name, port): 100 exists_confs = readConfs() 101 102 # 检查给定的端口号和名称是否已经使用 103 for conf in exists_confs: 104 if conf.split('/')[-1][:-4] == name: 105 print >> sys.stderr, 'Instance: %s is exists' % name 106 sys.exit(-1) 107 if checkPort(conf, port): 108 print >> sys.stderr, 'Port: %s is exists' % port 109 sys.exit(-1) 110 111 # 1. 生成配置文件 112 cnf = getCNF(name) 113 if not os.path.exists(cnf): 114 c = _genDict(name, port) 115 mc = MySQLDConfig(cnf, **c) 116 mc.save() 117 datadir = os.path.join(MYSQL_DATA_DIR, name) 118 if not os.path.exists(datadir): 119 # 2. 安装初始化MySQL实例 120 mysql_install(name) 121 # 3. 设置属主属组 122 setOwner(datadir) 123 # 4. 启动MySQL运行 124 mysql_run(name) 125 126 127 if __name__ == '__main__': 128 _init() 129 options, args = opt() 130 instance_name = options.name 131 instance_port = options.port 132 instance_cmd = options.command 133 if instance_cmd == 'create': 134 createInstance(instance_name, instance_port) >>>> 扩展:实现几个简单使用功能 启动 MySQL 实例 关闭 MySQL 实例 重启 MySQL 实例 四、MySQLdb 模块的使用 1. 安装 MySQL-python 直接使用 yum 工具安装: [root@mysql ~]# yum install MySQL-python -y 2. 设置库路径 使用 rpm -ql MySQL-python 命令可以查看到默认安装 MySQLdb 库路径是:/usr/lib64/python2.6/site-packages 。如果没有升级 python 版本,可以正常使用,直接 import 即可。但是我升级了 python 2.7.10 ,默认 python 2.7 不会去旧版本路径下找库的,因此无法使用。解决办法有几种: 设置环境变量 PYTHONPATH 在 sys.path 追加路径 拷贝 MySQLdb 库目录到 Python2.7 路径下 添加 .pth 文件,添加路径 我这里使用的方式是第 4 种: [root@mysql site-packages]# pwd /usr/local/python27/lib/python2.7/site-packages [root@mysql site-packages]# cat python26sitepackages.pth # 添加 xxx.pth 文件,文件中设置第三方库路径 /usr/lib64/python2.6/site-packages# *.pth文件名称随意,文件后缀一定要是.pth 3. 基本使用 # 导入MySQLdb模块 In [1]: import MySQLdb # 连接数据库 In [2]: conn = MySQLdb.Connect(host='127.0.0.1', port=3306, user='root', passwd='') # 获取游标句柄 In [3]: cur = conn.cursor() # 执行SQL语句 In [4]: cur.execute('show databases;') Out[4]: 3L # 获取SQL执行结果 In [5]: cur.fetchall() Out[5]: (('information_schema',), ('mysql',), ('test',)) 五、MySQL 配置文件检查(与内存) MySQL 可以通过配置文件和数据库命令行方式去修改参数变量,经常会有这种情况,临时通过命令行手动修改了选项参数,比如最大连接数不够了,通过 set global max_connections=200 来临时设置了参数。但是下次重启 MySQL 又会从配置文件读取参数。现在可以通过开发的工具箱来读取配置文件和内存中的参数是否一致,如果不一致,考虑以内存中的值为准,写入配置文件。部分代码如下: 1 # 根据配置文件信息连接MySQL数据库 2 def connMysql(name): 3 cnf = getCNF(name) 4 if os.path.exists(cnf): 5 mc = MySQLDConfig(cnf) 6 port = int(mc.mysqld_vars['port']) 7 host = '127.0.0.1' 8 user = 'root' 9 conn = MySQLdb.connect(host=host , port=port, user=user,) 10 cur = conn.cursor() 11 return cur 12 13 # 获取内存中全局变量, 输出是一个字典. 14 def getMyVariables(cur): 15 sql = 'show global variables' 16 cur.execute(sql) 17 data = cur.fetchall() 18 return dict(data) 19 20 # 对比配置文件和内存中变量, 找出不一致相 21 # 遗留问题: 单位不一致和目录最后'/'符号问题 ????????????????????????????????? 22 def diffMyVariables(name): 23 cur = connMysql(name) 24 vars = getMyVariables(cur) 25 cnf = getCNF(name) 26 mc = MySQLDConfig(cnf) 27 for k, v in mc.mysqld_vars.items(): 28 k = k.replace('-', '_') 29 if k in vars and v != vars[k]: 30 print k, v, vars[k] 31 32 if __name__ == '__main__': 33 _init() 34 options, args = opt() 35 instance_name = options.name 36 instance_port = options.port 37 instance_cmd = options.command 38 if instance_cmd == 'create': 39 createInstance(instance_name, instance_port) 40 elif instance_cmd == 'check': 41 diffMyVariables(instance_name) 命令执行:python myman.py -n my01 -p 3306 -c check 六、MySQL 配置文件调整(同步) 通过 check 命令找到了配置文件和内存中参数的不同,然后就可以通过 adjust 命令来调整一下配置文件,保持配置文件和内存的同步。实现代码如下: 1 # 根据命令行参数调整配置文件 2 def setMyVariables(name, k, v): 3 cnf = getCNF(name) 4 mc = MySQLDConfig(cnf) 5 mc.set_vars(k, v) 6 mc.save() 7 8 if __name__ == '__main__': 9 _init() 10 options, args = opt() 11 instance_name = options.name 12 instance_port = options.port 13 instance_cmd = options.command 14 if instance_cmd == 'create': 15 createInstance(instance_name, instance_port) 16 elif instance_cmd == 'check': 17 diffMyVariables(instance_name) 18 elif instance_cmd == 'adjust': 19 try: 20 k = args[0] 21 except IndexError: 22 print 'Usage: -c adjust max_connections 300. At least 1 arg.' 23 try: 24 v = args[1] 25 except IndexError: 26 v = None 27 setMyVariables(instance_name, k, v) 命令执行:python myman.py -n my01 -p 3306 -c adjust max_connections 200 七、数据库 MySQL 主从复制介绍 1. MySQL 主节点设置步骤 打开bin-log:log-bin = mysql-bin 设置server-id:server-id = 1 创建主从账号:grant replication slave on *.* to 'slave'@'%' identified by '123456'; 2. MySQL 从节点设置步骤 设置server-id:server-id = 2(写入配置文件) 设置master-host:master-host = 192.168.0.8(写入配置文件) 设置master-port:master-port = 3306(写入配置文件) 设置master-user:master-user = slave(写入配置文件) 设置master-password:master-pass = 123456(写入配置文件) 注意:以上 master 开头的选项可以写入配置文件,但不建议写入配置文件,不安全。可以使用如下命令代替: mysql> change master to master_host = '192.168.0.8', master_port = 3306, master_user = 'repl', master_password = '123456' 3. 先后运行 Mster 和 Slave 实例 先运行 master,后运行 slave。在从节点上运行命令查看:show slave status\G; 4. 其它选项 skip-slave-start:从库节点 slave 运行时,不会直接启动 slave 的主从复制replicate-ignore-db:忽略某个数据库的同步复制,比如 mysql 库,忽略,常容易出错 八、代码实现主库和从库创建 以下是继续使用 create 子命令来实现主从库的创建。 1 REPLICATION_USER = 'repl' 2 REPLICATION_PASS = '123456' 3 4 # 设置主从用户时执行创建用户的SQL语句 5 def runSQL(name): 6 sql = "grant replication slave on *.* to %s@'%%' identified by '%s'" % (REPLICATION_USER, REPLICATION_PASS) 7 cur = connMysql(name) 8 cur.execute(sql) 9 10 # 代替配置文件,在slave上设置master相关信息 11 def changeMaster(name, host, port, user, password): 12 sql = """change master to 13 master_host = '%s', 14 master_port = %s, 15 master_user = '%s', 16 master_password = '%s' 17 """ % (host, port, user, password) 18 19 cur = connMysql(name) 20 cur.execute(sql) 21 22 if __name__ == '__main__': 23 _init() 24 options, args = opt() 25 instance_name = options.name 26 instance_port = options.port 27 instance_cmd = options.command 28 if instance_cmd == 'create': 29 if not args: 30 createInstance(instance_name, instance_port) 31 else: 32 dbtype = args[0] 33 serverid = args[1] 34 mysql_options = {'server-id': serverid} 35 if dbtype == 'master': 36 mysql_options['log-bin'] = 'mysql-bin' 37 createInstance(instance_name, instance_port, **mysql_options) 38 runSQL(instance_name) 39 elif dbtype == 'slave': 40 # 5.5 版本以上不建议将master开头字段写入配置文件,其实低版本写入配置文件也是不妥当的 41 #mysql_options['master-host'] = args[2] 42 #mysql_options['master-port'] = args[3] 43 #mysql_options['master-user'] = REPLICATION_USER 44 #mysql_options['master-pass'] = REPLICATION_PASS 45 mysql_options['replicate-ignore-db'] = 'mysql' # 设置忽略mysql库 46 mysql_options['skip-slave-start'] = None # 设置slave运行时不自动启动主从复制 47 createInstance(instance_name, instance_port, **mysql_options) # 按照从选项来创建实例 48 host = args[2] # 传入master的主机 49 port = args[3] # 传入master的端口 50 user = REPLICATION_USER 51 password = REPLICATION_PASS 52 # 设置master信息 53 changeMaster(instance_name, host, port, user, password) 54 elif instance_cmd == 'check': 55 diffMyVariables(instance_name) 56 elif instance_cmd == 'adjust': 57 try: 58 k = args[0] 59 except IndexError: 60 print 'Usage: -c adjust max_connections 300. At least 1 arg.' 61 try: 62 v = args[1] 63 except IndexError: 64 v = None 65 setMyVariables(instance_name, k, v) 创建主库:python myman.py -n master01 -p 3306 -c create master 1创建从库:python myman.py -n slave01 -p 3307 -c create slave 2 192.168.0.8 3306 九、备份 MySQL 数据库 数据库备份很重要,要制定好备份策略。部分代码如下: 1 # MySQL实例的数据目录(会以name为子目录) 2 MYSQL_DATA_DIR = '/var/mysqlmanager/data' 3 # MySQL实例的配置文件目录 4 MYSQL_CONF_DIR = '/var/mysqlmanager/conf' 5 # MySQL实例的备份文件目录 6 MYSQL_BACK_DIR = '/var/mysqlmanager/back' 7 8 # MySQL备份,有待完善,调整下灵活性 9 def backupMysql(name): 10 import datetime 11 now = datetime.datetime.now() 12 ts = now.strftime('%Y-%m-%d.%H:%M:%S') 13 sqlfile = os.path.join(MYSQL_BACK_DIR, name, '%s.sql' % ts) 14 _dir = os.path.dirname(sqlfile) 15 16 # 如果目录不存在,则创建 17 if not os.path.exists(_dir): 18 os.makedirs(_dir) 19 cnf = getCNF(name) 20 mc = MySQLDConfig(cnf) 21 port = mc.mysqld_vars['port'] 22 # -A:所有数据库 -x:dump时锁表,只读 -F:dump前刷log 23 cmd = "mysqldump -A -x -F --master-data=1 --host=127.0.0.1 --port=%s --user=root > %s 2>/dev/null" % (port, sqlfile) 24 p = Popen(cmd, stdout=PIPE, shell=True) 25 stdin, stdout = p.communicate() 26 p.returncode 27 28 if __name__ == '__main__': 29 _init() 30 options, args = opt() 31 instance_name = options.name 32 instance_port = options.port 33 instance_cmd = options.command 34 if instance_cmd == 'create': 35 if not args: 36 createInstance(instance_name, instance_port) 37 # ...... 省略其它部分 38 elif instance_cmd == 'check': 39 diffMyVariables(instance_name) 40 elif instance_cmd == 'adjust': 41 try: 42 k = args[0] 43 except IndexError: 44 print 'Usage: -c adjust max_connections 300. At least 1 arg.' 45 try: 46 v = args[1] 47 except IndexError: 48 v = None 49 setMyVariables(instance_name, k, v) 50 elif instance_cmd == 'backup': 51 backupMysql(instance_name) 执行命令:python myman.py -n master01 -c backup 十、MySQL 备份的恢复还原 通过 backup 命令备份后得到 SQL 文件,在另一个 MySQL 实例中可以直接导入备份文件。但是如果 master 节点又有数据写入,那么导入SQL后的节点和原来节点的数据是不一致的,缺少了后续的写入数据。在 master 导出 SQL 文件后,记录 bin-log 文件和 log 位置,那么在从节点导入 SQL 文件后,再次根据 bin-log 和 pos 来和 master 进行再次同步,那么,master 和 slave 就保持数据的同步了。具体代码如下: 1 # 代替配置文件,在slave上设置master相关信息, 注意最后2个缺省参数(是为restore命令而加) 2 def changeMaster(name, host, port, user, password, logfile=None, logpos=None): 3 if logfile is None: 4 sql = """change master to 5 master_host = '%s', 6 master_port = %s, 7 master_user = '%s', 8 master_password = '%s' 9 """ % (host, port, user, password) 10 else: 11 sql = """change master to 12 master_host = '%s', 13 master_port = %s, 14 master_user = '%s', 15 master_password = '%s', 16 master_log_file = '%s', 17 master_log_pos = %s 18 """ % (host, port, user, password, logfile, logpos) 19 20 cur = connMysql(name) 21 cur.execute(sql) 22 23 # 定义MySQL备份恢复主函数,参数需要指定备份的sql文件 24 def restoreMysql(name, port, sqlfile, **kw): 25 # 创建从实例 传入选项参数 26 createInstance(name, port, **kw) 27 cnf = getCNF(name) 28 mc = MySQLDConfig(cnf) 29 port = mc.mysqld_vars['port'] 30 # 执行导入sql的shell命令 31 cmd = "mysql -h 127.0.0.1 -u root -P %s < %s" % (port, sqlfile) 32 p = Popen(cmd, stdout=PIPE, shell=True) 33 stdin, stdout = p.communicate() 34 p.returncode 35 36 # 定义内部正则过滤参数的功能函数, 获取:master-log-file 和 master-log-pos 37 def findPos(s): 38 import re 39 rlog = re.compile(r"MASTER_LOG_FILE='(\S+)'") 40 rpos = re.compile(r"MASTER_LOG_POS=(\d+)") 41 log = rlog.search(s) 42 pos = rpos.search(s) 43 if log and pos: 44 return log.group(1), pos.group(1) 45 else: 46 return (None, None) 47 48 # 读SQL文件,返回log、pos 49 def getPos(sqlfile): 50 with open(sqlfile) as fd: 51 for line in fd: 52 log, pos = findPos(line) 53 if log and pos: 54 return log, pos 55 56 if __name__ == '__main__': 57 _init() 58 # ... 省略了中间部分代码 ... 59 elif instance_cmd == 'backup': # MySQL备份命令 60 backupMysql(instance_name) 61 elif instance_cmd == 'restore': # MySQL备份的恢复命令 62 server_id = args[0] 63 master_host = args[1] 64 master_port = args[2] 65 sqlfile = args[3] 66 mysqld_options = {'server-id': server_id} 67 mysqld_options['skip-slave-start'] = None 68 mysqld_options['replicate-ignore-db'] = 'mysql' 69 # 恢复固定SQL内容(SQL文件备份的内容) 70 restoreMysql(instance_name, instance_port, sqlfile, **mysqld_options) 71 logfile, logpos = getPos(sqlfile) 72 # 指定log和pos,来配置slave激活master的主从复制(实现增量备份的恢复) 73 changeMaster(instance_name, master_host, master_port, REPLICATION_USER, REPLICATION_PASS, logfile, logpos) 测试实验过程:>>> python myman.py -n master01 -p 3306 -c create master 1>>> create database db1; create database db2; create database db3;>>> python myman.py -n master01 -p 3306 -c backup>>> create database db4;>>> python myman.py -n slave01 -p 3307 -c restore 2 192.168.0.8 3306 /var/mysqlmanager/back/master01/2017-01-01.03:08:36.sql 测试结果:slave01 可以同步到 master01 两次的写入数据。 这篇篇幅太长了,另开一篇博文《使用 python 管理 mysql 开发工具箱 - 2》 ---------- 本文结束 ----------
CentOS 6 系统默认 Python 版本是:2.6.6 平时在使用中遇到很多的库要求是 2.7.x 版本的库,比如使用 ConfigParser 库,在 2.6 版本库就不支持没有 value 值的配置项,需要升级到 2.7 以上的库才行,这次就尝试升级一下 Python 到 2.7.x 版本,记录于此。 一、升级 Python 2.7.10 版本 1. 准备安装包,系统是最小化安装 # 下载安装依赖的相关包[root@vip ~]# yum install vim gcc make wget -y [root@vip ~]# yum install openssl-devel zlib-devel readline-devel sqlite-devel -y # 下载 [root@vip ~]# cd /usr/local/src[root@vip ~]# wget https://www.python.org/ftp/python/2.7.10/Python-2.7.10.tgz # 解压 [root@vip ~]# tar -zxvf Python-2.7.10.tgz [root@vip ~]# ls Python-2.7.10 Python-2.7.10.tgz 2. 编译配置安装 [root@vip ~]# cd Python-2.7.10 [root@vip Python-2.7.10]# ./configure --enable-shared --enable-loadable-sqlite-extensions \ --prefix=/usr/local/python27 --with-zlib --with-ssl [root@vip Python-2.7.10]# vim ./Modules/Setup # 找到下边这一行内容,去掉注释 #zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz [root@vip Python-2.7.10]# make && make install 3. 查看 python 版本信息 [root@vip Python-2.7.10]# python -V Python 2.6.6 # 版本依旧是 2.6.6 4. 用 python2.7 替换旧版本 [root@vip Python-2.7.10]# cd /usr/bin/ [root@vip bin]# ls python* -l # 旧 python 版本信息 -rwxr-xr-x. 2 root root 4864 2月 22 2013 python lrwxrwxrwx. 1 root root 6 10月 22 18:38 python2 -> python -rwxr-xr-x. 2 root root 4864 2月 22 2013 python2.6 [root@vip bin]# mv /usr/bin/python /usr/bin/python2.6.6 [root@vip bin]# ln -s /usr/local/python27/bin/python2.7 /usr/bin/python [root@vip bin]# ls python* -l lrwxrwxrwx. 1 root root 33 10月 23 00:01 python -> /usr/local/python27/bin/python2.7 lrwxrwxrwx. 1 root root 6 10月 22 18:38 python2 -> python -rwxr-xr-x. 2 root root 4864 2月 22 2013 python2.6 -rwxr-xr-x. 2 root root 4864 2月 22 2013 python2.6.6 5. 重新验证 Python 版本信息 [root@vip bin]# python -V Python 2.7.10 可以看到,系统识别的 python 版本已经是 python 2.7.10 执行 python -V 遇到的问题: python: error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory # 原因:linux系统默认没有把/usr/local/python27/lib路径加入动态库搜索路径 解决: [root@vip ~]# vim /etc/ld.so.conf # 添加如下一行内容 /usr/local/python27/lib [root@vip ~]# ldconfig # 使新添加的路径生效 二、解决 yum 兼容性问题 因为 yum 是不兼容 Python 2.7 的,所以 yum 不能正常工作,我们需要指定 yum 的 Python 为 2.6。 1. 升级 python 后 yum 出现的问题 [root@vip bin]# yum There was a problem importing one of the Python modules required to run yum. The error leading to this problem was: No module named yum ... ... ... ... 2. 编辑 yum 配置文件 [root@vip bin]# vim /usr/bin/yum #!/usr/bin/python # 第一行修改为 python2.6.6 #!/usr/bin/python2.6.6 3. 验证 yum 问题解决 [root@vip bin]# yum repolist Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile ... ... ... ... 三、升级 python 后,安装 pip 工具 1. 下载安装 [root@vip ~]# wget https://bootstrap.pypa.io/get-pip.py [root@vip ~]# python get-pip.py 2. 设置软连接 [root@vip ~]# ln -s /usr/local/python27/bin/pip2.7 /usr/bin/pip 四、安装 ipython [root@vip ~]# pip install ipython==1.2.1 [root@vip ~]# ln -s /usr/local/python27/bin/ipython /usr/bin/ipython 参考链接:http://www.jianshu.com/p/7d7c5cf267f4http://xitongjiagoushi.blog.51cto.com/9975742/1681983/ ---------- 本文结束 ----------
kickstart 能实现 linux 系统的自动化安装,需要设置 ks.cfg 文件,而这个 ks.cfg 文件的生成最好使用 system-config-kickstart 工具生成,比较准确,也很方便。下面记录一下在 CentOS 6 系统下安装 system-config-kickstart 工具。 1. Linux 执行安装包 [root@kvm ~]# yum groupinstall "X Window System" -y [root@kvm ~]# yum install -y system-config-kickstart 2. windows 宿主机下安装 xmanager 需要安装 xshell 5 和 xmanager 5 。 3. xshell 客户端连接 linux 执行 system-config-kickstart 命令 [C:\Users\Administrator]$ ssh 172.16.1.8 [root@kvm ~]# echo $DISPLAY localhost:13.0 [root@kvm ~]# LANG= [root@kvm ~]# system-config-kickstart 待更新...
整理了一些 shell 相关的练习题,记录到这里。 1. 请按照这样的日期格式 xxxx-xx-xx 每日生成一个文件,例如:今天生成的文件为 2013-09-23.log, 并且把磁盘的使用情况写到到这个文件中。 1 #!/bin/bash 2 # Date: 2016-12-22 3 # Author: liwei 4 5 today=$(date +%Y-%m-%d) 6 log_file_name=/var/log/selfdefine/${today}.log 7 /bin/df -lh > $log_file_name 然后再增加一条每日定点执行的任务计划即可 2.
任何一种编程语言中循环是比不可少的,当然 shell 脚本也少不了循环语句,包括 for 语句、 while 语句。文中主要以实际用例来说明 for while 都有哪些常见的使用方法和技巧。 一、for 循环使用 1. seq 命令方式 for i in $(seq 10);do echo "$i" done 2. C语言语法方式 for ((i=0; i<9; i++));do echo "$i" done 3. 循环遍历文件列表 for file in $(ls /root/); do echo "$file" done 4. 循环遍历数组元素 for i in ${arr[@]}; do echo "$i" done 5. 循环遍历脚本参数列表 for arg in $*; do echo "$arg" done 6. 显式指定列表 for i in 1 2 3 ;do echo $i done 7. 列表 for 循环 for i in {1..5}; do echo "$i" done 8. 步数跳跃方式进行循环 for i in {1..20..2};do echo "$i" done 二、while 循环使用 1. 普通 while 语法 while(( i<=100 ));do ... ... done while [[ "$num" != 4 ]]; do ... ... done while [ "$num" -gt 0 ]; do ...... done 待更新。。。
使用 Linux 系统这么长时间,对 shell 脚本也算是比较熟悉。其实不管是搞开发,还是搞运维,shell 脚本都是必备的基本技能。这次抽时间好好总结一下 shell 方面的知识,综合的再学习一下,记录。这篇文章总结的是 shell 脚本的 if 语句的使用。 一、if 语句的基本语法 if [ 条件判断 ]; then ... ... elif [ 条件判断 ]; then ... ... else ... ... fi 二、文件/目录的判断 关于文件或目录的相关判断方法: [ -e file ] 判断文件是否存在 [ -f file ] 判断文件是否是普通文件 [ -d file ] 判断文件是否是目录文件 [ -b file ] 判断文件是否块设备文件 [ -c file ] 判断文件是否字符设备文件 [ -s file ] 判断文件是否是非空文件 [ -r file ] 判断文件是否可读 [ -w file ] 判断文件是否可写 [ -x file ] 判断文件是否可执行 三、数字比较判断 关于针对数字的判断,首先定义a=10, b=8 [root@vip ~]# a=10 b=8 [ $a -eq $b ] 判断 a 是否等于 b [ $a -ne $b ] 判断 a 是否不等 b [ $a -gt $b ] 判断 a 是否大于 b [ $a -ge $b ] 判断 a 是否大等 b [ $a -lt $b ] 判断 a 是否小于 b [ $a -le $b ] 判断 a 是否小等 b 四、字符串的判断和比较 关于针对字符串的判断比较,首先定义 str1=abc, str2=abd [root@vip ~]# str1=abc; str2=abd [ -z str ] 判断 str 是否长度为0 [ -n str ] 判断 str 是否长度不为0 [ str1 == str2 ] 判断 str1 与 str2 是否相等 [ str1 != str2 ] 判断 str1 与 str2 是否不等 [ str1 \> str2 ] 判断 str1 是否大于 str2 [ str1 \< str2 ] 判断 str1 是否小于 str2 [[ str1 > str2 ]] 判断 str1 是否大于 str2 [[ str1 < str2 ]] 判断 str1 是否小于 str2 五、逻辑判断 [ $a -lt $b -a $str1 == $str2 ] a 大于 b, 而且 str1 与 str2 相等 (&&) [ $a -lt $b -o $str1 == $str2 ] a 大于 b, 或者 str1 与 str2 相等 (||) [[ $a -gt $b && $str1 == $str2 ]] a 大于 b, 而且 str1 与 str2 相等 (&&) [[ $a -gt $b || $str1 == $str2 ]] a 大于 b, 或者 str1 与 str2 相等 (||) [ $a -lt $b ] && [ $str1 == $str2 ] a 大于 b, 而且 str1 与 str2 相等 (&&) [ $a -lt $b ] || [ $str1 == $str2 ] a 大于 b, 或者 str1 与 str2 相等 (||) 六、常用 if 实例 待更新。。。
kvm 是虚拟化技术的一个典型实现,功能非常强大,使用很方便。kvm 本身主要实现对 CPU 的虚拟化,内存和IO的虚拟化使用了开源软件 qemu,qemu 是纯软件层面的虚拟化,其实就是个模拟器。kvm 要求 cpu 必须支持硬件虚拟化,可以通过 Linux 命令查看。 说到 kvm 必须提及 libvirt 程序集,它是用来管理 kvm 虚拟机的,当然其实也可以管理 xen 等其它虚拟化的虚拟机。libvirt 包括三部分:(1). libvirtd是后台服务程序;(2). libvirt 是管理虚拟机的 API 接口,可以通过 python c java 等语言来编写程序管理虚拟机,比较典型的 virt-manager 就是使用 python 写的可视化工具;(3). virsh 等命令行管理工具。 一、准备工作 1. 实验环境 操作系统:CentOS 6.4 x86_64 mini宿主机:vmware workstation 虚拟机 2. 检查宿主机处理器是否支持虚拟化 [root@kvm ~]# egrep -o 'vmx | svm' /proc/cpuinfo | wc -l 如果显示数值是 0,则表示该 CPU 不支持虚拟化。 3. 配置或设置宿主机 CPU:2-4 core 开启 cpu 虚拟化(bios 设置 或 vmware 设置)内存:4-8 GB硬盘:100 GB 4. 关闭 iptables 和 selinux 关闭 iptables 服务: [root@kvm ~]# service iptables stop [root@kvm ~]# chkconfig iptables off 关闭 selinux: [root@kvm ~]# setenforce 0 [root@kvm ~]# vi /etc/selinux/config SELINUX=disabled 二、安装和配置 kvm 环境 1. 安装 kvm 虚拟化相关软件包 [root@kvm ~]# yum install -y kvm virt-* libvirt bridge-utils qemu-img 2. 查看 kvm 模块是否加载到内核 [root@kvm ~]# lsmod | grep kvm_intel kvm_intel 53484 0 kvm 316506 1 kvm_intel 如果没有加载,可以尝试执行命令:modprobe kvm_intel ,不行的话,试试重启宿主机。 3. 设置相关网络 设置方式一:网桥模式。 [root@kvm ~]# cd /etc/sysconfig/network-scripts/ [root@kvm ~]# cp ifcfg-eth0 ifcfg-br0 [root@kvm ~]# vi ifcfg-eth0 DEVICE=eth0 TYPE=Ethernet ONBOOT=yes BOOTPROTO=none BRIDGE=br0 [root@kvm ~]# vi ifcfg-br0 DEVICE=br0 TYPE=Bridge ONBOOT=yes BOOTPROTO=static IPADDR=172.16.1.8 PREFIX=24 GATEWAY=172.16.1.1 DNS1=114.114.114.114 NAME=br0 # 重启网络 [root@kvm ~]# service network restart 设置方式二:NAT 模式。 暂 略。 4. 启动 libvirtd 相关服务 [root@kvm ~]# /etc/init.d/libvirtd start [root@kvm ~]# /etc/init.d/messagebus restart 遇到错误: [root@vip ~]# /etc/init.d/libvirtd start libvirtd: relocation error: libvirtd: ... version ... libdevmapper.so.1.02 [失败] 解决: [root@kvm ~]# yum upgrade device-mapper-libs 结果: [root@kvm ~]# brctl show bridge name bridge id STP enabled interfaces br0 8000.000c29181c75 no eth0 virbr0 8000.525400c207c7 yes virbr0-nic 三、安装虚拟机 1. 创建虚拟机镜像 关于虚拟机镜像,有很多种类型:raw、qcow2、vmdk等,我们推荐使用 qcow2 格式的镜像,因为 qcow2 格式的镜像支持快照,使用的比较广泛。在创建虚拟机之前需要手动去创建 qcow2 格式的镜像磁盘文件,以供安装虚拟机时使用。按照如下命令进行创建: qemu-img create -f qcow2 -o preallocation=metadata /data/kvm/liwei01.qcow2 50G 2. 执行虚拟机的安装 安装方式一:通过网络镜像安装,文本控制台,无vnc支持。 virt-install --name liwei01 --ram 1024 --vcpus 1 \ -f /data/kvm/liwei01.qcow2 --os-type linux \ --os-variant rhel6 --network bridge=br0 \ --graphics none --console pty,target_type=serial \ --location 'http://mirrors.163.com/centos/6.8/os/i386/' \ --extra-args 'console=ttyS0,115200n8 serial' 安装方式二:通过网络镜像安装,支持 vnc ,默认无文本控制台。 virt-install --name liwei01 --ram 1024 --vcpus 1 \ -f /data/kvm/liwei01.qcow2 --os-type linux \ --os-variant rhel6 --network bridge=br0 \ --graphics vnc,listen=0.0.0.0,port=5920 \ --location 'http://mirrors.163.com/centos/6.8/os/i386/' 安装方式三:通过 iso 镜像实现本地安装,支持 vnc ,无文本控制台。 virt-install --name liwei01 --ram 1024 --vcpus 1 \ -f /data/kvm/liwei01.qcow2 --os-type linux \ --os-variant rhel6 --network bridge=br0 \ --cdrom CentOS-6.8-i386-minimal.iso \ --graphics vnc,listen=0.0.0.0,port=5920 安装方式四:通过基础镜像模板快速安装(拷贝) 创建镜像文件: [root@kvm ~]# qemu-img create -f qcow2 /data/kvm/liwei.qcow2 50G# 通过 liwei.qcow2 安装虚拟机 ... 安装完毕.[root@kvm ~]# cp /data/kvm/liwei.qcow2 /data/kvm/liwei01.qcow2 安装命令: # 以拷贝的 liwei01.qcow2 为模板进行安装,安装方式是从 liwei01.qcow2 镜像启动[root@kvm ~]# virt-install --name liwei01 --ram 1024 --vcpus=1 \ --disk /data/kvm/liwei01.qcow2,format=qcow2,bus=virtio \ --network bridge=br0 --graphics vnc,listen=0.0.0.0,port=5904 \ --boot hd 说明: 本方式创建 img 镜像的时候没有指定 preallocation=metadata 选项,这样存储文件空间显示比较小,方便拷贝,不加这个选项时,在 virt-install 时候需要在 --disk 选项后边加上 bus=virtio,如果不加在安装操作系统的时候似乎是识别不出来磁盘空间,会提示磁盘空间不足。采用这种方式安装的速度非常快,其实就是从已经存在的操作系统镜像启动虚拟机并 define 一个新的虚拟机 liwei01,可以通过脚本快速创建出多个相同配置的虚拟机。当然可以在基础镜像中安装公共的软件包和设置相同的配置,这样后续基于这个 img 安装的虚拟机都有类似的配置,省去重复安装软件包的麻烦。 安装方式五:通过基础镜像模板快速安装(共享) 创建镜像: [root@kvm ~]# qemu-img create -f qcow2 -o preallocation=metadata /data/kvm/liwei.qcow2 50G# 通过 liwei.qcow2 安装虚拟机 ... 安装完毕.# 以 liwei.qcow2 镜像为模板创建 liwei01.qcow2 镜像[root@kvm ~]# qemu-img create -f qcow2 -o backing_file=liwei.qcow2 liwei01.qcow2 10G 安装命令: [root@kvm ~]# virt-install --name liwei01 --ram 1024 --vcpus=1 \ --disk /data/kvm/liwei01.qcow2,format=qcow2,bus=virtio \ --network bridge=br0 --graphics vnc,listen=0.0.0.0,port=5904 \ --boot hd 说明: 在创建镜像 liwei01.qcow2 指定了 backing_file=liwei.qcow2 选项,表示以 liwei.qcow2 为后端镜像,以后对虚机 liwei01 的所有的写操作都会记录到 liwei01 镜像,实际操作系统是在 liwei.qcow2 镜像中,liwei.qcow2 镜像是只读的。也就是说后续以 liwei.qcow2 镜像为后端的虚机都共享这个镜像,而具体某个虚机的写操作内容都要记录到对应自己的镜像文件中去。注意和方式4的区别。 3. 通过 vnc 或 文本控制台进行系统安装 方式一:通过文本控制台进行管理安装 virsh console liwei01 后续也能用此方式进行登陆管理虚拟机。方式二:通过 vnc 客户端进行连接, virsh vncdisplay liwei01 :20 客户端通过url: 172.16.1.8:20 进行连接。方式三:同方式二一样,具体安装过程与普通操作系统安装过程一样,过程略。 四、kvm 虚拟机常见的基础操作命令 开机:virsh start vm关机:virsh shutdown vm 如果不生效,需要在 vm 中执行:yum install -y acpid强关:virsh destroy vm删除:virsh undefine vm定义:virsh define vm挂起:virsh suspend vm恢复:virsh resume vm 虚拟机列表:virsh list包含关机的虚机:virsh list --all设置自动启动:virsh autostart vm关闭自动启动:virsh autostart --disable vm登陆虚机控制台:virsh console vm # 只对指定了console的虚机才管用,方式一退出虚机控制台:ctrl + ] 五、虚拟机的克隆 将虚拟机 liwei01 克隆为虚拟机 liwei02 [root@kvm ~]# virt-clone --original liwei01 --name liwei02 --file /data/kvm/liwei02.qcow2 注意:克隆前需要先关闭虚拟机;克隆完毕,一般需要设置虚拟机的网络。 六、创建虚拟机的快照 1. 创建快照的条件 虚拟机是关机状态。 虚拟机镜像格式是 qcow2。 2. 创建快照 [root@kvm ~]# virsh snapshot-create liwei 3. 查看快照列表 [root@kvm ~]# virsh snapshot-list liwei # 可以通过 qemu-img 查看镜像的快照信息 [root@kvm ~]# qemu-img info /data/kvm/liwei.img 4. 切换快照 [root@kvm ~]# virsh snapshot-revert liwei 1477285698 5. 查看当前快照 [root@kvm ~]# virsh snapshot-current liwei 6. 删除快照 [root@kvm ~]# virsh snapshot-delete liwei 1477285698 7. 快照文件存储位置 /var/lib/libvirt/qemu/snapshot 七、虚拟机磁盘扩容和添加磁盘 1. 虚拟机扩容磁盘,给现有磁盘增加容量 [root@kvm ~]# qemu-img resize /data/kvm/liwei.qcow2 +5G # 重启虚拟机 reboot虚机不生效 [root@kvm ~]# virsh destroy liwei [root@kvm ~]# virsh start liwei 在虚拟机中使用 fdisk -l 查看,通过观察block 块 id 可以发现存储空间多了,还必须将多余部分分区、格式化使用,默认使用 lvm 。 2. 给虚拟机添加磁盘 按照如下步骤: 关闭虚拟机 使用 qemu-img 创建磁盘镜像 使用 virsh edit liwei 编辑虚机配置文件,添加一条磁盘记录,适当修改信息 虚拟机开机 -> fdisk -> 格式化 -> ok. 注:可以尝试不分区直接格式化,也可以尝试使用 lvm 。 八、使用虚拟磁盘恢复虚拟机 思路:首先得有镜像文件(已有) + xml 配置文件 [root@kvm ~]# virsh dumpxml liwei > /etc/libvirt/qemu/liwei01.xml # 编辑配置文件,修改为适当的值 # 添加定义 virsh define /etc/libvirt/qemu/liwei01.xml virsh list --all #即可查到该虚拟机 九、调整CPU、内存规格 如果要调整的 cpu 核数和内存超过安装虚机时指定的最大值,则需要关闭虚机来修改最大值,动态调整的值不能超过设置最大值,擦,一般使用值和最大值都是保持一致,一起修改。所以在线动态修改没什么意义,推荐直接修改配置文件就 OK。 [root@kvm ~]# virsh edit liwei01 <memory unit='KiB'>1048576</memory> <currentMemory unit='KiB'>1048576</currentMemory> <vcpu placement='static'>1</vcpu> 重启虚机 liwei01 就ok. 十、调整虚拟机网卡 1. 添加虚拟机网卡 # 临时命令生效 [root@kvm ~]# virsh attach-interface liwei --type bridge --source br0 # 修改虚机配置文件 [root@kvm ~]# virsh dumpxml liwei > /etc/libvirt/qemu/liwei.xml [root@kvm ~]# virsh define /etc/libvirt/qemu/liwei.xml 2. 删除虚拟机网卡 [root@kvm ~]# virsh detach-interface liwei --type bridge --mac 52:54:00:14:86:cf 3. 指定网卡类型 网卡默认类型是 rtl 品牌的网卡,这里设置为 intel 网卡 e1000 系列。修改如下配置文件即可。 <interface type='bridge'> <mac address='52:54:00:b5:68:a4'/> <source bridge='br0'/> <model type='e1000'/> # 添加设置字段 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> </interface> 使用 virsh 重启虚拟机,在虚拟机中查看: [root@localhost ~]# lspci | grep "Ethernet" 00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03) 十一、虚拟机的迁移 几个步骤: 1.关闭虚拟机 2.拷贝镜像文件 3.拷贝配置文件 4.virsh define vm 待更新......
使用 shell 脚本来实现 LANMP 系统的一键安装。使用的操作系统是 CentOS 6 ,不区分 32 位和 64 位,要求机器可以连通互联网。支持 LAMP 和 LNMP ,MySQL 支持 5.1 和 5.6 两个版本, php 支持 5.3 和 5.6 两个版本, apache 2.2 ,nginx 1.8。 代码如下: #!/bin/bash echo "It will install lamp or lnmp." sleep 1 # get the archive of the system: i686 or x86_64. ar=`arch` # check last command is OK or Not. function check_ok() { if [ $? != 0 ];then echo "Error, Check the error log." exit 1 fi } # close seliux function close_selinux() { sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config selinux_s=`getenforce` if [ $selinux_s == "Enforcing" -o $selinux_s == "enforcing" ];then setenforce 0 fi } # close iptables function clear_iptables() { iptables-save > /etc/sysconfig/iptables_`date +%s` iptables -F service iptables save } # if the packge installed ,then omit. function myum() { if ! rpm -qa|grep -q "^$1" ;then yum install -y $1 check_ok else echo $1 already installed. fi } ## install some packges. function install_pre_packages { for p in gcc wget perl perl-devel libaio libaio-devel pcre-devel zlib-devel ;do myum $p done } install_pre_packages # install epel repo. function install_epel() { if rpm -qa epel-release >/dev/null ;then rpm -e epel-release fi if ls /etc/yum.repos.d/epel-6.repo* >/dev/null 2>&1 ;then /bin/rm -f /etc/yum.repos.d/epel-6.repo* fi wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-6.repo } install_epel # function of installing mysqld. function install_mysqld() { echo "Chose the version of mysql." select mysql_v in 5.1 5.6 do case $mysql_v in 5.1) cd /usr/local/src [ -f mysql-5.1.72-linux-$ar-glibc23.tar.gz ] || wget \ http://mirrors.sohu.com/mysql/MySQL-5.1/mysql-5.1.72-linux-$ar-glibc23.tar.gz tar zxf mysql-5.1.72-linux-$ar-glibc23.tar.gz check_ok [ -d /usr/local/mysql ] && /bin/mv /usr/local/mysql /usr/local/mysql_`date +%s` /bin/mv mysql-5.1.72-linux-$ar-glibc23 /usr/local/mysql check_ok if ! grep '^mysql:' /etc/passwd ;then useradd -M mysql -s /sbin/nologin check_ok fi myum compat-libstdc++-33 [ -d /data/mysql ] && /bin/mv /data/mysql /data/mysql_`date +%s` mkdir -p /data/mysql chown -R mysql:mysql /data/mysql cd /usr/local/mysql ./scripts/mysql_install_db --user=mysql --datadir=/data/mysql check_ok /bin/cp support-files/my-huge.cnf /etc/my.cnf check_ok sed -i '/^\[mysqld\]$/a\datadir = /data/mysql' /etc/my.cnf /bin/cp support-files/mysql.server /etc/init.d/mysqld sed -i 's#^datadir=#datadir=/data/mysql#' /etc/init.d/mysqld chmod 755 /etc/init.d/mysqld chkconfig --add mysqld chkconfig mysqld on service mysqld start check_ok break ;; 5.6) cd /usr/local/src [ -f mysql-5.6.35-linux-glibc2.5-$ar.tar.gz ] || wget \ http://mirrors.sohu.com/mysql/MySQL-5.6/mysql-5.6.35-linux-glibc2.5-$ar.tar.gz tar zxf mysql-5.6.35-linux-glibc2.5-$ar.tar.gz check_ok [ -d /usr/local/mysql ] && /bin/mv /usr/local/mysql /usr/local/mysql_bak mv mysql-5.6.35-linux-glibc2.5-$ar /usr/local/mysql if ! grep '^mysql:' /etc/passwd then useradd -M mysql -s /sbin/nologin fi myum compat-libstdc++-33 [ -d /data/mysql ] && /bin/mv /data/mysql /data/mysql_bak mkdir -p /data/mysql chown -R mysql:mysql /data/mysql cd /usr/local/mysql ./scripts/mysql_install_db --user=mysql --datadir=/data/mysql check_ok /bin/cp support-files/my-default.cnf /etc/my.cnf check_ok sed -i '/^\[mysqld\]$/a\datadir = /data/mysql' /etc/my.cnf /bin/cp support-files/mysql.server /etc/init.d/mysqld sed -i 's#^datadir=#datadir=/data/mysql#' /etc/init.d/mysqld chmod 755 /etc/init.d/mysqld chkconfig --add mysqld chkconfig mysqld on service mysqld start check_ok break ;; *) echo "only 1(5.1) or 2(5.6)" exit 1 ;; esac done } # function of install httpd. function install_httpd() { echo "Install apache version 2.2." cd /usr/local/src [ -f httpd-2.2.31.tar.gz ] || wget http://mirror.bit.edu.cn/apache/httpd/httpd-2.2.31.tar.gz tar zxf httpd-2.2.31.tar.gz && cd httpd-2.2.31 check_ok ./configure \ --prefix=/usr/local/apache2 \ --with-included-apr \ --enable-so \ --enable-deflate=shared \ --enable-expires=shared \ --enable-rewrite=shared \ --with-pcre check_ok make && make install check_ok } # function of install lamp's php. function install_php() { echo -e "Install php.\nPlease chose the version of php." select php_v in 5.4 5.6 do case $php_v in 5.4) cd /usr/local/src/ [ -f php-5.4.45.tar.bz2 ] || wget \ 'http://cn2.php.net/get/php-5.4.45.tar.bz2/from/this/mirror' -O php-5.4.45.tar.bz2 tar jxf php-5.4.45.tar.bz2 && cd php-5.4.45 for p in openssl-devel bzip2-devel \ libxml2-devel curl-devel libpng-devel \ libjpeg-devel freetype-devel libmcrypt-devel\ libtool-ltdl-devel perl-devel do myum $p done check_ok ./configure \ --prefix=/usr/local/php \ --with-apxs2=/usr/local/apache2/bin/apxs \ --with-config-file-path=/usr/local/php/etc \ --with-mysql=/usr/local/mysql \ --with-libxml-dir \ --with-gd \ --with-jpeg-dir \ --with-png-dir \ --with-freetype-dir \ --with-iconv-dir \ --with-zlib-dir \ --with-bz2 \ --with-openssl \ --with-mcrypt \ --enable-soap \ --enable-gd-native-ttf \ --enable-mbstring \ --enable-sockets \ --enable-exif \ --disable-ipv6 check_ok make && make install check_ok [ -f /usr/local/php/etc/php.ini ] || /bin/cp php.ini-production \ /usr/local/php/etc/php.ini break ;; 5.6) cd /usr/local/src/ [ -f php-5.6.6.tar.gz ] || wget http://mirrors.sohu.com/php/php-5.6.6.tar.gz tar zxf php-5.6.6.tar.gz && cd php-5.6.6 for p in openssl-devel bzip2-devel \ libxml2-devel curl-devel libpng-devel \ libjpeg-devel freetype-devel libmcrypt-devel\ libtool-ltdl-devel perl-devel do myum $p done ./configure \ --prefix=/usr/local/php \ --with-apxs2=/usr/local/apache2/bin/apxs \ --with-config-file-path=/usr/local/php/etc \ --with-mysql=/usr/local/mysql \ --with-libxml-dir \ --with-gd \ --with-jpeg-dir \ --with-png-dir \ --with-freetype-dir \ --with-iconv-dir \ --with-zlib-dir \ --with-bz2 \ --with-openssl \ --with-mcrypt \ --enable-soap \ --enable-gd-native-ttf \ --enable-mbstring \ --enable-sockets \ --enable-exif \ --disable-ipv6 check_ok make && make install check_ok [ -f /usr/local/php/etc/php.ini ] || /bin/cp php.ini-production \ /usr/local/php/etc/php.ini break ;; *) echo "only 1(5.4) or 2(5.6)" ;; esac done } ##function of apache and php configue. function join_apa_php() { sed -i '/AddType .*.gz .tgz$/a\AddType application\/x-httpd-php .php' \ /usr/local/apache2/conf/httpd.conf check_ok sed -i 's/DirectoryIndex index.html/DirectoryIndex index.php index.html index.htm/' \ /usr/local/apache2/conf/httpd.conf check_ok echo -e "<?php\n phpinfo();\n?>" > /usr/local/apache2/htdocs/index.php if /usr/local/php/bin/php -i |grep -iq 'date.timezone => no value' ;then sed -i '/;date.timezone =$/a\date.timezone = "Asia\/Chongqing"' /usr/local/php/etc/php.ini fi /usr/local/apache2/bin/apachectl restart check_ok } # function of check service is running or not, example nginx, httpd, php-fpm. function check_service() { if [ "$1" == "phpfpm" ];then s="php-fpm" else s=$1 fi n=`ps aux |grep "$s"|wc -l` if [ $n -gt 1 ];then echo "$1 service is already started." else if [ -f /etc/init.d/$1 ];then /etc/init.d/$1 start check_ok else install_$1 fi fi } # function of install lamp function lamp() { check_service mysqld check_service httpd install_php join_apa_php echo "LAMP done,Please use 'http://your ip/index.php' to access." } # function of install nginx function install_nginx() { cd /usr/local/src [ -f nginx-1.8.0.tar.gz ] || wget http://mirrors.sohu.com/nginx/nginx-1.8.0.tar.gz tar zxf nginx-1.8.0.tar.gz cd nginx-1.8.0 myum pcre-devel ./configure --prefix=/usr/local/nginx check_ok make && make install check_ok if [ -f /etc/init.d/nginx ];then /bin/mv /etc/init.d/nginx /etc/init.d/nginx_`date +%s` fi curl http://www.apelearn.com/study_v2/.nginx_init -o /etc/init.d/nginx check_ok chmod 755 /etc/init.d/nginx chkconfig --add nginx chkconfig nginx on curl http://www.apelearn.com/study_v2/.nginx_conf -o /usr/local/nginx/conf/nginx.conf check_ok service nginx start check_ok echo -e "<?php\n phpinfo();\n?>" > /usr/local/nginx/html/index.php check_ok } # function of install php-fpm function install_phpfpm() { echo -e "Install php.\nPlease chose the version of php." select php_v in 5.4 5.6 do case $php_v in 5.4) cd /usr/local/src/ [ -f php-5.4.45.tar.bz2 ] || wget \ 'http://cn2.php.net/get/php-5.4.45.tar.bz2/from/this/mirror' -O php-5.4.45.tar.bz2 tar jxf php-5.4.45.tar.bz2 && cd php-5.4.45 for p in openssl-devel bzip2-devel \ libxml2-devel curl-devel libpng-devel \ libjpeg-devel freetype-devel libmcrypt-devel\ libtool-ltdl-devel perl-devel do myum $p done if ! grep -q '^php-fpm:' /etc/passwd ;then useradd -M -s /sbin/nologin php-fpm check_ok fi ./configure \ --prefix=/usr/local/php-fpm \ --with-config-file-path=/usr/local/php-fpm/etc \ --enable-fpm \ --with-fpm-user=php-fpm \ --with-fpm-group=php-fpm \ --with-mysql=/usr/local/mysql \ --with-mysql-sock=/tmp/mysql.sock \ --with-libxml-dir \ --with-gd \ --with-jpeg-dir \ --with-png-dir \ --with-freetype-dir \ --with-iconv-dir \ --with-zlib-dir \ --with-mcrypt \ --enable-soap \ --enable-gd-native-ttf \ --enable-ftp \ --enable-mbstring \ --enable-exif \ --enable-zend-multibyte \ --disable-ipv6 \ --with-pear \ --with-curl \ --with-openssl check_ok make && make install check_ok [ -f /usr/local/php-fpm/etc/php.ini ] || /bin/cp php.ini-production \ /usr/local/php-fpm/etc/php.ini if /usr/local/php-fpm/bin/php -i |grep -iq 'date.timezone => no value' then sed -i '/;date.timezone =$/a\date.timezone = "Asia\/Chongqing"' \ /usr/local/php-fpm/etc/php.ini check_ok fi [ -f /usr/local/php-fpm/etc/php-fpm.conf ] || curl \ http://www.apelearn.com/study_v2/.phpfpm_conf -o /usr/local/php-fpm/etc/php-fpm.conf [ -f /etc/init.d/phpfpm ] || /bin/cp sapi/fpm/init.d.php-fpm /etc/init.d/phpfpm chmod 755 /etc/init.d/phpfpm chkconfig phpfpm on service phpfpm start check_ok break ;; 5.6) cd /usr/local/src/ [ -f php-5.6.6.tar.gz ] || wget http://mirrors.sohu.com/php/php-5.6.6.tar.gz tar zxf php-5.6.6.tar.gz && cd php-5.6.6 for p in openssl-devel bzip2-devel \ libxml2-devel curl-devel libpng-devel \ libjpeg-devel freetype-devel libmcrypt-devel\ libtool-ltdl-devel perl-devel do myum $p done if ! grep -q '^php-fpm:' /etc/passwd then useradd -M -s /sbin/nologin php-fpm fi check_ok ./configure \ --prefix=/usr/local/php-fpm \ --with-config-file-path=/usr/local/php-fpm/etc \ --enable-fpm \ --with-fpm-user=php-fpm \ --with-fpm-group=php-fpm \ --with-mysql=/usr/local/mysql \ --with-mysql-sock=/tmp/mysql.sock \ --with-libxml-dir \ --with-gd \ --with-jpeg-dir \ --with-png-dir \ --with-freetype-dir \ --with-iconv-dir \ --with-zlib-dir \ --with-mcrypt \ --enable-soap \ --enable-gd-native-ttf \ --enable-ftp \ --enable-mbstring \ --enable-exif \ --disable-ipv6 \ --with-pear \ --with-curl \ --with-openssl check_ok make && make install check_ok [ -f /usr/local/php-fpm/etc/php.ini ] || /bin/cp php.ini-production \ /usr/local/php-fpm/etc/php.ini if /usr/local/php-fpm/bin/php -i |grep -iq 'date.timezone => no value' then sed -i '/;date.timezone =$/a\date.timezone = "Asia\/Chongqing"' \ /usr/local/php-fpm/etc/php.ini check_ok fi [ -f /usr/local/php-fpm/etc/php-fpm.conf ] || curl \ http://www.apelearn.com/study_v2/.phpfpm_conf -o /usr/local/php-fpm/etc/php-fpm.conf check_ok [ -f /etc/init.d/phpfpm ] || /bin/cp sapi/fpm/init.d.php-fpm /etc/init.d/phpfpm chmod 755 /etc/init.d/phpfpm chkconfig phpfpm on service phpfpm start check_ok break ;; *) echo 'only 1(5.4) or 2(5.6)' ;; esac done } ##function of install lnmp function lnmp() { check_service mysqld check_service nginx check_service phpfpm echo "The lnmp done, Please use 'http://your ip/index.php' to access." } read -p "Please chose which type env you install, (lamp|lnmp)? " t case $t in lamp) lamp ;; lnmp) lnmp ;; *) echo "Only 'lamp' or 'lnmp' your can input." ;; esac
一、命令 except 实例详解 1. 介绍 expect 使用场景 expect可以让我们实现自动登录远程机器,并且可以实现自动远程执行命令。当然若是使用不带密码的密钥验证同样可以实现自动登录和自动远程执行命令。但当不能使用密钥验证的时候,我们就没有办法了。所以,这时候只要知道对方机器的账号和密码就可以通过expect脚本实现登录和远程命令。 使用之前先安装 expect 软件 yum install -y expect 2. 自动远程登录,登陆后不退出 #! /usr/bin/expect set user "root" set host "192.168.11.102" set passwd "123456" spawn ssh $user@$host expect { "yes/no" { send "yes\r"; exp_continue} "assword:" { send "$passwd\r" } } interact 3. 登录后执行命令,然后退出 #!/usr/bin/expect set user "root" set host "192.168.11.18" set passwd "123456" spawn ssh $user@$host expect { "yes/no" { send "yes\r"; exp_continue} "password:" { send "$passwd\r" } } expect "]*" send "touch /tmp/12.txt\r" expect "]*" send "echo 1212 > /tmp/12.txt\r" expect "]*" send "exit\r" 4. 还可以传递参数 #!/usr/bin/expect set user [lindex $argv 0] set host [lindex $argv 1] set passwd "123456" set cm [lindex $argv 2] spawn ssh $user@$host expect { "yes/no" { send "yes\r"} "password:" { send "$passwd\r" } } expect "]*" send "$cm\r" expect "]*" send "exit\r" 执行: [root@localhost ~]# ./test.expect root 192.168.11.18 2 5. 自动同步文件 #!/usr/bin/expect set passwd "123456" spawn rsync -av root@192.168.11.18:/tmp/12.txt /tmp/ expect { "yes/no" { send "yes\r"} "password:" { send "$passwd\r" } } expect eof 6. 指定 host 和要同步的文件 #!/usr/bin/expect set passwd "123456" set host [lindex $argv 0] set file [lindex $argv 1] spawn rsync -av $file root@$host:$file expect { "yes/no" { send "yes\r"} "password:" { send "$passwd\r" } } expect eof 执行: [root@localhost ~]# ./test.expect 192.168.11.18 /tmp/12.txt 二、构建文件分发系统 1. 需求背景 对于大公司而言,肯定时不时会有网站或者配置文件更新,而且使用的机器肯定也是好多台,少则几台,多则几十甚至上百台。所以,自动同步文件是至关重要的。 2. 实现思路 首先要有一台模板机器,把要分发的文件准备好,然后只要使用expect脚本批量把需要同步的文件分发到目标机器即可。 3. 核心命令 rsync -av --files-from=list.txt / root@host:/ 4. 文件分发系统的实现 rsync.expect #!/usr/bin/expect set passwd "123456" set host [lindex $argv 0] set file [lindex $argv 1] spawn rsync -av --files-from=$file / root@$host:/ expect { "yes/no" { send "yes\r"} "password:" { send "$passwd\r" } } expect eof rsync.sh #!/bin/bash for ip in `cat ip.list` do echo $ip ./rsync.expect $ip list.txt done 5. 自动批量执行脚本命令 cat exe.expect #!/usr/bin/expect set host [lindex $argv 0] set passwd "123456" set cm [lindex $argv 1] spawn ssh root@$host expect { "yes/no" { send "yes\r"} "password:" { send "$passwd\r" } } expect "]*" send "$cm\r" expect "]*" send "exit\r" cat exe.sh #!/bin/bash for ip in `cat ip.list` do echo $ip ./exe.expect $ip "w;free -m;ls /tmp" done 三、扩展 expect 语法介绍 shell脚本需要交互的地方可以使用 Here 文档是实现,但是有些命令却需要用户手动去就交互如passwd、scp。对自动部署免去用户交互很痛苦,expect能很好的解决这类问题。 1. expect的核心是spawn expect send set spawn:调用要执行的命令expect:等待命令提示信息的出现,也就是捕捉用户输入的提示:send:发送需要交互的值,替代了用户手动输入内容set:设置变量值interact:执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。expect eof:这个一定要加,与 spawn 对应表示捕获终端输出信息终止,类似于 if....endifexpect 脚本必须以 interact 或 expect eof 结束,执行自动化任务通常 expect eof 就够了。 2. 设置 timeout 设置 expect 永不超时set timeout -1 设置 expect 300 秒超时,如果超过 300 没有 expect 内容出现,则推出set timeout 300
使用 python 代码收集主机的系统信息,主要:主机名称、IP、系统版本、服务器厂商、型号、序列号、CPU信息、内存等系统信息。 #!/usr/bin/env python #encoding: utf-8 ''' 收集主机的信息: 主机名称、IP、系统版本、服务器厂商、型号、序列号、CPU信息、内存信息 ''' from subprocess import Popen, PIPE import os,sys ''' 获取 ifconfig 命令的输出 ''' def getIfconfig(): p = Popen(['ifconfig'], stdout = PIPE) data = p.stdout.read() return data ''' 获取 dmidecode 命令的输出 ''' def getDmi(): p = Popen(['dmidecode'], stdout = PIPE) data = p.stdout.read() return data ''' 根据空行分段落 返回段落列表''' def parseData(data): parsed_data = [] new_line = '' data = [i for i in data.split('\n') if i] for line in data: if line[0].strip(): parsed_data.append(new_line) new_line = line + '\n' else: new_line += line + '\n' parsed_data.append(new_line) return [i for i in parsed_data if i] ''' 根据输入的段落数据分析出ifconfig的每个网卡ip信息 ''' def parseIfconfig(parsed_data): dic = {} parsed_data = [i for i in parsed_data if not i.startswith('lo')] for lines in parsed_data: line_list = lines.split('\n') devname = line_list[0].split()[0] macaddr = line_list[0].split()[-1] ipaddr = line_list[1].split()[1].split(':')[1] break dic['ip'] = ipaddr return dic ''' 根据输入的dmi段落数据 分析出指定参数 ''' def parseDmi(parsed_data): dic = {} parsed_data = [i for i in parsed_data if i.startswith('System Information')] parsed_data = [i for i in parsed_data[0].split('\n')[1:] if i] dmi_dic = dict([i.strip().split(':') for i in parsed_data]) dic['vender'] = dmi_dic['Manufacturer'].strip() dic['product'] = dmi_dic['Product Name'].strip() dic['sn'] = dmi_dic['Serial Number'].strip() return dic ''' 获取Linux系统主机名称 ''' def getHostname(): with open('/etc/sysconfig/network') as fd: for line in fd: if line.startswith('HOSTNAME'): hostname = line.split('=')[1].strip() break return {'hostname':hostname} ''' 获取Linux系统的版本信息 ''' def getOsVersion(): with open('/etc/issue') as fd: for line in fd: osver = line.strip() break return {'osver':osver} ''' 获取CPU的型号和CPU的核心数 ''' def getCpu(): num = 0 with open('/proc/cpuinfo') as fd: for line in fd: if line.startswith('processor'): num += 1 if line.startswith('model name'): cpu_model = line.split(':')[1].strip().split() cpu_model = cpu_model[0] + ' ' + cpu_model[2] + ' ' + cpu_model[-1] return {'cpu_num':num, 'cpu_model':cpu_model} ''' 获取Linux系统的总物理内存 ''' def getMemory(): with open('/proc/meminfo') as fd: for line in fd: if line.startswith('MemTotal'): mem = int(line.split()[1].strip()) break mem = '%.f' % (mem / 1024.0) + ' MB' return {'Memory':mem} if __name__ == '__main__': dic = {} data_ip = getIfconfig() parsed_data_ip = parseData(data_ip) ip = parseIfconfig(parsed_data_ip) data_dmi = getDmi() parsed_data_dmi = parseData(data_dmi) dmi = parseDmi(parsed_data_dmi) hostname = getHostname() osver = getOsVersion() cpu = getCpu() mem = getMemory() dic.update(ip) dic.update(dmi) dic.update(hostname) dic.update(osver) dic.update(cpu) dic.update(mem) ''' 将获取到的所有数据信息并按简单格式对齐显示 ''' for k,v in dic.items(): print '%-10s:%s' % (k, v) 实验测试结果: product :VMware Virtual Platform osver :CentOS release 6.4 (Final) sn :VMware-56 4d b4 6c 05 e5 20 dc-c6 49 0c e1 e0 18 1c 75 Memory :1870 MB cpu_num :2 ip :192.168.0.8 vender :VMware, Inc. hostname :vip cpu_model :Intel(R) i7-4710MQ 2.50GHz
我们可以使用 python 代码通过调用 ifconfig 命令来获取 Linux 主机的 IP 相关信息,包括:网卡名称、MAC地址、IP地址等。 第一种实现方式: 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from subprocess import Popen, PIPE 5 6 def getIfconfig(): 7 p = Popen(['ifconfig'], stdout = PIPE) 8 data = p.stdout.read().split('\n\n') 9 return [i for i in data if i and not i.startswith('lo')] 10 11 def parseIfconfig(data): 12 dic = {} 13 for devs in data: 14 lines = devs.split('\n') 15 devname = lines[0].split()[0] 16 macaddr = lines[0].split()[-1] 17 ipaddr = lines[1].split()[1].split(':')[1] 18 dic[devname] = [ipaddr, macaddr] 19 return dic 20 21 22 if __name__ == '__main__': 23 data = getIfconfig() 24 print parseIfconfig(data) 第二种实现方式: 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from subprocess import Popen, PIPE 5 6 def getIP(): 7 p = Popen(['ifconfig'], stdout = PIPE, stderr = PIPE) 8 stdout, stderr = p.communicate() 9 data = [i for i in stdout.split('\n') if i] 10 return data 11 12 def genIP(data): 13 new_line = '' 14 lines = [] 15 for line in data: 16 if line[0].strip(): 17 lines.append(new_line) 18 new_line = line + '\n' 19 else: 20 new_line += line + '\n' 21 lines.append(new_line) 22 return [i for i in lines if i and not i.startswith('lo')] 23 24 def parseIP(data): 25 dic = {} 26 for devs in data: 27 lines = devs.split('\n') 28 devname = lines[0].split()[0] 29 macaddr = lines[0].split()[-1] 30 ipaddr = lines[1].split()[1].split(':')[1] 31 dic[devname] = [ipaddr, macaddr] 32 return dic 33 34 if __name__ == '__main__': 35 data = getIP() 36 nics = genIP(data) 37 print parseIP(nics) 第三种方式实现(正则表达式): 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from subprocess import Popen, PIPE 5 import re 6 7 def getIfconfig(): 8 p = Popen(['ifconfig'], stdout = PIPE) 9 data = p.stdout.read().split('\n\n') 10 return [i for i in data if i and not i.startswith('lo')] 11 12 def parseIfconfig(data): 13 dic = {} 14 # re.M 多行模式,改变'^'和'$'的行为 15 for line in data: 16 re_devname = re.compile(r'(\w+).*Link encap', re.M) 17 re_macaddr = re.compile(r'HWaddr\s([0-9A-F:]{17})', re.M) 18 re_ipaddr = re.compile(r'inet addr:([\d\.]{7,15})', re.M) 19 devname = re_devname.search(line) 20 mac = re_macaddr.search(line) 21 ip = re_ipaddr.search(line) 22 if devname: 23 devname = devname.group(1) 24 else: 25 devname = '' 26 27 if mac: 28 mac = mac.group(1) 29 else: 30 mac = '' 31 32 if ip: 33 ip = ip.group(1) 34 else: 35 ip = '' 36 dic[devname] = [mac, ip] 37 return dic 38 39 if __name__ == '__main__': 40 data = getIfconfig() 41 print parseIfconfig(data) 实验结果: {'eth1': ['00:0C:29:18:1C:7F', '172.16.254.8'], 'eth0': ['00:0C:29:18:1C:75', '192.168.0.8']}
通过 dmidecode 命令可以获取到 Linux 系统的包括 BIOS、 CPU、内存等系统的硬件信息,这里使用 python 代码来通过调用 dmidecode 命令来获取 Linux 必要的系统信息,更多的信息都可以通过这种方式去获取。 方式1: 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from subprocess import Popen, PIPE 5 6 p = Popen(['dmidecode'], stdout = PIPE) 7 data = p.stdout 8 lines = [] 9 dicts = {} 10 11 while True: 12 line = data.readline() 13 if line.startswith('System Information'): 14 while True: 15 line = data.readline() 16 if line == '\n': 17 break 18 else: 19 lines.append(line) 20 break 21 22 d = dict([i.strip().split(':') for i in lines]) 23 24 #for k,v in dicts.items(): 25 # dicts[k] = v.strip() 26 dicts['Manufacturer'] = d['Manufacturer'].strip() 27 dicts['Product Name'] = d['Product Name'].strip() 28 dicts['Serial Number'] = d['Serial Number'].strip() 29 print dicts 方式2: 1 #!/usr/bin/python 2 #encoding: utf-8 3 4 from subprocess import Popen, PIPE 5 6 def getDmi(): 7 p = Popen(['dmidecode'], stdout = PIPE) 8 data = p.stdout.read() 9 return data 10 11 def parseDmi(data): 12 lines = [] 13 line_in = False 14 dmi_list = [i for i in data.split('\n') if i] 15 for line in dmi_list: 16 if line.startswith('System Information'): 17 line_in = True 18 continue 19 if line_in: 20 if not line[0].strip(): 21 lines.append(line) 22 else: 23 break 24 return lines 25 26 def dimDic(): 27 dmi_dic = {} 28 data = getDmi() 29 lines = parseDmi(data) 30 dic = dict([i.strip().split(': ') for i in lines]) 31 dmi_dic['vendor'] = dic['Manufacturer'] 32 dmi_dic['product'] = dic['Product Name'] 33 dmi_dic['sn'] = dic['Serial Number'] 34 35 return dmi_dic 36 37 38 if __name__ == '__main__': 39 print dimDic()
#!/usr/bin/env python #encoding: utf-8 ''' 思路: /proc/xx_pid/status 文件中的关键字段 VmRSS 来获取某个进程占用的物理内存 步骤: 获取 httpd 进程ID列表 --> 通过每个进程id来获取该进程占用物理内存 ''' from subprocess import Popen, PIPE import os,sys # 通过程序名称获取 pid 列表 def getProgPids(prog): p = Popen(['pidof', prog], stdout=PIPE, stderr=PIPE) pids = p.stdout.read().split() return pids # 通过具体的进程 id 来获取该进程占用的物理内存 def getMemByPid(pid): fn = os.path.join('/proc', pid, 'status') with open(fn) as fd: for line in fd: if line.startswith('VmRSS'): mem = int(line.split()[1]) break return mem # 获取 httpd 服务所有进程占用的物理内存 def getHttpdMem(): httpd_mem_sum = 0 pids = getProgPids('httpd') for pid in pids: httpd_mem_sum += getMemByPid(pid) return httpd_mem_sum # 获取系统总的物理内存 def getOsTotalMemory(): with open('/proc/meminfo') as fd: for line in fd: if line.startswith('MemTotal'): total_mem = int(line.split()[1]) break return total_mem if __name__ == '__main__': http_mem = getHttpdMem() total_mem = getOsTotalMemory() scale = http_mem / float(total_mem) * 100 print 'Httpd: %d KB' % http_mem print 'Percent: %.2f%%' % scale
这里使用了 python 的基本代码实现了 Linux 系统下 wc 命令程序的基本功能。 #!/usr/bin/env python #encoding: utf-8 # Author: liwei # Function: wc program by python from optparse import OptionParser import sys,os def opt(): parser = OptionParser() parser.add_option('-c', '--char', dest='chars', action='store_true', default=False, help='only count chars') parser.add_option('-w', '--word', dest='words', action='store_true', default=False, help='only count words') parser.add_option('-l', '--line', dest='lines', action='store_true', default=False, help='only count lines') parser.add_option('-n', '--nototal', dest='nototal', action='store_true', default=False, help='don\'t print total information') options, args = parser.parse_args() return options, args #print options def get_count(data): chars = len(data) words = len(data.split()) lines = data.count('\n') return lines, words, chars #if not options.chars and not options.words and not options def print_wc(options, lines, words, chars, fn): if options.lines: print lines, if options.words: print words, if options.chars: print chars, print fn def main(): options, args = opt() if not (options.lines or options.words or options.chars): options.lines, options.words, options.chars = True, True, True if args: total_lines, total_words, total_chars = 0, 0, 0 for fn in args: if os.path.isfile(fn): with open(fn) as fd: data = fd.read() lines, words, chars = get_count(data) print_wc(options, lines, words, chars, fn) total_lines += lines total_words += words total_chars += chars elif os.path.isdir(fn): print >> sys.stderr, '%s is a directory' %fn else: sys.stderr.write('%s: No such file or directory\n' % fn) # 只有多个文件的时候会计算出total字段 if len(args) > 1 and not options.nototal: print_wc(options, total_lines, total_words, total_chars, 'total') else: fn = '' data = sys.stdin.read() lines, words, chars = get_count(data) print_wc(options, lines, words, chars, fn) if __name__ == '__main__': main()
#!/usr/bin/python #coding:utf-8 import sys import os from subprocess import Popen, PIPE class Memcached(object): ''' memcached rc script ''' args = {'USER':'memcached', 'PORT':11211, 'MAXCONN':1024, 'CACHESIZE':64, 'OPTIONS':''} def __init__(self, name, program, workdir): self.name = name self.program = program #self.args = args self.workdir = workdir def _init(self): ' /var/tmp/memcached ' if not os.path.exists(self.workdir): os.mkdir(self.workdir) os.chdir(self.workdir) def _pidFile(self): ''' /var/tmp/memcached/memcached.pid ''' return os.path.join(self.workdir, '%s.pid' % self.name) def _writePid(self): if self.pid: with open(self._pidFile(), 'w') as fd: fd.write(str(self.pid)) def _readConf(self, f): with open(f) as fd: lines = fd.readlines() return dict([i.strip().replace('"', '').split('=') for i in lines]) def _parseArgs(self): conf = self._readConf('/etc/sysconfig/memcached') if 'USER' in conf: self.args['USER'] = conf['USER'] if 'PORT' in conf: self.args['PORT'] = conf['PORT'] if 'MAXCONN' in conf: self.args['MAXCONN'] = conf['MAXCONN'] if 'CACHESIZE' in conf: self.args['CACHESIZE'] = conf['CACHESIZE'] options = ['-u', self.args['USER'], '-p', self.args['PORT'], '-m', self.args['CACHESIZE'], '-C', self.args['MAXCONN']] os.system('chown %s %s' % (self.args['USER'], self.workdir)) ''' 这个地方要修改工作目录的权限,用户为memcached,需要有写目录的权限。 ''' return options def start(self): pid = self._getPid() if pid: print '%s is running...' % self.name sys.exit() self._init() cmd = [self.program] + self._parseArgs() + ['-d', '-P', self._pidFile()] print cmd p = Popen(cmd, stdout = PIPE) #self.pid = p.pid #self._writePid() # 如果程序选项中有-P选项,那么应用程序会自动去写pid文件,不用手动去写,而且要注意pid与手动写入的pid有何区别。 print '%s start Sucessful' % self.name def _getPid(self): p = Popen(['pidof', self.name], stdout=PIPE) pid = p.stdout.read().strip() return pid def stop(self): pid = self._getPid() if pid: os.kill(int(pid), 15) if os.path.exists(self._pidFile()): os.remove(self._pidFile()) print '%s is stopped' % self.name def restart(self): self.stop() self.start() def status(self): pid = self._getPid() if pid: print '%s is already running' % self.name else: print '%s is not running' % self.name def help(self): print 'Usage: %s {start|stop|status|restart}' % __file__ def main(): name = 'memcached' prog = '/usr/bin/memcached' #args = '-u nobody -p 11211 -c 1024 -m 64' wdir = '/var/tmp/memcached' rc = Memcached(name, prog, wdir) try: cmd = sys.argv[1] except IndexError, e: print "Option error" sys.exit() if cmd == 'start': rc.start() elif cmd == 'stop': rc.stop() elif cmd == 'restart': rc.restart() elif cmd == 'status': rc.status() else: rc.help() if __name__ == '__main__': main()
CentOS7系统如果用mini镜像安装或者服务器版本安装,默认是没有安装图形界面的。如果需要额外去安装图形界面,可以手动来安装CentOS Gnome GUI包。然后会总结一下,在CentOS7系统中的系统运行级别的设置以及如何切换。 命令行下安装 Gnome 包 1 yum groupinstall "GNOME Desktop" "Graphical Administration Tools" 更新系统的默认运行级别 如果你想在系统下次启动的时候自动进入图形界面,那么我们需要更改系统的运行级别,输入下面的命令来启用图形界面。 1 ln -sf /lib/systemd/system/runlevel5.target /etc/systemd/system/default.target 修改系统的运行级别 顺便总结下CentOS7下运行级别的修改,systemd使用比sysvinit的运行级别更为自由的target概念作为替代。比如,第三运行级别multi-user.target,第五运行级别graphical.target。 两种表示方式: 1 runlevel3.target -> multi-user.target 2 runlevel5.target -> graphical.target 注:前者使用符号链接指向了后面的 target 切换运行级别: 1 systemctl isolate multi-user.target 2 systemctl isolate runlevel3.target 3 systemctl isolate graphical.target 4 systemctl isolate runlevel5.target 修改开机默认运行级别 systemd使用链接来指向默认的运行级别,由/etc/systemd/system/default.target文件决定。 设置开机运行级别3 1 rm /etc/systemd/system/default.target 2 ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target 3 ln -sf /lib/systemd/system/runlevel3.target /etc/systemd/system/default.target 4 sytemctl set-default multi-user.target 设置开机运行级别5 1 ln -sf /lib/systemd/system/graphical.target /etc/systemd/system/default.target 2 ln -sf /lib/systemd/system/runlevel5.target /etc/systemd/system/default.target 3 systemctl set-default graphical.target 查看当前运行级别 1 runlevel
sed 是一个比较古老的,功能十分强大的用于文本处理的流编辑器,加上正则表达式的支持,可以进行大量的复杂的文本编辑操作。sed 本身是一个非常复杂的工具,有专门的书籍讲解 sed 的具体用法,但是个人觉得没有必要去学习它的每个细节,那样没有特别大的实际意义。网上也有很多关于 sed 的教程,我也是抱着学习的心态来学习 sed 的常见的用法,并进行系统的总结,内容基本覆盖了 sed 的大部分的知识点。文中的内容比较简练,加以实际示例来帮助去理解 sed 的使用。 一、写在前边 1、sed介绍 sed 全名为 stream editor,流编辑器,用程序的方式来编辑文本,功能相当的强大。是贝尔实验室的 Lee E.McMahon 在 1973 年到 1974 年之间开发完成,目前可以在大多数操作系统中使用,sed 的出现作为 grep 的继任者。与vim等编辑器不同,sed 是一种非交互式编辑器(即用户不必参与编辑过程),它使用预先设定好的编辑指令对输入的文本进行编辑,完成之后再输出编辑结构。sed 基本上就是在玩正则模式匹配,所以,玩sed的人,正则表达式一般都比较强。 2、sed工作原理 sed会一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,成为"模式空间",接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。 3、正则表达式概念 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具,换句话说,正则表达式就是记录文本规则的代码。许多程序设计语言都支持利用正则表达式进行字符串操作。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。 4、正则表达式的匹配过程 简单描述一下正则表达式的匹配过程,就是拿正则表达式所表示的字符串去和原文字符串内容去匹配,直到匹配到原文内容字符串中的一个完整子串就表示匹配成功。举个例子,有一行文件内容"this is better desk",这里用"esk"去匹配,匹配过程是这样的:首先拿e去匹配文件行内容,从this开始,直到better的e,第一个字符匹配成功,接着s去匹配better字符e后边的t字符,没有匹配成功;然后重新拿esk中的e去和better的第二个t去匹配,没有成功,接着原始内容的下一个字符,直到desk中的e字符,逐个匹配s,k字符,到此为止,esk成功匹配,正则表达式匹配完毕,整个过程就是这样,即使再复杂的正则表达式的匹配过程也是按照此过程来进行的。 二、基本正则表达式 关于正则表达式的内容挺多的,掌握好下文中提及的内容就能满足正常工作中的需要,如果是专门做正则编程的,可以去买本正则表达式的书籍来看好了^_^。只有多动手多练习,才是学开发编程的最好姿势。 1. 符号"." 匹配任意一个字符,除了换行符,但是需要注意的是,在sed中不能匹配换行符,但是在awk中可以匹配换行符。类似shell通配符中的"?",匹配一个任意字符。 2. 符号"*" "*"表示前边字符有0个或多个。".*"表示任意一个字符有0个或多个,也就是能匹配任意的字符。类似shell通配符中的"*",可以匹配任意字符。 3. 符号"[]" "[ ]"中括号中可以包含表示字符集的表达式。使用方法大概有如下几种。 [a-z]:表示a-z字符中的一个,也就是小写字母。 [0-9]:表示0-9字符中的一个,也就是表示数字。 [A-Z]:表示大写字母。 [a-zA-Z]:表示字符集为小写字母或者大写字母。 [a-zA-Z0-9]:表示普通字符,包括大小写字母和数字。 [abc]:表示字符a或者字符b或者字符c。 [^0-9]:表示非数字类型的字符,^表示取反意思,只能放在中括号的开始处才有意义。 [-cz]:表示字符-或者字符c或者字符z,注意与[c-z]的区别,因为-符号没有放在e和f之间。 4. 符号"^" "^"表示行首的意思,也就是每一行的开始位置。在这里并不是上边字符范围中取反的意思,^符号只有在"[]"符号的开头处才能表示字符取反。 ^abc:表示以abc开头的字符串abc。 ^abc.*:表示以abc开头的字符串abcxxx。 5. 符号"$" "$"表示行尾的意思,也就是每一行的结尾位置,很好理解,和"^"正好相反。 world$:表示以world结尾的字符串world,如果该行中间有world字符串是不符合匹配条件的。 ^$:表示空行。行首和行尾没有内容,可不就是空行嘛。 6. 符号"\" "\"表示是转义字符,和其它语言中用到的转义字符意义基本上是一样的。其实简单理解,就是把元字符转义为普通字符,比如"\\"表示普通符号"\",把普通字符转换为特殊意义符号,比如"\n"表示把普通字符n转义为换行符。 7. 符号"{}" "{}"表示前边字符的数量范围,大概有三种用法,其实容易理解,看例子就知道了,但是必须注意要加上转义字符"\",否则不生效,表示为普通字符"{"或"}"。 \{2\}:表示前边字符的重复次数是2。 \{2,\}:表示前边字符的重复次数至少是2,也就是大于等于2。 \{2,9\}:表示前边字符的重复次数大于2但小于9。 8. 符号"\<"和"\>" "\<"表示匹配条件为词首的位置,理解上可以对比 "^" 行首。举个例子,"nihao 1hello 2hello3 hello4"有这么内容的一行内容。"\<hello"匹配结果"nihao 1hello 2hello3 hello4";"hello\>"匹配结果"nihao 1hello 2hello3 hello4",这种匹配方式用的不是太多,用到会用就OK。 三、扩展正则表达式 扩展正则表达式是在基本正则表达式中扩展出来的,内容不是很多,使用频率上可能没有基本正则表达式那么高,但是扩展正则依然很重要,很多情况下没有扩展正则是搞不定的。sed命令使用扩展正则需要加上选项-r。 1. 符号"?" "?":表示前置字符有0个或1个。 2. 符号"+" "+":表示前置字符有1个或多个。 3. 符号"|" "|":表示指明两项之间的一个选择。abc|ABC:表示可以匹配abc或者ABC。 4. 符号"()" "()"表示分组,类似算数表达式中的()。子命令表达式中可以通过\1,\2,\3等来表示分组匹配到的内容。其实"()"也可以在基本正则表达式中使用的。 (a|b)b:表示可以匹配ab或者bb字串 ([0-9])|([0][0-9])|([1][0-9]):表示匹配0-9或者00-09或者10-19范围的字符。 5. 符号"{}" 这里的"{}"和基本正则表达式中的大括号意义是一样的,只不过在使用时不用加"\"转义符号。 四、正则表达式的分类和应用 字符类[Ww]hat \.H[12345] 字符的范围[a-z] [0-9] [Cc]hapter[1-9] [-+*/] [0-1][0-9][-/][0-3][0-9][-/][0-9][0-9] 排除字符类[^0-9] 重复出现的字符[15]0* [15]00 字符的跨度*与\{n,m\} 电话号码的匹配[0-9]\{3\}-[0-9]\{7,8\} 分组操作compan(y|ies) 五、sed语法和常用选项 1、语法 sed [选项] ‘command’ 文件名称选项部分,常见选项包括-n,-e,-i,-f,-r选项。command部分包括:[地址1,地址2] [函数] [参数(标记)] 2、常用选项 选项-n sed默认会把模式空间处理完毕后的内容输出到标准输出,也就是输出到屏幕上,加上-n选项后被设定为安静模式,也就是不会输出默认打印信息,除非子命令中特别指定打印选项,则只会把匹配修改的行进行打印。 例子1: echo -e 'hello world\nnihao' | sed 's/hello/A/' 结果: A world nihao 例子2: echo -e 'hello world\nnihao' | sed -n 's/hello/A/' 结果:加-n选项后什么也没有显示。 例子3: echo -e 'hello world\nnihao' | sed -n 's/hello/A/p' 结果:A world/ 说明:-n选项后,再加p标记,只会把匹配并修改的内容打印了出来。 选项-e 如果需要用sed对文本内容进行多种操作,则需要执行多条子命令来进行操作。 例子1: echo -e 'hello world' | sed -e 's/hello/A/' -e 's/world/B/' 结果:A B 例子2: echo -e 'hello world' | sed 's/hello/A/;s/world/B/' 结果:A B 说明:例子1和例子2的写法的作用完全等同,可以根据喜好来选择,如果需要的子命令操作比较多的时候,无论是选择-e选项方式,还是选择分号的方式,都会使命令显得臃肿不堪,此时使用-f选项来指定脚本文件来执行各种操作会比较清晰明了。 选项-i sed默认会把输入行读取到模式空间,简单理解就是一个内存缓冲区,sed子命令处理的内容是模式空间中的内容,而非直接处理文件内容。因此在sed修改模式空间内容之后,并非直接写入修改输入文件,而是打印输出到标准输出。如果需要修改输入文件,那么就可以指定-i选项。 例子1: cat file.txt hello world [root@localhost]# sed 's/hello/A/' file.txt A world [root@localhost]# cat file.txt hello world 例子2: [root@localhost]# sed -i 's/hello/A/' file.txt [root@localhost]# cat file.txt A world 例子3: [root@localhost]# sed –i.bak 's/hello/A/' file.txt 说明:最后一个例子会把修改内容保存到file.txt,同时会以file.txt.bak文件备份原来未修改文件内容,以确保原始文件内容安全性,防止错误操作而无法恢复原来内容。 选项-f 还记得 -e 选项可以来执行多个子命令操作,用分号分隔多个命令操作也是可以的,如果命令操作比较多的时候就会比较麻烦,这时候把多个子命令操作写入脚本文件,然后使用 -f 选项来指定该脚本。例子1: echo "hello world" | sed -f sed.script 结果:A B sed.script脚本内容: s/hello/A/ s/world/B/ 说明:在脚本文件中的子命令串就不需要输入单引号了。 选项-r sed命令的匹配模式支持正则表达式的,默认只能支持基本正则表达式,如果需要支持扩展正则表达式,那么需要添加-r选项。例子1: echo "hello world" | sed -r 's/(hello)|(world)/A/g' A A 六、数字定址和正则定址 1、关于定址的概念 默认情况下sed会对每一行内容进行匹配、处理、输出,某些情况不需要对处理的文本全部编辑,只需要其中的一部分,比如1-10行,偶数行,或者是包含"hello"字符串的行,这种情况下就需要我们去定位特定的行来处理,而不是全部内容,这里把这个定位指定的行叫做"定址"。 2、数字定址 数字定址其实就是通过数字去指定具体要操作编辑的行,数字定址有几种方式,每种方式都有不同的应用场景,下边以举例的方式来描述每种数字定址的用法。 例子1: sed –n ‘4s/hello/A/’ message 说明:将第4行中hello字符串替换为A,其它行如果有hello也不会被替换。 例子2: sed –n ‘2,4s/hello/A/’ message 说明:将第2-4行中hello字符串替换为A,其它行如果有hello也不会被替换。 例子3: sed –n ‘2,+4s/hello/A/’ message 说明:从第2行开始,再接着往下数4行,也就是2-6行,这些行会把hello字符替换为A。 例子4: sed –n ‘4,~3s/hello/A/’ message 说明:第4行开始,到第6行。解释6的由来,"4,~3"表示从4行开始到下一个3的倍数,这里从4开始算,那就是6了,当然9就不是了,因为是要求3的第一个超过前边数字4的倍数,感觉这种适用场景不会太多。 例子5: sed –n ‘4~3s/hello/A/’ message 说明:从第4行开始,每隔3行就把hello替换为A。比如从4行开始,7行,10行等依次+3行。这个比较常用,比如3替换为2的时候,也就是每隔2行的步调,可以实现奇数和偶数行的操作。 例子6: sed –n ‘$s/hello/A/’ message 说明:$符号表示最后一行,和正则中的$符号类似,但是第1行不用^表示,直接1就行了。 例子7: sed -n ‘1!s/hello/A/’ message 说明:!符号表示取反,该命令是将除了第1行,其它行hello替换为A,上述定址方式也可以使用!符号。 3、正则定址 正则定址使用目的和数字定址完全一样,使用方式上有所不同,是通过正则表达式的匹配来确定需要处理编辑哪些行,其它行就不需要额外处理。 例子1: sed -n ‘/nihao/d’ message 说明:将匹配到nihao的行执行删除操作。 例子2: sed -n ‘/^$/d’ message 说明:删除空行 例子3: sed -n ‘/^TS/,/^TE/d’ message 说明:匹配以TS开头的行到TE开头的行之间的行,把匹配到的这些行删除。 4、数字定址和正则定址混用 其实数字定址和正则定址可以配合使用,参考下边的例子。 例子1: sed -n ‘1,/^TS/d’ message 说明:匹配从第1行到TS开头的行,把匹配的行删除。 5、关于定址的分组命令 例子1: /^TS/,/^TE/{ s/CN/China/ s/Beijing/BJ/ } 说明:该命令表示将从TS开头的行到TE开头的行之间范围的行内容中CN替换为China,并且把Beijing替换为BJ,类似于多命令之间用分号的那种方式,不过这样定址代码只写了一遍,相当于执行了一条子命令。 例子2: sed -n ‘2,3s{/cn/china/;/a/b/}’ message 说明:效果类似例子1,有点数学上的乘法分配率的意思。 6、sed定址的总结 sed 默认的命令执行范围是全局编辑的,如果不明确指定行的话,命令会在所有输入行上执行,如果想仅对其中部分行执行命令,可以使用地址限制。如果给了 2 个地址,即地址对(地址范围),则命令匹配的这个地址范围内执行,但是需要注意的是:对于像 "addr1,addr2" 这种形式的地址匹配,如果addr1 匹配,则匹配成功,"开关"打开,在该行上执行命令,此时不管 addr2 是否匹配,即使 addr2 在 addr1 这一行之前;接下来读入下一行,如果addr2 匹配,则执行命令,同样开关"关闭";如果 addr2 在 addr1 之后,则一直处理到匹配为止,换句话说,如果 addr2 一直不匹配,则开关一直不关闭,因此会持续执行命令到最后一行。 七、基本子命令 1、子命令a 子命令a表示在指定行下边插入指定行的内容。 例子1: sed ‘a A’ message 说明:将message文件中每一行下边都插入添加一行内容是A。 例子2: sed ‘1,2a A’ message 说明:将message文件中1-2行的下边插入添加一行内容是A 例子3: sed ‘1,2a A\nB\nC’ message 说明:将message文件中1-2行的下边分别添加3行,3行内容分别是A、B、C,这里使用了\n,插入多行内容都可以按照这种方式来实现。 2、子命令i 子命令i和a使用上基本上一样,只不过是在指定行上边插入指定行的内容。 例子1: sed ‘i A’ message 说明:将message文件中每一行上边都插入添加一行内容是A。 例子2: sed ‘1,2i A’ message 说明:将message文件中1-2行的上边插入添加一行内容是A 例子3: sed ‘1,2i A\nB\nC’ message 说明:将message文件中1-2行的上边分别添加3行,3行内容分别是A、B、C,这里使用了\n,插入多行内容都可以按照这种方式来实现。 3、子命令c 子命令c是表示把指定的行内容替换为自己需要的行内容。 例子1: sed ‘c A’ message 说明:将message文件中所有的行内容都分别替换为A行内容。 例子2: sed ‘1,2c A’ message 说明:将message文件中1-2行的内容替换为A,注意这里说的是将1-2行所有的内容只替换为一个A内容,也就是1-2行内容编程了一行,定址如果连续就是这种情况。如果想把1-2行分别替换为A,可以参考下个例子的方式。 例子3: sed ‘1,2c A\nA’ message 说明:将message中1-2行内容分别替换为了A,需要在替换内容上手动加换行\n,这样当然也可以将一行内容替换为多行内容。 4、子命令d 子命令d表示删除指定的行内容,比较简单,更容易理解。 例子1: sed ‘d’ message 说明:将message所有行全部删除,因为没有加定址表达式,所以平时如果需要删除指定行内容,需要在子命令前加定址表达式。 例子2: sed ‘1,3d’ message 说明:将message文件中1-3行内容删除。 5、子命令y 子命令y表示字符替换,可以替换多个字符,只能替换字符不能替换字符串,且不支持正则表达式,具体使用方法看例子。 例子1: sed ‘y/ab/AB/’ message 说明:把message中所有a字符替换为A符号,所有b字符替换为B符号。 强调一下,这里的替换源字符个数和目的字符个数必须相等;字符不支持正则表达式;源字符和目标字符每个字符需要一一对应。 6、子命令= 子命令=,可以将行号打印出来。例子: sed ‘1,2=’ message 结果: 1 nihao 2 hello world 说明:将指定行的上边显示行号。 7、子命令r 子命令r,类似于a,也是将内容追加到指定行的后边,只不过r是将指定文件内容读取并追加到指定行下边。 例子1: sed ‘2r a.txt’ message 说明:将a.txt文件内容读取并插入到message文件第2行的下边。 8、子命令s 子命令s为替换子命令,是平时sed使用的最多的子命令,没有之一。因为支持正则表达式,功能变得强大无比,下边来详细地说说子命令s的使用方法。 基本语法:[address]s/pattern/replacement/flags s字符串替换,替换的时候可以把/换成其它的符号,比如=,replacement部分用下列字符会有特殊含义: >>> &:用正则表达式匹配的内容进行替换 >>> \n:回调参数 >>> \(\):保存被匹配的字符以备反向引用\n时使用,最多9个标签,标签书序从左到右 Flags >>> n:可以是1-512,表示第n次出现的情况进行替换 >>> g:全局更改 >>> p:打印模式空间的内容 >>> w file:写入到一个文件file中 实例用法 测试文件: # cat message hello 123 world 例子1: sed ‘s/hello/HELLO/’ message 说明:将message每行包含的第一个hello的字符串替换为HELLO,这是最基本的用法。 例子2: sed -r ‘s/[a-z]+ [0-9]+ [a-z]+/A/’ message 结果:A 说明:使用了扩展正则表达式,需要加-r选项。 例子3: sed -r ‘s/([a-z]+)( [0-9]+ )([a-z]+)/\1\2\3/’ message 结果:hello 123 world 说明:再看下一个例子就明白了。 例子4: sed -r ‘s/([a-z]+)( [0-9]+ )([a-z]+)/\3\2\1/’ message 结果:world 123 hello 说明:\1表示正则第一个分组结果,\2表示正则匹配第二个分组结果,\3表示正则匹配第三个分组结果。 例子5: sed -r ‘s/([a-z]+)( [0-9]+ )([a-z]+)/&/’ message 结果:hello 123 world 说明:&表示正则表达式匹配的整个结果集。 例子6: sed -r ‘s/([a-z]+)( [0-9]+ )([a-z]+)/111&222/’ message 结果:111hello 123 world222 说明:在匹配结果前后分别加了111、222。 例子7: sed -r ‘s/.*/111&222/’ message 说明:在message文件中每行的首尾分别加上111、222。 例子8: sed ‘s/i/A/g’ message 说明:把message文件中每行的所有i字符替换为A,默认不加g标记时只替换每行的第一个字符。 例子9: sed ‘s/i/A/2’ message 说明:把message文件中每行的第2个i字符替换为A。 例子10: sed -n ‘s/i/A/p’ message 说明:加-p标记会把被替换的行打印出来,再加上-n选项会关闭模式空间打印模式,因此该命令的效果就是只显示被替换修改的行。 例子11: sed -n ‘s/i/A/w b.txt’ message 说明:把message文件中内容的每行第一个字符i替换为A,然后把修改内容另存为b.txt文件。 例子12: sed -n ‘s/i/A/i’ message 说明:把message文件中每一行的第一个i或I字符替换为A字符,也即是忽略大小写。 八、sed工作模式 1、模式空间和保持空间 模式空间初始化为空,处理完一行后会自动输出到屏幕并清除模式空间;保持空间初始化为一个空行,也就是默认带一个\n,处理完后不会自动清除。模式空间和保持空间,从程序的角度去看,其实就是sed在工作的时候占用了一些内存空间和地址,sed工作完毕就会把内存释放并归还给操作系统。 2、sed工作流程 大概简单描述一下sed的工作流程,读取文件的一行,存入模式空间,然后进行所有子命令的处理,处理完后默认会将模式空间的内容输出打印到标准输出,也就是在屏幕上显示出来,接着清空模式空间的内存,继续读取下一行的内容到模式空间,继续处理,依次循环处理。 3、模式空间和保持空间的置换 h:把模式空间内容覆盖到保持空间中 H:把模式空间内容追加到保持空间中 g:把保持空间内容覆盖到模式空间中 G:把保持空间内容追加到模式空间中 x:交换模式空间与保持空间的内容 4、实例用法 测试文件: # cat test.txt 11111 22222 33333 44444 例子1: sed ‘{1h;2,3H;4G}’ test.txt 结果: 11111 22222 33333 44444 11111 22222 33333 解释说明:略。懒得写了。 例子2: sed ‘{1h;2x;3g;$G}’ test.txt 结果: 11111 11111 22222 44444 22222 解释说明:略。 例子3: sed ‘{1!G;h;$!d}’ test.txt 结果: 44444 33333 22222 11111 九、高级子命令 高级子命令比较少,但是比较复杂,平时用的也会相对少些,却也很重要,有的内容处理不用高级子命令是完成不了的。 n:读入下一行到模式空间,例:’4{n;d}’ 删除第5行。 N:追加下一行到模式空间,再把当前行和下一行同时应用后面的命令。 P:输出多行模式空间的第一部分,直到第一个嵌入的换行符位置。在执行完脚本的最后一个命令之后,模式空间的内容自动输出。P命令经常出现在N命令之后和D命令之前。 D:删除模式空间中第一个换行符的内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用与模式空间剩余的内容。这3个命令能建立一个输入、输出循环,用来维护两行模式空间,但是一次只输出一行。 例子1: sed ‘N;$!P;D’ a.txt #说明:删除文件倒数第二行 例子2: sed ‘N;$!P;$!D;$d’ a.txt # 说明:删除文件最后两行 十、分支和测试 分支命令用于无条件转移,测试命令用于有条件转移。 1、分支branch跳转的位置与标签相关联。如果有标签则跳转到标签所在的后面行继续执行。如果没有标签则跳转到脚本的结尾处。标签:以冒号开始后接标签名,不要在标签名前后使用空格。 2、跳转到标签指定位置测试文件: grep seker /etc/passwd seker:x:500:500::/home/seker:/bin/bash 例子1: grep seker /etc/passwd | sed ‘:top;s/seker/blues/;/seker/b top;s/5/555/’ 结果:blues:x:55500:500::/home/blues:/bin/bash 选择执行例子2: grep ‘seker’ /etc/passwd | sed ‘s/seker/blues/;/seker/b end;s/5/555/;:end;s/5/666/’ 结果:blues:x:66600:500::/home/seker:/bin/bash 测试命令,如果前一个替换命令执行成功则跳转到脚本末尾(case结构)例子3: grep ‘seker’ /etc/passwd | sed ‘s/seker/ABC;t;s/home/DEF/;t;s/bash/XYZ/’ 结果:ABC:x:500:500::/home/seker:/bin/bash 例子4: grep ‘zorro’ /etc/passwd | sed ‘s/seker/ABC/;t;s/home/DEF/;t;s/bash/XYZ’ 结果:zorro:x:500:500::/DEF/zorro:/bin/bash 与标签关联,跳转到标签位置。例子5: grep ‘seker’ /etc/passwd | sed ‘s/seker/ABC/;t end;s/home/DEF/;t;end;s/bash/XYZ’ 结果:ABC:x:500:500::/home/seker:/bin/XYZ 十一、sed实战练习 实例1:删除文件每行的第二个字符。 sed -r 's/(.*)(.)$/\1/' 实例2:删除文件每行的最后一个字符。 sed -r 's/(.*)(.)$/\1/' 实例3:删除文件每行的倒数第2个单词。 sed -r ‘s/(.*)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]*$)/\1\2\4\5/’ /etc/passwd 实例4:交换每行的第一个字符和第二个字符。 sed -r ‘s/(.)(.)(.*)/\2\1\3/’ /etc/passwd 实例5:交换每行的第一个单词和最后一个单词。 sed -r ‘s/([a-Z]+)([^a-Z]+)(.*)([^a-Z]+)([a-Z]+)([^a-Z]*$)/\5\2\3\4\1\6/’ /etc/passwd 实例6:删除一个文件中所有的数字。 sed ‘s/[0-9]//g’ /etc/passwd 实例7:用制表符替换文件中出现的所有空格。 sed -r ‘s/ +/\t/g’ /etc/passwd 实例8:把所有大写字母用括号()括起来。 sed -r ‘s/([A-Z])/(\1)/g’ /etc/passwd 实例9:打印每行3次。 sed ‘p;p’ /etc/passwd 实例10:隔行删除 sed ‘0~2{=;d}’ /etc/passwd 实例11:把文件从第22行到第33行复制到56行后面。 sed ‘22h;23,33H;56G’ /etc/passwd 实例12:把文件从第22行到第33行移动到第56行后面。 sed ‘22{h;d};23,33{H;d};56g’ /etc/passwd 实例13:只显示每行的第一个单词。 sed -r ‘s/([a-Z]+)([^a-Z]+)(.*)/\1/’ /etc/passwd 实例14:打印每行的第一个单词和第三个单词。 sed -r ‘s/([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)(.*)/\1\t\5/’ /etc/passwd 实例15:将格式为mm/yy/dd的日期格式换成 mm;yy;dd date '+%m/%y/%d' | sed 's/\//;/g'
1、iotop命令 iotop命令是一个用来监视磁盘I/O使用状况的top类工具。iotop具有与top相似的UI,其中包括PID、用户、I/O、进程等相关信息。Linux下的IO统计工具如iostat,nmon等大多数是只能统计到per设备的读写情况,如果你想知道每个进程是如何使用IO的就比较麻烦,使用iotop命令可以很方便的查看。 iotop使用Python语言编写而成,要求Python2.5(及以上版本)和Linux kernel2.6.20(及以上版本)。iotop提供有源代码及rpm包,可从其官方主页下载。 2、安装 # Ubuntu系统 apt-get install iotop 或 # CentOS系统 yum install iotop 编译安装 1 wget http://guichaz.free.fr/iotop/files/iotop-0.4.4.tar.gz 2 tar zxf iotop-0.4.4.tar.gz 3 python setup.py build 4 python setup.py install 3、语法 iotop (选项) 4、选项 -o:只显示有io操作的进程 -b:批量显示,无交互,主要用作记录到文件。 -n NUM:显示NUM次,主要用于非交互式模式。 -d SEC:间隔SEC秒显示一次。 -p PID:监控的进程pid。 -u USER:监控的进程用户。 5、iotop常用快捷键 左右箭头:改变排序方式,默认是按IO排序。 r:改变排序顺序。 o:只显示有IO输出的进程。 p:进程/线程的显示方式的切换。 a:显示累积使用量。 q:退出。
python正则表达式基础 简单介绍 正则表达式并不是python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。在提供正则表达式的语言里,正则表达式的语法是一样的,区别只在于不同的贬称语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的的部分。 正则表达式进行匹配的流程 正则表达式引擎“编译”<正则表达式>得到正则表达式对象(正则表达式引擎编译表达式字符串得到的对象,包含应如何进行匹配的信息),正则表达式对象需要匹配的文本进行"匹配",生成匹配结果(正则表达式对象对文本进行匹配后得到的结果,包含了这次成功匹配的信息,如匹配到的字符串、分组以及在文本中的索引)。 匹配大概过程 正则表达式的大致匹配过程是:一次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有些不同,但也是很好理解的,多看实例多使用几次就能明白。 python支持的正则表达式元字符和语法 语法 说明 表达实例 完整匹配的字符串 一般字符 匹配自身 abc abc . 匹配任意一个字符(除"\n"外) a.c abc \ 转义字符, 使后一个字符改变原来的意思 a\.c a.c [...] 字符集(字符类)对应位置可以是字符集中任意一个字符 a[bcd]e abe ace ade a|b 字符a或字符b <-等价于-> [ab] [0-9] 数字0-9其中一个字符 [a-z] 小写字母a-z其中一个字符 [A-Z] 大写字母A-Z其中一个字符 [^m] 不是字符m --------------------------------------------------------------------------------------------------- \d 数字:[0-9] a\dc a1c \D 非数字:[^\d] a\Dc abc \s 空白字符:[<空格>\t\r\n\f\v] a\sc a c \S 非空白字符:[^\s] a\Sc abc \w 单词字符:[A-Za-z0-9] a\wc abc \W 非单词字符:[^\w] a\Wc a c -------------------------数量词(重复)------------------------------------------------------ * 匹配前一个字符0次或无限次(>=0) abc* ab abc abcc abccc ... + 匹配前一个字符1次或无限次(>=1) abc+ abc abcc abccc ... ? 匹配前一个字符0次或1次 abc? ab abc {m} 匹配前一个字符m次 ab{2}c abbc {m,n} 匹配前一个字符m至n次(m<= >=n); m和n可以省略 ab{1,2}c abc abbc *? +? ?? {m,n}? 使* + ? {m,n}变成非贪婪模式 ------------------------------边界匹配------------------------------------------------------ ^ 匹配字符串开头(在多行模式中匹配每一行的开头) $ 匹配字符串末尾(在多行模式中匹配每一行的末尾) \A 仅匹配字符串开头 这几种匹配模式有点疑惑?????? \Z 仅匹配字符串末尾 \b 匹配\w和\W之间 \B [^\b] ------------------------------逻辑、分组------------------------------------------------------ | |代表左右表达式任意匹配一个(类似短路或,先匹配左边) abc|def abc def (...) 被括起来的表达式将作为分组, 从表达式左边开始每遇到 (abc){2} abcabc 一个分组的左括号'(',编号+1;分组表达式作为一个整体, a(123|456)c a123c a456c 可以后接数量词.表达式中的|仅在该组中有效 (?P<name>...)分组, 除了原有的编号外再指定一个额外的别名 (?P<id>abc){2} abcabc \<number> 引用编号为<number>的分组匹配到的字符串 (\d)abc\1 1abc1 5abc5 (?P=name) 引用别名为<name>的分组匹配到的字符串 (?P<id>\d)abc(?P=id) 1abc1 5abc5 ------------------------------特殊构造(不作为分组)--------------------------------------------------- (?:...) (...)的不分组版本, 用于使用'|'或后接数量词 (?:abc){2} abcabc (?iLmsux) iLmsux的每个字符代表一个匹配模式, 只能用在正则的开头(?i)abc Abc (?#...) #后的内容作为注释被忽略 abc(?#comment)123 abc123 (?=...) 之后的字符串内容需要匹配表达式才能成功匹配,不消耗字符串内容 a(?=\d) 后面是数字的a (?!...) 之后的字符串内容需要不匹配表达式才能成功匹配,不消耗字符串内容a(?!\d) 后面不是数字的a (?<=...) 之前的字符串内容需要匹配表达式才能成功匹配,不消耗字符串内容(?<=\d)a 前面不是数字的a (?<!...) 之前的字符串内容需要不匹配表达式才能成功匹配,不消耗字符串内容(?<!\d)a 前面不是数字的a 参考: http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html http://www.runoob.com/python/python-reg-expressions.html http://www.cnblogs.com/PythonHome/archive/2011/11/19/2255459.html http://www.cnblogs.com/kaituorensheng/p/3489492.html
目录 1. 所用系统与软件版本 2. 虚拟机配置 3. Ubuntu 12.04上的配置 3.1 准备 3.2 通过setup脚本进行配置 3.3 通过命令配置 4. CentOS 7.0上的配置 4.1 准备 正文 DPDK介绍见:www.dpdk.org 本文介绍的步骤基本适用于dpdk 1.7.0 - dpdk 2.0.0 各版本。只是setup.sh显示的菜单有一些小的不同; 同样的,也适用于ubuntu更高版本(已在ubuntu 12.04+及14.04上验证过) 回到顶部 1. 所用系统与软件版本 系统:Ubuntu 12.04.3 LTS 64位, CentOS Linux release 7.0.1406 64位 dpdk: 1.7.0 (下载页) dpdk 1.7.1 经过试验,发现在这两个系统上都有问题, 运行各示例程序都有以下错误 EAL: Error reading from file descriptor 这个bug已经由dpdk的开发人员修复,patch内容如下: diff --git a/lib/librte_eal/linuxapp/igb_uio/igb_uio.c b/lib/librte_eal/linuxapp/igb_uio/igb_uio.c index d1ca26e..c46a00f 100644 --- a/lib/librte_eal/linuxapp/igb_uio/igb_uio.c +++ b/lib/librte_eal/linuxapp/igb_uio/igb_uio.c @@ -505,14 +505,11 @@ igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) } /* fall back to INTX */ case RTE_INTR_MODE_LEGACY: - if (pci_intx_mask_supported(dev)) { - dev_dbg(&dev->dev, "using INTX"); - udev->info.irq_flags = IRQF_SHARED; - udev->info.irq = dev->irq; - udev->mode = RTE_INTR_MODE_LEGACY; - break; - } - dev_notice(&dev->dev, "PCI INTX mask not supported\n"); + dev_dbg(&dev->dev, "using INTX"); + udev->info.irq_flags = IRQF_SHARED; + udev->info.irq = dev->irq; + udev->mode = RTE_INTR_MODE_LEGACY; + break; /* fall back to no IRQ */ case RTE_INTR_MODE_NONE: udev->mode = RTE_INTR_MODE_NONE; 在虚拟机里使用时,打上以上的补丁,或手工修改文件后重新编译即可。 回到顶部 2. 虚拟机配置 虚拟机软件:VMWare WorkStation 10.0.1 build-1379776 CPU: 2个CPU, 每个CPU2个核心 内存: 1GB+ 网卡:intel网卡*2, 用于dpdk试验;另一块网卡用于和宿主系统进行通信 回到顶部 3. Ubuntu 12.04上的配置 3.1 准备 需要安装gcc及其他一些小工具等,默认都有了,没有的话运行sudo apt-get install装一下。dkdk的一些脚本用到了python,也装一下。 3.2 通过setup脚本进行配置 首先运行su切换到root权限,root没有开的话使用 sudo passwd root 来开一下。 dpdk提供了一个方便的配置脚本: <dpdk>/tools/setup.sh,通过它可以方便地配置环境。 1) 设置环境变量,这里是linux 64位的配置 export RTE_SDK=<dpdk主目录> export RTE_TARGET=x86_64-native-linuxapp-gcc 2)运行setup.sh,显示如下 ------------------------------------------------------------------------------ RTE_SDK exported as /home/hack/dpdk-1.7.0 ------------------------------------------------------------------------------ ---------------------------------------------------------- Step 1: Select the DPDK environment to build ---------------------------------------------------------- [1] i686-native-linuxapp-gcc [2] i686-native-linuxapp-icc [3] x86_64-ivshmem-linuxapp-gcc [4] x86_64-ivshmem-linuxapp-icc [5] x86_64-native-bsdapp-gcc [6] x86_64-native-linuxapp-gcc [7] x86_64-native-linuxapp-icc ---------------------------------------------------------- Step 2: Setup linuxapp environment ---------------------------------------------------------- [8] Insert IGB UIO module [9] Insert VFIO module [10] Insert KNI module [11] Setup hugepage mappings for non-NUMA systems [12] Setup hugepage mappings for NUMA systems [13] Display current Ethernet device settings [14] Bind Ethernet device to IGB UIO module [15] Bind Ethernet device to VFIO module [16] Setup VFIO permissions ---------------------------------------------------------- Step 3: Run test application for linuxapp environment ---------------------------------------------------------- [17] Run test application ($RTE_TARGET/app/test) [18] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd) ---------------------------------------------------------- Step 4: Other tools ---------------------------------------------------------- [19] List hugepage info from /proc/meminfo ---------------------------------------------------------- Step 5: Uninstall and system cleanup ---------------------------------------------------------- [20] Uninstall all targets [21] Unbind NICs from IGB UIO driver [22] Remove IGB UIO module [23] Remove VFIO module [24] Remove KNI module [25] Remove hugepage mappings [26] Exit Script 选择6, 进行编译 3)选择8, 插入igb_uio模块 4)选择11,配置大页内存(非NUMA),选择后会提示你选择页数,输入64,128什么的即可 Removing currently reserved hugepages Unmounting /mnt/huge and removing directory Input the number of 2MB pages Example: to have 128MB of hugepages available, enter '64' to reserve 64 * 2MB pages Number of pages: 128 选择19,可以确认一下大页内存的配置: AnonHugePages: 0 kB HugePages_Total: 128 HugePages_Free: 128 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB 5)选择14, 绑定dpdk要使用的网卡 Network devices using DPDK-compatible driver ============================================ <none> Network devices using kernel driver =================================== 0000:02:01.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth0 drv=e1000 unused=igb_uio *Active* 0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth1 drv=e1000 unused=igb_uio 0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth2 drv=e1000 unused=igb_uio Other network devices ===================== <none> Enter PCI address of device to bind to IGB UIO driver: 0000:02:06.0 绑定好后,选择13,可以查看当前的网卡配置: Network devices using DPDK-compatible driver ============================================ 0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000 0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000 Network devices using kernel driver =================================== 0000:02:01.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth0 drv=e1000 unused=igb_uio *Active* Other network devices ===================== <none> 6)选择18, 运行testpmd测试程序 注意,运行这个测试程序,虚拟机最好提供2个网卡用于dpdk。 Enter hex bitmask of cores to execute testpmd app on Example: to execute app on cores 0 to 7, enter 0xff bitmask: f 如果没问题,按回车后会出现以下输出: Launching app EAL: Detected lcore 0 as core 0 on socket 0 EAL: Detected lcore 1 as core 1 on socket 0 EAL: Detected lcore 2 as core 0 on socket 0 EAL: Detected lcore 3 as core 1 on socket 0 EAL: Support maximum 64 logical core(s) by configuration. EAL: Detected 4 lcore(s) EAL: Setting up memory... EAL: Ask a virtual area of 0xf000000 bytes EAL: Virtual area found at 0x7fe828000000 (size = 0xf000000) EAL: Ask a virtual area of 0x200000 bytes EAL: Virtual area found at 0x7fe827c00000 (size = 0x200000) EAL: Ask a virtual area of 0x200000 bytes EAL: Virtual area found at 0x7fe827800000 (size = 0x200000) EAL: Ask a virtual area of 0x800000 bytes EAL: Virtual area found at 0x7fe826e00000 (size = 0x800000) EAL: Ask a virtual area of 0x400000 bytes EAL: Virtual area found at 0x7fe826800000 (size = 0x400000) EAL: Requesting 128 pages of size 2MB from socket 0 EAL: TSC frequency is ~3292453 KHz EAL: Master core 0 is ready (tid=37c79800) EAL: Core 3 is ready (tid=24ffc700) EAL: Core 2 is ready (tid=257fd700) EAL: Core 1 is ready (tid=25ffe700) EAL: PCI device 0000:02:01.0 on NUMA socket -1 EAL: probe driver: 8086:100f rte_em_pmd EAL: 0000:02:01.0 not managed by UIO driver, skipping EAL: PCI device 0000:02:06.0 on NUMA socket -1 EAL: probe driver: 8086:100f rte_em_pmd EAL: PCI memory mapped at 0x7fe837c23000 EAL: PCI memory mapped at 0x7fe837c13000 EAL: PCI device 0000:02:07.0 on NUMA socket -1 EAL: probe driver: 8086:100f rte_em_pmd EAL: PCI memory mapped at 0x7fe837bf3000 EAL: PCI memory mapped at 0x7fe837be3000 Interactive-mode selected Configuring Port 0 (socket 0) Port 0: 00:0C:29:14:50:CE Configuring Port 1 (socket 0) Port 1: 00:0C:29:14:50:D8 Checking link statuses... Port 0 Link Up - speed 1000 Mbps - full-duplex Port 1 Link Up - speed 1000 Mbps - full-duplex Done testpmd> 输入start, 开始包转发 testpmd> start io packet forwarding - CRC stripping disabled - packets/burst=32 nb forwarding cores=1 - nb forwarding ports=2 RX queues=1 - RX desc=128 - RX free threshold=0 RX threshold registers: pthresh=8 hthresh=8 wthresh=0 TX queues=1 - TX desc=512 - TX free threshold=0 TX threshold registers: pthresh=32 hthresh=0 wthresh=0 TX RS bit threshold=0 - TXQ flags=0x0 输入stop,停止包转发,这时会显示统计信息 testpmd> stop Telling cores to stop... Waiting for lcores to finish... ---------------------- Forward statistics for port 0 ---------------------- RX-packets: 5544832 RX-dropped: 0 RX-total: 5544832 TX-packets: 5544832 TX-dropped: 0 TX-total: 5544832 ---------------------------------------------------------------------------- ---------------------- Forward statistics for port 1 ---------------------- RX-packets: 5544832 RX-dropped: 0 RX-total: 5544832 TX-packets: 5544832 TX-dropped: 0 TX-total: 5544832 ---------------------------------------------------------------------------- +++++++++++++++ Accumulated forward statistics for all ports+++++++++++++++ RX-packets: 11089664 RX-dropped: 0 RX-total: 11089664 TX-packets: 11089664 TX-dropped: 0 TX-total: 11089664 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Done. 3.3 通过命令配置 最好切换到root权限。 1)编译dpdk 进入dpdk主目录<dpdk>,输入 make install T=x86_64-native-linuxapp-gcc 进行编译 2)配置大页内存(非NUMA) echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages mkdir /mnt/huge mount -t hugetlbfs nodev /mnt/huge 可以用以下命令查看大页内存状态: cat /proc/meminfo | grep Huge 3)安装igb_uio驱动 modprobe uio insmod x86_64-native-linuxapp-gcc/kmod/igb_uio.ko 4)绑定网卡 先看一下当前网卡的状态 ./tools/dpdk_nic_bind.py --status Network devices using DPDK-compatible driver ============================================ <none> Network devices using kernel driver =================================== 0000:02:01.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth0 drv=e1000 unused=igb_uio *Active* Other network devices ===================== 0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' unused=e1000,igb_uio 0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' unused=e1000,igb_uio 进行绑定: ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:06.0 ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:07.0 如果网卡有接口名,如eth1, eth2, 也可以在-b igb_uio后面使用接口名, 而不使用pci地址。 5) 运行testpmd测试程序 ./x86_64-native-linuxapp-gcc/app/testpmd -c 0x3 -n 2 -- -i 6)编译运行其他示例程序 <dpdk>/examples下面有很多示例程序,这些程序在dpdk编译时,没有被编译。这里以编译helloworld为例,首先要设置环境变量: export RTE_SDK=<dpdk主目录> export RTE_TARGET=x86_64-native-linuxapp-gcc 之后进入<dpdk>/examples/helloworld,运行make,成功会生成build目录,其中有编译好的helloworld程序。 回到顶部 4. CentOS 7.0上的配置 4.1 准备 安装CentOS虚拟机时,如果选择minimal安装,还需要安装其下的基本开发工具集(含gcc,python等) 另外,dpdk提供的dpdk_nic_bind.py脚本中会调用到lspci命令,这个默认没有安装,运行以下命令安装(不安装此工具则无法绑定网卡): yum install pciutils ifconfig默认也没有安装,如果想用它,应运行: yum install net-tools 在CentOS上,要绑定给dpdk使用的网卡在绑定前,可能是活动的(active),应将其禁用,否则无法绑定。禁用的一种方式是运行: ifconfig eno33554984 down eno33554984是接口名,如同eth0一样。 在CentOS上使用setup.sh和通过命令编译和配置dpdk的过程与Ubuntu一样,这里就从略了。
Linux上主要操作是命令,懂一点linux知识的都知道,其实windows下边很多工具也是可以用命令来操作打开的,这样会提高效率,节省很多的时间。下边就记录一下常用的命令。 一、常用命令 1、远程桌面连接 mstsc 2、计算器 calc 3、画图软件 mspaint 4、命令提示符 cmd
最近2周在做 ineedle 的国舜项目扩展,需要使用 socket 的 tcp 连接向对方发送消息,当然需求很简单,只是按照对方要求发送指定格式的消息,程序结构也非常的简单,一对多的 client/server 模型,ineedle 发送给多个服务器消息。我们这边在分析出结果,封装为相应格式消息后发送给对方,只需要在线程循环发送消息即可,便在测试环境中编写简单的socket进行模拟消息发送,一对一发送,能够正常发送消息。可以遇到了一个棘手的问题:在服务器端用ctrl+c 来结束服务器接收进程来模拟服务器宕机的情况,结束服务 socket 进程之后,服务端自然关闭进程,可是 client 端也竟然出乎意料的关闭掉。 这就奇怪了,我在服务端关闭进程,服务器会关闭 tcp 连接,向 client 发送FIN包,client 响应 ack 后,等双向 tcp 连接关闭后,tcp 连接彻底关闭,在 client 端发送消息会失败,返回错误信息,错误消息会被正常的打印出来,可是很多时候错误信息都没有打印( write 函数没有返回),有时候返回了错误信息,但是 client 端进程仍然无辜死掉,我的 client 端可是用的 while(1) 死循环啊,怎么可能。于是乎百度了一番,说法千奇百怪,有说防火墙的问题,关闭防火墙仍然如此。郁闷 ing,测试了其它方法都不行,打印出错误错误码,也没有查询到结果。 最后问了下我们的张总,问题刚给他说完,他便说他以前也遇到这个问题,给我找到了他以前的解决方案。解决方法是使用 send 函数时候在最后一个参数上加 MSG_NOSIGNAL 标记即可。于是自己更改发送函数 write 为 send 并添加 MSG_NOSIGNAL 标志,重新编译,运行,中断 server,果然这个问题被很潇洒的解决了,感谢张总的英明神武。 参考一个博文的介绍 Linux 下当连接断开,还发送数据的时候,不仅 send() 的返回值会有反映,而且还会向系统发送一个异常消息,如果不作处理,系统会出 BrokePipe,程序会退出,这对于服务器提供稳定的服务将造成巨大的灾难。为此,send() 函数的最后一个参数可以设置为 MSG_NOSIGNAL,禁止 send() 函数向系统发送常消息。
一、概述 用过虚拟机的都知道,如果在系统运行的时候去给虚拟机添加一块新设备,比如说硬盘,系统是读取不到这个新硬盘的,因为系统在启动的时候会去检测硬件设备。但是我们也可能会遇到这样的情况,比如正在运行比较重要的程序,这时候不想重启linux系统,又需要添加一块新硬盘,该怎么办呢?今儿个就遇到这个情况,vmware上添加新硬盘,系统不识别,于是百度了一番,有很多文章,不过大多都是要设置lvm的,鄙人比较懒,不想做那么复杂,终于找到了一篇文件讲述如何不重启的情况下添加新硬盘并识别出来,于是乎操作了一般,并记录总结下来。 二、测试环境: 1.Linux系统:CentOS6.5 32bit 2.vmware:8.04 3.硬盘类型:SCSI -->系统上电运行过程只能添加SCSI硬盘 三、虚拟机添加硬盘 虚拟机-->设置-->添加-->硬盘-->下一步-->创建一个新的虚拟硬盘-->下一步—>SCSI-->下一步-->最大磁盘空间[key]-->单个文件存储虚拟磁盘-->下一步-->磁盘文件名称填写-->完成-->确定 四、linux系统上操作 添加一块新的虚拟硬盘之后在linux下查看不到硬盘的设备信息: fdisk –l -->结果并没有显示新硬盘的信息 按照下边操作步骤即可使linux系统重新读取并识别到新硬盘: 1.确定主机总线号: [root@iNeedle~]# ls /sys/class/scsi_host/ host0host1host2 2.重新扫描SCSI总线来添加设备: [root@iNeedle~]# echo "- - -" > /sys/class/scsi_host/host0/scan [root@iNeedle~]# echo "- - -" > /sys/class/scsi_host/host1/scan [root@iNeedle~]# echo "- - -" > /sys/class/scsi_host/host2/scan 3.验证硬盘添加结果: 1: Disk/dev/sdc:21.5GB, 21474836480bytes 2: 255heads, 63sectors/track, 2610cylinders 3: Units=cylindersof16065*512=8225280bytes 4: Sectorsize(logical/physical):512bytes/512bytes 5: I/Osize(minimum/optimal):512bytes/512bytes 6: Diskidentifier:0x00000000 // 可以看到我们新添加的硬盘已经被系统正确的读取。
一、磁盘基础知识 磁盘安装在计算机上后,在系统读取到硬盘后并不能直接使用,必须经过分区、格式化才能够正确使用。这一次主要是针对磁盘分区进行简单总结,存储设备类型:U盘、光盘、软盘、硬盘、磁带。 硬盘接口: 硬盘接口 硬盘标示 设备文件标示 分区表示 IDE ATA hd hda hdb hdc hda1 hda2 SATA sd sda sdb sdc sda1 sda2 SCSI sd sda sdb sdc sda1 sda2 USB sd sda sdb sdc sda1 sda2 查看硬盘分区的内核信息: [root@localhost ~]cat /proc/partitions 二、磁盘分区 1、查看磁盘设备文件 1: [root@iNeedle~]# ls /dev/sd* 2: /dev/sda/dev/sda1/dev/sda2/dev/sdb/dev/sdb1/dev/sdb2/dev/sdb3/dev/sdb5/dev/sdc // 可以看出一共有3块磁盘,第三块磁盘sdc还没有进行分区过,下边我们就以sdc为例来做实验。 2、磁盘分区命令 磁盘分区命令使用fdisk 使用方式如: [root@iNeedle~]# fdisk /dev/sdc 弹出二级命令提示符: Command (m for help): -->提示我们输入m来查看帮助信息 3、查看帮助信息 Command (m for help):m -->输入m命令来查看帮助信息 弹出如下帮助信息: -->这个信息非常有用 1: atoggleabootableflag #设置引导扇区 2: beditbsddisklabel #编辑BSD磁盘标签 3: ctogglethedoscompatibilityflag #切换DOS兼容性标志 4: ddeleteapartition #删除一个分区 5: llistknownpartitiontypes #列出已知分区类型 6: mprintthismenu #打印出菜单(帮助信息) 7: naddanewpartition #新建一个分区 8: ocreateanewemptyDOSpartitiontable #创建一个空的DOS分区表 9: pprintthepartitiontable #打印分区表 10: qquitwithoutsavingchanges #不保存退出 11: screateanewemptySundisklabel #创建一个空的SUN磁盘标签 12: tchangeapartitionsystemid #改变一个分区的系统ID 13: uchangedisplay/entryunits # 14: vverifythepartition #验证一个分区 15: wwritetabletodiskandexit #保存分区表到磁盘并且退出 16: xextrafunctionality(expertsonly) #额外功能->专家选项,不要轻动 最主要的当然是新建分区命令n(new),新建分区分类有:主分区和扩展分区。 4、创建一般主分区 1: [root@iNeedle~]# fdisk/dev/sdc 2: ............ 3: Command(mforhelp):n<----输入n新建分区命令 4: eextended 5: pprimarypartition(1-4) 6: p<----输入p代表主分区 7: Partitionnumber(1-4):1<----输入分区编号1(主分区1-4,逻辑分区从5开始编号) 8: Firstcylinder(1-2610,default1)<----回车选择默认第1个柱面开始即可 9: Lastcylinder,+cylindersor+size(K,M,G)(1-2610,default2610):+5G<----第一个主分区为5G空间 10: Command(mforhelp):w<----又返回上级命令,输入w表示保存刚才创建分区到磁盘上,OVER 11: ----就这样一个主分区完成创建 12: 验证分区创建: 13: [root@iNeedle~]# fdisk-l/dev/sdc 14: Disk/dev/sdc:21.5GB,21474836480bytes 15: 255heads,63sectors/track,2610cylinders 16: Units=cylindersof16065*512=8225280bytes 17: Sectorsize(logical/physical):512bytes/512bytes 18: I/Osize(minimum/optimal):512bytes/512bytes 19: Diskidentifier:0x40642f29 20: DeviceBootStartEndBlocksIdSystem 21: /dev/sdc116545253223+83Linux 22: ----上述信息表明主分区创建成功。 5、创建swap交换分区 1: [root@iNeedle~]# fdisk/dev/sdc 2: ............ 3: Command(mforhelp):n<----输入n新建分区命令 4: eextended 5: pprimarypartition(1-4) 6: p<----输入p代表主分区,这里也是选择主分区类型 7: 8: Partitionnumber(1-4):2<----输入分区编号2(主分区1-4,逻辑分区从5开始编号) 9: Firstcylinder(655-2610,default655)<----回车选择默认第655个柱面开始即可 10: Lastcylinder,+cylindersor+size(K,M,G)(655-2610,default2610):+5G<----第二个主分区也为5G空间 11: ----这时不要直接输入w命令保存分区,需要修改分区类型id,修改为swap类型,id=82 12: Command(mforhelp):t<----又返回上级命令,输入t表示修改分区的类型id,这里要修改第二个分区为swap分区,id=82 13: Partitionnumber(1-4):2<----选择第二个分区,为该分区修改分区类型id 14: Hexcode(typeLtolistcodes):82 15: Changedsystemtypeofpartition2to82(Linuxswap/Solaris)<----成功设置为swap分区类型 16: Command(mforhelp):w<----同样需要保存分区 17: ----到此为止swap分区完成创建 18: 验证分区创建: 19: [root@iNeedle~]# fdisk-l/dev/sdc 20: Disk/dev/sdc:21.5GB,21474836480bytes 21: 255heads,63sectors/track,2610cylinders 22: Units=cylindersof16065*512=8225280bytes 23: Sectorsize(logical/physical):512bytes/512bytes 24: I/Osize(minimum/optimal):512bytes/512bytes 25: Diskidentifier:0x40642f29 26: DeviceBootStartEndBlocksIdSystem 27: /dev/sdc116545253223+83Linux 28: /dev/sdc26551308525325582Linuxswap/Solaris 29: ----上述信息表明swap分区创建成功。 常见分区类型id: 1: 0Empty24NECDOS81Minix/oldLinbfSolaris 2: 1FAT1239Plan982Linuxswap/Soc1DRDOS/sec(FAT- 3: 2XENIXroot3cPartitionMagic83Linuxc4DRDOS/sec(FAT- 4: 3XENIXusr40Venix8028684OS/2hiddenC:c6DRDOS/sec(FAT- 5: 4FAT16<32M41PPCPRePBoot85Linuxextendedc7Syrinx 6: 5Extended42SFS86NTFSvolumesetdaNon-FSdata 7: 6FAT164dQNX4.x87NTFSvolumesetdbCP/M/CTOS/. 8: 7HPFS/NTFS4eQNX4.x2ndpart88LinuxplaintextdeDellUtility 9: 8AIX4fQNX4.x3rdpart8eLinuxLVMdfBootIt 10: 9AIXbootable50OnTrackDM93Amoebae1DOSaccess 11: aOS/2BootManag51OnTrackDM6Aux94AmoebaBBTe3DOSR/O 12: bW95FAT3252CP/M9fBSD/OSe4SpeedStor 13: cW95FAT32(LBA)53OnTrackDM6Auxa0IBMThinkpadhiebBeOSfs 14: eW95FAT16(LBA)54OnTrackDM6a5FreeBSDeeGPT 15: fW95Ext'd (LBA) 55 EZ-Drive a6 OpenBSD ef EFI (FAT-12/16/ 16: 10 OPUS 56 Golden Bow a7 NeXTSTEP f0 Linux/PA-RISC b 17: 11 Hidden FAT12 5c Priam Edisk a8 Darwin UFS f1 SpeedStor 18: 12 Compaq diagnost 61 SpeedStor a9 NetBSD f4 SpeedStor 19: 14 Hidden FAT16 <3 63 GNU HURD or Sys ab Darwin boot f2 DOS secondary 20: 16 Hidden FAT16 64 Novell Netware af HFS / HFS+ fb VMware VMFS 21: 17 Hidden HPFS/NTF 65 Novell Netware b7 BSDI fs fc VMware VMKCORE 22: 18 AST SmartSleep 70 DiskSecure Mult b8 BSDI swap fd Linux raid auto 23: 1b Hidden W95 FAT3 75 PC/IX bb Boot Wizard hid fe LANstep 24: 1c Hidden W95 FAT3 80 Old Minix be Solaris boot ff BBT 25: 1e Hidden W95 FAT1 6、创建扩展分区并创建逻辑分区 1、扩展分区创建 1: [root@iNeedle~]#fdisk/dev/sdc 2: Command(mforhelp):n<----输入n,进行创建新的分区 3: Commandaction 4: eextended 5: pprimarypartition(1-4) 6: e<----输入e,分区类型为扩展分区 7: Partitionnumber(1-4):3<----输入分区编号,这里为3 8: Firstcylinder(1309-2610,default1309):<----使用默认柱面起始即可 9: Usingdefaultvalue1309 10: Lastcylinder,+cylindersor+size{K,M,G}(1309-2610,default2610):2000<----指定末尾柱面 11: Command(mforhelp):w 12: Thepartitiontablehasbeenaltered!<----保存分区到磁盘 13: #注意如果使用3P+E,该扩展分区一定要将全部分区分给E,否则后续的磁盘空间不能有效利用. 14: #到此位置已经创建扩展分区:扩展分区实质上不能直接存储使用,需要再进行逻辑分区创建,在逻辑分区上才可存储数据 验证扩展分区创建结果: 1: [root@iNeedletest]#fdisk-l/dev/sdc 2: Disk/dev/sdc:21.5GB,21474836480bytes 3: 255heads,63sectors/track,2610cylinders 4: Units=cylindersof16065*512=8225280bytes 5: Sectorsize(logical/physical):512bytes/512bytes 6: I/Osize(minimum/optimal):512bytes/512bytes 7: Diskidentifier:0x40642f29 8: DeviceBootStartEndBlocksIdSystem 9: /dev/sdc116545253223+83Linux 10: /dev/sdc26551308525325582Linuxswap/Solaris 11: /dev/sdc31309200055584905Extended<----扩展分区已经创建 2、创建逻辑分区 1: [root@iNeedle~]#fdisk/dev/sdc 2: Command(mforhelp):n<----n:创建分区 3: Commandaction 4: llogical(5orover) 5: pprimarypartition(1-4) 6: l<----l:创建逻辑分区 7: Firstcylinder(1309-2000,default1309):<----默认1309;(1309-2000)为扩展分区的大小,逻辑分区是在扩展分区中创建的 8: Usingdefaultvalue1309 9: Lastcylinder,+cylindersor+size{K,M,G}(1309-2000,default2000):<----使用默认2000 10: Usingdefaultvalue2000 11: Command(mforhelp):w<----保存分区信息到磁盘 12: Thepartitiontablehasbeenaltered! 验证逻辑分区: 1: [root@iNeedletest]#fdisk-l/dev/sdc 2: Disk/dev/sdc:21.5GB,21474836480bytes 3: 255heads,63sectors/track,2610cylinders 4: Units=cylindersof16065*512=8225280bytes 5: Sectorsize(logical/physical):512bytes/512bytes 6: I/Osize(minimum/optimal):512bytes/512bytes 7: Diskidentifier:0x40642f29 8: DeviceBootStartEndBlocksIdSystem 9: /dev/sdc116545253223+83Linux 10: /dev/sdc26551308525325582Linuxswap/Solaris 11: /dev/sdc31309200055584905Extended 12: /dev/sdc5130920005558458+83Linux 13: #最后一行信息表示逻辑分区已经创建成功,并且逻辑分区编号是从5开始的,并没有手动指定
Linux一切接文件,除了普通文件和目录文件,还包括一些其它的特殊文件:块设备文件、字符设备文件、套接字文件、链接文件等。今天这里主要说一下常见的块设备文件和字符设备文件,这2类是最常见的设备文件类。 设备常见有鼠标、键盘、显示器、硬盘等等。其中硬盘是块设备,鼠标、键盘、终端等是属于字符设备。这些设备在linux中也是以文件形式存在,存在于目录/dev/中,这些设备文件不占磁盘块空间,只用一个inode来表示即可,主要记录设备文件的一些信息,包括主设备号和次设备号等信息。主设备号指的是设备主类型,比如是硬盘还是串口,标示一类设备,这些设备用一个驱动程序就行;次设备号标示同类设备中的不同设备个体,比如有3个串口,每个串口的主设备号一样,此设备号分别不同用于区分不同的串口。 一、字符设备文件 举例说明,例如终端。 linux有终端的概念,其实就可以简单理解为显示器,当然这样理解有些不准确。如果连接的是真实的显示器,就是物理终端,设备文件名称就为tty*;如果用远程ssh工具连接过来的终端被称为虚拟终端,设备文件名为pts*。 物理终端设备文件名称: [root@iNeedle~]# ls /dev/tty* /dev/tty/dev/tty12/dev/tty17/dev/tty21/dev/tty26/dev/tty30/dev/tty35/dev/tty4/dev/tty44/dev/tty49/dev/tty53/dev/tty58/dev/tty62/dev/ttyS0 /dev/tty0/dev/tty13/dev/tty18/dev/tty22/dev/tty27/dev/tty31/dev/tty36/dev/tty40/dev/tty45/dev/tty5/dev/tty54/dev/tty59/dev/tty63/dev/ttyS1 /dev/tty1/dev/tty14/dev/tty19/dev/tty23/dev/tty28/dev/tty32/dev/tty37/dev/tty41/dev/tty46/dev/tty50/dev/tty55/dev/tty6/dev/tty7/dev/ttyS2 /dev/tty10/dev/tty15/dev/tty2/dev/tty24/dev/tty29/dev/tty33/dev/tty38/dev/tty42/dev/tty47/dev/tty51/dev/tty56/dev/tty60/dev/tty8/dev/ttyS3 /dev/tty11/dev/tty16/dev/tty20/dev/tty25/dev/tty3/dev/tty34/dev/tty39/dev/tty43/dev/tty48/dev/tty52/dev/tty57/dev/tty61/dev/tty9 虚拟终端设备文件名称: [root@iNeedle~]# ls /dev/pts* 01ptmx 查看当前有哪些终端在登陆可以使用以下命令: [root@iNeedle~]# who roottty12015-12-0106:48 rootpts/02015-12-0217:49(172.16.1.20) rootpts/12015-12-0306:20(222.128.159.250) 查看当前我正在使用的终端: [root@iNeedle~]# who am i rootpts/12015-12-0306:20(222.128.159.250) 简单测试举例,向终端发消息: [root@iNeedle~]# echo "Hello World" > /dev/pts/1 HelloWorld 这一块的知识点感觉比较少,只需要简单了解即可。如果后续有内容再补充吧。 二、块设备文件 举例说明,典型的是硬盘。硬盘是我们最常用的设备之一,也是计算机必备的物理设备,主要作用是存储数据。在linux系统看来硬盘也是个设备,块设备,块设备读取不同于字符设备,它可以随机访问非连续的数据块,不像字符设备是字符流的形式,只能连续读取和写入。 硬盘在linux中的存储设备文件查看: [root@iNeedle~]# ls /dev/sd* /dev/sda/dev/sda1/dev/sda2 sda表示第一块硬盘,sda1表示第一块硬盘的第一个分区,sda2表示第一块硬盘的第二个分区。如果有第二个硬盘的话,名称为sdb,第二块硬盘的第一个分区为sdb1,依次类推。 其实也可以像刚才那样向硬盘中写入数据,但是这种方式非常规写法,对硬盘数据造成不可预料的破坏,这类操作只可在虚拟机上做实验,切不可在物理机上,否则系统在下次重启的时候就启动不了了。 [root@iNeedle~]# ls /dev/sd* /dev/sda/dev/sda1/dev/sda2 这样做相当于把把这些信息填充到了sda硬盘的MBR中,如果下次启动系统就会找不到MBR信息,导致系统启动不了,所以说最好在虚拟机上做,然后恢复快照就行了。可以看出如果想毁掉一个系统是多么的简单,只需要执行这个命令,系统就再也起不了了,除非你有MBR数据的备份,这也在提示我们在真实的物理服务器上要最好MBR的备份,防止被破坏。
最近在学习 MySQL 的 bin-log 时候考虑到数据备份的问题,突然想到如果能将数据通过 Linux 命令行方式备份到百度网盘,那是一件多么牛逼的事情。百度网盘有免费的 2TB 存储空间,而且有百度做靠山,不怕数据丢失,安全可靠。说干就干,通过百度 and 谷歌找到了几种方式,比较喜欢 bypy 的方式,使用简单,方便。下边简单的总结一下如何使用 bypy 实现百度网盘数据的同步。 这是一个百度云的 Python 客户端,其主要目的和功能,就是为 Linux 使用者提供一种在命令行下,使用百度云盘中2T存储空间的方法。它提供文件列表、下载、上传、比较、向上同步、向下同步,等操作。 系统环境: Linux 系统 + Python 2.7 安装软件工具: pip install requests pip install bypy 授权登陆: 执行 bypy info,显示下边信息,根据提示,通过浏览器访问下边灰色的https链接,如果此时百度网盘账号正在登陆,会出现长串授权码,复制。 [root@ineedle ~]# bypy info Please visit: # 访问下边这个连接,复制授权码 https://openapi.baidu.com/oauth/2.0/authorize?scope=basic+netdisk&redirect_uri=oob&response_type=code&client_id=q8WE4EpCsau1oS0MplgMKNBn And authorize this app Paste the Authorization Code here within 10 minutes. Press [Enter] when you are done # 提示在下边粘贴授权码 在下边图示红色位置粘贴授权码,耐心等待一会即可(1-2分钟) Press [Enter] when you are done a288f3d775fa905a6911692a0808f6a8 Authorizing, please be patient, it may take upto None seconds... Authorizing/refreshing with the OpenShift server ... OpenShift server failed, authorizing/refreshing with the Heroku server ... Successfully authorized Quota: 2.015TB Used: 740.493GB 授权成功。 测试上传和同步本地文件到云盘 由于百度PCS API权限限制,程序只能存取百度云端/apps/bypy目录下面的文件和目录。我们可以通过: [root@ineedle ~]# bypy list /apps/bypy ($t $f $s $m $d): 把本地当前目录下的文件同步到百度云盘: # bypy upload 把云盘上的内容同步到本地: # bypy downdir 比较本地当前目录和云盘根目录,看是否一致,来判断是否同步成功: # bypy compare
一、存储设备分区简述 文件系统最终目的是把大量数据有组织的放入持久性的存储设备,如硬盘。硬盘存储能力具有持久性,不会因为断电而消失,存储量大,但读取速度慢。操作系统读取硬盘的时候,不会一个一个扇区读取,效率太低,而是一次性连续读取多个扇区,即一次性读取一个“块”。这种由扇区组成的块,是文件存取的最小单位,块最常见的是4KB,即连续的8个sector组成一个block。文件数据都存储在块中,那么显然必须找到一个地方存储文件的元信息,比如文件的创建者、创建日期、文件大小等等。这种存储文件元信息的区域就叫做inode,中文译名为“索引节点”。 硬盘最开始的区域是MBR,用于Linux开机启动。剩余空间可分成若干分区。每个分区有一个相关的分区表,记录分区的相关信息,这个分区表是存储在分区之外的,分区表说明了对应分区的起始位置和分区的大小。 一个典型的Linux分区包含有下面各个部分: 1、分区的第一个部分是启动分区(Boot Block),它主要是为计算机开机服务的。Linux开机启动后,会首先载入MBR,随后MBR从某个硬盘的启动区加载程序。该程序负责进一步的操作系统的加载和启动。为了方便管理,即使某个分区中没有安装操作系统,Linux也会在该分区预留启动区。 2、启动区之后的是超级区,它存储有文件系统的相关信息,包括文件系统的类型,inode的数目,数据块的数目;貌似一个inode位图和block位图,用一些空间的二进制来表示inode和block的使用情况,1表示使用过,0表示未使用,快速定位,具体怎么存储不知道。 3、随后是多个inodes,它们是实现文件存储的关键。在Linux系统中,一个文件可以分成几个数据块存储,每个文件对应一个inode,这个inode中包含多个指针,指向属于该文件的各个数据块。当操作系统需要读取文件时,就根据inode指向的数据块进行读取。 4、最后一部分,就是真正存储数据的数据块了。 二、iNode节点 在Linux文件管理中,文件除了自身数据之外,还有附属信息,即文件的元数据;这个元数据用于记录文件的许多信息,比如文件大小,属主,属组,修改日期等等。元数据并不包含在文件数据中,由系统维护,也就是包含在inode中。每个inode有一个唯一的整数编号表示(inode number)。 三、inode的内容 inode包含文件的元信息,具体来说具有以下内容: **文件的字节数 **文件拥有者的UID **文件的组ID **文件的读、写、执行权限 **文件的时间戳,共有三个:ctime指inode创建时间,mtime文件内容上一次修改时间,atime指文件最后一次访问的时间。 **链接数,即有多少个文件名指向这个inode **文件数据块block的位置 可以使用stat命令,查看某个文件的inode信息:stat example.txt 总之,除了文件名以外的所有文件信息,都存在inode之中。至于为什么没有文件名,下文会有详细解释。 四、inode大小 inode也会消耗硬盘空间,所以硬盘格式化的时候,系统自动将硬盘分成2个区域;一个是数据区,存放文件数据,另一个是inode区,存放inode所包含的信息。每个inode节点大小一般是128字节。inode节点的总数,在格式化时就给定,一般是每1KB或2KB就设置一个inode,此数值不确实是不是正确。 查看每个硬盘分区的inode总数和已使用的数量,可以使用df命令:df –i 查看每个inode节点的大小,可以用如下命令:dumpe2fs –h /dev/hda | grep “Inode size” 由于每个文件都必须有inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况,这时就无法在硬盘上创建新文件。 五、inode号码 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或绰号。表面上用户通过文件名打开文件,实际上系统内部过程分三步:首先系统找到这个文件名对应的inode号码;其次通过inode号获取inode信息;最后根据inode信息找到文件数据所在的block,读出数据。 使用ls -i命令,可以看到文件名对应的inode号: ls –i example.txt 六、目录文件 Linux系统中,目录也是一种文件。打开目录,实际上就是打开目录文件。目录文件的结构非常简单,就是一系列目录项的列表。每个目录项由2部分组成:所包含文件的文件名和文件对应的inode号码。 ls命令只能列出目录文件中的所有文件名:ls /etc/ -i可以列出整个目录文件,即文件名和inode号码:ls –i /etc 如果要查看文件的详细信息,就必须根据inode号码,访问inode节点,读取信息。 举例:文件系统查找文件/var/log/message的过程 首先确定/目录文件的数据块,事实上/目录是由系统决定在哪里存储的,我们不用关心太多;读取/目录文件,找到var的文件名,找到对应的inode号,根据var的inode号查找到inode节点,从inode节点中读取var的block,得知var是个目录文件;打开var目录文件,查找到名称为log的文件名,找出log对应的inode号,查询inode对应的inode节点,通过inode节点找到log的block,读取log的block,发现log是个目录文件;打开log目录文件,查找到名称message文件名,找出message对应的inode号,查询inode对应的inode节点,通过inode节点找到message的block,即找到了message文件;由此可以看到操作系统经过了哪些曲折的步骤。 七、硬链接 一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。 ln命令可以创建硬链接:ln 源文件 目标文件 运行上面这条命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做"链接数",记录指向该inode的文件名总数,这时就会增加1。反过来,删除一个文件名,就会使得inode节点中的"链接数"减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。删除一个文件本质上就是去删除这个文件的硬链接。 顺便说一下目录文件的链接数。目录文件是不可以通过ln命令来创建硬链接的,但是目录文件本身却有硬链接,且至少2个硬链接数。创建目录时,默认会生成两个目录项:"."和".."。前者的inode号码就是当前目录的inode号码,等同于当前目录的"硬链接";后者的inode号码就是当前目录的父目录的inode号码,等同于父目录的"硬链接"。所以,任何一个目录的"硬链接"总数,总是等于2加上它的子目录总数(含隐藏目录),这里的2是原目录对其的“硬链接”和当前目录下的".硬链接“。 八、软连接 除了硬链接以外,还有一种特殊情况。文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。 这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。创建软连接时会增加一个inode节点,而硬链接不会。 其它区别: 软连接可以连接目录,而硬链接不可以链接目录。 软连接可以跨分区进行链接,而硬链接不可以跨分区,更不可以跨磁盘作链接,这是由于硬链接的inode号是针对分区来说的,inode在分区里才有实际意义。 九、inode的特殊作用 由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象: 1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用:find . -inum 69905165 -exec rm -i {} \; 2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。 3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。 第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。 十、实际问题 在一台配置较低的Linux服务器(内存、硬盘比较小)的/data分区内创建文件时,系统提示磁盘空间不足,用df -h命令查看了一下磁盘使用情况,发现/data分区只使用了66%,还有12G的剩余空间,按理说不会出现这种问题。 后来用df -i查看了一下/data分区的索引节点(inode),发现已经用满(IUsed=100%),导致系统无法创建新目录和文件。 查找原因: /data/cache目录中存在数量非常多的小字节缓存文件,占用的Block不多,但是占用了大量的inode。 解决方案: 1、删除/data/cache目录中的部分文件,释放出/data分区的一部分inode。 2、用软连接将空闲分区/opt中的newcache目录连接到/data/cache,使用/opt分区的inode来缓解/data分区inode不足的问题:ln -s /opt/newcache /data/cache 十一、文件共享 在Linux的进程中,当我们打开一个文件时,返回的是一个文件描述符。这个文件描述符是一个数组的下标,对应数组元素为一个指针。有趣的是,这个指针并没有直接指向文件的inode,而是指向了一个文件表格,再通过该表格,指向加载到内存中的目标文件的inode。如下图,一个进程打开了两个文件。 可以看到,每个文件表格中记录了文件打开的状态(status flags),比如只读,写入等,还记录了每个文件的当前读写位置(offset)。当有两个进程打开同一个文件时,可以有两个文件表格,每个文件表格对应的打开状态和当前位置不同,从而支持一些文件共享的操作,比如同时读取。要注意的是进程fork之后的情况,子进程将只复制文件描述符的数组,而和父进程共享内核维护的文件表格和inode。此时要特别小心程序的编写
一、主引导扇区 主引导扇区位于硬盘的0磁道0柱面1扇区,共占用了63个扇区,但实际上只使用了512字节,由三大部分组成: 1、主引导记录MBR(Master Boot Record):占446字节。 负责检查硬盘分区表、寻找可引导分区并负责将可引导分区的引导扇区DBR装入内存,系统由此开始启动 2、分区表DPT(Disk Parttion Table):占64字节,每份16字节的4份硬盘分区表,记载了每个分区类型、大小和分区开始、结束位置等重要内容 3、分区有效标志(Magic Number):占2字节,固定为55AA。 主引导扇区所在硬盘磁道上的其它扇区一般均空出,且这个扇区所在硬盘磁道是不属于分区范围内的,紧接着它后面才是分区内容,操作系统是无法读取的。广义的MBR包括整个扇区(引导程序、分区表、幻数),狭义的MBR仅仅指引导程序,不同操作系统MBR是不同的,用安装盘装系统可以重写硬盘MBR,而GHOST恢复C盘却不行,因此初次装系统或更换操作系统一般都用安装盘安装。 二、逻辑结构图 三、BIOS如何加载MBR 电源开启之后,BIOS某个芯片上程序运行,开始检测电脑上的周围设备,检测完毕无误后开始按照管理员设定的BIOS启动顺序进行检测;常见的启动设备包括:光盘、硬盘、USB、网卡。假设设定的顺序就是上述的顺序,BIOS会先检测光盘的第一个扇区,查看扇区中是否有MBR存在,如果不存在则会检测第二个设备硬盘,读取硬盘的第一个扇区,如果没有MRB,则会继续读取USB设备;如果该扇区中有MBR,则会读取MBR程序,如果MBR程序有错误,则BIOS会停止下一个设备的检测,并发出错误信息;如果MBR程序没有错误,则执行MBR程序负责检查硬盘分区表、寻找可引导分区并负责将可引导分区的引导扇区(DBR)装入内存,系统由此开始启动。 四、磁盘分区 由于磁盘分区表只有64字节,每个分区信息需要16字节,也就是只能记录4个分区信息。分区可以是主分区和扩展分区,扩展分区本身没有记录分区信息,而通过扩展分区可以再次分区为逻辑分区,扩展分区最多可以分16个逻辑分区。因此我们可以设置的分区方案: 4个主分区P + 0个扩展分区E 3个主分区P + 1个扩展分区E(N个逻辑分区) 2个主分区P + 1个扩展分区E(N个逻辑分区) 1个主分区P + 1个扩展分区E(N个逻辑分区) 常见的做法是:3P + 1E。但是需要注意的是,在最后一个主分区时或者扩展分区时,一定要将磁盘剩余容量全部分配给该分区,否则硬盘剩余的磁盘空间就不能够再使用了。逻辑分区的编号永远是从5开始,不管有几个主分区,例如只有一个主分区,一个扩展分区,那么逻辑分区也是从5开始编号的。总之P + E <= 4; 五、常见问题 1、区分MBR区、DBR区、FAT区、DIR区、DATA区 1).0磁道0柱面区(MBR),包括广义MBR区 + 剩余未用62扇区。MBR通过检查DPT分区信息引导系统跳转至DBR。 2).DBR区,每个分区前的引导扇区,同时记录本区的参数。对于第一主分区它通常位于硬盘的0磁道1柱面1扇区,是操作系统可以直接访问的第一个扇区,DBR是由高级格式化程序所产生的。 3).其它区域:FAT文件分配表区+DIR根目录区+DATA数据区,操作系统可以直接访问。 2、分区、重装系统、格式化对各区影响 1).PM分区:不更改狭义MBR(446字节),只更改DPT分区表数值,当然也会更改新改变的分区。 2).安装盘装系统:更改狭义MBR(446字节)为相应系统的MBR,更改活动分区的引导扇区DBR为相应类型。 3).Ghost安装系统:不更改MBR,更改Ghost分区的引导扇区DBR为相应类型,不更改活动分区DBR。无论是将其克隆到其它主分区中,还是克隆到逻辑分区中,克隆软件都还将自动完成以下两项工作:一是更改boot.ini等系统文件的内容,使其能在新的分区运行;而是克隆系统文件.gho包中含有原分区的分区引导记录,克隆到其它主分区或逻辑分区时,都将新的分区创建引导分区,并将包中引导程序复制到该引导分区中,但不将该引导分区中的引导程序指向新分区中的系统文件。 4).格式化分区:不更改MBR,更改该分区的引导扇区为当前操作系统的引导扇区DBR为相应类型。 3、windows和Linux的MBR有何不同 像nt 5.x的mbr与nt 6.x的mbr均占用1个扇区,位于0磁道0柱面1扇区上,但是像GRUB4DOS占用多个扇区,它还占用0磁道0柱面部分未用扇区。 4、操作系统从硬盘启动需要哪些条件 1).正确的MBR信息 2).启动分区必须是活动的 3).启动分区上有正确的操作系统引导文件 5、如何设置正确的MBR和活动分区 1).写入MBR信息,可以写列方式: a.使用光盘安装OS b.使用光盘引导到复制文件前,或进入命令行fixmbr c.将硬盘接上电脑,在Windows磁盘工具下初始化磁盘(经测试会写入Win98 MBR) d.使用BOOTICE/DiskGenius等工具初始化MBR(最新版可写入nt5/nt6的mbr以启动Winxp/Vista/7/8/8.1) e.使用GHOST全盘恢复(危险!可能更改446后面的分区表) 2)设置活动分区 a.使用光盘安装 b.将硬盘接上电脑,在Windows磁盘工具下设为活动 c.使用DiskGenius等工具
一、磁盘的分类 磁盘主要有IDE、SATA、SCSI。IDE是比较老的硬盘,数据速度比较慢;SATA是现在用的比较多的,台式机、笔记本大多都用的SATA硬盘;SCSI硬盘速度最快,但是价格相对较高。 二、硬盘的物理结构 硬盘存储数据是根据电、磁转换原理实现的。硬盘主要是由若干张带有磁性物质的金属或玻璃盘片、磁头、永磁电机构成。盘片会随着中心轴位置的主轴电机高速旋转,而磁头会随磁头臂围绕音圈马达为中心进行一定角度的摆动,来读取或写入数据。很明显,磁盘中最重要的就是磁头和磁盘片。磁盘旋转速度有几种,比如笔记本硬盘一般是5400转/min,台式机电脑硬盘转速是7200转/分种,服务器上硬盘就更高了,一般会有10000+转/分钟;硬盘的转速越快,读取数据的速度就越快。 三、硬盘的逻辑结构及概念 1、磁盘片 为了提高磁盘的容量,磁盘有若干个盘片叠加而成,每个磁盘片都有上下2面,盘片的数量决定了磁盘的容量,但是也不能无限制的增加盘片。 2、磁头 每个盘面对应一个磁 头,主要是靠磁头来读取盘片上的信息,因此磁头数量和盘片数量是一致的。 3、磁道 当磁盘旋转时,磁头若保持在一个位置上,则每个磁头都会在盘面上划出一个圆形轨迹,这些圆形轨迹叫做磁道。每个盘面上有很多同心圆的磁道,从外圆到圆心分别标号0磁道、1磁道、2磁道… …且要注意,同一盘面上不同磁道之间是有间隙的,防止不同磁道之间磁场干扰。 4、扇区 每个磁道又分为若干个圆弧,叫做扇区。每个扇区一般存放的是512字节,磁盘驱动器向磁盘读取和写入数据是以扇区为单位进行的。因为由外到内的磁道周长是不一样的,导致内圈磁道的扇区数量会变小,具体怎么变化根据不同磁盘厂商而定。还有说法是之前比较老的磁盘每个磁道的扇区数量是一致的,这个问题没有得到权威的答案。扇区也类似磁道,每个磁道上的不同扇区也是有间隙的,没有连接着,也是为了防止磁场的干扰。 5、柱面 由每个磁盘片的相同编号的磁道组成了磁盘柱面。磁盘柱面数量和磁盘片上的磁道数量是相等的。磁盘转速是恒定的,磁盘外柱面存储数据要大于靠近圆心的柱面,因此外柱面磁道读取数据速度是比较快的(相对于内磁道而言),因此安装操作系统分区都是把分区设置为从0柱面开始的。操作系统的磁盘分区就是以磁盘柱面为单位进行分区的,比如C盘(0-98柱面),D盘(98-126柱面),E盘(127-结束柱面),也有可能C盘结束边界并没有把98柱面全部用完,剩余那部分由D盘继续使用,因此D盘也是从98柱面开始的。Linux系统下可以使用fdisk –l /dev/sda查看磁盘1的分区情况,甚至可以看到每个分区使用了多少柱面。 四、关于磁盘的最小单位 磁盘分区的最小单位是柱面 磁盘存储的最小单位是扇区 文件系统的最小单位是区块