本篇内容介绍了“怎么理解redis中的分布式锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联建站是专业的潜山网站建设公司,潜山接单;提供网站建设、成都网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行潜山网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
Redis 分布式锁
大家项目中都会使用到分布式锁把,通常用来做数据的有序操作场景,比如一笔订单退款(如果可以退多次的情况)。或者用户多端下单。【相关推荐:Redis视频教程】
Maven 依赖
我主要是基于 Spring-Boot 2.1.2
+ Jedis
进行实现
4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.2.RELEASE cn.edu.cqvie redis-lock 1.0-SNAPSHOT UTF-8 1.8 2.9.0 5.0.7 org.springframework.boot spring-boot-autoconfigure org.springframework.data spring-data-redis redis.clients jedis ${redis.version} org.springframework.boot spring-boot-starter-logging org.slf4j log4j-over-slf4j org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
配置文件
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 RedisTemplateredisTemplate; private ThreadLocal threadLocal = new ThreadLocal (); 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 ) 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 keys = Collections.singletonList(key); List args = Collections.singletonList(threadLocal.get()); Long result = redisTemplate.execute((RedisCallback ) 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; } }
测试代码
最后再来看看如何使用吧. (下面是一个模拟秒杀的场景)
@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"); ListfutureList = new ArrayList<>(); for (int i = 0; i < 100; i++) { futureList.add(executors.submit(this::seckill)); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } // 等待结果,防止主线程退出 futureList.forEach(action -> { 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; } }
总结
本文是 Redis 锁的一种简单的实现方式,基于 jedis
实现了锁的重试操作。
但是缺点还是有的,不支持锁的自动续期,锁的重入,以及公平性(目前通过自旋的方式实现,相当于是非公平的方式)。
“怎么理解Redis中的分布式锁”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
网页标题:怎么理解Redis中的分布式锁
标题路径:http://scyingshan.cn/article/jejeso.html