IOT集群扩容实践:问题剖析与解决策略
概述
随着业务的蓬勃发展,设备规模不断攀升,我们的智能物联网(IOT)集群也开启了新一轮的扩容之旅。在这次扩容过程中,我们遇到了诸多挑战,接下来就让我们一起深入探究这些问题的出现原因、排查思路以及最终形成的完善解决方案。
背景
上图展示了当前的系统架构。业务集群会与IOT集群的每个节点建立多个长连接,并根据既定规则向其中一个节点发送指令。如果选中的节点未持有目标设备的长连接,则该节点会将消息转发至IOT集群中的其他节点。
上线更新要点
- 新增IOT节点:新增的IOT节点需正常构建并启动,并在外部负载均衡器(SLB)中增加相应的配置。
- 更新旧IOT节点:修改IOT集群节点列表,加入新节点的内网地址,随后依次进行构建和发布。
- 更新业务服务节点:修改本地IOT集群节点配置后进行构建发布,并刷新本地长连接,以供业务使用。
上述发布方式存在一定的窗口期问题,虽然目前业务体量尚可接受,但考虑到未来的发展,最终还是需要改造为动态扩缩容的方式。
消息偏移阻塞现象
经过前期的准备,运维团队在凌晨6点开始按照既定顺序依次进行操作。然而,不妙的情况悄然发生。
如图所示,在完成三台旧IOT节点的构建和发布后,大量设备与IOT集群的长连接开始断开,并尝试重新连接。在此过程中,Kafka的生产消息量飙升至接近1000 tps的峰值。虽然监控显示消费端能够跟上生产速率,但问题逐渐浮现。
以其中一台机器为例,在重新发布旧IOT节点后,负责消费的业务集群(2个节点,配置为8核32G)的CPU和内存利用率均达到了告警阈值。
通过查看业务集群的Kafka消费任务警告日志,发现两个节点本地缓存了大量未处理的消息任务。这是由于采用了无界队列,并且持续拉取消息,导致消息大量堆积在本地进程中。
经过后续计算,单台设备掉线重连后至少会产生4~6条消息,这还不包括因IOT集群阻塞导致的失败重连。当时两个节点共处理了一百多万条消息,而实际消费能力为:单机10线程能达到约370 tps。
实际消费能力:单机10线程能达到370tps左右
原因推断:进一步分析业务服务的拉取消息方式:采用循环拉取,每次拉取的最大等待时间为100ms,若拉取为空则暂停100ms后继续。拉取到的消息会写入线程池(无界队列)。这种机制导致大量消息被缓存在本地,从而解释了为什么服务节点本地会积压如此多的消息。
消息顺序问题
在完成现场的消息阻塞排查后,我得出一个结论:目前无法恢复,重新部署也来不及。消息已经拉取到本地,重启会导致消息丢失。因此,我们只能继续观察,等待消息慢慢处理完毕(消息发送端的流量已经降低,仅需处理存量消息)。然而,在这个过程中,另一个问题悄然浮现。
现象:旧节点完成构建和发布后,设备连接逐渐恢复,但从我们的业务平台上看,在线设备总量却一直在下降。目前仅剩约五万台在线设备,这与外网SLB入口的连接数相差甚远。 问题推测:一个不妙的念头浮出水面:消息顺序性可能出现了问题。在这种情况下,设备断开连接并重新连接时,会快速发出两条消息。然而,我们将这些消息全部抛入工作线程池中,无法保证并发时同一台设备的消息会被顺序处理。如果“上线”事件在“离线”事件之前被执行,就会出现设备实际上已经连接到IOT集群,但业务系统仍显示为离线状态的情况。问题修复
1. 消息顺序性问题在正常业务场景中,这种情况很少出现,因为同一设备的消息通常存在一定的时间间隔。然而,针对此类特殊场景,尽管业务层面已有兜底处理,我们仍需进行修复以确保系统的稳定性和可靠性。
解决方案:a. 创建多个队列(Queue)。消费主线程拉取到消息后,根据设备编号对队列数量(QueueSize)取模,计算出对应的队列编号,并将消息发送到该队列中。每个队列都对应一个线程池进行消费(每个线程池仅包含一个线程)。通过这种方式,可以确保消息的顺序消费。
补充说明:b. 生产端会根据设备编号对Kafka主题的分区数量进行取模,从而始终将消息发送到同一个分区中。这与消费端的队列分配机制相匹配,进一步保证了消息的顺序性。2. Kafka消费集群数据偏移问题由于我们采用的消费模式是先将消息拉取到本地再进行消费,因此在消费节点重新构建和发布时,会出现重平衡现象。此时,另一个节点会接管所有主题分区的消费权限,并将大量消息拉取到本地。在日常发版且消费压力较小时(我们日常的消息生产tps远低于消费能力上限),这种现象并不会引发问题。然而,在上述异常场景下,这种模式就显得捉襟见肘,漏洞百出。3. Kafka消息消费阻塞问题
如上所述,该问题的根本原因是消费能力不足。我们从以下三个方面进行分析和处理:a. 增加Work线程池数量针对第一点进行补充优化,通过增加工作线程池的数量,提升消息处理的并发能力。b. 增加业务消费集群的节点数扩大业务消费集群的规模,增加节点数量,从而分散消费压力,提升整体消费效率。c. 优化消费业务的执行效率对消费业务的逻辑进行优化,减少不必要的处理步骤,提升单个消费任务的执行效率。d. 物理层面区分核心业务消息从物理层面出发,对设备消息进行优先级划分(例如分为A、B、C三个优先级)。核心交易相关的消息将被发送至A_topic,其他消息则根据优先级分别发送至B_topic和C_topic。在消费端,通过不同线程分别拉取并消费这些消息,确保在各类场景下,核心业务的正常运行不受影响,进一步保障了系统的稳定性和可靠性。4. 扩容流程问题
在尚未实现自动扩缩容的情况下,优化IOT集群的扩容流程如下:a. 构建并启动新IOT节点首先,完成新IOT节点的构建并启动,确保其正常运行。b. 更新业务集群中的IOT节点列表在业务集群中,仅将IOT集群节点列表更新为新IOT节点的IP地址,随后进行构建和重启。c. 调整外网SLB权重如果计划优先构建1号节点,则在外网SLB中将另外两个旧IOT节点(此时为2号和3号节点)的权重下调至10(初始权重均为100)。d. 构建并重启1号节点构建并重启1号节点。此时,大部分连接数据将路由到1号、4号和5号节点,少量连接仍可能路由到2号和3号节点。e. 观察节点重启后的状态等待1号节点重启完成,密切观察外网SLB的连接数、业务集群的消费情况以及业务层设备的在线数量。如果一切正常,无异常情况,则按照步骤c依次对2号和3号节点进行操作。f. 更新业务集群节点列表并重启将业务集群中的IOT集群节点列表更新为扩容后的所有节点IP地址,然后进行构建和重启。g. 完成扩容流程至此,扩容流程结束。
结语
尽管在IOT集群扩容过程中遇到了一些问题,但通过一系列分析与解决措施,我们最终顺利完成了任务,并在一定程度上提升了系统的稳定性和可靠性。这一过程让我们深刻认识到问题的复杂性,也让我们更加清楚地看到了自身的不足。未来,我们将继续总结经验教训,优化系统架构,以更好地应对更复杂的业务场景,为业务的持续发展提供更加坚实的技术保障。