网络编程(一):演进从Apache到Nginx


著作权归作者所有。
商业转载请联系作者获得授权非商业转载请注明出处。
作者大家可以看我的知乎专栏
链接http://zhuanlan.zhihu.com/auxten/20204159
这是一个系列的文章之二之三已经写完了会陆陆续续搬到Linuxtone着急的同学可以看

  • 网络编程二戏说非阻塞网络编程

  • 网络编程三从libevent到事件通知机制


Apache Apache HTTP服务器是 Robert McCool 在1995年写成并在1999年开始在Apache软件基金会的 框架下进行开发。由于Apache HTTP服务器是基金会最开始的一个项目也是最为有名的一个项目 所以通常大家提到Apache这个词都是说的Apache HTTP Server。
Apache web服务器从1996年开始就是互联网上最为流行的HTTP服务器。Apache之所以这么流行 很大程度上是由于相比其他的软件项目在Apache基金会的精心维护下他的文档十分的详尽还有 集成的支持服务。

Apache由于其可变性、高性能和广泛的支持经常是系统管理员的首选。他可以通过一系列 的语言相关的扩展模块支持很多解释型语言的后端而不需要连接一个独立的后端程序。

Apache软件基金会也是利用开源软件盈利的一个范本。时至今日Apache软件基金会 已经枝繁叶茂在基金会名下的开源项目我们耳熟能详的有

  • Apache HTTP Server

  • AntJava的编译工具

  • ActiveMQMQ集群

  • Cassandra强一致的分布式KV数据库

  • CloudStackOpenStack的劲敌

  • CouchDBKV数据库

  • Flume日志收集工具

  • Hadoop、Hbase、Hive

  • Kafka流式计算

  • Lucene开源搜索引擎

  • MavenJava编译&依赖管理工具

  • Mesos分布式协调

  • OpenNLP开源自然语言处理库

  • OpenOffice开源的类Office工具

  • PerlPerl语言

  • Spark分布式计算集群

  • Storm流式计算

  • StrutsJava SSH框架的第二个S

  • SubversionSVN你懂的

  • TclTcl语言

  • ThriftJava网络框架

  • Tomcat大名鼎鼎的Java容器

  • ZooKeeper分布式协调集群

完整的Apache基金会的项目列表参见Welcome to The Apache Software Foundation!

Nginx 2002年一个叫Igor Sysoev的俄罗斯哥们儿貌似俄罗斯叫Igor的人挺多的 写出了一个叫Nginx和Engine X谐音取引擎之义。 那时候有一个时代背景当时C10KConcurrency 10K1万并发问题还是困扰绝大多数 web服务器的一个难题。Nginx利用异步事件驱动的架构写成是C10K问题的一个很好的答卷。 Nginx的第一个公开发行版是在2004年发布的之前都是作为俄罗斯访问量第二的网站Rambler 的内部使用。

Nginx的主要优势在于"轻、快、活":

很低的资源占用甚至能在很多嵌入式设备上运行。

响应速度超快几乎不会由于高并发影响响应速度。

配置灵活广泛的模块支持。

网上关于Apache和Nginx性能比较的文章非常多基本上有如下的定论

  • Nginx在并发性能上比Apache强很多如果是纯静态资源图片、JS、CSS那么Nginx是不二之选。

  • Apache有mod_php、在PHP类的应用场景下比Nginx部署起来简单很多。一些老的PHP项目用Apache 来配置运行非常的简单例如Wordpress。

  • 对于初学者来说Apache配置起来非常复杂冗长的类XML语法甚至支持在子目录放置.htaccess 文件来配置子目录的属性。Nginx的配置文件相对简单一点。

  • Nginx的模块比较容易写可以通过写C的mod实现接口性质的服务并且拥有惊人的性能。 分支OpenResty可以配合lua来实现很多自定义功能兼顾扩展性和性能。

这里我们要着重讨论的是为什么Nginx在并发性能上比Apache要好很多。
想要了解这个问题不得不先做一些铺垫讲讲并发网络编程的一些历史


