系列目錄
- NodeJS與Django協同應用開發(0) node.js基礎知識
- NodeJS與Django協同應用開發(1)原型搭建
- NodeJS與Django協同應用開發(2)業務框架
- NodeJS與Django協同應用開發(3)測試與優化
- NodeJS與Django協同應用開發(4)部署
這篇文章主要會講業務的框架設計,不過可講內容不會那么多,畢竟不能將公司業務細節公布出來,權當拋磚引玉。
目前來說我們的業務只有一個實時性的要求,簡單來說就是多人共同參與一場活動,每個人都能看到其他人的操作,此外還有一部分用戶授權系統模擬其自身操作的業務。我們分別說一下開發過程中的幾個問題。
首先是系統的入口。
為了靈活起見,前端在連接socket.io時所使用的地址是django后臺指定的。這么做有兩個好處:
- 分散業務
- 分散壓力
不過我們的項目目前還體現不出這么做的好處,因為體量比較小,不過體量變大后,一臺服務器難以承擔壓力時,這么做就比較有必要了。我們可以把不同的業務劃分在不同集群上,然后按id分配給不同的服務器。
這么做的代碼量是會比較大,所以也不必一步做到位,目前對于業務的擴展我們采取的策略還是先擴展模塊,并在node.js端做連接類型識別,然后分發給不同的模塊。至于我們的服務器,目前還沒涉及到集群。
不過這么做麻煩的地方是在部署的時候有些重復的工作,這個在后面的文章再討論。
其次是用戶身份驗證。
在連接socket.io時,會由django后臺生成identification作為合法用戶認證用來連接node.js服務器。這里可以做的工作也比較多。
第一是登錄用戶和非登錄用戶的區別。
session管理是由django完成的,因此對于node.js來說一切的連接請求都不可信。所以這里安全的做法是將明文identification(比如用戶名或者id)加鹽后加密,node.js端再解密進行對比。不過并不確定解密是否會給node.js造成計算壓力,node.js對于CPU密集型任務并不是很擅長,如果真的瓶頸在此那可以考慮用cluster.fork()專門開進程處理解密任務。
第二是同一登錄用戶多終端連接的問題。
非登錄用戶本身不可識別就不必考慮了,登錄用戶如果多開終端,那如何針對性推送是需要考慮的事情。如果按socket為單位處理用戶,那當同一個用戶的socketA觸發了一個事件并有一些針對性的回調時,還需要同時調用socketB和socketC的回調。這在設計時非常容易被忽略,并造成一定的困擾。
在這種情況下一個可行的辦法是同時只允許一個終端連接,如果有新終端連接那就把舊終端斷開。不過這也得按照需求場景來制定策略,并不是所有的業務都可以這么做的。
當然更好的辦法是按用戶id為單位處理,同一個id下可以有多個socket,任何終端上的操作只要是同一id就視作同一來源。這樣在邏輯上更加清晰,只是需要額外的數據結構來保存用戶信息,并且要注意及時回收資源以免浪費內存。
第三是用戶授權模擬自身操作時的驗證。
這相當于用戶扔了一個異步任務給后臺,后臺在某個時間或者某個事件之后會觸發該任務,視同用戶本身進行了某些操作。原本的方案是復用django api,但是這帶來的問題是我們需要記錄用戶的sessionid和csrftoken,才能夠在之后的時刻模擬這個用戶。但是一旦用戶在隨后的時間內登出了或者在終端上切換了賬戶,sessionid就會失效,整個授權行為就變得無意義了。現在想想最初的方案真的是非常愚蠢,sessionid在任何時候都不該被記錄下來并當作憑證,因為它可能在任何時候失效。
后來的做法是將相關的函數提取出純業務邏輯,以用戶id為憑證,再用RPC復用。由于RPC是部署在內網的,就不必擔心安全問題,只用確保在授權時是用戶本人即可。
再次是API設計
這里的API是指暴露給用戶的操作。前文我們也說過在我們設計初衷里node.js服務器不承擔或只承擔小部分的業務邏輯,所以用戶的操作都是不經過node.js而是直接提交給django端,再由django向node.js推送消息并最終將結果推送給用戶。并且上文也說到了,對于node.js來說所有連接都是不可信的,這么做就更有必要了。
在我們的需求里,用戶提交操作后要求其他用戶立刻看到,但是部分操作又包含了大量的信息驗證,不僅僅只是登錄驗證。這些驗證與數據庫里的數據交互非常多,所以這也是不能放在node.js端的原因之一。
因此我們客戶端的代碼里除了向node.js提交身份信息以外,其余任務只有接受推送而已。但是這不代表node.js不能夠提供api或者socket.io不能夠提供event,一些弱安全性的,比如聊天就不需要走django,這反而會給django帶來不必要的壓力。
也就是說,讓node.js服務器變得單純一些,甚至可以把node.js當作一個可替換組件,不要讓它涉及核心業務。
最后是可持續化設計
有沒有考慮過一旦node.js崩潰了,正在參與活動的用戶怎么辦?不像django,node.js可能保存了一定量的用戶狀態(這種情況可能是業務所需),一旦系統崩潰了這些狀態都丟了。這里的解決辦法很多,網絡上類似的話題也不少,放開了說是一個很大的范疇,就說說我們是怎么辦的吧。
我們的解決方案是,不在node.js端儲存任何用戶狀態,一切需要儲存的內容都經由django存入數據庫。想要允許node.js使用數據庫是可以的,但是要保持和django ORM一致卻有一定的工作量,而且也不建議這么做。
在我們的業務里用戶的狀態是由用戶操作和一些定時任務修改的,加上我們的api是走的django,所以在用戶狀態這一環節上node.js完全沒有參與。還是那句話,node.js服務器不承擔或只承擔小部分業務邏輯,多數情況下只把它當作一個單純的推送服務器。
總的來說開發過程里值得記錄的就是這么些問題,然后就是對node.js的測試和優化了,這個我們后文再聊。