高并发学习之使用RateLimiter实现令牌桶限流

RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率。
通常可应用于抢购限流防止冲垮系统;限制某接口、服务单位时间内的访问量,譬如一些第三方服务会对用户访问量进行限制;限制网速,单位时间内只允许上传下载多少字节等。

guava的maven依赖

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

令牌桶的原理,有一个独立线程一直以一个固定的速率往桶中存放令牌,客户端去桶中获取令牌,获取到令牌,就可以访问,获取不到,说明请求过多,需要服务降级。

示例代码:

(1)请求限流注解

/**
 * @创建人: hadoop
 * @创建时间: 2020/2/12
 * @描述:
 */
@Target(value = {ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LimitingAnnotation {

    /**
     * 获取令牌超时时间
     */
    long timeOut() default 0L;

    /**
     * 限流速率,每秒最多产生令牌数
     */
    long produceRate() default 1000L;

}

(2)请求限流切面

/**
 * @创建人: hadoop
 * @创建时间: 2020/2/12
 * @描述: 服务限流降级切面,防止每秒请求数过多
 */
@Component
@Aspect
@Slf4j
public class LimitingAop {

    @Value("${request.limit}")
    private Integer limitValue ;

    @Autowired
    private HttpServletResponse response;

    // 令牌桶
    // limitValue : 表示每秒中生成limitValue个令牌存放在桶中
    @SuppressWarnings("UnstableApiUsage")
    private RateLimiter rateLimiter = RateLimiter.create(limitValue);

    /**
     * 限流切点
     */
    @Pointcut("@annotation(com.service.bussiness.aop.LimitingAnnotation)")
    public void limitingPointCut() {
    }

    @SuppressWarnings({"rawtypes", "UnstableApiUsage"})
    @Around("limitingPointCut()")
    public Object controllerAround( ProceedingJoinPoint point ) {
        String description = CommonConst.EMPTYSTRING;
        Method method = null;
        String methodName = point.getSignature().getName();
        Class[] paramTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
        try {
            method = point.getTarget().getClass().getMethod(methodName, paramTypes);
            if ( !method.isAnnotationPresent(LimitingAnnotation.class) ) {
                return null;
            }
        } catch ( NoSuchMethodException e ) {
            log.error("限流切面出现异常,异常原因是: " + e.getMessage());
            throw new CustomException(
                    Integer.parseInt(CustomExceptionType.SYSTEM_ERROR.getCode()) ,
                    e.getMessage(),
                    "限流切面出现异常",
                    "请求的目标方法不存在"
            );
        }
        LimitingAnnotation limitingAnnotation =
                method.getAnnotation(LimitingAnnotation.class);
        if ( null == limitingAnnotation ){
            return null;
        }
        long timeOut = limitingAnnotation.timeOut();
        long produceRate = limitingAnnotation.produceRate();
        // 设置限流速率,每秒产生多少令牌
        rateLimiter.setRate(produceRate);
        // 每次发送请求,在设定ms内没有获取到令牌,则对服务进行降级处理
        boolean acquire = rateLimiter.tryAcquire(timeOut, TimeUnit.MILLISECONDS);
        if ( !acquire ){
            getErrorMsg();
            return null;
        }
        try {
            return point.proceed();
        } catch (Throwable throwable) {
            log.error(methodName+"请求出现异常,异常原因是: " + throwable.getMessage());
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 向客户端输出服务降级信息
     *
     */
    public void getErrorMsg(){
       response.setHeader("Content-Type","application/json;charset=UTF-8");
        PrintWriter printWriter = null;
        try {
            printWriter = response.getWriter();
            Map<String,Object> resultMap = new HashMap<>();
            resultMap.put("msg","前方任务过多,请稍后再试");
            printWriter.write(JSON.toJSONString(resultMap));
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

(3) 请求服务

    @RequestMapping(value = "/search", method = RequestMethod.POST, consumes = "application/json")
    @ResponseBody
    @LimitingAnnotation( timeOut = 500, produceRate = 1000 )
    public ResponseEntity<String> search( @RequestBody String param )     {return this.searchService.searchBillInfo( xEncryption , params );
    }

参考:

https://www.cnblogs.com/pickKnow/p/11252519.html

原文地址:https://www.cnblogs.com/sunfie/p/12298892.html

时间: 2024-07-31 09:47:33

高并发学习之使用RateLimiter实现令牌桶限流的相关文章

coding++:高并发解决方案限流技术-使用RateLimiter实现令牌桶限流-Demo

RateLimiter是guava提供的基于令牌桶算法的实现类,可以非常简单的完成限流特技,并且根据系统的实际情况来调整生成token的速率. 通常可应用于抢购限流防止冲垮系统:限制某接口.服务单位时间内的访问量,譬如一些第三方服务会对用户访问量进行限制:限制网速,单位时间内只允许上传下载多少字节等. guava的maven依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guav

高并发学习(一)

要理解java内存模型以及一些处理高并发的技术手段,理解一些主要的硬件知识是必须的. 一个主要CPU运行计算的步骤例如以下: 程序以及数据被载入到主内存 指令和数据被载入到CPU的快速缓存 CPU运行指令,把结果写到快速缓存 快速缓存中的数据写会主内存 高并发的问题: CPU多级缓存:缓存一致性,乱序执行优化 缓存一致性:eg.(i初值为1,两个线程对i进行加1操作)两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存.此时线程2的高速缓

Dubbo学习系列之十(Sentinel之限流与降级)

各位看官,先提个问题,如果让你设计一套秒杀系统,核心要点是啥???我认为有三点:缓存.限流和分离.想当年12306大面积崩溃,还有如今的微博整体宕机情况,感觉就是限流降级没做好,"用有限的资源响应过量请求"——这就是限流降级的核心.限流降级组件,当今开源界应该是Hystrix最为出名,这也得益于SpringCloud的流行,当然,挑战者总是有的,于是Sentinel横空出世,正因实际生产使用中似乎并不多见,所以才有必要拿来一用,不然就脱离了此系列文章的主旨了,就是要见些不一样的风景!

java高并发学习-死锁(一)

死锁发生的必要条件: 1.互斥条件 2.请求和保持条件 3.资源不剥夺条件 4.环路等待条件 多线程的最佳并发实践; 1. 使用本地变量 2.使用不可变类 3.最小化锁的作用域范围 : S = 1/(1-a+a/n)   (阿木达尔定律) 其中,a为串行计算部分所占比例,n为并行处理结点个数.这样,当a=0时,最大加速比s=n:当a=1时,最小加速比s=1:当n→∞时,极限加速比s→ 1/a,这也就是加速比的上限.例如,若串行代码占整个代码的25%,则并行处理的总体性能不可能超过4.这一公式已被

转:java高并发学习记录-死锁,活锁,饥饿

死锁 两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 为什么会产生死锁: ① 因为系统资源不足. ② 进程运行推进的顺序不合适. ③ 资源分配不当. 产生死锁的条件有四个: ① 互斥条件:所谓互斥就是进程在某一时间内独占资源. ② 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放. ③ 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺. ④ 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系. 避免

Java并发和高并发学习总结(四)- J.U.C之工具类

1.总览 CountDownLatch允许一个或多个线程等待某些操作完成 Semaphore Java版本的信号量实现 CyclicBarrier 一种辅助性的同步结构,允许多个线程等待到达某个屏障 Exchanger 在线程间交换数据的一种手段 2.CountDownLatch 当一个或多个线程需要等待其他线程完成操作时,就可以使用CountDownLatch了,当然,最简单的你也可以使用join方法 2.1.join public class JoinTest { public static

Java多线程高并发学习笔记(三)——深入理解线程池

线程池最核心的一个类:ThreadPoolExecutor. 看一下该类的构造器: public ThreadPoolExecutor(int paramInt1, int paramInt2, long paramLong, TimeUnit paramTimeUnit, BlockingQueue<Runnable> paramBlockingQueue) { this(paramInt1, paramInt2, paramLong, paramTimeUnit, paramBlockin

高并发学习(二)

安全发布对象-发布与逃逸 发布对象:使一个对象能够被当前范围之外的代码所使用 对象逃逸:一种错误的发布.当一个对象还没有构造完成的,就使它被其他线程所见 安全发布对象(单例模式:列一下) 在静态初始化函数中初始化一个对象引用 将对象的引用保存到volatile类型或者AtomicReference对象中 将对象的引用保存到某个正确构造对象的final类型域中 将对象的引用保存到锁保护的域中 不可变对象 对象创建之后其状态就不能修改 对象所有域都是final类型 对象是正确创建的(在创建期间,th

高并发限流策略

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到