分布式接口幂等性、分布式限流:Guava 、nginx和lua限流

转载:https://mp.weixin.qq.com/s/kRI9iQc695vIeqhkry6RRw

来源:blog.csdn.net/qq_34886352/article/details/104694550

在这里插入图片描述

update set version = version +1 ,xxx=${xxx} where id =xxx and version = ${version};

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

在这里插入图片描述

@RestController
@Slf4j
public class Controller{
    //每秒钟可以创建两个令牌
    RateLimiter limiter = RateLimiter.create(2.0);
    
    //非阻塞限流
    @GetMapping("/tryAcquire")
    public String tryAcquire(Integer count){
        //count 每次消耗的令牌
        if(limiter.tryAcquire(count)){
            log.info("成功,允许通过,速率为{}",limiter.getRate());
            return "success";
        }else{
            log.info("错误,不允许通过,速率为{}",limiter.getRate());
            return "fail";
        }
    }
    
    //限定时间的非阻塞限流
    @GetMapping("/tryAcquireWithTimeout")
    public String tryAcquireWithTimeout(Integer count, Integer timeout){
        //count 每次消耗的令牌  timeout 超时等待的时间
        if(limiter.tryAcquire(count,timeout,TimeUnit.SECONDS)){
            log.info("成功,允许通过,速率为{}",limiter.getRate());
            return "success";
        }else{
            log.info("错误,不允许通过,速率为{}",limiter.getRate());
            return "fail";
        }
    }
    
    //同步阻塞限流
    @GetMapping("/acquire")
    public String acquire(Integer count){
        limiter.acquire(count);
        log.info("成功,允许通过,速率为{}",limiter.getRate());
        return "success";
    }
}

在这里插入图片描述

@RestController
@Slf4j
public class Controller{
    //nginx测试使用
    @GetMapping("/nginx")
    public String nginx(){
        log.info("Nginx success");
    }
}

在这里插入图片描述

127.0.0.1   www.test.com

在这里插入图片描述

vim /usr/local/nginx/conf/nginx.conf

在这里插入图片描述

#根据IP地址限制速度
#1)$binary_remote_addr   binary_目的是缩写内存占用,remote_addr表示通过IP地址来限流
#2)zone=iplimit:20m   iplimit是一块内存区域(记录访问频率信息),20m是指这块内存区域的大小
#3)rate=1r/s  每秒放行1个请求
limit_req_zone $binary_remote_addr zone=iplimit:20m rate=1r/s;

server{
    server_name www.test.com;
    location /access-limit/ {
        proxy_pass http://127.0.0.1:8080/;
        
        #基于ip地址的限制
        #1)zone=iplimit 引用limit_rep_zone中的zone变量
        #2)burst=2  设置一个大小为2的缓冲区域,当大量请求到来,请求数量超过限流频率时,将其放入缓冲区域
        #3)nodelay   缓冲区满了以后,直接返回503异常
        limit_req zone=iplimit burst=2 nodelay;
    }
}

在这里插入图片描述

www.test.com/access-limit/nginx

在这里插入图片描述

#根据IP地址限制速度
limit_req_zone $binary_remote_addr zone=iplimit:20m rate=10r/s;
#根据服务器级别做限流
limit_req_zone $server_name zone=serverlimit:10m rate=1r/s;
#根据ip地址的链接数量做限流
limit_conn_zone $binary_remote_addr zone=perip:20m;
#根据服务器的连接数做限流
limit_conn_zone $server_name zone=perserver:20m;

server{
    server_name www.test.com;
    location /access-limit/ {
        proxy_pass http://127.0.0.1:8080/;
        
        #基于ip地址的限制
        limit_req zone=iplimit burst=2 nodelay;
        #基于服务器级别做限流
        limit_req zone=serverlimit burst=2 nodelay;
        #基于ip地址的链接数量做限流  最多保持100个链接
        limit_conn zone=perip 100;
        #基于服务器的连接数做限流 最多保持100个链接
        limit_conn zone=perserver 1;
        #配置request的异常返回504(默认为503)
        limit_req_status 504;
        limit_conn_status 504;
    }
    
     location /download/ {
        #前100m不限制速度
        limit_rate_affer 100m;
        #限制速度为256k
        limit_rate 256k;
     }
}

在这里插入图片描述

print 'Hello Lua'

在这里插入图片描述

-- 模拟限流

-- 用作限流的key
local key = 'my key'

-- 限流的最大阈值
local limit = 2

-- 当前限流大小
local currentLimit = 2

-- 是否超过限流标准
if currentLimit + 1 > limit then
    print 'reject'
    return false
else
    print 'accept'
    return true
end

在这里插入图片描述

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

在这里插入图片描述

server.port=8080
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6376

在这里插入图片描述

-- 获取方法签名特征
local methodKey = KEYS[1]
redis.log(redis.LOG_DEBUG,'key is',methodKey)

-- 调用脚本传入的限流大小
local limit = tonumber(ARGV[1])

-- 获取当前流量大小
local count = tonumber(redis.call('get',methodKey) or "0")

--是否超出限流值
if count + 1 >limit then
    -- 拒绝访问
    return false
else
    -- 没有超过阈值
    -- 设置当前访问数量+1
    redis.call('INCRBY',methodKey,1)
    -- 设置过期时间
    redis.call('EXPIRE',methodKey,1)
    -- 放行
    return true
end

在这里插入图片描述

@Service
@Slf4j
public class AccessLimiter{
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisScript<Boolean> rateLimitLua;

    public void limitAccess(String key,Integer limit){
        boolean acquired = stringRedisTemplate.execute(
            rateLimitLua,//lua脚本的真身
            Lists.newArrayList(key),//lua脚本中的key列表
            limit.toString()//lua脚本的value列表
        );

        if(!acquired){
            log.error("Your access is blocked,key={}",key);
            throw new RuntimeException("Your access is blocked");
        }
    }
}

在这里插入图片描述

@Configuration
public class RedisConfiguration{
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
        return new StringRedisTemplate(factory);
    }
    
    public DefaultRedisScript loadRedisScript(){
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setLocation(new ClassPathResource("rateLimiter.lua"));
        redisScript.setResultType(java.lang.Boolean.class);
        return redisScript;
    }
}

在这里插入图片描述

@RestController
@Slf4j
public class Controller{
    @Autowired
    private AccessLimiter accessLimiter;
    
    @GetMapping("test")
    public String test(){
        accessLimiter.limitAccess("ratelimiter-test",1);
        return "success";
    }
} 

在这里插入图片描述

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimiterAop{
    int limit();
    
    String methodKey() default "";
}

在这里插入图片描述

@Slf4j
@Aspect
@Component
public class AccessLimiterAspect{
    @Autowired
    private AccessLimiter  accessLimiter;

    //根据注解的位置,自己修改
    @Pointcut("@annotation(com.gyx.demo.annotation.AccessLimiter)")
    public void cut(){
        log.info("cut");
    }
    
    @Before("cut()")
    public void before(JoinPoint joinPoint){
        //获取方法签名,作为methodkey
        MethodSignature signature =(MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AccessLimiterAop annotation = method.getAnnotation(AccessLimiterAop.class);
        
        if(annotation == null){
            return;
        }
        String key = annotation.methodKey();
        Integer limit = annotation.limit();
        //如果没有设置methodKey,就自动添加一个
        if(StringUtils.isEmpty(key)){
            Class[] type = method.getParameterType();
            key = method.getName();
            if (type != null){
                String paramTypes=Arrays.stream(type)
                    .map(Class::getName)
                    .collect(Collectors.joining(","));
                    key += "#"+paramTypes;
            }
        }
        
        //调用redis
        return accessLimiter.limitAccess(key,limit);
    }
}

在这里插入图片描述

@RestController
@Slf4j
public class Controller{
    @Autowired
    private AccessLimiter accessLimiter;
    
    @GetMapping("test")
    @AccessLImiterAop(limit =1)
    public String test(){
        return "success";
    }
} 

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值