我看到有很多系統都用靜態的方式來分配,有的用hash,有的就簡單地輪流分析。這些都不夠好,一個是不能完美地負載均衡,另一個靜態的方法的致命缺陷是,如果有一臺計算服務器死機了,或是我們需要加入新的服務器,對于我們的分配器來說,都需要知道的。
還有一種方法是使用搶占式的方式進行負載均衡,由下游的計算服務器去任務服務器上拿任務。讓這些計算服務器自己決定自己是否要任務。這樣的好處是可以簡化系統的復雜度,而且還可以任意實時地減少或增加計算服務器。但是唯一不好的就是,如果有一些任務只能在某種服務器上處理,這可能會引入一些復雜度。不過總體來說,這種方法可能是比較好的負載均衡。
五、異步、 throttle 和 批量處理
異步、throttle(節流閥) 和批量處理都需要對并發請求數做隊列處理的。
異步在業務上一般來說就是收集請求,然后延時處理。在技術上就是可以把各個處理程序做成并行的,也就可以水平擴展了。但是異步的技術問題大概有這些,a)被調用方的結果返回,會涉及進程線程間通信的問題。b)如果程序需要回滾,回滾會有點復雜。c)異步通常都會伴隨多線程多進程,并發的控制也相對麻煩一些。 d)很多異步系統都用消息機制,消息的丟失和亂序也會是比較復雜的問題。
throttle 技術其實并不提升性能,這個技術主要是防止系統被超過自己不能處理的流量給搞垮了,這其實是個保護機制。使用throttle技術一般來說是對于一些自己無法控制的系統,比如,和你網站對接的銀行系統。
批量處理的技術,是把一堆基本相同的請求批量處理。比如,大家同時購買同一個商品,沒有必要你買一個我就寫一次數據庫,完全可以收集到一定數量的請求,一次操作。這個技術可以用作很多方面。比如節省網絡帶寬,我們都知道網絡上的MTU(最大傳輸單元),以態網是1500字節,光纖可以達到4000多個字節,如果你的一個網絡包沒有放滿這個MTU,那就是在浪費網絡帶寬,因為網卡的驅動程序只有一塊一塊地讀效率才會高。因此,網絡發包時,我們需要收集到足夠多的信息后再做網絡I/O,這也是一種批量處理的方式。批量處理的敵人是流量低,所以,批量處理的系統一般都會設置上兩個閥值,一個是作業量,另一個是 timeout,只要有一個條件滿足,就會開始提交處理。
所以,只要是異步,一般都會有throttle機制,一般都會有隊列來排隊,有隊列,就會有持久化,而系統一般都會使用批量的方式來處理。
云風同學設計的“排隊系統” 就是這個技術。這和電子商務的訂單系統很相似,就是說,我的系統收到了你的購票下單請求,但是我還沒有真正處理,我的系統會跟據我自己的處理能力來throttle住這些大量的請求,并一點一點地處理。一旦處理完成,我就可以發郵件或短信告訴用戶你來可以真正購票了。
在這里,我想通過業務和用戶需求方面討論一下云風同學的這個排隊系統,因為其從技術上看似解決了這個問題,但是從業務和用戶需求上來說可能還是有一些值得我們去深入思考的地方:
1)隊列的DoS攻擊
首先,我們思考一下,這個隊是個單純地排隊的嗎?這樣做還不夠好,因為這樣我們不能杜絕黃牛,而且單純的ticket_id很容易發生DoS攻擊,比如,我發起N個 ticket_id,進入購票流程后,我不買,我就耗你半個小時,很容易我就可以讓想買票的人幾天都買不到票。有人說,用戶應該要用身份證來排隊, 這樣在購買里就必需要用這個身份證來買,但這也還不能杜絕黃牛排隊或是號販子。因為他們可以注冊N個帳號來排隊,但就是不買。黃牛這些人這個時候只需要干一個事,把網站搞得正常人不能訪問,讓用戶只能通過他們來買。
2)對列的一致性?
對這個隊列的操作是不是需要鎖?只要有鎖,性能一定上不去。試想,100萬個人同時要求你來分配位置號,這個隊列將會成為性能瓶頸。你一定沒有數據庫實現得性能好,所以,可能比現在還差
3)隊列的等待時間
購票時間半小時夠不夠?多不多?要是那時用戶正好不能上網呢?如果時間短了,用戶不夠時間操作也會抱怨,如果時間長了,后面在排隊的那些人也會抱怨。這個方法可能在實際操作上會有很多問題。另外,半個小時太長了,這完全不現實,我們用15分鐘來舉例:有1千萬用戶,每一個時刻只能放進去1萬個,這1萬個用戶需要15分鐘完成所有操作,那么,這1千萬用戶全部處理完,需要1000*15m = 250小時,10天半,火車早開了。(我并非亂說,根據鐵道部專家的說明:這幾天,平均一天下單100萬,所以,處理1000萬的用戶需要十天。這個計算可能有點簡單了,我只是想說,在這樣低負載的系統下用排隊可能都不能解決問題)
4)隊列的分布式
這個排隊系統只有一個隊列好嗎?還不足夠好。因為,如果你放進去的可以購票的人如果在買同一個車次的同樣的類型的票(比如某動車臥鋪),還是等于在搶票,也就是說系統的負載還是會有可能集中到其中某臺服務器上。因此,最好的方法是根據用戶的需求——提供出發地和目的地,來對用戶進行排隊。而這樣一來,隊列也就可以是多個,只要是多個隊列,就可以水平擴展了。
我覺得完全可以向網上購物學習。在排隊(下單)的時候,收集好用戶的信息和想要買的票,并允許用戶設置購票的優先級,比如,A車次臥鋪買 不到就買 B車次的臥鋪,如果還買不到就買硬座等等,然后用戶把所需的錢先充值好,接下來就是系統完全自動地異步處理訂單。成功不成功都發短信或郵件通知用戶。
這樣系統不僅可以省去那半個小時的用戶交互時間,自動化加快處理,還可以合并相同購票請求的人,進行批處理(減少數據庫的操作次數)。這種方法最妙的事是可以知道這些排隊用戶的需求,不但可以優化用戶的隊列,把用戶分布到不同的隊列,還可以像亞馬遜的心愿單一樣,讓鐵道部做車次統籌安排和調整(最后,排隊系統(下單系統)還是要保存在數據庫里的或做持久化,不能只放在內存中,不然機器一down,就等著被罵吧)。
小結
寫了那么多,我小結一下:
0)無論你怎么設計,你的系統一定要能容易地水平擴展。也就是說,你的整個數據流中,所有的環節都要能夠水平擴展。這樣,當你的系統有性能問題時,“加3倍的服務器”才不會被人譏笑。