【项目学习】nginx

目录

架构:

 Nginx进程处理模型

锁的争用与负载均衡(HTTP服务情景)

负载均衡(反向代理情景)

RR

权重

ip_hash

fair(第三方)

url_hash(第三方)

平滑升级

nginx内存池源码解析

安装与使用


看一下大名鼎鼎的nginx,学学别人是怎么设计一个服务器的

可以看看这位博主的文章,从nginx的数据结构开始剖析,讲得很好https://blog.csdn.net/chen19870707

Nginx设计时重视的几个关键点:

  • 性能:包括网络性能,单词请求的延迟性,网络效率;(名词就不解释了~)
  • 可伸缩性:可通过添加组件来提升服务,或者允许组件之间具有交互功能;
  • 简单性:组件的简单程度,便于理解和实现;
  • 可修改性:包括可进化性,可扩展性,可定制性,可配置性,可重用性;
  • 可见性:可监控关键组件的运行情况;
  • 可移植性:跨平台运行;
  • 可靠性:在服务出现故障时,一个架构容易受到系统层面故障影响的程度;

架构:

                           

1.1. 主进程

Nginx 启动时,会生成两种类型的进程,一个是 主进程 ( master ), 一个 ( windows版本的目前只有一个)或 多个工作进程 ( worker )。 主进程 并不处理网络请求,主要负责 调度工作进程 ,也就是图示的 3 项: 加载配置 、 启动工作进程 及 非停升级 。所以, Nginx 启动以后,查看操作系统的进程列表,我们就能看到 至少有两个 Nginx 进程。

1.2. 工作进程

服务器实际 处理网络请求 及 响应 的是 工作进程 ( worker ),在类 unix 系统上, Nginx可以配置 多个 worker ,而每个 worker 进程 都可以同时处理 数以千计 的 网络请求 。

1.3. 模块化设计

Nginx 的 worker 进程,包括核心和功能性模块 ,核心模块负责维持一个运行循环 ( run-loop ),执行网络请求处理的不同阶段的模块功能,比如: 网络读写 、 存储读写 、 内容传输 、 外出过滤 ,以及将请求发往上游服务器 等。而其代码的模块化设计 ,也使得我们可以根据需要对功能模块进行适当的选择和修改 ,编译成具有特定功能的服务器。

                    

image

1.4. 事件驱动模型

基于异步及非阻塞的事件驱动模型 ,可以说是 Nginx 得以获得高并发 、 高性能的关键因素,同时也得益于对 Linux 、 Solaris 及类 BSD 等操作系统内核中事件通知 及 I/O 性能增强功能的采用,如 kqueue 、 epoll 及 event ports 。

1.5. 代理(proxy)设计

代理设计,可以说是 Nginx 深入骨髓的设计,无论是对于 HTTP ,还是对于 FastCGI 、 Memcache 、 Redis 等的网络请求或响应,本质上都采用了代理机制 。所以, Nginx 天生就是高性能的代理服务器 。
 

 Nginx进程处理模型

Nginx 服务器使用 master/worker 多进程模式 。多线程启动和执行的流程如下:

  1. 主程序 Master process 启动后,通过一个 for 循环来接收和处理外部信号 ;
  2. 主进程通过 fork() 函数产生 worker 子进程 ,每个 子进程 执行一个 for 循环来实现 Nginx 服务器对事件的接收和处理 。

一般推荐 worker 进程数与 CPU 核心数 一致,这样一来不存在 大量的子进程生成和管理任务,避免了进程之间竞争 CPU 资源和进程切换的开销。而且 Nginx 为了更好的利用多核特性 ,CPU亲和:将进程/线程与cpu绑定,最直观的好处就是提高了cpu cache的命中率,从而减少内存访问损耗,提高程序的速度。

对于每个请求,有且只有一个工作进程对其处理。首先,每个 worker 进程都是从 master进程 fork 过来。在 master 进程里面,先建立好需要 listen 的 socket(listenfd) 之后,然后再 fork 出多个 worker 进程。

