cache教程 5.分布式節點的通信

0.對原教程的一些見解

其回顧完請求流程就是抽象了兩個接口,PeerPicker和PeerGetter。這樣操作,讀者閱讀時可能很難快速明白其含義,不好理解為什么就創建出兩個接口,感覺會比較疑惑。原教程的評論中也有討論這點。

?本教程就先不創建接口,而是使用struct方式,這樣可能好理解點。

1.節點請求處理的流程

先弄清楚我們查詢緩存的邏輯。

單節點:?

客戶發送查詢請求到節點A,該節點有緩存就立即返回,若是沒有就執行用戶設置的回調函數獲取值并添加到緩存中,然后返回。

分布式節點:

客戶端發送查詢請求到某個緩存節點,該節點會判斷該key是否在本地,若是不在本地,使用一致性哈希選擇節點,若不是在遠程節點,則就退回到本地節點處理;若在遠程節點,該節點會發送請求去訪問其他?node?節點。(不是客戶端再去訪問其他節點)

從這可以看出,一個node要處理兩種請求,一個是來自客戶端的外部請求,一個是來自其他遠端節點的內部請求

為了清晰,劃分職責,我們可以在一個node中啟動兩種HTTP服務,一個處理客戶端請求(APIServer), 一個處理節點之間的請求(CacheServer)

2.HTTP客戶端

之前我們為?HTTPPool?實現了服務端功能,通信不僅需要服務端還需要客戶端,因此,我們接下來先實現客戶端的功能。這個客戶端是節點作為客戶端去訪問其他節點

  • baseURL 表示將要訪問的遠程節點的地址,例如?http://example.com/geecache/
type httpGetter struct {baseURL string
}func (h *httpGetter) Get(group string, key string) ([]byte, error) {//QueryEscape 對字符串進行轉義,以便可以將其安全地放置在 URL 查詢中。u := fmt.Sprintf("%v/%v/%v", h.baseURL,url.QueryEscape(group),url.QueryEscape(key))res, err := http.Get(u)if err != nil {return nil, err}defer res.Body.Close()if res.StatusCode != http.StatusOK {return nil, fmt.Errorf("server returned: %v", res.Status)}bytes, err := io.ReadAll(res.Body)if err != nil {return nil, fmt.Errorf("reading response body: %v", err)}return bytes, nil
}

3.回顧上一章節實現的單節點的訪問流程

func (g *Group) Get(key string) (ByteView, error) {//現在本地查詢if v, ok := g.mainCache.get(key); ok {return v, nil}return g.load(key)   
}func (g *Group) load(key string) (ByteView, error) {bytes, err := g.getter.Get(key)if err != nil {return ByteView{}, err}value := ByteView{b: cloneByte(bytes)}g.mainCache.add(key, value)return value, nil
}

那很明顯是需要修改load方法,讓其可以去訪問遠程節點。

在load方法中,偽代碼如下。

func func (g *Group) load(key string) (ByteView, error){if 有遠程節點 {if 找到key所在的遠程節點 {本地作為客戶端去訪問該遠程節點}}沒有遠程節點,只能在本地調用回調函數去源地方獲取
}

要想在Group中訪問節點,那么就要在Group中存儲節點集合。

節點結合結構體Peers

那節點集合是不是又要創建一個結構體?那先試試創建一個結構體Peers。

因為 hash 環的 map 不是線程安全的,所以這里要加鎖。

成員變量?httpGetters,映射遠程節點與對應的 httpGetter。(httpGetter就是個客戶端,是一個節點作為客戶端),每一個遠程節點對應一個 httpGetter,因為 httpGetter 與遠程節點的地址?baseURL?有關,map的key是遠程節點的地址,比如"http://localhost:10000"

type Peers struct {addr          string //這個是用于進行選擇節點時用來判斷是不是本地節點basePath      stringmutex         sync.Mutex    //guards peersHashRing and httpGetterspeersHashRing *consistenthash.HashRinghttpGetters   map[string]*httpGetter
}//這是HTTP服務端章節的HTTPPool,這是很相似的
type HTTPPool struct {addr     stringbasePath string
}

那么該結構體Peers就要有添加遠程節點和通過key去獲取遠程節點的方法。

增添遠程節點方法Set

通過該方法可以知道其map的key是遠程節點的地址。

// 使用用例:Set("http://localhost:8001","http://localhost:8002")
func (p *Peers) Set(peers ...string) {p.mutex.Lock()defer p.mutex.Unlock()p.peersHashRing = consistenthash.NewHash(50, nil)p.peersHashRing.Add(peers...) //在 hash 環上添加真實節點和虛擬節點//存儲遠端節點信息p.httpGetters = make(map[string]*httpGetter)for _, peer := range peers {p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}}
}

