点击关注公众号,实用技术文章及时了解

来源: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);@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic 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;}}}@Overridepublic void refreshLimited(String key) {redisTemplate.delete(key);}@Overridepublic 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;}}@Overridepublic 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;}}@Overridepublic 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;}}@Overridepublic 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;}}@Overridepublic 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);@Autowiredprivate AccessLimiter limiter;@AutowiredGenerateRedisKey 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实现吧,目前就这样实现了。其实我是新来的,好多东西还不太明白,很多决定都是上面决定的,我只是尽力实现罢了。不足之处,请多多指教!

推荐

主流Java进阶技术(学习资料分享)

Java面试题宝典

加入Spring技术开发社区

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

接口的屏蔽和限流很难么?Redis全搞定!相关推荐

  1. 分布式接口幂等性、分布式限流总结整理

    - 1 - 文章目录 一.接口幂等性 1.Update操作的幂等性 1)根据唯一业务号去更新数据 2.使用Token机制,保证update.insert操作的幂等性 1)没有唯一业务号的update与 ...

  2. 快手引流卖什么暴利?很多人现在都觉得在各大平台引流很难

    快手引流卖什么暴利?很多人现在都觉得在各大平台引流很难 在快手引流及变现的一个冷门模式的思路.废话不多说,直接进入正题. 很多人现在都觉得在各大平台引流很难了,而且粉丝还不精准,确实也是如此,现在各大 ...

  3. android+延迟拍摄,延时摄影很难吗? iphone拍+后期全搞定

    手机也能拍出大片,还是目前高端大气的延时摄影,这听起来有点儿不可思议!但如果你的智能手机支持延时摄影拍摄,你还真可以用手机拍大片,甚至说后期都全靠手机来制作.不信你且看我娓娓道来. 在生物演变.天体运 ...

  4. 告别卸载软件难 四大方法轻松搞定

    我们常会遇到一些永远不会使用的原装软件,这些软件占据着你的系统空间,但是却像狗皮膏药一样赖着不走.前些日的苹果IOS10更新后,很多人都卸载了邮件等部分APP,而近日,被誉为"全家桶&quo ...

  5. 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流

    点击关注公众号,实用技术文章及时了解 一.接口幂等性 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用.举个最简单的例子,那就是支付,用户购买商品 ...

  6. 如何优雅的实现分布式接口幂等性、分布式限流(荣耀典藏版)

    目录 一.接口幂等性 1.Update操作的幂等性 2.使用Token机制,保证update.insert操作的幂等性 二.分布式限流 2.1.分布式限流的几种维度 2.1.1.QPS和连接数控制 2 ...

  7. 分布式接口幂等性、分布式限流(Guava 、nginx和lua限流)

    一.接口幂等性 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用.举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时 ...

  8. Sentinel结合Fejgn接口,进行调用远程接口的调用和限流

    sentinel 适配了 Feign 组件.如果想使用,除了引入 sentinel-starter 的依赖外还需要 2 个步骤: 1.引入依赖 <!--feign对sentinel的支持--&g ...

  9. API Gateway/API 网关(三) - Kong的使用 - 限流rate limiting(redis)

    一.前言 Kong的限流支持三种方式,分别是本地限流(Local).数据库限流(Cluster)和Redis限流,这三种限流方式采用的限流算法都是计数器法.支持按照秒/分/小时/日/月/年等不同时间单 ...

最新文章

  1. java web dao_JavaWeb项目,DAO应该怎么写?
  2. python os模块下载_Python OS模块目录文件处理
  3. Boost:异步操作,涉及重新打包多个操作,但选择仅调用其中一个的测试程序
  4. [支持库] 易语言超文本浏览框支持库3.1#51
  5. ssl提高组国庆模拟赛【2018.10.5】
  6. 关于mac的open命令
  7. 实现微信小程序版本管理
  8. 原生js实现锚点定位,tab跟随内容变化,PC/移动端均适用
  9. 开心豆少儿英语好吗,收费怎么样,一年多少钱的学费
  10. SQLite3使用详解之二
  11. java 取上界_Java中的上界通配符 - java
  12. Oracle dual表详解(zzl)
  13. php漂浮,【飘】【漂】:【飘浮】【漂浮】、【漂泊】【飘泊】【飘薄】
  14. 艾永亮:酒店浮沉录,睡不明白的生意经
  15. 推荐系统架构及流程说明
  16. hyper-v 搭建本地yum镜像源,以及hadoop完全分布式 name节点分离集群
  17. 计算机视觉基础之数字图像(1)
  18. 112.使用 sketch.js 实现彗星特效
  19. Ae 表达式语言引用​之:Property
  20. 搭建自己网站----内网穿透

热门文章

  1. “摔杯一怒为俞渝” 当当创始人李国庆:蓄谋已久的阴谋 不吐不快
  2. “浴霸”改“花洒”?华为Mate 30最新保护壳谍照曝光...
  3. 拳王寻你项目公社:普通人怎么创业,普通人的创业法宝,容易上手的兼职副业项目
  4. 首秀双折叠屏手机却被友商炮轰 小米回应:悍然碰瓷!
  5. R40 gpio 寄存器地址操作【原创】
  6. 我的世界药水合成表图Java_我的世界药水合成表图高清配方-我的世界药水合成表图一览...
  7. 再探正则表达式c++-html中搜索url
  8. 应用人工智能识别增加教育医疗产品的趣味和实用性
  9. opencv 高反差保留算法
  10. iptables学习笔记:端口转发命令优化