forked from gorpc101/gorpc101
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6e7a4c8
commit 3db4bb5
Showing
3 changed files
with
216 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,4 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
_book |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,211 @@ | ||
# 模块设计 | ||
|
||
在前面的章节中,我们已经对微服务框架的整体架构进行了深入的探讨和设计。我们了解了微服务框架的基本概念,探讨了其核心价值,以及如何从宏观角度进行架构设计。现在,我们将进入下一个阶段,即具体模块的设计和实现。在接下来的章节中,我们将逐一深入到每个模块,详细解析其设计原理,实现方式,以及如何与其他模块协同工作。我们将从理论到实践,从抽象到具体,一步步揭示微服务框架的内在机制。希望通过这些深入的探讨,能够帮助读者更好地理解和应用微服务框架,构建出高效、稳定、可扩展的系统。 | ||
我们介绍了系统架构从单体架构向微服务架构的演变,微服务架构、service mesh中微服务框架扮演的重要作用,也介绍了一些流行的微服务框架及其创新点,如何结合业务、团队情况去选择合适的微服务框架。最后介绍了在腾讯又是如何通过自研微服务框架来兼容存量、增量服务的。 | ||
|
||
在微服务框架的整体架构设计部分,我们介绍了一个功能完备、具备良好扩展性的微服务框架会面临的一些问题,以及这些问题应该在哪些抽象层次去解决。框架整体也是由不同的模块拼接而成,每个模块解决一个子领域的专属问题,或者多个模块的协作来解决一个更复杂的问题。接下来我们就将对框架的模块设计进行探讨。 | ||
|
||
## 整体设计回顾 | ||
|
||
在进入各个具体模块的设计实现之前,为了避免大家过度陷入某个模块细节而忽视框架全貌,建议读者经常回顾下框架整体架构设计、各模块之间的协作关系,这样更有助于理解模块解决问题的影响面、价值。 | ||
|
||
高可扩展框架整体架构设计: | ||
|
||
![gorpc整体架构](../../.gitbook/assets/gorpc-整体架构设计.png) | ||
|
||
框架核心模块间的协作关系: | ||
|
||
![gorpc概要设计](<../../.gitbook/assets/gorpc-概要设计.png>) | ||
|
||
## 核心模块设计 | ||
|
||
结合前一小节的框架核心模块UML类图,这里先对核心模块进行分类,并对模块下的重要接口定义进行简单介绍,为我们进入各个模块的具体介绍做一点铺垫。 | ||
|
||
### 服务声明方式 | ||
|
||
通过声明式配置来说明一个服务支持的API以及请求、响应参数,是一种逐渐被接受的做法。IDL(Interface Definition Language)被提出,到现在流行的protocolbuffers(下文简称pb)、flatbuffer、thrift等,除了解决高效序列化、反序列化的问题,也可以用来解决接口声明的问题。 | ||
|
||
以pb为例,下面通过特有的语法声明了一个逻辑服务HelloSvr,该逻辑服务包含一个唯一的接口Hello,请求、响应参数分别是HelloReq、HelloRsp。微服务框架开发者可以开发一各工具解析该声明文件,并生成必要的胶水代码将其与框架处理逻辑打通,业务开发人员仅需要在空接口处理函数里进行“填鸭式”编码即可。 | ||
|
||
```protobuf | ||
syntax = "proto3"; | ||
package helloworld; | ||
option go_package="github.com/gorpc101/helloworld;" | ||
message HelloReq{ | ||
string msg = 1; | ||
} | ||
message HelloRsp{ | ||
uint32 err = 1; | ||
string msg = 2; | ||
} | ||
service HelloSvr { | ||
rpc Hello(HelloReq) returns(HelloRsp); | ||
} | ||
``` | ||
|
||
### server端 | ||
|
||
#### 进程 vs. 服务概念 | ||
|
||
提到一个“服务”我们会倾向于将其与一个物理服务进程挂钩,在真实业务场景里面,还需要逻辑服务的概念,比如一个物理服务进程希望对外同时支持TCP、UDP、HTTP服务,并且它们各自对外暴漏的服务还有可能是不同的,举个例子说明: | ||
|
||
```protobuf | ||
service HelloSvr1 { | ||
rpc Hello1(HelloReq) returns(HelloRsp) | ||
rpc Bye(ByeReq) returns(ByeRsp) | ||
} | ||
service HelloSvr2 { | ||
rpc Hello(HelloReq) returns(HelloRsp) | ||
} | ||
``` | ||
|
||
实际上就有业务可能希望同一个服务进程能同时支持两个逻辑服务HelloSvr1、HelloSvr2,并且它们对外暴露的接口列表是有区分的,通过这种方式可以进行更细粒度的管理,而不用额外部署两个分别支持不同接口列表的服务进程。 | ||
|
||
这样的话,我们就需要对Server、Service进行一个更加准确的声明: | ||
|
||
- Server:一个物理服务进程,它可以包含多个逻辑Service; | ||
|
||
```go | ||
type Server struct { | ||
Services []*Service | ||
... | ||
} | ||
``` | ||
|
||
- Service:一个逻辑服务,它描述了该逻辑服务准备暴漏的接口列表、传输层协议、业务协议、序列化、压缩算法等; | ||
|
||
```go | ||
type Service struct { | ||
Name string | ||
Methods []Method | ||
|
||
Transport string | ||
Protocol string | ||
Serialization string | ||
Compression string | ||
} | ||
``` | ||
|
||
#### 网络IO:连接管理 | ||
|
||
像grpc框架目前是基于HTTP协议实现的,由于早起HTTP协议实现存在一些问题,如不支持HTTP/1.x不支持pipeline或者后续版本厂商支持的不好,导致其服务端处理性能比较低下。另外由于HTTP是基于TCP实现的,TCP存在HOL头部阻塞问题,有很多服务设计者会考虑采用UDP协议来进行数据传输。 | ||
|
||
当然我们支持QUIC协议很好,或者类似Unreal Engine在UDP基础上实现可靠UDP通信,但是业务不只是增量,也要维护存量、考虑协议迁移的成本。OK,这里不展开了。 | ||
|
||
服务端需要考虑支持TCP、UDP、QUIC、Unix等多种不同的通信方式,这些是属于七层OS协议里传输层的范畴,在服务端在哪个层次解决好呢? | ||
|
||
```go | ||
type TransportService interface { | ||
ListenAndServe(network, address string) error | ||
} | ||
``` | ||
|
||
注意transport是传输的含义,在网络通信这个领域,这也算是一个计算机专业术语,比如grpc里面servertransport实际上是tcp connection层次的概念,还是在强调专业术语“通信传输”,而不是服务端传输层handler“listen and server”层次的概念。 | ||
|
||
> ps:trpc中使用了ServerTransport,但是我更倾向于将transport放在连接层面的传输这一层次,而不是仅因为它字面上带有“传输”的意思,就将其用作传输层的handler。或者说仅因为clienttransport、servertransport这样可以保持接口声明对称,就这样使用。transport指的就是“传输”,它关注的是连接层面的数据读写,而非更高维的连接管理。我认为transport甚至也不应该区分为clientside、serverside,除非真的想不出更贴切的名字了。 | ||
每改变一个专业术语的含义,都相当于在“教育”别人改变已有的共识,所以这里我使用TransportService来强调这不是一个连接数据读写的层次。每个逻辑服务Service可以引用一个TransportService来负责完成连接管理。 | ||
|
||
#### 网络IO:连接读写 | ||
|
||
在TransportService建立完连接net.Conn后,我们将通过Endpoint来表示通信双方的一端,我们将在Endpoint层次完成连接收发包buffer、codec、serializer、compressor等的创建、复用、整合操作。 | ||
|
||
```go | ||
type Endpoint struct { | ||
net.Conn | ||
|
||
codec codec.Codec | ||
serializer codec.Serializer | ||
compressor codec.Compressor | ||
|
||
buf []byte | ||
} | ||
``` | ||
|
||
#### 编解码:业务协议 | ||
|
||
```go | ||
type Codec interface { | ||
Encode([]byte) ([]byte, error) | ||
Decode([]byte) ([]byte, error) | ||
} | ||
``` | ||
|
||
#### 编解码:序列化 | ||
|
||
```go | ||
type Serializer interface { | ||
Marshal(any) ([]byte, error) | ||
Unmarshal([]byte, any) error | ||
} | ||
``` | ||
|
||
#### 编解码:压缩算法 | ||
|
||
```go | ||
type Compressor interface { | ||
Compress([]byte) ([]byte, error) | ||
Decompress([]byte) ([]byte, error) | ||
} | ||
``` | ||
|
||
#### 请求处理 | ||
|
||
当网络IO完成了连接管理、连接数据读取、拆包、反序列化、解压缩,一个完整的客户端请求就拿到了,此时服务器就需要改请求进行处理。一个进程包含多个逻辑服务,每个逻辑服务包含多个RPC接口,那客户端必须告知服务器自己请求的是哪个逻辑服务的哪个接口。 | ||
|
||
我为框架默认协议起了个很好听的名字“whisper”: | ||
|
||
```protobuf | ||
syntax = "proto2"; | ||
package whisper; | ||
// FrameHeader is the frame header of Request and Response. | ||
// | ||
// The encoding of FrameHeader is as follows: | ||
// | ||
// |-----|------------|-----------------------|-----| | ||
// |-----|------------|-----------------------|-----| | ||
// \ STX \ PKG Length \ Request/Response Body \ ETX \ | ||
// 0 1 5 ~ ~+1 | ||
// | ||
// STX: 1B (0x38) | ||
// PKG Length: 4B (length of body) | ||
// Req/Rsp Body: ... | ||
// ETX: 1B (0x49) | ||
// Request definition | ||
// | ||
// In Request, tracing `traceContext` is stored in map `meta`. | ||
message Request { | ||
optional uint64 seqno = 1; // 包序号 | ||
optional string appid = 2; // 业务分配ID | ||
optional string rpcname = 3; // rpc名称 | ||
optional string userid = 4; // 用户ID | ||
optional string userkey = 5; // 用户key,鉴权用 | ||
optional uint32 version = 6; // 协议版本 | ||
optional bytes body = 7; // 业务包体 | ||
map<string, string> meta = 8; // 元信息 | ||
} | ||
// Response definition | ||
// | ||
// `err_code` and `err_msg` should indicate errors in framework, | ||
// rather than business logic error or error description. | ||
message Response { | ||
optional uint64 seqno = 1; // 包序号 | ||
optional uint32 err_code = 2; // 错误码 | ||
optional string err_msg = 3; // 错误描述信息 | ||
optional bytes body = 4; // 业务包体 | ||
} | ||
``` | ||
|
||
> ps:该协议定义支持unary mode RPC,不支持streaming mode RPC,对于流式RPC,实际上在我工作这些年中用到的场景比较少,所以我并不打算在该电子书第一版草稿完成前就予以支持 … 如果有时间后考虑支持。 | ||
### client端 | ||
|
||
|
||
|
||
### 工具集 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash -e | ||
|
||
docker run --name gorpc --rm \ | ||
-p 4000:4000 -p 35729:35729 \ | ||
-v $(pwd -P):/root/gitbook hitzhangjie/gitbook-cli:latest \ | ||
gitbook serve |