維基是這樣定義 Real-time web 的:
The real-time web is a set of technologies and practices which enable users to receive information
as soon as it is published by its authors, rather than requiring that they or their software check
a source periodically for updates.
大意是指當作者在網頁上更新訊息時,其他使用者不需要做任何裝置上的刷新操作可以立即的在裝置上得到新資訊。
不過在科技上的做法還是有差異,到底是真正的即時,還只是用其他特殊的方法讓使用者感覺即時。後者的意思是假如伺服器只支援 request, response 這種使用者一個 request 接著伺服器才會有一個 response,類似一個輪播循環,伺服器就會以被動的姿態等待並且傳輸訊息。因此早期的 real-time 作法可能會像是使用者端(client)做一個每間隔多久就重新整理的指令,但實際上這和使用者操作瀏覽器上的重新整理按鈕是一模一樣的。這個例子可以追朔到大概在 win95, win98 年代那時候很流行的奇摩聊天室等等大概都是以這個原理來操作的。不過這種方式其實對於伺服器和使用者體驗都不佳,想想假如設定三秒重新整理一次,那每一次就要把網頁上所有內容重新下載一次,如果同時很多人在線上伺服器的頻寬不足就會讓整個操作變得卡卡的,於是乎 AJAX 技術就發展起來。AJAX(Asynchronous JavaScript and XML) 被稱為非同步的 javascript 與 XML,字義上就能理解是透過 Javascript 程式語言與 XML 的資料格式進行非同步的資料傳遞,而實際做法是只針對局部的資料做更新,這樣就能大大的降低傳遞的資料量,而因為使用 Javascript 因此很多處理可以在使用者電腦上進行,這也降低了伺服器端的負荷量。
但這樣還是有個小問題,所有的行為還是圍繞在 request 與 response 這個循環裡,這個循環我們稱為 polling(輪詢),想想有沒有更好的做法?因為想像伺服器端與使用者端如同生產者與消費者一般,生產者在產生資料過程中應該不希望消費者時時刻刻去詢問,假如生產者資料產生資料後可以用一個方式統一告訴正在等待的消費者:各位,有新資料囉!那這件事應該就完美了。
於是 WebSocket 就登場了。2011 年 WebSocket 被 IETF 定為標準 RFC 6455
,並由 RFC7936
補充規範,接著 2014 年的 HTML 5
定義了 Websocket 協定,於是乎 TCP 也有了全雙工
的模式了。全雙工的意思是伺服器端可以主動推播訊息給使用者端,當使用者與伺服器進行了第一次成功交握(Handshake),就是 client 發出一個 SYN 接著伺服器發回 ACK 接著就能建立即時的資料傳輸。於是這樣的技術讓 web 真的可以實現真正的 real-time web 架構。
- Twisted
- Tornado
- gevent
- uWSGI
| nginx(proxy) | <-(uwsgi 協議)-> | uWSGI | <-(wsgi)-> | Django App |
________________ ___________
^ ^
| |
|<------ |cgi| ------->|
- nginx 代理伺服器: 負責靜態資源發送(js, css, media),動態請求轉發與結果回覆。
- uWSGI 後端伺服器: 接受 nginx 請求轉發並處理後發給 Django App 以及接收 Django 應用回傳訊息轉給 nginx。
- Django App 應用程式: 收到請求後處理數據並且進行渲染對應頁面給 uWSGI 服務器。
講非同步之前稍微說明過去同步執行所面臨的一些困境:程式執行可分成 Process 與 Thread 兩種,兩個主要差異就在於是否可以共用資料結構。
Process 是獨立的程式,因此無法共享資料結構,也因為這樣要撰寫平行的程式是困難且複雜的。
Thread 可以共享資料結構,多個 thread 被稱為並行(concurrency),它透過 context-switch(上下文切換)去切換不同之間的進程來達到並行的效用,上下文切換意思是在 cpu 切換過程勢先將目前進程
的儲存,接著切換到另一個進程,接著在切換回原來進程...但這也可能會造成更多額外的成本。
但如果我們想要達到在多個執行緒執行中能有效利用 cpu 資源而不用浪費在等待 I/O 那這件事可能就會需要由 application 來處理並且透過它來回應程式完成,application 能夠達到被稱為*協同程序(coroutine)*
。
要能夠成為非阻塞的非同步程序需要有幾個規範:
- non-blocking sockets(非阻塞)
- callback(回呼)
- event loop(事件循環)
從這三個規則來看,必須要滿足不能是阻塞的 socket,這也是為什麼產生器(generator)可以演化成協同程序。 再來是他要能是回呼(callback),最後一定要有 event loop 讓每個進程做完自己的事能夠回報。
那如果要實現非同步 coroutine 就需要借助協同程序參考
Django Channels 是 Django 基金會所維護的一個 open project 預計未來會整合進入 Django Project 但未來是何時呢??不過這個問題,我覺得不會是重點,重點先來談談 Channels 這個官方的套件架構是什麼?
圖片是傳統的 Django 視圖,就是上面稱為的輪播(polling)。
接著是 Django Channels 加入後的架構。
- Interface:
Daphne
這個套件用來取代 WSGI Server 讓 interface 可以選擇接受 WebSocket 或是 HTTP。 - Channel layer: 一個資料結構 FIFO(ASGI)
- Worker: Redis
官方網站有個很好舉例:
In particular for large sites it will be possible to configure a production-grade HTTP server like nginx to route requests based on path to either (1) a production-grade WSGI server like Gunicorn+Django for ordinary HTTP requests or (2) a production-grade ASGI server like Daphne+Channels for WebSocket requests.
Channel layer 簡單說就是允許你在一個應用曾裡不同的兩個實例間可以彼此溝通(shared memory)。假如你比須透過資料庫傳遞訊息與事件,這在分散式的即時系統非常有用。此外也可以 worker 整合在一起做一個基礎的任務列隊(task queue)或是卸載任務(offload tasks)。
在 Channels 裡面採取 Redis 作為 channel 方式,當然也可以自己嘗試做自己的 channel。channel 的作法主要來自於 go
關於一些文獻整理: