>  기사  >  운영 및 유지보수  >  nginx 패닉 문제를 해결하는 방법

nginx 패닉 문제를 해결하는 방법

WBOY
WBOY앞으로
2023-05-17 20:49:141587검색

1. 솔루션

각 작업자 프로세스가 생성되면 ngx_worker_process_init() 메서드가 호출되어 현재 작업자 프로세스를 초기화합니다. 이 프로세스에는 매우 중요한 단계가 있습니다. 즉, 각 작업자 프로세스가 epoll_create를 호출합니다. () 메소드는 자체적으로 고유한 epoll 핸들을 생성합니다. 모니터링해야 하는 각 포트에는 이에 해당하는 파일 설명자가 있으며 작업자 프로세스는 epoll_ctl() 메서드를 통해 현재 프로세스의 epoll 핸들에 파일 설명자를 추가하고 accept 이벤트를 수신하기만 합니다. 이벤트를 처리하기 위한 클라이언트의 연결 설정 이벤트입니다. 또한 작업자 프로세스가 모니터링해야 하는 포트에 해당하는 파일 설명자를 프로세스의 epoll 핸들에 추가하지 않으면 해당 이벤트가 트리거될 수 없음을 알 수 있습니다. 이 원칙에 따라 nginx는 공유 잠금을 사용하여 현재 프로세스의 epoll 핸들에 모니터링해야 하는 포트를 추가할 수 있는 권한이 있는지 여부를 제어합니다. 즉, 잠금을 획득한 프로세스만 수신 대기합니다. 대상 포트로. 이러한 방식으로 이벤트가 발생할 때마다 하나의 작업자 프로세스만 트리거되는 것이 보장됩니다. 다음 그림은 작업자 프로세스의 작업 주기에 대한 개략도입니다.

nginx 패닉 문제를 해결하는 방법

그림의 프로세스와 관련하여 설명해야 할 한 가지는 각 작업자 프로세스가 해당 프로세스에 진입한 후 공유 잠금을 획득하려고 시도한다는 것입니다. 루프를 얻지 못하면 모니터링되는 포트의 파일 설명자가 현재 프로세스의 epoll 핸들에서 제거됩니다(존재하지 않더라도 제거됩니다). 클라이언트 연결 이벤트의 손실로 인해 약간의 Thundering 무리 문제가 발생할 수 있지만 심각하지는 않습니다. 이론에 따르면 현재 프로세스가 잠금을 해제할 때 수신 포트의 파일 설명자가 epoll 핸들에서 제거되고 다음 작업자 프로세스가 잠금을 획득하기 전에 이 기간 동안 각 포트에 해당하는 파일 설명자는 다음과 같습니다. 수신할 epoll 핸들이 없으면 이벤트가 손실됩니다. 반면, 그림과 같이 잠금 획득에 실패한 경우에만 모니터링되는 파일 설명자를 제거하는 경우 잠금 획득에 실패하므로 현재 이러한 파일 설명자를 모니터링하는 프로세스가 있어야 하므로 안전합니다. 지금은 제거하세요. 그러나 이로 인해 발생할 수 있는 한 가지 문제는 위 그림에 따르면 현재 프로세스가 루프 실행을 완료할 때 잠금을 해제한 다음 다른 이벤트를 처리한다는 것입니다. 이 프로세스 중에는 모니터링되는 파일 설명자를 해제하지 않는다는 점에 유의하세요. . 이때 다른 프로세스가 잠금을 획득하고 파일 디스크립터를 모니터링하면 이때 파일 디스크립터를 모니터링하는 프로세스가 두 개이므로 클라이언트에서 연결 설정 이벤트가 발생하면 두 개의 워커 프로세스가 트리거됩니다. 이 문제는 두 가지 주요 이유 때문에 허용됩니다.

  1. 이 엄청난 무리 현상은 더 적은 수의 작업자 프로세스만 트리거하며, 이는 매번 모든 작업자 프로세스를 깨우는 것보다 훨씬 낫습니다.

  2. 이 문제의 주된 이유 패닉 문제는 현재 프로세스가 잠금을 해제하지만 모니터링되는 파일 설명자를 해제하지 않는다는 것입니다. 그러나 잠금을 해제한 후 작업자 프로세스는 주로 클라이언트 연결의 읽기 및 쓰기 이벤트를 처리하고 플래그 비트를 확인합니다. 매우 짧습니다. 처리 후 잠금을 획득하려고 시도하지만 이때 모니터링되는 파일 설명자는 잠금을 획득한 작업자 프로세스가 클라이언트의 연결 설정 이벤트를 처리하기 위해 대기합니다. , 따라서 천둥소리가 나는 무리 문제가 발생할 확률은 여전히 ​​상대적으로 작습니다.

2. 소스코드 설명

작업자 프로세스의 초기 이벤트 메소드는 주로 ngx_process_events_and_timers() 메소드에서 전체 프로세스를 처리하는 방법을 살펴보겠습니다. method 소스 코드:

void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
 ngx_uint_t flags;
 ngx_msec_t timer, delta;

 if (ngx_trylock_accept_mutex(cycle) == ngx_error) {
  return;
 }

 // 这里开始处理事件,对于kqueue模型,其指向的是ngx_kqueue_process_events()方法,
 // 而对于epoll模型,其指向的是ngx_epoll_process_events()方法
 // 这个方法的主要作用是,在对应的事件模型中获取事件列表,然后将事件添加到ngx_posted_accept_events
 // 队列或者ngx_posted_events队列中
 (void) ngx_process_events(cycle, timer, flags);

 // 这里开始处理accept事件,将其交由ngx_event_accept.c的ngx_event_accept()方法处理;
 ngx_event_process_posted(cycle, &ngx_posted_accept_events);

 // 开始释放锁
 if (ngx_accept_mutex_held) {
  ngx_shmtx_unlock(&ngx_accept_mutex);
 }

 // 如果不需要在事件队列中进行处理,则直接处理该事件
 // 对于事件的处理,如果是accept事件,则将其交由ngx_event_accept.c的ngx_event_accept()方法处理;
 // 如果是读事件,则将其交由ngx_http_request.c的ngx_http_wait_request_handler()方法处理;
 // 对于处理完成的事件,最后会交由ngx_http_request.c的ngx_http_keepalive_handler()方法处理。

 // 这里开始处理除accept事件外的其他事件
 ngx_event_process_posted(cycle, &ngx_posted_events);
}

위 코드에서는 대부분의 확인 작업을 생략하고 스켈레톤 코드만 남겼습니다. 먼저 작업자 프로세스는 잠금을 획득하기 위해 ngx_trylock_accept_mutex() 메서드를 호출합니다. 잠금이 획득되면 각 포트에 해당하는 파일 설명자를 수신합니다. 그런 다음 ngx_process_events() 메서드가 호출되어 epoll 핸들에서 모니터링되는 이벤트를 처리합니다. 그런 다음 공유 잠금이 해제되고 마지막으로 연결된 클라이언트의 읽기 및 쓰기 이벤트가 처리됩니다. ngx_trylock_accept_mutex() 메서드가 공유 잠금을 획득하는 방법을 살펴보겠습니다.

ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) {
 // 尝试使用cas算法获取共享锁
 if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

  // ngx_accept_mutex_held为1表示当前进程已经获取到了锁
  if (ngx_accept_mutex_held && ngx_accept_events == 0) {
   return ngx_ok;
  }

  // 这里主要是将当前连接的文件描述符注册到对应事件的队列中,比如kqueue模型的change_list数组
  // nginx在启用各个worker进程的时候,默认情况下,worker进程是会继承master进程所监听的socket句柄的,
  // 这就导致一个问题,就是当某个端口有客户端事件时,就会把监听该端口的进程都给唤醒,
  // 但是只有一个worker进程能够成功处理该事件,而其他的进程被唤醒之后发现事件已经过期,
  // 因而会继续进入等待状态,这种现象称为"惊群"现象。
  // nginx解决惊群现象的方式一方面是通过这里的共享锁的方式,即只有获取到锁的worker进程才能处理
  // 客户端事件,但实际上,worker进程是通过在获取锁的过程中,为当前worker进程重新添加各个端口的监听事件,
  // 而其他worker进程则不会监听。也就是说同一时间只有一个worker进程会监听各个端口,
  // 这样就避免了"惊群"问题。
  // 这里的ngx_enable_accept_events()方法就是为当前进程重新添加各个端口的监听事件的。
  if (ngx_enable_accept_events(cycle) == ngx_error) {
   ngx_shmtx_unlock(&ngx_accept_mutex);
   return ngx_error;
  }

  // 标志当前已经成功获取到了锁
  ngx_accept_events = 0;
  ngx_accept_mutex_held = 1;

  return ngx_ok;
 }

 // 前面获取锁失败了,因而这里需要重置ngx_accept_mutex_held的状态,并且将当前连接的事件给清除掉
 if (ngx_accept_mutex_held) {
  // 如果当前进程的ngx_accept_mutex_held为1,则将其重置为0,并且将当前进程在各个端口上的监听
  // 事件给删除掉
  if (ngx_disable_accept_events(cycle, 0) == ngx_error) {
   return ngx_error;
  }

  ngx_accept_mutex_held = 0;
 }

 return ngx_ok;
}

위 코드에서는 기본적으로 세 가지 주요 작업을 수행합니다.

  1. cas 메서드를 사용하여 ngx_shmtx_trylock(을 통해 공유 잠금을 획득해 보세요. ) 메서드;

  2. 잠금을 획득한 후 ngx_enable_accept_events() 메서드를 호출하여 대상 포트에 해당하는 파일 설명자를 모니터링합니다.

  3. 잠금을 획득하지 못한 경우 ngx_disable_accept_events() 메서드를 호출하여 모니터링을 해제합니다. 파일 설명자.

위 내용은 nginx 패닉 문제를 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제