git command

git command

  1. git 删除分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //查看当前分支
    $ git branch
    * master
    release

    //删除本地分支
    $ git branch -d release
    $ git branch -D release

    //删除远程分支
    $ git push origin :release
    说明:
    git push [远程名] [本地分支]:[远程分支] 语法,如果省略[本地分支],那就等于是在说"在这里提取空白然后把它变成[远程分支]"
  2. git 创建分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //查看当前分支
    $ git branch
    * master

    //创建一个新的分支,并切换到分支
    $ git checkout -b release
    $ git branch
    master
    * release
  3. git push 分支到远程仓库

    1
    $ git push --set-upstream origin release
  4. git pull 远程某个分支

    1
    2
    3
    4
    $ git branch release
    $ git checkout release
    $ git branch --set-upstream-to=origin/<branch> release
    $ git pull
  5. 合并分支

    1
    2
    3
    //合并某分支到当前分支
    $ git checkout master
    $ git merge <branch>
  6. 克隆指定分支

    1
    2
    $ git clone -b <branch_name> http://<user>:<passwd>@<git>/<project>/<project.git>
    $ git clone -b lp http://test:test123@git.hub.io/ops/cmdb.git

ftp proxy install

FTP proxy install

  1. ftpproxy install

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@59 ~]# yum -y install ctags
    [root@59 ~]# wget http://www.ftpproxy.org/download/ftpproxy-1.2.3.tgz
    [root@59 ~]# tar xzf ftpproxy-1.2.3.tgz
    [root@59 ~]# cd ftpproxy-1.2.3
    [root@59 ftpproxy-1.2.3]# make
    [root@59 ftpproxy-1.2.3]# make install

    [root@59 ftpproxy-1.2.3]# mkdir -p /mnt/app/ftp_proxy
    [root@59 ftpproxy-1.2.3]# cp /usr/local/sbin/ftp.proxy /mnt/app/ftp_proxy
  2. ftpproxy start

    1
    [root@59 ~]# /mnt/app/ftp_proxy/ftp.proxy -D 10021 -e -l -m -t 1800
  3. ftp.proxy help

    1
    2
    3
    4
    5
    6
    说明:
    -D 10021 指定端口为10021
    -e 启用客户端服务器选择(非常重要),通常我们的ftp账号为ftpuser这种,那么使用此参数后,我们可以访问后端的某台服务器,比如192.168.1.15,使用的账号名变成:ftpuser@192.168.1.15
    -l Logging
    -m monitor mod
    -t 1800 超时时间
  4. 通过ftp.proxy登录FTP

    1
    2
    3
    4
    文件协议 : FTP
    主机名 : {ftp.proxy.ipaddr}:{ftp.proxy.port}
    用户名 : {ftp.user}@{ftp.real.ipaddr}
    密码 : {ftp.user.passwd}

ftp.proxy.login

  1. 将ftp.proxy加入到systemd
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [root@59 ftp_proxy]# cat > /mnt/app/ftp_proxy/ftpproxy.conf <<EOF
    > #代理服务器监听端口。
    > BIND_PORT=10021
    > #其他选项
    > #-e 启用客户端服务器选择(非常重要),通常我们的ftp账号为ftpuser这种,那么使用此参数后,我们可以访问后端的某台服务器,比如192.168.1.15,使用的账号名变成:ftpuser@192.168.1.15
    > #-l Logging
    > #-t 1800 超时时间
    > OPTIONS='-e -l -m -t 1800'
    > EOF

    [root@59 ftp_proxy]# cat > /usr/lib/systemd/system/ftpproxy.service <<EOF
    > [Unit]
    > Description=Ftp proxy server daemon
    > After=network.target
    >
    > [Service]
    > Type=forking
    > EnvironmentFile=/mnt/app/ftp_proxy/ftpproxy.conf
    > ExecStart=/mnt/app/ftp_proxy/ftp.proxy -D $BIND_PORT $OPTIONS
    >
    > [Install]
    > WantedBy=multi-user.target
    > EOF

    [root@59 ftp_proxy]# systemctl enable ftpproxy.service
    [root@59 ftp_proxy]# systemctl start ftpproxy.service

mycat jvm optimize

mycat jvm 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//优化前(8核16G)
[root@10 ~]# vim /mnt/app/mycat/conf/wrapper.conf
# Java Additional Parameters
#wrapper.java.additional.1=
wrapper.java.additional.1=-DMYCAT_HOME=.
wrapper.java.additional.2=-server
wrapper.java.additional.3=-XX:MaxPermSize=128M
wrapper.java.additional.4=-XX:+AggressiveOpts
wrapper.java.additional.5=-XX:MaxDirectMemorySize=4G
wrapper.java.additional.6=-Dcom.sun.management.jmxremote
wrapper.java.additional.7=-Dcom.sun.management.jmxremote.port=1984
wrapper.java.additional.8=-Dcom.sun.management.jmxremote.authenticate=false
wrapper.java.additional.9=-Dcom.sun.management.jmxremote.ssl=false
wrapper.java.additional.10=-Xmx4G
wrapper.java.additional.11=-Xms1G

//优化后(8核16G)
[root@10 ~]# vim /mnt/app/mycat/conf/wrapper.conf
# Java Additional Parameters
#wrapper.java.additional.1=
wrapper.java.additional.1=-DMYCAT_HOME=.
wrapper.java.additional.2=-server
wrapper.java.additional.3=-XX:MaxPermSize=512M
wrapper.java.additional.4=-XX:+AggressiveOpts
wrapper.java.additional.5=-XX:MaxDirectMemorySize=12G
wrapper.java.additional.6=-Dcom.sun.management.jmxremote
wrapper.java.additional.7=-Dcom.sun.management.jmxremote.port=1984
wrapper.java.additional.8=-Dcom.sun.management.jmxremote.authenticate=false
wrapper.java.additional.9=-Dcom.sun.management.jmxremote.ssl=false
wrapper.java.additional.10=-Xmx10G
wrapper.java.additional.11=-Xms5G
wrapper.java.additional.12=-XX:+UseG1GC
wrapper.java.additional.13=-XX:NewRatio=3
wrapper.java.additional.14=-XX:SurvivorRatio=3
wrapper.java.additional.15=-XX:ParallelGCThreads=8

说明:
-XX:MaxPermSize Perm,持久代.用于存放静态文件,如今Java类,方法等.持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类
-XX:+AggressiveOpts 加快编译
-XX:MaxDirectMemorySize 此参数的含义是当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC.注意该值是有上限的,默认是64M,最大为sun.misc.VM.maxDirectMemory(),在程序中中可以获得-XX:MaxDirectMemorySize的设置的值
-Xmx和-Xms 类对象分配在JVM的堆内存里面,直接由Java虚拟机负责垃圾回收
-XX:+UseG1GC 使用G1垃圾处理器
-XX:NewRatio 设置年轻代和年老代的比值。例如:值为3,则表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:ParallelGCThreads 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

java jvm 原理

JVM 原理

  1. JVM简介

    1
    2
    3
    JVM是虚拟机,也是一种规范,他遵循着冯·诺依曼体系结构的设计原理(冯·诺依曼体系结构中,指出计算机处理的数据和指令都是二进制数,采用存储程序方式不加区分的存储在同一个存储器里,并且顺序执行,指令由操作码和地址码组成,操作码决定了操作类型和所操作的数的数字类型,地址码则指出地址码和操作数).

    从DOS到window8,从unix到ubuntu和CentOS,还有MAC OS等等,不同的操作系统指令集以及数据结构都有着差异,而JVM通过在操作系统上建立虚拟机,自己定义出来的一套统一的数据结构和操作指令,把同一套语言翻译给各大主流的操作系统,实现了跨平台运行,可以说JVM是Java的核心,是java可以一次编译到处运行的本质所在.
  2. JVM的组成和运行原理

    1
    2
    3
    4
    5
    JVM的毕竟是个虚拟机,是一种规范,虽说符合冯诺依曼的计算机设计理念,但是他并不是实体计算机,所以他的组成也不是什么存储器,控制器,运算器,输入输出设备.

    在我看来,JVM放在运行在真实的操作系统中表现的更像应用或者说是进程,他的组成可以理解为JVM这个进程有哪些功能模块,而这些功能模块的运作可以看做是JVM的运行原理.

    JVM有多种实现,例如: Oracle的JVM,HP的JVM和IBM的JVM等,而在本文中研究学习的则是使用最广泛的Oracle的HotSpot JVM.
  3. JVM在JDK中的位置

    1
    2
    JDK是Java开发的必备工具箱,JDK其中有一部分是JRE,JRE是JAVA运行环境,JVM则是JRE最核心的部分.
    如图:JDK Standard Edtion

JDK Standard Edtion

1
2
3
4
5
6
7
8
9
10
11
12
13
从最底层的位置可以看出来JVM有多重要,而实际项目中JAVA应用的性能优化,OOM等异常的处理最终都得从JVM这儿来解决

HotSpot是Oracle关于JVM的商标,区别于IBM,HP等厂商开发的JVM

Java HotSpot Client VM和Java HotSpot Server VM是JDK关于JVM的两种不同的实现,前者可以减少启动时间和内存占用,而后者则提供更加优秀的程序运行速度

例如:
[root@10 ~]# java -version
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

HotSpot的版本号是25.73-b02,类型是Java HotSpot Server VM

  1. JVM的组成
    1
    JVM由4大部分组成: ClassLoader,Runtime Data Area,Execution Engine,Native Interface

jvm.组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
JVM组成部分说明:
* ClassLoader
ClassLoader是负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定

* Native Interface
Native Interface是负责调用本地接口的.它的作用是调用不同语言的接口给JAVA用,他会在Native Method Stack中记录对应的本地方法,然后调用该方法时就通过Execution Engine加载对应的本地lib

* Execution Engine
Execution Engine是执行引擎,也叫Interpreter.Class文件被加载后,会把指令和数据信息放入内存中,Execution Engine则负责把这些命令解释给操作系统

* Runtime Data Area
Runtime Data Area则是存放数据的,分为五部分:Stack,Heap,Method Area,PC Register,Native Method Stack.几乎所有的关于java内存方面的问题,都是集中在这块
* Stack
Stack是java栈内存,它等价于C语言中的栈,栈的内存地址是不连续的,每个线程都拥有自己的栈.
栈里面存储着的是StackFrame,在《JVM Specification》中文版中被译作java虚拟机框架,也叫做栈帧.
StackFrame包含三类信息: 局部变量,执行环境,操作数栈
* 局部变量用来存储一个类的方法中所用到的局部变量
* 执行环境用于保存解析器对于java字节码进行解释过程中需要的信息,包括:上次调用的方法,局部变量指针和操作数栈的栈顶和栈底指针
* 操作数栈用于存储运算所需要的操作数和结果
StackFrame在方法被调用时创建,在某个线程中,某个时间点上,只有一个框架是活跃的,该框架被称为Current Frame,而框架中的方法被称为Current Method,其中定义的类为Current Class.局部变量和操作数栈上的操作总是引用当前框架.当Stack Frame中方法被执行完之后,或者调用别的StackFrame中的方法时,则当前栈变为另外一个StackFrame.Stack的大小是由两种类型:固定和动态的,动态类型的栈可以按照线程的需要分配
* Heap
Heap是用来存放对象信息的,和Stack不同,Stack代表着一种运行时的状态.换句话说,栈是运行时单位,解决程序该如何执行的问题,而堆是存储的单位,解决数据存储的问题.
Heap是伴随着JVM的启动而创建,负责存储所有对象实例和数组的.堆的存储空间和栈一样是不需要连续的,它分为Young Generation和Old Generation(也叫Tenured Generation)两大部分.Young Generation分为Eden和Survivor,Survivor又分为From Space和 ToSpace.

和Heap经常一起提及的概念是PermanentSpace,它是用来加载类对象的专门的内存区,是非堆内存,和Heap一起组成JAVA内存,它包含MethodArea区(在没有CodeCache的HotSpotJVM实现里,则MethodArea就相当于GenerationSpace)

在JVM初始化的时候,我们可以通过参数来分别指定,PermanentSpace的大小,堆的大小,以及Young Generation和Old Generation的比值,Eden区和From Space的比值,从而来细粒度的适应不同JAVA应用的内存需求
* PC Register
PC Register是程序计数寄存器,每个JAVA线程都有一个单独的PC Register,他是一个指针,由Execution Engine读取下一条指令.如果该线程正在执行java方法,则PC Register存储的是正在被执行的指令的地址;如果是本地方法,PC Register的值没有定义.PC寄存器非常小,只占用一个字宽,可以持有一个returnAdress或者特定平台的一个指针
* Method Area
Method Area在HotSpot JVM的实现中属于非堆区,非堆区包括两部分:Permanet Generation和Code Cache,而Method Area属于Permanert Generation的一部分.
Permanent Generation用来存储类信息,比如说:class definitions,structures,methods,field,method(data and code)和constants.
Code Cache用来存储Compiled Code,即编译好的本地代码,在HotSpot JVM中通过JIT(Just In Time)Compiler生成,JIT是即时编译器,他是为了提高指令的执行效率,把字节码文件编译成本地机器代码
* Native Method Stack
Native Method Stack是供本地方法(非java)使用的栈.每个线程持有一个Native Method Stack

  1. JVM的运行原理简介

    1
    2
    3
    4
    Java程序被javac工具编译为.class字节码文件之后,我们执行java命令,该class文件便被JVM的Class Loader加载,可以看出JVM的启动是通过JAVA Path下的java.exe或者java进行的.

    JVM的初始化,运行到结束大概包括这么几步:
    调用操作系统API判断系统的CPU架构,根据对应CPU类型寻找位于JRE目录下的/lib/jvm.cfg文件,然后通过该配置文件找到对应的jvm.dll文件(如果我们参数中有-server或者-client,则加载对应参数所指定的jvm.dll,启动指定类型的JVM),初始化jvm.dll并且挂接到JNIENV结构的实例上,之后就可以通过JNIENV实例装载并且处理class文件了.class文件是字节码文件,它按照JVM的规范,定义了变量,方法等的详细信息,JVM管理并且分配对应的内存来执行程序,同时管理垃圾回收.直到程序结束,一种情况是JVM的所有非守护线程停止,一种情况是程序调用System.exit(),JVM的生命周期也结束
  2. JVM的内存管理

    1
    JVM中的内存管理主要是指JVM对于Heap的管理,这是因为Stack,PC Register和Native Method Stack都是和线程一样的生命周期,在线程结束时自然可以被再次使用.虽然说Stack的管理不是重点,但是也不是完全不讲究的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    * 栈的管理
    JVM允许栈的大小是固定的或者是动态变化的.Stack的设置是通过-Xss来设置其大小.

    我们一般通过减少常量,参数的个数来减少栈的增长,在程序设计时,我们把一些常量定义到一个对象中,然后来引用他们可以体现这一点.另外,少用递归调用也可以减少栈的占用

    栈是不需要垃圾回收的,尽管说垃圾回收是java内存管理的一个很热的话题,栈中的对象如果用垃圾回收的观点来看,他永远是live状态,是可以reachable的,所以也不需要回收,他占有的空间随着Thread的结束而释放

    另外栈上有一点得注意的是,对于本地代码调用,可能会在栈中申请内存,比如C调用malloc(),而这种情况下,GC是管不着的,需要我们在程序中,手动管理栈内存,使用free()方法释放内存

    关于栈一般会发生以下两种异常:
    1.当线程中的计算所需要的栈超过所允许大小时,会抛出StackOverflowError
    2.当Java栈试图扩展时,没有足够的存储器来实现扩展,JVM会报OutOfMemoryError
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    * 堆的管理
    堆的管理要比栈管理复杂的多,我通过堆的各部分的作用,设置,以及各部分可能发生的异常,以及如何避免各部分异常

    下图是Heap和PermanentSapce的组合图,其中Eden区里面存着是新生的对象,From Space和To Space中存放着是每次垃圾回收后存活下来的对象,所以每次垃圾回收后,Eden区会被清空.存活下来的对象先是放到From Space,当From Space满了之后移动到To Space.当To Space满了之后移动到Old Space.

    Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor复制过来的对象.而且,Survivor区总有一个是空的.同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能

    Old Space中则存放生命周期比较长的对象,而且有些比较大的新生对象也放在Old Space中

    堆的大小通过-Xms和-Xmx来指定最小值和最大值,通过-Xmn来指定Young Generation的大小(一些老版本也用-XX:NewSize指定(即下图中的Eden加FromSpace和ToSpace的总大小)),然后通过-XX:NewRatio来指定Eden区的大小,在Xms和Xmx相等的情况下,该参数不需要设置.通过-XX:SurvivorRatio来设置Eden和一个Survivor区的比值

    堆异常分为两种:
    1.Out of Memory(OOM)
    2.Memory Leak(ML)
    Memory Leak最终将导致OOM.实际应用中表现为:从Console看,内存监控曲线一直在顶部,程序响应慢,从线程看,大部分的线程在进行GC,占用比较多的CPU,最终程序异常终止,报OOM.OOM发生的时间不定,有短的一个小时,有长的10天一个月的.
    关于异常的处理,确定OOM/ML异常后,一定要注意保护现场,可以dump heap,如果没有现场则开启GCFlag收集垃圾回收日志,然后进行分析,确定问题所在.如果问题不是ML的话,一般通过增加Heap,增加物理内存来解决问题,是的话,就修改程序逻辑

jvm.heap

  1. JVM垃圾回收
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    JVM中会在以下情况触发回收: 对象没有被引用,作用域发生未捕捉异常,程序正常执行完毕,程序执行了System.exit(),程序发生意外终止

    JVM中标记垃圾使用的算法是一种根搜索算法.简单的说,就是从一个叫GC Roots的对象开始,向下搜索,如果一个对象不能达到GC Roots对象的时候,说明它可以被回收了.这种算法比一种叫做引用计数法的垃圾标记算法要好,因为它避免了当两个对象啊互相引用时无法被回收的现象

    JVM中对于被标记为垃圾的对象进行回收时又分为了一下3种算法:
    * 标记清除算法
    该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收,这种算法在存活对象较多时比较高效,但会产生内存碎片
    * 复制算法
    该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效
    * 标记整理算法
    标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题

    JVM中,不同的内存区域作用和性质不一样,使用的垃圾回收算法也不一样,所以JVM中又定义了几种不同的垃圾回收器:
    * Serial GC
    从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停.这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中
    * ParNew GC
    是在Serial GC的基础上,增加了多线程机制.但是如果机器是单CPU的,这种收集器是比Serial GC效率还低
    * Parrallel Scavenge GC
    这种收集器又叫吞吐量优先收集器,而吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%.Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置
    * ParallelOld
    ParallelOld是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代只能使用串行回收收集器
    * Serial Old
    Serial Old是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器
    * CMS
    CMS又称响应时间优先回收器,使用标记清除算法.他的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些.CMS分为4个过程:初始标记,并发标记,重新标记,并发清除
    * GarbageFirst(G1)
    比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation.它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间


    默认的GC种类可以通过jvm.cfg或者通过jmap dump出heap来查看,一般我们通过jstat -gcutil [pid] 1000可以查看每秒gc的大体情况,或者可以在启动参数中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log来记录GC日志

    GC中有一种情况叫做Full GC,以下几种情况会触发Full GC也叫MajorGC:
    * Tenured Space空间不足以创建打的对象或者数组,会执行FullGC,并且当FullGC之后空间如果还不够,那么会OOM:java heap space
    * Permanet Generation的大小不足,存放了太多的类信息,在非CMS情况下回触发FullGC.如果之后空间还不够,会OOM:PermGen space。
    * CMS GC时出现promotion failed和concurrent mode failure时,也会触发FullGC.promotion failed是在进行Minor GC时,survivor space放不下,对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的
    * 判断MinorGC后,要晋升到TenuredSpace的对象大小大于TenuredSpace的大小,也会触发FullGC
    可以看出,当FullGC频繁发生时,一定是内存出问题了


    注意: 下图中连线代表两个回收器可以同时使用

jvm.gc

kafka high throughput