所有 worker 进程的 listenfd 会在新连接 到来时变得可读 ,为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件 前抢占 accept_mutex ,抢到互斥锁的那个进程 注册 listenfd 读事件 ,在读事件里调用 accept 接受该连接。

当一个 worker 进程在 accept 这个连接之后,就开始读取请求 , 解析请求 , 处理请求 ,产生数据后,再返回给客户端 ,最后才断开连接 ,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个 worker 进程中处理。

 

在 Nginx 服务器的运行过程中, 主进程和工作进程需要进程交互。交互依赖于 Socket 实现的管道来实现。

Master-Worker 交互

这条管道与普通的管道不同,它是由主进程指向工作进程的单向管道 ,包含主进程向工作进程发出的指令 , 工作进程 ID 等;同时主进程与外界通过信号通信 ;每个子进程具备接收信号 ,并处理相应的事件的能力。

worker-worker 交互

这种交互是和主进程-工作进程交互是基本一致的,但是会通过主进程间接完成。 工作进程之间是相互隔离的,所以当工作进程 W1 需要向工作进程 W2 发指令时,首先找到 W2 的 进程ID ,然后将正确的指令写入指向 W2 的 通道 。 W2 收到信号采取相应的措施。
 

 

锁的争用与负载均衡(HTTP服务情景)

  • 所谓的惊群

简单举例来说

    TCP服务端socket的建立,一般经过socket、bind、listen初始化后,调用accept等待客户端的连接。服务器一般做法会在listen后,fork多个子进程同时accept客户端的连接。子进程在调用accept后会堵塞睡眠,当第一个客户端连接到来后,所有子进程都会唤醒,但只会有一个子进程accept调用成功,其他子进程返回失败,代码中的处理往往在accept返回失败后,继续调用accept。虽然这在功能上没有什么问题,但在性能上很是浪费。

  • 惊群的解决

    Linux内核2.6已经解决了accept时的惊群问题,多个子进程accept堵塞睡眠时,连接到来,只有一个进程的accept会被唤醒返回。但现在子进程的实现方式不是直接accept,而是将初始化好的fd加入到epoll 的事件队列中,epoll返回后再调用accept。Linux无法解决多个子进程epoll返回的情况。这需要子进程自己处理。

  • Nginx的处理

    Nginx中处理epoll时惊群问题的思路很简单,多个子进程有一个锁,谁拿到锁,谁才将accept的fd加入到epoll队列中,其他的子进程拿不到锁,也就不会将fd加入到epoll中,连接到来也就不会导致所有子进程的epoll被唤醒返回。

  • 惊群处理的代码

    ngx_process_events_and_timers()处理事件,这个函数中的以下代码,处理惊群问题,顺带实现了负载均衡,因为处理惊群问题和负载均衡问题的代码在一起,下面一起分析一下。

if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }
ngx_use_accept_mutex配置nginx是否利用ngx_accept_mutex锁的方式解决惊群问题(nginx只有这种方式,如果不使用,惊群问题就会存在),默认配置是打开的。 

ngx_accept_disable用于处理负载均衡。

这里可以看出使用ngx_accept_mutex锁的情况下,一个进程要处理accept事件,必须满足两个条件:

  1. 满足负载均衡条件(负载压力低,稍后介绍nginx怎么判断压力高低)
  2. 获取ngx_accept_mutex锁
  • 负载均衡条件

Ngx_accept_disable会在每次accept事件正确处理后,更新其值

ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;

connection_n是配置文件中配置的该进程处理的最大连接数,free_connection_n初始值为connection_n,新建一个连接--,关闭一个连接++。

当free_connection_n 大于connection_n的1 / 8(连接数少于总数的7 / 8)时,ngx_accept_disable是负的,此时进程会参与锁的争夺,如果获得锁,该进程处理accept事件,其他进程,不处理accept事件。

