最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Go語言中的監(jiān)視器模式與配置熱更新

 更新時(shí)間:2024年03月11日 08:37:22   作者:碼途漫漫  
這篇文章主要為大家詳細(xì)介紹了Go語言中的監(jiān)視器模式與配置熱更新的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

上篇介紹 GO 的 GUI 庫 Fyne 時(shí),提到 Fyne 的數(shù)據(jù)綁定用到了監(jiān)聽器模式。本文就展開說下我對 Go 中監(jiān)聽器模式的理解和應(yīng)用吧。

監(jiān)聽器模式簡介

監(jiān)聽器模式,或稱觀察者模式,它主要涉及兩個(gè)組件:主題(Subject)和監(jiān)聽器(Listener)。

Subject 負(fù)責(zé)維護(hù)一系列的監(jiān)聽器,在所觀測主題狀態(tài)變化,將這個(gè)事件通知給所有注冊的監(jiān)聽器。我統(tǒng)一將其定義為注冊中心 Registry。而監(jiān)聽器 Listener 則是實(shí)現(xiàn)了特定接口的對象,用于響應(yīng)事件消息,執(zhí)行處理邏輯。

對具體應(yīng)用而言,通常還會分出一個(gè) Watcher 或者 Monitor 用于檢測變化并推送給 Registry。從而實(shí)現(xiàn)將檢測目標(biāo)從系統(tǒng)解耦,無視監(jiān)控組件類別。

這個(gè)模式在組件之間建立一種松散耦合的關(guān)系。將特定事件通知到關(guān)心它的其他組件,無需它們直接相互引用??雌饋磉@個(gè)不也是發(fā)布-訂閱模式嗎?差不多一個(gè)意思。

之前工作中,用它最多的是配置的熱更新場景,這篇文章也會簡單介紹基于它的 ETCD 配置熱更新。

Go 實(shí)現(xiàn)監(jiān)聽器模式

如何用 Go 實(shí)現(xiàn)監(jiān)聽模式?我將定義兩個(gè)新類型分別是注冊中心(Registry)和監(jiān)聽器接口(Listener)。

首先是 Listener,它是一個(gè)接口,用于實(shí)現(xiàn)事件的響應(yīng)邏輯。

type Listener interface {
    OnTrigger()
}

先將其定義為一個(gè)接口,它的實(shí)現(xiàn)類型要求支持 OnTrigger 方法,會在事件發(fā)生時(shí)被執(zhí)行。

type Registry struct {
    listeners []Listener
}

func (r *Registry) Register(l Listener) {
    r.listeners = append(r.listeners, l)
}

func (r *Registry) NotifyAll() {
    for _, listener := range r.listeners {
        listener.OnTrigger(key, value)
    }
}

Registry 是所有監(jiān)聽器的注冊地,當(dāng)特定事件發(fā)生,我們通過 Registry.NotifyAll 將事件傳遞給所有 Listener。

我們實(shí)現(xiàn)一個(gè)簡單的案例,當(dāng)監(jiān)聽到某個(gè)事件發(fā)生,打印 "A specified event accured"。

為了模擬效果,本案例沒有 watcher,直接通過主函數(shù)調(diào)用 NotifyAll 模擬觸發(fā)事件。

為了打印事件消息,我們實(shí)現(xiàn) Listener接口,創(chuàng)建新類型 EventPrinter,如下所示:

type EventPrinter struct {
}

func (printer *EventPrinter) OnTrigger() {
  fmt.Println("A specified event accured!")
}

寫個(gè)主函數(shù)觸發(fā)下事件,測試看看是否符合預(yù)期,代碼如下所示:

func main() {
  r := &Registry{}
  r.Reigster(&EventPrinter{})

  // 模擬接收到消息,觸發(fā)事件通知
  r.NotifyAll()
}

執(zhí)行測試,內(nèi)容如下所示:

$ go run main.go
A specified event occurred

如果希望自定義處理函數(shù),只需讓 Listener 支持自定義事件回調(diào)函數(shù)即可。

修改代碼如下所示:

type EventHandler struct {
  callback func()
}

func NewEventHandler(callback func()) *EventHandler {
  return &EventHandler{callback: callback}
}
func (e *EventHandler) OnTrigger() {
  e.callback()
}

我們注冊一個(gè) EventHandler 到 Registry,主函數(shù)代碼:

func main() {
  r := &Registry{}
  r.Reigster(&EventPrinter{})
  r.Reigster(NewEventHandler(func() {
    fmt.Println("Custom Print: a specified event occurred!")  
  }))
  r.NotifyAll()
}

測試執(zhí)行:

$ go run main.go
A specified event occurred!
Custom Print: a specified event occurred!

基于 Go Channel 實(shí)現(xiàn)并發(fā)處理

前面的示例中 NotifyAll 是通過 for 循環(huán)依次調(diào)用 listener.OnTrigger 將消息發(fā)送給 Listener,處理效率低下。

如何加速呢?

最直接的方法是通過 goroutine 運(yùn)行 listener.OnTrigger 方法。

func (r *Registry) NotifyAll() {
  for _, listener := range r.listeners {
      go listener.OnTrigger()
  }
}

還有一種方法,通過 Channel 傳遞事件消息,這樣每個(gè) Listener 有獨(dú)立的 goroutine 監(jiān)聽和處理。

如下是 Listener 的實(shí)現(xiàn)代碼:

type Listener struct {
    EventChannel chan struct{}
    Callback     func()
}

func NewListener(callback func()) *Listener {
    return &Listener{
        EventChannel: make(chan struct{}, 1), // 帶緩沖的 channel,防止阻塞
        Callback:     callback,
    }
}

func (l *Listener) Start() {
    go func() {
        for range l.EventChannel {
            l.Callback()
        }
    }()
}

這里 Listener 的事件處理函數(shù)在單獨(dú)的 goroutine 中運(yùn)行。而相應(yīng)的 Registry 實(shí)現(xiàn)也需要修改,代碼變更如下所示:

type Registry struct {
    listeners []*Listener
}

func (r *Registry) Register(listener *Listener) {
    r.listeners = append(r.listeners, listener)
    listener.Start() // 啟動(dòng)監(jiān)聽器的 goroutine
}

func (r *Registry) NotifyAll(message string) {
    for _, listener := range r.listeners {
        listener.EventChannel <- struct{}{} // 發(fā)送事件到監(jiān)聽器
    }
}

func (r *Registry) Close() {
    for _, listener := range r.listeners {
        close(listener.EventChannel) // 關(guān)閉 channel,停止監(jiān)聽器 goroutine
    }
}

整體上的變化不大,在 listner.Register 方法中啟動(dòng) Listener 事件處理 goroutine 等待事件消息。

實(shí)際案例:ETCD 配置熱更新

讓我們實(shí)踐一個(gè)具體的應(yīng)用場景:實(shí)現(xiàn)配置的動(dòng)態(tài)更新以及組件的自動(dòng)重連機(jī)制。

我們將針對包括 MySQL、Redis 在內(nèi)的各種組件,實(shí)現(xiàn)它們在配置變更時(shí)能夠自動(dòng)重連。這些組件的配置信息將以 JSON 格式存儲于 ETCD 的多個(gè)鍵(Key)中。

假設(shè),配置結(jié)構(gòu)如下所示:

type MySQLConfig struct {
  Host     string
  Port     int
  User     string
  Password string
}

type RedisConfig struct {
  Host     string
  Port     int
}

這些配置被保存在 ETCD 中,我們要實(shí)時(shí)監(jiān)控配置的變化并據(jù)此更新配置和執(zhí)行重連操作。

示例用法如下所示:

registry.Register("/config/mysql", func(data) {
  // unmarshal data
  // reconnect mysql
})

讓我們基于監(jiān)聽器模式簡單設(shè)計(jì)一個(gè)模塊,實(shí)現(xiàn) ETCD 熱更新:

  • 每個(gè)監(jiān)聽器可以訂閱特定的 key 或 key 前綴的更新事件。
  • 使用 channel 通知配置變更,觸發(fā)對應(yīng)的監(jiān)聽回調(diào)。

這個(gè)示例,函數(shù)回調(diào)和輪詢其實(shí)已經(jīng)滿足需求,此處只是為了演示,而是否使用 channel 要具體分析。

我們這個(gè)設(shè)計(jì)要涉及到三個(gè)部分。分別是 Watcher、Listener 和 Registry。

  • Watcher 責(zé)監(jiān)聽 ETCD 中的 key 變更事件。
  • Listener 定義了當(dāng)特定 key 發(fā)生變化時(shí)需要執(zhí)行的回調(diào)邏輯。
  • Registry 管理所有 Listener,將 ETCD 變更事件分發(fā)給對應(yīng) Listener。

先定義 Event 類型,一個(gè)簡單的結(jié)構(gòu)體,表示 ETCD 中 key 的變更事件:

type Event struct {
  Key   string
  Value string
}

Listener

Listener 實(shí)現(xiàn)如下所示:

type Listener struct {
    EventChannel chan *Event
    Callback     func(*Event)
}

func NewListner(callback func(*Event)) *Listener {
    l := &Listener{
        EventChannel: make(chan *Event),
        Callback:     callback,
    }
    return l
}

func (l *Listener) Start() {
    go func() {
        for event := range l.EventChannel {
            l.Callback(event)
        }
    }()
}

基本之前的沒太大差別,從 EventChannel 中拿到事件消息,調(diào)用回調(diào)函數(shù)。

實(shí)現(xiàn) Registry

Registry 負(fù)責(zé)維護(hù) Listener 的注冊,并在接收到 key 變更事件時(shí)通知相關(guān)的 Listener

type Registry struct {
    listeners map[string][]*Listener
}

func NewRegistry() *Registry {
    return &Registry{
        listeners: make(map[string][]*Listener),
    }
}

func (r *Registry) Register(key string, listener *Listener) {
    r.listeners[key] = append(r.listeners[key], listener)
    listener.Start()
}

func (r *Registry) Notify(event *Event) {
    if listeners, ok := r.listeners[event.Key]; ok {
        for _, listener := range listeners {
            listener.EventChannel <- event
        }
    }
}

注冊 Listener 到 Registry 中,通過 map 將 key 與 Listener 關(guān)聯(lián)起來。

實(shí)現(xiàn) Watcher

Watcher 負(fù)責(zé)從 ETCD 訂閱 key 的變更事件,并將這些事件發(fā)送到 Registry 的 eventChannel 上:

func WatchEtcdKeys(client *clientv3.Client, registry *Registry, watchKeys ...string) {
  for _, key := range watchKeys {
    go func(key string) {
      watchChan := client.Watch(context.Background(), key, clientv3.WithPrefix())
      for wresp := range watchChan {
        for _, ev := range wresp.Events {
          event := &Event{
            Key:   string(ev.Kv.Key),
            Value: string(ev.Kv.Value),
          }
          registry.Notify(event)
        }
      }
    }(key)
  }
}

使用示例

讓我們實(shí)際在 main 函數(shù)上使用一下,觀察行為是否正常。

func main() {
  client, err := clientv3.New(clientv3.Config{
      Endpoints:   []string{"localhost:2379"},
  })
  if err != nil {
      log.Fatal(err)
  }
  defer client.Close()

  registry := NewRegistry()
  // 注冊監(jiān)聽器
  registry.Register("/config/mysql", NewListener(func(event * Event) {
    fmt.Println(event)
    // 執(zhí)行數(shù)據(jù)重連之類的操作
  }))

  // 開始監(jiān)聽 ETCD key 變更
  WatchEtcdKeys(client, registry, "/config/")
  time.Sleep(10 * time.Minute)
}

這個(gè)示例創(chuàng)建了一個(gè) ETCD 客戶端,初始化了一個(gè) Registry,并為特定的 key 注冊了一個(gè) Listener。然后,通過 WatchEtcdKeys 函數(shù)開始監(jiān)聽 /config/ 前綴下的所有 key 的變更。

這種設(shè)計(jì)支持對特定 key 或 key 前綴的監(jiān)聽。當(dāng)相關(guān) key 變更時(shí),通過 channel 通知 Listener,而收到更新事件后的具體操作。視場景而定,這里是執(zhí)行重連操作。

