做者:markjiang7m2
原文地址:http://www.javashuo.com/article/p-tzjyhyew-ez.html
源码地址:https://gitee.com/Sevenm2/OcelotDemohtml
在上一篇Ocelot的文章中,我已经给你们介绍了何为Ocelot以及如何简单使用它的路由功能,若是你尚未不了解Ocelot为什么物,能够查看个人系列文章 Ocelot - .Net Core开源网关。在这篇文章中,我将会继续给你们介绍Ocelot的功能:请求聚合与负载均衡。git
开篇题外话:在上一篇文章的案例中,我直接使用API返回服务器的端口和接口的路径,我感受这样举例过于偏技术化,比较沉闷,而后我想到了以前参加PMP课程培训时候,咱们的培训讲师——孙志斌老师引用
小王
和老李
的模型给咱们讲述项目管理的各类实战,可谓是生动形象,并且所举的例子也很是贴近咱们的平常工做,通俗易懂,所以,我也尝试使用相似的人物形象进行案例的讲解。首先,本文将会引入两我的物Willing
和Jack
。Willing是一名资深专家,工做多年,而Jack则是.NET新手。github
本文中涉及案例的完整代码均可以从个人代码仓库进行下载。算法
咱们在案例一路由中已经知道,Ocelot能够定义多组路由,而后根据优先级对上游服务发出的请求进行不一样的转发处理,每一个路由转发都匹配惟一的一个下游服务API接口。然而,有时候,上游服务想要得到来自两个API接口返回的结果。Ocelot容许咱们在配置文件中声明聚合路由Aggregates
,从而实现这样的效果。
举个例子,有一天个人老板(用户)让我(上游服务)去了解清楚Willing和Jack两位同事对工做安排有什么意见(请求),固然了,我能够先跑去问Jack,而后再跑到Willing那里了解状况,但是这样我就要跑两趟,这样不划算啊,因而,我去找了他们的领导(聚合)说我老板想要了解他们两个的意见,他们领导一个电话打过去,Willing和Jack就都一块儿过来了,我也就很快完成了老板交代的任务。
在这个过程当中,我是能够单独访问Willing或者Jack的,所以,他们是在ReRoutes
中声明的两组普通的路由,而他们的领导是在Aggregates
中声明的一组聚合路由。刚刚咱们的举例当中,访问不一样的人须要到达不一样的地方,所以在声明路由时,也须要注意它们的UpstreamPathTemplate
都是不同的。
下面是具体的路由配置:json
"ReRoutes": [ { "DownstreamPathTemplate": "/api/ocelot/aggrWilling", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/ocelot/aggrWilling", "UpstreamHttpMethod": [ "Get" ], "Key": "aggr_willing", "Priority": 2 }, { "DownstreamPathTemplate": "/api/ocelot/aggrJack", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/ocelot/aggrJack", "UpstreamHttpMethod": [ "Get" ], "Key": "aggr_jack", "Priority": 2 } ], "Aggregates": [ { "ReRouteKeys": [ "aggr_willing", "aggr_jack" ], "UpstreamPathTemplate": "/aggrLeader" } ]
你们能够注意到,在ReRoutes
中声明的两组路由相比案例一不一样的是,多加了一个Key
属性。Aggregates
跟ReRoutes
是同级的,并且也是一个数组,这表明着咱们能够声明多个聚合路由,而在咱们声明的这一组聚合路由中的属性ReRouteKeys
,它包含的元素就是咱们真正须要响应的路由的Key
属性值。c#
固然,咱们的下游服务也相应添加两个API接口。api
// GET api/ocelot/aggrWilling [HttpGet("aggrWilling")] public async Task<IActionResult> AggrWilling(int id) { var result = await Task.Run(() => { return $"我是Willing,仍是多加工资最实际, path: {HttpContext.Request.Path}"; }); return Ok(result); } // GET api/ocelot/aggrJack [HttpGet("aggrJack")] public async Task<IActionResult> AggrJack(int id) { var result = await Task.Run(() => { return $"我是Jack,我很是珍惜如今的工做机会, path: {HttpContext.Request.Path}"; }); return Ok(result); }
下面咱们一块儿来看看执行的结果。数组
咱们按照案例一,先单独来问问Jack。
安全
而后再看看直接经过聚合路由访问
服务器
能够看到,在返回结果中同时包含了Willing和Jack的结果,而且是以json
串的格式返回,以路由的Key
属性值做为返回json的属性。
(返回的结果好像哪里不太对,不知道你是否发现了,但暂时先不要着急,我在后面会为你们揭晓)
须要注意的是,Ocelot仅支持GET
方式的请求聚合。Ocelot老是以application/json
的格式返回一个聚合请求的,当下游服务是返回404状态码,在返回结果中,其对应的值则为空值,即便聚合路由中全部的下游服务都返回404状态码,聚合路由的返回结果也不会是404状态码。
咱们在不添加任何API接口的状况下,声明一组下游服务不存在的路由,并将它添加到聚合路由当中。
"ReRoutes": [ ..., { "DownstreamPathTemplate": "/api/ocelot/aggrError/1", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "UpstreamPathTemplate": "/ocelot/aggrError/1", "UpstreamHttpMethod": [ "Get" ], "Key": "aggr_error", "Priority": 2 } ], "Aggregates": [ { "ReRouteKeys": [ "aggr_willing", "aggr_jack", "aggr_error" ], "UpstreamPathTemplate": "/aggrLeader" } ]
测试结果以下:
直接请求aggr_error
直接经过聚合路由访问
前面我说到返回结果好像有哪里不太对,那究竟是哪里出错了呢?我来将返回的json串进行格式化一下。
{ "aggr_willing":我是Willing,仍是多加工资最实际, path: /api/ocelot/aggrWilling, "aggr_jack":我是Jack,我很是珍惜如今的工做机会, path: /api/ocelot/aggrJack, "aggr_error": }
咱们会发现这并非一个正确的json串,那到底为何会这样呢?既然Ocelot是开源的,那咱们就来深挖一下源码究竟是怎么处理聚合请求返回结果的。
Ocelot Github:https://github.com/ThreeMammals/Ocelot
找到位于Ocelot.Middleware.Multiplexer
中的一个类SimpleJsonResponseAggregator
,静态方法MapAggregateContent
。
var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); contentBuilder.Append($"\"{responseKeys[k]}\":{content}");
由于个人下游服务返回结果是一个字符串,而后被Ocelot直接拼接到返回结果中,从而获得咱们上面看到的结果。
所以,在我看来,当咱们使用Ocelot的聚合路由功能时,下游服务的返回结果必需要保证是一个json串,这样才能最终被正确识别。
我把下游服务改一改,添加一个类,而后将API返回结果格式更改成这个类型。
public class ResponseResult { public string Comment { get; set; } }
// GET api/ocelot/aggrWilling [HttpGet("aggrWilling")] public async Task<IActionResult> AggrWilling(int id) { var result = await Task.Run(() => { ResponseResult response = new ResponseResult() { Comment = $"我是Willing,仍是多加工资最实际, path: {HttpContext.Request.Path}" }; return response; //return $"我是Willing,仍是多加工资最实际, path: {HttpContext.Request.Path}"; }); return Ok(result); } // GET api/ocelot/aggrJack [HttpGet("aggrJack")] public async Task<IActionResult> AggrJack(int id) { var result = await Task.Run(() => { ResponseResult response = new ResponseResult() { Comment = $"我是Jack,我很是珍惜如今的工做机会, path: {HttpContext.Request.Path}" }; return response; //return $"我是Jack,我很是珍惜如今的工做机会, path: {HttpContext.Request.Path}"; }); return Ok(result); }
运行看执行结果
简单总结为如下三点注意:
GET
方式在上一个案例中,我已经能够经过Willing和Jack的领导获得我想要的结果,但在这个过程当中,他们的领导(聚合)都只是在帮我得到结果,没有对获得的结果作任何的干预。那若是领导想着,既然老板想要了解状况,本身固然也要干点活,让老板知道在这个过程当中本身也是有出力的,这就涉及到进阶的请求聚合了。
在网上搜了一下关于进阶请求聚合的资料,好像没有怎么见到有相关实例的Demo,最全面的资料来自于官网文档说明,也许是在实际应用中这个功能不怎么被运用?或是我打开的方式不对?缘由暂时未知,知道的朋友们能够在留言区给我说一下。那么我在这里就用实例给你们介绍一下。
Ocelot支持在得到下游服务返回结果后,经过一个聚合器对返回结果进行再一步的加工处理,目前支持内容,头和状态代码的修改。咱们来看配置文件
"Aggregates": [ { "ReRouteKeys": [ "aggr_willing", "aggr_jack", "aggr_error" ], "UpstreamPathTemplate": "/aggrLeaderAdvanced", "Aggregator": "LeaderAdvancedAggregator" } ]
由于是请求聚合的进阶,因此ReRoutes
路由不须要任何更改。Aggregates
中一组配置增长了属性Aggregator
,表示当得到返回结果,由聚合器LeaderAdvancedAggregator
进行处理。
而后我在Ocelot项目中添加聚合器LeaderAdvancedAggregator
,要实现这个聚合器,就必须实现来自Ocelot.Middleware.Multiplexer
提供的接口IDefinedAggregator
。
public class LeaderAdvancedAggregator : IDefinedAggregator { public async Task<DownstreamResponse> Aggregate(List<DownstreamResponse> responses) { List<string> results = new List<string>(); var contentBuilder = new StringBuilder(); contentBuilder.Append("{"); foreach (var down in responses) { string content = await down.Content.ReadAsStringAsync(); results.Add($"\"{Guid.NewGuid()}\":{content}"); } //来自leader的声音 results.Add($"\"{Guid.NewGuid()}\":{{comment:\"我是leader,我组织了他们两个进行调查\"}}"); contentBuilder.Append(string.Join(",", results)); contentBuilder.Append("}"); var stringContent = new StringContent(contentBuilder.ToString()) { Headers = { ContentType = new MediaTypeHeaderValue("application/json") } }; var headers = responses.SelectMany(x => x.Headers).ToList(); return new DownstreamResponse(stringContent, HttpStatusCode.OK, headers, "some reason"); } }
当下游服务返回结果后,Ocelot就会调用聚合器的Aggregate
方法,所以,咱们的处理代码就写在这个方法中。
以后,咱们就须要将聚合器在容器中进行注册
Startup.cs
services .AddOcelot() .AddSingletonDefinedAggregator<LeaderAdvancedAggregator>();
运行,访问进阶请求聚合的Urlhttp://localhost:4727/aggrLeaderAdvanced
,获得以下结果:
也许你们已经留意到,我在处理返回结果是,并无像Ocelot内部返回结果同样使用路由的Key
做为属性,而是使用了Guid。其实这也是我在作Demo时候的一处疑惑,我彷佛没法像Ocelot内部同样处理。
在这个Aggregate
方法中提供的参数类型只有List<DownstreamResponse>
,但DownstreamResponse
中并无关于ReRouteKeys
的信息。我查看了Ocelot的源码,ReRouteKeys
只存在于DownstreamReRoute
中,但我没法经过DownstreamResponse
获取到DownstreamReRoute
。
但愿有知道的朋友能在留言区告诉我一下,感谢。
另外,这个聚合器也能像通常服务同样,可使用依赖注入的方式添加依赖。我也尝试在案例中添加了一个依赖LeaderAdvancedDependency
。如何使用依赖注入,我这里就不细说了,你们能够搜索 .net core依赖注入的相关资料。
LeaderAdvancedAggregator.cs
public LeaderAdvancedDependency _dependency; public LeaderAdvancedAggregator(LeaderAdvancedDependency dependency) { _dependency = dependency; }
Startup.cs
services.AddSingleton<LeaderAdvancedDependency>();
这样,咱们就能够在聚合器中使用依赖了。
Ocelot除了支持Singleton
的聚合器之外,还支持Transient
的聚合器,你们能够按需使用。
Startup.cs
services .AddOcelot() .AddTransientDefinedAggregator<LeaderAdvancedAggregator>();
在前面的案例中,咱们所有的路由配置中都是一组路由配置一个下游服务地址,也就意味着,当上游服务请求一个Url,Ocelot就一定转发给某一个固定的下游服务,但这样对于一个系统来讲,这是不安全的,由于有可能某一个下游服务阻塞,甚至挂掉了,那就可能致使整个服务瘫痪了,对于当前快速运转的互联网时代,这是不容许的。
Ocelot可以经过可用的下游服务对每一个路由进行负载平衡。咱们来看看具体的路由配置
{ "DownstreamPathTemplate": "/api/ocelot/{postId}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 }, { "Host": "localhost", "Port": 8002 } ], "UpstreamPathTemplate": "/ocelot/{postId}", "UpstreamHttpMethod": [ "Get" ], "LoadBalancerOptions": { "Type": "RoundRobin" } }
LeadConnection
负载均衡器算法共有4种:
为了能快速验证负载均衡器的有效性,咱们这个案例中采用了RoundRobin
轮询算法。而后下游服务仍是用了案例一中创建的基本服务,在IIS中部署两套一样的下游服务,分别占用端口8001和8002。
当咱们第一次请求http://localhost:4727/ocelot/5
,获得的是端口8001的返回结果
而当咱们再次请求http://localhost:4727/ocelot/5
,获得的是端口8002的返回结果
再次请求则又是8001的返回结果,如此轮询下去。
但须要注意的是,当我尝试将8002端口服务中止时
我获得了这样的结果:第一次请求获得8001的返回结果,第二次请求获得的则是500的状态码
根据官网文档的说明
RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot’s.
的确说的是轮询可用的服务,彷佛与个人测试结果不相符。不知道是个人测试环境出了问题,仍是我某个环节配置错误,亦或是这个算法真的没有避开不可用的服务。但愿有知道的朋友在留言区给我解惑,感谢。
在本案例中,我就再也不展开演示另外3种算法了,其中NoLoadBalancer
会与服务发现的案例再进行深刻探讨。
原本今天是想给你们写多两个功能案例的,奈何这个进阶的资料实在很少,固然也有我本身一方面实力不足的缘由,致使花了很长的时间进行消化。在本文中介绍了Ocelot的请求聚合与负载均衡,其中请求聚合在使用的过程当中仍是有几点须要注意的,负载均衡则须要你们按需选择适合本身系统的算法。后续还会有Ocelot的系列文章,但愿你们持续关注。