温习微服务基础知识

复习顺序:

  1. 网络通信:为什么两个服务需要互相发送数据?

  2. 序列化:什么是 JSON,为什么还需要 Protobuf?

  3. Proto:为什么要专门定义一个 .proto 文件?

  4. RPC:什么是远程过程调用?

  5. gRPC:它到底解决什么问题?

  6. 将知识与 Go + Kratos 项目串联

    微服务

    • 第一章:理解最底层的问题------两个程序如何通信?
      • [1. 如果两个功能都在同一个 Go 程序里](#1. 如果两个功能都在同一个 Go 程序里)
      • [2. 但是两个服务在不同服务器上呢?](#2. 但是两个服务在不同服务器上呢?)
    • [第二章:什么是序列化?为什么需要 Protobuf?](#第二章:什么是序列化?为什么需要 Protobuf?)
      • [1. 你熟悉的 JSON 就是一种序列化格式](#1. 你熟悉的 JSON 就是一种序列化格式)
      • [2. Protobuf 解决什么问题?](#2. Protobuf 解决什么问题?)
    • [第三章:Proto 又是什么?](#第三章:Proto 又是什么?)
      • [1. `.proto` 是数据结构的定义文件](#1. .proto 是数据结构的定义文件)
      • [2. 为什么一定要有 `= 1`、`= 2`?](#2. 为什么一定要有 = 1、= 2?)
      • [3. Proto 和 Protobuf 的准确关系](#3. Proto 和 Protobuf 的准确关系)
    • [第四章:RPC 是什么?为什么需要它?](#第四章:RPC 是什么?为什么需要它?)
      • [1. RPC 的本质是远程函数调用](#1. RPC 的本质是远程函数调用)
      • [2. 那么 gRPC 又是什么?](#2. 那么 gRPC 又是什么?)
    • [第五章:gRPC、HTTP、Protobuf 到底是什么关系?](#第五章:gRPC、HTTP、Protobuf 到底是什么关系?)
    • [第六章:用一份 `.proto` 把三种概念串起来](#第六章:用一份 .proto 把三种概念串起来)
      • [第一步:开发者先定义 `.proto`](#第一步:开发者先定义 .proto)
      • [第二步:通过生成器生成 Go 文件](#第二步:通过生成器生成 Go 文件)
      • [第三步:Go 服务调用远程商品服务](#第三步:Go 服务调用远程商品服务)
      • [第四步:Kratos 的 HTTP 生成代码又起什么作用?](#第四步:Kratos 的 HTTP 生成代码又起什么作用?)
      • 知识框架

第一章:理解最底层的问题------两个程序如何通信?

假设你正在开发一个商品管理系统。

系统中有两个服务:

  • 订单服务 OrderService:负责用户下单。
  • 商品服务 ProductService:负责管理商品信息和价格。

用户购买一台服务器,订单服务必须先向商品服务查询售价。

1. 如果两个功能都在同一个 Go 程序里

事情非常简单:

go 复制代码
func GetProductPrice(id int64) int64 {
    return 100
}

func main() {
    price := GetProductPrice(1001)
    fmt.Println(price)
}

直接调用函数就行。

因为 GetProductPrice() 和 main() 在同一个程序中,能够按照 Go 的调用规则完成参数传递和结果返回。

2. 但是两个服务在不同服务器上呢?

假设:

txt 复制代码
订单服务:10.0.0.1:8080
商品服务:10.0.0.2:9000

此时订单服务不能直接调用另一个进程中的普通 Go 函数。

因为两者有独立的进程地址空间,甚至运行在不同机器上。

所以需要一种通信机制:

  1. 把请求参数转换成能够传输的数据。
  2. 通过网络发送到商品服务。
  3. 商品服务接收并解析数据。
  4. 执行查询商品的业务逻辑。
  5. 把结果通过网络传回订单服务。

这就是后面所有知识的出发点。

第二章:什么是序列化?为什么需要 Protobuf?

这是理解 Protobuf 的第一道门槛。

假设 Go 中有个商品:

go 复制代码
# 结构体
type Product struct {
    ID    int64
    Name  string
    Price int64
}
# 实例
product := Product{
    ID:    1001,
    Name:  "云服务器",
    Price: 100,
}

现在你想把 product 发送给另一台服务器。

问题来了:

网络发送的是字节数据,不是 Go 语言中的结构体对象本身。

于是需要两个操作:

序列化就是把程序中的数据转换成适合存储或传输的字节表示;反序列化就是反过来。

1. 你熟悉的 JSON 就是一种序列化格式

例如:

json 复制代码
{
  "id": 1001,
  "name": "云服务器",
  "price": 100
}

它有一个显著特点:人能够直接阅读。

Go 使用:

go 复制代码
data, err := json.Marshal(product)

就能把对象编码成 JSON 字节。

接收方使用 json.Unmarshal() 还原到 Go 结构体。

那么问题来了:既然 JSON 已经能够完成通信,为什么还要发明 Protobuf?

2. Protobuf 解决什么问题?

Protobuf 的全称是 Protocol Buffers,是 Google 推出的结构化数据序列化技术。

它特别适合服务之间高频率传输结构化数据。

和 JSON 相比:

对比项 JSON Protobuf(二进制格式)
数据表示 可读文本 紧凑二进制
数据体积 通常较大 通常较小
编码解码 通常较简单 通常更高效
是否方便人类阅读 是 否
数据结构约定 可用 Schema,但非必需 依赖消息 Schema
跨语言支持 是 是

这里的"小"和"快"是通常情况下的优势,不意味着任何数据和程序中 Protobuf 都一定更快。

举个例子:

JSON 可能会反复传输 "price" 这个字段名称。

而 Protobuf 的二进制编码可以通过字段编号来标识该字段,避免每次传输完整名称。

所以你可以先这样记:

Protobuf 主要负责高效地表达、编码、解码结构化消息。它本身不负责把数据发送到另一台机器。

它不是一个网络通信框架。

第三章:Proto 又是什么?

现在遇到一个新问题。

假设我们收到这样的 Protobuf 二进制数据:

txt 复制代码
08 64

你怎么知道这里面的 100 是商品价格,还是商品数量,或者用户年龄?

仅凭这两个字节,你无法得知它的业务含义。

因此,发送方与接收方必须提前约定数据结构。

这就是 .proto 文件的用途。

1. .proto 是数据结构的定义文件

我们写:

proto 复制代码
syntax = "proto3";

message Product {
    int64 id = 1;
    string name = 2;
    int64 price = 3;
}

逐行理解:

语法 含义
syntax = "proto3" 使用 proto3 语法
message Product 定义名为 Product 的消息类型
int64 id = 1 id 是整数类型,字段编号为 1
string name = 2 name 是字符串类型,字段编号为 2
int64 price = 3 price 是整数类型,字段编号为 3

这很像 Go 中的:

复制代码
type Product struct {
    ID    int64
    Name  string
    Price int64
}

但一个重要区别是:

Go 结构体主要给 Go 程序使用;.proto 定义的消息可以被 Go、Java、Python、C++ 等不同语言共同理解。

例如,一个 Go 订单服务可以和 Java 商品服务使用相同的 Protobuf 消息定义。

2. 为什么一定要有 = 1、= 2?

这是初学者最容易忽略的一点。

go 复制代码
int64 id = 1;

这里的 1:

  • 不是赋值。
  • 不是默认值。
  • 不是字段位置的随意标记。
  • 它是 Protobuf 编码中识别该字段的重要编号。

所以它一旦投入使用,通常就不能随意更改。删除字段时也应保留其编号,防止未来误用。

3. Proto 和 Protobuf 的准确关系

这里有一个术语上的细节。

严格来讲,Proto 通常是开发人员对 .proto 文件或其定义语法的简称;Protobuf 是整个技术体系的名称,包含定义语言、代码生成、运行时和序列化格式。

因此,两者不是完全平级、互不相关的两项技术。

你可以把它们理解为:

  • .proto:设计好数据结构与服务接口的源文件。
  • Protobuf 编译器和运行时:根据定义生成代码,以及编码、解码消息的工具。
  • Protobuf 二进制格式:消息实际传输或存储的一种形式。

这三者共同组成你日常所说的 Protobuf 技术体系。

第四章:RPC 是什么?为什么需要它?

现在我们已经知道:

  • .proto 可以定义数据结构。
  • Protobuf 可以把结构化消息转换为二进制数据。
  • 网络可以把字节传给另一台服务器。

但还有一个问题:

订单服务怎样让商品服务执行指定的函数?

这就需要理解 RPC。

1. RPC 的本质是远程函数调用

RPC 全称:Remote Procedure Call,中文叫远程过程调用。

简单来说:

RPC 让一个程序可以像调用本地函数一样,调用另一个进程或另一台服务器提供的方法。

例如,订单服务想调用商品服务:

go 复制代码
price := productService.GetPrice(1001)

表面上看只是普通函数调用。

实际上可能发生了以下过程:

需要特别注意:RPC 并没有把远程函数真的变成本地函数。它只是隐藏了一部分通信细节。

所以 RPC 仍然可能遇到网络超时、服务不可用、连接断开等问题。

2. 那么 gRPC 又是什么?

RPC 是一种调用方式或设计思想。

gRPC 则是实现 RPC 的一个具体框架。

你可以把关系记成:

复制代码
RPC(远程调用的思想)
  │
  ├── gRPC
  ├── Apache Thrift
  └── 其他 RPC 框架

切记! 因为是要远程调用,所以需要生成两份。如:

gRPC 通常根据同一份 proto,生成客户端调用代码,以及服务端接口、注册和分发代码。双方使用各自需要的部分,服务端业务逻辑仍由开发者实现。

gRPC 主要帮你处理:

  • 客户端调用远程服务的方法。
  • 发送请求和接收响应。
  • 对接序列化与反序列化机制。
  • 服务端请求分发。
  • 超时、取消、状态码和流式通信等机制。

gRPC 默认使用 Protobuf 定义服务接口和消息,但并不是只能使用 Protobuf。

第五章:gRPC、HTTP、Protobuf 到底是什么关系?

这里我必须纠正一个特别容易形成的误区:

gRPC 和 HTTP 不是完全对立的两种底层协议。

gRPC 通常就是运行在 HTTP/2 之上的。

你可以从下面的通信协议分层理解:

因此,最容易理解的区别是:

普通 REST 风格接口通常强调"访问资源":

复制代码
GET /v1/products/1001

gRPC 则强调"调用服务方法":

复制代码
ProductService.GetProduct(
    id = "1001"
)

但两者都涉及网络通信。

gRPC 的能力也不限于普通的一次请求、一次响应。它支持服务端流、客户端流、双向流,一共四种 RPC 调用形式。

你目前开发 Kratos 业务时,先掌握最常见的 **一元 RPC(Unary RPC)**就足够了,即一个请求对应一个响应。

第六章:用一份 .proto 把三种概念串起来

到这里,我们终于可以完整理解 Kratos 项目中的文件了。

假设现在需要提供查询商品接口。

第一步:开发者先定义 .proto

复制代码
syntax = "proto3";

package product.v1;

option go_package = "example.com/shop/api/product/v1;v1";

service ProductService {
    rpc GetProduct(GetProductRequest)
        returns (GetProductReply);
}

message GetProductRequest {
    int64 id = 1;
}

message GetProductReply {
    int64 id = 1;
    string name = 2;
    int64 price = 3;
}

这里需要区分两个概念:

message 是数据结构,service 是服务接口。

例如:

复制代码
message GetProductRequest {
    int64 id = 1;
}

定义的是请求参数的数据结构。

而:

复制代码
service ProductService {
    rpc GetProduct(GetProductRequest)
        returns (GetProductReply);
}

定义的是一个远程方法,接受 GetProductRequest,返回 GetProductReply。

第二步:通过生成器生成 Go 文件

例如:

复制代码
protoc --go_out=. --go_opt=paths=source_relative \
  --go-grpc_out=. --go-grpc_opt=paths=source_relative \
  product.proto

这是原生生成 Go 消息与 gRPC 代码的示意命令,假设相关插件已安装、输入文件位于当前目录。

它会生成:

复制代码
product.pb.go
product_grpc.pb.go

你可以理解为:

  • protoc-gen-go:根据消息定义生成 Go 类型和 Protobuf 支持代码。
  • protoc-gen-go-grpc:根据服务定义生成 gRPC 客户端和服务端接口。

而 Kratos 再提供 HTTP 生成插件,让同样的服务接口定义可以建立 HTTP 映射。

第三步:Go 服务调用远程商品服务

订单服务中:

复制代码
client := v1.NewProductServiceClient(conn)

reply, err := client.GetProduct(
    ctx,
    &v1.GetProductRequest{
        Id: 1001,
    },
)
if err != nil {
    return err
}

fmt.Println(reply.Name)

这里 conn 是已经建立好的 gRPC 客户端连接。

只看 client.GetProduct() 这一行,你会觉得是普通方法调用。

但底层实际是:

复制代码
client.GetProduct()
        |
        v
gRPC 客户端生成代码
        |
        v
Protobuf 编码请求
        |
        v
通过 HTTP/2 传输
        |
        v
商品服务的 gRPC Server
        |
        v
解码请求 + 分发到业务方法
        |
        v
ProductService.GetProduct()
        |
        v
返回响应

所以现在再回头看:

product.pb.go 是数据结构与 Protobuf 支持。

product_grpc.pb.go 是 gRPC 客户端、服务接口与服务端绑定。

至于数据库查询、业务校验、事务等逻辑,是开发者在业务实现中完成的。

第四步:Kratos 的 HTTP 生成代码又起什么作用?

如果你希望前端也能通过 HTTP 请求访问商品,就可以在 RPC 上添加:

复制代码
import "google/api/annotations.proto";

service ProductService {
    rpc GetProduct(GetProductRequest)
        returns (GetProductReply) {

        option (google.api.http) = {
            get: "/v1/products/{id}"
        };
    }
}

然后使用 Kratos HTTP 生成插件生成 product_http.pb.go。

最终两种请求:

而且 Kratos 默认 HTTP 场景主要使用 JSON,gRPC 场景一般使用 Protobuf 二进制。两者可以共享同一套 Protobuf 消息类型和业务实现。

知识框架

概念 必须记住的本质
网络通信 不同进程通过网络交换字节
序列化 把程序数据转换为可传输的字节
Proto 用统一语言描述消息结构和服务接口
Protobuf 消息定义、代码生成、序列化技术体系
RPC 让程序调用远程服务的方法
gRPC 实现远程调用的框架,通常使用 Protobuf
Kratos 为 Go 微服务提供 HTTP、gRPC 等基础设施的框架

其实亲自用 Go 写一个最简单的 gRPC 客户端和服务端,最直观。

可以先不用 Kratos 框架,只用原生 gRPC。

可以更直观的看到 .proto 怎么变成 .pb.go、客户端怎么调用服务端、Protobuf 在哪个环节编码和解码。

相关推荐
阿文和她的Key1 小时前
DeepInfer 与 TorchTPU 同日开源:供应商锁定的成本结构正在被重写
网络·人工智能·架构·开源
AI Workforce1 小时前
数据质量:4 层去重 + 黑名单机制——宁可漏杀,不可错杀
大数据·人工智能·ai·架构·prompt
孔令飞1 小时前
OneX Harness AI Coding 工具介绍
云原生·go·ai编程
flytalei1 小时前
GitOps 实战:基于 Argo CD 的 Kubernetes 应用版本回滚
云原生·容器·kubernetes
Z21-X1 小时前
云原生运维学习笔记:从云计算概念到服务器硬件的入门之旅
运维·笔记·学习·云原生
KIDULT°2 小时前
【K8s 实战】Volume 存储、PV/PVC、ConfigMap 与 Secret 完整实验
云原生·容器·kubernetes
逐流人2 小时前
ELK企业级日志分析系统:从零搭建集中化日志平台
运维·elk·云原生·云计算·日志分析
zzu123zsw3 小时前
业务拆分后的治理深水区:解构企业级微服务六大核心设计模式与高可用落真实践
微服务·设计模式·架构·高可用·微服务架构设计
Z21-X3 小时前
云原生运维学习笔记:存储、网络与操作系统——云计算的底层基石
运维·笔记·学习·云原生