.Net Core with 微服务 - Consul 注册中心

上一次我们介绍了 Ocelot 网关的基本用法。这次我们开始介绍服务注册发现组件 Consul 的简单使用方法。

服务注册发现

首先先让我们回顾下服务注册发现的概念。
在实施微服务之后,我们的调用都变成了服务间的调用。服务间调用需要知道IP、端口等信息。再没有微服务之前,我们的调用信息一般都是写死在调用方的配置文件里(当然这话不绝对,有些公司会把这些信息写到数据库等公共的地方,以方便维护)。又由于业务的复杂,每个服务可能依赖N个其他服务,如果某个服务的IP,端口等信息发生变更,那么所有依赖该服务的服务的配置文件都要去修改,这样显然太麻烦了。有些服务为了负载是有个多个实例的,而且可能是随时会调整实例的数量。如果每次调整实例数量都要去修改其他服务的配置并重启那太麻烦了。
为了解决这个问题,业界就有了服务注册发现组件。
假设我们有服务A需要调用服务B,并且有服务注册发现组件R。整个大致流程将变成大噶3部:

  1. 服务B启动向服务R注册自己的信息

  2. 服务A从服务R拉取服务B的信息

  3. 服务A调用服务B

有了服务注册发现组件之后,当修改A服务信息的时候再也不用去修改其他相关服务了。

Consul

Consul 是 HashiCorp 公司推出的一套服务注册发现工具。它使用 golang 编写并且开源。由于使用 golang 的缘故所以它天生跨平台而且部署简单。它带有 web 管理后台方便用户查看维护 Consul 集群。其实除了服务注册发现功能,Consul 还支持 Key/Value 存储可以当一个简单的配置中心使用。

架构

上面是 Consul 官网上画的架构图。从图上可以看到 Consul 天生支持多数据中心部署。每个数据中心内部有多个 Consul 节点。Consul 的节点分为2种。

  1. Server
    Server 模式的节点是真正意义上集群的节点。它通过RAFT算法实现CAP里的CA。当Leader Server 挂掉的时候会自动选举出新的 Leader 使集群继续正常工作。

  2. Client
    Client 模式的节点虽然也叫节点,但是它并不会持久化数据,不维持状态,它仅仅是转发客户端的请求给后面的 Server 节点,同时负责注册到该 client 节点的服务的健康检测。它非常轻量级,按照 Consul 的说法最好是每个服务都配一个 client 。

为什么要有client模式的节点

我初看 Consul 这套架构的时候觉得很奇怪,为什么要在 Server 节点跟真正的服务之间插入一层 client 模式的节点。按照按照 Consul 的说法还得每个服务配一个 client 节点。

经过思考说说我的一些看法。在这个模式下服务不在关心真正的集群在哪,集群的节点有哪些,只需要知道这个伴随的 client 节点的地址就行了。通过这个 client 节点去感知到真正可用的 server 节点,所有跟 server 节点的交互全部交给 client 节点代理去完成,这就简化了服务跟 consul 交互的难度。还有一个好处是服务的健康检测由 client 节点负责,在一定程度上减轻了 server 节点的压力。当然这也会带来一个问题,那就是如果 client 挂了,那么服务可能就连不上 Consul 集群了,因为对于服务来说这个 client 节点相当于是单点的。

使用 docker 运行 Consul

docker run -p 8500:8500 --name=consulserver consul agent -server -bootstrap -client=0.0.0.0 -ui -node=0

使用 docker 命令运行初始化一个 consul 的 server 模式的节点。

  • -server 启动为Server模式

  • -bootstrap 设置为启动模式,这是第一个server节点,等待其它节点的加入

  • -client 指定可以访问的客户端IP 。

  • -ui 开启管理界面

  • -node 节点的名字

docker run -d --name=consulserver1 consul agent -server -node=1 -join=172.17.0.2

有了第一个节点,我们可以开始创建更多的 Server 节点来构造集群。Consul 推荐至少3个 Server 来组建集群。上面的 docker 命令表示启动第二个 Server 然后加入第一个节点构造的集群。

  • -join 加入某个集群,这里的 IP 为第一个启动的节点的内网 IP 。可以通过 docker exec XXX consul members 命令查看。后面会演示。

docker run --name=consulclient0 -e consul agent -client=0.0.0.0 -node=client0 -retry-join=172.17.0.2

我们有了 Server 集群,现在可以开始建立 Consul 的 client 节点,然后加入集群。启动 Consul client 的命令跟启动 Consul server 的差不多。去掉了 -server 就代表这个 agent 为 client 模式。

使用 docker-compose 运行 Consul

上面分步骤演示了如何使用 docker 命令来运行 Consul 集群。一行行敲还是太麻烦,为了简化部署,这里整理成了 docker-compose 启动文件。

version: '3.9'
services:consulserver1:image: consul:1.9.4restart: alwayscontainer_name: consulserver1hostname: consulserver1command: agent -server -bootstrap -client=0.0.0.0 -ui -node=consulserver1ports:- 8500:8500consulserver2:image: consul:1.9.4restart: alwayscontainer_name: consulserver2hostname: consulserver2command: agent -server -join=consulserver1 -node=consulserver2depends_on:- consulserver1consulserver3:image: consul:1.9.4restart: alwayscontainer_name: consulserver3hostname: consulserver3command: agent -server -join=consulserver1 -node=consulserver3depends_on:- consulserver1consulclient1:image: consul:1.9.4restart: alwayscontainer_name: consulclient1hostname: consulclient1command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient1depends_on:- consulserver2- consulserver3ports:- 8600:8500consulclient2:image: consul:1.9.4restart: alwayscontainer_name: consulclient2hostname: consulclient2command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient2depends_on:- consulserver2- consulserver3ports:- 8700:8500consulclient3:image: consul:1.9.4restart: alwayscontainer_name: consulclient3hostname: consulclient3command: agent -client=0.0.0.0 -retry-join=consulserver1 -node=consulclient3depends_on:- consulserver2- consulserver3ports:- 8800:8500

这个 docker-compose 文件描述了启动3个 server 模式的实例,3个 client 模式的实例。其中 consulserver1 开启了ui,端口映射8500,consulclient1,、consulclient2、consulclient3 端口分别映射为 8600、8700、8800 ,记住这些端口,后面要用到。

[root@localhost myservices]# docker-compose up -d
[root@localhost myservices]# docker exec consulserver1 consul members
Node           Address          Status  Type    Build  Protocol  DC   Segment
consulserver1  172.18.0.2:8301  alive   server  1.9.4  2         dc1  <all>
consulserver2  172.18.0.3:8301  alive   server  1.9.4  2         dc1  <all>
consulserver3  172.18.0.4:8301  alive   server  1.9.4  2         dc1  <all>
consulclient1  172.18.0.5:8301  alive   client  1.9.4  2         dc1  <default>
consulclient2  172.18.0.6:8301  alive   client  1.9.4  2         dc1  <default>
consulclient3  172.18.0.7:8301  alive   client  1.9.4  2         dc1  <default>

使用 docker-compose up -d 命令启动所有的容器。启动完成后使用 docker exec consulserver1 consul members 查看整个集群的状态。它列出了所有节点的类型,IP,是否存活等信息。

如果上面的操作一切正常,在浏览器里输入 http://宿主机IP:8500 访问 web 管理界面。界面上会显示6个绿色的节点。表示所有节点都正常运行中。

在 asp.net core 应用内使用 Consul

好了现在我们已经有了 Consul 集群,现在可以开始编写代码来注册跟拉取我们的服务了。我们需要完成4点操作。

  1. 定义一个健康检测的接口

  2. 在服务启动的时候自动注册该服务的基础信息

  3. 在服务关闭的时候自动移除该服务

  4. 拉取服务列表

健康检测

我们的服务注册到 consul 节点后,节点会定时去轮询我们的服务,所以需要提供一个 http 接口,如果返回 200 ok 就表示服务存活,否则代表服务故障。

    [ApiController][Route("[controller]")]public class HealthController : ControllerBase{[HttpGet]public string Get(){return "ok";}}
}

添加一个HealthController里面就实现一个Get方法简单的返回ok就可以了。

服务注册、移除

我们实现一个HostedService来实现自动注册跟移除服务。HostedService 有2个方法,start 跟 stop 。start 方法会在 app 启动的时候触发 , stop 会在 app 关闭的时候触发。跟我们的需求完美符合。

Install-Package Consul -Version 1.6.10.1

使用 nuget 安装 consul .net client 类库。我们跟 consul 节点的通讯需要它来完成。

    public class ServiceInfo{public string Id { get; set; }public string Name { get; set; }public string IP { get; set; }public int Port { get; set; }public string HealthCheckAddress { get; set; }}

定义一个类,存储服务的基本信息。

public class ConsulRegisterService : IHostedService{IConsulClient _consulClient;ServiceInfo _serviceInfo;public ConsulRegisterService(IConfiguration config, IConsulClient consulClient){_serviceInfo = new ServiceInfo();var sc = config.GetSection("serviceInfo");_serviceInfo.Id = sc["id"];_serviceInfo.Name = sc["name"];_serviceInfo.IP = sc["ip"];_serviceInfo.HealthCheckAddress = sc["HealthCheckAddress"];_serviceInfo.Port = int.Parse(sc["Port"]);_consulClient = consulClient;}public async Task StartAsync(CancellationToken cancellationToken){Console.WriteLine($"start to register service {_serviceInfo.Id} to consul client ...");await _consulClient.Agent.ServiceDeregister(_serviceInfo.Id, cancellationToken);await _consulClient.Agent.ServiceRegister(new AgentServiceRegistration{ID = _serviceInfo.Id,Name = _serviceInfo.Name,// 服务名Address = _serviceInfo.IP, // 服务绑定IPPort = _serviceInfo.Port, // 服务绑定端口Check = new AgentServiceCheck(){DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(0),//服务启动多久后注册Interval = TimeSpan.FromSeconds(5),//健康检查时间间隔HTTP = $"http://{_serviceInfo.IP}:{_serviceInfo.Port}/" + _serviceInfo.HealthCheckAddress,//健康检查地址Timeout = TimeSpan.FromSeconds(5)}});Console.WriteLine("register service info to consul client Successful ...");}public async Task StopAsync(CancellationToken cancellationToken){await _consulClient.Agent.ServiceDeregister(_serviceInfo.Id, cancellationToken);Console.WriteLine($"Deregister service {_serviceInfo.Id} from consul client Successful ...");}}

定义一个 ConsulRegisterService 类,实现 IHostedService 接口。在 start 方法内使用 consulclient 注册服务 。在 stop 方法内取消注册该服务。

        public void ConfigureServices(IServiceCollection services){//注册Consulclient对象services.AddSingleton<IConsulClient>(new ConsulClient(x => {x.Address = new Uri(Configuration["consul:clientAddress"]);}));//注册ConsulRegisterService 这个servcie在app启动的时候会自动注册服务信息services.AddHostedService<ConsulRegisterService>();services.AddControllers();}

在 startup 的 ConfigureServices 方法内先注入一个 IConsulClient 的实例。再注册我们的 ConsulRegisterService 服务。

  "serviceInfo": {"id": "hote_base_01", //服务id"name": "hote_base", //服务名"ip": "192.168.0.200", //服务部署的ip"port": 6002, //服务对应的端口"healthCheckAddress": "health" //健康检测的请求path},"consul": {"clientAddress": "http://192.168.0.117:8700" //consul client 的地址}

以我们的演示项目 hotel_base 为例,在 appsettings.json 文件内添加以上配置信息。其中 consul:clientAddress 为 consule client 节点的地址。

注意:这里的 ip 不要使用 localhost ,因为如果使用 docker 部署 , localhost 会出现网络访问方面的问题。

好了,让我们运行一下我们的项目。等待项目启动完成后,打开 consul 的 web 管理界面。查看 consulclient1 节点,可以看到我们的 hotelbase01 服务被注册上去了。

我们强制把启动的app关闭,可以看到 consul 管理界面显示 hotel_base 服务红色,代表故障。

注意:要演示故障这种情况,要先注释掉 ConsulRegisterService 的 stop 方法,不然关闭的时候会先取消注册,这样 consul 管理界面上就找不到对应的服务了。

我们按照 hotel_base 的套路,把其他几个服务都添加服务注册的代码。然后全部运行起来

拉取服务列表

下面我们演示下如何通过 consul client 读取服务列表。

   public interface IConsulService{Task<List<AgentService>> GetServicesAsync(string serviceName);}public class ConsulService : IConsulService{public IConsulClient _consulClient;public ConsulService(IConsulClient consulClient){_consulClient = consulClient;}public async Task<List<AgentService>> GetServicesAsync(string serviceName){var result = await _consulClient.Health.Service(serviceName, "", true);return result.Response.Select(x => x.Service).ToList();}}

定义一个ConsulService类,里面有个GetServicesAsync方法。该方法通过服务名称从 consul 集群获取服务的列表。

        public void ConfigureServices(IServiceCollection services){services.AddSingleton<IConsulClient>(new ConsulClient(x => {x.Address = new Uri(Configuration["consul:clientAddress"]);}));//注册ConsulService里面封装了一些方法services.AddSingleton<IConsulService, ConsulService>();services.AddHostedService<ConsulRegisterService>();services.AddControllers();}

在 ConfigureServices 方法内把 ConsulService 注册到容器内。

 [ApiController][Route("[controller]")]public class OrderController : ControllerBase{private static readonly List<OrderVM> _orders = new List<OrderVM>() { new OrderVM { Id = "OD001",StartDay = "2021-05-01",EndDay = "2021-05-02",RoomNo = "1001",MemberId = "M001",HotelId = "H8001",CreateDay = "2021-05-01"}};private IConsulService _consulservice;public OrderController(ILogger<OrderController> logger, IConsulService consulService){_consulservice = consulService;}[HttpGet("{id}")]public async Task<OrderVM> Get(string id){var order = _orders.FirstOrDefault(x=>x.Id == id);if (!string.IsNullOrEmpty(order.MemberId)){var memberServiceAddresses = await _consulservice.GetServicesAsync("member_center");var memberServiceAddress = memberServiceAddresses.FirstOrDefault();using (var httpClient = new HttpClient()){httpClient.BaseAddress = new Uri($"http://{memberServiceAddress.Address}:{memberServiceAddress.Port}");var memberResult = await httpClient.GetAsync("/member/" + order.MemberId);var json = await memberResult.Content.ReadAsStringAsync();var member = JsonConvert.DeserializeObject<MemberVM>(json);order.Member = member;}}return order;}}

我们通过在 ordering 服务项目的一个获取订单详细信息的接口来演示下如何使用ConsulService 。订单详细信息需要根据会员id获取会员的详细信息。我们通过 ConsulService 获得 member_center 的服务列表后,取出一个配置信息,获取 IP 跟端口号。组装成服务的真正的请求地址,使用 HttpClient 来请求这个接口,获取会员的基本信息。
当然这里我们有很多可以改进的地方,比如我们可以在本地缓存服务列表,这样不用每次都通过 consul client 拉取。比如我们可以写一个随机算法,每次从服务列表中随机取一个对象,从而达到负载均衡的目的,在这就不再演示了。

把所有项目都跑起来,使用 postman 去访问一下获取订单详情接口,可以看到订单详情的返回值包含了会员信息。

总结

通过以上,我们回顾了服务注册发现的概念。演示了如何通过 docker/docker-compose 环境来部署 Consul 集群。还通过简单的 .NET Core 代码演示了如何注册服务信息到 Consul 集群,如何通过代码获取服务列表并调用它。相信现在大家对服务注册发现、Consul 组件有了一个比较直观的了解。
谢谢阅读。

项目地址

https://github.com/kklldog/myhotel_microservice

相关文章

NET Core with 微服务 - 什么是微服务
.Net Core with 微服务 - 架构图
.Net Core with 微服务 - Ocelot 网关

关注我的公众号一起玩转技术

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

指定特定的内容为首页

2019独角兽企业重金招聘Python工程师标准>>> 默认的情况下&#xff1a;drupal7是将新发表的文件都显示在首页中。 通过下面的方法&#xff0c;指定特定的内容为首页&#xff1a; 管理--》配置--》系统--》站点信息--》默认首页&#xff0c;将相应的内容地址写入&a…

Python到底是有什么魅力,让程序猿为它折腰?

在大数据时代&#xff0c;信息更新非常快速&#xff0c;计算机语言也犹如雨后春笋般被我们所熟知。C语言、C、Java等可谓是各领风骚、独占鳌头&#xff0c;而Python则是一门近几年崛起很快也很火的编程语言。虽说编程语言难分好坏&#xff0c;各有千秋。但Python到底有什么魔力…

jenkins代理设置

2019独角兽企业重金招聘Python工程师标准>>> 抱怨&#xff1a;对于像我这样苦逼的用户&#xff0c;机器在内网&#xff0c;干啥都要“偷偷”通过代理。 言归正传&#xff0c;jenkins\hudson默认并没有提供proxy设置&#xff0c;nexus仓库倒是提供里proxy设置。jenk…

.NET 6 Preview5+VS2022实战千万并发秒杀项目,帅爆了(附源码)

Microsoft 宣布了Visual Studio 2022 的第一个预览版&#xff0c;并且同时也发布了.NET 6 Preview 5。具有里程碑意义的Visual Studio 2022 Preview 1正式发布&#xff0c;重点是64位&#xff0c;而没有增加新功能&#xff0c;并且同时也发布了.NET 6 Preview 5。下面的内容来自…

你们要的印度布线,这是一种极端的牛X

全世界只有3.14 % 的人关注了数据与算法之美前天&#xff0c;数据汪分享了一篇文章「德国布线牛到不行&#xff1f;今天带你看看中国的&#xff01;」&#xff0c;一个中国的布线&#xff0c;小伙伴们表示非常过瘾&#xff0c;强迫症的最爱。不过数据汪也收到不少粉丝留言说&am…

MIPS衰落 LoongArch崛起

不久前&#xff0c;龙芯发布了自主指令集LoongArch和基于LoongArch设计的3A5000&#xff0c;与MIPS彻底分道扬镳。从龙芯最初基于MIPS添加指令&#xff0c;到发展出基于MIPS的LoongISA&#xff0c;再到最新的LoongArch&#xff0c;龙芯的目的是非常明确的&#xff0c;也是显而易…

html答题赚钱源码,WTS在线答题系统 v1.0.0

WTS在线答题系统为在线答题系统(在线考试)&#xff0c;支持在线考试、在线练习等功能... 支持题型&#xff1a;单选题、多选题、填空题、问答题、判断题、附件题、材料题、视频题、音频题支持答题类型&#xff1a;手工配置 试卷答题、随机抽题练习社交功能&#xff1a;试题收藏…

连锁反应装置积木好玩到尖叫!

▲数据汪特别推荐点击上图进入玩酷屋小木用真金白银来给大家送礼物啦&#xff0c;特别感谢这些年一直以来大家对我们的支持&#xff0c;才让我们越做越好。&#xff08;点我参与送礼活动&#xff09;之前推荐的“小小机械师”成了孩子最受欢迎的玩具&#xff0c;玩了的孩子几乎…

chrome vue.js插件文档_神级宝库!GitHub 标星 1.2w+,Chrome 最天秀的插件都在这里啦!...

文章来源&#xff1a;Python空间作者&#xff1a;Rocky0429作为一个在远古时代用过什么 IE、360、猎豹等浏览器的资深器哥&#xff0c;当我第一次了解 Chrome 的时候&#xff0c;就被它的美貌给吸引住了...就在我用了一段时间之后&#xff0c;我坚决的卸载了电脑上其它碍眼的浏…

Android实现通用的ActivityGroup(效果类似Android微博客户端主界面),...

为什么80%的码农都做不了架构师&#xff1f;>>> 可以说ActivityGroup是Google提供的一个非常优秀的API&#xff0c;但它需要做稍微复杂的重写才能用起来比较方便&#xff0c;本文拟将实现这个稍微复杂的重写。TabActivity作为ActivityGroup唯一的子类却让人大失所望…

类选项html 最后无距离,各种距离 一览无遗

offsetLeft,Left,clientLeft的区别假设 obj 为某个 HTML 控件obj.offsetTop 指 obj 相对于版面或由 offsetParent 属性指定的父坐标的计算上侧位置&#xff0c;整型&#xff0c;单位像素。obj.offsetLeft 指 obj 相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置&a…

华为起诉最新进展,国内航司暂停运行有关客机,收买家庭不得继续抚养被解救儿童,脸书创始人后悔没早学微信,这就是今天的大新闻...

今天是3月11日农历二月初五今天星期一没带耳机线感觉这样用手机是没有灵魂的下面是今天的大新闻华为起诉最新进展&#xff08;环球网&#xff09;多个来自美国的消息证实&#xff0c;美国的联邦法院已经就华为公司起诉美国政府一案给美国政府及其多位部长发去了法院传票。上图&…

前目的地罗伯森是谁_距离目的地只剩10公里,开车师傅却在高速公路上睡着了...

钱江晚报小时新闻记者 吴崇远 通讯员 俞斐“我从杭州过来&#xff0c;刚才开累了&#xff0c;就休息了几分钟……”“杭州到嘉善那么一点路都犯困了&#xff1f;”4月6日凌晨4点30分&#xff0c;高速交警嘉兴支队值班民警在巡逻时发现&#xff0c;一辆厢式小货车开着双跳灯&…

WPF 写一个提醒工具软件(完整项目)

昨天整理硬盘时&#xff0c;偶然发现一个很久之前写的小工具&#xff0c;一个提醒工具。包含定时提醒&#xff0c;间隔提醒功能。看看效果&#xff1a;界面看起来也还凑合&#xff0c;还使用了HandyControl&#xff0c;有桌面托盘功能界面是下面这样的提醒窗口有两种&#xff0…

她只用1个方法,就把英语拿下了!

全世界只有3.14 % 的人关注了数据与算法之美这3招让你未来6个月讲一口流利英语怎么摆脱单词记不住&#xff1f;如何解决听不懂老外讲英语&#xff1f;让老外叹服你口语的唯一方法……请看中国著名口语教学专家写给你的一封信&#xff1a;亲爱的朋友&#xff1a;你好&#xff01…

英语计算机工程师求职信,计算机工程师英文求职信范文

计算机工程师英文求职信范文是由个人简历模板网为你提供的一份个人求职信范文&#xff0c;可直接使用或根据实际情况进行修改。祝您职场顺利&#xff0c;早日找到称心如意的工作&#xff01;Dear Sir or Madam,I am writing to apply for a position as a computer engineer in…

WPF实现环(圆)形菜单

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织“ 前言&#xff0c;接着上一篇圆形菜单。”欢迎转发、分享、点赞、在看&#xff0c;谢谢~。 01—效果预览效果预览&#xff08;更多效果请下载源码体验&#xff09;&#xff1a;02—代码如下一、…

win7系统桌面计算机怎么打的开,windows7系统双击计算机打不开怎么解决|win7双击计算机打不开的解决方法...

运行windows7系统的时候双击"计算机"遇到打不开的情况&#xff0c;也不知道怎么回事&#xff0c;尝试用杀毒软件解决也无效。如果要查看磁盘的具体情况&#xff0c;都没办法查看了。针对win7双击"计算机"打不开的问题&#xff0c;下面小编介绍两种解决方法…

每日一笑 | 在俄罗斯人眼里,没有什么是胶带解决不了的

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图片来源于网络&#xff0c;侵权删&#xff09;

钣金缺口lisp_UG用钣金模块的放样创建天圆地方,还能学钣金展开,必看

上个给大家用“直纹”做了一个天圆地方的圆台模型&#xff0c;今天给大家讲解下UG钣金模块如何使用“放样弯边”做天圆地方管并展开&#xff0c;借用下上次的图纸数据。首先创建天圆地方钣金模型1首先新建模型&#xff0c;点击草图&#xff0c;以XY为草图平面&#xff0c;点击确…