記一次資料量上億的後臺服務的效能優化

特務依昂 發表於 2021-07-18

背景

  最近被分配到的一個需求,資料量每週新增上千萬,預計兩個月就會破億,這裡記錄一下對這個服務的效能優化的過程。


正文

需求介紹

  首先大致介紹一下這個需求的內容。這個需求是一個週報服務,每週日向使用者推送他本週使用服務的時常,最晚使用時間等統計資料,這應該是很多應用都有實現的功能。而對於後臺服務來說,只需要提供一個介面,它實現的功能就是去查詢使用者的週報資料。但是這個服務的使用者量龐大,有數千萬,而被篩選後,需要統計週報資訊的使用者,大致有1000w左右。這也就是說,每週將會新增1000w條資料,兩個月左右,總資料就會破億。


問題說明

  這個服務資料儲存比較簡單,就只有一張DB表,儲存每一個使用者的週報資料,但是因為資料量龐大,所以如果只是簡單的接收請求後,直接去查詢DB,那服務效能就太低了,而且也扛不住多少請求量,隨著資料量的增大,這個問題也會越來越嚴重。所以這裡我們需要對服務進行優化。


優化方式一:建立索引

  由於資料量巨大,查詢DB的時候,如果需要全表掃描,那查詢速度將非常緩慢,首先可以想到的就是建立索引。查詢條件有兩個,一個就是使用者的id,一個就是週報的時間(那週週一的日期,表示需要查詢使用者第幾周的週報),所以我們可以直接使用這兩個欄位建立一個聯合索引。使用1000w左右的資料進行測試,未新增索引前,查詢速度在10秒以上,而新增索引後,單次查詢速度降低到了10毫秒以下。


優化方式二:分表

  由於這個服務每週會增加千萬條資料,所以使用一張表進行儲存,那隻會使得查詢的速度越來越低,單張表的索引也會越來越大,所以此時肯定是需要考慮分表的。那如果來進行分表呢,對於這個服務來說就簡單了。這是一個週報服務,DB表每週只會新增一次資料,那就是在每週的週日,會將使用者這周的週報資料匯入,所以我們自然而然的就可以想到,按周進行分表。將每週的資料,單獨儲存在一張表中,表名加上這周的時間,查詢的時候,找到對應時間的表進行查詢即可。比如,2021年7月5號到7月11號這周的資料,我們可以放到t_weekly_info_20210705這張表中,表的字尾就是這週週一的日期。這樣一來,我們就控制住了每張表的資料量,同時每次查詢也只需要在一週的資料中進行查詢,提升了查詢的效率。使用者在請求週報時,會帶上一個時間引數,以此來表明需要哪一週的資料,而根據這個引數,我們即可拼出對應的表名。

  除此之外,使用了分表之後,我們也可以對索引進行優化,之前的索引,由使用者id和週報時間組成,但是由於使用了分表,同一張表中所有的資料,週報時間都是相同的,所以索引可以不需要週報時間這個欄位,只留一個使用者id即可。


優化方式三:新增快取

  這麼大資料量的服務,快取必不可少。這裡我使用Redis實現快取,在接收到使用者的請求後,我們先去中Redis中查詢使用者的週報,如果查詢成功,則直接返回;如何查詢失敗,則再去對應的DB表中查詢,再更新到Redis中。除此之外,查詢DB時,根據查詢失敗的原因不同,處理方式也有所區別,如果是因為DB中沒有這個使用者的資料,導致查詢失敗,那我們也需要將空資料快取到Redis,因為即使他下次再查,也是不會有資料的;但是如果查詢失敗的原因是網路等原因導致查詢異常,那此時我們就不需要快取了,因為下一次查詢,是有可能成功的。而且由於週報記錄的是一週的資料,使用者一般查詢的也是本週的週報,所以我們的快取時間可以長一些,比如一天。

  但是這個方式並不保險,很有可能出現快取擊穿的問題,當我們還沒有更新某個使用者的快取,或者這個使用者的快取失效後,突然有大量的請求進來,請求這個使用者的資料,由於是併發的請求,此時也沒有快取,所以都打到了DB上,給DB造成巨大的壓力,從而查詢效率降低,請求超時,使用者的快取將無法得到更新,最終甚至可能導致DB被打掛。而為了防止這個問題的發生,我們就需要用到另一種快取方式了:資料預熱。


優化方式四:資料預熱

  什麼是資料預熱呢,其實就是“非同步更新快取”。我們提前將DB中所有的資料都載入到Redis中,然後有請求過來,直接去Redis中查,然後每隔一段時間,使用一個非同步的執行緒去更新快取,也就是讓快取永不過期。但是這裡無法真正的做到快取永不過期,因為資料量巨大,且每週都在增長,所以快取所有的資料並不現實,而且也沒有必要。對於週報,一般來說,使用者只會檢視上週的週報,而對於幾周前的週報,會檢視的頻率就比較低了,所以我們快取最近一到兩週的資料,基本上就可以涵蓋大部分的請求了。假設以兩週的資料來算的話,大概需要多大的Redis空間內?一條資料大70B左右,以一週1000w條資料來算的話,快取一週的資料,大概需要不到700MB,所以我們使用一個4G的Redis,也足夠快取兩週的資料了。


總結

  以上使用了四種方式對服務的效能進行了優化,其實都是比較簡單的技巧,但是卻非常的有效。這個服務的主要瓶頸是在DB,所以優化的主要思路就是儘量少查詢DB,已經查詢DB時查詢更少的資料。還有一些優化的方式上面沒有提到,比如調整資料庫連線池的連線數量,但是這裡一般框架都封裝的比較好了,再加上我對這裡的調整標準也不是很瞭解,就不細說了。