前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >剖析Web技术栈(三)

剖析Web技术栈(三)

作者头像
老齐
发布2020-05-14 20:41:38
8480
发布2020-05-14 20:41:38
举报
文章被收录于专栏:老齐教室老齐教室老齐教室

2 Web框架

2.1基本原理

开始Web框架!

正如我多次讨论过的,Web框架的作用是将HTTP请求转换为函数调用,将函数返回值转换为HTTP响应。框架的真正本质是一个层,它通过HTTP和相关协议将工作的业务逻辑连接到Web。该框架负责我们的会话管理,并将URL映射到函数,使我们能够专注于应用逻辑。

在HTTP服务的总体方案中,应该这样认识框架。框架提供的,比如访问数据库、模板引擎和其他系统的接口,都是一个附加功能。作为程序员,你可能会发现这个附加功能很有用,但原则上它并不是我们运用框架的根本原因,我们首批美国框架是因为它充当了业务逻辑和HTTP之间的一个层。

2.2 实施

多亏了Miguel Gringberg撰写的Flask超级教程,我可以非常快地学会Flask。我不会在这里介绍整个教程,因为你可以在他的网站上阅读。我只使用第一篇文章的内容(共23篇!)创建一个非常简单的“Hello,world”应用。另外,同样的应用也可以用Django实现,推荐书籍:《跟老齐学Python:Django实战》。

要运行下面的示例,你需要一个虚拟环境,并且必须使用pip install flask安装。如果你需要更多关于这方面的细节,请阅读相关教程。

app/__init__.py 文件是:

from flask import Flask

application = Flask(__name__)

from app import routes

app/routes.py 文件是:

from app import application


@application.route('/')
@application.route('/index')
def index():
    return "Hello, world!"

