【Go | 從0實現簡單分布式緩存】-7:增加etcd和gRPC功能

在這里插入圖片描述

本文目錄

  • 1.序
  • 2.引入etcd
    • 緩存流程
    • 項目結構
  • 3.gocachepb.proto
  • 4.服務注冊register.go
  • 5.服務發現discover.go
  • 6.gRPC客戶端client.go
    • peers.go
    • client.go
  • 7.gRPC服務端實現server.go
  • 一些問題
    • 緩存獲取流程
    • 緩存設置流程
    • 為什么要帶超時的上下文?

1.序

GeeCache項目并沒有引入服務發現,對于現代分布緩存系統,服務發現和節點間通信是兩個重要環節,所以可以引入etcd和gRPC來使得系統更加健壯和高效。

etcd作為一個高可用的分布式鍵值存儲系統,扮演著服務注冊與發現的角色。通過etcd,緩存節點可以動態地注冊自己的存在,同時發現其他節點的位置。這種機制使得系統能夠自動適應節點的加入和退出,無需手動配置。

而且etcd的租約機制提供了節點健康檢查的能力,當某個節點宕機時,系統能夠及時感知并進行相應調整,保證整個緩存集群的可用性。

gRPC則解決了節點間高效通信的問題。相比傳統的HTTP/JSON通信方式,gRPC基于HTTP/2協議和Protocol Buffers序列化格式,提供了更高的性能和更低的延遲。傳輸前使用 protobuf 編碼,接收方再進行解碼,可以顯著地降低二進制傳輸的大小。另外一方面,protobuf 可非常適合傳輸結構化數據,便于通信字段的擴展。GeeCache中只是單純用了protobuf進行通信。

在緩存系統中,節點間需要頻繁交換數據,如緩存查詢、更新等操作,gRPC的高效通信能力顯著提升了系統的吞吐量。

本文所參考的部分實現代碼是Github上的cache項目,原地址:https://github.com/Spr1n9/springcache

2.引入etcd

緩存流程

  • 當一個節點需要獲取不在本地緩存的數據時,它會通過一致性哈希算法選擇一個節點
  • 使用 etcd 進行服務發現,獲取該節點的地址
  • 建立 gRPC 連接并調用遠程節點的 Get 方法獲取數據
  • 如果遠程節點也沒有緩存該數據,它會從數據源獲取并返回

項目結構

這里我們需要編寫幾個新的文件,分別是:

cachepb.proto Protocol Buffers定義文件,用于定義gRPC服務接口和消息格式。

  • 定義了 SpringCache 服務,包含 Get 和 Set 兩個RPC方法
  • 定義了請求和響應的消息結構: GetRequest 、 GetResponse 、 SetRequest 和 SetResponse
  • 這個文件是gRPC通信的基礎,通過protoc工具生成Go代碼

server.go 服務端核心實現,負責處理來自其他節點的緩存請求。

  • 實現了gRPC服務接口,處理 Get 和 Set 請求
  • 管理節點間的通信和緩存數據的分發
  • 使用一致性哈希算法選擇節點
  • 啟動gRPC服務器,接收來自其他節點的請求

client.go 客戶端實現,負責向其他節點發送請求。

  • 封裝了gRPC客戶端調用邏輯
  • 實現了 PeerGetter 接口,用于從遠程節點獲取或設置緩存
  • 處理與遠程節點的連接和通信

discover.go 服務發現相關功能,負責發現和連接其他節點。

  • 提供 DialPeer 函數,用于建立與其他節點的gRPC連接
  • 提供 GetAddrByName 函數,通過etcd查詢服務名對應的地址
  • 使用etcd的服務發現機制獲取節點信息

register.go 服務注冊相關功能,負責將節點注冊到etcd。

  • 提供 Etcd 結構體,封裝etcd客戶端操作
  • 實現租約創建、綁定和續約功能
  • 提供 RegisterServer 方法,將服務注冊到etcd

3.gocachepb.proto

在這里插入圖片描述

首先來看看proto文件,定義了整個系統的RPC接口和消息格式。

GeeCache一樣,有兩個GetRequestGetResponse,是用來Get獲取緩存的請求和響應。

value :緩存的值,使用 bytes 類型可以存儲任意二進制數據。

SetRequestSetResponse 是設置緩存的請求消息和響應消息。

這里請求消息有:group 緩存組名、key 緩存鍵、 value 緩存值、expire 過期時間戳(Unix時間戳格式)、ishot 是否為熱點數據。

設置緩存的響應消息有:ok 返回bool格式操作是否成功。

service SpringCache {rpc Get(GetRequest) returns (GetResponse);rpc Set(SetRequest) returns (SetResponse);
}

Get :獲取緩存,接收 GetRequest 參數,返回 GetResponse, Set :設置緩存,接收 SetRequest 參數,返回 SetResponse

使用protoc工具生成對應的兩個代碼如下,生成的代碼將被服務端和客戶端使用,服務端( server.go )實現了 SpringCacheServer 接口,處理 Get 和 Set 請求,客戶端( client.go )使用 SpringCacheClient 接口向遠程節點發送請求。

  • springcachepb.pb.go :包含消息類型的定義和序列化/反序列化代碼
  • springcachepb_grpc.pb.go :包含 gRPC 客戶端和服務端接口代碼

4.服務注冊register.go

在這里插入圖片描述

Etcd 結構體是對 etcd 客戶端的封裝,包含了與 etcd 交互所需的核心組件。其中 EtcdClietcd 的客戶端實例,用于執行各種 etcd 操作; leaseId 存儲租約 ID,用于后續的租約管理; ctxcancel 則用于控制上下文和超時處理。將 etcd 的操作封裝在一個結構體中,便于統一管理和使用。

NewEtcd 函數負責初始化 etcd 客戶端連接。它接收 etcd 服務器的地址列表,創建一個新的 etcd 客戶端實例,并設置連接超時時間。這里使用了 clientv3.New 方法創建客戶端,這是 etcd v3 API 的標準做法。還創建一個帶超時的上下文,用于控制后續操作的超時行為。這是與分布式系統交互的常見模式,可以避免因網絡問題導致的長時間阻塞。

DialTimeout 是客戶端嘗試連接到 etcd 服務時的超時時間。如果在指定時間內無法建立連接,客戶端會返回錯誤。

在這里插入圖片描述
CreateLease 為注冊在etcd上的節點創建租約。 由于服務端無法保證自身是一直可用的,可能會宕機,所以與etcd的租約是有時間期限的,租約一旦過期,服務端存儲在etcd上的服務地址信息就會消失。

如果服務端是正常運行的,etcd中的地址信息又必須存在,因此發送心跳檢測,一旦發現etcd上沒有自己的服務地址時,請求重新添加(續租)。

CreateLease 函數實現了 etcd 的租約創建機制。在分布式系統中,租約是一種重要的機制,用于表示節點的存活狀態。該函數調用 Grant 方法向 etcd 申請一個指定過期時間的租約,并將獲得的租約 ID 保存在 Etcd 結構體中。租約機制是 etcd 實現服務健康檢查的基礎,一旦租約過期,與之關聯的鍵值對會被自動刪除,這樣就能自動清理已經宕機的節點信息。

在這里插入圖片描述
BindLease 函數將服務信息與租約綁定。它使用 Put 方法將服務名稱和地址作為鍵值對存儲到 etcd 中,并通過 clientv3.WithLease 選項將這個鍵值對與之前創建的租約關聯起來。這樣,當租約過期時,這個服務信息也會被自動刪除。這種機制確保了 etcd 中只保存活躍節點的信息,是實現服務自動注冊和注銷的關鍵。

在這里插入圖片描述

KeepAlive 函數實現了租約的續約機制。它調用 etcdKeepAlive 方法,定期向 etcd 發送心跳,以延長租約的有效期。該函數啟動一個 goroutine 持續監聽續約響應通道,如果收到 nil 響應,表示續約失敗,服務可能已經與 etcd 斷開連接。這種心跳機制是分布式系統中保持節點活躍狀態的標準做法,確保了只要服務正常運行,其信息就會一直保存在 etcd 中。

在這里插入圖片描述