壹 最原始最原始的网络编程的伪代码大致是这样:

00 listen(port)  # 监听在接收服务的端口上 
01 while True:   # 一直循环 
02  conn = accept()    # 接收连接 
03  read_content = read(conn) # 读取连接发送过来的请求 
04  response = process(conn) # 执行业务逻辑,并得到给客户端回应的内容 
05  conn.write(response) # 将回应写回给连接

我们需要了解最原始的Linux中accept、read、write调用都是 阻塞的现在阻塞也是这些调用的默认行为。这就导致了以上代码只能同时 处理一个连接所以就有了下面的方法

贰 每个连接开一个进程后来大家想到了办法

00 listen(port)  # 监听在接收服务的端口上 
01 while True:   # 一直循环 
02  conn = accept()    # 接收连接 
03  if fork() == 0:
04    # 子进程 
05    read_content = read(conn) # 读取连接发送过来的请求 
06    response = process(conn) # 执行业务逻辑,并得到给客户端回应的内容 
07    conn.write(response) # 将回应写回给连接


用子进程来处理连接父进程继续等待连接进来。但这种方式有如下两个明显的缺陷

  • fork()调用比较费时需要对进程进行内存拷贝。即使现在的Linux普遍 引入了COWCopy On Write技术fork的时候不做内存拷贝只有其中一个 副本发生了write的时候才进行copy加速了fork的效率但fork依旧是个 比较“重”的系统调用。

  • 较多的内存占用也是由于上述的内存复制造成的。


叁 引入线程
得益于之前提过的Linux对于线程的引入上面例子的开进程被换成了开线程 这样上一小节说的两个缺陷都大大的被缓解了。

肆 进程/线程池计算机领域有很多算法或者是方法都会用到一种智慧“空间换时间”。 即用使用更多内存的方式换取更快的运行速度事先创建出很多进程/线程 就像一个池子这样虽然会浪费一部分的内存但连接过来的时候就省去了 开启进程/线程的时间。
但这种方式会有一个比较显著的缺陷当并发数大于进程/线程池的大小的时候 性能就会发生很大的下滑退化成“贰”的情况。

伍 非阻塞&事件驱动那么是不是想要达到高性能就一定要付出高系统资源占用呢 答案是否定的如果我们注意观察生活中的一个细节肯德基和麦当劳的不同 服务方式

  • 肯德基

    • 服务员在前台问“先生/小姐有什么可以帮你”

    • 顾客思考一下点什么比较好“我要xxxxx”

    • 服务员去后台配餐、取餐3分钟过去了“您的餐齐了下一位”

  • 麦当劳

    • 服务员在前台问“先生/小姐有什么可以帮你”

    • 顾客“我要xxxxx”。如果顾客思考超过5秒“后面的顾客请先点” 点完餐前台服务员继续为下一位顾客点餐。后台有别的服务员完成配餐。

可以思考一下这两种运作方式那种比较好

  • 在肯德基如果遇到需要纠结半天吃什么的客户。服务员和后面的顾客 都会陷入较长时间的等候。原因就是如果最前面的客户先让后面的顾客点餐 他想好了还需要较长时间的等候。相比之下麦当劳就更胜一筹。

  • 在麦当劳后面配餐的服务员如果发现有两个订单都要了可乐。他可以 智能地把两个订单的可乐一次性灌好这样会大大的提高效率。各个岗位上 的服务员可以灵活的采用各种方式优化自己的工作效率。

  • 这里肯德基的服务方式就是古老的进程/线程池麦当劳的服务方式 就是一个简单的非阻塞&事件驱动。

  • 那么非阻塞&事件驱动这么好为什么大家没有一开始就采用这种方式呢

      原因有二
                1、非阻塞&事件驱动需要系统的支持提供non-blocking版的整套 系统调用。
                2、非阻塞&事件驱动编程难度较大需要很高的抽象思维能力 把整个任务拆解采用有限状态机编程才能实现。

想要了解给多可以关注

联系Reboot
交流QQ群238757010 新群、365534424 1000大群


你可能感兴趣的:(apache,nginx,网络编程)