您当前的位置:首页 > 电脑百科 > 数据库 > Redis

接口的屏蔽和限流很难么?Redis全搞定

时间:2022-03-24 09:20:30  来源:  作者:架构界的郭德纲

需求

线上出现的问题是,一些非核心的查询数据业务,在请求超时或者错误的时候,用户会越查询,导致数据库cup飙升,拖垮核心的业务。

领导让我做三件事,一是把这些接口做一个限流,这些限流参数是可配的,第二是这些接口可以设置开关,当发现问题时,可以手动关闭这些接口,不至于数据库压力过大影响核心业务的服务。第三是做接口的熔断,熔断设置可以配置。

经过确定,前两个实现用redis来实现,第三个因为熔断讨论觉得比较复杂,决定采用我提出的用Hystrix,目前项目不能热加载生效配置中心的最新的配置,所以后期推荐使用ArchAIus,这些网上查到的,具体为啥不选其他的,原因就是其他的比较复杂,上手感觉这个最快。

这篇文章说实现,其他问题不涉及,请多多指教。

思路

接口的屏蔽:通过AOP实现,每次访问接口的时候,通过接口的Key值,在Redis取到接口设置开关值,如果打开继续,否在拒绝。接口限流也是基于AOP,根据接口的Key值,取到这个接口的限流值,表示多长时间,限流几次,每次访问都会请求加一,通过比较,如果超过限制再返回,否在继续。

代码

 

接口的屏蔽和限流很难么?Redis全搞定

 

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 = diagonalL.NEToCamel(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实现吧,目前就这样实现了。其实我是新来的,好多东西还不太明白,很多决定都是上面决定的,我只是尽力实现罢了。不足之处,请多多指教!

作者:Ingram--MSN

来源:
blog.csdn.net/u010843114/article/details/102695570



Tags:Redis   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
兄弟,王者荣耀的段位排行榜是通过Redis实现的?
在王者荣耀中,我们会打排位赛,而且大家最关注的往往都是你的段位,还有在好友中的排名。作为程序员的你,是否思考过这个段位排行榜是怎么实现的?了解它的实现原理,会不会对上分有所...【详细内容】
2024-04-15  Search: Redis  点击:(3)  评论:(0)  加入收藏
16个Redis常见使用场景总结
来源:blog.csdn.net/qq_39938758/article/details/105577370目录 缓存 数据共享分布式 分布式锁 全局ID 计数器 限流 位统计 购物车 用户消息时间线timeline 消息...【详细内容】
2024-04-11  Search: Redis  点击:(8)  评论:(0)  加入收藏
Linux获取Redis 性能指标方法
一、监控指标&Oslash; 性能指标:Performance&Oslash; 内存指标: Memory&Oslash; 基本活动指标:Basic activity&Oslash; 持久性指标: Persistence&Oslash; 错误指标:Error二、监...【详细内容】
2024-04-11  Search: Redis  点击:(10)  评论:(0)  加入收藏
Redis与缓存一致性问题
缓存一致性问题是在使用缓存系统,如Redis时经常遇到的问题。当数据在原始数据源(如数据库)中发生变化时,如何确保缓存中的数据与数据源保持一致,是开发者需要关注的关键问题。一...【详细内容】
2024-04-11  Search: Redis  点击:(8)  评论:(0)  加入收藏
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  Search: Redis  点击:(23)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25  Search: Redis  点击:(13)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  Search: Redis  点击:(21)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  Search: Redis  点击:(14)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  Search: Redis  点击:(50)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  Search: Redis  点击:(44)  评论:(0)  加入收藏
▌简易百科推荐
兄弟,王者荣耀的段位排行榜是通过Redis实现的?
在王者荣耀中,我们会打排位赛,而且大家最关注的往往都是你的段位,还有在好友中的排名。作为程序员的你,是否思考过这个段位排行榜是怎么实现的?了解它的实现原理,会不会对上分有所...【详细内容】
2024-04-15    dbaplus社群  Tags:Redis   点击:(3)  评论:(0)  加入收藏
16个Redis常见使用场景总结
来源:blog.csdn.net/qq_39938758/article/details/105577370目录 缓存 数据共享分布式 分布式锁 全局ID 计数器 限流 位统计 购物车 用户消息时间线timeline 消息...【详细内容】
2024-04-11    书圈  Tags:Redis   点击:(8)  评论:(0)  加入收藏
Linux获取Redis 性能指标方法
一、监控指标&Oslash; 性能指标:Performance&Oslash; 内存指标: Memory&Oslash; 基本活动指标:Basic activity&Oslash; 持久性指标: Persistence&Oslash; 错误指标:Error二、监...【详细内容】
2024-04-11  上海天正信息科技有限    Tags:Redis   点击:(10)  评论:(0)  加入收藏
Redis与缓存一致性问题
缓存一致性问题是在使用缓存系统,如Redis时经常遇到的问题。当数据在原始数据源(如数据库)中发生变化时,如何确保缓存中的数据与数据源保持一致,是开发者需要关注的关键问题。一...【详细内容】
2024-04-11  后端Q    Tags:Redis   点击:(8)  评论:(0)  加入收藏
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  dbaplus社群    Tags:Redis   点击:(23)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25    51CTO  Tags:Redis   点击:(13)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  后端Q  微信公众号  Tags:Redis   点击:(21)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  OSC开源社区    Tags:Redis   点击:(14)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  编程技术汇    Tags:Redis   点击:(50)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  楼仔  微信公众号  Tags:Redis   点击:(44)  评论:(0)  加入收藏
站内最新
站内热门
站内头条