• 客户端维护大量的ip和port信息,直接访问指定服务
  • 认证和授权操作,需要在每一个模块中都添加认证和授权的操作
  • 项目的迭代,服务要拆分,服务要合并,需要客户端进行大量的变化
  • 统一的把安全性校验都放在Zuul中

image.png

1 Zuul的快速入门

创建Maven项目,修改为SpringBoot
导入依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  8. </dependency>

添加一个注解

  1. @EnableEurekaClient
  2. @EnableZuulProxy

编写配置文件

  1. # 指定Eureka服务地址
  2. eureka:
  3. client:
  4. service-url:
  5. defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
  6. #指定服务的名称
  7. spring:
  8. application:
  9. name: ZUUL
  10. server:
  11. port: 80

2 Zuul的监控界面

导入依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

访问地址:
http://localhost/actuator/routeshttp://ip:port/actuator/routes)

编写配置文件

  1. # 查看zuul的监控界面(开发时,配置为*,上线,不要配置)
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: "*"

3 忽略服务配置
  1. # zuul的配置
  2. zuul:
  3. # 基于服务名忽略服务,无法查看 ,如果要忽略全部的服务 "*",默认配置的全部路径都会被忽略掉(自定义服务的配置,无法忽略的)
  4. ignored-services: eureka
  5. # 监控界面依然可以查看,在访问的时候,404
  6. ignored-patterns: /**/search/**

4 自定义服务配置
  1. # zuul的配置
  2. zuul:
  3. # 指定自定义服务(方式一 , key(服务名):value(路径))
  4. # routes:
  5. # search: /ss/**
  6. # customer: /cc/**
  7. # 指定自定义服务(方式二)
  8. routes:
  9. kehu: # 自定义名称
  10. path: /ccc/** # 映射的路径
  11. serviceId: customer # 服务名称

image.png

5 灰度发布

添加一个配置类

  1. @Bean
  2. public PatternServiceRouteMapper serviceRouteMapper() {
  3. return new PatternServiceRouteMapper(
  4. "(?<name>^.+)-(?<version>v.+$)",
  5. "${version}/${name}");
  6. }

准备一个服务,提供2个版本

  1. version: v1
  2. #指定服务的名称
  3. spring:
  4. application:
  5. name: CUSTOMER-${version}

-Dversion=v2 -Dserver.port=9099

修改Zuul的配置

  1. # zuul的配置
  2. zuul:
  3. # 基于服务名忽略服务,无法查看 , 如果需要用到-v的方式,一定要忽略掉
  4. # ignored-services: "*"

6 Zuul的过滤器执行流程

客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会吧请求再次转发给RoutingFilter,请求转发到一个指定的服务,在指定的服务响应一个结果之后,再次走一个PostFilter的过滤器链,最终再将响应信息交给客户端。

image.png

Zuul过滤器入门

创建POJO类,继承ZuulFilter抽象类

  1. @Component
  2. public class TestZuulFilter extends ZuulFilter {}

指定当前过滤器的类型

  1. @Override
  2. public String filterType() {
  3. return FilterConstants.PRE_TYPE;
  4. }

指定过滤器的执行顺序

  1. @Override
  2. public int filterOrder() {
  3. return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
  4. }

配置是否启用

  1. @Override
  2. public boolean shouldFilter() {
  3. // 开启当前过滤器
  4. return true;
  5. }

指定过滤器中的具体业务代码

  1. @Override
  2. public Object run() throws ZuulException {
  3. System.out.println("prefix过滤器执行~~~");
  4. return null;
  5. }

PreFilter实现token校验 (作业)

准备访问路径,请求参数传递token

http://localhost/v2/customer/version?token=123

创建AuthenticationFilter

  1. @Component
  2. public class AuthenticationFilter extends ZuulFilter {
  3. @Override
  4. public String filterType() {
  5. return FilterConstants.PRE_TYPE;
  6. }
  7. @Override
  8. public int filterOrder() {
  9. return PRE_DECORATION_FILTER_ORDER - 2;
  10. }
  11. @Override
  12. public boolean shouldFilter() {
  13. return true;
  14. }
  15. @Override
  16. public Object run() throws ZuulException {
  17. //..
  18. }
  19. }

在run方法中编写具体的业务逻辑代码

  1. @Override
  2. public Object run() throws ZuulException {
  3. //1. 获取Request对象
  4. RequestContext requestContext = RequestContext.getCurrentContext();
  5. HttpServletRequest request = requestContext.getRequest();
  6. //2. 获取token参数
  7. String token = request.getParameter("token");
  8. //3. 对比token
  9. if(token == null || !"123".equalsIgnoreCase(token)) {
  10. //4. token校验失败,直接响应数据
  11. requestContext.setSendZuulResponse(false);
  12. requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
  13. }
  14. return null;
  15. }

Zuul的降级

创建POJO类,实现接口FallbackProvider

  1. @Component
  2. public class ZuulFallBack implements FallbackProvider {}

重写两个方法

  1. @Override
  2. public String getRoute() {
  3. return "*"; // 代表指定全部出现问题的服务,都走这个降级方法
  4. }
  5. @Override
  6. public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
  7. System.out.println("降级的服务:" + route);
  8. cause.printStackTrace();
  9. return new ClientHttpResponse() {
  10. @Override
  11. public HttpStatus getStatusCode() throws IOException {
  12. // 指定具体的HttpStatus
  13. return HttpStatus.INTERNAL_SERVER_ERROR;
  14. }
  15. @Override
  16. public int getRawStatusCode() throws IOException {
  17. // 返回的状态码
  18. return HttpStatus.INTERNAL_SERVER_ERROR.value();
  19. }
  20. @Override
  21. public String getStatusText() throws IOException {
  22. // 指定错误信息
  23. return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
  24. }
  25. @Override
  26. public void close() {
  27. }
  28. @Override
  29. public InputStream getBody() throws IOException {
  30. // 给用户响应的信息
  31. String msg = "当前服务:" + route + "出现问题!!!";
  32. return new ByteArrayInputStream(msg.getBytes());
  33. }
  34. @Override
  35. public HttpHeaders getHeaders() {
  36. // 指定响应头信息
  37. HttpHeaders headers = new HttpHeaders();
  38. headers.setContentType(MediaType.APPLICATION_JSON);
  39. return headers;
  40. }
  41. };
  42. }

Zuul动态路由(作业)

创建一个过滤器

  1. // 执行顺序最好放在Pre过滤器的最后面

在run方法中编写业务逻辑

  1. @Override
  2. public Object run() throws ZuulException {
  3. //1. 获取Request对象
  4. RequestContext context = RequestContext.getCurrentContext();
  5. HttpServletRequest request = context.getRequest();
  6. //2. 获取参数,redisKey
  7. String redisKey = request.getParameter("redisKey");
  8. //3. 直接判断
  9. if(redisKey != null && redisKey.equalsIgnoreCase("customer")){
  10. // http://localhost:8080/customer
  11. context.put(FilterConstants.SERVICE_ID_KEY,"customer-v1");
  12. context.put(FilterConstants.REQUEST_URI_KEY,"/customer");
  13. }else if(redisKey != null && redisKey.equalsIgnoreCase("search")){
  14. // http://localhost:8081/search/1
  15. context.put(FilterConstants.SERVICE_ID_KEY,"search");
  16. context.put(FilterConstants.REQUEST_URI_KEY,"/search/1");
  17. }
  18. return null;
  19. }