Zuul简介

所有微服务之间的调用,都应该通过服务网关进行路由,服务网关充当服务与服务之间的中介。服务网关像交通警察一样指挥交通,将用户引导到目标微服务实例。服务网关还充当着应用程序内所有微服务调用的入站流量的守门人。有了服务网关,服务客户端永远不会直接调用单个服务的URL,而是将所有调用都放到服务网关上。

构建一个Zuul Spring boot项目

首先,在pom.xml中添加依赖spring-cloud-starter-netflix-zuul。

<!-- Spring cloud starter: netflix-zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

其次,在启动类Application中加入@EnableZuulProxy注解。

@SpringBootApplication
@EnableZuulProxy
public class ServerZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServerZuulApplication.class, args);
}
}

此外,它还是一个Eureka Client和Config Client,如何配置Eureka Client和Config Client请看前面章节。

在Zuul中配置路由

Zuul的核心是一个反向代理,即一个中间服务器,它位于客户端服务器与资源服务器之间,客户端服务器只需访问反向代理服务器,而反向代理服务器负责捕获客户端请求,然后代表客户端调用远程资源。配置Zuul有3种方式:

(1)通过服务发现自动映射路由,此时不需要任何配置。

比如我们正常访问一个在Eureka Server注册的服务(Eureka的服务ID为app-sql):

http://localhost:10200/app-sql/sql-sp-search/list(格式为http://[host]:[port]/[context-path]/[path])

如果使用Zuul访问,则为:

http://localhost:10030/server-zuul/app-sql/app-sql/sql-sp-search/list(格式为http://[host]:[port]/[context-path]/[app service-id]/[app context-path]/[path])

(2)通过服务发现手动映射路由,Zuul使用了Hystrix和Ribbon库,来帮助方式长时间运行服务调用而影响服务网关的性能。

