这段时间我把「一个真实互联网系统从编码到上线」涉及的模块,完整梳理成了一张全景图,作为自己 PC + WSL2 实验室的搭建蓝图。技术选型上,我刻意对齐了自己在公司 Graphene 平台上已经熟悉的栈,也顺手把它落成了一份可以真正跑起来的玩具工程规划。这篇博客就把这两份东西——全景图和落地方案——整合起来,算是给自己这段学习路径存个档。
整体请求链路:先建立一个全局心智模型
最基础的一条链路是:用户请求先经过 CDN,再到负载均衡(LB),进入 API 网关,路由到微服务集群,微服务再和缓存、数据库、消息队列打交道;与此同时,日志、指标、链路数据会被统一收集,送去监控告警系统。其实我工作中接触的 Graphene 平台,本质上就是这套模式的企业级实现,只是规模和治理复杂度高出很多——先把这张最简的图刻进脑子里,后面每一层展开都是在往这张图上加细节。
一、开发与交付层:从编码到上线
版本控制用 Git,本地已经在用;代码托管和 CI 触发可以用 GitHub / GitLab,自建的话 Gitea 更轻量;CI/CD 流水线打算用 Jenkins,或者 GitLab CI;构建工具是 Java 侧常见的 Maven;镜像仓库用 Harbor(之前已经搞过同步脚本,正好对应上),简单场景也可以直接用 Docker Hub;制品仓库(存 Jar/依赖包)进阶可以上 Nexus 或 Artifactory;代码质量扫描用 SonarQube 做静态检查。
二、接入层:用户请求的第一站
DNS 和 CDN 都用 Cloudflare——这两块在做「牛马チャン」网站项目时已经用过,概念完全可以复用;负载均衡(LB)本地用 Nginx 自建练手最直接,云上则对应 ALB/NLB;API 网关负责路由、鉴权、限流这类入口逻辑,用 Spring Cloud Gateway,这也是 Graphene 技术栈里本来就有的组件。
三、应用/微服务层
业务服务本体用 Spring Boot;服务间调用走 Feign(Spring Cloud 生态);服务注册与发现直接用 K8s Service + CoreDNS 的固定域名方式,不额外引入 Nacos 做服务发现——这是 K8s 原生环境下的标准做法,Graphene 也是这样处理的;配置中心才轮到 Nacos 登场,在 K8s 环境下它只承担「集中管理各环境配置」这一个角色;熔断限流用 Sentinel,属于 Spring Cloud Alibaba 生态,配 Nacos 很自然;数据访问层用 MyBatis 做 ORM;前端用 React,这既是招聘要求里明确写的技能,也正好是「牛马チャン」网站项目里已经积累的前端经验。
四、数据存储层
核心业务数据放 MySQL;应对大数据量的分库分表用 ShardingSphere,对应 Graphene 里熟悉的 Sharding Ops 概念;缓存分两级——Redis 做分布式缓存,Caffeine 做本地缓存,组成多级缓存架构,用来加速读取、降低数据库压力;文件、图片这类非结构化数据用对象存储,选型是 AWS S3,正好也是目前在学的 SAA-C03 里的内容。
五、异步与消息层
消息队列用 Kafka,削峰填谷、做异步解耦;死信队列/延迟队列没有单独引入新组件,靠 Kafka 自身机制加业务层实现来处理失败消息和定时任务;批处理调度对应工作中熟悉的续期批处理这类场景,进阶可以考虑 XXL-Job 或 Spring Batch,第一期先不装也没关系。
六、搜索与日志层(EFK 栈)
全文检索和复杂查询用 Elasticsearch;日志采集从各服务收集用 Filebeat;日志处理管道 Logstash 是可选项——如果 Filebeat 直接把日志送进 ES,这一层可以先跳过;日志的查询与展示用 Kibana。Elasticsearch + Filebeat + Kibana 这个组合通常被称为 EFK 栈,如果再加上 Logstash,就是更经典的 ELK 栈——目前规划里用到的正好是 EFK。
七、容器编排与云基础设施
应用打包用 Docker;调度、伸缩、自愈这些能力交给 K8s,目前已经用 k3d 在本地搭起来了;K8s 集群的流量入口是 nginx-ingress,是接下来打算自己装、亲手练一遍的部分;基础设施即代码(IaC)用 Terraform 或 Ansible,属于长期规划里的一环;承载以上这一切的云平台是 AWS,对应正在学习的 SAA-C03 内容。
八、可观测性三支柱(さんほんばしら)
可观测性通常拆成三根支柱:指标(Metrics)由 Prometheus 负责采集和存储,Grafana 负责把这些指标做成仪表盘展示——这两个已经在着手安装;告警交给 Prometheus 生态自带的 Alertmanager,在阈值触发时发通知;日志这一支柱就是前面提到的 EFK 栈;链路追踪(Tracing)用 Jaeger 或 Zipkin,属于进阶内容,能看清一个请求是怎么跨越网关、服务 A、服务 B 一路传递下去的——顺带一提,Graphene 技能表里的「Sleuth + Zipkin」正是 Spring 生态下最经典的链路追踪组合,等基础监控跑顺了,这一层很值得加进来。
九、安全与网络(进阶,可以后置)
用户身份认证鉴权用 Spring Security + JWT;密钥管理基础阶段用 K8s Secret,进阶可以上 Vault;服务间的访问控制用 K8s NetworkPolicy。这一节整体优先级靠后,等前面的骨架跑顺了再回头补。
全景图给出的七个搭建阶段
整张全景图落到我的 PC 上,被规划成七个阶段:阶段一是 K8s 基座(k3d)加 Prometheus/Grafana 监控,已经开了个头;阶段二是写两个简单的 Spring Boot 微服务,服务间调用直接走 K8s Service DNS,不经 Nacos,Nacos 只接入「配置中心」这一个角色——把「网关路由 → K8s DNS 调用 → 配置从 Nacos 拉取」这条链路跑通,是所有微服务玩法的地基,也和 Graphene 真实架构一致;阶段三接入 MySQL、Redis/Caffeine、MyBatis,体验缓存穿透、缓存击穿这些概念的真实现象;阶段四装 Kafka,做一个「下单 → 异步扣库存」式的场景,体验消息堆积和消费延迟;阶段五搭 EFK 日志链路,把前几个服务的日志统一采集起来;阶段六装 Jenkins CI/CD,把「代码提交 → 自动构建 → 自动部署到 k3d」这个闭环跑通;阶段七是进阶内容,包括自定义 Ingress、链路追踪(Zipkin)、以及用 Terraform 把整套基础设施管起来。
进阶挑战:复刻 Graphene 的 Zorro BLCS——自研 CDC 中间件
Zorro BLCS 的核心原理是伪装成 MySQL 从库,监听主库 binlog,把变更事件统一封装成 ZorroEvent 格式,推送到 Kafka 集群,下游再按 topic 分流、各自独立消费、互不感知。其中有两条完全不同处理方式的消费链路:CDC 微服务解析 ZorroEvent 后,把变更 insert/update/delete 进 Elasticsearch,供保单列表这类查询走 ES 的 CQRS 读路径;Alphad 微服务则是把 ZorroEvent 还原成 SQL 并直接执行,把源库的写操作原样镜像到 CBQ 报表库——这种「binlog 事件 → 还原 SQL → 在目标库重放」的模式,本质上是一种自研的逻辑复制(logical replication),效果类似 MySQL 自身的 row-based binlog 复制,只是跨系统、经过 Kafka 中转。从 BLCS-OPS 截图里确认的几个关键设计点:位点用的是 MySQL GTID Set,格式是 server-uuid 加事务序号,GTID 的好处是主从切换后依然全局唯一、单调递增,任务重启时能从上次的位点继续读;有一个「自动回补消息」的选项,中断期间漏掉的 binlog 事件可以在重启时选择性回放补发,是保证不丢数据的兜底机制;report 和 cdc 是两条独立配置的 BLCS 任务,各自单独连只读副本、单独发各自的 Kafka topic;过滤维度包括库名表达式、表名正则表达式(对应分库分表的数字后缀)、以及分库分表键,供下游做跨分片聚合;源库连接专门用带 _ro 后缀的只读账号,隔离 CDC 读取对生产主库写路径的影响;还有一个「使用 zorro 格式」的选项,决定是否用 ZorroEvent 这套统一事件协议封装该 filter 规则匹配到的 binlog 变更。
这套机制在 homelab 里打算用开源的 Canal 来复刻——原理和 Zorro BLCS 是一致的,用来模拟生产端:连自己的 MySQL、订阅 binlog、发 Kafka。消费端写两个简单服务,一个模拟 CDC 微服务(消费后写入本地 Elasticsearch),一个模拟 Alphad 微服务(消费后把事件还原成 SQL,在另一个 MySQL schema 里执行,模拟报表库镜像)。这样就能完整体验 GTID 位点、断点续传,以及「同一份变更事件、两种完全不同消费策略」的效果。目前已经确认不用再问同事的两个问题是:report topic 是由 Alphad 微服务在消费做 SQL 镜像执行;Zorro 是统一的 CDC 事件协议加工具名,而不是单纯的一个格式选项。仍然待确认的开放问题是 ZorroEvent 具体的 schema/字段结构,以及「分配机器」按钮的机制是不是也是一种服务发现/调度问题,能不能和 batch 调度的 ZK 方案类比。
ES + CQRS:读写分离,读强依赖 ES
微服务写入 MySQL 后主动发一条 Kafka 消息,独立的 CDC 消费服务订阅消息并写入 ES 索引;保单列表这类查询直接读 ES、不读库——这就是标准的 CQRS(命令查询职责分离)模式:写路径走 MySQL,读路径走 ES,中间靠异步消息做最终一致。常见的故障点是:CDC 消费服务延迟或挂掉,会导致 ES 数据滞后于真实库,用户刚创建的数据「暂时看不到」;Kafka 消息丢失或重复消费,会导致 ES 索引缺数据或数据重复;还需要确认有没有定时全量比对库和 ES 的补偿机制,有没有失败重试或死信队列。homelab 里打算用一个简单 Spring Boot 服务写 MySQL 后发 Kafka 消息,另一个服务消费消息写入 Elasticsearch,做一个「列表查询走 ES、详情查询走 MySQL」的小 demo,故意让消费者挂掉一段时间,观察数据不一致窗口的真实样子。
自研 Batch 调度 + Zookeeper:强一致协调 vs K8s 原生发现
Batch 任务本体分散在各个微服务工程里,独立的调度服务通过 Zookeeper 做服务发现——各微服务注册到 ZK 的临时节点(ephemeral znode),而不是走 K8s Service。这背后的技术判断是:K8s Service 只能回答「路由到一个健康实例」,回答不了「当前有哪些实例、谁是 leader」这类需要精确实例清单的问题;批处理调度常见的分片调度(任务按 ID 哈希分片给多个 worker)和 leader 选举(多个调度器实例只能有一个真正下发任务),天然需要 ZK 这类强一致(CP)加分布式协调原语——临时节点、顺序节点、watch 机制,而不只是服务级别的负载均衡。更可能的真实情况是:批处理调度框架的诞生时间早于系统全面上 K8s,ZK 是框架代码逻辑本身的一部分(可能用了 elastic-job/tbschedule 之类的开源框架,或同样思路的自研版本),是历史遗留加框架绑定的自然结果,而不是团队特意选择两套注册发现。待确认的开放问题包括:batch 调度框架是自研还是基于开源框架二次开发;ZK 集群是否有过连接抖动、session 超时配置这类稳定性问题历史。homelab 复刻思路是本地装一个单节点 Zookeeper,用 ZK 官方推荐的 Curator 框架写一个「多实例注册 + leader 选举」的 demo,把临时节点、watch 这些概念亲手用一遍,再对比着写一版纯 K8s Service DNS 的等价实现,体会两者的本质区别。
更现代的补充方向:现有骨架的真实缺口
网关加微服务加 K8s 加 CDC/CQRS 加监控日志,这套骨架已经是中大型公司的教科书级标准脚手架,但从 2026 年视角看还有几处真实缺口:一是分布式事务——CQRS 解决的是读写分离下的最终一致,但一次业务操作横跨多个微服务的写操作,怎么保证要么都成功要么都回滚,单体应用里一个 @Transactional 就能搞定的事,微服务拆开后没有天然方案,标准解法是 Saga 模式(每一步失败都触发前面步骤的补偿操作)和 TCC(Try-Confirm-Cancel 三阶段),homelab 打算用阿里开源的 Seata 接入玩具订单服务,故意让某一步失败,观察补偿机制怎么撤销前面的操作;二是灰度发布/蓝绿部署——生产标准做法是先给一小部分流量验证新版本再全量切换,K8s 原生可以用 Service 权重路由简单模拟,进阶用 Argo Rollouts 体验「能上线」和「敢上线」之间的差距;三是压测与混沌工程,对 SRE 方向尤其重要,压测用 k6/JMeter/Gatling 测极限 QPS、找性能瓶颈,混沌工程用阿里开源的 ChaosBlade 或 Chaos Mesh 主动注入故障(杀 Pod、模拟网络延迟、模拟连接池耗尽),观察系统能不能自愈、告警能不能及时发现,打算先用 k6 压测再用 Chaos Mesh 随机杀 Pod,把 RCA 能力从被动等故障变成主动练手感;四是 Service Mesh——Spring Cloud Gateway 加 Feign 这套需要在业务代码里手动接入熔断/重试/超时/加密,Istio 这类 Service Mesh 把这些能力下沉到基础设施层的 sidecar 代理,业务代码完全无感知,不是说 Spring Cloud 过时,而是值得体验另一种更现代的服务治理方式;五是 OLAP 报表引擎——Alphad 镜像到 MySQL 能跑,但 MySQL 本质是为事务优化的行式存储,海量聚合查询会比列式存储慢很多,现代做法更倾向 ClickHouse、Apache Doris 这类专门的分析引擎,这一项和后面的大数据平台直接相关,打算放在一起练。
大数据平台:从单笔业务到海量分析
大数据平台的祖师爷是 Hadoop,2006 到 2013 年代的标准三件套是 HDFS(分布式文件系统,管数据存哪)、MapReduce(计算框架,管数据怎么算,但每一步都要落盘,比较慢)、YARN(资源调度,管任务分给集群哪台机器)。现代大数据栈本质上是这三个组件被逐个替换的产物:HDFS 换成 MinIO/S3,更轻量、更云原生;MapReduce 换成 Spark,基于内存计算,快一个数量级;YARN 换成 K8s,被容器编排统一收编。骨架逻辑是 Hadoop 发明的,但具体实现已经换代——homelab 不打算花时间部署 Hadoop 本身,但如果以后工作里遇到还在用 Hadoop/Hive 的老系统,至少要认得这套术语。落到具体分层:数据采集层复用已经有的 Canal(模拟 Zorro BLCS)加 Kafka,同一份 binlog 事件多接一个消费者;数据湖存储层用 MinIO,开源、兼容 S3 协议,是本地实验室的最佳选择;数据仓库/OLAP 层用 ClickHouse 或 Apache Doris,列式存储,专为聚合查询优化;批处理计算引擎用 Spark,业界标准,尤其是 Spark SQL;实时流计算引擎用 Flink,可以直接消费 Kafka 里的 CDC 事件做实时聚合,比如实时保费统计;任务调度用 Airflow,业界标准,DAG 可视化;元数据管理/数据目录用 DataHub,属于进阶可选,先不装也行;BI/可视化用 Superset,开源的 Apache 基金会项目,上手快。
这里最有价值的部分是和现有架构的连接点:不需要凭空造数据,直接复用阶段四搭的 Kafka + Canal CDC 链路,同一份 binlog 变更事件,除了原有的写 ES 消费者,再加一个写进数据湖/触发 Flink 实时计算的消费者,这样能真实体验「同一份数据,业务系统看它是 CQRS 的一环,大数据平台看它是分析数据源」这个视角切换。建议的搭建顺序是:先装 MinIO 把数据能落地这件事跑通;再装 ClickHouse,把之前 Alphad 镜像的报表数据从 MySQL 换成 ClickHouse,直观对比查询速度差异;然后接 Flink,消费 Kafka 里的 CDC 事件做一个实时保费金额滚动汇总的小 demo,写入 ClickHouse;接着接 Superset,连上 ClickHouse 五分钟拖拽出一个仪表盘,是成就感最强的一步;最后进阶再上 Airflow,编排每天凌晨跑一次的全量对账/汇总任务,可以对比 Graphene 自研 batch 调度,体会数据 ETL 调度和业务批处理调度的设计差异;Spark 放在最后加,因为批处理引擎跟流处理有一定概念重叠,放在流处理练熟之后理解会更快。
AI 与向量数据库:从零讲清楚的新模块
这一块此前完全没接触过,所以从最基础的概念讲起。向量(vector)/embedding 是什么:把一段文字或图片丢给一个 embedding 模型,模型会输出一串数字(比如 1536 个浮点数),这串数字代表这段文字的语义,关键性质是语义相近的文字转出来的数字串在数学上也相近,可以用余弦相似度衡量有多近——比如「保险理赔」和「索赔申请」字面完全不同,但转成向量后数学距离会很近。向量数据库是什么:传统数据库(MySQL)擅长精确匹配,向量数据库擅长相似度检索,是完全不同的索引结构和查询方式。RAG(检索增强生成)是目前 AI 应用最核心的模式,解决 LLM 不知道私有数据、知识会过时的问题,流程是:先把私有文档(比如自己深度学习过的 SLI-BAT001 这些规范文档)切成小段,每段转成向量存进向量数据库;用户提问时先把问题也转成向量,去向量数据库里找最相似的几段原文;再把这几段原文加用户问题一起丢给 LLM,让 LLM 参考这些资料回答——这样 LLM 回答时用的是真实的私有资料,而不是凭训练记忆瞎编(幻觉),也是解决 LLM 知识过时问题最主流的办法。
架构里需要新增的模块包括:embedding 模型,用 Ollama 跑本地模型(比如 nomic-embed-text),不用调用付费 API;向量数据库,homelab 起步用 pgvector——PostgreSQL 的插件,已经会用关系型数据库,装个插件就有向量能力,不用学一整套新系统,进阶可选功能更全但部署更复杂的 Milvus;LLM 推理服务用 Ollama 本地跑开源模型;编排框架决定什么时候调 LLM、调之前查不查资料、要不要调用工具、结果怎么处理,这层胶水逻辑的标准化很关键,LangChain 适合链式/线性流程,RAG 这种「查资料 → 喂 LLM → 出结果」很适合,LangGraph 适合有循环/条件判断的图状流程,是目前做 Agent 智能体的主流框架;AI 网关进阶可选,用 LiteLLM Proxy 统一管理多个 LLM 调用的限流/路由/成本追踪。不用编排框架也能手写实现 RAG,但流程一旦变复杂——先判断问题属于哪类知识库,再决定查不查资料,查完可能还要调用计算器或查天气之类的外部工具,最后再总结——手写的 if-else 会迅速失控,LangChain 把加载文档、切分、embedding、检索、拼 prompt、调 LLM、解析输出这些常见步骤封装成可组合的链,LangGraph 把整个流程建模成一个状态机,支持循环、条件跳转、来回试几次直到满意,Agent 现在业界基本都在用 LangGraph 或类似框架,两者不是二选一,很多实际项目是 LangChain 处理简单 RAG 部分、LangGraph 处理更复杂的多步骤 Agent 逻辑。
建议的搭建顺序是:先装 PostgreSQL + pgvector 插件,比装专业向量数据库简单得多;用 Ollama 跑一个小的 embedding 模型,写脚本把 BSD 学习文档(比如 SLI-BAT001)按段落切开转成向量存进 pgvector;写一个简单的检索 demo,输入一句话比如「保全变更的处理流程是什么」,从 pgvector 里检索出最相关的几段原文,先只验证语义相似这件事真的 work;接上 Ollama 的对话模型,把检索出来的原文加问题一起丢给它,完整跑通一次 RAG 流程——做完这一步就有了一个能回答 BSD 规范问题的私有 AI 助手,直接对现在的 BSD 学习有实用价值;以后如果 PC 配了 3090,同样这套流程可以直接换成跑更大的本地模型。这一块还能反哺已有的两个项目:BSD 学习方面,可以把已深度学习的 SLI-API/BAT/ACC/USR 文档全部灌进去,做一个专属的保险业务知识问答助手;牛马チャン家族方面,以后如果想给网站加语义搜索故事内容或者简单的角色问答互动,这套 RAG 的经验能直接复用。
从蓝图到落地:迷你保单管理系统(Mini Policy System)
光有全景图还不够,需要一份具体能跑起来的落地方案,交给 Claude Code 在 VSCode 里按图施工。设计原则有五条:第一,业务域统一,避免为了用中间件而用中间件,全部服务围绕同一个简化业务域——一个迷你保单管理系统,命名和字段可以用已经熟悉的テンガン/イングラム式术语,练起来更有代入感;第二,两套环境同一份代码,本地开发用 docker-compose 一键拉起 MySQL/Redis/Kafka/ES 等中间件,服务本身直接用 IDE 或 Claude Code 跑(不进容器,方便调试),生产环境所有服务和中间件都跑在 k3s 里,通过 Harbor 镜像仓库加 CI/CD 流水线部署,模拟真实发布流程;第三,消费者/生产者关系必须是真实的而不是摆设,至少要有两条独立的 Kafka 消费链路,对应全景图里「同一份事件,两种消费策略」的设计;第四,每个阶段都要能跑通、能看见效果,不堆一堆装了但没用上的空壳中间件;第五,为后续大数据/AI 阶段预留钩子,但不在第一期实现,只是设计上不给自己埋雷,比如 Kafka topic 设计和事件 schema 要考虑到未来会被多个消费者复用。
业务域一句话描述:用户登录,创建/查询保单,保单创建后异步触发通知加异步写入搜索索引,最后可以搜索保单。核心实体 Policy(保单)包含 id、policyNo(保单号)、holderName(投保人姓名)、productType(产品类型,TENGAN 或 INGURAMU,对应熟悉的两个产品线)、premium(保费)、status(状态:DRAFT/ACTIVE/CANCELLED)以及创建/更新时间。服务清单一共五个:frontend 是前端 SPA,用 React + Vite,负责登录、保单列表/创建表单、搜索页;gateway-service 是 API 网关,用 Spring Cloud Gateway,负责路由、JWT 鉴权、限流;policy-service 是核心写服务,用 Spring Boot + MyBatis + Liquibase + MySQL + Redis,负责保单 CRUD 写路径、Redis 缓存详情查询、创建/更新时发 Kafka 事件,启动时 Liquibase 自动建表迁移;notification-service 是消费者 A,纯消费者不带数据库,订阅 policy-events topic 模拟发通知,打日志或写本地文件即可;search-service 是消费者 B 加读服务,用 Spring Boot + Elasticsearch,订阅同一个 topic 写入 ES,对外暴露保单搜索这个只读 API,构成 CQRS 读路径。表结构管理不采用应用代码启动时检测建表的土办法,而是用 Liquibase,和公司实际使用的工具保持一致,变更集按顺序编号放在 changelog 目录下,应用启动时自动检查数据库当前版本并执行未应用的变更集,让建库建表本身也是受版本控制、可追溯的过程。
目录结构是一个 monorepo,全部放一个文件夹方便 VSCode 统一管理:frontend、gateway-service、policy-service、notification-service、search-service 五个服务各自一个目录;infra 目录下放 docker-compose.dev.yml(本地开发中间件)、k8s 子目录(每服务一个部署清单子目录,加一个 middleware 子目录放 k3s 里生产用的中间件)、jenkins 子目录放 Jenkinsfile 模板;docs 目录放 kafka-event-schema.md,作为各服务共享的事件格式契约;根目录一个 README 做项目总览。约定每个服务子目录下必须有自己的 README,写清楚这个服务是干什么的、怎么本地单独跑、依赖哪些中间件、暴露哪些端口和 API。
环境上采取三元制,而不是简单的本地/生产二分:本地开发环境的 docker-compose 只装中间件(MySQL、Redis、用 KRaft 模式不需要额外装 Zookeeper 的 Kafka、Elasticsearch、Kibana),业务服务本身用 mvn spring-boot:run 或 npm run dev 直接跑,方便打断点调试。生产环境这边的核心决策是:有状态中间件 MySQL、Redis 不进 k3s,直接用 Docker 常驻容器加 systemd 管理跑在宿主机上,扮演云厂商托管服务(RDS/ElastiCache)的角色——因为生产环境本来就不会在业务集群里自己管理有状态 DB 的高可用,模拟这个外部依赖关系比在 k3s 里搭一套简化版 HA 更贴近真实架构,而且 MySQL 跑在宿主机上反而让后续 Canal 监听 binlog 更直接。k3s 业务集群访问宿主机上的 MySQL/Redis,用到一个新知识点叫「无 selector Service」:正常 Service 靠 selector 自动关联同 namespace 里匹配标签的 Pod,无 selector Service 专门用来指向集群外部资源,手动写一个 Endpoints 对象指向宿主机 IP 加端口,业务代码看到的还是标准的 Service DNS 名字,完全不用关心数据库其实在集群外面,这跟真实云环境里应用不关心 RDS 具体部署在哪是同一种解耦思路。Kafka 和 Elasticsearch 因为本身是无状态或自带分布式冗余设计,跑进 k3s 问题不大,用 Bitnami Helm chart 单副本部署即可,重点练手写 YAML/Helm values,不纠结生产级多副本配置。镜像流转上,业务服务镜像统一推到 Harbor,k3s 从 Harbor 拉镜像,本地 build 的镜像需要 docker push 到 Harbor,k3s 侧配置好 imagePullSecrets 指向 Harbor,顺带练一次私有仓库鉴权的实操。整套系统单独开一个 toy-system namespace,跟 k3s 自带的 kube-system 分开,方便管理和后续整体清理。另外安排了一个独立、不阻塞主线的练习模块:额外部署一个单节点 MySQL StatefulSet(叫 mysql-statefulset-demo),单独建测试库不接业务服务,纯粹体验 Pod 删除重建后数据还在、PVC 和 PV 的绑定关系,以及它跟无 selector Service 这种方式的本质差异,可以安排在阶段五之后、阶段六之前随时插入。
CI/CD 流水线的组件选择是:代码仓库先用 Gitea 自建,比 GitLab CE 轻量很多,更适合 8 核 15G 内存这样的机器,之后如果想练更接近企业环境的功能(MR 审批流、CI 变量管理 UI)可以换成 GitLab CE;CI/CD 引擎用已经计划要装的 Jenkins;镜像仓库复用已有经验的 Harbor;第一期的部署方式是 Jenkins 流水线里直接 kubectl set image / kubectl apply,简单直接先把闭环跑通;进阶第二期再加 Argo CD 做 GitOps,对应灰度发布方向,是更现代的部署范式。以 policy-service 为例,流水线阶段是:开发者 git push 到 Gitea,Gitea webhook 触发 Jenkins,Jenkins Pipeline 依次跑 Checkout 代码、mvn test 跑单元测试、mvn package 打 Jar、docker build 打标签、docker push 到 Harbor、最后 kubectl set image 触发 k8s 的滚动发布,可以顺手观察 Deployment 的滚动更新过程。Jenkinsfile 模板会写成参数化的,各服务的 Jenkinsfile 只引用它、传服务名端口等差异化参数,减少重复。
分阶段实施顺序对齐全景图但按这套玩具系统重新排布,一共十二个阶段:P0 已完成,k3s 单节点加 Docker;P1 本地 docker-compose 拉起 MySQL/Redis,写 policy-service(含 Redis 缓存),本地直接跑通 CRUD;P2 加 gateway-service 接 JWT 鉴权,加 frontend 登录加列表页,走通完整的前端到网关鉴权到后端链路;P3 本地加 Kafka,policy-service 创建保单时发事件,写 notification-service 消费,体验同一个事件独立消费者的解耦;P4 本地加 ES,写 search-service 消费同一事件写入 ES 并暴露搜索 API,前端加搜索页,走通完整 CQRS 读写分离链路;P5 宿主机上用 Docker 常驻起 MySQL/Redis 模拟云托管,k3s 里建 toy-system namespace,通过无 selector Service 让业务服务连上宿主机 MySQL/Redis,Kafka/ES 用 Helm 单副本部署进 k3s,所有业务服务先手动 kubectl apply 部署到 k3s,不接 CI/CD,在生产环境跑通一次全链路;P5.5 是插入式练习,额外部署一个独立的 MySQL StatefulSet + PV demo,不接业务数据;P6 这时候再装 Prometheus/Grafana,因为已经有真实的服务和流量可以观察,能看到真实的 CPU/内存/QPS 曲线,可以故意 kill 掉一个 Pod 观察自愈;P7 接入 Gitea + Jenkins + Harbor,把 P5 的手动部署变成自动化流水线,git push 即部署,滚动发布跑通;P8 EFK 日志链路接入这几个服务,排查问题时能查日志而不是 kubectl logs 挨个看;P9 是进阶内容,引入 Canal 监听宿主机 MySQL 的 binlog,新增独立的 report-events topic 加 report-service,Canal 发到 report-events,report-service 消费后把事件还原成 SQL、镜像写入 ClickHouse 而不是 MySQL,体验 OLAP 列式存储的聚合查询优势,原有 policy-service 手动发 policy-events 保留不变,两条链路并存对比,完整复刻 Zorro BLCS 同一份 binlog、两条独立消费管道的设计思想;P10 进阶内容是 Argo CD 做 GitOps 式部署加简单灰度发布 demo;P11 进阶内容是用 k6 对 policy-service 压测加 Chaos Mesh 随机杀 Pod。大数据和 AI/RAG 这两块作为独立的后续扩展,不塞进第一期范围,但设计上已经兼容:policy-events topic 未来可以再加一个消费者写入 MinIO 或触发 Flink,不影响现有两个消费者;ES 里的保单数据后续也可以作为给 pgvector/embedding 做实验的数据源。
Kafka 事件契约写在 docs/kafka-event-schema.md 里,是 Claude Code 需要严格遵守的部分。policy-events 这个 topic 的事件结构包含 eventId、eventType(POLICY_CREATED/POLICY_UPDATED/POLICY_CANCELLED)、occurredAt 时间戳,以及嵌套的 policy 对象(id、policyNo、holderName、productType、premium、status),单 topic 多事件类型,通过 eventType 字段分流,符合大部分 Kafka 事件设计的实践。P9 阶段新增的 report-events topic 由 Canal 监听宿主机 MySQL binlog 产生,格式对齐 ZorroEvent 的思路,用统一事件协议封装 binlog 变更,而不是照搬 policy-events 这套应用层事件 schema,包含 gtid、database、table、eventType(INSERT/UPDATE/DELETE)、data(变更后的行数据)以及 occurredAt,report-service 消费这个 topic 把事件还原成 SQL 并在 ClickHouse 里重放,对应 Alphad 的镜像执行模式,这条链路和 policy-events 是两条独立的 topic,互不影响。
给 Claude Code 的落地要求一共八条:每个服务独立的 pom.xml/package.json,不做成父子 module,保持简单;所有服务配置通过环境变量注入,不硬编码,本地用 .env 或 IDE 的 run configuration,k3s 里用 ConfigMap/Secret;每个服务提供一个 /actuator/health,为后续接 Prometheus 做准备;policy-service 发 Kafka 事件的代码单独封装成一个 EventPublisher 类,P9 阶段替换成 Canal 时只需要把「谁来触发发送事件」这一层换掉,EventPublisher 本身和下游消费者不用动;先按 P1 到 P4 的顺序实现,每完成一个阶段就应该能跑起来看到效果,不要一次性把所有服务代码都写完再联调;policy-service 的表结构变更一律通过 Liquibase changelog 管理,不允许在 Java 代码里手写检测建表的逻辑;P5 阶段无 selector Service 加 Endpoints 的配置单独写清楚在一个文件里,顶部用注释说明这模拟的是云托管数据库、指向宿主机 IP,方便回头复习时一眼看懂意图。
结语:把「理解架构」和「写业务代码」分开
这整套规划背后有一个刻意的分工:练的是架构的骨架和各组件怎么协作,业务逻辑代码——玩具订单服务、embedding 切片脚本、Flink 聚合作业这些——没必要自己从头手写,交给 Claude Code 去实现更省时间,自己专注在「理解每一层在干什么、为什么这么设计、怎么排查问题」这个核心目标上。全景图是「知道有什么」,落地方案是「亲手把它跑起来」,两者合在一起,大概就是接下来这段时间给自己安排的功课。