您的当前位置:首页正文

redis如何防止并发?

2024-11-28 来源:个人技术集锦

对于很多系统而言,都有集群处理。在集群中使用quartz或者task处理任务的时候,一般有三种选择:

1. 多实例顺序执行

2. 单实例执行

3. 多实例并行执行

对于2,需要单独的实例进行部署运行,如果任务较多,需要独立的资源耗费较多,对于执行任务的实例要求就更高。

对于3,需要成熟的数据驱动式的任务分配,防止多实例重复执行。

2和3会在后续分享中进行。

对于多实例,同一数据库的情况下。创保目前考虑的是1情况。

redis对于并发较小的情况下,进行锁控制,效果是较为理想的。目前创保只有八台实例,适用于该方案。

1. 首先,redis提供了一个setnx方法,该方法执行后会返回key值在设置之前是否存在。

这是进行并发控制的基础,但并不够。

2. 在利用 setnx 进行 key 值设定后,如果instance发生异常(不仅仅是exception)会导致该锁无法正常释放。

所以,我们需要为锁设置一个失效时间,即expire命令。

由于redis有顺序执行命令的特性,如果我们setnx方法与expire命令并不是以一个原子粒度执行的话,将会导致一些不可控的现象发生。

所以,我们3. 使用了multi方法进行命令绑定,将setnx与expire进行绑定,保障同一原子粒度执行。

同时,4. 为了避免不必要的设置损耗,同时为了兼容对于锁的value值进行特别设置的情况,我们进行了exists判断。

整体代码进行整合,方法如下:

public boolean setLock(String key, int seconds, String value) throws BusinessServiceException {
    boolean isBroken = false;
    Jedis jedis = null;
    try {
        jedis = this.jedisPool.getResource();
        boolean exists = jedis.exists(key);
        if (exists) {
            return false;
        }
        Transaction transaction = jedis.multi();
        Response<Long> result = transaction.setnx(key, value);
        transaction.expire(key, seconds);
        transaction.exec();
        return result.get() == 1;
    } catch (Exception e) {
        logger.error("redis getLock error" + e.getMessage());
        logger.error(e);
        return false;
    } finally {
        this.releaseJedis(jedis, isBroken);
    }
}

同时,在使用该锁的时候,也有一些规范需要遵守。

在获取到锁之后,才能进行业务逻辑处理。

在业务逻辑处理之后,必须手动释放锁。

未获取到锁的instance不允许释放锁。

过期时间必须设置,推荐设置时间为业务逻辑执行时间的1.5倍左右或任务间隔的2~3倍左右。

示例代码如下:

boolean result = false;
try {
    result = redisService.setLock(TIMER_NAME, EXPIRE_TIME, LOCK_STATUE);
} catch (BusinessServiceException e) {
    logger.error("timer get redis lock error:" + e.getMessage(), e);
}

if (result) {
    try {
        //此处执行业务逻辑
    } catch (Exception e) {
        logger.error("syncPolicy error:" + e.getMessage(), e);
    } finally {
        try {
            redisService.delRedisValue(TIMER_NAME);
        } catch (BusinessServiceException e) {
            logger.error("timer relase redis lock error:" + e.getMessage(), e);
        }
    }
}

对于2和3的方案,后续再进行分享。

显示全文