现在的位置: 首页 > 综合 > 正文

[原创] 超越nginx

2014年04月13日 ⁄ 综合 ⁄ 共 3699字 ⁄ 字号 评论关闭
     文章是我在baidu的博客上写的,这里专业人士比较多,以后就在这里首发了,这个题目有些大(总要吸引一下游客么) ,全面超越Nginx是很困难的一件事情,毕竟运营了那么长时间,稳定性+性能+扩展性才是综合指标,现在说的超越指的是在内存控制和性能两个简单的方面,就算仅仅是代码书写上,Nginx也是我见过的最规范的c代码,超越无从谈起。下面和正文:
     当初打算扩展修改nginx,因为代码的阅读困难也就放弃了,很多事情都是作起来比想象的容易,自己写代码之初并没有指望程序能有所突破,在局部超越
nginx,等走到这一步回过头来看看。有种“山穷水尽疑无路,柳暗花明又一村”的感觉,说实话,nginx的代码写得够好了,至少对于c的代码我很少见
过比他更优雅的,毕竟也是一个人写的代码,能跟apache有得一拼甚至超越(代码规范清晰方面),这很不容易了,但即使是这样的代码,没有作者的中文文
档我们阅读代码也是很难懂的,我就是被吓了回来,在linux下基本的调试环境都建立不起来,归根结底还是不理解他的模型,nginx的代码有别于一般的
c工程代码,他很多文件是在编译时选择的(类似于apache),模块是根据配置文件生成一个c文件然后编译的,所以象我这样的新手就很难理解和调试,没
办法建立起来调试环境还何谈理解其架构?
    幸好,这是个互联网的时代,牛人有的是,尽管很少,网上还是得到了很多的帮助。
  
其中一个就是smartc项目(向作者表示感谢),是一个国内人写的静态服务器和反响代理软件,作者声称自己大量阅读了nginx和lighttpd等代
码,借鉴了其中优秀的部分,但缺点跟nginx一样,代码注释都太少,还好,国人的代码量比较少,很容易很出架构的思路,后来一对比,果然跟nginx的
思路很象,于是大量参阅了它。
   
对于复杂一些的c工程文件,一定要有个合理的架构,不然代码逻辑分支多了之后维护就是分麻烦,对于socket网络程序尤其如此,本身还有一些网络超时、
异常中断等不确定因素,如果都是流水式的调用很难对分配的内存有个合理的控制,用函数指针实现类似于面相对想语言中的委托的方法就是不错的方法
(nginx等都是基于此的),我最开始的时候也是流水式的程序控制,发现程序需要一个数据容器全局表,维护相当麻烦,既然是全局表,在网络变成中往往与
fd挂钩,初始化容器的数量是很大的,而利用率确很低,如果每次增加新的模块就要改动全局表不是一个好主意,你会发现系统开发到一定阶段的时候全局表非常
大而且复杂,很多模块的临时数据都要有个容器,不然对于timeout的异常就很容易造成内存泄露,你无法精确控制问题发生在哪个地方。
    基于函数指针的方法实现handle委托就不一样了,我设置的几个全局表是这样的:
     typedef struct _FdTable {
        int begintime;//开始连接的时间,或者再次重新初始化厚的时间
        struct {
            void *readData;
            void *writeData;
            void *errorData;
            void (*readHandler)(int fd, void *data);
            void (*writeHandler)(int fd, void *data);
            void (*errorHandler)(int fd, void *data);
        }ops;
    }FdTable;
    
一个时间搓、一组操作数据的指针,对于网络上所有的事件都简化抽象成in和out两个事件,然后还有一个errorHandler,这样循环部分就可以不
断地循环事件,然后抽象地处理,具体函数的指针会在不同的函数进行控制,比如对于web服务器是这样一个流程:
     connected触发socketConnecedHandler(int fd, void
*data);,事件到达就将该函数赋值给FdTable的void (*readHandler)(int fd, void
*data),然后监视In事件,SocketIn实践就可以触发SocketReadhandler(int fd, void
*data),在此函数内部就可以调用ParseHttpHeader(int fd, void
*data),如果没有完成一次性的读取,可以循环上一个过程,ParseHttpHeader结束后就可以进入逻辑判断过程:比如对于GET,交给
GetHttpHandler(int fd, void *data),posti交给PostHttpHandler(int fd, void
*data),反向代理交给ProxySendHandler(int fd, void
*data)......等等,然后所有的过程都是不断的循环执行事件引发的handle指针函数,每个过程将相应的错误错里函数赋值给全局容,这样就可
以委托式地进行处理,而不用关心程序执行到哪一个阶段,哪一个逻辑,具体功能由逻辑代码实现,这样的流程就可以让开发者腾出更多的时间用于实现具体功能,
某一个单独的模出现问题也不会影响到其他的业务流程,需要的逻辑数据就放在下一个逻辑的data里面,不需要的执行完毕或则错误就执行相应的
handlError函数,这就是架构的魅力!

    通过一系列的改进,我的webserver总于可以压载100万次无故障,执行效率高于nginx20%(静态文件)左右,下面是基本的ab测试:
    我的server:
Requests per second:    8358.26 [#/sec] (mean)
Time per request:       11.964 [ms] (mean)
Time per request:       0.120 [ms] (mean, across all concurrent requests)
Transfer rate:          1902.01 [Kbytes/sec] received

Connection Times (ms)
              min mean[+/-sd] median   max
Connect:        0    4   1.8      4      21
Processing:     1    6   3.0      6      38
Waiting:        0    4   1.9      3      20
Total:          4   10   3.9     10      55

Percentage of the requests served within a certain time (ms)
50%     10
66%     10
75%     10
80%     11
90%     14
95%     17
98%     21
99%     32
100%     55 (longest request)
nginx:
Requests per second:    6048.14 [#/sec] (mean)
Time per request:       15.508 [ms] (mean)
Time per request:       0.155 [ms] (mean, across all concurrent requests)
Transfer rate:          1902.01 [Kbytes/sec] received

Connection Times (ms)
              min mean[+/-sd] median   max
Connect:        0    0   0.6      0      13
Processing:     3   14   1.8     15      33
Waiting:        2   14   1.7     15      33
Total:          8   15   1.3     15      35

Percentage of the requests served within a certain time (ms)
50%     15
66%     15
75%     15
80%     15
90%     15
95%     16
98%     18
99%     21
100%     35 (longest request)
以上测试我的服务器没有开启任何缓存,都是通过sendfile读取文件发送,100用户模拟10万次访问

    现在总于可以放心的在此基础上去完善功能了,以前总是些一段代码就发现段错误问题,并且在多线程状态下根本追踪不到错误源。
    理解了架构具体代码倒是很简单的事情,不敢说我的服务器比nginx有优势(毕竟需要实践检验),但相信可以满足我的要求:世上的解决方案无数种,但我只要最适合我的哪一种。
   没有打算将这个服务器做成通用的web服务器,因为很多功能我们根本不需要,只是想在一个稳定的高效的静态文件基础之上构建一套服务,这套服务要具备接近于静态文件的处理速度,稳定高效,才会有上层建筑。

抱歉!评论已关闭.