nginx源码分析——event模块

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情
一、简介
     nginx是一款非常受欢迎的软件,具备高性能、模块化可定制的良好特性。之前写了一篇 nginx的http模块分析的文章,主要对http处理模块进行了分析讲解,同时也涉及了nginx模块化的内容。至于nginx高性能的原因,希望能够在在这篇文章中就自己对于这方面的理解给大家分享一下。
     nginx的event处理模型包含两个方面:高效的IO处理函数,事件的异步处理(可选的线程池)。
 
二、IO复用函数
     nginx中包含epoll、poll、select、devpoll、kqueue、iocp等不同的IO处理函数。下面就常见的epoll、poll、select来做简单的分析
 
select
poll
epoll
fd数量
用fd_set类型来存储fd,每个fd占一位,fd_set在linux系统中默认1024位,所以select最多支持1024个fd(可以通过修改FD_SETSIZE来增加)
采用pollfd结构体来存储fd,因此支持的最大fd数目跟系统内存有关(支持存储多少个)
采用epoll_event存储,因此也只与内存有关
通知方式
需要遍历fd_set集合中所有的fd,然后一个个检查确定是哪些fd上有event发生
与select类似
fd上有事件发生时,epoll函数会直接返回,且只会返回有事件发生的fd,不需要遍历
消息传递
将fd_set拷贝到内核空间,有event发生时再从内核空间拷贝到用户空间
与select类似
mmap申请一块共享内存来存放消息,省去内存拷贝开销
 
     另外:epoll有两种工作模式:LT(level triggered)和ET(edge triggered);LT又称水平触发,也就是说如果fd集合中还有没有处理完的event,对epoll调用会立刻返回这些未处理完的event。ET又称水平触发,调用方需要一次性把返回的所有fd都处理完成,因此下次调用epoll时这些event标识就会被清空(这些事件就会丢失)。
     由于每个IO复用函数对应的操作方式都不一样,nginx为了方便切换不同的IO复用函数,就利用nginx模块化的机制将每种函数及其相关操作都封装成一个NGX_EVENT_MODULE模块,然后用配置的方式来指明采用哪种方式(每个平台下都会有默认的IO处理函数)。下面就用代码来做些说明:
1
2
3
4
5
6
7
///////////  nginx/src/event/ngx_event.c    ////////
//这里各个模块并不像phase_handler或者filter那样用某种结构来存储,而是直接外部声明(方便直接调用)
extern  ngx_module_t ngx_kqueue_module;
extern  ngx_module_t ngx_eventport_module;
extern  ngx_module_t ngx_devpoll_module;
extern  ngx_module_t ngx_epoll_module;
extern  ngx_module_t ngx_select_module;
   
  下面先介绍event模块有关的调用流程,然后再具体分析模块的各个函数的相关功能。
 
     调用fork创建worker进程之前,依据配置初始化一部分模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
