[{"data":1,"prerenderedAt":3629},["ShallowReactive",2],{"search-docs":3,"doc-\u002Fproject\u002Frocket-leaf\u002Fclient-manager":886},[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,159,162,165,169,172,175,178,182,186,190,194,198,202,206,210,214,218,222,226,230,234,238,242,246,250,254,258,262,266,269,273,277,281,285,288,291,294,298,301,304,307,310,313,316,319,322,325,329,332,336,340,344,348,352,356,359,362,365,368,371,374,377,380,383,386,389,393,396,399,402,405,408,411,414,417,420,424,428,432,435,438,442,446,450,454,458,462,466,470,474,477,480,483,487,491,494,497,500,504,507,511,515,518,521,524,527,530,533,536,539,542,545,548,551,554,557,560,563,566,569,572,575,579,583,587,591,595,599,603,606,610,614,617,620,623,626,629,633,637,640,643,646,649,652,655,658,661,664,667,670,673,676,679,682,685,688,691,694,697,700,703,706,709,712,716,720,724,728,732,736,740,744,748,752,756,760,764,768,772,775,779,783,787,790,793,796,799,802,805,808,811,814,818,822,825,829,832,835,838,841,844,848,851,854,858,862,865,869,873,876,879,882],{"path":5,"title":6,"description":7},"\u002Fabout\u002Fauthor","作者相关","只想纯粹的做一个程序员...",{"path":9,"title":10,"description":11},"\u002Fabout\u002Fjourney","心路历程","",{"path":13,"title":14,"description":15},"\u002Fai\u002Fagent\u002Fframeworks","Agent 框架","主流 Agent 框架：LangChain、LlamaIndex、AutoGen、CrewAI",{"path":17,"title":18,"description":19},"\u002Fai\u002Fagent\u002Fhooks","Agent Hooks 与自动化","Claude Agent 的 Hooks 生命周期、事件类型、典型自动化场景",{"path":21,"title":22,"description":23},"\u002Fai\u002Fagent\u002Fintroduction","AI Agent 概述","AI Agent 核心概念：感知、规划、执行、记忆",{"path":25,"title":26,"description":27},"\u002Fai\u002Fagent\u002Fpractice","Agent 实战","AI Agent 实战：构建自主任务执行系统",{"path":29,"title":30,"description":31},"\u002Fai\u002Fagent\u002Fsdk","Claude Agent SDK 开发","使用 Claude Agent SDK 构建自定义 AI Agent：架构、API、生命周期",{"path":33,"title":34,"description":35},"\u002Fai\u002Fagent\u002Fsubagents","Subagents 子代理","用 Subagents 分解复杂任务、并发执行、隔离上下文",{"path":37,"title":38,"description":39},"\u002Fai\u002Fagent\u002Ftool-use","工具调用","AI Agent 工具调用：Function Calling、Tool Use 原理与实践",{"path":41,"title":42,"description":43},"\u002Fai\u002Ffundamentals\u002Fdeep-learning","深度学习入门","深度学习基础知识：前向传播、反向传播、损失函数、优化器",{"path":45,"title":46,"description":47},"\u002Fai\u002Ffundamentals\u002Fml-basics","机器学习基础","机器学习核心概念：监督学习、无监督学习、强化学习",{"path":49,"title":50,"description":51},"\u002Fai\u002Ffundamentals\u002Fneural-networks","神经网络原理","神经网络架构：CNN、RNN、注意力机制",{"path":53,"title":54,"description":55},"\u002Fai\u002Fgetting-started","AI 学习路线","AI 技术学习路线图，从基础到实战的完整指南",{"path":57,"title":58,"description":59},"\u002Fai\u002Fllm\u002Ffine-tuning","模型微调","大模型微调技术：LoRA、QLoRA、全量微调、RLHF",{"path":61,"title":62,"description":63},"\u002Fai\u002Fllm\u002Fintroduction","大模型概述","大语言模型发展历程、核心能力与主流模型对比",{"path":65,"title":66,"description":67},"\u002Fai\u002Fllm\u002Flocal-deploy","本地部署","大模型本地部署：Ollama、vLLM、llama.cpp",{"path":69,"title":70,"description":71},"\u002Fai\u002Fllm\u002Ftransformer","Transformer 架构","Transformer 架构详解：自注意力机制、位置编码、多头注意力",{"path":73,"title":74,"description":75},"\u002Fai\u002Fmcp\u002Fclient","MCP Client 开发","MCP Client 开发指南：连接、调用、集成",{"path":77,"title":78,"description":79},"\u002Fai\u002Fmcp\u002Fdebugging","MCP 调试与排错","MCP Server 开发与集成过程中的常见问题、日志分析、诊断工具",{"path":81,"title":82,"description":83},"\u002Fai\u002Fmcp\u002Fintroduction","MCP 概述","Model Context Protocol 协议概述：架构、核心概念、应用场景",{"path":85,"title":86,"description":87},"\u002Fai\u002Fmcp\u002Fserver","MCP Server 开发","MCP Server 开发指南：资源、工具、提示词的实现",{"path":89,"title":90,"description":91},"\u002Fai\u002Fmcp\u002Ftools","MCP Tools 深入","深入理解 MCP Tools：与 Resources\u002FPrompts 的差异、Schema 设计、Annotations 与权限控制",{"path":93,"title":94,"description":95},"\u002Fai\u002Fprompt\u002Fadvanced","高级 Prompt 模式","高级 Prompt 设计模式：Tree-of-Thought、自我反思、多轮对话策略",{"path":97,"title":98,"description":99},"\u002Fai\u002Fprompt\u002Fbasics","Prompt 基础","Prompt Engineering 入门：基本概念、角色设定、输出格式控制",{"path":101,"title":102,"description":103},"\u002Fai\u002Fprompt\u002Ftechniques","提示词技巧","常用提示词技巧：Few-shot、Chain-of-Thought、ReAct",{"path":105,"title":106,"description":107},"\u002Fai\u002Frag\u002Fembedding","文本嵌入","文本嵌入模型：Embedding 原理、模型选择、相似度计算",{"path":109,"title":110,"description":111},"\u002Fai\u002Frag\u002Fintroduction","RAG 概述","检索增强生成（RAG）架构原理、优势与应用场景",{"path":113,"title":114,"description":115},"\u002Fai\u002Frag\u002Fpractice","RAG 实战","RAG 应用实战：文档问答系统、知识库搭建",{"path":117,"title":118,"description":119},"\u002Fai\u002Frag\u002Fvector-database","向量数据库","主流向量数据库对比：Milvus、Pinecone、Chroma、Weaviate",{"path":121,"title":122,"description":123},"\u002Fai\u002Fskills\u002Fbest-practices","Skill 最佳实践","编写高质量 Skill 的设计原则、常见陷阱与优化技巧",{"path":125,"title":126,"description":127},"\u002Fai\u002Fskills\u002Fcreating","创建自定义 Skill","从零编写一个可被 Agent 自动发现和调用的 Skill",{"path":129,"title":130,"description":131},"\u002Fai\u002Fskills\u002Fintroduction","Agent Skills 概述","Claude Agent Skills 概念、工作原理、与 Tools\u002FMCP 的区别",{"path":133,"title":134,"description":135},"\u002Fgolang\u002Fadvanced\u002Fconcurrency","Go - 并发深入","深入理解 Go 并发编程的核心机制。",{"path":137,"title":138,"description":139},"\u002Fgolang\u002Fadvanced\u002Fgc","Go - 垃圾回收","理解 Go 的垃圾回收机制，掌握 GC 调优方法。",{"path":141,"title":142,"description":143},"\u002Fgolang\u002Fadvanced\u002Fgmp","Go - GMP 调度模型","GMP 是 Go 运行时调度器的核心模型，理解它对于编写高性能 Go 程序至关重要。",{"path":145,"title":146,"description":147},"\u002Fgolang\u002Fadvanced\u002Fgo-concurrency","Go - 并发编程","Go 的并发是其核心特性之一，通过 Goroutine 和 Channel 实现。",{"path":149,"title":150,"description":151},"\u002Fgolang\u002Fadvanced\u002Fmemory","Go - 内存模型","理解 Go 的内存分配机制和内存模型。",{"path":153,"title":154,"description":155},"\u002Fgolang\u002Fadvanced\u002Fprofiling","Go - 性能分析","掌握 Go 的性能分析工具：pprof、trace、benchmark。",{"path":157,"title":158,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-basic","Go - 基础语法",{"path":160,"title":161,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-composite","Go - 复合类型",{"path":163,"title":164,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-control","Go - 流程控制",{"path":166,"title":167,"description":168},"\u002Fgolang\u002Fcore\u002Fgo-error","Go - 错误处理","Go 使用显式的错误返回值来处理错误，而不是异常机制。",{"path":170,"title":171,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-function","Go - 函数",{"path":173,"title":174,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-install","Go - 环境搭建",{"path":176,"title":177,"description":11},"\u002Fgolang\u002Fcore\u002Fgo-interface","Go - 接口",{"path":179,"title":180,"description":181},"\u002Fgolang\u002Fcore\u002Fgo-module","Go - 包管理","Go Modules 是 Go 1.11 引入的官方依赖管理方案，Go 1.16 后成为默认模式。",{"path":183,"title":184,"description":185},"\u002Fgolang\u002Fdistributed\u002Fgrpc","Go - gRPC","gRPC 是 Google 开发的高性能 RPC 框架，使用 Protocol Buffers 作为序列化协议。",{"path":187,"title":188,"description":189},"\u002Fgolang\u002Fdistributed\u002Fmicroservice","Go - 微服务","微服务架构的核心组件：服务发现、负载均衡、熔断降级。",{"path":191,"title":192,"description":193},"\u002Fgolang\u002Fdistributed\u002Fmq","Go - 消息队列","使用 Go 操作 Kafka 和 RabbitMQ。",{"path":195,"title":196,"description":197},"\u002Fgolang\u002Fdistributed\u002Fredis","Go - Redis","使用 go-redis 操作 Redis，实现缓存、分布式锁等功能。",{"path":199,"title":200,"description":201},"\u002Fgolang\u002Fengineering\u002Fconfig","Go - 配置管理","使用 viper 进行配置管理，支持多种配置格式和配置中心。",{"path":203,"title":204,"description":205},"\u002Fgolang\u002Fengineering\u002Fdocker","Go - Docker 部署","使用 Docker 容器化部署 Go 应用。",{"path":207,"title":208,"description":209},"\u002Fgolang\u002Fengineering\u002Fkubernetes","Go - Kubernetes 部署","在 Kubernetes 上部署和管理 Go 应用。",{"path":211,"title":212,"description":213},"\u002Fgolang\u002Fengineering\u002Flogging","Go - 日志系统","使用 zap 和 logrus 构建高性能结构化日志系统。",{"path":215,"title":216,"description":217},"\u002Fgolang\u002Fengineering\u002Ftesting","Go - 单元测试","Go 内置了强大的测试框架，掌握测试是编写高质量代码的基础。",{"path":219,"title":220,"description":221},"\u002Fgolang\u002Fstdlib\u002Fbufio","bufio","在 Go 语言中，bufio 包提供了带缓冲的 I\u002FO 操作，能够提高读写性能。以下是一些常用的 bufio 包 API 及其详细说明：",{"path":223,"title":224,"description":225},"\u002Fgolang\u002Fstdlib\u002Fcontainer","container","在Go语言标准库中，container 包提供了几种常用的数据结构实现，这些数据结构对于高效地管理和操作数据非常有用。以下是 container 包中主要的数据结构：",{"path":227,"title":228,"description":229},"\u002Fgolang\u002Fstdlib\u002Fcrypto","crypto","在 Go 语言中，crypto 包提供了一组用于加密和解密的功能。以下是一些常用的 crypto 包及其子包的 API 及其详细说明：",{"path":231,"title":232,"description":233},"\u002Fgolang\u002Fstdlib\u002Fencoding-csv","encoding\u002Fcsv","在 Go 语言中，encoding\u002Fcsv 包提供了对 CSV（逗号分隔值）文件进行读写的功能。以下是一些常用的 encoding\u002Fcsv 包的 API 及其详细说明：",{"path":235,"title":236,"description":237},"\u002Fgolang\u002Fstdlib\u002Fencoding-json","encoding\u002Fjson","在 Go 语言中，encoding\u002Fjson 包提供了对 JSON 数据进行编码和解码的功能。以下是一些常用的 encoding\u002Fjson 包的 API 及其详细说明：",{"path":239,"title":240,"description":241},"\u002Fgolang\u002Fstdlib\u002Fencoding-xml","encoding\u002Fxml","在 Go 语言中，encoding\u002Fxml 包提供了对 XML 数据进行编码和解码的功能。以下是一些常用的 encoding\u002Fxml 包的 API 及其详细说明：",{"path":243,"title":244,"description":245},"\u002Fgolang\u002Fstdlib\u002Fflag","flag","在Go语言中，flag 包是用于处理命令行参数的标准库，它提供了一种简单而直接的方式来解析和使用命令行参数。下面是关于 flag 包的一些基本介绍和常用功能：",{"path":247,"title":248,"description":249},"\u002Fgolang\u002Fstdlib\u002Ffmt","fmt","在 Go 语言的标准库中，fmt 包是非常重要的，它提供了处理格式化输入和输出的基本工具。以下是一些 fmt 包内常用的API：",{"path":251,"title":252,"description":253},"\u002Fgolang\u002Fstdlib\u002Fhttp","net\u002Fhttp","在 Go 语言中，net\u002Fhttp 包提供了用于构建 HTTP 客户端和服务器的强大工具。以下是一些常用的 net\u002Fhttp 包的 API 及其详细说明：",{"path":255,"title":256,"description":257},"\u002Fgolang\u002Fstdlib\u002Fio","io","在 Go 语言中，io 包提供了基本的输入输出功能。以下是一些常用的 io 包的 API 及其详细说明：",{"path":259,"title":260,"description":261},"\u002Fgolang\u002Fstdlib\u002Flog","log","在 Go 语言中，log 包提供了简单的日志记录功能。以下是一些常用的 log 包的 API 及其详细说明：",{"path":263,"title":264,"description":265},"\u002Fgolang\u002Fstdlib\u002Fmath","math","在 Go 语言中，math 包提供了基本的数学函数和常量。以下是一些常用的 math 包的 API 及其详细说明：",{"path":267,"title":268,"description":11},"\u002Fgolang\u002Fstdlib\u002Fnet","net",{"path":270,"title":271,"description":272},"\u002Fgolang\u002Fstdlib\u002Fos","os","在Go语言中，os 包是一个非常重要且常用的标准库，它提供了与操作系统交互的功能，包括文件操作、环境变量管理、进程管理等。下面是一些 os 包中常用的功能和API：",{"path":274,"title":275,"description":276},"\u002Fgolang\u002Fstdlib\u002Fsort","order","在 Go 语言中，sort 包提供了对切片和用户定义的集合进行排序的函数。它实现了常见的排序算法，如快速排序（Quicksort）和堆排序（Heapsort），并且为自定义集合提供了接口，使得用户可以根据特定的需求进行排序。",{"path":278,"title":279,"description":280},"\u002Fgolang\u002Fstdlib\u002Fstrconv","strconv","在 Go 语言中，strconv 包提供了字符串和基本数据类型之间的转换函数，例如将整数转换为字符串、字符串转换为整数，以及其他类型之间的转换。这些功能非常有用，特别是在处理用户输入或从外部数据源读取数据时。",{"path":282,"title":283,"description":284},"\u002Fgolang\u002Fstdlib\u002Ftime","time","在 Go 语言中，time 包提供了处理时间和日期的功能。以下是一些常用的 time 包的 API 及其详细说明：",{"path":286,"title":287,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Ferror","Gin - 错误处理",{"path":289,"title":290,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Ffile","Gin - 文件处理",{"path":292,"title":293,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fmiddleware","Gin - 中间件",{"path":295,"title":296,"description":297},"\u002Fgolang\u002Fweb\u002Fgin\u002Fquickstart","Gin - 快速开始","Gin 是目前最流行的 Go Web 框架，以高性能和简洁 API 著称。",{"path":299,"title":300,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Frequest","Gin - 请求处理",{"path":302,"title":303,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fresponse","Gin - 响应处理",{"path":305,"title":306,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Frouter","Gin - 路由",{"path":308,"title":309,"description":11},"\u002Fgolang\u002Fweb\u002Fgin\u002Fvalidation","Gin - 参数校验",{"path":311,"title":312,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fassociation","GORM - 关联关系",{"path":314,"title":315,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fcrud","GORM - CRUD 操作",{"path":317,"title":318,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fmodel","GORM - 模型定义",{"path":320,"title":321,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fperformance","GORM - 日志与性能",{"path":323,"title":324,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fquery","GORM - 高级查询",{"path":326,"title":327,"description":328},"\u002Fgolang\u002Fweb\u002Fgorm\u002Fquickstart","GORM - 快速开始","GORM 是 Go 语言最流行的 ORM 库，功能强大，使用简单。",{"path":330,"title":331,"description":11},"\u002Fgolang\u002Fweb\u002Fgorm\u002Ftransaction","GORM - 事务与 Hook",{"path":333,"title":334,"description":335},"\u002Finterview\u002Fbasic","计算机基础面经","本章节汇总了面试中常见的通用技术概念，不局限于特定语言或数据库，是考察技术内功的关键考点。",{"path":337,"title":338,"description":339},"\u002Finterview\u002Fgolang","Golang 面试题","Go 语言面试高频考点，覆盖基础语法、数据结构、并发编程、内存管理、GC、调度器等核心知识。",{"path":341,"title":342,"description":343},"\u002Finterview\u002Fk8s","Kubernetes 面试题","Kubernetes（K8s）面试高频考点，覆盖架构原理、核心资源、网络存储、调度策略、运维监控等核心知识。",{"path":345,"title":346,"description":347},"\u002Finterview\u002Fmysql","MySQL 面试题","MySQL 数据库面试高频考点，覆盖索引、事务、锁、优化、主从复制等核心知识。",{"path":349,"title":350,"description":351},"\u002Finterview\u002Fredis","Redis 面试题","Redis 面试高频考点，覆盖数据结构、持久化、集群、缓存一致性、性能优化等核心知识。",{"path":353,"title":354,"description":355},"\u002Finterview\u002Frocketmq","RocketMQ 面试题","RocketMQ 面试高频考点，覆盖消息模型、可靠性、顺序消息、事务消息、存储与高可用等核心知识。",{"path":357,"title":358,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-arraylist","List - ArrayList 源码解析",{"path":360,"title":361,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-linkedlist","List - LinkedList 源码解析",{"path":363,"title":364,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-stack","List - Satck源码解析",{"path":366,"title":367,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Flist-vectore","List - Vector 源码解析",{"path":369,"title":370,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-hashmap","Map - HashMap 源码解析",{"path":372,"title":373,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-linkedhashmap","Map - LinkedHashMap 源码解析",{"path":375,"title":376,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fmap-treemap","Map - TreeMap 源码解析",{"path":378,"title":379,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fqueue-deque","Queue - Deque 接口解析",{"path":381,"title":382,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fqueue-queue","Queue - Queue 接口解析",{"path":384,"title":385,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-hashset","Set - HashSet源码解析",{"path":387,"title":388,"description":11},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-linkedhashset","Set - LinkedHashSet 源码解析",{"path":390,"title":391,"description":392},"\u002Fother\u002Fjava\u002Fcollection\u002Fset-treeset","Set - TreeSet源码解析","TreeSet 是一个 Set 集合接口的实现类，与 HashSet 类似，其底层也是通过维护了一个 TreeMap 对象来封装了一些实现方法，故本篇不再对 TreeSet 的底层原理进行详细说明，仅对常用 API 做简单介绍，如需了解 TreeMap 的底层实现原理，请移步 Map - HashMap 源码解析",{"path":394,"title":395,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fannotation","Java核心 - 注解",{"path":397,"title":398,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fbasic-grammar","Java核心 - 基础语法",{"path":400,"title":401,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fclass-and-object","Java核心 - 面向对象",{"path":403,"title":404,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fcommon-classes","Java核心 - 常用类",{"path":406,"title":407,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fexception","Java核心 - 异常处理",{"path":409,"title":410,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fgenerics","Java核心 - 泛型",{"path":412,"title":413,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fjdk-env-path","Java核心 - 环境搭建",{"path":415,"title":416,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Freflection","Java核心 - 反射",{"path":418,"title":419,"description":11},"\u002Fother\u002Fjava\u002Fcore\u002Fstring","Java核心 - String 字符串",{"path":421,"title":422,"description":423},"\u002Fother\u002Fjava\u002Fio\u002Fbuffer-stream","Java IO - 缓冲流","缓冲流是对基本流的包装，通过内置缓冲区减少系统调用次数，大幅提升读写效率。",{"path":425,"title":426,"description":427},"\u002Fother\u002Fjava\u002Fio\u002Fbyte-stream","Java IO - 字节流","字节流是 Java IO 中最基本的流类型，以字节（byte）为单位进行数据读写，可以处理任意类型的文件。",{"path":429,"title":430,"description":431},"\u002Fother\u002Fjava\u002Fio\u002Fchar-stream","Java IO - 字符流","字符流以字符为单位进行读写，专门用于处理文本文件。相比字节流，字符流能够正确处理字符编码，避免中文乱码问题。",{"path":433,"title":434,"description":11},"\u002Fother\u002Fjava\u002Fio\u002Ffile","Java IO - File 类",{"path":436,"title":437,"description":11},"\u002Fother\u002Fjava\u002Fio\u002Fio-stream-system","Java IO - IO流概述",{"path":439,"title":440,"description":441},"\u002Fother\u002Fjava\u002Fio\u002Fnio","Java IO - NIO","NIO（New IO）是 JDK 1.4 引入的新 IO 模型，提供了更高效的 IO 操作方式，支持非阻塞 IO 和多路复用。",{"path":443,"title":444,"description":445},"\u002Fother\u002Fjava\u002Fjvm\u002Fclass-loading","类加载机制","类加载机制是 JVM 将 .class 文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可被 JVM 直接使用的 Java 类型的过程。",{"path":447,"title":448,"description":449},"\u002Fother\u002Fjava\u002Fjvm\u002Fgarbage-collection","垃圾回收","垃圾回收（Garbage Collection，GC）是 JVM 自动管理内存的机制，负责回收不再使用的对象所占用的内存。",{"path":451,"title":452,"description":453},"\u002Fother\u002Fjava\u002Fjvm\u002Fjvm-memory","JVM 内存结构","JVM 在执行 Java 程序时，会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途、创建和销毁时间。",{"path":455,"title":456,"description":457},"\u002Fother\u002Fjava\u002Fjvm\u002Fjvm-tuning","JVM 调优","JVM 调优是优化 Java 应用性能的重要手段，主要包括参数配置、性能监控和问题排查。",{"path":459,"title":460,"description":461},"\u002Fother\u002Fjava\u002Fthread\u002Fatomic","原子类","Java 原子类（Atomic Classes）提供了一种无锁的线程安全方式，基于 CAS（Compare-And-Swap）操作实现。",{"path":463,"title":464,"description":465},"\u002Fother\u002Fjava\u002Fthread\u002Fcompletable-future","CompletableFuture","CompletableFuture 是 JDK 8 引入的异步编程工具，实现了 Future 和 CompletionStage 接口，支持函数式编程和链式调用。",{"path":467,"title":468,"description":469},"\u002Fother\u002Fjava\u002Fthread\u002Fconcurrent-collections","并发集合","Java 并发包提供了多种线程安全的集合类，用于替代传统的同步集合（如 Collections.synchronizedList）。",{"path":471,"title":472,"description":473},"\u002Fother\u002Fjava\u002Fthread\u002Fconcurrent-utils","并发工具类","Java 并发包提供了多种实用的并发工具类，用于控制线程之间的协调与同步。",{"path":475,"title":476,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fsynchronized-lock","同步机制",{"path":478,"title":479,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fthread-basic","线程基础",{"path":481,"title":482,"description":11},"\u002Fother\u002Fjava\u002Fthread\u002Fthread-pool","线程池",{"path":484,"title":485,"description":486},"\u002Fother\u002Fspring-series\u002Fspring\u002Fannotations-beans","Spring - 基于注解管理Bean","从 Java 5 开始，Java 增加了对注解（Annotation）的支持，它是代码中的一种特殊标记，可以在编译、类加载和运行时被读取，执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下，在源代码中嵌入补充信息。",{"path":488,"title":489,"description":490},"\u002Fother\u002Fspring-series\u002Fspring\u002Fimplement-ioc","Spring - 原理手写IoC","Spring 框架的 IOC 是基于 Java 反射机制实现的，在学习手写 IoC 之前，你需要具备一定的 Java 反射相关的知识，参考本站内的 Java 教程。",{"path":492,"title":493,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fintroduction-case","Spring - 入门案例",{"path":495,"title":496,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-aop","Spring - 面向切面AOP",{"path":498,"title":499,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-aot","Spring - AOT提前编译",{"path":501,"title":502,"description":503},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-data-validation","Spring - 数据校验","在开发中，我们经常遇到参数校验的需求，比如用户注册的时候，要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式，我们会把校验的代码和真正的业务处理逻辑耦合在一起，而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则，把校验和业务逻辑分离开，让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装，方便在Spring中使用。",{"path":505,"title":506,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-i18n","Spring - 国际化i18n",{"path":508,"title":509,"description":510},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-ioc","Spring - IOC容器","IoC 是 Inversion of Control 的简写，译为“控制反转”，它不是一门技术，而是一种设计思想，是一个重要的面向对象编程法则，能够指导我们如何设计出松耦合、更优良的程序。",{"path":512,"title":513,"description":514},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-junit","Spring - 单元测试JUnit","在之前的测试方法中，几乎都能看到以下的两行代码：",{"path":516,"title":517,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-resources","Spring - 资源操作",{"path":519,"title":520,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-summarize","Spring - Spring概述",{"path":522,"title":523,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fspring-transaction","Spring - 事务",{"path":525,"title":526,"description":11},"\u002Fother\u002Fspring-series\u002Fspring\u002Fxml-beans","Spring - 基于XML管理Bean",{"path":528,"title":529,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-config","SpringBoot - 配置详解",{"path":531,"title":532,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-data","SpringBoot - 数据访问",{"path":534,"title":535,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-quickstart","SpringBoot - 快速入门",{"path":537,"title":538,"description":11},"\u002Fother\u002Fspring-series\u002Fspringboot\u002Fspringboot-web","SpringBoot - Web 开发",{"path":540,"title":541,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-config","SpringCloud - 配置中心",{"path":543,"title":544,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-discovery","SpringCloud - 服务注册与发现",{"path":546,"title":547,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-feign","SpringCloud - 服务调用",{"path":549,"title":550,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-gateway","SpringCloud - 服务网关",{"path":552,"title":553,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-introduction","SpringCloud - 微服务概述",{"path":555,"title":556,"description":11},"\u002Fother\u002Fspring-series\u002Fspringcloud\u002Fspringcloud-sentinel","SpringCloud - 服务保护",{"path":558,"title":559,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-databind","SpringMVC - 数据绑定与转换",{"path":561,"title":562,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-exception","SpringMVC - 异常处理",{"path":564,"title":565,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-interceptor","SpringMVC - 拦截器",{"path":567,"title":568,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-introduction","SpringMVC - 简介与环境搭建",{"path":570,"title":571,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-request","SpringMVC - 请求处理",{"path":573,"title":574,"description":11},"\u002Fother\u002Fspring-series\u002Fspringmvc\u002Fspringmvc-response","SpringMVC - 响应处理",{"path":576,"title":577,"description":578},"\u002Fproject\u002Frocket-leaf\u002Farchitecture","项目架构","Rocket-Leaf 的目录结构、模块划分、数据流向，以及各层之间的依赖关系。",{"path":580,"title":581,"description":582},"\u002Fproject\u002Frocket-leaf\u002Fbackend-layers","后端分层设计","Rocket-Leaf 的 model \u002F rocketmq \u002F service 三层结构，以及服务之间的依赖关系与设计取舍。",{"path":584,"title":585,"description":586},"\u002Fproject\u002Frocket-leaf\u002Fclient-manager","RocketMQ 客户端管理器","AdminClientManager 的多客户端池、默认连接懒加载、自动重连重试的设计与实现。",{"path":588,"title":589,"description":590},"\u002Fproject\u002Frocket-leaf\u002Fencryption","连接信息加密存储","AES-256-GCM + SHA-256 字段级派生密钥的实现，以及如何在不破坏兼容性的前提下为历史明文数据做透明迁移。",{"path":592,"title":593,"description":594},"\u002Fproject\u002Frocket-leaf\u002Ffrontend","前端结构与类型绑定","React + Vite 目录组织、自动生成的 Wails 绑定、api 薄封装与自定义 hooks 的职责划分。",{"path":596,"title":597,"description":598},"\u002Fproject\u002Frocket-leaf","项目简介","Rocket-Leaf 是一款基于 Wails v3 构建的跨平台 RocketMQ 桌面管理客户端，Go 后端 + React 前端。本文档系列拆解它的架构与关键实现。",{"path":600,"title":601,"description":602},"\u002Fproject\u002Frocket-leaf\u002Fwails-v3","Wails v3 入门","Wails v3 的核心概念、Service 绑定机制，以及 Rocket-Leaf 是如何用它把 Go 后端和 React 前端打通的。",{"path":604,"title":605,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-basic","Docker - 入门基础",{"path":607,"title":608,"description":609},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-compose","Docker - Compose","在部署应用时，常常使用到不止一个容器，那么在部署容器的时候就需要一个一个进行部署，这样的部署过程也相对来说比较繁琐复杂，也容易出问题，那么有没有一种更为简单的方法呢？",{"path":611,"title":612,"description":613},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-container-connection","Docker - 容器互联","在上一个章节中我们学习了 Docker 容器的端口映射，可以将 Docker 容器和本地以及网络中的端口进行连接起来。",{"path":615,"title":616,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-dockerfile","Docker - Dockerfile",{"path":618,"title":619,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-helloworld","Docker - HelloWorld",{"path":621,"title":622,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-install","Docker - 安装",{"path":624,"title":625,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-introduce","Docker - 简介",{"path":627,"title":628,"description":11},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-object","Docker - 镜像、容器、仓库",{"path":630,"title":631,"description":632},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-warehouse","Docker - 仓库管理","仓库是集中存放资源的地方，代码仓库是存放代码的，那么Docker 中的仓库就是存放 Docker 镜像的。",{"path":634,"title":635,"description":636},"\u002Ftutorials\u002Fcloud\u002Fdocker\u002Fdocker-web-containers","Docker - WEB应用实例","在之前的章节中，仅对普通容器进行了演示，但在实际中常常使用到 Docker 容器中的 WEB 应用程序。",{"path":638,"title":639,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-config","Kubernetes - ConfigMap 与 Secret",{"path":641,"title":642,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-helm","Kubernetes - Helm 包管理",{"path":644,"title":645,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-install","Kubernetes - 集群安装",{"path":647,"title":648,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-introduction","Kubernetes - 简介与架构",{"path":650,"title":651,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-kubectl","Kubernetes - kubectl 命令行工具",{"path":653,"title":654,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-monitoring","Kubernetes - 监控与日志",{"path":656,"title":657,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-network-security","Kubernetes - 网络与安全",{"path":659,"title":660,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-service","Kubernetes - Service 与 Ingress",{"path":662,"title":663,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-storage","Kubernetes - 持久化存储",{"path":665,"title":666,"description":11},"\u002Ftutorials\u002Fcloud\u002Fkubernetes\u002Fk8s-workload","Kubernetes - 工作负载资源",{"path":668,"title":669,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-bash","Linux - Bash 基础语法",{"path":671,"title":672,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-file-directory","Linux - 文件与目录操作",{"path":674,"title":675,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-network","Linux - 网络配置",{"path":677,"title":678,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-package","Linux - 软件包管理",{"path":680,"title":681,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-process","Linux - 进程管理",{"path":683,"title":684,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-scripts","Linux - 常用脚本示例",{"path":686,"title":687,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-service","Linux - 服务管理",{"path":689,"title":690,"description":11},"\u002Ftutorials\u002Fcloud\u002Flinux\u002Flinux-user-permission","Linux - 用户与权限管理",{"path":692,"title":693,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-https","Nginx - HTTPS 配置",{"path":695,"title":696,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-install","Nginx - 安装与配置",{"path":698,"title":699,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-loadbalance","Nginx - 负载均衡",{"path":701,"title":702,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-optimization","Nginx - 性能优化",{"path":704,"title":705,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-proxy","Nginx - 反向代理",{"path":707,"title":708,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-static","Nginx - 静态资源服务",{"path":710,"title":711,"description":11},"\u002Ftutorials\u002Fcloud\u002Fnginx\u002Fnginx-vhost","Nginx - 虚拟主机配置",{"path":713,"title":714,"description":715},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-architecture","MySQL 高可用架构","主从复制、读写分离、分库分表。",{"path":717,"title":718,"description":719},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-index","MySQL 索引","索引是帮助 MySQL 高效获取数据的有序数据结构。",{"path":721,"title":722,"description":723},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-lock","MySQL 锁","锁用于解决并发访问时的数据一致性问题。",{"path":725,"title":726,"description":727},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-optimize","MySQL 性能优化","SQL 优化是后端开发必备技能。",{"path":729,"title":730,"description":731},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fmysql-transaction","MySQL 事务","事务是一组不可分割的操作，要么全部成功，要么全部失败。",{"path":733,"title":734,"description":735},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fsql-advanced","SQL 进阶","多表查询、子查询、函数、视图、存储过程。",{"path":737,"title":738,"description":739},"\u002Ftutorials\u002Fdatabase\u002Fmysql\u002Fsql-basic","SQL 基础","SQL（Structured Query Language）是操作关系型数据库的标准语言。",{"path":741,"title":742,"description":743},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-advanced","Redis 进阶功能","事务、发布订阅、Lua 脚本、Pipeline。",{"path":745,"title":746,"description":747},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-basic","Redis 基础","Redis 安装配置与基本命令。",{"path":749,"title":750,"description":751},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-cluster","Redis 高可用","主从复制、哨兵、Cluster 集群。",{"path":753,"title":754,"description":755},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-datatype","Redis 数据类型","Redis 5 种基本数据类型 + 4 种特殊类型。",{"path":757,"title":758,"description":759},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-optimize","Redis 性能优化","内存优化、缓存问题、最佳实践。",{"path":761,"title":762,"description":763},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-persistence","Redis 持久化","Redis 提供 RDB 和 AOF 两种持久化方式。",{"path":765,"title":766,"description":767},"\u002Ftutorials\u002Fdatabase\u002Fredis\u002Fredis-principle","Redis 底层原理","数据结构、线程模型、网络模型。",{"path":769,"title":770,"description":771},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fbehaiver-patterns\u002Fobserver-pattern","观察者模式","观察者模式属于行为型模式，定义了对象之间的一对多的依赖关系，在这种模式中，当一个对象的状态发生变化时，所有依赖于它的对象都会得到通知，并且执行相关操作。观察者模式又被成为“发布—订阅模式”，即发布者发生改变后，会通知所有订阅者。",{"path":773,"title":774,"description":11},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fcreate-patterns\u002Ffactory-pattern","工厂模式",{"path":776,"title":777,"description":778},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fcreate-patterns\u002Fsingleton-pattern","单例模式","单例模式是最常用的设计模式之一，他可以保证在整个应用中，某个类只存在一个实例化对象，即全局使用到该类的只有一个对象，这种模式在需要限制某些类的实例数量时非常有用，通常全局只需要一个该对象即可，如一些配置文件映射对象、数据库连接对象等。",{"path":780,"title":781,"description":782},"\u002Ftutorials\u002Fdev-idea\u002Fdesign-patterns\u002Fstructural-patterns\u002Fadapter-pattern","适配器模式","适配器模式是一种结构型模式，可以将一个类的接口转换成客户端所期望的另一种接口，适配器模式可以帮助开发人员在不修改现有代码的情况下，将不兼容的类组合在一起。",{"path":784,"title":785,"description":786},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-basic-operations","Git 创建版本库","在 Git 上创建版本库有两种方式，一种是直接拷贝远程 Git 仓库到本地，另外一种是我们自己创建本地的版本库。",{"path":788,"title":789,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-branch-manage","Git 分支管理",{"path":791,"title":792,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-content-operations","Git 仓库内容操作",{"path":794,"title":795,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-introduce-install","Git 介绍和安装",{"path":797,"title":798,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-remote-manage","Git 远程管理",{"path":800,"title":801,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fgit\u002Fgit-workspace-index-repo","Git 工作原理",{"path":803,"title":804,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fhomebrew","HomeBrew 教程",{"path":806,"title":807,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fidea\u002Fshortcuts","快捷键",{"path":809,"title":810,"description":11},"\u002Ftutorials\u002Fdev-tools\u002Fmaven\u002Fintroduce-install-config","Maven - 介绍、安装、配置",{"path":812,"title":813,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fbasic-knowledge","2. 基础知识",{"path":815,"title":816,"description":817},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcomponent-communication","9. 组件通信","在前面的章节内，介绍了 Vue 中最核心的内容——组件的介绍和使用，和 Java 等编程语言相反，组件并不近似于这些变成语言中的类，类可以通过类或者其实例化的对象来相互交互，但 Vue 组件之间的作用域是相互独立的，这就意味着不同组件之间的数据无法相互引用。",{"path":819,"title":820,"description":821},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcomputed","4. 计算属性","虽然直接在模板中使用表达式方便，但是如果在模板中添加很多逻辑，会让模板变的臃肿且难维护，耦合度较高。有没有一种简单的方式来实现呢？答案是有的。",{"path":823,"title":824,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fcreate-vue-project","1. 环境搭建及安装",{"path":826,"title":827,"description":828},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Flife-cycle","6. 生命周期","生命周期是指组件从创建、挂载、更新到销毁的整个过程中所经历的一系列阶段。在 Vue 中，每个组件都有自己的生命周期，可以通过生命周期钩子函数来监听和处理组件在不同阶段的行为和状态。",{"path":830,"title":831,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fother-api","10. 其他 API",{"path":833,"title":834,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fpinia","8. Pinia",{"path":836,"title":837,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Frouter","7. 路由",{"path":839,"title":840,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Ftemplate-grammar","3. 指令及模板语法",{"path":842,"title":843,"description":11},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fvue3-new-component","11. Vue3 新组件",{"path":845,"title":846,"description":847},"\u002Ftutorials\u002Ffront-end\u002Fvue3\u002Fwatch","5. 监视","Watch 是 Vue 提供的一个用于监视响应式数据变化并执行相应操作的 API，能够对响应式数据的变化做出一些操作的功能。Vue3 中的 Watch 支持多种用法，包括监视响应式对象、ref 对象、数组、函数等。",{"path":849,"title":850,"description":11},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-introduction","Kafka 简介与安装",{"path":852,"title":853,"description":11},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-producer-consumer","Kafka 生产者与消费者",{"path":855,"title":856,"description":857},"\u002Ftutorials\u002Fmq\u002Fkafka\u002Fkafka-springboot","Spring Boot 整合 Kafka","Spring Kafka 提供了对 Apache Kafka 的便捷集成。",{"path":859,"title":860,"description":861},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-exchange","RabbitMQ Exchange 详解","Exchange（交换机）是 RabbitMQ 的核心组件，负责接收生产者发送的消息，并根据规则将消息路由到一个或多个队列。",{"path":863,"title":864,"description":11},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-introduction","RabbitMQ 简介与安装",{"path":866,"title":867,"description":868},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-reliability","RabbitMQ 消息可靠性","消息可靠性是消息队列的核心要求，RabbitMQ 提供了多种机制来保证消息不丢失。",{"path":870,"title":871,"description":872},"\u002Ftutorials\u002Fmq\u002Frabbitmq\u002Frabbitmq-springboot","Spring Boot 整合 RabbitMQ","Spring AMQP 提供了对 RabbitMQ 的便捷集成，大大简化了开发工作。",{"path":874,"title":875,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-client","RocketMQ 客户端使用",{"path":877,"title":878,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-concepts","RocketMQ 核心概念",{"path":880,"title":881,"description":11},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-installation","RocketMQ 安装部署",{"path":883,"title":884,"description":885},"\u002Ftutorials\u002Fmq\u002Frocketmq\u002Frocketmq-message-type","RocketMQ 消息类型","RocketMQ 支持多种消息类型，满足不同业务场景需求。",{"id":887,"title":585,"body":888,"description":586,"extension":3624,"meta":3625,"navigation":1052,"path":584,"seo":3626,"stem":3627,"__hash__":3628},"docs\u002Fproject\u002Frocket-leaf\u002Fclient-manager.md",{"type":889,"value":890,"toc":3604},"minimark",[891,896,910,926,933,936,1146,1149,1178,1181,1768,1771,1776,1789,1793,1810,1873,1883,1887,1904,1907,1914,1917,1946,1952,1974,1977,1983,2301,2339,2342,2355,2378,2381,2384,2394,2599,2605,2608,2615,2917,2920,2923,3133,3151,3154,3157,3369,3399,3402,3493,3499,3502,3597,3600],[892,893,895],"h2",{"id":894},"为什么需要一个-manager","为什么需要一个 Manager",[897,898,899,900,904,905,909],"p",{},"Rocket-Leaf 支持同时配置",[901,902,903],"strong",{},"多个 RocketMQ 集群","，用户随时切换。如果每次业务调用都 ",[906,907,908],"code",{},"admin.NewClient(...)","，会有两个问题：",[911,912,913,920],"ol",{},[914,915,916,919],"li",{},[901,917,918],{},"连接开销高","：NameServer 握手、路由拉取都不便宜，频繁建连会让 UI 卡顿",[914,921,922,925],{},[901,923,924],{},"难以管理生命周期","：切换集群时要显式关闭旧连接，否则连接泄露",[897,927,928,929,932],{},"所以需要一个",[901,930,931],{},"带缓存、带生命周期管理的 Client 池","。",[892,934,935],{"id":935},"数据结构",[937,938,942],"pre",{"className":939,"code":940,"language":941,"meta":11,"style":11},"language-go shiki shiki-themes github-light github-light github-dark","type AdminClientManager struct {\n    mu                       sync.RWMutex\n    clients                  map[string]*admin.Client \u002F\u002F key: NameServer 地址\n    defaultConn              string                   \u002F\u002F 默认连接的 NameServer 地址\n    defaultClientInitializer func() error             \u002F\u002F 默认连接懒初始化器\n}\n\nvar clientManager = &AdminClientManager{\n    clients: make(map[string]*admin.Client),\n}\n\nfunc GetClientManager() *AdminClientManager {\n    return clientManager\n}\n","go",[906,943,944,964,979,1012,1023,1041,1047,1054,1075,1106,1111,1116,1132,1141],{"__ignoreMap":11},[945,946,949,953,957,960],"span",{"class":947,"line":948},"line",1,[945,950,952],{"class":951},"s8jYJ","type",[945,954,956],{"class":955},"snPdu"," AdminClientManager",[945,958,959],{"class":951}," struct",[945,961,963],{"class":962},"sxrX7"," {\n",[945,965,967,970,973,976],{"class":947,"line":966},2,[945,968,969],{"class":962},"    mu                       ",[945,971,972],{"class":955},"sync",[945,974,975],{"class":962},".",[945,977,978],{"class":955},"RWMutex\n",[945,980,982,985,988,991,994,997,1000,1003,1005,1008],{"class":947,"line":981},3,[945,983,984],{"class":962},"    clients                  ",[945,986,987],{"class":951},"map",[945,989,990],{"class":962},"[",[945,992,993],{"class":951},"string",[945,995,996],{"class":962},"]",[945,998,999],{"class":951},"*",[945,1001,1002],{"class":955},"admin",[945,1004,975],{"class":962},[945,1006,1007],{"class":955},"Client",[945,1009,1011],{"class":1010},"sCsY4"," \u002F\u002F key: NameServer 地址\n",[945,1013,1015,1018,1020],{"class":947,"line":1014},4,[945,1016,1017],{"class":962},"    defaultConn              ",[945,1019,993],{"class":951},[945,1021,1022],{"class":1010},"                   \u002F\u002F 默认连接的 NameServer 地址\n",[945,1024,1026,1029,1032,1035,1038],{"class":947,"line":1025},5,[945,1027,1028],{"class":962},"    defaultClientInitializer ",[945,1030,1031],{"class":951},"func",[945,1033,1034],{"class":962},"() ",[945,1036,1037],{"class":951},"error",[945,1039,1040],{"class":1010},"             \u002F\u002F 默认连接懒初始化器\n",[945,1042,1044],{"class":947,"line":1043},6,[945,1045,1046],{"class":962},"}\n",[945,1048,1050],{"class":947,"line":1049},7,[945,1051,1053],{"emptyLinePlaceholder":1052},true,"\n",[945,1055,1057,1060,1063,1066,1069,1072],{"class":947,"line":1056},8,[945,1058,1059],{"class":951},"var",[945,1061,1062],{"class":962}," clientManager ",[945,1064,1065],{"class":951},"=",[945,1067,1068],{"class":951}," &",[945,1070,1071],{"class":955},"AdminClientManager",[945,1073,1074],{"class":962},"{\n",[945,1076,1078,1081,1084,1087,1089,1091,1093,1095,1097,1099,1101,1103],{"class":947,"line":1077},9,[945,1079,1080],{"class":962},"    clients: ",[945,1082,1083],{"class":955},"make",[945,1085,1086],{"class":962},"(",[945,1088,987],{"class":951},[945,1090,990],{"class":962},[945,1092,993],{"class":951},[945,1094,996],{"class":962},[945,1096,999],{"class":951},[945,1098,1002],{"class":955},[945,1100,975],{"class":962},[945,1102,1007],{"class":955},[945,1104,1105],{"class":962},"),\n",[945,1107,1109],{"class":947,"line":1108},10,[945,1110,1046],{"class":962},[945,1112,1114],{"class":947,"line":1113},11,[945,1115,1053],{"emptyLinePlaceholder":1052},[945,1117,1119,1121,1124,1126,1128,1130],{"class":947,"line":1118},12,[945,1120,1031],{"class":951},[945,1122,1123],{"class":955}," GetClientManager",[945,1125,1034],{"class":962},[945,1127,999],{"class":951},[945,1129,1071],{"class":955},[945,1131,963],{"class":962},[945,1133,1135,1138],{"class":947,"line":1134},13,[945,1136,1137],{"class":951},"    return",[945,1139,1140],{"class":962}," clientManager\n",[945,1142,1144],{"class":947,"line":1143},14,[945,1145,1046],{"class":962},[897,1147,1148],{},"关键字段：",[1150,1151,1152,1158,1164],"ul",{},[914,1153,1154,1157],{},[906,1155,1156],{},"clients"," 按 NameServer 地址缓存客户端，同一个地址永远只有一个活跃连接",[914,1159,1160,1163],{},[906,1161,1162],{},"defaultConn"," 记录当前默认连接，避免每次查询都去遍历 map",[914,1165,1166,1169,1170,1173,1174,1177],{},[906,1167,1168],{},"defaultClientInitializer"," 是一个",[901,1171,1172],{},"回调函数","，由 ",[906,1175,1176],{},"ConnectionService"," 注入，实现懒加载",[892,1179,1180],{"id":1180},"创建连接",[937,1182,1184],{"className":939,"code":1183,"language":941,"meta":11,"style":11},"func (m *AdminClientManager) CreateClient(nameServer string, timeout time.Duration,\n    enableACL bool, accessKey, secretKey string) (*admin.Client, error) {\n    m.mu.Lock()\n    defer m.mu.Unlock()\n\n    \u002F\u002F 如果已存在，先关闭旧客户端（避免重复连接）\n    if oldClient, exists := m.clients[nameServer]; exists {\n        oldClient.Close()\n    }\n\n    options := []admin.Option{\n        admin.WithNameServers([]string{nameServer}),\n        admin.WithTimeout(timeout),\n    }\n    if enableACL {\n        if strings.TrimSpace(accessKey) == \"\" || strings.TrimSpace(secretKey) == \"\" {\n            return nil, fmt.Errorf(\"启用 ACL 时 AccessKey\u002FSecretKey 不能为空\")\n        }\n        options = append(options, admin.WithACL(accessKey, secretKey))\n    }\n\n    client, err := admin.NewClient(options...)\n    if err != nil {\n        return nil, fmt.Errorf(\"创建客户端失败: %w\", err)\n    }\n    if err := client.Start(); err != nil {\n        return nil, fmt.Errorf(\"启动客户端失败: %w\", err)\n    }\n\n    \u002F\u002F 关键：验证连接可用性\n    ctx, cancel := context.WithTimeout(context.Background(), timeout)\n    defer cancel()\n    if _, err := client.ExamineBrokerClusterInfo(ctx); err != nil {\n        client.Close()\n        return nil, fmt.Errorf(\"无法连接到 NameServer: %w\", err)\n    }\n\n    log.Printf(\"[ClientManager] 连接 NameServer 成功: %s\", nameServer)\n    m.clients[nameServer] = client\n    return client, nil\n}\n",[906,1185,1186,1232,1270,1281,1294,1298,1303,1317,1327,1332,1336,1355,1371,1381,1385,1393,1431,1455,1461,1481,1486,1491,1513,1528,1554,1559,1583,1605,1610,1615,1621,1643,1653,1677,1687,1709,1714,1719,1741,1752,1763],{"__ignoreMap":11},[945,1187,1188,1190,1193,1197,1199,1201,1204,1207,1209,1212,1215,1218,1221,1224,1226,1229],{"class":947,"line":948},[945,1189,1031],{"class":951},[945,1191,1192],{"class":962}," (",[945,1194,1196],{"class":1195},"sP4rz","m ",[945,1198,999],{"class":951},[945,1200,1071],{"class":955},[945,1202,1203],{"class":962},") ",[945,1205,1206],{"class":955},"CreateClient",[945,1208,1086],{"class":962},[945,1210,1211],{"class":1195},"nameServer",[945,1213,1214],{"class":951}," string",[945,1216,1217],{"class":962},", ",[945,1219,1220],{"class":1195},"timeout",[945,1222,1223],{"class":955}," time",[945,1225,975],{"class":962},[945,1227,1228],{"class":955},"Duration",[945,1230,1231],{"class":962},",\n",[945,1233,1234,1237,1240,1242,1245,1247,1250,1252,1255,1257,1259,1261,1263,1265,1267],{"class":947,"line":966},[945,1235,1236],{"class":1195},"    enableACL",[945,1238,1239],{"class":951}," bool",[945,1241,1217],{"class":962},[945,1243,1244],{"class":1195},"accessKey",[945,1246,1217],{"class":962},[945,1248,1249],{"class":1195},"secretKey",[945,1251,1214],{"class":951},[945,1253,1254],{"class":962},") (",[945,1256,999],{"class":951},[945,1258,1002],{"class":955},[945,1260,975],{"class":962},[945,1262,1007],{"class":955},[945,1264,1217],{"class":962},[945,1266,1037],{"class":951},[945,1268,1269],{"class":962},") {\n",[945,1271,1272,1275,1278],{"class":947,"line":981},[945,1273,1274],{"class":962},"    m.mu.",[945,1276,1277],{"class":955},"Lock",[945,1279,1280],{"class":962},"()\n",[945,1282,1283,1286,1289,1292],{"class":947,"line":1014},[945,1284,1285],{"class":951},"    defer",[945,1287,1288],{"class":962}," m.mu.",[945,1290,1291],{"class":955},"Unlock",[945,1293,1280],{"class":962},[945,1295,1296],{"class":947,"line":1025},[945,1297,1053],{"emptyLinePlaceholder":1052},[945,1299,1300],{"class":947,"line":1043},[945,1301,1302],{"class":1010},"    \u002F\u002F 如果已存在，先关闭旧客户端（避免重复连接）\n",[945,1304,1305,1308,1311,1314],{"class":947,"line":1049},[945,1306,1307],{"class":951},"    if",[945,1309,1310],{"class":962}," oldClient, exists ",[945,1312,1313],{"class":951},":=",[945,1315,1316],{"class":962}," m.clients[nameServer]; exists {\n",[945,1318,1319,1322,1325],{"class":947,"line":1056},[945,1320,1321],{"class":962},"        oldClient.",[945,1323,1324],{"class":955},"Close",[945,1326,1280],{"class":962},[945,1328,1329],{"class":947,"line":1077},[945,1330,1331],{"class":962},"    }\n",[945,1333,1334],{"class":947,"line":1108},[945,1335,1053],{"emptyLinePlaceholder":1052},[945,1337,1338,1341,1343,1346,1348,1350,1353],{"class":947,"line":1113},[945,1339,1340],{"class":962},"    options ",[945,1342,1313],{"class":951},[945,1344,1345],{"class":962}," []",[945,1347,1002],{"class":955},[945,1349,975],{"class":962},[945,1351,1352],{"class":955},"Option",[945,1354,1074],{"class":962},[945,1356,1357,1360,1363,1366,1368],{"class":947,"line":1118},[945,1358,1359],{"class":962},"        admin.",[945,1361,1362],{"class":955},"WithNameServers",[945,1364,1365],{"class":962},"([]",[945,1367,993],{"class":951},[945,1369,1370],{"class":962},"{nameServer}),\n",[945,1372,1373,1375,1378],{"class":947,"line":1134},[945,1374,1359],{"class":962},[945,1376,1377],{"class":955},"WithTimeout",[945,1379,1380],{"class":962},"(timeout),\n",[945,1382,1383],{"class":947,"line":1143},[945,1384,1331],{"class":962},[945,1386,1388,1390],{"class":947,"line":1387},15,[945,1389,1307],{"class":951},[945,1391,1392],{"class":962}," enableACL {\n",[945,1394,1396,1399,1402,1405,1408,1411,1415,1418,1420,1422,1425,1427,1429],{"class":947,"line":1395},16,[945,1397,1398],{"class":951},"        if",[945,1400,1401],{"class":962}," strings.",[945,1403,1404],{"class":955},"TrimSpace",[945,1406,1407],{"class":962},"(accessKey) ",[945,1409,1410],{"class":951},"==",[945,1412,1414],{"class":1413},"sIIMD"," \"\"",[945,1416,1417],{"class":951}," ||",[945,1419,1401],{"class":962},[945,1421,1404],{"class":955},[945,1423,1424],{"class":962},"(secretKey) ",[945,1426,1410],{"class":951},[945,1428,1414],{"class":1413},[945,1430,963],{"class":962},[945,1432,1434,1437,1441,1444,1447,1449,1452],{"class":947,"line":1433},17,[945,1435,1436],{"class":951},"            return",[945,1438,1440],{"class":1439},"sBjJW"," nil",[945,1442,1443],{"class":962},", fmt.",[945,1445,1446],{"class":955},"Errorf",[945,1448,1086],{"class":962},[945,1450,1451],{"class":1413},"\"启用 ACL 时 AccessKey\u002FSecretKey 不能为空\"",[945,1453,1454],{"class":962},")\n",[945,1456,1458],{"class":947,"line":1457},18,[945,1459,1460],{"class":962},"        }\n",[945,1462,1464,1467,1469,1472,1475,1478],{"class":947,"line":1463},19,[945,1465,1466],{"class":962},"        options ",[945,1468,1065],{"class":951},[945,1470,1471],{"class":955}," append",[945,1473,1474],{"class":962},"(options, admin.",[945,1476,1477],{"class":955},"WithACL",[945,1479,1480],{"class":962},"(accessKey, secretKey))\n",[945,1482,1484],{"class":947,"line":1483},20,[945,1485,1331],{"class":962},[945,1487,1489],{"class":947,"line":1488},21,[945,1490,1053],{"emptyLinePlaceholder":1052},[945,1492,1494,1497,1499,1502,1505,1508,1511],{"class":947,"line":1493},22,[945,1495,1496],{"class":962},"    client, err ",[945,1498,1313],{"class":951},[945,1500,1501],{"class":962}," admin.",[945,1503,1504],{"class":955},"NewClient",[945,1506,1507],{"class":962},"(options",[945,1509,1510],{"class":951},"...",[945,1512,1454],{"class":962},[945,1514,1516,1518,1521,1524,1526],{"class":947,"line":1515},23,[945,1517,1307],{"class":951},[945,1519,1520],{"class":962}," err ",[945,1522,1523],{"class":951},"!=",[945,1525,1440],{"class":1439},[945,1527,963],{"class":962},[945,1529,1531,1534,1536,1538,1540,1542,1545,1548,1551],{"class":947,"line":1530},24,[945,1532,1533],{"class":951},"        return",[945,1535,1440],{"class":1439},[945,1537,1443],{"class":962},[945,1539,1446],{"class":955},[945,1541,1086],{"class":962},[945,1543,1544],{"class":1413},"\"创建客户端失败: ",[945,1546,1547],{"class":1439},"%w",[945,1549,1550],{"class":1413},"\"",[945,1552,1553],{"class":962},", err)\n",[945,1555,1557],{"class":947,"line":1556},25,[945,1558,1331],{"class":962},[945,1560,1562,1564,1566,1568,1571,1574,1577,1579,1581],{"class":947,"line":1561},26,[945,1563,1307],{"class":951},[945,1565,1520],{"class":962},[945,1567,1313],{"class":951},[945,1569,1570],{"class":962}," client.",[945,1572,1573],{"class":955},"Start",[945,1575,1576],{"class":962},"(); err ",[945,1578,1523],{"class":951},[945,1580,1440],{"class":1439},[945,1582,963],{"class":962},[945,1584,1586,1588,1590,1592,1594,1596,1599,1601,1603],{"class":947,"line":1585},27,[945,1587,1533],{"class":951},[945,1589,1440],{"class":1439},[945,1591,1443],{"class":962},[945,1593,1446],{"class":955},[945,1595,1086],{"class":962},[945,1597,1598],{"class":1413},"\"启动客户端失败: ",[945,1600,1547],{"class":1439},[945,1602,1550],{"class":1413},[945,1604,1553],{"class":962},[945,1606,1608],{"class":947,"line":1607},28,[945,1609,1331],{"class":962},[945,1611,1613],{"class":947,"line":1612},29,[945,1614,1053],{"emptyLinePlaceholder":1052},[945,1616,1618],{"class":947,"line":1617},30,[945,1619,1620],{"class":1010},"    \u002F\u002F 关键：验证连接可用性\n",[945,1622,1624,1627,1629,1632,1634,1637,1640],{"class":947,"line":1623},31,[945,1625,1626],{"class":962},"    ctx, cancel ",[945,1628,1313],{"class":951},[945,1630,1631],{"class":962}," context.",[945,1633,1377],{"class":955},[945,1635,1636],{"class":962},"(context.",[945,1638,1639],{"class":955},"Background",[945,1641,1642],{"class":962},"(), timeout)\n",[945,1644,1646,1648,1651],{"class":947,"line":1645},32,[945,1647,1285],{"class":951},[945,1649,1650],{"class":955}," cancel",[945,1652,1280],{"class":962},[945,1654,1656,1658,1661,1663,1665,1668,1671,1673,1675],{"class":947,"line":1655},33,[945,1657,1307],{"class":951},[945,1659,1660],{"class":962}," _, err ",[945,1662,1313],{"class":951},[945,1664,1570],{"class":962},[945,1666,1667],{"class":955},"ExamineBrokerClusterInfo",[945,1669,1670],{"class":962},"(ctx); err ",[945,1672,1523],{"class":951},[945,1674,1440],{"class":1439},[945,1676,963],{"class":962},[945,1678,1680,1683,1685],{"class":947,"line":1679},34,[945,1681,1682],{"class":962},"        client.",[945,1684,1324],{"class":955},[945,1686,1280],{"class":962},[945,1688,1690,1692,1694,1696,1698,1700,1703,1705,1707],{"class":947,"line":1689},35,[945,1691,1533],{"class":951},[945,1693,1440],{"class":1439},[945,1695,1443],{"class":962},[945,1697,1446],{"class":955},[945,1699,1086],{"class":962},[945,1701,1702],{"class":1413},"\"无法连接到 NameServer: ",[945,1704,1547],{"class":1439},[945,1706,1550],{"class":1413},[945,1708,1553],{"class":962},[945,1710,1712],{"class":947,"line":1711},36,[945,1713,1331],{"class":962},[945,1715,1717],{"class":947,"line":1716},37,[945,1718,1053],{"emptyLinePlaceholder":1052},[945,1720,1722,1725,1728,1730,1733,1736,1738],{"class":947,"line":1721},38,[945,1723,1724],{"class":962},"    log.",[945,1726,1727],{"class":955},"Printf",[945,1729,1086],{"class":962},[945,1731,1732],{"class":1413},"\"[ClientManager] 连接 NameServer 成功: ",[945,1734,1735],{"class":1439},"%s",[945,1737,1550],{"class":1413},[945,1739,1740],{"class":962},", nameServer)\n",[945,1742,1744,1747,1749],{"class":947,"line":1743},39,[945,1745,1746],{"class":962},"    m.clients[nameServer] ",[945,1748,1065],{"class":951},[945,1750,1751],{"class":962}," client\n",[945,1753,1755,1757,1760],{"class":947,"line":1754},40,[945,1756,1137],{"class":951},[945,1758,1759],{"class":962}," client, ",[945,1761,1762],{"class":1439},"nil\n",[945,1764,1766],{"class":947,"line":1765},41,[945,1767,1046],{"class":962},[897,1769,1770],{},"几个细节值得注意：",[1772,1773,1775],"h3",{"id":1774},"_1-覆盖式替换","1. 覆盖式替换",[897,1777,1778,1779,1782,1783,1788],{},"如果同一个 NameServer 已经有客户端，先 ",[906,1780,1781],{},"Close()"," 再创建新的。这样调用方可以",[901,1784,1785,1786],{},"无脑调用 ",[906,1787,1206],{}," 来\"重连\"，不需要自己先判断再删除。",[1772,1790,1792],{"id":1791},"_2-同步验证连接","2. 同步验证连接",[897,1794,1795,1798,1799,1802,1803,1806,1807,1809],{},[906,1796,1797],{},"admin.NewClient"," + ",[906,1800,1801],{},"Start()"," 只是初始化，",[901,1804,1805],{},"不代表能连上","。真正检验网络的是 ",[906,1808,1667],{},"：",[937,1811,1813],{"className":939,"code":1812,"language":941,"meta":11,"style":11},"if _, err := client.ExamineBrokerClusterInfo(ctx); err != nil {\n    client.Close()  \u002F\u002F 关键：验证失败要清理资源\n    return nil, fmt.Errorf(\"无法连接到 NameServer: %w\", err)\n}\n",[906,1814,1815,1836,1849,1869],{"__ignoreMap":11},[945,1816,1817,1820,1822,1824,1826,1828,1830,1832,1834],{"class":947,"line":948},[945,1818,1819],{"class":951},"if",[945,1821,1660],{"class":962},[945,1823,1313],{"class":951},[945,1825,1570],{"class":962},[945,1827,1667],{"class":955},[945,1829,1670],{"class":962},[945,1831,1523],{"class":951},[945,1833,1440],{"class":1439},[945,1835,963],{"class":962},[945,1837,1838,1841,1843,1846],{"class":947,"line":966},[945,1839,1840],{"class":962},"    client.",[945,1842,1324],{"class":955},[945,1844,1845],{"class":962},"()  ",[945,1847,1848],{"class":1010},"\u002F\u002F 关键：验证失败要清理资源\n",[945,1850,1851,1853,1855,1857,1859,1861,1863,1865,1867],{"class":947,"line":981},[945,1852,1137],{"class":951},[945,1854,1440],{"class":1439},[945,1856,1443],{"class":962},[945,1858,1446],{"class":955},[945,1860,1086],{"class":962},[945,1862,1702],{"class":1413},[945,1864,1547],{"class":1439},[945,1866,1550],{"class":1413},[945,1868,1553],{"class":962},[945,1870,1871],{"class":947,"line":1014},[945,1872,1046],{"class":962},[897,1874,1875,1876,1879,1880,1882],{},"对用户而言，\"连接成功\"的定义就是",[901,1877,1878],{},"能拿到集群信息","。把这一步提前到 ",[906,1881,1206],{}," 里，后续业务操作就不会再在意\"到底连上了没\"。",[1772,1884,1886],{"id":1885},"_3-错误包装","3. 错误包装",[897,1888,1889,1892,1893,1895,1896,1899,1900,1903],{},[906,1890,1891],{},"fmt.Errorf(\"...: %w\", err)"," 使用 ",[906,1894,1547],{}," 占位符保留原始 error 链，调用方可以用 ",[906,1897,1898],{},"errors.Is"," \u002F ",[906,1901,1902],{},"errors.As"," 去判断底层错误类型。",[892,1905,1906],{"id":1906},"懒加载默认连接",[897,1908,1909,1910,1913],{},"Rocket-Leaf 启动时",[901,1911,1912],{},"不立刻","连接任何集群。如果每次开启 App 都要等待几秒网络握手，体验会很差。",[897,1915,1916],{},"懒加载通过一个回调函数实现：",[937,1918,1920],{"className":939,"code":1919,"language":941,"meta":11,"style":11},"\u002F\u002F main.go 中注入\nrocketmq.GetClientManager().\n    SetDefaultClientInitializer(connectionService.ConnectDefault)\n",[906,1921,1922,1927,1938],{"__ignoreMap":11},[945,1923,1924],{"class":947,"line":948},[945,1925,1926],{"class":1010},"\u002F\u002F main.go 中注入\n",[945,1928,1929,1932,1935],{"class":947,"line":966},[945,1930,1931],{"class":962},"rocketmq.",[945,1933,1934],{"class":955},"GetClientManager",[945,1936,1937],{"class":962},"().\n",[945,1939,1940,1943],{"class":947,"line":981},[945,1941,1942],{"class":955},"    SetDefaultClientInitializer",[945,1944,1945],{"class":962},"(connectionService.ConnectDefault)\n",[897,1947,1948,1951],{},[906,1949,1950],{},"ConnectDefault"," 会：",[911,1953,1954,1961,1967],{},[914,1955,1956,1957,1960],{},"读取本地配置里 ",[906,1958,1959],{},"IsDefault == true"," 的连接",[914,1962,1963,1964,1966],{},"调用 ",[906,1965,1206],{}," 建立连接",[914,1968,1969,1970,1973],{},"通过 ",[906,1971,1972],{},"SetDefaultConnection"," 把默认连接标记过去",[1772,1975,1976],{"id":1976},"触发时机",[897,1978,1979,1980,1809],{},"业务 service 需要客户端时，调用的是 ",[906,1981,1982],{},"GetDefaultClient",[937,1984,1986],{"className":939,"code":1985,"language":941,"meta":11,"style":11},"func (m *AdminClientManager) GetDefaultClient() (*admin.Client, error) {\n    m.mu.RLock()\n    defaultConn := m.defaultConn\n    client, exists := m.clients[defaultConn]\n    initializer := m.defaultClientInitializer\n    m.mu.RUnlock()\n\n    \u002F\u002F 场景一：已经有客户端，直接返回\n    if defaultConn != \"\" && exists {\n        return client, nil\n    }\n\n    \u002F\u002F 场景二：还没初始化，调用 initializer\n    if initializer != nil {\n        if err := initializer(); err != nil {\n            return nil, fmt.Errorf(\"初始化默认连接失败: %w\", err)\n        }\n        m.mu.RLock()\n        defaultConn = m.defaultConn\n        client, exists = m.clients[defaultConn]\n        m.mu.RUnlock()\n        if defaultConn != \"\" && exists {\n            return client, nil\n        }\n    }\n\n    if defaultConn == \"\" {\n        return nil, fmt.Errorf(\"未设置默认连接\")\n    }\n    return nil, fmt.Errorf(\"默认连接客户端不存在: %s\", defaultConn)\n}\n",[906,1987,1988,2021,2030,2040,2050,2060,2069,2073,2078,2095,2103,2107,2111,2116,2129,2148,2169,2173,2182,2191,2200,2208,2222,2230,2234,2238,2242,2254,2271,2275,2297],{"__ignoreMap":11},[945,1989,1990,1992,1994,1996,1998,2000,2002,2004,2007,2009,2011,2013,2015,2017,2019],{"class":947,"line":948},[945,1991,1031],{"class":951},[945,1993,1192],{"class":962},[945,1995,1196],{"class":1195},[945,1997,999],{"class":951},[945,1999,1071],{"class":955},[945,2001,1203],{"class":962},[945,2003,1982],{"class":955},[945,2005,2006],{"class":962},"() (",[945,2008,999],{"class":951},[945,2010,1002],{"class":955},[945,2012,975],{"class":962},[945,2014,1007],{"class":955},[945,2016,1217],{"class":962},[945,2018,1037],{"class":951},[945,2020,1269],{"class":962},[945,2022,2023,2025,2028],{"class":947,"line":966},[945,2024,1274],{"class":962},[945,2026,2027],{"class":955},"RLock",[945,2029,1280],{"class":962},[945,2031,2032,2035,2037],{"class":947,"line":981},[945,2033,2034],{"class":962},"    defaultConn ",[945,2036,1313],{"class":951},[945,2038,2039],{"class":962}," m.defaultConn\n",[945,2041,2042,2045,2047],{"class":947,"line":1014},[945,2043,2044],{"class":962},"    client, exists ",[945,2046,1313],{"class":951},[945,2048,2049],{"class":962}," m.clients[defaultConn]\n",[945,2051,2052,2055,2057],{"class":947,"line":1025},[945,2053,2054],{"class":962},"    initializer ",[945,2056,1313],{"class":951},[945,2058,2059],{"class":962}," m.defaultClientInitializer\n",[945,2061,2062,2064,2067],{"class":947,"line":1043},[945,2063,1274],{"class":962},[945,2065,2066],{"class":955},"RUnlock",[945,2068,1280],{"class":962},[945,2070,2071],{"class":947,"line":1049},[945,2072,1053],{"emptyLinePlaceholder":1052},[945,2074,2075],{"class":947,"line":1056},[945,2076,2077],{"class":1010},"    \u002F\u002F 场景一：已经有客户端，直接返回\n",[945,2079,2080,2082,2085,2087,2089,2092],{"class":947,"line":1077},[945,2081,1307],{"class":951},[945,2083,2084],{"class":962}," defaultConn ",[945,2086,1523],{"class":951},[945,2088,1414],{"class":1413},[945,2090,2091],{"class":951}," &&",[945,2093,2094],{"class":962}," exists {\n",[945,2096,2097,2099,2101],{"class":947,"line":1108},[945,2098,1533],{"class":951},[945,2100,1759],{"class":962},[945,2102,1762],{"class":1439},[945,2104,2105],{"class":947,"line":1113},[945,2106,1331],{"class":962},[945,2108,2109],{"class":947,"line":1118},[945,2110,1053],{"emptyLinePlaceholder":1052},[945,2112,2113],{"class":947,"line":1134},[945,2114,2115],{"class":1010},"    \u002F\u002F 场景二：还没初始化，调用 initializer\n",[945,2117,2118,2120,2123,2125,2127],{"class":947,"line":1143},[945,2119,1307],{"class":951},[945,2121,2122],{"class":962}," initializer ",[945,2124,1523],{"class":951},[945,2126,1440],{"class":1439},[945,2128,963],{"class":962},[945,2130,2131,2133,2135,2137,2140,2142,2144,2146],{"class":947,"line":1387},[945,2132,1398],{"class":951},[945,2134,1520],{"class":962},[945,2136,1313],{"class":951},[945,2138,2139],{"class":955}," initializer",[945,2141,1576],{"class":962},[945,2143,1523],{"class":951},[945,2145,1440],{"class":1439},[945,2147,963],{"class":962},[945,2149,2150,2152,2154,2156,2158,2160,2163,2165,2167],{"class":947,"line":1395},[945,2151,1436],{"class":951},[945,2153,1440],{"class":1439},[945,2155,1443],{"class":962},[945,2157,1446],{"class":955},[945,2159,1086],{"class":962},[945,2161,2162],{"class":1413},"\"初始化默认连接失败: ",[945,2164,1547],{"class":1439},[945,2166,1550],{"class":1413},[945,2168,1553],{"class":962},[945,2170,2171],{"class":947,"line":1433},[945,2172,1460],{"class":962},[945,2174,2175,2178,2180],{"class":947,"line":1457},[945,2176,2177],{"class":962},"        m.mu.",[945,2179,2027],{"class":955},[945,2181,1280],{"class":962},[945,2183,2184,2187,2189],{"class":947,"line":1463},[945,2185,2186],{"class":962},"        defaultConn ",[945,2188,1065],{"class":951},[945,2190,2039],{"class":962},[945,2192,2193,2196,2198],{"class":947,"line":1483},[945,2194,2195],{"class":962},"        client, exists ",[945,2197,1065],{"class":951},[945,2199,2049],{"class":962},[945,2201,2202,2204,2206],{"class":947,"line":1488},[945,2203,2177],{"class":962},[945,2205,2066],{"class":955},[945,2207,1280],{"class":962},[945,2209,2210,2212,2214,2216,2218,2220],{"class":947,"line":1493},[945,2211,1398],{"class":951},[945,2213,2084],{"class":962},[945,2215,1523],{"class":951},[945,2217,1414],{"class":1413},[945,2219,2091],{"class":951},[945,2221,2094],{"class":962},[945,2223,2224,2226,2228],{"class":947,"line":1515},[945,2225,1436],{"class":951},[945,2227,1759],{"class":962},[945,2229,1762],{"class":1439},[945,2231,2232],{"class":947,"line":1530},[945,2233,1460],{"class":962},[945,2235,2236],{"class":947,"line":1556},[945,2237,1331],{"class":962},[945,2239,2240],{"class":947,"line":1561},[945,2241,1053],{"emptyLinePlaceholder":1052},[945,2243,2244,2246,2248,2250,2252],{"class":947,"line":1585},[945,2245,1307],{"class":951},[945,2247,2084],{"class":962},[945,2249,1410],{"class":951},[945,2251,1414],{"class":1413},[945,2253,963],{"class":962},[945,2255,2256,2258,2260,2262,2264,2266,2269],{"class":947,"line":1607},[945,2257,1533],{"class":951},[945,2259,1440],{"class":1439},[945,2261,1443],{"class":962},[945,2263,1446],{"class":955},[945,2265,1086],{"class":962},[945,2267,2268],{"class":1413},"\"未设置默认连接\"",[945,2270,1454],{"class":962},[945,2272,2273],{"class":947,"line":1612},[945,2274,1331],{"class":962},[945,2276,2277,2279,2281,2283,2285,2287,2290,2292,2294],{"class":947,"line":1617},[945,2278,1137],{"class":951},[945,2280,1440],{"class":1439},[945,2282,1443],{"class":962},[945,2284,1446],{"class":955},[945,2286,1086],{"class":962},[945,2288,2289],{"class":1413},"\"默认连接客户端不存在: ",[945,2291,1735],{"class":1439},[945,2293,1550],{"class":1413},[945,2295,2296],{"class":962},", defaultConn)\n",[945,2298,2299],{"class":947,"line":1623},[945,2300,1046],{"class":962},[2302,2303,2305,2333],"note",{"title":2304},"依赖反转",[897,2306,2307,2308,1809,2310,2313,2314,2317,2318,2320,2321,2324,2325,2328,2329,2332],{},"这里是典型的",[901,2309,2304],{},[906,2311,2312],{},"rocketmq"," 包本身",[901,2315,2316],{},"不知道"," ",[906,2319,1176],{}," 的存在，它只是持有一个 ",[906,2322,2323],{},"func() error"," 回调。",[906,2326,2327],{},"main.go"," 在启动阶段把 ",[906,2330,2331],{},"ConnectionService.ConnectDefault"," 塞进去，完成了运行时的依赖装配。",[897,2334,2335,2336,2338],{},"好处：",[906,2337,2312],{}," 包可以独立测试、独立复用，而不会被业务逻辑污染。",[1772,2340,2341],{"id":2341},"锁的使用",[897,2343,2344,2346,2347,2350,2351,2354],{},[906,2345,1982],{}," 在读-写-再读的过程中",[901,2348,2349],{},"不能一直持有写锁","，否则 ",[906,2352,2353],{},"initializer"," 内部再加写锁会自锁。所以代码的模式是：",[911,2356,2357,2366,2372],{},[914,2358,2359,2362,2363],{},[906,2360,2361],{},"RLock()"," 读状态 → ",[906,2364,2365],{},"RUnlock()",[914,2367,2368,2369],{},"在锁外调用 ",[906,2370,2371],{},"initializer()",[914,2373,2374,2375,2377],{},"再 ",[906,2376,2361],{}," 读一次状态",[897,2379,2380],{},"这是多线程代码里常见的\"double-checked locking\"变体。",[892,2382,2383],{"id":2383},"测试连接",[897,2385,2386,2389,2390,2393],{},[906,2387,2388],{},"TestConnection"," 用来在用户点击\"测试\"按钮时做一次性的连通性检查，",[901,2391,2392],{},"不会","把客户端缓存下来：",[937,2395,2397],{"className":939,"code":2396,"language":941,"meta":11,"style":11},"func (m *AdminClientManager) TestConnection(...) error {\n    \u002F\u002F ... options ...\n    client, err := admin.NewClient(options...)\n    if err != nil {\n        return fmt.Errorf(\"创建测试客户端失败: %w\", err)\n    }\n    defer client.Close()  \u002F\u002F 注意：用完就关\n\n    if err := client.Start(); err != nil {\n        return fmt.Errorf(\"启动测试客户端失败: %w\", err)\n    }\n\n    ctx, cancel := context.WithTimeout(context.Background(), timeout)\n    defer cancel()\n\n    _, err = client.ExamineBrokerClusterInfo(ctx)\n    return err\n}\n",[906,2398,2399,2425,2430,2446,2458,2478,2482,2495,2499,2519,2538,2542,2546,2562,2570,2574,2588,2595],{"__ignoreMap":11},[945,2400,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2421,2423],{"class":947,"line":948},[945,2402,1031],{"class":951},[945,2404,1192],{"class":962},[945,2406,1196],{"class":1195},[945,2408,999],{"class":951},[945,2410,1071],{"class":955},[945,2412,1203],{"class":962},[945,2414,2388],{"class":955},[945,2416,1086],{"class":962},[945,2418,1510],{"class":951},[945,2420,1203],{"class":962},[945,2422,1037],{"class":951},[945,2424,963],{"class":962},[945,2426,2427],{"class":947,"line":966},[945,2428,2429],{"class":1010},"    \u002F\u002F ... options ...\n",[945,2431,2432,2434,2436,2438,2440,2442,2444],{"class":947,"line":981},[945,2433,1496],{"class":962},[945,2435,1313],{"class":951},[945,2437,1501],{"class":962},[945,2439,1504],{"class":955},[945,2441,1507],{"class":962},[945,2443,1510],{"class":951},[945,2445,1454],{"class":962},[945,2447,2448,2450,2452,2454,2456],{"class":947,"line":1014},[945,2449,1307],{"class":951},[945,2451,1520],{"class":962},[945,2453,1523],{"class":951},[945,2455,1440],{"class":1439},[945,2457,963],{"class":962},[945,2459,2460,2462,2465,2467,2469,2472,2474,2476],{"class":947,"line":1025},[945,2461,1533],{"class":951},[945,2463,2464],{"class":962}," fmt.",[945,2466,1446],{"class":955},[945,2468,1086],{"class":962},[945,2470,2471],{"class":1413},"\"创建测试客户端失败: ",[945,2473,1547],{"class":1439},[945,2475,1550],{"class":1413},[945,2477,1553],{"class":962},[945,2479,2480],{"class":947,"line":1043},[945,2481,1331],{"class":962},[945,2483,2484,2486,2488,2490,2492],{"class":947,"line":1049},[945,2485,1285],{"class":951},[945,2487,1570],{"class":962},[945,2489,1324],{"class":955},[945,2491,1845],{"class":962},[945,2493,2494],{"class":1010},"\u002F\u002F 注意：用完就关\n",[945,2496,2497],{"class":947,"line":1056},[945,2498,1053],{"emptyLinePlaceholder":1052},[945,2500,2501,2503,2505,2507,2509,2511,2513,2515,2517],{"class":947,"line":1077},[945,2502,1307],{"class":951},[945,2504,1520],{"class":962},[945,2506,1313],{"class":951},[945,2508,1570],{"class":962},[945,2510,1573],{"class":955},[945,2512,1576],{"class":962},[945,2514,1523],{"class":951},[945,2516,1440],{"class":1439},[945,2518,963],{"class":962},[945,2520,2521,2523,2525,2527,2529,2532,2534,2536],{"class":947,"line":1108},[945,2522,1533],{"class":951},[945,2524,2464],{"class":962},[945,2526,1446],{"class":955},[945,2528,1086],{"class":962},[945,2530,2531],{"class":1413},"\"启动测试客户端失败: ",[945,2533,1547],{"class":1439},[945,2535,1550],{"class":1413},[945,2537,1553],{"class":962},[945,2539,2540],{"class":947,"line":1113},[945,2541,1331],{"class":962},[945,2543,2544],{"class":947,"line":1118},[945,2545,1053],{"emptyLinePlaceholder":1052},[945,2547,2548,2550,2552,2554,2556,2558,2560],{"class":947,"line":1134},[945,2549,1626],{"class":962},[945,2551,1313],{"class":951},[945,2553,1631],{"class":962},[945,2555,1377],{"class":955},[945,2557,1636],{"class":962},[945,2559,1639],{"class":955},[945,2561,1642],{"class":962},[945,2563,2564,2566,2568],{"class":947,"line":1143},[945,2565,1285],{"class":951},[945,2567,1650],{"class":955},[945,2569,1280],{"class":962},[945,2571,2572],{"class":947,"line":1387},[945,2573,1053],{"emptyLinePlaceholder":1052},[945,2575,2576,2579,2581,2583,2585],{"class":947,"line":1395},[945,2577,2578],{"class":962},"    _, err ",[945,2580,1065],{"class":951},[945,2582,1570],{"class":962},[945,2584,1667],{"class":955},[945,2586,2587],{"class":962},"(ctx)\n",[945,2589,2590,2592],{"class":947,"line":1433},[945,2591,1137],{"class":951},[945,2593,2594],{"class":962}," err\n",[945,2596,2597],{"class":947,"line":1457},[945,2598,1046],{"class":962},[897,2600,2601,2604],{},[906,2602,2603],{},"defer client.Close()"," 确保无论成功失败都会释放资源。这是 Go 里资源清理的标准写法。",[892,2606,2607],{"id":2607},"自动重连重试",[897,2609,2610,2611,2614],{},"RocketMQ 的长连接偶尔会被 NAT、防火墙、Broker 重启等因素切断。Rocket-Leaf 用一个通用的 ",[906,2612,2613],{},"executeWithClientRetry"," 函数处理这种场景：",[937,2616,2618],{"className":939,"code":2617,"language":941,"meta":11,"style":11},"\u002F\u002F internal\u002Fservice\u002Fclient_retry.go\nfunc executeWithClientRetry(client *admin.Client, call func(*admin.Client) error) error {\n    err := call(client)\n    if err == nil {\n        return nil\n    }\n    if !isRetryableNetworkError(err) {\n        return err  \u002F\u002F 非网络错误，直接返回\n    }\n\n    manager := rocketmq.GetClientManager()\n    defaultNameServer := strings.TrimSpace(manager.GetDefaultConnection())\n    if defaultNameServer == \"\" {\n        return err\n    }\n\n    log.Printf(\"[Service] 检测到连接异常，准备重连默认连接并重试: %v\", err)\n\n    \u002F\u002F 移除旧默认客户端，触发后续懒加载重新建立连接\n    manager.RemoveClient(defaultNameServer)\n\n    retryClient, reconnectErr := manager.GetDefaultClient()\n    if reconnectErr != nil {\n        return fmt.Errorf(\"请求失败: %w；自动重连失败: %v\", err, reconnectErr)\n    }\n\n    return call(retryClient)\n}\n",[906,2619,2620,2625,2674,2687,2699,2706,2710,2723,2733,2737,2741,2755,2775,2788,2794,2798,2802,2820,2824,2829,2840,2844,2858,2871,2896,2900,2904,2913],{"__ignoreMap":11},[945,2621,2622],{"class":947,"line":948},[945,2623,2624],{"class":1010},"\u002F\u002F internal\u002Fservice\u002Fclient_retry.go\n",[945,2626,2627,2629,2632,2634,2637,2640,2642,2644,2646,2648,2651,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672],{"class":947,"line":966},[945,2628,1031],{"class":951},[945,2630,2631],{"class":955}," executeWithClientRetry",[945,2633,1086],{"class":962},[945,2635,2636],{"class":1195},"client",[945,2638,2639],{"class":951}," *",[945,2641,1002],{"class":955},[945,2643,975],{"class":962},[945,2645,1007],{"class":955},[945,2647,1217],{"class":962},[945,2649,2650],{"class":1195},"call",[945,2652,2653],{"class":951}," func",[945,2655,1086],{"class":962},[945,2657,999],{"class":951},[945,2659,1002],{"class":955},[945,2661,975],{"class":962},[945,2663,1007],{"class":955},[945,2665,1203],{"class":962},[945,2667,1037],{"class":951},[945,2669,1203],{"class":962},[945,2671,1037],{"class":951},[945,2673,963],{"class":962},[945,2675,2676,2679,2681,2684],{"class":947,"line":981},[945,2677,2678],{"class":962},"    err ",[945,2680,1313],{"class":951},[945,2682,2683],{"class":955}," call",[945,2685,2686],{"class":962},"(client)\n",[945,2688,2689,2691,2693,2695,2697],{"class":947,"line":1014},[945,2690,1307],{"class":951},[945,2692,1520],{"class":962},[945,2694,1410],{"class":951},[945,2696,1440],{"class":1439},[945,2698,963],{"class":962},[945,2700,2701,2703],{"class":947,"line":1025},[945,2702,1533],{"class":951},[945,2704,2705],{"class":1439}," nil\n",[945,2707,2708],{"class":947,"line":1043},[945,2709,1331],{"class":962},[945,2711,2712,2714,2717,2720],{"class":947,"line":1049},[945,2713,1307],{"class":951},[945,2715,2716],{"class":951}," !",[945,2718,2719],{"class":955},"isRetryableNetworkError",[945,2721,2722],{"class":962},"(err) {\n",[945,2724,2725,2727,2730],{"class":947,"line":1056},[945,2726,1533],{"class":951},[945,2728,2729],{"class":962}," err  ",[945,2731,2732],{"class":1010},"\u002F\u002F 非网络错误，直接返回\n",[945,2734,2735],{"class":947,"line":1077},[945,2736,1331],{"class":962},[945,2738,2739],{"class":947,"line":1108},[945,2740,1053],{"emptyLinePlaceholder":1052},[945,2742,2743,2746,2748,2751,2753],{"class":947,"line":1113},[945,2744,2745],{"class":962},"    manager ",[945,2747,1313],{"class":951},[945,2749,2750],{"class":962}," rocketmq.",[945,2752,1934],{"class":955},[945,2754,1280],{"class":962},[945,2756,2757,2760,2762,2764,2766,2769,2772],{"class":947,"line":1118},[945,2758,2759],{"class":962},"    defaultNameServer ",[945,2761,1313],{"class":951},[945,2763,1401],{"class":962},[945,2765,1404],{"class":955},[945,2767,2768],{"class":962},"(manager.",[945,2770,2771],{"class":955},"GetDefaultConnection",[945,2773,2774],{"class":962},"())\n",[945,2776,2777,2779,2782,2784,2786],{"class":947,"line":1134},[945,2778,1307],{"class":951},[945,2780,2781],{"class":962}," defaultNameServer ",[945,2783,1410],{"class":951},[945,2785,1414],{"class":1413},[945,2787,963],{"class":962},[945,2789,2790,2792],{"class":947,"line":1143},[945,2791,1533],{"class":951},[945,2793,2594],{"class":962},[945,2795,2796],{"class":947,"line":1387},[945,2797,1331],{"class":962},[945,2799,2800],{"class":947,"line":1395},[945,2801,1053],{"emptyLinePlaceholder":1052},[945,2803,2804,2806,2808,2810,2813,2816,2818],{"class":947,"line":1433},[945,2805,1724],{"class":962},[945,2807,1727],{"class":955},[945,2809,1086],{"class":962},[945,2811,2812],{"class":1413},"\"[Service] 检测到连接异常，准备重连默认连接并重试: ",[945,2814,2815],{"class":1439},"%v",[945,2817,1550],{"class":1413},[945,2819,1553],{"class":962},[945,2821,2822],{"class":947,"line":1457},[945,2823,1053],{"emptyLinePlaceholder":1052},[945,2825,2826],{"class":947,"line":1463},[945,2827,2828],{"class":1010},"    \u002F\u002F 移除旧默认客户端，触发后续懒加载重新建立连接\n",[945,2830,2831,2834,2837],{"class":947,"line":1483},[945,2832,2833],{"class":962},"    manager.",[945,2835,2836],{"class":955},"RemoveClient",[945,2838,2839],{"class":962},"(defaultNameServer)\n",[945,2841,2842],{"class":947,"line":1488},[945,2843,1053],{"emptyLinePlaceholder":1052},[945,2845,2846,2849,2851,2854,2856],{"class":947,"line":1493},[945,2847,2848],{"class":962},"    retryClient, reconnectErr ",[945,2850,1313],{"class":951},[945,2852,2853],{"class":962}," manager.",[945,2855,1982],{"class":955},[945,2857,1280],{"class":962},[945,2859,2860,2862,2865,2867,2869],{"class":947,"line":1515},[945,2861,1307],{"class":951},[945,2863,2864],{"class":962}," reconnectErr ",[945,2866,1523],{"class":951},[945,2868,1440],{"class":1439},[945,2870,963],{"class":962},[945,2872,2873,2875,2877,2879,2881,2884,2886,2889,2891,2893],{"class":947,"line":1530},[945,2874,1533],{"class":951},[945,2876,2464],{"class":962},[945,2878,1446],{"class":955},[945,2880,1086],{"class":962},[945,2882,2883],{"class":1413},"\"请求失败: ",[945,2885,1547],{"class":1439},[945,2887,2888],{"class":1413},"；自动重连失败: ",[945,2890,2815],{"class":1439},[945,2892,1550],{"class":1413},[945,2894,2895],{"class":962},", err, reconnectErr)\n",[945,2897,2898],{"class":947,"line":1556},[945,2899,1331],{"class":962},[945,2901,2902],{"class":947,"line":1561},[945,2903,1053],{"emptyLinePlaceholder":1052},[945,2905,2906,2908,2910],{"class":947,"line":1585},[945,2907,1137],{"class":951},[945,2909,2683],{"class":955},[945,2911,2912],{"class":962},"(retryClient)\n",[945,2914,2915],{"class":947,"line":1607},[945,2916,1046],{"class":962},[1772,2918,2919],{"id":2919},"网络错误识别",[897,2921,2922],{},"没有通用的错误类型可以表示\"连接断了\"，只能按关键字匹配：",[937,2924,2926],{"className":939,"code":2925,"language":941,"meta":11,"style":11},"func isRetryableNetworkError(err error) bool {\n    if err == nil {\n        return false\n    }\n    errMsg := strings.ToLower(err.Error())\n    indicators := []string{\n        \"broken pipe\",\n        \"connection reset by peer\",\n        \"use of closed network connection\",\n        \"connection refused\",\n        \"no route to host\",\n        \"network is unreachable\",\n        \"i\u002Fo timeout\",\n        \"eof\",\n        \"发送数据失败\",\n        \"所有 nameserver 请求失败\",\n    }\n    for _, indicator := range indicators {\n        if strings.Contains(errMsg, indicator) {\n            return true\n        }\n    }\n    return false\n}\n",[906,2927,2928,2950,2962,2969,2973,2993,3006,3013,3020,3027,3034,3041,3048,3055,3062,3069,3076,3080,3096,3108,3115,3119,3123,3129],{"__ignoreMap":11},[945,2929,2930,2932,2935,2937,2940,2943,2945,2948],{"class":947,"line":948},[945,2931,1031],{"class":951},[945,2933,2934],{"class":955}," isRetryableNetworkError",[945,2936,1086],{"class":962},[945,2938,2939],{"class":1195},"err",[945,2941,2942],{"class":951}," error",[945,2944,1203],{"class":962},[945,2946,2947],{"class":951},"bool",[945,2949,963],{"class":962},[945,2951,2952,2954,2956,2958,2960],{"class":947,"line":966},[945,2953,1307],{"class":951},[945,2955,1520],{"class":962},[945,2957,1410],{"class":951},[945,2959,1440],{"class":1439},[945,2961,963],{"class":962},[945,2963,2964,2966],{"class":947,"line":981},[945,2965,1533],{"class":951},[945,2967,2968],{"class":1439}," false\n",[945,2970,2971],{"class":947,"line":1014},[945,2972,1331],{"class":962},[945,2974,2975,2978,2980,2982,2985,2988,2991],{"class":947,"line":1025},[945,2976,2977],{"class":962},"    errMsg ",[945,2979,1313],{"class":951},[945,2981,1401],{"class":962},[945,2983,2984],{"class":955},"ToLower",[945,2986,2987],{"class":962},"(err.",[945,2989,2990],{"class":955},"Error",[945,2992,2774],{"class":962},[945,2994,2995,2998,3000,3002,3004],{"class":947,"line":1043},[945,2996,2997],{"class":962},"    indicators ",[945,2999,1313],{"class":951},[945,3001,1345],{"class":962},[945,3003,993],{"class":951},[945,3005,1074],{"class":962},[945,3007,3008,3011],{"class":947,"line":1049},[945,3009,3010],{"class":1413},"        \"broken pipe\"",[945,3012,1231],{"class":962},[945,3014,3015,3018],{"class":947,"line":1056},[945,3016,3017],{"class":1413},"        \"connection reset by peer\"",[945,3019,1231],{"class":962},[945,3021,3022,3025],{"class":947,"line":1077},[945,3023,3024],{"class":1413},"        \"use of closed network connection\"",[945,3026,1231],{"class":962},[945,3028,3029,3032],{"class":947,"line":1108},[945,3030,3031],{"class":1413},"        \"connection refused\"",[945,3033,1231],{"class":962},[945,3035,3036,3039],{"class":947,"line":1113},[945,3037,3038],{"class":1413},"        \"no route to host\"",[945,3040,1231],{"class":962},[945,3042,3043,3046],{"class":947,"line":1118},[945,3044,3045],{"class":1413},"        \"network is unreachable\"",[945,3047,1231],{"class":962},[945,3049,3050,3053],{"class":947,"line":1134},[945,3051,3052],{"class":1413},"        \"i\u002Fo timeout\"",[945,3054,1231],{"class":962},[945,3056,3057,3060],{"class":947,"line":1143},[945,3058,3059],{"class":1413},"        \"eof\"",[945,3061,1231],{"class":962},[945,3063,3064,3067],{"class":947,"line":1387},[945,3065,3066],{"class":1413},"        \"发送数据失败\"",[945,3068,1231],{"class":962},[945,3070,3071,3074],{"class":947,"line":1395},[945,3072,3073],{"class":1413},"        \"所有 nameserver 请求失败\"",[945,3075,1231],{"class":962},[945,3077,3078],{"class":947,"line":1433},[945,3079,1331],{"class":962},[945,3081,3082,3085,3088,3090,3093],{"class":947,"line":1457},[945,3083,3084],{"class":951},"    for",[945,3086,3087],{"class":962}," _, indicator ",[945,3089,1313],{"class":951},[945,3091,3092],{"class":951}," range",[945,3094,3095],{"class":962}," indicators {\n",[945,3097,3098,3100,3102,3105],{"class":947,"line":1463},[945,3099,1398],{"class":951},[945,3101,1401],{"class":962},[945,3103,3104],{"class":955},"Contains",[945,3106,3107],{"class":962},"(errMsg, indicator) {\n",[945,3109,3110,3112],{"class":947,"line":1483},[945,3111,1436],{"class":951},[945,3113,3114],{"class":1439}," true\n",[945,3116,3117],{"class":947,"line":1488},[945,3118,1460],{"class":962},[945,3120,3121],{"class":947,"line":1493},[945,3122,1331],{"class":962},[945,3124,3125,3127],{"class":947,"line":1515},[945,3126,1137],{"class":951},[945,3128,2968],{"class":1439},[945,3130,3131],{"class":947,"line":1530},[945,3132,1046],{"class":962},[3134,3135,3137],"warning",{"title":3136},"字符串匹配的代价",[897,3138,3139,3140,3142,3143,3146,3147,3150],{},"按错误消息匹配的做法不够严谨 —— 上游库改动错误文案就会悄悄失效。更健壮的方式是 ",[906,3141,1902],{}," 到具体的 ",[906,3144,3145],{},"*net.OpError"," 等结构体。这里用字符串匹配是为了兼容 ",[906,3148,3149],{},"rocketmq-admin-go"," 里包装过的中文错误，是一种工程取舍。",[1772,3152,3153],{"id":3153},"调用方式",[897,3155,3156],{},"业务 service 调用时只要包一层：",[937,3158,3160],{"className":939,"code":3159,"language":941,"meta":11,"style":11},"func (s *TopicService) GetTopics() ([]*model.Topic, error) {\n    client, err := rocketmq.GetClientManager().GetDefaultClient()\n    if err != nil {\n        return nil, err\n    }\n    var topics []*model.Topic\n    err = executeWithClientRetry(client, func(c *admin.Client) error {\n        result, cErr := c.FetchAllTopicList(context.Background())\n        if cErr != nil {\n            return cErr\n        }\n        topics = convertTopics(result)\n        return nil\n    })\n    return topics, err\n}\n",[906,3161,3162,3200,3217,3229,3238,3242,3259,3291,3310,3323,3330,3334,3347,3353,3358,3365],{"__ignoreMap":11},[945,3163,3164,3166,3168,3171,3173,3176,3178,3181,3184,3186,3189,3191,3194,3196,3198],{"class":947,"line":948},[945,3165,1031],{"class":951},[945,3167,1192],{"class":962},[945,3169,3170],{"class":1195},"s ",[945,3172,999],{"class":951},[945,3174,3175],{"class":955},"TopicService",[945,3177,1203],{"class":962},[945,3179,3180],{"class":955},"GetTopics",[945,3182,3183],{"class":962},"() ([]",[945,3185,999],{"class":951},[945,3187,3188],{"class":955},"model",[945,3190,975],{"class":962},[945,3192,3193],{"class":955},"Topic",[945,3195,1217],{"class":962},[945,3197,1037],{"class":951},[945,3199,1269],{"class":962},[945,3201,3202,3204,3206,3208,3210,3213,3215],{"class":947,"line":966},[945,3203,1496],{"class":962},[945,3205,1313],{"class":951},[945,3207,2750],{"class":962},[945,3209,1934],{"class":955},[945,3211,3212],{"class":962},"().",[945,3214,1982],{"class":955},[945,3216,1280],{"class":962},[945,3218,3219,3221,3223,3225,3227],{"class":947,"line":981},[945,3220,1307],{"class":951},[945,3222,1520],{"class":962},[945,3224,1523],{"class":951},[945,3226,1440],{"class":1439},[945,3228,963],{"class":962},[945,3230,3231,3233,3235],{"class":947,"line":1014},[945,3232,1533],{"class":951},[945,3234,1440],{"class":1439},[945,3236,3237],{"class":962},", err\n",[945,3239,3240],{"class":947,"line":1025},[945,3241,1331],{"class":962},[945,3243,3244,3247,3250,3252,3254,3256],{"class":947,"line":1043},[945,3245,3246],{"class":951},"    var",[945,3248,3249],{"class":962}," topics []",[945,3251,999],{"class":951},[945,3253,3188],{"class":955},[945,3255,975],{"class":962},[945,3257,3258],{"class":955},"Topic\n",[945,3260,3261,3263,3265,3267,3270,3272,3274,3277,3279,3281,3283,3285,3287,3289],{"class":947,"line":1049},[945,3262,2678],{"class":962},[945,3264,1065],{"class":951},[945,3266,2631],{"class":955},[945,3268,3269],{"class":962},"(client, ",[945,3271,1031],{"class":951},[945,3273,1086],{"class":962},[945,3275,3276],{"class":1195},"c",[945,3278,2639],{"class":951},[945,3280,1002],{"class":955},[945,3282,975],{"class":962},[945,3284,1007],{"class":955},[945,3286,1203],{"class":962},[945,3288,1037],{"class":951},[945,3290,963],{"class":962},[945,3292,3293,3296,3298,3301,3304,3306,3308],{"class":947,"line":1056},[945,3294,3295],{"class":962},"        result, cErr ",[945,3297,1313],{"class":951},[945,3299,3300],{"class":962}," c.",[945,3302,3303],{"class":955},"FetchAllTopicList",[945,3305,1636],{"class":962},[945,3307,1639],{"class":955},[945,3309,2774],{"class":962},[945,3311,3312,3314,3317,3319,3321],{"class":947,"line":1077},[945,3313,1398],{"class":951},[945,3315,3316],{"class":962}," cErr ",[945,3318,1523],{"class":951},[945,3320,1440],{"class":1439},[945,3322,963],{"class":962},[945,3324,3325,3327],{"class":947,"line":1108},[945,3326,1436],{"class":951},[945,3328,3329],{"class":962}," cErr\n",[945,3331,3332],{"class":947,"line":1113},[945,3333,1460],{"class":962},[945,3335,3336,3339,3341,3344],{"class":947,"line":1118},[945,3337,3338],{"class":962},"        topics ",[945,3340,1065],{"class":951},[945,3342,3343],{"class":955}," convertTopics",[945,3345,3346],{"class":962},"(result)\n",[945,3348,3349,3351],{"class":947,"line":1134},[945,3350,1533],{"class":951},[945,3352,2705],{"class":1439},[945,3354,3355],{"class":947,"line":1143},[945,3356,3357],{"class":962},"    })\n",[945,3359,3360,3362],{"class":947,"line":1387},[945,3361,1137],{"class":951},[945,3363,3364],{"class":962}," topics, err\n",[945,3366,3367],{"class":947,"line":1395},[945,3368,1046],{"class":962},[1150,3370,3371,3380,3392],{},[914,3372,3373,3375,3376,3379],{},[906,3374,2613],{}," 只尝试",[901,3377,3378],{},"一次","重连，避免出现无限重试",[914,3381,3382,3383,3386,3387,3389,3390],{},"重连后用的是",[901,3384,3385],{},"新的 client","，旧的 client 在 ",[906,3388,2836],{}," 里已经被 ",[906,3391,1781],{},[914,3393,3394,3395,3398],{},"非网络错误（例如权限不足、参数错误）会",[901,3396,3397],{},"直接","返回，不浪费一次重连",[892,3400,3401],{"id":3401},"关闭资源",[937,3403,3405],{"className":939,"code":3404,"language":941,"meta":11,"style":11},"func (m *AdminClientManager) CloseAll() {\n    m.mu.Lock()\n    defer m.mu.Unlock()\n    for nameServer, client := range m.clients {\n        client.Close()\n        delete(m.clients, nameServer)\n    }\n    m.defaultConn = \"\"\n}\n",[906,3406,3407,3427,3435,3445,3459,3467,3475,3479,3489],{"__ignoreMap":11},[945,3408,3409,3411,3413,3415,3417,3419,3421,3424],{"class":947,"line":948},[945,3410,1031],{"class":951},[945,3412,1192],{"class":962},[945,3414,1196],{"class":1195},[945,3416,999],{"class":951},[945,3418,1071],{"class":955},[945,3420,1203],{"class":962},[945,3422,3423],{"class":955},"CloseAll",[945,3425,3426],{"class":962},"() {\n",[945,3428,3429,3431,3433],{"class":947,"line":966},[945,3430,1274],{"class":962},[945,3432,1277],{"class":955},[945,3434,1280],{"class":962},[945,3436,3437,3439,3441,3443],{"class":947,"line":981},[945,3438,1285],{"class":951},[945,3440,1288],{"class":962},[945,3442,1291],{"class":955},[945,3444,1280],{"class":962},[945,3446,3447,3449,3452,3454,3456],{"class":947,"line":1014},[945,3448,3084],{"class":951},[945,3450,3451],{"class":962}," nameServer, client ",[945,3453,1313],{"class":951},[945,3455,3092],{"class":951},[945,3457,3458],{"class":962}," m.clients {\n",[945,3460,3461,3463,3465],{"class":947,"line":1025},[945,3462,1682],{"class":962},[945,3464,1324],{"class":955},[945,3466,1280],{"class":962},[945,3468,3469,3472],{"class":947,"line":1043},[945,3470,3471],{"class":955},"        delete",[945,3473,3474],{"class":962},"(m.clients, nameServer)\n",[945,3476,3477],{"class":947,"line":1049},[945,3478,1331],{"class":962},[945,3480,3481,3484,3486],{"class":947,"line":1056},[945,3482,3483],{"class":962},"    m.defaultConn ",[945,3485,1065],{"class":951},[945,3487,3488],{"class":1413}," \"\"\n",[945,3490,3491],{"class":947,"line":1077},[945,3492,1046],{"class":962},[897,3494,3495,3496,3498],{},"应用退出时调用 ",[906,3497,3423],{},"，确保所有 goroutine 正常结束、TCP 连接被释放。这一步在桌面应用里很容易被忽略，但对保持良好的系统状态很重要。",[892,3500,3501],{"id":3501},"设计小结",[3503,3504,3505,3518],"table",{},[3506,3507,3508],"thead",{},[3509,3510,3511,3515],"tr",{},[3512,3513,3514],"th",{},"问题",[3512,3516,3517],{},"解决思路",[3519,3520,3521,3530,3538,3550,3560,3571,3581],"tbody",{},[3509,3522,3523,3527],{},[3524,3525,3526],"td",{},"多集群切换",[3524,3528,3529],{},"按 NameServer 地址的客户端池",[3509,3531,3532,3535],{},[3524,3533,3534],{},"启动慢",[3524,3536,3537],{},"默认连接懒加载，首次访问时才连",[3509,3539,3540,3543],{},[3524,3541,3542],{},"连接验证",[3524,3544,3545,3547,3548],{},[906,3546,1206],{}," 里同步调用 ",[906,3549,1667],{},[3509,3551,3552,3555],{},[3524,3553,3554],{},"依赖方向",[3524,3556,3557,3559],{},[906,3558,2312],{}," 不依赖 service，通过回调注入",[3509,3561,3562,3565],{},[3524,3563,3564],{},"线程安全",[3524,3566,3567,3570],{},[906,3568,3569],{},"sync.RWMutex","，注意锁外调用 initializer",[3509,3572,3573,3576],{},[3524,3574,3575],{},"连接断线",[3524,3577,3578,3580],{},[906,3579,2613],{}," 按错误文本识别 + 单次重连",[3509,3582,3583,3586],{},[3524,3584,3585],{},"资源泄露",[3524,3587,3588,3589,3591,3592,3594,3595],{},"覆盖时 ",[906,3590,1324],{},"，失败时 ",[906,3593,1324],{},"，退出时 ",[906,3596,3423],{},[897,3598,3599],{},"下一章看敏感信息加密存储的实现。",[3601,3602,3603],"style",{},"html pre.shiki code .s8jYJ, html code.shiki .s8jYJ{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .snPdu, html code.shiki .snPdu{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sxrX7, html code.shiki .sxrX7{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sCsY4, html code.shiki .sCsY4{--shiki-light:#6A737D;--shiki-default:#6A737D;--shiki-dark:#6A737D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sP4rz, html code.shiki .sP4rz{--shiki-light:#E36209;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sIIMD, html code.shiki .sIIMD{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sBjJW, html code.shiki .sBjJW{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":11,"searchDepth":966,"depth":966,"links":3605},[3606,3607,3608,3613,3617,3618,3622,3623],{"id":894,"depth":966,"text":895},{"id":935,"depth":966,"text":935},{"id":1180,"depth":966,"text":1180,"children":3609},[3610,3611,3612],{"id":1774,"depth":981,"text":1775},{"id":1791,"depth":981,"text":1792},{"id":1885,"depth":981,"text":1886},{"id":1906,"depth":966,"text":1906,"children":3614},[3615,3616],{"id":1976,"depth":981,"text":1976},{"id":2341,"depth":981,"text":2341},{"id":2383,"depth":966,"text":2383},{"id":2607,"depth":966,"text":2607,"children":3619},[3620,3621],{"id":2919,"depth":981,"text":2919},{"id":3153,"depth":981,"text":3153},{"id":3401,"depth":966,"text":3401},{"id":3501,"depth":966,"text":3501},"md",{},{"title":585,"description":586},"project\u002Frocket-leaf\u002Fclient-manager","aOraaj-46CRrFxZlI2lS1vC4TZl3ly4-8rg3kJ9Hg6w",1775474633960]