SpringCloud简介与微服务架构 解析SpringCloud简介与微服务架构
MPolaris 人气:01. 微服务架构
1.1 微服务架构理解
微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。你可以将其看作是在架构层次而非获取服务的类上应用很多SOLID原则。微服务架构是个很有趣的概念,它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。
概念
:把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。定义
:围绕业务领域组件来创建应用,这些应用可独立地进行开发、管理和迭代。在分散的组件中使用云架构和平台式部署、管理和服务功能,使产品交付变得更加简单。本质
:用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。
1.2 传统开发模式和微服务的区别
传统的web开发方式
通过对比比较容易理解什么是Microservice Architecture。和Microservice相对应的,这种方式一般被称为Monolithic(单体式开发)。所有的功能打包在一个 WAR包里,基本没有外部依赖(除了容器),部署在一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI等所有逻辑。
优点:
- 开发简单,集中式管理
- 基本不会重复开发
- 功能都在本地,没有分布式的管理和调用消耗
缺点:
- 效率低:开发都在同一个项目改代码,相互等待,冲突不断
- 维护难:代码功功能耦合在一起,新人不知道何从下手
- 不灵活:构建时间长,任何小修改都要重构整个项目,耗时
- 稳定性差:一个微小的问题,都可能导致整个应用挂掉
- 扩展性不够:无法满足高并发下的业务需求
常见的系统架构遵循的三个标准和业务驱动力:
- 提高敏捷性:及时响应业务需求,促进企业发展
- 提升用户体验:提升用户体验,减少用户流失
- 降低成本:降低增加产品,客户或业务方案的成本
基于微服务架构的设计
目的:有效的拆分应用,实现敏捷开发和部署
关于微服务的一个形象表达
X轴:运行多个负载均衡器之后的运行实例Y轴:将应用进一步分解为微服务(分库)Z轴:大数据量时,将服务分区(分表)
1.3 微服务的具体特征
官方定义
- 一些列的独立的服务共同组成系统
- 单独部署,跑在自己的进程中
- 每个服务为独立的业务开发
- 分布式管理
- 非常强调隔离性
大概的标准
- 分布式服务组成的系统
- 按照业务,而不是技术来划分组织
- 做有生命的产品而不是项目
- 强服务个体和弱通信( Smart endpoints and dumb pipes )
- 自动化运维( DevOps )
- 高度容错性
- 快速演化和迭代
1.4 怎么具体实践微服务
客户端如何访问这些服务 - API Gateway
原来的单体开发,所有的服务都是本地的,UI可以直接调用,现在按功能拆分成独立的服务,跑在独立的一般都在独立的虚拟机上的 Java进程了。客户端UI如何访问他的?后台有N个服务,前台就需要记住管理N个服务,一个服务下线/更新/升级,前台就要重新部署,这明显不符合我们拆分的理念,特别当前台是移动应用的时候,通常业务变化的节奏更快。另外,N个小服务的调用也是一个不小的网络开销。还有一般微服务在系统内部,通常是无状态的,用户登录信息和权限管理最好有一个统一的地方维护管理(OAuth)。
所以一般在后台N个服务和UI之间一般会一个代理或者叫 API Gateway
,他的作用包括:
- 提供统一服务入口,让微服务对前台透明
- 聚合后台的服务,节省流量,提升性能
- 提供安全,过滤,流控等API管理功能
其实这个API Gateway可以有很多广义的实现办法,可以是一个软硬一体的盒子,也可以是一个简单的MVC框架,甚至是一个Node.js的服务端。他们最重要的作 用是为前台(通常是移动应用)提供后台服务的聚合,提供一个统一的服务出口,解除他们之间的耦合,不过API Gateway也有可能成为单点故障点或者性能的瓶颈。用过Taobao Open Platform(淘宝开放平台)的就能很容易的体会,TAO就是这个API Gateway。
每个服务之间如何通信 - IPC
所有的微服务都是独立的Java进程跑在独立的虚拟机上,所以服务间的通信就是IPC(inter process communication),已经有很多成熟的方案。现在基本最通用的有两种方式:
同步调用:① REST(JAX-RS,Spring Boot)② RPC(Thrift, Dubbo)
异步消息调用:(Kafka, Notify, MetaQ)
同步和异步的区别:
一般同步调用比较简单,一致性强,但是容易出调用问题,性能体验上也会差些,特别是调用层次多的时候。RESTful和RPC的比较也是一个很有意思的话题。一般REST基于HTTP,更容易实现,更容易被接受,服务端实现技术也更灵活些,各个语言都能支持,同时能跨客户端,对客户端没有特殊的要求,只要封装了HTTP的SDK就能调用,所以相对使用的广一些。RPC也有自己的优点,传输协议更高效,安全更可控,特别在一个公司内部,如果有统一个的开发规范和统一的服务框架时,他的开发效率优势更明显些。就看各自的技术积累实际条件自己的选择了。
而异步消息的方式在分布式系统中有特别广泛的应用,他既能减低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢。不过需要付出的代价是一致性的减弱,需要接受数据最终一致性;还有就是后台服务一般要 实现幂等性,因为消息发送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验);最后就是必须引入一个独立的broker,如果公司内部没有技术积累,对broker分布式管理也是一个很大的挑战。
如此多的服务如何实现?- 服务发现
在微服务架构中,一般每一个服务都是有多个拷贝来做负载均衡。一个服务随时可能下线也可能应对临时访问压力增加新的服务节点。服务之间如何相互感知?服务如何管理?这就是服务发现的问题了。一般有两类做法,也各有优缺点。基本都是通过zookeeper等类似技术做服务注册信息的分布式管理。当服务上线时,服务提供者将自己的服务信息注册到ZK(或类似框架),并通过心跳维持长链接,实时更新链接信息。服务调用者通过ZK寻址,根据可定制算法找到一个服务,还可以将服务信息缓存在本地以提高性能。当服务下线时,ZK会发通知给服务客户端。
客户端做服务发现:优点是架构简单,扩展灵活,只对服务注册器依赖。缺点是客户端要维护所有调用服务的地址有技术难度,一般大公司都有成熟的内部框架支持,比如Dubbo。
服务端做服务发现:优点是简单,所有服务对于前台调用方透明,一般在小公司在云服务上部署的应用采用的比较多。
服务挂了如何解决 - 熔断机制,限流,负载均衡...
前面提到,Monolithic方式开发一个很大的风险是把所有鸡蛋放在一个篮子里,一荣俱荣一损俱损。而分布式最大的特性就是网络是不可靠的。通过微服务拆分能降低这个风险,不过如果没有特别的保障结局肯定是噩梦。所以当我们的系统是由一系列的服务调用链组成的时候,我们必须确保任一环节出问题都不至于影响整体链路。
相应的手段有很多:这些方法基本都很明确通用,比如Netflix的Hystrix:https://github.com/Netflix/Hystrix
- 重试机制
- 限流
- 熔断机制
- 负载均衡
- 降级(本地缓存)
1.5 微服务的优缺点
微服务的优点:
关键点:复杂度可控,独立按需扩展,技术选型灵活,容错,可用性高
- 它解决了复杂性的问题。它会将一种怪异的整体应用程序分解成一组服务。虽然功能总量 不变,但应用程序已分解为可管理的块或服务。每个服务都以RPC或消息驱动的API的形式定义了一个明确的边界;Microservice架构模式实现了一个模块化水平。
- 这种架构使每个服务都能够由专注于该服务的团队独立开发。开发人员可以自由选择任何有用的技术,只要该服务符合API合同。当然大多数组织都希望避免完全无政府状态并限制技术选择。然而这种自由意味着开发人员不再有义务使用在新项目开始时存在的可能过时的技术。在编写新服务时,他们可以选择使用当前的技术。此外由于服务相对较小,使用当前技术重写旧服务变得可行。
- Microservice架构模式使每个微服务都能独立部署。开发人员不需要协调部署本地服务的变更。这些变化可以在测试后尽快部署。例如UI团队可以执行A | B测试,并快速迭代UI更改。Microservice架构模式使连续部署成为可能。
- Microservice架构模式使每个服务都可以独立调整。您可以仅部署满足其容量和可用性限制的每个服务的实例数。此外您可以使用最符合服务资源要求的硬件。
微服务的缺点
关键点(挑战):多服务运维难度,系统部署依赖,服务间通信成本,数据一致性,系统集成测试,重复工作,性能监控等
- 一个缺点是名称本身。术语microservice过度强调服务规模。但重要的是要记住,这是一种手段而不是主要目标。微服务的目标是充分分解应用程序以便于敏捷应用程序开发和部署。
- 微服务器的另一个主要缺点是分布式系统而产生的复杂性。开发人员需要选择和实现基于消息传递或RPC的进程间通信机制。此外他们还必须编写代码来处理部分故障,因为请求的目的地可能很慢或不可用。
- 微服务器的另一个挑战是分区数据库架构。更新多个业务实体的业务交易是相当普遍的。但是在基于微服务器的应用程序中,您需要更新不同服务所拥有的多个数据库。使用分布式事务通常不是一个选择,而不仅仅是因为CAP定理。许多今天高度可扩展的NoSQL数据库都不支持它们。你最终不得不使用最终的一致性方法,这对开发人员来说更具挑战性。
- 测试微服务应用程序也更复杂。服务类似的测试类将需要启动该服务及其所依赖的任何服务(或至少为这些服务配置存根)。再次,重要的是不要低估这样做的复杂性。
- Microservice架构模式的另一个主要挑战是实现跨越多个服务的更改。例如我们假设您正在实施一个需要更改服务A,B和C的故事,其中A取决于B和B取决于C,在单片应用程序中您可以简单地更改相应的模块,整合更改并一次性部署。相比之下,在Microservice架构模式中,您需要仔细规划和协调对每个服务的更改。例如,您需要更新服务C,然后更新服务B,然后再维修A,幸运的是大多数更改通常仅影响一个服务,而需要协调的多服务变更相对较少。
- 部署基于微服务的应用程序也更复杂。单一应用程序简单地部署在传统负载平衡器后面的一组相同的服务器上。每个应用程序实例都配置有基础架构服务(如数据库和消息代理)的位置(主机和端口)。相比之下,微服务应用通常由大量服务组成。例如每个服务将有多个运行时实例。更多的移动部件需要进行配置,部署,扩展和监控。此外您还需要实现服务发现机制,使服务能够发现需要与之通信的任何其他服务的位置(主机和端口)。传统的基于故障单和手动操作的方法无法扩展到这种复杂程度。因此,成功部署微服务应用程序需要开发人员更好地控制部署方法,并实现高水平的自动化。
2. SpringCloud引入
SpringCloud并不是一个框架而是一个微服务整体架构,或者说SpringCloud是一个生态圈,里面包含了很多的服务,每一个服务独立存在,相互之间互不干扰,可以直接运行。
其实SpringCloud就是一个完整的微服务架构,提供了所有功能,整个开发项目中所需要的架构功能微服务都有,也就是说整个springcloud就是一个完整的项目,这个架构已经搭建完毕了,用到了直接获取即可,只需要往架构中注入自己的业务代码就可以。
它具有微服务的以下几大优势:
- 复杂度可控
在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率
- 独立部署
具备独立的运行进程,所以每个微服务也可以独立部署。当某个服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期
- 技术选型灵活
微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,故需要对技术栈进行升级时所面临的风险就较低,甚至完全重构一个微服务也是可行的
- 容错能力
在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过 重试、平稳退化等机制实现应用层面的容错
- 扩展性
每个服务可以根据实际需求独立进行扩展
3. SpringCloud五大组件浅析
3.1 举例业务场景
如上图,假设现在开发一个电商网站,要实现支付订单功能流程如下
- 创建一个订单后,如果用户立刻支付了这个订单,我们需要将这个订单状态更新为
已支付
- 扣减相对应的商品库存
- 通知仓储中心进行发货
- 给用户这次购物添加加相对应的积分
针对上述流程我们需要有订单服务、库存服务、仓储服务、积分服务,整个流程的大体思路如下:
- 用户针对一个订单完成支付后,就会去找订单服务,更新订单状态
- 订单服务调用库存服务,完成相应的功能
- 订单服务调用仓储服务,完成相应的功能
- 订单服务调用积分服务,完成相应的功能
3.2 服务发现 - Netflix Eureka(类似zookeeper)
首先考虑一个问题,订单服务要调用库存服务、仓储服务、积分服务,如何调用呢?
订单服务根本不知道上述服务在哪台服务器上,所以没法调用,而Eureka的作用就是来告诉订单服务它想调用的服务在哪台服务器上,Eureka有客户端和服务端,每一个服务上面都有Eureka客户端,可以把本服务的相关信息注册到Eureka服务端上,那么我们的订单服务就可以就可以找到库存服务、仓储服务、积分服务了
我们上述的业务使用Eureka后如下图:
总结:
- Eurake客户端:负责将这个服务的信息注册到Eureka服务端中Eureka
- 服务端:相当于一个注册中心,里面有注册表,注册表中保存了各个服务所在的机器和端口号,可通过Eureka服务端找到各个服务
3.3 WebService客户端Feign(类似Dubbo)
通过上面的Eureka,现在订单服务确实知道库存服务、积分服务、仓储服务在哪了,但是我们如何去调用这些服务呢,如果我们自己去写很多代码调用那就太麻烦了,而SpringCloud已经为我们准备好了一个核心组件:Feign,接下来看如何通过Feign让订单服务调用库存服务,注意Feign也是用在消费者端的。
订单服务与仓库服务Service
没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,人家Feign全给你干了。
问题来了,Feign是如何做到的呢?其实Feign的一个机制就是使用了动态代理:
- 首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理
- 接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
- Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
- 最后针对这个地址,发起请求、解析响应
3.4 客服端负载均衡 - Netflix Ribbon
上面可以通过Eureka可以找到服务,然后通过Feign去调用服务,但是如果有多台机器上面都部署了库存服务,我应该使用Feign去调用哪一台上面的服务呢,这个时候就需要Ribbon了,它在服务消费者端配置和使用,作用就是负载均衡,默认使用的负载均衡算法是轮询算法,Ribbon会从Eureka服务端中获取到对应的服务注册表,然后就知道相应服务的位置,然后Ribbon根据设计的负载均衡算法去选择一台机器,Feigin就会针对这些机器构造并发送请求。
3.5 断路器 - Netflix Hystrix
在微服务架构里一个系统会有多个服务,以本文的业务场景为例:订单服务在一个业务流程里需要调用三个服务,现在假设订单服务自己最多只有100个线程可以处理请求,如果积分服务出错,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。
分析下这样会导致什么问题呢?如果系统在高并发的情况下,大量请求涌过来的时候,订单服务的100个线程会卡在积分服务这块,导致订单服务没有一个多余的线程可以处理请求,这种问题就是微服务架构中恐怖的服务器雪崩问题,这么多的服务互相调用要是不做任何保护的话,某一个服务挂掉会引起连锁反应导致别的服务挂掉。
服务也不应该挂掉啊,我们只要让存储服务和仓储服务正常工作就可以了,至于积分服务我们后期可以手动给用户加上积分,这个时候就轮到Hystrix了,Hystrix是隔离、熔断以及降级的一个框架,说白了就是Hystrix会搞很多小线程池然后让这些小线程池去请求服务,返回结果,Hystrix相当于是个中间过滤区,如果我们的积分服务挂了,那我们请求积分服务直接就返回了,不需要等待超时时间结束抛出异常,这就是所谓的熔断,但是也不能啥都不干就返回啊,不然我们之后手动加积分咋整啊,那我们每次调用积分服务就在数据库里记录一条消息,这就是所谓的降级,Hystrix隔离、熔断和降级的全流程如下:
3.6 服务网关 - Netflix Zuul (类似于服务端的Nginx)
该组件是负责网络路由的,假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service,并且部署在5台机器上,就算人家肯记住这一个,那你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?
上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。
3.7 总结Eureka:
- Eureka:服务启动的时候,服务上的Eureka客户端会把自身注册到Eureka服务端,并且可以通过Eureka服务端知道其他注册的服务。
- Ribbon:服务间发起请求的时候,服务消费者方基于Ribbon服务做到负载均衡,从服务提供者存储的多台机器中选择一台,如果一个服务只在一台机器上面,那就用不到Ribbon选择机器了,如果有多台机器,那就需要使用Ribbon选择之后再去使用。
- Feign:Feign使用的时候会集成Ribbon,Ribbon去Eureka服务端中找到服务提供者的所在的服务器信息,然后根据随机策略选择一个,拼接Url地址后发起请求。
- Hystrix:发起的请求是通过Hystrix的线程池去访问服务,不同的服务通过不同的线程池,实现了不同的服务调度隔离,如果服务出现故障,通过服务熔断,避免服务雪崩的问题 ,并且通过服务降级,保证可以手动实现服务正常功能。
- Zuul:如果前端调用后台系统,统一走zull网关进入,通过zull网关转发请求给对应的服务。
加载全部内容