• 记一次大数据量后台服务的性能优化


    背景

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


    正文

    需求介绍

      首先大致介绍一下这个需求的内容。这个需求是一个周报服务,每周日向用户推送他本周使用服务的时常,最晚使用时间等统计数据,这应该是很多应用都有实现的功能。而对于后台服务来说,只需要提供一个接口,它实现的功能就是去查询用户的周报数据。但是这个服务的用户量庞大,有数千万,而被筛选后,需要统计周报信息的用户,大致有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时查询更少的数据。本人刚工作不久的小白,技术水平有限,了解的知识不多,所以各位如果还有其他优化的方式,或者相关的建议,欢迎评论指导!

  • 相关阅读:
    pow()函数结果强制转化为int造成误差的分析
    warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    博客园鼠标点击特效代码
    codeblocks更改颜色主题
    codeblocks1712设置中文
    SQl
    项目中nodejs包高效升级插件npm-check-updates
    正则表达式的整理(将金钱数变成带有千分位)
    从一个数组中过滤出另外一个数组中相关字段相等的数据
    IONIC3 打包安卓apk详细过程(大量图文)
  • 原文地址:https://www.cnblogs.com/tuyang1129/p/15028363.html
Copyright © 2020-2023  润新知