2.微服务--RPC
1.RPC简介
1.远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。
2.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。
3.如果涉及的软件采用面向对象编程,那么远程过程调用也可称为远程调用或远程方法调用。
2.流行的RPC框架的对比
建邺网站建设公司成都创新互联,建邺网站设计制作,有大型网站制作公司丰富经验。已为建邺上千提供企业网站建设服务。企业网站搭建\外贸网站建设要多少钱,请找那个售后服务好的建邺做网站的公司定做!
3.golang中如何实现RPC
Golang中实现RPC比较简单,官方提供了封装好的库,还有第三方库。
官方库:net/rpc
1.net/rpc库使用encoding/gob进行编码
2.支持tcp和http数据传输方式
3.由于其他语言不支持gob编解码方式,所以golang的RPC只支持golang开发的服务端与客户端的交互
官方库:net/jsonrpc
1.jsonrpc采用JSON进行数据编解码,因而支持跨语言调用。
2.jsonrpc库是基于tcp协议实现的,暂不支持http传输方式。
golang的RPC必须符合四个条件才可以:
1.结构体字段首字母要大写,要跨域访问,所以大写。
2.函数名必须首字母大写。
3.函数第一个参数是接收参数,第二个参数是返回给客户端参数,必须是指针类型。
4.函数必须有一个返回值err。
3.1示例1
golang实现RPC程序,实现求矩形面积和周长
//rpcClient/server.go
package main
import (
"log"
"net/http"
"net/rpc"
)
//服务端,求举行面积和周长
//声明矩形对象
type Rect struct {
}
//声明参数结构体,字段首字母大写
type Param struct {
Width,Height int
}
//golang的RPC必须符合4个条件才可以
//◼ 结构体字段首字母要大写,要跨域访问,所以大写
//◼ 函数名必须首字母大写(可以序列号导出的)
//◼ 函数第一个参数是接收参数,第二个参数是返回给客户端参数,必须是指针类型
//◼ 函数必须有一个返回值error
//定义求矩形面积的方法
func (r Rect) Area(p Param,ret *int) error{
*ret = p.Width * p.Height
return nil
}
//定义求矩形周长的方法
func (r Rect) Perimeter(p Param,ret *int) error{
*ret = (p.Width + p.Height)*2
return nil
}
func main() {
//1.注册服务
rect := new(Rect)
rpc.Register(rect)
//2.把服务处理绑定到http协议上
rpc.HandleHTTP()
//3.监听服务,等待客户端调用请求面积和周长的方法
err := http.ListenAndServe(":8080",nil)
if err != nil{
log.Fatal(err)
}
}
//rpcServer/client.go
package main
import (
"fmt"
"log"
"net/rpc"
)
//声明参数结构体,字段首字母大写
type Params struct {
Width,Height int
}
//调用服务
func main() {
//1.链接远程RPC服务
rp,err := rpc.DialHTTP("tcp","127.0.0.1:8080")
if err != nil{
log.Fatal(err)
}
//2.调用远程方法
//定义接收服务端传回来的计算结果的变量
ret := 0
//求矩形面积
err2 := rp.Call("Rect.Area",Params{50,10},&ret)
if err2 != nil{
log.Fatal(err2)
}
fmt.Println("面积:",ret)
//求矩形周长
err3 := rp.Call("Rect.Perimeter",Params{50,10},&ret)
if err3 != nil{
log.Fatal(err3)
}
fmt.Println("周长:",ret)
}
3.2示例2
模仿前面例题,自己实现 RPC 程序,服务端接收 2 个参数,可以做乘法运算,也可以做商和余数的运算,客户端进行传参和访问,得到结果如下:
9*2 = 18
9/2,商=4,余数=1
下面使用net/rpc/jsonrpc库通过json格式编码解码,支持跨语言调用
rpcServer/server.go
package main
import (
"errors"
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
//结构体,用于注册的
type Arith struct {
}
//声明参数结构体
type ArithRequest struct {
A,B int
}
//返回给客户端的结果
type ArithResponse struct {
//乘积
Pro int
//商
Quo int
//余数
Rem int
}
//乘法
func (a *Arith) Multiply(req ArithRequest,res *ArithResponse) error {
res.Pro = req.A * req.B
return nil
}
//商和余数
func (a *Arith) Divide(req ArithRequest,res *ArithResponse) error {
if req.B == 0{
return errors.New("除数不能为0")
}
//商
res.Quo = req.A/req.B
//余数
res.Rem = req.A%req.B
return nil
}
func main() {
rpc.Register(new(Arith))
lis,err := net.Listen("tcp",":8080")
if err != nil{
log.Fatal(err)
}
//循环监听服务
for{
conn,err := lis.Accept()
if err != nil{
continue
}
go func(conn net.Conn) {
fmt.Println("new client")
jsonrpc.ServeConn(conn)
}(conn)
}
}
rpcClient/client.go
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
//声明参数结构体
type ArithRequest struct {
A,B int
}
//返回给客户端的结果
type ArithResponse struct {
//乘积
Pro int
//商
Quo int
//余数
Rem int
}
//调用服务
func main() {
conn,err := jsonrpc.Dial("tcp",":8080")
if err != nil{
log.Fatal(err)
}
req := ArithRequest{9,2}
var res ArithResponse
err2 := conn.Call("Arith.Multiply", req, &res)
if err2 != nil {
log.Fatal(err2)
}
fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
err3 := conn.Call("Arith.Divide", req, &res)
if err3 != nil {
log.Fatal(err3)
}
fmt.Printf("%d / %d 商 %d,余数 = %d\n", req.A, req.B, res.Quo, res.Rem)
}
3.3RPC调用流程
1.微服务架构下数据交互一般是对内RPC,对外REST。
2.将业务按功能模块拆分到各个微服务,具有提高项目协作效率、降低模块耦合度、提高系统可用性等优点,但是开发门槛比较高,比如RPC框架的时候,后期的服务监控等工作。
3.一般情况下,我们会将功能代码在本地直接调用,微服务架构下,我们需要将这个函数作为单独的服务运行,客户端通过网络调用。
3.5自己实现RPC调用库(类似于net/rpc、net/rpc/jsonrpc)
3.5.1网络传输数据格式
成熟的RPC框架会有自定义传输协议,这里网络传输格式定义如下:
前面是固定长度消息头,后面是变长消息体。
//会话中读写数据测试
//rpc/session.go
package rpc
import (
"encoding/binary"
"io"
"net"
)
//测试网络中读写数据的情况
//会话连接的结构体
type Session struct {
conn net.Conn
}
//构造方法
func NewSession(conn net.Conn) *Session {
return &Session{conn:conn}
}
//向连接中写数据
func (s *Session) Write(data []byte) error {
//定义写数据的格式
//4字节头部+可变体的长度
buf := make([]byte,4+len(data))
//写入头部,记录数据长度
binary.BigEndian.PutUint32(buf[:4],uint32(len(data)))
//将整个数据,放在索引4的后面
copy(buf[4:],data)
_,err := s.conn.Write(buf)
if err != nil{
return err
}
return nil
}
//从连接中读数据
func (s *Session) Read() ([]byte,error) {
//读取头部记录的长度
header := make([]byte, 4)
//按长度读取头部消息
_,err := io.ReadFull(s.conn, header)
if err != nil{
return nil,err
}
//根据头部信息,读取数据长度
dataLen := binary.BigEndian.Uint32(header)
data := make([]byte,dataLen)
//读取到的数据存到data中
_,err = io.ReadFull(s.conn, data)
if err != nil{
return nil,err
}
return data,nil
}
//rpc/session_test.go
package rpc
import (
"fmt"
"net"
"sync"
"testing"
)
func TestSession_ReadWriter(t *testing.T) {
//定义地址
addr := "127.0.0.1:8000"
my_data := "hello lili"
wg := sync.WaitGroup{}
wg.Add(2)
//写数据的协成
go func() {
defer wg.Done()
lis,err := net.Listen("tcp", addr)
if err != nil{
t.Fatal(err)
}
conn,_ := lis.Accept()
s:= NewSession(conn)
err = s.Write([]byte(my_data))
if err != nil{
t.Fatal(err)
}
}()
//读数据的协成
go func() {
defer wg.Done()
conn,err := net.Dial("tcp", addr)
if err != nil{
t.Fatal(err)
}
s:= NewSession(conn)
data,err := s.Read()
if err!= nil{}
t.Fatal(err)
//最后一层校验
if string(data) != my_data{
t.Fatal(err)
}
fmt.Println("读取到的数据为:",string(data))
}()
}
测试结果:
rpc go test -v
=== RUN TestSession_ReadWriter
--- PASS: TestSession_ReadWriter (0.00s)
PASS
ok _/Users/tongchao/Desktop/gopath/src/test/rpc 0.008s
➜ rpc
3.5.2gob编码/解码
//codec.go
package rpc
import (
"bytes"
"encoding/gob"
)
//定义RPC交互的数据结构
type RPCData struct {
//访问的函数
Name string
//访问时的参数
Args []interface{}
}
//编码
func encode(data RPCData) ([]byte, error) {
//得到字节数组的编码器
var buf bytes.Buffer
bufEnc := gob.NewEncoder(&buf)
//编码器对数据编码
if err := bufEnc.Encode(data); err != nil{
return nil,err
}
return buf.Bytes(),nil
}
//解码
func decode(b []byte) (RPCData,error) {
buf := bytes.NewBuffer(b)
//得到字节数组的解码器
bufDec := gob.NewDecoder(buf)
//解码器对数据解码
var data RPCData
if err:= bufDec.Decode(&data);err != nil{
return data,err
}
return data,nil
}
//codec_test.go
package rpc
import (
"fmt"
"testing"
)
func TestCode(t *testing.T) {
args := make([]interface{},0)
data := RPCData{
Name: "lili",
Args: args,
}
encodeData,err := encode(data)
if err != nil{
fmt.Println(err)
}
fmt.Printf("encodeData:%v\n",encodeData)
decodeData,err := decode(encodeData)
if err != nil{
fmt.Println(err)
}
fmt.Printf("decodeData:%v\n",decodeData)
}
结果:
=== RUN TestCode
encodeData:[40 255 129 3 1 1 7 82 80 67 68 97 116 97 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 4 65 114 103 115 1 255 132 0 0 0 28 255 131 2 1 1 14 91 93 105 110 116 101 114 102 97 99 101 32 123 125 1 255 132 0 1 16 0 0 9 255 130 1 4 108 105 108 105 0]
decodeData:{lili []}
--- PASS: TestCode (0.00s)
PASS
Process finished with exit code 0
网站名称:2.微服务--RPC
标题链接:http://scjbc.cn/article/pppiej.html