缘起
最近阅读 [Go微服务实战] (刘金亮, 2021.1)
本系列笔记拟采用golang练习之
gitee: https://gitee.com/ioly/learning.gooop
net/rpc
微服务中的进程间通信概述对于进程间通信的技术,开发者有多种选择。可以选择基于同步通信的通信机制,比如HTTP RESTful;也可以选择基于异步通信的方式,Go语言提供了标准的net/rpc包以支持异步。远程过程调用协议(Remote Procedure Call Protocol, RPC),是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
目标
- 使用net/rpc包写个时间请求rpc, 并测试并发性能.
设计
- TimeServer: 时间服务端, 将自身注册到rpc, 并提供GetTime服务
- TimeClient: 连接到时间服务器, 并远程调用GetTime服务。短连接模式,即总是在rpc调用后立即断开连接。
单元测试
net_rpc_test.go, 分别并发创建100/300/500/1000个时间客户端, 向服务器请求时间, 并统计失败次数和平均耗时
package net_rpcimport ("learning/gooop/net_rpc""sync""testing""time")func fnAssertTrue(t *testing.T, b bool, msg string) {if !b {t.Fatal(msg)}}type CallLog struct {done boolcost int64}func Test_NetRPC(t *testing.T) {server := new(net_rpc.TimeServer)err := server.Serve(3333)if err != nil {t.Fatal(err)}time.Sleep(100 * time.Millisecond)fnTestRpcCall := func(log *CallLog) {c := net_rpc.NewTimeClient("localhost:3333")t0 := time.Now().UnixNano()err, ret := c.GetTime()log.cost = time.Now().UnixNano() - t0log.done = err == nilif log.done {fnAssertTrue(t, ret > 0, "expecting ret>0")}}fnTestConcurrency := func(threads int) {logs := make([]*CallLog, threads)var g sync.WaitGroupfor i, _ := range logs {logs[i] = new(CallLog)n := ig.Add(1)go func() {fnTestRpcCall(logs[n])g.Done()}()}g.Wait()var failed, max, avg int64 = 0, 0, 0for _, it := range logs {if !it.done {failed++}if it.cost > max {max = it.cost}avg += it.cost}avg = avg / int64(threads)maxf := float64(max) / float64(time.Millisecond/time.Nanosecond)avgf := float64(avg) / float64(time.Millisecond/time.Nanosecond)t.Logf("threads=%d, failed=%d, max=%fms, avg=%fms", threads, failed, maxf, avgf)}fnTestConcurrency(100)fnTestConcurrency(300)fnTestConcurrency(500)fnTestConcurrency(1000)}
测试输出
脚趾头告诉我, 时间都花在net.dial上面了
$ go test -v net_rpc_test.go=== RUN Test_NetRPC2021/03/24 23:55:21 rpc.Register: method "Serve" has 2 input parameters; needs exactly threenet_rpc_test.go:75: threads=100, failed=0, max=50.962322ms, avg=42.961170msnet_rpc_test.go:75: threads=300, failed=0, max=45.608988ms, avg=30.233982msnet_rpc_test.go:75: threads=500, failed=0, max=99.810739ms, avg=81.164639msnet_rpc_test.go:75: threads=1000, failed=0, max=359.049068ms, avg=185.030143ms--- PASS: Test_NetRPC (0.66s)PASSok command-line-arguments 0.666s
TimeServer.go
时间服务端, 将自身注册到rpc, 并提供GetTime服务
package net_rpcimport ("fmt""net""net/rpc""time")type TimeServer intfunc (me *TimeServer) GetTime(_ int, t *int64) error {*t = time.Now().UnixNano()return nil}func (me *TimeServer) Serve(port int) error {addy, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", port))if err != nil {return err}err = rpc.Register(me)if err != nil {return err}inbound, err := net.ListenTCP("tcp", addy)if err != nil {return err}go rpc.Accept(inbound)return nil}
TimeClient.go
连接到时间服务器, 并远程调用GetTime服务。短连接模式,即总是在rpc调用后立即断开连接。
package net_rpcimport "net/rpc"type TimeClient struct {serverAddress string}func NewTimeClient(serverAddress string) *TimeClient {it := new(TimeClient)it.init(serverAddress)return it}func (me *TimeClient) init(serverAddress string) {me.serverAddress = serverAddress}func (me *TimeClient) GetTime() (error, int64) {client, err := rpc.Dial("tcp", me.serverAddress)if err != nil {return err, 0}defer client.Close()var t int64 = 0err = client.Call("TimeServer.GetTime", 1, &t)if err != nil {return err, 0}return nil, t}
(end)
