分布式缓存Redis
4 Redis实现分布式锁
田超凡
20191125
转载请注明原作者
1 线程锁和分布式锁
- 线程锁
线程锁指的是在同一个JVM中发生多线程操作时,保证同一时刻只有一个线程能够执行同步操作(同步方法/同步代码块),抢到线程锁的线程才能得到执行机会,线程执行完同步操作之后会释放锁,然后其他线程可以继续抢占线程锁然后执行同步操作,以此类推。
线程锁用来解决同一个JVM中多线程并发时的线程安全问题。
- 分布式锁
分布式锁指的是在SOA、微服务等面向服务的架构中,同时启动了多个具有部分相同功能的服务,使用分布式锁保证同一时刻只有一个服务(一个JVM)能够执行对应的操作,实现策略是当触发每个服务相同功能代码执行点的时候,抢到分布式锁的服务才能执行对应的操作,其他服务不能同时执行,这样就可以保证同一时刻只有一个服务(一个JVM)能够执行这段相同的操作。
分布式锁用来解决多个JVM并行时的JVM线程安全问题
2 Redis实现分布式锁
Redis实现分布式锁基于setnx命令实现,因为在Redis中key保证是唯一的。所以当多个线程同时执行setnx命令创建Key时,只要谁能够创建成功谁就能够获取到锁。
set 命令:set key的时候如果key不存在则新增,如果key存在则直接覆盖。
setnx命令:每次setnx检查该 key是否已经存在,如果key不存在则新增,如果已经存在的话不会执行任何操作。
setnx会返回执行后受影响的Key的数量,返回为0表示key已经存在,没有执行任何操作。(1表示新增Key成功,0表示新增Key失败)
Redis实现分布式锁主要是通过setnx命令操作key实现
Redis实现分布式锁有三个步骤:获取锁、释放锁、超时锁
获取锁:当多个线程同时创建setnx,只要谁能够创建成功谁就能够获取到锁。
释放锁:可以对该key设置一个有效期,避免死锁的现象。
超时锁:体现方式有2种,一种是长时间没有获取到锁导致超时,一种是获取到锁但是长时间没有释放锁导致超时。
3 Zookeeper实现分布式锁
Zookeeper实现分布式锁的核心思想是采用:临时节点+事件通知
因为zk节点路径是保证全局唯一的,当多个线程同时创建该临时节点,只要谁能够创建成功谁就能够获取到锁。
Zookeeper实现分布式锁主要是基于创建临时节点实现。
Zookeeper实现分布式锁也有三个步骤:获取锁、释放锁、超时锁
获取锁:当多个线程同时创建该临时节点,只要谁能够创建成功谁就能够获取到锁。
释放锁:关闭当前Session连接,自动的删除当前的zk节点路径,其他线程重新进入到获取锁阶段。
超时锁:分为2种情况,一种是长时间没有获取到锁(没有创建临时节点)导致超时,一种是获取到锁(创建了临时节点),但是长时间没有释放锁(删除临时节点)导致超时。
4 分布式锁的应用场景
(1) 分布式任务调度平台中保证分布式任务的幂等性。(同一时刻只能有一个JVM执行相同的分布式任务)
(2) 分布式全局id的生成(同一时刻只能有一个JVM生成全局id)
(3) 当服务器采用的是服务集群的时候,需要执行的定时任务可能会重复执行,这种情况可以用分布式锁解决
5 Redis实现分布式锁
Redis分布式锁核心代码
public class RedisUtil { //protected static Logger logger = Logger.getLogger(RedisUtil.class); private static String IP = "192.168.212.148";
//Redis的端口号 private static int PORT = 6379;
//可用连接实例的最大数目,默认值为8; //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 private static int MAX_ACTIVE = 100;
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 private static int MAX_IDLE = 20;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException; private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; private static boolean TEST_ON_BORROW = true;
//在return给pool时,是否提前进行validate操作; private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/** * redis过期时间,以秒为单位 */ public final static int EXRP_HOUR = 60 * 60; //一小时 public final static int EXRP_DAY = 60 * 60 * 24; //一天 public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一个月
/** * 初始化Redis连接池 */ private static void initialPool() { try { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(MAX_ACTIVE); config.setMaxIdle(MAX_IDLE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456"); } catch (Exception e) { //logger.error("First create JedisPool error : "+e); e.getMessage(); } }
/** * 在多线程环境同步初始化 */ private static synchronized void poolInit() { if (jedisPool == null) { initialPool(); } }
/** * 同步获取Jedis实例 * * @return Jedis */ public synchronized static Jedis getJedis() { if (jedisPool == null) { poolInit(); } Jedis jedis = null; try { if (jedisPool != null) { jedis = jedisPool.getResource(); } } catch (Exception e) { e.getMessage(); // logger.error("Get jedis error : "+e); } return jedis; }
/** * 释放jedis资源 * * @param jedis */ public static void returnResource(final Jedis jedis) { if (jedis != null && jedisPool != null) { jedisPool.returnResource(jedis); } }
public static Long sadd(String key, String... members) { Jedis jedis = null; Long res = null; try { jedis = getJedis(); res = jedis.sadd(key, members); } catch (Exception e) { //logger.error("sadd error : "+e); e.getMessage(); } return res; } } |
public class MayiktRedisLock {
private static final int setnxSuccss = 1;
/** * 获取锁 * * @param lockKey 定义锁的key * @param notLockTimeOut 没有获取锁的超时时间 * @param lockTimeOut 使用锁的超时时间 * @return */ public String getLock(String lockKey, int notLockTimeOut, int lockTimeOut) { // 获取Redis连接 Jedis jedis = RedisUtil.getJedis(); // 定义没有获取锁的超时时间 Long endTimeOut = System.currentTimeMillis() + notLockTimeOut; while (System.currentTimeMillis() < endTimeOut) { String lockValue = UUID.randomUUID().toString(); // 如果在多线程情况下谁能够setnx 成功返回0 谁就获取到锁 if (jedis.setnx(lockKey, lockValue) == setnxSuccss) { jedis.expire(lockKey, lockTimeOut / 1000); return lockValue; } // 否则情况下 在超时时间内继续循环 } try { if (jedis != null) { jedis.close(); } } catch (Exception e) { e.printStackTrace(); } return null; }
/** * 释放锁 其实就是将该key删除 * * @return */ public Boolean unLock(String lockKey, String lockValue) { Jedis jedis = RedisUtil.getJedis(); // 确定是对应的锁 ,才删除 if (lockValue.equals(jedis.get(lockKey))) { return jedis.del(lockKey) > 0 ? true : false; } return false; } } |
public class OrderService {
private MayiktRedisLock mayiktRedisLock = new MayiktRedisLock(); private String lockKey = "mayikt_lock";
public void service() { // 1.获取锁 String lockValue = mayiktRedisLock.getLock(lockKey, 5000, 5000); if (StringUtils.isEmpty(lockValue)) { System.out.println(Thread.currentThread().getName() + ",获取锁失败!"); return; } // 2.获取锁成功执行业务逻辑 System.out.println(Thread.currentThread().getName() + ",获取成功,lockValue:" + lockValue); // // 3.释放lock锁 mayiktRedisLock.unLock(lockKey, lockValue); } } |
6 实现分布式锁的方案
- 基于数据库操作实现分布式锁
- 基于Zookeeper临时节点+事件通知实现分布式锁
- 基于Redis的setnx命令操作key实现分布式锁
- 基于Redis分布式锁框架redission实现分布式锁
转载请注明原作者