🧑🎓 个人主页:Silence Lamb
📖 本章内容:【基于Redis自定义注解实现限流配置】
一、Redis配置
1.1【Maven依赖】
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
1.2【Redis信息】
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
1.3【Redis配置类】
/**
* @author Silence Lamb
* @apiNote Redis配置
*/
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {
@Bean
@SuppressWarnings("all")
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);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
@SuppressWarnings("all")
public CacheManager cacheManager(RedisConnectionFactory factory) {
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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
1.4【Redis工具类】
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisUtils {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection) {
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey) {
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
二、限流配置
2.1【限流脚本】
/**
* @author Silence Lamb
* @apiNote redis脚本常量
*/
@Component
public class RedisCacheConstant {
@ApiModelProperty("加锁脚本")
public static final String LOCK_LUA = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end";
@ApiModelProperty("解锁脚本")
public static final String RELEASE_LOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
@ApiModelProperty("限流脚本")
public static final String LIMITER_LUA = "local key = KEYS[1] local count = tonumber(ARGV[1]) local time = tonumber(ARGV[2]) local current = redis.call('get', key); if current and tonumber(current) > count then return tonumber(current); end current = redis.call('incr', key) if tonumber(current) == 1 then redis.call('expire', key, time) end return tonumber(current)";
}
/**
* @author Silence Lamb
* @apiNote 脚本配置
*/
public class RedisScript {
/**
* 限流脚本
*
* @return
*/
@Bean
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(RedisCacheConstant.LIMITER_LUA);
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 加锁脚本
*
* @return
*/
@Bean
public DefaultRedisScript<Long> lockScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(RedisCacheConstant.LOCK_LUA);
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 解锁脚本
*/
@Bean
public DefaultRedisScript<Long> ReleaseLockScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(RedisCacheConstant.RELEASE_LOCK_LUA);
redisScript.setResultType(Long.class);
return redisScript;
}
}
2.2【缓存常量】
/**
* @author Silence Lamb
* @apiNote 缓存常量
*/
public class RedisCacheConstant {
private static final String RATE_LIMIT_KEY = "rate:limit:key";
}
/**
* @author Silence Lamb
* @Describe: 限流类型
*/
public enum LimitType {
@ApiModelProperty("默认策略全局限流")
DEFAULT,
@ApiModelProperty("根据请求者IP进行限流")
IP
}
/**
* @author SilenceLamb
* @apiNote 自定义业务异常
*/
public final class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
@ApiModelProperty("业务名称")
private String name;
@ApiModelProperty("错误码")
private Integer code;
@ApiModelProperty("错误提示")
private String msg;
@ApiModelProperty("错误明细,内部调试错误")
private String detailMessage;
public ServiceException() {
}
public ServiceException(String name, Throwable e) {
super(e.getMessage(), e);
this.name = name;
}
public ServiceException(String msg) {
this.msg = msg;
}
public ServiceException(String msg, Integer code) {
this.msg = msg;
this.code = code;
}
public ServiceException(String name, String msg, Integer code) {
this.name = name;
this.msg = msg;
this.code = code;
}
public ServiceException(String name, String msg) {
this.name = name;
this.msg = msg;
}
public String getDetailMessage() {
return detailMessage;
}
public String getName() {
return name;
}
@Override
public String getMessage() {
return msg;
}
public Integer getCode() {
return code;
}
public ServiceException setMessage(String msg) {
this.msg = msg;
return this;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}
2.3【限流注解】
/**
* @author Silence Lamb
* @apiNote 限流注解
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiter {
@ApiModelProperty("限流key")
String key() default RedisCacheConstant.RATE_LIMIT_KEY;
@ApiModelProperty("限流时间,单位秒")
int time() default 60;
@ApiModelProperty("限流次数")
int count() default 100;
@ApiModelProperty("限流类型")
LimitType limitType() default LimitType.DEFAULT;
}
2.4【限流切面类】
/**
* @Author: 花棉袄
* @CreateDate: 2022年08月23日
* @Describe: 限流处理处理 切面类
*/
@Aspect
@Component
public class RateLimiterAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
private RedisTemplate<Object, Object> redisTemplate;
private RedisScript<Long> limitScript;
@Autowired
public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Autowired
public void setLimitScript(RedisScript<Long> limitScript) {
this.limitScript = limitScript;
}
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
String key = rateLimiter.key();
int time = rateLimiter.time();
int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point);
List<Object> keys = Collections.singletonList(combineKey);
try {
/* 判断redis中的缓存key的值是否存在且大于限制的次数 */
Long number = redisTemplate.execute(limitScript, keys, count, time);
if (StringUtils.isNull(number) || number.intValue() > count) {
/* 缓存过于频繁 */
throw new ServiceException("Redis限流业务","访问过于频繁,请稍候再试");
}
log.info("限制请求最大次数'{}',当前请求次数'{}',缓存的key'{}'", count, number.intValue(), key);
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("服务器限流异常,请稍候再试");
}
}
/**
* 获取缓存的key
*/
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP) {
stringBuffer.append(IpUtils.getIpAddress(ServletUtils.getRequest())).append("-");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Class<?> targetClass = method.getDeclaringClass();
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
return stringBuffer.toString();
}
/**
* 获取request
*/
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes.getRequest();
}
}
2.5【限流测试】
/**
* @Author: 花棉袄
* @CreateDate: 2022年08月28日
* @Describe: 限流测试
*/
@RestController
@RequestMapping("/test")
public class LimitController {
@RateLimiter
@GetMapping("/limit")
@ApiOperation(value = "", notes = "")
public AjaxResult list() {
return AjaxResult.success();
}
}
限制请求最大次数'100',当前请求次数'1',缓存的key'rate:limit:key'
限制请求最大次数'100',当前请求次数'2',缓存的key'rate:limit:key'
限制请求最大次数'100',当前请求次数'3',缓存的key'rate:limit:key'
限制请求最大次数'100',当前请求次数'4',缓存的key'rate:limit:key'
限制请求最大次数'100',当前请求次数'5',缓存的key'rate:limit:key'