SpringBoot、Redis、Jedis(JedisPool) 分布式锁、分布式限流 详解

前言:网上针对基于Redis的分布式锁,分布式限流的教程很多,也很全面,但是大多重点着墨于分布式锁和限流的实现细节,笔者阅读完之后,可以很好的梳理出 相应的逻辑,但是具体操作时,却发现缺少了Jedis连接池的部分细节,导致仍然要花点时间去研究下,所以 笔者想写一篇Blog从头至尾介绍 Jedis配置、分布式锁、分布式限流的实现细节,目的在于 让读者仅靠一篇Blog就可以实操基于Redis的分布式锁和分布式限流(部分)。

一、Jedis配置

maven:  Jedis和Lombk

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <scope>provided</scope>
        </dependency>
<!--ExceptionUtils.getFullStackTrace(e)-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
            <scope>compile</scope>
        </dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.0.6.RELEASE</version>
        </dependency>

yml配置:

jedis:
  pool:
    host: 127.0.0.1
    port: 6379
    config:
      maxTotal: 50  #最大连接数
      maxIdle: 5    #最大空闲连接数
      maxWaitMillis: 3000  #获取连接时的最大等待时间
      password:

配置读取类:

package com.example.redislock;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @author fandong
 * @create 2018/10/24
 */
@Configuration
@Data   
public class JedisPoolConfigProperties {

    @Value("${jedis.pool.host}")
    private String host;
    @Value("${jedis.pool.port}")
    private Integer port;
    @Value("${jedis.pool.config.maxTotal}")
    private Integer maxTotal;
    @Value("${jedis.pool.config.maxIdle}")
    private Integer maxIdle;
    @Value("${jedis.pool.config.maxWaitMillis}")
    private Long maxWaitMillis;
    @Value("${jedis.pool.config.password}")
    private String password;

    public JedisPoolConfigProperties() {
    }
}

JedisPool配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;

/**
 * @author fandong
 * @create 2018/10/24
 */
@Configuration
public class RedisConfig {

    @Autowired
    private JedisPoolConfigProperties jedisPoolConfigProperties;

    @Bean
    public JedisPool getJedisPool(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(jedisPoolConfigProperties.getMaxIdle());
        jedisPoolConfig.setMaxTotal(jedisPoolConfigProperties.getMaxTotal());
        jedisPoolConfig.setMaxWaitMillis(jedisPoolConfigProperties.getMaxWaitMillis());
        return new JedisPool(jedisPoolConfig, jedisPoolConfigProperties.getHost(), jedisPoolConfigProperties.getPort(), Protocol.DEFAULT_TIMEOUT);
    }
}

二、基于Redis的分布式锁


import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * @author fandong
 * @create 2018/10/24
 * 基于redis的分布式锁
 */
public class RedisLockTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    /**
     * ms
     */
    private static final String PX_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 尝试获取分布式锁  利用redis set值的 NX参数
     * @param jedis reids客户端
     * @param lockKey 锁
     * @param requestId 锁拥有者标识
     * @param expireTime 超期时间 ms为单位
     * @return
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime){
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, PX_EXPIRE_TIME, expireTime);
        return LOCK_SUCCESS.equals(result);
    }

    /**
     * 释放分布式锁  利用 LUA脚本保证操作的原子性(Redis单进程单线程并保证执行LUA脚本时不执行其它命令)
     * @param jedis redis客户端
     * @param lockKey 锁
     * @param requestId 锁拥有者标识
     * @return
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId){
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object res = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        return RELEASE_SUCCESS.equals(res);
    }



}

实际使用代码 带资源的try语句

@Autowired
private JedisPool jedisPool;

try (Jedis jedis = jedisPool.getResource()) {
            res = RedisLockTool.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
            Thread.sleep(2000);
        } catch (Exception e) {
            logger.error(ExceptionUtils.getFullStackTrace(e));
        }

三、基于Redis的分布式限流


import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * 同样利用 Redis+LUA的组合  可以对 任意方法限制一秒内 任意次数的访问
 * 以 指定的前缀和当前时间的秒数组合为 KEY 
 * @author fandong
 * @create 2018/10/25
 */
public class RedisLimitTool {

    private static final String LUA_LIMIT_SCRIPT = "local key = KEYS[1]\n" +
            "local limit = tonumber(ARGV[1])\n" +
            "local current = tonumber(redis.call('get', key) or \"0\")\n" +
            "if current + 1 > limit then\n" +
            "   return 0\n" +
            "else\n" +
            "   redis.call(\"INCRBY\", key,\"1\")\n" +
            "   redis.call(\"expire\", key,\"2\")\n" +
            "   return 1\n" +
            "end";

    private static final Long SUCCESS_CODE = 1L;

    public static Boolean limit(Jedis jedis, String keyPrefix, String limit){
        String key = keyPrefix + ":" + System.currentTimeMillis() / 1000;
        Long res =(Long) jedis.eval(LUA_LIMIT_SCRIPT, Collections.singletonList(key), Collections.singletonList(limit));
        return SUCCESS_CODE.equals(res);
    }
}

实际使用时 使用 AOP剥离出 限流逻辑:

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author fandong
 * @create 2018/10/25
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisLimiter {

    String keyPrefix() default "";
    String limit() default  "";
}

Aspect:

import org.apache.commons.lang.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @author fandong
 * @create 2018/10/25
 */
@Component
@Aspect
public class RedisLimitAspect {

    @Autowired
    private JedisPool jedisPool;

    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Around("execution(* com.example.redislock..*(..)) && @annotation(redisLimiter)")
    public Object redisLimiter(ProceedingJoinPoint proceedingJoinPoint, RedisLimiter redisLimiter){
        try(Jedis jedis = jedisPool.getResource()) {
            if (RedisLimitTool.limit(jedis, redisLimiter.keyPrefix(), redisLimiter.limit())){
                return proceedingJoinPoint.proceed();
            }else {
                logger.error("限流:" + redisLimiter.keyPrefix());
                return null;
            }
        } catch (Throwable throwable) {
            logger.error(ExceptionUtils.getFullStackTrace(throwable));
        }
        return null;
    }
}

实际使用:

@RedisLimiter(keyPrefix = "testLimit", limit = "10")
    public Object testLimit(){
        return null;
    }

四、测试

使用 Jmeter进行压测,实际结果符合预期。

欢迎指正。

猜你喜欢

转载自blog.csdn.net/weixin_37882382/article/details/83378959