nginx 进程通信--共享内存
共享内存是Linux下进程之间进行数据通信的最有效方式之一,而nginx就为我们提供了统一的操作接口来使用共享内存。
在nginx里,一块完整的内存以结构体ngx_shm_zone_s封装.其中包括是共享内存的名字(shm_zone[i].shm.name),大小(shm_zone[i].shm.size),标签(shm_zone[i].tag), ngx_shm_zone_init_pt init; (初始化共享内存时的回调函数)
一些共享内存的结构体:
struct ngx_shm_zone_s void *data; ngx_shm_t shm; ngx_shm_zone_init_pt init; void *tag; };
typedef struct { u_char *addr; size_t size; ngx_str_t name; //名字 ngx_log_t *log; ngx_uint_t exists; /* unsigned exists:1; */ } ngx_shm_t;
这些字段大都容易理解,只有tag字段需要解释一下,因为看上去它和name字段有点重复,而事实上,name字段主要用作共享内存的唯一标识,它能让nginx知道我想使用哪个共享内存,但它没法让nginx区分我到底是想新创建一个共享内存,还是使用那个已存在的旧的共享内存。举个例子,模块A创建了共享内存sa,模块A或另外一个模块B再以同样的名称sa去获取共享内存,那么此时nginx是返回模块A已创建的那个共享内存sa给模块A/模块B,还是直接以共享内存名重复提示模块A/模块B出错呢?不管nginx采用哪种做法都有另外一种情况出错,所以新增一个tag字段做冲突标识,该字段一般也就指向当前模块的ngx_module_t变量即可。这样在上面的例子中,通过tag字段的帮助,如果模块A/模块B再以同样的名称sa去获取模块A已创建的共享内存sa,模块A将获得它之前创建的共享内存的引用(因为模块A前后两次请求的tag相同),而模块B则将获得共享内存已做它用的错误提示(因为模块B请求的tag与之前模块A请求时的tag不同)。
当我们要使用一个共享内存时,总会在配置文件里加上该共享内存的相关配置信息,而nginx在进行配置解析的过程中,根据这些配置信息就会创建相应的共享内存,不过此时的创建只是代表共享内存的结构体变量的ngx_shm_zone_t的创建,具体实现在share_memory_add()中.nginx中所有共享内存都是以list链表的形式组织在全局变量cf->cycle->shared_memory下,在创建新的共享内存之前会先对该链表进行遍历查找以及冲突检测,对于已经存在且不存在冲突的共享内存可直接返回引用。
以nginx中ngx_http_limit_req_module模块为例://nginx限制链接
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
rate=1r/s 的意思是每个地址每秒只能请求一次,原理 burst=120 一共有120块令牌,并且每秒钟只新增1块令牌,120块令牌发完后 多出来的那些请求就会返回503
nginx在进行配置解析的时,遇到limit_req_zone配置项时,调用ngx_http_limit_req_zone(),而在该函数中继续调用share_memory_add()创建ngx_shm_zone_t结构体变量并加入到全局链表中:ngx_http_limit_req_zone() -> ngx_shared_memory_add() -> ngx_list_push()。
共享内存的真正创建是在配置文件全部解析完后,所有代表共享内存的结构体ngx_shm_zone_t变量以链表的形式挂接在全局变量cf->cycle->shared_memory下,nginx此时遍历该链表并逐个进行实际创建,即分配内存、管理机制(比如锁、slab)初始化等:
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { part = &cycle->shared_memory.part; shm_zone = part->elts; ............... for (i = 0; /* void */ ; i++) { if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) { goto failed; } if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) { goto failed; } if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) { goto failed; } }
其中函数ngx_shm_alloc()时共享内存的实际分配,针对当前系统可提供的接口,可以时mmap,shmget等,而ngx_init_zone_pool()函数是共享内存管理机制的初始化,共享内存涉及2个主题:(1)多进程共同使用共享内存块,必须考虑互斥问题 (2)nginx以性能著称,那么对于共享内存自然也有其独特的使用方式,虽然我们可以不用,但在这里也默认都会以这种slab的高效访问机制进行初始化.
回调函数shm_zone[i].init()是各个共享内存所特定的,根据使用方的自身需求不同而不同,这也是我们在使用共享内存时需特别注意的函数.
函数ngx_http_limit_req_init_zone()的第二个参数data表示‘旧’数据,在进行重新加载配置时(即nginx收到SIGHUP信号)该值将不为空,如果旧数据可继续使用,那么可直接返回NGX_OK;否则,需根据自身模块逻辑对共享内存的使用做相关初始化,比如ngx_http_limit_req_module模块,在第634、642行直接使用默认已初始化好的slab机制,进行内存的分配等。当函数ngx_http_limit_req_init_zone()正确执行结束,一个完整的共享内存就已创建并初始完成,接着要做的就是共享内存的使用,这即回到前面提到的两个主题:互斥与slab。
函数 |
含义 |
ngx_shmtx_create() |
创建 |
ngx_shmtx_destory() |
销毁 |
ngx_shmtx_trylock() |
尝试加锁(加锁失败则直接返回,不等待) |
ngx_shmtx_lock() |
加锁(持续等待,直到加锁成功) |
ngx_shmtx_unlock() |
解锁 |
ngx_shmtx_force_unlock() |
强制解锁(可对其它进程进行解锁) |
ngx_shmtx_wakeup() |
唤醒等待加锁进程(系统支持信号量的情况下才可用) |