背景
最近被分配到的一個需求,數據量每周新增上千萬,預計兩個月就會破億,這里記錄一下對這個服務的性能優化的過程。
正文
需求介紹
首先大致介紹一下這個需求的內容。這個需求是一個周報服務,每周日向用戶推送他本周使用服務的時常,最晚使用時間等統計數據,這應該是很多應用都有實現的功能。而對於后台服務來說,只需要提供一個接口,它實現的功能就是去查詢用戶的周報數據。但是這個服務的用戶量龐大,有數千萬,而被篩選后,需要統計周報信息的用戶,大致有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時查詢更少的數據。本人剛工作不久的小白,技術水平有限,了解的知識不多,所以各位如果還有其他優化的方式,或者相關的建議,歡迎評論指導!