在大型单片应用程序中,由于缺乏明确的责任,错误跟踪和监控通常变得无效。本指南通过提出一种通过域注释分配责任的结构化方法来解决这个问题。
为拥有多个团队的大型单体系统设置有效的监控可能具有挑战性。如果没有明确的所有权,错误跟踪就会变得千篇一律,而且经常被忽略。一种解决方案是让值班工程师确定哪个团队应该响应监控警报。然而,更有效的方法是在每个日志和 Datadog 跨度中包含域和团队信息。
为了跟踪哪个团队负责我们应用程序的各个部分,我们使用了一个名为“域注释”的系统。域注释标记了应用程序代码的每个部分,清楚地表明了谁负责什么。这在管理职责方面提供了明确的组织和责任。
域注释提供了一种清晰且有条理的方法来跟踪单体应用程序中的团队职责。通过使用域注释标记代码的各个部分,您可以:
为了确保高效的监控和可追溯性,每个 Web 请求都标记了相应的域信息。这是通过多个组件的协作实现的: DomainProvider
、 DomainSpanService
、 DomainMdcProvider
和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 中单体应用程序的监控。通过实施此策略,您可以增强日志、跨度和指标的可管理性,将监控设置转变为针对特定团队的工具。这提高了责任感和组织性,并有助于在您的应用程序中更有效、更高效地进行故障排除和性能分析。
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 跟踪过滤
通过遵循这些步骤,您可以在单片应用程序中有效地实现域注释,确保改善监控、问责制和整体效率。