特別說明,示例僅作為概念驗(yàn)證,實(shí)際應(yīng)用中需要更多的錯(cuò)誤處理和優(yōu)化。

到此這篇關(guān)于詳解Go語言中的監(jiān)視器模式與配置熱更新的文章就介紹到這了,更多相關(guān)Go監(jiān)視器模式與配置熱更新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言實(shí)現(xiàn)基于模板替換內(nèi)容生成Word文檔

    Go語言實(shí)現(xiàn)基于模板替換內(nèi)容生成Word文檔

    在自動(dòng)化辦公、批量生成文檔、模板填充等場景中,經(jīng)常需要程序化處理?Word?文件,下面我們就來看看如何使用github.com/nguyenthenguyen/docx庫實(shí)現(xiàn)基于模板替換內(nèi)容生成?Word?文檔吧
    2026-03-03
  • Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解

    Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解

    這篇文章主要介紹了Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解,需要的朋友可以參考下
    2022-04-04
  • 詳解如何利用Golang泛型提高編碼效率

    詳解如何利用Golang泛型提高編碼效率

    Golang的泛型已經(jīng)出來有一段時(shí)間了,大家應(yīng)該或多或少對它有所了解。雖然Golang的泛型在功能上確實(shí)比較簡單,而且確實(shí)可能會增加代碼的復(fù)雜度,過度使用可能還會降低代碼可讀性。本文就來介紹一下Golang泛型的相關(guān)知識吧
    2023-04-04
  • Golang泛型與類型約束的用法詳解

    Golang泛型與類型約束的用法詳解

    這篇文章主要介紹了Golang泛型與類型約束的用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換)

    go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換)

    這篇文章主要介紹了go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • graphql---go http請求使用詳解

    graphql---go http請求使用詳解

    這篇文章主要介紹了graphql---go http請求使用詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 一文帶你掌握Go語言中的時(shí)間處理操作

    一文帶你掌握Go語言中的時(shí)間處理操作

    這篇文章主要為大家詳細(xì)介紹了Go語言中的時(shí)間處理操作的相關(guān)知識,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下
    2026-04-04
  • golang抓取tcp包的實(shí)現(xiàn)方式

    golang抓取tcp包的實(shí)現(xiàn)方式

    使用`golang`的`packet`和`pcap`庫可以抓取TCP數(shù)據(jù)包,首先,確保安裝了`pcap`庫,然后使用以下代碼打開網(wǎng)絡(luò)接口,設(shè)置過濾規(guī)則為“tcp”,開始捕獲并解析TCP數(shù)據(jù)包,運(yùn)行代碼時(shí)需要管理員權(quán)限
    2024-12-12
  • 詳解如何使用Go語言進(jìn)行文件監(jiān)控和通知

    詳解如何使用Go語言進(jìn)行文件監(jiān)控和通知

    在Go語言中,文件監(jiān)控通常涉及到文件系統(tǒng)事件的監(jiān)聽,文件或目錄的狀態(tài)發(fā)生變化(如創(chuàng)建、刪除、修改等)時(shí),你的程序需要得到通知,所以本文給大家介紹了如何使用Go語言進(jìn)行文件監(jiān)控和通知,需要的朋友可以參考下
    2024-06-06
  • 一文詳解Golang的函數(shù)特性

    一文詳解Golang的函數(shù)特性

    函數(shù)是?Golang?中非常重要的組成部分之一,它們提供了代碼的可重用性和組織性。在本文中,我們將深入了解?Golang?函數(shù)的多個(gè)方面,希望對大家有所幫助
    2023-04-04

最新評論

南漳县| 巴马| 临江市| 马鞍山市| 杭锦旗| 德昌县| 固阳县| 佳木斯市| 沽源县| 马龙县| 青阳县| 乐都县| 伊春市| 汕头市| 铁力市| 当阳市| 徐水县| 五台县| 台州市| 玉屏| 乌拉特中旗| 增城市| 兴仁县| 开江县| 南皮县| 饶河县| 肥乡县| 读书| 榆林市| 双柏县| 会同县| 屏山县| 昭觉县| 遂溪县| 临湘市| 钟山县| 泸西县| 青田县| 绍兴市| 商南县| 浦江县|