SpringCloud之服务网关

网关统一服务入口,可方便实现对平台众多服务接口进行管控,对访问服务的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制,甚至基于API调用的计量或者计费等等。

1.zuul

1.1定义

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

目前zuul已更新到2.0版本,但SpringCloud官方不推荐使用zuul2.0,但支持zuul2.0。

1.2基础环境搭建

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

创建maven的父工程cloud-zuul-demo,删除src目录,在pom中导入依赖,对SpringBoot和SpringCloud版本进行锁定

 <properties>
        <spring.boot.version>2.2.2.RELEASE</spring.boot.version>
        <spring.cloud.version>Hoxton.SR1</spring.cloud.version>
    </properties>

    <!--  依赖管理,父工程锁定版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

1.3搭建服务提供者

1)新建maven子模块(cloud-provider8001),导入依赖

     <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

2)新建启动类ProviderMain8001并添加注解

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

3)配置application.yml

server:
  port: 8001

spring:
  application:
    name: cloud-consul-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

4)新建controller接口

package com.zys.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

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

    @GetMapping("/user/get")
    public String get() {
        return "我是服务提供者,端口:" + port;
    }

}

1.4搭建路由子模块

1)创建子模块(cloud-zuul-gateway9527),导入依赖

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

2)application.yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-zuul-gateway
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

#配置微服务代理
zuul:
  #忽略真实的服务名,即不能使用服务名访问,只能通过代理访问以保证安全
  ignored-services: "*"
  routes:
    #指定要代理的微服务名称和路径
    cloud-consul-provider: /users/**

这里也需要把服务注册到consul中,除此之外主要配置了网关路由的详细信息。当访问到/users路径时会转发到cloud-consul-provider服务中,根据后面的路径进行转发。

3)创建启动类

package com.zys.cloud;

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

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

4)启动测试

先启动服务提供者,再启动网关服务。浏览器输入http://localhost:9527/users/user/get可以正常访问到提供者的服务,此时路由功能就完成了。

注意:这里必须要给每个服务加一个前缀。

2.gateway

2.1定义

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

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

2.2zuul与gateway的区别

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

2.3基础环境搭建

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

创建maven的父工程cloud-gateway-demo,删除src目录,在pom中导入依赖,对SpringBoot和SpringCloud版本进行锁定

 <properties>
        <spring.boot.version>2.2.2.RELEASE</spring.boot.version>
        <spring.cloud.version>Hoxton.SR1</spring.cloud.version>
    </properties>

    <!--  依赖管理,父工程锁定版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.4搭建服务提供者

1)新建maven子模块(cloud-provider8001),导入依赖

     <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

2)新建启动类ProviderMain8001并添加注解

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

3)配置application.yml

server:
  port: 8001

spring:
  application:
    name: cloud-consul-provider
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

4)新建controller接口

package com.zys.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

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

    @GetMapping("/user/get")
    public String get() {
        return "我是服务提供者,端口:" + port;
    }

}

根据8001再创建一个服务提供者模块,端口为8002.

2.5搭建服务消费者

1)新建maven子模块(cloud-consumer80),导入依赖

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

2)新建启动类ConsumerMain80并添加注解

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

3)配置application.yml

server:
  port: 80

spring:
  application:
    name: cloud-consul-consumer
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

feign:
  client:
    config:
      #指定全局
      default:
        #连接超时时间
        connectTimeout: 5000
        #服务等待时间
        readTimeout: 5000
        loggerLevel: full

logging:
  level:
    com.zys.cloud.service.UserServiceClient: debug

3)创建服务接口UserServiceClient,对于服务提供者接口

package com.zys.cloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

//指定微服务名称
@FeignClient(value = "cloud-consul-provider")
public interface UserServiceClient {

    @GetMapping("/user/get")
    String get();
}

4)创建controller接口,将UserServiceClient注入使用

package com.zys.cloud.controller;

import com.zys.cloud.service.UserServiceClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/consumer")
public class TestController {

    @Resource
    private UserServiceClient userServiceClient;

    @GetMapping("/get")
    public String get() {
        return userServiceClient.get();
    }
}

2.6搭建路由子模块

1)创建子模块(cloud-gateway9527),导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

需要注意的是,这里不能导入web,否则会报错

2)yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
    gateway:
      #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      discovery:
        locator:
          enabled: true
      #配置路由列表
      routes:
        - id: provider_route      #路由的ID,没有固定规则但要求唯一
          uri: lb://cloud-consul-provider   #匹配后提供服务的路由地址,根据服务名访问,lb表示后台使用负载均衡
          predicates:
            - Path=/user/**          #断言,路径相匹配的进行路由

        - id: consumer_route
          uri: lb://cloud-consul-consumer
          predicates:
              - Path=/consumer/**

这里也需要把服务注册到consul中,除此之外主要配置了网关路由的详细信息。

使用断言配置了路径匹配路由,当访问/user相关的请求,都会转发到服务名为cloud-consul-provider的服务上,并把路由拼接到后面,对外暴露统一的接口。如访问http://localhost:9527/user/get那么会转发到http://cloud-consul-provider/user/get。

当然也可以对路由进行切割,需配置filters:

#配置路由列表
routes:
  - id: provider_route      #路由的ID,没有固定规则但要求唯一
    uri: lb://cloud-consul-provider   #匹配后提供服务的路由地址,根据服务名访问,lb表示后台使用负载均衡
    predicates:
       - Path=/api/user/**          #断言,路径相匹配的进行路由
    filters:
       - StripPrefix=1             #路由截取

StripPrefix的意思是将路径切掉一级,这个例子中 /api被剪。若请求路径是localhost:9527/api/user/list,则最终转发的路径是lb://cloud-consul-provider/user/list。

3)创建启动类

package com.zys.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

4)启动测试

先启动服务提供者集群,再启动服务消费者,最后启动网关服务。浏览器输入http://localhost:9527/consumer/get可以正常访问到消费者的服务,此时路由功能就完成了。多刷新几次,可以看出是服务提供者轮询的。

2.7编码方式配置路由

除了上述使用yml的方式配置路由外,还可以通过编码方式配置(不推荐)。新建配置类GateWayConfig,这里设置别的路由,避免和yml方式混淆。

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 {
    @SuppressWarnings("JavaDoc")
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("baidu_route", 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.8断言配置

上面的小节中在yml中也配置了断言,用于路由匹配。除此之外,还可以配置其他的断言。这些配置只在路由转发时生效,对于服务之间的调用,没有任何意义。

2.8.1 案例说明

假如配置服务提供者请求在某个时间后生效。只需要在yml的predicates设置After即可:

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

配置后访问http://localhost:9527/user/get,在此时间之前访问,会返回404信息,只有过了设置时间才能正常访问。

需要注意是的,后面的时间格式必须正确,获取格式时间的方法:

    public static void main(String[] args) {
        //获取当前的时间
        ZonedDateTime zbj = ZonedDateTime.now();
        int year = 2021, month = 8, day = 10, hour = 10, minute = 20, second = 0;
        //获取指定的时间
        ZonedDateTime of = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault());
        System.out.println(zbj);
        System.out.println(of);
    }

可根据需求进行获取时间格式。

2.8.2其他的配置

1)配置Before

表示在某个时间之前

predicates:
    - Path=/user/**             
    - Before=2020-04-09T21:15:28.872+08:00[Asia/Shanghai]

Between表示在某个时间段内,配置两个时间,中间用逗号隔开。

2)配置Method

Method用于配置请求的方法,若设置必须是post请求

predicates:
    - Path=/user/**             
    - Method=GET

2.9查看网关路由规则

在开发时,若需要查看网关路由规则信息,可以在yml中进行开启,默认是关闭的:

management:
  endpoints:
    web:
      exposure:
        #开启所有web端点暴露
        include: "*"

配置后,重启网关服务,访问http://localhost:9527/actuator/gateway/routes,即可看到相关的信息:

2.10过滤器配置

在路由中提供了许多了过滤器配置,可以直接使用,但也可以根据需求自定义配置,把公共的服务放到网关中。例如在请求中header必须携带token才能访问。

创建过滤器类MyGateWayFilter 

package com.zys.cloud.config;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


/**
 * 自定义过滤器
 */
@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    private static Logger log = LoggerFactory.getLogger(MyLogGateWayFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("token");
        //非法用户直接拦截
        if (token == null) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            Map<String, Object> map = new HashMap<>();
            map.put("code", 401);
            map.put("msg", "未授权,无法访问");
            byte[] errs = JSON.toJSONBytes(map);
            DataBuffer buffer = response.bufferFactory().wrap(errs);
            return response.writeWith(Flux.just(buffer));
        }
        return chain.filter(exchange);
    }

    //order是过滤器的执行顺序,数字越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

重启9527,访问地址http://127.0.0.1:9527/user/get请求头中未携带token就无法访问,而携带token可以正常访问。

为了能够让读者正常访问,故先把此过滤器注释。

2.11跨域配置

跨域配置有两种方式,一种是在配置文件配置,一种是通过编码方式。这里只介绍配置文件 方式,因为编码方式和SpringBoot配置跨域类似:

spring:
  cloud:
    gateway:
      #跨域配置
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"

只需一个配置即可。

2.12项目部署

对于网关项目,不能使用war方式启动,必须使用jar方式。因此需要将其打包为jar包,在启动时需要指定编码:

java -Dfile.encoding=utf-8 -jar xxx-gateway.jar

若不配置编码,则启动时会读取不到nacos的配置内容。

posted @ 2020-04-12 16:10  钟小嘿  阅读(513)  评论(0编辑  收藏  举报