這篇文章原本是繼續分享IdentityServer4
的相關文章,因爲以前有博友問我關於微服務
相關的問題,我就先跳過IdentityServer4
的分享,進行微服務
相關的技術學習和分享。微服務
在個人分享目錄裏面是放到四月份開始系列文章分享的,這裏就先穿越下,提早安排微服務
應用的開篇文章 電商系統升級之微服務架構的應用
。
本博客以及公衆號堅持以架構的思惟來分享技術,不只僅是單純的分享怎麼使用的Demo。html
先來回顧下我上篇文章 Asp.Net Core 中IdentityServer4 受權中心之應用實戰 中,電商架構由單體式架構
拆分升級到多網關架構
git
然而升級以後問題又來了,因爲以前增長了代理商業務而且把受權中心
和支付網關
單獨拆出來了,這使得公司的業務訂單量翻了幾十倍,這個時候整個電商系統達到了瓶頸,若是再不找解決方案系統又得宕機了。github
通過技術的調研及問題分析,致使這個瓶頸的問題主要有如下幾個緣由,只須要把下面問題解決就能夠獲得很大的性能提高算法
單表
、單數據庫
(未進行讀寫分離),以至於訂單數據持續暴增。爲了一勞永逸的解決以上問題,通過技術的調研,決定對訂單業務作以下升級改造:數據庫
ES
進行數據遷移(按年進行劃分,而且進行讀寫分離,這裏就不着重講,下次來跟你們一塊兒學習和分享)分佈式緩存
(也不是本次的重點,後續再來跟你們學習和分享)通過升級後的架構圖以下:
編程
架構圖說明:json
單體式架構
,爲拆分的單體架構業務,其中在業務處理上夾雜了一層分佈式緩存的處理微服務
)微服務
的相關概念我就很少說了,如下就先簡單概況下微服務帶來的利和弊。api
Docker
、Kubernets
、Jenkins
、Git
等。說到單體架構
拆分,那也不是隨意拆分,是要有必定的原則,拆分的好是優點,拆分的很差是混亂。如下是我查閱資料以及個人經驗總結出來的拆分原則緩存
領域驅動設計
去進行底層服務的設計,後續會單獨分析該設計的相關文章)好了,到這裏你們已經對微服務有了必定的理解,就不繼續詳細概述相關理念的東西,下面來直接擼代碼,讓你們熟悉微服務的應用。這裏我使用 莫堇蕈
在github 上開源的微服務框架,框架源代碼地址 :https://github.com/overtly/core-grpc (我這裏強烈推薦該框架,目前已經比較成熟的用於公司生產環境)服務器
爲了更好的維護開源項目以及技術交流,特地建立了一個交流羣,羣號:1083147206 有興趣者開源加入交流
core-grpc
微服務框架的優點:Jlion.NetCore.OrderService
訂單微服務咱們用vs2019
建立控制檯應用程序 選擇框架.Net Core 3.1 命名爲Jlion.NetCore.OrderService
後面簡稱訂單服務
,建立完後咱們經過nuget
包引入 core-grpc
微服務框架,以下圖:
目前core-grpc
微服務框架,最新正式發佈版本是 1.0.3
引用了core-grpc
後咱們還須要安裝一個工具VS RPC Menu
,這個工具也是大神免費提供的,圖片以下:
因爲微軟官方下載比較慢,我這裏共享到 百度網盤,百度網盤下載地址以下:
連接: https://pan.baidu.com/s/1twpmA4_aErrsg-m0ICmOPw 提取碼: cshs
若是經過下載後安裝不是vs 集成安裝方式,下載完成後須要關閉vs 2019相關才能正常安裝。
VS RPC Menu 工具說明以下:
訂單服務
項目 中建立OrderRequest.proto
文件,這個是Grpc
的語法,不瞭解該語法的同窗能夠 點擊 gRPC 官方文檔中文版_V1.0 進行學習,地址:http://doc.oschina.net/grpc?t=56831OrderRequest.proto
代碼以下:
syntax = "proto3"; package Jlion.NetCore.OrderService.Service.Grpc; //定義訂單查找參數實體 message OrderSearchRequest{ string OrderId = 1; //定義訂單ID string Name = 2; } //定義訂單實體 message OrderRepsonse{ string OrderId = 1; string Name = 2; double Amount = 3; int32 Count = 4; string Time = 5; } //定義訂單查找列表 message OrderSearchResponse{ bool Success = 1; string ErrorMsg = 2; repeated OrderRepsonse Data = 3; }
上面主要是定義了幾個消息實體,
咱們再建立JlionOrderService.proto
,代碼以下:
syntax = "proto3"; package Jlion.NetCore.OrderService.Service.Grpc; import "OrderRequest.proto"; service JlionOrderService{ rpc Order_Search(OrderSearchRequest) returns (OrderSearchResponse){} }
上面的代碼中均可以看到最上面有 package Jlion.NetCore.OrderService.Service.Grpc
代碼,這是聲明包名也就是後面生成代碼後的命名空間,這個很重要。
同時定義了JlionOrderService
服務入口,而且定義了一個訂單搜索的方法Order_Search
,到這裏咱們已經完成了一小部分了。
再在JlionOrderService.proto
文件裏面右鍵 》選擇Grpc代碼生成》Grpc 代碼 會自動生存微服務客戶端代碼 。
生存工具中具備以下功能:
Jlion.NetCore.OrderService.Grpc
類庫把剛剛經過工具生成的Grpc
客戶端代碼直接copy到 Jlion.NetCore.OrderService.Grpc
這個類庫中(必須和上面Grpc 的代碼聲明的package 一致)如下簡稱訂單服務客戶端
,而且須要經過Nuget
包添加Overt.Core.Grpc
的依賴,代碼結構以下:
Jlion.NetCore.OrderService.Grpc
類庫已經構建完成,如今讓 Jlion.NetCore.OrderService
服務引用Jlion.NetCore.OrderService.Grpc
類庫
訂單服務
中 實現本身的IHostedService
建立HostService
類,繼承IHostedService
代碼以下:
public class HostedService : IHostedService { readonly ILogger _logger; readonly JlionOrderServiceBase _grpcServImpl; public HostedService( ILogger<HostedService> logger, JlionOrderServiceBase grpcService) { _logger = logger; _grpcServImpl = grpcService; } //服務的啓動機相關配置 public Task StartAsync(CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { var channelOptions = new List<ChannelOption>() { new ChannelOption(ChannelOptions.MaxReceiveMessageLength, int.MaxValue), new ChannelOption(ChannelOptions.MaxSendMessageLength, int.MaxValue), }; GrpcServiceManager.Start(BindService(_grpcServImpl), channelOptions: channelOptions, whenException: (ex) => { _logger.LogError(ex, $"{typeof(HostedService).Namespace.Replace(".", "")}開啓失敗"); throw ex; }); System.Console.WriteLine("服務已經啓動"); _logger.LogInformation($"{nameof(Jlion.NetCore.OrderService.Service).Replace(".", "")}開啓成功"); }, cancellationToken); } //服務的中止 public Task StopAsync(CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { GrpcServiceManager.Stop(); _logger.LogInformation($"{typeof(HostedService).Namespace.Replace(".", "")}中止成功"); }, cancellationToken); } }
上面代碼主要是建立宿主機而且實現了StartAsync
服務啓動及StopAsync
服務中止方法。
咱們建立完HostedServicce
代碼再來建立以前定義的Grpc
服務的方法實現類JlionOrderServiceImpl
,代碼以下:
public partial class JlionOrderServiceImpl : JlionOrderServiceBase { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; public JlionOrderServiceImpl(ILogger<JlionOrderServiceImpl> logger, IServiceProvider provider) { _logger = logger; _serviceProvider = provider; } public override async Task<OrderSearchResponse> Order_Search(OrderSearchRequest request, ServerCallContext context) { //TODO 從底層ES中查找訂單數據, //能夠設計成DDD 方式來進行ES的操做,這裏我就爲了演示直接硬編碼了 var response = new OrderSearchResponse(); try { response.Data.Add(new OrderRepsonse() { Amount = 100.00, Count = 10, Name = "訂單名稱測試", OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"), Time = DateTime.Now.ToString() }); response.Data.Add(new OrderRepsonse() { Amount = 200.00, Count = 10, Name = "訂單名稱測試2", OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"), Time = DateTime.Now.ToString() }); response.Data.Add(new OrderRepsonse() { Amount = 300.00, Count = 10, Name = "訂單名稱測試2", OrderId = DateTime.Now.ToString("yyyyMMddHHmmss"), Time = DateTime.Now.ToString() }); response.Success = true; } catch (Exception ex) { response.ErrorMsg = ex.Message; _logger.LogWarning("異常"); } return response; } }
再修改Program
代碼,並把HostedService
和JlionOrderServiceImpl
注入到容器中,代碼以下:
class Program { static void Main(string[] args) { var host = new HostBuilder() .UseConsoleLifetime() //使用控制檯生命週期 .ConfigureAppConfiguration((context, configuration) => { configuration .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables(); }) .ConfigureLogging(logger => { logger.AddFilter("Microsoft", LogLevel.Critical) .AddFilter("System", LogLevel.Critical); }) .ConfigureServices(ConfigureServices) .Build(); AppDomain.CurrentDomain.UnhandledException += (sender, e) => { var logFactory = host.Services.GetService<ILoggerFactory>(); var logger = logFactory.CreateLogger<Program>(); logger.LogError(e.ExceptionObject as Exception, $"UnhandledException"); }; host.Run(); } /// <summary> /// 通用DI注入 /// </summary> /// <param name="context"></param> /// <param name="services"></param> private static void ConfigureServices(HostBuilderContext context, IServiceCollection services) { //HostedService 單例注入到DI 中 services.AddSingleton<IHostedService, HostedService>(); services.AddTransient<JlionOrderServiceBase, JlionOrderServiceImpl>(); } }
到了這裏簡單的微服務
已經編碼完成,可是還缺乏兩個配置文件,咱們建立appsettings.json
配置文件和consulsettings.json
服務註冊發現的配置文件
consulsettings.json
配置文件以下:
{ "ConsulServer": { "Service": { "Address": "127.0.0.1:8500"// 你的Consul 服務註冊及發現配置地址 } } }
上面的地址配置只是簡單的例子,我這裏假定個人Consul
服務地址是 127.0.0.1:8500 等下服務啓動是會經過這個地址進行註冊。
appsettings.json
配置文件以下:
{ "GrpcServer": { "Service": { "Name": "JlionOrderService", "Port": 10001, "HostEnv": "serviceaddress", "Consul": { "Path": "dllconfigs/consulsettings.json" } } } }
我這裏服務監聽了10001 端口,後面註冊到Consul
中也會看到該端口
官方完整的配置文件以下:
{ "GrpcServer": { "Service": { "Name": "OvertGrpcServiceApp", // 服務名稱使用服務名稱去除點:OvertGrpcServiceApp "Host": "service.g.lan", // 專用註冊的域名 (可選)格式:ip[:port=default] "HostEnv": "serviceaddress", // 獲取註冊地址的環境變量名字(可選,優先)環境變量值格式:ip[:port=default] "Port": 10001, // 端口自定義 "Consul": { "Path": "dllconfigs/consulsettings.json" // Consul路徑 } } } }
好了,訂單服務
已經所有完成了,訂單服務
服務總體結構圖以下:
好了,咱們這裏經過命令行啓動下JlionOrderService
服務,生產環境大家能夠搭建在Docker
容器裏面
咱們能夠來看下我以前搭建好的Consul
服務 ,打開管理界面,如圖:
圖片中能夠發現剛剛啓動的服務已經註冊進去了,可是裏面有一個健康檢查未經過,主要是因爲服務端不能訪問我本地的訂單服務
,全部健康檢查不能經過。你能夠在你本地搭建 Consul
服務用於測試。
我本地再來開啓一個服務,配置中的的端口號由10001 改爲10002,再查看下Consul
的管理界面,以下圖:
發現已經註冊了兩個服務,端口號分別是10001 和10002,這樣能夠經過自定化工具自動添加服務及下架服務,分佈式服務也即完成。
到這裏訂單服務
的啓動已經徹底成功了,咱們接下來是須要客戶端也就是上面架構圖中的電商業務網關
或者支付網關
等等要跟訂單服務
進行通信了。
建立訂單網關以前我先把上面的 訂單服務客戶端
類庫發佈到個人nuget包上,這裏就不演示了。我發佈的測試包名稱JlionOrderServiceDemo
nuget官方能夠搜索找到。大家也能夠直接搜索添加到大家的Demo中進行測試。
我經過VS 2019 建立Asp.Net Core 3.1 框架的WebApi
取名爲Jlion.NetCore.OrderApiService
下面簡稱訂單網關服務
如今我把前面發佈的微服務
客戶端依賴包 JlionOrderServiceDemo
添加到訂單網關服務
中,以下圖:
如今在訂單網關服務
中添加OrderController
api控制器,代碼以下:
namespace Jlion.NetCore.OrderApiService.Controllers { [Route("[controller]")] [ApiController] public class OrderController : ControllerBase { private readonly IGrpcClient<OrderService.Service.Grpc.JlionOrderService.JlionOrderServiceClient> _orderService; public OrderController (IGrpcClient<OrderService.Service.Grpc.JlionOrderService.JlionOrderServiceClient> orderService) { _orderService = orderService; } [HttpGet("getlist")] public async Task<List<OrderRepsonse>> GetList() { var respData =await _orderService.Client.Order_SearchAsync(new OrderService.Service.Grpc.OrderSearchRequest() { Name = "test", OrderId = "", }); if ((respData?.Data?.Count ?? 0) <= 0) { return new List<OrderRepsonse>(); } return respData.Data.ToList(); } } }
代碼中經過構造函數注入 OrderService
而且提供了一個GetList
的接口方法。接下來咱們還須要把OrderService.Service.Grpc.JlionOrderService
注入到容器中,代碼以下:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); //註冊Grpc 客戶端,具體能夠查看源代碼 services.AddGrpcClient(); }
如今整個訂單網關服務
項目結構以下圖:
項目中有兩個最重要的配置dllconfig//Jlion.NetCore.OrderService.Grpc.dll.json
和consulsettings.json
他們分別是幹什麼的呢?咱們先分別來看我本地這兩個配置的內容
Jlion.NetCore.OrderService.Grpc.dll.json
配置以下:
{ "GrpcClient": { "Service": { "Name": "JlionOrderService", // 服務名稱與服務端保持一致 "MaxRetry": 0, // 最大可重試次數,默認不重試 "Discovery": { "Consul": { // Consul集羣,集羣優先原則 "Path": "dllconfigs/consulsettings.json" }, "EndPoints": [ // 單點模式 { "Host": "127.0.0.1", "Port": 10001 }] } } } }
Jlion.NetCore.OrderService.Grpc.dll.json
配置主要是告訴訂單網關服務
和訂單服務
應該怎樣進行通訊,以及通訊當中的一些參數配置。我爲了測試,本地使用單點模式,不使用Consul模式
consulsettings.json
配置以下:
{ "ConsulServer": { "Service": { "Address": "127.0.0.1:8500" } } }
有沒有發現這個配置和以前服務端的配置同樣,主要是告訴訂單網關服務
(客戶端調用者)和訂單服務
服務端服務發現的集羣地址,若是上面的配置是單點模式則這個配置不會起做用。
到這裏訂單網關服務
(客戶調用端)編碼完成,咱們開始啓動它:
我這裏固定5003端口,如今完美的啓動了,咱們訪問下訂單接口,看下是否成功。訪問結果以下圖:
微服務完美的運行成功。
上面的構建微服務仍是比較麻煩,官方提供了比較快速構建你須要的微服務方式,不須要寫上面的那些代碼,那些代碼所有經過模板的方式進行構建你的微服務,有須要學習的能夠到點擊
微服務項目構建模板使用教程
教程地址:http://www.javashuo.com/article/p-nvoqxwey-cg.html
文章中的Demo 代碼已經提交到github 上,代碼地址:https://github.com/a312586670/IdentityServerDemo
微服務框架開源項目地址:https://github.com/overtly/core-grpc