RegisterServer 函數是服務注冊的入口,它整合了前面幾個函數的功能,完成服務的完整注冊流程。首先創建租約,然后將服務信息與租約綁定,接著啟動心跳保持租約活躍,最后使用 etcdendpoints 管理器注冊服務端點。

使用 endpoints.NewManager 創建一個管理器,用于管理同一服務的所有實例。通過 AddEndpoint 方法,將當前實例的信息添加到服務組織中,并使用之前的租約進行關聯。

em 會將所有 UserService 的實例組織在一起,以 UserService/ 為前綴存儲在 etcd 中。通過 AddEndpoint,可以動態地添加服務實例。如果后續有更多實例(如 192.168.1.2:8080 和 192.168.1.3:8080),可以繼續調用 AddEndpoint 將它們添加到 etcd 中。比如下面的。

/CacheService/instance1:8001  (leaseID: 123)/instance2:8002  (leaseID: 456)/instance3:8003  (leaseID: 789)

每個實例都有自己的租約保證存活,而 Manager 則負責將這些實例組織在一起,方便 gRPC 客戶端進行服務發現和負載均衡。

5.服務發現discover.go

在這里插入圖片描述

DialPeer 函數負責建立與遠程服務節點的 gRPC 連接。它接收 etcd 客戶端和服務名稱作為參數,返回一個 gRPC 連接。函數首先創建一個 etcd resolver 構建器,這是 gRPC 服務發現的核心組件。然后設置一個帶超時的上下文,確保連接操作不會無限期阻塞。最后,使用 grpc.DialContext 方法建立連接,其中 "etcd:///"+service 表示使用 etcd 作為服務發現機制,并指定要連接的服務名稱。

DialPeer 函數中, resolver.NewBuilder(c) 創建了一個 etcd resolver 構建器。這個構建器實現了 gRPC 的 resolver.Builder 接口,能夠解析 etcd 中存儲的服務信息。當使用 "etcd:///"+service 作為目標地址時,gRPC 會使用這個 resolveretcd 中查找所有注冊在該服務名下的實例地址。這種機制使得客戶端可以通過服務名而非具體 IP 地址進行通信,實現了服務發現的解耦。

在這里插入圖片描述
GetAddrByName 函數提供了一種更直接的服務發現方式。它接收 etcd 客戶端和服務名稱作為參數,直接從 etcd 中查詢該服務名對應的地址。函數首先創建一個帶超時的上下文,然后使用 etcd 的 Get 方法查詢指定鍵(服務名)的值(服務地址)。這種方式適用于簡單的鍵值查詢,不依賴 gRPCresolver 機制。

這兩種方式分別滿足不同場景的需求。例如, DialPeer 用于建立 gRPC 連接進行緩存操作,而 GetAddrByName 則用于服務器啟動時發現其他節點。

兩個函數都使用了 context.WithTimeout 創建帶超時的上下文,這是 Go 語言中處理超時的標準模式。在分布式系統中,網絡延遲和服務不可用是常見問題,設置適當的超時可以避免長時間阻塞,提高系統的可用性和響應速度。在這個文件中,超時時間設置為 2 秒,這是一個較為合理的值,既能容忍一定的網絡延遲,又不會因等待過長而影響用戶體驗。

6.gRPC客戶端client.go

peers.go

在這里插入圖片描述


client.go

在這里插入圖片描述

Client 結構體是對遠程節點通信客戶端的封裝,包含兩個關鍵字段: Name 表示目標服務節點的名稱,用于在 etcd 中查找對應的服務地址; Etcdetcd 客戶端的引用,用于進行服務發現。

在這里插入圖片描述
Get 函數實現了從遠程節點獲取緩存數據的功能。它首先通過 DialPeer 函數利用 etcd 進行服務發現,獲取目標節點的 gRPC 連接。然后創建 gRPC 客戶端,構造請求參數,并設置超時上下文,最后發起 RPC 調用并處理響應。這個函數展示了 gRPC 客戶端的標準使用模式,以及如何將 etcd 服務發現與 gRPC 調用結合起來。

