热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于Consul实现MagicOnion(GRpc)服务注册与发现

0.简介0.1什么是ConsulConsul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。这里所谓的服务,不仅仅包括常用的Api这些服务,也

0.简介

0.1 什么是 Consul

Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。

这里所谓的服务,不仅仅包括常用的 Api 这些服务,也包括软件开发过程当中所需要的诸如 Rpc、Redis、Mysql 等需要调用的资源。

简而言之 Consul 就是根据 Key/Value 存储了一套所有服务的 IP/Port 集合,当你 Grpc 客户端需要请求某种服务的时候,具体的 IP 与端口不需要你自己来进行指定,而是通过与 Consul Agent 通信获得某个服务下面可用的 IP/Port 集合。

而 Consul 还提供了健康检查等附加的功能,你可以通过对可用服务节点的遍历来自己进行负载均衡或者服务选择。

0.2 为什么要用 Consul

没用 Consul 之前的情况是,我 new 一个 Channel 的话,需要指定 Grpc Server 的地址与端口,一单服务挂掉或者 Grpc Server 的 IP 地址或者端口有变更,那么我还得重新更改 setting 才能够使用我的服务。

使用了 Consul 之后我只需要去 Consul Agent 里面查询我指定的服务有哪些节点可用,返回给我对应的 IP 地址和端口,我就可以进行连接了。

1.准备工作

1.1 Consul 集群安装与配置

Consul 我是直接使用 Docker 的官方 Consul 镜像来进行安装的,直接执行以下命令 pull 到最新的镜像:

docker pull consul

拿到之后我们先运行一个服务:

docker run -d --name=dev-consul-server1 -e CONSUL_BIND_INTERFACE=eth0 consul agent -server -bootstrap

之后我们再运行两个 Consul Server:

docker run -d --name=dev-consul-server2 -e CONSUL_BIND_INTERFACE=eth0 consul agent -server -retry-join 172.17.0.20

这里 172.17.0.20 是之前 dev-consul-server1 的 IP 地址。

docker run -d --name=dev-consul-server3 -e CONSUL_BIND_INTERFACE=eth0 consul agent -server -retry-join 172.17.0.20

我们可以运行 consul members 命令来查看 Consul 集群信息:

docker exec -t dev-consul-server1 consul members 
Node          Address          Status  Type    Build  Protocol  DC   Segment
5019b941791a  172.17.0.20:8301  alive   server  1.1.0  2         dc1  
ac53858f8c34  172.17.0.21:8301  alive   server  1.1.0  2         dc1  
fc3aba2ddc25  172.17.0.22:8301  alive   server  1.1.0  2         dc1  

可以看到已经有 3 个 Consul Server 启动了。

下面我们再来运行一个 Consul Client 作为服务注册与发现的端口:

docker run -d -p 8500:8500 --name=dev-consul-client -e CONSUL_BIND_INTERFACE=eth0 -e CONSUL_UI_BETA=true consul agent -retry-join 172.17.0.20 -bind 0.0.0.0 -ui -client 0.0.0.0

这里注意 -bind-client 命令是你绑定的 IP 地址,这里我直接将其与 0.0.0.0 绑定,而 -e CONSUL_UI_BETA=true 则是用于启动新版本的 WebUI 界面,-ui 是启用 WebUI 界面。

启动完成之后我们可以访问已经启动的 Client Agent 了:

2.客户端与服务端编写

在这里我以 Abp 框架作为演示,如何编写一个支持 Consul 的 Grpc 服务端与 Grpc 客户端,在演示当中所使用到的 Abp.Grpc.Server 包与 Abp.Grpc.Client 包可以从 NuGet 站点当中搜索安装,其源代码我托管到 GitHub 上面的,地址为:https://github.com/GameBelial/Abp.Grpc,欢迎 Star。

2.1 Grpc 服务端编写

2.1.1 Abp 集成

首先建立一个标准的 ASP.NET Core Web Application 程序,引入 AbpAbp.AspNetCoreAbp.Grpc.Server 包,项目取名为 Abp.Grpc.Server.Demo,类型选择空项目,在我们的 Startup 类当中编写如下代码:

