Spring Cloud 学习笔记5——网关

网关的主要作用:

  1. 校验过滤:统一在网关做校验过滤,避免维护每个微服务的校验逻辑
  2. 请求路由:相当于一个门面,避免把内部服务的url暴露给外部调用者,网关负责url映射
  3. 负载均衡

spring cloud基于Netflix Zuul来实现网关功能

导入Zuul依赖、开启Zuul功能

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

该依赖不仅包含了核心依赖zuul-core,还包含了网关所需的其它依赖:Hystrix、Ribbon和Actuator(可以通过/routes端点查看路由规则)

大多数情况下,网关需要注册到注册中心,所以也要引入eureka依赖,大致所需依赖如下:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-zuul</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-feign</artifactId>
		</dependency>

在入口类处加上@EnableZuulProxy开启Zuul网关功能。

请求路由

传统路由

该方式需要指定目标服务地址,无需依赖注册中心,需要花大量时间来维护path和url的对应关系。

zuul.routes.yourRouterName.path=/sourceAddress/**

zuul.routes.yourRouterName.url=targetAddress

yourRouterName是路由名字,可随意取,但一组path和url的路由名字必须相同,sourceAddress通常取值与yourRouterName相同,如下:

zuul.routes.studentInfo.path=/studentInfo/**
zuul.routes.studentInfo.url=http://localhost:8080/

假设网关地址为http://localhost:5555,当我们访问http://localhost:5555/studentInfo/studentscore时,网关会把请求路由到http://localhost:8080/studentscore。http://localhost:8080是studentInfo这个服务的一个微服务地址。

服务路由

该方式依赖于eureka,可以通过微服务名字来路由,如:

zuul.routes.studentInfo.path=/studentInfo/**
zuul.routes.studentInfo.serviceId=studentInfo

studentInfo是一个注册到注册中心的微服务,当我们访问网关http://localhost:5555/studentInfo/studentscore时,会路由到微服务studentInfo的studentscore接口,我们无需关心微服务studentInfo的实例个数及地址信息,服务列表由eureka来维护,请求操作由ribbon(负载均衡)和Hytrixs(熔断控制)来完成。

如果不想依赖于eureka,也是可以的:

zuul.routes.studentInfo.path=/studentInfo/**
zuul.routes.studentInfo.serviceId=studentInfo
ribbon.eureka.enabled=false
studentInfo.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/,http://localhost:8082/

这又回到了传统路由方式了,只是可以利用ribbon做负载均衡。

请求过滤

这里可以做一些权限校验,请求参数校验,甚至是响应结果校验。只要继承抽象类ZuulFilter,实现其4个抽象方法即可对请求(包括响应)拦截和过滤。

1、public String filterType()

过滤器类型,决定过滤器在请求的哪个阶段被执行:

pre:在请求被路由之前执行;

route:在路由请求时执行,这里处理的事情是,将外部请求路由到具体的服务实例上去;

post:在请求路由之后(route和error之后)执行,不管是否发生错误,总会进入post过滤器执行,可以获取响应报文;

error:处理请求发生错误时执行,如果异常来自pre或route阶段,error处理完之后还是会进入post,如果错误来自post,则error处理完后不再重复进入post;

2、public int filterOrder()

过滤器执行顺序,如果有多个过滤器,可以通过该值来确定过滤器的执行顺序,值越小优先级越高,起始为0。如果过滤器没有顺序要求,则该值可以相同,如都返回0。

3、public boolean shouldFilter()

过滤器是否生效

4、public Object run()

过滤器具体逻辑,这里常用到一些方法:

1)RequestContext ctx = RequestContext.getCurrentContext();获取请求上下文对象

2)HttpServletRequest request = ctx.getRequest();获取请求对象

request.getServletPath();获取请求url中的servlet部分路径信息

request.getRequestURL();获取请求url

通过如下语句可以获取请求数据(包括请求头部和请求参数)

String req = StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));

可以进一步转成map等类型:JSON.parseObject(req, Map.class);

如果请求是get类型,也可以通过request.getParameter();或request.getParameterMap();来获取请求参数。

3)获取响应报文头部信息

		    List<Pair<String, String>> objs = ctx.getZuulResponseHeaders();
		    for(Pair<String, String> obj:objs){
		    	String key = obj.first();
		    	String value = obj.second();
		    	//TODO 
		    }

4)获取响应报文的body信息

			InputStream resStream = ctx.getResponseDataStream();
			try {
				String body = StreamUtils.copyToString(resStream, Charset.forName("UTF-8"));
				Map<String,Object> bodyMap = JSON.parseObject(body, Map.class);
			} catch (IOException e) {}

应该也可以用String body = ctx.getResponseBody();来获取报文的body信息。

5)过滤请求

可以通过以下语句过滤掉请求,不对其进行路由

ctx.setSendZuulResponse(false);//为false的时候,filterType需要是pre,如果是post,则已经路由了

默认为true,即路由请求,如果校验不通过,或请求参数不满足要求,可以将其设为false。过滤请求后,通常会设置一些返回码和返回报文内容

6)设置返回码

ctx.setResponseStatusCode(401);//通常在请求被过滤掉的时候使用,如果请求能被路由,则用默认返回码即可

7)设置返回报文body的内容

ctx.setResponseBody(str);

如果请求被过滤掉,可以在这里设置一些过滤原因等信息。如果请求被路由,也可以用该方法对返回的响应报文加工处理,例如去掉或加入一些内容。

8)修改路由地址

        RequestContext context = RequestContext.getCurrentContext();
        context.put(FilterConstants.SERVICE_ID_KEY, "新的服务serviceId");//如studentinfo
        context.put(FilterConstants.REQUEST_URI_KEY, "/新的接口地址");//如/getScore

设置FilterConstants.SERVICE_ID_KEY可以修改请求的服务Id

设置FilterConstants.REQUEST_URI_KEY可以修改请求的URI,URI不包含前面第域名和服务Id

自定义ZuulFilter如下:

@Component
public class MyZuulFilter extends ZuulFilter {
	@Override
	public boolean shouldFilter() {
		return true;
	}
	@Override
	public String filterType() {
		return "post";
	}
	@Override
	public int filterOrder() {
		return 0;
	}
	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		// TODO 过滤逻辑
		return null;// 返回null即可
	}
}

MyZuulFilter需要用@component注解,或在配置类中利用@Bean创建实例

自定义异常处理

Zuul有默认的异常处理过滤器,但有时候我们希望在发生异常时能返回自定义的异常信息,这可以通过继承DefaultErrorAttributes来实现:

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
	@Override
	public Map<String, Object> getErrorAttributes(
			RequestAttributes requestAttributes, boolean includeStackTrace) {
		Map<String, Object> result = new LinkedHashMap<String, Object>(2);
		result.put("code", "错误码");
		result.put("msg", "错误说明");
		return result;
	}
}

回调/服务降级

当网关路由请求到微服务时,如果服务不可用或请求超时,则会抛出异常,Hystrix为请求失败提供回调接口(和Feign的回调一样),Zuul包含了Hystrix。

实现起来很简单,不同Spring boot版本接口有一点不同

假如spring cloud版本是Edgware.SR3,Spring boot版本是1.5.8.RELEASE,则实现FallbackProvider接口:

@Component
public class YourRouterNameFallbackProvider implements FallbackProvider{
	
	private static final ObjectMapper mapper = new ObjectMapper();

	@Override
	public String getRoute() {
		return "yourRouterName";
	}

	@Override
	public ClientHttpResponse fallbackResponse() {
		return new ClientHttpResponse() {
			@Override
			public HttpStatus getStatusCode() throws IOException {
				return HttpStatus.OK;
			}

			@Override
			public int getRawStatusCode() throws IOException {
				return 200;
			}

			@Override
			public String getStatusText() throws IOException {
				return "OK";
			}

			@Override
			public void close() {
				return;
			}

			@Override
			public InputStream getBody() throws IOException {
				Map<String,Object> bodyMap = new HashMap<String, Object>();
				bodyMap.put("code", "错误码");
				bodyMap.put("msg", "错误信息");
				return new ByteArrayInputStream(mapper.writeValueAsString(
						bodyMap).getBytes("UTF-8"));
			}

			@Override
			public HttpHeaders getHeaders() {
				HttpHeaders headers = new HttpHeaders();
				headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
				return headers;
			}
		};
	}

	@Override
	public ClientHttpResponse fallbackResponse(Throwable cause) {
		//可以利用cause获取异常的内容
		return fallbackResponse();
	}
}

getRoute()指定回调对应的路由名字,与配置文件一致;

两个fallbackResponse方法(一个无参,一个有参)用于设置响应报文的头部和body内容;

假如使用的Spring cloud是C或D版本,则实现ZuulFallbackProvider接口,和上面的一样,只是后者只有一个无参的fallbackResponse方法,我们无法获取导致异常的原因。

如果回调类把路由请求的异常捕获并处理了,则异常不会再进入MyErrorAttribute。
 

异常处理

从前面内容看到,有多个地方可以处理异常:

  1. 类型为error的过滤器ZuulFilter
  2. 回调FallbackProvider
  3. 自定义异常DefaultErrorAttributes

服务间调用异常(服务不可用、超时等)时,只会进入指定的回调类或自定义异常类,不会进入类型为error的ZuulFilter,但最后还是会进入类型为post的ZuulFilter,如果有对应的回调类,则只会进入回调FallbackProvider,不会再进入自定义异常类,没有指定的回调类可用时才会进入自定义处理类。

回调类和自定义异常都只能处理服务间调用异常,其它异常则考虑用类型为error的ZuulFilter,ZuulFilter只能处理网关内部抛出的异常,比如在一些处理逻辑上的运行时异常等。

因为所有的请求都会通过网关,所以可以在网关统计接口的响应时间及成功率等监控数据,统计响应时间可以通过创建一个pre和一个post的ZuulFilter。在pre的ZuulFilter中记下开始时间,在post的ZuulFilter中计算时间差,因为一个请求是同一个线程,所以,可以利用ThreadLocal来实现。

动态修改路由规则

如果配置中心是通过git来管理配置的,可以自动刷新路由配置,非git配置中心,可以通过应用自身定时刷新配置,然后修改路由规则。

首先,需要在配置类或启动类配置bean:

	@Bean
	public ZuulProperties zuulProperties() {
		return new ZuulProperties();
	}

然后,在定时任务中修改该bean的配置:

@Component
public class ConfigScheduled {

	@Autowired
	public ZuulProperties zuulProperties;
	
    @Scheduled(fixedRate = 10000)//单位是毫秒
    public void zuulRoute() {
    	Map<String, ZuulRoute> routeMap = zuulProperties.getRoutes();
    	ZuulRoute zuulRoute = routeMap.get("userInfoservice");
    	/*
    	 * 这里切到另一个网关,比如两组独立的服务,可以轮流更新两组服务的应用而不会导致服务中断
    	 * zuulRoute.setUrl("http://localhost:1003")是直接切到userInfo-service这个服务
         * http://localhost:1003是userInfo-service的服务地址     	
    	 */
    	zuulRoute.setUrl("http://127.0.0.1:1002/userInfo-service");
    }
}

上面代码是把网关1(http://127.0.0.1:1001)的请求路由到网关2(http://127.0.0.1:1002),网关1的property配置文件内容是:

zuul.routes.userInfoservice.path=/userInfo-service/**
zuul.routes.userInfoservice.serviceId=userInfo-service

如果直接路由到服务userInfo-service,则是zuulRoute.setUrl("http://127.0.0.1:1003"),不需要再加服务名userInfo-service。

ZuulRoute的url优先级比serviceId要高,如果url非空,则url生效,如果url为空,则serviceId生效,所以,如果想使用配置文件中的路由配置,则只需把url设为null。

动态修改路由规则在更新应用时,可以做到服务不中断。

修改请求头部信息

请求经过网关,会删除被认为属于敏感的header信息,默认情况下会消除以下头部信息:

Authorization、Set-Cookie、Cookie、Host、Connection、Content-Length、Content-Encoding、Server、Transfer-Encoding、X-Application-Context

如果不希望被删除以上信息,可以修改配置项zuul.sensitive-headers,如果所有敏感信息都不消除,可以把该配置项设为空,如

zuul.sensitive-headers=

如果只要消除其中某几个头部,可以配置如下:

zuul.sensitive-headers=Set-Cookie、Cookie、Host、Connection、Content-Length、Content-Encoding、Server、Transfer-Encoding、X-Application-Context

RequestContext ctx = RequestContext.getCurrentContext();//获取请求上下文对象

HttpServletRequest request = ctx.getRequest();//获取请求对象
		
request.getHeader("X-B3-TraceId");//获取发给网关的请求的头部信息
		
ctx.addZuulRequestHeader("X-B3-TraceId", "new value");//往转发报文加头部信息(add a header to be sent to the origin)

参考:springcloud之zuul网关服务并携带头信息转发token_gaoklongzzz的博客-CSDN博客_zuul携带token启动类注解@EnableZuulProxy@EnableEurekaClient配置文件server.port=1120#指定服务名spring.application.name=eureka-client1#Eureka客户端与Eureka服务端进行交互的地址eureka.client.serviceUrl.defaultZone=http://127.0.0.1:6868/e...https://blog.csdn.net/a1430490717/article/details/105404753/

高并发Zuul参数调优:

高并发下-Zuul参数调优_Aldeo的博客-CSDN博客_zuul 最大连接数目录what is Zuul?Zuul参数剖析routessemaphoreribbonhystrix高并发下常见Zuul异常无法获取信号量(semaphore异常)超时熔断Zuul高可用Zuul版本 - 1.x vs 2.xZuul-1.xZuul-2.x总结what is Zuul?官方介绍:Zuul is the...https://zhangzijie.blog.csdn.net/article/details/101070379?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-101070379-blog-108345812.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-101070379-blog-108345812.pc_relevant_default&utm_relevant_index=1如报错 could not acquire a semaphore for executio,可以看以上文章。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值