写在前面
API网关是系统内部服务暴露在外部的一个访问入口,类似于代理服务器,就像一个公司的门卫承担着寻址、限制进入、安全检查、位置引导等工作,我们可以形象的用下图来表示: 外部设备需要访问内部系统服务时必须要通过我们的API Gateway,目的是为了隔离内部服务和外部访问来做统一的认证授权,限流熔断,请求聚合,负载均衡,日志记录,监控预警等 通用功能,就像是我们系统的防火墙一样,在任何外部请求访问系统时都必须经过防火墙的验证。
更多关于网关的信息请参考前面的一篇文章《API网关模式》
Ocelot是什么?
Ocelot是基于.NET Core实现的轻量级API网关,它包括的主要功能有:路由、请求聚合、服务发现、认证、授权、限流熔断、并内置了LoadBanalce以及集成了Service Fabric、 Consul、Eureka等功能,这些功能只都只需要简单的配置即可使用。目前腾讯财付通的API Gateway就是基于此做的实现(参考善友兄的这篇文章),下面是详细信息以及Ocelot如何在微软官方示例代码库 eShopContainers中的使用。
Ocelot在腾讯的使用:https://customers.microsoft.com/en-us/story/tencent-telecommunications-dotnetcore
微软官方示例:https://github.com/dotnet-architecture/eShopOnContainers
Ocelot的实现机制
简单的来说它是一堆的asp.net core middleware组成的pipeline,当它拿到请求之后会用一个request builder来构造一个HttpRequestMessage发到下游的真实服务器,等下游的服务 返回response之后再由一个middleware将它返回的HttpResponseMessage映射到HttpResponse上。
代码示例
我在Github上创建了示例代码库仅供参考,我们可以使用下面的步骤来创建示例代码:(示例代码)
1.创建BookingApi: dotnet new -n BookingApi
2.创建PassengerApi: dotnet new -n PassengerApi
3.创建ApiGateway: dotnet new -n ApiGateway
4.添加BookingApi和PassengerApi的实现代码
5.在ApiGateway项目中用Nuget安装Ocelot依赖包
6.添加configuration.json的配置文件
7.配置路由响应规则
8.启动服务并通过Api网关访问服务
启动BookingApi PassengerApi这两个服务,我们可以看到他们分别提供了两个接口
此时再启动我们的Api Gateway项目,通过Gateway来访问我们这两个API
我们可以看到原本我们可以直接访问的两个API现在都可以通过Gateway来访问了,那这一切是怎么做到的呢?
当我们通过Nuget安装Ocelot的依赖之后,我们需要在项目中添加.json的配置文件,在此项目中我们配置文件命名为configuration.json,内容如下:
{ "ReRoutes": [ { "DownstreamPathTemplate": "/api/booking", "UpstreamPathTemplate": "/api/getbooking", "UpstreamHttpMethod": [ "Get" ], "ReRouteIsCaseSensitive": false, "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ], "Key": "booking", "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": true, "Period": "1s", "PeriodTimespan": 15, "Limit": 1 } }, { "DownstreamPathTemplate": "/api/booking/{pnr}", "UpstreamPathTemplate": "/api/getbooking/{pnr}", "UpstreamHttpMethod": [ "Get" ], "ReRouteIsCaseSensitive": false, "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8001 } ] }, { "DownstreamPathTemplate": "/api/passenger", "UpstreamPathTemplate": "/api/getpassenger", "UpstreamHttpMethod": [ "Get" ], "ReRouteIsCaseSensitive": false, "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8002 } ], "Key": "passenger" }, { "DownstreamPathTemplate": "/api/passenger/{id}", "UpstreamPathTemplate": "/api/getpassenger/{id}", "UpstreamHttpMethod": [ "Get" ], "ReRouteIsCaseSensitive": false, "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 8002 } ] } ], "GlobalConfiguration": { "BaseUrl": "https://localhost:5000" }, "Aggregates": [ { "ReRouteKeys": [ "booking", "passenger" ], "UpstreamPathTemplate": "/api/getbookingpassengerinfo" } ] }
API Gateway帮助我们做的事情是当有请求访问网关的时候,我们经过认证授权等一系列操作确保此次访问是被允许的之后便会转发到它实际需要请求服务上去,请求结束之后再由Gateway统一将结果返回给客户端,从模板中我们可以看到UpStreamPathTemplate实际上就是我们上游请求的地址,即网关公开给外部调用的地址(此服务名称和地址我们可以根据需要随便设置更多的是为了对外界隐藏我们真实的服务地址),而实际的调用地址便是DownStreamPathTemplate中给定的实际地址。为了方便大家理解此配置的含义,我查阅了官方资料,将我们上面用到的配置文件做了注解:(更齐全的参数请参考官方文档)
{ "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" //网关基地址 } }
示例代码中我们在Aggregates节点下配置了路由聚合,它将两个请求的结果combine到一起再返回给客户端,当我们请求/api/getbookingpassengerinfo 时就会返回下面结果:
需要注意的是:
- 聚合服务目前只支持返回json
- 目前只支持Get方式请求下游服务
- 任何下游的response header并会被丢弃
- 如果下游服务返回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" ] } ] }
官方也给出了示例:https://github.com/ThreeMammals/Ocelot/tree/develop/samples/OcelotGraphQL
此框架包含的内容比较多,在此并不一一解释,下面将谈谈其他的几个功能:
请求限流
大家注意到我们在上面例子中通过RateLimitOptions节点配置了限流的相关设置,目前我们配置的是1s钟之内只允许对booking api访问一次,否则的话便停止继续转发至下游服务,我们通过测试就会发现当在1s内多次访问的时候,网关便会返回下面的信息:
负载均衡
当我们路由到的下游服务有多个结点的时候,我们可以在DownstreamHostAndPorts中进行配置负载
{ "DownstreamPathTemplate": "/api/posts/{postId}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "10.127.1.10", "Port": 5001, }, { "Host": "10.127.1.11", "Port": 5002, } ], "UpstreamPathTemplate": "/posts/{postId}", "LoadBalancer": "LeastConnection", "UpstreamHttpMethod": [ "Put", "Delete" ] }
LoadBalancer将决定负载均衡的算法,目前支持下面三种方式
- LeastConnection – 将请求发往最空闲的那个服务器
- RoundRobin – 轮流发送
- NoLoadBalance – 总是发往第一个请求或者是服务发现
Prioirty优先级
当我们配置多个请求产生冲突的时候,通过路由设置访问优化级
{ "UpstreamPathTemplate": "/goods/{catchAll}" "Priority": 0 } { "UpstreamPathTemplate": "/goods/delete" "Priority": 1 }
万能模板
如果不希望对请求做任何的处理,则可以使用下面的万能模板:(万能模板的优先级最低,只要有其它的路由模板,其它的路由模板则会优先生效)
{ "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 80, } ], "UpstreamPathTemplate": "/{url}", "UpstreamHttpMethod": [ "Get" ] }
本篇内容就先介绍到这里,后面将会继续探究Ocelot的内部实现。个人感觉现在.NET Core的生态越来越好,越来越多的开发人员开始尝试.NET Core并创建了很多优秀的开源项目,从微软这几年的开源策略我们更多的感受到了微软对于拥抱开源的决心,这也更加的让我们有信心在Core平台上去构建越来越多的优秀项目。如果你对技术有热情可以扫码加入我们的微信群一起探讨。
参考资料:
http://ocelot.readthedocs.io/en/latest/index.html
https://www.cnblogs.com/shanyou/p/7787183.html
Polly:
https://github.com/App-vNext/Polly
Consul:
https://github.com/hashicorp/consul