>웹 프론트엔드 >JS 튜토리얼 >Node.js 비동기 I/O 연구 Notes_node.js

Node.js 비동기 I/O 연구 Notes_node.js

WBOY
WBOY원래의
2016-05-16 16:32:051203검색

'비동기'라는 용어는 Javascript, AJAX와 함께 웹을 휩쓴 Web 2.0 물결에서 널리 사용되었습니다. 그러나 대부분의 고급 프로그래밍 언어에서는 비동기성이 거의 발생하지 않습니다. PHP는 이 기능을 가장 잘 구현합니다. 즉, 비동기식을 차단할 뿐만 아니라 멀티스레딩도 제공하지 않습니다. PHP는 동기식 차단 방식으로 실행됩니다. 이러한 이점은 프로그래머가 비즈니스 논리를 순차적으로 작성하는 데 도움이 되지만 복잡한 네트워크 애플리케이션에서는 차단으로 인해 동시성이 향상되지 않습니다.

서버 측에서 I/O는 매우 비싸고, 분산 I/O는 훨씬 더 비쌉니다. 백엔드가 리소스에 빠르게 응답할 수 있어야만 프런트엔드 경험이 더 좋아질 수 있습니다. Node.js는 비동기식을 주요 프로그래밍 방식과 설계 개념으로 사용하는 최초의 플랫폼입니다. 비동기식 I/O와 함께 Node.js의 톤을 형성하는 이벤트 중심 및 단일 스레드도 있습니다. 이 기사에서는 Node가 비동기 I/O를 구현하는 방법을 소개합니다.

1. 기본 개념

"비동기"와 "비차단"은 실제 효과 측면에서 둘 다 병렬성의 목적을 달성하는 것처럼 들립니다. 그러나 컴퓨터 커널 I/O의 관점에서 보면 차단과 비차단이라는 두 가지 방법만 있습니다. 따라서 비동기/동기 및 차단/비차단은 실제로 서로 다른 두 가지입니다.

1.1 차단 I/O 및 비차단 I/O

블로킹 I/O의 특징 중 하나는 호출 후 호출이 끝나기 전에 시스템 커널 수준에서 모든 작업이 완료될 때까지 기다려야 한다는 것입니다. 디스크의 파일 읽기를 예로 들면, 이 호출은 시스템 커널이 디스크 탐색을 완료하고, 데이터를 읽고, 데이터를 메모리에 복사한 후에 종료됩니다.

I/O를 차단하면 CPU가 I/O를 기다리게 되어 대기 시간이 낭비되고 CPU의 처리 능력을 최대한 활용하지 못하게 됩니다. Non-Blocking I/O의 특징은 호출 후 즉시 반환된다는 점이며, 반환 후 CPU 타임 슬라이스를 사용하여 다른 트랜잭션을 처리할 수 있습니다. 완전한 I/O가 완료되지 않았기 때문에 즉시 반환되는 것은 비즈니스 계층에서 기대하는 데이터가 아니라 현재 호출의 상태뿐입니다. 완전한 데이터를 얻으려면 애플리케이션은 완료 여부(즉, 폴링)를 확인하기 위해 I/O 작업을 반복적으로 호출해야 합니다. 폴링 기술에는 다음이 포함됩니다.

1.read: 반복 호출을 통해 I/O 상태를 확인하는 것은 가장 원시적이고 성능이 가장 낮은 방법입니다
2.select: 파일 설명자의 이벤트 상태로 판단하여 읽기 기능이 향상되었습니다. 단점은 최대 파일 설명자 수가 제한되어 있다는 것입니다
3.poll: 최대 수 제한을 피하기 위해 연결된 목록을 사용하여 선택 기능이 개선되었지만 설명자가 많은 경우 성능은 여전히 ​​매우 낮습니다
4.epoll: 폴링에 들어갈 때 I/O 이벤트가 감지되지 않으면 이벤트가 발생하여 깨울 때까지 절전 모드로 유지됩니다. 이는 현재 Linux에서 가장 효율적인 I/O 이벤트 알림 메커니즘입니다

폴링은 완전한 데이터 수집을 보장하기 위한 비차단 I/O의 요구 사항을 충족하지만 애플리케이션의 경우 여전히 I/O가 반환될 때까지 기다려야 하기 때문에 일종의 동기화로만 간주될 수 있습니다. 완전히. 대기 기간 동안 CPU는 파일 설명자의 상태를 탐색하거나 절전 모드로 전환하여 이벤트가 발생할 때까지 기다리는 데 사용됩니다.

