2 ---詳細情報通知システムのバックエンド設計


小さなセクションでは、我々は、我々がバックエンドアーキテクチャを導入このセクションでは、どのように設計するために、通信の選択が終了する前と後の話しましたか?

全体的なデザイン

ユーザーは、新しいメッセージ通知は、2つのモードがあり得ます

  • システムを得るためには、オンラインイニシアチブにログインした後
  • 受信者にシステムに新しいメッセージをプッシュするオンラインイニシアチブ

以下の下では想定され、新しいユーザ通知メッセージ通知リマインダーデータは、データベースに頻繁に読み取りおよび書き込み操作のデータベースを配置しています。メッセージが大きい場合、DB圧力、データのボトルネックが発生する可能性があります。

上記の二つのモード側によると、分割されたデザイン:

システムへのオンラインリクエストにログインした後

このモデルシステムは、受信者の新しいメッセージ通知モード、次の手順に復帰要求の受信者です:

  1. サーバネッティーへの要求の受信者
  2. 自分の接続プールに接続するための網状のWebSocket接続サービス
  3. クエリメッセージRabbitMQのに受信者の情報に基づいてネッティー
  4. 新しいメッセージがある場合は、新しいメッセージ通知が返されます
  5. 番号を使ってWebSocketの接続は、受信者が新しいメッセージを返します。
    ここに画像を挿入説明

受信者にシステムをプッシュするオンラインイニシアチブ

このモードは、次のようにプロセスは、受信機のモードに新しいメッセージ通知システムリターンします。

  1. RabbitMQのは、新しいメッセージネッティーにデータをプッシュします
  2. ネッティーのWebSocket接続は、接続プールの受信者から削除しました
  3. 受信者用WebSocketの接続数によって返さネッティー新しいメッセージ
    ここに画像を挿入説明

IOプログラミング

上記のあなたがネッティーを理解し始める前に、最初は、伝統的なIOのプログラミングを使用して、その差は何NIOを使用してプログラミングし、クライアント側のサービスと通信するためのプログラムを実装するために、網状に言及しました。

伝統的なIOプログラミング

各クライアント接続経由した後、サーバーは、クライアントの要求を処理するためのスレッドを開始します。:通信モデルは、次のようにI / O図のは、ブロッキング
ここに画像を挿入説明
受領後、コンソールに、サーバーにプリントサーバーを文字列を送信するために、クライアントごとに2秒:ビジネスシナリオを。

public class IOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            // (1) 阻塞方法获取新的连接
            Socket socket = serverSocket.accept();
            new Thread() {
                @Override
                public void run() {
                    String name = Thread.currentThread().getName();
                    try {
                        // (2) 每一个新的连接都创建一个线程,负责读取数据
                        byte[] data = new byte[1024];
                        InputStream inputStream = socket.getInputStream();
                        while (true) {
                            int len;
                            // (3) 按字节流方式读取数据
                            while ((len = inputStream.read(data)) != -1) {
                                System.out.println("线程" + name + ":" + new String(data, 0, len));
                            }
                        }
                    } catch (Exception e) {
                    }
                }
            }.start();
        }
    }
}

クライアントの実装:

public class MyClient {
    public static void main(String[] args) {
        //测试使用不同的线程数进行访问
        for (int i = 0; i < 5; i++) {
            new ClientDemo().start();
        }
    }
    static class ClientDemo extends Thread {
        @Override
        public void run() {
            try {
                Socket socket = new Socket("127.0.0.1", 8000);
                while (true) {
                    socket.getOutputStream().write(("测试数据").getBytes());
                    socket.getOutputStream().flush();
                    Thread.sleep(2000);
                }
            } catch (Exception e) {
            }
        }
    }
}

私たちは、伝統的なIOモデルでは、各接続は、各スレッドの成功はしばらく無限ループが含まれていた後、維持するためのスレッドを作成するために必要とされ、サーバ側のコードから見ることができます。

あなたが少数のユーザーの場合に実行している場合は問題ありませんが、ビジネスユーザーの比較的多数のために、サーバが接続の数十万人をサポートする必要があり、IOモデルは適切ではないかもしれません。
10 000 10 000は、次に万ループしながら、対応するスレッドに接続がある場合、このモデルは次のような問題があります。

  • 複数のクライアントは、それがより多くの処理スレッドを作成します。スレッドは、オペレーティングシステムの非常に貴重な資源であり、同じ時間は、ブロックされた状態で多数のスレッドは、リソースの非常に深刻な廃棄物です。サービスは、このような二重の11回の活動として、ピークフローへの影響を、遭遇した場合や、スレッドプールは、瞬時にサーバーの麻痺、その結果、枯渇されます。
  • それは頻繁なスレッド切り替え、アプリケーションの性能が急激に低下スレッドオペレーティングシステムの爆発後、通信をブロックしているためです。
  • IOプログラミングデータをバイトストリーム単位で読み出され、効率が高くありません。

NIOプログラミング

