网上很多资源都把API网关,是什么,能做什么解释得非常清楚,但是对于初学者来说我觉得是不够友好的,Zuul就是SpringCloud微服务中的网关。
对于初学者入门来说,只需要知道Zuul就是当服务增多之后,就要对API进行一个统一的管理,某个类型的API就会调用某个类型的服务,除此之外还能对请求过来的API进行一个过滤。更进一步才是Zuul其它作用,具体有哪些作用如图所示:
本文重点讲解的是路由转发 和过滤器 。
1 如何引入Zuul
一样的,建立一个Zuul模块,本例中没有什么消费端,所以就没有采取之前建立空父模块再建立具体子模块的方法。然后往Zuul中的pom文件中添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
到现在整个项目的目录结构如图所示:
2 主启动类和配置文件
因为不涉及服务消费等,只是做api的处理,所以主启动类还是比较简单的
@SpringBootApplication
@EnableZuulProxy //开启Zuul
@EnableEurekaClient
public class ZuulMain9401 {
public static void main(String[] args) {
SpringApplication.run(ZuulMain9401.class, args);
}
}
配置文件的话和常规的Eureka客户端是一样的
spring:
application:
name: zuul9401
server:
port: 9401
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8001/eureka/
instance:
instance-id: zuul9401
3 路由转发
路由转发主要是通过配置文件来修改,往上面配置文件中增加内容 ,下面会讲3种方式的转发路由。
设置注册Eureka的服务id
增加第一波配置文件,是在原有的配置文件上增加了以下内容。
zuul:
routes:
user-a:
path: /api-a/**
serviceId: eureka-provide
user-a
是随便定义 ,path
是外部访问的路径,serviceId是微服务配置文件的spring.application.name
的值。
所以上面增加的配置文件整体意思是,当外部访问/api-a/
相关路径时候,会转发给名字为eureka-provid
的服务提供服务。
开启Eureka服务注册中心EurekaServer8001
,服务提供者EurekaProvide7001/2/3
,API网关ZuulMain9401
:
接着访问http://localhost:9401/api-a/e... ,按照分析,应该会被转发到eureka-provide
服务里的eureka/provide路径。
为了防止有点混,贴一次第一个项目的代码,详情请看本系列的第一篇文章。
@SpringBootApplication
@RestController
@EnableEurekaClient
public class EurekaProvide7001 {
@Value("${server.port}")
int port;
@GetMapping("/eureka/provide")
public String getInfo() {
return "hello, i am eureka provide, the provide service. My port: " + port;
}
@GetMapping("/eureka/delayProvide")
public String delayGetInfo() throws InterruptedException {
Thread.sleep(3000);
return "hello, delay to do something";
}
public static void main(String[] args) {
SpringApplication.run(EurekaProvide7001.class, args);
}
}
可以看到能够成功转发路由
设置URL
增加第二波配置文件
zuul:
routes:
# user-a:
# path: /api-a/**
# serviceId: eureka-provide
user-b:
path: /api-b/**
url: http://localhost:7101/
其它如上,url
需要转发到哪个服务
通过Edit Configurations
更改端口以及服务名以模拟新的服务,具体操作同样在第一篇文章中有清晰的gif图。
其它服务不用关闭,继续开启刚新建的Provide7101
,重启ZuulMain9401
服务,访问http://localhost:9401/api-b/e... ,同样能过够看到成功转发
设置非注册Eureka的服务id
之前在学习Ribbon的时候也说过,我们可以通过Ribbon设置访问一些没有注册进Eureka的服务,同样在API网关也能通过配置文件设置Ribbon来达到一样的效果。
增加第三波配置文件
zuul:
routes:
# user-a:
# path: /api-a/**
# serviceId: eureka-provide
# user-b:
# path: /api-b/**
# url: http://localhost:7101/
user-c:
path: /api-c/**
serviceId: provide-without-eureka
#一定需要这个才行
ribbon:
eureka:
enabled: false
provide-without-eureka:
ribbon:
ServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: localhost:7201, localhost:7202
ConnectTimeout: 1000
ReadTimeout: 3000
MaxTotalHttpConnections: 500
MaxConnectionsPerHost: 100
如果取消user-a
的相关注释,此时访问user-a是无法转发的,会报500的错误,我猜是因为设置了ribbon.eureka.enabled = false
的缘故。
serviceId同样是微服务的名称,然后对这个微服务设置,所以是设置微服务名[provid-without-eureka].ribbon
,其它属性都是相关属性,最重要的同样是listOfServers
,表示这个访问这个服务名会在这些服务列表中进行分配。
为了简单还是用同一个服务,用上面的方法修改配置文件即可,修改端口号7201,修改eureka.client.register.with.eureka = false
来模拟没有注册进Eureka的服务。
接着再复制一份配置,其它都不遍,把端口号改成7202,总共是新建了端口为7201,7202的两个服务。
其它服务不用关,开启ProvideWithoutEureka7201/2
服务,重启ZuulMain9401
服务,此时所有的服务开启如下
访问http://localhost:9401/api-c/e... ,服务还是一样能进行成功转发
4 查看路由状态
顺便简单说下查看路由状态,首先还是需要增加配置文件,是一定要增加
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
接着访问http://localhost:9401/actuato... ,正常情况会出现以下
如果想得到详细信息,那么只需要访问http://localhost:9401/actuato...
5 转发路由时的Fallback
和Hystrix,当转发路由发现服务不能够正常提供服务的时候,就可以fallback。
新建一个类MyFallbackProvider
实现FallbackProvider
接口
@Component
public class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//为所有的路由提供回退
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
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("something wrong, fallback now".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
现在来手动关闭ProvideWithoutEureka7201/2
服务模拟服务宕机,来看看是否能回退
6 过滤器
之所以Zuul能完成验证、授权、静态资源处理等,就是得益于下面要讲的过滤器,但是主要是讲最基本的过滤,以后可能以后进阶的时候可能再深入讲。
创建过滤器
首先创建filter包,然后创建一个过滤器类MyPreFilter
,需要实现ZuulFilter
接口
public class MyPreFilter extends ZuulFilter {
@Override
public String filterType() { //过滤器类型
return FilterConstants.PRE_TYPE; //请求前处理
}
@Override
public int filterOrder() { //过滤器顺序,越小越优先
return 0;
}
@Override
public boolean shouldFilter() { //是否开启过滤
return true;
}
@Override
public Object run() throws ZuulException { //执行逻辑
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
System.out.println("[ PreFilter" + " ]" + String.format("send %s request to %s",request.getMethod(),request.getRequestURL()));
return null;
}
}
在FilterConstants
类中定义了一系列常量,其中对于过滤器就是以下几种
public static final String ERROR_TYPE = "error"; //出错时执行
public static final String POST_TYPE = "post"; //请求后请求
public static final String PRE_TYPE = "pre"; //请求前请求
public static final String ROUTE_TYPE = "route"; //处理目标请求
同时再建立一个后置请求
public class MyPostFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//这里把PreFilter改为PostFilter
System.out.println("[ PostFilter" + " ]" + String.format("send %s request to %s",request.getMethod(),request.getRequestURL()));
return null;
}
}
注入容器中
新建一个config包,在包下创建一个类ZuulConfiguration
@Configuration
public class ZuulConfiguration {
@Bean
public MyPreFilter getZuulPreFilterBean() {
return new MyPreFilter();
}
@Bean
public MyPostFilter getZuulPostFilterBean() {
return new MyPostFilter();
}
}
此时Zuul模块的目录结构如下
注意,这里有个坑,就是当开启了过滤器后,会发现前一小节的fallback失效。
重启ZuulMain9401
服务,并且清空idea输出控制台
如果是接着上面一节的内容,那么此时应该是转发的是非注册进Eureka服务的路由
访问http://localhost:9401/api-c/e... ,查看控制台输出
创作不易,如果对你有帮助,欢迎点赞,收藏和分享啦!
下面是个人公众号,有兴趣的可以关注一下,说不定就是你的宝藏公众号哦,基本2,3天1更技术文章!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。