常见的远程调用方式有以下几种:
1、RPC:
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
简单来说,就是跟远程访问或者web请求差不多,都是一个client向远端服务器请求服务返回结果,但是web请求
使用的网络协议是http高层协议,而rpc所使用的协议多为TCP,是网络层协议,减少了信息的包装,加快了处理速度。
其自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型。
类似的还有RMI(Remote Methods Invoke 远程方法调用,是JAVA中的概念,是JAVA十三大技术之一)
2、Http:
http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。现在热门的Rest风格,就可以通过http协议来实现。
相同点:底层通讯都是基于socket,都可以实现远程调用,都可以实现服务调用服务。
不同点:
RPC:框架有:dubbo、cxf、(RMI远程方法调用)Hessian
当使用RPC框架实现服务间调用的时候,要求服务提供方和服务消费方 都必须使用统一的RPC框架,要么都dubbo,要么都cxf
跨操作系统在同一编程语言内使用
优势:调用快、处理快
http:框架有:httpClient
当使用http进行服务间调用的时候,无需关注服务提供方使用的编程语言,也无需关注服务消费方使用的编程语言,服务提供方只需要提供restful风格的接口,服务消费方,按照restful的原则,请求服务,即可
跨系统跨编程语言的远程调用框架
总结:对比RPC和http的区别
1 RPC要求服务提供方和服务调用方都需要使用相同的技术,要么都hessian,要么都dubbo
而http无需关注语言的实现,只需要遵循rest规范
2 RPC的开发要求较多,像Hessian框架还需要服务器提供完整的接口代码(包名.类名.方法名必须完全一致),否则客户端无法运行
3 Hessian只支持POST请求
4 Hessian只支持JAVA语言
优势:通用性强
如何选择?
- 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿
- 难度来看,RPC实现较为复杂,http相对比较简单
- 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
因此,两者都有不同的使用场景:
- 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。
- 如果需要更加灵活,跨语言、跨平台,显然http更合适。
那么我们该怎么选择呢?
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。
RPC
golang本身有rpc包,可以方便的使用,来构建自己的rpc服务,下边是一个简单是实例,可以加深我们的理解
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据
服务端:
package main
import (
"io"
"log"
"net"
"net/http"
"net/rpc"
) //- 方法是导出的
//- 方法有两个参数,都是导出类型或内建类型
//- 方法的第二个参数是指针
//- 方法只有一个error接口类型的返回值
//
//func (t *T) MethodName(argType T1, replyType *T2) error
type Panda int
func (this *Panda) Getinfo(argType int, replyType *int) error {
log.Println(argType) // 123
*replyType = 1 + argType
return nil
}
func main() {
//注册1个页面请求
http.HandleFunc("/panda", pandatext)
//new 一个对象
pd := new(Panda)
//注册服务
//Register在默认服务中注册并公布 接收服务 pd对象 的方法
rpc.Register(pd)
rpc.HandleHTTP()
//建立网络监听
ln, err := net.Listen("tcp", "127.0.0.1:10086")
if err != nil {
log.Println("网络连接失败")
}
log.Println("正在监听10086")
//service接受侦听器l上传入的HTTP连接,
http.Serve(ln, nil)
}
//用来现实网页的web函数
func pandatext(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "panda")
}
客户端 :
import (
"net/rpc"
"log"
)
func main() {
//rpc的与服务端建立网络连接
cli, err := rpc.DialHTTP("tcp", "127.0.0.1:10086")
if err != nil {
log.Println("网络连接失败")
}
var val int
//远程调用函数(被调用的方法,传入的参数 ,返回的参数)
err = cli.Call("Panda.Getinfo", 123, &val)
if err != nil {
log.Println("打call失败")
return
}
log.Printf("val:%d", val) // val:124
GRPC
Go简单实现RPC和gRPC的调用:https://blog.csdn.net/lk2684753/article/details/84436190
go的grpc实现:https://blog.csdn.net/ys5773477/article/details/77834697
在 gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC系统类似, gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。 gRPC客户端和服务端可以在多种环境中运行和交互 -从 google内部的服务器到你自己的笔记本,并且可以用任何 gRPC支持的语言 来编写。所以,你可以很容易地用 Java创建一个gRPC服务端,用 Go、 Python、Ruby来创建客户端。此外, Google最新 API将有 gRPC版本的接口,使你很容易
地将 Google的功能集成到你的应用里。
GRPC使用 protocol buffers
gRPC默认使用protoBuf,这是 Google开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如JSON)。正如你将在下方例子里所看到的,你用 proto files创建 gRPC服务,用 protoBuf消息类型来定义方法参
数和返回类型。你可以在 Protocol Buffers文档找到更多关于 protoBuf的资料。 虽然你可以使用 proto2 (当前默认的 protocol buffers版本 ),我们通常建议你在 gRPC里使用 proto3,因为这样你可以使用 gRPC支持全部范围的的语言,并且能避免 proto2客户端与 proto3服务端交互时出现的兼容性问题,反之亦然。
gRPC是Google的RPC框架,开源、高性能、跨语言,基于HTTP/2通讯协议和Protocol Buffer 3数据序列化协议。
调用的双方可以使用完全不同的两种语言来实现,分别实现client端和server端,按照约定的protobuf协议进行交互。client端会保存与server端的长连接对象或叫存根,通过这个存根可以直接调用服务端的方法。而服务端则实现了proto中指定的服务接口。
proto的安装
要编译proto文件生成go代码需要两个工具
protoc :用于编译(其他语言只需要protoc足以)
protoc-gen-go : 用于生成go语言的文件(go语言专用插件)
1、protoc-gen-go下载:
go get github.com/golang/protobuf/protoc-gen-go
1
会直接生成protoc-gen-go 二进制文件到GOPATH的bin目录。
2、protoc下载:https://github.com/google/protobuf/releases
找到对应平台win/linux/mac的下载包,复制二进制文件protoc到GOPATH的bin目录。
定义proto文件
syntax = "proto3";
package protos;
// 要获取的数据结构
message User{
int32 id = 1;
string name = 2;
}
// 请求数据结构
message UserReq{
int32 id = 1;
}
// 定义服务,关键字'service',方法关键字'rpc'
service IUserService {
// 单一请求应答,一对一
rpc Get (UserReq) returns (User);
// 服务端流式应答,一对多,可用于下载
rpc GetList (UserReq) returns (stream User);
// 客户端流式请求,多对一,可用于上传
rpc WaitGet(stream UserReq) returns (User);
// 双向流式请求应答,支持HTTP/2.0
rpc LoopGet(stream UserReq) returns (stream User);
}
或者:
syntax = "proto3";
package mygrpcproto;
service HelloServer{
// 创建第一个接口
rpc SayHello(HelloRequest)returns(HelloReplay){}
// 创建第二个接口
rpc GetHelloMsg(HelloRequest)returns(HelloMessage){}
}
message HelloRequest{
string name = 1 ;
}
message HelloReplay{
string message = 1;
}
message HelloMessage{
string msg = 1;
}
$ protoc --go_out=./ *.proto #不加grpc插件
$ protoc --go_out=plugins=grpc:./ *.proto #添加grpc插件
#对比发现内容增加
#得到 helloServer.pb.go文件
Grpc服务:
package main
import (
"net"
"fmt"
"google.golang.org/grpc"
pt "demo/grpc/proto"
"context"
)
const (
post = "127.0.0.1:18881"
)
//对象要和proto内定义的服务一样
type server struct{}
//实现RPC SayHello 接口
func(this *server)SayHello(ctx context.Context,in *pt.HelloRequest)(*pt.HelloReplay, error){
return &pt.HelloReplay{Message:"hello"+in.Name},nil
}
//实现RPC GetHelloMsg 接口
func (this *server) GetHelloMsg(ctx context.Context, in *pt.HelloRequest)(*pt.HelloMessage, error) {
return &pt.HelloMessage{Msg: "this is from server HAHA!"}, nil
}
func main() {
//监听网络
ln ,err :=net.Listen("tcp",post)
if err!=nil {
fmt.Println("网络异常",err)
}
// 创建一个grpc的句柄
srv:= grpc.NewServer()
//将server结构体注册到 grpc服务中
pt.RegisterHelloServerServer(srv,&server{})
//监听grpc服务
err= srv.Serve(ln)
if err!=nil {
fmt.Println("网络启动异常",err)
}
}
gRpc客户端:
package main
import (
"google.golang.org/grpc"
pt "demo/grpc/proto"
"fmt"
"context"
)
const (
post = "127.0.0.1:18881"
)
func main() {
// 客户端连接服务器
conn,err:=grpc.Dial(post,grpc.WithInsecure())
if err!=nil {
fmt.Println("连接服务器失败",err)
}
defer conn.Close()
//获得grpc句柄
c:=pt.NewHelloServerClient(conn)
// 远程调用 SayHello接口
//远程调用 SayHello接口
r1, err := c.SayHello(context.Background(), &pt.HelloRequest{Name: "panda"})
if err != nil {
fmt.Println("cloud not get Hello server ..", err)
return
}
fmt.Println("HelloServer resp: ", r1.Message)
//远程调用 GetHelloMsg接口
r2, err := c.GetHelloMsg(context.Background(), &pt.HelloRequest{Name: "panda"})
if err != nil {
fmt.Println("cloud not get hello msg ..", err)
return
}
fmt.Println("HelloServer resp: ", r2.Msg)
}
#先运行 server,后运行 client
#得到以下输出结果
HelloServer resp: hellopanda
HelloServer resp: this is from server HAHA!
#如果反之则会报错