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

背景

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

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

相关推荐
xjz18422 小时前
Rust 注释:从 // 到 rustdoc
后端
Eyfcom3 小时前
〖物流低代码实战 01〗物流页面可视化搭建的底层架构:协议、引擎与产物的三重奏
低代码·架构·快递系统·物料体系·物流低代码实战
syyjhchou4 小时前
自己搭企业内部中转站,怎么让请求更容易命中缓存
人工智能·后端·语言模型·aigc
特立独行的猫A4 小时前
Ionic + 仓颉语言的 cj-tauri 开发框架实战:开发精美移动端 APP
java·后端·python
SomeOtherTime5 小时前
Rust集合(AI回答)
开发语言·后端·rust
我不会起名字3225 小时前
req.Seqs[len-1]:空切片防御缺口与 RPC 二次校验
后端·mongodb·go·即时通讯·im·故障排查
脚本自动化开发6 小时前
京东PLUS开卡红包刷50元的方法
后端
程序员Query6 小时前
day01-认识Go语言 + golang环境的安装
开发语言·后端·golang
浪遏11 小时前
recruitment-cli:让 AI 接手求职招聘,从架构到一次发送超时
架构·typescript·node.js
码事漫谈14 小时前
黄仁勋被裁第一天,就收到了橄榄枝
后端