本文讲解4.6版jxTMS中数据采集系统的高可用性,整个系列的文章请查看:4.6版升级内容
docker版本的使用,请查看:docker版jxTMS使用指南
4.0版jxTMS的说明,请查看:4.0版升级内容
4.2版jxTMS的说明,请查看:4.2版升级内容
4.4版jxTMS的说明,请查看:4.4版升级内容
standalone构型转为了分布式构型,就自然而然的会出现一个高可用性的问题。传统上,高可用性有三种,即所谓的负载均衡、双机热备、双机冷备。
但这三者的概念以今天的技术水平来看都很过时了,所以笔者作了自己的修正:分布式系统、在线热备、在线冷备。
三者的共性都是通过增加冗余的备份机以在某台正在提供服务的服务器失效后能自动的由备份机接管服务,最大化的减少服务中断时间、尽可能的提高服务的可用性。
三者的区别是:
1、分布式系统,所有机器同时工作,无分主次
优点是所有服务器同时提供服务,浪费率低,抗冲击性好,服务宕机时影响面小,理论上的故障间隔最小。
缺点是系统需要专门的调度管理将服务请求较平均的分散到所有服务器上,这就需要复杂的服务跟踪、管理与调度,建设成本会高很多。此外,提供的最好是无状态服务,这样就不需要考虑服务交接时的复杂性来。
但这样的要求对业务的限制太多、效率太低;而如果提供有状态的服务,那就需要采取多种技术手段来确保状态的维持,如基于状态/连接的服务分发、采用高速缓存、服务事务化等等。
总的来说,分布式的优缺点都比较鲜明,需要针对应用的特点进行细致的调教,一般都是高投入的专用系统,不太适合jxTMS所期望的低成本、通用性与开箱即用的要求。
2、在线热备,分为主机与备份机,主机工作备份机不工作,在主机故障时,备份机可直接接管服务
3、在线冷备,分为主机与备份机,主机工作备份机不工作,在主机故障时,备份机丢弃现有服务请求后接管服务
可以看出,热备与冷备的区别在于对待正在处理服务请求的响应机制:热备需要从机保持切换前的主机服务状态;冷备则直接丢弃主机的服务状态,从零开始提供服务。
热备可以用高速缓存甚至数据库来实现,但其缺点和分布式是一样的:阶段性的中间数据全部要放入缓存以实现服务的事务化,性能影响较大而且服务编写较为复杂【自然成本就高】。
冷备则非常简单,明确所提供的服务存在失效的可能,这样在请求方请求服务超时或返回无效请求后,再次请求即可,即用会话的复杂性来对冲服务的可靠性承诺。
想实现完美的带状态接管,必须使用持久化的高速缓存完整记录服务请求、服务状态并将服务事务化,以及服务的请求异步化,这都会导致成本高、性能低且应用编写繁琐。
综上,我们可以用两个公式来概括这三者的关系:
在线热备 = 在线冷备 + 服务事务化基础上的状态同步
分布式 = 在线热备 + 服务管控与调度
相比来说,在线冷备实现起来简便、可靠、坚固,适用面广、技术门槛低、系统开销也小的多。缺点就是服务存在失效的可能,所以需要将服务请求会话化,即保留服务请求的现场,检查服务请求的执行结果,当请求失败后重新发起服务请求或进行容错处理。但相对复杂的服务状态同步来说,会话管理的技术难度与实现成本都非常低。
考虑到这一点,所以jxTMS依托catalogService实现的就是在线冷备方案。
但docker版jxTMS所展示的数据采集与处理系统却自然而然的成了在线热备。这是基于如下两点:
1、数据采集是天然的无状态。即本次所接收到的数据,和之前接收到的历史数据无关
2、前面强调过多次的,通过数据总线将数据采集与数据应用切分开来。所以,哪怕数据应用的部分是有状态的,但因为是和采集部分隔离的,所以不会影响到数据采集与处理的无状态性
注:数据采集系统的热备并不意味着主机故障到从机接管之间的数据不会丢失。但由于目前jxTMS的数据采集主要通过mqtt推送,而mqtt也是具备数据保持能力甚至数据持久能力的,如果调整这方面的配合,以及优化服务保活间隔的设置,是可以实现最小化的数据丢失率的。甚至,理论上,是可以以部分性能的损失为代价实现0数据丢失的服务接管能力
1、服务的状态检测
catalogService提供了保活机制,即各服务必须先注册,注册成功后必须以可自定义的间隔向catalogService发送keeplive消息。
当超过三次未收到当前已注册的服务的keeplive消息,catalogService即认为该服务已经失联,就会将其从目录中删除掉,其它服务就可以再次注册了。
2、一致性保证
主机宕机,从机接管,中间出现数据丢失是可以接受的【取决于业务需要与零丢失所需成本之间的平衡】,但系统必须保证主从的一致性。即从机的行为不能与主机行为不一样。
而影响主从一致性的是三个方面:
代码的一致性,这是通过代码管理、版本管理来实现的,不在本文讨论范围之内
配置的一致性,以前的docker版jxTMS主要采用本地文件的配置方式,这时想要保持配置的一致性,只要保证两服务器上的配置文件一致即可。但目前已经将配置逐步迁移到基于数据总线,所以配置一致性的保持就较为复杂了【需要考虑前文所提到的水平切片、单服务器上运行多种服务等等】,但由于笔者目前也没有太复杂的应用场景,所以目前这一部分比较简单,并没有对请求方以及请求参数做任何的识别与处理
管理命令执行结果的一致性,以前的docker版jxTMS主要通过服务来进行管理,但我们正在讨论的在线备份方案中从机根本就没有注册到系统中来,所以管理命令或者迁移到基于数据总线来做;或者还是通过服务发送给主机来执行,但在主机执行完毕后需要通过数据总线广播给从机进行同步。笔者更倾向于后者
注1:目前的数据总线中已经实现了缓存功能,所以管理命令执行结果的一致性可通过主机执行完管理命令后刷新相应的缓存即可实现。即管理命令的执行结果需要存放到数据库中或基于数据总线上的缓存中,这就自动实现了主从在一致性方面的实时同步
注2:这里只讲了系统层面的一致性,还有应用层面各功能模块的一致性【本质上其实就是工作数据的同步】。而各功能模块的一致性保证,本质上就是热备与冷备的区别,所以其基本原则是:如果应用层面的一致性难以保证,那就应以冷备模式工作
3、检测到主机失联后,从机立即接管服务
当主机所注册的服务因为超时没有keeplive而被catalogService从目录中被删除掉后,以同样间隔持续向catalogService进行注册的从机再次注册时,catalogService就会批准本次注册,然后从机会接收到注册通过的响应,就可以执行接管服务的准备动作了。
从机的服务接管主要包括两部分内容:
1、主机失联后会取消对服务地址的监听,而从机在接管服务后会向消息系统注册对服务地址的监听
每个服务都有一个服务监听地址:{服务类型}.{服务名}。已经完成注册的主机会监听该地址,一方面通过消息系统为客户提供服务;另一方面则是通过该地址接受jxTMS主系统发出的管理命令。
2、执行所有受主从切换影响的模块注册的模块相关的服务开关命令
有的功能模块不受主从切换的影响,始终处于就绪待命状态,如用户授权、设备等模块;有的模块则受主从切换的影响,如mqtt模块,只有主机才能从mqtt服务器订阅主题接收数据,从机则不能订阅【包括主机失去和MQ的连接但没有失去到mqtt的连接这种极端情况,当出现这种情况时会切换到从机,应取消订阅】;又如site站点模块,从机是不会接收数据的,所以必须停止通过钉钉发送站点失联告警。
这些会受到主从切换影响的模块在启动时需要注册两个事件通知:注册到catalogService、和catalogService失联。我们以mqtt模块来举例说明,其相应的代码是:
#__init__函数中
#允许订阅有两种情况:没有启动服务以及启动服务并注册到了服务中
self._permit = False
mainService.registerConnectDual(self._name, self.permit, self.refuse, informDual=self._checkServiceState)
#下面是三个事件响应函数与状态通知函数
#服务是否启动
def _checkServiceState(self, state):
if not state:
#服务没有启动需要允许连接
self.permit()
#注册到catalogService
def permit(self):
self._permit = True
if self._connectted:
for topic in self._mqttServerTopic:
self._subscribe(topic)
#和catalogService失联
def refuse(self):
self._permit = False
if self._connectted:
for topic in self._mqttServerTopic:
jxGo.log('info', f'mqttClient[{self._name}] unsubscribe topic[{topic}]')
self._client.unsubscribe(topic)
上述代码已经非常直白了,所以我们介绍一下mainService模块中的registerConnectDual函数、
registerConnectDual(cls, connectDual, disconnectDual, delaySeconds=5, informDual=None)
注册主服务的连接与失联事件响应函数
参数:
connectDual:注册到catalogService的事件响应函数,无参
disconnectDual:和catalogService失联的事件响应函数,无参
delaySeconds:延时多少秒后通知是否启动了服务
informDual:服务状态通知函数,delaySeconds秒被调用
返回值:
无
说明:
informDual的签名是:
informDual(state)
state--服务是否启动,True:启动;False:未启动
设置informDual函数的原因在于,所有的功能模块,如mqtt,都必须在服务启动前完成初始化工作。这是由于jxTMS的服务会启动一个无限循环来实现注册与保活。
所以呢,所有的功能模块在初始化时都是不知道是否启动了服务的。启动了服务,自然会通过connectDual来切换到工作状态,但如果没有启动服务,又需要切换到工作状态才是,而这就需要informDual通过延时进行检测并发出通知了。
本文所讲述的在线备份,属于系统部分失效后的抢救性措施。而在线备份能否抢救成功,关键是主从的一致性保证。而热备所要求的严格一致,需要的成本太高,只适合特定应用场景下的、高投入的自用系统。
jxTMS首先考虑的是低成本下的通用性,是以低的建设成本、低的开发成本、低的维护成本来快速构建自己的应用系统,所以提供的是简单、可靠、坚固的在线冷备模式,只有天然无状态的,如数据采集与处理系统,可工作于在线热备模式。
参考资料:
jxTMS设计思想
jxTMS编程手册
下面的系列文章讲述了如何用jxTMS开发一个实用的业务功能:
如何用jxTMS开发一个功能
下面的系列文章讲述了jxTMS的一些基本开发能力:
jxTMS的HelloWorld