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

     1  [English](overview.md) | 中文
     2  
     3  tRPC-Go 客户端开发向导
     4  
     5  # 前言
     6  
     7  tRPC-Go 框架和插件为服务提供了接口调用,用户可以像调用本地函数一样来调用下游服务,而不用关心底层实现细节。本文首先通过对服务调用的整个处理流程的梳理,来介绍框架为服务调用能提供哪些能力,用户可以采用哪些手段来控制服务调用各个环节的行为。接下来,本文会从服务调用,配置,寻址,拦截器,协议选择等关键环节来阐述如何开发和配置一个客户端调用。文章会就服务调用的典型场景为用户提供开发指导,尤其是程序既作为服务端又作为客户端的场景。
     8  
     9  # 框架能力
    10  
    11  本节首先会介绍框架支持的服务调用类型,然后通过对服务调用的整个处理流程的梳理,来了解框架为服务调用提供了哪些能力,哪些关键环节的行为是可以定制。从而为后面客户端的开发提供知识基础。
    12  
    13  ## 调用类型
    14  
    15  tRPC-Go 框架提供了多种类型的服务调用,我们把服务调用按协议大致分为:内置协议调用,第三方协议调用,存储协议调用,消息队列生产者调用 这 4 类。这些协议的调用接口各不相同,比如 tRPC 协议提供的是 PB 文件定义好的服务接口,而 mysql 则提供的接口是“query(),exec(),transaction()”。用户在开发客户端时,需要查询各自协议文档来获取接口信息,可以参考以下开发指导文档:
    16  
    17  **内置协议**:
    18  
    19  tRPC-Go 提供了以下内置协议的服务调用:
    20  
    21  - [调用 tRPC 服务](/docs/quick_start.zh_CN.md)
    22  
    23  **第三方协议**:
    24  
    25  tRPC-Go 提供了丰富的协议插件,供客户端实现和第三方协议服务进行对接。同时框架也支持用户自定义协议插件。关于协议插件的开发,请参考 [这里](/docs/developer_guide/develop_plugins/protocol.zh_CN.md),常见的第三方协议可以参考 [trpc-ecosystem/go-codec](https://github.com/trpc-ecosystem/go-codec)
    26  
    27  **存储协议**:
    28  
    29  tRPC-Go 对常见数据库的访问做了封装,通过以服务访问的方式来进行数据库操作,具体可以参考 [tRPC-Go 调用存储服务](/docs/developer_guide/develop_plugins/storage.zh_CN.md)。
    30  
    31  **消息队列**:
    32  
    33  tRPC-Go 提供了对常见消息队列的生产者操作做了封装,通过以服务访问的方式来生产消息。
    34  
    35  - [kafka](https://github.com/trpc-ecosystem/go-database/tree/main/kafka)
    36  
    37  虽然各个协议的调用接口各不相同,但是框架采用了统一服务调用流程,让所有的服务调用都能复用相同的服务治理能力,包括拦截器,服务寻址,监控上报等能力。
    38  
    39  ## 调用流程
    40  
    41  接下来让我们来看看一次完整的服务调用流程是怎么样的。下面这张图展示了客户端从发生服务调用请求到收到服务响应的全过程,图中第一行从左往右代表服务请求的流程。第二行从右往左的方向,代表客户端处理服务响应报文的流程。
    42  
    43  ![call_flow](/.resources/user_guide/client/overview/call_flow_zh_CN.png)
    44  
    45  框架为每个服务都提供了一个服务调用代理 (又称为 "ClientProxy"), 它封装了服务调用的接口函数(“桩函数”),包括接口的入参,出参和错误返回码。从用户使用上来讲,桩函数的调用和本地函数的调用是一样的。
    46  
    47  正如 tRPC 框架概述所描述的,框架采用了基于接口编程的思想,框架只提供了标准接口,由插件来实现具体功能。从流程图可以看到,服务调用的核心流程包括拦截器的执行,服务寻址,协议处理和网络连接这四部分,而每个部分都是通过插件来实现的,用户需要选择和配置插件,来完成整个调用流程的串联。
    48  
    49  用户可以通过框架配置文件和 Option 函数选项两种方式来选择和配置插件,同时框架也支持用户自行开发插件来实现服务调用行为的定制。拦截器是最为典型的使用场景,例如自定义拦截器实现服务调用的认证和授权,调用质量的监控和上报等。
    50  
    51  服务寻址是服务调用流程中非常重要的一个环节,寻址插件(selector)在服务大规模使用场景,提供服务实例的策略路由选择,负载均衡和熔断处理能力,是客户端开发中需要特别关注的部分。
    52  
    53  ## 治理能力
    54  
    55  tRPC-Go 除了为各种协议提供了接口调用外,还为服务的调用提供了丰富的服务治理能力,实现与服务治理组件的对接,开发人员只需要关注业务自身逻辑即可。框架通过插件可以实现以下服务治理能力:
    56  
    57  - 服务寻址
    58  - [调用超时控制](/docs/user_guide/timeout_control.zh_CN.md)
    59  - [拦截器机制](/docs/developer_guide/develop_plugins/interceptor_zh-CN.md),实现包括,调用链跟踪,监控上报,[重试对冲](https://github.com/trpc-ecosystem/go-filter/tree/main/slime)....
    60  - [远程日志](/log/README.zh_CN.md)
    61  - [配置中心](/config/README.zh_CN.md)
    62  - ......
    63  
    64  # 客户端开发
    65  
    66  本节主要以代码开发的角度,阐述业务如何初始化客户端,如何调用服务接口,以及如何通过参数配置来控制服务调用的行为。
    67  
    68  ## 开发模式
    69  
    70  客户端开发主要分成以下两种模式:
    71  
    72  - 模式一:程序既作为服务端也作为客户端。tRPC-Go 服务调用下游的客户端请求为最常见的场景
    73  - 模式二:非服务的纯客户端小工具请求,常见于开发运维小工具的场景
    74  
    75  ### 服务内调用 client
    76  
    77  对于模式一,在创建启动服务的时候会读取框架配置文件,所有配置插件的初始化都会在 trpc.NewServer() 里自动完成。代码示例为:
    78  
    79  ```go
    80  import (
    81      "trpc.group/trpc-go/trpc-go/errs"
    82      // 被调服务的协议生成文件 pb.go 的 git 地址,协议接口管理看这里:todo
    83      pb "github.com/trpcprotocol/app/server"
    84  )
    85  
    86  // SayHello 是 server 请求入口函数,一般的客户端调用都是在一个服务内部再调用下游服务。
    87  // SayHello 携带了 ctx 信息,在该函数内部继续调用下游服务时需要一路透传 ctx。
    88  func (s *greeterServerImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
    89      // 创建一个客户端调用代理,该操作很轻量不会创建连接,可以每次请求创建,也可以全局初始化一个 proxy,建议放在 service impl struct 里面,方便 mock 测试,详细 demo 见框架源码 examples/helloworld
    90      proxy := pb.NewGreeterClientProxy()
    91      // 正常情况 都不要自己在代码里面指定任何 option 参数,全部使用配置,更灵活,指定 option 的话,以 option 为最高优先级
    92      reply, err := proxy.SayHi(ctx, req)
    93      if err != nil {
    94          log.ErrorContextf(ctx, "say hi fail:%v", err)
    95          return nil, errs.New(10000, "xxxxx") 
    96      }
    97      return &pb.HelloReply{Xxx: reply.Xxx} nil
    98  }
    99  
   100  func main(){
   101      // 创建一个服务对象,底层会自动读取服务配置及初始化插件,必须放在 main 函数首行,业务初始化逻辑必须放在 NewServer 后面
   102      s := trpc.NewServer()
   103      // 注册当前实现到服务对象中
   104      pb.RegisterService(s, &greeterServerImpl{})
   105      // 启动服务,并阻塞在这里
   106      if err := s.Serve(); err != nil {
   107          panic(err)
   108      }
   109  }
   110  ```
   111  
   112  ### 纯客户端小工具
   113  
   114  对于模式二,客户端小工具没有配置文件,需要自己设置 option 发起后端调用,而且也没有 ctx,必须使用 trpc.BackgroundContext(),因为没有配置文件初始化插件,所以一些寻址方式需要自己手动注册,如北极星。代码样例如下:
   115  
   116  ```go
   117  import (
   118      "trpc.group/trpc-go/trpc-go/client"
   119      pb "github.com/trpcprotocol/app/server"
   120      pselector "trpc.group/trpc-go/trpc-naming-polarismesh/selector" // 需要自己引入需要的名字服务插件代码
   121      trpc "trpc.group/trpc-go/trpc-go"
   122  )
   123  
   124  // 一般小工具都是从 main 函数写起
   125  func main {
   126      // 由于没有配置文件帮忙初始化插件,所以需要自己手动初始化北极星
   127      pselector.RegisterDefault()
   128      // 创建一个客户端调用代理
   129      proxy := pb.NewGreeterClientProxy() 
   130      // 必须自己通过 trpc.BackgroundContext() 创建 ctx,通过代码传入 option 参数
   131      rsp, err := proxy.SayHi(trpc.BackgroundContext(), req, client.WithTarget("ip://ip:port")) 
   132      if err != nil {
   133          log.Errorf("say hi fail:%v", err)
   134          return
   135      }
   136      return
   137  }
   138  ```
   139  
   140  其实大部分的小工具都可以使用定时器模式执行,所以尽量使用 timer 来实现,以模式一的方式来执行工具,所有的服务端功能就能自动配备齐全。
   141  
   142  ## 接口调用
   143  
   144  在客户端,框架为每个服务都定义了一个“ClientProxy”,“ClientProxy”会提供服务调用的桩函数,用户只需要像调用普通函数一样调用桩函数即可。proxy 是一个很轻量级的结构,内部不会创建链接等资源。proxy 的调用是并发安全的,用户可以为每个服务全局初始化一个 proxy,也可以为每次服务调用都生产一个 proxy。
   145  
   146  对应不同的协议,它们所提供的服务接口都是不一样的,用户在具体的开发过程中需要参考各自协议的客户端开发文档来做(参考 服务类型 章节)。虽然接口的定义各部相同,但是它们都有共性的部分:“ClientProxy”和“Option 函数选项”。我们基于协议类型和桩代码的生产方式,把它们分成两类:“IDL 类型服务调用”和“非 IDL 类型服务调用”
   147  
   148  **1. IDL 类型服务调用**
   149  
   150  对于 IDL 类型的服务(比如 tRPC 服务和泛 HTTP RPC 服务),通常使用工具来生成客户端桩函数,生成的代码包括“ClientProxy 创建函数”和“接口调用函数”,函数定义大致如下:
   151  
   152  ```go
   153  // ClientProxy 的初始化函数
   154  var NewHelloClientProxy = func(opts ...client.Option) HelloClientProxy{...}
   155  
   156  // 服务接口定义
   157  type HelloClientProxy interface {
   158      SayHello(ctx context.Context, req *HelloRequest, opts ...client.Option) (rsp *HelloReply, err error)
   159  }
   160  ```
   161  
   162  桩代码为用户提供了 ClientProxy 的创建函数,服务接口函数,以及对应的参数定义。用户使用这两套函数就可以调用下游服务了。接口的调用是采用同步调用的方式完成的。option 参数可以配置服务调用的行为,具体在后面章节介绍。一次完整的服务调用示例如下:
   163  
   164  ```go
   165  import (
   166      "context"
   167  
   168      "trpc.group/trpc-go/trpc-go/client"
   169      "trpc.group/trpc-go/trpc-go/log"
   170      pb "github.com/trpcprotocol/test/helloworld"
   171  )
   172  
   173  func main() {
   174      // 创建 ClientProxy
   175      proxy := pb.NewGreeterClientProxy()
   176      // 填充请求参数
   177      req :=  &pb.HelloRequest{Msg: "Hello, I am tRPC-Go client."}
   178      // 调用服务请求接口
   179      rsp, err := proxy.SayHello(context.Background(), req, client.WithTarget("ip://127.0.0.1:8000"))
   180      if err != nil {
   181          return
   182      }
   183      // 获取请求响应数据
   184      log.Debugf("response: %v", rsp)
   185  }
   186  ```
   187  
   188  **2. 非 IDL 类型服务调用**
   189  
   190  对于非 IDL 类型的服务,同样是使用“ClientProxy”来封装服务调用接口的。ClientProxy 创建函数和接口调用函数通常是由协议插件来提供的,不同插件对函数的封装略有不同,开发时需要遵循各自协议的使用文档。以泛 HTTP 标准服务为例,接口定义如下:
   191  
   192  ```go
   193  // NewClientProxy 新建一个 ClientProrxy, 必传参数 http 服务名
   194  var NewClientProxy = func(name string, opts ...client.Option) Client
   195  // 泛 HTTP 标准服务,提供 get,put,delete,post 四个通用接口
   196  type Client interface {
   197      Get(ctx context.Context, path string, rspbody interface{}, opts ...client.Option) error
   198      Post(ctx context.Context, path string, reqbody interface{}, rspbody interface{}, opts ...client.Option) error
   199      Put(ctx context.Context, path string, reqbody interface{}, rspbody interface{}, opts ...client.Option) error
   200      Delete(ctx context.Context, path string, reqbody interface{}, rspbody interface{}, opts ...client.Option) error
   201  }
   202  ```
   203  
   204  一次完整的服务调用示例如下:
   205  
   206  ```go
   207  import (
   208      "trpc.group/trpc-go/trpc-go/client"
   209      "trpc.group/trpc-go/trpc-go/codec"
   210      "trpc.group/trpc-go/trpc-go/http"
   211  )
   212  
   213  func main() {
   214      // 创建 ClientProxy
   215      httpCli := http.NewClientProxy("trpc.http.inews_importable",
   216          client.WithSerializationType(codec.SerializationTypeForm))
   217      // 填充请求参数
   218      req := url.Values{} // 设置表单参数
   219      req.Add("certify", "1")
   220      req.Add("clientIP", ip)
   221      // 调用服务请求接口
   222      rsp: = &A{}
   223      if err := httpCli.Post(ctx, "/i/getUserUid", req, rsp); err != nil {
   224          return
   225      }
   226      // 获取请求响应数据
   227      log.Debugf("response: %v", rsp)
   228  }
   229  ```
   230  
   231  ## Option
   232  
   233  tRPC-Go 框架提供两级 Option 函数选项来设置 Client 参数,它们分别为“ClientProxy 级配置”和“接口调用级配置”。Option 实现使用的是函数选项设计模式 (Functional Options Pattern)。Option 配置通常用于作为纯客户端的工具上。
   234  
   235  ```go
   236  // ClientProxy 级 Option 设置,配置对每次使用 clientProxy 调用服务时都生效
   237  clientProxy := NewXxxClientProxy(option1, option2...)
   238  // 接口调用级 Option 设置,配置仅对此次服务调用生效
   239  rsp, err := proxy.SayHello(ctx, req, option1, option2...)
   240  ```
   241  
   242  对于程序既是服务端也是客户端的场景,系统推荐使用框架配置文件的方式来配置 Client,这样可以实现配置与程序的解耦,方便配置管理。对于 Option 和配置文件组合使用的场景,配置设置的优先级为:`接口调用级 Option` > `ClientProxy 级 Option` > `框架配置文件`。
   243  
   244  框架提供了丰富的 Option 参数,本文重点介绍在开发中经常使用的一些配置。
   245  
   246  **1. 我们可以通过以下参数来设置服务的协议,序列化类型,压缩方式和服务地址**
   247  
   248  ```go
   249  proxy.SayHello(ctx, req,
   250      client.WithProtocol("http"),
   251      client.WithSerializationType(codec.SerializationTypeJSON),
   252      client.WithCompressType(codec.CompressTypeGzip),
   253      client.WithTLS(certFile, keyFile, caFile, serverName),
   254      client.WithTarget("ip://127.0.0.1:8000"))
   255  ```
   256  
   257  **2. 我们可以通过以下参数获取下游被调服务的调用地址**
   258  
   259  ```go
   260  node := &registry.Node{}
   261  proxy.SayHello(ctx, req, client.WithSelectorNode(node))
   262  ```
   263  
   264  **3. 我们可以通过以下参数设置透传信息**
   265  
   266  ```go
   267  proxy.SayHello(ctx, req, client.WithMetaData(version.Header, []byte(version.Version)))
   268  ```
   269  
   270  **4. 我们可以通过以下参数获取下游服务返回的透传信息**
   271  
   272  ```go
   273  trpcRspHead := &trpc.ResponseProtocol{} // 不同的协议对应不同的 head 结构体
   274  proxy.SayHello(ctx, req, client.WithRspHead(trpcRspHead))
   275  // trpcRspHead.TransInfo 即为下游服务返回的透传信息
   276  ```
   277  
   278  **5. 我们可以通过以下参数设置服务调用为单向调用**
   279  
   280  ```go
   281  proxy.SayHello(ctx, req, client.WithSendOnly())
   282  ```
   283  
   284  ## 常用 API
   285  
   286  tRPC-Go 采用 GoDoc 来管理 tRPC-Go 框架 API 文档的。通过查阅 [tRPC-Go API 文档](https://pkg.go.dev/github.com/trpc.group/trpc-go) 可以获取 API 的接口规范,参数含义和使用示例。
   287  
   288  对于 log,metrics 和 config,框架提供了标准调用接口,服务开发只有使用这些标准接口才能和服务治理系统对接。比如日志,如果不使用标准日志接口,而直接使用“fmt.Printf()”,日志信息是无法上报到远程日志中心的。
   289  
   290  ## 错误码
   291  
   292  tRPC-Go 对错误码的数据类型和含义都做了规划,对于常见错误码的问题定位也都做了解释。具体请参考 [tRPC-Go 错误码手册](/docs/user_guide/error_codes.zh_CN.md)。
   293  
   294  # 客户端配置
   295  
   296  客户端配置可以通过框架配置文件中的“client”部分来配置,配置分为“全局服务配置”和“指定服务配置”。具体配置的含义,取值范围和默认值请参考 [tRPC-Go 框架配置](/docs/user_guide/framework_conf.zh_CN.md)。
   297  
   298  以下是 client 配置的一个典型示例:
   299  
   300  ```yaml
   301  client:                                    # 客户端调用的后端配置
   302    timeout: 1000                            # 针对所有后端的请求最长处理时间,单位 ms
   303    namespace: Development                   # 针对所有后端的环境,正式环境 Production,测试环境 Development
   304    filter:                                  # 针对所有后端的拦截器配置数组
   305      - debuglog                             # 强烈建议使用这个debuglog打印日志,非常方便排查问题,具体可以参考:https://github.com/trpc-ecosystem/go-filter/tree/main/debuglog
   306    service:                                 # 针对单个后端的配置,默认都有默认值,可以完全不用配置
   307      - callee: trpc.test.helloworld.Greeter # 后端服务协议文件的 service name, 如果 callee 和下面的 name 一样,那只需要配置一个即可
   308        name: trpc.test.helloworld.Greeter1  # 后端服务名字路由的 service name,有注册到北极星名字服务的话,下面 target 不用配置
   309        target: ip://127.0.0.1:8000          # 后端服务地址,ip://ip:port polaris://servicename
   310        network: tcp                         # 后端服务的网络类型 tcp udp, 默认 tcp
   311        protocol: trpc                       # 应用层协议 trpc http...,默认 trpc
   312        timeout: 800                         # 当前这个请求最长处理时间,默认 0 不超时
   313        serialization: 0                     # 序列化方式 0-pb 1-jce 2-json 3-flatbuffer,默认不要配置
   314        compression: 1                       # 压缩方式 1-gzip 2-snappy 3-zlib,默认不要配置
   315        filter:                              # 针对单个后端的拦截器配置数组
   316          - debuglog                         # 只有当前这个后端使用 debuglog
   317  ```
   318  
   319  需要重点关注的配置项为:
   320  
   321  **1. 关于“callee”和“name”的区别:**
   322  
   323  “callee”表示下游服务的 Proto Service,格式为:“{package}.{proto service}”。“name”表示下游服务的 Naming Service,用于服务寻址。
   324  
   325  按照 tRPC-Go 研发规范 建议的,通常情况“callee”和“name”是一样的,用户可以只配置“name”。对于一个 Proto Service 映射到多个 Naming Service 的场景,用户需要同时设置“callee”和“name”。
   326  
   327  
   328  **2. 关于"target"的设置:**
   329  
   330  tRPC-Go 提供了两套寻址配置:“基于 Naming Service 寻址”和“基于 Target 寻址”。“target”配置可以不配,框架默认使用 name 寻址。当配置了“target”时,框架会基于 Target 寻址。“target”的格式为:`选择器://服务标识`,例如:`ip://127.0.0.1:1000`.
   331  
   332  **3. 关于协议的配置**
   333  
   334  服务协议相关配置主要包括“network”,“protocol”,“serialization”, “compression”这几个字段。“network”和“protocol”需要以服务端配置为准。
   335  
   336  **4. 关于 TLS 的配置**
   337  
   338  对于 tRPC 协议,https, http2 和 http3 协议都支持 tls 配置,典型 tls 配置示例如下:
   339  
   340  ```yaml
   341  client:
   342    service:                               # 下游服务的 service
   343      - name: trpc.test.helloworld.Greeter # service 的路由名称
   344        network: tcp                       # 网络监听类型  tcp udp
   345        protocol: trpc                     # 应用层协议 trpc http
   346        timeout: 1000                      # 请求最长处理时间 单位 毫秒
   347        tls_key: client.pem                # client 秘钥文件地址路径,秘钥文件不要直接提交到 git 上,应该在程序启动时,从配置中心拉取到本地存到该指定路径上
   348        tls_cert: client.cert              # client 证书文件地址路径
   349        ca_cert: ca.cert                   # ca 证书文件地址路径,用于校验 server 证书,调用 tls 服务,如 https server
   350        tls_server_name: xxx               # client 校验 server 服务名,调用 https 时,默认为 hostname
   351  ```
   352  
   353  对于纯客户端工具,需要通过 option 指定:
   354  
   355  ```go
   356  proxy.SayHello(ctx, req, client.WithTLS(certFile, keyFile, caFile, serverName))
   357  ```
   358  
   359  **5. 关于拦截器配置**
   360  
   361  框架支持为两级拦截器配置:全局配置和单一服务配置,执行的优先级为:全局设置 > 单一服务配置。如果两者有重复的拦截器,则只执行优先级最高的那个。具体示例如下:
   362  
   363  ```yaml
   364  client:                                   # 客户端调用的后端配置
   365    timeout: 1000                           # 针对所有后端的请求最长处理时间,单位 ms
   366    namespace: Development                  # 针对所有后端的环境,正式环境 Production,测试环境 Development
   367    filter:                                 # 针对所有后端的拦截器配置数组
   368      - debuglog                            # debuglog 打印日志
   369    service:                                # 针对单个后端的配置,默认都有默认值,可以完全不用配置
   370      - name: trpc.test.helloworld.Greeter1 # 后端服务名字路由的 service name,有注册到北极星名字服务的话,下面 target 不用配置
   371        network: tcp                        # 后端服务的网络类型 tcp udp, 默认 tcp
   372        protocol: trpc                      # 应用层协议 trpc http tars oidb ...,默认 trpc
   373        timeout: 800                        # 当前这个请求最长处理时间,默认 0 不超时
   374        filter:                             # 针对单个后端的拦截器配置数组
   375          - debuglog
   376  ```
   377  
   378  # 服务寻址
   379  
   380  服务寻址是服务调用中非常重要的环节,框架通过插件的方式来实现服务发现,策略路由,负载均衡和熔断器,框架不包括任何具体实现,用户可根据需要引入相应的插件。服务寻址的很多功能都是和名字服务提供的功能密切相关的,用户需要结合名字服务文档和对应插件文档来获取功能详情。本节后续的描述均已北极星插件为例。
   381  
   382  ## 命名空间与环境
   383  
   384  框架通过命名空间(namespace)和环境(env_name)两个概念来实现服务调用的隔离。namespace 通常用于区分生产环境和非生产环境,两个 namespace 的服务是完全隔离的。env_name 只用于非生产环境,通过 env_name 为用户提供个人测试环境。
   385  
   386  系统建议通过框架配置文件来设置客户端的 namespace 和 env_name, 在服务调用时默认使用客户端的 namespace 和 env_name。
   387  
   388  ```yaml
   389  global:
   390    # 必填,通常使用 Production 或 Development
   391    namespace: String
   392    # 选填,环境名称
   393    env_name: String
   394  ```
   395  
   396  框架也支持在服务调用时,指定服务的 namespace 和 env_name,我们把它称为指定环境服务调用。指定环境服务调用需要关闭服务路由功能(系统默认是打开的)。可以通过 Option 函数来设置:
   397  
   398  ```go
   399  opts := []client.Option{
   400      // 命名空间,不填写默认使用本服务所在环境的 namespace
   401      client.WithNamespace("Development"),
   402      // 服务名
   403      client.WithServiceName("trpc.test.helloworld.Greeter"),
   404      // 设置被调服务环境
   405      client.WithCalleeEnvName("62a30eec"),
   406      // 关闭服务路由
   407      client.WithDisableServiceRouter()
   408  }
   409  ```
   410  
   411  也可以通过框架配置文件来设置:
   412  
   413  ```yaml
   414  client:                                   # 客户端调用的后端配置
   415    namespace: Development                  # 针对所有后端的环境
   416    service:                                # 针对单个后端的配置
   417      - name: trpc.test.helloworld.Greeter1 # 后端服务名字路由的 service name
   418        disable_servicerouter: true         # 单个 client 是否禁用服务路由
   419        env_name: eef23fdab                 # 设置下游服务多环境的环境名,需要 disable_servicerouter 为 true 才生效
   420        namespace: Development              # 对端服务环境
   421  ```
   422  
   423  ## 寻址方式
   424  
   425  框架提供了两套寻址配置:“基于 Naming Service 寻址”和“基于 Target 寻址”。可以通过 Option 函数选项来设置,系统默认和推荐使用“基于 Naming Service 寻址”,基于 Naming Service 寻址的 Option 函数定义和示例为:
   426  
   427  ### 基于 Namine Service 寻址
   428  
   429  ```go
   430  // 基于 Naming Service 寻址接口定义
   431  func WithServiceName(s string) Option
   432  
   433  // 示例代码
   434  func main() {
   435      opts := []client.Option{
   436          client.WithServiceName("trpc.app.server.service"),
   437      }
   438      rsp, err := clientProxy.SayHello(ctx, req, opts...)
   439  }
   440  ```
   441  
   442  ### 基于 Target 寻址
   443  
   444  使用基于 Target 寻址的 Option 函数定义和示例为:
   445  
   446  ```go
   447  // 基于 Target 寻址接口定义,target 格式:选择器://服务标识
   448  func WithTarget(t string) Option
   449  
   450  // 示例代码
   451  func main() {
   452      opts := []client.Option{
   453          client.WithNamespace("Development"),
   454          client.WithTarget("ip://127.0.0.1:8000"),
   455      }
   456      rsp, err := clientProxy.SayHello(ctx, req, opts...)
   457  }
   458  ```
   459  
   460  “ip”和“dns”在工具类型的客户端中使用比较常见的选择器,target 的格式为:`ip://ip1:port1,ip2:port2`,支持 ip 列表。IP 选择器会在 IP 列表中随机选择一个 IP 用于服务调用。IP 和 DNS 选择器不依赖外部名字服务。
   461  
   462  #### `ip://<ip>:<port>`
   463  
   464  指定直连 ip 寻址,如 ip://127.1.1.1:8080,也可以设置多个ip,格式为 ip://ip1:port1,ip2:port2
   465  
   466  #### `dns://<domain>:<port>`
   467  
   468  指定域名寻址,常用于 http 请求,如 dns://www.qq.com:80
   469  
   470  ## 插件设计
   471  
   472  服务寻址包括服务发现、负载均衡、服务路由、熔断器等部分,服务发现的流程可以简化为:
   473  
   474  ![server_discovery](/.resources/user_guide/client/overview/server_discovery_zh_CN.png)
   475  
   476  框架通过“selector”来组合这四个模块,并提供了两种插件方式来实现服务寻址:
   477  
   478  - 整体接口:名字服务作为整体注册到框架,作为一个 selector 插件。整体接口的优势在于注册到框架比较简单,框架不关心名字服务流程中各个模块的具体实现,插件可以整体控制名字服务寻址的整个流程,方便做性能优化和逻辑控制。
   479  - 分模块接口:使用框架默认提供的 selector,服务发现、负载均衡、服务路由、熔断器等分别注册到框架,框架组合这些模块。分模块优势在于更加的灵活,用户可以根据自己的需要对不同模块进行选择然后自由组合,但同时会增加插件的实现复杂度。
   480  
   481  框架支持用户开发新的名字服务插件。名字服务插件的开发请参考 [tRPC-Go 开发名字服务插件](/docs/developer_guide/develop_plugins/naming.zh_CN.md)。
   482  
   483  # 插件选择
   484  
   485  对于插件的使用,我们需要同时“在 main 文件中 import 插件”和“在框架配置文件中配置插件”的方式来引入插件。如何使用插件请参考 [北极星名字服务](https://trpc.group/trpc-go/trpc-naming-polarismesh) 中的示例。
   486  
   487  tRPC 插件生态提供了丰富的插件,程序如何选择合适的插件呢?这里我们提供了一些思路供大家参考。我们可以把插件可以大致分成三类:独立插件,服务治理插件 和 存储接口插件。
   488  
   489  - 独立插件:比如协议,压缩,序列化,本地内存缓存等插件,其插件的运行不依赖外部系统组件。这类插件的思路比较简单,主要是依据业务功能的需要,和插件的成熟度来做选择
   490  - 服务治理插件:绝大部分服务治理插件,比如远程日志,名字服务,配置中心等,它们都需要和外部系统对接,对于微服务治理体系有很大的依赖。对这类插件的选择,我们需要明确服务最终运行在什么运营平台上,平台提供了哪些治理组件,服务有哪些能力一定要和平台对接,哪些则不需要。
   491  - 存储接口插件:存储插件主要封装了业界和公司内部成熟数据库,消息队列等组件的接口调用。关于这部分插件,我们首先需要考虑业务的技术选型,什么样的数据库更适合业务的需求。然后基于技术选型来看 tRPC 是否支持,如果不支持,我们可以选择使用数据库原生 SDK,或者建议大家贡献插件到 tRPC 社区
   492  
   493  # 拦截器
   494  
   495  tRPC-Go 提供了拦截器(filter)机制,拦截器在服务请求和响应的上下文设置埋点,允许业务在埋点处插入自定义处理逻辑。tRPC-Go [插件生态](https://github.com/trpc-ecosystem) 提供了丰富的拦截器,其中 调用链,监控插件也都是通过拦截器来实现的。
   496  
   497  关于拦截器的原理,触发时机,执行顺序和自定义拦截器的示例代码,请参考 [tRPC-Go 开发拦截器插件](/filter)。
   498  
   499  # 调用场景
   500  
   501  对于程序作为纯客户端的场景,服务调用的方式比较简单,通常采用同步调用方式直接等待调用返回,或者创建一个 goroutine 并在 goroutine 中同步调用等待返回结果,这里不做赘述。
   502  
   503  对于程序既做服务端又做客户端的场景(服务在收到上游请求时,需要调用下游服务)会相对复杂点,本文按照同步处理,异步处理,多并发处理三种方式来给用户开发提供思路。
   504  
   505  ## 同步处理
   506  
   507  同步处理的典型场景:一个服务当它收到上游的服务请求时,需要调用下游服务并等待下游服务调用完成后再给上游回包。
   508  
   509  对于同步处理,程序对下游服务调用可以使用请求服务的 ctx,支持包括 ctx 日志,全链路超时控制等功能。代码示例为:
   510  
   511  ```go
   512  func (s *serverImpl) Call(ctx context.Context, req *pb.Req) (*pb.Rsp, error) {
   513      ....
   514  
   515      // 同步处理后续服务调用,可以使用服务请求里的 ctx
   516      proxy := redis.NewClientProxy("trpc.redis.test.service") // proxy 不要每次创建,这里只是演示
   517      val1, err := redis.String(proxy.Do(ctx, "GET", "key1")) 
   518      ....
   519  }
   520  ```
   521  
   522  ## 异步处理
   523  
   524  异步处理的典型场景:一个服务当它收到上游的服务请求,需要提前给上游回包,然后再慢慢处理下游的服务调用。
   525  
   526  对于异步处理,程序可以启一个 goroutine 执行后续服务调用,但是后续服务调用不能使用原服务请求的 ctx,因为原 ctx 完成回包后会自动取消。后续服务调用可以使用 trpc.BackgroundContext() 创建一个新的 ctx,也可以直接使用 trpc 提供的 trpc.Go 工具函数:
   527  
   528  ```go
   529  func (s *serverImpl) Call(ctx context.Context, req *pb.Req) (*pb.Rsp, error) {
   530      ....
   531  
   532      trpc.Go(ctx, time.Minute, func(ctx context.Context) {  // 这里可以直接传入请求入口的 ctx,trpc.Go 里面会先 clone context 再 go and recover,内部会包含日志,监控,recover,超时控制
   533          proxy := redis.NewClientProxy("trpc.redis.test.service")  // proxy 不要每次创建,这里只是演示
   534          val1, err := redis.String(proxy.Do(ctx, "GET", "key1")) 
   535      })
   536  
   537      // 不用等待下游响应,直接回包。ctx 在完成回包后会自动 cancel
   538      ....
   539  }
   540  ```
   541  
   542  ## 多并发处理
   543  
   544  多并发调用的典型场景:一个上线服务,当它收到上游的服务请求时,需要同时调用多个下游服务,并等待所有下游服务的响应。
   545  
   546  这种场景,业务可以自己启动多个 goroutine 来发起请求,但是这样比较麻烦,需要自己 waitgroup,recover,如果没有 recover,自己启动的 goroutine 很容易导致服务 crash,框架封装了一个简单的多并发函数 GoAndWait() 供用户使用。
   547  
   548  ```go
   549  // GoAndWait 封装更安全的多并发调用,启动 goroutine 并等待所有处理流程完成,自动 recover
   550  // 返回值 error: 返回的是多并发协程里面第一个返回的不为 nil 的 error
   551  func GoAndWait(handlers ...func() error) error
   552  ```
   553  
   554  示例:假设服务收到 Call() 请求后,服务需要向两个后端服务 redis 获取 key1,key2 的值,只有完成下游服务调用后,才会返回响应给上游。
   555  
   556  ```go
   557  func (s *serverImpl) Call(ctx context.Context, req *pb.Req) (*pb.Rsp, error) {
   558      var value [2]string
   559      proxy := redis.NewClientProxy("trpc.redis.test.service")
   560      if err := trpc.GoAndWait(
   561          func() error {
   562              // 假设第一个下游服务调用是从 redis 获取 key1 的值,由于 GoAndWait 会等待所有 goroutine 都完成才会退出,ctx 不会取消,所以这里可以使用请求入口的 ctx,若要拷贝新的 ctx,可以在 GoAndWait 前面使用`newCtx := trpc.CloneContext(ctx)`
   563              val1, err := redis.String(proxy.Do(ctx, "GET", "key1"))
   564              if err != nil {
   565                  // key1 不是关键数据,失败了也无所谓,可以兜底一个假数据并返回成功
   566                  value[0] = "fake1"
   567                  return nil
   568              }
   569              log.DebugContextf(ctx, "get key1, val1:%s", val1)
   570              value[0] = val1
   571              return nil
   572          },
   573          func() error {
   574              // 假设第二个下游服务调用是从 redis 获取 key2 的值
   575              val2, err := redis.String(proxy.Do(ctx, "GET", "key2"))
   576              if err != nil {
   577                  // key2 是关键数据,获取不到需要提前终止逻辑,所以这里返回失败
   578                  return errs.New(10000, "get key2 fail: "+err.Error())
   579              }
   580              log.DebugContextf(ctx, "get key2, val2:%s", val2)
   581              
   582              value[1] = val2
   583              return nil
   584          },
   585      );     err != nil { // 多并发请求有失败,返回错误码给上游服务
   586          return nil, err
   587      }
   588      // ...
   589  }
   590  ```
   591  
   592  # 高级功能
   593  
   594  ## 超时控制
   595  
   596  tRPC-Go 框架为服务的调用提供了调用超时机制。关于调用超时机制的介绍和相关配置,请参考 [tRPC-Go 超时控制](/docs/user_guide/timeout_control.zh_CN.md) 。
   597  
   598  ## 链路透传
   599  
   600  tRPC-Go 框架提供在客户端与服务端之间透传字段,并在整个调用链路透传下去的机制。关于链路透传的机制和使用,请参考 [tRPC-Go 链路透传](/docs/user_guide/metadata_transmission.zh_CN.md)。此功能需要协议支持元数据下发功能,tRPC 协议,泛 HTTP RPC 协议,taf 协议均支持链路透传功能。其它协议请联系各自协议负责人。
   601  
   602  ## 自定义压缩
   603  
   604  tRPC-Go 框架支持业务自己定义压缩、解压缩方式。具体请参考 [这里](/codec/compress_gzip.go)。
   605  
   606  ## 自定义序列化
   607  
   608  tRPC-Go 框架业务自己定义序列化、反序列化类型。具体示例请参考 [这里](/codec/serialization_json.go)。