이 글은 제가 최근 Node.js를 공부하면서 떠올린 아이디어로, 여러분과 함께 논의하고 싶습니다.
Node.js용 HTTP 서버
Node.js를 사용하여 http 서비스를 구현하는 것은 매우 쉽습니다. 가장 간단한 예는 공식 웹사이트에 나와 있습니다:
1. Node.js의 단일 스레드 특성으로 인해 견고성 보장은 개발자에게 상대적으로 높은 요구 사항을 갖습니다.
2. 서버에 포트 80을 사용하는 다른 http 서비스가 있을 수 있으며 포트 80 이외의 웹 서비스는 분명히 사용자 친화적이지 않습니다.
3.Node.js는 파일 IO 처리에 큰 이점이 없습니다. 예를 들어 일반 웹사이트처럼 이미지와 다른 파일 리소스에 동시에 응답해야 할 수도 있습니다.
4. 분산 로드 시나리오도 문제입니다.
따라서 Node.js를 웹 서비스로 사용하는 것은 게임 서버 인터페이스 및 기타 유사한 시나리오로 사용될 가능성이 높습니다. 대부분은 사용자가 직접 액세스할 필요가 없고 데이터 교환에만 사용되는 서비스를 다루고 있습니다. .
Nginx를 프론트엔드 머신으로 기반으로 한 Node.js 웹 서비스
위의 이유로 Node.js를 사용하여 구축된 웹사이트 형태의 제품이라면 기존의 사용법은 Nginx와 같은 Node.js 웹 서비스의 프런트 엔드에 또 다른 성숙한 http 서버를 배치하는 것입니다. 가장 일반적으로 사용됩니다.
그런 다음 Nginx를 역방향 프록시로 사용하여 Node.js 기반 웹 서비스에 액세스하세요. 예:
위치 / {
Proxy_pass http://127.0.0.1:1337;
}
위치 ~ .(gif|jpg|png|swf|ico|css|js)$ {
root /home/andy/wwwroot/yekai/static;
}
}
이렇게 하면 위에서 제기된 문제가 더 잘 해결될 것입니다.
FastCGI 프로토콜을 사용하여 통신
그러나 위에서 언급한 프록시 방식에도 몇 가지 단점이 있습니다.
가능한 시나리오 중 하나는 기본 Node.js 웹 서비스에 대한 직접 http 액세스를 제어해야 하는 것입니다. 그러나 문제를 해결하려면 자체 서비스를 사용하거나 방화벽 차단에 의존할 수도 있습니다.
또 다른 이유는 프록시 방식은 결국 네트워크 응용 계층에 대한 솔루션이고 연결 유지 처리 등 클라이언트 http와 상호 작용하는 데이터를 직접 가져와 처리하는 것이 그리 편리하지 않기 때문입니다. , 트렁크, 심지어 쿠키까지. 물론 이는 프록시 서버 자체의 성능 및 기능적 완성도와도 관련이 있습니다.
그래서 다른 처리 방식을 시도해볼까 생각하다가 가장 먼저 떠오른 것은 현재 PHP 웹 애플리케이션에서 흔히 사용하는 FastCGI 방식이었습니다.
FastCGI란 무엇인가
Fast Common Gateway Interface/FastCGI는 대화형 프로그램이 웹 서버와 통신할 수 있도록 하는 프로토콜입니다.
FastCGI의 배경은 cgi 웹 애플리케이션의 대안으로 사용된다는 것입니다. 가장 분명한 특징 중 하나는 FastCGI 서비스 프로세스를 사용하여 일련의 요청을 처리할 수 있다는 것입니다. FastCGI 프로세스는 Unix 도메인 소켓이나 TCP/IP 연결을 통해 웹 서버에 연결됩니다. 더 많은 배경 지식은 Wikipedia 항목을 참조하세요.
Node.js를 위한 FastCGI 구현
이론적으로 Node.js를 사용하여 FastCGI 프로세스를 생성한 다음 Nginx의 모니터링 요청이 이 프로세스로 전송되도록 지정하면 됩니다. Nginx와 Node.js는 모두 이벤트 중심 서비스 모델을 기반으로 하기 때문에 "이론적으로" 자연스러운 솔루션이어야 합니다. 아래에서 직접 구현해 보겠습니다.
Node.js의 net 모듈을 사용하여 소켓 서비스를 만들 수 있습니다. 편의상 Unix 소켓 방법을 선택합니다.
Nginx 측 구성을 약간 수정합니다.
var server = net.createServer();
server.listen('/tmp/node_fcgi.sock');
server.on('연결', function(sock){
console.log('connection');
sock.on('data', function(data){
console.log(data);
});
});
그런 다음 실행합니다(권한 이유로 인해 Nginx 및 노드 스크립트가 동일한 사용자 또는 상호 권한이 있는 계정에 의해 실행되는지 확인하십시오. 그렇지 않으면 양말 파일을 읽고 쓸 때 권한 문제가 발생합니다):
node node_fcgi.js
브라우저에서 액세스하면 노드 스크립트를 실행하는 터미널이 다음과 같이 데이터 콘텐츠를 정상적으로 수신하는 것을 볼 수 있습니다.
이는 우리의 이론적 기초가 첫 번째 단계를 달성했음을 증명합니다. 다음으로 이 버퍼의 내용을 구문 분석하는 방법만 알아내면 됩니다.
FastCGI 프로토콜 기본
FastCGI 레코드는 고정 길이 접두사, 그 뒤에 가변적인 콘텐츠 양 및 패딩 바이트로 구성됩니다. 레코드 구조는 다음과 같습니다.
version: FastCGI 프로토콜 버전, 이제 기본값은 1입니다. 다중화 및 동시성의 경우 여기서는 1을 사용하세요.
contentLength: 콘텐츠 길이, 여기서 최대 길이는 65535입니다.
paddingLength: 패딩 길이, 함수 긴 데이터를 8바이트의 정수 배수로 채우는 것입니다. 주로 성능 고려 사항으로 인해 더 효율적으로 정렬된 데이터를 처리하는 데 사용됩니다.
reserved: 후속 확장을 위해 예약된 바이트
contentData: 실제 콘텐츠 데이터입니다. 나중에 자세히 설명하겠습니다.
paddingData: 패딩 데이터, 어쨌든 모두 0이므로 무시하세요.
구체적인 구조와 설명은 공식 홈페이지 문서(http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3)를 참고하세요.
요청 섹션
매우 간단해 보입니다. 한 번만 구문 분석하고 데이터를 얻으면 됩니다. 그러나 여기에는 함정이 있는데, 여기서 정의하는 것은 전체 버퍼의 구조가 아니라 데이터 단위(레코드)의 구조라는 것이다. 프론트엔드 개발에 익숙한 우리들에게는 처음에는 이해하기 쉽지 않을 수도 있지만 이는 FastCGI 프로토콜을 이해하는 기초가 되며 나중에 더 많은 예제를 살펴보겠습니다.
var body = contentLength ? data.slice(end, contentLength) : null;
rcds.push([type, body, requestId]);
returnargs.callee();
}
}
//使用
sock.on('data', function(data){
getRcds(data, function(rcds ){
})();
}
注意这里只是简单处理,如果有上传文件等复杂情况这个函数不适应,为了最简演示就先简便处理了。同时,也忽略了requestId参数,如果是多路复用的情况下不能忽略,并且处理会需要复杂得多。
接下来就可以根据type来对不同的记录进行处理了。type 의 결정义如下 :
다음 유형을 사용하여 FCGI_PARAMS, FCGI_GET_VALUES, FCGI_GET_VALUES_RESULT 정보를 확인하세요.他们的解析방법은 式是一致的입니다. 자체가 다른 방식으로 설정되어 있지 않으며, 可以参考规范의 설정이 만족스럽지 않습니다.
FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_RESULTtour是“编码name-值”类型数据,标准格式为:以name字长島,后跟值的속도, 后跟이름, 后跟值 모양의 형태式传送, 其中127字节或更少的长道能는 一字节中编码, 而更长的长德总是에서 4字节中编码。 대단한 높이指示长道的编码方式. 高位为0의식 味着一个字节的编码方式,1意味着字节的编码方式。看个综例子,比如长name短值ative情况🎜>
JS 방식의 응용 프로그램:
if(body[j] >> 7 == 1){
valueLength = ((body[j ] & 0x7f) << 24) (body[j ] << 16) (body[j ] << 8) body[j ];
} else {
valueLength = body[j ];
}
var ret = body.asciiSlice(j, j nameLength valueLength);
name = ret.substring(0, nameLength);
value = ret.substring(nameLength);
params[name] = 값;
j = (nameLength valueLength);
}
return params;
}
这样就实现了一个简单可获取各种参数와环境变是数获这样就实现了一个简单可获取各种参数와环境变是数过樣想. >
이제 FastCGI 요청 부분의 기본 사항을 이해했습니다. 다음으로 응답 부분을 구현하고 마지막으로 간단한 에코 응답 서비스를 완성하겠습니다.
응답 섹션
응답 부분은 비교적 간단합니다. 가장 간단한 경우에는 FCGI_STDOUT과 FCGI_END_REQUEST라는 두 개의 레코드만 보내면 됩니다.
기록된 개체의 특정 내용에 대해 자세히 설명하지 않고 코드만 살펴보겠습니다.
function buffer0(len){
return new Buffer((new Array(len 1)).join('u0000'));
};
function writeStdout(data){
var rcdStdoutHd = new Buffer(8),
contendLength = data.length,
paddingLength = 8 - contendLength % 8;
rcdStdoutHd[0] = 1;
rcdStdoutHd[1] = TYPES.FCGI_STDOUT;
rcdStdoutHd[2] = 0;
rcdStdoutHd[3] = 1;
rcdStdoutHd[4] = contendLength >> 8;
rcdStdoutHd[5] = contendLength;
rcdStdoutHd[6] = paddingLength;
rcdStdoutHd[7] = 0;
return Buffer.concat([rcdStdoutHd, data, buffer0(paddingLength)]);
};
함수 writeHttpHead(){
return writeStdout(new Buffer("HTTP/1.1 200 OKrnContent-Type:text/html; charset=utf-8rnConnection: closenrn"));
}
function writeHttpBody(bodyStr){
var bodyBuffer = [],
body = new Buffer(bodyStr);
for(var i = 0, l = body.length; i < l; i = MaxLength 1){
함수 writeEnd(){
var rcdEndHd = new Buffer(8);
rcdEndHd[0] = 1;
rc dEndHd[2] = 0;
rcdEndHd[3] = 1;
rcdEndHd[4] = 0;
rcdEndHd[5] = 8;
rcdEndHd[6] = 0;
rcd EndHd[7 ] = 0;
return Buffer.concat([rcdEndHd, buffer0(8)]);
}
return function(data){
return Buffer.concat([writeHttpHead(), writeHttpBody(data), writeEnd()]);
};
가장 간단한 경우에는 완전한 응답을 보낼 수 있습니다. 최종 코드 수정:
코드 복사
비교 테스트
마지막으로 우리가 고려해야 할 질문은 이 솔루션이 실현 가능한지 여부입니다. 일부 학생들은 문제를 발견했을 수도 있으므로 간단한 스트레스 테스트 결과를 먼저 게시하겠습니다.
500개의 클라이언트, 20초 실행
속도=22131페이지/분, 63359바이트/초.
요청: 6523 성공, 854 실패
//프록시 모드:
500개 클라이언트, 10초 실행
속도=28752페이지/분, 73191바이트/초.
요청: 3724 지속, 1068 실패
500개의 클라이언트, 20초 실행
속도=26508페이지/분, 66267바이트/초.
요청: 6716 성공, 2120 실패
//Node.js 서비스 방법에 직접 액세스:
500개 클라이언트, 10초 실행
Speed=101154페이지/분, 264247바이트/초.
요청: 15729 성공, 1130 실패.
500개의 클라이언트, 20초 실행.
속도=43791페이지/분, 115962바이트/초.
요청: 13898 성공, 699 실패.
후기
계속 플레이하고 싶다면 이 글에서 구현한 예제의 소스코드를 확인해보세요. 지난 이틀 동안 프로토콜 사양을 공부했는데 어렵지 않습니다.
동시에 다시 uWSGI를 플레이할 계획인데 v8이 이미 직접 지원할 준비를 하고 있다고 관계자가 밝혔습니다.
게임은 매우 간단합니다. 틀린 부분이 있으면 바로잡아주세요.