接口的屏蔽和限流很难么?Redis全搞定
需求
线上出现的问题是,一些非核心的查询数据业务,在请求超时或者错误的时候,用户会越查询,导致数据库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
Python入门题041比较两个列表的异同题目给出2个列表,1,2和2,3。输出他们的相同元素,和不同元素。结果分别为2和1,3pythonlist集合操作异或操作视频教程Python入门题041比较两个列表的异同代码1l
我是真的不明白并且非常讨厌,现在的有些手机为什么用玻璃背壳,难道是自信的认为这样很帅?我曾经拿着iphone6,在高铁上电话打不出去,网也上不了。看着身边拿着华为的几位同事上网刷剧打电话都不耽误。我陷入了深深的思考中,装X却被同事取笑,说我买高档手机是拿来当装饰品的
java提供的线程池java提供的线程池Java中默认实现好的线程池又有哪些呢?今天就给大家分享一下Java中默认实现了的线程池,废话不多说,我们就一起直接看一下吧。SingleThreadExecu
为什么现在打滴滴越来越慢,好长时间打不到?为什么现在打滴滴越来越慢,好长时间打不到呢?我就是兼职跑滴滴的,下图为证,是我这几个月的流水我以滴滴司机的立场来谈谈原因,主要有以下几点司机没利润。由于平台派单规则,3公里以内都得
为什么说光刻机难以制作?光刻机是芯片制造中必不可少的精密设备。其难度甚至超过航空发动机。首先是在技术上的难度光刻机可以说每个部件都是科技含量很高,步步困难重重。瓶颈主要集中在透镜掩膜版光源能量控制器等。下
过几年计算机专业将烂大街呢,为什么?儿子今年大一计算机专业?计算机专业人才不会饱和,只是分工越来越细,要求越来越高,工资也会随着所从事的具体工作具体岗位而定。拿尖端的来说,华为够牛的吧,它还在不断地说需要芯片人才,需要创新人才。拿日常的来说
三星GalaxyS22明年春节前后发布梦幻配置,苹果表示压力很大由于上一代三星GalaxyS21功耗发热问题饱受用户的吐槽,表示骁龙888发热非常严重,不少三星用户表示想等GalaxyS22,根据最新的消息称,三星GalaxyS22预计将在明年
LCD永不为奴的背后究竟是什么前段时间,Redmi产品总监王腾在微博上与网友互动时的一句话引起了我的注意想要LCD旗舰可以考虑RedmiK30S至尊版,现在还有货。咋的,都2021年了还在推荐去年的旗舰呢?其实
曝Windows11致使游戏性能暴跌,iPhone12让路新机跌至爱疯价Windows11正式发布,你更新了吗?如果您使用的是AMD锐龙处理器并且喜欢玩游戏,那么您应该等待。AMD和微软官方今天宣布了两个AMD处理器在Windows11系统上的兼容性问
什么手机实用?过不了过久就是一年一度的双十一,手机厂商么每年都会发布自家双十一当天的从成绩,证明自己,国产手机现在基本都还停留在用出货量证明自己的层面,还没有哪一家开始用利润证明自己。往往在双十
助听器只佩戴一个,跟人沟通时有障碍吗?如果你的听力有一只耳朵正常,那就可以戴一个,如果两个耳朵都有听力损失,那就必须要两个耳朵同时佩戴。双耳配戴可以提高语言的清晰度,听声音是立体声,假如你只带一个就好像用一只眼睛看世界