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

背景

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

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

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

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

分析

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

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

中间链路

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

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

(图片来源rocketmq.apache.org/zh/docs/4.x...

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

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

ini 复制代码
public ConsumeConcurrentlyStatus consumeMessage(
        List<MessageExt> 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;
}    

服务端

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

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

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

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

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

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

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

数据库端

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

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

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

业务端

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

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

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

开干

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

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

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

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

相关推荐
酷酷的鱼5 小时前
跨平台技术选型方案(2026年App实战版)
react native·架构·鸿蒙系统
晚风吹长发5 小时前
初步了解Linux中的动静态库及其制作和使用
linux·运维·服务器·数据结构·c++·后端·算法
梁下轻语的秋缘6 小时前
ESP32-WROOM-32E存储全解析:RAM/Flash/SD卡读写与速度对比
java·后端·spring
wanzhong23336 小时前
开发日记8-优化接口使其更规范
java·后端·springboot
The Open Group6 小时前
架构驱动未来:2026年数字化转型中的TOGAF®角色
架构
鸣弦artha7 小时前
Flutter 框架跨平台鸿蒙开发——Flutter引擎层架构概览
flutter·架构·harmonyos
羊小猪~~7 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
张彦峰ZYF8 小时前
商品供给域的工程化简要设计考量
后端·系统架构·商品模型·商品供给
这儿有一堆花8 小时前
CDN 工作原理:空间换取时间的网络架构
网络·架构·php
小北方城市网9 小时前
微服务注册中心与配置中心实战(Nacos 版):实现服务治理与配置统一
人工智能·后端·安全·职场和发展·wpf·restful