首先,給大家講一個關(guān)于小明的故事。

小明的一天
小明是一名應(yīng)屆生,從大一接觸C語言后就勵志要做一名憑借自己雙手改變世界的程序員。經(jīng)過4年的努力,他也如愿以償?shù)啬玫搅四硞€特別火熱的UGC平臺的研發(fā)offer。在經(jīng)過短暫的實習(xí)后,他正式步入工作崗位。
小明哭了
有一天晚上,在小明正準(zhǔn)備回家的時候,產(chǎn)品MM來找他說要做一個排行榜功能:“要在一個頁面中展示發(fā)表評論最多的Top10用戶”,還說是老板提的,明天就要上線。小明一聽不敢怠慢,心想是時候展現(xiàn)自己的才華了,便立刻在工位上陷入了沉思。
評論表的定義是這樣的:
CREATE TABLE `news_comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`author_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '作者Id',
`news_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '新聞Id',
`content` text NOT NULL COMMENT '評論內(nèi)容',
`created` datetime NOT NULL,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`up_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '贊數(shù)',
PRIMARY KEY (`id`),
KEY `idx_news_id` (`news_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='評論表'
第一個想法很快誕生,不就是要Top10嗎,直接從db把結(jié)果查出來不就行了嘛。 group by order by一句sql搞定。同時作為一個在學(xué)校就做了很多項目的他,一下就想到了可以通過在author_id上加個索引以提升查詢效率。
首先,建索引:
alter table news_comment add index `idx_author_id` (`author_id`);
接著,寫出查詢sql:
select author_id, count(*) as comment_count from news_comment group by author_id order by comment_count desc limit 10;
又然后,經(jīng)過了短暫時間的啪啪啪啪,全部搞定!接下來怎么辦?肯定是上線啊。 不行,還是要先在線下測一下?吧,怎么測呢?
首先,他很聰明的想到要先借助一個簡單的存儲過程在線下庫mock一批數(shù)據(jù)
(PS:如果大家看不懂下面的存儲過程,可以先忽略,你只要知道我們這一步是往我們的線下庫插入了1000W數(shù)據(jù)即可。)
create procedure add_data(num int)
begin
declare i int;
declare news_id int;
declare author_id int;
set i=0;
while i<num do
SET news_id = MOD(i, 10000) + 1;
SET author_id = MOD(i, 100000) + 1;
insert into news_comment(author_id, news_id, content, created) values(author_id, news_id, '評論內(nèi)容', NOW());
set i=i+1;
end while;
end
然后,調(diào)用這個存儲過程來為mock數(shù)據(jù)
call add_data(10000000)
伴隨著一萬匹草泥馬路過,數(shù)據(jù)終于造好了(PS:過程比較慢,大家可以在自己的機器上跑一下)。
最后,到了最關(guān)鍵的一步了,調(diào)下接口看看成果吧~ 要下班啦要下班啦

只聽回車鍵啪的一聲,1s過去了... 2s過去了... 等了N秒之后結(jié)果才展示出來。此時此刻,小明的內(nèi)心是崩潰的。

一定是網(wǎng)絡(luò)原因,緊接著又是一聲回車鍵,然后~

網(wǎng)絡(luò)看不下去了,大喊道:“這鍋我不背!”。小明不得不承認(rèn),這不是網(wǎng)絡(luò)原因。
怎么辦?趕緊查原因吧,先從最底層查起,看看查詢sql用了多長時間。小明得到了下面的結(jié)果
mysql> select author_id, count(*) as comment_count from news_comment group by author_id order by comment_count limit 10;
+-----------+---------------+
| author_id | comment_count |
+-----------+---------------+
| 59206 | 100 |
| 63302 | 100 |
| 40004 | 100 |
| 44100 | 100 |
| 63975 | 100 |
| 68071 | 100 |
| 72072 | 100 |
| 76168 | 100 |
| 88106 | 100 |
| 92202 | 100 |
+-----------+---------------+
10 rows in set (2.09 sec)

水落石出!怪不得請求這么慢,光花在db的查詢時間就這么久,得趕緊想辦法解決了。
這時,小明落下了委屈的淚水:”我想回家~~~“
(PS:想知道為什么這么慢?請關(guān)注我后面的文章哦,如果你已經(jīng)知道了可以忽略)
小明笑了
哭是沒用的,小明告訴自己要振作起來。馬上又陷入了新一輪思考。
很快,小明又想到了第二種方案。
- 通過定時Task,每5s從db中查詢出top10結(jié)果,放到固定的存儲空間中。
- 用戶請求top10結(jié)果時,可以直接從該存儲空間中讀取返回給用戶。
但該方案有兩個要解決的問題。
- 由于top10結(jié)果是定時Task每5s計算一次,也就意味在第N個任務(wù)執(zhí)行后,第N+1個任務(wù)執(zhí)行前的這段時間內(nèi),讀取的結(jié)果都是基于“第N次計算時的數(shù)據(jù)集合”計算出來的。所以存在一定的誤差。
- 由于我們的核心訴求就是降低響應(yīng)時間,所以存儲空間的讀取性能一定要非常高。
首先第一個問題,在跟PM說明問題后,PM很爽快的就答應(yīng)了。那么第二個問題,就要靠自己來解決了。
突然,小明靈光一現(xiàn),想到了一個存儲空間,那就是Redis!
前方高能!前方高能!
(PS:一篇講Redis的文章,到現(xiàn)在才提到,也是沒誰了,大家再忍耐一下繼續(xù)看完)
- Redis是一個由C語言編寫的開源的key-value數(shù)據(jù)庫。
- Redis將所有的數(shù)據(jù)都保存到內(nèi)存中。
- Redis性能極高,由于將數(shù)據(jù)保存在內(nèi)存中,再加上內(nèi)部獨特的設(shè)計和實現(xiàn),讀QPS能達(dá)到11W,寫QPS能到達(dá)8W。
當(dāng)然,這些只是Redis的其中一部分特性,但已經(jīng)完全可以滿足我們的需求。
解決了存儲問題,一條完整的解決方案出現(xiàn)在小明的腦海里。
- 異步任務(wù)計算Top10,計算結(jié)果放到Redis,key = authorIds,value = #{計算出來的結(jié)果},并且該任務(wù)5秒鐘重新計算一次并更新value。
- 用戶請求Top10展示,直接從Redis中讀取key = authorIds 的值,返回給用戶即可。
接下來,又伴隨著一大波啪啪啪啪的鍵盤聲,小明終于搞定了這個需求。
小明抬頭一看,此時正好4點鐘。他想起了自己偶像說過的一句話:“你見過凌晨4點鐘的洛杉磯嗎?”
“是的,那時候我剛下班”,小明情不自禁的說。
(PS:我真的是科密)
Ending
整個故事結(jié)束了。從這個故事中我們可以看到,當(dāng)數(shù)據(jù)量達(dá)到一定的量級的時候,傳統(tǒng)的關(guān)系型數(shù)據(jù)庫就會暴露出一些問題,而這些問題必須通過其他的方式去解決,比如我們提到的Redis。
其實這篇文章關(guān)于Redis的知識很少,但我想任何一個剛接觸Redis的人都不想一開始就學(xué)習(xí)他的語法,而是想知道Redis到底有什么用?有哪些場景可以用到?他能給我們帶來什么?從上面小明的例子中,我想大家或多或少能對Redis有了一個初步的認(rèn)識,如果你因此對Redis產(chǎn)生了興趣,那就好好學(xué)下去吧~
接下來,我會根據(jù)這個故事中提到的關(guān)于Redis點,來跟大家進(jìn)行深一步的探討。