Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)

news2025/1/23 16:23:22

系列文章目录

1、.Net Core微服务入门系列(一)——项目搭建
2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)
3、.Net Core微服务入门全纪录(三)——Consul-服务注册与发现(下)
4、.Net Core微服务入门全纪录(四)——Ocelot-API网关(上)
5、.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)
6、.Net Core微服务入门全纪录(六)——EventBus-事件总线
7、.Net Core微服务入门全纪录(七)——IdentityServer4-授权认证
8、.Net Core微服务入门全纪录(八)——Docker Compose与容器网络


在这里插入图片描述


前言📃

关于 微服务 的概念解释网上有很多, 个人理解微服务是一种系统架构模式,它和语言无关,和框架无关,和工具无关,和服务器环境无关。

微服务思想 是将传统的单体系统按照业务拆分成多个职责单一、且可独立运行的接口服务。至于服务如何拆分,没有明确的定义。几乎任何后端语言都能做微服务开发。微服务也并不是完美无缺的,微服务架构会带来更多的问题,增加系统的复杂度,引入更多的技术栈。

上一篇【.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上)】已经成功将我们的服务注册到Consul中,接下来就该客户端通过Consul去做服务发现了。


一、服务发现🔎

🔖同样Nuget安装一下 Consul

在这里插入图片描述
改造一下业务系统的代码:

在这里插入图片描述
ServiceHelper.cs:

    public class ServiceHelper : IServiceHelper
    {
        private readonly IConfiguration _configuration;

        public ServiceHelper(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        public async Task<string> GetOrder()
        {
            //string[] serviceUrls = { "http://localhost:9060", "http://localhost:9061", "http://localhost:9062" };//订单服务的地址,可以放在配置文件或者数据库等等...

            var consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
            });

            //consulClient.Catalog.Services().Result.Response;
            //consulClient.Agent.Services().Result.Response;
            var services = consulClient.Health.Service("OrderService", null, true, null).Result.Response;//健康的服务

            string[] serviceUrls = services.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();//订单服务地址列表

            if (!serviceUrls.Any())
            {
                return await Task.FromResult("【订单服务】服务列表为空");
            }

            //每次随机访问一个服务实例
            var Client = new RestClient(serviceUrls[new Random().Next(0, serviceUrls.Length)]);
            var request = new RestRequest("/orders", Method.GET);

            var response = await Client.ExecuteAsync(request);
            return response.Content;
        }

        public async Task<string> GetProduct()
        {
            //string[] serviceUrls = { "http://localhost:9050", "http://localhost:9051", "http://localhost:9052" };//产品服务的地址,可以放在配置文件或者数据库等等...

            var consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
            });

            //consulClient.Catalog.Services().Result.Response;
            //consulClient.Agent.Services().Result.Response;
            var services = consulClient.Health.Service("ProductService", null, true, null).Result.Response;//健康的服务

            string[] serviceUrls = services.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();//产品服务地址列表

            if (!serviceUrls.Any())
            {
                return await Task.FromResult("【产品服务】服务列表为空");
            }

            //每次随机访问一个服务实例
            var Client = new RestClient(serviceUrls[new Random().Next(0, serviceUrls.Length)]);
            var request = new RestRequest("/products", Method.GET);

            var response = await Client.ExecuteAsync(request);
            return response.Content;
        }
    }

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConsulSetting": {
    "ConsulAddress": "http://localhost:8500"
  }
}

OK,以上代码就完成了服务列表的获取。

浏览器测试一下:

在这里插入图片描述
随便停止2个服务:

在这里插入图片描述
继续访问:

在这里插入图片描述
这时候停止的服务地址就获取不到了,客户端依然正常运行。

这时候解决了服务的发现,新的问题又来了…

客户端每次要调用服务,都先去 Consul 获取一下地址,这不仅浪费资源,还增加了请求的响应时间,这显然让人无法接受。
那么怎么保证不要每次请求都去 Consul 获取地址,同时又要拿到可用的地址列表呢?
Consul 提供的解决方案:——Blocking Queries (阻塞的请求)。详情请见官网:https://www.consul.io/api-docs/features/blocking

二、Blocking Queries

这是什么意思呢,简单来说就是当客户端请求 Consul 获取地址列表时,需要携带一个版本号信息,Consul 会比较这个客户端版本号是否和 Consul 服务端的版本号一致,如果一致,则Consul 会阻塞这个请求,直到 Consul 中的服务列表发生变化,或者到达阻塞时间上限;如果版本号不一致,则立即返回。这个阻塞时间默认是5分钟,支持自定义。

那么我们另外启动一个线程去干这件事情,就不会影响每次的用户请求了。这样既保证了客户端服务列表的准确性,又节约了客户端请求服务列表的次数。

继续改造代码:
IServiceHelper 增加一个获取服务列表的接口方法:

    public interface IServiceHelper
    {
        /// <summary>
        /// 获取产品数据
        /// </summary>
        /// <returns></returns>
        Task<string> GetProduct();

        /// <summary>
        /// 获取订单数据
        /// </summary>
        /// <returns></returns>
        Task<string> GetOrder();

        /// <summary>
        /// 获取服务列表
        /// </summary>
        void GetServices();
    }

ServiceHelper 实现接口:

    public class ServiceHelper : IServiceHelper
    {
        private readonly IConfiguration _configuration;
        private readonly ConsulClient _consulClient;
        private ConcurrentBag<string> _orderServiceUrls;
        private ConcurrentBag<string> _productServiceUrls;

        public ServiceHelper(IConfiguration configuration)
        {
            _configuration = configuration;
            _consulClient = new ConsulClient(c =>
            {
                //consul地址
                c.Address = new Uri(_configuration["ConsulSetting:ConsulAddress"]);
            });
        }

        public async Task<string> GetOrder()
        {
            if (_productServiceUrls == null)
                return await Task.FromResult("【订单服务】正在初始化服务列表...");

            //每次随机访问一个服务实例
            var Client = new RestClient(_orderServiceUrls.ElementAt(new Random().Next(0, _orderServiceUrls.Count())));
            var request = new RestRequest("/orders", Method.GET);

            var response = await Client.ExecuteAsync(request);
            return response.Content;
        }

        public async Task<string> GetProduct()
        {
            if(_productServiceUrls == null)
                return await Task.FromResult("【产品服务】正在初始化服务列表...");

            //每次随机访问一个服务实例
            var Client = new RestClient(_productServiceUrls.ElementAt(new Random().Next(0, _productServiceUrls.Count())));
            var request = new RestRequest("/products", Method.GET);

            var response = await Client.ExecuteAsync(request);
            return response.Content;
        }

        public void GetServices()
        {
            var serviceNames = new string[] { "OrderService", "ProductService" };
            Array.ForEach(serviceNames, p =>
            {
                Task.Run(() =>
                {
                    //WaitTime默认为5分钟
                    var queryOptions = new QueryOptions { WaitTime = TimeSpan.FromMinutes(10) };
                    while (true)
                    {
                        GetServices(queryOptions, p);
                    }
                });
            });
        }
        private void GetServices(QueryOptions queryOptions, string serviceName)
        {
            var res = _consulClient.Health.Service(serviceName, null, true, queryOptions).Result;
            
            //控制台打印一下获取服务列表的响应时间等信息
            Console.WriteLine($"{DateTime.Now}获取{serviceName}:queryOptions.WaitIndex:{queryOptions.WaitIndex}  LastIndex:{res.LastIndex}");

            //版本号不一致 说明服务列表发生了变化
            if (queryOptions.WaitIndex != res.LastIndex)
            {
                queryOptions.WaitIndex = res.LastIndex;

                //服务地址列表
                var serviceUrls = res.Response.Select(p => $"http://{p.Service.Address + ":" + p.Service.Port}").ToArray();

                if (serviceName == "OrderService")
                    _orderServiceUrls = new ConcurrentBag<string>(serviceUrls);
                else if (serviceName == "ProductService")
                    _productServiceUrls = new ConcurrentBag<string>(serviceUrls);
            }
        }
    }

StartupConfigure 方法中调用一下获取服务列表:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceHelper serviceHelper)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });

            //程序启动时 获取服务列表
            serviceHelper.GetServices();
        }

代码完成,运行测试:

在这里插入图片描述
现在不用每次先请求服务列表了,是不是流畅多了?

