55.手寫實現grpc連接池以及gin和grpc交互

文章目錄

  • 一、簡介
    • 前置說明
  • 二、敏感詞過濾服務
    • 1、定義sensitive.proto文件
    • 2、protoc生成pb.go文件
    • 3、sensitive服務端實現
  • 三、關鍵詞匹配服務
    • 1、編寫keywords.proto文件
    • 2、生成pb.go文件
    • 3、keywords服務端實現
  • 四、gin web 路由服務
    • 1、新建grpcpool服務作為gin web服務
    • 2、根據proto文件,分別生成keywords服務和sensitive服務的pb.go文件
  • 五、grpc 連接池實現
    • 1、連接池的實現,通過sync.Pool實現
    • 2、連接池的使用

代碼地址: https://gitee.com/lymgoforIT/golang-trick/tree/master/33-grpc-pool

一、簡介

當我們在使用需要連接的資源時,一般都應該想到可以通過池化的技術去做一定的性能優化。比如數據庫連接池就是最常見的連接池。

在微服務中,服務與服務之間的通信也是需要建立連接的,如果需要頻繁的交互,那么 建立連接池就可以避免每次交互都需要新建連接的性能消耗。

本案例就是要手寫一個grpc的客戶端連接池,整合到gin web服務中,而這個web服務需要頻繁調用另外兩個grpc遠程服務,分別是關鍵詞匹配服務和敏感詞過濾服務(當然這里不會有很復雜的匹配和過濾上的業務邏輯,畢竟主要演示的是調用鏈路),鏈路大致如下:

在這里插入圖片描述

前置說明

  1. 因為本博客主要學習的是連接池的實現方法、grpc服務的開發、gin web服務的開發、以及gin web 服務調用遠程grpc服務此外,該案例會包含三個服務,工作中一般這三個服務會在不同的服務器上,這里為了演示,就在同一個代碼包下,通過不同的端口號模擬多個服務。
  2. gin web 服務調用grpc服務時,本案例中我們也沒有用到服務注冊與發現功能,而是在gin web服務中寫死了grpc客戶端

二、敏感詞過濾服務

該服務就一個接口,接收一段文本,然后輸出是否包含敏感詞,為了簡便,我們不真的校驗是否包含敏感詞,直接返回true即可。

1、定義sensitive.proto文件

syntax  = "proto3";
package sensitive;option go_package = "33-grpc-pool/sensitive/proto";message ValidateRequest{string input = 1;
}message ValidateResponse {bool ok = 1;string word = 2;
}service SensitiveFilter {rpc Validate(ValidateRequest) returns (ValidateResponse);
}

2、protoc生成pb.go文件

 protoc --proto_path=33-grpc-pool/sensitive/proto --go_out=. --go-grpc_out=. 33-grpc-pool/sensitive/proto/sensitive.proto

在這里插入圖片描述

3、sensitive服務端實現

編寫服務端代碼server.go

package serverimport ("context""fmt""golang-trick/33-grpc-pool/sensitive/proto"
)type SensitiveServer struct {proto.UnimplementedSensitiveFilterServer
}func (s SensitiveServer) Validate(ctx context.Context, request *proto.ValidateRequest) (*proto.ValidateResponse, error) {fmt.Printf("%+v\n", request)// 我們直接認為沒有敏感詞,直接返回true,敏感詞為空return &proto.ValidateResponse{Ok:   true,Word: "",}, nil
}

啟動服務代碼main.go

package mainimport ("flag""fmt""golang-trick/33-grpc-pool/sensitive/proto""golang-trick/33-grpc-pool/sensitive/sensitive-server/server""log""net""google.golang.org/grpc"
)var (port = flag.Int("port", 50051, "")
)func main() {flag.Parse()// 監聽端口lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))if err != nil {log.Fatal(err)}// 建立rpc服務,并注冊SensitiveServers := grpc.NewServer()proto.RegisterSensitiveFilterServer(s, &server.SensitiveServer{})// 啟動服務err = s.Serve(lis)if err != nil {log.Fatal(err)}
}

當前sensitive整體代碼結構如下:
在這里插入圖片描述

三、關鍵詞匹配服務

關鍵詞匹配 服務,編碼與上面的敏感詞過濾服務幾乎一模一樣,主要就是改了proto文件以及服務的實現。但編碼流程完全沒變的。
我們寫完后會發現,keywords服務sensitive服務的代碼結構一致。再次強調一下,這里為了演示,所以兩個微服務寫到了一起,通過端口分為兩個微服務啟動,實際一般是不同的兩個微服務項目,部署到不同的機器上的。
在這里插入圖片描述

1、編寫keywords.proto文件

syntax  = "proto3";
package sensitive;option go_package = "33-grpc-pool/keywords/proto";message MatchRequest{string input = 1;
}message MatchResponse {bool ok = 1;string word = 2;
}service KeyWordsMatch {rpc Match(MatchRequest) returns (MatchResponse);
}

2、生成pb.go文件

注意命令中的路徑和sensitive服務的有所不同,需要修改

protoc --proto_path=33-grpc-pool/keywords/proto --go_out=. --go-grpc_out=. 33-grpc-pool/keywords/proto/keywords.proto

3、keywords服務端實現

編寫服務端代碼server.go

package serverimport ("context""fmt""golang-trick/33-grpc-pool/keywords/proto"
)type KwServer struct {proto.UnimplementedKeyWordsMatchServer
}func (k KwServer) Match(ctx context.Context, request *proto.MatchRequest) (*proto.MatchResponse, error) {fmt.Printf("%+v\n", request)// 我們直接認為沒有敏感詞,直接返回true,敏感詞為空return &proto.MatchResponse{Ok:   true,Word: "",}, nil
}

服務啟動代碼main.go

注意端口換為了50052,sensitive服務的是50051

package mainimport ("flag""fmt""golang-trick/33-grpc-pool/keywords/keywords-server/server""golang-trick/33-grpc-pool/keywords/proto""log""net""google.golang.org/grpc"
)var (port = flag.Int("port", 50052, "")
)func main() {flag.Parse()// 監聽端口lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))if err != nil {log.Fatal(err)}// 建立rpc服務,并注冊SensitiveServers := grpc.NewServer()proto.RegisterKeyWordsMatchServer(s, &server.KwServer{})// 啟動服務err = s.Serve(lis)if err != nil {log.Fatal(err)}
}

四、gin web 路由服務

1、新建grpcpool服務作為gin web服務

由于proto文件與上面兩個服務的一樣的,只是go_package路徑需要改變一下,就不重復貼這里了,看下面目錄結構即可

在這里插入圖片描述

2、根據proto文件,分別生成keywords服務和sensitive服務的pb.go文件

因為gin web 服務相當于路由服務,里面會通過rpc調用遠程的兩個服務,所以那兩個遠程服務的客戶端代碼就是寫到gin web 服務中的,因此也需要pb.go文件存根。生成后如下:

keywords客戶端存根生成命令

 protoc --proto_path=33-grpc-pool/grpcpool/services/keywords/proto --go_out=. --go-grpc_out=. 33-grpc-pool/grpcpool/services/keywords/proto/keywords.proto

sensitive客戶端存根生成命令

protoc --proto_path=33-grpc-pool/grpcpool/services/sensitive/proto --go_out=. --go-grpc_out=. 33-grpc-pool/grpcpool/services/sensitive/proto/sensitive.proto

在這里插入圖片描述

五、grpc 連接池實現

1、連接池的實現,通過sync.Pool實現

sync.Pool 知識補充

**結構如下:**主要是關注New字段,是一個方法,需要我們在初始化的時候提供,用于告知如何生成 池中的連接
在這里插入圖片描述
Pool具有的方法: 主要關注GetPut方法,用于獲取和歸還連接。與數據庫連接池不太一樣,數據庫連接池一個連接用完了會自動返回池中,而sync.Pool中的連接用完了,需要我們手動的放回去,故提供了一個Put方法
在這里插入圖片描述

定義grpc-client-pool.go文件實現連接池,內容如下

package servicesimport ("log""sync""google.golang.org/grpc""google.golang.org/grpc/connectivity"
)// 注意這里是大寫開頭,定義的是一個接口
type ClientPool interface {Get() *grpc.ClientConnPut(conn *grpc.ClientConn)
}// 注意這里是小寫開頭,定義的是結構體,用于實現上面的ClientPool接口
type clientPool struct {pool sync.Pool
}// 獲取連接池對象,并定義新建連接的方法,返回ClientPool接口類型
func GetPool(target string, opts ...grpc.DialOption) (ClientPool, error) {return &clientPool{pool: sync.Pool{New: func() any {conn, err := grpc.Dial(target, opts...)if err != nil {log.Fatal(err)}return conn},},}, nil
}// 從連接池中獲取一個連接
func (c *clientPool) Get() *grpc.ClientConn {conn := c.pool.Get().(*grpc.ClientConn)// 當連接不可用時,關閉當前連接,并新建一個連接if conn.GetState() == connectivity.Shutdown || conn.GetState() == connectivity.TransientFailure {conn.Close()conn = c.pool.New().(*grpc.ClientConn)}return conn
}// 與數據庫連接池不太一樣,數據庫連接池一個連接用完了會自動返回池中
// 而sync.Pool中的連接用完了,需要我們手動的放回去,故提供一個Put方法
func (c *clientPool) Put(conn *grpc.ClientConn) {// 當連接不可用時,關閉當前連接,并不再放回池中if conn.GetState() == connectivity.Shutdown || conn.GetState() == connectivity.TransientFailure {conn.Close()return}c.pool.Put(conn)
}

