SpringCloud学习之四:服务网关之Zuul
服务网关之Zuul
Spring Cloud版本:Hoxton.SR5
1. 简介
之前的文章已经搭建服务注册中心、服务提供者、服务消费者以及Turbine聚合监控。随着微服务越来越大,服务调用也将越来越麻烦。因为一个正常的业务处理可能涉及到多个服务调用,那么客户端就需要多次调用不同的微服务。这样就存在如下问题:
- 客户端需要多次调用http请求。多次创建/销毁http连接,效率低
- 每个微服务都需要实现一套认证功能。若认证方式不同,客户端的逻辑就更加复杂
- 跨域问题。每个微服务都需要进行跨域处理
- 客户端可能需要维护多个ip、端口。若服务进行迁移,客户端改动大
所以可以通过增加一个服务网关解决这些问题。所有的外部请求都先通过服务网关,在网关中进行统一的认证,认证通过后转发到相应的微服务中。这样客户端就仅与服务网关进行交互,隐藏了各个微服务。
2. Zuul网关
2.1 实现
-
创建一个Spring Boot项目,引入如下依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
-
在启动类上添加
@EnableZuulProxy
注解,声明一个Zuul代理。@EnableZuulProxy @SpringBootApplication public class SclZuulApplication { public static void main(String[] args) { SpringApplication.run(SclZuulApplication.class, args); } }
-
编写配置文件application.yml
server: port: 8500 spring: application: name: scl-zuul # 服务名称 eureka: client: service-url: defaultZone: http://root:123456@test1:8100/eureka-server1/eureka,http://root:123456@test2:8200/eureka-server2/eureka,http://root:123456@test3:8300/eureka-server3/eureka # 服务注册中心地址 management: endpoint: health: show-details: always # 展示详细的健康检查信息 endpoints: web: exposure: include: info,health,routes,hystrix.stream # 暴露actuator端点
-
启动项目,请求url http://localhost:8500/微服务名称/url,zuul会自动转发到对应的微服务上。
2.2 路由配置详解
-
自定义指定微服务的访问路径
zuul.routes.微服务名称 = 指定路径
zuul: routes: scl-eureka-client-consumer: /secc/**
此时访问 http://localhost:8500/scl-eureka-client-consumer/consumer/info 和 http://localhost:8500/secc/consumer/info 的结果一致。都会转发到scl-eureka-client-consumer对应的微服务上。
-
忽略指定微服务
zuul.ignored-services.需要忽略的微服务名称
zuul: ignored-services: scl-eureka-client-provider
此时访问 http://localhost:8500/scl-eureka-client-provider/consumer/info将无法进行转发,返回404
-
忽略所有微服务,只路由指定微服务
zuul: ignored-services: '*' # 使用'*'可忽略所有微服务 routes: scl-eureka-client-consumer: /secc/**
-
同时指定path和URL
zuul: routes: # user-route为路由名称,可以任意起名 user-route: url: http://localhost:8090 # 指定的url path: /user/** # url对应的路径
此时访问 http://localhost:8500/user/** 将会转发到 http://localhost:8090/**
这种方式配置的路由不会作为HystrixCommand执行,也无法使用Ribbon进行负载均衡多个URL。
-
同时指定path和URL,并且不破坏Zuul的Hystrix和Ribbon特性
zuul: routes: # user-route为路由名称,可以任意起名 user-route: path: /user/** # url对应的路径 service-id: 'a' ribbon: eureka: enabled: false # 为Ribbon禁用Eureka a: ribbon: listOfServers: localhost:8080,localhost:8090
- 若指定的service-id已存在服务注册中心里,则后续的不需要配置,结果与示例1相同
- 若指定的service-id不存在服务注册中心里,则后续的必须配置
2.3 Zuul过滤器
Zuul中定义了4中标准过滤器
- PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证等
- ROUTING:这种过滤器建将请求路由到微服务。这种过滤器用于构建发送给微服务的请求
- POST:这种过滤器在路由到微服务之后执行。可为响应添加Header、收集统计信息和指标等
- ERROR:在其他阶段发生错误时执行该过滤器
本文可实现一个简单的Zuul过滤器
-
实现一个自定义的Zuul过滤器
public class PreRequestFilter extends ZuulFilter { private static final Logger log = LoggerFactory.getLogger(PreRequestFilter.class); @Override public String filterType() { // 过滤器类型,可返回pre、routing、post、error return "pre"; } @Override public int filterOrder() { // 过滤器的执行顺序 return 1; } @Override public boolean shouldFilter() { // 可通过一些业务逻辑判断是否要执行该过滤器。true执行,false不执行 log.info("该请求将经过过滤器..."); return true; } @Override public Object run() throws ZuulException { // 过滤器的具体逻辑 log.info("进行身份认证等处理..."); RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info("该请求信息为: " + request.getMethod() + " " + request.getRequestURL()); return null; } }
-
在启动类中添加如下内容
@Bean public PreRequestFilter preRequestFilter() { return new PreRequestFilter(); }
-
启动项目,访问http://localhost:8500/微服务名称/url,会在控制台看见如下输出内容。表明自定义的Zuul过滤器被执行了。
2.4 为Zuul添加回退
-
实现一个Zuul的回退类
@Component public class ProviderFallback implements FallbackProvider { @Override public String getRoute() { // 指定为哪个微服务提供回退 return "scl-eureka-client-provider"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { // 回退的状态码 return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { // 数字类型的状态码 return this.getStatusCode().value(); } @Override public String getStatusText() throws IOException { // 状态文本 return this.getStatusCode().getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { // 响应体 return new ByteArrayInputStream("用户微服务不可用,请稍后再试。".getBytes()); } @Override public HttpHeaders getHeaders() { // 响应头 HttpHeaders headers = new HttpHeaders(); MediaType mt = new MediaType("application", "json", StandardCharsets.UTF_8); headers.setContentType(mt); return headers; } }; } }
-
启动项目,并停止scl-eureka-client-provider对应的服务。然后访问http://localhost:8500/scl-eureka-client-consumer/consumer/info
2.5 使用Zuul聚合微服务
客户端的一个操作可能需要请求多次微服务,如果只是通过Zuul进行转发,那么网络开销、耗费时长依然会很长。所以我们可以使用Zuul聚合微服务请求。做到客户端只发送一个请求给Zuul,由Zuul请求其他的微服务,将数据组织好后返回。
本文利用RxJava结合Zuul实现微服务的聚合请求。
-
需要利用OpenFeign请求其他的微服务,因此需要引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
创建CustomerFeign接口,访问服务消费者接口
@FeignClient(name = "scl-eureka-client-consumer", fallbackFactory = ConsumerFeignFallback.class) public interface ConsumerFeign { @GetMapping("/consumer/info") Map<String, String> get(); } @Component class ConsumerFeignFallback implements FallbackFactory<ConsumerFeign> { @Override public ConsumerFeign create(Throwable throwable) { return () -> { Map<String, String> map = new HashMap<>(); map.put("errcode", "500"); map.put("errmsg", "Consumer异常,请稍后重试"); return map; }; } }
-
创建ProviderFeign接口,访问服务提供者接口
@FeignClient(name = "scl-eureka-client-provider", fallbackFactory = ProviderFeignFallback.class) public interface ProviderFeign { @GetMapping("/provider/info") Map<String, String> get(); } @Component class ProviderFeignFallback implements FallbackFactory<ProviderFeign> { @Override public ProviderFeign create(Throwable throwable) { return () -> { Map<String, String> map = new HashMap<>(); map.put("errcode", "500"); map.put("errmsg", "Provider异常,请稍后重试"); return map; }; } }
-
在启动类中添加
@EnableHystrix
和@EnableFeignClients
注解,支持Feign请求调用及回退@EnableHystrix @EnableZuulProxy @EnableFeignClients @SpringBootApplication public class SclZuulApplication { public static void main(String[] args) { SpringApplication.run(SclZuulApplication.class, args); } @Bean public PreRequestFilter preRequestFilter() { return new PreRequestFilter(); } }
-
修改配置文件application.yml,添加如下配置,使Feign支持Hystrix进行熔断回退
feign: hystrix: enabled: true
-
实现一个聚合服务类
@Service public class AggregationService { private final ConsumerFeign consumerFeign; private final ProviderFeign providerFeign; @Autowired public AggregationService(ConsumerFeign consumerFeign, ProviderFeign providerFeign) { this.consumerFeign = consumerFeign; this.providerFeign = providerFeign; } public Observable<Map> getByConsumer() { return Observable.create(observer -> { Map<String, String> map = this.consumerFeign.get(); observer.onNext(map); observer.onCompleted(); }); } public Observable<Map> getByProvider() { return Observable.create(observer -> { Map<String, String> map = this.providerFeign.get(); observer.onNext(map); observer.onCompleted(); }); } }
-
创建一个Controller,聚合多个请求。
@RestController @RequestMapping("/aggregate") public class AggregationController { private static final Logger log = LoggerFactory.getLogger(AggregationController.class); private final AggregationService aggregationService; @Autowired public AggregationController(AggregationService aggregationService) { this.aggregationService = aggregationService; } @GetMapping public DeferredResult<Map> aggregate() { Observable<Map> result = this.aggregateObservable(); return this.toDeferredResult(result); } private DeferredResult<Map> toDeferredResult(Observable<Map> result) { DeferredResult<Map> deferredResult = new DeferredResult<>(); result.subscribe(new Observer<Map>() { @Override public void onCompleted() { log.info("执行完成..."); } @Override public void onError(Throwable e) { log.info("执行失败:" + e.toString()); } @Override public void onNext(Map map) { log.info("onNext..."); deferredResult.setResult(map); } }); return deferredResult; } private Observable<Map> aggregateObservable() { return Observable.zip( this.aggregationService.getByConsumer(), this.aggregationService.getByProvider(), (consumer, provider) -> { Map<String, Map<String, String>> map = new HashMap<>(); map.put("consumer", consumer); map.put("provider", provider); return map; } ); } }
-
启动项目,访问http://localhost:8500/aggregate
-
停止服务提供者,再次访问http://localhost:8500/aggregate
因为服务提供者停止,所以provider对应的数据在Zuul中进行熔断,返回Zuul中回退数据;服务消费者正常请求,在服务消费者请求服务提供者时熔断,返回服务消费者中回退数据。
-
由上述可知,服务聚合与Hystrix容错都正常
2.6 Turbine监控
之前已经利用Turbine+RabbitMQ+dashboard实现了聚合监控,因为也可将Zuul纳入监控中。只需将Zuul将Hystrix上报到RabbitMQ中接口。
-
添加
spring-cloud-netflix-hystrix-stream
和spring-cloud-starter-stream-rabbit
依赖。完整依赖如下<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-hystrix-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
-
在配置文件中添加RabbitMQ的连接信息。完整配置如下
server: port: 8500 spring: application: name: scl-zuul # 服务名称 rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://root:123456@test1:8100/eureka-server1/eureka,http://root:123456@test2:8200/eureka-server2/eureka,http://root:123456@test3:8300/eureka-server3/eureka # 服务注册中心地址 management: endpoint: health: show-details: always endpoints: web: exposure: include: info,health,routes,hystrix.stream zuul: routes: scl-eureka-client-consumer: /secc/** feign: hystrix: enabled: true
-
重启项目,访问http://localhost:8401/hystrix,在输入框输入Turbine的监控端口地址http://localhost:8401,然后点击Monitor Stream按钮,即可看到监控数据