Spring Cloud(5):服务路由(Zuul)
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:
过滤器
当我们通过网关自定义逻辑时(如安全性,日志,服务跟踪等),我们可以使用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)的更多相关文章
- Spring Cloud 微服务:Eureka+Zuul+Ribbon+Hystrix+SpringConfig实现流程图
相信现在已经有很多小伙伴已经或者准备使用springcloud微服务了,接下来为大家搭建一个微服务框架,后期可以自己进行扩展.会提供一个小案例: 服务提供者和服务消费者 ,消费者会调用提供者的服务,新 ...
- Spring Cloud 网关服务 zuul 三 动态路由
zuul动态路由 网关服务是流量的唯一入口.不能随便停服务.所以动态路由就显得尤为必要. 数据库动态路由基于事件刷新机制热修改zuul的路由属性. DiscoveryClientRouteLocato ...
- Spring Cloud微服务中网关服务是如何实现的?(Zuul篇)
导读 我们知道在基于Spring Cloud的微服务体系中,各个微服务除了在内部提供服务外,有些服务接口还需要直接提供给客户端,如Andirod.IOS.H5等等. 而一个很尴尬的境地是,如果直接将提 ...
- spring cloud 学习之路由网关(zuul)
学习自方志朋的博客 http://blog.csdn.net/forezp/article/details/69939114 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现.服务消费. ...
- Spring Cloud 微服务二:API网关spring cloud zuul
前言:本章将继续上一章Spring Cloud微服务,本章主要内容是API 网关,相关代码将延续上一章,如需了解请参考:Spring Cloud 微服务一:Consul注册中心 Spring clou ...
- Spring Cloud (13) 服务网关-路由配置
传统路由配置 所谓传统路由配置方式就是在不依赖于服务发现机制情况下,通过在配置文件中具体制定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由.没有Eureka服务治理框架帮助的时候, ...
- Spring Cloud 网关服务 zuul 二
有一点上篇文章忘了 讲述,nacos的加载优先级别最高.服务启动优先拉去配置信息.所以上一篇服务搭建我没有讲述在nacos 中心创建的配置文件 可以看到服务端口和注册中心都在配置文件中配置化 属性信息 ...
- spring cloud 2.x版本 Zuul路由网关教程
前言 本文采用Spring cloud本文为2.1.8RELEASE,version=Greenwich.SR3 本文基于前两篇文章eureka-server.eureka-client.eureka ...
- Spring Cloud 系列之 Netflix Zuul 服务网关
什么是 Zuul Zuul 是从设备和网站到应用程序后端的所有请求的前门.作为边缘服务应用程序,Zuul 旨在实现动态路由,监视,弹性和安全性.Zuul 包含了对请求的路由和过滤两个最主要的功能. Z ...
- Dubbo和Spring Cloud微服务架构'
微服务架构是互联网很热门的话题,是互联网技术发展的必然结果.它提倡将单一应用程序划分成一组小的服务,服务之间互相协调.互相配合,为用户提供最终价值.虽然微服务架构没有公认的技术标准和规范或者草案,但业 ...
随机推荐
- [USACO07MAR]面对正确的方式Face The Right Way
题目概括 题目描述 Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing f ...
- python 学习笔记_1 pip安装、卸载、更新包相关操作及数据类型学习
'''prepare_1 pip安装.卸载.更新组件type 各数据类型''' py -3 -m pip py -3 -m pip listpy -3 -m pip show nosepy -3 -m ...
- 手写KMeans算法
KMeans算法是一种无监督学习,它会将相似的对象归到同一类中. 其基本思想是: 1.随机计算k个类中心作为起始点. 将数据点分配到理其最近的类中心. 3.移动类中心. 4.重复2,3直至类中心不再改 ...
- 新安装的Ubuntu如何切换到root的方法
Ubuntu中root用户和user用户的相互切换Ubuntu是最近很流行的一款Linux系统,因为Ubuntu默认是不启动root用户,现在介绍如何进入root的方法. (1)从user用户切 ...
- 基于 C++ 的脚本语言 cpps 脚本
cpps 脚本是一个基于 C++ 的脚本语言. 基础语法: if&else 接口说明 根据括号中数据判断执行相关代码. 代码演示 var i = toint(io.getc()); if(i ...
- BZOJ 5496: [2019省队联测]字符串问题 (后缀数组+主席树优化建图+拓扑排序)
题意 略 分析 考场上写了暴力建图40分溜了-(结果只得了30分) 然后只要优化建边就行了 首先给出的支配关系无法优化,就直接A向它支配的B连边. 考虑B向以B作为前缀的所有A连边,做一遍后缀数组,两 ...
- 015_STM32程序移植之_NRF24L01模块
STM32程序移植之NRF24L01模块 引脚接线图如下所示 STM32引脚 NRF24L01引脚 功能 GND GND 3.3V 3.3V PB8 CE PB9 CSN PB13 SCK PB15 ...
- ES6-21.class基本语法
1.简介(详情参考) class是构造函数的语法糖. class的constructor方法内的实现,就是原来构造函数的实现. class内的所有方法都是在prototype上的,就是原来构造函数的p ...
- 034_非交互自动生成 SSH 密钥文件
#!/bin/bash#-t 指定 SSH 密钥的算法为 RSA 算法;-N 设置密钥的密码为空;-f 指定生成的密钥文件存放在哪里 rm -rf ~/.ssh/{known_hosts,id_rsa ...
- CF796C Bank Hacking 细节
思路十分简单,答案只有 3 种可能,但是有一些细节需要额外注意一下. code: #include <bits/stdc++.h> #define N 300002 #define set ...