Surging实践经验
背景
在去年9月份的时候,我入职一家做航空软件产品的公司。当时公司部门领导决定构建一个技术平台(或称为技术中台),通过该技术平台进而孵化各个业务系统。说白了就是需要通过一个分布式框架或是微服务框架提高应用系统的性能和并发处理能力、业务处理能力。
当时现有的系统是在 .net framework平台上搭建的简单的单体应用,并不具备可用性、扩展性、易用性等。
我在入职前也学习过一些微服务相关的知识,并通过搜索引擎了解了.net平台下的一些微服务框架和分布式架构。在对比不同技术框架的背景后,我决定使用surging作为公司的技术平台。原因在于:
- 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进行了如下调整:
- 根据公司要求,对名称空间和包名称进行了调整。
- 对异常处理进行了重构
- 将消息返回的数据结果名称重命名为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服务。
业务框架
构建微服务主机
由于在构建每个微服务主机的代码和配置文件都是一致的,无法就是对配置文件的一些配置项进行调整,所以可以将构建微服务主机的代码和配置文件抽象出来,统一放置在Shared
目录中,再在项目文件中通过import
进入即可。
如何将公共的脚本、配置文件、属性抽象出来,可以参考:https://github.com/surging-cloud/Surging.Hero/tree/develop/hero/src/Shared 。
如何构建主机呢?Surging通过ServiceHostBuilder
来构建微服务主机,在构建主机过程中,可以添加一些服务组件或是指定相应的配置文件。构建主机的代码如下:
需要注意的是可以通过SurgingServiceEngine
来指定surging服务引擎扫描的业务组件的目录。以及也可以通过Startup
注入相应的服务或是制定配置文件。
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层,托管应用服务本身。
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
来指定调试过程中的相关设置(部署中可不指定该编排文件)。
需要注意的是,surging在开发过程中,基础服务也通过docker-compose来编排和启动,且必须在开发和调试前启动基础服务。基础服务和surging服务组件指定的网络必须同一个。
基础服务编排如下所示:
由于开发过程中的基础服务并没有考虑到高可用,在生产环境中建议基础服务集群化。
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集群中。
整个devops流程如下所述(但是我们没有与钉钉做集成):
注意事项
- 企业内部的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的经验或是对源码的理解。
Surging实践经验的更多相关文章
- CI Weekly #6 | 再谈 Docker / CI / CD 实践经验
CI Weekly 围绕『 软件工程效率提升』 进行一系列技术内容分享,包括国内外持续集成.持续交付,持续部署.自动化测试. DevOps 等实践教程.工具与资源,以及一些工程师文化相关的程序员 Ti ...
- 根据实践经验,讲述些学习Java web能少走的弯路,内容摘自java web轻量级开发面试教程
在和不少比较上进的初级程序员打交道的过程中,我们总结出了一些能帮到合格程序员尽快进阶的经验,从总体上来讲,多学.多实践不吃亏.本文来是从 java web轻量级开发面试教程从摘录的. 1 哪些知识点 ...
- 华为云对Kubernetes在Serverless Container产品落地中的实践经验
华为云容器实例服务,它基于 Kubernetes 打造,对最终用户直接提供 K8S 的 API.正如前面所说,它最大的优点是用户可以围绕 K8S 直接定义运行应用. 这里值得一提是,我们采用了全物理机 ...
- 关于Flask使用Celery的实践经验分享
最近大Boss反馈Celery经常出现问题,几经实践终于把问题解决了!于是乎有了这篇博客的诞生,算是一个实践经验的分享吧! 软件版本如下: Celery () Flask () RabbitMQ( ...
- 领域驱动设计(DDD)的实践经验分享之ORM的思考
原文:领域驱动设计(DDD)的实践经验分享之ORM的思考 最近一直对DDD(Domain Driven Design)很感兴趣,于是去网上找了一些文章来看看,发现它确实是个好东西.于是我去买了两本关于 ...
- 领域驱动设计(DDD)的实践经验分享之持久化透明
原文:领域驱动设计(DDD)的实践经验分享之持久化透明 前一篇文章中,我谈到了领域驱动设计中,关于ORM工具该如何使用的问题.谈了很多我心里的想法,大家也对我的观点做了一些回复,或多或少让我深深感觉到 ...
- Sobol 序列并行化的实践经验
目录 Sobol 序列并行化的实践经验 随机数发生器并行化的常见策略 Sobol 序列的原理和跳转功能 Sobol 序列并行化实践 分块策略 蛙跳策略 蛙跳策略的计算量分析 减少异或计算的技巧 分块策 ...
- 在单体应用的一些DDD实践经验
阅读此文需要一定的DDD基础,如果你是第一次接触DDD读者,建议先去阅读一些DDD相关的书籍或者文章之后再来阅读本文. 背景 自从我在团队中推行DDD以来,我们团队经历了一系列的磨难--先是把核心项目 ...
- Java小白如何一步步学好Java,听听企业Java培训师的实践经验吧
今天我准备给小主展示一篇Java培训老师的文章,希望能给Java小白一个学好Java的路径或者提示.以下就是原文: 从大学到现在,我使用Java已经将近20年,日常也带实习生,还在公司内部做train ...
随机推荐
- MFC中的模态对话框与非模态对话框,模态对话框测试
http://blog.csdn.net/u010839382/article/details/52972427 http://blog.csdn.net/u010839382/article/det ...
- 深度强化学习day01初探强化学习
深度强化学习 基本概念 强化学习 强化学习(Reinforcement Learning)是机器学习的一个重要的分支,主要用来解决连续决策的问题.强化学习可以在复杂的.不确定的环境中学习如何实现我们设 ...
- 跟我学SpringCloud | 第二篇:注册中心Eureka
Eureka是Netflix开源的一款提供服务注册和发现的产品,它提供了完整的Service Registry和Service Discovery实现.也是springcloud体系中最重要最核心的组 ...
- 系统学习 Java IO (八)----装饰流 FilterInputStream/FilterOutputStream
目录:系统学习 Java IO---- 目录,概览 这两个流的作用是:"封装其它的输入流,并为它们提供额外的功能" 他们的直接子类有: BufferedInputStream 的作 ...
- CSS中属性的详细运用(新手必看)
=不同的浏览器有不同的默认字体大小font-size 这里以谷歌浏览器为准字体大小为10px (其他浏览器是12px) 1.这里强调一个备注:属性继承 a 是特殊的,要改变a里面的颜色,必须在它后 ...
- PATB 1028. 人口普查(20)
1028. 人口普查(20) 注意特判合理人数为0,否则格式错误.很暴力的sort排序找出最大最小. 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Stan ...
- ubuntu镜像快速下载
由于官网服务器在国外,下载速度奇慢,所以我们可以利用阿里云镜像下载ubuntu ubuntu 14.04: http://mirrors.aliyun.com/ubuntu-releases/14.0 ...
- oralce中的dual详解 转 http://blog.sina.com.cn/s/blog_a5a24bcb0100zeay.html
dual是属于sys的只有一个X varchar2(1)列查询虚拟列不会产生逻辑IO========================================================== ...
- kafka入门(一)简介
1 什么是kafk Apache kafka是消息中间件的一种,在开始学习之前,先简单的解释一下什么是消息中间件. 举个例子,生产者消费者,生产者生产鸡蛋,消费者消费鸡蛋,生产者生产一个鸡蛋,消费者就 ...
- 我在工作中遇到的redis集群使用
写次随笔,给自己工作学习中记录一个笔记 废话不多说,直接上代码: 功能:将相关信息存到redis中,并设置过期时间,如果redis中有,从redis获取,如果没有,从mysql中获取.redis配置了 ...