Несколько способов реализации RPC в golang

Go

что такое РПЦ

Удаленный вызов процедур (RPC) — это компьютерный протокол связи. Протокол позволяет программе, работающей на одном компьютере, вызывать подпрограмму на другом компьютере без необходимости дополнительного программирования этого взаимодействия программистом. Если задействованное программное обеспечение использует объектно-ориентированное программирование, то удаленный вызов процедуры также можно назвать удаленным вызовом или удаленным вызовом метода.Википедия: Удаленный вызов процедур

Описано простым языком: RPC позволяет вызывать методы компьютерных программ на разных машинах и языках. Например, я написал метод getUserInfo для получения информации о пользователе на языке go и развернул программу go на сервере Alibaba Cloud Теперь у меня есть проект php, развернутый в облаке Tencent, и мне нужно вызвать метод getUserInfo для golang, чтобы получить Информация о том, что процесс php, вызывающий метод go на разных машинах, является вызовом RPC.

Как реализовать RPC в голанге

Реализация RPC в Голан очень прост, поддерживается упакованными официальными библиотеками и некоторыми сторонними библиотеками. Go RPC можно использовать TCP или HTTP для передачи данных и может использовать несколько типов кодеков для передачи данных. Голанг официальныйnet/rpcиспользование библиотекиencoding/gobкодек, поддержкаtcpилиhttpСпособ передачи данных, так как другие языки не поддерживаютсяgobкодек, поэтому используйтеnet/rpcМетоды RPC, реализованные библиотекой, нельзя назвать межъязыковыми.

Чиновник также предоставил golangnet/rpc/jsonrpcБиблиотека реализует методы RPC, а JSON RPC использует JSON для кодирования и декодирования данных, поддерживая, таким образом, межъязыковые вызовы. Однако текущая библиотека jsonrpc реализована на основе протокола tcp и временно не поддерживает использование http для передачи данных.

В дополнение к библиотеке rpc, официально предоставляемой golang, существует множество сторонних библиотек, обеспечивающих поддержку реализации RPC в golang.Большинство сторонних библиотек rpc реализованы с использованиемprotobufКодирование и декодирование данных, согласноprotobufФайл объявления автоматически генерирует определение метода rpc и код регистрации службы, и очень удобно вызывать службу rpc в golang.

библиотека net/rpc

В следующем примере показано, как использовать официальный сайт golang.net/rpcБиблиотека реализует методы RPC, используяhttpКак носитель РПК, черезnet/httpПакет прослушивает запросы на подключение клиента.

$GOPATH/src/test/rpc/rpc_server.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.Register(new(Arith)) // 注册rpc服务
rpc.HandleHTTP() // 采用http协议作为rpc载体

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")

http.Serve(lis, nil)
}

После запуска вышеуказанной серверной программы она будет прослушивать локальный порт 8095. Мы можем реализовать клиентскую программу, которая подключается к серверу и реализует вызовы методов RPC.

$GOPATH/src/test/rpc/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("dailing 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, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

библиотека net/rpc/jsonrpc

В приведенном выше примере мы демонстрируем использованиеnet/rpcПроцесс реализации RPC, но нет возможности вызвать метод RPC, реализованный в приведенном выше примере, на других языках. Итак, в следующем примере мы демонстрируем использованиеnet/rpc/jsonrpcБиблиотека реализует методы RPC, а методы RPC, реализованные на этом способе поддерживают перекрестные вызовы.

$GOPATH/src/test/rpc/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.Register(new(Arith)) // 注册rpc服务

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")

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)
}
}

После запуска приведенной выше серверной программы он будет контролировать локальный порт 8096 и обработал запрос на соединение TCP от клиента. Мы можем использовать Golang Allurage клиентскую программу, которая соединяет сервер и RPC вызовы.