当该进程处理accept事件增多,处理的连接也就增多,当free_connection_n小于connection_n的1 / 8(连接数大于总数的7 / 8)时,ngx_accept_disabled是正的,此时该进程会简单的将ngx_accept_disabled--,并退出锁的争夺,把机会让给其他进程。就这样平衡了负载,防止一个进程负载过高。

进程的连接越多,放弃争夺的次数也就越多,而ngx_accept_disabled--,避免了进程一直退出锁的争夺,防止不再接受新连接。

当每个进程的连接数都比较少时,谁抢到了ngx_accept_disable,谁处理连接;当一个进程抢的比较多,连接数率先达到了 7/ 8,那么就会退出锁的争夺,把机会让给其他进程,并不时ngx_accept_disabled--,保证一段时间后继续参加锁的争夺;当所有进程的连接数都大于 7 / 8时,这时再有新连接到来,处理延迟应该会比较大,因为所有进程都放弃了争夺,直到ngx_accept_disable—到小于0,再次争夺。

  • 获取ngx_accept_mutex锁

当满足了负载均衡条件,进程就会参与ngx_accept_mutex锁的争夺 ngx_trylock_accept_mutex(cycle)

Ngx_trylock_accept_mutex出现错误,直接return了

没有错误的情况,ngx_accept_mutex_held=1表示拿到了锁

拿到锁会设置flags |= NGX_POST_EVENTS; 设置了这个标志,epoll返回的所有事件不会立即处理,而是将事件放到post队列中,然后释放锁后在把队列中的事件一一拿出处理,这样做的原因是防止处理事件导致锁长时间得不到释放,新的accept连接事件得不到(其他进程的)及时处理。

ngx_accept_mutex_held=0表示没有拿到锁

拿不到锁的进程会修改epoll超时时间,让epoll尽快返回,早早的参与到下一次锁的争夺上来,也就能快速的处理新的accept连接事件。(写到这里不得不佩服作者真是各种情况都考虑到了,牛啊)

接下来看看关于锁的战争

ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
        if (ngx_accept_mutex_held
            && ngx_accept_events == 0
            && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
        {
            return NGX_OK;
        }
        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;
    }
    if (ngx_accept_mutex_held) {
        if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
            return NGX_ERROR;
        }
        ngx_accept_mutex_held = 0;
    }
    return NGX_OK;
}

line4       试图获取锁

line20     没有获取到锁,但ngx_accept_mutex_held却是1,是个异常,禁止当前进程处理accept事件(将accept fd将epoll队列中移除),重置ngx_accept_mutex_held=0

line5-10   成功获取锁,发现自己早就获得了该锁,只是没有accept事件发生,又循环了一次,继续获得了该锁。(第一次没有事件发生,应该释放锁,再进入第二次的争夺呀??)

line11-16 成功获得锁后,将accept fd加入到epoll队列中,准备监听accept事件,并置ngx_accept_mutex_head = 1表示获取到了锁(ngx_accept_events具体什么时候会用到)。

 

负载均衡(反向代理情景)

    负载均衡也是Nginx常用的一个功能,负载均衡其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。简单而言就是当有2台或以上服务器时,根据规则随机的将请求分发到指定的服务器上处理,负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡。而Nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略。

RR

按照轮询(默认)方式进行负载,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。虽然这种方式简便、成本低廉。但缺点是:可靠性低和负载分配不均衡。

权重

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

upstream test{
     server localhost:8080 weight=9;
     server localhost:8081 weight=1;
}


此时8080和8081分别占90%和10%。

ip_hash

上面的2种方式都有一个问题,那就是下一个请求来的时候请求可能分发到另外一个服务器,当我们的程序不是无状态的时候(采用了session保存数据),这时候就有一个很大的很问题了,比如把登录信息保存到了session中,那么跳转到另外一台服务器的时候就需要重新登录了,所以很多时候我们需要一个客户只访问一个服务器,那么就需要用iphash了,iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

