头像加载失败

阅读量:
评论数:

图片加载失败

Kitex 学习笔记

Tags: #Golang


前置知识

参考自 https://www.cloudwego.io/zh/docs/kitex/getting-started/pre-knowledge/

什么是 RPC

RPC (Remote Procedure Call) ,即远程过程调用。通俗来讲,就是调用远端服务的某个方法,并获取到对应的响应。RPC 本质上定义了一种通信的流程,而具体的实现技术没有约束,核心需要解决的问题为序列化网络通信。如可以通过 gob/json/pb/thrift 来序列化和反序列化消息内容,通过 socket/http 来进行网络通信。只要客户端与服务端在这两方面达成共识,能够做到消息正确的解析接口即可。

一般来说,RPC 框架包括了代码生成、序列化、网络通讯等,主流的微服务框架也会提供服务治理相关的能力,比如服务发现、负载均衡、熔断等等。

RPC 调用的流程

一次 rpc 调用包括以下基本流程,分为客户端和服务端两个部分:

  1. (客户端)构造请求参数,发起调用
  2. (客户端)通过服务发现、负载均衡等得到服务端实例地址,并建立连接
  3. (客户端)请求参数序列化成二进制数据
  4. (客户端)通过网络将数据发送给服务端

  1. (服务端)服务端接收数据
  2. (服务端)反序列化出请求参数
  3. (服务端)handler 处理请求并返回响应结果
  4. (服务端)将响应结果序列化成二进制数据
  5. (服务端)通过网络将数据返回给客户端

  1. (客户端)接收数据
  2. (客户端)反序列化出结果
  3. (客户端)得到调用的结果

其中步骤 2 中包含的流程称为「服务治理」,通常包括并不限于服务发现、负载均衡、ACL、熔断、限流等等功能。这些功能是由其他组件提供的,并不是 Thrift 框架所具有的功能。

RPC 服务开发流程

例如基于 Thrift 的 RPC 服务开发,通常包括如下过程:

  1. 编写 IDL,定义服务 (Service) 接口。
  2. 使用 thrift(或者等价的生成代码工具,如 kitex 等)生成客户端、服务端的支持代码。
  3. 服务端开发者编写 handler ,即请求的处理逻辑。
  4. 服务端开发者运行服务监听端口,处理请求。
  5. 客户端开发者编写客户端程序,经过服务发现连接上服务端程序,发起请求并接收响应。

环境准备

安装 Golang

Go 语言的环境准备参考 Golang 安装

完成安装后打开终端并输入 go version ,正确输出 Go 版本以及系统架构信息代表安装成功。例如

  1. $ go version
  2. // output
  3. go version go1.24.1 linux/amd64

安装 Kitex

首先确保 GOPATH 环境变量已经被正确地定义(例如 export GOPATH=~/go)并且将$GOPATH/bin 添加到 PATH 环境变量之中(例如 export PATH=$GOPATH/bin:$PATH

安装 kitex

  1. go install github.com/cloudwego/kitex/tool/cmd/kitex@latest

安装成功后,执行 kitex --version 可以看到具体版本号的输出:

  1. $ kitex --version
  2. v0.12.3

开发示例

本章节中,将会模拟一个简单的电商场景,包括商品服务、库存服务与 API 服务,商品服用调用库存服务查询库存,API 服务调用商品服务查询商品信息,对前端或用户暴露 HTTP 接口供查询商品信息。

编写 IDL

  1. mkdir idl
  2. cd idl

base.thrift

  1. namespace go example.shop.base
  2. struct BaseResp {
  3. 1: string code
  4. 2: string msg
  5. }

item.thrift

  1. namespace go example.shop.item
  2. include "base.thrift"
  3. struct Item {
  4. 1: i64 id
  5. 2: string title
  6. 3: string description
  7. 4: i64 stock
  8. }
  9. struct GetItemReq {
  10. 1: required i64 id
  11. }
  12. struct GetItemResp {
  13. 1: Item item
  14. 255: base.BaseResp baseResp
  15. }
  16. service ItemService{
  17. GetItemResp GetItem(1: GetItemReq req)
  18. }

stock.thrift

  1. namespace go example.shop.stock
  2. include "base.thrift"
  3. struct GetItemStockReq {
  4. 1: required i64 item_id
  5. }
  6. struct GetItemStockResp {
  7. 1: i64 stock
  8. 255: base.BaseResp BaseResp
  9. }
  10. service StockService {
  11. GetItemStockResp GetItemStock(1:GetItemStockReq req)
  12. }

代码生成

在本章节中,将会模拟一个简单的电商场景,包括商品服务、库存服务与 API 服务,商品服用调用库存服务查询库存,API 服务调用商品服务查询商品信息,对前端或用户暴露 HTTP 接口供查询商品信息。

创建项目目录

在本项目中,借助远程 Git 仓库,可以让 IDL、kitex_gen、API 服务、多个 RPC 服务的代码,分散多个仓库,不局限于本地同目录。

先创建一个项目目录:

  1. mkdir example_shop
  2. cd example_shop

编写 IDL

按照开发流程,首先需要编写 IDL,这里以 thrift IDL 为例子。

创建 idl 目录用于存放项目 idl 文件

  1. mkdir idl
  2. cd idl

一般不同的服务都会使用不同的 IDL,这里创建 item.thriftstock.thrift 分别定义商品服务与库存服务的接口,同时创建 base.thrift 定义公共数据结构。

base.thrift

  1. namespace go example.shop.base
  2. struct BaseResp {
  3. 1: string code
  4. 2: string msg
  5. }

item.thrift

  1. namespace go example.shop.item
  2. include "base.thrift"
  3. struct Item {
  4. 1: i64 id
  5. 2: string title
  6. 3: string description
  7. 4: i64 stock
  8. }
  9. struct GetItemReq {
  10. 1: required i64 id
  11. }
  12. struct GetItemResp {
  13. 1: Item item
  14. 255: base.BaseResp baseResp
  15. }
  16. service ItemService{
  17. GetItemResp GetItem(1: GetItemReq req)
  18. }

stock.thrift

  1. namespace go example.shop.stock
  2. include "base.thrift"
  3. struct GetItemStockReq {
  4. 1: required i64 item_id
  5. }
  6. struct GetItemStockResp {
  7. 1: i64 stock
  8. 255: base.BaseResp BaseResp
  9. }
  10. service StockService {
  11. GetItemStockResp GetItemStock(1:GetItemStockReq req)
  12. }

最后将 idl 目录上传到远程 Git 服务,此处假设为 https://code.example.com/example_shop/idl

生成 kitex_gen 代码

有了 IDL 以后,便可以通过 kitex 工具生成项目代码了。因为有两个 IDL 定义了服务,所以执行两次 kitex 命令:

  1. # 此处的 ${module} 参数用 code.example.com/example_shop(示例)替代
  2. kitex -module ${module} idl/item.thrift
  3. kitex -module ${module} idl/stock.thrift

生成的代码分两部分,一部分是结构体的编解码序列化代码,由 IDL 编译器生成;另一部分由 kitex 工具在前者产物上叠加,生成用于创建和发起 RPC 调用的桩代码。它们默认都在 kitex_gen 目录下。

最后将 kitex_gen 目录上传到远程 Git 服务,此处同样假设为 https://code.example.com/example_shop/kitex_gen

生成 RPC 脚手架

前面生成的 kitex_gen 代码并不能直接运行,需要自己完成 NewClientNewServer 的构建。kitex 命令行工具提供了 -service 参数能直接生成带有脚手架的代码,接下来让我们为商品服务和库存服务分别生成脚手架。

首先为两个 RPC 服务分别单独创建目录

  1. mkdir -p rpc/item rpc/stock

再分别进入各自的目录中,执行如下命令生成代码:

  1. # 此处的 ${module} 参数用 code.example.com/example_shop(示例)替代
  2. // item 目录下执行
  3. kitex -module item -service example.shop.item -use ${module}/kitex_gen ../../idl/item.thrift
  4. // stock 目录下执行
  5. kitex -module stock -service example.shop.stock -use ${module}/kitex_gen ../../idl/stock.thrift

kitex 默认会将代码生成到执行命令的目录下,kitex 的命令中:

在 item 和 stock 各自的目录下执行 go mod tidy 拉取依赖。

最后,项目结构如下(可以划分为 4 个 Git 仓库):

  1. .
  2. ├── idl // 示例 idl 存放的目录
  3. ├── base.thrift
  4. ├── item.thrift
  5. └── stock.thrift
  6. ├── kitex_gen
  7. └── example
  8. └── shop
  9. ├── base
  10. ├── base.go // 根据 IDL 生成的编解码文件,由 IDL 编译器生成
  11. ├── k-base.go // kitex 专用的一些拓展内容
  12. └── k-consts.go
  13. ├── item
  14. ├── item.go // 根据 IDL 生成的编解码文件,由 IDL 编译器生成
  15. ├── itemservice // kitex 封装代码主要在这里
  16. ├── client.go
  17. ├── itemservice.go
  18. └── server.go
  19. ├── k-consts.go
  20. └── k-item.go // kitex 专用的一些拓展内容
  21. └── stock
  22. ├── k-consts.go
  23. ├── k-stock.go // kitex 专用的一些拓展内容
  24. ├── stock.go // 根据 IDL 生成的编解码文件,由 IDL 编译器生成
  25. └── stockservice // kitex 封装代码主要在这里
  26. ├── client.go
  27. ├── server.go
  28. └── stockservice.go
  29. └── rpc
  30. ├── item
  31. ├── build.sh // 用来编译的脚本,一般情况下不需要更改
  32. | ├── go.mod // go module 文件
  33. | ├── go.sum
  34. ├── handler.go // 服务端的业务逻辑都放在这里,这也是需要更改和编写的文件
  35. ├── kitex_info.yaml
  36. ├── main.go
  37. └── script
  38. └── bootstrap.sh
  39. └── stock
  40. ├── build.sh // 用来编译项目的脚本,一般情况下不需要更改
  41. ├── go.mod // go module 文件
  42. ├── go.sum
  43. ├── handler.go // 服务端的业务逻辑都放在这里,这也是需要更改和编写的文件
  44. ├── kitex_info.yaml
  45. ├── main.go // 服务启动函数,一般在这里做一些资源初始化的工作,可以更改
  46. └── script
  47. └── bootstrap.sh

服务调用

最终状态调用关系:API 服务(HTTP 协议) -> 商品服务(rpc/item,RPC 协议)-> 库存服务(rpc/stock,RPC 协议)

运行库存服务

需要编写的服务端逻辑都在 handler.go 这个文件中,库存服务的业务逻辑在 rpc/stock/handler.go 中修改,此处不作赘述,直接运行服务。

直接看看 rpc/stock/main.gomain.go 中的代码很简单,使用 kitex 生成的代码创建一个 server 服务端,并调用其 Run 方法开始运行。通常使用 main.go 进行一些项目初始化,如加载配置等。

  1. package main
  2. import (
  3. "log"
  4. stock "code.example.com/example_shop/kitex_gen/example/shop/stock/stockservice"
  5. )
  6. func main() {
  7. svr := stock.NewServer(new(StockServiceImpl))
  8. err := svr.Run()
  9. if err != nil {
  10. log.Println(err.Error())
  11. }
  12. }

修改监听的端口

如果直接运行该服务的话,会默认监听 8888 端口。可以在创建 server 时传入 option 配置将监听的端口修改为 8890

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. stock "code.example.com/example_shop/kitex_gen/example/shop/stock/stockservice"
  6. "github.com/cloudwego/kitex/server"
  7. )
  8. func main() {
  9. addr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:8890")
  10. svr := stock.NewServer(new(StockServiceImpl), server.WithServiceAddr(addr))
  11. err := svr.Run()
  12. if err != nil {
  13. log.Println(err.Error())
  14. }
  15. }

