网络服务器开发总结

 

网络服务器开发总结java

 

 

1、概述python

通过多年网络服务器开发实战,于此总结实践体会。本文涉及到异步链接、异步域名解析、热更新、过载保护、网络模型与架构及协程等,但不会涉及accept4epoll等基本知识点。mysql

 

 

2、可写事件react

相信大多数初学者都会迷惑可写事件的做用,可能以为可写事件没有什么意义。但在网络服务器中监听并处理可写事件必不可少,其做用在于判断链接是否能够发送数据,主要用于当网络缘由暂时没法当即发送数据时监听。linux

当有数据须要发送到客户端时则直接发送。若没能当即完整发送,则先将其缓存到发送缓冲区,并监听其可写事件,当该链接可写时则再发送之且再也不监听其可写事件(防止滥用可写事件)。ios

值得注意的是,对于指定网络链接须要先将发送缓冲区数据发送完成后才能发送新数据,此也可能比较容易忽略,至少本人当年被坑过。nginx

 

 

3、链接缓冲区web

对于长链接来讲,维持网络链接缓冲区也必不可少。目前一些网络服务器(如QQ宠物旧接入层)都没有维持链接的接收与发送缓冲区,更不会在暂没法发送时监听可写事件。其直接接收数据并处理,若处理过程当中遇到不完整数据包则直接丢掉,如此则可能致使该链接的后续网络数据包大量出错,从而致使丢包;在发送数据时也会在没法发送时直接丢弃。redis

对每一网络链接均须要维持其接收与发送数据缓冲区,当链接可读取时则先读取数据到接收缓冲区,而后判断是否完整并处理之;当向链接发送数据时通常都直接发送,若不能当即完整发送时则将其缓存到发送缓冲区,而后等链接可写时再发送,但须要注意的是,若在可写缓冲区非空且可写以前须要发送新数据,则此时不能直接发送而是应该将其追加到发送缓冲区后统一发送,不然会致使网络数据窜包。算法

链接缓冲区内存分配常采用slab内存分配策略,能够直接实现slab算法(如memcached),但推荐直接采用jemalloctcmalloc等(如redis)。

 

 

4、accept阻塞性

阻塞型listen监听套接字,其accept时也可能会存在小几率阻塞。

accept队列为空时,对于阻塞套接字时accept会致使阻塞,而非阻塞套接字则当即返回EAGAIN错误。所以bindlisten后应该将其设置为非阻塞,并在accept时检查是否成功。

此外listen_fd有可读事件时不该仅accept一次,而最好循环accept直到其返回-1

 

 

5、异步链接

网络服务器常须要链接到其它后端服务器,但做为服务器阻塞链接是不可接受的,所以须要异步链接。

异步链接时首先须要建立socket并设置为非阻塞,而后connect链接该套接字便可。若connect返回0则表示链接当即创建成功;不然须要根据errno来判断是链接出错仍是处于异步链接过程;若errnoEINPROGRESS则表示仍然处于异步链接链接,须要epoll来监听socket的可写事件(注意不是可读事件)。当可写后经过getsockopt来获取错误码(即getsockopt(c->sfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len);),若getsockopt返回0且错误码err0则表示链接创建成功,不然链接失败。

因为网络异常或后端服务器重启等缘由,网络服务器须要可以自动异步断线重连,同时也应该避免后端服务器不可用时无限重试,所以须要一些重连策略。假设须要存在最多M条链接到同类型后端服务器集群的网络链接,若当前有效网络链接断开且当前链接数(包括有效和异步链接中的链接)少于M/2时则当即进行异步链接。若该链接为异步链接失败则不能进行再次链接,以防止远程服务器不可用时无限重连。当须要使用链接时,则可在M条链接随机取N次来获取有效链接,若遇到不可用链接则进行异步链接。若N次仍获取不到有效链接则循环M条链接来获得有效链接对象。

 

 

6、异步域名解析

当仅知道后端服务器的域名时,异步链接前须要先域名解析出远程服务器的IP地址(如WeQuiz接入层),一样,阻塞式域名解析对于网络服务器来讲也不是好方式。

幸亏linux系统提供getaddrinfo_a函数来支持异步域名解析。getaddrinfo_a函数能够同步或异步解析域名,参数为GAI_NOWAIT时表示执行异步解析,函数调用会当即返回,但解析将在后台继续执行。异步解析完成后会根据sigevent设置来产生信号(SIGEV_SIGNAL)或启动新线程来启动指定函数(SIGEV_THREAD)。

struct gaicb* gai = (gaicb*)calloc(1, sizeof(struct gaicb));

gai->ar_name = config_ get_dns_url(); /* url */

struct sigevent sig;

sig.sigev_notify = SIGEV_SIGNAL;

sig.sigev_value.sival_ptr = gai;

sig.sigev_signo = SIGRTMIN; /* signalfd/epoll */

getaddrinfo_a(GAI_NOWAIT, &gai, 1, &sig);

对于异步完成后产生指定信号,须要服务器进行捕获该信号并进一步解析出IP地址。为了可以在epoll框架中统一处理网络链接、进程间通讯、定时器与信号等,linux系统提供eventfdtimerfdsignalfd等。在此建立dns_signal_fd = signalfd(-1, &sigmask, SFD_NONBLOCK|SFD_CLOEXEC));并添加到epoll中;当异步完成后产生指定信号会触发dns_signal_fd可读事件;由read函数读取到signalfd_siginfo对象,并经过gai_error函数来判断异步域名解析是否成功,若成功则可遍历gai->ar_result获得IP地址列表。

 

 

7、热更新

热更新是指更新可执行文件时正在运行逻辑没有受到影响(如网络链接没有断开等),但新网络链接处理将会按更新后的逻辑处理(如玩家登录等)。热更新功能对接入层服务器(如游戏接入服务器或nginx等)显得更加剧要,由于热更新功能大部分时候能够避免停机发布,且随时重启而不影响当前处理链接。

WeQuiz手游接入服务器中热更新的实现要点:

1)在父进程中建立listenfdeventfd,而后建立子进程、监听SIGUSR1信号并等待子进程结束;而子进程将监听listenfdeventfd,并进入epoll循环处理。

2)当须要更新可执行文件时,发送SIGUSR1信号给父进程则可;当父进程收到更新信号后,其经过eventfd来通知子进程,同时fork出新进程并execv新可执行文件;此时存在两对父子进程。

3)子进程经过epoll收到eventfd更新通知时,则再也不监听并关闭listenfdeventfd。因为关闭listenfd则没法再监听新链接,但现有网络链接与处理则不受影响,不过其处理还是旧逻辑。当全部客户端断开链接后,epoll主循环退出则该子进程结束。值得注意的是,因为没法经过系统函数来获取到epoll处理队列中的链接数,则须要应用层维持当前链接数,当其链接数等于0时则退出epoll循环。此时新子进程监听listenfd并处理新网络链接。

4)当旧父进程等待到旧子进程退出信号后则也结束,此时仅存在一对父子进程,完成热更新功能。

 

 

8、过载保护

对于简单网络服务器来讲,达到100W级链接数(8G内存)与10W级并发量(千兆网卡)基本没问题。但网络服务器的逻辑处理比较复杂或交互消息包过大,若不对其进行过载保护则可能服务器不可用。尤为对于系统中关键服务器来讲(如游戏接入层),过载可能会致使长时间没法响应甚至整个系统雪崩。

络服务器的过载保护常有最大文件数、最大链接数、系统负载保护、系统内存保护、链接过时、指定地址最大链接数、指定链接最大包率、指定链接最大包量、指定链接最大缓冲区、指定地址或id黑白名单等方案。

1)最大文件数

能够在main函数中经过setrlimit设置RLIMIT_NOFILE最大文件数来约束服务器所能使用的最大文件数。此外,网络服务器也经常使用setrlimit设置core文件最大值等。

2)最大链接数

因为没法经过epoll相关函数获得当前有效的链接数,故须要应用服务器维持当前链接数,即建立链接时累加并在关闭时递减。能够在accept/accept4接受网络链接后判断当前链接数是否大于最大链接数,若大于则直接关闭链接便可。

3)系统负载保护

经过定时调用getloadavg来更新当前系统负载值,可在accept/accept4接受网络链接后检查当前负载值是否大于最大负载值(如cpu* 0.8*1000,若大于则直接关闭链接便可。

4)系统内存保护

经过定时读取/proc/meminfo文件系统来计算当前系统内存相关值,可在accept/accept4接受网络链接后检查当前内存相关值是否大于设定内存值(如交换分区内存占用率、可用空闲内存与已使用内存百分值等),若大于则直接关闭链接便可。

g_sysguard_cached_mem_swapstat = totalswap == 0 ? 0 : (totalswap - freeswap) * 100 / totalswap;

g_sysguard_cached_mem_free = freeram + cachedram + bufferram;

g_sysguard_cached_mem_used = (totalram - freeram - bufferram - cachedram) * 100 / totalram;

5)链接过时

链接过时是指客户端链接在较长时间内没有与服务器进行交互。为防止过多空闲链接占用内存等资源,故网络服务器应该有机制可以清理过时网络链接。目前经常使用方法包括有序列表或散列表等方式来处理,但对后端服务器来讲,轮询总不是最佳方案。QQ宠物与WeQuiz接入层经过每一链接对象维持惟一timerfd描述符,而timerfd做为定时机制可以添加到epoll事件队列中,当接收该链接的网络数据时调用timerfd_settime更新空闲时间值,若空闲时间过长则epoll会返回并直接关闭该链接便可。虽然做为首次尝试(至少本人没有看到其它项目中采用过),但接入服务器一直以来都比较稳定运行,应该能够放心使用。

c->tfd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC) ;

struct itimerspec timerfd_value;

timerfd_value.it_value.tv_sec = g_cached_time + settings.sysguard_limit_timeout;

timerfd_value.it_value.tv_nsec = 0;

timerfd_value.it_interval.tv_sec = settings.sysguard_limit_timeout;

timerfd_value.it_interval.tv_nsec = 0;

timerfd_settime(c->tfd, TFD_TIMER_ABSTIME, &timerfd_value, NULL) ;

add_event(c->tfd, AE_READABLE, c) ;

6)指定地址最大链接数

经过维持key为地址value为链接数的散列表或红黑树,并在在accept/accept4接受网络链接后检查该地址对应链接对象数目是否大于指定链接数(如100,若大于则直接关闭链接便可。

7)指定链接最大包率

链接对象维持单位时间内的服务器协议完整数据包数目,读取网络数据后则判断是否为完整数据包,若完整则数目累加,同时若当前读取数据包间隔大于单位时间则计数清零。当单元时间内的完整数据包数目大于限制值(如80)则推迟处理数据包(即仅收取到读取缓冲区中而暂时不处理或转发数据包),若其数目大于最大值(如100)则直接断开链接便可。固然也能够不须要推迟处理而直接断开链接。

8)指定链接最大数率

链接最大数率与链接最大包率的过载保护方式基本一致,其区别在于链接最大包率针对单位时间的完整数据包数目,而链接最大数率是针对单位时间的缓冲区数据字节数。

9)指定链接最大缓冲区

可在recv函数读取网络包后判断该链接对象的可读缓冲区的最大值,若大于指定值(如256M)则可断开链接;固然也能够针对链接对象的可写缓冲区;此外,读取完整数据包后也可检查是否大于最大数据包。

10)指定地址或id黑白名单

     能够设置链接ip地址或玩家id做为黑白名单来拒绝服务或不受过载限制等,目前WeQuiz暂时没有实现此过载功能,而将其放到大区logicsvr服务器中。

此外,还能够设置TCP_DEFER_ACCEPTSO_KEEPALIVE等套接字选项来避免无效客户端或清理无效链接等,如开启TCP_DEFER_ACCEPT选项后,若操做系统在三次握手完成后没有收到真正的数据则链接一直置于accpet队列中,而且当同一客户端链接(但不发送数据时)达到必定数目(如linux2.6+系统16左右)后则没法再正常链接;如开启SO_KEEPALIVE选项则能够探测出因异常而没法及时关闭的网络链接。

setsockopt(sfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, (void*)&flags, sizeof(flags));

setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (int[]){1}, sizeof(int));

          setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, (int[]){600}, sizeof(int));

          setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, (int[]){30}, sizeof(int));

          setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, (int[]){3}, sizeof(int));

 

 

9、超时或定时机制

超时或定时机制在网络服务器中基本必不可少,如收到请求后须要添加到超时列表中以便没法异步处理时可以超时回复客户端并清理资源。对于服务器来讲,超时或定时机制并不须要真正定时器来实现,能够经过维持超时列表并在while循环或epoll调用后进行检测处理便可。

定时器管理常使用最小堆(如libevent)、红黑树(如nginx)与时间轮(如linux)等方式。

应用层服务器一般没必要本身实现最小堆或红黑树或时间轮等方式来实现定时器管理,而可采用stlboost中多键红黑树来管理,其中超时时间做为键,相关对象做为值;而红黑树则自动按键排序,检测时仅须要从首结点开始遍历,直到键值大于当时时间便可;固然能够获得首结点的超时时间做为epoll_wait的超时时间。此外,游戏服务器上大区逻辑服务器或实时对战服务器也常须要持久化定时器,能够经过boost库将其持久化到共享内存。

1)定时器管理对象

typedef std::multimap<timer_key_t, timer_value_t> timer_map_t;

typedef boost::interprocess::multimap<timer_key_t, timer_value_t, std::less<timer_key_t>, shmem_allocator_t> timer_map_t;

 

2)定时器类

class clock_timer_t

{

public:

    static clock_timer_t &instance() {static clock_timer_t instance; return instance;         }

     static uint64_t rdtsc() {

                            uint32_t low, high;

                            __asm__ volatile ("rdtsc" : "=a" (low), "=d" (high));

                            return (uint64_t) high << 32 | low;

                   }

                   static uint64_t now_us() {

                            struct timespec tv;

                            clock_gettime(CLOCK_REALTIME, &tv);

                            return (tv.tv_sec * (uint64_t)1000000 + tv.tv_nsec/1000);

                   }

                   uint64_t now_ms() {

                            uint64_t tsc = rdtsc();

                            if (likely(tsc - last_tsc <= kClockPrecisionDivTwo && tsc >= last_tsc)) {

                                     return last_time;

                            }

                            last_tsc = tsc;

                            last_time = now_us() / 1000;

                            return last_time;

                   }

private:

                   const static uint64_t kClockPrecisionDivTwo = 500000;

                   uint64_t last_tsc;

                   uint64_t last_time;

                   clock_timer_t() : last_tsc(rdtsc()), last_time(now_us()/1000) { }

                   clock_timer_t(const clock_timer_t&);

                   const clock_timer_t &operator=(const clock_timer_t&);

};

 

3)超时检测函数(whileepoll循环中调用),能够返回超时对象集合,也能够返回最小超时时间。

timer_values_t xxsvr_timer_t::process_timer()

{

                   timer_values_t ret;

                   timer_key_t current = clock_timer_t::instance().now_ms();

                   timer_map_it it = timer_map->begin();

                   while (it != timer_map->end()) {

                            if (it->first > current) {

                                     return ret; //返回超时对象集合,return it->first - current返回超时时间则.

                            }

                            ret.push_back(it->second);

timer_map->erase(it++);

                   }

                   return ret;

}

 

 

10、网络模型

Linux存在阻塞、非阻塞、复用、信号驱动与异步等多种IO模型,但并不是每一类型IO模型均能应用于网络方面,如异步IO不能用于网络套接字(如linux)。经过不一样设计与相关IO模型能够概括出一些通用的网络模型,如经常使用的异步网络模型包括reactorproactor、半异步半同步(hahs)、领导者跟随者(lf)、多进程异步模型与分布式系统(server+workers)等。

1reactor

Reactor网络模型常指采用单进程单线程形式,以epoll为表明的IO复用的事件回调处理方式。此网络在网络服务器开发方面最为经常使用(如redis),尤为对于逻辑相对简单的服务器,由于其瓶颈不在于cpu而在网卡(如千兆网卡)。

 

2proactor

Proactor网络模型通常采用异步IO模式,目前经常使用于window操做系统,如完成端口 IOCP;在linux能够在socket描述符上使用aio,而macosx中没法使用。尝试过socket + epoll + eventfd + aio模式,但没法成功;不过测试socket + sigio(linux2.4主流) + aio则能够。在linux服务器开发方面,异步IO通常只用于异步读取文件方面,如nginx中使用filefd + O_DIRECT + posix_memalign + aio + eventfd + epoll模式(可禁用),但其也未必比直接读取文件高效;而写文件与网络方面基本不采用异步IO模式。

 

3)半异步半同步(hahs

半异步半同步模型(HalfAsync-HalfSync)常采用单进程多线程形式,其包括一个监听主线程与一组工做者线程,其中监听线程负责接受请求,并选取处理当前请求的工做线程(如轮询方式等),同时将请求添加该工做线程的队列,而后通知该工做线程处理之,最后工做线程处理并回复。对于hahs模式,全部线程(包括主线程与工做线程)均存在各自的epoll处理循环,每一工做线程对应一个队列,主要用于主线程与工做线程间数据通讯,而主线程与工做线程间通知通讯常采用pipe管道或eventfd方式,且工做线程的epoll会监听该通知描述符。hahs模式应用也比较普遍,如memcachedthrift等,此外zeromq消息队列也采用相似模型。

/* 主线程main_thread_process */

while (!quit) {

ev_s = epoll_wait(...);

for (i = 0; i < ev_s; i++) {

if (events[i].data.fd == listen_fd) {

    accept4(….);

} else if (events[i].events & EPOLLIN) {

recv(…);

select_worker(…);

send_worker(…);

notify_worker(…);

}

}

/* 工做线程worker_thread_process */

while (!quit) {

ev_s = epoll_wait(...);

for (i = 0; i < ev_s; i++) {

if (events[i].data.fd == notify_fd) {

read(….);

do_worker(…);

}

}

}

 

4)领导者跟随者(lf

领导者跟随者模型(Leader-Follower)也常采用单进程多线程形式,其基本思想是一个线程做为领导者,而其他线程均为该线程的跟随者(本质上为平等线程);当请求到达时,领导者首先获取请求,并在跟随者中选取一个做为新领导者,而后继续处理请求;在实现过程当中,全部线程(包括领导者与跟随者线程)均存在自各的epoll处理循环,其经过平等epoll等待,并用加锁方式来让系统自动选取领导线程。lf模式应用也比较普遍,如webpcl与一些java开源框架等。lf模式与hahs模式均可以充分利用多核特性,对于逻辑相对复杂的服务器其有效提升并发量。对于lf模式,全部线程都可平等利用epoll内核的队列机制,而hahs模式须要主线程读取并维持在工做线程的队列中,故本人比较经常使用lf模型,如QQPetWeQuiz项目中接入服务器。

while (!quit) {

         pthread_mutex_lock(&leader);

Loop:

         while (stats.curr_conns && !loop.nready && !quit)

                   loop.nready = epoll_wait(...);

         if (!quit) {

                   pthread_mutex_unlock(&leader);

                   break;

         }

         loop.nready--;

         int fd = loop.fired[loop.nready];

         conn *c = loop.conns[fd];

         if (!c) { close(fd); goto Loop; }

         loop.conns[fd] = NULL;

         pthread_mutex_unlock(&leader);

         do_worker(c);

}

 

5)多进程异步模型

多进程异步模型(Leader-Follower)常采用主进程与多工做进程形式,主要偏用于没有数据共享的无状态服务器,如nginxlighttpdweb服务器;其主进程主要用于管理工做进程组(如热更新或拉起异常工做进程等),而工做进程则同时监听与处理请求,但也容易引发惊群,能够经过进程间的互斥锁来避免惊群(如nginx)。

 

综上所述,经常使用网络模型各有优缺点,如reacor足够简单,lf利用多核等。但其实有时并没必要太过于在乎单台服务器性能(如链接数与并发量等),更应该着眼于总体架构的可线性扩容方面等(如网络游戏服务器)。固然一些特定应用服务器除外,如推送服务器偏向链接数,web服务器偏向并发量等。此外,阅读nginxzeromqredismemcached等优秀开源代码来有效提升技术与设计能力,如Nginx可达到几百万链接数与万兆网络环境至少可达50RPSzeromq采用相对独特设计让其成为最佳消息队列之一。

 

 

11、架构

系统架构每每依赖于具体业务,限于篇幅仅简述WeQuiz手游服务器的总体架构设计。游戏常采用接入层、逻辑层与存储层的通用三层设计,结合目录服务器与大区间中转服务器等构成整个游戏框架。但不一样于端游页游,手游具备弱网络、碎片玩法与强社交性等特色,故总体架构不只须要优雅解决断线重连,还能够作到简化管理、负载均衡、有效容灾与方便扩容等。架构层面解决:引入转发层。

转发层能够避免因网络环境或碎片玩法等致使玩家频繁换大区而不断加载数据问题,维持玩家在线大区信息,同时管理所有服务器信息与维持其存活性,其链接星状结构也有效解耦服务器间关联性,让内部服务器不需关心其它服务器,从而简化总体架构。

1)断线重连:转发层router维持玩家大区信息,不管从那个接入层进入都可以到达指定大区,从而不会致使玩家数据从新加载等问题。

