SpringCloud系列——限流、熔断、降级

  前言

  分布式环境下,服务直接相互调用,一个复杂的业务可能要调用多个服务,例如A -> B -> C -> D,当某个服务出现异常(调用超时、调用失败等)将导致整个流程阻塞崩溃,严重的整个系统都会崩掉,为了实现高可用,必要的保护机制必不可少

  本文记录限流、熔断、降级的实现处理

 

  限流

  我们采用令牌桶限流法,并自己实现一个简单令牌桶限流

  有个任务线程以恒定速率向令牌桶添加令牌

  一个请求会消耗一个令牌,令牌桶里的令牌大于0,才会放行,反正不允许通过

/**
 * 简单的令牌桶限流
 */
public class RateLimiter {

    /**
     * 桶的大小
     */
    private Integer limit;

    /**
     * 桶当前的token
     */
    private static Integer tokens = 0;

    /**
     * 构造参数
     */
    public RateLimiter(Integer limit, Integer speed){
        //初始化桶的大小,且桶一开始是满的
        this.limit = limit;
        tokens = this.limit;

        //任务线程:每秒新增speed个令牌
        new Thread(() ->{
            while (true){
                try {
                    Thread.sleep(1000L);

                    int newTokens = tokens + speed;
                    if(newTokens > limit){
                        tokens = limit;
                        System.out.println("令牌桶满了!!!");
                    }else{
                        tokens = newTokens;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 根据令牌数判断是否允许执行,需要加锁
     */
    public synchronized boolean execute() {
        if (tokens > 0) {
            tokens = tokens - 1;
            return true;
        }
        return false;
    }
}

  main简单测试

    public static void main(String[] args) {
        //令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
        RateLimiter rateLimiter = new RateLimiter(10, 3);

        //模拟请求
        while (true){
            //在控制台输入一个值按回车,相对于发起一次请求
            Scanner scanner = new Scanner(System.in);
            scanner.next();

            //令牌桶返回true或者false
            if(rateLimiter.execute()){
                System.out.println("允许访问");
            }else{
                System.err.println("禁止访问");
            }
        }
    }

  在SpringCloud分布式下实现限流,需要把令牌桶的维护放到一个公共的地方,比如Zuul路由,当然也可以同时针对具体的每个服务进行单独限流

  另外,guava里有现成的基于令牌桶的限流实现,引入

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>26.0-jre</version>
        </dependency>

  具体用法这里就不阐述了

 

  我们找出之前的springcloud项目,在zuul-server中的AccessFilter过滤器进行限流,其他的都不变,只需要做如下修改

  PS:我这里为了方便测试,调小了令牌桶的大小,跟速率,正常情况下要服务器的承受能力来定

/**
 * Zuul过滤器,实现了路由检查
 */
public class AccessFilter extends ZuulFilter {
    //令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
//PS:我这里为了方便测试,调小了令牌桶的大小,跟速率,正常情况下按服务器的承受能力来定
private RateLimiter rateLimiter = new RateLimiter(2, 1); //业务不变,省略其他代码... /** * 过滤器的具体逻辑 */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); HttpServletResponse response = ctx.getResponse(); //限流 if(!rateLimiter.execute()){ try { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(200); //直接写入浏览器 response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.println("系统繁忙,请稍后在试!<br/>System busy, please try again later!"); writer.flush();return null; } catch (Exception e) { e.printStackTrace(); } } //业务不变,省略其他代码.. } }

  按照我们设置的值,一秒能处理一个请求,峰值一秒能处理两个请求,下面疯狂刷新进行测试

 

 

  熔断

  yml配置开启Hystrix熔断功能,进行容错处理

feign:
  hystrix:
    enabled: true

  设置Hystrix的time-out时间

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000 #毫秒
      #或者设置从不超时
      #timeout:
      #  enabled: false

  在使用Feign调用服务提供者时配置@FeignClient的 fallback,进行容错处理(服务提供者发生异常),如果需要获取到异常信息,则要配置fallbackFactory<T>

@FeignClient(name = "sso-server", path = "/",/*fallback = SsoFeign.SsoFeignFallback.class,*/fallbackFactory = SsoFeign.SsoFeignFallbackFactory.class)

 

    /**
     * 容错处理(服务提供者发生异常,将会进入这里)
     */
    @Component
    public class SsoFeignFallback implements SsoFeign {

        @Override
        public Boolean hasKey(String key) {
            System.out.println("调用sso-server失败,进行SsoFeignFallback.hasKey处理:return false;");
            return false;
        }
    }

 

    /**
     * 只打印异常,容错处理仍交给 SsoFeignFallback
     */
    @Component
    public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> {
        private final SsoFeignFallback ssoFeignFallback;

        public SsoFeignFallbackFactory(SsoFeignFallback ssoFeignFallback) {
            this.ssoFeignFallback = ssoFeignFallback;
        }

        @Override
        public SsoFeign create(Throwable cause) {
            cause.printStackTrace();
            return ssoFeignFallback;
        }
    }

 

  FallbackFactory也可以这样写

    /**
     * 容错处理
     */
@Component
public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> { @Override public SsoFeign create(Throwable cause) { //打印异常 cause.printStackTrace(); return new SsoFeign() { @Override public Boolean hasKey(String key) { System.out.println("调用sso-server失败:return false;"); return false; } }; } }

 

  因为我们没有启动Redis,报错,但我们进行容错处理,所以还是返回了false

 

 

  降级

   当调用服务发送异常,容错处理的方式有多种,我们可以:

  1、重连,比如服务进行了限流,本次连接被限制,重连一次或N次就可以得到数据

  2、直接返回一个友好提示

  3、降级调用备用服务、返回缓存的数据等

 

  后记

  降级也可以叫做“备胎计划”...

 

  代码开源

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/springCloud

  码云:https://gitee.com/huanzi-qch/springCloud

posted @ 2019-06-20 10:27  huanzi-qch  阅读(23519)  评论(0编辑  收藏  举报