缘起

最近阅读 [Go微服务实战] (刘金亮, 2021.1)
本系列笔记拟采用golang练习之
gitee: https://gitee.com/ioly/learning.gooop

net/rpc

  1. 微服务中的进程间通信概述
  2. 对于进程间通信的技术,开发者有多种选择。
  3. 可以选择基于同步通信的通信机制,比如HTTP RESTful
  4. 也可以选择基于异步通信的方式,Go语言提供了标准的net/rpc包以支持异步。
  5. 远程过程调用协议(Remote Procedure Call Protocol, RPC),
  6. 是一种通过网络从远程计算机程序上请求服务,
  7. 而不需要了解底层网络技术的协议。

目标(Day 2)

  • Day 1的rpc测试是短连接,频繁dial和close非常影响吞吐,改为长连接版本看看

设计

  • TimeConnection:封装rpc.dial以提供长连接的rpc.Client句柄。 rpc.Client.Call方法是并发安全的, 因此允许我们使用共享的长连接。
  • ITimeClientV2: 时间客户端接口
  • tTimeClientV2:时间客户端的实现, 持有长连接的rpc.Client句柄,进行远程GetTime调用

单元测试

  • common.go,封装单元测试的通用代码 ```go package net_rpc

import “testing”

func fnAssertTrue(t *testing.T, b bool, msg string) { if !b { t.Fatal(msg) } }

type CallLog struct { done bool cost int64 }

  1. - **net_rpc_v2_test.go**
  2. - 使用TimeConnection提供的共享的长连接
  3. - 并发100/300/500/1000/10000/50000rpc调用
  4. - 统计失败次数和平均耗时
  5. ```go
  6. package net_rpc
  7. import (
  8. "learning/gooop/net_rpc"
  9. "sync"
  10. "testing"
  11. "time"
  12. )
  13. func Test_NetRPC_V2(t *testing.T) {
  14. server := new(net_rpc.TimeServer)
  15. err := server.Serve(3333)
  16. if err != nil {
  17. t.Fatal(err)
  18. }
  19. time.Sleep(100 * time.Millisecond)
  20. fnTestRpcCall := func(client net_rpc.ITimeClientV2, log *CallLog) {
  21. t0 := time.Now().UnixNano()
  22. err, ret := client.GetTime()
  23. log.cost = time.Now().UnixNano() - t0
  24. log.done = err == nil
  25. if log.done {
  26. fnAssertTrue(t, ret > 0, "expecting ret>0")
  27. }
  28. }
  29. fnTestConcurrency := func(threads int) {
  30. logs := make([]*CallLog, threads)
  31. for i, _ := range logs {
  32. logs[i] = new(CallLog)
  33. }
  34. var g sync.WaitGroup
  35. conn := new(net_rpc.TimeConnection)
  36. _ = conn.Connect("localhost:3333", func(client net_rpc.ITimeClientV2) error {
  37. for i, _ := range logs {
  38. n := i
  39. g.Add(1)
  40. go func() {
  41. fnTestRpcCall(client, logs[n])
  42. g.Done()
  43. }()
  44. }
  45. g.Wait()
  46. return nil
  47. })
  48. var failed, max, avg int64 = 0, 0, 0
  49. for _, it := range logs {
  50. if !it.done {
  51. failed++
  52. }
  53. if it.cost > max {
  54. max = it.cost
  55. }
  56. avg += it.cost
  57. }
  58. avg = avg / int64(threads)
  59. maxf := float64(max) / float64(time.Millisecond/time.Nanosecond)
  60. avgf := float64(avg) / float64(time.Millisecond/time.Nanosecond)
  61. t.Logf("threads=%d, failed=%d, max=%fms, avg=%fms", threads, failed, maxf, avgf)
  62. }
  63. fnTestConcurrency(100)
  64. fnTestConcurrency(300)
  65. fnTestConcurrency(500)
  66. fnTestConcurrency(1000)
  67. fnTestConcurrency(10000)
  68. fnTestConcurrency(50000)
  69. }

测试输出

比Day 1的短连接模式快一个数量级

  1. $ go test -v *.go -test.run Test_NetRPC_V2
  2. === RUN Test_NetRPC_V2
  3. 2021/03/25 13:51:55 rpc.Register: method "Serve" has 2 input parameters; needs exactly three
  4. net_rpc_v2_test.go:71: threads=100, failed=0, max=1.715795ms, avg=1.179659ms
  5. net_rpc_v2_test.go:71: threads=300, failed=0, max=6.225059ms, avg=4.792163ms
  6. net_rpc_v2_test.go:71: threads=500, failed=0, max=10.110459ms, avg=5.502403ms
  7. net_rpc_v2_test.go:71: threads=1000, failed=0, max=17.945680ms, avg=8.392062ms
  8. net_rpc_v2_test.go:71: threads=10000, failed=0, max=153.765575ms, avg=85.053883ms
  9. net_rpc_v2_test.go:71: threads=50000, failed=0, max=709.802299ms, avg=299.928400ms
  10. --- PASS: Test_NetRPC_V2 (1.02s)
  11. PASS
  12. ok command-line-arguments 1.031s

TimeConnection.go

  • 封装rpc.dial以提供长连接的rpc.Client句柄。
  • rpc.Client.Call方法是并发安全的, 因此允许我们使用共享的长连接。 ```go package net_rpc

import “net/rpc”

type TimeConnection int

func (me *TimeConnection) Connect(serverAddress string, action func(client ITimeClientV2) error) error { conn, err := rpc.Dial(“tcp”, serverAddress) if err != nil { return err } defer conn.Close()

  1. return action(newTimeClientV2(conn))

}

  1. <a name="VZP4a"></a>
  2. # ITimeClientV2
  3. 时间客户端接口
  4. ```go
  5. package net_rpc
  6. type ITimeClientV2 interface {
  7. GetTime() (error, int64)
  8. }

tTimeClientV2.go

持有长连接的rpc.Client指针,进行远程GetTime调用

  1. package net_rpc
  2. import "net/rpc"
  3. type tTimeClientV2 struct {
  4. client *rpc.Client
  5. }
  6. func newTimeClientV2(client *rpc.Client) ITimeClientV2 {
  7. return &tTimeClientV2{ client, }
  8. }
  9. func (me *tTimeClientV2) GetTime() (error, int64) {
  10. var t int64 = 0
  11. err := me.client.Call("TimeServer.GetTime", 1, &t)
  12. if err != nil {
  13. return err, 0
  14. }
  15. return nil, t
  16. }

(end)