Nginx开机自动启动脚本

Nginx开机自动启动脚本

 

0

解决上传文件时 413 Request Entity Too Large 报错问题

上传文件出现问题 413 Request Entity Too Large,如下图所示

解决上传文件时 413 Request Entity Too Large 报错问题

我们可以看到请求的body的大小,在Content-Length后显示,Nginx默认的request body为1M,小于我们上传的大小

解决上传文件时 413 Request Entity Too Large 报错问题

解决方案

找到自己主机的nginx.conf配置文件,打开
http{}中加入 client_max_body_size 10m;
然后重启nginx

 

0

Nginx源码分析(6)

Nginx具有一系列的模块,包括HTTP模块,核心模块和mail模块等。简要分析一下一些具有代表性模块的原理。


event模块

event模块的主要功能是监听accept后建立的连接,对读写事件进行添加删除。事件处理模型和Nginx的非阻塞IO模型结合在一起使用。当IO可读可写的时候,相应的读写时间就会被唤醒,此时就会去处理事件的回调函数。

对于Linux,Nginx使用的是epollepollpoolselect的增强版本。

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符(fd),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

所以epoll本质是同步IO + 阻塞的(其实从其它角度看也可以算非阻塞,因为不是所有的数据到达后才返回)。

select模型的调用流程如下:

Nginx源码分析(6)

epoll对select模型和poll模型有诸多改进。主要改进就是引入notify机制mmap机制

  • mmap将内核空间的一段内存区域映射到进程内存中,相当于内核空间和用户空间共享内存。当epoll回调上层的callback函数来处理socket数据时, 数据已经从内核层自动映射到了用户空间。
  • select用O(n)的效率不断地去查看那些fd,效率太低。而epoll通过在内核中提供callback机制的方式,在内部使用链表把O(n)降到了O(1),讨论参见SO

我们看看怎么实现callback机制的。

Nginx源码分析(6)

当为一个用户建立一个socket连接时,调用epoll的current进程会加入到驱动的wait_queue中。当其它用户连接时,进行同样的处理。所以current进程在所有驱动的等待队列中。

当IO没有就绪,调用read函数会阻塞在这一步。一旦只要有一个用户连接的IO就绪,current进程就会被唤醒。

与select相比,epoll加入了callback这个hock,记录了这个唤醒者,避免了在current进程醒着的时候查询到底是谁唤醒了我。


##timer模块 先看一段代码,timer什么时候起作用。

代码出自一个简单的http服务器。它新建了一个定时器,然后计算自连接开始后的时间。主要用来判断连接是否timeout。

Nginx使用红黑树来构造定时器。定时器的机制就是,二叉树的值是其超时时间,每次查找二叉树的最小值,如果最小值已经过期,就删除该节点,然后继续查找,直到所有超时节点都被删除。我们知道,自平衡二叉搜索树rbtree的树节点中,最左边叶子节点(或根节点)所代表的那个定时器的超时时间是最小的,因此只需要O(1)时间删除它就可以。

Nginx源码分析(6)

0

Nginx源码分析(5)

当需要为服务器增加一个自定义的扩展功能时,需要用到模块,相当于Nginx给开发者提供的一个模板范式。

比如现在实现一个书签收藏网站的signin功能,用Flask框架可以这样实现:

它实现的功能就是解析用户GETPOST过来的数据,然后构造相应的响应。这和Nginx的handler模块所做的工作相似。

Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。下图表示一次请求和相应的完整过程。

Nginx源码分析(5)

现在在浏览器中输入http://127.0.0.1/hello_world,让浏览器显示 hello_world, testing!!!怎么实现呢?这需要我们编写一个hello handler模块。


模块定义ngx_module_t

开发一个模块,需要定义一个ngx_module_t类型的变量来说明这个模块的信息。它定义在/nginx/src/core/ngx_config_file中。

hello模块定义如下:

模块的编写步骤是:

  • 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
  • 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
  • 编写handler处理函数。模块的功能主要通过这个函数来完成。这是最关键的,hello模块的功能是简单返回一个字符串。在ngx_http_hello_world_handler中实现。

配置和编译

在Nginx文件夹下

ngx_http_hello_world_module.c是主要的函数,config是配置文件。

在配置文件中加入

这个config文件的内容就是告诉nginx的编译脚本,该如何进行编译。

add-module后接上文中新建目录的路径。


##使用

/usr/local/nginx/conf路径下的nginx.conf文件中加入

访问http://127.0.0.1/hello_world即可看见成功的页面。

Nginx源码分析(5)

0

Nginx源码分析(4)

前面分析了Nginx的基本数据结构,现在看一下功能模块。

Nginx将各功能模块组成一条链,当有请求到达时,请求依次进过这条链上的部分或全部模块进行处理。


