欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot?實(shí)現(xiàn)流控的操作方法

 更新時(shí)間:2024年12月02日 11:13:33   作者:小小工匠  
本文介紹了限流算法的基本概念和常見(jiàn)的限流算法,包括計(jì)數(shù)器算法、漏桶算法和令牌桶算法,還介紹了如何在Spring?Boot中使用Guava庫(kù)和自定義注解以及AOP實(shí)現(xiàn)接口限流功能,感興趣的朋友一起看看吧

概述

限流 簡(jiǎn)言之就是當(dāng)請(qǐng)求達(dá)到一定的并發(fā)數(shù)或速率,就對(duì)服務(wù)進(jìn)行等待、排隊(duì)、降級(jí)、拒絕服務(wù)等操作。

限流算法

我們先簡(jiǎn)單捋一捋限流算法

Spring Boot接口限流的常用算法及特點(diǎn)

SpringBoot接口限流的實(shí)現(xiàn)方法小結(jié)

計(jì)數(shù)器限流

漏桶算法

把水比作是請(qǐng)求,漏桶比作是系統(tǒng)處理能力極限,水先進(jìn)入到漏桶里,漏桶里的水按一定速率流出,當(dāng)流出的速率小于流入的速率時(shí),由于漏桶容量有限,后續(xù)進(jìn)入的水直接溢出(拒絕請(qǐng)求),以此實(shí)現(xiàn)限流

令牌桶算法

可以簡(jiǎn)單地理解為醫(yī)去銀行辦理業(yè)務(wù),只有拿到號(hào)以后才可以進(jìn)行業(yè)務(wù)辦理。

系統(tǒng)會(huì)維護(hù)一個(gè)令牌(token)桶,以一個(gè)恒定的速度往桶里放入令牌(token),這時(shí)如果有請(qǐng)求進(jìn)來(lái)想要被處理,則需要先從桶里獲取一個(gè)令牌(token),當(dāng)桶里沒(méi)有令牌(token)可取時(shí),則該請(qǐng)求將被拒絕服務(wù)。令牌桶算法通過(guò)控制桶的容量、發(fā)放令牌的速率,來(lái)達(dá)到對(duì)請(qǐng)求的限制。

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個(gè)請(qǐng)求
     */
    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毫秒內(nèi),沒(méi)拿到令牌,就直接進(jìn)入服務(wù)降級(jí)
        boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);
        if (!tryAcquire) {
            log.warn("BOOM 服務(wù)降級(jí),時(shí)間{}", LocalDateTime.now().format(dtf));
            return "系統(tǒng)繁忙,請(qǐng)稍后再試!";
        }
        log.info("獲取令牌成功,時(shí)間{}", LocalDateTime.now().format(dtf));
        return "業(yè)務(wù)處理成功";
    }

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

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

測(cè)試一下

V2.0 自定義注解+AOP實(shí)現(xiàn)接口限流

1.0的功能實(shí)現(xiàn)了,但是業(yè)務(wù)代碼和限流代碼混在一起,非常的不美觀。

搞依賴

 <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 "";
    /**
     * 最多的訪問(wèn)限制次數(shù)
     */
    double permitsPerSecond();
    /**
     * 獲取令牌最大等待時(shí)間
     */
    long timeout();
    /**
     * 獲取令牌最大等待時(shí)間,單位(例:分鐘/秒/毫秒) 默認(rèn):毫秒
     */
    TimeUnit timeunit() default TimeUnit.MILLISECONDS;
    /**
     * 得不到令牌的提示語(yǔ)
     */
    String message() default "系統(tǒng)繁忙,請(qǐng)稍后再試.";
}

搞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;
            //驗(yàn)證緩存是否有命中key
            if (!limitMap.containsKey(key)) {
                // 創(chuàng)建令牌桶
                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);
    }
}

用上驗(yàn)證

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

源碼

https://github.com/yangshangwei/boot2

到此這篇關(guān)于SpringBoot 實(shí)現(xiàn)流控的操作方法的文章就介紹到這了,更多相關(guān)SpringBoot流控內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)OTP(動(dòng)態(tài)口令)服務(wù)

    Java實(shí)現(xiàn)OTP(動(dòng)態(tài)口令)服務(wù)

    OTP是一種動(dòng)態(tài)生成的短時(shí)有效密碼,用于身份驗(yàn)證,通常在登錄或執(zhí)行敏感操作時(shí)提供額外的安全保障,本文主要介紹了Java實(shí)現(xiàn)OTP(動(dòng)態(tài)口令)服務(wù),感興趣的可以了解一下
    2025-03-03
  • Java獲取指定字符串出現(xiàn)次數(shù)的方法

    Java獲取指定字符串出現(xiàn)次數(shù)的方法

    這篇文章主要為大家詳細(xì)介紹了Java獲取指定字符串出現(xiàn)次數(shù)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Windows10系統(tǒng)下修改jar中的文件并重新打包成jar文件然后運(yùn)行的操作步驟

    Windows10系統(tǒng)下修改jar中的文件并重新打包成jar文件然后運(yùn)行的操作步驟

    這篇文章主要介紹了Windows10系統(tǒng)下修改jar中的文件并重新打包成jar文件然后運(yùn)行的操作步驟,文中通過(guò)圖文結(jié)合的形式給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-08-08
  • 一文帶你掌握J(rèn)ava中Scanner類(lèi)的使用

    一文帶你掌握J(rèn)ava中Scanner類(lèi)的使用

    Scanner類(lèi)是java.util包中的一個(gè)類(lèi),常用于控制臺(tái)的輸入,當(dāng)需要使用控制臺(tái)輸入時(shí)即可調(diào)用這個(gè)類(lèi)。本文將通過(guò)一些簡(jiǎn)單的例子為大家介紹一下Java中Scanner類(lèi)的使用,需要的可以參考一下
    2023-04-04
  • Spring Boot容器加載時(shí)執(zhí)行特定操作(推薦)

    Spring Boot容器加載時(shí)執(zhí)行特定操作(推薦)

    這篇文章主要介紹了Spring Boot容器加載時(shí)執(zhí)行特定操作及spring內(nèi)置的事件,需要的朋友可以參考下
    2018-01-01
  • 在SpringBoot中整合數(shù)據(jù)源的示例詳解

    在SpringBoot中整合數(shù)據(jù)源的示例詳解

    這篇文章主要介紹了在SpringBoot中如何整合數(shù)據(jù)源,本文介紹了如何在SpringBoot項(xiàng)目中整合常見(jiàn)的數(shù)據(jù)源,包括JdbcTemplate、MyBatis和JPA,并探討了如何配置和使用多數(shù)據(jù)源,需要的朋友可以參考下
    2023-06-06
  • Mybatis的介紹、基本使用、高級(jí)使用

    Mybatis的介紹、基本使用、高級(jí)使用

    這篇文章主要介紹了Mybatis的介紹、基本使用、高級(jí)使用,Mybatis是一款半自動(dòng)的ORM持久層框架,具有較高的SQL靈活性,如何使用看這篇就夠了,需要的朋友可以參考下
    2023-03-03
  • 關(guān)于Maven依賴沖突解決之exclusions

    關(guān)于Maven依賴沖突解決之exclusions

    這篇文章主要介紹了關(guān)于Maven依賴沖突解決之exclusions用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java設(shè)計(jì)模式之策略模式示例詳解

    Java設(shè)計(jì)模式之策略模式示例詳解

    這篇文章主要為大家詳細(xì)介紹了Java的策略模式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-03-03
  • 解決Request.getParameter獲取不到特殊字符bug問(wèn)題

    解決Request.getParameter獲取不到特殊字符bug問(wèn)題

    這篇文章主要介紹了解決Request.getParameter獲取不到特殊字符bug問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07

最新評(píng)論