1.2 이상과 현실의 비동기 I/O

완벽한 비동기 I/O는 애플리케이션이 비차단 호출을 시작하고 폴링 없이 다음 작업을 직접 처리할 수 있어야 하며 I/O가 완료된 후 신호나 콜백을 통해 애플리케이션에 데이터를 전달하기만 하면 됩니다. 완전한. .

실제로 비동기 I/O는 운영 체제에 따라 구현이 다릅니다. 예를 들어 *nix 플랫폼은 사용자 정의 스레드 풀을 사용하고 Windows 플랫폼은 IOCP 모델을 사용합니다. Node는 플랫폼 호환성 판단을 캡슐화하고 상위 노드와 하위 플랫폼의 비동기 I/O 구현이 독립적인지 확인하기 위해 추상 캡슐화 계층으로 libuv를 제공합니다. 또한 Node가 단일 스레드라는 점을 자주 언급한다는 점을 강조할 필요가 있습니다. 이는 Javascript가 단일 스레드에서 실행된다는 의미일 뿐입니다. Node 내부에는 실제로 I/O 작업을 완료하는 스레드 풀이 있습니다.

2. 노드의 비동기 I/O

2.1 이벤트 루프

노드의 실행 모델은 실제로 이벤트 루프입니다. 프로세스가 시작되면 Node는 무한 루프를 생성하고 루프 본문의 각 실행은 Tick이 됩니다. 각 Tick 프로세스는 처리 대기 중인 이벤트가 있는지 확인하고, 있으면 해당 이벤트 및 관련 콜백 함수를 검색하여 해당 콜백 함수를 실행한 후 다음 루프로 진입합니다. 더 이상 처리할 이벤트가 없으면 프로세스를 종료합니다.

2.2 관찰자

각 이벤트 루프에는 여러 명의 관찰자가 있으며, 이들 관찰자에게 물어보면 처리할 이벤트가 있는지 여부를 확인할 수 있습니다. 이벤트 루프는 일반적인 생산자/소비자 모델입니다. 노드에서 이벤트는 주로 네트워크 요청, 파일 I/O 등에서 발생합니다. 이러한 이벤트에는 해당 네트워크 I/O 관찰자, 파일 I/O 관찰자 등이 있습니다. 이벤트 루프는 관찰자로부터 이벤트를 가져와 처리합니다.

2.3 요청 객체

Javascript에서 I/O 작업을 완료하는 커널 호출을 시작하는 전환 과정에는 요청 개체라는 중간 제품이 있습니다. Windows에서 가장 간단한 fs.open() 메소드(지정된 경로 및 매개변수에 따라 파일을 열고 파일 설명자를 얻기 위해)를 예로 들면, libuv를 통해 JS에서 내장 모듈로 시스템을 호출하는 것은 실제로 uv_fs_open( ) 방법. 호출 프로세스 중에 JS 계층에서 전달된 매개변수와 메소드가 이 요청 객체에 캡슐화됩니다. 우리가 가장 관심을 갖는 콜백 함수는 이 객체의 oncompete_sym 속성에 설정됩니다. 개체가 래핑된 후 FSReqWrap 개체는 스레드 풀에 푸시되어 실행을 기다립니다.

이 시점에서 JS 호출은 즉시 반환되며 JS 스레드는 후속 작업을 계속 수행할 수 있습니다. 현재 I/O 작업은 비동기 호출의 첫 번째 단계를 완료하는 스레드 풀에서 실행되기를 기다리고 있습니다.

2.4 실행 콜백

콜백 알림은 비동기 I/O의 두 번째 단계입니다. 스레드 풀의 I/O 작업을 호출한 후 얻은 결과를 저장한 후 IOCP에 현재 개체 작업이 완료되었음을 알리고 스레드를 스레드 풀로 반환합니다. 각 Tick 실행 중에 이벤트 루프의 I/O 관찰자는 관련 메서드를 호출하여 스레드 풀에 완료된 요청이 있는지 확인합니다. 있는 경우 요청 개체가 I/O 대기열에 추가됩니다. 그런 다음 이벤트로 처리하십시오.

3. I/O가 아닌 비동기 API

작업을 즉시 비동기식으로 수행하는 타이머 setTimeout(), setInterval(), process.nextTick() 및 setImmdiate() 등 I/O와 관련이 없는 일부 비동기 API도 Node에 있습니다. 여기 간략한 소개입니다.

