使用 Go 優化我們的接口

使用 Go 優化我們的接口

標題起的是有點大,不過還好本片文章主要也是使用 Go 來優化 HTTP 服務的,也算打個擦邊球吧~

背景

特徵數據暴增,導致獲取一個城市下所有的特徵的接口延時高,下面是監控上看到的接口響應耗時,最慢的時候接口響應時間能達到 5s 多。

使用 Go 優化我們的接口


緩存優化方案

代碼優化思路:

1,使用緩存。

1.1為什麼使用內存,而不是 Redis?

分析業務需求,當前需要存儲起來的數據是ObjectId,ObjectId 是一個長度為14左右的字符串,我們假設平均下來ObjectId是長度為16的字符串,這樣算下來就是每個 ObjectId 佔用的內存大小是2個字節,當前業務需要存儲的ObjectId大概是30萬條,這樣算下來當前業務需要存儲的 ObjectId 要佔用的內存在 0.5M 完全可以在內存中進行操作。相比於使用 Redis 來說沒有網絡開銷,效率更高。

1.2 緩存初始化:當服務啟動時,本地緩存初始化為空。

1.3 關於緩存版本的概念。

緩存版本是離線特徵生產任務更新後將數據版本更新到 DB 中。

下面三種方案都是基於內存存儲 ObjectId 數據,在內存更新的時候策略有所不同。

方案一

2.1 緩存更新

使用主動更新緩存的方式,創建定時任務,每間隔1分鐘查一次 DB 的數據版本,若更新則更新緩存中的數據。

2.2 缺點

單獨啟動一個緩存更新線程,代碼不好維護,也會有定時任務線程掛掉的情況,不易發現。還有就是需要提前把相關參數配置到代碼中或者引入配置中心,維護成本較高。

方案二

3.1 緩存更新

採用被動觸發的緩存更新策略,由接口調用觸發。請求進來後檢測當前緩存中的數據的版本與 DB 中的數據版本是否一致,若版本更新,則重新讀取當前請求對應城市的所有數據到緩存中,並將更新後的數據返回給調用方。

3.2 缺點

由於是被動觸發的是同步更新緩存的,容易造成接口調用時如果正好遇上版本更新,需要更新數據到內存中,會出現偶現的毛刺。

3.3 業務執行時序圖

使用 Go 優化我們的接口

方案三(最終採用的方案)

4.1,緩存更新

採用被動更新緩存的策略,由接口調用方觸發。若當前緩存中有數據則直接返回緩存中的數據,然後檢測當前緩存中的數據的版本與 DB 中的數據版本是否一致,若版本更新,則重新讀取當前請求對應城市的所有feature數據到緩存中,反之結束緩存更新邏輯。

4.2 業務執行時序圖

使用 Go 優化我們的接口

併發優化方案

使用 Goroutine 來優化我們的串行邏輯

Go語言最大的特色就是從語言層面支持併發(Goroutine),Goroutine是Go中最基本的執行單元。事實上每一個Go程序至少有一個Goroutine:主Goroutine。當程序啟動時,它會自動創建。

為了更好理解Goroutine,現講一下線程和協程的概念:

線程(Thread):有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。

線程擁有自己獨立的棧和共享的堆,共享堆,不共享棧,線程的切換一般也由操作系統調度。

協程(coroutine):又稱微線程與子例程(或者稱為函數)一樣,協程(coroutine)也是一種程序組件。相對子例程而言,協程更為一般和靈活,但在實踐中使用沒有子例程那樣廣泛。

和線程類似,共享堆,不共享棧,協程的切換一般由程序員在代碼中顯式控制。它避免了上下文切換的額外耗費,兼顧了多線程的優點,簡化了高併發程序的複雜。

golang 中的 map 是線程不安全的

很顯然,我們可以用鎖機制解決 Map 的併發讀寫問題。我們將上面的map結構改成如下:

<code>// M
type M struct {
    Map    map[string]string
    lock sync.RWMutex // 加鎖
}

// Set ...
func (m *M) Set(key, value string) {
    m.lock.Lock()
    defer m.lock.Unlock()
    m.Map[key] = value
}

// Get ...
func (m *M) Get(key string) string {
    return m.Map[key]
}
複製代碼/<code>

在上面的代碼中,我們引入了鎖機制操作,從而保證了map在多個goroutine中的安全。

使用策略模式優化我們的邏輯

這塊主要是因為代碼中存在太多的 if/else ,故採用策略模式來優化我們的代碼結構。這裡先放上一篇網上找到的文章,之後有時間再單獨出一篇相關文章吧。優化後的代碼相較於之前代碼量少了 50% ,更加清晰與便於維護。下面是優化的代碼上線後的效果,請求耗時都在100ms以下:

使用 Go 優化我們的接口


分享到:


相關文章: