效果演示在最后。都是简单理解实现其功能demo,别杠,最终解释权归作者所有
1、令牌桶算法这是关于令牌桶的定义,我也不用去解释了,直接百度就OK。平时我们可能会使用guava的RateLimiter。我个人的理解是感觉该接口每秒可以请求的次数。
那么我们根据该解释来手动去实现一个所谓的令牌桶算法。令牌桶首先要有一个桶对吧,还要有令牌、还要实现平均发送令牌,桶满了丢弃等,那么你们有思路了吗,开始diy吧。
import java.util.concurrent.linkedBlockingQueue;
public class MyRateLimiter {
private volatile linkedBlockingQueue tokenBucket = null;
public static MyRateLimiter create(int capactity){
return new MyRateLimiter(capactity);
}
private MyRateLimiter(int capactity){
tokenBucket = new linkedBlockingQueue(capactity);
initTokenBucket(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();
}
private void addToken(int capactity) {
for (int i = 0; i < capactity; i++) {
tokenBucket.offer("#");
}
}
public boolean tryAcquire() {
return tokenBucket.poll() == null ? false : true;
}
}
2、漏桶算法
import java.util.concurrent.linkedBlockingQueue;
public class LeakyBucket {
private volatile linkedBlockingQueue tokenBucket = null;
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();
}
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配置类
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 redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
RedisSerializer 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 limitscript()
{
DefaultRedisscript redisscript = new DefaultRedisscript<>();
redisscript.setscriptText(limitscriptText());
redisscript.setResultType(Long.class);
return redisscript;
}
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 thenn" + //如果获取到的次数大于设置的count 直接返回
" return tonumber(current);n" +
"endn" +
"current = redis.call('incr', key)n" + //key+1
"if tonumber(current) == 1 thenn" + //如果current=1 设置过期时间
" redis.call('expire', key, time)n" +
"endn" +
"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 redisTemplate;
@Autowired
private Redisscript 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 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();
}
}



