上一次我们通过一张架构图(.Net Core with 微服务 - 架构图)来讲述了微服务的结构,分层等内容。从现在开始我们开始慢慢搭建一个最简单的微服务架构。这次我们先用几个简单的 web api 项目以及 ocelot 网关项目来演示下网关是如何配置,如何工作的。
Ocelot 网关
Ocelot 是使用 asp.net core 开发的一个 api 网关项目。它功能丰富,集成了路由、限流、缓存、聚合等功能。它使用 .net 编写,本质上就是一堆 asp.net core 的中间件,所以它天生对 .net 友好。这些中间件拦截外部的请求,根据路由配置转发到对应的内部服务上,再把内部的返回结果对外暴露。
搭建项目结构
新建一个解决方案,新建几个项目。
- api_gateway API网关
- hotel_base 酒店基本信息服务
- member_center 会员中心服务
- ordering 订单服务
安装 Ocelot
在API网关项目上使用nuget安装Ocelot的类库。Ocelot本质上就是一堆 asp.net Core 的 middleware。所以我们需要在UseOcelot扩展方法在注册这些中间件。
Install-Package Ocelot
public static void Main(string[] args)
{
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile("routes.json")
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddOcelot();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}
}
在 main 函数内注册Ocelot的中间件,服务,使用AddJsonFile指定路由的配置文件。
路由
Ocelot最基本的功能就是反向代理。代理的配置通过一个json文件来配置。下面让我们来简单的演示下如何配置。
以下是通过网关代理访问酒店服务的酒店列表的配置示例。
{
//获取酒店列表
"UpstreamPathTemplate": "/api/hotel",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/hotel",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
]
}
配置主要是分为Upstream跟Downstream两部分。Upstream其实就是指代ocelot网关本身。Downstream代表真正的服务。
- UpstreamPathTemplate 网关匹配的路径
- UpstreamHttpMethod 网关匹配的请求方法
- DownstreamPathTemplate 服务匹配的路径
- DownstreamScheme 服务的Scheme,http、https
- DownstreamHostAndPorts 服务的主机地址跟端口
上面的配置描述的意思是:把对网关的/api/hotel的GET请求转发到主机http://localhost:6003/hotel接口上。
路由参数
Ocelot的path模板可以使用{param}模式来匹配参数,然后传递到下游服务器上。
{
//获取单个酒店
"UpstreamPathTemplate": "/api/hotel/{hotel_id}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/hotel/{hotel_id}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
],
"Key": "hotel_base_info",
}
使用{hotel_id}匹配hotelId参数。
{
//获取酒店房间列表
"UpstreamPathTemplate": "/api/hotel_rooms/{hotel_id}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/room/hotel_rooms/{hotel_id}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//hotel service
"Host": "localhost",
"Port": 6003
}
],
"Key": "hotel_rooms"
}
使用{hotel_id}匹配hotelId参数。
{
//获取查询订单
"UpstreamPathTemplate": "/api/order/query?day={day}",
"UpstreamHttpMethod": [ "Get" ],
"DownstreamPathTemplate": "/order/get_orders?day={day}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
//order service
"Host": "localhost",
"Port": 6001
}
]
}
在QueryString上使用{day}匹配参数。
限流
Ocelot支持对请求的限流操作。
"RateLimitOptions": {
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 1,
"Limit": 1
}
在路由配置节点添加RateLimitOptions节点。
- EnableRateLimiting = true 开启限流
- Period = 1s 限流的时间区间为1s
- PeriodTimespan = 1 限流后重置时间
- Limit = 1 限制请求的数量
上面的配置的意思是1秒内限制一次请求,1秒后重置这个限制。
缓存
Ocelot可以对请求的响应值提供缓存服务。
//缓存5s
"FileCacheOptions": { "TtlSeconds": 5 }
在路由配置节点上配置FileCacheOptions字段,TtlSeconds代表需要缓存的时间,单位是秒。
聚合
上一回我们讲微服务架构的时候说到“聚合服务层”,我们说这一层的主要功能是对请求进行聚合适配跟裁剪。其实ocelot已经提供了简单的api聚合功能。如果聚合的需求比较简单,那么可以使用ocelot直接实现。
简单聚合
简单聚合可以通过配置把几个请求的聚合成一个请求,一次性返回几个请求的响应。响应通过json格式被包装返回。
"Aggregates": [
{
//聚合 查询酒店信息跟酒店房间列表
"RouteKeys": [
"hotel_base_info",
"hotel_rooms"
],
"UpstreamPathTemplate": "/api/hotel_detail/{hotel_id}"
},
]
RouteKeys 代表需要聚合的请求的键值。
使用代码聚合
上面我们直接通过配置实现了api之间聚合请求。这种聚合比较简单,会把聚合的几个请求的响应值原封不动的返回回来。有的时候我们需要对返回值做一些转换或者裁剪,比如同一个api我们对移动端的响应可能需要裁剪掉部分字段。这种需求在ocelot内我们可以使用代码来完成。
这里不太推荐这种聚合方式,这会造成网关跟下游服务的强耦合关系。
这里我们演示下如何把获取酒店信息跟酒店房间列表的返回值进行裁剪,并返回一个新的响应。
public class HotelDetailInfoForMobileAggregator : IDefinedAggregator
{
public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses)
{
dynamic hotelInfo = new ExpandoObject();
List<dynamic> rooms = new List<dynamic>();
foreach (var context in responses)
{
if ((context.Items["DownstreamRoute"] as dynamic).Key == "hotel_base_info")
{
var respContent = await context.Items.DownstreamResponse().Content.ReadAsStringAsync();
hotelInfo = JsonConvert.DeserializeObject<dynamic>(respContent);
}
if ((context.Items["DownstreamRoute"] as dynamic).Key == "hotel_rooms")
{
var respContent = await context.Items.DownstreamResponse().Content.ReadAsStringAsync();
rooms = JsonConvert.DeserializeObject<List<dynamic>>(respContent);
}
}
dynamic newResponse = new ExpandoObject();
newResponse.hotel = new {
hotelInfo.id,
hotelInfo.name
};
newResponse.rooms = rooms.Select(x => new {
x.id,
x.no
});
var stringContent = new StringContent(JsonConvert.SerializeObject(newResponse));
return new DownstreamResponse(
stringContent,
System.Net.HttpStatusCode.OK,
responses.SelectMany(x => x.Items.DownstreamResponse().Headers).ToList(),
"OK");
}
}
每一个聚合都需要继承IDefinedAggregator这个接口然后实现Aggregate方法。在这个方法内对每个请求的响应值进行裁剪,然后重新组合。
{
//聚合 查询酒店信息跟酒店房间列表 移动端 裁剪
"RouteKeys": [
"hotel_base_info",
"hotel_rooms"
],
"UpstreamPathTemplate": "/api/m/hotel_detail/{hotel_id}",
"Aggregator": "HotelDetailInfoForMobileAggregator"
}
在配置文件的Aggregates内添加一个配置节点在“Aggregator”字段上指定Aggregator的类名。
.ConfigureServices(s => {
s.AddOcelot()
.AddTransientDefinedAggregator<HotelDetailInfoForMobileAggregator>();
})
同时在ConfigureServices方法内配置HotelDetailInfoForMobileAggregator的依赖注入。
总结
本次我们通过几个最简单的web api项目,演示了如何使用 ocelot 网关进行反向代理,限流,聚合等常用功能。可以看到 ocelot 的配置使用还是比较简单的。因为是 .net 代码编写,所以对.net 开发者比较友好,我们可以直接使用 .net 代码来编写一些功能,比如直接使用代码来聚合请求的结果。
相关文章
NET Core with 微服务 - 什么是微服务
.Net Core with 微服务 - 架构图
演示代码
https://github.com/kklldog/myhotel_microservice