惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
aimingoo的专栏
aimingoo的专栏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
J
Java Code Geeks
博客园_首页
Google Online Security Blog
Google Online Security Blog
Hugging Face - Blog
Hugging Face - Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
O
OpenAI News
SecWiki News
SecWiki News
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
N
News | PayPal Newsroom
Project Zero
Project Zero
Forbes - Security
Forbes - Security
IT之家
IT之家
A
Arctic Wolf
WordPress大学
WordPress大学
Jina AI
Jina AI
T
Tor Project blog
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
博客园 - 聂微东
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy International News Feed
Cloudbric
Cloudbric
G
GRAHAM CLULEY
博客园 - 叶小钗
H
Hacker News: Front Page
腾讯CDC
量子位
Help Net Security
Help Net Security
人人都是产品经理
人人都是产品经理
C
Cyber Attacks, Cyber Crime and Cyber Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
爱范儿
爱范儿
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
C
CERT Recently Published Vulnerability Notes

Random Thoughts

LLM 训练与推理的基本理解 昨日的世界不再重来 Magic Brush - 画出你自己的产品宇宙 写在 AI Coding 奇点之后 尼泊尔布恩山小环线纪行 2025 投资组合年报 三十而笠 我的 8 年职业生涯回顾 我的颈椎病康复之旅 - 关于选择的故事 熊野古道中边路纪行 新加坡旅居三年再回首 高加索三国 - 阿塞拜疆行记 2024 投资组合年报 重新思考 Go:了解程序在线上是如何运行的 欧游散记 —— 环勃朗峰之旅 欧游散记 —— 特摩索斯古城 重新思考 Go:Channel 不是「消息队列」 重新思考 Go:Slice 只是「操作视图」 那一天,我决定踏出一步 True Story 少数价值 A Snapshot of Myself - 2021 RPC 漫谈: 连接问题 RPC 漫谈:序列化问题 RPC 漫谈: 限流问题 科学,技术与工程 Pond: Golang 通用对象池 Golang for-range 内部实现 Golang Interface 内部实现 真理的有限性 Golang rand 库锁竞争优化 Meaningless or Meaningful 分布式文件系统的演化 从动物森友会聊主机游戏联机机制 创业公司的文化 伊朗见闻录 NodeJS 内存泄漏检测与定位 设计实现高性能本地内存缓存 Travel Map Linux I/O 栈浅析 SSD 背后的奥秘 什么是真正的编程能力 沉默与反沉默的理由 命令行里的设计艺术 自由意志下的选择 学习思维方式而非学习观点 从程序到人 —— 情头配对助手的前世今生 即刻多端实时通信实践 朝鲜 —— 小国寡民的主体思想实践 使用 Surge 提升多网络环境下的流畅开发体验 一份其实好吃的 LaTeX 入门餐 Kafka 的设计与实践思考 欧游散记 —— 民主专制下的德国 欧游散记 —— 维也纳 欧游散记 —— 西班牙 欧游散记 —— 德国国会大厦 社会矛盾讨论统一框架的一种可能性 欧游散记 —— 伪君子布拉格 Golang : Make Programming Happy Again Podcast 闲言碎语 Kubernetes 中使用 API Gateway 替代 Ingress ElasticSearch 最佳实践 blog.joway.io Presentations
Lemon : Koa 风格的 Python 异步 Web 框架
Joway · 2018-01-08 · via Random Thoughts

前段时间想要写一些简短高效的 API ,背后的工作无非就是一些计算和数据处理,但是可能并发量会比较高。当我在 Python 的生态里去搜寻一些靠谱的 Web 框架时,很难找到一个设计优秀且运行效率高的框架。尤其是当我已经习惯了 NodeJS 的 Koa 那种简洁明了的设计时,很难再喜欢上像 Flask 那种装饰器的写法和各种概念拢杂在一起的设计。最后实在没有办法,就自己写了个符合我个人审美的框架 Lemon

在讲 Lemon 的设计前,我们先来看一看一个请求是如何被响应的 ,以及框架在其中的作用是什么 :

当一个请求从客户端进入服务器,首先以 TCP 包的形式被接收到,这个时候我们需要手动去建立连接,接收到 TCP 报文后我们需要去判断它的协议,然后再解成相应协议(一般都是HTTP协议)的报文,传给 application 去处理,当处理完后还要把结果写回成 TCP 报文传回去,如果有 keep alive 还需要去手动管理这个连接的生命周期。以上这些统称为 server 部分。

而和开发者关系最密切的 application 部分做的就是拿到一个 http 或其它协议的报文,然后根据其信息做针对性的响应,传给 server 。

无论你是使用任何语言任何框架,都逃不开上面这个处理过程。而我们在比较框架的时候,无外乎是对以下几个方面做一些针对性的优化:

  1. TCP报文解析成 HTTP(或其它协议) 报文的效率
  2. 并发策略 (多线程,多进程,协程,线程池,Event Loop)
  3. application 本身的运行效率 (由框架的效率,所用的语言和使用者自身的代码质量共同决定)

对于第一点,python有一个叫 httptools 的库就是使用了 NodeJS 的 http parser 以达到高性能解包的目的。

针对第二点,有许多OS层面和工程层面的技术在致力于解决这个问题。uvloop 就是其中的一种,采用事件循环的方式,底层用的是 libuv , 同样也是 NodeJS 的底层异步IO库 。

第三点可能是我们大部分人的考虑的重点,需要各自根据团队情况,在开发效率和运行效率中间进行权衡。作为框架本身,它能够做的极致就是不给 application 拖后腿,而现实中,大部分时间其实都是在运行用户自己编写的代码。

当我们在谈论一个 Web 框架的时候,更多是在谈论第三点 application 的部分。第一二两点是 server 的实现部分。大部分时候,框架自身并不会去实现一个完整的 server 。为了让我们使用各种框架来进行开发的 application 能够在不同 server 中可以运行,会指定一些接口标准,在 Python 里同步的比如 wsgi , 异步的比如 asgi 。

对于 application 部分,显然所用语言已经限定了,而代码质量又取决于开发者自己的造化,那么框架能够做的,就是帮开发者简化编写业务逻辑的困难,形成一套代码的格式与套路。而许多人关心的框架的并发能力和运行效率,大多时候并不取决于框架自己,而是依赖于 server 的实现 。

Lemon 想要做的,就是在第一二层利用已有的最新的技术实现其高效率能力,而在 application 层,做一个设计简单,使用舒适的 API 框架。

Lemon 初探

Python 3.5 以上已经支持类似 NodeJS 的那种 async 的写法了,加上uviloop 这个高性能事件循环库。我们完全可以借助这两个在 Python 里实现一个类 Koa 的异步 Web 框架。

在 Lemon v0.0.1 版本的时候,我用 uvloophttptools 自己去实现了一个 server 。但是后来发现这件事情要做得好且稳定还是挺复杂的。偶然间发现了 uvicorn 这个项目,代码写的非常优雅,而且也是用的 uvloop 和 httptools , 很适合作为一个 server 来使用。于是后来就又删掉了我自己的 server , 转而使用 uvicorn 。

Sanic 的架构和上述很像,但是它没有用 uvicorn 而是自己写了一套 server 并且它是基于 Flask 的 API 风格的,项目也写的比较乱。

在 Lemon 中,一个 application 的设计是这样的 :

async def middleware1(ctx, nxt):
    ctx.body = {
        'msg': 'hello'
    }
    await nxt()

	
async def middleware2(ctx: Context):
    ctx.body['ack'] = 'yeah !'
	
app = Lemon()
	
app.use(middleware1, middleware2)
	
app.listen()

这种设计思路非常简单,就是用 一条函数链 描述整个请求过程 。ctx 作为传递各种参数的媒介,每个 middleware 都有能力直接返回结果或者传递给下一个 middleware 。

那么如果我们要设计路由该怎么做呢 ? 很简单,路由就是最前面的 middleware1 ,由它去拿 ctx.path 然后决定要走哪个函数。

同样的全局的错误处理或者鉴权处理都可以被抽象到这个 middleware 对象中来。这样在我们的设计里,就不会有那么多人为的概念。

我们可以看一下Flask框架中示例代码是怎么样的:

@app.route('/<name>')
def hello_world(name):
    return jsonify({
        'hello': 'flask',
    })

app.run()

首先它用一些很魔法的设计,强制我的 hello_world 函数必须接受一个叫 name 的参数,因为我在路由里命名了它。并且如果我要加一些中间件,我只能通过装饰器的方式去加,而且我还要时刻留心函数参数这件事情。如果我要拿请求参数,还要用一个全局的变量 request 去拿。这种局部不像局部,全局不像全局的方式非常的脏乱 。

Lemon 模块设计

Server

可以支持任何实现了 asgi 接口的 server 。默认调用 app.listen() 会启动一个 uvicorn server 。无需配置即可部署在生产环境中。

Router

默认的 Router 使用了 Trie 树作为路由的数据结构。如果你使用的是 HTTP RPC Style 形式的 API ,可以使用 SimpleRouter ,直接用字典形式存储。你也可以自己继承 AbstractRouter 或 AbstractBaseRouter 实现你自己的 Router 。

Context

ctx 实例,ctx.state : {…} 用来存储 middleware 链上的信息,供下游使用。

Request

ctx.req 的对象,存储客户端请求的所有信息。

Response

ctx.res 的对象,存储返回的结果。

Lemon 目前的状态

Lemon 目前还只是一个 alpha 的版本 , 并不建议在生产环境上用,我最近会去写一系 benchmark 和 稳定性测试。包括还需要完善使用文档,添加一些易用的功能。

我用我自己写的小工具测试了下, 结果如下:

Lemon

Lemon

Sanic

Sanic

Flask

Flask

测试结果很符合预期,因为和 Sanic 用的底层技术都差不多,所以两者性能几乎一样。之后会做更为广泛和专业的测试。

项目地址

https://github.com/joway/lemon