看一下控制台打印:

在这里插入图片描述
这时候如果服务列表没有发生变化的话,获取服务列表的请求会一直阻塞到我们设置的10分钟。

随便停止2个服务:

在这里插入图片描述
在这里插入图片描述
这时候可以看到,数据被立马返回了。

在这里插入图片描述
继续访问客户端网站,同样流畅。

至此,我们就通过 Consul 完成了服务的注册与发现。
接下来又引发新的思考。。。

  1. 每个客户端系统都去维护这一堆服务地址,合理吗 ❓
  2. 服务的 ip 端口直接暴露给所有客户端,安全吗❓
  3. 这种模式下怎么做到客户端的统一管理呢❓

在这里插入图片描述

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

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

相关文章

vue3+webOffice合集

1、webOffice 初始化 1&#xff09;officeType: 文档位置&#xff1a;https://solution.wps.cn/docs/web/quick-start.html#officetype 2&#xff09;appId: 前端使用appId 后端需要用到AppSecret 3&#xff09;fileId: 由后端返回&#xff0c;前端无法生成&#xff0c;与上传文…

Python - itertools- pairwise函数的详解

前言&#xff1a; 最近在leetcode刷题时用到了重叠对pairwise,这里就讲解一下迭代工具函数pairwise,既介绍给大家&#xff0c;同时也提醒一下自己&#xff0c;这个pairwise其实在刷题中十分有用&#xff0c;相信能帮助到你。 参考官方讲解&#xff1a;itertools --- 为高效循…

【优选算法】5----有效三角形个数

又是一篇算法题&#xff0c;今天早上刚做的热乎的~ 其实我是想写博客但不知道写些什么&#xff08;就水一下啦&#xff09; -------------------------------------begin----------------------------------------- 题目解析: 这道题的题目算是最近几道算法题里面题目最短的&a…

C语言中 指针类型的意义

对于初学者也包括我来说指针不就是来存放地址的吗 应该会有一个疑惑 为什么还有那么多类型char* int*等等不都能存放一个地址无论是整形还是字符 那这个指针类型能有什么意义呢 有的有的兄弟 他的真正意义就在于解引用上面*p 指针类型决定了 指针进行解引用操作时 一次能访问…

easyexcel读取写入excel easyexceldemo

1.新建springboot项目 2.添加pom依赖 <name>excel</name> <description>excelspringboot例子</description><parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…

数据结构(精讲)----栈 stack

什么是栈 栈是只能在一端进行插入和删除操作的线性表(又称为堆栈)&#xff0c;进行插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。 特点&#xff1a;栈是先进后出FILO(First In Last Out) (LIFO(Last In First Out)) 顺序栈 特性 逻辑结构&#xff1a;线性结构…

Hexo + NexT + Github搭建个人博客

文章目录 一、 安装二、配置相关项NexT config更新主题主题样式本地实时预览常用命令 三、主题设置1.侧边栏2.页脚3.帖子发布字数统计 4.自定义自定义页面Hexo 的默认页面自定义 404 页自定义样式 5.杂项搜索服务 四、第三方插件NexT 自带插件评论系统阅读和访问人数统计 五、部…

I2S是什么通信协议?它如何传输音频数据?它和I2C是什么关系?

首先我们先明确一点&#xff0c;I2S和I2C没有什么关系&#xff0c;如果非要扯点共同点的话那就是它们都是由飞利浦制定的。 I2C我们用的比较多&#xff0c;我们用的大多数的传感器模块用的通信协议就是I2C&#xff0c;SPI&#xff0c;UART这些。 而I2S应用领域比较单一&#…

大模型:LangChain技术讲解

一、什么是LangChain 1、介绍 LangChain是一个用于开发由大型语言模型提供支持的Python框架。它提供了一系列工具和组件&#xff0c;帮助我们将语言模型集成到自己的应用程序中。 有了它之后&#xff0c;我们可以更轻松地实现对话系统、文本生成、文本分类、问答系统等功能。…

【2024 博客之星评选】请继续保持Passion

我尝试复盘自己2024年走的路&#xff0c;希望能给诸君一些借鉴。 文章目录 回头望感想与收获成长与教训今年计划感恩一些体己话 回头望 回望我的2024年&#xff0c;年初拿高绩效&#xff0c;但感觉逐渐被公司一点点剥离出中心&#xff1b;年中一直在学习防患于未然&#xff1b…

Node.js接收文件分片数据并进行合并处理

前言&#xff1a;上一篇文章讲了如何进行文件的分片&#xff1a;Vue3使用多线程处理文件分片任务&#xff0c;那么本篇文章主要看一下后端怎么接收前端上传来的分片并进行合并处理。 目录&#xff1a; 一、文件结构二、主要依赖1. express2. multer3. fs (文件系统模块)4. pat…

【2025小年源码免费送】

&#x1f496;学习知识需费心&#xff0c; &#x1f4d5;整理归纳更费神。 &#x1f389;源码免费人人喜&#xff0c; &#x1f525;码农福利等你领&#xff01; &#x1f496;山高路远坑又深&#xff0c; &#x1f4d5;大军纵横任驰奔&#xff0c; &#x1f389;谁敢横刀立马行…

【JavaSE】(8) String 类

一、String 类常用方法 1、构造方法 常用的这4种构造方法&#xff1a;直接法&#xff0c;或者传参字符串字面量、字符数组、字节数组。 在 JDK1.8 中&#xff0c;String 类的字符串实际存储在 char 数组中&#xff1a; String 类也重写了 toString 方法&#xff0c;所以可以直…

css普通用法

Css普通用法 这是一个链接 W3C&#xff0c;用这个语法可以访问W3C,自己可以去看更加详细的内容。 基本语法 名字{ 类型&#xff1a;参数 类型&#xff1a;参数 }a{ color:blue }引入方法 直接在html之中进行带入到html代码之中&#xff0c;文件不需要重新写一个&#xff0c…

大数据Hadoop中MapReduce的介绍包括编程模型、工作原理(MapReduce、MapTask、ReduceTask、Shuffle工作原理)

MapReduce概述 MapReduce是Hadoop的核心项目之一&#xff0c;它是一个分布式计算框架&#xff0c; 可用于大数据并行处理的计算模型、框架和平台&#xff0c;主要解决海量数据的计算&#xff0c;是大数据中较为熟知的分布式计算框架。 MapReduce作为分布式计算框架&#xff0…

【学习笔记】计算机网络(一)

第1章 概述 文章目录 第1章 概述1.1 计算机网络在信息时代中的作用1.2 互联网概述1.2.1 网络的网络1.2.2互联网基础结构发展的三个阶段1.2.3 互联网的标准化工作 1.3 互联网的组成1.3.1 互联网的边缘部分1.3.2 互联网的核心部分 1.4 计算机网络在我国的发展1.5 计算机网络的类别…

[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)

一、简介 本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念&#xff0c;实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 &#xff08;Deferred shading&#xff09;技术。 按照本文代码实现后&#xff0c;可以实现以下…

KubeSphere 开源社区 2024 年度回顾与致谢

随着 2024 年圆满落幕&#xff0c;我们回顾 KubeSphere 社区这一年走过的每一步&#xff0c;感慨万千。2024 年&#xff0c;KubeSphere 继续领跑云原生技术的创新与发展&#xff0c;推动开源文化的传播&#xff0c;致力于为全球开发者和企业用户提供更强大的平台和解决方案。感…

ToDesk云电脑、顺网云、网易云、易腾云、极云普惠云横测对比:探寻电竞最佳拍档

一、云电脑&#xff1a;电竞新宠崛起 在电竞游戏不断发展的今天&#xff0c;硬件性能成为了决定游戏体验的关键因素。为了追求极致的游戏画面与流畅度&#xff0c;玩家们往往需要投入大量资金购置高性能电脑。然而&#xff0c;云电脑技术的出现&#xff0c;为玩家们提供了一种…

GitCode 助力 AutoTable:共创 MyBatis 生态的自动表格管理新篇章

项目仓库https://gitcode.com/dromara/auto-table 解放双手&#xff0c;专注业务&#xff1a;MyBatis 生态的“自动表格”创新 AutoTable 是一款致力于为 MyBatis 生态赋予“自动表格”功能的创新插件。其核心理念是通过 Java 实体类自动生成和维护数据库的表结构&#xff0c…