Golang的序列化-RPC和GRPC

                               作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

 

一.RPC概述

1>.什么是RPC

  RPC(Remote Procedure Call Protocol),是远程过程调用的缩写,通俗的说就是调用远处(一般指不同的主机)的一个函数。

2>.为什么微服务需要RPC

  我们使用微服务化的一个好处就是,不限定服务的提供方使用什么技术选型,能够实现公司跨团队的技术解耦。

  这样的话,如果没有统一的服务框架,RPC框架,各个团队的服务提供方就需要各自实现一套序列化、反序列化、网络框架、连接池、收发线程、超时处理、状态机等“业务之外”的重复技术劳动,造成整体的低效。

  所以,统一RPC框架把上述“业务之外”的技术劳动统一处理,是服务化首要解决的问题。

 

二.RPC入门案例

  在互联网时代,RPC已经和IPC(进程间通信)一样成为一个不可或缺的基础构件。因此Go语言的标准库也提供了一个简单的RPC实现,我们将以此为入口学习RPC的常见用法。

1>.RPC的服务端

package main

import (
    "fmt"
    "net"
    "net/rpc"
)

type Zabbix struct{}

/**
定义成员方法:
    第一个参数是传入参数.
    第二个参数必须是传出参数(引用类型).

Go语言的RPC规则:
    方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法。

温馨提示:
    当调用远程函数之后,如果返回的错误不为空,那么传出参数为空。
*/
func (Zabbix) MonitorHosts(name string, response *string) error {
    *response = name + "主机监控中..."
    return nil
}

func main() {
    /**
    进程间交互有很多种,比如基于信号,共享内存,管道,套接字等方式.

    1>.rpc基于是TCP的,因此我们需要先开启监听端口
    */
    listener, err := net.Listen("tcp", ":8888")
    if err != nil {
        fmt.Println("开启监听器失败,错误原因: ", err)
        return
    }
    defer listener.Close()
    fmt.Println("服务启动成功...")

    /**
    2>.接受链接,即接受传输的数据
    */
    conn, err := listener.Accept()
    if err != nil {
        fmt.Println("建立链接失败...")
        return
    }
    defer conn.Close()
    fmt.Println("建立连接: ", conn.RemoteAddr())
    /**
    3>.注册rpc服务,维护一个hash表,key值是服务名称,value值是服务的地址。服务器有很多函数,希望被调用的函数需要注册到RPC上。
    以下是RegisterName的函数签名:
        func RegisterName(name string, rcvr interface{}) error
    以下是对函数签名相关参数的说明:
        name:
            指的是服务名称。
        rcvr:
            指的是结构体对象(这个结构体必须含有成员方法)。
    */
    rpc.RegisterName("zabbix", new(Zabbix))

    /**
    4>.链接的处理交给RCP框架处理,即rpc调用,并返回执行后的数据,其工作原理大致分为以下3个步骤:
        (1)read,获取服务名称和方法名,获取请求数据;
        (2)调用对应服务里面的方法,获取传出数据;
        (3)write,把数据返回给client;
    */
    rpc.ServeConn(conn)
}

2>.RPC的客户端

package main

import (
    "fmt"
    "net"
    "net/rpc"
)

func main() {
    /**
    1>.首选是通过rpc.Dial拨号RPC服务

    温馨提示:
        默认数据传输过程中编码方式是gob,可以选择json
    */
    conn, err := net.Dial("tcp", "127.0.0.1:8888")
    if err != nil {
        fmt.Println("链接服务器失败")
        return
    }
    defer conn.Close()

    /**
    2>.把conn和rpc进行绑定
    */
    client := rpc.NewClient(conn)

    /**
    3>.然后通过client.Call调用具体的RPC方法。其中Call函数的签名如下所示:
            func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
        以下是对函数签名的相关参数进行补充说明:
            serviceMethod:
                用点号(.)链接的RPC服务名字和方法名字
            args:
                指定输入参数
            reply:
                指定输出参数接收的
    */
    var data string
    err = client.Call("zabbix.MonitorHosts", "Nginx", &data)
    if err != nil {
        fmt.Println("远程接口调用失败,错误原因: ", err)
        return
    }
    fmt.Println(data)
}

Golang的序列化-RPC和GRPC

 

三.跨语言的RPC

  标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。跨语言是互联网时代RPC的一个首要条件,这里我们再来实现一个跨语言的RPC。

  得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。这里我们将尝试通过官方自带的net
/rpc/jsonrpc扩展实现一个跨语言RPC。

1>.RPC的服务端

package main

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

