美文网首页
物联网系统的通信后台

物联网系统的通信后台

作者: 看不见的BUG | 来源:发表于2019-08-14 21:53 被阅读0次

    工作的内容和物联网智能设备相关,当前公司的通信后台是很多年前开发的,结合了目前自己掌握的技术,设计了一套改善方案,主要关注通信、业务解耦、可拓展性方面,以备日后重构系统时当一个笔记。
    先说下简化后的业务场景,智能终端需要和总控后台做连接进行一些诸如上传数据、接受远程指令的任务,通过TCP+应用层协议进行数据交互。所以总体来说主要包含两部分逻辑模块,一个是负责和智能终端进行网络连接的通信部分,一个是将数据帧解析出来后处理各种业务逻辑的模块。


    终端与通信后台.png

    解析模块

    不同的智能终端会使用不同的协议,可以是国际标准协议也可以是自定义的协议。这类协议解析器一般都可以打成jar包使用,是无状态的。当然,如果系统需要监控解析过程中发生的错误,并且不是简单的将异常收集,而是对错误信息进行分析聚类,以更好的定位系统或者终端的程序问题,那么整个解析过程可能就需要在一个单独的模块中了。鉴于与本设计的关注点不太相关,所以先假定解析器以jar包工具栏的形式提供。

    连接模块

    NIO框架有选择比较新社区也比较活跃的Netty。这里有两个问题:

    1. 协议的解析是否需要在Netty中处理。
    2. 如何最大化NIO的非阻塞特性。
    3. 突然下线的模块处理。

    首先来看第一个问题。对数据帧的Codec可以在两个地方,一个是在Netty的ChannelPipeline中添加相应的编解码器,这样在数据流入、流出时会经过管道处理自动的编解码。这种方式比较适合编解码器是无状态的,处理方式上也比较直观。另外一种方式就是不把编解码器写成MessageToMessageCodec,而是通过消息队列由其他服务组件进行编解码,连接模块只负责收到帧后把帧push到Inbound消息队列,同时监听outbound消息队列有帧就找到相应的连接发出去。该种方法适合需要编解码过程比较复杂(需要查数据库档案、调用其他系统等)、需要对过程进行充分的监控分析的场景,其实也就是当编解码成为一个庞大的模块时,就需要将其分离出连接模块以达到模块解耦,同时方便针对不同模块性能的瓶颈调整集群实例。接下来的方案将以在Netty中编解码为基础。
    第二个问题也是关于模块解耦的问题。一般Netty Server端的实现会有一个BossGroup作为接受请求的池,一个WorkerGroup作为处理请求的池,当数据准备好时WorkerGroup将会在其中的一个线程中处理该数据,所以,WorkerGroup处理数据的速度直接影响到了Server的并发量。试想如果一个数据帧进来,例如是一个上报设备数据的帧,那么解码完成后再去查智能终端的档案,再通过rest调用其他服务做校验,然后做一些数据的汇总处理,最后将数据归档到MongoDB里。整个过程包含了大量了查库、跨系统调用等阻塞行为,Server的并发能力大打折扣。因此,系统需要割离出业务处理模块,解码完成后,直接把java对象发布到消息队列中,将连接上下文缓存到内存中(例如一个K-V Map,设备id作为key),就完成了WorkerGroup的逻辑。


    通信与业务模块.png

    第三个问题分布式问题,在通信模块的集群中,如果其中某个实例突然下线了,但发帧队列中还有等待发送的帧,这些帧该如何处理?首先先明确一个条件:任务的进行是需要内存中的上下文数据的,一旦内存数据丢失那么任务就应该终止(例如等待超时),也就是说这些帧到最后都应该会被丢弃。
    一种丢弃的方式就是设置消息的超时时间(根据任务的超时时间而定),消息超时后进入Dead Letter Queue,然后就可以消费Dead Letter Queue里的消息修改任务的状态。为了防止实例在消息超时之前上线回去消费消息,可以根据规则使用不同的队列(最简单的就是加时间戳),这个就看业务的需求了。这种等待超时的方法虽然简单,但是对任务状态的处理会有一定的时延。
    另一种方法就是依靠注册中心,但注册中心发现有通讯模块下线后,通知一个“善后”模块去做后续的处理,其中就包括消费帧并修改相关任务的状态。虽然多了一个业务系统,但整体系统也更可靠,能够主动去处理问题而不是被动的解决。

    业务模块

    不同的公司会有不同的业务模块,这里不展开讨论,主要针对一些典型的场景记录一些解决方案。

    串行化任务

    个人PC电脑拥有强大的核心,可以在同一时间内处理不同的业务,但智能终端的硬件条件可能比较苛刻,同一时间只能处理一个任务,所以Server必须控制任务的执行,必须等一个任务结束后(正常结束、异常结束、打断结束)再进行下一个任务的处理。
    一个实现的方式就是保证每个智能终端在业务模块中总是只有一个任务在进行,即在消费任务队列时,需要去检查该终端是否在执行任务,如果有则“不消费”该条任务。那么这里就涉及到两个问题,一个是如何知道该终端正在执行任务,另一个是如何知道该“不消费”任务。

    判断是否在执行任务

    判断终端是否在执行任务,考虑到集群不能在内存中简单的用Map解决。那么最容易想到的是在终端的sql表里添加标记字段,通过查询来判断,但很明显数据量大时sql数据库将无法支持。考虑到单个设备最多只能有一个正在执行的任务,和锁的概念很相似,在分布式环境下可以用zookeeper或者redis来实现。
    使用zookeeper实现的话,要注意当zk的结点数量过大(看了网上的经验值是10w+)时会导致zk卡顿,如果任务比较密集的话zk的结点数量近似于终端数量,实践中要对此进行压力测试。使用zk的好处是当业务服务器down机时,建立的临时结点会被删除也就是锁能立即释放,这样其他的业务服务器能立即获取锁。
    使用redis通过setnx实现的话,只要保证内存足够(用终端的id做key理论上服务器级别的内存都是够用的),为了防止实例down机导致锁无法释放需要添加过期时间(根据任务的超时时间而定)。

    “不消费”任务

    消息队列中的消息是主动投递出去的,而不是像数据库那样等待client端被动查询,如果想做到“不消费”某些任务,大概有两种方式:

    1. 在消费任务时使用过滤器进行消费。在这里这个方法显然不行,过滤条件是动态改变的并且过滤条件可能会很长导致性能问题。
    2. 设置延时后“退信”。消费了任务消息后,发现终端正在执行其他任务,遂增加延时时间后将消息插入到消息队列里,类似“退信”的效果。

    目前能想到的只有第二种方法可行,同时需要注意监控消息队列消息的数量,当有大量消息积压时,可能是任务生成得太快,或者通信发生故障无法执行任务,这时候应当对任务分发模块限流。


    串行化任务.png

    上述串行化任务的方式是在消费任务的时候做的处理,另外一个可行的途径就是在生成任务的时候,当设备有任务在执行的时候就阻塞不让生成任务,考虑到实际场景大部分还是将控制放在消费端比较合适。

    相关文章

      网友评论

          本文标题:物联网系统的通信后台

          本文链接:https://www.haomeiwen.com/subject/dntojctx.html