upstream test {
    ip_hash;
    server localhost:8080;
    server localhost:8081;
}


fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

upstream backend { 
    fair; 
    server localhost:8080;
    server localhost:8081;
}


url_hash(第三方)

按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。

upstream backend { 
    hash $request_uri; 
    hash_method crc32; 
    server localhost:8080;
    server localhost:8081;
} 

 

 

 

平滑升级

https://www.cnblogs.com/liuqiang0/p/8528009.html

一、解释nginx的平滑升级

随着nginx越来越流行使用,并且nginx的优势也越来越明显,nginx的版本迭代也开始了加速模式,1.9.0版本的nginx更新了许多新功能,例如stream四层代理功能。伴随着nginx的广泛应用,版本升级必然是越来越快的,线上业务不能停,此时nginx的升级就是运维的重要工作了,下面就带大家一起来理解下nginx平滑升级。

二、nginx平滑升级原理

多进程模式下的请求分配方式

Nginx默认工作在多进程模式下,即主进程(master process)启动后完成配置加载和端口绑定等动作,fork出指定数量的工作进程(worker process),这些子进程会持有监听端口的文件描述符(fd),并通过在该描述符上添加监听事件来接受连接(accept)。

信号的接收和处理

Nginx主进程在启动完成后会进入等待状态,负责响应各类系统消息,如SIGCHLD、SIGHUP、SIGUSR2等。

Nginx信号简介

主进程支持的信号

  • TERMINT: 立刻退出
  • QUIT: 等待工作进程结束后再退出
  • KILL: 强制终止进程
  • HUP: 重新加载配置文件,使用新的配置启动工作进程,并逐步关闭旧进程。
  • USR1: 重新打开日志文件
  • USR2: 启动新的主进程,实现热升级
  • WINCH: 逐步关闭工作进程

工作进程支持的信号

  • TERMINT: 立刻退出
  • QUIT: 等待请求处理结束后再退出
  • USR1: 重新打开日志文件

三、nginx平滑升级实战

3.1 备份原Nginx二进制文件

备份二进制文件和nginx的配置文件

  1. [root@iZ28t900vpcZ ~]#cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx_$(date +%F)
  2. [root@iZ28t900vpcZ ~]#cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf_$(date +%F)

3.2 编译新的nginx源码包

编译新Nginx源码,安装路径需与旧版一致

  1. [root@iZ28t900vpcZ nginx-1.9.12]#./configure --prefix=/usr/local/nginx-1.9.12 --user=www --group=www --with-http_ssl_module --with-openssl=/path/to/openssl_src
  2. [root@iZ28t900vpcZ nginx-1.9.12]#make&make install

3.3 发送USR2信号

向主进程发送USR2信号,Nginx会启动一个新版本的master进程和工作进程,和旧版一起处理请求

  1. [root@iZ28t900vpcZ ~]# ps -ef|grep nginx|grep -v grep
  2. root 900 1 0 Mar07 ? 00:00:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
  3. nginx 28475 900 0 11:32 ? 00:00:00 nginx: worker process
  4. [root@iZ28t900vpcZ ~]#kill -USR2 900

3.4 发送WITCH信号

向原Nginx主进程发送WINCH信号,它会逐步关闭旗下的工作进程(主进程不退出),这时所有请求都会由新版Nginx处理

  1. [root@iZ28t900vpcZ ~]#kill -WITCH 900

3.5 发送HUP信号

如果这时需要回退,可向原Nginx主进程发送HUP信号,它会重新启动工作进程, 仍使用旧版配置文件 。然后可以将新版Nginx进程杀死(使用QUIT、TERM、或者KILL)

  1. [root@iZ28t900vpcZ ~]#kill -HUP 900

注:此步骤只需在回滚的时候执行即可

3.6 升级完毕

如果不需要回滚,可以将原Nginx主进程杀死(使用QUIT、TERM、或者KILL),至此完成热升级。

 

 

 

nginx内存池源码解析

Author:Echo Chen(陈斌)

Email:chenb19870707@gmail.com

Blog:Blog.csdn.net/chen19870707

Date:Nov 11th, 2014

  • 1.源代码位置
     

头文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_palloc.h

源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_palloc.c

  • 2.数据结构定义

先来学习一下nginx内存池的几个主要数据结构:

 ngx_pool_data_t(内存池数据块结构)

   1: typedef struct {
   2:     u_char               *last;        
   3:     u_char               *end;
   4:     ngx_pool_t           *next;
   5:     ngx_uint_t            failed;
   6: } ngx_pool_data_t;

    last:是一个unsigned char 类型的指针,保存的是/当前内存池分配到末位地址,即下一次分配从此处开始。
    end:内存池结束位置;
    next:内存池里面有很多块内存,这些内存块就是通过该指针连成链表的,next指向下一块内存。
    failed:内存池分配失败次数。

ngx_pool_s(内存池头部结构)

   1: struct ngx_pool_s {
   2:     ngx_pool_data_t       d;
   3:     size_t                max;
   4:     ngx_pool_t           *current;
   5:     ngx_chain_t          *chain;
   6:     ngx_pool_large_t     *large;
   7:     ngx_pool_cleanup_t   *cleanup;
   8:     ngx_log_t            *log;
   9: };

    d:内存池的数据块;
    max:内存池数据块的最大值;
    current:指向当前内存池;
    chain:该指针挂接一个ngx_chain_t结构;
    large:大块内存链表,即分配空间超过max的情况使用;
    cleanup:释放内存池的callback
    log:日志信息

由ngx_pool_data_t和ngx_pool_t组成的nginx内存池结构如下图所示:

  • 3.相关函数介绍

在分析内存池方法前,需要对几个主要的内存相关函数作一下介绍:

ngx_alloc:(只是对malloc进行了简单的封装)

   1: void *
   2: ngx_alloc(size_t size, ngx_log_t *log)
   3: {
   4:     void  *p;
   5:  
   6:     p = malloc(size);
   7:     if (p == NULL) {
   8:         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
   9:                       "malloc(%uz) failed", size);
  10:     }
  11:  
  12:     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
  13:  
  14:     return p;
  15: }

ngx_calloc:(调用malloc并初始化为0)

   1: void *
   2: ngx_calloc(size_t size, ngx_log_t *log)
   3: {
   4:     void  *p;
   5:  
   6:     p = ngx_alloc(size, log);
   7:  
   8:     if (p) {
   9:         ngx_memzero(p, size);
  10:     }
  11:  
  12:     return p;
  13: }

ngx_memzero:

 1: #define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

ngx_free :

   1: #define ngx_free          free

ngx_memalign

   1: void *
   2: ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
   3: {
   4:     void  *p;
   5:     int    err;
   6:  
   7:     err = posix_memalign(&p, alignment, size);
   8:  
   9:     if (err) {
  10:         ngx_log_error(NGX_LOG_EMERG, log, err,
  11:                       "posix_memalign(%uz, %uz) failed", alignment, size);
  12:         p = NULL;
  13:     }
  14:  
  15:     ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
  16:                    "posix_memalign: %p:%uz @%uz", p, size, alignment);
  17:  
  18:     return p;
  19: }

    这里alignment主要是针对部分unix平台需要动态的对齐,对POSIX 1003.1d提供的posix_memalign( )进行封装,在大多数情况下,编译器和C库透明地帮你处理对齐问题。nginx中通过宏NGX_HAVE_POSIX_MEMALIGN来控制;调用posix_memalign( )成功时会返回size字节的动态内存,并且这块内存的地址是alignment的倍数。参数alignment必须是2的幂,还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面,函数返回值是0.

  • 4.内存池基本操作

内存池对外的主要方法有:

