Sage ERP

  微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。

  微服务是否适合小团队是个见仁见智的问题。但小团队并不代表出品的一定是小产品,当业务变得越来越复杂,如何使用微服务分而治之就成为一个不得不面对的问题。

  因为微服务是对整个团队的考验,从开发到交付,每一步都充满了挑战。经过 1 年多的探索和实践,本着将 DevOps 落实到产品中的愿景,一步步建设出适合我们的微服务平台。

  我们的产品是 Linkflow,企业运营人员使用的客户数据平台(CDP)。产品的一个重要部分类似企业版的“捷径,让运营人员可以像搭乐高积木一样创建企业的自动化流程,无需编程即可让数据流动起来。

  从这一点上,我们的业务特点就是聚少成多,把一个个服务连接起来就成了数据的海洋。

  理念上跟微服务一致,一个个独立的小服务最终实现大功能。当然我们一开始也没有使用微服务,当业务还未成型就开始考虑架构,那么就是“过度设计。

  另一方面需要考虑的因素就是“人,有没有经历过微服务项目的人,团队是否有 DevOps 文化等等,综合考量是否需要微服务化。

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

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

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

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

  一旦设计不合理,交叉调用,相互依赖频繁,就会出现牵一发动全身的局面。想象单个应用内 Service 层依赖复杂的场面就明白了。

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

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

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

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

  一般来说代码自动执行的都是单元测试(Unit Test),即不依赖任何资源(数据库,消息队列)和其他服务,只测试本系统的代码逻辑。

  但这种测试需要 Mock 的部分非常多,一是写起来复杂,二是代码重构起来跟着改的测试用例也非常多,显得不够敏捷。而且一旦要求开发团队要达到某个覆盖率,就会出现很多造假的情况。

  所以我们选择主要针对 API 进行测试,即针对 Controller 层的测试。另外对于一些公共组件如分布式锁,Json 序列化模块也会有对应的测试代码覆盖。

  测试代码在运行时会采用一个随机端口拉起项目,并通过 HTTP Client 对本地 API 发起请求,测试只会对外部服务做 Mock,数据库的读写,消息队列的消费等都是真实操作,相当于把 Jmeter 的事情在 Java 层面完成一部分。

  Spring Boot 项目可以很容易的启动这样一个测试环境,代码如下:

  测试时需要注意的一个点是测试数据的构造和清理。构造又分为 Schema 的创建和测试数据的创建:

  顺带说一下,基于 Flyway 的 Schema Upgrade 功能我们封成了独立的项目,每个微服务都有自己的 Upgrade 项目。

  最终会生成测试报告和覆盖率报告,覆盖率报告采用 JaCoCo 的 Gradle 插件生成,如下图:

  这里多提一点,除了集成测试,服务之间的接口要保证兼容,实际上还需要一种 consumer-driven testing tool。

  就是说接口消费端先写接口测试用例,然后发布到一个公共区域,接口提供方发布接口时也会执行这个公共区域的用例,一旦测试失败,表示接口出现了不兼容的情况。

  比较推荐大家使用 Pact 或是 Spring Cloud Contact。我们目前的契约基于“人的信任”,毕竟服务端开发者还不多,所以没有必要使用这样一套工具。

  集成测试的同时还会进行静态代码检查,我们用的是 Sonar,当所有检查通过后 Jenkins 会 +1 分,再由 Reviewer 进行代码 Review。

  单独拿自动化测试出来说,就是因为它是质量保证的非常重要的一环,上文能在 CI 中执行的测试都是针对单个微服务的。

  那么当所有服务(包括前端页面)都在一起工作的时候是否会出现问题,就需要一个更接近线上的环境来进行测试了。

  在自动化测试环节,我们结合 Docker 提高一定的工作效率并提高测试运行时环境的一致性以及可移植性。

  在准备好基础的 Pyhton 镜像以及 Webdriver(Selenium)之后,我们的自动化测试工作主要由以下主要步骤组成:

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

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

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

  关于部分性能测试的执行,我们同样也将其集成到 Jenkins 中,在可以直观的通过一些结果数值来观察版本性能变化情况的回归测试和基础场景,将会很大程度的提高效率,便捷的观察趋势:

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

  拉取***的性能测试代码,通过 Jenkins 的性能测试插件来调用测试脚本。

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

  上面提到微服务一定需要结合容器化才能发挥全部优势,容器化就意味着线上有一套容器编排平台。我们目前采用是 Redhat 的 OpenShift。

  所以发布过程较原来只是启动 Jar 包相比要复杂的多,需要结合容器编排平台的特点找到合适的方法。

  在 Jenkins 的发布任务中会调用打包 Job,避免了重复打包镜像,这样就大大的加快了发布速度。

  我们发布是使用了 Jenkins+OpenShift 插件,只需要将项目对应的 ImageStreamTag 指向到新生成的镜像上,就触发了部署。

  因为 LivenessProbe 在健康检查失败之后,会将故障的 Pod 直接干掉,故障现场没有保留,不利于问题的排查定位。而 ReadinessProbe 只会将故障的 Pod 从 Service 中踢除,不接受流量。

  使用了 ReadinessProbe 后,可以实现滚动升级不中断业务,只有当 Pod 健康检查成功之后,关联的 Service 才会转发流量请求给新升级的 Pod,并销毁旧的 Pod。

  Spring Cloud 使用 Eruka 接受服务注册请求,并在内存中维护服务列表。

  当一个服务作为客户端发起跨服务调用时,会先获取服务提供者列表,再通过某种负载均衡算法取得具体的服务提供者地址(IP + Port),即所谓的客户端服务发现。在本地开发环境中我们使用这种方式。

  由于 OpenShift 天然就提供服务端服务发现,即 Service 模块,客户端无需关注服务发现具体细节,只需知道服务的域名就可以发起调用。

  由于我们有 Node.js 应用,在实现 Eureka 的注册和去注册的过程中都遇到过一些问题,不能达到生产级别。

  如果一个服务需要暴露到外部怎么办,比如暴露前端的 HTML 文件或者服务端的 Gateway。

  我们将前端的资源也作为一个 Pod 并有对应的 Service,当请求进入 HAProxy 符合规则就会转发到 UI 所在的 Service。

  其目的就是当一个请求经过多个服务时,可以通过一个固定值获取整条请求链路的行为日志,基于此可以再进行耗时分析等,衍生出一些性能诊断的功能。

  不过对于我们而言,首要目的就是 Trouble Shooting,出了问题需要快速定位异常出现在什么服务,整个请求的链路是怎样的。

  为了让解决方案轻量,我们在日志中打印 RequestId 以及 TraceId 来标记链路。

  RequestId 在 Gateway 生成表示唯一一次请求,TraceId 相当于二级路径,一开始与 RequestId 一样,但进入线程池或者消息队列后,TraceId 会增加标记来标识唯一条路径。

  举个例子,当一次请求向 MQ 发送一个消息,那么这个消息可能会被多个消费者消费,此时每个消费线程都会自己生成一个 TraceId 来标记消费链路。加入 TraceId 的目的就是为了避免只用 RequestId 过滤出太多日志。

  当日志汇总到日志系统后,如果出现问题,只需要捕获发生异常的 RequestId 或是 TraceId 即可进行问题定位。

  经过一年来的使用,基本可以满足绝大多数 Trouble Shooting 的场景,一般半小时内即可定位到具体业务。

  自带的监控项目已经比较全面,包括 Node,Pod 资源的监控,在新增 Node 后也会自动添加进来。

  开源软件是对中小团队的一种福音,无论是 Spring Cloud 还是 Kubernetes 都大大降低了团队在基础设施建设上的时间成本。

  当然其中有更多的话题,比如服务升降级,限流熔断,分布式任务调度,灰度发布,功能开关等等都需要更多时间来探讨。

  对于小团队,要根据自身情况选择微服务的技术方案,不可一味追新,适合自己的才是***的。

  Java、Python、C++、PHP、JavaScript5大编程语言,我该选哪个?

  2019年8月编程语言排行榜:Python优势尽显,Kotlin一蹶不振

  本书是讲解C++语言程序设计的优秀教程。全书围绕C++语言来组织,开始章节介绍编程的普通感念,接下来详细介绍C++中的继承、多态、异常处理...