參考
書籍:《ASP.NET Core 微服務實戰》
微軟微服務框架 dapr V1.0版本 2021-02-17 正式發布
百度百科說明: 微服務
知乎上:什么是微服務架構
微軟文檔:.NET 微服務 - 體系結構電子書 項目示例: eShopOnContainers GitHub
ABP微服務示例 --模塊化 + ddd + 微服務 + 容器化
Blog.Core官網 -- 前后端分離(.net core + vue) + 微服務 + 容器化 + CI/CD
surging: surging作者博客 surging git地址 -- dotnetty + 微服務 + websocket-sharp + rpc + Mqtt
分布式與集群
分布式
參考:分布式系統--百度百科
分布式就是把一個系統拆分成多個服務節點,每個節點部署在不同的服務器上,可以理解為把一個事情分為多個簡單的步驟。
集群
集群就把一個服務復制部署在多台電腦上,多台電腦同時執行同一個服務節點的功能,可以理解為多個一起做同一個步驟。
集群一般需要通過分布式技術來保證數據一致和同步等問題,例如分布式事務、分布式緩存等
對稱集群與非對稱集群:
- 對稱集群: 集群實例角色地位相同 ,特點:數據計算
- 非對稱集群 :集群實例角色地位不相同 ,特點:數據存儲,redis集群是非對稱集群
微服務中先分布式后集群
先分布式:例如12306,會分成登錄、查票、訂單、支付等多個服務。
后集群:根據請求訪問量多的服務弄成集群模式,例如12306中的查票服務。
微服務是什么
微服務是一個支持特定業務場景的獨立部署單元。它借助語義化版本管理、定義良好的 API 與其他后端服務交互。它的天然特點就是嚴格遵守單一職責原則。
包含以下特點
- 每個微服務獨立完整性:,雖然每個微服務會有重復部分,但是不能提取出來放到公共服務中,因為要保證每個微服務都有獨立性完整的功能,以便於橫向集群擴展
- 公共服務:包括注冊、通信、認證、限流、負載均衡、熔斷、日志等,這些公共服務有變化時不會影響到單個微服務
架構四要素:
- 問題:確定問題,怎么做
- 問題邊界 (約束 ):誰的問題,給出約束
- 生命周期:
- 拆分:根據問題的生命周期拆分
設計模式
轉載自:六種微服務架構的設計模式
聚合器(常用)
這是一種最常用也最簡單的設計模式,如下圖所示:
聚合器調用多個服務實現應用程序所需的功能。它可以是一個簡單的Web頁面,將檢索到的數據進行處理展示。它也可以是一個更高層次的組合微服務,對檢索到的數據增加業務邏輯后進一步發布成一個新的微服務,這符合DRY原則。另外,每個服務都有自己的緩存和數據庫。如果聚合器是一個組合服務,那么它也有自己的緩存和數據庫。聚合器可以沿X軸和Z軸獨立擴展。
異步消息傳遞(常用)
備注:abp的微服務demo就是使用此模式
雖然REST設計模式非常流行,但它是同步的,會造成阻塞。因此部分基於微服務的架構可能會選擇使用消息隊列代替REST請求/響應,如下圖所示:
數據共享
自治是微服務的設計原則之一,就是說微服務是全棧式服務。但在重構現有的“單體應用(monolithic application)”時,SQL數據庫反規范化可能會導致數據重復和不一致。因此,在單體應用到微服務架構的過渡階段,可以使用這種設計模式,如下圖所示:
在這種情況下,部分微服務可能會共享緩存和數據庫存儲。不過,這只有在兩個服務之間存在強耦合關系時才可以。對於基於微服務的新建應用程序而言,這是一種反模式。
代理
這是聚合器模式的一個變種,如下圖所示:
在這種情況下,客戶端並不聚合數據,但會根據業務需求的差別調用不同的微服務。代理可以僅僅委派請求,也可以進行數據轉換工作。
鏈式
這種模式在接收到請求后會產生一個經過合並的響應,如下圖所示:
在這種情況下,服務A接收到請求后會與服務B進行通信,類似地,服務B會同服務C進行通信。所有服務都使用同步消息傳遞。在整個鏈式調用完成之前,客戶端會一直阻塞。因此,服務調用鏈不宜過長,以免客戶端長時間等待。
分支
這種模式是聚合器模式的擴展,允許同時調用兩個微服務鏈,如下圖所示:
微服務通訊方式
一般是:外部通信使用http,內部通信使用grpc
Http通信
IHttpClientFactory:組件的工廠抽象,該組件可使用自定義配置為給定邏輯名稱創建 HttpClient 實例
IHttpClientFactory.CreateClient(String):使用與 name
指定的邏輯名稱相對應的配置來創建和配置 HttpClient 實例。
HttpClient:提供基本類,用於發送 HTTP 請求和接收來自通過 URI 確認的資源的 HTTP 響應。
HttpClient.PostAsync(string requestUri, HttpContent content):以異步操作將 POST 請求發送給指定 URI。
HttpContent:表示 HTTP 實體正文和內容標頭的基類。
StringContent(String, Encoding, String):創建 System.Net.Http.StringContent 類的新實例
HttpResponseMessage:表示包括狀態代碼和數據的 HTTP 響應消息。
在 ASP.NET Core 中使用 IHttpClientFactory 發出 HTTP 請求
.net core HttpClient 使用之掉坑解析(一)
核心的4行代碼
private readonly IHttpClientFactory httpClientFactory; // HttpClient httpClient = httpClientFactory.CreateClient(String); // HttpContent hc = new StringContent(String, Encoding, String); // HttpResponseMessage response = await httpClient.PostAsync(string requestUri, HttpContent content);
封裝好的注冊通信客戶端類 ConsulHttpClient 的完整代碼如下:

using Newtonsoft.Json; using RuanMou.MicroService.Core.Cluster; using RuanMou.MicroService.Core.Registry; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; namespace RuanMou.MicroService.Core.HttpClientConsul { /// <summary> /// consul httpclient擴展 /// </summary> public class ConsulHttpClient { private readonly IServiceDiscovery serviceDiscovery; private readonly ILoadBalance loadBalance; private readonly IHttpClientFactory httpClientFactory; public ConsulHttpClient(IServiceDiscovery serviceDiscovery, ILoadBalance loadBalance, IHttpClientFactory httpClientFactory) { this.serviceDiscovery = serviceDiscovery; this.loadBalance = loadBalance; this.httpClientFactory = httpClientFactory; } /// <summary> /// Get方法 /// </summary> /// <typeparam name="T"></typeparam> /// param name="ServiceSchme">服務名稱:(http/https)</param> /// <param name="ServiceName">服務名稱</param> /// <param name="serviceLink">服務路徑</param> /// <returns></returns> public async Task<T> GetAsync<T>(string Serviceshcme, string ServiceName,string serviceLink) { // 1、獲取服務 IList<ServiceUrl> serviceUrls = await serviceDiscovery.Discovery(ServiceName); // 2、負載均衡服務 ServiceUrl serviceUrl = loadBalance.Select(serviceUrls); // 3、建立請求 Console.WriteLine($"請求路徑:{Serviceshcme} +'://'+{serviceUrl.Url} + {serviceLink}"); HttpClient httpClient = httpClientFactory.CreateClient("mrico"); // HttpResponseMessage response = await httpClient.GetAsync(serviceUrl.Url + serviceLink); HttpResponseMessage response = await httpClient.GetAsync(Serviceshcme +"://"+serviceUrl.Url + serviceLink); // 3.1 json轉換成對象 if (response.StatusCode == HttpStatusCode.OK) { string json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<T>(json); } else { // 3.2 進行自定義異常處理,這個地方進行了降級處理 throw new Exception($"{ServiceName}服務調用錯誤:{response.Content.ReadAsStringAsync()}"); } } /// <summary> /// Post方法 /// </summary> /// <typeparam name="T"></typeparam> /// param name="ServiceSchme">服務名稱:(http/https)</param> /// <param name="ServiceName">服務名稱</param> /// <param name="serviceLink">服務路徑</param> /// <param name="paramData">服務參數</param> /// <returns></returns> public T Post<T>(string Serviceshcme, string ServiceName, string serviceLink, object paramData = null) { // 1、獲取服務 IList<ServiceUrl> serviceUrls = serviceDiscovery.Discovery(ServiceName).Result; // 2、負載均衡服務 ServiceUrl serviceUrl = loadBalance.Select(serviceUrls); // 3、建立請求 Console.WriteLine($"請求路徑:{Serviceshcme} +'://'+{serviceUrl.Url} + {serviceLink}"); HttpClient httpClient = httpClientFactory.CreateClient("mrico"); // 3.1 轉換成json內容 HttpContent hc = new StringContent(JsonConvert.SerializeObject(paramData), Encoding.UTF8, "application/json"); // HttpResponseMessage response = await httpClient.GetAsync(serviceUrl.Url + serviceLink); HttpResponseMessage response = httpClient.PostAsync(Serviceshcme + "://" + serviceUrl.Url + serviceLink, hc).Result; // 3.1json轉換成對象 if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created) { string json = response.Content.ReadAsStringAsync().Result; return JsonConvert.DeserializeObject<T>(json); } else { // 3.2、進行自定義異常處理,這個地方進行了降級處理 throw new Exception($"{ServiceName}服務調用錯誤:{response.Content.ReadAsStringAsync()}"); } } } }
使用HttpContext來進行微服務通信
httpcontext:封裝有關單個HTTP請求的所有特定於HTTP的信息
里面有request、response、Connection 等
ASP.NET Core管道詳解[2]: HttpContext本質論
ABP中的動態C# API客戶端
要查看源碼看下是使用什么通信的
RPC框架通信:rpc、grpc
參考:
ASP.NET Core 使用 gRPC 初探 --老張的哲學
RPC(Remote Procedure Call):遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的思想。
gRPC:
gRpc是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。
gRPC 基於 HTTP/2 標准設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多復用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間占用。
異步消息通信
一般是 RabbitMQ 、Kafka
分層法
以文件分層法為主,以程序集分層法為次
架構說明:
- 網關層:路由、限流、日志、監控、負載均衡、緩存等功能
- 負載均衡:代理多個網關,做網關集群
- oclet:
- 熔斷降級:
- 網關集群:
- 認證授權層 IdentityServer4:
- 聚合層:為頁面服務,處理頁面請求及給頁面提供數據。
- 服務層:各種微服務,根據功能來拆分,例如訂單服務、商品服務、支付服務、認證服務等
- 工具基礎層:服務發現、熔斷降級、日志、分布式事務、配置中心
- 服務發現、注冊、配置 consul:客戶端發現模式、服務端發現模式、服務注冊表
- 熔斷降級 Polly:
- 熔斷:作用就是在特定的場景下關掉當前的通路,從而起到保護整個系統的效果
- 降級:保證系統核心服務正常運行,暫停非核心的一些外圍服務
- 分布式事務 Saga:保證同時操作多個服務時保持數據原子性
- 原子性:如生成訂單時要同時扣減庫存,而訂單和商品庫存是兩個服務,使用分布式事務來保證兩個操作同時成功或同時失敗,避免數據不一致。
- 消息隊列 CAP.RabbtMq:
- 鏈路監控 SkyWalking:
- 日志中心 Elasticsearch:
- 數據庫:SQLServer、Mysql
- 緩存
- 內存緩存:MemoryCache
- 分布式緩存:Redis
- 對象映射器 AutoMapper
- Web框架 AspNetCore
- ORM EntityFrameworkCore
- 通信 HTTP:
微服務架構圖:
層次調用說明
先后順序:【網關層】(先Nginx后到網關)=》【聚合層】=》【微服務層】=》【數據層】
【核心層 / 基礎設施層】:沒有先后先后順序,因為其他服務都有都有項目引用 核心層,相當於它們的項目內都內已經包含了工具層,要用的時候直接調用就好
項目結構說明:
項目結構
- AggregateService(聚合服務)
- LocationService(位置服務)
- MemberService(成員服務)
- MicroService.Core(核心層 / 基礎設施層):只有它是類庫,其他服務都是控制台應用程序
- MicroService.Gateway(網關服務)
- MicroService.IdentityServer4(認證、授權服務)
- MicroService.MVCClient(MVC客戶端)
- MicroService.VideoService(視頻服務)
- TeamService(團隊服務)
項目依賴
AggregateService 、TeamService、MicroService.VideoService都是依賴MicroService.Core
項目依賴與集群問題
主要區分清楚項目依賴和微服務之間的通訊就好:
- MicroService.Core是類庫,項目依賴是類庫依賴,發布時會直接打包了依賴項目的相關文件
- 微服務之間通訊是通過http或grpc的方式進行服務之間的通訊,相互之間沒有依賴
項目依賴的時候,在發布是會自動把依賴的項目打包進來,包括.dll、.exe、.pdb等文件,如下圖:
AggregateService(聚合服務)結構說明
TeamService(團隊服務)結構說明
- Controllers:控制器層 / 接口層(resful api)
- 依賴Server層:構造函數注入時依賴,控制器的方法調用服務層server的方法
- 依賴appsettings文件的ConsulRegistry配置:構造函數時依賴,在Starup類的Configure方法中有注入Consul相關配置
- 依賴Models層
- Models:領域模型層
- 數據庫模型:數據庫根據它生產對應的表的字段、關系等
- 視圖模型 / 領域模型:提供前端使用到的視圖模型 / 領域模型
- Services:領域服務層(數據交互,包含業務邏輯)
- 依賴倉儲層:構造函數注入時依賴
- 增、刪、改、查方法,方法內只有調用倉儲層的方法
- 為什么要服務層:
- 重用:把不同服務的調用都在server層中完成
- 擴展:如果控制器直接調用倉儲,有修改時可能會修改很多控制器很麻煩,有服務層把相同功能放在一起,修改也小
- Repositories:領域倉儲層
- 增、刪、改、查的方法,方法內有具體實現,沒有業務邏輯
- 依賴數據庫上下文:構造函數注入時依賴
- 方便更換數據庫
分布式事務omega
備注:omega文件夾下的4個類庫不需要手工創建,下載源碼中包含有,在項目中添加一個文件夾然后添加現有項目,然后在微服務層引用
- Servicecomb.Saga.Omega.Abstractions:抽象層
- Servicecomb.Saga.Omega.AspNetCore:AspNetCore層
- Servicecomb.Saga.Omega.Core:核心層
- Servicecomb.Saga.Omega.Protocol:協議層
測試Test
- TeamService.Tests:團隊服務測試
微服務之間通信
- 每個服務器啟用時用Core層的UseConsulRegistry把自己服務根據服務名、地址注冊到consul中
- 在Core層中的ConsulHttpClient類內已經把HttpClient、IHttpClientFactory、HttpResponseMessage
- 在聚合服務調用其他服務時,根據地址去調用,例如調用團隊服務器時是通過HttpTeamServiceClient中團隊地址是:https/TeamService//Teams
微服務如何拆分:
架構演進
- 單體架構
- 單數據庫多應用架構
- 主從數據庫讀寫分離架構
- 主從數據庫讀寫分離+緩存架構
- 消息隊列架構
- 面向服務(SOA)架構
- 微服務架構
dapr