9

记一次数据量上亿的后台服务的性能优化

 3 years ago
source link: https://www.cnblogs.com/tuyang1129/p/15028363.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

  最近被分配到的一个需求,数据量每周新增上千万,预计两个月就会破亿,这里记录一下对这个服务的性能优化的过程。

  首先大致介绍一下这个需求的内容。这个需求是一个周报服务,每周日向用户推送他本周使用服务的时常,最晚使用时间等统计数据,这应该是很多应用都有实现的功能。而对于后台服务来说,只需要提供一个接口,它实现的功能就是去查询用户的周报数据。但是这个服务的用户量庞大,有数千万,而被筛选后,需要统计周报信息的用户,大致有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时查询更少的数据。还有一些优化的方式上面没有提到,比如调整数据库连接池的连接数量,但是这里一般框架都封装的比较好了,再加上我对这里的调整标准也不是很了解,就不细说了。

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK