每秒30W次的點贊業(yè)務(wù),怎么優(yōu)化?
繼續(xù)答星球水友提問,30WQPS的點贊計數(shù)業(yè)務(wù),如何設(shè)計?
可以看到,這個業(yè)務(wù)的特點是:
- 吞吐量超高;
- 能夠接受一定數(shù)據(jù)不一致;
畫外音:計數(shù)有微小不準(zhǔn)確,不是大問題。
先用最樸素的思想,只考慮點贊計數(shù),可以怎么做?有幾點是最容易想到的:
- 肯定不能用數(shù)據(jù)庫抗實時讀寫流量;
- redis天然支持固化,可以用高可用redis集群來做固化存儲;
- 也可以用MySQL來做固化存儲,redis做緩存,讀寫操作都落緩存,異步線程定期刷DB;
- 架一層計數(shù)服務(wù),將計數(shù)與業(yè)務(wù)邏輯解耦;
此時MySQL核心數(shù)據(jù)結(jié)構(gòu)是:
- t_count(msg_id, praise_count)
此時redis的KV設(shè)計也不難:
- key:msg_id
- value:praise_count
似乎很容易就搞定了:
- 服務(wù)可以水平擴展;
- 數(shù)據(jù)量增加時,數(shù)據(jù)庫可以水平擴展;
- 讀寫量增加時,緩存也可以水平擴展;
計數(shù)系統(tǒng)的難點,還在于業(yè)務(wù)擴展性問題,以及效率問題。
以微博為例:
- 用戶微博首頁,有多條消息list
,這是一種擴展; 同一條消息msg_id,不止有點贊計數(shù),還有閱讀計數(shù),轉(zhuǎn)發(fā)計數(shù),評論計數(shù),這也是一種擴展;
- // (1)獲取首頁所有消息msg_id
- list<msg_id> = getHomePageMsg(uid);
- // (2)對于首頁的所有消息要拉取多個計數(shù)
- for( msg_id in list<msg_id>){
- //(3.1)獲取閱讀計數(shù)
- getReadCount(msg_id);
- //(3.2)獲取轉(zhuǎn)發(fā)計數(shù)
- getForwordCount(msg_id);
- //(3.3)獲取評論計數(shù)
- getCommentCount(msg_id);
- //(3.4)獲取贊計數(shù)
- getPraiseCount(msg_id);
- }
由于同一個msg_id多了幾種業(yè)務(wù)計數(shù),redis的key需要帶上業(yè)務(wù)flag,升級為:
- msg_id:read
- msg_id:forword
- msg_id:comment
- msg_id:praise
用來區(qū)分共一個msg_id的四種不同業(yè)務(wù)計數(shù),redis不能支持key的模糊操作,必須訪問四次reids。
假設(shè)首頁有100條消息,這個方案總結(jié)為:
- for循環(huán)每一條消息,100條消息100次;
- 每條消息4次RPC獲取計數(shù)接口調(diào)用;
- 每次調(diào)用服務(wù)要訪問reids,拼裝key獲取count;
畫外音:這種方案的擴展性和效率是非常低的。
那如何進(jìn)行優(yōu)化呢?
首先看下數(shù)據(jù)庫層面元數(shù)據(jù)擴展,常見的擴展方式是,增加列,記錄更多的業(yè)務(wù)計數(shù)。
如上圖所示,由一列點贊計數(shù),擴充為四列閱讀、轉(zhuǎn)發(fā)、評論、點贊計數(shù)。
增加列這種業(yè)務(wù)計數(shù)擴展方式的缺點是:每次要擴充業(yè)務(wù)計數(shù)時,總是需要修改表結(jié)構(gòu),增加列,很煩。
有沒有不需要變更表結(jié)構(gòu)的擴展方式呢?
行擴展是一種擴展性更好的方式。
表結(jié)構(gòu)固化為:
- t_count(msg_id, count_key, count_value)
當(dāng)要擴充業(yè)務(wù)計數(shù)時,增加一行就行,不需要修改表結(jié)構(gòu)。
畫外音:很多配置業(yè)務(wù),會使用這種方案,方便增加配置。
增加行這種業(yè)務(wù)計數(shù)擴展方式的缺點是:表數(shù)據(jù)行數(shù)會增加,但這不是主要矛盾,數(shù)據(jù)庫水平擴展能很輕松解決數(shù)據(jù)量大的問題。
接下來看下redis批量獲取計數(shù)的優(yōu)化方案。
原始方案,通過拼裝key來區(qū)分同一個msg_id的不同業(yè)務(wù)計數(shù)。
可以升級為,同一個value來存儲多個計數(shù)。
如上圖所示,同一個msg_id的四個計數(shù),存儲在一個value里,從而避免多次redis訪問。
畫外音:通過value來擴展,是不是很巧妙?
總結(jié)
計數(shù)業(yè)務(wù),在數(shù)據(jù)量大,并發(fā)量大的時候,要考慮的一些技術(shù)點:
- 用緩存抗讀寫;
- 服務(wù)化,計數(shù)系統(tǒng)與業(yè)務(wù)系統(tǒng)解耦;
- 水平切分?jǐn)U展吞吐量、數(shù)據(jù)量、讀寫量;
- 要考慮擴展性,數(shù)據(jù)庫層面常見的優(yōu)化有:列擴展,行擴展兩種方式;
- 要考慮批量操作,緩存層面常見的優(yōu)化有:一個value存儲多個業(yè)務(wù)計數(shù);
計數(shù)系統(tǒng)優(yōu)化先聊到這里,希望大家有收獲。
【本文為51CTO專欄作者“58沈劍”原創(chuàng)稿件,轉(zhuǎn)載請聯(lián)系原作者】