Linux主机作为服务器,在很多高并发的场景下,需要对系统参数进行优化来提升主机性能。而确认主机的性能瓶颈点在哪里就非常重要了,这里主要在以下几个方面进行说明:
1、CPU
2、内存
3、磁盘
4、网络
下面就这几个方面借助网友的经验,简单的总结一下。内容主要来自http://www.vpsee.com/
CPU的监测
在确定是否需要对系统进行优化时,我们首先需要确认系统CPU目前的负载状态。我们可以使用 vmstat命令来查看当前系统的负载。
vmstat 工具提供了一种低开销的系统性能观察方式.因为 vmstat 本身就是低开销工具,在非常高负荷的服务器上,你需要查看并监控系统的健康情况,在控制窗口还是能够使用vmstat 输出结果.这个工具运行在2种模式下:average 和 sample 模式.sample 模式通过指定间隔时间测量状态值.这个模式对于理解在持续负荷下的性能表现,很有帮助.下面就是vmstat 运行1秒间隔的示例:
# vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 1334844 405280 68 23723064 0 0 1 3 0 0 0 0 99 0 0 0 0 1334844 404920 68 23723064 0 0 0 52 1809 3469 0 0 99 0 0 0 0 1334844 404944 68 23723064 0 0 0 1888 1891 3796 0 0 99 0 0 0 0 1334844 404956 68 23723064 0 0 0 0 1941 3591 0 0 99 0 0 1 0 1334844 405204 68 23723064 0 0 0 0 1873 3631 0 0 99 0 0 0 0 1334844 405184 68 23723064 0 0 0 0 1883 3574 0 0 99 0 0
cpu相关的参数含义:
r 当前运行队列中线程的数目.代表线程处于可运行状态,但CPU 还未能执行
b 当前等待IO请求而被阻塞的进程数目
in 当前中断被处理的数目
cs 当前kernel system中,发生上下文切换的数目
us CPU利用率
sy 内核和中断利用率的百分比
wa 所有可运行状态线程被阻塞在等待IO 请求的百分比
id CPU 空闲时间的百分比
想要读懂系统输出的这些参数前,我们需要了解CPU相关的知识:
CPU的运行优先级(从高到低):
Interrupts(中断) - 设备通知内核,他们完成一次数据处理的过程.如:当一块网卡设备递送网络数据包或者一块硬件提供了一次IO 请求.
Kernel(System) Processes(内核处理过程) - 内核处理过程就是控制优先级别.
User Processes(用户进程) - 这里涉及"userland".所有软件程序都运行在这个user space.这块在内核调度机制中处于低优先级.
上下文切换:Linux 内核把每个处理器核心的双核心芯片作为独立的处理器.比如,以Linux 内核的系统在一个双核心处理器上,是报告显示为两个独立的处理器.一个标准的Linux 内核可以运行50 至 50,000 的处理线程.在只有一个CPU时,内核将调度并均衡每个进程线程.每个线程都分配一个在处理器中被开销的时间额度.一个线程要么就是获得时间额度,或者已抢先获得一些具有较高优先级(比如硬件中断),其中较高优先级的线程将从区域重新放置回处理器的队列中.这种线程的转换关系就是我们提到的上下文切换.每次内核的上下文切换,资源被用于关闭在CPU寄存器中的线程和放置在队列中.系统中越多的上下文切换,在处理器的调度管理下,内核将得到更多的工作.运行队列:每个CPU 都维护一个线程的运行队列.理论上,调度器应该不断的运行和执行线程.进程线程不是在阻塞中和等待IO中,或就是在可运行状态中。系统 load 就是指在CPU 队列中有多少数目的线程,以及其中当前有多少进程线程数目被执行的组合.如果一个双核系统执行了2个线程,还有4个在运行队列中,则 load 应该为 6.使用top或w 命令显示的load averages 是指1,5,15 分钟以内的load 情况.CPU利用率:CPU 利用率就是定义CPU 使用的百分比.评估系统最重要的一个度量方式就是CPU 的利用率.多数性能监控工具关于CPU 利用率的分类有以下几种:
User Time(译注:用户进程时间) - 关于在user space中被执行进程在CPU 开销时间百分比.
System Time(译注:内核线程以及中断时间) - 关于在kernel space中线程和中断在CPU 开销时间百分比.
Wait IO(译注:IO 请求等待时间) - 所有进程线程被阻塞等待完成一次IO 请求所占CPU 开销idle的时间百分比.
Idle(译注:空闲) - 一个完整空闲状态的进程在CPU 处理器中开销的时间百分比.
理解运行队列,利用率,上下文切换对怎样CPU 性能最优化之间的关系.早期提及到,性能是相对于基准线数据的.在一些系统中,通常预期所达到的性能包括:
Run Queues - 每个处理器应该运行队列不超过1-3 个线程.例子,一个双核处理器应该运行队列不要超过6 个线程.
CPU Utiliation - 如果一个CPU 被充分使用,利用率分类之间均衡的比例应该是
65% - 70% User Time
30% - 35% System Time
0% - 5% Idle Time
Context Switches - 上下文切换的数目直接关系到CPU 的使用率,如果CPU 利用率保持在上述均衡状态时,大量的上下文切换是正常的.
很多Linux 上的工具可以得到这些状态值,首先就是 vmstat 和 top 这2个工具.
举两个现实中的例子来实际分析一下:
$ vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 4 0 140 2915476 341288 3951700 0 0 0 0 1057 523 19 81 0 0 0 4 0 140 2915724 341296 3951700 0 0 0 0 1048 546 19 81 0 0 0 4 0 140 2915848 341296 3951700 0 0 0 0 1044 514 18 82 0 0 0 4 0 140 2915848 341296 3951700 0 0 0 24 1044 564 20 80 0 0 0 4 0 140 2915848 341296 3951700 0 0 0 0 1060 546 18 82 0 0 0
从上面的数据可以看出几点:
interrupts(in)非常高,context switch(cs)比较低,说明这个 CPU 一直在不停的请求资源;
system time(sy)一直保持在 80% 以上,而且上下文切换较低(cs),说明某个进程可能一直霸占着 CPU(不断请求资源);
run queue(r)刚好在4个。
$ vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 14 0 140 2904316 341912 3952308 0 0 0 460 1106 9593 36 64 1 0 0 17 0 140 2903492 341912 3951780 0 0 0 0 1037 9614 35 65 1 0 0 20 0 140 2902016 341912 3952000 0 0 0 0 1046 9739 35 64 1 0 0 17 0 140 2903904 341912 3951888 0 0 0 76 1044 9879 37 63 0 0 0 16 0 140 2904580 341912 3952108 0 0 0 0 1055 9808 34 65 1 0 0
从上面的数据可以看出几点:
context switch(cs)比 interrupts(in)要高得多,说明内核不得不来回切换进程;
进一步观察发现 system time(sy)很高而 user time(us)很低,而且加上高频度的上下文切换(cs),说明正在运行的应用程序调用了大量的系统调用(system call);
run queue(r)在14个线程以上,按照这个测试机器的硬件配置(四核),应该保持在12个以内。
那么对CPU的一个判断标准应该是这样:
CPU 利用率,如果 CPU 有 100% 利用率,那么应该到达这样一个平衡:65%-70% User Time,30%-35% System Time,0%-5% Idle Time;
上下文切换,上下文切换应该和 CPU 利用率联系起来看,如果能保持上面的 CPU 利用率平衡,大量的上下文切换是可以接受的;
可运行队列,每个可运行队列不应该超过3个线程(每处理器),比如:双处理器系统的可运行队列里不应该超过6个线程。
内存的监测和优化
在操作系统里,虚拟内存被分成页,在 x86 系统上每个页大小是 4KB。Linux 内核读写虚拟内存是以 “页” 为单位操作的,把内存转移到硬盘交换空间(SWAP)和从交换空间读取到内存的时候都是按页来读写的。
我们可以使用如下命令来查看当前内存页大小:
# getconf PAGE_SIZE 4096
内存和 SWAP 的这种交换过程称为页面交换(Paging),值得注意的是 paging 和 swapping 是两个完全不同的概念。swapping 也翻译成交换,在操作系统里是指把某程序完全交换到硬盘以腾出内存给新程序使用,和 paging 只交换程序的部分(页面)是两个不同的概念。纯粹的 swapping 在现代操作系统中已经很难看到了,因为把整个程序交换到硬盘的办法既耗时又费力而且没必要,现代操作系统基本都是 paging 或者 paging/swapping 混合。
这里重点介绍下性能监测有关的两个内核进程:kswapd 和 pdflush。
kswapd daemon 用来检查 pages_high 和 pages_low,如果可用内存少于 pages_low,kswapd 就开始扫描并试图释放 32个页面,并且重复扫描释放的过程直到可用内存大于 pages_high 为止。
扫描的时候检查3件事:
1)如果页面没有修改,把页放到可用内存列表里;
2)如果页面被文件系统修改,把页面内容写到磁盘上;
3)如果页面被修改了,但不是被文件系统修改的,把页面写到交换空间。
pdflush daemon 用来同步文件相关的内存页面,把内存页面及时同步到硬盘上。比如打开一个文件,文件被导入到内存里,对文件做了修改后并保存后,内核并不马上保存文件到硬盘,由 pdflush 决定什么时候把相应页面写入硬盘,这由一个内核参数 vm.dirty_background_ratio 来控制,比如下面的参数显示脏页面(dirty pages)达到所有内存页面10%的时候开始写入硬盘。
# sysctl -n vm.dirty_background_ratio 10
使用vmstat来查看当前内存信息:
# vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 565616 6054496 88 24970528 0 0 0 10 0 0 0 0 99 0 0 0 1 565616 6054476 88 24970528 0 0 0 12 1336 2473 0 0 100 0 0 0 1 565616 6054800 88 24970528 0 0 0 4 1230 2437 0 0 100 0 0 0 0 565616 6054788 88 24970528 0 0 0 52 1240 2454 0 0 100 0 0 0 1 565616 6054940 88 24970528 0 0 0 28 1193 2479 0 0 100 0 0 1 0 565616 6054920 88 24970528 0 0 0 0 1371 2653 0 0 100 0 0 0 0 565616 6054856 88 24970528 0 0 0 12 1184 2327 0 0 100 0 0
内存相关的参数:
swpd,已使用的 SWAP 空间大小,KB 为单位;
free,可用的物理内存大小,KB 为单位;
buff,物理内存用来缓存读写操作的 buffer 大小,KB 为单位;
cache,物理内存用来缓存进程地址空间的 cache 大小,KB 为单位;
si,数据从 SWAP 读取到 RAM(swap in)的大小,KB 为单位;
so,数据从 RAM 写到 SWAP(swap out)的大小,KB 为单位;
bi,磁盘块从文件系统或 SWAP 读取到 RAM(blocks in)的大小,block 为单位;
bo,磁盘块从 RAM 写到文件系统或 SWAP(blocks out)的大小,block 为单位;
如果使用free命令,我们可以看到相同的参数:
# free -m total used free shared buff/cache available Mem: 64024 40482 401 49 23139 23001 Swap: 32127 1325 30802
这里显示的available列才是当前系统闲置的内存。
列举一个来自 VPSee 的一个 256MB RAM,512MB SWAP 的 Xen VPS的例子:
# vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 0 3 252696 2432 268 7148 3604 2368 3608 2372 288 288 0 0 21 78 1 0 2 253484 2216 228 7104 5368 2976 5372 3036 930 519 0 0 0 100 0 0 1 259252 2616 128 6148 19784 18712 19784 18712 3821 1853 0 1 3 95 1 1 2 260008 2188 144 6824 11824 2584 12664 2584 1347 1174 14 0 0 86 0 2 1 262140 2964 128 5852 24912 17304 24952 17304 4737 2341 86 10 0 0 4
这是一个频繁读取交换分区的例子:
物理可用内存 free 基本没什么显著变化,swapd 逐步增加,说明最小可用的内存始终保持在 256MB X 10% = 2.56MB 左右,当脏页达到10%的时候(vm.dirty_background_ratio = 10)就开始大量使用 swap;
buff 稳步减少说明系统知道内存不够了,kwapd 正在从 buff 那里借用部分内存;
kswapd 持续把脏页面写到 swap 交换区(so),并且从 swapd 逐渐增加看出确实如此。根据上面讲的 kswapd 扫描时检查的三件事,如果页面被修改了,但不是被文件系统修改的,把页面写到 swap,所以这里 swapd 持续增加。
SWAP:
当系统没有足够物理内存来应付所有请求的时候就会用到 swap 设备,swap 设备可以是一个文件,也可以是一个磁盘分区。不过要小心的是,使用 swap 的代价非常大。如果系统没有物理内存可用,就会频繁 swapping,如果 swap 设备和程序正要访问的数据在同一个文件系统上,那会碰到严重的 IO 问题,最终导致整个系统迟缓,甚至崩溃。swap 设备和内存之间的 swapping 状况是判断 Linux 系统性能的重要参考,我们已经有很多工具可以用来监测 swap 和 swapping 情况,比如:top、cat /proc/meminfo、vmstat 等
查看当前SWAP状态:
# cat /proc/sys/vm/swappiness 30
这里显示的30,表示在物理内存使用70%(100-30)的时候就是使用SWAP分区。如果设置为0,表示尽量不使用SWAP,但是在需要使用的时候系统还会使用,在vps主机上最佳的方是不划分SWAP分区,防止系统性能下降。
内存优化:
在某些场景,如使用Oracle 数据库,或者KVM主机上,可以调整使用大页内存。
使用大内存页有哪些好处:
1. 针对Huge Page的页表,在各进程之间可以共享,降低了Page Table的大小。
2. Huge Page内存只能锁定在物理内存中,不能被交换到交换区。这样避免了交换引起的性能影响。
3. 由于页表数量的减少,使得CPU中的TLB(可理解为CPU对页表的CACHE)的命中率大大提高。
查看系统所支持的大页内存:
# grep Huge /proc/meminfo AnonHugePages: 12288 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB
配置方式:
修改/etc/sysctl.conf文件,增加如下行:
vm.nr_hugepages=9218 # 参数配置需要根据当前场景进行计算
然后执行sysctl –p命令,使配置生效。
IO的监测和优化
磁盘通常是计算机最慢的子系统,也是最容易出现性能瓶颈的地方,因为磁盘离 CPU 距离最远而且 CPU 访问磁盘要涉及到机械操作,比如转轴、寻轨等。访问硬盘和访问内存之间的速度差别是以数量级来计算的,就像1天和1分钟的差别一样。要监测 IO 性能,有必要了解一下基本原理和 Linux 是如何处理硬盘和内存之间的 IO 的。
缺页中断:
Linux 利用虚拟内存极大的扩展了程序地址空间,使得原来物理内存不能容下的程序也可以通过内存和硬盘之间的不断交换(把暂时不用的内存页交换到硬盘,把需要的内存页从硬盘读到内存)来赢得更多的内存,看起来就像物理内存被扩大了一样。事实上这个过程对程序是完全透明的,程序完全不用理会自己哪一部分、什么时候被交换进内存,一切都有内核的虚拟内存管理来完成。当程序启动的时候,Linux 内核首先检查 CPU 的缓存和物理内存,如果数据已经在内存里就忽略,如果数据不在内存里就引起一个缺页中断(Page Fault),然后从硬盘读取缺页,并把缺页缓存到物理内存里。缺页中断可分为主缺页中断(Major Page Fault)和次缺页中断(Minor Page Fault),要从磁盘读取数据而产生的中断是主缺页中断;数据已经被读入内存并被缓存起来,从内存缓存区中而不是直接从硬盘中读取数据而产生的中断是次缺页中断。
上面的内存缓存区起到了预读硬盘的作用,内核先在物理内存里寻找缺页,没有的话产生次缺页中断从内存缓存里找,如果还没有发现的话就从硬盘读取。很显然,把多余的内存拿出来做成内存缓存区提高了访问速度,这里还有一个命中率的问题,运气好的话如果每次缺页都能从内存缓存区读取的话将会极大提高性能。要提高命中率的一个简单方法就是增大内存缓存区面积,缓存区越大预存的页面就越多,命中率也会越高。
File Buffer Cache:
从上面的内存缓存区(也叫文件缓存区 File Buffer Cache)读取页比从硬盘读取页要快得多,所以 Linux 内核希望能尽可能产生次缺页中断(从文件缓存区读),并且能尽可能避免主缺页中断(从硬盘读),这样随着次缺页中断的增多,文件缓存区也逐步增大,直到系统只有少量可用物理内存的时候 Linux 才开始释放一些不用的页。我们运行 Linux 一段时间后会发现虽然系统上运行的程序不多,但是可用内存总是很少,这样给大家造成了 Linux 对内存管理很低效的假象,事实上 Linux 把那些暂时不用的物理内存高效的利用起来做预存(内存缓存区)。
顺序IO和随机IO:
$ iostat -kx 1 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 2.50 25.25 0.00 72.25 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 24.00 19995.00 29.00 99.00 4228.00 45060.00 770.12 45.01 539.65 7.80 99.80 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 1.00 30.67 0.00 68.33 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 3.00 12235.00 3.00 112.00 768.00 54272.00 957.22 144.85 576.44 8.70 100.10
随机 IO 是指随机请求数据,其 IO 速度不依赖于数据的大小和排列,依赖于磁盘的每秒能 IO 的次数,比如 Web 服务、Mail 服务等每次请求的数据都很小,随机 IO 每秒同时会有更多的请求数产生,所以磁盘的每秒能 IO 多少次是关键。
$ iostat -kx 1 avg-cpu: %user %nice %system %iowait %steal %idle 1.75 0.00 0.75 0.25 0.00 97.26 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 0.00 52.00 0.00 57.00 0.00 436.00 15.30 0.03 0.54 0.23 1.30 avg-cpu: %user %nice %system %iowait %steal %idle 1.75 0.00 0.75 0.25 0.00 97.24 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 0.00 56.44 0.00 66.34 0.00 491.09 14.81 0.04 0.54 0.19 1.29
按照上面的公式得出:436.00 / 57.00 = 7.65 KB per IO,491.09 / 66.34 = 7.40 KB per IO. 与顺序 IO 比较发现,随机 IO 的 KB per IO 小到可以忽略不计,可见对于随机 IO 而言重要的是每秒能 IOPS 的次数,而不是每次 IO 的吞吐能力(KB per IO)。
磁盘的调度算法:
在CentOS7上,系统支持三种调度算法,
# cat /sys/block/sda/queue/scheduler noop deadline [cfq]
使用下面命令可以查看当前系统默认的调度算法:
# dmesg | grep -i scheduler [ 0.699145] io scheduler noop registered [ 0.699147] io scheduler deadline registered (default) [ 0.699161] io scheduler cfq registered
CFQ: 完全公平排队I/O调度算法。CFQ试图均匀地分布对I/O带宽的访问,避免进程被饿死并实现较低的延迟,CFQ为每个进程/线程,单独创建一个队列来管理该进程所产生的请求,也就是说每个进程一个队列,各队列之间的调度使用时间片来调度。CFQ对于多媒体应用(video,audio)和桌面系统是最好的选择.
NOOP:电梯式调度算法(NO OPERATION)。NOOP对于闪存设备,RAM,嵌入式系统是最好的选择.如果使用的是SSD磁盘,需要调整磁盘调度为NOOP.
Deadline:截止时间调度程序。 Deadline确保了在一个截止时间内服务请求,这个截止时间是可调整的,而默认读期限短于写期限.这样就防止了写操作因为不能被读取而饿死的现象,Deadline对数据库环境(ORACLE RAC,MYSQL等)是最好的选择.
网络检测
1、使用ethtool 工具来查看网卡的配置和工作模式:
# ethtool eth0 Settings for eth0: Supported ports: [ TP MII ] Supported link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full Supported pause frame use: No Supports auto-negotiation: Yes Advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full Advertised pause frame use: Symmetric Advertised auto-negotiation: Yes Link partner advertised link modes: 10baseT/Half 10baseT/Full 100baseT/Half 100baseT/Full Link partner advertised pause frame use: Symmetric Link partner advertised auto-negotiation: No Speed: 100Mb/s Duplex: Full Port: MII PHYAD: 32 Transceiver: internal Auto-negotiation: on Supports Wake-on: pumbg Wake-on: d Current message level: 0x00000007 (7) drv probe link Link detected: yes
调整网卡工作模式:
ethtool -s eth0 speed 1000 duplex full autoneg off
2、iptraf
两台主机之间有网线(或无线)、路由器、交换机等设备,测试两台主机之间的网络性能的一个办法就是在这两个系统之间互发数据并统计结果,看看吞吐量、延迟、速率如何。iptraf 就是一个很好的查看本机网络吞吐量的好工具,支持文字图形界面,很直观.# iptraf-ng -i eth0
3、tcpdump 和 tcptrace
tcmdump 和 tcptrace 提供了一种更细致的分析方法,先用 tcpdump 按要求捕获数据包把结果输出到某一文件,然后再用 tcptrace 分析其文件格式。这个工具组合可以提供一些难以用其他工具发现的信息:
# /usr/sbin/tcpdump -w network.dmp tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes 511942 packets captured 511942 packets received by filter 0 packets dropped by kernel
# tcptrace network.dmp 1 arg remaining, starting with 'network.dmp' Ostermann's tcptrace -- version 6.6.7 -- Thu Nov 4, 2004 511677 packets seen, 511487 TCP packets traced elapsed wallclock time: 0:00:00.510291, 1002714 pkts/sec analyzed trace file elapsed time: 0:02:35.836372 TCP connection info: 1: zaber:54581 - boulder:111 (a2b) 6> 5< (complete) 2: zaber:833 - boulder:32774 (c2d) 6> 5< (complete) 3: zaber:pcanywherestat - 172.16.39.5:53086 (e2f) 2> 3< 4: zaber:716 - boulder:2049 (g2h) 347> 257< 5: 172.16.39.100:58029 - zaber:12865 (i2j) 7> 5< (complete) 6: 172.16.39.100:47592 - zaber:36814 (k2l) 255380> 255378< (reset) 7: breakpoint:45510 - zaber:7012 (m2n) 9> 5< (complete) 8: zaber:35813 - boulder:111 (o2p) 6> 5< (complete) 9: zaber:837 - boulder:32774 (q2r) 6> 5< (complete) 10: breakpoint:45511 - zaber:7012 (s2t) 9> 5< (complete) 11: zaber:59362 - boulder:111 (u2v) 6> 5< (complete) 12: zaber:841 - boulder:32774 (w2x) 6> 5< (complete) 13: breakpoint:45512 - zaber:7012 (y2z) 9> 5< (complete)
tcptrace 功能很强大,还可以通过过滤和布尔表达式来找出有问题的连接,比如,找出转播大于100 segments 的连接:
# tcptrace -f'rexmit_segs>100' network.dmp
如果发现连接 #10 有问题,可以查看关于这个连接的其他信息:
# tcptrace -o10 network.dmp
参考链接:
http://www.vpsee.com/2009/11/linux-system-performance-monitoring-cpu/
http://www.vpsee.com/2009/11/linux-system-performance-monitoring-memory/
http://www.vpsee.com/2009/11/linux-system-performance-monitoring-io/
http://www.cnblogs.com/lhrbest/p/5866284.html
http://longmans1985.blog.163.com/blog/static/7060547520109262178736/
http://blog.163.com/digoal@126/blog/static/1638770402015117214118/