上一篇写了一个单据项目,springboot-vue的简单前后端项目,本次介绍当前主流的微服务架构springcloud项目搭建
springboot-vue项目链接
https://blog.csdn.net/qq_44899022/article/details/131638136
一、服务治理-注册中心Eureka搭建
在没有进行服务治理前,服务之间的通信是通过服务间直接相互调用来实现的。微服务系统中服务众多,这样会导致服务间的相互调用非常不便,因为要记住提供服务的IP地址、名称、端口号等。这时就需要中间代理,通过中间代理来完成调用。
在架构选型的时候,我们需要注意一下切记不能为了新而新,忽略了对于当前业务的支持,虽然Eureka2.0不开源了,但是谁知道以后会不会变化,而且1.0也是可以正常使用的,也有一些贡献者在维护这个项目,所以我们不必要过多的担心这个问题,要针对于业务看下该技术框架是否支持在做考虑。
Spring Cloud Eureka 是Netflix 开发的注册发现组件,本身是一个基于 REST 的服务。提供注册与发现,同时还提供了负载均衡、故障转移等能力。
注册中心有eureka,zookeeper等等,本项目选用eureka。
区别:
Zookeeper:CP架构,有选举机制,搭建集群后,当leader挂掉,会从follower选举一个作为新的leader,但选举时间过长,30s~120s之间
Eureka:AP架构,优先保证服务的可用性
注意:
- Eureka Server:服务器端。它提供服务的注册和发现功能,即实现服务的治理。
- Service Provider:服务提供者。它将自身服务注册到Eureka Server中,以便“服务消费者”能够通过服务器端提供的服务清单(注册服务列表)来调用它。
- Service Consumer:服务消费者。它从 Eureka 获取“已注册的服务列表”,从而消费服务。
1.微服务聚合工程-父工程创建
1.1 maven创建项目
idea中直接new,新建,选择maven,然后直接next
修改命名和项目存储空间,然后finish
1.2 修改字符集
左上角file->settings->file encodings 修改成utf-8
1.3 注解生效激活
勾选上
1.4 修改maven编译版本
我本地是8,改成8,根据自身环境修改
1.5 统一jar包管理
将下列代码粘到pom中,注意,GroupId需要和自己项目一致
springboot在创建springcloud时候,需要对应版本,在官网中,我们选择GA版本的springcloud,然后查看对应的springboot,我们选择2021.0.8,对应的springboot是2.6.15
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.Architecture</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-cloud.version>2021.0.8</spring-cloud.version>
<spring-boot.version>2.6.15</spring-boot.version>
</properties>
<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.6.15-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud 2021.0.8-->
<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>
</project>
1.6 IDEA开启Dashboard
微服务中组件很多,我们要启动很多项服务,所以配置dashboard后,我们会直观的看到具体哪些服务启动
在idea中找到.idea配置文件,然后点开workspace.xml配置文件
加入下列配置,然后重启idea
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
</component>
2.Eureka注册中心
1.注册服务发现_单机eureka注册中心搭建
idea中右键单击项目,创建模块,同样是maven项目
命名cloud-eureka-server7001 7001是我们的端口号
现在我们的目录是这样
然后在eureka文件夹中,修改pom文件,加入下列代码,引入eureka依赖和lombok插件
<dependencies>
<!-- 服务注册发现Eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
在eureka中,创建文件夹com.eurekaServer,同时在resources下创建application.yml文件,com.eurekaserver文件夹中创建启动类EurekaServerMain7001.java
编写启动类EurekaServerMain7001.java文件
@SpringBootApplication
@Slf4j
@EnableEurekaServer
public class EurekaServerMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerMain7001.class,args);
log.info("********Eureka服务 7001 启动成功**********");
}
}
写resources下application.yml文件
server:
port: 7001
eureka:
instance:
# eureka服务端的实例名字
hostname: localhost
client:
# 表示是否将自己注册到Eureka Server
register-with-eureka: false
# 表示是否从Eureka Server获取注册的服务信息
fetch-registry: false
# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
然后启动主启动类,浏览器搜索localhost:7001,成功后如图
2.注册服务发现_创建服务提供者
新建一个模块,我们创建服务的提供者
提供者中pom引入相关依赖
<dependencies>
<!-- 引入Eureka 客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
创建com.payment文件夹,随后在文件夹下创建payment8001主启动类paymentMain8001.class,
在resourse下创建application.yml文件
主启动类paymentMain8001.class写入
@EnableEurekaClient
@SpringBootApplication
@Slf4j
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
log.info("********** 服务提供者启动成功 ***********");
}
}
提供者的application.yml写入,我们需要指定eureka的server服务地址,在defaultZone指定
server:
port: 8001
eureka:
client:
service-url:
# Eureka server 地址
defaultZone: http://localhost:7001/eureka/
随后我们启动服务
我们首先要启动7001的注册中心服务,然后才能启动提供者8001服务
启动成功如下,我们去浏览器验证localhost:7001
如下,我们就启动成功了
但是我们现在没有应用名字,在提供者中yml继续写入
spring:
application:
# 设置应用名词
name: cloud-payment-provider
再次打开浏览器,localhost:7001,我们能看到刚刚修改的名字出现在这,说明提供者启动成功了!!!
3.注册中心前端页面解读
- Environment: 环境,默认为test,该参数在实际使用过程中,可以不用更改
- Data center: 数据中心,使用的是默认的是 “MyOwn”
- Current time:当前的系统时间
- Uptime:已经运行了多少时间
- Lease expiration enabled:是否启用租约过期 ,自我保护机制关闭时,该值默认是true, 自我保护机制开启之后为false。
- Renews threshold: 每分钟最少续约数,Eureka Server 期望每分钟收到客户端实例续约的总数。
- Renews (last min): 最后一分钟的续约数量(不含当前,1分钟更新一次),Eureka Server 最后 1 分钟收到客户端实例续约的总数
DS Replicas
显示eureka相邻的节点
Instances currently registered with Eureka
- Application:服务名称。配置的spring.application.name属性
- AMIs:n/a (1),字符串n/a+实例的数量,我不了解
- Availability Zones:实例的数量
- Status:实例的状态 + eureka.instance.instance‐id的值。
实例的状态分为UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOWN.
- UP: 服务正常运行,特殊情况当进入自我保护模式,所有的服务依然是UP状态,所以需要做好熔断重试等容错机制应对灾难性网络出错情况
- OUT_OF_SERVICE : 不再提供服务,其他的Eureka Client将调用不到该服务,一般有人为的调用接口设置的,如:强制下线。
- UNKNOWN: 未知状态
- STARTING : 表示服务正在启动中
- DOWN: 表示服务已经宕机,无法继续提供服务
General Info
- total-avail-memory : 总共可用的内存
- environment : 环境名称,默认test
- num-of-cpus : CPU的个数
- current-memory-usage : 当前已经使用内存的百分比
- server-uptime : 服务启动时间
- registered-replicas : 相邻集群复制节点
- unavailable-replicas :不可用的集群复制节点,如何确定不可用? 主要是server1 向 server2和server3发送接口查询自身的注册信息。
- available-replicas :可用的相邻集群复制节点
Instance Info
- ipAddr:eureka服务端IP
- status:eureka服务端状态
4.注册服务发现_创建服务消费者
新建一个模块,老样子,还是maven
命名cloud-comsumer-order80
生产者也需要注册到eureka注册中心中,pom中导入依赖,也需要client
<dependencies>
<!-- 引入Eureka client依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
消费中java文件夹下创建文件夹com.consumer,然后在新创建的order文件夹下创建主启动类OrderMain.class,同时,在resources下创建application.yml文件
生产者和消费者创建过程很相似!
OrderMain80编写启动类
/**
* 主启动类
*/
@SpringBootApplication
@EnableEurekaClient
@Slf4j
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
log.info("*************** 订单服务消费者启动成功 ***********");
}
}
写yml文件
server:
port: 80
eureka:
client:
service-url:
# Eureka server 地址
defaultZone: http://localhost:7001/eureka/
spring:
application:
# 设置应用名字
name: cloud-order-consumer
消费者完成了,我们打开浏览器localhost:7001试一下
OK!消费者完成了
5.服务注册发现_actuator微服务信息完善
SpringCloud体系里的,服务实体向eureka注册时,注册名默认是IP名:应用名:应用端口名,我们修改一下eureka的实例名
生产者和消费者的pom中添加依赖
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
同时,在生产者和消费者的property.yml中加入如下配置
instance:
#根据需要自己起名字
instance-id: cloud-payment-provider-8001
生产者是8001,消费者是80,我们从下面的name中名字粘过来,然后后面加端口号
然后我们重启两个服务,在eureka中能看到如下信息
6.服务注册发现_服务发现Discovery
服务发现,就是新注册的这个服务模块能够及时的被其他调用者发现。不管是服务新增和服务删减都能实现自动发现。
在微服务时代,我们所有的服务都被劲量拆分成最小的粒度,原先所有的服务都在混在1个server里,现在就被按照功能或者对象拆分成N个服务模块,这样做的好处是深度解耦,1个模块只负责自己的事情就好,能够实现快速的迭代更新。坏处就是服务的管理和控制变得异常的复杂和繁琐,人工维护难度变大。还有排查问题和性能变差(服务调用时的网络开销)
各个微服务相互独立,每个微服务,由多台机器或者单机器不同的实例组成,各个微服务之间错综复杂的相互关联调用
首先我们在消费者中建立一个controller文件夹,然后,新建一个OrderController
ordercontroller中代码如下
@RestController
@Slf4j
@RequestMapping("order")
public class OrderController {
@Autowired
//服务发现
private DiscoveryClient discoveryClient;
/**
* 服务发现获取列表
* @return
*/
@GetMapping("discovery")
public Object discovery(){
// 获取所有微服务信息
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("server:={}",service);
}
return this.discoveryClient;
}
}
然后我们重启服务,打开浏览器搜索localhost/order/discovery
浏览器展示如下
7.引入RestTemplate
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。
我们在消费者下创建包config,然后创建类RestTemplateConfig
作用是将RestTemplate放入到spring的Ioc容器中
RestTemplate代码如下,@LoadBalanced是加入负载均衡的功能
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在payment8001工程下创建PaymentController
编写order80工程Controller
@RestController
@RequestMapping("/order")
public class OrderController {
// HTTP 请求工具
@Autowired
private RestTemplate restTemplate;
/**
* 测试服务发现接口
* @return
*/
@GetMapping("/index")
public String index(){
//1.远程调用方法的主机
//Stringhost="http://localhost:1000";
//将远程微服务调用地址从"IP地址+端口号改成"微服务名称""
String host = "http://cloud-payment-provider";
// 2. 远程调用方法具体URL地址
String url = "/payment/index";
// 3. 发起远程调用
//getForObject:返回响应体中数据转化成的对象,可以理解为json
//getForEntity:返回的是ResponseEntity的对象包含了一些重要的信息
String forObject = restTemplate.getForObject(host + url, String.class);
return forObject;
}
}
浏览器打开http://localhost/order/index
OK!
8.Eureka高可用注册中心搭建
在微服务架构这样的分布式环境中,我们需要充分考虑发生故障的情况,所以在生产环境中必须对各个组件进行高可用部署,对于微服务如此,对于服务注册中心也一样。
Spring-Cloud为基础的微服务架构,所有的微服务都需要注册到注册中心,如果这个注册中心阻塞或者崩了,那么整个系统都无法继续正常提供服务,所以,这里就需要对注册中心搭建,高可用(HA)集群。
Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中,所有节点即是服务提供方,也是服务消费方,服务注册中心也不例外。是否还记得在单节点的配置中,我们设置过下面这两个参数,让服务注册中心不注册自己:
eureka.client.register-with-eureka-false
eureka.client.fetch-registry-false
好,我们开始搭建eureka集群
首先在父项目下建一个模块,同理于创建7001端口eureka
然后我们在7002下得pom文件 加入依赖
<dependencies>
<!-- 服务注册发现Eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
我们在7002下创建一个文件夹,com.eurekaServer,我们直接将7001的启动类copy过来,然后改个名,如图
将主启动类里面的7001改成7002
在7002下创建yml文件
我们需要修改7001和7002的俩个yml文件
#修改7001主机yml文件
server:
port: 7001
eureka:
instance:
# eureka服务端的实例名字
hostname: eureka7001.com
client:
#表 示是否将自己注册到Eureka Server
register-with-eureka: false
# 表示是否从Eureka Server获取注册的服务信息
fetch-registry: false
# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
#修改7002主机yml文件
server:
port: 7002
eureka:
instance:
# eureka服务端的实例名字
hostname: eureka7002.com
client:
#表 示是否将自己注册到Eureka Server
register-with-eureka: false
# 表示是否从Eureka Server获取注册的服务信息
fetch-registry: false
# 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
每个配置文件最后一行,7001获取7002的配置,7002获取7001的配置,这样可以实现相互获取配置,达到高可用
我们在修改生产者8001下的配置文件,因为我们现在搭建了eureka的集群
8001下我们需要把两个注册中心都加入进去,用英文逗号进行拼接
80端口同理,需要都发布到集群中
然后我们需要修改映射配置
找到C:\Windows\System32\drivers\etc\hosts
#添加如下配置
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
修改后重启四个服务,浏览器用localhost:7001和 localhost:7002都行,如下图,成功
3.负载均衡
俗话说在生产队薅羊毛不能逮着一只羊薅,在微服务领域也是这个道理。面对一个庞大的微服务集群,如果你每次发起服务调用都只盯着那一两台服务器,在大用户访问量的情况下,这几台被薅羊毛的服务器一定会不堪重负
负载均衡要干什么事情
负载均衡有两类,服务端负载均衡和客户端负载均衡。
1.负载均衡分类
服务端负载均衡
在服务集群内设置一个中心化负载均衡器,比如Nginx。发起服务间调用的时候,服务请求并不直接发向目标服务器,而是发给这个全局负载均衡器,它再根据配置的负载均衡策略将请求转发到目标服务。
客户端负载均衡
Spring Cloud Loadbalancer 采用了客户端负载均衡技术,每个发起服务调用的客户端都存有完整的目标服务地址列表,根据配置的负载均衡策略,由客户端自己决定向哪台服务器发起调用。
由于Ribbon已经进入维护模式,并且Ribbon 2并不与Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons项目中,添加了Spring cloud Loadbalancer作为新的负载均衡器,并且做了向前兼容,就算你的项目中继续用 Spring Cloud Netflix 套装(包括Ribbon,Eureka,Zuul,Hystrix等等)让你的项目中有这些依赖,你也可以通过简单的配置,把Ribbon替换成Spring Cloud LoadBalancer。
2.负载均衡策略
springcloud loadbalancer只提供两种
- RandomLoadBalancer 随机
- RoundRobinLoadBalancer 轮询
二、服务调用
1.OpenFeign
Spring Cloud OpenFeign用于Spring Boot应用程序的声明式REST客户端。
我们之前用的是resttemplate,在实际开发过程中,我们很少使用restTemplate,于是,我们为了效率和可读性,引用openfeign
简而言之,OpenFeign是帮我们完成服务调用,也就是接口+注解的方式完成调用
Feign旨在使编写Java Http客户端变得更容易。前面在使用RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。
2.替代restTemplate
在父项目下创建maven工程,下面的流程跟之前的80端口流程一样,目录结构也一样,文件夹命名为cloud-consumer-openfeign-order80
在openFeign80下pom文件引入依赖
<dependencies>
<!-- 引入Eureka client依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入OpenFeign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
写openFeign80的主启动类
/**
* 主启动类
*/
@Slf4j
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
log.info("************** OrderFeignMain80 服务启动成功 **********");
}
}
写yml文件
eureka:
client:
# 表示是否将自己注册到Eureka Server
register-with-eureka: true
# 示是否从Eureka Server获取注册的服务信息
fetch-registry: true
# Eureka Server地址
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: cloud-openfeign-order-consumer
prefer-ip-address: true
spring:
application:
# 设置应用名词
name: cloud-openfeign-order-consumer
server:
port: 80
然后创建控制层和服务层,文件目录格式如下
OrderController代码
/**
* 订单控制层
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private PaymentFeignService paymentFeignService;
/**
* 测试OpenFeign接口调用
* @return
*/
@GetMapping("/index")
public String get(){
return paymentFeignService.index();
}
}
PaymentFeignService代码,注意 PaymentFeignService是接口
/**
* 支付远程调用Feign接口
*/
@Component
@FeignClient(value = "cloud-payment-provider")
public interface PaymentFeignService {
@GetMapping("/payment/index")
String index();
}
这里我们是填写调用的生产者的名字,下面是生产者接口的路由,对应的如下图
然后我们重启服务验证一下,把之前的80端口关掉,换成新的openfeign的80,浏览器搜索http://localhost/order/index,如下图OK
3.日志增强
OpenFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。
OpenFeign的日志级别如下:
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
创建config文件夹,然后创建实体类
然后设置目录级别,在OpenFeignConfig类下写, log包是feign下的log
@Configuration
public class OpenFeignConfig{
/**
* 日志级别定义
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
在feign80下yml文件加入配置,这里面的路径要对应自己建的文件夹路径
logging:
level:
com.consumer.service: debug
然后重启feign80,浏览器打开http://localhost/order/index
idea控制台出现如下,日志增强启用成功了
4.超时机制
超时机制是保护服务的一种手段
问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
- 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
为了解决这些问题,超时机制来了!
在openfeign80的yml文件加入如下配置
# 默认超时时间
feign:
client:
config:
default:
# 连接超时时间
connectTimeout: 2000
# 读取超时时间
readTimeout: 2000
然后我们在生产者的控制层写一个超时的接口
/**
* 测试超时机制
*
* @return
*/
@GetMapping("timeout")
public String paymentFeignTimeOut() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "payment success";
}
服务者80添加超时方法PaymentFeignService
@GetMapping("/payment/timeout")
String timeout();
服务者80添加超时方法OrderController,来测试超时机制
/**
* 测试超时机制
* @return
*/
@GetMapping("timeout")
public String timeout() {
return paymentFeignService.timeout();
}
然后我们把修改的后台服务重启,浏览器搜索http://localhost/order/timeout
浏览器会报错,我们的idea控制台如下,抛出异常
三、服务断路器(熔断器)
1.服务熔断器_雪崩效应解决方案
服务熔断
熔断就跟保险丝一样,当一个服务请求并发特别大,服务器已经招架不住了,调用错误率飙升,当错误率达到一定阈值后,就将这个服务熔断了。熔断之后,后续的请求就不会再请求服务器了,以减缓服务器的压力。
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
服务降级
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
两种场景:
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
概念:服务器繁忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。
出现服务降级的情况:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务隔离
服务限流
服务熔断和服务隔离都属于出错后的容错处理机制,而限流模式则可以称为预防模式。
限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。
流量控制
- 网关限流:防止大量请求进入系统,Mq实现流量消峰
- 用户交流限流:提交按钮限制点击频率限制等
2.服务熔断器_Resilience4j
我们耳熟能详的就是Netflix Hystrix,这个断路器是SpringCloud中最早支持的一种容错方案,现在这个断路器已经处于维护状态,已经不再更新了,你仍然可以使用这个断路器,但是呢,我不建议你去使用,因为这个已经不再更新,所以Spring官方已经出现了Netflix Hystrix的替换方案。
什么是Resilience4j?
Resilience4j是一个轻量级的容错组件,其灵感来自于Hystrix,但主要为Java 8和函数式编程所设计,也就是我们的lambda表达式。轻量级体现在其只用 Vavr 库(前身是 Javaslang),没有任何外部依赖。而Hystrix依赖了Archaius ,Archaius本身又依赖很多第三方包,例如 Guava、Apache Commons Configuration 等。
Resilience4J 提供了一系列增强微服务的可用性功能:
- resilience4j-circuitbreaker:熔断
- resilience4j-ratelimiter:限流
- resilience4j-bulkhead:隔离
- resilience4j-retry:自动重试
- resilience4j-cache:结果缓存
- resilience4j-timelimiter:超时处理
3.服务熔断器_Resilience4j的断路器之超时降级
在消费者80(后续所有消费者都指的是openfeign的80模块)下增加pom依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-cloud2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
80下yml文件增加超时机制,我们测试一下熔断器的超时机制,timeoutDuration设置两秒钟返回数据
# 超时机制
resilience4j:
timelimiter:
instances:
delay:
# 设置超时时间 5秒
timeoutDuration: 2
我们将80下之前调用timeout接口的方法注掉
然后加入如下代码,需要引入日志注解
@GetMapping("/timeout")
@TimeLimiter(name = "delay",fallbackMethod = "timeoutfallback")
public CompletableFuture<String> timeout() {
log.info("********* 进入方法 ******");
//异步操作
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync((Supplier<String>) () -> (paymentFeignService.timeout()));
log.info("********* 离开方法 ******");
return completableFuture;
}
在这段代码的注解@timelimiter中,有一个reply,这个reply(命名)需要跟yml文件中一致
,这个名字是可以更改的,自定义的
然后再80端口的controller再加入如下代码,用于超时返回
/**
* 超时服务降级方法
* @param e
* @return
*/
public CompletableFuture<ResponseEntity> timeoutfallback(Exception e){
e.printStackTrace();
return CompletableFuture.completedFuture(ResponseEntity.ok("超时啦"));
}
如下图,成功了
4.服务熔断器_Resilience4j的断路器之重试机制
重试机制比较简单,当服务端处理客户端请求异常时,服务端将会开启重试机制,重试期间内,服务端将每隔一段时间重试业务逻辑处理。 如果最大重试次数内成功处理业务,则停止重试,视为处理成功。如果在最大重试次数内处理业务逻辑依然异常,则此时系统将拒绝该请求。
在消费者80的yml增加配置,原超时降级机制不用注掉
resilience4j:
retry:
instances:
backendA:
# 最大重试次数
maxRetryAttempts: 3
# 固定的重试间隔
waitDuration: 10s
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
80的controller加入如下代码,@retry注解后的机制名和yml文件相同,自定义即可
/**
* 重试机制
* @return
*/
@GetMapping("/retry")
@Retry(name = "backendA")
public CompletableFuture<String> retry() {
log.info("********* 进入方法 ******");
//异步操作
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync((Supplier<String>) () -> (paymentFeignService.index()));
log.info("********* 离开方法 ******");
return completableFuture;
}
然后我们重启服务,进行压力测试,我们自行下载Apache JMeter,使用教程网上都有,然后修改为中文(我英语比较菜- -!)
我们先创建线程组-HTTP请求&查看结果树,修改线程数为10,同时十个请求
修改后点击上方的三角号,我们现在都请求成功了
我们关掉8001的生产者,在重新请求,如图
(半道换了个主题,md,无法保存,不让测,垃圾工具有bug!!!!)
我们后台会看到一直在请求,往下拉,一直在重试,这个重试机制就成功了
5.服务熔断器_Resilience4j的断路器之异常熔断
80控制层写入
/**
* 异常比例熔断降级
* @return
*/
@GetMapping("/citcuitBackend")
@CircuitBreaker(name = "backendA")
public String citcuitBackend(){
log.info("************ 进入方法 ***********");
String index = paymentFeignService.index();
log.info("************ 离开方法 ***********");
return index;
}
80yml文件写入,具体细节都在yml文件中写了,然后我们重启,此时我们8001是关闭的
# 熔断降级
resilience4j.circuitbreaker:
configs:
default:
# 熔断器打开的失败阈值
failureRateThreshold: 30
# 默认滑动窗口大小,circuitbreaker使用基于计数和时间范围欢动窗口聚合统计失败率
slidingWindowSize: 10
# 计算比率的最小值,和滑动窗口大小去最小值,即当请求发生5次才会计算失败率
minimumNumberOfCalls: 5
# 滑动窗口类型,默认为基于计数的滑动窗口
slidingWindowType: TIME_BASED
# 半开状态允许的请求数
permittedNumberOfCallsInHalfOpenState: 3
# 是否自动从打开到半开
automaticTransitionFromOpenToHalfOpenEnabled: true
# 熔断器从打开到半开需要的时间
waitDurationInOpenState: 2s
recordExceptions:
- java.lang.Exception
instances:
backendA:
baseConfig: default
然后我们进行压力测试,我们控制台是这样,请求进来了,但是没有返回结果
然后我们将8001打开,修改请求地址,再重新测试
我们只进来了三个请求,然后会切换到半开启状态
6.服务熔断器_Resilience4j的断路器之慢调用熔断降级
在消费者80的yml中,resilience4j.circuitbreaker最后面加入配置
backendB:
# 熔断器打开的失败阈值
failureRateThreshold: 50
# 慢调用时间阈值 高于这个阈值的
slowCallDurationThreshold: 2s
# 慢调用百分比阈值,断路器吧调用事件大于slow
slowCallRateThreshold: 30
slidingWindowSize: 10
slidingWindowType: TIME_BASED
minimumNumberOfCalls: 2
permittedNumberOfCallsInHalfOpenState: 2
waitDurationInOpenState: 2s
eventConsumerBufferSize: 10
我们跟之前的配置大部分都一样,只有这两个地方不同
意思是2s后打开熔断降级,超过30%就打开断路降级
我们写控制层来验证一下
80的控制层写入
/**
* 慢调用熔断降级
* @return
*/
public String slowcircuitbreakerfallback(Exception e){
e.printStackTrace();
return "太慢了。。。。。。。。。。。";
}
/**
* 慢调用比例熔断降级
* @return
*/
@GetMapping("/slowcircuitbackend")
@CircuitBreaker(name = "backendB",fallbackMethod = "slowcircuitbreakerfallback")
public String slowcircuitbackend(){
log.info("************ 进入方法 ***********");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
String index = paymentFeignService.index();
log.info("************ 离开方法 ***********");
return index;
}
然后我们修改一下8001的index方法,不干点坏事,测不了!!!让他睡十秒钟!
@GetMapping("/index")
public String index() {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "payment + successs";
}
jmeter我们测试一下,修改一下接口路径
我们返回结果如下
![](https://img-blog.csdnimg.cn/9ff1cb664323495f96a95e03218a314e.png)
7.服务断路器_Resilience4j信号量隔离实现
信号量隔离与其他不同,需要引入隔离依赖,我们在80的pom文件中加入依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
<version>1.7.0</version>
</dependency>
我们打开maven,之前引入的断路器版本是1.7.0,所以我们信号量隔离最好也要引入这个版本
我们在80yml下加入配置,要加入到断路器中,不要放到熔断里
# 信号量隔离
bulkhead:
instances:
backendA:
# 隔离允许并发线程执行的最大数量
maxConcurrentCalls: 5
# 当达到并发调用数量时,新的线程的阻塞时间
maxWaitDuration: 20ms
我们接着在80的控制层测,加入代码,重启项目,jmeter修改路径
/**
* 测试信号量隔离
* @return
*/
@Bulkhead(name = "backendA",type = Bulkhead.Type.SEMAPHORE)
@GetMapping("bulkhead")
public String bulkhead() throws InterruptedException {
log.info("************** 进入方法 *******");
TimeUnit.SECONDS.sleep(10);
String index = paymentFeignService.index();
log.info("************** 离开方法 *******");
return index;
}
我们后台控制台能看到,只进来五个请求
jmeter如下,我们的后台控制台有报错,意思是已经达到最大线程数,所以就不允许访问了。
8.服务断路器_Resilience4j的线程池服务隔离
80yml文件熔断器下加入配置
# 线程池服务隔离
thread-pool-bulkhead:
instances:
backendA:
# 最大线程池大小
maxThreadPoolSize: 4
# 核心线程池大小
coreThreadPoolSize: 2
# 队列容量
queueCapacity: 2
80控制层写入,然后重启,修改jmeter路径
/**
* 测试线程池服务隔离
* @return
*/
@Bulkhead(name = "backendA",type = Bulkhead.Type.THREADPOOL)
@GetMapping("/futrue")
public CompletableFuture future(){
log.info("********** 进入方法 *******");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("********** 离开方法 *******");
return CompletableFuture.supplyAsync(() -> "线程池隔离信息......");
}
控制台
具体哪种隔离我们可以自行选择,都可以,只需要通过注解配置即可
9.服务断路器_Resilience4j限流
80yml文件熔断器下加入配置
# 限流
ratelimiter:
instances:
backendA:
# 限流周期时长。 默认:500纳秒
limitRefreshPeriod: 5s
# 周期内允许通过的请求数量。 默认:50
limitForPeriod: 2
80控制层写方法,都采用异步调用
/**
* 限流
* @return
*/
@GetMapping("/limiter")
@RateLimiter(name = "backendA")
public CompletableFuture<String> RateLimiter() {
log.info("********* 进入方法 ******");
//异步操作
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync((Supplier<String>) () -> (paymentFeignService.index()));
log.info("********* 离开方法 ******");
return completableFuture;
}
然后重启,我们观察jmeter,每5s通过两个,就OK
四、服务网关Gateway
1.服务网关Gateway_三大核心概念
路由
这是网关的基本构建块。它由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
断言
输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。
过滤
可以在请求被路由前或者之后对请求进行修改。
总结
首先任何请求进来,网关都会把它们拦住。根据请求的URL把它们分配到不同的路由上,路由上面会有断言,来判断请求能不能进来。进来之后会有一系列的过滤器对请求被转发前或转发后进行改动。 具体怎么个改动法,那就根据业务不同而自定义了。一般就是监控,限流,日志输出等等
2.服务网关Gateway_入门
我们在父项目下创建个maven项目,cloud-gateway-gateway9527
在9527的pom文件中引入依赖
<dependencies>
<!-- 引入网关Gateway依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
在9527下创建主启动类GatewayMain9527,在resources下创建application.yml,文件层级如下
在9527主启动类写入
@Slf4j
@EnableEurekaClient
@SpringBootApplication
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
log.info("********** GatewayMain 服务启动成功 *********");
}
}
9527下yml文件加入配置,注意yml文件配置中格式层级
server:
port: 9527
spring:
cloud:
gateway:
routes:
# 路由ID,没有固定规则但要求唯一,建议配合服务名
- id: payment_provider
# 匹配后提供服务的路由地址
uri: http://localhost:80
# 断言
predicates:
# 路径相匹配的进行路由
- Path=/order/*
predicates中是我们本地文件的路径,如果请求这个路径中有这个服务地址,断言返回的是布尔类型值,返回true
--链路追踪后续单独会写,ok