基于Spring-cloud-gateway实现全局日志记录

一 引言

最近项目在线上运行出现了一些难以复现的bug需要定位相应api的日志,通过nginx提供的api请求日志难以实现,于是在gateway通过全局过滤器记录api请求日志。

二 实现逻辑

在这里插入图片描述

  1. 接受到用户请求后,经过全局过滤器,检验是否开启相应的日志配置及相应的黑白名单配置
  2. 在gateway前置处理如记录当前请求开始时间,请求url,请求方法等
  3. 在gateway后置处理中获得相应的请求结果,响应状态码
  4. 记录当前请求日志,根据实际需求,通过mq异步处理工具持久化相应的日志(本案列作处理)
  5. 诊断请求结果,对请求异常,慢api等推送相应的消息给研发人人员

三 代码实现

定义相应的配置类

@Data
@RefreshScope
@Component
@ConfigurationProperties(prefix = LogProperties.PREFIX)
public class LogProperties {
   public static final String PREFIX = "config.gateway.log.access";
   /**
    * 是否开启日志打印
    */
   private Boolean enabled = true;

   /**
    * 忽略的pattern
    */
   private List<String> ignoredPatterns;

   private ApiAlarmConfiguration fail = new ApiAlarmConfiguration();

   private SlowApiAlarmConfiguration slow = new SlowApiAlarmConfiguration();

   /**
    * 慢API报警配置
    */
   @Data
   public static class SlowApiAlarmConfiguration {

   	/**
   	 * 是否开启API慢日志打印
   	 */
   	private boolean alarm = true;

   	/**
   	 * 报警阈值 (单位:毫秒)
   	 */
   	private long threshold = 500;
   }


   /**
    * API异常报警(根据http状态码判定)
    */
   @Data
   public static class ApiAlarmConfiguration {

   	/**
   	 * 是否开启异常报警 默认关闭
   	 */
   	private boolean alarm = false;

   	/**
   	 * 排除状态码
   	 */
   	private List<Integer> exclusion;
   }
}

定义log实体

@Data
public class GatewayLog implements Serializable {
  	private static final long serialVersionUID = -3205904134722576668L;

    /**
     * 访问实例
	 */
	private String targetServer;

    /**
     * 请求路径
	 */
	private String requestPath;

    /**
     * 请求与方法
	 */
	private String method;

    /**
     * 请求协议
	 */
	private String schema;

    /**
     * 请求ip
	 */
	private String ip;

    /**
	 * 请求时间
	 */
	private Date requestTime;

	/**
	 * 请求参数
	 */
	private Map<String,String> queryParams;

    /**
     * 请求体
	 */
	private String requestBody;

	/**
	 * 请求执行时间
	 */
	private Long executeTime;

    /**
     * 请求类型
	 */
	private String requestContentType;

    /**
     * 相应状态码
	 */
	private int code;
}

定义相应日志工厂及常量

public interface GatewayLogType {
	/**
	 * 常规输出
	 */
	String APPLICATION_JSON_REQUEST = "applicationJsonRequest";

	String FORM_DATA_REQUEST = "formDataRequest";

	String BASIC_REQUEST = "basicRequest";

	String NORMAL_REQUEST = "normalRequest";

    /**
     * 慢查询
	 */
	String SLOW = "slow";

    /**
     * 非200响应
	 */
	String FAIL = "fail";
}