kafka高性能吞吐

  1. kafka涉及到的架构和名词

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    * Topic
    用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上 |
    * Partition
    是Kafka中横向扩展和一切并行化的基础,每个Topic都至少被切分为1个Partition |
    * Offset
    消息在Partition中的编号,编号顺序不跨Partition
    * Consumer
    用于从Broker中取出/消费Message
    * Producer
    用于往Broker中发送/生产Message
    * Replication
    Kafka支持以Partition为单位对Message进行冗余备份,每个Partition都可以配置至少1个Replication(当仅1个Replication时即仅该Partition本身)
    * Leader
    每个Replication集合中的Partition都会选出一个唯一的Leader,所有的读写请求都由Leader处理.其他Replicas从Leader处把数据更新同步到本地.
    * Broker
    Kafka中使用Broker来接受Producer和Consumer的请求,并把Message持久化到本地磁盘.<br>每个Cluster当中会选举出一个Broker来担任Controller,负责处理Partition的Leader选举,协调Partition迁移等工作
    * ISR(In-Sync Replica)
    是Replicas的一个子集,表示目前Alive且与Leader能够"Catch-up"的Replicas集合.
    由于读写都是首先落到Leader上,所以一般来说通过同步机制从Leader上拉取数据的Replica都会和Leader有一些延迟(包括了延迟时间和延迟条数两个维度),任意一个超过阈值都会把该Replica踢出ISR.每个Partition都有它自己独立的ISR
  2. Kafka优异的吞吐性能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    1.Broker
    不同于Redis和MemcacheQ等内存消息队列,Kafka的设计是把所有的Message都要写入速度低容量大的硬盘,以此来换取更强的存储能力.实际上,Kafka使用硬盘并没有带来过多的性能损失,"规规矩矩"的超了一条"近道"

    首先,说"规规矩矩"是因为Kafka在磁盘上只做Sequence I/O,由于消息系统读写的特殊性,这并不存在什么问题.所以通过只做Sequence I/O的限制,规避了磁盘访问速度低下对性能可能造成的影响.

    其次,说超"近道"是因为Kafka重度依赖底层操作系统提供的PageCache功能.

    当上层有写操作时,操作系统只是将数据写入PageCache,同时标记Page属性为Dirty.当读操作发生时,先从PageCache中查找,如果发生缺页才进行磁盘调度,最终返回需要的数据.实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用.同时如果有其他进程申请内存,回收PageCache的代价又很小,所以现代的OS都支持PageCache.

    使用PageCache功能同时可以避免在JVM内部缓存数据,JVM为我们提供了强大的GC能力,同时也引入了一些问题不适用与Kafka的设计.
    * 如果在Heap内管理缓存,JVM的GC线程会频繁扫描Heap空间,带来不必要的开销.如果Heap过大,执行一次Full GC对系统的可用性来说将是极大的挑战
    * 所有在在JVM内的对象都不免带有一个Object Overhead(千万不可小视),内存的有效空间利用率会因此降低.
    * 所有的In-Process Cache在OS中都有一份同样的PageCache.所以通过将缓存只放在PageCache,可以至少让可用缓存空间翻倍
    * 如果Kafka重启,所有的In-Process Cache都会失效,而OS管理的PageCache依然可以继续使用

    PageCache还只是第一步,Kafka为了进一步的优化性能还采用了Sendfile技术.
    在解释Sendfile之前,首先介绍一下传统的网络I/O操作流程,大体上分为以下4步:
    1.OS从硬盘把数据读到内核区的PageCache
    2.用户进程把数据从内核区Copy到用户区
    3.用户进程再把数据写入到Socket,数据流入内核区的Socket Buffer上
    4.OS再把数据从Buffer中Copy到网卡的Buffer上,这样完成一次发送

    整个过程共经历两次Context Switch,四次System Call.同一份数据在内核Buffer与用户Buffer之间重复拷贝,效率低下.其中2,3两步没有必要,完全可以直接在内核区完成数据拷贝.这也正是Sendfile所解决的问题,经过Sendfile优化后,整个I/O过程就变成了下面这个样子:
    1.OS从硬盘把数据读到内核区的PageCache
    2.Sendfile把数据copy到Socket
    3.OS再把数据从Buffer中Copy到网卡的Buffer上,这样完成一次发送

    Kafka的设计初衷是尽一切努力在内存中完成数据交换,无论是对外作为一整个消息系统,或是内部同底层操作系统的交互.如果Producer和Consumer之间生产和消费进度上配合得当,完全可以实现数据交换零I/O.这也就是我为什么说Kafka使用"硬盘"并没有带来过多性能损失的原因

    拓展1:
    * Kafka官方并不建议通过Broker端的log.flush.interval.messages和log.flush.interval.ms来强制写盘,认为数据的可靠性应该通过Replica来保证,而强制Flush数据到磁盘会对整体性能产生影响.
    * 可以通过调整/proc/sys/vm/dirty_background_ratio和/proc/sys/vm/dirty_ratio来调优性能
    a.脏页率超过第一个指标会启动pdflush开始Flush Dirty PageCache
    b.脏页率超过第二个指标会阻塞所有的写操作来进行Flush
    c.根据不同的业务需求可以适当的降低dirty_background_ratio和提高dirty_ratio
    * 建议值
    log.flush.interval.messages=100000
    log.flush.interval.ms=60000
    log.flush.scheduler.interval.ms = 5000

    vm.dirty_background_ratio = 5
    vm.dirty_ratio = 20

    拓展2:
    //查看内核参数
    [root@10 ~]# sysctl -a | grep dirty
    vm.dirty_background_bytes = 0
    vm.dirty_background_ratio = 10
    vm.dirty_bytes = 0
    vm.dirty_expire_centisecs = 3000
    vm.dirty_ratio = 20
    vm.dirty_writeback_centisecs = 500

    //查看脏数据
    [root@10 ~]# cat /proc/vmstat | egrep "dirty|writeback"
    nr_dirty 16696
    nr_writeback 0
    nr_writeback_temp 0
    nr_dirty_threshold 51787
    nr_dirty_background_threshold 25893

    vm.dirty_background_ratio is the percentage of system memory that can be filled with "dirty" pages — memory pages that still need to be written to disk — before the pdflush/flush/kdmflush background processes kick in to write it to disk. My example is 10%, so if my virtual server has 32 GB of memory that’s 3.2 GB of data that can be sitting in RAM before something is done.

    vm.dirty_ratio is the absolute maximum amount of system memory that can be filled with dirty pages before everything must get committed to disk. When the system gets to this point all new I/O blocks until dirty pages have been written to disk. This is often the source of long I/O pauses, but is a safeguard against too much data being cached unsafely in memory.

    vm.dirty_background_bytes and vm.dirty_bytes are another way to specify these parameters. If you set the _bytes version the _ratio version will become 0, and vice-versa

    vm.dirty_expire_centisecs is how long something can be in cache before it needs to be written. In this case it’s 30 seconds. When the pdflush/flush/kdmflush processes kick in they will check to see how old a dirty page is, and if it’s older than this value it’ll be written asynchronously to disk. Since holding a dirty page in memory is unsafe this is also a safeguard against data loss.

    vm.dirty_writeback_centisecs is how often the pdflush/flush/kdmflush processes wake up and check to see if work needs to be done.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    2.Partition
    Partition是Kafka可以很好的横向扩展和提供高并发处理以及实现Replication的基础

    扩展性方面:
    首先,Kafka允许Partition在集群内的Broker之间任意移动,以此来均衡可能存在的数据倾斜问题.
    其次,Partition支持自定义的分区算法,例如可以将同一个Key的所有消息都路由到同一个Partition上去.同时Leader也可以在In-Sync的Replica中迁移.由于针对某一个Partition的所有读写请求都是只由Leader来处理,所以Kafka会尽量把Leader均匀的分散到集群的各个节点上,以免造成网络流量过于集中

    并发方面:
    任意Partition在某一个时刻只能被一个Consumer Group内的一个Consumer消费(反过来一个Consumer则可以同时消费多个Partition),Kafka非常简洁的Offset机制最小化了Broker和Consumer之间的交互,这使Kafka并不会像同类其他消息队列一样,随着下游Consumer数目的增加而成比例的降低性能.此外,如果多个Consumer恰巧都是消费时间序上很相近的数据,可以达到很高的PageCache命中率,因而Kafka可以非常高效的支持高并发读操作,实践中基本可以达到单机网卡上限

    不过,Partition的数量并不是越多越好,Partition的数量越多,平均到每一个Broker上的数量也就越多.考虑到Broker宕机(Network Failure, Full GC)的情况下,需要由Controller来为所有宕机的Broker上的所有Partition重新选举Leader,假设每个Partition的选举消耗10ms,如果Broker上有500个Partition,那么在进行选举的5s的时间里,对上述Partition的读写操作都会触发LeaderNotAvailableException

    再进一步,如果挂掉的Broker是整个集群的Controller,那么首先要进行的是重新任命一个Broker作为Controller.新任命的Controller要从Zookeeper上获取所有Partition的Meta信息,获取每个信息大概3-5ms,那么如果有10000个Partition这个时间就会达到30s-50s.而且不要忘记这只是重新启动一个Controller花费的时间,在这基础上还要再加上前面说的选举Leader的时间.

    此外,在Broker端,对Producer和Consumer都使用了Buffer机制.其中Buffer的大小是统一配置的,数量则与Partition个数相同.如果Partition个数过多,会导致Producer和Consumer的Buffer内存占用过大.


    拓展:
    * Partition的数量尽量提前预分配,虽然可以在后期动态增加Partition,但是会冒着可能破坏Message Key和Partition之间对应关系的风险
    * Replica的数量不要过多,如果条件允许尽量把Replica集合内的Partition分别调整到不同的Rack
    * 尽一切努力保证每次停Broker时都可以Clean Shutdown,否则问题就不仅仅是恢复服务所需时间长,还可能出现数据损坏或其他很诡异的问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    3.Producer
    在Producer端的优化大部分消息系统采取的方式都比较单一,无非也就化零为整,同步变异步这么几种

    Kafka系统默认支持MessageSet,把多条Message自动地打成一个Group后发送出去,均摊后拉低了每次通信的RTT.而且在组织MessageSet的同时,还可以把数据重新排序,从爆发流式的随机写入优化成较为平稳的线性写入.

    此外,还要着重介绍的一点是,Producer支持End-to-End的压缩.数据在本地压缩后放到网络上传输,在Broker一般不解压(除非指定要Deep-Iteration),直至消息被Consume之后在客户端解压.当然用户也可以选择自己在应用层上做压缩和解压的工作(毕竟Kafka目前支持的压缩算法有限,只有GZIP和Snappy),不过这样做反而会意外的降低效率!!!! Kafka的End-to-End压缩与MessageSet配合在一起工作效果最佳,上面的做法直接割裂了两者间联系.至于道理其实很简单,压缩算法中一条基本的原理"重复的数据量越多,压缩比越高".无关于消息体的内容,无关于消息体的数量,大多数情况下输入数据量大一些会取得更好的压缩比.

    不过Kafka采用MessageSet也导致在可用性上一定程度的妥协.每次发送数据时,Producer都是send()之后就认为已经发送出去了,但其实大多数情况下消息还在内存的MessageSet当中,尚未发送到网络,这时候如果Producer挂掉,那就会出现丢数据的情况

    为了解决这个问题,Kafka在0.8版本的设计借鉴了网络当中的ack机制.如果对性能要求较高,又能在一定程度上允许Message的丢失,那就可以设置request.required.acks=0 来关闭ack,以全速发送.如果需要对发送的消息进行确认,就需要设置request.required.acks为1或-1,那么1和-1又有什么区别呢?这里又要提到前面聊的有关Replica数量问题.如果配置为1,表示消息只需要被Leader接收并确认即可,其他的Replica可以进行异步拉取无需立即进行确认,在保证可靠性的同时又不会把效率拉得很低.如果设置为-1,表示消息要Commit到该Partition的ISR集合中的所有Replica后,才可以返回ack,消息的发送会更安全,而整个过程的延迟会随着Replica的数量正比增长,这里就需要根据不同的需求做相应的优化

    拓展:
    * Producer的线程不要配置过多,尤其是在Mirror或者Migration中使用的时候,会加剧目标集群Partition消息乱序的情况(如果你的应用场景对消息顺序很敏感的话)
    * 0.8版本的request.required.acks默认是0(同0.7)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    4.Consumer
    Consumer端的设计大体上还算是比较常规的
    通过Consumer Group,可以支持生产者消费者和队列访问两种模式:

    Consumer API分为High level和Low level两种:
    第一种重度依赖Zookeeper,所以性能差一些且不自由,但是超省心.
    第二种不依赖Zookeeper服务,无论从自由度和性能上都有更好的表现,但是所有的异常(Leader迁移、Offset越界、Broker宕机等)和Offset的维护都需要自行处理

    发布的0.9 Release。开发人员又用Java重写了一套Consumer.把两套API合并在一起,同时去掉了对Zookeeper的依赖.据说性能有大幅度提升

    拓展:
    强烈推荐使用Low level API,虽然繁琐一些,但是目前只有这个API可以对Error数据进行自定义处理,尤其是处理Broker异常或由于Unclean Shutdown导致的Corrupted Data时,否则无法Skip只能等着"坏消息"在Broker上被Rotate掉,在此期间该Replica将会一直处于不可用状态

