为了提升 Dubbo 里程碑版本2.7.0的使用体验,我们于去年年中启动了 Dubbo Admin 的重构计划,并作为Dubbo生态的子项目,于近期发布了v0.1,重构后的项目在结构上的变化如下:
将后端框架从webx替换成spring boot
前端采用Vue和Vuetify.js作为开发框架
移除velocity模板
集成swagger,提供api管理功能
当前版本的Dubbo Admin包含了之前版本中的绝大部分功能,包括服务治理,服务查询等,同时支持了Dubbo2.7中服务治理的新特性。
配置规范
由于在Dubbo2.7中,配置中心和注册中心做了分离,并且增加了元数据中心,因此Dubbo Admin的配置方式也做了更新,application.properties中的配置如下:
admin.registry.address=zookeeper://127.0.0.1:2181
admin.config-center=zookeeper://127.0.0.1:2181
admin.metadata.address=zookeeper://127.0.0.1:2181
也可以和Dubbo2.7一样,在配置中心指定元数据和注册中心的地址,以zookeeper为例,配置的路径和内容如下:
# /dubbo/config/dubbo/dubbo.properties
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
配置中心里的地址会覆盖掉本地application.properties的配置
功能介绍
功能上,主要延续了之前版本的功能,包括服务查询和服务治理,2.7版本在服务治理的功能上有了很大的改进,这些改进也大部分都会以Dubbo Admin作为入口来体现。
标签路由
标签路由是Dubbo2.7引入的新功能,配置以应用作为维度,给不同的服务器打上不同名字的标签,配置如下图所示:
1548675129621_1f2f3f68_4f35_4105_97e2_c4a0d7dc62c8_jpeg
调用的时候,客户端可以通过setAttachment的方式,来设置不同的标签名称,比如本例中,setAttachment(tag1),客户端的选址范围就在如图所示的三台机器中,可以通过这种方式来实现流量隔离,灰度发布等功能。
应用级别的服务治理
在Dubbo2.6及更早版本中,所有的服务治理规则都只针对服务粒度,如果要把某条规则作用到应用粒度上,需要为应用下的所有服务配合相同的规则,变更,删除的时候也需要对应的操作,这样的操作很不友好,因此Dubbo2.7版本中增加了应用粒度的服务治理操作,对于条件路由(包括黑白名单),动态配置(包括权重,负载均衡)都可以做应用级别的配置:
1548675095660_2abe00e4_7a3c_4c49_8649_0cc81e00e324_jpeg
上图是条件路由的配置,可以按照应用名,服务名两个维度来填写,也可以按照这两个维度来查询。
1548675169501_57b22613_ba3a_462b_b3ba_af34ea778589_jpeg
条件路由,标签路由和动态配置都采用了yaml格式的文本编写,其他的规则配置还是采用了表单的形式。
关于兼容性
Dubbo2.6到Dubbo2.7,服务治理发生了比较大的变化,Dubbo Admin兼容两个版本的用法:
对于服务级别的配置,会按照Dubbo2.6(URL)和Dubbo2.7(配置文件)两种格式进行写入,保证Dubbo2.6的客户端能够正确读取,解析规则
对于应用级别的配置,包括标签路由,只会按照Dubbo2.7的格式进行写入,因为Dubbo2.6无此功能,不需要做向前兼容。
Dubbo Admin只会按照Dubbo2.7的格式进行配置读取,因此,所有在Dubbo Admin上做的配置都可以被读到,但是之前遗留的,Dubbo2.6格式的URL无法被读取。
对于同一个应用或者服务,每种规则只能够配置一条,否则新的会覆盖旧的。
配置管理
配置管理也是配合Dubbo2.7新增的功能,在Dubbo2.7中,增加了全局和应用维度的配置。
全局配置:
1548675196442_4611bfd9_5077_4bd2_a2b1_ad89c5eefc13_jpeg
全局配置里可以指定注册中心,元数据中心的地址,服务端和客户端的超时时间等,这些配置在全局内生效。除了配置写入,也可以用来查看。如果使用zookeeper作为注册中心和元数据中心,还可以看到配置文件所在位置的目录结构。
应用和服务配置
1548675223170_772fd97b_f49f_4cf6_be40_34355f8159a1_jpeg
应用级别的配置可以为应用或者应用内的服务指定配置,在服务维度上,需要区分提供者和消费者。dubbo.reference.{serviceName}表示作为该服务消费者的配置,dubbo.provider.{servcieName}表示作为该服务提供者的配置。优先级服务 > 应用 > 全局。其中注册中心和元数据中心的地址,只能在全局配置中指定,这也是Dubbo2.7中推荐的使用方式。
元数据和服务测试
元数据是Dubbo2.7中新引入的元素,主要的使用场景就在Dubbo Admin中,主要体现在两个地方:
服务详情展示:
1548675234502_67cefa2f_e180_4715_a203_a98bf515fe4f_jpeg
跟之前版本相比,Dubbo2.7中增加了对服务方法完整签名的记录,因此服务详情中也增加了方法信息的详情,可以看到方法名,方法参数列表以及返回值信息。
服务测试:
1548675056214_730b7a7b_d4af_4a57_ab3f_380c5afcdabb_jpeg
更重要的,元数据为服务测试提供了数据基础,可以在页面上调用真实的服务提供者,方便测试,也不需要为了调用服务去搭建一套Dubbo环境以及编写消费端代码。服务测试的详细使用方式可通过点击这里进行了解。
添加 SessionController 用于用户登录,内容如下:
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Mvc;
namespace ServiceA.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SessionController : ControllerBase
{
public async Task<string> Login(UserRequestModel userRequestModel)
{
// discover endpoints from metadata
var client = new HttpClient();
DiscoveryResponse disco = await client.GetDiscoveryDocumentAsync("http://127.0.0.1:8021");
if (disco.IsError)
{
return "认证服务器未启动";
}
TokenResponse tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "ServiceAClient",
ClientSecret =dasheng178.com "ServiceAClient",
UserName = userRequestModel.Name,
Password = userRequestModel.Password
});
return tokenResponse.IsError ? tokenResponse.Error : tokenResponse.AccessToken;
}
}
public class UserRequestModel
{
[Required(ErrorMessage = "用户名称不可以为空")]
public string Name {www.078886.cn get; set; }
[Required(ErrorMessage = "用户密码不可以为空")]
public string Password { get; set; }
}
}
添加 HealthController 用于 Consul 进行服务健康检查,内容如下:
using Microsoft.AspNetCore.Mvc;
namespace ServiceA.Controllers
{
[Route("api/[controller]"), ApiController]
public class HealthController : ControllerBase
{
/// <summary>
/// 健康检查
/// </summary>
/// <returns>www.yongshiyule178.com</returns>
[HttpGet]
public IActionResult Get()
{
return Ok();
}
}
}
更改 ValuesController.cs 内容如下:
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ServiceA.Controllers
{
[Authorize] //添加 Authorize Attribute 以使该控制器启用认证
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new[] { "value1", "value2" };
}
}
}
注意,以上基本完成了 ServiceA 的服务构建,但在实际应用中应做一些修改,例如:IdentityServer 地址应在 appsettings.json 中进行配置,不应把地址分散于项目中各处;认证服务启用最好在全局启用,以防止漏写等等。ServiceB 的内容与 ServiceA 大致相似,因此文章中将不再展示 ServiceB 的构建过程。
Gateway 构建
添加ASP.Net Web
添加空项目
打开程序包管理器控制台输入命令:
csharp install-package Ocelot //添加 Ocelot
csharp install-package Ocelot.Provider.Consul // 添加 Consul 服务发现
添加 ocelot.json 文件,内容如下
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/ServiceA/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
"ServiceName": "ServiceA", //consul 服务中 ServiceA 的名称
"LoadBalancerOptions": {
"Type": "LeastConnection"
}
},
{
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/ServiceB/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
"ServiceName": "ServiceB", //consul 服务中 ServiceB 的名称
"LoadBalancerOptions": {
"Type": "LeastConnection"
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": { // Consul 服务发现配置
"Host": "localhost", // Consul 地址
"Port": 8500,
"Type": "Consul"
}
}
}
删除 StartUp.cs 文件,在 Program.cs 文件中添加如下内容
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Consul;
namespace ApiGateway
{
public class Program
{
public static void Main(string[] args)
{
new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) =>
{
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile($"appsettings.www.ouyi3pt1.cn{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
.AddJsonFile(www.dituyule.org"ocelot.json")
.AddEnvironmentVariables(www.yingxionghui1.cn);
})
.ConfigureServices(services =>
{
services.AddOcelot().AddConsul();
})
.ConfigureLogging((hostingContext, logging) =>
{
//add your logging
})
.UseIISIntegration()
.Configure(app =>
{
app.UseOcelot().Wait();
})
.Build()
.Run();
}
}
}
注意:打开 Gateway.csproj 文件,更改
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
为
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>www.fengshen157.com/ OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
至此,一个基础网关基本构建完成。
构建 Consul 服务
使用 Chocoletey 安装 Consul,
choco install consul
新建一个文件夹以保存 Consul 服务配置
在 consul.d 文件夹中添加配置文件,内容如下:
{
"services": [{
"ID": "ServiceA",
"Name": "ServiceA",
"Tags": [
"ServiceAWebApi", "Api"
],
"Address": "127.0.0.1",
"Port": 8010,
"Check": {
"HTTP": "http://127.0.0.1:8010/Api/health",
"Interval": "10s"
}
}, {
"id": "ServiceB",
"name": "ServiceB",
"tags": [
"ServiceBWebApi","Api"
],
"Address": "127.0.0.1",
"Port": 8011,
"Check": [{
"HTTP": "http://127.0.0.1:8011/Api/health",
"Interval": "10s"
}
]
}
]
}
启动 consul 服务
consul agent -dev -config-dir=./consul.d
启动后在浏览器中输入 http://localhost:8500/ui/ 以查看Consul服务
Postman 验证
F5 启动 Gateway 项目,启动 Postman 发送请求到 ServiceA 获取 Token。
使用 Token 请求 ServiceA Values 接口
当尝试使用 ServiceA 获取到的 Token 去获取 ServiceB 的数据时,请求也如意料之中返回 401
总结
至此,一个由 .NET Core、IdentityServer4、Ocelot、Consul实现的基础架构搭建完毕。源码地址