nginx是一个多进程程序,那么不同的worker进程之间,如果需要共享数据,那么只能通过共享内存。那么下面我们来看一看,nginx中的共享内存是怎么使用的。
nginx的进程间的通讯方式,主要有两种,第一种是信号,那么之前我们在说如何管理nginx的过程中,已经比较详细的介绍过了,那么如果需要做数据的同步呢?那么只能通过共享内存,所谓共享内存,也就是说我们打开了一块内存,比如说10M,那么一整块0-10M之间,多个worker进程之间,可以同时的访问它,包括读取和写入。那么为了使用好这样一块共享内存,就会引入另外两个问题。第一个问题呢,就是锁,因为多个worker进程同时操作一块内存,一定会出现竞争关系,所以我们需要加锁,在nginx的锁中,在早期它还有基于信号量的锁,信号量是一种linux里比较久远的进程同步方式,它会导致你的进程进入了休眠状态,也就是发生了主动切换,而现在大多数操作系统版本中,nginx所使用的锁,都是自旋锁,而不会基于信号量,自旋锁,也就是说,当这个锁的条件没有满足,比如说这块内存现在被1号worker进程所使用,那么2号worker进程需要去获取锁的时候,只要1号进程没有释放锁,2号进程会一直在不停地去请求这个把锁,就好像,如果是基于信号量的早期的nginx锁,那么假设这把锁锁住了一扇门,如果worker进程1已经拿到了这把锁进到了屋里,那么worker进程2试图去拿锁,敲门,发现里面已经有人了,那么worker进程2就会就地休息,等待worker进程1从门里出来以后,通知它。而自选锁不一样,worker2进程2发现门里已经有worker进程1了,它就一直的持续的在敲门,所以,使用自旋锁,要求所有的nginx模块必须快速的使用共享内存,也就是快速的取得锁以后,快速的释放锁。一旦出现有第三方模块不遵守这样的规则,就可能导致出现死锁,或者说性能下降的问题。那么,有了这么一块共享内存,会引入第2个问题,因为一整块共享内存,往往是给许多对象同时使用的,如果我们在模块中手动去编写分配,把这些内存给到不同的对象,是非常繁琐的,所以,这个时候,我们使用了slab内存管理器。这个接下来我们会再说,那么nginx哪些模块使用了共享内存呢?我对官方的常用的nginx模块使用共享内存做了一个总结。那么使用共享内存,主要使用这两种数据结构,第一个是红黑树,比如我们想做限速或者流控等等场景时,我们是不同容忍在内存中做的,否则一个worker进程,对某一个用户,触发了流控,而其他worker进程还不知道,所以我们只能在共享内存中做,比如说,limit connection,比如说limit request(ngx_stream_limit_req_module),还有所有的http cache,做反向代理时用的,还有ssl,那么红黑树有一个特点,就是它的增删改查特别的快,当然也可以做遍历,所以这些模块都有一个特点,我需要做快速的插入和删除,比如我现在发现了一个客户端,我对它限速,那么限速如果达到了,我需要把这个客户端从我的限速数据结构容器中移出,都需要非常的快速。第二个常用的数据结构是单链表,也就是说我只要这些需要共享的元素都串起来就可以了。Ngx_http_upstream_zone_module或者Ngx_stream_upstream_zone_module,然后我们再来一个稍微复杂的例子,也就是ngx_http_lua_api,这个模块,实际上是openresty的核心模块,那么openresty在这个模块中定义了一个sdk,这个sdk叫lua_shared_dict,当这个指令出现的时候,它会分配一块共享内存,比如这个我们指定了10m,m就是我们的空间大小。那么这块共享内存会有一个名称,叫做dogs。接下来,我们在lua代码中,比如这一段content_by_lua_blocks,这对应这我们nginx收到了set这个url的时候需要做一些什么样的事情,我们首先从dogs共享内存中取出,然后设置了一个key value,然后向客户端返回,我已存储。然后在get请求中,我们把刚才存下的值取出来,返回给用户。那么在这一段代码中呢,我们同时使用了刚刚我们介绍中红黑树,以及链表。那么,这个lua_shared_dict dogs10m了。中间呢,使用红黑树来保存每一个key value,红黑树中的每一个节点就是它的key,它的value就是8,那么为什么我还需要一个链表呢,是因为这个10m是有限的,但我们的lua代码,涉及到了我们的应用业务代码,应用业务代码,很容易就超过了10m的限制,当我们超过10m的限制了呢,我们有很多种处理办法,比如让它写入失败,但是lua_shared_dict采用了另外一种处理方式,就是它用lru淘汰。也就是最早不用的节点,会被最早淘汰,当已经达到10m的最大值时。所以,这个lua_shared_dict中,它对共享内存的使用,同时满足了红黑树和链表。
共享内存是nginx跨多个worker通讯的最有效的手段,只要我们需要让一段业务逻辑,在多个worker进程中,同时生效,比如许多在集群的流控上,那么必须使用共享内存,而不能在每一个worker内存中去操作。