技术开发者应该如何构建小团队的微服务方案?

 

640?wx_fmt=gif
640?wx_fmt=png
作者 | 徐鹏
责编 | 刘静
出品 | CSDN(ID:CSDNnews)
我们的产品是Linkflow,企业运营人员使用的客户数据平台(CDP)。产品的一个重要部分类似企业版的”捷径”,让运营人员可以像搭乐高积木一样创建企业的自动化流程,无需编程即可让数据流动起来。从这一点上,我们的业务特点就是聚少成多,把一个个服务连接起来就成了数据的海洋。理念上跟微服务一致,一个个独立的小服务最终实现大功能。当然我们一开始也没有使用微服务,当业务还未成型就开始考虑架构,那么就是”过度设计”。另一方面需要考虑的因素就是”人”,有没有经历过微服务项目的人,团队是否有devops文化等等,综合考量是否需要微服务化。
 

640?wx_fmt=png

要不要微服务

 
微服务的好处是什么?
  • 相比于单体应用,每个服务的复杂度会下降,特别是数据层面(数据表关系)更清晰,不会一个应用上百张表,新员工上手快;

  • 对于稳定的核心业务可以单独成为一个服务,降低该服务的发布频率,也减少测试人员压力;

  • 可以将不同密集型的服务搭配着放到物理机上,或者单独对某个服务进行扩容,实现硬件资源的充分利用;

  • 部署灵活,在私有化项目中,如果客户有不需要的业务,那么对应的微服务就不需要部署,节省硬件成本,就像上文提到的乐高积木理念。

 
 
 
 
微服务有什么挑战?
  • 一旦设计不合理,交叉调用,相互依赖频繁,就会出现牵一发动全身的局面。想象单个应用内service层依赖复杂的场面就明白了;

  • 项目多了,轮子需求也会变多,需要有人专注公共代码的开发;

  • 开发过程的质量需要通过持续集成(CI)严格把控,提高自动化测试的比例,因为往往一个接口改动会涉及多个项目,光靠人工测试很难覆盖所有情况;

  • 发布过程会变得复杂,因为微服务要发挥全部能力需要容器化的加持,容器编排就是最大的挑战;

  • 线上运维,当系统出现问题需要快速定位到某个机器节点或具体服务,监控和链路日志分析都必不可少。

 
 
 
 
 
下面详细说说我们是怎么应对这些挑战的:
 

640?wx_fmt=png

开发过程的挑战

 
持续集成:
 
  • 通过CI将开发过程规范化,串联自动化测试和人工Review;

  • 我们使用Gerrit作为代码&分支管理工具,在流程管理上遵循Gitlab的工作流模型;

  • 开发人员提交代码至Gerrit的magic分支;

  • 代码Review人员Review代码并给出评分;

  • 对应Repo的Jenkins job监听分支上的变动,触发Build job。经过IT和Sonar的静态代码检查给出评分;

  • Review和Verify皆通过之后,相应Repo的负责人将代码merge到真实分支上若有一项不通过,代码修改后重复过程;

  • Gerrit将代码实时同步备份至的两个远程仓库中。

 
 
 
 
 
 
 

640?wx_fmt=png

集成测试
一般来说代码自动执行的都是单元测试(Unit Test),即不依赖任何资源(数据库,消息队列)和其他服务,只测试本系统的代码逻辑。但这种测试需要mock的部分非常多,一是写起来复杂,二是代码重构起来跟着改的测试用例也非常多,显得不够敏捷。而且一旦要求开发团队要达到某个覆盖率,就会出现很多造假的情况。所以我们选择主要针对API进行测试,即针对controller层的测试。另外对于一些公共组件如分布式锁,json序列化模块也会有对应的测试代码覆盖。测试代码在运行时会采用一个随机端口拉起项目,并通过http client对本地API发起请求,测试只会对外部服务做mock,数据库的读写,消息队列的消费等都是真实操作,相当于把Jmeter的事情在Java层面完成一部分。Spring Boot项目可以很容易的启动这样一个测试环境,代码如下:
 
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

测试过程的http client推荐使用io.rest-assured:rest-assured支持JsonPath,十分好用。

测试时需要注意的一个点是测试数据的构造和清理。构造又分为schema的创建和测试数据的创建。
  • schema由flyway处理,在启用测试环境前先删除所有表,再进行表的创建;

  • 测试数据可以通过@Sql读取一个sql文件进行创建,在一个用例结束后再清除这些数据。

 
 
顺带说一下,基于flyway的schema upgrade功能我们封成了独立的项目,每个微服务都有自己的upgrade项目,好处一是支持command-line模式,可以细粒度的控制升级版本,二是也可以支持分库分表以后的schema操作。upgrade项目也会被制作成docker image提交到docker hub。
测试在每次提交代码后都会执行,Jenkins监听gerrit的提交,通过docker run -rm {upgrade项目的image}先执行一次schema upgrade,然后gradle test执行测试。最终会生成测试报告和覆盖率报告,覆盖率报告采用jacoco的gradle插件生成。如图:
640?wx_fmt=png
640?wx_fmt=png
这里多提一点,除了集成测试,服务之间的接口要保证兼容,实际上还需要一种consumer-driven testing tool,就是说接口消费端先写接口测试用例,然后发布到一个公共区域,接口提供方发布接口时也会执行这个公共区域的用例,一旦测试失败,表示接口出现了不兼容的情况。比较推荐大家使用Pact或是Spring Cloud Contact。我们目前的契约基于”人的信任“,毕竟服务端开发者还不多,所以没有必要使用这样一套工具。
集成测试的同时还会进行静态代码检查,我们用的是sonar,当所有检查通过后jenkins会+1分,再由reviewer进行代码review。
自动化测试
单独拿自动化测试出来说,就是因为它是质量保证的非常重要的一环,上文能在CI中执行的测试都是针对单个微服务的,那么当所有服务(包括前端页面)都在一起工作的时候是否会出现问题,就需要一个更接近线上的环境来进行测试了。
在自动化测试环节,我们结合Docker提高一定的工作效率并提高测试运行时环境的一致性以及可移植性。在准备好基础的Pyhton镜像以及Webdriver(selenium)之后,我们的自动化测试工作主要由以下主要步骤组成:
  • 测试人员在本地调试测试代码并提交至Gerrit;

  • Jenkins进行测试运行时环境的镜像制作,主要将引用的各种组件和库打包进一个Python的基础镜像;

  • 通过Jenkins定时或手动触发,调用环境部署的job将专用的自动化测试环境更新,然后拉取自动化测试代码启动一次性的自动化测试运行时环境的Docker容器,将代码和测试报告的路径镜像至容器内;

  • 自动化测试过程将在容器内进行;

  • 测试完成之后,不必手动清理产生的各种多余内容,直接在Jenkins上查看发布出来的测试结果与趋势。

 
 
 
 
 
640?wx_fmt=png
640?wx_fmt=png
关于部分性能测试的执行,我们同样也将其集成到Jenkins中,在可以直观的通过一些结果数值来观察版本性能变化情况的回归测试和基础场景,将会很大程度的提高效率、便捷的观察趋势。
  • 测试人员在本地调试测试代码并提交至Gerrit;

  • 通过Jenkins定时或手动触发,调用环境部署的job将专用的性能测试环境更新以及可能的Mock Server更新;

  • 拉取最新的性能测试代码,通过Jenkins的性能测试插件来调用测试脚本;

  • 测试完成之后,直接在Jenkins上查看通过插件发布出来的测试结果与趋势。

 
 
 
 
640?wx_fmt=png
640?wx_fmt=png
 

640?wx_fmt=png

布过程的挑战

 
上面提到微服务一定需要结合容器化才能发挥全部优势,容器化就意味线上有一套容器编排平台。我们目前采用是Redhat的Openshift。所以发布过程较原来只是启动jar包相比要复杂的多,需要结合容器编排平台的特点找到合适的方法。
镜像准备
公司开发基于gitlab的工作流程,git分支为master,pre-production和prodution三个分支,同时生产版本发布都打上对应的tag。每个项目代码里面都包含dockerfile与jenkinsfile,通过jenkins的多分支pipeline来打包docker镜像并推送到harbor私库上。
640?wx_fmt=png
docker镜像的命令方式为 项目名/分支名:git_commit_id,如 funnel/production:4ee0b052fd8bd3c4f253b5c2777657424fccfbc9,tag版本的docker镜像命名为 项目名/release:tag名,如 funnel/release:18.10.R1
640?wx_fmt=png
640?wx_fmt=png
在jenkins中执行build docker image job时会在每次pull代码之后调用harbor的api来判断此版本的docker image是否已经存在,如果存在就不执行后续编译打包的stage。在jenkins的发布任务中会调用打包job,避免了重复打包镜像,这样就大大的加快了发布速度。
数据库Schema升级
数据库的升级用的是flyway,打包成docker镜像后,在openshift中创建job去执行数据库升级。job可以用最简单的命令行的方式去创建:
oc run upgrade-foo --image=upgrade/production --replicas=1 --restart=OnFailure --command -- java -jar -Dprofile=production /app/upgrade-foo.jar

脚本升级任务也集成在jenkins中。

容器发布
openshift有个特别概念叫DeploymentConfig,原生k8s Deployment与之相似,但openshift的DeploymentConfig功能更多些。
Deploymentconfig关联了一个叫做ImageStreamTag的东西,而这个ImagesStreamTag和实际的镜像地址做关联,当ImageStreamTag关联的镜像地址发生了变更,就会触发相应的DeploymentConfig重新部署。我们发布是使用了jenkins+openshift插件,只需要将项目对应的ImageStreamTag指向到新生成的镜像上,就触发了部署。
640?wx_fmt=png
如果是服务升级,已经有容器在运行怎么实现平滑替换而不影响业务呢?
配置Pod的健康检查,Health Check只配置了ReadinessProbe,没有用LivenessProbe。因为LivenessProbe在健康检查失败之后,会将故障的pod直接干掉,故障现场没有保留,不利于问题的排查定位。而ReadinessProbe只会将故障的pod从service中踢除,不接受流量。使用了ReadinessProbe后,可以实现滚动升级不中断业务,只有当pod健康检查成功之后,关联的service才会转发流量请求给新升级的pod,并销毁旧的pod。
 
readinessProbe:
    failureThreshold: 4
    httpGet:
      path: /actuator/metrics
      port: 8090
      scheme: HTTP
    initialDelaySeconds: 60
    periodSeconds: 15
    successThreshold: 2
    timeoutSeconds: 2
 

640?wx_fmt=png

线上运维的挑战

 
服务间调用
Spring Cloud使用eruka接受服务注册请求,并在内存中维护服务列表。当一个服务作为客户端发起跨服务调用时,会先获取服务提供者列表,再通过某种负载均衡算法取得具体的服务提供者地址(ip + port),即所谓的客户端服务发现。在本地开发环境中我们使用这种方式。
由于Openshift天然就提供服务端服务发现,即service模块,客户端无需关注服务发现具体细节,只需知道服务的域名就可以发起调用。由于我们有nodejs应用,在实现eureka的注册和去注册的过程中都遇到过一些问题,不能达到生产级别。所以决定直接使用service方式替换掉eureka,也为以后采用service mesh做好铺垫。具体的做法是,配置环境变量EUREKA_CLIENT_ENABLED=false,RIBBON_EUREKA_ENABLED=false,并将服务列表如 FOO_RIBBON_LISTOFSERVERS: '[http://foo:8080](http://foo:8080/)' 写进configmap中,以envFrom: configMapRef方式获取环境变量列表。
如果一个服务需要暴露到外部怎么办,比如暴露前端的html文件或者服务端的gateway。
Openshift内置的haproxy router,相当于k8s的ingress,直接在Openshift的web界面里面就可以很方便的配置。我们将前端的资源也作为一个Pod并有对应的Service,当请求进入haproxy符合规则就会转发到ui所在的Service。router支持A/B test等功能,唯一的遗憾是还不支持url rewrite。
640?wx_fmt=png
640?wx_fmt=png
对于需要url rewrite的场景怎么办?那么就直接将nginx也作为一个服务,再做一层转发。流程变成 router → nginx pod → 具体提供服务的pod。
链路跟踪
开源的全链路跟踪很多,比如spring cloud sleuth + zipkin,国内有美团的CAT等等。其目的就是当一个请求经过多个服务时,可以通过一个固定值获取整条请求链路的行为日志,基于此可以再进行耗时分析等,衍生出一些性能诊断的功能。不过对于我们而言,首要目的就是trouble shooting,出了问题需要快速定位异常出现在什么服务,整个请求的链路是怎样的。
为了让解决方案轻量,我们在日志中打印RequestId以及TraceId来标记链路。RequestId在gateway生成表示唯一一次请求,TraceId相当于二级路径,一开始与RequestId一样,但进入线程池或者消息队列后,TraceId会增加标记来标识唯一条路径。举个例子,当一次请求会向MQ发送一个消息,那么这个消息可能会被多个消费者消费,此时每个消费线程都会自己生成一个TraceId来标记消费链路。加入TraceId的目的就是为了避免只用RequestId过滤出太多日志。
实现上,通过ThreadLocal存放APIRequestContext串联单服务内的所有调用,当跨服务调用时,将APIRequestContext信息转化为Http Header,被调用方获取到Http Header后再次构建APIRequestContext放入ThreadLocal,重复循环保证RequestId和TraceId不丢失即可。如果进入MQ,那么APIRequestContext信息转化为Message Header即可(基于Rabbitmq实现)。
当日志汇总到日志系统后,如果出现问题,只需要捕获发生异常的RequestId或是TraceId即可进行问题定位。
640?wx_fmt=png
经过一年来的使用,基本可以满足绝大多数trouble shooting的场景,一般半小时内即可定位到具体业务。
容器监控
容器化前监控用的是telegraf探针,容器化后用的是prometheus,直接安装了openshift自带的cluster-monitoring-operator。自带的监控项目已经比较全面,包括node,pod资源的监控,在新增node后也会自动添加进来。
Java项目也添加了prometheus的监控端点,只是可惜cluster-monitoring-operator提供的配置是只读的,后期将研究怎么将java的jvm监控这些整合进来。
640?wx_fmt=png
640?wx_fmt=png
 
640?wx_fmt=png
MORE
 
开源软件是对中小团队的一种福音,无论是Spring Cloud还是k8s都大大降低了团队在基础设施建设上的时间成本。当然其中有更多的话题,比如服务升降级,限流熔断,分布式任务调度,灰度发布,功能开关等等都需要更多时间来探讨。对于小团队,要根据自身情况选择微服务的技术方案,不可一味追新,适合自己的才是最好的。
 
作者简介:徐鹏, Linkflow运维开发负责人
声明:本文为作者投稿,版权归作者个人所有。

【END】

让你成为Python全栈工程师:

https://edu.csdn.net/topic/python115?utm_source=csdn_bw

640?wx_fmt=png

 热 文 推 荐 

 

 

 

 

 

 

640?wx_fmt=png
你点的每个“在看”,我都认真当成了喜欢
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度,实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务的调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析Quartz、Spring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架和框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。
[入门数据分析的第一堂课]这是一门为数据分析小白量身打造的课程,你从网络或者公众号收集到很多关于数据分析的知识,但是它们零散不成体系,所以第一堂课首要目标是为你介绍:Ø  什么是数据分析-知其然才知其所以然Ø  为什么要学数据分析-有目标才有动力Ø  数据分析的学习路线-有方向走得更快Ø  数据分析的模型-分析之道,快速形成分析思路Ø  应用案例及场景-分析之术,掌握分析方法[哪些同学适合学习这门课程]想要转行做数据分析师的,零基础亦可工作中需要数据分析技能的,例如运营、产品等对数据分析感兴趣,想要更多了解的[你的收获]n  会为你介绍数据分析的基本情况,为你展现数据分析的全貌。让你清楚知道自己该如何在数据分析地图上行走n  会为你介绍数据分析的分析方法和模型。这部分是讲数据分析的道,只有学会底层逻辑,能够在面对问题时有自己的想法,才能够下一步采取行动n  会为你介绍数据分析的数据处理和常用分析方法。这篇是讲数据分析的术,先有道,后而用术来实现你的想法,得出最终的结论。n  会为你介绍数据分析的应用。学到这里,你对数据分析已经有了初步的认识,并通过一些案例为你展现真实的应用。[专享增值服务]1:一对一答疑         关于课程问题可以通过微信直接询问老师,获得老师的一对一答疑2:转行问题解答         在转行的过程中的相关问题都可以询问老师,可获得一对一咨询机会3:打包资料分享         15本数据分析相关的电子书,一次获得终身学习

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值