背景
在去年9月份的时候,我入职一家作航空软件产品的公司。当时公司部门领导决定构建一个技术平台(或称为技术中台),经过该技术平台进而孵化各个业务系统。说白了就是须要经过一个分布式框架或是微服务框架提升应用系统的性能和并发处理能力、业务处理能力。
当时现有的系统是在 .net framework平台上搭建的简单的单体应用,并不具有可用性、扩展性、易用性等。
我在入职前也学习过一些微服务相关的知识,并经过搜索引擎了解了.net平台下的一些微服务框架和分布式架构。在对比不一样技术框架的背景后,我决定使用surging做为公司的技术平台。缘由在于:linux
- Surging的设计思想和理念更符合微服务的架构思想,经过dotnetty框架实现的RPC通讯,内置了服务治理保证通讯的可靠性。
- 经过向服务注册中心(Zookeeper、Consul)注册相关元数据来管理集群的服务命令、路由信息、缓存中间件等数据,服务注册不须要进行额外的处理
- Surging内置了负载均衡算法。
- Surging支持多种协议的通讯方式,而且支持ws服务主机与通常服务(Http、TCP)主机之间直接经过RPC进行通讯。
- 服务之间的调用很方便,做者提供了基于
ServiceProxyProvider
、和基于ServiceProxyFactory
的rpc调用方式,简单易用。 - 模块化设计,很方便的对模块进行扩展。
- 支持事件总线,经过消息对象实现的时效件纵向的适配能够实现发布订阅的交互模式。
- .net core 跨平台,性能更高。
架构维护
因为做者一直在维护surging,并且咱们也须要对surging的一些模块进行调整,也须要扩展一些surging的组件包,因此咱们在使用surging的过程当中是直接获取源代码后,在公司维护一份本身的源码,而后打包成nuget包,并发布到内部的nuget服务,经过内部的nuget对surging组件进行分发。
在获取surging源码后,我对surging进行了以下调整:git
- 根据公司要求,对名称空间和包名称进行了调整。
- 对异常处理进行了重构
- 将消息返回的数据结果名称重命名为Data,统一了消息返回码。
- 修改了默认的json序列化器,默认使用camelCame风格
- 重构了签发token的方法(使用jose-jwt组件)
- 支持经过RpcContext设置token的payload和获取payload,经过扩展
RpcContextSession
获取运行时登陆用户 - 扩展了Dapper、Domain、Validation、Schedule(基于Quartz的分布式任务调度)等组件包
- swagger文档支持jwt token验证
- 新增surging打包脚本等等
- 如今surging的demo案例和内部的开发者文档
若是你在使用surging的过程当中,对surging源码较为熟悉,并但愿对surging进行必定的调整、扩展本身公司的一些组件的时候,您能够经过社区获取surging的源代码,并在公司的代码库维护本身的分支。可是须要对做者对源码的修改要及时了解和熟悉。
nuget服务的搭建可使用nuget官方提供的nuget.server或是nexus 。
对架构的维护多是一个持续的和长久的过程,你能够经过企业内部的需求和做者对框架的调整对技术框架持续的进行调整和维护。在对surging进行调整维护后,就经过经过打包脚本进行打包发布到内部的nuget服务。github
业务框架
构建微服务主机
因为在构建每一个微服务主机的代码和配置文件都是一致的,没法就是对配置文件的一些配置项进行调整,因此能够将构建微服务主机的代码和配置文件抽象出来,统一放置在Shared
目录中,再在项目文件中经过import
进入便可。
如何将公共的脚本、配置文件、属性抽象出来,能够参考:https://github.com/surging-cloud/Surging.Hero/tree/develop/hero/src/Shared 。
如何构建主机呢?Surging经过ServiceHostBuilder
来构建微服务主机,在构建主机过程当中,能够添加一些服务组件或是指定相应的配置文件。构建主机的代码以下:web
须要注意的是能够经过SurgingServiceEngine
来指定surging服务引擎扫描的业务组件的目录。以及也能够经过Startup
注入相应的服务或是制定配置文件。redis
var host = new ServiceHostBuilder() .RegisterServices(builder => { builder.AddMicroService(option => { option.AddServiceRuntime() .AddClientProxy() .AddRelateServiceRuntime() .AddConfigurationWatch() .AddServiceEngine(typeof(SurgingServiceEngine)) ; builder.Register(p => new CPlatformContainer(ServiceLocator.Current)); }); }) .ConfigureLogging(loggging => { loggging.AddConfiguration( AppConfig.GetSection("Logging")); }) .UseServer(options => { }) .UseConsoleLifetime() .Configure(build => { #if DEBUG build.AddCacheFile("${cachePath}|/app/configs/cacheSettings.json", optional: false, reloadOnChange: true); build.AddCPlatformFile("${surgingPath}|/app/configs/surgingSettings.json", optional: false, reloadOnChange: true); build.AddEventBusFile("${eventBusPath}|/app/configs/eventBusSettings.json", optional: false); build.AddConsulFile("${consulPath}|/app/configs/consul.json", optional: false, reloadOnChange: true); #else build.AddCacheFile("${cachePath}|configs/cacheSettings.json", optional: false, reloadOnChange: true); build.AddCPlatformFile("${surgingPath}|configs/surgingSettings.json", optional: false,reloadOnChange: true); build.AddEventBusFile("configs/eventBusSettings.json", optional: false); build.AddConsulFile("configs/consul.json", optional: false, reloadOnChange: true); #endif }) .UseProxy() .UseStartup<Startup>() .Build(); using (host.Run()) { Console.WriteLine($"服务主机启动成功{DateTime.Now}。"); }
业务框架的分层
通常地,我会将每一个微服务组件分为以下几层:算法
1. Host
用于构建微服务主机和服务寄宿,通常我会直接引用Application层,托管应用服务自己。docker
2. IApplication 应用接口层
- 用于定义应用接口,每一个应用接口都应当继承
IServiceKey
,Surging经过应用接口生成服务条目(ServiceEntry
) - 使用
ServiceBundle
特性来标识路由模板。 - 可使用
ServiceCommand
来对Action进行注解,该元数据会被注册到服务注册中心,在RPC通讯过程当中,经过ServiceCommand
注解的元数据实现服务治理。该特性可不须要配置,能够经过SurgingSettings.json
统一指定相关的配置,若是配置了ServiceCommand
特性,会优先选择特性指定的配置值。 - 能够经过
Service
特性指定Action的一些元数据。 - 应用接口层除了定义应用接口以外,还须要定义相关的DTO对象。
- 应用接口层能够被其余微服务组件应用或是经过nuget进行分发,经过
IServiceProxyFactory
建立应用接口的代理,从而实现RPC通讯。
3. Application 应用层
- 应用层主要是实现业务流程和输入输出判断,不实现复杂的业务逻辑
- 应用层的应用须要实现应用接口定义的接口,并继承
ProxyServiceBase
,基类ProxyServiceBase
提供了一些通用的方法。
4. Domain 领域层
- 领域层主要是实现具体的业务逻辑
5. Domian.Shared
- 定义微服务组件通用的值类型(model或是枚举类型),可被其余微服务组件引用
容器化服务和服务编排
服务容器化
docker是一款优秀的容器引擎产品。将服务容器化,可以最大化的发挥微服务的体验性。可以让开发者感觉到docker构建一次,到处运行的魅力所在。因此我强烈推荐在开发过程当中,使用docker容器化服务组件,使用docker-compose编排微服务组件。
vs对docker-compose进行开发调试提供了很是友好的体验性。
通常地,会在服务组件的Host层提供Dockerfile用于构建docker镜像。以下的dockerfile提供了微服务组件的编译、构建等过程。数据库
FROM microsoft/dotnet:2.2.0-runtime AS base WORKDIR /app ARG rpc_port=100 ARG http_port=8080 ARG ws_port=96 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone EXPOSE ${rpc_port} ${http_port} ${ws_port} FROM microsoft/dotnet:2.2-sdk AS build WORKDIR /src COPY . . ARG sln_name RUN dotnet restore ${sln_name} && \ dotnet build --no-restore -c Release ${sln_name} FROM build AS publish ARG host_workdir WORKDIR ${host_workdir} RUN dotnet publish --no-restore -c Release -o /app FROM base AS final ARG host_name ENV host_name=${host_name} COPY --from=publish /app . ENTRYPOINT dotnet ${host_name}
服务编排
使用docker-compose编排微服务组件,通常的,使用docker-compose.yml
定义镜像构建的上下文、指定网络、镜像名称、挂载的目录等,经过docker-compose.override.yml
来指定配置文件的环境变量,.env
来设置环境变量的值,经过docker-compose.vs.debug.yml
来指定调试过程当中的相关设置(部署中可不指定该编排文件)。npm
须要注意的是,surging在开发过程当中,基础服务也经过docker-compose来编排和启动,且必须在开发和调试前启动基础服务。基础服务和surging服务组件指定的网络必须同一个。json
基础服务编排以下所示:
因为开发过程当中的基础服务并无考虑到高可用,在生产环境中建议基础服务集群化。
version: '3.7' services: consul: image: consul:latest ports: - "8400:8400" - "8500:8500" - "8600:8600" - "8600:8600/udp" command: "agent -server -bootstrap-expect 1 -ui -client 0.0.0.0" networks: - surging_hero_service_net redis: image: redis:latest ports: - "6379:6379" networks: - surging_hero_service_net rabbitmq: image: rabbitmq:management environment: RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG" RABBITMQ_DEFAULT_USER: "rabbitmq" RABBITMQ_DEFAULT_PASS: "rabbitmq" RABBITMQ_DEFAULT_VHOST: "/" ports: - "15672:15672" - "5672:5672" networks: - surging_hero_service_net networks: surging_hero_service_net: driver: bridge name: surging_hero_service_net ipam: driver: default config: - subnet: 172.22.0.1/16
微服务组件的编排请参考: https://github.com/surging-cloud/Surging.Hero/tree/develop/hero/docker-compose/surging.hero
开发与调试
其实在开发过程当中,因为业务模块的不一样,责任人不一样,开发团队不一样,开发者拥有的权限不一样,业务模块的代码有可能放到不一样的git仓库。建议将微服务服务组件的应用接口层和Domian.Shared能够发布的企业内部的nuget服务。其余微服务组件能够经过nuget服务引用应用接口层和Domian.Shared组件。
若是源代码都放到一个git仓库中,也能够创建多个解决方案或是docker-compose编排文件项目来编排不一样的服务,方便开发和调试。
常见问题
首次使用docker-compose进行调试服务时,因为vs会从网络上下载vsdbg
组件,因为网络缘由,通常都会比较慢,开发者能够从其余同事的电脑的家目录下拷贝vsdbg
到本机,从新打开vs,而后再进行调试。
Devops
业务流程
在开发过程当中,咱们使用Jenkins实现持续集成和部署。整个流程以下所述:
- 开发者编写业务代码或修复完bug后,提交代码,push到远程仓库,并发起pr请求,请求合并到develop分支。
- 当代码审核经过后,合并到develop分支后,经过设置
gitlab
或是gitee
的webhook
,触发jenkins执行构建。或是经过设置Jenkins的定时任务检测代码库变化,当代码库变化后,jenkins获取最新代码,执行构建操做(因为当时咱们Jenkins部署的环境是内网,gitee没法访问公司内网,因此没法设置webhook) - Jenkins经过预先设置好的命令和脚本执行构建打包程序。本质上是执行
docker-compose build
打包docker镜像,当完成构建和打包docker镜像后,而后将镜像推送到企业内部的docker镜像仓库。 - 以后,jenkins经过Jenkins SSH插件将部署脚本拷贝到k8集群的master节点,经过ssh插件在k8s master节点执行部署命令。完成后,微服务集群将自动部署到指定的k8s集群中。
注意事项
- 企业内部的docker仓库除了可使用
harbor
搭建以外,还可使用nexus
。推荐使用nexus
做为仓库管理服务,由于nexus
除了支持docker镜像仓库以外,还支持nuget包、npm等格式的包管理。 - 建议企业内部在构建业务平台时,根据业务模块划分主题,一个主题对应一个数据库,一个git仓库,一个项目组,多个相关的微服务组件,一个Jenkins构建项目。每一个主题独立的进行持续集成与部署。
- 建议基础服务consul、rabbitmq、redis考虑集群。
产品交付和部署
- 通常的,咱们经过docker镜像完成产品交付与部署。能够经过编写部署脚本在k8s集群或是经过rancher进行部署。
- 可使用k8s或是rancher提供的Dashborad进行容器和服务的监控和管理。
体会
- surging的设计思想是无疑正确的。相比于市面上其余的.net微服务框架或是分布式框架,不管是服务治理仍是内部通讯机制,服务引擎设置,主机寄宿均有独到之处。(abp vnext的微服务框架经过内部网关Ocelot进行通讯,彻底违反的去中心化设计,并且性能也相对较差的多)
- 在使用surging的过程当中,也遇到了一些问题或是bug(例如:1.首次访问性能较差;2.服务实例没法支持同时扩展),在反馈到github社区或是请求做者协助,都可以获得及时反馈。目前做者已经即将完成对surging2.0的开发,相信会有更优秀的体验。
- 在开发和测试、部署和产品交付中推荐将服务容器化,推荐使用linux做为部署服务器。
-
最后
- 若是你对surging感兴趣,能够在github上对surging关注。
- 若是你对如何使用surging落地开发,您能够在github上关注surging.hero。
- surging.hero是一个使用surging做为开发框架的权限管理平台。目前项目刚刚开始,欢迎各位开发者加入,若是您想加入surging.hero的开发或是愿意为surging的生态作出贡献,欢迎加入
surging-cloud
社区。 - 若是你但愿加入
surging-cloud
社区,能够将你的github帐号经过email到:1029765111@qq.com,并备注`申请加入 surging-cloud社区 便可。 - 若是您对surging.hero感兴趣并但愿加入surging.hero的开发,也能够申请加入qq群:
713943626
。
- surging.hero是一个使用surging做为开发框架的权限管理平台。目前项目刚刚开始,欢迎各位开发者加入,若是您想加入surging.hero的开发或是愿意为surging的生态作出贡献,欢迎加入
- 若是你们对surging确实感兴趣,后期我有时间的话,能够写一些我使用surging的经验或是对源码的理解。