本文主要參考《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 方法返回。 這樣我們就可以實現對某些狀態的監控。