缘起

最近阅读 [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. 而不需要了解底层网络技术的协议。

目标

  • 使用net/rpc包写个时间请求rpc, 并测试并发性能.

设计

  • TimeServer: 时间服务端, 将自身注册到rpc, 并提供GetTime服务
  • TimeClient: 连接到时间服务器, 并远程调用GetTime服务。短连接模式,即总是在rpc调用后立即断开连接。

单元测试

net_rpc_test.go, 分别并发创建100/300/500/1000个时间客户端, 向服务器请求时间, 并统计失败次数和平均耗时

  1. package net_rpc
  2. import (
  3. "learning/gooop/net_rpc"
  4. "sync"
  5. "testing"
  6. "time"
  7. )
  8. func fnAssertTrue(t *testing.T, b bool, msg string) {
  9. if !b {
  10. t.Fatal(msg)
  11. }
  12. }
  13. type CallLog struct {
  14. done bool
  15. cost int64
  16. }
  17. func Test_NetRPC(t *testing.T) {
  18. server := new(net_rpc.TimeServer)
  19. err := server.Serve(3333)
  20. if err != nil {
  21. t.Fatal(err)
  22. }
  23. time.Sleep(100 * time.Millisecond)
  24. fnTestRpcCall := func(log *CallLog) {
  25. c := net_rpc.NewTimeClient("localhost:3333")
  26. t0 := time.Now().UnixNano()
  27. err, ret := c.GetTime()
  28. log.cost = time.Now().UnixNano() - t0
  29. log.done = err == nil
  30. if log.done {
  31. fnAssertTrue(t, ret > 0, "expecting ret>0")
  32. }
  33. }
  34. fnTestConcurrency := func(threads int) {
  35. logs := make([]*CallLog, threads)
  36. var g sync.WaitGroup
  37. for i, _ := range logs {
  38. logs[i] = new(CallLog)
  39. n := i
  40. g.Add(1)
  41. go func() {
  42. fnTestRpcCall(logs[n])
  43. g.Done()
  44. }()
  45. }
  46. g.Wait()
  47. var failed, max, avg int64 = 0, 0, 0
  48. for _, it := range logs {
  49. if !it.done {
  50. failed++
  51. }
  52. if it.cost > max {
  53. max = it.cost
  54. }
  55. avg += it.cost
  56. }
  57. avg = avg / int64(threads)
  58. maxf := float64(max) / float64(time.Millisecond/time.Nanosecond)
  59. avgf := float64(avg) / float64(time.Millisecond/time.Nanosecond)
  60. t.Logf("threads=%d, failed=%d, max=%fms, avg=%fms", threads, failed, maxf, avgf)
  61. }
  62. fnTestConcurrency(100)
  63. fnTestConcurrency(300)
  64. fnTestConcurrency(500)
  65. fnTestConcurrency(1000)
  66. }

测试输出

脚趾头告诉我, 时间都花在net.dial上面了

  1. $ go test -v net_rpc_test.go
  2. === RUN Test_NetRPC
  3. 2021/03/24 23:55:21 rpc.Register: method "Serve" has 2 input parameters; needs exactly three
  4. net_rpc_test.go:75: threads=100, failed=0, max=50.962322ms, avg=42.961170ms
  5. net_rpc_test.go:75: threads=300, failed=0, max=45.608988ms, avg=30.233982ms
  6. net_rpc_test.go:75: threads=500, failed=0, max=99.810739ms, avg=81.164639ms
  7. net_rpc_test.go:75: threads=1000, failed=0, max=359.049068ms, avg=185.030143ms
  8. --- PASS: Test_NetRPC (0.66s)
  9. PASS
  10. ok command-line-arguments 0.666s

TimeServer.go

时间服务端, 将自身注册到rpc, 并提供GetTime服务

  1. package net_rpc
  2. import (
  3. "fmt"
  4. "net"
  5. "net/rpc"
  6. "time"
  7. )
  8. type TimeServer int
  9. func (me *TimeServer) GetTime(_ int, t *int64) error {
  10. *t = time.Now().UnixNano()
  11. return nil
  12. }
  13. func (me *TimeServer) Serve(port int) error {
  14. addy, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", port))
  15. if err != nil {
  16. return err
  17. }
  18. err = rpc.Register(me)
  19. if err != nil {
  20. return err
  21. }
  22. inbound, err := net.ListenTCP("tcp", addy)
  23. if err != nil {
  24. return err
  25. }
  26. go rpc.Accept(inbound)
  27. return nil
  28. }

TimeClient.go

连接到时间服务器, 并远程调用GetTime服务。短连接模式,即总是在rpc调用后立即断开连接。

  1. package net_rpc
  2. import "net/rpc"
  3. type TimeClient struct {
  4. serverAddress string
  5. }
  6. func NewTimeClient(serverAddress string) *TimeClient {
  7. it := new(TimeClient)
  8. it.init(serverAddress)
  9. return it
  10. }
  11. func (me *TimeClient) init(serverAddress string) {
  12. me.serverAddress = serverAddress
  13. }
  14. func (me *TimeClient) GetTime() (error, int64) {
  15. client, err := rpc.Dial("tcp", me.serverAddress)
  16. if err != nil {
  17. return err, 0
  18. }
  19. defer client.Close()
  20. var t int64 = 0
  21. err = client.Call("TimeServer.GetTime", 1, &t)
  22. if err != nil {
  23. return err, 0
  24. }
  25. return nil, t
  26. }

(end)