2)简化管理:仅须要router维持全部服务器信息,其它服务器均不须要任何服务器信息(包括router与同类服务器)。好比大区服务器须要判断两个玩家是否为好友,仅须要调用router提供接口发送便可,不用指定任何地址,也不用关心好友服务器的任何信息(好比服务器的地址与数目及存活等)。其中router接口封装tbus读写功能、自动心跳回复与映射关系回调构建功能,还维持全部router列表与最新存活router服务器。

3)负载均衡:对于router来讲,采用最近心跳机制,其它服务器须要转发包时总会向最近收到心跳的router服务器发送。经统计,全部router转发量基本一致。而其它服务器存在多种转发模式,好比大区服务器,若新用户上线则选择大区人数最少大区转发;其它服务器采用取模或随机方式,基本作到负载均衡。

4)有效容灾:主要是基于心跳机制,router会定时发送心跳来探测全部服务器存活,当三次没收到心跳回复,则将其标记为不可用,转发时再也不向该服务器转发。同时还会向该服务器发送间隔较大的心跳探测包(目前使用60秒),以便服务器恢复后能够继续服务。若是router挂掉,则其它服务器不会收到该router心跳包,天然不会向其发包。

5)方便扩容:若是须要添加其它服务器,仅须要向router配置文件的对应集群中添加新服务器,router随后会向该服务器发送探测心跳,收到心跳回复后则能够正常服务。若是须要添加router,仅须要复制一份router,其它服务器都不须要修改任何信息。Router会自动重建映射关系(发三次重建请求,若是失败则将该大区去除),成功后再向全部服务器发送心跳包以表示router此时能够正常服务,而其它服务器收到router心跳包则将其维持到router列表(相关功能均由router接口自动完成)。

 

 

12、协程