mysql use memory as disk

mysql使用内存作为磁盘存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.创建挂载点
mkdir -p /path/to/ram_data
2.挂载目录
mount tmpfs /path/to/ram_data -t tmpfs -o size=160G (size指定的是能供使用的内存的上限)
3.授权
chown mysql.mysql /path/to/ram_data
4.修改数据库配置文件
在mysql配置文件中将mysql的数据目录路径配置为上面新建的文件路径下.这样,向/path/to/ram_data里写的文件其实都是写在内存而不是硬盘上了.
这会极大的提升mysql数据库中文件的IO性能.
5.开机自启动
将"mount tmpfs /path/to/ram_data -t tmpfs -o size=160G"添加到"/etc/rc.local",这样每次开机就会自动挂载该目录

注意:
由于是内存盘存储的mysql数据,所以系统重启后mysql中的数据会全部丢失,需要手动将数据文件重新复制到指定挂载目录下再启动数据库,切记!

简单的讲就是把内存当作磁盘分区挂载上可以大大提高数据库的IO
这样做的好处是,数据库的速度不存在io瓶颈,但是服务器不能重启
上面的示例已经在生产环境中经过长期使用验证了其实际性能是可靠的!!!

drbd install

DRBD 介绍

  1. DRBD是什么

    1
    DRBD,叫做分布式复制块设备,这是一种基于软件,无共享,复制的解决方案.在服务器之间的块设备(包括硬盘,分区,逻辑卷)进行镜像.也就是说当某一个应用程序完成写操作后,它提交的数据不仅仅会保存在本地块设备上,DRBD也会将这份数据复制一份,通过网络传输到另一个节点的块设备上,这样,两个节点上的块设备上的数据将会保存一致,这就是镜像功能
  2. DRBD特性

    1
    2
    3
    * 实时性: 当某个应用程序完成对数据的修改时,复制功能立即发生
    * 透明性: 应用程序的数据存储在镜像块设备上是独立透明的,他们的数据在两个节点上都保存一份,因此,无论哪一台服务器宕机,都不会影响应用程序读取数据的操作,所以说是透明的
    * 同步镜像和异步镜像: 同步镜像表示当应用程序提交本地的写操作后,数据后会同步写到两个节点上去;异步镜像表示当应用程序提交写操作后,只有当本地的节点上完成写操作后,另一个节点才可以完成写操作
  3. DRBD用户空间管理工具

    1
    2
    3
    4
    为了能够配置和管理DRBD的资源,DRBD提供了一些管理工具与内核模块进行通信:
    * drbdadm: 高级的DRBD程序管理套件工具.它从配置文件/etc/drbd.conf中获取所有配置参数.drbdadm为drbdsetup和drbdmeta两个命令充当程序的前端应用,执行drbdadm实际是执行的drbdsetup和drbdeta两个命令
    * drbdsetup: drbdsetup可以让用户配置已经加载在内核中运行的DRBD模块,它是底层的DRBD程序管理套件工具.使用该命令时,所有的配置参数都需要直接在命令行中定义,虽然命令很灵活,但是大大的降低了命令的简单易用性,因此很多的用户很少使用drbdsetup
    * drbdmeta: drbdmeta允许用户创建、转储、还原和修改drbd的元数据结构.这个命令也是用户极少用到
  4. DRBD模式

    1
    2
    3
    4
    5
    6
    7
    DRBD有2中模式: 一种是DRBD的主从模式;另一种是DRBD的双主模式
    1.DRBD的主从模式
    这种模式下,其中一个节点作为主节点,另一个节点作为从节点.其中主节点可以执行读,写操作;从节点不可以挂载文件系统,因此,也不可以执行读写操作.在这种模式下,资源在任何时间只能存储在主节点上.这种模式可用在任何的文件系统上(EXT3、EXT4、XFS等等).默认这种模式下,一旦主节点发生故障,从节点需要手工将资源进行转移,且主节点变成从节点和从节点变成主节点需要手动进行切换.不能自动进行转移,因此比较麻烦.
    为了解决手动将资源和节点进行转移,可以将DRBD做成高可用集群的资源代理(RA),这样一旦其中的一个节点宕机,资源会自动转移到另一个节点,从而保证服务的连续性
    2.DRBD的双主模式
    这是DRBD8.0之后的新特性
    在双主模式下,任何资源在任何特定的时间都存在两个主节点.这种模式需要一个共享的集群文件系统,利用分布式的锁机制进行管理,如GFS和OCFS2.部署双主模式时,DRBD可以是负载均衡的集群,这就需要从两个并发的主节点中选取一个首选的访问数据.这种模式默认是禁用的,如果要是用的话必须在配置文件中进行声明.
  5. DRBD同步协议

    1
    2
    3
    4
    5
    6
    7
    DRBD的复制功能就是将应用程序提交的数据一份保存在本地节点,一份复制传输保存在另一个节点上.但是DRBD需要对传输的数据进行确认以便保证另一个节点的写操作完成,就需要用到DRBD的同步协议,DRBD同步协议有三种:

    * 协议A: 数据在本地完成写操作且数据已经发送到TCP/IP协议栈的队列中,则认为写操作完成.如果本地节点的写操作完成,此时本地节点发生故障,而数据还处在TCP/IP队列中,则数据不会发送到对端节点上.因此,两个节点的数据将不会保持一致.这种协议虽然高效,但是并不能保证数据的可靠性

    * 协议B: 数据在本地完成写操作且数据已到达对端节点则认为写操作完成.如果两个节点同时发生故障,即使数据到达对端节点,这种方式同样也会导致在对端节点和本地节点的数据不一致现象,也不具有可靠性

    * 协议C: 只有当本地节点的磁盘和对端节点的磁盘都完成了写操作,才认为写操作完成.这是集群流行的一种方式,应用也是最多的,这种方式虽然不高效,但是最可靠
  6. DRBD资源

    1
    2
    3
    4
    5
    在DRBD中,资源是所有可复制移动存储设备的总称,它包括:
    * 资源名称: 资源名称可以是除了空白字符以外的任意ASCII码字符
    * DRBD设备: DRBD的虚拟块设备.在双方节点上,DRBD设备的设备文件命名方式,一般为/dev/drbdN,其主设备号147,N是次设备号
    * 磁盘配置:DRBD内部应用需要本地数据副本,元数据.在双方节点上,为各自提供的存储设备
    * 网络配置: 双方数据同步时所使用的网络属性
  7. DRBD配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    DRBD的主配置文件为/etc/drbd.conf
    为了管理的便捷性,目前通常会将些配置文件分成多个部分,且都保存至/etc/drbd.d目录中,主配置文件中仅使用"include"指令将这些配置文件片断整合起来.通常,/etc/drbd.d目录中的配置文件为global_common.conf和所有以.res结尾的文件.其中global_common.conf中主要定义global段和common段,而每一个.res的文件用于定义一个资源.

    在配置文件中,global段仅能出现一次,且如果所有的配置信息都保存至同一个配置文件中而不分开为多个文件的话,global段必须位于配置文件的最开始处.目前global段中可以定义的参数仅有minor-count, dialog-refresh, disable-ip-verification和usage-count

    common段则用于定义被每一个资源默认继承的参数,可以在资源定义中使用的参数都可以在common段中定义.实际应用中,common段并非必须,但建议将多个资源共享的参数定义为common段中的参数以降低配置文件的复杂度

    resource段则用于定义drbd资源,每个资源通常定义在一个单独的位于/etc/drbd.d目录中的以.res结尾的文件中.资源在定义时必须为其命名,名字可以由非空白的ASCII字符组成.每一个资源段的定义中至少要包含两个host子段,以定义此资源关联至的节点,其它参数均可以从common段或drbd的默认中进行继承而无须定义
  8. DRBD配置文件信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    [root@ha1 ~]# cat /etc/drbd.d/global_common.conf
    global {
    usage-count yes; //DRBD用于统计应用各个版本的信息.当新的版本的drbd被安装就会和http server进行联系.当然也可以禁用该选项,默认情况下是启动
    # minor-count dialog-refresh disable-ip-verification //这里是global可以使用的参数
    # minor-count:32 //从(设备)个数,取值范围1~255,默认值为32。该选项设定了允许定义的resource个数,当要定义的resource超过了此选项的设定时,需要重新载入drbd内核模块。
    #disable-ip-verification:no //是否禁用ip检查
    }

    common {
    protocol C; //指定复制协议,复制协议共有三种,为协议A,B,C,默认协议为协议C
    handlers { //该配置段用来定义一系列处理器,用来回应特定事件。
    # These are EXAMPLE handlers only.
    # They may have severe implications,
    # like hard resetting the node under certain circumstances.
    # Be careful when chosing your poison.
    # pri-on-incon-degr "/usr/lib/drbd/notify-pri-on-incon-degr.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    # pri-lost-after-sb "/usr/lib/drbd/notify-pri-lost-after-sb.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    # local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo o > /proc/sysrq-trigger ; halt -f";
    # fence-peer "/usr/lib/drbd/crm-fence-peer.sh";
    # split-brain "/usr/lib/drbd/notify-split-brain.sh root";
    # out-of-sync "/usr/lib/drbd/notify-out-of-sync.sh root";
    # before-resync-target "/usr/lib/drbd/snapshot-resync-target-lvm.sh -p 15 -- -c 16k";
    # after-resync-target /usr/lib/drbd/unsnapshot-resync-target-lvm.sh;
    }

    startup { //该配置段用来更加精细地调节drbd属性,它作用于配置节点在启动或重启时。常用选项有:
    # wfc-timeout degr-wfc-timeout outdated-wfc-timeout wait-after-sb
    // wfc-timeout:该选项设定一个时间值,单位是秒。在启用DRBD块时,初始化脚本drbd会阻塞启动进程的运行,直到对等节点的出现。该选项就是用来限制这个等待时间的,默认为0,即不限制,永远等待。
    // degr-wfc-timeout:该选项也设定一个时间值,单位为秒。也是用于限制等待时间,只是作用的情形不同:它作用于一个降级集群(即那些只剩下一个节点的集群)在重启时的等待时间。
    // outdated-wfc-timeout:同上,也是用来设定等待时间,单位为秒。它用于设定等待过期节点的时间
    }

    disk {
    # on-io-error fencing use-bmbv no-disk-barrier no-disk-flushes 这里是disk段内可以定义的参数
    # no-disk-drain no-md-flushes max-bio-bvecs 这里是disk段内可以定义的参数
    on-io-error: detach
    //此选项设定了一个策略,如果底层设备向上层设备报告发生I/O错误,将按照该策略进行处理.有效的策略包括:
    //detach: 发生I/O错误的节点将放弃底层设备,以diskless mode继续工作.在diskless mode下,只要还有网络连接,drbb从secondary node读写数据,而不需要failover(故障转移).该策略会导致一定的损失,但好处也很明显,drbd服务不会中断.官方推荐和默认策略。
    //pass_on: 把I/O错误报告给上层设备.如果错误发生在primary节点,把它报告给文件系统,由上层设备处理这些错误(例如,它会导致文件系统以只读方式重新挂载),它可能会导致drbd停止提供服务;如果发生在secondary节点,则忽略该错误(因为secondary节点没有上层设备可以报告).该策略曾经是默认策略,但现在已被detach所取代
    //call-local-io-error: 调用预定义的本地local-io-error脚本进行处理.该策略需要在resource(或common)配置段的handlers部分,预定义一个相应的local-io-error命令调用.该策略完全由管理员通过local-io-error命令(或脚本)调用来控制如何处理I/O错误

    fencing:dont-care
    //该选项设定一个策略来避免split brain的状况。有效的策略包括:
    //dont-care:默认策略。不采取任何隔离措施。
    //resource-only:在此策略下,如果一个节点处于split brain状态,它将尝试隔离对端节点的磁盘。这个操作通过调用fence-peer处理器来实现。fence-peer处理器将通过其它通信路径到达对等节点,并在这个对等节点上调用drbdadm outdate res命令
    //resource-and-stonith:在此策略下,如果一个节点处于split brain状态,它将停止I/O操作,并调用fence-peer处理器。处理器通过其它通信路径到达对等节点,并在这个对等节点上调用drbdadm outdate res命令。如果无法到达对等节点,它将向对等端发送关机命令。一旦问题解决,I/O操作将重新进行。如果处理器失败,你可以使用resume-io命令来重新开始I/O操作
    }

    net { //该配置段用来精细地调节drbd的属性,网络相关的属性。常用的选项有:
    # sndbuf-size rcvbuf-size timeout connect-int ping-int ping-timeout max-buffers 这里是net段内可以定义的参数
    # max-epoch-size ko-count allow-two-primaries cram-hmac-alg shared-secret 这里是net段内可以定义的参数
    # after-sb-0pri after-sb-1pri after-sb-2pri data-integrity-alg no-tcp-cork 这里是net段内可以定义的参数
    sndbuf-size:该选项用来调节TCP send buffer的大小,drbd 8.2.7以前的版本,默认值为0,意味着自动调节大小;新版本的drbd的默认值为128KiB。高吞吐量的网络(例如专用的千兆网卡,或负载均衡中绑定的连接)中,增加到512K比较合适,或者可以更高,但是最好不要超过2M。

    timeout:该选项设定一个时间值,单位为0.1秒。如果搭档节点没有在此时间内发来应答包,那么就认为搭档节点已经死亡,因此将断开这次TCP/IP连接。默认值为60,即6秒。该选项的值必须小于connect-int和ping-int的值。

    connect-int:如果无法立即连接上远程DRBD设备,系统将断续尝试连接。该选项设定的就是两次尝试间隔时间。单位为秒,默认值为10秒。

    ping-timeout:该选项设定一个时间值,单位是0.1秒。如果对端节点没有在此时间内应答keep-alive包,它将被认为已经死亡.默认值是500ms

    max-buffers:该选项设定一个由drbd分配的最大请求数,单位是页面大小(PAGE_SIZE),大多数系统中,页面大小为4KB。这些buffer用来存储那些即将写入磁盘的数据。最小值为32(即128KB)。这个值大一点好。

    max-epoch-size:该选项设定了两次write barriers之间最大的数据块数。如果选项的值小于10,将影响系统性能。大一点好

    ko-count:该选项设定一个值,把该选项设定的值 乘以 timeout设定的值,得到一个数字N,如果secondary节点没有在此时间内完成单次写请求,它将从集群中被移除(即,primary node进入StandAlong模式)。取值范围0~200,默认值为0,即禁用该功能。

    allow-two-primaries:这个是drbd8.0及以后版本才支持的新特性,允许一个集群中有两个primary node。该模式需要特定文件系统的支撑,目前只有OCFS2和GFS可以,传统的ext3、ext4、xfs等都不行!

    cram-hmac-alg:该选项可以用来指定HMAC算法来启用对端节点授权。drbd强烈建议启用对端点授权机制。可以指定/proc/crypto文件中识别的任一算法。必须在此指定算法,以明确启用对端节点授权机制,实现数据加密传输。

    shared-secret:该选项用来设定在对端节点授权中使用的密码,最长64个字符。

    data-integrity-alg:该选项设定内核支持的一个算法,用于网络上的用户数据的一致性校验。通常的数据一致性校验,由TCP/IP头中所包含的16位校验和来进行,而该选项可以使用内核所支持的任一算法。该功能默认关闭。
    }

    syncer { 该配置段用来更加精细地调节服务的同步进程。常用选项有
    # rate after al-extents use-rle cpu-mask verify-alg csums-alg
    rate:设置同步时的速率,默认为250KB。默认的单位是KB/sec,也允许使用K、M和G,如40M。注意:syncer中的速率是以bytes,而不是bits来设定的。配置文件中的这个选项设置的速率是永久性的,但可使用下列命令临时地改变rate的值:drbdsetup /dev/drbdN syncer -r 100M。如果想重新恢复成drbd.conf配置文件中设定的速率,执行如下命令: drbdadm adjust resource

    verify-alg:该选项指定一个用于在线校验的算法,内核一般都会支持md5、sha1和crc32c校验算法。在线校验默认关闭,必须在此选项设定参数,以明确启用在线设备校验。DRBD支持在线设备校验,它以一种高效的方式对不同节点的数据进行一致性校验。在线校验会影响CPU负载和使用,但影响比较轻微。drbd 8.2.5及以后版本支持此功能。一旦启用了该功能,你就可以使用下列命令进行一个在线校验: drbdadm verify resource。该命令对指定的resource进行检验,如果检测到有数据块没有同步,它会标记这些块,并往内核日志中写入一条信息。这个过程不会影响正在使用该设备的程序。
    如果检测到未同步的块,当检验结束后,你就可以如下命令重新同步它们:drbdadm disconnect resource or drbdadm connetc resource
    }
    }


    common段是用来定义共享的资源参数,以减少资源定义的重复性。common段是非必须的。resource段一般为DRBD上每一个节点来定义其资源参数的。
    资源配置文件详解


    [root@ha1 ~]# cat /etc/drbd.d/web.res
    resource web { web为资源名称
    on ha1.xsl.com { on后面为节点的名称,有几个节点就有几个on段,这里是定义节点ha1.xsl.com上的资源
    device /dev/drbd0; 定义DRBD虚拟块设备,这个设备不要事先不要格式化
    disk /dev/sda6; 定义存储磁盘为/dev/sda6,该分区创建完成之后就行了,不要进行格式化操作
    address 192.168.108.199:7789; 定义DRBD监听的地址和端口,以便和对端进行通信
    meta-disk internal; 该参数有2个选项:internal和externally,其中internal表示将元数据和数据存储在同一个磁盘上,而externally表示将元数据和数据分开存储,元数据被放在另一个磁盘上。
    }
    on ha2.xsl.com { 这里是定义节点ha2.xsl.com上的资源
    device /dev/drbd0;
    disk /dev/sda6;
    address 192.168.108.201:7789;
    meta-disk internal;
    }
    }
  9. DRBD安装步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    * 安装drbd
    * 配置资源文件(定义资料名称,磁盘,节点信息,同步限制等)
    * 将drbd加入到系统服务chkconfig --add drbd
    * 初始化资源组drbdadm create-md resource_name
    * 启动服务 service drbd start
    * 设置primary主机,并同步数据
    * 分区、格式化/dev/drbd*
    * 一个节点进行挂载
    * 查看状态

