关于java:小团队真的适合引入SpringCloud微服务吗

2次阅读

共计 5072 个字符,预计需要花费 13 分钟才能阅读完成。

更多干货文章浏览

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

回归景象看实质,随着业务复杂度的进步,单体利用越来越宏大,就如同一个类的代码行越来越多,分而治之,切成多个类应该是更好的解决办法,所以一个宏大的单体利用分出多个小利用也更合乎这种分治的思维。

当然微服务架构不应该是一个小团队一开始就该思考的问题,而是缓缓演变的后果,审慎适度设计尤为重要。

公司的背景是提供 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,eruka 等属于根本服务被其余服务依赖,部署也产生了先后顺序。基于 Jenkins 做 pipeline 能够很好的解决这个问题。

小结

其实以上的每一点都能够深刻的写成一篇文章,微服务的架构演进波及到开发,测试和运维,要求团队内多工种严密单干。

分治是软件行业解决大零碎的不二法门,作为小团队咱们并没有自觉追新,而是在倒退的过程通过服务化的形式解决问题。

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

(感激浏览,心愿对你所有帮忙)

正文完
 0