Skip to content

Instantly share code, notes, and snippets.

@chairco
Last active May 1, 2019 12:28
Show Gist options
  • Save chairco/7726fae29c57eba6455b666595b55a85 to your computer and use it in GitHub Desktop.
Save chairco/7726fae29c57eba6455b666595b55a85 to your computer and use it in GitHub Desktop.
Real-time web, Asynchronous I/O and Django channels 介紹,為 PyConTW 2018 演講概略內容。

Real-time web

維基是這樣定義 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 架構。

其他框架

Framework/web servers

  • Twisted
  • Tornado

Async engines with websocket servers

  • gevent

Adding support to existing WSGI Servers

  • uWSGI
| nginx(proxy) | <-(uwsgi 協議)-> | uWSGI | <-(wsgi)-> | Django App |
                 ________________        ___________
                        ^                      ^
                        |                      |  
                        |<------ |cgi| ------->|
  • nginx 代理伺服器: 負責靜態資源發送(js, css, media),動態請求轉發與結果回覆。
  • uWSGI 後端伺服器: 接受 nginx 請求轉發並處理後發給 Django App 以及接收 Django 應用回傳訊息轉給 nginx。
  • Django App 應用程式: 收到請求後處理數據並且進行渲染對應頁面給 uWSGI 服務器。

Asynchronous

講非同步之前稍微說明過去同步執行所面臨的一些困境:程式執行可分成 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(1.x)

Django Channels 是 Django 基金會所維護的一個 open project 預計未來會整合進入 Django Project 但未來是何時呢??不過這個問題,我覺得不會是重點,重點先來談談 Channels 這個官方的套件架構是什麼?

圖片是傳統的 Django 視圖,就是上面稱為的輪播(polling)。

Imgur

接著是 Django Channels 加入後的架構。

Imgur

  • Interface: Daphne 這個套件用來取代 WSGI Server 讓 interface 可以選擇接受 WebSocket 或是 HTTP。
  • Channel layer: 一個資料結構 FIFO(ASGI)
  • Worker: Redis

Interface

官方網站有個很好舉例:

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

Channel layer 簡單說就是允許你在一個應用曾裡不同的兩個實例間可以彼此溝通(shared memory)。假如你比須透過資料庫傳遞訊息與事件,這在分散式的即時系統非常有用。此外也可以 worker 整合在一起做一個基礎的任務列隊(task queue)或是卸載任務(offload tasks)。

在 Channels 裡面採取 Redis 作為 channel 方式,當然也可以自己嘗試做自己的 channel。channel 的作法主要來自於 go

Django Channels(2.x)

@chairco
Copy link
Author

chairco commented Apr 23, 2018

草稿 -4/23

@chairco
Copy link
Author

chairco commented Apr 25, 2018

@chairco
Copy link
Author

chairco commented Apr 25, 2018

@chairco
Copy link
Author

chairco commented Apr 29, 2018

@chairco
Copy link
Author

chairco commented Apr 29, 2018

[pycon]pycon 2016 day 3 小整理 from 共筆
關於Django & Twisted
Scaling Django Application
Django server only response one request a time
concurrent request = thread x pools
Higher scale means higher complexity
有沒有更好的方法去容納更多的 request ?
CPU-bound: 數學運算、資料運算等
IO-bound: Database requests, web requests, other network IO
ex. 跟資料庫溝通獲取某些資訊

Asynchronous IO Programming
所有的事件都是從 IO 來的,我們就等他來就好啦 ~
Twist
selector function
有一個 list 有一堆 file descriptor(socket, …)
selector loop 可以處理上千個 file descriptor (?)
nothing locks, it give control to the next event
no blocking means no threads
event driven 的最佳case是I/O bound的程式,低CPU使用率
若程式是CPU bound,則搭配task queue
Putting task on the queue and removing them is cheap
所以 task queue 要擴張比較容易
想要做更多工作的話,就增加更多的 worker 吧
Django channel
Project for Asynchronous django
interface server > channel queue > many of workers
interface server: 就只是接收 requests
workers: 拿走 queue 裡面的 request 然後處理他們,處理完後在放回 channel
當 requests 被 worker 處理好的時候
interface server 會把 response 撿走
拿去回應到對的 requests
daphne: Daphne is a HTTP, HTTP2 and WebSocket protocol server for ASGI, and developed to power Django Channels.
Daphne is written in Twisted.
The channel layer can be shard ( ?
可以有很多個 channel queue 一起處理這些 request
worker 也不一定要被放在 web server 裡面
如果規模不大的話,也可以把 channel queue 放在 shared memory 裡面
how channels work
request
incoming HTTP request
workers listen on these channel name(如果有工作就送給 worker)
http.response!c134x7y
有這個 code 就能把他指回去對的地方
worker 自己預設不會使用非同步 IO,你不會有 blocked worker 這種東西
group
worker can listening on specific channel, they don’t need to listen all of them
fan-out message
Channel is a bridge to Asynchronous feature
Django Channel document
可能在 Django 1.11/2.0 會 release
QA Time
Q: Why JSON not BSON…?
A: 在一般狀況下 JSON 足以,但在需要的情況下, 可以自訂需要的 serialization(非官方)
Q: queue 是 FIFO,可以自訂嗎?
A: 在 Channel 這層只能 FIFO, 如果需要針對不同的 task 有不同的處理優先權, 可以把不同種類的 tasks 放到不同的 channel 上, 就能用個別的 worker 去處理
Q: in-memory queue?
A: 需要 IPC,很多 machine 的情況下沒辦法 (?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment