Tomcatの中のアプリケーションNIO2(AIO)

アウトライン

注:NIO2(AIO)非同期IO

NIO2プロフィール

AIOそして、NIO2実際には、日・ウォーカーとして、日が実際にサルですが、本質的に異なる名前が同じである一つのこと受けています

それでは、どのようにこの概念を理解していますか?例えば

妹は単純なもの、何を達成するための効率的な方法を望んでいるならば、妹は多くのなめ犬(スペアタイヤを)持っていると仮定?

答えは犬がOK実行中の彼らにそんなにをなめることです。だから、Gouzi作業中に、妹は物事はそれを成し遂げるGouzi待ちますか?いや、もちろん、私たちは、この期間中に他のGouzi他のタスクに配信し続けることができます。姉に対処するために必要なものがある場合にGouziは作業中に、あなたが扱うことができるものに気づくとき。

もちろんGouziは、一般的にデータをコピーするなど、一生懸命重い仕事を扱う、I / Oああ、新しい接続漢(残念)受信。姉妹は、コアビジネスに集中しています。

それらはGouzi(コア+ I / Oスレッド)抽象化であるが、この例では、姉妹スレッドは、主にビジネスロジック処理のために、コアビジネスに対応します。

PS

  • あなたがNIO2を知っていれば、私はあなたが直接NIO2モデル解釈部を読むことをお勧め、NIO2 DEMOセクションを読む必要はありません(時間は貴重です)

  • あなたは上に直接、すべての章を参照してくださいすることができます要約あなたは、単に読むことができる、付録始めるために直接デバッグコードを

NIO2 DEMO

NIO2コア・ポイントは、カーネルが犬のなめを提供する必要があり、どのようなイベント通知手続きを担当し、主に担当して受信し、コピーデータ接続がプログラムスレッドは、これらのものは、あなたが私の姉(コアビジネスのスレッド)を理解することができます提供するために必要とされるということですがありますこれらの事を行うには、カーネルにプール(スレッドプール)

話は安いです、私にあなたの髪を表示します

あなたがNIO2について学習したい場合は、上をクリックすることができます学ぶために

あなたはコメントが文字化けしている見ればGBKのエンコーディング用のソースノートは、それはそれを変更することが最良であるGBKコード化

これは、デモである。この例では、明示的にスレッドプールを作成しませんが、これがあることは注目に値する理由は明示されていない場合、あなた()サーバの時刻は、システムはオープンでスレッドプールをのServerSocketChannelに割り当てられているデフォルトになる場合イベントを処理するために、我々はテストするためにJConsoleを開くことができます。

channel = AsynchronousServerSocketChannel.open();
复制代码

JConsoleのは、表示されたスレッド

スレッド-4に、図のスレッド0に示すように、I / Oイベントを処理するためのデフォルトのスレッドプールを割り当てられます。(天の恵みは、犬を舐めます)

我々はI / Oイベントを扱っている場合は、すべてのライブスレッドがブロックされているだろう、想像し、その後、システム全体以下に示すようにI / Oは、ブロックに突入します。

新しいI / Oイベントが来るには、カーネルは、クライアントから遮断され、その要求へのI / Oスレッドの処理が常に返すことができない、生きるためにブロックされる場合は、I / Oイベントを処理するスレッドを選択します。

好ましくは、I / Oイベントをスレッドこうして処理のみI / Oイベント(新しい接続を受信し、データをカーネルからスレッドにコピーされます)

閉塞のコアビジネスイベントまたは状況が最善の姉(に提出起こるとしてあなたは、犬を舐め犬を舐める、一生懸命、すなわち、重い作業を行うための唯一の最高のものを理解することができ、ビジネス・ロジック・スレッド・プールをに対処するため)。

TomcatのNIO2モデル

キークラス org.apache.tomcat.util.net.Nio2Endpoint

