美文网首页
python web server 体悟(非基础,非高级)

python web server 体悟(非基础,非高级)

作者: lzp1234 | 来源:发表于2020-05-29 16:37 被阅读0次

    前言

    本文意在表达:如何用 python 写好 web server

    在这里,不会手把手告诉你怎么写代码,也不会告诉你什么是高级的 web server。

    我所写的仅仅是,我眼里的 python web server。

    希望能对你有所帮助,有所启示。

    欢迎指正。

    需要额外提示的是,该文主要讨论的是无状态服务的编写。有状态的不在此处讨论。

    环境

    文档编写时间:2020.05.29
    wsgi app: flask
    wsgi server: gunicorn
    模式:前后端分离,此处仅讨论后端

    正文

    web server 可以分为两个部分:框架业务功能

    大多时候框架部分的代码,都是在解决如何使用好一个第三方框架,而非自己去实现一个框架。

    业务功能部分便是实现具体业务功能的代码了。

    每个部分解决自己的问题,让工作有条不紊的进行。没有孰优孰劣之分,业务功能代码会更加考验你的代码逻辑。

    业务功能部分

    我想大多数人接触 web server 时首先接触的便是这块内容,或者正处于这个阶段,所以先提这部分内容。

    对于实现业务功能,有两个点需要着重考虑:规范模块化编写

    规范

    规范涉及:方法命名格式规范、url 格式规范、接口文档规范、接口数据格式规范以及 pep8

    每个规范的具体参考在文章的最后【扩展章节】给出。

    在协作开发中首先制定好规范十分重要,这是你必须应该做的事,哪怕只有你自己在开发。没有规范的代码,后期维护将一团糟。

    模块化编写

    模块化代码,可以增加代码的可阅读性,增加代码的扩展性,增加代码的健壮性。

    个人偏向于将总体代码分为三层(非照搬MVC):路由层、控制层、接口层。

    每层负责自己的事情

    路由层(Router)

    1. 定义 apidoc (接口文档)
    2. 全局 try catch
    3. 定义 url 及 url 的入口。

    控制层(Manager):

    1. 输入参数检查
    2. 显式定义输入参数(在这一层将 request 拆解出来,不要再往下传递 request)
    3. 代码逻辑层(组合访问下层接口,使用多线程之类)

    接口层(Client,该层的方法功能尽可能遵守单一职责,以便上层组合使用)

    1. 与外部交互
    2. 格式化返回参数

    框架部分

    作为框架的搭建者,需要额外考虑如下问题:

    1. gunicorn 配置调优(提供并行能力,推荐多进程)
    2. 日志:
      1)标准输出的内置 debug 信息及格式。(框架自带的debug,比如 gunicorn 的 debug,一般输出到屏幕)
      2)编写程序时手动记录的日志格式。(输出到文件,比如记录 exception、traceback)
      3)自动记录下接口访问信息。(输出到文件或者屏幕,可记录访问端地址、接口花费时间等)
    3. 线程池(解决并发 IO 等待)
    4. 提供配置(单独编写实现配置对象,方便程序内使用相应配置)
    5. 打包方式(setup 等)
    6. 应用启动方式 (通过 argparse 增加 -h、--conf 之类命令方便启动,注册进 systemd 等)
    7. 项目基础文档,包含整体架构设计、某些细节功能设计(比如权限设计)、如何构建开发环境等。

    进阶

    到这里,一个小型的中规中矩、能满足大部分场景的 web server 基本齐全了。单节点万级并发,多节点到十万并发也是没太大问题的。

    为了让它可以拥有进化的能力,我们需要看的更远,了解更多的东西。

    以上的内容完全基于个人经验的沉淀。以下将要介绍的东西,仅供各位一看,并非来源于长年累月的经验。

    这个时候,我们应该考虑分布式服务了。

    分布式服务主要需要解决的问题是:组件之间如何进行通信。

    目前组件间主流通信方式如下:

    1. 以 restful 方式进行通信。
    2. 以消息队列(中间件)进行通信。

    restful 方式

    该方式就是以 http 访问接口来实现功能,就是普普通通的 web server。

    缺点:

    1. 需要自己维护高可用。也就是,你得想明白,如果 3个A(A1,A2,A3)服务同时部署,由谁来对外进行服务,如果A1挂了,由谁来接手服务。这是高可用
    2. 需要自己维护负载均衡。将一波流量分别引流至不同的服务节点上,降低单个节点的压力,这是负载均衡。(什么都不是一蹴而就的,首先考虑你现在需要这个吗)
    3. 需要自己维护横向扩展性。如果现在已有3个节点同样的A服务,但是性能不够用,我再加个A服务节点,变成4节点同样的服务,你如何应对?需要重启现有节点吗?会中断现有应用吗?(可结合服务发现解决)
    4. 需要仔细考虑认证设计。由于组件可以独立对外部提供服务,因此如果一次请求跨越资源则需要重复的认证。

    这是目前中规中矩的分布式实现方案,成熟且原始。

    消息队列方式

    消息队列,有的地方也称之为中间件,但是个人偏向直接这么称呼,中间件是一个很宽泛的概念。

    缺点:

    1. 组件不能独立对外提供服务。
    2. 十分依赖消息队列的性能,消息队列十分可能成为你集群服务的瓶颈。
    3. 一定程度上的紧耦合,一段代码既是生产者又是消费者,这种情况下你的代码逻辑会有点糟糕。

    优点:

    1. 统一的入口。实际上这既是缺点,也是优点。统一的入口可以让认证变的十分高效简单,但是相对来说统一入口,会稍微紊乱代码结构。
    2. 显著的增加并发、异步能力。消息可以堆积,然后慢慢去处理,当然堆积处理不当就是一场雪崩。
    3. 简单配置即可实现高可用、负载均衡、扩展性。

    融合实现

    前面提的两种方式,可以融合起来对外提供服务。

    1. 独立的组件以 restful 风格通信。
    2. 组件内部以消息队列的方式进行通信,增加异步、并发能力。
    3. 额外增加服务发现(比如采用 etcd),提供整个集群服务的高可用、负载均衡、扩展性。

    这将会造就一个庞大而复杂的系统。除了认证有弊端外(重复认证),似乎无所不能。

    有一个好消息,一个跨语言的微服务框架(也称服务网格)istio 正在变的越来越成熟。它可以帮助你简便的实现服务注册,服务之间的通信,服务的管理等,值得持续关注,持续学习。

    扩展

    python 编写 web server,性能够吗?

    性能足够。

    如果你的代码涉及大量计算,十分敏感性能,不建议使用 python。但是如果你愿意接受调库(C库),python 也依然性能足够。

    规范参考

    方法命名规范

    # 例如:user_create (创建用户)
    <资源>_<动作>
    

    url 格式规范

    # 例如:/app1/project/user_add?project_id=123&user_id=456 (向app1 的项目中添加用户)
    /<1级资源>/<2级资源>/<3级资源>_<3级资源动作>
    

    目前有很多规范是将 uuid 放入前半部分中。例如:

    /app1/<project_id>/user_add?user_id=456
    

    这类 url 的前半部分十分可能包含过长的 uuid(32位),从而造成实际的 url 可读性十分差。因此个人倾向于将所有的 uuid 作为参数传递。

    接口文档规范

    接口文档,在小型的开发团队当中均由个人编写、维护。
    可以采用 apidoc 之类的工具去方便的生成接口文档。
    个人常用接口文档规范如下:

    @api {GET} /kdcloud/networks?detail=true 获取网络列表
    @apiGroup network
    @apiName network_list
    @apiDescription
        接口描述
    @apiParamExample {json} 参数示例
        Args: detail=true 表示获取更加详细的信息
        Headers: {
            "token": "1234",
            "project_id": "1234",
            "role": "",
        }
        Body: 无
    @apiSuccessExample {json} 成功返回值示例
        返回码 200
        {...}
    @apiErrorExample {json} 失败返回值示例
        返回码 500
        {...}
    

    效果参考:


    image.png

    接口数据格式规范

    接口数据规范定义,某一类的接口返回固定的数据格式。这就看你们的偏好了。

    比如通过http调用的接口数据结构规范:

    # 成功时:
    {
        "status": "success",
        "inventory": {}
    }
    # 失败时:
    {
        "status": "failure",
        "error": ""
    }
    

    比如统一方法返回值结构:返回字典列表而不是对象列表之类。

    pep8

    python 开源项目一般均会采用 pep8 检查代码格式。其中包含很多杂项,比如文件末尾空行,单行不能超过80字符之类。
    建议一定使用该规范约束自己写代码。和大牛接轨,总比自己瞎想来的实际。

    相关文章

      网友评论

          本文标题:python web server 体悟(非基础,非高级)

          本文链接:https://www.haomeiwen.com/subject/cgrxzhtx.html