Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nginx 与 lua_nginx_module #21

Open
jinhailang opened this issue May 30, 2018 · 1 comment
Open

Nginx 与 lua_nginx_module #21

jinhailang opened this issue May 30, 2018 · 1 comment

Comments

@jinhailang
Copy link
Owner

jinhailang commented May 30, 2018

启动 Nginx

系统执行流程(概述)

20131009165600781

Nginx master

Nginx 启动后,首先进入 main() 函数,加载初始化配置信息(nginx.conf),调用所有模块的 init_module 方法,然后再调用 ngx_master_process_cycle

26142403-71f4cccd438240f086019a43e11ad879

Nginx worker

如上图,函数 ngx_start_worker_processes 会循环 fork() 出配置项 worker_processes 指定的 worker 进程。关于系统函数 fork,需要知道的是:

Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。

函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝:

* 子进程和父进程使用相同的代码段;
* 子进程复制父进程的堆栈段和数据段;

如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?
一般CPU都是以"页"为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,象INTEL的CPU,其一页在通常情况下是 4086字节大小,而无论是数据段还是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上的。
也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的" 页"从物理上也分开。
系统在空间上的开销就可以达到最小。这种技术又叫 [Copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write
) 

因此,worker 进程的 lua vm,是 fork 时,从 master 进程拷贝而来,因为是拷贝,在 worker 进程内的操作,自然就不会影响 master 堆栈(lua vm)变化。

PS:nginx 进程间通信,主要使用三种方式:

  • 系统信号量,例如 hup 等;
  • socketpair 匿名套接字,fork 子进程也会即继承父进程的 socketpair,从而实现父子进程间通信;
  • 共享内存,调用系统函数 shmget 等操作;

openresty 的 ngx.shared.DICT 就是基于系统共享内存实现的。

lua_nginx_module

lua_nginx_module 模块的函数 ngx_http_lua_init 也会在 main 调用所有模块的 init_module 方法时被调用,该函数调用 ngx_http_lua_init_vm 创建 lua 虚拟机实例,大致流程如下:

main -> ngx_http_lua_init -> ngx_http_lua_init_vm -> ngx_http_lua_new_state (创建虚拟机实例)

ngx_http_lua_new_state 主要功能

1)生成新 vm,lua_State
2)设置默认的package路径,路径由编译脚本生成
3)ngx_http_lua_init_registry() 初始化 lua registry table。registry 中保存了多个 lua 运行期需要保持的变量,例如:cache 的 lua 代码,协程的引用地址等,这些变量如果放在 lua 堆栈中会被 GC 机制自动回收,所以需要另外保存。
4)ngx_http_lua_init_globals() 初始化 global 全局变量。ngx 的各种 api 和内置变量就是在这里由 ngx_http_lua_inject_ngx_api() 进行注入,提供给 lua 脚本调用。

虚拟机创建完成后,继续调用 init_handle 函数,该函数将加载执行配置项 init_by_lua* 对应的 lua 代码文件。即 init_by_lua* lua 代码是加载在 master 虚拟机的。

nginx 分为 11 个执行阶段:


typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,   // 接收到完整的HTTP头部后处理的阶段
 
    NGX_HTTP_SERVER_REWRITE_PHASE,  // URI与location匹配前,修改URI的阶段,用于重定向
 
    NGX_HTTP_FIND_CONFIG_PHASE,     // 根据URI寻找匹配的location块配置项
    NGX_HTTP_REWRITE_PHASE,         // 上一阶段找到location块后再修改URI
    NGX_HTTP_POST_REWRITE_PHASE,    // 防止重写URL后导致的死循环
 
    NGX_HTTP_PREACCESS_PHASE,       // 下一阶段之前的准备
 
    NGX_HTTP_ACCESS_PHASE,          // 让HTTP模块判断是否允许这个请求进入Nginx服务器
    NGX_HTTP_POST_ACCESS_PHASE,     // 向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝
 
    NGX_HTTP_TRY_FILES_PHASE,       // 为访问静态文件资源而设置
    NGX_HTTP_CONTENT_PHASE,         // 处理HTTP请求内容的阶段,大部分HTTP模块介入这个阶段
 
    NGX_HTTP_LOG_PHASE              // 处理完请求后的日志记录阶段
} ngx_http_phases;

lua_nginx_module 模块在其中的 rewrite, access, content,log 阶段注册了 handler 函数。这几个阶段执行流程如下:

image

上图,除了 init_by_lua* 都是在 worker 进程内处理的,模块初始化阶段(ngx_http_lua_init),将这些阶段处理函数挂载到对应的阶段处理函数数组,以 content_by_lua 为例,大致流程如下:

ngx_http_lua_content_by_lua(设置 handle file 路径) -> ngx_http_lua_content_handle -> ngx_http_lua_content_handle_file(加载 lua 代码) -> ngx_http_lua_content_by_chunk -> ngx_http_lua_by_thread(在协程里面执行)

所有协程共享 woker 进程的 lua vm,每个外部请求都由一个 lua 协程处理,协程之间数据隔离,即不同请求间的数据隔离。

ngx_lua 协程

详见另一篇博客(ngx_lua 中协程的使用 )

@jinhailang
Copy link
Owner Author

jinhailang commented Jun 1, 2018

nginx 模块开发(实例)
https://my.oschina.net/u/2539854/blog/846628

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant