API网关

  API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口。这样就可以明显的简化客户端实现和微服务应用程序之间的沟通方式。以前的话,客户端不得不去请求微服务A,然后再到微服务B,然后是微服务C。客户端需要去知道怎么去一起来消费这三个不同的service。使用API网关,我们可以抽象所有这些复杂性,并创建客户端们可以使用的优化后的端点,并向那些模块们发出请求。API网关的核心要点是:所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能(比如验证、鉴权、监控、限流、请求合并...)

Ocelot

  Ocelot是一个使用.NET Core平台上的一个API Gateway,这个项目的目标是在.NET上面运行微服务架构。它功能强大,包括了:路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器与Service Fabric、Butterfly Tracing集成,还引入了Polly来进行故障处理。

Polly

  olly是一种.NET弹性和瞬态故障处理库,允许我们以非常顺畅和线程安全的方式来执诸如行重试,断路,超时,故障恢复等策略。

  重试策略(Retry)
    重试策略针对的前置条件是短暂的故障延迟且在短暂的延迟之后能够自我纠正。允许我们做的是能够自动配置重试机制。

  断路器(Circuit-breaker)
    断路器策略针对的前置条件是当系统繁忙时,快速响应失败总比让用户一直等待更好。保护系统故障免受过载,Polly可以帮其恢复。

  超时(Timeout)
    超时策略针对的前置条件是超过一定的等待时间,想要得到成功的结果是不可能的,保证调用者不必等待超时。

  隔板隔离(Bulkhead Isolation)

    隔板隔离针对的前置条件是当进程出现故障时,多个失败一直在主机中对资源(例如线程/ CPU)一直占用。下游系统故障也可能导致上游失败。这两个风险都将造成严重的后果。都说一粒老鼠子屎搅浑一锅粥,而Polly则将受管制的操作限制在固定的资源池中,免其他资源受其影响。

  缓存(Cache)
    缓存策略针对的前置条件是数据不会很频繁的进行更新,为了避免系统过载,首次加载数据时将响应数据进行缓存,如果缓存中存在则直接从缓存中读取。

  回退(Fallback)
    操作仍然会失败,也就是说当发生这样的事情时我们打算做什么。也就是说定义失败返回操作。

  策略包装(PolicyWrap)

    策略包装针对的前置条件是不同的故障需要不同的策略,也就意味着弹性灵活使用组合

Consul

  Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案、在Ocelot已经支持简单的负载功能,也就是当下游服务存在多个结点的时候,Ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动在配置文件里面添加完成。这不够灵活并且在一定程度下会有风险。这个时候我们就可以用Consul来做服务发现并实现健康检查。

微服务搭建

  一、设计思路

  二、安装consul服务

    文件结构信息

      data

      conf

      consul.exe

    配置conf.json信息 并运行 :consul agent -config-dir="E:/PersonCode/.Net Core/consul/conf/conf.json"

{
"datacenter": "wf",
"data_dir": "E:/PersonCode/.Net Core/consul/data",
"log_level": "INFO",
"server": true,
"ui": true,
"bind_addr": "192.168.14.8",
"client_addr": "127.0.0.1",
"advertise_addr": "192.168.14.8",
"bootstrap_expect": 1,
"ports":{
"http": 8500,
"dns": 8600,
"server": 8300,
"serf_lan": 8301,
"serf_wan": 8302
}
}

运行结果:

  三、创建多个API服务 并注册consul

     1、创建 api项目 添加swagger Ui

     2、引用Consul-1.6.1.1版本

     3、添加Consul 服务配置

    "Consul": {
      "ServiceName": "Zfkr.WF.Core.API",
      "ServiceIP": "localhost",
      "ConsulClientUrl": "http://localhost:8500",
      "HealthCheckRelativeUrl": "/wf/Base/health",
      "HealthCheckIntervalInSecond": 5
    }

4、添加Consul 服务注册类(RegisterCansulExtension)

    5、添加服务注册与中间件

   6、启用api 多个服务

      dotnet Zfkr.WF.Core.API.dll --urls=http://*:8001

      dotnet Zfkr.WF.Core.API.dll --urls=http://*:8002

      dotnet Zfkr.WF.Core.API.dll --urls=http://*:8003

public static class RegisterCansulExtension
{
public static void RegisterToConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStarted.Register(() =>
{
string serviceName = configuration.GetValue<string>("Consul:ServiceName");
string serviceIP = configuration.GetValue<string>("Consul:ServiceIP");
string consulClientUrl = configuration.GetValue<string>("Consul:ConsulClientUrl");
string healthCheckRelativeUrl = configuration.GetValue<string>("Consul:HealthCheckRelativeUrl");
int healthCheckIntervalInSecond = configuration.GetValue<int>("Consul:HealthCheckIntervalInSecond");

ICollection<string> listenUrls = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;

if (string.IsNullOrWhiteSpace(serviceName))
{
throw new Exception("Please use --serviceName=yourServiceName to set serviceName");
}
if (string.IsNullOrEmpty(consulClientUrl))
{
consulClientUrl = "http://127.0.0.1:8500";
}
if (string.IsNullOrWhiteSpace(healthCheckRelativeUrl))
{
healthCheckRelativeUrl = "health";
}
healthCheckRelativeUrl = healthCheckRelativeUrl.TrimStart('/');
if (healthCheckIntervalInSecond <= 0)
{
healthCheckIntervalInSecond = 1;
}

string protocol;
int servicePort = 0;
if (!TryGetServiceUrl(listenUrls, out protocol, ref serviceIP, out servicePort, out var errorMsg))
{
throw new Exception(errorMsg);
}

var consulClient = new ConsulClient(ConsulClientConfiguration => ConsulClientConfiguration.Address = new Uri(consulClientUrl));

var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(healthCheckIntervalInSecond),
HTTP = $"{protocol}://{serviceIP}:{servicePort}/{healthCheckRelativeUrl}",
Timeout = TimeSpan.FromSeconds(2)
};

// 生成注册请求
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceName,
Address = serviceIP,
Port = servicePort,
Meta = new Dictionary<string, string>() { ["Protocol"] = protocol },
Tags = new[] { $"{protocol}" }
};
consulClient.Agent.ServiceRegister(registration).Wait();

//服务停止时, 主动发出注销
lifetime.ApplicationStopping.Register(() =>
{
try
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
}
catch
{ }
});
});
}

private static bool TryGetServiceUrl(ICollection<string> listenUrls, out string protocol, ref string serviceIP, out int port, out string errorMsg)
{
protocol = null;
port = 0;
errorMsg = null;
if (!string.IsNullOrWhiteSpace(serviceIP)) // 如果提供了对外服务的IP, 只需要检测是否在listenUrls里面即可
{
foreach (var listenUrl in listenUrls)
{
Uri uri = new Uri(listenUrl);
protocol = uri.Scheme;
var ipAddress = uri.Host;
port = uri.Port;

if (ipAddress == serviceIP || ipAddress == "0.0.0.0" || ipAddress == "[::]")
{
return true;
}
}
errorMsg = $"The serviceIP that you provide is not in urls={string.Join(',', listenUrls)}";
return false;
}
else // 没有提供对外服务的IP, 需要查找本机所有的可用IP, 看看有没有在 listenUrls 里面的
{
var allIPAddressOfCurrentMachine = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
// 这里排除了 127.0.0.1 loopback 地址
.Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
.Select(p => p.Address.ToString()).ToArray();
var uris = listenUrls.Select(listenUrl => new Uri(listenUrl)).ToArray();
// 本机所有可用IP与listenUrls进行匹配, 如果listenUrl是"0.0.0.0"或"[::]", 则任意IP都符合匹配
var matches = allIPAddressOfCurrentMachine.SelectMany(ip =>
uris.Where(uri => ip == uri.Host || uri.Host == "0.0.0.0" || uri.Host == "[::]")
.Select(uri => new { Protocol = uri.Scheme, ServiceIP = ip, Port = uri.Port })
).ToList();

if (matches.Count == 0)
{
errorMsg = $"This machine has IP address=[{string.Join(',', allIPAddressOfCurrentMachine)}], urls={string.Join(',', listenUrls)}, none match.";
return false;
}
else if (matches.Count == 1)
{
protocol = matches[0].Protocol;
serviceIP = matches[0].ServiceIP;
port = matches[0].Port;
return true;
}
else
{
errorMsg = $"Please use --serviceIP=yourChosenIP to specify one IP, which one provide service: {string.Join(",", matches)}.";
return false;
}
}
}

   四、创建API网关

    1、创建.core Api 项目 并引用相关包文件

       Consul  1.6.11

       Ocelot   16.0.1

       Ocelot.Provider.Consul 16.0.1

    2、添加ocelot.json文件 并配置

    注意:"ServiceName": "Zfkr.WF.Core.API", 服务名称必须与第三步中的 注册服务时的 服务名称一致,若未注册服务 可配置DownstreamHostAndPorts实现负载,之所以要使用Consul 是因为Ocelot自身 没有无法 实现健康检查  服务自动添加与移除

{

"Routes": [
/*Swagger 网关*/
{
"DownstreamPathTemplate": "/swagger/WorkFlow/swagger.json",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8001
}
],
"LoadBalancer": "RoundRobin",
"UpstreamPathTemplate": "/WorkFlow/swagger/WorkFlow/swagger.json",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
},
//{
// "UseServiceDiscovery": true, // 使用服务发现
// "DownstreamPathTemplate": "/swagger/TaskSchedul/swagger.json",
// "DownstreamScheme": "http",
// "DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8002
// }
// ],
// "LoadBalancer": "RoundRobin",
// "UpstreamPathTemplate": "/TaskSchedul/swagger/TaskSchedul/swagger.json",
// "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
//},
/*Api网关*/
{
"UseServiceDiscovery": true, //启用服务发现,若Ocelot集合Consul必须配置此项
"DownstreamPathTemplate": "/WF/{url}",
"DownstreamScheme": "http",
//"DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8001
// },
// {
// "Host": "localhost",
// "Port": 8003
// }
//],
"UpstreamPathTemplate": "/WF/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "Zfkr.WF.Core.API", //服务名称
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
//,
//{
// "DownstreamPathTemplate": "/TS/{url}",
// "DownstreamScheme": "http",
// "DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 8002
// }
// ],
// "ServiceName": "node-2", // 服务名称
// "UseServiceDiscovery": true,
// "UpstreamPathTemplate": "/TS/{url}",
// "UpstreamHttpMethod": [ "Get", "Post" ],
// "LoadBalancerOptions": {
// "Type": "LeastConnection"
// }
//}
],

"GlobalConfiguration": {
"BaseUrl": "http://localhost:8899", //网关对外地址
"ReRouteIsCaseSensitive": false, //是否区分路由字母大小写
"ServiceDiscoveryProvider": { //服务发现提供者,配置Consul地址
"Host": "localhost", //Consul主机名称
"Port": 8500, //Consul端口号
"Type": "Consul" //必须指定Consul服务发现类型
}
//,
//"限流相关配置"
//"RateLimitOptions": {
// "ClientIdHeader": "ClientId",
// "QuotaExceededMessage": "RateLimit SCscHero", //限流响应提示
// "RateLimitCounterPrefix": "ocelot",
// "DisableRateLimitHeaders": false,
// "HttpStatusCode": 429
//}
}
}

  3、添加Consul注册类 并注册到中间件,添加健康检查服务

    3.1、添加appsettings 配置信息

    "Consul": {
      "ServiceName": "Gateway-9988-Service",
      "Datacenter": "wf",
      "ServiceIP": "localhost",
      "ConsulClientUrl": "http://localhost:8500",
      "HealthCheckRelativeUrl": "/wf/Base/health",
      "HealthCheckIntervalInSecond": 5
    }

    3.2、添加Consul注册类

public static class RegisterCansulExtension
{
public static void RegisterToConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStarted.Register(() =>
{
string serviceName = configuration.GetValue<string>("Consul:ServiceName");
string serviceIP = configuration.GetValue<string>("Consul:ServiceIP");
string consulClientUrl = configuration.GetValue<string>("Consul:ConsulClientUrl");
string healthCheckRelativeUrl = configuration.GetValue<string>("Consul:HealthCheckRelativeUrl");
int healthCheckIntervalInSecond = configuration.GetValue<int>("Consul:HealthCheckIntervalInSecond");

ICollection<string> listenUrls = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;

if (string.IsNullOrWhiteSpace(serviceName))
{
throw new Exception("Please use --serviceName=yourServiceName to set serviceName");
}
if (string.IsNullOrEmpty(consulClientUrl))
{
consulClientUrl = "http://127.0.0.1:8500";
}
if (string.IsNullOrWhiteSpace(healthCheckRelativeUrl))
{
healthCheckRelativeUrl = "health";
}
healthCheckRelativeUrl = healthCheckRelativeUrl.TrimStart('/');
if (healthCheckIntervalInSecond <= 0)
{
healthCheckIntervalInSecond = 1;
}

string protocol;
int servicePort = 0;
if (!TryGetServiceUrl(listenUrls, out protocol, ref serviceIP, out servicePort, out var errorMsg))
{
throw new Exception(errorMsg);
}

var consulClient = new ConsulClient(ConsulClientConfiguration => ConsulClientConfiguration.Address = new Uri(consulClientUrl));

var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10),//服务启动多久后注册
Interval = TimeSpan.FromSeconds(healthCheckIntervalInSecond),
HTTP = $"{protocol}://{serviceIP}:{servicePort}/{healthCheckRelativeUrl}",
Timeout = TimeSpan.FromSeconds(2)
};

// 生成注册请求
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceName,
Address = serviceIP,
Port = servicePort,
Meta = new Dictionary<string, string>() { ["Protocol"] = protocol },
Tags = new[] { $"{protocol}" }
};
consulClient.Agent.ServiceRegister(registration).Wait();

//服务停止时, 主动发出注销
lifetime.ApplicationStopping.Register(() =>
{
try
{
consulClient.Agent.ServiceDeregister(registration.ID).Wait();
}
catch
{ }
});
});
}

private static bool TryGetServiceUrl(ICollection<string> listenUrls, out string protocol, ref string serviceIP, out int port, out string errorMsg)
{
protocol = null;
port = 0;
errorMsg = null;
if (!string.IsNullOrWhiteSpace(serviceIP)) // 如果提供了对外服务的IP, 只需要检测是否在listenUrls里面即可
{
foreach (var listenUrl in listenUrls)
{
Uri uri = new Uri(listenUrl);
protocol = uri.Scheme;
var ipAddress = uri.Host;
port = uri.Port;

if (ipAddress == serviceIP || ipAddress == "0.0.0.0" || ipAddress == "[::]")
{
return true;
}
}
errorMsg = $"The serviceIP that you provide is not in urls={string.Join(',', listenUrls)}";
return false;
}
else // 没有提供对外服务的IP, 需要查找本机所有的可用IP, 看看有没有在 listenUrls 里面的
{
var allIPAddressOfCurrentMachine = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
.Select(p => p.GetIPProperties())
.SelectMany(p => p.UnicastAddresses)
// 这里排除了 127.0.0.1 loopback 地址
.Where(p => p.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !System.Net.IPAddress.IsLoopback(p.Address))
.Select(p => p.Address.ToString()).ToArray();
var uris = listenUrls.Select(listenUrl => new Uri(listenUrl)).ToArray();
// 本机所有可用IP与listenUrls进行匹配, 如果listenUrl是"0.0.0.0"或"[::]", 则任意IP都符合匹配
var matches = allIPAddressOfCurrentMachine.SelectMany(ip =>
uris.Where(uri => ip == uri.Host || uri.Host == "0.0.0.0" || uri.Host == "[::]")
.Select(uri => new { Protocol = uri.Scheme, ServiceIP = ip, Port = uri.Port })
).ToList();

if (matches.Count == 0)
{
errorMsg = $"This machine has IP address=[{string.Join(',', allIPAddressOfCurrentMachine)}], urls={string.Join(',', listenUrls)}, none match.";
return false;
}
else if (matches.Count == 1)
{
protocol = matches[0].Protocol;
serviceIP = matches[0].ServiceIP;
port = matches[0].Port;
return true;
}
else
{
errorMsg = $"Please use --serviceIP=yourChosenIP to specify one IP, which one provide service: {string.Join(",", matches)}.";
return false;
}
}
}
}

    

    4、运行网关服务 dotnet Zfkr.WF.Core.Gateway.dll --urls=http://*:9988

Ocelot+Consul实现微服务架构的更多相关文章

  1. 【3分钟就会系列】使用Ocelot+Consul搭建微服务吧!

    一.什么Ocelot? API网关是一个服务器,是系统的唯一入口.API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端实现和微服务应用程 ...

  2. 庐山真面目之六微服务架构Consul集群、Ocelot网关集群和Nginx版本实现

    庐山真面目之六微服务架构Consul集群.Ocelot网关集群和Nginx版本实现 一.简介      在上一篇文章<庐山真面目之五微服务架构Consul集群.Ocelot网关和Nginx版本实 ...

  3. 庐山真面目之七微服务架构Consul集群、Ocelot网关集群和IdentityServer4版本实现

    庐山真面目之七微服务架构Consul集群.Ocelot网关集群和IdentityServer4版本实现 一.简介      在上一篇文章<庐山真面目之六微服务架构Consul集群.Ocelot网 ...

  4. 庐山真面目之十二微服务架构基于Docker搭建Consul集群、Ocelot网关集群和IdentityServer版本实现

    庐山真面目之十二微服务架构基于Docker搭建Consul集群.Ocelot网关集群和IdentityServer版本实现 一.简介      在第七篇文章<庐山真面目之七微服务架构Consul ...

  5. 庐山真面目之三微服务架构Consul版本实现

    庐山真面目之三微服务架构Consul版本实现 一.简介           在上一篇文章<庐山真面目之二微服务架构NGINX版本实现>中,我们已经探讨了如何搭建基于Nginx 网关的微服务 ...

  6. .net core 微服务架构-docker的部署-包括网关服务(Ocelot)+认证服务(IdentityServer4)+应用服务(asp.net core web api)

    本文主要介绍通过Docker来部署通过.Net Core开发的微服务架构,部署的微服务主要包括统一网关(使用Ocelot开发).统一认证(IdentityServer4).应用服务(asp.net c ...

  7. (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型

    开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...

  8. (1).NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型

    开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点:1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块:2)系统耦合性强,一旦其中一个模块有问题,整个 ...

  9. 什么是微服务架构,.netCore微服务选型

    什么是微服务架构,.netCore微服务选型 https://www.cnblogs.com/uglyman/p/9182485.html 开发工具:VS2017 .Net Core 2.1 什么是微 ...

随机推荐

  1. 蜻蜓点水说说Redis的ziplist的奥秘

    本篇博客参考: Redis 深度历险:核心原理与应用实践 Redis内部数据结构详解(4)--ziplist Redis的压缩列表ZipList 上篇博客中,我给大家蜻蜓点水般的介绍了Redis中SD ...

  2. 一个试图了解JVM内存模型的两年经验的初级程序员,透彻!

    所有的编程语言中都有内存模型这个概念,区别于微架构的内存模型,高级语言的内存模型包括了编译器和微架构两部分.我试图了解了Java.C#和Go语言的内存模型,发现内容基本大同小异,只是这些语言在具体实现 ...

  3. 在centos 上安装python

    1.下载最新版本python源码包 下载地址为https://www.python.org/ftp/python/3.8.5/Python-3.8.5.tgz 2.解压源码包 tar -zxf Pyt ...

  4. Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现

    Spring中事务的使用示例.属性及使用中可能出现的问题 前言 本专题大纲如下: 对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细,本来是想在源码分析的文章中附带讲一讲事务使用中的 ...

  5. JavaFX桌面应用-MVC模式开发,“真香”

    使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...

  6. 重回OI的第一篇博客

    太久没学OI了, 代码都不会写了, 先写一篇BFS练练手, 是我太菜了qwq #include<cstdio> #include<queue> #include<iost ...

  7. centos 安装vimplus

    参考链接:https://www.jianshu.com/p/75cde8a80fd7 git clone https://github.com/chxuan/vimplus.git ~/.vimpl ...

  8. A Distributional Perspective on Reinforcement Learning

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! arXiv:1707.06887v1 [cs.LG] 21 Jul 2017 In International Conference on ...

  9. HM16.0之帧间Merge模式——xCheckRDCostMerge2Nx2N

    参考:https://blog.csdn.net/nb_vol_1/article/details/51163625 1.源代码: /** check RD costs for a CU block ...

  10. Java 实例 - 获取本机ip地址及主机名

    package guyu.day0824; import java.net.InetAddress; /** * @Author: Fred * @Date: 2020/8/24 09:39 */ p ...