자바 니 오 는 느 린 연결 을 어떻게 처리 합 니까?

6416 단어 java NIO
자바 니 오 는 느 린 연결 을 어떻게 처리 합 니까?
기업 급 서버 소프트웨어 에 대해 고성능 과 확장 성 은 기본 적 인 요구 이다.그 외 에 도 다양한 환경 에 대응 할 수 있 는 능력 이 있어 야 한다.예 를 들 어 좋 은 서버 소프트웨어 는 모든 클 라 이언 트 가 빠 른 처리 능력 과 좋 은 네트워크 환경 을 가지 고 있다 고 가정 해 서 는 안 된다.클 라 이언 트 의 운행 속도 가 느 리 거나 네트워크 속도 가 느 리 면 전체 요청 시간 이 길 어 지 는 것 을 의미한다.서버 의 경우 이 클 라 이언 트 의 요청 이 더 오래 걸 릴 것 임 을 의미한다.이 시간의 지연 은 서버 에 의 한 것 이 아니 므 로 CPU 의 점용 은 증가 하지 않 지만 네트워크 연결 시간 이 증가 하고 처리 스 레 드 의 점용 시간 도 증가 합 니 다.이 로 인해 현재 처리 스 레 드 와 다른 자원 이 빨리 방출 되 지 못 하고 다른 클 라 이언 트 의 요청 에 의 해 재 활용 되 지 못 합 니 다.예 를 들 어 Tomcat 는 느 린 속도 로 연 결 된 클 라 이언 트 가 대량으로 존재 할 때 스 레 드 자원 이 느 린 속도 로 연결 되 어 서버 가 다른 요청 에 응 하지 못 하 게 합 니 다.
앞에서 소개 한 바 와 같이 NIO 의 비동기 적 이 고 막 히 지 않 는 형식 은 적은 라인 으로 대량의 요청 에 서 비 스 를 제공 할 수 있 게 한다.Selector 의 등록 기능 을 통 해 준 비 된 채널 로 선택적으로 돌아 갈 수 있 습 니 다. 그러면 모든 요청 에 단독 스 레 드 를 할당 하지 않 아 도 됩 니 다.
일부 유행 하 는 NIO 프레임 에서 OP 를 볼 수 있 습 니 다.ACCEPT 와 OPREAD 의 처리.OPWRITE 의 처리.우리 가 자주 보 는 코드 는 요청 처리 가 끝 난 후에 다음 코드 를 통 해 결 과 를 클 라 이언 트 에 게 되 돌려 주 는 것 입 니 다.
[예 17.7] 아니 OPWRITE 가 처리 하 는 샘플:

while (bb.hasRemaining()) {

    int len = socketChannel.write(bb);

    if (len < 0) {

        throw new EOFException();

    }

}


이렇게 쓰 는 것 은 대부분의 상황 에서 아무런 문제 가 없다.그러나 클 라 이언 트 의 네트워크 환경 이 매우 나 쁜 상황 에서 서버 는 심각 한 타격 을 받 을 것 이다.
클 라 이언 트 의 네트워크 나 중간 교환기 의 문제 로 인해 네트워크 전송 효율 이 낮 을 경우 서버 가 준비 한 반환 결과 가 TCP / IP 층 을 통 해 클 라 이언 트 로 전송 되 지 못 하기 때 문 입 니 다.이때 위의 절 차 를 실행 할 때 다음 과 같은 상황 이 발생 한다.
(1) bb. hasRemaining () 은 항상 "true" 입 니 다. 서버 의 반환 결과 가 준비 되 어 있 기 때 문 입 니 다.
(2) socketChannel. write (bb) 의 결 과 는 0 이 었 습 니 다. 네트워크 때문에 데이터 가 전달 되 지 않 았 습 니 다.
(3)   비동기 비 차단 방식 이기 때문에 socketChannel. write (bb) 는 막 히 지 않 고 바로 돌아 갑 니 다.
(4)   한 동안 이 코드 는 끊임없이 빠르게 실행 되 어 대량의 CPU 자원 을 소모 하고 있다.사실 네트워크 가 현재 데이터 전송 을 허용 할 때 까지 구체 적 인 임 무 는 하지 않 았 다.
이런 결 과 는 분명히 우리 가 원 하 는 것 이 아니다.그래서 우 리 는 OPWRITE 도 처리 해 야 한다.NIO 에서 가장 많이 쓰 이 는 방법 은 다음 과 같다.
[예 17.8] 일반 NIO 프레임 중 OPWRITE 의 처리:

while (bb.hasRemaining()) {

    int len = socketChannel.write(bb);

    if (len < 0){

        throw new EOFException();

    }

    if (len == 0) {

        selectionKey.interestOps(

                        selectionKey.interestOps() | SelectionKey.OP_WRITE);

        mainSelector.wakeup();

        break;

    }

}


