本篇文章给大家主要带大家了解一下redis中分布式锁的实现和代码解析,希望对大家有所帮助!
redis 分布式锁
大家项目中都会使用到分布式锁把,通常用来做数据的有序操作场景,比如一笔订单退款(如果可以退多次的情况)。或者用户多端下单。【相关推荐:Redis视频教程】
Maven 依赖
我主要是基于 Spring-Boot 2.1.2 + Jedis 进行实现
<?xml version="1.0" encoding="UTF-8"?><project> <modelversion>4.0.0</modelversion> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.1.2.RELEASE</version> </parent> <groupid>cn.edu.cqvie</groupid> <artifactid>redis-lock</artifactid> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceencoding>UTF-8</project.build.sourceencoding> <java.version>1.8</java.version> <redis.version>2.9.0</redis.version> <spring-test.version>5.0.7</spring-test.version> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-autoconfigure</artifactid> </dependency> <dependency> <groupid>org.springframework.data</groupid> <artifactid>spring-data-redis</artifactid> </dependency> <dependency> <groupid>redis.clients</groupid> <artifactid>jedis</artifactid> <version>${redis.version}</version> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-logging</artifactid> </dependency> <dependency> <groupid>org.slf4j</groupid> <artifactid>log4j-over-slf4j</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> </plugin> </plugins> </build></project>
配置文件
application.properties 配置文件内容如下:
spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.timeout=30000 spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.min-idle=2 spring.redis.jedis.pool.max-idle=4 logging.level.root=INFO
接口定义
接口定义,对于锁我们核心其实就连个方法 lock 和 unlock.
public interface RedisLock { long TIMEOUT_MILLIS = 30000; int RETRY_MILLIS = 30000; long SLEEP_MILLIS = 10; boolean tryLock(String key); boolean lock(String key); boolean lock(String key, long expire); boolean lock(String key, long expire, long retryTimes); boolean unlock(String key); }
分布式锁实现
我的实现方式是通过 setnx 方式实现了,如果存在 tryLock 逻辑的话,会通过 自旋 的方式重试
// AbstractRedisLock.java 抽象类 public abstract class AbstractRedisLock implements RedisLock { @Override public boolean lock(String key) { return lock(key, TIMEOUT_MILLIS); } @Override public boolean lock(String key, long expire) { return lock(key, TIMEOUT_MILLIS, RETRY_MILLIS); } } // 具体实现 @Component public class RedisLockImpl extends AbstractRedisLock { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisTemplate<string> redisTemplate; private ThreadLocal<string> threadLocal = new ThreadLocal<string>(); private 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(); } @Override public boolean tryLock(String key) { return tryLock(key, TIMEOUT_MILLIS); } public boolean tryLock(String key, long expire) { try { return !StringUtils.isEmpty(redisTemplate.execute((RedisCallback<string>) connection -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); threadLocal.set(uuid); return commands.set(key, uuid, "NX", "PX", expire); })); } catch (Throwable e) { logger.error("set redis occurred an exception", e); } return false; } @Override public boolean lock(String key, long expire, long retryTimes) { boolean result = tryLock(key, expire); while (!result && retryTimes-- > 0) { try { logger.debug("lock failed, retrying...{}", retryTimes); Thread.sleep(SLEEP_MILLIS); } catch (InterruptedException e) { return false; } result = tryLock(key, expire); } return result; } @Override public boolean unlock(String key) { try { List<string> keys = Collections.singletonList(key); List<string> args = Collections.singletonList(threadLocal.get()); Long result = redisTemplate.execute((RedisCallback<long>) connection -> { Object nativeConnection = connection.getNativeConnection(); if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args); } if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } return 0L; }); return result != null && result > 0; } catch (Throwable e) { logger.error("unlock occurred an exception", e); } return false; } }</long></string></string></string></string></string></string>
测试代码
最后再来看看如何使用吧. (下面是一个模拟秒杀的场景)
@RunWith(SpringRunner.class) @SpringBootTest public class RedisLockImplTest { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private RedisLock redisLock; @Autowired private StringRedisTemplate redisTemplate; private ExecutorService executors = Executors.newScheduledThreadPool(8); @Test public void lock() { // 初始化库存 redisTemplate.opsForValue().set("goods-seckill", "10"); List<future> futureList = new ArrayList(); for (int i = 0; i { try { action.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); } public int seckill() { String key = "goods"; try { redisLock.lock(key); int num = Integer.valueOf(Objects.requireNonNull(redisTemplate.opsForValue().get("goods-seckill"))); if (num > 0) { redisTemplate.opsForValue().set("goods-seckill", String.valueOf(--num)); logger.info("秒杀成功,剩余库存:{}", num); } else { logger.error("秒杀失败,剩余库存:{}", num); } return num; } catch (Throwable e) { logger.error("seckill exception", e); } finally { redisLock.unlock(key); } return 0; } }</future>
总结
本文是 Redis 锁的一种简单的实现方式,基于 jedis 实现了锁的重试操作。 但是缺点还是有的,不支持锁的自动续期,锁的重入,以及公平性(目前通过自旋的方式实现,相当于是非公平的方式)。
更多编程相关知识,请访问:Redis视频教程!!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END