Web 服务器架构

很久以前,web服务器就是Apache,或者LAMP这样的标准组合。 手头一个嵌入式项目也直接叫做 web server, 把HTTP + 网页 + 请求处理作为web服务的全部集成在一个程序上了。翻看了一些稍微有些规模的网站,发现现在的web 服务架构已经和我以往所了解的有了很大的不同。在这里正好梳理一下相关的内容,也算是补一下全栈的web sever 技术知识(full stack knowledge of web server)的一部分。

总体结构

实际上并没有web sever的标准架构只说,每个网站都有针对自己服务的需求进行灵活设计的原则。在这里,我只是列举了一个虚拟的 Django 网站作为范例来说明可能使用的架构,及其模块的相互关系。

  • HTTP 服务器
  • 缓存
  • 数据库
  • 消息服务(message broker)
  • 应用
  • 其他服务和其他辅助系统

HTTP 服务器

网站入口是 HTTP 服务器。客户访问到的 80 端口或者 443 端口,就是由这类服务器提供服务的。用户浏览器所得到的数据,也都是冲HTTP服务器上传送过来的。最常见的 HTTP server 有:

  • Nginx
  • Apache

对HTTP服务器的要求就是在稳定可靠的前提下,提高单位时间内的对如下内容的请求处理数量 (TPS) :

  • 传送静态的文件(静态文件服务)
  • 转发其他服务的数据(反向代理)

其他的功能都已经削弱了。HTTP就是做一个城堡大门的作用。这个大门又要非常的可靠,又能有足够的通过能力,不要让内外的交流产生拥堵。由于整个城堡只有这么一个大门,安全性也极大地得到了保障。

静态文件如果是媒体文件,也常用 CDN 服务来加速。这样又加快了各个地区的访问速度,有节省了web服务的带宽。不过随着前端的快速发展,网页前端中也包含了大量的 web 应用程序,关于这些前端网页的加载和缓存技术就不包含在这里具体谈了(像是PWA, AMP, MIP ……)

反向代理在web服务中占据了非常重要的地位,百川汇流到HTTP端口就是在这里实现的。

如果网站的规模已经让一个HTTP服务器无法应对,还可能要在域名服务上做文章。通过访问客户的IP地址,将服务器的域名解析到一个离客户最近的HTTP服务器,从而进行访问分流,负载的平衡。流量费用也就是一个恐怖的数了。不过换句话,做到这么大规模的服务,就不差钱了。

缓存层

缓存的存在主要是为了照顾数据库而发展起来的。MySQL 这样的数据库每秒处理请求的数量级和内存中已有的缓存相比至少有两个以上的数量级差(啰嗦了点,就是至少慢100倍以上)。 有的复杂查询,差一下要几十秒,但其实来绝大多数时间从应用的逻辑上,我们知道这个查询结果是不会变更的,只要缓存起来下次直接调用就好了。

常见的缓存层有两个,都是通过key-value来存储数据在内存(以及硬盘)上来加快数据访问的:

  • Memcached
  • Redis

区别就是Redis 更像数据库一些,数据结构,查询,处理、持久化都能做,如果不是特别担心持久化上风险,直接用它做NoSQL数据库也是可以的。不过,还是应该让专业的人做专业的事情,在这里还是简单的将它作为缓存工具来对待。

不过缓存层一般还是只是用来做数据缓冲,加速,并不会参加数据的一致性和计算工作。比如Redis最通用的工作流如下,总是首先尝试查看缓存中的数据然后再进行复杂的操作来得到这些数据:

if cache.has_key('complex-query'):
    return cache.get('complex-query')
else:
    result = do_complex_query()
    cache.set('complex-query', result)
    return result

数据库

数据库就基本没有什么可以说的了,什么网站都要用。不过除了静态文件对网站是一个打的考验,数据库常常是一个网站最大的性能瓶颈。大量的 DBA 在性能和可靠之间不眠不休的进行着永无终结的优化。

这里有些 web 应用最常见的数据库:

  • MySQL
  • PostgreSQL
  • SQLite
  • MongoDB

其中 MySQL和 PostgreSQL 算是最标准的数据库服务器了。SQLite 放在这里有点不正经,不过加上了缓存的情况下,很多时候即便是这样一个单线程、单文件数据库都能把一个网站转的很流畅。 MongoDB 由于直接存储 JSON 文档,配 JavaScript 还是挺合适的,也是很有特色的一个数据库。当然还有大量各种有特色的数据库(比如12306订票网站),这里就不再讨论了。

如果是一个小型的网站,一个数据库在缓存的配合下,性能已经是非常高了。如果访问数据超越了单个数据库服务器的负载,而且无法通过优化数据库来加速,就要考虑更进一步的升级:

  • 数据库的读写分离:这个比较常见,因为80%的访问都是读取数据,写入量比较少。而且写入的操作涉及的操作更加复杂。读取数据库和写入数据库分开,只读数据库从写入数据的数据库同步。如果规模稍大,还可以有多个只读数据,一个写入数据库。
  • 集群数据库服务器:非常复杂~
  • 云数据库:直接上阿里,腾讯、亚马逊的数据库服务

