门面模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。
除了要实现请求路由、 负载均衡、 校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。
首先,对于路由规则与服务实例的维护间题。SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。这样的设计非常巧妙地将服务治理体系中维护的实例信息利用起来,使得将维护服务实例的工作交给了服务治理框架自动完成,不再需要人工介入。
其次,对千类似签名校验、登录校验在微服务架构中的冗余问题。SpringCloud Zuul提供了一套过滤器机制,它可以 很好地支持这样的任务。开发者可以通过使用Zuul来创建各种校验过滤器,然后指定哪些规则的请求需要执行校验逻辑,只有通过校验的才会被路由到具体的微服务接口,不然就返回错误提示。通过这样的改造,各个业务层的微服务应用就不再需要非业务性质的校验逻辑了,这使得我们的微服务应用可以更专注千业务逻辑的开发,同时微服务的自动化测试也变得更容易实现。
网管是在网络拓扑中是单独的组件,任何请求先通过网管,然后在进行转发。所以需要创建单独Maven工程。
主要添加两个依赖spring-cloud-starter-netflix-eureka-server和spring-cloud-starter-netflix-zuul。zuul依赖中则包含了ribbon、hystrix、actuator等
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-zuul
完整:
4.0.0
com.beyond.zuul
zuul-server1
0.0.1-SNAPSHOT
jar
zuul-server1
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.6.RELEASE
UTF-8
UTF-8
1.8
Finchley.SR2
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
入口类上添加@EnableZuulProxy注解表示开启Zuul的API网关服务功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulServer1Application {
public static void main(String[] args) {
SpringApplication.run(ZuulServer1Application.class, args);
}
}
application.properties文件中的配置可以分为两部分,一部分是Zuul应用的基础信息,还有一部分则是路由规则,如下:
#设置项目端口
server.port=9093
#设置项目名
#server.servlet.context-path=/zuul-server1
#设置注册的服务名称
spring.application.name=zuul-server1
#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
#设置路由规则
# 注意:
# 1. 访问的地址必须匹配才能被路由,比如必须带boot1,boot2这类
# 2. 后面解析的部分会拼接在对应的ServiceID或者url后边进行查询
# 3. 要是报错可能地址没有匹配上,或者项目名称的问题
# routes to serviceId 这里边是通过serviceid来绑定地址,当在路径后添加/api-a/ 则是访问service-A对应的服务。
# ZUUL :http://localhost:9093/boot1/fun1 ;boot1是api-a.path的名称,serviceId=boot1是Eureka注册的服务名称
# BOOT1:http://localhost:9091/fun1 ;这里没有/boot1/fun1进行访问时,boot1工程没有设置项目名
# 如果boot1工程名为boot1,那么访问ZUUL地址就得为:http://localhost:9093/boot1/boot1/fun1,前面的:第一个boot1为匹配的字符;第二个为项目名
# 总结:ZUUL地址 = BOOT1工程项目地址 + api-a.path前缀 + 地址
# 举例:
# 1.无项目名:http://localhost:9093/boot1/fun1 = http://localhost:9093 + /boot1 + /fun1
# 2.有项目名:http://localhost:9093/boot1/boot1/fun1 = http://localhost:9093/boot1 + /boot1 + /fun1
zuul.routes.api-a.path=/boo1/**
zuul.routes.api-a.serviceId=boot1
# routes to url 这里是绑定具体的ip地址
# ZUUL :http://localhost:9093/boot2/hello2
# BOOT2:http://localhost:9092/boot2/hello2
# ZUUL.URL = ZUUL.BasePath + api-a-url.path + URI = http://localhost:9093 + /boot2 + /hello2
# 转发到BOOT2工程地址为:api-a-url.url + URI = http://localhost:9092/boot2/ + /hello2
# 说明:如果你api-a-url.url配置的是http://localhost:9092,那么你访问的时候就得是http://localhost:9093/boot2/boot2/hello2了,这样URI才会是/boot2/hello2,拼接起来才能访问
zuul.routes.api-a-url.path=/boot2/**
zuul.routes.api-a-url.url=http://localhost:9092/boot2/
ZUUL:http://localhost:9093/boot1/fun1
BOOt1:http://localhost:9091/boot1/fun1
ZUUL :http://localhost:9093/boot2/hello/44444
BOOT2:http://localhost:9092/boot2/hello/44444
注意:不要被这里的地址蒙蔽,并不是简单的端口替换,需要看你的配置怎样。
构建好了网关,接下来我们就来看看如何利用网关来实现一个简单的权限验证。这里就涉及到了Spring Cloud Zuul中的另外一个核心功能:请求过滤。请求过滤有点类似于Java中Filter过滤器,先将所有的请求拦截下来,然后根据现场情况做出不同的处理,这里我们就来看看Zuul中的过滤器要如何使用。很简单,两个步骤:
首先我们定义一个过滤器继承自ZuulFilter,如下:
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
public class PermisFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@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 login = request.getParameter("login");
if (login == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
ctx.setResponseBody("非法访问");
}
return null;
}
}
然后在入口类中配置相关的Bean即可,如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.beyond.zuul1.zuulserver1.filter.PermisFilter;
@EnableEurekaClient
@EnableZuulProxy
@SpringBootApplication
public class ZuulServer1Application {
public static void main(String[] args) {
SpringApplication.run(ZuulServer1Application.class, args);
}
@Bean
PermisFilter permisFilter() {
return new PermisFilter();
}
}
关于这个类我说如下几点:
1.filterType方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre表示在路由之前执行过滤器,其他可选值还有post、error、route和static,当然也可以自定义。
2.filterOrder方法表示过滤器的执行顺序,当过滤器很多时,这个方法会有意义。
3.shouldFilter方法用来判断过滤器是否执行,true表示执行,false表示不执行,在实际开发中,我们可以根据当前请求地址来决定要不要对该地址进行过滤,这里我直接返回true。
4.run方法则表示过滤的具体逻辑,假设请求地址中携带了login参数的话,则认为是合法请求,否则就是非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false);表示不对该请求进行路由,然后设置响应码和响应值。这个run方法的返回值在当前版本(Dalston.SR3)中暂时没有任何意义,可以返回任意值。
http://localhost:9093/boot2/hello2
非法访问
http://localhost:9093/boot2/hello2?login=123
正常访问
end