RPC

什么是RPC

RPC(Remote Promote Call) 一 远程过程调用,即一种进程间的通信方式。通过网络通信调用不同的服务。允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。

image.png

RPC采用客户机/服务器模式,如下图:

image.png

用通俗易懂的语言描述就是:RPC允许跨机器、跨语言调用计算机程序方法。可以理解为:

  • 客户端 A 服务器上,调用 B 服务器上应用提供的函数/方法
  • RPC 由客户端发起,调用服务端的方法进行通信
  • 服务端处理后,通过同样的方式,再把结果返回给客户端

如下图
客户端发起: 1 2 3 4 5
服务端发起:6 7 8 9 10

image.png

在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。

image.png
由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

RPC 的核心

RPC 的核心点有两个:

  • 通信协议
  • 序列化

序列化和反序列化是一种把传输数据进行编码和解码的方式,常见的编解码方式有 JSON、Protobuf 等。

核心流程解析

  1. 客户端调用:服务调用方在调用服务时,一般进行相关初始化,通过配置文件/配置中心 获取服务端地址用户调用。
  2. 动态代理:服务调用者用的服务实际是远程服务的本地代理。
  3. 网络传输:将方法参数进行处理后,就要使用发起网络请求,使用tcp传输的就利用socket通信进行传输。其中请求的方式有同步阻塞/非阻塞。
  4. 序列化:毕竟是远程通信,需要将对象转化成二进制流进行传输。
  5. 服务端返回:服务端获取客户端请求的数据后,将返回值其序列化封装,通过netty进行数据返回,客户端在接受数据并解析,这就完成了一次rpc请求调用的全过程。


    常见RPC技术和框架

  6. 应用级的服务框架:Dubbo/Dubbox、ZeroICE、GRpc、Spring Boot/Spring Cloud。

  7. 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  8. 通信框架:MINA和Netty。

    Go 语言 RPC

    Go SDK中,内置了 net/rpc 包来实现 RPC。使用 encoding/gob 进行编解码,支持tcp或http数据传输方式。由于其他语言不支持gob编解码方式,所以使用 net/rpc 库实现的RPC方法没办法进行跨语言调用。

golang官方还提供了 net/rpc/jsonrpc 库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。

但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用。

net/rpc库

下面的例子演示一下如何使用golang官方的net/rpc库实现RPC方法,使用http作为RPC的载体,通过net/http包监听客户端连接请求。

服务端: rpc_server.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "net/http"
  7. "net/rpc"
  8. "os"
  9. )
  10. // 矩形结构体
  11. type Rect struct {}
  12. type Params struct {
  13. Width int
  14. Height int
  15. }
  16. // 面积
  17. func (r *Rect) Area(p Params, ret *int) error {
  18. *ret = p.Width * p.Height
  19. return nil
  20. }
  21. // 周长
  22. func (r *Rect) Perimeter(p Params, ret *int) error {
  23. *ret = (p.Width + p.Height) * 2
  24. return nil
  25. }
  26. func main() {
  27. // 注册RPC服务
  28. rpc.Register(new(Rect))
  29. // 采用http协议作为rpc载体
  30. rpc.HandleHTTP()
  31. lis, err := net.Listen("tcp", "127.0.0.1:8083")
  32. if err != nil {
  33. log.Fatalln("fatal error:", err)
  34. }
  35. fmt.Println(os.Stdout, "%s", "start connect\n")
  36. http.Serve(lis, nil)
  37. }

客户端: rpc_client.go

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/rpc"
  6. )
  7. type Params struct {
  8. Width int
  9. Height int
  10. }
  11. func main() {
  12. // 连接远程RPC服务
  13. conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8083")
  14. if err != nil {
  15. log.Fatal(err)
  16. }
  17. ret := 0
  18. err = conn.Call("Rect.Area", Params{50, 100}, &ret)
  19. if err != nil {
  20. log.Fatalln("error:", err)
  21. }
  22. fmt.Println("面积:", ret)
  23. err = conn.Call("Rect.Perimeter", Params{50, 100}, &ret)
  24. if err != nil {
  25. log.Fatalln("error:", err)
  26. }
  27. fmt.Println("周长:", ret)
  28. /*
  29. 打印结果
  30. 面积: 5000
  31. 周长: 300
  32. */
  33. }

