基于 golang 的实现简单的 RPC 版 Watch 功能

本文主要参考《Go 语言高级编程》一书!
在很多系统中都提供了 Watch 监视功能的接口,当系统满足某种条件时 Watch 方法返回监控的结果。在这里我们可以尝试通过 RPC 框架实现一个基本的 Watch 功能。因为 RPC client.send 方法是线程安全的,我们可以通过在不同的 Goroutine 中同时并发阻塞调用 RPC 方法,通过在一个独立的 Goroutine 中调用 Watch 函数进行监控。

package main

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

// 简单的内存 KV 数据库
type KVStoreService struct {
	m      map[string]string           // 存储数据
	filter map[string]func(key string) // Watch 调用时的过滤器函数列表
	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")
}

// 输入参数是 key 和 value 组成的数组,匿名结构体则表示忽略输出参数
func (p *KVStoreService) Set(kv [2]string, reply *struct{}) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	key, value := kv[0], kv[1]
	oldValue := p.m[key]
	// 当修改 key 对应的 value 时,调用每一个过滤器函数
	if 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)
	p.mu.Lock()
	// 注册过滤器函数
	p.filter[id] = func(key string) {
		ch <- key
	}
	p.mu.Unlock()
	select {
	// 是否超时
	case <-time.After(time.Duration(timeoutSecond) * time.Second):
		return errors.New("timeout")
	case key := <-ch:
		*keyChanged = key
		return nil
	}
	return nil
}

func main() {
	// 将 KVStoreService 对象注册为一个 RPC 服务
	// 将对象中所有满足 RPC 规则的对象方法注册为 RPC 函数
	// 所有注册的方法会放在 “KVStoreService” 服务空间执行
	_ = rpc.RegisterName("KVStoreService", NewKVStoreService())
	listener, err := net.Listen("tcp", ":1234")
	if err != nil {
		log.Fatal(err)
	}
	conn, err := listener.Accept()
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	// 在该 TCP 连接上为对方提供 RPC 服务
	rpc.ServeConn(conn)
}

package main

import (
	"fmt"
	"log"
	"net/rpc"
)

func main() {
	quit := make(chan struct{})
	client, err := rpc.Dial("tcp", ":1234")
	if err != nil {
		log.Fatal(err)
	}
	// 启动独立的 Goroutine 监控 key 的变化,同步阻塞,直到有 key 发生变化或者超时
	go func() {
		var keyChanged string
		err := client.Call("KVStoreService.Watch", 30, &keyChanged)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("watch: ", keyChanged)
		quit <- struct{}{}
	}()
	var key string
	// 获取某个 key 的值
	err = client.Call("KVStoreService.Get", "abc", &key)
	if err != nil {
		log.Println(err)
	} else {
		fmt.Println("get key: ", key)
	}
	// 设置某个 key 的值,因为原来对应的为空,调用该方法,会触发 Watch 返回
	err = client.Call("KVStoreService.Set", [2]string{"abc", "abc-value"}, new(struct{}))
	if err != nil {
		log.Fatal(err)
	}
	// 再次 key 的值
	err = client.Call("KVStoreService.Get", "abc", &key)
	if err != nil {
		log.Println(err)
	} else {
		fmt.Println("get key: ", key)
	}
	<-quit
}

首先启动一个独立的 Goroutine 监控 key 的变化。同步的watch调用会阻塞, 直到有 key 发生变化或者超时。然后在通过 Set 方法修改 KV 值时,服务器会将变化的 key 通过 Watch 方法返回。 这样我们就可以实现对某些状态的监控。

你可能感兴趣的:(golang)