nettyの上位6つの最適化

みなさんこんにちは。 この記事では、サーバーのパフォーマンスを向上させるために行われた最適化の具体例とともに、 コアあたり10kを続けています。 最初の部分を書いてから5か月が経過し、この間に本番サーバーの負荷は500川秒から2000に増加し、ピークは5000川秒になりました。 nettyのおかげで、この増加に気付くことすらありませんでした(ディスク容量が速くならない限り)。



ブリンク荷重

(ピークに注意を払わないでください、これらは展開中のバグです)



この記事は、nettyを使用している、または開始したばかりのすべての人に役立ちます。 行きましょう。



LinuxのネイティブEpollトランスポート



誰もが使用すべき主要な最適化の1つは、Javaで実装する代わりにネイティブEpollトランスポートを接続することです。 特にnettyでは、これは1つの依存関係のみを追加することを意味するためです。



<dependency> <groupId>io.netty</groupId> <artifactId>netty-transport-native-epoll</artifactId> <version>${netty.version}</version> <classifier>linux-x86_64</classifier> </dependency>
      
      





次のクラスを置き換えるコードによるオートコレクト:





実際、ノンブロッキングソケットを操作するためのJava実装はSelectorクラスを介して実装されているため、多くの接続で効果的に作業できますが、Javaでの実装は最適ではありません。 すぐに3つの理由:





私の特定のケースでは、パフォーマンスが約30%向上しました。 もちろん、この最適化はLinuxサーバーでのみ可能です。



ネイティブOpenSSL



CISでの方法はわかりませんが、TAMはプロジェクトの重要な要素です。 「セキュリティについてはどうですか?」プロジェクト、システム、サービス、または製品に興味があるかどうかを尋ねられることは避けられない質問です。



私が出てきたアウトソーシングの世界では、チームは常にこの問題を常にシフトできる1-2 DevOpsを持っていました。 たとえば、アプリケーションレベルでhttps、SSL / TLSのサポートを追加する代わりに、管理者に常にnginxを設定し、そこから通常のhttpをサーバーに転送するように依頼できます。 そして高速かつ効率的です。 今日、私がスイス人と刈り取り師の両方であり、私が男であるとき、私はすべてを自分でやらなければなりません-開発、展開、監視する そのため、アプリケーションレベルでhttpsを接続する方が、nginxを展開するよりもはるかに高速で簡単です。



nettyでopenSSLを機能させるのは、ネイティブのepollトランスポートを接続するよりも少し複雑です。 新しい依存関係をプロジェクトに接続する必要があります。



 <dependency> <groupId>io.netty</groupId> <artifactId>netty-tcnative</artifactId> <version>${netty.tcnative.version}</version> <classifier>linux-x86_64</classifier> </dependency>
      
      





SSLプロバイダーとしてopenSSLを指定します。



  return SslContextBuilder.forServer(serverCert, serverKey, serverPass) .sslProvider(SslProvider.OPENSSL) .build();
      
      





パイプラインに別のハンドラーを追加します。



 new SslHandler(engine)
      
      





最後に、サーバー上でopenSSLを操作するためのネイティブコードを収集します。 指示はこちらです。 実際、プロセス全体は次のようになります。



私にとって、パフォーマンスの向上は約15%でした。

完全な例はここここで見ることができます



システムコールの保存



多くの場合、同じソケットに複数のメッセージを送信する必要があります。 次のようになります。



 for (Message msg : messages) { ctx.writeAndFlush(msg); }
      
      





このコードは最適化できます。



 for (Message msg : messages) { ctx.write(msg); } ctx.flush();
      
      





2番目のケースでは、 書き込みに、ネットはすぐにネットワークを介してメッセージを送信しませんが、処理後にメッセージをバッファに入れます(メッセージがバッファより小さい場合)。 したがって、ネットワークを介してデータを送信するシステムコールの数を減らします。



最適な同期は、同期の欠如です。



前の記事ですでに書いたように、少数のロジックハン​​ドラスレッド(通常はnコア* 2)を備えたnetty非同期フレームワークです。 したがって、そのような各ハンドラスレッドは、可能な限り迅速に実行する必要があります。 特に毎秒数万のリクエストの負荷がある場合、あらゆる種類の同期によってこれを防ぐことができます。



この目的のために、nettyは各新しい接続を同じハンドラー(スレッド)にバインドして、同期のためのコードの必要性を減らします。 たとえば、ユーザーがサーバーに参加して特定のアクションを実行した場合-たとえば、ユーザーのみが関連付けられているモデルの状態を変更した場合、同期や揮発性は不要です。 このユーザーからのすべてのメッセージは、同じスレッドによって処理されます。 これはすばらしく、一部のプロジェクトで機能します。



しかし、状態が複数の接続から変更される可能性がある場合、接続は異なるスレッドに関連付けられている可能性が最も高いでしょうか? たとえば、ゲームルームを作成し、ユーザーのチームが私たちの周りの世界を変えなければならない場合はどうでしょうか?



これを行うために、1つのハンドラーから別のハンドラーに接続を再割り当てできるnettyのregisterメソッドがあります。



 ChannelFuture cf = ctx.deregister(); cf.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { targetEventLoop.register(channelFuture.channel()).addListener(completeHandler); } });
      
      





このアプローチにより、1つのスレッドで1つのゲームルームのイベントを処理し、このルームの状態を変更するために同期と揮発性を完全に取り除くことができます。

ここここで私のコードにログインするために再バインドする例



EventLoopを再利用する



サーバーはさまざまなプロトコルの作業をサポートする必要があるため、サーバーソリューションにはしばしばNettyが選択されます。 たとえば、私の控えめなIoTクラウドは、さまざまなハードウェアと独自のバイナリプロトコル用のHTTP / S、WebSocket、SSL / TCPソケットをサポートしています。 つまり、これらの各プロトコルには、IOスレッド(ボスグループ)とスレッドハンドラーロジック(ワークグループ)が必要です。 通常、これらのハンドラーのいくつかを作成すると次のようになります。



 //http server new ServerBootstrap().group(new EpollEventLoopGroup(1), new EpollEventLoopGroup(workerThreads)) .channel(channelClass) .childHandler(getHTTPChannelInitializer(()) .bind(80); //https server new ServerBootstrap().group(new EpollEventLoopGroup(1), new EpollEventLoopGroup(workerThreads)) .channel(channelClass) .childHandler(getHTTPSChannelInitializer(()) .bind(443);
      
      





しかし、nettyの場合、作成する余分なスレッドが少ないほど、より生産的なアプリケーションを作成する可能性が高くなります。 幸いなことに、netty EventLoopでは再利用できます:



 EventLoopGroup boss = new EpollEventLoopGroup(1); EventLoopGroup workers = new EpollEventLoopGroup(workerThreads); //http server new ServerBootstrap().group(boss, workers) .channel(channelClass) .childHandler(getHTTPChannelInitializer(()) .bind(80); //https server new ServerBootstrap().group(boss, workers) .channel(channelClass) .childHandler(getHTTPSChannelInitializer(()) .bind(443);
      
      







オフヒープメッセージ



高負荷アプリケーションのボトルネックの1つがガベージコレクターであることは周知の事実です。 Nettyは高速です。これには、Javaヒープの外部でメモリが広く使用されているためです。 Nettyには、オフヒープバッファとメモリリーク検出システムに関する独自のエコシステムさえあります。 同じことができます。 例:



 ctx.writeAndFlush(new ResponseMessage(messageId, OK, 0));
      
      





に変更



 ByteBuf buf = ctx.alloc().directBuffer(5); buf.writeByte(messageId); buf.writeShort(OK); buf.writeShort(0); ctx.writeAndFlush(buf); //buf.release();
      
      





ただし、この場合、パイプラインのハンドラーの1つがこのバッファーを解放することを確認する必要があります。 これは、すぐにコードを実行して変更する必要があるという意味ではありませんが、この最適化の機会について知っておく必要があります。 より複雑なコードとメモリリークを取得する機能にもかかわらず。 ホットメソッドの場合、これは完璧なソリューションかもしれません。



これらの簡単なヒントにより、アプリケーションを高速化できることを願っています。

私のプロジェクトはオープンソースであることを思い出させてください。 したがって、これらの最適化が既存のコードでどのように見えるかに興味がある場合は、 こちらを参照してください



All Articles