目录
限流
熔断
缓存
Header 转化
HTTP 方法转换
负载均衡
注入 / 重写中间件
后台管理
最后
在上篇. Net 微服务实践 (三)[网关]:Ocelot 配置路由和请求聚合中我们介绍了 Ocelot 的配置, 主要特性路由以及服务聚合. 接下来, 我们会介绍 Ocelot 的限流, 熔断, 缓存以及负载均衡.
限流
我们先来看限流的配置
Reroute 节点中的配置如下:
- {
- "DownstreamPathTemplate": "/api/orders",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- }
- ],
- "UpstreamPathTemplate": "/api/orders",
- "UpstreamHttpMethod": [ "Get" ],
- "RateLimitOptions": {
- "ClientWhitelist": [],
- "EnableRateLimiting": true,
- "Period": "10m",
- "PeriodTimespan": 3,
- "Limit": 1
- }
- }
GlobalConfiguration 中的配置如下:
- "GlobalConfiguration": {
- "BaseUrl": "http://localhost:5000",
- // 限流
- "RateLimitOptions": {
- "QuotaExceededMessage": "您的请求量超过了配额 1/10 分钟",
- "HttpStatusCode": 999
- }
- }
配置说明
在 Reroute 和 GlobalConfiguration 节点中添加了 RateLimitOptions 节点
ClientWhitelist - 白名单, 也就是不受限流控制的客户端
EnableRateLimiting - 是否开启限流
Period & Limit - 在一段时间内允许的请求次数
PeriodTimespan - 客户端的重试间隔数, 也就是客户端间隔多长时间可以重试
QuotaExceededMessage - 限流以后的提示信息
HttpStatusCode - 超出配额时, 返回的 http 状态码
示例说明
客户端在 10 分钟之内只允许请求一次 http://localhost:5000/API/orders, 在请求之后 3 秒钟之后可以重试
验证
修改配置, 运行示例程序,
访问 http://localhost:5000/API/orders, 第一次可以正常获取返回结果, 再次访时, 显示 " 您的请求量超过了配额 1/10 分钟, 并且 response 状态码是 999
PeriodTimespan 的验证
修改 Period 为 1s, 修改 PeriodTimespan 为 10, 这样当前的配置是 1 秒中允许一个请求, 10 秒后才能重试. 再次运行示例程序.
访问 http://localhost:5000/API/orders, 第一次可以正常获取返回结果, 等待两秒, 再次访问, 大家想一下, 这个时候, 会不会返回正常结果 (已经过了两秒). 这时还是返回 999, 为什么? 因为尽管配额上是允许的, 但是因为配置是客户端 10 秒以后才能重试, 而这时只等待了 2 秒, 所以还是返回 999.
熔断
Ocelot 的熔断使用了 Polly 来实现, 在 OcelotGateway 项目添加 Polly 包
注入 Polly
- services
- .AddOcelot()
- .AddPolly();
修改配置
- {
- "DownstreamPathTemplate": "/api/orders",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- }
- ],
- "UpstreamPathTemplate": "/api/orders",
- "UpstreamHttpMethod": [ "Get" ],
- "QoSOptions": {
- "ExceptionsAllowedBeforeBreaking": 2,
- "DurationOfBreak": 5000,
- "TimeoutValue": 2000
- }
- }
配置说明
在 Reroute 节点中添加了 QoSOptions 节点
ExceptionsAllowedBeforeBreaking - 在熔断之前允许的异常次数
DurationOfBreak - 熔断时长 , 单位毫秒
TimeoutValue - 请求超时设置, 单位毫秒
示例说明
当访问 http://localhost:5000/API/orders 出现 2 次异常后, 服务熔断 5 秒, 如果服务响应超过 2 秒, 也触发熔断条件
验证
场景一: 服务宕机
修改配置, 只启动网关, 不启动 oder API, 访问 http://localhost:5000/API/orders, 第一次有响应耗时, 返回 500, 第二次也有响应耗时, 返回 500. 第三次则快速返回 503 Service Unavalible, 服务熔断了.
场景二: 超时
修改配置
修改 API/orders 代码, 等待 3 秒
- // GET: API/orders
- [Route("api/orders")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- Task.Delay(3000).Wait();
- return new string[] { "刘明的订单", "王天的订单" };
- }
启动网关, 启动 order-API, 访问 http://localhost:5000/API/orders, 返回 503
场景三: 服务正常响应, 但是服务 500 内部错误
修改配置
修改 API/orders 代码, 抛出异常
- // GET: API/orders
- [Route("api/orders")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- throw new Exception("获取所有订单出错");
- }
启动网关, 启动 order-API, 访问 http://localhost:5000/API/orders, 不触发熔断
缓存
缓存使用了 CacheManageer 来实现, 添加 CacheManager 包
Install-Package Ocelot.Cache.CacheManager
注入缓存组件
- services.AddOcelot()
- .AddCacheManager(x =>
- {
- x.WithDictionaryHandle();
- });
Ocelot.JSON 配置文件修改
- {
- "DownstreamPathTemplate": "/api/orders",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- }
- ],
- "UpstreamPathTemplate": "/api/orders",
- "UpstreamHttpMethod": [ "Get" ],
- "FileCacheOptions": {
- "TtlSeconds": 60,
- "Region": "orders"
- }
- }
缓存是根据 downstream service 的 URL 来缓存的
配置说明
在 Reroute 节点中添加了 FileCacheOptions 节点
TtlSeconds - 缓存有效期, 单位是秒
Region - 缓存分区, 可以通过调用后台 API 来清空一个 region 下的缓存
示例说明
当访问 http://localhost:5000/API/orders 后, 结果会缓存 60 秒, 在缓存有效期内即使原始的 order API 的返回结果发生变化, 通过网关请求时, 还是会返回缓存的结果.
验证
修改配置, 只启动网关, 启动 oder API, 访问 http://localhost:5000/API/orders, 返回的结果如下
"刘明的订单", "王天的订单"
修改 API/orders, 重新启动 API/orders
- [Route("api/orders")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- return new string[] { "帅的订单", "我的订单" };
- }
再次访问 http://localhost:5000/API/orders, 这个时候是会返回什么结果呢?, 验证显示还是返回
"刘明的订单", "王天的订单"
因为结果被缓存了
等待 2 分钟, 再访问 http://localhost:5000/API/orders, 这个时候是会返回什么结果呢?, 验证显示返回了新的结果
"帅的订单", "我的订单"
因为缓存有效期已经过了
Header 转化
Ocelot 允许在上游服务的 request 和下游服务的 response 的 header 中添加, 替换信息
配置如下:
- {
- "DownstreamPathTemplate": "/api/shopping-carts",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- }
- ],
- "DownstreamHeaderTransform": {
- "devops": "rdc"
- },
- "UpstreamPathTemplate": "/api/shopping-carts",
- "UpstreamHttpMethod": [ "Get" ],
- "UpstreamHeaderTransform": {
- "lakin": "rdc",
- "CI": "msbuild, jenkins",
- "Location": "http://localhost:5001, {BaseUrl}"
- }
- }
配置说明
在 Reroute 节点中添加了 DownstreamHeaderTransform 节点和 UpstreamHeaderTransform 节点
- "DownstreamHeaderTransform": {
- "devops": "rdc"
- }
说明: 在下游服务的 response 中添加一个 header, key 是 devops, value 是 rdc
- "UpstreamHeaderTransform": {
- "lakin": "rdc",
- "CI": "msbuild, jenkins",
- "Location": "http://localhost:5001, {BaseUrl}"
- }
添加 header 信息
在上游服务的 request 中添加一个 header, key 是 lakin, value 是 rdc
替换 header 信息
在上游服务的 request 中, 将 key 是 CI 的 header, 其值由 msbuild 替换为 jenkins
替换时使用 placeholder
在上游服务的 request 中, 将 key 是 Location 的 header, 其值由 http://localhost:5001 替换为 {BaseUrl} placehokder
示例说明
当访问 http://localhost:5000/API/orders 后, 结果会缓存 60 秒, 在缓存有效期内即使原始的 order API 的返回结果发生变化, 通过网关请求时, 还是会返回缓存的结果.
验证
修改 Order Service, 添加一个 ShoppingCart API, 代码如下
- // GET: API/shopping-carts
- [Route("api/shopping-carts")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- Console.WriteLine($"开始打印 header 信息");
- foreach (var item in this.Request.Headers)
- {
- Console.WriteLine($"{item.Key} - {item.Value}");
- }
- Console.WriteLine($"打印 header 信息完成");
- return new string[] { "洗发水", "无人机" };
- }
启动网关, 启动 Order Service
使用 Postman 调用 http://localhost:5000/API/shopping-carts, 在 request 中添加两个 header 项
- "CI": "msbuild",
- "Location": "http://localhost:5001"
发起请求
在 Order Service 的控制台, 可以看到如下输出, 添加一个 header 项, 替换两个 header 项的值
开始打印 header 信息 CI
- lakin - rdc
- CI - jenkins
- Location - http://localhost:5000
打印 header 信息完成
检查 Postman 中 response 的 header 信息, 会发现添加了如下的 header 项
devops - rdc
HTTP 方法转换
Ocelot 允许在路由时转化 HTTP 方法
- {
- "DownstreamPathTemplate": "/api/shopping-carts",
- "DownstreamScheme": "http",
- "DownstreamHttpMethod": "POST",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- }
- ],
- "UpstreamPathTemplate": "/api/shopping-carts",
- "UpstreamHttpMethod": [ "Get" ]
- }
示例说明
上述示例中, 将 GET /API/shopping-carts 路由到 POST /API/shopping-carts, 将 GET 转换成了 POST
适用场景: 例如有些已经存在的的 API, 因为某些历史原因都是用 POST, 在通过网关对外提供服务时, 就可以按照标准 API 进行转换
验证
当前 GET /API/shopping-carts 返回如下结果
"洗发水", "无人机"
我们在 ShoppingCartController 中添加一个新的 POST API
- [Route("api/shopping-carts")]
- [HttpPost]
- public string Post()
- {
- return "添加商品到购物车成功";
- }
运行网关和 Order Service
访问 GET http://localhost:5000/API/shopping-carts , 返回的结果如下, 已经将 GET 转换成了 POST
添加商品到购物车成功
负载均衡
Ocelot 内置了负载均衡, 我们先来看配置
- {
- "DownstreamPathTemplate": "/api/orders",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5001
- },
- {
- "Host": "localhost",
- "Port": 6001
- }
- ],
- "UpstreamPathTemplate": "/api/orders",
- "UpstreamHttpMethod": [ "Get" ],
- "LoadBalancerOptions": {
- "Type": "RoundRobin"
- }
- }
配置说明
在 DownstreamHostAndPorts 指指定多个服务地址
在 Reroute 节点中添加 LoadBalancerOptions, 这是负载均衡的配置节点, 其中 Type 属性指定了负载均衡的算法, 它有如下几个值:
LeastConnection - 将请求发往最空闲的那个服务器
RoundRobin - 轮流发送
NoLoadBalance - 总是发往第一个请求 (如果配置了服务发现, 则总是发往发现的第一个服务地址)
验证
启动网关
修改 API/orders 的代码, 并启动 Order Service
- [Route("api/orders")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- return new string[] { "刘明的订单", "王天的订单" };
- }
拷贝一份 Order Service 的副本, 修改 API/orders 的代码, 修改副本的启动端口为 6001, 并启动 Order Service
- [Route("api/orders")]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- return new string[] { "帅的订单", "我的订单" };
- }
在 Postman 中调用 GET http://localhost:5000/API/orders
第一次的结果是
"刘明的订单", "王天的订单"
第二次的结果是
"帅的订单", "我的订单"
第三次的结果是
"刘明的订单", "王天的订单"
注入 / 重写中间件
Ocelot 本身是一组中间件, 它也提供了方式来注入和重写其中的某些中间件:
PreErrorResponderMiddleware - 在所有 Ocelot 中间件之前运行, 允许用户在 Ocelot 管道运行之前和运行之后提供任何行为.
PreAuthenticationMiddleware - 提供预身份认证逻辑, 在身份认证中间件之前运行
AuthenticationMiddleware - 覆写 Ocelot 中间件提供的身份认证逻辑
PreAuthorisationMiddleware - 提供预授权逻辑, 在授权中间件之前运行
AuthorisationMiddleware - 覆写 Ocelot 中间件提供的授权逻辑
PreQueryStringBuilderMiddleware - 在 QueryString 转换之前, 提供预处理逻辑
下面是注入 PreErrorResponderMiddleware 中间件的代码示例:
- // 注入中间件
- var configuration = new OcelotPipelineConfiguration
- {
- PreErrorResponderMiddleware = async (ctx, next) =>
- {
- ctx.HttpContext.Request.Headers.Add("myreq", "ocelot-request");
- await next.Invoke();
- }
- };
- App.UseOcelot(configuration).Wait();
注意: Ocelot 也是一组中间件, 所以可以在 Ocelot 中间件之前, 按常规方式添加任何中间件, 但是不能在 Ocelot 中间件之后添加, 因为 Ocelot 没有调用 next
后台管理
Ocelot 提供了一组后台管理的 API, 从前三篇文章可以看出, Ocelot 主要也就是配置文件的管理, 所以 API 主要也就是管理配置
获取管理后台的 token
POST {adminPath}/connect/token
获取配置
GET {adminPath}/configuration
创建 / 修改配置
POST {adminPath}/configuration
删除缓存
DELETE {adminPath}/outputcache/{region}
最后
本篇我们介绍了 Ocelot 的限流, 熔断, 缓存, 负载均衡以及其他一些特性. 到目前为止, Ocelot 的基本配置和功能都已经介绍完了. 接下里我们会结合 consul 来介绍服务发现, 以及 Ocelot 和 Consul 的集成.
示例代码下载地址: https://github.com/lcyhjx/ocelot-demo/tree/master
来源: https://www.cnblogs.com/lcyhjx/p/12687152.html