日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot - 优雅的实现【流控】

發布時間:2025/3/21 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot - 优雅的实现【流控】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 概述
  • 限流算法
    • 計數器限流
    • 漏桶算法
    • 令牌桶算法
  • V1.0
  • V2.0 自定義注解+AOP實現接口限流
    • 搞依賴
    • 搞自定義限流注解
    • 搞AOP
    • 用上驗證
  • 源碼


概述

限流 簡言之就是當請求達到一定的并發數或速率,就對服務進行等待、排隊、降級、拒絕服務等操作。

限流算法

我們先簡單捋一捋限流算法

并發編程-25 高并發處理手段之消息隊列思路 + 應用拆分思路 + 應用限流思路

深入理解分布式技術 - 限流

計數器限流

漏桶算法

把水比作是請求,漏桶比作是系統處理能力極限,水先進入到漏桶里,漏桶里的水按一定速率流出,當流出的速率小于流入的速率時,由于漏桶容量有限,后續進入的水直接溢出(拒絕請求),以此實現限流


令牌桶算法

可以簡單地理解為醫去銀行辦理業務,只有拿到號以后才可以進行業務辦理。

系統會維護一個令牌(token)桶,以一個恒定的速度往桶里放入令牌(token),這時如果有請求進來想要被處理,則需要先從桶里獲取一個令牌(token),當桶里沒有令牌(token)可取時,則該請求將被拒絕服務。令牌桶算法通過控制桶的容量、發放令牌的速率,來達到對請求的限制。


V1.0

上 guava

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version></dependency> package com.artisan.controller;import com.artisan.annos.ArtisanLimit; import com.google.common.util.concurrent.RateLimiter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.TimeUnit;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j @RestController @RequestMapping("/rateLimit") public class RateLimitController {/*** 限流策略 : 1秒鐘1個請求*/private final RateLimiter limiter = RateLimiter.create(1);private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@SneakyThrows@GetMapping("/test")public String testLimiter() {//500毫秒內,沒拿到令牌,就直接進入服務降級boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);if (!tryAcquire) {log.warn("BOOM 服務降級,時間{}", LocalDateTime.now().format(dtf));return "系統繁忙,請稍后再試!";}log.info("獲取令牌成功,時間{}", LocalDateTime.now().format(dtf));return "業務處理成功";}

我們可以看到RateLimiter的2個核心方法:create()、tryAcquire()

  • acquire() 獲取一個令牌, 改方法會阻塞直到獲取到這一個令牌, 返回值為獲取到這個令牌花費的時間
  • acquire(int permits) 獲取指定數量的令牌, 該方法也會阻塞, 返回值為獲取到這 N 個令牌花費的時間
  • tryAcquire() 判斷時候能獲取到令牌, 如果不能獲取立即返回 false
  • tryAcquire(int permits) 獲取指定數量的令牌, 如果不能獲取立即返回 false
  • tryAcquire(long timeout, TimeUnit unit) 判斷能否在指定時間內獲取到令牌, 如果不能獲取立即返回 false
  • tryAcquire(int permits, long timeout, TimeUnit unit) 同上

測試一下


V2.0 自定義注解+AOP實現接口限流

1.0的功能實現了,但是業務代碼和限流代碼混在一起,非常的不美觀。

搞依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

搞自定義限流注解

package com.artisan.annos;import java.lang.annotation.*; import java.util.concurrent.TimeUnit;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface ArtisanLimit {/*** 資源的key,唯一* 作用:不同的接口,不同的流量控制*/String key() default "";/*** 最多的訪問限制次數*/double permitsPerSecond();/*** 獲取令牌最大等待時間*/long timeout();/*** 獲取令牌最大等待時間,單位(例:分鐘/秒/毫秒) 默認:毫秒*/TimeUnit timeunit() default TimeUnit.MILLISECONDS;/*** 得不到令牌的提示語*/String message() default "系統繁忙,請稍后再試."; }

搞AOP

使用AOP切面攔截限流注解

package com.artisan.aop;import com.artisan.annos.ArtisanLimit; import com.artisan.resp.ResponseCode; import com.artisan.resp.ResponseData; import com.artisan.utils.WebUtils; import com.google.common.collect.Maps; import com.google.common.util.concurrent.RateLimiter; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.Map;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/@Slf4j @Aspect @Component public class ArtisanLimitAop {/*** 不同的接口,不同的流量控制* map的key為 ArtisanLimit.key*/private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();@Around("@annotation(com.artisan.annos.ArtisanLimit)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();//拿ArtisanLimit的注解ArtisanLimit limit = method.getAnnotation(ArtisanLimit.class);if (limit != null) {//key作用:不同的接口,不同的流量控制String key = limit.key();RateLimiter rateLimiter = null;//驗證緩存是否有命中keyif (!limitMap.containsKey(key)) {// 創建令牌桶rateLimiter = RateLimiter.create(limit.permitsPerSecond());limitMap.put(key, rateLimiter);log.info("新建了令牌桶={},容量={}", key, limit.permitsPerSecond());}rateLimiter = limitMap.get(key);// 拿令牌boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());// 拿不到命令,直接返回異常提示if (!acquire) {log.warn("令牌桶={},獲取令牌失敗", key);this.responseFail(limit.message());return null;}}return joinPoint.proceed();}/*** 直接向前端拋出異常** @param msg 提示信息*/private void responseFail(String msg) {HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();ResponseData<Object> resultData = ResponseData.fail(ResponseCode.LIMIT_ERROR.getCode(), msg);WebUtils.writeJson(response, resultData);}}

用上驗證

@GetMapping("/test2")@ArtisanLimit(key = "testLimit2", permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS, message = "test2 當前排隊人數較多,請稍后再試!")public String test2() {log.info("令牌桶test2獲取令牌成功");return "test2 ok";}


源碼

https://github.com/yangshangwei/boot2

總結

以上是生活随笔為你收集整理的SpringBoot - 优雅的实现【流控】的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。