一次关于数据缓存的优化和记录

问题背景描述:由于国庆期间公司停电维护,国庆后第一天出现服务中断的问题

问题排查与分析:通过公司luban的监控日志发现,找到一台mysql数据库服务器,8点多开始出现CPU占有率出现持续升高,9点左右CPU占有率将近100%,并且一直居高不下,数据服务异常,后端服务出现中断

  1. 通过APM查看API调用记录发现,在对应的时间段内有大量请求响应超过10秒,正常情况下这些API的请求响应时间应该都是毫秒级的;分析发现其中获取xxx列表的接口/xxx/list(并发量3800,平时并发量600左右)和/xxx/xxx/query(并发量3000,平时并发量180左右)并发量激增,相较平时的并发量存在明显的异常情况。推断原因问题可能是该接口的并发调用激增导致数据服务压力居高不下。
  2. 为了验证判断,在Nginx代理设置为将/xxx/list和/xxx/xxx/query两个接口直接返回,不进入后端业务逻辑,修改后CPU压力很快降下来了,服务也恢复了正常。
  3. 进一步查看访问日志,单/xxx/list接口在这个时间段范围内就有来自超过4k多个不同IP的请求,总请求次数40w+,并发量是平时平时并发量的N倍。进一步分析发现单个IP的请求次数基本几次几十次,少部分在100+次,排除了接口恶意调用的情况。国庆后第一个工作日,异常时间点正好是上班时间点,用户重启电脑,相关业务请求获取xxx列表接口。

  • 结论:基于上述分析,可以断定此次服务中断的原因是特定场景下形成短时对市场接口的大量请求导致数据库服务异常,从而导致后端服务异常。

 

解决措施分析:

通过分析,问题主要在数据库服务,而涉及接口的业务是根据当前的用户和其他条件从数据库中获取插件列表,特点是查询SQL执行效率不慢,但是传输数据量比较大,单个请求达到M级别。对于/xxx/list和/xxxxquery两个接口,影响查询结果主要是两个因素,一个是当前用户,一个是当前条件类型及版本。当前用户主要跟查xxx条件相关,这个数据量非常小,对于结果影响甚微;当前的实现是默认取出所有的数据列表,然后在业务代码中根据条件版本进行过滤。所以对查询结果影响比较大的查询因子就是筛选条件类型,但是该类型比较有限,很容易列举出来,这样就可以从这里来着手进行优化,减少数据库服务器的压力。

解决方案制定:

通过分析,瓶颈主要在数据库,解决措施主要是并发量大时减少数据库的压力,相同数据尽量从缓存中获取。

在代码方面,可以根据筛选类型对应插件列表来创建缓存,请求时优先根据该类型从缓存中获取数据,按照每一次请求N次数据库访问量来算,同等并发量的情况至少会减少25%左右的数据库访问次数和99%的数据传输,大大减少数据库压力,从而提升查询插件列表接口的吞吐量。

主要代码:主要信息打码处理

业务service

Set<XXXX> xxxxSet = null;
        if (queryConditions == null && queryValue == null) {
            xxxxSet = redisService.getXxxxSet Set();
            if (CollectionUtils.isEmpty(xxxxSet)) {
                xxxxSet = mapper.queryInfo(xxValue, null, null);
                redisService.setXxxxSet(xxxxSet);
            }
        } else {
            xxxxSet = mapper.queryInfo(xxValue, queryConditions, queryValue);
        }

redis service

public void setXxxxSet(Set<Xxxx> xxxSet) {
        redisTemplate.opsForValue().set(KEY, JSON.toJSONString(xxxSet));
        redisTemplate.expire(KEY, TASK_STATUS_DURATION_SECOND, TimeUnit.SECONDS);
}

public Set<Xxxx> getXxxxSet() {
        return Optional.ofNullable(redisTemplate.opsForValue().get(KEY)).map(tempString -> 
        {
            List<Xxxx> xxxList =
                    JSON.parseArray(tempString, Xxxx.class);
            Set<Xxxx> xxxSet = new HashSet<>(xxxList);
            return xxxSet ;
        }).orElse(null);
}

数据库方面,采取对于数据库的一方面优化,索引、优化查询、主从复制、读写分离、分库分表等手段进行优化,涉及到公司机密,不展示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值