说明

  1. # 启动服务端,运行后将会监听本地的8083端口
  2. go run rpc_server.go
  3. # 启动客户端,连接服务端并实现RPC方法调用
  4. go run rpc_client.go

net/rpc/jsonrpc库

  1. // server.go
  2. package main
  3. import (
  4. "fmt"
  5. "log"
  6. "net"
  7. "net/rpc"
  8. "net/rpc/jsonrpc"
  9. "os"
  10. )
  11. // 矩形结构体
  12. type Rect struct {}
  13. type Params struct {
  14. Width int
  15. Height int
  16. }
  17. // 面积
  18. func (r *Rect) Area(p Params, ret *int) error {
  19. *ret = p.Width * p.Height
  20. return nil
  21. }
  22. // 周长
  23. func (r *Rect) Perimeter(p Params, ret *int) error {
  24. *ret = (p.Width + p.Height) * 2
  25. return nil
  26. }
  27. func main() {
  28. // 注册RPC服务
  29. rpc.Register(new(Rect))
  30. // 采用http协议作为rpc载体
  31. rpc.HandleHTTP()
  32. lis, err := net.Listen("tcp", "127.0.0.1:8083")
  33. if err != nil {
  34. log.Fatalln("fatal error:", err)
  35. }
  36. fmt.Println(os.Stdout, "%s", "start connect\n")
  37. for {
  38. conn, err := lis.Accept() // 接收客户端连接请求
  39. if err != nil {
  40. continue
  41. }
  42. go func(conn net.Conn) { // 并发处理客户端请求
  43. fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
  44. jsonrpc.ServeConn(conn)
  45. }(conn)
  46. }
  47. }
  48. // client.go
  49. package main
  50. import (
  51. "fmt"
  52. "log"
  53. "net/rpc/jsonrpc"
  54. )
  55. type Params struct {
  56. Width int
  57. Height int
  58. }
  59. func main() {
  60. // 连接远程RPC服务
  61. conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8083")
  62. if err != nil {
  63. log.Fatal(err)
  64. }
  65. ret := 0
  66. err = conn.Call("Rect.Area", Params{50, 100}, &ret)
  67. if err != nil {
  68. log.Fatalln("error:", err)
  69. }
  70. fmt.Println("面积:", ret)
  71. err = conn.Call("Rect.Perimeter", Params{50, 100}, &ret)
  72. if err != nil {
  73. log.Fatalln("error:", err)
  74. }
  75. fmt.Println("周长:", ret)
  76. /*
  77. 打印结果
  78. 面积: 5000
  79. 周长: 300
  80. */
  81. }

说明

代码基本相似

  1. # 启动服务端,运行后将会监听本地的8083端口,
  2. # 当客户端启动成功连接后,服务端会打印 new client in coming
  3. go run server.go
  4. # 启动客户端,连接服务端并实现RPC方法调用
  5. go run client.go

总结

  • 远程过程调用(Remote Procedure Call,缩写为 RPC)
  • 可以将一些比较通用的场景抽象成微服务,然后供其他系统远程调用
  • RPC 可以基于HTTP协议 也可以基于TCP协议,基于HTTP协议的RPC像是我们访问网页一样(GET/POST/PUT/DELETE/UPDATE),大部分的RPC都是基于TPC协议的(因为基于传输层,效率稍高一些)
  • 基于TCP 的 RPC 工作过程
    • 客户端对请求的对象序列化
    • 客户端连接服务端,并将序列化的对象通过socket 传输给服务端,并等待接收服务端的响应
    • 服务端收到请求对象后将其反序列化还原客户端的对象
    • 服务端从请求对象中获取到请求的参数,然后执行对应的方法,得到返回结果
    • 服务端将其结果序列化并传给客户端,客户端得到响应结果对象后将其反序列化,得到响应结果

参考: