持续集成对于微服务的意义:拆之前要先解决合的问题

发布时间:2026/7/3 2:26:08
持续集成对于微服务的意义:拆之前要先解决合的问题 在很多微服务化的文章中很少会把持续集成放在第一篇因为大多数的文章都会将如何拆的问题例如拆的粒度拆的时机拆的方式。为什么需要拆呢因为这是人类处理问题的本质方式将一个大的复杂问题变成很多个小问题解决。所以当一个系统复杂到一定程度当维护一个系统的人数多到一定程度解决问题的难度和沟通成本大大提高因而需要拆成很多个工程拆成很多个团队分而治之。然而当每个子团队将子问题解决了整个系统的问题就解决了么你可以想象你将一辆整车拆成零件然后再组装起来的过程你就可以想象拆虽然不容易合则更难需要各种标准各种流水线才能将零件组装称为车。我们先来回顾一下拆的过程。最初的应用大多数是一个单体应用单体应用一个Java后端后面跟一个数据库基本上就搞定了。随着系统复杂度的增加首先Java程序需要做的是纵向的拆分。纵向拆分首先最外面是一个负载均衡接着是接入的Nginx做不同服务的路由。不同的服务拆成独立的进程独立部署每个服务使用自己的数据库和缓存解决数据库和缓存的单点瓶颈。数据库使用一主多从的模式进行读写分离主要针对读多写少的场景。为了承载更多的请求设置缓存层将数据缓存到Memcached或者Redis中增加命中率。当然还有些跨服务的查询或者非结构化数据的查询引入搜索引擎比关系型数据库的查询速度快很多。服务化架构在高并发情况下仅仅纵向拆分还不够因而需要做真正的服务化。一个服务化的架构如图所示。首先是接入层这一层主要实现API网关和动态资源和静态资源的分离及缓存并且可以在这一层做整个系统的限流。接下来是Web层也就是controller提供最外层的API是对外提供服务的一层。下面是组合服务层有时候被称为编排层compose层是实现复杂逻辑的一层。下面是基础服务层是提供原子性的基本的逻辑的一层他下面是缓存数据库。服务之间需要治理需要相互发现所以一般会有dubbo或者springcloud一样的框架。对所有的服务都应该有监控告警及时发现异常并自动修复或者告警运维手动修复。对于所有的服务的日志应该有相同的格式收集到一起称为日志中心方便发现错误的时候在统一的一个地方可以debug。对于所有的服务的配置有统一的管理的地方称为配置中心可以通过修改配置中心下发配置对于整个集群进行配置的修改例如打开熔断或者降级开关等。通过简单的描述大家可以发现从一个简单的单体应用变成如此复杂的微服务架构除了关心怎么拆的问题还必须关注如何控制拆的风险如何保证代码质量如何保证功能不变不引入新的Bug答案当然就是集成从一开始就集成并且不断的集成反复的将拆分的模块重新组合看看是否能够顺利组合起来并且保证功能的不变。要是不没事儿就组合一下天知道几个月以后还能不能合的起来。别忘了程序是人写的你和你媳妇长时间不沟通都对不上默契别说两个程序员了。二、持续集成就是不断的尝试在一起集成就是在一起。集成的逻辑为什么需要一个统一的代码仓库Git来做代码管理呢是为了代码集成在一起。为什么需要进行构建build呢就是代码逻辑需要集成在一起编译不出错。为什么要单元测试呢一个模块的功能集成在一起能够正确工作。为什么需要联调测试Staging环境呢需要将不同模块之间集成在一起在一个类生产的环境中进行测试。最终才是部署到生产环境中将所有人分开做的工作才算真正的合在了一起。持续集成解决的问题持续集成就是制定一系列流程或者一个系列规则将需要在一起的各个层次规范起来方便大家在一起强迫大家在一起。三、持续集成持续交付持续部署敏捷开发DevOps都啥关系这些概念都容易混淆他们之间是什么关系呢持续集成持续交付持续部署敏捷开发DevOps的关系敏捷开发Agile是一种开发流程是一种快速迭代的开发流程每个开发流程非常短长到一个月短到两个星期就会是一个周期在这个周期中每天都要开会同步每天都要集成。正是因为周期短才需要持续的做这件事情如果一个开发周期长达几个月则不需要持续的集成最后留几个星期的集成时间一起做也是可以的但是这样就不能达到互联网公司的快速迭代也是我们常常看到传统公司的做法。持续集成往往指对代码的提交构建测试的过程也就是上述的在一起的过程。持续交付是指将集成好的交付物例如warjar或者容器镜像部署在联调环境或者预发环境的过程。持续部署是指将交付物持续部署在生产环境的过程。我们常说CICDCD有时候指的是Delivery交付有的是指Deployment部署对于非生产环境自动部署是没有问题的对于生产环境往往还是需要有专人来进行更为严肃的部署过程不会完全的自动化。接下来就是DevOpsDevOps不只是CICD除了技术和流程还包含文化。例如容器化带来的一个巨大的转变是原来只有运维关心环境的部署无论是测试环境还是生产环境都是运维搞定的而容器化之后需要开发自己写Dockerfile自己关心环境的部署。因为微服务之后模块太多了让少数的运维能够很好的管理所有的服务压力大易出错然而开发往往分成很多的团队每个模块自己关心自己的部署则不易出错这就需要运维一部分的工作让研发来做需要研发和运维的打通如果公司没有这个文化研发的老大说我们不写Dockerfile则DevOps是搞不定的。四、从一个持续集成的日常看上述的几个概念如何实践持续集成的流程这是一个持续集成的流程但是运行起来更加的复杂。首先项目开发的流程使用的是Agile用常见的Scrum为例子。Scrum每天早上第一件事情就是开站会standup meeting为什么要站着呢因为时间不能太长微服务的一个模块大概需要5-9人的团队规模如果团队规模太大了说明服务应该进行拆分了这个团队规模是能够保证比较短的时间之内过完昨天的状态的。一定要大家一起开而不要线下去更新Jira虽然看起来一样但是执行起来完全不一样。只有大家一起开一起看燃尽图一起说我昨天做了什么今天打算做什么有什么阻碍才能够让大家都了解情况不要期望大家会去看别人的Jira经验告诉你不会的。而且这个站会对于开发是比较大的压力例如你的一个功能block了依赖方的开发在会议上会暴露出来大家都知道这件事情了一天block两天block第三天你都不好意思去说了这会强迫你将大任务比如原来写1周干一件什么事情写成小时级别这样每天你都有的说昨天完成了一个task而不是周只在那里说干同样一件事情而且一旦有了blockteam lead会知道这件事情会帮你赶紧解决这个事情推进整个项目的进展。让一个技术人员在团队面前承认这件事情我尝试了几天的确搞不定了也是一种压力。站会中的内容其实在前一天晚上就要开始准备了。持续集成要求每天都提交代码这样才能降低代码集成的风险不能埋头写一周一起提交这样往往集成不成功。怎么样才能鼓励每天都提交代码呢一个就是第二天的站会你这个功能代码提交了单元测试通过了第二天才能说做完了否则不算这就逼得你将大任务拆成小任务每天都多次提交。而且Git的提交方式是后提交者有责任去merge保证代码的编译通过和测试通过你会发现如果你不及时提交等你改了一大片代码别人都提交完了这一大片的冲突都是你来merge测试用例不通过的你来fix所以逼的你有一个小的功能的改动就尽早提交pull一下发现没有人提交赶紧提交。提交不是马上进入主库而是需要代码审核这是把控代码质量的重要的环节。代码质量的控制往往每个公司都有文档甚至你可以从网上下载一篇很长很长的Java代码规范。但是我们常常看到的例子是规范是有但是虱子多了不咬人规范太多的谁也记不住等于没有规范。所以建议将复杂的规范通过项目组内部的讨论简化为简单的10几条军规深入人心大家都容易记住并且容器执行。代码审核往往需要注意下面的几方面代码结构整个项目组应该规定统一的代码组织结构使得每个开发拿到另一个人的代码都能看的熟悉的面孔。这也是scrum中提倡的每个开发之间是可替代的当一个模块有了阻碍其他人是可以帮上忙的。至于核心的逻辑估计审核人员也来不及细看这不要紧核心逻辑是否通过不能靠眼睛要靠测试。有没有注释尤其是对外的接口应该有完善的注释方便自动生成接口文档。异常的处理是否抛出太过宽泛的异常是否吞掉异常是否吞掉异常的日志等。对于pom是否有修改引入了新的jar。对于配置文件是否有修改对外访问是否设置超时对于数据库是否有修改是否经过DBA审核接口实现是否幂等因为Dubbo和springcloud都会重试接口。接口是否会升级是否带版本号是否有单元测试当然还有一些不容易一眼看出来的可以通过一段时间通过统一的代码review来修改这些问题某个类代码长度过长设计是否合理高内聚低耦合数据库设计是否合理数据库事务是否使用合理代码是否有明显的阻塞代码审核完毕之后提交上去之后一个是要通过静态代码审查可以发现一些可能带来代码风险的问题例如异常过于宽泛等。在就是要通过单元测试。我们应该要求每个类都要有单元测试并且单元测试覆盖率要达到一定的指标。单元测试要有带Mock的模块内的集成测试。在编译过程中会触发单元测试单元测试不通过已经代码覆盖率都会统计后发邮件抄送所有的人这对于研发来讲又是一个压力。当有一天你的提交break掉了测试或者代码覆盖率很低则就像通报批评一样你需要赶紧去修改。单元测试完毕之后就会上传成果物或者是war或者是jar一般会用nexus因为有版本号有md5可以保证安装在环境中的就是某个版本的某个包我们还遇到过有使用FTP的这样一个是很难保证版本号的维护升级和回滚比较难弄另一个是没有md5很可能包不完整都有可能的而且一旦发生很难发现。如果使用了容器则还需要编译Dockerfile使用Docker镜像作为交付能够实现更好的环境一致性保证原子的升级和回滚。每天下班前当天的代码需要提交到库中去晚上会做一次统一的环境部署和集成测试。每天晚上凌晨会有自动化的脚本将Docker镜像通过编排部署一个完整的环境然后跑集成测试用例集成测试用例应该是基于API的很多的公司是基于UI的这样由于UI变化太快还有UI不能覆盖所有的场景所以还是建议UI和API分离通过API进行集成测试有了每天的测试才能保证每天晚上的版本都是可以交付的版本也保证我们微服务拆分的时候尽管改了很多不会因为新的修改破坏掉原来能够通过的测试用例保证不会有了新的坏了旧的。这个集成测试或者叫回归测试每天晚上都做都是在一个全新的环境中这就是持续部署和持续交付。如果某一天测试不通过则会发出邮件来是因为当天谁的哪个提交导致测试不通过抄送所有人这是另一个压力。所以第二天的站会上昨天你完成了哪些功能是否提交了是否完成了单元测试是否通过了集成测试就都知道了你需要给大家一个解释然后进入到新一天的开发。到了两周一个周期完毕可以上线到生产环境了可以通知有权限的运维进行操作但是也是通过自动化的脚本进行部署的。这就是整个过程层层保证质量从中可以看到敏捷开发持续集成持续交付持续部署DevOps是互相联系的少了任何一个整个流程都玩不转。五、有关代码结构代码结构往往包括API接口包访问外部服务包数据库DTO访问数据库包服务与商务逻辑外部服务如果使用Dubbo RPC则API接口往往在一个单独的jar里面被服务端和客户端共同依赖但是使用了springcloud的restful方式就不用了只要在各自的代码里面定义就可以了会变成json的方式传递这样的好处是当jar有多个版本依赖需要升级的时候关系非常复杂难以维护而json的方式比较好的解决了这个问题。这个模块提供了哪些接口只要到API接口这个package下面找就可以了。因为无论是Dubbo还是springcloud接口的调用都会重试因而接口需要实现幂等。访问外部服务的包这将所有对外的访问独立出来好处一是可以抽象出来在服务拆分的时候可能会用到例如原来支付的逻辑在下单的模块中要讲支付独立出来则会有一个抽象层涉及到老的支付方式还是调用本模块中的逻辑涉及到新接入的支付方式使用远程调用有了这一层方便的多。好处二是可以实现熔断当被调用的服务不正常的时候在这里可以返回托底数据。好处三是可以实现Mock这样对于单元测试来讲非常好不用依赖于其他服务就可以自己进行测试。DTO和访问数据库的包看到了这些数据结构会帮助程序员快速掌握代码逻辑不知道大家有没有这个体验你去看一个开源软件的代码首先要看的是他的数据结构数据结构和关系看懂了代码逻辑就比较容易懂了如果数据结构没看懂则光看逻辑就容易云里雾里的。还有就是核心的代码逻辑和对接口的实现。在这里面是软件代码设计的内功所在但是却不是流程能够控制的。六、有关接口设计规范