一、RPC介绍及原理
RPC(Remote Procedure Call)翻译过来是远程过程调度,一般应用在微服务中,用于多个服务间进行通信,也是一种网络通信协议
rpc的方言(协议):tcp(常用)、http2(grpc框架使用这种)、http、udp ...
rpc的语义(序列化):json、xml、protobuf ...
rpc通讯流程:
相互建立连接协议(自定义,常用tcp),连接可以是短连接也可以长连接
server-A 通过Rpc框架根据 ip,port,method等信息通过 rpcClient将请求封装后,传递给server-B【信息会序列化】
Server-B 收到请求后对信息反序列化,恢复成请求的表达式,找到对应的方法进行调用,然后得到返回值
server-B 把返回值发送回 server-A ,也需要经过序列化的方式发送,server-A 反序列化给应用
使用rpc需注意的问题:
根据服务的情况知道它的压力上限,做好限流
调用服务异常时,要考虑降级、慎重考虑重试 等措施
核心的服务不能强依赖于非核心的服务,否则一旦非核心的服务崩了,会导致核心服务出现异常
go底层实现 rpc 原理:包在 net\rpc 下
二、rpc和http的区别
-
调用方式不同
http通过url进行调用,rpc通过函数/方法进行调用
-
参数传递不同
http通过在url或者body中传参,rpc通过函数/方法的参数进行传参
-
性能方面
rpc通常比http更快,http基于tcp,rpc可使用tcp或者http。虽然核心都是tcp通信,但是比http少了很多臃肿的封装;而且rpc会有一些优化的技术,如:连接池、批量请求等等
-
应用场景
http一般应用在浏览器或其他客户端与应用程序(服务端)之间的通信,提供如 RESTful 风格的API服务
rpc一般应用在分布式微服务系统中,对部署在不同机器上的服务进行通信
三、go实现rpc通信
1. rpc
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
type User struct {
Id int
Name string
Pass string
}
type UserReq struct {
Name string
Pass string
}
type UserResp struct {
Code int
Msg string
Data *User
}
func main() {
conn, err := rpc.DialHTTP("tcp", ":8000")
if err != nil {
log.Fatal(err)
}
var resp UserResp
// 测试:发送一个正确的信息
err = conn.Call("User.Login", UserReq{
Name: "0000",
Pass: "0000",
}, &resp)
if err != nil {
log.Fatalln(err)
}
fmt.Println(resp, resp.Data) // {200 查询成功 0xc000180a20} &{0 0000 0000}
// 测试:发送一个错误的信息
err = conn.Call("User.Login", UserReq1{
Name: "1234",
Pass: "1111",
}, &resp)
if err != nil {
log.Fatalln(err)
}
fmt.Println(resp, resp.Data) // {404 数据不存在 0xc000180a20} &{0 0000 0000}
}
服务端
package main
import (
"log"
"net/http"
"net/rpc"
)
type User struct {
Id int
Name string
Pass string
}
type UserReq struct {
Name string
Pass string
}
type UserResp struct {
Code int
Msg string
Data *User
}
// 定义数据源,实际会去数据库查询
var users = map[string]*User{
"0000": {0, "0000", "0000"},
"1111": {1, "1111", "1111"},
}
func main() {
// 创建服务
server := new(User)
// 注册服务
err := rpc.Register(server)
if err != nil {
log.Panicln("服务注册失败")
}
// 绑定服务
rpc.HandleHTTP()
// 启动服务
if err = http.ListenAndServe(":8000", nil); err != nil {
log.Panicln("服务启动失败")
}
log.Panicln("服务启动成功")
}
func (u *User) Login(req UserReq, resp *UserResp) error {
user, ok := users[req.Name]
if !ok {
// 这里注意,不能写成 resp = &UserResp{}
*resp = &UserResp{
Code: 404,
Msg: "数据不存在",
Data: nil,
}
return nil
}
if user.Pass != req.Pass {
*resp = UserResp{
Code: 500,
Msg: "密码不正确",
Data: nil,
}
return nil
}
*resp = UserResp{
Code: 200,
Msg: "查询成功",
Data: user,
}
return nil
}
2. json-rpc
因为go底层是使用GOB编程的,用的 encoding/gob ,有些语言是不支持的,所以如果要跨语言通信,建议rpc的语义使用万金油:json
PS:go自带 net/rpc/jsonrpc 包,但是是1.0的版本,不能与2.0的版本通信
客户端:
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
type User struct {
Id int
Name string
Pass string
}
type UserReq struct {
Name string
Pass string
}
type UserResp struct {
Code int
Msg string
Data *User
}
func main() {
conn, err := jsonrpc.Dial("tcp", "127.0.0.1:9503")
if err != nil {
log.Fatal(err)
}
var resp UserResp
err = conn.Call("User.Login", UserReq{
Name: "0000",
Pass: "0000",
}, &resp)
if err != nil {
log.Fatalln(err)
}
fmt.Println(resp, resp.Data) // {200 查询成功 0xc000090810} &{0 0000 0000}
}
服务端
package main
import (
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type User struct {
Id int
Name string
Pass string
}
type UserReq struct {
Name string
Pass string
}
type UserResp struct {
Code int
Msg string
Data *User
}
var users = map[string]*User{
"0000": {0, "0000", "0000"},
"1111": {1, "1111", "1111"},
}
func main() {
// 创建服务
server := new(User)
// 注册服务
if err := rpc.Register(server); err != nil {
log.Panicln("服务注册失败")
}
listen, err := net.Listen("tcp", ":8000")
if err != nil {
log.Panicln(err)
}
for {
conn, err := listen.Accept()
if err != nil {
continue
}
go func() {
// 使用json方式通信并处理
jsonrpc.ServeConn(conn)
}()
}
}
func (u *User) Login(req UserReq, resp *UserResp) error {
user, ok := users[req.Name]
if !ok {
*resp = UserResp{
Code: 404,
Msg: "数据不存在",
Data: nil,
}
return nil
}
if user.Pass != req.Pass {
*resp = UserResp{
Code: 500,
Msg: "密码不正确",
Data: nil,
}
return nil
}
*resp = UserResp{
Code: 200,
Msg: "查询成功",
Data: user,
}
return nil
}
四、跨语言通信:go - hyperf(PHP协程框架)
前言
-
因为go自带的jsonrpc包是1.0版本,hyperf的 json-rpc 组件包是2.0的版本,所以需要给go先安装一个jsonrpc2.0的版本
我这里使用到了第三方库,也可以使用别的,直接去github上搜就可以
go get -u github.com/iloveswift/go-jsonrpc
-
hyperf 需要安装 json-rpc 组件包及进行一些配置,直接参考官方文档即可:https://hyperf.wiki/3.0/#/zh-cn/json-rpc
下边案例只放了核心业务代码
案例一. go 客户端;hyperf 服务端
go
package main
import (
"fmt"
jrpc "github.com/iloveswift/go-jsonrpc"
)
type Mem struct {
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
type MemReq struct {
UserId []int `json:"user_id"` // 请求单条数据可以直接定义为 int
}
type MemResp struct {
Code int
Msg string
Data []Mem
}
func main() {
c, _ := jrpc.NewClient("http", "127.0.0.1", "9503")
// 单个请求
res := new(MemResp)
err := c.Call("member/info", MemReq{[]int{1, 2}}, res, true) // 请求单个值可以直接写 MemReq{1}
fmt.Println(err)
fmt.Println(*res) // {200 ok [{1 111 26} {2 222 28}]}
// 这里虽然提供了批量请求方式,但是经过测试,始终报错 Method not found. 未找到原因...
//res1 := new(MemResp)
//c.BatchAppend("member/info", MemReq{[]int{1, 2}}, res, true)
//err := c.BatchCall()
//fmt.Println(err)
//fmt.Println(*res1)
}
hyperf
<?php
namespace App\JsonRpc;
use Hyperf\RpcServer\Annotation\RpcService;
#[RpcService(name: "MemberService", protocol: "jsonrpc-http", server: "jsonrpc-http")]
class MemberService
{
public function info(mixed $user_id): array
{
var_dump($user_id);
// 模拟数据库用户数据
$users = [
1 => ['id' => 1, 'name' => '111', 'age' => 26],
2 => ['id' => 2, 'name' => '222', 'age' => 28],
3 => ['id' => 3, 'name' => '333', 'age' => 32],
];
$data = [];
if (is_array($user_id)) {
foreach ($user_id as $id) {
$data[] = $users[$id];
}
} elseif (is_numeric($user_id)) {
$data = $users[$user_id];
} else {
return [
'code' => 500,
'msg' => '参数错误',
'data' => []
];
}
return [
'code' => 200,
'msg' => 'ok',
'data' => $data
];
}
}
案例二. go 服务端;hyperf 客户端
go
package main
import (
"fmt"
jrpc "github.com/iloveswift/go-jsonrpc"
)
type ClientLog struct{}
type LogReq struct {
JobId int
Sn string
}
type Result struct {
Id int
Status int
Name string
}
type LogResp struct {
Code int
Message string
Data Result
}
func main() {
s, _ := jrpc.NewServer("http", "127.0.0.1", "8000")
s.Register(new(ClientLog))
s.Start()
}
func (*ClientLog) GetLog(req *LogReq, res *LogResp) error {
// 模拟用户数据
data := map[string]Result{
"1-qwe": Result{1, 0, "job-1"},
"2-asd": Result{2, 1, "job-2"},
}
val, _ := data[fmt.Sprintf("%d-%s", req.JobId, req.Sn)]
*res = LogResp{
Code: 200,
Message: "ok",
Data: val,
}
//*res = interface{}(val).(Result)
return nil
}
hyperf:消费者类
<?php
namespace App\JsonRpc;
use Hyperf\RpcClient\AbstractServiceClient;
class LogServiceConsumer extends AbstractServiceClient
{
/**
* 定义对应服务提供者的服务名称
*/
protected $serviceName = 'ClientLog';
/**
* 定义对应服务提供者的服务协议
*/
protected $protocol = 'jsonrpc-http';
public function GetLog(int $jobid, string $sn): array
{
return $this->__request(__FUNCTION__, compact('jobid', 'sn'));
}
}
hyperf:测试
<?php
declare(strict_types=1);
namespace App\Controller;
use App\JsonRpc\LogServiceConsumer;
use Hyperf\Di\Annotation\Inject;
class IndexController extends AbstractController
{
#[Inject]
protected LogServiceConsumer $consumer;
public function index()
{
$params = $this->request->all();
$res = $this->consumer->GetLog((int)$params['jobid'], $params['sn']);
var_dump($res);
/**
array(3) {
["Code"]=>
int(200)
["Message"]=>
string(2) "ok"
["Data"]=>
array(3) {
["Id"]=>
int(1)
["Status"]=>
int(0)
["Name"]=>
string(5) "job-1"
}
}
*/
}
}