从入门到大神,表弟的Python 开发进击之路

python server.py

  • Running on http://0.0.0.0:8000/ (Press CTRL+C to quit)

打开浏览器,访问 http://127.0.0.1:8000/index,如无意外,会看到下面的响应。

{

“msg”: “hello world”

}

一个最简单的web程序就完成了!让我们看下过程中都发生了什么:

  1. 客户端(浏览器)根据输入的地址 http://127.0.0.1:8000/index 找到协议(http),主机(127.0.0.1),端口(8000)和路径(/index),与服务器(application server)建立三次握手,并发送一个http请求。

  2. 服务器(application server)把请求报文封装成请求对象,根据路由(router)找到/index这个路径所对应的视图函数,调用这个视图函数。

  3. 视图函数生成一个http响应,返回一个json数据给客户端。

HTTP/1.0 200 OK

Content-Type: application/json

Content-Length: 27

Server: Werkzeug/0.11.15 Python/3.5.2

Date: Thu, 26 Jan 2017 05:14:36 GMT

当我们输入python server.py时,会建立一个服务器(也叫应用程序服务器,即application server)来监听请求,并把请求转给flask来处理。那么这个服务器是如何跟python程序打交道的呢?答案就是 WSGI 接口,它是server端(服务器)与application端(应用程序)之间的一套约定俗成的规范,使我们只要编写一个统一的接口,就能应用到不同的wsgi server上。用图表示它们的关系,就是下面这样的:

只要application端(flask)和server端(flask内建的server)都遵循wsgi这个规范,那么他们就能够协同工作了,关于WSGI规范,可参阅Python官方的 PEP 333 里的说明。

目前为止,应用是下面这个样子的:

一切都很简单,现在我们要做一个Todo应用,提供添加todo,修改todo状态和删除todo的接口。

先不考虑数据库,可以迅速地写出下面的代码:

from flask import Flask, jsonify, request, abort, Response

from time import time

from uuid import uuid4

import json

app = Flask(name)

class Todo(object):

def init(self, content):

self.id = str(uuid4())

self.content = content #todo内容

self.created_at = time() #创建时间

self.is_finished = False #是否完成

self.finished_at = None #完成时间

def finish(self):

self.is_finished = True

self.finished_at = time()

def json(self):

return {

‘id’: self.id,

‘content’: self.content,

‘created_at’: self.created_at,

‘is_finished’: self.is_finished,

‘finished_at’: self.finished_at

}

todos = {}

get_todo = lambda tid: todos.get(tid, False)

@app.route(‘/todo’)

def index():

return jsonify(data=[todo.json() for todo in todos.values()])

@app.route(‘/todo’, methods=[‘POST’])

def add():

content = request.form.get(‘content’, None)

if not content:

abort(400)

todo = Todo(content)

todos[todo.id] = todo

return Response() #200

