Zuul案例、常见使用方式
搭建Zuul服务
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在程序的启动类EurekaZuulClientApplication 加上@EnableEurekaClient注解,开启EurekaClient的功能;加上@SpringBootApplication注解,表明自己是一一个Spring Boot工程;加上@EnableZuulProxy注解,开启Zuul的功能。代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class EurekaZuulClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaZuulClientApplication.class, args);
}
}
配置:
server:
port: 5000
spring:
application:
name: service-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
在本案例中,zuul.routes.hiapi.path为"/hiapi/**",zuul.routes.hiapi.serviceId为"eureka-client",这两个配置就可以将以"/hiapi"开头的Url路由到eureka-client服务。其中,zuul.routes.hiapi 中的“hiapi" 是自己定义的,需要指定它的path 和serviceld, 两者配合使用,就可以将指定类型的请求Url 路由到指定的ServiceId。
同理,满足以“/ribbonapi" 开头的请求Url都会被分发到eureka-ibbon- client, 满足以“/feignapi/"开头的请求Url都会被分发到eureka-feign-client服务。如果某服务存在多个实例,Zuul结合Ribbon会做负载均衡,将请求均分的部分路由到不同的服务实例。
依次启动工程eureka-server、 eureka-client、 eureka-ribbon-client、 eureka-feign-client和eureka-zuul-client,其中eureka-client启动两个实例,端口为8762和8763。在浏览器上多次访问htp:/:ocalhost:000hipihinamn= forezp
,浏览器会交替显示以下内容:
hi forezp, i am from port:8762
hi forezp, i am from port:8763
可见Zuul在路由转发做了负载均衡。同理,多次访问htpt:ocalhost:5000/ei/gnapi/jin hi?name-forezp和ht://ocahtosto000/rbbonapihi? name=forezp,也可以看到相似的内容。
如果不需要用Ribbon做负载均衡,可以指定服务实例的Url,用zuul.routes.hiapi.url配置指定,这时就不需要配置zuul.routes.hiapi.serviceld了
。一旦指定了 Url, Zuul 就不能做负载均衡了,而是直接访问指定的Url, 在实际的开发中这种做法是不可取的。修改配置的代码如下:
zuul:
routes:
hiapi:
path: /hiapi/**
url: http://localhost:8762
如果你想指定Url,并且想做负载均衡,那么就需要自己维护负载均衡的服务注册列表。首先,将ribbon.eureka.enabled改为false,即Ribbon负载均衡客户端不向Eureka Client获取服务注册列表信息
。然后需要自己维护一份注册列表, 该注册列表对应的服务名为hiapi-v1(这个名字可自定义),通过配置hiapi-v1.ribbon.listOfServers来配置多个负载均衡的Url。代码如下:
zuul:
routes:
path: /hiapi/**
srerviceId: hiapi-v1
ribbon:
eureka:
enabled: false
hiapi-v1:
ribbon:
listOfServers: http://localhost:8762,http://localhost:8763
在Zuul上配置API接口的版本号
如果想给每一个服务的 API接口加前缀,例如ht://oclthost:50000 /v1/hiapihi?name=forezp/,即在所有的API接口上加一一个v1作为版本号。这时需要用到zul.prefix的配置,配置示例代码如下:
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
prefix: /v1
重新启动eureka- zuul-service服务,在浏览器*问http://localhost:5000/v1/hiapi/hi?name=test
,浏览器会显示:
在Zuul上配置熔断器
Zuul作为Netflix组件,可以与Ribbon、Eureka和Hystrix等组件相结合,实现负载均衡、熔断器的功能。
在默认情况下,Zuul 和Ribbon相结合,实现了负载均衡的功能
。
在Zuul中实现熔断功能需要实现ZzulFallbackProvider的接口
。实现该接口有两个方法,
- 一个是getRoute()方法,用于指定熔断功能应用于哪些路由的服务;
- 另一个方法fallbackResponse()为进入熔断功能时执行的逻辑。
ZuulFallbackProvider 的源码如下:
实现一个针对eureka-client服务的熔断器,当eureka-client的服务出现故障时,进入熔断逻辑,向浏览器输入一句错误提示,代码如下:
@Component
public class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "eureka-client";
}
@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() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("oooops!error,i'm the fallback.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
重新启动eureka-zuul-client 工程,并且关闭eureka-client 的所有实例,在浏览器*问htp://ocalhost:5000 /hiapi/hi?name-forezp,浏览器显示:
如果需要所有的路由服务都加熔断功能,只需要在getRoute()方法上返回"*
"的匹配符:
@Override
public String getRoute() {
return "*";
}
在Zuul中使用过滤器
实现过滤器很简单,只需要继承ZuulFiter,并实现ZuulFiter中的抽象方法,包括filterType()和filterOrder(),以及IZuulFilter的shouldFilter()和Object run()的两个方法
。
- filterType()即过滤器的类型,它有4种类型,分别是“pre"“post”“routing"和error”。
- filterOrder()是过滤顺序, 它为一个Int类型的值, 值越小,越早执行该过滤器。
- shouldFilter()表示该过滤器是否过滤逻辑,如果为true,则执行run()方法;如果为false,则不执行run()方法。
- run()方法写具体的过滤的逻辑。
在本例中,检查请求的参数中是否传了token这个参数,如果没有传,则请求不被路由到具体的服务实例,直接返回响应,状态码为401。
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String accessToken = request.getParameter("token");
if (accessToken == null) {
log.warn("token is empty!");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty!");
} catch (IOException e) {
e.printStackTrace();
}
}
log.info("ok");
return null;
}
}
重新启动服务,打开浏览器,访问http://localhost:5000/hiapi/hi?name=forezp
,浏览器显示:
再次输入http://localhost:5000/hiapi/hi?name=forezp&token=HJHKLL
可见,MyFilter 这个Bean注入IoC容器之后,对请求进行了过滤,并在请求路由转发之前进行了逻辑判断。在实际开发中,可以用此过滤器进行安全验证。本例的架构图如图:
Zuul的常见使用方式
Zuul是采用了类似于SpringMVC的DispatchServlet来实现的,采用的是异步阻塞模型,所以性能比Ngnix差
。由于Zuul和其他Netflix组件可以相互配合、无缝集成,Zuul 很容易就能实现负载均衡、智能路由和熔断器等功能。在大多数情况下,Zuul都是以集群的形式存在的。由于Zuul的横向扩展能力非常好,所以当负载过高时,可以通过添加实例来解决性能瓶颈。
一种常见的使用方式是对不同的渠道使用不同的Zuul来进行路由
,例如移动端共用一个Zuul网关实例,Web端用另一个Zuul网关实例,其他的客户端用另外一个Zuul实例进行路由。这种不同的渠道用不同Zul实例的架构如下图所示:
另外一种常见的集群是通过Ngnix和Zuul相互结合来做负载均衡。暴露在最外面的是Ngnix主从双热备进行Keepalive, Ngnix 经过某种路由策略,将请求路由转发到Zuul集群上,Zul最终将请求分发到具体的服务上。架构图如下图所示:
下一篇: OpenGL进阶之实例化
推荐阅读
-
Linux计划任务Crontab学习笔记(5):常见错误使用案例
-
php使用redis的几种常见操作方式和用法示例
-
IOS学习(九)UITabView使用及几种常见的创建方式
-
使用Python和AWK两种方式实现文本处理的长拼接案例
-
SpringBoot2.0 基础案例(04):定时任务和异步任务的使用方式
-
夯实Java基础系列17:一文搞懂Java多线程使用方式、实现原理以及常见面试题
-
Linux计划任务Crontab学习笔记(5):常见错误使用案例
-
C# NAudio 库的各种常见使用方式之播放 录制 转码 音频可视化
-
php使用redis的几种常见操作方式和用法示例
-
使用Vue.js下载方式案例详解