DRBD install

  1. DRBD安装

    1
    yum -y install drbd84 kmod-drbd84
  2. DRBD主配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    [root@node1 ~]# vim /etc/drbd.conf #查看主配置文件
    # You can find an example in /usr/share/doc/drbd.../drbd.conf.example
    include "drbd.d/global_common.conf";
    include "drbd.d/*.res";
    [root@node1 ~]# cat /etc/drbd.d/global_common.conf #查看主配置文件
    global {
    usage-count yes;
    # minor-count dialog-refresh disable-ip-verification
    }
    common {
    handlers {
    pri-on-incon-degr "/usr/lib/drbd/notify-pri-on-incon-degr.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    pri-lost-after-sb "/usr/lib/drbd/notify-pri-lost-after-sb.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo o > /proc/sysrq-trigger ; halt -f";
    # fence-peer "/usr/lib/drbd/crm-fence-peer.sh";
    # split-brain "/usr/lib/drbd/notify-split-brain.sh root";
    # out-of-sync "/usr/lib/drbd/notify-out-of-sync.sh root";
    # before-resync-target "/usr/lib/drbd/snapshot-resync-target-lvm.sh -p 15 -- -c 16k";
    # after-resync-target /usr/lib/drbd/unsnapshot-resync-target-lvm.sh;
    }
    startup {
    # wfc-timeout degr-wfc-timeout outdated-wfc-timeout wait-after-sb
    }
    options {
    # cpu-mask on-no-data-accessible
    }
    disk {
    # size max-bio-bvecs on-io-error fencing disk-barrier disk-flushes
    # disk-drain md-flushes resync-rate resync-after al-extents
    # c-plan-ahead c-delay-target c-fill-target c-max-rate
    # c-min-rate disk-timeout
    }
    net {
    # protocol timeout max-epoch-size max-buffers unplug-watermark
    # connect-int ping-int sndbuf-size rcvbuf-size ko-count
    # allow-two-primaries cram-hmac-alg shared-secret after-sb-0pri
    # after-sb-1pri after-sb-2pri always-asbp rr-conflict
    # ping-timeout data-integrity-alg tcp-cork on-congestion
    # congestion-fill congestion-extents csums-alg verify-alg
    # use-rle
    }
    }
  3. DRBD 全局配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    [root@node1 ~]# vim /etc/drbd.d/global_common.conf  
    global {
    usage-count no; #让linbit公司收集目前drbd的使用情况,yes为参加,我们这里不参加设置为no
    # minor-count dialog-refresh disable-ip-verification
    }
    common {
    handlers {
    pri-on-incon-degr "/usr/lib/drbd/notify-pri-on-incon-degr.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    pri-lost-after-sb "/usr/lib/drbd/notify-pri-lost-after-sb.sh; /usr/lib/drbd/notify-emergency-reboot.sh; echo b > /proc/sysrq-trigger ; reboot -f";
    local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo o > /proc/sysrq-trigger ; halt -f";
    # fence-peer "/usr/lib/drbd/crm-fence-peer.sh";
    # split-brain "/usr/lib/drbd/notify-split-brain.sh root";
    # out-of-sync "/usr/lib/drbd/notify-out-of-sync.sh root";
    # before-resync-target "/usr/lib/drbd/snapshot-resync-target-lvm.sh -p 15 -- -c 16k";
    # after-resync-target /usr/lib/drbd/unsnapshot-resync-target-lvm.sh;
    }
    startup {
    # wfc-timeout degr-wfc-timeout outdated-wfc-timeout wait-after-sb
    }
    options {
    # cpu-mask on-no-data-accessible
    }
    disk {
    # size max-bio-bvecs on-io-error fencing disk-barrier disk-flushes
    # disk-drain md-flushes resync-rate resync-after al-extents
    # c-plan-ahead c-delay-target c-fill-target c-max-rate
    # c-min-rate disk-timeout
    on-io-error detach; #同步错误的做法是分离
    }
    net {
    # protocol timeout max-epoch-size max-buffers unplug-watermark
    # connect-int ping-int sndbuf-size rcvbuf-size ko-count
    # allow-two-primaries cram-hmac-alg shared-secret after-sb-0pri
    # after-sb-1pri after-sb-2pri always-asbp rr-conflict
    # ping-timeout data-integrity-alg tcp-cork on-congestion
    # congestion-fill congestion-extents csums-alg verify-alg
    # use-rle
    cram-hmac-alg "sha1"; #设置加密算法sha1
    shared-secret "mydrbdlab"; #设置加密key
    }
    }
  4. DRBD 增加资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@node1 drbd.d]# cat web.res  
    resource web {
    on node1.test.com {
    device /dev/drbd0;
    disk /dev/sdb;
    address 192.168.1.201:7789;
    meta-disk internal;
    }
    on node2.test.com {
    device /dev/drbd0;
    disk /dev/sdb;
    address 192.168.1.202:7789;
    meta-disk internal;
    }
    }
  5. 将配置文件global_common.conf web.res同步到node2

  6. node1与node2上初始化资源

    1
    [root@nodex ~]# drbdadm create-md web
  7. node1与node2上启动DRBD服务

    1
    [root@nodex ~]# systemctl start drbd
  8. node1与node2查看状态

    1
    2
    [root@nodex ~]# cat /proc/drbd
    [root@nodex ~]# drbd-overview
  9. 将node1设置为主节点

    1
    2
    3
    4
    5
    [root@node1 ~]# drbdsetup /dev/drbd0 primary –o
    or
    [root@node1 ~]# drbdadm -- --overwrite-data-of-peer primary web

    [root@node1 ~]# drbd-overview
  10. DRBD格式化并挂载

    1
    2
    3
    4
    5
    6
    [root@node1 ~]# mke2fs -j /dev/drbd0

    [root@node1 ~]# mkdir /drbd
    [root@node1 ~]# mount /dev/drbd0 /drbd/
    [root@node1 ~]# mount |grep drdb0
    /dev/drbd0 on /drbd type ext3 (rw)
  11. DRBD 主从切换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    对主Primary/Secondary模型的drbd服务来讲,在某个时刻只能有一个节点为Primary.因此,要切换两个节点的角色,只能在先将原有的Primary节点设置为Secondary后,才能原来的Secondary节点设置为Primary

    node1:
    [root@node1 ~]# umount /drbd/
    [root@node1 ~]# drbdadm secondary web
    [root@node1 ~]# drbd-overview
    0:web/0 Connected Secondary/Secondary UpToDate/UpToDate C r-----

    node2:
    [root@node2 ~]# drbdadm primary web
    [root@node2 ~]# drbd-overview
    0:web/0 Connected Primary/Secondary UpToDate/UpToDate C r-----
    [root@node2 ~]# mkdir /drbd
    [root@node2 ~]# mount /dev/drbd0 /drbd/
  12. DRBD 双主模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [root@node ~]# drbdadm primary --force resource
    配置资源双主模型的示例:
    resource mydrbd {
    net {
    protocol C;
    allow-two-primaries yes;
    }
    startup {
    become-primary-on both;
    }
    disk {
    fencing resource-and-stonith;
    }
    handlers {
    # Make sure the other node is confirmed
    # dead after this!
    outdate-peer "/sbin/kill-other-node.sh";
    }
    on node1.magedu.com {
    device /dev/drbd0;
    disk /dev/vg0/mydrbd;
    address 172.16.200.11:7789;
    meta-disk internal;
    }
    on node2.magedu.com {
    device /dev/drbd0;
    disk /dev/vg0/mydrbd;
    address 172.16.200.12:7789;
    meta-disk internal;
    }
    }

