图的拓扑排序管理 Go 服务启动时的组件初始化顺序

在构建复杂的 Go 应用程序时,服务的启动过程往往涉及多个组件的初始化,例如日志、配置、数据库连接、缓存、服务管理器、适配器等等。这些组件之间通常存在着复杂的依赖关系:日志可能需要配置信息,数据库连接可能依赖日志和追踪(trace),服务管理器又可能依赖数据库存储。

如果手动管理这些初始化顺序,不仅容易出错,而且随着项目规模的增长,维护起来会变得异常困难。想象一下,当新的组件加入或现有依赖关系改变时,你不得不小心翼翼地调整启动代码的顺序。这不仅耗时,还可能引入难以发现的启动问题。

拓扑排序是一种对有向无环图(DAG)的顶点进行线性排序的方法,使得对于图中每一条从顶点 A 指向顶点 B 的有向边,A 都出现在 B 之前。这完美契合了组件初始化时的“依赖”关系:如果组件 B 依赖于组件 A,那么 A 必须在 B 之前初始化。

这样做的好处显而易见:

  • 自动化依赖管理: 无需手动维护复杂的 if-else 或顺序调用链。
  • 避免循环依赖: 如果不小心引入了循环依赖(A 依赖 B,B 依赖 A),拓扑排序算法能够立即检测到并报错,防止运行时死锁或错误。
  • 可扩展性强: 增加新的组件及其依赖时,只需修改依赖图的定义,而无需修改核心的初始化逻辑。
  • 错误隔离: 某个组件初始化失败,能清晰地知道是哪个组件在哪个阶段失败了。

通过一个 initial 包来演示这个机制。它包含一个通用的拓扑排序算法和一套组件注册机制。

package initialimport ("context" // 引入 context 包,以支持带 context 的初始化函数"errors""fmt""your_project/internal/model/dao""your_project/internal/pkg/config""your_project/internal/pkg/logger""your_project/internal/pkg/trace""your_project/internal/service/adapter""your_project/internal/service/core"
)// InitializerType 定义了初始化器的类型标识符
type InitializerType string// 定义各种初始化器常量
const (CONFIG  InitializerType = "config"TRACE   InitializerType = "trace"LOGGER  InitializerType = "logger"STORE   InitializerType = "store"MANAGER InitializerType = "manager"ADAPTER InitializerType = "adapter"
)// 定义不同类型的初始化函数或接口,以便于在 initializers 映射中存储
type Initializer interface {Init() error
}
type InitializerFunc func() error
type InitializerFuncWithCtx func(ctx context.Context) errorvar (// init_graph 定义了初始化器的依赖关系:key 依赖 value(s)// 例如: TRACE 依赖 CONFIG,意味着 CONFIG 必须在 TRACE 之前初始化。init_graph = map[InitializerType][]InitializerType{TRACE:   {CONFIG},  // TRACE 模块依赖 CONFIG 模块CONFIG:  {LOGGER},  // CONFIG 模块依赖 LOGGER 模块LOGGER:  {},        // LOGGER 模块没有外部依赖STORE:   {TRACE, CONFIG, LOGGER}, // STORE 模块依赖 TRACE, CONFIG, LOGGERMANAGER: {STORE},   // MANAGER 模块依赖 STORE 模块ADAPTER: {MANAGER}, // ADAPTER 模块依赖 MANAGER 模块}// initializers 映射了初始化器类型到具体的初始化函数或接口实现initializers = map[InitializerType]interface{}{LOGGER:  InitializerFunc(logger.Init),      // 假设 logger.Init 是 func() errorCONFIG:  InitializerFunc(config.Init),      // 假设 config.Init 是 func() errorTRACE:   InitializerFunc(trace.Init),       // 假设 trace.Init 是 func() errorSTORE:   InitializerFunc(dao.Init),         // 假设 dao.Init 是 func() errorMANAGER: InitializerFunc(core.InitManager), // 假设 core.InitManager 是 func() errorADAPTER: InitializerFunc(adapter.Init),     // 假设 adapter.Init 是 func() error}
)// Run 执行所有组件的初始化,并按照依赖关系进行排序
func Run() error {// 1. 生成初始化顺序init_order, err := topoSort(init_graph)if err != nil {return fmt.Errorf("failed to generate initialization order: %w", err)}// 2. 按照拓扑排序的顺序逐一初始化组件for _, it := range init_order {if i, ok := initializers[it]; ok {switch initializer := i.(type) {case Initializer:if err := initializer.Init(); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}case InitializerFunc:if err := initializer(); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}case InitializerFuncWithCtx:// 对于需要 context 的初始化函数,这里传入 nil context,实际应用中可能需要更具体的 contextif err := initializer(nil); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}default:return fmt.Errorf("unknown initializer type for %s", it)}}}return nil
}// topoSort 执行初始化依赖图的拓扑排序 (Kahn's Algorithm)
// graph: key 依赖 value(s),表示如果 key 存在,它依赖 value(s) 中的所有节点。
// 换句话说,在依赖图中,存在从 value -> key 的边。
func topoSort(graph map[InitializerType][]InitializerType) ([]InitializerType, error) {// 1. 构建邻接表 (adjList) 和计算入度 (inDegree)// adjList[node]: 存储 node 指向的所有节点 (即 node 是谁的依赖)// inDegree[node]: 存储有多少条边指向 node (即 node 被多少个其他节点依赖)adjList := make(map[InitializerType][]InitializerType)inDegree := make(map[InitializerType]int)// 初始化所有在依赖图中出现的节点for node := range graph {// 确保所有作为依赖出现但未作为主键的节点也被初始化if _, exists := adjList[node]; !exists {adjList[node] = []InitializerType{}}if _, exists := inDegree[node]; !exists {inDegree[node] = 0}}for _, dependencies := range graph {for _, dep := range dependencies {if _, exists := adjList[dep]; !exists {adjList[dep] = []InitializerType{}}if _, exists := inDegree[dep]; !exists {inDegree[dep] = 0}}}// 填充 adjList 和计算入度for dependent, dependencies := range graph {for _, dep := range dependencies {// 如果 dependent 依赖 dep (即 dep -> dependent 有一条边)// adjList[dep] 存储 dep 依赖的节点// inDegree[dependent]++adjList[dep] = append(adjList[dep], dependent)inDegree[dependent]++}}// 2. 找到所有入度为 0 的节点,放入队列var queue []InitializerTypefor node, degree := range inDegree {if degree == 0 {queue = append(queue, node)}}// 3. 循环处理队列中的节点var topoOrder []InitializerTypeprocessedNodesCount := 0 // 记录已处理的节点数量for len(queue) > 0 {node := queue[0] // 取出队列头部节点queue = queue[1:]topoOrder = append(topoOrder, node) // 将节点加入拓扑排序结果processedNodesCount++// 遍历当前节点所指向的所有邻居(即依赖它的节点),减少它们的入度// 根据 `adjList` 的构建方式,adjList[node] 存储的是 node 依赖的节点// 它们在图中是以 node 为起点的边所指向的节点for _, neighbor := range adjList[node] {inDegree[neighbor]--if inDegree[neighbor] == 0 {queue = append(queue, neighbor)}}}// 4. 检查是否存在循环依赖// 如果拓扑排序结果的节点数量不等于图中所有节点的数量,则存在循环if processedNodesCount != len(inDegree) {return nil, errors.New("cycle detected in graph")}return topoOrder, nil
}

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

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

相关文章

【物理重建】SPLART:基于3D高斯泼溅的铰链估计与部件级重建

标题:《SPLART: Articulation Estimation and Part-Level Reconstruction with 3D Gaussian Splatting》 项目:https://github.com/ripl/splart 文章目录 摘要一、引言二、相关工作2.1 数据驱动的铰链学习2.2 物体重建的表征方法2.3 铰链物体重建 三、方…

vscode中vue自定义组件的标签失去特殊颜色高亮

遇到的问题 最近接触了一个历史遗留项目时,我遭遇了堪称"史诗级屎山"的代码结构——各种命名混乱的自定义组件和原生HTML标签混杂在一起,视觉上完全无法区分。这让我突然想起,之前在使用vue或者其他框架开发的时候,只要…

【Dify精讲】第19章:开源贡献指南

今天,让我们深入 Dify 的开源贡献体系,看看这个项目是如何在短短时间内聚集起一个活跃的开发者社区的。作为想要参与 Dify 开发的你,这一章将是你的实战指南。 一、代码贡献流程:从想法到合并的完整路径 1.1 贡献前的准备工作 …

Web攻防-CSRF跨站请求伪造Referer同源Token校验复用删除置空联动上传或XSS

知识点: 1、Web攻防-CSRF-原理&检测&利用&防御 2、Web攻防-CSRF-防御-Referer策略隐患 3、Web攻防-CSRF-防御-Token校验策略隐患 一、演示案例-WEB攻防-CSRF利用-原理&构造 CSRF 测试功能点 删除帐户 更改电子邮件 如果不需要旧密码,请…

Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights

“拖拽式大模型定制”(Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights)。 核心问题: 现在的大模型(比如GPT-4)很厉害,但想让它们专门干好某个特定任务(比如解数学题、写代码)&am…

抖音视频怎么去掉抖音号水印保存

随着抖音成为短视频平台的领军者,越来越多的人喜欢在上面拍摄、观看和分享各种创意内容。对于用户来说,下载抖音视频并去除水印保存,以便后续使用或分享成为了一种常见需求。抖音号水印的存在虽然能帮助平台追溯视频源头,但也让许…

【RAG技术(1)】大模型为什么需要RAG

文章目录 为什么需要RAG?RAG的工作原理关键的Embedding技术 RAG vs 模型微调:选择的核心逻辑RAG的关键挑战与解决思路1. 检索质量决定一切2. 上下文长度限制 实际应用场景分析企业知识问答技术文档助手法律咨询系统 构建RAG系统的关键步骤总结 为什么需要…

JS红宝书笔记 - 8.1 理解对象

对象就是一组没有特定顺序的值,对象的每个属性或者方法都可由一个名称来标识,这个名称映射到一个值。可以把对象想象成一张散列表,其中的内容就是一组名值对,值可以是数据或者函数 创建自定义对象的通常方式是创建Object的一个新…

Meson介绍及编译Glib库

一.概述 1.Meson 的简介 Meson(The Meson Build System)是个项目构建系统,类似的构建系统有 Makefile、CMake、automake …。 Meson 是一个由 Python 实现的开源项目,其思想是,开发人员花费在构建调试上的每一秒都是…

Qt元对象系统实践指南:从入门到应用

目录 摘要 元对象系统核心概念 项目示例:动态UI配置工具 元对象系统在项目中的应用 1. 信号与槽机制 2. 动态属性系统 3. 运行时反射能力 4. 属性绑定与响应 实际项目应用场景 动态UI配置 对象序列化 插件系统 性能优化建议 结论 参考资料 摘要 本文…

Kafka 与其他 MQ 的对比分析:RabbitMQ/RocketMQ 选型指南(一)

消息队列简介 在当今的分布式系统架构中,消息队列(Message Queue,MQ)扮演着举足轻重的角色。随着业务规模的不断扩大和系统复杂度的日益提升,各个组件之间的通信和协同变得愈发关键 。消息队列作为一种异步的通信机制…

[创业之路-441]:行业 - 互联网+移动互联网和大数据时代的100个预言:技术个性、商业变革、社会重构、文化娱乐、环境、教育、健康医疗、未来生活方式

目录 一、技术革新 二、商业变革 三、社会重构 四、文化与娱乐 六、环境与可持续发展 七、教育与知识传播 八、健康与医疗 九、伦理与法律 十、未来生活方式 十一、终极预言 结语 在移动互联网和大数据时代,技术革新正以前所未有的速度重塑社会、经济与文…

基于STM32单片机WIFI无线APP控灯亮度灭设计

基于STM32单片机控灯设计 (程序+原理图+设计报告) 功能介绍 具体功能: 本设计由STM32F103C8T6单片机核心电路两位白色高亮LED灯电路WIFI模块ESP8266电路电源电路组成。 1、stm32实时监测wifi数据,解析数…

学会C++中的vector的基本操作

vector 是 C 标准库中的一个动态数组类,它可以在运行时自动调整大小,非常适合用于处理大小不确定的集合。下面是 vector 的常见用法示例,帮助你更好地理解如何使用它。 注意:所有用数组完成的任务都可以用vector完成。 1. 引入头…

AI时代工具:AIGC导航——AI工具集合

大家好!AIGC导航是一个汇集多种AIGC工具的平台,提供了丰富的工具和资源。 工具功能​: 该平台整合了多样的AIGC工具,涵盖了绘画创作、写作辅助以及视频制作等多个领域。绘画工具能够生成高质量的图像作品;写作工具支持从构思到润色的全流程写…

java-SpringBoot框架开发计算器网页端编程练习项目【web版】

今天分享一个使用springboot 写一个 前后端不分离的项目,网页计算器,来熟悉springboot框架的使用。 java版本:8。 springboot:2.6.13 使用的技术是: Java Spring Boot Thymeleaf HTML/CSS/JS 构建的 Web 端简约按钮…

linux操作系统的软件架构分析

一、linux操作系统的层次结构 1.内核的主要功能 1)进程管理 2)内存管理 3)文件系统 4)进程间通信、I/O系统、网络通信协议等 2.系统程序 1)系统接口函数库,比如libc 2)shell程序 3)编译器、编辑…

浅谈Java对象在内存中的存储形式

我们知道计算机以二进制的方式存储数据,以 64 位虚拟机为例,Java 对象在内存中的存储形式为: 开头是 8 个字节的 markword,用于标记对象的状态。(也就是一个 long 型数据的大小。不妨记作对象头里有一个长长的 markwo…

Android 开发问题:Wrong argument type for formatting argument ‘#2‘ in info_message

<string name"info_message">name: %1$s, age: %2$d</string>String str getString(R.string.info_message, "zs");在 Android 开发中&#xff0c;上述代码&#xff0c;出现如下警告信息 Wrong argument type for formatting argument #2 in…

Vue+spring boot前后端分离项目搭建---小白入门

首先&#xff0c;介绍一下软件准备工作 1.vscode 2.maven 3.vue搭建&#xff1a;node.jsyarnvite 一.后端搭建 打开vscode,建立一个springboot项目&#xff0c;参考链接&#xff1a;sping boot项目搭建 建立一个项目&#xff0c;目录结构如下&#xff1a; helloController.java…