通過key去獲取遠程節點的方法PickPeer

Peers結構體中的變量addr在這里派上用場了,返回的地址要是等于本身addr,那就返回false,不用自己作為客戶端再去訪問自己。

func (p *Peers) PickPeer(key string) (*httpGetter, bool) {p.mutex.Lock()defer p.mutex.Unlock()//這里返回的peer是個地址,可以查看(Peers).Set函數中的參數if peer := p.peersHashRing.Get(key); peer != "" && peer != p.addr {fmt.Println("pick peer ", peer)return p.httpGetters[peer], true}return &httpGetter{}, false
}

Peers這個結構體就實現了,可以看到其與HTTPPool是很相似的。對比HTTPPool,就是成員變量添加了一些,方法也添加了一些,也沒有改變HTTPPool原有的邏輯,只是擴張了。所以可以把Peers的內容添加到HTTPPool中去,具體的代碼就不在這里顯示了。

type HTTPPool struct {addr     stringbasePath string//新添加的,把Peers內容增添到HTTPPool中mutex         sync.MutexpeersHashRing *consistenthash.HashRinghttpGetters   map[string]*httpGetter
}

4.集成,實現主流程

最后,我們需要將上述新增的功能集成在主流程(geecache.go)中。

在Group結構體中有改變。

新增?RegisterPeers()?方法,將 peers?注入到 Group 中。

type Group struct {name      stringmainCache cachegetter    Getterpeers *Peers //添加了節點集合
}// 往分組內注冊節點集合
func (g *Group) RegisterPeers(peers *Peers) {if g.peers != nil {panic("RegisterPeerPicker called more than once")}g.peers = peers
}

最終再回到load函數,這個函數是需要修改的。

func (g *Group) load(key string) (value ByteView, err error) {if g.peers != nil {    //有遠程節點的情況if peer, ok := g.peers.PickPeer(key); ok {    //通過key找到該遠程節點if value, err = g.getFromPeer(peer, key); err == nil {return value, nil        //找到值}log.Println("[GeeCache] Failed to get from peer", err)}}return g.getLocally(key)    //回到本地處理
}func (g *Group) getFromPeer(peer *httpGetter, key string) (ByteView, error) {bytes, err := peer.Get(g.name, key)if err != nil {return ByteView{}, err}return ByteView{b: bytes}, nil
}func (g *Group) getLocally(key string) (ByteView, error) {bytes, err := g.getter.Get(key)if err != nil {return ByteView{}, err}value := ByteView{b: cloneByte(bytes)}g.mainCache.add(key, value)return value, nil
}
  • 新增?getFromPeer()?方法,使用httpGetter 訪問遠程節點,獲取緩存值。
  • 修改 load 方法,使用?PickPeer()?方法選擇節點,若非本機節點,則調用?getFromPeer()?從遠程獲取。若是本機節點或失敗,則回退到?getLocally()

5. 測試

總結——緩存節點啟動的流程

  1. 創建 Group 對象.(用于存儲我們的緩存數據)
  2. 啟動緩存 http 服務.(創建 HTTPPool,添加節點信息,注冊到緩存分組中)
  3. 啟動 API 服務.(用于與客戶端進行交互)

?測試代碼:

var db = map[string]string{"Tom":  "630","Jack": "589","Sam":  "567",
}func main() {var port intvar api boolflag.IntVar(&port, "port", 8001, "Geecache server port")flag.BoolVar(&api, "api", false, "Start a api server?")flag.Parse()apiAddr := "http://localhost:9999"addrMap := map[int]string{8001: "http://localhost:8001",8002: "http://localhost:8002",8003: "http://localhost:8003",}var addrs []stringfor _, v := range addrMap {addrs = append(addrs, v)}gee := createGroup()if api {go startAPIServer(apiAddr, gee)}startCacheServer(addrMap[port], addrs, gee)time.Sleep(time.Second * 1000)
}func createGroup() *cache.Group {return cache.NewGroup("scores", 2<<10, cache.GetterFunc(func(key string) ([]byte, error) {if v, ok := db[key]; ok {return []byte(v), nil}return nil, fmt.Errorf("%s not exit", key)}))
}func startCacheServer(addr string, addrs []string, groups *cache.Group) {//HTTPPool是節點結合和HTTP服務端peers := cache.NewHTTPPool(addr, cache.DefaultBasePath)peers.Set(addrs...)         //添加節點groups.RegisterPeers(peers) //注冊節點集合log.Println("geecache is running at", addr)http.ListenAndServe(addr[7:], peers)
}func startAPIServer(apiAddr string, groups *cache.Group) {http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {key := r.URL.Query().Get("key")view, err := groups.Get(key)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}w.Header().Set("Content-Type", "application/octet-stream")w.Write(view.ByteSlice())})log.Println("fontend server is running at", apiAddr)http.ListenAndServe(apiAddr[7:], nil)
}

為了方便,我們將啟動的命令封裝為一個?shell?腳本:

我們開啟了三個節點(都是在同一個臺機器上的,只是用不同端口來當做一個節點,進行區分)。

在端口8003的節點上開啟APIServer,用戶去訪問時候,都是訪問端口8003的那個節點。

#!/bin/bash#trap 命令用于在 shell 腳本退出時,刪掉臨時文件,結束在該shell腳本運行的后臺程序
trap "rm server;kill 0" EXITgo build -o server
./server -port=8001 &
./server -port=8002 &
./server -port=8003 -api=1 &sleep 2
echo ">>> start test"
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &
curl "http://localhost:9999/api?key=Tom" &wait

結果

測試的時候,我們并發了 3 個請求??key=Tom,從日志中可以看到,三次均選擇了節點?8001,這是一致性哈希算法的功勞。

但是會有一個問題,同時向?8001?發起了 3 次請求。試想,假如有 10 萬個在并發請求該數據呢?那就會向?8001?同時發起 10 萬次請求,如果?8001?又同時向數據庫發起 10 萬次查詢請求,很容易導致緩存被擊穿。

三次請求的結果是一致的,對于相同的 key,能不能只向?8001?發起一次請求?這個問題下一次解決。

6.多節點的訪問流程圖

完整代碼:https://github.com/liwook/Go-projects/tree/main/go-cache/5-multi-nodes

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/215083.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/215083.shtml
英文地址,請注明出處:http://en.pswp.cn/news/215083.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何寫好PPT報告

寫好PPT的技巧和方法 PPT報告是現代工作和學習中常用的一種形式&#xff0c;它能夠通過圖文并茂的方式將信息傳達給觀眾。然而&#xff0c;很多人在制作PPT報告時并不知道如何下手&#xff0c;容易出現混亂的情況。為了幫助大家寫好PPT報告&#xff0c;下面總結了一些方法和…

Python爬取酷我音樂

&#x1f388; 博主&#xff1a;一只程序猿子 &#x1f388; 博客主頁&#xff1a;一只程序猿子 博客主頁 &#x1f388; 個人介紹&#xff1a;愛好(bushi)編程&#xff01; &#x1f388; 創作不易&#xff1a;喜歡的話麻煩您點個&#x1f44d;和?&#xff01; &#x1f388;…

pytest + yaml 框架 -59.用例失敗重跑機制pytest-rerunfailures

前言 有些接口可能不太穩定&#xff0c;第一次跑的時候由于網絡原因或者其它原因失敗&#xff0c;但是重新跑2次又成功了。 對于這種需要重新跑幾次的場景&#xff0c;可以使用用例失敗重跑機制&#xff0c;需安裝pytest-rerunfailures 插件。 場景示例 失敗重跑需要依賴 py…

【Axure原型分享】3D多柱狀圖_中繼器版

今天和大家分享3D多柱狀圖_中繼器版的原型模板&#xff0c;鼠標移入時&#xff0c;對應區域的背景會高亮變色&#xff0c;并且顯示對應柱狀體的數據。那這個原型是用Axure原生元件制作的&#xff0c;樣式交互都可以自行修改&#xff0c;圖表數據在中繼器表格里填寫&#xff0c;…

【二者區別】cuda和cudatoolkit

Pytorch 使用不同版本的 cuda 由于課題的原因&#xff0c;筆者主要通過 Pytorch 框架進行深度學習相關的學習和實驗。在運行和學習網絡上的 Pytorch 應用代碼的過程中&#xff0c;不少項目會標注作者在運行和實驗時所使用的 Pytorch 和 cuda 版本信息。由于 Pytorch 和 cuda 版…

mac安裝elasticsearch8.x

es下載地址&#xff1a; Past Releases of Elastic Stack Software | Elastic https://www.elastic.co/cn/downloads/past-releases#elasticsearch 選擇8.10版本 進入es bin目錄下執行啟動命令 ./elasticsearch 這個時候localhost:9200無法訪問 原因是是因為開啟了ssl認證…

R語言,table()函數實現統計每個元素出現的頻數+并將最終統計頻數結果轉換成dataframe數據框形式

在 R中&#xff0c;要統計dataframe數據框中每個元素出現的頻數&#xff0c;可以使用table()函數。以下是一個示例&#xff1a; 目錄 一、創建數據 二、統計第一列每個元素出現的頻數 三、統計第二列每個元素出現的頻數 四、將頻數結果轉換為數據框&#xff0c;并改列名 一…

Cannot find cache named ‘‘ for Builder Redis

當引入 Redissson 時&#xff0c;springCache 緩存機制失效 原因&#xff1a;springCache 默認使用本地緩存 Redisson 使用redis 緩存 最后都轉成redis了。。。 總感覺哪不對 兩者居然不共存

