OpenFeign 生产实战——Nacos + Sentinel + 性能调优

生产实战:Nacos + Sentinel + 性能调优 📖 前置阅读:本文假设读者已掌握 OpenFeign 的配置和容错机制。如果还不熟悉,建议先阅读 OpenFeign 核心概念与快速上手 和 进阶——配置、拦截器与容错。 一、⚡ Feign 调通了——但生产环境的三块拼图还缺着 前两篇搞定了 Feign 的基本用法、超时、重试、拦截器、Fallback。但生产环境还有三件事必须做: ① Feign + Nacos —— 不再写死 URL——服务自动发现、负载均衡 ② Feign + Sentinel —— 被调服务慢/挂了——熔断降级保护调用方 ③ Feign + 性能调优 —— Gzip 压缩、连接池、异步并发 二、🧩 Feign + Nacos 服务发现——零 URL 硬编码 2.1 依赖 <dependencies> <!-- OpenFeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- Nacos 服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- LoadBalancer——Feign 自动集成——不需要显式引入 --> </dependencies> 2.2 配置 spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 namespace: production # 命名空间——隔离环境 group: ORDER_GROUP # 分组 // Feign 接口——只声明服务名,不写 URL @FeignClient(name = "user-service") // Nacos 中有 user-service 这个服务 public interface UserClient { @GetMapping("/api/users/{userId}") User getUser(@PathVariable("userId") Long userId); } 2.3 负载均衡策略 Feign 默认用 Spring Cloud LoadBalancer——轮询策略。想换成随机或加权策略: ...

十二月 12, 2022 · 7 分钟 · 1285 字 · yaomingye

OpenFeign 进阶——配置、拦截器与容错

进阶指南:配置、拦截器与容错 📖 前置阅读:本文假设读者已掌握 OpenFeign 的基本用法——@FeignClient、注解映射。如果还不熟悉,建议先阅读 OpenFeign 核心概念与快速上手。 一、⚡ 调通了——但第二天线上就出问题 Feign 的基本调用 5 分钟搞定。但一上生产——问题一个接一个: 问题 ①:用户服务偶尔慢 2 秒——订单服务的 Feign 一直等——线程全卡死 → 需要超时配置 问题 ②:网络抖动——请求偶尔失败——直接抛异常给用户 → 需要重试机制 问题 ③:用户服务需要 Token 鉴权——每次调 Feign 都要手动传 Header → 需要拦截器自动注入 问题 ④:用户服务挂了——Feign 调不通——订单服务的线程池被占满 → 需要 Fallback 降级 问题 ⑤:排查问题——Feign 到底发了什么请求?返回了什么? → 需要日志 这一篇把以上每个问题都给出具体配置和代码。 二、⏱️ 超时与重试——Feign 最容易被忽略的配置 2.1 默认的超时太长了 Feign 底层用 Ribbon(老版本)或 LoadBalancer(新版本)做负载均衡。默认超时: 参数 默认值 说明 connect-timeout 1s 建立 TCP 连接的超时——默认还好 read-timeout 60s 等响应的超时——太长了!一个慢请求能卡 60 秒 # application.yml——Feign 超时配置 spring: cloud: openfeign: client: config: # ① 全局配置——对所有 FeignClient 生效 default: connect-timeout: 3000 # 建连接最多等 3s read-timeout: 5000 # 等响应最多等 5s logger-level: BASIC # ② 按服务配置——针对特定服务 user-service: # 这个名字和 @FeignClient(name="user-service") 对应 connect-timeout: 2000 read-timeout: 3000 # 用户服务是核心——超时设短点 product-service: connect-timeout: 5000 read-timeout: 10000 # 商品服务偶尔慢——多给点时间 2.2 重试——哪些请求能重试,哪些不能 spring: cloud: openfeign: client: config: default: retryer: com.example.feign.DefaultRetryer # 自定义重试器 // 自定义重试策略 @Configuration public class FeignRetryConfig { @Bean public Retryer feignRetryer() { // 参数:period(初始间隔), maxPeriod(最大间隔), maxAttempts(最多尝试次数) // 下面 = 初始等 100ms → 每次乘 1.5 → 最多重试 3 次(总共 4 次) return new Retryer.Default(100, 1500, 3); } } 重试的时间线: 第 1 次请求 → 失败 → 等 100ms 第 2 次请求 → 失败 → 等 250ms 第 3 次请求 → 失败 → 等 625ms 第 4 次请求 → 成功 → 返回 如果第 4 次也失败 → 抛异常 ⚠️ 新手提示:POST 请求不要重试!如果创建订单的 POST 请求超时——Feign 自动重试——用户被扣了两次钱。GET 可以重试(幂等),POST/PUT/DELETE 绝不重试。要控制这个——用 @FeignClient 的 configuration 属性对不同接口用不同的重试策略。 ...

十二月 11, 2022 · 6 分钟 · 1144 字 · yaomingye

OpenFeign 核心概念与快速上手

核心概念与快速上手 一、⚡ 微服务拆了——然后呢? 你把单体应用拆成了用户服务和订单服务。数据库拆了、代码拆了、团队也拆了。然后订单服务需要查用户信息: // 拆分之前——同一个 JVM,直接调方法 User user = userService.getUserById(userId); // 拆分之后——跨 JVM、跨机器 // 你必须写一堆 HTTP 调用代码 RestTemplate restTemplate = new RestTemplate(); String url = "http://user-service:8081/api/users/" + userId; User user = restTemplate.getForObject(url, User.class); 这段代码有四个问题: 问题 RestTemplate 写法 怎么解决 URL 硬编码 "http://user-service:8081/api/users/" 服务名代替 IP:Port——自动发现 参数拼接繁琐 url + userId 手动拼 声明参数——自动放到 URL 上 返回值无类型保障 getForObject(url, User.class) 手动指定 接口方法声明返回类型——编译器检查 和本地调用差距太大 完全不同的调用方式——学习成本 写法完全和本地方法一样 OpenFeign 解决的就是这个问题——让你调远程服务和调本地方法一样,而且完全不侵入被调方的接口。 二、🧩 OpenFeign 是什么——一句话 OpenFeign 是一个声明式 HTTP 客户端。你只需要写一个 Java 接口 + 注解,Feign 自动帮你生成 HTTP 调用的实现。 ...

十二月 10, 2022 · 4 分钟 · 842 字 · yaomingye

gRPC Gateway 与生产环境部署

gRPC-Gateway:让前端也能调 gRPC 📖 前置阅读:本文假设读者已完成 gRPC 服务的开发和微服务拆分。如果还不熟悉,建议先阅读 SpringBoot gRPC 全操作指南 和 微服务拆分实战:以 proto 为契约。 一、⚡ gRPC 最大的问题:浏览器不支持 你花了两周把微服务之间的通信全换成了 gRPC——性能翻了 3 倍,Protobuf 二进制传输省了 60% 带宽。看起来很完美。 然后前端同事找来了:“你的接口怎么调?Postman 发 HTTP 请求连不上。" 这就是 gRPC 最大的现实问题——gRPC 基于 HTTP/2,浏览器不直接支持 gRPC 协议。你在浏览器里 fetch('http://localhost:9090/...') 是调不通的——浏览器不会说 gRPC。 解决方案是gRPC-Gateway——在 gRPC 服务前面放一个网关,对外提供标准的 HTTP RESTful JSON 接口,对内转成 gRPC 调用: 浏览器/移动端/curl(HTTP/1.1 JSON) ↓ [gRPC-Gateway / Envoy / grpc-web] ← 协议转换层 ↓ gRPC(HTTP/2 Protobuf) [gRPC Server] 二、🔌 方案选择:三种网关方案 方案 原理 适用场景 复杂度 gRPC-Gateway 从 proto 自动生成反向代理代码——HTTP JSON ↔ gRPC 转换 gRPC 服务需要同时支持 HTTP JSON 和 gRPC 调用方 中 Envoy gRPC-JSON Transcoder Envoy 代理层做协议转换——不需要修改代码 有服务网格——统一的入口网关 中 grpc-web + Envoy 浏览器用 grpc-web 协议(HTTP/1.1),Envoy 转成 gRPC 前端直接在浏览器中调 gRPC(不需要 REST 包装) 高 本文重点讲方案一 gRPC-Gateway——它最直接、不需要额外的代理基础设施、和 SpringBoot 整合最简单。 ...

十二月 1, 2022 · 10 分钟 · 2072 字 · yaomingye

微服务拆分实战:以 proto 为契约

微服务拆分实战 📖 前置阅读:本文假设读者已掌握 Protobuf 语法和 SpringBoot gRPC 的基本用法。如果还不熟悉,建议先阅读 Protobuf 语法精讲与 gRPC 概念 和 SpringBoot gRPC 全操作指南。 一、⚡ 微服务拆分后的第一个难题 你把这个巨大的 SpringBoot 单体应用拆成了三个微服务——订单服务、用户服务、商品服务。拆得很干净——各自有自己的数据库、各自独立部署。 然后你发现了一个问题:订单服务在创建订单时需要查用户信息、扣商品库存——这三个服务之间怎么通信? 创建订单的流程: OrderService → 查用户是否存在 → UserService OrderService → 查商品价格 + 库存 → ProductService OrderService → 创建订单 → 自己的数据库 REST 当然能做——但这里有一个更关键的问题:你怎么保证 OrderService 调 UserService 的参数格式不出错? UserService 说它接收 GET /users/{id} 返回 {"userId": 1, "userName": "张三"}。OrderService 的开发者在代码里写了 restTemplate.getForObject("/users/" + userId, UserDTO.class) —— 但 UserDTO 的字段名是 user_name 还是 userName?谁说了算? proto 文件就是"谁说了算"的答案——它就是服务之间的契约。 二、🧬 以 proto 为契约:核心思想 没有契约(REST 口头约定): OrderService 开发者问 UserService 开发者:"你的接口返回什么字段?" UserService 开发者:"userName 和 userId" OrderService 写代码 → 字段名写成了 username → 运行时炸了 有契约(proto): user.proto 里写死了 User 的字段和类型 UserService 实现时 → 必须遵守 OrderService 调用时 → 编译器帮你检查 → 字段名不一致?编译都过不了——根本跑不起来 proto 是微服务之间的"合同"——双方签字画押,编译器当法官。谁不遵守谁编译不过。 ...

十一月 30, 2022 · 11 分钟 · 2284 字 · yaomingye

SpringBoot gRPC 全操作指南

SpringBoot gRPC 实战 📖 前置阅读:本文假设读者已掌握 Protobuf 语法和 gRPC 四种调用模式的概念。如果还不熟悉,建议先阅读 Protobuf 语法精讲与 gRPC 概念。 🎯 第一步:目标说明 上一篇用 protoc 写了 .proto 文件,手动编译生成了 Java 代码。接下来把这一切接入 SpringBoot——用 @GrpcService 暴露 gRPC 服务,用 @GrpcClient 注入远程代理,四种 RPC 模式全部用代码跑通。 📋 第二步:前置条件 前置项 具体要求 验证命令 JDK 17+ java -version SpringBoot 3.x mvn dependency:tree | grep spring-boot protoc 3.25+ (Maven 插件会自动下载) — 前置知识 Protobuf 语法、gRPC 四种模式概念 — 🔧 第三步:项目结构与依赖 3.1 多模块 Maven 项目 grpc-demo ├── pom.xml # 父 POM ├── grpc-api/ # proto 文件 + 生成的 Java 代码 │ ├── pom.xml # 有 protobuf-maven-plugin │ └── src/main/proto/ │ └── order.proto ├── grpc-server/ # gRPC 服务端——实现业务逻辑 │ ├── pom.xml # 依赖 grpc-api │ └── src/main/java/... └── grpc-client/ # gRPC 客户端——调用远程服务 ├── pom.xml # 依赖 grpc-api └── src/main/java/... 为什么要把 proto 放在独立模块? 服务端和客户端都依赖 proto 生成的 Java 代码——放独立模块中双方共享编译结果,而不是各自编译一份。 ...

十一月 29, 2022 · 8 分钟 · 1495 字 · yaomingye

Protobuf 语法精讲与 gRPC 概念

Protobuf 语法与 gRPC 概念 一、⚡ Protobuf 是什么:为什么非要学一门"新语言"? 前面讲了 JSON 序列化——Jackson 和 Fastjson2 把 Java 对象和 JSON 之间互转。JSON 是人眼可读的文本——但文本格式有两个天然的劣势: JSON 消息体(108 字节): { "orderId": 10001, "userId": 2001, "productName": "iPhone 15", "amount": 6999.00 } 同样的信息——Protobuf 二进制(约 35 字节): \x08\x91N\x12\x04...(肉眼不可读的二进制) JSON Protobuf 文本格式——每个字符占 1 字节 二进制格式——用最少的字节表达同样的数据 字段名重复传输("orderId" 每次都要传) 字段名不传——用数字编号代替(orderId = 1) 解析慢——文本解析器 解析快——二进制解码器 人眼可读——curl 能调 人眼不可读——需要专用工具 Protobuf 省的不是几字节——是高并发场景下每一条消息都省 60%~80% 带宽。这就是为什么 gRPC 选择 Protobuf 作为默认序列化格式。 二、🧬 Protobuf 语法逐一拆解 Protobuf 的语法就是定义数据结构和服务接口的语言。文件后缀是 .proto。先看一个完整的例子,然后每个语法元素拆开讲: // order.proto —— 订单服务的完整 proto 定义 syntax = "proto3"; // ① 语法版本 package com.example.order; // ② 包名 option java_multiple_files = true; // ③ 编译选项 option java_package = "com.example.order.dto"; import "google/protobuf/timestamp.proto"; // ④ 导入其他 proto // ⑤ 枚举定义 enum OrderStatus { ORDER_STATUS_UNSPECIFIED = 0; // 枚举第一个值必须是 0 ORDER_STATUS_CREATED = 1; ORDER_STATUS_PAID = 2; ORDER_STATUS_CANCELLED = 3; } // ⑥ 消息定义——Protobuf 的核心 message Order { int64 order_id = 1; // 字段编号 = 1 string product_name = 2; // 字段编号 = 2 double amount = 3; // 字段编号 = 3 OrderStatus status = 4; // 使用上面定义的枚举 google.protobuf.Timestamp created_at = 5; // 使用导入的时间戳类型 repeated string tags = 6; // repeated = 数组/列表 map<string, string> metadata = 7; // map 类型 } // ⑦ 服务定义——gRPC 的方法声明 service OrderService { rpc GetOrder(GetOrderRequest) returns (Order); rpc ListOrders(ListOrdersRequest) returns (stream Order); // 服务端流 rpc CreateOrder(stream CreateOrderRequest) returns (stream Order); // 双向流 } // 消息定义可以在 service 之后——顺序不要求 message GetOrderRequest { int64 order_id = 1; } message ListOrdersRequest { int64 user_id = 1; int32 page_size = 2; } message CreateOrderRequest { string product_name = 1; double amount = 2; } 2.1 syntax —— 声明语法版本 syntax = "proto3"; // 必须在文件第一行(注释上面可以,语法上面不能有东西) 版本 特点 当前状态 proto2 老版本——有 required/optional/default 关键字 gRPC 也支持,但不推荐新项目用 proto3 新版本——去掉了 required 和 default,所有字段默认可选 当前主流——新项目统一用 proto3 2.2 package —— 包名 package com.example.order; // 防止命名冲突——和 Java 的 package 概念一样 // 生成 Java 代码时:类放在 com.example.order 包下 2.3 option —— 编译选项 // java_multiple_files: true → 每个 message 生成一个独立的 .java 文件 // false(默认) → 所有 message 生成到一个巨大的外部类中 option java_multiple_files = true; // java_package: 指定生成的 Java 文件所在的包——如果不写,用 package 的值 option java_package = "com.example.order.dto"; // java_outer_classname: java_multiple_files = false 时——指定外部类的类名 option java_outer_classname = "OrderProto"; // optimize_for: 生成代码优化方向——SPEED / CODE_SIZE / LITE_RUNTIME option optimize_for = SPEED; 选项 默认值 建议 java_multiple_files false true——每个 message 一个文件,方便 IDE 导航 java_package package 的值(但它是默认的,不是必然) 写成和 Java 项目一致的包名 optimize_for SPEED 保持默认 2.4 import —— 导入其他 proto 文件 import "google/protobuf/timestamp.proto"; // 导入 Google 内置类型 import "common/common.proto"; // 导入自己项目的公共 proto import public "common/new.proto"; // 公开导入——谁 import 你,谁也看得到 new.proto 的类型 常用内置类型(google/protobuf/ 下的类型): ...

十一月 28, 2022 · 7 分钟 · 1378 字 · yaomingye

Dubbo 生产环境部署与调优

Dubbo 生产部署 📖 前置阅读:本文是 Dubbo 系列的终篇,假设读者已经掌握前五篇的全部内容(核心架构、SpringBoot 集成、集群容错与负载均衡、注册中心、Dubbo 3.x 新特性)。 一、⚡ 问题切入:单机开发的配置能上生产吗? 前五篇的页面配置——超时 1 秒、重试 2 次、单注册中心、无限流无监控——只能用来学习。生产环境: 单点/隐患 后果 一个 Nacos 实例 Nacos 挂了 → 新 Consumer 启动不了 → 新 Provider 无法注册 没设并发限制 一个 Provider 被大量请求打爆 → 线程池满 → 所有请求排队或失败 超时设太短 Provider 还在处理,Consumer 已断开 → 重复调用 没有监控 Provider 变慢了、Success Rate 下降了——完全不知道 无优雅上下线 重启 Provider → 正在处理的请求全部失败 生产最低配:Nacos 集群(至少 3 台) + Provider 并发限制 + Consumer 合理超时 + Dubbo Admin 监控 + 优雅上下线。 ...

十一月 24, 2022 · 6 分钟 · 1139 字 · yaomingye

Dubbo 3.x 新特性:Triple 协议与应用级服务发现

Dubbo 3.x 新特性 📖 前置阅读:本文假设读者已掌握 Dubbo 的基本 RPC 开发、注册中心使用(Nacos)和 dubbo 协议。如果还不熟悉,建议先阅读 Dubbo 核心架构与 RPC 模型 和 注册中心:Nacos 与 Zookeeper。 一、⚡ 问题切入:dubbo 协议有什么不够用的? 第一篇说了 dubbo 协议的优点——TCP 长连接 + Hessian2 二进制序列化,性能极佳。但它的设计产生于 2011 年: dubbo 协议的局限 为什么是问题 私有二进制协议 浏览器和 curl 调不了——非 HTTP,无法穿透通用 HTTP 网关 Java 中心 协议体是 Java 特有的——Go/Node.js/Python 客户端需要单独实现协议栈 服务网格不友好 Istio/Envoy 基于 HTTP/1.1 和 HTTP/2 做流量治理——私有协议无法被 Sidecar 理解 接口级服务发现 注册中心存储的是接口粒度数据(org.example.OrderService:getOrderById)——微服务有几百个接口时,注册数据爆炸 Dubbo 3.x 的两大革新直接解决这些问题: Triple 协议——基于 HTTP/2 + Protobuf,解决私有协议问题 应用级服务发现——从接口粒度改为应用粒度,解决注册数据爆炸 二、Triple 协议 —— HTTP/2 能力 + RPC 性能 2.1 Triple 协议的本质 Triple 协议 = 将 gRPC 的传输协议(HTTP/2 + Protobuf)搬到 Dubbo 上,同时保留 Dubbo 的服务治理能力(注册中心、负载均衡、集群容错)。 ...

十一月 23, 2022 · 5 分钟 · 944 字 · yaomingye

Dubbo 注册中心:Nacos 与 Zookeeper

Dubbo 注册中心 📖 前置阅读:本文假设读者已掌握 Dubbo 的基本 RPC 开发和集群容错配置。如果还不熟悉,建议先阅读 SpringBoot Dubbo 全操作指南 和 集群容错与负载均衡。 一、⚡ 问题切入:Registry 到底是什么? 前面三篇反复提到"注册中心"——Provider 向它注册,Consumer 从它订阅。但注册中心不只是一个"存地址的地方": 注册中心的职责 具体行为 服务注册 Provider 启动时把"IP:Port + 接口名 + 元数据"写入 Registry 服务发现 Consumer 启动时从 Registry 拉取"接口名 → 地址列表"的映射 健康检查 检测 Provider 是否存活——不存活就剔除 变更推送 Provider 地址列表变化时,主动通知 Consumer 配置管理(Nacos) 动态下发配置——不需要重启应用 关键点:Registry 只在服务发现阶段起作用。Consumer 拿到 Provider 地址后——直连调用,不经过 Registry。这是和 MQ 的 Broker 最本质的区别。 二、服务注册与发现的全链路 2.1 详细流程 flowchart TD classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; subgraph REGISTER [注册阶段] P1[Provider 启动] --> P2["向 Nacos 注册\nServiceName: order-provider\nIP: 192.168.1.10\nPort: 20880\nInterface: org.example.OrderService\nMethods: getOrderById, createOrder..."] end subgraph DISCOVER [发现阶段] C1[Consumer 启动] --> C2["向 Nacos 订阅\nInterface: org.example.OrderService"] C2 --> C3["Nacos 返回\n[192.168.1.10:20880\n 192.168.1.11:20880\n 192.168.1.12:20880]"] C3 --> C4[Consumer 存到本地缓存] C4 --> C5["选一个 Provider\n建立 TCP 长连接"] end subgraph HEARTBEAT [心跳阶段] P2 --> H1["Provider 每 5s\n向 Nacos 发心跳"] H1 --> H2{Nacos 15s\n没收到心跳?} H2 -- "是" --> H3["标记为不健康\n30s 后剔除"] H3 --> H4["推送变更通知\n给所有订阅的 Consumer"] H4 --> C6[Consumer 更新\n本地地址缓存] end class P1,C1,C5 startEnd; class P2,C2,C3,C4,C6,H1,H3,H4 process; class H2 highlight; 2.2 注册中心的存储结构 以 Nacos 为例,注册的数据长这样: ...

十一月 22, 2022 · 4 分钟 · 845 字 · yaomingye
Cat Radio