其實分別真的不太大的,還不是一樣的load balancer(看你想玩AWS ELB還是自架HAproxy),Application Server,然後便配上RDBMS
今天能輕易租到32core再配512GB RAM的機器來架RDBMS,讓今天C10K的難度跟5年前真的簡單了不止10倍。
在C10K環境,你的公司應該也有一定預算給伺服器的。
所以正常地寫,postgresql和mariadb都應該可以支持的。特別是現在的RDBMS都對multithreading已經有大幅改善,你給他多少core都可以用光光的。(之前的postgresql 8.X時只吃到2個core,那時真的會哭的)
在C10K環境,系統會死掉的100%原因,其實都是細節地方沒做好。
- 像是nginx沒設定好
- connection pool沒設定好
- linux的file descriptor沒設定好
然後程式中的小問題,在C100可能是小火花的,在C10K時便直接趴給你看。
- 然後db schema設計如果有失誤(比如說:亂加了不該加的index),C100不會浮出來的問題,C10K時便又是趴得一敗塗地的。
簡單來說:C100K我還沒戰過沒話能說。但是C10K其實是考驗大家在所有細節上的基本功夫是否有做好,其架構跟C100分別不會太大的。
簡單來說,C10K下跑Zrange會讓Redis變超慢,如果你不會BigO那根本不用談了
其實很多新產品都說自己效能一流,用了他便能戰C10K嘛。
最明顯例子:Mongodb,和整天號稱能Concurrent 1M connection的Node.JS
(吐嘈1:Node.JS的那1M connection是指Idle TCP/IP connection,基本上Java在Node.JS出來的第一天也早便做到了)
(吐嘈2:Mongodb的笑能嘛……呵呵呵)
有些細節,是你沒辛苦過便不會知道的:
例如我有10個docker worker,每一個worker每大約500ms都要連線到資料庫做一些事情(critical zone)
程式碼1:
t = timeTicker.New(500ms)
for every tick in t {
DBCriticalZoneFunc()
}
程式碼2:
while (1) {
DBCriticalZoneFunc()
sleep(500ms)
}
程式碼2的系統效能遠比程式碼1的高 (關鍵字:jitter)
在IP 網路中,封包到達目的地的時間長短不一,稱為jitter(抖動)
There is probably jitter in the network traffic, which is normal, particularly for TCP traffic where the network guarantees in-order delivery, so that when one packet is late, all the others behind it have to wait.
然後有人回答:以redis搶token,搶到token的人才能進購買頁面,這樣子才能保護資料庫不被瞬間高流量炸掉。
————————————————————————————————————————————————
首先,不管你是否面對C10K,所有的基本功夫請先做好。
不管何時,請嚴格遵守"Keep it simple, stupid"這個重要原則。
另一個說法: 不作死就不會死。 系統越是複雜,設計失誤/執行上有coding bug的可能性便越高。 (同義版本:「premature optimization is the root of all evil」)
- 「商品狀態」的頁面是完全地read-only的
- 頁面本身有設定好CDN嗎?
- 商品餘額有做好caching嗎?
好的系統設計,應該盡可能強壯,即使局部性有問題也不會整個系統全滅的。
現在搶買時段,資料庫100%忙碌於write上面,好的系統應該讓read-only traffic相對上不受影響的。
- 有測過資料庫的每秒流量上限,然後以此去正確設定資料庫的max connection嗎?還是吃到飽吃到當機為止?
- Application上的connection pool有好好設定?
- Application的max worker thread有設定好嗎?
- Load balancer有設定好嗎?
以上任何一個細節錯誤,就是你當機/performance bottleneck的地方。 不管你們架構設計得怎漂亮也好,細節上犯錯,系統還是會坦白地當給你看。
同一份的資料,如果你需要維持strong consistency,最終你的Write是沒法distributed,讓你的系統出現hotspot的。
如果現在為了保護資料庫,進購買頁面前都要先從redis拿一個token,拿到了才給進購買頁面。
這樣子以redis作rate-limiting,就代表了所有的request都要先問一下redis
去查詢同一個資料結構(通常都是用上token bucket
算法吧)。
存有這一個用作rate-limiting資料結構的redis-node肯定會成為hot-spot,最後炸掉當機。
假設資料庫每秒寫入上限是1000,那麼我們大約定下希望900左右的rate-limiting吧
而最終每秒是920還是880,其實沒太關係的。(因為真正的交易在資料庫,不在這裡)
所以,面對上>C10K的搶票流量時,
Application server應該每次不是拿一個token from redis,而是一次拿上100(這數字怎計出來有空再說XD),然後再把剩下來的99個存到local memory未來使用。
這種local buffering雖然讓系統的ratelimiting沒法精準地停在每秒900,但是現在就不止有redis上的一個資料結構而是同一時間每台Application server也有其buffer,不會引發hotspot。
如果Application server從redis拿不到token,未來random(500ms)時間內Application server應該直接回傳http 419 / 503。 配合第2點,在ratelimiting 1000下,redis的每秒流量只會是Application server總數+1000 / 100。 這樣子的redis才不會炸掉的。
———————————————————————————————————————————————— 補充:
打算以C#在application server上建立task queue的人。 這樣子在真正常流量時,他們的task queue會不停變長,最後引發application server不夠RAM當機。然後因為一台application server當機,剩下的伺服機更忙而相繼當機。 最終整個系統當得什麼也沒剩下total outage。 以上本人親人經歷。
xizhibei/blog#29 (這是一篇 2016 年的文章資訊)
- Token Bucket:定時的往每個bucket內放入token,然後每個請求都會減去token,假如沒有token的話就采取限流措施,能夠限制平均請求頻率;
- Leaky Bucket:做個緩沖隊列,所有請求進入bucket排隊,超過bucket size則丟棄,能夠應付流量暴漲,請求集中;
兩個算法,都有優缺點,都有適合的場景,比如Leaky Bucket
很適合秒殺,因為需要應對所有用戶的請求,用在正常業務中,容易卡著正常業務;而Token Bucket的話,更適合用在我們正常業務中的場景,限制接口的請求頻率。
拿TJ的一個項目來說:RateLimiter
算是一個Token Bucket的實現,每過一個duration,就讓bucket重新填滿,
- 優點是處理簡單,快速,
- 缺點還是是無法避免請求的集中效應
比如你限制每個小時1000次,那就是說,每個小時的開始的一秒內,爬蟲可以用1000個並發(實際上,如果上個小時,爬蟲沒有任何請求,則可以在上個小時結束的一秒以及這個小時開始的時候的2秒內請求2000次)來搞垮你的服務器。
當然了,你可以再加入更小的duration,比如10分鐘內100次,1分鐘內10次,這樣的確可以避免這種情況,但是效率比較低下,畢竟要經過三層,而且,每層都要計數,一旦一層超過了,其它兩層可能無法計數了,如果反過來,先檢查最大的1小時,則還是會遇到每小時開始時候重置的問題。
所以,我們需要這個固定duration滑動起來,不會有reset,就可以一定程度上避免了集中效應。
目前查到的Node.js實現的項目中,只有兩個實現了滑動窗口限流:
只是實現略覆雜,效率可能比不上上面的,簡單來說是
- 把duration切分成多個block,
- 然後單獨計數,時間每經過一個block的長度,就向前滑動一個block,
- 然後每次請求都會計算那個duration直接的block內的請求數量。
只是,如果還是單個duration的話,並不能解決集中效應。簡單比較下這兩個滑動窗口的方案:
- 優點:實現簡單優雅,代碼容易看懂,由於沒用lua腳本(需要支持script相關命令),很多雲服務商提供的redis可以用了;
- 缺點:功能不夠豐富,代碼很久沒更新了,只支持node_redis;
- 優點:使用lua腳本實現了具體邏輯,減少通訊時間,效率高,支持多個duration,並且還有白黑名單功能,支持ioredis以及node_redis;
- 缺點:可能還是因為lua腳本,如果不熟悉的話,看起來比較吃力;
對於惡意請求,假如對方的反反爬蟲做的一般的話,完全可以直接將對方的IP加入黑名單,但如果對方用的IP代理,那就不是限流這個方案能解決的了,需要更高級的反爬蟲方案。
但是我想提一點,對方既然爬了你的數據,肯定有對方的用處,假如對方沒有惡意,請求頻率也沒有讓你的機器有太多壓力,那也就算了,畢竟你可能也在爬其它人的數據,大家都是搞技術的,沒準對方背著萬惡的KPI呢。
但是,如果對方惡意爬取,那麽你完全可以在探測到對方的請求之後,返回空數據,甚至以假亂真的數據欺騙對方,讓對方無利可圖,對方也可能就會主動放棄了。
查日志,統計下用戶的正常請求就行。
express以及koa框架下,如果是在反向代理服務器nginx或者haproxy之類的後面,這時候獲取用戶的IP的話,則需要設置trust proxy
可能你註意到了,我介紹的三個項目都是基於redis做的,原因無非是快,效率高,多進程多機器狀態共享,對於其它的我覺得mencached勉強可以考慮,如果基於內存的話,無法處理多進程,多機器的情況。
什麽?MongoDB?MySQL?別逗了。。。