5、Zuul使用和源码分析

Zuul使用和源码分析

1丶网关介绍
  • 统一入口:为全部微服务提供唯一入口点,网关起到外部和内部隔离,保障了后台服务的安全
    性。
  • 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
  • 动态路由:动态的将请求路由到不同的后端集群中。
  • 减少客户端与服务的耦合,服务可以独立发展。通过网关层来做映射。

image-20230612224302288

Zuul介绍

zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。

Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和
Netflix 流应用的 Web 网站后端所有请求的前门。

2丶 基础环境搭建
  • 创建工程Zuul

  • 引入logback.xml,进行配置

    server:
      port: 8888
    logging:
      config: classpath:logback.xml
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka
    
  • 引入依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
        </dependencies>
    
  • 开启zuulimage-20230612224619423

  • 引入msb-order配置image-20230612224703244

  • 启动服务访问zuulimage-20230612224729217

  • 请求路由

    Zuul可以通过配置完成请求路由配置

    Zuul服务路由默认支持serviceld作为上下文

    ignored-services可以禁用serviceld

    image-20230612224821360image-20230612224831005

3丶Zuul请求表达式详解

请求路由表达式

  • ? : 匹配任意单个字符
  • *:配置任意数量的字符
  • ** :配置任意数量的字符,支持多级目录
4丶Zuul架构介绍

image-20230612235058654image-20230612225015805

讲解调用流程

  • PRE Filters(前置过滤器) :当请求会路由转发到具体后端服务器前执行的过滤器,比如鉴权过滤
    器,日志过滤器,还有路由选择过滤器
  • ROUTING Filters (路由过滤器):该过滤器作用是把请求具体转发到后端服务器上,一般是
    通过Apache HttpClient 或者 Netflix Ribbon把请求发送到具体的后端服务器上
  • POST Filters(后置过滤器):当把请求路由到具体后端服务器后执行的过滤器;场景有添加标
    准http 响应头,收集一些统计数据(比如请求耗时等),写入请求结果到请求方等。
  • ERROR Filters(错误过滤器):当上面任何一个类型过滤器执行出错时候执行该过滤器
5丶自定义Filter
  • 继承ZuulFilter并实现相应的方法
  • 设置Filter类型、级别和是否启用
  • 开发具体的业务逻辑

编写Filter

package com.msb.zuul.filter;

import com.netflix.zuul.ZuulFilter;


import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

@Slf4j
public class MyFilter extends ZuulFilter {
    /**
     *filter类型
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * filter执行顺序,越小优先级越高
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 是否启动:这给定制化编程提供了方便,比如我给不同公司提供不同方案,有的公司需要使用这个
     * 过滤器,有的不需要
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return false;
    }

    /**
     * Filter里面具体执行业务逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //
        RequestContext requestContext = RequestContext.getCurrentContext();


        HttpServletRequest request = requestContext.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String headName = headerNames.nextElement();
            log.info("headName:{}, headValue:{}", headName, request.getHeader(headName));
        }

        return null;
    }
}

配置Filter

@Configuration
public class ZuulConfig {
    @Bean
    public MyFilter initMyFilter(){
        return new MyFilter();
    }
}
6丶限制ip黑名单的频繁请求
  • redis依赖引入

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 引入配置

    spring:
      redis:
        database: 0
        host: localhost
        port: 6379
    # ip请求限制的参数配置
    blackIp:
      continueCounts: ${counts:10}    # ip连续请求的次数
      timeInterval: ${interval:10}    # ip判断的事件间隔,单位:秒
      limitTimes: ${times:15}         # 限制的事件,单位:秒
    
  • 代码

    package com.msb.zuul.util;
    
    import javax.servlet.http.HttpServletRequest;
    
    public class IPUtil {
    
        /**
         * 获取请求的IP:
         * 用户的真实IP不能用reqeust.getRemoteAddr()
         * 这是因为可能会使用一些代理软件,这样IP获取就不准确了,除此之外
         * 如果使用了多级反向代理的话,ip需要从X-Forwarded-For中获取一个非unknow的ip才是有效的IP
         * @param request
         * @return
         */
        public static String getRequestIP(HttpServletRequest request){
            String ip = request.getHeader("x-forwarded-for");
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("Proxy-Client-IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("HTTP_CLIENT—IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getRemoteAddr();
            }
            return ip;
        }
    }
    
    
    package com.msb.zuul.util;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    import java.util.concurrent.TimeUnit;
    
    @Component
    public class RedisOperator {
    
        @Autowired
        private StringRedisTemplate  redisTemplate;
    
        /**
         * 获取剩余时间
         * @param ipRedisLimitKey
         * @return
         */
        public  long ttl(String ipRedisLimitKey) {
            return  redisTemplate.getExpire(ipRedisLimitKey);
        }
    
        /**
         * 实现命令 increment key 增加key delta次
         * @param ipRedisKey
         * @param delta
         * @return
         */
        public long increment(String ipRedisKey, int delta) {
            return redisTemplate.opsForValue().increment(ipRedisKey,delta);
        }
    
        /**
         * 实现命令expire 设置过期时间 单位秒
         * @param key
         * @param timeout
         */
        public void expire(String key, Integer timeout) {
            redisTemplate.expire(key,timeout, TimeUnit.SECONDS);
        }
    
        /**
         * 对key设置超时时间
         * @param key
         * @param value
         * @param timeout
         */
        public void set(String key, String value, Integer timeout) {
    
            redisTemplate.opsForValue().set(key,value,timeout,TimeUnit.SECONDS);
        }
    }
    
    @Component
    @RefreshScope
    public class BlackIPFilter extends ZuulFilter {
    
        @Value("${blackIp.continueCounts}")
        public Integer continueCounts;
        @Value("${blackIp.timeInterval}")
        public Integer timeInterval;
        @Value("${blackIp.limitTimes}")
        public Integer limitTimes;
    
        @Autowired
        private RedisOperator redis;
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 2;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            System.out.println("执行【ip黑名单】过滤器...");
    
            System.out.println("continueCounts: " + continueCounts);
            System.out.println("timeInterval: " + timeInterval);
            System.out.println("limitTimes: " + limitTimes);
    
            // 获得上下文对象
            RequestContext context = RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
    
        // 获得ip
        String ip = IPUtil.getRequestIp(request);
    
        /**
         * 需求:
         *  判断ip在10秒内的请求次数是否超过10次
         *  如果超过,则限制这个ip访问15秒,15秒以后再放行
         */
    
        final String ipRedisKey = "zuul-ip:" + ip;
        final String ipRedisLimitKey = "zuul-ip-limit:" + ip;
    
        // 获得当前ip这个key的剩余时间
        long limitLeftTime = redis.ttl(ipRedisLimitKey);
        // 如果当前限制ip的key还存在剩余时间,说明这个ip不能访问,继续等待
        if (limitLeftTime > 0) {
            stopRequest(context);
            return null;
        }
    
        // 在redis中累加ip的请求访问次数
        long requestCounts = redis.increment(ipRedisKey, 1);
        // 从0开始计算请求次数,初期访问为1,则设置过期时间,也就是连续请求的间隔时间
        if (requestCounts == 1) {
            redis.expire(ipRedisKey, timeInterval);
        }
    
        // 如果还能取得请求次数,说明用户连续请求的次数落在10秒内
        // 一旦请求次数超过了连续访问的次数,则需要限制这个ip的访问
        if (requestCounts > continueCounts) {
            // 限制ip的访问时间
            redis.set(ipRedisLimitKey, ipRedisLimitKey, limitTimes);
            stopRequest(context);
        }
    
        return null;
    }
    
    private void stopRequest(RequestContext context) {
        // 停止zuul继续向下路由,禁止请求通信
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(200);
        Map result = new HashMap<>();
        result.put("message","数据进行限流");
        String resultStr = JSONObject.toJSONString(result);
        context.setResponseBody(resultStr);
    
    
        context.getResponse().setCharacterEncoding("utf-8");
        context.getResponse().setContentType(MediaType.APPLICATION_JSON_VALUE);
    }
    

    }

    
    
7丶过滤器代码重构
/**
 * 通用的抽象过滤器类
 */
public abstract class AbstractZuulFilter extends ZuulFilter {

    // 用于在过滤器之间传递消息,数据保存在每个请求的ThreadLocal中
    // 扩展Map
    RequestContext context;

    private final static String NEXT = "next";

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        return (boolean)ctx.getOrDefault(NEXT,true);
    }

    @Override
    public Object run() throws ZuulException {
        context = RequestContext.getCurrentContext();
        return cRun();
    }

    protected abstract Object cRun();

    Object fail(int code ,String msg){
        context.set(NEXT, false);
        context.setSendZuulResponse(false);
        context.getResponse().setContentType("text/html;charset=UTF-8");
        context.setResponseStatusCode(code);
        context.setResponseBody(String.format("{\"result\": \"%s!\"}", msg));

        return null;
    }
    Object success() {

        context.set(NEXT, true);


        return null;
    }
}

/**
 * 前置路由过滤器
 */
public abstract class AbstractPreZuulFilter extends AbstractZuulFilter{
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }
}

/**
 * 后置路由过滤器
 */
public abstract class AbstractPostZuulFilter extends AbstractZuulFilter{
    @Override
    public String filterType() {
        return FilterConstants.POST_TYPE;
    }
}
8丶增加日志过滤器
@Slf4j
@Component
public class PreRequestFilter extends AbstractPreZuulFilter {

 @Override
 protected Object cRun() {

     context.set("startTime", System.currentTimeMillis());

     return success();
 }
 @Override
 public int filterOrder() {
     return 0;
 }


}

@Component
public class AccessLogFilter extends AbstractPostZuulFilter {

 @Override
 protected Object cRun() {

     HttpServletRequest request = context.getRequest();

     // 从 PreRequestFilter 中获取设置的请求时间戳
     Long startTime = (Long) context.get("startTime");
     String uri = request.getRequestURI();
     long duration = System.currentTimeMillis() - startTime;

     // 从网关通过的请求都会打印日志记录: uri + duration
     log.info("uri: {}, duration: {}", uri, duration);

     return success();
 }

 @Override
 public int filterOrder() {
     return 10;
 }
}
9丶Zuul整合Hystrix
  • 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    
  • 进行Hystrix属性配置

    hystrix:
      command:
        default:
          execution:
            timeout:
              enabled: true
            isolation:
              thread:
                timeoutInMilliseconds: 10
    
  • 进行访问image-20230612230134975

  • 报错超时image-20230612230156366

  • 降级设置

    @Component
    public class MyFallback implements FallbackProvider {
        /**
         * 针对那一个路由进行 降级,return可以写 *
         * @return
         */
        @Override
        public String getRoute() {
            return "msb-order";
        }
    
        /**
         * 降级的处理方式
         * @return
         */
        @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 200;
                }
    
                @Override
                public String getStatusText() throws IOException {
                    return "ok";
                }
    
                @Override
                public void close() {
    
                }
                /**
                 * 业务降级处理方式
                 * @return
                 */
                @Override
                public InputStream getBody() throws IOException {
    
                    Map result = new HashMap<>();
                    result.put(200,"服务降级");
                    String resultJson = JSONObject.toJSONString(result);
                    return new ByteArrayInputStream(resultJson.getBytes());
                }
    
                /**
                 * 设置head信息
                 * @return
                 */
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);
                    return headers;
                }
            };
        }
    }
    
  • 引入对应的依赖

    <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.60</version>
    </dependency>
    

    image-20230612230431658

10丶Cookie和头信息处理
  • Zuul帮助我们过滤了一些非安全的信息
  • 诸如cookie、set-Cookie和authorization等
  • 可以通过设置sensitiveHeaders来修改

msb-order项目中打印一下对应头信息image-20230612230600349

image-20230612230619461image-20230612230634653

我们可以发现msb-zuul的日志里面是有数据的image-20230612230706410

msb-order里面也有对应的数据image-20230612230725290

我们配置sensitive-headers或者ignored-servicesimage-20230612230758077

11丶源码解析image-20230612230845125
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值