nodejs+vue+微信小程序+python+PHP的外賣數據分析-計算機畢業設計推薦django

構建一種完全可實現、可操作的開放源代碼信息收集系統&#xff0c;幫助記者完成工作任務。采編人員僅需輸入所收集到的網址及題目即可迅速啟動收集工作并進行信息歸類。 2.根據新的數據收集要求&#xff0c;采用云計算技術實現新的收集器的迅速部署。對于資料采集點的改版&…

Java接入ChatGPT接口簡單示例

我們定義了一個名為ChartGPTConfig的類&#xff0c;它有兩個私有成員變量apiKey和apiUrl&#xff0c;分別表示ChartGPT的API密鑰和API URL。 public class ChartGPTConfig {private final String apiKey;private final String apiUrl;public ChartGPTConfig(String apiKey, St…

angular hero學習

install nodehttps://nodejs.org C:\xxx\Downloads\node-v20.10.0-x64 C:\Program Files\nodejs\ C:\angular>node -v v20.10.0 C:\angular>npm -v 10.2.3 install angualr npm install -g angular/cli error # 設置淘寶源npm config set registry https://registry.…

基于Qt的登錄頁面設計

題目&#xff1a; 完善對話框&#xff0c;點擊登錄對話框&#xff0c;如果賬號和密碼匹配&#xff0c;則彈出信息對話框&#xff0c;給出提示”登錄成功“&#xff0c;提供一個Ok按鈕&#xff0c;用戶點擊Ok后&#xff0c;關閉登錄界面&#xff0c;跳轉到其他界面 如果賬號和…

Node.js管理工具npm簡單介紹

1.npm用途說明 我們在寫node.js項目的時候npm工具是避免不的&#xff0c;那么我們如何使用該工具去管理包文具呢&#xff1f;首先我們先介紹npm工具的幾種用途&#xff1a; 可以從npm服務器下載別人編寫的第三方包到本地使用。可以從npm服務器下載并安裝別人編寫的命令行程序…

音樂制作工具 Ableton Live 12中文最新 for Mac

Ableton Live 12 Mac具有直觀的界面和強大的功能&#xff0c;使得音樂制作變得更加簡單和高效。它支持實時錄制、編輯和混音&#xff0c;用戶可以在創作過程中隨時進行修改和調整。此外&#xff0c;該軟件還提供了各種音頻效果、虛擬樂器和采樣器&#xff0c;使用戶可以創建出更…

Springboot入門篇

一、概述 Spring是一個開源框架&#xff0c;2003 年興起的一個輕量級的Java 開發框架&#xff0c;作者Rod Johnson 。Spring是為了解決企業級應用開發的復雜性而創建的&#xff0c;簡化開發。 1.1對比 對比一下 Spring 程序和 SpringBoot 程序。如下圖 坐標 Spring 程序中的…

深入理解模板引擎:解鎖 Web 開發的新境界(下)

&#x1f90d; 前端開發工程師&#xff08;主業&#xff09;、技術博主&#xff08;副業&#xff09;、已過CET6 &#x1f368; 阿珊和她的貓_CSDN個人主頁 &#x1f560; 牛客高級專題作者、在牛客打造高質量專欄《前端面試必備》 &#x1f35a; 藍橋云課簽約作者、已在藍橋云…

TrustZone之完成器:外圍設備和內存

到目前為止,在本指南中,我們集中討論了處理器,但TrustZone遠不止是一組處理器功能。要充分利用TrustZone功能,我們還需要系統其余部分的支持。以下是一個啟用了TrustZone的系統示例: 本節探討了該系統中的關鍵組件以及它們在TrustZone中的作用。 完成器:外圍設備…

11、vue3(十一):sku管理:商品上下架,商品詳情,刪除sku

目錄 一、sku查詢頁面完成 1.代碼實現 (1)完成入參出參、接口代碼 (2)首頁頁面

點評項目——秒殺優化

2023.12.11 上一張的秒殺券下單還可以進行優化&#xff0c;先來回顧一下下單流程&#xff1a; 可以看出流程設計多次查詢和操作數據庫的操作&#xff0c;并且執行順序是一個線程串行執行&#xff0c;執行性能是比較低的。 優化方案&#xff1a;我們將判斷秒殺庫存和校驗一人一單…

WPF里面的Dispatcher詳解

在WPF應用程序中,Application.Current.Dispatcher是一個重要的屬性。它允許開發者在WPF應用程序的主線程上執行操作,這對于確保UI響應性和避免假死(程序沒有響應用戶輸入)非常關鍵。主線程負責接收輸入、處理事件、繪制屏幕等任務。為了避免在主線程上執行耗時的操作,開發…