1. RPC

1.1. RPC概念

RPC(Remote Procedure Call),远程过程调用,即两个进程跨网络进行调用,就如同调用本地的方法一样,无须关注细节。RPC是一种服务端-客户端模式,大概流程如下:

  • 客户端stub(client stub)将这些参数包装,并通过系统调用发送到服务端机器。打包的过程叫 marshalling。(常见方式:XML、JSON、二进制编码)
  • 客户端本地操作系统发送信息至服务器。(可通过自定义TCP协议或HTTP传输)
  • 服务器系统将信息传送至服务端stub(server stub)。
  • 服务端stub(server stub)解析信息。该过程叫 unmarshalling。
  • 服务端stub(server stub)调用程序,并通过类似的方式返回给客户端。

    1.2. Golang RPC编码流程

    1.2.1. 常用方法

    ```go // 注册服务,name为服务的名,rcvr为结构体对象(通常为指针) func RegisterName(name string, rcvr interface{}) error // 处理客户端连接 func ServeConn(conn io.ReadWriteCloser)

// 连接服务端 func Dial(network, address string) (Client, error) // 调用服务端的方法 func (client Client) Call(serviceMethod string, args interface{}, reply interface{})

  1. <a name="OZeWL"></a>
  2. ### 1.2.2. 编码流程
  3. <a name="DvG9W"></a>
  4. #### 1.2.2.1. 一般流程
  5. - 服务端
  6. ```go
  7. // 1. 定义结构体
  8. type Server struct{}
  9. // 2. 定义对外的RPC方法:
  10. // 只能有两个参数,第一个是传入参数,第二个是传出参数,传出参数必须是指针
  11. // 只能有一个返回值,并且是 error
  12. // 方法必须是可以导出的
  13. func (s *Server) Handler(input,*output) error {}
  14. // 3. 注册RCP结构体
  15. err := rpc.RegisterName("pkg/server", new(Server))
  16. // 4. 监听端口
  17. Listner,err := net.Listen("tcp", "0.0.0.0:8080")
  18. // 5. 接收请求
  19. conn,err := server.Listener.Accept()
  20. // 6. 处理请求
  21. go rpc.ServeConn(conn)
  • 客户端 ```go // 1. 拨号 client, err := rpc.Dail(“tcp”,”127.0.0.1:8080)

// 2. 调用远程RCP接口 err := client.Call(“pkg/server.Handler”, &resp)

  1. <a name="oxXER"></a>
  2. #### 1.2.2.2. 规范RPC编码流程

基本定义

  1. 定义服务名称常量:通常为包路径.结构体名称
  2. 定义该服务接口:通过接口规范需要对外暴露的RPC方法列表
  3. 服务端定义注册RPC的结构体,参数类型为上述接口

接口实现

  1. 客户端和服务端同时定义和实现上述接口
  2. 客户端额外增加一个Close方法关闭RPC客户端 ```

    1.3. 案例

    1.3.1. 简单案例1

    ```go // server/server.go

package server

import ( “fmt” log “github.com/sirupsen/logrus” “net” “net/rpc” )

const ( HelloServiceMethodName = “rpc/server/HelloService” )

var ( Listener net.Listener )

// 定义结构体 type HelloService struct { }

// rcp 方法必须满足三个条件: // 1. 只能有两个可序列化的参数,第二个是指针。通过第二个参数(传出参数)获取返回结果 // 2. 方法的返回值有且只能有一个 error // 3. 方法必须是可以导出的 func (p HelloService) Hello( request string, reply string) error { *reply = fmt.Sprintf(“Hello %s !”, request) return nil }

func init() { // 注册RCP对象 err := rpc.RegisterName(HelloServiceMethodName, new(HelloService)) if err != nil { log.Fatalf(“register hello service failed,err:%s”,err.Error()) } // 启用监听 Listener, err = net.Listen(“tcp”, “0.0.0.0:55999”) if err != nil { log.Fatalf(“bind address 0.0.0.0:55999 failed,err:%s”,err.Error()) } }

  1. ```go
  2. // server.go
  3. package main
  4. import (
  5. log "github.com/sirupsen/logrus"
  6. "learn/network/rpc/server"
  7. "net/rpc"
  8. )
  9. func main() {
  10. for {
  11. // 接收新的连接
  12. conn, err := server.Listener.Accept()
  13. if err != nil {
  14. log.Errorf("accept new conn failed,err:%s", err.Error())
  15. }
  16. log.Infof("recv new conn:%s", conn.RemoteAddr().String())
  17. go func() {
  18. defer conn.Close()
  19. rpc.ServeConn(conn)
  20. }()
  21. }
  22. }
  1. package main
  2. import (
  3. "fmt"
  4. log "github.com/sirupsen/logrus"
  5. "net/rpc"
  6. )
  7. func main() {
  8. // 拨号连接 rpc 服务端
  9. dial, err := rpc.Dial("tcp", "127.0.0.1:55999")
  10. if err != nil {
  11. log.Errorf("dial to address 127.0.0.1:55999 failed, err:%s", err.Error())
  12. return
  13. }
  14. var resp string
  15. // 远程调用服务端
  16. err = dial.Call("rpc/server/HelloService.Hello", "张三", &resp)
  17. if err != nil {
  18. log.Errorf("call service failed, err:%s",err.Error())
  19. return
  20. }
  21. fmt.Println(resp)
  22. }

1.3.2. 简单案例2

  1. // server.go
  2. package main
  3. import (
  4. "context"
  5. "errors"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/client"
  8. log "github.com/sirupsen/logrus"
  9. "net"
  10. "net/rpc"
  11. "strings"
  12. )
  13. var DockerClient *client.Client
  14. func GetDockerClient() (*client.Client, error) {
  15. if DockerClient != nil {
  16. return DockerClient, nil
  17. }
  18. return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  19. }
  20. type Image struct{}
  21. // 根据镜像的Id获取镜像Tag列表的方法
  22. func (i *Image) GetImageTag(id string, tags *[]string) error {
  23. cli, err := GetDockerClient()
  24. if err != nil {
  25. log.Errorf("get docker client failed ,err:%s", err.Error())
  26. return err
  27. }
  28. images, err := cli.ImageList(context.TODO(), types.ImageListOptions{})
  29. if err != nil {
  30. log.Errorf("list images failed ,err:%s", err.Error())
  31. return err
  32. }
  33. for _, image := range images {
  34. if strings.Contains(image.ID, id) {
  35. *tags = image.RepoTags
  36. return nil
  37. }
  38. }
  39. return errors.New("the container id not found")
  40. }
  41. // 根据镜像id删除镜像
  42. func (i *Image) DeleteImage(id string, resp *[]string) error {
  43. cli, err := GetDockerClient()
  44. if err != nil {
  45. log.Errorf("get docker client failed ,err:%s", err.Error())
  46. return err
  47. }
  48. responseList, err := cli.ImageRemove(context.TODO(), id, types.ImageRemoveOptions{})
  49. if err != nil {
  50. log.Errorf("remove image failed,err:%s", err.Error())
  51. return err
  52. }
  53. for _, response := range responseList {
  54. if response.Untagged != "" {
  55. *resp = append(*resp, response.Untagged)
  56. }
  57. }
  58. return nil
  59. }
  60. func main() {
  61. // 注册结rpc服务
  62. err := rpc.RegisterName("container/image", &Image{})
  63. if err != nil {
  64. log.Fatalf("registry rpc service failed,err:%s", err.Error())
  65. }
  66. // 监听端口
  67. listen, err := net.Listen("tcp", "0.0.0.0:8089")
  68. if err != nil {
  69. log.Fatalf("binding rpc service to 0.0.0.0:8089 failed,err:%s", err.Error())
  70. }
  71. for {
  72. conn, err := listen.Accept()
  73. if err != nil {
  74. log.Errorf("accept connect failed,err:%s", err.Error())
  75. continue
  76. }
  77. go func() {
  78. rpc.ServeConn(conn)
  79. _ = conn.Close()
  80. }()
  81. }
  82. }
  1. // client.go
  2. package main
  3. import (
  4. "fmt"
  5. log "github.com/sirupsen/logrus"
  6. "net/rpc"
  7. )
  8. func main() {
  9. dial, err := rpc.Dial("tcp", "127.0.0.1:8089")
  10. if err != nil {
  11. log.Errorf("connet to rpc server failed,err:%s", err.Error())
  12. return
  13. }
  14. var resp []string
  15. err = dial.Call("container/image.GetImageTag", "ed0e1d894299", &resp)
  16. if err != nil {
  17. log.Errorf("call container/image.GetImageTag failed,err:%s", err.Error())
  18. return
  19. }
  20. fmt.Println(resp)
  21. err = dial.Call("container/image.DeleteImage", "ed0e1d894299", &resp)
  22. if err != nil {
  23. log.Errorf("call container/image.DeleteImage failed,err:%s", err.Error())
  24. return
  25. }
  26. fmt.Println(resp)
  27. }