위의 프로그램 이 네트워크 가 좋 지 않 을 때 이 채널 의 OPWRITE 작업 이 Selector 에 등록 되면 네트워크 가 복구 되 고 채널 이 결과 데 이 터 를 클 라 이언 트 에 계속 되 돌 릴 수 있 을 때 Selector 는 Selection Key 를 통 해 프로그램 에 알 리 고 쓰기 작업 을 수행 합 니 다.이렇게 하면 대량의 CPU 자원 을 절약 하여 서버 가 각종 열악한 네트워크 환경 에 적응 할 수 있 게 할 수 있다.
하지만, Grizzly 중 OPWRITE 의 처 리 는 그렇지 않 습 니 다.우리 먼저 Grizzly 의 소스 코드 를 봅 시다.Grizzly 에서 요청 결과 에 대한 반환 은 ProcessTask 에서 처리 되 며, SocketChannel OutputBuffer 클래스 를 거 쳐 OutputWriter 클래스 를 통 해 결 과 를 되 돌려 주 는 동작 을 수행 합 니 다.Output Writer 에서 OP 처리WRITE 의 코드 는 다음 과 같 습 니 다:
[예 17.9] Grizzly 중 대 OPWRITE 의 처리:

public static long flushChannel(SocketChannel socketChannel,

        ByteBuffer bb, long writeTimeout) throws IOException

{

    SelectionKey key = null;

    Selector writeSelector = null;

    int attempts = 0;

    int bytesProduced = 0;

    try {

        while (bb.hasRemaining()) {

            int len = socketChannel.write(bb);

            attempts++;

            if (len < 0){

                throw new EOFException();

            }

            bytesProduced += len;

            if (len == 0) {

                if (writeSelector == null){

                    writeSelector = SelectorFactory.getSelector();

                    if (writeSelector == null){

                        // Continue using the main one

                        continue;

                    }

                }

                key = socketChannel.register(writeSelector, key.OP_WRITE);

                if (writeSelector.select(writeTimeout) == 0) {

                    if (attempts > 2)

                        throw new IOException("Client disconnected");

                } else {

                    attempts--;

                }

            } else {

                attempts = 0;

            }

        }

    } finally {

        if (key != null) {

            key.cancel();

            key = null;

        }

        if (writeSelector != null) {

            // Cancel the key.

            writeSelector.selectNow();

            SelectorFactory.returnSelector(writeSelector);

        }

    }

    return bytesProduced;

} 


위의 프로그램 예 17.9 와 예 17.8 의 차이 점 은 네트워크 상황 으로 인 한 전송 데이터 가 지장 을 받 는 것 을 발견 할 때 예 17.8 의 처 리 는 현재 채널 을 현재 의 Selector 에 등록 하 는 것 이다.예 17.9 에서 프로그램 은 Selector Factory 에서 임시 Selector 를 얻 었 다.이 임시 Selector 를 얻 은 후에 프로그램 은 막 힌 동작 을 했 습 니 다: writeSelector. select (writeTimeout).이 차단 작업 은 일정 시간 내 에 이 채널 의 전송 상 태 를 기다 릴 것 입 니 다.대기 시간 이 너무 길 면 현재 클 라 이언 트 의 연결 이 이상 하 게 중단 되 었 다 고 생각 합 니 다.
이런 실현 방식 은 꽤 논쟁 을 받는다.많은 개발 자 들 이 Grizzly 의 작성 자가 왜 예 17.8 모드 를 사용 하지 않 는 지 의심 하고 있다.또한 실제 처리 에서 Grizzly 의 처리 방식 은 NIO 의 비 차단 적 인 장점 을 사실상 포기 하고 writeSelector. select (writeTimeout) 를 사용 하여 차단 작업 을 했다.CPU 의 자원 은 낭비 되 지 않 았 지만 스 레 드 자원 은 막 힌 시간 내 에 이 요청 에 의 해 점유 되 어 다른 요청 에 사용 할 수 없습니다.
Grizzly 의 저자 가 이에 대한 대답 은 다음 과 같다.
(1)   임시 Selector 를 사용 하 는 목적 은 스 레 드 간 전환 을 줄 이 는 것 입 니 다.현재 Selector 는 일반적으로 OP 를 처리 합 니 다.ACCEPT 와 OPREAD 의 조작.임시 Selector 를 사용 하면 주 Selector 의 부담 을 줄 일 수 있 습 니 다.등록 할 때 스 레 드 전환 이 필요 해 불필요 한 시스템 호출 을 일 으 킬 수 있다.이런 방식 은 스 레 드 간 의 빈번 한 전환 을 피하 고 시스템 의 성능 향상 에 유리 하 다.
(2)   writeSelector. select (writeTimeout) 가 차단 작업 을 했 지만 이런 상황 은 소수의 극단 적 인 환경 에서 만 발생 한다.대부분의 클 라 이언 트 는 이런 현상 이 자주 나타 나 지 않 기 때문에 같은 시간 에 막 히 는 스 레 드 가 많 지 않 습 니 다.
(3)   이 차단 작업 을 이용 하여 이상 하 게 중 단 된 고객 연결 을 판단 합 니 다.
(4)   압력 실험 을 통 해 이런 실현 의 성능 이 매우 좋다 는 것 을 증명 하 였 다.

좋은 웹페이지 즐겨찾기