在這里插入圖片描述
Set 函數實現了向遠程節點設置緩存數據的功能。它的流程與 Get 函數類似,也是先通過 etcd 獲取連接,然后創建 gRPC 客戶端發起調用。不同的是,它需要處理更多的參數,包括緩存值、過期時間和熱點標記。這個函數展示了如何處理復雜的 RPC 參數,以及如何處理 RPC 調用的錯誤和響應。

var _ PeerGetter = (*Client)(nil)

通過這種方式驗證 Client 結構體是否實現了 PeerGetter 接口。

這是 Go 語言中常用的接口實現檢查技巧,如果 Client 沒有完全實現 PeerGetter 接口,編譯器會報錯。這種靜態檢查可以早期發現接口實現的問題,提高代碼質量。

7.gRPC服務端實現server.go

在這里插入圖片描述

Server 結構體是 分布式緩存系統的服務端核心組件,它實現了 gRPC 服務接口和節點選擇功能。結構體中包含多個重要字段: status 標記服務運行狀態; self 記錄自身 IP 地址; peers 是一致性哈希環,用于節點選擇; etcdetcd 客戶端實例,用于服務發現; clients 存儲了所有遠程節點的客戶端實例。

NewServer 函數負責創建并初始化一個 Server 實例。它接收服務名稱、自身地址和 etcd 客戶端作為參數,初始化一致性哈希環和客戶端映射。

在這里插入圖片描述

GetSet 方法實現了 gRPC 服務接口,分別用于處理遠程緩存獲取和設置請求。當其他節點通過 gRPC 調用這些方法時,服務器會從本地緩存組中獲取或設置數據,并返回相應的結果。這兩個方法是分布式緩存系統中節點間數據交換的核心實現,它們將 gRPC 請求轉換為本地緩存操作,實現了遠程調用與本地存儲的橋接。

在這里插入圖片描述

SetPeers 方法是服務發現和節點管理的關鍵。它接收一組節點名稱,通過 etcd 查找每個節點的 IP 地址,然后將這些地址添加到一致性哈希環中,并創建對應的客戶端實例。這個方法展示了如何使用 etcd 進行服務發現:通過 GetAddrByName 函數查詢 etcd 中存儲的服務地址信息。
在這里插入圖片描述

PickPeer 方法實現了 connect.PeerPicker 接口,用于根據鍵選擇合適的遠程節點。它使用一致性哈希算法確定鍵應該存儲在哪個節點上,然后返回該節點的客戶端實例。這個方法是分布式緩存系統中數據分片的核心實現,它確保了相同的鍵總是被路由到相同的節點,提高了緩存的命中率和一致性。

在這里插入圖片描述

StartServer 方法負責啟動 gRPC 服務器。它首先檢查服務是否已經啟動,然后初始化 TCP 監聽器,創建 gRPC 服務器實例,注冊服務處理器,最后開始監聽和處理請求。這個方法是服務器組件的入口點,它將所有組件組合在一起,形成一個完整的服務節點。

一些問題

緩存獲取流程

當客戶端通過 API 請求獲取緩存數據時:

  1. HTTP 服務器接收請求,調用 group.Get 方法獲取數據。

  2. group.Get 首先嘗試從本地緩存獲取數據,如果命中則直接返回。

  3. 如果本地緩存未命中, group.Get 會調用 group.load 方法加載數據。

  4. group.load 會先檢查是否有遠程節點負責存儲該鍵的數據:

    • 如果有,則通過 PeerPicker.PickPeer 選擇合適的節點
    • 然后通過 PeerGetter.Get 從遠程節點獲取數據
    • 這個過程涉及 gRPC 調用,由 Client.Get 方法實現
  5. 如果遠程節點獲取失敗或沒有遠程節點負責該鍵,則調用回調函數從數據源獲取數據。

  6. 獲取到數據后,將其存儲到本地緩存并返回給客戶端。

緩存設置流程

當客戶端通過 API 請求設置緩存數據時:

  1. HTTP 服務器接收請求,解析參數,調用 group.Set 方法設置數據。

  2. group.Set 會根據鍵選擇合適的節點:

    • 如果是本地節點,則直接設置本地緩存
    • 如果是遠程節點,則通過 PeerGetter.Set 設置遠程節點的緩存
    • 這個過程也涉及 gRPC 調用,由 Client.Set 方法實現
  3. 設置成功后,返回成功響應給客戶端。