zuul:
# 排除所有的基于Eureka的服务ID注册的路由
ignored-services: '*'
# 添加前缀
prefix: /api
# Eureka的服务ID
routes:
app-sql: /s1/**
app-one: /s2/**
app-anther-one: /s3/**
# 设置Hystrix超时(default可以替换成具体的某个服务ID)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1
# 设置Ribbon超时(如果是具体的某个服务ID,可以用[service-id].ribbon)
ribbon:
ConnectTimeout:
ReadTimeout: 8000
# Zuul不会将敏感HTTP首部(如Cookie,Set-Cookie,Authorization)转发到下游服务。这里排除了Authorization为后面的OAuth2服务
sensitiveHeaders: Cookie,Set-Cookie

此时url为:http://localhost:10030/server-zuul/api/s1/app-sql/sql-sp-search/list(格式为http://[host]:[port]/[context-path]/[prefix]/[app routes.app-sql]/[app context-path]/[path])

[注1] 一般来说,hystrixTimeout >= ribbonTimeout(ReadTimeout + ConnectTimeout)。如果小于,则会出现警告(参考AbstractRibbonCommand.getHystrixTimeout())。其中ribbonTimeout的计算公式可以参考AbstractRibbonCommand.getRibbonTimeout()。

这里计算公式是ribbonTimeout = (ReadTimeout + ConnectTimeout)*(MaxAutoRetries+ 1)*(MaxAutoRetriesNextServer + 1) = (8000 + 1000)* 1 * 2 = 18000ms,所以hystrixTimeout要设置>=18000。

[注2]  这里配置的sensitiveHeaders会在Spring Cloud Security OAuth2中用到。

(3)使用静态URL手动映射路由

有些服务没有向Eureka Server注册,并没有受到Eureka Server的管理,比如一个用python写的服务,这时仍然可以建立Zuul直接路由到静态URL,并且可以手动配置Hystrix和Ribbon做到熔断和负载均衡。

zuul:
routes:
python-service:
path: /ps1/**
# 定义一个服务ID
serviceId: python-service
hystrix:
command:
python-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 1
python-service:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
# 如果python-service服务有多个实例,则可以负载均衡映射到多个路由
listOfServers: http://localhost:9221,http://localhost:9222
# 设置ribbon的timeout
ConnectTimeout:
ReadTimeout:
MaxTotalHttpConnections:
MaxConnectionsPerHost:

[注1] 参考https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.2.RELEASE/single/spring-cloud-netflix.html#netflix-zuul-reverse-proxy

过滤器

当我们通过网关自定义逻辑时(如安全性,日志,服务跟踪等),我们可以使用Zuul过滤器

(1)前置过滤器(PRE Filters):在Zuul将请求发送到目的地前调用,可以检查request header,验证用户信息,log记录等。

(2)路由过滤器(ROUTING Filters):调用目标服务前调用。比如它可以将服务调用重定向到另一个地方,这里的重定向并不是HTTP重定向,而是会终止传入的HTTP请求,然后再代表原始调用者发送新的请求。

(3)后置过滤器(POST Filters):在目标服务被调用并返回响应后调用。比如在response header中添加一些信息。

(4)Error过滤器(ERROR Filters):发生error时调用。

它们之间的关系如下图:

[注] 参考https://github.com/Netflix/zuul/wiki/How-it-Works

下面是3个过滤器的代码示例:

package com.mytools.filter;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; /**
* 前置过滤器<br>
*/
@Component
public class PreFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PreFilter.class); private static final String PRE_FILTER_TYPE = "pre";
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true; /* Filter type: PRE Filter
* @see com.netflix.zuul.ZuulFilter#filterType()
*/
@Override
public String filterType() {
return PRE_FILTER_TYPE;
} /* 过滤器的执行顺序
* @see com.netflix.zuul.ZuulFilter#filterOrder()
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
} /* 是否执行过滤器
* @see com.netflix.zuul.IZuulFilter#shouldFilter()
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
} /* run()是每次服务通过过滤器时执行的代码
* @see com.netflix.zuul.IZuulFilter#run()
*/
@Override
public Object run() { logger.debug("<<<<< PreFilter start >>>>>");
RequestContext ctx = RequestContext.getCurrentContext();
printReqHeader(ctx);
printZuulReqHeader(ctx);
logger.debug("<<<<< PreFilter end >>>>>"); return null;
} private void printReqHeader(RequestContext ctx) { HttpServletRequest req = ctx.getRequest();
List<String> headerNameList = new ArrayList<>(); if (ctx.getRequest() != null) {
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
headerNameList.add(headerNames.nextElement());
}
} if (headerNameList.isEmpty()) {
logger.info("----- Original Request Header is NULL. -----");
} else {
logger.info("----- Original Request Header: -----");
for (String headerName : headerNameList) {
logger.info(String.format("%s: %s", headerName, req.getHeader(headerName)));
}
}
} private void printZuulReqHeader(RequestContext ctx) {
Map<String, String> reqMap = ctx.getZuulRequestHeaders();
if (reqMap == null || reqMap.isEmpty()) {
logger.info("----- Zuul Request Header is NULL. -----");
} else {
logger.info("----- Zuul Request Header: -----");
reqMap.forEach((p, q) -> {
logger.info(String.format("%s: %s", p, q));
});
}
}
}

PreFilter

package com.mytools.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter; /**
* 路由过滤器<br>
*/
@Component
public class RoutingFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PostFilter.class); public static final String ROUTE_FILTER_TYPE = "route";
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true; /* Filter type: ROUTING Filter
* @see com.netflix.zuul.ZuulFilter#filterType()
*/
@Override
public String filterType() {
return ROUTE_FILTER_TYPE;
} /* 过滤器的执行顺序
* @see com.netflix.zuul.ZuulFilter#filterOrder()
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
} /* 是否执行过滤器
* @see com.netflix.zuul.IZuulFilter#shouldFilter()
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
} /* run()是每次服务通过过滤器时执行的代码
* @see com.netflix.zuul.IZuulFilter#run()
*/
@Override
public Object run() { logger.debug("<<<<< RoutingFilter start >>>>>");
logger.info("This is Routing Filter.");
logger.debug("<<<<< RoutingFilter end >>>>>"); return null;
}
}

RoutingFilter

package com.mytools.filter;

import java.util.ArrayList;
import java.util.List; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import com.netflix.util.Pair;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; /**
* 后置过滤器<br>
*/
@Component
public class PostFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(PostFilter.class); private static final String POST_FILTER_TYPE = "post";
private static final int FILTER_ORDER = 1;
private static final boolean SHOULD_FILTER = true; /* Filter type: POST Filter
* @see com.netflix.zuul.ZuulFilter#filterType()
*/
@Override
public String filterType() {
return POST_FILTER_TYPE;
} /* 过滤器的执行顺序
* @see com.netflix.zuul.ZuulFilter#filterOrder()
*/
@Override
public int filterOrder() {
return FILTER_ORDER;
} /* 是否执行过滤器
* @see com.netflix.zuul.IZuulFilter#shouldFilter()
*/
@Override
public boolean shouldFilter() {
return SHOULD_FILTER;
} /* run()是每次服务通过过滤器时执行的代码
* @see com.netflix.zuul.IZuulFilter#run()
*/
@Override
public Object run() { logger.debug("<<<<< PostFilter start >>>>>");
RequestContext ctx = RequestContext.getCurrentContext();
printResHeader(ctx);
printZuulResHeader(ctx);
logger.debug("<<<<< PostFilter end >>>>>"); return null;
} private void printResHeader(RequestContext ctx) { HttpServletResponse res = ctx.getResponse();
List<String> headerNameList = new ArrayList<>(); if (ctx.getRequest() != null) {
headerNameList.addAll(res.getHeaderNames());
} if (headerNameList.isEmpty()) {
logger.info("----- Original Response Header is NULL. -----");
} else {
logger.info("----- Original Response Header: -----");
for (String headerName : headerNameList) {
logger.info(String.format("%s: %s", headerName, res.getHeader(headerName)));
}
}
} private void printZuulResHeader(RequestContext ctx) {
List<Pair<String, String>> resList = ctx.getZuulResponseHeaders();
if (resList == null || resList.isEmpty()) {
logger.info("----- Zuul Response Header is NULL. -----");
} else {
logger.info("----- Zuul Response Header: -----");
resList.forEach(elem -> {
logger.info(String.format("%s: %s", elem.first(), elem.second()));
});
}
}
}

PostFilter

使用Actuator查询路由和过滤器信息

Zuul新添加了两个Endpoints用于查看路由和过滤器信息,只需作以下配置即可。

## Actuator info (need add '/actuator' prefix)
management:
endpoints:
web:
exposure:
# routes: 查看所有路由 | filters: 查看所有过滤器
include: routes,filters,info,health

Spring Cloud(5):服务路由(Zuul)的更多相关文章

  1. Spring Cloud 微服务:Eureka+Zuul+Ribbon+Hystrix+SpringConfig实现流程图

    相信现在已经有很多小伙伴已经或者准备使用springcloud微服务了,接下来为大家搭建一个微服务框架,后期可以自己进行扩展.会提供一个小案例: 服务提供者和服务消费者 ,消费者会调用提供者的服务,新 ...

  2. Spring Cloud 网关服务 zuul 三 动态路由

    zuul动态路由 网关服务是流量的唯一入口.不能随便停服务.所以动态路由就显得尤为必要. 数据库动态路由基于事件刷新机制热修改zuul的路由属性. DiscoveryClientRouteLocato ...

  3. Spring Cloud微服务中网关服务是如何实现的?(Zuul篇)

    导读 我们知道在基于Spring Cloud的微服务体系中,各个微服务除了在内部提供服务外,有些服务接口还需要直接提供给客户端,如Andirod.IOS.H5等等. 而一个很尴尬的境地是,如果直接将提 ...

  4. spring cloud 学习之路由网关(zuul)

    学习自方志朋的博客 http://blog.csdn.net/forezp/article/details/69939114 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现.服务消费. ...

  5. Spring Cloud 微服务二:API网关spring cloud zuul

    前言:本章将继续上一章Spring Cloud微服务,本章主要内容是API 网关,相关代码将延续上一章,如需了解请参考:Spring Cloud 微服务一:Consul注册中心 Spring clou ...

  6. Spring Cloud (13) 服务网关-路由配置

    传统路由配置 所谓传统路由配置方式就是在不依赖于服务发现机制情况下,通过在配置文件中具体制定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由.没有Eureka服务治理框架帮助的时候, ...

  7. Spring Cloud 网关服务 zuul 二

    有一点上篇文章忘了 讲述,nacos的加载优先级别最高.服务启动优先拉去配置信息.所以上一篇服务搭建我没有讲述在nacos 中心创建的配置文件 可以看到服务端口和注册中心都在配置文件中配置化 属性信息 ...

  8. spring cloud 2.x版本 Zuul路由网关教程

    前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...

  9. Spring Cloud 系列之 Netflix Zuul 服务网关

    什么是 Zuul Zuul 是从设备和网站到应用程序后端的所有请求的前门.作为边缘服务应用程序,Zuul 旨在实现动态路由,监视,弹性和安全性.Zuul 包含了对请求的路由和过滤两个最主要的功能. Z ...

  10. Dubbo和Spring Cloud微服务架构'

    微服务架构是互联网很热门的话题,是互联网技术发展的必然结果.它提倡将单一应用程序划分成一组小的服务,服务之间互相协调.互相配合,为用户提供最终价值.虽然微服务架构没有公认的技术标准和规范或者草案,但业 ...

随机推荐

  1. zznu-oj-2134- 发红包!!!-【多项式加法,模拟题目】

    2134: 发红包!!! 题目描述 给你两个最简多项式,请输出两个多项式相加后的结果.给定的多项式的格式为ax^num1+bx^num2+...其中x前面的a,b代表系数num代表指数(次方数),输入 ...

  2. 解决echarts的叠堆折线图数据出现坐标和值对不上的问题

    原文:https://blog.csdn.net/qq_36538012/article/details/88889545 ------------------------------- 说一个小bu ...

  3. c语言1博客作业09

    一.本周作业头 这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/SE2019-3/homework/10033 ...

  4. 第93题:复原IP地址

    一. 问题描述 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式. 示例: 输入: "25525511135" 输出: ["255.255.11.135 ...

  5. uniq cut wc 命令详解

    uniq uniq命令可以去除排序过的文件中的重复行,因此uniq经常和sort合用.也就是说,为了使uniq起作用,所有的重复行必须是相邻的. uniq语法 [root@www ~]# uniq [ ...

  6. 001_STM32程序移植之_DS1302

    1. 测试环境:STM32C8T6 2. 测试模块:DS1302时钟模块 3. 测试接口: 1. DS1302模块接口: DS1302引脚 单片机引脚 VCC--------------------3 ...

  7. D. Restore Permutation(权值线段树)

    D. Restore Permutation time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  8. 微信浏览器H5开发常见的坑

    ios端兼容input光标高度 问题详情描述: input输入框光标,在安卓手机上显示没有问题,但是在苹果手机上 当点击输入的时候,光标的高度和父盒子的高度一样.例如下图,左图是正常所期待的输入框光标 ...

  9. easyui-combobox和C标签判断回显

    <td width="40%"> <select class="easyui-combobox" id="work_property ...

  10. Nginx中配置非英文域名

    前两天遇到个配置越南语的域名的情况.域名和ip解析完成后,直接ping域名也不通,还以为是解析问题.研究了半天,nginx配置非英文域名时,需要有其他操作. 非英文域名转换成punycode编码才可以 ...