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

背景

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

业务有一个系统,管理了非常多的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了。

相关推荐
维克兜率天2 小时前
【维克】ARMA模型:AR与MA的完美结合
后端·restful
JMchen1232 小时前
Jetpack 内核实战(八):组件整合——DataStore + Room 构建离线优先架构
架构·room·datastore·组件整合·android 开发架构·jetpack 实战
是未才3 小时前
从输入 URL 到页面返回:DNS、路由、TLS 与 HTTP 完整链路
java·后端·计算机网络
ltl3 小时前
AI 原生架构:LLM 时代的系统重构
架构
ltl3 小时前
边缘计算架构:算力下沉的设计挑战
架构
上海安当技术3 小时前
半天接入:USBKey RESTful API + C 动态库,Web 和 C/S 两套集成路径实战
前端·后端·restful·集成·usbkey
JouYY4 小时前
大模型底层学习(三)-从零训练一个 BPE 分词器
架构·llm·agent
DevUI团队4 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端
Dawson Zhu4 小时前
工业世界模型——基于AI Agent+数学仿真架构
人工智能·架构·agi
Wang's Blog4 小时前
AI Agent白手起家52: 从零搭建钉钉智能助手——资源准备与核心架构实现
人工智能·架构·钉钉