1.Redis Lock

使用场景

同步锁,让业务方法在锁设定的时间内是同步执行的

  • redisService.setIfAbsent

  • redisService.expire

@PostMapping("/update")
public MMHResponse      saveOrUpdateMemberBaby(@RequestBody SaveOrUpdateMemberBabyForm form){
		GlobalLock globalLock = null;
		try {
			globalLock = GlobalLockRedisImpl.getInstance(key);
			globalLock.lock();
		//TODO ...业务方法
			return SuccessResponse.newInstance();
		} catch (Exception e) {
			logger.error("[ebiz-user-service][saveOrUpdateMemberBaby]操作失败,原因{}", StackTraceLogUtil.getStackTraceAsString(e));
			return ErrorResponse.newInstance("操作失败");
		} finally {
			//释放锁
			if (globalLock != null) {
				globalLock.unlock();
			}
		}
	}

不难看出,redis不参与业务逻辑代码,仅仅作一个定时的开发,保证在某个时间段内 业务代码不受干扰

我们再来看一下lock 方法

GlobalLock
/**
 * <pre>
 * Example 1: 强制获取锁,会阻塞, 超时失败会抛出异常
 *   GlobalLock lock = GlobalLockRedisImpl.getInstance("local key name")
 *   try{
 *      lock.lock(5);
 *      doSamething()
 *   } catch(GDSystemException e) {
 *       //获取锁异常
 *   } finally {
 *      lock.unlock();
 *   }
 *
 *
 *  Example 2: 尝试获取锁, 失败会直接返回false
 *   GlobalLock lock = ...;
 *      if (lock.tryLock()) {
 *          try {
 *              // manipulate protected state
 *          } finally {
 *              lock.unlock();
 *          }
 *      } else {
 *          // perform alternative actions
 *      }
 * </pre>
 */
public interface GlobalLock {


    /**
     * 申请加锁,此操作为阻塞操作,且不响应线程中断
     * 超过指定时间会抛出异常,程序需要对异常处理, 最大锁住时间为1分钟
     *
     * @throws RuntimeException 在指定的时间内获取锁失败
     */
    void lock(int timeout) throws RuntimeException;


    /**
     * 申请加锁, 最大时间30秒
     *
     * @throws RuntimeException
     */
    void lock() throws RuntimeException;

    /**
     * Acquires the lock only if it is free at the time of invocation.
     * <p>
     * <p>Acquires the lock if it is available and returns immediately
     * with the value {@code true}.
     * If the lock is not available then this method will return
     * immediately with the value {@code false}.
     * <p>
     * <p>A typical usage idiom for this method would be:
     * <pre>
     *      Lock lock = ...;
     *      if (lock.tryLock()) {
     *          try {
     *              // manipulate protected state
     *          } finally {
     *              lock.unlock();
     *          }
     *      } else {
     *          // perform alternative actions
     *      }
     * </pre>
     * This usage ensures that the lock is unlocked if it was acquired, and
     * doesn't try to unlock if the lock was not acquired.
     *
     * @return {@code true} if the lock was acquired and
     * {@code false} otherwise
     */
    boolean tryLock();


    /**
     * 释放锁
     */
    void unlock();


}

  

GlobalLockRedisImpl
/**
 * 基于redis实现的全局锁
 */
public class GlobalLockRedisImpl implements GlobalLock {

    private ILog logger = LogFactory.getLog(GlobalLockRedisImpl.class, LogBusinessModule.TRACE_LOG);

    private RedisService redisService;

    // timeout(ms)
    private static final int TIME_OUT = 30;

    // private Jedis jedis;
    private String key;

    // state flag
    private volatile boolean locked = false;


    private static ConcurrentMap<String, GlobalLock> map = Maps.newConcurrentMap();


    /**
     * 构造函数
     *
     * @param key
     */
    private GlobalLockRedisImpl(String key) {
        this.key = "_LOCK_" + key;
        this.redisService = (RedisService) MyApplicationContext.getBean("redisService");
    }


    public static GlobalLock getInstance(String key) {
        GlobalLock globalLock = map.get(key);
        if (globalLock == null) {
            map.put(key, new GlobalLockRedisImpl(key));
            return map.get(key);
        }
        return globalLock;
    }


    public boolean tryLock() {
        long result = redisService.increment(key);
        boolean success = result <= 1;
        if (!success) {// 锁已被占用,等待释放
            result = redisService.increment(key);
            success = result <= 1;
        }
        if (success) {// 处理锁的自动释放
            redisService.set(key, String.valueOf(result), TIME_OUT);
            locked = true;
            if (logger.isDebugEnabled()) {
                logger.debug("尝试获取锁{}成功, 锁将在 {} 秒后释放", key, TIME_OUT);
            }
        }
        return success;
    }


    public void lock(int timeout) throws RuntimeException {
        long nanoTime = System.nanoTime();
        do {
            if (redisService.setIfAbsent(key, key)) {
                redisService.expire(key, 1, TimeUnit.MINUTES); //设定锁过期时间为1分钟
                locked = true;
                if (logger.isDebugEnabled()) {
                    logger.debug("get key: {} lock success! -- {} 毫秒", key, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - nanoTime));
                }
                return;
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } while ((System.nanoTime() - nanoTime) < TimeUnit.SECONDS.toNanos(timeout));
        throw new RuntimeException("获取锁超时, 消耗时间: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - nanoTime) + "毫秒");
    }


    public void lock() throws RuntimeException {
        lock(TIME_OUT);
    }


    public void unlock() {
        if (locked) {
            redisService.delete(key);
        }
    }


}

  


public void lock() throws GDSystemException { lock(30); } public void lock(int timeout) throws GDSystemException { long nanoTime = System.nanoTime(); while(!redisService.setIfAbsent(this.key, this.key)) { try { Thread.sleep(300L); } catch (InterruptedException var5) { var5.printStackTrace(); } if(System.nanoTime() - nanoTime >= TimeUnit.SECONDS.toNanos((long)timeout)) { throw new GDSystemException("获取锁超时, 消耗时间: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - nanoTime) + "毫秒"); } } this.redisService.expire(this.key,60, TimeUnit.SECONDS); this.locked = true; if(logger.isDebugEnabled()) { logger.debug("get key: {} lock success! -- {} 毫秒", new Object[]{this.key, Long.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - nanoTime))}); } }

lock 默认你的时间30s,

redisService.setIfAbsent(this.key, this.key)

进一步来看setIfAbsent的源码:

public Boolean setIfAbsent(String key, Object value) {
        return this.getRedisTemplate().opsForValue().setIfAbsent(key, value);
    }
    
      public Boolean setIfAbsent(K key, V value) {
        final byte[] rawKey = this.rawKey(key);
        final byte[] rawValue = this.rawValue(value);
        return (Boolean)this.execute(new RedisCallback<Boolean>() {
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.setNX(rawKey, rawValue);
            }
        }, true);
    }

对 Redis setNX的这个方法,参考官方文档,判断key是否存在(1/0)


Return value
Integer reply, specifically:
1 if the key was set
0 if the key was not set

再往下看 redisService.expire 保证key 60s有效,足够执行业务代码,

执行往后,delete该key

posted @ 2018-04-09 20:32  谢幕ゾ华丽  阅读(704)  评论(0编辑  收藏  举报