使用Guava實現限流器

爲什麼需要限流?

在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。限流可以認爲服務降級的一種,限流通過限制請求的流量以達到保護系統的目的。


一般來說,系統的吞吐量是可以計算出一個閾值的,爲了保證系統的穩定運行,一旦達到這個閾值,就需要限制流量並採取一些措施以完成限制流量的目的。比如:延遲處理,拒絕處理,或者部分拒絕處理等等。否則,很容易導致服務器的宕機。


現有的方案

Google的Guava工具包中就提供了一個限流工具類——RateLimiter,本文也是通過使用該工具類來實現限流功能。RateLimiter是基於“令牌通算法”來實現限流的。


令牌桶算法

令牌桶算法是一個存放固定容量令牌(token)的桶,按照固定速率往桶裏添加令牌。令牌桶算法基本可以用下面的幾個概念來描述:

  1. 假如用戶配置的平均發送速率爲r,則每隔1/r秒一個令牌被加入到桶中。

  2. 桶中最多存放b個令牌,當桶滿時,新添加的令牌被丟棄或拒絕。

  3. 當一個n個字節大小的數據包到達,將從桶中刪除n個令牌,接着數據包被髮送到網絡上。

  4. 如果桶中的令牌不足n個,則不會刪除令牌,且該數據包將被限流(要麼丟棄,要麼緩衝區等待)。


限流器實現

1.pom文件中引入Guava包

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>23.0</version>
</dependency>


2.自定義攔截器,並在攔截器中實現限流

a)定義一個攔截器抽象類,用於多個攔截器複用,主要是繼承HandlerInterceptorAdapter,重寫preHandle方法;並提供preFilter抽象方法,供子類實現。



b)定義流量控制攔截器,流量控制攔截器繼承自上面的攔截器抽象類,在preFilter方法中進行流量控制。

@Component("rateLimitInterceptor")
public class RateLimitInterceptor extends AbstractInterceptor {
   /**
    * 單機全侷限流器(限制QPS爲1)
    */

   private static final RateLimiter rateLimiter = RateLimiter.create(1);
   @Override
   protected ResponseEnum preFilter(HttpServletRequest request) {
       if (!rateLimiter.tryAcquire()) {
           System.out.println("限流中......");
           return ResponseEnum.RATE_LIMIT;
       }
       System.out.println("請求成功");
       return ResponseEnum.OK;
   }
}

使用Guava提供的RateLimiter類來實現流量控制,過程很簡單:定義了一個QPS爲1的全侷限流器(便於測試),使用tryAcquire()方法來嘗試獲取令牌,如果成功則返回ResponseEnum.OK,否則返回ResponseEnum.RATE_LIMIT。


3.繼承WebMvcConfigurerAdapter來添加自定義攔截器



4.寫一個Controller來提供一個簡單的訪問接口

@Controller
@RequestMapping("/user")
public class UserController {
   @Autowired
   private UserPOMapper userPOMapper;
   @RequestMapping("getUserList")
   @ResponseBody
   public ResponseDTO getUserList() {
       ResponseDTO responseDTO = new ResponseDTO();
       responseDTO.setCode(ResponseEnum.OK.getCode());
       UserPOExample userPOExample = new UserPOExample();
       UserPOExample.Criteria criteria = userPOExample.createCriteria();
       // and Name = 'admin'
       criteria.andNameEqualTo("admin");
       criteria.andPasswordEqualTo("admin");
       // and Age Between 24, 26
       criteria.andAgeBetween(24, 26);
       try {
           List<UserPO> userPOList = userPOMapper.selectByExample(userPOExample);
           responseDTO.setContent(userPOList);
           return responseDTO;
       } catch (Exception e) {
           responseDTO.setCode(ResponseEnum.QUERY_USER_FAILED.getCode());
           responseDTO.setMsg(ResponseEnum.QUERY_USER_FAILED.getMsg());
           return responseDTO;
       }
   }
}


上文使用到的ResponseEnum是一個返回Code的枚舉:



所有文件的目錄結構如下:



5.使用Postman來測試接口

快速並且反覆的調用接口,可以很容易的看到兩種結果。

成功通過限流器的結果:



沒有成功通過限流器的返回結果:



反覆調用時,Console輸出如下:



至此,簡單的限流器實現完成。


錯誤情況

如果在測試時,出現以下錯誤。



解決:在pom依賴中添加以下jar包即可解決。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.3</version>
</dependency>




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