运行服务

有两种运行方式:

  1. 编译后运行二进制文件
  2. 使用 go run . 直接运行服务

下面细讲第一种方式:

编译并运行服务

kitex 为我们生成了编译脚本,即 build.sh。在 build.sh 主要做了以下事情:

  1. 定义了一个变量 RUN_NAME,用于指定生成的可执行文件的名称,值为我们在 IDL 中指定的 namespace。本例中为 example.shop.stock
  2. 创建 output 目录,此后的编译出的二进制文件放在 output/bin 下。同时将 script 目录下的项目启动脚本复制进去
  3. 根据环境变量 IS_SYSTEM_TEST_ENV 的值判断生成普通可执行文件或测试可执行文件。值为 1 则代表使用 go test -c 生成测试文件,否则正常使用 go build 命令编译。

直接执行 sh build.sh 即可编译项目。

编译成功后,生成 output 目录:

  1. output
  2. ├── bin // 存放二进制可执行文件
  3. └── example.shop.stock
  4. └── bootstrap.sh // 运行文件的脚本

执行 sh output/bootstrap.sh 即可启动编译后的二进制文件。输出类似以下命令,代表运行成功:

  1. 2025/03/16 08:35:11.654937 server.go:79: [Info] KITEX: server listen at addr=[::]:8890

运行商品服务

已经成功运行了库存服务,接下来补充商品服务,实现对库存服务的调用。
为了实现 client 的复用,在 ItemServiceImpl 中补充字段 stockservice.Client ,在 rpc/item/handler.go 中补充以下方法:

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. item "code.example.com/example_shop/kitex_gen/example/shop/item"
  6. "code.example.com/example_shop/kitex_gen/example/shop/stock"
  7. "code.example.com/example_shop/kitex_gen/example/shop/stock/stockservice"
  8. "github.com/cloudwego/kitex/client"
  9. )
  10. // ItemServiceImpl implements the last service interface defined in the IDL.
  11. type ItemServiceImpl struct {
  12. stockCli stockservice.Client
  13. }
  14. func NewStockClient(addr string) (stockservice.Client, error) {
  15. return stockservice.NewClient("example.shop.stock", client.WithHostPorts(addr))
  16. }
  17. // GetItem implements the ItemServiceImpl interface.
  18. func (s *ItemServiceImpl) GetItem(ctx context.Context, req *item.GetItemReq) (resp *item.GetItemResp, err error) {
  19. resp = item.NewGetItemResp()
  20. resp.Item = item.NewItem()
  21. resp.Item.Id = req.GetId()
  22. resp.Item.Title = "Kitex"
  23. resp.Item.Description = "Kitex is an excellent framework!"
  24. stockReq := stock.NewGetItemStockReq()
  25. stockReq.ItemId = req.GetId()
  26. stockResp, err := s.stockCli.GetItemStock(context.Background(), stockReq)
  27. if err != nil {
  28. log.Println(err)
  29. stockResp.Stock = 0
  30. }
  31. resp.Item.Stock = stockResp.GetStock()
  32. return
  33. }

ItemServiceImpl 补充了库存服务的客户端,需要初始化后才能使用,在 rpc/item/main.go 中完成初始化操作:

  1. package main
  2. import (
  3. "log"
  4. item "code.example.com/example_shop/kitex_gen/example/shop/item/itemservice"
  5. )
  6. func main() {
  7. itemServiceImpl := new(ItemServiceImpl)
  8. stockCli, err := NewStockClient("0.0.0.0:8890")
  9. if err != nil {
  10. log.Fatal(err)
  11. }
  12. itemServiceImpl.stockCli = stockCli
  13. svr := item.NewServer(itemServiceImpl)
  14. err = svr.Run()
  15. if err != nil {
  16. log.Println(err.Error())
  17. }
  18. }

由于库存服务跑在 8890 端口,所以指定 8890 端口创建客户端。
至此,商品服务代码编写完整,参照上文重新编译启动商品服务,看到如下输出代表运行成功:

  1. 2025/03/16 08:52:09.836315 server.go:79: [Info] KITEX: server listen at addr=[::]:8888

运行 API 服务

有了商品服务和库存服务后,接下来需要编写 API 服务,用于调用这些 RPC 服务,并对外暴露 HTTP 接口。

此处使用 Gin 做一个简单演示,有关 Gin 框架的用法参见 Gin 文档
创建 API 目录,并新建 main.go,代码如下:

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "github.com/cloudwego/kitex/client"
  7. "github.com/cloudwego/kitex/client/callopt"
  8. "github.com/gin-gonic/gin"
  9. "code.example.com/example_shop/kitex_gen/example/shop/item"
  10. "code.example.com/example_shop/kitex_gen/example/shop/item/itemservice"
  11. )
  12. var (
  13. itemCli itemservice.Client
  14. )
  15. func main() {
  16. // 创建 client
  17. c, err := itemservice.NewClient("example.shop.item", client.WithHostPorts("0.0.0.0:8888"))
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. itemCli = c
  22. // 启动 Gin 服务
  23. router := gin.Default()
  24. router.GET("/api/item", Handler)
  25. router.Run() // 监听并在 0.0.0.0:8080 上启动服务
  26. }
  27. func Handler(c *gin.Context) {
  28. req := item.NewGetItemReq()
  29. req.Id = 1024
  30. resp, err := itemCli.GetItem(context.Background(), req, callopt.WithRPCTimeout(3*time.Second))
  31. if err != nil {
  32. log.Fatal(err)
  33. }
  34. c.JSON(200, gin.H{"code": 0, "value": resp.String()})
  35. }

