1. 定义
流量整形是为了控制当前服务的流量输出,保证下游节点的正常处理,如图所示,将流量洪峰放入队列中,使用令牌桶算法来保证流量不会突破输出极限,保证下游收到的数据都是平稳的。
分为:
1) GlobalTrafficShapingHandler:全局流量整形,放在服务器端,表示所有链接该服务器的channel整体的流量不超过阈值
2)ChannelTrafficShapingHandler:表示单个channel的流量作出限制
客户端:启动代码
public class TrafficShappingClient {
public void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
// ByteBuf delimiter = Unpooled.copiedBuffer("$_"
// .getBytes());
// ch.pipeline().addLast(
// new DelimiterBasedFrameDecoder(2048 * 1024,
// delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TrafficShappingClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
int port = 18091;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new TrafficShappingClient().connect(port, "127.0.0.1");
}
}
客户端:自定义的额handler
public class TrafficShappingClientHandler extends ChannelInboundHandlerAdapter {
private static AtomicInteger SEQ = new AtomicInteger(0);
static final byte[] ECHO_REQ = new byte[1024 * 1024];
static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
@Override
public void channelActive(ChannelHandlerContext ctx) {
scheduledExecutorService.scheduleAtFixedRate(()
-> {
ByteBuf buf = null;
for (int i = 0; i < 3; i++) {
buf = Unpooled.copiedBuffer(ECHO_REQ);
if (ctx.channel().isWritable()) {
SEQ.getAndAdd(buf.readableBytes());
ctx.write(buf);
}
}
ctx.flush();
int counter = SEQ.getAndSet(0);
System.out.println("The client send rate is : " + (double)counter/(1024*1024) + " M/s");
}, 0, 1000, TimeUnit.MILLISECONDS);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
服务器:启动代码
public class TrafficShappingServer {
public void bind(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast("Channel Traffic Shaping",new GlobalTrafficShapingHandler(ch.eventLoop().parent(),1*1024 * 1024,1*1024 * 1024, 1000));
// ByteBuf delimiter = Unpooled.copiedBuffer("$_"
// .getBytes());
// ch.pipeline().addLast(
// new DelimiterBasedFrameDecoder(2048 * 1024,
// delimiter));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TrafficShapingServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 18091;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new TrafficShappingServer().bind(port);
}
}
服务器:自定义代码
@Sharable
public class TrafficShapingServerHandler extends ChannelInboundHandlerAdapter {
// 设置一个计数器,用于计算流量的大小
AtomicLong counter = new AtomicLong(0);
ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
/*
* 工具方法:开始流量计算
* 这是一个定时线程,每秒计算读写的速率,然后将count设置为0,用于下一次计算1秒内的读写总和
*/
public TrafficShapingServerHandler() {
es.scheduleAtFixedRate(() ->
{
double counnterTmp = (double)counter.getAndSet(0)/(1024*1024);
System.out.println("The server receive client rate is : " + counnterTmp + " M/s");
}, 0, 1000, TimeUnit.MILLISECONDS);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String) msg;
counter.addAndGet(body.getBytes().length);// 计算传入的数据的流量
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
客户端每秒发送3M/s的数据,服务端流量限制为1M/s
服务端测试:
客户端测试: