微服务拆分实战:以 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

Fastjson 进化史:从 1.x 漏洞到 Fastjson2

Fastjson 进化史 📖 前置阅读:本文假设读者已理解序列化的基本概念和 JSON 序列化工具的用法。如果还不熟悉,建议先阅读 序列化本质与 Jackson 全操作指南。 一、⚡ Fastjson 曾经有多火 Fastjson 是阿里巴巴 2011 年开源的 JSON 序列化库。在 Jackson 还比较沉重、Gson 性能一般的年代,Fastjson 凭三个特点迅速占领了国内 Java 项目: 卖点 具体表现 快 号称"Java 语言最快的 JSON 处理库"——字节码生成 + ASM 动态优化,比 Jackson 快 2x API 简洁 JSON.toJSONString(obj) 一行搞定——比 Jackson 的 ObjectMapper 样板代码少 阿里出品 阿里巴巴开源——国内 Java 圈号召力最强背书 最火的那些年,几乎所有国内 Java 项目引入 Fastjson——Dubbo、RocketMQ、Nacos 内部都依赖了它。 二、💣 autoType:从"核心卖点"到"最大漏洞" 2.1 autoType 是什么 Fastjson 有一个 Jackson 没有的独特功能——autoType。它的作用是:JSON 中有一个 @type 字段,Fastjson 根据它自动反序列化为对应的 Java 类。 // Fastjson 的 autoType 机制 // 序列化时——自动写入类的全限定名 String json = JSON.toJSONString(order); // 结果:{"@type":"com.example.Order","orderId":10001,"amount":6999.00} // ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ // @type 字段记录了类的全限定名 // 反序列化时——根据 @type 字段自动还原为正确的子类型 // JSON 数据:"{\"@type\":\"com.example.Order\",\"orderId\":10001}" // 即使你只声明为 Object,Fastjson 也能还原出 Order 对象 Object obj = JSON.parse(json); // 实际返回 Order 实例 这个功能在 Jackson 中需要 @JsonTypeInfo 注解手动声明——Fastjson 默认自动开启。 ...

十一月 26, 2022 · 5 分钟 · 1055 字 · yaomingye

序列化本质与 Jackson 全操作指南

序列化本质与 Jackson 一、⚡ 序列化是什么:Java 对象和 JSON 之间的翻译官 日常写的代码里全是 Java 对象——Order、User、Product。但网络传输只能传二进制/文本,数据库只能存文本,前端浏览器只认得 JSON。怎么把 Java 对象变成 JSON、再把 JSON 变回 Java 对象?这就是序列化和反序列化: 序列化(Serialization): Java 对象 → JSON / XML / 二进制 反序列化(Deserialization): JSON / XML / 二进制 → Java 对象 如果不做序列化,你就得手动拼 JSON: // 没有序列化工具——手动拼 JSON(又臭又长) public String orderToJson(Order order) { return "{" + "\"orderId\":" + order.getOrderId() + "," + "\"userId\":" + order.getUserId() + "," + "\"productName\":\"" + escape(order.getProductName()) + "\"," + "\"amount\":" + order.getAmount() + "}"; } // 手写这段代码的时候,你就知道自己需要一个序列化工具了 序列化框架做的事就是自动完成这个转换——你要做的只是加几个注解、调一行方法。 ...

十一月 25, 2022 · 7 分钟 · 1368 字 · 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 集群容错、负载均衡与异步调用

Dubbo 集群容错 📖 前置阅读:本文假设读者已掌握 SpringBoot Dubbo 的基本 RPC 操作(@DubboService / @DubboReference)。如果还不熟悉,建议先阅读 SpringBoot Dubbo 全操作指南。 一、⚡ 问题切入:RPC 调用失败了怎么办? 上一篇文章的 @DubboReference 默认配置了三件事: Provider 有多个实例时——随机选一个(负载均衡) 调用失败时——自动重试 2 次(集群容错) 调用超过 1 秒——抛超时异常 默认策略覆盖了 80% 的场景,但剩下的 20% 需要精确控制——这就是 Dubbo 服务治理的核心:集群容错(怎么处理失败)、负载均衡(怎么选择节点)、调用模式(同步还是异步)。 二、集群容错 —— 失败了怎么办 2.1 六种集群容错策略 Dubbo 的 Cluster 接口定义了容错行为。Consumer 侧配置: 策略 配置值 行为 适用场景 Failover(默认) failover 失败后自动切换其他 Provider 重试,默认重试 2 次(共 3 次调用) 幂等的读操作 Failfast failfast 失败后立即报错——不重试 非幂等写操作(创建订单、扣库存) Failsafe failsafe 失败后吞掉异常——返回 null / 忽略错误 非关键操作(日志、通知) Failback failback 失败后记录到后台线程——定时重试 最终一致性场景(数据同步) Forking forking 同时调用所有 Provider——取第一个成功返回的 高可用低延迟,但浪费资源 Broadcast broadcast 逐个调用所有 Provider——任何一个失败就报错 通知所有实例(缓存刷新、配置更新) // 消费端配置集群容错策略 @DubboReference( cluster = "failfast", // 写操作——不重试 retries = 0 // 即使 Failover 模式,也可以设 retries=0 关掉重试 ) private OrderService orderService; 2.2 Failover 原理与幂等陷阱 Failover 是默认策略——它的逻辑是: ...

十一月 21, 2022 · 6 分钟 · 1164 字 · yaomingye

SpringBoot Dubbo 全操作指南

SpringBoot Dubbo 📖 前置阅读:本文假设读者已理解 Dubbo 的核心概念(Registry、Provider、Consumer、RPC 调用链路)。如果还不熟悉,建议先阅读 Dubbo 核心架构与 RPC 模型。 🎯 第一步:目标说明 上一篇用原生 Dubbo + Spring XML 写了 <dubbo:service>、<dubbo:reference>、<dubbo:registry>。SpringBoot 时代不需要那些 XML——dubbo-spring-boot-starter 用两个注解 + 一套 yml 配置替代全部 XML。 读完这篇会掌握: dubbo-spring-boot-starter 环境搭建——依赖 + yml 配置 @DubboService——暴露服务,替代 <dubbo:service> @DubboReference——引用远程服务,替代 <dubbo:reference> dubbo 协议 vs triple 协议——什么时候用哪个 Hessian2 / Fastjson2 序列化配置 Nacos 注册中心的 SpringBoot 集成 📋 第二步:前置条件 前置项 具体要求 验证命令 JDK 17+(8+ 也兼容) java -version SpringBoot 3.x(文中用 3.2) mvn dependency:tree | grep spring-boot Nacos 2.3.0(单机即可) docker ps | grep nacos 前置知识 Registry/Provider/Consumer 概念 — 确认 Nacos 在跑: ...

十一月 20, 2022 · 6 分钟 · 1213 字 · yaomingye

Kafka 生产环境部署与调优

Kafka 生产环境实战 📖 前置阅读:本文是 Kafka 系列的终篇,假设读者已经掌握前五篇的全部内容(核心架构、SpringBoot 集成、Producer 深入、Consumer 深入、Kafka Streams)。 一、⚡ 问题切入:单节点 Docker 的瓶颈 第一篇搭的单节点 KRaft Kafka(一个 Controller + Broker 合体进程)只能用来学习——生产环境中: 单点 后果 唯一的 Broker 挂了 所有消息不可发送/消费——整个 Kafka 瘫痪 无副本 Broker 磁盘损坏 → 消息永久丢失 JVM 内存不足 Full GC 频繁 → 消息延迟抖动 → Producer 超时失败 磁盘写满 Partition 无法写入 → Producer 阻塞或报错 生产最低配:3 台 Broker + 3 台 Controller(或 3 台合体节点,controller + broker 混合模式),每个 Topic 至少 2 副本。 二、KRaft 三节点集群搭建 2.1 架构设计 第一篇用的 KRaft 模式是单节点(Controller 和 Broker 运行在一个进程中)。生产环境拆开: ...

十一月 18, 2022 · 8 分钟 · 1529 字 · yaomingye

Kafka Streams 与高级特性

Kafka Streams 流处理实战 📖 前置阅读:本文假设读者已掌握 Kafka Consumer/Producer 的使用和 Offset/Partition 概念。如果还不熟悉,建议先阅读 Consumer 深入:位移管理与 Rebalance。 一、⚡ 问题切入:用 Consumer + Producer 写流处理有什么毛病? 假设需要统计每个商品的近 5 分钟销量。用 Consumer + Producer 写: // 用 Consumer + Producer 实现滑动窗口计数——代码量爆炸 Map<String, List<Long>> windowCache = new HashMap<>(); // 还需要处理:窗口过期清理、状态持久化、故障恢复、乱序数据... 这个需求正是流处理引擎的用武之地。Kafka Streams 是 Kafka 官方的流处理库——它不是另一个需要部署的服务(不像 Flink/Spark Streaming),而是一个 Java 库,跑在你的应用进程里。 Kafka Streams 本质:Consume → 计算 → Produce,全部走 Kafka,中间状态存在 Kafka 的本地 RocksDB 实例中。 flowchart LR 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; INPUT[("input-topic\n原始数据")] -->|"consume"| KS[Kafka Streams\n计算引擎\n+ RocksDB 本地状态] KS -->|"produce"| OUTPUT[("output-topic\n处理结果")] KS -->|"备份"| CHANGELOG[("changelog-topic\n状态变更日志")] class INPUT,OUTPUT data; class CHANGELOG process; class KS highlight; 二、Kafka Streams 核心概念 2.1 KStream vs KTable Kafka Streams 有两个核心抽象,理解它们的区别是正确使用的前提: ...

十一月 17, 2022 · 6 分钟 · 1176 字 · yaomingye
Cat Radio