Drools - 存储多状态会话

Drools - Store Multi Stateful Sessions

我们在我们的平台中实施了 drools 引擎,以便能够评估来自流的规则。 在我们的用例中,我们有一个变化检测流,其中包含多个实体的变化。

需要在一段时间内针对流中的每个实体评估规则,并使其状态与其他实体(会话)区分开来。这些规则根据每个实体的状态生成警报。由于这个原因,实体应该进入边界,因此一个实体的状态不会干扰其他实体。

为实现这一点,我们为每个实体 ID 创建一个会话作为 Spring Bean,并将其存储在 inMemory HashMap 中。因此,每次实体到达时,我们都会尝试使用它的 Id 在 inMemory Map 上找到它的会话。如果我们得到一个 null return 我们就创建它。

这似乎不是完成它的正确方法。因为它不提供灾难恢复策略,也不提供出色的内存管理。

我们可以使用某种内存数据库,例如 Redis 或 Memchached。但我不认为它能够精确地恢复有状态会话。

有人知道如何使用具有多会话的嵌入式 Drools 以正确的方式实现灾难恢复和良好的内存管理吗?该平台是否提供一些解决方案?

非常感谢您的关注与支持

答案不是尝试持久化和重用会话,而是持久化对实体当前状态建模的对象。

您当前的工作流程是这样的:

  1. 实体到达您的应用程序(来自变更检测流或其他地方)
  2. 您在哈希图上进行查找以获取存储了实体状态的会话
  3. 您启动规则,更新会话(可能还有实体)
  4. 您坚持会话 in-memory。

您的工作流程应该是这样的:

  1. (相同)实体到达您的应用程序
  2. 您对实体状态的外部数据源执行 look-up -- 例如来自数据库或数据存储
  3. 你触发规则,传入实体状态。您没有更新会话,而是更新了状态实例。
  4. 您将状态保存到外部数据源。

如果添加适当的 write-through 缓存,您可以同时保证性能和一致性。如果您为数据源实施适当的锁定/事务处理,这也将允许您横向扩展应用程序。


这是一个玩具示例。

假设我们有一个模拟图书馆的应用程序,允许用户借阅图书。一个用户一次最多只能借阅3本书。

'event'我们收到模型一本书check-in或check-out事件:

class BookBorrowEvent {
  int userId;
  int bookId;
  EventType eventType; // EventType.CHECK_IN or EventType.CHECK_OUT
}

在外部数据源中,我们维护一条 UserState 记录——可能作为传统 RDBMS 或聚合中的不同记录;我们如何存储它与示例并不相关。但是假设我们从数据源 returned 的 UserState 记录看起来像这样:

class UserState {
  int userId;
  int[] borrowedBookIds;
}

当我们收到事件时,我们将首先从外部数据存储(或 internally-managed write-through 缓存中检索用户状态,然后将 UserState 添加到规则输入中。当然,我们应该适当地处理我们的会话(使用后处理它们,根据需要使用会话池)。

public void handleBookBorrow(BookBorrowEvent event) {
  UserState state = getUserStateFromStore(event.getUserId());

  KieSession kieSession = ...; 
  kieSession.insert( event );
  kieSession.insert( state ); 
  kieSession.fireAllRules();

  persistUserStateToStore(state);
}

然后您的规则将针对 UserState 实例执行它们的工作,而不是将值存储在局部变量中。

一些示例规则:

rule "User borrows a book"
when
  BookBorrowEvent( eventType == EventType.CHECK_OUT,
                   $bookId: bookId != null )
  $state: UserState( $checkedOutBooks: borrowedBookIds not contains $bookId )
  Integer( this < 3 ) from $checkedOutBooks.length
then
  modify( $state ) { ... }
end

rule "User returns a book"
when
  BookBorrowEvent( eventType == EventType.CHECK_IN,
                   $bookId: bookId != null )
  $state: UserState( $checkedOutBooks: borrowedBookIds contains $bookId )
then
  modify( $state ) { ... }
end

显然是一个玩具示例,但您可以轻松地为以下情况添加额外的规则,例如用户尝试签出一本书的副本,用户尝试 return 他们没有签出的书, return 如果用户超过 3 本书最大借阅限制,则会出现错误,添加 time-based 允许的结帐长度等逻辑

即使您正在使用 stream-based 处理以便您可以利用时间运算符,此工作流程仍然有效,因为您会在收到状态实例时将其传递到评估流中。当然,在这种情况下,出于性能原因,正确实现 write-through 缓存更为重要(除非您的时态运算符足够宽容以允许某些数据源事务延迟)。您需要做的唯一更改是重新调整您的规则,将它们的数据持久性定位到状态对象而不是会话本身——无论如何通常不推荐这样做,因为会话被设计为被处理掉。