3.1 타이머 API

setTimeout() 및 setInterval()의 브라우저 측 API는 일관성이 있습니다. 구현 원칙은 비동기 I/O와 유사하지만 I/O 스레드 풀의 참여가 필요하지 않습니다. 타이머 API를 호출하여 생성된 타이머는 타이머 관찰자 내부의 빨간색-검정 트리에 삽입됩니다. 이벤트 루프의 각 Tick은 반복적으로 빨간색-검정 트리에서 타이머 개체를 제거하고 타이머가 초과되었는지 확인합니다. 이를 초과하면 이벤트가 발생하고 콜백 함수가 즉시 실행됩니다. 타이머의 주요 문제점은 타이밍이 특히 정확하지 않다는 것입니다(허용 범위 내에서 밀리초).

3.2 즉시 비동기 작업 실행 API

노드가 등장하기 전에는 많은 사람들이 작업을 즉시 비동기적으로 실행하기 위해 이것을 호출했을 것입니다.

코드 복사 코드는 다음과 같습니다.

setTimeout(함수() {
// TODO
}, 0);

이벤트 루프의 특성상 타이머의 정확도가 충분하지 않으며, 타이머를 사용하려면 레드-블랙 트리를 사용해야 하며, 다양한 연산의 시간 복잡도는 O(log(n))입니다. process.nextTick() 메서드는 콜백 함수만 대기열에 넣고 Tick의 다음 라운드에서 실행하기 위해 가져옵니다. 복잡성은 O(1)이며 이는 더 효율적입니다.

위 메소드와 비슷하게 콜백 함수의 실행을 지연시키는 setImmediate() 메소드도 있습니다. 그러나 이벤트 루프는 관찰자를 순서대로 확인하므로 전자가 후자보다 우선순위가 높습니다. 또한 전자의 콜백 함수는 배열에 저장되고 Tick의 각 라운드는 배열의 모든 콜백 함수를 실행합니다. 후자의 결과는 연결된 목록에 저장되며 각 라운드마다 하나의 콜백 함수만 실행됩니다. 틱 라운드.

4. 이벤트 중심의 고성능 서버

이전 예제에서는 Node가 비동기 I/O를 구현하는 방법을 설명하기 위해 fs.open()을 사용했습니다. 실제로 Node는 비동기 I/O를 사용하여 네트워크 소켓을 처리하는데, 이는 Node가 웹 서버를 구축하는 기초이기도 합니다. 클래식 서버 모델은 다음과 같습니다.

1. 동기식: 한 번에 하나의 요청만 처리할 수 있으며 나머지 요청은 대기 상태입니다
2. 프로세스별/요청별: 각 요청에 대해 프로세스를 시작하지만 시스템 리소스가 제한되어 확장 가능하지 않습니다.
3. 스레드별/요청별: 각 요청에 대해 스레드를 시작합니다. 스레드는 프로세스보다 가볍지만 각 스레드는 일정량의 메모리를 차지합니다. 대규모 동시 요청이 도착하면 메모리가 빨리 소모됩니다

유명한 Apache는 스레드별/요청별 형식을 사용하므로 높은 동시성에 대처하기 어렵습니다. 노드는 이벤트 중심 방식으로 요청을 처리하므로 스레드 생성 및 삭제에 따른 오버헤드를 줄일 수 있습니다. 동시에 운영 체제에서는 작업을 예약할 때 스레드 수가 적기 때문에 컨텍스트 전환 비용도 매우 낮습니다. 노드는 연결 수가 많아도 순서대로 요청을 처리합니다.

잘 알려진 서버 Nginx도 멀티스레딩 방식을 버리고 Node.js와 동일한 이벤트 중심 방식을 채택했습니다. 요즘 Nginx는 Apache를 대체할 수 있는 큰 잠재력을 가지고 있습니다. Nginx는 순수 C로 작성되어 성능이 뛰어나지만 웹 서버, 역방향 프록시 또는 로드 밸런싱 등에 대해서만 적합합니다. Node는 Nginx와 동일한 기능을 구축할 수 있고 다양한 특정 비즈니스도 처리할 수 있으며 자체 성능도 좋습니다. 실제 프로젝트에서는 각각의 장점을 결합하여 최고의 애플리케이션 성능을 달성할 수 있습니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.