圖解kubernetes容器運行時狀態緩存數據結構

緩存和發佈訂閱都是後端開發中常用的手段,其中緩存主要是用於可丟失數據的暫存,發佈訂閱主要是用於消息傳遞,今天給大家介紹一個k8s中帶有發佈訂閱的緩存實現,其目標是給定一個時間,只關注該時間後續的事件,主要是用於近實時狀態數據的獲取

1. 業務背景

image.png在k8s中的kubelet中支持不同的容器運行時,爲了緩存容器運行時當前所有可見的Pod/Container就構造了一個Cache結構,當一個事件發生後,kubelet接收到事件後,此時需要獲取當前Pod的狀態,此時要獲取的狀態,就必須要求是在事件產生後的最新的狀態,而不能是之前的狀態,

2. 核心實現

image.png

2.1 數據與訂閱記錄

2.1.1 狀態數據

狀態數據主要是存儲一個pod的狀態數據

type data struct {
    // 存儲Pod的狀態
    status *PodStatus
    // 試圖檢測Pod狀態出錯信息
    err error
    // 上次數據的修改時間
    modified time.Time
}

2.1.2 訂閱記錄

訂閱記錄其實指的是一個訂閱需求,其通過一個chan來進行數據通知,其中time字段是過濾條件,即只有時間大於time的記錄才允許被加入到chan中

type subRecord struct {
    time time.Time
    ch   chan *data
}

2.2 Cache實現

2.2.1 核心成員結構

cache裏面的數據在kubelet每次進行PLEG更新的時候,都會更新timestamp,並且會重新獲取最新的Pod狀態進行填充cache,所以這裏會更新timestamp,寓意着讓之前舊的狀態都過期,並且會針對舊的訂閱的進行數據的返回

// cache implements Cache.
type cache struct {
    // 讀寫鎖
    lock sync.RWMutex
    // 存儲Pod的狀態數據,用於滿足不帶時間戳的狀態獲取
    pods map[types.UID]*data
    // 全局時間戳,即當前緩存中的數據,至少都要比該時間戳新
    timestamp *time.Time
    //存儲對應Pod的定語記錄列表
    subscribers map[types.UID][]*subRecord
}

2.2.3 普通狀態數據獲取

普通狀態獲取即直接通過Map來進行數據的返回

func (c *cache) Get(id types.UID) (*PodStatus, error) {
    c.lock.RLock()
    defer c.lock.RUnlock()
    d := c.get(id)
    return d.status, d.err
}

2.2.4 默認狀態構造器

當發現當前的cahce中並不存在對應的數據,則是直接根據ID來生成一個默認的狀態數據

func (c *cache) get(id types.UID) *data {
    d, ok := c.pods[id]
    if !ok {
        return makeDefaultData(id)
    }
    return d
}
// 默認狀態構造器
func makeDefaultData(id types.UID) *data {
    return &data{status: &PodStatus{ID: id}, err: nil}
}

2.2.5 最新狀態數據獲取

會給定一個時間戳,只有噹噹前緩存的數據的時間在該時間戳之後,纔有效,否則返回nil,這裏有個關鍵點就是timestamp的相關設計,因爲在每個PLEG週期中,都會更新timestamp

如果minTime

func (c *cache) getIfNewerThan(id types.UID, minTime time.Time) *data {
    // 獲取當前的狀態
    d, ok := c.pods[id]

    // 如果全局時間戳大於給定的時間,則會直接返回
    globalTimestampIsNewer := (c.timestamp != nil && c.timestamp.After(minTime))
    if !ok && globalTimestampIsNewer {
        // 狀態沒有緩存,但是全局時間比最小時間新,就直接返回
        return makeDefaultData(id)
    }
    // 如果之前數據的時間在獲取時間之後,或者全局時間已經更新
    if ok && (d.modified.After(minTime) || globalTimestampIsNewer) {
        return d
    }
    // The pod status is not ready.
    return nil
}

2.2.6 訂閱狀態管道構造

訂閱管道最終會返回一個狀態的管道,同時會進行檢查,如果發現當前有可用數據,則會直接丟進管道中,否則則創建一個subRecords訂閱記錄,並保存

func (c *cache) subscribe(id types.UID, timestamp time.Time) chan *data {
    ch := make(chan *data, 1)
    c.lock.Lock()
    defer c.lock.Unlock()
    // 獲取狀態數據
    d := c.getIfNewerThan(id, timestamp)
    if d != nil {
        // 如果已經有狀態數據,則立即返回
        ch <- d
        return ch
    }
    // 否則添加一個訂閱記錄到subscribers中對應的列表中
    c.subscribers[id] = append(c.subscribers[id], &subRecord{time: timestamp, ch: ch})
    return ch
}

2.2.7 通知清理過期管道

通知的時候回根據subRecord的訂閱時間進行檢測,如果訂閱時間已經超過當前的 timestamp則直接獲取數據進行返回,最後只會保留那些還未過期的訂閱記錄

func (c *cache) notify(id types.UID, timestamp time.Time) {
    // 獲取事件的ID列表
    list, ok := c.subscribers[id]
    if !ok {
        // No one to notify.
        return
    }
    newList := []*subRecord{}
    // 遍歷所有的訂閱記錄subRecords
    for i, r := range list {
        // 如果這些訂閱記錄的時間在timestamp之前,就不進行操作, 即當前管道時間>timestamp
        if timestamp.Before(r.time) {
            newList = append(newList, list[i])
            continue
        }
        // 獲取一個數據返回, 同時關閉管道
        r.ch <- c.get(id)
        close(r.ch)
    }
    if len(newList) == 0 {
        // 如果不存在訂閱記錄,則就刪除對應的key
        delete(c.subscribers, id)
    } else {
        // 剩餘的訂閱列表
        c.subscribers[id] = newList
    }
}

2.2.8 全局時間戳更新

全局時間戳更新,則會遍歷所有的訂閱,以最新的全局時間戳作爲時間,進行通知

func (c *cache) UpdateTime(timestamp time.Time) {
    c.lock.Lock()
    defer c.lock.Unlock()
    c.timestamp = &timestamp
    // Notify all the subscribers if the condition is met.
    for id := range c.subscribers {
        c.notify(id, *c.timestamp)
    }
}

2.2.9 Pod事件更新通知函數

更新的時候,則會調用notify來進行通知

func (c *cache) Set(id types.UID, status *PodStatus, err error, timestamp time.Time) {
    c.lock.Lock()
    defer c.lock.Unlock()
    // 進行事件的通知
    defer c.notify(id, timestamp)
    // 保存最新的狀態數據 
    c.pods[id] = &data{status: status, err: err, modified: timestamp}
}

今天就到這裏,這些數據結構和設計有很多值得學習地方,希望大家能多多交流,一起學習雲原生相關的設計與關鍵實現

微信號:baxiaoshi2020

關注公告號閱讀更多源碼分析文章 21天大棚

更多文章關注 www.sreguide.com

本文由博客一文多發平臺 OpenWrite 發佈

發佈了28 篇原創文章 · 獲贊 0 · 訪問量 2208
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章