前言:网上针对基于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进行压测,实际结果符合预期。
欢迎指正。