Zuul作为微服务系统的网关组件,用于构建边界服务,致力于动态路由、过滤、监控、弹性伸缩和安全。
为什么需要Zuul
Zuul、Ribbon以及Eureka结合可以实现智能路由和负载均衡的功能;网关将所有服务的API接口统一聚合,统一对外暴露。外界调用API接口时,不需要知道微服务系统中各服务相互调用的复杂性,保护了内部微服务单元的API接口;网关可以做用户身份认证和权限认证,防止非法请求操作API接口;网关可以实现监控功能,实时日志输出,对请求进行记录;网关可以实现流量监控,在高流量的情况下,对服务降级;API接口从内部服务分离出来,方便做测试。
Zuul通过Servlet来实现,通过自定义的ZuulServlet来对请求进行控制。核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列过滤器。Zuul采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。
Zuul生命周期如下图。 当一个客户端Request请求进入Zuul网关服务时,网关先进入”pre filter“,进行一系列的验证、操作或者判断。然后交给”routing filter“进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完成后,最后由”post filter“进行处理,该类型的处理器处理完成之后,将Request信息返回客户端。
Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器.
Zuul功能:
- 认证
- 压力测试
- 金丝雀测试
- 动态路由
- 负载削减
- 安全
- 静态响应处理
- 主动/主动交换管理
Zuul的规则引擎允许通过任何JVM语言来编写规则和过滤器, 支持基于Java和Groovy的构建。
现在我们简单的先搭建一个网关服务器
首先单独建一个项目,用来做网关服务器
引入相关的主要jar包:
1<dependency> 2 <groupId>org.springframework.cloud</groupId> 3 <artifactId>spring-cloud-starter-netflix-zuul</artifactId> 4</dependency> 5<dependency> 6 <groupId>org.springframework.cloud</groupId> 7 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 8</dependency>
引入spring-cloud-starter-netflix-eureka-client的目的是本身通过url映射的方式来实现zuul的转发有局限性,比如每增加一个服务就需要配置一条内容,另外后端的服务如果是动态来提供,就不能采用这种方案来配置了。实际上在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,所以只需要将Zuul注册到eureka server上去发现其他服务,就可以实现对serviceId的映射。
加入yml的配置文件,主要用来连接eureka,及配置网关路由规则
1server: 2 port: 8088 3spring.application.name: startGateway 4eureka: 5 client: 6 service-url: 7 defaultZone: http://root:root@localhost:10000/eureka 8zuul.routes.three.path: /three/** 9zuul.routes.three.service-id: three 10zuul.routes.three.stripPrefix: false 11zuul.routes.five.path: /five/** 12zuul.routes.five.service-id: five 13zuul.routes.five.stripPrefix: false
这边有个坑:
设置 zuul.prefix 可以为所有的匹配增加前缀, 例如 /api,代理前缀默认会从请求路径中移除(通过 zuul.stripPrefix=false 可以关闭这个功能).
- 反响代理配置
- 这里的配置类似nginx的反向代理
- 当请求/api/**会直接交给listOfServers配置的服务器处理
- 当stripPrefix=true的时候 (http://127.0.0.1:3333/api/user/list -> http://192.168.1.100:8080/user/list)
- 当stripPrefix=false的时候(http://127.0.0.1:5555/api/user/list -> http://192.168.1.100:8080/api/user/list)
- zuul.routes.api.path=/api/**
- zuul.routes.api.stripPrefix=false
- api.ribbon.listOfServers=192.168.1.100:8080,192.168.1.101:8080,192.168.1.102:8080
你也可以在指定服务中关闭这个功能:
1zuul.routes.five.path: /five/** 2zuul.routes.five.service-id: five 3zuul.routes.five.stripPrefix: false
在这个例子中, 请求"/five/a"将被跳转到"five"服务的"/five/a"上.如果不配置stripPrefix: false的话就会默认路由到/a上,忽略/five这个前缀,导致404找不到资源
之后在启动类上配置
1package cn.chinotan; 2 3import org.springframework.boot.SpringApplication; 4import org.springframework.boot.autoconfigure.SpringBootApplication; 5import org.springframework.cloud.client.SpringCloudApplication; 6import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 8 9/** 10 * @program: test 11 * @description: 启动类 12 * @author: xingcheng 13 * @create: 2018-12-9 15:39 14 **/ 15@SpringCloudApplication 16@EnableEurekaClient 17@EnableZuulProxy 18public class StartGateway { 19 20 public static void main(String[] args) { 21 SpringApplication.run(StartGateway.class, args); 22 } 23 24}
就完成了一个简单的网关路由
其中@EnableZuulProxy是@EnableZuulServer的加强,@SpringCloudApplication会包含@EnableEurekaClient,所以其实@EnableEurekaClient不需要写
之后在写两个测试controller进行路由判断:
1package cn.chinotan.controller; 2 3import org.springframework.web.bind.annotation.GetMapping; 4import org.springframework.web.bind.annotation.PathVariable; 5import org.springframework.web.bind.annotation.RequestMapping; 6import org.springframework.web.bind.annotation.RestController; 7 8/** 9 * @program: test 10 * @description: zuul测试控制器 11 * @author: xingcheng 12 * @create: 2018-12-08 18:09 13 **/ 14@RestController 15@RequestMapping("/five") 16public class ZuulTestFiveController { 17 18 @GetMapping("/hello/{name}") 19 public String ZuulTestFive(@PathVariable("name") String name) { 20 return "hello " + name + " this is ZuulTestFive"; 21 } 22 23} 24 25 26package cn.chinotan.controller; 27 28import org.springframework.web.bind.annotation.GetMapping; 29import org.springframework.web.bind.annotation.PathVariable; 30import org.springframework.web.bind.annotation.RequestMapping; 31import org.springframework.web.bind.annotation.RestController; 32 33/** 34 * @program: test 35 * @description: zuul测试控制器 36 * @author: xingcheng 37 * @create: 2018-12-08 18:09 38 **/ 39@RestController 40@RequestMapping("/three") 41public class ZuulTestThreeController { 42 43 @GetMapping("/hello/{name}") 44 public String ZuulTestFive(@PathVariable("name") String name) { 45 return "hello " + name + " this is ZuulTestThree"; 46 } 47 48}
当然这些服务也得连接到euerka上,好让代理网关可以根据service-id进行服务发现
其中,euerka上的连接情况如下:

可以看到网关服务器的端口是8088,两个服务的网关是5555-five和3333-three
接下来,我们不直接请求提供服务的three和five服务器,而是请求网关代理


可以看到网关服务成功的路由了这两次请求
服务过滤
Zuul还有一个主要的功能,便是服务过滤,比如,用户在登录前,可以将服务请求过滤到指定的页面去。
在项目中,新增一个MyFilter类。代码如下:
1@Component 2public class MyFilter extends ZuulFilter { 3 @Override 4 public String filterType() { 5 return FilterConstants.PRE_TYPE; 6 } 7 8 @Override 9 public int filterOrder() { 10 return 0; 11 } 12 13 @Override 14 public boolean shouldFilter() { 15 return true; 16 } 17 18 @Override 19 public Object run(){ 20 RequestContext ctx = RequestContext.getCurrentContext(); 21 HttpServletRequest request = ctx.getRequest(); 22 Object accessToken = request.getParameter("token"); 23 if(accessToken == null) { 24 ctx.setSendZuulResponse(false); 25 ctx.setResponseStatusCode(401); 26 try { 27 ctx.getResponse().setHeader("Content-Type", "text/html;charset=UTF-8"); 28 ctx.getResponse().getWriter().write("登录信息为空!"); 29 }catch (Exception e){} 30 return null; 31 } 32 return null; 33 } 34}
其中,filterType方法,返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre:路由之前
routing:路由之时
post: 路由之后
error:发送错误调用
filterOrder:过滤的顺序
shouldFilter:这里可以写逻辑判断,是否要开启过滤
run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
一般我们在使用时,不手打“pre”这些类型,而是通过调用Zuul中已写好的FilterConstants类,其中封装了所有的过滤器类型。这样可以避免打错字符而导致错误的发生。
接下来,我们访问http://localhost:9005/feign/welcome?name=Cheng 显示:
登录信息为空!
在后面加上token,http://localhost:9005/feign/welcome?name=Cheng&token=111就可以正常访问了
类似的过滤器很多,我们可以自定义,从而实现统一的网关控制、监控、跨域、流量控制、负载均衡,身份认证,服务降级等等。