前言
一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目。正好由于最近刚好辞职,有了时间可以写写自己感兴趣的东西,所以在此想把自己了解的微服务相关的概念和技术框架使用实现记录在一个完整的工程中,由于本人技术有限,所以错误的地方希望大家指出。\
项目地址:https://github.com/yingpanwang/fordotnet/tree/dev
什么是Api网关
由于微服务把具体的业务分割成单独的服务,所以如果直接将每个服务都与调用者直接,那么维护起来将相当麻烦与头疼,Api网关担任的角色就是整合请求并按照路由规则转发至服务的实例,并且由于所有所有请求都经过网关,那么网关还可以承担一系列宏观的拦截功能,例如安全认证,日志,熔断
为什么需要Api网关
因为Api网关可以提供安全认证,日志,熔断相关的宏观拦截的功能,也可以屏蔽多个下游服务的内部细节
有哪些有名的Api网关项目
- Zuul Spring Cloud 集成
- Kong 一款lua轻量级网关项目
- Ocelot .NETCore网关项目
Ocelot使用
1.通过Nuget安装Ocelot
2.准备并编辑Ocelot配置信息
Ocelot.json
{
"ReRoutes": [
// Auth
{
"UpstreamPathTemplate": "/auth/{action}", // 上游请求路径模板
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ], // 上游请求方法
"ServiceName": "Auth", // 服务名称
"UseServiceDiscovery": true, // 是否使用服务发现
"DownstreamPathTemplate": "/connect/{action}", // 下游匹配路径模板
"DownstreamScheme": "http", // 下游请求
"LoadBalancerOptions": { // 负载均衡配置
"Type": "RoundRobin"
}
//,
// 如果不采用服务发现需要指定下游host
//"DownstreamHostAndPorts": [
// {
// "Host": "10.0.1.10",
// "Port": 5000
// },
// {
// "Host": "10.0.1.11",
// "Port": 5000
// }
//]
}
],
"GlobalConfiguration": { // 全局配置信息
"BaseUrl": "http://localhost:5000", // 请求 baseurl
"ServiceDiscoveryProvider": { //服务发现提供者
"Host": "106.53.199.185",
"Port": 8500,
"Type": "Consul" // 使用Consul
}
}
}
3.添加Ocelot json文件到项目中
4.在网关项目中 StartUp ConfigService中添加Ocelot的服务,在Configure中添加Ocelot的中间件(由于我这里使用了Consul作为服务发现,所以需要添加Consul的依赖的服务AddConsul,如果不需要服务发现的话可以不用添加)
5.将需要发现的服务通过代码在启动时注册到Consul中
我这里自己封装了一个注册服务的扩展(写的比较随意没有在意细节)
appsettings.json 中添加注册服务配置信息
"ServiceOptions": {
"ServiceIP": "localhost",
"ServiceName": "Auth",
"Port": 5800,
"HealthCheckUrl": "/api/health",
"ConsulOptions": {
"Scheme": "http",
"ConsulIP": "localhost",
"Port": 8500
}
}
扩展代码 ConsulExtensions(注意:3.1中 IApplicationLifetime已废弃 所以使用的是IHostApplicationLifetime 作为程序生命周期注入的方式)
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace ForDotNet.Common.Consul.Extensions
{
///
/// 服务配置信息
///
public class ServiceOptions
{
///
/// 服务ip
///
public string ServiceIP { get; set; }
///
/// 服务名称
///
public string ServiceName { get; set; }
///
/// 协议类型http or https
///
public string Scheme { get; set; } = "http";
///
/// 端口
///
public int Port { get; set; }
///
/// 健康检查接口
///
public string HealthCheckUrl { get; set; } = "/api/values";
///
/// 健康检查间隔时间
///
public int HealthCheckIntervalSecond { get; set; } = 10;
///
/// consul配置信息
///
public ConsulOptions ConsulOptions { get; set; }
}
///
/// consul配置信息
///
public class ConsulOptions
{
///
/// consul ip
///
public string ConsulIP { get; set; }
///
/// consul 端口
///
public int Port { get; set; }
///
/// 协议类型http or https
///
public string Scheme { get; set; } = "http";
}
///
/// consul注册客户端信息
///
public class ConsulClientInfo
{
///
/// 注册信息
///
public AgentServiceRegistration RegisterInfo { get; set; }
///
/// consul客户端
///
public ConsulClient Client { get; set; }
}
///
/// consul扩展(通过配置文件配置)
///
public static class ConsulExtensions
{
private static readonly ServiceOptions serviceOptions = new ServiceOptions();
///
/// 添加consul
///
public static void AddConsulServiceDiscovery(this IServiceCollection services)
{
var config = services.BuildServiceProvider().GetService();
config.GetSection("ServiceOptions").Bind(serviceOptions);
//config.Bind(serviceOptions);
if (serviceOptions == null)
{
throw new Exception("获取服务注册信息失败!请检查配置信息是否正确!");
}
Register(services);
}
///
/// 添加consul(通过配置opt对象配置)
///
///
/// 引用生命周期
/// 配置参数
public static void AddConsulServiceDiscovery(this IServiceCollection services, Action options)
{
options.Invoke(serviceOptions);
Register(services);
}
///
/// 注册consul服务发现
///
///
///
public static void UseConsulServiceDiscovery(this IApplicationBuilder app, IHostApplicationLifetime life)
{
var consulClientInfo = app.ApplicationServices.GetRequiredService();
if (consulClientInfo != null)
{
life.ApplicationStarted.Register( () =>
{
consulClientInfo.Client.Agent.ServiceRegister(consulClientInfo.RegisterInfo).Wait();
});
life.ApplicationStopping.Register( () =>
{
consulClientInfo.Client.Agent.ServiceDeregister(consulClientInfo.RegisterInfo.ID).Wait();
});
}
else
{
throw new NullReferenceException("未找到相关consul客户端信息!");
}
}
private static void Register(this IServiceCollection services)
{
if (serviceOptions == null)
{
throw new Exception("获取服务注册信息失败!请检查配置信息是否正确!");
}
if (serviceOptions.ConsulOptions == null)
{
throw new ArgumentNullException("请检查是否配置Consul信息!");
}
string consulAddress = $"{serviceOptions.ConsulOptions.Scheme}://{serviceOptions.ConsulOptions.ConsulIP}:{serviceOptions.ConsulOptions.Port}";
var consulClient = new ConsulClient(opt =>
{
opt.Address = new Uri(consulAddress);
});
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10), // 服务启动多久后注册
Interval = TimeSpan.FromSeconds(serviceOptions.HealthCheckIntervalSecond), // 间隔
HTTP = $"{serviceOptions.Scheme}://{serviceOptions.ServiceIP}:{serviceOptions.Port}{serviceOptions.HealthCheckUrl}",
Timeout = TimeSpan.FromSeconds(10)
};
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceOptions.ServiceName,
Address = serviceOptions.ServiceIP,
Port = serviceOptions.Port,
};
services.AddSingleton(new ConsulClientInfo()
{
Client = consulClient,
RegisterInfo = registration
});
}
}
}
6.启动运行
- 启动consul
- 启动 Auth,Gateway项目
- 通过网关项目访问Auth
启动Consul
为了方便演示这里是以开发者启动的consul
在consul.exe的目录下执行
consul agent -dev -ui // 开发者模式运行带ui
启动 Auth,Gateway项目
通过网关访问Auth
我们这里访问 http://localhost:5000/auth/token 获取token
我们可以看到网关项目接收到了请求并在控制台中打印出以下信息
然后在Auth项目中的控制台中可以看到已经成功接收到了请求并响应