庆祝中国共产党成立100周年
已有98人围观 来源:架构师 发布于:2021-07-23 22:43:04
架构师(JiaGouX)
我们都是架构师!
架构未来,你来不来?



  • 单体运用时期
    • 接口定义
    • 连续集成(CI)
  • 微服务时期
    • 服务拆分原则
    • 框架选择
    • 架构改革
    • 主动化安排
    • 链路跟踪
    • 运维监控
  • 容器化时期
    • 架构改革
    • Spring Cloud与k8s的融会
    • CI的改革
  • 小结

微服务是否合适小团队是个见仁见智的问题。

回归现象看实质,随着业务庞杂度的进步,单体运用越来越宏大,就好像一个类的代码行越来越多,分而治之,切成多个类应当是更好的解决办法,所以一个宏大的单体运用分出多个小运用也更符合这种分治的思想。

当然微服务架构不应当是一个小团队一开端就该斟酌的问题,而是慢慢演变的成果,谨严过度设计尤为主要。

公司的背景是供给SaaS服务,对于大客户也会有定制开发以及私有化安排。经过2年不到的时光,技巧架构阅历了从单体到微服务再到容器化的进程。

单体运用时期

早期开发只有两个人,斟酌微服务之类的都是过剩。不过由于受前公司影响,最初就决议了前后端分别的路线,因为不须要斟酌SEO的问题,索性就做成了SPA单页运用。

多说一句,前后端分别也不必定就不能服务端渲染,例如电商体系或者一些匿名即可拜访的体系,加一层薄薄的View层,无论是php还是用Thymeleaf都是不错的选择。

安排架构上,我们应用Nginx代理前端HTML资源,在吸收要求时依据路径反向代理到server的8080端口实现业务。

接口定义

接口依照尺度的Restful来定义,

  • 版本,统一跟在 /api/后面,例如 /api/v2
  • 以资源为中心,应用复数表述,例如/api/contacts,也可以嵌套,如/api/groups/1/contacts/100
  • url中尽量不应用动词,实践中发现做到这一点真的比拟难,每个研发人员的思路不一致,起的名字也千奇百怪,都须要在代码Review中笼罩。
  • 动作支撑,POST / PUT / DELELE / GET ,这里有一个坑,PUT和PATCH都是更新,但是PUT是全量更新而PATCH是部分更新,前者如果传入的字段是空(未传也视为空)那么也会被更新到数据库中。目前我们虽然是应用PUT但是疏忽空字段和未传字段,实质上是一种部分更新,这也带来了一些问题,比如确有置空的业务须要特别处置。
  • 接口通过swagger生成文档供前端同事应用。

连续集成(CI)

团队初始成员之前都有在大团队共事的阅历,所以对于质量管控和流程管理都有一些共同的要求。因此在开发之初就引入了集成测试的体系,可以直接开发针对接口的测试用例,统一履行并盘算笼罩率。

一般来说代码主动履行的都是单元测试(Unit Test),我们之所以叫集成测试是因为测试用例是针对API的,并且包括了数据库的读写,MQ的操作等等,除了外部服务的依附根本都是符合真实生产场景,相当于把Jmeter的事情直接在Java层面做掉了。

这在开发初期为我们供给了非常大的方便性。但值得注意的是,由于数据库以及其他资源的引入,数据预备以及数据清算时要斟酌的问题就会更多,例如如何掌握并行义务之间的测试数据互不影响等等。

为了让这一套流程可以主动化的运作起来, 引入Jenkins也是理所当然的事情了。

开发人员提交代码进入gerrit中,Jenkins被触发开端编译代码并履行集成测试,完成后生成测试报告,测试通过再由reviewer进行代码review。在单体运用时期这样的CI架构已经足够好用,由于有集成测试的笼罩,在坚持API兼容性的前提下进行代码重构都会变得更有信念。

微服务时期

服务拆分原则

从数据层面看,最简略的方法就是看数据库的表之间是否有比拟少的关联。例如最容易分别的一般来说都是用户管理模块。如果从范畴驱动设计(DDD)看,其实一个服务就是一个或几个相干联的范畴模型,通过少量数据冗余划清服务边界。

单个服务内通过范畴服务完成多个范畴对象协作。当然DDD比拟庞杂,要求范畴对象设计上是充血模型而非贫血模型。

从实践角度讲,充血模型对于大部离开发人员来说难度非常高,什么代码应当属于行动,什么属于范畴服务,很多时候非常考验人员程度。

服务拆分是一个大工程,往往须要几个对业务以及数据最熟习的人一起讨论,甚至要斟酌到团队构造,最终的后果是服务边界清楚, 没有环形依附和避免双向依附。

框架选择

由于之前的单体服务应用的是spring boot,所以框架自然而的选择了spring cloud。其实个人以为微服务框架不应当限制技巧与语言,但生产实践中发现无论dubbo还是spring cloud都具有侵入性,我们在将nodejs运用融入spring cloud体系时就发现了许多问题。也许未来的service mesh才是更合理的发展途径。


这是典范的Spring Cloud的应用办法

  • zuul作为gateway,分发不同客户端的要求到具体service
  • erueka作为注册中心,完成了服务发现和服务注册
  • 每个service包括gateway都自带了Hystrix供给的限流和熔断功效
  • service之间通过feign和ribbon互相调用,feign实际上是屏蔽了service对erueka的操作

上文说的一旦要融入异构语言的service,那么服务注册,服务发现,服务调用,熔断和限流都须要自己处置。

