構建億級前端讀服務

jopen 9年前發布 | 9K 次閱讀 服務


從入職京東到現在,做讀服務已經一年多的時間了,經歷了各種億級到百億級的讀服務;這段時間也進行了一些新的讀服務架構嘗試,從架構到代碼的編寫,各個環節都進行了反復嘗試,壓測并進行調優,希望得到一個自己滿意的讀服務架構。

一些設計原則

  • 無狀態
  • 數據閉環
  • 緩存銀彈
  • 并發化
  • 降級開關
  • 限流
  • 切流量
  • 其他

無狀態

如果設計的應用是無狀態的,那么應用就可以水平擴展,當然實際生產環境可能是這樣子的: 應用無狀態,配置文件有狀態。比如不同的機房需要讀取不同的數據源,此時就需要通過配置文件指定。

數據閉環

如果依賴的數據來源特別多,此時就可以考慮使用數據閉環,基本步驟:

1、數據異構:通過如MQ機制接收數據變更,然后原子化存儲到合適的存儲引擎,如redis或持久化KV存儲;

2、數據聚合:這步是可選的,數據異構的目的是把數據從多個數據源拿過來,數據聚合目的是把這些數據做個聚合,這樣前端就可以一個調用拿到所有數據,此步驟一般存儲到KV存儲中;

3、前端展示:前端通過一次或少量幾次調用拿到所需要的數據。

這種方式的好處就是數據的閉環,任何依賴系統出問題了,還是能正常工作,只是更新會有積壓,但是不影響前端展示。

另外此處如果一次需要多個數據,可以考慮使用Hash Tag機制將相關的數據聚合到一個實例,如在展示商品詳情頁時需要:商品基本信息:p:123:, 商品規格參數:d:123:,此時就可以使用冒號中間的123作為數據分片key,這樣相同id的商品相關數據就在一個實例。

緩存銀彈

緩存對于讀服務來說可謂抗流量的銀彈。

瀏覽器端緩存

設置請求的過期時間,如響應頭Expires、Cache-control進行控制。這種機制適用于如對實時性不太敏感的數據,如商品詳情頁框架、商家評分、評價、廣告詞等;但對于如價格、庫存等實時要求比較高的,就不能做瀏覽器端緩存。

CDN緩存

有些頁面/活動頁/圖片等服務可以考慮將頁面/活動頁/圖片推送到離用戶最近的CDN節點讓用戶能在離他最近的節點找到想要的數據。一般有兩種機 制:推送機制(當內容變更后主動推送到CDN邊緣節點),拉取機制(先訪問邊緣節點,當沒有內容時回源到源服務器拿到內容并存儲到節點上),兩種方式各有 利弊。 使用CDN時要考慮URL的設計,比如URL中不能有隨機數,否則每次都穿透CDN,回源到源服務器,相當于CDN沒有任何效果。對于爬蟲可以返回過期數 據而選擇不回源。

接入層緩存

對于沒有CDN緩存的應用來說,可以考慮使用如Nginx搭建一層接入層,該接入層可以考慮如下機制實現:

1、URL重寫:將URL按照指定的順序或者格式重寫,去除隨機數;

2、一致性哈希:按照指定的參數(如分類/商品編號)做一致性Hash,從而保證相同數據落到一臺服務器上;

3、proxy_cache:使用內存級/SSD級代理緩存來緩存內容;

4、proxy_cache_lock:使用lock機制,將多個回源合并為一個,減少回源量,并設置相應的lock超時時間;

5、shared_dict:此處如果架構使用了nginx+lua實現,可以考慮使用lua shared_dict進行cache,最大的好處就是reload緩存不丟失。

此處要注意,對于托底/異常數據不應該讓其緩存,否則用戶會在很長一段時間看到這些數據。

應用層緩存

如我們使用Tomcat時可以使用堆內緩存/堆外緩存,堆內緩存的最大問題就是重啟時內存中的緩存丟失,如果此時流量風暴來臨可能沖垮應用;還可 以考慮使用local redis cache來代替堆外內存;或者在接入層使用shared_dict來將緩存前置,減少風暴。

