【初识够go-rpc】
RPC
1.RPC简介
1.1 什么是rpc协议
-
什么是rpc?
- rpc (remote procedure call protocol) 就是远程过程调用时一个计算机远程调用
- rpc底层是通过http协议来实现的
-
rpc如何实现的?
- 想调用本地函数一样,区调用远端 函数。
- 通过rpc协议,传递:函数名称、函数参数、调用yuan
- 想调用本地函数一样,区调用远端 函数。
-
为什么微服务要使用rpc?
- 每个服务都被封装为进程。彼此独立
- 进程和进程之间,可以使用不同的语言实现
1.2 网络模型
osi 7层协议
- 物理层
- 数据链路层
- 网络层
- 传输层
- 会话层
- 表示层
- 应用层
tcp-ip4层
- 链路层
- 网络层
- 传输层:tcp\udp
- 应用层:rpc\ws\http
2.RPC框架对比
3.RPC入门使用
go语言的一般性的socket网络通信
# server端 伪代码
listener := net.Listen() # listener
conn := listener.Accept() # conn
conn.Read()
conn.Write()
defer conn.Close()
defer listener.Close()
# client端 伪代码
conn := net.Dial()
conn.Write()
conn.Read()
defer conn.Close()
-
RPC的使用步骤
-
server端
-
注册rpc服务对象。给对象绑定方法(1.注册类、2.绑定类方法)
rpc.RegisterName("服务名",回调对象)
-
去创建一个监听器
listener,err := net.Listen()
-
建立连接
conn,err := listener.Accept()
-
将连接绑定rpc服务
rpc.ServerConn(conn)
-
-
client端
-
用rpc先连接服务器
conn,err := rpc.Dial()
-
传递参数调用远程函数
conn.Call("服务名.方法名",传入参数,传出参数)
-
-
-
rpc相关函数
-
注册rpc服务
func(server *server)RegisterName(name string,recv interface{})error // name 服务名称 string // recv rpc对象 interface{}
# 该对象绑定方法必须满足如下条件 # 1.方法必须是导出的,包外可见,首字母大写 # 2.方法必须有两个参数,都是导出类型,内建类型 # 3.方法的第二个参数必须是指针(传出参数必须是指针) # 4.方法只有一个error接口类型的返回值
//示例 type World struct(){ } func(this *world)HelloWorld(name string,resp *string)error{ } rpc.RegisterName("服务名",new(World))
-
绑定rpc服务
func (server *Server) ServeConn(conn io.ReadWriteCloser) // conn 是成功建立好连接的socket
-
调用远程函数
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error //serviceMethod 服务吗.方法名 //args 函数的参数 传入参数 //reply 传出参数 定义一个变量 取地址 &变量
-
4.RPC编码实现
client
package main
import (
"fmt"
"net/rpc"
)
func main(){
// 1.用rpc连接服务器
conn,err := rpc.Dial("tcp","127.0.0.1:8800")
if err != nil{
fmt.Println("conn error")
return
}
defer conn.Close()
// 2.调用远程方法
var reply string
err = conn.Call("hello.HelloWorld","libai",&reply)
if err != nil{
fmt.Println("Call error")
return
}
fmt.Println(reply)
}
server
package main
import (
"fmt"
"net"
"net/rpc"
)
//定义类对象
type World struct {
}
//绑定类方法
func (self *World) HelloWorld(name string, resp *string) error {
*resp = name + ",你好!"
return nil
}
func main() {
// 1.注册rpc服务,绑定对象方法
err := rpc.RegisterName("hello", new(World))
if err != nil {
fmt.Println("Register error", err)
}
// 2.设置监听
listener,err := net.Listen("tcp","127.0.0.1:8800")
if err != nil{
fmt.Println("Listen error")
return
}
defer listener.Close()
fmt.Println("开始监听!")
//3.建立连接
conn,err := listener.Accept()
if err != nil{
fmt.Println("Accept error")
return
}
defer conn.Close()
//4.绑定服务
rpc.ServeConn(conn)
}
5.JSON-rpc
-
使用
nc -l 127.0.0.1 8800
来充当服务器-
使用rpc客户端
# client conn,err := rpc.Dial("tcp","127.0.0.1:8800")
# server huhao@huhaodeMacBook-Pro ~ % nc -l 127.0.0.1 8800 ServiceMethod Seq��hello.HelloWorld libai
-
使用jsonrpc客户端后
# client conn,err := jsonrpc.Dial("tcp","127.0.0.1:8800")
# server huhao@huhaodeMacBook-Pro ~ % nc -l 127.0.0.1 8800 {"method":"hello.HelloWorld","params":["libai"],"id":0}
-
6.protobuf
google开发的
// .proto
// 默认是proto2
syntax = "proto3";
// 指定所在的包
package pb;
// 枚举
enum Week {
Monday = 0;
Tuesday = 1;
}
// 定义消息
message Student {
int32 age = 1;
string name = 2;
People p = 3;
repeated int32 score = 4; //数组
//枚举
Week w = 5;
//联合体
oneof data {
string teacher = 6;
string class = 7;
}
}
// 消息可以嵌套
message People{
int32 weight = 1;
}
7.编译protobuf
# protoc等安装
go get -u -v github.com/golang/protobuf/proto
go get google.golang.org/grpc
go get github.com/golang/protobuf/protoc-gen-go
-
go中编译编译
protoc --go__out=./ *.proto
-
GO的RPC必须满足四个条件
- 结构体字段要大写,字段访问要大写
- 函数名首字母大写
- 函数第一个参数代表的是接收参数,第二个参数是返回给客户端的参数,必须是指针类型
- 函数必须有一个返回值error
8.GRPC简单示例
示例1:求长方形的面积和周长
-
client
// server package main import ( "fmt" "log" "net/http" "net/rpc" ) //服务端 求长方形的面积和周长 //声明一个矩形对象 type Rect struct { } //声明参数的结构体 type Params struct { //长和宽 Width, Height int } //定义求面积的方法 func (r *Rect) Area(p Params, ret *int) error { *ret = p.Width * p.Height fmt.Println(ret) fmt.Println(*ret) fmt.Println(&ret) return nil } //定义求周长的方法 func (r *Rect) Perimeter(p Params, ret *int) error { *ret = (p.Width + p.Height) * 2 fmt.Println(ret) fmt.Println(*ret) fmt.Println(&ret) 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) } }
-
client
//clientpackage mainimport ( "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,100},&ret) fmt.Println("面积:",ret) if err2 != nil{ log.Fatal(err2) } //求周长 err3 := rp.Call("Rect.Perimeter",Params{50,100},&ret) fmt.Println("周长:",ret) if err3 != nil{ log.Fatal(err3) }}
示例2:除法求结果
-
server
package mainimport ( "log" "net/http" "net/rpc")//声明一个除法的结构体type Division struct {}//声明除法的两个变量type Params struct { Data1 int //除数 Data2 int //被除数}//定义一个求除法结果的方法func (d *Division)Get_devision(p Params,ret *int)error{ *ret = p.Data1/p.Data2 return nil}func main(){ //建立一个rpc连接 rect := new(Division) rpc.Register(rect) rpc.HandleHTTP() err := http.ListenAndServe(":8081",nil) if err != nil { log.Fatal(err) } //监听一个端口}
-
client
package mainimport ( "fmt" "log" "net/rpc")//声明一个参数的结构体type Params struct { Data1 int //除数 Data2 int //被除数}func main(){ //1.连接远程的rpc服务 rp,err := rpc.DialHTTP("tcp","127.0.0.1:8081") if err != nil{ log.Fatal(err) } //2.调用远程方法 ret := 0 err2 := rp.Call("Division.Get_devision",Params{10,5},&ret) if err2 != nil{ log.Fatal(err2) } fmt.Println("除法的结果:",ret)}
9.rpc调用流程
对内rpc,对外rest
10.go micro框架
// go get -u -v github.com/micro/protoc-gen-micro// go get -u -v github.com/micro/micro// go get -u -v github.com/micro/go-micro
代码结构:
10.1 服务发现
是微服务必须的技术
- 服务发现也可以看作一个服务,是给服务提供服务的
10.2 服务发现的种类
- consul:常用语go-micro
- mdns:go-micro默认的
- etcd:k8s内嵌的服务发现
- zookeeper:java的服务发现