ネッティーは徹底的にスレッドモデルを知ってもらいます

編集者注:ネッティーは、有名なJavaのオープンソースのネットワークライブラリフィールドであり、高いパフォーマンスと拡張性によって特徴付けられる、多くの一般的なフレームワークを構築することに基づいており、そのような私たちのよく知られたダボ、Rocketmq、Hadoopのなどなど。議論の分析で起動この記事ネッティースレッドモデル:)

IOモデル

  • BIO:同期のブロッキングIOモデル。
  • NIO:「非ブロック同期」IOモデルに基づいて、IO多重化技術。アプリケーションを開始読み取りおよび書き込み操作によって簡単に言えば、カーネルは、イベント通知アプリケーションを読み取りおよび書き込みます。
  • AIO:非同期IOモデルを非ブロック。簡単に言えば、カーネルはカーネルによって、完全なイベント通知アプリケーションを読んで読んでます完了すると、アプリケーションはデータのみを操作することができ、すぐにカーネルがキューと書き込みを書き込みます、非同期書き込み操作を行うためのアプリケーションを返します。

アプリケーションが本物の読み取りおよび書き込み操作であることを除いて、NIOとAIO。

原子炉のモデルとproactor

  • 原子炉:NIOベースの技術、読み取りおよび書き込みアプリケーションに通知します。
  • proactor:読み取りが完了すると、アプリケーションに通知AIOベースの技術、アプリケーションがカーネルに通知するために書き込みます。

ネッティースレッドモデル

ネッティースレッドモデルは、原子炉のモデルに基づいています。

ネッティーシングルスレッドモデル

新しい接続要求を受信し、読み取りおよび書き込み操作:反応器は、スレッドNIO機能であった場合には、すべてのI / O操作を指すが、NIOが完了同じ上糸であり、モデルのねじ。

いくつかのシナリオでは、小容量、シングルスレッドモデルを使用することができます(注意,Redis的请求处理也是单线程模型,为什么Redis的性能会如此之高呢?因为Redis的读写操作基本都是内存操作,并且Redis协议比较简洁,序列化/反序列化耗费性能更低)。しかし、高負荷のため、並行性の高いシナリオは不適切で、主な理由は次の通りです:

  • スレッドNIO接続の処理何百ものパフォーマンスをサポートすることができない、でもCPU負荷NIOスレッド100%の場合、読み取り、送信、復号化、大規模なメッセージエンコードを満たすことができません。
  • NIOスレッドが過負荷状態にすると、処理速度は、クライアントの接続タイムアウトの多数を引き起こす、遅くなり、タイムアウトが後にNIOのスレッドが最終的にメッセージのバックログとハンドルタイムアウトの数が多いにつながる、より重い負荷である、再送される傾向にあり、システムのパフォーマンスのボトルネックになります。
  • 信頼性の問題:NIOスレッドが突然切れたら、またはループには、システム全体の通信モジュールは、ノードの障害を引き起こし、外部メッセージを受信し処理するために利用可能でない引き起こし得ます。

原子炉のマルチスレッドモデル

レクターは、モデルをマルチスレッド化し、最大の違いは、書き込み動作設定ハンドル接続にシングルスレッドモデルNIOスレッド、NIOが受け入れ処理スレッドです。スレッドが複数の接続NIOイベントを処理することができ、接続の際には、唯一のNIOスレッドに属することができます。

ほとんどのシナリオでは、原子炉マルチスレッドモデルは、性能要件を満たすことができます。しかし、個々の特別なシーンで、NIOスレッドは監視とパフォーマンスの問題を存在しうるすべてのクライアント接続を処理する責任があります。例えば、百万同時クライアント接続、またはサーバがクライアントのセキュリティ認証ハンドシェイクが必要ですが、認証自体は損失性能です。反応器からのマルチスレッドマスターモデル - そのようなシナリオでは、シングルスレッドアクセプターでモデルを通す第3の反応器で、その結果、パフォーマンスの問題を解決するために不十分なパフォーマンスの問題が存在してもよいです。

反応器マスタ・スレーブマルチスレッドモデル

原子炉のスレッドは、マスターモデルから備えていません:クライアント接続を受信するためのサービス端末は、もはや別のスレッドNIOが、独立したNIOスレッドプールです。アクセプターが(アクセス認証を含むことができる)、クライアントにTCP接続要求を受信し、完了後に処理を終了し、新たに作成されたSocketChannelそのによってI / OスレッドのI / Oスレッドプール(サブリアクタースレッドプール)に登録しますSocketChannelの仕事を読み書きやコーデックを担当します。アクセプタスレッドプールは、唯一のクライアントのログイン、握手や安全認証のために使用されているリンクが確立されると、それはリンクツーバックしますI / Oスレッドからの後続のI / Oを担当するI / Oスレッドの登録subReactorスレッドプール、操作。

