用Go造輪子 管理集群中的配置檔案

2022-07-04 22:24:10 字數 3749 閱讀 6530

最近一年來,我都在做公司的rtb廣告系統,包括ssp**服務,adx服務和dsp系統。因為是第一次在公司用go語言實現這麼乙個大的系統,中間因為各種原因造了很多輪子。現在稍微有點時間,覺著有必要總結這一年來用go造輪子的經驗和不足。

rtb廣告系統中涉及到的服務程式並不算很多,但是因為rtb系統會面臨很多的流量,而且為了確保可用性,最基本的就是多例項組成集群,同時考慮到後續業務增長,集群的擴縮容也是要做的。我們在設計的時候,基於zoookeeper做了服務發現,而我們的服務接入依靠nginx集群,然後通過反向**把請求負載均衡到不同的服務例項中。這裡就存在以下問題:

業界其實有很多成熟的方案解決這類問題:

解析模版,獲取要動態查詢的節點

向指定的伺服器,比如zookeeper發起查詢請求,並觀察指定節點的變化

當第一次或者節點發生變更後,查詢最新資料

把最新資料應用到模版中生成新的配置檔案資料

儲存最新的配置檔案資料到目標路徑,並呼叫指定的命令應用最新的配置檔案

go官方標準庫提供了template包,支援if, range等控制語句,也支援使用者自定義方法。模版機制的方便之處在於,它本身是一種dsl,也算是一種支援計算的超級printf。舉例如下:

package main

import (

"os"

"text/template"

)var tplcontent = `

}server } } }

}`type apigatewayservice struct

func main() ,}},

}).parse(tplcontent)

if err != nil

tmpl.execute(os.stdout, nil)、

}

上面的**實際上是做了類似這樣的過程,為了簡單描述,我還是直接寫一段go**

package main

import (

"fmt"

"os"

)type apigatewayservice struct

func service(servicename string) apigatewayservice , }}

func main()

}

上面的**中,模版內容如下:

var tplcontent = `

}server } } }

}`

整個**實現中略微複雜,其中的核心既不是如何快取,也不是後台如何查詢,而是要記錄下未知的服務名,以及假如已經知道了服務名並且快取了資料,如何從快取中查詢資料,這個過程還是拿service方法舉例,**如下:

func servicefunc(tracker *datatracker, used, missing map[string]dependency.dependency) func(...string) (dependency.service, error) 

d, err := dependency.parseservice(s...)

if err != nil

adddependency(used, d)

data, ok := tracker.get(d)

if ok

adddependency(missing, d)

return r, nil

}}

其中tracker表示的是快取物件,used和missing是乙個map,其中value型別是dependency.dependency, key為乙個dependency的hashcode,也就是乙個唯一身份標識。

dependency是乙個介面,主要用於查詢資料,是乙個阻塞過程。servicefunc返回乙個lambda,內部主要主要的事情是記錄哪個dependency物件用到了,並且嘗試從tracker中查詢快取資料,如果有就返回,沒有更新missing,記錄dependency物件沒有相應的快取資料,然後返回空資料。

當我們完成了解析和渲染過程後,我們要檢查渲染過程中記錄的missing是否不為空,如果不空,就需要發起後台查詢進行處理,大體過程如下:

if len(missing) > 0 

}continue

}

其中每個dependency物件都有乙個fetch方法,當通過watcher.add方法時,就會啟動乙個獨立的goroutine負責呼叫dependency.fetch方法,然後通過channel把dependency.fetch的結果轉給datatracker進行快取。

func atomicwrite(path string, contents byte, perms os.filemode, backup bool) error 

} f, err := ioutil.tempfile(parent, "")

if err != nil

defer os.remove(f.name())

if _, err := f.write(contents); err != nil

if err := f.sync(); err != nil

if err := f.close(); err != nil

if err := os.chmod(f.name(), perms); err != nil

if backup

} }if err := os.rename(f.name(), path); err != nil

return nil

}

當儲存好檔案後,我們需要呼叫指定的命令,通知相應的程式載入最新的配置,主要**如下:

func execute(command string, timeout time.duration) error  else 

cmd := exec.command(shell, flag, command)

cmd.stdout = os.stdout

cmd.stderr = os.stderr

if err := cmd.start(); err != nil

done := make(chan error, 1)

go func() ()

select

} <-done // allow the goroutine to finish

return fmt.errorf(

"command %q\n"+

"did not return for %s - if your command does not return, please\n"+

"make sure to background it",

command, timeout)

case err := <-done:

return err

}}

這個程式目前已經有一年之久,現在回顧頭來看,幸好還記得當初的決策原因。通過這個程式,對模版的使用倒是掌握了很多。目前這個輪子功能還較為簡單,僅僅實現了乙個service方法,但是dependency包是獨立抽象的,可以支援任意的儲存型別,比如consul。目前線上執行穩定。

這個工具在nginx配置管理中,較為方便,當然在一些流量較大的場景中,如果後端服務例項較多,擴縮容時會帶來較大的波動,這點可以參考微博團隊的upsync:微博開源基於nginx容器動態流量管理方案

但是我們沒有採用這個方案,考慮到的是基於模版的更新機制更為簡單,支援更多的服務,普適性更強,整體也更容易維護。

前端造輪子(二) JS中的深複製與淺複製

在讀到這篇文章前,對淺複製的理解存在誤區 js 深拷貝 vs 淺拷貝 var obj1 var obj2 obj1 varshallowcopy function obj for var prop in obj return copy var obj3 shallowcopy obj1 現在改變ob...

hadoop集群環境安裝中的hosts 配置問題

今天在安裝hadoop集群的時候,所有節點配置完畢,發現執行下面的命令的時候 hadoop name node hadoop bin hadoop fs ls name節點會報如下錯誤 11 04 02 17 16 13 warn conf.configuration mapred.task.id ...

GO中的包管理

我們知道,程式是可以通過封裝來提高 的復用性。那麼go就是可以通過包管理的機制來管理 go modules於go語言1.11版本時引入,在1.12版本正式支援,是由go語言官方提供的包管理解決方案。modules是相關go包的集合,是源 交換和版本控制的單元。go命令直接支援使用modules,包括...