NIOは、また新しい-IOまたは非ブロッキングIOと呼ばれる、非ブロックIOであると理解。NIOのプログラミングモデルは、接続が新しいスレッドを作成することはもはや新しいではありませんが、あなたは責任によって読み書きするために、このスレッドの全てを直接固定スレッドにバインドされた接続を入れ、その後、接続することができ、我々 IOとNIOを比較する画像と:
ここに画像を挿入説明
上記のように、IOモデルを、接続は、スレッドを作成し、whileループに対応し、無限ループの目的は、常にデータは、この接続上で読み取ることができるかどうかを監視することです。データがないので、死のサイクルの数が無駄にされながら、しかし、ほとんどの場合、10000は、同時にそのため、接続されている読み込むべきデータ量の少ない内部で接続されています。

ループが無限ループ、スレッドによるループ制御となりながら、NIOのモデルでは、あなたは非常に多くの死者を置くことができます。これは、NIOのモデル選択(セレクタ)、および接続後の効果であり、我々はデータを読み取るために、しかしチェックにより、セレクタバーにこのレジスタに直接接続することがあるかどうかを監視するために、無限ループを作成していないながら、このセレクタは、モニタは、バッチ可読データに接続し、データを読み出すことができます。

チャンネル(チャンネル)、バッファー(緩衝液)、セレクタ(セレクタ):3つのコア・コンポーネントのNIO

チャンネル(チャンネル)

IOは、ストリーム(ストリーム)アップグレードの伝統的です。流れは一方向、別個読み取りおよび書き込み(入力ストリームとのOutputStream)であり、チャネルは、双方向であり、読み出し動作が実行されてもよいが、また書き込むことができます。

バッファ(バッファ)

バッファメモリ領域として理解することができる、データが書き込まれ、それの後に読み取ることができます。

セレクタ(セレクタ)

セレクタ(セレクタ)効果を多重達成する特定の機構を選択することにより、彼女の上部流路(チャネル)は、複数のレジスタを監視するために別のスレッドを実行することができます。

NIO IO相対的な利点:

IOストリーム指向の、バイトが1バイトの基礎となるオペレーティング・システム・データから読み出され、データが唯一の他の端から読み取ることができ、あなたは、データストリームに前後に移動することはできませんあるたびに。NIOバッファに直面して、データは、この内部バッファから読み取られ、必要なときにバッファ中で前後に移動させることができます。
IOスレッドがデータや書き込みデータを読み取るためにする場合があり、いくつかのデータが読み出される、またはデータが完全にスレッドが何かを行うことができない時に、書き込まれるまで、スレッドはブロックされ、その手段、ブロックされています。NIOは、ノンブロッキング、必要性は常に他のことを行う前に、完全に操作を待つのではなく、プロセスを待っている間に、あなたが何かを行うことができ、我々は、サーバーのリソースを最大限に活用することができます。
NIOは、IOマルチプレクサセレクタを導入しました。チャネルセレクタを提供する複数のチャネルは、チャネルアダプタ用同時に、スレッドプール内のドッキングハンドルチャネルに適切なスレッドを選択することができ、登録されたサービス・スレッドです。NIOモデルは大幅スレッドの数を減少させるため、スレッドスイッチング効率は、従って、大幅に改善されます。
そして、NIOを使用して同じシーンの前に(コピーコードが効果を発揮)を実現。

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 负责轮询是否有新的连接
        Selector serverSelector = Selector.open();
        // 负责轮询处理连接中的数据
        Selector clientSelector = Selector.open();
        new Thread() {
            @Override
            public void run() {
                try {
                    // 对应IO编程中服务端启动
                    ServerSocketChannel listenerChannel = ServerSocketChannel.open();
                    listenerChannel.socket().bind(new InetSocketAddress(8000));
                    listenerChannel.configureBlocking(false);
                    // OP_ACCEPT表示服务器监听到了客户连接,服务器可以接收这个连接了
                    listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
                    while (true) {
                        // 监测是否有新的连接,这里的1指的是阻塞的时间为1ms
                        if (serverSelector.select(1) > 0) {
                            Set<SelectionKey> set = serverSelector.selectedKeys();
                            Iterator<SelectionKey> keyIterator = set.iterator();
                            while (keyIterator.hasNext()) {
                                SelectionKey key = keyIterator.next();
                                if (key.isAcceptable()) {
                                    try {
                                        // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
                                        SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
                                        clientChannel.configureBlocking(false);
                                        // OP_READ表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
                                        clientChannel.register(clientSelector, SelectionKey.OP_READ);
                                    } finally {
                                        keyIterator.remove();
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException ignored) {
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                try {
                    while (true) {
                        // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为1ms
                        if (clientSelector.select(1) > 0) {
                            Set<SelectionKey> set = clientSelector.selectedKeys();
                            Iterator<SelectionKey> keyIterator = set.iterator();
                            while (keyIterator.hasNext()) {
                                SelectionKey key = keyIterator.next();
                                if (key.isReadable()) {
                                    try {
                                        SocketChannel clientChannel = (SocketChannel) key.channel();
                                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                        // (3) 读取数据以块为单位批量读取
                                        clientChannel.read(byteBuffer);
                                        byteBuffer.flip();
                                        System.out.println("线程" + name + ":" + Charset.defaultCharset().newDecoder().decode(byteBuffer)
                                                .toString());
                                    } finally {
                                        keyIterator.remove();
                                        key.interestOps(SelectionKey.OP_READ);
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException ignored) {
                }
            }
        }.start();
    }
}
公開された41元の記事 ウォン称賛47 ビュー30000 +

おすすめ

転載: blog.csdn.net/u014526891/article/details/105388898