UNP第三十章。

a) 迭代型服务器(-)

已经不具备广泛实用性,仅在各个范式的比较中起基准作用

b) 并发性服务器(子进程)

主程序进行accept等待,为每一个客户fork一个子线程进行处理。

客户数目的限制是操作系统对运行服务器程序的用户ID能同时拥有子进程数的限制。面对当前的web站点每天上百万的请求,采用多主机平摊负载。(TCPv3讨论采用DNS轮询策略)。

性能方面,由于现场fork的开销导致性能较为低下。

c) 预先派生子进程(进程池)

必须预估会有多少并发客户访问,多余的会被忽略,因此当剩余空闲进程较多时要收回,较少时要额外分配,代价过高。

i. 子进程分别accept并不进行上锁保护:

预先派生进程池,当来客户连接时由子进程进行竞争,会引起”accept惊群现象,即所有阻塞在accept的子进程都会被唤醒来竞争,但只有一个进程会返回并取得连接,其他子进程则继续阻塞等待,这种太多的子进程被唤醒会导致性能受损。

Btw:让子进程在accept(实体)上竞争的效果要远好于select上,在select上会引发select冲突。

ii. 子进程分别accept进行上锁保护

此种方式让所有进程阻塞在锁的获取而不是accept套接口上,这样就会避免当连接到来时的accept的惊群作用。

上锁方式有两种,使用文件上锁,利用fcntl函数(SETLKGETLK),另外一种采用线程上锁,需要将pthread_mutex_tmmap映射到共享的内存区,然后设置pthread_mutexattr_tPTHREAD_PROCESS_SHARED即可。后者比较方便并且不涉及文件操作,性能上会提升不少。

iii. 主线程等待并进行描述字分配。

这种技术导致父进程必须跟踪子进程的状态并采用unix域或者pipe进行描述符传送,UNP上采用socketpair()函数。如果为了使得各个子进程处理平衡,还得涉及算法平衡各个子进程的负载均衡,但是通常这样做没有必要,对于多个空闲子进程而言,谁处理连接并没有效率影响。

并且描述符的传递的效率要远低于采用锁的机制,除了父进程传递描述字,子进程处理完后还得传递状态。

d) 并发性服务器(线程)

一般线程操作会比进程更快一点,因此在支持线程的系统上采用线程是较好的方式。简单的现场生成新线程的范式也要快于进程池的版本(SolarisDigital Unix上,根据UNP的描述)

e) 并发性服务器(线程池)

i. 每个线程各自accept

同样预先分配线程也会快于现场生成新线程,并且使用互斥锁将线程阻塞在锁上,此种方式的各个线程的负载均衡性是由线程调度算法导致,会在互斥锁上轮询所有线程。

此种方式的速度是所有范式中最快的。

ii. 主线程accept并分配描述符字给子线程

实际上采用的是一个描述符字数组,主线程accept一个就往队列中添加一个,子线程取出一个就从队列中删除一个。对队列的添加和删除操作进行加锁保护。

速度慢于上面各线程分别accept的版本。

转载请注明来源:Leoncom-《Unix下常见客户服务器范式比较》
Trackback

only 1 comment untill now

  1. mark

Add your comment now