1、为什么要使用微服务网关
1.1、没有网关的话,存在的问题
- 客户端要多次请求不同微服务,增加客户端复杂性
- 存在跨域请求问题
- 认证复杂,每个项目都要单独一套认证
- 难以重构,如果微服务做修改,所有调用的客户端都要改一遍
- 某些微服务使用防火墙/不友好协议,直接访问比较困难
使用微服务网关就可以解决以上问题
1.2、使用微服务网关的有点
- 易于监控
- 易于认证,不需要每个微服务都进行认证
- 减少客户端与微服务端交互次数
2、Zuul简介
Zuul是微服务网关插件,本身已经集成了Ribbon和Hystrix,并且可以和Eureka配合使用。Zuul的核心是一系列过滤器,这些过滤器可以完成以下功能:
- 身份认证与安全
- 审查和监控
- 负载分配和动态路由:动态分配到不同的后端集群
- 静态响应处理
3、编写Zuul微服务网关
3.1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.2、启动类添加注解@EnableZuulProxy
声明Zuul代理,已经整合Ribbon定位微服务,整合Hystrix实现容错
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
3.3、编写配置文件application.yml
server:
port: 8040
spring:
application:
name: microservice-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
4、Zuul的路由端点
@EnableZuulProxy与SpringBootActuator配合使用时,Zuul会暴露一个路由管理端点/routes。直观地查看及管理Zuul的路由。(zuul里面已经整合了actuator,所以不需要再增加依赖)
访问http://localhost:8040/routes,即可获得如下结果
{
"/microservice-provider-user/**" : "microservice-provider-user",
"/microservice-consumer-movie/**": "microservice-consumer-movie"
}
5、路径配置详解
5.1、自定义指定微服务的访问路径
zuul:
routes:
microservice-provider-user: /user/**
只要是/user/**的请求都会自动在microservice-provider-user对应的服务中请求
5.2、忽略指定微服务
zuul:
ignored-services: microservice-provider-user,microservice-consumer-movie
#zuul:
# ignored-services: '*' #忽略所有,只路由指定微服务
# routes:
# microservice-provider-user: /user/**
5.3、同时指定微服务的serviceId和对应路径
zuul:
routes:
user-route: #任意起的名字,作为该配置的标识
service-id: provider-microservice-user
path: /user/**
本例配置效果同5.1
5.4、同时指定path和URL
zuul:
routes:
user-route: #任意起的名字,作为该配置的标识
url: http://localhost:8000/
path: /user/**
6、Zuul的安全与Header
7、Zuul上传文件
8、Zuul的过滤器
过滤器是Zuul的核心组件
8.1、过滤器的类型与请求生命周期
- pre:这种过滤器在请求路由之前被调用,可做身份验证
- routing:将请求路由到微服务,用于构建发送给微服务的请求,使用HttpClient/Ribbon
- post:在路由到微服务以后执行,可为响应添加指定内容
- error:在其他阶段发生错误时执行该过滤器
8.2、编写Zuul过滤器
8.2.1、过滤器类代码
public class PreRequestLogFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);
private static int n = 1;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE; //过滤器类型
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;//过滤器执行顺序
}
@Override
public boolean shouldFilter() {
return true; //过滤器是否执行
}
@Override
public Object run() { //过滤器业务逻辑,该例打印了请求的HTTP方法及请求地址
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
PreRequestLogFilter.LOGGER.info("——————————————————————拦截到第"+(n++)+"次请求!");
return null;
}
}
- filterType:过滤器类型——pre、route、post、error
- filterOrder:返回int值来指定过滤器的执行顺序,不同过滤器可相同数字
- shouldFilter:返回boolean确定该过滤器是否执行
- run:过滤器具体的业务逻辑
8.2.2、启动类添加
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
public PreRequestLogFilter preRequestLogFilter() {
return new PreRequestLogFilter();
}
}
8.3、禁用过滤器
zuul.<SimpleClassName>.<filterType>.disable=true
例如:zuul.PreRequestLogFilter.pre.disable=true
9、Zuul的容错与回退
Zuul中已经默认整合了Hystrix
9.1、编写Zuul的回退类
@Component
public class UserFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
// 表明是为哪个微服务提供回退
return "microservice-provider-user";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// fallback时的状态码
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
// 数字类型的状态码,本例返回的其实就是200,详见HttpStatus
return this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
// 状态文本,本例返回的其实就是OK,详见HttpStatus
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 响应体
return new ByteArrayInputStream("用户微服务不可用,请稍后再试。".getBytes());
}
@Override
public HttpHeaders getHeaders() {
// headers设定
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}
之后,当microservice-provider-user微服务无法正常响应时,将返回——“用户微服务不可用,请稍后再试”
10、Zuul的高可用
启动多个Zuul的网关服务,均注册到EurekaServer中即可。然后前置nginx将请求分发到各个网关即可!
11、使用Sidecar整合非JVM微服务
12、使用zuul聚合微服务
如果客户端需要访问多个微服务,如果通过网关分别调用、分别返回的话,网络开销、耗时、流量消耗都会很不好。这时我们可以在Zuul整合客户端的请求,客户端只给Zuul网关发送一个请求,网关中分别请求各个微服务,然后给客户端返回一个最终结果!
12.1、启动类代码
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
12.2、Service层代码
@Service
public class AggregationService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "fallback")
public Observable<User> getUserById(Long id) {
// 创建一个被观察者
return Observable.create(observer -> {
// 请求用户微服务的/{id}端点
User user = restTemplate.getForObject("http://microservice-provider-user/{id}", User.class, id);
observer.onNext(user);
observer.onCompleted();
});
}
@HystrixCommand(fallbackMethod = "fallback")
public Observable<User> getMovieUserByUserId(Long id) {
return Observable.create(observer -> {
// 请求电影微服务的/user/{id}端点
User movieUser = restTemplate.getForObject("http://microservice-consumer-movie/user/{id}", User.class, id);
observer.onNext(movieUser);
observer.onCompleted();
});
}
public User fallback(Long id) {
User user = new User();
user.setId(-1L);
return user;
}
}
12.3、Controller层代码
@RestController
public class AggregationController {
public static final Logger LOGGER = LoggerFactory.getLogger(ZuulApplication.class);
@Autowired
private AggregationService aggregationService;
@GetMapping("/aggregate/{id}")
public DeferredResult<HashMap<String, User>> aggregate(@PathVariable Long id) {
Observable<HashMap<String, User>> result = this.aggregateObservable(id);
return this.toDeferredResult(result);
}
public Observable<HashMap<String, User>> aggregateObservable(Long id) {
// 合并两个或者多个Observables发射出的数据项,根据指定的函数变换它们
return Observable.zip(
this.aggregationService.getUserById(id),
this.aggregationService.getMovieUserByUserId(id),
(user, movieUser) -> {
HashMap<String, User> map = Maps.newHashMap();
map.put("user", user);
map.put("movieUser", movieUser);
return map;
}
);
}
public DeferredResult<HashMap<String, User>> toDeferredResult(Observable<HashMap<String, User>> details) {
DeferredResult<HashMap<String, User>> result = new DeferredResult<>();
// 订阅
details.subscribe(new Observer<HashMap<String, User>>() {
@Override
public void onCompleted() {
LOGGER.info("完成...");
}
@Override
public void onError(Throwable throwable) {
LOGGER.error("发生错误...", throwable);
}
@Override
public void onNext(HashMap<String, User> movieDetails) {
result.setResult(movieDetails);
}
});
return result;
}
}