SpringCloud(五)--Zuul

Zuul API网关

SpringCloud(五)--Zuul_第1张图片

zuul API 网关,为微服务应用提供统一的对外访问接口。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。

Zuul API 网关总结:

  • 统一调用入口
  • 统一的权限校验
  • 集成ribbon
  • 集成hystrix
统一调用入口
  1. zuul依赖
  2. @EnableZuulProxy
  3. yml 配置转发规则
# zuul的默认规则:serviceid 直接映射成一个子路径
# zull可以根据注册表的信息来自动配置这个默认规则
# 为了防止注册表信息不全,最好手动配置

zuul:
 routes:
 item-service: /item-service/**
 user-service: /user-service/**
 order-service: /order-service/**
统一的权限校验

使用 zuul 的过滤器实现权限验证

http://localhost:3001/item-service/y4y433 没有登录不允许访问
http://localhost:3001/item-service/y4y433?token=78oi6i544 有token认为已登录,可以访问

添加过滤器

  • 只需要添加一个zuul过滤器子类,添加 @Component 注解,没有任何其他配置,zuul可以自动配置
  • 继承 ZuulProxy 父类
  • 实现几个抽象方法
zuul 集成 ribbon(zuul 不推荐启用重试)

默认启用了 ribbon 的负载均衡,没有启用重试

配置启用重试:

  1. 添加 spring-retry 依赖
  2. zuul.retryable=true
  3. 重试参数,默认值:
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 1
    ReadTimeout: 1000
zuul 集成 hystrix(0配置已经启用了hystrix)

添加降级代码:

  1. 实现 FallbackProvider 接口,按接口要求添加降级代码
  2. 只需要加 @Component 注解,zuul会进行自动配置

暴露hystrix.stream 端点

  1. actuator依赖(zuul已包含)
  2. m.e.w.e.i=hystrix.stream
  3. 修改 10-turbine,聚合的服务添加 zuul 服务

新建 sp11-zuul 项目

SpringCloud(五)--Zuul_第2张图片

SpringCloud(五)--Zuul_第3张图片

pom.xml

  • 需要添加 sp01-commons 依赖


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.1.RELEASE
         
    
    cn.tedu
    sp11-zuul
    0.0.1-SNAPSHOT
    sp11-zuul
    Demo project for Spring Boot

    
        1.8
        Hoxton.RELEASE
    

    
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-zuul
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
        
            cn.tedu
            sp01-commons
            0.0.1-SNAPSHOT
        
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

application.yml

spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  routes:
    item-service: /item-service/**
    user-service: /user-service/**
    order-service: /order-service/**

主程序

添加 @EnableZuulProxy@EnableDiscoveryClient 注解

package cn.tedu.sp11;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class Sp11ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(Sp11ZuulApplication.class, args);
    }

}

启动服务,访问测试

SpringCloud(五)--Zuul_第4张图片

zuul + ribbon 负载均衡

zuul 已经集成了 ribbon,默认已经实现了负载均衡

zuul + ribbon 重试

pom.xml 添加 spring-retry 依赖

  • 需要 spring-retry 依赖

    org.springframework.retry
    spring-retry

配置 zuul 开启重试,并配置 ribbon 重试参数

  • 需要开启重试,默认不开启
spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  retryable: true

#  routes:
#    item-service: /item-service/**
#    user-service: /user-service/**
#    order-service: /order-service/**
    
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1

zuul + hystrix 降级

创建降级类
  • getRoute() 方法中指定应用此降级类的服务id,星号或null值可以通配所有服务

ItemFB

package cn.tedu.sp11.fallback;

import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

@Component
@Slf4j
public class ItemFB implements FallbackProvider {
    /*
    返回: service-id
    针对哪个服务进行降级

    返回:"*"或是null
    针对所有服务进行降级
    */
    @Override
    public String getRoute() {
        return "item-service";
    }


    //设置向客户端返回的降级响应
    //ClientHttpResponse中封装降级响应
    @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 HttpStatus.OK.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //{code:200,msg:"调用后台服务失败”,data:null}
                String json= JsonResult.ok().msg("调用后台商品服务失败").toString();
                return new ByteArrayInputStream(json.getBytes("UTF-8"));

            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.add("Content-Type", "application/json;charset=UTF-8");
                return headers;
            }
        };
    }
}

OrderFB

package cn.tedu.sp11.fallback;

import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

@Component
@Slf4j
public class OrderFB implements FallbackProvider {
    /*
    返回: service-id
    针对哪个服务进行降级

    返回:"*"或是null
    针对所有服务进行降级
    */
    @Override
    public String getRoute() {
        return "order-service";
    }

    /*
    设置向客户端返回的降级响应
     */
    @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 HttpStatus.OK.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                //{code:200,msg:"调用后台服务失败”,data:null}
                String json= JsonResult.ok().msg("调用后台订单服务失败").toString();
                return new ByteArrayInputStream(json.getBytes("UTF-8"));

            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.add("Content-Type", "application/json;charset=UTF-8");
                return headers;
            }
        };
    }
}

降低 hystrix 超时时间,以便测试降级

spring:
  application:
    name: zuul
    
server:
  port: 3001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka

zuul:
  retryable: true
    
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 2000
  MaxAutoRetriesNextServer: 1
  MaxAutoRetries: 1
  
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500

启动服务,测试降级

http://localhost:3001/item-service/35
SpringCloud(五)--Zuul_第5张图片

zuul + hystrix 数据监控

暴露 hystrix.stream 监控端点

  • zuul 已经包含 actuator 依赖
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

开启监控

启动 sp08-hystrix-dashboard,填入 zuul 的监控端点路径,开启监控

http://localhost:4001/hystrix

填入监控端点:
http://localhost:3001/actuator/hystrix.stream

SpringCloud(五)--Zuul_第6张图片
必须通过zuul网关访问后台服务才会长生监控数据

zuul + turbine 聚合监控

修改 turbine 项目,聚合 zuul 服务实例

 spring:
  application:
    name: turbin
    
server:
  port: 5001
  
eureka:
  client:
    service-url:
      defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
      
turbine:
  app-config: order-service, zuul
  cluster-name-expression: new String("default")

SpringCloud(五)--Zuul_第7张图片

熔断测试

ab -n 20000 -c 50 http://localhost:3001/order-service/123abc

SpringCloud(五)--Zuul_第8张图片

zuul 请求过滤

  1. 继承 ZuulProxy
  2. 添加 @Component

zuul 会对过滤器进行自动配置
SpringCloud(五)--Zuul_第9张图片

定义过滤器,继承 ZuulFilter

在 sp11-zuul 项目中新建过滤器类

package cn.tedu.sp11.filter;

import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
@Slf4j
public class AccessFilter extends ZuulFilter {
    // 设置zuul过滤器的类型:pre,routing,post,error
    @Override
    public String filterType() {
        //return "pre";

        return FilterConstants.PRE_TYPE;    //设置成前置过滤器
    }
    // 设置过滤器的顺序  该过滤器顺序要 > 5,才能得到 serviceid
    @Override
    public int filterOrder() {
        return 6;
    }

    /**
     对客户端的当前请求是否要执行过滤代码
     */
    @Override
    public boolean shouldFilter() {
        // 如果调用 item-service 要执行过滤代码判断权限
        // 否则不执行过滤代码

        //获取调用的服务id
        RequestContext ctx = RequestContext.getCurrentContext();//请求行下文对象
        String serviceId = (String)ctx.get(FilterConstants.SERVICE_ID_KEY);

        //判断是不是 item-service
        //对指定的serviceid过滤,如果要过滤所有服务,直接返回 true
        return "item-service".equalsIgnoreCase(serviceId);//equalsIgnoreCase忽略大小写来判断
    }

    //过滤代码
    @Override
    public Object run() throws ZuulException {
        // http://localhost:3001/item-service/35?token=1234
        //获取request对象
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        //接收token参数
        String token = request.getParameter("token");
        // 如果没有收到token参数,认为用户没有登录,阻止继续继续调用
        if(StringUtils.isBlank(token)) {    //commons.lang3.StringUtils
            //阻止他继续调用
            ctx.setSendZuulResponse(false); //不再向后台服务转发


            //设置向客户端发送的响应
            ctx.addZuulResponseHeader("Content-Type", "application/json");
            //JsonResult -{code:401,msg:"not login".data:null}
            ctx.setResponseBody(JsonResult.err().msg("not login").toString());
        }

        return null;    //在当前zuul版本中,这个返回值没有作用
    }
}

访问测试

zuul Cookie过滤

zuul 会过滤敏感 http 协议头,默认过滤以下协议头:

  • Cookie
  • Set-Cookie
  • Authorization

可以设置 zuul 不过滤这些协议头

zuul:
  sensitive-headers: 

Feign 和 Zuul

  • 向后台服务调用
  • 集成ribbon
  • 集成hystrix

什么时候使用 Feign 和 Zuul

  • 业务服务之间调用使用 Feign
  • Zuul 部署在最前面,作为系统入口

Feign 不推荐启用 Hystrix

  • 业务服务之间添加Hystrix会造成混乱,难以确定故障点
  • 应该在最前面添加 Hystrix 避免混乱

Zuul 不推荐启用 Ribbon 的重试

  • 在网关处重试会造成后台多个服务压力翻倍
  • 重试动作应尽量往后放

你可能感兴趣的:(springcloud)