高可用之限流-03-Semaphore 訊號量做限流

老马啸西风發表於2024-10-11

限流系列

開源元件 rate-limit: 限流

高可用之限流-01-入門介紹

高可用之限流-02-如何設計限流框架

高可用之限流-03-Semaphore 訊號量做限流

高可用之限流-04-fixed window 固定視窗

高可用之限流-05-slide window 滑動視窗

高可用之限流-06-slide window 滑動視窗 sentinel 原始碼

高可用之限流-07-token bucket 令牌桶演算法

高可用之限流 08-leaky bucket漏桶演算法

高可用之限流 09-guava RateLimiter 入門使用簡介 & 原始碼分析

主流的限流方式

目前主要有以下幾種限流方式:

  • 訊號量

  • 計數器

  • 滑動視窗

  • 漏桶演算法

  • 令牌桶演算法

  • 分散式限流

訊號量

訊號量實際上就是限制系統的併發量,來達到限流的目的。

常見的用法是:建立Semaphore,指定permit的數量。

在方法開始時,呼叫 Semaphore.acquire() 或者 Semaphore.tryAcquire() 來獲取permit,並在方法返回前,呼叫Semaphore.release()來返還permit。

核心程式碼實現

public class LimitSemaphore extends LimitAdaptor {

    /**
     * 日誌
     *
     * @since 0.0.5
     */
    private static final Log LOG = LogFactory.getLog(LimitSemaphore.class);

    /**
     * 訊號量
     *
     * @since 0.0.5
     */
    private final Semaphore semaphore;

    /**
     * 構造器
     *
     * @param context 上下文
     * @since 0.0.5
     */
    public LimitSemaphore(final ILimitContext context) {
        this.semaphore = new Semaphore(context.count());
    }

    @Override
    public synchronized void acquire() {
        try {
            LOG.debug("[Limit] start acquire");
            this.semaphore.acquire(1);
            LOG.debug("[Limit] end acquire");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LOG.error("[Limit] semaphore meet ex: ", e);
        }
    }

    @Override
    public void release() {
        LOG.debug("[Limit] start release");
        this.semaphore.release(1);
        LOG.debug("[Limit] end release");
    }

}

測試

我們限定每次只有一個執行緒可以執行核心方法,如下:

public class LimitSemaphoreTest {

    private static final Log LOG = LogFactory.getLog(LimitSemaphoreTest.class);

    private static final ILimit LIMIT = LimitBs.newInstance(LimitSemaphore.class)
            .count(1)
            .build();

    static class LimitRunnable implements Runnable {
        @Override
        public void run() {
            for(int i = 0; i < 2; i++) {
                try {
                    LIMIT.acquire();
                    LOG.info("{}-{}", Thread.currentThread().getName(), i);
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    LIMIT.release();
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new LimitRunnable()).start();
        new Thread(new LimitRunnable()).start();
    }

}
  • 日誌輸出
13:35:37.501 [Thread-1] INFO  com.github.houbb.rate.limit.test.semaphore.LimitSemaphoreTest - Thread-1-0
13:35:38.501 [Thread-2] INFO  com.github.houbb.rate.limit.test.semaphore.LimitSemaphoreTest - Thread-2-0
13:35:39.502 [Thread-1] INFO  com.github.houbb.rate.limit.test.semaphore.LimitSemaphoreTest - Thread-1-1
13:35:40.503 [Thread-2] INFO  com.github.houbb.rate.limit.test.semaphore.LimitSemaphoreTest - Thread-2-1

可以看到每次只有一個執行緒可以執行方法。

小結

這種方法最為簡單,但是存在很多問題。

併入併發量問題,比如控制的力度不夠靈活細緻等。

後續我們來看下其他的實現方式。

參考資料

限流技術總結

相關文章