协程在pythonluago等脚本语言获得普遍应用,而且linux系统也原生支持c协程ucontext。协程能够与网络框架(如epolllibeventnginx等)完美结合(如gevent等);通常作法是收到请求建立新协程并处理,若遇到阻塞操做(如请求后端服务)则保存上下文并切换到主循环中,当可处理时(如后端服务器回复或超时)则经过上下文来找到指定协程并处理之。对于网络层的阻塞函数,能够经过dlsym函数来挂载相应的钩子函数,而后在钩子函数中直接调用原函数,并在阻塞时切换处理,这样应用层则能够直接调用网络层的阻塞函数而没必要手动切换。

游戏服务器通常采用单线程的全异步模式,直接使用协程模式可能相对比较少,但在一些cgi调用形式的web应用(如游戏社区或运营活动等)则逐步获得应用。好比QQ宠物社区游戏原来采用apache+cgi/fcgi模式的阻塞请求处理,基本仅能达到每秒300并发量,经过strace观察到时间基本消耗在网络阻塞中,因此须要寻求一种代码尽可能兼容但能提升吞吐量的技术,从而协程成为最佳选择,即采用libevent+greenlet+python来开发新业务,而选择nginx+module+ucontext来重用旧代码,最后作到修改不到20行代码则性能提升20倍(siege压测实际业务可达到8kQPS)。

 

 

十3、其它

网络服务器方面除了基本代码开发之外,还涉及到构建、调试、优化、压测与监控等方面,但因为最近新手游项目开发任务比较重,将后期再逐步总结,现仅简单罗列一下。

1)构建

一直以来都使用cmake来构建各种工程(如linux服务器与window/macosx客户端程序等),体会到cmake是最优秀的构建工具之一,其应用也比较普遍,如mysqlcocos2dxvtk等。

project(server)

add_executable(server server.c)

target_link_libraries(server pthread tcmalloc)

cmake .; make; make install

 

2)调试

网络服务器开发调试大部分状况均可以经过日志来完成,必要时能够经过gdb调试,固然也能够在Linux系统下直接使用eclipse/gdb来可视化调试。

当程序异常时,有core文件直接使用gdb调试,如bt full查看全栈详细信息或f跳到指定栈p查看相关信息;没有core文件时则能够查看/var/log/message获得地址信息,而后经过addr2lineobjdump来定位到相关异常代码。

对于服务器来讲,内存泄漏检测也是必不可少的,其中valgrind为最佳的内存泄漏检测工具。

此外,其它经常使用的调试工具(编译阶段与运行阶段)有nmstringsstripreadelflddpstackstraceltracemtrace等。

 

3)优化

网络服务器优化涉及算法与技术等多个方面。

算法方面须要根据不一样处理场景来选择最优算法,如九宫格视野管理算法、跳跃表排行算法与红黑树定时器管理算法等,此外,还能够经过有损服务来设定最佳方案,如WeQuie中采用到的有损排行榜服务。

技术方面能够涉及到IO线程与逻辑分离、slab内存管理(如jemalloctcmalloc等)、socket函数(如accept4readvwritevsendfile64等)、socket选项(如TCP_CORKTCP_DEFER_ACCEPTSO_KEEPALIVETCP_NODELAYTCP_QUICKACK等)、新实现机制(如aioO_DIRECTeventfdclock_gettime等)、无锁队列(如CASboost::lockfree::spsc_queuezmq::yqueue_t等)、异步处理(如操做mysql时采用异步接口库libdrizzlewebscalesqlmongodbredis异步接口与gevent类异步框架等)、协议选择(如httppb类型)、数据存储形式(如mysqlblob类型、mongodbbjson类型或pb类型等)、存储方案(如mysqlmongodbredisbitcaskleveldbhdfs等)、避免惊群(如加锁避免)、用户态锁(如nginx经过应用层的CAS实现(更好跨平台性))、网络状态机、引用计数、时间缓存、CPU亲缘性与模块插件形式(如pythonlua等)。

