当数据库写入流量突增,阁下该如何应对?

背景

最近遇到了一个比较有意思的问题,这里简单探讨一下解决方案。

业务有一个系统,管理了非常多的IOT设备,设备每隔30S会通过HTTP方式汇报心跳到服务端,然后服务端更新数据库记录,用于后续的设备监控使用。

通常情况下,设备间的启动时间实际是有差异的。从时间维度上看,分布会比较均匀,落到数据库层面的流量分布也是相对均匀的。

但是某一天,设备依赖的第三方服务有变动,导致设备集体重启。重启后的设备,在同一时刻上报心跳的并发度增加,进而导致数据库压力剧增。

分析

从整理链路上我们分析一下,该如何应对。

当数据库写入流量突增,阁下该如何应对?_第1张图片

从问题的源头出发,心跳信息是在设备启动以后,每隔30S时间进行上报的。一个比较简单的方式,设备启动以后,随机增加一定的时间间隔(如0-30s),然后再上报心跳,从而概率性规避所有设备上报的频次一致。

中间链路

从链路上看,IOT设备直接上报心跳到服务端。这里可以考虑引入MQ,毕竟MQ的核心用途就是削峰填谷。

对于IOT设备,通常采用消息队列MQTT,然后服务端采用RocketMQ、Kafka、RabbitMQ即可。MQTT和服务端MQ之间的桥接,需要一些额外的工作,云厂商通常都有对应的产品。

当数据库写入流量突增,阁下该如何应对?_第2张图片
(图片来源https://rocketmq.apache.org/zh/docs/4.x/mqtt/01RocketMQMQTTOverview)

在引入了MQ以后,服务端可根据自己的实际处理能力进行消费即可。

当然,此方案需要考虑消息积压的问题。关于这个问题,一方面可以通过扩容服务端消费增速,一方面可以结合业务实际场景考虑丢弃已经过期的业务数据,比如这里的心跳数据,如果心跳已经是10分钟以前的数据库,明显可以不处理了。

  • Kafka 配置Kafka消息保留时间_kafka 日志保留时间_梦想画家的博客-CSDN博客
  • RabbitMQ消息存活时间的定义、应用场景、注意事项_云消息队列 RabbitMQ 版-阿里云帮助中心
  • RocketMQ 在消费时按照业务需要丢弃消息即可。
public ConsumeConcurrentlyStatus consumeMessage(
        List msgs,
        ConsumeConcurrentlyContext context) {
    long offset = msgs.get(0).getQueueOffset();
    String maxOffset =
            msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET);
    long diff = Long.parseLong(maxOffset) - offset;
    if (diff > 100000) {
        // TODO 消息堆积情况的特殊处理
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
    // TODO 正常消费过程
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}    

服务端

对于服务端来说,在此场景里面,服务端压力不算大。

所以,首先要考虑的是做好限流工作。

单机的限流可以采用令牌桶方式(https://mikechen.cc/20379.html),可以支持这种突增的流量。分布式场景下可以采用Sentinel(https://sentinelguard.io/zh-cn/)。

通常和限流一起出现的就是降级。这里由于是写数据库操作,那么当数据库写压力非常大的时候,我们可以考虑几种类型的操作。

第一种操作就是不操作(手动狗头),直接拒绝写操作,这通常用于处理部分不重要的数据。

第二种操作就是延迟操作,由于当前数据库压力大,那就等一段时间再处理这条消息。延迟处理的方式可以采用本地延迟队列DelayQueue,或者直接再写回MQ中,等待后续的二次处理。

第三种操作就是在数据库前面增加缓存,缓存的写操作速度和性能要高于数据库。

数据库端

对于数据库,一种简单直接的方式就是纵向扩容,配置上来了,处理能力和支持并发会有相应的提升。

第二种方案就是横向拆分,也就是存储的分库分表,通过将写压力分担到多个节点上,也可以做到支持更多的写操作。

第三种方式,可以考虑使用分布式NoSQL替换传统的关系型数据库,毕竟NoSQL基本都是sharding方式,性能比关系型数据库高很多。

业务端

上面的一些操作是通用的,当然也要结合实际业务场景。比如该场景下的心跳数据,它是偏实时的操作。如果处理的时候,时间已经过期了,或者有更新的数据到来,那可以直接丢弃旧的数据。

对于丢弃数据,可以在服务端处理的时候,按照事件发生的时间和实际处理的时间进行比对。也可以在服务端维护基于时间的优先级队列,优先处理更新的数据。

当然,如果引入了MQ,如果MQ支持消息过期时间设置,可以直接利用即可。

开干

好了,前面的都是马后炮了。

对于当时的系统状况,没有MQ,没有限流,端上没有改造,我们能做的只有求助DBA大大先帮忙扩容顶一下。待扩容以后,然后分区域分批次重启设备。

对于后续肯定是前面的多管齐下,MQ、限流、丢弃超时心跳数据通通安排上,我想这样应该可以应对了吧。

当数据库写入流量突增,阁下该如何应对?_第3张图片

如果还有其他建议,还请大佬们尽快提出,晚了就得等到下次case study了。

你可能感兴趣的:(数据库,java,架构)