[spring guides]网关入门
目的
构建一个spring cloud的网关
实现
简单demo
-
通过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>
-
创建一个Route,路由表。spring cloud gateway通过route来把请求转发给下游服务。这个demo中我们把所有的请求都转发给HttpBin。route的配置方法有很多种,常见的可以通过配置文件的方式或者java api的方式。这里使用java api的方式。
-
创建一个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(); }
-
运行测试一下
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
。 -
顺道测试了一下,如果我们自己写一个get 的
GetMapping
,那么请求会直接返回自己定义的GetMapping
,而不是走到route里面定义的转发。
使用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());
});
}
}