스킬을 재생 : NIO [시리즈] - 원자로 모델

개인 블로그 탐색 페이지 (클릭 오른쪽에있는 링크를 개인 블로그를 엽니) : 다니엘은 기술 스택에 당신을 데려 갈 

유닉스에 의한 IO 모델을 도입하기 전에, 아마도 또한 다섯 개 IO 모델을 배웠다. 동기 비 차단 IO 속하는 자바 NIO는 IO 자바 더 IO 모델 대응 다중화 없지만,이 반응기 NIO에 기초한 대응하는 프로그래밍 모델은 모드 멀티플렉싱 구현. 이 문서는 다음에서 원자로 모델을 설명합니다 :

  1. 원자로는 무엇인가

  2. 왜 우리는 문제를 해결하기 위해, 사용합니까

  3. 어떻게 더 나은 방법

  4. 다른 이벤트 처리 모드

리액터 무엇 첫째,

원자로에 대한 어떤 것은 우리가보기 위키의 시점에서 시작한다는 것입니다 :

반응기의 디자인 패턴은 하나 개 이상의 입력하여 서비스 핸들러에 동시에 전달 서비스 요청을 처리하기위한 패턴을 처리하는 이벤트입니다. 서비스 처리기는 들어오는 요청을 다중화 및 관련 요청 처리기에 기적을 전달합니다.

위의 텍스트에서, 우리는 다음과 같은 주요 사항을 볼 수 있습니다 :

  1. 이벤트 기반 (이벤트 처리)

  2. 하나 이상의 입력 소스 (하나 이상의 입력)를 처리 할 수

  3. 분산 서비스 처리기 대응 요청 핸들러 (들)을 동기화 된 멀티플렉서를 이용하여 상기 입력 이벤트 (이벤트) 가공

도입의 원자로 패턴에 POSA2 때문에, 우리는 원자로의 접근 방식을 이해 :

  1. 동기화 소스 (선택 () 실현을 사용하여) 여러 이벤트를 대기

  2. 이벤트 할당 다중화 및 대응 이벤트 처리 서비스 디스패치 서버를 사용하여 중앙 처리부 (파견)

  3. 이벤트의 분리 및 분해 밖으로 송출 서비스에서 해당 이벤트 서비스 프로그램 (핸들러)

 

OMT 클래스 다이어그램의 원자로 패턴 디자인에 관하여 :

 

둘째, 왜 사용하는 원자로

일반적인 네트워크 서비스는 각 클라이언트는 서버와의 연결을 유지하고 로그인하는 경우. 그런 다음 서버 및 다중 연결을 유지하기 위해 클라이언트가 특히 긴 링크 서비스, 쓰기, 및 contnect 클라이언트를 나올 읽고, C 측의 수는, 우리는 같은 IO 연결의 끝을 유지해야합니다. 이 서버에 대한 상당한 오버 헤드이다.

1, BIO

예를 들어, 우리는 유지하고 클라이언트 연결에 BIO 방법을 사용합니다 :

// 主线程维护连接
  public void run() {
      try {
          while (true) {
              Socket socket = serverSocket.accept();
              //提交线程池处理
              executorService.submit(new Handler(socket));
          }
      } catch (Exception e) {
          e.printStackTrace();
      }
  }
