分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 技术分享

ASP.NET Core OceLot 微服务实践

发布时间:2023-09-06 02:12责任编辑:赖小花关键词:.NET

1、OceLot中间件介绍

  在传统的BS应用中,随着业务需求的快速发展变化,需求不断增长,迫切需要一种更加快速高效的软件交付方式。微服务可以弥补单体应用不足,是一种更加快速高效软件架构风格。单体应用被分解成多个更小的服务,每个服务有自己的独立模块,单独部署,然后共同组成一个应用程序。把范围限定到单个独立业务模块功能。分布式部署在各台服务器上。

  而Ocelot开发的目标就是使用.NET运行面向微服务/服务的架构,要达到这个目标需要一个统的系统入口点(我们统称为API网关),同时需要与IdentityServer或相关的验证服务进行集成。

  Ocelot同时是一系列按特定顺序排列的中间件。

  包括(IdentityServer,Consul,Butterfly)等等。

  Ocelot将HttpRequest对象操作到其配置指定的状态,直到它到达请求构建器中间件,在该中间件中它创建HttpRequestMessage对象,该对象用于向下游服务发出请求。发出请求的中间件是Ocelot管道中的最后一件事。它不会调用下一个中间件。下游服务的响应存储在每个请求范围的存储库中,并在请求返回Ocelot管道时进行检索。有一个中间件将HttpResponseMessage映射到HttpResponse对象并返回给客户端。

  以下是部署Ocelot时使用的结构图:

  与IdentityServer4的集成:

  与Consul的集成:

  从上面基本的介绍可以得出一个结论,OceLot微服务网关类似于经典设计模式的Fa?ade模式,它将底层的复杂细节进行屏蔽,对外提供简单而统一的调用方式,通常使用HTTP的RESTful API服务。此时,对于客户端而言,可以是PC端网页,也可以是移动设备,客户端通过HTTP方式调用OceLot网关。

2、整体设计

看了前面的基于OceLot微服务的基础介绍,发现OceLot已提供了足够强大的功能及扩展能力,而我们只需要进行集成,对就是集成(我们不生产水,我们只是大自然的搬运工),如下图:

如图,我们采用了Nginx作为入口的高可用及负载均衡,通过API网关作为具体二次转发接入的统一入口,先向IdentityService进行Login以进行验证并获取Token,在IdentityService的验证过程中会访问数据库以验证。接着再通过Consul服务器获取服务的所在服务器,最后再带上Token通过API网关去访问具体的服务A、B、C。这里我们的IdentityService基于IdentityServer4开发,它具有统一登录验证和授权的功能。

  而API网关的模块组件图大致如下图:

3、Nginx负载均衡

点击进入:https://www.cnblogs.com/littlewrong/p/9491901.html。

4、OceLot网关服务

4.1、路由

  Ocelot的主要功能是接管进入的http请求并把它们转发给下游服务。目前是以另一个http请求的形式(将来可能是任何传输机制)。

捕获所有

  Ocelot的路由还支持捕获所有样式的路由,用户可以指定他们想要匹配所有流量。如果你像下面那样设置你的配置,请求将被直接代理(它不一定叫url,任何占位符名称都可以)。

{

"DownstreamPathTemplate": "/{url}",

"DownstreamScheme": "https",

"DownstreamHostAndPorts": [

{

"Host": "localhost",

"Port": 80,

}

],

"UpstreamPathTemplate": "/{url}",

"UpstreamHttpMethod": [ "Get" ]

}

该捕获所有的优先级低于其他任何ReRoute。 如果你的配置中还有下面的ReRoute,那么Ocelot会在捕获所有配置之前先匹配它。

{
 "DownstreamPathTemplate": "/",
 "DownstreamScheme": "https",
 "DownstreamHostAndPorts": [
 {
 "Host": "10.0.10.1",
 "Port": 80,
 }
 ],
 "UpstreamPathTemplate": "/",
 "UpstreamHttpMethod": [ "Get" ]
}

上游主机

此功能允许您基于上游主机进行ReRoutes。 这是通过查看客户端使用的主机头来工作,然后将其用作识别ReRoute的信息的一部分。

为了使用这个功能,在你的配置中加上如下配置。

{
 "DownstreamPathTemplate": "/",
 "DownstreamScheme": "https",
 "DownstreamHostAndPorts": [
 {
 "Host": "10.0.10.1",
 "Port": 80,
 }
 ],
 "UpstreamPathTemplate": "/",
 "UpstreamHttpMethod": [ "Get" ],
 "UpstreamHost": "somedomain.com"
}

上面的ReRoute只会匹配主机头是somedomain.com的请求。

如果您没有在ReRoue上设置UpstreamHost,则任何主机头都可以匹配它。 这基本上是一个捕获所有功能并保留构建功能时的现有功能。这意味着如果您有两个相同的ReRoute,其中一个与UpstreamHost是null,另一个有值。 Ocelot会倾向于设定值的那个。

这个功能在问题 216提出要求。

优先级

{
 "Priority": 0
}

0是最低优先级,Ocelot将始终使用0作为/{catchAll}路由条目,并且可以硬编码。之后,你可以自由设置你想要的任何优先级。

例如你可以这样:

{
 "UpstreamPathTemplate": "/goods/{catchAll}",
 "Priority": 0
}

还可以:

{
 "UpstreamPathTemplate": "/goods/delete",
 "Priority": 1
}

在上面的例子中,如果您向Ocelot请求/goods/delete,Ocelot将匹配/goods/delete这个ReRoute。不过在不设置优先级以前它会匹配/goods/{catchAll}(因为这是列表中的第一个ReRoute!)。

以上摘自官方的介绍,地址为:https://ocelot.readthedocs.io/en/latest/features/routing.html

最终实现代码如下:

调试时,确认启动多个项目。

启动多个项目:

  进行路由测试:

通过PostMan测试发现,BookingAPI,及PassengerAPI已通过网关API成功访问。

配置文件解析

