带你详细了解SpringCloud微架构服务的发展史
前⾔
Spring Cloud是⼀个基于Spring Boot实现的云应⽤开发⼯具,它为基于JVM的云应⽤开发中的配置管理、服务注册,服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集状态管理等操作提供了⼀种简单的开发⽅式。
系统架构演变概述
在公司业务初创时期,⾯对的主要问题是如何将⼀个想法变成实际的软件实现,在这个时候整个软件系统的架构并没有搞得那么复杂,为了快速迭“App+后台服务”组成,⽽后台服务也只是从⼯程⾓度将应⽤进⾏Jar包的拆分。此时软件系统架构如下:
代,整个软件系统就是由“App+后台服务”
⽽此时整个软件系统的功能也⽐较简单,只有基本的⽤户、订单、⽀付等功能,并且由于业务流程没有那么复杂,这些功能基本耦合在⼀起。⽽随着App的受欢迎程度(作者所在的公司正好处于互联⽹热点),所以App下载量在2017年迅猛增长,在线注册⼈数此时也是蹭蹭往上涨。
随着流量的迅猛增长,此时整个后台服务的压⼒变得⾮常⼤,为了抗住压⼒只能不断的加机器,平⾏扩展后台服务节点。此时的部署架构如下:
通过这种⽅式,整个软件系统抗住了⼀波压⼒,然⽽系统往往还是会偶尔出点事故,特别是因为api中的某个接⼝性能问题导致整个服务不可⽤,因为这些接⼝都在⼀个JVM进程中,虽然此时部署了多个节点,但因为底层数据库、缓存系统都是⼀套,所以还是会出现⼀挂全挂的情况。
另⼀⽅⾯,随着业务的快速发展,以往相对简单的功能变得复杂起来,这些功能除了有⽤户看得见的、也会包括很多⽤户看不见的,就好像百度搜索,⽤户能看见的可能只是⼀个搜索框,但是实际上
后台对应的服务可能是成百上千,如有些增长策略相关的功能:红包、分享拉新等。还有些如⼴告推荐相关的变现功能等。
此外,流量/业务的增长也意味着团队⼈数的迅速增长,如果此时⼤家开发各⾃的业务功能还是⽤⼀套服务代码,很难想象百⼗来号⼈,在同⼀个⼯程在叠加功能会是⼀个什么样的场景。所以如何划分业务边界、合理的进⾏团队配置也是⼀件⼗分迫切的事情了!
为了解决上述问题,适应业务、团队发展,架构团队决定进⾏微服务拆分。⽽要实施微服务架构,除了需要合理的划分业务模块边界外,也需要⼀整套完整的技术解决⽅案。
在技术⽅案的选择上,服务拆分治理的框架也是有很多,早期的有如WebService,近期的则有各种Rpc框架(如Dubbo、Thirft、Grpc)
(如Dubbo、Thirft、Grpc)。⽽Spring Cloud则是基于Springboot提供的⼀整套微服务解决⽅案,因为技术栈⽐较新,并且各类组件的⽀撑也⾮常全⾯,所以Spring Cloud 就成为了⾸选。
经过⼀系列的重构+扩展,整个系统架构最终形成了以app为中⼼的⼀套微服务软件系统,结构如下:
到这⾥,整个软件系统就基于SpringCloud初步完成了微服务体系的拆分。⽀付、订单、⽤户、⼴告等核⼼功能抽离成独⽴的微服务,与此同时各⾃微服务对应的数据库也按照服务边界进⾏了拆分。
在完成服务的拆分以后,原来功能逻辑之间的代码调⽤关系,转换成了服务间⽹络的调⽤关系,⽽各
个微服务需要根据各⾃所承载的功能提供相应的服务,此时服务如何被其他服务发现并调⽤,就成了整个微服务体系中⽐较关键的部分,使⽤过Dubbo框架的同学知道,在Dubbo中服务的注
Consul来实现。另外在基于SpringCloud的架构体系中,提供了配置中册&发现是依赖于Zookeeper实现的,⽽在SpringCloud中我们是通过Consul
⼼(ConfigServer)
⽹关服务了。(ConfigServer)来帮助各个微服务管理配置⽂件,⽽原本的api服务,随着各个功能的抽离,逐步演变成前置⽹关服务
Consul、ConfigServer、⽹关服务这⼏个关键组聊到这⾥,基于SpringCloud我们进⾏了微服务拆分,⽽在这个体系结构中,分别提到了Consul、ConfigServer、⽹关服务
件,那么这⼏个关键组件具体是如何⽀撑这个庞⼤的服务体系的呢?
SpringCloud关键组件
Consul
Consul是⼀个开源的,使⽤go语⾔开发的注册中⼼服务。它⾥⾯内置了服务发现与注册框架、分布⼀致性协议实现、健康检查、Key/Value存
Eurke作为注册中⼼,这⾥之所以选择Consul主要原因在于Consul对异构的服储、多数据中⼼等多个⽅案。在SpringCloud框架中还可以选择Eurke
务的⽀持,如:grpc服务。
事实上,在后续的系统架构演进中,在某些服务模块进⼀步向⼦系统化拆分的过程中,就采⽤了grpc作为⼦系统服务间的调⽤⽅式。例如,⽀付模块的继续扩张,对⽀付服务本⾝⼜进⾏了微服务架构的拆分,此时⽀付微服务内部就采⽤了grpc的⽅式进⾏调⽤,⽽服务注册与发现本⾝则还是依赖于同⼀套Consul集。
此时的系统架构演进如下:
原有微服务架构中的模块服务在规模达到⼀定程度或复杂性达到⼀定程度后,都会朝着独⽴的体系发展,从⽽将整个微服务的调⽤链路变的⾮常长,⽽从Consul的⾓度看,所有的服务⼜都是扁平的。
随着微服务规模的越来越⼤,Consul作为整个体系的核⼼服务组件,在整个体系中处于关键的位置,
⼀旦Consul挂掉,所有的服务都将停⽌服务。那么Consul到底是什么样服务?其容灾机制⼜该如何设计呢?
要保证Consul服务的⾼可⽤,在⽣产环境Consul应该是⼀个集(关于Consul集的安装与配置可以参考⽹络资料),这是毫⽆疑问的。⽽在Server、Client,这两种⾓⾊与Consul集上运⾏的应⽤服务并没有什么关系,只是基于Consul层⾯的⼀种Consul集中,存在两种⾓⾊:Server、Client
⾓⾊划分。实际上,维持整个Consul集状态信息的还是Server节点,与Dubbo中使⽤Zookeeper实现注册中⼼⼀样,Consul集中的各个
使⽤GOSSIP协议、Raft⼀致性算法,这⾥不做详细展开,在后⾯的⽂章中可以和⼤家单独讨论)来Server节点也需要通过选举的⽅式(使⽤GOSSIP协议、Raft⼀致性算法,这⾥不做详细展开,在后⾯的⽂章中可以和⼤家单独讨论Leader节点来负责处理所有查询和事务,并向其他节点同步状态信息。
选举整个集中的Leader节点
Client⾓⾊则是相对⽆状态的,只是简单的代理转发RPC请求到Server节点,之所以存在Client节点主要是分担Server节点的压⼒,作⼀层⽽Client⾓⾊则是相对⽆状态的
缓冲⽽已,这主要是因为Server节点的数量不宜过多,因为Server节点越多也就意味着达成共识的过
程越慢,节点间同步的代价也就越⾼。对于Server节点,⼀般建议3-5台,⽽Client节点则没有数量的限制,可以根据实际情况部署数千或数万台。事实上,这也只是⼀种策略,在现实的⽣产环境中,⼤部分应⽤只需要设置3~5台Server节点就够了,作者所在的公司⼀套⽣产集中的Consul集的节点配置就是5个Server节点,并没有额外再设置Client节点。
Agent,事实上每个Server或Client都是⼀个consul agent,它是运⾏在Consul集中每个成员上的⼀另外,在Consul集中还有⼀个概念是Agent
个守护进程,主要的作⽤是运⾏DNS或HTTP接⼝,并负责运⾏时检查和保持服务信息同步。我们在启动Consul集的节点(Server或Client)时,都是通过consul agent的⽅式启动的。例如:
consul agent -server -bootstrap -syslog \ -ui \-data-dir=/opt/consul/data \-dns-port=53-recursor=10.211.55.3-config-
dir=/opt/consul/conf\-pid-file=/opt/consul/run/consul.pid \-client=10.211.55.4\-bind=10.211.55.4\-node=consul-server01 \-disable-host-node-id &
以实际的⽣产环境为例,Consul集的部署结构⽰意图如下:
实际⽣产案例中并没有设置Client节点,⽽是通过5个Consul Server节点组成的集,来服务整套⽣产集的应⽤注册&发现。这⾥有细节需要了解下,实际上5个Consul Server节点的IP地址是不⼀样的,具体的服务在连接Consul集进⾏服务注册与查询时应该连接Leader节点的IP,⽽问题是,如果Leader节点挂掉了,相应的应⽤服务节点,此时如何连接通过Raft选举产⽣的新Leader节点呢?难道⼿⼯切换IP不成?
显然⼿⼯切换IP的⽅式并不靠谱,⽽在⽣产实践中,Consul集的各个节点实际上是在Consul Agent上运⾏DNS(如启动参数中红⾊字体部分),应⽤服务在连接Consul集时的IP地址为DNS的IP,DNS会将地址解析映射到Leader节点对应的IP上,如果Leader节点挂掉,选举产⽣的新Leader节点会将⾃⼰的IP通知DNS服务,DNS更新映射关系,这⼀过程对各应⽤服务则是透明的。
通过以上分析,Consul是通过集设计、Raft选举算法,Gossip协议等机制来确保Consul服务的稳定与⾼可⽤的。如果需要更⾼的容灾级别,也可以通过设计双数据中⼼的⽅式,来异地搭建两个Consul数据中⼼,组成⼀个异地灾备Consul服务集,只是这样成本会更⾼,这就看具体是否真的需要了。
ConfigServer(配置中⼼)
配置中⼼是对微服务应⽤配置进⾏管理的服务,例如数据库的配置、某些外部接⼝地址的配置等等。在SpringCloud中ConfigServer是独⽴的服务组件,它与Consul⼀样也是整个微服务体系中⽐较关键的⼀个组件,所有的微服务应⽤都需要通过调⽤其服务,从⽽获取应⽤所需的配置信息。
随着微服务应⽤规模的扩⼤,整个ConfigServer节点的访问压⼒也会逐步增加,与此同时,各个微服务的各类配置也会越来越多,如何管理好这些配置⽂件以及它们的更新策略(确保不因⽣产配置随意改动⽽造成线上故障风险),以及搭建⾼可⽤的ConfigServer集,也是确保微服务体系稳定很重要的⼀个⽅⾯。
在⽣产实践中,因为像Consul、ConfigServer这样的关键组件,需要搭建独⽴的集,并且部署在物理机⽽不是容器⾥。在上⼀节介绍Consul 的时候,我们是独⽴搭建了5个Consul Server节点。⽽ConfigServer因为主要是http配置⽂件访问服务,不涉及节点选举、⼀致性同步这样的操作,所以还是按照传统的⽅式搭建⾼可⽤配置中⼼。具体结构⽰意图如下:
我们可以单独通过git来管理应⽤配置⽂件,正常来说由ConfigSeever直接通过⽹络拉取git仓库的配置供服务获取就可以了,这样只要git仓库配置更新,配置中⼼就能⽴刻感知到。但是这样做的不稳定之处,就在于git本⾝是内⽹开发⽤的代码管理⼯具,如果让线上实时服务直接读取,很容易将git仓库拉挂了,所以,我们在实际的运维过程中,是通过git进⾏配置⽂件的版本控制,区分线上分⽀/master与功能开发分⽀/feature,并且在完成mr后还需要⼿⼯(通过发布平台触发)同步⼀遍配置,过程是将新的master分⽀的配置同步⼀份到各个configserver节点所在主机的本地路径,这样configserver服务节点就可以通过其本地⽬录获取配置⽂件,⽽不⽤多次调⽤⽹络获取配置⽂件了。
⽽另⼀⽅⾯,随着微服务越来越多,git仓库中的配置⽂件数量也会越来越多。为了便于配置的管理,我们需要按照⼀定的组织⽅式来组织不同应⽤类型的配置。在早期所有的应⽤因为没有分类,所以导致上百个微服务的配置⽂件放在⼀个仓库⽬录,这样⼀来导致配置⽂件管理成本增加,另外⼀⽅⾯也会影响ConfigServer的性能,因为某个微服务⽤不到的配置也会被ConfigServer加载。
所以后期的实践是,按照配置的层次关系进⾏组织,将公司全局的项⽬配置抽象到顶层,由ConfigServer默认加载,⽽其他所有的微服务则按照应⽤类型进⾏分组(通过git项⽬空间的⽅式分组)
,相同的应⽤放在⼀个组,然后这个组下单独设⽴⼀个名为config的git仓库来存放这个组下相关微服务的配置⽂件。层次结构如下:
⽽对于ConfigServer服务本⾝来说,需要按照这样的组织⽅式进⾏配置类型匹配,例如上述的例⼦中,假设还存在finance的配置仓库,⽽pay组下的服务访问配置中⼼的时候,是不需要finance空间下的配置⽂件的,所以ConfigServer可以不⽤加载。这⾥就需要在ConfigServer服务配置中进⾏⼀些配置。具体如下:
spring:application:name:@project.artifactId@version:@project.version@build:@buildNumber@branch:@scmBranch@cloud:inetutil s:ignoredInterfaces: - docker0config:server: abled: falsegit:uri: /opt/repos/configsearchPaths:'common,
{application}'cloneOnStart: truerepos:pay:pattern: pay-*cloneOnStart: trueuri: /opt/repos/example/configsearchPaths:'common, {application}'finance:pattern: finance-*cloneOnStart: trueuri: /opt/repos/finance/configsearchPaths:'common,{application}'
通过在ConfigServer服务本⾝的l本地配置中,设置其配置搜索⽅式,来实现这样的⽬的。
⽹关服务&服务熔断&监控
通过上⾯两⼩节的内容,我们相对详细地介绍了基于SpringCloud体系中⽐较关键的两个服务组件。然⽽在微服务架构体系中,还有很多关键的问题需要解决,例如,应⽤服务在Consul中部署了多个节点,那么调⽤⽅如何实现负载均衡?
关于这个问题,在传统的架构⽅案中是通过Nginx实现的,但是在前⾯介绍Consul的时候只提到了Consul的服务注册&发现、选举等机制,并没有提到Consul如何在实现服务调⽤的负载均衡。难道基于SpringCloud的微服务体系中的应⽤服务都是单节点在提供服务,哪怕即使部署了多个
@FeignClient("user")注解进⾏服务调⽤时,就已经服务节点?事实上,我们在服务消费⽅通过@EnableFeignClients
@EnableFeignClients注解开启调⽤,通过@FeignClient
springcloud难学吗Robbin代理的,⽽Robbin是实现客户端负载均衡的⼀个组件,通过从Consul拉实现了负载均衡,为什么呢?因为,这个注解默认是会默认开启Robbin
这⼀切都是在消费端的进程内部通过代码的取服务节点信息,从⽽以轮询的⽅式转发客户端调⽤请求⾄不同的服务端节点来实现负载均衡。⽽这⼀切都是在消费端的进程内部通过代码的
Service Mesh(服务⽹
⽅式实现的。这种负载⽅式寄宿于消费端应⽤服务上,对消费端存在⼀定的代码侵⼊性,这是为什么后⾯会出现Service Mesh(服务⽹
⽅式实现的
格)概念的原因之⼀,这⾥就不展开了,后⾯有机会再和⼤家交流。
格)
Hystrix框架来提供这种机制的⽀持,与负载均衡服务熔断、限流等机制的实现,SpringCloud通过集成Netflix的Hystrix
另⼀需要解决的关键问题是服务熔断、限流等机制的实现
机制⼀样也是在消费端实现。由于篇幅的关系,这⾥也不展开了,在后⾯的⽂章中有机会再和⼤家交流。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论