SpringCloud组件之Zuul,单独使用,consul,fegin等配置

SpringCloud组件之Zuul
  项目地址: https://gitee.com/yaiys/spring-cloud,还在完善中。

限流的分类如下所示:

  1. 合法性验证限流:比如验证码、IP 黑名单等,这些手段可以有效的防止恶意攻击和爬虫采集;

  2. 容器限流:比如 Tomcat、Nginx 等限流手段,其中 Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待执行;而 Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数;

  3. 服务端限流:比如我们在服务器端通过限流算法实现限流,此项也是我们本文介绍的重点。

合法性验证限流为最常规的业务代码,就是普通的验证码和 IP 黑名单系统

容器限流

Tomcat 限流

Tomcat 8.5 版本的最大线程数在 conf/server.xml 配置中,如下所示:

 
  1. <Connector port="8080" protocol="HTTP/1.1"

  2.           connectionTimeout="20000"

  3.           maxThreads="150"

  4.           redirectPort="8443" />

其中 maxThreads 就是 Tomcat 的最大线程数,当请求的并发大于此值(maxThreads)时,请求就会排队执行,这样就完成了限流的目的。

Nginx 限流

Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数。

控制速率

我们需要使用 limit_req_zone 用来限制单位时间内的请求数,即速率限制,示例配置如下:

 
  1. limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;

  2. server { 

  3.     location / { 

  4.         limit_req zone=mylimit;

  5.     }

  6. }

以上配置表示,限制每个 IP 访问的速度为 2r/s,因为 Nginx 的限流统计是基于毫秒的,我们设置的速度是 2r/s,转换一下就是 500ms 内单个 IP 只允许通过 1 个请求,从 501ms 开始才允许通过第 2 个请求。

控制并发数

利用 limit_conn_zone 和 limit_conn 两个指令即可控制并发数,示例配置如下:

  1. limit_conn_zone $binary_remote_addr zone=perip:10m;

  2. limit_conn_zone $server_name zone=perserver:10m;

  3. server {

  4.     ...

  5.     limit_conn perip 10;

  6.     limit_conn perserver 100;

  7. }

其中 limit_conn perip 10 表示限制单个 IP 同时最多能持有 10 个连接;limit_conn perserver 100 表示 server 同时能处理并发连接的总数为 100 个。

  • Zuul,顾名思义,服务网关。可以和Eureka、consul、Ribbon、Hystrix等组件配合使用,Zuul的主要功能是路由转发和过滤器。可以独立使用,也可以搭配consul,fegin等使用。

  • 单节点单独使用

MVN引入

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
集成consul
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
集成feigin
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置文件application.yml

server:
  port: 8080
spring:
  application:
    name: zuul
zuul:
  routes:
    # 自定义转发规则
    domo:
      # 匹配的路由规则,即所有demo/**的请求都会被转发到url: http://192.168.50.33为服务器上。可以是任何请求。
      path: /demo/**
      url: http://192.168.50.33
  # 过滤路由,test开头的任意请求不转发
  ignored-patterns: /test/**
  • 单节点搭配hystrix单独使用,对于小网站,防止恶意攻击,以及限制刷注册等有一定作用,配置文件如下
     

    server:
      port: 8080
    spring:
      application:
        name: zuul
      redis:
        host: 192.168.50.129
        password:
    # 配置eureka地址
    zuul:
      routes:
        # 这里可以自定义
        demo2:
          # 匹配的路由规则
          path: /**/**
          # 路由的目标地址
          url: demo
      # 过滤路由,test开头的任意请求
      ignored-patterns: /test/**
    # 如果不使用eureka的话,需要自己定义路由的那个服务的其他负载服务
    ribbon:
      eureka:
        enabled: false
    demo:
      ribbon:
        # 这里写你要路由的demo服务的所有负载服务请求地址,
        listOfServers: http://192.168.50.33:5210,http://192.168.50.13:5210
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 5000
    
      # 配置限流
      ratelimit:
        enabled: true
        # 对应存储类型(用来统计存储统计信息)
        repository: redis
        # 配置路由的策略
        policy-list:
          demo:
            # 每秒允许多少个请求
            - limit: 500
              # 刷新时间(单位秒)
              refresh-interval: 1
              # 根据URL统计
              type:
                - URL   #url类型的限流就是通过请求路径区分,origin是通过客户端IP地址区分,user是通过登录用户名进行区分,也包括匿名用户
    
    
  1. RateLimit:限流
     

    RateLimit:限流
    what:什么是限流
    顾名思义限制流量

    why:为什么我们的服务需要限流

    用户量病毒增长

    微博热搜/淘宝双十一

    竞品爬虫

    恶意攻击

    how:如何限流
    一般可以根据服务的某项核心指标,如QPS,来决定是否将后续的请求拦截。比如设定某系统1s的QPS阈值为100,当1s内的QPS达到了110,那么差值的10个请求则会被拦截,直接返回503状态码:服务器繁忙。
    根据以上结果导向论,又衍生出了如下3套算法:

    (1)计数器
    1.思想
    假设服务设定的最高QPS为100,声明一个计数器counter,在接下来的1s内,每有一个请求则counter+1,如果在这1s内,counter>100,则限流,1s结束后,counter重置清零。
    2.缺点
    如果在0.59s时QPS达到了100,在1.00s时QPS也达到了100,那么其实在1s内,QPS达到了200,限流GG!

    (2)漏桶算法
    1.基本思想
    想象有一个木桶,以恒定的速度漏水(处理请求),有新的请求来,可以放进桶里,如果桶满了,则直接拒绝请求。
    2.优点
    可以平滑收到的请求,以恒定的速度处理。
    缺点
    请求的处理(漏水)有一定的延时性

    (3)令牌桶算法
    1.基本思想
    想象有一个木桶,按一定速率往桶里放令牌,满了令牌则溢出舍弃。每来一个请求则取一个令牌,桶内无令牌可取则拒绝请求
    2.优点
    完美解决了计数器存在的临界问题,同时突增的QPS只要桶内有令牌就可以访问。

    关键代码
    1.注解 Limit.java

    @Inherited
    @Documented
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Limit {

        //资源的名字
        String name() default "";

        //资源的key
        String key() default "";

        //Key的prefix
        String prefix() default "";

        //给定的时间段 单位秒
        int period() default 1;

        //最多的访问限制次数
        int count();

        //类型
        LimitType limitType() default LimitType.CUSTOMER;
    }

    2.枚举LimitType.java

    public enum LimitType {
        /**
         * 自定义key
         */
        CUSTOMER,
        /**
         * 根据请求者IP
         */
        IP;
    }
    3.切面LimitAspect.java

    @Aspect
    @Slf4j
    public class LimitAspect {

        @Resource(name = "limitRedisTemplate")
        private RedisTemplate<String, Serializable> limitRedisTemplate;

        @Pointcut("@annotation(com.workbei.ratelimit.spring.boot.limit.Limit)")
        public void limitAnnotationPointcut() {
        }

        @Around("limitAnnotationPointcut()")
        public Object interceptor(ProceedingJoinPoint pjp) {
            //获得方法上的注解
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();
            Limit limitAnnotation = method.getAnnotation(Limit.class);
            LimitType limitType = limitAnnotation.limitType();
            String name = limitAnnotation.name();
            String key;
            int limitPeriod = limitAnnotation.period();
            int limitCount = limitAnnotation.count();
            switch (limitType) {
                case IP:
                    key = IPUtils.getIpAddr();
                    break;
                case CUSTOMER:
                    key = limitAnnotation.key();
                    break;
                default:
                    key = StringUtils.upperCase(method.getName());
            }
            ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
            //加载原子性lua脚本
            String luaScript = buildLuaScript();
            RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
            //执行脚本
            Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
            log.info("Access try count is {} for name={} and key = {}", count, name, key);
            try {
                if (count != null && count.intValue() <= limitCount) {//无需限流:通过
                    return pjp.proceed();
                } else {
                    throw new RateLimitException("rate limit ing");
                }
            } catch (Throwable e) {
                throw new RateLimitException(e.getMessage());
            }
        }

        /**
         * 限流 脚本
         *
         * @return lua脚本
         */
        private String buildLuaScript() {
            return ScriptUtils.loader("limit.lua");
        }
    }

    4.limit.lua(redis命令脚本文件)

    local c
    c = redis.call('get',KEYS[1])
    -- 调用不超过最大值,则直接返回
    if c and tonumber(c) > tonumber(ARGV[1]) then
    return c;
    end
    -- 执行计算器自加
    c = redis.call('incr',KEYS[1])
    if tonumber(c) == 1 then
    -- 从第一次调用开始限流,设置对应键值的过期
    redis.call('expire',KEYS[1],ARGV[2])
    end
    return c;
    ————————————————

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值