集群: cluster 同一种软件服务的多个服务节点共同为系统提供服务过程称之为该软件服务集群
分布式: distribute 不同软件集群共同为一个系统提供服务 这个系统称之为分布式系统
官方定义:微服务是一种架构
通俗定义:微服务是一种架构,这种架构是将单个的整体应用程序分割成更小的项目关联的独立的服务。一个服务通常实现一组独立的特性或功能,包含自己的业务逻辑和适配器。各个微服务之间的关联通过暴露api来实现。这些独立的徽服务不需要都署在同一个虚拟机,同一个系统和同一个应用服务器中。
优点:
缺点:
优点:
缺点:
# 1.架构的演变过程
- [单一应用架构] ===> [垂直应用架构] ===> [分布式服务架构] ===> [流动计算架构] | [微服务架构] ===> [未知]
国内阿里系:
spring cloud技术栈:
**官方定义:**springcloud为开发人员提供了在分布式系统中快速构建一些通用模式的工具(例如配置管理、服务发现、断路器、智能路由、微代理、控制总线)
**通俗定义:**springc loud含有众多子项目的工具集tools collection 微服务工具集合
基于单体应用围绕业务进行服务拆分,拆分出来每一个服务独立应用、独立运行、独立都署、运行在自己计算机进程中。基于分布式服务管理
springcloud是一个由众多独立子项目组成的大型综合项目,原则每个子项目上有不同的发布节奏,都维护自己发布版本号。为了更好的管理springcloud的版本,通过一个资源清单BOw(Gi1l of Materials),为避免与子项目的发布号混猾,所以没有采用版本号的方式,而是通过命名的方式。这些名字是按字母顺序排列的。如伦敦地铁站的名称“天使"是第一个版本,“布里斯顿"是第二个版本, “卡姆登"是第三个版本)。当单个项目的点发布累积到一个临界量,或者其中一个项目中有一个关键缺陷需要每个人都可以使用时,发布序列将推出名称以” .SRX"“结尾的“服务发布”",其中"×"是一个数字。
在父项目中继承spring boot父项目
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.12.RELEASEversion>
parent>
维护springcloud的依赖
<properties>
<spring.cloud-version>Hoxton.SR12spring.cloud-version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud-version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
服务注册中心就是在整个微服务架构单独抽取一个服务,这个服务不完成项目中任何业务功能,仅仅用来在微服务中记录微服务以及对整个系统微服务进行健康状态检查,以及服务元数据信息存储
服务注册中心作用:
常用注册中心组件: eureka(netflix)、 zookeeper (java) 、consul (Go) 、nacos (java阿里巴巴)
创建项目并引入eureka server依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
编写配置文件,指定eureka server端口、服务地址
#eurrka server 默认端口号
server.port=8761
#eurrka server服务中心注册地址,暴露服务地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
在入口类中加入注解@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Eureka Server 细节
注意:在微服务架构中服务名称代表服务唯一标识,至关重要,服务名称必须唯一,使用时必须通过如下配置指定服务名称
# 指定服务名称 建议大写 且不运行名称中带有下划线_
spring.application.name=EUREKASERVER
出错解释: eureka含有两个组件 eureka server , eureka client组件,当项目中引入enrekaserver组件时,这个组件同时将eureka client引入到项目中,因此启动时即会将自己作为一个服务中心启动,同时也会将自己作为服务客户端进行注册,默认启动时立即注册,注册时服务还没有准备完成因此会出现当前错误
如何关闭erueka server 自己注册自己
# 关闭eureka的立即注册功能 默认true,启动就注册,关闭后,启动不会报错
eureka.client.fetch-registry=false
# 让当前应用仅为注册中心,启动后不再把自己注册进去(关闭自己注册自己)
eureka.client.register-with-eureka=false
开发 eureka client 就是日后基于业务拆分出来的一个个微服务
创建新的springboot应用,引入相关依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
编写application.properties
# 指定服务端口
server.port=8989
# 服务名称
spring.application.name=EUREKACLIENT
# 指定服务注册中心地址
eureka.client.service-url.defaulZone=http://localhost:8761/eureka
在入口类中加入注解@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient //让当前微服务作为一个eureka serve客户端进行服务注册
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class,args);
}
}
自我保护机制(Self Perservation Mode)
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。EurekaServer在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server会将这些实例保护起来,让这些实例不会过期。这种设计的哲学原理就是""可信其有不可信其无!"。自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
eureka server关闭自我保护机制
# 关闭自我保护机制 默认true开启
eureka.server.enable-self-preservation=false
#超过3000毫秒自动清除 默认60*1000 1分钟后清除
eureka.server.eviction-interval-timer-in-ms=3000
客户端
# 默认接受心跳最大时间,默认90s
eureka.instance.lease-expiration-duration-in-seconds=10
#指定客户端多久想eureka注册中心发送心跳检测频率,默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
关闭自我保护机制 官方不推荐 关闭时同样发出警告
集群搭建
完全集群
创建3个springboot项目
引入eureka server依赖
配置文件application.properties
node1: server.port=8761
http://localhost:8762/eureka,http://localhost:8763/eureka
node2: server.port=8761
http://localhost:8761/eureka,http://localhost:8763/eureka
node3: server.port=8763
http://localhost:8761/eureka,http://localhost:8762/eureka
在每个项目入口类加入@EnableEurekaClient
注解
consul是一个可以提供服务发现,健康检查,多数据中心,Key/Value存储等功能的分布式服务框架,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,使用起来也较为简单。consul用Golang实现,因此具有天然可移植性(支持Linux、windows和Mac os x);安装包仅包含个可执行文件,方便部署。
启动服务注册中心 consul agent -dev
访问consul管理界面
http:端口默认8500
浏览器::localhost: 8500
管理界面基本介绍
dc1:数据中心名称 datacenter 默认为: dc1 指定数据中心启动 consul agent -dev -datacenter=aa
services:当前consul服务中注册服务列表 默认:client server同时启动自己注册自己会出现一个consul服务
nodes:用来查看consul的集群节点
创建独立的springboot应用
引入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
编写application.properties
server.port=8082
spring.application.name=CONSULCLIENT
# consul server 服务注册地址
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 指定当前服务注册名 默认${spring.application.name}
spring.cloud.consul.discovery.service-name=${spring.application.name}
# 关闭健康检查 不建议关闭
#spring.cloud.consul.discovery.register-health-check=false
在入口类中加入注解@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient //作用:通用服务注册客户端注解代表consul client, zk client, nacos client
public class ConsulClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulClientApplication.class, args);
}
}
直接启动consul elient出现如下问题
原因:consul server检测所有客户端心跳,但是发送心跳时client必须给予响应才能该服务才能正常使用,在现有客户端中我们并没有引入健康检查依赖,所以导致健康检查始终不同通过,导致服务不能使用
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
HTTP Rest方式 | 使用http协议进行数据传输 | JSON | spring cloud 使用http协议传输数据 |
---|---|---|---|
RPC方式 | 远程过程调用 | 二进制 |
OSI:物理层 数据链路层 网络层 传输层(RPC) 会话层 表示层 应用层(Http)
开发两个测试服务 用户服务users 订单服务orders
用户服务 订单服务 都是两个独立springboot应用
两个服务都引入consul client依赖和健康检查依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
配置两个服务application.properties
# 各自端口号与项目名
# consul server
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
在入口类中加入注解@EnableDiscoveryClient
案例
@RestController
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@GetMapping("order")
public String demo(){
log.info("order demo ...");
return "order demo ok!";
}
}
@RestController
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("user")
public String invoke(){
log.info("demo user...");
// 调用订单服务
RestTemplate template = new RestTemplate();
String orderResult = template.getForObject("http://localhost:9999/order", String.class);
log.info("调用订单服务成功+",orderResult);
return "调用order服务成功,结果为:" + orderResult;
}
}
现有RestTemplate在进行服务间通信时
解决RestTemplate负载均衡问题:
使用springcloud提供的组件 ribbon 解决负载均衡调用
spring cloud - netflix - ribbon 作用:负载均衡客户端组件,就是用来实现请求调用时负载均衡
使用用户调用订单服务用户服务中引入ribbon依赖
注意:consul client依赖中已经存在ribbon相关依赖无需项目中
直接使用Ribbon组件根据服务id实现请求负裁均衡
Discoveryclient 服务发现客户端对象、根据服务id去服务注册中心获取对应服务服务列表到本地中
缺点:没有负载均衡需要自己实现负载均衡
@RestController
public class UserController {
@Autowired //服务与注册发现客户端对象
private DiscoveryClient discoveryClient;
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("user")
public String invoke(){
log.info("demo user...");
// 使用ribbon组件+RestTemplate实现负载均衡调用 1.DiscoveryClient 2.LoadBalanceClient 3.@LoadBalanceClient
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("ORDERS");
serviceInstances.forEach(serviceInstance -> {
log.info("服务主机:{} 服务端口:{} 服务地址:{}",serviceInstance.getHost(),serviceInstance.getPort(),serviceInstance.getUri());
});
String ordersResult = new RestTemplate().getForObject(serviceInstances.get(0).getUri()+"/order", String.class);
return "ok" + ordersResult;
}
}
LoadBalanceClient 负载均衡客户端对象 根据服务id去服务注册中心获取对应服务列表,根据默认负载均衡策略选择列表中一台机器进行返回
缺点:使用时需要每次先根据服务id获取一个负载均衡机器之后再通过restTemplate调用服务
@RestController
public class UserController {
@Autowired
private LoadBalancerClient loadBalancerClient;
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("user")
public String invoke(){
log.info("demo user...");
ServiceInstance serviceInstance = loadBalancerClient.choose("ORDERS"); //默认轮询
log.info("服务主机:{} 服务端口:{} 服务地址:{}",serviceInstance.getHost(),serviceInstance.getPort(),serviceInstance.getUri());
String ordersResult = new RestTemplate().getForObject(serviceInstance.getUri()+"/order", String.class);
return "ok" + ordersResult;
}
}
@LoadBalance + RestTemplate 负裁均衡客户端注解
修饰范围:用在方法上 作用:让当前方法、当前对象具有ribbon负载均衡特性
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@GetMapping("user")
public String invoke(){
log.info("demo user...");
String ordersResult = restTemplate.getForObject("http://ORDERS/order", String.class);
return "ok" + ordersResult;
}
}
使用RestTemplate + Ribbon这种完成服务间通信
路径写死在代码中不利于维护 restTemplate.getForObject("http://ORDERS/order", String.class);
Ribbon组件实现负载均衡原理
原理:根据调用服务的服务id去服务注册中心获取对应服务id的服务列表,并将服务列表拉取本地进行缓存,然后在本地通过默认的轮询的负载均衡策略在现有列表中选择一个可用节点提供服务
注意:客户端负裁均衡
Ribbon组件支持那些负载均衡策略
源码分析:
Ribbon负载均衡策略支持哪些?
# 1.Ribbon负载均衡算法
- RoundRobinRule 轮询策略 按顺序循环选择server
- RandomRule 随机策略 随机选择 server
- AvailabilityFi1teringRule 可用过滤策略
`会先过滤由于多次访间故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
- weightedResponseTimeRule 响应时间加权策略
`根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到
- RetryRule 重试策略
`先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
- BestAviableRule 最低并发策略
`会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
设置服务间调用负载均衡策略
服务id.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
Ribbon组件现在状态(停止维护)
ribbon-core、ribbon-loadbalance依然在大规模生产实践中部署、意味着日后如果实现服务间通信负载均衡依然使用ribbon组件
总结:
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性(可以使用springmvc的注解),可使用Feign注解和AX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,默认实现了负载均衡的效果并且springcloud为feign添加了springmvc注解的支持。
- 简介:Rest client: 0penFeign与RestTemplate作用一致,都是一个http客户端
- RestTemplate:spring框架封装Httpclient对象
- openFeign:伪Httpclient客户端对象 他可以使服务间通信变得更加简单 Feign默认集成了Ribbon 实现请求负载均衡
简单:1.使用写一个接口加一个注解
2.调用服务代码更加简单自动完成数据传递过程中对象转换
- 为什么使用openFeign
a.RestTemplate 使用问题:
1.路径写死
2.不能自动转换响应结果为对应对象
3.必须集成ribbon实现负载均衡
b. openFeign组件可以解决RestTemplate实现服务间通信所有问题
建立两个独立的springboot应用,并注册到服务中心
引入服务注册中心依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-consul-discoveryartifactId>
dependency>
修改配置文件
server.port=8787
spring.application.name=CATEGORY
#server.port=8788
#spring.application.name=PRODUCT
# 注册到consul server
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
加入入口注解
@SpringBootApplication
@EnableDiscoveryClient
使用openfeign进行调用
在调用服务方引入openFeign依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在服务调用方入口类加注解,开启Feign调用支持
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 开启openFeign客户端调用
public class CategoryApplication {
}
在服务调用方建立接口类,其中方法与被调用方controller方法返回值、路径一致
@FeignClient("PRODUCT") // value:用来书写调用服务的服务id
public interface ProductClient {
@GetMapping("/product")
public String product();
}
// 被调用方controller方法
@RestController
public class ProductController {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
@Value("${server.port}")
private int port;
@GetMapping("/product")
public String product(){
log.info("product demo...");
return "product ok! 服务端口号是:" + port;
}
}
在服务调用方使用该接口,完成调用
@RestController
public class CategoryController {
private static final Logger log = LoggerFactory.getLogger(CategoryController.class);
@Autowired
private ProductClient productClient;
@GetMapping("/category")
public String category(){
log.info("category service...");
String product = productClient.product();
log.info("结果 {}", product);
return "category ok!" + product;
}
}
微服务架构中服务间通信手段
http协议: springcloud两种方式: 1.RestTeamplate + Ribbon 2. openFeign推荐
服务间通信,参数传递和响应处理
参数传递: 1.传递零散类型参数 ⒉.传递对象类型 3.数组或集合类型参数
零散类型参数传递
对象类型参数传递
application/json 方式:推荐
注意:使用json方式在openfeign接口声明中必须给参数加入注解@RequestBody
注解
数组参数传递
注意:在openFeign传递数组类型参数时必须在声明时使用@RequestParam
注解标识
集合类型的参数接收
使用openFeign调用服务,并返回对象
// 声明调用根据id查询商品信息接口
@GetMapping("/product/{id}")
Product product(@PathVariable("id") Integer id);
使用openFeign调用服务,返回集合List Map集合
// 声明调用商品服务根据类别id查询一组商品信息
@GetMapping("/products")
List<Product> findByCategoryId(@RequestParam("categoryId") Integer categoryId);
openFeign默认超时处理
默认的调用超时:使用openFeign组件在进行服务间通信时要求被调用服务必须在1s内给予响应,一旦服务执行业务逻辑时间超过1s , openFeigm组件将直接报错: Read timed out execut ing GET http:/ /PRODUCT/product
修改openFeign超时时间
# 指定服务修改基个服务调用超时时间
feign.client.config.PRODUCTS.connectTimeout=5000 #配置指定服务连接超时
feign.client.config.PRODUCTS.readTimeout=5000 #配置指定服务等待超时
#PRODUCTS为调用服务的服务id
#修改openfeign默认调用所有服务超时间
feign.client.config.default.connectTimeout=5000 #配置所有服务连接超时
feign.client.config.default.readTimeout=5000 #配置所有服务等待超时
openFeign日志调用
openFeign 伪HttpClient客户端对象,用来帮助我们完成服务间通信 底层用http协议 完成服务间调用
**日志:**OpenFeign为了更好方便在开发过程中调试openFeigm数据传递和响应处理,,openFeign在设计时添加了日志功能,默认openFeign白志功能需鉴手动开启的
日志使用:
# 1.展示openfeign日志
- logging.level.com.baizhi.feignclient=debug
# 2.feign每一个客户端提供一个日志对象
` HOHE 不记录任何日志
`BASIC 仅仅记录请求方法,ur1,响应状态代码及执行时间
`HEADERS 记录Basic级别的基础上,记录请求和响应的header
`FULL记录请求和响应的header,body和元数据,展示全部http协议状态
- feign.client.config.PRODUCT.loggerLevel = FULL
在分布式环境中,许多服务依赖项不可避免地会失败。Hystrix是一个库,它通过添加延迟容忍和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止它们之间的级联故障以及提供后备选项来实现这一点,所有这些都可以提高系统的整体弹性。
通俗定义:Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调用失败,超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障(服务雪崩现象),提高分布式系统的弹性。
Hystrix 豪猪 起源: netFlix springcloud netflix hystrix
作用:用来防止微服务系统中服务雪崩现象﹑实现服务熔断 熔断是防止雪崩手段
- 在一个时刻微服务系统中所有微服务均不可用的这种现象称之为服务雪崩现象
# 1.服务雪崩
- **在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程**
- 根本原因:在调用链路中链路某一服务因为执行业务时间过长,或者是大规模出现异常导致自身服务不可用,并把这种不可用放大情况
# 2.图解雪崩效应
- 如存在如下链路调用:
- 而此时,Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算service A能扛得往请求,Sservice B和Service c未必能扛得住这突发的请求。此时,如果Service c因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽service B的线程资源,Service B就会变得不可用。紧接着,service A也会不可用,这一过程如下图所示
作用:用来在为服务系统中防止服务雪崩现象的出现
熔断机制:所有的微服务中必须引入Hystrix组件,一旦引入hystrix这个组件就具有了服务熔断功能
# 服务熔断
- "熔断器"本身是一种开关装置,当某个服务单元发生故障之后,通过断踏器(hystrix)的故障监控,某个异常条件被触发,直接熔断整个服务。向调用方法返回一个符合预期的、可处理的备选响应(Fa11Back) ,而不是长时间的等待或者抛出调用方法无法处理的异常,就保证了服务调用方的线程不会破长时间占用,避免故障在分布式系统中蔓延,乃至雪崩。如果目标服务情况好转则恢复调用。服务熔断是解袂服务雪崩的重要手段
# 服务熔断示意图
# 服务降级
- 定义:服务压力剧增的时侯根据当前的业务情祝及流量对一些服务和页面有策略的降级,以此缓解服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的响应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。
- 通俗定义:当网站|服务流量突然增加时,为了保证系统核心服务正常运行,有策略关闭系统中边缘服务,以保证核心服务正常运行
- 服务降级:关闭微服务系统中某些边缘服务保证系统核心服务正常运行
- 12 淘宝 京东
- 删除订单 --- 关闭订单 确认收货 ----> 服务繁忙,!!
# 服务降级图示
# 1.共同点
- 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
- 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
- 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增珊改)﹔
- 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段: sentinel
# 2.异同点
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
- 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务边缘服务开始)
# 3.总结
- 熔断必会触发降级,所以熔断也是降级一种,区别在于熔断是对调用链路的保护,而降级是对系统过载的一种保护处理
# 服务熔断的实现思路
- 引入hystrix依赖,并开启熔断器(断路器)
- 模拟降级方法
- 进行调用测试
项目中引入hystrix依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
开启断路器(开启熔断功能在入口类加入注解@EnableCircuitBreaker
)
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker // 开启hystrix服务熔断
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class,args);
}
}
在控制器方法中加入备选处理@HystrixCommand(fallbackMethod = "demoFallBack")
@GetMapping("/demo/{id}")
@HystrixCommand(fallbackMethod = "demoFallBack") //熔断之后处理 fallbackMethod 书写快速失败方法名
public String demo(@PathVariable("id") Integer id){
System.out.println("demo ok!!!");
if (id < 0) {
throw new RuntimeException("无效id!!!!");
}
return "demo ok !";
}
public String demoFallBack(Integer id){
return "当前活动过于火爆,服务已经被熔断了!!";
}
@HystrixCommand(fallbackMethod = "demoFallBack") //自定义备选处理
@HystrixCommand(defaultFallback = "默认处理方法名") //默认处理
断路器打开条件
# 打开关闭条件
- 1.当满足一定的阀值的时候(默认10秒内超过20个请求次数)
- 2.当失败率达到一定的时候(默认10秒内超过50%的请求失败)
- 3.到达以上阀值,断路器将会开启
- 4.当开启的时候,所有请求都不会进行转发
- 5.一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和
# 面试重点问题:断路器流程
- **整个流程**
- 当Hystrix监控到对该服务接口调用触发1 2两个阈值时,会在系统中自动触发熔断器,在熔断器打开期间内,任何到该接口请求均不可用,同时在断路器打开ss后断路器会处于半开状态,此时断路器允许放行一个请求到该服务接口,如果该请求执行成功,断路器彻底关闭,如果该请求执行失败断路器重新打开。
引入Hystrix依赖
注意:openfeign组件底层自动依赖Hystrix依赖项目中无须显示引入
开启openfeign对Hystrix支持
#开启openfeign在调用服务过程中开启hystrix支持默认:就是没有开启
feign.hystrix.enabled=true
开发openfeign服务调用失败默认处理的实现类
在openFeign客户端接口中的@Feignclients(value="服务id" ,fallBack=默认处理.class)
仪表盘:用来显示状态信息
作用:监控每一个@HystrixCommond
注解创建一组度量,构建一组信息,然后通过图形化方式展示当前方法@RHystrixComnond
的状态信息
构建Hystrix DashBoard 仅仅是一个仪表盘应用
创建springboot应用
引入hystrix dashboard依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
在入口类加入注解,开启仪表盘应用
@SpringBootApplication
//@EnableDiscoveryClient //注意:默认只要引入discovery client依赖该注解无须显示声明自动注册
@EnableHystrixDashboard //注意:这个注解作用用来开启当前应用为仪表盘应用
public class HystrixDashBoardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashBoardApplication.class, args);
}
}
访问仪表盘web页面
http://localhost:当前应用的端口号(9909)/hystrix
# 1.解决第一个问题,直接引入如下代码(加入要监控的微服务里面)
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean(streamServlet);
regBean.setLoadOnStartup(1);
List mappingList = new ArrayList();
mappingList.add("/hystrix.stream");
regBean.setUrlMappings(mappingList);
regBean.setName("HystrixMetricsStreamServlet");
return regBean;
}
# Unable to connect to Command Metric Stream 错误
hystrix.dashboard.proxy-stream-allow-list=localhost
management.endpoints.web.exposure.include=hystrix.stream
Hystrix & Hystrix DashBoard
目前状态
a . Hystrix不再处于积极开发中,目前处于维护模式.
b. Hystrix DashBoard已经被废弃
The hystrix-dashboard component of this project has been deprecated and moved to
替换产品: Hetflix- Skunkworks/hystrix-dashboard .
日后如何解决服务雪崩
a . Hystrix(版本1.5.18)足够稳定,可以满足Netflix对我们现有应用程序的需求
b. resilience4j 替换Hystrix 实时自适应系统熔断 Springcloud resilience4j
c .sentinel 被称为流量卫兵、流量控制、降低策略(根据异常)推荐 sentinel & sentinel dashboard
# 1.说明
- 网关统一服务入口,可方便实现对平台众多服务接口进行管控,对访问服务的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制,甚至基于API调用的计量或者计费等等。
- 网关 = 路由转发 + 过滤器
`路由转发:接收一切外界请求,转发到后端的微服务上去;
`在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成
# 2.为什么需要网关
- 1.网关可以实现服务的统一管理
- 2.网关可以解决微服务中通用代码的冗余问题(如权限控制,流量监控,限流等)
# 3.网管组件在微服务中架构
网关作用:
netflix zuul 1. x(效率) zuul 2.x(近两年)
spring cloud gateway组件(Flux异步非阻塞Io模型) 推荐
# gateway(spring)
- 这个项目提供了一个在springmvc之上构建API网关的库。springcloudgateway旨在提供一种简单而有效的方法来路由到api,并为api提供横切关注点,比如:安全性、监控/度量和弹性。
# 特性
- 动态路由
- 请求过滤
创建一个springboot应用
引入相关依赖 注意:去掉项目中的spring-boot-starter-web
因为存在冲突
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
<exclusions>
<exclusion>
<groupId>*groupId>
<artifactId>*artifactId>
exclusion>
exclusions>
dependency>
编写配置
配置文件方式
server:
port: 7979
spring:
application:
name: GATEWAY
cloud:
consul:
host: localhost
port: 8500
gateway:
routes:
- id: category_router #路由对象唯一标识
uri: http://localhost:8787 #用来类别服务地址 http//localhost:8787/category
predicates: #断言 用来配置路由规则
- Path=/category
- id: product_router #路由对象唯一标识
#uri: http://localhost:8788 #用来类别服务地址 http//localhost:8788/list
uri: lb://PRODUCT #实现负载均衡
predicates: #断言 用来配置路由规则
- Path=/product
java代码配置(了解)
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
// 类别路由
.route("category_router", r -> r.path("/category").uri("http://localhost:8787"))
// 商品路由
.route("product_router", r -> r.path("/product").uri("http://localhost:8788"))
.build();
}
现有网关配置存在的问题
现有网关配置方式在uri的属性中路径直接写死为服务的某一个节点,这样没有办法实现请求的负载均衡
网关gateway = 断言predicate + 过滤(后置filter)
断言:当请求到达网关时,网关前置处理,满足断言放心请求,不满足断言立即返回
过滤:当请求满足断言的所有条件之后,会向后端服务转发,在向后端服务转发之前会经过一些过滤
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
- Path=/product
路径断言
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
代表该路由规则必须在指定时间之后才能生效
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
代表该路由规则必须在指定时间之前有效过了时间失效
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
代表路由规则在某个时间段有效
- Cookie=name, xiaosiao
- Cookie=chocolate, ch.p
此路由匹配具有名称chocolate
与ch.p
正则表达式匹配的cookie 的请求
- Header=X-Request-Id, \d+
请求必须含有指定请求头才行
- Method=GET,POST
限定指定请求方式
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
内置filter
AddRequestHeader Filter
- AddRequestHeader=X-Request-red, blue 用来给路由对象的所有转发请求加入指定请求头信息
AddRequestParameter Filter
- AddRequestParameter=red, blue 用来给路由对象的所有转发请求加入指定请求参数
AddResponseHeader Filter
- AddResponseHeader=X-Response-Red, Blue 用来给路由对象的所有转发请求的响应加入指定头信息
PrefixPath Filter
- PrefixPath=/mypath 用来给路由对象的所有转发请求的ur1加入指定前缀信息
如:浏览器访问网关地址/list 前缀路径/mypath 转发到后端服务地址为: uri+前缀路径+地址栏路径===> uri+/mypath/list
StripPrefix Filter
- StripPrefix=2 用来给路由对象的所有转发请求的ur1去掉指定2个前缀
如:浏览器访问网关地址:/produet/list stripPefix=1====>/list 后端接口:list
自定义全局filter 所有请求都整经过全局filter之后再转发到后端服务
@Configuration
public class filterCustomerGlobalFilter implements GlobalFilter, Ordered {
//类似javaweb doFilter
// exchange :交换request response 封装了request response
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//httpRequest对象
ServerHttpRequest request = exchange.getRequest();
// HttpResponse对象
ServerHttpResponse response = exchange.getResponse();
System.out.println("经过全局filter处理。。。。。");
Mono<Void> filter = chain.filter(exchange);
System.out.println("响应回来的Filter处理.....");
return filter;
}
// order 排序 int数字:用来指定filter执行顺序﹒默认顺序按照自然数字进行排序 -1表示在所有filter执行之前执行
@Override
public int getOrder() {
return -1;
}
}
通过网关提供web路径查看路由详细规则
http: / / localhost : 7979/ actuator /gateway/routes
查看网关路由规则详细路径必须在网关配置文件中暴露当前路径
management:
endpoints:
web:
exposure:
include: "*"
config(配置)又称为统一配置中心顾名思义,就是将配置统一管理,配置统一管理的好处是在日后大规模集群部署服务应用时相同的服务配置一致,日后再修改配置只需要统一修改全部同步,不需要一个一个服务手动维护。
作用:用来实现微服务系统中服务配置统一管理组件
组件:统一配置中心服务端(集中管理配置文件)、统一配置中心客户端client
选择—个远端git仓库
搭建config server统一配置中心服务
创建独立spring boot应用
引入config server依赖
<--引入config server依赖-->
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
配置application. properties 远程仓库地址
# 远程仓库地址
spring.cloud.config.server.git.uri=https://gitee.com/zi_long006/configs.git
spring.cloud.config.server.git.default-label=master
# spring.cloud.config.server.git.username= 私有仓库访问用户名与密码
# spring.cloud.config.server.git.password=
开启统—配置中心,在入口类加入注解@EnableConfigServer
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer // 表示为统一配置中心服务
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
创建springboot应用
将自身配置交给远端git仓库管理
引入config client依赖
<--引入config client-->
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
配置文件编写 告诉config server地址
#告诉当前configclient统一配置中心在注册中心服务id
spring.cloud.config.discovery.service-id=CONFIGSERVE
#开启当前configclient根据服务id去注册中心获取
spring.cloud.config.discovery.enable=true
# 配置注册中心
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
# 获取那个配置文件 1.确定分支 2.确定文件名 3.确定环境
spring.cloud.config.lable=master
spring.cloud.config.name=configclient
spring.cloud.config.profile=prod
启动configclient
当远端qit仓库中配置发生变化时,不需要重启微服务就可以直接读取远端修改之后配置信息,这种就叫手动配置刷新
在每一个需要获取配置文件参数的controller中添加注解@RefreshScope
修改完远端git仓库配置文件之后,向每一个微服务发送一个post方式请求
必须是 post方式 cmd
curl -X POST http://localhost:8990/actuator/refresh
必须在微服务配置文件中暴露远端配置刷新端点(endpoint )
management.endpoints.web.exposure.include=*
springcloudbus使用轻量级消息代理将分布式系统的节点连接起来。然后,可以使用它来广播状态更改(例如配置更改)或其他管理指令。AMQP和Kafka broker(中间件)实现包含在项目中。或者,在类路径上找到的任何springcloudstream绑定器都可以作为传输使用。
通俗定义:bus称之为springcloud中消息总线,主要用来在微服务系统中实现远端配置更新时通过广播形式通知所有客户端刷新配置信息,避免手动重启服务的工作
作用:利用bus 广播特性当某一个状态(配置文件)发生改变时通知到bus中所有服务节点更新当前状态(更新自身配置)
https://www.bilibili.com/video/BV1S5411c7hM?p=50&spm_id_from=pageDriver
准备启动MQ服务
MQ服务主机: 192.168.80.128
MQ: 15672端口(Web) 5672端口(JAVA)
虚拟主机:/默认用户: guest 密码:guest
配置统—配置中心通过Bus连接到MQ服务
统─配置中心服务中引入Bus依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
配置config server文件
spring.rabbitmq.host=192.168.80.128
spring.rabbitmq.port=5679
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
重启config server
配置微服务(config client)通过bus连接MQ服务
在所有微服务项目中引入Bus依赖
在所有微服务项目中配置MQ连接配置,主要这段配置要放入远端仓库管理
spring.rabbitmq.host=192.168.80.128
spring.rabbitmq.port=5679
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
重启所有微服务时出现,在引入bus依赖之后启动出现如下错误:
错误原因:引入bus依赖启动立即根据配置文件bus配置连接mq服务器,但是此时mq配置信息都在远端,因此bus连接不到mq直接报错,阻止了应用启动
解决方案:允许项目启动时bus组件立即连接mq这个失败,因为获取远端配置之后可以再以远端配置初始化bus组件
spring.cloud.config.fail-fast=true #代表在启动时还没有拉取远端配置完成是的失败都是允许的
通过向config server统—配置发送POST方式请求实现自动配置刷新
注意:/actuator/bus-refresh
必须在config server中暴露
management.endpoints.web.exposure.include=*
刷新所有服务:POST http://localhost:8848(configserver地址)/actuator/bus-refresh
刷新指定服务:POST http://localhost:8848(configserver地址)/actuator/bus-refresh/服务id
钩子 hooks
根据仓库触发事件执行相应操作
webhooks
根据远程仓库触发对应事件发送一个web请求这个请求默认就是POST方式请求
在远端仓库中配置webhooks
添加webhooks,借助内网穿透(将内网地址转换为公网地址) natapp
下载客户端,cmd启动 natapp --authtoken=XXXXXXXXX 穿透地址:http://hgw3uy.natappfree.cc/actuator/bus-refresh
首次配置完成之后config server 发送post方式出现400错误,需要在configServer中加入filter配置
package com.wg.config.component;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class WebHooksFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String url = new String(httpServletRequest.getRequestURI());
if(!url.endsWith("/bus-refresh")){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
private class RequestWrapper extends HttpServletRequestWrapper {
public RequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true : false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
}
}
在入口类中加注解@ServletComponentScan (basePackages = "com.zzl.filters")