$GOPATH/src/test/rpc/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("dailing 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, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

библиотека protorpc

Чтобы реализовать межъязыковые вызовы, при реализации метода RPC в golang мы должны выбрать метод кодирования и декодирования данных на разных языках, такой как JSON, указанный выше.jsonrpcЭто требование может быть выполнено, но есть и некоторые недостатки, такие как отсутствие поддержки http-передачи и невысокая производительность кодирования и декодирования данных. Итак, некоторые сторонние библиотеки rpc предпочитают использоватьprotobufВыполнение кодирования и декодирования данных, а также предоставление некоторых функций автоматической генерации регистрационного кода службы. В следующем примере мы используемprotobufопределять методы RPC и их параметры запроса-ответа, а также использовать сторонниеprotorpcбиблиотека для создания регистрационного кода службы RPC.

Во-первых, вам нужно установитьprotobufиprotocИсполняемая команда, вы можете обратиться к этой статье:краткое руководство по protobuf

Затем мы пишем прото-файл, определяющий реализуемый метод RPC и связанные с ним параметры.

$GOPATH/src/test/rpc/pb/arith.proto

syntax = "proto3";
package pb;

// 算术运算请求结构
message ArithRequest {
int32 a = 1;
int32 b = 2;
}

// 算术运算响应结构
message ArithResponse {
int32 pro = 1; // 乘积
int32 quo = 2; // 商
int32 rem = 3; // 余数
}

// rpc方法
service ArithService {
rpc multiply (ArithRequest) returns (ArithResponse); // 乘法运算方法
rpc divide (ArithRequest) returns (ArithResponse); // 除法运算方法
}

Далее нам нужно определитьarith.protoфайл для создания кода службы RPC.
Первая установкаprotorpcБиблиотеки:go get github.com/chai2010/protorpc
затем используйтеprotocИнструмент генерирует код:protoc --go_out=plugin=protorpc=. arith.proto
воплощать в жизньprotocПосле команды сarith.protoФайл создается в том же каталоге, что и файлarith.pb.goфайл, который содержит код для определения методов RPC и регистрации службы.

На основе сгенерированногоarith.pb.goКод Давайте реализуем сервер rpc

$GOPATH/src/test/rpc/protorpc_server.go

package main

import (
"errors"
"test/rpc/pb"
)

// 算术运算结构体
type Arith struct {
}

// 乘法运算方法
func (this *Arith) Multiply(req *pb.ArithRequest, res *pb.ArithResponse) error {
res.Pro = req.GetA() * req.GetB()
return nil
}

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

func main() {
pb.ListenAndServeArithService("tcp", "127.0.0.1:8097", new(Arith))
}

Запуск вышеуказанной программы будет прослушивать локальный порт 8097 и получать TCP-соединения от клиентов.

на основеariti.pb.goЗатем реализуйте клиентскую программу.

$GOPATH/src/test/protorpc_client.go

package main

import (
"fmt"
"log"
"test/rpc/pb"
)

func main() {
conn, err := pb.DialArithService("tcp", "127.0.0.1:8097")
if err != nil {
log.Fatalln("dailing error: ", err)
}
defer conn.Close()

req := &pb.ArithRequest{9, 2}

res, err := conn.Multiply(req)
if err != nil {
log.Fatalln("arith error: ", err)
}
fmt.Printf("%d * %d = %d\n", req.GetA(), req.GetB(), res.GetPro())

res, err = conn.Divide(req)
if err != nil {
log.Fatalln("arith error ", err)
}
fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
}

Как вызывать методы Golang RPC на разных языках

Для трех приведенных выше примеров мы используемnet/rpc,net/rpc/jsonrpc,protorpcРеализовал сервер RPC в golang и дал соответствующий пример вызова RPC клиента golang, поскольку JSON и protobuf поддерживают несколько языков, поэтому используйтеjsonrpcиprotorpcМы можем вызвать реализованный метод RPC на других языках. Ниже приведена клиентская программа php, которая вызывает метод RPC на стороне сервера, реализованный jsonrpc, через соединение через сокет.

$PHPROOT/jsonrpc.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']);

Другие библиотеки RPC

В дополнение к трем способам реализации RPC в golang, упомянутым выше, есть и другие библиотеки rpc, предоставляющие аналогичные функции.Более известная из них — grpc с открытым исходным кодом от Google, но первоначальная установка grpc сопряжена с трудностями, поэтому я не буду продолжать. здесь.Представлено, и те, кому интересно, могут узнать об этом сами.

использованная литература