你已经在这里看到了一个框架的作用。我们定义了一个index函数,并在3行Python中将它与两个不同的URL(//index)连接起来。这给我们留出了时间和精力来正确处理业务逻辑。在本例中,这是一个革命性的“Hello, world”。

最后,service.py 文件是:

from app import application

Flask附带了一个所谓的web开发服务器(这些词现在听起来熟悉了吗?),我们可以在终端上运行它

$ FLASK_APP=service.py flask run
 * Serving Flask app "service.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

现在,你可以使用浏览器访问给定的URL,并查看一切是否正常运行。请记住,127.0.0.1是指“这台计算机”的特殊IP地址;操作系统通常将名称localhost作为此IP的别名,因此这两个名称可以互换。如你所见,Flask开发服务器的标准端口是5000,因此你必须明确地提到它,否则你的浏览器将尝试访问端口80(默认的HTTP端口)。当你连接到浏览器时,将看到关于HTTP请求的一些日志消息。

127.0.0.1 - - [14/Feb/2020 14:54:27] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [14/Feb/2020 14:54:28] "GET /favicon.ico HTTP/1.1" 404 -

现在你可以同时识别这两个,因为这与我们在文章前一部分中使用小服务器得到的请求是相同的。

2.3 参考资料

这些参考资料为本节讨论的主题提供更详细的信息

  • Miguel Gringberg's amazing Flask mega-tutorial[1]
  • What is localhost[2]
  • 本示例代码[3]

2.4 问题

很显然,我们解决了所有的问题,很多程序员就到此为止。他们学会了如何使用框架(这是一个巨大的成就!),但正如我们将很快发现的,这对于生产系统是不够的。让我们仔细看看Flask服务器的输出。上面清楚地写着:

WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.

我们在处理任何生产系统时所面临的主要问题是性能。当我们最小化代码时,考虑一下我们如何处理JavaScript:我们有意识地混淆代码以使文件更小,但这样做的唯一目的是使文件的读取速度更快。

对于HTTP服务器来说,情况并不是完全不同。Web框架通常提供一个Web开发服务器,正如Flask所做的那样,它正确地实现了HTTP,但是效率非常低。首先,这是一个阻塞框架,这意味着如果我们的请求需要几秒钟才能被服务(例如,客户端从一个非常慢的数据库检索数据),那么任何其他请求都必须排队等待服务。这最终意味着用户将在浏览器的选项卡中看到一个下拉列表,只是摇摇头,认为我们不能建立一个现代网站。其他性能问题可能与内存管理或磁盘缓存有关。但一般而言,我们可以有把握地说,此Web服务器无法处理任何生产负载(即多个用户同时访问web站点并期望良好的服务质量)。

这并不奇怪。毕竟,我们为了专注于自己的业务,不想处理TCP/IP连接,所以我们将此委托给了维护框架的其他编码人员。而框架的作者则希望关注中间件、路由、HTTP方法的正确处理等等。他们不想花时间去优化“多用户”体验的性能。在Python世界中尤其如此(但对于Node.js来说,这一点就不那么适用了):Python不是高度面向并发的,编程风格和性能都不利于快速、无阻塞的应用程序。最近,随着异步和解释器的改进,这种情况正在发生变化,但我将这个问题留在另一篇文章中阐述。

所以,现在我们有了一个成熟的HTTP服务,我们需要让它变得如此之快,以至于用户甚至不会注意到:它没有在他们的计算机上本地运行。

3 提高并发性

3.1 基本原理

如果每当你遇到性能问题,就求助于并发。现在你会有很多问题!(见此处)

是的,并发解决了许多问题,但它也是很多问题的根源,所以我们需要找到一种最安全、不那么复杂的方法来使用它。我们基本上可能希望添加一个层,这个层以某种并发方式运行框架,而不需要更改框架本身的任何内容。

每当你不得不使不同的事物同质化的时候,就创造出一个间接的层面。这几乎解决了任何问题,只有一个问题除外。(见此处)

因此,我们需要创建一个层,让它以并发方式运行我们的服务,但我们也希望将其与服务的特定实现分离,这与我们正在使用的框架或库无关。

3.2 实施

在这种情况下,解决方案是给出Web框架必须公开的API规范,以便让独立的第三方组件可以使用它。在Python世界中,这组规则被命名为WSGI,即Web服务器网关接口,对于其他语言(如Java或Ruby),也存在这样的接口。这里提到的“网关”是系统框架之外的部分,在本文中讨论的是处理生产性能的部分。通过WSGI,我们为框架定义了一种公开公共接口的方式,使得对并发感兴趣的人可以自由地独立实现一些东西。

如果框架与网关接口兼容,我们可以添加处理并发性的软件,并通过兼容层使用框架。这样的组件是一个可用于生产的HTTP服务器,在Python世界中有两个常见的选择是Gunicorn和uWSGI。

可用于生产的HTTP服务器意味着软件可以像开发服务器那样理解HTTP,但同时为了支持更大的工作负载而提高性能,正如我们之前所说的,这是通过并发完成的。

Flask与WSGI兼容,所以我们可以让它与Gunicorn一起工作。要在我们的虚拟环境中安装它,请运行pip install gunicorn,并设置它。创建一个名为wsgi.py的文件,其中包含以下内容

from app import application


if __name__ == "__main__":
    application.run()

要运行Gunicorn,请指定并发实例的数目和外部端口

$ gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi
[2020-02-12 18:39:07 +0000] [13393] [INFO] Starting gunicorn 20.0.4
[2020-02-12 18:39:07 +0000] [13393] [INFO] Listening at: http://0.0.0.0:8000 (13393)
[2020-02-12 18:39:07 +0000] [13393] [INFO] Using worker: sync
[2020-02-12 18:39:07 +0000] [13396] [INFO] Booting worker with pid: 13396
[2020-02-12 18:39:07 +0000] [13397] [INFO] Booting worker with pid: 13397
[2020-02-12 18:39:07 +0000] [13398] [INFO] Booting worker with pid: 13398

如你所见,Gunicorn有worker模式,它是实现并发性的一种通用方法。具体来说,Gunicorn实现了一个pre-fork worker模式,这意味着它为每个worker预先创建了一个不同的Unix进程。你可以用ps命令查看进程。

$ ps ax | grep gunicorn
14919 pts/1    S+     0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi
14922 pts/1    S+     0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi
14923 pts/1    S+     0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi
14924 pts/1    S+     0:00 ~/venv3/bin/python3 ~/venv3/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi

在Unix系统中,使用进程只是实现并发的两种方法之一,另一种是使用线程。然而,每种解决方案的优点和缺点都不在本文的讨论范围之内。目前只需记住,你正在应对多个worker,这些worker异步处理传入的请求,从而实现一个非阻塞服务器,准备接受多个连接。

3.3 参考资料

这些资源为本节讨论的主题提供了更详细的信息

  • The WSGI official documentation[4] and the Wikipedia page[5]
  • The homepages of Gunicorn[6] and uWSGI[7]
  • A good entry point for your journey into the crazy world of concurrency: [multithreading](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture "multithreading")).
  • 本例代码[8]