それはNIO2を説明するためのプロセスモデルであるので、我々は次のキーの役割を理解する必要があります

  • Nio2Acceptorアクセプターは、特定のスレッドにバインドされませんが、私たちが主なタスクの実行に役立つために、下からプロセスですアクセプターのコードを実行するスレッドプールからスレッドを選択するための新しい接続の到着は新しい接続アクセプタを受信するとき、処理対象を接続するための読み取りおよび書き込みレジスタ

  • LimitLatchは、非同期I / Oの状況では、スレッドプールI / Oイベントスレッドのロックによってブロックされている接続の数を制限する主な方法の接続数を制限します

  • I / Oプロセッサの I / Oの処理、同じスレッドNio2Acceptorプールで実行

ServerSocketを開始

あなたはコードを見たくない場合は、むしろ鈍い非同期プロセスを開始するServerSocketを、それは以下のプロセスを開始します

  • パッケージには、スレッドプールを作成します。AsynchronousChannelGroup
  • オープンServerSocketを
  • ポートを結合し、接続の最大数を設定します
    @Override
    public void bind() throws Exception {

        // 创建线程池
        if (getExecutor() == null) {
            createExecutor();
        }
        if (getExecutor() instanceof ExecutorService) {
            //创建用于I/O的线程池(需要用AsynchronousChannelGroup包装,才能提供给AsynchronousServerSocketChanne用)
            threadGroup = AsynchronousChannelGroup.withThreadPool((ExecutorService) getExecutor());
        }
        // AsynchronousChannelGroup needs exclusive access to its executor service
        if (!internalExecutor) {
            log.warn(sm.getString("endpoint.nio2.exclusiveExecutor"));
        }
        //创建ServerSocketChannel
        serverSock = AsynchronousServerSocketChannel.open(threadGroup);
        socketProperties.setProperties(serverSock);
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        //绑定端口并设置backlog的参数
        //backlog可以理解为当前最大待执行accept操作的连接数
        serverSock.bind(addr, getAcceptCount());

        // Initialize SSL if needed
        initialiseSsl();
    }
复制代码

以下に、そしてバインドされているのServerSocketChannel現在の非同期スレッドプール、コネクタの801が待機しているポート(付録もちろんオープンNIO2の)

nio2スレッドプール

Nio2Acceptor

Nio2Acceptor主な機能接收新连接、そして限制最大连接数、非同期の使用I / Oので、アクセプターは、特定のスレッドにバインドしていますが、新しいタスクを実行する必要がある場合、スレッドプールからのミッションを選択しないように。以下に示すように存在するとき、クライアントは、新しい接続が到着すると、プログラムはNio2Acceptorが着信クライアントソケットを完了し、新しい接続処理ビジネスロジックを始める方法を実行するためにスレッドプールからスレッドを選択します

ブレークポイント

AcceptHandler登録

非同期では、I / O、我々はイベントプロセッサに登録する必要があり、次のコードに示す接続イベント処理を完了することのServerSocketChannelを受け入れるTomcatが起動したとき、それはスレッドを開きます呼び出すNio2SocketAcceptorrunメソッドがしますNio2SocketAcceptorとして登録することがServerSocketChannel受け入れるイベントプロセッサ

 protected class Nio2Acceptor extends Acceptor<AsynchronousSocketChannel>
        implements CompletionHandler<AsynchronousSocketChannel, Void> {
    ...
        @Override
        public void run() {
            // The initial accept will be called in a separate utility thread
            if (!isPaused()) {
                // 连接数限制,如果达到最大连接数,则调用此方法的线程会陷入等待
                try {
                    countUpOrAwaitConnection();
                } catch (InterruptedException e) {
                    // Ignore
                }
                if (!isPaused()) {
                   //将自己注册为accept事件的处理器(注意此类实现的接口)
                    serverSock.accept(null, this);
                } else {
                    state = AcceptorState.PAUSED;
                }
            } else {
                state = AcceptorState.PAUSED;
            }
        }
    ...
}
复制代码

新しい接続を処理

