zoukankan      html  css  js  c++  java
  • 基于redis实现IP访问频次控制

    一、背景描述

    思路:以类名+调用方法名+ip作为key

    1. 当用户调用接口的时候,先查询redis中是否有存在该key,获取该key所对应的value,比较value和frequency,如果小于frequency,则在原来的基础上value++;如果大于则返回访问频率过于频繁。
    2. 如果不存在,则将该key存入redis,value为1,设置过期时间。

    二、代码演示

    1. pom文件

    <!--引入web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--spring boot 测试-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--分布式锁-->
    <dependency>
        <groupId>net.javacrumbs.shedlock</groupId>
        <artifactId>shedlock-provider-redis-spring</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>net.javacrumbs.shedlock</groupId>
        <artifactId>shedlock-spring</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!--连接池-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.0</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- swagger -->
    <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>swagger-bootstrap-ui</artifactId>
        <version>1.9.6</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.2</version>
    </dependency>

    2、redis的配置

      1.  配置文件
        #redis
        redis.host=192.168.1.6
        redis.password=
        redis.port=6379
        redis.taskScheduler.poolSize=100
        redis.taskScheduler.defaultLockMaxDurationMinutes=10
        redis.default.timeout=10
        redisCache.expireTimeInMilliseconds=1200000
         配置类
    package com.example.redis_demo_limit.redis;
    
    import io.lettuce.core.ClientOptions;
    import io.lettuce.core.resource.ClientResources;
    import io.lettuce.core.resource.DefaultClientResources;
    import net.javacrumbs.shedlock.core.LockProvider;
    import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
    import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration;
    import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder;
    import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.RedisPassword;
    import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
    import org.springframework.data.redis.core.RedisTemplate;
    
    import java.time.Duration;
    
    @Configuration
    public class RedisConfig {
        @Value("${redis.host}")
        private String redisHost;
    
        @Value("${redis.port}")
        private int redisPort;
    
        @Value("${redis.password}")
        private String password;
    
        @Value("${redis.taskScheduler.poolSize}")
        private int tasksPoolSize;
        @Value("${redis.taskScheduler.defaultLockMaxDurationMinutes}")
        private int lockMaxDuration;
    
        @Bean(destroyMethod = "shutdown")
        ClientResources clientResources() {
            return DefaultClientResources.create();
        }
    
        @Bean
        public RedisStandaloneConfiguration redisStandaloneConfiguration() {
            RedisStandaloneConfiguration redisStandaloneConfiguration =
                    new RedisStandaloneConfiguration(redisHost, redisPort);
            if (password != null && !password.trim().equals("")) {
                RedisPassword redisPassword = RedisPassword.of(password);
                redisStandaloneConfiguration.setPassword(redisPassword);
            }
            return redisStandaloneConfiguration;
        }
    
        @Bean
        public ClientOptions clientOptions() {
            return ClientOptions.builder()
                    .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                    .autoReconnect(true).build();
        }
    
        @Bean
        LettucePoolingClientConfiguration lettucePoolConfig(ClientOptions options, ClientResources dcr) {
            return LettucePoolingClientConfiguration.builder().poolConfig(new GenericObjectPoolConfig())
                    .clientOptions(options).clientResources(dcr).build();
        }
    
        @Bean
        public RedisConnectionFactory connectionFactory(
                RedisStandaloneConfiguration redisStandaloneConfiguration,
                LettucePoolingClientConfiguration lettucePoolConfig) {
            return new LettuceConnectionFactory(redisStandaloneConfiguration, lettucePoolConfig);
        }
    
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        @Primary
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
            return new RedisLockProvider(connectionFactory);
        }
    
        @Bean
        public ScheduledLockConfiguration taskSchedulerLocker(LockProvider lockProvider) {
            return ScheduledLockConfigurationBuilder.withLockProvider(lockProvider)
                    .withPoolSize(tasksPoolSize).withDefaultLockAtMostFor(Duration.ofMinutes(lockMaxDuration))
                    .build();
        }
    }

    3. redis操作工具类

      1. 接口类
        package com.example.redis_demo_limit.redis;
        
        
        public interface DataCacheRepository<T> {
        
          boolean add(String collection, String hkey, T object, Long timeout);
        
          boolean delete(String collection, String hkey);
        
          T find(String collection, String hkey, Class<T> tClass);
        
          Boolean isAvailable();
        
          /**
           * redis 加锁
           * 
           * @param key
           * @param second
           * @return
           */
          Boolean lock(String key, String value, Long second);
        
          Object getValue(String key);
        
          /**
           * redis 解锁
           * 
           * @param key
           * @return
           */
          void unLock(String key);
        
          void setIfAbsent(String key, long value, long ttl);
        
          void increment(String key);
        
          Long get(String key);
        
          void set(String key, long value, long ttl);
        
          void set(Object key, Object value, long ttl);
        
          Object getByKey(String key);
        
        
          void getLock(String key, String clientID) throws Exception;
        
          void releaseLock(String key, String clientID);
        }
      2. 实现类
        package com.example.redis_demo_limit.redis;
        
        import com.fasterxml.jackson.databind.ObjectMapper;
        import lombok.extern.slf4j.Slf4j;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.data.redis.core.RedisTemplate;
        import org.springframework.data.redis.core.ValueOperations;
        import org.springframework.data.redis.support.atomic.RedisAtomicLong;
        import org.springframework.stereotype.Repository;
        
        import java.time.Duration;
        import java.util.TimeZone;
        import java.util.concurrent.TimeUnit;
        
        @Slf4j
        @Repository
        public class CacheRepository<T> implements com.example.redis_demo_limit.redis.DataCacheRepository<T> {
        
          private static final ObjectMapper OBJECT_MAPPER;
          private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("UTC");
        
          static {
            OBJECT_MAPPER = new ObjectMapper();
            OBJECT_MAPPER.setTimeZone(DEFAULT_TIMEZONE);
          }
        
          Logger logger = LoggerFactory.getLogger(CacheRepository.class);
          @Autowired
          RedisTemplate template; // and we're in business
          @Value("${redis.default.timeout}00")
          Long defaultTimeOut;
        
          public boolean addPermentValue(String collection, String hkey, T object) {
            try {
              String jsonObject = OBJECT_MAPPER.writeValueAsString(object);
              template.opsForHash().put(collection, hkey, jsonObject);
              return true;
            } catch (Exception e) {
              logger.error("Unable to add object of key {} to cache collection '{}': {}", hkey, collection,
                  e.getMessage());
              return false;
            }
          }
        
          @Override
          public boolean add(String collection, String hkey, T object, Long timeout) {
        
            Long localTimeout;
            if (timeout == null) {
              localTimeout = defaultTimeOut;
            } else {
              localTimeout = timeout;
            }
            try {
              String jsonObject = OBJECT_MAPPER.writeValueAsString(object);
              template.opsForHash().put(collection, hkey, jsonObject);
              template.expire(collection, localTimeout, TimeUnit.SECONDS);
              return true;
            } catch (Exception e) {
              logger.error("Unable to add object of key {} to cache collection '{}': {}", hkey, collection,
                  e.getMessage());
              return false;
            }
          }
        
          @Override
          public boolean delete(String collection, String hkey) {
            try {
              template.opsForHash().delete(collection, hkey);
              return true;
            } catch (Exception e) {
              logger.error("Unable to delete entry {} from cache collection '{}': {}", hkey, collection,
                  e.getMessage());
              return false;
            }
          }
        
          @Override
          public T find(String collection, String hkey, Class<T> tClass) {
            try {
              String jsonObj = String.valueOf(template.opsForHash().get(collection, hkey));
              return OBJECT_MAPPER.readValue(jsonObj, tClass);
            } catch (Exception e) {
              if (e.getMessage() == null) {
                logger.error("Entry '{}' does not exist in cache", hkey);
              } else {
                logger.error("Unable to find entry '{}' in cache collection '{}': {}", hkey, collection,
                    e.getMessage());
              }
              return null;
            }
          }
        
          @Override
          public Boolean isAvailable() {
            try {
              return template.getConnectionFactory().getConnection().ping() != null;
            } catch (Exception e) {
              logger.warn("Redis server is not available at the moment.");
            }
            return false;
          }
        
          @Override
          public Boolean lock(String key, String value, Long second) {
            Boolean absent = template.opsForValue().setIfAbsent(key, value, second, TimeUnit.SECONDS);
            return absent;
          }
        
          @Override
          public Object getValue(String key) {
            return template.opsForValue().get(key);
          }
        
          @Override
          public void unLock(String key) {
            template.delete(key);
          }
        
          @Override
          public void increment(String key) {
            RedisAtomicLong counter = new RedisAtomicLong(key, template.getConnectionFactory());
            counter.incrementAndGet();
          }
        
          @Override
          public void setIfAbsent(String key, long value, long ttl) {
            ValueOperations<String, Object> ops = template.opsForValue();
            ops.setIfAbsent(key, value, Duration.ofSeconds(ttl));
          }
        
          @Override
          public Long get(String key) {
            RedisAtomicLong counter = new RedisAtomicLong(key, template.getConnectionFactory());
            return counter.get();
          }
        
          @Override
          public void set(String key, long value, long ttl) {
            RedisAtomicLong counter = new RedisAtomicLong(key, template.getConnectionFactory());
            counter.set(value);
            counter.expire(ttl, TimeUnit.SECONDS);
          }
        
          @Override
          public void set(Object key, Object value, long ttl) {
            template.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);
          }
        
          @Override
          public Object getByKey(String key) {
            return template.opsForValue().get(key);
          }
        
          @Override
          public void getLock(String key, String clientID) throws Exception {
            Boolean lock = false;
        
            // 重试3次,每间隔1秒重试1次
            for (int j = 0; j <= 3; j++) {
              lock = lock(key, clientID, 10L);
              if (lock) {
                log.info("获得锁》》》" + key);
                break;
              }
              try {
                Thread.sleep(5000);
              } catch (InterruptedException e) {
                log.error("线程休眠异常", e);
                break;
              }
            }
            // 重试3次依然没有获取到锁,那么返回服务器繁忙,请稍后重试
            if (!lock) {
              throw new Exception("服务繁忙");
            }
          }
        
          @Override
          public void releaseLock(String key, String clientID) {
            if (clientID.equals(getByKey(key))) {
              unLock(key);
            }
          }
        }

        4. 访问频次实现核心逻辑

          1. 注解
            package com.example.redis_demo_limit.annotation;
            
            import java.lang.annotation.*;
            
            @Documented
            @Target(ElementType.METHOD)
            @Retention(RetentionPolicy.RUNTIME)
            public @interface LimitedAccess {
              /**
               * 从第一次访问接口的时间到周期时间内,最大访问频率次,默认60次
               * @return
               */
              long frequency() default 60;
            
              /**
               * 周期时间,默认30分钟内
               * @return
               */
              long second() default 30*60;
            }
          2. 切面类
            package com.example.redis_demo_limit.annotation;
            
            import com.example.redis_demo_limit.redis.DataCacheRepository;
            import lombok.extern.log4j.Log4j2;
            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.springframework.beans.factory.annotation.Autowired;
            import org.springframework.stereotype.Component;
            import org.springframework.web.context.request.RequestContextHolder;
            import org.springframework.web.context.request.ServletRequestAttributes;
            
            import javax.servlet.http.HttpServletRequest;
            
            @Aspect
            @Component
            @Log4j2
            //@Order
            public class LimitedAccessAspect {
            
                public static String LIMITED_ACCESS_ASPECT_COLLECTION = "LIMITED_ACCESS_ASPECT_COLLECTION";
            
                @Autowired
                private DataCacheRepository redisCacheService;
            
                @Pointcut("@annotation(limitedAccess)")
                public void limitAccessPointCut(LimitedAccess  limitedAccess) {
                    // 限制接口调用切面类
                }
            
                @Around(value = "limitAccessPointCut(limitedAccess)", argNames = "point,limitedAccess")
                public Object doAround(ProceedingJoinPoint point, LimitedAccess limitedAccess) throws Throwable {
                    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                    if (null != attributes) {
                        String className = point.getTarget().getClass().getName();
                        String methodName = point.getSignature().getName();
                        HttpServletRequest request = attributes.getRequest();
                        String remoteAddr = request.getRemoteAddr();
                        log.info("remoteAddr地址:" + remoteAddr);
                        //String realRequestIps = request.getHeader("X-Forwarded-For");
            
            
                        String key = LIMITED_ACCESS_ASPECT_COLLECTION + className + "." + methodName + "#" + remoteAddr;
                        try {
                            long limit = redisCacheService.get(key);
                            if (limit > 0) {
                                // 时间段内超过访问频次上限 - 阻断
                                if (limit >= limitedAccess.frequency()) {
                                    log.info("接口调用过于频繁 {}", key);
            //
                                    return "接口调用过于频繁!!!";
                                }
                                redisCacheService.increment(key);
                            } else {
                                redisCacheService.set(key, 1, limitedAccess.second());
                            }
                        } catch (Exception e) {
                            log.debug(e.getStackTrace());
                        }
                    }
                    return point.proceed();
                }
            }

            三、调用方法

            package com.example.redis_demo_limit.controller;
            
            import com.example.redis_demo_limit.annotation.LimitedAccess;
            import com.example.redis_demo_limit.redis.DataCacheRepository;
            import org.springframework.web.bind.annotation.PostMapping;
            import org.springframework.web.bind.annotation.RequestMapping;
            import org.springframework.web.bind.annotation.RestController;
            
            import javax.annotation.Resource;
            
            @RestController
            @RequestMapping("/redis")
            public class RedisController {
            
                @Resource
                private DataCacheRepository dataCacheRepository;
            
                //这个设置为1秒1次,方便测试
                @LimitedAccess(frequency = 1,second = 1)
                @PostMapping("/add")
                public String add(String str){
                    dataCacheRepository.set("str","add success",200L);
                    return "success";
                }
            }

            原文链接:https://zhuanlan.zhihu.com/p/257455401

         
  • 相关阅读:
    快速分栏
    伪元素:before和:after的简单应用——清除浮动
    C# 调用事件
    C# 创建文件夹
    (C#-VisionPro)用代码方式新建VisionPro视觉文件(.vpp)
    C# 选择下拉框文件时触发
    C# 将指定目录下的文件夹名称加载到下拉框中
    (C#-VisionPro)用C#加载、保存ViaionPro的'.vpp'文件
    C# 关闭程序时保存程序数据
    (C#-VisionPro)用C#调用VisionPro保存的.vpp文件
  • 原文地址:https://www.cnblogs.com/fengwenzhee/p/14308817.html
Copyright © 2011-2022 走看看