本文为博主原创,转载请注明出处:

1.spring cloud gateway 配置路由

  在网关模块的配置文件中配置路由:

spring:
cloud:
gateway:
routes:
- id: user
uri: lb://user-server
predicates:
- Path=/api-web/** #前端访问需加入例如 http:ip:port/api-web
filters:
- StripPrefix=1 #访问后端服务过滤掉m 必填否则找不到后端服务也可以在服务加上统一路径

  其中lb表示采用了负载均衡,user-server表示服务名

  当后端有多个服务节点时,网关会以负载均衡的方式将请求发送到后端的各个服务节点上,当某个服务节点关闭以后,后续的请求不会发送到该节点上。这个过程会存在一定的时间延迟,比如30秒左右。

2.查看 GatewayLoadBalancerClientAutoConfiguration 的配置类

  这个配置类会加载一个过滤器,使用这个过滤器可以实现负载均衡

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter({RibbonAutoConfiguration.class})
@EnableConfigurationProperties({LoadBalancerProperties.class})
public class GatewayLoadBalancerClientAutoConfiguration {
public GatewayLoadBalancerClientAutoConfiguration() {
} @Bean
@ConditionalOnBean({LoadBalancerClient.class})
@ConditionalOnMissingBean({LoadBalancerClientFilter.class, ReactiveLoadBalancerClientFilter.class})
public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
// 会加载一个负载均衡的 过滤器 :LoadBalancerClientFilter
return new LoadBalancerClientFilter(client, properties);
}
}

3.查看 LoadBalancerClientFilter 过滤器的实现

  查看该过滤器的实现

public class LoadBalancerClientFilter implements GlobalFilter, Ordered {  
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties; public LoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
this.loadBalancer = loadBalancer;
this.properties = properties;
} public int getOrder() {
return 10100;
} public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
} ServiceInstance instance = this.choose(exchange);
if (instance == null) {
throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost());
} else {
URI uri = exchange.getRequest().getURI();
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
} URI requestUrl = this.loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
} exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
} else {
return chain.filter(exchange);
}
} protected ServiceInstance choose(ServerWebExchange exchange) {
    // 该loadBalancer 为ribbon 配置的负载均衡器,会根据指定的规则进行负载均衡,默认是轮询            
    return this.loadBalancer.choose(((URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR)).getHost());  
  }
}

  

  NettyRoutingFilter 过滤器:

  

public class NettyRoutingFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(NettyRoutingFilter.class);
private final HttpClient httpClient;
private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider;
private final HttpClientProperties properties;
private volatile List<HttpHeadersFilter> headersFilters; public NettyRoutingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider, HttpClientProperties properties) {
this.httpClient = httpClient;
this.headersFiltersProvider = headersFiltersProvider;
this.properties = properties;
} public List<HttpHeadersFilter> getHeadersFilters() {
if (this.headersFilters == null) {
this.headersFilters = (List)this.headersFiltersProvider.getIfAvailable();
} return this.headersFilters;
} public int getOrder() {
return 2147483647;
} public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {
ServerWebExchangeUtils.setAlreadyRouted(exchange);
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
String url = requestUrl.toASCIIString();
HttpHeaders filtered = HttpHeadersFilter.filterRequest(this.getHeadersFilters(), exchange);
DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Flux<HttpClientResponse> responseFlux = ((RequestSender)this.getHttpClient(route, exchange).headers((headers) -> {
headers.add(httpHeaders);
headers.remove("Host");
if (preserveHost) {
String host = request.getHeaders().getFirst("Host");
headers.add("Host", host);
} }).request(method).uri(url)).send((req, nettyOutbound) -> {
if (log.isTraceEnabled()) {
nettyOutbound.withConnection((connection) -> {
log.trace("outbound route: " + connection.channel().id().asShortText() + ", inbound: " + exchange.getLogPrefix());
});
} return nettyOutbound.send(request.getBody().map(this::getByteBuf));
}).responseConnection((res, connection) -> {
exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);
exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach((entry) -> {
headers.add((String)entry.getKey(), (String)entry.getValue());
});
String contentTypeValue = headers.getFirst("Content-Type");
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put("original_response_content_type", contentTypeValue);
} this.setResponseStatus(res, response);
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(this.getHeadersFilters(), headers, exchange, Type.RESPONSE);
if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {
response.getHeaders().remove("Transfer-Encoding");
} exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
response.getHeaders().putAll(filteredResponseHeaders);
return Mono.just(res);
});
Duration responseTimeout = this.getResponseTimeout(route);
if (responseTimeout != null) {
responseFlux = responseFlux.timeout(responseTimeout, Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))).onErrorMap(TimeoutException.class, (th) -> {
return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th);
});
} return responseFlux.then(chain.filter(exchange));
} else {
return chain.filter(exchange);
}
} protected ByteBuf getByteBuf(DataBuffer dataBuffer) {
if (dataBuffer instanceof NettyDataBuffer) {
NettyDataBuffer buffer = (NettyDataBuffer)dataBuffer;
return buffer.getNativeBuffer();
} else if (dataBuffer instanceof DefaultDataBuffer) {
DefaultDataBuffer buffer = (DefaultDataBuffer)dataBuffer;
return Unpooled.wrappedBuffer(buffer.getNativeBuffer());
} else {
throw new IllegalArgumentException("Unable to handle DataBuffer of type " + dataBuffer.getClass());
}
} private void setResponseStatus(HttpClientResponse clientResponse, ServerHttpResponse response) {
HttpStatus status = HttpStatus.resolve(clientResponse.status().code());
if (status != null) {
response.setStatusCode(status);
} else {
while(true) {
if (!(response instanceof ServerHttpResponseDecorator)) {
if (!(response instanceof AbstractServerHttpResponse)) {
throw new IllegalStateException("Unable to set status code " + clientResponse.status().code() + " on response of type " + response.getClass().getName());
} ((AbstractServerHttpResponse)response).setStatusCodeValue(clientResponse.status().code());
break;
} response = ((ServerHttpResponseDecorator)response).getDelegate();
}
} } protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
Object connectTimeoutAttr = route.getMetadata().get("connect-timeout");
if (connectTimeoutAttr != null) {
Integer connectTimeout = getInteger(connectTimeoutAttr);
return this.httpClient.tcpConfiguration((tcpClient) -> {
return tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
});
} else {
return this.httpClient;
}
} static Integer getInteger(Object connectTimeoutAttr) {
Integer connectTimeout;
if (connectTimeoutAttr instanceof Integer) {
connectTimeout = (Integer)connectTimeoutAttr;
} else {
connectTimeout = Integer.parseInt(connectTimeoutAttr.toString());
} return connectTimeout;
} private Duration getResponseTimeout(Route route) {
Object responseTimeoutAttr = route.getMetadata().get("response-timeout");
Long responseTimeout = null;
if (responseTimeoutAttr != null) {
if (responseTimeoutAttr instanceof Number) {
responseTimeout = ((Number)responseTimeoutAttr).longValue();
} else {
responseTimeout = Long.valueOf(responseTimeoutAttr.toString());
}
} return responseTimeout != null ? Duration.ofMillis(responseTimeout) : this.properties.getResponseTimeout();
}
}

  

  在NettyRoutingFilter中根据GATEWAY_REQUEST_URL_ATTR属性读取requestUrl,然后进行相应请求。

   LoadBalancerClientFilter会作用在url以lb开头的路由,然后利用loadBalancer来获取服务实例,构造目标requestUrl,设置到GATEWAY_REQUEST_URL_ATTR属性中,供NettyRoutingFilter使用。
 

  

Spring cloud gateway 如何在路由时进行负载均衡的更多相关文章

  1. Spring Cloud微服务开发笔记5——Ribbon负载均衡策略规则定制

    上一篇文章单独介绍了Ribbon框架的使用,及其如何实现客户端对服务访问的负载均衡,但只是单独从Ribbon框架实现,没有涉及spring cloud.本文着力介绍Ribbon的负载均衡机制,下一篇文 ...

  2. Spring Cloud Gateway的动态路由怎样做?集成Nacos实现很简单

    一.说明 网关的核心概念就是路由配置和路由规则,而作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,是尽量要避免重启的,所以实现动态路由是非常有必要的:本文主要介绍 Spring Clo ...

  3. spring boot 2.0.3+spring cloud (Finchley)2、搭建负载均衡Ribbon (Eureka+Ribbon+RestTemplate)

    Ribbon是Netflix公司开源的一个负载均衡组件,将负载均衡逻辑封装在客户端中,运行在客户端的进程里. 本例子是在搭建好eureka的基础上进行的,可参考spring boot 2.0.3+sp ...

  4. spring cloud使用zuul实现反向代理和负载均衡

    首先,这篇文章参考的是http://blog.didispace.com/springcloud5/这位大牛的博客.本人是通过这篇博客来学习zuul的,现在写的博客只是个人在学习时个人的一些感受和理解 ...

  5. 一起来学Spring Cloud | 第三章:服务消费者 (负载均衡Ribbon)

    一.负载均衡的简介: 负载均衡是高可用架构的一个关键组件,主要用来提高性能和可用性,通过负载均衡将流量分发到多个服务器,多服务器能够消除单个服务器的故障,减轻单个服务器的访问压力. 1.服务端负载均衡 ...

  6. 关于spring cloud eureka整合ribbon实现客户端的负载均衡

    1. 实现eureka整合ribbon非常简单, 1.1.首先引入所需maven依赖 <dependency> <groupId>org.springframework.boo ...

  7. spring cloud 系列第3篇 —— ribbon 客户端负载均衡 (F版本)

    源码仓库地址:https://github.com/heibaiying/spring-samples-for-all 一.ribbon 简介 ribbon是Netfix公司开源的负载均衡组件,采用服 ...

  8. Spring Cloud(十四):Ribbon实现客户端负载均衡及其实现原理介绍

    年后到现在一直很忙,都没什么时间记录东西了,其实之前工作中积累了很多知识点,一直都堆在备忘录里,只是因为近几个月经历了一些事情,没有太多的经历来写了,但是一些重要的东西,我还是希望能坚持记录下来.正好 ...

  9. Spring Cloud分区发布实践(3) 网关和负载均衡

    注意: 因为涉及到配置测试切换, 中间环节需按此文章操作体验, 代码仓库里面的只有最后一步的代码 准备好了微服务, 那我们就来看看网关+负载均衡如何一起工作 新建一个模块hello-gateway, ...

随机推荐

  1. js获取url查询字符串参数

    最近看js高级程序设计 对其中查询字符串参数的获得重新写了,当传递一个完整的URL的时候对查询字符串的提取 function getQueryArgs(){ var qs = (location.se ...

  2. 论文解读(Graph-MLP)《Graph-MLP: Node Classification without Message Passing in Graph》

    论文信息 论文标题:Graph-MLP: Node Classification without Message Passing in Graph论文作者:Yang Hu, Haoxuan You, ...

  3. iOS全埋点解决方案-控件点击事件

    前言 ​ 我们主要介绍如何实现控件点击事件($AppClick)的全埋点.在介绍如何实现之前,我们需要先了解一下,在 UIKit 框架下,处理点击或拖动事件的 Target-Action 设计模式. ...

  4. python基本数据类型介绍

    数据类型 首先知道什么是数据类型 针对不同的数据类型采用不同的处理方法 --目录-- 一.数据类型之整型 二.数据类型之浮点型 三.数据类型之字串符 四.数据之列表 一.数据类型之整型 1.其实呢就是 ...

  5. fs本地文件系统

    node 中有一个内置模块,fs全称flie system 文件系统,可以对本地文件执行增删改查操作(以下为异步相关API) 先引入内置模块   var fs=require('fs') 1,增: w ...

  6. addEventListener() 和 removeEventListener() 简介

    DOM方法  addEventListener()  和  removeEventListener()  是用来分配和删除事件的函数   这两个方法都需要三个参数  分别为: 事件名称(String) ...

  7. 聊一聊Redis的离线分析

    背景 Redis 或许是我们大部分场景都会用到的一个利器,虽然是利器,用的姿势不对的话,终究还是会整出幺蛾子的. 比较常见的问题,不外乎内存暴增,慢查询等情况. 那么对于内存问题,可以借助redis自 ...

  8. Git批量下载MODIS数据

    1.download.sh获取 EarthData(需注册账号)中获取MODIS的产品类型.地理范围.时间年份等,进入下载页面Download Status 下载点击得到_download.sh 文件 ...

  9. kali 更换国内源

    打开source源 vim /etc/apt/sources.list 没有权限进行更改 获取root 权限 按 i 提示内容: E325: 注意 发现交换文件 "/var/tmp/sour ...

  10. Halo 开源项目学习(四):发布文章与页面

    基本介绍 博客最基本的功能就是让作者能够自由发布自己的文章,分享自己观点,记录学习的过程.Halo 为用户提供了发布文章和展示自定义页面的功能,下面我们分析一下这些功能的实现过程. 管理员发布文章 H ...