再有关于zuul要多说几句,Sprin Cloud供给的zuul对Netflix版本的做了裁剪,去掉了动态路由功效(Groovy实现),另外一点就是zuul的性能一般,由于采取同步编程模型,对于IO密集型等后台处置时光长的链路非常容易将servlet的线程池占满,所以如果将zuul与主要service放置在同一台物理机上,在流量大的情形下,zuul的资源消费非常大。

实际测试也发现经过zuul与直接调用service的性能丧失在30%左右,并发压力大时更为显著。现在spring cloud gateway是pivotal的主推了,支撑异步编程模型,后续架构优化也许会采取,或是直接应用Kong这种基于nginx的网关来供给性能。当然同步模型也有长处,编码更简略,后文将会提到应用ThreadLocal如何树立链路跟踪。

架构改革

经过大半年的改革以及新需求的参加,单体服务被不断拆分,最终形成了10余个微服务,并且搭建了Spark用于BI。初步形成两大体系,微服务架构的在线业务体系(OLTP) + Spark大数据剖析体系(OLAP)。数据源从只有Mysql增长到了ES和Hive。多数据源之间的数据同步也是值得一说的话题,但内容太多不在此文赘述。

主动化安排

与CI比起来,连续交付(CD)实现更为庞杂,在资源不足的情形我们尚未实现CD,只是实现履行了主动化安排。

由于生产环境须要通过跳板机操作,所以我们通过Jenkins生成jar包传输到跳板机,之后再通过Ansible安排到集群。


简略粗鲁的安排方法在小范围团队开发时还是够用的,只是须要在安排前保证测试(人工测试 + 主动化测试)到位。

链路跟踪

开源的全链路跟踪很多,比如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即可进行问题定位


运维监控

在容器化之前,采取telegraf + influxdb + grafana的计划。telegraf作为探针收集jvm,system,mysql等资源的信息,写入influxdb,最终通过grafana做数据可视化。spring boot actuator可以配合jolokia裸露jvm的endpoint。全部计划零编码,只须要花时光配置。

容器化时期

架构改革

因为在做微服务之初就筹划了容器化,所以架构并未大动,只是每个服务都会树立一个Dockerfile用于创立docker image


涉及变更的部分包括:

  1. CI中多了构建docker image的步骤
  2. 主动化测试进程中将数据库升级从运用中剥离单独做成docker image
  3. 生产中用k8s自带的service替代了eruka

理由下文一一道来。

Spring Cloud与k8s的融会

我们应用的是Redhat的Openshift,可以以为是k8s企业版,其本身就有service的概念。一个service下有多个pod,pod内即是一个可服务单元。service之间互相调用时k8s会供给默认的负载均衡掌握,发起调用方只须要写被调用方的serviceId即可。这一点和spring cloud fegin应用ribbon供给的功效如出一辙。

也就是说服务治理可以通过k8s来解决,那么为什么要调换呢?其实上文提到了,Spring Cloud技巧栈对于异构语言的支撑问题,我们有许多BFF(Backend for Frontend)是应用nodejs实现的,这些服务要想融会到Spring Cloud中,服务注册,负载均衡,心跳检讨等等都要自己实现。

如果以后还有其他语言架构的服务参加进来,这些轮子又要重造。基于此类原因综合考量后,决议采取Openshift所供给的网络才能调换eruka。

由于本地开发和联调进程中依然依附eruka,所以只在生产上通过配置参数来掌握,

eureka.client.enabled` 设置为 false,停滞各服务的eureka注册
`ribbon.eureka.enabled` 设置为 false,让ribbon不从eureka获取服务列表
以服务foo为例,`foo.ribbon.listofservers` 设置为 `http://foo:8080`,那么当一个服务须要应用服务foo的时候,就会直接调用到`http://foo:8080

CI的改革

CI的改革主要是多了一部编译docker image并打包到Harbor的进程,安排时会直接从Harbor拉取镜像。另一个就是数据库的升级工具。之前我们应用flyway作为数据库升级工具,当运用启动时主动履行SQL脚本。

随着服务实例越来越多,一个服务的多个实例同时升级的情形也时有发生,虽然flyway是通过数据库锁实现了升级进程不会有并发,但会导致被锁服务启动时光变长的问题。

从实际升级进程来看,将可能发生的并发升级变为单一进程可能更靠谱。此外后期分库分表的架构也会使随运用启动主动升级数据库变的艰苦。综合考量,我们将升级义务做了拆分,每个服务都有自己的升级项目并会做容器化。

在应用时,作为run once的工具来应用,即docker run -rm的方法。并且后续也支撑了设定目的版本的功效,在私有化项目的跨版本升级中起到了非常好的后果。

至于主动安排,由于服务之间存在高低游关系,例如config,eureka等属于根本服务被其他服务依附,安排也发生了先后次序。基于Jenkins做pipeline可以很好的解决这个问题。

小结

其实以上的每一点都可以深刻的写成一篇文章,微服务的架构演进涉及到开发,测试和运维,要求团队内多工种紧密合作。

分治是软件行业解决大体系的不二法门,作为小团队我们并没有盲目追新,而是在发展的进程通过服务化的方法解决问题。

从另一方面我们也领会到了微服务对于人的要求,以及对于团队的挑衅都比过去要高要大。未来仍需摸索,演进仍在路上。

- END -


如爱好本文,请点击右上角,把文章分享到朋友圈
如有想懂得学习的技巧点,请留言给若飞支配分享

·END·

作者:Dean

起源:deanwangpro.com/2019/02/18/road-of-microservice

版权声名:内容起源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告诉,我们会立即删除并表现歉意。谢谢!

架构师

我们都是架构师!



关注架构师(JiaGouX),添加“星标”

获取每天技巧干货,一起成为牛逼架构师

技巧群请加若飞:1321113940 进架构师群

投稿、合作、版权等邮箱:admin@137x.com