Ocelot(二)- 请求聚合
Ocelot(二)- 请求聚合与负载均衡
作者:markjiang7m2
原文地址:https://www.cnblogs.com/markjiang7m2/p/10865511.html
源码地址:https://gitee.com/Sevenm2/OcelotDemo
在上一篇Ocelot的文章中,我已经给大家介绍了何为Ocelot以及如何简单使用它的路由功能,如果你还没有不了解Ocelot为何物,可以查看我的系列文章 Ocelot - .Net Core开源网关。在这篇文章中,我将会继续给大家介绍Ocelot的功能:请求聚合与负载均衡。
开篇题外话:在上一篇文章的案例中,我直接使用API返回服务器的端口和接口的路径,我感觉这样举例过于偏技术化,比较沉闷,然后我想到了之前参加PMP课程培训时候,我们的培训讲师——孙志斌老师引用
小王
和老李
的模型给我们讲述项目管理的各种实战,可谓是生动形象,而且所举的例子也非常贴近我们的日常工作,通俗易懂,因此,我也尝试使用类似的人物形象进行案例的讲解。首先,本文将会引入两个人物Willing
和Jack
。Willing是一名资深专家,工作多年,而Jack则是.NET新手。
本文中涉及案例的完整代码都可以从我的代码仓库进行下载。
- 仓库地址:https://gitee.com/Sevenm2/OcelotDemo
案例二 请求聚合
我们在案例一路由中已经知道,Ocelot可以定义多组路由,然后根据优先级对上游服务发出的请求进行不同的转发处理,每个路由转发都匹配唯一的一个下游服务API接口。然而,有时候,上游服务想要获得来自两个API接口返回的结果。Ocelot允许我们在配置文件中声明聚合路由Aggregates
,从而实现这样的效果。
举个例子,有一天我的老板(用户)让我(上游服务)去了解清楚Willing和Jack两位同事对工作安排有什么意见(请求),当然了,我可以先跑去问Jack,然后再跑到Willing那里了解情况,可是这样我就要跑两趟,这样不划算啊,于是,我去找了他们的领导(聚合)说我老板想要了解他们两个的意见,他们领导一个电话打过去,Willing和Jack就都一起过来了,我也就很快完成了老板交代的任务。
在这个过程中,我是可以单独访问Willing或者Jack的,因此,他们是在ReRoutes
中声明的两组普通的路由,而他们的领导是在Aggregates
中声明的一组聚合路由。刚刚我们的举例当中,访问不同的人需要到达不同的地方,因此在声明路由时,也需要注意它们的UpstreamPathTemplate
都是不一样的。
下面是具体的路由配置:
"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
属性值。
当然,我们的下游服务也相应添加两个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
方式 - 下游服务返回类型要求为application/json
- 返回内容类型为application/json,不会返回404请求
进阶请求聚合
在上一个案例中,我已经可以通过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种:
- LeastConnection 把新请求发送到现有请求最少的服务上
- RoundRobin 轮询可用的服务并发送请求
- NoLoadBalancer 不负载均衡,总是发往第一个可用的下游服务
- CookieStickySessions 使用cookie关联所有相关的请求到制定的服务
为了能快速验证负载均衡器的有效性,我们这个案例中采用了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的系列文章,希望大家持续关注。
转载于:https://www.cnblogs.com/lonelyxmas/p/10873526.html
Ocelot(二)- 请求聚合相关推荐
- Ocelot(二)- 请求聚合与负载均衡
Ocelot(二)- 请求聚合与负载均衡 作者:markjiang7m2 原文地址:http://letyouknow.net/ocelot/ocelot-tutorial-2.html 源码地址:h ...
- Ocelot简易教程(四)之请求聚合以及服务发现
上篇文章给大家讲解了Ocelot的一些特性并对路由进行了详细的介绍,今天呢就大家一起来学习下Ocelot的请求聚合以及服务发现功能.希望能对大家有所帮助. 作者:依乐祝 原文地址:https://ww ...
- PHP:使用curl请求聚合支付的驾考宝典题库接口数据, 并渲染到页面上;composer 常用的指令;mvc pathinfo 路由映射实现
PHP:使用curl请求聚合支付的驾考宝典题库接口数据, 并渲染到页面上;composer 常用的指令;mvc pathinfo 路由映射实现 一. API 基础知识 url 的相关知识 1.1 一切 ...
- TP5后端,VUE前端请求聚合数据驾照题库
选择效果: 演示效果: 1: Vue 配置: /config/index.js 'use strict' // Template version: 1.3.1 // see http://vuejs- ...
- 关于吃掉物理的二次聚合无法实现的需要之旁门左道实现法
需求: 吃掉物理想要统计一下,在指定时间内,所有的吃掉物理用户(包括老师,学生,家长等),按访问次数进行一下聚合,显示每个访问次数对应的人数,可以进一步获知僵尸用户和活跃用户的个数,以后可能扩展为想知 ...
- BeetleX网关之请求聚合
在网关服务中请求聚合是允许把多个请求打包成一个响应给请求方,这样不仅可以节省请求方的请求数量,还可以根据需求的情况整合不同业务数据响应请求.BeetleX.Bumblebee虽然并没有内置这一功能,但 ...
- TP5后端,VUE前端请求聚合数据新闻接口
问题描述: TP5当后端,VUE当前端, 请求聚合数据新闻接口 演示效果如下: ps: 最开始加载页面的时候,只加载本地的文件(因为请求次数有限制) 问题解决: 1: vue 文件: <temp ...
- Flink使用二次聚合实现TopN计算-乱序数据
一.背景说明: 在上篇文章实现了TopN计算,但是碰到迟到数据则会无法在当前窗口计算,需要对其中的键控状态优化 Flink使用二次聚合实现TopN计算 本次需求是对数据进行统计,要求每隔5秒,输出最近 ...
- TP5后端,VUE前端请求聚合数据成语大全
PS: 聚合接口上描述的是成语大全,其实只是以用户查找字为开头的成语而已.先上演示效果: 1: VUE 前端代码 <template><div class="content ...
最新文章
- pandas笔记(pandas Data Structures)
- 李开复:明年会有一批AI公司倒闭
- VUE2.X组件之间通信的2种方式(针对子组件值变化去改变相应父组件的值)
- CentOS6安装devtoolset(使用高版本gcc)GCC 4.8 GCC 4.9 GCC 5.2
- cpu压测 windows_cpu压测神器:cpuburn深度指南
- 解决faster-rcnn中训练时assert(boxes[:,2]=boxes[:,0]).all()的问题
- python可迭代对象 迭代器生成器_Python可迭代对象、迭代器和生成器
- python大神交流网站_学习Python必去的8个网站
- Java字符与数字的计算
- php model get set方法,创建数据库对象之__set方法
- 数据库读写锁的C++实现
- dockerfile制作docker镜像
- java gui 数独_数独-GUI开发
- MATLAB 棋类小游戏合集(围棋,六子棋,走四棋,九子棋,含禁手五子棋等等)
- 陈强教授《机器学习及R应用》课程 第六章作业
- idea设置project路径_idea 启动界面修改显示的项目存储物理路径,reopen project
- 微信支付-超详细java开发-小程序对接
- 小米10青春版科学计算机,中端新势力 小米10青春版对华为nova 7 SE
- MyBatis-Plus 新增插入成功并获取自增Id(没问题 已实验哟)
- Mysql引擎之MyISAM引擎