首先,给大家讲一个关于小明的故事。
小明的一天
小明是一名应届生,从大一接触C语言后就励志要做一名凭借自己双手改变世界的程序员。经过4年的努力,他也如愿以偿地拿到了某个特别火热的UGC平台的研发offer。在经过短暂的实习后,他正式步入工作岗位。
小明哭了
有一天晚上,在小明正准备回家的时候,产品MM来找他说要做一个排行榜功能:“要在一个页面中展示发表评论最多的Top10用户”,还说是老板提的,明天就要上线。小明一听不敢怠慢,心想是时候展现自己的才华了,便立刻在工位上陷入了沉思。
评论表的定义是这样的:
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 '评论内容',
`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 '赞数',
PRIMARY KEY (`id`),
KEY `idx_news_id` (`news_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表'
第一个想法很快诞生,不就是要Top10吗,直接从db把结果查出来不就行了嘛。 group by
order by
一句sql搞定。同时作为一个在学校就做了很多项目的他,一下就想到了可以通过在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;
又然后,经过了短暂时间的啪啪啪啪,全部搞定!接下来怎么办?肯定是上线啊。 不行,还是要先在线下测一下�吧,怎么测呢?
首先,他很聪明的想到要先借助一个简单的存储过程在线下库mock一批数据
(PS:如果大家看不懂下面的存储过程,可以先忽略,你只要知道我们这一步是往我们的线下库插入了1000W数据即可。)
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, '评论内容', NOW());
set i=i+1;
end while;
end
然后,调用这个存储过程来为mock数据
call add_data(10000000)
伴随着一万匹草泥马路过,数据终于造好了(PS:过程比较慢,大家可以在自己的机器上跑一下)。
最后,到了最关键的一步了,调下接口看看成果吧~ 要下班啦要下班啦
只听回车键啪的一声,1s过去了... 2s过去了... 等了N秒之后结果才展示出来。此时此刻,小明的内心是崩溃的。
一定是网络原因,紧接着又是一声回车键,然后~
网络看不下去了,大喊道:“这锅我不背!”。小明不得不承认,这不是网络原因。
怎么办?赶紧查原因吧,先从最底层查起,看看查询sql用了多长时间。小明得到了下面的结果
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:想知道为什么这么慢?请关注我后面的文章哦,如果你已经知道了可以忽略)
小明笑了
哭是没用的,小明告诉自己要振作起来。马上又陷入了新一轮思考。
很快,小明又想到了第二种方案。
- 通过定时Task,每5s从db中查询出top10结果,放到固定的存储空间中。
- 用户请求top10结果时,可以直接从该存储空间中读取返回给用户。
但该方案有两个要解决的问题。
- 由于top10结果是定时Task每5s计算一次,也就意味在第N个任务执行后,第N+1个任务执行前的这段时间内,读取的结果都是基于“第N次计算时的数据集合”计算出来的。所以存在一定的误差。
- 由于我们的核心诉求就是降低响应时间,所以存储空间的读取性能一定要非常高。
首先第一个问题,在跟PM说明问题后,PM很爽快的就答应了。那么第二个问题,就要靠自己来解决了。
突然,小明灵光一现,想到了一个存储空间,那就是Redis!
前方高能!前方高能!
(PS:一篇讲Redis的文章,到现在才提到,也是没谁了,大家再忍耐一下继续看完)
- Redis是一个由C语言编写的开源的key-value数据库。
- Redis将所有的数据都保存到内存中。
- Redis性能极高,由于将数据保存在内存中,再加上内部独特的设计和实现,读QPS能达到11W,写QPS能到达8W。
当然,这些只是Redis的其中一部分特性,但已经完全可以满足我们的需求。
解决了存储问题,一条完整的解决方案出现在小明的脑海里。
- 异步任务计算Top10,计算结果放到Redis,key = authorIds,value = #{计算出来的结果},并且该任务5秒钟重新计算一次并更新value。
- 用户请求Top10展示,直接从Redis中读取key = authorIds 的值,返回给用户即可。
接下来,又伴随着一大波啪啪啪啪的键盘声,小明终于搞定了这个需求。
小明抬头一看,此时正好4点钟。他想起了自己偶像说过的一句话:“你见过凌晨4点钟的洛杉矶吗?”
“是的,那时候我刚下班”,小明情不自禁的说。
(PS:我真的是科密)
Ending
整个故事结束了。从这个故事中我们可以看到,当数据量达到一定的量级的时候,传统的关系型数据库就会暴露出一些问题,而这些问题必须通过其他的方式去解决,比如我们提到的Redis。
其实这篇文章关于Redis的知识很少,但我想任何一个刚接触Redis的人都不想一开始就学习他的语法,而是想知道Redis到底有什么用?有哪些场景可以用到?他能给我们带来什么?从上面小明的例子中,我想大家或多或少能对Redis有了一个初步的认识,如果你因此对Redis产生了兴趣,那就好好学下去吧~
接下来,我会根据这个故事中提到的关于Redis点,来跟大家进行深一步的探讨。
网友评论