当前位置: 首页 > 科技观察

Redis中的分布式锁,你有几种姿势?

时间:2023-03-12 06:18:12 科技观察

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可以通过一个命令一起执行。这个命令设置了,还有很多A参数:从图中可以看出,在key/value后面,有一个EX5表示过期时间,单位秒(PX表示过期时间,单位毫秒),最后一个NX的意思是如果k1不存在,则这条命令执行成功,否则失败,相当于setnx的效果。因此,我们封装的锁如下:publicclassLockTest{publicstaticvoidmain(String[]args){Redisredis=newRedis();redis.execute(jedis->{Stringset=jedis.set("k1","v1",newSetParams().nx().ex(5));if(set!=null&&"OK".equals(set)){//无人占用jedis.set("name","javaboy");Stringname=jedis.get("name");System.out.println(name);jedis.del("k1");//释放资源}else{//有人占用,停止/暂停运行}});}}对于上面的代码,大家应该关注思路,不要深究代码细节:首先构造一个Redis实例,然后调用execute方法。这是我自己封装的方法,目的是配置Jedis连接池,并及时回收用过的资源。这块小伙伴在测试的时候可以直接使用自己创建的Jedis实例,效果是一样的。调用jedis中的set方法,注意第三个参数,我们设置nx,设置过期时间为5秒,相当于setnx和expire两个命令的组合。如果set命令执行成功,就可以在if里面写自己的业务了。如果抢到锁失败,可以进入一个延迟消息队列,停一会再试。但是这种封装带来了一个新的问题,那就是超时问题。关于超时问题,宋哥会通过视频教程分享给大家。2.3方案二上面的代码比较长,有没有更简单的方法呢?当然有!那就是雷迪森。Redisson相对于Jedis的原生态应用,对Redis的请求做了更多的封装,也提供了相应的锁方法,可以直接使用:configconfig=newConfig();//配置Redis基本连接信息config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123");//获取一个RedissonClient对象RedissonClientredisson=Redisson.create(config);//获取一个锁对象实例RLocklock=redisson.getLock("锁");try{//获取锁booleanb=lock.tryLock(500,1000,TimeUnit.MILLISECONDS);if(b){//获取锁开始写业务RBucketbucket=redisson.getBucket("javaboy");bucket.set("www.javaboy.org");对象o=bucket.get();System.out.println(o);}else{系统。out.println("没有拿到锁");}}catch(InterruptedExceptione){e.printStackTrace();}finally{//释放锁lock.unlock();}这段代码中,核心是锁.tryLock(500,1000,TimeUnit.MILLISECONDS);,第一个参数是尝试加锁的等待时间为500毫秒,第二个参数表示加锁超时时间为1000毫秒,即锁会在1000毫秒后自动失效。朋友发现这和我们在方案一中配置的参数是一样的,其实思路还是一样的。Redisson只是封装了我们写的锁相关的方法。3.总结当然,这里我只是简单介绍一下加锁的思想以及在Redis单机中如何加锁。后面宋哥给大家分享一下如何在一个Redis集群上加锁。