SpringCloud之服务网关
阅读原文时间:2021年07月05日阅读:1

1.1定义

zuul叫路由网关,它包含对请求的路由和过滤的功能。

路由负责将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。而过滤是负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能基础。

1.2项目开发

源代码:https://github.com/zhongyushi-git/cloud-zuul.git

1)参考ribbon的项目新建一个项目cloud-zuul,把eureka集群、api以及服务提供者8001拿过来

2)在pom中导入依赖  

<!--web-->  
    <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>  
    <!--eureka-client-->  
    <dependency>  
        <groupId>org.springframework.cloud</groupId>  
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>  
    </dependency>  
    <!-- zuul路由网关 -->  
    <dependency>  
        <groupId>org.springframework.cloud</groupId>  
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>  
    </dependency>

3)配置yml

server:
port: 9527

spring:
application:
name: cloud-zuul-gateway

#把客户端注册到服务列表中
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
#设置入驻的服务的名称,是唯一的
instance:
instance-id: gateway-9527.com
#访问路径显示ip
prefer-ip-address: true

4)在hosts文件添加映射

127.0.0.1 myzuul.com

5)在com.zys.cloud包下创建启动类并添加注解@EnableZuulProxy

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class ZuulMain9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulMain9527.class, args);
}
}

6)启动测试

先启动eureka集群,然后启动微服务提供者8001,最后启动路由9527。

不用路由访问:http://localhost:8001/user/get/1
使用路由访问: http://myzuul.com:9527/cloud-provider/user/get/1,其中cloud-provider是微服务提供者的名称
在使用路由访问的时候,暴露出了微服务的名称,但是一般情况下是不会这样做的,因此需要对微服务名称进行代理。

7)配置服务代理

在路由的yml配置代理的名称并设置不能使用微服务名进行访问。

#配置微服务代理
zuul:

