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)
}
}

浙公网安备 33010602011771号