接口重试的 4 种实现方案:手动重试、Spring Retry、Resilience4j、OpenFeign

接口调失败了?重试之前先看看这四种姿势 为什么需要重试 分布式系统里,接口调用失败是常态,不是意外。网络抖动、服务重启、连接池满、Full GC 停摆——这些故障每天都在发生。 但并不是每次失败都值得重试。有些失败重试一下就好了(瞬时故障),有些失败重试一万次也没用(业务异常、参数错误)。区分这两类失败,是设计重试策略的前提: flowchart TD Call(["发起 RPC 调用"]) --> Result{调用结果} Result -->|"成功"| OK([结束]) Result -->|"失败"| Type{失败类型} Type -->|"网络超时\n连接 refused\n503 Service Unavailable"| Retryable["可重试\n瞬时故障"] Type -->|"400 Bad Request\n403 Forbidden\n业务状态异常"| NoRetry(["直接抛出\n重试无意义"]) Retryable --> Idempotent{接口是否幂等} Idempotent -->|"是"| DoRetry(["执行重试"]) Idempotent -->|"否"| Warn["警告\n需人工介入"] Warn --> Check([手动排查]) classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fecaca,font-weight:bold; class Call,OK,NoRetry,DoRetry,Check startEnd; class Result,Type,Idempotent condition; class Warn reject; ⚠️ 新手提示:重试只适用于瞬时故障(transient failure)。如果下游返回 400/403/ 业务校验失败,查日志修代码,别重试。 方案一:手动重试——最简单但也最危险 最直接的方式就是用循环自己搞: ...

二月 16, 2023 · 3 分钟 · 568 字 · yaomingye

Go Runtime vs JVM:调度、GC、反射全方位对比

Go 运行时 vs JVM 运行时 一个 JVM 调优经验丰富的开发者第一次部署 Go 服务,看到监控数据时的反应: 这进程怎么只占 4MB? -Xmx 在哪设置?GC 日志怎么看? 接着打开 top ,看到 Go 服务起了几千个 goroutine,内存和 CPU 都低得离谱。而旁边跑了类似流量的 Spring Boot 服务, -Xmx512m 、GC 日志一大堆。 这不是魔法,是 Go runtime 和 JVM 的设计哲学完全不同。本文把两个运行时的核心差异讲清楚。 📌 前置知识:本文假定读者了解 JVM 的基本运行时概念(堆/栈/GC/类加载/JIT)和 Go 的 goroutine 基础知识。Go 版本为 1.22,对比 JVM HotSpot 17/21。 进程内存:4MB vs 512MB 的真相 维度 Go JVM(HotSpot) 最小内存 ~2-4MB ~50-200MB(含堆 + 元空间) 内存控制 自动,GOGC 环境变量 -Xmx / -Xms + 大量 JVM 参数 启动时间 毫秒级(AOT 编译) 秒级(类加载 + 解释执行 + JIT 预热) 部署产物 单一静态二进制(~10-20MB) JAR(需要 JRE/JDK 运行时) 内存占用大头 goroutine 栈 + 堆 + GC 元数据 堆 + 类元数据 + JIT 代码缓存 + 线程栈 两张图看清两个运行时各自的内存里到底装了什么: ...

二月 10, 2023 · 7 分钟 · 1322 字 · yaomingye

Go Web 开发全栈:从 Gin 到微服务

Go Web 开发:从 Gin 到微服务 一个 Spring Boot 程序员打开 Go 的 Web 项目,看到的是这样的代码: // 这是什么?Controller 在哪?@Autowired 在哪? func main() { db, _ := sql.Open("mysql", "user:pass@/dbname") repo := NewUserRepo(db) svc := NewUserService(repo) handler := NewUserHandler(svc) r := gin.Default() r.GET("/users/:id", handler.GetUser) r.Run(":8080") } 没有 @Controller 、没有 @Service 、没有 @Autowired 、没有 application.yml 。依赖是一个个手动拼起来的,路由是函数式注册的,连配置文件都得自己选库来读。 习惯 Spring Boot 全家桶的开发者,第一次面对 Go 的 Web 生态,大概有两类困惑: 框架选型:Gin、Echo、Fiber、Iris、go-zero、Kratos……每个都说自己性能好,到底该用哪个? 组织方式:没有注解驱动的 DI、没有 AOP、没有 Filter 接口——同样的需求在 Go 里怎么写? 本文用 Spring Boot/Spring Cloud 的对应视角,把 Go Web 开发的技术栈讲清楚。 ...

二月 8, 2023 · 9 分钟 · 1846 字 · yaomingye

Go 网络编程与 IO 模型

Go 网络编程 Java 程序员写网络服务,技术栈大概是这样的: // Java —— Netty 写一个 HTTP 服务 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new HttpServerCodec()) .addLast(new HttpObjectAggregator(65536)) .addLast(new SimpleChannelInboundHandler<FullHttpRequest>() { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) { // 业务逻辑... } }); } }); b.bind(8080).sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } 配置 EventLoopGroup、Channel Pipeline、Codec、Handler……对于一个简单的 HTTP 服务,一半代码在处理 Netty 的样板。 ...

二月 6, 2023 · 5 分钟 · 1011 字 · yaomingye

Go 并发编程:Goroutine、Channel 与 CSP 模型

Go 并发编程 写了 5 年 Java 并发,你手上的工具大概是这样的: // Java —— 线程池 + Future + BlockingQueue var executor = Executors.newFixedThreadPool(10); var future = executor.submit(() -> { return remoteService.query(); }); try { var result = future.get(5, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); } 现在看 Go 的等价写法: // Go —— goroutine + channel + select func queryWithTimeout() { ch := make(chan string, 1) go func() { ch <- remoteService.Query() }() select { case result := <-ch: fmt.Println(result) case <-time.After(5 * time.Second): fmt.Println("超时了") } } 没有 Executor ,没有 Future ,没有 BlockingQueue 。 go 关键字一写,协程就启动了。 chan 一建,数据就在协程间流动。 这是 Go 语言最独特的基因 。 ...

二月 4, 2023 · 7 分钟 · 1383 字 · yaomingye

Java vs Go 语法快速对比

Java vs Go:语法对比 打开 .go 文件,第一眼看到这些,脑子直接宕机: func getUser(id int) (*User, error) { if user, ok := cache.Load(id); ok { return user.(*User), nil } defer func() { metrics.Record("getUser") }() // ... } := 是什么? *User 和 error 为什么挤在返回值里? defer 又是什么鬼? ok 从哪冒出来的? 写了 5 年 Spring Boot,习惯了 var user = new User() 、 try-catch-finally 、 public class 之后,Go 的语法看起来像是故意反着来。这篇文章的目的就是一句话:把所有 Java 里习以为常的语法点,在 Go 里找到对应写法 。没有废话,全是代码对比。 📌 前置知识:本文假定读者有 Java 基础(Java 8+),了解基本的编程概念(变量、函数、循环、异常)。Go 版本为 1.22。 ...

二月 2, 2023 · 10 分钟 · 1994 字 · yaomingye

Go 语言历史与设计哲学

Go 是怎么来的? 第一次打开 .go 文件的 Java 程序员,通常会愣住。 type Handler struct { db *sql.DB } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { users, err := h.queryUsers(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } json.NewEncoder(w).Encode(users) } 脑子里弹出一串问题:class 在哪?构造函数在哪?try-catch 在哪?implements 在哪?为什么 err 是个返回值? 这很正常。写了 5 年 Spring Boot,习惯了 @Autowired 、 @Transactional 、 try-catch-finally 之后,Go 看起来像删掉了 90% 语法的 Java。但这不是残缺,是刻意的——Go 的设计哲学就是 少即是多 。 诞生:三个大佬对 C++ 的"不满" 2007 年,Google 的三个工程师——Robert Griesemer(Google V8 引擎参与者)、 Rob Pike(Unix 元老,Plan 9 作者)、 Ken Thompson(Unix 之父,B 语言/C 语言设计者)——在等 C++ 编译的时候,决定搞点事情。 ...

一月 31, 2023 · 5 分钟 · 902 字 · yaomingye

统一开发团队的流水线哲学

统一开发团队的流水线哲学:从 .gitlab-ci.yml 到 IDP 平台化治理 问题:为什么每个项目的流水线都长得不一样? 团队规模还小的时候,CI/CD 流水线通常是怎么来的?某个开发者把上个项目的 .gitlab-ci.yml 拷过来,改两行,能跑就行。再过两个月新开一个服务,又从那个改过的版本拷过去再改两行。一年下来,十个微服务有十种写法,review 流水线配置的时间比 review 业务代码还长。 这不是某个团队的个例,而是缺少 统一流水线规范 的必然结果。 造成这种混乱的根源有三层: 第一层,认知门槛。GitLab CI 的配置语法看似简单——stages、jobs、script、only/except,但真正写好需要理解 runner 的执行模型、cache 和 artifact 的区别、image 与 service 的作用域。大部分人止步于"能跑就行",不会主动深究。 第二层,缺乏约束。GitLab CI 本身不做 schema 校验,before_script 里写什么都行,Dockerfile 里的 RUN 指令堆多少层也没人管。没有门禁、没有模板、没有 review 机制,流水线质量完全依赖开发者个人习惯。 第三层,业务压力。“先把功能上线"永远排在"把流水线写好"前面。流水线的技术债不像业务代码那样直接影响用户,于是越欠越多,直到有一天构建 40 分钟没人敢动。 📌 前置知识——GitLab CI 基础:建议先理解 .gitlab-ci.yml 的 stages、jobs、script、image、cache、artifacts 六个核心关键字(只需理解各自的职责和生效范围即可)。 结构:一条理想流水线的骨架 先从最核心的问题开始: 一条"完美"的 .gitlab-ci.yml 应该长什么样? 答案不是给你一个 500 行的 YAML 文件,而是说清楚 原则。原则对了,具体写法可以按项目微调。 四阶段流水线 flowchart TD S([📥 代码提交]) --> A1 subgraph Stage1["🔍 验证阶段"] A1[📌 编译检查]:::process A2[📌 代码风格]:::process A3[📌 单元测试]:::process end Stage1 --> Stage2 subgraph Stage2["📦 构建阶段"] B1[📌 构建镜像]:::process B2[📌 推送镜像仓库]:::process B3[📌 导出制品]:::process end Stage2 --> Stage3 subgraph Stage3["🚀 部署阶段"] C1[📌 部署开发环境]:::highlight C2[📌 集成测试]:::process C3{📌 验收通过?}:::condition end C3 -->|✅ 是| Stage4 C3 -->|❌ 否| Rollback[⛔ 回滚通知]:::reject subgraph Stage4["📡 生产发布"] D1[📌 灰度发布]:::highlight D2[📌 全量上线]:::startEnd end classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; 四个阶段各司其职: ...

一月 29, 2023 · 6 分钟 · 1241 字 · yaomingye

SpringCloud微服务测试实战

SpringCloud微服务测试实战:分层策略、完整代码与AI时代的新思路 问题切入 写了一万行业务代码,测试用例只有三行——这种事情在微服务项目里尤其常见。不是开发者不想写测试,而是SpringCloud环境下的测试确实比单体应用复杂得多:服务之间通过Feign/Dubbo调用、配置在Nacos远端、消息通过RocketMQ/Kafka传递、数据库还分库分表。随便写个Service都依赖五六个外部组件,怎么测? 先说结论:微服务测试的核心思路是分层隔离。不同层级关注不同的验证目标,用不同的策略来隔离外部依赖。每一层有明确的边界和颗粒度,而不是不管三七二十一全部启动Spring容器。 flowchart TD subgraph Top[🔺 测试金字塔:越往上越慢、越贵、越少] subgraph L5[⏱️ 端到端测试] E2E[🌐 E2E测试\n全链路验证\n数量:极少] end subgraph L4[🔗 契约/集成测试] CONTRACT[📋 契约测试\nFeign/Dubbo接口契约\n数量:少量] INTEG[🔧 Service集成测试\nSpring容器+真实DB/Redis\n数量:适中] end subgraph L3[🧩 切片测试] WEB[🌐 Web层测试\n@WebMvcTest\n仅Controller上下文] DATA[🗄️ 数据层测试\n@DataJpaTest\n仅JPA上下文] end subgraph L2[⚡ 单元测试] UNIT[📐 纯单元测试\n无Spring容器\nMock所有依赖\n数量:大量] end end L5 --> L4 L4 --> L3 L3 --> L2 classDef layer fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; class E2E,CONTRACT,INTEG,WEB,DATA,UNIT layer class L2 highlight class L2 data 这个金字塔翻译成SpringCloud语境下的操作指南,就是下面这张分层策略表: 测试层级 启动Spring容器? 真实依赖 Mock/Stub 单个耗时 覆盖目标 纯单元测试 否 无 所有外部依赖 毫秒级 业务逻辑分支 Web层切片 是(仅Controller) 无 Service/Mapper 1 ~ 3秒 参数校验/序列化/异常处理 数据层切片 是(仅JPA) 内嵌数据库(H2) 无 1 ~ 3秒 SQL映射/查询方法 Service集成测试 是(完整) H2/内嵌Redis Feign/MQ/外部API 3 ~ 8秒 事务边界/缓存/业务编排 契约测试 是(Consumer端) 无 对Provider的Stub 2 ~ 5秒 Feign接口签名一致性 端到端测试 是(全部服务) 全部 无 分钟级 全链路连通性 ⚠️ 新手提示:这张表建议存下来当速查卡。每次写完代码准备写测试时,先对着表想清楚"这一层该启动什么、该Mock什么",比盲目写省一半时间。 ...

一月 26, 2023 · 9 分钟 · 1870 字 · yaomingye

从写完代码到上线运行

从写完代码到上线运行:SpringBoot微服务CI/CD完整链路 目标说明 这篇教程要解决一个很实际的问题:写完SpringBoot微服务代码之后,怎么把它弄到线上稳定运行? 很多开发者(尤其是刚入行的)对这块的认知是模糊的——“代码写完了,接下来是不是找个服务器丢上去就行了?” 实际过程远比这个复杂,涉及到测试验证、容器化、CI/CD流水线、配置中心、网关路由等一系列环节。 本教程将以一个典型的SpringBoot微服务项目为例,从代码提交前的本地测试开始,一步步走到Kubernetes集群上的生产环境部署。每个环节都给出完整可复制的脚本和配置文件,不跳步,不给半截代码。 ⚠️ 新手提示:这篇教程假设读者能独立用SpringBoot写CRUD接口,但对DevOps/运维侧的流程不熟悉。如果连SpringBoot项目怎么创建都还不太清楚,建议先去翻翻SpringBoot入门文档再回来看。 前置条件 开始之前,先确认本地环境是否满足以下条件。每项后面附了验证命令,直接在终端里跑一下就能确认。 序号 前置条件 最低版本 验证命令 说明 1 JDK 8+ java -version 编译和运行SpringBoot项目 2 Maven 3.6+ mvn -version 项目构建和依赖管理 3 Docker 20.10+ docker version 容器镜像构建 4 Git 2.30+ git version 版本控制和协作 5 SpringBoot项目 2.x mvn spring-boot:run 已有可正常启动的项目 6 kubectl 1.20+ kubectl version 部署阶段需要(可最后装) 📌 前置知识:Docker的基础概念(镜像、容器、仓库三者的关系)。如果不清楚,可以先跑一遍 docker run hello-world 感受一下,然后大致了解 docker build、docker push、docker pull 三条命令的作用。 环境搭建 开始实践之前,先把必要的环境准备到位。下面按依赖顺序逐步完成。 确认Docker环境 # 检查Docker是否安装并运行 docker version # 预期输出(版本号可能不同): # Client: Docker Engine - Community # Version: 20.10.16 # Server: Docker Engine - Community # Engine: # Version: 20.10.16 # 如果Docker daemon没启动,先启动它 # Linux: sudo systemctl start docker # Mac/Windows: 打开Docker Desktop 安装Docker Compose(用于本地集成测试) # 检查是否已安装 docker compose version # 预期输出:Docker Compose version v2.10.2 # 如果没有,参考官方文档安装: # https://docs.docker.com/compose/install/ 配置Maven settings.xml Maven默认从中央仓库拉依赖,在国内网络环境下可能很慢。建议配置国内镜像: ...

一月 25, 2023 · 13 分钟 · 2676 字 · yaomingye
Cat Radio