使用 Semaphore 和 RateLimiter 完成限流

目录

  • 保护系统机制
  • 常用的限流算法
  • Semaphore 信号量
  • RateLimiter 限流工具类
  • CountDownLatch 计数器

保护系统机制

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。

缓存:提升系统访问速度和增大系统处理容量。
降级:当服务出现问题获取影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开。
限流:通过对并发访问/请求进行限速,一旦达到限制速率则可以拒绝服务、排队等待等处理。

常用的限流算法

漏桶算法:

水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,实现了强行限制数据的传输速率。

令牌桶算法:

系统以一个恒定的速度往桶里放入令牌token,请求进来后,需要先从桶里获取一个token,当桶里没有token可用时,则拒绝服务或者排队等待等处理。

Semaphore 信号量

定义:

java.util.concurrent.Semaphore

Semaphore 主要用于控制当前活动线程的数目,每个线程都需要通过acquire()方法获取许可,使用release()释放许可。当活动线程数目等于最大许可后,那么在调用acquire()方法后,线程会进入等待队列,等待已获取许可的线程释放许可。

Semaphore semaphore = new Semaphore(10, true):10 表示线程活动的数目,true 表示是公平锁
semaphore.acquire():获取许可
semaphore.release():释放许可

限流使用案例:

public class SemaphoreDemo {

    // 请求总数
    public static int clientTotal = 10;
    // 同时并发执行的线程数
    public static int threadTotal = 5;
    // 已执行线程的数量
    public static int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 信号量,此处用于控制并发的线程数
        final Semaphore semaphore = new Semaphore(threadTotal);
        // 闭锁,可实现计数器递减
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                try {
                    // 执行此方法用于获取执行许可,当总计未释放的许可数不超过5时,允许通行,否则线程阻塞等待,直到获取到许可。
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+":start");
                    add();
                    System.out.println(Thread.currentThread().getName()+":end");
                    // 释放许可
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 闭锁减一
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();// 线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
        executorService.shutdown();
        System.out.println("执行的总数:" + count);
        return;
    }

    // 业务逻辑
    private static void add() throws InterruptedException {
        count++;
        Random random = new Random();
        int i = random.nextInt(3) + 1;
        Thread.sleep(i * 1000);
    }
}

打印结果:

pool-1-thread-1:start
pool-1-thread-2:start
pool-1-thread-3:start
pool-1-thread-4:start
pool-1-thread-5:start
pool-1-thread-2:end
pool-1-thread-6:start
pool-1-thread-4:end
pool-1-thread-7:start
pool-1-thread-1:end
pool-1-thread-8:start
pool-1-thread-3:end
pool-1-thread-9:start
pool-1-thread-5:end
pool-1-thread-7:end
pool-1-thread-10:start
pool-1-thread-8:end
pool-1-thread-6:end
pool-1-thread-9:end
pool-1-thread-10:end
执行的总数:10

Process finished with exit code 0

发现总是只有5个线程在执行中,当其中一个线程执行完并释放许可后,就会唤醒一个等待的线程执行。

RateLimiter 限流工具类

定义:

com.google.common.util.concurrent.RateLimiter

Guava 提供的限流工具类 RateLimiter ,该类基于令牌桶算法实现的流量限制。
RateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费)。

// 创建一个限流器,参数代表每秒生成的令牌数
RateLimiter.create(1); 
// 阻塞的方式获取令牌
acquire(i); 
// 来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回
tryAcquire(int permits, long timeout, TimeUnit unit); 

限流使用案例:

package fireland.yangqian.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

import com.google.common.util.concurrent.RateLimiter;

@Slf4j
public class MiaoShaConcurrentRequest {

    private static RateLimiter rateLimiter = RateLimiter.create(10);
    // 请求总数
    public static int          clientTotal = 20;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 闭锁,可实现计数器递减
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(() -> {
                log.info("start");
                try {
                    System.out.println(miao());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 闭锁减一
                    countDownLatch.countDown();
                    log.info("end");
                });
        }
        countDownLatch.await();// 线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
        executorService.shutdown();
        return;
    }

    private static String miao() {
        //判断能否在1秒内得到令牌,如果不能则立即返回false,不会阻塞程序  
        if (!rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
            System.out.println("短期无法获取令牌,真不幸,排队也瞎排");
            return "失败";
        }
        return "成功";
    }
}

CountDownLatch 计数器

CountDownLatch 是一个能够使一个线程等待其他线程完成各自的工作后再执行。

// 闭锁,可实现计数器递减
final CountDownLatch countDownLatch = new CountDownLatch(10);
// 闭锁减一
countDownLatch.countDown();
// 线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
countDownLatch.await();

参考:
使用Guava RateLimiter限流以及源码解析

你可能感兴趣的:(多线程)