routes:
#指定要代理的微服务名称
myuser.serviceId: cloud-provider
#指定代理的路径
myuser.path: /myuser/**
#忽略真实的服务名,即不能使用服务名访问,只能通过代理访问以保证安全
#忽略单个微服务
ignored-services: cloud-provider
#批量忽略微服务

ignored-services: "*"

重启路由的服务,访问http://myzuul.com:9527/myuser/user/get/1是没有问题的,访问http://myzuul.com:9527/cloud-provider/user/get/1出现404,已达到效果。

实例中设置忽略了单个微服务,如果要批量忽略微服务,给ignored-services设置为"*"即可。

8)设置统一前缀

有时候多个系统之间访问的路径需要相同的前缀,这个时候只需要在yml配置即可。

zuul:
#设置统一的前缀
prefix: /springcloud

然后访问的时候都需要加这个前缀,访问路径是http://myzuul.com:9527/springcloud/myuser/user/get/1。

2.1定义

旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤功能,如熔断、限流、重试等。三大核心概念如下:

Route(路由):是构建网关的基本模块,由ID、URL、一系列断言和过滤器组成。
Predicate(断言):用于匹配Http请求中的所有内容,如果匹配则进行路由。
Filter(过滤):指的是Spring框架中GatewayFilter的实例

2.2zuul与gateway的区别

它是基于WebFlux框架实现的,而WebFlux框架底层使用了高性能的Reactor模式通信框架Netty,是基于异步非阻塞模型开发的。而zuul使用的是阻塞框架。

2.3项目开发

源代码:https://github.com/zhongyushi-git/cloud-gateway.git

2.3.1环境搭建

1)创建maven的父工程cloud-gateway,导入依赖


UTF-8 1.8 1.8 4.12

<!--  依赖管理,父工程锁定版本-->  
<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>  
        <!--junit-->  
        <dependency>  
            <groupId>junit</groupId>  
            <artifactId>junit</artifactId>  
            <version>${junit.version}</version>  
        </dependency>  
    </dependencies>  
</dependencyManagement>

<build>  
    <plugins>  
        <plugin>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-maven-plugin</artifactId>  
            <configuration>  
                <fork>true</fork>  
                <addResources>true</addResources>  
            </configuration>  
        </plugin>  
    </plugins>  
</build>

2)创建eureka服务子模块cloud-eureka-server7001,导入依赖



org.springframework.cloud spring-cloud-starter-netflix-eureka-server

org.springframework.boot spring-boot-starter-web
org.springframework.boot spring-boot-starter-actuator

</dependencies>

3)eureka的yml配置

server:
port: 7001

eureka:
instance:
#eureka服务端的实例名称
hostname: localhost
client:
#false表示不向注册中心注册自己
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

4)创建eureka启动类

package com.zys.cloud;

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);
}
}

5)创建服务提供者子模块cloud-provider8001,导入依赖

org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator

6)8001的yml配置

server:
port: 8001

spring:
application:
name: cloud-provider

#把客户端注册到服务列表中
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/
#设置入驻的服务的名称,是唯一的
instance:
instance-id: cloud-provider8001
#访问路径显示ip
prefer-ip-address: true

7)创建8001启动类

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ProviderMain8001 {
public static void main(String[] args) {
SpringApplication.run(ProviderMain8001.class, args);
}
}

8)创建8001的controller接口

package com.zys.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@RestController
@RequestMapping("/user")
public class UserController {

@Value("${server.port}")  
private String port;

@Value("${eureka.client.service-url.defaultZone}")  
private String eureka;

@GetMapping("/get")  
public String get(){  
    return "server port is \\t"+port+".\\t\\t\\t\\t time:"+new Date();  
}

@GetMapping("/get2")  
public String get2(){  
    return "eureka server is \\t"+eureka+".\\t\\t\\t\\t time:"+new Date();  
}

}

9)创建服务提供者子模块cloud-provider8002,方式同8001,只是端口号不同。创建完成后先启动eureka,然后启动8001和8002,在浏览器测试接口。

2.3.2创建路由子模块cloud-gateway9527

1)导入依赖

org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-gateway

需要注意的是,这里不需要导入web。

2)yml配置

server:
port: 9527

spring:
application:
name: cloud-gateway
cloud:
gateway:
#开启从注册中心动态创建路由的功能,利用微服务名进行路由
discovery:
locator:
enabled: true
routes:
- id: provider_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: lb://cloud-provider #匹配后提供服务的路由地址,根据服务名访问
predicates:
- Path=/user/get/** #断言,路径相匹配的进行路由

    - id: provider\_routh   #路由的ID,没有固定规则但要求唯一,简易配合服务名  
      uri: lb://cloud-provider     #匹配后提供服务的路由地址  
      predicates:  
        - Path=/user/get2/\*\*             #断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
instance-id: cloud-gateway
#访问路径显示ip
prefer-ip-address: true
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/

3)创建启动类

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}

4)启动测试

先启动eureka,然后启动8001和8002,最后启动9527。浏览器输入http://127.0.0.1:9527/user/get2就可以正常访问了。多刷新几次,可以看出是8001和8002轮询的。

2.3.3动态路由配置

除了2.3.2中使用yml的方式配置路由外,还可以通过编码方式配置。

新建配置类GateWayConfig

package com.zys.cloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 编码方式配置路由
*/
@Configuration
public class GateWayConfig {
/**
* 配置一个id为route-name的路由规则,
* 当访问地址http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
* @param routeLocatorBuilder
* @return
*/
@SuppressWarnings("JavaDoc")
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route1",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}

重启9527,访问地址http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei。

2.3.4断言配置

有时候为了进行配置,就可以配置断言。

例如:配置get2请求在某个时间后生效。只需要在yml的predicates设置After即可。

predicates:
- Path=/user/get2/** #断言,路径相匹配的进行路由
- After=2020-04-09T21:15:28.872+08:00[Asia/Shanghai]

后面的时间格式必须正确,获取格式时间的方法

public static void getTime(){
ZonedDateTime zbj = ZonedDateTime.now();
System.out.println(zbj);
}

又如设置请求的方法必须为get

predicates:
- Path=/user/get2/** #断言,路径相匹配的进行路由
- After=2020-04-09T21:15:28.872+08:00[Asia/Shanghai]
- Method=GET

2.3.5过滤器配置

自定义一个过滤器,只有在请求中包含unmae且不为null时才能访问。创建过滤器类MyLogGateWayFilter

package com.zys.cloud.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* 自定义过滤器
*/
@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//每次进来后判断带不带uname这个key
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
//uname为null非法用户
if(uname == null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}

重启9527,访问地址http://127.0.0.1:9527/user/get2时无法访问,而http://127.0.0.1:9527/user/get2?uname=1可以正常访问。