Node.js 비동기 이상 처리 및 domain 모듈 분석

비동기 이상 처리
비동기 이상 특징
node 의 반전 비동기 특성 으로 인해 try catch 를 통 해 모든 이상 을 포착 할 수 없습니다:

try {
 process.nextTick(function () {
  foo.bar();
 });
} catch (err) {
 //can not catch it
}
웹 서비스 에 있어 서 사실은 이런 것 을 매우 바 랍 니 다.

//express     
app.get('/index', function (req, res) {
 try {
  //    
 } catch (err) {
  logger.error(err);
  res.statusCode = 500;
  return res.json({success: false, message: '     '});
 }
});
try catch 가 모든 이상 을 포착 할 수 있다 면 코드 에 예상 치 못 한 오류 가 발생 했 을 때 오 류 를 기록 하 는 동시에 호출 자 에 게 500 오 류 를 친절하게 되 돌려 줄 수 있 습 니 다.안 타 깝 게 도 try catch 는 비동기 의 이상 을 포착 할 수 없습니다.그래서 우리 가 할 수 있 는 일 은:

app.get('/index', function (req, res) {
 //      
});

process.on('uncaughtException', function (err) {
 logger.error(err);
});
이 때,비록 우 리 는 이 잘못된 로 그 를 기록 할 수 있 고,프로 세 스 도 이상 하 게 종료 되 지 않 지만,우 리 는 잘못된 요청 을 발견 한 것 에 대해 우호 적 으로 돌아 갈 수 없고,시간 을 초과 해서 돌아 올 수 있 을 뿐이다.
domain
node v 0.8+버 전에 서 모듈 domain 을 발 표 했 습 니 다.이 모듈 은 try catch 가 할 수 없 는 것 입 니 다.비동기 반전 에 나타 난 이상 을 포착 합 니 다.
그래서 우리 위의 어 쩔 수 없 는 예 는 해결 방안 이 있 는 것 같다.

var domain = require('domain');

//    domain    ,               domain 
//domain     
app.use(function (req,res, next) {
 var d = domain.create();
 //  domain     
 d.on('error', function (err) {
  logger.error(err);
  res.statusCode = 500;
  res.json({sucess:false, messag: '     '});
  d.dispose();
 });
 
 d.add(req);
 d.add(res);
 d.run(next);
});

app.get('/index', function (req, res) {
 //    
});

우 리 는 중간 부품 의 형식 을 통 해 domain 을 도입 하여 비동기 중의 이상 을 처리 합 니 다.물론 domain 은 이상 을 포 착 했 지만 이상 으로 인 한 스 택 손실 로 인해 메모리 가 누 출 될 수 있 기 때문에 이런 상황 이 발생 했 을 때 이 프로 세 스 를 다시 시작 해 야 합 니 다.관심 있 는 학생 들 은 domain-middleware 라 는 domain 미들웨어 를 보 러 갈 수 있 습 니 다.
기괴 한 실효
우리 의 테스트 는 모든 것 이 정상 입 니 다.정식으로 생산 환경 에서 사용 할 때 domain 이 갑자기 효력 을 잃 었 습 니 다!비동기 의 이상 을 포착 하지 못 해 프로 세 스 가 이상 하 게 종료 되 었 습 니 다.한 차례 의 조 사 를 통 해,마지막 으로 redis 를 도입 하여 session 을 저장 하기 때문에 발생 한 것 을 발견 하 였 다.

var http = require('http');
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var domainMiddleware = require('domain-middleware');

var server = http.createServer();
var app = connect();
app.use(connect.session({
 key: 'key',
 secret: 'secret',
 store: new RedisStore(6379, 'localhost')
}));
//domainMiddleware           
app.use(domainMiddleware({
 server: server,
 killTimeout: 30000
}));

이때,우리 의 업무 논리 코드 에 이상 이 생 겼 을 때,뜻밖에도 domain 에 의 해 포착 되 지 않 았 음 을 발견 하 였 습 니 다!한 번 의 시 도 를 거 쳐 마침내 문 제 를 찾 았 다.

var domain = require('domain');
var redis = require('redis');
var cache = redis.createClient(6379, 'localhost');

function error() {
 cache.get('a', function () {
  throw new Error('something wrong');
 });
}

function ok () {
 setTimeout(function () {
  throw new Error('something wrong');
 }, 100);
}
var d = domain.create();
d.on('error', function (err) {
 console.log(err);
});

d.run(ok);  //domain     
d.run(error); //     
이상 하 다!모두 비동기 호출 인 데 왜 전 자 는 잡 히 고 후 자 는 잡 을 수 없 습 니까?
도 메 인 분석
돌 이 켜 보면 domain 이 무엇 을 했 는 지 알 아 보 겠 습 니 다.
node 이벤트 순환 메커니즘
Domain 의 원 리 를 보기 전에 nextTick 과 에 대해 알 아 보 겠 습 니 다.tick Callback 의두 가지 방법.

function laterCall() {
 console.log('print me later');
}

process.nextTick(laterCallback);
console.log('print me first');
위의 코드 에 node 라 고 쓰 여 있 는 사람들 은 모두 잘 알 고 있 습 니 다.nextTick 의 역할 은 later Callback 을 다음 이벤트 순환 에 두 고 실행 하 는 것 입 니 다.그리고tickcallback 방법 은 비공개 적 인 방법 입 니 다.이 방법 은 현재 시간 순환 이 끝 난 후에 다음 이벤트 순환 을 계속 하 는 입구 함수 로 호출 됩 니 다.
다시 말 하면 node 는 이벤트 순환 을 위해 하나의 대열 을 유 지 했 습 니 다.nextTick 입 대 는tick Callback 이 나 옵 니 다.
domain 의 실현
node 의 이벤트 순환 체 제 를 알 게 된 후에 domain 이 무엇 을 했 는 지 다시 봅 시다.
domain 자 체 는 이벤트 Emitter 대상 으로 이벤트 방식 으로 캡 처 된 오 류 를 전달 합 니 다.이렇게 해서 우 리 는 그것 을 연구 할 때 두 가지 점 으로 간소화 한다.
domain 의 error 이 벤트 를 언제 실행 합 니까?
프로 세 스 가 이상 을 던 졌 습 니 다.try catch 에 잡 히 지 않 았 습 니 다.이 때 전체 프로 세 스 의 processFatal 을 촉발 합 니 다.이 때 domain 패키지 에 있 으 면 domain 에서 error 사건 을 촉발 합 니 다.반대로 process 에서 uncaughtException 사건 을 촉발 합 니 다.
domain 은 여러 이벤트 순환 에서 어떻게 전달 합 니까?
  • domain 이 실례 화 된 후에 저 희 는 보통 run 방법(예 를 들 어 이전에 웹 서비스 에서 사용 한 것)을 호출 하여 특정한 함 수 를 이 domain 예제 의 패키지 에서 실행 합 니 다.소 포 된 함수 가 실 행 될 때 process.domain 이라는 전역 변 수 는 이 domain 인 스 턴 스 를 가리 킬 것 입 니 다.이 이벤트 가 순환 중 프로 세 스 Fatal 을 이상 호출 할 때 프로 세 스 domain 이 존재 하 는 것 을 발견 하면 domain 에서 error 이벤트 가 발생 합 니 다.
  • require 에서 domain 모듈 을 도입 한 후 전역 의 nextTick 과 를 다시 씁 니 다.tickCallback,domain 관련 코드 주입:
  • 
    //    domain      
    function nextDomainTick(callback) {
     nextTickQueue.push({callback: callback, domain: process.domain});
    }
    
    function _tickDomainCallback() {
     var tock = nextTickQueue.pop();
     //  process.domain = tock.domain
     tock.domain && tock.domain.enter();
     callback();
     //  process.domain
     tock.domain && tock.domain.exit();    
     }
    };
    
    이것 은 여러 이벤트 순환 에서 domain 을 전달 하 는 관건 입 니 다.nextTick 이 입 대 했 을 때 현재 domain 을 기록 합 니 다.이 가입 대기 열 에 있 는 이벤트 순환 은tickCallback 이 실 행 될 때 새로운 이벤트 순환 의 process.domain 을 기 록 된 domain 으로 설정 합 니 다.이렇게 하면 domain 에 소 포 된 코드 에서 process.nextTick 을 어떻게 호출 하 든 domain 은 계속 전 달 될 것 입 니 다.
    물론 node 의 비동기 에는 두 가지 상황 이 있 는데 하 나 는 이벤트 형식 이다.따라서 EventEmitter구조 함수에는 다음 과 같은 코드 가 있 습 니 다.
    
     if (exports.usingDomains) {
      // if there is an active domain, then attach to it.
      domain = domain || require('domain');
      if (domain.active && !(this instanceof domain.Domain)) {
       this.domain = domain.active;
      }
     }
    EventEmitter 를 예화 할 때 이 대상 을 현재 domain 과 연결 합 니 다.emit 를 통 해 이 대상 의 사건 을 촉발 할 때 와 같 습 니 다.tickCallback 이 실 행 될 때 와 마찬가지 로 리 셋 함 수 는 현재 domain 에 다시 감 싸 집 니 다.
    또 다른 상황 은 setTimeout 과 setInterval 입 니 다.마찬가지 로 timer 의 소스 코드 에서 도 이러한 코드 를 발견 할 수 있 습 니 다.
    
     if (process.domain) timer.domain = process.domain;
    EventEmmiter 와 마찬가지 로 이 timer 의 리 셋 함수 도 현재 domain 에 감 싸 집 니 다.
    node 는 nextTick,timer,event 세 가지 관건 적 인 곳 에 domain 코드 를 삽입 하여 서로 다른 이벤트 순환 에서 전달 할 수 있 도록 합 니 다.
    더 복잡 한 domain
    어떤 경우 에는 더 복잡 한 domain 사용 이 필요 할 수도 있 습 니 다.
    domain 내장:우 리 는 외부 에 domain 이 있 을 수 있 습 니 다.내부 에 다른 domain 이 있 습 니 다.사용 상황 은 문서 에서찾아내다
    
    // create a top-level domain for the server
    var serverDomain = domain.create();
    
    serverDomain.run(function() {
     // server is created in the scope of serverDomain
     http.createServer(function(req, res) {
      // req and res are also created in the scope of serverDomain
      // however, we'd prefer to have a separate domain for each request.
      // create it first thing, and add req and res to it.
      var reqd = domain.create();
      reqd.add(req);
      reqd.add(res);
      reqd.on('error', function(er) {
       console.error('Error', er, req.url);
       try {
        res.writeHead(500);
        res.end('Error occurred, sorry.');
       } catch (er) {
        console.error('Error sending 500', er, req.url);
       }
      });
     }).listen(1337);
    });
    
    이 기능 을 실현 하기 위해 사실 domain 은 몰래 domain 의 stack 을 유지 하기 도 한다.관심 있 는 동 화 는 여기 서 볼 수 있다.
    뒤 돌아 서서 의혹 을 해결 하 다
    돌 이 켜 보면 우 리 는 방금 만난 문 제 를 다시 살 펴 보 자.왜 두 사람 은 모두 같은 비동기 호출 으로 보이 지만 하나의 domain 은 이상 을 포착 하지 못 합 니까?원 리 를 이해 한 후에 생각 하기 어렵 지 않 습 니 다.redis 를 호출 한 그 비동기 호출 은 잘못된 이 사건 순환 에서 domain 의 범위 안에 있 지 않 습 니 다.우 리 는 도대체 어디에서 문제 가 발생 했 는 지 더 짧 은 코드 를 통 해 보 았 다.
    
    var domain = require('domain');
    var EventEmitter = require('events').EventEmitter;
    
    var e = new EventEmitter();
    
    var timer = setTimeout(function () {
     e.emit('data'); 
    }, 10);
    
    function next() {
     e.once('data', function () {
      throw new Error('something wrong here');
     });
    }
    
    var d = domain.create();
    d.on('error', function () {
     console.log('cache by domain');
    });
    
    d.run(next);
    
    이때 우 리 는 오류 가 domain 에 포착 되 지 않 는 다 는 것 을 알 게 되 었 습 니 다.그 이 유 는 분명 합 니 다.timer 와 e 두 관건 적 인 대상 이 초기 화 할 때 domain 의 범위 안에 있 지 않 기 때문에 next 함수 에서 감청 한 사건 이 촉발 되 고 이상 한 반전 함 수 를 실행 할 때 사실은 domain 의 소포 에 있 지 않 습 니 다.물론 domain 에 이상 이 잡 히 지 는 않 을 겁 니 다!
    사실 node 는 이런 상황 을 겨냥 하여 전문 적 으로 하나API:domain.add를 설계 했다.domain 이외 의 timer 와 이벤트 대상 을 현재 domain 에 추가 할 수 있 습 니 다.위의 그 예 에 대해:
    
    d.add(timer);
    //or
    d.add(e);
    timer 나 e 의 임의의 대상 을 domain 에 추가 하면 오 류 를 domain 에 캡 처 할 수 있 습 니 다.
    다시 보면 처음에 redis 로 인해 domain 에서 이상 한 문 제 를 포착 하지 못 했 습 니 다.우리 도 해결 할 방법 이 있 지 않 을까요?
    사실 이런 상황 에 대해 서 는 최선 의 해결 방안 을 실현 할 방법 이 없다.현재 예상 치 못 한 이상 이 발생 했 을 때,우 리 는 현재 요청 이 시간 을 초과 한 후에 이 프로 세 스 가 서 비 스 를 중단 한 후에 다시 시작 할 수 있 습 니 다.graceful 모듈 은 cluster 와 결합 하면 이 해결 방안 을 실현 할 수 있 습 니 다.
    __domain 은 매우 강하 지만 만능 은 아 닙 니 다.이 글 을 본 후에 도 메 인 을 정확하게 사용 하여 구 덩이 를 밟 지 않 기 를 바 랍 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기