线程模型

Nginx使用一个多进程模型来提供服务,一个master进程和多个worker进程。框架如下:

Nginx源码分析(4)

master分管多个work进程。每个worker执行死循环接收来自客户端的请求。死循环由ngx_worker_process_cycle实现。一个请求的简单处理流程如下:

  • 操作系统提供的机制(例如epoll, kqueue等)产生相关的事件
  • 接收和处理这些事件,如是接受到数据,则产生更高层的request对象
  • 处理request的header和body
  • 产生响应,并发送回客户端
  • 完成request的处理
  • 重新初始化定时器及其他事件

show the code

其中几个关键函数是

ngx_worker_process_init 处理子进程初始化

ngx_process_events_and_timers(cycle)

ngx_worker_process_exit(cycle) 子进程退出

下面来看一下子进程的初始化操作:

  • 全局性的设置,根据全局的配置信息设置执行环境、优先级、限制、setgid、setuid、信号初始化等
  • 调用所有模块的钩子init_process

里面关键的函数是init_process

  • 关闭不使用的socket,关闭当前worker的channel[0]句柄和其他worker的channel[1]句柄,当前worker会使用其他worker的channel[0]句柄发送消息,使用当前worker的channel[1]句柄监听可读事件
  • 在当前worker的channel[1]句柄监听可读事件

ngx_add_channel_event把句柄ngx_channel(当前worker的channel[1])上建立的连接的可读事件加入事件监控队列,事件处理函数为ngx_channel_hanlder。当有可读事件的时候,ngx_channel_handler负责处理消息,具体代码可以查看src/os/unix/ngx_process_cycle.c,过程如下:

0

Nginx源码分析(3)

前面分析了ngx_array_t数组,现在看一下ngx_queue队列和ngx_hash哈希表的实现。


ngx_queue队列

ngx_queue_t是一个双向链表,实现了一个队列的操作逻辑。但是它的结构只行指针的操作,因而在定义自己的节点时,需要自己定义数据结构和分配空间,并包含一个ngx_queue_t类型的成员。

这和Linux内核的数据结构很像。它们都将链表节点塞入数据结构。Linux内核的链表这样定义:

使用的时候

结构如图所示:

自动草稿

所以它用fox.list.next指向下一个节点,用fox.list.prev指向上一个节点。那我们如何从list_head找到fox的地址呢。内核提供了一个container_of()宏可以从链表指针找到父结构中包含的任何变量。

而在Nginx也是效仿采用一样的宏获取父结构地址。


##用法

它的API定义了初始化,插入,排序,找中位节点等一系列操作。

用法如下:


ngx_hash哈希表

ngx_hash表所用的hash算法为分桶后线性查找法,因而查找效率同key-value对成反比。对于常用的解决冲突的方法有线性探测、二次探测和开链法等。这里使用的是最常用的开链法(也是STL中使用的方法)。

哈希表整个结构如图所示:

自动草稿

哈希表用下列数据结构进行管理

在使用过程中,先会用ngx_hash_init_t实例化(类似于OOP思想,和内核驱动的用法相同)一个hash_init

然后对这个“对象”赋值。

第一行分配了ngx_hash_t大小的内存存储如下hash结构。

然后创建一个需要加到hash table中的数组。

最后将elements数组放到hash_init结构中,即将数组以hash table形式存储。

这就是整个hash结构的存储过程,查找相对简单,这里不再详述。

0

Nginx源码分析(2)

Nginx有很多封装好的内部结构,实现诸如数组、链表、队列、哈希表等这样的容器。和STL一样,它们定义了自己的逻辑、功能及API。


ngx_array_t数组

ngx_array_t是nginx内部封装的使用ngx_pool_t对内存池进行分配的数组容器,其中的数据是在一整片内存区中连续存放的。更新数组时只能在尾部压入1个或多个元素,定义在nginx/src/core/ngx_array.h中。

其中elts指向数组元素所在的内存地址,nelts为实际元素个数,size是单个元素大小,nalloc为数组容量。

pool指向要使用的实例化的内存池。

  • 创建ngx_array_t数组

a是一个nginx数组指针,使用p指向的内存池分配内存。

传进去实例化的内存池p、数组大小n和单个元素大小size。使用ngx_array_init来初始化数组,其中array->elts指向使用内存池p分配n * size大小的内存。

图示如下:

Nginx源码分析(2)

  • 删除ngx_array_t数组

数组删除分为两步,第一个if中将标示数组空内存起始地址的last更新到上图的data(m)结尾;第二个if中将last更新到上图的ngx_pool_t结尾。

  • ngx_array_push增加元素

数组使用方法如下:

