tc命令——linux基于ip进行流量限制原理部分参考多方文档进行整理，本文主要目的是记录几个限速的实例来进行直观认识和学习（git限速1、2）。参考：http://blog.csdn.net/zhongbeida_xue/article/details/54613750http://www.cnblogs.com/endsock/archive/2011/12/09/2281519.htmlhttp://blog.163.com/ninja_wk/blog/static/989155620084280154811/http://www.chinaunix.net/jh/4/16110.htmlhttps://my.oschina.net/u/3497124/blog/1632937http://blog.csdn.net/qinyushuang/article/details/46611709一、tc原理Linux操作系统中的流量控制器TC（Traffic Control）用于Linux内核的流量控制，主要是通过在输出端口处建立一个队列来实现流量控制。 接收包从输入接口进来后，经过流量限制丢弃不符合规定的数据包，由输入多路分配器进行判断选择：如果接收包的目的主机是本主机，那么将该包送给上层处理，否则需要进行转发，将接收包交到转发块（Forwarding Block）处理。转发块同时也接收本主机上层(TCP、UDP等)产生的包，通过查看路由表，决定所处理包的下一跳。然后，对包进行排列以便将它们送到输出接口。一般只能限制网卡发送的数据包，不能限制网卡接收的数据包，所以可以通过改变发送次序来控制传输速率。Linux流量控制主要是在输出接口排列时进行处理和实现的。二、规则2.1 流量控制方式流量控制包括以下几种方式：SHAPING(限制)： 当流量被限制，它的传输速率就被控制在某个值以下。限制值可以大大小于有效带宽，这样可以平滑突发数据流量，使网络更为稳定。shaping（限制）只适用于向外的流量。SCHEDULING(调度)： 通过调度数据包的传输，可以在带宽范围内，按优先级分配带宽。SCHEDULING(调度)也只适于向外的流量。POLICING(策略)： SHAPING用于处理向外的流量，而POLICIING(策略)用于处理接收到的数据。DROPPING(丢弃)： 如果流量超过某个设定的带宽，就丢弃数据包，不管是向内还是向外。2.2 流量控制处理对象流量的处理由三种对象控制，它们是：qdisc（排队规则）class（类别）filter（过滤器）2.2.1 qdisc（排队规则）QDisc(排队规则)是queueing discipline的简写，它是理解流量控制(traffic control)的基础。无论何时，内核如果需要通过某个网络接口发送数据包，它都需要按照为这个接口配置的qdisc(排队规则)把数据包加入队列。然后，内核会尽可能多地从qdisc里面取出数据包，把它们交给网络适配器驱动模块。最简单的QDisc是pfifo它不对进入的数据包做任何的处理，数据包采用先入先出的方式通过队列。不过，它会保存网络接口一时无法处理的数据包。 qdisc的类别如下：CLASSLESS QDisc(不可分类QDisc)[p|b]fifo： 使用最简单的qdisc，纯粹的先进先出。只有一个参数：limit，用来设置队列的长度,pfifo是以数据包的个数为单位；bfifo是以字节数为单位。pfifo_fast： 在编译内核时，如果打开了高级路由器(Advanced Router)编译选项，pfifo_fast就是系统的标准QDISC。它的队列包括三个波段(band)。在每个波段里面，使用先进先出规则。而三个波段(band)的优先级也不相同，band 0的优先级最高，band 2的最低。如果band0里面有数据包，系统就不会处理band 1里面的数据包，band 1和band 2之间也是一样。数据包是按照服务类型(Type of Service,TOS)被分配多三个波段(band)里面的。red： red是Random Early Detection(随机早期探测)的简写。如果使用这种QDISC，当带宽的占用接近于规定的带宽时，系统会随机地丢弃一些数据包。它非常适合高带宽应用。sfq： sfq是Stochastic Fairness Queueing的简写。它按照会话(session--对应于每个TCP连接或者UDP流)为流量进行排序，然后循环发送每个会话的数据包。tbf： tbf是Token Bucket Filter的简写，适合于把流速降低到某个值。不可分类qdisc配置： 如果没有可分类QDisc，不可分类QDisc只能附属于设备的根。它们的用法如下：tc qdisc add dev DEV root QDISC QDISC-PARAMETERS要删除一个不可分类QDisc，需要使用如下命令：tc qdisc del dev DEV root一个网络接口上如果没有设置QDisc，pfifo_fast就作为缺省的QDisc。CLASSFUL QDISC(分类QDisc):可分类的qdisc包括：CBQ： CBQ是Class Based Queueing(基于类别排队)的缩写。它实现了一个丰富的连接共享类别结构，既有限制(shaping)带宽的能力，也具有带宽优先级管理的能力。带宽限制是通过计算连接的空闲时间完成的。空闲时间的计算标准是数据包离队事件的频率和下层连接(数据链路层)的带宽。HTB： HTB是Hierarchy Token Bucket的缩写。通过在实践基础上的改进，它实现了一个丰富的连接共享类别体系。使用HTB可以很容易地保证每个类别的带宽，它也允许特定的类可以突破带宽上限，占用别的类的带宽。HTB可以通过TBF(Token Bucket Filter)实现带宽限制，也能够划分类别的优先级。PRIO： PRIO QDisc不能限制带宽，因为属于不同类别的数据包是顺序离队的。使用PRIO QDisc可以很容易对流量进行优先级管理，只有属于高优先级类别的数据包全部发送完毕，才会发送属于低优先级类别的数据包。为了方便管理，需要使用iptables或者ipchains处理数据包的服务类型(Type Of Service,ToS)。2.2.2 class（类）某些QDisc(排队规则)可以包含一些类别，不同的类别中可以包含更深入的QDisc(排队规则)，通过这些细分的QDisc还可以为进入的队列的数据包排队。通过设置各种类别数据包的离队次序，QDisc可以为设置网络数据流量的优先级。2.2.3 filter（过滤器）Filter(过滤器)用于为数据包分类，决定它们按照何种QDisc进入队列。无论何时数据包进入一个划分子类的类别中，都需要进行分类。分类的方法可以有多种，使用fileter(过滤器)就是其中之一。使用filter(过滤器)分类时，内核会调用附属于这个类(class)的所有过滤器，直到返回一个判决。如果没有判决返回，就作进一步的处理，而处理方式和QDISC有关。需要注意的是，filter(过滤器)是在QDisc内部，它们不能作为主体。2.3 执行过程类(Class)组成一个树，每个类都只有一个父类，而一个类可以有多个子类。某些QDisc(例如：CBQ和HTB)允许在运行时动态添加类，而其它的QDisc(例如：PRIO)不允许动态建立类。允许动态添加类的QDisc可以有零个或者多个子类，由它们为数据包排队。此外，每个类都有一个叶子QDisc，默认情况下，这个叶子QDisc使用pfifo的方式排队，我们也可以使用其它类型的QDisc代替这个默认的QDisc。而且，这个叶子QDisc有可以分类，不过每个子类只能有一个叶子QDisc。 当一个数据包进入一个分类QDisc，它会被归入某个子类。 我们可以使用以下三种方式为数据包归类，不过不是所有的QDisc都能够使用这三种方式：tc过滤器(tc filter)： 如果过滤器附属于一个类，相关的指令就会对它们进行查询。过滤器能够匹配数据包头所有的域，也可以匹配由ipchains或者iptables做的标记。服务类型(Type of Service)： 某些QDisc有基于服务类型（Type of Service,ToS）的内置的规则为数据包分类。skb->priority： 用户空间的应用程序可以使用SO_PRIORITY选项在skb->priority域设置一个类的ID。 树的每个节点都可以有自己的过滤器，但是高层的过滤器也可以直接用于其子类。 如果数据包没有被成功归类，就会被排到这个类的叶子QDisc的队中。相关细节在各个QDisc的手册页中。2.4 命名规则所有的QDisc、类和过滤器都有ID。ID可以手工设置，也可以有内核自动分配。ID由一个主序列号和一个从序列号组成，两个数字用一个冒号分开。QDISC： 一个QDisc会被分配一个主序列号，叫做句柄(handle)，然后把从序列号作为类的命名空间。句柄采用象10:一样的表达方式。习惯上，需要为有子类的QDisc显式地分配一个句柄。class： 在同一个QDisc里面的类分享这个QDisc的主序列号，但是每个类都有自己的从序列号，叫做类识别符(classid)。类识别符只与父QDisc有关，和父类无关。类的命名习惯和QDisc的相同。filter： 过滤器的ID有三部分，只有在对过滤器进行散列组织才会用到。详情请参考tc-filters手册页。2.5 单位带宽或流速单位：kbps千字节/smbps兆字节/skbitKbit/smbitMbit/sbps或者一个无单位数字字节/s数据数量单位：kb或者k千字节mb或者m兆字节mbit兆bitkbit千bitb或者一个无单位数字字节数三、tc命令参数解读tc可以使用以下命令对QDisc、类和过滤器进行操作：add： 在一个节点里加入一个QDisc、类或者过滤器。添加时，需要传递一个祖先作为参数，传递参数时既可以使用ID也可以直接传递设备的根。如果要建立一个QDisc或者过滤器，可以使用句柄(handle)来命名；如果要建立一个类，可以使用类识别符(classid)来命名。remove： 删除有某个句柄(handle)指定的QDisc，根QDisc(root)也可以删除。被删除QDisc上的所有子类以及附属于各个类的过滤器都会被自动删除。change： 以替代的方式修改某些条目。除了句柄(handle)和祖先不能修改以外，change命令的语法和add命令相同。换句话说，change命令不能一定节点的位置。replace： 对一个现有节点进行近于原子操作的删除／添加。如果节点不存在，这个命令就会建立节点。link： 只适用于DQisc，替代一个现有的节点。四、应用Linux流量控制主要分为建立队列、建立分类和建立过滤器三个方面。4.1 步骤：针对网络物理设备（如以太网卡eth0）绑定一个队列QDisc；在该队列上建立分类class；为每一分类建立一个基于路由的过滤器filter；最后与过滤器相配合，建立特定的路由表。4.2 应用1：git限速1：针对端口进行限速在使用git拉去代码时很容易跑满带宽，为了控制带宽的使用，配置如下：#查看现有的队列 tc -s qdisc ls dev eth0 #查看现有的分类 tc -s class ls dev eth0 #创建队列 tc qdisc add dev eth0 root handle 1:0 htb default 1 #添加一个tbf队列，绑定到eth0上，命名为1:0 ，默认归类为1 #handle：为队列命名或指定某队列 #创建分类 tc class add dev eth0 parent 1:0 classid 1:1 htb rate 10Mbit burst 15k #为eth0下的root队列1:0添加一个分类并命名为1:1，类型为htb，带宽为10M #rate: 是一个类保证得到的带宽值.如果有不只一个类,请保证所有子类总和是小于或等于父类. #ceil: ceil是一个类最大能得到的带宽值. #创建一个子分类 tc class add dev eth0 parent 1:1 classid 1:10 htb rate 10Mbit ceil 10Mbit burst 15k #为1:1类规则添加一个名为1:10的类，类型为htb，带宽为10M #为了避免一个会话永占带宽,添加随即公平队列sfq. tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10 #perturb：是多少秒后重新配置一次散列算法，默认为10秒 #sfq,他可以防止一个段内的一个ip占用整个带宽 #使用u32创建过滤器 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip sport 22 flowid 1:10 #删除队列 tc qdisc del dev eth0 root 配置完成后加入本地启动文件： /etc/rc.localgit限速2：针对ip进行限速情景： 因为带宽资源有限（20Mbit≈2Mbyte），使用git拉取代码的时候导致带宽资源告警，所以对git进行限速，要求：内网不限速；外网下载速度为1M左右。（注意：此处需要注意单位转换1byte=8bit）...<脚本>#!/bin/bash #针对不同的ip进行限速 #清空原有规则 tc qdisc del dev eth0 root #创建根序列 tc qdisc add dev eth0 root handle 1: htb default 1 #创建一个主分类绑定所有带宽资源（20M） tc class add dev eth0 parent 1:0 classid 1:1 htb rate 20Mbit burst 15k #创建子分类 tc class add dev eth0 parent 1:1 classid 1:10 htb rate 20Mbit ceil 10Mbit burst 15k tc class add dev eth0 parent 1:1 classid 1:20 htb rate 20Mbit ceil 20Mbit burst 15k #避免一个ip霸占带宽资源（git1有讲到） tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10 tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10 #创建过滤器 #对所有ip限速 tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dst 0.0.0.0/0 flowid 1:10 #对内网ip放行 tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 12.0.0.0/8 flowid 1:20该过程遇到的问题：未配置优先级以至于只有一条规则生效（所有的ip都被限速）解决办法：在过滤器中加入prio，指定规则的优先级不知道本地带宽是多大：解决办法：直接给你一个很大很大的带宽（100000....），对需要限速的ip分配指定的带宽资源，然后把剩余的带宽全部分配给内网ip。。。（简单粗暴有效）4.3 应用2：环境模拟实例需求：流量控制器上的以太网卡(eth0) 的IP地址为192.168.1.66，在其上建立一个CBQ队列。假设包的平均大小为1000字节，包间隔发送单元的大小为8字节，可接收冲突的发送最长包数目为20字节。 假如有三种类型的流量需要控制:是发往主机1的，其IP地址为192.168.1.24。其流量带宽控制在8Mbit，优先级为2；是发往主机2的，其IP地址为192.168.1.30。其流量带宽控制在1Mbit，优先级为1；是发往子网1的，其子网号为192.168.1.0，子网掩码为255.255.255.0。流量带宽控制在1Mbit，优先级为6。实现：建立队列：一般情况下，针对一个网卡只需建立一个队列。 将一个cbq队列绑定到网络物理设备eth0上，其编号为1:0；网络物理设备eth0的实际带宽为10 Mbit，包的平均大小为1000字节；包间隔发送单元的大小为8字节，最小传输包大小为64字节。tc qdisc add dev eth0 root handle 1: cbq bandwidth 10Mbit avpkt 1000 cell 8 mpu 64 #cell:包间隔发送单元的大小为8字节建立分类：分类建立在队列之上。 一般情况下，针对一个队列需建立一个根分类，然后再在其上建立子分类。对于分类，按其分类的编号顺序起作用，编号小的优先；一旦符合某个分类匹配规则，通过该分类发送数据包，则其后的分类不再起作用。1） 创建根分类1:1；分配带宽为10Mbit，优先级别为8tc class add dev eth0 parent 1:0 classid 1:1 cbq bandwidth 10Mbit rate 10Mbit maxburst 20 allot 1514 prio 8 avpkt 1000 cell 8 weight 1Mbit #prio:用来指示借用带宽时的竞争力,prio越小,优先级越高,竞争力越强.该队列的最大可用带宽为10Mbit，实际分配的带宽为10Mbit，可接收冲突的发送最长包数目为20字节；最大传输单元加MAC头的大小为1514字节，优先级别为8，包的平均大小为1000字节，包间隔发送单元的大小为8字节，相应于实际带宽的加权速率为1Mbit。 2） 创建分类1:2，其父分类为1:1，分配带宽为8Mbit，优先级别为2tc class add dev eth0 parent 1:1 classid 1:2 cbq bandwidth 10Mbit rate 8Mbit maxburst 20 allot 1514 prio 2 avpkt 1000 cell 8 weight 800Kbit split 1:0 bounded该队列的最大可用带宽为10Mbit，实际分配的带宽为 8Mbit，可接收冲突的发送最长包数目为20字节；最大传输单元加MAC头的大小为1514字节，优先级别为1，包的平均大小为1000字节，包间隔发送单元的大小为8字节，相应于实际带宽的加权速率为800Kbit，分类的分离点为1:0，且不可借用未使用带宽。 3）创建分类1:3，其父分类为1:1，分配带宽为1Mbit，优先级别为1。tc class add dev eth0 parent 1:1 classid 1:3 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 1 avpkt 1000 cell 8 weight 100Kbit split 1:0该队列的最大可用带宽为10Mbit，实际分配的带宽为 1Mbit，可接收冲突的发送最长包数目为20字节；最大传输单元加MAC头的大小为1514字节，优先级别为2，包的平均大小为1000字节，包间隔发送单元的大小为8字节，相应于实际带宽的加权速率为100Kbit，分类的分离点为1:0。 4） 创建分类1:4，其父分类为1:1，分配带宽为1Mbit，优先级别为6。tc class add dev eth0 parent 1:1 classid 1:4 cbq bandwidth 10Mbit rate 1Mbit maxburst 20 allot 1514 prio 6 avpkt 1000 cell 8 weight 100Kbit split 1:0建立过滤器过滤器主要服务于分类。 一般只需针对根分类提供一个过滤器，然后为每个子分类提供路由映射。 1）应用路由分类器到cbq队列的根，父分类编号为1:0；过滤协议为ip，优先级别为100，过滤器为基于路由表。tc filter add dev eth0 parent 1:0 protocol ip prio 100 route2）建立路由映射分类1:2, 1:3, 1:4tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 2 flowid 1:2 tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 3 flowid 1:3 tc filter add dev eth0 parent 1:0 protocol ip prio 100 route to 4 flowid 1:4建立路由该路由是与前面所建立的路由映射一一对应。 1）发往主机192.168.1.24的数据包通过分类2转发(分类2的速率8Mbit)ip route add 192.168.1.24 dev eth0 via 192.168.1.66 realm 22）发往主机192.168.1.30的数据包通过分类3转发(分类3的速率1Mbit)ip route add 192.168.1.30 dev eth0 via 192.168.1.66 realm 33）发往子网192.168.1.0/24的数据包通过分类4转发(分类4的速率1Mbit)ip route add 192.168.1.0/24 dev eth0 via 192.168.1.66 realm 4注：一般对于流量控制器所直接连接的网段建议使用IP主机地址流量控制限制，不要使用子网流量控制限制。如一定需要对直连子网使用子网流量控制限制，则在建立该子网的路由映射前，需将原先由系统建立的路由删除，才可完成相应步骤。4.4 应用3：MySQL数据库同步数据限速方案mysql1: 10.9.57.162 may1: 10.12.1.45 # mysql1上实施(限制到may1的流量: 8mbit) tc qdisc add dev eth0 root handle 1: htb r2q 1 tc class add dev eth0 parent 1: classid 1:1 htb rate 8mbit ceil 8mbit tc qdisc add dev eth0 parent 1:1 handle 10: sfq perturb 10 tc filter add dev eth0 parent 1: protocol ip prio 16 u32 match ip dst 10.12.1.45 flowid 1:1 # may1(限制到mysql1的流量: 8mbit) tc qdisc del dev em1 root [ ] tc qdisc add dev em1 root handle 2: htb r2q 1 tc class add dev em1 parent 2: classid 2:1 htb rate 8mbit ceil 8mbit tc qdisc add dev em1 parent 2:1 handle 11: sfq perturb 10 tc filter add dev em1 parent 2: protocol ip prio 16 u32 match ip dst 10.9.57.162 flowid 2:1监视主要包括对现有队列、分类、过滤器和路由的状况进行监视。 1）显示队列的状况 简单显示指定设备(这里为eth0)的队列状况# tc qdisc ls dev eth0 qdisc cbq 1: rate 10Mbit (bounded,isolated) prio no-transmit详细显示指定设备(这里为eth0)的队列状况tc -s qdisc ls dev eth0这里主要显示了通过该队列发送了13232个数据包，数据流量为7646731个字节，丢弃的包数目为0，超过速率限制的包数目为0。 2）显示分类的状况 简单显示指定设备(这里为eth0)的分类状况tc class ls dev eth0详细显示指定设备(这里为eth0)的分类状况tc -s class ls dev eth0这里主要显示了通过不同分类发送的数据包，数据流量，丢弃的包数目，超过速率限制的包数目等等。其中根分类(class cbq 1:0)的状况应与队列的状况类似。 例如，分类class cbq 1:4发送了8076个数据包，数据流量为5552879个字节，丢弃的包数目为0，超过速率限制的包数目为0。 显示过滤器的状况tc -s filter ls dev eth0这里flowid 1:2代表分类class cbq 1:2，to 2代表通过路由2发送。 显示现有路由的状况ip route如上所示，结尾包含有realm的显示行是起作用的路由过滤器。实例脚本限速# ！/bin/sh touch /var/lock/subsys/local echo 1 > /proc/sys/net/ipv4/ip_forward （激活转发） route add default gw 10.0.0.0 (这是加入电信网关，如果你已设了不用这条） DOWNLOAD=640Kbit (640/8 =80K ,我这里限制下载最高速度只能80K） UPLOAD=640Kbit (640/8 =80K,上传速度也限制在80K） INET=192.168.0. (设置网段，根据你的情况填） IPS=1 (这个意思是从192.168.0.1开始） IPE=200 (我这设置是从IP为192.168.0.1-200这个网段限速，根据自已的需要改） ServerIP=253 (网关IP） IDEV=eth0 ODEV=eth1 /sbin/tc qdisc del dev $IDEV root handle 10: /sbin/tc qdisc del dev $ODEV root handle 20: /sbin/tc qdisc add dev $IDEV root handle 10: cbq bandwidth 100Mbit avpkt 1000 /sbin/tc qdisc add dev $ODEV root handle 20: cbq bandwidth 1Mbit avpkt 1000 /sbin/tc class add dev $IDEV parent 10:0 classid 10:1 cbq bandwidth 100Mbit rate 100Mbit allot 1514 weight 1Mbit prio 8 maxburst 20 avpkt 1000 /sbin/tc class add dev $ODEV parent 20:0 classid 20:1 cbq bandwidth 1Mbit rate 1Mbit allot 1514 weitht 10Kbit prio 8 maxburst 20 avpkt 1000 COUNTER=$IPS while [ $COUNTER -le $IPE ] do /sbin/tc class add dev $IDEV parent 10:1 classid 10:1$COUNTER cbq banwidth 100Mbit rate $DOWNLOAD allot 1514 weight 20Kbit prio 5 maxburst 20 avpkt 1000 bounded /sbin/tc qdisc add dev $IDEV parent 10:1$COUNTER sfq quantum 1514b perturb15 /sbin/tc filter add dev $IDEV parent 10:0 protocol ip prio 100 u32 match ipdst $INET$COUNTER flowid 10:1$COUNTER COUNTER=` expr $COUNTER + 1 ` done iptables -t nat -A POSTROUTING -o eth1 -s 192.168.0.0/24 -J MASQUERADE模型#!/bin/sh tc qdisc del dev eth7 root &> /dev/null tc qdisc del dev eth8 root &> /dev/null #Add qdisc tc qdisc add dev eth7 root handle 10: htb default 9998 tc qdisc add dev eth8 root handle 10: htb default 9998 #Add htb root node tc class add dev eth7 parent 10: classid 10:9999 htb rate 1000000kbit ceil 1000000kbit tc class add dev eth8 parent 10: classid 10:9999 htb rate 1000000kbit ceil 1000000kbit #Add htb fake default node here tc class add dev eth7 parent 10:9999 classid 10:9998 htb rate 1000000kbit ceil 1000000kbit tc class add dev eth8 parent 10:9999 classid 10:9998 htb rate 1000000kbit ceil 1000000kbit #Add rule node tc class add dev eth7 parent 10:9999 classid 10:3 htb rate 1kbit ceil 50kbit tc filter add dev eth7 parent 10: protocol ip handle 3 fw classid 10:3 tc class add dev eth8 parent 10:9999 classid 10:3 htb rate 1kbit ceil 50kbit tc filter add dev eth8 parent 10: protocol ip handle 3 fw classid 10:3 #Add htb real default node here tc class change dev eth7 classid 10:9998 htb rate 1kbit ceil 1000000kbit tc class change dev eth8 classid 10:9998 htb rate 1kbit ceil 1000000kbittc#创建一个主队列 tc qdisc add dev eth0 root handle 1： htb default 1 #tc 队列 添加 设置接口 root为最上层 句柄(做标记用)： 标记类型 默认使用1的class #命令解释：将一个htb队列绑定在eth0上,编号为1:0，默认归类是1 tc class add dev eth0 parent 1：0 classid1：30 htb rate 10mbit #为eth0 下的root队列1:0 添加分类并命名为 1:30 类型为htb 速度为10M tc class add dev eth0 parent 1：30 classid 1：31 htb rate 10mbit tc qdisc add dev eth0 root tbf match ip sport 22 0xffff rate 10mbit 网络速度测试的方法测试本机网络状态centos7系统# yum install -y speedtest-cli # speedtest-cli测试来自某ip的下载scp root@ip:/dir/filename /dir/针对ip限速：http://blog.csdn.net/zhongbeida_xue/article/details/54613750http://blog.csdn.net/daniel117/article/details/17350243wondershaper了解完tc工具后再来看一个简单实用的限速工具——wondershaper。这个单词看似很复杂，但是当你看过源码之后会发现，这只不过是将tc一些简单的功能封装成了一个shell脚本而已，安装和使用方法如下：安装$ yum install epel-release -y # 依赖第三方库 $ yum install wondershaper -y查看使用说明：$ wondershaper Wonder Shaper 1.2.1 Usage: /usr/sbin/wondershaper [device] clean|[upload speed in Kb/s] [download speed in Kb/s] Example: /usr/sbin/wondershaper eth0 20 500 https://bugzilla.redhat.com/enter_bug.cgi?product=Fedora&component=wondershaper示例：# 将上行带宽限制为1M，下行带宽限制为10M $ wondershaper eth0 1024 10240 # 清除原有规则 (两个参数可以是任何字符) $ wondershaper clean clean # 查看已有规则 (查看eth0的规则) $ wondershaper eth0Note: 一般为3个参数，第一个参数为网卡名，第二个上行速度（出）限制，第三个参数为下行速度（入）限制。原文：https://cloud.tencent.com/developer/article/1409664
在进行网络服务的测试时，有时需要模拟一些异常的网络情况，例如网络延时长、丢包、网络地址连接不通等。在Linux下，可以通过tc工具来模拟各种网络情况；通过iptables禁止访问某个网络地址。一、netem与tc介绍netem 是 Linux 2.6 及以上内核版本提供的一个网络模拟功能模块。该功能模块可以用来在性能良好的局域网中，模拟出复杂的互联网传输性能，诸如低带宽、传输延迟、丢包等等情况。使用 Linux 2.6 (或以上) 版本内核的很多发行版 Linux 都开启了该内核功能，比如Fedora、Ubuntu、Redhat、OpenSuse、CentOS、Debian等等。tc 是 Linux 系统中的一个工具，全名为traffic control（流量控制）。tc 可以用来控制 netem 的工作模式，也就是说，想要使用 netem ，则需要内核开启了 netem，而且安装了 tc工具。tc控制的是发包动作，不能控制收包动作。它直接对物理接口生效，如果控制了物理的eth0，那么逻辑网卡（比如eth0:1）也会受到影响，反之则不行，控制逻辑网卡是无效的。二、模拟延迟传输将 eth0 网卡的传输设置为延迟100毫秒发送。$ tc qdisc add dev eth0 root netem delay 100ms真实的情况下，延迟值不会这么精确，会有一定的波动，下面命令模拟带有波动性的延迟值：$ tc qdisc add dev eth0 root netem delay 100ms 10ms该命令将 eth0 网卡的传输设置为延迟 100ms ± 10ms （90 ~ 110 ms 之间的任意值）发送。由于各个包的延迟值不通，也会在一定程度上打乱发包的次序。还可以更进一步加强这种波动的随机性，将 eth0 网卡的传输设置为 100ms ，同时，大约有30%的包会延迟 ± 10ms 发送：$ tc qdisc add dev eth0 root netem delay 100ms 10ms 30%三、模拟网络丢包将 eth0 网卡的传输设置为随机丢掉 1% 的数据包。$ tc qdisc add dev eth0 root netem loss 1%也可以设置丢包的成功率，将 eth0 网卡的传输设置为随机丢掉 1% 的数据包，成功率为30% ：$ tc qdisc add dev eth0 root netem loss 1% 30%四、模拟包重复将 eth0 网卡的传输设置为随机产生 1% 的重复数据包。$ tc qdisc add dev eth0 root netem duplicate 1%五、模拟数据包损坏将 eth0 网卡的传输设置为随机产生 0.2% 的损坏的数据包。 (内核版本需在2.6.16以上）$ tc qdisc add dev eth0 root netem corrupt 0.2%六、模拟数据包乱序将 eth0 网卡的传输设置为:有25%的数据包（50%相关）会被立即发送，其他的延迟10秒。$ tc qdisc change dev eth0 root netem delay 10ms reorder 25% 50%七、一个实例step1：使用ifconfig命令查看你的网卡信息，如:eth0step2：将网卡加入监控列表$ sudo tc qdisc add dev eth0 root netemstep3：# 设置丢包率 $ sudo tc qdisc change dev eth0 root netem loss 0.5% # 设置重发 $ sudo tc qdisc change dev eth0 root netem duplicate 1% # 设置发乱序包 $ sudo tc qdisc change dev eth0 root netem gap 5 delay 10ms如果想让网络恢复正常，只需要删除监控，或将设置的值相应归0即可。例如，设置延时$ sudo tc qdisc add dev eth0 root netem delay 4s取消延时$ sudo tc qdisc del dev eth0 root netem delay 4s八、禁止访问一个IP如果要禁止访问一个IP，不使用tc，而是用iptables。iptalbes可以简单理解为linux下的防火墙，控制网络访问。1、禁止访问10.237.0.1$ iptables -A OUTPUT -d 10.237.0.1 -j REJECT2、查看规则$ iptables -L OUTPUT -nChain OUTPUT (policy ACCEPT)target prot opt source destination REJECT all -- 0.0.0.0/0 10.237.0.1 reject-with icmp-port-unreachable3、查看规则号$ iptables -L OUTPUT -n --line-numbersChain OUTPUT (policy ACCEPT)num target prot opt source destination 1 REJECT all -- 0.0.0.0/0 10.237.0.1 reject-with icmp-port-unreachable4、删除规则$ iptables -D OUTPUT 1参考：http://www.dbafree.net/?p=180————————————————版权声明：本文为CSDN博主「shelldon」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。原文链接：https://blog.csdn.net/shelldon/article/details/54585742
关于性能优化这是一个比较大的话题，在《由12306.cn谈谈网站性能技术》中我从业务和设计上说过一些可用的技术以及那些技术的优缺点，今天，想从一些技术细节上谈谈性能优化，主要是一些代码级别的技术和方法。本文的东西是我的一些经验和知识，并不一定全对，希望大家指正和补充。在开始这篇文章之前，大家可以移步去看一下酷壳以前发表的《代码优化概要》，这篇文章基本上告诉你——要进行优化，先得找到性能瓶颈！ 但是在讲如何定位系统性能瓶劲之前，请让我讲一下系统性能的定义和测试，因为没有这两件事，后面的定位和优化无从谈起。一、系统性能定义让我们先来说说如何什么是系统性能。这个定义非常关键，如果我们不清楚什么是系统性能，那么我们将无法定位之。我见过很多朋友会觉得这很容易，但是仔细一问，其实他们并没有一个比较系统的方法，所以，在这里我想告诉大家如何系统地来定位性能。 总体来说，系统性能就是两个事： Throughput，吞吐量。也就是每秒钟可以处理的请求数，任务数。 Latency，系统延迟。也就是系统在处理一个请求或一个任务时的延迟。 一般来说，一个系统的性能受到这两个条件的约束，缺一不可。比如，我的系统可以顶得住一百万的并发，但是系统的延迟是2分钟以上，那么，这个一百万的负载毫无意义。系统延迟很短，但是吞吐量很低，同样没有意义。所以，一个好的系统的性能测试必然受到这两个条件的同时作用。 有经验的朋友一定知道，这两个东西的一些关系： Throughput越大，Latency会越差。因为请求量过大，系统太繁忙，所以响应速度自然会低。 Latency越好，能支持的Throughput就会越高。因为Latency短说明处理速度快，于是就可以处理更多的请求。 二、系统性能测试经过上述的说明，我们知道要测试系统的性能，需要我们收集系统的Throughput和Latency这两个值。 首先，需要定义Latency这个值，比如说，对于网站系统响应时间必需是5秒以内（对于某些实时系统可能需要定义的更短，比如5ms以内，这个更根据不同的业务来定义） 其次，开发性能测试工具，一个工具用来制造高强度的Throughput，另一个工具用来测量Latency。对于第一个工具，你可以参考一下“十个免费的Web压力测试工具”，关于如何测量Latency，你可以在代码中测量，但是这样会影响程序的执行，而且只能测试到程序内部的Latency，真正的Latency是整个系统都算上，包括操作系统和网络的延时，你可以使用Wireshark来抓网络包来测量。这两个工具具体怎么做，这个还请大家自己思考去了。 最后，开始性能测试。你需要不断地提升测试的Throughput，然后观察系统的负载情况，如果系统顶得住，那就观察Latency的值。这样，你就可以找到系统的最大负载，并且你可以知道系统的响应延时是多少。 再多说一些， 关于Latency，如果吞吐量很少，这个值估计会非常稳定，当吞吐量越来越大时，系统的Latency会出现非常剧烈的抖动，所以，我们在测量Latency的时候，我们需要注意到Latency的分布，也就是说，有百分之几的在我们允许的范围，有百分之几的超出了，有百分之几的完全不可接受。也许，平均下来的Latency达标了，但是其中仅有50%的达到了我们可接受的范围。那也没有意义。 关于性能测试，我们还需要定义一个时间段。比如：在某个吞吐量上持续15分钟。因为当负载到达的时候，系统会变得不稳定，当过了一两分钟后，系统才会稳定。另外，也有可能是，你的系统在这个负载下前几分钟还表现正常，然后就不稳定了，甚至垮了。所以，需要这么一段时间。这个值，我们叫做峰值极限。 性能测试还需要做Soak Test，也就是在某个吞吐量下，系统可以持续跑一周甚至更长。这个值，我们叫做系统的正常运行的负载极限。 性能测试有很多很复要的东西，比如：burst test等。 这里不能一一详述，这里只说了一些和性能调优相关的东西。总之，性能测试是一细活和累活。三、定位性能瓶颈有了上面的铺垫，我们就可以测试到到系统的性能了，再调优之前，我们先来说说如何找到性能的瓶颈。我见过很多朋友会觉得这很容易，但是仔细一问，其实他们并没有一个比较系统的方法。3.1）查看操作系统负载首先，当我们系统有问题的时候，我们不要急于去调查我们代码，这个毫无意义。我们首要需要看的是操作系统的报告。看看操作系统的CPU利用率，看看内存使用率，看看操作系统的IO，还有网络的IO，网络链接数，等等。Windows下的perfmon是一个很不错的工具，Linux下也有很多相关的命令和工具，比如：SystemTap，LatencyTOP，vmstat, sar, iostat, top, tcpdump等等 。通过观察这些数据，我们就可以知道我们的软件的性能基本上出在哪里。比如：1）先看CPU利用率，如果CPU利用率不高，但是系统的Throughput和Latency上不去了，这说明我们的程序并没有忙于计算，而是忙于别的一些事，比如IO。（另外，CPU的利用率还要看内核态的和用户态的，内核态的一上去了，整个系统的性能就下来了。而对于多核CPU来说，CPU 0 是相当关键的，如果CPU 0的负载高，那么会影响其它核的性能，因为CPU各核间是需要有调度的，这靠CPU0完成）2）然后，我们可以看一下IO大不大，IO和CPU一般是反着来的，CPU利用率高则IO不大，IO大则CPU就小。关于IO，我们要看三个事，一个是磁盘文件IO，一个是驱动程序的IO（如：网卡），一个是内存换页率。这三个事都会影响系统性能。3）然后，查看一下网络带宽使用情况，在Linux下，你可以使用iftop, iptraf, ntop, tcpdump这些命令来查看。或是用Wireshark来查看。4）如果CPU不高，IO不高，内存使用不高，网络带宽使用不高。但是系统的性能上不去。这说明你的程序有问题，比如，你的程序被阻塞了。可能是因为等那个锁，可能是因为等某个资源，或者是在切换上下文。通过了解操作系统的性能，我们才知道性能的问题，比如：带宽不够，内存不够，TCP缓冲区不够，等等，很多时候，不需要调整程序的，只需要调整一下硬件或操作系统的配置就可以了。3.2）使用Profiler测试接下来，我们需要使用性能检测工具，也就是使用某个Profiler来差看一下我们程序的运行性能。如：Java的JProfiler/TPTP/CodePro Profiler，GNU的gprof，IBM的PurifyPlus，Intel的VTune，AMD的CodeAnalyst，还有Linux下的OProfile/perf，后面两个可以让你对你的代码优化到CPU的微指令级别，如果你关心CPU的L1/L2的缓存调优，那么你需要考虑一下使用VTune。 使用这些Profiler工具，可以让你程序中各个模块函数甚至指令的很多东西，如：运行的时间?，调用的次数，CPU的利用率，等等。这些东西对我们来说非常有用。我们重点观察运行时间最多，调用次数最多的那些函数和指令。这里注意一下，对于调用次数多但是时间很短的函数，你可能只需要轻微优化一下，你的性能就上去了（比如：某函数一秒种被调用100万次，你想想如果你让这个函数提高0.01毫秒的时间 ，这会给你带来多大的性能）使用Profiler有个问题我们需要注意一下，因为Profiler会让你的程序运行的性能变低，像PurifyPlus这样的工具会在你的代码中插入很多代码，会导致你的程序运行效率变低，从而没发测试出在高吞吐量下的系统的性能，对此，一般有两个方法来定位系统瓶颈：1）在你的代码中自己做统计，使用微秒级的计时器和函数调用计算器，每隔10秒把统计log到文件中。2）分段注释你的代码块，让一些函数空转，做Hard Code的Mock，然后再测试一下系统的Throughput和Latency是否有质的变化，如果有，那么被注释的函数就是性能瓶颈，再在这个函数体内注释代码，直到找到最耗性能的语句。最后再说一点，对于性能测试，不同的Throughput会出现不同的测试结果，不同的测试数据也会有不同的测试结果。所以，用于性能测试的数据非常重要，性能测试中，我们需要观测试不同Throughput的结果。四、常见的系统瓶颈下面这些东西是我所经历过的一些问题，也许并不全，也许并不对，大家可以补充指正，我纯属抛砖引玉。关于系统架构方面的性能调优，大家可移步看一下《由12306.cn谈谈网站性能技术》，关于Web方面的一些性能调优的东西，大家可以看看《Web开发中需要了解的东西》一文中的性能一章。我在这里就不再说设计和架构上的东西了。一般来说，性能优化也就是下面的几个策略： 用空间换时间。各种cache如CPU L1/L2/RAM到硬盘，都是用空间来换时间的策略。这样策略基本上是把计算的过程一步一步的保存或缓存下来，这样就不用每次用的时候都要再计算一遍，比如数据缓冲，CDN，等。这样的策略还表现为冗余数据，比如数据镜象，负载均衡什么的。 用时间换空间。有时候，少量的空间可能性能会更好，比如网络传输，如果有一些压缩数据的算法（如前些天说的“Huffman 编码压缩算法” 和 “rsync 的核心算法”），这样的算法其实很耗时，但是因为瓶颈在网络传输，所以用时间来换空间反而能省时间。 简化代码。最高效的程序就是不执行任何代码的程序，所以，代码越少性能就越高。关于代码级优化的技术大学里的教科书有很多示例了。如：减少循环的层数，减少递归，在循环中少声明变量，少做分配和释放内存的操作，尽量把循环体内的表达式抽到循环外，条件表达的中的多个条件判断的次序，尽量在程序启动时把一些东西准备好，注意函数调用的开销（栈上开销），注意面向对象语言中临时对象的开销，小心使用异常（不要用异常来检查一些可接受可忽略并经常发生的错误），…… 等等，等等，这连东西需要我们非常了解编程语言和常用的库。 并行处理。如果CPU只有一个核，你要玩多进程，多线程，对于计算密集型的软件会反而更慢（因为操作系统调度和切换开销很大），CPU的核多了才能真正体现出多进程多线程的优势。并行处理需要我们的程序有Scalability，不能水平或垂直扩展的程序无法进行并行处理。从架构上来说，这表再为——是否可以做到不改代码只是加加机器就可以完成性能提升？ 总之，根据2：8原则来说，20%的代码耗了你80%的性能，找到那20%的代码，你就可以优化那80%的性能。 下面的一些东西都是我的一些经验，我只例举了一些最有价值的性能调优的的方法，供你参考，也欢迎补充。4.1）算法调优。算法非常重要，好的算法会有更好的性能。举几个我经历过的项目的例子，大家可以感觉一下。 一个是过滤算法，系统需要对收到的请求做过滤，我们把可以被filter in/out的东西配置在了一个文件中，原有的过滤算法是遍历过滤配置，后来，我们找到了一种方法可以对这个过滤配置进行排序，这样就可以用二分折半的方法来过滤，系统性能增加了50%。 一个是哈希算法。计算哈希算法的函数并不高效，一方面是计算太费时，另一方面是碰撞太高，碰撞高了就跟单向链表一个性能（可参看Hash Collision DoS 问题）。我们知道，算法都是和需要处理的数据很有关系的，就算是被大家所嘲笑的“冒泡排序”在某些情况下（大多数数据是排好序的）其效率会高于所有的排序算法。哈希算法也一样，广为人知的哈希算法都是用英文字典做测试，但是我们的业务在数据有其特殊性，所以，对于还需要根据自己的数据来挑选适合的哈希算法。对于我以前的一个项目，公司内某牛人给我发来了一个哈希算法，结果让我们的系统性能上升了150%。（关于各种哈希算法，你一定要看看StackExchange上的这篇关于各种hash算法的文章?） 分而治之和预处理。以前有一个程序为了生成月报表，每次都需要计算很长的时间，有时候需要花将近一整天的时间。于是我们把我们找到了一种方法可以把这个算法发成增量式的，也就是说我每天都把当天的数据计算好了后和前一天的报表合并，这样可以大大的节省计算时间，每天的数据计算量只需要20分钟，但是如果我要算整个月的，系统则需要10个小时以上（SQL语句在大数据量面前性能成级数性下降）。这种分而治之的思路在大数据面前对性能有很帮助，就像merge排序一样。SQL语句和数据库的性能优化也是这一策略，如：使用嵌套式的Select而不是笛卡尔积的Select，使用视图，等等。 4.2）代码调优。从我的经验上来说，代码上的调优有下面这几点： 字符串操作。这是最费系统性能的事了，无论是strcpy, strcat还是strlen，最需要注意的是字符串子串匹配。所以，能用整型最好用整型。举几个例子，第一个例子是N年前做银行的时候，我的同事喜欢把日期存成字符串（如：2012-05-29 08:30:02），我勒个去，一个select ?where between语句相当耗时。另一个例子是，我以前有个同事把一些状态码用字符串来处理，他的理由是，这样可以在界面上直接显示，后来性能调优的时候，我把这些状态码全改成整型，然后用位操作查状态，因为有一个每秒钟被调用了150K次的函数里面有三处需要检查状态，经过改善以后，整个系统的性能上升了30%左右。还有一个例子是，我以前从事的某个产品编程规范中有一条是要在每个函数中把函数名定义出来，如：const char fname[]=”functionName()”, 这是为了好打日志，但是为什么不声明成 static类型的呢？ 多线程调优。有人说，thread is evil，这个对于系统性能在某些时候是个问题。因为多线程瓶颈就在于互斥和同步的锁上，以及线程上下文切换的成本，怎么样的少用锁或不用锁是根本（比如：多版本并发控制(MVCC)在分布式系统中的应用?中说的乐观锁可以解决性能问题），此外，还有读写锁也可以解决大多数是读操作的并发的性能问题。这里多说一点在C++中，我们可能会使用线程安全的智能指针AutoPtr或是别的一些容器，只要是线程安全的，其不管三七二十一都要上锁，上锁是个成本很高的操作，使用AutoPtr会让我们的系统性能下降得很快，如果你可以保证不会有线程并发问题，那么你应该不要用AutoPtr。我记得我上次我们同事去掉智能指针的引用计数，让系统性能提升了50%以上。对于Java对象的引用计数，如果我猜的没错的话，到处都是锁，所以，Java的性能问题一直是个问题。另外，线程不是越多越好，线程间的调度和上下文切换也是很夸张的事，尽可能的在一个线程里干，尽可能的不要同步线程。这会让你有很多的性能。 内存分配。不要小看程序的内存分配。malloc/realloc/calloc这样的系统调非常耗时，尤其是当内存出现碎片的时候。我以前的公司出过这样一个问题——在用户的站点上，我们的程序有一天不响应了，用GDB跟进去一看，系统hang在了malloc操作上，20秒都没有返回，重启一些系统就好了。这就是内存碎片的问题。这就是为什么很多人抱怨STL有严重的内存碎片的问题，因为太多的小内存的分配释放了。有很多人会以为用内存池可以解决这个问题，但是实际上他们只是重新发明了Runtime-C或操作系统的内存管理机制，完全于事无补。当然解决内存碎片的问题还是通过内存池，具体来说是一系列不同尺寸的内存池（这个留给大家自己去思考）。当然，少进行动态内存分配是最好的。说到内存池就需要说一下池化技术。比如线程池，连接池等。池化技术对于一些短作业来说（如http服务） 相当相当的有效。这项技术可以减少链接建立，线程创建的开销，从而提高性能。 异步操作。我们知道Unix下的文件操作是有block和non-block的方式的，像有些系统调用也是block式的，如：Socket下的select，Windows下的WaitforObject之类的，如果我们的程序是同步操作，那么会非常影响性能，我们可以改成异步的，但是改成异步的方式会让你的程序变复杂。异步方式一般要通过队列，要注间队列的性能问题，另外，异步下的状态通知通常是个问题，比如消息事件通知方式，有callback方式，等，这些方式同样可能会影响你的性能。但是通常来说，异步操作会让性能的吞吐率有很大提升（Throughput），但是会牺牲系统的响应时间（latency）。这需要业务上支持。 语言和代码库。我们要熟悉语言以及所使用的函数库或类库的性能。比如：STL中的很多容器分配了内存后，那怕你删除元素，内存也不会回收，其会造成内存泄露的假像，并可能造成内存碎片问题。再如，STL某些容器的size()==0 ?和 empty()是不一样的，因为，size()是O(n)复杂度，empty()是O(1)的复杂度，这个要小心。Java中的JVM调优需要使用的这些参数：-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold，还需要注意JVM的GC，GC的霸气大家都知道，尤其是full GC（还整理内存碎片），他就像“恐龙特级克赛号”一样，他运行的时候，整个世界的时间都停止了。 4.3）网络调优关于网络调优，尤其是TCP Tuning（你可以以这两个关键词在网上找到很多文章），这里面有很多很多东西可以说。看看Linux下TCP/IP的那么多参数就知道了（顺便说一下，你也许不喜欢Linux，但是你不能否认Linux给我们了很多可以进行内核调优的权力）。强烈建议大家看看《TCP/IP 详解 卷1:协议》这本书。我在这里只讲一些概念上的东西。A） TCP调优我们知道TCP链接是有很多开销的，一个是会占用文件描述符，另一个是会开缓存，一般来说一个系统可以支持的TCP链接数是有限的，我们需要清楚地认识到TCP链接对系统的开销是很大的。正是因为TCP是耗资源的，所以，很多攻击都是让你系统上出现大量的TCP链接，把你的系统资源耗尽。比如著名的SYNC Flood攻击。所以，我们要注意配置KeepAlive参数，这个参数的意思是定义一个时间，如果链接上没有数据传输，系统会在这个时间发一个包，如果没有收到回应，那么TCP就认为链接断了，然后就会把链接关闭，这样可以回收系统资源开销。（注：HTTP层上也有KeepAlive参数）对于像HTTP这样的短链接，设置一个1-2分钟的keepalive非常重要。这可以在一定程度上防止DoS攻击。有下面几个参数（下面这些参数的值仅供参考）：net.ipv4.tcp_keepalive_probes = 5 net.ipv4.tcp_keepalive_intvl = 20 net.ipv4.tcp_fin_timeout = 30对于TCP的TIME_WAIT这个状态，主动关闭的一方进入TIME_WAIT状态，TIME_WAIT状态将持续2个MSL(Max Segment Lifetime)，默认为4分钟，TIME_WAIT状态下的资源不能回收。有大量的TIME_WAIT链接的情况一般是在HTTP服务器上。对此，有两个参数需要注意，net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1前者表示重用TIME_WAIT，后者表示回收TIME_WAIT的资源。TCP还有一个重要的概念叫RWIN（TCP Receive Window Size），这个东西的意思是，我一个TCP链接在没有向Sender发出ack时可以接收到的最大的数据包。为什么这个很重要？因为如果Sender没有收到Receiver发过来ack，Sender就会停止发送数据并会等一段时间，如果超时，那么就会重传。这就是为什么TCP链接是可靠链接的原因。重传还不是最严重的，如果有丢包发生的话，TCP的带宽使用率会马上受到影响（会盲目减半），再丢包，再减半，然后如果不丢包了，就逐步恢复。相关参数如下：net.core.wmem_default = 8388608 net.core.rmem_default = 8388608 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216一般来说，理论上的RWIN应该设置成：吞吐量 ?* 回路时间。Sender端的buffer应该和RWIN有一样的大小，因为Sender端发送完数据后要等Receiver端确认，如果网络延时很大，buffer过小了，确认的次数就会多，于是性能就不高，对网络的利用率也就不高了。也就是说，对于延迟大的网络，我们需要大的buffer，这样可以少一点ack，多一些数据，对于响应快一点的网络，可以少一些buffer。因为，如果有丢包（没有收到ack），buffer过大可能会有问题，因为这会让TCP重传所有的数据，反而影响网络性能。（当然，网络差的情况下，就别玩什么高性能了） 所以，高性能的网络重要的是要让网络丢包率非常非常地小（基本上是用在LAN里），如果网络基本是可信的，这样用大一点的buffer会有更好的网络传输性能（来来回回太多太影响性能了）。另外，我们想一想，如果网络质量非常好，基本不丢包，而业务上我们不怕偶尔丢几个包，如果是这样的话，那么，我们为什么不用速度更快的UDP呢？你想过这个问题了吗？B）UDP调优说到UDP的调优，有一些事我想重点说一样，那就是MTU——最大传输单元（其实这对TCP也一样，因为这是链路层上的东西）。所谓最大传输单元，你可以想像成是公路上的公交车，假设一个公交车可以最多坐70人，带宽就像是公路的车道数一样，如果一条路上最多可以容下100辆公交车，那意味着我最多可以运送7000人，但是如果公交车坐不满，比如平均每辆车只有20人，那么我只运送了2000人，于是我公路资源（带宽资源）就被浪费了。 所以，我们对于一个UDP的包，我们要尽量地让他大到MTU的最大尺寸再往网络上传，这样可以最大化带宽利用率。对于这个MTU，以太网是1500字节，光纤是4352字节，802.11无线网是7981。但是，当我们用TCP/UDP发包的时候，我们的有效负载Payload要低于这个值，因为IP协议会加上20个字节，UDP会加上8个字节（TCP加的更多），所以，一般来说，你的一个UDP包的最大应该是1500-8-20=1472，这是你的数据的大小。当然，如果你用光纤的话， 这个值就可以更大一些。（顺便说一下，对于某些NB的千光以态网网卡来说，在网卡上，网卡硬件如果发现你的包的大小超过了MTU，其会帮你做fragment，到了目标端又会帮你做重组，这就不需要你在程序中处理了）再多说一下，使用Socket编程的时候，你可以使用setsockopt() 设置 SO_SNDBUF/SO_RCVBUF 的大小，TTL和KeepAlive这些关键的设置，当然，还有很多，具体你可以查看一下Socket的手册。最后说一点，UDP还有一个最大的好处是multi-cast多播，这个技术对于你需要在内网里通知多台结点时非常方便和高效。而且，多播这种技术对于机会的水平扩展（需要增加机器来侦听多播信息）也很有利。C）网卡调优对于网卡，我们也是可以调优的，这对于千兆以及网网卡非常必要，在Linux下，我们可以用ifconfig查看网上的统计信息，如果我们看到overrun上有数据，我们就可能需要调整一下txqueuelen的尺寸（一般默认为1000），我们可以调大一些，如：ifconfig eth0 txqueuelen 5000。Linux下还有一个命令叫：ethtool可以用于设置网卡的缓冲区大小。在Windows下，我们可以在网卡适配器中的高级选项卡中调整相关的参数（如：Receive Buffers, Transmit Buffer等，不同的网卡有不同的参数）。把Buffer调大对于需要大数据量的网络传输非常有效。D）其它网络性能关于多路复用技术，也就是用一个线程来管理所有的TCP链接，有三个系统调用要重点注意：一个是select，这个系统调用只支持上限1024个链接，第二个是poll，其可以突破1024的限制，但是select和poll本质上是使用的轮询机制，轮询机制在链接多的时候性能很差，因主是O(n)的算法，所以，epoll出现了，epoll是操作系统内核支持的，仅当在链接活跃时，操作系统才会callback，这是由操作系统通知触发的，但其只有Linux Kernel 2.6以后才支持（准确说是2.5.44中引入的），当然，如果所有的链接都是活跃的，过多的使用epoll_ctl可能会比轮询的方式还影响性能，不过影响的不大。另外，关于一些和DNS Lookup的系统调用要小心，比如：gethostbyaddr/gethostbyname，这个函数可能会相当的费时，因为其要到网络上去找域名，因为DNS的递归查询，会导致严重超时，而又不能通过设置什么参数来设置time out，对此你可以通过配置hosts文件来加快速度，或是自己在内存中管理对应表，在程序启动时查好，而不要在运行时每次都查。另外，在多线程下面，gethostbyname会一个更严重的问题，就是如果有一个线程的gethostbyname发生阻塞，其它线程都会在gethostbyname处发生阻塞，这个比较变态，要小心。（你可以试试GNU的gethostbyname_r()，这个的性能要好一些） 这种到网上找信息的东西很多，比如，如果你的Linux使用了NIS，或是NFS，某些用户或文件相关的系统调用就很慢，所以要小心。4.4）系统调优A）I/O模型前面说到过select/poll/epoll这三个系统调用，我们都知道，Unix/Linux下把所有的设备都当成文件来进行I/O，所以，那三个操作更应该算是I/O相关的系统调用。说到 ?I/O模型，这对于我们的I/O性能相当重要，我们知道，Unix/Linux经典的I/O方式是（关于Linux下的I/O模型，大家可以读一下这篇文章《使用异步I/O大大提高性能》）：第一种，同步阻塞式I/O，这个不说了。第二种，同步无阻塞方式。其通过fctnl设置?O_NONBLOCK 来完成。第三种，对于select/poll/epoll这三个是I/O不阻塞，但是在事件上阻塞，算是：I/O异步，事件同步的调用。第四种，AIO方式。这种I/O 模型是一种处理与 I/O 并行的模型。I/O请求会立即返回，说明请求已经成功发起了。在后台完成I/O操作时，向应用程序发起通知，通知有两种方式：一种是产生一个信号，另一种是执行一个基于线程的回调函数来完成这次 I/O 处理过程。第四种因为没有任何的阻塞，无论是I/O上，还是事件通知上，所以，其可以让你充分地利用CPU，比起第二种同步无阻塞好处就是，第二种要你一遍一遍地去轮询。Nginx之所所以高效，是其使用了epoll和AIO的方式来进行I/O的。再说一下Windows下的I/O模型，a）一个是WriteFile系统调用，这个系统调用可以是同步阻塞的，也可以是同步无阻塞的，关于看文件是不是以Overlapped打开的。关于同步无阻塞，需要设置其最后一个参数Overlapped，微软叫Overlapped I/O，你需要WaitForSingleObject才能知道有没有写完成。这个系统调用的性能可想而知。b）另一个叫WriteFileEx的系统调用，其可以实现异步I/O，并可以让你传入一个callback函数，等I/O结束后回调之， 但是这个回调的过程Windows是把callback函数放到了APC（Asynchronous Procedure Calls）的队列中，然后，只用当应用程序当前线程成为可被通知状态（Alterable）时，才会被回调。只有当你的线程使用了这几个函数时WaitForSingleObjectEx,?WaitForMultipleObjectsEx,?MsgWaitForMultipleObjectsEx,?SignalObjectAndWait?和?SleepEx，线程才会成为Alterable状态。可见，这个模型，还是有wait，所以性能也不高。c）然后是IOCP – IO Completion Port，IOCP会把I/O的结果放在一个队列中，但是，侦听这个队列的不是主线程，而是专门来干这个事的一个或多个线程去干（老的平台要你自己创建线程，新的平台是你可以创建一个线程池）。IOCP是一个线程池模型。这个和Linux下的AIO模型比较相似，但是实现方式和使用方式完全不一样。当然，真正提高I/O性能方式是把和外设的I/O的次数降到最低，最好没有，所以，对于读来说，内存cache通常可以从质上提升性能，因为内存比外设快太多了。对于写来说，cache住要写的数据，少写几次，但是cache带来的问题就是实时性的问题，也就是latency会变大，我们需要在写的次数上和相应上做权衡。B）多核CPU调优关于CPU的多核技术，我们知道，CPU0是很关键的，如果0号CPU被用得过狠的话，别的CPU性能也会下降，因为CPU0是有调整功能的，所以，我们不能任由操作系统负载均衡，因为我们自己更了解自己的程序，所以，我们可以手动地为其分配CPU核，而不会过多地占用CPU0，或是让我们关键进程和一堆别的进程挤在一起。 对于Windows来说，我们可以通过“任务管理器”中的“进程”而中右键菜单中的“设置相关性……”（Set Affinity…）来设置并限制这个进程能被运行在哪些核上。 对于Linux来说，可以使用taskset命令来设置（你可以通过安装schedutils来安装这个命令：apt-get install schedutils） 多核CPU还有一个技术叫NUMA技术（Non-Uniform Memory Access）。传统的多核运算是使用SMP(Symmetric Multi-Processor )模式，多个处理器共享一个集中的存储器和I/O总线。于是就会出现一致存储器访问的问题，一致性通常意味着性能问题。NUMA模式下，处理器被划分成多个node， 每个node有自己的本地存储器空间。关于NUMA的一些技术细节，你可以查看一下这篇文章《Linux 的 NUMA 技术》，在Linux下，对NUMA调优的命令是：numactl?。如下面的命令：（指定命令“myprogram arg1 arg2”运行在node 0 上，其内存分配在node 0 和 1上）numactl --cpubind=0 --membind=0,1 myprogram arg1 arg2当然，上面这个命令并不好，因为内存跨越了两个node，这非常不好。最好的方式是只让程序访问和自己运行一样的node，如：numactl --membind 1 --cpunodebind 1 --localalloc myapplicationC）文件系统调优关于文件系统，因为文件系统也是有cache的，所以，为了让文件系统有最大的性能。首要的事情就是分配足够大的内存，这个非常关键，在Linux下可以使用free命令来查看 free/used/buffers/cached，理想来说，buffers和cached应该有40%左右。然后是一个快速的硬盘控制器，SCSI会好很多。最快的是Intel SSD 固态硬盘，速度超快，但是写次数有限。接下来，我们就可以调优文件系统配置了，对于Linux的Ext3/4来说，几乎在所有情况下都有所帮助的一个参数是关闭文件系统访问时间，在/etc/fstab下看看你的文件系统 有没有noatime参数（一般来说应该有），还有一个是dealloc，它可以让系统在最后时刻决定写入文件发生时使用哪个块，可优化这个写入程序。还要注间一下三种日志模式：data=journal、data=ordered和data=writeback。默认设置data=ordered提供性能和防护之间的最佳平衡。当然，对于这些来说，ext4的默认设置基本上是最佳优化了。这里介绍一个Linux下的查看I/O的命令—— iotop，可以让你看到各进程的磁盘读写的负载情况。其它还有一些关于NFS、XFS的调优，大家可以上google搜索一些相关优化的文章看看。关于各文件系统，大家可以看一下这篇文章——《Linux日志文件系统及性能分析》4.5）数据库调优数据库调优并不是我的强项，我就仅用我非常有限的知识说上一些吧。注意，下面的这些东西并不一定正确，因为在不同的业务场景，不同的数据库设计下可能会得到完全相反的结论，所以，我仅在这里做一些一般性的说明，具体问题还要具体分析。A）数据库引擎调优我对数据库引擎不是熟，但是有几个事情我觉得是一定要去了解的。 数据库的锁的方式。这个非常非常地重要。并发情况下，锁是非常非常影响性能的。各种隔离级别，行锁，表锁，页锁，读写锁，事务锁，以及各种写优先还是读优先机制。性能最高的是不要锁，所以，分库分表，冗余数据，减少一致性事务处理，可以有效地提高性能。NoSQL就是牺牲了一致性和事务处理，并冗余数据，从而达到了分布式和高性能。 数据库的存储机制。不但要搞清楚各种类型字段是怎么存储的，更重要的是数据库的数据存储方式，是怎么分区的，是怎么管理的，比如Oracle的数据文件，表空间，段，等等。了解清楚这个机制可以减轻很多的I/O负载。比如：MySQL下使用show engines;可以看到各种存储引擎的支持。不同的存储引擎有不同的侧重点，针对不同的业务或数据库设计会让你有不同的性能。 数据库的分布式策略。最简单的就是复制或镜像，需要了解分布式的一致性算法，或是主主同步，主从同步。通过了解这种技术的机理可以做到数据库级别的水平扩展。 B）SQL语句优化关于SQL语句的优化，首先也是要使用工具，比如：MySQL SQL Query Analyzer，Oracle SQL Performance Analyzer，或是微软SQL Query Analyzer，基本上来说，所有的RMDB都会有这样的工具，来让你查看你的应用中的SQL的性能问题。 还可以使用explain来看看SQL语句最终Execution Plan会是什么样的。还有一点很重要，数据库的各种操作需要大量的内存，所以服务器的内存要够，优其应对那些多表查询的SQL语句，那是相当的耗内存。下面我根据我有限的数据库SQL的知识说几个会有性能问题的SQL： 全表检索。比如：select * from user where lastname = “xxxx”，这样的SQL语句基本上是全表查找，线性复杂度O(n)，记录数越多，性能也越差（如：100条记录的查找要50ms，一百万条记录需要5分钟）。对于这种情况，我们可以有两种方法提高性能：一种方法是分表，把记录数降下来，另一种方法是建索引（为lastname建索引）。索引就像是key-value的数据结构一样，key就是where后面的字段，value就是物理行号，对索引的搜索复杂度是基本上是O(log(n)) ——用B-Tree实现索引（如：100条记录的查找要50ms，一百万条记录需要100ms）。 索引。对于索引字段，最好不要在字段上做计算、类型转换、函数、空值判断、字段连接操作，这些操作都会破坏索引原本的性能。当然，索引一般都出现在Where或是Order by字句中，所以对Where和Order by子句中的子段最好不要进行计算操作，或是加上什么NOT之类的，或是使用什么函数。 多表查询。关系型数据库最多的操作就是多表查询，多表查询主要有三个关键字，EXISTS，IN和JOIN（关于各种join，可以参看图解SQL的Join一文）。基本来说，现代的数据引擎对SQL语句优化得都挺好的，JOIN和IN/EXISTS在结果上有些不同，但性能基本上都差不多。有人说，EXISTS的性能要好于IN，IN的性能要好于JOIN，我各人觉得，这个还要看你的数据、schema和SQL语句的复杂度，对于一般的简单的情况来说，都差不多，所以千万不要使用过多的嵌套，千万不要让你的SQL太复杂，宁可使用几个简单的SQL也不要使用一个巨大无比的嵌套N级的SQL。还有人说，如果两个表的数据量差不多，Exists的性能可能会高于In，In可能会高于Join，如果这两个表一大一小，那么子查询中，Exists用大表，In则用小表。这个，我没有验证过，放在这里让大家讨论吧。另，有一篇关于SQL Server的文章大家可以看看《IN vs JOIN vs EXISTS》 JOIN操作。有人说，Join表的顺序会影响性能，只要Join的结果集是一样，性能和join的次序无关。因为后台的数据库引擎会帮我们优化的。Join有三种实现算法，嵌套循环，排序归并，和Hash式的Join。（MySQL只支持第一种） 嵌套循环，就好像是我们常见的多重嵌套循环。注意，前面的索引说过，数据库的索引查找算法用的是B-Tree，这是O(log(n))的算法，所以，整个算法复法度应该是O(log(n)) *?O(log(m)) 这样的。 Hash式的Join，主要解决嵌套循环的O(log(n))的复杂，使用一个临时的hash表来标记。 排序归并，意思是两个表按照查询字段排好序，然后再合并。当然，索引字段一般是排好序的。 还是那句话，具体要看什么样的数据，什么样的SQL语句，你才知道用哪种方法是最好的。 部分结果集。我们知道MySQL里的Limit关键字，Oracle里的rownum，SQL Server里的Top都是在限制前几条的返回结果。这给了我们数据库引擎很多可以调优的空间。一般来说，返回top n的记录数据需要我们使用order by，注意在这里我们需要为order by的字段建立索引。有了被建索引的order by后，会让我们的select语句的性能不会被记录数的所影响。使用这个技术，一般来说我们前台会以分页方式来显现数据，Mysql用的是OFFSET，SQL Server用的是FETCH NEXT，这种Fetch的方式其实并不好是线性复杂度，所以，如果我们能够知道order by字段的第二页的起始值，我们就可以在where语句里直接使用>=的表达式来select，这种技术叫seek，而不是fetch，seek的性能比fetch要高很多。 字符串。正如我前面所说的，字符串操作对性能上有非常大的恶梦，所以，能用数据的情况就用数字，比如：时间，工号，等。 全文检索。千万不要用Like之类的东西来做全文检索，如果要玩全文检索，可以尝试使用Sphinx。 其它。 不要select *，而是明确指出各个字段，如果有多个表，一定要在字段名前加上表名，不要让引擎去算。 不要用Having，因为其要遍历所有的记录。性能差得不能再差。 尽可能地使用UNION ALL ?取代 ?UNION。 索引过多，insert和delete就会越慢。而update如果update多数索引，也会慢，但是如果只update一个，则只会影响一个索引表。 等等。 关于SQL语句的优化，网上有很多文章， 不同的数据库引擎有不同的优化技巧，正如本站以前转发的《MySQL性能优化的最佳20+条经验》先写这么多吧，欢迎大家指正补充。（转载本站文章请注明作者和出处 酷 壳 – CoolShell.cn ，请勿用于任何商业用途）