99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Go 語言 分布式配置管理

2023-03-22 15:05 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch6-cloud/ch6-06-config.html


6.6 分布式配置管理

在分布式系統(tǒng)中,常困擾我們的還有上線問題。雖然目前有一些優(yōu)雅重啟方案,但實際應(yīng)用中可能受限于我們系統(tǒng)內(nèi)部的運行情況而沒有辦法做到真正的 “優(yōu)雅”。比如我們?yōu)榱藢θハ掠蔚牧髁窟M行限制,在內(nèi)存中堆積一些數(shù)據(jù),并對堆積設(shè)定時間或總量的閾值。在任意閾值達到之后將數(shù)據(jù)統(tǒng)一發(fā)送給下游,以避免頻繁的請求超出下游的承載能力而將下游打垮。這種情況下重啟要做到優(yōu)雅就比較難了。

所以我們的目標(biāo)還是盡量避免采用或者繞過上線的方式,對線上程序做一些修改。比較典型的修改內(nèi)容就是程序的配置項。

6.6.1 場景舉例

6.6.1.1 報表系統(tǒng)

在一些偏 OLAP 或者離線的數(shù)據(jù)平臺中,經(jīng)過長期的迭代開發(fā),整個系統(tǒng)的功能模塊已經(jīng)漸漸穩(wěn)定??勺儎拥捻椫怀霈F(xiàn)在數(shù)據(jù)層,而數(shù)據(jù)層的變動大多可以認為是 SQL 的變動,架構(gòu)師們自然而然地會想著把這些變動項抽離到系統(tǒng)外部。比如本節(jié)所述的配置管理系統(tǒng)。

當(dāng)業(yè)務(wù)提出了新的需求時,我們的需求是將新的 SQL 錄入到系統(tǒng)內(nèi)部,或者簡單修改一下老的 SQL。不對系統(tǒng)進行上線,就可以直接完成這些修改。

6.6.1.2 業(yè)務(wù)配置

大公司的平臺部門服務(wù)眾多業(yè)務(wù)線,在平臺內(nèi)為各業(yè)務(wù)線分配唯一 id。平臺本身也由多個模塊構(gòu)成,這些模塊需要共享相同的業(yè)務(wù)線定義(要不然就亂套了)。當(dāng)公司新開產(chǎn)品線時,需要能夠在短時間內(nèi)打通所有平臺系統(tǒng)的流程。這時候每個系統(tǒng)都走上線流程肯定是來不及的。另外需要對這種公共配置進行統(tǒng)一管理,同時對其增減邏輯也做統(tǒng)一管理。這些信息變更時,需要自動通知到業(yè)務(wù)方的系統(tǒng),而不需要人力介入(或者只需要很簡單的介入,比如點擊審核通過)。

除業(yè)務(wù)線管理之外,很多互聯(lián)網(wǎng)公司會按照城市來鋪展自己的業(yè)務(wù)。在某個城市未開城之前,理論上所有模塊都應(yīng)該認為帶有該城市 id 的數(shù)據(jù)是臟數(shù)據(jù)并自動過濾掉。而如果業(yè)務(wù)開城,在系統(tǒng)中就應(yīng)該自己把這個新的城市 id 自動加入到白名單中。這樣業(yè)務(wù)流程便可以自動運轉(zhuǎn)。

再舉個例子,互聯(lián)網(wǎng)公司的運營系統(tǒng)中會有各種類型的運營活動,有些運營活動推出后可能出現(xiàn)了超出預(yù)期的事件(比如公關(guān)危機),需要緊急將系統(tǒng)下線。這時候會用到一些開關(guān)來快速關(guān)閉相應(yīng)的功能。或者快速將想要剔除的活動 id 從白名單中剔除。在 Web 章節(jié)中的 AB 測試一節(jié)中,我們也提到,有時需要有這樣的系統(tǒng)來告訴我們當(dāng)前需要放多少流量到相應(yīng)的功能代碼上。我們可以像那一節(jié)中,使用遠程 RPC 來獲知這些信息,但同時,也可以結(jié)合分布式配置系統(tǒng),主動地拉取到這些信息。

6.6.2 使用 etcd 實現(xiàn)配置更新

我們使用 etcd 實現(xiàn)一個簡單的配置讀取和動態(tài)更新流程,以此來了解線上的配置更新流程。

6.6.2.1 配置定義

簡單的配置,可以將內(nèi)容完全存儲在 etcd 中。比如:

etcdctl get /configs/remote_config.json
{
    "addr" : "127.0.0.1:1080",
    "aes_key" : "01B345B7A9ABC00F0123456789ABCDAF",
    "https" : false,
    "secret" : "",
    "private_key_path" : "",
    "cert_file_path" : ""
}

6.6.2.2 新建 etcd client

cfg := client.Config{
    Endpoints:               []string{"http://127.0.0.1:2379"},
    Transport:               client.DefaultTransport,
    HeaderTimeoutPerRequest: time.Second,
}

直接用 etcd client 包中的結(jié)構(gòu)體初始化,沒什么可說的。

6.6.2.3 配置獲取

resp, err = kapi.Get(context.Background(), "/path/to/your/config", nil)
if err != nil {
    log.Fatal(err)
} else {
    log.Printf("Get is done. Metadata is %q\n", resp)
    log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value)
}

獲取配置使用 etcd KeysAPI 的 Get() 方法,比較簡單。

6.6.2.4 配置更新訂閱

kapi := client.NewKeysAPI(c)
w := kapi.Watcher("/path/to/your/config", nil)
go func() {
    for {
        resp, err := w.Next(context.Background())
        log.Println(resp, err)
        log.Println("new values is", resp.Node.Value)
    }
}()

通過訂閱 config 路徑的變動事件,在該路徑下內(nèi)容發(fā)生變化時,客戶端側(cè)可以收到變動通知,并收到變動后的字符串值。

6.6.2.5 整合起來

package main

import (
    "log"
    "time"

    "golang.org/x/net/context"
    "github.com/coreos/etcd/client"
)

var configPath =  `/configs/remote_config.json`
var kapi client.KeysAPI

type ConfigStruct struct {
    Addr           string `json:"addr"`
    AesKey         string `json:"aes_key"`
    HTTPS          bool   `json:"https"`
    Secret         string `json:"secret"`
    PrivateKeyPath string `json:"private_key_path"`
    CertFilePath   string `json:"cert_file_path"`
}

var appConfig ConfigStruct

func init() {
    cfg := client.Config{
        Endpoints:               []string{"http://127.0.0.1:2379"},
        Transport:               client.DefaultTransport,
        HeaderTimeoutPerRequest: time.Second,
    }

    c, err := client.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
    kapi = client.NewKeysAPI(c)
    initConfig()
}

func watchAndUpdate() {
    w := kapi.Watcher(configPath, nil)
    go func() {
        // watch 該節(jié)點下的每次變化
        for {
            resp, err := w.Next(context.Background())
            if err != nil {
                log.Fatal(err)
            }
            log.Println("new values is", resp.Node.Value)

            err = json.Unmarshal([]byte(resp.Node.Value), &appConfig)
            if err != nil {
                log.Fatal(err)
            }
        }
    }()
}

func initConfig() {
    resp, err = kapi.Get(context.Background(), configPath, nil)
    if err != nil {
        log.Fatal(err)
    }

    err := json.Unmarshal(resp.Node.Value, &appConfig)
    if err != nil {
        log.Fatal(err)
    }
}

func getConfig() ConfigStruct {
    return appConfig
}

func main() {
    // init your app
}

如果業(yè)務(wù)規(guī)模不大,使用本節(jié)中的例子就可以實現(xiàn)功能了。

這里只需要注意一點,我們在更新配置時,進行了一系列操作:watch 響應(yīng),json 解析,這些操作都不具備原子性。當(dāng)單個業(yè)務(wù)請求流程中多次獲取 config 時,有可能因為中途 config 發(fā)生變化而導(dǎo)致單個請求前后邏輯不一致。因此,在使用類似這樣的方式來更新配置時,需要在單個請求的生命周期內(nèi)使用同樣的配置。具體實現(xiàn)方式可以是只在請求開始的時候獲取一次配置,然后依次向下透傳等等,具體情況具體分析。

6.6.3 配置膨脹

隨著業(yè)務(wù)的發(fā)展,配置系統(tǒng)本身所承載的壓力可能也會越來越大,配置文件可能成千上萬。客戶端同樣上萬,將配置內(nèi)容存儲在 etcd 內(nèi)部便不再合適了。隨著配置文件數(shù)量的膨脹,除了存儲系統(tǒng)本身的吞吐量問題,還有配置信息的管理問題。我們需要對相應(yīng)的配置進行權(quán)限管理,需要根據(jù)業(yè)務(wù)量進行配置存儲的集群劃分。如果客戶端太多,導(dǎo)致了配置存儲系統(tǒng)無法承受瞬時大量的 QPS,那可能還需要在客戶端側(cè)進行緩存優(yōu)化,等等。

這也就是為什么大公司都會針對自己的業(yè)務(wù)額外開發(fā)一套復(fù)雜配置系統(tǒng)的原因。

6.6.4 配置版本管理

在配置管理過程中,難免出現(xiàn)用戶誤操作的情況,例如在更新配置時,輸入了無法解析的配置。這種情況下我們可以通過配置校驗來解決。

有時錯誤的配置可能不是格式上有問題,而是在邏輯上有問題。比如我們寫 SQL 時少 select 了一個字段,更新配置時,不小心丟掉了 json 字符串中的一個 field 而導(dǎo)致程序無法理解新的配置而進入詭異的邏輯。為了快速止損,最快且最有效的辦法就是進行版本管理,并支持按版本回滾。

在配置進行更新時,我們要為每份配置的新內(nèi)容賦予一個版本號,并將修改前的內(nèi)容和版本號記錄下來,當(dāng)發(fā)現(xiàn)新配置出問題時,能夠及時地回滾回來。

常見的做法是,使用 MySQL 來存儲配置文件或配置字符串的不同版本內(nèi)容,在需要回滾時,只要進行簡單的查詢即可。

6.6.5 客戶端容錯

在業(yè)務(wù)系統(tǒng)的配置被剝離到配置中心之后,并不意味著我們的系統(tǒng)可以高枕無憂了。當(dāng)配置中心本身宕機時,我們也需要一定的容錯能力,至少保證在其宕機期間,業(yè)務(wù)依然可以運轉(zhuǎn)。這要求我們的系統(tǒng)能夠在配置中心宕機時,也能拿到需要的配置信息。哪怕這些信息不夠新。

具體來講,在給業(yè)務(wù)提供配置讀取的 SDK 時,最好能夠?qū)⒛玫降呐渲迷跇I(yè)務(wù)機器的磁盤上也緩存一份。這樣遠程配置中心不可用時,可以直接用硬盤上的內(nèi)容來做兜底。當(dāng)重新連接上配置中心時,再把相應(yīng)的內(nèi)容進行更新。

加入緩存之后務(wù)必需要考慮的是數(shù)據(jù)一致性問題,當(dāng)個別業(yè)務(wù)機器因為網(wǎng)絡(luò)錯誤而與其它機器配置不一致時,我們也應(yīng)該能夠從監(jiān)控系統(tǒng)中知曉。

我們使用一種手段解決了我們配置更新痛點,但同時可能因為使用的手段而帶給我們新的問題。實際開發(fā)中,我們要對每一步?jīng)Q策多多思考,以使自己不在問題到來時手足無措。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號