创建内存池ngx_create_pool

ngx_create_pool用于创建一个内存池,我们创建时,传入我们的需要的初始大小:

   1: ngx_pool_t *
   2: ngx_create_pool(size_t size, ngx_log_t *log)
   3: {
   4:     ngx_pool_t  *p;
   5:     
   6:     //以16(NGX_POOL_ALIGNMENT)字节对齐分配size内存
   7:     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
   8:     if (p == NULL) {
   9:         return NULL;
  10:     }
  11:  
  12:     //初始状态:last指向ngx_pool_t结构体之后数据取起始位置
  13:     p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  14:     //end指向分配的整个size大小的内存的末尾
  15:     p->d.end = (u_char *) p + size;
  16:     
  17:     p->d.next = NULL;
  18:     p->d.failed = 0;
  19:  
  20:     size = size - sizeof(ngx_pool_t);
  21:     //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1),内存池最大不超过4095,x86中页的大小为4K
  22:     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
  23:  
  24:     p->current = p;
  25:     p->chain = NULL;
  26:     p->large = NULL;
  27:     p->cleanup = NULL;
  28:     p->log = log;
  29:  
  30:     return p;
  31: }

    nginx对内存的管理分为大内存与小内存,当某一个申请的内存大于某一个值时,就需要从大内存中分配空间,否则从小内存中分配空间。

    nginx中的内存池是在创建的时候就设定好了大小,在以后分配小块内存的时候,如果内存不够,则是重新创建一块内存串到内存池中,而不是将原有的内存池进行扩张。当要分配大块内存是,则是在内存池外面再分配空间进行管理的,称为大块内存池。

内存申请 ngx_palloc

   1: void *
   2: ngx_palloc(ngx_pool_t *pool, size_t size)
   3: {
   4:     u_char      *m;
   5:     ngx_pool_t  *p;
   6:  
   7:     //如果申请的内存大小小于内存池的max值
   8:     if (size <= pool->max) {
   9:  
  10:         p = pool->current;
  11:  
  12:         do {
  13:             //对内存地址进行对齐处理
  14:             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
  15:  
  16:             //如果当前内存块够分配内存,则直接分配
  17:             if ((size_t) (p->d.end - m) >= size) 
  18:             {
  19:                 p->d.last = m + size;
  20:  
  21:                 return m;
  22:             }
  23:             
  24:             //如果当前内存块有效容量不够分配,则移动到下一个内存块进行分配
  25:             p = p->d.next;
  26:  
  27:         } while (p);
  28:  
  29:         //当前所有内存块都没有空闲了,开辟一块新的内存,如下2详细解释
  30:         return ngx_palloc_block(pool, size);
  31:     }
  32:  
  33:     //分配大块内存
  34:     return ngx_palloc_large(pool, size);
  35: }

需要说明的几点:

