Go每日一库之40:jsonrpc

2023年 10月 13日 103.3k 0

简介

在上一篇文章中我们介绍了 Go 标准库net/rpc的用法。在默认情况下,rpc库内部使用gob格式传输数据。我们仿造gob的编解码器实现了一个json格式的。实际上标准库net/rpc/jsonrcp中已有实现。本文是对上一篇文章的补充。

快速使用

标准库无需安装。

首先是服务端,使用net/rpc/jsonrpc之后,我们就不用自己去编写json的编解码器了:

package main

import (
  "log"
  "net"
  "net/rpc"
  "net/rpc/jsonrpc"
)

type Args struct {
  A, B int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
  *reply = args.A * args.B
  return nil
}

func main() {
  l, err := net.Listen("tcp", ":1234")
  if err != nil {
    log.Fatal("listen error:", err)
  }

  arith := new(Arith)
  rpc.Register(arith)

  for {
    conn, err := l.Accept()
    if err != nil {
      log.Fatal("accept error:", err)
    }

    // 注意这一行
    go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  }
}

直接调用jsonrpc.NewServerCodec(conn)创建一个服务端的codec。客户端也是类似的:

func main() {
  conn, err := net.Dial("tcp", ":1234")
  if err != nil {
    log.Fatal("dial error:", err)
  }

  // 这里,这里
  client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

  args := &Args{7, 8}
  var reply int
  err = client.Call("Arith.Multiply", args, &reply)
  if err != nil {
    log.Fatal("Multiply error:", err)
  }
  fmt.Printf("Multiply: %d*%d=%d\n", args.A, args.B, reply)
}

先运行服务端程序:

$ go run main.go

然后在一个新的控制台中运行客户端程序:

$ go run client.go
Multiply: 7*8=56

下面这段代码基本上每个使用jsonrpc的程序都要编写:

conn, err := net.Dial("tcp", ":1234")
if err != nil {
  log.Fatal("dial error:", err)
}

client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

因此jsonrpc为了方便直接提供了一个Dial方法。使用Dial简化上面的客户端程序:

func main() {
  client, err := jsonrpc.Dial("tcp", ":1234")
  if err != nil {
    log.Fatal("dial error:", err)
  }

  args := &Args{7, 8}
  var reply int
  err = client.Call("Arith.Multiply", args, &reply)
  if err != nil {
    log.Fatal("Multiply error:", err)
  }
  fmt.Printf("Multiply: %d*%d=%d\n", args.A, args.B, reply)
}

效果是一样的。

JSON-RPC 标准

JSON-RPC 1.0 标准在 2005 年发布,经过数年演化,于 2010 年发布了 2.0 版本。JSON-RPC 标准的内容可在www.jsonrpc.org/specificati…查看。Go 标准库net/rpc/jsonrpc实现了 1.0 版本。关于 2.0 版本的实现可以在pkg.go.dev上搜索json-rpc+2.0。本文以 1.0 版本为基础进行介绍。

JSON-RPC 传输的是单一的对象,序列化为 JSON 格式。请求对象包含以下 3 个属性:

  • method:请求调用的方法;
  • params:一个数组表示传给方法的各个参数;
  • id:请求 ID。ID 可以是任何类型,在收到响应时根据这个属性判断对应哪个请求。

响应对象包含以下 3 个属性:

  • result:方法返回的对象,如果error非空时,该属性必须为null
  • error:表示调用是否出错;
  • id:对应请求的 ID。

另外标准还定义了一种通知类型,除了id属性为null之外,通知对象的属性与请求对象完全一样。

调用client.Call("echo", "Hello JSON-RPC", &reply)时:

请求:{ "method": "echo", "params": ["Hello JSON-RPC"], "id": 1}
响应:{ "result": "Hello JSON-RPC", "error": null, "id": 1}

使用 zookeeper 实现简单的负载均衡

下面我们使用zookeeper实现一个简单的客户端侧的负载均衡。zookeeper中记录所有的可提供服务的服务器,客户端每次请求时都随机挑选一个。我们的示例中,请求必须是无状态的。首先,我们改造一下服务端程序,将监听地址提取出来,通过flag指定:

package main

import (
  "flag"
  "log"
  "net"
  "net/rpc"
  "net/rpc/jsonrpc"
)

var (
  addr *string
)

type Args struct {
  A, B int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
  *reply = args.A * args.B
  return nil
}

func init() {
  addr = flag.String("addr", ":1111", "addr to listen")
}

func main() {
  flag.Parse()

  l, err := net.Listen("tcp", *addr)
  if err != nil {
    log.Fatal("listen error:", err)
  }

  arith := new(Arith)
  rpc.Register(arith)

  for {
    conn, err := l.Accept()
    if err != nil {
      log.Fatal("accept error:", err)
    }

    go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  }
}

关于有哪些服务器可用,我们存储在zookeeper中。

首先要启动一个zookeeper的程序。在 Apache Zookeeper 官网可以下载能直接运行的 Windows 程序。下载之后解压,将conf文件夹中的样板配置zoo_sample.cfg复制一份,文件名改为zoo.cfg。在编辑器中打开zoo.cfg,将dataDir改为一个已存在的目录,或创建一个新目录。我在bin同级目录中创建了一个data目录,然后设置dataDir=../data。切换到bin目录下执行zkServer.batzookeeper程序就运行起来了。使用zkClient.bat连接上这个zookeeper,增加一个节点,设置数据:

$ create /rpcserver
$ set /rpcserver 127.0.0.1:1111,127.0.0.1:1112,127.0.0.1:1113

我们用,分隔多个服务器地址。

准备工作完成后,接下来就开始编写客户端代码了。我们实现一个代理类,负责监听zookeeper的数据变化,根据zookeeper中新的地址创建到服务器的连接,删除老的连接,将调用请求随机转发到一个服务器处理:

type Proxy struct {
zookeeper string
clients map[string]*rpc.Client
events

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论