using Abp.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace Abp.Grpc.Server.Demo
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            // 添加 MVC
            services.AddMvc();
            // 添加 ABP 框架,注意更改 ConfigureServices 返回值为 IServiceProvider
            return services.AddAbp();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // 启用 ABP 框架中间件
            app.UseAbp();
            // 启用 MVC 中间件
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "defaultWithArea",
                    template: "{area}/{cOntroller=Home}/{action=Index}/{id?}");

                routes.MapRoute(
                    name: "default",
                    template: "{cOntroller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

2.1.2 建立项目启动模块

新建一个 AbpGrpcServerDemoModule 类,并编写以下代码:

using Abp.AspNetCore;
using Abp.Grpc.Server.Extensions;
using Abp.Modules;

namespace Abp.Grpc.Server.Demo
{
    // 此处依赖 ABP 的 AspNetCore 模块与我们的 GRPC 服务模块
    [DependsOn(typeof(AbpAspNetCoreModule),
        typeof(AbpGrpcServerModule))]
    public class AbpGrpcServerDemoModule : AbpModule
    {
        public override void PreInitialize()
        {
            Configuration.Modules.UseGrpcService(option =>
            {
                // GRPC 服务绑定的 IP 地址
                option.GrpcBindAddress = "0.0.0.0";
                // GRPC 服务绑定的 端口号
                option.GrpcBindPort = 5001;
                // 启用 Consul 服务注册
                option.UseConsul(cOnsulOption=>
                {
                    // Consul 服务注册地址
                    consulOption.COnsulAddress= "10.0.75.1";
                    // Consul 服务注册端口号
                    consulOption.COnsulPort= 8500;
                    // 注册到 Consul 的服务名称
                    consulOption.RegistratiOnServiceName= "TestGrpcService";
                    // 健康检查接口的端口号
                    consulOption.COnsulHealthCheckPort= 5000;
                });
            })
            .AddRpcServiceAssembly(typeof(AbpGrpcServerDemoModule).Assembly); // 扫描当前程序集的所有 GRPC 服务
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(AbpGrpcServerDemoModule).Assembly);
        }
    }
}

2.1.3 编写健康检查控制器

新建一个文件夹叫做 Controllers ,并且新建一个 HealthController 类,其内容如下:

using Abp.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc;

namespace Abp.Grpc.Server.Demo.Controllers
{
    public class HealthController : AbpController
    {
        /// 
        /// 健康检查接口
        /// 
        public IActionResult Check()
        {
            return Ok("OJBK");
        }
    }
}

注意:此处应该继承自 AbpController 基类

2.1.4 编写 RPC 服务

新建一个 RpcServices 文件夹,并且新建一个 TestGrpcService 文件,其内容如下:

using MagicOnion;
using MagicOnion.Server;

namespace Abp.Grpc.Server.Demo.RpcServices
{
    public interface ITestGrpcService : IService
    {
        UnaryResult Sum(int x, int y);
    }

    public class TestGrpcService : ServiceBase, ITestGrpcService
    {
        public UnaryResult Sum(int x, int y)
        {
            return UnaryResult(x + y);
        }
    }
}

可以看到我们编写了一个简单的 Sum 方法,该方法接收两个 int 类型的参数,计算其和并返回。

2.1.5 编写 Dockerfile 文件

因为我们的 Consul 是放在 Docker 容器当中的,所以我们将我们的站点发布出去,并且编写一个 Dockerfile 文件,内容如下:

FROM microsoft/dotnet
ENV ASPNETCORE_URLS http://+:5000
## 开放 5000 网站端口
EXPOSE 5000
## 开放 5001 RPC 端口
EXPOSE 5001

WORKDIR /app
COPY ./ .

ENTRYPOINT [ "dotnet","Abp.Grpc.Server.Demo.dll" ]

将其拷贝到发布好的站点,并且执行 docker build 命令:

PS D:\Project\DEMO\Abp.Grpc.Server.Demo\Abp.Grpc.Server.Demo\bin\Release\netcoreapp2.1\publish> docker build -t grpc-server-demo .
Sending build context to Docker daemon   29.9MB
Step 1/7 : FROM microsoft/dotnet
 ---> d8381e1175a1
Step 2/7 : ENV ASPNETCORE_URLS http://+:5000
 ---> Using cache
 ---> da7659cff6d2
Step 3/7 : EXPOSE 5000
 ---> Using cache
 ---> 7ecfc480ad43
Step 4/7 : EXPOSE 5001
 ---> Using cache
 ---> 75f10934ad1e
Step 5/7 : WORKDIR /app
 ---> Using cache
 ---> dee9739da4cd
Step 6/7 : COPY ./ .
 ---> 1a5acc1f0298
Step 7/7 : ENTRYPOINT [ "dotnet","Abp.Grpc.Server.Demo.dll" ]
 ---> Running in a46efbabc7fc
Removing intermediate container a46efbabc7fc
 ---> 321201373ecf
Successfully built 321201373ecf
Successfully tagged grpc-server-demo:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories

构建完镜像之后,我们运行该镜像:

docker run -d -p 5000:5000 -p 5001:5001 --name=grpc-server-demo grpc-server-demo

2.1.6 查看 Consul

来到 Consul 的 UI 界面查看效果:

可以看到已经成功注册,说明已经成功了。

2.2 Grpc 客户端编写

2.2.1 Abp 集成

首先建立一个标准的 .Net Console 程序,引入 Abp.Grpc.Client 包,在我们的 Program 类当中编写如下代码:

using Abp.Grpc.Client.Demo.RpcServices;
using Abp.Grpc.Client.Utility;
using System;

namespace Abp.Grpc.Client.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var bootstrapper = AbpBootstrapper.Create())
            {
                bootstrapper.Initialize();

                Console.WriteLine("Press enter to stop application...");
                Console.ReadLine();
            }

            Console.WriteLine("Hello World!");
        }
    }
}

2.2.2 建立项目启动模块

然后我们新建一个 AbpGrpcClientDemoModule 类,该类一样是一个启动模块,用于配置连接信息:

using Abp.Grpc.Client.Configuration;
using Abp.Grpc.Client.Extensions;
using Abp.Modules;

namespace Abp.Grpc.Client.Demo
{
    [DependsOn(typeof(AbpGrpcClientModule))]
    public class AbpGrpcClientDemoModule : AbpModule
    {
        public override void PreInitialize()
        {
            Configuration.Modules.UseGrpcClient(new ConsulRegistryConfiguration("10.0.75.1", 8500, null));
        }
    }
}

很简单,直接配置 Consul 注册的 IP 与端口号即可。

2.2.3 建立 RPC 接口定义

要调用我们 Server 提供的 RPC 端口的话,得编写一个接口定义,就是我们在 Server 项目里面写的那个,新建一个 ITestGrpcService 接口,内容如下:

using MagicOnion;

namespace Abp.Grpc.Client.Demo.RpcServices
{
    public interface ITestGrpcService : IService
    {
        UnaryResult Sum(int x, int y);
    }
}

2.2.4 调用 RPC 接口

using Abp.Grpc.Client.Demo.RpcServices;
using Abp.Grpc.Client.Utility;
using System;

namespace Abp.Grpc.Client.Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var bootstrapper = AbpBootstrapper.Create())
            {
                bootstrapper.Initialize();
                
                // 调用接口
                var cOnnectionUtility= bootstrapper.IocManager.Resolve();
                var result = connectionUtility.GetRemoteService("TestGrpcService").Sum(10, 5).ResponseAsync.Result;
                // 展示结果
                Console.WriteLine("Result:" + result);

                Console.WriteLine("Press enter to stop application...");
                Console.ReadLine();
            }

            Console.WriteLine("Hello World!");
        }
    }
}

调用接口的话,需要注入 IGRpcConnectionUtility 工具类,使用其 GetRemoteService 方法就可以调用你的远程方法,记住一定要传入有效的服务名称。

2.2.5 编写 Dockerfile 文件

一样的,我们新建一个 Dockerfile 文件,将我们的 client 也打包成镜像:

FROM microsoft/dotnet

WORKDIR /app
COPY ./ .

ENTRYPOINT [ "dotnet","Abp.Grpc.Client.Demo.dll" ]

内容很简单,一样的复制到发布成功的文件夹,构建镜像:

docker build -t grpc-client-demo .

构建之后运行:

docker run grpc-client-demo

不出意外的话会看到如下输出:

PS D:\Project\DEMO\Abp.Grpc.Client.Demo\Abp.Grpc.Client.Demo\bin\Release\netcoreapp2.1\publish> docker run grpc-client-demo
Result:15
Press enter to stop application...
Hello World!

3.代码分析

抛开 ABP 框架部分的代码,其实要实现服务注册很简单,核心就是 ConsulClient 这个类,下面就来分析一下 Abp.Grpc 库里面的代码。

3.1 注册服务

注册服务其核心就在于 ConsulClient.Agent.ServiceRegister() 方法,通过传入一个构造好的 AgentServiceRegistration 对象就可以成功注册一个服务到 Consul。

例如:

_agentServiceRegistration = new AgentServiceRegistration
{
    ID = Guid.NewGuid().ToString(),// 唯一ID
    Name = config.RegistrationServiceName,// 注册的服务名称
    Address = currentIpAddress, // 服务提供者地址
    Port = config.GrpcBindPort, // 服务提供者端口
    Tags = new[] { "Grpc", $"urlprefix-/{config.RegistrationServiceName}" }, // 注册的服务标签
    Check = new AgentServiceCheck // 健康检查
    {
        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5), // 取消注册时间
        Interval = TimeSpan.FromSeconds(10), // 检查间隔
        Status = HealthStatus.Passing, // 检查通过的状态
        Timeout = TimeSpan.FromSeconds(5), // 超时时间
        HTTP = $"http://{currentIpAddress}:{config.ConsulHealthCheckPort}/health/check" // 健康检查接口地址
    }
};

构建成功后通过 ConsulClient.Agent.ServiceRegister() 方法即可注册到 Consul。

取消注册则是通过 ConsulClient.Agent.ServiceDeregister 方法。

3.2 发现服务

服务发现相较于服务注册简单得多,只需要通过 ConsulClient.Catalog.Services 遍历其结果即可获得所有节点,并且通过 LINQ 来筛选出指定 tag 的服务。

4.其他相关参考资料

田园里的蟋蟀:Docker & Consul & Fabio & ASP.NET Core 2.0 微服务跨平台实践)

Edison Chou:.NET Core微服务之基于Consul实现服务治理

Cecilphillip:Using Consul for Service Discovery with ASP.NET Core

5.所使用到的代码

Abp.Grpc 库代码:https://github.com/GameBelial/Abp.Grpc

DEMO 代码:

https://github.com/GameBelial/Abp.Grpc.Server.Demo

https://github.com/GameBelial/Abp.Grpc.Client.Demo


推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Redis API
    安装启动最简启动命令行输入验证动态参数启动配置文件启动常用配置通用命令keysbdsize计算key的总数exists判断是否存在delkeyvalue删除指定的keyvalue成 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • ElasticSerach初探第一篇认识ES+环境搭建+简单MySQL数据同步+SpringBoot整合ES
    一、认识ElasticSearch是一个基于Lucene的开源搜索引擎,通过简单的RESTfulAPI来隐藏Lucene的复杂性。全文搜索,分析系统&# ... [详细]
  • 面试经验分享:华为面试四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试
    最近有朋友去华为面试,面试经历包括四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试。80%的人都在第一轮电话面试中失败,因为缺乏基础知识。面试问题涉及 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 容器管理与容器监控influxDB
    容器管理与容器监控-influxDB什么是influxDBinfluxDB安装(1)下载镜像(2)创建容器(3 ... [详细]
author-avatar
bjkml
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有