make提高源码编译速度

make 提交源码编译速度

1
2
3
4
5
6
7
8
9
10
make 默认是以单核进行编译,速度很慢,需要增加一个参数"-j(表示指定内核参数)",提交编译速度.

例如:
[root@10 app]# tar xzf otp_src_19.2.tar.gz
[root@10 app]# cd otp_src_19.2/
[root@10 otp_src_19.2]# ./configure --prefix=/mnt/app/erlang
[root@10 otp_src_19.2]# make -j 8
[root@10 otp_src_19.2]# make -j 8 install

上面的"make -j 8" 表示使用8核CPU进行编译

keepalived install

keepalived install

Download

  1. keepalived 介绍

    1
    2
    Keepalived是一个基于VRRP协议来实现的LVS服务高可用方案,可以利用其来避免单点故障.
    一个LVS服务会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候,备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性
  2. VRRP协议简介

    1
    2
    3
    4
    5
    在现实的网络环境中,两台需要通信的主机大多数情况下并没有直接的物理连接.对于这样的情况,它们之间路由怎样选择?主机如何选定到达目的主机的下一跳路由,这个问题通常的解决方法有二种:
    * 在主机上使用动态路由协议(RIP、OSPF等)
    * 在主机上配置静态路由

    很明显,在主机上配置动态路由是非常不切实际的,因为管理,维护成本以及是否支持等诸多问题.配置静态路由就变得十分流行,但路由器(或者说默认网关default gateway)却经常成为单点故障.VRRP的目的就是为了解决"静态路由单点故障"问题,VRRP通过一竞选(election)协议来动态的将路由任务交给LAN中虚拟路由器中的某台VRRP路由器
  3. VRRP工作机制

    1
    2
    3
    4
    5
    在一个VRRP虚拟路由器中,有多台物理的VRRP路由器,但是这多台的物理的机器并不能同时工作,而是由一台称为MASTER的负责路由工作,其它的都是BACKUP,MASTER并非一成不变,VRRP让每个VRRP路由器参与竞选,最终获胜的就是MASTER.MASTER拥有一些特权,比如:拥有虚拟路由器的IP地址,我们的主机就是用这个IP地址作为静态路由的.拥有特权的MASTER要负责转发发送给网关地址的包和响应ARP请求

    VRRP通过竞选协议来实现虚拟路由器的功能,所有的协议报文都是通过IP多播(multicast)包(多播地址224.0.0.18)形式发送的.虚拟路由器由VRID(范围0-255)和一组IP地址组成,对外表现为一个周知的MAC地址.所以,在一个虚拟路由器中,不管谁是MASTER,对外都是相同的MAC和IP(称之为VIP).客户端主机并不需要因为MASTER的改变而修改自己的路由配置,对客户端来说,这种主从的切换是透明的.

    在一个虚拟路由器中,只有作为MASTER的VRRP路由器会一直发送VRRP通告信息(VRRPAdvertisement message),BACKUP不会抢占MASTER,除非它的优先级(priority)更高.当MASTER不可用时(BACKUP收不到通告信息),多台BACKUP中优先级最高的这台会被抢占为MASTER.这种抢占是非常快速的(<1s),以保证服务的连续性.由于安全性考虑,VRRP包使用了加密协议进行加密.
  4. VRRP工作流程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    1.初始化:    
    路由器启动时,如果路由器的优先级是255(最高优先级,路由器拥有路由器地址),要发送VRRP通告信息,并发送广播ARP信息通告路由器IP地址对应的MAC地址为路由虚拟MAC,设置通告信息定时器准备定时发送VRRP通告信息,转为MASTER状态;否则进入BACKUP状态,设置定时器检查定时检查是否收到MASTER的通告信息
    2.Master
    * 设置定时通告定时器
    * 用VRRP虚拟MAC地址响应路由器IP地址的ARP请求
    * 转发目的MAC是VRRP虚拟MAC的数据包
    * 如果是虚拟路由器IP的拥有者,将接受目的地址是虚拟路由器IP的数据包,否则丢弃
    * 当收到shutdown的事件时删除定时通告定时器,发送优先权级为0的通告包,转初始化状态
    * 如果定时通告定时器超时时,发送VRRP通告信息
    * 收到VRRP通告信息时,如果优先权为0,发送VRRP通告信息,否则判断数据的优先级是否高于本机,或相等而且实际IP地址大于本地实际IP,设置定时通告定时器,复位主机超时定时器,转BACKUP状态;否则的话,丢弃该通告包
    3.Backup
    * 设置主机超时定时器
    * 不能响应针对虚拟路由器IP的ARP请求信息
    * 丢弃所有目的MAC地址是虚拟路由器MAC地址的数据包
    * 不接受目的是虚拟路由器IP的所有数据包
    * 当收到shutdown的事件时删除主机超时定时器,转初始化状态
    * 主机超时定时器超时的时候,发送VRRP通告信息,广播ARP地址信息,转MASTER状态
    * 收到VRRP通告信息时,如果优先权为0,表示进入MASTER选举;否则判断数据的优先级是否高于本机,如果高的话承认MASTER有效,复位主机超时定时器;否则的话,丢弃该通告包
  5. ARP查询处理

    1
    当内部主机通过ARP查询虚拟路由器IP地址对应的MAC地址时,MASTER路由器回复的MAC地址为虚拟的VRRP的MAC地址,而不是实际网卡的MAC地址,这样在路由器切换时让内网机器觉察不到;而在路由器重新启动时,不能主动发送本机网卡的实际MAC地址.如果虚拟路由器开启的ARP代理(proxy_arp)功能,代理的ARP回应也回应VRRP虚拟MAC地址