為什么要帶超時的上下文?

可以看到,這里我們經常使用上下文,并且還要加入超時的設定。

使用帶超時的上下文(context.WithTimeout)是一種非常重要的編程實踐,它能有效防止系統因為某些操作卡住而導致資源耗盡。

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

創建一個新的上下文,它會在2秒后自動超時。同時返回一個cancel函數,可以手動取消這個上下文。defer cancel() 確保無論函數如何返回,都會調用cancel函數釋放資源。

打個比方,假設我們去餐廳點餐,如果沒有超時控制 :你告訴服務員"我要一份牛排",然后一直等,不管廚房是否忙碌、是否缺貨,你可能會一直等下去。

有超時控制 :你告訴服務員"我要一份牛排,但我只能等15分鐘"。如果15分鐘內上菜了,太好了;如果沒有,你就可以取消訂單離開,不用無限期等待。

如果沒有超時控制,當目標節點宕機或網絡異常時,請求可能會一直阻塞,有了2秒的超時控制,即使目標節點無響應,最多也只會等待2秒就返回錯誤。

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

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

相關文章

Pytorch系列教程:可視化Pytorch模型訓練過程

深度學習和理解訓練過程中的學習和進步機制對于優化性能、診斷欠擬合或過擬合等問題至關重要。將訓練過程可視化的過程為學習的動態提供了有價值的見解,使我們能夠做出合理的決策。訓練進度必須可視化的兩種方法是:使用Matplotlib和Tensor Board。在本文…

18 | 實現簡潔架構的 Handler 層

提示: 所有體系課見專欄:Go 項目開發極速入門實戰課;歡迎加入我的訓練營:云原生AI實戰營,一個助力 Go 開發者在 AI 時代建立技術競爭力的實戰營;本節課最終源碼位于 fastgo 項目的 feature/s14 分支&#x…

藍隊第三次

1.了解什么是盲注 盲注(Blind SQL Injection)是SQL注入的一種形式,攻擊者無法直接通過頁面回顯或錯誤信息獲取數據,而是通過觀察頁面的布爾狀態(真/假)或時間延遲來間接推斷數據庫信息。例如,通…

sql server 2016 版本補丁說明

包信息和發布類型 Microsoft為創建和分發的 SQL Server 的所有軟件更新包采用了標準化命名架構。 軟件更新包是一個可執行文件(.exe 或 .msi)文件,其中包含一個或多個文件,這些文件可能應用于 SQL Server 安裝以更正特定問題。 …

STM32之I2C硬件外設

注意:硬件I2C的引腳是固定的 SDA和SCL都是復用到外部引腳。 SDA發送時數據寄存器的數據在數據移位寄存器空閑的狀態下進入數據移位寄存器,此時會置狀態寄存器的TXE為1,表示發送寄存器為空,然后往數據控制寄存器中一位一位的移送數…

從青銅到王者:六大排序算法實戰解析

前言 在編程的世界里,排序算法如同一顆璀璨的明珠,閃耀著智慧的光芒。它不僅是計算機科學的基礎知識點,更是每一位程序員必備的技能。今天,就讓我們一同走進排序算法的世界,深入探究冒泡排序、選擇排序、插入排序、快速排序、歸并排序、堆排序這六大經典算法的精髓所在,…

小程序配置webview

1.在微信公眾平臺配置業務域名 1)包括把校驗文件放在服務器根目錄 2)配置域名 2.在小程序中 新建文件 小程序新建頁面:web-view json配置:{ "pageOrientation": "landscape", "renderer":&qu…

不用 Tomcat?SpringBoot 項目用啥代替?

在SpringBoot框架中,我們使用最多的是Tomcat,這是SpringBoot默認的容器技術,而且是內嵌式的Tomcat。 同時,SpringBoot也支持Undertow容器,我們可以很方便的用Undertow替換Tomcat,而Undertow的性能和內存使…

線索二叉樹構造及遍歷算法

線索二叉樹構造以及遍歷算法 線索二叉樹(中序遍歷版)構造線索二叉樹構造雙向線索鏈表遍歷中序線索二叉樹 線索二叉樹(中序遍歷版) 中序遍歷找到對應結點的前驅(土方法) #mermaid-svg-eunGO5d2GhjLxCn5 {fo…

基于SpringBoot的“體育購物商城”的設計與實現(源碼+數據庫+文檔+PPT)

基于SpringBoot的“體育購物商城”的設計與實現(源碼數據庫文檔PPT) 開發語言:Java 數據庫:MySQL 技術:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系統展示 系統總體模塊設計 前臺用戶登錄界面 系統首頁界面…

數據篇| App爬蟲入門(一)

App 的爬取相比 Web 端爬取更加容易,反爬蟲能力沒有那么強,而且數據大多是以 JSON 形式傳輸的,解析更加簡單。在 Web 端,我們可以通過瀏覽器的開發者工具監聽到各個網絡請求和響應過程,在 App 端如果想要查看這些內容就需要借助抓包軟件。常見抓包軟件有: ?工具名稱??…

go context學習

1.Context接口2.emptyCtx3.Deadline()方法4.Done()方法5.Err方法6.Value方法()7.contex應用場景8.其他context方法 1.Context接口 Context接口只有四個方法,以下是context源碼。 type Context interface {Deadline() (deadline time.Time, …

在VMware Workstation Pro上輕松部署CentOS7 Linux虛擬機

首先我們需要下載VM虛擬機和Centos7的鏡像 下載并安裝VMware Workstation Pro 訪問VMware Workstation Pro官網下載 https://www.vmware.com/ 第二步:下載centos7鏡像 訪問centos官網下載 https://www.centos.org/ 開始部署Centos7 點擊創建新的虛擬機 這里是Cen…

Jsoup 解析商品信息時需要注意哪些細節?

在使用Jsoup解析商品信息時,需要注意以下細節和最佳實踐,以確保爬蟲的穩定性和數據的準確性: 1. 檢查HTML文檔的合法性 在解析之前,需要確認所解析的文檔是否是一份合法正確的HTML文檔。如果HTML結構不完整或存在錯誤&#xff0…

Android AudioFlinger(五)—— 揭開AudioMixer面紗

前言: 在 Android 音頻系統中,AudioMixer 是音頻框架中一個關鍵的組件,用于處理多路音頻流的混音操作。它主要存在于音頻回放路徑中,是 AudioFlinger 服務的一部分。 上一節我們講threadloop的時候,提到了一個函數pr…

go的”ambiguous import in multiple modules”

執行“go mod tidy”報如下錯誤: go mod tidy -compat1.17 go: finding module for package github.com/gomooon/goredis go: found github.com/gomooon/goredis in github.com/gomooon/goredis v0.3.5 go: github.com/gomooon/core importsgithub.com/gomooon/gor…

從0開始的操作系統手搓教程27:下一步,實現我們的用戶進程

目錄 第一步:添加用戶進程虛擬空間 準備沖向我們的特權級3(用戶特權級) 討論下我們創建用戶線程的基本步驟 更加詳細的分析代碼 用戶進程的視圖 說一說BSS段 繼續看process.c中的函數 添加用戶線程激活 現在,我們做好了TSS…

Java線程池深度解析,從源碼到面試熱點

Java線程池深度解析,從源碼到面試熱點 一、線程池的核心價值與設計哲學 在開始討論多線程編程之前,可以先思考一個問題?多線程編程的原理是什么? 我們知道,現在的CUP是多核CPU,假設你的機器是4核的&#x…

大數據技術在土地利用規劃中的應用分析

大數據技術在土地利用規劃中的應用分析 一、引言 土地利用規劃是對一定區域內的土地開發、利用、整治和保護所作出的統籌安排與戰略部署,對于實現土地資源的優化配置、保障社會經濟的可持續發展具有關鍵意義。在當今數字化時代,大數據技術憑借其海量數據處理、高效信息挖掘等…

Node 使用 SSE 結合redis 推送數據(echarts 圖表實時更新)

1、實時通信有哪些實現方式? 特性輪詢(Polling)WebSocketSSE (Server-Sent Events)通信方向單向(客戶端 → 服務端)雙向(客戶端 ? 服務端)單向(服務端 → 客戶端)連接方…