[{"content":"博客开张，随便写点什么。\n为什么写博客 工作这么多年，一直没有认真打理过一个技术博客。这次用 Hugo + PaperMod 搭了这个站点，顺便记录一些踩坑经历和技术思考。\n选 Hugo 的原因很简单：快、简单、不需要数据库。Markdown 写文章，Git 管理版本，Nginx 静态托管，完美。\n博客定位 主要记录这几类内容：\n🛠️ 技术实践 — 踩过的坑、做过的项目、代码片段 📖 学习笔记 — Go、Rust、C++、前端这些日常接触的技术 💡 随想 — 偶尔的技术思考和行业观察 不追求高频更新，只写有价值的东西。\n关于这个站 🏗️ Hugo + PaperMod 主题 🚀 跑在一台 Raspberry Pi 4 上 🌐 通过 Cloudflare 代理访问 ⚡ Nginx 托管，静态页面加载飞快 树莓派功耗低，7x24 小时开着跑博客挺合适。\n开头语 Talk is cheap, show me the code.\n这句话虽然被说烂了，但确实是程序员最好的写照。\n欢迎来访，欢迎交流。\n","permalink":"https://www.bvbej.com/posts/hello-world/","summary":"\u003cp\u003e博客开张，随便写点什么。\u003c/p\u003e\n\u003ch2 id=\"为什么写博客\"\u003e为什么写博客\u003c/h2\u003e\n\u003cp\u003e工作这么多年，一直没有认真打理过一个技术博客。这次用 Hugo + PaperMod 搭了这个站点，顺便记录一些踩坑经历和技术思考。\u003c/p\u003e\n\u003cp\u003e选 Hugo 的原因很简单：\u003cstrong\u003e快、简单、不需要数据库\u003c/strong\u003e。Markdown 写文章，Git 管理版本，Nginx 静态托管，完美。\u003c/p\u003e\n\u003ch2 id=\"博客定位\"\u003e博客定位\u003c/h2\u003e\n\u003cp\u003e主要记录这几类内容：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e🛠️ \u003cstrong\u003e技术实践\u003c/strong\u003e — 踩过的坑、做过的项目、代码片段\u003c/li\u003e\n\u003cli\u003e📖 \u003cstrong\u003e学习笔记\u003c/strong\u003e — Go、Rust、C++、前端这些日常接触的技术\u003c/li\u003e\n\u003cli\u003e💡 \u003cstrong\u003e随想\u003c/strong\u003e — 偶尔的技术思考和行业观察\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e不追求高频更新，只写有价值的东西。\u003c/p\u003e\n\u003ch2 id=\"关于这个站\"\u003e关于这个站\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e🏗️ Hugo + PaperMod 主题\u003c/li\u003e\n\u003cli\u003e🚀 跑在一台 Raspberry Pi 4 上\u003c/li\u003e\n\u003cli\u003e🌐 通过 Cloudflare 代理访问\u003c/li\u003e\n\u003cli\u003e⚡ Nginx 托管，静态页面加载飞快\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e树莓派功耗低，7x24 小时开着跑博客挺合适。\u003c/p\u003e\n\u003ch2 id=\"开头语\"\u003e开头语\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eTalk is cheap, show me the code.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这句话虽然被说烂了，但确实是程序员最好的写照。\u003c/p\u003e\n\u003cp\u003e欢迎来访，欢迎交流。\u003c/p\u003e","title":"Hello World"},{"content":"你好 我是 BvBeJ，一名高级开发工程师。\n热爱技术，专注于系统底层与高性能服务开发。\n技术栈 后端 语言 描述 Go 主要工作语言，微服务、并发处理 Rust 系统编程，追求极致性能 C++ 基础架构，游戏引擎相关 前端 框架 描述 Vue3 日常前端开发 TypeScript 类型安全 Node.js 工具链、BFF层 DevOps Docker · Kubernetes · Linux · Git · CI/CD\n关于这个博客 记录技术探索与日常折腾。\n\u0026ldquo;Talk is cheap, show me the code.\u0026rdquo;\n🛠️ Hugo + PaperMod 构建 🚀 跑在 Raspberry Pi 上 ⚡ 追求简洁与性能 联系方式 GitHub: BvBeJ 邮箱: contact@bvbej.com ","permalink":"https://www.bvbej.com/about/","summary":"\u003ch2 id=\"你好\"\u003e你好\u003c/h2\u003e\n\u003cp\u003e我是 \u003cstrong\u003eBvBeJ\u003c/strong\u003e，一名高级开发工程师。\u003c/p\u003e\n\u003cp\u003e热爱技术，专注于系统底层与高性能服务开发。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"技术栈\"\u003e技术栈\u003c/h2\u003e\n\u003ch3 id=\"后端\"\u003e后端\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e语言\u003c/th\u003e\n          \u003cth\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eGo\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e主要工作语言，微服务、并发处理\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eRust\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e系统编程，追求极致性能\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eC++\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e基础架构，游戏引擎相关\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"前端\"\u003e前端\u003c/h3\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e框架\u003c/th\u003e\n          \u003cth\u003e描述\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eVue3\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e日常前端开发\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eTypeScript\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e类型安全\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003cstrong\u003eNode.js\u003c/strong\u003e\u003c/td\u003e\n          \u003ctd\u003e工具链、BFF层\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch3 id=\"devops\"\u003eDevOps\u003c/h3\u003e\n\u003cp\u003eDocker · Kubernetes · Linux · Git · CI/CD\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"关于这个博客\"\u003e关于这个博客\u003c/h2\u003e\n\u003cp\u003e记录技术探索与日常折腾。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cem\u003e\u0026ldquo;Talk is cheap, show me the code.\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e🛠️ Hugo + PaperMod 构建\u003c/li\u003e\n\u003cli\u003e🚀 跑在 Raspberry Pi 上\u003c/li\u003e\n\u003cli\u003e⚡ 追求简洁与性能\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"联系方式\"\u003e联系方式\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGitHub: \u003ca href=\"https://github.com\"\u003eBvBeJ\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e邮箱: \u003ca href=\"mailto:contact@bvbej.com\"\u003econtact@bvbej.com\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"关于"},{"content":"背景 微服务架构下，服务间调用链路错综复杂。一旦出问题，没有可观测性支撑，排查起来就是噩梦。\n可观测性三驾马车：日志（Logs）、指标（Metrics）、追踪（Traces）。\n日志：结构化日志是基础 别再用 fmt.Printf 了，结构化日志才是正道：\nimport \u0026#34;github.com/rs/zerolog\u0026#34; func main() { log := zerolog.New(os.Stdout). With(). Timestamp(). Caller(). Logger() log.Info(). Str(\u0026#34;service\u0026#34;, \u0026#34;user-service\u0026#34;). Int(\u0026#34;request_id\u0026#34;, 12345). Msg(\u0026#34;User login successful\u0026#34;) } 输出：\n{\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;service\u0026#34;:\u0026#34;user-service\u0026#34;,\u0026#34;request_id\u0026#34;:12345,\u0026#34;time\u0026#34;:\u0026#34;2026-04-11T10:00:00Z\u0026#34;,\u0026#34;caller\u0026#34;:\u0026#34;main.go:25\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;User login successful\u0026#34;} 指标：Prometheus + Grafana import \u0026#34;github.com/prometheus/client_golang/prometheus\u0026#34; import \u0026#34;github.com/prometheus/client_golang/prometheus/promhttp\u0026#34; var ( httpRequests = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: \u0026#34;http_requests_total\u0026#34;, Help: \u0026#34;Total HTTP requests\u0026#34;, }, []string{\u0026#34;method\u0026#34;, \u0026#34;path\u0026#34;, \u0026#34;status\u0026#34;}, ) httpDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: \u0026#34;http_request_duration_seconds\u0026#34;, Buckets: prometheus.DefBuckets, }, []string{\u0026#34;method\u0026#34;, \u0026#34;path\u0026#34;}, ) ) func init() { prometheus.MustRegister(httpRequests, httpDuration) } // 中间件示例 func promMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() rw := \u0026amp;responseWriter{ResponseWriter: w, statusCode: 200} next.ServeHTTP(rw, r) duration := time.Since(start).Seconds() httpRequests.WithLabelValues(r.Method, r.URL.Path, strconv.Itoa(rw.statusCode)).Inc() httpDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration) }) } 分布式追踪：OpenTelemetry import \u0026#34;go.opentelemetry.io/otel\u0026#34; import \u0026#34;go.opentelemetry.io/otel/exporters/jaeger\u0026#34; import \u0026#34;go.opentelemetry.io/otel/sdk/trace\u0026#34; func initTracer() (func(), error) { exp, err := jaeger.New(jaeger.WithAgentEndpoint()) if err != nil { return nil, err } tp := trace.NewTracerProvider( trace.WithBatcher(exp), trace.WithSampler(trace.AlwaysSample()), ) otel.SetTracerProvider(tp) return func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() tp.Shutdown(ctx) }, nil } // 在 HTTP handler 中使用 func handleGetUser(w http.ResponseWriter, r *http.Request) { ctx, span := otel.Tracer(\u0026#34;user-service\u0026#34;).Start(r.Context(), \u0026#34;GetUser\u0026#34;) defer span.End() span.SetAttributes( attribute.String(\u0026#34;user.id\u0026#34;, r.URL.Query().Get(\u0026#34;id\u0026#34;)), ) user, err := getUserFromDB(ctx, r.URL.Query().Get(\u0026#34;id\u0026#34;)) if err != nil { span.RecordError(err) // ... } // 传递给后续调用 go someAsyncOperation(ctx, user) } 三者结合：一个完整示例 type UserService struct { logger zerolog.Logger tracer trace.Tracer metrics *UserMetrics userRepo *UserRepository } func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) { // 1. 开始追踪 ctx, span := s.tracer.Start(ctx, \u0026#34;UserService.GetUser\u0026#34;) defer span.End() span.SetAttributes(attribute.String(\u0026#34;user.id\u0026#34;, id)) // 2. 记录指标 s.metrics.requests.Inc() timer := s.metrics.duration.NewTimer() // 3. 结构化日志 s.logger.Info(). Str(\u0026#34;user_id\u0026#34;, id). Str(\u0026#34;trace_id\u0026#34;, span.SpanContext().TraceID().String()). Msg(\u0026#34;Fetching user\u0026#34;) // 4. 业务逻辑 user, err := s.userRepo.FindByID(ctx, id) if err != nil { // 记录错误，包含追踪上下文 s.logger.Error(). Err(err). Str(\u0026#34;user_id\u0026#34;, id). Str(\u0026#34;trace_id\u0026#34;, span.SpanContext().TraceID().String()). Msg(\u0026#34;Failed to fetch user\u0026#34;) span.RecordError(err) s.metrics.errors.Inc() return nil, err } timer.ObserveDuration() return user, nil } 可视化：用 Grafana 大盘 常见 Dashboard 布局：\n┌─────────────────────────────────────────────────────┐ │ 请求量 (QPS) │ 延迟 P99 │ 错误率 │ ├─────────────────────────────────────────────────────┤ │ │ │ 请求量折线图 │ │ │ ├─────────────────────────────────────────────────────┤ │ 服务 A → 服务 B → 服务 C (追踪链路) │ └─────────────────────────────────────────────────────┘ 总结 可观测性不是可选项，是微服务必须具备的能力。\n维度 工具 用途 日志 zerolog / zap + Loki 排查问题、审计 指标 Prometheus + Grafana 容量规划、告警 追踪 OpenTelemetry + Jaeger 链路分析、性能瓶颈 投入时间做可观测性，出问题时会感谢自己。\n下一步可以聊聊怎么用 eBPF 做零侵扰的追踪。\n","permalink":"https://www.bvbej.com/posts/go-microservices-observability/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e微服务架构下，服务间调用链路错综复杂。一旦出问题，没有可观测性支撑，排查起来就是噩梦。\u003c/p\u003e\n\u003cp\u003e可观测性三驾马车：\u003cstrong\u003e日志（Logs）\u003c/strong\u003e、\u003cstrong\u003e指标（Metrics）\u003c/strong\u003e、\u003cstrong\u003e追踪（Traces）\u003c/strong\u003e。\u003c/p\u003e\n\u003ch2 id=\"日志结构化日志是基础\"\u003e日志：结构化日志是基础\u003c/h2\u003e\n\u003cp\u003e别再用 \u003ccode\u003efmt.Printf\u003c/code\u003e 了，结构化日志才是正道：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/rs/zerolog\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ezerolog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStdout\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eWith\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eTimestamp\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eCaller\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInfo\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eStr\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;service\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user-service\u0026#34;\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eInt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;request_id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e12345\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;User login successful\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\u003cspan style=\"color:#f92672\"\u003e\u0026#34;level\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;info\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;service\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user-service\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;request_id\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e12345\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2026-04-11T10:00:00Z\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;caller\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;main.go:25\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;User login successful\u0026#34;\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"指标prometheus--grafana\"\u003e指标：Prometheus + Grafana\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/prometheus/client_golang/prometheus\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/prometheus/client_golang/prometheus/promhttp\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ehttpRequests\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewCounterVec\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCounterOpts\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http_requests_total\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eHelp\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Total HTTP requests\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;method\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;path\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ehttpDuration\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewHistogramVec\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eHistogramOpts\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e:    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;http_request_duration_seconds\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eBuckets\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDefBuckets\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;method\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;path\u0026#34;\u003c/span\u003e},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003einit\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eprometheus\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMustRegister\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ehttpRequests\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ehttpDuration\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 中间件示例\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epromMiddleware\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003enext\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eHandler\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eHandler\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eHandlerFunc\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResponseWriter\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003estart\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNow\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003erw\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eresponseWriter\u003c/span\u003e{\u003cspan style=\"color:#a6e22e\"\u003eResponseWriter\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003ew\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estatusCode\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003enext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eServeHTTP\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003erw\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eduration\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSince\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003estart\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eSeconds\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ehttpRequests\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithLabelValues\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMethod\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eURL\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePath\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estrconv\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eItoa\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003erw\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003estatusCode\u003c/span\u003e)).\u003cspan style=\"color:#a6e22e\"\u003eInc\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ehttpDuration\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithLabelValues\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMethod\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eURL\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePath\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eObserve\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eduration\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"分布式追踪opentelemetry\"\u003e分布式追踪：OpenTelemetry\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;go.opentelemetry.io/otel\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;go.opentelemetry.io/otel/exporters/jaeger\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;go.opentelemetry.io/otel/sdk/trace\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003einitTracer\u003c/span\u003e() (\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e(), \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eexp\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ejaeger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ejaeger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithAgentEndpoint\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etrace\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewTracerProvider\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003etrace\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithBatcher\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eexp\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003etrace\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithSampler\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etrace\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAlwaysSample\u003c/span\u003e()),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eotel\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetTracerProvider\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003etp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ecancel\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithTimeout\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eBackground\u003c/span\u003e(), \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecancel\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003etp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eShutdown\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 在 HTTP handler 中使用\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehandleGetUser\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResponseWriter\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ehttp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eotel\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTracer\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user-service\u0026#34;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eStart\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;GetUser\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eEnd\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetAttributes\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eattribute\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user.id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eURL\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eQuery\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eGet\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e)),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetUserFromDB\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eURL\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eQuery\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eGet\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;id\u0026#34;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRecordError\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 传递给后续调用\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esomeAsyncOperation\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"三者结合一个完整示例\"\u003e三者结合：一个完整示例\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserService\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003elogger\u003c/span\u003e       \u003cspan style=\"color:#a6e22e\"\u003ezerolog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etracer\u003c/span\u003e      \u003cspan style=\"color:#a6e22e\"\u003etrace\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTracer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emetrics\u003c/span\u003e     \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eUserMetrics\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003euserRepo\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eUserRepository\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eUserService\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eGetUser\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e) (\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eUser\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 1. 开始追踪\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003etracer\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStart\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;UserService.GetUser\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eEnd\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetAttributes\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eattribute\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user.id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 2. 记录指标\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emetrics\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003erequests\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInc\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etimer\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emetrics\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eduration\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewTimer\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 3. 结构化日志\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elogger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInfo\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eStr\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user_id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eStr\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;trace_id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpanContext\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eTraceID\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e()).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Fetching user\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 4. 业务逻辑\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003euserRepo\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFindByID\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 记录错误，包含追踪上下文\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elogger\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e().\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eErr\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eStr\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;user_id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eStr\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;trace_id\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpanContext\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eTraceID\u003c/span\u003e().\u003cspan style=\"color:#a6e22e\"\u003eString\u003c/span\u003e()).\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eMsg\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Failed to fetch user\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003espan\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRecordError\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003es\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emetrics\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eerrors\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInc\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etimer\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObserveDuration\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"可视化用-grafana-大盘\"\u003e可视化：用 Grafana 大盘\u003c/h2\u003e\n\u003cp\u003e常见 Dashboard 布局：\u003c/p\u003e","title":"Go 微服务可观测性：日志、指标、追踪实战"},{"content":"背景 Go 的并发模型是其最强大的特性之一。goroutine + channel 的组合让我们能以极低的成本构建高性能的并发系统。\n今天聊聊 Pipeline 模式——一种将数据处理流程抽象为一系列阶段的编程范式。\n什么是 Pipeline 想象工厂流水线：原料从一端进入，经过多个工序处理，最终成品从另一端出来。\nfunc main() { // 生成数据 data := generate(1, 2, 3, 4, 5) // 流水线：平方 -\u0026gt; 过滤偶数 -\u0026gt; 输出 result := pipeline(data, square, filterEven, printResult, ) \u0026lt;-result.done // 等待完成 } 实战：图片处理流水线 假设我们要处理一批图片：下载 → 缩放 → 添加水印 → 上传。\ntype Image struct { URL string Data []byte } func ProcessImages(urls []string) error { downloads := make(chan Image, 100) resized := make(chan Image, 100) watermarked := make(chan Image, 100) var wg sync.WaitGroup // 下载阶段 wg.Add(1) go func() { defer wg.Done() for _, url := range urls { img, err := download(url) if err != nil { log.Printf(\u0026#34;下载失败: %v\u0026#34;, err) continue } downloads \u0026lt;- img } close(downloads) }() // 缩放阶段 (3个worker) for i := 0; i \u0026lt; 3; i++ { wg.Add(1) go func() { defer wg.Done() for img := range downloads { resizedImg, _ := resize(img, 800, 600) resized \u0026lt;- resizedImg } }() } // 水印阶段 (2个worker) for i := 0; i \u0026lt; 2; i++ { wg.Add(1) go func() { defer wg.Done() for img := range resized { watermarkedImg, _ := watermark(img, \u0026#34;© My Blog\u0026#34;) watermarked \u0026lt;- watermarkedImg } }() } // 上传阶段 wg.Add(1) go func() { defer wg.Done() for img := range watermarked { if err := upload(img); err != nil { log.Printf(\u0026#34;上传失败: %v\u0026#34;, err) } } }() wg.Wait() return nil } 优雅的错误处理 Pipeline 中如何处理错误？一个不错的方案是用错误 channel：\nfunc ProcessWithErrors(urls []string) { data := generate(urls) result := make(chan Result) errs := make(chan error, 10) for item := range data { go func(item Item) { res, err := process(item) if err != nil { errs \u0026lt;- err return } result \u0026lt;- res }(item) } for r := range result { fmt.Println(\u0026#34;结果:\u0026#34;, r) } close(errs) for err := range errs { fmt.Println(\u0026#34;错误:\u0026#34;, err) } } 总结 Pipeline 模式让复杂的数据处理流程变得清晰、可控、可扩展。\n优点：\n解耦各处理阶段 天然支持并发 易于理解和维护 注意点：\n合理的 buffer 大小 做好错误处理 记得 close channel 或用 WaitGroup 好的架构不是一蹴而就的，是逐步迭代出来的。\n","permalink":"https://www.bvbej.com/posts/go-concurrency-patterns/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003eGo 的并发模型是其最强大的特性之一。\u003ccode\u003egoroutine\u003c/code\u003e + \u003ccode\u003echannel\u003c/code\u003e 的组合让我们能以极低的成本构建高性能的并发系统。\u003c/p\u003e\n\u003cp\u003e今天聊聊 \u003cstrong\u003ePipeline 模式\u003c/strong\u003e——一种将数据处理流程抽象为一系列阶段的编程范式。\u003c/p\u003e\n\u003ch2 id=\"什么是-pipeline\"\u003e什么是 Pipeline\u003c/h2\u003e\n\u003cp\u003e想象工厂流水线：原料从一端进入，经过多个工序处理，最终成品从另一端出来。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 生成数据\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egenerate\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 流水线：平方 -\u0026gt; 过滤偶数 -\u0026gt; 输出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epipeline\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003esquare\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efilterEven\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eprintResult\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eresult\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003edone\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// 等待完成\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"实战图片处理流水线\"\u003e实战：图片处理流水线\u003c/h2\u003e\n\u003cp\u003e假设我们要处理一批图片：下载 → 缩放 → 添加水印 → 上传。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eURL\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eData\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003ebyte\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eProcessImages\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eurls\u003c/span\u003e []\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e) \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003edownloads\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eresized\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ewatermarked\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e make(\u003cspan style=\"color:#66d9ef\"\u003echan\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esync\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWaitGroup\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 下载阶段\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eurls\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edownload\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eurl\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;下载失败: %v\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#66d9ef\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003edownloads\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        close(\u003cspan style=\"color:#a6e22e\"\u003edownloads\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 缩放阶段 (3个worker)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edownloads\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eresizedImg\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresize\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e800\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e600\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eresized\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresizedImg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 水印阶段 (2个worker)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eresized\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003ewatermarkedImg\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewatermark\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;© My Blog\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003ewatermarked\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewatermarkedImg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 上传阶段\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAdd\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ego\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edefer\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDone\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003erange\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ewatermarked\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eupload\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eimg\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePrintf\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;上传失败: %v\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ewg\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWait\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"优雅的错误处理\"\u003e优雅的错误处理\u003c/h2\u003e\n\u003cp\u003ePipeline 中如何处理错误？一个不错的方案是用\u003cstrong\u003e错误 channel\u003c/strong\u003e：\u003c/p\u003e","title":"Go 并发模式：Pipeline 实战"},{"content":"C++ 错误处理的痛 C++ 里错误处理方式一大堆，但没一个完美的：\n// 方式1: 返回值 + 特殊值 int get_value() { if (failed) return -1; // -1 是魔法值 } // 方式2: 异常 try { do_something(); } catch (const std::exception\u0026amp; e) { // 异常才是正文... } 异常的问题是：不知道会抛什么，不知道该不该 catch，析构函数里抛异常还会 std::terminate。\nRust 的错误哲学 Rust 把错误分为两类：\n可恢复错误 → Result\u0026lt;T, E\u0026gt; 不可恢复错误 → panic! // 可恢复：用 Result fn read_file(path: \u0026amp;str) -\u0026gt; Result\u0026lt;String, std::io::Error\u0026gt; { std::fs::read_to_string(path) } // 不可恢复：用 panic fn main() { let v = vec![1, 2, 3]; v.get(10).expect(\u0026#34;索引超出范围\u0026#34;); // 程序员的bug } 实战：错误处理的几种模式 1. 基本用法 use std::fs::File; use std::io::{self, Read}; fn read_config() -\u0026gt; Result\u0026lt;String, io::Error\u0026gt; { let mut file = File::open(\u0026#34;config.toml\u0026#34;)?; let mut contents = String::new(); file.read_to_string(\u0026amp;mut contents)?; Ok(contents) } fn main() { match read_config() { Ok(config) =\u0026gt; println!(\u0026#34;配置: {}\u0026#34;, config), Err(e) =\u0026gt; eprintln!(\u0026#34;读取配置失败: {}\u0026#34;, e), } } ? 操作符是灵魂——错误自动向上传播，不需要手写 match。\n2. 多错误类型：用 thiserror use thiserror::Error; #[derive(Error, Debug)] pub enum DatabaseError { #[error(\u0026#34;连接失败: {0}\u0026#34;)] ConnectionFailed(String), #[error(\u0026#34;查询超时\u0026#34;)] Timeout, #[error(\u0026#34;记录不存在\u0026#34;)] NotFound, #[error(\u0026#34;数据库内部错误\u0026#34;)] Internal(#[from] rusqlite::Error), } fn get_user(id: i32) -\u0026gt; Result\u0026lt;User, DatabaseError\u0026gt; { let conn = Connection::open(\u0026#34;app.db\u0026#34;)?; // ... } 调用方只需要处理一种错误类型。\n3. 应用程序错误：用 anyhow use anyhow::{Context, Result}; fn main() -\u0026gt; Result\u0026lt;()\u0026gt; { let config = read_config() .context(\u0026#34;无法读取配置文件\u0026#34;)?; let user = db::get_user(config.user_id) .context(\u0026#34;查询用户失败\u0026#34;)?; println!(\u0026#34;用户: {:?}\u0026#34;, user); Ok(()) } anyhow 的优势：\n? 可以自动收集任何实现了 std::error::Error 的错误 带上下文信息，错误信息更清晰 适合应用程序（而非库） 4. 自定义错误转换 impl From\u0026lt;redis::RedisError\u0026gt; for AppError { fn from(e: redis::RedisError) -\u0026gt; Self { AppError::Cache(e.to_string()) } } impl From\u0026lt;sqlx::Error\u0026gt; for AppError { fn from(e: sqlx::Error) -\u0026gt; Self { match e { sqlx::Error::RowNotFound =\u0026gt; AppError::NotFound, _ =\u0026gt; AppError::Database(e.to_string()), } } } 错误处理 vs C++ 场景 C++ Rust 打开文件失败 返回空指针 / throw Result\u0026lt;File, Error\u0026gt; JSON 解析失败 throw exception Result\u0026lt;T, ParseError\u0026gt; 忘记处理错误 编译器不报错（warning 容易被忽略） 编译器报错！ 错误传播 手写 if/else 或 try/catch ? 操作符 最佳实践 库用具体错误类型 — 方便调用方精确处理 应用用 anyhow — 快速开发，不需要定义一堆错误类型 慎用 panic! — 只用于真正的程序员 bug（如数组越界） expect() 用于确定不会失败的场景 — 比如 unwrap() 明确知道有值 代码示例：完整的服务错误处理 use anyhow::{Context, Result}; use thiserror::Error; #[derive(Error, Debug)] pub enum ServiceError { #[error(\u0026#34;用户不存在\u0026#34;)] UserNotFound, #[error(\u0026#34;权限不足\u0026#34;)] PermissionDenied, #[error(\u0026#34;操作过于频繁\u0026#34;)] RateLimited, } pub struct UserService { db: Database, cache: Cache, } impl UserService { pub async fn get_user(\u0026amp;self, id: i64) -\u0026gt; Result\u0026lt;User\u0026gt; { // 先查缓存 if let Some(user) = self.cache.get(\u0026amp;id).await { return Ok(user); } // 查数据库 let user = self.db .find_user(id) .await .context(\u0026#34;数据库查询失败\u0026#34;)?; match user { Some(u) =\u0026gt; { self.cache.set(\u0026amp;id, \u0026amp;u).await; Ok(u) } None =\u0026gt; Err(ServiceError::UserNotFound.into()), } } } 总结 Rust 的错误处理让我想起一句话：\u0026ldquo;让错误在编译期暴露，而不是在运行时.\u0026rdquo;\n虽然 Result 代码量看起来多一些，但：\n错误处理是显式的，不会漏掉 错误类型清晰，容易追查 ? 操作符让传播优雅 比 C++ 的\u0026quot;盲试 catch\u0026quot;好太多了。\n下一篇文章聊聊 Rust 异步编程：Tokio 实战。\n","permalink":"https://www.bvbej.com/posts/rust-error-handling-practical/","summary":"\u003ch2 id=\"c-错误处理的痛\"\u003eC++ 错误处理的痛\u003c/h2\u003e\n\u003cp\u003eC++ 里错误处理方式一大堆，但没一个完美的：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 方式1: 返回值 + 特殊值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eget_value\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (failed) \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e;  \u003cspan style=\"color:#75715e\"\u003e// -1 是魔法值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 方式2: 异常\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    do_something();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e} \u003cspan style=\"color:#66d9ef\"\u003ecatch\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eexception\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 异常才是正文...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e异常的问题是：不知道会抛什么，不知道该不该 catch，析构函数里抛异常还会 \u003ccode\u003estd::terminate\u003c/code\u003e。\u003c/p\u003e\n\u003ch2 id=\"rust-的错误哲学\"\u003eRust 的错误哲学\u003c/h2\u003e\n\u003cp\u003eRust 把错误分为两类：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e可恢复错误\u003c/strong\u003e → \u003ccode\u003eResult\u0026lt;T, E\u0026gt;\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e不可恢复错误\u003c/strong\u003e → \u003ccode\u003epanic!\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-rust\" data-lang=\"rust\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 可恢复：用 Result\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eread_file\u003c/span\u003e(path: \u003cspan style=\"color:#66d9ef\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003estr\u003c/span\u003e) -\u0026gt; Result\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eString, std::io::Error\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std::fs::read_to_string(path)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 不可恢复：用 panic\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e v \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003evec!\u003c/span\u003e[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    v.get(\u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e).expect(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;索引超出范围\u0026#34;\u003c/span\u003e);  \u003cspan style=\"color:#75715e\"\u003e// 程序员的bug\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"实战错误处理的几种模式\"\u003e实战：错误处理的几种模式\u003c/h2\u003e\n\u003ch3 id=\"1-基本用法\"\u003e1. 基本用法\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-rust\" data-lang=\"rust\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003euse\u003c/span\u003e std::fs::File;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003euse\u003c/span\u003e std::io::{self, Read};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eread_config\u003c/span\u003e() -\u0026gt; Result\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eString, io::Error\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003emut\u003c/span\u003e file \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e File::open(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;config.toml\u0026#34;\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003emut\u003c/span\u003e contents \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e String::new();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    file.read_to_string(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003emut\u003c/span\u003e contents)\u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Ok(contents)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ematch\u003c/span\u003e read_config() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Ok(config) \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eprintln!\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;配置: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, config),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        Err(e) \u003cspan style=\"color:#f92672\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eeprintln!\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;读取配置失败: \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e?\u003c/code\u003e 操作符是灵魂——错误自动向上传播，不需要手写 match。\u003c/p\u003e","title":"Rust 错误处理：从 panic 到 anyhow"},{"content":"缘起 从 C++ 转 Rust，最不适应的不是语法，而是一种全新的思维模式。\nRust 的所有权系统（Ownership）是语言最核心的创新，也是最陡峭的学习曲线。\nC++ 的惯性思维 在 C++ 里，我们习惯了这样的写法：\nstd::string get_name() { return \u0026#34;BvBeJ\u0026#34;; // 编译器会处理返回值优化 } void process() { std::string name = get_name(); std::string alias = name; // 拷贝？还是引用？ // ... } // name 和 alias 都会析构 直觉告诉我们这里发生了拷贝。但在 Rust 里，同样的思维会让你碰壁。\nRust 的所有权规则 Rust 遵循三条简单规则：\n每个值有一个所有者（Owner） 同一时间只有一个所有者 当所有者离开作用域，值被丢弃（Dropped） fn main() { let s1 = String::from(\u0026#34;hello\u0026#34;); let s2 = s1; // s1 被\u0026#34;移动\u0026#34;到 s2 // println!(\u0026#34;{}\u0026#34;, s1); // ❌ 编译错误！s1 已经无效 println!(\u0026#34;{}\u0026#34;, s2); // ✅ } // s2 离开作用域，内存被释放 \u0026ldquo;移动\u0026quot;语义取代了 C++ 的拷贝——这是最大的思维转变。\n借用：不用所有权的艺术 如果每次都要转移所有权，编程会非常麻烦。于是有了借用（Borrow）：\nfn calculate_length(s: \u0026amp;String) -\u0026gt; usize { s.len() } // s 离开作用域，但并不拥有所有权，所以不释放 fn main() { let s1 = String::from(\u0026#34;hello\u0026#34;); let len = calculate_length(\u0026amp;s1); // 借用，而不是移动 println!(\u0026#34;\u0026#39;{}\u0026#39; 的长度是 {}\u0026#34;, s1, len); // ✅ s1 仍然有效 } 借用就像是指针，但更安全。Rust 编译器会确保：\n要么多个不可变引用 \u0026amp;T 要么一个可变引用 \u0026amp;mut T 二者不能同时存在 可变引用的风险 fn main() { let mut s = String::from(\u0026#34;hello\u0026#34;); let r1 = \u0026amp;s; // ✅ let r2 = \u0026amp;s; // ✅ println!(\u0026#34;{} and {}\u0026#34;, r1, r2); // r1 和 r2 在这里之后不再使用 let r3 = \u0026amp;mut s; // ✅ 没有问题，因为 r1, r2 已经不再使用 r3.push_str(\u0026#34; world\u0026#34;); } 这叫非词法作用域生命周期（Non-Lexical Lifetimes, NLL），Rust 2018 edition 引入的优化。\n实际例子：用 Rust 重写 C++ 的配置解析 // C++ 版本 class Config { std::string path_; std::unique_ptr\u0026lt;Parser\u0026gt; parser_; public: Config(const std::string\u0026amp; path) : path_(path) { parser_ = std::make_unique\u0026lt;Parser\u0026gt;(path_); } }; // Rust 版本 struct Config { path: String, } impl Config { fn new(path: \u0026amp;str) -\u0026gt; Result\u0026lt;Self, Box\u0026lt;dyn Error\u0026gt;\u0026gt; { let path = path.to_string(); // 不需要 Box\u0026lt;dyn Parser\u0026gt;，直接在作用域内处理 let _parser = Parser::new(\u0026amp;path)?; Ok(Config { path }) } } 不需要 Box、unique_ptr，Rust 编译器帮我们管理生命周期。\n我的心得 C++ Rust 手动管理 new/delete 编译器推导生命周期 shared_ptr 到处飞 借用检查器静态保证 悬垂指针是运行时bug 编译时就能发现 析构顺序靠经验 作用域决定析构 Rust 教会我一件事：很多运行时错误，可以在编译时避免。\n代价是学习曲线陡峭。但一旦理解了这套系统，你会对内存管理有全新的认识。\n下一篇文章聊聊 Rust 的 Trait 对象与动态分发。\n","permalink":"https://www.bvbej.com/posts/rust-ownership-and-borrow/","summary":"\u003ch2 id=\"缘起\"\u003e缘起\u003c/h2\u003e\n\u003cp\u003e从 C++ 转 Rust，最不适应的不是语法，而是一种全新的\u003cstrong\u003e思维模式\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003eRust 的所有权系统（Ownership）是语言最核心的创新，也是最陡峭的学习曲线。\u003c/p\u003e\n\u003ch2 id=\"c-的惯性思维\"\u003eC++ 的惯性思维\u003c/h2\u003e\n\u003cp\u003e在 C++ 里，我们习惯了这样的写法：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring get_name() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;BvBeJ\u0026#34;\u003c/span\u003e;  \u003cspan style=\"color:#75715e\"\u003e// 编译器会处理返回值优化\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eprocess\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e get_name();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring alias \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e name;  \u003cspan style=\"color:#75715e\"\u003e// 拷贝？还是引用？\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}  \u003cspan style=\"color:#75715e\"\u003e// name 和 alias 都会析构\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e直觉告诉我们这里发生了拷贝。但在 Rust 里，同样的思维会让你碰壁。\u003c/p\u003e\n\u003ch2 id=\"rust-的所有权规则\"\u003eRust 的所有权规则\u003c/h2\u003e\n\u003cp\u003eRust 遵循三条简单规则：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e每个值有一个\u003cstrong\u003e所有者\u003c/strong\u003e（Owner）\u003c/li\u003e\n\u003cli\u003e同一时间只有一个所有者\u003c/li\u003e\n\u003cli\u003e当所有者离开作用域，值被\u003cstrong\u003e丢弃\u003c/strong\u003e（Dropped）\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-rust\" data-lang=\"rust\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e s1 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e String::from(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hello\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e s2 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e s1;  \u003cspan style=\"color:#75715e\"\u003e// s1 被\u0026#34;移动\u0026#34;到 s2\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// println!(\u0026#34;{}\u0026#34;, s1);  // ❌ 编译错误！s1 已经无效\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eprintln!\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e, s2);      \u003cspan style=\"color:#75715e\"\u003e// ✅\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}  \u003cspan style=\"color:#75715e\"\u003e// s2 离开作用域，内存被释放\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e\u0026ldquo;移动\u0026quot;语义\u003c/strong\u003e取代了 C++ 的拷贝——这是最大的思维转变。\u003c/p\u003e","title":"Rust 所有权与借用：我的理解之路"},{"content":"背景 C++ 一直缺少反射（Reflection），虽然 C++20 引入了 std::reflect，但目前编译器支持还很有限。\n实际项目中，我们经常需要：\n根据字符串创建对象 自动注入依赖 序列化/反序列化 这篇文章聊聊怎么在 C++ 里实现一套轻量反射系统。\n反射的核心：类型注册 反射的本质是在运行时动态查询类型信息。实现思路很简单——用全局注册表。\n1. 基础类型注册表 #include \u0026lt;functional\u0026gt; #include \u0026lt;unordered_map\u0026gt; #include \u0026lt;string\u0026gt; #include \u0026lt;memory\u0026gt; class TypeRegistry { public: template\u0026lt;typename T\u0026gt; static void registerType(const std::string\u0026amp; name) { creators()[name] = []() -\u0026gt; std::any { return std::make_any\u0026lt;T\u0026gt;(); }; } static std::any create(const std::string\u0026amp; name) { auto it = creators().find(name); if (it != creators().end()) { return it-\u0026gt;second(); } throw std::runtime_error(\u0026#34;Unknown type: \u0026#34; + name); } private: static auto\u0026amp; creators() { static std::unordered_map\u0026lt;std::string, std::function\u0026lt;std::any()\u0026gt;\u0026gt; map; return map; } }; 2. 宏简化注册 #define REGISTER_TYPE(T) \\ namespace { \\ struct Registrar##T { \\ Registrar##T() { \\ TypeRegistry::registerType\u0026lt;T\u0026gt;(#T); \\ } \\ }; \\ static Registrar##T registrar_##T; \\ } 3. 使用 struct User { std::string name; int age; }; REGISTER_TYPE(User) int main() { auto any_user = TypeRegistry::create(\u0026#34;User\u0026#34;); auto\u0026amp; user = std::any_cast\u0026lt;User\u0026amp;\u0026gt;(any_user); user.name = \u0026#34;BvBeJ\u0026#34;; user.age = 28; } 进阶：带构造函数参数 上面的实现只能调用默认构造函数。实际场景往往需要传参：\nclass TypeFactory { public: template\u0026lt;typename Base, typename Derived, typename... Args\u0026gt; static void registerType(const std::string\u0026amp; name) { creators()[name] = [](Args... args) -\u0026gt; std::any { return std::any(std::make_shared\u0026lt;Derived\u0026gt;(args...)); }; } template\u0026lt;typename Base, typename... Args\u0026gt; std::shared_ptr\u0026lt;Base\u0026gt; create(const std::string\u0026amp; name, Args... args) { auto it = creators().find(name); if (it != creators().end()) { auto any = it-\u0026gt;second(args...); return std::any_cast\u0026lt;std::shared_ptr\u0026lt;Base\u0026gt;\u0026gt;(any); } throw std::runtime_error(\u0026#34;Unknown type: \u0026#34; + name); } private: static auto\u0026amp; creators() { static std::unordered_map\u0026lt;std::string, std::function\u0026lt;std::any(Args...)\u0026gt;\u0026gt; map; return map; } }; 实用：依赖注入容器 基于反射系统，可以实现一个简单的 DI 容器：\ntemplate\u0026lt;typename T\u0026gt; class Container { public: template\u0026lt;typename... Args\u0026gt; std::shared_ptr\u0026lt;T\u0026gt; resolve(Args... args) { return std::make_shared\u0026lt;T\u0026gt;(args...); } }; // 特化：已经是 shared_ptr template\u0026lt;typename T\u0026gt; class Container\u0026lt;std::shared_ptr\u0026lt;T\u0026gt;\u0026gt; { public: std::shared_ptr\u0026lt;T\u0026gt; resolve() { return get\u0026lt;T\u0026gt;(); } template\u0026lt;typename U\u0026gt; void registerService() { services()[typeid(U)] = std::make_shared\u0026lt;U\u0026gt;(); } private: template\u0026lt;typename U\u0026gt; std::shared_ptr\u0026lt;U\u0026gt; get() { auto it = services().find(typeid(U)); if (it != services().end()) { return std::any_cast\u0026lt;std::shared_ptr\u0026lt;U\u0026gt;\u0026gt;(it-\u0026gt;second); } throw std::runtime_error(\u0026#34;Service not registered\u0026#34;); } static auto\u0026amp; services() { static std::unordered_map\u0026lt;std::type_index, std::any\u0026gt; map; return map; } }; 应用场景 1. Plugin 系统 // 插件接口 class IPlugin { public: virtual ~IPlugin() = default; virtual void init() = 0; virtual std::string name() const = 0; }; // 插件注册 REGISTER_TYPE(MyPlugin) // 动态加载 auto plugin = factory.create\u0026lt;IPlugin\u0026gt;(\u0026#34;MyPlugin\u0026#34;); plugin-\u0026gt;init(); 2. 序列化框架 class Serializer { public: template\u0026lt;typename T\u0026gt; void registerType() { type_handler\u0026lt;T\u0026gt; = [](const T\u0026amp; obj, JSON\u0026amp; json) { // 使用反射遍历成员 reflect_members(obj, json); }; } template\u0026lt;typename T\u0026gt; void to_json(const T\u0026amp; obj, JSON\u0026amp; json) { type_handler\u0026lt;T\u0026gt;(obj, json); } private: std::unordered_map\u0026lt;std::type_index, std::function\u0026lt;void(std::any, JSON\u0026amp;)\u0026gt;\u0026gt; type_handler; }; 现代替代：C++20 Reflection C++20 引入了 \u0026lt;reflect\u0026gt; 库（部分编译器支持）：\n#include \u0026lt;reflect\u0026gt; constexp auto r = reflexpr(MyStruct); using fields = get_members_t\u0026lt;r\u0026gt;; // ... 但目前 GCC/Clang/MSVC 支持还不完整，线上项目暂时还是需要自己实现。\n总结 方法 优点 缺点 手写注册表 简单、完全可控 需要手动维护 宏自动注册 简洁 宏的调试困难 C++20 Reflection 标准、语言级支持 编译器支持有限 自己造轮子的过程很有意思，也能更深入理解语言的本质。\n这个项目的完整代码放在了 GitHub，有兴趣可以看看。\n","permalink":"https://www.bvbej.com/posts/cpp-design-patterns-reflection/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003eC++ 一直缺少反射（Reflection），虽然 C++20 引入了 \u003ccode\u003estd::reflect\u003c/code\u003e，但目前编译器支持还很有限。\u003c/p\u003e\n\u003cp\u003e实际项目中，我们经常需要：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e根据字符串创建对象\u003c/li\u003e\n\u003cli\u003e自动注入依赖\u003c/li\u003e\n\u003cli\u003e序列化/反序列化\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这篇文章聊聊怎么在 C++ 里实现一套轻量反射系统。\u003c/p\u003e\n\u003ch2 id=\"反射的核心类型注册\"\u003e反射的核心：类型注册\u003c/h2\u003e\n\u003cp\u003e反射的本质是\u003cstrong\u003e在运行时动态查询类型信息\u003c/strong\u003e。实现思路很简单——用全局注册表。\u003c/p\u003e\n\u003ch3 id=\"1-基础类型注册表\"\u003e1. 基础类型注册表\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;functional\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;unordered_map\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;string\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;memory\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTypeRegistry\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e T\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e registerType(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e name) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        creators()[name] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e []() \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eany {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003emake_any\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eT\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eany create(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e name) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e it \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e creators().find(name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (it \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e creators().end()) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e it\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esecond();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ethrow\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eruntime_error(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Unknown type: \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e name);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eprivate\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e creators() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003estatic\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eunordered_map\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring, std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003efunction\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eany()\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e map;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e map;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"2-宏简化注册\"\u003e2. 宏简化注册\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#define REGISTER_TYPE(T) \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e    namespace { \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e        struct Registrar##T { \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e            Registrar##T() { \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e                TypeRegistry::registerType\u0026lt;T\u0026gt;(#T); \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e            } \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e        }; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e        static Registrar##T registrar_##T; \\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"3-使用\"\u003e3. 使用\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUser\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring name;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e age;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eREGISTER_TYPE(User)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e main() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e any_user \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e TypeRegistry\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecreate(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;User\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e user \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eany_cast\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eUser\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026gt;\u003c/span\u003e(any_user);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    user.name \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;BvBeJ\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    user.age \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e28\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"进阶带构造函数参数\"\u003e进阶：带构造函数参数\u003c/h2\u003e\n\u003cp\u003e上面的实现只能调用默认构造函数。实际场景往往需要传参：\u003c/p\u003e","title":"C++ 反射实现：从 0 到依赖注入容器"},{"content":"背景 现代编程离不开异步。Python 有 asyncio，JavaScript 有 async/await，Go 有 goroutine。C++20 终于引入了协程（Coroutines）。\n今天从 Python asyncio 的视角来看看 C++ 协程怎么用。\nPython asyncio 的模式 async def fetch(url: str) -\u0026gt; str: # 模拟异步IO await asyncio.sleep(1) return f\u0026#34;data from {url}\u0026#34; async def main(): results = await asyncio.gather( fetch(\u0026#34;a.com\u0026#34;), fetch(\u0026#34;b.com\u0026#34;), fetch(\u0026#34;c.com\u0026#34;), ) print(results) asyncio.run(main()) 核心概念：async def 定义协程函数，await 挂起等待，asyncio.gather 并发执行。\nC++20 协程入门 C++ 协程的关键类型：\nco_await — 挂起协程 co_return — 返回值（相当于 return） co_yield — 产出值（用于生成器） std::suspend_never / std::suspend_always — 挂起策略 简单例子：模拟异步任务 #include \u0026lt;coroutine\u0026gt; #include \u0026lt;future\u0026gt; #include \u0026lt;iostream\u0026gt; template\u0026lt;typename T\u0026gt; struct Task { struct promise_type { T value; std::exception_ptr error; auto get_return_object() { return Task{std::coroutine_handle\u0026lt;promise_type\u0026gt;::from_promise(*this)}; } auto initial_suspend() { return std::suspend_never{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void return_value(T v) { value = v; } void unhandled_exception() { error = std::current_exception(); } }; std::coroutine_handle\u0026lt;promise_type\u0026gt; handle; Task(std::coroutine_handle\u0026lt;promise_type\u0026gt; h) : handle(h) {} ~Task() { if (handle) handle.destroy(); } T get() { handle.resume(); return handle.promise().value; } }; // 模拟异步操作 Task\u0026lt;int\u0026gt; async_fetch() { std::cout \u0026lt;\u0026lt; \u0026#34;开始异步任务...\\n\u0026#34;; co_await std::suspend_always{}; // 挂起，模拟异步等待 std::cout \u0026lt;\u0026lt; \u0026#34;异步任务完成\\n\u0026#34;; co_return 42; } int main() { auto task = async_fetch(); std::cout \u0026lt;\u0026lt; \u0026#34;做一些其他事情...\\n\u0026#34;; int result = task.get(); std::cout \u0026lt;\u0026lt; \u0026#34;结果: \u0026#34; \u0026lt;\u0026lt; result \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } 和 Python asyncio 对比 Python C++ async def Task\u0026lt;T\u0026gt; 返回类型 await co_await return value co_return value asyncio.sleep(1) std::suspend_always{} 事件循环自动调度 手动 resume() 实际应用：HTTP 客户端 Task\u0026lt;Response\u0026gt; http_get(const std::string\u0026amp; url) { co_await socket_.async_connect(url); co_await socket_.async_write(request_); Response resp = co_await socket_.async_read(); co_return resp; } Task\u0026lt;std::vector\u0026lt;Response\u0026gt;\u0026gt; fetch_all(const std::vector\u0026lt;std::string\u0026gt;\u0026amp; urls) { std::vector\u0026lt;Task\u0026lt;Response\u0026gt;\u0026gt; tasks; for (const auto\u0026amp; url : urls) { tasks.push_back(http_get(url)); } std::vector\u0026lt;Response\u0026gt; results; for (auto\u0026amp; task : tasks) { results.push_back(task.get()); } co_return results; } 坑点 协程不能用在模板参数里 — std::vector\u0026lt;Task\u0026lt;int\u0026gt;\u0026gt; 可以，但 Task\u0026lt;Task\u0026lt;int\u0026gt;\u0026gt; 不行 生命周期管理 — 协程句柄必须手动 destroy()，除非用 RAII 封装 调试困难 — 栈帧被编译器切分，gdb 支持还在完善中 总结 C++20 协程还很年轻，库支持不如 Python 完善。但对于构建高性能网络服务，它的零成本抽象是其他语言难以比拟的。\n学习曲线： Python asyncio → C++ 协程，会发现语言虽不同，思维是相通的。\n我的项目里更多用 Go 做网络并发，C++ 协程主要用于极致性能场景。\n","permalink":"https://www.bvbej.com/posts/cpp-coroutine-async/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003e现代编程离不开异步。Python 有 \u003ccode\u003easyncio\u003c/code\u003e，JavaScript 有 \u003ccode\u003easync/await\u003c/code\u003e，Go 有 goroutine。C++20 终于引入了\u003cstrong\u003e协程\u003c/strong\u003e（Coroutines）。\u003c/p\u003e\n\u003cp\u003e今天从 Python asyncio 的视角来看看 C++ 协程怎么用。\u003c/p\u003e\n\u003ch2 id=\"python-asyncio-的模式\"\u003ePython asyncio 的模式\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003easync\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efetch\u003c/span\u003e(url: str) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e str:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e# 模拟异步IO\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e asyncio\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003esleep(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ef\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;data from \u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e{\u003c/span\u003eurl\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003easync\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e():\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    results \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e asyncio\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003egather(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        fetch(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;a.com\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        fetch(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;b.com\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        fetch(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;c.com\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    print(results)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003easyncio\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003erun(main())\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e核心概念：\u003cstrong\u003e\u003ccode\u003easync def\u003c/code\u003e\u003c/strong\u003e 定义协程函数，\u003ccode\u003eawait\u003c/code\u003e 挂起等待，\u003ccode\u003easyncio.gather\u003c/code\u003e 并发执行。\u003c/p\u003e\n\u003ch2 id=\"c20-协程入门\"\u003eC++20 协程入门\u003c/h2\u003e\n\u003cp\u003eC++ 协程的关键类型：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eco_await\u003c/code\u003e — 挂起协程\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eco_return\u003c/code\u003e — 返回值（相当于 \u003ccode\u003ereturn\u003c/code\u003e）\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eco_yield\u003c/code\u003e — 产出值（用于生成器）\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003estd::suspend_never\u003c/code\u003e / \u003ccode\u003estd::suspend_always\u003c/code\u003e — 挂起策略\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"简单例子模拟异步任务\"\u003e简单例子：模拟异步任务\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;coroutine\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;future\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;iostream\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e T\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eTask\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epromise_type\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        T value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eexception_ptr error;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eget_return_object\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e Task{std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecoroutine_handle\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epromise_type\u003cspan style=\"color:#f92672\"\u003e\u0026gt;::\u003c/span\u003efrom_promise(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e)};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003einitial_suspend\u003c/span\u003e() { \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003esuspend_never{}; }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efinal_suspend\u003c/span\u003e() \u003cspan style=\"color:#66d9ef\"\u003enoexcept\u003c/span\u003e { \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003esuspend_always{}; }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereturn_value\u003c/span\u003e(T v) { value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e v; }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eunhandled_exception\u003c/span\u003e() { error \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecurrent_exception(); }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    };\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecoroutine_handle\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epromise_type\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e handle;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Task(std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecoroutine_handle\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003epromise_type\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e h) \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e handle(h) {}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e~\u003c/span\u003eTask() { \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (handle) handle.destroy(); }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    T \u003cspan style=\"color:#a6e22e\"\u003eget\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        handle.resume();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e handle.promise().value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 模拟异步操作\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTask\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e async_fetch() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecout \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;开始异步任务...\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_await\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003esuspend_always{};  \u003cspan style=\"color:#75715e\"\u003e// 挂起，模拟异步等待\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecout \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;异步任务完成\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_return\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e42\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e task \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e async_fetch();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecout \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;做一些其他事情...\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e result \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e task.get();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ecout \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;结果: \u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e result \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\n\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"和-python-asyncio-对比\"\u003e和 Python asyncio 对比\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003ePython\u003c/th\u003e\n          \u003cth\u003eC++\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003easync def\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003eTask\u0026lt;T\u0026gt;\u003c/code\u003e 返回类型\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003eawait\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003eco_await\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003ereturn value\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003eco_return value\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e\u003ccode\u003easyncio.sleep(1)\u003c/code\u003e\u003c/td\u003e\n          \u003ctd\u003e\u003ccode\u003estd::suspend_always{}\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e事件循环自动调度\u003c/td\u003e\n          \u003ctd\u003e手动 \u003ccode\u003eresume()\u003c/code\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"实际应用http-客户端\"\u003e实际应用：HTTP 客户端\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTask\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eResponse\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e http_get(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e url) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_await\u003c/span\u003e socket_.async_connect(url);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_await\u003c/span\u003e socket_.async_write(request_);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    Response resp \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eco_await\u003c/span\u003e socket_.async_read();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_return\u003c/span\u003e resp;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eTask\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eResponse\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e fetch_all(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026amp;\u003c/span\u003e urls) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eTask\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eResponse\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e tasks;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e url : urls) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        tasks.push_back(http_get(url));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eResponse\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e results;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e task : tasks) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        results.push_back(task.get());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eco_return\u003c/span\u003e results;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"坑点\"\u003e坑点\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e协程不能用在模板参数里\u003c/strong\u003e — \u003ccode\u003estd::vector\u0026lt;Task\u0026lt;int\u0026gt;\u0026gt;\u003c/code\u003e 可以，但 \u003ccode\u003eTask\u0026lt;Task\u0026lt;int\u0026gt;\u0026gt;\u003c/code\u003e 不行\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e生命周期管理\u003c/strong\u003e — 协程句柄必须手动 \u003ccode\u003edestroy()\u003c/code\u003e，除非用 RAII 封装\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e调试困难\u003c/strong\u003e — 栈帧被编译器切分，gdb 支持还在完善中\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"总结\"\u003e总结\u003c/h2\u003e\n\u003cp\u003eC++20 协程还很年轻，库支持不如 Python 完善。但对于构建高性能网络服务，它的零成本抽象是其他语言难以比拟的。\u003c/p\u003e","title":"C++ 协程：从 asyncio 理解现代异步编程"},{"content":"背景 Kubernetes Operator 是 CNCF 主推的云原生扩展机制。用 Go 写 Operator 是我日常工作的重要部分。\n这篇文章聊聊怎么从零开发一个生产级的 Operator。\n核心概念 Operator 核心是声明式 API + reconciliation loop：\n用户声明期望状态 → Controller 调和 → 实际状态趋近期望 项目结构 my-operator/ ├── main.go ├── api/ │ └── v1/ │ └── myapp_types.go # CRD 定义 ├── controllers/ │ └── myapp_controller.go # Reconciliation 逻辑 └── config/ ├── crd/ └── rbac/ 第一步：定义 CRD (Custom Resource Definition) // api/v1/myapp_types.go package v1 import ( metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; ) type MyAppSpec struct { Replicas int32 `json:\u0026#34;replicas,omitempty\u0026#34;` Image string `json:\u0026#34;image\u0026#34;` Port int32 `json:\u0026#34;port\u0026#34;` EnvVars []EnvVar `json:\u0026#34;envVars,omitempty\u0026#34;` } type EnvVar struct { Name string `json:\u0026#34;name\u0026#34;` Value string `json:\u0026#34;value\u0026#34;` } type MyAppStatus struct { AvailableReplicas int32 `json:\u0026#34;availableReplicas,omitempty\u0026#34;` Conditions []metav1.Condition `json:\u0026#34;conditions,omitempty\u0026#34;` } // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:shortName=myapp type MyApp struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34;` Spec MyAppSpec `json:\u0026#34;spec,omitempty\u0026#34;` Status MyAppStatus `json:\u0026#34;status,omitempty\u0026#34;` } func (r *MyApp) Hub() {} 第二步：生成代码 # 安装 controller-gen go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest # 生成 CRD + RBAC + DeepCopy controller-gen object:headerFile=\u0026#34;hack/boilerplate.go.txt\u0026#34; paths=\u0026#34;./...\u0026#34; # 生成 CRD YAML controller-gen crd:crdVersions=v1 paths=\u0026#34;./...\u0026#34; output:crd:artifacts:config=config/crd/bases 第三步：实现 Controller // controllers/myapp_controller.go package controllers type MyAppReconciler struct { Client client.Client Scheme *runtime.Scheme Log logr.Logger } func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues(\u0026#34;myapp\u0026#34;, req.NamespacedName) // 1. 获取资源 var myapp v1.MyApp if err := r.Get(ctx, req.NamespacedName, \u0026amp;myapp); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } // 2. 构建 Deployment deploy := r.buildDeployment(\u0026amp;myapp) if err := ctrl.SetControllerReference(\u0026amp;myapp, deploy, r.Scheme); err != nil { return ctrl.Result{}, err } // 3. 创建或更新 Deployment found := \u0026amp;appsv1.Deployment{} err := r.Get(ctx, req.NamespacedName, found) if err != nil \u0026amp;\u0026amp; errors.IsNotFound(err) { log.Info(\u0026#34;Creating Deployment\u0026#34;, \u0026#34;name\u0026#34;, deploy.Name) err = r.Create(ctx, deploy) } else if err == nil { // 更新（需要对比 spec 差异） if !r.deploymentEqual(found, deploy) { found.Spec = deploy.Spec log.Info(\u0026#34;Updating Deployment\u0026#34;) err = r.Update(ctx, found) } } // 4. 更新 Status r.updateStatus(\u0026amp;myapp, found) return ctrl.Result{RequeueAfter: 30 * time.Second}, nil } func (r *MyAppReconciler) buildDeployment(app *v1.MyApp) *appsv1.Deployment { replicas := app.Spec.Replicas if replicas == 0 { replicas = 1 } return \u0026amp;appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: app.Name, Namespace: app.Namespace, }, Spec: appsv1.DeploymentSpec{ Replicas: \u0026amp;replicas, Selector: \u0026amp;metav1.LabelSelector{ MatchLabels: map[string]string{\u0026#34;app\u0026#34;: app.Name}, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{\u0026#34;app\u0026#34;: app.Name}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ Name: \u0026#34;myapp\u0026#34;, Image: app.Spec.Image, Ports: []corev1.ContainerPort{{ ContainerPort: app.Spec.Port, }}, Env: r.buildEnvVars(app.Spec.EnvVars), }}, }, }, }, } } 第四步：启动 Controller // main.go func main() { ctrl.SetLogger(zap.New(zap.UseDevMode(true))) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, }) if err != nil { setupLog.Error(err, \u0026#34;unable to start manager\u0026#34;) os.Exit(1) } if err = (\u0026amp;controllers.MyAppReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, \u0026#34;unable to create controller\u0026#34;) os.Exit(1) } if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, \u0026#34;problem running manager\u0026#34;) os.Exit(1) } } 高级特性 1. Webhook 验证 // webhooks/myapp_webhook.go func (r *MyApp) ValidateCreate() error { if r.Spec.Replicas \u0026lt; 0 { return field.Invalid( field.NewPath(\u0026#34;spec\u0026#34;).Child(\u0026#34;replicas\u0026#34;), r.Spec.Replicas, \u0026#34;replicas must be non-negative\u0026#34;, ) } return nil } 2. Finalizer（防止误删） func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { myapp := \u0026amp;v1.MyApp{} r.Get(ctx, req.NamespacedName, myapp) // 删除标记？ if myapp.DeletionTimestamp.IsZero() { // 添加 finalizer if !containsString(myapp.GetFinalizers(), \u0026#34;myapp.finalizer\u0026#34;) { myapp.Finalizers = append(myapp.GetFinalizers(), \u0026#34;myapp.finalizer\u0026#34;) r.Update(ctx, myapp) } } else { // 执行清理逻辑 r.cleanup(myapp) // 移除 finalizer myapp.Finalizers = removeString(myapp.GetFinalizers(), \u0026#34;myapp.finalizer\u0026#34;) r.Update(ctx, myapp) } } 测试 import ( . \u0026#34;github.com/onsi/ginkgo/v2\u0026#34; . \u0026#34;github.com/onsi/gomega\u0026#34; ) var _ = Describe(\u0026#34;MyApp controller\u0026#34;, func() { Context(\u0026#34;with basic spec\u0026#34;, func() { It(\u0026#34;should create a Deployment\u0026#34;, func() { myapp := \u0026amp;v1.MyApp{ ObjectMeta: metav1.ObjectMeta{ Name: \u0026#34;test\u0026#34;, Namespace: \u0026#34;default\u0026#34;, }, Spec: v1.MyAppSpec{ Replicas: 2, Image: \u0026#34;nginx:latest\u0026#34;, Port: 80, }, } Expect(k8sClient.Create(ctx, myapp)).Should(Succeed()) }) }) }) 部署 Operator # config/manager/manager.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-operator spec: replicas: 1 template: spec: containers: - name: operator image: myorg/my-operator:v1.0.0 env: - name: WATCH_NAMESPACE value: \u0026#34;\u0026#34; # OLM (Operator Lifecycle Manager) 安装 operator-sdk olm install operator-sdk run bundle myorg/my-operator-bundle:v1.0.0 总结 Operator 开发的核心：\nCRD — 定义声明式 API Reconcile Loop — 调和实际状态到期望状态 Status — 反馈当前状态给用户 Finalizer — 安全清理资源 用 Go 写 Operator 是一种享受——强类型 + K8s 生态 + 声明式哲学，完美结合。\n你的下一个 Operator 在哪里？\nOperator SDK 是开发 Operator 的利器，推荐从它开始。\n","permalink":"https://www.bvbej.com/posts/kubernetes-operator-development/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003eKubernetes Operator 是 CNCF 主推的云原生扩展机制。用 Go 写 Operator 是我日常工作的重要部分。\u003c/p\u003e\n\u003cp\u003e这篇文章聊聊怎么从零开发一个生产级的 Operator。\u003c/p\u003e\n\u003ch2 id=\"核心概念\"\u003e核心概念\u003c/h2\u003e\n\u003cp\u003eOperator 核心是\u003cstrong\u003e声明式 API +  reconciliation loop\u003c/strong\u003e：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e用户声明期望状态 → Controller 调和 → 实际状态趋近期望\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"项目结构\"\u003e项目结构\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003emy-operator/\n├── main.go\n├── api/\n│   └── v1/\n│       └── myapp_types.go    # CRD 定义\n├── controllers/\n│   └── myapp_controller.go   # Reconciliation 逻辑\n└── config/\n    ├── crd/\n    └── rbac/\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"第一步定义-crd-custom-resource-definition\"\u003e第一步：定义 CRD (Custom Resource Definition)\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// api/v1/myapp_types.go\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMyAppSpec\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003eint32\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;replicas,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e     \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;image\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ePort\u003c/span\u003e      \u003cspan style=\"color:#66d9ef\"\u003eint32\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;port\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eEnvVars\u003c/span\u003e   []\u003cspan style=\"color:#a6e22e\"\u003eEnvVar\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;envVars,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEnvVar\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e  \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;name\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eValue\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;value\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMyAppStatus\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eAvailableReplicas\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint32\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;availableReplicas,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eConditions\u003c/span\u003e        []\u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCondition\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;conditions,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// +kubebuilder:object:root=true\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// +kubebuilder:subresource:status\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// +kubebuilder:resource:shortName=myapp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eTypeMeta\u003c/span\u003e   \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;,inline\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;metadata,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e   \u003cspan style=\"color:#a6e22e\"\u003eMyAppSpec\u003c/span\u003e   \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;spec,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eStatus\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMyAppStatus\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`json:\u0026#34;status,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eHub\u003c/span\u003e() {}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"第二步生成代码\"\u003e第二步：生成代码\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 安装 controller-gen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ego install sigs.k8s.io/controller-tools/cmd/controller-gen@latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 生成 CRD + RBAC + DeepCopy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtroller-gen object:headerFile\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hack/boilerplate.go.txt\u0026#34;\u003c/span\u003e paths\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;./...\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 生成 CRD YAML\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003econtroller-gen crd:crdVersions\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ev1 paths\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;./...\u0026#34;\u003c/span\u003e output:crd:artifacts:config\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003econfig/crd/bases\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"第三步实现-controller\"\u003e第三步：实现 Controller\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// controllers/myapp_controller.go\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003epackage\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtrollers\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMyAppReconciler\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eClient\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eClient\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eScheme\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eruntime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eScheme\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eLog\u003c/span\u003e    \u003cspan style=\"color:#a6e22e\"\u003elogr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLogger\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMyAppReconciler\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eReconcile\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) (\u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResult\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eWithValues\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;myapp\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNamespacedName\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 1. 获取资源\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGet\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNamespacedName\u003c/span\u003e, \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResult\u003c/span\u003e{}, \u003cspan style=\"color:#a6e22e\"\u003eclient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eIgnoreNotFound\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 2. 构建 Deployment\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ebuildDeployment\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetControllerReference\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eScheme\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResult\u003c/span\u003e{}, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 3. 创建或更新 Deployment\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eappsv1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDeployment\u003c/span\u003e{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGet\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNamespacedName\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerrors\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eIsNotFound\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInfo\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Creating Deployment\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCreate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 更新（需要对比 spec 差异）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e !\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003edeploymentEqual\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003edeploy\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInfo\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Updating Deployment\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUpdate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 4. 更新 Status\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eupdateStatus\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003efound\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResult\u003c/span\u003e{\u003cspan style=\"color:#a6e22e\"\u003eRequeueAfter\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e30\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etime\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSecond\u003c/span\u003e}, \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMyAppReconciler\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003ebuildDeployment\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eappsv1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDeployment\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ereplicas\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereplicas\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ereplicas\u003c/span\u003e = \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eappsv1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDeployment\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e:      \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eNamespace\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNamespace\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eappsv1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDeploymentSpec\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e: \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ereplicas\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eSelector\u003c/span\u003e: \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eLabelSelector\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eMatchLabels\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eTemplate\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003ecorev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePodTemplateSpec\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eLabels\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e]\u003cspan style=\"color:#66d9ef\"\u003estring\u003c/span\u003e{\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003ecorev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePodSpec\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eContainers\u003c/span\u003e: []\u003cspan style=\"color:#a6e22e\"\u003ecorev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContainer\u003c/span\u003e{{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e:  \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;myapp\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#a6e22e\"\u003ePorts\u003c/span\u003e: []\u003cspan style=\"color:#a6e22e\"\u003ecorev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContainerPort\u003c/span\u003e{{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                            \u003cspan style=\"color:#a6e22e\"\u003eContainerPort\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ePort\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                        \u003cspan style=\"color:#a6e22e\"\u003eEnv\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ebuildEnvVars\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eEnvVars\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    }},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"第四步启动-controller\"\u003e第四步：启动 Controller\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// main.go\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetLogger\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ezap\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNew\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ezap\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUseDevMode\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e)))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emgr\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewManager\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetConfigOrDie\u003c/span\u003e(), \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eOptions\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eScheme\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003escheme\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003esetupLog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;unable to start manager\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eExit\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e = (\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003econtrollers\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyAppReconciler\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eClient\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003emgr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetClient\u003c/span\u003e(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eScheme\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003emgr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetScheme\u003c/span\u003e(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }).\u003cspan style=\"color:#a6e22e\"\u003eSetupWithManager\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emgr\u003c/span\u003e); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003esetupLog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;unable to create controller\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eExit\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emgr\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eStart\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSetupSignalHandler\u003c/span\u003e()); \u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003esetupLog\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eError\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eerr\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;problem running manager\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eos\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eExit\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"高级特性\"\u003e高级特性\u003c/h2\u003e\n\u003ch3 id=\"1-webhook-验证\"\u003e1. Webhook 验证\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// webhooks/myapp_webhook.go\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eValidateCreate\u003c/span\u003e() \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e \u0026lt; \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efield\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eInvalid\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003efield\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNewPath\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;spec\u0026#34;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003eChild\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;replicas\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;replicas must be non-negative\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"2-finalizer防止误删\"\u003e2. Finalizer（防止误删）\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eMyAppReconciler\u003c/span\u003e) \u003cspan style=\"color:#a6e22e\"\u003eReconcile\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econtext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eRequest\u003c/span\u003e) (\u003cspan style=\"color:#a6e22e\"\u003ectrl\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eResult\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eerror\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e{}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGet\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eNamespacedName\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 删除标记？\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eDeletionTimestamp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eIsZero\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 添加 finalizer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e !\u003cspan style=\"color:#a6e22e\"\u003econtainsString\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetFinalizers\u003c/span\u003e(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;myapp.finalizer\u0026#34;\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFinalizers\u003c/span\u003e = append(\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetFinalizers\u003c/span\u003e(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;myapp.finalizer\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUpdate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    } \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 执行清理逻辑\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecleanup\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#75715e\"\u003e// 移除 finalizer\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eFinalizers\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eremoveString\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eGetFinalizers\u003c/span\u003e(), \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;myapp.finalizer\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003er\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eUpdate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"测试\"\u003e测试\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-go\" data-lang=\"go\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eimport\u003c/span\u003e (\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    . \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/onsi/ginkgo/v2\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    . \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;github.com/onsi/gomega\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_\u003c/span\u003e = \u003cspan style=\"color:#a6e22e\"\u003eDescribe\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;MyApp controller\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eContext\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;with basic spec\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eIt\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;should create a Deployment\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyApp\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003emetav1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eObjectMeta\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eName\u003c/span\u003e:      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;test\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eNamespace\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;default\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003eSpec\u003c/span\u003e: \u003cspan style=\"color:#a6e22e\"\u003ev1\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eMyAppSpec\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eReplicas\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003eImage\u003c/span\u003e:    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;nginx:latest\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                    \u003cspan style=\"color:#a6e22e\"\u003ePort\u003c/span\u003e:     \u003cspan style=\"color:#ae81ff\"\u003e80\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#a6e22e\"\u003eExpect\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ek8sClient\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eCreate\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ectx\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003emyapp\u003c/span\u003e)).\u003cspan style=\"color:#a6e22e\"\u003eShould\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eSucceed\u003c/span\u003e())\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"部署-operator\"\u003e部署 Operator\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# config/manager/manager.yaml\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003eapiVersion\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eapps/v1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ekind\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eDeployment\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003emetadata\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003emy-operator\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003espec\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003ereplicas\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003etemplate\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003espec\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003econtainers\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eoperator\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eimage\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003emyorg/my-operator:v1.0.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003eenv\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        - \u003cspan style=\"color:#f92672\"\u003ename\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003eWATCH_NAMESPACE\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003evalue\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# OLM (Operator Lifecycle Manager) 安装\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eoperator-sdk olm install\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eoperator-sdk run bundle myorg/my-operator-bundle:v1.0.0\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"总结\"\u003e总结\u003c/h2\u003e\n\u003cp\u003eOperator 开发的核心：\u003c/p\u003e","title":"Kubernetes Operator 开发实战：用 Go 告别手动运维"},{"content":"背景 Vue3 发布两年多了，从 Options API 迁移到 Composition API 的项目也有了不少。这里总结一些实战经验。\n为什么需要 Composition API Options API 的问题：逻辑关注点分散在一个组件的各个选项里（data、methods、computed、watch\u0026hellip;）。\n// Options API - 逻辑分散 export default { data() { return { count: 0 } }, methods: { increment() { this.count++ } }, computed: { doubled() { return this.count * 2 } }, watch: { count(newVal) { console.log(\u0026#39;count changed:\u0026#39;, newVal) } } } \u0026lt;!-- Composition API - 逻辑内聚 --\u0026gt; \u0026lt;script setup\u0026gt; import { ref, computed, watch } from \u0026#39;vue\u0026#39; const count = ref(0) const doubled = computed(() =\u0026gt; count.value * 2) const increment = () =\u0026gt; count.value++ watch(count, (newVal) =\u0026gt; { console.log(\u0026#39;count changed:\u0026#39;, newVal) }) \u0026lt;/script\u0026gt; 实用技巧 1. ref vs reactive 该用哪个？ // primitive types (String, Number, Boolean) -\u0026gt; ref const name = ref(\u0026#39;BvBeJ\u0026#39;) const age = ref(18) // objects/arrays -\u0026gt; reactive const user = reactive({ name: \u0026#39;BvBeJ\u0026#39;, skills: [\u0026#39;Go\u0026#39;, \u0026#39;Rust\u0026#39;, \u0026#39;C++\u0026#39;] }) // 或者对象也用 ref，通过 .value 访问 const user = ref({ name: \u0026#39;BvBeJ\u0026#39; }) user.value.name = \u0026#39;New Name\u0026#39; // 需要 .value 我的习惯： 简单类型用 ref，复杂对象用 reactive。TypeScript 类型推导更清晰。\n2. 自定义 Hooks：逻辑复用 // useWindowSize.ts import { ref, onMounted, onUnmounted } from \u0026#39;vue\u0026#39; export function useWindowSize() { const width = ref(window.innerWidth) const height = ref(window.innerHeight) const update = () =\u0026gt; { width.value = window.innerWidth height.value = window.innerHeight } onMounted(() =\u0026gt; window.addEventListener(\u0026#39;resize\u0026#39;, update)) onUnmounted(() =\u0026gt; window.removeEventListener(\u0026#39;resize\u0026#39;, update)) return { width, height } } // 组件中使用 \u0026lt;script setup\u0026gt; import { useWindowSize } from \u0026#39;@/hooks/useWindowSize\u0026#39; const { width, height } = useWindowSize() \u0026lt;/script\u0026gt; 3. provide / inject 替代 Vuex/Pinia？ 对于简单场景，provide/inject 比 Pinia 更轻量：\n\u0026lt;!-- Parent.vue --\u0026gt; \u0026lt;script setup\u0026gt; import { provide } from \u0026#39;vue\u0026#39; const theme = ref(\u0026#39;dark\u0026#39;) provide(\u0026#39;theme\u0026#39;, theme) \u0026lt;/script\u0026gt; \u0026lt;!-- Child.vue --\u0026gt; \u0026lt;script setup\u0026gt; import { inject } from \u0026#39;vue\u0026#39; const theme = inject(\u0026#39;theme\u0026#39;) \u0026lt;/script\u0026gt; 注意： provide 的是响应式的，但子组件修改会影响父组件，小心副作用。\n4. 异步组件与 Suspense \u0026lt;script setup\u0026gt; import { defineAsyncComponent } from \u0026#39;vue\u0026#39; const HeavyComponent = defineAsyncComponent(() =\u0026gt; import(\u0026#39;./HeavyComponent.vue\u0026#39;) ) \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;Suspense\u0026gt; \u0026lt;template #default\u0026gt; \u0026lt;HeavyComponent /\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;template #fallback\u0026gt; \u0026lt;LoadingSpinner /\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;/Suspense\u0026gt; \u0026lt;/template\u0026gt; TypeScript 集成 \u0026lt;script setup lang=\u0026#34;ts\u0026#34;\u0026gt; interface User { name: string age: number skills: string[] } const user = ref\u0026lt;User | null\u0026gt;(null) // 泛型指定类型 const list = ref\u0026lt;User[]\u0026gt;([]) // with default const count = ref\u0026lt;number\u0026gt;(0) \u0026lt;/script\u0026gt; 常见坑 解构 reactive 对象会丢失响应式 const user = reactive({ name: \u0026#39;BvBeJ\u0026#39;, age: 18 }) const { name } = user // ❌ name 失去响应式 // 正确做法： const name = toRef(user, \u0026#39;name\u0026#39;) // ✅ watch 监听 reactive 对象属性 const user = reactive({ count: 0 }) // ❌ 错误 - 第一个参数需要 getter watch(() =\u0026gt; user.count, (newVal) =\u0026gt; { console.log(newVal) }) 总结 Composition API 让我们能像写函数一样组织组件逻辑，更容易抽取、更容易测试、更容易 TypeScript 化。\n建议： 新项目直接用 Composition API，老项目逐步迁移核心逻辑。Vue3 的 \u0026lt;script setup\u0026gt; 语法糖是真的香。\n你更喜欢 Options API 还是 Composition API？欢迎留言讨论。\n","permalink":"https://www.bvbej.com/posts/vue3-composition-api-tips/","summary":"\u003ch2 id=\"背景\"\u003e背景\u003c/h2\u003e\n\u003cp\u003eVue3 发布两年多了，从 Options API 迁移到 Composition API 的项目也有了不少。这里总结一些实战经验。\u003c/p\u003e\n\u003ch2 id=\"为什么需要-composition-api\"\u003e为什么需要 Composition API\u003c/h2\u003e\n\u003cp\u003eOptions API 的问题：逻辑关注点分散在一个组件的各个选项里（\u003ccode\u003edata\u003c/code\u003e、\u003ccode\u003emethods\u003c/code\u003e、\u003ccode\u003ecomputed\u003c/code\u003e、\u003ccode\u003ewatch\u003c/code\u003e\u0026hellip;）。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Options API - 逻辑分散\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emethods\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eincrement\u003c/span\u003e() { \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ecomputed\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003edoubled\u003c/span\u003e() { \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ewatch\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003enewVal\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;count changed:\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003enewVal\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-vue\" data-lang=\"vue\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u0026lt;!--\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eComposition\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eAPI\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e逻辑内聚\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esetup\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ecomputed\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ewatch\u003c/span\u003e } \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;vue\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edoubled\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecomputed\u003c/span\u003e(() =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eincrement\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e () =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ewatch\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e, (\u003cspan style=\"color:#a6e22e\"\u003enewVal\u003c/span\u003e) =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;count changed:\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003enewVal\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"实用技巧\"\u003e实用技巧\u003c/h2\u003e\n\u003ch3 id=\"1-ref-vs-reactive-该用哪个\"\u003e1. \u003ccode\u003eref\u003c/code\u003e vs \u003ccode\u003ereactive\u003c/code\u003e 该用哪个？\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// primitive types (String, Number, Boolean) -\u0026gt; ref\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;BvBeJ\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e18\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// objects/arrays -\u0026gt; reactive\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereactive\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;BvBeJ\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eskills\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Go\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Rust\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;C++\u0026#39;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 或者对象也用 ref，通过 .value 访问\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eref\u003c/span\u003e({ \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;BvBeJ\u0026#39;\u003c/span\u003e })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003evalue\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;New Name\u0026#39;\u003c/span\u003e  \u003cspan style=\"color:#75715e\"\u003e// 需要 .value\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e我的习惯：\u003c/strong\u003e 简单类型用 \u003ccode\u003eref\u003c/code\u003e，复杂对象用 \u003ccode\u003ereactive\u003c/code\u003e。TypeScript 类型推导更清晰。\u003c/p\u003e","title":"Vue3 Composition API 实战经验"}]