炸毁巨石

作者: fossilman | 来源:发表于2019-11-08 16:25 被阅读0次

炸毁巨石 - 采用基于微服务的架构

引言

对于软件行业来说,这是一个激动人心的时刻。

我们正处在一个软件革命的时代,构建、部署以及我们使用服务的方式正发生着巨大的变化,正如我们所学到的,变化不仅存在于技术方面,也体现在组织层面。新的范式、模式和基础正在逐步形成新的行业,无论从技术还是文化上讲,我们都正迈入一个新的时代。开源软件一直以来都是该行业的重要贡献者,时至今日,它已经成为了企业所采用的最重要的组成部分。

有人建议解耦单体应用就像将单个繁琐的结构拆开,并像拼装乐高积木一样重新搭建它,这可能是一个相当困难的过程,然而一旦每一个独立的组件完成了其应有的功能,并且能够拼装在一起,全部功能运行起来就好像一个单独的整体。对于应用程序来说,意味着从原先单一的大型代码库迁移到了较小的隔离服务,服务之间相互交流以提供整体功能。由于此时可能客户还在使用该应用程序,还有线上流量,需要一种方式在重构乐高积木过程中,继续保持现有结构功能有效。

你应当炸毁巨石嘛?

在转向微服务制定计划时间并且筹备资源之前,思考是否有必要是一个很好的问题。组织的总体目标是什么,以及怎样的应用程序体系结构最有助于实现这些目标?迁移到微服务架构的利弊同样引人注目。微服务在发布和更新应用时提供了更大的灵活性,因为微服务开发周期更短也更为专注。微服务也会带来更高的效率,开发团队可以拆分成较小的小组,例如披萨团队,在开发应用程序时可以独立工作。此外,微服务使得应用程序更容易扩展,无论是现有的扩展方式或者云部署方式。

具有讽刺意味的是,微服务架构的某些特性,也有助于考虑使用在单体应用结构中,尽管微服务在应用程序整体的开发和管理中提供了更大的灵活性,但是由于在部署微服务时需要更多的协调和通信,也增加了应用程序的复杂性,从而影响了应用程序的性能。单体应用是一个单一的,独立的代码库,因此应用程序性能方面的考虑仅限于代码开发,而可以广泛分布的微服务系统更多的需要考虑网络的需求,以便每个微服务能够彼此通信,向着同一个目标努力。在选择微服务,还是单体应用时,性能、可部署行和可伸缩性无疑将排在考量指标的首位。

采用微服务的注意事项

如前所述,转向微服务的一个主要动力,即是保持单一代码库效率极低,并且当组织追求业务敏捷时异常困难,但这并不意味着过渡到微服务就一帆风顺。

在开始过渡计划之前,请确定单一代码库中最大的痛点和边界,并将它们解耦到单独的服务中,前期不要为服务的大小(sizing)投入过多的精力,因为这与它们背后的代码量有关,更为重要的是确保这些服务能够在它们所分配的边界内继续处理其特定的业务逻辑。当涉及微服务时,对架构师和开发人员来说,过多地关注从单体应用解耦的服务的代码量是很常见的,但现实情况时,这些服务的大小取决于处理特定业务逻辑所需要的代码多少,解耦的太细,可能会产生过多的活动组件,在用户真正了解了这种新架构下构建和操作的难点,将来有的是时间去拆分更细粒度的服务。

需要记住的另一个重要考虑因素是,当用户开始过渡到微服务时,现有的业务仍在单体应用上运行并不断增长,因此可以将开发工作拆分成两个较小的团队来赢的省力,一个团队维护旧的代码库,另一个则在新的代码库。这样做意味着必须对资源分配保持敏锐的洞察力,两个团队分散工作会产生副作用,因为维护单体应用程序并不像研究新技术那样令人兴奋,在较大的团队中,团队成员可以在两个项目之间轮换,这样有助于解决刚提到的问题,这使所有团队成员都承担着新的开发任务并学习到了新技能,同时也关注到了单体应用的变化,这是程序员喜欢的工作方式。

还有一个需要考虑的因素:时间。事实是,向微服务过渡不会在一夜之间完成,无论前期如何细致的规划,过渡都不会很快完成。

微服务的两种通信方式

除上述情况外,使用微服务时要学习的最大课程之一,就是了解何时使用服务间通信或异步通信。即使在今天,这种观点看上去只是推动了服务间通信的发展,尽管这并不一定是具体场景的最佳实践。
两种模式之间的主要区别在于,异步系统不会直接与另一个微服务通信,而是会异步传播一个时间,另一个服务对其进行拦截并作出反应(通常可以使用 Apache Kafka 之类的日志收集器,也可以使用 RabbitMQ 或 AWS SQS 之类的云服务)。

服务间通信.png 异步通信.png

当服务不需要立即响应时,此模式就非常有用,因为从第一天开始,它基本就在体系结构模式中实现错误处理,它是在微服务之间保证数据状态一致性的最佳选择。

通信场景

假设,用户有两个微服务,即订单和发票,当每次创建订单时,同时需要创建一张发票。在服务间通信模式中,每次创建订单时,订单服务都必须向发票服务发出请求,但是如果发票服务关闭或者最终不可用,则请求将超时并失败。即使该请求是由中间代理发出的,会尝试一遍又一遍发出请求,如果最终发票服务仍处于关闭,还是会超时失败,这将导致数据不一致。

通过异步模式,订单服务会在日志收集器或者队列中创建一个事件,无论何时决定轮询或者监听,发票服务都会对其进行异步处理。因此,即使发票服务长时间处于关闭状态,一旦服务再次上线(并且假设日志收集器中的消息还在),这些发票就不会丢失,并且最终数据在我们系统中保持一致。
使用类似 Apache Kafka 的系统,开发人员还可以轻松地重放从特定时间戳开始的一系列事件,以便在本地或线上任何事件重现数据状态。

理想情况下,微服务体系结构会根据特定场景利用这些模式达到最终状态一致,服务间通信并不是唯一的答案。

过渡中的技术细节

既然已经了解了单体服务和微服务可以带来什么,现在该考虑如何进行技术过渡了。可以采用不同的策略,但是所有的策略都需要相同的准备工作,确定边界并改进测试,这些准备工作对我们是否成功至关重要,否则会深陷泥潭,这一点不容忽视。

边界

开始过渡之前,首先要想清楚的是,需要从单体代码库中创建或分解哪些服务,以及在完整的微服务体系结构中,自身的体系结构是怎么样的,多大,彼此之间如果交互。出发点是检查那些受单体应用负面影响更大的边界,比如那些更常部署、更频繁修改或者更需要弹性扩展的组件。

测试

过渡到微服务实际上是一种重构,因此在常规重构之前遵循的所有常规预防措施也适用于此,尤其是测试。
随着过渡的进行,系统从根本上的工作方式也随之变化。请务必注意,在过渡阶段之后,整体中曾经存在的所有功能在重新设计的架构中仍然起作用,此处的最佳实践是,在尝试进行任何更改之前,必须对整体编写可靠的集成和回归测试套件。
其中一些测试用例可能会在此过程中失败,但是经过良好测试的功能可以保证生产环境正常运行。

过渡策略

过渡到微服务时,有多重策略可供选择,每个策略都有各自的优缺点,在选择之前应该加以考虑。

冰激凌策略

此策略要求通过将单体服务中的不同组件挖出到单独的服务中,从而从单体应用逐步过渡到微服务体系结构,这种过渡是渐进的,有时会同时存在单体服务和微服务。
优点:在不影响正常运行时间和终端用户体验的情况下,以较低的风险逐渐迁移到微服务。
缺点:这是一个循序渐进的过程,需要一段时间才能完全执行。

乐高策略

