题外话
有幸认识了一位做nginx开源的大咖,加对方微信,尽然很爽快地同意了。闲聊几句关于写文章,他说,现在的nginx的文章网上已经非常多了,完全不像十年前他开始学习nginx时,几乎是一片空白,所有东西都需要自己一行一行地去看代码自己去理解和领悟。而且,写这些文章也不用指望有太多的点击量,更多是作为对自己学习的一个记录就好。这一点和我的初衷是一致的。写文章更多是记录和帮助自己更好地去理解问题。如果恰好也能对别人有所帮助也算是意外的惊喜了。很多时候自己觉得都明白了,真的开始写文章才发现还有很多地方不清楚。而且,即使是真的理解了怎么样更好地用文字清晰简洁地表述出来也不是一件容易的事情。
另外,谈话的过程中,深感他的简单,专注,平易和大气。这也是接触的几位从事开源开发工程师身上一个普遍的特质。向他们致敬。甚至不敢说向他们学习,因为学习意味着改变,改变是特别痛苦和困难的事情。认识自己都很难,更何况改变。。。
有些跑题,言归正传。
一 简介
Nginx采用多进程加IO多路复用的方式来处理并发请求。进程之间采用信号量,共享内存等方式进行信息共享和同步。采用共享内存方式在多进程之间进行数据共享,必然需要进程之间采用同步机制进行同步。
Nginx中最有名的同步锁就是ngx_accept_mutex。Nginx用它来解决多进程并发处理的“网络惊群”问题。只有获得ngx_accept_mutex的进程才有资格处理当前的新的连接。这样避免了新的网络连接会唤醒所有的工作进程,竞争处理当前新的连接。下面我们通过分析Nginx中ngx_accept_mutex的实现和使用,来理解Nginx中的同步机制。
二 原理
Nginx 通过ngx_shmtx_t变量类型自身实现了进程数据同步机制。根据如下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;
在有原子操作支持的情况下,如果同时有信号量的支持,可以加入信号量来减少加锁的开销。其原理就是在获取锁时,如果等待时间很长,在没有信号量支持的情况下,进程只能循环检查是否满足条件,这时候对cpu的消耗会比较大。引入了POSIX信号量的支持后,如果等待时候过长,进程可以通过信号量进行睡眠,让出cpu。等锁被别的进程释放时再进行唤起。这样可以达到节省cpu的目的。
文件锁的实现方式相对简单,通过对同一文件调用fctnl的不同操作,依靠fcntl函数本身来实现同步操作。下面我们着重分析支持原子操作的情况下,Nginx的同步机制的实现原理。
三代码解读
初始化
通过原子操作来实现同步需要共享内存的支持。每个进程拥有的ngx_shmtx_t结构的成员lock需要指向同一段内存区域。
变量ngx_accept_mutex对应的成员lock初始化代码位于函数ngx_event_module_init中。在该函数中,首先通过ngx_shm_alloc