基于redis的分布式锁
基于redis的分布式锁:
大概原理:跟基于DB的类似,基于redis的分布式锁是争抢redis上的一个键,谁给这个键赋值了谁就抢到了锁,主要是用了redis的setnx机制,也就是set if not exists。实际一般不用setnx这个api,主要是粒度略粗,不能指定失效时间,用set(key,value,"nx","px",1000),几个参数分别为键、值、设置方式、时间单位、时间。
---------Talk Is Cheap, Just Show Me The Code-------------------------------------------------
获取锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 阻塞锁 * @return */ public boolean lock(){ boolean flag = tryLock(); if (flag){ return true ; } else { int time = ( int ) (Math.random() * 1000 ); try { //随机睡眠一段时间,避免多个线程同时睡眠,同时醒来 Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } return lock(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * 非阻塞锁 * @return */ public boolean tryLock(){ String str = UUID.randomUUID().toString(); Jedis jedis = new Jedis( "localhost" ); String result = jedis.set(lock,str, "NX" , "PX" , 1000 ); if (result!= null && result.equals( "OK" )){ local.set(str); return true ; } return false ; } |
释放锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * 释放锁 * @return */ public void unlock(){ String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then " + " return redis.call(\"del\",KEYS[1]) " + "else " + " return 0 " + "end" ; ArrayList<String> keysList = new ArrayList<>(); keysList.add(lock); ArrayList<String> argsList = new ArrayList<>(); String args = local.get(); argsList.add(args); Jedis jedis = new Jedis( "localhost" ); jedis.eval(script, keysList, argsList); } |
解锁过程就是删除redis中数据的过程,由于有失效时间的问题,如果已经失效了就不用再删了,没失效则删除。
注意,这里用了一段lua脚本,原因是要保证解锁操作为原子操作,如果用普通jedis的api,要经过get值,比较,del值三个阶段,该过程中可能键已失效,导致误删。redis支持lua脚本,此处我们将该过程写成脚本,发到redis端,因为redis为单进程单线程,执行过程中可以保证该过程的原子性。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @Test public void testLock(){ for ( int i= 0 ;i< 10 ;i++){ MyThread t = new MyThread( "mythread" +i); t.start(); cdl.countDown(); } try { Thread.sleep( 10000 ); } catch (InterruptedException e) { e.printStackTrace(); } } //内部类 class MyThread extends Thread{ private String threadName; public MyThread(){}; public MyThread(String threadName){ this .threadName = threadName; } @Override public void run() { try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } if (redisLock.lock()){ System.out.println( "线程-" +threadName+ "获得锁,orderId为:" +MyResources.getInstance().getNextId()); redisLock.unlock(); } } } |
执行结果:
线程-mythread3获得锁,orderId为:1
线程-mythread6获得锁,orderId为:2
线程-mythread5获得锁,orderId为:3
线程-mythread1获得锁,orderId为:4
线程-mythread2获得锁,orderId为:5
线程-mythread0获得锁,orderId为:6
线程-mythread7获得锁,orderId为:7
线程-mythread8获得锁,orderId为:8
线程-mythread9获得锁,orderId为:9
线程-mythread4获得锁,orderId为:10
------------------------------------------------
结果就不解释了,redis的分布式锁相比数据库的,一大优势是性能高,但没有缺点么?显然也不是:
1、锁的失效时间难以把握,过短业务没处理完就失效了,造成多个业务竞争资源,可能会失去锁应有的作用,过长会导致其它锁盲目等待,导致锁的性能较低,经验来看,一般为单线程处理业务时长的2-3倍较合适;
2、可能出现锁失效的问题,比如某个线程执行时间特别长,超出了有效期,别的线程也会抢到锁,可能造成锁失效;
3、在redis集群中不太适合,因为是基于master节点的,master节点挂掉的时候如果数据没有同步到salve中,那么会造成锁失效;
整体来说,redis的分布式锁仍然有较多问题,下一篇我们整理基于zk的分布式锁,并对三种锁作比较。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步