执行 go run main.go 命令即可启动 API 服务,监听 8080 端口,请求 localhost:8080/api/item 即可发起 RPC 调用商品服务提供的 GetItem 接口,并获取到响应结果。

接入服务注册中心

部署 ETCD

为了更贴近真实环境,接下来为以上服务接入注册中心,在本例中选择了 etcd 作为注册中心,etcd 的安装与使用可参考 etcd.io 或使用下述 docker compose 文件,接下来默认你已经安装并启动 etcd 服务实例。

1. 拉取镜像

推荐使用官方镜像,支持版本控制:

  1. docker pull quay.io/coreos/etcd:v3.5.12

​2. 创建持久化目录

防止容器重启后数据丢失:

  1. mkdir -p /opt/etcd/{data,config} # 数据目录和配置目录
  1. docker pull quay.io/coreos/etcd:v3.5.12

3. 启动容器

有两种方式:

  1. 单节点部署的核心命令

    1. docker run -d \
    2. --name etcd-single \
    3. -p 2379:2379 \ # 客户端访问端口
    4. -p 2380:2380 \ # 节点间通信端口(单节点可忽略)
    5. -v /opt/etcd/data:/var/lib/etcd \ # 数据持久化
    6. -v /opt/etcd/config:/etc/etcd \ # 配置文件目录
    7. quay.io/coreos/etcd:v3.5.12 \
    8. etcd \
    9. --name single-node \
    10. --data-dir /var/lib/etcd \
    11. --advertise-client-urls http://0.0.0.0:2379 \
    12. --listen-client-urls http://0.0.0.0:2379 \
    13. --initial-cluster-state new
  2. 或者,使用配置文件启动(推荐)​
    创建配置文件 /opt/etcd/config/etcd.conf.yml

    1. name: etcd-single
    2. data-dir: /var/lib/etcd
    3. listen-client-urls: http://0.0.0.0:2379
    4. advertise-client-urls: http://0.0.0.0:2379
    5. initial-cluster-token: etcd-single-cluster
    6. initial-cluster-state: new

    启动时加载配置文件

    1. docker run -d \
    2. --name etcd-single \
    3. -p 2379:2379 \
    4. -p 2380:2380 \
    5. -v /opt/etcd/data:/var/lib/etcd \
    6. -v /opt/etcd/config:/etc/etcd \
    7. -v /opt/etcd/config/etcd.conf.yml:/etc/etcd/etcd.conf.yml \
    8. quay.io/coreos/etcd:v3.5.12 \
    9. etcd --config-file /etc/etcd/etcd.conf.yml

​4. 检查服务状态

  1. docker exec -it etcd-single etcdctl endpoint health
  2. # 输出示例:http://0.0.0.0:2379 is healthy

服务注册

注册库存服务

rpc/stock/main.go 中补充以下逻辑:

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. stock "code.example.com/example_shop/kitex_gen/example/shop/stock/stockservice"
  6. "github.com/cloudwego/kitex/pkg/rpcinfo"
  7. "github.com/cloudwego/kitex/server"
  8. etcd "github.com/kitex-contrib/registry-etcd"
  9. )
  10. func main() {
  11. // 使用时请传入真实 etcd 的服务地址,本例中为 127.0.0.1:2379
  12. r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. addr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:8890")
  17. svr := stock.NewServer(new(StockServiceImpl),
  18. server.WithServiceAddr(addr),
  19. // 指定 Registry 与服务基本信息
  20. server.WithRegistry(r),
  21. server.WithServerBasicInfo(
  22. &rpcinfo.EndpointBasicInfo{
  23. ServiceName: "example.shop.stock",
  24. },
  25. ),
  26. )
  27. err = svr.Run()
  28. if err != nil {
  29. log.Println(err.Error())
  30. }
  31. }

如果库存服务和其他服务不在同一个网络环境,其他服务将无法通过 0.0.0.0:8890 访问库存服务。

解决方法:(详情参见 服务注册扩展

当 Client 端无法访问 Server 端的 Listen Address,而需要使用公共的 IP 地址访问 Server 端时,可以通过指定 RegistryInfo 中的 AddrSkipListenAddr 进行设置。

  1. func main() {
  2. // 使用时请传入真实 etcd 的服务地址,本例中为 127.0.0.1:2379
  3. r, err := etcd.NewEtcdRegistry([]string{"127.0.0.1:2379"})
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. // 服务端本地监听 8890 端口
  8. addr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:8890")
  9. // 服务注册时,传入可访问的公网 IP + 端口
  10. remoteAddr, _ := net.ResolveTCPAddr("tcp", os.Getenv("HOST_IP")+":8890")
  11. registryInfo := ®istry.Info{
  12. Addr: remoteAddr,
  13. SkipListenAddr: true,
  14. }
  15. svr := stock.NewServer(new(StockServiceImpl),
  16. server.WithServiceAddr(addr),
  17. // 指定 Registry 与服务基本信息
  18. server.WithRegistry(r),
  19. server.WithRegistryInfo(registryInfo),
  20. server.WithServerBasicInfo(
  21. &rpcinfo.EndpointBasicInfo{
  22. ServiceName: "example.shop.stock",
  23. },
  24. ),
  25. )
  26. err = svr.Run()
  27. if err != nil {
  28. log.Println(err.Error())
  29. }
  30. )

注册商品服务

以同样的方式实现服务注册,在 rpc/item/main.go 中补充以下逻辑:

  1. package main
  2. import (
  3. "log"
  4. item "code.example.com/example_shop/kitex_gen/example/shop/item/itemservice"
  5. "github.com/cloudwego/kitex/pkg/rpcinfo"
  6. "github.com/cloudwego/kitex/server"
  7. etcd "github.com/kitex-contrib/registry-etcd"
  8. )
  9. func main() {
  10. // 使用时请传入真实 etcd 的服务地址,本例中为 127.0.0.1:2379
  11. r, err := etcd.NewEtcdRegistry([]string{"0.0.0.0:2379"})
  12. if err != nil {
  13. log.Fatal(err)
  14. }
  15. itemServiceImpl := new(ItemServiceImpl)
  16. stockCli, err := NewStockClient("0.0.0.0:8890")
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. itemServiceImpl.stockCli = stockCli
  21. svr := item.NewServer(itemServiceImpl,
  22. // 指定 Registry 与服务基本信息
  23. server.WithRegistry(r),
  24. server.WithServerBasicInfo(
  25. &rpcinfo.EndpointBasicInfo{
  26. ServiceName: "example.shop.item",
  27. }
  28. ),
  29. )
  30. err = svr.Run()
  31. if err != nil {
  32. log.Println(err.Error())
  33. }
  34. }

验证

补充完成代码后,分别启动这两个服务,两个服务的终端都会出现类似输出:

  1. 2025/03/16 17:51:47.507073 etcd_registry.go:299: [Info] start keepalive lease 38f7959f4b9c4334 for etcd registry

再使用 etcdctl 确认是否注册成功,执行 docker exec -it etcd-single etcdctl get --prefix "kitex" 会有类似如下输出:

  1. kitex/registry-etcd/example.shop.item/192.168.196.240:8888
  2. {"network":"tcp","address":"192.168.196.240:8888","weight":10,"tags":null}
  3. kitex/registry-etcd/example.shop.stock/127.0.0.1:8890
  4. {"network":"tcp","address":"127.0.0.1:8890","weight":10,"tags":null}

如果都能正确输出,代表服务注册成功。

服务发现

商品服务接入

在前面的代码中,商品服务调用库存服务的逻辑放在了 rpc/item/handler.go 中,故在此文件的 NewStockClient 中添加逻辑:

  1. func NewStockClient() (stockservice.Client, error) {
  2. // 使用时请传入真实 etcd 的服务地址,本例中为 127.0.0.1:2379
  3. r, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. return stockservice.NewClient("example.shop.stock", client.WithResolver(r)) // 指定 Resolver
  8. }

记得把 rpc/item/main.go 中调用 NewStockClient() 的参数去掉。

API 服务接入

修改 API 目录下的 main.go 中的 main 函数

  1. var (
  2. itemCli itemservice.Client
  3. )
  4. func main() {
  5. // 使用时请传入真实 etcd 的服务地址,本例中为 127.0.0.1:2379
  6. resolver, err := etcd.NewEtcdResolver([]string{"127.0.0.1:2379"})
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. // 创建 client
  11. c, err := itemservice.NewClient("example.shop.item", client.WithResolver(resolver))
  12. if err != nil {
  13. log.Fatal(err)
  14. }
  15. itemCli = c
  16. // 启动 Gin 服务
  17. router := gin.Default()
  18. router.GET("/api/item", Handler)
  19. router.Run() // 监听并在 0.0.0.0:8080 上启动服务
  20. }