一、什么是SpringCloud
1、官方定义
1)官方定义:springcloud为开发人员提供了在分布式系统中快速构建一些通用模式的工具(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线)。分布式系统的协调导致了锅炉板模式,使用springcloud开发人员可以快速地建立实现这些模式的服务和应用程序。
2)springcloud是一个含概多个子项目的开发工具集,集合了众多的开源框架,他利用了spring boot开发的便利性实现了很多功能,如服务注册,服务注册发现,负载均衡等,springcloud在整合过程中主要是针对Netflix开源组件的封装,springcloud的出现真正的简化了分布式架构的开发。netflix是美国的一个在线视频网站,微服务业的翘楚,他是公认的大规模生产级微服务的杰出实践者,netflix的开源组件已经在他大规模分布式微服务环境中经过多年的生产实战验证,因此springcloud中很多组件都是基于netflix组件的封装。
2、核心架构及其组件
1)核心组件说明
eureka/consul/nacos(alibaba):服务注册中心组件 rabbion 、 openfeign:服务负载均衡和服务调用组件 hystrix 、 hystrix dashboard:服务断路器和服务监控组件 zuul/gateway:服务网关组件 config:统一配置中心组件 bus:消息总线组件 |
3、环境搭建
1)版本命名
springcloud是一个由众多独立子项目组成的大型综合项目,原则每个子项目有不同的发布节奏,都维护自己发布版本号。为了更好的管理springcloud的版本,通过一个资源清单BOM(bill of materials),为了避免与子项目的发布好混淆,所以没有采用版本号的方式,而是通过命名的方式。这些名字是按字母顺序排列的。当单个项目的点发布累积到一个临界量,或者其中一个项目中有一个关键缺陷需要每个人都可以使用时,发布序列将推出名称以“.SRX”结尾的“服务发布”,其中“x”是一个数字。
2)版本选择(springboot ,springcloud)
3)环境搭建(所有springcloud项目都要准备以下内容)
(1)非自创父项目
新建一个空项目,然后按下述方式建模块。(如下左图结构)
- 说明
springboot 2.2.x.RELEASE、springcloud Hoxton SR6、java8+、maven3.3.6+、idea2020+
- 创建springboot模块
指定版本为2.2.5版本
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
- 引入springcloud的版本管理
<properties> <!--springcloud具体版本号--> <spring-cloud.version>Hoxton.SR6</spring-cloud.version> </properties> <!--全局管理springboot版本,并不会引入具体依赖--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
(2)自创父项目
将上述的依赖整合到一个父项目中,然后后续以此为基础创建子模块,并引入该父项目。(如下右图结构)
- 使用方式
将父项目的(groupId、artifactId)放入子项目的<parent/>中,同时在父项目中添加<modules>
父:
<modules> <module> test3 </module> </modules>
子:
<parent> <groupId>com.icucoder</groupId> <artifactId>parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
二、服务注册中心
1、基本内容
1)概念
所谓的服务注册中心就是在整个微服务架构中单独提出一个服务,整个服务不完成系统的任何业务功能,仅仅用来对整个微服务系统的服务注册和服务发现,以及对服务健康状态的监控和管理功能。
2)功能
可以对所有的微服务的信息进行存储,如微服务的名称、ip和端口等
可以在进行服务调用时通过服务发现查询可用的微服务列表及网络地址进行服务调用
可以对所有的微服务进行心跳检测,如发现某实例长时间无法访问,就会从服务注册表移除该实例。
3)常用的注册中心
Eureka(Netflix)、Consul、Zookeeper、以及阿里巴巴的Nacos组件。这些注册中心在本质上都是用来管理服务的注册和发现以及服务状态的检查的。
2、使用方法
(1)简介
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务。springcloud将它集成在其子项目spring-cloud-netflix中,以实现springcloud的服务注册和发现功能。
Eureka包含两个组件:Eureka Server和Eureka Client。
(2)开发Eureka Server
- 创建项目并引入eureka server依赖
<!--一引入Eureka Server依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
- 编写配置application.properties
#指定服务端口 server.port=9000 #指定服务名称 唯一标识 spring.application.name=eurekaserver #指定服务注册中心的地址 eureka.client.service-url.defaultZone=http://localhost:9000/eureka
- 开启Eureka Server入口类加入注解
@SpringBootApplication @EnableEurekaServer public class Eurekaserver9000Application { public static void main(String[] args) { SpringApplication.run(Eurekaserver9000Application.class, args); } }
- 访问: http://localhost:9000/
- 说明:
出现上述问题的原因:Eureka组件包含Eureka server和Eureka client。server是一个服务注册中心,用来接收客户端的注册。client的特性会让当前启动的服务把自己作为Eureka的客户端进行服务中心的注册,当项目启动时服务注册中心还没有创建好,所以找不到服务的客户端组件就直接报错了,当启动成功服务注册中心创建好了,日后client也能进行 注册,就不会报错了。
关闭自己注册自己:
#不再将自己同时作为客户端进行注册(用在eureka server上) eureka.client.register-with-eureka=false #关闭作为客户端时从Eureka server获取服务信息 eureka.client.fetch-registry=false
(3)开发Eureka Client
- 创建项目并引入eureka client依赖
<!--一引入Eureka Client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 编写配置application.properties
#指定服务端口 server.port=8000 #指定服务名称 唯一标识 spring.application.name=eurekaclient #指定服务注册中心的地址 eureka.client.service-url.defaultZone=http://localhost:9000/eureka
- 开启Eureka client入口类加入注解
@SpringBootApplication @EnableEurekaClient public class Eurekaclient8000Application { public static void main(String[] args) { SpringApplication.run(Eurekaclient8000Application.class, args); } }
(4)eureka自我保护机制
- 默认情况下,如果eureka server在一定时间内(默认90s)没有接收到某个微服务实例的心跳,eureka server将会移除该实例。自我保护机制是:eureka server在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,eureka sever会将这些实例保护起来,让这些实例永不过期。
- 在eureka server端关闭自我保护机制
#关闭自我保护 eureka.server.enable-self-preservation=false #超时3s自动清除 eureka.server.eviction-interval-timer-in-ms=3000
- 微服务修改减短服务心跳时间
#用来修改eureka sever默认接受心跳的最大时间,默认是90s eureka.instance.lease-expiration-duration-in-seconds=10 #指定客户端多久向eureka server发送一次心跳,默认是30s eureka.instance.lease-renewal-interval-in-seconds=5
2)consul
(1)简介
consul是一个可以提供服务发现、健康检查、多数据中心、key/value存储等功能的分布式服务框架,用于实现分布式系统的服务发现与配置。与其他分布式服务注册方案相比,使用起来也较为简单。consul用golang实现,因此具有天然可移植性(支持linux、windows和mac os x)。安装包仅包含一个可执行文件,方便部署。
下载consul,解压后执行命令:
- linux:执行consul agent -dev -client 0.0.0.0 -ui 启动consul服务器
- windows:执行consul.exe agent -dev 启动consul服务器
然后访问:http://localhost:8500
(3)开发Consul Client
- 创建项目并引入consul依赖
<!--引入consul依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
- 编写application.properties配置
server.port=8100 spring.application.name=consulclient #注册consul服务的主机 spring.cloud.consul.host=192.168.227.134 #注册consul服务的端口号 spring.cloud.consul.port=8500 #关闭consul的服务健康检查(不推荐)这里开启 spring.cloud.consul.discovery.register-health-check=true #指定注册的服务名称 默认就是应用名 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 启动服务查看consul界面服务信息
(4)consul开启健康监控检查
默认情况下consul监控健康是开启的,但是必须依赖健康监控依赖才能正确监控健康状态,所以直接开启会显示错误,引入健康监控依赖之后服务正常。
<!--consul监控健康依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
3)zookeeper
todo
4)nacos
todo
3、不同注册中心区别
1)CAP定理
CAP定理又称为CAP原则,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
(1)一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
(2)可用性:在集群中一部分接地那故障后,集群整体是否还能响应客户端的读写要求。(对数据更新具备高可用性)
(3)分区容错性:就是高可用性,一个节点崩了,并不影响其他的节点(100个节点,挂了几个,不影响服务,越多机器越好)
2)Eureka特点
Eureka中没有使用任何的数据一致性算法保障不同集群间的server的数据一致,仅通过数据拷贝的方式争取注册中心数据的最终一致性,虽然放弃数据一致性但是换来了server的可用性,降低了注册的代价,提高了集群运行的健壮性。
3)Consul特点
基于Raft算法,Consul提供强一致性的注册中心服务,但是由于Leader节点承担了所有的处理工作,势必加大了注册和发现的代价,降低了服务的可用性,通过Gossip协议,Consul可以很好地监控Consul集群的运行,同时可以方便通知各类事件,如Leader选择发生、server地址变更等。
4)zookeeper特点
基于Zab协议,Zookeeper可以用于构建具备数据强一致性的服务注册于发现中心,而与此相对地牺牲了服务的可用性和提高了注册需要的时间。
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 | Spring Cloud集成 |
Eureka | java | ap | 无 | 可配支持 | HTTP | 已集成 |
Consul | go | cp | Raft | 支持 | HTTP/DNS | 已集成 |
Zookeeper | java | cp | Paxos | 支持 | 客户端 | 已集成 |
三、服务间通信方式
- 一个服务开启多个端口:
在springcloud中服务间调用主要是使用http restful方式进行服务调用。
1、基于RestTemplate的服务调用
1)说明
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
2)示例
- 商品服务提供一个接口:http://localhost:8200/product/showMsg
@RestController public class ProductController { @Value("${server.port}") private int port; @GetMapping("/product/showMsg") public String ShowMsg() { return "进入商品服务,展示商品~~~,当前服务的端口:" + port; } }
- 用户服务调用这个接口:http://localhost:8200/user/showProductMsg1
@RestController public class UserController { @GetMapping("/user/showProductMsg1") public String showProductMsg1() { RestTemplate restTemplate = new RestTemplate(); String msg = restTemplate.getForObject("http://localhost:8200/product/showMsg", String.class); return msg; } }
3)分析
rest template是直接基于服务地址调用没有在服务注册中心获取服务,也没有办法完成服务的负载均衡,如果需要实现服务的负载均衡需要自己书写服务负载均衡策略。
2、基于Ribbon的服务调用
1)说明
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。
2)示例
(1)项目中引入依赖
如果使用的是eureka client和consul client,无须引入依赖,因为在eureka和consul中默认集成了ribbon组件。如果使用的client没有ribbon依赖,则需要引入下述依赖:
<!--引入ribbon依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
(2)使用restTemplate+ribbon进行服务调用有三种方式(DiscoveryClient、LoadBalancerClient、@LoadBalanced):
- 使用DiscoveryClient进行客户端调用
@RestController public class UserController { @Autowired private DiscoveryClient discoveryClient; /*** * ribbon:DiscoveryClient 没有提供负载均衡 * @return */ @GetMapping("/user/findProductAll") public List<ServiceInstance> findProductAll() { List<ServiceInstance> products = discoveryClient.getInstances("products"); //自己写负载均衡,通过获取的ip:port,然后使用RestTemplate return products; } }
- 使用LoadBalancerClient进行客户端调用
@RestController public class UserController { @Autowired private LoadBalancerClient loadBalancerClient; /*** * ribbon:LoadBalancerClient 负载均衡:默认轮询 * @return */ @GetMapping("/user/findProductAll") public ServiceInstance findProductAll() { ServiceInstance products = loadBalancerClient.choose("products"); //通过获取的ip:port,使用RestTemplate return products; } }
- 使用@LoadBalanced进行客户端调用
新建一个配置类GetRestTemplateConfig ,并使用@LoadBalanced注解:
@Configuration public class GetRestTemplateConfig { //在工厂中创建一个RestTemplate对象 @Bean @LoadBalanced//代表具有ribbon负载均衡的RestTemplate对象 public RestTemplate getRestTemplate() { return new RestTemplate(); } }
在controller中使用:
@RestController public class UserController { @Autowired private RestTemplate restTemplate; /*** * ribbon:@LoadBalanced 直接使用服务名:products * @return */ @GetMapping("/user/findProductAll") public String findProductAll() { String forObject = restTemplate.getForObject("http://products/product/findAll", String.class); return forObject; } }
3)ribbon的负载均衡策略说明
(1)ribbon负载均衡算法
- RoundRobinRule:轮询测试,按顺序循环选择
- RandomRule:随机策略,随机选择
- AvailabilityFilteringRule:可用过滤策略,会先过滤由于多次访问故障而处于断路跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。
- WeightedResponseTimeRule:响应时间加权策略,根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大,被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到
- RetryRule:重试策略,先按照RoundRobinRule的策略获取服务,如果获取失败则在指定时间内重试,获取可用的服务。
- BestAviableRule:最低并发策略,会先过滤由于多次访问故障而处于断路跳闸状态的服务,然后选择一个并发量最小的服务。
(2)修改默认的负载均衡策略
3、OpenFeign组件的使用
1)使用RestTemplate+ribbon已经可以完成服务间的调用,为什么还要使用feign?
问题:
每次调用服务的代码存在大量的代码冗余;服务地址如果修改,维护成本增高;使用时不灵活。
2)OpenFeign组件
Feign是一个声明式伪http客户端,它使得写http客户端变的更简单。使用feign,只需要创建一个接口并注解。它具有可拔插的注解特性(可以使用springmvc的注解),可使用feign注解和jax-rs注解。feign支持可拔插的编码器和解码器。feign默认集成了ribbon,默认实现了负载均衡的效果并且springcloud未feign添加了springmvc注解的支持。
(1)示例
- 服务调用方法 引入OpenFeign依赖
<!--引入openfeign依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 入口类加入注解开启OpenFeign支持
@SpringBootApplication @EnableFeignClients//开启支持openfeign组件方式调用 public class UsersApplication { public static void main(String[] args) { SpringApplication.run(UsersApplication.class, args); } }
- 创建一个接口
//调用商品服务的openfeign组件 @FeignClient(value = "products")//标识当前接口是一个feign组件 value=调用服务的id public interface ProductsClient { @GetMapping("/product/showMsg") String ShowMsg(); @GetMapping("/product/findAll") Map<String, Object> findAll(); }
- controller中使用
@RestController public class UserController { @Autowired private ProductsClient productsClient; /*** * openfein: * @return */ @GetMapping("/user/findProductAll") public Map<String, Object> findProductAll() { return productsClient.findAll(); } }
访问:http://127.0.0.1:8100/user/findProductAll
(2)参数传递(服务和服务之间通信,不仅仅是调用,往往在调用过程中还伴随着参数传递。)
- 单个变量:使用openfeign的get、post方式传递参数,接口类中的参数变量必须通过@RequestParam注解进行修饰;
//1、ProductController @RestController public class ProductController { @GetMapping("/product/findOne/{productId}")//必须加@PathVariable("productId") public Map<String, Object> findOne(@PathVariable("productId") String productId) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "根据商品id查询商品成功"); map.put("productId", productId); return map; } @PostMapping("/product/save")//可以加@RequestParam也可以不加 public Map<String, Object> save(@RequestParam("productName") String productName) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "保存商品成功"); map.put("productName", productName); return map; } } //2、ProductsClient //调用商品服务的openfeign组件 @FeignClient(value = "products")//标识当前接口是一个feign组件 value=调用服务的id public interface ProductsClient { @GetMapping("/product/findOne/{productId}") Map<String, Object> findOne(@PathVariable("productId") String productId); @PostMapping("/product/save") Map<String, Object> save(@RequestParam("productName") String productName); } //3、UserController @RestController public class UserController { @Autowired private ProductsClient productsClient; @GetMapping("/user/findProductOne") public Map<String, Object> findProductOne(String productId) { return productsClient.findOne(productId); } @GetMapping("/user/saveProduct") public Map<String, Object> saveProduct(String productName) { return productsClient.save(productName); } }
访问:http://127.0.0.1:8100/user/findProductOne?productId=1001或http://127.0.0.1:8100/user/saveProduct?productName=orange
- 对象:使用openfeign方式传递对象参数,接口类中的参数变量和被调用的controller中的参数变量必须通过@RequestBody注解进行修饰;
//1、实体类 @Data public class Product { private String id; private String name; private Date upate; } //2、ProductController @RestController public class ProductController { @PostMapping("/product/update")//必须加@RequestBody,作用:将json格式字符串转为对应对象信息 public Map<String, Object> update(@RequestBody Product product) { Map<String, Object> map = new HashMap<>(); map.put("status", true); map.put("msg", "保存商品成功"); map.put("product", product); return map; } } //3、ProductsClient //调用商品服务的openfeign组件 @FeignClient(value = "products")//标识当前接口是一个feign组件 value=调用服务的id public interface ProductsClient { @PostMapping("/product/update") Map<String, Object> update(@RequestBody Product product); } //4、UserController @RestController public class UserController { @Autowired private ProductsClient productsClient; @GetMapping("/user/updateProduct") public Map<String, Object> updateProduct(Product product) { return productsClient.update(product); } }
访问:http://127.0.0.1:8100/user/updateProduct?id=1001&name=zhansan&upate=2021/04/21
(3)超时设置
默认情况下,openfeign在进行服务调用时,要求服务提供方处理业务逻辑时间必须在1S内,如果超过1S没有返回则openfeign会直接报错,不会等待服务执行,但是往往在处理复杂业务逻辑是会超过1S,因此需要修改openfeign的默认服务调用超时时间。
#配置指定服务 feign.client.config.PRODUCTS.connectTimeout=5000 feign.client.config.PRODUCTS.readTimeout=5000 #配置所有服务 feign.client.config.default.connectTimeout=5000 feign.client.config.default.readTimeout=5000
(4)日志配置
往往在服务调用时我们需要详细的展示feign的日志,默认feign在调用时并不是最详细日志输出,因此在调试程序时应该开启feign的详细日志展示,feign对日志的处理非常灵活,可为每个feign客户端指定日志记录策略,每个客户端都会创建一个logger,默认情况下logger的名称是feign的全限定名,需要注意的是,feign日志的打印只会DEBUG级别做出响应。
我们可以为feign客户端配置各自的logger.level对象,告诉feign记录哪些日志。logger.lever有以下几种值:
- NONE:不记录任何日志
- BASIC:仅仅记录请求方法,url,响应状态代码及执行时间
- HEADERS:记录Basic级别的基础上,记录请求和响应的header
- FULL:记录请求和响应的header,body和元数据
#开启指定服务日志展示 feign.client.config.PRODUCTS.loggerLevel=full #全局开启服务日志展示 feign.client.config.default.loggerLevel=full #指定feign调用客户端对象所在包,必须是debug级别 logging.level.com.icucoder.feignclients=debug
四、Hystrix组件
在分布式环境中,许多服务依赖项不可避免地会失败。Hystrix是一个库,它通过添加延迟容忍和容错逻辑来帮助控制这些分布式服务直接的交互。Hystrix通过隔离服务之间的访问点、停止它们之间的级联故障以及提供后备选项来实现这一点,所有这些都可以提高系统的整体弹性。
1、服务雪崩、服务熔断、服务降级
1)服务雪崩
在微服务直接进行服务调用时由于一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费放不可用并将不可用逐渐放大的过程。
2)服务熔断
“熔断器”本身是一种开关装置,当某个服务单元发生故障之后,通过熔断器的故障监控,某个异常条件被触发,直接熔断整个服务(对调用链路的保护)。向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,就保证了服务调用方的线程不会被长时间占用,避免在分布式系统中蔓延,乃至雪崩。如果目标服务情况好转则恢复调用。服务熔断是解决服务雪崩的重要手段。
3)服务降级
服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此缓解服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的响应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。
4)降级和熔断总结
(1)共同点
目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段。
(2)不同点
触发原因不一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是整体负荷考虑;
管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始);
(3)总结
熔断必会触发降级,所以熔断也是降级的一种,区别在于熔断是对调用链路的保护,而降级是对系统过敏的一种保护处理。
2、实现服务熔断
1)步骤
(1)引入hystrix依赖(如果引入了openfeign依赖,则不需要再引入hystrix依赖了)
<!--引入hystrix:熔断器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
(2)在Application上添加@EnableCircuitBreaker注解,开启断路器
@SpringBootApplication @EnableCircuitBreaker//开启断路器 public class ProductsApplication { public static void main(String[] args) { SpringApplication.run(ProductsApplication.class, args); } }
(3)编写ProductController(在要熔断的方法上添加@HystrixCommand),并回调fallbackMethod
@RestController public class ProductController { @GetMapping("/product/testBreak") @HystrixCommand(fallbackMethod = "testFallBack") public String testBreak(Integer id) { if (id < 0) { throw new RuntimeException("非常参数,id不能小于0。"); } return "访问成功,当前查询id为:" + id; } //触发熔断的fallback方法 public String testFallBack(Integer id) { return "当前传入的参数id:" + id + "不是有效参数,触发熔断"; } }
(4)测试:http://127.0.0.1:8200/product/testBreak?id=-1
2)断路器打开条件
- 如果触发一定条件断路器会自动打开,过了一段时间之后,正常之后又会关闭。
(1)当满足一定的阈值的时候(默认10秒内超过20个请求次数);
(2)当失败率达到一定的时候(默认10秒内超过50%的请求失败);
(3)到达以上阈值,断路器会开启;
(4)当开启的时候,所有请求都不会进行转发;
(5)一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启,重复(4)、(5)。
3)默认的服务FallBack处理方法
如果为每个服务方法都开发一个fallback方法,对于我们来说,可能会出现大量的代码冗余,不利于维护,这个时候就需要加入默认fallback方法;
@RestController public class ProductController { @GetMapping("/product/testBreak") @HystrixCommand(defaultFallback = "testDefaultFallBack") public String testBreak(Integer id) { if (id < 0) { throw new RuntimeException("非常参数,id不能小于0。"); } return "访问成功,当前查询id为:" + id; } //默认触发熔断的fallback方法 public String testDefaultFallBack() { return "触发熔断"; } }
3、实现服务降级
客户端openfein+hystrix实现服务降级,调用者users,被调用者product,当满足降级条件,如product服务不可用时,则降级。当满足product熔断条件时,则熔断。
1)步骤
(1)引入hystrix依赖
(2)开启openfeign支持服务降级
#开启openfeign支持降级 feign.hystrix.enabled=true
(3)在openfeign客户端中加入hystrix
//调用商品服务的openfeign组件 @FeignClient(value = "products")//标识当前接口是一个feign组件 value=调用服务的id public interface ProductsClient { @GetMapping("/product/testBreak") String testBreak(@PathVariable("id") Integer id); }
(4)开发fallback处理类
//因为使用了openfeign 所以要先开发client接口 //调用商品服务的openfeign组件 @FeignClient(value = "products", fallback = ProductFallback.class)//标识当前接口是一个feign组件 value=调用服务的id public interface ProductsClient { @GetMapping("/product/testBreak") String testBreak(@RequestParam("id") Integer id); } //开发fallback处理类实现client接口 @Component public class ProductFallback implements ProductsClient{ @Override public String testBreak(Integer id) { return "hystrix熔断,id:"+id; } }
(5)使用openfeign调用,并访问:http://127.0.0.1:9200/user/showProductTestBreak
@RestController public class UserController { @Autowired private ProductsClient productsClient; /*** * product服务可用时,如果满足熔断条件则熔断,product服务不可用时,则降级。 * @return */ @GetMapping("/user/showProductTestBreak") public String showProductTestBreak() { String rst=productsClient.testBreak(-1); return rst; } }
4、Hystrix DashBoard
Hystrix DashBoard的一个主要优点是它收集了关于每个HystrixCommand的一组度量。Hystrix仪表盘以高效的方式显示每个断路器的运行情况。
1)使用方法
(1)引入hystrix dashboard依赖
<!--引入hystrix dashboard依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
(2)在application上开启hystrix dashboard注解
@SpringBootApplication @EnableCircuitBreaker//开启断路器 @EnableHystrixDashboard//开启hystrix dashboard public class ProductsApplication { public static void main(String[] args) { SpringApplication.run(ProductsApplication.class, args); } }
(3)访问:http://127.0.0.1:8200/hystrix
未完待续
五、Gateway组件使用
1、什么是服务网关
1)说明
网关统一服务入口,可方便实现对平台众多服务接口进行管控,对访问服务的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制,甚至基于API调用的计算或者计费等等。
网关=路由转发+过滤器
路由转发:接收一切外界请求,转发熬后端的微服务上去;
在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成。
2)为什么需要网关
网关可以实现服务的统一管理;网关可以解决微服务中通用代码的冗余问题(如权限控制,流量监控,限流等)
3)网关组件在微服务中架构
4)服务网关组件分类
(1)zuul
zuul是从设备和网站到Netflix流媒体应用程序后端的所有请求的前门。作为一个边缘应用程序,zuul被构建为支持动态路由、监视、弹性和安全性。目前zuul组件以及从1.0更新到2.0,但是作为springcloud官方不再推荐使用zuul2.0,但是依赖支持zuul2.0。
(2)gateway
这个项目提供了一个在springmvc之上构建API网关的库。springcloud gateway旨在提供一种简单而有效的方法来路由到api,并为api提供横切关注点,比如:安全性、监控/度量和弹性。
特性:基于springboot2.x和spring webFlux和Reactor构建响应式异步非阻塞IO模型;动态路由;请求过滤。
2、服务网关组件-gateway
网关配置有两种方式:一种是快捷方式,一种是完全展开方式。
1)示例
(1)引入依赖
<!--引入gateway网关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
另外还需要引入consul依赖(也可以引入其他的服务中心依赖)、consul健康检查依赖。(因为要向服务中心注册)
在启动日志中发现,gateway为了效率使用webflux进行异步非阻塞模型的实现,因此和原来的web包冲突,使用gateway时不能引入spring-boot-starter-web包。
(2)配置路由
- 使用配置文件(application.yaml)配置路由(推荐)
server: port: 7500 spring: application: name: gateway cloud: consul: port: 8500 host: localhost discovery: service-name: ${spring.application.name} gateway: routes: - id: users #指定路由唯一标识 uri: http://localhost:8100/ #指定路由服务的地址 predicates: - Path=/user/** #指定路由规则
- 使用java类方式配置路由
@Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes(). route("users_route", r -> r.path("/user/**"). uri("http://localhost:8100/")).build(); } }
(3)测试
http://127.0.0.1:8100/user/findProductAll 和 http://127.0.0.1:7500/user/findProductAll
2)查看网关路由规则列表
gateway提供路由访问列表的web页面,但是默认是关闭的,如果想要查看服务器的路由规则可以在配置文件中开启。
management: endpoints: web: exposure: include: "*" #开启所有web端点暴露
访问路由管理列表地址:http://localhost:7500/actuator/gateway/routes
3)实现负载均衡路由转发
将原来配置文件中配置的uri: http://localhost:9999/改写成uri: lb://users【其中:lb即loadbalance代表转发后台服务时使用负载均衡,users代表服务注册中心上的服务名】
然后开启根据服务名动态获取路由;
spring: cloud: gateway: discovery: locator: enabled: true #开启根据服务名动态获取路由
4)常用路由predicate(断言、验证)
spring: cloud: routes: predicates: - Path=/product/** #指定路由规则 - After=2021-05-01T08:00:00.993+09:00[Asia/Shanghai] #指定日期之后的请求进行路由 - Before=2021-05-02T08:00:00.993+09:00[Asia/Shanghai] #指定日期之前的请求进行路由 - Between=2021-05-01T08:00:00.993+09:00[Asia/Shanghai],2021-05-02T08:00:00.993+09:00[Asia/Shanghai] - Cookie=username,zhansan #基于指定 cookie的请求进行路由 curl http://127.0.0.1:7500/product/showMsg --cookie "username=zhansan" - Cookie=username,[A-Za-z0-9]+ - Header=X-Request-Id,d+ #基于请求头总的指定属性的正则匹配路由 curl http://127.0.0.1:7500/product/showMsg -H "X-Request-Id:121" - Method=GET,POST # 基于指定的请求方式请求进行路由
5)常用的Filter以及自定义filter
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由筛选器的作用域是特定路由。SpringCloud Gateway包括许多内置的GateWayFilter工厂。
当我们有很多个服务时,客户端请求各个服务的API时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
(1)内置过滤器
spring: cloud: gateway: routes: filters: - AddRequestParameter=productId,0001 #增加请求参数 - AddResponseHeader=isGray,true #增加响应参数 - AddRequestHeader=isGray,true #增加请求头
(2)自定义过滤器
@Configuration public class CustomGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { if(exchange.getRequest().getQueryParams().get("username")!=null){ System.out.println("用户身份信息合法,请求放行。"); System.out.println("经过全局Filter处理"); Mono<Void> filter=chain.filter(exchange);//放行 放行后继续向后执行 System.out.println("响应回来Filter处理"); return filter; } System.out.println("用户身份信息非法,请求拦截。"); return exchange.getResponse().setComplete(); } @Override public int getOrder() { return 0;//数字越小越先执行 } }
六、Config组件使用
1、什么是Config
1)定义
config(配置)又称为统一配置中心,顾名思义,就是将配置统一管理,配置统一管理的好处是在日后大规模集群部署应用时,相同的服务配置一致,日后再修改配置只需要统一修改全部同步,不需要一个一个服务手动维护。
2、ConfigServer开发
1)开发步骤
(1)引入config server依赖
<!--引入统一配置中心依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
另外还需要引入consul依赖(也可以引入其他的服务中心依赖)、consul健康检查依赖。(因为要向服务中心注册)
(2)开启统一配置中心服务
@SpringBootApplication @EnableConfigServer public class Configserver6500Application { public static void main(String[] args) { SpringApplication.run(Configserver6500Application.class, args); } }
(3)编写配置文件(application.properties)
- 服务基本配置
server.port=6500 spring.application.name=configserver spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 配置远端仓库地址
#配置仓库地址
spring.cloud.config.server.git.uri=https://gitlab.com/**/springcloud-config.git
#私有库访问
#spring.cloud.config.server.git.username=****
#spring.cloud.config.server.git.password=****
- 指定分支和本地仓库位置
#指定分支和仓库位置 spring.cloud.config.server.git.basedir=D:\git #一定要是一个空目录,在首次会将该目录清空 spring.cloud.config.server.git.default-label=master
2)查看
(1)拉取远端配置(三种方式)
http://localhost:6500/test-XXXX.properties 或 http://localhost:6500/test-XXXX.json 或 http://localhost:6500/test-XXXX.yaml
(2)查看拉取配置详细信息
http://localhost:6500/client/dev (client:代表远端配置的名称,dev:代表远端配置的环境),如:http://127.0.0.1:6500/test/dev
3、ConfigClient开发
1)开发步骤
(1)引入config client依赖
<!--引入统一配置中心client依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
另外还需要引入consul依赖(也可以引入其他的服务中心依赖)、consul健康检查依赖。(因为要向服务中心注册)
(2)编写配置文件(application.properties)
- 服务基本配置(要放在远程仓库的)
server.port=9100 spring.application.name=configclient spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.discovery.service-name=${spring.application.name}
- 配置客户端(原来是application.properties,现在要改成bootstrap.properties,见下面说明)
#服务名放在远端 #spring.application.name=configclient #开启统一配置中心服务 spring.cloud.config.discovery.enabled=true #指定统一配置服务中心的服务标识 spring.cloud.config.discovery.service-id=configserver #指定从仓库的哪个分支拉取配置 spring.cloud.config.label=master #指定拉取配置文件的名称 spring.cloud.config.name=test #指定拉取配置文件的环境 spring.cloud.config.profile=dev
(3)远程仓库创建配置文件
公共配置:test.properties
dev配置:test-dev.properties
prod配置:test-prod.properties
2)使用application.properties报错
(1)说明:项目目前使用的是application.properties启动项目,使用这个配置文件在springboot项目启动过程中不会等待远程配置拉取,直接根据配置文件中内容启动,因此当需要注册中心、服务端口等信息时,远程配置还没有拉取到,所以直接报错。
(2)解决方案:
应该在项目启动时先等待拉取远程配置,拉取远程配置成功之后再根据远程配置信息启动即可,为了完成上述要求springboot官方提供了一种解决方案,就是在使用统一配置中心时,应该将微服务的配置文件名修改为bootstrap.(properties|yml),bootstrap.properties作为配置启动项目时,会优先拉取远程配置,远程配置拉取成功之后根据远程配置启动当前应用。
4、配置修改配置文件自动生效
在生产环境中,微服务可能非常多,每次修改完远端配置后,不可能对所有服务进行重新启动,这个时候需要让修改配置文件的服务能够刷新远程修改之后的配置,从而不要每次重启服务才能生效,进一步提高微服务系统的维护效率。在springcloud中也为我们提供了手动刷新配置和自动刷新配置两种策略。
1)手动配置刷新
(1)在config client端加入刷新暴露端点
#开启所有web端点暴露 management.endpoints.web.exposure.include=*
(2)在需要刷新代码的类中加入刷新配置的注解:@RefreshScope
@RestController @RefreshScope public class InitController { @Value("${server.port}") private int port; @GetMapping("/configclient/init") public String init(){ return "当前服务的端口为:"+port; } }
(3)使用post请求向config client刷新配置
curl -X POST http://localhost:6500/actuator/refresh #6500 config server端口
2)自动刷新配置
见bus组件使用
七、Bus组件使用
1、简介
2、实现配置刷新原理
3、自动配置刷新
spring.cloud.config.fail-fast=true
(6)修改远程配置后在配置中心服务通过执行post接口刷新配置
curl -X POST http://localhost:8080/actuator/bus-refresh
3)指定服务刷新配置
curl -X POST http://localhost:8080/actuator/bus-refresh/configclient:9090
curl -X POST http://localhost:8080/actuator/bus-refresh/configclient
其中configclient代表属性服务的唯一标识。
4)集成webhook实现自动刷新