2、連接池的使用

和連接池相關的代碼文件如下:
在這里插入圖片描述
各個接口,類之間的關系如下:
在這里插入圖片描述

首先,由于我們gin web 服務需要調用多個不同rpc服務,每個遠程rpc服務,我們都應該建立一個對應的客戶端連接池,所以為了統一,建立一個ServiceClient接口,并提供一個默認實現DefaultClient。第二點,建立遠程rpc服務的客戶端時(我們給sync.PoolNew字段傳的函數grpc.Dial(target, opts...)),可能想傳入不同的可選項,所以我們提供了一個opts文件,專門存放這些可選性,如安全連接校驗等。

client.go

package clientimport ("golang-trick/33-grpc-pool/grpcpool/services""log"
)type ServiceClient interface {GetPool(addr string) services.ClientPool
}type DefaultClient struct {
}func (c *DefaultClient) GetPool(addr string) services.ClientPool {pool, err := services.GetPool(addr, c.getOptions()...)if err != nil {log.Fatal(err)}return pool
}// 還可以有很多其他的實現,比如KeywordsClient,SensitiveClient等,這里為了簡單,就只寫了DefaultClient

opts.go

package clientimport ("google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func (c *DefaultClient) getOptions() (opts []grpc.DialOption) {opts = make([]grpc.DialOption, 0)opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))return opts
}// 不同的實現可能有不同的opts,比較復雜的時候,還可以考慮使用函數式選項模式

現在可以分別創建keywordssensitive服務對應的客戶端連接池對象了,使用單例

33-grpc-pool/grpcpool/services/keywords/client.go

