一个操做要修改用户的状态,修改状态须要先读出用户的状态,在内存里进行修改,改完了再存回去。若是这样的操做同时进行了,就会出现并发问题,由于读取和保存状态这两个操做不是原子的。这个时候就要使用到分布式锁来限制程序的并发执行。redis
通常是使用 setnx(set if not exists) 指令占坑, 用完再调用 del 指令释放茅坑。若是逻辑执行到中间出现异常了,可能会致使 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。因而咱们在拿到锁以后,再给锁加上一个过时时间,好比 5s,这样即便中间出现异常也能够保证 5 秒以后锁会自动释放。算法
/** * @Auther: majx2 * @Date: 2019-3-21 16:02 * @Description: */ public class DistributedLockTest { Jedis jedis = RedisDS.create().getJedis(); final static String KEY = "KEY"; @Test public void testLock() throws InterruptedException { new Thread(new Runnable() { @Override public void run() { Assert.assertTrue(exec()); } }).start(); Thread.sleep(1000); Assert.assertFalse(exec()); Thread.sleep(3000); } public boolean exec(){ return new RedLock().trylock(KEY, new LockWrap() { @Override public boolean invoke() { try { Thread.sleep(2000); } catch (InterruptedException e) {} return true; } }); } public class RedLock{ public boolean trylock(String key,LockWrap wrap){ Long result = jedis.setnx(key, KEY);// 占坑 if(result == 1L){ jedis.expire(key,5000); // 避免没有删除 boolean invoke = wrap.invoke(); jedis.del(key); return invoke; } return false; } } public interface LockWrap{ boolean invoke(); } }
可是以上逻辑还有问题。若是在 setnx 和 expire 之间服务器进程忽然挂掉了,多是由于机器掉电或者是被人为杀掉的,就会致使 expire 得不到执行,也会形成死锁。这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。 解决这些问题,可使用开源分布式组建redission。缓存
Redis 的分布式锁不能解决超时问题,若是在加锁和释放锁之间的逻辑执行的太长,以致于超出了锁的超时限制,就会出现问题。由于这时候锁过时了,第二个线程从新持有了这把锁,第二个线程就会在第一个线程逻辑执行完之间拿到了锁;紧接着第一个线程执行完了业务逻辑,就把锁给释放了,第三个线程就会在第二个线程逻辑执行完之间拿到了锁。为了不这个问题,Redis 分布式锁不要用于较长时间的任务。若是真的偶尔出现了,数据出现的小波错乱可能须要人工介入解决。安全
在 Sentinel 集群中,主节点挂掉时,从节点会取而代之,客户端上却并无明显感知。原先第一个客户端在主节点中申请成功了一把锁,可是这把锁尚未来得及同步到从节点,主节点忽然挂掉了。而后从节点变成了主节点,这个新的节点内部没有这个锁,因此当另外一个客户端过来请求加锁时,当即就批准了。这样就会致使系统中一样一把锁被两个客户端同时持有,不安全性由此产生。服务器
若是你很在意高可用性,但愿挂了一台 redis 彻底不受影响,那就应该考虑 redlock算法
。不过代价也是有的,须要更多的 redis 实例,性能也降低了。并发
注: Redlock算法,须要提供多个 Redis 实例,这些实例以前相互独立没有主从关系。加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,须要向全部节点发送 del 指令。不过, Redlock 算法还须要考虑出错重试、时钟漂移等不少细节问题,同时由于 Redlock 须要向多个节点进行读写,意味着相比单实例 Redis 性能会降低一些。分布式
本文基于《Redis深度历险:核心原理和应用实践》一文的JAVA实践。更多文章请参考:高性能缓存中间件Redis应用实战(JAVA)ide