nginx共享内存
来源:互联网 发布:淘宝助理登入验证 编辑:程序博客网 时间:2024/06/06 09:48
Nginx共享内存
1. 介绍
1.1. 介绍
nginx共享内存是利用mmap将内容存储在内存中以及自旋锁。当master启动的时候,根据相应的指令去初始化共享内存。利用共享内存实现一个轻量级的k/v系统。
2. 结构
2.1. 全局变量
ngx_cycle_s结构:
struct ngx_cycle_s {
void ****conf_ctx; //配置上下文数组(含所有模块)
ngx_pool_t *pool; //内存池
ngx_log_t *log; //日志
ngx_log_t new_log;
ngx_connection_t **files; //连接文件
ngx_connection_t *free_connections; //空闲连接
ngx_uint_t free_connection_n; //空闲连接个数
ngx_queue_t reusable_connections_queue; //再利用连接队列
ngx_array_t listening; //监听数组
ngx_array_t pathes; //路径数组
ngx_list_t open_files; //打开文件链表
ngx_list_t shared_memory; //共享内存链表
ngx_uint_t connection_n; //连接个数
ngx_uint_t files_n; //打开文件个数
ngx_connection_t *connections; //连接
ngx_event_t *read_events; //读事件
ngx_event_t *write_events; //写事件
ngx_cycle_t *old_cycle; //old cycle指针
ngx_str_t conf_file; //配置文件
ngx_str_t conf_param; //配置参数
ngx_str_t conf_prefix; //配置前缀
ngx_str_t prefix; //前缀
ngx_str_t lock_file; //锁文件
ngx_str_t hostname; //主机名
};
2.2. 共享内存结构
ngx_zone_s结构:
struct ngx_shm_zone_s {
void *data; //指向自定义数据结构,一般用来初始化时使用,可能指向本地地址
ngx_shm_t shm; //真正的共享内存
ngx_shm_zone_init_pt init; //初始化函数
void *tag; //标记
};
ngx_shm_t结构:
typedef struct {
u_char *addr; // 共享内存首地址
size_t size; // 共享内存大小
ngx_str_t name; // 共享内存名称
ngx_log_t * // 日志
ngx_uint_t exists;
} ngx_shm_t
回收共享内存:
void
ngx_shm_free(ngx_shm_t *shm)
{
if(munmap((void *) shm->addr, shm->size) == -1) {
ngx_log_error(NGX_LOG_ALERT,shm->log, ngx_errno,
"munmap(%p, %uz) failed", shm->addr, shm->size);
}
}
分配共享内存:
ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{
shm->addr =(u_char *) mmap(NULL, shm->size,
PROT_READ|PROT_WRITE,
MAP_ANON|MAP_SHARED, -1, 0);
if(shm->addr == MAP_FAILED) {
ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
"mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
returnNGX_ERROR;
}
return NGX_OK;
}
ngx_shm_zone_t * ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void*tag)
{
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_list_part_t *part;
part =&cf->cycle->shared_memory.part;
shm_zone = part->elts;
//先查找所有已经存在的共享内存,看看要创建的共享内存是否存在于这里面,如果存在的话就直接返回,否则
//创建一个新的共享内存结构体再返回
for (i = 0; /* void */ ; i++) {
if(i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
shm_zone = part->elts;
i = 0;
}
if(name->len != shm_zone[i].shm.name.len) {
continue;
}
if(ngx_strncmp(name->data, shm_zone[i].shm.name.data, name->len)
!= 0)
{
continue;
}
if(size && size != shm_zone[i].shm.size) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the size %uz of shared memory zone\"%V\" "
"conflictswith already declared size %uz",
size,&shm_zone[i].shm.name, shm_zone[i].shm.size);
return NULL;
}
if(tag != shm_zone[i].tag) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the sharedmemory zone \"%V\" is "
"alreadydeclared for a different use",
&shm_zone[i].shm.name);
return NULL;
}
//此共享内存已经存在,直接返回
return &shm_zone[i];
}
//插入一个新的共享内存结构体结点
shm_zone = ngx_list_push(&cf->cycle->shared_memory);
if(shm_zone == NULL) {
return NULL;
}
shm_zone->data = NULL;
shm_zone->shm.log = cf->cycle->log;
shm_zone->shm.size = size;
shm_zone->shm.name = *name;
shm_zone->shm.exists = 0;
shm_zone->init = NULL;
shm_zone->tag = tag;
returnshm_zone;
}
2.3. 自旋锁数据结构及实现
ngx_shmtx_t数据结构:
typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)
ngx_atomic_t *lock; //如果支持原子锁的话,那么使用它
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t *wait;
ngx_uint_t semaphore;
sem_t sem;
#endif
#else
ngx_fd_t fd; //不支持原子操作的话就使用文件锁来实现
u_char *name;
#endif
ngx_uint_t spin; //自旋锁
} ngx_shmtx_t;
创建锁:
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t*addr, u_char *name)
{
mtx->lock = &addr->lock; //其实就是直接将内存地址赋个mtx的lock域就完事了
if(mtx->spin == (ngx_uint_t) -1) { //如果等于-1是自旋锁
return NGX_OK;
}
mtx->spin = 2048;
returnNGX_OK;
}
尝试加锁(加锁失败则直接返回,不等待):
ngx_shmtx_trylock(ngx_shmtx_t *mtx){
ngx_err_t err;
err =ngx_trylock_fd(mtx->fd);
if (err== 0) {
return 1;
}
}
ngx_err_t
ngx_trylock_fd(ngx_fd_t fd)
{
structflock fl;
ngx_memzero(&fl, sizeof(struct flock));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if(fcntl(fd, F_SETLK, &fl) == -1) {
return ngx_errno;
}
return0;
}
解锁:
void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
ngx_err_t err;
err =ngx_unlock_fd(mtx->fd);
if (err== 0) {
return;
}
ngx_log_abort(err, ngx_unlock_fd_n " %s failed",mtx->name);
}
ngx_err_t
ngx_unlock_fd(ngx_fd_t fd)
{
structflock fl;
ngx_memzero(&fl, sizeof(struct flock));
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
if(fcntl(fd, F_SETLK, &fl) == -1) {
return ngx_errno;
}
return0;
}
spinlock的实现原理是?
a. 用户态尝试竞争一个共享资源. 如果竞争不到, 则不断尝试竞争. 但是不借助内核提供的mutex等变量机制. 因为涉及到内核,就意味这效率低下.
b. 要想在用户态实现竞争一个共享资源, 必须借助cpu提供的原子操作指令. 如果是SMP多cpu,还需要lock指令锁总线.
c. 为了避免在长时间竞争却一直得不到资源导致的不断尝试浪费cpu, 在每两次尝试之间间隔一段时间. 并且随着尝试次数的增加,间隔时间也增加. 间隔期间可以让cpu稍加休息(注意,绝不是让出cpu),这依赖于cpu提供pausse指令. (当然如果cpu没有提供pause也没关系,只是会很消耗电力资源)PAUSE指令提升了自旋等待循环(spin-wait loop)的性能。
d. 在等待相当长时间还是得不到锁之后,只好让出cpu. 但必须让出很小一会. 否则就不叫自旋锁了. 如何让出cpu,却有可以很快的回来? 内核提供了 sched_yield()函数
sched_yield()主要功能:简单的讲,可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序
// lock:一个整形变量的指针
// value:将lock设置新的值
// spin:自旋的次数.该值越大会尝试更多次获得锁. 然后才会转入让内核调度线程暂时让出cpu.
void ngx_spinlock(ngx_atomic_t *lock,ngx_atomic_int_t value, ngx_uint_t spin)
{
#if (NGX_HAVE_ATOMIC_OPS)
ngx_uint_t i, n;
for (;; ) {
//原子操作 尝试把lock的值设置成value,如果不为0就说明已经有其他进程获取了该锁
if(*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return; //获取锁成功
}
if(ngx_ncpu > 1) {
//spin=1024,每次shift一位
for (n = 1; n < spin; n <<= 1) {
//n=1,2,4,8,16,32,64,128 ...
//每次等待时间增加一倍
for (i = 0; i < n; i++) {
ngx_cpu_pause(); // 在空转的同时, 降低cpu功耗,提高效率
}
//等待一段时间再去尝试获取锁
if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
return;
}
}
}
// 尝试这么久了还没有得到锁, 让出cpu等待一下.
ngx_sched_yield();
}
#else
#if (NGX_THREADS)
#error ngx_spinlock() or ngx_atomic_cmp_set() arenot defined !
#endif
#endif
}
#if (NGX_HAVE_SCHED_YIELD)
#define ngx_sched_yield() sched_yield()
#else
#define ngx_sched_yield() usleep(1)
#endif
2.4. 内存分配器数据结构及实现
ngx_slab_pool_t结构:
typedef struct {
ngx_atomic_t lock; //mutex的锁
size_t min_size; //最小分配单元,一般是1个byte
size_t min_shift; // 最小分配单元对应的位移
ngx_slab_page_t *pages; //页数组
ngx_slab_page_t free; //空闲页链表
u_char *start; //可分配空间的起始地址
u_char *end; //内存块的结束地址
ngx_shmtx_t mutex;
u_char *log_ctx;
u_char zero;
void *data;
void *addr; //指向ngx_slab_pool_t的开头
} ngx_slab_pool_t;
ngx_slab_page_s结构:
struct ngx_slab_page_s {
uintptr_t slab; // 保存当前页的一些信息
ngx_slab_page_t *next;// 下一个
uintptr_t prev;// 上一个
};
slab实现(nginx会通过两种方式来管理内存):
1. 小于ngx_slab_max_size的都会通过一个slots数组来进行管理,然后每次进来的size都会根据它的shift来划分到对应的数组的位置。(下面就是数组的位置,size以及shift的对应关系,可以看到这里分配的大小都是向上对其的,也就是说你申请35字节那么会返还给你64字节)
slot size shift
0 <= 8 3
1 9 ~ 16 4
2 17 ~ 32 5
3 33 ~ 64 6
4 65 ~ 128 7
5 129 ~ 256 8
6 257 ~ 512 9
7 513 ~ 1024 10
8 1025 ~ 2047 11
2. 大于ngx_slab_max_size从page数组分配,因为这个值是页大小的一半,大于它则说明我们需要直接返回一个页或者几个页,此时就不需要象小的块那样需要一个复杂的管理块的东西,因此此时直接通过一个pages数组来进行管理,而数组元素的位置是紧跟着slot部分。
实现:
初始化slab池:
void ngx_slab_init(ngx_slab_pool_t*pool)
{
u_char *p;
size_t size;
ngx_int_t m;
ngx_uint_t i, n,pages;
ngx_slab_page_t *slots;
/* STUB*/
if(ngx_slab_max_size == 0) {
// 最大分配空间为页大小的一半
ngx_slab_max_size = ngx_pagesize / 2;
// 精确分配大小,8为一个字节的位数,sizeof(uintptr_t)为一个uintptr_t的字节,我们后面会根据这个size来判断使用不同的分配算法
ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
// 计算出此精确分配的移位数
for(n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
/* void */
}
}
/**/
pool->min_size = 1 << pool->min_shift;
// p 指向slot数组
p =(u_char *) pool + sizeof(ngx_slab_pool_t);
size =pool->end - p;
// 将开始的size个字节设置为0
ngx_slab_junk(p, size);
// 某一个大小范围内的页,放到一起,具有相同的移位数
slots =(ngx_slab_page_t *) p;
// 最大移位数,减去最小移位数,得到需要的slot数量
// 默认为8
n =ngx_pagesize_shift - pool->min_shift;
// 初始化各个slot
for (i= 0; i < n; i++) {
slots[i].slab = 0;
slots[i].next = &slots[i];
slots[i].prev = 0;
}
// 指向页数组
p += n* sizeof(ngx_slab_page_t);
// 计算出当前内存空间可以放下多少个页,此时的计算没有进行对齐,在后面会进行调整
pages =(ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));
ngx_memzero(p, pages * sizeof(ngx_slab_page_t));
pool->pages = (ngx_slab_page_t *) p;
pool->free.prev = 0;
pool->free.next = (ngx_slab_page_t *) p;
pool->pages->slab = pages;
pool->pages->next = &pool->free;
pool->pages->prev = (uintptr_t) &pool->free;
// 计算出对齐后的返回内存的地址
pool->start = (u_char *)
ngx_align_ptr((uintptr_t) p + pages * sizeof(ngx_slab_page_t),
ngx_pagesize);
// 用于判断我们对齐后的空间,是否需要进行调整
m =pages - (pool->end - pool->start) / ngx_pagesize;
// 说明之前是没有对齐过的,由于对齐之后,最后那一页,有可能不够一页,所以要去掉那一块
if (m> 0) {
pages -= m;
pool->pages->slab = pages;
}
pool->log_ctx = &pool->zero;
pool->zero = '\0';
}
在调用前已加锁,分配指定大小空间:
void * ngx_slab_alloc_locked(ngx_slab_pool_t*pool, size_t size)
{
size_t s;
uintptr_t p, n, m, mask,*bitmap;
ngx_uint_t i, slot, shift, map;
ngx_slab_page_t *page, *prev,*slots;
// 如果超出slab最大可分配大小,即大于2048,则我们需要计算出需要的page数,
// 然后从空闲页中分配出连续的几个可用页
if(size >= ngx_slab_max_size) {
// 计算需要的页数,然后分配指针页数
page = ngx_slab_alloc_pages(pool, (size + ngx_pagesize - 1)
>> ngx_pagesize_shift);
if(page) {
// 由返回page在页数组中的偏移量,计算出实际数组地址的偏移量
p = (page - pool->pages) << ngx_pagesize_shift;
// 计算出实际的数据地址
p += (uintptr_t) pool->start;
}else {
p = 0;
}
goto done;
}
// 如果小于2048,则启用slab分配算法进行分配
// 计算出此size的移位数以及此size对应的slot以及移位数
if(size > pool->min_size) {
shift = 1;
// 计算移位数
for(s = size - 1; s >>= 1; shift++) { /* void */ }
// 由移位数得到slot
slot = shift - pool->min_shift;
} else{
// 小于最小可分配大小的都放到一个slot里面
size = pool->min_size;
shift = pool->min_shift;
// 因为小于最小分配的,所以就放在第一个slot里面
slot = 0;
}
slots =(ngx_slab_page_t *) ((u_char *) pool + sizeof(ngx_slab_pool_t));
// 得到当前slot所占用的页
page =slots[slot].next;
// 找到一个可用空间
if(page->next != page) {
// 分配大小小于128字节时的算法,看不懂的童鞋可以先看等于128字节的情况
// 当分配空间小于128字节时,我们不可能用一个int来表示这些块的占用情况
// 此时,我们就需要几个int了,即一个bitmap数组
// 我们此时没有使用page->slab,而是使用页数据空间的开始几个int空间来表示了
// 看代码
if(shift < ngx_slab_exact_shift) {
do {
// 得到页数据部分
p = (page - pool->pages) << ngx_pagesize_shift;
// 页的开始几个int大小的空间来存放位图数据
bitmap = (uintptr_t *) (pool->start + p);
// 当前页,在当前size下可分成map*32个块
// 我们需要map个int来表示这些块空间
map = (1 << (ngx_pagesize_shift - shift))
/ (sizeof(uintptr_t)* 8);
for (n = 0; n < map; n++) {
if (bitmap[n] !=NGX_SLAB_BUSY) {
for (m = 1, i = 0; m; m<<= 1, i++) {
if ((bitmap[n]& m)) {
// 当前位表示的块已被使用了
continue;
}
// 设置已占用
bitmap[n] |=m;
i = ((n *sizeof(uintptr_t) * 8 ) << shift)
+ (i <<shift);
// 如果当前bitmap所表示的空间已都被占用,就查找下一个bitmap
if (bitmap[n] ==NGX_SLAB_BUSY) {
for (n = n + 1;n < map; n++) {
// 找到下一个还剩下空间的bitmap
if(bitmap[n] != NGX_SLAB_BUSY) {
p =(uintptr_t) bitmap + i;
gotodone;
}
}
// 剩下所有的bitmap都被占用了,表明当前的页已完全被使用了,把当前页从链表中删除
prev =(ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next =page->next;
page->next->prev = page->prev;
page->next =NULL;
// 小内存分配
page->prev =NGX_SLAB_SMALL;
}
p = (uintptr_t)bitmap + i;
goto done;
}
}
}
page = page->next;
} while (page);
}else if (shift == ngx_slab_exact_shift) {
// 如果分配大小正好是128字节,则一页可以分成32个块,我们可以用一个int来表示这些个块的使用情况
// 这里我们使用page->slab来表示这些块的使用情况,当所有块被占用后,该值就变成了0xffffffff,即NGX_SLAB_BUSY
// 表示该块都被占用了
do {
// 当前页可用
if (page->slab != NGX_SLAB_BUSY) {
for (m = 1, i = 0; m; m<<= 1, i++) {
// 如果当前位被使用了,就继续查找下一块
if ((page->slab& m)) {
continue;
}
// 设置当前为已被使用
page->slab |=m;
// 最后一块也被使用了,就表示此页已使用完
if (page->slab ==NGX_SLAB_BUSY) {
// 将当前页从链表中移除
prev =(ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next =page->next;
page->next->prev = page->prev;
page->next =NULL;
// 标识使用类型,精确
page->prev =NGX_SLAB_EXACT;
}
p = (page -pool->pages) << ngx_pagesize_shift;
p += i <<shift;
p += (uintptr_t)pool->start;
goto done;
}
}
// 查找下一页
page = page->next;
} while (page);
}else { /* shift > ngx_slab_exact_shift */
// 当需要分配的空间大于128时,我们可以用一个int的位来表示这些空间
//所以我们依然采用跟等于128时类似的情况,用page->slab来表示
// 但由于 大于128的情况比较多,移位数分别为8、9、10、11这些情况
// 对于一个页,我们如何来知道这个页的分配大小呢?
// 而我们知道,最小我们只需要使用16位即可表示这些空间了,即分配大小为256~512时
// 那么我们采用高16位来表示这些空间的占用情况
// 而最低位,我们也利用起来,表示此页的分配大小,即保存移位数
// 比如我们分配256,当分配第一个空间时,此时的page->slab位图情况是:0x0001008
// 那分配下一空间就是0x0003008了,当为0xffff008时,就分配完了
// 看代码
// page->slab & NGX_SLAB_SHIFT_MASK 即得到最低一位的值,其实就是当前页的分配大小的移位数
// ngx_pagesize_shift减掉后,就是在一页中标记这些块所需要的移位数,也就是块数对应的移位数
n = ngx_pagesize_shift - (page->slab & NGX_SLAB_SHIFT_MASK);
// 得到一个页面所能放下的块数
n = 1 << n;
// 得到表示这些块数都用完的bitmap,用现在是低16位的
n = ((uintptr_t) 1 << n) - 1;
// 将低16位转换成高16位,因为我们是用高16位来表示空间地址的占用情况的
mask = n << NGX_SLAB_MAP_SHIFT;
do {
// 判断高16位是否全被占用了
if ((page->slab & NGX_SLAB_MAP_MASK) != mask) {
// NGX_SLAB_MAP_SHIFT 为移位偏移, 得到0x10000
for (m = (uintptr_t) 1<< NGX_SLAB_MAP_SHIFT, i = 0;
m & mask;
m <<= 1,i++)
{
// 当前块是否被占用
if ((page->slab& m)) {
continue;
}
// 将当前位设置成1
page->slab |=m;
// 当前页是否完全被占用完
if ((page->slab& NGX_SLAB_MAP_MASK) == mask) {
prev =(ngx_slab_page_t *)
(page->prev & ~NGX_SLAB_PAGE_MASK);
prev->next =page->next;
page->next->prev = page->prev;
page->next =NULL;
page->prev =NGX_SLAB_BIG;
}
p = (page -pool->pages) << ngx_pagesize_shift;
p += i <<shift;
p += (uintptr_t)pool->start;
goto done;
}
}
page = page->next;
} while (page);
}
}
// 如果当前slab对应的page中没有空间可分配了,则重新从空闲page中分配一个页
page =ngx_slab_alloc_pages(pool, 1);
if(page) {
if(shift < ngx_slab_exact_shift) {
// 小于128时
p = (page - pool->pages) << ngx_pagesize_shift;
bitmap = (uintptr_t *) (pool->start + p);
// 需要的空间大小
s = 1 << shift;
n = (1 << (ngx_pagesize_shift - shift)) / 8 / s;
if (n == 0) {
n = 1;
}
bitmap[0] = (2 << n) - 1;
// 需要使用的
map = (1 << (ngx_pagesize_shift - shift)) / (sizeof(uintptr_t) *8);
for (i = 1; i < map; i++) {
bitmap[i] = 0;
}
page->slab = shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
slots[slot].next = page;
p = ((page - pool->pages) << ngx_pagesize_shift) + s * n;
p += (uintptr_t) pool->start;
goto done;
}else if (shift == ngx_slab_exact_shift) {
// 第一块空间被占用
page->slab = 1;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
slots[slot].next = page;
p = (page - pool->pages) << ngx_pagesize_shift;
p += (uintptr_t) pool->start;
goto done;
}else { /* shift > ngx_slab_exact_shift */
// 低位表示存放数据的大小
page->slab = ((uintptr_t) 1 <<NGX_SLAB_MAP_SHIFT) | shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
slots[slot].next = page;
p = (page - pool->pages) << ngx_pagesize_shift;
p += (uintptr_t) pool->start;
goto done;
}
}
p =0;
done:
return(void *) p;
}
分配指定个数的页:
static ngx_slab_page_t *
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_tpages)
{
ngx_slab_page_t *page, *p;
//开始遍历free指针。
for(page = pool->free.next; page != &pool->free; page = page->next) {
//判断slab值,也就是看还能分配多少page。
if(page->slab >= pages) {
//如果是远远大于pages,则直接分配对应的page,然后将当前的要分配的page从free中去除。
if (page->slab > pages) {
//更新page的属性。
page[pages].slab = page->slab - pages;
page[pages].next = page->next;
page[pages].prev = page->prev;
//从free中去除
p = (ngx_slab_page_t *) page->prev;
p->next = &page[pages];
page->next->prev = (uintptr_t) &page[pages];
} else {
p = (ngx_slab_page_t *) page->prev;
p->next = page->next;
page->next->prev = page->prev;
}
//设置slab
page->slab = pages | NGX_SLAB_PAGE_START;
page->next = NULL;
page->prev = NGX_SLAB_PAGE;
//如果只是分配一个page,则直接返回
if (--pages == 0) {
return page;
}
//否则需要将已经分配的页的slab全部设置为busy。
for (p = page + 1; pages; pages--) {
p->slab = NGX_SLAB_PAGE_BUSY;
p->next = NULL;
p->prev = NGX_SLAB_PAGE;
p++;
}
return page;
}
}
ngx_slab_error(pool, NGX_LOG_CRIT, "ngx_slab_alloc() failed: nomemory");
returnNULL;
}
3. 流程图
3.1. 共享内存初始化
3.2. 添加k/v
3.3. 获取k/v
3.4. 过期回收k/v
3.5. 内部流程
ngx_shard_memory_add流程:
ngx_init_zone_pool流程:
nginx内部哪些模块在使用:
3.6. 过期策略
1. 主动过期(定时器)
a. Nginx单独起一个进程添加定时器事件每隔几秒进行主动删除过期的key
b. Nginx单独起一个进程添加读事件支持redis协议,用redis-cli可以查看管理
https://github.com/lidaohang/ngx_shm_dict_manager(需要打patch包支持proc)
2. 被动过期(请求时过期)
a.每次set/get 的时候进行清理过期的key
4. 接口及功能
4.1. 接口结构及功能
class CNgxDictInterface {
public:
CNgxDictInterface():s_zone_name(""),zone_t(NULL){};
virtual~CNgxDictInterface(){};
/**
* key 字典的key.
* value 字典的value
* exptime 过期时间(秒)
* zoneName 共享内存名称
**/
//添加k/v
intGet(const std::string &key,const std::string &value,uint32_t*exptime=0,const
std::string&zoneName="");
//设置k/v
intSet(const std::string& key,const std::string& value,uint32_texptime=0,const
std::string& zoneName="");
intSet(shm_str_t* key, shm_str_t* value,uint32_t exptime=0,const std::string&
zoneName="");
//删除k/v
intDel(const std::string& key,const std::string& zoneName="");
intDel(shm_str_t* key,const std::string& zoneName="");
//key增加n 计数
intIncr(const std::string& key,int count,int64_t *res,uint32_t exptime=0,const
std::string& zoneName="");
intIncr(shm_str_t* shm_key,int count,int64_t *res,uint32_t exptime=0,const
std::string& zoneName="");
//清空
intFlushAll(const std::string& zoneName="");
std::stringGetZoneName();
intSetZoneName(const std::string& zoneName);
std::stringGetError(int ret);
private:
std::strings_zone_name;
void*zone_t;
};
4.2. 接口内部结构
5. 配置
5.1. nginx配置
plugin_conf_path name=test so_path=libshm_dict_view.so so_conf=so_conf;
ah_shm_dict_zone zone=lands max_size=2048m;
ah_shm_dict_zone zone=click max_size=2048m;
server{
listen 8011;
server_name localhost;
location/ {
set$plugin_name "test";
ah_shm_dict_name name=lands|click;
ah_ngx_handler;
}
}
server {
listen 8012;
server_name localhost;
location / {
ah_shm_dict_view;
}
}
5.2. 配置功能及测试
1. 初始化共享内存
ah_shm_zone zone=test max_size=10m;
ah_shm_zone zone=test1 max_size=10m;
ah_shm_zone zone=test2 max_size=10m;
zone共享内存名称
max_size共享内存大小
2. 模块使用共享内存
shm_zone_name test|test1;
a.使用的共享内存名称
b.必须跟初始化共享内存的名称对应
c.如果需要使用多块共享内存配置多个即可
3. libshm_dict_view.so用于查询共享内存信息(必须结合ah_shm_zone,plugin_conf_path模块)
http://127.0.0.1:8011/get?zone=lands&key=abc
http://127.0.0.1:8011/set?zone=lands&key=abc&value=123&exptime=100
http://127.0.0.1:8011/del?zone=lands&key=abc
http://127.0.0.1:8011/incr?zone=lands&key=abc&count=1
4. ah_shm_dict_view 用于查询共享内存信息(必须结合ah_shm_zone模块)
http://127.0.0.1:8011/get?zone=lands&key=abc
http://127.0.0.1:8011/set?zone=lands&key=abc&value=123&exptime=100
http://127.0.0.1:8011/del?zone=lands&key=abc
http://127.0.0.1:8011/incr?zone=lands&key=abc&count=1
6. 性能测试
6.1. 单核和多核小数据读写
单核CPU小数据读写
结果
ab -c 500 -n 100000
"http://127.0.0.1:8011/set?zone=lands&key=440932&value=440932&exptime=441032“
单核CPU 写入QPS:17000左右
ab –c 500 -n 100000
"http://127.0.0.1:8011/get?zone=lands&key=440932“
单核CPU 读入QPS:19000左右
6.2. 单核和多核20kb数据读写
单核CPU 20kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_20k?zone=lands&key=68146&exptime=1000
单核CPU 写入20k QPS:9000左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_20k
单核CPU 读入20K QPS:10000左右
30核CPU 20kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_20k?zone=lands&key=68146&exptime=1000
30核CPU写入20k QPS:13000左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_20k
30核CPU读入20KQPS:14000左右
6.3. 单核和多核44kb数据读写
单核CPU 44kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_44k?zone=lands&key=68146&exptime=1000
单核CPU 写入44k QPS:6100左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_44k
单核CPU 读入44K QPS:6400左右
30核CPU 44kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_44k?zone=lands&key=68146&exptime=1000
30核CPU写入44k QPS:1000左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_44k
30核CPU读入44K QPS:9000左右
6.4. 单核和多核100kb数据读写
单核CPU 100kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_100k?zone=lands&key=68146&exptime=1000
单核CPU 写入100k QPS:3100左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_100k
单核CPU 读入100K QPS:3700左右
30核CPU 100kb数据读写
结果
ab -c 500 -n 100000
http://127.0.0.1:8011/set_100k?zone=lands&key=68146&exptime=1000
30核CPU写入100k QPS:7100左右
ab -c 500 -n 100000
http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_100k
30核CPU读入100K QPS:7200左右
7. 其它
7.1. 红黑树ngx_rbtree_t
1. 介绍
ngx_rbtree是一种使用红黑树实现的关联容器,容器中的元素都是有序的,支持快速索引,插入,删除操作,也支持范围查询,遍历操作,应用非常广泛。
struct ngx_rbtree_s {
ngx_rbtree_node_t *root;//根节点
ngx_rbtree_node_t *sentinel;//设置树的哨兵节点
ngx_rbtree_insert_pt insert;//插入方法的函数指针
};
struct ngx_rbtree_node_s {
ngx_rbtree_key_t key;//无符号的键值
ngx_rbtree_node_t *left;//左子节点
ngx_rbtree_node_t *right;//右子节点
ngx_rbtree_node_t *parent;//父节点
u_char color;//节点颜色,0表示黑色,1表示红色
u_char data;//数据
};
2. 初始化 ngx_rbtree_init
#define ngx_rbtree_init(tree, s, i) \
ngx_rbtree_sentinel_init(s); \
(tree)->root = s; \
(tree)->sentinel = s; \
(tree)->insert = i
3. 左旋 ngx_rbtree_left_rotate 和 右旋 ngx_rbtree_right_rotate
4. 插入 ngx_rbtree_insert
5. 删除ngx_rbtree_delete
7.2. 双向链表ngx_queue_t
1. 介绍
ngx_queue作为顺序容器链表,它优势在于其可以高效地执行插入、删除、合并操作,在插入删除的过程中,只需要修改指针指向,而不需要拷贝数据,因此,对于频繁修改的容器很适合。此外,相对于STL list,它还具有以下特点:
1. 自身实现了排序功能
2. 轻量级,不负责内存的分配
3. 自身支持两个链表的合并
结构
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
2. 初始化ngx_queue_init
3. 判断链表容器是否为空ngx_queue_empty
4. 头部插入ngx_queue_insert_head
5. 尾部插入ngx_queue_insert_tail
6. 链表删除ngx_queue_remove
7. 链表拆分ngx_queue_split
8. 链表合并ngx_queue_add
9. 链表中心元素ngx_queue_middle
10. .链表排序ngx_queue_sort
11. 根据ngx_queue_t 找到链表元素
- nginx共享内存管理
- nginx共享内存
- nginx之共享内存
- linux 共享内存与nginx共享内存
- nginx共享内存:共享内存的实现
- nginx+redis+lua 共享内存
- nginx--共享内存使用详解
- 2013.3.26 nginx 共享内存学习
- nginx - 共享内存与锁的实现
- Nginx + Lua + 共享内存实现动态查询(简单例子)
- Nginx + Lua + 共享内存实现动态查询(简单例子)
- Nginx + Lua + 共享内存实现动态查询(简单例子)
- Nginx + Lua + 共享内存实现动态查询(简单例子)
- 【共享内存】共享内存
- Nginx内存管理及数据结构浅析–共享内存的实现
- Nginx内存管理及数据结构浅析–共享内存的实现
- 共享内存
- 共享内存
- Python--ZODB
- Android bitmap OutOfMemory 避免措施
- 1.GetModuleHandle 获取一个特定的应用程序或动态链接库的模块句柄
- 好贴收藏-cygwin下安装包
- log
- nginx共享内存
- 【深度探索C++对象模型读书笔记】【第7章】站在对象模型的尖端
- struts2接收参数的几种形式
- STM32串口悬空导致CPU持续进入中断函数
- C#中的线程 -- 线程入门
- poj_1062
- 使用backtrace和backtrace_symbols打印函数调用链
- 文章标题
- SSD 之Trim指令