对于认为自己的单体应用太大而无法重构的组织,并希望保持原样,该策略主张仅将新功能构建为微服务。实际上。这不能解决现有的单体代码库的问题,但可以解决产品将来的扩展问题,该选项要求在混合体系结构中有效地将单体服务和微服务彼此堆叠。
优点:无需对原来的单体应用做过多工作,而且速度很快。
缺点:原先的单体应用还是存在问题,很可能必须创建新的 API 来支持面向微服务的功能。
乐高策略有助于在大型重构上换取一些时间,但最终会冒着单体应用增加更多技术债的风险。

核选择策略

第三种策略很少采用,将整个单体应用立即全部重写为微服务,也许同时仍通过补丁修改老的单体应用,但是在新的代码库中会构建每个新功能,出乎意料的是,我遇到过几家决定采用此项策略的企业,因为他们认为在旧的单体上再进行工作时不可行的,因此决定放弃这项工作。
优点:允许组织重新考虑事情的完成方式,从头开始有效地重写应用程序。
缺点:需要从头开始重写应用。
核选择策略还可能会面临第二个系统综合症,在这种情况下,终端用户将陷入僵局,直到新的架构完成部署。

数据库

新的面向微服务的体系架构的最终目标是每个服务不依赖于同一个数据库,取而代之的是,由于这种新架构需要将不同服务之间的业务逻辑解耦,因此用户还需要解耦数据库访问,即每个微服务都拥有一个独立的数据库。有时这是不可避免的,例如处理用户的服务适用关系型数据库,而处理订单的服务适用例如 Cassandra 这样既能保证最终一致性,同时满足高性能写入的数据库。

有时,某些服务确实会使用相同底层技术的数据库,对于低吞吐量的实例来说,使用同一个数据库存储不同业务逻辑的数据会十分方便,但即使如此,还是最好为每个微服务设置专用的数据库集群,之前方案的缺点是如果某个微服务影响了数据的正常运行,其他微服务也会受到影响(因为它们共用一个数据库),因此需要避免这种情况,加强隔离。

无论如何设置,数据一致性始终都是问题,当旧的代码库仍在读写老的代码库,而新的数据库用于微服务是,将会产生一个停滞期。此时,微服务将看不到老的单体应用所做的读写数据,反之亦然,这不是一个容易解决的问题,需要考虑如下问题,包括:

  1. 写入单体应用的数据也将迁移到微服务数据库,反之亦然;
  2. 为老的数据库构建易于使用的 API,微服务将使用该 API 查询旧数据库中的数据;
  3. 引入一个时间收集器层(例如 Kafka),该层负责向两个数据库之间传播写操作。
微服务数据层.png

路由和版本

每个微服务都可以通过某种类型的 API 进行访问,如前所述,每个微服务将通过其 API 使用其他服务,而无需了解底层实现,所以只要 API 接口保持不变,无论底层如何修复 Bug,或者提升性能,其他微服务还是如往常一样调用该服务。

每次进行更改时,用户都不希望将所有流量都路由到该服务的新版本,毕竟这是一个冒险的举动,如果存在任何错误,将会影响整个应用程序。取而代之是逐步转移流量,监控新版本的行为,只有在对新版本充满信心的情况下,再转移其余流量,该策略也称为金丝雀发布,可以减少由于错误发布而导致的停机时间。

微服务网关.png

无论是解耦单体应用,还是微服务升级不同版本,都需要路由功能。

微服务网关2.png

库和安全

根据代码库的不同,将单体应用中的功能解耦到第三方库中非常有用,然后在单体应用和微服务中都可以使用它们,这对同时修复两个库的 Bug 或者提升性能时的修改也十分有好处。

一般而言,处理特定逻辑的库仅由一个微服务实现不会造成任何问题,因为库中的任何更改仅需要升级一项服务,但是同时更新许多微服务共同使用库会导致问题,因为任何更新都必须触发多次重新启动。

