Created
December 4, 2017 08:43
-
-
Save chenqiyue/18f14183262b9e4b52ee0767b02b9c57 to your computer and use it in GitHub Desktop.
Implementation for Redlock: https://redis.io/topics/distlock
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.laobai.distributed; | |
import org.springframework.core.NestedRuntimeException; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* 提供方便的 redis distributed lock | |
* @see RedisLockSupportAspect | |
* | |
* @author cqy | |
* @since 2017/11/29. | |
*/ | |
@Retention(RetentionPolicy.RUNTIME) | |
@Documented | |
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) | |
public @interface RedisLock { | |
/** | |
* 分布式锁的key | |
* 支持 SpEl: method | |
*/ | |
String key(); | |
/** | |
* 默认超时时间 | |
*/ | |
int expired() default 3000; | |
/** | |
* 超时时间的单位 | |
*/ | |
TimeUnit timeUnit() default TimeUnit.MILLISECONDS; | |
String exceptionMessage() default "请稍后重试"; | |
class RedisLockException extends NestedRuntimeException { | |
private static final long serialVersionUID = -196589592790629707L; | |
public RedisLockException(String msg) { | |
super(msg); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.laobai.distributed; | |
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.data.redis.connection.jedis.JedisConnection; | |
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; | |
import org.springframework.data.redis.core.RedisOperations; | |
import redis.clients.jedis.Jedis; | |
/** | |
* @author cqy | |
* @since 2017/11/29. | |
*/ | |
@Configuration | |
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }) | |
public class RedisLockAutoConfiguration { | |
@Bean | |
// @ConditionalOnBean(JedisConnectionFactory.class) | |
public RedisLockSupportAspect redisLockSupportAspect(JedisConnectionFactory jcf) { | |
return new RedisLockSupportAspect(jcf); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.laobai.distributed; | |
import org.aspectj.lang.ProceedingJoinPoint; | |
import org.aspectj.lang.annotation.Around; | |
import org.aspectj.lang.annotation.Aspect; | |
import org.aspectj.lang.reflect.MethodSignature; | |
import org.springframework.context.expression.MethodBasedEvaluationContext; | |
import org.springframework.core.DefaultParameterNameDiscoverer; | |
import org.springframework.core.ParameterNameDiscoverer; | |
import org.springframework.data.redis.connection.RedisConnection; | |
import org.springframework.data.redis.connection.ReturnType; | |
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; | |
import org.springframework.data.redis.core.RedisConnectionUtils; | |
import org.springframework.expression.spel.standard.SpelExpressionParser; | |
import java.lang.reflect.Method; | |
import java.util.UUID; | |
import java.util.concurrent.TimeUnit; | |
import java.util.concurrent.locks.LockSupport; | |
import javax.annotation.Nullable; | |
import lombok.Setter; | |
import redis.clients.jedis.Jedis; | |
/** | |
* Implementation for Redlock: <a>https://redis.io/topics/distlock</a> | |
* @author cqy | |
* @since 2017/11/29. | |
*/ | |
@Aspect | |
public class RedisLockSupportAspect { | |
final SpelExpressionParser parser = new SpelExpressionParser(); | |
final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); | |
final JedisConnectionFactory jcf; | |
final byte[] delScript = ("if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + | |
" return redis.call(\"del\",KEYS[1])\n" + | |
"else\n" + | |
" return 0\n" + | |
"end").getBytes(); | |
@Setter | |
int defaultRetryIntervalMilli = 500; | |
public RedisLockSupportAspect(JedisConnectionFactory jcf) { | |
this.jcf = jcf; | |
} | |
@Around("@annotation(lock)") | |
public Object invoke(ProceedingJoinPoint pjp, RedisLock lock) throws Throwable { | |
String lockKey = getLockId(pjp, lock); | |
String lockValue = null; | |
long end = System.currentTimeMillis() + lock.timeUnit().toMillis(lock.expired()); | |
RedisConnection conn = null; | |
boolean first = true; | |
try { | |
while (lockValue == null) { | |
if (end < System.currentTimeMillis()) { | |
throw new RedisLock.RedisLockException(lock.exceptionMessage()); | |
}else if(!first){ | |
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(defaultRetryIntervalMilli)); | |
}else{ | |
first = false; | |
} | |
conn = conn == null ? conn = RedisConnectionUtils.getConnection(jcf) : conn; | |
lockValue = tryLock(conn, lock, lockKey); | |
} | |
return pjp.proceed(); | |
} finally { | |
releaseLock(conn, lockKey, lockValue); | |
RedisConnectionUtils.releaseConnection(conn, jcf); | |
} | |
} | |
/** | |
* SET resource_name my_random_value NX PX 30000 | |
* @return lockValue or null | |
*/ | |
private String tryLock(RedisConnection conn, RedisLock lock, | |
String lockKey) { | |
String uuid = UUID.randomUUID().toString(); | |
//坑爹居然没有返回值 | |
// conn.set(lockKey.getBytes(), uuid.getBytes(), | |
// Expiration.milliseconds(lock.timeUnit().toMillis(lock.expired())), | |
// RedisStringCommands.SetOption.SET_IF_ABSENT); | |
Jedis jedis = (Jedis) conn.getNativeConnection(); | |
String r = jedis.set(lockKey, uuid, "NX", "PX", lock.timeUnit().toMillis(lock.expired())); | |
// null or 'OK' | |
return r == null ? null : uuid; | |
} | |
private void releaseLock(RedisConnection conn, String lockKey, | |
@Nullable String lockValue) { | |
if (lockValue != null) { | |
Object o = conn.eval(delScript, ReturnType.INTEGER, 1, keysAndArgs(lockKey, lockValue)); | |
} | |
} | |
private byte[][] keysAndArgs(String lockKey, String lockValue) { | |
byte[][] keysAndArgs = new byte[2][]; | |
keysAndArgs[0] = lockKey.getBytes(); | |
keysAndArgs[1] = lockValue.getBytes(); | |
return keysAndArgs; | |
} | |
String getLockId(ProceedingJoinPoint pjp, RedisLock lock) { | |
Method method = ((MethodSignature) pjp.getSignature()).getMethod(); | |
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(pjp.getTarget(), method, pjp.getArgs(), | |
parameterNameDiscoverer); | |
return parser.parseExpression(lock.key()).getValue(context, String.class); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment