效果演示在最后。都是简单理解实现其功能demo,别杠,最终解释权归作者所有
1、令牌桶算法
这是关于令牌桶的定义,我也不用去解释了,直接百度就OK。平时我们可能会使用guava的RateLimiter。我个人的理解是感觉该接口每秒可以请求的次数。
那么我们根据该解释来手动去实现一个所谓的令牌桶算法。令牌桶首先要有一个桶对吧,还要有令牌、还要实现平均发送令牌,桶满了丢弃等,那么你们有思路了吗,开始diy吧。
import java.util.concurrent.LinkedBlockingQueue;
/**
* 简单实现令牌桶算法
*/
public class MyRateLimiter {
/**
* 设置为可见性
* tokenBucket桶容器
*/
private volatile LinkedBlockingQueue tokenBucket = null;
/**
* 对外提供创建桶的方法
* @param capactity 设置桶的容量
* @return
*/
public static MyRateLimiter create(int capactity){
return new MyRateLimiter(capactity);
}
/**
* 在构造方法中初始化
* @param capactity
*/
private MyRateLimiter(int capactity){
tokenBucket = new LinkedBlockingQueue<String>(capactity);
initTokenBucket(capactity);
}
/**
* 第一次初始化的时候要首先放入指定令牌数量,不然第一次初始化的时候没有令牌可咋玩
* @param capactity
*/
private void initTokenBucket(int capactity) {
//先放入令牌,省的还没放入就已经调用导致无法使用
addToken(capactity);
//定时任务每秒方法指定数量令牌
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
// 每隔1s 执行
Thread.sleep(1000);
addToken(capactity);
} catch (Exception e) {
}
}
}
}).start();
}
/**
* 每秒放入指定数量的令牌
* @param capactity
*/
private void addToken(int capactity) {
for (int i = 0; i < capactity; i++) {
tokenBucket.offer("#");
}
}
/**
* 获取令牌
* @return
*/
public boolean tryAcquire() {
return tokenBucket.poll() == null ? false : true;
}
}
2、漏桶算法
import java.util.concurrent.LinkedBlockingQueue;
public class LeakyBucket {
/**
* 还是定义通
*/
private volatile LinkedBlockingQueue<String> tokenBucket = null;
/**
* 对外提供创建桶方法
* @param capactity
* @return
*/
public static LeakyBucket create(int capactity) {
return new LeakyBucket(capactity);
}
private LeakyBucket(int capactity) {
tokenBucket = new LinkedBlockingQueue<>(capactity);
initConsume();
}
/**
* 以恒定速率流出桶
*/
private void initConsume() {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tokenBucket.poll();//以固定速率消费
}
}
}).start();
}
/**
* 是否能存入,能存入则代表桶没满可以允许请求返回true
* @return
*/
public boolean tryAcquire() {
return tokenBucket.offer("#");
}
}
两种桶都是用了LinkedBlockingQueue去实现,为什么要这样做呢,主要还是api好用
3、计数器算法
public class CountLimiter {
private volatile int count;//总量
private volatile long startTime = System.currentTimeMillis();
private static final long MIN_TIME = 2*1000;//规定时间内 赞设为2s
private static final int REQ_LIMIT = 5;//规定时间内请求的次数最大为5
public boolean tryAcquire(){
long currentTimeMillis = System.currentTimeMillis();
if(currentTimeMillis-startTime>=MIN_TIME){
startTime = currentTimeMillis;
count = 0;
}
if(count<=REQ_LIMIT){
count++;
return true;
}
return false;
}
}
测试使用
测试代码
//CountLimiter limiter = new CountLimiter();
//LeakyBucket limiter = LeakyBucket.create(5);
MyRateLimiter limiter = MyRateLimiter.create(5);
@GetMapping("/test")
public String te() {
boolean tryAcquire = limiter.tryAcquire();
if(!tryAcquire) {
return "服务被限流了";
}
return "服务访问成功了";
}
正常访问请求
加快频率访问
实战 redis+lua脚本限流
先贴上redis配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
template.setKeySerializer(redisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 参考文章https://blog.csdn.net/shy_1762538422/article/details/113569335
*/
private String limitScriptText(){
return "local key = KEYS[1]\n" + //获取key中的第一个参数
"local count = tonumber(ARGV[1])\n" + //获取可变参数中的第一个参数 rateLimit.count()
"local time = tonumber(ARGV[2])\n" + //获取可变参数中的第二个参数 rateLimit.time()
"local current = redis.call('get', key);\n" + //执行redis命令:获取当前key的使用次数
"if current and tonumber(current) > count then\n" + //如果获取到的次数大于设置的count 直接返回
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" + //key+1
"if tonumber(current) == 1 then\n" + //如果current=1 设置过期时间
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);"; // 返回current
}
}
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义限流注解
* 默认一秒内最多三次请求
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
//唯一标识
String key() default "";
//指定限流时间
int time() default 1;
//指定时间内的次数
int count() default 3;
}
切面代码
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
@Aspect
@Component
public class LimitAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedisScript<Long> limitScript;
@Autowired
HttpServletResponse response;
@Around("@annotation(com.ljw.lovely.limit.RateLimit)")
public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
redisTemplate.opsForValue().set("name","测试名称");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
String key = rateLimit.key();
int count = rateLimit.count();
int time = rateLimit.time();
StringBuilder sb = new StringBuilder();
sb.append(key).append("-").append(method.getName());
//创建单个元素的List集合 这个方法主要用于只有一个元素的优化,减少内存分配,无需分配额外的内存
List<String> keys = Collections.singletonList(sb.toString());
try{
Long number = redisTemplate.execute(limitScript, keys, count, time);
if (number==null || number.intValue() > count){
return "服务被限流了";
}
}catch (Exception e){
e.printStackTrace();
return "服务被限流了";
}
return joinPoint.proceed();
}
}
今天的文章手写常用限流算法分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/27896.html