[spring guides]网关入门

原文

目的

构建一个spring cloud的网关

实现

简单demo

  1. 通过spring initializr来初始化一个spring cloud gateway项目,Gateway属于Routing(路由)模块。Routing里面包含了Gateway,OpenFeign,Cloud LoadBalancer项目。maven会添加spring-cloud-starter-gateway依赖。

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    
  2. 创建一个Route,路由表。spring cloud gateway通过route来把请求转发给下游服务。这个demo中我们把所有的请求都转发给HttpBin。route的配置方法有很多种,常见的可以通过配置文件的方式或者java api的方式。这里使用java api的方式。

  3. 创建一个RouteLocator的Bean,参数是RouteLocatorBuilder,他的routes方法返回的Builder对象可以方便我们编写predicate和filter来根据条件处理请求。让我们来实现把/get转发到https://httpbin.org/get,同时在header上加入hello world。

    @Bean
    public RouteLocator myRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("test-route1", predicateSpec -> predicateSpec
                        .path("/get")// 处理/get的请求
                        .filters(gatewayFilterSpec -> gatewayFilterSpec.addRequestHeader("hello", "world"))// 对这个请求应用一个filter,具体就是给request的header加上hello world
                        .uri("http://httpbin.org")// 请求转发到httpbin去
                )
                .build();
    }
    
  4. 运行测试一下

    curl http://localhost:8080/get
    
    
    // 返回
    {
        "args": {},
        "headers": {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Cache-Control": "no-cache",
            "Content-Length": "0",
            "Forwarded": "proto=http;host=\"localhost:8080\";for=\"0:0:0:0:0:0:0:1:64073\"",
            "Hello": "world",//这里是加入的hello world
            "Host": "httpbin.org",
            "User-Agent": "PostmanRuntime/7.26.5",
            "X-Amzn-Trace-Id": "Root=1-5f869ff3-0db7117c0d97563a670a294f",
            "X-Forwarded-Host": "localhost:8080"
        },
        "origin": "0:0:0:0:0:0:0:1, xxx.xxx.xxx.xxx",
        "url": "http://localhost:8080/get"
    }
    
    

    可以看到请求被转发到了httpbin,因为我们并没有写自己/get

  5. 顺道测试了一下,如果我们自己写一个get 的GetMapping,那么请求会直接返回自己定义的GetMapping,而不是走到route里面定义的转发。

使用Hystrix

springcloud-hystrix

简介

它是Netflix公司出的,2018年11月17发布了最后一个版本后,就转到了维护阶段,因为功能上已经满足他们的需求。现在他们内部新的系统使用resilience4j来实现Hystrix的功能。

分布式系统中,服务之间存在非常多的相互依赖,当某个依赖项出现不可用的情况(这个是无法避免的)的时候,Hystrix会提供一个fallback的方法,快速返回结果(虽然是错误的),来避免错误整个系统出现一连串级联的调用报错之类的。

顺道提一下,他通过命令模式来实现,也就是说把具体方法的执行交给了命令实现对象。

一个简单demo

//首先通过继承HystrixCommand<String>来定义一个返回String的Command。
public class HelloFailureCommand extends HystrixCommand<String> {
    //一般给命令起个名字
    private String name;

    public HelloFailureCommand(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }
    //正常情况下会执行run方法,这里模拟run方法出错。
    @Override
    protected String run() throws Exception {
        throw new RuntimeException("this command always fails");
    }
    //提供一个fallback,用于正常run方法出错的时候调用
    @Override
    protected String getFallback() {
        return "Hello Failure " + name + "!";
    }
}
@Test
public void testSynchronous() {
  	//调用execute来执行run
    assertEquals("Hello Failure World!", new HelloFailureCommand("World").execute());
    assertEquals("Hello Failure Bob!", new HelloFailureCommand("Bob").execute());
}

使用

我们来实现一个circuit breaker(断路器)来实现服务降级。当服务调用失败,返回一个自定义信息。

Maven依赖

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

httpbin中有个delay(延迟)服务,地址http://httpbin.org/delay/3 表示延迟3秒后返回数据。springcloud-gateway默认的超时时间为1秒,可以通过hystrix.command.default.execution.isolation.thread.timeoutInMillseconds来设置。如下设置成4秒。

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 4000

route

@Bean
public RouteLocator myRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("hystrix-route", predicateSpec -> predicateSpec
                    .host("*.hystrix.com")//处理host=*.hystrix.com的请求。
                    .filters(gatewayFilterSpec -> gatewayFilterSpec
                            .hystrix(config -> {
                                        config
                                                .setName("mycmd")
                                                .setFallbackUri("forward:/fallback");//设置错误的之后的地址
                                    }
                            ))
                    .uri("http://httpbin.org"))
            .build();
}

测试

$ curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/4
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 8

fallback  

上面设置了delay为4秒,那么httpbin的请求时间就会超过4秒,就会触发hystrix的fallback,然后根据设置的fallbackUri,转发到/fallback地址去,返回fallback的内容。

$ curl --dump-header - --header 'Host: www.hystrix.com' http://localhost:8080/delay/2
HTTP/1.1 200 OK
Date: Fri, 16 Oct 2020 06:33:25 GMT
Content-Type: application/json
Content-Length: 470
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "args": {},
  "data": "",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "*/*",
    "Content-Length": "0",
    "Forwarded": "proto=http;host=www.hystrix.com;for=\"127.0.0.1:59041\"",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.64.1",
    "X-Amzn-Trace-Id": "Root=1-5f893eb3-40498f08215677a430065c80",
    "X-Forwarded-Host": "www.hystrix.com"
  },
  "origin": "127.0.0.1, x.x.x.x",
  "url": "http://www.hystrix.com/delay/2"
}

测试delay为2秒,httpbin在4秒内返回了数据,gateway就可以显示httpbin的返回。

测试

wiremock是个nb的东西。

添加maven依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-web</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>
// 模拟返回的body
class TempBody {
    private HashMap<String, String> headers;

    public HashMap<String, String> getHeaders() {
        return headers;
    }
    public void setHeaders(HashMap<String, String> headers) {
        this.headers = headers;
    }
}
package com.lou.demo5;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.assertj.core.api.Assertions.*;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"httpbin=http://localhost:${wiremock.server.port}"})
@AutoConfigureWireMock(port = 0)//随机端口
class Demo5ApplicationTests {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void contextLoads() {
      	final TempBody getBody = new TempBody();
      	final HashMap<String, String> headers = new HashMap<>();
      	headers.put("Hello", "World");
    		getBody.setHeaders(headers);
      	//通过stub 来mock http服务
        stubFor(get(urlEqualTo("/get"))
                .willReturn(aResponse()
                        //设置返回的body
                        .withBody(JSON.toJSONString(getBody))
                        //设置类型
                        .withHeader("Content-Type", "application/json")//
                ));

        stubFor(get(urlEqualTo("/delay/3"))
                .willReturn(aResponse()
                        .withBody("fallback")
                        .withFixedDelay(3000))
        );

        webTestClient
                .get().uri("/get")
                .exchange()
                .expectStatus().isOk()
                .expectBody()
                .jsonPath("$.headers.Hello").isEqualTo("World");
        webTestClient
                .get().uri("/delay/3")
                .header("Host", "www.hystrix.com")
                .exchange()
                .expectStatus().isOk()
                .expectBody()
                .consumeWith(entityExchangeResult -> {
                    assertThat(entityExchangeResult.getResponseBody()).isEqualTo("fallback".getBytes());
                });
    }
}
posted @ 2020-10-19 09:38  Sheldon_Lou  阅读(26)  评论(0编辑  收藏