今天第 19 天了,我們剩下 11 天的時間了。
有點可惜,但似乎是沒有機會把所有的思考過程全部寫出來了呢。
真可惜,時間真的過得好快。
話說回來,我們真的能做完嗎?
不知道囉,盡人事,聽天命吧。
做不完不就證明我這次時間規劃可能有問題嗎?
小問題啦,就算做不完這也會是一個很難得的體驗。
不對阿,廢話怎麼這麼多,進正題。
挫屎,加了新的即時通,整個系統的複雜度對我而言有點高了。
我決定了,先砍掉,留到以後再做。
你的以後是多久以後?留著明年繼續打鐵人賽嗎?
超好笑,那也要我有空再說。
希望明年這個時候還有精力吧,寫鐵人賽真的好累。
先回到主題吧。
蛤?何意味?
你知道的吧。
原本的廣播就像一間吃到飽自助吧,你的目標是把全部的食物都吃一遍。
並出去跟大家分享都有哪些餐點,且餐點會臭酸。
當你想看新廣播時,就去跟老闆說:「老闆,給我來一份我目前能點到的所有餐點!」(這邊假設會隨機刷新新餐點)
雖然做法十分簡單暴力,但你浪費的食材是真的多。
會員制餐廳(喜)。
惡臭(悲)。
總之,要處理這問題也不難,最直覺的解法就是請一位服務員來,在有新餐點時讓服務員給你上一份就好了。
但如此一來又會衍生出許多問題如:
怎麼吃個飯這麼麻煩?這些聽起來都是奧客(惱)
那這樣好了,讓客人自己跟櫃台說他最後吃的那道菜是什麼不就好了嗎?
哇靠!你是天才阿!
這算一種自誇嗎?
我怎麼感覺不到天才在哪?
有,你把整個問題難度降低了不只一丁半點。
有差很多嗎?
就這麼說吧,我幾乎已經想好整個實作流程了。
太扯了吧?
你應該知道兩種上餐方式分別對應什麼更新方式吧?
輪詢跟推播?
y,這兩個方式在設計和實作上的複雜度完全不是一個等級的。
用推播我本來要搬出一整個 class 寫一個 Manager 來管理所有有的推播連線。
用輪詢我不用搞出一整套設計,只要用一點點小巧思就能完成了。
再利用資料庫查詢的優化,每次只撈上次之後的新廣播,也不用一直把整桌菜重新端上來。
沒有會員制餐廳了(悲)。
何異味???但新設計還是可以叫他會員制餐廳啦(惱)。
(喜)
所以到底要怎麼做?
總不能真的讓使用者每隔幾秒填一次:「我看到第 114514 則廣播了」吧?
當然不是。
還記得我們前面說的嗎?
每一位客人只需要記住一件事情:
「我最後吃到哪一道菜?」
假設現在伺服器裡有五則廣播:
101
102
103
104
105
而我的瀏覽器已經拿到了:
101
102
103
那下一次輪詢的時候,我根本不用再跟伺服器要一次 101 ~ 105。
只需要告訴它:
我最後拿到
103,還有新的嗎?
後端收到之後,就可以去資料庫裡找:
id > 103
最後只需要把:
104
105
丟回來。
如果沒有呢?
那就什麼都不要給我。
過幾秒再問一次就好了。
這就是我打算使用的 Incremental Polling(增量輪詢)。
與其每次都重新同步所有資料,不如讓 Client(客戶端)告訴 Server(伺服器):
「我已經同步到哪裡了?」
Server 再把後面的資料補給它。
等一下。
那第一次進來的人勒?
跟老闆說:
我一道都沒吃過。
好可憐。
……
這個做法還有另一個我很喜歡的地方。
假設今天我同時開了三台裝置:
手機:最後拿到 105
平板:最後拿到 102
電腦:第一次開啟
完全沒關係。
手機可以說:
我拿到 105 了。
平板可以說:
我只拿到 102。
電腦則是:
我什麼都沒有。
Server 根本不需要記住:
蘇某的手機 → 105
蘇某的平板 → 102
蘇某的電腦 → NULL
每個 Client 自己記住就好了。
等到下一次要資料時,再把自己的進度一起帶上來。
這個「目前同步到哪裡」的位置,我們就可以把它當成一個 Cursor(游標)。
所以整件事情最後其實變得非常單純:
Client 保存 Cursor
↓
詢問 Cursor 後有沒有新廣播
↓
Server 查詢新資料
↓
回傳
↓
Client 更新 Cursor
↓
過幾秒再來一次
所以你剛剛講得一副發現新大陸的樣子,結果就這樣?
對啊。
天才在哪?
我收回,你似乎真的是傻子。
。
而且既然 Cursor 是由 Client 自己保存的,那重新整理其實也沒什麼特別的。
例如我現在已經同步到:
105
那瀏覽器只要把這個 105 保存下來。
就算我現在直接把網頁關掉,這段時間 Server 又出現:
106
107
108
下次打開網站時,我還是可以直接告訴 Server:
我上次拿到
105。
然後把:
106
107
108
補回來。
也就是說,Server 根本不用特別維護:
「這個使用者現在看到哪裡?」
更不用管同一個使用者到底開了幾台裝置。
每台裝置管好自己就好了。
等等,你這個設計是不是有洞?
假設我已經拿到105了,但後來87被修改了。
我之後不是永遠只會問105後面的東西?
那87的修改勒?
對。
如果今天我們處理的是一個資料可以隨意修改、刪除的系統,那單純記住最後一筆資料的 ID 確實不夠。
因為 Client 現在的 Cursor 如果是:
105
下一次它只會去找:
id > 105
那前面的 87 就算被改得面目全非,Client 也不會知道。
那你這個設計不是炸了嗎?
還好。
因為我們很幸運。
這是廣播。
廣播一旦送出去,就不應該偷偷修改,更不能偷偷撤回。
如果真的寫錯了呢?
那就再發一則更正。
#105
明天下午 3:00 於活動中心集合
#106
【更正】上一則廣播集合時間有誤,
應為明天下午 4:00。
這樣不只能讓收到原廣播的人知道內容有更正,也不會讓一則已經發出去的廣播莫名其妙變成另一個東西。
所以對我們來說,廣播的資料其實很接近 Append-only(只追加):
101
102
103
104
105
↓
新增
↓
106
新的廣播只會繼續往後加,前面的資料則不會突然被修改或消失。
等等,那你前面說餐點會臭酸又是什麼?
你不是說資料不會消失嗎?
這是兩件事情。
餐點臭酸,不代表餐點憑空消失了。
假設老師在下課時發了一則:
請資訊股長下節上課前來辦公室拿東西。
結果我們的系統直到下一節課都上完了,才把這則廣播送到資訊股長手上。
資料有被修改嗎?
沒有。
資料有被刪掉嗎?
也沒有。
那系統有正常完成工作嗎?
……
好像也沒有。
對。
因為這則廣播雖然還是完全正確的資料,但它已經沒有用了。
餐點臭酸了。
所以不能送太慢?
對。
這也是為什麼我們雖然決定不用 WebSocket,卻不能:
反正是輪詢,那一小時問一次好了。
Polling interval(輪詢間隔)還是得控制在合理範圍內。
我們真正需要的並不是:
「新廣播發布的瞬間就必須抵達。」
而是:
「新廣播必須在失去時效性以前抵達。」