redis锁,redis分布式锁: RedisLock
最编程
2024-07-28 19:11:34
...
最近在做一个项目,类型增减库存的,但是发现我的springboot版本太低,springboot1.5.9版本的,redis是2.9.0的。springboot2.x,redis3.x好的东西用不了。
首先确定你的springboot版本,redis版本。
1.如果不想考虑springboot,redis版本,那么用:Redisson分布式锁。
Redisson分布式锁
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.7.5</version> </dependency>
application.properties中的相关配置
# Redis服务器地址(默认session使用) spring.redis.host=192.168.1.201 # Redis服务器连接密码(默认为空) spring.redis.password= # Redis服务器连接端口 spring.redis.port=6390
定义一个Loker接口,用于分布式锁的一些操作
import java.util.concurrent.TimeUnit; /** * 锁接口 * @author jie.zhao */ public interface Locker { /** * 获取锁,如果锁不可用,则当前线程处于休眠状态,直到获得锁为止。 * * @param lockKey */ void lock(String lockKey); /** * 释放锁 * * @param lockKey */ void unlock(String lockKey); /** * 获取锁,如果锁不可用,则当前线程处于休眠状态,直到获得锁为止。如果获取到锁后,执行结束后解锁或达到超时时间后会自动释放锁 * * @param lockKey * @param timeout */ void lock(String lockKey, int timeout); /** * 获取锁,如果锁不可用,则当前线程处于休眠状态,直到获得锁为止。如果获取到锁后,执行结束后解锁或达到超时时间后会自动释放锁 * * @param lockKey * @param unit * @param timeout */ void lock(String lockKey, TimeUnit unit, int timeout); /** * 尝试获取锁,获取到立即返回true,未获取到立即返回false * * @param lockKey * @return */ boolean tryLock(String lockKey); /** * 尝试获取锁,在等待时间内获取到锁则返回true,否则返回false,如果获取到锁,则要么执行完后程序释放锁, * 要么在给定的超时时间leaseTime后释放锁 * * @param lockKey * @param waitTime * @param leaseTime * @param unit * @return */ boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException; /** * 锁是否被任意一个线程锁持有 * * @param lockKey * @return */ boolean isLocked(String lockKey); }
实现类RedissonLocker,实现Locker中的方法
import java.util.concurrent.TimeUnit; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; /** * 基于Redisson的分布式锁 * @author jie.zhao */ public class RedissonLocker implements Locker { private RedissonClient redissonClient; public RedissonLocker(RedissonClient redissonClient) { super(); this.redissonClient = redissonClient; } @Override public void lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); } @Override public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } @Override public void lock(String lockKey, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, TimeUnit.SECONDS); } @Override public void lock(String lockKey, TimeUnit unit, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); } public void setRedissonClient(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Override public boolean tryLock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); return lock.tryLock(); } @Override public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { RLock lock = redissonClient.getLock(lockKey); return lock.tryLock(waitTime, leaseTime, unit); } @Override public boolean isLocked(String lockKey) { RLock lock = redissonClient.getLock(lockKey); return lock.isLocked(); } }
工具类LockUtil
import java.util.concurrent.TimeUnit; /** * redis分布式锁工具类 * @author jie.zhao */ public final class LockUtil { private static Locker locker; /** * 设置工具类使用的locker * * @param locker */ public static void setLocker(Locker locker) { LockUtil.locker = locker; } /** * 获取锁 * * @param lockKey */ public static void lock(String lockKey) { locker.lock(lockKey); } /** * 释放锁 * * @param lockKey */ public static void unlock(String lockKey) { locker.unlock(lockKey); } /** * 获取锁,超时释放 * * @param lockKey * @param timeout */ public static void lock(String lockKey, int timeout) { locker.lock(lockKey, timeout); } /** * 获取锁,超时释放,指定时间单位 * * @param lockKey * @param unit * @param timeout */ public static void lock(String lockKey, TimeUnit unit, int timeout) { locker.lock(lockKey, unit, timeout); } /** * 尝试获取锁,获取到立即返回true,获取失败立即返回false * * @param lockKey * @return */ public static boolean tryLock(String lockKey) { return locker.tryLock(lockKey); } /** * 尝试获取锁,在给定的waitTime时间内尝试,获取到返回true,获取失败返回false,获取到后再给定的leaseTime时间超时释放 * * @param lockKey * @param waitTime * @param leaseTime * @param unit * @return * @throws InterruptedException */ public static boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { return locker.tryLock(lockKey, waitTime, leaseTime, unit); } /** * 锁释放被任意一个线程持有 * * @param lockKey * @return */ public static boolean isLocked(String lockKey) { return locker.isLocked(lockKey); } }
redisson的配置类RedissonConfig
import java.io.IOException; import com.rxjy.common.redissonlock.LockUtil; import com.rxjy.common.redissonlock.RedissonLocker; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedissonConfig { @Value("${spring.redis.database}") private int database; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; /** * RedissonClient,单机模式 * * @return * @throws IOException */ @Bean(destroyMethod = "shutdown") public RedissonClient redisson() { Config config = new Config(); SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig.setAddress("redis://" + host + ":" + port); singleServerConfig.setTimeout(timeout); singleServerConfig.setDatabase(database); if (password != null && !"".equals(password)) { //有密码 singleServerConfig.setPassword(password); } return Redisson.create(config); } @Bean public RedissonLocker redissonLocker(RedissonClient redissonClient) { RedissonLocker locker = new RedissonLocker(redissonClient); //设置LockUtil的锁处理对象 LockUtil.setLocker(locker); return locker; } }
测试:
import com.rxjy.common.redissonlock.LockUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RedissonLockTest { static final String KEY = "LOCK_KEY"; @GetMapping("/test") public Object test(){ //加锁 LockUtil.lock(KEY); try { //TODO 处理业务 System.out.println(" 处理业务。。。"); } catch (Exception e) { //异常处理 }finally{ //释放锁 LockUtil.unlock(KEY); } return "SUCCESS"; } }
注意:Redisson中的key,不能有数字,分号,冒号等特殊字符。
可以是这样:test-lock
2.springboot2.x, redis3.x 自己编写RedisLock
RedisLock
VariableKeyLock.java
import java.util.concurrent.locks.Lock; /** * 可变key锁 * */ public interface VariableKeyLock extends Lock { boolean tryLock(String key); void lock(String key); void unlock(String key); }
RedisLock.java
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import java.util.Arrays; import java.util.Random; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; /** * redis锁 * */ @Component @Slf4j public class RedisLock implements VariableKeyLock { public static final String LOCK = "LOCK"; @Autowired private RedisConnectionFactory factory; private ThreadLocal<String> localValue = new ThreadLocal<String>(); /** * 解锁lua脚本 */ private static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"; @Override public void lock() { if (!tryLock()) { try { Thread.sleep(new Random().nextInt(10) + 1); } catch (InterruptedException e) { log.error(e.getMessage(), e); } lock(); } } @Override public void lock(String key) { if (!tryLock(key)) { try { Thread.sleep(new Random().nextInt(10) + 1); } catch (InterruptedException e) { log.error(e.getMessage(), e); } lock(key); } } @Override public boolean tryLock() { RedisConnection connection = null; try { connection = factory.getConnection(); Jedis jedis = (Jedis)connection.getNativeConnection(); String value = UUID.randomUUID().toString(); localValue.set(value); String ret = jedis.set(LOCK, value, "NX", "PX", 10000); return ret != null && "OK".equals(ret); } catch (Exception e) { log.error(e.getMessage(), e); } finally { if (connection != null) { connection.close(); } } return false; } @Override public boolean tryLock(String key) { RedisConnection connection = null; try { connection = factory.getConnection(); Jedis jedis = (Jedis)connection.getNativeConnection(); String value = UUID.randomUUID().toString(); localValue.set(value); String ret = jedis.set(key, value, "NX", "PX", 10000); return ret != null && "OK".equals(ret); } catch (Exception e) { log.error(e.getMessage(), e); } finally { if (connection != null) { connection.close(); } } return false; } @Override public void unlock() { String script = UNLOCK_LUA; RedisConnection connection = null; try { connection = factory.getConnection(); Object jedis = connection.getNativeConnection(); if (jedis instanceof Jedis) { ((Jedis)jedis).eval(script, Arrays.asList(LOCK), Arrays.asList(localValue.get())); } else if (jedis instanceof JedisCluster) { ((JedisCluster)jedis).eval(script, Arrays.asList(LOCK), Arrays.asList(localValue.get())); } } catch (Exception e) { log.error(e.getMessage(), e); } finally { if (connection != null) { connection.close(); } } } @Override public void unlock(String key) { String script = UNLOCK_LUA; RedisConnection connection = null; try { connection = factory.getConnection(); Object jedis = connection.getNativeConnection(); if (jedis instanceof Jedis) { ((Jedis)jedis).eval(script, Arrays.asList(key), Arrays.asList(localValue.get())); } else if (jedis instanceof JedisCluster) { ((JedisCluster)jedis).eval(script, Arrays.asList(key), Arrays.asList(localValue.get())); } } catch (Exception e) { log.error(e.getMessage(), e); } finally { if (connection != null) { connection.close(); } } } // ------------------------------------- @Override public void lockInterruptibly() throws InterruptedException { } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
RedisConfig.java配置文件
import com.alibaba.fastjson.parser.ParserConfig; import com.xxx.common.support.FastJsonSerializationRedisSerializer; import com.xxx.common.support.log.LogRedisReceiver; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPoolConfig; import java.time.Duration; import java.util.Optional; @Configuration public class RedisConfig { @Autowired private RedisProperties redisProperties; @Value("${spring.application.name}") private String applicationName; /** * 实例化 RedisTemplate 对象 * * @return */ @Bean public RedisTemplate<String, Object> functionDomainRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); initDomainRedisTemplate(redisTemplate, connectionFactory()); return redisTemplate; } /** * 设置数据存入 redis 的序列化方式 * * @param redisTemplate * @param factory */ private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new FastJsonSerializationRedisSerializer(Object.class)); // redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setHashValueSerializer(new FastJsonSerializationRedisSerializer(Object.class)); ParserConfig.getGlobalInstance().setAutoTypeSupport(true); redisTemplate.setConnectionFactory(factory); } /** * 实例化 HashOperations 对象,可以使用 Hash 类型操作 * * @param redisTemplate * @return */ @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } /** * 实例化 ValueOperations 对象,可以使用 String 操作 * * @param redisTemplate * @return */ @Bean public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForValue(); } /** * 实例化 ListOperations 对象,可以使用 List 操作 * * @param redisTemplate * @return */ @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } /** * 实例化 SetOperations 对象,可以使用 Set 操作 * * @param redisTemplate * @return */ @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } /** * 实例化 ZSetOperations 对象,可以使用 ZSet 操作 * * @param redisTemplate * @return */ @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } @Bean public RedisConnectionFactory connectionFactory() { JedisPoolConfig poolConfig = new JedisPoolConfig(); if (redisProperties.getJedis().getPool() != null) { poolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive()); poolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle()); poolConfig.setMaxWaitMillis(redisProperties.getJedis().getPool().getMaxWait().toMillis()); poolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle()); } poolConfig.setTestOnBorrow(true); poolConfig.setTestOnReturn(false); poolConfig.setTestWhileIdle(true); JedisClientConfiguration jedisClientConfiguration; jedisClientConfiguration = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and() .readTimeout(Optional.ofNullable(redisProperties.getTimeout()).orElse(Duration.ofMillis(5000))).build(); // 如果集群配置未空,则是单机。否则是集群 if (redisProperties.getCluster() == null || CollectionUtils.isEmpty(redisProperties.getCluster().getNodes())) { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase()); redisStandaloneConfiguration.setPort(redisProperties.getPort()); redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword())); redisStandaloneConfiguration.setHostName(redisProperties.getHost()); return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); } else { RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes()); redisClusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword())); return new JedisConnectionFactory(redisClusterConfiguration, jedisClientConfiguration); } } @Bean public RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory()); container.addMessageListener(listenerAdapter, new PatternTopic(applicationName + "-log")); return container; } @Bean public MessageListenerAdapter listenerAdapter(LogRedisReceiver logRedisReceiver) { return new MessageListenerAdapter(logRedisReceiver, "receiveMessage"); } }
application.propeties的redis配置,参考spring.redis.*的配置
测试:
redisLock.lock(RedisKeyConstants.SECKILL_PREX + id ); try{ log.debug("increaseSeckillStock-减库存: seckillId-" + seckillId + ",id-"+id); try{ maxPdfNo = seckillInventoryService.deductionInventory(id, seckillId, seckillNo, passengerNum); }catch (Exception e){ e.printStackTrace(); log.debug("deductionSeckillStock-减库存:" + e.getMessage()); } }finally { redisLock.unlock(RedisKeyConstants.SECKILL_PREX + id); }