Golang RPC总结
1. RPC
- 客户端(client): 服务调用的发起方
- 客户端存根(client Stub):
- 运行在客户端机器上
- 存储调用服务器地址
- 将客户端请求数据信息打包
- 通过网络发给服务端存根程序
- 接收服务端响应的数据包,解析后给客户端
- 服务端(server): 服务提供者
- 服务端存根(server Stub):
- 存在与服务端机器上
- 接收客户端Stub程序发送来请求消息数据包
- 调用服务端的程序方法
- 将结果打包成数据包发给客户端Stub程序
1.1 Go 语言实现 RPC
- Golang 提供RPC标准包,支持开发 RPC 服务端和客户端,采用 gob 编码。
- 支持三种请求方式:HTTP、TCP 和 JSONRPC
- Golang RPC 函数必须特定的格式写法才能被远程调用,格式如下:
func (t *T) MethodName(argType T1, replyType *T2) error
T1 和 T2 必须能被 encoding/gob 包编码和解码
2. 简单实例
2.1 服务端
- 新增rpc服务
func (p *HelloService) Hello(request string, reply *string) error{}
- 注册rpc服务
rpc.Register(HelloService)
- 开启服务监听
net.Listen("tcp", ":1234")
type HelloService struct{}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello: " + request
return nil
}
func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
rpc.ServeConn(conn)
}
2.2 客户端
- 连接rpc服务器
rpc.Dail("tcp", "localhost:1234")
- 调用rpc服务
client.Call("HelloService.Hello", "chloe", &reply)
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal(err)
}
var reply string
err = client.Call("HelloService.Hello", "chloe", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
3. 接口化拆分
// svc.go
const HelloServiceName = "HelloService"
// 服务端接口
type HelloServiceInterface interface {
Hello(request string, reply *string) error
}
func RegisterHelloService(svc HelloServiceInterface) error {
return rpc.RegisterName(HelloServiceName, svc)
}
// 客户端接口
type HelloServiceClient struct {
*rpc.Client
}
var _ HelloServiceInterface = (*HelloServiceClient)(nil)
func DialHelloService(network, address string) (*HelloServiceClient, error) {
client, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: client}, nil
}
func (p *HelloServiceClient) Hello(request string, reply *string) error {
return p.Client.Call(HelloServiceName+".Hello", request, reply)
}
// server.go
type HelloService struct{}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello: " + request
return nil
}
func main() {
RegisterHelloService(new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go rpc.ServeConn(conn)
}
}
// client.go
func main() {
client, err := DialHelloService("tcp", "localhost:1234")
if err != nil {
log.Fatal(err)
}
var reply string
err = client.Hello("chloe", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
4. 跨语言RPC
4.1 测试服务端
// server.go
func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
//go rpc.ServeConn(conn)
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
# 启动服务
$ go run server.go
# 测试服务
$ echo -e '{"method":"HelloService.Hello","params":["eli"],"id":0}' | nc localhost 1234
{"id":0,"result":"hello: eli","error":null}
4.2 测试客户端
// client.go
func main() {
//client, err := rpc.Dial("tcp", "localhost:1234")
conn, err := net.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal(err)
}
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
var reply string
err = client.Call("HelloService.Hello", "chloe", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
# 启动1234端口
$ nc -l 1234
# 客户端发起请求
$ go run client.go
# nc服务收到请求
{"method":"HelloService.Hello","params":["chloe"],"id":0}
5. RPC over HTTP
// server.go
func main() {
rpc.RegisterName("HelloService", new(HelloService))
http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
var conn io.ReadWriteCloser = struct {
io.Writer
io.ReadCloser
}{
ReadCloser: r.Body,
Writer: w,
}
rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
})
http.ListenAndServe(":1234", nil)
}
$ curl localhost:1234/jsonrpc -X POST \
--data '{"method":"HelloService.Hello","params":["sara"],"id":1}'
{"id":1,"result":"hello: sara","error":null}
6. 实战示例
6.1 基于RPC实现Watch功能
// server.go
type KVStoreService struct {
m map[string]string
filter map[string]func(key string)
mu sync.Mutex
}
func NewKVStoreService() *KVStoreService {
return &KVStoreService{
m: make(map[string]string),
filter: make(map[string]func(key string)),
}
}
func (p *KVStoreService) Get(key string, value *string) error {
p.mu.Lock()
defer p.mu.Unlock()
if v, ok := p.m[key]; ok {
*value = v
return nil
}
return errors.New("not found")
}
func (p *KVStoreService) Set(kv [2]string, reply *struct{}) error {
p.mu.Lock()
defer p.mu.Unlock()
key, value := kv[0], kv[1]
if oldValue := p.m[key]; oldValue != value {
for _, fn := range p.filter {
fn(key)
}
}
p.m[key] = value
return nil
}
func (p *KVStoreService) Watch(timeoutSecond int, keyChanged *string) error {
id := fmt.Sprintf("watch-%s-%03d", time.Now(), rand.Int())
ch := make(chan string, 10) // buffered
p.mu.Lock()
p.filter[id] = func(key string) {
ch <- key
}
p.mu.Unlock()
select {
case <-time.After(time.Duration(timeoutSecond) * time.Second):
return fmt.Errorf("timeout")
case key := <-ch:
*keyChanged = key
return nil
}
return nil
}
func main() {
rpc.RegisterName("KVStoreService", NewKVStoreService())
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
rpc.ServeConn(conn)
}
}
// client.go
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal(err)
}
doClientWork(client)
}
func doClientWork(client *rpc.Client) {
go func() {
var keyChanged string
err := client.Call("KVStoreService.Watch", 30, &keyChanged)
if err != nil {
log.Fatal(err)
}
fmt.Println("watch:", keyChanged)
}()
err := client.Call(
"KVStoreService.Set",
[2]string{"abc", "abc-value-1"},
new(struct{}),
)
if err != nil {
log.Fatal(err)
}
time.Sleep(3 * time.Second)
}
6.2 反向RPC
服务端主动向客户端发起请求
// server.go
type HelloService struct{}
func (p *HelloService) Hello(request string, reply *string) error {
*reply = "hello: " + request
return nil
}
func main() {
rpc.RegisterName("HelloService", new(HelloService))
for {
conn, _ := net.Dial("tcp", "localhost:1234")
if conn == nil {
time.Sleep(time.Second)
continue
}
rpc.ServeConn(conn)
conn.Close()
}
}
// client.go
func main() {
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
clientChan := make(chan *rpc.Client)
go func() {
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
clientChan <- rpc.NewClient(conn)
}
}()
doClientWork(clientChan)
}
func doClientWork(clientChan <-chan *rpc.Client) {
client := <-clientChan
defer client.Close()
var reply string
err := client.Call("HelloService.Hello", "chloe", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
6.3 上下文信息
上下文支持为每个链接提供独立的RPC服务
// server.go
type HelloService struct {
conn net.Conn
isLogin bool
}
func (p *HelloService) Login(request string, reply *string) error {
if request != "admin:123456" {
return fmt.Errorf("auth failed")
}
log.Info("Login ok")
*reply = "login: ok"
p.isLogin = true
return nil
}
func (p *HelloService) Hello(request string, reply *string) error {
if !p.isLogin {
return fmt.Errorf("please login")
}
*reply = "hello: " + request + ", from " + p.conn.RemoteAddr().String()
return nil
}
func main() {
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go func() {
defer conn.Close()
p := rpc.NewServer()
p.Register(&HelloService{conn: conn})
p.ServeConn(conn)
}()
}
}
// client.go
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal(err)
}
var reply string
err = client.Call("HelloService.Login", "admin:123456", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
err = client.Call("HelloService.Hello", "chloe", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}