这篇文章原本是继续分享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