1、ngx_align_ptr,这是一个用来内存地址取整的宏,非常精巧,一句话就搞定了。作用不言而喻,取整可以降低CPU读取内存的次数,提高性能。因为这里并没有真正意义调用malloc等函数申请内存,而是移动指针标记而已,所以内存对齐的活,C编译器帮不了你了,得自己动手。
 

   1: #define ngx_align_ptr(p, a)                                                   \
   2:      (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

2、开辟一个新的内存块 ngx_palloc_block(ngx_pool_t *pool, size_t size)

这个函数是用来分配新的内存块,为pool内存池开辟一个新的内存块,并申请使用size大小的内存;

   1: static void *
   2: ngx_palloc_block(ngx_pool_t *pool, size_t size)
   3: {
   4:     u_char      *m;
   5:     size_t       psize;
   6:     ngx_pool_t  *p, *new;
   7:  
   8:     //计算内存池第一个内存块的大小
   9:     psize = (size_t) (pool->d.end - (u_char *) pool);
  10:  
  11:     //分配和第一个内存块同样大小的内存块
  12:     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
  13:     if (m == NULL) {
  14:         return NULL;
  15:     }
  16:  
  17:     new = (ngx_pool_t *) m;
  18:  
  19:     //设置新内存块的end
  20:     new->d.end = m + psize;
  21:     new->d.next = NULL;
  22:     new->d.failed = 0;
  23:  
  24:     //将指针m移动到d后面的一个位置,作为起始位置
  25:     m += sizeof(ngx_pool_data_t);
  26:     //对m指针按4字节对齐处理
  27:     m = ngx_align_ptr(m, NGX_ALIGNMENT);
  28:     //设置新内存块的last,即申请使用size大小的内存
  29:     new->d.last = m + size;
  30:  
  31:     //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,就忽略,不需要每次都从头找起
  32:     for (p = pool->current; p->d.next; p = p->d.next) {
  33:         if (p->d.failed++ > 4) {
  34:             pool->current = p->d.next;
  35:         }
  36:     }
  37:  
  38:     p->d.next = new;
  39:  
  40:     return m;
  41: }

3、分配大块内存 ngx_palloc_large(ngx_pool_t *pool, size_t size)

ngx_palloc中首先会判断申请的内存大小是否超过内存块的最大限值,如果超过,则直接调用ngx_palloc_large,进入大内存块的分配流程;

   1: static void *
   2: ngx_palloc_large(ngx_pool_t *pool, size_t size)
   3: {
   4:     void              *p;
   5:     ngx_uint_t         n;
   6:     ngx_pool_large_t  *large;
   7:  
   8:     // 直接在系统堆中分配一块大小为size的空间
   9:     p = ngx_alloc(size, pool->log);
  10:     if (p == NULL) {
  11:         return NULL;
  12:     }
  13:  
  14:     n = 0;
  15:  
  16:     // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理  
  17:     for (large = pool->large; large; large = large->next) {
  18:         if (large->alloc == NULL) {
  19:             large->alloc = p;
  20:             return p;
  21:         }
  22:         //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
  23:         if (n++ > 3) {
  24:             break;
  25:         }
  26:     }
  27:  
  28:  
  29:     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  30:     if (large == NULL) {
  31:         ngx_free(p);
  32:         return NULL;
  33:     }
  34:     
  35:     //将large链接到内存池
  36:     large->alloc = p;
  37:     large->next = pool->large;
  38:     pool->large = large;
  39:  
  40:     return p;
  41: }

整个内存池分配如下图:

内存池重置 ngx_reset_pool

   1: void
   2: ngx_reset_pool(ngx_pool_t *pool)
   3: {
   4:     ngx_pool_t        *p;
   5:     ngx_pool_large_t  *l;
   6:     
   7:     //释放大块内存
   8:     for (l = pool->large; l; l = l->next) {
   9:         if (l->alloc) {
  10:             ngx_free(l->alloc);
  11:         }
  12:     }
  13:     
  14:     // 重置所有小块内存区
  15:     for (p = pool; p; p = p->d.next) {
  16:         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  17:         p->d.failed = 0;
  18:     }
  19:  
  20:     pool->current = pool;
  21:     pool->chain = NULL;
  22:     pool->large = NULL;
  23: }

内存池释放 ngx_pfree

   1: ngx_int_t
   2: ngx_pfree(ngx_pool_t *pool, void *p)
   3: {
   4:     ngx_pool_large_t  *l;
   5:  
   6:     //只检查是否是大内存块,如果是大内存块则释放
   7:     for (l = pool->large; l; l = l->next) {
   8:         if (p == l->alloc) {
   9:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
  10:                            "free: %p", l->alloc);
  11:             ngx_free(l->alloc);
  12:             l->alloc = NULL;
  13:  
  14:             return NGX_OK;
  15:         }
  16:     }
  17:  
  18:     return NGX_DECLINED;
  19: }

    所以说Nginx内存池中大内存块和小内存块的分配与释放是不一样的。我们在使用内存池时,可以使用ngx_palloc进行分配,使用ngx_pfree释放。而对于大内存,这样做是没有问题的,而对于小内存就不一样了,分配的小内存,不会进行释放。因为大内存块的分配只对前3个内存块进行检查,否则就直接分配内存,所以大内存块的释放必须及时。