@app.route(‘/todo//finish’, methods=[‘PUT’])

def finish(tid):

todo = get_todo(tid)

if todo:

todo.finish()

todos[todo.id] = todo

return Response()

abort(404)

@app.route(‘/todo/’, methods=[‘DELETE’])

def delete(tid):

todo = get_todo(tid)

if todo:

todos.pop(tid)

return Response()

abort(404)

if name == ‘main’:

app.run(host=‘0.0.0.0’, port=8000)

这个程序基本实现了需要的接口,现在测试一下功能。

  • 添加一个todo

http -f POST http://127.0.0.1:8000/todo content=好好学习

HTTP/1.0 200 OK

Content-Length: 0

Content-Type: text/html; charset=utf-8

Date: Thu, 26 Jan 2017 06:45:37 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

  • 查看todo列表

http http://127.0.0.1:8000/todo

HTTP/1.0 200 OK

Content-Length: 203

Content-Type: application/json

Date: Thu, 26 Jan 2017 06:46:16 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

{

“data”: [

“{“created_at”: 1485413137.305699, “id”: “6f2b28c4-1e83-45b2-8b86-20e28e21cd40”, “is_finished”: false, “finished_at”: null, “content”: “\u597d\u597d\u5b66\u4e60”}”

]

}

  • 修改todo状态

http -f PUT http://127.0.0.1:8000/todo/6f2b28c4-1e83-45b2-8b86-20e28e21cd40/finish

HTTP/1.0 200 OK

Content-Length: 0

Content-Type: text/html; charset=utf-8

Date: Thu, 26 Jan 2017 06:47:18 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

http http://127.0.0.1:8000/todo

HTTP/1.0 200 OK

Content-Length: 215

Content-Type: application/json

Date: Thu, 26 Jan 2017 06:47:22 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

{

“data”: [

“{“created_at”: 1485413137.305699, “id”: “6f2b28c4-1e83-45b2-8b86-20e28e21cd40”, “is_finished”: true, “finished_at”: 1485413238.650981, “content”: “\u597d\u597d\u5b66\u4e60”}”

]

}

  • 删除todo

http -f DELETE http://127.0.0.1:8000/todo/6f2b28c4-1e83-45b2-8b86-20e28e21cd40

HTTP/1.0 200 OK

Content-Length: 0

Content-Type: text/html; charset=utf-8

Date: Thu, 26 Jan 2017 06:48:20 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

http http://127.0.0.1:8000/todo

HTTP/1.0 200 OK

Content-Length: 17

Content-Type: application/json

Date: Thu, 26 Jan 2017 06:48:22 GMT

Server: Werkzeug/0.11.15 Python/3.5.2

{

“data”: []

}

但是这个的程序的数据都保存在内存里,只要服务一停止所有的数据就没办法保存下来了,因此,我们还需要一个数据库用于持久化数据。

那么,应该选择什么数据库呢?

  • 传统的rdbms,例如mysql,postgresql等,他们具有很高的稳定性和不俗的性能,结构化查询,支持事务,由ACID来保持数据的完整性。

  • nosql,例如mongodb,cassandra等,他们具有非结构化特性,易于横向扩展,实现数据的自动分片,拥有灵活的存储结构和强悍的读写性能。

这里使用mongodb作例子,使用mongodb改造后的代码是这样的:

from flask import Flask, jsonify, request, abort, Response

from time import time

from bson.objectid import ObjectId

from bson.json_util import dumps

import pymongo

app = Flask(name)

mongo = pymongo.MongoClient(‘127.0.0.1’, 27017)

db = mongo.todo

class Todo(object):

@classmethod

def create_doc(cls, content):

return {

‘content’: content,

‘created_at’: time(),

‘is_finished’: False,

‘finished_at’: None

}

@app.route(‘/todo’)

def index():

todos = db.todos.find({})

return dumps(todos)

@app.route(‘/todo’, methods=[‘POST’])

def add():

content = request.form.get(‘content’, None)

if not content:

abort(400)

db.todos.insert(Todo.create_doc(content))

return Response() #200

@app.route(‘/todo//finish’, methods=[‘PUT’])

def finish(tid):

result = db.todos.update_one(

{‘_id’: ObjectId(tid)},

{

‘$set’: {

‘is_finished’: True,

‘finished_at’: time()

}

}

)

if result.matched_count == 0:

abort(404)

return Response()

@app.route(‘/todo/’, methods=[‘DELETE’])

def delete(tid):

result = db.todos.delete_one(

{‘_id’: ObjectId(tid)}

)

if result.matched_count == 0:

abort(404)

return Response()

if name == ‘main’:

app.run(host=‘0.0.0.0’, port=8000)

这样一来,应用的数据便能持久化到本地了。现在,整个应用看起来是下面这样的:

现在往mongodb插入1万条数据。

import requests

for i in range(10000):

requests.post(‘http://127.0.0.1:8000/todo’, {‘content’: str(i)})

获取todo的接口目前是有问题的,因为它一次性把数据库的所有记录都返回了,当数据记录增长到一万条的时候,这个接口的请求就会变的非常慢,需要500ms后才能发出响应。现在对它进行如下的改造:

@app.route(‘/todo’)

def index():

start = request.args.get(‘start’, ‘’)

start = int(start) if start.isdigit() else 0

todos = db.todos.find().sort([(‘created_at’, -1)]).limit(10).skip(start)

return dumps(todos)

每次只取十条记录,按创建日期排序,先取最新的,用分页的方式获取以往记录。改造后的接口现在只需50ms便能返回响应。

现在对这个接口进行性能测试:

wrk -c 100 -t 12 -d 5s http://127.0.0.1:8000/todo

Running 5s test @ http://127.0.0.1:8000/todo

12 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 1.22s 618.29ms 1.90s 48.12%

Req/Sec 14.64 10.68 40.00 57.94%

220 requests in 5.09s, 338.38KB read

Socket errors: connect 0, read 0, write 0, timeout 87

Requests/sec: 43.20

Transfer/sec: 66.45KB

rps只有43。我们继续进行改进,通过观察我们发现我们查询todo时需要通过created_at这个字段进行排序再过滤,这样以来每次查询都要先对10000条记录进行排序,效率自然变的很低,对于这个场景,可以对created_at这个字段做索引:

db.todos.ensureIndex({‘created_at’: -1})

通过explain我们轻易地看出mongo使用了索引做扫描

db.todos.find().sort({‘created_at’: -1}).limit(10).explain()

/* 1 */

{

“queryPlanner” : {

“plannerVersion” : 1,

“namespace” : “todo.todos”,

“indexFilterSet” : false,

“parsedQuery” : {},

“winningPlan” : {

“stage” : “LIMIT”,

“limitAmount” : 10,

“inputStage” : {

“stage” : “FETCH”,

“inputStage” : {

“stage” : “IXSCAN”,

“keyPattern” : {

“created_at” : -1.0

},

“indexName” : “created_at_-1”,

“isMultiKey” : false,

“multiKeyPaths” : {

“created_at” : []

},

“isUnique” : false,

“isSparse” : false,

“isPartial” : false,

“indexVersion” : 2,

“direction” : “forward”,

“indexBounds” : {

“created_at” : [

“[MaxKey, MinKey]”

]

}

}

}

},

“rejectedPlans” : []

},

“serverInfo” : {

“host” : “841bf506b6ec”,

“port” : 27017,

“version” : “3.4.1”,

“gitVersion” : “5e103c4f5583e2566a45d740225dc250baacfbd7”

},

“ok” : 1.0

}

现在再做一轮性能测试,有了索引之后就大大降低了排序的成本,rps提高到了298。

wrk -c 100 -t 12 -d 5s http://127.0.0.1:8000/todo

Running 5s test @ http://127.0.0.1:8000/todo

12 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 310.32ms 47.51ms 357.47ms 94.57%

Req/Sec 26.88 14.11 80.00 76.64%

1511 requests in 5.06s, 2.27MB read

Requests/sec: 298.34

Transfer/sec: 458.87KB

再把重心放到app server上,目前我们使用flask内建的wsgi server,这个server由于是单进程单线程模型的,所以性能很差,一个请求不处理完的话服务器就会阻塞住其他请求,我们需要对这个server做替换。关于python web的app server选择,目前主流采用的有:

  • gunicorn

  • uWSGI

我们看[gunicorn]文档可以得知,gunicorn是一个python编写的高效的WSGI HTTP服务器,gunicorn使用pre-fork模型(一个master进程管理多个child子进程),使用gunicorn的方法十分简单:

gunicorn --workers=9 server:app --bind 127.0.0.1:8000

根据文档说明使用(2 * cpu核心数量)+1个worker,还要传入一个兼容wsgi app的start up方法,通过Flask的源码可以看到,Flask这个类实现了下面这个接口:

def call(self, environ, start_response):

“”“Shortcut for :attr:wsgi_app.”“”

return self.wsgi_app(environ, start_response)

也就是说我们只需把flask实例的名字传给gunicorn就ok了:

gunicorn --workers=9 server:app --bind 127.0.0.1:8000

[2017-01-27 11:20:01 +0800] [5855] [INFO] Starting gunicorn 19.6.0

[2017-01-27 11:20:01 +0800] [5855] [INFO] Listening at: http://127.0.0.1:8000 (5855)

[2017-01-27 11:20:01 +0800] [5855] [INFO] Using worker: sync

[2017-01-27 11:20:01 +0800] [5889] [INFO] Booting worker with pid: 5889

[2017-01-27 11:20:01 +0800] [5890] [INFO] Booting worker with pid: 5890

[2017-01-27 11:20:01 +0800] [5891] [INFO] Booting worker with pid: 5891

[2017-01-27 11:20:01 +0800] [5892] [INFO] Booting worker with pid: 5892

[2017-01-27 11:20:02 +0800] [5893] [INFO] Booting worker with pid: 5893

[2017-01-27 11:20:02 +0800] [5894] [INFO] Booting worker with pid: 5894

[2017-01-27 11:20:02 +0800] [5895] [INFO] Booting worker with pid: 5895

[2017-01-27 11:20:02 +0800] [5896] [INFO] Booting worker with pid: 5896

[2017-01-27 11:20:02 +0800] [5897] [INFO] Booting worker with pid: 5897

可以看到gunicorn启动了9个进程(其中1个父进程)监听请求。使用了多进程的模型看起来是下面这样的:

继续进行性能测试,可以看到吞吐量又有了很大的提升:

wrk -c 100 -t 12 -d 5s http://127.0.0.1:8000/todo

Running 5s test @ http://127.0.0.1:8000/todo

12 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 109.30ms 16.10ms 251.01ms 90.31%

Req/Sec 72.47 10.48 100.00 78.89%

4373 requests in 5.07s, 6.59MB read

Requests/sec: 863.35

Transfer/sec: 1.30MB

那么gunicorn还能再优化吗,答案是肯定的。回到之前我们发现了这一行:

[2017-01-27 11:20:01 +0800] [5855] [INFO] Using worker: sync

也就是说,gunicorn worker使用的是sync(同步)模式来处理请求,那么它支持async(异步)模式吗,再看gunicorn的文档有下面一段说明:

Async Workers

The asynchronous workers available are based on Greenlets (via Eventlet and Gevent). Greenlets are an implementation of cooperative multi-threading for Python. In general, an application should be able to make use of these worker classes with no changes.

gunicorn支持基于greenlet的异步的worker,它使得worker能够协作式地工作。当worker阻塞在外部调用的IO操作时,gunicorn会聪明地把执行调度给其他worker,挂起当前的worker,直至IO操作完成后,被挂起的worker又会重新加入到调度队列中,这样gunicorn便有能力处理大量的并发请求了。

gunicorn有两个不错的async worker:

  • meinheld

  • gevent

meinheld是一个基于picoev的异步WSGI Web服务器,它可以很轻松地集成到gunicorn中,处理wsgi请求。

gunicorn --workers=9 --worker-class=“meinheld.gmeinheld.MeinheldWorker” server:app --bind 127.0.0.1:8000

[2017-01-27 11:47:01 +0800] [7497] [INFO] Starting gunicorn 19.6.0

[2017-01-27 11:47:01 +0800] [7497] [INFO] Listening at: http://127.0.0.1:8000 (7497)

[2017-01-27 11:47:01 +0800] [7497] [INFO] Using worker: meinheld.gmeinheld.MeinheldWorker

[2017-01-27 11:47:01 +0800] [7531] [INFO] Booting worker with pid: 7531

[2017-01-27 11:47:01 +0800] [7532] [INFO] Booting worker with pid: 7532

[2017-01-27 11:47:01 +0800] [7533] [INFO] Booting worker with pid: 7533

[2017-01-27 11:47:01 +0800] [7534] [INFO] Booting worker with pid: 7534

[2017-01-27 11:47:01 +0800] [7535] [INFO] Booting worker with pid: 7535

[2017-01-27 11:47:01 +0800] [7536] [INFO] Booting worker with pid: 7536

[2017-01-27 11:47:01 +0800] [7537] [INFO] Booting worker with pid: 7537

[2017-01-27 11:47:01 +0800] [7538] [INFO] Booting worker with pid: 7538

[2017-01-27 11:47:01 +0800] [7539] [INFO] Booting worker with pid: 7539

可以看到现在使用的是meinheld.gmeinheld.MeinheldWorker这个worker。再进行性能测试看看:

wrk -c 100 -t 12 -d 5s http://127.0.0.1:8000/todo

Running 5s test @ http://127.0.0.1:8000/todo

12 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 84.53ms 39.90ms 354.42ms 72.11%

Req/Sec 94.52 20.84 150.00 70.28%

5684 requests in 5.04s, 8.59MB read

Requests/sec: 1128.72

Transfer/sec: 1.71MB

果然提升了不少。

现在有了app server,那需要nginx之类的web server吗?看看[nginx]反向代理能带给我们什么好处:

  • 负载均衡,把请求平均地分到上游的app server进程。

  • 静态文件处理,静态文件的访问交给nginx来处理,降低了app server的压力。

  • 接收完客户端所有的TCP包,再一次交给上游的应用来处理,防止app server被慢请求干扰。

  • 访问控制和路由重写。

  • 强大的ngx_lua模块。

  • Proxy cache。

  • Gzip,SSL…

为了以后的扩展性,带上一个nginx是有必要的,但如果你的应用没大的需求,那么可加可不加。

想让nginx反向代理gunicorn,只需对nginx的配置文件加入几行配置,让nginx通过proxy_pass打到gunicorn监听的端口上就可以了:

server {

listen 8888;

location / {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)

访问控制和路由重写。

  • 强大的ngx_lua模块。

  • Proxy cache。

  • Gzip,SSL…

为了以后的扩展性,带上一个nginx是有必要的,但如果你的应用没大的需求,那么可加可不加。

想让nginx反向代理gunicorn,只需对nginx的配置文件加入几行配置,让nginx通过proxy_pass打到gunicorn监听的端口上就可以了:

server {

listen 8888;

location / {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-G7ssARt8-1713753338508)]

[外链图片转存中…(img-2XnqCpuR-1713753338509)]

[外链图片转存中…(img-NCrsDRTg-1713753338509)]

[外链图片转存中…(img-5UDHbpVD-1713753338510)]

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注:Python)

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值