在大型单片应用程序中,由于缺乏明确的责任,错误跟踪和监控通常变得无效。本指南通过提出一种通过域注释分配责任的结构化方法来解决这个问题。 为拥有多个团队的大型单体系统设置有效的监控可能具有挑战性。如果没有明确的所有权,错误跟踪就会变得千篇一律,而且经常被忽略。一种解决方案是让值班工程师确定哪个团队应该响应监控警报。然而,更有效的方法是在每个日志和 Datadog 跨度中包含域和团队信息。 理解域注释 为了跟踪哪个团队负责我们应用程序的各个部分,我们使用了一个名为“域注释”的系统。域注释标记了应用程序代码的每个部分,清楚地表明了谁负责什么。这在管理职责方面提供了明确的组织和责任。 使用域注释的好处 域注释提供了一种清晰且有条理的方法来跟踪单体应用程序中的团队职责。通过使用域注释标记代码的各个部分,您可以: :根据特定标准(例如团队责任)过滤日志和跟踪,从而快速识别和解决问题。 简化日志和跟踪管理 :无缝适应团队职责的变化,因为注释与域名而不是团队名称相关。 保持准确的跟踪 :明确规定哪个团队负责每个领域,改进组织和有针对性的监控。 加强问责 :通过提供精确的责任制和提高整体效率来促进更好的监测实践。 提高监测效率 域注释处理 为了确保高效的监控和可追溯性,每个 Web 请求都标记了相应的域信息。这是通过多个组件的协作实现的: 、 、 和 。 DomainProvider DomainSpanService DomainMdcProvider DomainHandlerInterceptor 以下是下图所示流程的高层概述: 关键部件说明 :标识与特定处理程序方法或 bean 关联的域。它有助于在 AOP(面向方面编程)和 MVC(模型-视图-控制器)调用中查找域注释。 DomainProvider :向 span(跟踪系统中的工作单元)添加域标签。此服务确保每个 span 都标记有适当的域信息。 DomainSpanService :管理 MDC(映射诊断上下文)内的域标签,这是日志框架的一项功能,允许使用上下文信息标记日志条目。 DomainMdcProvider :拦截Web请求,确保每个请求都标记有适当的域信息,以便更好地监控和可追溯性。 DomainHandlerInterceptor 这些组件的详细实现将封装在一个共享库中,为大型单片应用程序中的标记和监控 Web 请求提供可重用的解决方案。 理清谁拥有哪些代码 使用域注释,在类级别定义所有权非常简单。通过将顶级注释应用于主类,所有权将向下传播到这些类中的所有详细资源。每个团队都可以使用适当的域注释标记他们拥有的类,从而确保清晰度和责任感,而无需标记每个方法。 如果多个团队拥有一个类中的代码,并且立即重构不合适,则可以使用不同的域注释标记各个方法,这些注释优先于类级注释。这样可以将特定方法分配给不同的团队,从而提供灵活性而不会使整体结构复杂化。 解决注释不支持的情况 虽然域注释非常有用,但也有极少数情况下无法使用它们。例如,我们在 Quartz 作业创建中遇到了问题,由于 Quartz 的 AOP 逻辑与用于域注释的 AOP 逻辑之间存在冲突,因此无法与域注释无缝协作。 对于无法直接注释的作业和流程,我们在作业实现中直接使用了 DomainTagsService。这种方法使我们能够在作业的执行逻辑中手动添加域标签。 下面是我们如何将 DomainTagsService 集成到 Quartz 作业中的示例: final override fun execute(context: JobExecutionContext) { domainTagsService.invoke(domain) { withLoggedExecutionDetails(context, ::doExecute) } } 利用人工智能服务提高监控和可视性 虽然为每个团队提供单独的服务在监控和所有权方面具有显著优势,但拆分单体需要高昂的成本和精力,以及潜在的额外开发费用。考虑到将单体拆分为模块后使用 Gradle 缩短构建时间的可能性,在许多情况下,维护单一存储库可能是最有效的解决方案。 引入人工服务 为了简化在 Datadog 中监控每个团队的活动,您可以为不同团队的范围分配人工服务名称。这种方法可确保每个团队在 Datadog 的监控工具中都有自己的专用部分。如果您要管理许多服务,使用人工服务名称可能会造成混淆,但使用有限数量的后端服务就可以管理。在这些人工服务名称中添加前缀有助于保持 Datadog 设置的条理性和清晰度,从而更容易区分不同的团队及其职责。 使用图表而不是截图??在这里使用 worker/webapp 毫无意义 为什么不对日志使用人工服务? 使用人工服务名称作为日志可能会造成混淆,因为相同的日志条目可能会出现在不同的服务下。 例如,考虑使用相同身份验证服务的两个端点。如果这些端点使用不同的域进行注释,则身份验证逻辑将在不同的人工服务下生成日志。这可能会在浏览日志时造成混淆,因为它们出现在多个服务名称下。为了避免这个问题,最好只将人工服务名称应用于在跟踪中聚合在一起的跨度,这样可以减少混淆 这有意义吗?我认为没有 以下是该问题的直观表示: 在监控和仪表板中使用人工服务 使用人工服务不仅可以让您处理 APM 跟踪,还可以通过 Datadog Metrics 中的服务进行过滤,这些跟踪会长期存储,从而可以跟踪长期内的变化。 监视器示例 下面是 Datadog 中监视器的屏幕截图,它在查询中使用了人工服务名称 : konsus-assets 仪表板示例 下面是 Datadog 中仪表板的屏幕截图,它在过滤器中使用了人工服务名称 : konsus-assets 通过在监控策略中使用虚假服务,您可以增强单体应用程序中每个团队活动的可见性和可追溯性。这种方法简化了创建和维护特定于团队的监视器和仪表板的过程,从而使 Datadog 中的监控更有效、更有条理。 包起来 域注释提供了一种简单的方法来简化 Datadog 中单体应用程序的监控。通过实施此策略,您可以增强日志、跨度和指标的可管理性,将监控设置转变为针对特定团队的工具。这提高了责任感和组织性,并有助于在您的应用程序中更有效、更高效地进行故障排除和性能分析。 关键要点 :通过使用域注释对代码的各个部分进行注释,您可以明确定义哪个团队负责每个域。这有助于更好地组织和有针对性的监控。 增强所有权和责任感 :域注释允许您根据特定标准(例如团队责任)过滤日志和跟踪,从而快速识别和解决问题。 改进的日志和跟踪管理 :对跨度(而非日志)使用人工服务名称可确保日志保持清晰且可追溯到其真实来源,从而避免混淆。 人工服务的灵活性 :对于无法直接应用注释的情况,例如某些作业执行框架(如 Quartz),在作业实现中直接使用 等服务可确保仍可维护特定于域的监控。 克服集成挑战 DomainTagsService 使用域注释的分步方法: 定义领域和团队 这将随着 lib 而改变!!! 在您的应用程序中创建代表不同域和团队的枚举: 是一个可以应用于类或函数的注释,用特定的域值标记它们。 @Domain 是一个枚举,代表不同的域,每个域与一个团队相关联。 DomainValue 是一个枚举,代表从事该应用程序的各个团队。 Team @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) annotation class Domain(val value: DomainValue) enum class DomainValue(val team: Team) { USER_MANAGEMENT(Team.TEAM_A), PAYMENT_PROCESSING(Team.TEAM_B), NOTIFICATIONS(Team.TEAM_C) } enum class Team { TEAM_A, TEAM_B, TEAM_C } 注释类(如有必要,注释方法) @Domain(DomainValue.USER_MANAGEMENT) class UserService { @Domain(DomainValue.PAYMENT_PROCESSING) fun processPayment() { ... } } 处理不受支持的案例 对于不能直接注解的情况,直接使用 来包装逻辑 DomainTagsService fun executeNotSupportedByAnnotationsLogic() { domainTagsService.invoke(domain) { executeLogic() } } 使用 Datadog 监控 使用人工服务过滤器进行监视器、仪表板和 APM 跟踪过滤 通过遵循这些步骤,您可以在单片应用程序中有效地实现域注释,确保改善监控、问责制和整体效率。 感谢您阅读这篇文章!