在说Nginx是如何通信的之前,我们先聊一下线程都是如何通信的吧。
单个进程中的多个线程
在每个进程的内存空间中都会有一块特殊的公共区域,通常称之为堆内存。所有的线程都可以访问这块内存。这就会引发一些问题。
假设A线程对内存中数据进行了修改,由于很累,A准备放下手上的活休息一下再工作,但是这时候B过来修改了这块的数据。等A再回来的时候,发现数据不见了。
- 由于堆内存中的数据可以被任何线程访问到,所以没有限制的情况下会存在被修改的风险。
为了解决这个问题,那A线程把数据放到别的线程访问不到的地方不就可以了么。所以操作系统会为每个线程分配属于它自己的一块内存空间,这就是栈内存。其他线程是无权访问的,这也是由操作系统保障的。
最常见的就是局部变量,他们都会被分配到栈内存中。其他的线程不仅不能访问,而且也对这块内存的数据毫无感知。
但是现实中,会有一个变量需要多个方法都能使用的情况,此时定义这个变量的位置就不能在方法里了,从(方法的)局部变量变为(类的)成员变量。这时,变量只能放到堆内存了。
那为了解决很多线程访问同一块数据的问题。
- 将数据拷贝多份,每个线程认领一份。
- 变量设置为只读。
- 互斥锁,想要修改数据就要获取这把锁。
前两种都不能做到 共享信息,就是多个线程对数据修改,第一个是修改了其他线程看不到,第二个是根本改不了。第三个似乎最友好了,可以改数据,改变的数据其他线程也可见。
好了,费了那么多的口舌,重新回到Nginx是如何通信这个话题上吧。
Nginx worker 进程协同工作的关键:共享内存
Nginx 是多进程单线程程序 ,多个worker要通信,有两种方式。
- 信号: 这个是与运行相关的
- 共享内存: 想要数据的同步只能靠共享内存了。
为了更好的使用共享内存,就会引入之前所提到的那些问题了。
当然不可避免的,Nginx 也就引进了锁。(为了防止竞争关系)
Nginx的锁为自旋锁,自旋锁的特点是,当A进程获取了这把锁,B进程没有获取到这把锁时,会不停的请求这把锁。(早期的锁,worker A已经获得锁,worker B 会休息,等待A释放锁后通知B)。
自旋锁是要求使用共享内存必须快速,所有的模块都必须遵守。快速使用锁,快速释放锁。一旦第三方模块不遵守,就会引发死锁,或者性能下降。
使用共享内存的模块
- Ngx_http_lua_api (openresty 核心)
- rbtree
- stream_limit_conn_module
- http_limit_conn_module
- http_limit_req_module
- http cache (file_cache proxy scgi wusgi fastcgi )
- ssl_module
- 单链表
- http_upstream_zone_module
- http_stream_zone_module
rbtree 十分高效。因为涉及快速的插入删除。
lua 对共享内存的使用
http {
lua_shared_dict dogs 10m;
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("jim",8)
ngx.say("stored")
}
}
location /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("jim"))
}
}
}
}
dogs 的 k,v 数据,这里同时使用了红黑树和链表。
共享内存中的k,v 都是由红黑树去保存的,链表是为了防止10m被写满。lua的share_dict 采用的lru淘汰,也就是当10M 满了之后,会将最早写入的优先数据淘汰(lru 淘汰)。
共享内存在Nginx的使用场景
一般需要多worker进程协同去配合的场景,例如upstream的负载均衡算法,limit限流等。
共享内存的分配:Slab 分配器
既然说到了共享内存,那来了解下内存是如何分配的。
为了将一大块内存切割为一小块来分配给红黑树上的节点使用,就需要slab分配器。
slab 分配器会将一整块的内存分为很多个页面(每个页面4K)。每个页面会被切分成各种大小的slot,如4 字节,8 字节,16 字节,32 字节 ,当需要放一块数据为28k的数据的话,他会将数据放到 32 字节 的slot里。
(KB 1KB=1024B 字节 )
这样就可以让申请的内存尽量避免碎片化。
且有自动的内存对齐的功能。
tengine 的 slab_stat 模块就可以看到,共享内存的使用情况。
lua_shared_dict dogs 10m;
server {
listen 80;
location = /slab_stat {
slab_stat;
}
location = /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim",8)
ngx.say("STORED")
}
}
location = /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("Jim"))
}
}
}
访问可以看到
curl 127.0.0.1:80/slab_stat
* shared memory: dogs
total: 10240(KB) free: 10168(KB) size: 4(KB)
pages: 10168(KB) start:00007F043FEFB000 end:00007F04408EB000
slot: 8(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 16(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 32(Bytes) total: 127 used: 1 reqs: 1 fails: 0
slot: 64(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 128(Bytes) total: 32 used: 1 reqs: 1 fails: 0
slot: 256(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 512(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 1024(Bytes) total: 0 used: 0 reqs: 0 fails: 0
slot: 2048(Bytes) total: 0 used: 0 reqs: 0 fails: 0
网友评论