Kerry 的筆記本
  • Table of contents
  • Kerry的Mac裝機必要
  • ASP.NET Core 教育訓練文件
    • .NET 9 OpenAPI 介紹與教學
    • 目錄
    • ASP.NET Core Authentication系列(一)理解Claim, ClaimsIdentity, ClaimsPrincipal
    • ASP.NET Core Authentication系列(三)Cookie選項
    • ASP.NET Core Authentication系列(二)實現認證、登錄和註銷
    • ASP.NET Core Authentication系列(四)基於Cookie實現多應用間單點登錄(SSO)
    • ASP.NET Core Consul 教學
    • ASP.NET Core Hangfire 排程管理
    • ASP.NET Core KeyCloak 實作
    • ASP.NET Core NLog-依照Environment使用Nlog.Config檔案
    • ASP.NET Core NLog-如何使用 NLog 將 log 寫到檔案
    • ASP.NET Core Nlog-發送訊息到ElasticSearch
    • 目錄
    • ASP.NET Core Quartz.NET 管理介面
    • ASP.NET Core RDLC 報表設計
    • ASP.NET Core SFTP (使用第三方套建 SSH.Net) - 類別庫為案例
    • ASP.NET Core 中使用 HttpReports 進行接口統計,分析, 可視化, 監控,追踪等
    • ASP.NET 使用 MassTransit 與 RabbitMQ,實現事件發佈、訂閱
    • Asp.Net Core 分散式Session – 使用 Redis
    • ASP.NET Core 前台會員修改個人資料
    • ASP.NET Core 前台會員忘記密碼與重設密碼
    • ASP.NET Core 前台會員登入
    • ASP.NET Core 前台會員註冊
    • ASP.NET Core 呼叫 API 發生 CORS 錯誤
    • ASP.NET Core 如何套網頁設計樣版
    • ASP.NET Core 客製化Model Validation 預設錯誤訊息
    • ASP.NET Core 後台查詢頁面教學
    • ASP.NET Core 網站生命週期
    • ASP.NET Feature Management 使用說明與教學
    • ASP.NET RulesEngine 介紹
    • ASP.NET WinForms APP 程式安裝檔
    • LinePay 支付完成後返回 LINE 應用而不跳出外部瀏覽器
    • EntityFramework
      • EF Core Migrations 完整教學手冊
      • EntityFramework Core DB Migrations
      • 使用 Entity Framework Core (EF Core) 的 Migrations 功能進行版本控制
    • NET 6
      • .NET 6 Autofac範例
      • .NET 6 Automapper範例
      • .NET 6 BenchmarkDotNet範例
      • .NET 6 Bogus範例
      • .NET 6 Dapper範例
      • .NET 6 Dapper語法說明
      • .NET 6 EFCore範例
      • .NET 6 EFCore語法說明
      • .NET 6 EPPlus圖表範例
      • .NET 6 EPPlus範例
      • .NET 6 Hangfire範例
      • .NET 6 HttpClient單元測試範例
      • .NET 6 MailKit前置作業
      • .NET 6 MailKit範例
      • .NET 6 Moq範例
      • .NET 6 NLog範例
      • .NET 6 NLog進階範例
      • .NET 6 Serilog範例
      • .NET 6 Serilog進階範例
      • .NET 6 Telegram.Bot前置作業
      • .NET 6 Telegram.Bot範例
      • .NET 6 Text.Json範例
      • .NET 6 swagger授權
      • .NET 6 swagger範例
      • .NET 6 xUnit範例
      • .NET 6 取得appsettings檔案內容
      • .NET 6 更改回傳Json時為大駝峰命名
      • .NET 6 解決System.Text.Json序列化後會將所有非ASCII轉為Unicode
    • WDMIS
      • CORS
      • FeatureManagement
      • Serilog
      • Spectre.Console
      • 資料模型實戰:從 MSSQL 設計到 .NET 8 WebAPI 實作(以刀具管理為例)
  • Azure
    • 如何在 ASP.NET CORE 5.0 WEB 應用程序中實現 AZURE AD 身份驗證
    • Azure App Configuration 使用教學
    • Azure Blob Storage
    • Azure DevOps 持續整合(CI) + Artifacts
  • CSharp
    • ASP.NET await 與 wait 的差異
    • AutoMapper —— 類別轉換超省力
    • C# 中的 HTTPClient — 入門指南
    • C# 正則表達式:從零到英雄指南
    • C# 集合, List<> 取交集、差集、聯集的方法
    • C#單元測試教學
    • CORS 介紹與設定方式
    • CSharp Coding Conventions
    • Using jQuery Unobtrusive AJAX in ASP.NET Core Razor Pages
    • 深入Dapper.NET源碼
    • 菜雞與物件導向
      • 菜雞與物件導向 (0): 前言
      • 菜雞與物件導向 (1): 類別、物件
      • 菜雞與物件導向 (10): 單一職責原則
      • 菜雞與物件導向 (11): 開放封閉原則
      • 菜雞與物件導向 (12): 里氏替換原則
      • 菜雞與物件導向 (13): 介面隔離原則
      • 菜雞與物件導向 (14): 依賴反轉原則
      • 菜雞與物件導向 (15): 最少知識原則
      • 菜雞與物件導向 (2): 建構式、多載
      • 菜雞與物件導向 (3): 封裝
      • 菜雞與物件導向 (4): 繼承
      • 菜雞與物件導向 (5): 多型
      • 菜雞與物件導向 (6): 抽象、覆寫
      • 菜雞與物件導向 (7): 介面
      • 菜雞與物件導向 (8): 內聚、耦合
      • 菜雞與物件導向 (9): SOLID
      • 菜雞與物件導向 (Ex1): 小結
  • DBeaver
    • 如何強制讓 DBeaver 在 Mac 上使用英文介面
  • DesignPattern
    • OAuth
    • Repository 模式 (Repository Pattern)
    • Single Sign On 實作方式介紹 (CAS)
    • 【SOP製作教學】新手適用,SOP範例、流程圖、製作流程全公開!
    • 【SOP製作教學】流程圖教學、重點範例、BPMN符號介紹!
    • 【SOP製作教學】流程圖符號整理、BPMN2.0進階符號教學!
    • 多奇數位 C# 程式碼撰寫規範 (C# Coding Guideline)
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 開源程式碼檢測平台 SonarQube
    • 菜雞新訓記
      • 菜雞新訓記 (0): 前言
      • 菜雞新訓記 (1): 使用 Git 來進行版本控制吧
      • 菜雞新訓記 (2): 認識 Api & 使用 .net Core 來建立簡單的 Web Api 服務吧
      • 菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧
      • 菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧
      • 菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧
      • 菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧
      • 菜雞新訓記 (7): 使用 Fluent Validation 來驗證參數吧
  • DevOps
    • Repository 模式 (Repository Pattern)
    • pipeline工具研究
    • 單例模式 (Singleton Pattern)
    • 單元測試
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 雙重檢查鎖定模式 (Double-Checked Locking Pattern)
  • Docker
    • Docker 中部署 .NET 8 Web App 並支援 HTTPS
    • Docker指令大全
    • 第七章 安裝Nomad
    • Docker - 第三章 | 安裝 MSSQL
    • Docker - 第九章 | 安裝 datalust seq
    • 第二章 docker-compose 教學
    • Docker - 第五章 | 安裝 Redis
    • 第八章 安裝SonarQube
    • Docker - 第六章 | 安裝RabbitMQ
    • 第十一章 安裝 VtigerCRM
    • 第十二章 安裝KeyCloak
    • Docker - 第十章 | 安裝 Redmine
    • 第四章 安裝MySQL
    • Docker Desktop (含更改 Docker Image 路徑)
  • Git
    • Git Flow 指令大全(完整指令整理) 🚀
    • Git 安裝及配置SSH Key
    • Git 建立到上傳
    • 將現有專案的遠端儲存庫直接更改為新的儲存庫
    • Git 流程規劃
    • Git 語法大全
    • 30 天精通 Git 版本控管
      • 30 天精通 Git 版本控制
        • 第 01 天:认识 Git 版本控制
        • 第 02 天:在 Windows 平台必装的三套 Git 工具
        • 第 03 天:建立仓库
        • 第 04 天:常用的 Git 版本控制指令
        • 第 05 天:了解仓库、工作目录、物件与索引之间的关系
        • 第 06 天:解析 Git 资料结构 - 物件结构
        • 第 07 天:解析 Git 资料结构 - 索引结构
        • 第 08 天:关于分支的基本观念与使用方式
        • 第 09 天:比对文件与版本差异
        • 第 10 天:认识 Git 物件的绝对名称
        • 第 11 天:认识 Git 物件的一般参照与符号参照
        • 第 12 天:认识 Git 物件的相对名称
        • 第 13 天:暂存工作目录与索引的变更状态
        • 第 14 天: Git for Windows 选项设定
        • 第 15 天:标签 - 标记版本控制过程中的重要事件
        • 第 16 天:善用版本日志 git reflog 追踪变更轨迹
        • 第 17 天:关于合并的基本观念与使用方式
        • 第 18 天:修正 commit 过的版本历史记录 Part 1
        • 第 19 天:设定 .gitignore 忽略清单
        • 第 20 天:修正 commit 过的版本历史记录 Part 2
        • 第 21 天:修正 commit 过的版本历史记录 Part 3
        • 第 22 天:修正 commit 过的版本历史记录 Part 4 (Rebase)
        • 第 23 天:修正 commit 过的版本历史记录 Part 5
        • 第 24 天:使用 GitHub 远端仓库 - 入门篇
        • 第 25 天:使用 GitHub 远端仓库 - 观念篇
        • 第 26 天:多人在同一个远端仓库中进行版控
        • 第 27 天:通过分支在同一个远端仓库中进行版控
        • 第 28 天:了解 GitHub 的 fork 与 pull request 版控流程
        • 第 29 天:如何将 Subversion 项目汇入到 Git 仓库
        • 第 30 天:分享工作中几个好用的 Git 操作技巧
      • zh-tw
        • 第 01 天:認識 Git 版本控管
        • 第 02 天:在 Windows 平台必裝的三套 Git 工具
        • 第 03 天:建立儲存庫
        • 第 04 天:常用的 Git 版本控管指令
        • 第 05 天:了解儲存庫、工作目錄、物件與索引之間的關係
        • 第 06 天:解析 Git 資料結構 - 物件結構
        • 第 07 天:解析 Git 資料結構 - 索引結構
        • 第 08 天:關於分支的基本觀念與使用方式
        • 第 09 天:比對檔案與版本差異
        • 第 10 天:認識 Git 物件的絕對名稱
        • 第 11 天:認識 Git 物件的一般參照與符號參照
        • 第 12 天:認識 Git 物件的相對名稱
        • 第 13 天:暫存工作目錄與索引的變更狀態
        • 第 14 天: Git for Windows 選項設定
        • 第 15 天:標籤 - 標記版本控制過程中的重要事件
        • 第 16 天:善用版本日誌 git reflog 追蹤變更軌跡
        • 第 17 天:關於合併的基本觀念與使用方式
        • 第 18 天:修正 commit 過的版本歷史紀錄 Part 1
        • 第 19 天:設定 .gitignore 忽略清單
        • 第 20 天:修正 commit 過的版本歷史紀錄 Part 2
        • 第 21 天:修正 commit 過的版本歷史紀錄 Part 3
        • 第 22 天:修正 commit 過的版本歷史紀錄 Part 4 (Rebase)
        • 第 23 天:修正 commit 過的版本歷史紀錄 Part 5
        • 第 24 天:使用 GitHub 遠端儲存庫 - 入門篇
        • 第 25 天:使用 GitHub 遠端儲存庫 - 觀念篇
        • 第 26 天:多人在同一個遠端儲存庫中進行版控
        • 第 27 天:透過分支在同一個遠端儲存庫中進行版控
        • 第 28 天:了解 GitHub 的 fork 與 pull request 版控流程
        • 第 29 天:如何將 Subversion 專案匯入到 Git 儲存庫
        • 第 30 天:分享工作中幾個好用的 Git 操作技巧
  • Hands-On Labs - LineBotSDK 實作手札 (C#, .net core)
    • 00. 如何申請LINE Bot
    • CLI
      • 使用CLI來發送新的Channel Access Token(LINE Bot)
      • 使用CLI免費發送LINE Notify通知
    • basic
      • 如何發送LINE訊息(Push Message)
      • 如何發送LINE Template Messages
      • 如何發送ImageMap訊息
      • 如何發送Flex Message
      • 如何在訊息後面加上QuickReply快捷選項
    • liff
      • Lab 21: 建立第一個LIFF應用
    • webhook
      • 如何建立可Echo的基本LINE Bot
      • 如何在WebHook中取得用戶個人資訊(名稱、頭像、狀態)
      • 如何在WebHook中取得用戶上傳的圖片(Bytes)
  • Markdown
    • Markdown Cheatsheet 中文版
    • Markdown語法大全
    • 使用HackMD建立書本目錄
    • 使用HackMD建立簡報
  • SAP ABAP
    • ABAP開發環境和總體介紹
    • SAP MM模塊常用表總結
    • SAP QM數據庫表清單
    • SAP欄位與表的對應關係
  • SQL Server
    • [SQL SERVER] Like in
    • SQL Server 中,移除資料庫中所有的關聯限制
    • SQL Server 刪除資料庫中所有資料表
    • SQL Server View、Function 及 Stored Procedure 定義之快速備份
    • SSMS v18 清除登入畫面中,下拉選單歷史紀錄
    • [MS SQL]如何透過Database Mail進行郵件發送
    • [SQL SERVER]撰寫Stored Procedure小細節
    • 使用 Data Migration Assistant 移轉 SQL Server 資料庫與帳戶
    • 使用SSIS創建同步資料庫數據任務
  • Tools
    • 免費 FTP 伺服器 FileZilla Server 安裝教學 (新版設定)
  • VisualStudio
    • .NET CLI 指令碼介紹
    • Visual Studio 使用 Git 版本控制
    • 使用 Visual Studio 2022 可透過 .editorconfig 鎖定文字檔案的儲存編碼格式分享
  • Web API
    • ASP.NET Core 6 Web API 進行 JWT 令牌身份驗證
    • [ASP.NET Core]如何使用SwaggerAPI說明文件
    • ASP.NET Core Web Api實作JWT驗證筆記
    • ECFIT API 範例
    • JWT Token Authentication And Authorizations In .Net Core 6.0 Web API
    • 微服務架構 - 從狀態圖來驅動 API 的設計
  • Windows
    • [C#] 伺服器監控常用語法 (事件檢視器、CPU 硬碟使用率、程式執行狀況)
    • Configure IIS Web Server on Windows Server 2019
    • Log Paser Studio 分析 IIS W3C Log
    • Windows Server 2019 如何安裝 IIS 運行 ASP.NET 專案
    • 如何檢查安裝在 IIS 上的 .NET Core Hosting Bundle 版本
    • [IIS] 如何解決網站第一個請求 Request 特別慢 ?
    • IIS 不停機更版設置
    • SQL Server 2019 Standard 繁體中文標準版安裝
    • WINDOWS共用資料夾的網路認證密碼放在哪?如何清除?
    • 如何設定 ASP.NET CORE 網站應用程式持續執行在 IIS 上
  • 專案管理
    • SSDLC (Secure Software Development Life Cycle)
    • 系統開發原則
    • MIS及專案管理-使用Redmine
      • 第10章 - [日常管理]MIS部門週會工作進度追蹤
      • 第11章 - [日常管理]MIS部門主管月會報告管理
      • 第12章 - [日常管理]機房工作日誌
      • 第13章 - [日常管理]MIS部門耗用工時及工作進度檢討
      • 第14章 - [日常管理]MIS文件知識庫
      • 第15章 - [日常管理]整理及管理分享
      • 第16章 - [異常管理]使用者問題回報系統
      • 第17章 - [異常管理]資安事件及異常紀錄
      • 第18章 - [異常管理]整理及管理分享
      • 第19章 - [變革管理]MIS的專案及專案管理五大階段
      • 第1章 - [MIS及專案管理]中小企業MIS的鳥事
      • 第20章 - [變革管理]MIS的新專案管理:起始階段
      • 第21章 - [變革管理]MIS的新專案管理:規劃階段
      • 第22章 - [變革管理]MIS的新專案管理:執行階段
      • 第23章 - [變革管理]MIS的新專案管理:監控階段
      • 第24章 - [變革管理]MIS的新專案管理:結束階段
      • 第25章 - [變革管理]整理及管理分享
      • 第26章 - [ISMS管理]ISMS平台整體規劃
      • 第27章 - [ISMS管理]ISMS文管中心
      • 第28章 - [ISMS管理]ISMS表單紀錄的管理
      • 第29章 - [ISMS管理]整理及管理分享
      • 第2章 - [MIS及專案管理]專案管理的概念及MIS應用
      • 第30章 - 初心、來時路及感謝:系列文章總結回顧
      • 第3章 - [MIS及專案管理]管理工具的選擇
      • 第4章 - [Redmine]Redmine的安裝及設定
      • 第5章 - [Redmine]Redime系統邏輯說明
      • 第6章 - [Redmine]自行建立及維護表單
      • 第7章 - [Redmine]專案版面的規劃
      • 第8章 - [日常管理]AR管理
      • 第9章 - [日常管理]資訊服務申請
  • 微服務架構
    • DDD + CQRS + MediatR 專案架構
    • 微服務架構 #2, 按照架構,重構系統
    • 淺談微服務與網站架構的發展史
    • API First Workshop 設計概念與實做案例
      • API First #1 架構師觀點 - API First 的開發策略 - 觀念篇
      • API First #2 架構師觀點 - API First 的開發策略 - 設計實做篇
    • 基礎建設 - 建立微服務的執行環境
      • Part #1 微服務基礎建設 - Service Discovery
      • Part #2 微服務基礎建設 - 服務負載的控制
      • Part #3 微服務基礎建設 - 排隊機制設計
      • Part #4 可靠的微服務通訊 - Message Queue Based RPC
      • Part #5 非同步任務的處理機制 - Process Pool
    • 實做基礎技術 API & SDK Design
      • API & SDK Design #1, 資料分頁的處理方式
      • API & SDK Design #2, 設計專屬的 SDK
      • API & SDK Design #3, API 的向前相容機制
      • API & SDK Design #4, API 上線前的準備 - Swagger + Azure API Apps
      • API & SDK Design #5 如何強化微服務的安全性 API Token JWT 的應用
    • 建構微服務開發團隊
      • 架構面試題 #1, 線上交易的正確性
      • 架構面試題 #2, 連續資料的統計方式
      • 架構面試題 #3, RDBMS 處理樹狀結構的技巧
      • 架構面試題 #4 - 抽象化設計;折扣規則的設計機制
    • 架構師觀點 - 轉移到微服務架構的經驗分享
      • Part #1 改變架構的動機
      • Part #2 實際改變的架構案例
    • 案例實作 - IP 查詢服務的開發與設計
      • 容器化的微服務開發 #1 架構與開發範例
      • 容器化的微服務開發 #2 IIS or Self Host
  • 系統評估
    • RPA 與 WebAPI 評估
    • 數位轉型:從現有系統到數位化的未來
    • 數位轉型:從現有系統到數位化的未來
  • 面試
    • CV_黃子豪_2024
    • HR 問題集
    • .NET 工程師 面試問題集
    • 資深工程師 問題集
    • 資深開發人員 / 技術主管
    • 題目
Powered by GitBook
On this page
  • Part #2 微服務基礎建設 - 服務負載的控制
  • 微服務の斷路器
  • 如何定義 “服務量” ?
  • 題目: 抽象化的 ThrottleBase 類別
  • 解法 1, CounterThrottle
  • 執行結果
  • 小結
  • 解法 1.5, StatisticEngineThrottle
  • 執行結果
  • 小結
  • 解法 2, Leaky Bucket
  • 執行結果
  • 小結
  • 解法 3, Token Bucket
  • 執行結果
  • 小結
  • 總結
  1. 微服務架構
  2. 基礎建設 - 建立微服務的執行環境

Part #2 微服務基礎建設 - 服務負載的控制

PreviousPart #1 微服務基礎建設 - Service DiscoveryNextPart #3 微服務基礎建設 - 排隊機制設計

Last updated 1 year ago

微服務基礎建設 - 服務負載的控制

微服務の斷路器

服務量管控這件事,在微服務架構上的意義,最直接的就是跟系統乘載能力直接相關。任何系統都有它服務量的上限,某個環節的服務量若沒有掌控好,開始無法正常回應時;這時呼叫端可能會不斷 retry, 讓原本已經很重的負擔更加沉重。這樣的模式拖垮了某個服務之後,可能會讓影響範圍持續擴大,讓其它原本正常的服務也跟著失效,雪崩效應會加速整個系統無法提供服務。

解決問題的方式就是服務量的管控,偵測到這個現象時 (錯誤率增高,或是服務量超過安全警戒),就打開斷路器,暫時隔絕新的 request 繼續丟給該服務處理,以求整體系統的可靠。這時就是斷路器 (circuit breaker) 的設計目的。不過這機制困難點不在於你要挑選什麼 framework 或是 service 來用,而是你是否有辦法精準的掌握你系統內的服務服務量數據,並且有能力做好整合,進一步做到自動化調節服務量的機制?

img

我這篇文章不是要大家自己搞斷路器,而是你要搞清楚服務量這件事,你才能夠正確的運用斷路器來解決問題。這跟高速公路逢年過節,就會啟用匝道管控,確保交通暢通是一樣的道理。這是我把這問題放到微服務架構系列文章的主要原因。不過做這件事情最大的門檻在於 developer 的應變能力與視野啊 (眼界短淺的就直接雙手一攤,讓系統掛掉了)。

如何定義 “服務量” ?

“服務量” 這件事很簡單明確,卻也很抽象。你要先讓 “服務量” 能夠量化,能被測量,後面才能依據這度量值做出反應,才能進一步 “控制” 服務量!

“服務量” 指的就是 “單位時間內的處理量” 而已。例如我家的光纖上網是 100Mbps, 意思就是每一秒鐘, HiNET 能夠提供我最大 100Mbits 的傳輸量。不過我們還是的明確的定義它才行。舉個例子說明,假設我家光纖實體的線路乘載能力遠高於 100Mbps, 那 HiNet 會用什麼機制來 “限制” 我的流量? 我隨便想幾種機制:

  1. 每秒統計累計傳輸量,在 1 sec 內只要不超過 100Mbits, 你能傳多快就傳多快。

  2. 平均下來每傳 1bits 要花費 0.01 ns.. 因此每傳完 1bits 後必須等到 0.01ns 過後才能再傳下個 bits

  3. (懶得算這麼精準了) 只要 60 sec 內傳輸不超過 60sec x 100Mbps = 6Gbits 就好 (average)

  4. …..

講到這邊,你就會發現真正要限制還沒那麼容易,這些規則會影響到你寫出什麼樣的 code… 如果我真的用 (2) 來處理,大概會搞死自己吧! 搞不好你更新統計資料就遠遠超過 0.01 ns … 因此,我重新定義一下題目,主要是該怎麼處理這兩個核心問題:

  1. 你要如何 “定義” 流量的計算方式 (如上所述)? 你會怎麼實作它? 你的方式能否運用到大型分散式的架構上?

  2. 超過流量後的處理方式? (就是斷路器要做的事)

這兩部份想清楚之後,剩下的部分就單純多了。系統的處理流程就很單純了,接到新的 request, 只要確認目前的服務量是否還有餘力受理新的 request ? 若無則直接回應 error, 若有則受理該 request, 用 sync 或是 async 的方式處理都可以。

主流程想通之後,接下來的挑戰則是實作。

題目: 抽象化的 ThrottleBase 類別

我就直接出題了,這次的目標是要繼承 ThrottleBase ,補上你的實作:

public abstract class ThrottleBase
{
    /// <summary>
    /// 每秒鐘處理量的上限 (平均值)
    /// </summary>
    protected double _rate_limit = 100;


    /// <summary>
    /// 
    /// </summary>
    /// <param name="rate">指定服務量上限</param>
    protected ThrottleBase(double rate)
    {
        this._rate_limit = rate;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="amount">Request 的處理量</param>
    /// <param name="exec"></param>
    /// <returns>傳回是否受理 request 的結果。
    /// true: 受理, 會在一定時間內處理該 request;
    /// false: 不受理該 request</returns>
    public abstract bool ProcessRequest(int amount, Action exec = null);
}

唯一要實做的,只有 bool ProcessRequest(int amount, Action exec = null) 這個 method 了。如果你的 throttle 認為能夠受理這個 request, 就直接 return true, 若不能受理, 則 return false… 由於受理跟實際處理,可能還有時間差,因此真正要處理的時候,則呼叫 delegate: exec (如果有給的話)。

至於寫好的 Throttle 應該怎麼使用? 由於這些服務量掌控不是明確的 0 或 1, 有些許誤差存在,因此我沒有用先前的單元測試方式出題;我改用 test console, 預先產生來源資料餵給 Throttle, 透過 console output 輸出統計資料 (CSV),放進 EXCEL CHART 來看看處理的結果。這樣我不需要安裝複雜的監控工具就能視覺化程式的服務量處理結果。

程式主要分為兩部分,第一是產生模擬的服務量。我準備了三段服務量產生的程式碼:

  1. 持續產生平均約 600 RPS (request per second) 的服務量。這邊由 30 個 threads 負責,每個 thread 間隔 0 ~ 100 ms (隨機決定) 產生一個 request

  2. 每 17 sec 會有 2 sec, 產生約 500 RPS 的額外瞬間服務量,藉以測試 Throttle 對於 peek 的處理效果。

  3. 每 20 sec 會有 3 sec, 完全阻隔 (1) 與 (2) 的運作,在這段期間內不產生任何服務量。

第二部分比較簡單,就是固定每秒把統計資料輸出成 CSV 而已:

我先打造了一個很不負責的 DummyThrottle, 它完全不做服務量管控,因此輸出的數據就是產生的服務量:

public class DummyThrottle : ThrottleBase
{
    public DummyThrottle(double rate) : base(rate)
    {
    }

    public override bool ProcessRequest(int amount, Action exec = null)
    {
        //exec?.Invoke();
        Task.Run(exec);
        return true;
    }
}

正好可以對照著看一下第一部份產生甚麼樣的 request:

以上,各位可以驗證一下是否符合來源服務量的描述。接著我們就可以看看各種方式控制服務量後的成果了。輸出的 CSV 統計數據共有這五個欄位:

  1. TotalRequest, 所有發出的 request 總數 => (1) + (2)

  2. SuccessRequest, 成功受理的 request 總數

  3. FailRequest, 拒絕受理的 request 總數 (被 Throttle 拒絕的 request 統計)

  4. ExecutedRequest, 已執行的 request 總數 (Success 後不一定會立即執行,不同演算法可能有不同的時間差)

  5. AverageExecuteTime, 所有已執行的 request, 從受理 (success) 到執行 (execute) 的平均等待時間

這些數據,在前面兩部分的 code 都會不斷的累加 counter, 在這段 code 則會每秒讀出數據之後,reset counter 歸零重新統計。CSV 的資料,只要複製貼上 EXCEL 就可以看到服務量統計表 (如下例):

寫到這邊,萬事俱備! 接下來就等著看面試者的發揮了。不過這題其實有點難度,你可以拿來用在內部的教育訓練,或是內部面試篩選能處理這些問題的人員,也可以拿來面試 senior engineer。搭配 EXCEL 很容易視覺化執行的結果,因此也很適合拿來做 POC 跟討論評估各種做法的優缺點。

這樣的討論方式,可以讓團隊在最短的時間內,花費最少的資源,就能評估做法是否適合。這段落的最後,我放一下完整的 test console 程式碼,有興趣的朋友們可以仔細瞧瞧:

static void Main(string[] args)
{
    ThrottleBase t =
        //new DummyThrottle(500);
        //new CounterThrottle(500, TimeSpan.FromSeconds(5));
        //new CounterThrottle(500, TimeSpan.FromSeconds(1));
        //new StatisticEngineThrottle(500, TimeSpan.FromSeconds(5));
        //new StatisticEngineThrottle(500, TimeSpan.FromSeconds(1));
        //new LeakyBucketThrottle(500, TimeSpan.FromSeconds(5));
        //new LeakyBucketThrottle(500, TimeSpan.FromSeconds(1));
        //new TokenBucketThrottle(500, TimeSpan.FromSeconds(5));
        new TokenBucketThrottle(500, TimeSpan.FromSeconds(1));


    int statistic_success = 0;
    int statistic_fail = 0;

    int statistic_execute = 0;
    long statistic_executeTime = 0;

    //
    //  製造 (平均) 200qps 穩定的流量
    //  產生多個 threads, 用穩定的速度 (會加上亂數打散) 產生 request(s), 交給 throttle 處理。
    //
    List<Thread> threads = new List<Thread>();
    bool stop = false;
    bool idle = false;
    for (int i = 0; i < 30; i++)
    {
        Thread thread = new Thread(() =>
        {
            Random rnd = new Random();
            while (stop == false)
            {
                Stopwatch _timer = new Stopwatch();
                _timer.Start();

                if (idle)
                {

                }
                else if (t.ProcessRequest(1, ()=> { Interlocked.Increment(ref statistic_execute); Interlocked.Add(ref statistic_executeTime, _timer.ElapsedMilliseconds); }))
                {
                    Interlocked.Increment(ref statistic_success);
                }
                else
                {
                    Interlocked.Increment(ref statistic_fail);
                }
                Thread.Sleep(rnd.Next(100));
            }
        });
        thread.Start();
        threads.Add(thread);
    }


    //
    //
    //  製造 peek 的流量 (約 700 ~ 100 qps)
    //  產生1個 threads, 每隔 17 sec, 就有 2 sec 會產生大量 request 交給 throttle 處理。用以測試尖峰流量的處理效果。
    //
    {
        Thread thread = new Thread(() =>
        {
            Stopwatch timer = new Stopwatch();
            while (stop == false)
            {
                timer.Restart();
                while (timer.ElapsedMilliseconds < 2000)
                {
                    Stopwatch _timer = new Stopwatch();
                    _timer.Start();

                    if (idle)
                    {

                    }
                    else if (t.ProcessRequest(1, () => { Interlocked.Increment(ref statistic_execute); Interlocked.Add(ref statistic_executeTime, _timer.ElapsedMilliseconds); }))
                    {
                        Interlocked.Increment(ref statistic_success);
                    }
                    else
                    {
                        Interlocked.Increment(ref statistic_fail);
                    }
                    Thread.Sleep(1);
                    //SpinWait.SpinUntil(() => false, 5);
                }
                Task.Delay(15000).Wait();
            }
        });
        thread.Start();
        threads.Add(thread);
    }

    //
    //  製造離峰的流量, 每 21 秒約 3 秒沒有任何流量
    //
    {
        Thread thread = new Thread(() =>
        {
            Stopwatch timer = new Stopwatch();
            while (stop == false)
            {
                idle = true;
                timer.Restart();
                SpinWait.SpinUntil(() => timer.ElapsedMilliseconds >= 3000);
                idle = false;

                Task.Delay(18000).Wait();
            }
        });
        thread.Start();
        threads.Add(thread);
    }

    //
    //  產生一個 thread, 定期每秒分別統計:
    //  0. 所有發出的 request 總數 => (1) + (2)
    //  1. 成功受理的 request 總數
    //  2. 拒絕受理的 request 總數
    //  3. 已執行的 request 總數
    //
    {
        Thread thread = new Thread(() =>
        {
            Console.WriteLine($"TotalRequests,SuccessRequests,FailRequests,ExecutedRequests,AverageExecuteTime");
            while (stop == false)
            {
                //Console.WriteLine("{0} per sec", Interlocked.Exchange(ref statistic_success, 0));

                int success = Interlocked.Exchange(ref statistic_success, 0);
                int fail = Interlocked.Exchange(ref statistic_fail, 0);
                int exec = Interlocked.Exchange(ref statistic_execute, 0);
                long exectime = Interlocked.Exchange(ref statistic_executeTime, 0);

                double avgExecTime = 0;
                if (exec > 0) avgExecTime = 1.0D * exectime / exec;

                Console.WriteLine($"{success+fail},{success},{fail},{exec},{avgExecTime}");
                Task.Delay(1000).Wait();
            }
        });
        thread.Start();
        threads.Add(thread);
    }


    Thread.Sleep(1000 * 120);
    Console.WriteLine("Shutdown...");

    stop = true;

    foreach(Thread thread in threads)
    {
        thread.Join();
    }
}

解法 1, CounterThrottle

絕大部分的人,拿到這個題目,大概就是很直覺的分析題目的語意吧! 既然是限制單位時間內的總處理量,那我就設置一個 counter, 每隔固定時間就 reset 歸零。處理過程中只要 counter 不超過上限即可。於是就有第一版 CounterThrottle 出現了:

public class CounterThrottle : ThrottleBase
{
    private TimeSpan _timeWindow = TimeSpan.Zero;
    private double _counter = 0;

    public CounterThrottle(double rate, TimeSpan timeWindow) : base(rate)
    {
        this._timeWindow = timeWindow;

        Thread t = new Thread(() =>
        {
            Stopwatch timer = new Stopwatch();
            while (true)
            {
                this._counter = 0;
                timer.Restart();
                SpinWait.SpinUntil(() => { return timer.Elapsed >= this._timeWindow; });
            }
        });
        t.Start();
    }

    public override bool ProcessRequest(int amount, Action exec = null)
    {
        if (amount + this._counter > this._rate_limit * this._timeWindow.TotalSeconds) return false;

        this._counter += amount;
        Task.Run(exec);
        return true;
    }
}

執行結果

程式碼很短,我就不說明了。我設定了 rate: 500 rps, time window: 5 sec, 來看看執行的結果:

console output (csv):

TotalRequests,SuccessRequests,FailRequests,ExecutedRequests,AverageExecuteTime
30,30,0,30,4.76666666666667
0,0,0,0,0
0,0,0,0,0
0,0,0,0,0
547,547,0,547,0
582,582,0,582,0
545,545,0,545,0
664,664,0,664,0
552,552,0,552,0
483,483,0,483,0
581,343,238,343,0.00291545189504373
575,575,0,575,0

(以下略)

statistic (chart):

很明顯的可以看到,雖然我們的目的是服務量管控,但是從結果來看,最後通過執行的服務量表現很不穩定啊… 在 time window (5 sec) 的範圍內,可能碰到 peek 在一瞬間就把 time window (5 sec) 內的額度 (500 x 5 = 2500 requests) 通通用光了,導致後面一段時間內的服務量都被拒絕了。

CounterThrottle 額定的服務量限定在 500 RPS, 不過看跑出來的結果,很明顯的實際服務量在 0 ~ 1175 之間跳動啊,感覺沒有發揮太大的效用。這演算法的缺點就是: 你無法掌控 time window 範圍內的表現。設的過短,你會花很多運算能力在服務量控制這件事上。設的過長,就會有這案例的現象。服務量掌控的不夠精準,後果就是後端的服務可能在瞬間被打垮,而垮掉之後又有一段時間內沒有 request ..

我把設定範圍調整一下,改為 rate: 500, time window: 1 sec, 重新看一次執行結果:

藉由縮小 time window, 得到的結果稍為平緩了一些。不過還是可以看到波動的幅度很大,難以預測跟控制。如果你有選購過 UPS 你就知道,這樣的凸波會害死後面的機器.. XD

小結

我下個簡單的結論: 這個演算法只能做到基本的限流。如果你的目的只是要限制 user 的用量,或是按照速度計費,這個方式就夠了。如果你的目的是預測或是限制服務量,要保護後端的服務平穩可靠的運行,這方是明顯的不足。

解法 1.5, StatisticEngineThrottle

這個解法是我額外插進來的,基本上只是 (1) CounterThrottle 的延伸,本質上還是依樣透過統計的方式來修正服務量的計算。

上個做法主要的缺點,在於每個 time window 之間有明顯的分界。例如 0 ~ 5 sec 之間是一個段落,6 ~ 10 sec 又是一個段落。每個段落是獨立統計的,因此很容易在兩個段落的交界處發生爆量的情況。很極端的狀況下,交界處可能在一瞬間 (ex: 0.1 sec) 把兩個段落的所有額度 ( 5 sec x 500 rps x 2 = 5000 requests ) 用光,造成瞬間巨量。這就違背了我們限制服務量的用意了。

直接看程式碼:

public class StaticEngineThrottle : ThrottleBase
{
    //private EngineBase _peek_engine = null;// new InMemoryEngine(timeWindow: TimeSpan.FromSeconds(1));
    private EngineBase _average_engine = null;// new InMemoryEngine(timeWindow: TimeSpan.FromSeconds(3));
    //private double _peek_limit = 0;


    public StaticEngineThrottle(double averageRate, TimeSpan averageTimeWindow) : base(averageRate)
    {
        //this._peek_limit = peekRate;

        this._average_engine = new InMemoryEngine(timeWindow: averageTimeWindow);
        //this._peek_engine = new InMemoryEngine(timeWindow: peekTimeWindow);
    }

    public override bool ProcessRequest(int amount, Action exec = null)
    {
        if (
            //this._peek_engine.AverageResult < this._peek_limit &&
            this._average_engine.AverageResult < this._rate_limit)
        {
            //this._peek_engine.CreateOrders(amount);
            this._average_engine.CreateOrders(amount);
            exec?.Invoke();
            return true;
        }

        return false;
    }
}

執行結果

我在 StaticEngineThrottle 裡面,藏了一個 InMemoryEngine: _average_engine, 用來取代上個例子單獨的 counter… 運作的原理是透過這個 InMemoryEngine, 統計過去 {time window} sec 內累計的處理量。修正過的做法,我一樣採用指定速率: rate = 500 rps, time window = 5 sec, 以下是跑出來的結果, 服務量控制的波動幅度穩定一些:

一樣做個對照組,改用 rate = 500 rps, time window = 1 sec:

小結

結論: 這個做法雖然較 (1) CounterThrottle 來的好一些,但是我認為只有做到 “改良” 的程度而已。基本上只靠 counter 的限制還是存在。我單純只是延續前一篇文章的內容,順便驗證一下改善的效果。這方法在技術使用上較為精進,不過還沒有很到位的解決這個需求,仍有改進的空間。

解法 2, Leaky Bucket

用 counter 的方式,最大的障礙在於存在 time window 的邊界會。在 time window 切換的瞬間,容易產生瞬間的大量請求造成 peek,無法穩定的控制服務量。我們需要尋求其他更理想的控制方式。

我順手 google 了業界較通用的演算法,其中一種就叫做漏桶演算法 (Leaky Bucket)。它的觀念很簡單,想像一個桶子,你的 request 就像是水一樣不斷的倒進去,只要桶子還沒滿你都可以繼續倒,能倒的進去就算 success, 倒不進去就算 fail。而桶子的底部有個小孔,可以用你指定的固定速率把水漏掉。漏掉的水就是會被處理的服務量 (exec)。

The leaky bucket is an algorithm based on an analogy of how a bucket with a leak will overflow if either the average rate at which water is poured in exceeds the rate at which the bucket leaks or if more water than the capacity of the bucket is poured in all at once, and how the water leaks from the bucket at an (almost) constant rate. It can be used to determine whether some sequence of discrete events conforms to defined limits on their average and peak rates or frequencies, or to directly limit the actions associated to these events to these rates, and may be used to limit these actions to an average rate alone, i.e. remove any variation from the average.

我自己實做了一個簡單的版本: LeakyBucketThrottle

public class LeakyBucketThrottle : ThrottleBase
{
    private double _max_bucket = 0;
    private double _current_bucket = 0;
    private object _syncroot = new object();
    private int _interval = 100;    // ms

    private Queue<(int amount, Action exec)> _queue = new Queue<(int amount, Action exec)>();

    public LeakyBucketThrottle(double rate, TimeSpan timeWindow) : base(rate)
    {
        this._max_bucket = rate * timeWindow.TotalSeconds;

        Thread t = new Thread(() =>
        {
            Stopwatch timer = new Stopwatch();
            timer.Restart();


            while(true)
            {
                timer.Restart();
                SpinWait.SpinUntil(() => { return timer.ElapsedMilliseconds >= _interval; });


                double step = this._rate_limit * _interval / 1000;
                double buffer = 0;

                lock (this._syncroot)
                {
                    if (this._current_bucket > 0)
                    {
                        buffer += Math.Min(step, this._current_bucket);
                        this._current_bucket -= buffer;
                        while (this._queue.Count > 0)
                        {
                            var i = this._queue.Peek();
                            if (i.amount > buffer) break;
                            this._queue.Dequeue();
                            buffer -= i.amount;
                            //i.exec?.Invoke();
                            Task.Run(i.exec);
                        }
                    }
                }
            }
        });
        t.Start();
    }


    public override bool ProcessRequest(int amount, Action exec = null)
    {
        lock (this._syncroot)
        {
            if (this._current_bucket + amount > this._max_bucket) return false;

            this._current_bucket += amount;
            this._queue.Enqueue((amount, exec));
            return true;
        }
    }
}

執行結果

解釋 code 之前,先來看看執行結果。我用一樣的兩組設定來觀察…

rate: 500 rps, time window: 5 sec

rate: 500 rps, time window: 1 sec

小結

實際上,Leaky Bucket 的原理,就是中間設置一個 queue 當做緩衝,只是這個 queue 的大小是有限的 (就是桶子的大小)。能夠進的了 queue, 你的 request 就能保證在一定時間內能被處理 (time window)。由於處理 request 的部分被 queue 隔離保護得很好,因此幾乎可以用完美穩定的速率來處理 request。

其實這就是常見的 queue + worker 處理模式啊! 生產者跟消費者之間的平衡機制就是這個做法的精神。這個演算法的圖表,有很多有趣的細節,我逐一一個一個拿出來討論。我們以 5 sec time window 這張圖為例:

  • Executed Requests (黃線): 這個做法最大的優點,就是實際執行的速率是完美的水平線。中間的波動只是某個 request 只是統計的時間差而已,在 1 sec 統計的邊界,有些 request 被算在後面的區間而已。

  • Success Requests (橘線): 由於這個作法是有 buffer (queue) 的,因此它有能力受理一定程度內的 peek… 我們可以看到超過服務量的 request, 只要桶子還有空間,它都會被吸收進去,等後面慢慢消化。只有服務量超過的部分已經讓桶子都滿了裝不下,才會被丟棄,所以我們可以看到橘色的線在 peek 剛發生的時候都還跟的上,直到桶子滿了為止。

  • AverageExecuteTime (下圖藍線): 這個做法的缺點是,由於有桶子做緩衝,因此某些 request 可能會延遲一段時間才會流出桶子。這時 time window 就等同於最大的等待時間。我們可以看到 time window: 5 sec 那張圖,averaget execute time 爬到 5000 msec 就停止了 (因為再之後的 request 都灌不進來了)。另一張圖 time window: 1 sec, 也一樣,average execute time 爬到 1000 msec 就停止不再成長了。

這是這個演算法的優點,也是缺點。你可以按照你的情況做調整。用 time window 可以更具體化的告訴你的客戶 (產生 request 的那端) 你的服務水準 (最大等待時間) 有多長。過去如果你只是用 queue, 你只能告訴客戶 “此 request 已收到,等待處理中”,而你沒有辦法預估一個精準的處理時間。搭配 Leaky Bucket 你就能做到這點。

另一個好處是,這也是一個你 scale out 的評估標準。假設 queue 後面的 worker 每個能提供 100 RPS 的處理能力,後面有五個 worker 就能提供 100 x 5 = 500 RPS, 也就是這個範例設定的條件。當你觀察到 fail requests 的數量增加時,就可以擴充 worker 數量。當 worker 個數從 5 -> 6, time window 不變,改變的是你的處理速度,從 500 -> 600。這一切都能夠更直覺得量化,也更容易被度量,負責為運系統的 operation team 也能更直覺得按照需求條配系統組態。

唯一要留意的是,這些需求如果不是非同步的處理 (async), 這些 idle 的 request 可能都會佔住 http connection. time window 設的過大,有可能導致前端的 connection 被用光了,造成前端的服務垮掉的現象。

解法 3, Token Bucket

漏桶演算法,是控制桶子流出來的速率。另一種相對的演算法: 令牌桶 (Token Bucket) 則是剛好反過來,用一定速率把令牌放到桶子內,直到桶子滿了為止。當你接到 request, 只要你能從桶子裡拿到令牌,你的 request 就能被受理執行。

The token bucket algorithm is based on an analogy of a fixed capacity bucket into which tokens, normally representing a unit of bytes or a single packet of predetermined size, are added at a fixed rate. When a packet is to be checked for conformance to the defined limits, the bucket is inspected to see if it contains sufficient tokens at that time. If so, the appropriate number of tokens, e.g. equivalent to the length of the packet in bytes, are removed (“cashed in”), and the packet is passed, e.g., for transmission. The packet does not conform if there are insufficient tokens in the bucket, and the contents of the bucket are not changed. Non-conformant packets can be treated in various ways:

  • They may be dropped.

  • They may be enqueued for subsequent transmission when sufficient tokens have accumulated in the bucket.

  • They may be transmitted, but marked as being non-conformant, possibly to be dropped subsequently if the network is overloaded.

A conforming flow can thus contain traffic with an average rate up to the rate at which tokens are added to the bucket, and have a burstiness determined by the depth of the bucket. This burstiness may be expressed in terms of either a jitter tolerance, i.e. how much sooner a packet might conform (e.g. arrive or be transmitted) than would be expected from the limit on the average rate, or a burst tolerance or maximum burst size, i.e. how much more than the average level of traffic might conform in some finite period.

我自己也實作了一個簡單的版本: TokenBucketThrottle:

public class TokenBucketThrottle : ThrottleBase
{
    private double _max_bucket = 0;
    private double _current_bucket = 0;
    private object _syncroot = new object();
    private int _interval = 100;

    public TokenBucketThrottle(double rate, TimeSpan timeWindow) : base(rate)
    {
        this._max_bucket = this._rate_limit * timeWindow.TotalSeconds;

        Thread t = new Thread(() =>
        {
            //int _interval = 100;
            Stopwatch timer = new Stopwatch();
            timer.Restart();


            while (true)
            {
                timer.Restart();
                SpinWait.SpinUntil(() => { return timer.ElapsedMilliseconds >= _interval; });


                double step = this._rate_limit * _interval / 1000;
                lock (this._syncroot)
                {
                    this._current_bucket = Math.Min(this._max_bucket, this._current_bucket + step);
                }
            }
        });
        t.Start();
    }

    public override bool ProcessRequest(int amount, Action exec = null)
    {
        lock (this._syncroot)
        {
            if (this._current_bucket > amount)
            {
                this._current_bucket -= amount;
                //exec?.Invoke();
                Task.Run(exec);
                return true;
            }
        }
        return false;
    }
}

執行結果

不多說,一樣先來看看兩種組態跑出來的成果:

rate: 500 rps, time window: 5 sec

rate: 500 rps, time window: 1 sec

小結

Leaky Bucket 跟 Token Bucket 只是分別管控 buffer 的前端 (來源) 跟後端 (處理) 的速率而已啊! 因此大致上兩者的 Success Requests 的曲線是差不多的,在 peek 發生的瞬間,都還有一定的 “吸收” 能力。差別在於 Leaky 會保護後端,吸收下來的 peek 會延遲執行,而 token 則是先預留處理能力 (把 token 放進 bucket), 有保留資源的才能執行。

相較於 Leaky Bucket 能夠保證最大的 idle time, Token Bucket 則是保證後端有一定的 peek 吸收能力的前題下,盡可能地接受 request 並且立刻處理它, 一旦 request 被接受了,不需要任何等待的時間就能開始進到處理程序階段。只是當系統剛消化完一部分 peek 之後,Token Bucket 需要等一段時間 (time window) 讓後端恢復足夠的處理能力 (讓桶子裡放滿令牌) 後才能繼續吸收下一次的 peek…

整體而言,Token Bucket 較 Leaky Bucket 來的有效,Leaky Bucket 不論後端的設備有無餘力,都只能用一定的速率消化 request, 對於較健全的系統來說服務的利用率較低,因為任何的 Peek 都會被擋下來。而 Token Bucket 則能夠有限度地允許後端設備接受 peek , 就好像戰鬥機有後燃器一樣,能夠瞬間加大推力,只要你有限度的使用它就不會有問題。這可以讓你更有效率地去適應外界的 request 變化。只是你必須事先掌握你的 token 填充速率以及 time window 的大小,找出適合你系統的組態才能達到最佳效率。

總結

終於寫到結論了 @@, 寫到這邊,我只能說… 服務量控制真的是很煩人的一件事啊! 之前有篇文章,就是要寫程式控制 CPU 的使用率,想辦法讓 CPU 的 utilization 劃出正弦波… 為了修飾波形,想辦法消除雜訊,就花掉不少心思… 有興趣的朋友們可以去體會一下:

花了這麼多篇幅 (最近文章的長度越來越誇張了 @@),總算把服務量 (流量) 控制的做法交代完畢,告一段落。我會花這些篇幅說明這些演算法,是有原因的。我看過很多說明斷路器的文章,清一色都是說後端服務開始 “不正常” 之後就要啟動了… 不過這時才啟動應變措施,代表也已經有些災難發生了 (只是還沒擴大而已)。而我的想法是,如果我能夠更精準的掌握服務量 (流量) 這件事的話,也許我就有機會在快要發生災難時 (還沒發生) 就開始做好準備了。除了可以啟動斷路器之外,我也可以有更充裕的時間做 scale out, 或是優先調配資源,優先讓重要服務 (如交易) 通過。這些進階的控制技巧,都不是直接拿現成的 framework, 或是套用現成的 infra (如 load balancer, 或是 api gateway … 等等) 可以解決的。

我期望帶給讀者們的是個高度整合的 solution, 我不想綁定在特定的 infra / service 上,而是想帶給大家一個處理這類問題的 concept… 不說別的,光是能夠在 client side 透過你的 code 靈活操作 service discovery, throttle, circuit breaker, key-value store (configuration management), health check … 等機制,你就能用很少量的 code 做到很高的靈活程度。

舉個例子 QoS (quality of servics), 若你想讓特定客戶可以享用較高等級的服務, 但是這些背後的服務都是同樣一份 code.. 該怎麼做? 很簡單,你只要把這些 service instance 在註冊時先貼上標籤,標記那些是 VIP only, service discovery 時就能夠依照客戶身分別找到特定的 service group 了。不同 service group 你可以用不同的限流 (throttle) 標準來控制服務水準,讓你的 SLA 也是可控的。

開始能體會這些技巧帶來的效益了嗎? 掌握這些知識,才是微服務架構成功的途徑啊! 後續的文章我都會繼續用這角度介紹,實作的 labs 也會把這些應用的情境用實際的 code 表現出來。敬請期待, 也感謝各位長期支持 :)

出處:

先來解釋一下我怎麼測試,程式碼有點長,我分段說明,有需要看完整程式碼的可以直接參考我的 …

img
img
img
img

還記得之前那篇 嗎? 這篇探討的就是如何讓這種時間區段能夠 “平滑” 的統計? 不是 0 ~ 5 sec, 6 ~ 10 sec 這樣,而是 5 sec 那瞬間可以統計 0 ~ 5 sec 之間的資料,而 5.1 sec 則可以統計 0.1 sec ~ 5.1 sec 之間的資料。這個版本就是拿那篇文章講到的 sample code (我就不再拿分散是版本了,我直接拿單機版本 InMemoryEngine 來使用)。這種方法改善了 CounterThrottle 不夠 “平滑” 的問題,我們可以更精準的統計長時間的累計資料。

img
img

在 wiki 上也有詳細的介紹:

img
img

同樣的,可以看看 Wiki 上的介紹:

img
img
img

防雪崩利器:熔斷器 Hystrix 的原理與使用
github
架構面試題 #2, 連續資料的統計方式
Leaky bucket
Token bucket
Microsoft 面試考題: 用 CPU utilization 畫出正弦波
Part #2