24、事件处理与流程管理技术解析

事件处理与流程管理技术解析

1. 扁平文件结构与事件消费

在处理事件时,采用扁平文件结构存在一定的缺点。这种结构不仅要明确一个日志扁平文件应写入多少事件,还要规定文件在磁盘上的布局。操作系统对单个目录可容纳的文件数量有限制,即便系统能在单个目录存储大量文件,这些限制也会降低访问速度。不过,类似电子邮件服务器的分层结构方法能让扁平文件的访问变得非常快速。

与关系型数据库相比,大量的扁平文件和目录布局降低了篡改内容的可能性。若这还不足以起到威慑作用,那么对文件系统的安全访问也能提供保障。

可以通过REST方式消费事件,常见的方法有以下两种:

1.1 订阅者轮询

订阅者可对日志资源进行简单轮询,例如:

GET /streams/{name}/1-20
GET /streams/{name}/21-40
GET /streams/{name}/41-60
GET /streams/{name}/61-80
GET /streams/{name}/81-100
GET /streams/{name}/101-120

其中, {name} 需替换为要读取的流名称,如 underwriting policy-marketplace 。前者仅提供承保相关的特定事件,后者则提供包括承保、风险和费率等各个子系统上下文中的所有事件流。

然而,若订阅者轮询实现不当,客户端会不断请求尚未可用的下一个日志,这会产生大量网络流量。因此,请求的日志大小必须合理。可以通过将资源标识设置为固定范围,并在响应头中使用超媒体链接引用上一个和下一个日志来实现这一点。还可利用响应头元数据建立缓存技术和定时读取间隔,以缓解请求过载问题。此外,使用通用的生成式URI,可提供部分日志:

GET /streams/policy-marketplace/current

current 资源用于获取最新的事件日志资源。若当前日志(如101 - 120)超出了客户端尚未读取的上一个事件日志,HTTP响应头会提供导航到上一个日志的链接,客户端会先读取并应用上一个日志,再处理当前日志。这种反向导航会持续进行,直到客户端读取到最近应用的事件。从该点起,应用所有尚未应用的事件,包括向前导航直至到达当前日志。缓存技术在这个过程中也很重要,它可防止已读取但尚未应用的日志被重复从服务器读取。

下面是订阅者轮询的流程图:

graph LR
    A[客户端发起轮询请求] --> B{日志是否可用}
    B -- 是 --> C[处理日志]
    B -- 否 --> D[等待并再次请求]
    C --> E{是否到达当前日志}
    E -- 否 --> F[根据响应头导航到上一个日志]
    F --> C
    E -- 是 --> G[结束轮询]
1.2 服务器发送事件

服务器发送事件(SSE)通常用于服务器向浏览器提供事件流,但这里主要用于事件生产者与非浏览器的服务/应用程序客户端之间的集成。并非所有浏览器都支持SSE规范,不过它仍是一种不错的集成选项。

客户端若要订阅事件流,需向服务器请求一个长连接。订阅时,客户端可指定最后成功应用的事件标识符。例如:

GET /streams/policy-marketplace
...
Last-Event-ID: 102470

客户端需负责维护其在事件流中的当前位置。订阅后,事件会从开始位置或 Last-Event-ID 指定的位置开始流式传输,直到客户端取消订阅或断开连接。SSE规范的事件格式如下:

id: 102470
event: RiskAssessed
data: { “name” : “value”, ... }

id: 102480
event: RateCalculated
data: { “name” : “value”, ... }

客户端若要取消订阅,需发送以下消息:

DELETE /streams/policy-marketplace

发送此消息后,订阅终止,服务器返回 200 OK 响应并关闭通道,客户端收到响应后也应关闭通道。

2. 事件驱动与流程管理

前面提到的事件驱动流程管理侧重于编排式流程,而这里将重点转向编排式流程(如Netflix的Conductor框架),由一个中央组件负责从头到尾驱动流程。

以“应用保费流程”为例,该流程管理器负责将完整构建的报价结果提供给提交申请的申请人,具体步骤如下:
1. 申请人提交申请文档,创建聚合类型 Application ,产生 ApplicationSubmitted 事件,流程管理器检测到该事件后流程启动。
2. ApplicationSubmitted 事件转换为 AssessRisk 命令,并加入消息总线。
3. AssessRisk 命令被发送到风险上下文,由 RiskAssessor 领域服务处理。
4. 风险评估完成后,发出 RiskAssessed 事件并加入消息总线。
5. RiskAssessed 事件被发送到流程管理器。
6. RiskAssessed 事件转换为 CalculateRate 命令,并加入消息总线。
7. CalculateRate 命令被发送到费率上下文,由 RateCalculator 领域服务处理。
8. 费率计算完成后,发出 RateCalculated 事件并加入消息总线。
9. RateCalculated 事件被发送到流程管理器。
10. RateCalculated 事件转换为 GenerateQuote 命令,直接发送给 QuoteGenerator 领域服务。 QuoteGenerator 将保费费率转换为报价行,并发送给 PremiumQuote 聚合。记录最终报价行后,发出 QuoteGenerated 事件并存储在数据库中。
11. 事件存储在数据库后,可加入消息总线。收到 QuoteGenerated 事件标志着“应用保费流程”结束。

所有事件和由其转换的命令会先持久化到数据库,再放入消息总线,以确保至少一次传递。这种编排式流程中,流程管理器负责驱动流程,协作上下文只需关注自身核心职责,无需了解流程细节。

“应用保费流程”默认部署在承保上下文中,但也可单独部署。该流程和相关上下文可以部署为单体应用或微服务,消息总线的使用并不一定意味着采用微服务架构:
- 单体应用可使用轻量级消息传递(如ZeroMQ)提供消息总线。
- 团队也可选择更可靠的消息中间件或基于云的消息总线(如RabbitMQ、Kafka等)。
- 解决方案可能需要微服务架构,或单体应用和微服务的混合架构,可靠的消息传递机制是不错的选择。使用模式注册表可降低跨上下文依赖和不同发布语言之间的转换复杂性。

下面是应用保费流程的步骤表格:
| 步骤 | 事件/命令 | 处理组件 |
| ---- | ---- | ---- |
| 1 | ApplicationSubmitted | 流程管理器 |
| 2 | AssessRisk(命令) | 消息总线 |
| 3 | AssessRisk(命令) | RiskAssessor |
| 4 | RiskAssessed | 消息总线 |
| 5 | RiskAssessed | 流程管理器 |
| 6 | CalculateRate(命令) | 消息总线 |
| 7 | CalculateRate(命令) | RateCalculator |
| 8 | RateCalculated | 消息总线 |
| 9 | RateCalculated | 流程管理器 |
| 10 | GenerateQuote(命令) | QuoteGenerator |
| 11 | QuoteGenerated | 数据库、消息总线 |

事件处理与流程管理技术解析

3. 事件溯源

传统软件开发中,开发者常将对象存储在关系型数据库中,通过对象关系映射工具持久化聚合的整体状态。近年来,一些关系型数据库支持将对象序列化为JSON存储,可通过特定SQL扩展进行查询。

而事件溯源则是一种截然不同的对象持久化方法,它不直接存储对象,而是记录对象状态的变化。当聚合处理命令导致状态改变时,至少会产生一个事件来表示这种变化。这些事件粒度很细,代表了捕获变化本质所需的最小状态。它们会被存储在数据库中,并按照特定聚合的发生顺序排列,这个有序的事件集合称为聚合的流。每次聚合状态改变并产生新事件时,流的版本会更新,长度也会增加。

若聚合实例超出作用域被垃圾回收,后续需要使用时,就需重新构建其状态。这通过按事件原始发生顺序从数据库读取聚合的流,并逐个应用到聚合上来实现,从而使聚合状态逐步反映每个事件所代表的变化。

不过,事件溯源也存在一些挑战:
- 设计变更 :当一个或多个聚合类型发生重大设计变更时,需要生成能分割或合并流的新流。
- 事件错误 :若聚合流中的某个事件出现错误,不能直接修改数据库中的事件数据,而是添加一个新事件(可能是用于修补的不同类型事件),以补偿重构聚合状态和下游消费者受影响的部分。由于错误可能源于生成事件的代码,所以需要修复代码并按上述方式修补所有受影响的流。
- 大事件流 :当聚合的状态由大量事件流构成时,重构状态的性能会受到影响。可以通过定期创建状态快照来提高性能,例如每隔100或200个版本创建一次。重构时,先读取快照,再读取并应用快照版本之后的事件。
- 复杂数据视图 :使用事件溯源时,通常需要结合命令查询职责分离(CQRS),将聚合发出的事件投影到用于查询和展示的视图中。

下面是事件溯源面临挑战及应对方法的表格:
| 挑战类型 | 应对方法 |
| ---- | ---- |
| 设计变更 | 生成能分割或合并流的新流 |
| 事件错误 | 添加新事件补偿,修复代码并修补流 |
| 大事件流 | 创建状态快照,先读快照再应用后续事件 |
| 复杂数据视图 | 结合CQRS投影事件到视图 |

尽管存在这些挑战,但事件溯源也有显著的优势,它可以为使用该方法的每个聚合类型的实例维护完整的变更审计轨迹,这在某些特定行业可能是必要的或明智的选择。

事件溯源的流程图如下:

graph LR
    A[聚合状态改变] --> B[生成事件]
    B --> C[存储事件到数据库]
    C --> D{聚合实例需重构?}
    D -- 是 --> E[按顺序读取事件流]
    E --> F[逐个应用事件到聚合]
    F --> G[聚合状态重构完成]
    D -- 否 --> H[继续正常使用]
4. 总结与权衡

在事件处理和流程管理中,不同的技术和方法各有优劣。扁平文件结构在事件存储和访问上有其特点,订阅者轮询和服务器发送事件为事件消费提供了不同的方式,各有适用场景和需要注意的问题。事件驱动的流程管理从编排式转向编排式,能降低协作上下文的复杂度,而消息总线的使用为架构选择提供了灵活性。

事件溯源作为一种独特的对象持久化方法,虽然面临一些挑战,但能提供强大的审计功能。在实际应用中,需要根据项目的具体需求、行业规范和性能要求等因素,综合考虑这些技术的使用。不能盲目跟从技术潮流,而应基于业务驱动的目的做出合理的选择,充分认识到每种技术带来的权衡和后果,以确保项目的成功实施。

例如,在选择架构时,若项目对审计要求高,且能接受事件溯源带来的一定复杂性,那么可以考虑采用;若追求简单快速的开发和部署,可能传统的存储和流程管理方式更合适。在事件消费方面,若客户端对实时性要求不高,订阅者轮询结合缓存技术可以有效减少网络流量;若需要实时获取事件,服务器发送事件则是更好的选择。

总之,软件开发中的技术选择是一个综合考量的过程,需要全面了解各种技术的优缺点,并结合实际情况做出明智的决策。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值