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

文章描述了一个用Kotlin编写的RedisLockService接口及其实现类RedisLockServiceImpl。该服务提供了加锁和释放锁的方法,利用lua脚本确保操作的原子性,并处理了锁的过期和释放。加锁操作通过setnx命令和pexpire命令实现,释放锁则使用lua脚本来避免锁过期后误删其他线程的锁。
摘要由CSDN通过智能技术生成

以下代码使用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
      )
    }
  }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值