請求量太大扛不住怎麼辦?進來學一招

来源:https://www.cnblogs.com/zhuochongdashi/archive/2022/12/23/17001202.html
-Advertisement-
Play Games

hello,大家好呀,我是小樓。 上篇文章《一言不合就重構》 說了我最近重構的一個系統,雖然重構完了,但還在灰度,這不,在灰度過程中又發現了一個問題。 背景 這個問題簡單說一下背景,如果不明白可以看上篇文章 ,不想看也沒關係,這是個通用的解法,後面我會總結抽象下。 在上篇文章的最後提到對每個摘除的地 ...


hello,大家好呀,我是小樓。

上篇文章《一言不合就重構》 說了我最近重構的一個系統,雖然重構完了,但還在灰度,這不,在灰度過程中又發現了一個問題。

背景

這個問題簡單說一下背景,如果不明白可以看上篇文章 ,不想看也沒關係,這是個通用的解法,後面我會總結抽象下。

在上篇文章的最後提到對每個摘除的地址做決策時,需要順序執行,且每一個要摘除的地址都要實時獲取該集群的地址信息,以便做出是否需要兜底的決策。

當被摘除的機器非常多時,獲取地址信息的請求量就會非常大,對註冊中心造成了不小的壓力。

請求數據源的介面如下所示(其中 cuuid 是集群的 id)

type Read interface {
	ListClusterEndpoints(ctx context.Context, cuuid string) ([]ptypes.Endpoint, error)
}

相信大家也能理解這個非常簡單的背景並且能想到一些解法。每次決策需要按 cuuid 獲取集群,也就是單個單個地獲取實時集群地址信息,由於是實時信息,緩存首先排除,其次自然而然地能想到如果能將請求合併一下,是不是就能解決請求量大的問題?

難點

如果只是改邏輯合併一下請求,吭哧吭哧改代碼就完了,也不值得寫這篇文章了,如何改最少的代碼來實現合併請求才是最難的。

解法

那天遇到這個問題,晚上輾轉反側想到了這個解法,其實主要也是參考 Go http client 的實現,都說看源碼沒用,這不就是用處麽?

Read 數據源介面定義保持不變,也就是上層的業務代碼完全不用改,只需要把 ListClusterEndpoints 的實現換掉。

我們可以用一個隊列把每個請求入隊,入隊列以後,調用方阻塞,然後起一些協程去隊列里取一批請求參數,發起批量請求,響應之後喚醒阻塞的調用方。

image

為此,我們實現一個可以阻塞並被其他協程喚醒的工具:

type token struct {
	value interface{}
	err   error
}

type Token chan token

func NewToken() Token {
	return make(Token, 1)
}

func (t Token) Done(value interface{}, err error) {
	t <- token{value: value, err: err}
}

func (t Token) Wait(timeout time.Duration) (value interface{}, err error) {
	if timeout <= 0 {
		tk := <-t
		return tk.value, tk.err
	}

	select {
	case tk := <-t:
		return tk.value, tk.err
	case <-time.After(timeout):
		return nil, ErrTokenTimeout
	}
}

其次,定義隊列和其他參數:

type DataSource struct {
	paramCh chan param
	readTimeout time.Duration
	concurrency int
	step int
}

type param struct {
	cuuid string
	token Token
}

替換掉原來 ListClusterEndpoints 的實現:

func (p *DataSource) ListClusterEndpoints(ctx context.Context, cuuid string) ([]ptypes.Endpoint, error) {
	req := param{
		cuuid: cuuid,
		token: NewToken(),
	}

	select {
	case p.paramCh <- req:
	default:
		return nil, fmt.Errorf("list cluster endpoints write channel failed")
	}

	value, err := req.token.Wait(p.readTimeout)
	if err != nil {
		return nil, err
	}
	eps, ok := value.([]ptypes.Endpoint)
	if !ok {
		return nil, fmt.Errorf("value is not endpoints")
	}
	return endpoints, nil
}

再起幾個協程來處理任務:

func (p *DataSource) startListClusterEndpointsLoop() {
	for i := 0; i < p.concurrency; i++ {
		go func() {
			for {
				reqs := p.getListClusterEndpointsReqFromChan()
				p.doBatchListClusterEndpoints(reqs)
			}
		}()
	}
}

最關鍵的是 getListClusterEndpointsReqFromChan 的實現,既不能讓協程空跑,這樣太消耗cpu,又要能及時地取到一批參數,我們採取的方法是先阻塞地獲取一個參數,如果沒數據則阻塞,如果有數據,繼續取,直到數量達到上限或者取不到數據為止,此時這一批數據就可以批量地進行調用了。

func (p *DataSource) getListClusterEndpointsReqFromChan() []param {
	reqs := make([]param, 0)
	select {
	case req := <-p.paramCh:
		reqs = append(reqs, req)
		for i := 1; i < p.step; i++ {
			select {
			case reqNext := <-p.paramCh:
				reqs = append(reqs, reqNext)
			default:
				break
			}
		}
	}
	return reqs
}

最後

這個方法很簡單,但是有一些要註意的地方,得做好監控,比如調用方單個請求的QPS、RT,實際批量請求的QPS、RT,這樣才好計算出處理協程開多少個合適,還有隊列寫入失敗、隊列長度等等監控,當容量不足時及時做出調整。

推薦閱讀

與本文相關的文章也順便推薦給你,如果覺得還不錯,記得關註點贊在看分享


搜索關註微信公眾號"捉蟲大師",後端技術分享,架構設計、性能優化、源碼閱讀、問題排查、踩坑實踐;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • HTML5 介紹 引用 最全面的前端筆記來啦,包含了入門到入行的筆記,還支持實時效果預覽。小伙伴們不需要在花時間去寫筆記,或者是去網上找筆記了。面試高頻提問和你想要的筆記都幫你寫好了。支持移動端和PC端閱讀,深色和淺色模式。 原文鏈接:https://note.noxussj.top/ 什麼是 HT ...
  • 原型鏈與繼承 new 關鍵字的執行過程 讓我們回顧一下,this 指向里提到的new關鍵字執行過程。 創建一個新的空對象 將構造函數的原型賦給新創建對象(實例)的隱式原型 利用顯式綁定將構造函數的 this 綁定到新創建對象併為其添加屬性 返回這個對象 手寫new關鍵字的執行過程: function ...
  • 作者:周可強 一、責任鏈模式簡介 1、責任鏈模式定義 責任鏈(Chain of Responsibility)模式的定義:為了避免請求發送者與多個請求處理者耦合在一起,於是將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有對象處理它為止。 ...
  • 本篇將對 RM 中管理 Application Master 的部分進行深入的講解。 下麵將會介紹 RM 與 AM 整體通信執行流程,並對 RM 中涉及的對應服務進行具體講解。 為了更好的學習本篇知識,建議先熟悉以下知識點,不瞭解的部分可翻到前面對應的文章進行學習: ...
  • 大家好,我是王有志。好久不見,不過這次沒有休假,而是搞了個“大”工程,花了點時間自學Python,然後寫了“玩具爬蟲”,爬某準網的面經數據,為來年的“春暖花開”做準備。 今天作為開篇,還是和大家隨便聊聊: 我為什麼想換工作? 做了哪些準備工作? Java面試到底問啥? 最後,會和大家分享這個“玩具爬 ...
  • 家居網購項目實現07 以下皆為部分代碼,詳見 https://github.com/liyuelian/furniture_mall.git 16.功能15-會員顯示登錄名 16.1需求分析/圖解 會員登錄成功 login_ok.jsp顯示歡迎信息 返迴首頁,顯示登錄相關菜單,如果有登錄過,顯示如上 ...
  • C++核心編程 本階段主要針對C++==面向對象==編程技術做詳細講解,探討C++中的核心和精髓。 1 記憶體分區模型 C++程式在執行時,將記憶體大方向劃分為4個區域 代碼區:存放函數體的二進位代碼,由操作系統進行管理的 全局區:存放全局變數和靜態變數以及常量 棧區:由編譯器自動分配釋放, 存放函數的 ...
  • 聲明 本文章中所有內容僅供學習交流,抓包內容、敏感網址、數據介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯繫我立即刪除! 本文章未經許可禁止轉載,禁止任何修改後二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請在公眾號【K ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...