win64安装redis,同时鞋废一手redis接口限流,码垛,真滴C

  1. 下载网址:windows64位redis

  2. 解压下载的安装包,我的系统只有一个C盘直接扔在C盘,新建一个redis文件夹放下载的压缩包,解压
    在这里插入图片描述

  3. 修改默认配置,打开解压的文件,找到redis.windows.conf修改默认密码
    在这里插入图片描述
    在这里插入图片描述

  4. 启动服务,方法有很多,我喜欢比较快的,直接拖动redis.windows.conf到redis-server.exe就启动了,启动后的窗口不要关闭,可以最小化,不然服务就没了,如下视频

启动redis服务

  1. 连接测试,在文件夹中填写cmd按回车,打开命令窗口
    在这里插入图片描述

输入命令连接服务redis-cli.exe -h 127.0.0.1 -p 6379 -a 123456
在这里插入图片描述
PS:host:127.0.0.1代表本机地址
port: 6379 端口号
authentication: 123456 密码

往redis写数据 set testKey 1
获取指定key数据get testKey
在这里插入图片描述

码垛,Redis都安装好了,不深入学习一波?直接来个java+springBoot+springRedis完成一个接口限流不香吗?

这是笔者在微信公众号上面看到的文章哈,都是各位大神的贡献成果我自己鞋废了,也分享给兄弟们,有侵权的地方兄弟们指出删帖哈,丧失狗叫权!
在这里插入图片描述

先说想法,理清思路好办事,达到事半功倍的效果。

  1. 自定义注解给限流的接口使用
  2. 通过aop实现拦截自定义的注解
  3. redis存储限制请求接口key在过期时间内叠加的次数
  4. 获取redis存储的次数并与指定的总次数做比对,返回是否超过限制次数结果
    思路理清了,接下来开始编码

依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--redis接口限流依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

配置文件:

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456

编写自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLimit {
    // 限流key
    String key() default "rate_limit:";

    // 默认每60s限制请求100次以内接口
    int time() default 60;
    int count() default 100;

    // 限流类型
    LimitType limitType() default LimitType.Default;
}

写拦截,采用spring的面向切面编程思维,简称aop

@Aspect
@Component
@Slf4j
public class RateLimitAspect {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private RedisScript<Long> limitScript;

    @Before("@annotation(requestLimit)")
    public void doBefore(JoinPoint point, RequestLimit requestLimit) {
        String key = requestLimit.key();
        key = buildKey(key, point.getSignature().getName(), requestLimit.limitType());
        int time = requestLimit.time();
        int count = requestLimit.count();
        List<Object> keys = Collections.singletonList(key);
        Long number = redisTemplate.execute(limitScript, keys, count, time);
        if (number == null || number.intValue() > count) {
            throw new WebServiceException("访问过于频繁,请稍后重试");
        }
        log.info("{}s内限制请求总次数:{},当前请求已用次数:{},缓存key:{}", time, count, number.intValue(), key);
    }

    /**
     * key = 默认前缀:函数名:是否ip?ip:无
     * @param key
     * @param methodName
     * @param type
     * @return
     */
    private String buildKey(String key, String methodName, LimitType type) {
        StringBuffer result = new StringBuffer(key);
        result.append(methodName);
        if (type.equals(LimitType.IP)) {
            HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = IPUtils.getIPAddress(req);
            if(StringUtils.isNotBlank(ip)) result.append(":").append(ip);
        }
        return result.toString();
    }
}

注意这里@Before(“@annotation(requestLimit)”)在这里插入图片描述

Ip地址获取工具类,度娘里一大堆,不好用的自己去抠

public class IPUtils {
    public static String getIPAddress(HttpServletRequest request) {
        String ip = null;

        //X-Forwarded-For:Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP:apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP:weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP:有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP:nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }

        //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到,最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


接下来,抄代码的时候有没看到limitScript这个对象?是不是好奇这个是啥,我先上代码你们看了就明白了

@Configuration
public class LuaExecuteConfig {
    @Bean
    public DefaultRedisScript<Long> limitScript(){
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/ratelimit.lua")));
        redisScript.setResultType(Long.class);
        return redisScript;
    }
}

注意这一段代码 在这里插入图片描述

加载的是这个文件,路径很重要兄弟,不要粗心了乱放
在这里插入图片描述

文件里的内容

local key = KEYS[1]
local count = tonumber(ARGV[1])
local time = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count then
    return tonumber(current)
end
current = redis.call('incr', key)
if tonumber(current) == 1 then
    redis.call('expire', key, time)
end
return tonumber(current)

写到这里相信就明白了limitScript是干嘛用的了,没错,小伙伴们真滴C,就是Spring redisTemplate执行lua脚本用的,为啥要使用lua脚本?直接告诉你们一个特性:原子性,这段脚本意思是第一次请求,给过期时间(传参进来的),第二次进来就返回值并+1,场景就是访问次数+1

好了,注意一下这个
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
这里自定义序列化防止乱码

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        // 使用Jackson2JsonRedisSerializer序列化
        Jackson2JsonRedisSerializer<Object> js = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        js.setObjectMapper(mapper);
        redisTemplate.setKeySerializer(js);
        redisTemplate.setValueSerializer(js);
        redisTemplate.setHashValueSerializer(js);
        redisTemplate.setHashKeySerializer(js);
        return redisTemplate;
    }
}

写完了,收工。额打扰了,还是测试下吧在这里插入图片描述

@RestController
@Slf4j
@RequestMapping("/async/executor")
public class AsyncExecutorController {
    private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @GetMapping("ramitTest")
    @RequestLimit(time = 20, count = 5, limitType = LimitType.IP)
    public String testRateLimit() {
        return "request time:" + df.format(new Date());
    }
}

看结果吧,期望:20s内Ip的方式限制请求5次,超过5次得等过完20s才让重新请求
在这里插入图片描述
拿下redis新场景,码垛,兄弟们真滴C,直接鞋废了
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值