接口幂等性的设计之————redis分布式锁的应用

接口幂等性的设计之————redis分布式锁的应用

在集群分布式机器部署的前提下,接口在相同数据高并发的情况下如果没有唯一索引的情况下,可能会有一些问题。

比如:

插入或更新商品的接口,如果没有则插入,有则更新的接口。支持多次修改。

考虑一种情况,前端页面第一次提交时瞬间点击多次。这种情况下会先去数据库查询,然后再插入。(当然唯一索引也可以解决,但是这种的有一次提交将会被拒绝)。

所有分布式锁的使用场景可以类比于以前使用sychronized的场景,java自己的锁只适用于单jvm的场景,在多jvm的时候只能用分布式锁来解决。

下面看下简单的使用

/**
 * 
 * @param lockName  锁名称
 * @param acquireTimeout   等待获取锁时间
 * @param lockTimeout   锁超时时间
 * @return
 */
public String acquireRedisLock(String lockName, long acquireTimeout, long lockTimeout) {
    Jedis jedis = getRedisResource();
    if (jedis == null) {
        logger.error("get resource failed");
        return null;
    }
    String identifier = UUID.randomUUID().toString();
    String lockKey =lockName;
    int lockExpire = (int)(lockTimeout / 1000);
    try {
        long end = System.currentTimeMillis() + acquireTimeout;
        while (System.currentTimeMillis() < end) {
            if (jedis.setnx(lockKey, identifier) == 1){
                jedis.expire(lockKey, lockExpire);
                return identifier;
            }
            if (jedis.ttl(lockKey) == -1) {
                jedis.expire(lockKey, lockExpire);
            }
            try {
                Thread.sleep(1);
            }catch(InterruptedException ie){
                Thread.currentThread().interrupt();
            }
        }
    } catch (Exception e) {
        logger.error("acquireLockWithFullLockKey 获取分布式锁出现问题,",e);
    } finally {
        jedis.close();
    }
    // null indicates that the lock was not acquired
    return null;
}


/**
 * 释放分布式锁
 * @param lockName
 * @param identifier
 * @return
 */
public boolean releaseLock(String lockName, String identifier) {
    Jedis jedis = getRedisResource();
    String lockKey =  lockName;
    try {
        while (true){
            jedis.watch(lockKey);
            if (identifier.equals(jedis.get(lockKey))){
                Transaction trans = jedis.multi();
                trans.del(lockKey);
                List results = trans.exec();
                if (results == null){
                    continue;
                }
                return true;
            }
            jedis.unwatch();
            break;
        }
    } catch (Exception e) {
        logger.error("releaseLock 释放分布式锁出现问题,",e);
    } finally {
        jedis.close();
    }
    return false;
}
 
  

释放的时候用到了redis的watch命令和multi,exec,主要是事务方面操作,防止被key的值修改后删除。和jedis.get出来的值不一样。

redis的操作可以见我博客后续关于redis的文章。

这两个方法有了之后,一个redis分布式锁就好了,来看看使用:

public void runAmethod() {
    String lockId = lock.acquireLock("get", 2000, 5000);
    boolean isLock = StringUtils.isNotBlank(lockId);
    if (isLock) {
        System.out.println("执行方法============");
    } else {
        logger.error("未获取到redis锁,不能执行");
    }
    if (null != lockId) {
        lock.releaseLock("get", lockId);
    }
}

使用时直接这样,锁名字每个方法应该不同,锁获取时间根据方法一般执行时间设置,超时时间应该要考虑到吞吐量和接口的最长执行时间。太长了吞吐量不够,太短了可能会导致并发问题。

后续,如果要进一步封装可以这样:

首先定义一个接口

package com.service;

public interface RedisLockMethod {


    void runMethod();

}

然后使用:

先看无锁情况下:

public void test() {
    noLockMethod();
}

private void noLockMethod() {
    System.out.println("我是之前无锁的方法");
}

test方法里面的方法现在要加分布式锁处理,该怎么操作呢?

public void test() {
    //1,lamda写法
    runAmethod(()->noLockMethod());
    //2,匿名内部类写法
    runAmethod(new RedisLockMethod() {

        @Override
        public void runMethod() {
            //原有的方法
            noLockMethod();
        }
    });


}
private void noLockMethod() {
    System.out.println("我是之前无锁的方法");
}


public void runAmethod(RedisLockMethod aaa){
    String lockId = lock.acquireLock("get", 2000, 5000);
    boolean isLock = StringUtils.isNotBlank(lockId);
    if (isLock) {
        aaa.runMethod();
    } else {
        logger.error("未获取到redis锁,不能执行");
    }
    if (null != lockId) {
        lock.releaseLock("get", lockId);
    }
}

提供了两种方法,比较推荐lamda写法。这种写法下runAmethod()这个方法可以抽取出来作为公共的方法,所有需要分布式锁的时候直接调用即可,里面封装要锁的方法即可。

你可能感兴趣的:(业务知识,Spring知识,redis)