​
  // 处理读写服务
  class Handler implements Runnable {
      public void run() {
          try {
              //获取Socket的输入流,接收数据
              BufferedReader buf = new BufferedReader(new InputStreamReader(socket.getInputStream()));
              String readData = buf.readLine();
              while (readData != null) {
                  readData = buf.readLine();
                  System.out.println(readData);
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }

 

물론, 자원의 피할 고갈하기 위해, 우리는 읽기와 쓰기 서비스를 처리하기 위해 스레드 풀을 사용합니다. 그러나 그렇게 여전히 일을하는 것은 명백한 단점이 있습니다 :

  1. 동기 차단 IO, 쓰기 차단, 스레드 대기 너무 오래

  2. 스레드의 발전 전략에서만 사용할 CPU 리소스가 한계에 연결되어 동시 연결의 수를 기반으로 개발 될 수 없습니다 스레드 수의 관점에서 정의 될 수있다. 효율적이고 공평한 클라이언트 요청을 보장하기 어렵다.

  3. 여러 스레드간에 문맥 전환은 스레드 생성 효율이 높지 않고, 그리고 확장 될 수 없다

  4. 상태 데이터 및 기타 데이터가 일치해야, 우리는 동기화 제어 동시성이 필요합니다

2, NIO

당신이 더 잘 처리 할 수있는 다른 방법은 우리가 NIO을 지원하는 기본 메커니즘을 처리하기 위해 NIO를 사용할 수 있습니다 :

  1. IO는 읽기 및 쓰기 비는 차단

  2. IO 작업 기반 이벤트를 분산, 및 FD의 다중 모니터 지원

우리는 NIO 구현에 의존 방식을 보면 :

public NIOServer(int port) throws Exception {
      selector = Selector.open();
      serverSocket = ServerSocketChannel.open();
      serverSocket.socket().bind(new InetSocketAddress(port));
      serverSocket.configureBlocking(false);
      serverSocket.register(selector, SelectionKey.OP_ACCEPT);
  }
​
  @Override
  public void run() {
      while (!Thread.interrupted()) {
          try {
              //阻塞等待事件
              selector.select();
              // 事件列表
              Set selected = selector.selectedKeys();
              Iterator it = selected.iterator();
              while (it.hasNext()) {
                  it.remove();
                  //分发事件
                  dispatch((SelectionKey) (it.next()));
              }
          } catch (Exception e) {
​
          }
      }
  }
​
  private void dispatch(SelectionKey key) throws Exception {
      if (key.isAcceptable()) {
          register(key);//新链接建立,注册
      } else if (key.isReadable()) {
          read(key);//读事件处理
      } else if (key.isWritable()) {
          wirete(key);//写事件处理
      }
  }
​
  private void register(SelectionKey key) throws Exception {
      ServerSocketChannel server = (ServerSocketChannel) key
              .channel();
      // 获得和客户端连接的通道
      SocketChannel channel = server.accept();
      channel.configureBlocking(false);
      //客户端通道注册到selector 上
      channel.register(this.selector, SelectionKey.OP_READ);
  }

 

우리는 NIO의 예는 반응기의 거의 그림자되었습니다 볼 수 있습니다

  1. 이벤트 기반 -> 선택 (여러 모니터 SocketChannel에 대한 지원)

  2. 통합 이벤트 파견 센터 -> 파견

  3. 이벤트 처리 서비스 -> 읽기 및 쓰기

 

사실 NIO는 BIO 1 & 2 노출의 문제를 해결하고, 클라이언트 - 서버 업그레이드의 양 동시있다, 더 이상 (셀렉터를 클라이언트에 핸들을 스레드를 제한,하지만 스레드는 여러 클라이언트를 유지할 수 여러 SocketChannel에 모니터 지원).

그러나 여전히 완벽한 원자로 패턴 아니라, 제 1 반응기는 좋은 모델이 더 나은 확장 성을 지원한다 디자인 패턴, 더 분명히 다음과 같은 특성을 가지고 있어야 좋은 원자로 패턴뿐만 아니라, 지원하지 않습니다

  1. 적은 리소스를 사용하여, 일반적으로는 클라이언트 스레드가 필요하지 않습니다

  2. 적은 오버 헤드, 적은 콘텍스트 스위치 및 록킹

  3. 서버 상태를 추적 할 수

  4. 바인딩 이벤트 처리기를 관리 할 수

그래서 좋은 원자로 패턴처럼해야 하는가?

세, 원자로

자바 NIO 응용 프로그램에서 원자로 패턴의 건설은 "위대한 하나님 더그 레아 (자바는 위대한 하나님의 무한한 존경한다) 자바에서 확장 IO 좋은 박람회에서". 우리는 위대한 하나님이 각각 소개 삼가지 원자로를 설명 사용합니다.

첫째, 우리는 원자로 패턴 기반 처리 모드는 다음과 같은 세 가지 역할을 정의 :

  • 원자로  의 I / O 이벤트는 해당 처리기에 할당

  • 셉터  처리 클라이언트 새로운 접속 및 프로세서 사슬로 요청을 디스패치

  • 핸들러는  비 차단 읽기 / 쓰기 작업을 수행

 

1, 단일 반응기 단일 스레드 모델

우리는 코드의 구현을 참조하십시오

  /**
    * 等待事件到来,分发事件处理
    */
  class Reactor implements Runnable {
​
      private Reactor() throws Exception {
​
          SelectionKey sk =
                  serverSocket.register(selector,
                          SelectionKey.OP_ACCEPT);
          // attach Acceptor 处理新连接
          sk.attach(new Acceptor());
      }
​
      public void run() {
          try {
              while (!Thread.interrupted()) {
                  selector.select();
                  Set selected = selector.selectedKeys();
                  Iterator it = selected.iterator();
                  while (it.hasNext()) {
                      it.remove();
                      //分发事件处理
                      dispatch((SelectionKey) (it.next()));
                  }
              }
          } catch (IOException ex) {
              //do something
          }
      }
​
      void dispatch(SelectionKey k) {
          // 若是连接事件获取是acceptor
          // 若是IO读写事件获取是handler
          Runnable runnable = (Runnable) (k.attachment());
          if (runnable != null) {
              runnable.run();
          }
      }
​
  }
  
  /**
    * 连接事件就绪,处理连接事件
    */
  class Acceptor implements Runnable {
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注册读写
                  new Handler(c, selector);
              }
          } catch (Exception e) {
​
          }
      }
  }