外部资源的清理

    Nginx内存池支持通过回调函数,对外部资源的清理。ngx_pool_cleanup_t是回调函数结构体,它在内存池中以链表形式保存,在内存池进行销毁时,循环调用这些回调函数对数据进行清理。

   1: typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
   2:  
   3: struct ngx_pool_cleanup_s {
   4:     ngx_pool_cleanup_pt   handler;
   5:     void                 *data;
   6:     ngx_pool_cleanup_t   *next;
   7: };

    handler:是回调函数指针;
    data:回调时,将此数据传入回调函数;
    next:指向下一个回调函数结构体; 

    如果我们需要添加自己的回调函数,则需要调用ngx_pool_cleanup_add来得到一个ngx_pool_cleanup_t,然后设置handler为我们的清理函数,并设置data为我们要清理的数据。这样在ngx_destroy_pool中会循环调用handler清理数据;

     比如:我们可以将一个开打的文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描述符资源了。

   1: ngx_pool_cleanup_t *
   2: ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
   3: {
   4:     ngx_pool_cleanup_t  *c;
   5:     
   6:     //分配ngx_pool_cleanup_t
   7:     c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
   8:     if (c == NULL) {
   9:         return NULL;
  10:     }
  11:  
  12:     //给data分配内存
  13:     if (size) {
  14:         c->data = ngx_palloc(p, size);
  15:         if (c->data == NULL) {
  16:             return NULL;
  17:         }
  18:  
  19:     } else {
  20:         c->data = NULL;
  21:     }
  22:  
  23:     //将回掉函数链入内存池
  24:     c->handler = NULL;
  25:     c->next = p->cleanup;
  26:  
  27:     p->cleanup = c;
  28:  
  29:     ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
  30:  
  31:     return c;
  32: }

内存池销毁 ngx_destroy_pool

   1: void
   2: ngx_destroy_pool(ngx_pool_t *pool)
   3: {
   4:     ngx_pool_t          *p, *n;
   5:     ngx_pool_large_t    *l;
   6:     ngx_pool_cleanup_t  *c;
   7:  
   8:     //依次调用外部析构回调函数
   9:     for (c = pool->cleanup; c; c = c->next) {
  10:         if (c->handler) {
  11:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
  12:                            "run cleanup: %p", c);
  13:             c->handler(c->data);
  14:         }
  15:     }
  16:     
  17:     //释放大块内存
  18:     for (l = pool->large; l; l = l->next) {
  19:  
  20:         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
  21:  
  22:         if (l->alloc) {
  23:             ngx_free(l->alloc);
  24:         }
  25:     }
  26:     //释放小块内存
  27:     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
  28:         ngx_free(p);
  29:  
  30:         if (n == NULL) {
  31:             break;
  32:         }
  33:     }
  34: }

 

 

安装与使用

安装见https://www.cnblogs.com/dongye95/p/11196118.html

使用:

因为我是安装在/usr/local/nginx目录下,在/usr/local/nginx/conf/nginx.conf文件中新增一个自己的server

 82     server {
 83         listen      8011;
 84         server_name localhost;
 85         charset     utf_8;
 86         location / {
 87             alias   /usr/local/nginx/;
 88 
 89         }
 90     }

在/usr/local/nginx目录新建一个文件

记得修改访问权限 chmod 755 index.html

访问方式:

先启动nginx 在 /usr/local/nginx/sbin 里面 sudo ./nginx 可执行文件即可

1.命令行

curl localhost:8011

返回 hello world

2.浏览器

在目录下有日志文件,可方便查看每次访问的请求内容和错误记录等信息

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值