1.3.3. 使用接口

在涉及RPC的应用中,作为开发人员一般至少有三种角色:首先是服务端实现RPC方法的开发人员,其次是客户端调用RPC方法的人员,最后也是最重要的是制定服务端和客户端RPC接口规范的设计人员。之前的两个案例中,并不规范,以下是一个比较规范的RPC开放方法:

  1. // server.go
  2. package main
  3. import (
  4. "context"
  5. "errors"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/client"
  8. log "github.com/sirupsen/logrus"
  9. "net"
  10. "net/rpc"
  11. "strings"
  12. )
  13. var DockerClient *client.Client
  14. func GetDockerClient() (*client.Client, error) {
  15. if DockerClient != nil {
  16. return DockerClient, nil
  17. }
  18. return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
  19. }
  20. // 定义服务名称常量;通常为路径格式,避免重名
  21. const ImageServiceName = "rpc/server/pkg.ImageService"
  22. // 定义对外暴露的RPC方法
  23. type ImageServiceInterface interface {
  24. GetImageTag(id string, reply *[]string) error
  25. DeleteImage(id string, reply *[]string) error
  26. }
  27. // 注册RPC方法
  28. func ImageServiceRegistry(svc ImageServiceInterface) error {
  29. return rpc.RegisterName(ImageServiceName, svc)
  30. }
  31. // 定义RPC的服务结构体
  32. type Image struct{}
  33. // 定义RPC服务的方法
  34. // 根据镜像的Id获取镜像Tag列表的方法
  35. func (i *Image) GetImageTag(id string, tags *[]string) error {
  36. cli, err := GetDockerClient()
  37. if err != nil {
  38. log.Errorf("get docker client failed ,err:%s", err.Error())
  39. return err
  40. }
  41. images, err := cli.ImageList(context.TODO(), types.ImageListOptions{})
  42. if err != nil {
  43. log.Errorf("list images failed ,err:%s", err.Error())
  44. return err
  45. }
  46. for _, image := range images {
  47. if strings.Contains(image.ID, id) {
  48. *tags = image.RepoTags
  49. return nil
  50. }
  51. }
  52. return errors.New("the container id not found")
  53. }
  54. // 定义RPC服务的方法
  55. // 根据镜像id删除镜像
  56. func (i *Image) DeleteImage(id string, tags *[]string) error {
  57. cli, err := GetDockerClient()
  58. if err != nil {
  59. log.Errorf("get docker client failed ,err:%s", err.Error())
  60. return err
  61. }
  62. responseList, err := cli.ImageRemove(context.TODO(), id, types.ImageRemoveOptions{})
  63. if err != nil {
  64. log.Errorf("remove image failed,err:%s", err.Error())
  65. return err
  66. }
  67. for _, response := range responseList {
  68. if response.Untagged != "" {
  69. *tags = append(*tags, response.Untagged)
  70. }
  71. }
  72. return nil
  73. }
  74. func main() {
  75. // 注册结rpc服务
  76. err := ImageServiceRegistry(new(Image))
  77. if err != nil {
  78. log.Errorf("registry ImageService failed,err:%s", err.Error())
  79. return
  80. }
  81. // 监听端口
  82. listen, err := net.Listen("tcp", "0.0.0.0:8089")
  83. if err != nil {
  84. log.Fatalf("binding rpc service to 0.0.0.0:8089 failed,err:%s", err.Error())
  85. }
  86. for {
  87. conn, err := listen.Accept()
  88. if err != nil {
  89. log.Errorf("accept connect failed,err:%s", err.Error())
  90. continue
  91. }
  92. go func() {
  93. rpc.ServeConn(conn)
  94. _ = conn.Close()
  95. }()
  96. }
  97. }
  1. // client.go
  2. package main
  3. import (
  4. "fmt"
  5. log "github.com/sirupsen/logrus"
  6. "net/rpc"
  7. )
  8. // 定义服务名称常量;通常为路径格式,避免重名
  9. const imageServiceName = "rpc/server/pkg.ImageService"
  10. // 定义对外暴露的RPC方法
  11. type ImageServiceInterface interface {
  12. GetImageTag(id string, reply *[]string) error
  13. DeleteImage(id string, reply *[]string) error
  14. Close()
  15. }
  16. // 定义服务结构体
  17. type ImageClient struct {
  18. Name string
  19. Address string
  20. client *rpc.Client
  21. }
  22. func ImageDail(address string) (ImageServiceInterface, error) {
  23. c, err := rpc.Dial("tcp", address)
  24. if err != nil {
  25. return nil, err
  26. }
  27. return &ImageClient{Name: imageServiceName, Address: address, client: c}, nil
  28. }
  29. func (i *ImageClient) GetImageTag(id string, tags *[]string) error {
  30. return i.client.Call(i.Name+".GetImageTag",id, tags)
  31. }
  32. func (i *ImageClient) DeleteImage(id string, tags *[]string) error {
  33. return i.client.Call(i.Name+".DeleteImage", id , tags)
  34. }
  35. func (i *ImageClient) Close () {
  36. _ = i.client.Close()
  37. }
  38. func main() {
  39. client, err := ImageDail("127.0.0.1:8089")
  40. if err != nil {
  41. log.Errorf("connet to rpc server failed,err:%s", err.Error())
  42. return
  43. }
  44. var resp []string
  45. err = client.GetImageTag("a9d583973f65", &resp)
  46. if err != nil {
  47. log.Errorf("ImageClient call GetImageTag failed,err:%s", err.Error())
  48. return
  49. }
  50. fmt.Println(resp)
  51. err = client.DeleteImage("a9d583973f65", &resp)
  52. if err != nil {
  53. log.Errorf("ImageClient call DeleteImage failed,err:%s", err.Error())
  54. return
  55. }
  56. fmt.Println(resp)
  57. client.Close()
  58. }

2. GRPC

2.1. 介绍

在Golang RPC中,存在一个文件,就是只能在golang语言项目之间调用,无法跨语言。在企业中,不同的组件可能会使用不同的语言进行项目开发,不同语言项目之间使用RPC调用,一般推荐使用GRPC。
GRPC 是一种现代化开源的高性能RPC框架,能够运行于任意环境之中。最初由谷歌进行开发。它使用HTTP/2作为传输协议。

2.2. protocol buffers

Protocol buffers是一种轻便高效的结构化数据存储方式,可以用于数据序列化,非常适合RPC通信中数据序列化,GRPC 是的数据序列化是通过protocol buffers实现的。

  • 优势:
    • 序列化后体积比Json小,适合网络传输
    • 与语言无关,即跨语言、跨平台
    • 消息序列化和反序列化速度快
  • 劣势:

    • 应用范围不广,最常见的是用于gRPC通信

      2.2.1. 基本语法

      2.2.1.1. 规则

  • 文件以 .proto 结尾,除了 { } 结构和rpc方法定义末尾除外的语句都是以分号结尾

  • 结构定义可以包含: message, service, enum
  • Message类型名称用驼峰式命名,字段采用下划线命名
  • Enum类型名称采用驼峰式命名,字段采用大写下划线命名
  • Service方法名采用驼峰式命名

    2.2.2. 命令行操作

    ```

go get google.golang.org/grpc/cmd/protoc-gen-go-grpc

  1. ```
  2. // 不带 -I 会在services下创建 pbfiles目录
  3. // --go_out会在指定目录下创建结构体
  4. // --go-grpc_out 会创建服务相关的rpc方法
  5. protoc -I=pbfiles --go_out=services --go-grpc_out=services pbfiles/prod.proto

2.3. GRPC操作