一、概述
![](https://i-blog.csdnimg.cn/blog_migrate/d4ab623692900bb34a6a2ff73a3936b5.png)
二、实现流程
环境: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); }
} |