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 写入QPS17000左右

ab –c 500 -n 100000

"http://127.0.0.1:8011/get?zone=lands&key=440932“

 

单核CPU 读入QPS19000左右

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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 QPS9000左右

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_20k

 

单核CPU 读入20K QPS10000左右

 

 

30核CPU 20kb数据读写

结果

ab -c 500 -n 100000

http://127.0.0.1:8011/set_20k?zone=lands&key=68146&exptime=1000

 

30CPU写入20k QPS13000左右

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_20k

 

30CPU读入20KQPS14000左右

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 QPS6100左右

 

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_44k

 

单核CPU 读入44K QPS6400左右

 

 

 

30核CPU 44kb数据读写

结果

ab -c 500 -n 100000

http://127.0.0.1:8011/set_44k?zone=lands&key=68146&exptime=1000

 

30CPU写入44k QPS1000左右

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_44k

 

30CPU读入44K QPS9000左右

 

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 QPS3100左右

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_100k

 

单核CPU 读入100K QPS3700左右

 

 

30核CPU 100kb数据读写

结果

ab -c 500 -n 100000

http://127.0.0.1:8011/set_100k?zone=lands&key=68146&exptime=1000

 

30CPU写入100k QPS7100左右

ab -c 500 -n 100000

http://10.168.100.187:8011/get?zone=lands&key=68146_shm_dict_set_string_100k

 

30CPU读入100K QPS7200左右

 

 

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 找到链表元素

0 0
原创粉丝点击