3.4 问题

使用Gunicorn,我们现在已经有了一个用于生产的HTTP服务器,并且显然实现了我们需要的一切。不过,仍有许多考虑因素和缺失的部分。

再回到性能

3个worker是否足以支撑我们新的杀手级的移动应用程序的负载?预计每分钟有成千上万的访问者,所以也许我们应该增加一些线程。但是,当我们增加线程的数量时,必须记住,我们正在使用的机器具有有限的CPU功率和内存。因此,我们必须再次关注性能,特别是可伸缩性:如何在不停止应用程序的情况下继续添加线程,用更强大的电脑替换现有的电脑,还是重新启动服务?

积极迎接变化

这不是我们在生产中必须面对的唯一问题。技术的一个重要方面是:随着新的、(希望是)更好的解决方案的普及,它会随着时间的推移而改变。我们设计系统时,通常尽可能把它们划分为多个通信层,正是因为我们希望能够自由地用其他的东西来替换一个层,无论是用更简单的组件还是更高级的组件,一个性能更好的组件,或者可能只是一个更便宜的组件。所以,再一次,我们希望能够优化底层系统,但要保持相同的界面,就像我们在Web框架中所做的那样。

HTTPS

系统中缺少的另一部分是HTTPS。Gunicorn和uWSGI不支持HTTPS协议,因此我们需要另外有一些东西来处理协议的“S”部分,将“HTTP”部分留给内部层。

负载均衡器

一般来说,负载均衡只是系统中的一个组件,它将工作分配给一个线程池。Gunicorn已经可以在它的工作线程之间分配负载了,所以这不是一个新的概念,但是我们通常希望在更大的层次上,在机器之间或者整个系统之间这样做。负载均衡可以是分层的,并且可以在多个级别上进行结构化。我们还可以为系统的某些组件标记为准备接受更多的负载(例如,因为它们的硬件更好)。负载均衡在网络服务中是非常重要的,而且负载的定义在不同的系统之间可能有很大的不同:一般来说,在Web服务中,连接的数量是负载的标准度量,因为我们假设:平均来说,所有连接都会给系统带来相同的负荷。

反向代理

负载均衡是转发代理,因为它们允许客户端联系池中的任何服务器。同时,反向代理允许客户端通过同一入口点检索多个系统生成的数据。反向代理是一个完美的方法,它将HTTP请求转发到可以用不同技术实现的子系统,例如,你可能希望用Python、Django和Postgres实现系统的一部分,用Go语言中的AWS Lambda函数实现另一部分,并与非关系数据库(如DynamoDB)连接。通常,在HTTP服务中,这个选择是根据URL做出的(例如,路由以/api/开头的每个URL)。

逻辑层

我们还需要一个可以实现一定数量逻辑的层来管理简单规则,这些规则与我们实现的服务无关。一个典型的例子是HTTP重定向:如果用户访问服务时使用的前缀是http://而不是https://,会发生什么?正确的方法是通过HTTP 301代码来处理问题,但是你不希望这样的请求影响你的框架,这样简单的任务会浪费资源。

(未完,待续)

阅读链接

  • 剖析Web技术栈(一)
  • 剖析Web技术栈(二)

参考资料

[1]

Miguel Gringberg's amazing Flask mega-tutorial: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world

[2]

What is localhost: https://en.wikipedia.org/wiki/Localhost

[3]

本示例代码: https://github.com/lgiordani/dissecting-a-web-stack-code/tree/master/2_web_framework

[4]

WSGI official documentation: https://wsgi.readthedocs.io/en/latest/index.html

[5]

Wikipedia page: https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface

[6]

Gunicorn: https://gunicorn.org/

[7]

uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/

[8]

本例代码: https://github.com/lgiordani/dissecting-a-web-stack-code/tree/master/3_concurrency_and_facades

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2 Web框架
    • 2.1基本原理
      • 2.2 实施
        • 2.3 参考资料
          • 2.4 问题
          • 3 提高并发性
            • 3.1 基本原理
              • 3.2 实施
                • 3.3 参考资料
                  • 3.4 问题
                    • 再回到性能
                      • 积极迎接变化
                        • HTTPS
                          • 负载均衡器
                            • 反向代理
                              • 逻辑层
                                • 参考资料
                                相关产品与服务
                                负载均衡
                                负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档