在向微服务框架这样的分布式架构中,需要确保跨多个服务调用的关键行文的正常运行,如安全、日志记录和用户跟踪。要实现此功能,开发人员需要在所有服务中始终如一地强制这些特性,而不需要每个开发团队都构建自己的解决方案。虽然可以使用公共库或扩家来帮助在单个服务中直接构建这些功能,但这样做会造成3个影响。
为了解决这个问题,需要将这些横切关注点抽象成一个独立且作为应用程序中所有微服务调用的过滤器和路由器的服务。这种横切关注点被称为服务网关(service gateway)。服务客户端不再直接调用服务。取而代之的是,服务网关作为单个策略执行点,所有调用都通过服务网关进行路由,然后被路由到最终目的地。
使用服务网关时的注意事项:
在实现服务网关功能时,我们通常将服务网关置于所有服务实例的前面,但是这样做有一定的危险性,因为它会是服务网关成为瓶颈。
为了尽量避免瓶颈的出现,我们要保持为服务网关编写的代码是无状态的。不要在内存中为服务网关存储任何信息。如果不小心,就有可能限制网关的伸缩性,导致不得不确保数据在所有服务网关实例中被复制。
Spring Cloud集成了Netflix开源项目Zuul。Zuul是一个服务网关,它非常容易通过Spring Cloud注解进行创建和使用。Zuul提供了许多功能,具体包括以下几个:
在pom.xml中导入依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
在bootstrap.properties中加入配置:
spring.application.name=zuul-server
server.port=9002
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
spring.rabbitmq.host=192.168.3.12
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.profile=dev
# 开放一个API接口,用于获取路由表
management.endpoints.web.exposure.include=routes
至此,我们就搭建好了Zuul服务器。
怎样获取路由表:
使用GET方法,访问以下URL:http://: /actuator/routes,可以获取到路由表。
Zuul的所有路由映射都是通过在properties或者yml文件中定义路由来完成的。但是,Zuul可以根据其服务ID自动路由请求,而不需要配置,比如有一个服务的ID的order-eureka
,那么路由映射中就会自动多出一行"/order-eureka/**": "order-eureka"
。
使用自动映射路由有一个好处,当我们想Eureka服务器中添加新服务的时候,Zuul可以将新服务添加到路由映射表中,而无需修改Zuul。
Zuul允许开发人员更细粒度地明月定义路由映射,而不是单纯依赖服务的Eureka服务ID创建的自动路由。假设开发人员希望通过缩短服务名称来简化路由,而不是通过默认路由在Zuul中访问服务,做法如下:
# 将order-eureka服务的路由映射为/order/**
zuul.routes.order.service-id=order-rureka
zuul.routes.order.path=/order/**
如果想要排除Eureka服务ID路由的自动映射,只提供自定义的服务路由,可以这样来做:
# 排除order-rureka的自动路由映射
zuul.ignored-services=order-rureka
如果要排除所有基于Eureka的路由,可以将ignored-services
属性设置为*
。
Zuul可以用来路由那些不受Eureka管理的服务。在这种情况下,可以建立Zuul直接路由到一个静态定义的URL。做法如下:
zuul.routes.angular.path=/angular/**
zuul.routes.angular.url=http://>:>
虽然通过Zuul网关代理所有请求确实可以简化服务调用,但是在想要编写应用于所有流经网关的服务调用的自定义逻辑时,Zuul的真正威力才发挥出来,在大多数情况下,这种自定义逻辑用语强制执行一组一致的应用程序策略,如安全性、日志记录和对所有服务的追踪。
这些应用程序策略被认为是横切关注点,因为开发人员希望将它们应用于应用程序中的所有服务,而无须修改每个服务来实现它们。通过这种方式,Zuul 过滤器可以按照与J2EE servlet 过滤器或Spring Aspect 类似的方式来使用。这种方式可以拦截大量行为,并且在原始编码人员意识不到变化的情况下,对调用的行为进行装饰或更改。servlet 过滤器或Spring Aspect 被本地化为特定的服务,而使用Zuul 和Zuul 过滤器允许开发人员为通过Zuul 路由的所有服务实现横切关注点。
Zuul允许开发人员使用Zuul网关内的过滤器构建自定义逻辑。过滤器可用于实现每个服务请求在执行时都会经过的业务逻辑。Zuul支持以下3中类型的过滤器。
流程:
我们要做一个前置过滤器,用来检测URL参数里面有没有token
,当存在token
时不做任何处理,当不存在token
时,返回一个401异常。
代码如下:
@Component
public class TokenFilter extends ZuulFilter {
/**
* filterType()方法用于告诉Zuul,该过滤器是前置过滤器、后置过滤器还是路由过滤器
* @return
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* filterOrder()方法返回一个整数值,只是不同类型的过滤器的执行顺序
* 数值越小,越优先执行
* @return
*/
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
/**
* shouldFilter()方法返回一个布尔值来指示该过滤器是否要执行
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String token = request.getParameter("token");
if (StringUtils.isBlank(token)) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
我们要做一个后置过滤器,当请求结果返回给客户端的时候,往HTTP Header中添加数据。
代码如下:
@Component
public class AddHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse response = requestContext.getResponse();
response.setHeader("token", "this is token");
return null;
}
}