践行微服务(Ocelot+Consul+.NetCore)
先复习一下微服务的基本概念吧~
微服务是一个架构思想,是一种项目建设部署的手段
微服务架构风格是一种将单个应用程序开发为一组小型服务的方法,每个小型服务都在自己的进程中运行并与轻量级机制(通常是Http资源API)进行通信。这些服务围绕业务功能构建,并且可以由全自动部署机制独立部署。有一个集中管理的最低限度这些服务,可以用不同的编程语言和使用不同的数据存储技术
随着公司的发展项目不停迭代,会使得我们的项目变得非常庞大。在单体应用中需要完成业务的所有功能,从项目管理的角度看整个系统的业务混杂度就会随之变高,各业务之间耦合互相引用调用是一件非常让人头疼的事情。所以就需要搬出微服务架构来用了
回到“践行微服务”的主题
先来看一张图了解本文所涉及的大纲(黄色框)
业务逐渐增大,为提升单个服务的性能,我们会对单个服务做集群部署。如图日志服务(s1、s2、s3),如果按单体架构的方式需要去配置这几个实例的链接字符串到配置文件内,但写死的话遇到服务重新部署或新增或减少的话,那又得去改配置文件,显然是不合理的,所以本文会涉及到一个服务注册与发现的角色(consul)来完成这里的服务发现使命, consul做的事情比较少(负载均衡、服务注册与发现、健康检查),这里不负责调用具体的服务。具体的调用会在网关层(Gateway+Ocelot)实现,本文gateway用ocelot来建设,在gateway这一步可以做很多事情(熔断、降级、鉴权等等)。
先完成Consul与(s1、s2、s3)的代码建设
1. 践行Consul: 初始化服务项目
新建一个Webapi项目(这里用的是.Net Core3.1),并在Nuget中引用Consul。
这里把默认的控制器及相关无用的代码删掉了,同时自建一个DataController控制器提供数据服务。
运行测试一下该接口能否获取数据..................
2. 践行Consul:结构图
Consul是个独立的应用,所以需要三连(下载、运行、查看Consul面板)
Consul详情:https://www.consul.io/docs
下载Consul:https://www.consul.io/downloads.html
运行Consul:cmd到consul.exe文件目录下运行 consul agent -dev
检查Consul运行面板:如上图所示,Consult已经运行成功。打开浏览器 http://localhost:8500 (8500是Consul默认端口)
3. 践行Consul:服务注册与发现&健康检查
3.1 服务注册与发现
需要将具体的站点(webapi)注册进Consul内,与Consul建立连接。在项目里加一个Consul辅助类(ConsulHelper.cs),加一个方法用来完成与Consul建立连接的代码,然后再Startup.Configure()方法内调用 Configuration.ConsulRegister(); 代码如下:
public static class ConsultHelper
{
/// <summary>
/// 将当前站点注入到Consul
/// </summary>
/// <param name="configuration"></param>
public static void ConsulRegister(this IConfiguration configuration) {
//初始化Consul服务
ConsulClient client = new ConsulClient(c => {
c.Address = new Uri("http://localhost:8500");
c.Datacenter = "dc1";
});
client.Agent.ServiceRegister(new AgentServiceRegistration {
ID = "service"+Guid.NewGuid(), //注册进Consul后给个唯一id,
Name = "ZdConsulService", //站点做集群时,Name作为该集群的群名,
Address = "127.0.0.1",
Port = 21211,
Tags = null, //这个就是一个随意的参数,文章后面做负载均衡的时候说
});
}
}
运行项目后可查看Consul面板,Serivces列表已经显示了ZdConsulService的服务
3.2 环境变量控制IP与端口
再3.1时ip和端口都是直接写死的,如果需要启动多个实例,那必须将每个ip+端口都置唯一。可以取读取配置文件也可以使用.NetCore提供的命令行参数的方式来完成。使用命令行参数时需在main方法与ConsulRegister方法内加入如下代码块:
//Program.cs
public static void Main(string[] args) { //使用命令行参数
new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddCommandLine(args)
.Build();
CreateHostBuilder(args).Build().Run();
}
//ConsulHelper.cs 读取命令行参数
public static void ConsulRegister(this IConfiguration configuration) {
//初始化Consul服务
ConsulClient client = new ConsulClient(c => {
c.Address = new Uri("http://localhost:8500");
c.Datacenter = "dc1";
});
//站点做集群时ip和端口都会不一样,所以我们通过环境变量来确定当前站点的ip与端口
string ip = configuration["ip"];
int port = int.Parse(configuration["port"]);
client.Agent.ServiceRegister(new AgentServiceRegistration {
ID = "service"+Guid.NewGuid(), //注册进Consul后给个唯一id,
Name = "ZdConsulService", //站点做集群时,Name作为该集群的群名,
Address = ip,
Port = port,
Tags = null, //这个就是一个随意的参数,文章后面做负载均衡的时候说
});
}
接下来用这行命令启动三个实例服务: dotnet Zd.MicroService.WebApi.dll --urls="http://*:21211" --ip="127.0.0.1" --port="21211" ,注意每个实例的端口都需不一制,这里用 21211、21212、21213。运行后查看consul,并检查每个端口下的 /api/data 接口可用。注意下图为运行成功的结果:
3.3 服务实例健康检查
完成服务注册后还需要做Consul的健康检查,在单个站点服务挂掉后需要及时将该服务踢出群聊,避免客户端请求时出现错误。在上个步骤(3.2)的时候可以模拟将21212站点先停掉保留另外两个,此时能用的站点其实只有21211和21213,但在Consul面板上还是可以检测到三个可用的实例。健康检查代码编写: 只需要Webapi在注册进Consul时ServiceRegister内多加一些代码即可,如下代码:
/// <summary>
/// 将当前站点注入到Consul
/// </summary>
/// <param name="configuration"></param>
public static void ConsulRegister(this IConfiguration configuration) {
//初始化Consul服务
ConsulClient client = new ConsulClient(c => {
c.Address = new Uri("http://localhost:8500");
c.Datacenter = "dc1";
});
//站点做集群时ip和端口都会不一样,所以我们通过环境变量来确定当前站点的ip与端口
string ip = configuration["ip"];
int port = int.Parse(configuration["port"]);
client.Agent.ServiceRegister(new AgentServiceRegistration {
ID = "service" + Guid.NewGuid(), //注册进Consul后给个唯一id,
Name = "ZdConsulService", //站点做集群时,Name作为该集群的群名,
Address = ip,
Port = port,
Tags = null, //这个就是一个随意的参数,文章后面做负载均衡的时候说
Check = new AgentServiceCheck {
Interval = TimeSpan.FromSeconds(10),
HTTP = $"http://{ip}:{port}/api/health",
Timeout = TimeSpan.FromSeconds(5),
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5)
}
});
}
Interval: 定时请求一次HTTP指定接口
HTTP: 定时请求本站的接口,如果这个接口能通则代表服务正在正常运行。这个接口直接返回return Ok()就行了
Timeout: 操作这个时间代表请求失败,也就认为该服务挂了
DeregisterCriticalServiceAfter:将该站点下线
编写完代码后,执行步骤:编译一下-->停掉三个实例与consul-->重启consul-->重启三个实例-->查看Consul面板,此时还可以看到三个可用的实例。停掉其中一个站点再查看Consul面板 Timeout后就会发现停掉的站点被打上了不可用标记,重启的话会产生两个相同接口的实例(因为ServiceID不同了),之前停掉的哪个实例也会再次启用
4. 践行Consul: 负载均衡的三种模式
三种模式分别为:均衡策略、轮询策略、权重策略
为了方便演示, 先准备一个web项目,用于通过Consul来获取指定服务后完成调用的动作。暂且将这个项目命名为 Td.MicroService.Client,该项目也需要Nuget安装Consul。同时还需要准备一个包含Get方法的HttpHelper类。核心代码参考下图:
上图演示了通过Consul发现服务实例并调用获取数据的功能,页面通过ViewData["Content"]显示。注意服务实例集lstService
4.1 均衡策略
通过使用Random随机获取实例, 随机从lstService取出一个服务实例service,用service去拼接实际url地址(tagetUrl)
// 均衡策略
var rd = new Random();
int index = rd.Next(0, lstServices.Count());
var service = lstServices.ElementAt(index);
//拼接数据url 并调用
string tagetUrl =
$"{uri.Scheme}://{service.Value.Address}:{service.Value.Port}{uri.PathAndQuery}";
4.2 轮询策略
从lstService逐个取出服务实例,这里会用到一个小轮询算法,定义全局静态int值 ISeed
// 轮询策略
var service = lstServices.ElementAt(index);
4.3 权重策略
注册时为每一个实例配置占比权重。还记得站点在注册时有个被制成null的Tags属性吗,现在要做的就是关闭站点启动时命令行多加一个权重参数,然后Webapi项目注册进Consul时赋值给Tags。之后客户端调用时检查各实例的权重来决定用哪个实例调用,演示代码如下:
// 权重策略 Tags我传的是 new string[]{启动时传进来的权重环境变量}
Dictionary<string, int> weights = lstServices
.Select(t => new KeyValuePair<string, int>(t.Key, Convert.ToInt32(t.Value.Tags[0])))
.ToDictionary(k => k.Key, v => v.Value);
List<KeyValuePair<string, AgentService>> services = new List<KeyValuePair<string, AgentService>>();
for (int i=0; i<weights.Count; i++) {
var item = weights.ElementAt(i);
var itemService = lstServices.First(t => t.Key == item.Key);
//按权重新增加实例个数
for (int j = 0; j <= item.Value; j++) {
services.Add(itemService);
}
}
//通过Random随机获取
var rd = new Random();
int index = rd.Next(0, lstServices.Count());
var service = services.ElementAt(index);
//拼接数据url 并调用
string tagetUrl =
$"{uri.Scheme}://{service.Value.Address}:{service.Value.Port}{uri.PathAndQuery}";
来到网关Gateway(Ocelot)
网关这个节点做限流、熔断、鉴权, 最直观的功能就是避免内部服务地址暴露出外端应用。
Ocelot将Ocelot管道中的最后一件事。它不会调用下一个中间件。有一块中间件将HttpResponseMessage映射到HttpResponse对象,然后将其返回给客户端。
详情参见:https://ocelot.readthedocs.io/en/latest/features/servicediscovery.html#consul
1. 践行Ocelot+Consul: 初始化网关项目
准备一个Webapi项目,然后把那些没有的控制器删除(个人习惯,不删也可)。Nuget需引用两个包Ocelot包与Ocelot.Provider.Consul (Ocelot在16.0.1之后不再支持.NetCore3.1及之前)
2. 践行Ocelot+Consul: ocelot.json配置文件
ocelot有自己的配置文件参数,详情需到官网去查看。以下给出当前解决方案中的实例配置。编写好后需要在Main方法中指定配置文件。
配置文件中 LoadBalancerOptions.Type 为了告诉Ocelot路由要将服务发现提供程序用于其主机和端口,您必须添加在向下游发出请求时希望使用的ServiceName和负载均衡器。目前,Ocelot具有您可以使用的RoundRobin和LeastConnection算法。如果未指定负载均衡器,则Ocelot将不会进行负载均衡请求。
{
"Routes": [
{
"UpstreamPathTemplate": "/consul/{url}", //网关地址--客户端请求地址
"UpstreamHttpMethod": [ "Get", "Post" ], //网关服务类型
"DownstreamPathTemplate": "/api/{url}", //具体服务地址
"DownstreamScheme": "http",
"ServiceName": "ZdConsulService", //实例服务名
"LoadBalancerOptions": {
"Type": "RoundRobin" //轮询 LeastConnection-最少连接数的服务器 NoLoadBalance不负载均衡
},
"UseServiceDiscovery": true //使用服务发现
}
],
"GlobalConfiguration": {
"BaseUrl": "http://127.0.0.1:1222", //网关对外地址,
//指定服务信息
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul" //指定由Consul提供服务实例
}
}
}
3. 践行Ocelot+Consul: 项目大纲
3. 践行Ocelot+Consul: 运行调试
到了这一步,已经完成代码的开发,接下来就是要调试测试了。过程中要特别留意启动顺序。
启动顺序:停止已启动的consul及相关服务实例 --> 生成一下解决方案 --> 启动consul --> 启动三个服务实例 --> 启动OcelotGw
测试调试:调用http://127.0.0.1:1222/consul/data
结尾
源代码已上传至 Gitee
配上几个程序的启动命令(太长了免得手写):
1. consul: consul agent -dev
2. 三个webapi服务实例:
1. dotnet Zd.MicroService.WebApi.dll --urls="http://*:21213" --ip="127.0.0.1" --port="21213"
2. dotnet Zd.MicroService.WebApi.dll --urls="http://*:21212" --ip="127.0.0.1" --port="21212"
3. dotnet Zd.MicroService.WebApi.dll --urls="http://*:21211" --ip="127.0.0.1" --port="21211"
3. OcelotGw:dotnet Zd.MicroService.OcelotGw.dll --urls="http://*:1222" --ip="127.0.0.1" --port="1222"
如有描述错误之处,恭请仁兄指点~
奉上在下qq:744357273