高并发系统设计
注:本文大多数观点和代码都是从网上或者开源代码中抄来的,为了疏理和组织这片文章,作者也费了不少心血,为了表示对我劳动的尊重,请转载时注明作者和出处。
一、 引子
最近失业在家,闲来无事。通过网上查找资料和查看开源代码,研究了一下互联网高并发系统的一些设计。这里主要从服务器内部设计和整个系统设计两个方面讨论,更多的是从互联网大型网站设计方面考虑,高性能计算之类系统没有研究过。
二、 服务器内部设计
服务器设计涉及Socket的阻塞/非阻塞,操作系统IO的同步和异步(之前被人问到过两次。第一次让我说说知道的网络模型,我说ISO模型和TCP/IP模型,结果被鄙视了。最后人说了解linux epoll吗?不了解呀!汉,回去查资料才知道是这回事。第二次让我说说知道线程模型,汉!这个名词感觉没有听说过,线程?模型?半同步/半异步,领导者/跟随者知道吗。再汉,我知道同步/异步,还有半同步/半异步?啥呀?领导者/跟随者,我现在没有领导。回去一顿恶补,原来是ACE框架里边经常有这样的提法,Reactor属于同步/半同步,PREACTOR属于领导者/跟随者模式。瀑布汗。小插曲一段,这些不懂没关系,下边我慢慢分解),事件分离器,线程池等。内部设计希望通过各个模块的给出一个简单设计,经过您的进一步的组合和打磨,就可以实现一个基本的高并发服务器。
1. Java高并发服务器
Java设计高并发服务器相对比较简单。直接是用ServerSocket或者Channel+selector实现。前者属于同步IO设计,后者采用了模拟的异步IO。为什么说模拟的异步IO呢?记得网上看到一篇文章分析了java的selector。在windows上通过建立一个127.0.0.1到127.0.0.1的连接实现IO的异步通知。在linux上通过建立一个管道实现IO的异步通知。考虑到高并并发系统的要求和java上边的异步IO的限制(通常操作系统同时打开的文件数是有限制的)和效率问题,java的高并发服务器设计不做展开深入的分析,可以参考C高并发服务器的分析做同样的设计。
2. C高并发服务器设计
1) 基本概念
Ø 阻塞和非阻塞socket
所谓阻塞Socket,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。所谓非阻塞Socket,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。一个比较有意思的问题是accept的Socket是阻塞的还是非阻塞的呢?下边是MSDN上边的一段话:The accept function extracts thefirst connection on the queue of pending connections on socket s. It thencreates and returns a handle to the new socket. The newly created socket is thesocket that will handle the actual connection; it has the same properties assocket s, including the asynchronous events registered with the WSAAsyncSelector WSAEventSelect functions.
Ø 同步/异步IO
有两种类型的文件IO同步:同步文件IO和异步文件IO。异步文件IO也就是重叠IO。
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。
同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。高并发系统通常采用异步IO方式提高系统性能。
Ø 事件分离器
事件分离器的概念是针对异步IO来说的。在同步IO的情况下,执行操作等待返回结果,不要事件分离器。异步IO的时候,发送请求后,结果是通过事件通知的。这是产生了事件分离器的需求。事件分离器主要任务是管理和分离不同文件描述符上的所发生的事件,让后通知相应的事件,派发相应的动作。下边是lighthttpd事件分离器定义:
/**
* fd-event handler for select(), poll() andrt-signals on Linux 2.4
*
*/
typedef struct fdevents {
fdevent_handler_t type;
fdnode **fdarray;
size_t maxfds;
#ifdef USE_LINUX_SIGIO
int in_sigio;
int signum;
sigset_t sigset;
siginfo_t siginfo;
bitset *sigbset;
#endif
#ifdef USE_LINUX_EPOLL
int epoll_fd;
struct epoll_event *epoll_events;
#endif
#ifdef USE_POLL
struct pollfd *pollfds;
size_t size;
size_t used;
buffer_int unused;
#endif
#ifdef USE_SELECT
fd_set select_read;
fd_set select_write;
fd_set select_error;
fd_set select_set_read;
fd_set select_set_write;
fd_set select_set_error;
int select_max_fd;
#endif
#ifdef USE_SOLARIS_DEVPOLL
int devpoll_fd;
struct pollfd *devpollfds;
#endif
#ifdef USE_FREEBSD_KQUEUE
int kq_fd;
struct kevent *kq_results;
bitset *kq_bevents;
#endif
#ifdef USE_SOLARIS_PORT
int port_fd;
#endif
int (*reset)(struct fdevents *ev);
void (*free)(struct fdevents *ev);
int (*event_add)(struct fdevents *ev, int fde_ndx, int fd,int events);
int (*event_del)(struct fdevents *ev, int fde_ndx, int fd);
int (*event_get_revent)(struct fdevents *ev, size_t ndx);
int (*event_get_fd)(struct fdevents *ev, size_t ndx);
int (*event_next_fdndx)(struct fdevents *ev, int ndx);
int (*poll)(struct fdevents *ev, int timeout_ms);
int (*fcntl_set)(struct fdevents *ev, int fd);
} fdevents;
fdevents *fdevent_init(size_tmaxfds, fdevent_handler_t type);
int fdevent_reset(fdevents*ev);
void fdevent_free(fdevents*ev);
int fdevent_event_add(fdevents*ev, int *fde_ndx, int fd, int events);
int fdevent_event_del(fdevents*ev, int *fde_ndx, int fd);
intfdevent_event_get_revent(fdevents *ev, size_t ndx);
intfdevent_event_get_fd(fdevents *ev, size_t ndx);
fdevent_handlerfdevent_get_handler(fdevents *ev, int fd);
void *fdevent_get_context(fdevents *ev, int fd);
int fdevent_event_next_fdndx(fdevents*ev, int ndx);
int fdevent_poll(fdevents *ev,int timeout_ms);
int fdevent_register(fdevents*ev, int fd, fdevent_handler handler, void *ctx);
int fdevent_unregister(fdevents*ev, int fd);
int fdevent_fcntl_set(fdevents*ev, int fd);
intfdevent_select_init(fdevents *ev);
int fdevent_poll_init(fdevents*ev);
intfdevent_linux_rtsig_init(fdevents *ev);
intfdevent_linux_sysepoll_init(fdevents *ev);
intfdevent_solaris_devpoll_init(fdevents *ev);
intfdevent_freebsd_kqueue_init(fdevents *ev);
具体系统的事件操作通过:
fdevent_freebsd_kqueue.c
fdevent_linux_rtsig.c
fdevent_linux_sysepoll.c
fdevent_poll.c
fdevent_select.c
fdevent_solaris_devpoll.c
几个文件实现。
Ø 线程池
线程池基本上比较简单,实现线程的借入和借出,创建和销毁。最完好可以做到通过一个事件触发一个线程开始工作。下边给出一个简单的,没有实现根据事件触发的,linux和windows通用的线程池模型:
spthread.h
#ifndef __spthread_hpp__
#define __spthread_hpp__
#ifndef WIN32
/// pthread
#include <pthread.h>
#include <unistd.h>
typedef void *sp_thread_result_t;
typedef pthread_mutex_tsp_thread_mutex_t;
typedef pthread_cond_t sp_thread_cond_t;
typedef pthread_t sp_thread_t;
typedef pthread_attr_t sp_thread_attr_t;
#definesp_thread_mutex_init(m,a) pthread_mutex_init(m,a)
#definesp_thread_mutex_destroy(m) pthread_mutex_destroy(m)
#definesp_thread_mutex_lock(m) pthread_mutex_lock(m)
#define sp_thread_mutex_unlock(m) pthread_mutex_unlock(m)
#definesp_thread_cond_init(c,a) pthread_cond_init(c,a)
#definesp_thread_cond_destroy(c) pthread_cond_destroy(c)
#definesp_thread_cond_wait(c,m) pthread_cond_wait(c,m)
#definesp_thread_cond_signal(c) pthread_cond_signal(c)
#definesp_thread_attr_init(a) pthread_attr_init(a)
#definesp_thread_attr_setdetachstate pthread_attr_setdetachstate
#defineSP_THREAD_CREATE_DETACHED PTHREAD_CREATE_DETACHED
#define sp_thread_self pthread_self
#define sp_thread_create pthread_create
#define SP_THREAD_CALL
typedef sp_thread_result_t ( *sp_thread_func_t )( void * args );
#define sp_sleep(x) sleep(x)
#else///
// win32 thread
#include <winsock2.h>
#include <process.h>
typedef unsigned sp_thread_t;
typedef unsignedsp_thread_result_t;
#define SP_THREAD_CALL__stdcall
typedef sp_thread_result_t (__stdcall * sp_thread_func_t )( void * args );
typedef HANDLE sp_thread_mutex_t;
typedef HANDLE sp_thread_cond_t;
typedef DWORD sp_thread_attr_t;
#defineSP_THREAD_CREATE_DETACHED 1
#define sp_sleep(x)Sleep(1000*x)
int sp_thread_mutex_init(sp_thread_mutex_t * mutex, void * attr )
{
*mutex = CreateMutex( NULL, FALSE, NULL );
return NULL == * mutex ? GetLastError() : 0;
}
int sp_thread_mutex_destroy(sp_thread_mutex_t * mutex )
{
int ret = CloseHandle( *mutex );
return 0 == ret ? GetLastError() : 0;
}
int sp_thread_mutex_lock(sp_thread_mutex_t * mutex )
{
int ret = WaitForSingleObject( *mutex, INFINITE );
return WAIT_OBJECT_0 == ret ? 0 : GetLastError();
}
int sp_thread_mutex_unlock(sp_thread_mutex_t * mutex )
{
int ret = ReleaseMutex( *mutex );
return 0 != ret ? 0 : GetLastError();
}
int sp_thread_cond_init(sp_thread_cond_t * cond, void * attr )
{
*cond = CreateEvent( NULL, FALSE, FALSE, NULL );
return NULL == *cond ? GetLastError() : 0;
}
int sp_thread_cond_destroy(sp_thread_cond_t * cond )
{
int ret = CloseHandle( *cond );
return 0 == ret ? GetLastError() : 0;
}
/*
Caller MUST be holding themutex lock; the
lock is released and the calleris blocked waiting
on 'cond'. When 'cond' issignaled, the mutex
is re-acquired before returningto the caller.
*/
int sp_thread_cond_wait(sp_thread_cond_t * cond, sp_thread_mutex_t * mutex )
{
int ret = 0;
sp_thread_mutex_unlock( mutex );
ret