分布式緩存

一種機制就是廢棄分布式緩存,改成應用local redis cache,即在應用所在服務器中部署一個redis,然后使用主從機制同步數據。如果數據量不大這種架構是最優的;如果數據量太大,單服務器存儲不了, 還可以考慮分片機制將流量分散到多臺;或者直接就是分布式緩存實現。常見的分片規則就是一致性哈希了。

構建億級前端讀服務 如上圖就是我們一個應用的架構: 

1、首先接入層讀取本地proxy cache / local cache;

2、如果不命中,會讀取分布式redis集群;

3、如果還不命中,會回源到tomcat,然后讀取堆內cache;如果沒有,則直接調用依賴業務獲取數據;然后異步化寫到redis集群;

因為我們使用了nginx+lua,第二、三步可以使用lua-resty-lock非阻塞鎖減少峰值時的回源量;如果你的服務是用戶維度的,這種非阻塞鎖不會有什么大作用。

并發化

假設一個讀服務是需要如下數據:

1、數據A  10ms

2、數據B  15ms

3、數據C   20ms

4、數據D   5ms

5、數據E   10ms

那么如果串行獲取那么需要:60ms;

而如果數據C依賴數據A和數據B、數據D誰也不依賴、數據E依賴數據C;那么我們可以這樣子來獲取數據:

構建億級前端讀服務

那么如果并發化獲取那么需要:30ms;能提升一倍的性能。

假設數據E還依賴數據F(5ms),而數據F是在數據E服務中獲取的,此時就可以考慮在此服務中在取數據A/B/D時預取數據F,那么整體性能就變為了:25ms。

降級開關

對于一個讀服務,很重要的一個設計就是降級開關,在設計降級開關時主要如下思路:

1、開關集中化管理:通過推送機制把開關推送到各個應用;

2、可降級的多級讀服務:比如只讀本地緩存、只讀分布式緩存、或者只讀一個默認的降級數據;

3、開關前置化:如架構是nginx--->tomcat,可以將開關前置到nginx接入層,在nginx層做開關,請求不打到后端應用。

限流

目的是防止惡意流量,惡意攻擊,可以考慮如下思路:

1、惡意流量只訪問cache;

2、對于穿透到后端應用的可以考慮使用nginx的limit模塊處理;

3、對于惡意ip可以使用如nginx deny進行屏蔽。

大部分時候是不進行接入層限流的,而是限制流量穿透到后端薄弱的應用層。

切流量

對于一個大型應用,切流量是非常重要的,比如多機房有機房掛了、或者有機架掛了、或者有服務器掛了等都需要切流量,可以使用如下手段進行切換:

1、DNS:切換機房入口;

2、LVS/HaProxy:切換故障的nginx接入層;

3、Nginx:切換故障的應用層;

另外我們有些應用為了更方便切換,還可以在nginx接入層做切換,通過nginx進行一些流量切換,而沒有通過如LVS/HaProxy做切換。

其他

不需要cookie的應用使用無狀態域名,如3.cn;

接入層請求頭過濾,只轉發有用的請求頭到后端應用;

數據過濾邏輯前置,比如在接入層進行請求參數的合法性過濾;

內網設置合理的連接、讀、寫超時時間;

根據需要開啟gzip壓縮減少流量;

使用unix domain socket減少本機連接數;

內網考慮使用http長連接;

響應請求時,考慮響應頭加上服務器ip等信息,方便調試。

我們處理的讀服務大部分都是KV的,因此抗流量的思路就是大量緩存;而且怎么讓緩存怎么更接近用戶,離用戶越近速度就越快。再一個點就是要考慮好 降級方案,在異常情況下應用不被拖垮拖死。我們系統大量使用了如nginx+lua+redis技術,使用這些技術解決了我們很多讀服務問題。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!