随着现代数据库不断加速查询速度,并设计了非常高效的缓存机制,数据库也越来越像Redis这样的内存数据库靠近了,也许有一天这两者会合并也不一定。

消息服务

这是一个让我有点困惑的地方,为啥有一个消息服务层出现呢。有什么事数据库放不下的,又或者缓存层效率还满足不了的呢?

解释起这一部分的内容,可能先要跳到应用来。现在的 web 服务包含了大量的后台操作,比如一个博客网页:

  • 发表文章并提交
    • 新文章上媒体要针对不同的设备做分辨率调整
    • 新文章项目要增加到RSS上
    • 历史文章列表要更新
    • 更新博客排名
    • 启动SEO 优化操作
    • 更新缓存
  • 等所有信息更新完毕后
    • 通知关注本博客的用户
    • 发邮件给管理员
    • 同步文章到其他的网站
    • 内容更新后考虑释放后要备份

如果这些工作都要添加到发表博客这个应用中,那这个功能就会越来越复杂,响应也越来越慢。随着添加新的功能,而且考虑到每项工作之间前后顺序的影响,整个程序就很难维护。不过如果仔细分析,像是发邮件通知xxx之类的操作根本就不是一个需要立刻完成的操作,如果把这些缓慢的操作集成到主流程中,就会让用户感觉响应非常迟缓。

而消息流,通过消息的产生者和消费者,这样一种简单的模式,将所有操作拆解为多个独立的程序,他们通过相互发送邮件(消息)的方式来通知对方,有秩序的完成一系列的操作。而消息代理 (message broker) 就是这其中的核心组件。

常见的消息代理有

  • RabbitMQ – 很早不过很小巧的一个消息代理程序
  • ActiveMQ – 和前一个很像
  • Kafka – 来自Apache和LinkedIn的大型消息代理

应用

如何使用以上这些组件的核心就是应用本身,这里也可以分为三部分

  • 中间件或者框架以及组件
  • web 应用
  • 其他任务

中间件

在这里只用 Django 来举例,这个框架以及其可选的大量组件构成了web 服务的中间件部分。

通过这个中间件,在开发web程序的时候,我们就不用再分析 HTTP 请求,缓存的获取和更新,数据库和模型之间的对应关系……这一切都给中间件给包裹了起来。

Web 应用

这一部分就是整个 web 服务的核心,也就是我们常说的

  • Model – 数据模型
  • Control – 业务逻辑
  • View – 表达

在 Django web 应用中,model就是第一层,views (包含form, template等)就是第三层,对于业务逻辑一般都是根据实际业务的要求所编写的。不过,Django 还是对最常用的 CRUD 操作提供了模板式的通用业务逻辑,来简化这些部分业务的处理。

其他应用

如果回到刚才博客上那个案例,还有很多操作没有必要都集成在主业务逻辑中。 比如发送邮件给管理员,更新博客排名,SEO操作等等等,都没有必要放在服务进程中。他们甚至都没有必要view这个模块,而是直接操作数据库,或者在操作系统中进行一些额外的动作。他们应该是一个个独立的任务,根据消息服务来判断是否需要进行一些必要同步或者异步的操作。

可能的应用

  • 耗时的异步操作
    • 图片的处理
    • AI 数据处理
    • 爬虫
  • 后台优化和通讯
    • 微信接口
    • SEO 优化
    • 缓存优化
  • Celery 程序
    • 用户积分,奖励等计算和处理
    • 内部通知分发

为了管理这些主进程之外的同步或者异步操作, 我们也需要一个组件,对应 Django 就是芹菜 celery ,由它来和 Django 程序打交道,按照需要启动相应的任务。

服务支持和维护

如上所说,正是由于 web 服务变得如此复杂,我们也就没有办法简单的启动一下 Apache 就搭建一个现代的 web 服务器了。这些程序之间相互依赖,紧密合作才能让 web 服务达到最优化的效能。

这里有一些能想到的组件

  • 服务进程看护: 如supervisor ,可以保证django,celery服务的持续性
  • 数据文件的备份和同步
  • 服务器状态监控
  • SSL证书维护
  • 邮件服务器
  • 其他定时任务

总结

现代的web 服务器已经和几年前有了本质的差别,从一个高度集成的系统,变成了耦合变少,模块化更清晰的系统。这种变革,个人觉得是两个原因

  • 性能的优化
  • 开发的便利

首先,从上面的很多模块中都可以看到web服务对性能无止境的追求。网页打开速度快100毫秒,对很多网站就能获得几十亿的业务。所以在性能上的投资也是不惜血本的。通过将性能的瓶颈不断的分拆,系统也可以从一个单独的服务器变成一个跨洲际的分布式集群系统来最大化性能,保证可靠性。

开发者而言,过多的模块化是利好,也是不利的消息。不过这时候架构师的思想在如消息服务这样的设计上为开发者提供了非常灵活强大,却又很轻松耦合应用模块设计方案。让应用工程师可以基本上独立的开发各种模块来提供丰富额功能,同时又不用太担心对整个系统性能的影响。

这种模式的推广,也推动了时下很流行的 DevOps – 开发运营模式。甚至有时候觉得 function as a service 真的是非常迫在眉睫的事情了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注