死亡三连问
SpringCloud 是啥?
- 官方一点的说法:SpringCloud是在SpringBoot基础上构建的,用于快速构建分布式系统的通用模式的工具集(注意了:重点在于它只是一个工具),因此我把它简单的理解为构建微服务的一个工具
- SpringCloud主要就是将各家较为成熟的服务框架组合起来,再通过SpringBoot风格进行封装,屏蔽掉了原先复杂的配置和实现原理,最终让开发者可以简单方便且易维护的开发的分布式系统开发工具包
- 那么SpringCloud和云到底有啥关系呢,其实由于使用SpringCloud开发的应用程序非常适合在Docker或Pass上部署,所以由SpringCloud构建的程序又叫做云原生应用,可以简单的理解为面向云环境的软件架构
微服务 是啥?
- 微服务,见名知义,就是较小的服务单元,与微服务相对的单体架构
- 单体架构 单体架构就是将所有的业务场景(表现层、业务逻辑层、数据访问层)都放在一个工程中,部署在一台服务器上
- 那么相对单体架构的微服务架构就是将单一程序开发成一个微服务
- 每个微服务运行在自己的进程中,并使用轻量级的机制通信,通常是HTTP RESTFUL API(也可以采用消息队列来通信)。
- 这些服务围绕着业务能力来划分,并通过自动化部署机制来独立部署(如Jenkins),降低出错频率
- 这些服务可以使用不同的编程语言,不同的数据库,以保证最低限度的集中式管理(例如Eureka,Zookeeper)。
为啥 需要微服务?
- 一般呢,小的公司是用不到微服务的,怎么样?是不是这个技术瞬间高大上了起来
- 微服务架构的出现 当然是由于单体架构不给力导致的
- 在业务的逻辑越来越复杂的时候,单体架构的代码可读性和可维护性就会越来越低,面对海量用户时,单体架构系统的并发能力也会随之下降
- 那么就需要使用微服务架构将一个复杂业务拆分成多个小业务,将复杂问题不断拆分成一个个简单的小问题
- 微服务是分布式系统,业务与业务之间完全解耦,也就是说一个业务出现了问题,不会影响其他的业务,可以显著提高生产力
- 同时当我们需要增加业务时,可以根据业务再拆分,具有极强的横向扩展能力,面对高并发的场景可以将服务集群化部署,加强系统负载能力
- 因为现实中,一个工程可能由多个服务单元组成,但是多个服务可能使用不同的技术或语言实现,微服务可以让这种融合更自由
一些概念性的东西
SpringCloud的主要应用过程
首先展示一下源自网络的springCloud组件架构图: 结合这张图来说一下springCloud的应用过程:
- 所有的请求(移动端、客户端有一个算一个)都统一通过网关服务(Zuul Proxy)来访问内部服务
- 网关接收到请求后,从注册中心(Eureka)获取可用的微服务,所有的微服务都需要在注册中心进行注册
- 其中Ribbon主要用于负载均衡,它可以帮助判断并分发具体的请求到后端的具体应用服务实例,起到了分发压力的作用
- 微服务(services)之间通过Feign进行通信处理业务,保持服务的一致性
- Hystrix负责处理服务超时熔断
- Turbine监控 服务间的调用和熔断的相关指标
从上面这张图中也可以看出SpringCloud主要的组件,解释一下这些组件都具体是做什么用的:
- Netflix Eureka 该组件负责服务的注册与发现,Eureka体系包括:服务注册中心、服务提供者、服务消费者
- Netflix Hystrix 熔断器,容错管理工具,旨在通过熔断机制控制服务和第三方库的节点。比如突然某个服务出现了故障,当请求多时,就会发生严重的阻塞影响整体服务响应。Hystrix会及时发现某个不在状态不稳定服务,将其他服务调过来响应
- Netflix Zuul 该组件是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul相当于设备和Netflix流应用的Web网站后端所有请求的前门
- Netflix Archaius 该组件用于配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。原理是每隔60s从配置源读取一次内容,这样修改配置文件后不需要重启服务就可以使修改后的内容生效,前提是使用archaius的API来读取
- Spring Cloud Config 配置中心,配置管理工具包,可以将配置放在远程服务器,集中化管理集群配置,目前支持本地存储、GIt以及SubVersion。方便以后统一管理、升级装备
- Spring Cloud Bus 事件、消息总线,用于在集群中传播状态变化,可以与Spring Cloud Config联合实现热部署
- Spring Cloud for Cloud Foundry 一个开源Paas云平台,支持多种框架、语言、运行时环境、云平台以及应用服务,使开发人员可以在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构问题
- Spring Cloud Cluster 提供在分布式系统中的集群所需要的基础功能支持,如选举、集群的状态一致性、全局锁、tokens等常见状态模式的抽象和实现
- Spring Cloud Consul Consul是一个支持多数据中心分布式高可用的服务发现和配置共享的服务软件,consul是一个服务发现与配置工具,与Docker容器可以无缝集成
开始操作
完整的文件结构
整个项目分为四个模块consumer、provider、euraka、gateway consumer结构 eureka结构 gateway文件结构 provider文件结构
搭建基础框架
- IDEA创建maven项目
- 更改父级Pom文件
完整的父级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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springCloud</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<gson.version>2.8.0</gson.version>
<jackson.version>2.9.5</jackson.version>
<swagger.version>2.9.2</swagger.version>
<mybatis.plus.boot.version>3.1.0</mybatis.plus.boot.version>
<hutool.version>5.7.19</hutool.version>
<druid.version>1.1.10</druid.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
主要的部分就是Properties和dependency两部分,本次测试项目中并没有使用到数据库,但实际业务需要使用到,所以记得添加
- 创建生产者Provider
在项目的父级目录上右键new->module->maven - 更改provider的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springCloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 添加provider的基础文件
Controller层代码如下
@RestController
@RequestMapping("provider")
public class ProviderController {
private Map<Integer, User> map;
{
map = new HashMap<>();
map.put(1, new User(1, "Johnny"));
map.put(2, new User(2, "Timmy"));
}
@GetMapping("/findAll")
public List<User> findAll() {
System.out.println("Provider::findAll");
List<User> list = new ArrayList<>();
for (Map.Entry<Integer, User> users : map.entrySet()) {
list.add(users.getValue());
}
return list;
}
@GetMapping("/findById/{id}")
public User findById(@PathVariable("id") Integer id) {
return map.get(id);
}
}
pojo层代码如下
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
}
ProviderApplication启动类代码如下
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class);
}
}
- Provider启动测试
启动provider的启动类,使用postman测试 如下即provider基础编写完成 - 添加消费者Consumer模块
步骤同Provider - 更改consumer的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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springCloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 添加Consumer基础代码
Controller层代码如下:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findAll")
public List<User> findAll() {
System.out.println("Consumer:: findAll");
String baseUrl = "http://localhost:8081/provider/findAll";
List list = restTemplate.getForObject(baseUrl, List.class);
return list;
}
@GetMapping("/findById/{id}")
public User findById(@PathVariable("id") Integer id) {
String baseUrl = "http://localhost:8081/provider/findById/" + id;
User user = restTemplate.getForObject(baseUrl, User.class);
return user;
}
}
pojo层代码如下 和provider的完全一致
Consumer启动类代码如下
@SpringBootApplication
public class ConsumerApplication {
@Autowired
private RestTemplateBuilder templateBuilder;
@Bean
public RestTemplate restTemplate() {
return templateBuilder.build();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
- Consumer代码启动测试
因为同时Provider代码也必须是运行状态,所以需要先给provider改一下运行端口号 provider的application.yml文件中添加 server.port: 8081 这里有个idea小技巧,为了看到所有的模块运行情况,我们需要底栏显示Services 找到本项目的.idea文件夹下面的workspace.xml文件,在文件末尾添加
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
</component>
后重启IDEA使之生效
启动consumer,provider
Eureka注册中心
- 创建一个子模块eureka
方式同consumer、provider - 更改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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springCloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
- 因为Eureka只是注册中心,没有实际业务,所以完整的文件结构如下
启动类EurekaApplication中代码
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}
}
配置文件
server:
port: 8761
eureka:
client:
service-url:
defaultZone: http://0.0.0.0:8761/eureka
fetch-registry: false
register-with-eureka: false
Done!! 启动程序,url访问localhost:8761 我们看到Instance currently registered with Eureka中空空如也,这是因为我们没有进行对微服务的注册,重申一遍:所有的微服务都需要注册到注册中心
- 将provider注册到注册中心
1、在Provider的pom文件中增加eureka client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、启动类上加注解
@EnableDiscoveryClient
这里想说一下@EnableDiscoveryClient和@EnableEurekaClient
从表面意思上看,似乎是@EnableEurekaClient更应该用在这里,但实际上这个注解已经是老版了,应用场景单一
并且使用后会在界面显示这样的信息 3、配置文件增加配置
server:
port: 8081
# 服务名称
spring:
application:
name: provider
#eureka配置
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
启动后显示eureka页面显示已经注册了一个实例
- 将consumer注册到注册中心
步骤同provider,相同部分不再赘述
- consumer中通过注册中心的方式调用provider
ConsumerController改为如下内容
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/findAll")
public List<User> findAll() {
System.out.println("Eureka:: Consumer");
List<ServiceInstance> providers = discoveryClient.getInstances("provider");
ServiceInstance serviceInstance = providers.get(0);
URI uri = serviceInstance.getUri();
System.out.println(uri);
String scheme = serviceInstance.getScheme();
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String eurekaUrl = scheme + "://" + host + ":" + port + "/provider/findAll";
System.out.println(eurekaUrl);
List list = restTemplate.getForObject(eurekaUrl, List.class);
return list;
}
@GetMapping("/findById/{id}")
public User findById(@PathVariable("id") Integer id) {
String baseUrl = "http://localhost:8081/provider/findById/" + id;
User user = restTemplate.getForObject(baseUrl, User.class);
return user;
}
}
运行Consumer后使用postman调用findAll接口,控制台显示如下
Ribbon负载均衡
- 首先我们需要启动两个provider
只需在启动了一个provider后,更改端口号再启动一个provider即可 启动完成后,在注册中心会看到有两个provider - 在Consumer的RestTemplate上面添加@LoadBalanced
该注释是开启负载均衡的作用
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
@Autowired
private RestTemplateBuilder templateBuilder;
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return templateBuilder.build();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
- 在Consumer的Controller层更改访问provider的形式
开启负载均衡之后,可以直接使用实例名称进行访问接口,而无需通过discoveryClient自己手动获取
以下仅显示Conusmer的findAll的更改,其余模块没有更改
@GetMapping("/findAll")
public List<User> findAll() {
String balancedUrl = "http://provider/provider/findAll";
List list = restTemplate.getForObject(balancedUrl, List.class);
return list;
}
- 启动所有微服务后,访问consumer的接口
可以看到是拿到了结果,那么我们怎么知道到底是访问了那个Provider呢,虽然实际是不用关心的,但在学习阶段我们最好关心以下 通过打印日志可以看到,刚刚请求的 8082端口的provider - PS:我们还可以通过配置文件更改负载均衡的策略
比如当前默认的是轮询机制,我们可以配置成随机之类的 研究了一下,好像是如果要针对某个服务配置负载均衡策略,就需要在配置文件里增加
provider:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
如果设置全局负载均衡策略,就需要在consumer的配置类中返回一个配置 像这样
@Bean
public IRule defaultLBStrategy(){
return new RandomRule();
}
!!!需要注意的是,两者不能同时用,用其中一个的时候,记得注释另一个
Hystrix 熔断器
插播! 插播! 紧急插播 Hystrix原理 先上手操作,再了解一下原理,还是蛮重要的
- Consumer的Pom文件中增加Hystrix的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
hystrix:
command:
default:
circuitBreaker:
forceOpen: false
errorThresholdPercentage: 50
sleepWindowInMilliseconds: 5000
requestVolumeThreshold: 10
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
- 在Conusmer的Controller层编写降级的FallBack方法
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@HystrixCommand(fallbackMethod = "findAllFallBack")
@GetMapping("/findAll")
public List<User> findAll() {
List<ServiceInstance> providers = discoveryClient.getInstances("provider");
ServiceInstance serviceInstance = providers.get(0);
URI uri = serviceInstance.getUri();
System.out.println(uri);
String scheme = serviceInstance.getScheme();
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String eurekaUrl = scheme + "://" + host + ":" + port + "/provider/findAll";
System.out.println(eurekaUrl);
List list = restTemplate.getForObject(eurekaUrl, List.class);
return list;
}
public List<User> findAllFallBack() {
System.out.println("Ops!! find All Fall Back");
return null;
}
}
!! 注意:FallBack方法的返回值必须和FindAll方法一致
-
在Provider的findAll方法中让线程睡几秒,触发熔断 因为我们配置的触发熔断时间为1秒,所以当请求返回超过1秒时就会触发熔断 -
熔断效果 在请求时,没有得到正确返回,触发了熔断 Cosumer的运行控制台输出了FallBack方法中的内容 -
PS:当我们需要所有的请求都共用一个FallBack方法时,可以配置全局熔断器 只需要两步 1、在Controller类(注意这里是类不是方法)上添加注解 @DefaultProperties(defaultFallback = “defaultFallback”) 2、在目标方法上添加注解 @HystrixCommand (注意不需要再次指定fallbackMethod) ps:暗戳戳的说一句,感觉也没有省事到哪里去
Feign远程调用
老样子,先操作后理解 插播! 插播! Feign的插播
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- Consumer启动类中添加开启Feign客户端的注释
@EnableFeignClients
- Consumer中编写Feign客户端
feign客户端在Consumer里就是一个仿照ProviderController中方法的接口,没有实质的方法实现
@FeignClient(value = "provider",path = "/provider")
public interface ConsumerClient {
@GetMapping("/findAll")
List<User> findAll() throws Exception;
}
- Consumer中的Controller使用Feign发送请求
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Qualifier("com.dean.feign.ConsumerClient")
@Autowired
private ConsumerClient consumerClient;
@GetMapping("/findAll")
public List<User> findAll() throws Exception {
return consumerClient.findAll();
}
}
- 启动后发送请求可正常获得结果
Feign的熔断器
是的,Feign里面也结合了熔断器,让熔断器的实现更加容易 如果需要进一步配置feign的熔断器,需要添加依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>9.7.0</version>
</dependency>
然后在配置文件里进行配置
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 15000
threadpool:
default:
coreSize: 40
maximumSize: 100
maxQueueSize: 100
如不需要这些高级配置只需要按下列步骤进行即可
- Consumer里实现ConsumerClient
我们之前写了一个接口ConsumerClient假装是ProviderController,那么当我们实现它的时候,它就成为了一个编写降级服务的类
@Component
public class ConsumerFallBack implements ConsumerClient {
@Override
public List<User> findAll() throws Exception {
System.out.println("feign Hystrix");
return null;
}
}
- Consumer的配置文件中编写Feign Hystrix的配置
因为Feign内置了熔断器,我们就不再需要熔断器本身的一些配置和注解了,只需要设置Feign的Hystrix配置即可
feign:
hystrix:
enabled: true
- Consumer的Feign客户端添加FallBack所在的类
@FeignClient(value = "provider",path = "/provider",fallback = ConsumerFallBack.class)
public interface ConsumerClient {
@GetMapping("/findAll")
List<User> findAll() throws Exception;
}
- 启动后,可以看到熔断器有效
Consumer的控制台这边可以看到输出了我们降级方法中的内容
GateWay网关
-
创建一个SpringBoot组件Gateway 方式同创建provider 和consumer 文件结构如下: -
Gateway的pom文件中添加依赖 注意这里有个大坑,因为我们之前在父工程的中添加了spring-boot-starter-web的依赖,但是网关使用时不能有这个依赖,所以需要我们从父工程中移除,分别添加到使用到这个依赖的子工程中(在本项目里就是consumer、provider、eureka) 一定要移除,移除完成后在gateway的pom文件中添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 编写Gateway启动类GateWayApplication
注意网关也只是个微服务,需要注册到注册中心
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class);
}
}
- 配置Gateway的配置文件Appliaction.yml
网关重在配置
server:
port: 9000
spring:
application:
name: gateway
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
routes:
- id: provider-router
uri: lb://provider
predicates:
- Path=/provider/**
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class TokenFliter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("There is GateWay");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
- 完成后顺序启动Eureka、Provider、GateWay
可以看到注册中心出现了两个服务provider和gateway 本来我们访问provider的findAll方式是这样 也就是访问的是provider服务 但当我们配置了网关以后,可以将provider的真正服务端口号隐藏起来,访问网关服务来访问到findAll 网关控制台这边也可以看到我们通过TokenFliter中的fliter方法打印的字样
路由前缀
- 网关的配置文件中更改配置(隐藏前缀)
在gateway中可以通过配置路由的过滤器PrefixPath来实现映射路径的前缀添加,可以起到隐藏接口地址的作用,避免接口地址暴露 啥意思呢 就是以我们的provider来看,我们之前请求路径是/provider/findAll,但通过配置之后我们可以隐藏/provider,只访问findAll即可
predicates:
- Path=/**
filters:
- PrefixPath=/provider
测试 可以看到通过该配置,即隐藏了实际服务的端口号也隐藏了请求的前缀
- 网关的配置文件中更改配置(增加前缀)
在gateway中通过配置路由过滤器StripPrefix,实现映射路径中的地址的去除。 通过StripPrefix=1来指定路由中需要去除的前缀个数 例如我们通过访问路径/api/provider/findAll将会被路由解析到/provider/findAll
predicates:
- Path=/api/**
filters:
- StripPrefix=1
测试 PS:以上两个也可以都加
predicates:
- Path=/api/**
filters:
- StripPrefix=1
- PrefixPath=/provider
但一定要注意StripPrefix和PrefixPath的顺序问题 写成现在这样的意思就是先去掉/api,再添加/provider,所以请求的时候可以直接用/api/findAll请求
过滤器
过滤器是网关的一个重要功能,实现了请求的鉴权,前面路由前缀的功能也是使用过滤器实现的
过滤器分为全局过滤器和局部过滤器 可以同时配置全局过滤器和局部过滤器
全局过滤器(实现GlobalFilter及Ordered) 例如上面所说的TokenFilter
@Component
public class TokenFliter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("There is GateWay");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
自定义局部过滤器 有两种实现方式
- 直接实现GatewayFilter接口,重写里面的filter方法
- 继承AbstractGatewayFilterFactory类,重写apply方法(实际上底层也是重写filter方法)
以下是第二种方式的代码示例
package com.dean.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
public static final String AUTH_NAME = "name";
public AuthGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(AUTH_NAME);
}
@Override
public GatewayFilter apply(AuthGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
System.out.println("AuthGateWay Filter");
System.out.println(config.name);
return chain.filter(exchange);
};
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
主要有几个重点: 1、类名必须是XXXGatewayFilterFactory 2、配置文件里的配置就是这个XXX 3、shortcutFieldOrder()方法必须重写,否则拿不到配置的值
gateway的yaml中路由处进行配置
routes:
- id: provider-router
uri: lb://provider
predicates:
- Path=/**
filters:
- PrefixPath=/provider
- Auth=name
其实只是多了一个我们自己增加的-Auth
通过网关访问接口后控制台的输出
PS:当我们想要配置多个路由及每个路由配置自己的过滤器时 从配置中可以看出route下的-id是个列表的格式
彩蛋:GateWay自带的过滤器 gateway自带的过滤器有几十个,常见的有
过滤器名称 | 说明 |
---|
AddRequestHeader | 对匹配上的请求加上Header | AddRequestParameters | 对匹配上的请求路由 | AddResponseHeader | 对从网关返回的响应添加Header | StripPrefifix | 对匹配上的请求路径去除前缀 | PrefifixPath | 对匹配上的请求路径添加前缀 |
例如配置文件中配置
spring:
application:
name: gateway
cloud:
gateway:
default-filters:
- AddResponseHeader=ilove,web
请求后就可以看到响应头添加了ilove=web了
感兴趣的话,可以去官方文档看看
以上。
|