Netty优化指导文档
目录
1 优化概述 2
1.1 服务架构 2
1.2 调优目的 2
2 详细优化方法 3
2.1操作系统优化 3
2.2 Netty性能调优 9
2.2.1线程池优化 9
2.2.2 心跳优化 10
2.2.3 接收和发送缓冲区调优 12
2.2.4 内存池 14
2.2.5 防止I/O线程被意外阻塞 16
2.2.6 流控 17
3 Q&A 19
1优化概述
1.1服务架构
1.2调优目的
根据当客户端的并发连接数达到数十万或者数百万时,系统一个较小的抖动就会导致很严重的后果,例如服务端的GC,导致应用暂停(STW)的GC持续几秒,就会导致海量的设备端设备掉线或者消息积压,一旦系统恢复,会有海量的设备接入或者海量的数据发送,很可能瞬间就把服务器冲垮。
设备接入通常有如下特点:
1.使用的网络主要是移动网络,网络质量不稳定,例如在一些偏远地区、丘陵地带等信号很差,网络容易闪断;
2.海量的端测设备接入,而且通常使用长连接,服务端的压力很大
3.不稳定,消息丢失,重复发送,延迟送达,过期发送时有发生
4.协议不统一,有各种私有协议,开发和测试成本较高
要想实现海量设备的接入,需要对操作系统相关参数、Netty框架、JVM GC参数,甚至业务代码针对性的优化,各种优化要素互相影响,设置或者组合不当就容易导致性能问题,这也是服务端实现海量设备接入的最大挑战。
2详细优化方法
通信优化方式主要分为两个方向优化,操作系统配置和Netty本身代码实现上两点调整。
2.1操作系统优化
要实现百万级的长连接接入,首先需要对服务端的操作系统参数进行性能优化,如果保持出厂的默认设置,性能是无法满足业务需求。
2.1.1文件描述符
在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。
一般当前用户的每个进程最多允许同时打开1024个文件,这1024个文件中还得除去每个进程必然打开的标准输入,标准输出,标准错误,服务器监听 socket,进程间通讯的unix域socket等文件,那么剩下的可用于客户端socket连接的文件数就只有大概1024-10=1014个左右。也就是说缺省情况下,基于Linux的通讯程序最多允许同时1014个TCP并发连接。
对于想支持更高数量的TCP并发连接的通讯处理程序,就必须修改Linux对当前用户的进程同时打开的文件数量。
修改单个进程打开最大文件数限制的最简单的办法就是使用ulimit命令:$ ulimit –n 1000000
如果系统回显类似于"Operation not permitted"之类的话,说明上述限制修改失败,实际上是因为在中指定的数值超过了Linux系统对该用户打开文件数的软限制或硬限制。因此,就需要修改Linux系统对用户的关于打开文件数的软限制和硬限制。
(1)软限制(soft limit):是指Linux在当前系统能够承受的范围内进一步限制用户同时打开的文件数;
(2)硬限制(hardlimit):是根据系统硬件资源状况(主要是系统内存)计算出来的系统最多可同时打开的文件数量。
1.设置系统最大文件句柄数
//查看
cat /proc/sys/fs/file-max//修改
在 /etc/sysctl.conf 插入 fs.file-max = 1000000//配置生效
sysctl -p
2.设置单进程打开的最大句柄数
设置单进程打开的文件最大句柄数,执行命令ulimit -n查看当前设置是否满足要求:
[root@server1 /]# ulimit -n
1024
当并发接入的Tcp连接数超过上限时,就会提示“Too many open files”,所有的新客户端接入将会失败。通过vim /etc/security/limits.conf 修改配置参数:
- soft nofile 1000000
- hard nofile 1000000
修改配置参数后注销后重新登录再使用ulimit -a查看是否生效。
2.1.2 TCP/IP相关参数
1.查看客户端端口范围限制
[root@server1 nginx]# cat /proc/sys/net/ipv4/ip_local_port_range
32768 61000
2.客户端修改端口范围的限制
以上范围大概也就是共61000-32768=28232个端可用,单个IP对外只能发送28232个TCP请求。
以管理员身份,把端口的范围区间增到最大:
echo “1024 65535”> /proc/sys/net/ipv4/ip_local_port_range
现在有64511个端口可用.
以上做法只是临时,系统下次重启,会还原。 需要做永久修改, vi /etc/sysctl.conf文件,增加一行内容
net.ipv4.ip_local_port_range= 1024 65535
sysctl -p
3.优化TCP参数,通过vim /etc/sysctl.conf 修改网络参数
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_wmem = 4096 4096 16777216
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
参数说明:
(1)net.ipv4.tcp_mem:
分配给tcp连接的内存,单位是page(1个Page通常是4KB,可以通过getconf PAGESIZE命令查看),三个值分别是最小、默认、和最大。比如以上配置中的最大是3145728,那分配给tcp的最大内存=31457284 / 1024 / 1024 = 12GB。一个TCP连接大约占7.5KB,粗略可以算出百万连接≈(7.5*1000000)/4=1875000 <3145728,足以满足测试所需。
(2)net.ipv4.tcp_wmem:
为每个TCP连接分配的写缓冲区内存大小,单位是字节。三个值分别是最小、默认、和最大。
(3)net.ipv4.tcp_rmem:
为每个TCP连接分配的读缓冲区内存大小,单位是字节。三个值分别是最小、默认、和最大。
(4)net.ipv4.tcp_keepalive_time:
最近一次数据包发送与第一次keep alive探测消息发送的事件间隔,用于确认TCP连接是否有效。
(5)net.ipv4.tcp_keepalive_intvl:
在未获得探测消息响应时,发送探测消息的时间间隔。
(6)net.ipv4.tcp_keepalive_probes:
判断TCP连接失效连续发送的探测消息个数,达到之后判定连接失效。
(7)net.ipv4.tcp_tw_reuse:
是否允许将TIME_WAIT Socket 重新用于新的TCP连接,默认为0,表示关闭。
(8)net.ipv4.tcp_tw_recycle:
是否开启TIME_WAIT Socket 的快速回收功能,默认为0,表示关闭。
(9)net.ipv4.tcp_fin_timeout:
套接字自身关闭时保持在FIN_WAIT_2 状态的时间。默认为60。
修改完后,执行以下命令使之立即生效:
#sysctl -p
2.1.3多网卡队列和软中断
(1)配置多队列网卡
多队列网卡是一种技术,最初是用来解决网络IO QoS (quality of service)问题的,后来随着网络IO的带宽的不断提升,单核CPU不能完全处满足网卡的需求,通过多队列网卡驱动的支持,将各个队列通过中断绑定到不同的核上,以满足网卡的需求。
1、判断当前系统环境是否支持多队列网卡,执行命令:
lspci -vvv
如果在Ethernet项中。含有MSI-X: Enable+ Count=9 Masked-语句,则说明当前系统环境是支持多队列网卡的,否则不支持。
2、ethtool -l eth0命令可以看到eth0网卡是否支持多队列,最多支持多少、当前开启多少
3、设置网卡当前使用多队列。运行命令:ethtool -L eth0 combined N为要使能的队列数
4、要确保多队列确实生效,可以查看文件:
ls /sys/class/net/eth0/queues/
rx-0 rx-2 rx-4 rx-6 tx-0 tx-2 tx-4 tx-6
rx-1 rx-3 rx-5 rx-7 tx-1 tx-3 tx-5 tx-7
如上,如果rx数量是设定值,则正确。
举例:
使用lspci -vvv命令查看网卡的参数,Ethernet controller的条目内容,如果有MSI-X && Enable+ && TabSize > 1,则该网卡是多队列网卡,如图1.1所示。
图1.1 lspci
Message Signaled Interrupts(MSI)是PCI规范的一个实现,可以突破CPU 256条interrupt的限制,使每个设备具有多个中断线变成可能,多队列网卡驱动给每个queue申请了MSI。MSI-X是MSI数组,Enable+指使能,TabSize是数组大小。
cat /etc/modprobe.conf查看网卡驱动。
broadcom网卡的驱动为e1000,默认打开网卡多队列。如图1.2。
图1.2 broadcom e1000
intel网卡的驱动为igb,默认不打开网卡多队列,需要添加options igb RSS=8,8(不同网卡之间的配置用“逗号”隔开)。如图1.3。
图1.3 intel igb
修改完驱动后需要重启。查看是否打开了网卡多队列,以broadcom网卡为例。cat /proc/interrupt | grep eth。产生了8个网卡队列,并且对应着不同的中断。如图1.4。
图1.4 打开网卡多队列
(2)配置软中断
对于不支持多队列网卡的系统,如果内核版本支持RPS,linux 2.6.35以上版本可以设置rps,主要目的是将数据包的处理均匀打散在每个cpu上。提升网络处理能力。
RFS需要内核编译CONFIG_RPS选项,RFS才起作用。全局数据流表(rps_sock_flow_table)的总数可以通过下面的参数来设置:
/proc/sys/net/core/rps_sock_flow_entries
每个队列的数据流表总数可以通过下面的参数来设置:
/sys/class/net//queues/rx/rps_flow_cnt
echo ff > /sys/class/net//queues/rx-/rps_cpus
echo 4096 > /sys/class/net//queues/rx-/rps_flow_cnt
echo 30976 > /proc/sys/net/core/rps_sock_flow_entries
对于2个物理cpu,8核的机器为ff,具体计算方法是第一颗cpu是00000001,第二个cpu是00000010,第3个cpu是 00000100,依次类推,由于是所有的cpu都负担,所以所有的cpu数值相加,得到的数值为11111111,十六进制就刚好是ff。而对于 /proc/sys/net/core/rps_sock_flow_entries的数值是根据你的网卡多少个通道,计算得出的数据,例如你是8通道的 网卡,那么1个网卡,每个通道设置4096的数值,8*4096就是/proc/sys/net/core/rps_sock_flow_entries 的数值,对于内存大的机器可以适当调大rps_flow_cnt,
参考资料:Linux RPS/RFS 实现原理
2.2 Netty性能调优
2.2.1线程池优化
项目3.1优化点:
可以配置监听多端口。
优化指导方案:
对于Netty服务端,通常只需要启动一个监听端口用于端侧设备接入,但是如果集群实例较少,甚至是单机部署,那么在短时间内大量设备接入时,需要对服务端的监听方式和线程模型做优化,即服务端监听多个端口,利用主从Reactor线程模型。由于同时监听了多个端口,每个ServerSocketChannel都对应一个独立的Acceptor线程,这样就能并行处理,加速端侧设备的接入速度,减少端侧设备的连接超时失败率,提高单节点服务端的处理性能。
public void start(int beginPort, int nPort) {
…省略…
//这里开启 10000到100099这100个端口
for (int i = 0; i < nPort; i++) {
int port = beginPort + i;
bootstrap.bind(port).addListener((ChannelFutureListener) future -> {
System.out.println("端口绑定成功: " + port);
});
}
}
2.2.2 心跳优化
项目3.1优化点:
目前项目3.0已设置各心跳时间180s,可暂不做优化。
优化指导方案:
百万级的推送服务,意味着会存在百万个长连接,每个长连接都需要靠和App之间的心跳来维持链路。合理设置心跳周期是非常重要的工作,推送服务的心跳周期设置需要考虑移动无线网络的特点。
当一台智能手机连上移动网络时,其实并没有真正连接上Internet,运营商分配给手机的IP其实是运营商的内网IP,手机终端要连接上Internet还必须通过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来说就是手机终端连接Internet 其实就是移动内网IP,端口,外网IP之间相互映射。
GGSN(GateWay GPRS Support Note)模块就实现了NAT功能,由于大部分的移动无线网络运营商为了减少网关NAT映射表的负荷,如果一个链路有一段时间没有通信时就会删除其对应表,造成链路中断,正是这种刻意缩短空闲连接的释放超时,原本是想节省信道资源的作用,没想到让互联网的应用不得以远高于正常频率发送心跳来维护推送的长连接。以中移动的2.5G网络为例,大约5分钟左右的基带空闲,连接就会被释放。
由于移动无线网络的特点,推送服务的心跳周期并不能设置的太长,否则长连接会被释放,造成频繁的客户端重连,但是也不能设置太短,否则在当前缺乏统一心跳框架的机制下很容易导致信令风暴(例如微信心跳信令风暴问题)。具体的心跳周期并没有统一的标准,180S也许是个不错的选择,微信为300S。
在Netty中,可以通过在ChannelPipeline中增加IdleStateHandler的方式实现心跳检测,在构造函数中指定链路空闲时间,然后实现空闲回调接口,实现心跳的发送和检测,代码如下:
public void initChannel({@link Channel} channel) {
channel.pipeline().addLast(“idleStateHandler”, new {@link IdleStateHandler}(0, 0, 180));
channel.pipeline().addLast(“myHandler”, new MyHandler());
}
拦截链路空闲事件并处理心跳:
public class MyHandler extends {@link ChannelHandlerAdapter} {
{@code @Override}
public void userEventTriggered({@link ChannelHandlerContext} ctx, {@link Object} evt) throws {@link Exception} {
if (evt instanceof {@link IdleStateEvent}} {
//心跳处理
}
}
}
2.2.3 接收和发送缓冲区调优
项目3.1优化点:
配置缓冲区动态扩展AdaptiveRecvByteBufAllocator。
优化指导方案:
对于长链接,每个链路都需要维护自己的消息接收和发送缓冲区,JDK原生的NIO类库使用的是java.nio.ByteBuffer,它实际是一个长度固定的Byte数组,我们都知道数组无法动态扩容,ByteBuffer也有这个限制。
容量无法动态扩展会给用户带来一些麻烦,例如由于无法预测每条消息报文的长度,可能需要预分配一个比较大的ByteBuffer,这通常也没有问题。但是在海量推送服务系统中,这会给服务端带来沉重的内存负担。假设单条推送消息最大上限为10K,消息平均大小为5K,为了满足10K消息的处理,ByteBuffer的容量被设置为10K,这样每条链路实际上多消耗了5K内存,如果长链接链路数为100万,每个链路都独立持有ByteBuffer接收缓冲区,则额外损耗的总内存 Total(M) = 1000000 * 5K = 4882M。内存消耗过大,不仅仅增加了硬件成本,而且大内存容易导致长时间的Full GC,对系统稳定性会造成比较大的冲击。
实际上,最灵活的处理方式就是能够动态调整内存,即接收缓冲区可以根据以往接收的消息进行计算,动态调整内存,利用CPU资源来换内存资源,具体的策略如下:
ByteBuffer支持容量的扩展和收缩,可以按需灵活调整,以节约内存;
接收消息的时候,可以按照指定的算法对之前接收的消息大小进行分析,并预测未来的消息大小,按照预测值灵活调整缓冲区容量,以做到最小的资源损耗满足程序正常功能。
幸运的是,Netty提供的ByteBuf支持容量动态调整,对于接收缓冲区的内存分配器,Netty提供了两种:
FixedRecvByteBufAllocator:固定长度的接收缓冲区分配器,由它分配的ByteBuf长度都是固定大小的,并不会根据实际数据报的大小动态收缩。但是,如果容量不足,支持动态扩展。动态扩展是Netty ByteBuf的一项基本功能,与ByteBuf分配器的实现没有关系;
AdaptiveRecvByteBufAllocator:容量动态调整的接收缓冲区分配器,它会根据之前Channel接收到的数据报大小进行计算,如果连续填充满接收缓冲区的可写空间,则动态扩展容量。如果连续2次接收到的数据报都小于指定值,则收缩当前的容量,以节约内存。
相对于FixedRecvByteBufAllocator,使用AdaptiveRecvByteBufAllocator更为合理,可以在创建客户端或者服务端的时候指定RecvByteBufAllocator,代码如下:
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
如果默认没有设置,则使用AdaptiveRecvByteBufAllocator。
另外值得注意的是,无论是接收缓冲区还是发送缓冲区,缓冲区的大小建议设置为消息的平均大小,不要设置成最大消息的上限,这会导致额外的内存浪费。通过如下方式可以设置接收缓冲区的初始大小:
/**
* Creates a new predictor with the specified parameters.
*
* @param minimum
* the inclusive lower bound of the expected buffer size
* @param initial
* the initial buffer size when no feed back was received
* @param maximum
* the inclusive upper bound of the expected buffer size
/
public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum)
2.2.4 内存池
项目3.1优化点:
目前项目3.0的NioEventLoop配置1个boss线程组和2个work线程组,目前优化可以适当增加2倍,2个boss和4个work。不使用默认CPU核数2,为了异常高并发抢占机器太多资源而导致其它服务异常。
优化指导方案:
推送服务器承载了海量的长链接,每个长链接实际就是一个会话。如果每个会话都持有心跳数据、接收缓冲区、指令集等数据结构,而且这些实例随着消息的处理朝生夕灭,这就会给服务器带来沉重的GC压力,同时消耗大量的内存。
最有效的解决策略就是使用内存池,每个NioEventLoop线程处理N个链路,在线程内部,链路的处理时串行的。假如A链路首先被处理,它会创建接收缓冲区等对象,待解码完成之后,构造的POJO对象被封装成Task后投递到后台的线程池中执行,然后接收缓冲区会被释放,每条消息的接收和处理都会重复接收缓冲区的创建和释放。如果使用内存池,则当A链路接收到新的数据报之后,从NioEventLoop的内存池中申请空闲的ByteBuf,解码完成之后,调用release将ByteBuf释放到内存池中,供后续B链路继续使用。
使用内存池优化之后,单个NioEventLoop的ByteBuf申请和GC次数从原来的N = 1000000/64 = 15625 次减少为最少0次(假设每次申请都有可用的内存)。
下面我们以推特使用Netty4的PooledByteBufAllocator进行GC优化作为案例,对内存池的效果进行评估,结果如下:
垃圾生成速度是原来的1/5,而垃圾清理速度快了5倍。使用新的内存池机制,几乎可以把网络带宽压满。
Netty4之前的版本问题如下:每当收到新信息或者用户发送信息到远程端,Netty 3均会创建一个新的堆缓冲区。这意味着,对应每一个新的缓冲区,都会有一个new byte[capacity]。这些缓冲区会导致GC压力,并消耗内存带宽。为了安全起见,新的字节数组分配时会用零填充,这会消耗内存带宽。然而,用零填充的数组很可能会再次用实际的数据填充,这又会消耗同样的内存带宽。如果Java虚拟机(JVM)提供了创建新字节数组而又无需用零填充的方式,那么我们本来就可以将内存带宽消耗减少50%,但是目前没有那样一种方式。
在Netty 4中实现了一个新的ByteBuf内存池,它是一个纯Java版本的 jemalloc (Facebook也在用)。现在,Netty不会再因为用零填充缓冲区而浪费内存带宽了。不过,由于它不依赖于GC,开发人员需要小心内存泄漏。如果忘记在处理程序中释放缓冲区,那么内存使用率会无限地增长。
Netty默认不使用内存池,需要在创建客户端或者服务端的时候进行指定,代码如下:
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
使用内存池之后,内存的申请和释放必须成对出现,即retain()和release()要成对出现,否则会导致内存泄露。
值得注意的是,如果使用内存池,完成ByteBuf的解码工作之后必须显式的调用ReferenceCountUtil.release(msg)对接收缓冲区ByteBuf进行内存释放,否则它会被认为仍然在使用中,这样会导致内存泄露。
2.2.5 防止I/O线程被意外阻塞
项目3.1优化点:
排查服务在通信业务处理阶段是否有不必要的I/O操作,尽量避免。
优化指导方案:
通常情况下,大家都知道不能在Netty的I/O线程上做执行时间不可控的操作,例如访问数据库、发送Email等。但是有个常用但是非常危险的操作却容易被忽略,那便是记录日志。
通常,在生产环境中,需要实时打印接口日志,其它日志处于ERROR级别,当推送服务发生I/O异常之后,会记录异常日志。如果当前磁盘的WIO比较高,可能会发生写日志文件操作被同步阻塞,阻塞时间无法预测。这就会导致Netty的NioEventLoop线程被阻塞,Socket链路无法被及时关闭、其它的链路也无法进行读写操作等。
以最常用的log4j为例,尽管它支持异步写日志(AsyncAppender),但是当日志队列满之后,它会同步阻塞业务线程,直到日志队列有空闲位置可用,相关代码如下:
synchronized (this.buffer) {
while (true) {
int previousSize = this.buffer.size();
if (previousSize < this.bufferSize) {
this.buffer.add(event);
if (previousSize != 0) break;
this.buffer.notifyAll(); break;
}
boolean discard = true;
if ((this.blocking) && (!Thread.interrupted()) && (Thread.currentThread() != this.dispatcher)) //判断是业务线程
{
try
{
this.buffer.wait();//阻塞业务线程
discard = false;
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
类似这类BUG具有极强的隐蔽性,往往WIO高的时间持续非常短,或者是偶现的,在测试环境中很难模拟此类故障,问题定位难度非常大。这就要求读者在平时写代码的时候一定要当心,注意那些隐性地雷。
2.2.6 流控
项目3.1优化点:
对于项目3.1作为服务端,应用流量整形,针对具体流量情况来实现流控。
优化指导方案:
无论服务端的性能优化到多少,都需要考虑流控功能,当资源成为瓶颈,或者遇到端侧设备的大量接入,Netty本身也提供了高低水位和流量整形,两种方式来控制流量。
高低水位:
netty中的高低水位机制会在发送缓冲区的数据超过高水位时触发channelWritabilityChanged事件同时将channel的写状态设置为false,但是这个写状态只是程序层面的状态,程序还是可以继续写入数据。所以使用这个机制时需要自行判断是否可写,并做相应的处理。为了防止在高并发场景下,由于服务端处理慢导致的客户端消息积压,客户端需要做并发保护,防止自身发生消息积压。Netty提供了一个高低水位机制实现客户端精准的流控。
流量整形:
1.单个链路流量整形ChannelTrafficShapingHandler
2.全局流量整形GlobalTrafficShapingHandler
3.全局和单个链路综合型流量整形GlobalChannelTrafficShapingHandler
这三个流量整形处理器,依次用于控制单个channel、所有channel、所有channel。
这些处理器控制流量的原理相同的。控制读取速度是通过先从TCP缓存区读取数据,然后根据读取的数据大小和读取速率的限制计算出,需要暂停读取数据的时间。这样从平均来看读取速度就被降低了。控制写速度则是根据数据大小和写出速率计算出预期写出的时间,然后封装为一个待发送任务放到延迟消息队列中,同时提交一个延迟任务查看消息是否可发送了。这样从平均来看每秒钟的数据读写速度就被控制在读写限制下了。
Netty流量整形的主要作用:
1、防止由于上下游网元性能不均衡导致下游网元被冲垮,业务流程中断;
2、防止由于通信模块接收消息过快,后端业务线程处理不及时,导致出现“撑死”问题。
场景:设备向服务端不断上报大量数据,服务端处理不过来时,就可以使用流量整形来控制接收消息速度。
代码实例:
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ChannelTrafficShapingHandler(1024 * 1024,1024 * 1024,1000));//流量整形
sc.pipeline().addLast(new MyHandler());
}
});
第一个参数writeLimit对单个channel每秒发送的流量限制为1兆大小;
第二个参数readLimit对单个channel每秒读取的流量限制了1兆大小;
第三个参数checkInterval:1000,单位是毫秒,表示每1秒统计一次发送和读取的字节总和。
public class MyHandler extends ChannelTrafficShapingHandler{
public MyHandler (long writeLimit, long readLimit, long checkInterval) {
super(writeLimit, readLimit, checkInterval);
}
@Override
protected void doAccounting(TrafficCounter counter) {
super.doAccounting(counter);
System.out.println(“周期内总共读取字节数::”+counter.lastReadBytes());
System.out.println(“周期内总共发送字节数::”+counter.lastWrittenBytes());
}
}
3Q&A
1.集群对于负载均衡的处理
2.通信协议文档定最终稿