本文將以我的(開發)的角度,講述如何從零開始,編寫、搭建和部署一個基於Python的Web應用程序。php
從最簡單的出發點來剖析,一個web應用後端要完成的工做抽象出來無非就是3點:html
對於初學者來講,咱們關心的只需這些步驟就夠了。要檢驗這三個步驟,最簡單的方法是先寫出一個hello world。前端
request->"hello world"->response
python有許多流行的web框架,咱們該如何選擇呢?試着考慮三個因素:python
根據場景使用合適的框架能少走許多彎路,固然,你還能本身寫一個框架,這個下面再說。mysql
對於缺少經驗的人來講,易用性無疑是排在第一位的,推薦用flask做爲python web入門的第一個框架,另外也推薦django。ios
首先用virtualenv建立python的應用環境,爲何用virtualenv呢,virtualenv能建立一個純淨獨立的python環境,避免污染全局環境。(順便安利kennethreitz大神的pipenv)nginx
mkdir todo cd todo virtualenv venv source venv/bin/activate pip install flask touch server.py
代碼未寫,規範先行。在寫代碼以前要定義好一套良好代碼規範,例如PEP8。這樣才能使得你的代碼變的更加可控。git
心中默唸The Zen of Python:github
Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
下面用flask來編寫第一個程序:web
from flask import Flask, jsonify app = Flask(__name__) @app.route('/index') def index(): return jsonify(msg='hello world') if __name__ == '__main__': app.run(host='0.0.0.0', port=8000)
在命令行輸入python server.py
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程序就完成了!讓咱們看下過程當中都發生了什麼:
客戶端(瀏覽器)根據輸入的地址http://127.0.0.1:8000/index找到協議(http),主機(127.0.0.1),端口(8000)和路徑(/index),與服務器(application server)創建三次握手,併發送一個http請求。
服務器(application server)把請求報文封裝成請求對象,根據路由(router)找到/index這個路徑所對應的視圖函數,調用這個視圖函數。
視圖函數生成一個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(Web Server Gateway Interface)接口,它是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 json.dumps({ '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/<tid>/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/<tid>', 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)
這個程序基本實現了須要的接口,如今測試一下功能。
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
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\"}" ] }
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\"}" ] }
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": [] }
可是這個的程序的數據都保存在內存裏,只要服務一中止全部的數據就沒辦法保存下來了,所以,咱們還須要一個數據庫用於持久化數據。
那麼,應該選擇什麼數據庫呢?
這裏使用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(self, 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/<tid>/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/<tid>', 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文檔能夠得知,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是一個基於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反向代理能帶給咱們什麼好處:
爲了之後的擴展性,帶上一個nginx是有必要的,但若是你的應用沒大的需求,那麼可加可不加。
想讓nginx反向代理gunicorn,只需對nginx的配置文件加入幾行配置,讓nginx經過proxy_pass打到gunicorn監聽的端口上就能夠了:
server { listen 8888; location / { proxy_pass http://127.0.0.1:8000; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
如今應用的結構是這樣的:
但僅僅是這樣仍是不足以應對高併發下的請求的,洪水般的請求勢必是對數據庫的一個重大考驗,把請求數提高到1000,出現了大量了timeout:
wrk -c 1000 -t 12 -d 5s http://127.0.0.1:8888/todo Running 5s test @ http://127.0.0.1:8888/todo 12 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 239.50ms 235.76ms 1.93s 91.13% Req/Sec 83.07 76.77 434.00 76.78% 4548 requests in 5.10s, 6.52MB read Socket errors: connect 0, read 297, write 0, timeout 36 Non-2xx or 3xx responses: 289 Requests/sec: 892.04 Transfer/sec: 1.28MB
阻止洪峯的方法有:
等等..這裏重點說緩存,緩存系統是每一個web應用程序重要的一個模塊,緩存的做用是把熱點數據放入內存中,下降對數據庫的壓力。
下面用redis來對第一頁的數據進行緩存:
rds = redis.StrictRedis('127.0.0.1', 6379) @app.route('/todo') def index(): start = request.args.get('start', '') start = int(start) if start.isdigit() else 0 data = rds.get('todos') if data and start == 0: return data todos = db.todos.find().sort([('created_at', -1)]).limit(10).skip(start) data = dumps(todos) rds.set('todos', data, 3600) return data
只有在第一次請求時接觸到數據庫,其他請求都會從緩存中讀取,瞬間就提升了應用的rps。
wrk -c 1000 -t 12 -d 5s http://127.0.0.1:8888/todo Running 5s test @ http://127.0.0.1:8888/todo 12 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 68.33ms 95.27ms 1.34s 93.69% Req/Sec 277.32 258.20 1.60k 77.33% 15255 requests in 5.10s, 22.77MB read Socket errors: connect 0, read 382, write 0, timeout 0 Non-2xx or 3xx responses: 207 Requests/sec: 2992.79 Transfer/sec: 4.47MB
上面的這個示例只展現了基礎的緩存方式,並無針對多用戶的狀況處理,在涉及到狀態條件的影響下,應該使用更加複雜的緩存策略。
如今再來考慮使用緩存不當會形成幾個問題,設置緩存的時間是3600秒,當3600秒事後緩存失效,而新緩存又沒完成的中間時間內,若是有大量請求到來,就會蜂擁去查詢數據庫,這種現象稱爲緩存雪崩,針對這個狀況,能夠對數據庫請求這個動做進行加鎖,只容許第一個請求訪問數據庫,更新緩存後其餘的請求都會訪問緩存,第二種方法是作二級緩存,拷貝緩存比一級緩存設置更長的過時時間。還有緩存穿透和緩存一致性等問題雖然這裏沒有體現,但也是緩存設計中值得思考的幾個點。
下面是加入緩存後的系統結構:
目前爲止還不能說完善,若是中間某個進程掛掉了,那麼整個系統的穩定性就會土崩瓦解。爲此,要在中間加入一個進程管理工具:supervisor來監控和重啓應用進程。
首先要創建supervisor的配置文件:supervisord.conf
[program:gunicorn] command=gunicorn --workers=9 --worker-class="meinheld.gmeinheld.MeinheldWorker" server:app --bind 127.0.0.1:8000 autostart=true autorestart=true stdout_logfile=access.log stderr_logfile=error.log
而後啓動supervisord做爲後臺進程。
supervisord -c supervisord.conf
雖然緩存能夠有效地幫咱們減輕數據庫的壓力,但若是系統遇到大量併發的耗時任務時,進程也會阻塞在任務的處理上,影響了其餘普通請求的正常響應,嚴重時,系統極可能會出現假死現象,爲了針對對耗時任務的處理,咱們的應用還須要引入一個外部做業的處理系統,當程序接收到耗時任務的請求時,交給任務的工做進程池來處理,而後再經過異步回調或消息通知等方式來得到處理結果。
應用程序與任務進程的通訊一般藉助消息隊列的方式來進行通訊,簡單來講,應用程序會把任務信息序列化爲一條消息(message)放入(push)與特定任務進程之間的信道里(channel),消息的中間件(broker)負責把消息持久化到存儲系統,此時任務進程再經過輪詢的方式獲取消息,處理任務,再把結果存儲和返回。
顯然,此時咱們須要一個負責分發消息和與隊列打交道的調度器和一個存儲消息的中間件。
Celery是基於Python的一個分佈式的消息隊列調度系統,咱們把Celery做爲消息調度器,Redis做爲消息存儲器,那麼應用看起來應該是這樣的。
通常來講,這個結構已經知足大多數的小規模應用了,剩下作的就是代碼和組件配置的調優了。
而後還有一個很重要的點就是:測試
雖然不少人不喜歡寫測試(我也不喜歡),但良好的測試對調試和排錯是有很大幫助的。這裏指的測試不只僅是單元測試,關於測試能夠從幾個方面入手:
還有另外一個沒提到的點就是:安全,主要注意幾個點,其餘奇形怪狀的坑根據實際狀況做相應的調整:
面對系統日益複雜和增加的依賴,有什麼好的方法來保持系統的高可用和穩定一致性呢?docker是應用隔離和自動化構建的最佳選擇。docker提供了一個抽象層,虛擬化了操做系統環境,用容器技術爲應用的部署提供了沙盒環境,使應用之間能靈活地組合部署。
將每一個組件獨立爲一個docker容器:
docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cdca11112543 nginx "nginx -g 'daemon off" 2 days ago Exited (128) 2 days ago nginx 83119f92104a cassandra "/docker-entrypoint.s" 2 days ago Exited (0) 2 days ago cassandra 841bf506b6ec mongo "/entrypoint.sh mongo" 2 days ago Exited (1) 2 minutes ago 0.0.0.0:27017->27017/tcp, 0.0.0.0:28017->28017/tcp mongo b110a4530c4a python:latest "python3" 2 days ago Exited (0) 46 hours ago python b522b2a8313b phpfpm "docker-php-entrypoin" 4 days ago Exited (0) 4 days ago php-fpm f8630d4b48d7 spotify/kafka "supervisord -n" 2 weeks ago Exited (0) 6 days ago kafka
關於docker的用法,能夠了解官方文檔。
當業務逐漸快速增加時,原有的架構極可能已經不能支撐大流量帶來的訪問壓力了。
這時候就可使用進一步的方法來優化應用:
不過即便再怎麼優化,單機所能承載的壓力畢竟是有限的,這時候就要引入更多的服務器,作LVS負載均衡,提供更大的負載能力。但多機器的優化帶來了許多額外的問題,好比,機器之間怎麼共享狀態和數據,怎麼通訊,怎麼保持一致性,全部的這些都迫使着原有的結構要更進一步,進化爲一個分佈式系統,使各個組件之間鏈接爲一個互聯網絡。
這時候你須要解決的問題就更多了:
分佈式的應用的結構是下面這樣的:
展開來講還有不少,服務架構,自動化運維,自動化部署,版本控制、前端,接口設計等,不過我認爲到這裏,做爲後端的基本職責就算是完成了。除了基本功,幫你走得更遠的是內功:操做系統、數據結構、計算機網絡、設計模式、數據庫這些能力能幫助你設計出更加無缺的程序。