Golang RPC总结

1. RPC

  • 客户端(client): 服务调用的发起方
  • 客户端存根(client Stub):
    • 运行在客户端机器上
    • 存储调用服务器地址
    • 将客户端请求数据信息打包
    • 通过网络发给服务端存根程序
    • 接收服务端响应的数据包,解析后给客户端
  • 服务端(server): 服务提供者
  • 服务端存根(server Stub):
    • 存在与服务端机器上
    • 接收客户端Stub程序发送来请求消息数据包
    • 调用服务端的程序方法
    • 将结果打包成数据包发给客户端Stub程序

reflect_1

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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值