上一篇我们介绍了一种redis 分布式锁,介绍了其应用场景和一些存在的问题,而今天我们介绍 redis 第二种分布式锁的使用;
这种锁同时采用lua脚本保证原子性能很好解决以上问题。实例如下:
1、代码如下:
package com.jy.utils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCommands; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * RedisTemplate工具类 */ @Component public class RedisUtil implements ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class); private static RedisTemplate<Object, Object> redisTemplate; private static ThreadLocal<String> lockFlag = new ThreadLocal<String>(); public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call('get',KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call('del',KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } //加锁核心方法 public static boolean setRedisLock(String key, long expire) { try { String result = redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); lockFlag.set(uuid);//给该线程设置唯一标识,释放所得时候会用到 return commands.set(key, uuid, "NX", "PX", expire);//锁和超时时间同时设置,保证改锁的原子性 } }); logger.info("获取锁成功[{}]结果:[{}]",key,result); return StringUtils.isNotEmpty(result); } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } //释放锁的核心方法 public static boolean releaseRedisLock(String key) { /** 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期, *此时有可能已经被另外一个线程持有锁,所以不能直接删除,需要判断该线程的value 值(加锁时的uuid 值), */ try { List<String> keys = new ArrayList<String>(); keys.add(key); List<String> args = new ArrayList<String>(); args.add(lockFlag.get());//设置需要删除的线程的标识value值(每个线程精准删除自己设置的锁) /** 使用lua脚本删除redis中匹配value的key, * 可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 * 从而避免锁的错乱 */ Long result = redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); /** 单机模式 */ return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } }); return result != null && result > 0; } catch (Exception e) { logger.error("release lock occured an exception", e); } return false; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { redisTemplate = applicationContext.getBean(RedisTemplate.class); } }
2、这种redis 分布式锁,支持多线程、安全性更好,可以试一下。下篇我们再介绍一种redis 分布式锁,对比一下,根据不同场景选择合适的分布式锁,敬请期待!