国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

分布式限流,你想知道的都在這里

網絡 通信技術 分布式
在一個高并發系統中對流量的把控是非常重要的,當巨大的流量直接請求到我們的服務器上沒多久就可能造成接口不可用,不處理的話甚至會造成整個應用不可用。

前言

在一個高并發系統中對流量的把控是非常重要的,當巨大的流量直接請求到我們的服務器上沒多久就可能造成接口不可用,不處理的話甚至會造成整個應用不可用。

比如最近就有個這樣的需求,我作為客戶端要向kafka生產數據,而kafka的消費者則再源源不斷的消費數據,并將消費的數據全部請求到web服務器,雖說做了負載(有4臺web服務器)但業務數據的量也是巨大的,每秒鐘可能有上萬條數據產生。如果生產者直接生產數據的話極有可能把web服務器拖垮。

[[263347]]

對此就必須要做限流處理,每秒鐘生產一定限額的數據到kafka,這樣就能極大程度的保證web的正常運轉。

其實不管處理何種場景,本質都是降低流量保證應用的高可用。

常見算法

對于限流常見有兩種算法:

  • 漏桶算法
  • 令牌桶算法

漏桶算法比較簡單,就是將流量放入桶中,漏桶同時也按照一定的速率流出,如果流量過快的話就會溢出(漏桶并不會提高流出速率)。溢出的流量則直接丟棄。

如下圖所示:

分布式限流,你想知道的都在這里

這種做法簡單粗暴。

漏桶算法雖說簡單,但卻不能應對實際場景,比如突然暴增的流量。

這時就需要用到令牌桶算法:

令牌桶會以一個恒定的速率向固定容量大小桶中放入令牌,當有流量來時則取走一個或多個令牌。當桶中沒有令牌則將當前請求丟棄或阻塞。

相比之下令牌桶可以應對一定的突發流量。

RateLimiter實現

對于令牌桶的代碼實現,可以直接使用Guava包中的RateLimiter。

  1. @Override 
  2. public BaseResponse<UserResVO> getUserByFeignBatch(@RequestBody UserReqVO userReqVO) { 
  3.  //調用遠程服務 
  4.  OrderNoReqVO vo = new OrderNoReqVO() ; 
  5.  vo.setReqNo(userReqVO.getReqNo()); 
  6.  RateLimiter limiter = RateLimiter.create(2.0) ; 
  7.  //批量調用 
  8.  for (int i = 0 ;i< 10 ; i++){ 
  9.  double acquire = limiter.acquire(); 
  10.  logger.debug("獲取令牌成功!,消耗=" + acquire); 
  11.  BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo); 
  12.  logger.debug("遠程返回:"+JSON.toJSONString(orderNo)); 
  13.  } 
  14.  UserRes userRes = new UserRes() ; 
  15.  userRes.setUserId(123); 
  16.  userRes.setUserName("張三"); 
  17.  userRes.setReqNo(userReqVO.getReqNo()); 
  18.  userRes.setCode(StatusEnum.SUCCESS.getCode()); 
  19.  userRes.setMessage("成功"); 
  20.  return userRes ; 

詳見此。

調用結果如下:

代碼可以看出以每秒向桶中放入兩個令牌,請求一次消耗一個令牌。所以每秒鐘只能發送兩個請求。按照圖中的時間來看也確實如此(返回值是獲取此令牌所消耗的時間,差不多也是每500ms一個)。

使用RateLimiter有幾個值得注意的地方:

允許先消費,后付款,意思就是它可以來一個請求的時候一次性取走幾個或者是剩下所有的令牌甚至多取,但是后面的請求就得為上一次請求買單,它需要等待桶中的令牌補齊之后才能繼續獲取令牌。

總結

針對于單個應用的限流 RateLimiter 夠用了,如果是分布式環境可以借助 Redis 來完成。

來做演示。

在 Order 應用提供的接口中采取了限流。首先是配置了限流工具的 Bean:

  1. @Configuration 
  2. public class RedisLimitConfig { 
  3.  @Value("${redis.limit}"
  4.  private int limit; 
  5.  @Autowired 
  6.  private JedisConnectionFactory jedisConnectionFactory; 
  7.  @Bean 
  8.  public RedisLimit build() { 
  9.  RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection(); 
  10.  JedisCluster jedisCluster = (JedisCluster) clusterConnection.getNativeConnection(); 
  11.  RedisLimit redisLimit = new RedisLimit.Builder<>(jedisCluster) 
  12.  .limit(limit) 
  13.  .build(); 
  14.  return redisLimit; 
  15.  } 

接著在 Controller 使用組件:

  1. @Autowired 
  2. private RedisLimit redisLimit ; 
  3. @Override 
  4. @CheckReqNo 
  5. public BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) { 
  6.  BaseResponse<OrderNoResVO> res = new BaseResponse(); 
  7.  //限流 
  8.  boolean limit = redisLimit.limit(); 
  9.  if (!limit){ 
  10.  res.setCode(StatusEnum.REQUEST_LIMIT.getCode()); 
  11.  res.setMessage(StatusEnum.REQUEST_LIMIT.getMessage()); 
  12.  return res ; 
  13.  } 
  14.  res.setReqNo(orderNoReq.getReqNo()); 
  15.  if (null == orderNoReq.getAppId()){ 
  16.  throw new SBCException(StatusEnum.FAIL); 
  17.  } 
  18.  OrderNoResVO orderNoRes = new OrderNoResVO() ; 
  19.  orderNoRes.setOrderId(DateUtil.getLongTime()); 
  20.  res.setCode(StatusEnum.SUCCESS.getCode()); 
  21.  res.setMessage(StatusEnum.SUCCESS.getMessage()); 
  22.  res.setDataBody(orderNoRes); 
  23.  return res ; 

為了方便使用,也提供了注解:

  1. @Override 
  2. @ControllerLimit 
  3. public BaseResponse<OrderNoResVO> getOrderNoLimit(@RequestBody OrderNoReqVO orderNoReq) { 
  4.  BaseResponse<OrderNoResVO> res = new BaseResponse(); 
  5.  // 業務邏輯 
  6.  return res ; 

該注解攔截了 http 請求,會再請求達到閾值時直接返回。

普通方法也可使用:

  1. @CommonLimit 
  2. public void doSomething(){} 

會在調用達到閾值時拋出異常。

為了模擬并發,在 User 應用中開啟了 10 個線程調用 Order(限流次數為5) 接口(也可使用專業的并發測試工具 JMeter 等)。

  1. @Override 
  2. public BaseResponse<UserResVO> getUserByFeign(@RequestBody UserReqVO userReq) { 
  3.  //調用遠程服務 
  4.  OrderNoReqVO vo = new OrderNoReqVO(); 
  5.  vo.setAppId(1L); 
  6.  vo.setReqNo(userReq.getReqNo()); 
  7.  for (int i = 0; i < 10; i++) { 
  8.  executorService.execute(new Worker(vo, orderServiceClient)); 
  9.  } 
  10.  UserRes userRes = new UserRes(); 
  11.  userRes.setUserId(123); 
  12.  userRes.setUserName("張三"); 
  13.  userRes.setReqNo(userReq.getReqNo()); 
  14.  userRes.setCode(StatusEnum.SUCCESS.getCode()); 
  15.  userRes.setMessage("成功"); 
  16.  return userRes; 
  17. private static class Worker implements Runnable { 
  18.  private OrderNoReqVO vo; 
  19.  private OrderServiceClient orderServiceClient; 
  20.  public Worker(OrderNoReqVO vo, OrderServiceClient orderServiceClient) { 
  21.  this.vo = vo; 
  22.  this.orderServiceClient = orderServiceClient; 
  23.  } 
  24.  @Override 
  25.  public void run() { 
  26.  BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNoCommonLimit(vo); 
  27.  logger.info("遠程返回:" + JSON.toJSONString(orderNo)); 
  28.  } 

為了驗證分布式效果啟動了兩個 Order 應用。

效果如下:

實現原理

實現原理其實很簡單。既然要達到分布式全局限流的效果,那自然需要一個第三方組件來記錄請求的次數。

其中 Redis 就非常適合這樣的場景。

  • 每次請求時將當前時間(精確到秒)作為 Key 寫入到 Redis 中,超時時間設置為 2 秒,Redis 將該 Key 的值進行自增。
  • 當達到閾值時返回錯誤。
  • 寫入 Redis 的操作用 Lua 腳本來完成,利用 Redis 的單線程機制可以保證每個 Redis 請求的原子性。

Lua 腳本如下:

--lua 下標從 1 開始-- 限流 keylocal key = KEYS[1]-- 限流大小local limit = tonumber(ARGV[1])-- 獲取當前流量大小local curentLimit = tonumber(redis.call('get', key) or "0")if curentLimit + 1 > limit then -- 達到限流大小 返回 return 0;else -- 沒有達到閾值 value + 1 redis.call("INCRBY", key, 1) redis.call("EXPIRE", key, 2) return curentLimit + 1end

Java 中的調用邏輯:

  1. --lua 下標從 1 開始 
  2. -- 限流 key 
  3. local key = KEYS[1] 
  4. -- 限流大小 
  5. local limit = tonumber(ARGV[1]) 
  6. -- 獲取當前流量大小 
  7. local curentLimit = tonumber(redis.call('get'keyor "0"
  8. if curentLimit + 1 > limit then 
  9.  -- 達到限流大小 返回 
  10.  return 0; 
  11. else 
  12.  -- 沒有達到閾值 value + 1 
  13.  redis.call("INCRBY"key, 1) 
  14.  redis.call("EXPIRE"key, 2) 
  15.  return curentLimit + 1 
  16. end 

所以只需要在需要限流的地方調用該方法對返回值進行判斷即可達到限流的目的。

當然這只是利用 Redis 做了一個粗暴的計數器,如果想實現類似于上文中的令牌桶算法可以基于 Lua 自行實現。

Builder 構建器

在設計這個組件時想盡量的提供給使用者清晰、可讀性、不易出錯的 API。

比如***步,如何構建一個限流對象。

最常用的方式自然就是構造函數,如果有多個域則可以采用重疊構造器的方式:

  1. public A(){} 
  2. public A(int a){} 
  3. public A(int a,int b){} 

缺點也是顯而易見的:如果參數過多會導致難以閱讀,甚至如果參數類型一致的情況下客戶端顛倒了順序,但不會引起警告從而出現難以預測的結果。

第二種方案可以采用 JavaBean 模式,利用 setter 方法進行構建:

  1. A a = new A(); 
  2. a.setA(a); 
  3. a.setB(b); 

這種方式清晰易讀,但卻容易讓對象處于不一致的狀態,使對象處于線程不安全的狀態。

所以這里采用了第三種創建對象的方式,構建器:

  1. public class RedisLimit { 
  2.  private JedisCommands jedis; 
  3.  private int limit = 200; 
  4.  private static final int FAIL_CODE = 0; 
  5.  /** 
  6.  * lua script 
  7.  */ 
  8.  private String script; 
  9.  private RedisLimit(Builder builder) { 
  10.  this.limit = builder.limit ; 
  11.  this.jedis = builder.jedis ; 
  12.  buildScript(); 
  13.  } 
  14.  /** 
  15.  * limit traffic 
  16.  * @return if true 
  17.  */ 
  18.  public boolean limit() { 
  19.  String key = String.valueOf(System.currentTimeMillis() / 1000); 
  20.  Object result = null
  21.  if (jedis instanceof Jedis) { 
  22.  result = ((Jedis) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit))); 
  23.  } else if (jedis instanceof JedisCluster) { 
  24.  result = ((JedisCluster) this.jedis).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit))); 
  25.  } else { 
  26.  //throw new RuntimeException("instance is error") ; 
  27.  return false
  28.  } 
  29.  if (FAIL_CODE != (Long) result) { 
  30.  return true
  31.  } else { 
  32.  return false
  33.  } 
  34.  } 
  35.  /** 
  36.  * read lua script 
  37.  */ 
  38.  private void buildScript() { 
  39.  script = ScriptUtil.getScript("limit.lua"); 
  40.  } 
  41.  /** 
  42.  * the builder 
  43.  * @param <T> 
  44.  */ 
  45.  public static class Builder<T extends JedisCommands>{ 
  46.  private T jedis = null ; 
  47.  private int limit = 200; 
  48.  public Builder(T jedis){ 
  49.  this.jedis = jedis ; 
  50.  } 
  51.  public Builder limit(int limit){ 
  52.  this.limit = limit ; 
  53.  return this; 
  54.  } 
  55.  public RedisLimit build(){ 
  56.  return new RedisLimit(this) ; 
  57.  } 
  58.  } 

這樣客戶端在使用時:

  1. RedisLimit redisLimit = new RedisLimit.Builder<>(jedisCluster) 
  2.  .limit(limit) 
  3.  .build(); 

更加的簡單直接,并且避免了將創建過程分成了多個子步驟。

這在有多個構造參數,但又不是必選字段時很有作用。

因此順便將分布式鎖的構建器方式也一并更新了:

https://github.com/crossoverJie/distributed-redis-tool#features

API

從上文可以看出,使用過程就是調用 limit 方法。

  1. //限流 
  2.  boolean limit = redisLimit.limit(); 
  3.  if (!limit){ 
  4.  //具體限流邏輯 
  5.  } 

為了減少侵入性,也為了簡化客戶端提供了兩種注解方式。

@ControllerLimit

該注解可以作用于 @RequestMapping 修飾的接口中,并會在限流后提供限流響應。

實現如下:

  1. @Component 
  2. public class WebIntercept extends WebMvcConfigurerAdapter { 
  3.  private static Logger logger = LoggerFactory.getLogger(WebIntercept.class); 
  4.  @Autowired 
  5.  private RedisLimit redisLimit; 
  6.  @Override 
  7.  public void addInterceptors(InterceptorRegistry registry) { 
  8.  registry.addInterceptor(new CustomInterceptor()) 
  9.  .addPathPatterns("/**"); 
  10.  } 
  11.  private class CustomInterceptor extends HandlerInterceptorAdapter { 
  12.  @Override 
  13.  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
  14.  Object handler) throws Exception { 
  15.  if (redisLimit == null) { 
  16.  throw new NullPointerException("redisLimit is null"); 
  17.  } 
  18.  if (handler instanceof HandlerMethod) { 
  19.  HandlerMethod method = (HandlerMethod) handler; 
  20.  ControllerLimit annotation = method.getMethodAnnotation(ControllerLimit.class); 
  21.  if (annotation == null) { 
  22.  //skip 
  23.  return true
  24.  } 
  25.  boolean limit = redisLimit.limit(); 
  26.  if (!limit) { 
  27.  logger.warn("request has bean limit"); 
  28.  response.sendError(500, "request limit"); 
  29.  return false
  30.  } 
  31.  } 
  32.  return true
  33.  } 
  34.  } 

其實就是實現了 SpringMVC 中的攔截器,并在攔截過程中判斷是否有使用注解,從而調用限流邏輯。

前提是應用需要掃描到該類,讓 Spring 進行管理。

  1. @ComponentScan(value = "com.crossoverjie.distributed.intercept"

@CommonLimit

當然也可以在普通方法中使用。實現原理則是 Spring AOP (SpringMVC 的攔截器本質也是 AOP)。

  1. @Aspect 
  2. @Component 
  3. @EnableAspectJAutoProxy(proxyTargetClass = true
  4. public class CommonAspect { 
  5.  private static Logger logger = LoggerFactory.getLogger(CommonAspect.class); 
  6.  @Autowired 
  7.  private RedisLimit redisLimit ; 
  8.  @Pointcut("@annotation(com.crossoverjie.distributed.annotation.CommonLimit)"
  9.  private void check(){} 
  10.  @Before("check()"
  11.  public void before(JoinPoint joinPoint) throws Exception { 
  12.  if (redisLimit == null) { 
  13.  throw new NullPointerException("redisLimit is null"); 
  14.  } 
  15.  boolean limit = redisLimit.limit(); 
  16.  if (!limit) { 
  17.  logger.warn("request has bean limit"); 
  18.  throw new RuntimeException("request has bean limit") ; 
  19.  } 
  20.  } 

很簡單,也是在攔截過程中調用限流。

當然使用時也得掃描到該包:

  1. @ComponentScan(value = "com.crossoverjie.distributed.intercept"

總結

限流在一個高并發大流量的系統中是保護應用的一個利器,成熟的方案也很多,希望對剛了解這一塊的朋友提供一些思路。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2023-09-11 08:51:23

LinkedList雙向鏈表線程

2021-06-17 13:40:47

區塊鏈比特幣公有鏈

2019-11-04 09:07:48

DevOps互聯網IT

2020-03-18 18:20:19

區塊鏈數字貨幣比特幣

2018-11-28 10:39:01

5G網絡運營商

2018-03-31 08:45:52

iPhone交通卡iOS 11.3

2019-04-26 09:38:36

中臺平臺化轉型

2021-07-01 09:00:00

安全數字化轉型滲透

2022-11-08 15:55:34

鴻蒙開發套件

2021-07-02 14:09:36

開發技能代碼

2017-01-11 08:37:07

Apache SparStreamingDataFrames

2017-08-15 15:35:21

大數據數據分析薪資秘密

2017-08-15 16:05:18

大數據數據分析薪資秘密

2017-12-13 14:24:08

Google 開發者瀏覽器

2015-10-12 15:50:40

2025-05-27 03:22:00

分布式ID部署

2018-05-10 08:50:31

AndroidGoogle 移動系統

2019-10-29 15:28:40

Refs組件前端

2018-08-23 11:58:53

區塊鏈數字貨幣比特幣

2022-09-15 14:22:19

協作規范前后端
點贊
收藏

51CTO技術棧公眾號

亚洲成人网在线观看| 欧美日韩成人在线视频| 国产老熟妇精品观看| 91tv官网精品成人亚洲| 国产丝袜精品第一页| 国产69精品久久久久孕妇| 国产精品中文字幕日韩精品| 成人啪啪免费看| 日韩视频免费看| 色吊丝在线永久观看最新版本| 国产一区二区三区精品欧美日韩一区二区三区 | 2021年精品国产福利在线| 黄色激情在线播放| 国产精品中文欧美| 精品成人免费观看| 一级片在线播放| caoprom在线| 狠狠色噜噜狠狠狠狠97| 鲁一鲁一鲁一鲁一色| 极品少妇xxxx精品少妇| 久久精品99| 欧美特黄一区| 国产精品一二三在线| 久久影视三级福利片| 亚洲国产私拍精品国模在线观看| 每日更新在线观看av| 亚洲免费观看视频| 色爱综合网站| 欧美日韩亚洲在线观看| 久久伊人色综合| 日韩性xxx| 亚洲网址在线观看| 亚洲深夜av| 国产成人亚洲欧美| 99久久精品国产亚洲精品| 欧美又大粗又爽又黄大片视频| 日韩精品久久久久久久软件91 | 51xx午夜影福利| 久久人人97超碰国产公开结果| 国产精品yjizz| 国产一区视频在线观看免费| 91蜜桃网站免费观看| 欧美黄色aaaa| 国产亚洲精品美女久久久m| 午夜久久影院| 韩国精品一区二区三区六区色诱| 小嫩嫩12欧美| 国产91在线视频| 亚州av乱码久久精品蜜桃| 成人午夜小视频| 亚洲欧洲日本mm| 色就是色欧美| 岛国精品一区二区| 国产xxxxx视频| 一区二区三区欧美日| 欧美美女搞黄| 亚洲第一福利在线观看| 国产精品4hu.www| 久久久爽爽爽美女图片| 久久不见久久见中文字幕免费| 成人欧美在线视频| 视频一区在线播放| 伊人成色综合网| 一区二区三区蜜桃| 麻豆系列在线观看| 亚洲日本aⅴ片在线观看香蕉| 亚洲精品tv| 欧美一区二区三区图| 欧美男gay| 日本一区二区三区视频在线观看| 久久久国产精品不卡| 污污网站免费看| 国产精品亲子伦对白| 午夜在线观看av| 亚洲二区在线观看| 91制片在线观看| 国产精品视频久久| 黄色网免费看| 成人性生交大片免费看96| 91精品国产电影| 亚洲品质视频自拍网| 网友自拍一区| 中文字幕在线中文字幕日亚韩一区| 久久久久久久久久久久久女国产乱| 快射av在线播放一区| 欧美日韩精品专区| 日韩在线高清| 亚洲精品一区二区三区樱花| 日韩精品一级中文字幕精品视频免费观看| 国产精品国产三级国产有无不卡| 川上优的av在线一区二区| 成人手机在线视频| 国产精品国产三级国产三级人妇 | 欧美精品少妇| 亚洲欧美激情插| 永久www成人看片| 亚洲激情在线观看| 欧美舌奴丨vk视频| 在线影院国内精品| 国产第一页在线| 水蜜桃一区二区| 亚洲人成小说网站色在线| 91欧美极品| 99超碰麻豆| 国产美女在线观看一区| 濑亚美莉一二区在线视频| 久久免费在线观看| 黑丝美女一区二区| 久久国产手机看片| 91丨九色porny丨蝌蚪| 最近免费观看高清韩国日本大全| 免费观看久久久4p| 欧美日韩精品久久久免费观看| 国产精品综合视频| 日韩欧美视频免费在线观看| 一区二区三区日韩精品视频| 一区视频网站| aa在线免费观看| 久久精品国产91精品亚洲| 国产国产一区| 2020中文字幕在线| 免费电影一区| 成人两性免费视频| 国产精品都在这里| 豆国产97在线| 亚洲理论在线a中文字幕| 国产一区二区在线影院| www.成人在线视频| 一级在线免费观看| 大陆极品少妇内射aaaaa| 成人国产精品av| 精品国产123| 久久久久久影视| 精品动漫av| 国产免费拔擦拔擦8x高清在线人| 亚洲国产欧美日韩| 97碰碰碰免费色视频| 欧美自拍丝袜亚洲| 一区二区三区国产在线| 免费99热在线观看| 久久久这里只有精品视频| 国产亚洲视频在线观看| 成人午夜av电影| 国产精品美女| 日本一区二区免费在线| 国产精品自拍网站| 国产激情91久久精品导航| 国产主播一区二区三区| 国产欧美日韩在线看| 国产亚洲欧美日韩俺去了| 美女毛片一区二区三区四区| 久久精品欧洲| 久久亚洲一级片| 成人在线一区二区三区| 在线视频你懂得一区二区三区| 亚洲欧洲在线视频| 韩国视频理论视频久久| 最新欧美日韩亚洲| 69久久久久久| 国产乱对白刺激视频不卡| 日韩av片免费在线观看| 欧美一区日韩一区| 亚洲曰韩产成在线| 国产精品久线在线观看| 久久亚洲综合| 亚洲高清电影| 久久综合久久鬼色中文字| 午夜精品一区二区三区免费视频| 日韩一级完整毛片| 国产一区二区三区中文| 欧美劲爆第一页| 大桥未久一区二区三区| 日本视频在线| 尤物在线网址| 亚洲美女主播视频免费观看| 四虎精品在永久在线观看 | 久久国产精品区| 国产精品一区二区果冻传媒| 亚洲图片一区二区| 久久综合久久八八| 国产精品二区在线观看| 农民人伦一区二区三区| 成人在线免费看片| 性xxxx丰满孕妇xxxx另类| 日本在线视频www鲁啊鲁| 亚洲丁香日韩| 神马电影久久| 日韩中文视频| 成人不卡视频| 国产精品毛片aⅴ一区二区三区| 亚洲精品**中文毛片| 日本午夜大片a在线观看| 高潮在线视频| 国产美女亚洲精品7777| www.一区| 999在线观看精品免费不卡网站| 亚洲一二三区在线观看| 成视人a免费观看视频| 国产九色porn网址| 久久av中文字幕|