ngx_array_push(arr)在数组arr上新追加一个元素,并返回指向新元素的指针(ele)。需要把返回的指针使用类型转换,转换为具体的类型,然后再给新元素赋值*ele = n

0

Nginx源码分析(1)

Nginx是一个高性能的静态HTTP服务器和反向代理服务器,Nginx本身不支持现在流行的 JSP、ASP、PHP等动态页面,但是它可以通过反向代理将请求发送到后端的服务器,例如 Tomcat、Apache等来完成动态页面的请求处理。

服务器的架构大同小异,而性能的差异主要来自对数据的处理方式上,也即进程模型和事件模型上。一个客户端请求的数据如何进行接收、存储、解析、返回是服务器做的最主要的工作。


##源码文件结构 源码位于src目录下,分为七个部分。


最基础的数据结构

C++之所以不被用来写网站是因为,网站的核心是处理字符串。而C++的字符串处理实在比较弱,还要不断惦记GC和内存泄漏。

而Nginx首先封装了一下字符串的数据结构和API。定义位于nginx/src/core/nginx_string.h中。

Nginx中一个字符串被表示为指针data指示首地址+len指示长度的方式,这种方式唯一定位一个字符串。和标准的glibc API\0的方式标识字符串结束不同,它有很多好处。

  • 通过长度表示字符串长度,减少计算字符串长度的步骤
  • 可以重复引用一段字符串内存,减少不必要的内存分配

这样的表示是不是似曾相识?在Redis的sds中有类似的处理。

再看看它的API,API以宏的方式定义。

第一个宏定义一个nginx的字符串,用法是

这里直接是宏展开,对结构体进行赋值。先生成了一个临时变量"stop",然后把临时变量的长度和地址赋值给结构体。ngx_stringngx_null_string{,}格式的,而结构体只能在初始化时进行整体赋值,因而API只能用于赋值时初始化。还需要注意str必须是常量字符串,因为sizeof是以\0为结束标志的。

打印结果


内存管理池ngx_pool_t

ngx_pool_t是一个资源管理池。它提供一种机制帮助管理一系列的资源(如内存、文件等),接管这些资源的所有权,负责资源的使用和释放。类似于C++中的shared_ptr智能指针。

ngx_pool_t定义在文件nginx/src/core/ngx_palloc.h中,相关的内存分配函数(如ngx_alloc)定义在nginx/src/os/unix/ngx_alloc.h中。

ngx_pool_data_t指示资源池数据块的位置信息。

size_t是数据块的最大值

整个内存管理如图所示:

再看一下相关的API函数。

  • ngx_alloc使用malloc进行内存分配,同时把错误信息写到log文件。
  • ngx_create_pool使用posix_memalign()申请大小为NGX_POOL_ALIGNMENT字节对其的内存。

p->d.last位移ngx_pool_t大小指向数据区段未使用部分的开始。

p->d.last指向数据区段未使用部分的结束。

可见分配的size大小的内存一部分要分给ngx_pool_t结构。

p->max表示分配的最大内存为NGX_MAX_ALLOC_FROM_POOL

最大限制为一个分页大小,在x86上其返回值应为4096Bytes = 4KB。

0

自建CDN加速节点实现DNS智能解析

如果需要便捷一点的可以直接用第三方提供的CDN加速服务,比如百度CDN七牛又拍云腾讯云阿里云等等服务商都有提供这类服务。但是前提条件是需要一定的成本,以及网站域名是需要BA才可以使用国内CDN服务商。

有没有办法,如果我们的网站域名没有BA也可以用来加速效果?有两种方法,第一种是将我们的网站搬到亚洲的计算机房,例如香港、新加坡、日本等地的服务器。速度肯定比美国快。二是我们可以使用反向解析构建自己的反向解析cdn,然后自己建立cdn节点,这比直接使用海外服务器要快。

我们可以利用DNSPOD等第三方解析工具提供的移动、联通、电信等分线路设定不同的解析节点。比如我们可以用电信速度快的服务器做电信节点、移动访问快的服务器做移动节点、联通访问速度快的做联通节点,然后三个节点都自动指向源站服务器网站。

1、准备工作

1、源站点网站

我们在准备部署CDN节点之前,我们需要有一个正在运行的源站点。这个我们可以任意找一个在使用的站点,我们目的就是希望将这个网站加速。

2、自备CDN服务器或是VPS

我们需要准备搭建CDN节点的服务器,这里我为了演示的方便,就只用一台服务器。因为多台的方法也是类似的,只是到时候DNSPOD解析的节点和备用节点智能切换解析而已,搭建一台其他就都一样。

2、架设Nginx环境

3、配置反向解析

1、修改DNS配置

我们需要先修改CDN节点DNS,授权用来解析哪个域名和服务器。

添加一行:

2、添加网站解析

我们需要在CDN节点服务器里创建站点,用来缓存数据的目录。

791202.com.conf中添加下面的内容,缓存目录/缓存时间请根据实际情况调整。

根据上面的配置,我们需要将对应的目录修改成自己的。

重启Nginx生效:

3、DNS分节点 解析

然后根据实际的需要在第三方DNS解析,你要设置全部用CDN节点或者是电信、移动、联通,还是其他线路,就指向对应IP。解析完毕之后,我们用第三方PING工具看看是不是生效,但是最好的办法是先全区域默认用节点IP,自己也看看是不是真实生效。

0

Nginx面试题:谈谈你对Nginx使用的epoll模型的理解?

我们都知道 Nginx 能做到高并发情况下的高效处理得意于它的 epoll模型,那如何理解它呢?

一些Linux基础知识

在这之前,我们需要了解一些 Linux 知识来作铺垫。

对于 Linux 而言:

一切都是文件

然而为了区分不同类型的事物,还有:

  • 普通文件
  • 目录文件
  • 链接文件
  • 设备文件

其中文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其值是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行 I/O 操作的系统调用都通过文件描述符

文件描述符
文件描述符

如果直接这么讲可能有些难以理解,对于 Linux 有一些使用的用户来说,会有类似如下的写法:

其中2>&1中的 2 就是表示的「标准错误」,1 就是「标准输出」,中间的 & 表示后面跟的数字是文件描述符而不是一个文件(不然所有的「标准错误」就都重定向到了一个名为 1 的文件中了)。

有了上面的知识,接下来就可以看看 select,poll 和 epoll 分别是什么了~

多路复用

select、poll 、epoll 这三者都是 I/O 多路复用机制,且简要介绍了多路复用的定义,那么如何更加直观地了解多路复用呢?看下图:

多路复用机制
多路复用机制

对于网页服务器 Nginx 来说,会有很多链接进来, epoll 会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。

一般来说以下场合需要使用 I/O 多路复用:

  • 当客户处理多个描述字时(一般是交互式输入和网络套接口)
  • 如果一个服务器既要处理 TCP,又要处理 UDP,一般要使用 I/O 复用
  • 如果一个 TCP 服务器既要处理监听套接口,又要处理已连接套接口

select

I/O 多路复用这个概念被提出来以后, select 是第一个实现,一个 select 的调用过程图如下所示:

select 的调用过程
select 的调用过程

其缺点为:

  • 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大
  • 同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大
  • select 支持的文件描述符数量只有 1024,非常小

如果系统支持的文件描述符数量不够,在 Linux 上一般就会表现为:

此时就需要通过类似:ulimit -n 2048的方式来临时提升。

​poll

poll 和 select 原理一样,不过相比较 select 而言,poll 可以支持大于 1024 个文件描述符。

epoll

相比较 select 和 poll,epoll 的最大特点是:

  • epoll 现在是线程安全的,而 select 和 poll 不是。
  • epoll 内部使用了 mmap 共享了用户内核的部分空间,避免了数据的来回拷贝。
  • epoll 基于事件驱动,epoll_ctl 注册事件并注册 callback 回调函数,epoll_wait 只返回发生的事件避免了像 select 和 poll 对事件的整个轮寻操作。

什么是回调?

一个简单的例子

  • 驾照考试成绩快要出来的那段时间,小张每隔一段时间就去尝试查一下成绩,这个被称为轮询。
  • 小张知道不需要去盯着官网看成绩,等成绩出来后自然会有短信发到自己手机上:「叮,你的科目三没过」,这样就是回调。

另一个方便理解的对比如下:

  1. 对于 select / poll 模型来说,可以理解为让酒店代理订票,然后每隔几个小时就问一下买到没有,酒店在第二天订到了票,交钱给酒店拿到票,这样会需要额外的打电话时间和精力。
  2. 对于 epoll 来说则是委托酒店帮忙订票,但是并不反复去问,酒店在第二天买到了票,酒店打电话通知来领票,交钱给酒店拿到票。

epoll 和 Nginx

回到文章开头,最后我们可以简单总结一下为什么有了 epoll 的 Nginx 会有很高的运行效率,其原因在于它使用了异步,非阻塞,IO 多路复用。但是我们是不是就应该吹爆 Nginx,表示「Nginx 完爆 Apache」呢?

其实不是,相比较 Nginx 而言,Apache 作为一个非常老牌的网页服务器,其有丰富的模块组件支持,稳定性强,BUG 少,动态内容处理强,而 Nginx 的优势主要则在于占用资源少,负载均衡,高并发处理强,静态内容处理高效,所以只有掌握了自己的具体业务场景,才可以分情况地讨论这两个服务器之间的区别。

0