springcloud入门
- 1.简介
- 2.微服务cloud整体聚合父工程project
- 3.Eureka
- 4.Eurekea集群
- 5.Ribbon
- 6.OpenFeign
- 6.1 OpenFeign简介
- 6.2 Feign的作用
- 7.Hystrix
- 8. Getway
- 9.Config分布式配置中心
1.简介
1.1springcloud介绍
SpringCloud=分布式微服务构架的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶,是微服务开发的主流技术栈
1.2版本选择
-
Spring Boot 2.X 版
- 源码地址
- Spring Boot 2 的新特性
- 通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本
-
Spring Cloud H版
-
Spring Boot 与 Spring Cloud 兼容性查看
- 文档
- JSON接口
-
SpringBoot与SpringCloud对应的版本选择
SpringCloud Version | SpringBoot Version |
---|---|
2021.0.x aka Jubilee | 2.6.x, 2.7.x (Starting with 2021.0.3) |
2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
Hoxton | 2.2.x, 2.3.x (Starting with SR5) |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
- 本次SpringCloud所需要的环境
选项 | 版本 |
---|---|
cloud | Hoxton.SR1 |
boot | 2.2.2.REALEASE |
cloud alibaba | 2.1.0.REALEASE |
java | java8 |
Maven | 3.5以上 |
MySQL | 5.7以上 |
2.微服务cloud整体聚合父工程project
2.1选择maven
2.2导入父工程的依赖
<?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>com.blb</groupId>
<artifactId>spring-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>spring-cloud-payment8001</module>
</modules>
<packaging>pom</packaging>
<name>Maven</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org/</url>
<inceptionYear>2001</inceptionYear>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<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>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.verison>1.1.16</druid.verison>
<mybatis.spring.boot.verison>1.3.0</mybatis.spring.boot.verison>
</properties>
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.verison}</version>
</dependency>
<!-- mybatis-springboot整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.verison}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<locales>en,fr</locales>
</configuration>
</plugin>
</plugins>
</build>
<!-- <reporting>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <artifactId>maven-project-info-reports-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </reporting>-->
</project>
2.3 spring-cloud8001模块(支付模块)
主要代码
业务层
package com.blb.controller;
import com.blb.domain.CommentResult;
import com.blb.domain.Payment;
import com.blb.service.PaymentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "支付模块")
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@GetMapping("/text")
@ApiOperation("text")
public String text()
{
return "111";
}
@GetMapping("/listPayment")
@ApiOperation("listPayment")
public CommentResult listPayent()
{
List<Payment> paymentList = paymentService.listPayment();
return new CommentResult(200,"ok",paymentList);
}
@GetMapping("/getPaymentById/{id}")
@ApiOperation("getPaymentById")
public CommentResult getPaymentById(@PathVariable("id") long id)
{
Payment paymentById = paymentService.getPaymentById(id);
return new CommentResult(200,"ok",paymentById);
}
@PostMapping("/insertPayment")
@ApiOperation("插入支付信息")
public CommentResult insertPayment(@RequestBody Payment payment)
{
if(payment==null)
{
return new CommentResult(200,"插入信息为空",null);
}
int i = paymentService.insertPayment(payment);
return new CommentResult(200,"ok",i);
}
}
server层
package com.blb.service.imp;
import com.blb.domain.Payment;
import com.blb.mapper.PaymentMapper;
import com.blb.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PaymentServiceImpl implements PaymentService {
@Autowired
private PaymentMapper paymentMapper;
@Override
public List<Payment> listPayment() {
List<Payment> paymentList = paymentMapper.selectList(null);
return paymentList;
}
/**通过id查找支付信息**/
@Override
public Payment getPaymentById(long id) {
return paymentMapper.selectById(id);
}
@Override
public int insertPayment(Payment payment) {
if(payment==null)//传入对象为空
return 0;
return paymentMapper.insert(payment);
}
}
2.4 cloud-customer-order80模块(订单模块)
主要代码
配置类
package com.blb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
controller层
package com.blb.coltroller;
import com.blb.domain.CommentResult;
import com.blb.domain.Payment;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apiguardian.api.API;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
@Api(tags = "消费者订单模块")
@RequestMapping("/customer")
public class OrderController {
public static final String PRIMAL_URL="http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
@GetMapping("/listPayment")
@ApiOperation("得到消费所有信息")
public CommentResult getPayment()
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/listPayment",CommentResult.class);
}
@GetMapping("/addPayment")
@ApiOperation("插入指定订单")
public CommentResult<Payment> addPayment(Payment payment)
{
return restTemplate.postForObject(PRIMAL_URL+"/payment/insertPayment",payment,CommentResult.class);
}
}
3.Eureka
3.1服务治理
Spring cloud封装了Netflix公司开发Eureka模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,管理比较复杂,所有需要使用服务治理,管理服务与服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册
3.2服务注册
Eureka采用了CS的设计架构,Eureka Server作为服务功能的服务器,他就是服务注册中心,而系统中其他的微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常的运行
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前服务器的信息比如:服务地址、通讯地址等以别名的方式注册到注册中心上,另一方面消费者(服务提供者),以该别名的形式去注册中心上获取到实际的服务通讯地址,然后在实现本地RPC调用RPC远程调用。框架核心设计思想在于注册中心,因为服务注册中心管理每个服务与每个服务之间的依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
3.3Eureka包含的两个组件
Eureka Server和Eureka Cilent
3.3.1 EurakeServer提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到
3.3.2EurekaClient通过注册中心进行访问
是一个java客户端,用于简化EurekaServer的交互,客户端也同时具备一个内置的、使用轮询(round-join)负载算法的负载均衡器。在应用启动后,将会想Eureka Server发送心跳(默认周期为30秒)如果EurekaServer在多个心跳周期内没有接收到每个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
3.3.3Eureka服务器的安装
- 新建一个module
- 导入相关依赖
<?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">
<parent>
<artifactId>spring-cloud</artifactId>
<groupId>com.blb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--Eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入自己通用的jar包,可以使用payment支付entity-->
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--bootweb和autuator-->
<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>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
- yaml文件配置
server:
port: 7001
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
defaultZone : http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动类
package com.blb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
- 启动成功的界面
- EurekaClient端spring-cloud-payment8001将注册进EurekaServer成为服务提供者provide
在spring-cloud-payment-8001端口加一个Eureka客户端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone : http://localhost:7001/eureka
启动类加上注解@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
- EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer
在客户端cloud-consumer-order80端口加入Eureka服务的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true #表示是否将自己注册进EurekaServer 默认为true
fetchRegistry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka
启动类
@SpringBootApplication
@EnableEurekaClient
public class CloudConsumerOrder80 {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerOrder80.class,args);
}
}
4.Eurekea集群
4.1集群原理说明
微服务RPC远程服务调用的核心
高可用,试想你的注册中心只有一个only one,如果他出故障了会导致整个服务环境不可用,解决办法是搭建Eureka注册中心集群,实现负载均衡+故障容错(互相注册,互相守望)
使用方法
在原来项目的基础上上一个名为cloud-eureka-server7002的module,内容和cloud-eureka-server7001内容一样
7001中yaml的配置为
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7002.com:7002/eureka/
defaultZone : http://eureka7002.com:7002
7002中yml配置为
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7001.com:7001/eureka/
defaultZone : http://eureka7001.com:7001
在C:\Windows\System32\drivers\etcwe文件下的hosts文件改配置
增加以下两行配置
4.2订单支付两个微服务注册进Eureka集群
4.2.1支付模块spring-cloud-payment8001模块
修改yml,增加eureka的服务地址
server:
port: 8002
spring:
application:
name: cloud-payment-service #微服务的名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/class?serverTimezone=UTC&useSSL=false
username: root
password: root
mybatis:
configuration:
map-underscore-to-camel-case: true
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone : http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
spring-cloud-payment8001的修改和spring-cloud-payment8002修改一样,改yml文件
4.2.2订单模块cloud-customer-order80modules
- 修改yml的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true #表示是否将自己注册进EurekaServer 默认为true
fetchRegistry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- z ApplicationContextConfig.class中修改代码,赋予RestTemplate的负载均衡能力
package com.blb.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //赋予了RestTemplate负载均衡的能力
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
根据eureka修改访问路径
OrderVController中进行修改
@RestController
@Api(tags = "消费者订单模块")
@RequestMapping("/customer")
public class OrderController {
// public static final String PRIMAL_URL="http://localhost:8001";
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
@GetMapping("/listPayment")
@ApiOperation("得到消费所有信息")
public CommentResult getPayment()
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/listPayment",CommentResult.class);
}
@GetMapping("/addPayment")
@ApiOperation("插入指定订单")
public CommentResult<Payment> addPayment(Payment payment)
{
return restTemplate.postForObject(PRIMAL_URL+"/payment/insertPayment",payment,CommentResult.class);
}
}
4.3Eureka自我保护机制
4.3.1概述
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,Eureka Server将会尝试保护器注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务
4.3.2导致原因
某时刻某一个微服务不可用了,Eureka不会立即清理,依旧会对该服务信息进行保存,属于CAP里面的AP分支
4.3.3如何进制自我保护
- 在EurekaServer端关闭自我保护机制
例如:在cloud-eureka-server7001d yam中配置关闭自我保护机制
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7002.com:7002/eureka/
defaultZone : http://eureka7002.com:7002
server:
# 关闭自我保护机制,保证不可用服务及时删除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
- 在spring-cloud-payment8001模块设置心跳的时间间隔以及最后一次心跳的等待上限
server:
port: 8001
spring:
application:
name: cloud-payment-service #微服务的名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/class?serverTimezone=UTC&useSSL=false
username: root
password: root
mybatis:
configuration:
map-underscore-to-camel-case: true
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone : http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #修改服务名称
prefer-ip-address: true # ip显示
# erureka客户端向服务器发送心跳的时间间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等到时间上限,单位为秒(默认30秒),超时将删除服务
lease-expiration-duration-in-seconds: 2
5.Ribbon
5.1. 简介
Spring Cloud Ribbon是基于Netfix Ribbon实现的一套客户端 负载均衡的工具
简单的说,Ribbon 是Netfix发布的开源项目,主要的功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如:超时连接,重试等。简单的说,就是在配置文件中列出Load Balance(简称LB)后面所有的机器,Ribbon会自动帮助你基于某种规则(如:轮询,随机连接等)、去连接这些机器,我们很容易使用Ribbon实现自定义的负载均衡算法
Ribbon已经进入维护模式!未来的替换方案:Load Banlancer
5.1.1LB(负载均衡)
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
常见的负载均衡有软件 Nginx LVS 硬件F5等
5.1.2Ribbon本地负载均衡客户端VS Nginx 服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的
Ribbon 本地负载均衡,再调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
5.1.3集中式LB
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
5.1.4进程式LB
将LB逻辑集成到消费方,消费方从服务注册中心或知有哪些地址可用,然后自己再从这些地址中选择出一个何时的服务器。
Ribbon就属于是进程内LB, 它只是一个类库,集成与消费方进程,消费方通过它来获取到服务提供方的地址。
5.2.负载均衡演示
5.2.1架构说明
总结:Ribbon其实就是一个软负载均衡的客户端组件。
它可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例
5.2.2Ribbon工作过程
Ribbon在工作时分为两步:
- 第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server
- 第二步再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如 轮询,随机和根据响应时间加权。
5.2.3依赖引入
Eureka Client 会自己带着Ribbon 所以不需要添加Ribbon依赖
5.3.RestTemplate的使用
5.3.1 getForObject() 和 getForEntity()
getForObject() // 返回对象为响应体中数据转化成的对象,基本上可以理解为JSON
getForEntity() // 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
getForObject
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
//返回对象为响应体中数据转化成的对象,基本上可以理解为json
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
getForEntity
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
// 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
@GetMapping("/getForEntity/{id}")
@ApiOperation("通过id得到实体类")
public CommentResult<Payment> getpayment(@PathVariable("id") long id)
{
// System.out.println(id);
// 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
ResponseEntity<CommentResult> entity = restTemplate.getForEntity(PRIMAL_URL + "/payment/getPaymentById/"+id, CommentResult.class);
//判断是否成功
if(entity.getStatusCode().is2xxSuccessful())
{
return entity.getBody();
}
else
return new CommentResult<>(400,"操作失败");
}
5.4Ribbon核心组件IRule
5.4.1 IRule:根据特定算法从服务列表中选取一个要访问的服务
- com.netflix.loadbalancer.RoundRobinRule:轮询
- com.netflix.loadbalancer.RandomRule: 随机
- com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
- WeightedResponseTimeRule :对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule :会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
5.4.2替换负载均衡算法
- 修改消费者服务 consumer
- 配置类
注意这个 自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包否则我们自定义的这个配置类旧会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
由于主启动类的com.blb包下,我们建一个com.myrule包,在主启动类的上一个层次建一个MySelfRule类
@Configuration
public class MySelfRule {
@Bean
public IRule myRule()
{
return new RandomRule();
}
}
5.4.3负载均衡的原理
负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始。
List<ServiceInstance> instances = doscoveryClient.getInstances("cloud-payment-service");
List[0] instances = 127.0.0.1:8002;
List[1] instances = 127.0.0.1:8001;
8001 + 8002 组合为集群,他们共计2台机器,集群总数为2,按照轮询算法原理:
当总请求数为1时:1 % 2 = 1,对应下标为 1,则获得服务地址为 127.0.0.1:8001
当总请求数为2时:2 % 2 = 0,对应下标为 0,则获得服务地址为 127.0.0.1:8002
当总请求数为3时:3 % 2 = 1,对应下标为 1,则获得服务地址为 127.0.0.1:8001
当总请求数为4时:4 % 2 = 0,对应下标为 0,则获得服务地址为 127.0.0.1:8002
如此类推
6.OpenFeign
6.1 OpenFeign简介
Feign是一个声明式WebService客户端,使用Feign能让编写Web Service客户端更加简单,只需要创建一个接口并添加注解即可
他的使用方法时定义一个服务接口然后在上面添加注解,Feign也支持可插拔式的编码器和解码器。Spring Cloud 对Feign进行了封装。使其支持了SpringMVC 标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
6.2 Feign的作用
Feign旨在使编写Java Http客户端变得更加容易。
前面在使用Ribbon + RestTemplate时,利用RestTemplate 对http请求的封装处理,形成一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多出调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可) 即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
6.3Feign集成了Ribbon
利用Ribbon维护了 [payment]的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,简单而优雅的实现了服务调用。
6.4OpenFeign使用步骤
- 创建cloud-consumer-openfeign-order80 模块
- 写pom
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.blb</groupId>
<artifactId>spring-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.blb</groupId>
<artifactId>cloud-consumer-feign-order80</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-consumer-feign-order80</name>
<description>cloud-consumer-feign-order80</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--openfeign-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 可以使用payment支付entity-->
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 写yml
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
register-with-eureka: false
spring:
application:
name: cloud-feign-consumer
# 设置 feign 客户端超时时间(OpenFeign 默认支持 ribbon)
ribbon:
# 值的是建立连接所用的时间,使用与网络状态正常的情况,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectionTimeout: 5000
logging:
level:
com.blb.cloudconsumerfeignorder80.service.PaymentFeignService: debug
- 修改主启动类
加上@RibbonClient(name = “CLOUD-PAYMENT-SERVICE”,configuration = MySelfRule.class)
@SpringBootApplication
@EnableEurekaClient
// name 写要负载均衡访问的provider的微服务名字
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class CloudConsumerOrder80 {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerOrder80.class,args);
}
}
- 业务类
声明一个远程调用服务接口,这个接口可以在 commons 模块中
如果是在一个别的模块中,那么这个远程调用服务接口所在的包结构,必须要能被Springboot扫描到
5.1. 声明远程调用服务接口
@FeignClient(“provider微服务名字”)
注意:
这里声明的方法签命,必须和provider服务中的controller中方法的签命一致
如果需要传递参数,那么@RequestParam 和@RequestBody @PathVariable 不能省 必加
@Component
//@Service
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/getPaymentById/{id}")
@ApiOperation("getPaymentById")
public CommentResult getPaymentById(@PathVariable("id") long id);
}
5.2 写controller
provider的controller这里声明的方法 要和 远程调用服务接口中的 方法签命保持一致
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommentResult getPaymentById(@PathVariable("id")long id )
{
return paymentFeignService.getPaymentById(id);
}
6.5超时报错
6.5.1 OpenFeign默认等待时间为1秒钟,超过后报错
默认Feign客户端只等待一秒钟,但是服务段处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这种请况,有时候我们需要设置Feign客户端的超时控制
在yml中配置
# 设置 feign 客户端超时时间(OpenFeign 默认支持 ribbon)
ribbon:
# 值的是建立连接所用的时间,使用与网络状态正常的情况,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectionTimeout: 5000
6.6OpenFeign日志打印功能
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。
说白了就是:对Feign接口的调用情况进行监控和输出。
6.6.1、日志级别
NONE | 默认的,不显示任何日志 |
---|---|
BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
HEADERS | 除了BASIC中定义的信息之外,还有请求和响应的头信息 |
FULL | 除了HEADERS中定义的信息外,还有请求和响应的正文及元数据。 |
6.6.2 配置日志
6.6.2.1配置日志bean
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLever()
{
return Logger.Level.FULL;
}
}
6.6.2.2配置消费端的yml
logging:
level:
# feign 日志以什么级别监控哪个接口
com.blb.cloudconsumerfeignorder80.service.PaymentFeignService: debug
7.Hystrix
7.1概述
7.1.1分布式系统面临的问题
复杂分布式体系机构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
7.1.1.1 服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
通常当你发现一个模块下某个实例失败后,这时候这个模块依然还会接收流量,然而这个有问题的模块还调用了其他模块,这样就会发生级联故障,或者叫雪崩。
7.1.2Hystrix 是什么
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
7.1.2Hystrix能干什么
- 服务降级
- 服务熔断
- 接近实时的监控
7.2Hystrix重要概念
7.2.1、服务降级
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个好友提示。fallback
哪些情况会触发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
7.2.2、服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示 break
就是保险丝 : 服务的降级—>进而熔断—>恢复调用链路
7.2.3、服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行 flowlimit
7.3 Hystrix案例
7.3.1搭建正确提供者模块
搭建基础平台:从正确—>错误—>降级熔断—>恢复
以此平台 演示 Hystrix 服务降级 服务熔断 服务限流
7.3.1.1创建cloud-provider-hystrix-payment8001模块
7.3.1.2添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
7.3.1.3yaml配置
server:
port: 8001
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
spring:
application:
name: cloud-provider-hystrix-payment
7.3.1.4Controller
@RestController
@Slf4j
@Api(tags = "hystrix测试")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
@ApiOperation("正常")
public String paymentInfoOk(@PathVariable("id") int id)
{
String result = paymentService.paymentInfo_ok(id);
log.info(result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
@ApiOperation("超时")
public String paymentInfoTimeOut(@PathVariable("id") int id) throws InterruptedException {
Thread.sleep(3000);
String result = paymentService.paymentInfo_timeOut(id);
log.info(result);
return result;
}
}
7.3.1.5service
@Service
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程次: "+Thread.currentThread().getName()+" paymentInfo_ok"+id;
}
/**
* 时间超时
* @param id
* @return
*/
@Override
public String paymentInfo_timeOut(Integer id) {
return "线程次: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id;
}
}
7.3.1.6 测试
能够测试通过,但是访问 /test/hystrix/timeout/1 时会等待3秒钟
在上诉测试非高并发的情形下还能勉强满足,但是当我们开启Jmeter,来20000个并发压死8001,2000个请求都去访问/payment/hystrix/timeout,浏览器转圈圈
以上还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端80直接被拖死
7.3.2搭建正确的消费者模块
7.3.2.1创建cloud-consumer-feign-hystris-order80模块
7.3.2.2依赖导入
<dependencies>
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
7.3.2.3yml配置
server:
port: 80
eureka:
client:
register-with-eureka: false
# fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
7.3.2.4service层的编写
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") int id);
@GetMapping("`/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") int id);
}
7.3.2.5controller层的编写
@RestController
public class OrderHystrixController {
@Autowired
private PaymentService paymentService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
private String hystrisOk(@PathVariable("id") Integer id)
{
String infoOk = paymentService.paymentInfoOk(id);
return infoOk;
}
@GetMapping("/consumer/payment/hystrix/timeOk/{id}")
private String hystrisTomeOut(@PathVariable("id") Integer id)
{
String infoOk = paymentService.paymentInfoTimeOut(id);
return infoOk;
}
}
测试发现的问题:8081同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8081,客户端访问响应缓慢
7.3.2.6、Hystrix解决问题
- provider服务 8001超时了,调用者80不能一直卡死等待,必须有服务降级
- provider服务 8001 宕机了,调用者80不能一直卡死等待,必须有服务降级
- provdier服务 8001OK,调用者80自己出现了故障或有自我要求(自己的等待时间小于服务提供者)
7.3.3服务降级
7.3.3.1对服务器端进行降级操作
对cloud-provider-hystrix-payment80进行改造
- 对服务端进行改造
@HystrixCommand 注解
@Service
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_ok"+id+"o(n_n)o哈哈";
}
/**
* 时间超时
* 设置自身调用超时时间的峰值,峰值内可以正常运行,如果在峰值外就执行备用方法 做降级服务fallback
* 如果不设置峰值的话,直接在方法里抛出 10 / 0 by zero 异常 也会调用备用方法(异常也会调用fallback方法)
* 设置时间为3s而方法延时到5s会调用fallbackMethod
* 一旦调用服务方法失败并抛出错误信息后,会自动调用@HystrixCommand(标注好的fallbackMethod调用类中指定的方法
* @param id
* @return
*/
@HystrixCommand(fallbackMethod ="paymentInfo_timeOutHandler",
commandProperties={
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@Override
public String paymentInfo_timeOut(Integer id) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(n_n)o哈哈";
}
public String paymentInfo_timeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(u_u)o呜呜";
}
}
- 主启动类激活
@EnableCircuitBreaker
你在消费端 进行了降级 就在消费端添加该注解,反之.
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker
public class CloudProviderHystrixPayment8001Application {
public static void main(String[] args) {
SpringApplication.run(CloudProviderHystrixPayment8001Application.class, args);
}
}
7.3.3.2全局服务降级@DefaultProperties
解决问题:
解决了每个方法都需要有兜底的方法的痛处
解决了兜底方法和业务逻辑混在一起的问题
7.3.3.2.1 解决代码膨胀
解决每个方法都要有兜底方法问题
@DefaultProperties(defaultFallback = “方法名”)
标注在类上,表示没有指定@HystrisCommand(fallbackMethod=“方法名”)的方法就是用@DefaultProperties(defaultFallback=“方法名”)所指定的做备用方法。
注意:就算使用全局降级配置 也需要在方法上添加@HystrisCommand 注解
1:1每个方法配置一个服务降级的方法,技术上可以,实际中不可用
1:N 除了个别重要核心业务有专属,其他普通的可以通过@DefaultProperties(defaultFallback= “”)统一跳转到统一处理结果页面
@Service
@DefaultProperties(defaultFallback = "error")
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_ok"+id+"o(n_n)o哈哈";
}
//使用指定的降级服务
// @HystrixCommand(fallbackMethod ="paymentInfo_timeOutHandler",
// commandProperties={
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
// })
//使用全局的降级服务
@HystrixCommand
@Override
public String paymentInfo_timeOut(Integer id) {
System.out.println(1/0);
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(n_n)o哈哈";
}
public String paymentInfo_timeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(u_u)o呜呜";
}
public String error()
{
return "报错了";
}
}
7.3.3.3.服务降级,客户端调用服务端,服务端宕机了
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系
只需要为Feign客户端定义的远程调用接口添加一个服务降级处理的实现类即可实现解耦合
未来面临的异常
运行
超时
宕机
对cloud-consumer-feign-hystrix-orser80进行改造
- 定义远程调用接口实现类
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfoOk(int id) {
return "调用服务器出现异常";
}
@Override
public String paymentInfoTimeOut(int id) {
return "调用服务器出现异常";
}
}
- 远程调用接口中@FeignClient注解添加fallback属性
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentServiceImpl.class)
public interface PaymentService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") int id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") int id);
}
7.3.4服务熔断
熔断器:一句话就是家里的保险丝
7.3.4.1熔断机制的概述
熔断机制式应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,自动恢复调用链路。
熔断状态: 开启 关闭 半开启
在SpringCloud框架中,熔断机制通过Hystrix实现Hystrix会监控微服务间调用的状况。
当失败的调用到一定阈值,缺省时5秒内20此调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand
大神论文:https://martinfowler.com/bliki/CircuitBreaker.html
7.3.4.2实操
- 对8001服务端的service进行改造 增加熔断机制
//服务熔断
@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(Integer id)
{
if(id<0)
{
throw new RuntimeException("*******id不为负数");
}
String s = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号"+s;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id")Integer id)
{
return id+"为负数,请稍后在试";
}
- controller层
//服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id")Integer id)
{
String s = paymentService.paymentCircuitBreaker(id);
log.info(s);
return s;
}
测试
在web页面 输入参数为负数会进入到 兜底方法,如果输入负数的次数 【在一个10秒钟的窗口期,如果有10个请求 60%都失败了就熔断】符合这个要求,那么就会触发熔断机制,然后你再输入正数都不会执行成功了!慢慢的他自己会检测到后台输入好几个正数了,就会自动关闭熔断
7.3.4.3总结
- 熔断类型
- 熔断打开:请求不再进行调用当前服务,内部设置始终一般为MTTR(平均故障处理时间),当打开时长达到所设始终则进入半熔断状态。
- 熔断关闭:熔断关闭不会对服务进行熔断
- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认位当前服务恢复正常,关闭熔断
- 断路器在什么情况下开始起作用
涉及到断路器的三个重要因素:快照时间窗,请求总数阈值,错误百分比阈值
-
快照时间窗:
断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。 -
请求总数阈值:
在快照时间窗内,必须满足请求总数阈值才有资格熔断,默认为20,意味着在10秒内,如果该Hystrix命令的调用次数不足20次,即使所有的请求都超时或者其他原因失败了,断路器都不会打开。 -
错误百分比阈值:
当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
-
断路器开始或者关闭条件
当满足一定的阈值的时候(默认是10秒内超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%的请求失败)
到达以上阈值,断路器将会开启
当开启的时候,所有的请求都不会进行转发。
一段时间后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发,如果成功,断路器关闭,如果失败,继续开启 -
断路器打开之后
-
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级的fallback方法,通过断路器,实现了自动的发现错误并将降级逻辑升级为主逻辑,减少响应延迟的效果。
-
原来的主逻辑要如何恢复?
对于这一问题mhystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑。
当休眠时间窗到期,断路器将进入半开状态,释放给一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合。
主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
7.3.5服务监控 HystrixDashboard
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard)Hystrix会持续的记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等,Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控,Spring Cloud提供了Hystrix Dashboard的整合,对监控内容转化成可视化页面。
7.3.5.1搭建HystrixDashboard
Hystrix 做服务监控还需要创建一个模块,而阿里巴巴的sentinel 直接给你要给网站就能使用
- 创建新的模块 could-consumer-hystrix-dashboard9001
- 添加依赖
<dependencies>
<!--新增hystrix dashboard-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.0.2</version>
</dependency>
<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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- yaml
server:
port: 9001
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
spring:
application:
name: cloud-consumer-hystrix-dashboard
- 主启动类添加注解 @EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class CouldConsumerHystrixDashboard9001Application {
public static void main(String[] args) {
SpringApplication.run(CouldConsumerHystrixDashboard9001Application.class, args);
}
}
- 启动测试
7.3.5.2监控8001端口
- 8001一定要有一下两个依赖
<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>
- 主启动类中添加一下代码
/**
*此配置是为了服务监控而配置,与服务容错本身无关,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;
}
- 输入信息进行监控
- 点击Monitor Stream
- 如何查看仪表盘
8. Getway
8.1. Getway概述
8. 1.1Getway是什么
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用Zuul网关;但在2.x版本中,zuul的升级就是一直跳票,SpringCloud最后自己研发了一个网关代替Zuul那就是 SpringCloud Gateway ,gateway是zuul 1.x版本的替代。
Gateway是在Spring生态系统之上架构的API网关服务,基于Spring 5,Spring Boot2 和Project Reactor技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway作为Spring cloud生态系统中的网关,目标是代替 Zuul,在SpringCloud2.0以上版本中,没有对新版本Zuul 2.0以上实现最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,【说穿了就SpringCloud Gateway是异步非阻塞式】
springcloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关的基本功能,例如:安全,监控/指标,和限流
springcloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
8.1.2Gateway能干什么
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
微服务架构中网关在哪里
8.1.3.微服务架构中网关在哪里
8.1.4、SpringCloud Gateway具有的特征
- 基于Spring Frameword5 ,Project Reactor 和 SpringBoot 2.0进行构建;
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能;
- 集成Spring Cloud的服务发现功能
- 易于编写的Predicate(断言)和Filter(过滤器)
- 请求限流功能;
- 支持路径重写
8.1.5 SpringCloud Gateway与zuul的区别
在SpringCloud Finchley 正式版之前(现在H版),SpringCloud推荐的网关是Netflix提供的zuul。
-
Zuul1.x 是一个基于阻塞 I/O的API网关
-
Zuul1.x 基于Servlet2.5使用阻塞架构它不支持任何长连接 (如WebSocket)Zuul的设计模式和Nginx较像,每次I/O操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。
-
Zuul 2.x理念更加先进,像基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul2.x的性能较Zuul 1.x有较大的提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求次数)是Zuul的1.6倍。
-
Spring Cloud Gateway建立在Spring Framework 5、project Reactor和Spring Boot2 之上,使用非阻塞API
-
Spring Cloud Gateway 还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验。
8.2Getway三大核心概念
- Route路由
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。 - Predicate 断言
开发人员可以匹配Http请求中的所有内容(例如请求头或者请求参数),如果请求参数与断言相匹配则进行路由。 - Filter 过滤
指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。 - 总结
web 请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制
predicate 就是我们的匹配条件
filter:就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标的uri,就可以实现一个具体的路由了。
8.2 SpringCloud Getway 的工作流程
- 客户端向Spring Cloud Gateway发出请求,然后再Gateway Handler Mapping 中找到与请求相匹配的路由,将其发生到Gateway Web Handler
- Handler 再通过指定的的过滤器链来讲请求发送到我们实际的服务执行业务逻辑,然后返回。
- 过滤器之间使用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”) 或之后(“post”)执行业务逻辑。
- Filter在“pre”类型的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等。
- 在“post”类型的过滤器可以做响应内容、响应头的修改,日志的输出,流量监控等有者非常重要的作用。
核心逻辑:路由转发+执行过滤链
8.3 入门配置
8.3.1 新建module cloud-Getway-9527
8.3.2导依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
8.3.3写yml文件
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eu
8.3.4主启动
@SpringBootApplication
@EnableEurekaClient
public class CloudGetway9527Application {
public static void main(String[] args) {
SpringApplication.run(CloudGetway9527Application.class, args);
}
}
8.3.5 9527网关如何做路由映射
cloud-provider-payment8001看看controller的访问地址,以getPaymentById和listPayment为例,因为目前不想暴露8001端口,希望我8001外面套一层9527
8.3.6 yml新增网关配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/payment/getPaymentById/**需要
# http://localhost:9527//payment/getPaymentById/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: h
8.3.7 测试
分别启动7001、8001、9527网关
添加网关之前可以访问:http://localhost:8001·/payment/listPayment
添加网关之后可以访问:http://localhost:9527/payment/listPayment
8.3.8 Gateway配置的两种方式
- 在配置文件yaml中进行配置
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/get/payment/** 需要
# http://localhost:9527/get/payment/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
- 代码中注入 RouteLocator的Bean
需求:通过 952网关访问到外网的百度新闻网址
创建一个配置类来配置网关,我当前module下创建config.GateWayConfig
@Configuration
public class GatewayConfig {
/**
* 配置了一个名为path_route_springcloud的路由规则
* 当访问地址为http://localhost9527/guonei是自动转发到地址http://news.baidu.com/guonei
* @param builder
* @return
*/
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder)
{
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_springcloud",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
8.3.9 通过微服务名实现动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
开启动态路由:java spring.cloud.gateway.discovery.locator.enabled:true;
在添加uri的时候,开头是 lb://微服务名
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启冲注册中心动态‘创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/get/payment/** 需要
# http://localhost:9527/get/payment/**
- id: payment_routh2
# uri: http://localhost:8001
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
测试
分别启动7001、Payment8001、Payment8002、9527网关
因为开启了8001和8002两个端口,所以网关负载均衡的效果是 8001/8002切换
实现了路由的切换
8.4 Predicate 断言的使用
gateway启动时打印的信息
Spring Cloud Gateway 将路由匹配作为Spring WebFlux Handler Mapping基础架构的一部分。
Spring Cloud Gateway 包括许多内置的Route Predicate 工厂,所有的这些Predicate都和Http请求的不同属性匹配,多个Route Predicate可以进行组合。
Spring Cloud Gateway 创建route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route,SpringCloud Gateway包含许多内置的Route Predicate Factories.
所有的 这些谓词都匹配Http的请求的各种属性,多种谓词工厂可以组合,并通过逻辑and
8.4.1常用的断言
常用的Route Predicate
- After Route Predicate
java After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
匹配该断言时间之后的 uri请求 - Before Route Predicate
java After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
java Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Between Route Predicate
java Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Cookie Route Predicate
不带cookies访问
带上cookies访问java Cookie=username,atguigu #并且Cookie是username=zhangshuai才能访问
Cookie Route Predicate 需要两个参数,一个时Cookie name,一个是正则表达式。
路由规则会通过获取对应的Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上就不执行 - Header Route Predicate
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行; - Host Route Predicate
Host=**.atguigu.com
- Method Route Predicate
Method=GET
- Path Route Predicate
- Query Route Predicate
Query=username, \d+ #要有参数名称并且是正整数才能路由
总结
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
8.5Filter
8.5.1 Filter是什么路由
过滤器可用于修改进入HTTP请求和返回的HTTP响应,路由过滤器只能在指定路由进行使用
SpringCloud Gateway内置了多种路由过滤器,他们都是由GatewayFilter的工厂类来产生
Filter声明周期
pre、post
filter种类
GatewayFilter、GlobalFilter
8.5.2自定义全局GlobalFilter
8.5.2.1两个接口
impiemerts GlobalFilter,Ordered
8.5.2.2能干嘛
全局日志记录
统一网关鉴定
8.5.2.3实例代码
@Component
@Slf4j
public class GlobalFilter implements Ordered, org.springframework.cloud.gateway.filter.GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String username = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
9.Config分布式配置中心
9.1 分布式配置中心概述
9.1.1分布式系统面临的—配置问题
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能够运行,所以一套集中式的,动态的配置管理设施是必不可少的
9.1.2配置中心是什么
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
9.1.3配置中心怎么用
SpringCloud Config 分为服务端和客户端两部分。
服务端也成为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并未客户端提供获取配置信息,加密/解密等信息访问接口.
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样既有助于对环境配置进行版本管理,并且可以通过git客户端来方便的管理和访问配置内容。
9.1.4配置中心能干什么
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如 dev/test/prod/beta/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务回想配置中心统一拉去配置自己的信息。
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置。
- 将配置信息以Rest接口的形式暴露。