2021最新Java面经整理 | 框架篇(六)SpringCloud框架
2021最新Java面经整理 | 框架篇(六)SpringCloud框架SpringCloud是对Springboot使用的分布式解决方案,适合分布式、中大型的项目架构开发,现在也逐渐成为Java服务端的主流框架。使用Spring Cloud开发的应用程序非常适合在Docker和PaaS(比如Pivotal Cloud Foundry)上部署,所以又叫做云原生应用(Cloud Native App
2021最新Java面经整理 | 框架篇(六)SpringCloud框架
SpringCloud是对Springboot使用的分布式解决方案,适合分布式、中大型的项目架构开发,现在也逐渐成为Java服务端的主流框架。使用Spring Cloud开发的应用程序非常适合在Docker和PaaS(比如Pivotal Cloud Foundry)上部署,所以又叫做云原生应用(Cloud Native Application)。云原生可以简单地理解为面向云环境的软件架构。
目录
一、SpringCloud简介
1、微服务架构的优缺点
1)、优点
- 服务拆分粒度更细,有利于资源重复利用,有利于提高开发效率
- 可以更精准的制定优化服务方案,提高系统的可维护性
- 微服务架构采用去中心化思想,服务之间采用Restful等轻量级通讯,比ESB更轻量
- 适于互联网时代,产品迭代周期更短
2)、缺点
- 微服务过多,治理成本高,不利于维护系统
- 分布式系统开发的成本高(容错,分布式事务等)对团队挑战大
2、SpringCloud全家桶
SpringCloud提供的全生态的分布式组件支持,
- 服务注册发现:Eureka(其他选择: Consul,Zookeeper,Nacos,Etcd)
- 负载均衡:Robbin(声明式调用:Fegin)
- 断路器:Hystrix
- 网关:Zuul2,Spring Cloud Gateway
- 配置中心:Spring Cloud Config
- 监控:Spring Boot Admin,Spring Boot Actuator
- 链路跟踪:Spring Cloud Sleuth
二、Eureka
Eureka是SpringCloud官方推荐的服务注册发现组件。Eureka的角色和Dubbo中Zookeeper的角色类似,体系划分如下,
- 业务上可以分为:服务注册中心、服务提供者、服务消费者。
- 代码逻辑上分为:Eureka Server 和 Eureka Client。
1、Eureka 的核心概念
Eureka 分 Eureka Server 和 Eureka Client。
Server端主要完成三个功能:
- 服务注册:服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表。
- 提供注册表:服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表。
- 同步状态:Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。
Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。
服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。
Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,此时间可配置,一般情况不建议更改。服务续约的两个重要配置,
eureka.instance.lease-renewal-interval-in-seconds=30 # 服务续约任务的调用间隔时间,默认为30秒
eureka.instance.lease-expiration-duration-in-seconds=90 # 服务失效的时间,默认为90秒
当 Eureka Client 和 Eureka Server 不再有心跳时,Eureka Server 会将该服务实例从服务注册列表中删除,即服务剔除。
Eureka Client 在程序关闭时向 Eureka Server 发送取消请求。 发送请求后,该客户端实例信息将从 Eureka Server 的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:
DiscoveryManager.getInstance().shutdownComponent()
Eureka Client 从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与 Eureka Client 的缓存信息不同,Eureka Client 自动处理。
如果由于某种原因导致注册列表信息不能及时匹配,Eureka Client 则会重新获取整个注册表信息。 Eureka Server 缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka Client 和 Eureka Server 可以使用 JSON/XML 格式进行通讯。在默认情况下 Eureka Client 使用压缩 JSON 格式来获取注册列表的信息。
获取服务是服务消费者的基础,有两个重要参数:
eureka.instance.lease-expiration-duration-in-seconds # 服务过期时间配置
eureka.instance.lease-renewal-interval-in-seconds # 服务刷新时间配置
当 Eureka Client 从注册中心获取到服务提供者信息后,就可以通过 Http 请求调用对应的服务;服务提供者有多个时,Eureka Client 客户端会通过 Ribbon 自动进行负载均衡。
2、Eureka 的自我保护机制
默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制。
Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%(可配置),如果低于 85%,Eureka Server 即会进入自我保护机制。(比如网络问题,放在大量服务被误杀)
Eureka Server 进入自我保护机制,会出现以下几种情况:
- Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)。
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中。
通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:
eureka.server.enable-self-preservation=true
Eureka Server 触发自我保护机制后,页面会出现提示:
注:如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
3、Eureka 的工作流程
Eureka的工作流程,这里参考了两个版本,主要看自己理解,
- 服务启动时会生成服务的基本信息对象InstanceInfo,然后在启动时会register到服务治理中心。
- 注册完成后会从服务治理中心拉取所有的服务信息,缓存在本地。
- 之后服务会被30s(可配置)发送一个心跳信息,续约服务。
- 如果服务治理中心在90s内没有收到一个服务的续约,就会认为服务已经挂了,会把服务注册信息删掉。
- 服务停止前,服务会主动发送一个停止请求,服务治理中心会删除这个服务的信息。
- 如果Eureka Server收到的心跳包不足正常值的85%(可配置)就会进入自我保护模式,在这种模式下,Eureka Server不会删除任何服务信息。
有两个比较重要的配置,
eureka.instance.lease-expiration-duration-in-seconds # 服务过期时间配置
eureka.instance.lease-renewal-interval-in-seconds # 服务刷新时间配置
- Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息
- Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务
- Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
- 当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例
- 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端
- 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式
- Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地
- 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存
- Eureka Client 获取到目标服务器信息,发起服务调用
- Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除
4、Eureka 集群(三节点,两两注册)
从图中可以看出 Eureka Server 集群相互之间通过 Replicate 来同步数据,相互之间不区分主节点和从节点,所有的节点都是平等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。
如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点。当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行节点间复制,将请求复制到其它 Eureka Server 当前所知的所有节点中。
图中步骤说:
- Register(服务注册):把自己的IP和端口注册给Eureka。
- Renew(服务续约):发送心跳包,每30秒发送一次。告诉Eureka自己还活着。
- Cancel(服务下线):当provider关闭时会向Eureka发送消息,把自己从服务列表中删除。
- Get Registry(获取服务注册列表):获取其他服务列表。
- Replicate(集群中数据同步):eureka集群中的数据复制与同步。
- Make Remote Call(远程调用):完成服务的远程调用。
Eureka 提供了 Region 和 Zone 两个概念来进行分区,这两个概念均来自于亚马逊的 AWS。
- region:可以理解为地理上的不同区域,比如亚洲地区,中国区或者深圳等等。没有具体大小的限制。根据项目具体的情况,可以自行合理划分 region。
- zone:可以简单理解为 region 内的具体机房,比如说 region 划分为深圳,然后深圳有两个机房,就可以在此 region 之下划分出 zone1、zone2 两个 zone。
Eureka Server 集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的(Eureka保证AP,Zookeeper强调CP)。
二、Robbin
Robbin是springcloud的LB调用组件,提供客户端的软件负载均衡。Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。使用 @LoadBalanced 注解和 RestTemplate 注入。
1、Robbin 的 LoadBalance 分类
目前主流的LB方案可分成两类,
- 集中式LB, 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方。
- 进程内LB,将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon 属于进程内LB,即客户端的软件负载均衡。
2、Robbin 的负载均衡策略
- 轮询(RoundRobin),以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
- 随机(Random),随机选择状态为UP的Server。
- 加权响应时间(WeightedResponseTime),根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
- 区域感知轮询(ZoneAvoidanceRule),复合判断server所在区域的性能和server的可用性选择server。
3、Robbin 的核心组件和工作流程
Ribbon的核心组件(均为接口类型)有以下几个,
- ServerList,用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。
- ServerListFilter,仅当使用动态ServerList时使用,用于在原始的服务列表中使用一定策略过虑掉一部分地址。
- IRule,选择一个最终的服务地址作为LB结果。选择策略有轮询、根据响应时间加权、断路器(当Hystrix可用时)等。
Ribbon在工作时首选会通过ServerList来获取所有可用的服务列表,然后通过ServerListFilter过虑掉一部分地址,最后在剩下的地址中通过IRule选择出一台服务器作为最终结果。
4、Robbin 的使用示例
使用 Robbin 做服务调用的时候,我们用 @LoadBalanced 注解和 RestTemplate 注入。
a、启动类里,可以引入 RestTemplate,并使用 @LoadBalance 注解,
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class RibbonConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
@Bean
@LoadBalanced // robbin 注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
/*
//这里也可以自己定义Robbin的负载策略
@Bean
public IRule ribbonRule(){
return new RandomRule();
}*/
}
b、使用 RestTemplate 调用
@Service
public class ConsumerService {
@Autowired
private RestTemplate restTemplate; // 注入 restTemplate
public String helloService() {
return restTemplate.getForEntity("http://service-1/sayHi", String.class).getBody();
}
}
三、Fegin
Fegin 对 Robbin 进行了封装,是一个声明式的Http端调用。Feign整合了Ribbon和Hystrix,使服务端的调用编程更容易(使用@EnableFeignClients 和 @FeignClient 注解)。Feign具有如下特性:
- 可插拔的注解支持,包括Feign注解和JAX-RS注解;
- 支持可插拔的HTTP编码器和解码器;
- 支持Hystrix和它的Fallback;
- 支持Ribbon的负载均衡;
- 支持HTTP请求和响应的压缩。
1、Fegin 解决的问题
Fegin 封装了Http调用流程,更适合面向接口化的变成习惯。在服务调用的场景中,经常调用基于Http协议的服务,使用到的框架有HttpURLConnection、OkHttp3 、Netty等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:
在springcloud中使用Robbin和RestTemplate的时候,也要走一下上面的流程,还是相对比较复杂的。而Fegin的出现,对HTTP请求调用的流程进行了封装,使服务端的调用编程更容易。
2、Fegin 的类加载流程
- 通过主类上的EnableFeignClients 注解开启FeignClient;
- 根据Feign 的规则实现接口,并加上FeignClient注解,供调用的地方注入调用;
- 程序启动后,会扫描所有FeignClient 注解的类,并将这些信息注入到IOC 容器中;
- 当b中接口被调用时,通过jdk代理,以及反射(Spring处理注解的方式),来生成具体的RequestTemplate
- RequestTemplate 生成Reqest
- Request 交给httpclient处理,这里的httpclient 可以是OkHttp,也可以是HttpUrlConnection 或者HttpClient
- 最后Client被封装到LoadBalanceClient类,这个类结合Ribbon 实现负载均衡。
3、Fegin 的运行原理
4、Fegin 使用示例
Fegin 的使用,主要注意两个注解,
- @EnableFeignClients:加在应用的启动类上。
- @FeignClient:加在定义的服务接口上,表示该接口支持Feign调用。
启动类 @EnableFeignClients 注解,
fegin接口调用,
四、Hystrix
Hystrix 是springcloud生态的断路器(隔离、限流、降级),主要是用来预防服务雪崩现象,剔除掉分布式系统中某些挂掉或请求过慢的服务节点。Hystrix 是一个帮助解决分布式系统中超时处理和容错的类库, 拥有保护系统的能力。
Hystrix的设计原则,
- 防止任何单个依赖项耗尽所有容器(如Tomcat)用户线程。
- 甩掉包袱,快速失败而不是排队。
- 在任何可行的地方提供回退,以保护用户不受失败的影响。
- 使用隔离技术(如隔离板、泳道和断路器模式)来限制任何一个依赖项的影响。
- 通过近实时的度量、监视和警报来优化发现时间。
- 通过配置的低延迟传播来优化恢复时间。
- 支持对Hystrix的大多数方面的动态属性更改,允许使用低延迟反馈循环进行实时操作修改。
- 避免在整个依赖客户端执行中出现故障,而不仅仅是在网络流量中。
1、Hystrix 的隔离策略(隔离、限流、降级)
微服务使用Hystrix熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定性,也较好的解决雪崩效应。Hystrix断路器有两种隔离策略:信号量隔离(默认)和线程池隔离。
1)信号量模式
信号量模式,从始至终都只有请求线程自身,是同步调用模式,不支持超时调用,不支持直接熔断,由于没有线程的切换,开销非常小。
信号量隔离,常用于获取共享资源的场景中,比如计算机连接了两个打印机,那么初始的信号量就是2,被某个进程或线程获取后减1,信号量为0后,一般情况下,需要获取的线程或进程进入资源等待状态。Hystrix的处理有些不同,其不等待,直接返回失败。
2)线程池模式
线程池模式,可以支持异步调用,支持超时调用,支持直接熔断,存在线程切换,开销大。
线程池隔离,采用的就是jdk的线程池,其默认选用不使用阻塞队列的线程池,例如线程池大小为10,如果某时刻10个线程均被使用,那么新的请求将不会进入等待队列,而是直接返回失败,起到限流的作用。
3)断路器机制
断路器机制,当断路器处于打开状态时,直接返回失败或进入降级流程。断路器打开和关闭的触发流程为,
- 当总的请求数达到阈值或总的请求失败百分比达到了阈值时,将断路器的状态由关闭设置为打开。
- 当断路器打开时,所有的请求均被短路,在经过指定休眠时间窗口后,让下一个请求通过(断路器被认为是半开状态)。如果请求失败,断路器进入打开状态,并进入新的休眠窗口;否则进入关闭状态。
2、Hystrix 的整体处理流程
流程如上图所示,Hystrix 框架通过命令模式来实现方法粒度上的服务保障。
- HystrixCommand 类,提供同步的
execute
和异步的queue
方法。 - HystrixObservableCommand 类提供立即执行
observe
和延迟执行toObservable
的回调方法。 - 此外,实际项目中通常不会使用Hystrix集成的本地缓存。
3、 Hystrix 如何解决服务间的依赖调用隔离
微服务分布式架构下,众多的服务节点,没有断路器的保护,挂掉或者响应慢的节点有引发服务雪崩的风险,我们来看下 Hystrix 是如何解决服务间的依赖调用的。
- Hystrix 使用命令模式 HystrixCommand 包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。
- 可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可。当调用超时时,直接返回或执行fallback逻辑。
- 为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
- 依赖调用结果分:成功,失败(抛出异常),超时,拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行 fallback(降级)逻辑。
- 提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。
- 提供近实时依赖的统计和监控。
四、Hystrix 的使用示例
1、在springcloud中的使用
Hystrix 在springcloud中的使用,我们主要关注两个注解 @EnableCircuitBreaker 和 @HystrixCommand 。
- @EnableCircuitBreaker,加在程序的启动类上,表明开启Hystrix断路器。
- @HystrixCommand,加在具体的方法上,实现断路器功能,可以加 fallbackMethod 等参数。
a、启动类上开启Hystrix断路器
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker // 开启 Hystrix 断路器
public class RibbonConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
b、方法上加@HystrixCommand注解,实现断路器功能
@Service
public class ConsumerService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "helloFallback")
public String helloService() {
return restTemplate.getForEntity("http://service-1/sayHi", String.class).getBody();
}
public String helloFallback(){
return "exception....";
}
}
五、Spring Cloud Gateway
Spring Cloud Gateway 是 springcloud 全新推出的第二代微服务网关,基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术,用来替代Zuul。Gateway 不仅提供了统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,如转发、限流、熔断监控和权限校验等。
1、构成和核心概念
Spring Cloud Gateway 依赖 Spring Boot 和 Spring WebFlux,基于 Netty 运行。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。Gateway 由一个netty server,一个netty client,Route(包含Predicate和Filter)构成。在 Gateway 中最重要的应该是Route(Netty Server和Client已经封装好了),它由RouteLocatorBuilder构建,内部包含Predicate和Filter。
1)Route(路由)
Route 是网关的基础元素,由 ID、目标 URI、断言、过滤器组成。当请求到达网关时,由 Gateway Handler Mapping 通过断言进行路由匹配(Mapping),当断言为真时,匹配到路由。
2)Predicate(断言)(匹配条件)
Predicate 是 Java 8 中提供的一个函数。输入类型是 Spring Framework ServerWebExchange。它允许开发人员匹配来自 HTTP 的请求,例如请求头或者请求参数。简单来说它就是匹配条件。
3)Filter(过滤器)
Filter 是 Gateway 中的过滤器,可以在请求发出前后进行一些业务上的处理。
2、工作原理
Spring Cloud Gateway 的工作原理跟 Zuul 的差不多,最大的区别就是 Gateway 的 Filter 只有 pre(之前) 和 post(之后)两种。如下是 Gateway 的工作原理图,
客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关 Web 处理程序,此时处理程序运行特定的请求过滤器链。(如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。)
过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后执行逻辑。所有 pre 过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行 post 过滤器逻辑。
3、使用示例
1)路由、断言、过滤器配置示例
Spring Cloud Gateway 的配置项可以查询官网,这里简单举例下,
server.port: 8082
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: path_route
uri: http://localhost:8000 # 还可以用这种Robbin的形式:lb://service-consumer
order: 0
predicates:
- Path=/foo/**
- Method=GET # 断言中可以指定方法类型,POST、GET、PUT、DELTE等
- Header=X-Request-Id, \d+ # 可以指定header匹配规则
filters:
- StripPrefix=1
- AddRequestParameter=foo, bar #可以在filter上加参数,会自动添加foo=bar
说明:上面给出了一个根据请求路径来匹配目标uri的例子,如果请求的路径为/foo/bar,则目标uri为 http://localhost:8000/bar。如果上面例子中没有加一个StripPrefix=1过滤器,则目标uri 为http://localhost:8000/foo/bar,StripPrefix过滤器是去掉一个路径。
2)Gateway 跨域
Gateway 还提供了 CROS 跨域配置,在实际项目中也是比较实用的。
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "https://docs.spring.io"
allowedMethods:
- GET
allowHeaders:
- Content-Type
上面示例中,允许来自https://docs.spring.io的get请求进行访问,并且表明服务器允许请求头中携带字段Content-Type。
3)Gateway 限流
a)Spring Cloud Gateway本身集成了限流操作,限流需要使用 Redis,需要引入 Redis 依赖。
b)然后yml中,配置 Redis 的信息,并配置 RequestRateLimiter 的限流过滤器,该过滤器需要配置三个参数:
- burstCapacity:令牌桶的总容量。
- replenishRate:令牌通每秒填充平均速率。
- key-resolver:用于限流的解析器的Bean对象的名字。它使用SpEL表达式#{@beanName}从Spring容器中获取bean对象。
spring:
cloud:
gateway:
routes:
- id: rate_limit_route
uri: lb://service-consumer
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@hostAddrKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
- StripPrefix=1
consul:
host: 127.0.0.1
port: 8500
discovery:
service-name: service-gateway
instance-id: service-gateway-233
redis:
host: localhost
port: 6379
注意:这里 filter下的name必须是RequestRateLimiter。
c)Key-resolver参数后面的bean需要自己实现,然后注入到Spring容器中。KeyResolver需要实现resolve方法,比如根据ip进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。还可以根据uri限流,同hostname限流是一样的。例如以ip限流为例,在gateway模块中添加以下实现:
public class HostAddrKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
}
把该类注入到spring容器中:
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
public HostAddrKeyResolver hostAddrKeyResolver(){
return new HostAddrKeyResolver();
}
}
基于上述配置,可以对请求基于ip的访问进行限流。
4)Gateway 重试
通过简单的配置,Spring Cloud Gateway就可以支持请求重试功能(filters --> args --> retries: 3)。Retry Gateway Filter 通过四个参数来控制重试机制,参数说明如下:
- retries:重试次数,默认值是 3 次。
- statuses:HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus。
- methods:指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法,取值参考:org.springframework.http.HttpMethod。
- series:一些列的状态码配置,取值参考:org.springframework.http.HttpStatus.Series。符合的某段状态码才会进行重试逻辑,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5个值。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://localhost:8080/test/hello
predicates:
- Path=/test/**
filters:
- name: Retry
args:
retries: 3 # 重试次数
status: 503 # 503时重试
- StripPrefix=1
使用上述配置进行测试,当后台服务不可用时,会在控制台看到请求三次的日志,证明此配置有效。
5)Gateway 熔断
网关是流量的入口,访问请求非常大,Gateway也可以利用Hystrix的熔断特性,在流量过大时进行服务降级。建议在网关微服务中加入Hystrix依赖,配置熔断,如下
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: lb://consumer-service
predicates:
- Path=/consumer/**
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
- StripPrefix=1
hystrix:
command:
fallbackcmd:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 #超时时间,若不设置超时时间则有可能无法触发熔断
上述配置中给出了熔断之后返回路径 fallbackUri: forward:/fallback,这个配置了 fallback 时要会调的路径,当调用 Hystrix 的 fallback 被调用时,请求将转发到/fallback这个 URI,并以此路径的返回值作为返回结果。
@RestController
public class GatewayController {
@RequestMapping(value = "/fallback")
public String fallback(){
return "fallback nothing";
}
}
6)自定义 GatewayFilter 和 GlobalFilter
Spring Cloud Gateway根据作用范围分为GatewayFilter和GlobalFilter,二者区别如下:
- GatewayFilter :需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。(需要在yml中配置,局部过滤)
- GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。(不需要在yml中配置,全局过滤)
在Spring Cloud Gateway自定义过滤器,过滤器需要实现 GatewayFilter 和 Ordered 这两个接口。
a)自定义GatewayFilter类型的过滤器:RequestTimeFilter,以log日志的形式记录每次请求耗费的时间。
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null) {
log.info("请求路径:"+exchange.getRequest().getURI().getRawPath() + "消耗时间: " + (System.currentTimeMillis() - startTime) + "ms");
}
})
);
}
@Override
public int getOrder() {
return 0;
}
}
- getOrder()方法,是来给过滤器定优先级的,值越大优先级越低。
- filter(...) 方法,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器。然后再chain.filter()的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。
GatewayFilter 类型的过滤器写完后,还需要再yml中配置,和普通的过滤器配置一样。
b)自定义 GlobalFilter 类型的过滤器:TokenFilter,判断所有请求中是否含参数token,如果不合法,则校验不通过;合法,则通过。
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger= LoggerFactory.getLogger( TokenFilter.class );
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.isEmpty()) {
logger.info( "token 为空,无法进行访问." );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
六、Spring Boot Admin
Spring Boot Admin 是springcloud提供的监控组件,用于管理和监控SpringBoot各个微服务。Admin通过注册中心(如Eureka)来监控各个节点的状态。可以结合 Spring Boot Actuator 使用,常用的监控数据有,
- 显示健康状况
- 显示详细信息,例如
- JVM和内存指标
- micrometer.io指标
- 数据源指标
- 缓存指标
- 查看jvm系统和环境属性
监控 server端需要 @EnableAdminServer 注解,client端只需要配置好yaml文件就好了,admin会通过注册中心获取各个服务节点的状态。
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
七、Spring Cloud Config
Spring Cloud Config 是springcloud的分布式配置中心组件。
分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。
SpringCloudConfig分为服务端(Config Server)和客户端(Config Client),服务端负责将git(svn)中存储的配置文件发布成REST接口,客户端可以从服务端REST接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置, 它需要每个客户端通过POST方法触发各自的/refresh,SpringCloudBus就通过一个轻量级消息代理连接分布式系统的节点。
Config Server用于配置属性的存储,存储的位置可以为Git仓库、SVN仓库、本地文件等,Config Client用于服务属性的读取。
spring cloud config是将配置保存在git/svn上,依赖git每次push后,触发webhook回调,最终触发spring cloud bus(消息总线),然后由消息总线通知相关的应用。
八、Nacos
nacos目前资料较少,可参看官网:https://nacos.io/zh-cn/docs/architecture.html
九、注册中心对比
分布式理论基础 CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。
1、常用的注册中心对比
Zookeeper、Eureka、Consul、Etcd、Nacos,其中Zookeeper、Consul、Etcd 保证CP;Eureka保证AP;Nacos对CP、AP都支持。和SpringCloud集成最好的是Eureka、Consul。
2、Zookeeper 保证 CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。
但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
3、Eureka 保证 AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
4、Nacos
Nacos 同时支持AP和CP模式,她根据服务注册选择临时和永久来决定走AP模式还是CP模式。一般情况下,作为集群的配置中心(类似Zookeeper),适用CP模式;作为集群的注册中心(类似Eureka),适用AP模式。
Nacos 作为后起之秀,性能非常优越。由于Eureka2.0版本后不再更新,Nacos不失为一种优秀的替代选择。
5、Eureka vs Zookeeper
Zookeeper保证的是CP,而Eureka则是AP。Eureka作为单纯的服务注册中心来说要比zookeeper更加“专业”,因为注册服务更重要的是可用性,我们可以接受短期内达不到一致性的状况。
十、其他问题
1. 什么是 spring cloud?
spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 spring boot 的开发风格做到一键启动和部署。
2. spring cloud 断路器的作用是什么?
在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
3. spring cloud 的核心组件有哪些?
-
Eureka:服务注册于发现。
-
Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
-
Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
-
Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
-
Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。
4. 微服务之间如何独立通讯的?
同步通信:dobbo通过 RPC 远程过程调用、springcloud通过 REST 接口json调用 等。
异步:消息队列,如:RabbitMq、ActiveM、Kafka 等。
5. SpringBoot和SpringCloud
SpringBoot是Spring推出用于解决传统框架配置文件冗余,装配组件繁杂的基于Maven的解决方案,旨在快速搭建单个微服务。而SpringCloud专注于解决各个微服务之间的协调与配置,服务之间的通信,熔断,负载均衡等技术维度并相同,并且SpringCloud是依赖于SpringBoot的,而SpringBoot并不是依赖与SpringCloud,甚至还可以和Dubbo进行优秀的整合开发。
总结:
-
SpringBoot专注于快速方便的开发单个个体的微服务
-
SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服务之间提供,配置管理,服务发现,断路器,路由,事件总线等集成服务
-
SpringBoot不依赖于SpringCloud,SpringCloud依赖于SpringBoot,属于依赖关系
-
SpringBoot专注于快速,方便的开发单个的微服务个体,SpringCloud关注全局的服务治理框架
6. 微服务之间是如何独立通讯的
1)远程过程调用,也就是我们常说的服务的注册与发现,直接通过远程过程调用来访问别的service。
优点:简单,常见,因为没有中间件代理,系统更简单。
缺点:只支持请求/响应的模式,不支持别的,比如通知、请求/异步响应、发布/订阅、发布/异步响应。降低了可用性,因为客户端和服务端在请求过程中必须都是可用的。
2)消息,使用异步消息来做服务间通信。服务间通过消息管道来交换消息,从而通信。
优点:把客户端和服务端解耦,更松耦合。提高可用性,因为消息中间件缓存了消息,直到消费者可以消费。支持很多通信机制比如通知、请求/异步响应、发布/订阅、发布/异步响应。
缺点:消息中间件有额外的复杂。
7. 负载均衡的意义是什么?
在计算中,负载均衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载均衡旨在优化资源使用,最大吞吐量,最小响应时间并避免任何单一资源的过载。使用多个组件进行负载均衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。
8. springcloud如何实现服务的注册?
1)服务发布时,指定对应的服务名,将服务注册到 注册中心(eureka zookeeper)。
2)注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。
9. 什么是服务熔断?什么是服务降级
在复杂的分布式系统中,微服务之间的相互调用,有可能出现各种各样的原因导致服务的阻塞,在高并发场景下,服务的阻塞意味着线程的阻塞,导致当前线程不可用,服务器的线程全部阻塞,导致服务器崩溃,由于服务之间的调用关系是同步的,会对整个微服务系统造成服务雪崩。为了解决某个微服务的调用响应时间过长或者不可用进而占用越来越多的系统资源引起雪崩效应就需要进行服务熔断和服务降级处理。
服务熔断:指的是某个服务故障或异常一起类似显示世界中的“保险丝"当某个异常条件被触发就直接熔断整个服务,而不是一直等到此服务超时。
服务熔断:就是相当于我们电闸的保险丝,一旦发生服务雪崩的,就会熔断整个服务,通过维护一个自己的线程池,当线程达到阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值。
10. 微服务的优缺点分别是什么?说下你在项目开发中碰到的坑
优点
- 每一个服务足够内聚,代码容易理解
- 开发效率提高,一个服务只做一件事
- 微服务能够被小团队单独开发
- 微服务是松耦合的,是有功能意义的服务
- 可以用不同的语言开发,面向接口编程
- 易于与第三方集成
- 微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合
- 可以灵活搭配,连接公共库/连接独立库
缺点
- 分布式系统的负责性
- 多服务运维难度,随着服务的增加,运维的压力也在增大
- 系统部署依赖
- 服务间通信成本
- 数据一致性
- 系统集成测试
- 性能监控
11. 你所知道的微服务技术栈?
- 维度(springcloud)
- 服务开发:springboot spring springmvc
- 服务配置与管理:Netfix公司的Archaiusm ,阿里的Diamond
- 服务注册与发现:Eureka,Zookeeper
- 服务调用:Rest RPC gRpc
- 服务熔断器:Hystrix
- 服务负载均衡:Ribbon Nginx
- 服务接口调用:Fegin
- 消息队列:Kafka Rabbitmq activemq
- 服务配置中心管理:SpringCloudConfig
- 服务路由(API网关)Zuul
- 事件消息总线:SpringCloud Bus
13. eureka自我保护机制是什么?
当Eureka Server 节点在短时间内丢失了过多实例的连接时(比如网络故障或频繁启动关闭客户端)节点会进入自我保护模式,保护注册信息,不再删除注册数据,故障恢复时,自动退出自我保护模式。
14. 什么是Ribbon?
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。
15. 什么是feigin?它的优点是什么?
- feign采用的是基于接口的注解
- feign整合了ribbon,具有负载均衡的能力
- 整合了Hystrix,具有熔断的能力
使用:
- 添加pom依赖。
- 启动类添加@EnableFeignClients
- 定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务
16. Ribbon和Feign的区别?
- Ribbon都是调用其他服务的,但方式不同。
- 启动类注解不同,Ribbon是@RibbonClient feign的是@EnableFeignClients
- 服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。
17. 什么是Spring Cloud Bus?
spring cloud bus 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控。
如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。使用:
- 添加依赖
- 配置rabbimq
18. 什么是Hystrix?
防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)。
服务降级:双十一 提示 哎哟喂,被挤爆了。 app秒杀 网络开小差了,请稍后再试。
优先核心服务,非核心服务不可用或弱可用。通过HystrixCommand注解指定。fallbackMethod(回退函数)中具体实现降级逻辑。
19. springcloud断路器作用?
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)。
断路器有完全打开状态:一段时间内达到一定的次数无法调用并且多次监测没有恢复的迹象。断路器完全打开,那么下次请求就不会请求到该服务。
半开:短时间内有恢复迹象,断路器会将部分请求发给该服务,正常调用时断路器关闭。
关闭:当服务一直处于正常状态,能正常调用。
20. 什么是SpringCloudConfig?
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。使用:
- 添加pom依赖
- 配置文件添加相关配置
- 启动类添加注解@EnableConfigServer
21. cloud架构?
在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理,配置服务的配置文件放在git仓库,方便开发人员随时改配置。
22. Spring Cloud 和dubbo区别?
Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。
(1)服务调用方式 dubbo是RPC springcloud Rest Api
(2)注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper
(3)服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。
23. SpringBoot和SpringCloud的区别?
SpringBoot专注于快速方便的开发单个个体微服务。SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,服务发现、断路器、路由、等集成服务。
SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系。
SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
- SpringBoot:专注于快速方便的开发单个个体微服务(关注微观);SpringCloud:关注全局的微服务协调治理框架,将SpringBoot开发的一个个单体微服务组合并管理起来(关注宏观);
- SpringBoot可以离开SpringCloud独立使用,但是SpringCloud不可以离开SpringBoot,属于依赖关系。
24. 微服务的优缺点是什么?说下你在项目中碰到的坑。
优点:松耦合,聚焦单一业务功能,无关开发语言,团队规模降低。在开发中,不需要了解多有业务,只专注于当前功能,便利集中,功能小而精。微服务一个功能受损,对其他功能影响并不是太大,可以快速定位问题。微服务只专注于当前业务逻辑代码,不会和 html、css 或其他界面进行混合。可以灵活搭配技术,独立性比较舒服。
缺点:随着服务数量增加,管理复杂,部署复杂,服务器需要增多,服务通信和调用压力增大,运维工程师压力增大,人力资源增多,系统依赖增强,数据一致性,性能监控。
25. 什么是微服务?
单个轻量级服务一般为一个单独微服务,微服务讲究的是 专注某个功能的实现,比如登录系统只专注于用户登录方面功能的实现,讲究的是职责单一,开箱即用,可以独立运行。微服务架构系统是一个分布式的系统,按照业务进行划分服务单元模块,解决单个系统的不足,满足越来越复杂的业务需求。
微服务强调的是服务大小,关注的是某一个点,具体解决某一个问题/落地对应的一个服务应用,可以看做是idea 里面一个 module。
更多推荐
所有评论(0)