1、API网关解决了什么问题
第一负载均衡的维护,当实例的ip地址发生变动的时候运维人员需要手工去修改实例信息和中间件的一致,若服务增多系统规模增大维护就比较麻烦了。第二在开发的时候为了服务的安全性都会有权限校验机制,若在微服务的各个接口来做这些事情将比较冗余。API网关就是微服务的门面所有外部的访问都要通过它来调度和过滤。
2、zuul解决问题的方式
第一将自身注册为eureka服务,这样实例的维护就有eureka来自动完成,路由的维护zuul通过以服务名作为contextpath的方式来创建路由映射。第二把校验和过滤独立成一个单独的服务,独立出来之后不让各个微服务调用,而是通过网关进行统一的调用来对微服务接口做前置过滤。
3、搭建一个zuul
构建网关
(1)创建一个spring-boot工程并在pom.xml中引入是spring-cloud-starter-zuul依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
(2)在应用启动的主类上增加EnableZuulProxy注解开启Zuul的API网关
@EnableZuulProxy
//注意这里用的不是springBootApplication
@SpringCloudApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
(3)在配置文件中配置Zuul应用基础信息
spring.application.name=api-gateway
server.port=5555
请求路由
(1)传统方式:在配置文件中进行路由规则的配置来实现转发功能
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:8081/
//这样在访问http://localhost:5555/api-a-url/hello就会转发到
http://localhost:8081/hello上
(2)面向服务的路由:传统配置对运维来说需要化费大量的时间来维护path与url的关系,面向服务让其映射到具体的服务来解决这个问题。主要的做法是与eureka结合
//1、zuul中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
//2、应用主类上添加服务发现的注解@EnableDiscoveryClient
//3、在配置文件中进行路由转发和服务注册的配置
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
//这样在访问http://localhost:5555/api-a/hello就会转发到hello-service的一个实例上
请求过滤
(1)创建一个类继承ZuulFilter来实现过滤的内容
/**
* 请求被路由之前检查httpServletRequest中是否有accessToken的参数
* 若有则被路由,若没有则拒绝访问
* @author ywzuo
*/
public class AccessFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);
//判断过滤器是否被执行,实际来指定过滤器的有效范围
@Override
public boolean shouldFilter() {
return true;
}
//过滤器的具体逻辑
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("send{} request to {}",request.getMethod(),request.getRequestURL().toString());
Object accessToken = request.getParameter("accessToken");
if(accessToken == null) {
logger.warn("accessToken is null");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);//设置返回值的代码
return null;
}
logger.info("accessToken is ok");
return null;
}
/*
*过滤器的类型,决定在请求的那个生命周期中执行,
*pre代表请求被路由之前执行
*routing在路由请求时被调用
*post咋routing和error过滤器之后被调用
*error处理请求时发生错误时被调用
*/
@Override
public String filterType() {
return "pre";
}
//过滤器的执行顺序,当请求在一个阶段有多个过滤器时指定过滤器的执行顺序
@Override
public int filterOrder() {
return 0;
}
}
(2)为过滤类创建bean对象
@EnableDiscoveryClient
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
当访问http://localhost:5555/api-a/hello返回401的错误。http://localhost:5555/api-a/hello?accessToken=token这样就会返回正确的结果。
4、路由配置
(1)传统路由的配置
- 单实例配置
zuul.routes..path和zuul.routes..url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:8081/ - 多实例配置
zuul.routes..path与 zuul.routes..serviceId
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
(2)服务路由配置
zuul.routes..path与 zuul.routes..serviceId
也可以更简洁的 zuul.routes.=
zuul.routes.hello-service = /api-a/**
(3)服务路由的默认规则
zuul在引入eureka之后每个服务就会自动创建一个默认的路由规则。可以使用zuul.ignored-services参数来忽略一些不自动创建路由的配置
(4)自定义路由映射规则
用patternServiceRouteMapper对象来定义服务与路由的映射关系
(5)路径匹配
?:匹配任意单个字符
*:匹配任意数量的字符
* :匹配任意量量的字符,支持多级目录
同时zuul.ignored-patterns可以忽略一些借口不被路由
zuul.ignored-patterns=/ /hello/ *
(6)路由前缀
zuul.prefix
(7)本地跳转
zuul支持forward的服务端跳转配置
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.url=forward:/local
(8)cookie与头信息
zuul在请求路由的时候会过滤掉http请求头信息中的一些敏感信息其中包括cookie。如果要获取cookie可以通过下面的配置
zuul.routes..customSensitiveHeaders=true
zuul.routes..sensitiveHeaders=
(9)重定向
网关在进行路由转发前为请求设置Host头信息
zuul.addHostHeader=true
(10)hytrix和ribbon的支持
当使用path与url映射关系来配置路由规则时没有线程隔离和断路器的保护也没有负载均衡。path和serviceId是支持的,并且能够通过hytrix和ribbon的参数来调整路由请求的配置。
zuul.retryable=false来关闭全局的重试机制
zuul.routes..retryable=false指定路由来关闭重试的机制
5、过滤器
(1)过滤器的生命周期
(2)异常的处理
try-catch处理
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try{
doSomething();
}catch(Exception e){
ctx.set(“error.stutas_code”,HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set(“error.exception”,e);
}
return null;
}
errorfilter处理
@Override
public String filterType() {
return “error”;
}
(3)禁用过滤器
zuul...disable=true
6、动态加载
在微服务中,API网关担负着外部统一入口的重任,与其他服务不同,它必须保证不关闭不停机,从而确保整个系统的对外服务。所以我们就不能关机改东西再上线,这样会影响到其它服务。Zuul早已考虑了这点,它实现了不关机状态下的动态路由和动态添加/删除过滤器等功能。接下来我们分别来看看这些功能如何使用。
动态路由
(1)创建一个spring boot工程,在pom.xml中引入zuul,eurake,config依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
(2)创建配置文件bootstrap.properties
spring.application.name=api-gateway
server.port=5556
eureka.client.serviceUrl.defaultZone= http://localhost:1111/eureka/
spring.cloud.config.uri= http://localhost:7001/
(3)在启动应用主类中增加EnableZuulProxy和refreshScope注解将配置内容动态化
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayDynamicRouteApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayDynamicRouteApplication.class, args);
}
//动态的刷新
@RefreshScope
//将配置内容实例化
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
}
(4)在git仓库中创建配置文件api-gateway.properties文件
文件内容如下
zuul.routes.service-a.path=/service-a/**
zuul.routes.service-a.serviceId=hello-service
(5)访问http://localhost:5556/routes
这时我们把内容改为
zuul.routes.service-b.path=/service-b/**
zuul.routes.service-b.serviceId=hello-service
然后post访问http://localhost:5556/refresh得到如下结果
在访问http://localhost:5556/routes得到如下结果:
动态过滤器
(1)创建springboot工程在pom.xml中引入zuul、eurekal、groovy的依赖
(2)在配置文件中增加配置
spring.application.name=api-gateway
server.port=5555
eureka.client.serviceUrl.defaultZone= http://localhost:1111/eureka/
zuul.routes.service-a.path=/service-a/**
zuul.routes.service-a.serviceId=hello-service
zuul.filter.root=filter
zuul.filter.inteval=5
(3)创建加载自定义属性的类
@ConfigurationProperties("zuul.filter")
public class FilterConfiguration {
private String root;
private Integer interval;
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public Integer getInterval() {
return interval;
}
public void setInterval(Integer interval) {
this.interval = interval;
}
}
(4)创建应用主类并写下面的逻辑
@EnableConfigurationProperties({FilterConfiguration.class})
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayDynamicFilterApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayDynamicFilterApplication.class, args);
}
private FilterConfiguration filterConfiguration;
@Bean
public FilterLoader filterLoader() {
FilterLoader filterLoader = FilterLoader.getInstance();
filterLoader.setCompiler(new GroovyCompiler());
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(
filterConfiguration.getInterval(),
filterConfiguration.getRoot()+"/pre",
filterConfiguration.getRoot()+"/post");
}catch(Exception e) {
throw new RuntimeException(e);
}
return filterLoader;
}
}
根据上面的定义,api网关应用会每隔5秒,从api网关服务所在的位置的filter/pre和filter/post目录下获取Groovy定义的过滤器,并对其进行编译和动态加载使用。