nginx中共享内存的使用详解
在nginx的进程模型下,类似流量统计、流量控制、数据共享、等需要多个工作进程共同配合完成任务,共享内存是一个重要的进程通讯的方案。本文介绍在nginx的代码中与共享内存相关的功能,包括ngx_shmem与ngx_slab的使用与注意事项,但不包括ngx_slab中实现的内存管理算法。
ngx_shmem的使用
ngx_shmem.c/h文件只是对mmap()/munmap()系统调用或者shmget()/shmdt()的一个很简单的封装。实现了ngx风格的基础库,可以申请和释放一段连续的共享内存空间。一般用于固定长度的共享数据使用,使用过程中数据长度固定不会伸缩。
typedef struct { u_char *addr; size_t size; ... } ngx_shm_t; ngx_int_t ngx_shm_alloc(ngx_shm_t *shm); void ngx_shm_free(ngx_shm_t *shm);
在ngxin中共享内存的使用流程,一般是由master进程创建,worker进程通过继承的方式获得内存指针。
关于ngx_shmem的使用,可以参考ngx_event_module_init()中部分片段,这部分代码在共享内存中创建了若干个变量,用于记录各个状态(accepted/reading/writing...)的请求数量,并在ngx_event_module中的几个关键事件入口对这几个变量进行加减统计操作。实现统计所有worker进程当前的请求状态。
shm.size = size; ngx_str_set(&shm.name, "nginx_shared_zone"); shm.log = cycle->log; if (ngx_shm_alloc(&shm) != NGX_OK) { return NGX_ERROR; } shared = shm.addr; ... ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl); ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl); ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl); ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl); ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl); ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl); ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);
关于这个功能的更多细节,可以查看代码中的NGX_STAT_STUB宏定义相关代码与ngx_http_stub_status_module。
ngx_slab的使用
ngx_shmem是一层极简的封装,实现了共享内存的基本功能。但我们程序中大部分的场景共享数据并不会一个固定大小的结构,而更多是像ngx_array、ngx_list、ngx_queue、ngx_rbtree这类大小可以变化的数据结构。
我们期望能有像ngx_pool_t一样可以动态申请释放空间一个内存池。ngx_slab正是一个这样的结构体,原理上与系统的malloc()有相识之处都是通过一系列算法实现对一段段内存片段的申请与释放。只不过ngx_slab操作的对象是基于ngx_shmem的共享内存。
先看一下ngx_slab的接口
typedef struct { ngx_shmtx_t mutex; ... void *data; /* 一般存放从pool中申请获得的根数据地址(pool中第一个申请的数据接口) */ void *addr; /* 使用ngx_shmem申请获得的共享内存基地址 */ } ngx_slab_pool_t; void ngx_slab_init(ngx_slab_pool_t *pool); void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size); void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size); void *ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size); void *ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size); void ngx_slab_free(ngx_slab_pool_t *pool, void *p); void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);
可以看到接口并不复杂,alloc与calloc的区别在于是否对申请获得的内存段清零,_locked结尾的接口表示操作的pool已经是获取到锁的。在ngx_slab_pool_t的结构体有一个ngx_shmtx_t的互斥锁用于同步多进程同时访问pool的并发场景。注意ngx_slab_alloc()会先获取锁、然后申请空间、最后释放锁。而ngx_slab_alloc_locked()则直接申请空间,认为程序已经在其他逻辑中获得锁了。
在nginx的开发中使用ngx_shmem一般需要遵循以下初始化流程:
- 模块在配置解析过程中调用ngx_shared_memory_add()接口,注册一段共享内存。提供共享内存大小与内存初始化的回调函数。
- 框架在ngx_init_cycle()中使用ngx_shmem申请内存,并初始化ngx_slab,然后回调模块注册的初始化函数
- 模块使用ngx_slab的申请/是否接口
在这个流程中,涉及到ngx_shared_memory_add()接口与对应的ngx_shm_zone_t结构体。
struct ngx_shm_zone_s { void *data; ngx_shm_t shm; ngx_shm_zone_init_pt init; void *tag; void *sync; ngx_uint_t noreuse; /* unsigned noreuse:1; */ }; ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag);
其中值得一提的是noreuse属性,这个属性控制了在nginx的reload过程中是否会重新申请共享内存。
由于关于ngx_init_cycle()函数较长,这个流程可以通过查找/* create shared memory */这个注释或者cycle->shared_memory这个对象查看相关代码。
关于ngx_slab更多细节的使用,建议可以参考ngx_http_limit_conn_module,这是通过共享内存实现连接数限制的模块,模块复杂度底,是一个很好的参考范例。
参考资料
深入理解Nginx(第2版) https://book.douban.com/subject/26745255/
ngx_http_limit_conn_module http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- 关于curl网站运维与开发的那些事
- 并查集Union-find及其在最小生成树中的应用
- go 语言的库文件放在哪里?如何通过nginx代理后还能正确获取远程地址
- 离线Tarjan算法-最近公共祖先问题
- Java文件上传下载实训
- 【网络编程系列】二:socket通信原理及实践
- textrank算法原理与提取关键词、自动提取摘要PYTHON
- 【网络编程系列】一:字节顺序的大端与小端表示法
- Linux下的make命令用法
- 增量数据丢失的原因分析(三)(r8笔记第91天)
- JS之浏览器对象BOM
- 超清晰的makefile解释、编写与示例
- 一个简单的sql审核案例 (r8笔记第90天)
- Linux wait() 和 waitpid()函数介绍
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 并发工具的使用
- js创建对象
- 前端笔试题(附答案)
- .Net Core微服务入门全纪录(一)——项目搭建
- JS中的call()方法和apply()方法和slice()用法总结
- 完整的url以及同源跨域处理
- .Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)
- .Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
- MTO变量缩放与统一决策空间
- css相关的几个点
- .Net Core微服务入门全纪录(四)——Ocelot-API网关(上)
- Apache下如何禁止指定目录运行PHP脚本
- .Net Core微服务入门全纪录(五)——Ocelot-API网关(下)
- 在Docker中运行PostgreSQL + pgAdmin 4
- .Net Core微服务入门全纪录(六)——EventBus-事件总线