keepalived 安装

  1. keepalived master/slave install

    1
    yum -y install keepalived ipvsadm
  2. keepalived master conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    [root@master ~]# cat /etc/keepalived/keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com #配置管理员邮箱
    }
    notification_email_from root #配置发件人
    smtp_server 127.0.0.1 #配置邮件服务器
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_instance VI_1 {
    state MASTER #配置模式
    interface eth0
    virtual_router_id 51
    priority 101 #配置优先级
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200 #配置虚拟IP地址
    }
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    #persistence_timeout 50
    protocol TCP
    real_server 192.168.18.201 80 { #配置realaserver
    weight 1
    HTTP_GET { #监控配置
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    }
    sorry_server 127.0.0.1 80 #增加一行sorry_server
  3. keepalived slave conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    [root@slave keepalived]# cat keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com
    }
    notification_email_from root
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_instance VI_1 {
    state BACKUP #修改为BACKUP
    interface eth0
    virtual_router_id 51
    priority 100 #修改优先级
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200
    }
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    #persistence_timeout 50
    protocol TCP
    real_server 192.168.18.201 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    }
    sorry_server 127.0.0.1 80 #增加一行sorry_server
  4. keepalived master/slave start

    1
    2
    3
    4
    5
    [root@master ~]# systemctl start keepalived
    [root@master ~]# systemctl enable keepalived

    [root@slave ~]# systemctl start keepalived
    [root@slave ~]# systemctl enable keepalived
  5. 查看LVS状态

    1
    2
    3
    4
    5
    6
    [root@master ~]# ipvsadm -L -n  
    IP Virtual Server version 1.2.1 (size=4096)
    Prot LocalAddress:Port Scheduler Flags
    -> RemoteAddress:Port Forward Weight ActiveConn InActConn
    TCP 192.168.18.200:80 rr
    -> 192.168.18.202:80 Route 1 0 0
  6. 通过stop keepalived 服务进行验证


keepalived 在不关闭keepalived情况下实现维护

  1. keepalived master conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    [root@master ~]# cat /etc/keepalived/keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com
    }
    notification_email_from root
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_script chk_schedown { #定义vrrp执行脚本
    script "[ -e /etc/keepalived/down ] && exit 1 || exit 0" #查看是否有down文件,有就进入维护模式
    interval 1 #监控间隔时间
    weight -5 #降低优先级
    fall 2 #失败次数
    rise 1 #成功数次
    }
    vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200
    }
    track_script { #执行脚本
    chk_schedown
    }
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    protocol TCP
    real_server 192.168.18.201 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    sorry_server 127.0.0.1 80
    }
  2. keepalived slave conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    [root@slave ~]# cat /etc/keepalived/keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com
    }
    notification_email_from root
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_script chk_schedown {
    script "[ -e /etc/keepalived/down ] && exit 1 || exit 0"
    interval 1
    weight -5
    fall 2
    rise 1
    }
    vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200
    }
    track_script {
    chk_schedown
    }
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    protocol TCP
    real_server 192.168.18.201 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    sorry_server 127.0.0.1 80

3.测试

1
2
3
4
5
[root@master ~]# cd /etc/keepalived
[root@master keepalived ~]# touch down

[root@master keepalived ~]# tail -f /var/log/messages
[root@master keepalived ~]# ip add show


keepalived 邮件报警通知

  1. keepalived master/slave 编辑报警邮件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    [root@x ~]#vim /etc/keepalived/notify.sh
    #!/bin/bash
    # Author: freeloda
    # description: An example of notify script
    # Usage: notify.sh -m|--mode {mm|mb} -s|--service SERVICE1,... -a|--address VIP -n|--notify {master|backup|falut} -h|--help
    contact='smallasa@sina.com'
    helpflag=0
    serviceflag=0
    modeflag=0
    addressflag=0
    notifyflag=0
    Usage() {
    echo "Usage: notify.sh [-m|--mode {mm|mb}] [-s|--service SERVICE1,...] <-a|--address VIP> <-n|--notify {master|backup|falut}>"
    echo "Usage: notify.sh -h|--help"
    }
    ParseOptions() {
    local I=1;
    if [ $# -gt 0 ]; then
    while [ $I -le $# ]; do
    case $1 in
    -s|--service)
    [ $# -lt 2 ] && return 3
    serviceflag=1
    services=(`echo $2|awk -F"," '{for(i=1;i<=NF;i++) print $i}'`)
    shift 2 ;;
    -h|--help)
    helpflag=1
    return 0
    shift
    ;;
    -a|--address)
    [ $# -lt 2 ] && return 3
    addressflag=1
    vip=$2
    shift 2
    ;;
    -m|--mode)
    [ $# -lt 2 ] && return 3
    mode=$2
    shift 2
    ;;
    -n|--notify)
    [ $# -lt 2 ] && return 3
    notifyflag=1
    notify=$2
    shift 2
    ;;
    *)
    echo "Wrong options..."
    Usage
    return 7
    ;;
    esac
    done
    return 0
    fi
    }
    #workspace=$(dirname $0)
    RestartService() {
    if [ ${#@} -gt 0 ]; then
    for I in $@; do
    if [ -x /etc/rc.d/init.d/$I ]; then
    /etc/rc.d/init.d/$I restart
    else
    echo "$I is not a valid service..."
    fi
    done
    fi
    }
    StopService() {
    if [ ${#@} -gt 0 ]; then
    for I in $@; do
    if [ -x /etc/rc.d/init.d/$I ]; then
    /etc/rc.d/init.d/$I stop
    else
    echo "$I is not a valid service..."
    fi
    done
    fi
    }
    Notify() {
    mailsubject="`hostname` to be $1: $vip floating"
    mailbody="`date '+%F %H:%M:%S'`, vrrp transition, `hostname` changed to be $1."
    echo $mailbody | mail -s "$mailsubject" $contact
    }
    # Main Function
    ParseOptions $@
    [ $? -ne 0 ] && Usage && exit 5
    [ $helpflag -eq 1 ] && Usage && exit 0
    if [ $addressflag -ne 1 -o $notifyflag -ne 1 ]; then
    Usage
    exit 2
    fi
    mode=${mode:-mb}
    case $notify in
    'master')
    if [ $serviceflag -eq 1 ]; then
    RestartService ${services[*]}
    fi
    Notify master
    ;;
    'backup')
    if [ $serviceflag -eq 1 ]; then
    if [ "$mode" == 'mb' ]; then
    StopService ${services[*]}
    else
    RestartService ${services[*]}
    fi
    fi
    Notify backup
    ;;
    'fault')
    Notify fault
    ;;
    *)
    Usage
    exit 4
    ;;
    esac
    [root@x ~]#chmod +x /etc/keepalived/notify.sh
    [root@x ~]#/etc/keepalived/notify.sh -m mb -a 1.1.1.1 -n master


    help:
    -s, --service SERVICE,...:指定服务脚本名称,当状态切换时可自动启动,重启或关闭此服务
    -a, --address VIP: 指定相关虚拟路由器的VIP地址
    -m, --mode {mm|mb}:指定虚拟路由的模型,mm表示主主,mb表示主备;它们表示相对于同一种服务而方,其VIP的工作类型
    -n, --notify {master|backup|fault}:指定通知的类型,即vrrp角色切换的目标角色
    -h, --help:获取脚本的使用帮助;
  2. keepalived master conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    [root@master keepalived]# cat keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com
    }
    notification_email_from root
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_script chk_schedown {
    script "[ -e /etc/keepalived/down ] && exit 1 || exit 0"
    interval 1
    weight -5
    fall 2
    rise 1
    }
    vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 101
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200
    }
    track_script {
    chk_schedown
    }
    #增加以下三行
    notify_master "/etc/keepalived/notify.sh -n master -a 192.168.18.200"
    notify_backup "/etc/keepalived/notify.sh -n backup -a 192.168.18.200"
    notify_fault "/etc/keepalived/notify.sh -n fault -a 192.168.18.200"
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    protocol TCP
    real_server 192.168.18.201 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    sorry_server 127.0.0.1 80
    }
  3. keepalived slave conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    [root@slave keepalived]# cat keepalived.conf  
    ! Configuration File for keepalived
    global_defs {
    notification_email {
    smallasa@sina.com
    }
    notification_email_from root
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id LVS_DEVEL
    }
    vrrp_script chk_schedown {
    script "[ -e /etc/keepalived/down ] && exit 1 || exit 0"
    interval 1
    weight -5
    fall 2
    rise 1
    }
    vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
    auth_type PASS
    auth_pass 1111
    }
    virtual_ipaddress {
    192.168.18.200
    }
    track_script {
    chk_schedown
    }
    #增加以下三行
    notify_master "/etc/keepalived/notify.sh -n master -a 192.168.18.200"
    notify_backup "/etc/keepalived/notify.sh -n backup -a 192.168.18.200"
    notify_fault "/etc/keepalived/notify.sh -n fault -a 192.168.18.200"
    }
    virtual_server 192.168.18.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    nat_mask 255.255.255.0
    protocol TCP
    real_server 192.168.18.201 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    real_server 192.168.18.202 80 {
    weight 1
    HTTP_GET {
    url {
    path /
    status_code 200
    }
    connect_timeout 2
    nb_get_retry 3
    delay_before_retry 1
    }
    }
    sorry_server 127.0.0.1 80
    }

mysql innodb buffer pool flush

InnoDB buffer pool简介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数个内存块加上一组控制结构体对象组成.内存块的个数取决于buffer pool instance的个数,不过在5.7版本中开始默认以128M(可配置)的chunk单位分配内存块,这样做的目的是为了支持buffer pool的在线动态调整大小.

Buffer pool的每个内存块通过mmap的方式分配内存,因此你会发现,在实例启动时虚存很高,而物理内存很低.这些大片的内存块又按照16KB划分为多个frame,用于存储数据页.

虽然大多数情况下buffer pool是以16KB来存储数据页,但有一种例外: 使用压缩表时,需要在内存中同时存储压缩页和解压页,对于压缩页,使用Binary buddy allocator算法来分配内存空间.例如我们读入一个8KB的压缩页,就从buffer pool中取一个16KB的block,取其中8KB,剩下的8KB放到空闲链表上,如果紧跟着另外一个4KB的压缩页读入内存,就可以从这8KB中分裂4KB,同时将剩下的4KB放到空闲链表上.

为了管理buffer pool,每个buffer pool instance使用如下几个链表来管理:
* LRU链表包含所有读入内存的数据页
* Flush_list包含被修改过的脏页
* unzip_LRU包含所有解压页
* Free list上存放当前空闲的block

另外为了避免查询数据页时扫描LRU,还为每个buffer pool instance维护了一个page hash,通过space id和page no可以直接找到对应的page

一般情况下,当我们需要读入一个Page时,首先根据space id 和page no找到对应的buffer pool instance.然后查询page hash,如果page hash中没有,则表示需要从磁盘读取.在读盘前首先我们需要为即将读入内存的数据页分配一个空闲的block.当free list上存在空闲的block时,可以直接从free list上摘取,如果没有,就需要从unzip_lru 或者 lru上驱逐page.

这里需要遵循一定的原则(参考函数buf_LRU_scan_and_free_block , 5.7.5):
首先尝试从unzip_lru上驱逐解压页,如果没有,再尝试从Lru链表上驱逐Page;如果还是无法从Lru上获取到空闲block,用户线程就会参与刷脏,尝试做一次SINGLE PAGE FLUSH,单独从Lru上刷掉一个脏页,然后再重试.Buffer pool中的page被修改后,不是立刻写入磁盘,而是由后台线程定时写入,和大多数数据库系统一样,脏页的写盘遵循日志先行WAL原则,因此在每个block上都记录了一个最近被修改时的Lsn,写数据页时需要确保当前写入日志文件的redo不低于这个Lsn.

然而基于WAL原则的刷脏策略可能带来一个问题: 当数据库的写入负载过高时,产生redo log的速度极快,redo log可能很快到达同步checkpoint点.这时候需要进行刷脏来推进Lsn.由于这种行为是由用户线程在检查到redo log空间不够时触发,大量用户线程将可能陷入到这段低效的逻辑中,产生一个明显的性能拐点.

Page Cleaner线程

1
2
3
4
5
6
7
8
9
10
11
12
13
在MySQL5.6中,开启了一个独立的page cleaner线程来进行刷lru list 和flush list.默认每隔一秒运行一次,5.6版本里提供了一大堆的参数来控制page cleaner的flush行为,包括:
* innodb_adaptive_flushing_lwm
* innodb_max_dirty_pages_pct_lwm
* innodb_flushing_avg_loops
* innodb_io_capacity_max
* innodb_lru_scan_depth

如果你发现redo log推进的非常快,为了避免用户线程陷入刷脏,可以通过调大innodb_io_capacity_max来解决,该参数限制了每秒刷新的脏页上限,调大该值可以增加Page cleaner线程每秒的工作量.
如果你发现你的系统中free list不足,总是需要驱逐脏页来获取空闲的block时,可以适当调大innodb_lru_scan_depth.该参数表示从每个buffer pool instance的lru上扫描的深度,调大该值有助于多释放些空闲页,避免用户线程去做single page flush

为了提升扩展性和刷脏效率,在5.7.4版本里引入了多个page cleaner线程,从而达到并行刷脏的效果.目前Page cleaner并未和buffer pool绑定,其模型为一个协调线程 + 多个工作线程,协调线程本身也是工作线程.因此如果innodb_page_cleaners设置为4,那么就是一个协调线程,加3个工作线程,工作方式为生产者-消费者.工作队列长度为buffer pool instance的个数,使用一个全局slot数组表示.

协调线程在决定了需要flush的page数和lsn_limit后,会设置slot数组,将其中每个slot的状态设置为PAGE_CLEANER_STATE_REQUESTED,并设置目标page数及lsn_limit,然后唤醒工作线程(pc_request).工作线程被唤醒后,从slot数组中取一个未被占用的slot,修改其状态,表示已被调度,然后对该slot所对应的buffer pool instance进行操作.直到所有的slot都被消费完后,才进入下一轮.通过这种方式,多个page cleaner线程实现了并发flush buffer pool,从而提升flush dirty page/lru的效率.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
MySQL5.7的InnoDB flush策略优化
在之前版本中,因为可能同时有多个线程操作buffer pool刷page(在刷脏时会释放buffer pool mutex),每次刷完一个page后需要回溯到链表尾部,使得扫描bp链表的时间复杂度最差为O(N*N)

在5.6版本中针对Flush list的扫描做了一定的修复,使用一个指针来记录当前正在flush的page,待flush操作完成后,再看一下这个指针有没有被别的线程修改掉,如果被修改了,就回溯到链表尾部,否则无需回溯.但这个修复并不完整,在最差的情况下,时间复杂度依旧不理想

在5.7版本中对这个问题进行了彻底的修复,使用多个名为hazard pointer的指针,在需要扫描LIST时,存储下一个即将扫描的目标page,根据不同的目的分为几类:
* flush_hp: 用作批量刷FLUSH LIST
* lru_hp: 用作批量刷LRU LIST
* lru_scan_itr: 用于从LRU链表上驱逐一个可替换的page,总是从上一次扫描结束的位置开始,而不是LRU尾部
* single_scan_itr: 当buffer pool中没有空闲block时,用户线程会从FLUSH LIST上单独驱逐一个可替换的page 或者 flush一个脏页,总是从上一次扫描结束的位置开始,而不是LRU尾部

后两类的hp都是由用户线程在尝试获取空闲block时调用,只有在推进到某个buf_page_t::old被设置成true的page(大约从Lru链表尾部起至总长度的八分之三位置的page)时,再将指针重置到Lru尾部

这些指针在初始化buffer pool时分配,每个buffer pool instance都拥有自己的hp指针.当某个线程对buffer pool中的page进行操作时,例如需要从LRU中移除Page时,如果当前的page被设置为hp,就要将hp更新为当前Page的前一个page.当完成当前page的flush操作后,直接使用hp中存储的page指针进行下一轮flush.
1
2
3
4
5
6
7
8
9
10
11
12
社区优化:
一如既往的,Percona Server在5.6版本中针对buffer pool flush做了不少的优化,主要的修改包括如下几点:
* 优化刷LRU流程buf_flush_LRU_tail
* 该函数由page cleaner线程调用
原生的逻辑:依次flush每个buffer pool instance,每次扫描的深度通过参数innodb_lru_scan_depth来配置.而在每个instance内,又分成多个chunk来调用
修改后的逻辑为:每次flush一个buffer pool的LRU时,只刷一个chunk,然后再下一个instance,刷完所有instnace后,再回到前面再刷一个chunk.简而言之,把集中的flush操作进行了分散,其目的是分散压力,避免对某个instance的集中操作,给予其他线程更多访问buffer pool的机会.
* 允许设定刷LRU/FLUSH LIST的超时时间,防止flush操作时间过长导致别的线程(例如尝试做single page flush的用户线程)stall住;当到达超时时间时,page cleaner线程退出flush.
* 避免用户线程参与刷buffer pool
当用户线程参与刷buffer pool时,由于线程数的不可控,将产生严重的竞争开销,例如free list不足时做single page flush,以及在redo空间不足时,做dirty page flush,都会严重影响性能.Percona Server允许选择让page cleaner线程来做这些工作,用户线程只需要等待即可.出于效率考虑,用户还可以设置page cleaner线程的cpu调度优先级.另外在Page cleaner线程经过优化后,可以知道系统当前处于同步刷新状态,可以去做更激烈的刷脏(furious flush),用户线程参与到其中,可能只会起到反作用.
* 允许设置page cleaner线程,purge线程,io线程,master线程的CPU调度优先级,并优先获得InnoDB的mutex
使用新的独立后台线程来刷buffer pool的LRU链表,将这部分工作负担从page cleaner线程剥离
实际上就是直接转移刷LRU的代码到独立线程了.从之前Percona的版本来看,都是在不断的强化后台线程,让用户线程少参与到刷脏/checkpoint这类耗时操作中

为什么InnoDB关闭会慢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InnoDB较之MyISAM,一个重要特性是InnoDB会在内存中开辟一个Buffer Pool来存储最近访问的数据块/索引块,使得下次再次访问这个块时速度能够很快.当InnoDB对需要修改数据块的时候,会先记录修改日志,然后直接对Buffer_Pool中的数据块的操作.记录日志是顺序写,对数据块的操作是内存操作,这让InnoDB在很多场景下有这很好的速度优势.

上面对内存块修改完成后,InnoDB就向客户端返回了.可这时实际磁盘上的数据块,还并没有被更新,我们把这样的page称为Dirty Page.在InnoDB的后台有一个专门的线程来做将内存数据块Flush到磁盘的工作.这个Flush操作就是主要影响InnoDB关闭时间的因素.在关闭MySQL/InnoDB时,所有的Dirty_Page都需要Flush,所以Dirty_Page越多,要Flush的数据块也就越多,意味着InnoDB关闭时间越长



我们可以通过下面的命令来观察Dirty Page的数量:
mysqladmin -uroot ext -i 1 |grep "Innodb_buffer_pool_pages_dirty"

参数innodb_max_dirty_pages_pct:
Buffer_Pool中Dirty_Page所占的数量,直接影响InnoDB的关闭时间.参数innodb_max_dirty_pages_pct可以直接控制了Dirty_Page在Buffer_Pool中所占的比率,而且幸运的是innodb_max_dirty_pages_pct是可以动态改变的.所以,在关闭InnoDB之前先将innodb_max_dirty_pages_pct调小,强制数据块Flush一段时间,则能够大大缩短MySQL关闭的时间

set global innodb_max_dirty_pages_pct=0;
一般执行了上面的命令之后,Dirty_Page的Flush仍需要一段时间,所以要稍等一会儿,再关闭MySQL才有效果