热门标签 | 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


推荐阅读
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文详细介绍了如何解决Uploadify插件在Internet Explorer(IE)9和10版本中遇到的点击失效及JQuery运行时错误问题。通过修改相关JavaScript代码,确保上传功能在不同浏览器环境中的一致性和稳定性。 ... [详细]
  • 导航栏样式练习:项目实例解析
    本文详细介绍了如何创建一个具有动态效果的导航栏,包括HTML、CSS和JavaScript代码的实现,并附有详细的说明和效果图。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • MQTT技术周报:硬件连接与协议解析
    本周开发笔记重点介绍了在新项目中使用MQTT协议进行硬件连接的技术细节,涵盖其特性、原理及实现步骤。 ... [详细]
  • 如何在窗口右下角添加调整大小的手柄
    本文探讨了如何在传统MFC/Win32 API编程中实现类似C# WinForms中的SizeGrip功能,即在窗口的右下角显示一个用于调整窗口大小的手柄。我们将介绍具体的实现方法和相关API。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
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社区 版权所有