package keywordsimport ("golang-trick/33-grpc-pool/grpcpool/services""golang-trick/33-grpc-pool/grpcpool/services/client""sync"
)//	注意是小寫的,因為一個gin web服務,我們只希望它對一個grpc服務持有的連接池是一個單例
// 因此小寫,避免其他地方可以構造這個結構體的對象。然后這里通過once控制是單例
type kwClient struct {// 內嵌client.DefaultClient,從而實現了ServiceClient接口// 如果有其他實現,比如KeywordsClient ,那么內嵌KeywordsClient即可client.DefaultClient
}var pool services.ClientPool
var once sync.Once// 實際工作中,這里應該用服務的注冊與發現機制,這里只是會了簡單演示,所以寫死了服務端的地址
var kwAddr = "localhost:50052"func GetKwClientPool() services.ClientPool {once.Do(func() {c := &kwClient{}// 實際調用的是內嵌的DefaultClient的GetPoolpool = c.GetPool(kwAddr)})return pool
}

33-grpc-pool/grpcpool/services/sensitive/client.go

package sensitiveimport ("golang-trick/33-grpc-pool/grpcpool/services""golang-trick/33-grpc-pool/grpcpool/services/client""sync"
)//	注意是小寫的,因為一個gin web服務,我們只希望它對一個grpc服務持有的連接池是一個單例
// 因此小寫,避免其他地方可以構造這個結構體的對象。然后這里通過once控制是單例
type sensitiveClient struct {// 內嵌client.DefaultClient,從而實現了ServiceClient接口// 如果有其他實現,比如SensitiveClient ,那么內嵌SensitiveClient即可client.DefaultClient
}var pool services.ClientPool
var once sync.Once// 實際工作中,這里應該用服務的注冊與發現機制,這里只是會了簡單演示,所以寫死了服務端的地址
var sensitiveAddr = "localhost:50051"func GetSensitiveClientPool() services.ClientPool {once.Do(func() {c := &sensitiveClient{}// 實際調用的是內嵌的DefaultClient的GetPoolpool = c.GetPool(sensitiveAddr)})return pool
}

gin web啟動函數main.go

package mainimport ("golang-trick/33-grpc-pool/grpcpool/controllers""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("/ping", controllers.Ping)r.Run()
}

ping函數中通過客戶端連接池調用遠程服務

package controllersimport ("context""fmt""golang-trick/33-grpc-pool/grpcpool/services/keywords"kwProto "golang-trick/33-grpc-pool/grpcpool/services/keywords/proto""golang-trick/33-grpc-pool/grpcpool/services/sensitive"sensitiveProto "golang-trick/33-grpc-pool/grpcpool/services/sensitive/proto""net/http""github.com/gin-gonic/gin"
)func Ping(ctx *gin.Context) {// 建立一個sensitive服務的客戶端單例連接,并調用sensitive遠程rpc服務的Validate接口spool := sensitive.GetSensitiveClientPool()sconn := spool.Get()// 注意用完后需要將連接手動放回連接池defer spool.Put(sconn)sensitiveClient := sensitiveProto.NewSensitiveFilterClient(sconn)sIn := &sensitiveProto.ValidateRequest{Input: "今天天氣很好"}sensitiveRes, err := sensitiveClient.Validate(context.Background(), sIn)fmt.Printf("%+v    %+v  \n", sensitiveRes, err)// 建立一個keywords服務的客戶端單例連接,并調用keywords遠程rpc服務的Match接口kpool := keywords.GetKwClientPool()kconn := kpool.Get()// 注意用完后需要將連接手動放回連接池defer kpool.Put(kconn)keywordsClient := kwProto.NewKeyWordsMatchClient(kconn)kIn := &kwProto.MatchRequest{Input: "今天天氣很好"}keywordsRes, err := keywordsClient.Match(context.Background(), kIn)fmt.Printf("%+v    %+v  \n", keywordsRes, err)ctx.JSON(http.StatusOK, gin.H{"message": "pong",})
}

測試:
啟動keywords服務和sensitive服務,以及gin web服務,然后訪問http://localhost:8080/ping

在這里插入圖片描述
終端也可以看到兩個遠程服務都調用成功啦
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

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

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

相關文章

GEE影像升尺度(10m->250m)

GEE影像升尺度(10m->250m) 代碼 var ext /* color: #d63000 *//* shown: false *//* displayProperties: [{"type": "rectangle"}] */ee.Geometry.Polygon([[[108.74625980473367, 28.562445155322063],[108.74625980473367, …

【MySQL】之死鎖問題及其解決方案

前言 數據庫死鎖問題是我們老生常談的問題了,在我們實際開發過程中經常會遇到,為了盡量避免出現死鎖,我們需要了解出現死鎖的場景。同時,如果線上出現了死鎖之后怎么去分析、排查和解決,下面我就這兩點介紹一下。 一、…

ubuntu22.04 怎么開啟SSH服務

在 Ubuntu 22.04 LTS 中,默認情況下不會自動啟動 SSH 服務。如果你想通過 SSH 訪問你的 Ubuntu 系統,你需要手動安裝 SSH 服務器,并確保 22 端口(SSH 的默認端口)是開放的。以下是必要的步驟: 安裝 SSH 服…

Java 多線程之同步(鎖)相關類總結

文章目錄 一、概述二、volatile 可見性/有序性三、synchronized 互拆鎖/排他鎖/非觀鎖四、DCL(Double-Checked Locking)五、CAS(Compare and Set)六、ReentrantLock 可重入鎖/公平/非公平鎖七、ReentrantReadWriteLock 讀寫鎖/共享…

Day56力扣打卡

打卡記錄 數對統計&#xff08;DP狀態壓縮&#xff09; 參考文獻 #include <bits/stdc.h>using namespace std;void solve(){int n;cin >> n;map<int, int> mapp;vector<int> a(n);for (auto& x : a){cin >> x;mapp[x] ;}vector<array&…

使用WebyogSQLyog使用數據庫

數據庫 實現數據持久化到本地&#xff1a; 使用完整的管理系統統一管理&#xff0c; 數據庫&#xff08;DateBase&#xff09;&#xff1a; 為了方便數據存儲和管理&#xff08;增刪改查&#xff09;&#xff0c;將數據按照特定的規則存儲起來 安裝WebyogSQLyog -- 創建數…

101基于matlab的極限學習機ELM算法進行遙感圖像分類

基于matlab的極限學習機ELM算法進行遙感圖像分類&#xff0c;對所獲取的遙感圖片進行初步分類和最終分類。數據可更換自己的&#xff0c;程序已調通&#xff0c;可直接運行。

如何使用 Explain 分析 SQL 語句?

如何使用 Explain 分析 SQL 語句&#xff1f; MySQL中EXPLAIN命令是我們分析和優化SQL語句的利器。 如何使用EXPLAIN來分析SQL語句&#xff0c;接下來有15個例子&#xff0c;一起學習唄 1. EXPLAIN的基本使用 EXPLAIN可以用于分析MySQL如何執行一個SQL查詢&#xff0c;包括如…

ElasticSearch之cat repositories API

命令樣例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/repositories?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"執行結果輸出如下&#xff1a; id type repo1 fs repo2 s3查…

python+gdal地理坐標轉投影坐標

1 前言 地理坐標系&#xff0c;是使用三維球面來定義地球表面位置&#xff0c;以實現通過經緯度對地球表面點位引用的坐標系。 地理坐標系經過地圖投影操作后就變成了投影坐標系。而地圖投影是按照一定的數學法則將地球橢球面上點的經維度坐標轉換到平面上的直角坐標。 2 流程…

基于STM32的四位數碼管計數器設計與實現

?作者簡介&#xff1a;熱愛科研的嵌入式開發者&#xff0c;修心和技術同步精進&#xff0c; 代碼獲取、問題探討及文章轉載可私信。 ? 愿你的生命中有夠多的云翳,來造就一個美麗的黃昏。 &#x1f34e;獲取更多嵌入式資料可點擊鏈接進群領取&#xff0c;謝謝支持&#xff01;…

Docker Compose(容器編排)——9

目錄 什么是 Docker Compose生活案例為什么要 Docker ComposeDocker Compose 的安裝Docker Compose 的功能Docker Compose 使用場景Docker Compose 文件&#xff08;docker-compose.yml&#xff09; 文件語法版本文件基本結構及常見指令Docker Compose 命令清單 命令清單如下命…

垃圾回收器CMS和G1的區別

CMS和G1的區別 區別一&#xff1a; 使用范圍不一樣 CMS收集器是老年代的收集器&#xff0c;可以配合新生代的Serial和ParNew收集器一起使用 G1收集器收集范圍是老年代和新生代。不需要結合其他收集器使用 區別二&#xff1a; STW的時間 CMS收集器以最小的停頓時間為目標的收…

C++11(下)

可變參數模板 C11的新特性可變參數模板能夠創建可以接受可變參數的函數模板和類模板. 相比C98/03, 類模版和函數模版中只能含固定數量的模版參數, 可變模版參數無疑是一個巨大的改進, 然而由于可變模版參數比較抽象, 使用起來需要一定的技巧, 所以這塊還是比較晦澀的.掌握一些基…

Vue 3項目的運行過程

概述&#xff1a; 使用Vite構建Vue 3項目后&#xff0c;當執行yarn dev命令啟動服務時&#xff0c;項目就會運行起來&#xff0c;該項目會通過src\main.js文件將src\App.vue組件渲染到index.html文件的指定區域。 文件介紹&#xff1a; src\App.vue文件 Vue 3項目是由各種組件…

遞歸實現指數型枚舉

title: 遞歸實現指數型枚舉 date: 2023-12-10 19:29:20 tags: 遞歸 catgories: 算法進階指南 —> 傳送門 題目大意 從 1 ~ n n n 這 n n n 個整數隨機選取任意多個&#xff0c;輸出所有可能的選擇方案 思路 這等價于每個整數可以選或者不選&#xff0c;所有的方案總數共有…

Spring Boot的日志

打印日志 打印日志的步驟: ? 在程序中得到日志對象. ? 使用日志對象輸出要打印的內容 在程序中得到日志對象 在程序中獲取日志對象需要使用日志工廠LoggerFactory,代碼如下: package com.example.demo;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public c…

STM32——繼電器

繼電器工作原理 單片機供電 VCC GND 接單片機&#xff0c; VCC 需要接 3.3V &#xff0c; 5V 不行&#xff01; 最大負載電路交流 250V/10A &#xff0c;直流 30V/10A 引腳 IN 接收到 低電平 時&#xff0c;開關閉合。

Go Fyne 入門

Fyne是一個用于創建原生應用程序的UI工具包&#xff0c;它簡單易用&#xff0c;并且支持跨平臺。以下是一個簡單的Fyne教程&#xff0c;幫助你入門&#xff1a; 1. 安裝Fyne 首先&#xff0c;確保你已經安裝了Go語言。然后&#xff0c;在終端中運行以下命令來安裝Fyne&#x…

android-xml語法

xml解析器 Android的XML文件語法是由Android系統中的解析器解析的。具體來說&#xff0c;Android使用了一個名為"Android Asset Packaging Tool (AAPT)"的工具來解析和處理XML文件。AAPT負責將XML文件編譯為二進制格式&#xff0c;并在構建過程中將其打包到Android應…