Tip: 此篇已加入.NET Core微服务基础系列文章索引
一、负载均衡与请求缓存
1.1 负载均衡
为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientService分别部署于这两个节点内(192.168.80.70与192.168.80.71)。
为了更好的展示API Repsonse来自哪个节点,我们更改一下返回值:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { $"ClinetService: {DateTime.Now.ToString()} {Environment.MachineName} " +
$"OS: {Environment.OSVersion.VersionString}" };
} ......
}
Ocelot的配置文件中确保有负载均衡的设置:
{
"ReRoutes": [
......
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
......
}
接下来发布并部署到这两个节点上去,之后启动我们的API网关,这里我用命令行启动:
然后就可以测试负载均衡了,在浏览器中输入URL并连续刷新:可以通过主机名看到的确是根据轮询来进行的负载均衡。
负载均衡LoadBalance可选值:
- RoundRobin - 轮询,挨着来,雨露均沾
- LeastConnection - 最小连接数,谁的任务最少谁来接客
- NoLoadBalance - 不要负载均衡,让我一个人累死吧
1.2 请求缓存
Ocelot目前支持对下游服务的URL进行缓存,并可以设置一个以秒为单位的TTL使缓存过期。我们也可以通过调用Ocelot的管理API来清除某个Region的缓存。
为了在路由中使用缓存,需要在ReRoute中加上如下设置:
"FileCacheOptions": { "TtlSeconds": , "Region": "somename" }
这里表示缓存10秒,10秒后过期。另外,貌似只支持get方式,只要请求的URL不变,就会缓存。
这里我们仍以上面的demo为例,在增加了FileCacheOptions配置之后,进行一个小测试:因为我们设置的10s过期,所以在10s内拿到的都是缓存,否则就会触发负载均衡去不同节点拿数据。
二、限流与熔断器(QoS)
2.1 限流 (RateLimit)
对请求进行限流可以防止下游服务器因为访问过载而崩溃,我们只需要在路由下加一些简单的配置即可以完成。另外,看文档发现,这个功能是张善友大队长贡献的,真是666。同时也看到一个园友catcherwong,已经实践许久了,真棒。
对于限流,我们可以对每个服务进行如下配置:
"RateLimitOptions": {
"ClientWhitelist": [ "admin" ], // 白名单
"EnableRateLimiting": true, // 是否启用限流
"Period": "1m", // 统计时间段:1s, 5m, 1h, 1d
"PeriodTimespan": , // 多少秒之后客户端可以重试
"Limit": // 在统计时间段内允许的最大请求数量
}
同时,我们可以做一些全局配置:
"RateLimitOptions": {
"DisableRateLimitHeaders": false, // Http头 X-Rate-Limit 和 Retry-After 是否禁用
"QuotaExceededMessage": "Too many requests, are you OK?", // 当请求过载被截断时返回的消息
"HttpStatusCode": , // 当请求过载被截断时返回的http status
"ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
}
这里每个字段都有注释,不再解释。下面我们来测试一下:
Scenario 1:不带header地访问clientservice,1分钟之内超过5次,便会被截断,直接返回截断后的消息提示,HttpStatusCode:999
可以通过查看Repsonse的详细信息,验证是否返回了999的状态码:
Scenario 2:带header(client_id:admin)访问clientservice,1分钟之内可以不受限制地访问API
2.2 熔断器(QoS)
熔断的意思是停止将请求转发到下游服务。当下游服务已经出现故障的时候再请求也是无功而返,并且还会增加下游服务器和API网关的负担。这个功能是用的Pollly来实现的,我们只需要为路由做一些简单配置即可。如果你对Polly不熟悉,可以阅读我之前的一篇文章《.NET Core微服务之基于Polly+AspectCore实现熔断与降级机制》
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": , // 允许多少个异常请求
"DurationOfBreak": , // 熔断的时间,单位为毫秒
"TimeoutValue": // 如果下游请求的处理时间超过多少则视如该请求超时
},
*.这里针对DurationOfBreak,官方文档中说明的单位是秒,但我在测试中发现应该是毫秒。不知道是我用的版本不对,还是怎么的。anyway,这不是实验的重点。OK,这里我们的设置就是:如果Service Server的执行时间超过3秒,则会抛出Timeout Exception。如果Service Server抛出了第二次Timeout Exception,那么停止服务访问5s钟。
现在我们来改造一下Service,使其手动超时以使得Ocelot触发熔断保护机制。Ocelot中设置的TimeOutValue为3秒,那我们这儿简单粗暴地让其延时5秒(只针对前3次请求)。
[Route("api/[controller]")]
public class ValuesController : Controller
{
...... private static int _count = ;
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
_count++;
Console.WriteLine($"Get...{_count}");
if (_count <= )
{
System.Threading.Thread.Sleep();
} return new string[] { $"ClinetService: {DateTime.Now.ToString()} {Environment.MachineName} " +
$"OS: {Environment.OSVersion.VersionString}" };
} ......
}
下面我们就来测试一下:可以看到异常之后,便进入了5秒中的服务不可访问期(直接返回了503 Service Unavaliable),而5s之后又可以正常访问该接口了(这时不会再进入hard-code的延时代码)
通过日志,也可以确认Ocelot触发了熔断保护:
三、动态路由(Dynamic Routing)
记得上一篇中一位园友评论说他有500个API服务,如果一一地配置到配置文件,将会是一个巨大的工程,虽然都是copy,但是会增加出错的机会,并且很难排查。这时,我们可以牺牲一些特殊性来求通用性,Ocelot给我们提供了Dynamic Routing功能。这个功能是在issue 340后增加的(见下图官方文档),目的是在使用服务发现之后,直接通过服务发现去定位从而减少配置文件中的ReRoutes配置项。
Example:http://api.edc.com/productservice/api/products => Ocelot会将productservice作为key调用Consul服务发现API去得到IP和Port,然后加上后续的请求URL部分(api/products)进行最终URL的访问:http://ip:port/api/products。
这里仍然采用下图所示的实验节点结构:一个API网关节点,三个Consul Server节点以及一个Consul Client节点。
由于不再需要配置ReRoutes,所以我们需要做一些“通用性”的改造,详见下面的GlobalConfiguration:
{
"ReRoutes": [],
"Aggregates": [],
"GlobalConfiguration": {
"RequestIdKey": null,
"ServiceDiscoveryProvider": {
"Host": "192.168.80.100", // Consul Service IP
"Port": // Consul Service Port
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false, // Http头 X-Rate-Limit 和 Retry-After 是否禁用
"QuotaExceededMessage": "Too many requests, are you OK?", // 当请求过载被截断时返回的消息
"HttpStatusCode": , // 当请求过载被截断时返回的http status
"ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": ,
"DurationOfBreak": ,
"TimeoutValue":
},
"BaseUrl": null,
"LoadBalancerOptions": {
"Type": "LeastConnection",
"Key": null,
"Expiry":
},
"DownstreamScheme": "http",
"HttpHandlerOptions": {
"AllowAutoRedirect": false,
"UseCookieContainer": false,
"UseTracing": false
}
}
}
详细信息请浏览:http://ocelot.readthedocs.io/en/latest/features/servicediscovery.html#dynamic-routing
下面我们来做一个小测试,分别访问clientservice和productservice,看看是否能成功地访问到。
(1)访问clientservice
(2)访问productservice
可以看出,只要我们正确地输入请求URL,基于服务发现之后是可以正常访问到的。只是这里我们需要输入正确的service name,这个service name是在consul中注册的名字,如下高亮部分所示:
{
"services":[
{
"id": "EDC_DNC_MSAD_CLIENT_SERVICE_01",
"name" : "CAS.ClientService",
"tags": [
"urlprefix-/ClientService01"
],
"address": "192.168.80.71",
"port": ,
"checks": [
{
"name": "clientservice_check",
"http": "http://192.168.80.71:8810/api/health",
"interval": "10s",
"timeout": "5s"
}
]
}
]
}
四、集成Swagger统一API文档入口
在前后端分离大行其道的今天,前端和后端的唯一联系,变成了API接口;API文档变成了前后端开发人员联系的纽带,变得越来越重要,swagger就是一款让你更好的书写API文档的框架。
4.1 为每个Service集成Swagger
Step1.NuGet安装Swagger
NuGet>Install-Package Swashbuckle.AspNetCore
Step2.改写StartUp类
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
....... services.AddMvc(); // Swagger
services.AddSwaggerGen(s =>
{
s.SwaggerDoc(Configuration["Service:DocName"], new Info
{
Title = Configuration["Service:Title"],
Version = Configuration["Service:Version"],
Description = Configuration["Service:Description"],
Contact = new Contact
{
Name = Configuration["Service:Contact:Name"],
Email = Configuration["Service:Contact:Email"]
}
}); var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var xmlPath = Path.Combine(basePath, Configuration["Service:XmlFile"]);
s.IncludeXmlComments(xmlPath);
}); ......
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseMvc();
// swagger
app.UseSwagger(c=>
{
c.RouteTemplate = "doc/{documentName}/swagger.json";
});
app.UseSwaggerUI(s =>
{
s.SwaggerEndpoint($"/doc/{Configuration["Service:DocName"]}/swagger.json",
$"{Configuration["Service:Name"]} {Configuration["Service:Version"]}");
});
}
}
这里配置文件中关于这部分的内容如下:
{
"Service": {
"Name": "CAS.NB.ClientService",
"Port": "",
"DocName": "clientservice",
"Version": "v1",
"Title": "CAS Client Service API",
"Description": "CAS Client Service API provide some API to help you get client information from CAS",
"Contact": {
"Name": "CAS 2.0 Team",
"Email": "EdisonZhou@manulife.com"
},
"XmlFile": "Manulife.DNC.MSAD.NB.ClientService.xml"
}
}
需要注意的是,勾选输出XML文档文件,并将其copy到发布后的目录中(如果没有自动复制的话):
4.2 为API网关集成Swagger
Step1.NuGet安装Swagger => 参考4.1
Step2.改写StartUp类
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Ocelot
services.AddOcelot(Configuration);
// Swagger
services.AddMvc();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc($"{Configuration["Swagger:DocName"]}", new Info
{
Title = Configuration["Swagger:Title"],
Version = Configuration["Swagger:Version"]
});
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} // get from service discovery later
var apiList = new List<string>()
{
"clientservice",
"productservice",
"noticeservice"
};
app.UseMvc()
.UseSwagger()
.UseSwaggerUI(options =>
{
apiList.ForEach(apiItem =>
{
options.SwaggerEndpoint($"/doc/{apiItem}/swagger.json", apiItem);
});
}); // Ocelot
app.UseOcelot().Wait();
}
}
*.这里直接hard-code了一个apiNameList,实际中应该采用配置文件或者调用服务发现获取服务名称(假设你的docName和serviceName保持一致,否则无法准确定位你的文档)
Step3.更改configuration.json配置文件 => 与hard-code的名称保持一致,这里为了方便直接让上下游的URL格式保持一致,以方便地获取API文档
{
"ReRoutes": [
// API01:CAS.ClientService
// --> swagger part
{
"DownstreamPathTemplate": "/doc/clientservice/swagger.json",
"DownstreamScheme": "http",
"ServiceName": "CAS.ClientService",
"LoadBalancer": "RoundRobin",
"UseServiceDiscovery": true,
"UpstreamPathTemplate": "/doc/clientservice/swagger.json",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
},
// --> service part
{
"UseServiceDiscovery": true, // use Consul service discovery
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"ServiceName": "CAS.ClientService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UpstreamPathTemplate": "/api/clientservice/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"RateLimitOptions": {
"ClientWhitelist": [ "admin" ], // 白名单
"EnableRateLimiting": true, // 是否启用限流
"Period": "1m", // 统计时间段:1s, 5m, 1h, 1d
"PeriodTimespan": , // 多少秒之后客户端可以重试
"Limit": // 在统计时间段内允许的最大请求数量
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": , // 允许多少个异常请求
"DurationOfBreak": , // 熔断的时间,单位为秒
"TimeoutValue": // 如果下游请求的处理时间超过多少则视如该请求超时
},
"ReRoutesCaseSensitive": false // non case sensitive
},
// API02:CAS.ProductService
// --> swagger part
{
"DownstreamPathTemplate": "/doc/productservice/swagger.json",
"DownstreamScheme": "http",
"ServiceName": "CAS.ProductService",
"LoadBalancer": "RoundRobin",
"UseServiceDiscovery": true,
"UpstreamPathTemplate": "/doc/productservice/swagger.json",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
},
// --> service part
{
"UseServiceDiscovery": true, // use Consul service discovery
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"ServiceName": "CAS.ProductService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"FileCacheOptions": { // cache response data - ttl: 10s
"TtlSeconds": ,
"Region": ""
},
"UpstreamPathTemplate": "/api/productservice/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"RateLimitOptions": {
"ClientWhitelist": [ "admin" ],
"EnableRateLimiting": true,
"Period": "1m",
"PeriodTimespan": ,
"Limit":
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": , // 允许多少个异常请求
"DurationOfBreak": , // 熔断的时间,单位为秒
"TimeoutValue": // 如果下游请求的处理时间超过多少则视如该请求超时
},
"ReRoutesCaseSensitive": false // non case sensitive
}
],
"GlobalConfiguration": {
//"BaseUrl": "https://api.mybusiness.com"
"ServiceDiscoveryProvider": {
"Host": "192.168.80.100", // Consul Service IP
"Port": // Consul Service Port
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false, // Http头 X-Rate-Limit 和 Retry-After 是否禁用
"QuotaExceededMessage": "Too many requests, are you OK?", // 当请求过载被截断时返回的消息
"HttpStatusCode": , // 当请求过载被截断时返回的http status
"ClientIdHeader": "client_id" // 用来识别客户端的请求头,默认是 ClientId
}
}
}
*.这里需要注意其中新增加的swagger part配置,专门针对swagger.json做的映射.
4.3 测试
从此,我们只需要通过API网关就可以浏览所有服务的API文档了,爽歪歪!
五、小结
本篇基于Ocelot官方文档,学习了一下Ocelot的一些有用的功能:负载均衡(虽然只提供了两种基本的算法策略)、缓存、限流、QoS以及动态路由(Dynamic Routing),并通过一些简单的Demo进行了验证。最后通过继承Swagger做统一API文档入口,从此只需要通过一个URL即可查看所有基于swagger的API文档。通过查看Ocelot官方文档,可以知道Ocelot还支持许多其他有用的功能,而那些功能这里暂不做介绍(或许有些会在后续其他部分(如验证、授权、Trace等)中加入)。此外,一些朋友找我要demo的源码,我会在后续一齐上传到github。而这几篇中的内容,完全可以通过分享出来的code和配置自行构建,因此就不贴出来了=>已经贴出来,请点击下载。
示例代码
Click here => 点我下载
参考资料
jesse(腾飞),《.NET Core开源网关 - Ocelot 中文文档》
catcher wong,《Building API Gateway Using Ocelot In ASP.NET Core - QoS (Quality of Service)》
focus-lei,《.NET Core在Ocelot网关中统一配置Swagger》
Ocelot官方文档:http://ocelot.readthedocs.io/en/latest/index.html
.NET Core微服务之基于Ocelot实现API网关服务(续)的更多相关文章
-
.NET Core微服务之基于Ocelot实现API网关服务
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.啥是API网关? API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端 ...
-
Ocelot实现API网关服务
NET Core微服务之基于Ocelot实现API网关服务 https://www.cnblogs.com/edisonchou/p/api_gateway_ocelot_foundation_01. ...
-
NET Core微服务之路:基于Ocelot的API网关Relay实现--RPC篇
前言 我们都知道,API网关是工作在应用层上网关程序,为何要这样设计呢,而不是将网关程序直接工作在传输层.或者网络层等等更底层的环境呢?让我们先来简单的了解一下TCP/IP的五层模型. (图片 ...
-
.net core Ocelot Consul 实现API网关 服务注册 服务发现 负载均衡
大神张善友 分享过一篇 <.NET Core 在腾讯财付通的企业级应用开发实践>里面就是用.net core 和 Ocelot搭建的可扩展的高性能Api网关. Ocelot(http:// ...
-
第七章 API网关服务:Spring Cloud Zuul
API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实现 ...
-
微服务基础——厉害了!API网关
微服务刚刚诞生的时候,人们将服务进行拆分,实现服务之间的松耦合,并且每个服务有专门的团队维护,然后客户端直接和各个子服务进行交互.比如,订单,商品,会员服务. 那么这种客户端直接和后端服务交互的方式会 ...
-
spring cloud 入门系列六:使用Zuul 实现API网关服务
通过前面几次的分享,我们了解了微服务架构的几个核心设施,通过这些组件我们可以搭建简单的微服务架构系统.比如通过Spring Cloud Eureka搭建高可用的服务注册中心并实现服务的注册和发现: 通 ...
-
Spring Cloud Zuul 1(API 网关服务)
API网关是一个更为智能的应用服务器,它的存在就像是整个微服架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤. 它实现的功能包括:请求路由.负载均衡.校验过滤等功能. Spring ...
-
SpringCloud开发学习总结(八)—— API网关服务Zuul(一)
大多数情况下,为了保证对外服务的安全性,我们在服务端实现的为服务接口时往往都会有一定的权限校验机制,比如对用户登录状态的校验等:同时为了防止客户端在发起请求时被篡改等安全方面的考虑,还会有一些签名校验 ...
随机推荐
-
AJAX的封装(包括跨域问题)
注意: 1.同域下支持get和post方法 2.跨域问题必须得到后台的支持 3.跨域只支持get方法 function AJAX(obj){ //做网络请求的时候,参数以"对象"的 ...
-
git学习第一课
##git管理工具学习(Windows7)###1.下载安装git安装包###2.注册github账号①github.com官网注册并创建项目用户名xiaqiubo项目名xiaoxia②复制http ...
-
JavaScript垃圾回收
JavaScript内存监测工具 http://www.cnblogs.com/strick/p/4002010.html
-
selenium python (八)定位frame中的对象
#!/usr/bin/python# -*- coding: utf-8 -*-__author__ = 'zuoanvip'#在测试过程中经常遇到frame嵌套的应用,加入页面上有A.B两个fram ...
-
Python_正则表达式一
''' 常用的正则表达式元字符 . 匹配换行符以外的任意单个字符 * 匹配位于'*'之前的字符或子模的0次或多次出现 + 匹配位于'+'之前的字符或子模式的1次或多次出现 - 用在[]之内用来表示范围 ...
-
java 动态绑定 多态
继承链中对象方法的调用规则:当前类-->父类-->爷类-->..-->祖先类(只能向上找,不能向下找)优先级:this.method(Obj) > super.metho ...
-
base | Tread类
Tread类 Linux中,每个进程有一个pid,类型pid_t,由getpid()取得.Linux下的POSIX线程也有一个id,类型 pthread_t,由pthread_self()取得,该id ...
-
js中级小知识4
1.针对表单 form input select textarea type="radio/checkbox/passdord/button/submit/reset/ ...
-
R语言学习 第三篇:数据框
数据框(data.frame)是最常用的数据结构,用于存储二维表(即关系表)的数据,每一列存储的数据类型必须相同,不同数据列的数据类型可以相同,也可以不同,但是每列的行数(长度)必须相同.数据框的每列 ...
-
怎样写SQL语句可以提高数据库的性能
1.首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个10万条记录的表中查1条记录 ...