SpringBoot、Redis、Jedis(JedisPool) 分散式鎖、分散式限流 詳解
阿新 • • 發佈:2019-01-07
前言:網上針對基於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進行壓測,實際結果符合預期。
歡迎指正。