一个Redis分布式锁的工具类(升级版)

xiaoxiao2021-02-28  131

仅供个人记录

工具类:

RedisLock.java

import lombok.extern.slf4j.Slf4j; /** * 分布式锁工具类. * @author Pete.Lee Aug 29, 2017 3:23:41 PM */ @Slf4j public class RedisLock { private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100; private final String lockKey; /** * 锁超时时间,防止线程在入锁以后,无限的执行等待 */ private int expireMsecs = 120 * 1000; /** * 锁等待时间,防止线程饥饿 */ private int timeoutMsecs = 20 * 1000; private volatile Boolean locked = false; private String myExpires = ""; public RedisLock(final String lockKey) { this.lockKey = lockKey + "_lock"; } public RedisLock(final String lockKey, final int timeoutMsecs) { this(lockKey); this.timeoutMsecs = timeoutMsecs; } public RedisLock(final String lockKey, final int timeoutMsecs, final int expireMsecs) { this(lockKey, timeoutMsecs); this.expireMsecs = expireMsecs; } public String getLockKey() { return lockKey; } /** * 获得 lock. 实现思路: 主要是使用了redis 的setnx命令,缓存了锁. reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间) * 执行过程: 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值 * @return true if lock is acquired, false acquire timeouted * @throws InterruptedException in case of thread interruption */ public synchronized Boolean lock() throws InterruptedException { int timeout = timeoutMsecs; while (timeout >= 0) { final long expires = System.currentTimeMillis() + expireMsecs + 1; final String expiresStr = String.valueOf(expires); // 锁到期时间 if (RedisUtil.setNx(lockKey, expiresStr)) { // lock acquired myExpires = expiresStr; locked = true; return true; } final String currentValueStr = RedisUtil.get(lockKey); // redis里的时间 if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的 // lock is expired final String oldValueStr = RedisUtil.getSet(lockKey, expiresStr); //获取上一个锁到期时间,并设置现在的锁到期时间, //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { //防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受 //[分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁 // lock acquired myExpires = expiresStr; locked = true; return true; } } timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS; /* 延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程, 只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足. 使用随机的等待时间可以一定程度上保证公平性 */ Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS); } return false; } public synchronized void unlock() { // 如果当前redis中的锁与上锁相同删除锁 if (myExpires.equals(RedisUtil.get(lockKey))) { if (locked) { RedisUtil.del(lockKey); locked = false; } } } }

RedisUtil.java

import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.util.ArrayList; import java.util.List; /** * redis 工具类. * @author Pete.Lee 2017/9/5 15:01 */ @Slf4j public class RedisUtil { private static StringRedisTemplate template = ContextUtil.getBean("StringRedisTemplate", StringRedisTemplate.class); public static String get(final String key) { String obj = null; try { obj = template.execute((final RedisConnection c) -> { final StringRedisSerializer serializer = new StringRedisSerializer(); final byte[] data = c.get(serializer.serialize(key)); c.close(); return serializer.deserialize(data); }); } catch (Exception ex) { log.error("get redis error, key : {}", key); } return obj; } public static Boolean setNx(final String key, final String value) { Boolean b = false; try { b = template.execute((final RedisConnection c) -> { final StringRedisSerializer serializer = new StringRedisSerializer(); final Boolean success = c.setNX(serializer.serialize(key), serializer.serialize(value)); c.close(); return success; }); } catch (Exception e) { log.error("setNX redis error, key : {}", key); } return b; } public static String getSet(final String key, final String value) { String obj = null; try { obj = template.execute((final RedisConnection c) -> { final StringRedisSerializer serializer = new StringRedisSerializer(); final byte[] ret = c.getSet(serializer.serialize(key), serializer.serialize(value)); c.close(); return serializer.deserialize(ret); }); } catch (Exception ex) { log.error("setNX redis error, key : {}", key); } return obj; } public static Boolean del(final String key) { Boolean obj = null; try { obj = template.execute((final RedisConnection c) -> { final StringRedisSerializer serializer = new StringRedisSerializer(); return c.del(serializer.serialize(key)) > 0; }); } catch (Exception ex) { log.error("del redis error, key : {}", key); } return obj; } public static Long leftPush(final String key, final String value) { return template.opsForList().leftPush(key, value); } public static Long rightPush(final String key, final String value) { return template.opsForList().rightPush(key, value); } public static String leftPop(final String key) { return template.opsForList().leftPop(key); } public static String rightPop(final String key) { return template.opsForList().rightPop(key); } /*public RedisUtil() { final JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setHostName("xxxx"); factory.setPort(6379); factory.setDatabase(2); factory.afterPropertiesSet(); template = new StringRedisTemplate(factory); template.afterPropertiesSet(); // System.out.println(template.boundValueOps("自增Key").increment(1)); }*/ }

RedisConfigure.java

import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.joda.JodaModule; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; @Configuration @EnableCaching public class RedisConfigure { @Bean(name = "StringRedisTemplate") public StringRedisTemplate template(final RedisConnectionFactory factory) { final ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); om.registerModule(new JodaModule()); final Jackson2JsonRedisSerializer<?> serializer = new Jackson2JsonRedisSerializer<>(Object.class); serializer.setObjectMapper(om); final StringRedisTemplate template = new StringRedisTemplate(factory); template.setDefaultSerializer(serializer); template.setKeySerializer(serializer); template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }

ContextUtil.java

import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * 获取Bean工具类. * @author Pete.Lee 2017/9/5 15:02 */ @Component public class ContextUtil implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ContextUtil.context = applicationContext; } public static ApplicationContext getContext() { return context; } public static <T> T getBean(final String name, final Class<T> requiredType) { return context.getBean(name, requiredType); } }

YAML

spring: redis: host: 10.20.9.85 port: 6379 database: 0 password: pool.max-idle: 8 pool.min-idle: 4 pool.max-active: 16 pool.max-wait: -1

应用

final RedisLock lock = new RedisLock("lottery_result"); while (true) { try { if (lock.lock()) { // 需要加锁的代码 } } catch (InterruptedException ex) { } finally { lock.unlock(); } }
转载请注明原文地址: https://www.6miu.com/read-81010.html

最新回复(0)