redis使用lua脚本来实现分布式锁

以下代码使用kotlin实现:
RedisLockService

import java.time.Duration

interface RedisLockService {

  /**
   * 加锁
   *
   * @param key     redis键值对的key
   * @param timeout redis键值对的过期时间 毫秒为单位
   * @return boolean 是否加锁成功
   */
  fun lock(key: String, timeout: Duration): Boolean

  /**
   * 释放锁
   *
   * @param key   释放本请求对应的key
   */
  fun unlock(key: String)

}

实现类:

import com.aegis.oa.common.service.RedisLockService
import org.slf4j.LoggerFactory
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.data.redis.core.script.DefaultRedisScript
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class RedisLockServiceImpl(private val redisTemplate: StringRedisTemplate) : RedisLockService {

  /**  定义获取锁的lua脚本 */
  private val unlockLua = DefaultRedisScript(
      "if redis.call('get', KEYS[1]) == 'lock' then return redis.call('del',KEYS[1]) else return -1 end",
      Long::class.java
  )

  companion object {
    private const val LOCK_EXPIRED = -1L
    private const val LOCK_SUCCESS = 1L
    private val log = LoggerFactory.getLogger(RedisLockServiceImpl::class.java)
  }

  /**
   * 加锁
   *
   * @param key     redis键值对的key
   * @param timeout redis键值对的过期时间 毫秒为单位
   * @return boolean 是否加锁成功
   */
  override fun lock(key: String, timeout: Duration): Boolean {
    try { // 执行脚本
      @Suppress("SpellCheckingInspection")
      val lockLua = DefaultRedisScript(
          "if redis.call('setnx', KEYS[1], 'lock') == 1 then return redis.call('pexpire', " +
              "KEYS[1], KEYS[2]) else return 0 end",
          Long::class.java
      )

      /* 存储本地变量 */
      val result = redisTemplate.execute(
          lockLua,
          listOf(
              key, timeout.toMillis().toString()
          )
      )
      if (LOCK_SUCCESS == result) {
        return true
      }
    } catch (e1: Exception) {
      log.error(
          String.format(
              "REDIS加锁异常,key:%s,threadName:%s", key, Thread.currentThread().name
          ), e1
      )
    }
    return false
  }

  /**
   * 释放锁
   *
   * @param key   释放本请求对应的key
   */
  override fun unlock(key: String) {
    try { // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
      val keys = listOf(key)

      /* 如果这里抛异常,后续锁无法释放 */
      val result: Long = redisTemplate.execute(unlockLua, keys)
      if (LOCK_SUCCESS == result) {
        log.info("解锁成功,key:{},threadName:{},结果:{}", key, Thread.currentThread().name, result)
        return
      }
      if (LOCK_EXPIRED == result) { //
        // 返回-1说明获取到的KEY值与requestId不一致或者KEY不存在,可能已经过期或被其他线程加锁
        // 一般发生在key的过期时间短于业务处理时间,属于正常可接受情况
        log.debug(
            "解锁成功,key:{},threadName:{},结果:{}",
            key,
            Thread.currentThread().name,
            result
        )
      }
    } catch (e: Exception) {
      log.error(
          String.format(
              "REDIS解锁异常,key:%s,threadName:%s", key, Thread.currentThread().name
          ), e
      )
    }
  }

}

猜你喜欢

转载自blog.csdn.net/wwrzyy/article/details/131577509