首先,不破坏了服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。
其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
网关顾名思义就是网络请求的入口,外部服务通过网关进行访问我们的内部服务,网关可以做的事情很多,进行限流、安全、权限认证、黑白名单过滤、请求拦截、数据校验、api路由转发等等功能。
为了解决刚才我们微服务所面临的安全性等一些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器,它就是本文将来介绍的:服务网关。
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
使用spring cloud zuul非常的简单,按照惯例,引入依赖spring-cloud-starter-zuul,并且在主入口加入注解@EnableZuulProxy即可,最后我们对yml进行相关配置。
完成这步相关配置后,我们快速实现这两个service 然后使用zuul的地址进行访问他们,就可以得到进行路由调用的结果,非常的简单。当然zuul有自己的默认路由规则,还可以使用正则表达式进行自定义路由规则。
创建两个服务,用户服务,订单服务注册到注册中心
两个网关高可用,nignx高可用负载均衡,网关组件之间没有关系,只有高可用
1.pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.bfxy</groupId>
<artifactId>spring-cloud-master</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-05-zuul-a</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- <version>Dalston.SR5</version> -->
<version>Edgware.SR4</version>
<!-- <version>Finchley.SR1</version> -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-05-zuul-a</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bfxy.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.主入口
@EnableZuulProxy启用网关zuul
package com.bfxy.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy //启用网关
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.配置
routes:相当于集合,里面是key与value的集合,path指的是请求格式,service-id指的是服务名称
key=api-order value= {path: /order-service/** service-id: order-service}
key=api-user: value= { path: /user-service/** service-id: user-service}
如下:
zuul:
routes:
api-order:
path: /order-service/**
service-id: order-service
api-user:
path: /user-service/**
service-id: user-service
spring:
application:
name: zuul-service
##启用retry机制
cloud:
loadbalancer:
retry:
enabled: true
http:
encoding:
charset: UTF-8
multipart:
enabled: true
file-size-threshold: 10
max-file-size: 10MB
max-request-size: 20MB
server:
context-path: /
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:8001/eureka
zuul:
routes:
api-order:
path: /order-service/**
service-id: order-service
api-user:
path: /user-service/**
service-id: user-service
##设置断路器的超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 50000
##全局请求的负载均衡和重试配置
##The Hystrix timeout of 30000ms for the command order-service
##is set lower than the combination of the Ribbon read and connect timeout, 48000ms
ribbon:
ConnectTimeout: 5000
ReadTimeout: 3000
OkToRetryOnAllOperations: true
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 2
##全局的代理配置
feign:
hystrix:
enabled: true
compression:
request:
min-request-size: 2048
mime-types:
- text/html, application/xml, application/json
4.zuul过滤器(对每个接口请求前,请求时,请求后处理逻辑)
extends ZuulFilter
关键方法:run拦截器过滤逻辑
使用Zuul的过滤器, 可以对请求的一些列信息进行安全认证/权限认证/身份识别等功能。只需要自定义Filter后继承ZuulFilter类即可。重写其filterType、filterOrder、shouldFilter以及run方法。 filterType表示filter执行的时机:其value值含义如下所示:
pre:在请求被路由之前调用 比如:登录验证
routing: 在请求被路由之中调用
post: 在请求被路由之后调用
error: 处理请求发生错误时调用
filterOrder表示执行的优先级,值越小表示优先级越高
shouldFilter则表示该filter是否需要执行
package com.bfxy.springcloud;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
@Component
public class MyZuulFilter extends ZuulFilter {
/**
* 表示当前的过滤器是否被调用
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* filterOrder表示执行的优先级,值越小表示优先级越高
*/
@Override
public int filterOrder() {
return 0;
}
/**
* pre: 在请求被路由之前调用
routing: 在请求被路由之中调用
post: 在请求被路由之后调用
error: 处理请求发生错误时调用
*/
@Override
public String filterType() {
return "pre";
}
/**
* 真正执行Filter逻辑的方法
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
System.err.println("--------------uri: -------------- " + request.getRequestURI());
/**
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("x-token");
if(!StringUtils.isBlank(token) && "1234".equals(token)){
ctx.addZuulRequestHeader("token", token);
//当前用户的角色信息. 用户等级. ...
ctx.addZuulRequestHeader("level", "10");
} else {
System.err.println("------access token error!-----------");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody("------access token error!-----------");
return null; //阻止向下游执行
}
*/
return ctx;
}
}
5.FallbackProvider(请求异常处理)
网关的默认回退FallbackProvider的机制只适用于通过配置文件中配置的url来跨服务调用,而不拦截通过FeignClient跨服务调用,也就是说要自己实现一个FeignClient服务的fallback才可以,但是如果通过FeignClient的服务要是特别多怎么办,每一个都要写一个不得累死,这里想到了通过网关的错误过滤器来统一处理FeignClient跨服务调用出错的问题
package com.bfxy.springcloud;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
@Component
public class OrderServiceFallBackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "order-service";
}
//这个老的接口就进行保留
@Override
public ClientHttpResponse fallbackResponse() {
// TODO Auto-generated method stub
return null;
}
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("order-service is not available!".getBytes());
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
};
}
}