本文大纲:
1.什么是分布式锁
2.解决方案
3.小结
Redis 简简单单的几种数据类型,一个 key/value 数据库,现在又是分布式锁、又是限流工具、又是消息队列......,感觉都要被玩坏了。不过话说回来,Redis 在这么多场合被开发者们喜欢,还是得益于它极高的性能与使用的简洁性。
在面试的时候,说到 Redis ,很多人第一反应就是缓存,其实除了缓存,Redis 还有非常多丰富的使用场景,这些使用场景,松哥在未来都会和大家一一分享。
今天就先来看一个简单的,用 Redis 做分布式锁。
1.什么是分布式锁首先我们来看一个问题场景:
例如一个简单的用户操作,一个线程去修改用户的状态,首先从数据库中读出用户的状态,然后在内存中进行修改,修改完成后,再存回去。在单线程中,这个操作没有问题,但是在多线程中,由于读取、修改、存 这是三个操作,不是原子操作,所以在多线程中,这样会出问题。
解决这个问题,我们就需要锁,对于锁,大家应该不会陌生,在 Java 中的 synchronized 以及 ReentrantLock 可重入锁都是我们比较常见的,但是这种锁都是本地锁,现在微服务、分布式系统思想大行其道,在这样的系统中,本地锁显然是不够用的,于是大家纷纷想办法,如何在分布式环境下解决锁的问题。想出来的办法很多,我们可以通过 MySQL、可以通过 ZK、也可以通过 Redis ,都可以用来解决分布式锁的问题,这里我们主要来看看如何通过 Redis 解决分布式锁问题。
2.解决方案
2.1 整体思路
「分布式锁实现的思路很简单,就是进来一个线城先占位,当别的线城进来操作时,发现已经有人占位了,就会放弃或者稍后再试。」
在 Redis 中,占位一般使用 setnx 指令,先进来的线程先占位,线程的操作执行完成后,再调用 del 指令释放位子。同时为了防止死锁,我们一般还要给锁加一个过期时间,到期了自动释放。
基于这样的思路,我们来看两种不同的实现方式:
2.2 解决方案一
基于我们前面所说的思路,可以使用 setnx 和 expire 实现分布式锁,但是 setnx 和设置过期时间 expire 这是两个操作,这两个操作一起的话就不具备原子性(除非自己写 Lua 脚本),为了解决这个问题,从 Redis2.8 开始,setnx 和 expire 可以通过一个命令一起来执行了,这个命令就是 set,set 中多了一个参数:
从图中大家可以看到,在 key/value 之后,还有一个 EX 5 表示以秒计的过期时间(PX 表示以毫秒计的过期时间),最后的 NX 就是说如果 k1 不存在,这条命令执行成功,否则执行失败,这就相当于 setnx 的效果了。
因此,我们封装的锁如下:
- public class LockTest {
- public static void main(String[] args) {
- Redis redis = new Redis();
- redis.execute(jedis->{
- String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
- if (set !=null && "OK".equals(set)) {
- //没人占位
- jedis.set("name", "javaboy");
- String name = jedis.get("name");
- System.out.println(name);
- jedis.del("k1");//释放资源
- }else{
- //有人占位,停止/暂缓 操作
- }
- });
- }
- }
对于上面这段代码,大家重点看思路,不必深究代码细节:
2.3 解决方案二
上面的代码写着还是蛮长的,那么有没有简单一点的办法呢?当然是有的!那就是 Redisson。
相对于 Jedis 这种原生态的应用,Redisson 对 Redis 请求做了较多的封装,对于锁,也提供了对应的方法可以直接使用:
- Config config = new Config();
- //配置 Redis 基本连接信息
- config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123");
- //获取一个 RedissonClient 对象
- RedissonClient redisson = Redisson.create(config);
- //获取一个锁对象实例
- RLock lock = redisson.getLock("lock");
- try {
- //获取锁
- boolean b = lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);
- if (b) {
- //获取到锁了,开始写业务
- RBucket<Object> bucket = redisson.getBucket("javaboy");
- bucket.set("www.javaboy.org");
- Object o = bucket.get();
- System.out.println(o);
- }else{
- System.out.println("没拿到锁");
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- //释放锁
- lock.unlock();
- }
在这段代码中,核心的就是 lock.tryLock(500, 1000, TimeUnit.MILLISECONDS);,第一个参数是尝试加锁的等待时间为 500 毫秒,第二个参数表示锁的超时时间为 1000 毫秒,也就是这个锁在 1000 毫秒后会自动失效。
小伙伴们发现,这和我们在方案一里边配置的参数是一样的,其实思路是不变的,Redisson 只不过是将我们写的和锁相关的方法封装起来了而已。
3.小结
当然,这里我只是先简单介绍下加锁的思路以及在 Redis 单机中如何加锁,后面松哥再和大家分享 Redis 集群中如何加锁。
中共中央政治局常务委员会召开会议时提出,要加大公共卫生服务、应急物资保障领...
当消费者按照社交疏散准则纷纷涌向在线购物时,冠状病毒疫情刺激了本已十分繁荣...
【51CTO.com原创稿件】 在企业的数字化转型中,云已经成为必不可少的选项。随着...
近期,新基建被政府、民间、资本广泛看好,各省市相继公布投资计划,最终的数据...
CIO们在准备为全球新冠疫情肆虐的去年做总结时,许多人对未来仍然抱着非常乐观的...
知名半导体市场研究机构 IC Insights 发布了对中国集成电路(IC)市场的分析和预测...
预算是否超支一直就是检验项目成败的试金石。在2020年,新冠肺炎的大流行使保持...
根据IDC全球服务器季度追踪报告显示,2019年第四季度全球服务器市场厂商收入同比...
俄罗斯vps哪些内容不能放 ?部分IDC厂商会告诉你俄罗斯vps什么业务、什么内容都...
香港站群服务器怎么选 ?随着互联网高速发展,很多企业会搭建网站推广业务,网站...