接口的屏蔽和限流很难么?Redis全搞定!
Posted Java知音_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口的屏蔽和限流很难么?Redis全搞定!相关的知识,希望对你有一定的参考价值。
点击关注公众号,实用技术文章及时了解
来源:cnblogs.com/youzhibing/p/15354706.html
需求
线上出现的问题是,一些非核心的查询数据业务,在请求超时或者错误的时候,用户会越查询,导致数据库cup飙升,拖垮核心的业务。
领导让我做三件事,一是把这些接口做一个限流,这些限流参数是可配的,第二是这些接口可以设置开关,当发现问题时,可以手动关闭这些接口,不至于数据库压力过大影响核心业务的服务。第三是做接口的熔断,熔断设置可以配置。
经过确定,前两个实现用redis来实现,第三个因为熔断讨论觉得比较复杂,决定采用我提出的用Hystrix,目前项目不能热加载生效配置中心的最新的配置,所以后期推荐使用Archaius,这些网上查到的,具体为啥不选其他的,原因就是其他的比较复杂,上手感觉这个最快。
这篇文章说实现,其他问题不涉及,请多多指教。
思路
接口的屏蔽:通过AOP实现,每次访问接口的时候,通过接口的Key值,在Redis取到接口设置开关值,如果打开继续,否在拒绝。接口限流也是基于AOP,根据接口的Key值,取到这个接口的限流值,表示多长时间,限流几次,每次访问都会请求加一,通过比较,如果超过限制再返回,否在继续。
代码
AccessLimiter接口,主要有两类方法,是否开启限流,取Redis中的限流值
package com.hcfc.auto.util.limit;
import java.util.concurrent.TimeUnit;
/**
* @创建人 peng.wang
* @描述 访问限制器
*/
public interface AccessLimiter
/**
* 检查指定的key是否收到访问限制
* @param key 限制接口的标识
* @param times 访问次数
* @param per 一段时间
* @param unit 时间单位
* @return
*/
public boolean isLimited(String key, long times, long per, TimeUnit unit);
/**
* 移除访问限制
* @param key
*/
public void refreshLimited(String key);
/**
* 接口是否打开
* @return
*/
public boolean isStatus(String redisKey);
/**
* 接口的限流大小
* @param redisKeyTimes
* @return
*/
public long getTimes(String redisKeyTimes);
/**
* 接口限流时间段
* @param redisKeyPer
* @return
*/
public long getPer(String redisKeyPer);
/**
* 接口的限流时间单位
* @param redisKeyUnit
* @return
*/
public TimeUnit getUnit(String redisKeyUnit);
/**
* 是否删除接口限流
* @param redisKeyIsRefresh
* @return
*/
public boolean getIsRefresh(String redisKeyIsRefresh);
RedisAccessLimiter是AccessLimiter接口的实现类
package com.hcfc.auto.util.limit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @创建人 peng.wang
* @描述 基于Redis的实现
*/
@Component
public class RedisAccessLimiter implements AccessLimiter
private static final Logger LOGGER = LoggerFactory.getLogger(RedisAccessLimiter.class);
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean isLimited(String key, long times, long per, TimeUnit unit)
Long curTimes = redisTemplate.boundValueOps(key).increment(1);
LOGGER.info("curTimes ",curTimes);
if(curTimes > times)
LOGGER.debug("超频访问:[]",key);
return true;
else
if(curTimes == 1)
LOGGER.info(" set expire ");
redisTemplate.boundValueOps(key).expire(per, unit);
return false;
else
return false;
@Override
public void refreshLimited(String key)
redisTemplate.delete(key);
@Override
public boolean isStatus(String redisKey)
try
return (boolean)redisTemplate.opsForValue().get(redisKey+"IsOn");
catch (Exception e)
LOGGER.info("redisKey is not find or type mismatch, key: ", redisKey);
return false;
@Override
public long getTimes(String redisKeyTimes)
try
return (long)redisTemplate.opsForValue().get(redisKeyTimes+"Times");
catch (Exception e)
LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyTimes);
return 0;
@Override
public long getPer(String redisKeyPer)
try
return (long)redisTemplate.opsForValue().get(redisKeyPer+"Per");
catch (Exception e)
LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyPer);
return 0;
@Override
public TimeUnit getUnit(String redisKeyUnit)
try
return (TimeUnit) redisTemplate.opsForValue().get(redisKeyUnit+"Unit");
catch (Exception e)
LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyUnit);
return TimeUnit.SECONDS;
@Override
public boolean getIsRefresh(String redisKeyIsRefresh)
try
return (boolean)redisTemplate.opsForValue().get(redisKeyIsRefresh+"IsRefresh");
catch (Exception e)
LOGGER.info("redisKey is not find or type mismatch, key: ", redisKeyIsRefresh);
return false;
Limit标签接口,实现注解方式
package com.hcfc.auto.util.limit;
import java.lang.annotation.*;
/**
* @创建人 peng.wang
* @描述
*/
@Target(ElementType.METHOD,ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit
LimitAspect 切面的实现,实现接口屏蔽和限流的逻辑
package com.hcfc.auto.util.limit;
import com.hcfc.auto.vo.response.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @创建人 peng.wang
* @创建时间 2019/10/11
* @描述
*/
@Slf4j
@Aspect
@Component
public class LimitAspect
private static final Logger logger = LoggerFactory.getLogger(LimitAspect.class);
@Autowired
private AccessLimiter limiter;
@Autowired
GenerateRedisKey generateRedisKey;
@Pointcut("@annotation(com.hcfc.auto.util.limit.Limit)")
public void limitPointcut()
@Around("limitPointcut()")
public Object doArround(ProceedingJoinPoint joinPoint) throws Throwable
String redisKey = generateRedisKey.getMethodUrlConvertRedisKey(joinPoint);
long per = limiter.getPer(redisKey);
long times = limiter.getTimes(redisKey);
TimeUnit unit = limiter.getUnit(redisKey);
boolean isRefresh =limiter.getIsRefresh(redisKey);
boolean methodLimitStatus = limiter.isStatus(redisKey);
String bindingKey = genBindingKey(joinPoint);
if (methodLimitStatus)
logger.info("method is closed, key: ", bindingKey);
return ResponseDto.fail("40007", "method is closed, key:"+bindingKey);
//throw new OverLimitException("method is closed, key: "+bindingKey);
if(bindingKey !=null)
boolean isLimited = limiter.isLimited(bindingKey, times, per, unit);
if(isLimited)
logger.info("limit takes effect: ", bindingKey);
return ResponseDto.fail("40006", "access over limit, key: "+bindingKey);
//throw new OverLimitException("access over limit, key: "+bindingKey);
Object result = null;
result = joinPoint.proceed();
if(bindingKey!=null && isRefresh)
limiter.refreshLimited(bindingKey);
logger.info("limit refreshed: ", bindingKey);
return result;
private String genBindingKey(ProceedingJoinPoint joinPoint)
try
Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
return joinPoint.getTarget().getClass().getName() + "." + m.getName();
catch (Throwable e)
return null;
还有一个不重要的RedisKey实现类GenerateRedisKey和一个错误封装类,目前没有使用到,使用项目中其他的错误封装类了
GenerateRedisKey
package com.hcfc.auto.util.limit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
/**
* @创建人 peng.wang
* @描述
*/
@Component
public class GenerateRedisKey
public String getMethodUrlConvertRedisKey(ProceedingJoinPoint joinPoint)
StringBuilder redisKey =new StringBuilder("");
Method m = ((MethodSignature)joinPoint.getSignature()).getMethod();
RequestMapping methodAnnotation = m.getAnnotation(RequestMapping.class);
if (methodAnnotation != null)
String[] methodValue = methodAnnotation.value();
String dscUrl = diagonalLineToCamel(methodValue[0]);
return redisKey.append("RSK:").append("interfaceIsOpen:").append(dscUrl).toString();
return redisKey.toString();
private String diagonalLineToCamel(String param)
char UNDERLINE='/';
if (param==null||"".equals(param.trim()))
return "";
int len=param.length();
StringBuilder sb=new StringBuilder(len);
for (int i = 1; i < len; i++)
char c=param.charAt(i);
if (c==UNDERLINE)
if (++i<len)
sb.append(Character.toUpperCase(param.charAt(i)));
else
sb.append(c);
return sb.toString();
总结
关键的代码也就这几行,访问之前,对这个key值加一的操作,判断是否超过限制,如果等于一这个key加一之后的值为一,说明之前不存在,则设置这个key,放在Redis数据库中。
其实有更成熟的方案就是谷歌的Guava,领导说现在是咱们是分布式,不支持,还是用Redis实现吧,目前就这样实现了。其实我是新来的,好多东西还不太明白,很多决定都是上面决定的,我只是尽力实现罢了。不足之处,请多多指教!
推荐
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!
以上是关于接口的屏蔽和限流很难么?Redis全搞定!的主要内容,如果未能解决你的问题,请参考以下文章