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

前言

本文意在表达:如何用 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字符之类。
建议一定使用该规范约束自己写代码。和大牛接轨,总比自己瞎想来的实际。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352