新しい接続が到着すると、下部には、メソッドを実行するスレッドプールからスレッドを選択し、この方法は、メインプロセスを、以下のどの時点で着信クライアントソケットを、完了します

  • プロセスが継続実行されている場合、血管がまだ実行されているかどうかをチェックします
  • (この方法は、ブロックすることが起こる)アクセプターの方法を実行するためのスレッドプールのスレッドから実行接続数を制限する必要があるかどうかをチェックし、あなたが接続数を制限する必要がある場合は、選択し
  • 以上の動作が完了し、新しい接続の受信完了の点を扱う後続のI / Oイベントを実行setSocketOptions法と呼ばれています
        @Override
        public void completed(AsynchronousSocketChannel socket,
                Void attachment) {
            // Successful accept, reset the error delay
            errorDelay = 0;
            // Continue processing the socket on the current thread
            // Configure the socket
            if (isRunning() && !isPaused()) {
                //检查限制的最大连接数,如果没有设置(即-1)则不进行连接数限制
                if (getMaxConnections() == -1) {
                    serverSock.accept(null, this);
                } else {
                   //由于有新连接的到达,因此需要从线程池选一个线程执行增加连接数的操作,此操作可能会发生阻塞
                    getExecutor().execute(this);
                }
                //执行后续的I/O事件处理
                if (!setSocketOptions(socket)) {
                    closeSocket(socket);
                }
            } else {
                if (isRunning()) {
                    state = AcceptorState.PAUSED;
                }
                destroySocket(socket);
            }
        }
复制代码

接続の最大数を制限

あなたが最大接続数を制限する必要がある場合は、ロックを使用する必要があるので、アクセプターは、滞在するアイドルスレッドがブロックされている接続したときに、スレッドプールに提出する必要が新しい受け入れる理由、それはまた、ある特定のスレッドに結び付けられていないので、增加新连接数タスクを次のように、ショー(つまり、Nio2SocketAcceptorのrunメソッドを呼び出します)

 public void completed(AsynchronousSocketChannel socket,
                Void attachment) {
        ...
        getExecutor().execute(this);
        ...
}
复制代码

また、作成するために覚えてServerSocketChannel、我々は設定した時間backlog、それのパラメータを?

主に価値が接続バックログの数を超えていない場合に設定を受け入れなければならないことは、ServerSocketを受けていない許可された接続の現在の最大数を設定するために使用され、新しい接続が破棄されなければなりません。APIドキュメント

I / Oイベントの処理

それは、非同期I / O、それは読み取りおよび書き込みレジスタクライアントのソケットにバインドされているのでCompletionHandler、そのsetSocketOptions必然的なステップの発生につながる、このステップは、場合に発生しますか?

追跡後に発見されたデバッグsetSocketOptionsになりますNio2SocketWrapper作成し、実際のI / Oプロセスが新しいNio2SocketWrapper作成されたオブジェクトで行われたreadCompletionHandler、次はそのコードです

ReadCompletionHandlerデータを読んだ後、イベント・モニターを読み取るために呼び出される分析方法は、作業processSocketデータを開始します

        public Nio2SocketWrapper(Nio2Channel channel, final Nio2Endpoint endpoint) {
            super(channel, endpoint);
            nioChannels = endpoint.getNioChannels();
            socketBufferHandler = channel.getBufHandler();

            this.readCompletionHandler = new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer nBytes, ByteBuffer attachment) {
                    if (log.isDebugEnabled()) {
                        log.debug("Socket: [" + Nio2SocketWrapper.this + "], Interest: [" + readInterest + "]");
                    }
                    readNotify = false;
                    //加锁,其他线程可能会对标志位进行修改
                    synchronized (readCompletionHandler) {
                        //nBytes表示读取到的字节数,如果小于0
                        //抛出EOF异常,没数据读,那咋办吗,只好抛异常了
                        if (nBytes.intValue() < 0) {
                            failed(new EOFException(), attachment);
                        } else {
                            if (readInterest && !Nio2Endpoint.isInline()) {
                                readNotify = true;
                            } else {
                                // Release here since there will be no
                                // notify/dispatch to do the release.
                                readPending.release();
                            }
                            readInterest = false;
                        }
                    }
                    if (readNotify) {
                        //处理读事件 getEndpoint().processSocket(Nio2SocketWrapper.this, SocketEvent.OPEN_READ, false);
                    }
                }
                //省略代码,后面太长了
                ...
复制代码

以下に示すように、デバッグは、私たちの添付ファイル、検証、すなわちデータを読み込みます

ブレークポイント

注IDEA要求は、スレッドデバッグの切り替え時に発行することができる(データを読み取り、図に示すように、前の操作は、スレッドではありません。

スイッチスレッド

概要

TomcatのNIO2モデル

モデル
次のように要約

  • イベントを受け入れ、I / Oイベントは、スレッドプールを共有し、特定のスレッドに関連付けられていません

  • 新たな接続および登録処理対象I / Oイベントを受け取るためのアクセプター(Nio2Acceptor)

  • プール内の接続制限機能ライブスレッドをブロックすることによって達成することがLimitLatch

  • I / OハンドラというNio2SocketWrapperI / Oイベントの登録リテラシープロセッサが到着すると、プログラムは、これらのプロセッサ用のコードを実行するスレッドを選択します

  • 次のように全体的なプロセスがある >スレッドが実行を選択-新規接続するためのNio2Acceptorコードを- >スレッドプールへの接続数を増やすためにタスクを送信- >イベントを処理するために読み書きするために登録する- > I / Oイベントが到着し、I / Oイベントを処理するためのスレッドを1つ選択します

移行の思考

デフォルトのスレッドプールを使用しないでくださいスレッドプールは、このように、私たちの指先で接続制限機能を達成することができるので、代わりにデフォルトで提供スレッドプールを使用するのでは、Tomcatは、独自のスレッドプールを作成し、非同期のServerSocketChannel時間で作成します

I / Oスレッドをブロックしない実行のI / Oスレッドがブロックされ、長い時間のために起こるのだろうに動作しない、I / OスレッドがI / Oサブサブスレッドを持っている必要があります

付録Tomcatをデバッグする方法

すべて知っているバックエンドの猿は、(あなたが選択した方法に応じて、もちろん、桟橋)のTomcatに埋め込まSpringBootので、私たちは、特にTomcatのソースコードのデバッグを学ぶためSpringBootアプリケーションを作成することができます。

以下は、Tomcatのデバッグプロセスであります

  • 最初のステップは、IDEAを開きます
  • 第二のステップ、新しいプロジェクトSpringBoot
  • 第三段階のTomcatのjarパッケージを見つけるために、プロジェクトのサイドバーのCtrl + Fで

レッドボックスは、Tomcatのコアパッケージとしてマーク

  • 第四段階は、/ミュートすべて、ヘッドフォンを装着、ブレークポイントでマークされました

あなたは以下のクラスでは、TomcatのNIOのアプローチをテストしたい場合はポイントを壊す(あなたはNIO Tomcatのステップ何を知りたい場合は、あなたが私の見ることができます理解して

package org.apache.tomcat.util.net;
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> {
...
public class Poller implements Runnable {
    public void run() {
        //此方法的代码位于692行
    }
}
...
}
复制代码

あなたがTomcatのテストしたい場合はNIO2のアプローチを、あなたは、次のような構成を必要とする、あなたのコードに次のコードを追加します。(SpringBoot埋め込まれたTomcatのデフォルトのI / Oの方法なので、私たちは、構成によってNIOコネクタNIO2を増やす必要があります)

import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConnectorConf {
    //注意你的SpringBoot版本,此项目的版本是2.2.0,旧的版本1.5使用不同的类进行配置
    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcatServletWebServerFactory =
                new TomcatServletWebServerFactory();
        tomcatServletWebServerFactory.addAdditionalTomcatConnectors(getConnector());
        return tomcatServletWebServerFactory;
    }

    private Connector getConnector() {
       // 关键点哦
        Connector connector = new Connector("org.apache.coyote.http11.Http11Nio2Protocol");
        //将连接器的端口设置为801,这样访问801端口的就是NIO2的模式了
        connector.setPort(801);
        return connector;
    }
}
复制代码

行くorg.apache.tomcat.util.net.Nio2Endpoint逃げるようにマーク休憩を

マルチスレッドデバッグする方法

マルチスレッドの場合には、ケースが発生すること、次いでちょうどブレークポイントで選択し、ブレークポイントを入力することができないThread図に示すように、別のスレッドは、ブレークポイントに到達したときに、IDEA通知方法をすることができます。

おすすめ

転載: juejin.im/post/5db147436fb9a02032779f33