///////////  nginx/src/core/ngx_cycle.c     ////////
//在nginx主进程启动worker之前调用,用于初始化相关的配置
ngx_cycle_t *  ngx_init_cycle(ngx_cycle_t *old_cycle)
{
      .......
      //对类型为NGX_CORE_MODULE的模块调用对应的create_conf函数,进行申请空间之类的操作
      for  (i = 0; cycle->modules[i]; i++) {
         if  (cycle->modules[i]->type != NGX_CORE_MODULE) {
             continue ;
         }
         .....
         if  (module->create_conf) {
             rv = module->create_conf(cycle);
     .......
     conf.module_type = NGX_CORE_MODULE;
     conf.cmd_type = NGX_MAIN_CONF;
     .......
     //根据上面给conf的赋值语句,ngx_conf_parse主要对配置文件NGX_MAIN_CONF这一层级的
     //配置进行解析,并只对NGX_CORE_MODULE类型的模块中的cmd进行匹配,然后执行匹配cmd对
     //应的函数
     if  (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
         environ = senv;
         ngx_destroy_cycle_pools(&conf);
         return  NULL;
     }
     ......
     //对类型为NGX_CORE_MODULE的模块调用对应的int_conf函数,对相关的conf结构进行初始化
     for  (i = 0; cycle->modules[i]; i++) {
if  (cycle->modules[i]->type != NGX_CORE_MODULE) {
             continue ;
  }
  ......
        if  (module->init_conf(cycle, cycle->conf_ctx[cycle->modules[i]->index])
     ......
      //调用每个模块注册的init_module函数初始化相关参数(因为这个操作在fork worker进程之前,
      //所以初始化的参数都会被继承)
      if  (ngx_init_modules(cycle) != NGX_OK) {
         /* fatal */
         exit (1);
     }
      ......
}

 

     初始化worker进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
///////////  nginx/src/os/unix/ngx_process_cycle.c    ////////
//worker进程启动初始化
static  void   ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
      ......
     //调用每个模块注册的init_process函数(这个操作是在每个worker进程中的,所以申请一些进程
     //自用的一些资源,如内存、变量等)
     for  (i = 0; cycle->modules[i]; i++) {
         if  (cycle->modules[i]->init_process) {
             if  (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                 /* fatal */
                 exit (2);
             }
         }
     }
     ......
}

 

上面介绍了NGX_CORE_MODULE模块的相关函数执行的相关流程,下面就对应event模块具体介绍下这个流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/////////  nginx/src/event/ngx_event.c  /////////
  
//ngx_event_core_create_conf函数主要是申请空间的,比较简单这里就不讲了
  
//在执行ngx_conf_parse函数时,配置项events属于NGX_MAIN_CONF类型,且events模块类型为
//NGX_CORE_MODULE,所以events配置对应的ngx_events_block就会被调用
  
//ngx_event_block这个函数与上面的ngx_init_cycle以及ngx_http_block函数的结构类似,都是
//先调用特定模块的create_conf函数,然后解析对应level的配置项(调用配置注册的函数),接着
//调用init_conf函数实现完成模块对应配置的初始化
static  char  *  ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd,  void  *conf)
{
     ......
     //执行NGX_EVENT_MODULE类型的模块注册的create_conf函数
     for  (i = 0; cf->cycle->modules[i]; i++) {
         if  (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
             continue ;
         }
         .....
         if  (m->create_conf) {
             (*ctx)[cf->cycle->modules[i]->ctx_index] =  m->create_conf(cf->cycle);
     ......
     cf->module_type = NGX_EVENT_MODULE;
     cf->cmd_type = NGX_EVENT_CONF;
     //解析配置文件中涉及NGX_EVENT_MODULE模块并执行相关配置注册的相关函数
     rv = ngx_conf_parse(cf, NULL);
     ......
     //执行NGX_EVENT_MODULE类型的模块注册的init_conf函数完成模块conf的初始化
     //在event_core_module模块的init_conf阶段,即ngx_event_core_init_conf函数
     //中完成了IO复用模型的选择
     for  (i = 0; cf->cycle->modules[i]; i++) {
         if  (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
             continue ;
         }
   ......
        rv = m->init_conf(cf->cycle, (*ctx)[cf->cycle->modules[i]->ctx_index]);
      ......
}
  
//该函数在init_cycle中的init_module函数中被调用。调用时worker还没有启动
static  ngx_int_t  ngx_event_module_init(ngx_cycle_t *cycle)
{
     ......
     shm.size = size;
     shm.name.len =  sizeof ( "nginx_shared_zone" ) - 1;
     shm.name.data = (u_char *)  "nginx_shared_zone" ;
     shm. log  = cycle-> log ;
     //内部调用mmap申请一块共享内存,这样fork出来的worker也能继承这块共享内存的地址
     //worker进程可以通过继承的共享内存地址来实现与master以及其他worker的通信
     if  (ngx_shm_alloc(&shm) != NGX_OK) {
         return  NGX_ERROR;
     }
  
     shared = shm.addr;
     ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
     ngx_accept_mutex.spin = (ngx_uint_t) -1;
     //将accept锁放到共享内存中,这样就实现了对多个进程的互斥、同步操作
     if  (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
                          cycle->lock_file.data)
         != NGX_OK)
     {
         return  NGX_ERROR;
     }
      
     //将连接计数器以及其他的一些原子类型的变量也存放到共享内存,这样可以很方便统计所有整个nginx
     //的连接总数
     ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
     .....
}
  
//该函数ngx_worker_process_init中的init_process函数中被调用,因为执行阶段位于
//worker进程初始化时,所以这里申请的资源都属于各个worker独有,不用担心因被继承而导致的资源浪费
static  ngx_int_t  ngx_event_process_init(ngx_cycle_t *cycle)
{
      .....
     //创建保存event的队列用于保存accept、普通事件
     ngx_queue_init(&ngx_posted_accept_events);
     ngx_queue_init(&ngx_posted_events);
  
     //初始化事件定时器
     if  (ngx_event_timer_init(cycle-> log ) == NGX_ERROR) {
         return  NGX_ERROR;
     }
     .....
     //创建连接池
     cycle->connections =
         ngx_alloc( sizeof (ngx_connection_t) * cycle->connection_n, cycle-> log );
     .....
}
  以上就是event这个模块的基本内容,相较http模块来讲,东西少了很多,但是都是采取的ngx模块,方便解读。
 
三、高效事件处理
       nginx的master进程初始化过程中,调用函数ngx_open_listening_sockets对配置文件中每个listen的端口创建了socket并进行了bind,然后将这些已经bind的sockfd放到一个cycle->listening数组中,这个数组在fork worker子进程时被子进程继承(子进程继承父进程的文件描述符只是给文件描述符的打开数+1,并未改变该文件描述符指向的内容)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
////////////  nginx/src/event/ngx_event.c  /////////////
//在这个函数中完成了对从master进程继承的listen fd的初始化工作
static  ngx_int_t  ngx_event_process_init(ngx_cycle_t *cycle)
{    
     ......
     //遍历所有的listen fd,然后从连接池中获取一个连接并初始化为listen fd的连接
     /* for each listening socket */
     ls = cycle->listening.elts;
     for  (i = 0; i < cycle->listening.nelts; i++) {
      
#if (NGX_HAVE_REUSEPORT)
         //如果支持REUSEPORT选项,那么socket只能在一个worker上工作
         if  (ls[i].reuseport && ls[i].worker != ngx_worker) {
             continue ;
         }
#endif
          //从连接池中获取一个连接实例并进行部分初始化
         c = ngx_get_connection(ls[i].fd, cycle-> log );
         if  (c == NULL) {
             return  NGX_ERROR;
         }
         //继续初始化
         c->type = ls[i].type;
         c-> log  = &ls[i]. log ;
         .....
}
     
  由于ngx_event_process_init在每个worker初始化过程中被调用,因此一个listen 端口对应的文件描述符同时存在于所有的worker进程中,如果不采取任何处理措施直接将这些socket fd直接添加到epoll等IO监听函数中,那么当有客户端向listen port发送请求时,所有的监听该fd的worker进程中epoll等函数都会返回,进而去处理该连接事件,这样就出现了多个worker去争抢一个资源的情况,这就是常见的“惊群效应”,这种方式对系统性能的影响太大。为了解决这个问题,nginx采用了accept_mutex(在ngx_event_module_init函数中,用共享内存的方式来存放accept_mutex)的方式来对listen fd加锁,使得每次有新连接到达的时候,只有一个worker进程去处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
////////////  nginx/src/os/unix/ngx_process_cycle.c  /////////////
//worker进程处理连接、请求的主函数
static  void   ngx_worker_process_cycle(ngx_cycle_t *cycle,  void  *data)
{
     .....
     //初始化worker进程
     ngx_worker_process_init(cycle, worker);
     ngx_setproctitle( "worker process" );
  
     for  ( ;; ) {
         .....
         //处理新连接以及已有连接上的新数据
         ngx_process_events_and_timers(cycle);
         .....
     }
}
  
////////////  nginx/src/event/ngx_event.c  /////////////
//检查是否有新连接,并处理事件队列中的事件
void  ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
     .....
     //检查accept锁是否启用
     if  (ngx_use_accept_mutex) {
         //如果accept_disable大于0,说明当前进程负载偏高或者接受新连接异常
         //该值-1是为了尽快让该进程参与accept锁的竞争,防止一直空闲
         if  (ngx_accept_disabled > 0) {
             ngx_accept_disabled--;
         else  {
             //尝试获取accept,如果获取成功ngx_accept_mutex_held会被置1,
             if  (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                 return ;
             }
  
             if  (ngx_accept_mutex_held) {
                 //设置NGX_POST_EVENTS标志是为了在处理新连接时,将新的event放到
                 //事件队列异步处理,而不是立刻处理(防止阻塞)
                 flags |= NGX_POST_EVENTS;
  
             else  {
                 //没获取到accept锁
                 if  (timer == NGX_TIMER_INFINITE
                     || timer > ngx_accept_mutex_delay)
                 {
                     timer = ngx_accept_mutex_delay;
                 }
             }
         }
     }
     delta = ngx_current_msec;
      //这里是一个函数指针,指向当前使用IO模块中的事件处理函数,等待事件到来的超时时间是timer
     ( void ) ngx_process_events(cycle, timer, flags);
  
     delta = ngx_current_msec - delta;
  
     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle-> log , 0,
                    "timer delta: %M" , delta);
      
     //处理accept event队列中事件
     ngx_event_process_posted(cycle, &ngx_posted_accept_events);
      
     //如果持有accept锁,就释放,避免一个进程长期持有该锁。在大都是长连接的情况下,这回导致
     //各个worker负载的严重不均匀
     if  (ngx_accept_mutex_held) {
         ngx_shmtx_unlock(&ngx_accept_mutex);
     }
      
     //检查是否有超时(过期)的事件,如果有进行处理
     if  (delta) {
         ngx_event_expire_timers();
     }
      
     //处理普通event队列中的事件
     ngx_event_process_posted(cycle, &ngx_posted_events);
}
  
////////////  nginx/src/event/ngx_event_accept.c  /////////////
//接受新的连接
void   ngx_event_accept(ngx_event_t *ev)
{
      ......    
      //如果剩余的空闲连接数小于连接池总数的1/8,下次执行ngx_process_events_and_timers时
      //就不再竞争accept锁
      ngx_accept_disabled = ngx_cycle->connection_n / 8
                               - ngx_cycle->free_connection_n;
      ......
      //从连接池中取出一个连接并进行初始化
      c = ngx_get_connection(s, ev-> log );
      ......
     //将新建的连接通过ngx_add_conn函数指针加入到IO复用函数的等待队列中
     if  (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
           if  (ngx_add_conn(c) == NGX_ERROR) {
                 ngx_close_accepted_connection(c);
                 return ;
           }
     }
  
     log ->data = NULL;
     log ->handler = NULL;
     //ls对应的handler是在ngx_http_add_listening函数中将ngx_http_init_connection
     //函数赋值给了handler
     ls->handler(c);
     ......
}
  
////////////  nginx/src/event/modules/ngx_epoll_module.c  /////////////
//epoll模块事件处理函数
static  ngx_int_t  ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
      ......
      //等待新连接(最多阻塞timer时长)
      events = epoll_wait(ep, event_list, ( int ) nevents, timer);
      
      ......
      //如果设置了NGX_POST_EVENTS就说明当前持有accept锁,应当将新事件放到队列中,尽快返回
      if  (flags & NGX_POST_EVENTS) {
          queue = rev->accept ? &ngx_posted_accept_events
                                     : &ngx_posted_events;
          ngx_post_event(rev, queue);
      else  {
                 //没有持有accept锁,可以同步执行事件处理函数
                 //rev->handler在ngx_http_init_connection函数中被赋值为ngx_http_wait_request_handler
                 rev->handler(rev);
      }
      .......
}

 

     来总结一下:accept新创建的连接被添加到epoll中,然后调用ngx_http_init_connection来初始化http request,并将ngx_http_wait_request_handler赋值给rev->handler,这样无论是在epoll返回时调用rev->handler还是在处理ngx_posted_events队列时调用,都是相当于直接调用ngx_http_wait_request_handler来读取http的请求数据。至此,就将event模块和http模块连接起来,整个nginx的处理流程也就走通了。
nginx events 模块配置 这里写目录标题1.简介2.主要指令2.1 accept_mutex2.2 multi_accept2.3 worker_connections2.4 use3. nginx event模块的基本配置 1.简介 nginx events 模块主要是nginx 和用户交互网络连接优化的配置内容 2.主要指令 nginx events模块主要包含了accept_mutex, multi_accept,worker_connections 和 use这几个指令 2.1 accept_mutex 这个配置主要可以用 阅读详情

相关推荐

Nginx学习(10)—— event模块、core模块、变量

Nginx模块种类有很多,除了HTTP模块,还有一些核心模块和mail系列模块。核心模块主要是做一些基础功能,比如Nginx的启动初始化,event处理机制,错误日志的初始化,ssl的初始化,正则处理初始化。mail模块可以对imap,pop3,smtp等协议进行反向代理,这些模块本身不对邮件内容进行处理。启动模块从启动Nginx进程开始,做了一系列的初始化工作,源代码位于src/core/nginx.c,从main函数开始

Stars____的博客 942

Nginx源码分析 - Event事件篇 - Event模块和配置的初始化(16)

目录 一、Event模块的数据结构 1. event事件模块的配置 2. ngx_events_module 事件模块 3. ngx_event_core_module 事件核心模块 4. ngx_event_conf_t 事件conf结构 二、Event模块结构图 三、Event模块的初始化 1. ngx_event_module_init 模块初始化函数 四、Event模块配置的初始化 1. ngx_events_module 模块配置初始化 2. ngx_conf_parse 解

alpha_love的博客 646

Nginx03】Nginx学习:事件模块Event

Nginx学习:事件模块Event基础的核心模块中,事件模块是非常重要的一个部分,但是,它的配置项其实并不多,常见的或者说需要我们去配置的更少。不过本着基础学习和了解的态度,咱们还是要一个个的学习一下。首先来看一下什么是事件模块。在 Nginx 中,模块相关的配置都是在一对大花括号中的,比如 http{} 、server{} 这些,事件模块也是类似的。events{……………………}事件模块主要...

硬核项目经理 1145

Nginx源码分析 - Event事件篇 - NginxEvent事件模块概览(17)

目录 一、event模块的概览 二、重要数据结构 1. ngx_listening_s 侦听结构 2. ngx_connection_s socket连接对象结构 3. ngx_event_s 事件数据结构 前一章,我们讲解了《Nginx源码分析 - 主流程篇 - 多进程的惊群和进程负载均衡处理 》中实际上已经涉及到了一部分事件模块的概念。细心的可以发现,Nginx的事件都是由nginx_event.c文件中的ngx_process_events_and_timers进程事件分发器这个函数开..

alpha_love的博客 861

Nginx源码分析 - Event事件篇 - Event模块的进程

ngx_posted_events

qq35997793的博客 13

Nginx源码分析 - Event事件篇 - Event模块的进程初始化(18)

目录 一、源码分析 1. Event核心模块ngx_event_core_module 2. Event模块初始化ngx_worker_process_init 3. 初始化核心函数ngx_event_process_init 二、TCP连接和读取事件逻辑 1. ngx_event_process_init 初始化事件循环 2.ngx_http_init_connection 初始化http连接,读取read事件数据 3. ls->handler的回调函数是如何赋值的 前面一篇我..

alpha_love的博客 470

Nginx源码分析 - Event事件篇 - Event模块和配置的初始化

Event模块和配置的初始化,必须得结合我们讲过的两篇文章:《Nginx源码分析 - 主流程篇 - 模块的初始化》     和  《Nginx源码分析 - 主流程篇 - 解析配置文件》    前面我们讲解了模块的初始化以及核心模块的配置文件的解析。而Event的配置解析会比核心模块解析会复杂一些,但是原理是一样的。 Event模块的数据结构 event事件模块的配置:

keeper42的博客 351

nginx0.1.0之event模块初始化源码分析(1)

下面是nginx中几个重要的数据结构。 struct ngx_command_s { ngx_str_t name; // 命令名字 int type; // 命令的属性,参数个数、上下文等 char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); // 处理该命令的函...

标子 的专栏 409

c++结构体中的string初始化_nginx0.1.0之event模块初始化源码分析(3)

前面已经分析了event初始化的整体流程和第一步create_conf,接下来看一下第二步ngx_conf_parse。这里不分析该函数的代码,该函数主要是遍历配置文件的内容,然后读取命令和参数。最后匹配nginx所有模块的配置,找到处理该指令的函数。我们首先看一下event模块中ngx_event_core_module模块的指令配置。static ngx_command_t ngx_even...

weixin_31682031的博客 189

nginx0.1.0之event模块初始化源码分析(2)

本文分析nginxevent在初始化时的第一个步骤,就是执行每个类型为NGX_EVENT_MODULE的模块的create_conf函数。该版本一共有8个这样的模块。下面我们看一下各个模块create_conf函数的代码。 这是ngx_event_core_module模块的create_conf函数。 // 创建存储事件模块配置的结构体 static void *ngx_event_creat...

标子 的专栏 271

nginx0.1.0之event模块初始化源码分析(3)

前面已经分析了event初始化的整体流程和第一步create_conf,接下来看一下第二步ngx_conf_parse。这里不分析该函数的代码,该函数主要是遍历配置文件的内容,然后读取命令和参数。最后匹配nginx所有模块的配置,找到处理该指令的函数。我们首先看一下event模块中ngx_event_core_module模块的指令配置。 static ngx_command_t ngx_eve...

标子 的专栏 347

Nginx源码分析 - Event事件篇 - Epoll事件模块(19)

目录 一、Epoll模块的数据结构 1. epoll模块命令集 ngx_epoll_commands 2. epoll模块上下文 ngx_epoll_module_ctx 3. epoll模块配置 ngx_epoll_module 二、Epoll模块的初始化 1. 配置文件初始化ngx_events_block 2. 模块初始化ngx_event_process_init 三、核心函数 1. ngx_epoll_process_events实现了收集、分发事件接口 这一篇主要讲解一下..

alpha_love的博客 697

nginx0.1.0之event模块初始化源码分析(4)

event的配置解析相关的代码已经分析完毕。下面分析一下另一个流程中event模块的实现。即在nginx创建进程,并且开始执行进程里的代码的时候。入口函数是ngx_worker_process_cycle。 for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]-&gt;init_process) { if (n...

标子 的专栏 252

【新书速递】深入理解Nginx底层原理与设计,轻松应对高并发

导读:我们为什么需要学习Nginx呢?高性能,高稳定,优雅的模块化编程等就不提了,就说一个理由:Nginx是目前最受欢迎的web服务器,据统计,全球平均每3个网站,就有一个使用Nginx。...

华章IT官方博客 660
上一篇: SpringMVC是什么?
下一篇: NodeJs之fs的读写删移监
p77ll9l53x
博客等级 码龄9年 17粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值