1.先配置spring-data-redis
首先是依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.4.RELEASE</version> </dependency>
redisconfig 配置类
@Configuration @PropertySource("classpath:irongbei.properties") public class RedisConfig extends JCacheConfigurerSupport { @Autowired private Environment environment; @Bean public RedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory fac = new JedisConnectionFactory(); fac.setHostName(environment.getProperty("redis.host")); fac.setPort(Integer.parseInt(environment.getProperty("redis.port"))); fac.setPassword(environment.getProperty("redis.password")); fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout"))); // fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle"))); // fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal"))); // fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis"))); // fac.getPoolConfig().setMinEvictableIdleTimeMillis( // Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis"))); // fac.getPoolConfig() // .setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun"))); // fac.getPoolConfig().setTimeBetweenEvictionRunsMillis( // Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis"))); // fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow"))); // fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle"))); return fac; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, String> redis = new RedisTemplate<>(); // 设置redis的String/Value的默认序列化方式 DefaultStrSerializer defaultStrSerializer = new DefaultStrSerializer(); redis.setKeySerializer(defaultStrSerializer); redis.setValueSerializer(defaultStrSerializer); redis.setHashKeySerializer(defaultStrSerializer); redis.setHashValueSerializer(defaultStrSerializer); redis.setConnectionFactory(redisConnectionFactory); redis.afterPropertiesSet(); return redis; } } class DefaultStrSerializer implements RedisSerializer<Object> { private final Charset charset; public DefaultStrSerializer() { this(Charset.forName("UTF8")); } public DefaultStrSerializer(Charset charset) { Assert.notNull(charset, "Charset must not be null!"); this.charset = charset; } @Override public byte[] serialize(Object o) throws SerializationException { return o == null ? null : String.valueOf(o).getBytes(charset); } @Override public Object deserialize(byte[] bytes) throws SerializationException { return bytes == null ? null : new String(bytes, charset); } }
redisLock类 分布式锁实现的类
@Component public class RedisLock { private static final Logger log = LoggerFactory.getLogger(RedisLock.class); @Autowired private RedisTemplate<String, String> redisTemplate; /** * * @param key * @param value 当前时间+超时时间 * @return */ public boolean lock(String key,String value){ if(redisTemplate.opsForValue().setIfAbsent(key,value)){ log.info(" [Redis分布式锁] key:[{}] 获取到锁",key); return true; } //oldvalue 俩个线程都返回A String currentValue = redisTemplate.opsForValue().get(key); //如果锁过期->这里如果一个key的value时间是小于当前当前时间 那就是过期了,如果大于当前时间才没有过期 if(StringUtils.isNotEmpty(currentValue) && Long.parseLong(currentValue) <System.currentTimeMillis()){ //获取上一个锁的时间 //第一个线程获取上一个oldvalue 然后设置一个新的值进去 第二个线程就获取到是新的值. String oldValue = redisTemplate.opsForValue().getAndSet(key,value); //3 这一步就只有第一个线程能匹配到了 第二个线程就获取不到了 if(StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)){ log.info(" [Redis分布式锁] key:[{}] 获取到锁",key); return true; } } return false; } /** * 解锁 * @param key * @param value */ public void unlock(String key ,String value){ try { String currentValue = redisTemplate.opsForValue().get(key); if(StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)){ redisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { log.error(" [redis分布式锁] 解锁异常, {} ",e); } } }
测试类:
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations = {"/spring-context.xml","/spring-context-jedis.xml","/spring-context-shiro.xml"}) public class RedisLockTest { @Resource(name = "threadPoolTaskExecutor") private ThreadPoolExecutor taskExecutor; @Autowired private RedisLock redisLock; private long timeout = 5*1000; private static final Logger logger =LoggerFactory.getLogger(RedisLockTest.class); @Test public void test (){ for (int i = 0; i <200 ; i++) { taskExecutor.execute(()->{ String time = String.valueOf(System.currentTimeMillis()+timeout); if(!redisLock.lock("testlock",time)){ logger.info("哎呦喂..人太多了 在排队中.."); return; }else { try { Thread.sleep(4000); redisLock.unlock("testlock",time); } catch (InterruptedException e) { e.printStackTrace(); } } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
补充
和同事讨论,结果确实如同事所说,解锁的时候有些不严谨,因为是分俩步操作的.可能会在del之前被别人获取到锁然后再del删除掉别人获取的锁.下面是新的解锁方式,目前有些公司redis服务器不支持这样的命令
为什么使用lua语言在操作Redis 主要是保证操作的原子性.一步操作
但是使用lua要非常小心,如果脚本错误可能会阻塞整个Redis实例
private static final Long SUCCESS = 1L; /** * 释放锁 * @param key * @param value * @return */ public boolean releaseLock(String key, String value) { key =prifix+key; String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class); try { Object result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); System.out.println(result); if (SUCCESS.equals(result)) { log.info("[redis 分布式锁解锁成功] key:[{}] 结果:{}",key,result); return true; } log.info("[redis 分布式锁解锁失败] key:[{}] 结果{}",key,result); } catch (Exception e) { log.info("[redis 分布式锁解锁失败] key:[{}] msg:{}",key,e.getMessage()); e.printStackTrace(); } return false; }
测试代码
@Test public void testPrefix(){ redisLock.lock("test","10000"); redisLock.lock("test1","10000"); // redisLock.lock("test2","10000"); // redisLock.lock("test3","10000"); redisLock.releaseLock("test","10000"); redisLock.releaseLock("test1","10002"); }