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

基于令牌桶的限流器注解的簡單實(shí)現(xiàn)詳解

 更新時(shí)間:2023年08月02日 08:31:18   作者:Source_J  
令牌桶算法是一種常用的流量控制算法,用于限制請求或事件的發(fā)生速率,這篇文章主要介紹了如何基于令牌桶實(shí)現(xiàn)限流器注解,需要的可以參考一下

一、原理

令牌桶算法是一種常用的流量控制算法,用于限制請求或事件的發(fā)生速率,以防止系統(tǒng)過載。它的原理是基于令牌桶的數(shù)據(jù)結(jié)構(gòu)和一定的令牌產(chǎn)生規(guī)則。

在令牌桶算法中,可以將令牌桶看作是一個(gè)具有固定容量的桶,以一定的速率產(chǎn)生令牌,并按照一定的規(guī)則進(jìn)行存放。每當(dāng)有請求或事件發(fā)生時(shí),首先需要從桶中獲取一個(gè)令牌,如果桶中沒有可用的令牌,則需要等待或丟棄請求。當(dāng)桶中的令牌數(shù)達(dá)到最大容量時(shí),產(chǎn)生的令牌也不會繼續(xù)增加。

具體工作原理如下:

令牌桶中有兩個(gè)關(guān)鍵參數(shù):令牌產(chǎn)生速率(token generation rate)和令牌容量(token bucket capacity)。

在每個(gè)固定時(shí)間間隔(例如,每秒),系統(tǒng)會向令牌桶中添加一定數(shù)量的令牌(即產(chǎn)生令牌),直到桶的容量達(dá)到最大值。

當(dāng)有請求或事件發(fā)生時(shí),需要先從令牌桶中獲取一個(gè)令牌。

  • 如果桶中有可用的令牌,則允許請求通過,并移除一個(gè)令牌。
  • 如果桶中沒有令牌,則需要等待,或者直接拒絕請求,這取決于具體的限流策略。

令牌桶算法的優(yōu)點(diǎn)在于可以對請求的速率進(jìn)行平滑的控制,且具備較好的適應(yīng)性,可以應(yīng)對突發(fā)流量。由于令牌的產(chǎn)生速率是固定的,因此可以精確控制系統(tǒng)的請求處理速率,防止系統(tǒng)的過載和資源耗盡。

在分布式系統(tǒng)中,令牌桶算法也常被用于實(shí)現(xiàn)分布式限流,保護(hù)后端服務(wù)免受過多請求的影響,確保系統(tǒng)的穩(wěn)定性和可靠性。

二、基于redis實(shí)現(xiàn)令牌桶算法

這個(gè)算法,設(shè)計(jì)的時(shí)候主要是考慮到要支持分布式系統(tǒng)的令牌桶資源共享,因此這樣設(shè)計(jì),下面就是具體的實(shí)戰(zhàn)代碼

首先是配置:

application.yml

server:
  port: 8081
spring:
 ?#數(shù)據(jù)庫連接配置
  datasource:
 ?  driver-class-name: com.mysql.cj.jdbc.Driver
 ?  url: jdbc:mysql://ip:3306/study?characterEncoding=utf-8&useSSL=false
 ?  username:
 ?  password:
?
  redis:
 ? ?# 地址
 ?  host:
 ? ?# 端口,默認(rèn)為6379
 ?  port: 6379
 ? ?# 數(shù)據(jù)庫索引
 ?  database: 8
 ? ?# 密碼
 ?  password:
 ? ?# 連接超時(shí)時(shí)間
 ?  timeout: 10s
?
#mybatis的相關(guān)配置
mybatis:
 ?#mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.zhg.demo.mybatis.entity
 ?#開啟駝峰命名
  configuration:
 ?  map-underscore-to-camel-case: true

maven依賴

<!-- ? ? ?  引入了AspectJ的運(yùn)行時(shí)庫-->
 ? ? ? ?<dependency>
 ? ? ? ? ? ?<groupId>org.aspectj</groupId>
 ? ? ? ? ? ?<artifactId>aspectjrt</artifactId>
 ? ? ? ? ? ?<version>1.9.7</version>
 ? ? ? ?</dependency>
?
 ? ? ? ?<!-- AspectJ編譯器,用于編譯切面 -->
 ? ? ? ?<dependency>
 ? ? ? ? ? ?<groupId>org.aspectj</groupId>
 ? ? ? ? ? ?<artifactId>aspectjtools</artifactId>
 ? ? ? ? ? ?<version>1.9.7</version>
 ? ? ? ? ? ?<scope>provided</scope>
 ? ? ? ?</dependency>
?
 ? ? ? ?<dependency>
 ? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
 ? ? ? ? ? ?<artifactId>spring-boot-starter-data-redis</artifactId>
 ? ? ? ? ? ?<optional>true</optional>
 ? ? ? ?</dependency>
?
 ? ? ? ?<!-- redis 緩存操作 -->
 ? ? ? ?<dependency>
 ? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
 ? ? ? ? ? ?<artifactId>spring-boot-starter-data-redis</artifactId>
 ? ? ? ? ? ?<version>2.5.14</version>
 ? ? ? ?</dependency>

redis配置

package com.jlstest.springbootdemo.config;
?
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
?
/**
 * redis配置
 *
 * @author admin
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
 ? ?@Bean
 ? ?@SuppressWarnings(value = { "unchecked", "rawtypes" })
 ? ?public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
 ?  {
 ? ? ? ?RedisTemplate<Object, Object> template = new RedisTemplate<>();
 ? ? ? ?template.setConnectionFactory(connectionFactory);
?
 ? ? ? ?FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
?
 ? ? ? ?// 使用StringRedisSerializer來序列化和反序列化redis的key值
 ? ? ? ?template.setKeySerializer(new StringRedisSerializer());
 ? ? ? ?template.setValueSerializer(serializer);
?
 ? ? ? ?// Hash的key也采用StringRedisSerializer的序列化方式
 ? ? ? ?template.setHashKeySerializer(new StringRedisSerializer());
 ? ? ? ?template.setHashValueSerializer(serializer);
?
 ? ? ? ?template.afterPropertiesSet();
 ? ? ? ?return template;
 ?  }
?
 ? ?@Bean
 ? ?public DefaultRedisScript<Long> limitScript()
 ?  {
 ? ? ? ?DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
 ? ? ? ?redisScript.setScriptText(limitScriptText());
 ? ? ? ?redisScript.setResultType(Long.class);
 ? ? ? ?return redisScript;
 ?  }
?
 ? ?/**
 ? ? * 限流腳本
 ? ? */
 ? ?private String limitScriptText()
 ?  {
 ? ? ? ?return "local key = KEYS[1]\n" +
 ? ? ? ? ? ? ? ?"local count = tonumber(ARGV[1])\n" +
 ? ? ? ? ? ? ? ?"local time = tonumber(ARGV[2])\n" +
 ? ? ? ? ? ? ? ?"local current = redis.call('get', key);\n" +
 ? ? ? ? ? ? ? ?"if current and tonumber(current) > count then\n" +
 ? ? ? ? ? ? ? ?" ?  return tonumber(current);\n" +
 ? ? ? ? ? ? ? ?"end\n" +
 ? ? ? ? ? ? ? ?"current = redis.call('incr', key)\n" +
 ? ? ? ? ? ? ? ?"if tonumber(current) == 1 then\n" +
 ? ? ? ? ? ? ? ?" ?  redis.call('expire', key, time)\n" +
 ? ? ? ? ? ? ? ?"end\n" +
 ? ? ? ? ? ? ? ?"return tonumber(current);";
 ?  }
}
package com.jlstest.springbootdemo.config;
?
import java.nio.charset.Charset;
?
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
?
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
?
/**
 * Redis使用FastJson序列化
 * 
 * @author admin
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
 ? ?public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
?
 ? ?private Class<T> clazz;
?
 ? ?public FastJson2JsonRedisSerializer(Class<T> clazz)
 ?  {
 ? ? ? ?super();
 ? ? ? ?this.clazz = clazz;
 ?  }
?
 ? ?@Override
 ? ?public byte[] serialize(T t) throws SerializationException
 ?  {
 ? ? ? ?if (t == null)
 ? ? ?  {
 ? ? ? ? ? ?return new byte[0];
 ? ? ?  }
 ? ? ? ?return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
 ?  }
?
 ? ?@Override
 ? ?public T deserialize(byte[] bytes) throws SerializationException
 ?  {
 ? ? ? ?if (bytes == null || bytes.length <= 0)
 ? ? ?  {
 ? ? ? ? ? ?return null;
 ? ? ?  }
 ? ? ? ?String str = new String(bytes, DEFAULT_CHARSET);
?
 ? ? ? ?return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
 ?  }
}

aop編程實(shí)現(xiàn)

package com.jlstest.springbootdemo.aop;
?
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
?
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {
 ? ?// 資源名稱
 ? ?String resourceName();
 ? ?// 令牌桶初始容量
 ? ?int initialCapacity();
 ? ?// 令牌桶單位時(shí)間填充速率
 ? ?int refillRate();
 ? ?// 令牌桶填充時(shí)間單位
 ? ?TimeUnit refillTimeUnit();
}
package com.jlstest.springbootdemo.aop;
?
import com.jlstest.springbootdemo.util.RedisCache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
?
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
?
/**
 * @author JLS
 * @description:
 * @since 2023-06-21 14:14
 */
@Aspect
@Component
public class RateLimitAspect {
?
 ? ?@Resource
 ? ?private RedisCache redisCache;
?
 ? ?@Around("@annotation(rateLimit)")
 ? ?public Object aroundRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
?
 ? ? ? ?String resourceName = rateLimit.resourceName();
 ? ? ? ?int initialCapacity = rateLimit.initialCapacity();
 ? ? ? ?int refillRate = rateLimit.refillRate();
 ? ? ? ?TimeUnit refillTimeUnit = rateLimit.refillTimeUnit();
?
 ? ? ? ?DistributedRateLimiterNew distributedRateLimiterNew = new DistributedRateLimiterNew(redisCache, resourceName, initialCapacity, refillRate, 10000L);
?
?
 ? ? ? ?if (!distributedRateLimiterNew.allowRequest()) {
 ? ? ? ? ? ?throw new RuntimeException("限流了");
 ? ? ?  }
?
 ? ? ? ?return joinPoint.proceed();
 ?  }
}
package com.jlstest.springbootdemo.aop;
?
import com.jlstest.springbootdemo.util.RedisCache;
import lombok.extern.slf4j.Slf4j;
?
import java.util.Objects;
import java.util.concurrent.TimeUnit;
?
/**
 * @author JLS
 * @description:
 * @since 2023-06-25 15:01
 */
@Slf4j
public class DistributedRateLimiterNew {
?
 ? ?private final RedisCache redisCache;
 ? ?private final String resourceName;
 ? ?private final int maxTokens;
 ? ?private final int refillRate;
 ? ?private final long refillInterval;
?
 ? ?/**
 ? ? * 資源key值前綴
 ? ? */
 ? ?public static final String RATE_LIMIT_KEY = "rate_limit:";
?
 ? ?/**
 ? ? * 最近一次更新token的時(shí)間
 ? ? */
 ? ?public static final String LAST_REFILL_TIME_KEY = "last_refill_time:";
?
 ? ?/**
 ? ? * @param redisCache
 ? ? * ? ? ? ? ?  redis工具類
 ? ? * @param resourceName
 ? ? * ? ? ? ? ?  資源名稱
 ? ? * @param maxTokens
 ? ? * ? ? ? ? ?  令牌桶容量
 ? ? * @param refillRate
 ? ? * ? ? ? ? ?  令牌桶單位時(shí)間填充速率
 ? ? * @param refillInterval
 ? ? * ? ? ? ? ?  令牌桶填充時(shí)間間隔,單位ms
 ? ? */
 ? ?public DistributedRateLimiterNew(RedisCache redisCache, String resourceName, int maxTokens, int refillRate, long refillInterval) {
 ? ? ? ?this.redisCache = redisCache;
 ? ? ? ?this.resourceName = resourceName;
 ? ? ? ?this.maxTokens = maxTokens;
 ? ? ? ?this.refillRate = refillRate;
 ? ? ? ?this.refillInterval = refillInterval;
?
 ? ? ? ?// 初始化令牌桶
 ? ? ? ?initializeTokenBucket();
 ?  }
?
 ? ?/**
 ? ? * 是否允許請求
 ? ? */
 ? ?public boolean allowRequest() {
 ? ? ? ?String key = RATE_LIMIT_KEY + resourceName;
 ? ? ? ?long currentTime = System.currentTimeMillis();
?
 ? ? ? ?// 獲取當(dāng)前令牌數(shù)量
 ? ? ? ?Integer tokenCountCache = redisCache.getCacheObject(key);
 ? ? ? ?int tokenCount = Objects.isNull(tokenCountCache) ? 0 : tokenCountCache;
?
 ? ? ? ?// 補(bǔ)充令牌
 ? ? ? ?long lastRefillTime = redisCache.getCacheObject(LAST_REFILL_TIME_KEY + resourceName);
 ? ? ? ?long timePassed = currentTime - lastRefillTime;
 ? ? ? ?int newTokens = (int) (timePassed * refillRate / refillInterval);
 ? ? ? ?tokenCount = Math.min(tokenCount + newTokens, maxTokens);
?
 ? ? ? ?log.info("扣除之前tokenCount:{}", tokenCount);
?
 ? ? ? ?// 判斷是否允許請求
 ? ? ? ?if (tokenCount > 0) {
 ? ? ? ? ? ?tokenCount--;
 ? ? ? ? ? ?log.info("扣除之后tokenCount:{}", tokenCount);
 ? ? ? ? ? ?// 保存令牌桶數(shù)量
 ? ? ? ? ? ?redisCache.setCacheObject(key, tokenCount, 60, TimeUnit.MINUTES);
 ? ? ? ? ? ?// 保存最近一次更新token的時(shí)間
 ? ? ? ? ? ?redisCache.setCacheObject(LAST_REFILL_TIME_KEY + resourceName, System.currentTimeMillis(), 60, TimeUnit.MINUTES);
 ? ? ? ? ? ?return true;
 ? ? ?  } else {
 ? ? ? ? ? ?return false;
 ? ? ?  }
 ?  }
?
 ? ?/**
 ? ? * 初始化令牌桶
 ? ? */
 ? ?private void initializeTokenBucket() {
 ? ? ? ?// 當(dāng)資源為空時(shí),則進(jìn)行新建
 ? ? ? ?if (redisCache.getCacheObject(RATE_LIMIT_KEY + resourceName) == null) {
 ? ? ? ? ? ?// 保存最近一次更新token的時(shí)間
 ? ? ? ? ? ?redisCache.setCacheObject(LAST_REFILL_TIME_KEY + resourceName, System.currentTimeMillis(), 60, TimeUnit.MINUTES);
 ? ? ? ? ? ?// 保存令牌桶數(shù)量,設(shè)置默認(rèn)值,設(shè)置默認(rèn)值
 ? ? ? ? ? ?redisCache.setCacheObject(RATE_LIMIT_KEY + resourceName, maxTokens, 60, TimeUnit.MINUTES);
 ? ? ? ? ? ?// 設(shè)置過期時(shí)間,當(dāng)長期不用則進(jìn)行釋放
 ? ? ? ? ? ?redisCache.expire(resourceName, 3600L);
 ? ? ?  }
 ?  }
}

對應(yīng)講解

核心主要是通過redis來保存對應(yīng)的令牌桶實(shí)例名以及對應(yīng)的上次的更新token的時(shí)間,每次調(diào)用到令牌桶則重新計(jì)算令牌數(shù)量。當(dāng)然這個(gè)設(shè)計(jì)比較毛糙,比如在規(guī)定時(shí)間中未必會有對應(yīng)數(shù)量的令牌數(shù)量,主要是由于每次計(jì)算令牌數(shù)量,當(dāng)計(jì)算成功時(shí)是不管是否整除都默認(rèn)是整除來保存時(shí)間,所以會有數(shù)量偏少的情況

接口上的放置

package com.jlstest.springbootdemo.controller;
?
import java.util.concurrent.TimeUnit;
?
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
?
import com.jlstest.springbootdemo.aop.RateLimit;
import com.jlstest.springbootdemo.common.response.BaseController;
import com.jlstest.springbootdemo.common.response.JlsTestResponse;
?
/**
 * @author JLS
 * @description:
 * @since 2023-03-22 19:09
 */
@RestController
@RequestMapping("/test")
public class TestController extends BaseController {
?
 ? ?@GetMapping("/test")
 ? ?@ResponseBody
 ? ?@RateLimit(resourceName = "test", initialCapacity = 10, refillRate = 2, refillTimeUnit = TimeUnit.SECONDS)
 ? ?public JlsTestResponse<String> test() {
 ? ? ? ?return sendSuccessData("success");
 ?  }
?
}

如圖所示,放在接口上就行。

三、基于redisson實(shí)現(xiàn)

redisson本身就已經(jīng)封裝了限流器RRateLimiter,只要稍加封裝即可使用,

對應(yīng)的代碼:

package com.jlstest.springbootdemo.aop.newLimit;
?
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
?
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimitNew {
 ? ?// 資源名
 ? ?String resourceName();
 ? ?// 令牌總數(shù)設(shè)置
 ? ?int permits();
 ? ?// 恢復(fù)速率,一邊填寫個(gè)數(shù)單位默認(rèn)秒。
 ? ?int restoreRate();
}
package com.jlstest.springbootdemo.aop.newLimit;
?
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
?
import javax.annotation.Resource;
?
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
?
import com.jlstest.springbootdemo.common.exception.ServiceException;
?
/**
 * @author JLS
 * @description:
 * @since 2023-08-01 14:56
 */
@Aspect
@Component
@Scope
public class RateLimitAspectNew {
?
 ? ?private final Map<String, RRateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
?
 ? ?private final Map<String, Long> lastAccessTimeMap = new ConcurrentHashMap<>();
?
 ? ?// @Value("${redis.address}")
 ? ?// private String redisAddress; // Redis連接地址,可以從配置文件中讀取
?
 ? ?@Resource
 ? ?private RedissonClient redissonClient;
?
 ? ?@Before("@annotation(rateLimitNew)")
 ? ?public void before(JoinPoint joinPoint, RateLimitNew rateLimitNew) {
 ? ? ? ?String resourceName = rateLimitNew.resourceName();
 ? ? ? ?int permits = rateLimitNew.permits();
 ? ? ? ?int restoreRate = rateLimitNew.restoreRate();
?
 ? ? ? ?// 創(chuàng)建或獲取令牌桶
 ? ? ? ?RRateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(resourceName, key -> {
 ? ? ? ? ? ?// 獲取對應(yīng)資源名的實(shí)例,當(dāng)資源不存在時(shí)會新建一個(gè)
 ? ? ? ? ? ?RRateLimiter limiter = redissonClient.getRateLimiter(resourceName);
 ? ? ? ? ? ?// 使用 trySetRate 方法設(shè)置令牌桶的速率。,只有新建限流器的時(shí)候才會設(shè)置屬性
 ? ? ? ? ? ?limiter.trySetRate(RateType.OVERALL, permits, restoreRate, RateIntervalUnit.SECONDS);
 ? ? ? ? ? ?// 返回對應(yīng)實(shí)例
 ? ? ? ? ? ?return limiter;
 ? ? ?  });
?
 ? ? ? ?// 當(dāng)時(shí)消費(fèi)令牌
 ? ? ? ?if (!rateLimiter.tryAcquire()) {
 ? ? ? ? ? ?throw new ServiceException("Rate limit exceeded for resource: " + resourceName);
 ? ? ?  }
?
 ? ? ? ?lastAccessTimeMap.put(resourceName, System.currentTimeMillis());
 ?  }
?
 ? ?// 定期清除不活躍的令牌桶
 ? ?@Scheduled(fixedDelay = 60000) // 每分鐘執(zhí)行一次清理任務(wù)
 ? ?public void cleanUpRateLimiters() {
 ? ? ? ?long inactiveDuration = 5 * 60 * 1000; // 5分鐘不活躍則清除
 ? ? ? ?long currentTime = System.currentTimeMillis();
 ? ? ? ?rateLimiterMap.entrySet().removeIf(entry -> {
 ? ? ? ? ? ?String resourceName = entry.getKey();
 ? ? ? ? ? ?Long lastAccessTime = lastAccessTimeMap.get(resourceName);
 ? ? ? ? ? ?// 判斷是否超過不活躍時(shí)間
 ? ? ? ? ? ?if (lastAccessTime != null && currentTime - lastAccessTime > inactiveDuration) {
 ? ? ? ? ? ? ? ?// 移除令牌桶實(shí)例
 ? ? ? ? ? ? ? ?RRateLimiter rateLimiter = entry.getValue();
 ? ? ? ? ? ? ? ?rateLimiter.delete();
 ? ? ? ? ? ? ? ?// 移除資源名的記錄
 ? ? ? ? ? ? ? ?lastAccessTimeMap.remove(resourceName);
 ? ? ? ? ? ? ? ?return true; // 移除該令牌桶實(shí)例
 ? ? ? ? ?  }
 ? ? ? ? ? ?return false; // 不需要移除該令牌桶實(shí)例
 ? ? ?  });
 ?  }
}

測試接口

package com.jlstest.springbootdemo.controller;
?
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
?
import com.jlstest.springbootdemo.aop.newLimit.RateLimitNew;
import com.jlstest.springbootdemo.common.response.BaseController;
import com.jlstest.springbootdemo.common.response.JlsTestResponse;
?
/**
 * @author JLS
 * @description:
 * @since 2023-03-22 19:09
 */
@RestController
@RequestMapping("/test")
public class TestController extends BaseController {
?
 ? ?@GetMapping("/test")
 ? ?@ResponseBody
 ? ?// @RateLimit(resourceName = "test", initialCapacity = 10, refillRate = 2, refillTimeUnit =
 ? ?// TimeUnit.SECONDS)
 ? ?@RateLimitNew(resourceName = "test1", permits = 1, restoreRate = 10)
 ? ?public JlsTestResponse<String> test() {
 ? ? ? ?return sendSuccessData("success");
 ?  }
?
}

其他的一些配置代碼,同redis的實(shí)現(xiàn),就不再重復(fù)寫出,

每個(gè)服務(wù)實(shí)例可以獨(dú)立管理自己的限流器,但令牌桶的狀態(tài)和數(shù)據(jù)是存儲在 Redis 中的,這意味著所有實(shí)例共享相同的令牌桶信息。當(dāng)一個(gè)實(shí)例獲取或更新令牌桶的狀態(tài)時(shí),其他實(shí)例也可以立即感知到這些變化,從而實(shí)現(xiàn)在分布式系統(tǒng)中的限流效果。

結(jié)合 Redisson 的 RRateLimiter 和 Redis 緩存,可以實(shí)現(xiàn)分布式系統(tǒng)的限流,確保系統(tǒng)穩(wěn)定性和資源的合理利用。

四、現(xiàn)成的工具-sentinel實(shí)現(xiàn)

Sentinel是阿里巴巴開源的一款分布式系統(tǒng)的流量控制組件,用于保護(hù)后端服務(wù)免受過多請求的影響,確保系統(tǒng)的穩(wěn)定性和可靠性。Sentinel提供了多種限流策略和流量控制規(guī)則,能夠靈活地適應(yīng)不同場景的需求。

以下是Sentinel組件限流的基本使用步驟:

  • 引入依賴:首先,需要將Sentinel的依賴添加到項(xiàng)目中??梢酝ㄟ^Maven或Gradle引入相關(guān)的Sentinel依賴。
  • 配置流控規(guī)則:在項(xiàng)目的配置文件中,配置需要的流控規(guī)則。流控規(guī)則可以通過配置文件、注解或代碼進(jìn)行配置。
  • 初始化Sentinel:在應(yīng)用程序啟動(dòng)時(shí),需要初始化Sentinel組件,確保其正常工作。
  • 設(shè)置限流策略:選擇適合場景的限流策略,例如,根據(jù)QPS、線程數(shù)、并發(fā)數(shù)等設(shè)置限流策略。
  • 編寫業(yè)務(wù)邏輯:在業(yè)務(wù)邏輯中,使用Sentinel的限流功能來保護(hù)對資源的訪問。當(dāng)達(dá)到限流閾值時(shí),Sentinel會根據(jù)配置的限流策略進(jìn)行處理,例如拒絕請求、等待或者返回默認(rèn)值等。
  • 監(jiān)控和統(tǒng)計(jì):Sentinel提供了豐富的監(jiān)控和統(tǒng)計(jì)功能,可以通過Dashboard查看實(shí)時(shí)的流量控制情況和系統(tǒng)狀態(tài),幫助及時(shí)發(fā)現(xiàn)問題并進(jìn)行調(diào)整。

使用Sentinel進(jìn)行限流的示例代碼(基于Spring Boot):

引入Sentinel依賴:

Maven:

<dependency>
 ?  <groupId>com.alibaba.csp</groupId>
 ?  <artifactId>sentinel-core</artifactId>
 ?  <version>1.8.1</version>
</dependency>

在配置文件中配置限流規(guī)則(可以根據(jù)實(shí)際需求配置):

yamlCopy codespring:
  cloud:
 ?  sentinel:
 ? ?  transport:
 ? ? ?  dashboard: localhost:8080 # Sentinel Dashboard 地址

在啟動(dòng)類中初始化Sentinel組件:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
?
@SpringBootApplication
public class Application {
?
 ? ?public static void main(String[] args) {
 ? ? ? ?// 初始化 Sentinel
 ? ? ? ?initSentinel();
 ? ? ? ?SpringApplication.run(Application.class, args);
 ?  }
?
 ? ?private static void initSentinel() {
 ? ? ? ?// 這里的參數(shù)可以根據(jù)實(shí)際情況進(jìn)行調(diào)整
 ? ? ? ?System.setProperty("csp.sentinel.api.port", "8720");
 ? ? ? ?System.setProperty("csp.sentinel.dashboard.server", "localhost:8080");
 ? ? ? ?System.setProperty("project.name", "your-project-name");
 ?  }
}

在業(yè)務(wù)代碼中添加限流注解:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
?
@Service
public class YourService {
?
 ? ?@SentinelResource(value = "yourResourceName", blockHandler = "blockHandlerMethod")
 ? ?public void yourMethod() {
 ? ? ? ?// 業(yè)務(wù)邏輯
 ?  }
?
 ? ?// 定義限流策略的處理方法
 ? ?public void blockHandlerMethod(BlockException ex) {
 ? ? ? ?// 限流處理邏輯
 ?  }
}

上述示例代碼中,我們使用了Sentinel的注解@SentinelResource來標(biāo)注需要進(jìn)行限流保護(hù)的方法。當(dāng)達(dá)到限流閾值時(shí),會調(diào)用blockHandler指定的方法進(jìn)行限流處理。在blockHandlerMethod中,可以自定義限流策略的處理邏輯。

需要注意的是,Sentinel的流控規(guī)則可以在Dashboard中進(jìn)行配置和管理,也可以通過代碼進(jìn)行動(dòng)態(tài)配置,使得限流策略可以根據(jù)實(shí)際情況進(jìn)行靈活調(diào)整。同時(shí),Dashboard提供了實(shí)時(shí)的監(jiān)控和統(tǒng)計(jì)功能,方便查看應(yīng)用程序的流量控制情況和系統(tǒng)狀態(tài)。

sentinel雖然好 ,但是這個(gè)組件所包含的東西過大,有些時(shí)候只需要用到限流功能,則會顯得有點(diǎn)大材小用,沒有必要。

到此這篇關(guān)于基于令牌桶的Java限流器注解的簡單實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Java限流器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 一個(gè)截取字符串的函數(shù)

    java 一個(gè)截取字符串的函數(shù)

    java 編寫一個(gè)截取字符串的函數(shù),輸入為一個(gè)字符串和字節(jié)數(shù),輸出為按字節(jié)截取的字符串。 要求不能出現(xiàn)截半的情況
    2017-02-02
  • mybatis/mybatis-plus模糊查詢語句特殊字符轉(zhuǎn)義攔截器的實(shí)現(xiàn)

    mybatis/mybatis-plus模糊查詢語句特殊字符轉(zhuǎn)義攔截器的實(shí)現(xiàn)

    在開發(fā)中,我們通常會遇到這樣的情況。用戶在錄入信息是錄入了‘%’,而在查詢時(shí)無法精確匹配‘%’。究其原因,‘%’是MySQL的關(guān)鍵字,如果我們想要精確匹配‘%’,那么需要對其進(jìn)行轉(zhuǎn)義,本文就詳細(xì)的介紹一下
    2021-11-11
  • 大話Java混合運(yùn)算規(guī)則

    大話Java混合運(yùn)算規(guī)則

    這篇文章主要介紹了大話Java混合運(yùn)算規(guī)則,小編覺得挺不錯(cuò)的,在這里分享給大家,需要的朋友可以了解下。
    2017-10-10
  • Java框架入門之簡單介紹SpringBoot框架

    Java框架入門之簡單介紹SpringBoot框架

    今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著SpringBoot框架展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • SpringBoot持久化層操作支持技巧

    SpringBoot持久化層操作支持技巧

    這篇文章主要介紹了SpringBoot持久化層操作支持技巧,需要的朋友可以參考下
    2017-10-10
  • Java精品項(xiàng)目瑞吉外賣之員工信息管理篇

    Java精品項(xiàng)目瑞吉外賣之員工信息管理篇

    這篇文章主要為大家詳細(xì)介紹了java精品項(xiàng)目-瑞吉外賣訂餐系統(tǒng),此項(xiàng)目過大,分為多章獨(dú)立講解,本篇內(nèi)容為員工信息分頁查詢與啟用或禁用員工狀態(tài),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • java如何使用正則表達(dá)式限制特殊字符的個(gè)數(shù)

    java如何使用正則表達(dá)式限制特殊字符的個(gè)數(shù)

    這篇文章主要介紹了java如何使用正則表達(dá)式限制特殊字符的個(gè)數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java中實(shí)現(xiàn)String字符串用逗號隔開

    Java中實(shí)現(xiàn)String字符串用逗號隔開

    這篇文章主要介紹了Java中實(shí)現(xiàn)String字符串用逗號隔開,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Mybatis動(dòng)態(tài)SQL?foreach批量操作方法

    Mybatis動(dòng)態(tài)SQL?foreach批量操作方法

    這篇文章主要介紹了Mybatis動(dòng)態(tài)SQL?foreach批量操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Java 從互聯(lián)網(wǎng)上爬郵箱代碼示例

    Java 從互聯(lián)網(wǎng)上爬郵箱代碼示例

    這篇文章介紹了Java 從互聯(lián)網(wǎng)上爬郵箱的有關(guān)內(nèi)容,主要是一個(gè)代碼示例,小編覺得挺不錯(cuò)的,這里給大家分享下,需要的朋友可以了解。
    2017-10-10

最新評論