最近在做一個項目,類型增減庫存的,但是發現我的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); }