微服务下使用网关 Spring Cloud Gateway【vnsc5858威尼斯

时间:2019-05-31 12:50来源:计算机教程
如何启动 Spring Cloud Gateway 1、新建 Maven 工程,添加相关依赖 pom.xml ?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:schemaLoc

如何启动 Spring Cloud Gateway

1、新建 Maven 工程,添加相关依赖 pom.xml

<?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.anoyi</groupId>
    <artifactId>core-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>core-gateway</name>
    <description>gateway for miroservice</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-gateway</artifactId>
                <version>2.0.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、添加启动类 Application.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;

@Configuration
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

3、启动 Application(和 Spring Boot 项目一样)

访问 http://localhost:8080/ 报错 404,同时日志输出:

2018-06-27 09:18:48.981  WARN 44156 --- [ctor-http-nio-2] .a.w.r.e.DefaultErrorWebExceptionHandler : 
Failed to handle request [GET http://localhost:8080/]: Response status 404

配置服务的路由:配置文件方式

假设本地启动了另外两个 Spring Boot 服务,分别是 服务A( http://localhost:8081 )、服务B( http://localhost:8082 ),下面通过 Spring Cloud Gateway 来路由到这两个服务。

1、在 resources 路径下添加配置文件 application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://localhost:8081
        predicates:
        - Path=/a/**
        filters:
        - StripPrefix=1
      - id: host_route
        uri: http://localhost:8082
        predicates:
        - Path=/b/**
        filters:
        - StripPrefix=1

id:固定,不同 id 对应不同的功能,可参考 官方文档
uri:目标服务地址
predicates:路由条件
filters:过滤规则
2、重启 Gateway 服务

3、测试

访问 http://localhost:8080/a/ 路由到 服务A http://localhost:8081/

访问 http://localhost:8080/b/ 路由到 服务B http://localhost:8082/

其他地址,例如 http://localhost:8080/a/user/all 路由到 服务A http://localhost:8081/user/all

配置服务的路由:编码方式

实现如上服务路由,还可以通过编码的方式实现。

1、删除配置文件 application.yml

2、修改 Application.java, 添加自定义路由配置

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        StripPrefixGatewayFilterFactory.Config config = new StripPrefixGatewayFilterFactory.Config();
        config.setParts(1);
        return builder.routes()
                .route("host_route", r -> r.path("/a/**").filters(f -> f.stripPrefix(1)).uri("http://localhost:8081"))
                .route("host_route", r -> r.path("/b/**").filters(f -> f.stripPrefix(1)).uri("http://localhost:8082"))
                .build();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

其他功能

http://cloud.spring.io/spring-cloud-gateway/single/spring-cloud-gateway.html

官方提供了大量的路由规则,比如Time、Host、Header 等等,同时也提供了大量的过滤器,比如AddRequestHeader、AddRequestParameter、AddResponseHeader 等等。仅通过简单的配置即可实现功能强大的网关服务。

推荐阅读:https://www.roncoo.com/course/view/ad054a612db54315927a232ea722a03b

文章来源:https://www.jianshu.com/p/1c942a8abe18?utm_source=desktop&utm_medium=timeline

快速上手

引入spring-boot2.1.1.RELEASE ,springcloud的版本为Greenwich.M3

    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.1.1.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>        <properties>        <java.version>1.8</java.version>        <spring-cloud.version>Greenwich.M3</spring-cloud.version>    </properties>        <dependencyManagement>        <dependencies>            <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>

添加的依赖包如下

        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-gateway</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-webflux</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>        </dependency>

注意springcloud gateway使用的web框架为webflux,和springMVC不兼容。引入的限流组件是hystrix。redis底层不再使用jedis,而是lettuce。

Spring Cloud Gateway 工作原理

 

vnsc5858威尼斯城官网 1

 

客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特定的请求过滤器链。

过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求之前或之后执行逻辑。所有 "pre" 过滤器逻辑先执行,然后执行代理请求,代理请求完成后,执行 "post" 过滤器逻辑。

修改接口返回报文

因为网关路由的接口返回报文格式各异,并且网关也有有一些限流、认证、熔断降级的返回报文,为了统一这些报文的返回格式,网关必须要对接口的返回报文进行修改,过滤器代码如下:

package org.gateway.filter.global;import java.nio.charset.Charset;import org.gateway.response.Response;import org.reactivestreams.Publisher;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.core.io.buffer.DataBufferFactory;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.http.server.reactive.ServerHttpResponseDecorator;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import com.alibaba.fastjson.JSON;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;@Componentpublic class WrapperResponseFilter implements GlobalFilter, Ordered {    @Override    public int getOrder() {        // -1 is response write filter, must be called before that        return -2;    }    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        ServerHttpResponse originalResponse = exchange.getResponse();        DataBufferFactory bufferFactory = originalResponse.bufferFactory();        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {            @Override            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {                if (body instanceof Flux) {                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;                    return super.writeWith(fluxBody.map(dataBuffer -> {                        // probably should reuse buffers                        byte[] content = new byte[dataBuffer.readableByteCount()];                        dataBuffer.read;                        // 释放掉内存                        DataBufferUtils.release(dataBuffer);                        String rs = new String(content, Charset.forName("UTF-8"));                        Response response = new Response();                        response.setCode("1");                        response.setMessage("请求成功");                        response.setData;                                                byte[] newRs = JSON.toJSONString.getBytes(Charset.forName("UTF-8"));                        originalResponse.getHeaders().setContentLength(newRs.length);//如果不重新设置长度则收不到消息。                        return bufferFactory.wrap;                    }));                }                // if body is not a flux. never got there.                return super.writeWith;            }        };        // replace response with decorator        return chain.filter(exchange.mutate().response(decoratedResponse).build;    }}

需要注意的是order需要小于-1,需要先于NettyWriteResponseFilter过滤器执行。

有了一个这样的过滤器,我们就可以统一返回报文格式了。

熔断

当下游接口负载很大,或者接口不通等其他原因导致超时,如果接口不熔断的话将会影响到下游接口得不到喘息,网关也会因为超时连接一直挂起,很可能因为一个子系统的问题导致整个系统的雪崩。所以我们的网关需要设计熔断,当因为熔断器打开时,网关将返回一个降级的应答。

熔断配置如下:

server.port: 8082spring:  application:    name: gateway  redis:      host: localhost      port: 6379      password: 123456  cloud:    gateway:      routes:        - id: rateLimit_route          uri: http://localhost:8000          order: 0          predicates:            - Path=/foo/**          filters:            - StripPrefix=1            - name: RateLimiter            - name: Hystrix              args:                name: fallbackcmd                fallbackUri: forward:/fallback

hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 5000

package org.gateway.controller;import org.gateway.response.Response;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class FallbackController {    @GetMapping("/fallback")    public Response fallback() {        Response response = new Response();        response.setCode("100");        response.setMessage("服务暂时不可用");        return response;    }}

注意需要设置commandKey的超时时间。其他的hystrix配置请访问Hystrix wiki.

动态配置路由和过滤器

最后我们来看一下如何动态配置路由和过滤器。

定义路由实体

/** * Gateway的路由定义模型 */public class GatewayRouteDefinition {    /**     * 路由的Id     */    private String id;    /**     * 路由断言集合配置     */    private List<GatewayPredicateDefinition> predicates = new ArrayList<>();    /**     * 路由过滤器集合配置     */    private List<GatewayFilterDefinition> filters = new ArrayList<>();    /**     * 路由规则转发的目标uri     */    private String uri;    /**     * 路由执行的顺序     */    private int order = 0;}

路由断言实体

/** * 路由断言定义模型 */public class GatewayPredicateDefinition {    /**     * 断言对应的Name     */    private String name;    /**     * 配置的断言规则     */    private Map<String, String> args = new LinkedHashMap<>();
}

过滤器实体

/** * 过滤器定义模型 */public class GatewayFilterDefinition {    /**     * Filter Name     */    private String name;    /**     * 对应的路由规则     */    private Map<String, String> args = new LinkedHashMap<>();}

路由增删改controller

package org.gateway.controller;import java.net.URI;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import org.gateway.model.GatewayFilterDefinition;import org.gateway.model.GatewayPredicateDefinition;import org.gateway.model.GatewayRouteDefinition;import org.gateway.route.DynamicRouteServiceImpl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.filter.FilterDefinition;import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;import org.springframework.cloud.gateway.route.RouteDefinition;import org.springframework.util.CollectionUtils;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.util.UriComponentsBuilder;@RestController@RequestMapping("/route")public class RouteController {    @Autowired    private DynamicRouteServiceImpl dynamicRouteService;    /**     * 增加路由     * @param gwdefinition     * @return     */    @PostMapping("/add")    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {        try {            RouteDefinition definition = assembleRouteDefinition(gwdefinition);            return this.dynamicRouteService.add(definition);        } catch (Exception e) {            e.printStackTrace();        }        return "succss";    }    @GetMapping("/delete/{id}")    public String delete(@PathVariable String id) {        return this.dynamicRouteService.delete;    }    @PostMapping("/update")    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {        RouteDefinition definition = assembleRouteDefinition(gwdefinition);        return this.dynamicRouteService.update(definition);    }    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {        RouteDefinition definition = new RouteDefinition();        List<PredicateDefinition> pdList=new ArrayList<>();        definition.setId(gwdefinition.getId;        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {            PredicateDefinition predicate = new PredicateDefinition();            predicate.setArgs(gpDefinition.getArgs;            predicate.setName(gpDefinition.getName;            pdList.add(predicate);        }                List<GatewayFilterDefinition> gatewayFilterDefinitions = gwdefinition.getFilters();        List<FilterDefinition> filterList = new ArrayList<>();        if (!CollectionUtils.isEmpty(gatewayFilterDefinitions)) {            for (GatewayFilterDefinition gatewayFilterDefinition : gatewayFilterDefinitions) {                FilterDefinition filterDefinition = new FilterDefinition();                filterDefinition.setName(gatewayFilterDefinition.getName;                filterDefinition.setArgs(gatewayFilterDefinition.getArgs;                filterList.add(filterDefinition);            }        }        definition.setPredicates;        definition.setFilters(filterList);        URI uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri.build;        definition.setUri;        return definition;    }}

动态路由service

package org.gateway.route;import java.net.URI;import java.util.Arrays;import java.util.HashMap;import java.util.Map;import org.gateway.model.GatewayPredicateDefinition;import org.gateway.model.GatewayRouteDefinition;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.event.RefreshRoutesEvent;import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;import org.springframework.cloud.gateway.route.RouteDefinition;import org.springframework.cloud.gateway.route.RouteDefinitionWriter;import org.springframework.context.ApplicationEventPublisher;import org.springframework.context.ApplicationEventPublisherAware;import org.springframework.stereotype.Service;import org.springframework.web.util.UriComponentsBuilder;import com.alibaba.fastjson.JSON;import reactor.core.publisher.Mono;@Servicepublic class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {    @Autowired    private RouteDefinitionWriter routeDefinitionWriter;    private ApplicationEventPublisher publisher;    /**     * 增加路由     * @param definition     * @return     */    public String add(RouteDefinition definition) {        routeDefinitionWriter.save(Mono.just(definition)).subscribe();        this.publisher.publishEvent(new RefreshRoutesEvent(this));        return "success";    }    /**     * 更新路由     * @param definition     * @return     */    public String update(RouteDefinition definition) {        try {            this.routeDefinitionWriter.delete(Mono.just(definition.getId;        } catch (Exception e) {            return "update fail,not find route  routeId: " definition.getId();        }        try {            routeDefinitionWriter.save(Mono.just(definition)).subscribe();            this.publisher.publishEvent(new RefreshRoutesEvent(this));            return "success";        } catch (Exception e) {            return "update route  fail";        }    }    /**     * 删除路由     * @param id     * @return     */    public String delete(String id) {        try {            this.routeDefinitionWriter.delete(Mono.just;            return "delete success";        } catch (Exception e) {            e.printStackTrace();            return "delete fail";        }    }    @Override    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {        this.publisher = applicationEventPublisher;    }}

上面 routeDefinitionWriter的实现默认是InMemoryRouteDefinitionRepository,将路由存在内存中,我们可以自己实现一个将路由存在redis中的repository。

this.publisher.publishEvent(new RefreshRoutesEvent;则会将CachingRouteLocator中的路由缓存清空。

以上只是springcloud gateway支持的一小部分功能。

虽然springcloud gateway 才发布不久,相关的文档还不是很完善,代码中充满了TODO的地方,react代码友好性低。但是由于它的高性能而且是spring自己的框架,未来取代zuul不是没有可能。

编辑:计算机教程 本文来源:微服务下使用网关 Spring Cloud Gateway【vnsc5858威尼斯

关键词: