zoukankan      html  css  js  c++  java
  • 【转】 SpringCloud系列——限流、熔断、降级

    【转】 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

  • 相关阅读:
    redis总结
    java程序启动脚本
    mysql生成百万测试数据脚本
    java分布式锁的实现及在springboot中的应用
    mysql使用总结
    一个java实现代码(服务)编排的思路(未完)
    sentinel自定义统一限流降级处理
    shell学习
    oracle查看被锁的事务进程sql
    Sql查询两个时间段有重叠的记录
  • 原文地址:https://www.cnblogs.com/Javastudy-note/p/13818321.html
Copyright © 2011-2022 走看看