主頁 > 軟體設計 > nginx master 和 worker 之間的通信

nginx master 和 worker 之間的通信

2021-10-04 07:53:07 軟體設計

請添加圖片描述

文章目錄

    • 從 ngx_master_process_cycle 說起
    • ngx_start_worker_processes
    • ngx_spawn_process
    • ngx_worker_process_cycle
    • ngx_worker_process_init

從 ngx_master_process_cycle 說起

簡單做個偽代碼,看一下流程哈:

void ngx_master_process_cycle(ngx_cycle_t *cycle) {
 
 ···
 
  // 啟動各個worker行程
  ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);

···

  for (;;) {
    
  ···
    if (···) {
      // 這里主要是向各個行程發送命令
      ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 	}
 }
}

怎么通信先別管,再看一下是怎么啟動作業行程的唄:

ngx_start_worker_processes

先了解一下 ngx_channel_t,有點重要哈:
master行程每次發送給worker行程的指令用如下的一個結構來完成封裝

typedef struct {
    // 傳遞的 TCP 訊息中的命令
    ngx_uint_t  command;
    // 行程 ID,一般是發送命令方的行程 ID
    ngx_pid_t   pid;
    // 表示發送命令方在 ngx_processes 行程陣列間的序號
    ngx_int_t   slot;
    // 通信的套接字句柄
    ngx_fd_t    fd;
}ngx_channel_t;


Nginx 針對 command 成員定義了如下命令:
// 打開頻道,使用頻道這種方式通信前必須發送的命令
#define NGX_CMD_OPEN_CHANNEL 1

// 關閉已經打開的頻道,實際上也就是關閉套接字
#define NGX_CMD_CLOSE_CHANNEL 2

// 要求接收方正常地退出行程
#define NGX_CMD_QUIT 3

// 要求接收方強制地結束行程
#define NGX_CMD_TERMINATE 4

// 要求接收方重新打開行程已經打開過的檔案
#define NGX_CMD_REOPEN 5

static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) {
  ···
  ngx_channel_t ch;
 
  ···
 
  ch.command = NGX_CMD_OPEN_CHANNEL;	//當前是新建了一個行程
 
  for (i = 0; i < n; i++) {
 	//spawn,生成一個子行程
    // ngx_worker_process_cycle:該子行程所進行的事件回圈,這里先不管它什么回圈
    // worker行程在一個無限for回圈中,不斷的檢查相應的事件模型中是否存在對應的事件,
    // 然后將accept事件和read、write事件分開放入兩個佇列中,最后在事件回圈中不斷的處理事件
    ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type);
 
    // 下面的這段代碼的主要作用是將新建行程這個事件通知到其他的行程,
    // 其就會向ngx_processes陣列的每個行程的channel[0]上寫入當前廣播的事件,也即這里的ch,
    // 因為子行程之間也需要通信
    ch.pid = ngx_processes[ngx_process_slot].pid;
    ch.slot = ngx_process_slot;	//該新建行程所存放的陣列位置
    ch.fd = ngx_processes[ngx_process_slot].channel[0];
 
    // 將當前創建了子行程的事件廣播給其余的行程
    ngx_pass_open_channel(cycle, &ch);
  }
}
 

我們來看它怎么個生成法:

ngx_spawn_process

這里啊,需要關注一下:

typedef struct {
    ...
    // socketpair 創建的套接字對
    ngx_socket_t channel[2];
}ngx_processes_t;
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) {
  ···
    // 在ngx_processes陣列中存盤了當前創建的所有行程,而ngx_last_process理解為end,
    //只不過ngx_processes中記錄的行程有可能有部分已經失效了,
    //當前回圈就是從頭開始查找是否有某個行程已經失效了,
    //如果已經失效了,則復用該行程位置,否則直接使用ngx_last_process所指向的位置
    for (s = 0; s < ngx_last_process; s++) {
      if (ngx_processes[s].pid == -1) {
        break;
      }
    }
 
    // 這里說明所創建的行程數達到了最大限度
    if (s == NGX_MAX_PROCESSES) {
      ···
      return NGX_INVALID_PID;
    }

 
  // NGX_PROCESS_DETACHED標志表示當前fork出來的行程與原來的父行程沒有任何關系,比如進行nginx升級時,
  // 新生成的master行程就與原先的master行程沒有關系
  if (respawn != NGX_PROCESS_DETACHED) {
	//這里為什么采用sockpair?
    /* 這里的socketpair()方法的主要作用是生成一對套接字流,用于主行程和子行程的通信,
    這一對套接字會存盤在ngx_processes[s].channel中,本質上這個欄位是一個長度為2的整型陣列,
    在主行程和子行程 進行通信的之前,主行程會關閉其中一個,而子行程會關閉另一個,
    然后相互之間往未關閉的另一個檔案描述符中寫入或讀取資料即可實作通信,
    AF_UNIX表示當前使用的是UNIX檔案形式的socket地址族SOCK_STREAM指定了當前套接字建立的通信方式是管道流,
    并且這個管道流是雙向的,即管道雙方都可以進行讀寫操作第三個引數protocol必須為0,
    */
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) {
      ···
      return NGX_INVALID_PID;
    }
 
    ···
 
    // 將ngx_processes[s].channel[0]設定為非阻塞模式
    if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
      ···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    // 將ngx_processes[s].channel[1]設定為非阻塞模式
    if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
      ···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    on = 1;
    // 將ngx_processes[s].channel[0]套接字管道設定為異步模式
    if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
		···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    // 當前還處于主行程中,這里的ngx_pid指向了主行程的行程id,當前方法的作用主要是將
    // ngx_processes[s].channel[0]的操作權限設定給主行程,也就是說主行程通過向
    // ngx_processes[s].channel[0]寫入和讀取資料來與子行程進行通信
    if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
 		···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    // FD_CLOEXEC表示當前指定的套接字管道在子行程中可以使用,但是在execl()執行的程式中不可使用
    if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
      ···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    // FD_CLOEXEC表示當前指定的套接字管道在子行程中可以使用,但是在execl()執行的程式中不可使用
    if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
      ···
      ngx_close_channel(ngx_processes[s].channel, cycle->log);
      return NGX_INVALID_PID;
    }
 
    // ngx_processes[s].channel[1]是用于給子行程監聽相關事件使用的,當父行程向
    // ngx_processes[s].channel[0]發布事件之后,
    // ngx_processes[s].channel[1]中就會接收到對應的事件,從而進行相應的處理
    ngx_channel = ngx_processes[s].channel[1];
 
  } else {
    // 如果是NGX_PROCESS_DETACHED模式,則表示當前是另外新起的一個master行程,因而將其管道值都置為-1
    ngx_processes[s].channel[0] = -1;
    ngx_processes[s].channel[1] = -1;
  }
 
  ngx_process_slot = s;
 
  // fork()產生一個新的行程
  pid = fork();
 
  switch (pid) {
 
    case -1:
      // fork出錯
      ···
      return NGX_INVALID_PID;
 
    case 0:
      // 子行程執行的分支,這里的proc()方法是外部傳進來的,也就是說,當前方法只是創建一個新的行程,
      // 具體的行程處理邏輯,將交由外部代碼塊進行定義ngx_getpid()方法獲取的就是當前新創建的子行程的行程id
      ngx_pid = ngx_getpid();
      proc(cycle, data);
      break;
 
    default:
      // 父行程會走到這里
      break;
  }
 
  ···
 
  // 父行程會走到這里,當前的pid是fork()之后父行程得到的新創建的子行程的pid
  ngx_processes[s].pid = pid;
  ngx_processes[s].exited = 0;
 
  ···
  
  // 設定當前行程的各個屬性,并且存盤到ngx_processes陣列中的對應位置
  ngx_processes[s].proc = proc;
  ngx_processes[s].data = data;
  ngx_processes[s].name = name;
  ngx_processes[s].exiting = 0;
 
  ···
 
  if (s == ngx_last_process) {
    ngx_last_process++;
  }
 
  return pid;
}

現在看到,master行程使用sockpair開了兩個套接字,其中,第0號位用于master行程向worker行程發送資訊,并設定異步,制定1號為子行程可用,接下來我們看看子行程被創建出來執行什么作業:

ngx_worker_process_cycle

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
  ···
 
 //初始化,開啟個監聽啥的,先等等,后面展開
  ngx_worker_process_init(cycle, worker);
 
  ···
 
  for (;;) {
 		
 		//如果收到退出信號
    ···
 
    // 這里通過檢查相應的事件模型中是否存在對應的事件,然后將其放入佇列中進行處理,
    // 這里是worker行程處理事件的核心方法
    ngx_process_events_and_timers(cycle);
 
    // 如果當前nginx已經終止,則退出當前行程
    if (ngx_terminate) {
      ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
      ngx_worker_process_exit(cycle);
    }
 
    if (ngx_quit) {
      ···
    }
 
    ···
  }
}

這一段是沒看到我們想看的哈,看來還需要在深入一個函式去看看:


ngx_worker_process_init

/**
 * 這里主要是對當前行程進行初始化,為其設定優先級和打開的檔案限制等引數,
 * 最后會為當前行程添加一個監聽channel[1]的連接,以不斷讀取master行程的訊息,從而進行相應的處理
 */
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) {
  ···
 
  // 設定當前行程的優先級
  if (worker >= 0 && ccf->priority != 0) {
    if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
        ···
    }
  }
 
  // 設定當前行程能夠打開的檔案句柄數
  // 簡而言之就是設定核心檔案能夠使用的最大大小
  ···

  // 需要注意的是,對于cache manager和cache loader行程,這里的worker傳入的是-1,
  // 表示這兩個行程不需要設定親核性
  if (worker >= 0) {
    // 獲取當前worker的CPU親核性
    cpu_affinity = ngx_get_cpu_affinity(worker);
 
    if (cpu_affinity) {
      // 設定worker的親核性
      ngx_setaffinity(cpu_affinity, cycle->log);	//就是系結個CPU,后面專門出一篇寫這個,這個我也是要收入囊中的
    }
  }
 
···
  
  // 初始化空的set指令集合
  sigemptyset(&set);
 
  // ◆ SIG_BLOCK:將 set 引數指向信號集中的信號加入到信號掩碼中,
  // ◆ SIG_UNBLOCK:將 set 引數指向的信號集中的信號從信號掩碼中洗掉,
  // ◆ SIG_SETMASK:將 set 引數指向信號集設定為信號掩碼,
  // 這里就是直接初始化要阻塞的信號集,默認為空集
  if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
   ···
  }
 
 ···
  // 呼叫各個模塊的init_process()方法進行行程模塊的初始化,與本篇關系不大,后面講模塊了再說
  ···
 
  // 這里主要是關閉當前行程中各個模塊的channel[0]管道句柄
  for (n = 0; n < ngx_last_process; n++) {
 
	···
	//關閉父行程的channel[1]
    if (close(ngx_processes[n].channel[1]) == -1) {	//這是個全域的processes,好吧
  ···
    }
  }
 
  // 關閉當前行程的channel[0]管道句柄
  if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
  ···
  }
 
	···
 
  // ngx_channel指向的是當前行程的channel[1]句柄,也即監聽master行程發送訊息的句柄,
  // 當前方法中,首先會為當前的句柄創建一個connection物件,并且將其封裝為一個事件,
  //然后將該事件添加到對應的事件模型佇列中以監聽當前句柄的事件,事件的處理邏輯則主要有這里的ngx_channel_handler()方法進行,
  //這里的ngx_channel_handler的主要處理邏輯是:根據當前收到的訊息設定當前行程的一些標志位,或者更新某些快取資料,
  //如此,在當前進行的事件回圈中,通過不斷檢查這些標志位,從而實作在事件行程中處理真正的邏輯,
  //因而這里的ngx_channel_handler的處理效率是非常高的
  if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                            ngx_channel_handler) == NGX_ERROR) {
    exit(2);
  }
}

這里worker行程的初始化程序主要做了三件事:

為worker行程設定優先級和提升打開檔案的權限;
設定worker行程的親核性;
關閉當前行程與master行程通信的管道陣列中的channel[0],然后監聽channel[1],以處理master行程的訊息;

當收到master行程發過來的命令后,就呼叫ngx_channel_handler處理,

至此,master-worker 之間的通信就講完了,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/305160.html

標籤:其他

上一篇:IP地址詳解

下一篇:計算機網路(謝希仁 第七版) 第三章(資料鏈路層)-- 3.2 點對點協議PPP(PPP協議的特點 & PPP協議的幀格式 & PPP協議的作業狀態)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more