话虽如此,因为实际场景过于复杂,以至于很难给出一个准则。从长远来说,任何妨碍微服务独立部署或相互隔离的障碍都应该删除。
身份校验和鉴权可以由单体应用内部实现,也可以在第三库中实现,也可以通过在架构中独立出 API 网关层实现。考虑到需要接入越来越多的微服务而需要增加可伸缩性,鉴权模块有必要重新设计。

微服务之间的安全性应强制执行双向 TLS,以确保架构中未经授权的客户端无法使用它们,应该全面启用日志记录,以监控微服务的行为。

在这一点上,检查异常、延迟时间和错误率的可视化指标已成为关键,一个好的经验法则是为每个服务配置运行状况检查,如果发生太多错误,断路器可以跳闸并防止发生级联故障。

容器和服务网格

尽管利用诸如 Kubernetes 这样的容器编排工具可以使工作变的更加轻松,但从技术上来说,并不一定非要使用 Docker 之类的容器。Kubernetes 提供了许多现成的功能,包括用于扩展和缩减工作负载、服务发现以及联通微服务之间的网络功能。除此之外,Service Mesh 是一种新兴的架构设计,旨在通过一种模式将一些工作委托给第三方代理,实现服务与服务间的通信,通常情况下是采用 Kubernetes sidecar 与微服务共同运行,执行连接管理、安全性、错误处理和监控的操作。

服务网格模式还引入熟悉的网络概念,诸如 Control Plane,通常用来管理我们的系统;Data Plane,通常用于处理请求,这两层的数据相互通信,进而传播配置并收集度量数据。

服务网格.png

总结

过渡到微服务是一项重大的工程投资,对于达到一定规模的应用程序也具有同等显著的收益,因此每个行业的企业组织都在采用或部署微服务体系结构,以帮助应对不断增长的代码库和更大的团队痛苦。这既是技术上的过渡,也是组织结构的过渡,因为它不仅需要解耦代码,还需要调整开发团队的结构。

相关文章

  • 炸毁巨石

    炸毁巨石 - 采用基于微服务的架构 引言 对于软件行业来说,这是一个激动人心的时刻。 我们正处在一个软件革命的时代...

  • 巨石

    巨石 王浴海 一石耸起立长滩, 巨浪推花送脚前。 一二绿藻膝下绕, 六七白鸥脑顶盘。 远山生烟裏落...

  • 巨石

    巨石横陈天绝作, 武夷纵贯地依托。 盘古江浙本苍海, 数次大震换山河。 海水败流回东海, 地面向西高原多, 今朝巨...

  • 巨石

    巨石文/浮光掠影 锋芒深埋,只以荒草为邻风雨一遍又一遍地洗过巅峰之上,以挺拔的姿态看草长莺飞,花开花落.晶莹的水色...

  • 巨石

    路边横躺着的巨石 自被谁从河道里移到这里后 仍是默默无闻 无人理睬 不像旁边的花花草草和落叶乔木 还能时不时受到人...

  • 巨石

    石头压在身上多年 所有的努力都无济于事 推不动卸不下 直到成为身体的一部分 洗心石给它唤个好听的名字 石头主宰我同...

  • 巨石

    《巨石》 文/流年手卷 它斜倚在那里 仿佛随时都会倾倒下来 其实,它站着或躺着 都一样,一个...

  • 巨石

    山上有奇峰, 磷岣露峥嵘。 奇石浑天成。 亘古天生成。 石如定海针, 巨如十间房。 高大数十丈。 像玉皇的茶机, ...

  • 巨石

    牛儿在山坡吃草 牛铃摇动山腰 巨石应声落地 蹲在地上啜泣 巴马的大山 生我养我千万年 眨眼成了永远地别离 我该去哪...

  • 巨石

    巨石高耸 山外有山 2022.10.6随拍

网友评论

    本文标题:炸毁巨石

    本文链接:https://www.haomeiwen.com/subject/ykkwbctx.html