SpringCloud
什么是微服务架构
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务和服务之间采用轻量级的通信机制相互沟通(通常是基于HTTP的Restful API).每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构" ---- Martin Flower
简单的来说,微服务是将原来一体化架构拆分,微服务是每个模块由单个程序组成,能够方便迭代,以及机构专业化,数据层只提供数据,个性化只排序,上层组织数据返回给客户端,微服务能实现横向以及纵向拆分。并且可以每个模块根据流量进行扩展,流量小的可以减少计算资源投入,并且可以根据计算密集型,内存消耗性,io型进行不同处理。
RestTemplate
RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
官网地址:docs.spring.io/spring-fram…
使用方法:(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
/**
* Created by KingsLanding on 2022/10/20 17:43
*/
@RestController
@Slf4j
public class ConsumerController {
public static final String ORDER_TEST_URL="http://SPRINGCLOUD-ORDER-SERVICE";
@Value("${server.port}")
private String Service_port;
@Resource
RestTemplate restTemplate;
@GetMapping("/consumer/get/{id}")
public CommonResult Query(@PathVariable("id") Integer id){
CommonResult forObject = restTemplate.getForObject(ORDER_TEST_URL + "/select/" + id, CommonResult.class, id);
log.info("端口号"+Service_port);
return forObject;
}
@GetMapping("/consumer/create")//客户端用浏览器是get请求,但是底层实质发送post调用服务端8001
public CommonResult Insert(User user){
CommonResult result = restTemplate.postForObject(ORDER_TEST_URL + "/insert", user, CommonResult.class);
return result;
}
}
服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
Eureka
-
Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。
-
这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
-
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。
-
另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
Eureka包含两个组件
-
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
-
Eureka Client通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
搭建Eureka服务注册中心7001
pom.xml
核心依赖
<!--引入eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
全部
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!--引入eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置文件
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
主启动 @EnableEurekaServer
/**
* Created by KingsLanding on 2022/10/21 0:39
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
服务注册8001(服务提供者)
核心依赖
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
server:
port: 8001
spring:
application:
name: SpringCloud-order-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 com.mysql.jdbc.Driver
# driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbctest?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: zmj.666.999
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.springcloud # 所有别名类所在包
configuration:
map-underscore-to-camel-case: true #将下划线映射为驼峰原则
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, #http://eureka7002.com:7002/eureka/ #集群:同时绑定两个Eureka
instance:
instance-id: order8001
prefer-ip-address: true #访问路径可以显示IP地址
主启动类 @EnableEurekaClient
/**
* Created by KingsLanding on 2022/10/19 17:56
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderTest8001 {
public static void main(String[] args) {
SpringApplication.run(OrderTest8001.class,args);
}
}
业务方法
/**
* Created by KingsLanding on 2022/10/20 0:56
*/
@RestController
@Slf4j
public class UserController {
@Resource
UserServiceImpl userServiceImpl;
@Value("${server.port}")
private String Service_port;
@PostMapping("/insert")
public CommonResult InsertIntoUser(@RequestBody User user){//前端传过来的数据是json格式的,必须要加`@RequestBody`注解
int insertId = userServiceImpl.Insert(user);
if (insertId>0){
return new CommonResult(200,"插入数据库成功"+"通过端口号 "+Service_port,insertId);
}else {
return new CommonResult(444,"插入失败"+"通过端口号 "+Service_port,null);
}
}
@GetMapping("/select/{id}")
public CommonResult QueryUserById(@PathVariable("id") Integer id){
User user = userServiceImpl.QueryUserById(id);
log.info("查询结果"+user);
if (user!=null){
return new CommonResult(200,"查询成功"+"通过端口号 "+Service_port,user);
}else {
return new CommonResult(444,"查询失败:失败id"+id,null);
}
}
//超时设置,验证OpenFeign超时控制;OpenFeign默认等待1秒钟,超过后报错
@GetMapping(value = "/order/feign/timeout")
public String paymentFeignTimeOut(){
System.out.println("*****paymentFeignTimeOut from port: "+Service_port);
//暂停3秒钟线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Service_port;
}
}
服务注册80(服务消费方)
核心依赖
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
server:
port: 80
spring:
application:
name: SpringCloud-consumer-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
主启动类 @EnableEurekaClient
/**
* Created by KingsLanding on 2022/10/20 17:41
*/
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如:
//name:代表可供选择的服务提供方
@RibbonClient(name = "SPRINGCLOUD-ORDER-SERVICE",configuration= RibbonIRuleConfig.class)
public class ConsumerTest80 {
public static void main(String[] args) {
SpringApplication.run(ConsumerTest80.class,args);
}
}
@LoadBalanced
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
/**
* Created by KingsLanding on 2022/10/20 17:44
*/
@Configuration
public class ConsumerConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
业务类
/**
* Created by KingsLanding on 2022/10/20 17:43
*/
@RestController
@Slf4j
public class ConsumerController {
public static final String ORDER_TEST_URL="http://SPRINGCLOUD-ORDER-SERVICE";
@Value("${server.port}")
private String Service_port;
@Resource
RestTemplate restTemplate;
@GetMapping("/consumer/get/{id}")
public CommonResult Query(@PathVariable("id") Integer id){
CommonResult forObject = restTemplate.getForObject(ORDER_TEST_URL + "/select/" + id, CommonResult.class, id);
log.info("端口号"+Service_port);
return forObject;
}
@GetMapping("/consumer/create")//客户端用浏览器是get请求,但是底层实质发送post调用服务端8001
public CommonResult Insert(User user){
CommonResult result = restTemplate.postForObject(ORDER_TEST_URL + "/insert", user, CommonResult.class);
return result;
}
}
Eureka集群
搭建Eureka注册中心集群
实现负载均衡+故障容错
- 修改映射配置:找到C:\Windows\System32\drivers\etc路径下的hosts文件 修改映射配置添加进hosts文件
新建一个cloud-eureka-server7002,与7001完全一样,分别将对方注册到Eureka服务器中
修改7001配置文件
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
修改7002配置文件
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
集群下服务的注册
添加多个Eureka服务地址,使用,区分
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
8001消息提供方
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群:同时绑定两个Eureka
instance:
instance-id: order8001
prefer-ip-address: true #访问路径可以显示IP地址
80消息消费方
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
服务名称修改
instance:
instance-id: order8001
服务发现
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
主启动
@EnableDiscoveryClient //服务发现
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println(element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance element : instances) {
System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
+ element.getUri());
}
return this.discoveryClient;
}
Eureka自我保护机制
-
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
-
属于CAP里面的AP分支
-
出厂默认,自我保护机制是开启的
eureka.server.enable-self-preservation=true
修改心跳时间
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
instance:
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
Consul
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows
官方文档:www.springcloud.cc/spring-clou…
安装说明:learn.hashicorp.com/consul/gett…
通过以下地址可以访问Consul的首页:http://localhost:8500
核心依赖
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
配置文件
server:
port: 8005
spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
主启动类
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
/**
* Created by KingsLanding on 2022/10/23 21:36
*/
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class OrderTestConsul8005 {
public static void main(String[] args) {
SpringApplication.run(OrderTestConsul8005.class,args);
}
}
业务类
/**
* Created by KingsLanding on 2022/10/23 21:38
*/
@RestController
@Slf4j
public class ConsulTestController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/consul")
public String paymentInfo()
{
return "springcloud with consul: "+serverPort+"\t\t"+ UUID.randomUUID().toString();
}
}
CAP
- C:Consistency(强一致性)
- A:Availability(可用性)
- P:Partition tolerance(分区容错性)
CAP理论关注粒度是数据,而不是整体系统设计的策略
AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
负载均衡服务调用
Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
getForObject方法
返回对象为响应体中数据转化成的对象,基本上可以理解为Json
getForEntity方法
返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
Ribbon核心组件IRule
轮询:
RoundRobinRule();
随机:
RandomRule();
对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
return new WeightedResponseTimeRule();
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
BestAvailableRule();
先过滤掉故障实例,再选择并发较小的实例
AvailabilityFilteringRule();
默认规则,复合判断server所在区域的性能和server的可用性选择服务器
ZoneAvoidanceRule();
/**
* Created by KingsLanding on 2022/10/24 0:47
*
*
* 官方文档明确给出了警告:
* 这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
* 否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
*/
@Configuration
public class RibbonIRuleConfig {
@Bean
public IRule MyIRule(){
return new RandomRule();//定义服务选取方式为随机
/*
RoundRobinRule();//轮询
RandomRule();//定义服务选取方式为随机
return new WeightedResponseTimeRule();//对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule();//会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabilityFilteringRule();//先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule();//默认规则,复合判断server所在区域的性能和server的可用性选择服务器
*/
}
}
注意:官方文档明确给出了警告:
- 这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
- 否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
主启动类
@RibbonClient(name = "SPRINGCLOUD-ORDER-SERVICE",configuration= RibbonIRuleConfig.class)
在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如: name:代表可供选择的服务提供方
/**
* Created by KingsLanding on 2022/10/20 17:41
*/
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如:
//name:代表可供选择的服务提供方
@RibbonClient(name = "SPRINGCLOUD-ORDER-SERVICE",configuration= RibbonIRuleConfig.class)
public class ConsumerTest80 {
public static void main(String[] args) {
SpringApplication.run(ConsumerTest80.class,args);
}
}
Ribbon负载均衡算法
- rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
OpenFeign
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
核心依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
配置文件
server:
port: 80
spring:
application:
name: SpringCloud-consumer-Feign
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
主启动类 @EnableFeignClients //使用feign实现服务调用,替换restTemplate,减轻开发工作
/**
* Created by KingsLanding on 2022/10/24 21:07
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients //使用feign实现服务调用,替换restTemplate,减轻开发工作
@EnableHystrix //激活Hystrix功能
public class ConsumerFeignTest80 {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignTest80.class,args);
}
}
Feign接口 @FeignClient注解
/**
* Created by KingsLanding on 2022/10/24 21:20
*/
@Service
@FeignClient(value = "SPRINGCLOUD-ORDER-SERVICE")//配置调用provider服务,服务提供方8001端口
public interface ConsumerFeignService {
@GetMapping("/select/{id}")//服务提供方地址
public CommonResult Query(@PathVariable("id") Integer id);
}
业务类
@Resource
private ConsumerFeignService consumerFeignService;
@GetMapping("/consumer/feign/{id}")
public CommonResult Query(@PathVariable("id") Integer id){
return consumerFeignService.Query(id);
}
OpenFeign超时控制
- OpenFeign默认等待1秒钟,超过后报错
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。 为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
#设置feign客户端超时时间
feign:
client:
config:
default:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
connectTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间(默认1秒)
readTimeout: 5000
YML文件里需要开启OpenFeign客户端超时控制
这里的openFeign和ribbon以及hystix的超时时间还要研究研究
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
OpenFeign默认支持Ribbon
OpenFeign日志打印功能
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。 说白了就是对Feign接口的调用情况进行监控和输出
日志级别
/**
* Created by KingsLanding on 2022/10/24 22:55
*/
@Configuration
public class FeignConfig {
/*
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
YML文件里需要开启日志的Feign客户端
logging:
level:
com.springcloud.feignService.ConsumerFeignService: debug
# feign日志以什么级别监控哪个接口
服务降级与熔断
Hystrix(维护状态)
分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
核心依赖
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件
#feign自动导入了hystrix,不用额外导入,同时微服务作为消费方,OpenFeign想要使用Hystrix熔断机制,需要在yml文件中开启
#上面设置了feign连接超时的处理时间,似乎和hystrix的服务降级机制有冲突,不能同时开启;问题在于超时时间的设置问题(待解决)
hystrix:
enabled: true
@HystrixCommand
- 一旦调用服务方法失败并抛出了错误信息后,会自动调用
@HystrixCommand标注好的fallbackMethod调用类中的指定方法 - 全局fallback:
@DefaultProperties(defaultFallback = "Global_FallbackMethod")
/**
* Created by KingsLanding on 2022/10/30 0:37
*/
@RestController
@DefaultProperties(defaultFallback = "Global_FallbackMethod")
public class ConsumerHystrixController {
@Resource
private ConsumerHystrixService consumerHystrixService;
@GetMapping("/consumer/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
// value="5000")})
// @HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的;同时因为设置了接口解耦
//当调用服务端方法出现问题时为了不走客户端的降级,要将这个注解去掉
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(10/0);//目前存在超时无论如何都会fallback的问题,此为测试无超时问题的其他错误问题fallback
String result = consumerHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
//全局fallback
public String Global_FallbackMethod(){
return "全局fallback,我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
} value="5000")})
@EnableCircuitBreaker
主启动类激活
/**
* Created by KingsLanding on 2022/10/26 0:33
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker//激活Hystrix处理服务降级\熔断功能
public class OrderTestHystrix8003 {
public static void main(String[] args) {
SpringApplication.run(OrderTestHystrix8003.class,args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
这么写混合在一块 ,每个业务方法都要提供一个fallback方法?
显然不行
结合feign实现服务降级
配置文件
feign:
hystrix:
enabled: true #在Feign中开启Hystrix
OpenFeign接口
fallback = ConsumerHystrixHandler.class;降级方法类
/**
* Created by KingsLanding on 2022/10/30 0:41
*/
@Service
@FeignClient(value = "CLOUD-ORDER-HYSTRIX",fallback = ConsumerHystrixHandler.class)//对应8003端口
public interface ConsumerHystrixService {
@GetMapping("/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
异常处理的类必须实现OpenFeign接口
/**
* Created by KingsLanding on 2022/10/30 18:54
*/
@Component
public class ConsumerHystrixHandler implements ConsumerHystrixService {
@Override
public String paymentInfo_TimeOut(Integer id) {
return "全局ConsumerHystrixHandler,服务调用失败";
}
}
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
//=========服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();//hutool工具包
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
服务的降级->进而熔断->恢复调用链路
多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行
熔断类型
- 熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
- 熔断关闭
熔断关闭不会对服务进行熔断
- 熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
断路器在什么情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。 1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
-
当满足一定的阀值的时候(默认10秒内超过20个请求次数)
-
当失败率达到一定的时候(默认10秒内超过50%的请求失败)
-
到达以上阀值,断路器将会开启
-
当开启的时候,所有请求都不会进行转发
-
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。 如果成功,断路器会关闭,若失败,继续开启。重复4和5
断路器打开之后
1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2:原来的主逻辑要如何恢复呢? 对于这一问题,hystrix也为我们实现了自动恢复功能。 当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑, 当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合, 主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
所有的熔断规则
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
目前主要采用alibaba的sentinel技术
服务监控hystrixDashboard
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
构建服务监控9001
核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
配置文件
server:
port: 9001
主启动类 @EnableHystrixDashboard
/**
* Created by KingsLanding on 2022/10/31 1:23
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard9001.class,args);
}
}
所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
被监控方8003注意事项:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
/**
* Created by KingsLanding on 2022/10/26 0:33
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker//激活Hystrix处理服务降级\熔断功能
public class OrderTestHystrix8003 {
public static void main(String[] args) {
SpringApplication.run(OrderTestHystrix8003.class,args);
}
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
Gateway新一代网关
-
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等
-
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
-
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
-
Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway 特性:
- 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定 Predicate(断言)和 Filter(过滤器);
- 集成Hystrix的断路器功能;
- 集成 Spring Cloud 服务发现功能;
- 易于编写的 Predicate(断言)和 Filter(过滤器);
- 请求限流功能;
- 支持路径重写。
WebFlux是什么
传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。 但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)
Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
三大核心概念
Route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
Predicate(断言)
参考的是Java8的java.util.function.Predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
简单来说
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。 predicate就是我们的匹配条件;
而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
官网基本逻辑
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等, 在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
构建Gateway网关9527
核心依赖
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
主启动类
/**
* Created by KingsLanding on 2022/10/31 16:55
*/
@SpringBootApplication
@EnableEurekaClient
public class Gateway9527 {
public static void main(String[] args) {
SpringApplication.run(Gateway9527.class,args);
}
}
网关配置
方式一
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
方式二
/**
* Created by KingsLanding on 2022/10/31 17:06
*/
@Configuration
public class GatewayRouteLocatorConfig {
/**
* 配置了一个id为route-name的路由规则,
* 当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
* @param builder
* @return
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_cloud", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_cloud2", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
通过微服务名实现动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
#默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
discovery:
locator:
enabled: true
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8003 #匹配后提供服务的路由地址
#lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri 默认轮询
uri: lb://SPRINGCLOUD-ORDER-SERVICE #8001/8002
predicates:
# - Path=/hystrix/timeout/** # 断言,路径相匹配的进行路由 8003
- Path=/select/** #8001/8002
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8003 #匹配后提供服务的路由地址
uri: lb://SPRINGCLOUD-ORDER-SERVICE
predicates:
# - Path=/payment/circuit/** # 断言,路径相匹配的进行路由8003
- Path=/order/feign/timeout #8001/8002
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
Predicate的使用
predicates:
- Path=/payment/circuit/** # 断言,路径相匹配的进行路由
- After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
- Cookie=username,zzyy
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.atguigu.com
- Method=GET
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
Filter的使用
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
自定义过滤器
统一网关鉴权
/**
* Created by KingsLanding on 2022/10/31 23:34
*
* 自定义gateway网关过滤器,设置请求的放行条件
* 全局日志记录;统一网关鉴权
* ServerWebExchange:可以获取到servlet的请求内容
* GatewayFilterChain:过滤链放行
*/
@Component
@Slf4j
public class MyGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("time:"+new Date()+"\t 执行了自定义的全局过滤器: "+"MyLogGateWayFilter"+"hello");
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
log.info("用户名为null,非法访问!!!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
/**
测试:http://localhost:9527/payment/lb?uname=z3 正确
http://localhost:9527/payment/lb 错误
*/
Config分布式配置中心
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
SpringCloud Config分为服务端和客户端两部分。
-
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
-
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
官网介绍:cloud.spring.io/spring-clou…
构建Config服务端模块3344
核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
配置文件
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/KingsLanding71/SpringCloud-configServer.git #GitHub上面的git仓库名字
####搜索目录
search-paths:
- SpringCloud-configServer
####读取分支
label: main
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
主启动类 @EnableConfigServer
/**
* Created by KingsLanding on 2022/11/1 14:59
*/
@SpringBootApplication
@EnableConfigServer
public class SpringConfigServer3344 {
public static void main(String[] args) {
SpringApplication.run(SpringConfigServer3344.class,args);
}
}
构建Config客户端模块3355
核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
配置文件bootstrap.yml
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:main分支上config-dev.yml的配置文件被读取http://localhost:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
bootstrap.yml
bootstrap.yml是系统级的,优先级更加高
-
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的
Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。 -
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context和Application Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。 -
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的, 因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml
主启动
/**
* Created by KingsLanding on 2022/11/1 16:16
*/
@SpringBootApplication
@EnableEurekaClient
public class SpringConfigClient3355 {
public static void main(String[] args) {
SpringApplication.run(SpringConfigClient3355.class,args);
}
}
业务类
/**
* Created by KingsLanding on 2022/11/1 16:25
*
* 手动刷新
*/
@RestController
public class ConfigGetController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo()
{
return configInfo;
}
}
分布式配置的动态刷新问题
配置文件发生修改,Config服务端(配置中心)是即使更改的,因为再次读取其会重新从远程仓库读取
而Config客户端不能即使更改,因为其是从服务端的某个域里读取,而服务端的域需要更新
手动刷新
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
暴露监控端口
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
@RefreshScope
业务类手动刷新注解
/**
* Created by KingsLanding on 2022/11/1 16:25
*
* 手动刷新
*/
@RestController
@RefreshScope//刷新3355
public class ConfigGetController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo()
{
return configInfo;
}
}
发送Post请求刷新3355
curl -X POST "http://localhost:3355/actuator/refresh"
客户端才能随配置中心动态更改
存在的问题,多个配置中心的更改,需要多次发送post请求,采用SpringCloud Bus消息总线进行广播发送,一次发送,处处生效。
SpringCloud Bus消息总线
Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。
Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。
Spring Clud Bus目前支持RabbitMQ和Kafka。
-
什么是总线 在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
-
基本原理 ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。
构建消息Bus客户端3306
搭建RabbitMQ环境省略
配置文件
server:
port: 3366
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#rabbitmq相关配置
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
主启动类
/**
* Created by KingsLanding on 2022/11/2 14:17
*/
@SpringBootApplication
@EnableEurekaClient
public class SpringConfigBus3366 {
public static void main(String[] args) {
SpringApplication.run(SpringConfigBus3366.class,args);
}
}
业务类
/**
* Created by KingsLanding on 2022/11/2 14:19
* 消息总线bus:结合RabbitMQ动态刷新
*/
@RestController
@RefreshScope//刷新
public class ConfigGetController {
@Value("${server.port}")
private String serverPort;
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String configInfo()
{
return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
}
}
修改3344服务端-->Bus
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
配置文件
- rabbitmq相关配置
- rabbitmq相关配置,暴露bus刷新配置的端点
bus-refresh
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/KingsLanding71/SpringCloud-configServer.git #GitHub上面的git仓库名字
####搜索目录
search-paths:
- SpringCloud-configServer
####读取分支
label: main
#rabbitmq相关配置
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'
修改3355客户端-->Bus
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
配置文件
- rabbitmq相关配置
- 暴露监控端点
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: main #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:main分支上config-dev.yml的配置文件被读取http://localhost:3344/main/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
#rabbitmq相关配置
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
效果测试
此时就达到了一次发送,处处生效的效果
发送一次curl -X POST "http://localhost:3344/actuator/bus-refresh"所有的客户端获取的数据随之改变
SpringCloud Bus动态刷新定点通知
指定具体某一个实例生效而不是全部
公式:http://localhost:{server.port}/actuator/bus-refresh/{destination}
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
例如:发送:curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"将只通知3355进行刷新
SpringCloud Stream消息驱动
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
官网介绍:cloud.spring.io/spring-clou…
Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念
为什么用Cloud Stream
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区,这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。
stream凭什么可以统一底层差异?
在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,
- 由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
- 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
- 通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。
构建Stream生产者案例8801
核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
配置文件
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
主启动类
/**
* Created by KingsLanding on 2022/11/2 18:09
*/
@SpringBootApplication
@EnableEurekaClient
public class CloudStreamMQ_Provider8801 {
public static void main(String[] args) {
SpringApplication.run(CloudStreamMQ_Provider8801.class,args);
}
}
消息发送接口
/**
* Created by KingsLanding on 2022/11/2 18:11
*/
public interface IMessageProvider {
/**
* 消息发送
* @return
*/
public String send() ;
}
@EnableBinding(Source.class)
可以理解为是一个消息的发送管道的定义
消息发送实现类
/**
* Created by KingsLanding on 2022/11/2 18:12
*/
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class IMessageProviderImpl implements IMessageProvider{
@Resource
private MessageChannel output; // 消息的发送管道
@Override
public String send()
{
String serial = UUID.randomUUID().toString();
this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
System.out.println("***serial: "+serial);
return serial;
}
}
业务类
/**
* Created by KingsLanding on 2022/11/2 18:15
*/
@RestController
public class ProviderController {
@Resource
private IMessageProvider messageProvider;
@GetMapping(value = "/sendMessage")
public String sendMessage()
{
return messageProvider.send();
}
}
- 启动成功之后看到RabbitMQ中生成了studyExchange交换机
构建Stream消费者案例8802
核心依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
配置文件
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
@StreamListener(Sink.INPUT)
@EnableBinding(Sink.class)
业务类
- @EnableBinding(Sink.class)
- @StreamListener(Sink.INPUT)
/**
* Created by KingsLanding on 2022/11/2 18:50
*/
@RestController
@EnableBinding(Sink.class)
public class ConsumerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message)
{
System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
}
}
构建Stream消息消费者案例8003
同8002
运行后有两个问题(分组和持久化)
- 有重复消费问题
- 消息持久化问题
解决方案:分组和持久化属性group
- 实际上可以理解为,如果分组相同则只有一个队列,避免重复消费(默认轮询),
- 如果分组不同那么则按组数量构建同数量的队列同时分组属性的建立,同时相当于开启了消息的持久化
8802配置文件:group: zmjA
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: zmjA
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
8003配置文件
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.222.128
port: 5672
username: admin
password: 123123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: zmjA #实际上可以理解为,如果分组相同则只有一个队列,避免重复消费(默认轮询),
#如果分组不同那么则按组数量构建同数量的队列
#同时分组属性的建立,同时相当于开启了消息的持久化
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
SpringCloud Sleuth分布式请求链路跟踪
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路**,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。**
-
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
-
在分布式系统中提供追踪解决方案并且兼容支持了zipkin
Zipkin Server
SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可
下载:dl.bintray.com/openzipkin/…
运行:java -jar zipkin-server-2.12.9-exec.jar
图形化界面:http://localhost:9411/zipkin/
监控客户端
被监控者需要导入依赖
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
配置文件
#链路监控配置
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样率值介于 0 到 1 之间,1 则表示全部采集;一般是0.5
probability: 1