Go RPC入门指南:RPC的使用边界在哪里?如何实现跨语言调用?

Golang
444
0
0
2022-11-27
标签   gRPC

RPC

什么是RPC

  1. RPC的中文是“远程过程调用”,对应的英文全称是:Remote Procedure Call,可以简单理解为一个节点请求另一个节点提供的服务
  2. 理解“本地过程调用”可以更好的理解“远程过程调用”
  3. 知识点:RPC主要依赖于客户端与服务端建立socket链接;而HTTP REST实现通讯的代价比较高,这是RPC的一个优势体现。
  4. RPC详解看这里

为什么用RPC

就是因为无法在同一个进程内,或者无法在同一个服务器上通过本地调用的方式实现我们的需求。HTTP能满足需求但是不够高效,所以我们需要使用RPC。

RPC的优势

  1. RPC能够跨多种开发工具和平台
  2. RPC能够跨语言调用
  3. RPC能够提高系统的可扩展性,解耦,提高复用
  4. RPC相较于HTTP,传输效率更高,性能消耗更小,自带负载均衡策略,自动实现服务治理

RPC和HTTP对比

  1. RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。
  2. HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。
  3. RPC和HTTP的详细对别

RPC的使用边界

  1. 通过和HTTP的对比,我们倒推出RPC的边界:对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用。
  2. 上述这些都不适合RPC,不知道RPC不适合做什么,比知道RPC能做什么更重要。

RPC入门1:net/rpc

基本构成

  1. RPC的基本构成:服务端,客户端
  2. 服务端基本构成:结构体,请求结构体,响应结构体
  3. 客户端基本构成:请求结构体,响应结构体

代码示例

rpc_service.go

package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"net/rpc"
	"os"
)

type Arith struct {

}

//请求结构体
type ArithRequest struct {
	A int
	B int
}

//响应结构体
type ArithResponse struct {
	Pro int //乘积
	Quo int //商
	Rem int //余数
}

//乘积方法
func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{
	res.Pro = req.A * req.B
	return nil
}

//除法运算方法
func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{
	if req.B ==0 {
		return  errors.New("divide by zero")
	}
	res.Quo = req.A / req.B
	res.Rem = req.A % req.B
	return nil
}

func main()  {
	//注册rpc服务
	rpc.Register(new(Arith))
	//采用http协议作为rpc载体
	rpc.HandleHTTP()

	lis,err := net.Listen("tcp","127.0.0.1:8095")
	if err!=nil {
		log.Fatalln("fatal error:",err)
	}

	fmt.Fprintf(os.Stdout,"%s","start connection\n")

	//常规启动http服务
	http.Serve(lis,nil)
}
复制代码

rpc_client.go

package main

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

//算数运算请求结构体
type ArithRequest struct {
	A int
	B int
}

//响应结构体
type ArithResponse struct {
	Pro int //乘
	Quo int //商
	Rem int //余数
}

func main()  {
	conn,err := rpc.DialHTTP("tcp","127.0.0.1:8095")
	if err!=nil {
		log.Fatalln("dialing error:",err)
	}

	req := ArithRequest{10,20}
	var res  ArithResponse

	err = conn.Call("Arith.Multiply",req,&res) //乘法运算
	if err!=nil {
		log.Fatalln("arith error:",err)
	}
	fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)

	//除法运算
	err = conn.Call("Arith.Divide",req,&res)
	if err!=nil {
		log.Fatalln("arith error:",err)
	}
	fmt.Printf("%d / %d = %d 余数是:%d",req.A,req.B,res.Quo,res.Rem)
}
复制代码

运行结果

  1. 先启动服务端,再启动客户端连接服务端
//服务端console
start connection

//客户端console
10 * 20 = 200
10 / 20 = 0 余数是:10

复制代码

RPC入门2:net/rpc/jsonrpc

实现跨语言调用

jsonrpc_server.go

package main

import (
	"errors"
	"fmt"
	"log"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
)

type Arith struct {

}

//请求结构体
type ArithRequest struct {
	A int
	B int
}

//响应结构体
type ArithResponse struct {
	Pro int //乘积
	Quo int //商
	Rem int //余数
}

//乘积方法
func (this *Arith) Multiply(req ArithRequest,res *ArithResponse) error{
	res.Pro = req.A * req.B
	return nil
}

//除法运算方法
func (this *Arith) Divide(req ArithRequest,res *ArithResponse) error{
	if req.B ==0 {
		return  errors.New("divide by zero")
	}
	res.Quo = req.A / req.B
	res.Rem = req.A % req.B
	return nil
}

func main()  {
	//注册rpc服务
	rpc.Register(new(Arith))
	//采用http协议作为rpc载体
	rpc.HandleHTTP()

	lis,err := net.Listen("tcp","127.0.0.1:8096")
	if err!=nil {
		log.Fatalln("fatal error:",err)
	}

	fmt.Fprintf(os.Stdout,"%s","start connection\n")

	//接收客户端请求 并发处理 jsonrpc
	for {
		conn,err :=lis.Accept() //接收客户端连接请求
		if err!=nil {
			continue
		}

		//并发处理客户端请求
		go func(conn net.Conn) {
			fmt.Fprintf(os.Stdout,"%s","new client in coming\n")
			jsonrpc.ServeConn(conn)
		}(conn)
	}

	//常规启动http服务
	//http.Serve(lis,nil)
}
复制代码

jsonrpc_client.go

package main

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

//算数运算请求结构体
type ArithRequest struct {
	A int
	B int
}

//响应结构体
type ArithResponse struct {
	Pro int //乘
	Quo int //商
	Rem int //余数
}

func main()  {
	// 只有这里不一样
	conn,err := jsonrpc.Dial("tcp","127.0.0.1:8096")
	if err!=nil {
		log.Fatalln("dialing error:",err)
	}

	req := ArithRequest{9,2}
	var res  ArithResponse

	err = conn.Call("Arith.Multiply",req,&res) //乘法运算
	if err!=nil {
		log.Fatalln("arith error:",err)
	}
	fmt.Printf("%d * %d = %d\n",req.A,req.B,res.Pro)

	//除法运算
	err = conn.Call("Arith.Divide",req,&res)
	if err!=nil {
		log.Fatalln("arith error:",err)
	}
	fmt.Printf("%d / %d = %d 余数是:%d",req.A,req.B,res.Quo,res.Rem)
}
复制代码

运行结果

  1. 先启动服务端,再启动客户端连接服务端
//服务端console
start connection

//客户端console
9 * 2 = 18
9 / 2 = 4 余数是:1

//服务端console
new client in coming
复制代码

RPC入门3:go php跨语言调用

Go作为服务端,PHP作为客户端

jsonrpc_server.go:和入门2服务端的代码一样

jsonrpc_client.php

<?php


class JsonRPC
{

    private $conn;

    function __construct($host, $port)
    {
        $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
        if (!$this->conn) {
            return false;
        }
    }

    public function Call($method, $params)
    {
        if (!$this->conn) {
            return false;
        }
        $err = fwrite($this->conn, json_encode(array(
                'method' => $method,
                'params' => array($params),
                'id' => 0,
            )) . "\n");
        if ($err === false) {
            return false;
        }
        stream_set_timeout($this->conn, 0, 3000);
        $line = fgets($this->conn);
        if ($line === false) {
            return NULL;
        }
        return json_decode($line, true);
    }
}

$client = new JsonRPC("127.0.0.1", 8096);
$args = array('A' => 9, 'B' => 2);
$r = $client->Call("Arith.Multiply", $args);
printf("%d * %d = %d\n", $args['A'], $args['B'], $r['result']['Pro']);
$r = $client->Call("Arith.Divide", array('A' => 9, 'B' => 2));
printf("%d / %d, Quo is %d, Rem is %d\n", $args['A'], $args['B'], $r['result']['Quo'], $r['result']['Rem']);
复制代码

如何在本地启动PHP

运行结果

//本地启动PHP服务:http://127.0.0.1/jsonrpc_client.php,运行结果如下:
9 * 2 = 18 9 / 2, Quo is 4, Rem is 1
复制代码

参考博客

  • RPC相关博客,可以点击原文链接查看
  • Mac本地启动PHP,可以点击原文链接查看

名词解释

  • Thrift:是一种接口描述语言和二进制通讯协议,被当做RPC的框架来使用。

思考

如何优雅的使用RPC进行web开发