이것은 기본적인 단일 반응기 단일 스레드 모델이다. hanlder 처리 스레드되는 반응기 후, 소켓을 역 다중화에 대한 책임, 이벤트가 연결 트리거의 도착 후 새 연결이, IO는 읽기 및 쓰기 이벤트와 수락 자 처리 함.

채택의 주요 작업은 클라이언트와 관련 된 SocketChannel을 획득 한 후, 핸들러를 구축하는 것입니다, 바인드 SocketChannel에 해당 racotor 분배에 따라, 해당 hanlder에, 읽고 이벤트 이후 쓰기, hanlder는 (모든 이벤트는 IO 연결되어 처리 될 수 있습니다 선택기,이 원자로 유통)에 주어진.

이 모델은 신속하게 현장의 조립을 완료 할 수있는 서비스 처리 체인 프로세서에 적용 할 수있다. 그러나이 단일 스레드 모델은 너무별로, 실제 사용을 멀티 코어 자원을 최대한 활용하지 않습니다.

 

2, 하나의 멀티 스레드 모델 원자로

더 많은 일을 초점을 맞추고있는 주요 반응기의 성능 오버 헤드를 줄일 수 처리하는 스레드 풀에 넘겨 독자 IO 이벤트를, 얻는 것입니다 비즈니스 로직을 처리 한 후 첫 번째 단일 스레드 모델은 상대적이다 그래서 같은 작품의 이벤트 분포는 전체 응용 프로그램의 처리량을 향상시킬 수 있습니다.

우리는 방법을 보면 :

  /**
    * 多线程处理读写业务逻辑
    */
  class MultiThreadHandler implements Runnable {
      public static final int READING = 0, WRITING = 1;
      int state;
      final SocketChannel socket;
      final SelectionKey sk;
​
      //多线程处理业务逻辑
      ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
​
​
      public MultiThreadHandler(SocketChannel socket, Selector sl) throws Exception {
          this.state = READING;
          this.socket = socket;
          sk = socket.register(selector, SelectionKey.OP_READ);
          sk.attach(this);
          socket.configureBlocking(false);
      }
​
      @Override
      public void run() {
          if (state == READING) {
              read();
          } else if (state == WRITING) {
              write();
          }
      }
​
      private void read() {
          //任务异步处理
          executorService.submit(() -> process());
​
          //下一步处理写事件
          sk.interestOps(SelectionKey.OP_WRITE);
          this.state = WRITING;
      }
​
      private void write() {
          //任务异步处理
          executorService.submit(() -> process());
​
          //下一步处理读事件
          sk.interestOps(SelectionKey.OP_READ);
          this.state = READING;
      }
​
      /**
        * task 业务处理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }

 

3, 멀티 원자로 모델을 멀티 스레드

 

두번째 모델보다 셋째 모델은, 반응기의 두 부분으로 나누어 져,

  1. mainReactor 새 연결을 처리하기 위해, 서버 소켓을 모니터링에 대한 책임은 SocketChannel에이 subReactor에 등록 된 지정하는 설정됩니다.

  2. subReactor 등록 된 SocketChannel 여러 mainReactor에 따라 자신의 선택, 별도의 읽기 및 쓰기 IO 이벤트를 유지, 읽기 및 쓰기 데이터 네트워크 전체에 다른 작업자 스레드 풀을 던졌다 비즈니스 프로세스의 기능.

 

우리는 방법을 보면 :

  /**
    * 多work 连接事件Acceptor,处理连接事件
    */
  class MultiWorkThreadAcceptor implements Runnable {
​
      // cpu线程数相同多work线程
      int workCount =Runtime.getRuntime().availableProcessors();
      SubReactor[] workThreadHandlers = new SubReactor[workCount];
      volatile int nextHandler = 0;
​
      public MultiWorkThreadAcceptor() {
          this.init();
      }
​
      public void init() {
          nextHandler = 0;
          for (int i = 0; i < workThreadHandlers.length; i++) {
              try {
                  workThreadHandlers[i] = new SubReactor();
              } catch (Exception e) {
              }
​
          }
      }
​
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注册读写
                  synchronized (c) {
                      // 顺序获取SubReactor,然后注册channel 
                      SubReactor work = workThreadHandlers[nextHandler];
                      work.registerChannel(c);
                      nextHandler++;
                      if (nextHandler >= workThreadHandlers.length) {
                          nextHandler = 0;
                      }
                  }
              }
          } catch (Exception e) {
          }
      }
  }
 
  /**
    * 多work线程处理读写业务逻辑
    */
  class SubReactor implements Runnable {
      final Selector mySelector;
​
      //多线程处理业务逻辑
      int workCount =Runtime.getRuntime().availableProcessors();
      ExecutorService executorService = Executors.newFixedThreadPool(workCount);
​
​
      public SubReactor() throws Exception {
          // 每个SubReactor 一个selector 
          this.mySelector = SelectorProvider.provider().openSelector();
      }
​
      /**
        * 注册chanel
        *
        * @param sc
        * @throws Exception
        */
      public void registerChannel(SocketChannel sc) throws Exception {
          sc.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
      }
​
      @Override
      public void run() {
          while (true) {
              try {
              //每个SubReactor 自己做事件分派处理读写事件
                  selector.select();
                  Set<SelectionKey> keys = selector.selectedKeys();
                  Iterator<SelectionKey> iterator = keys.iterator();
                  while (iterator.hasNext()) {
                      SelectionKey key = iterator.next();
                      iterator.remove();
                      if (key.isReadable()) {
                          read();
                      } else if (key.isWritable()) {
                          write();
                      }
                  }
​
              } catch (Exception e) {
​
              }
          }
      }
​
      private void read() {
          //任务异步处理
          executorService.submit(() -> process());
      }
​
      private void write() {
          //任务异步处理
          executorService.submit(() -> process());
      }
​
      /**
        * task 业务处理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }
​

세 번째 모델, 우리가 그것을 볼 수 mainReactor 주로 IO 작업을 확립 네트워크 연결을 처리하는 데 사용됩니다, 일반적으로 스레드가 그것을 처리하지만, subReactor 주요 작업과 작업을 처리하는 데이터 교환 및 비즈니스 이벤트에 대한 소켓을 설정할 수 있습니다 수 및 CPU의 개수를 각각 subReactor 군을 처리하기 위해, 일반적으로 동일하다.

이 모델에서, 각 모듈 작업 충성, 적은 커플 링, 성능 및 안정성 개선의 많은 또한, 동시 클라이언트의 수는 수준의 수백만 지원할 수 있습니다.

이 모델의 응용 프로그램에서 많은 우수한 광산 ​​건설은 미나로와 그물코 등등에 적용된있다. 스레드 풀의 세 번째 형태는 위의 변형을 제거하기 위해, 기본 모드 인 Netty NIO이다. 다음 섹션에서는 우리는 그물코 아키텍처 패턴을 설명에 초점을 맞출 것이다.

 

넷째, 이벤트 처리 모드

에서는  더글라스 슈미트  사 개 이벤트 처리 모드가있는 경우의 처리 모드 걸작」POSA2 "에 소개했다 :

  1. 원자로  

  2. Proactor  

  3. 비동기 완료 토큰  

  4. 채택 커넥터  

1.  Proactor

이 문서에서는 리액터 반면 그 중 하나 Proactor 된 것을 제외하고는, 치료 reacotor 전체 구조와 유사한 Proactor는 손잡이 사용자 스레드없이 판독 비동기 처리에서 기록 데이터, 비 - 블로킹 비동기 IO 방식을 사용 서비스 프로그램은 비즈니스 이벤트를 처리하기보다는 IO 차단에 더 초점을 맞 춥니 다.

2.  비동기 완료 토큰

간단히 말해, ACT는 응용 프로그램 서비스 운영, 서비스 처리 및 대응 완료 이벤트를 처리하는 비동기 호출입니다. 이 토큰의 문자 적 의미에서, 우리는 아마 유지하고 상태를 제공하는 것입니다 이해할 수있을 것입니다.

예를 들어, 응용 프로그램이 타사 서비스, 타사 동기화를 시작하는 요청이있을 경우 타사의 자원을 필요로 일반적인 비즈니스 스레드 요청에 대한 수요를 호출하고, 애플리케이션 성능을 향상시키기 위해 일반적으로있다, 당신은 요청 비동기 방식으로 시작해야합니다 그러나, 우리의 응용 프로그램이 변경이 시간에 도착, 문맥과 상황에 맞는 정보를 한 후 비동기 요청 및 기타 데이터, 당신은 치료에 갈 수 없습니다.

ACT의  솔루션은받는 사람,받는 사람의 회신을 보낸 다음이 시간이 콜 현장 사업을 복원 할 수 있습니다,이 토큰을 찍을 때, 비동기 전송하기 전에 기록 정보에 대한 토큰 방법을 사용하여이 문제이다.

 

우리는 클라이언트 대신 차단의 다른 비즈니스 로직 처리를 계속할 수,이 단계를 처리하는 클라이언트 위의 그림에서 볼 수 있습니다. 리턴 서비스 중 토큰 정보를 가져올 것이다.  

3.  채택 커넥터

셉터 커넥터는 반응기에 결합되며, 그것의 변형으로 볼 수 있으며, 이는 상술 한 제 3 반응기의 구현처럼 보이지만 본질적으로 상이한있다.

수락 커넥터 모드 초기화는 연결이 설정되고 서비스 일단 시스템 초기화 서비스는 비 결합으로부터 분리되어, 별도의 다른 네트워크 서비스에 접속한다. 커넥터는 적극적 원격 수신기 구성 요소의 연결을 설정하고 연결에 교환 프로세스 데이터 서비스 프로세서를 초기화한다. 마찬가지로, 소켓은 수동적 요청이 도착하고 접속상에서 데이터 교환 처리 서비스 프로세서 초기화에서 이러한 연결이 설정되면, 리모트 커넥터로부터의 연결 요청을 대기한다. 서비스 프로세서이어서 애플리케이션 특정 처리를 수행하도록 초기화하고, 접속 통신 조립체 커넥터와 리셉터클에 의해 확립된다.

이러한 거래의 장점은 다음과 같습니다 :

  1. 일반적으로, 연결 설정 및 서비스 초기화에 대한 정책의 변화의 주파수는 애플리케이션 서비스 구현 및 통신 프로토콜보다 훨씬 적습니다.

  2. 쉬운 서비스, 새로운 서비스의 새로운 유형을 추가하고 기존의 연결 설정 및 서비스 초기화 소프트웨어에 영향을주지 않고, 새로운 통신 프로토콜을 구현합니다. 이러한 사용 IPX / SPX 또는 TCP 통신 프로토콜로.

  3. 접속 역할과 통신의 역할, 역할 연결 동수 디커플링은 연결 대 바로 연결을 받아 들인다. 통신 역할 만 데이터 교환.

  4. (소켓 또는 TLI와 같은) 낮은 수준의 네트워크 프로그래밍 API 보안의 부족의 유형 프로그래머는 오프 보호. 통신을 기본 사업 개발 관계

첨부 자바 / C / C ++ / 기계 학습 / 알고리즘 및 데이터 구조 / 프런트 엔드 / 안드로이드 / 파이썬 / 프로그래머 읽기 / 하나의 책 책 Daquan의 :

(건조 개인 블로그에이 열 오른쪽 클릭) : 기술 건조한 꽃을
===== >> ① [자바 다니엘은 진보의 길에 당신을 데려] << ====
===== >> ② [+ ACM 알고리즘 데이터 구조 다니엘 진보에 도로에 걸릴] << ===
===== >> ③ [데이터베이스 다니엘 진보에 도로에 걸릴] << == ===
===== >> ④ [다니엘 웹 프런트 엔드는 고급가는 길에 당신을 데려 갈 수 있습니다] << ====
===== >> ⑤ [기계 학습 파이썬 다니엘은 당신에게 항목을 고급 도로] << ====
===== >> ⑥ [건축가 다니엘은 진보의 길에 당신을 데려] << =====
===== >> ⑦ [C ++ 다니엘은 도로에 당신을 데려 갈 고급] << ====
===== >> ⑧ [다니엘은 진보의 길에 당신을 데려 갈 IOS] << ====
=====> > ⑨ [웹 보안 다니엘은가는 길에 당신을 데려 갈 고급] << =====
===== >> ⑩ [리눅스 운영 체제와 다니엘은 진보의 길에 당신을 데려] << = ====

더 불로 과일이 없다, 당신이 젊은 친구가, 친구가 기술을 배우고 싶은 희망, 도로의 방법으로 모든 장애물을 극복, 기술에 묶어 책을 이해 한 다음 코드에 노크, 원리를 이해하고 연습을 갈 것입니다 결정 그것은 당신의 미래, 당신에게 꿈을 생활, 직장을 가져올 것이다.

게시 47 개 원래 기사 · 원의 칭찬 0 · 조회수 278

추천

출처blog.csdn.net/weixin_41663412/article/details/104869211