trpc.group/trpc-go/trpc-go@v1.0.3/docs/user_guide/server/overview.zh_CN.md (about)

     1  [English](overview.md) | 中文
     2  
     3  tRPC-Go 服务端开发向导
     4  
     5  # 前言
     6  
     7  本文梳理了开发一个服务端程序需要考虑的问题,如:
     8  
     9  - 服务需要使用什么协议?
    10  - 如何定义服务?
    11  - 如何选择插件?
    12  - 如何测试?
    13  
    14  # 服务选型
    15  
    16  ## 内置协议服务
    17  
    18  tRPC 框架内置支持 **tRPC 服务**,**tRPC 流式服务**,**泛 HTTP RPC 服务** 和 **泛 HTTP 标准服务**。“泛 HTTP”特指服务的底层协议采用“http”, “https”, “http2”和“http3”这四种协议。
    19  
    20  - **泛 HTTP 标准服务**和**泛 HTTP RPC 服务** 有什么区别?泛 HTTP RPC 服务是 tRPC 框架自行设计的一套基于泛 HTTP 协议的 rpc 模型,其协议细节都已在框架内部封装,对用户来完全透明。泛 HTTP RPC 服务通过 PB IDL 协议来定义服务,由脚手架自动生成 rpc 桩代码。泛 HTTP 标准服务在使用上跟 golang http 标准库一模一样,由用户自行定义 handle 请求函数,自行注册 http 路由,自行填充 http head 等。标准 http 服务不需要 IDL 协议文件。
    21  - **泛 HTTP RPC 服务**和 **tRPC 服务**有什么区别?泛 HTTP RPC 服务和 tRPC 服务唯一的区别在于协议上的不同,泛 HTTP RPC 服务使用泛 http 协议,tRPC 服务使用 tRPC 私有协议。区别仅仅在框架内部可见,在业务开发使用上几乎没有区别。
    22  - **tRPC 服务**与 **tRPC 流式服务**有什么区别?tRPC 服务单次 RPC 调用需要客户端发起请求,等服务端处理完毕再返回给客户端。而 tRPC 流式服务在建立流连接之后,可支持客户端不断发送数据,服务端不断接收数据,持续进行响应。两种服务在协议格式、IDL 语法上都有所不同。
    23  
    24  ## 定时任务服务
    25  
    26  定时任务服务采用了普通服务模型,提供定时任务能力。比如程序需要定时加载 cache, 定时校验流水等场景。一个定时任务服务只支持一个定时任务,如果有多个定时任务,那么就需要创建多个定时任务服务。定时器任务服务的功能描述,请参考 [tRPC-Go 搭建定时器服务](https://github.com/trpc-ecosystem/go-database/tree/main/timer)。
    27  
    28  定时任务服务并不是 RPC 服务,它不对客户端提供服务调用。定时任务服务和 RPC 服务互不影响,一个应用程序可同时存在多个 RPC 服务和多个定时任务服务。
    29  
    30  ## 消费者服务
    31  
    32  消费者服务的使用场景是:程序作为消费者来消费消息队列中的消息。和定时任务服务一样,采用了普通服务模型,复用框架的服务治理能力,如自动上报监控,模调,调用链等功能。服务并不对客户端提供服务调用。
    33  
    34  目前 tRPC-Go 支持的消息队列包括:[kafka](https://github.com/trpc-ecosystem/go-database/tree/main/kafka) 等。各种消息队列的开发和配置略有区别,请参考各自文档。
    35  
    36  # 定义 Naming Service
    37  
    38  选择服务的协议之后,我们就需要定义 **Naming Service** 了,也就是确定提供服务的地址是什么,在名字系统用来寻址的路由标识是什么。
    39  
    40  Naming Service 负责网络通信和协议解析。一个 naming service 在寻址上最终用来代表一个 `[ip,port,protocol]` 组合。Naming Service 是通过框架配置文件中的“server”部分的“service”配置来定义。
    41  
    42  我们通常使用一个字符串来表示一个 Naming Service。其命名格式取决于服务所在的服务管理平台是如何定义服务模型的,本文以常用做法 `trpc.{app}.{server}.{service}` 四段式为例。
    43  
    44  ```yaml
    45  server:  # 服务端配置
    46    service:  # 业务服务提供的 service,可以有多个
    47      - name: trpc.test.helloworld.Greeter1  # service 的路由名称,这里是一个数组,注意:name 前面的减号
    48        ip: 127.0.0.1  # 服务监听 ip 地址,ip 和 nic 二选一,优先 ip
    49        port: 8000  # 服务监听端口
    50        network: tcp  # 网络监听类型  tcp udp
    51        protocol: trpc  # 应用层协议 trpc http
    52        timeout: 1000  # 请求最长处理时间 单位 毫秒
    53  ```
    54  
    55  在这个示例中,服务的路由标识是“trpc.test.helloworld.Greeter1”,协议类型为“trpc”,地址为“127.0.0.1:8000”。程序在启动时会自动读取这个配置,并生成 Naming Service。如果服务端选择了“服务注册”插件,则应用程序会自动注册 Naming Service 的“name”和“ipport”等信息到名字服务,这样客户端就可以使用这个名字进行寻址了。
    56  
    57  # 定义 Proto Service
    58  
    59  Proto Service 是一组接口的逻辑组合,它需要定义 package,proto service,rpc name 以及接口请求和响应的数据类型。同时还需要把 Proto Service 和 Naming Service 进行组合,完成服务的组装。对于服务的组装,虽然“IDL 协议类型”和“非 IDL 协议类型”提供给开发者的注册接口略有区别,但框架内部对两者实现是一致的。
    60  
    61  ## IDL 协议类型
    62  
    63  IDL 语言可以通过一种中立的方式来描述接口,并使用工具把 IDL 文件转换成指定语言的桩代码,使程序员专注于业务逻辑开发。tRPC 服务,tRPC 流式服务和泛 HTTP RPC 服务都是 IDL 协议类型服务。对于 IDL 协议类型的服务,Proto Service 的定义通常分为以下三步:
    64  
    65  **以下示例均以 tRPC 服务为例**
    66  
    67  第一步:采用了 IDL 语言描述 RPC 接口规范,生成 IDL 文件。以 tRPC 服务为例,其 IDL 文件的定义如下:
    68  
    69  ```protobuf
    70  syntax = "proto3";
    71  
    72  package trpc.test.helloworld;
    73  option go_package="github.com/some-repo/examples/helloworld";
    74  
    75  service Greeter {
    76      rpc SayHello (HelloRequest) returns (HelloReply) {}
    77  }
    78  
    79  message HelloRequest {
    80      string msg = 1;
    81  }
    82  
    83  message HelloReply {
    84      string msg = 1;
    85  }
    86  ```
    87  
    88  第二步:通过 [trpc-cmdline](https://github.com/trpc-group/trpc-cmdline) 工具可以生成对应服务端和客户端的桩代码
    89  
    90  ```shell
    91  trpc create -p helloworld.proto
    92  ```
    93  
    94  第三步:就把 Proto Service 注册到 Naming Service 上,完成服务的组装。
    95  
    96  ```go
    97  type greeterServerImpl struct{}
    98  
    99  // 接口处理函数
   100  func (s *greeterServerImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
   101      return &pb.HelloReply{ Msg: "Hello, I am tRPC-Go server." }, nil
   102  }
   103  
   104  func main() {
   105      // 通过读取框架配置中的 server.service 配置项,创建 Naming Service
   106      s := trpc.NewServer()
   107      // 注册 Proto Service 的实现实例到 Naming Service 中
   108      pb.RegisterGreeterService(s, &greeterServerImpl{})
   109      // ...
   110  }
   111  ```
   112  
   113  对于程序只有一个 Proto Service 和 Naming Service 时,可以直接使用 `trpc.NewServer()` 生成的 server 来和 Proto Service 映射。
   114  
   115  ## 非 IDL 协议类型
   116  
   117  对于非 IDL 协议类型,同样需要有 Proto Service 的定义和注册过程。通常框架和插件对各协议有不同程度的封装,开发时需要遵循各自协议的使用文档。以泛 HTTP 标准服务为例,其代码如下:
   118  
   119  ```go
   120  // 接口处理函数
   121  func handle(w http.ResponseWriter, r *http.Request) error {
   122      // 构建 Http Head
   123      w.WriteHeader(403)
   124      // 构建 Http Body
   125      w.Write([]byte("response body"))
   126  
   127      return nil
   128  }
   129  
   130  func main() {
   131      // 通过读取框架配置中的 server.service 配置项,创建 Naming Service
   132      s := trpc.NewServer()
   133  
   134      thttp.HandleFunc("/xxx/xxx", handle)
   135      // 注册 Proto Service 的实现实例到 Naming Service 中
   136      thttp.RegisterNoProtocolService(s)
   137      s.Serve()
   138  }
   139  ```
   140  
   141  ## 多服务注册
   142  
   143  对于程序不是单服务模式时(只有一个 naming service 和一个 proto service),用户需要明确指定 naming service 和 proto service 的映射关系。
   144  
   145  对于多服务的注册,我们以 tRPC 服务为例定义两个 Proto Service:`trpc.test.helloworld.Greeter` 和 `trpc.test.helloworld.Hello`:
   146  
   147  ```protobuf
   148  syntax = "proto3";
   149  package trpc.test.helloworld;
   150  option go_package="github.com/some-repo/examples/helloworld";
   151  service Greeter {
   152      rpc SayHello (HelloRequest) returns (HelloReply) {}
   153  }
   154  
   155  service Hello {
   156      rpc SayHi (HelloRequest) returns (HelloReply) {}
   157  }
   158  
   159  message HelloRequest {
   160      string msg = 1;
   161  }
   162  
   163  message HelloReply {
   164      string msg = 1;
   165  }
   166  ```
   167  
   168  与之对应也需要定义两个 Naming Service:`trpc.test.helloworld.Greeter` 和 `trpc.test.helloworld.Hello`:
   169  
   170  ``` yaml
   171  server:  # 服务端配置
   172    service:  # 业务服务提供的 service,可以有多个
   173      - name: trpc.test.helloworld.Greeter  # service 的路由名称,这里是一个数组,注意:name 前面的减号
   174        ip: 127.0.0.1  # 服务监听 ip 地址,ip 和 nic 二选一,优先 ip
   175        port: 8000  # 服务监听端口
   176        network: tcp  # 网络监听类型  tcp udp
   177        protocol: trpc  # 应用层协议 trpc http
   178        timeout: 1000  # 请求最长处理时间 单位 毫秒
   179      - name: trpc.test.helloworld.Hello  # service 的路由名称,这里是一个数组,注意:name 前面的减号
   180        ip: 127.0.0.1  # 服务监听 ip 地址,ip 和 nic 二选一,优先 ip
   181        port: 8001  # 服务监听端口
   182        network: tcp  # 网络监听类型  tcp udp
   183        protocol: trpc  # 应用层协议 trpc http
   184        timeout: 1000  # 请求最长处理时间 单位 毫秒
   185  ```
   186  
   187  把 Proto Service 注册到 Naming Service,多服务场景需要指定 Naming Service 的名称。
   188  
   189  ```go
   190  func main() {
   191      // 通过读取框架配置中的 server.service 配置项,创建 Naming Service
   192      s := trpc.NewServer()
   193      // 注册 Greeter 服务
   194      pb.RegisterGreeterService(s.Service("trpc.test.helloworld.Greeter"), &greeterServerImpl{})
   195      // 注册 Hello 服务
   196      pb.RegisterHelloService(s.Service("trpc.test.helloworld.Hello"), &helloServerImpl{})
   197      ...
   198  }
   199  ```
   200  
   201  ## 接口管理
   202  
   203  对于框架内置的 tRPC 服务,tRPC 流式服务和泛 HTTP RPC 服务,建议遵守一定的研发规范。
   204  
   205  这三类服务均采用 PB 文件来定义接口。为了方便上下游能更透明的获知接口信息,我们建议使用 **pb 文件与服务分离,与语言无关,通过独立的中心仓库进行统一版本管理** 的思路,通过一个公共平台来管理 PB 文件。
   206  
   207  # 服务开发
   208  
   209  常见服务类型的搭建,请参考如下链接:
   210  
   211  - [搭建 tRPC 流式服务](/stream/README.zh_CN.md)
   212  - [搭建泛 HTTP RPC/标准服务](/http/README.zh_CN.md)
   213  
   214  一些第三方协议插件见:[trpc-ecosystem/go-codec](https://github.com/trpc-ecosystem/go-codec)。
   215  
   216  ## 常用 API
   217  
   218  对于 log,metrics 和 config,框架提供了标准调用接口,服务开发只有使用这些标准接口才能和服务治理系统对接。比如日志,如果不使用标准日志接口,而直接使用“fmt.Printf()”,日志信息是无法上报到远程日志中心的。
   219  
   220  tRPC-Go 服务端配置支持“**通过框架配置文件**”和“**函数调用传参**”两种方式来配置服务。“函数调用传参”的优先级要大于“通过框架配置文件”的设置。建议优先使用框架配置文件来配置服务端,其好处是配置和代码解耦,方便管理。
   221  
   222  ## 错误码
   223  
   224  tRPC-Go 推荐在写服务端业务逻辑时,使用 tRPC-Go 封装的 `errors.New()` 来返回业务错误码,这样框架能自动上报业务错误码到监控系统。如果业务自定义 error 的话,就只能靠业务主动调用 Metrics SDK 来上报错误码。关于错误码的 API 使用,请参考 [这里](/errs)。
   225  
   226  # 框架配置
   227  
   228  对于服务端,必须要配置框架配置中“global”,“server”两部分的配置,配置参数的具体含义,取值范围等信息请参考 [框架配置](/docs/user_guide/framework_conf.zh_CN.md) 文档。“plugins”部分的配置取决于所选的插件,具体参考下面的插件选择章节。
   229  
   230  # 插件选择
   231  
   232  tRPC 框架的核心在于把框架功能插件化,框架核心并不包括具体的实现。对于插件的使用,我们需要同时“**在 main 文件中 import 插件**”和“**在框架配置文件中配置插件**”的方式来引入插件,这里需要强调的是 **插件的选择必须要在开发阶段确定**。如何使用插件请参考 [北极星名字服务](https://github.com/trpc-ecosystem/go-naming-polarismesh) 中的示例。
   233  
   234  tRPC 插件生态提供了丰富的插件,程序如何选择合适的插件呢?这里我们提供了一些思路供大家参考。我们可以把插件可以大致分成三类:独立插件,服务治理插件 和 存储接口插件。
   235  
   236  - 独立插件:比如协议,压缩,序列化,本地内存缓存等插件,其插件的运行不依赖外部系统组件。这类插件的思路比较简单,主要是依据业务功能的需要,和插件的成熟度来做选择。
   237  - 服务治理插件:绝大部分服务治理插件,比如远程日志,名字服务,配置中心等,它们都需要和外部系统对接,对于微服务治理体系有很大的依赖。对这类插件的选择,我们需要明确服务最终运行在什么运营平台上,平台提供了哪些治理组件,服务有哪些能力一定要和平台对接,哪些则不需要。
   238  - 存储接口插件:存储插件主要封装了业界和公司内部成熟数据库,消息队列等组件的接口调用。关于这部分插件,我们首先需要考虑业务的技术选型,什么样的数据库更适合业务的需求。然后基于技术选型来看 tRPC 是否支持,如果不支持,我们可以选择使用数据库原生 SDK,或者建议大家贡献插件到 tRPC 社区。
   239  
   240  ## 内置插件
   241  
   242  框架为服务内置了一些必要的插件,这样可以确保用户在不设置任何插件的情况下,框架仍然能够使用默认插件提供正常的 RPC 调用能力。用户可以自行替换默认插件。
   243  
   244  下面表格列出了作为服务端时框架提供的默认插件,以及插件的默认行为。
   245  
   246  | 插件类型  | 插件名称   | 默认插件 | 插件行为  |
   247  | ---------- | --------- | --------  | ------------------------------------- |
   248  | log      | Console  | 是      | 默认 debug 级别以上日志打 console,级别可通过配置或者 API 可设置   |
   249  | metric   | Noop     | 是      | 不上报 metric 信息     |
   250  | config   | File     | 是      | 支持用户使用接口从指定本地文件获取配置项   |
   251  | registry | Noop     | 是      | 不做服务的注册和注销   |
   252  
   253  # 拦截器
   254  
   255  tRPC-Go 提供了拦截器(filter)机制,拦截器在 RPC 请求和响应的上下文设置埋点,允许业务在埋点处插入自定义处理逻辑。像调用链跟踪和认证鉴权等功能通常是采用拦截器来实现的。常用拦截器请在 [trpc-ecosystem/go-filter](https://github.com/trpc-ecosystem/go-filter) 中查找。
   256  
   257  业务可以自定义拦截器。拦截器通常和插件组合来实现功能的,插件提供配置,而拦截器用于在 RPC 调用上下文插入处理逻辑。关于拦截器的原理,触发时机,执行顺序和自定义拦截器的示例代码,请参考 [trpc-go/filter](/filter)。
   258  
   259  # 测试相关
   260  
   261  tRPC-Go 从设计之初就考虑了框架的易测性,在通过 pb 生成桩代码时,默认会生成 mock 代码。
   262  
   263  # 高级功能
   264  
   265  ## 超时控制
   266  
   267  tRPC-Go 为 RPC 调用提供了 3 种超时机制控制:链路超时,消息超时和调用超时。关于这 3 种超时机制的原理介绍和相关配置,请参考 [tRPC-Go 超时控制](/docs/user_guide/timeout_control.zh_CN.md)。
   268  
   269  此功能需要协议的支持(协议需要携带 timeout 元数据到下游),tRPC 协议,泛 HTTP RPC 协议均支持超时控制功能。
   270  
   271  ## 空闲超时
   272  
   273  服务默认存在一个 60s 的空闲超时时间,以防止过多空闲连接消耗服务侧的资源,这个值可以通过框架配置中的 `idletimeout` 来进行修改:
   274  
   275  ```yaml
   276  server:
   277    service:
   278      - name: trpc.server.service.Method
   279        network: tcp
   280        protocol: trpc
   281        idletime: 60000 # 单位是毫秒, 设置为 -1 的时候表示没有空闲超时(这里设置为 0 时框架仍会自动转为默认的 60s)
   282  ```
   283  
   284  ## 链路透传
   285  
   286  tRPC-Go 框架提供在客户端与服务端之间透传字段,并在整个调用链路透传下去的机制。关于链路透传的机制和使用,请参考 [tRPC-Go 链路透传](/docs/user_guide/metadata_transmission.zh_CN.md)。
   287  
   288  此功能需要协议支持元数据下发功能,tRPC 协议,泛 HTTP RPC 协议均支持链路透传功能。
   289  
   290  ## 反向代理
   291  
   292  tRPC-Go 为类似做反向代理的程序提供了完成透传二进制 body 数据,不进行序列化、反序列化处理的机制,以提升转发效率。关于反向代理的原理和示例程序,请参考 [tRPC-Go 反向代理](/docs/user_guide/reverse_proxy.zh_CN.md)。
   293  
   294  ## 自定义压缩方式
   295  
   296  tRPC-Go 自定义 RPC 消息体的压缩、解压缩方式,业务可以自己定义并注册压缩、解压缩算法。具体示例请参考 [这里](/codec/compress_gzip.go)。
   297  
   298  ## 自定义序列化方式
   299  
   300  tRPC-Go 自定义 RPC 消息体的序列化、反序列化方式,业务可以自己定义并注册序列化、反序列化算法。具体示例请参考 [这里](/codec/serialization_json.go)。
   301  
   302  ## 设置服务最大协程数
   303  
   304  tRPC-Go 支持服务级别的同/异步包处理模式,对于异步模式采用协程池来提升协程使用效率和性能。用户可以通过框架配置和 Option 配置两种方式来设置服务的最大协程数,具体请参考 [tRPC-Go 框架配置](/docs/user_guide/framework_conf.zh_CN.md) 章节的 service 配置。