Skip to content

Latest commit

 

History

History
178 lines (126 loc) · 10.9 KB

2019-02-21:Triton 閒聊 C10K.md

File metadata and controls

178 lines (126 loc) · 10.9 KB

Triton 閒聊 C10K

可搭配 redis 那篇一起看

有人問C10K的系統跟C100系統的系統架構有什麼分別嘛。

其實分別真的不太大的,還不是一樣的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時,演算法是他媽的超級重要。

簡單來說,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)

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」)

  1. 「商品狀態」的頁面是完全地read-only的
  2. 頁面本身有設定好CDN嗎?
  3. 商品餘額有做好caching嗎?

好的系統設計,應該盡可能強壯,即使局部性有問題也不會整個系統全滅的。

現在搶買時段,資料庫100%忙碌於write上面,好的系統應該讓read-only traffic相對上不受影響的。

  1. 有測過資料庫的每秒流量上限,然後以此去正確設定資料庫的max connection嗎?還是吃到飽吃到當機為止?
  2. Application上的connection pool有好好設定?
  3. Application的max worker thread有設定好嗎?
  4. Load balancer有設定好嗎?

以上任何一個細節錯誤,就是你當機/performance bottleneck的地方。 不管你們架構設計得怎漂亮也好,細節上犯錯,系統還是會坦白地當給你看。

關鍵點1:

同一份的資料,如果你需要維持strong consistency,最終你的Write是沒法distributed,讓你的系統出現hotspot的。

如果現在為了保護資料庫,進購買頁面前都要先從redis拿一個token,拿到了才給進購買頁面。
這樣子以redis作rate-limiting,就代表了所有的request都要先問一下redis
去查詢同一個資料結構(通常都是用上token bucket算法吧)。

存有這一個用作rate-limiting資料結構的redis-node肯定會成為hot-spot,最後炸掉當機。

關鍵點2:

假設資料庫每秒寫入上限是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。

關鍵點3:

如果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。 以上本人親人經歷。

令牌桶(Token Bucket)和漏桶(leaky bucket)

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,就可以一定程度上避免了集中效應。

Token Bucket滑动窗口限流

目前查到的Node.js實現的項目中,只有兩個實現了滑動窗口限流:

只是實現略覆雜,效率可能比不上上面的,簡單來說是

  • 把duration切分成多個block,
  • 然後單獨計數,時間每經過一個block的長度,就向前滑動一個block,
  • 然後每次請求都會計算那個duration直接的block內的請求數量。

只是,如果還是單個duration的話,並不能解決集中效應。簡單比較下這兩個滑動窗口的方案:

redback/RateLimit:
  • 優點:實現簡單優雅,代碼容易看懂,由於沒用lua腳本(需要支持script相關命令),很多雲服務商提供的redis可以用了;
  • 缺點:功能不夠豐富,代碼很久沒更新了,只支持node_redis;
ratelimit.js:
  • 優點:使用lua腳本實現了具體邏輯,減少通訊時間,效率高,支持多個duration,並且還有白黑名單功能,支持ioredis以及node_redis;
  • 缺點:可能還是因為lua腳本,如果不熟悉的話,看起來比較吃力;

限制了後怎麽做

對於惡意請求,假如對方的反反爬蟲做的一般的話,完全可以直接將對方的IP加入黑名單,但如果對方用的IP代理,那就不是限流這個方案能解決的了,需要更高級的反爬蟲方案。

但是我想提一點,對方既然爬了你的數據,肯定有對方的用處,假如對方沒有惡意,請求頻率也沒有讓你的機器有太多壓力,那也就算了,畢竟你可能也在爬其它人的數據,大家都是搞技術的,沒準對方背著萬惡的KPI呢。

但是,如果對方惡意爬取,那麽你完全可以在探測到對方的請求之後,返回空數據,甚至以假亂真的數據欺騙對方,讓對方無利可圖,對方也可能就會主動放棄了。

如何設置限制參數

查日志,統計下用戶的正常請求就行。

PS1:

express以及koa框架下,如果是在反向代理服務器nginx或者haproxy之類的後面,這時候獲取用戶的IP的話,則需要設置trust proxy

PS2:

可能你註意到了,我介紹的三個項目都是基於redis做的,原因無非是快,效率高,多進程多機器狀態共享,對於其它的我覺得mencached勉強可以考慮,如果基於內存的話,無法處理多進程,多機器的情況。

什麽?MongoDB?MySQL?別逗了。。。

其他文章