【微服務穩定性之限流降級】【二】RateLimiter實戰

     RateLimiter是guava中concurrent包下的一個限流工具類,使用了令牌桶算法。下面簡單的介紹一下。

 

一、RateLimiter API

修飾符

方法

描述

double

acquire()

從RateLimiter獲取一個許可,該方法會被阻塞知道獲取到請求,阻塞動作,返回的double是從阻塞到獲取到令牌等待的時間。

double

acquire(int permits)

從RateLimiter獲取指定許可數,該方法會被阻塞知道獲取到請求

static

RateLimiter

create(double permitsPerSecond)

根據指定的穩定吞吐率創建RateLimiter,這裏的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少查詢)

static

RateLimiter

create(double permitsPerSecond, long warmupPeriod, TimeUnit unit)

根據指定的穩定吞吐率和預熱期來創建RateLimiter,這裏的吞吐率是指每秒多少許可數(通常是指QPS,每秒多少個請求量),在這段預熱時間內,RateLimiter每秒分配的許可數回平穩的增長知道預熱期結束時達到其最大速率。(只要存在足夠請求數來使其飽和)

double

getRate()

返回RateLimiter配置中的穩定速率,該速率單位是每秒多少許可數

void

setRate(double permitsPerSecond)

更新RateLimite的穩定速率,參數permit是PerSecond由構造RateLimiter的工廠方法提供。

String

toString()

返回對象的字符表現形式

boolean

tryAcquire()

從RateLimiter獲取許可,如果該許可在無延遲下的情況下立即獲取得到的話

boolean

tryAcquire(int permits)

從RateLimiter獲取許可數,如果該許可數可以在無延遲的情況下立即獲取得到的話

boolean

tryAcquire(int permits, long timeout, TimeUnit unit)

從RateLimiter獲取指定許可數如果該許可數可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout過期之前獲取得到許可數的話,那麼立即返回false(無需等待)

boolean

tryAcquire(long timeout, TimeUnit unit)

從RateLimiter獲取許可如果該許可可以不超過timeout的時間內獲取得到的話,或者如果無法在timeout過期之前獲取得到許可的話,那麼立即返回false(無需等待)

 

二、應用示例

需求:我們需要將流量限制在5QPS範圍內,爲了這個目的我們來看看該怎麼弄。

定義一個interface,AccessLimitService:

public interface AccessLimitService {

    boolean tryAcquire();

    double getRate();

}

 

定義實現類AccessLimitServiceImpl:

@Service
public class AccessLimitServiceImpl implements AccessLimitService {

    //每秒發出5個令牌
    private RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 嘗試獲取令牌
     * @Return
     */
    public boolean tryAcquire() {
        //這裏使用的是tryAcquire而不是acquire方法,快速失敗避免耗盡所有資源
        return rateLimiter.tryAcquire(); 
    }

    public double getRate() {
        return rateLimiter.getRate();
    }
}

接口類:


@Slf4j
@RestController
@RequestMapping("/flow/limit")
public class LimitController extends BaseController {
    
    @Autowired
    private AccessLimitService accessLimitService;

    @GetMapping(value = "/get")
    public String getInfo() {

        return "ok";
    }

    @GetMapping(value = "/access")
    public String access() {
        //嘗試獲取令牌,如果沒有請求到立即返回false
        if (accessLimitService.tryAcquire()) {
            return "access success [" + System.currentTimeMillis() + "]";
        } else {
            System.out.println(accessLimitService.getRate());

            return "access limit [" + System.currentTimeMillis() + "]";

        }
    }
}

單元測試:

import com.google.common.util.concurrent.RateLimiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;


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

public class FlowLimitTest {

    private RestTemplate restTemplate;

    private String ip_dev = "localhost:8101";

    private RateLimiter rateLimiter;

    ExecutorService fixedThreadPool;


    @Before
    public void setup() {
        restTemplate = new RestTemplate();
        ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setConnectTimeout(60000);
        ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setReadTimeout(60000);

    }

    @After
    public void destroy() {
    }

    @Test
    public void flowLimitTest() throws Exception {

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

        /**
         * access success [1557057930408]
         access limit [1557057930399]
         access limit [1557057930399]
         access success [1557057930409]
         access success [1557057930408]
         access success [1557057930409]
         access success [1557057930408]
         access limit [1557057930399]
         access success [1557057930409]
         access limit [1557057930399]
         */
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(requestApi());
                }
            });
        }

        fixedThreadPool.shutdown();
        fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
    }

    private String requestApi() {
        StringBuilder url = new StringBuilder();

        url.append("http://").append(ip_dev).append("/flow/limit/access");

        HttpEntity<String> httpEntity = new HttpEntity<String>(null, null);
        ResponseEntity<String> result = null;

        try {
            result = restTemplate.exchange(url.toString(), HttpMethod.GET, httpEntity, String.class);
            if (result.getStatusCode().equals(HttpStatus.OK)) {

                return result.getBody();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

可以看到結果,10個請求,6個通過,4個被限制,通過進一步測試發現create大小爲n的流量,但是在併發的過程中實際可以通過n+1個請求,其他的則被限制:

access success [1557057930408]
access limit [1557057930399]
access limit [1557057930399]
access success [1557057930409]
access success [1557057930408]
access success [1557057930409]
access success [1557057930408]
access limit [1557057930399]
access success [1557057930409]
access limit [1557057930399]

    RateLimiter是比較常用的限流的服務,但是在微服務的場景中只有限流是不夠的,同時還要保證在下游系統不可用的時,本身系統的可用性,那麼就要考慮到熔斷和降級了,後面將說到這些。

 

Author:憶之獨秀

Email:[email protected]

註明出處:https://blog.csdn.net/lavorange/article/details/95742831

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章