Harness 中的事件溯源:以事件日志重建状态

news2026/4/13 3:46:49
Harness 中的事件溯源以事件日志重建全链路 DevOps 状态引言痛点引入作为全链路 DevOps 平台Harness 每天会处理数百万到数千万级别的用户/系统操作开发者点击“启动流水线”、Feature Flag 规则引擎执行批量开关切换、云成本扫描器发现异常资源并标记、应用性能监控APM自动触发性能测试……这些操作的背后是整个 DevOps 协作链路的状态变迁——流水线从“空闲”到“排队”再到“构建中”“部署失败”Feature Flag 从“仅内部灰度”到“全量启用”EC2 实例从“待释放”到“标记忽略”……但在没有可靠的状态重建机制时全链路 DevOps 工程师会遇到哪些噩梦灾难性回滚束手无策比如 Feature Flag 误全量了一个导致支付接口挂掉的新功能传统的“直接改 Flag 配置”回滚只能覆盖当前状态但要查“谁、什么时候、通过什么方式改的配置前几次修改是什么对哪些测试环境/生产环境生效了支付接口挂掉的时间点和 Flag 切换的时间戳差多少”——这些问题没有清晰的、不可篡改的历史记录根本答不上回滚决策只能靠猜耽误的每一分钟都是真金白银的损失。审计合规无从谈起金融、医疗、政府等行业的 DevOps 平台必须满足严格的审计要求比如 SOC 2、HIPAA、GDPR 中的“操作可追溯”“变更审批留痕”。如果只用数据库快照或覆盖式的 CRUD 存储状态根本无法提供完整的、带因果关系的操作链——比如“张三提交的 Flag 变更李四在 14:30 拒绝王五后来又在 14:35 强制通过并部署”传统存储只能看到最后一次“部署中”的 Flag 状态中间的审批/拒绝/强制操作全丢了审计报告肯定过不了。问题定位效率极低比如一个流水线构建失败排查原因时只看到“Docker 镜像构建超时”但要查“超时前的 30 分钟该流水线使用的 Jenkins 节点有什么操作节点 CPU 利用率是多少镜像仓库的拉取速度突然下降是从什么时候开始的有没有其他流水线同时在使用同一个节点”——这些跨系统的状态变迁如果没有统一的事件源就得去翻 Jenkins 日志、Prometheus 监控、Docker 镜像仓库日志、Kubernetes 节点事件日志格式不一样、时间戳可能对不上、还有可能被日志清理策略删掉排查一个简单的问题可能要花几个小时甚至几天。状态一致性难以保证Harness 是一个分布式微服务架构比如有 Pipeline Service、Feature Flag Service、Cloud Cost Management Service 等几十个微服务不同微服务之间通过 RPC 或消息队列通信。如果只用覆盖式的 CRUD 存储当某个微服务调用失败或网络分区发生时很容易出现状态不一致——比如 Pipeline Service 已经更新了数据库里的“流水线构建中”状态但 Feature Flag Service 还没收到“启动灰度测试”的消息导致灰度测试没有触发用户看到的“流水线已完成灰度测试”和实际的“灰度测试未启动”状态冲突。解决方案概述Harness 解决上述所有问题的核心技术方案之一就是事件溯源Event Sourcing——这是一种以“事件”为核心而非以“当前状态”为核心的架构模式不再直接存储当前状态而是将所有对系统状态产生影响的操作都以“不可变事件Immutable Event”的形式按发生时间顺序写入“事件日志Event Log”。当前状态由事件日志重建任何时候需要获取某个实体比如某个流水线、某个 Feature Flag、某个 EC2 实例的当前状态只需要按时间顺序重放该实体相关的所有历史事件逐步更新状态最终得到的结果就是当前状态。事件日志是唯一的真相源所有的审计、回滚、问题定位、状态一致性保证都基于这个“不可变、按时间顺序、带因果关系”的事件日志。事件溯源听起来很简单但在 Harness 这种高并发、高可用、跨多租户、跨微服务的全链路 DevOps 平台上落地需要解决很多复杂的技术问题——比如事件的序列化/反序列化、事件日志的存储与检索、事件的重放性能优化、事件的版本兼容性、事件的因果关系追踪、多租户隔离、分布式事务的替代方案因为事件溯源通常结合 CQRS 模式使用不需要传统的分布式事务等。最终效果展示可选在 Harness 中事件溯源带来了哪些直观的、让用户/工程师眼前一亮的效果我们可以先展示几个典型的场景场景1Feature Flag 的完整历史与一键回滚在 Harness Feature Flag 的控制台中任何一个 Feature Flag 都有一个“事件时间线Event Timeline”页面上面清晰地列出了该 Flag 从创建到现在的所有操作比如“202X-05-20 10:00:00 张三创建 Flag PAYMENT_GATEWAY_V2状态为 OFF”“202X-05-20 14:00:00 张三提交 Flag 变更将 PAYMENT_GATEWAY_V2 开启至 内部团队10% 流量”“202X-05-20 14:20:00 李四审批通过该变更”“202X-05-20 14:21:00 Flag 变更生效10% 内部用户开始使用 V2 支付网关”“202X-05-20 15:00:00 王五提交 Flag 变更将 PAYMENT_GATEWAY_V2 开启至 全量用户”“202X-05-20 15:01:00 赵六发现支付接口错误率从 0.01% 飙升至 10%”“202X-05-20 15:02:00 赵六点击‘一键回滚到李四审批通过后的状态’”“202X-05-20 15:02:30 Flag 回滚生效错误率迅速下降至 0.01%”。而且在“一键回滚”时Harness不是直接修改当前的 Flag 配置而是生成一个新的“回滚事件Rollback Event”该事件的内容是“将 Flag 的状态恢复到事件 ID 为 12345李四审批通过后的事件时的状态”然后将这个回滚事件写入事件日志——这样回滚操作本身也被记录下来审计合规仍然没问题而且如果回滚操作本身有问题还可以“回滚回滚操作”真正做到了“任何操作都是可逆的只要不删除事件日志”。场景2流水线的全链路问题定位在 Harness Pipeline 的控制台中任何一个失败的流水线执行都有一个“执行追踪Execution Trace”页面这个页面基于事件溯源重建了整个流水线执行的完整状态变迁链并且关联了跨系统的所有相关事件比如“202X-05-21 09:00:00 开发者点击启动流水线生成事件 PIPELINE_TRIGGERED关联的 Git 提交 ID 是 abc123”“202X-05-21 09:00:10 事件 QUEUEING_JOB流水线被分配到 Jenkins 集群的节点 node-123”“202X-05-21 09:00:20 事件 BUILD_STARTEDDocker 镜像构建开始关联的 Dockerfile 路径是 /app/Dockerfile”“202X-05-21 09:05:00 事件 BUILD_STEP_FAILED错误信息是‘npm install 超时无法连接到 registry.npmjs.org’”“同时该页面还关联了 Prometheus 的监控事件 NODE_CPU_HIGHnode-123 的 CPU 利用率从 09:04:30 开始飙升至 95%、Docker 镜像仓库的事件 REGISTRY_LATENCY_HIGH从 09:04:40 开始registry.npmjs.org 的拉取延迟从 100ms 飙升至 10s、Kubernetes 的事件 POD_RESTARTINGnode-123 上的其他 3 个 Jenkins 代理 Pod 从 09:04:50 开始重启”。有了这个“执行追踪”页面全链路 DevOps 工程师只需要5分钟就能定位到问题的根本原因是 node-123 的 CPU 利用率过高导致其他 Jenkins 代理 Pod 重启进而导致该流水线的 npm install 请求无法及时处理最后超时。解决办法也很简单重启 node-123 或者将该流水线分配到其他空闲的 Jenkins 节点。场景3云成本的异常追踪与预算恢复在 Harness Cloud Cost ManagementCCM的控制台中任何一个超过预算的资源组都有一个“成本异常时间线Cost Anomaly Timeline”页面这个页面基于事件溯源重建了该资源组的成本变化链并且关联了所有对资源组成本产生影响的操作比如“202X-05-1 00:00:00 事件 BUDGET_SET资源组 prod-web 的月度预算设置为 10000 美元”“202X-05-15 08:00:00 事件 RESOURCE_SCALED_UP开发者张三将 prod-web 的 Kubernetes Deployment 副本数从 10 扩容到 50”“202X-05-20 00:00:00 事件 COST_ALERT_TRIGGEREDprod-web 的当前成本8000 美元已达到月度预算的 80%”“202X-05-22 00:00:00 事件 COST_ALERT_TRIGGEREDprod-web 的当前成本9500 美元已达到月度预算的 95%”“202X-05-22 09:00:00 事件 RESOURCE_AUTO_SCALED_DOWNHarness CCM 的自动优化规则触发将 prod-web 的 Kubernetes Deployment 副本数从 50 缩容到 15基于过去 7 天的流量数据15 个副本已经足够处理峰值流量”“202X-05-22 09:01:00 事件 COST_BUDGET_RESTOREDprod-web 的预计月度成本9800 美元已降至月度预算以下”。有了这个“成本异常时间线”页面云成本管理员可以清晰地看到谁、什么时候、通过什么方式导致了成本异常并且可以验证 Harness CCM 的自动优化规则是否有效——如果自动优化规则没有触发还可以手动生成一个“缩容事件”写入事件日志或者修改自动优化规则后生成一个“规则更新事件”写入事件日志。准备工作环境/工具如果你想跟着本文的内容动手实现一个简化版的 Harness 事件溯源系统你需要准备以下开发环境、软件版本和依赖库开发环境操作系统Windows 10/11、macOS Monterey/Ventura/Sonoma、LinuxUbuntu 20.04/22.04、CentOS 7/8、Debian 11/12 等编程语言Python 3.9因为 Python 3.9 支持类型提示、数据类等特性非常适合编写事件溯源系统的原型数据库事件日志存储MongoDB 6.0因为 MongoDB 是文档型数据库非常适合存储不可变的、半结构化的事件数据而且 MongoDB 支持按时间顺序排序、按实体 ID 检索、按事件类型过滤完全满足事件溯源的存储与检索需求当前状态快照存储可选但推荐用于优化重放性能Redis 7.0因为 Redis 是内存型数据库读写速度非常快非常适合存储实体的当前状态快照而且 Redis 支持 TTLTime To Live可以定期清理旧的状态快照消息队列可选但推荐用于结合 CQRS 模式使用Kafka 3.5因为 Kafka 是分布式、高吞吐、持久化的消息队列非常适合作为事件溯源系统的“事件总线Event Bus”而且 Kafka 支持按主题Topic、分区Partition、偏移量Offset存储和检索事件完全满足事件溯源的“按时间顺序、按实体分区”的需求依赖库我们需要安装以下 Python 依赖库pymongo用于连接和操作 MongoDB 数据库redis用于连接和操作 Redis 数据库kafka-python用于连接和操作 Kafka 消息队列dataclasses用于定义不可变的事件类和实体状态类Python 3.7 已经内置不需要额外安装但可以使用dataclasses-json库来简化序列化/反序列化dataclasses-json用于将数据类序列化为 JSON 字符串或者将 JSON 字符串反序列化为数据类python-dotenv用于从.env文件中加载环境变量比如 MongoDB 的连接字符串、Redis 的连接地址、Kafka 的 Bootstrap Servers 等pytest用于编写单元测试和集成测试可选但推荐用于保证事件溯源系统的质量你可以使用pip命令一次性安装所有依赖库pipinstallpymongo redis kafka-python dataclasses-json python-dotenv pytest基础知识在深入学习 Harness 中的事件溯源之前你需要具备以下前置知识1. 什么是事件溯源Event Sourcing事件溯源是一种以“事件”为核心的架构模式由 Martin Fowler 在 2005 年的一篇博客文章《Event Sourcing》中首次提出后来 Greg Young 对事件溯源的落地和推广做出了很大的贡献尤其是结合 CQRS 模式和领域驱动设计DDD使用。事件溯源的核心思想可以用一句话概括“所有对系统状态产生影响的操作都以不可变事件的形式按时间顺序记录下来当前状态由这些事件的重放得到。”2. 什么是不可变事件Immutable Event不可变事件是事件溯源的核心概念之一它具有以下几个关键特征不可修改一旦事件被写入事件日志就永远不能被修改或删除这是事件溯源保证审计合规和数据一致性的关键。按时间顺序事件必须按发生的时间顺序写入事件日志通常使用全局递增的序列号、UUIDv1带时间戳的 UUID、或者数据库的自增 ID 来保证时间顺序。半结构化事件通常包含一些固定的字段比如事件 ID、实体 ID、事件类型、发生时间戳、触发用户/系统、因果关系 ID 等以及一些与事件类型相关的可变字段比如 Feature Flag 变更事件会包含“变更前的状态”“变更后的状态”流水线构建失败事件会包含“错误信息”“失败步骤”等。唯一标识每个事件都有一个全局唯一的 ID用于标识该事件通常使用 UUIDv4 或者 MongoDB 的 ObjectId。3. 什么是事件日志Event Log事件日志是事件溯源系统的唯一真相源Single Source of Truth它是一个按时间顺序存储所有不可变事件的持久化存储系统。事件日志的关键特征包括不可变只能追加Append-Only写入事件不能修改或删除已写入的事件除非是为了合规性要求的“数据擦除Data Erasure”但这通常需要特殊的处理比如用“删除标记事件”代替真正的删除或者使用加密技术将事件的数据加密然后删除加密密钥。高可用因为事件日志是唯一的真相源所以它必须是高可用的通常使用分布式存储系统比如 MongoDB 副本集、Kafka 集群、AWS S3 等。高吞吐因为 Harness 每天要处理数百万到数千万级别的事件所以事件日志必须是高吞吐的通常使用支持批量写入的分布式存储系统比如 Kafka 集群。可检索事件日志必须支持按实体 ID、事件类型、时间范围、触发用户/系统等维度快速检索事件通常使用数据库的索引或者搜索引擎比如 Elasticsearch来实现。4. 什么是状态重放State Replay状态重放是事件溯源系统获取实体当前状态的核心方法它的过程是从事件日志中检索出该实体相关的所有历史事件按时间顺序排序。初始化该实体的空状态Empty State比如 Feature Flag 的空状态是“未创建”流水线的空状态是“不存在”。按时间顺序逐个重放Replay这些历史事件每个事件都会更新实体的状态比如 Feature Flag 创建事件会将状态从“未创建”更新为“已创建状态为 OFF”Feature Flag 变更事件会将状态从“已创建状态为 OFF”更新为“已创建状态为 内部团队 10% 流量”。重放完所有历史事件后得到的状态就是该实体的当前状态。5. 什么是状态快照State Snapshot状态快照是事件溯源系统优化状态重放性能的关键技术它的核心思想是定期或在特定条件下将实体的当前状态保存到一个快照存储系统中这样当需要获取该实体的当前状态时只需要重放快照之后的所有历史事件而不需要重放该实体相关的所有历史事件如果一个实体有 10000 个历史事件快照每 1000 个事件保存一次那么只需要重放最多 1000 个事件就能得到当前状态重放性能提升了 10 倍。状态快照的关键特征包括可选但推荐对于历史事件数量较少的实体比如只有几十个历史事件的 Feature Flag可以不使用状态快照但对于历史事件数量较多的实体比如有几百万个历史事件的流水线执行必须使用状态快照否则重放性能会非常差。定期或条件触发状态快照可以定期触发比如每 1000 个事件保存一次或者每小时保存一次也可以条件触发比如当实体的状态发生重大变化时保存一次或者当用户请求获取该实体的当前状态时保存一次。与事件日志关联每个状态快照都必须关联该快照对应的最后一个事件的 ID这样当需要重放状态时只需要从事件日志中检索出该快照对应的最后一个事件之后的所有历史事件。6. 什么是 CQRS 模式CQRSCommand Query Responsibility Segregation命令查询职责分离是一种将系统的“写操作Command”和“读操作Query”分离的架构模式由 Greg Young 首次提出通常与事件溯源和领域驱动设计DDD结合使用。CQRS 的核心思想可以用一句话概括“写操作和读操作使用不同的模型和存储系统。”写操作Command负责修改系统的状态使用领域模型Domain Model和事件日志作为存储系统写操作的结果是生成一个或多个不可变事件写入事件日志。读操作Query负责查询系统的状态使用查询模型Query Model和查询存储系统Query Store作为存储系统查询模型由事件日志中的事件重放或异步更新得到查询存储系统通常是数据库的索引、搜索引擎比如 Elasticsearch、或者数据仓库比如 Snowflake。CQRS 模式为什么要和事件溯源结合使用因为事件溯源系统的写操作非常高效只能追加写入事件日志不需要修改或删除已有的数据但读操作相对较慢需要重放历史事件才能得到当前状态——而 CQRS 模式正好可以解决这个问题将读操作分离出来使用专门的查询模型和查询存储系统让读操作也变得非常高效。7. 什么是领域驱动设计DDD领域驱动设计Domain-Driven Design简称 DDD是一种以“领域”为核心的软件开发方法由 Eric Evans 在 2003 年的著作《领域驱动设计软件核心复杂性应对之道》中首次提出。DDD 的核心概念包括领域Domain软件系统要解决的业务问题的范围。子域Subdomain将大的领域拆分成多个小的、相对独立的子领域比如 Harness 的领域可以拆分成 CI/CD 子域、Feature Flag 子域、Cloud Cost Management 子域等。限界上下文Bounded Context每个子域对应的软件系统的边界在这个边界内领域模型的概念是统一的、明确的比如 Harness 的 CI/CD 限界上下文内的“流水线”概念和 Feature Flag 限界上下文内的“流水线”概念是不同的。聚合Aggregate限界上下文内的一组相关的领域对象的集合由一个“聚合根Aggregate Root”来统一管理聚合根是聚合内唯一可以被外部直接访问的对象其他对象只能通过聚合根来访问。实体Entity具有唯一标识的领域对象其标识不会随着时间的推移而改变比如 Harness 的 Feature Flag 是一个实体其唯一标识是 Flag ID流水线是一个实体其唯一标识是 Pipeline IDEC2 实例是一个实体其唯一标识是 Instance ID。值对象Value Object没有唯一标识的领域对象其值由其属性决定比如 Harness 的 Feature Flag 规则是一个值对象其值由“目标用户群体”“流量百分比”“生效时间范围”等属性决定如果两个规则的所有属性都相同那么这两个规则就是相等的。领域事件Domain Event限界上下文内发生的、对业务有意义的事情也就是事件溯源中的“不可变事件”比如 Harness 的 Feature Flag 创建事件、Feature Flag 变更事件、流水线启动事件、流水线构建失败事件等。事件溯源和领域驱动设计结合使用时聚合根就是事件溯源中的“实体”领域事件就是事件溯源中的“不可变事件”——这样事件溯源系统就可以完全基于领域模型来构建更加符合业务需求也更加容易维护和扩展。核心概念与问题背景在上一章的准备工作中我们已经简单介绍了事件溯源、不可变事件、事件日志、状态重放、状态快照、CQRS 模式、领域驱动设计等前置知识。在这一章中我们将深入讲解 Harness 中的事件溯源的核心概念并且分析 Harness 为什么选择事件溯源作为核心架构模式之一也就是 Harness 中的事件溯源的问题背景。Harness 中的事件溯源的核心概念Harness 中的事件溯源并不是一个简单的“理论实现”而是一个针对全链路 DevOps 平台的特点进行了大量优化和扩展的工程化实现。因此Harness 中的事件溯源除了包含事件溯源的通用核心概念比如不可变事件、事件日志、状态重放、状态快照等之外还包含一些Harness 特有的核心概念比如多租户事件隔离、跨微服务事件因果关系追踪、事件版本兼容性管理、事件的“命令式重放”与“声明式重放”、事件的“审计重放”与“业务重放”等。在这一节中我们将逐一讲解 Harness 中的事件溯源的所有核心概念包括通用核心概念和 Harness 特有的核心概念。通用核心概念1. 聚合根Aggregate Root在 Harness 中聚合根是事件溯源的基本单位——也就是说事件是按“聚合根 ID”来组织和存储的状态重放也是按“聚合根”来进行的。Harness 中的典型聚合根包括Pipeline流水线唯一标识是pipelineId属于 CI/CD 限界上下文。PipelineExecution流水线执行唯一标识是executionId属于 CI/CD 限界上下文。FeatureFlag功能开关唯一标识是flagId属于 Feature Flag 限界上下文。FeatureFlagEnvironment功能开关环境配置唯一标识是flagEnvironmentId属于 Feature Flag 限界上下文。CloudResource云资源唯一标识是cloudResourceId属于 Cloud Cost Management 限界上下文。CloudBudget云预算唯一标识是budgetId属于 Cloud Cost Management 限界上下文。每个聚合根都有自己的领域模型Domain Model领域模型定义了聚合根的当前状态结构和状态更新规则也就是每个事件如何更新聚合根的状态。2. 领域事件Domain Event在 Harness 中领域事件是对聚合根状态产生影响的、对业务有意义的事情——它是不可变的按时间顺序写入事件日志是事件溯源系统的唯一真相源。Harness 中的典型领域事件包括PipelineCreated流水线创建事件当用户创建一个新的流水线时触发包含pipelineId、pipelineName、createdBy、createdAt等字段。PipelineUpdated流水线更新事件当用户更新一个已有的流水线时触发包含pipelineId、oldPipelineConfig、newPipelineConfig、updatedBy、updatedAt等字段。PipelineExecutionTriggered流水线执行触发事件当用户点击“启动流水线”、或者 Git 提交触发流水线、或者定时任务触发流水线时触发包含executionId、pipelineId、triggerType、triggeredBy、triggeredAt等字段。PipelineExecutionStepStarted流水线执行步骤启动事件当流水线执行的某个步骤启动时触发包含executionId、stepId、stepName、startedAt等字段。PipelineExecutionStepSucceeded流水线执行步骤成功事件当流水线执行的某个步骤成功完成时触发包含executionId、stepId、stepName、succeededAt、stepOutput等字段。PipelineExecutionStepFailed流水线执行步骤失败事件当流水线执行的某个步骤失败时触发包含executionId、stepId、stepName、failedAt、errorMessage、errorStackTrace等字段。FeatureFlagCreated功能开关创建事件当用户创建一个新的功能开关时触发包含flagId、flagName、flagType、createdBy、createdAt等字段。FeatureFlagEnvironmentUpdated功能开关环境配置更新事件当用户更新某个环境下的功能开关配置时触发包含flagEnvironmentId、flagId、environmentId、oldFlagConfig、newFlagConfig、updatedBy、updatedAt等字段。CloudResourceDiscovered云资源发现事件当云成本扫描器发现一个新的云资源时触发包含cloudResourceId、cloudProvider、resourceType、resourceId、discoveredAt等字段。CloudBudgetExceeded云预算超支事件当某个云资源组的成本超过预算时触发包含budgetId、resourceGroupId、currentCost、budgetAmount、exceededAt等字段。每个领域事件都必须包含以下固定字段Harness 称为“事件元数据Event Metadata”eventId事件的全局唯一标识通常使用 UUIDv4 或者 MongoDB 的 ObjectId。aggregateType聚合根的类型比如Pipeline、PipelineExecution、FeatureFlag等。aggregateId聚合根的唯一标识比如pipelineId、executionId、flagId等。eventType事件的类型比如PipelineCreated、PipelineExecutionStepFailed、FeatureFlagEnvironmentUpdated等。version聚合根的版本号每次聚合根的状态发生变化也就是每次写入一个事件版本号都会递增 1版本号是保证聚合根状态一致性的关键我们会在后面的章节中详细讲解。timestamp事件发生的时间戳通常使用 UTC 时间精确到毫秒或者微秒取决于业务需求。tenantId租户的唯一标识Harness 是多租户平台这个字段非常重要我们会在后面的章节中详细讲解。userId触发事件的用户的唯一标识如果是系统触发的事件这个字段可以为空或者设置为一个特殊的“系统用户 ID”。systemId触发事件的系统的唯一标识如果是用户触发的事件这个字段可以为空或者设置为一个特殊的“用户系统 ID”。causationId导致该事件发生的“原因事件”的 ID也就是该事件是由哪个事件触发的我们会在后面的章节中详细讲解“跨微服务事件因果关系追踪”。correlationId关联该事件和其他相关事件的“关联 ID”比如同一个流水线执行的所有事件都有相同的correlationId同一个 Feature Flag 变更的所有事件都有相同的correlationId我们会在后面的章节中详细讲解。每个领域事件还可以包含一些可变字段Harness 称为“事件数据Event Data”这些字段的内容取决于事件的类型比如PipelineExecutionStepFailed事件会包含errorMessage、errorStackTrace等字段FeatureFlagEnvironmentUpdated事件会包含oldFlagConfig、newFlagConfig等字段。3. 事件日志Event Log在 Harness 中事件日志是唯一的真相源——所有的写操作命令都会生成一个或多个领域事件写入事件日志所有的读操作查询、审计合规、问题定位、状态一致性保证都基于这个事件日志。Harness 的事件日志不是一个单一的存储系统而是一个由多个存储系统组成的分层存储架构我们会在后面的章节中详细讲解 Harness 的事件日志的架构设计包括事件持久化层Event Persistence Layer用于永久存储所有的领域事件必须是高可用、高吞吐、不可变的 Append-Only 存储系统Harness 早期使用 MongoDB 副本集作为事件持久化层后来为了支持更高的吞吐和更好的跨微服务事件总线功能迁移到了 Kafka 集群 MongoDB 副本集的组合Kafka 集群作为“事件总线”和“短期事件存储”MongoDB 副本集作为“长期事件存储”和“事件索引层”。事件索引层Event Index Layer用于快速检索事件支持按tenantId、aggregateType、aggregateId、eventType、timestamp、correlationId、causationId等维度快速检索事件Harness 使用 MongoDB 副本集的索引功能作为事件索引层后来为了支持更复杂的全文检索和跨租户/跨聚合根的事件分析引入了 Elasticsearch 作为“事件分析层”。事件分析层Event Analysis Layer用于对事件进行复杂的分析和统计比如“某个租户的某个 Feature Flag 在过去 30 天内的所有变更事件”“某个租户的某个流水线在过去 7 天内的所有失败事件的失败原因统计”等Harness 使用 Elasticsearch 作为事件分析层后来为了支持更大规模的事件分析引入了 Snowflake 作为“事件数据仓库”。4. 状态重放State Replay在 Harness 中状态重放是获取聚合根当前状态的核心方法——也就是说Harness 不会直接存储聚合根的当前状态除非是为了优化重放性能而使用状态快照而是每次需要获取聚合根的当前状态时从事件日志中检索出该聚合根相关的所有历史事件按时间顺序逐个重放这些事件最终得到聚合根的当前状态。Harness 中的状态重放分为两种类型业务重放Business Replay用于获取聚合根的当前业务状态也就是对用户可见的状态重放时会应用所有的“业务规则”比如 Feature Flag 的规则引擎、流水线的执行规则等。审计重放Audit Replay用于获取聚合根的完整历史状态变迁链也就是对审计人员可见的状态重放时不会应用任何“业务规则”而是会保留所有的“原始事件”和“原始状态变化”。Harness 中的状态重放还支持时间旅行Time Travel——也就是说你可以指定一个“目标时间戳”然后重放该目标时间戳之前的所有历史事件最终得到聚合根在该目标时间戳时的状态比如你可以查询“某个 Feature Flag 在 202X-05-20 15:00:00 时的状态”或者“某个流水线在 202X-05-21 09:03:00 时的状态”。5. 状态快照State Snapshot在 Harness 中状态快照是优化状态重放性能的关键技术——因为如果一个聚合根有几百万个历史事件每次都重放所有历史事件的话重放性能会非常差用户体验也会非常糟糕。Harness 中的状态快照分为两种类型定期快照Periodic Snapshot按固定的时间间隔或者固定的事件数量间隔保存状态快照比如 Harness 对PipelineExecution聚合根的定期快照策略是“每 100 个事件保存一次或者每 5 分钟保存一次取两者中先满足的条件”。按需快照On-Demand Snapshot在特定条件下保存状态快照比如当用户第一次查询某个聚合根的当前状态时保存一次或者当聚合根的状态发生重大变化时保存一次或者当聚合根的历史事件数量超过某个阈值时保存一次。Harness 的状态快照存储系统是Redis 集群 MongoDB 副本集的组合Redis 集群用于存储最近的状态快照比如过去 24 小时内的状态快照读写速度非常快可以满足用户的实时查询需求。MongoDB 副本集用于存储所有的历史状态快照持久化保存可以满足用户的时间旅行查询需求和审计需求。每个状态快照都必须包含以下固定字段snapshotId快照的全局唯一标识通常使用 UUIDv4 或者 MongoDB 的 ObjectId。aggregateType聚合根的类型。aggregateId聚合根的唯一标识。version聚合根的版本号也就是该快照对应的最后一个事件的版本号。lastEventId该快照对应的最后一个事件的 ID。lastEventTimestamp该快照对应的最后一个事件的时间戳。snapshotTimestamp快照保存的时间戳。tenantId租户的唯一标识。state聚合根的当前状态通常使用 JSON 格式或者 Protocol Buffers 格式存储。Harness 特有的核心概念1. 多租户事件隔离Multi-Tenant Event IsolationHarness 是一个多租户Multi-Tenant平台——也就是说多个租户不同的公司或组织共享同一个 Harness 平台的基础设施但每个租户的数据是完全隔离的一个租户的用户无法访问另一个租户的数据。多租户事件隔离是 Harness 中的事件溯源的最基本要求之一——因为如果不同租户的事件没有隔离那么一个租户的用户就可以访问另一个租户的事件日志这会导致严重的数据泄露问题也无法满足审计合规要求。Harness 中的多租户事件隔离主要通过以下几种方式实现事件元数据中的tenantId字段每个事件都必须包含tenantId字段用于标识该事件属于哪个租户。事件持久化层的租户隔离如果使用MongoDB 副本集作为事件持久化层可以使用数据库级别的租户隔离每个租户有自己的数据库、或者集合级别的租户隔离每个租户有自己的集合、或者文档级别的租户隔离所有租户的事件都存储在同一个集合中但每个事件都有tenantId字段并且在集合上创建tenantId的索引。Harness 早期使用文档级别的租户隔离后来为了更好的性能和隔离性迁移到了集合级别的租户隔离 文档级别的租户隔离的组合每个租户有自己的“事件集合组”每个聚合根类型有自己的集合每个集合上都创建tenantId、aggregateId、version的复合索引。如果使用Kafka 集群作为事件持久化层可以使用主题级别的租户隔离每个租户有自己的主题、或者分区级别的租户隔离每个租户的事件都存储在同一个主题的特定分区中、或者消息级别的租户隔离所有租户的事件都存储在同一个主题中但每个事件的消息头或消息体中都有tenantId字段。Harness 使用主题级别的租户隔离 消息级别的租户隔离的组合每个租户有自己的“事件主题组”每个聚合根类型有自己的主题每个主题上的每个事件的消息头中都有tenantId字段。事件检索时的租户过滤所有的事件检索操作无论是业务重放、审计重放、还是事件分析都必须首先根据tenantId字段进行过滤确保只检索当前租户的事件。访问控制列表ACL除了事件元数据中的tenantId字段和事件持久化层的租户隔离之外Harness 还使用访问控制列表ACL来进一步限制租户内部的用户对事件的访问比如只有管理员用户才能访问所有的事件普通用户只能访问自己触发的事件或者自己所在团队的事件。2. 跨微服务事件因果关系追踪Cross-Microservice Event Causation TrackingHarness 是一个分布式微服务架构——也就是说Harness 由几十个微服务组成比如 Pipeline Service、Feature Flag Service、Cloud Cost Management Service、Git Service、Notification Service 等不同微服务之间通过 RPCgRPC或消息队列Kafka通信。在分布式微服务架构中一个用户操作可能会触发多个微服务的多个事件——比如用户点击“启动流水线”这个操作会触发Pipeline Service生成一个PipelineExecutionTriggered事件。Pipeline Service调用Git Service拉取代码Git Service 生成一个GitCodePulled事件。Pipeline Service调用Feature Flag Service启动灰度测试Feature Flag Service 生成一个FeatureFlagEnvironmentUpdated事件。Pipeline Service调用Notification Service发送流水线启动通知Notification Service 生成一个NotificationSent事件。在这种情况下跨微服务事件因果关系追踪就变得非常重要——因为如果没有因果关系追踪当某个微服务的某个事件出现问题时比如FeatureFlagEnvironmentUpdated事件失败你根本无法知道这个事件是由哪个微服务的哪个事件触发的也就无法定位问题的根本原因。Harness 中的跨微服务事件因果关系追踪主要通过以下两个事件元数据字段实现correlationId关联 ID用于标识同一个用户操作或同一个业务流程触发的所有事件比如用户点击“启动流水线”这个操作触发的所有事件都有相同的correlationId同一个 Feature Flag 变更的审批、生效、通知等所有事件都有相同的correlationId。correlationId通常由第一个触发事件的微服务生成比如 Pipeline Service 生成PipelineExecutionTriggered事件时会生成一个correlationId然后将这个correlationId传递给 Git Service、Feature Flag Service、Notification Service这些微服务生成的事件都会使用这个correlationId。causationId原因 ID用于标识导致该事件发生的直接原因事件比如GitCodePulled事件的causationId是PipelineExecutionTriggered事件的eventIdFeatureFlagEnvironmentUpdated事件的causationId是PipelineExecutionTriggered事件的eventIdNotificationSent事件的causationId是FeatureFlagEnvironmentUpdated事件的eventId。通过correlationId和causationId这两个字段Harness 可以构建出跨微服务的事件因果关系图Event Causality Graph——这个图可以直观地展示出“同一个用户操作或同一个业务流程触发的所有事件之间的因果关系”当某个事件出现问题时只需要沿着因果关系图往上追溯就能很快定位到问题的根本原因。我们可以使用Mermaid 流程图来展示 Harness 中“用户点击启动流水线”这个操作触发的跨微服务事件因果关系图渲染错误:Mermaid 渲染失败: Parse error on line 5: ...elationIdabc123

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2511784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…