写点什么

搭建一套 ASP.NET Core+Nacos+Spring Cloud Gateway 项目

用户头像
yi念之间
关注
发布于: 2020 年 11 月 10 日

前言


    伴随着随着微服务概念的不断盛行,与之对应的各种解决方案也层出不穷。这毕竟是一个信息大爆发的时代,各种编程语言大行其道,各有各的优势。但是有一点未曾改变,那就是他们服务的方式,工作的时候各司其职,但是需要提供服务的时候必须要高度统一,这也是微服务的概念之一。日常的工作学习中,我个人更喜欢通用的解决方案,特别是能将不同编程语言亦或者不同编程框架整合到一起的那种,这种解决方案拉近了编程语言之间的距离,让开发者能更清楚的意识到编程语言只是工具,解决问题才是王道。好了口遁到此结束,接下来我就搭建一套.Net 体系结合 Java 体系的项目架构。


概念介绍

接下来我们用到的技术栈名词主要涉及到 ASP.NET Core、Nacos、Spring Cloud Gateway,接下来我们分别介绍所使用的的三种框架。

Nacos

Nacos 是阿里巴巴开源的致力于服务发现、配置和管理微服务的框架。提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。一般用到的最多的就是当做配置中心和注册中心。

  • 中文官网地址:https://nacos.io/zh-cn/

  • 官方 GayHub GitHub 地址:https://github.com/alibaba/nacos

  • 下载地址:https://github.com/alibaba/nacos/releases下载运行 Nacos 之前别忘了安装 JDK,如何安装 JDK 请自行百度这里就不再详细介绍了。下载 Nacos 方式有两种。第一种是直接下载打包好的文件直接运行。第二种是下载源码自己编译,还需要安装 maven,相对于第一个稍微复杂一些,我选择的是第一种方式。


ASP.NET Core

ASP.NET Core 是微软开源跨平台的 Web 开发框架,这个作为.Net 开发者相信大家已经非常熟悉了,目前最新的正式版本是 3.1.5,也是我们本次搭建框架的重头戏,作为业务的真正执行者



Spring Cloud Gateway#

Spring Cloud Gateway 为 Spring 生态系统上的一个 API 网关组件,主要提供一种简单而有效的方式路由映射到指定的 API,并为他们提供安全性、监控和限流等等。最主要的是可以轻松集成已有的 Spring 各种全家桶,比如咱们这次使用的 Nacos,搭建使用起来非常方便。



开始搭建

上面大致介绍了相关概念,相信大家也有了大致的了解。口说无凭,直接开干。


运行 Nacos

运行启动 Nacos,在浏览器输入输入http://localhost:8848/nacos/#会展示出如下界面。

本次我们主要是用 Nacos 作为注册中心,所以我们只需要关注服务管理模块即可。


搭建 ASP.NET Core 项目

    ASP.NET Core 项目是我们业务接口的真正提供者,这里我搭建两个项目用于模拟订单系统和商品系统。用 Visual Studio 新建两个 Web 空项目,分别是 OrderApi 和 ProductApi。OrderApi 调用 ProductApi 属于内部之间调用,不走 Gateway。由于我们使用 Nacos 作为注册中心,所以我们在需要对接到 Nacos 上。Nacos 有一套 Open API 的接口对接方式(官方文档)[https://nacos.io/zh-cn/docs/open-api.html]有详细的介绍。自己写终究还是比较麻烦的,好在随着 NET Core 的日渐成熟,已经有大佬为我们实现了一套 sdk 基本上满足我们的使用非常的方便,GitHub 地址为https://github.com/catcherwong/nacos-sdk-csharp别忘了给大佬个 Star😄😄😄,将程序包分别引入 OrderApi 和 ProductApi


<PackageReference Include="nacos-sdk-csharp-unofficial.AspNetCore" Version="0.2.6" />
复制代码


在 appsettings.json 中配置,本次展示默认使用的 OrderApi 作为演示,ProductApi 配置方式一致,只需更换注册名称即可


"nacos": {    "ServerAddresses": [ "localhost:8848" ],//Nacos地址    "DefaultTimeOut": 15000,    "Namespace": "",    "ListenInterval": 1000,    "ServiceName": "orderservice" //注册到Nacos上的服务名  }
复制代码


Startup 中配置如下


public void ConfigureServices(IServiceCollection services){    //注册Nacos相关服务    services.AddNacosAspNetCore(Configuration);
services.AddScoped<NacosDiscoveryDelegatingHandler>(); services.AddHttpClient(ServiceName.ProductService,client=> { //ServiceName是我为了方便定义的常量类用于承载我们可以使用到的服务名称这里即productservice client.BaseAddress = new Uri($"http://{ServiceName.ProductService}"); }).AddHttpMessageHandler<NacosDiscoveryDelegatingHandler>();
services.AddControllers();}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加Nacos相关中间件 app.UseNacosAspNetCore();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });}
复制代码


上面我们提到过,OrderApi 调用 ProductApi 属于内部系统间调用,所以我引入了 HttpClientFactory。由于我们使用的是 Nacos 作为注册中心,所以我写了一个 NacosDiscoveryDelegatingHandler 配合 HttpClientFactory,能更优雅的使用注册中心,如果对这种实现方式不熟悉的话可以参考我之前的博文.NET Core HttpClientFactory+Consul实现服务发现实现原理完全一致,具体代码如下

public class NacosDiscoveryDelegatingHandler: DelegatingHandler{    private readonly INacosServerManager _serverManager;    private readonly ILogger<NacosDiscoveryDelegatingHandler> _logger;
public NacosDiscoveryDelegatingHandler(INacosServerManager serverManager,ILogger<NacosDiscoveryDelegatingHandler> logger) { _serverManager = serverManager; }
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var current = request.RequestUri; try { //通过nacos sdk获取注册中心服务地址,内置了随机负载均衡算法,所以只返回一条信息 var baseUrl = await _serverManager.GetServerAsync(current.Host); request.RequestUri = new Uri($"{baseUrl}{current.PathAndQuery}"); return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } catch (Exception e) { _logger?.LogDebug(e, "Exception during SendAsync()"); throw; } finally { request.RequestUri = current; } }}
复制代码


由于我们需要模拟订单接口,所以我新建了 OrderController,大致代码如下


[Route("orderapi/[controller]")]public class OrderController : ControllerBase{    private List<OrderDto> orderDtos = new List<OrderDto>();    private readonly IHttpClientFactory _clientFactory;
public OrderController(IHttpClientFactory clientFactory) { orderDtos.Add(new OrderDto { Id = 1,TotalMoney=222,Address="北京市",Addressee="me",From="淘宝",SendAddress="武汉" }); orderDtos.Add(new OrderDto { Id = 2, TotalMoney = 111, Address = "北京市", Addressee = "yi", From = "京东", SendAddress = "北京" }); orderDtos.Add(new OrderDto { Id = 3, TotalMoney = 333, Address = "北京市", Addressee = "yi念之间", From = "天猫", SendAddress = "杭州" });
_clientFactory = clientFactory; }
[HttpGet("get/{id}")] public OrderDto GetOrder(long id) { return orderDtos.FirstOrDefault(i => i.Id == id); }
[HttpGet("getdetails/{id}")] public async Task<OrderDto> GetOrderDetailsAsync(long id) { OrderDto orderDto = GetOrder(id); if (orderDto != null) { OrderDetailDto orderDetailDto = new OrderDetailDto { Id = orderDto.Id, TotalMoney = orderDto.TotalMoney, Address = orderDto.Address, Addressee = orderDto.Addressee, From = orderDto.From, SendAddress = orderDto.SendAddress };
//内部调用ProductApi,配合自定义的NacosDiscoveryDelegatingHandler可以更优雅的使用注册中心方式 var client = _clientFactory.CreateClient(ServiceName.ProductService); var response = await client.GetAsync($"/productapi/product/getall"); var result = await response.Content.ReadAsStringAsync();
orderDetailDto.Products = JsonConvert.DeserializeObject<List<OrderProductDto>>(result); return orderDetailDto; } return orderDto; }}
复制代码

ProductApi 提供新建 ProductController 用于模拟提供商品信息

[Route("productapi/[controller]")]public class ProductController : ControllerBase{    private List<ProductDto> productDtos = new List<ProductDto>();    public ProductController()    {        productDtos.Add(new ProductDto { Id = 1,Name="酒精",Price=22.5m });        productDtos.Add(new ProductDto { Id = 2, Name = "84消毒液", Price = 19.9m });        productDtos.Add(new ProductDto { Id = 3, Name = "医用口罩", Price = 55 });    }
[HttpGet("get/{id}")] public ProductDto Get(long id) { return productDtos.FirstOrDefault(i => i.Id == id); }
[HttpGet("getall")] public IEnumerable<ProductDto> GetAll() { return productDtos; }}
复制代码

分别启动 OrderApi 和 ProductApi,然后去 Nacos 上查看,展示如下,说明服务注册成功

我们每个服务只启动了一个实例,每个服务可以启动多个实例,实现高可用和负载均衡。到这里 ASP.NET Core 相关的代码我们已经搭建完成了,以上只是展示了大致的流程,具体的实现可以去下载Demo查看。


搭建 Spring Cloud Gateway

如何搭建 Spring Cloud Gateway 网上有很多教程,IDEA 搭建非常简单,基本上就是起个名字,我们本项目名称就叫 apigateway,然后一直点下一步,由于本示例后端的业务系统都是对接到 Nacos 上的,所以需要在 Gateway 引入 Nacos 相关包,在 pom.xml 引入 Nacos


<dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>    <version>2.2.1.RELEASE</version></dependency>
复制代码


然后再启动类上加上 @EnableDiscoveryClient 注解


@SpringBootApplication@EnableDiscoveryClientpublic class ApiGatewayApplication {    public static void main(String[] args) {        SpringApplication.run(ApiGatewayApplication.class, args);    }}
复制代码


在 application.yml 中添加服务名称和 nacos 相关配置


server:  #网关启动端口号  port: 8080spring:  application:    #网关服务名称,也就是注册到Nacos的名称    name: apigateway  #Nacos相关配置  cloud:    nacos:      discovery:        #Nacos服务地址        server-addr: localhost:8848      gateway:        discovery:          locator:            enabled: true            lower-case-service-id: true
复制代码


启动网关项目 apigateway,打开 Nacos 管理界面,刷新服务列表,如下图所示,说明网关项目注册成功


接下来我们要在网关配置转发相关内容,让 apigateway 可以转发请求到我们具体的 OrderApi 和 ProductApi。Spring Cloud Gateway 默认支持两种配置转发的方式,一种是基于编码的方式,另一种是通过配置的方式。我选用的是基于配置的方式,相对比较灵活一点。在 application.yml 中添加转发相关配置,如下

server:  port: 8080spring:  application:    name: apigateway  redis:    host: localhost    port: 6379    database: 0  cloud:    nacos:      discovery:        server-addr: localhost:8848      gateway:        discovery:          locator:            enabled: true            lower-case-service-id: true    #转发相关配置    gateway:      routes:        #唯一标识        - id: productservice          #服务在注册中心的地址          uri: lb://productservice          #转发匹配,即满足/productapi/相关的路径则转发到productservice相关服务          predicates:            - Path=/productapi/**        - id: orderservice          uri: lb://orderservice          predicates:            - Path=/orderapi/**
复制代码


到这里网关相关的配置差不多先配置这么多,当然网关还需要集成许多核心组件比如限流相关熔断超时相关等等,Spring Cloud Gateway 都是可以接入相关组件的比如阿里的 Sentinel 等等,在这里我们就不做演示了。


测试调用

可以下载本文演示Demo启动 Postman 进行测试,通过调用网关项目 apigateway 看看运行效果,首先调用 OrderApi 接口,OderDetails 接口内部调用了 ProductApi 的接口。如下所示转发成功

然后我们在去调用 ProductApi 的相关接口,如图所示也是成功的


本文演示Demo下载


总结

    到这里我们的相关示例也就差不多了,能有一套公共的解决方案,使用起来还是非常方便的。这里我只是演示了非常基础的一种模式,就是为了展示技术通用性给我们带来的便利。我个人还是非常喜欢通用的解决方案的,这些方案能让我更关注问题本身,而非某种特定的语言。比如现在容器技术大行其道,我们其实可以忽略原有的许多技术细节,而通过容器平台本身的通用解决方案去解决,在我们使用的时候会非常方便。我也希望更多的开发者,能够关注技术本身或者解决方案本身带给我们的便利,而不是通过有色的眼光去看待这些。能解决的方案终究还是好的方案,它能指导我们的思想,提升我的思维方式,那我们为什么不去接触去学习呢?语言本身固然重要,但是解决问题的思维方式更是不可或缺。废话不多说,本次就到这里,欢迎大家评论区批评指导。

发布于: 2020 年 11 月 10 日阅读数: 52
用户头像

yi念之间

关注

星光不问赶路人,时光不负有心人。 2018.08.22 加入

普通程序员,主攻.net core方向,顺便学习Java和Python。喜欢架构设计,励志成为一名真正的架构师,喜欢研究新技术,喜欢阅读源码。

评论

发布
暂无评论
搭建一套ASP.NET Core+Nacos+Spring Cloud Gateway项目