基于令牌桶的限流器注解的簡單實(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)文章
mybatis/mybatis-plus模糊查詢語句特殊字符轉(zhuǎn)義攔截器的實(shí)現(xiàn)
在開發(fā)中,我們通常會遇到這樣的情況。用戶在錄入信息是錄入了‘%’,而在查詢時(shí)無法精確匹配‘%’。究其原因,‘%’是MySQL的關(guān)鍵字,如果我們想要精確匹配‘%’,那么需要對其進(jìn)行轉(zhuǎn)義,本文就詳細(xì)的介紹一下2021-11-11java如何使用正則表達(dá)式限制特殊字符的個(gè)數(shù)
這篇文章主要介紹了java如何使用正則表達(dá)式限制特殊字符的個(gè)數(shù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java中實(shí)現(xiàn)String字符串用逗號隔開
這篇文章主要介紹了Java中實(shí)現(xiàn)String字符串用逗號隔開,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Mybatis動(dòng)態(tài)SQL?foreach批量操作方法
這篇文章主要介紹了Mybatis動(dòng)態(tài)SQL?foreach批量操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03