ネッティースレッドモデルの考え方

ネッティースレッドモデルはそれが本当にユーザーの起動パラメータに依存し、静的ではありません。異なるパラメータを設定することでスタートは、ネッティー原子炉は、シングルスレッドモデル、マルチスレッドモデルをサポートすることができます。

そのようなI / Oスレッド内のシリアル操作としてロックフリー設計の多くの部品でネッティー、できるだけ性能を向上させるためには、競争によって引き起こされるマルチスレッドパフォーマンスの低下を避けます。表面には、シリアル設計は、CPU利用率が同時実行の十分に高い程度ではないようです。しかし、パラメータNIOスレッドプールのスレッドが同時に複数のスレッドを開始することができる調整することにより、より良好な作業キュースレッドの複数の性能と比較して、モデルの平行シリアライズ、そのような局在シリアル番号ロックスレッド設計で実行されます。小伙伴们后续多线程并发流程可参考该类实现方案

メッセージを読んだ後ネッティーのNioEventLoop、の直接呼び出しChannelPipeline fireChannelRead (Object msg)限り、ユーザーが積極的にスレッドを切り替えていないとして、スレッドの切り替えはNioEventLoopがユーザーを呼び出す時に、ChannelHandlerなしてきました。パフォーマンスの観点から、によるマルチスレッド動作にロック競合を避けるために、このシリアル化アプローチが最適です。

NIO網状は、それぞれ、2つのスレッド・プールを有し、bossGroupそしてworkerGroup元新しい接続要求が処理され、その後、同じ接続上のNioEventLoop workerGroupが処理するポーリング、その後の読み出しと書き込みに新たに確立された接続が行われますに対処するNioEventLoop。なおがbossGroupは複数NioEventLoop(NioEventLoopつのスレッド)を指定することができるが、アプリケーションのみ通常の状況下でポート外部モニタを使用するため、デフォルトでは、ケース内の唯一のスレッドであろう。

ここで想像し、あなたが同じのepollインスタンスにマルチスレッドのイベントがepoll_waitで同一の外部ポートこと、を聞くために複数のスレッドを使用していませんか?

マルチスレッドのepollインスタンスを操作しながら、epollを2つの主な方法は、あなたがスレッドセーフかどうかを確認するために関連した方法をepollをする最初の必要性、イベントがepoll_waitとepoll_ctl関連しています。簡単に言えば、ファイルディスクリプタは、保証スレッドセーフにロックすることにより、最小サイズのepollですスピンロックEP->ロック(スピンロック)レディキューを保護するために、ミューテックスEP-> MTXのepoll赤黒ツリーデータ構造を保護することが重要

ここを参照してください、いくつかの小さなパートナーがポートをリスニングするためにnginxのマルチプロセッシング戦略を考え、nginxのはaccept_mutexメカニズムによって保証されます。accept_mutexは、ロックのバランスをとるnginxの(新しい接続)負荷が、今度は、複数のワーカー・プロセスがクライアントへの新しい接続を処理することができますです。ワーカープロセスへの接続の数、接続worker_connections構成の最大数(単一ワーカー処理プロセスへの接続の最大数)を7/8に達したとき、それは大きく、各ワーカー・プロセスを達成するために、ロックを受け入れる得るために労働者を得る確率を減少させます負荷分散の数との間の接続。オープンデフォルトのロックを受け入れ、nginxの新しい接続時間のかかるプロセスは、あなたがそれを閉じると短くなりますが、ワーカープロセス間の接続のバランスできなかっただろう、と「雷鳴の群れ」という問題点があります。accept_mutexを有効にして、現在のシステムは、原子ロックをサポートしていないときにのみ、ロックがファイルを受け入れる達成されるであろう。現在のスレッドaccept_mutexロックの失敗をブロックしていないノート、類似したのtryLock。

最近のLinux、同じポートを監視しながら、複数のsockerはnginxの1.9.1も、この動作をサポートし、また実現可能です。Linux 3.9カーネルのサポートSO_REUSEPORTより多くのオプションは、複数のsockerバインドを許可する/同じポートでリッスン。このように、それぞれがsockerを申請することができ、複数のプロセスが接続イベントが来たときに、負荷分散、ハンドルにリスナー、reuseportメカニズムが効果的に解くのepoll雷鳴の群れ問題ウェイクアッププロセスを実行するカーネル、同じポートで待機します。

その後、戻ってちょうど提起の質問に、Javaはまだイベントがepoll_waitを聞くために使用する複数のスレッドを使用できるように、ファイルディスクリプタの方法は、スレッドセーフで、同一の外部ポートに耳を傾けるマルチスレッド、当然のことながら、驚きのepollを除き、乾燥をお勧めしませんグループの問題の外に、私たちはファイルディスクリプタ集合は(LTモデルで使用する一般的な開発があります水平触发方式,与之相对的是ET默认,前者只要连接事件未被处理就会在epoll_wait时始终触发,后者只会在真正有事件来时在epoll_wait触发一次)、このような場合はイベントがepoll_wait最初のスレッドがまだイベント処理を完了していない後が発生したとき、それは、マルチスレッドのイベントがepoll_waitにつながります2番目のスレッドが明らかにこれはJavaのNIOのテストのデモに、私たちが望むものではない、リターンれるepoll_waitます次のとおりです。

public class NioDemo {
    private static AtomicBoolean flag = new AtomicBoolean(true);
    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8080));
        // non-block io
        serverChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 多线程执行
        Runnable task = () -> {
            try {
                while (true) {
                    if (selector.select(0) == 0) {
                        System.out.println("selector.select loop... " + Thread.currentThread().getName());
                        Thread.sleep(1);
                        continue;
                    }

                    if (flag.compareAndSet(true, false)) {
                        System.out.println(Thread.currentThread().getName() + " over");
                        return;
                    }

                    Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();

                        // accept event
                        if (key.isAcceptable()) {
                            handlerAccept(selector, key);
                        }

                        // socket event
                        if (key.isReadable()) {
                            handlerRead(key);
                        }

                        /**
                         * Selector不会自己从已选择键集中移除SelectionKey实例,必须在处理完通道时手动移除。
                         * 下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
                         */
                        iter.remove();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(task);
            threadList.add(thread);
            thread.start();
        }
        for (Thread thread : threadList) {
            thread.join();
        }
        System.out.println("main end");
    }

    static void handlerAccept(Selector selector, SelectionKey key) throws Exception {
        System.out.println("coming a new client... " + Thread.currentThread().getName());
        Thread.sleep(10000);
        SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    }

    static void handlerRead(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();

        int num = channel.read(buffer);
        if (num <= 0) {
            // error or fin
            System.out.println("close " + channel.getRemoteAddress());
            channel.close();
        } else {
            buffer.flip();
            String recv = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
            System.out.println("recv: " + recv);

            buffer = ByteBuffer.wrap(("server: " + recv).getBytes());
            channel.write(buffer);
        }
    }
}

ネッティースレッドモデルの実践

直接I / Oスレッドでシンプルなサービス(1)時間制御されたプロセス

シンプルな時間は、直接、ビジネスは非常に単純であれば、実行時間が他のリソースを待たずに、外部ネットワークとの相互作用、およびデータベースへのディスクアクセスを必要としない、非常に短いですが、それはビジネスのChannelHandlerに直接推奨されるI / O処理スレッド上のトラフィックを制御します実行は、ビジネスやスレッドプールのスレッドを起動する必要はありません。スレッドコンテキストの切り替えを避けるため、どのスレッドの同時実行性の問題はありません。

(2)複雑さと時間の制御不能なビジネスのアドバイスは、バックエンド業務処理スレッドプールの団結に配信します

高い複雑さや時間が制御可能なビジネスのアドバイスではありませんビジネスのこのタイプの処理スレッドプールの団結は、ビジネスに直接スレッドまたはスレッドプールを開始することをお勧めされていないバックエンド事業に配信ChannelHandlerは団結、統一されたパッケージタスクにさまざまなサービスをお勧めしますバックエンド業務処理スレッドプールへのポスト。過度のビジネスChannelHandlerはまだ統合したり、独自のビジネスコンテナ、良いと網状の階層化アーキテクチャを開発する必要があり、最も複雑なビジネスの製品のために、サービスコンテナとしてではないネッティーに、開発効率と保守性の問題をもたらします。

直接運転ChannelHandlerを回避するために、(3)事業のスレッド

ビジネスモデルがChannelHandler存在します、通常はマルチスレッド、マルチスレッド操作であるため、直接操作ChannelHandlerを避けるために、ビジネスのスレッドは、ChannelHandlerため、IOスレッドとスレッド事業は、動作させることができます。別タスクの操作ではなく、直接操作スレッド操作よりも、統一NioEventLoopによって行わとしてパッケージ、その網状の実施において推奨されているように、マルチスレッド回避問題にするために、関連するコードは次のように

あなたは、データへの同時アクセスを確認または同時操作が安全である場合は、このニーズは、特定のビジネスシナリオ、柔軟性に応じて判断されるように、気にしないでください。

推奨読書

小さなパートナーへようこそ[トップコーダー]懸念読むよりエキサイティング良いテキスト。

IMG

おすすめ

転載: www.cnblogs.com/luoxn28/p/11875340.html