type OpenFalcon struct{}

/**
定义成员方法:
    第一个参数是传入参数.
    第二个参数必须是传出参数(引用类型).

Go语言的RPC规则:
    方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法。

温馨提示:
    当调用远程函数之后,如果返回的错误不为空,那么传出参数为空。
*/
func (OpenFalcon) MonitorHosts(name string, response *string) error {
    *response = name + "主机监控中..."
    return nil
}

func main() {
    /**
    进程间交互有很多种,比如基于信号,共享内存,管道,套接字等方式.

    1>.rpc基于是TCP的,因此我们需要先开启监听端口
    */
    listener, err := net.Listen("tcp", ":8888")
    if err != nil {
        fmt.Println("开启监听器失败,错误原因: ", err)
        return
    }
    defer listener.Close()
    fmt.Println("服务启动成功...")

    /**
    2>.接受链接,即接受传输的数据
    */
    conn, err := listener.Accept()
    if err != nil {
        fmt.Println("建立链接失败...")
        return
    }
    defer conn.Close()
    fmt.Println("建立连接: ", conn.RemoteAddr())
    /**
    3>.注册rpc服务,维护一个hash表,key值是服务名称,value值是服务的地址。服务器有很多函数,希望被调用的函数需要注册到RPC上。
    以下是RegisterName的函数签名:
        func RegisterName(name string, rcvr interface{}) error
    以下是对函数签名相关参数的说明:
        name:
            指的是服务名称。
        rcvr:
            指的是结构体对象(这个结构体必须含有成员方法)。
    */
    rpc.RegisterName("open_falcon", new(OpenFalcon))

    /**
    4>.链接的处理交给RCP框架处理,即rpc调用,并返回执行后的数据,其工作原理大致分为以下3个步骤:
        (1)read,获取服务名称和方法名,获取请求数据;
        (2)调用对应服务里面的方法,获取传出数据;
        (3)write,把数据返回给client;
    */
    jsonrpc.ServeConn(conn)
}

2>.RPC的客户端

package main

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

func main() {
    /**
    首选是通过rpc.Dial拨号RPC服务

    温馨提示:
        默认数据传输过程中编码方式是gob,可以选择json,需要导入"net/rpc/jsonrpc"包。
    */
    conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8888")
    if err != nil {
        fmt.Println("链接服务器失败")
        return
    }
    defer conn.Close()

    var data string

    /**
    其中Call函数的签名如下所示:
        func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
    以下是对函数签名的相关参数进行补充说明:
        serviceMethod:
            用点号(.)链接的RPC服务名字和方法名字
        args:
            指定输入参数
        reply:
            指定输出参数接收的
    */
    err = conn.Call("open_falcon.MonitorHosts", "Httpd", &data)
    if err != nil {
        fmt.Println("远程接口调用失败,错误原因: ", err)
        return
    }
    fmt.Println(data)
}

Golang的序列化-RPC和GRPC

 

四.ProtoBuf

  博主推荐阅读:
    https://www.cnblogs.com/yinzhengjie2020/p/12741943.html

 

五.GRPC框架

1>.什么是GRPC

  GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。

  GRPC是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供C,Java和Go语言版本,分别是:grpc, grpc-java, grpc-go. 其中C版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

  GRPC基于HTTP/2标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

  博主推荐阅读:
    GRPC 官方文档中文版:
      http://doc.oschina.net/grpc?t=60133
    GRPC官网:
      https://grpc.io

2>.安装grpc环境

C:\Users\yinzhengjie>go get -u -v google.golang.org/grpc

 Golang的序列化-RPC和GRPC

3>.基于protobuf编写Grpc服务

////protobuf默认支持的版本是2.0,现在一般使用3.0版本,所以需要手动指定版本号
//c语言的编程风格
syntax = "proto3";

//指定包名
package pb;

//定义传输数据的格式
message People{
    string name = 1; //1表示表示字段是1   数据库中表的主键id等于1,主键不能重复,标示位数据不能重复
    //标示位不能使用19000 -19999  系统预留位
    int32 age = 2;

    //结构体嵌套
    student s = 3;
    //使用数组/切片
    repeated string phone = 4;

    //oneof的作用是多选一
    oneof data{
        int32 score = 5;
        string city = 6;
        bool good = 7;
    }
}

//oneof  c语言中的联合体

message student{
    string name = 1;
    int32 age = 6;
}

//通过定义服务,然后借助框架,帮助实现部分的rpc代码
service Hello{
    rpc World(student)returns(student);
}
grpc.proto

相关文章: