用注解实现Redis分布式锁

点击跳转至我的个人网站 https://www.xiaoshenghao.cn

一.maven依赖

springboot版本2.0.6,redis版本2.0.11

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

二.redis命令 setnx

public class RedisHelper {

    @Autowired
    private RedisTemplate redisTemplate;

    @PostConstruct
    public void init() {
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(objectJackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(objectJackson2JsonRedisSerializer);
    }
    /**
     * 分布式锁
     */
    public Boolean setnx(Object key, Object value, Long expireTime, TimeUnit timeUnit) {
        if (key == null) {
            return false;
        }
        return this.setIfAbsent(key, value, expireTime, timeUnit);
    }

    private Boolean setIfAbsent(Object key, Object value, long timeout, TimeUnit unit) {
        byte[] rawKey = this.rawKey(key);
        byte[] rawValue = this.rawValue(value);
        Expiration expiration = Expiration.from(timeout, unit);
        return (Boolean) redisTemplate.execute((connection) -> {
            return connection.set(rawKey, rawValue, expiration, RedisStringCommands.SetOption.ifAbsent());
        }, true);
    }

    private byte[] rawKey(Object key) {
        return key instanceof byte[] ? (byte[]) ((byte[]) key) : this.keySerializer().serialize(key);
    }

    private byte[] rawValue(Object value) {
        return value instanceof byte[] ? (byte[]) ((byte[]) value) : this.valueSerializer().serialize(value);
    }

    private RedisSerializer valueSerializer() {
        return redisTemplate.getValueSerializer();
    }

    private RedisSerializer keySerializer() {
        return redisTemplate.getKeySerializer();
    }
    /**
     * 删除对应的value
     *
     * @param key
     */
    public boolean remove(String key) {
        if (key == null) {
            return false;
        }
        return redisTemplate.delete(key);
    }

}

特别注意:如果你的redis是2.1以上,setIfAbsent增加了设置过期时间。可以替换成下面的写法。

 public Boolean setnx(String key, Object value, Long expireTime,TimeUnit timeUnit) {
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        return operations.setIfAbsent(key,value,expireTime, timeUnit);
    }

三.注解写法

1.注解@DistributeLock(支持spel表达式)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributeLock {
    String key();
    long timeout() default 10;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

2.注解解释器


/**
 * @version 1.0
 * @Author shenghao.xiao
 * @Date 2019/6/27
 **/
@Aspect
@Component
@Order(1)
@Slf4j
public class DistributeLockAspect {
    @Autowired
    private RedisHelper redisHelper;
    private static final ParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    @Around("@annotation(distributeLock)")
    public Object doAround(ProceedingJoinPoint joinPoint, DistributeLock distributeLock) throws Throwable{
        Object[] args = joinPoint.getArgs();
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext(args);
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        String[] parametersName = DISCOVERER.getParameterNames(method);
        if (args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                standardEvaluationContext.setVariable(parametersName[i], args[i]);
            }
        }
        String key = distributeLock.key();
        key = generateKey(key, standardEvaluationContext);
        long timeout = distributeLock.timeout();
        TimeUnit timeUnit = distributeLock.timeUnit();
     	if (lock(key, timeout, timeUnit)) {
            Object proceed = joinPoint.proceed();
            unLock(key);
            return proceed;
        }
        return null;
    }

    private static String generateKey(String key, StandardEvaluationContext standardEvaluationContext) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(key);
        return exp.getValue(standardEvaluationContext, String.class);
    }

    private void unLock(String key) {
        boolean remove = redisHelper.remove(key);
        if (!remove) {
            log.error("释放分布式锁失败,key={},已自动超时", key);
        }
    }

    private Boolean lock(String key, Long timeout, TimeUnit timeUnit) {
        String value = UUID.randomUUID().toString();
        return redisHelper.setnx(key, value, timeout, timeUnit);
    }

}

3.使用方式

@Service
public class TestService {
    @DistributeLock(key = "#code+#phoneNo", timeout = 1800)
    public void sys(String code, String phoneNo) {
        System.out.println("今天天氣不錯哦!");
    }
}

四.遗留问题

redis超时时间是自己控制的,业务的实际执行时间并不能准确确定,redission续期可解决该问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值