silky微服务定义了三种类型的代理主机,开发者能够根据须要选择合适的silky代理主机托管微服务应用。代理主机定义了一个Startup
模块,该模块给出了使用该种类型主机所必须依赖的模块。html
该类型的主机通常用于托管业务应用,服务内部之间经过rpc进行通讯,不支持与微服务集群与外部进行通讯,web代理主机能够经过引用该类型的微服务的应用接口,经过应用接口生成的代理与该微服务进行通讯。该类型的微服务使用.net的通用主机进行托管引用。定义的Startup
模块以下所示:git
[DependsOn(typeof(ZookeeperModule), typeof(DotNettyTcpModule), typeof(RpcProxyModule), typeof(TransactionTccModule), typeof(ValidationModule), typeof(FluentValidationModule), typeof(RedisCachingModule), typeof(TransactionRepositoryRedisModule) )] public abstract class GeneralHostModule : StartUpModule { }
开发者若是须要建立一个微服务应用,只须要在建立一个控制台应用,经过nuget包管理工具安装Silky.Agent.GeneralHost
包,在主函数中注册SilkyServices
,并指定启动模块便可。github
public static async Task Main(string[] args) { await CreateHostBuilder(args).Build().RunAsync(); } private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .RegisterSilkyServices<AccountHostModule>() ; }
开发者经过继承GeneralHostModule
模块定义启动模块。能够经过DependsOn
依赖自定义的模块或是Silky提供的模块。web
启动模块以下所示:redis
// // [DependsOn(typeof(SilkySkyApmAgentModule), // typeof(JwtModule), // typeof(MessagePackModule))] public class AccountHostModule : GeneralHostModule { }
该类型的主机能够经过http端口与外部经过http协议进行通讯,经过引用其余业务微服务应用的应用接口,根据路由模版生成restful风格的webapi,开发者能够经过配置生成在线的swagger
文档。直观的看到在线api文档和进行调试。生成的swagger文档能够根据引用的应用接口进行分组。算法
web代理主机预约义的Startup
模块指定了web代理主机必须依赖的silky模块,以下所示:docker
[DependsOn(typeof(RpcProxyModule), typeof(ZookeeperModule), typeof(SilkyHttpCoreModule), typeof(DotNettyModule), typeof(ValidationModule), typeof(FluentValidationModule), typeof(RedisCachingModule) )] public abstract class WebHostModule : StartUpModule { }
该类型的主机通常用于网关,提供了外部与微服务集群进行通讯的桥梁,该类型的主机使用.net的web主机进行托管应用。开发者能够建立一个aspnetcore项目,经过安装Silky.Agent.WebHost
包便可建立web代理主机,须要同时指定启动模块和Startup
类。shell
public async static Task Main(string[] args) { await CreateHostBuilder(args).Build().RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) { var hostBuilder = Host.CreateDefaultBuilder(args) .RegisterSilkyServices<GatewayHostModule>() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); return hostBuilder; }
web代理主机的启动模块须要继承WebHostModule
,启动模块GatewayHostModule
以下所示:数据库
public class GatewayHostModule : WebHostModule { }
须要在Startup
类注册Silky
请求管道,Startup
类以下所示:json
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment() || env.EnvironmentName == "ContainerDev") { app.UseDeveloperExceptionPage(); } app.ConfigureSilkyRequestPipeline(); } }
websocket代理主机与通用代理主机基本一致,websocket代理主机具体提供ws服务的能力,web主机能够经过ws代理与websocket代理主机的ws服务进行通讯。
websocket代理主机的启动模块以下所示:
[DependsOn(typeof(ZookeeperModule), typeof(DotNettyTcpModule), typeof(RpcProxyModule), typeof(TransactionTccModule), typeof(WebSocketModule), typeof(ValidationModule), typeof(FluentValidationModule), typeof(RedisCachingModule), typeof(TransactionRepositoryRedisModule) )] public abstract class WebSocketHostModule : StartUpModule { }
开发者能够经过WebSocket
配置节点对ws服务进行配置,ws服务的默认端口为3000
,可是通常地,与ws服务创建握手时,不该该与ws服务直接进行握手,而是应该经过web代理主机的代理中间件进行握手,因此若是开发者使用ws服务,必需要在web代理主机安装Silky.WebSocket.Middleware
。
ws服务的建立与通用代理主机的建立一致,只须要将启动模块继承的基类修改成WebSocketHostModule
便可。
ws服务的定义以下:
public class WsTestAppService : WsAppServiceBase, IWsTestAppService { public async Task Echo(string businessId, string msg) { if (BusinessSessionIds.TryGetValue(businessId, out var sessionIds)) { foreach (var sessionId in sessionIds) { SessionManager.SendTo($"message:{msg},sessionId:{sessionId}", sessionId); } } else { throw new BusinessException($"不存在businessId为{businessId}的会话"); } } }
须要注意的时,在创建握手过程当中,必需要指定hashkey
从而保证每次回话的微服务实例都是同一个,更多关于ws服务请查看。
silky微服务使用拦截器和filter实现了TCC分布式事务,在tcc分布式事务过程当中,将事务参与者的调用参数做为undolog日志保存到数据仓库中(当前实现了redis做为undo日志的数据存储器),并在后台执行做业检查分布式事务的执行状况,从而保证数据的最终一致性。
[Transaction]
特性进行标识该接口是一个分布式应用方法。[Transaction] Task<GetOrderOutput> Create(CreateOrderInput input);
[TccTransaction]
特性标识,并指定ConfirmMethod
方法和CancelMethod
,指定实现的ConfirmMethod
方法和CancelMethod
必须为public
,方法参数与应用实现方法的保持一致。try方法若是须要向ConfirmMethod
方法和CancelMethod
传递参数经过RpcContext.Context
进行。[TccTransaction(ConfirmMethod = "OrderCreateConfirm", CancelMethod = "OrderCreateCancel")] [UnitOfWork] public async Task<GetOrderOutput> Create(CreateOrderInput input) { var orderOutput = await _orderDomainService.Create(input); return orderOutput; } [UnitOfWork] public async Task<GetOrderOutput> OrderCreateConfirm(CreateOrderInput input) { var orderId = RpcContext.Context.GetAttachment("orderId"); var order = await _orderDomainService.GetById(orderId.To<long>()); order.Status = OrderStatus.Payed; order.UpdateTime = DateTime.Now; order = await _orderDomainService.Update(order); return order.Adapt<GetOrderOutput>(); } [UnitOfWork] public async Task OrderCreateCancel(CreateOrderInput input) { var orderId = RpcContext.Context.GetAttachment("orderId"); if (orderId != null) { // await _orderDomainService.Delete(orderId.To<long>()); var order = await _orderDomainService.GetById(orderId.To<long>()); order.Status = OrderStatus.UnPay; await _orderDomainService.Update(order); } }
silky的服务定义很是简单,在这里的服务指的是应用服务,与传统MVC的Controller
的概念一致。
您只须要在一个业务微服务应用中,新增应用接口层,通常地,咱们能够命名为Project.IApplication
或是Project.Application.Contracts
,并新增应用接口,在应用接口中经过[ServiceRoute]
特性进行标识,并在Project.Application
项目中实现该接口。
您能够经过[ServiceRoute]
指定该应用服务的路由模板, 以及是否容许多个实现。
例如:
namespace Silky.Account.Application.Contracts.Accounts { /// <summary> /// 帐号服务 /// </summary> [ServiceRoute] public interface IAccountAppService { /// <summary> /// 新增帐号 /// </summary> /// <param name="input">帐号信息</param> /// <returns></returns> Task<GetAccountOutput> Create(CreateAccountInput input); } }
在应用层中实现该接口:
namespace Silky.Account.Application.Accounts { public class AccountAppService : IAccountAppService { private readonly IAccountDomainService _accountDomainService; public AccountAppService(IAccountDomainService accountDomainService) { _accountDomainService = accountDomainService; } public async Task<GetAccountOutput> Create(CreateAccountInput input) { var account = await _accountDomainService.Create(input); return account.Adapt<GetAccountOutput>(); } } }
应用接口能够被其余微服务应用或是被网关应用引用。其余微服务能够经过应用接口生成代理,并经过内部实现的rpc框架与该微服务进行通讯。silky的rpc通讯支持tcc方式的分布式事务,详情见上节。
应用接口被网关引用,web host主机能够经过该应用服务接口生成相应的webapi,并能够生成swagger在线文档。经过http请求,从而实现服务与集群外部进行通讯,当http请求到达webhost主机后,silky中间件经过webapi和请求方法路由到服务条目,而后经过内部实现的rpc通讯与微服务应用进行通讯。
RPC的过滤器: rpc通讯支持两种类型的过滤器,在客户端发起请求过程当中,会依次调用开发者定义的IClientFilter
过滤器,服务端收到请求后,会依次调用IServerFilter
而后再执行应用方法自己。
RpcContext: 能够经过RpcContext.Context
添加或是获取本次rpc调用的Attachments
参数。固然,开发者也能够经过注入IRpcContextAccessor
获取本次通讯的上线文参数RpcContext
。
获取当前登陆用户: 开发者能够经过NullSession.Instance
获取当前登陆用户,若是您已经登陆系统,那么经过该接口能够获取到当前登陆用户的userId
、userName
等信息。
针对每一个服务条目(应用服务接口方法),都实现了服务治理,开发者能够经过governance
或是[Governance()]
特性对服务的最大并发量、负载均衡算法、执行超时时间、是否使用缓存拦截、失败回调接口、接口是否对外网屏蔽等等属性进行配置。
如下描述了以服务条目为治理单位的属性表单:
属性 | 说明 | 缺省值 | 备注 |
---|---|---|---|
AddressSelectorMode | 负载均衡策略 | Polling(轮询) | 负载均衡算法支持:Polling(轮询)、Random(随机)、HashAlgorithm(哈希一致性,根据rpc参数的第一个参数值) |
ExecutionTimeout | 执行超时时间 | 3000(ms) | 单位为(ms),超时时发生熔断,-1表示在rpc通讯过程当中不会超时 |
CacheEnabled | 是否启用缓存拦截 | true | rpc通讯中是否启用缓存拦截 |
MaxConcurrent | 容许的最大并发量 | 100 | |
FuseProtection | 是否开启熔断保护 | true | |
FuseSleepDuration | 熔断休眠时长 | 60(s) | 发生熔断后,多少时长后再次重试该服务实例 |
FuseTimes | 服务提供者容许的熔断次数 | 3 | 服务实例连续n次发生熔断端,服务实例将被标识为不健康 |
FailoverCount | 故障转移次数 | 0 | rpc通讯异常状况下,容许的从新路由服务实例的次数,0表示有几个服务实例就转移几回 |
ProhibitExtranet | 是否禁止外网访问 | false | 该属性只容许经过GovernanceAttribute 特性进行设置 |
FallBackType | 失败回调指定的类型 | null | 类型为Type ,若是指定了失败回调类型,那么在服务执行失败,则会执行该类型的Invoke 方法,该类型,必需要继承IFallbackInvoker 该接口 |
开发者还能够经过[Governance()]
特性对某个服务方法进行标识,被该特性标识的治理属性将覆盖微服务的配置/缺省值。
为提升应用的响应,silky支持缓存拦截。缓存拦截在应用服务接口方法上经过缓存拦截特性进行设置,在silky框架中,存在以下三中类型的缓存特性,分别对数据缓存进行新增、更新、删除。
设置缓存特性--GetCachingInterceptAttribute
更新缓存特性--UpdateCachingInterceptAttribute
删除缓存特性--RemoveCachingInterceptAttribute
使用缓存拦截,必需要保证缓存一致性。在rpc通讯过程当中,使用缓存拦截,同一数据的缓存依据可能会不一样(设置的KeyTemplate
,例如:缓存依据可能会根据Id
、Name
、Code
分别进行缓存),从而产生不一样的缓存数据,可是在对数据进行更新、删除操做时,因为没法经过RemoveCachingInterceptAttribute
特性一次性删除该类型数据的全部缓存数据,这个时候,在实现业务代码过程当中,就须要经过分布式缓存接口IDistributedCache<T>
实现缓存数据的更新、删除操做。
silky使用zookeeper做为默认服务的注册中心。当前还未扩展支持其余的服务注册中心。
silky支持为微服务集群配置多个服务注册中心,您只须要在配置服务注册中心的连接字符串registrycenter:connectionStrings
中,使用分号;
就能够指定微服务框架的多个服务注册中心。
为微服务配置服务注册中心以下所示:
registrycenter: // 服务注册中心配置节点 connectionStrings: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;127.0.0.1:2184,127.0.0.1:2185,127.0.0.1:2186 // 服务配置中心连接 registryCenterType: Zookeeper // 注册中心类型 connectionTimeout: 1000 // 连接超时时间(单位:ms) sessionTimeout: 2000 // 会话超时时间(单位:ms) operatingTimeout: 4000 // 操做超时时间(单位:ms) routePath: /services/serviceroutes
silky框架存在两种类型的模块:
SilkyModule
就能够定义一个普通模块类;StartUpModule
定义一个服务注册启动模块类;开发者也能够经过继承StartUpModule
,选择合适的依赖包,实现本身的代理主机。模块的依赖关系: silky框架的模块经过DependsOn
特性指定模块的依赖关系,silky框架支持经过直接或是间接的依赖模块。
经过继承依赖注入标识接口实现服务的注册(推荐)
silky框架提供了三个依赖注册的相关标识接口:ISingletonDependency
(单例模式)、IScopedDependency
(区域模式)、ITransientDependency
(瞬态模式)。在微服务应用启动时,会扫描继承了这些标识接口的类(服务),并将其自身和继承的接口注册到Ioc容器中。
定义模块,并在模块中经过RegisterServices()
方法的ContainerBuilder
注册服务(autofac),或是经过ConfigureServices()
方法的IServiceCollection
注册服务(微软官方自带的ioc容器)
经过继承IConfigureService
或是ISilkyStartup
,经过Configure()
方法的IServiceCollection
注册服务
Silky由于支持经过IServiceCollection
进行服务注册,因此能够很方便的与第三方服务(组件)进行整合,例如:CAP
或是MassTransit
等分布式事件框架。
silky框架提供了serilog
做为日志记录器。只须要在构建主机时,增长UseSerilogDefault()
,并添加Serilog
相关配置便可。
代码:
public static async Task Main(string[] args) { await CreateHostBuilder(args).Build().RunAsync(); } private static IHostBuilder CreateHostBuilder(string[] args) { var hostBuilder = Host.CreateDefaultBuilder(args) .RegisterSilkyServices<OrderHostModule>() .UseSerilogDefault(); return hostBuilder; }
配置:
serilog: minimumLevel: default: Information override: Microsoft: Warning Microsoft.Hosting.Lifetime: Information Silky: Debug writeTo: - name: File args: path: "./logs/log-.log" rollingInterval: Day - name: Console args: outputTemplate: "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level:u11}] {Message:lj} {NewLine}{Exception}{NewLine}" theme: "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console"
要求必须在web主机项目(通常为网关项目)安装Silky.Http.MiniProfiler
包,并将swaggerDocument:injectMiniProfiler
配置项的属性设置为true
。
swaggerDocument: injectMiniProfiler: true
要求微服务在启动模块依赖SilkySkyApmAgentModule
模块,并配置skyWalking
相关属性。
[DependsOn(typeof(SilkySkyApmAgentModule))] public class AccountHostModule : GeneralHostModule { }
skyWalking: serviceName: AccountHost headerVersions: - sw8 sampling: samplePer3Secs: -1 percentage: -1.0 logging: level: Debug filePath: "logs/skyapm-{Date}.log" transport: interval: 3000 protocolVersion: v8 queueSize: 30000 batchSize: 3000 gRPC: servers: "127.0.0.1:11800" timeout: 10000 connectTimeout: 10000 reportTimeout: 600000
在silky的实例项目中,提供了skyWalking
经过docker-compose
快速启动的服务编排文件samples\docker-compose\infrastr\docker-compose.skywalking.yml
,开发者只须要进入到samples\docker-compose\infrastr
目录中,经过以下命令便可开始的启动一个skyWalking服务。
cd samples\docker-compose\infrastr docker-compose -f docker-compose.skywalking.yml
打开http://127.0.0.1:8180
便可看到微服务集群的运行状况:
网络拓扑图:
链路跟踪:
仪表盘:
必要前提是开发者已经部署了一套Apollo服务。开发者能够参考Apollo部署节点,部署一套Apollo服务。
在开发过程当中,更简单的作法是使用silky实例项目中使用docker-compose已经编排好的文件 docker-compose.apollo.yml
。
进入到samples\docker-compose\infrastr
目录,将.env
设置的环境变量EUREKA_INSTANCE_IP_ADDRESS
修改成您当前本机的IP地址,不容许为127.0.0.1
。
运行以下命令,等待1~2分钟便可启动apollo配置服务。
docker-compose -f docker-compose.apollo.yml up -d
在主机项目经过nuget安装Silky.Apollo
包。(这是一个空包,您也能够直接安装Com.Ctrip.Framework.Apollo.AspNetCoreHosting
和Com.Ctrip.Framework.Apollo.Configuration
包)
在服务注册时,添加对Appo服务配置中心的支持
private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .RegisterSilkyServices<AccountHostModule>() .AddApollo(); }
若是您您想在指定的运行环境中使用Apollo做为微服务的配置中心,而在另外其余运行环境中使用本地配置,那么您也能够经过以下当时处理:
private static IHostBuilder CreateHostBuilder(string[] args) { var hostBuilder = Host.CreateDefaultBuilder(args) .RegisterSilkyServices<AccountHostModule>() .UseSerilogDefault(); if (EngineContext.Current.IsEnvironment("Apollo")) { hostBuilder.AddApollo(); } return hostBuilder; }
备注
运行环境您能够经过修改Properties\launchSettings.json
的DOTNET_ENVIRONMENT
变量(本地开发模式)
或是经过.env
环境变量文件指定DOTNET_ENVIRONMENT
变量(docker-compose开发模式)
打开地址:http://127.0.0.1:8070 (Applo服务的web管理工具:portal),新建相关的应用。以下图:
为应用添加相应的配置:
普通业务微服务的配置以下:
# Application rpc:port = 2201 connectionStrings:default = server=127.0.0.1;port=3306;database=order;uid=root;pwd=qwe!P4ss; jwtSettings:secret = jv1PZkwjLVCEygM7faLLvEhDGWmFqRUW # TEST1.silky.sample registrycenter:registryCenterType = Zookeeper registrycenter:connectionStrings = 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;127.0.0.1:2184,127.0.0.1:2185,127.0.0.1:2186 distributedCache:redis:isEnabled = true distributedCache:redis:configuration = 127.0.0.1:6379,defaultDatabase=0 rpc:token = ypjdYOzNd4FwENJiEARMLWwK0v7QUHPW governance:executionTimeout = -1 cap:rabbitmq:hostName = 127.0.0.1 cap:rabbitmq:userName = rabbitmq cap:rabbitmq:password = rabbitmq
web网关的配置以下:
# TEST1.silky.sample.gateway gateway:injectMiniProfiler = true gateway:enableSwaggerDoc = true gateway:wrapResult = true gateway:jwtSecret = jaoaNPA1fo1rcPfK23iNufsQKkhTy8eh swaggerDocument:organizationMode = Group swaggerDocument:injectMiniProfiler = true swaggerDocument:termsOfService = https://www.github.com/liuhll/silky # TEST1.silky.sample registrycenter:registryCenterType = Zookeeper registrycenter:connectionStrings = 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;127.0.0.1:2184,127.0.0.1:2185,127.0.0.1:2186 distributedCache:redis:isEnabled = true distributedCache:redis:configuration = 127.0.0.1:6379,defaultDatabase=0 rpc:token = ypjdYOzNd4FwENJiEARMLWwK0v7QUHPW governance:executionTimeout = -1
appsettings.yml
),若是指定运行环境变量则读取appsettings.{Environment}.yml
中的配置例如:
apollo: appId: "silky-stock-host" cluster: default metaServer: "http://127.0.0.1:8080/" # secret: "ffd9d01130ee4329875ac3441c0bedda" namespaces: - application - TEST1.silky.sample env: DEV meta: DEV: "http://127.0.0.1:8080/" PRO: "http://127.0.0.1:8080/"
silky使用DistributedLock做为分布式锁,在服务路由注册和分布式事务做业中均使用了分布式锁.
silky身份认证与受权经过包Silky.Http.Identity
,经过webhost在网关实现统一的身份认证和受权。
silky经过Silky.Jwt
包提供的IJwtTokenGenerator
实现jwt格式的token。签发token的微服务应用须要经过nuget安装Silky.Jwt
包,并在启动模块中依赖JwtModule
模块。开发者能够对签发的token的密钥、token有效期、Jwt签名算法、签发者、受众等属性经过配置节点jwtSettings
进行配置。开发者至少须要对jwtSettings:secret
进行配置。
配置以下:
jwtSettings: secret: jv1PZkwjLVCEygM7faLLvEhDGWmFqRUW
用户登录接口以下:
public async Task<string> Login(LoginInput input) { var userInfo = await _accountRepository.FirstOrDefaultAsync(p => p.UserName == input.Account || p.Email == input.Account); if (userInfo == null) { throw new AuthenticationException($"不存在帐号为{input.Account}的用户"); } if (!userInfo.Password.Equals(_passwordHelper.EncryptPassword(userInfo.UserName, input.Password))) { throw new AuthenticationException("密码不正确"); } var payload = new Dictionary<string, object>() { { ClaimTypes.UserId, userInfo.Id }, { ClaimTypes.UserName, userInfo.UserName }, { ClaimTypes.Email, userInfo.Email }, }; return _jwtTokenGenerator.Generate(payload); }
IdentityModule
模块[DependsOn(typeof(IdentityModule))] public class GatewayHostModule : WebHostModule { }
gateway:jwtSecret
配置的属性必须与签发token的微服务应用配置的属性jwtSettings:secret
的值保持一致。gateway: jwtSecret: jv1PZkwjLVCEygM7faLLvEhDGWmFqRUW
开发者只须要在应用接口或是应用接口方法中标注[AllowAnonymous]
特性便可,这样无需用户登录,也能够访问该接口。
[AllowAnonymous] Task<string> Login(LoginInput input);
开发者能够在网关应用经过继承SilkyAuthorizationHandler
基类,并重写PipelineAsync
方法便可实现对自定义受权。
public class TestAuthorizationHandler : SilkyAuthorizationHandler { private readonly ILogger<TestAuthorizationHandler> _logger; private readonly IAuthorizationAppService _authorizationAppService; public TestAuthorizationHandler(ILogger<TestAuthorizationHandler> logger, IAuthorizationAppService authorizationAppService) { _logger = logger; _authorizationAppService = authorizationAppService; } public async override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) { // 获取访问的服务条目 var serviceEntry = httpContext.GetServiceEntry(); // 能够经过rpc调用IdentifyApp,实现自定义的受权 return _authorizationAppService.Authorization(sserviceEntry.ServiceDescriptor.Id); } }
silky实现了基于AutoMapper和Mapster的对象属性映射的包。实现的代理主机默认依赖MapsterModule
包,使用Mapster做为对象映射的组件。
只须要经过Adapt
方法便可实现对象属性映射。
efcore数据访问组件主要参考了furion的实现。提供了数据仓库、数据库定位器、多租户等实现方式。使用方式与其基本保持一致。
github: https://github.com/liuhll/silky
gitee: https://gitee.com/liuhll2/silky
开发者文档: http://docs.silky-fk.com/