经常使用的调优工具备valgrindstraceperfgprofgoogle-perftools等,如valgrindcallgrind工具,能够在须要分析代码段先后加上CALLGRIND_START_INSTRUMENTATION; CALLGRIND_TOGGLE_COLLECT; CALLGRIND_TOGGLE_COLLECT; CALLGRIND_STOP_INSTRUMENTATION;,而后运行valgrind --tool=callgrind --collect-atstart=no --instr-atstart=no ./webdir便可,获得分析结果文件还可用Kcachegrind可视化展现。

除了提升服务器运行效率外,还能够经过一些开发包或开源库来提升服务器开发效率,如采用boost库管理不定长对象的共享内存、python协程与go框架等。

 

4)压测

对于网络服务器来讲,压力测试过程必不可少,其可用于评估响应时间与吞吐量,也能够有效检查是否存在内存泄漏等,为后期修正与优化提供依据。

对于http服务器,经常使用absiege等工具进行压测,如./siege –c 500 –r 10000 –b –q http://10.193.0.102:8512/petcgi/xxx?cmd=yyy

对于其它类型服务器通常都须要本身编写压测客户端(如redis压测工具),经常使用方法是直接建立多线程,每一线程使用libevent建立多链接与定时器等来异步请求与统计。

此外,若须要测试大量链接数,则可能须要多台客户机或建立多个虚拟ip地址。

 

5)高可用性

服务器的高可用性实现策略包括主从机制(如redis等)、双主机制(如mysql+keepalive/heartbeat)、动态选择(如zookeeper)与对称机制(如dynamo)等,如双主机制可由两台等效机器的VIP地址与心跳机制来实现,经常采用keepalive服务,固然也能够由服务器自主实现,如服务器启动时须要指定参数来标识其为主机仍是从机,同时主备须要经过心跳包来保持异常时切换,如

void server_t::ready_as_master()

{

  primary = 1; backup = 0;

  system("/sbin/ifconfig eth0:havip 10.2.2.147 broadcast 10.2.2.255 netmask 255.255.255.0 up"); //! 虚拟IP

  system("/sbin/route add -host 10.2.2.147 dev eth0:havip");

  system("/sbin/arping -I eth0 -c 3 -s 10.2.2.147 10.2.2.254");

  up("tcp://10.2.2.147:5555");

}

void server_t::ready_as_slave()

{

  primary = 0; backup = 1;

  system("/sbin/ifconfig eth0:havip 10.2.2.147 broadcast 10.2.2.255 netmask 255.255.255.0 down");

  down("tcp://10.2.2.147:5555");

}

固然这是相对简单方式(其前提是主备机器都可正常通讯),没有考虑到异常状况(如主备机器间的网线断开状况等),此时能够考虑用双中心控制与动态选举择模式等。

 

6)监控

Linux在服务器监控方面工具很是丰富,包括pstoppingtraceroutenslookuptcpdumpnetstatsslsofncvmstatiostatdstatifstatmpstatpidstatfreeiotopdfdudmesggstackstracesar(如-n/-u/-r/-b/-q等)及/proc等,如ps auxw查看进程标记位(通常地D阻塞在IORcpuS表示未能及时被唤醒等),gstack pid查看进程当前栈信息,ss -s查看链接信息,sar -n DEV 1 5查看包量,sar -r 1 5查看内存使用状况,vmstat 1 5查看进程切换频率,iotopiostat -tdx 1dstat -tclmdny 1查看磁盘信息与mpstat 2查看CPU信息及/proc/net/sockstat查看socket状态等。此外有时最有效的是服务器日记文件。

 

十4、结束

除了网络服务器基本开发技术以外,系统总体架构更为重要(如可线性扩容性),后期有时间再详细总结,对于网络游戏架构方面可参见WeQuiz手游服务器架构与QQPet宠物架构设计等。

欢迎rtx(baokaichen)email(chenbk@foxmail.com)指正与讨论。Ths

相关文章
相关标签/搜索