精华内容
下载资源
问答
  • java接口限流

    千次阅读 2018-01-15 21:34:17
    为了避免这种情况,我们就需要对接口请求进行限流。  限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。  常见的限流模式...

    系统在设计之初就会有一个预估容量,长时间超过系统能承受的TPS/QPS阈值,系统可能会被压垮,最终导致整个服务不够用。为了避免这种情况,我们就需要对接口请求进行限流。 

    限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。 

    常见的限流模式有控制并发和控制速率,一个是限制并发的数量,一个是限制并发访问的速率,另外还可以限制单位时间窗口内的请求数量。

     

    控制并发数量

     

    属于一种较常见的限流手段,在实际应用中可以通过信号量机制(如Java中的Semaphore)来实现。 
    举个例子,我们对外提供一个服务接口,允许最大并发数为10,代码实现如下:

        
        final Semaphore permit = new Semaphore(10, true);// 允许最大并发数为10
    
        @RequestMapping(value="/p1",method=RequestMethod.GET)
    	public void process() {		
    
    		try {
    			permit.acquire();
    			// 业务逻辑处理
    			long start=System.currentTimeMillis();
    			System.out.println(i+"我要吃西瓜");
    			Thread.sleep(2000);
    			System.out.println(i+"→"+(System.currentTimeMillis()-start));
    
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} finally {
    			permit.release();
    			i++;
    		}
    	}
    


    在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证。

     

     

     

    控制访问速率

     

    在我们的工程实践中,常见的是使用令牌桶算法来实现这种模式,其他如漏桶算法也可以实现控制速率,但在我们的工程实践中使用不多,这里不做介绍,读者请自行了解。

    Wikipedia上,令牌桶算法是这么描述的:

    1. 每过1/r秒桶中增加一个令牌。
    2. 桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。
    3. 当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。
    4. 如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。

    令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。

    在我们的工程实践中,通常使用Guava中的Ratelimiter来实现控制速率,如我们不希望每秒的任务提交超过2个:

     

           final RateLimiter rateLimiter = RateLimiter.create(2.0);
    
    	/**
    	 * 控制访问速率 令牌桶算法,漏桶算法略
    	 */
    	@RequestMapping(value = "/p2",method=RequestMethod.GET)
    	public void process2() {
    
    		List<Runnable> tasks = new ArrayList<Runnable>();
    //		for (int i = 0; i < 10; i++) {
    //			tasks.add(new Task());
    //		}
    		tasks.add(new Task());
    		Executor executor = Executors.newFixedThreadPool(10);
    		submitTasks(tasks, executor);
    	}
    
          void submitTasks(List<Runnable> tasks, Executor executor) {
    
    		for (Runnable task : tasks) {
    			rateLimiter.acquire(); // 也许需要等待
    			executor.execute(task);
    		}
    	}
    	
    	 class Task extends Thread{
    		
    	   @Override
    		public void run() {
    			// TODO Auto-generated method stub
    			for (int i = 1; i < 1000001; i++) {
    				if(i%1000000==0){
    					System.err.println(" thread → "+(DateUtils.getDateStr("yyyy-MM-dd HH:mm:ss",new Date(System.currentTimeMillis()))));
    				}
    			}
    		}
    	}

     

     

     

     

     

     

    控制单位时间窗口内请求数

     

    某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数。例如限制服务每秒的调用次数为50,实现如下:

     

    private LoadingCache<Long, AtomicLong> counter = CacheBuilder.newBuilder()
    			.expireAfterWrite(2, TimeUnit.SECONDS)
    			.build(new CacheLoader<Long, AtomicLong>() {
    				@Override
    				public AtomicLong load(Long seconds) throws Exception {
    					return new AtomicLong(0);
    				}
    			});
     /**
    	    * 控制单位时间窗口内请求数
    	    * 某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数。例如限制服务每秒的调用次数为50,实现如下:
    	    * @return
    	    * @throws ExecutionException
    	    */
    		@RequestMapping(value = "/p3",produces="text/plain;charset=UTF-8",method=RequestMethod.GET)
    		public ResponseEntity<String> getData() throws ExecutionException {
    
    			// 得到当前秒
    			long currentSeconds = System.currentTimeMillis() / 1000;
    			if (counter.get(currentSeconds).incrementAndGet() > permit) {
    				 return new ResponseEntity<String>("访问速率过快", HttpStatus.FORBIDDEN);  
    			                
    			}
    			// 业务处理
    			new Task().start();
    			return null;
    
    		}
    

     

    https://blog.csdn.net/boling_cavalry/article/details/75174486?locationNum=7&fps=1
    https://blog.csdn.net/syc001/article/details/72841951
    https://blog.csdn.net/lzy_lizhiyang/article/details/47951423
    https://blog.csdn.net/sinat_26935081/article/details/79172715

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Java 接口限流

    千次阅读 2018-09-02 14:42:48
    1限流原理 -- 令牌桶算法  令牌桶算法的原理是系统会以一个恒定的速度(每秒生成一个令牌)往桶里放入令牌。当有访问者(针对于 IP)要访问接口时,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝...

    目录:

    1. 限流原理
    2. 知识点
    3. 具体实现
    4. 结语

     

    内容:

    1、限流原理 -- 令牌桶算法 

    令牌桶算法的原理是系统会以一个恒定的速度(每秒生成一个令牌)往桶里放入令牌。当有访问者(针对于 IP)要访问接口时,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 当桶满时,新添加的令牌被丢弃或拒绝。

     

    2、知识点

    • Springboot
    • Guava -- RateLimiter
    • Interceptor(拦截器)

     

    3、具体实现

    1)先写一个限流 Service -- LoadingCacheService,代码如下:

    @Service
    public class LoadingCacheService {
    
        private final Logger logger = LoggerFactory.getLogger(LoadingCacheService.class);
    
        private LoadingCache<String, RateLimiter> ipRequestCaches = CacheBuilder.newBuilder()
                // 设置缓存上限
                .maximumSize(10000)
                // 设置一分钟对象没有被读/写访问则对象从内存中删除
                .expireAfterAccess(1, TimeUnit.MINUTES)
                // CacheLoader 类实现自动加载
                .build(new CacheLoader<String, RateLimiter>() {
                    @Override
                    public RateLimiter load(String s) {
                        // 新的 IP 初始化 (限流每秒生成 2 个令牌)
                        return RateLimiter.create(2);
                    }
                });
    
        public boolean hasToken(HttpServletRequest request) {
            try {
                String ip = this.getIPAddress(request);
                String url = request.getRequestURL().toString();
                String key = "req_limit_".concat(url).concat(ip);
                // 有则返回,没有就添加后获取
                RateLimiter limiter = ipRequestCaches.get(key);
    
                return limiter.tryAcquire();
            } catch (Exception e) {
                logger.error("获取令牌异常:", e);
            }
            return false;
        }
    
        /**
         * 获取当前网络 ip
         *
         * @param request HttpServletRequest
         * @return 真实的 ip 地址
         */
        private String getIPAddress(HttpServletRequest request) {
            String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("x-forwarded-for");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
            // 对于通过多个代理的情况,第一个 IP 为客户端真实 IP,多个 IP 按照','分割
            // "***.***.***.***".length() = 15
            if (ipAddress != null && ipAddress.length() > 15) {
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
            return ipAddress;
        }
    
    }

    以上代码要注意的点:

    1. Guava 用到了缓存,感兴趣的同学,可以自己深入学习一下;
    2. RateLimiter.create(2) 意思就是每秒生成两个令牌,如果改为 3 ,就是每秒生成 3 个;
    3. 仅仅靠 request.getRemoteAddr() 有可能获取不到用户的真实 IP ,需要用 getIPAddress() 方法。

    2)写一个拦截器组件 -- RequestInterceptor

    @Component
    public class RequestInterceptor implements HandlerInterceptor {
    
        @Resource
        private LoadingCacheService loadingCacheService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws IOException {
            if (loadingCacheService.hasToken(request)) {
                return true;
            }
            outputError(response);
            return false;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) {
    
        }
    
        private void outputError(HttpServletResponse response) throws IOException {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(429);
            ServletOutputStream outputStream = null;
            try {
                outputStream = response.getOutputStream();
                outputStream.write(JSONObject.toJSONString(new ErrorRes("请求太频繁!")).getBytes("UTF-8"));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outputStream != null) {
                    outputStream.flush();
                    outputStream.close();
                }
            }
        }
    
    }

    3)编写拦截器配置类 -- InterceptorConfig,并注册刚才编写的拦截器 -- RequestInterceptor

    @Configuration
    public class InterceptorConfig extends WebMvcConfigurerAdapter {
    
        @Resource
        private RequestInterceptor requestInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(requestInterceptor).addPathPatterns("/api/v1/**");
            // 注册
            super.addInterceptors(registry);
        }
    
    }

    以上代码要注意的点:

    1. addPathPatterns 里面的内容,就是要拦截的接口;
    2. 要拦截多个地方,可以用逗号隔开,比如:addPathPatterns("/api/v1/**", "/api/v2/**") 。

     

    4、结语

    如果我的博客你看到了这里,我想说明一下,我一般会在开头就先写实现的具体代码,而在最后进行总结。

    之前在网上搜集过一些资料,还有用到自定义注解的,可以参考:https://blog.csdn.net/u013476435/article/details/82180663。而我没有用的原因是:那些注解都用到了 aop,大部分在超流了以后,会通过抛异常的形式来处理,但我想要的时候通过返回给用户一个“请求太频繁”的提示,来达到目的。

    当然,还没有考虑到就是恶意攻击。那就得再另起一篇来说明了,比如:增加 IP 黑名单等等。但就我们公司目前的业务,暂时是通过手动配置 IP 黑名单来处理的,还没有在程序中限制。关于这块,以后如果有用到的话,我会进行补充。在这写出来,也是给以后的自己提个醒!

    展开全文
  • Java接口限流算法

    千次阅读 2018-07-20 08:39:55
    常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。 1. 算法介绍 1.1 令牌桶算法 令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下: * ...

    0. 前言

    常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。

    1. 算法介绍

    1.1 令牌桶算法

    令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

    • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
    • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
    • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;
    • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

    1.2 漏桶算法

    漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

    • 一个固定容量的漏桶,按照常量固定速率流出水滴;
    • 如果桶是空的,则不需流出水滴;
    • 可以以任意速率流入水滴到漏桶;
    • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

    1.3 总结

    令牌桶和漏桶对比:

    • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
    • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
    • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
    • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
    • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
    • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

    另外有时候我们还使用计数器来进行限流,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发数;只要全局总请求数或者一定时间段的总请求数设定的阀值则进行限流,是简单粗暴的总数量限流,而不是平均速率限流。

    2. 算法实现

    《java接口限流小结》中的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。Guava框架提供了令牌桶算法实现,可直接拿来使用。

    Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

    2.1 SmoothBursty

    RateLimiter limiter = RateLimiter.create(5);
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    

    将得到类似如下的输出:

    0.0
    0.198903
    0.195463
    0.19712
    0.199252
    0.196105
    

    1、RateLimiter.create(5) 表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;
    2、limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌(如上测试用例返回的为0.198239,差不多等待了200毫秒桶中才有令牌可用),这种实现将突发请求速率平均为了固定请求速率。

    再看一个突发示例:

    RateLimiter limiter = RateLimiter.create(5);
    System.out.println(limiter.acquire(5));
    System.out.println(limiter.acquire(1));
    System.out.println(limiter.acquire(1));
    

    将得到类似如下的输出:

    0.0
    0.998729
    0.19379
    

    limiter.acquire(5)表示桶的容量为5且每秒新增5个令牌,令牌桶算法允许一定程度的突发,所以可以一次性消费5个令牌,但接下来的limiter.acquire(1)将等待差不多1秒桶中才能有令牌,且接下来的请求也整形为固定速率了。

    RateLimiter limiter = RateLimiter.create(5);
    System.out.println(limiter.acquire(10));
    System.out.println(limiter.acquire(1));
    System.out.println(limiter.acquire(1));
    

    将得到类似如下的输出:

    0.0
    1.998922
    0.19615
    

    同上边的例子类似,第一秒突发了10个请求,令牌桶算法也允许了这种突发(允许消费未来的令牌),但接下来的limiter.acquire(1)将等待差不多2秒桶中才能有令牌,且接下来的请求也整形为固定速率了。

    接下来再看一个突发的例子:

    RateLimiter limiter = RateLimiter.create(2);
    System.out.println(limiter.acquire());
    Thread.sleep(2000L);
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    System.out.println(limiter.acquire());
    

    接下来再看一个突发的例子:

    0.0
    0.0
    0.0
    0.0
    0.499738
    0.496078
    

    1、创建了一个桶容量为2且每秒新增2个令牌;
    2、首先调用limiter.acquire()消费一个令牌,此时令牌桶可以满足(返回值为0);
    3、然后线程暂停2秒,接下来的两个limiter.acquire()都能消费到令牌,第三个limiter.acquire()也同样消费到了令牌,到第四个时就需要等待500毫秒了。

    此处可以看到我们设置的桶容量为2(即允许的突发量),这是因为SmoothBursty中有一个参数:最大突发秒数(maxBurstSeconds)默认值是1s,突发量/桶容量=速率*maxBurstSeconds,所以本示例桶容量/突发量为2,例子中前两个是消费了之前积攒的突发量,而第三个开始就是正常计算的了。令牌桶算法允许将一段时间内没有消费的令牌暂存到令牌桶中,留待未来使用,并允许未来请求的这种突发。

    SmoothBursty通过平均速率和最后一次新增令牌的时间计算出下次新增令牌的时间的,另外需要一个桶暂存一段时间内没有使用的令牌(即可以突发的令牌数)。另外RateLimiter还提供了tryAcquire方法来进行无阻塞或可超时的令牌消费。

    因为SmoothBursty允许一定程度的突发,会有人担心如果允许这种突发,假设突然间来了很大的流量,那么系统很可能扛不住这种突发。因此需要一种平滑速率的限流工具,从而系统冷启动后慢慢的趋于平均固定速率(即刚开始速率小一些,然后慢慢趋于我们设置的固定速率)。Guava也提供了SmoothWarmingUp来实现这种需求,其可以认为是漏桶算法,但是在某些特殊场景又不太一样。

    2.2 SmoothWarmingUp

    SmoothWarmingUp创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)
    permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。
    示例如下:

    RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);
    for(int i = 1; i < 5;i++) {
        System.out.println(limiter.acquire());
    }
    Thread.sleep(1000L);
    for(int i = 1; i < 5;i++) {
        System.out.println(limiter.acquire());
    }
    

    将得到类似如下的输出:

    0.0
    0.518916
    0.356743
    0.219183
    0.0
    0.519927
    0.354751
    0.214637
    

    速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

    到此应用级限流的一些方法就介绍完了。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。

    参考:
    http://jinnianshilongnian.iteye.com/blog/2305117

    展开全文
  • Java接口限流小结

    千次阅读 2018-06-27 23:42:39
    在高并发系统开发时有时候需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如 1000 次,来保护接口。 2. Semaphore ...

    1. 引子

    在高并发系统开发时有时候需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如 1000 次,来保护接口。

    2. 计数器 AtomicLong

    可以使用Java中的AtomicLong进行限流:

    try {
        if(atomic.incrementAndGet() > 限流数) {
            //拒绝请求
       }
        //处理请求
    } finally {
        atomic.decrementAndGet();
    }
    

    适合对业务无损的服务或者需要过载保护的服务进行限流,如抢购业务,超出了大小要么让用户排队,要么告诉用户没货了,对用户来说是可以接受的。而一些开放平台也会限制用户调用某个接口的试用请求量,也可以用这种计数器方式实现。这种方式也是简单粗暴的限流,没有平滑处理,需要根据实际情况选择使用.

    3. Semaphore

    private static Semaphore  apiSemaphore = new Semaphore(100);
    // 并发访问控制
    boolean concurrentPermission = apiSemaphore.tryAcquire(50, TimeUnit.MILLISECONDS);
    if (concurrentPermission) {
        // 允许访问
    } else {
        // 并发访问超过系统允许上限,请稍后再试!
    }
    

    4. RateLimiter限制资源的并发访问线程数

    RateLimiter类似于JDK的信号量Semphore,他用来限制对资源并发访问的线程数。

    RateLimiter limiter = RateLimiter.create(4.0); //每秒不超过4个任务被提交
    limiter.acquire();  //请求RateLimiter, 超过permits会被阻塞
    executor.submit(runnable); //提交任务
    

    也可以以非阻塞的形式来使用:

    if (limiter.tryAcquire()) { //未请求到limiter则立即返回false
        doSomething();
    } else {
        doSomethingElse();
    }
    

    5. 利用缓存,存储一个计数器,然后用这个计数器来实现限流

    限流某个接口的时间窗请求数:即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数/调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒/每分钟的调用量进行限速;一种实现方式如下所示:

    static LoadingCache<Long, AtomicLong> count = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<Long, AtomicLong>() {
        @Override
        public AtomicLong load(Long o) throws Exception {
            //System.out.println("Load call!");
            return new AtomicLong(0L);
        }
    });
    

    我们通过 CacheBuilder 来新建一个 LoadingCache 缓存对象 count,然后设置其有效时间为 1 秒,即每 1 秒钟刷新一次;缓存中,key 为一个 long 型的时间戳类型,value 是一个计数器,使用原子性的 AtomicLong 保证自增和自减操作的原子性, 每次查询缓存时如果不能命中,即查询的时间戳不在缓存中,则重新加载缓存,执行 load 将当前的时间戳的计数值初始化为 0。这样对于每一秒的时间戳,能计算这一秒内执行的次数,从而达到限流的目的;
    测试代码如下:

    public class Counter {
        static int counter = 0;
        public static int getCounter() throws Exception{
            return counter++;
        }
    }
    

    现在我们创建多个线程来执行这个方法:

    public class Test {
        public static void main(String args[]) throws Exception {
            for (int i = 0; i < 100; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            System.out.println(Counter.getCounter());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
            }
    
        }
    }
    

    这里的 for 循环执行 100 个进程时间是很快的,那么现在我们要限制每秒只能有 10 个线程来执行 getCounter() 方法,该怎么办呢,上面讲的限流方法就派上用场了:

    public class Counter {
        static LoadingCache<Long, AtomicLong> count = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader<Long, AtomicLong>() {
            @Override
            public AtomicLong load(Long o) throws Exception {
                System.out.println("Load call!");
                return new AtomicLong(0L);
            }
        });
    
        static long limits  = 10;
        static int  counter = 0;
    
        public static synchronized int getCounter() throws Exception {
            while (true) {
                //获取当前的时间戳作为key
                Long currentSeconds = System.currentTimeMillis() / 1000;
                if (count.get(currentSeconds).getAndIncrement() > limits) {
                    continue;
                }
                return counter++;
            }
        }
    }
    

    这样一来,就可以限制每秒的执行数了。对于每个线程,获取当前时间戳,如果当前时间 (当前这 1 秒) 内有超过 10 个线程正在执行,那么这个进程一直在这里循环,直到下一秒,或者更靠后的时间,重新加载,执行 load,将新的时间戳的计数值重新为 0。
    执行结果可以看出每秒执行 11 个(因为从 0 开始),每一秒之后,load 方法会执行一次;为了更加直观,我们可以让每个for循环sleep一段时间:

    public class Test {
        public static void main(String args[]) throws Exception {
            for (int i = 0; i < 100; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            System.out.println(Counter.getCounter());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }.start();
                Thread.sleep(100);
            }
        }
    }
    

    在上述这样的情况下,一个线程如果遇到当前时间正在执行的线程超过 limit 值就会一直在 while 循环,这样会浪费大量的资源,我们在做限流的时候,如果出现这种情况,可以不进行 while 循环,而是直接抛出异常或者返回,来拒绝这次执行(查询),这样便可以节省资源。

    参考:
    http://www.54tianzhisheng.cn/2017/09/23/Guava-limit/

    展开全文
  • java接口限流自定义注解+拦截器+redis

    千次阅读 2019-01-18 09:51:34
    声明:以下代码基本都是参考网上...先定义自定义接口 @Inherited @Document @Target({ElementType.FIELD, ElementType.METHOD, ElementType.Type}) @Retention(RetentionPolicy.RUNTIME) public @interface Access...
  • 限流算法 漏桶算法 令牌算法 活动窗口算法 使用guava漏桶算法实现速率控制 @Test public void testRateLimiterParallel() throws Exception { Stopwatch started = Stopwatch.createStarted(); ExecutorService ...
  • Java对API接口进行限流

    千次阅读 2019-04-18 17:58:01
    用的时候,可以把这个限流工具类声明成Bean,然后注入到控制器中,处理请求之前先调用方法获取令牌,如果获取到了就继续进行请求,否则驳回,达到限流的目的。 或者也可以配置一个全局拦截器,在拦截器中获取令牌...
  • 高级JAVA - 高并发下接口限流 Semaphore

    千次阅读 2018-12-07 14:42:18
    Semaphore, 是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类 Semaphore字面意思即信号量 , 个人认为比较容易理解的说法应该是 许可证管理器 官方的解释为 Semaphore是一个计数信号量 从概念上将,...
  • 本章主要介绍redis对接口进行限流访问,当接口在高并发的情况下,会对我们的服务器造成一定影响,可通过次案例提供轻微的解决方案 应用场景:用户注册,电商秒杀接口,高并发 .... 实现方案:自定义注解+拦截器+...
  • 本篇主要讲如何使用Semaphore对单接口进行限流,例如有如下场景 a. A系统的有a接口主要给B系统调用,现在希望对B系统进行限流,例如处理峰值在100,超过100的请求快速失败 b. 接口作为总闸入口,希望限制所有外来...
  • Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面。...之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善。来看一下sentinel的简介: https://...
  • 高并发处理之接口限流

    万次阅读 多人点赞 2018-06-13 18:24:56
    最近开发的抢购活动上线后发现了两个...一方面,限流可以防止接口被刷,造成不必要的服务层压力,另一方面,是为了防止接口被滥用。 限流的方式也蛮多,本篇只讲几种我自己常用的,并且是后端的限流操作。 漏桶...
  • 分布式接口限流实现

    千次阅读 2020-06-19 23:02:57
    文章目录为什么要接口限流为什么要做分布式实现方式1. 算法实现(无分布式,单体架构,单节点)2. 分布式实现 为什么要接口限流 在我们项目开发过程中,有些接口是暴露在用户的常用中,包括一些高危接口,如 (支付,...
  • java实现系统限流及IP限流

    千次阅读 2018-10-11 22:34:14
    Java 对IP请求进行限流. 高并发系统下, 有三把利器 缓存 降级 限流. 缓存: 将常用数据缓存起来, 减少数据库或者磁盘IO 降级: 保护核心系统, 降低非核心业务请求响应 限流: 在某一个时间窗口内...
  • java 限流器实现

    千次阅读 2019-09-21 11:47:25
    java 限流器实现目录一、目的二、基本思路三、具体实现 一、目的    并发问题处理:单位时间内请求次数过多,访问量较大时,报错提示用户。往往需要进行限流(每一秒限制请求几次) 二、基本思路    使用 redis ...
  • java 限流策略

    千次阅读 2017-10-31 23:29:40
    此时你需要使用的技术手段之一就是限流,当请求达到一定的并发数或速率,就进行等待、排队、降级、拒绝服务等。在限流时,常见的两种算法是漏桶和令牌桶算法算法。 限流算法 令牌桶(Token Bucket)、漏桶(leaky ...
  • 接口限流方案

    千次阅读 2018-08-31 20:34:43
    服务接口的流量控制策略:分流、降级、限流 熔断 分流:扩容机器、单元化通道 降级:关闭非核心接口,保证核心接口链路的正常运行 限流:业务系统限流、数据库限流 常见的限流算法有:令牌桶、漏桶、Redis计数器...
  • 接口限流算法及解决方案

    千次阅读 2018-08-27 15:15:59
    接口限流算法:漏桶算法&amp;amp;amp;amp;令牌桶算法 redisson实现分布式限流 一、限流算法 1. 漏桶算法 2. 令牌桶算法 二、令牌桶算法VS漏桶算法 三、解决方案 1. 使用Guava的RateLimiter进行限流...
  • Spring自定义注解+redis实现接口限流

    千次阅读 2019-08-16 11:20:41
    在实际开发中,有时候我们需要对某些接口进行限流,防止有人恶意攻击或者是因为某些接口自身的原因,比如发短信接口,IO处理的接口。 这里我们通过自定义一个注解,并利用Spring的AOP拦截器功能来实现限流的功能。...
  • java单机限流实现

    2019-06-19 14:30:08
    令牌桶算法实现限流 public class TokenBucketDemo { private static long time = System.currentTimeMillis(); private static int createTokenRate = 3; private static int size = 10; //当前令牌数 priva....
  • api接口限流 防止恶意刷接口

    千次阅读 2019-12-26 18:36:16
    3.淘宝获取ip所在城市接口、微信公众号识别微信用户等开发接口,免费提供给用户时需要限流,更具有实时性和准确性的接口需要付费。 api限流实战 首先我们编写注解类AccessLimit,使用注解方式在方法上...
  • Hystrix 线程池隔离与接口限流

    千次阅读 2019-04-14 21:26:57
    我们需要对接口进行限流, 这样的话,hystrix就派上用场了,hystrix对这个问题大概有两种解决办法,其中一种就是限流+服务降级,对于外部的请求来说,当我们根据线上的生产情况对某个接口在高峰期的QPS进行合理的...
  • java限流策略

    千次阅读 2018-05-08 15:20:36
    在高并发情况下,经常会出现接口或服务不可用的情况,甚至会引发系统崩溃,对于该种情况需要使用限流,当请求数达到一定的并发数,就进行服务降级、拒绝、等待等。常见的限流算法是计数器限流算法、漏桶算法、令牌桶...
  • RateLimit--使用guava来做接口限流

    千次阅读 2017-05-09 09:35:44
     某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的...
  • @Configuration public class WebConfig extends WebMvcConfigurerAdapter{ @Autowired UserArgumentResolver userArgumentResolver; @Autowired AccessInterceptor accessInterceptor; ... public void ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 160,279
精华内容 64,111
关键字:

java接口限流1

java 订阅