- 浏览: 1101182 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
Tracyer:
你也太强了吧,请问楼主邮箱多少,请教一下plan_9
Plan 9安装Go -
lipengfeng:
不兼容2007
asp.net使用组件koogra读取Excel数据批量上传导入 -
jamie.wang:
博主是mm?
男人四十 -
niwowl:
count那去了!
通过java提供的URL类包读取网上的文件 -
uuid198909:
代码风格啊,博主
高并发系统设计
高并发系统设计
最近失业在家,闲来无事。通过网上查找资料和查看开源代码,研究了一下互联网高并发系统的一些设计。这里主要从服务器内部设计和整个系统设计两个方面讨论,更多的是从互联网大型网站设计方面考虑,高性能计算之类系统没有研究过。 服务器设计涉及Socket的阻塞/非阻塞,操作系统IO的同步和异步(之前被人问到过两次。第一次让我说说知道的网络模型,我说ISO模型和TCP/IP模型,结果被鄙视了。最后人说了解linux epoll吗?不了解呀!汉,回去查资料才知道是这回事。第二次让我说说知道线程模型,汉!这个名词感觉没有听说过,线程?模型?半同步/半异步,领导者/跟随者知道吗。再汉,我知道同步/异步,还有半同步/半异步?啥呀?领导者/跟随者,我现在没有领导。回去一顿恶补,原来是ACE框架里边经常有这样的提法,Reactor属于同步/半同步,PREACTOR属于领导者/跟随者模式。瀑布汗。小插曲一段,这些不懂没关系,下边我慢慢分解
),事件分离器,线程池等。内部设计希望通过各个模块的给出一个简单设计,经过您的进一步的组合和打磨,就可以实现一个基本的高并发服务器。 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高并发服务器的分析做同样的设计。 Ø
阻塞和非阻塞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的时候,发送请求后,结果是通过事件通知的。这是产生了事件分离器的需求。事件分离器主要任务是管理和分离不同文件描述符上的所发生的事件,让后通知相应的事件,派发相应的动作。下边是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 = WaitForSingleObject( *cond, INFINITE ); sp_thread_mutex_lock( mutex ); return WAIT_OBJECT_0 == ret ? 0 : GetLastError(); } int sp_thread_cond_signal(sp_thread_cond_t * cond ) { int ret = SetEvent( *cond ); return 0 == ret ? GetLastError() : 0; } sp_thread_t sp_thread_self() { return GetCurrentThreadId(); } int sp_thread_attr_init(sp_thread_attr_t * attr ) { *attr = 0; return 0; } intsp_thread_attr_setdetachstate( sp_thread_attr_t * attr, int detachstate ) { *attr |= detachstate; return 0; } int sp_thread_create(sp_thread_t * thread, sp_thread_attr_t * attr, sp_thread_func_t myfunc, void * args ) { // _beginthreadex returns 0 on an error HANDLE h = (HANDLE)_beginthreadex( NULL, 0, myfunc, args, 0,thread ); return h > 0 ? 0 : GetLastError(); } #endif #endif threadpool.h /** * threadpool.h * * This file declares the functionalityassociated with * your implementation of a threadpool. */ #ifndef __threadpool_h__ #define __threadpool_h__ #ifdef __cplusplus extern "C" { #endif // maximum number of threadsallowed in a pool #define MAXT_IN_POOL 200 // You must hide the internaldetails of the threadpool // structure from callers, thusdeclare threadpool of type "void". // In threadpool.c, you willuse type conversion to coerce // variables of type"threadpool" back and forth to a // richer, internal type. (See threadpool.c for details.) typedef void *threadpool; // "dispatch_fn"declares a typed function pointer. A // variable of type"dispatch_fn" points to a function // with the followingsignature: // // void dispatch_function(void *arg); typedef void(*dispatch_fn)(void *); /** * create_threadpool creates a fixed-sizedthread * pool. If the function succeeds, it returns a (non-NULL) * "threadpool", else it returnsNULL. */ threadpoolcreate_threadpool(int num_threads_in_pool); /** * dispatch sends a thread off to do somework. If * all threads in the pool are busy, dispatchwill * block until a thread becomes free and isdispatched. * * Once a thread is dispatched, this functionreturns * immediately. * * The dispatched thread calls into thefunction * "dispatch_to_here" with argument"arg". */ intdispatch_threadpool(threadpool from_me, dispatch_fn dispatch_to_here, void *arg); /** * destroy_threadpool kills the threadpool,causing * all threads in it to commit suicide, andthen * frees all the memory associated with thethreadpool. */ voiddestroy_threadpool(threadpool destroyme); #ifdef __cplusplus } #endif #endif threadpool.c /** * threadpool.c * * This file will contain your implementation ofa threadpool. */ #include <stdio.h> #include <stdlib.h> //#include <unistd.h> //#include <sp_thread.h> #include <string.h> #include"threadpool.h" #include "spthread.h" typedef struct _thread_st { sp_thread_t id; sp_thread_mutex_t mutex; sp_thread_cond_t cond; dispatch_fn fn; void *arg; threadpool parent; } _thread; // _threadpool is the internalthreadpool structure that is // cast to type"threadpool" before it given out to callers typedef struct _threadpool_st { // you should fill in this structure with whatever you need sp_thread_mutex_t tp_mutex; sp_thread_cond_t tp_idle; sp_thread_cond_t tp_full; sp_thread_cond_t tp_empty; _thread ** tp_list; int tp_index; int tp_max_index; int tp_stop; int tp_total; } _threadpool; threadpoolcreate_threadpool(int num_threads_in_pool) { _threadpool *pool; // sanity check the argument if ((num_threads_in_pool <= 0) || (num_threads_in_pool> MAXT_IN_POOL)) return NULL; pool = (_threadpool *) malloc(sizeof(_threadpool)); if (pool == NULL) { fprintf(stderr, "Out of memory creating a newthreadpool!\n"); return NULL; } // add your code here to initialize the newly createdthreadpool sp_thread_mutex_init( &pool->tp_mutex, NULL ); sp_thread_cond_init( &pool->tp_idle, NULL ); sp_thread_cond_init( &pool->tp_full, NULL ); sp_thread_cond_init( &pool->tp_empty, NULL ); pool->tp_max_index = num_threads_in_pool; pool->tp_index = 0; pool->tp_stop = 0; pool->tp_total = 0; pool->tp_list = ( _thread ** )malloc( sizeof( void * ) *MAXT_IN_POOL ); memset( pool->tp_list, 0, sizeof( void * ) * MAXT_IN_POOL); return (threadpool) pool; } int save_thread( _threadpool *pool, _thread * thread ) { int ret = -1; sp_thread_mutex_lock( &pool->tp_mutex ); if( pool->tp_index < pool->tp_max_index ) { pool->tp_list[ pool->tp_index ] = thread; pool->tp_index++; ret = 0; sp_thread_cond_signal( &pool->tp_idle ); if( pool->tp_index >= pool->tp_total ) { sp_thread_cond_signal(&pool->tp_full ); } } sp_thread_mutex_unlock( &pool->tp_mutex ); return ret; } sp_thread_result_tSP_THREAD_CALL wrapper_fn( void * arg ) { _thread * thread = (_thread*)arg; _threadpool * pool = (_threadpool*)thread->parent; for( ; 0 == ((_threadpool*)thread->parent)->tp_stop; ){ thread->fn( thread->arg ); if( 0 !=((_threadpool*)thread->parent)->tp_stop ) break; sp_thread_mutex_lock( &thread->mutex ); if( 0 == save_thread( thread->parent, thread )) { sp_thread_cond_wait(&thread->cond, &thread->mutex ); sp_thread_mutex_unlock(&thread->mutex ); } else { sp_thread_mutex_unlock(&thread->mutex ); sp_thread_cond_destroy(&thread->cond ); sp_thread_mutex_destroy(&thread->mutex ); free( thread ); break; } } sp_thread_mutex_lock( &pool->tp_mutex ); pool->tp_total--; if( pool->tp_total <= 0 ) sp_thread_cond_signal(&pool->tp_empty ); sp_thread_mutex_unlock( &pool->tp_mutex ); return 0; } intdispatch_threadpool(threadpool from_me, dispatch_fn dispatch_to_here, void*arg) { int ret = 0; _threadpool *pool = (_threadpool *) from_me; sp_thread_attr_t attr; _thread * thread = NULL; // add your code here to dispatch a thread sp_thread_mutex_lock( &pool->tp_mutex ); if( pool->tp_index <= 0 && pool->tp_total>= pool->tp_max_index ) { sp_thread_cond_wait( &pool->tp_idle,&pool->tp_mutex ); } if( pool->tp_index <= 0 ) { _thread * thread = ( _thread * )malloc( sizeof(_thread ) ); memset( &( thread->id ), 0, sizeof(thread->id ) ); sp_thread_mutex_init( &thread->mutex, NULL); sp_thread_cond_init( &thread->cond, NULL ); thread->fn = dispatch_to_here; thread->arg = arg; thread->parent = pool; sp_thread_attr_init( &attr ); sp_thread_attr_setdetachstate( &attr,SP_THREAD_CREATE_DETACHED ); if( 0 == sp_thread_create( &thread->id,&attr, wrapper_fn, thread ) ) { pool->tp_total++; printf( "create thread#%ld\n",thread->id ); } else { ret = -1; printf( "cannot createthread\n" ); sp_thread_mutex_destroy(&thread->mutex ); sp_thread_cond_destroy(&thread->cond ); free( thread ); } } else { pool->tp_index--; thread = pool->tp_list[ pool->tp_index ]; pool->tp_list[ pool->tp_index ] = NULL; thread->fn = dispatch_to_here; thread->arg = arg; thread->parent = pool; sp_thread_mutex_lock( &thread->mutex ); sp_thread_cond_signal( &thread->cond ) ; sp_thread_mutex_unlock ( &thread->mutex ); } sp_thread_mutex_unlock( &pool->tp_mutex ); return ret; } void destroy_threadpool(threadpooldestroyme) { _threadpool *pool = (_threadpool *) destroyme; // add your code here to kill a threadpool int i = 0; sp_thread_mutex_lock( &pool->tp_mutex ); if( pool->tp_index < pool->tp_total ) { printf( "waiting for %d thread(s) tofinish\n", pool->tp_total - pool->tp_index ); sp_thread_cond_wait( &pool->tp_full,&pool->tp_mutex ); } pool->tp_stop = 1; for( i = 0; i < pool->tp_index; i++ ) { _thread * thread = pool->tp_list[ i ]; sp_thread_mutex_lock( &thread->mutex ); sp_thread_cond_signal( &thread->cond ) ; sp_thread_mutex_unlock ( &thread->mutex ); } if( pool->tp_total > 0 ) { printf( "waiting for %d thread(s) toexit\n", pool->tp_total ); sp_thread_cond_wait( &pool->tp_empty,&pool->tp_mutex ); } for( i = 0; i < pool->tp_index; i++ ) { free( pool->tp_list[ i ] ); pool->tp_list[ i ] = NULL; } sp_thread_mutex_unlock( &pool->tp_mutex ); pool->tp_index = 0; sp_thread_mutex_destroy( &pool->tp_mutex ); sp_thread_cond_destroy( &pool->tp_idle ); sp_thread_cond_destroy( &pool->tp_full ); sp_thread_cond_destroy( &pool->tp_empty ); free( pool->tp_list ); free( pool ); } 根据Socket的阻塞非阻塞,IO的同步和异步。可以分为如下4中情形 阻塞同步 | 阻塞异步 _________|______________ 非阻塞同步 | 非阻塞异步 阻塞同步方式是原始的方式,也是许多教科书上介绍的方式,因为Socket和IO默认的为阻塞和同步方式。基本流程如下: listen_fd = socket( AF_INET,SOCK_STREAM,0 ) bind( listen_fd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr_in)) listen( listen_fd,1 ) accept( listen_fd, (struct sockaddr*)&remote_addr,&addr_len ) recv( accept_fd ,&in_buf ,1024 ,0 ) close(accept_fd) 阻塞异步方式有所改进,但是Socket的阻塞方式,前一个连接没有处理完成,下一个连接不能接入,是高并发服务器所不可接收的方式。只不过在上边阻塞同步方式的基础上使用select(严格来说select是一种IO多路服用技术。因为linux尚没有完整的实现异步IO,而winsock实在理解socket没有linux上面那么直观。,这里为了方便,没有做严格的区分)或者其它异步IO方式。 非阻塞同步方式,通过设置socket选项为NONBLOCK,可以很快的接收连接,但是处理采用同步IO方式,服务器处理性能也比较差。 上边三种方式不做深入介绍。下边主要从非阻塞异步IO方式介绍。 非阻塞异步IO方式中,由于异步IO方式在同一系统可能有多种实现,不同系统也有不同实现,下边介绍几种常见的IO方式和服务器框架。 Ø
Select
Select采用轮训注册的fd方式。是一种比较老的IO多路服用实现方式,效率相对要差一些。Select方式在windows和linux上都支持。 基本框架如下: socket( AF_INET,SOCK_STREAM,0 ) fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK); bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in)) listen( listen_fd,1 ) FD_ZERO( &fd_sets ); FD_SET(listen_fd,&fd_sets); for(k=0; k<=i; k++){ FD_SET(accept_fds[k],&fd_sets); } events = select( max_fd + 1,&fd_sets, NULL, NULL, NULL ); if(FD_ISSET(listen_fd,&fd_sets) ){ accept_fd = accept( listen_fd, (structsockaddr *)&remote_addr,&addr_len ); } for( j=0; j<=i; j++ ){ if( FD_ISSET(accept_fds[j],&fd_sets) ){ recv( accept_fds[j] ,&in_buf ,1024 ,0 ); } } Ø
Epoll
Epoll是linux2.6内核以后支持的一种高性能的IO多路服用技术。服务器框架如下: socket( AF_INET,SOCK_STREAM,0 ) fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK); bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in)) listen( listen_fd,1 ) epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev); ev_s = epoll_wait(epfd,events,20,500 ); for(i=0; i<ev_s;i++){ if(events[i].data.fd==listen_fd){ accept_fd = accept( listen_fd,(structsockaddr *)&remote_addr,&addr_len ); fcntl(accept_fd, F_SETFL,flags|O_NONBLOCK); epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev); } else if(events[i].events&EPOLLIN){ recv( events[i].data.fd ,&in_buf,1024 ,0 ); } } Ø
AIO
在windows上微软实现了异步IO,通过AIO可以方便的实现高并发的服务器。框架如下: WSAStartup( 0x0202 , & wsaData) CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0 , 0 ) WSASocket(AF_INET,SOCK_STREAM, 0 , NULL, 0 , WSA_FLAG_OVERLAPPED) bind(Listen, (PSOCKADDR) & InternetAddr, sizeof (InternetAddr)) listen(Listen, 5 ) WSAAccept(Listen, NULL, NULL,NULL, 0 ) PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof (PER_HANDLE_DATA) CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (DWORD) PerHandleData, 0 ) PerIoData= (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof (PER_IO_OPERATION_DATA)) WSARecv(Accept,&(PerIoData->DataBuf),1,&RecvBytes,&Flags,&(PerIoData->Overlapped), NULL) (GetQueuedCompletionStatus(CompletionPort, & BytesTransferred, (LPDWORD) & PerHandleData,(LPOVERLAPPED * ) & PerIoData, INFINITE) if (PerIoData -> BytesRECV > PerIoData -> BytesSEND){ WSASend(PerHandleData-> Socket, & (PerIoData ->DataBuf), 1 , & SendBytes, 0 , & (PerIoData ->Overlapped), NULL) } 由于上边只是单纯的使用非阻塞Socket和异步IO的方式。提高了接收连接和处理的速度。但是还是不能解决两个客户端同时连接的问题。这时就需要引入多线程机制。引入多线程后,又有许多策略。Linux上通常采用主进程负责接收连接,之后fork子进程处理连接。Windows通常采用线程池方式,避免线程创建和销毁的开销,当然linux上也可以采用线程池方式。采用多进程和多线程方式后。事件处理也可以再优化,定义一个简单的事件处理器,把所有事件放入一个队列,各个线程去事件队列取相应的事件,然后自己开始工作。这就是我上边提到的半同步/半异步方式了。如果线程工作的时候是接收到连接后,自己处理后续的发送和接收,然后选出另外一个线程作为领导继续接收连接,其它线程作为追随者。这就是领导者/追随者模式了。具体可以参考ACE的Reactor和Preactor的具体实现。半同步和/半异步网上也有很多的讨论,可以自己深入研究。代码就比较复杂了,这里就不给出代码了。给出一个linux下类似,相对简单的fork子进程+epoll方式的实现: #include<sys/socket.h> 前面讲述了分布式系统中的核心的服务器的实现。可以是http服务器,缓存服务器,分布式文件系统等的内部实现。下边主要从一个高并发的大型网站出发,看一个高并发系统的设计。下边是一个高并发系统的逻辑结构: 主要是参考这篇文章http://www.chinaz.com/web/2010/0310/108211.shtml
。下边主要想从这个架构的各个部分的实现展开。 缓存是每一个高并发,高可用系统不可或缺的模块。下边就几个常见缓存系统系统进行介绍。 Squid
Squid作为一个前端缓存,通常部署在网络的离用户最近的地方,通过缓存网站的页面,使用户不必每次都跑到服务器去取数据,提高系统响应和性能。实现应该比较简单:一个带有存储功能的代理。用户访问页面的时候,由它代理,然后存储请求结果,下次再访问的时候,查看是否需要更新,有更新就去服务器取新数据,否则直接返回用户页面。 Ehcache
Ehcache是一个对象缓存系统。通常在J2EE中配合Hibernate使用,这里请原谅作者本人之前是做J2EE开发的,其它使用方式暂不是很了解。应用查询数据库,对经常需要查询,却更新不频繁的数据,可以放入ehcache缓存,提高访问速度。Ehcahe支持内存缓存和硬盘两种方式,支持分布式缓存。数据缓存的基本原理就是:为需要缓存的对象建立一个map,临时对象放入map,查询的时候先查询map,没有找到再查找数据库。关机时可以序列化到硬盘。分布式缓存没有研究过。 页面缓存和动态页面静态化
在大型网站经常使用的一种缓存技术就是动态页面的缓存。由于动态页面经常更新,上边的缓存就不起作用了。通常会采用SSI(Server side include)等技术将动态页面的或者页面片段进行缓存。 还有一种就是动态页面静态化。下边是一本讲spring的书中给出的j2EE中的动态页面静态化的示例: /** * 动态内容静态化的Filter。将变化非常缓慢的动态文件生成静态文件。 */ package com.zsl.cache.filter; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Map; importjavax.management.RuntimeErrorException; importjavax.servlet.FilterChain; importjavax.servlet.ServletException; importjavax.servlet.ServletRequest; importjavax.servlet.ServletResponse; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importorg.apache.commons.io.FilenameUtils; importorg.apache.http.HttpResponse; importorg.springframework.core.io.Resource; importcom.sun.xml.bind.v2.runtime.output.Encoded; /** * @author zsl * */ public class FileCacheFilterextends AbstractCacheFilter{ private String root; private final String SUFFIX = ".html"; public final void setFileDir(Resource dir){ try { File f = dir.getFile(); f.mkdirs(); if(!f.isDirectory()){ throw newIllegalArgumentException("Invalid directory: "+f.getPath()); } if(!f.canWrite()) throw newIllegalArgumentException("Cannot write to directory: "+f.getPath()); root = f.getPath(); if(!root.endsWith("/")&&!root.endsWith("//")) root = root+"/"; } catch (IOException e) { // TODO Auto-generated catch block throw new IllegalArgumentException(e); } } public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); if(!new File(root).isDirectory()){ throw newIllegalArgumentException("No directory: "+root); } } public void doFilter(ServletRequest request,ServletResponseresponse,FilterChain chain) throws IOException,ServletException { HttpServletRequest httpRequest =(HttpServletRequest)request; String key = getKey(httpRequest); if(key == null){ chain.doFilter(request,response); }else{ File file = key2File(key); if(file.isFile()){ HttpServletResponse httpResponse= (HttpServletResponse)response; httpResponse.setContentType(getContentType()); httpResponse.setHeader("Content-Encoding","gzip"); httpResponse.setContentLength((int)file.length()); FileUtil.readFil(file,httpResponse.getOutputStream()); }else{ //缓存未找到文件 HttpServletResponse httpResponse= (HttpServletResponse)response; CachedResponseWrapper wrapper =new CachedResponseWrapper(httpResponse); chain.doFilter(request,response); if(wrapper.getStatus() ==HttpServletResponse.SC_OK){ byte[] data =GZipUtil.gzip(wrapper.getResponseData()); FileUtil.writeFile(file,data); httpResponse.setContentType(getContentType()); httpResponse.setHeader("Content-Encoding","gzip"); httpResponse.setContentLength(data.length); httpResponse.getOutputStream().write(data); } } } } private File key2File(String key){ int hash =key.hashCode(); int dir1 = (hash &0xff00)>>8; int dir2 = hash & 0xff; String dir= root+dir1+"/"+dir2; File fdir = new File(dir); if(!fdir.isAbsolute()){ if(!fdir.mkdirs()){ return null; } } return newFile(dir+"/"+encode(key)+SUFFIX); } private String encode(String key){ try { return URLEncoder.encode(key,"UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public void remove(String url,Map<String,String>parameters){ String key =getKey(HttpServletRequestFactory.create(url,parameters)); if(key != null){ FileUtil.remveFile(key2File(key)); } } } 书中给到的另外一个客户端缓存,不知道该归那类: /** * 网页静态资源(gif图像,css资源的缓存时间设置filter,避免频繁请求静态资源 */ package com.zsl.cache.filter; import java.io.IOException; import java.util.Enumeration; import java.util.Map; importjavax.servlet.FilterChain; importjavax.servlet.FilterConfig; importjavax.servlet.ServletException; import javax.servlet.Filter; importjavax.servlet.ServletRequest; importjavax.servlet.ServletResponse; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importorg.apache.commons.collections.map.HashedMap; importorg.apache.commons.logging.Log; importorg.apache.commons.logging.LogFactory; /** * @author zsl * */ public class ExpireFilterimplements Filter { private Log log = LogFactory.getLog(ExpireFilter.class); private Map<String, Long> map = new HashedMap(); @Override public void destroy() { log.info("destory ExpiredFilter"); } @Override public void doFilter(ServletRequest request, ServletResponseresponse, FilterChain chain) throws IOException,ServletException { String uriString =((HttpServletRequest)request).getRequestURI(); int n = uriString.lastIndexOf('.'); if(n!= -1){ String ext =uriString.substring(n); Long exp = map.get(ext); if(exp != null){ HttpServletResponseresp = (HttpServletResponse)response; resp.setHeader("Expires",System.currentTimeMillis()+exp*1000+""); } } chain.doFilter(request,response); } @Override public void init(FilterConfig config) throwsServletException { Enumeration em = config.getInitParameterNames(); while(em.hasMoreElements()){ String paramName =em.nextElement().toString(); String paramValue = config.getInitParameter(paramName); try { int time =Integer.valueOf(paramValue); if(time>0){ log.info("set"+paramName + " expired seconds: "+time); map.put(paramName, newLong(time)); } } catch (Exception e) { log.warn("Exception ininitilizing ExpiredFilter.",e); } } } } Ø
负载均衡策略
负载均衡策略有随机分配,平均分配,分布式一致性hash等。随机分配就是通过随机数选择一个服务器来服务。平均分配就是一次循环分配一次。分布式一致性hash算法,比较负载,把资源和节点映射到一个换上,然后通过一定的算法资源对应到节点上,使得添加和去掉服务器变得非常容易,减少对其它服务器的影响。很有名的一个算法,据说是P2P的基础。了解不是很深,就不详细说了,要露马脚了。 Ø
软件负载均衡
软件负载均衡可以采用很多方案,常见的几个方案有: 基于DNS的负载均衡,通过DNS正向区域的配置,将一个域名根据一定的策略解析到多个ip地址,实现负载均衡,这里需要DNS服务器的配合。 基于LVS的负载均衡。LVS可以将多个linux服务器做成一个虚拟的服务器,对外提供服务器,实现负载均衡。 基于Iptables的负载均衡。Iptables可以通过做nat,对外提供一个虚拟IP,对内映射到多个服务器实现负载均衡。基本上可以和硬件均衡方案一致了,这里的linux服务器相当于一台路由器。 Ø
硬件负载均衡
基于路由器的负载均衡,在路由器上配置nat实现负载均衡。对外网一个虚拟IP,内网映射几个内网IP。 一些网络设备厂商也提供了一些负载均衡的设备,如F5,不过价格不菲哦。 数据库的负载均衡 数据库的负载均衡可以是数据库厂商提供的集群方案。 ―――――――――――――――――――――――――――――――――――――― 今天先写到这里,这个题目太大了,东西太多。后边是将来要写的。还没有组织出来。 好多东西也没有展开。东西太多了。 ―――――――――――――――――――――――――――――――――――――― 分布式文件系统 Gfs hfs Map Reduce系统 云计算一、 引子
二、 服务器内部设计
1. Java高并发服务器
2. C高并发服务器设计
1) 基本概念
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。
同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。高并发系统通常采用异步IO方式提高系统性能。2) 常见的设计模式
3) 引入线程池和事件分离器后
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#define HANDLE_INFO 1
#define HANDLE_SEND 2
#define HANDLE_DEL 3
#define HANDLE_CLOSE 4
#define MAX_REQLEN 1024
#define MAX_PROCESS_CONN 3
#define FIN_CHAR 0x00
#define SUCCESS 0
#define ERROR -1
typedef struct event_handle{
int socket_fd;
int file_fd;
int file_pos;
int epoll_fd;
char request[MAX_REQLEN];
int request_len;
int ( * read_handle )( struct event_handle * ev );
int ( * write_handle )( struct event_handle * ev );
int handle_method;
} EV,* EH;
typedef int ( * EVENT_HANDLE )( struct event_handle * ev );
int create_listen_fd( int port ){
int listen_fd;
struct sockaddr_inmy_addr;
if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ){
perror( "create socket error" );
exit( 1 );
}
int flag;
int olen = sizeof(int);
if( setsockopt( listen_fd, SOL_SOCKET, SO_REUSEADDR
, (const void *)&flag, olen ) == -1 ){
perror( "setsockopt error" );
}
flag = 5;
if( setsockopt( listen_fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &flag, olen ) == -1 ){
perror( "setsockopt error" );
}
flag = 1;
if( setsockopt( listen_fd, IPPROTO_TCP, TCP_CORK, &flag, olen ) == -1 ){
perror( "setsockopt error" );
}
int flags = fcntl( listen_fd, F_GETFL, 0 );
fcntl( listen_fd, F_SETFL, flags|O_NONBLOCK );
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons( port );
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero( &( my_addr.sin_zero ), 8 );
if( bind( listen_fd, ( struct sockaddr * )&my_addr,
sizeof( struct sockaddr_in ) ) == -1 ) {
perror( "bind error" );
exit( 1 );
}
if( listen( listen_fd, 1 ) == -1 ){
perror( "listen error" );
exit( 1 );
}
return listen_fd;
}
int create_accept_fd( int listen_fd ){
int addr_len = sizeof( struct sockaddr_in );
struct sockaddr_inremote_addr;
int accept_fd = accept( listen_fd,
( struct sockaddr * )&remote_addr, &addr_len );
int flags = fcntl( accept_fd, F_GETFL, 0 );
fcntl( accept_fd, F_SETFL, flags|O_NONBLOCK );
return accept_fd;
}
int fork_process( int process_num ){
int i;
int pid=-1;
for( i = 0; i < process_num; i++ ){
if( pid != 0 ){
pid = fork();
}
}
return pid;
}
int init_evhandle(EH ev,int socket_fd,int epoll_fd,EVENT_HANDLEr_handle,EVENT_HANDLE w_handle){
ev->epoll_fd = epoll_fd;
ev->socket_fd = socket_fd;
ev->read_handle = r_handle;
ev->write_handle = w_handle;
ev->file_pos = 0;
ev->request_len = 0;
ev->handle_method = 0;
memset( ev->request, 0, 1024 );
}
//accept->accept_queue->request->request_queue->output->output_queue
//multi process sendfile
int parse_request(EH ev){
ev->request_len--;
*( ev->request + ev->request_len - 1 ) = 0x00;
int i;
for( i=0; i<ev->request_len; i++ ){
if( ev->request[i] == ':' ){
ev->request_len = ev->request_len-i-1;
char temp[MAX_REQLEN];
memcpy( temp, ev->request, i );
ev->handle_method = atoi( temp );
memcpy( temp, ev->request+i+1, ev->request_len );
memcpy( ev->request, temp, ev->request_len );
break
;
}
}
//handle_request(ev );
//registerto epoll EPOLLOUT
struct epoll_eventev_temp;
ev_temp.data.ptr = ev;
ev_temp.events = EPOLLOUT|EPOLLET;
epoll_ctl( ev->epoll_fd, EPOLL_CTL_MOD, ev->socket_fd, &ev_temp );
return SUCCESS;
}
int handle_request(EH ev){
struct statfile_info;
switch( ev->handle_method ){
case HANDLE_INFO:
ev->file_fd = open( ev->request, O_RDONLY );
if( ev->file_fd == -1 ){
send( ev->socket_fd, "open file failed\n
", strlen("open file failed\n
"), 0 );
return -1;
}
fstat(ev->file_fd, &file_info);
char info[MAX_REQLEN];
sprintf(info,"filelen:%d\n
",file_info.st_size);
send( ev->socket_fd, info, strlen( info ), 0 );
break
;
case HANDLE_SEND:
ev->file_fd = open( ev->request, O_RDONLY );
if( ev->file_fd == -1 ){
send( ev->socket_fd, "open file failed\n
", strlen("open file failed\n
"), 0 );
return -1;
}
fstat(ev->file_fd, &file_info);
sendfile( ev->socket_fd, ev->file_fd, 0, file_info.st_size );
break
;
case HANDLE_DEL:
break
;
case HANDLE_CLOSE:
break
;
}
finish_request( ev );
return SUCCESS;
}
int finish_request(EH ev){
close(ev->socket_fd);
close(ev->file_fd);
ev->handle_method = -1;
clean_request( ev );
return SUCCESS;
}
int clean_request(EH ev){
memset( ev->request, 0, MAX_REQLEN );
ev->request_len = 0;
}
int read_hook_v2( EH ev ){
char in_buf[MAX_REQLEN];
memset( in_buf, 0, MAX_REQLEN );
int recv_num = recv( ev->socket_fd, &in_buf, MAX_REQLEN, 0 );
if( recv_num ==0 ){
close( ev->socket_fd );
return ERROR;
}
else{
//checkifoverflow
if( ev->request_len > MAX_REQLEN-recv_num ){
close( ev->socket_fd );
clean_request( ev );
}
memcpy( ev->request + ev->request_len, in_buf, recv_num );
ev->request_len += recv_num;
if( recv_num == 2 && ( !memcmp( &in_buf[recv_num-2], "\r\n
", 2 ) ) ){
parse_request(ev);
}
}
return recv_num;
}
int write_hook_v1( EH ev ){
struct statfile_info;
ev->file_fd = open( ev->request, O_RDONLY );
if( ev->file_fd == ERROR ){
send( ev->socket_fd, "openfile failed\n
", strlen("openfile failed\n
"), 0 );
return ERROR;
}
fstat(ev->file_fd, &file_info);
int write_num;
while(1){
write_num = sendfile( ev->socket_fd, ev->file_fd, (off_t *)&ev->file_pos, 10240 );
ev->file_pos += write_num;
if( write_num == ERROR ){
if( errno == EAGAIN ){
break
;
}
}
else if( write_num == 0 ){
printf
( "writed:%d\n
", ev->file_pos );
//finish_request(ev );
break
;
}
}
return SUCCESS;
}
int main(){
int listen_fd = create_listen_fd( 3389 );
int pid = fork_process( 3 );
if( pid == 0 ){
int accept_handles = 0;
struct epoll_eventev, events[20];
int epfd = epoll_create( 256 );
int ev_s = 0;
ev.data.fd = listen_fd;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev );
struct event_handleev_handles[256];
for( ;; ){
ev_s = epoll_wait( epfd, events, 20, 500 );
int i = 0;
for( i = 0; i<ev_s; i++ ){
if( events[i].data.fd == listen_fd ){
if( accept_handles < MAX_PROCESS_CONN ){
accept_handles++;
int accept_fd = create_accept_fd( listen_fd );
init_evhandle(&ev_handles[accept_handles],accept_fd,epfd,read_hook_v2,write_hook_v1);
ev.data.ptr = &ev_handles[accept_handles];
ev.events = EPOLLIN|EPOLLET;
epoll_ctl( epfd, EPOLL_CTL_ADD, accept_fd, &ev );
}
}
else if( events[i].events&EPOLLIN ){
EVENT_HANDLE current_handle = ( ( EH )( events[i].data.ptr ) )->read_handle;
EH current_event = ( EH )( events[i].data.ptr );
( *current_handle )( current_event );
}
else if( events[i].events&EPOLLOUT ){
EVENT_HANDLE current_handle = ( ( EH )( events[i].data.ptr ) )->write_handle;
EH current_event = ( EH )( events[i].data.ptr );
if( ( *current_handle )( current_event ) == 0 ){
accept_handles--;
}
}
}
}
}
else{
//managerthe process
int child_process_status;
wait( &child_process_status );
}
return SUCCESS;
}三、 分布式系统设计
1. 缓存系统
2. 负载均衡系统
评论
扭正一点,在windows上通过建立一个127.0.0.1到127.0.0.1的连接实现IO的异步通知。在linux上通过建立一个管道实现IO的异步通知。
这两种方式不是用来实现IO异步的,而是用来唤醒select()的,其wakeup方法,就是通过这个连接,发送一字节消息,唤醒阻塞的select()
相关推荐
《高并发系统设计》 高并发系统设计是Java后端开发中的重要领域,它涉及到如何在面临大量用户请求时,确保系统的稳定性和高效性。在本文中,我们将探讨三种主要的高并发系统设计方法,以及如何利用缓存来提升系统的...
"P9纯手打亿级高并发系统设计手册" 高并发系统是一种能够支撑亿级流量的系统设计,通过巧妙的设计方法,可以抵抗巨大流量的冲击,带给用户更好的使用体验。高并发系统设计的魅力就在于我们能够凭借自己的聪明才智...
阿里P9纯手打亿级高并发系统设计手册1.pdf 本篇手册主要讲解了高并发系统设计的通用设计方法,包括Scale-out、缓存和异步三种方法。高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案,从而抵抗...
在Java领域,高并发系统设计是一项至关重要的技能,它涉及到如何构建能够同时处理大量请求的高效应用程序。在本文中,我们将深入探讨Java高并发系统设计的相关知识点,包括基础概念、核心技术以及最佳实践。 1. **...
### 高并发系统设计思路 #### 设计原则 在高并发系统的架构设计中,核心的原则是围绕着如何更合理地分配资源来进行。这不同于解决资源不足的问题,而是着重于现有资源的有效利用。以下几点是设计时需重点考虑的: ...
淘宝双11,618的京东节,滴滴打车高峰如何抗住亿级的并发量?...这一份阿里P9纯手打的高并发系统设计手册帮你解决! 这份手册分为基础篇、数据库篇、缓存篇、消息队列篇、分布式服务篇、维护篇、实战篇。
淘宝双11,618的京东节,滴滴打车高峰如何抗住亿级的并发量?...这一份阿里P9纯手打的高并发系统设计手册帮你解决! 这份手册分为基础篇、数据库篇、缓存篇、消息队列篇、分布式服务篇、维护篇、实战篇。
以下是对高并发系统设计的一些核心知识点的详细解释。 1. **Scale-out(横向扩展)**:这是应对高并发的常见策略,通过分布式部署,将流量分散到多个服务器,每个服务器承担部分并发。这种方式可以避免单点故障,...
《阿里巴巴P9百亿级高并发系统设计手册》是一本深入探讨大规模并发系统设计的专业书籍,旨在为读者提供在阿里巴巴这样大型互联网公司中处理高并发场景的实践经验与理论知识。本书内容丰富,涵盖三百多页,全彩印刷,...
【高并发系统设计】是IT领域中至关重要的一个主题,特别是在互联网大厂中,它直接影响着服务的稳定性和用户体验。唐扬,一位有着十年社区系统研发经验的技术专家,通过他在美图秀秀社区的工作经历,深刻理解了高并发...
【亿级高并发系统设计说明书】是一篇关于构建大规模并发处理系统的指导文档,适用于毕业设计或作为学习模板。本文档的核心在于介绍如何处理高流量带来的挑战,通过一系列策略确保系统的稳定性和用户体验。以下是对...
本课程“系统架构课程,后端通用知识体系,主要是高并发系统设计”聚焦于如何处理大规模并发访问的问题,这是许多互联网企业和大型网站面临的挑战。通过学习这门课程,开发者可以掌握构建高并发系统的必备技能。 ...
《阿里亿级高并发系统设计》是一份详细探讨在大规模并发环境下如何构建高效稳定系统的实践指南。这份328页的手册,源自阿里集团的高级工程师们的经验分享,旨在为IT行业的毕业生和开发者提供宝贵的实战知识,帮助...
《阿里P9纯手打亿级高并发系统设计手册》是针对高级Java开发人员的一份珍贵参考资料,尤其适合那些在大型互联网公司担任高并发系统设计和优化工作的工程师。该手册详细阐述了如何应对大规模并发场景下的挑战,确保...
在Java领域,高并发系统设计是开发者必备的技能之一,特别是在大型互联网企业中。下面将对"java高并发系统设计40问(持续更新共40篇)"中的关键知识点进行详细解析。 首先,我们需要理解“高并发”这个概念。它指的是...
【高并发数据库设计】在面对乐视集团支付系统中急剧增长的请求压力时,为了保证用户能够快速稳定地完成支付,特别是在抢购秒杀活动中提供强有力支撑,设计了一个每秒能处理10万订单的系统。关键策略是进行数据库的分...
高并发架构设计
"高并发秒杀系统设计" 高并发秒杀系统设计是指在电子商务平台中,设计一个能够承载高并发流量的秒杀系统,以满足用户的秒杀需求。该系统需要具备高性能、低延迟、可扩展性强等特点。 高并发秒杀系统设计的功能需求...
Scale-out(横向扩展):分而治之是一种常见的高并发系统设计方法,采用分布式部署的方式把流量分流开,让每个服务器都承担一部分并发和流量。 2. 缓存:使用缓存来提高系统的性能,就好比用“拓宽河道”的方式抵抗...