@Slf4j
public class GatewayLogInfoFactory {
	public static void log(String type, GatewayLog gatewayLog){
		switch (type){
			case GatewayLogType.APPLICATION_JSON_REQUEST:
				log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}"
						,gatewayLog.getIp()
						,gatewayLog.getMethod()
						,gatewayLog.getRequestPath()
						,gatewayLog.getTargetServer()
						,gatewayLog.getCode()
						,gatewayLog.getExecuteTime()
						,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"")
				);
				break;
			case GatewayLogType.FORM_DATA_REQUEST:
				log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}"
						,gatewayLog.getIp()
						,gatewayLog.getMethod()
						,gatewayLog.getRequestPath()
						,gatewayLog.getTargetServer()
						,gatewayLog.getCode()
						,gatewayLog.getExecuteTime()
						,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"")
				);
				break;
			case GatewayLogType.BASIC_REQUEST:
				log.info("[{}] {} {},route: {},status: {},excute: {} mills,requestBody: {}"
						,gatewayLog.getIp()
						,gatewayLog.getMethod()
						,gatewayLog.getRequestPath()
						,gatewayLog.getTargetServer()
						,gatewayLog.getCode()
						,gatewayLog.getExecuteTime()
						,StrUtil.replace(gatewayLog.getRequestBody(), StrPool.LF,"")
				);
				break;
			case GatewayLogType.NORMAL_REQUEST:
				log.info("[{}] {} {},route: {},status: {},excute: {} mills,queryParams: {}"
						,gatewayLog.getIp()
						,gatewayLog.getMethod()
						,gatewayLog.getRequestPath()
						,gatewayLog.getTargetServer()
						,gatewayLog.getCode()
						,gatewayLog.getExecuteTime()
						,gatewayLog.getQueryParams()
				);
				break;
			default:
				break;
		}
	}
}

定义日志全局过滤器

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import com.zkjc.xlcp.common.notifier.core.Notifier;
import com.zkjc.xlcp.gateway.log.GatewayLogInfoFactory;
import com.zkjc.xlcp.gateway.log.LogProperties;
import com.zkjc.xlcp.gateway.log.constant.GatewayLogType;
import com.zkjc.xlcp.gateway.log.entity.GatewayLog;
import com.zkjc.xlcp.gateway.util.IpUtil;
import io.netty.util.internal.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.*;

/**
 * @author likun
 * @date 2022年10月24日 9:52
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class AccessLogFilter implements GlobalFilter, Ordered {

	private final LogProperties logProperties;

	private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

	/**
	 * default HttpMessageReader.
	 */
	private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();

	private final AntPathMatcher antPathMatcher = new AntPathMatcher();

	private final Notifier notifier;
	/*
	*  在CncloudRequestGlobalFilter后面执行 先清洗url在进行路径的日志的打印
	* */
	@Override
	public int getOrder() {
		return -100;
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// 判断是否打开相应是日志配置 ingore配置校验
		if (!logProperties.getEnabled()||hasIgnoredFlag(exchange,logProperties)){
			return chain.filter(exchange);
		}
		// 获得请求上下文
		GatewayLog gatewayLog = parseGateway(exchange);
		ServerHttpRequest request = exchange.getRequest();
		MediaType mediaType = request.getHeaders().getContentType();
		if (Objects.isNull(mediaType)){
			return writeNormalLog(exchange,chain,gatewayLog);
		}
		gatewayLog.setRequestContentType(mediaType.getType() + "/" + mediaType.getSubtype());
		// 对不同的请求类型做相应的处理
		if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
			return writeBodyLog(exchange,chain,gatewayLog);
		}else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
			return readFormData(exchange,chain,gatewayLog);
		}else {
			return writeBasicLog(exchange,chain,gatewayLog);
		}
	}

	/**
	 * 校验白名单
	 * @param exchange
	 * @param logProperties
	 * @return
	 */
	private Boolean hasIgnoredFlag(ServerWebExchange exchange,LogProperties logProperties){
		List<String> ignoredPatterns = logProperties.getIgnoredPatterns();
		if (CollectionUtil.isEmpty(ignoredPatterns)){
			return Boolean.FALSE;
		}
		ServerHttpRequest request = exchange.getRequest();
		URI uri = request.getURI();
		for (String pattern : ignoredPatterns) {
			if (antPathMatcher.match(pattern,uri.getPath())){
				return Boolean.TRUE;
			}
		}
		return Boolean.FALSE;
	}

	/**
	 * 生成相应的报告并推送qq邮箱消息
	 */
	private void report(GatewayLog gatewayLog){
		if (notifier==null){
			return;
		}
		boolean reported = exceptionReport(gatewayLog);
		if (!reported){
			slowApiReport(gatewayLog);
		}
	}

	/**
	 * 异常报警
	 * @param gatewayLog
	 * @return
	 */
	private Boolean exceptionReport(GatewayLog gatewayLog){
		int code = gatewayLog.getCode();
		if (code==HttpStatus.OK.value()){
			return Boolean.FALSE;
		}
		LogProperties.ApiAlarmConfiguration apiAlarmConfiguration = logProperties.getFail();
		if (!apiAlarmConfiguration.isAlarm()){
			log.debug("api exception alarm disabled.");
			return Boolean.FALSE;
		}
		if (!CollectionUtils.isEmpty(apiAlarmConfiguration.getExclusion()) && apiAlarmConfiguration.getExclusion().contains(code)) {
			log.debug("status [{}] excluded.", code);
			return Boolean.FALSE;
		}
		String alarmContent = String.format("【API异常】 请求ip:[{%s}],请求路由:[{%s}],请求地址:[{%s}],返回状态码:[{%d}],执行时间:%d ms",gatewayLog.getIp(),gatewayLog.getTargetServer(),gatewayLog.getRequestPath(),code,gatewayLog.getExecuteTime());
		notifier.notify(alarmContent);
		return Boolean.TRUE;
	}

	private Boolean slowApiReport(GatewayLog gatewayLog){
		LogProperties.SlowApiAlarmConfiguration slowApiAlarmConfiguration = logProperties.getSlow();
		long threshold = slowApiAlarmConfiguration.getThreshold();
		if (gatewayLog.getExecuteTime()<threshold){
			return Boolean.FALSE;
		}
		if (!slowApiAlarmConfiguration.isAlarm()) {
			log.debug("slow api alarm disabled.");
			return Boolean.FALSE;
		}
		String slowContent = String.format("【API执行时间过长,超过设定阈值】 请求ip:[{%s}],请求路由:[{%s}],请求地址:[{%s}],执行时间:%d ms",gatewayLog.getIp(),gatewayLog.getTargetServer(),gatewayLog.getRequestPath(),gatewayLog.getExecuteTime());
		notifier.notify(slowContent);
		return Boolean.TRUE;
	}


	/**
	 * 获得当前请求分发的路由
	 * @param exchange
	 * @return
	 */
	private Route getGatewayRoute(ServerWebExchange exchange) {
		return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
	}

	private GatewayLog parseGateway(ServerWebExchange exchange){
		ServerHttpRequest request = exchange.getRequest();
		String requestPath = request.getPath().pathWithinApplication().value();
		Route route = getGatewayRoute(exchange);
		String ip = IpUtil.getIpAddress(request);
		GatewayLog gatewayLog = new GatewayLog();
		gatewayLog.setSchema(request.getURI().getScheme());
		gatewayLog.setMethod(request.getMethodValue());
		gatewayLog.setRequestPath(requestPath);
		gatewayLog.setTargetServer(route.getId());
		gatewayLog.setIp(ip);
		gatewayLog.setRequestTime(new Date());
		return gatewayLog;
	}

	private Mono writeNormalLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog){
		return chain.filter(exchange).then(Mono.fromRunnable(()->{
			ServerHttpResponse response = exchange.getResponse();
			int value = response.getStatusCode().value();
			gatewayLog.setCode(value);
			long executeTime = DateUtil.between(gatewayLog.getRequestTime(), new Date(), DateUnit.MS);
			gatewayLog.setExecuteTime(executeTime);
			ServerHttpRequest request = exchange.getRequest();
			MultiValueMap<String, String> queryParams = request.getQueryParams();
			Map<String, String> paramsMap = new HashMap<>();
			if (CollectionUtil.isNotEmpty(queryParams)) {
				for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
					paramsMap.put(entry.getKey(), StrUtil.join(StrPool.COMMA,entry.getValue()));
				}
			}
			gatewayLog.setQueryParams(paramsMap);
			GatewayLogInfoFactory.log(GatewayLogType.NORMAL_REQUEST,gatewayLog);
			// 推送相应的报告
			report(gatewayLog);
		}));
	}

	/**
	 * 解决 request body 只能读取一次问题,
	 * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
	 * @param exchange
	 * @param chain
	 * @param gatewayLog
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {
		ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
		Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
			gatewayLog.setRequestBody(body);
			return Mono.just(body);
		});
		// 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次
		BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
		HttpHeaders headers = new HttpHeaders();
		headers.putAll(exchange.getRequest().getHeaders());
		// the new content type will be computed by bodyInserter
		// and then set in the request decorator
		headers.remove(HttpHeaders.CONTENT_LENGTH);
		CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
		return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {                    // 重新封装请求
			ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);                    // 记录响应日志
			ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);                    // 记录普通的
			return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> {                                // 打印日志
				GatewayLogInfoFactory.log(GatewayLogType.APPLICATION_JSON_REQUEST,gatewayLog);
				// 推送相应的报告
				report(gatewayLog);
			}));
		}));
	}


	/**
	 * 读取form-data数据
	 * @param exchange
	 * @param chain
	 * @param accessLog
	 * @return
	 */
	private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {
		return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
			DataBufferUtils.retain(dataBuffer);
			final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
			final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
				@Override
				public Flux<DataBuffer> getBody() {
					return cachedFlux;
				}
				@Override
				public MultiValueMap<String, String> getQueryParams() {
					return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams();
				}
			};
			final HttpHeaders headers = exchange.getRequest().getHeaders();
			if (headers.getContentLength() == 0) {
				return chain.filter(exchange);
			}
			ResolvableType resolvableType;
			if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
				resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
			} else {
				//解析 application/x-www-form-urlencoded
				resolvableType = ResolvableType.forClass(String.class);
			}

			return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType, mutatedRequest.getHeaders().getContentType())).findFirst().orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType, mutatedRequest, Collections.emptyMap()).flatMap(resolvedBody -> {
				if (resolvedBody instanceof MultiValueMap) {
					LinkedMultiValueMap map = (LinkedMultiValueMap) resolvedBody;
					if (CollectionUtil.isNotEmpty(map)) {
						StringBuilder builder = new StringBuilder();
						final Part bodyPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("body");
						if (bodyPartInfo instanceof FormFieldPart) {
							String body = ((FormFieldPart) bodyPartInfo).value();
							builder.append("body=").append(body);
						}
						accessLog.setRequestBody(builder.toString());
					}
				} else {
					accessLog.setRequestBody((String) resolvedBody);
				}

				//获取响应体
				ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);
				return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> {                                    // 打印日志
					// 打印响应的日志
					GatewayLogInfoFactory.log(GatewayLogType.FORM_DATA_REQUEST,accessLog);
					// 推送相应的报告
					report(accessLog);
				}));
			});
		});
	}


	private Mono<Void> writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {
		return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
			DataBufferUtils.retain(dataBuffer);
			final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
			final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
				@Override
				public Flux<DataBuffer> getBody() {
					return cachedFlux;
				}

				@Override
				public MultiValueMap<String, String> getQueryParams() {
					return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams();
				}
			};
			StringBuilder builder = new StringBuilder();
			MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
			if (CollectionUtil.isNotEmpty(queryParams)) {
				for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
					builder.append(entry.getKey()).append("=").append(entry.getValue()).append(StrPool.COMMA);
				}
			}
			accessLog.setRequestBody(builder.toString());            //获取响应体
			ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);
			return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build()).then(Mono.fromRunnable(() -> {                        // 打印日志
				GatewayLogInfoFactory.log(GatewayLogType.BASIC_REQUEST,accessLog);
				// 推送相应的报告
				report(accessLog);
			}));
		});
	}

	/**
	 * 请求装饰器,重新计算 headers
	 * @param exchange
	 * @param headers
	 * @param outputMessage
	 * @return
	 */
	private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
		return new ServerHttpRequestDecorator(exchange.getRequest()) {
			@Override
			public HttpHeaders getHeaders() {
				long contentLength = headers.getContentLength();
				HttpHeaders httpHeaders = new HttpHeaders();
				httpHeaders.putAll(super.getHeaders());
				if (contentLength > 0) {
					httpHeaders.setContentLength(contentLength);
				} else {
					httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
				}
				return httpHeaders;
			}
			@Override
			public Flux<DataBuffer> getBody() {
				return outputMessage.getBody();
			}
		};
	}

	/**
	 * 记录响应日志
	 * 通过 DataBufferFactory 解决响应体分段传输问题。
	 */
	private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {
		ServerHttpResponse response = exchange.getResponse();
		DataBufferFactory bufferFactory = response.bufferFactory();
		return new ServerHttpResponseDecorator(response) {
			@Override
			public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
				if (body instanceof Flux) {
					// 计算执行时间
					long executeTime = DateUtil.between(gatewayLog.getRequestTime(), new Date(), DateUnit.MS);
					gatewayLog.setExecuteTime(executeTime);
					// 获取响应类型,如果是 json 就打印
					String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);//
					gatewayLog.setCode(this.getStatusCode().value());
					//
					if (Objects.equals(this.getStatusCode(), HttpStatus.OK)
							&& !StringUtil.isNullOrEmpty(originalResponseContentType)
							&& originalResponseContentType.contains("application/json")) {
						Flux<? extends DataBuffer> fluxBody = Flux.from(body);
						return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
							// 合并多个流集合,解决返回体分段传输
							DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
							DataBuffer join = dataBufferFactory.join(dataBuffers);
							byte[] content = new byte[join.readableByteCount()];
							// 释放掉内存
							join.read(content);
							DataBufferUtils.release(join);
							return bufferFactory.wrap(content);
						}));
					}else {

					}
				}
				return super.writeWith(body);
			}
		};
	}
}

<think>嗯,用户想深入了解Spring Cloud Gateway的内部工作原理和实现机制。从用户之前的提问来看,ta应该是个有经验的Java开发者,正在研究微服务架构中的网关技术。用户可能正在搭建或优化微服务架构,需要理解网关的核心机制来做出技术决策或解决实际问题。 用户提供的引用材料很丰富,主要涉及Spring Cloud Gateway的定位(替代Zuul)、核心依赖(WebFlux)、服务发现集成(Nacos、Kubernetes)和配置方式。不过用户明确要求的是"原理及工作机制",所以需要聚焦在架构设计层面而非配置细节。 首先需要解释清楚反应式编程模型这个基础,因为这是理解整个网关工作流的钥匙。WebFlux的非阻塞特性决定了网关的线程模型和请求处理方式,这点必须优先说明。然后要突出过滤器链这个核心设计模式,用户可能熟悉Servlet Filter,但反应式过滤器的差异需要特别强调。 路由机制部分需要区分配置阶段和运行时阶段:配置阶段强调Predicate和Filter的声明式特性,运行时阶段则要说明路由定位的流程。负载均衡作为高频使用功能,需要点明其通过ReactorLoadBalancer的集成位置。 用户可能还会关心实际性能表现,所以补充了工作流示例和性能优化点。最后的扩展机制说明是为了让用户知道如何定制网关,毕竟实际项目总会需要自定义Filter。 相关问题设计成阶梯式:从核心原理(WebFlux)到具体组件(Predicate),再到优化实践,最后是监控集成。这样用户可以根据当前需要选择深入方向。特别加入"与Zuul对比"的问题,因为引用[2]提到它是Zuul替代品,这个点开发者通常很关注。 需要避免陷入具体配置的讲解,比如引用[4]中的YAML示例就不展开,保持讨论在架构层面。但负载均衡的"lb://"语法值得提及,因为它是路由配置的关键符号。</think>Spring Cloud Gateway 的核心工作原理基于 **响应式编程模型(Spring WebFlux)** 和 **过滤器链(Filter Chain)** 设计,其工作机制可分解如下: --- ### 一、核心架构 1. **基于 WebFlux 的响应式引擎** Gateway 底层使用 **Project Reactor** 和 **Netty** 实现非阻塞 I/O,通过 `Reactor Netty` 处理 HTTP 请求,支持高并发场景[^1][^2]。 2. **路由(Route)核心组件** - **Predicate(断言)**:匹配请求的条件(如 Path、Header、Method 等),决定是否路由到目标 URI。 - **Filter(过滤器)**:处理请求和响应的逻辑单元(如添加 Header、重写路径、限流等)。 - **URI**:目标服务地址,支持 `lb://`(负载均衡)或 `http://`(直连)协议[^4]。 ```yaml spring: cloud: gateway: routes: - id: user_route uri: lb://user-service # 负载均衡到 user-service predicates: - Path=/api/users/** # 匹配路径 filters: - AddRequestHeader=X-Gateway, true # 添加请求头 ``` --- ### 二、请求处理流程 1. **路由匹配** 客户端请求 → Gateway 根据 `Predicate` 匹配路由规则 → 定位到目标 URI(如 `lb://service-name`)。 2. **过滤器链执行** 请求依次通过 **Gateway Filter** 链(Pre-Filters)→ 转发到目标服务 → 响应返回时通过 **Post-Filters** 处理[^1]。 ```mermaid graph LR A[客户端请求] --> B[Pre-Filters] B --> C{转发到目标服务} C --> D[目标服务处理] D --> E[Post-Filters] E --> F[返回响应] ``` 3. **负载均衡(Load Balancing)** 若 URI 为 `lb://service-name`,通过 **Spring Cloud LoadBalancer** 从注册中心(如 Nacos、Eureka)获取实例列表,按策略(轮询、随机等)选择实例[^3][^4]。 --- ### 三、关键机制详解 1. **过滤器类型** - **GatewayFilter**:作用于单个路由(如限流、重试)。 - **GlobalFilter**:全局生效(如认证、日志),通过 `@Order` 控制执行顺序。 ```java @Bean @Order(-1) public GlobalFilter authFilter() { return (exchange, chain) -> { // 身份验证逻辑 return chain.filter(exchange); }; } ``` 2. **动态路由** 支持通过 **Spring Cloud Config** 或 **Nacos 配置中心** 实时更新路由规则,无需重启网关。 3. **服务发现集成** 依赖 `spring-cloud-starter-kubernetes-discovery` 或 `spring-cloud-starter-alibaba-nacos-discovery`,自动同步注册中心的服务列表[^3][^4]。 --- ### 四、性能优化点 1. **使用缓存**:缓存路由定义,避免每次请求解析配置。 2. **精简 Filter 链**:减少非必要 Filter 的执行耗时。 3. **响应式编程优化**:避免在 Filter 中阻塞线程(如使用 `Mono.defer()` 处理异步操作)。 --- ### 五、扩展机制 1. **自定义 Predicate**:实现 `RoutePredicateFactory` 接口扩展匹配条件。 2. **自定义 Filter**:继承 `AbstractGatewayFilterFactory` 实现业务逻辑。 3. **集成 Sentinel**:通过 `spring-cloud-alibaba-sentinel-gateway` 实现熔断降级。 --- ### 工作流示例 ```mermaid sequenceDiagram Client->>+Gateway: HTTP 请求 /api/users Gateway->>Gateway: 匹配 Predicate (Path=/api/users/**) Gateway->>Gateway: 执行 Pre-Filters (认证、限流) Gateway->>LoadBalancer: 获取 user-service 实例 LoadBalancer-->>Gateway: 返回实例地址 (192.168.1.10:8080) Gateway->>user-service: 转发请求 user-service-->>Gateway: 返回响应 Gateway->>Gateway: 执行 Post-Filters (日志、修改响应头) Gateway-->>-Client: 返回最终响应 ``` --- **相关问题** 1. Spring WebFlux 的响应式模型如何提升 Gateway 的并发性能? 2. Gateway 的 GlobalFilter 与 Zuul 的 Filter 有何本质区别? 3. 如何实现动态路由更新而不重启网关服务? 4. Gateway 集成 Spring Cloud LoadBalancer 时如何自定义负载均衡策略? 5. 如何通过 Gateway 的 Filter 实现全链路灰度发布? 6. Gateway 与 Nacos 的服务发现集成机制是怎样的? 7. 如何监控 Gateway 的请求延迟和错误率? [^1]: Spring-cloud-gateway 源码浅析,基于 WebFlux 和 Filter Chain 架构。 [^2]: Spring-Cloud-Gateway 作为 Zuul 的替代方案,采用非阻塞式设计。 [^3]: 依赖 `spring-cloud-kubernetes-discovery` 实现 Kubernetes 服务发现。 [^4]: 通过 `lb://` 协议和 Nacos 实现负载均衡路由。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值