go原生rpc笔记

微服务兴起,传统的大单体服务拆分为多个微服务,各自独立开发、部署运维,服务之间通过rpc调用进行协调,降低了模块的耦合性。 rpc通常可以有两种方式,基于http协议的调用,如restful,和基于TCP的调用。
http实现简单,但处于OSI 7层模型中的第四层应用层,开销较大。基于TCP的rpc在传输层,性能更好。
rpc涉及 接口定义,client方法开发,server方法开发,参数序列化/反序列化。有非常成熟的框架如kite,基于thrift协议开发。

为了便于了解底层,基于go 原生进行rpc开发,核心是net/rpc包,包含server和client两部分。client创建连接后,后续所有请求直接复用该连接。
步骤如下:
详细代码见 https://github.com/RickyWangGithub/go_project/tree/main/rpc

一、实现两数求和

1. IDL定义,包括request,response, 支持的方法method

// 定义请求和响应格式
// 参数必须是可导出的(即大写开头)否则其他包无法调用
type SumRequest struct {
	X, Y int
}
type SumResponse struct {
	Z int
}

// 定义支持的方法
type MyServiceInterface = interface {
// 远程方法签名约定:两个可序列化的参数,第一个是输入,第二个是指针输出,返回类型是error;方法必须可导出
	Sum(sumRequest SumRequest, sumResponse *SumResponse) error
}

2. server实现

type SumService struct{}
func NewSumService() *SumService {
	return &SumService{}
}

// SumService 实现 MyServiceInterface
func (service *SumService) Sum(req SumRequest, resp *SumResponse) error {
	resp.Z = req.X + req.Y
	return nil
}

3. server端rpc注册、绑定+监听、启动

// 注册SumService方法,可以注册一类实现了MyServiceInterface的class
func RegisterSumService(svc MyServiceInterface) {
	rpc.RegisterName(NameSpaceSumService, svc)
}

// 启动server,包含注册,绑定,监听,提供服务 四部
func StartSumService() {
	RegisterSumService(NewSumService())
	listener, err := net.Listen(NetworkTCP, NetworkAddr)
	PanicErrorr(err)
	for {
		conn, err := listener.Accept()
		PanicErrorr(err)
		// 协程开起来,支持并发
		go rpc.ServeConn(conn)
	}
}

4. client端方法实现

// 客户端代码
type SumServiceClient struct {
	*rpc.Client
}
// 封装调用方法,这样可以在编译阶段校验格式准确性
func (c SumServiceClient) Sum(req SumRequest, resp *SumResponse) error {
	return c.Call(NameSpaceSumService+".Sum", req, resp)
}

// 统一构建一次client
func NewSumServiceClient() *SumServiceClient {
	client, err := rpc.Dial(NetworkTCP, NetworkAddr)
	PanicErrorr(err)
	return &SumServiceClient{client}
}

5. 初始化client,调用

func TestSumRpc() {
	client := NewSumServiceClient()
	req := SumRequest{
		X: 2,
		Y: 7,
	}
	var resp SumResponse
	err := client.Sum(req, &resp)
	PanicErrorr(err)
	fmt.Printf("req:%+v, resp:%+v", req, resp)
}

6. 最终输出

req:{X:2 Y:7}, resp:{Z:9}

二、实现kv存储,同时监听变化的key,通过rpc暴露给使用方,具体步骤和上述类似,直接贴代码

package project

import (
	"fmt"
	"math/rand"
	"net"
	"net/rpc"
	"strconv"
	"sync"
	"time"
)

/*
实现kv存储,切有数据变更的watch监听
包含服务端和客户端
*/
const (
	ServiceName = "KVStorage"
)

// 服务端代码
type SetReq struct {
	Key, Value string
}
type KvStroageInterface interface {
	Get(key string, string2 *string) error
	Set(setReq SetReq, str *string) error
	Watch(key string, keyChanged *string) error
}

func RegisterKVStorageService(svc KvStroageInterface) {
	rpc.RegisterName(ServiceName, svc)
}

type KVStorage struct {
	data   map[string]string
	filter map[string]func(key string)
	lock   sync.Mutex
}

func NewKVStroage() *KVStorage {
	return &KVStorage{
		data:   make(map[string]string),
		filter: make(map[string]func(key string)),
		lock:   sync.Mutex{},
	}
}
func (receiver *KVStorage) Set(setReq SetReq, str *string) error {
	receiver.lock.Lock()
	defer receiver.lock.Unlock()
	fmt.Printf("req:%+v,%s\n", setReq, *str)
	if value := receiver.data[setReq.Key]; value != setReq.Value {
		for id, fn := range receiver.filter {
			fmt.Printf("called:id %s\n", id)
			fn(setReq.Key)
		}
	}
	receiver.data[setReq.Key] = setReq.Value
	*str = strconv.Itoa(len(receiver.data))
	fmt.Printf("req:%+v,%s\n", setReq, *str)
	return nil
}
func (receiver *KVStorage) Get(key string, value *string) error {
	receiver.lock.Lock()
	defer receiver.lock.Unlock()
	*value = receiver.data[key]
	return nil
}
func (receiver *KVStorage) Watch(key string, keyChanged *string) error {
	ch := make(chan string, 10)
	id := fmt.Sprintf("%s_%3d", time.Now().Format(""), rand.Int())
	receiver.lock.Lock(
	receiver.filter[id] = func(key string) {
	// 缓冲区已满,直接退出,否则会阻塞数据更新
		if len(ch) == cap(ch) {
			return
		}
		ch <- key
	}
	receiver.lock.Unlock()
	
	timeout :=  time.After(time.Second*10)
	for {
		select {
		case val := <-ch:
			*keyChanged += val + ","
			fmt.Printf("select val:%s\n",*keyChanged)
		case <-timeout:
			fmt.Printf("timeout\n")
			return nil
		}
	}
	return nil
}

func StartKVStorageServer() {
	RegisterKVStorageService(NewKVStroage())
	//rpc.RegisterName()
	listener, err := net.Listen(NetworkTCP, ":1234")
	PanicErrorr(err)

	for true {
		conn, err := listener.Accept()
		PanicErrorr(err)
		go rpc.ServeConn(conn)
	}
}

// client start
type KVStroageClient struct {
	*rpc.Client
}

func NewKVStroageClient() *KVStroageClient {
	client, err := rpc.Dial(NetworkTCP, "localhost:1234")
	PanicErrorr(err)
	return &KVStroageClient{client}
}
func (c *KVStroageClient) Get(key string, string2 *string) error {
	return c.Client.Call(ServiceName+".Get", key, string2)
}
func (c *KVStroageClient) Set(setReq SetReq, str *string) error {
	return c.Client.Call(ServiceName+".Set", setReq, str)
}
func (c *KVStroageClient) Watch(key string, keyChanged *string) error {
	return c.Client.Call(ServiceName+".Watch", key, keyChanged)

}
func StartClient() {
	client := NewKVStroageClient()
	go func() {
		//for {
			var resp string
			err := client.Watch("", &resp)
			PanicErrorr(err)
			fmt.Printf("keyChanged:%s\n\n", resp)
		//}
	}()
	time.Sleep(time.Second*3)
	k := "key"
	v := "value"
	for i := 0; i < 20; i++ {
		err := client.Get(k, &v)
		PanicErrorr(err)
		fmt.Printf("get resp:%s\n\n", v)
		k += strconv.Itoa(i)
		setReq := SetReq{
			Key:   k,
			Value: strconv.Itoa(i),
		}
		err = client.Set(setReq, &v)
		PanicErrorr(err)
		fmt.Printf("set resp:%s\n\n", v)
		time.Sleep(time.Second * 1)
	}
}
func PanicErrorr(err error) {
	if err != nil {
		fmt.Printf("err:%+v\n", err)
		panic(err)
	}
}
posted @ 2021-08-14 22:05  dancingwolves  阅读(107)  评论(1)    收藏  举报