SpringBoot单体服务是如何搭建成SpringCloud微服务

本文是读了《Spring微服务实战》一书后的总结,介绍如何将传统应用程序一步步构建成分布式微服务架构。

传统应用程序是怎样的呢?

​ 传统应用程序是高度耦合的,开发者将业务逻辑,模块之间的调用硬编码在代码之中,使各个模块之间相互依赖。

​ 在软件规模扩大之后,通常遭遇到这样一种情况:针对某一需求的修改可能会对其他依赖此需求的部分产生影响,从而牵一发而动全身,同时每一修改都需要重新构建、编译和部署。虽然使用Spring框架可以降低耦合,但对于大型,需求复杂的多个开发团队同时开发的软件项目来说,依然捉襟见肘。

​ 不安全的。一般软件都将相同数据模型放在同一数据库中,即使是不同的业务数据。这会造成单个模块的访问权限过大,每个业务模块应当只负责并且只有当前所涉及业务内的数据访问权限。

​ 伸缩性是有限的,不灵活的。传统应用服务在处理高并发,多用户时,一般采用提升机器性能来提升处理速度,这样的解决方案在短期的效果是很显著的,但提升性能和机器的成本比例是不划算的,并且机器的性能是有瓶颈的;另一种解决方案是用多台性能一般的机器搭建服务集群,这种方案对服务程序是有要求的,应用程序必须是无状态的,也就是对于业务,用户等相关的数据不能再本地缓存,服务可以在任何超时时被杀死和替换,而不用担心一个服务实例的丢失会导致数据丢失。

​ 由于不均匀的容量需求,用户对某一快业务的访问量是不同的,在以上搭建集群的时候,水平扩展了所有的业务模块以满足某一部分功能的处理能力提升。在服务器中的任何资源都是十分宝贵的,这造成了对服务资源的浪费。

面向服务的分布式架构

​ 针对以上问题,软件不得不进行重构,采用面向服务的分布式架构(SOA)。合理拆分软件的不同业务模块,是模块之间协调工作,如图结构:

​ 服务消费者通过DNS解析地址到主负载均衡器,主负载均衡器查询路由表获取服务实例位置,调用服务。辅助负载均衡器会处于空闲状态,仅在主负载均衡器中断的情况下才会切换故障处理负载。

统一配置

​ 对于系统中的每个服务实例,开发人员必须在服务器上对应用程序进行配置,这可能适用于少量服务,在对包含数百个服务的应用程序配置时,不仅是配置的重复程度,开发人员无法保证每个服务的服务实例的运行环境以及配置属性是完全相同的。

​ 可以采用统一配置服务器控制所有的服务器配置(Spring Cloud Config)。这样在服务启动时就可以都向配置服务器拉取当前服务的配置信息,而配置服务可以统一管理所有服务配置,并对它进行版本化控制。

服务发现

​ 由图1结构可以发现一些弊端:

  • 服务消费者会高度依赖主负载均衡器,主负载均衡器是应用程序基础设施中的集中式阻塞点,也是整个基础设施的单点故障,如果它出现故障,那么依赖它的每个应用程序都会出现故障。
  • 有限的伸缩性依然存在。在服务集中到单个负载均衡器集群的情况下,跨多个服务器水平伸缩的能力有限,一般情况下只能使用单个服务器处理负载,而辅助负载均衡器会处于空闲状态,仅在主负载均衡器中断的情况下,才会切换故障处理负载。
  • 通过手动配置添加路由信息,不能快速对服务进行注册和注销。手动配置服务消费者的请求和服务路径的映射关系,这可能会造成由于部署或修改时的小失误而导致系统迅速崩溃。

​ 针对以上问题,可以引入服务发现引擎。通过服务发现集群实现高可用。服务可以在服务发现服务中进行注册,并且由于集群的特性,如果一个节点变得不可用,集群中的其他节点可以接管工作。

​ 根据服务发现的实现机制不同,集群的工作策略也不同。

​ 例如:

​ Eureka服务发现引擎侧重于提供高可用的服务发现服务。服务消费者本地储存多个服务发现集群节点,当集群中的某一节点崩溃时,将切换其他节点进行访问。

​ Zookeeper则侧重于提供统一的可用健康的服务实例,采用主/从模式。集群中有一个leader节点,其他节点从leader节点同步数据,当leader节点崩溃时,其他节点能够进行投票选举出新的leader节点对外提供服务,但在选举期间就会出现服务发现无法提供服务的情况。Zookeeper集群的特点是在保证一半以上的节点存活时能够对完提供服务,并且一般集群的主机数量为奇数。

​ 这样服务的提供者对于服务消费者来说是位置透明的,服务消费者不需要关心服务提供者是谁,也不用管理服务提供者的健康状态,服务发现服务会定期检查所注册服务的健康状态,对于未能返回良好状态的服务都将从服务实例池中删除。

​ 但是使用这种方法依然很脆弱,服务消费者完全依赖于服务发现引擎来查找调用服务。为服务消费者添加客户端负载均衡可以在服务发现代理不可用时,客户端依然能够与服务提供者正常通信,增加了整个系统的健壮性。

​ 当服务消费者调用服务时,首先会检查本地缓存中的服务位置信息,如果没有则会联系服务发现服务,获取它的所有服务实例,并缓存到本地。在获得服务实例列表后,它会使用简单的负载均衡算法,如“轮询”,以确保服务调用分布在多个服务实例之间。并且,客户端会定期与服务发现服务联系刷新服务实例缓存。

​ 当某次调用服务失败,客户端会通知服务发现引擎删除该实例,并从服务发现服务刷新缓存数据。

客户端弹性模式(Hystrix)

​ 当服务实例彻底崩溃时,很容易检测到服务死亡,应用程序可以绕过它,但当服务运行缓慢时,检测服务性能,健康状态不佳时是非常困难的。

​ 并且潜在的可能会由于它的运行缓慢将耗尽整个服务器的资源,乃至拖垮整个服务系统。比如一个系统有A,B,C三个服务,关系如图:

​ A 服务依赖B服务,B服务依赖C服务,C服务于数据库连接。

​ 此时,如果由于某些原因导致C服务与数据库通信缓慢,那么C服务的数据库连接将会堵塞,而接下来请求新建的数据库连接依然会堵塞,最终会导致数据库连接池资源耗尽;而调用C服务的B服务的线程也将会堵塞,并且最终服务器容器中的线程池也会迅速的消耗殆尽,最后A服务也将耗尽资源,因为他调用了B服务,而整个起点是因为C服务访问数据库缓慢。

断路器

​ 我们可以在服务消费者与服务提供者之间加上断路器,可以避免这种情况。

​ 当开启一个请求时,断路器将监视这个调用,如果调用时间太长,断路器就会强制终止此次调用,如果调用失败次数过多,那么断路器会采取快速失败阻止将来调用。

后备模式

​ 我们还可以添加后备处理,在远程调用失败之后。采用执行替代的代码给用户返回一个友好的结果而不是一个错误警告。有时候旧数据比没有数据好。

舱壁模式

​ 为了降低一个缓慢的调用拖垮整个应用程序的风险,可以为每个服务调用单独分配线程池,这样即使服务响应缓慢,也只会耗尽当前线程池的资源,而不会影响其他的服务调用。

服务路由(Zuul)

​ 在分布式架构中某些关键的行为经常要跨多个服务调用,如安全,日志记录,用户跟踪等。为了实现他们。要求开发人员在所有服务中始终如一地强制这些特性,而不需要每个开发团队构建自己的解决方案,尽管可以采用公共库或自定义框架来实现这些功能,但会带来一些影响,比如开发人员可能会忘记和遗漏编写日志或跟踪,并且这会对所有服务造成依赖,在修改公共框架或新增功能时,必须重新编译和部署所有的服务,这是一件非常糟糕的事情。

​ 对于日志记录此类事件,在服务中共同存在的称为横向关注点,还记得Spring中的切面、切点概念吗。我们可以将这些横切关注点抽象成一个独立且作为应用程序中所有微服务调用的过滤器和路由器的服务,称之为“服务网关”。将所有的调用服务放在服务网关的后面,客户端只与服务网关通信,由服务网关代理转发客户端调用。

​ 服务网关如此一来就控制住了微服务的所有流量入口,所有的横切服务都可以在这实现,并且所有服务调用都映射到一个URL后面,在搭建微服务时可以将所有服务都放在内网之中,只暴露服务网关的外网调用端口,极大极高了服务的安全性。

​ 之前的访问:

​ 加入服务网关:

​ 大家可能发现这样服务网关不就成了单点故障和阻塞点了吗?可以构建服务网关集群,在多个服务网关实例前放置负载均衡器。在单个服务组前面,负载均衡器是很有用的,但放置在所有服务实例的前面将会成为瓶颈。部署服务网关时要保证其实无状态的,轻量的,具有多个数据库调用的复杂代码可能是网关中难以跟踪的性能问题来源。

保护微服务(OAuth2)

​ 系统安全是非常重要的,对用户身份的验证与授权是保证资源不为恶意使用的手段之一,采用OAuth2构建验证服务器,对用户和应用程序进行身份验证和授权。

​ 用户使用客户端向验证服务器提供凭据,验证服务器需要验证客户端和用户,成功后返回一个令牌,客户端使用此令牌访问受保护资源服务,受保护资源服务收到令牌后向验证服务器确认令牌的合法性,并获取用户基本信息,如果受保护资源服务需要调用其他服务依然要提供客户端传来的令牌给被调用服务,被调用服务向验证服务确认。

总结

​ 这里只是本人对《Spring微服务实战》书的阅读总结,其中还有部分没有总结到,比如还可以在微服务中引入消息队列,缓存服务等,利用Zipkin进行分布式跟踪,构建持续集成/持续交付(CI/CD)管道等。该书采用理论是实践结合的方式讲述SpringBoot服务如何一步一步搭建成微服务。作为读者也对微服务的概念有了一个清晰的认识。并且在以后的开发过程中也会注意到书中所讲的一些问题,总之收获很大。

​ 最后一个完整的微服务架构图:

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章