主机的概念
首先,我们来了解主机的概念。在Asp.net Core主机文档中,将主机定义为: 主机是封装应用资源的对象,将应用的所有相互依赖资源包括在一个对象中可控制应用启动和正常关闭。换句话说,就是用于托管和管理应用资源和应用生命周期的。
在.net 中,有两种类型的主机,一种是泛型主机,一种是Web主机。区别主要在于Web主机提供了web容器和配置http请求处理管道的能力(所以web主机在启动后就有一个http端口和可以通过StartUp类配置http中间件),而通用主机并没有这个能力。
对于silky来说,由silky框架开发的微服务集群存在两种通信方式:
接受外部的http请求,并且通过webapi路由到相应的服务条目,然后通过本地或远程执行器执行对于的服务条目方法。
服务与服务内部之间通过dotnetty实现的rpc进行网络通信。
所以,根据不同的使用场景,我们可以使用不同的主机来托管应用。
对于 场景1 ,我们只能选择通过Web主机来托管应用,因为他必须提供Http服务的能力,是集群内部对外提供webapi访问的入口应用,一般可以用于构建网关,当然,如果用于托管业务应用也可以,这样的话,该业务应用也能直接对外部提供webapi服务。
但是,一般场景下,普通的业务微服务我们并不需要它直接对外部提供webapi服务。所以,更多通信场景是场景2。 所以,对于一般的业务微服务而言,我们可以使用他 通用主机 来构建微服务应用。
WebAPI 调用示例
以脚手架模板生成的 ISystemAppService 为例,说明 WebAPI 的完整调用链路。
1. 服务接口定义(合约层)
// SilkyApp.Application.Contracts/System/ISystemAppService.cs
[ServiceRoute(template: "api/system/{appservice=silkyapp}")]
public interface ISystemAppService
{
/// <summary>
/// 获取当前应用详细信息
/// </summary>
[AllowAnonymous]
GetSystemInfoOutput GetInfo();
}
silky 根据方法名 GetInfo 自动推断 HTTP 动词为 GET,剥离 Get 前缀后得到路由段 info。加上接口路由模板,最终生成的 WebAPI 路由为:
GET /api/system/silkyapp/info
2. 请求示例
使用 curl:
curl -X GET http://localhost:5000/api/system/silkyapp/info \
-H "Accept: application/json"
使用 HTTP 格式(如 Postman / ReST Client):
GET /api/system/silkyapp/info HTTP/1.1
Host: localhost:5000
Accept: application/json
使用 C# HttpClient:
using var client = new HttpClient { BaseAddress = new Uri("http://localhost:5000") };
var response = await client.GetAsync("/api/system/silkyapp/info");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
3. 响应示例
{
"hostName": "SilkyAppHost",
"environment": "Development",
"addresses": [
"http://127.0.0.1:2200"
]
}
4. 调用链路
外部请求进入后,在网关或 Web 主机内部的完整执行链路如下:
外部 HTTP 客户端
│ GET /api/system/silkyapp/info
▼
网关 / Web 主机(Web 主机,监听 HTTP 端口)
│ ASP.NET Core 路由中间件匹配到 ServiceEntry
▼
DefaultExecutor.Execute()
│ 判断 ISystemAppService 是否在当前实例:
│ ├── 本地实例(Web 主机托管 Application 层)→ LocalExecutor → 直接执行
│ └── 远程实例(网关模式)→ RemoteExecutor → DotNetty RPC → 业务微服务
▼
GetSystemInfoOutput(序列化为 JSON 返回)
提示
若使用网关模式(ConfigureSilkyGatewayDefaults),网关本身不包含 Application 层实现,ISystemAppService 在网关侧没有本地实现类,因此会通过 RPC 将请求转发到注册了该服务的业务微服务实例执行。
若使用 Web 主机模式(ConfigureSilkyWebHostDefaults)并且该项目本身包含 Application 实现层,则请求直接在本地执行,无需经过 RPC。
注册Silky微服务应用
我们在silky文档首页看到,构建一个最简单silky微服务应用,只需要通过如下一行简单的代码就可以做到。
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureSilkyGeneralHostDefaults();
}
我们通过包 Silky.Agent.Host 提供的 HostBuilderExtensions提供的扩展方法来构建Silky微服务应用。Silky.Agent.Host 包依赖了用于构建Silky微服务应用其他的必要的包。
HostBuilderExtensions通过实现IHostBuilder的扩展方法来注册Silky微服务应用。在HostBuilderExtensions类中,提供了诸如: ConfigureSilkyWebHostDefaults、ConfigureSilkyGateway、ConfigureSilkyGeneralHostDefaults等IHostBuilder的扩展方法。在这些方法中,无论哪个方法,我们看到,核心的代码就是 hostBuilder.RegisterSilkyServices<T>(),下面,我们深入了解 hostBuilder.RegisterSilkyServices<T>()究竟完成了什么工作。
在Silky.Core包中,我们通过HostBuilderExtensions提供的扩展方法RegisterSilkyServices实现了服务引擎(IEngine)的构建、模块的依赖与注册、服务的依赖与注册、配置文件的装载、模块的顺序执行等工作。
// HostBuilderExtensions.cs(Silky.Core)
public static IHostBuilder RegisterSilkyServices<T>(
this IHostBuilder builder,
Action<SilkyApplicationCreationOptions> optionsAction = null)
where T : SilkyModule // 泛型约束:T 必须是 SilkyModule 的子类(启动模块)
{
IEngine engine = null;
var options = new SilkyApplicationCreationOptions();
optionsAction?.Invoke(options);
builder
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureServices((context, services) => // (2) 服务注册 & 服务引擎构建
{
engine = services.AddSilkyServices<T>(
context.Configuration,
context.HostingEnvironment,
options); // 传入应用创建选项
})
.ConfigureContainer<ContainerBuilder>(containerBuilder => // (3) Autofac 容器注册
{
engine!.RegisterModules(containerBuilder); // 注册所有模块
engine!.RegisterDependencies(containerBuilder); // 注册约定接口的实现
})
.ConfigureAppConfiguration((hostBuilder, configurationBuilder) => // (1) 装载配置文件
{
hostBuilder.Configuration = ConfigurationHelper
.BuildConfiguration(configurationBuilder,
hostBuilder.HostingEnvironment,
options.Configuration); // JSON/YAML/环境变量均在此加载
});
return builder;
}
注意
上述代码中,ConfigureServices、ConfigureContainer、ConfigureAppConfiguration 三个方法的声明顺序与实际执行顺序不同。代码注释中的括号数字标注的是实际执行顺序:(1) ConfigureAppConfiguration → (2) ConfigureServices → (3) ConfigureContainer。
该方法约束了泛型参数
T必须为SilkyModule的子类,传入的类型即作为启动模块,框架通过它的依赖声明递归发现所有需要加载的模块。将服务提供者工厂替换为
AutofacServiceProviderFactory,这样,我们就可以通过Autofac来实现服务的依赖注入。IHostBuilder有三个核心的配置方法: (1)ConfigureAppConfiguration(2)ConfigureServices(3)ConfigureContainer; 在应用启动时,将按ConfigureAppConfiguration-->ConfigureServices-->ConfigureContainer依次执行。
3.1 在执行 ConfigureAppConfiguration 时,通过 ConfigurationHelper.BuildConfiguration() 统一加载所有配置文件(JSON、YAML、环境变量);
3.2 在执行ConfigureServices方法时,通过IServiceCollection的扩展方法AddSilkyServices<T>()实现必要的服务注册和 服务引擎(IEngine) 的构建;
3.3 在执行ConfigureContainer方法时,主要通过Autofac的ContainerBuilder实现服务的依赖注册;
- 在完成上述指定的方法后,主机接下来将会执行后台任务
InitSilkyHostedService,并根据模块的依赖顺序,依次执行各个模块的启动方法。完成服务以及服务条目的发现、向服务注册中心注册服务信息以及启动rpc消息监听器等工作。
