ビジネスのカスタム・スレッド・プールChannelHandlerの実行、および網状によって:ユーザーがChannelHandlerが含ま達成するか、複雑なビジネスロジックの同期ブロックにつながる可能性がある場合、パフォーマンスを向上させるために、多くの場合、スレッドプールが追加され、そこに2つの戦略があるスレッドプールによる同時実行性を改善する必要があります並列実行ChannelHandler用EventExecutorGroupメカニズム。
ケースの再生には、
並列解析して実行することができない
最適化戦略
ケース再生
ネッティーサーバ・ハンドラ、関連するコードのビジネスを起動するために、内蔵パラレルDefaultEventExecutorGroupを使用しました:
public class ConcurrentPerformanceServer {
static final EventExecutorGroup executor = new DefaultEventExecutorGroup(100);
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline p = socketChannel.pipeline();
p.addLast(executor, new ConcurrentPerformanceServerHandler());
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
私たちは、サーバがEventExecutorGroupの100のスレッド数を初期化するときに作成され、ビジネスハンドラにバインドハンドラは、パフォーマンスを向上させる、一方同時に、あなたがスレッドを処理I Oスレッドと分離/ビジネスロジックを実現することができますので、。
ランダム休眠モデルの複雑な時間のかかる事業運営により、ビジネスハンドラ、時限タスク処理のパフォーマンス統計を利用しながら、定期的にサーバのスレッドプールで。関連するコード:
public class ConcurrentPerformanceServerHandler extends ChannelInboundHandlerAdapter {
AtomicInteger counter = new AtomicInteger(0);
static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
scheduledExecutorService.scheduleAtFixedRate(() ->{
int qps = counter.getAndSet(0);
System.out.println("The Server QPS is : " + qps);
},0, 1000, TimeUnit.MILLISECONDS);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
((ByteBuf)msg).release();
counter.incrementAndGet();
Random random = new Random();
TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));
}
}
ロングサーバーの速度100QPS圧力測定、クライアントとサーバの間に次のコードへのTCP接続を確立します:
public class ConcurrentPerformanceClientHandler extends ChannelInboundHandlerAdapter {
static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
scheduledExecutorService.scheduleAtFixedRate(() ->{
for(int i = 0; i < 100; i++){
ByteBuf firstMessage = Unpooled.buffer(100);
for(int k = 0; k < firstMessage.capacity(); k++){
firstMessage.writeByte((byte) i);
}
ctx.writeAndFlush(firstMessage);
}
},0, 1000, TimeUnit.MILLISECONDS);
}
}
テスト結果:
ここでのスループット一桁、時間のかかる事業は100msで〜1000msのは、それがビジネスハンドラが同時に実行が、シングルスレッド実行されなかったことが疑われました。サーバスレッドのスタックを確認してください:
検索事業は100スレッド一つだけ実行を設定します。単一の実行スレッドが備えているので、性能が高くないので、ハンドラーは、ビジネス・ロジック・オペレーションを担当しています。
分析を並行して行うことができません
ソースコード解析、次のように、(DefaultChannelPipelineカテゴリ)、ビジネスChannelHandlerコードにバインドDefaultEventExecutorGroupを参照してください。
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
AbstractChannelHandlerContext newCtx;
synchronized(this) {
checkMultiplicity(handler);
newCtx = this.newContext(group, this.filterName(name, handler), handler);
this.addLast0(newCtx);
//省略后续代码
}
其中newContext具体代码为创建一个DefaultChannelHandlerContext类返回,创建过程会调用childExecutor(group)方法,从EventExecutorGroup中选择一个EventExecutor绑定到DefaultChannelHandlerContext,相关代码如下:
private EventExecutor childExecutor(EventExecutorGroup group) {
Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
if (childExecutors == null) {
childExecutors = this.childExecutors = new IdentityHashMap(4);
}
EventExecutor childExecutor = (EventExecutor)childExecutors.get(group);
if (childExecutor == null) {
childExecutor = group.next();
childExecutors.put(group, childExecutor);
}
return childExecutor;
}
通过group.next()方法,从EventExecutorGroup中选择一个EventExecutor,存放到EventExecutorMap中。对于某个具体的TCP连接,绑定到业务ChannelHandler实例上的线程池为DefaultEventExecutor,因此调用的就是DefaultEventExecutor的execute方法,由于DefaultEventExecutor继承自SingleThreadEventExecutor,所以执行execute方法就是把Runnable放入任务队列由单线程执行。
所以无论消费端有多少个线程来并发压测某条链路,对于服务端都只有一个DefaultEventExecutor线程来执行业务ChannelHandler,无法实现并行调用。
优化策略
- 如果所有客户端的并发连接数小于业务需要配置的线程数,建议将请求消息封装成任务,投递到后端业务线程池执行,ChannelHandler不需要处理复杂业务逻辑,也不需要绑定EventExecutorGroup。
public class ConcurrentPerformanceServerHandler extends ChannelInboundHandlerAdapter {
AtomicInteger counter = new AtomicInteger(0);
static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
static ExecutorService executorService = Executors.newFixedThreadPool(100);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
scheduledExecutorService.scheduleAtFixedRate(() ->{
int qps = counter.getAndSet(0);
System.out.println("The Server QPS is : " + qps);
},0, 1000, TimeUnit.MILLISECONDS);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
((ByteBuf)msg).release();
executorService.execute(() ->{
counter.incrementAndGet();
Random random = new Random();
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
结果展示:
The Server QPS is : 59
The Server QPS is : 55
The Server QPS is : 61
The Server QPS is : 68
The Server QPS is : 43
The Server QPS is : 78
QPS明显上升。
- 如果所有客户端并发连接数大于或等于业务需要配置的线程数,则可以为业务ChannelHandler绑定EventExecutorGroup,并在业务ChannelHandler中执行各种业务逻辑。客戶端创建10个TCP连接,每个连接每秒发送1条请求信息,同时将之前DefaultEventExecutorGroup的大小设置为10,则整体QPS也是10,线程堆栈情况: