基于redis实现最简单的计数器限流算法

一、概述

二、实现流程

环境:spring boot 2.7.1

1. 引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.3</version>
    </dependency>
</dependencies>

2. 创建RedisConfig配置类,注入RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会抛出异常
        //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);旧版本
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // 采用json序列化
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

3. 建立RedisTemplate管理类

@Component
public class RedisManager {
    protected static final Logger logger = LoggerFactory.getLogger(RedisManager.class);

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    public String get(String key){
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof String){
            return (String) value;
        }
        return null;
    }

    public void set(String key,String value){
        redisTemplate.opsForValue().set(key,value);
    }

    public long counter(String key, Integer dela,Integer initial,Long expiry){
        return (long) redisTemplate.execute((RedisCallback<Long>) connection -> {
            try {
                long result = 0L;
                if (expiry == 0L) {
                    if (connection.setNX(key.getBytes(), (initial + "").getBytes())) {
                        result = initial;
                    } else {
                        result = connection.incrBy(key.getBytes(), dela);
                    }
                } else if (expiry > 0L) {
                    if (connection.setNX(key.getBytes(), (initial + "").getBytes())) {
                        result = initial;
                        connection.expire(key.getBytes(), expiry);
                    } else {
                        result = connection.incrBy(key.getBytes(), dela);
                        long ttl = connection.ttl(key.getBytes());
                        if (ttl == -1L) {
                            connection.expire(key.getBytes(), expiry);
                        }
                    }
                } else if (connection.exists(key.getBytes())) {
                    result = connection.incrBy(key.getBytes(), dela);
                }

                return result;
            } catch (Exception var6) {
                logger.error("count: ", var6);
                return -1L;
            }
        });
    }

    public void updateExpiry(String key,Long expiry){
        redisTemplate.expire(key,expiry, TimeUnit.SECONDS);
    }
}

4. 创建实现计数器限流算法类RedisRateLimiter

@Component
public class RedisRateLimiter {
    protected static final Logger logger = LoggerFactory.getLogger(RedisRateLimiter.class);

    @Autowired
    public RedisManager redisManager;

    public boolean acquire(String key){
        if (StringUtil.isNullOrEmpty(key)){
            return false;
        }
        ExecutorService executorService = getExecutorService();
        String persecond = redisManager.get("DISTT_CRDT_ACC_PERSECOND");
        Integer maximum = persecond==null?0:Integer.valueOf(persecond)  ;
        if (maximum<1){
            return false;
        }
        while (getCounter(key, executorService)>maximum){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
               logger.error("现场休眠异常{}",e.getMessage());
            }
        }
        return true;
    }

    private long getCounter(String key, ExecutorService executorService) {
        long time = System.currentTimeMillis() / 1000;
        long counter = redisManager.counter(key + time, 1,1,3L);
        executorService.execute(()->{
            try {
                // 因不是lua脚本无法保证原子性,故兜底设置限流key自动过期
                redisManager.updateExpiry(key + time,3L);
            }catch (Exception e){
                logger.debug("更新过期时间异常",e);
            }
        });
        return counter;
    }

    public ExecutorService getExecutorService(){
        return ExecutorServiceHolder.executorService;
    }

    static class ExecutorServiceHolder{
        private static ExecutorService  executorService=null;
        static {
            executorService = new ThreadPoolExecutor(8, 8,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(100000), new DefaultThreadFactory("ExecutorServiceHolder")
                    , new ThreadPoolExecutor.AbortPolicy());
        }
    }
}

5. 创建测试类RedisApplicationTests

@SpringBootTest
class RedisApplicationTests {
    protected static final Logger logger = LoggerFactory.getLogger(RedisApplicationTests.class);

    @Autowired
    private RedisManager redisManager;
   @Autowired
    RedisRateLimiter redisRateLimiter;
    @Test
    void single() {
        redisManager.set("DISTT_CRDT_ACC_PERSECOND","10");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            redisRateLimiter.acquire("wdz");
        }
        long end = System.currentTimeMillis();
        logger.info("请求开始时间:{},请求结束时间:{},请求耗时:{}",start/1000,end/1000,end-start);
    }

    @Test
    void multi() {
        redisManager.set("DISTT_CRDT_ACC_PERSECOND","10");
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 100; i++) {
            executorService.execute(()->redisRateLimiter.acquire("wdz"));
        }
        executorService.shutdown();
        while (true){
            if (executorService.isTerminated()){
                break;
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        logger.info("请求开始时间:{},请求结束时间:{},请求耗时:{}",start/1000,end/1000,end-start);
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴代庄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值