Redis限流和事件通知

一、限流

1、定义一个注解标明需要使用限流的接口

import java.lang.annotation.*;
 
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 
 
@Retention(RUNTIME)
@Target(METHOD)
 
public @interface AccessLimit {
    int seconds();
    int maxCount();
}

2、配置拦截器

import com.alibaba.fastjson.JSONObject;
import com.vip.file.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.HashMap;
 
@Component
public class WebSecurityConfig implements HandlerInterceptor {
    @Autowired
    private RedisService redisService;
 
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        //如果请求输入方法
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            //获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit != null) {
                long seconds = accessLimit.seconds();
                int maxCount = accessLimit.maxCount();
//关于key的生成规则可以自己定义 本项目需求是对每个方法都加上限流功能,如果你只是针对ip地址限流,那么key只需要只用ip就好
//                String key =     SystemUtil.getClientIp(httpServletRequest)+hm.getMethod().getName();
                String key = "192.168.2.149";
 
                HashMap<Object, Object> hashMap = new HashMap<>();
                //从redis中获取用户访问的次数
                try {
                    long q = redisService.incr(key, seconds);//此操作代表获取该key对应的值自增1后的结果
                    System.out.println("从redis中获取用户访问的次数q: "+q);
                    if (q > maxCount) {
                        //加1
//                        render(httpServletResponse, new ResponseMsg(0, "请求过于频繁,请稍候再试", null)); //这里的CodeMsg是一个返回参数
                        render(httpServletResponse, "请求过于频繁,请稍候再试"); //这里的CodeMsg是一个返回参数
                        System.out.println("请求过于频繁,请稍候再试");
                        return false;
                    }
                    return true;
                } catch (RedisConnectionFailureException e) {
//                    logger.info("redis错误" + e.getMessage().toString());
                    System.out.println("redis错误" + e.getMessage().toString());
                    return true;
                }
            }
 
        }
 
 
        return false;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
 
    }
 
    private void render(HttpServletResponse response, String cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("message", "请求过于频繁,请稍候再试");
        JSONObject data = new JSONObject(hashMap);
        out.write(data.toJSONString().getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}

3、注册拦截器,设置拦截的接口路径 /pdf/test1

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private WebSecurityConfig webSecurityConfig;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(webSecurityConfig).addPathPatterns("/pdf/test1").excludePathPatterns("/static/**","/login.html","/user/login");
    }

4、上面使用到的redisservice

public interface  RedisService {
 
    /**
     * set存数据
     * @param key
     * @param value
     * @return
     */
    boolean set(String key, String value);
 
    /**
     * get获取数据
     * @param key
     * @return
     */
    String get(String key);
 
    /**
     * 设置有效天数
     * @param key
     * @param expire
     * @return
     */
    boolean expire(String key, long expire);
 
    /**
     * 移除数据
     * @param key
     * @return
     */
    boolean remove(String key);
 
    /**
     * 获取自增1后的 值
     * @param key
     * @param time
     * @return
     */
    Long incr(String key,long time);
}
import com.vip.file.service.RedisService;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
 
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
 
@Service("redisService")
public class RedisServiceImpl implements RedisService {
 
 
    @Resource
    private RedisTemplate<String, ?> redisTemplate;
 
 
    @Override
    public boolean set(final String key, final String value) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                return true;
            }
        });
        return result;
    }
 
    @Override
    public String get(final String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] value = connection.get(serializer.serialize(key));
                return serializer.deserialize(value);
            }
        });
        return result;
    }
 
    @Override
    public boolean expire(final String key, long expire) {
        return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }
 
    @Override
    public boolean remove(final String key) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.del(key.getBytes());
                return true;
            }
        });
        return result;
    }
    @Override
    public Long incr(String key,long time){
        long count = redisTemplate.opsForValue().increment(key, 1);
        if (count == 1) {
            //设置有效期一分钟
            set(key,"1");
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
        return count;
    }
}

 接口

    @AccessLimit(seconds = 5,maxCount = 5)
    @GetMapping("/test1")
    public void test2(){
        System.out.println("/test1");
    }

 

 并发100请求,只限流5个请求

二、事件通知

Keyspace 通知使客户端可以通过订阅频道来接收那些以某种方式改动了 Redis 数据集的事件(触发某些事件后可以向指定的频道发送通知),该功能需 Redis 版本大于 2.8。

问题:

    设置了生存时间的Key,在过期时能不能有所提示?
    如果能对过期key一个监听,如何对过期key进行一个回调处理?

Key过期事件的Redis配置

 

notify-keyspace-events Ex

notify-keyspace-events 的参数为 “Ex”。x 代表了过期事件

ok  重启redis

下面请看代码演示

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.1</version>
        </dependency>

application.yml

  redis:
    host: localhost
    port: 6379
   #password: xxx
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接
    timeout: 3000 # 连接超时时间(毫秒)

RedisConfig

package com.example.config;
 
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
import java.lang.reflect.Method;
 
 
@Configuration
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuffer sb = new StringBuffer();
                sb.append(o.getClass().getName()).append(method.getName());
                for (Object object : objects) {
                    sb.append(object.toString());
                }
                System.out.println(sb.toString());
                return sb.toString();
            }
        };
    }
 
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(json()));
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
        //return RedisCacheManager.create(lettuceConnectionFactory);
    }
 
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
        Jackson2JsonRedisSerializer jsonSerializer = json();
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jsonSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jsonSerializer);
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        return redisTemplate;
    }
 
    private Jackson2JsonRedisSerializer json() {
        Jackson2JsonRedisSerializer jsonSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jsonSerializer.setObjectMapper(om);
        return jsonSerializer;
    }
 
    @Bean
    RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory) {
 
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}
 

RedisKeyEExpirationListener.class

package com.example.config;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
 
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
//    @Resource
//    SeatService seatService;
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String expiredKey = message.toString();
        if(expiredKey.equals("keyTest")){
            log.info(expiredKey + "----动作触发");
//            seatService.updateToFreeSeat("1");
        }
        try {
            //业务处理
        } catch (Exception e) {
            log.error("key 过期通知处理异常,{}", e);
        }
 
    }
 
}

ProductController

import com.example.config.CIpProperties;
import com.example.config.NameProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
import java.util.concurrent.TimeUnit;
 
@Controller
public class ProductController {
    @Autowired
private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private CIpProperties cIpProperties;
    @Autowired
    private NameProperties nameProperties;
    @RequestMapping
    @ResponseBody
    public String hello(){
        return "success";
    }
 
    @RequestMapping("/test")
    @ResponseBody
    public Object test(){
        stringRedisTemplate.opsForValue().set("keyTest","1",10, TimeUnit.SECONDS);
        return null;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

越来越没意思

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值