{
 "ReRoutes": [ //路由是API网关最基本也是最核心的功能、ReRoutes下就是由多个路由节点组成。
 {
 "DownstreamPathTemplate": "", //下游服务模板
 "UpstreamPathTemplate": "", //上游服务模板
 "UpstreamHttpMethod": [ "Get" ],//上游方法类型Get,Post,Put
 "AddHeadersToRequest": {},//需要在转发过程中添加到Header的内容
 "FileCacheOptions": { //可以对下游请求结果进行缓存,主要依赖于CacheManager实现
 "TtlSeconds": 10,
 "Region": ""
 },
 "ReRouteIsCaseSensitive": false,//重写路由是否区分大小写
 "ServiceName": "",//服务名称
 "DownstreamScheme": "http",//下游服务schema:http, https
 "DownstreamHostAndPorts": [ //下游服务端口号和地址
 {
 "Host": "localhost",
 "Port": 8001
 }
 ],
 "RateLimitOptions": { //限流设置
 "ClientWhitelist": [], //客户端白名单
 "EnableRateLimiting": true,//是否启用限流设置
 "Period": "1s", //每次请求时间间隔
 "PeriodTimespan": 15,//恢复的时间间隔
 "Limit": 1 //请求数量
 },
 "QoSOptions": { //服务质量与熔断,熔断的意思是停止将请求转发到下游服务。当下游服务已经出现故障的时候再请求也是无功而返,
 并且增加下游服务器和API网关的负担,这个功能是用的Polly来实现的,我们只需要为路由做一些简单配置即可
 "ExceptionsAllowedBeforeBreaking": 0, //允许多少个异常请求
 "DurationOfBreak": 0, //熔断的时间,单位为秒
 "TimeoutValue": 0 //如果下游请求的处理时间超过多少则自如将请求设置为超时
 }
 }
 ],
 "UseServiceDiscovery": false,//是否启用服务发现
 "Aggregates": [ //请求聚合
 {
 "ReRouteKeys": [ //设置需要聚合的路由key
 "booking",
 "passenger"
 ],
 "UpstreamPathTemplate": "/api/getbookingpassengerinfo" //暴露给外部的聚合请求路径
 },
 "GlobalConfiguration": { //全局配置节点
 "BaseUrl": "https://localhost:5000" //网关基地址
 }
}
{
 "DownstreamPathTemplate": "/api/posts/{postId}",
 "DownstreamScheme": "https",
 "DownstreamHostAndPorts": [
 {
 "Host": "10.0.1.10",
 "Port": 5000,
 },
 {
 "Host": "10.0.1.11",
 "Port": 5000,
 }
 ],
 "UpstreamPathTemplate": "/posts/{postId}",
 "LoadBalancer": "LeastConnection",
 "UpstreamHttpMethod": [ "Put", "Delete" ]
}

万能模板

如果不希望对请求做任何的处理,则可以使用下面的万能模板:(万能模板的优先级最低,只要有其它的路由模板,其它的路由模板则会优先生效)

{
 "DownstreamPathTemplate": "/{url}",
 "DownstreamScheme": "https",
 "DownstreamHostAndPorts": [
 {
 "Host": "localhost",
 "Port": 80,
 }
 ],
 "UpstreamPathTemplate": "/{url}",
 "UpstreamHttpMethod": [ "Get" ]
}

Prioirty优先级

对多个产生冲突的路由设置优化级

{
 "UpstreamPathTemplate": "/goods/{catchAll}"
 "Priority": 0
}
{
 "UpstreamPathTemplate": "/goods/delete"
 "Priority": 1
}

请求聚合

可以通过gateway将客户端的多个请求聚合然后将结果一次返回到客户端去,此时我们需要给每个模板指定一个key

{
 "ReRoutes": [
 {
 "DownstreamPathTemplate": "/api/booking",
 "UpstreamPathTemplate": "/api/getbooking",
 "UpstreamHttpMethod": [ "Get" ],
 "DownstreamScheme": "http",
 "DownstreamHostAndPorts": [
 {
 "Host": "localhost",
 "Port": 8001
 }
 ],
 "Key": "booking"
 },
 {
 "DownstreamPathTemplate": "/api/passenger",
 "UpstreamPathTemplate": "/api/getpassenger",
 "UpstreamHttpMethod": [ "Get" ],
 "ReRouteIsCaseSensitive": false,
 "DownstreamScheme": "http",
 "DownstreamHostAndPorts": [
 {
 "Host": "localhost",
 "Port": 8002
 }
 ],
 "Key": "passenger"
 }
 ],
 "GlobalConfiguration": {
 "BaseUrl": "https://localhost:5000"
 },
 "Aggregates": [
 {
 "ReRouteKeys": [
 "booking",
 "passenger"
 ],
 "UpstreamPathTemplate": "/api/getbookingpassengerinfo"
 }
 ]
}

需要注意的是:

  1. 聚合服务目前只支持返回json
  2. 目前只支持Get方式请求下游服务
  3. 任何下游的response header并会被丢弃
  4. 如果下游服务返回404,聚合服务只是这个key的value为空,它不会返回404

  有没有觉得这里的聚合很类似于GraphQL的功能,但实际上在Ocelot中并不打算实现GraphQL的功能,因为毕竟Ocelot的主要职责是实现网关的功能,聚合只是其中的一个feature,GraphQL提供了一个库 graphql-dotnet ,我们可以用它来完成需要的功能,而在Ocelot中实现类似认证,授权等这样它擅长的事情:

{
 "ReRoutes": [
 {
 "DownstreamPathTemplate": "/graphql",
 "DownstreamScheme": "http",
 "DownstreamHostAndPorts": [
 {
 "Host": "yourgraphqlhost.com",
 "Port": 80
 }
 ],
 "UpstreamPathTemplate": "/graphql",
 "DelegatingHandlers": [
 "GraphQlDelegatingHandler"
 ]
 }
 ]
 }

  在此感谢KenWang007 https://github.com/KenWang007/OcelotDemo 示例,查了很久的资料,最终通过上面的示例调试成功。

  最后,github 有个 https://github.com/dbarkwell/Ocelot.ConfigEditor,这个项目实现了asp.net core mvc 的在线编辑路由。

4.2、API安全认证(OceLot+IdentityServer4)

IdentityServer4的介绍博客园上已经有非常多了,

点击打开:https://www.cnblogs.com/jaycewu/p/7791102.html 可以看一下。

这里主要介强OceLot集成需要进行改动的地方。

  新增加IdentityService服务API

参照https://www.cnblogs.com/jaycewu/p/7791102.html

实现后IdentityService代码如下:

Startup.cs调整

configuration.json网关配置文件增加Token的获取地址

{

"DownstreamPathTemplate": "/connect/token",

"UpstreamPathTemplate": "/api/connect/token",

"UpstreamHttpMethod": [ "Post" ],

"ReRouteIsCaseSensitive": false,

"DownstreamScheme": "http",

"DownstreamHostAndPorts": [

{

"Host": "localhost",

"Port": 5001

}

],

"Key": "IdentityService",

"RateLimitOptions": {

"ClientWhitelist": [],

"EnableRateLimiting": true,

"Period": "1s",

"PeriodTimespan": 15,

"Limit": 1

}

},

{

"DownstreamPathTemplate": "/api/passenger",

"UpstreamPathTemplate": "/api/passenger",

"UpstreamHttpMethod": [ "Get" ],

"ReRouteIsCaseSensitive": false,

"DownstreamScheme": "http",

"ServiceName": "API002",

"LoadBalancer": "NoLoadBalancer",

"UseServiceDiscovery": true,

"Key": "passenger",

"AuthenticationOptions": {

"AuthenticationProviderKey": "Bearer",

"AllowScopes": [ "TWAPI" ]

}

},

测试

启动程序

未获取Token的情况下:

  获取Token:

  带上Token进行访问:

  通过调用发现,返回的结果和预期一样,调用成功。

4.3、服务注册及发现(OceLot+Consul)

安装Consul就不介绍,可以参考:https://www.cnblogs.com/axzxs2001/p/8487521.html 自行调试,按照上面的写法需要非常注意的一点是OceLot9.X版本以下是OK的,最新版本的需要进行调整几个地方:

  安装OceLot.Provider.Consul包

    Install-Package Ocelot.Provider.Consul

  Startup.cs修改

  configuration.json调整

  官方参考:https://ocelot.readthedocs.io/en/latest/features/servicediscovery.html,按照官方的调整后,调试通过

  测试

  启动程序及Consul

  调用:

  我们发现,OceLot已通过Consul调用成功。

4.4、服务质量与熔断

  熔断的意思是停止将请求转发到下游服务。当下游服务已经出现故障的时候再请求也是功而返,并且增加下游服务器和API网关的负担。这个功能是用的Pollly来实现的,我们只需要为路由做一些简单配置即可

  "QoSOptions": {

  "ExceptionsAllowedBeforeBreaking":3,

  "DurationOfBreak":5,

  "TimeoutValue":5000

  }

  ExceptionsAllowedBeforeBreaking允许多少个异常请求

  DurationOfBreak熔断的时间,单位为秒

  TimeoutValue如果下游请求的处理时间超过多少则自如将请求设置为超时。

4.5、分流控制

对请求进行限流可以防止下游服务器因为访问过载而崩溃,这个功能就是我们的张善友添加进去的。非常优雅的实现,我们只需要在路由下加一些简单的配置即可以完成。

"RateLimitOptions": {

"ClientWhitelist": [],

"EnableRateLimiting": true,

"Period": "1s",

"PeriodTimespan": 1,

"Limit": 1

}

ClientWihteList 白名单

EnableRateLimiting 是否启用限流

Period 统计时间段:1s, 5m, 1h, 1d

PeroidTimeSpan 多少秒之后客户端可以重试

Limit 在统计时间段内允许的最大请求数量

在 GlobalConfiguration下我们还可以进行以下配置

"RateLimitOptions": {

"DisableRateLimitHeaders": false,

"QuotaExceededMessage": "Customize Tips!",

"HttpStatusCode": 999,

"ClientIdHeader" : "Test"

}

Http头 X-Rate-Limit 和 Retry-After 是否禁用

QuotaExceedMessage 当请求过载被截断时返回的消息

HttpStatusCode 当请求过载被截断时返回的http status

ClientIdHeader 用来识别客户端的请求头,默认是 ClientId

如图:

  连续点击几下后,返回:429 Too Many Requests.

4.6、负载均衡

当下游服务有多个结点的时候,我们可以在DownstreamHostAndPorts中进行配置。

{

"DownstreamPathTemplate": "/api/posts/{postId}",

"DownstreamScheme": "https",

"DownstreamHostAndPorts": [

{

"Host": "10.0.1.10",

"Port": 5000,

},

{

"Host": "10.0.1.11",

"Port": 5000,

}

],

"UpstreamPathTemplate": "/posts/{postId}",

"LoadBalancer": "LeastConnection",

"UpstreamHttpMethod": [ "Put", "Delete" ]

}

LoadBalancer将决定负载均衡的算法

  • LeastConnection –将请求发往最空闲的那个服务器
  • RoundRobin –轮流发送
  • NoLoadBalance –总是发往第一个请求或者是服务发现

在负载均衡这里,我们还可以和Consul结合来使用服务发现,同一个服务和Name配置成相同,则在请求时,会根据LoadBalancer请求的算法进行访问,如下:

{

"service": {

"id": "count1",

"name": "Count",

"tags": ["dev"],

"address": "localhost",

"port": 5001

}

}

{

"service": {

"id": "count2",

"name": "Count",

"tags": ["dev"],

"address": "localhost",

"port": 5002

}

}

4.7、Swagger文档生成

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步你可以通过一个文本编辑器来编辑 Swagger 文件,或者你也可以从你的代码注释中自动生成。各种工具都可以使用 Swagger 文件来生成互动的 API 文档。

在集成Swagger的时候,我们同时需要考虑到与IdentityServer的集成,在调试的时候让Swagger充许进行验证。

BookingApi Startup.cs修改

public void ConfigureServices(IServiceCollection services)

{

services.AddSwaggerGen(c =>

{

c.SwaggerDoc("v1", new Info { Title = "BookingApi", Version = "v1" });

c.AddSecurityDefinition("Bearer", new ApiKeyScheme

{

Description = "请输入带有Bearer的Token",

Name = "Authorization",

In = "header",

Type = "apiKey"

});

//Json Token认证方式,此方式为全局添加

c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>

{

{ "Bearer", Enumerable.Empty<string>() }

});

});

services.AddMvc();

}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

app.UseSwagger();

// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),

// specifying the Swagger JSON endpoint.

app.UseSwaggerUI(c =>

{

c.SwaggerEndpoint("/swagger/v1/swagger.json", "BookingApi");

});

app.UseMvc();

}

PassengerApi Startup.cs修改

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc();

services.AddSwaggerGen(c =>

{

c.SwaggerDoc("v1", new Info { Title = "PassengerApi", Version = "v1" });

c.AddSecurityDefinition("Bearer", new ApiKeyScheme

{

Description = "请输入带有Bearer的Token",

Name = "Authorization",

In = "header",

Type = "apiKey"

});

//Json Token认证方式,此方式为全局添加

c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>

&nbs

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved