MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架,允许用户对多个活动进行分组和自动化。
过去几年,在开发MirAIe 物联网平台时,我们意识到需要一个可扩展、可扩展的规则引擎框架。规则引擎使您能够对各种操作进行分组、管理和自动化,并可用于各种应用程序,例如家庭自动化、欺诈检测、风险管理和工作流程自动化。在松下,我们参与了移动、工业 4.0、楼宇管理和家庭自动化领域的多项举措。因此,该框架必须能够适应各种应用程序。在本文中,我将描述我们的规则引擎框架的高级设计。
规则剖析
规则本质上允许用户对大量任务进行分组和自动化。用户生成规则的示例包括,只要室温升至 27 度以上,就打开卧室空调;每天晚上 6 点打开办公室大厅的一组灯;当装配线中的机器效率低于以下时通知您80%,或者当电池电量低于 30% 时向您的电动汽车发送推送通知,并且在附近发现有空位的充电站。
规则具有指定何时激活规则的触发条件。触发可能取决于用户的位置、传感器的状况、特定时刻、外部天气条件或任何其他因素。可以使用一个或多个逻辑运算符来耦合多个触发器,这些逻辑运算符一起指定规则的触发条件。
规则还具有在满足这些触发条件时激活的一个或多个操作。操作可能像打开灯一样简单,也可能像创建报告并将其作为附件发送给多个用户一样复杂。
设计注意事项
规则触发条件可能取决于多个输入源。数据源可以是内部的,例如安装在实验室中的传感器,每隔几秒发送一次周期性状态,也可以是外部的,例如天气数据,需要每天检索和存储一次,因为数据不会经常更改。挑战在于吸收多个输入源以创建单个值并确定是否应激活规则。
由于我们的框架必须支持广泛的应用,我们决定重点关注两个基本设计原则。第一个原则是架构必须可扩展以支持各种输入触发和输出操作。对于家庭自动化用例,最常见的触发因素可能取决于时间、设备状态或外部天气条件。而对于我们的智能工厂用例,触发器可能是用户活动或随时间收集的聚合数据,例如机器效率。其想法是确保可以添加新的触发器类型,而无需对架构进行太多更改。同样,规则操作也是可扩展的。常见操作可以是调用 API、发送通知或创建任务并将其推送到任务队列。
我们对此设计的第二个指导原则是优先考虑灵活性,允许每个组件独立扩展。灵活的系统可以适应不断变化的需求,而无需扩展整个系统,从而提高弹性和成本效率。例如,如果晚上6点需要触发大量规则,系统只需适当扩展定时器触发服务和规则执行服务,而其他服务则继续按原有规模运行。通过提供这种灵活性,系统可以高效且有效地满足不同的需求。
规则引擎组件
为了实现可扩展性,我们解耦了规则触发服务、规则处理引擎和规则执行服务。规则触发服务是微服务的集合,每个微服务能够处理特定类型的规则触发逻辑。规则引擎根据规则定义组合各种触发状态,以确定是否应触发规则。最后,规则执行服务是特定于应用程序的规则执行逻辑,用于执行规则中指定的预期操作。每个组件都是独立开发的。它们实现了定义良好的接口并且可以独立扩展。
规则触发服务
规则触发服务实现逻辑以确定何时应触发规则。它是微服务的集合,每个微服务都能够处理非常特定类型的触发器。例如,时间点激活和基于持续时间的触发器的逻辑由定时器触发器的微服务处理。同样,不同的服务处理设备状态的触发器或基于天气的触发器。
根据规则定义中的规则触发条件,规则在首次创建时将其自身注册到一个或多个规则触发服务。每个触发器服务提供三个主要 API 来注册、更新和取消注册规则。注册触发器的实际有效负载可能因服务而异,但是,规则创建/更新 API 的设计方式使得规则管理服务可以快速识别触发器类型并将解析和解释触发条件委托给相应的触发器服务。各个触发器类型的端点可以作为配置或环境变量的一部分进行共享,也可以使用标准服务发现模式在运行时发现它们。
每种触发器类型都有不同的激活服务逻辑。触发器可以从一个或多个输入源捕获数据,对其进行处理,必要时对其进行缓存,并在决定是否应触发规则时发出事件。当特定规则满足触发条件时,规则触发服务会发出布尔值 true,否则会发出 false。
规则可以包括一个或多个触发器,其中每个触发器都建立一个特定条件,必须满足该条件才能执行规则。例如,考虑每天下午 6 点或当环境照明水平低于 100 勒克斯时打开客厅灯的规则。该规则使用 OR 逻辑组合两个条件,第一个是基于时间的触发器,第二个是设备(ALS 传感器)状态触发器。还可以通过多个触发器和逻辑运算符的组合来创建更复杂的规则。
为了管理每个触发器的状态,使用了持久缓存,该缓存由相应的触发器服务更新。这确保了规则处理引擎始终可以使用最新的触发器状态,从而允许其评估条件并调用适当的操作。上图中,红色触发状态表示当前触发条件不满足,绿色状态表示触发条件已满足。一旦规则的触发状态发生变化,相应的触发服务会将规则id添加到队列中进行处理,随后由规则处理引擎消费。
每个规则触发服务都被设计为可水平扩展,并且可以根据注册规则的数量独立于其他系统组件进行扩展。这种解耦还允许每个触发器的激活逻辑随着应用程序的发展而独立发展。此外,可以以最小的更改将新的触发器类型添加到系统中。
规则处理引擎
规则处理引擎从待处理规则队列中处理规则,并根据触发状态执行规则。如果触发逻辑是一个或多个规则触发器的组合,则处理引擎根据规则定义中指定的触发逻辑组合每个输入触发器的状态,以计算最终的布尔值。一旦它确定必须触发该规则,它就会调用规则执行服务来执行该规则。
触发状态大致有两类。时间点触发仅在触发状态发生变化时才有效,例如下午六点激活规则或设备状态变化触发(例如空调打开时关闭风扇)。如果还满足所有其他触发条件,则应在事件发生后立即激活此类规则。规则处理引擎在处理完规则后立即重置此类触发器的值。
第二类触发器代表实体在较长时间内的持久状态。例如,考虑这样一种场景:如果在下午 6 点到早上 6 点之间检测到运动,则应打开门廊灯。计时器触发服务会在下午 6 点将触发值设置为 true,然后在上午 6 点将其设置为 false。这些状态不会由规则处理引擎重置,并且保持不变,直到由触发器服务显式修改。这使得系统能够维护实体的持久状态并根据其持续状态做出决策。
规则执行服务
规则执行服务可以调用 HTTP API、发送 MQTT 消息或触发推送通知来执行规则。规则可以执行的操作列表是特定于应用程序的并且是可扩展的。与规则触发服务一样,规则操作服务与核心规则引擎解耦,并且可独立扩展。
将规则执行服务与多个规则操作服务解耦的一种方法是使用消息队列,例如 Kafka。根据操作类型,规则执行服务可以将规则操作发布到单独的 Kafka 主题,一组消费者可以使用这些主题并执行相关操作。规则操作有效负载可以特定于操作类型,并作为规则定义的一部分捕获并按原样传递到任务队列。
扩展触发服务
规则触发服务可以是有状态的,因此扩展它们可能是一个挑战。没有通用的方法来扩展所有触发器服务,因为它们的底层实现根据触发器类型和它们可能依赖的外部服务而变化。在本节中,我将解释用于两种重要触发器类型的缩放方法。
设备状态触发器
为了向设备状态触发服务注册规则,规则管理服务将提供设备标识符、设备属性及其相应的阈值。设备状态触发服务会将这些存储在共享缓存(例如 Redis)中,并确保仅使用设备 ID 即可访问它们。
在给定的示例中,有关设备状态变化的通知通过 MQTT 协议发送,然后添加到 Kafka 消息队列中。负责设备状态的 Kafka 消费者接收并处理每个传入事件。它检查规则触发器缓存以确定是否有任何规则与设备关联。根据此信息,消费者更新相应的触发器状态缓存,反映该设备触发器的当前状态。这种机制确保触发状态缓存与最新的设备状态变化保持同步,从而使系统能够根据最新的信息准确地评估规则。
我们所有的服务都是容器化的,并在 Kubernetes 集群中运行。设备状态触发服务是标准API服务,通过应用程序负载均衡和自动伸缩组进行扩展。设备状态使用者组根据传入设备状态更改事件的速率进行扩展。Kubernetes 事件驱动自动缩放 (KEDA)可以根据需要处理的事件数量来驱动设备状态使用者的缩放。此外,还有一些工具可以预测 Kafka 工作负载,可以用来更迅速地扩展消费者,从而提高性能。
定时器触发
定时器触发服务处理时间点触发和持续时间触发。触发请求有效负载可以像一天中的特定时间一样简单,也可以像Unix Cron 作业的规范一样详细。该服务不需要在内存中保存所有已注册的规则请求,因为规则可能几天或几个月都不会触发。相反,一旦它收到规则注册请求,它就会计算下一次应该运行该规则的时间,并将规则详细信息以及下一次运行时间存储在数据库中。
该服务会定期以固定的时间间隔获取后续时间窗口中需要激活的所有规则并将其拉入内存。这可以通过过滤规则的下一个运行时间字段来完成。一旦确定了需要运行的所有规则,该服务就会按触发时间对规则进行排序,并生成一个或多个 Kubernetes Pod 来处理这些规则。每个 Pod 只会被分配规则的一个子集。可以通过 Zookeeper 或 Kubernetes自定义资源定义(CRD)共享分配给不同 Pod 的规则。
Kubernetes CRD 可用于通过定义代表特定任务的自定义资源来共享数据并在多个 Pod 之间分配工作。定时器触发服务通过将下一个时间窗口中需要处理的所有规则划分为单独的任务并将它们存储在 CRD 中来使用此功能。随后创建多个工作单元并分配特定任务。然后,每个 Pod 处理规则并相应地更新触发器状态缓存。
可维护性和可扩展性
规则管理服务和规则执行服务是无状态服务,逻辑相当简单。规则管理提供用于规则创建、更新和删除的标准API。规则执行服务独立工作以执行规则操作,主要调用特定于应用程序的操作。
规则管理服务和触发服务之间的通信可以异步进行,从而无需服务发现。例如,每个触发器服务都可以在 Kafka 消息代理中拥有其专用队列。规则管理服务可以根据触发类型将触发请求添加到相应的队列中,供触发类型的Kafka消费者(触发服务)处理来消费。
添加对新触发器类型和新操作类型的支持很简单,因为所有关键组件都是相互解耦的。规则管理服务具有基于插件的设计,其中为每个支持的触发器类型和操作类型添加插件,以验证规则定义中相应的触发器和操作有效负载。规则触发器和操作类型名称可以转换为相应的 Kafka 队列名称,用于规则管理服务和触发器服务之间以及规则处理服务和执行服务之间的通信。
可以在多个级别上测试系统。通过单元测试覆盖各个服务并专注于特定功能相对容易。为了促进分布式环境中的轻松调试和故障排除,必须遵守分布式日志记录和跟踪的最佳实践。正确实施的日志记录和跟踪机制将使我们能够跟踪不同服务之间的请求流,有效地识别问题并诊断问题。遵循这些最佳实践可确保更好地了解系统行为并简化调试过程。
可靠性
让我们首先确定可能出现的潜在挑战。重要的是要承认系统内的任何服务都可能会意外停机,系统负载的增长速度可能快于其有效扩展的能力,并且某些服务或基础设施组件可能会遇到暂时不可用的情况。
使用 Kafka 进行通信有助于实现多个级别的可靠性。Kafka 提供有助于消息传递和消费可靠性的功能,包括消息持久性、强大的持久性保证、容错复制、消费者组之间的负载分配以及至少一种传递语义。
最直接的可靠性案例涉及触发服务。对于设备状态触发,Kafka配置为保证至少一次投递,从而保证状态变化事件不会丢失。然而,实现定时器触发服务的可靠性需要额外的步骤。在这里,重要的是要避免让任何单个工作人员因需要同时处理的大量事件而不堪重负。
我们的方法是按时间顺序排列下一个时间窗口中要处理的规则列表,并以循环方式在工作人员服务之间分发它们。此外,worker pod 的数量与时间窗口内的计时器任务数量以及任何给定时间要执行的最大任务数量成正比。这确保了有足够数量的工作节点可同时处理潜在的大量计时器任务。此外,将工作单元配置为在崩溃时自动重新启动也是有益的,使其能够恢复并完成分配的任务,而无需手动干预。
此外,Kubernetes 还具有为每个服务定义资源限制和最低要求的优势。这包括指定服务可以利用的最大 CPU 或 RAM 资源量以及成功启动所需的最少资源。借助 Kubernetes,可以减轻对"吵闹邻居"问题(即一个 Pod 的资源密集型行为影响同一集群节点上的其他 Pod)等问题的担忧。Kubernetes 提供隔离和资源管理能力,有助于维护整个系统的稳定性和可靠性。
概括
MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架,允许用户对多个活动进行分组和自动化。该框架支持各种内部或外部触发器,并侧重于两个设计原则:可扩展性和灵活性。该架构必须可扩展,以支持各种输入触发器和输出操作,从而允许在不进行太多更改的情况下添加新类型。该系统还优先考虑灵活性,以实现每个组件的独立扩展,从而提高弹性和成本效率。