Nginx 시간 초과 이벤트 처리 메커니즘

13309 단어
본 고 는 Nginx 0.8.55 소스 코드 를 바탕 으로 epoll 메커니즘 분석 을 바탕 으로 한다.
nginx 에 대해 말하자면 이벤트 메커니즘 의 처 리 는 단지 몇 가지 부분 일 뿐이다.
  • 네트워크 IO 사건 의 처리
  • 파일 IO 이벤트 처리
  • 타이머 이벤트 처리
  • (물론 다른 것 도 많 지만 저 는 지금 관심 이 많 지 않 습 니 다.) 저 는 Nginx 타이머 사건 과 관련 된 코드 를 읽 을 때 재 미 있 는 디자인 과 고려 를 많이 봤 습 니 다. 쓸 만 한 것 같 습 니 다. 물론 사내 들 은 흔히 볼 수 있 을 것 입 니 다.
    1. nginx 의 시간 캐 시
    우선, 초기 리 눅 스에 서 gettimeofday () 자 체 는 시스템 호출 이 었 기 때문에 잦 은 호출 에 비용 이 많이 들 었 기 때문에 Nginx 는 로 컬 캐 시 시간 을 사용 했다.
    Nginx 는 몇 개의 전역 변수 로 시간 을 캐 시 합 니 다.
    volatile ngx_msec_t      ngx_current_msec;                                        
    volatile ngx_time_t     *ngx_cached_time;                                         
    volatile ngx_str_t       ngx_cached_err_log_time;                                 
    volatile ngx_str_t       ngx_cached_http_time;                                    
    volatile ngx_str_t       ngx_cached_http_log_time;                                
    

    이름과 유형 에서 도 알 수 있 듯 이 Nginx 는 각 모듈 에 다양한 유형의 캐 시 변 수 를 제공 하여 호출 ngx_time()ngx_timeofday() 모듈 에 현재 시간 을 제공 하여 gettimeofday() 등 시스템 호출 비용 (물론 새로운 버 전의 Linux gettimeofday() 을 피 할 수 있 습 니 다.이미 전통 적 인 의미 의 시스템 호출 은 아니 지만, 비교적 새로운 Nginx 소스 코드 는 나 도 보지 않 았 다.
    Nginx 는 시간 을 캐 시 할 수 있 는 몇 개의 대기 열 을 제공 합 니 다.
    static ngx_time_t        cached_time[NGX_TIME_SLOTS];                             
    static u_char            cached_err_log_time[NGX_TIME_SLOTS]                      
                                        [sizeof("1970/09/28 12:00:00")];              
    static u_char            cached_http_time[NGX_TIME_SLOTS]                         
                                        [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];    
    static u_char            cached_http_log_time[NGX_TIME_SLOTS]                     
                                        [sizeof("28/Sep/1970:12:00:00 +0600")];       
    

    사실 위의 캐 시 변수의 실제 값 은 최종 적 으로 이 대기 열 에 있 는 값 을 가리 키 고 있 습 니 다. 그러나 저 는 grep 를 했 습 니 다. 이 대기 열 들 은 다른 곳 에서 사용 되 지 않 은 것 같 습 니 다. 그래서 그들 에 게 도 상세 하 게 소개 하지 않 았 습 니 다. 쉽게 말 하면 Nginx 는 하나의 slot 전역 변 수 를 유지 하고 매번 ngx_time_update 함수 에서 호출 gettimeofday() 하여 현재 시간 을 가 져 왔 습 니 다.대기 열 뒤에 새 시간 을 삽입 하고 이동 slot 해서 현재 캐 시 값 을 표시 하고 ngx_cache_time 등 바늘 을 최신 cache_time[slot] 으로 가리 키 면 됩 니 다.
    구체 적 으로 ngx_time_update() 의 실현 을 보면 된다.
    2. Nginx 는 캐 시 를 언제 업데이트 합 니까?
    여기 서 우리 가 주목 하 는 문 제 는 Nginx 가 시간 캐 시 를 업데이트 하 는 시기 가 언제 입 니까?
    물론 초기 시작 과 cycle 의 초기 화 는 몇 번 의 업데이트 시기 가 있 습 니 다. 여기 서 저 희 는 주로 사건 처리 과정 에서 시간 이 업데이트 되 는 시 기 를 고려 합 니 다.
    여기 서 Nginx 는 두 가지 서로 다른 해결 방안 을 제 시 했 고 ngx_time_resolution 변수 에 의 해 결정 된다.
  • ngx_timer_resolution 이 0 일 때 Nginx 는 호출 epoll_wait 할 때마다 시간 캐 시 업데이트
  • 를 한다.
  • ngx_timer_resolution 이 0 이 아 닐 때 이 값 은 시간 정밀도, 즉 '캐 시 를 얼마나 자주 업데이트 합 니까?' 를 나타 낸다. 이때 Nginx 는 시간 모듈 이 초기 화 될 때 타 이 머 를 설정 하여 타이머 의 중단 시간 을 ngx_timer_resolution 에 규정된 밀리초 로 한 번 SIGALRM 신 호 를 터치 할 때마다 한 번 ngx_time_update() 호출 한다.

  • 물론, 우 리 는 신호 처리 함수 자체 가 CPU 시간 을 너무 많이 차지 하 는 것 을 절대로 허용 하지 않 기 때문에, 그 신호 처리 함수 의 실현 은 매우 간단 하 다.
    void                                                                               
    ngx_timer_signal_handler(int signo)                                                
    {                                                                                  
        ngx_event_timer_alarm = 1;                                                     
                                                                                       
    #if 1                                                                              
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");    
    #endif                                                                             
    }                                                                                  
    

    실제 호출 ngx_time_update()ngx_process_events() 에서:
    static ngx_int_t                                                                  
    ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
    {                                                                                 
        int                events;                                                    
        uint32_t           revents;                                                   
        ngx_int_t          instance, i;                                               
        ngx_uint_t         level;                                                     
        ngx_err_t          err;                                                       
        ngx_log_t         *log;                                                       
        ngx_event_t       *rev, *wev, **queue;                                        
        ngx_connection_t  *c;                                                         
                                                                                      
        /* NGX_TIMER_INFINITE == INFTIM */                                            
                                                                                      
        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,                            
                       "epoll timer: %M", timer);                                     
                                                                                      
        //   epoll_wait                                                             
        events = epoll_wait(ep, event_list, (int) nevents, timer);                    
                                                                                      
        err = (events == -1) ? ngx_errno : 0;                                         
                                                                                      
        //                                                                
        //   SIGALRM        ,  ngx_event_timer_alarm  1,      
        if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {                       
            ngx_time_update();                                                        
        }                                                                             
                                                                                      
        if (err) {                                                                    
            if (err == NGX_EINTR) {                                                   
                                                                                      
                //         ,    NGX_OK                                   
                if (ngx_event_timer_alarm) {                                          
                    ngx_event_timer_alarm = 0;                                        
                    return NGX_OK;                                                    
                }                                                                     
                                                                                      
                level = NGX_LOG_INFO;                                                 
                                                                                      
            } else {                                                                  
                level = NGX_LOG_ALERT;                                                
            }                                                                         
                                                                                      
            ngx_log_error(level, cycle->log, err, "epoll_wait() failed");             
            return NGX_ERROR;                                                         
        }
        /*           */
    }
    
    

    함수 에서 먼저 flag 인자 의 NGX_UPDATE_TIME 표지 위 치 를 검사 하 는 것 을 볼 수 있 습 니 다. 이것 은 ngx_timer_resolution 이 0 일 때 만 설정 하 는 위치 로 이벤트 처리 때마다 시간 캐 시 를 업데이트 하 는 것 을 의미 합 니 다.그 밖 에 ngx_timer_resolution 이 0 이 아니 라 소프트 인 터 럽 트 에서 플래그 위 치 를 설정 해 야 시간 캐 시 업 데 이 트 를 촉발 할 수 있 습 니 다.
    여기 서도 신호 가 중 단 된 경우 epoll_wait 가 마침 EINTR 이 고 ngx_event_timer_alarm 가 1 이 라면 이 신호 가 중 단 된 것 으로 보고 epoll_wait 을 0 으로 정리 했다.
    3. 정시 이벤트 의 조직 방식
    정시 사건 이나 시간 초과 사건 의 처리 에 대해 우 리 는 매우 간단 한 직관 을 가지 고 있다. 모든 정시 사건 에 타 이 머 를 등록 하고 타이머 리 셋 에서 만 료 사건 을 처리 하면 되 지 않 겠 는가?코드 를 쓰 는 것 이 얼마나 간단 합 니까? lambda 표현 식 이 하나 더 있 으 면...
    안 타 깝 게 도 시스템 밑바닥 에서 제공 하 는 타이머 의 수량 은 곱 창 에 한계 가 있 지만, 우 리 는 오히려 API 를 이렇게 설계 할 수 있다.
    Nginx 는 간단 하고 효과 적 인 전략 을 사용 합 니 다. 적당 한 시 기 를 선택 하고 최근 에 만 료 될 사건 이 만 료 되 었 는 지 가능 한 한 검사 합 니 다. 있 으 면 처리 하고 없 으 면 건 너 뜁 니 다.
    그러면 '최근 에 만 료 될 구조' 를 빨리 얻 는 것 이 중요 하 다. 또한 복잡 한 사건 처리 과정 에서 정시 사건 이 무 작위 로 삽입 되 기 때문에 간단 한 대기 열 도 감당 할 수 없다. 이때 데이터 구 조 를 잘 아 는 학생 들 은 빨 간 검 은 나 무 를 곧 생각 할 것 이다.
    맞 아, Nginx 는 빨 간 검 은 나무 로 정시 사건 을 관리 하 는 거 야.만 료 시간 을 키 로 하여 이 붉 은 검 은 나 무 를 관리 합 니 다. 그러면 붉 은 검 은 나무 중 가장 왼쪽 에 있 는 사건 은 기한 을 넘 기 는 사건 입 니 다.한편, 사건 의 소비자 가 사건 을 삽입 하고 소비 가 끝 난 후에 사건 을 삭제 해 야 한다. 이 는 기한 이 지난 사건 을 찾 는 것 을 포함한다. 이런 작업 의 시간 복잡 도 는 모두 O (logN) 내 에서 통제 되 고 효율 이 상당히 높다.(붉 은 검 은 나무 와 같은 데이터 구조 에 대한 세부 사항 은 데이터 구조의 전문 저 서 를 참고 하 시기 바 랍 니 다)
    Nginx 가 시간 초과 이 벤트 를 관리 하 는 기조 다.구체 적 인 코드 실현 은 ngx_event_timer_alarm 의 세 가지 함 수 를 참조 할 수 있 는데 기본적으로 일부 붉 은 검 은 나무의 첨삭 과 수정 을 한 다음 에 조정 하 는 것 이다.
    4. 정시 사건 을 처리 할 시기
    시간 초과 사건 의 처리 시 기 는 물론 큰 사건 순환 에 두 어야 합 니 다. accept 자물쇠 의 점용 시간 을 줄 이기 위해 시간 초과 사건 의 처 리 는 당연히 accept 자 물 쇠 를 풀 때 이후 에 두 어야 합 니 다.
    오픈 하지 않 는 경우 src/event/ngx_event_timer.c 를 방지 하기 위해 timer_resolution 너무 많은 시간 을 차지 합 니 다. 호출 epoll_wait() 하기 전에 Nginx 는 가장 가 까 운 시간 초과 사건 의 시간 을 계산 한 다음 에 이 시간 을 timer 변수 에 기록 하여 ngx_process_events(cycle, timer, flags) 의 시간 초과 매개 변 수 를 제어 합 니 다 epoll_wait 의 점용 시간 을 제어 합 니 다.
    또한 Nginx 는 epoll_wait 호출 이 차지 하 는 시간 ngx_process_events 변 수 를 계산 delta 하고 delta > 0 의 경우 에 만 호출 ngx_event_expire_timers() 을 사용 하여 시간 초과 사건 을 처리 함으로써 무의미 한 검색 을 피 할 수 있 습 니 다 (정말 고심 하 셨 습 니 다).
    다음은 이벤트 순환 에서 시간 초과 이벤트 처리 가 차지 하 는 위 치 를 전체적으로 볼 수 있 습 니 다.
    void
    ngx_process_events_and_timers(ngx_cycle_t *cycle)                              
    {                                                                              
        ngx_uint_t  flags;                                                         
        ngx_msec_t  timer, delta;                                                  
                                                                                   
        if (ngx_timer_resolution) {                                                
            timer = NGX_TIMER_INFINITE;                                            
            flags = 0;                                                             
                                                                                   
        } else {                                                                   
            //                 timer                              
            timer = ngx_event_find_timer();                                        
            flags = NGX_UPDATE_TIME;                                               
        }                                                                          
                                                                                   
        //            accept                                        
        /*    accept     */                                                                               
        delta = ngx_current_msec;                                                  
                                                                                   
        //          process_events,    epoll_wait                 
        (void) ngx_process_events(cycle, timer, flags);                            
    delta = ngx_current_msec - delta; //    events                 
                                                                                   
        //         accept  ,                                  
        if (ngx_posted_accept_events) {                                            
            ngx_event_process_posted(cycle, &ngx_posted_accept_events);            
        }                                                                          
                                                                                   
        //   accept                                                             
        if (ngx_accept_mutex_held) {                                               
            ngx_shmtx_unlock(&ngx_accept_mutex);                                   
        }                                                                          
                                                                                   
        //                                                                
        if (delta) {                                                               
            ngx_event_expire_timers();                                             
        }
        /*            */
    }                                                                              
    

    좋은 웹페이지 즐겨찾기