【问题标题】:Lock Service not always locking in Google Apps Script锁定服务并不总是锁定在 Google Apps 脚本中
【发布时间】:2017-05-11 16:50:47
【问题描述】:

在我的 Google Apps 脚本应用程序中,有一个地方可以生成唯一的递增订单号。为此,我使用了内置的LockService 和PropertiesService。

我将一个数字存储为脚本属性。当我需要一个新的订单号时,应用程序会获取当前值,将其递增,然后保存新值以备下次使用。为了确保运行应用程序的两个人不会获得相同的数字,对脚本属性的访问被放置在脚本锁或互斥锁内。

这很好用,每天都有数百个调用通过该函数。但是在过去的一个月里,有两次,两个用户得到了相同的订单号。

// Increment the existing value, and return our new value.
function getPropIncrementViaMutex(propId) {
  try {
    var scriptProperties = PropertiesService.getScriptProperties();
    var lock = LockService.getScriptLock();
    var success = false;
    var prevValue = null;
    var newValue = null;
    var wasSet = null;

    while (!success) {
      success = lock.tryLock(500);
      if (!success) {
        Utilities.sleep(1000);
      } else {
        prevValue = Number(scriptProperties.getProperty(propId));
        scriptProperties.setProperty(propId, prevValue + 1);
        newValue  = Number(scriptProperties.getProperty(propId));
        lock.releaseLock();
        wasSet = (newValue === (prevValue + 1));
      }
    }
    if (wasSet) {
      return newValue;
    } else {
      throw new Error("Error incrementing order number. Previous Value: " + prevValue + " New Value: " + newValue);
    }

  } catch(e) {
    if (lock) {
      lock.releaseLock();
    }
    throw e;
  }
}

我在这里做错了吗?是谷歌的问题吗?

一位同事建议将lock.tryLock(500) 中的锁定时间增加到更高的时间,例如lock.tryLock(800)。 他还建议在释放锁的时候,提前调用Utility.sleep(300),这样脚本就有足够的时间来更新属性。 他认为发布是在属性更新之前发生的。

我将尝试实施他的建议,因为它们不会造成伤害,但我想听听关于这个问题的任何其他想法。

【问题讨论】:

  • 感谢桑迪的建议。目前,如果我无法获得tryLock 的锁,我会等待一秒钟,然后再试一次,一遍又一遍。我仍然能够知道锁是否有效,因为tryLock 返回一个布尔值,指示是否获得了锁:developers.google.com/apps-script/reference/lock/…
  • 就个人而言,我不喜欢使用 PropertiesServices 进行快速访问和编写。总是有问题,您可以尝试使用电子表格来生成您的订单号。我认为您的代码在使用锁方面没有任何问题。但是,要指出的是,有序列号是在线系统中的一个缺陷/风险(Spoofing risk),在订单号后添加一个随机字符串可以降低风险并保持订单号的唯一性
  • 我同意你同事的观点,并猜测 scriptProperties.getProperty 会返回一个缓存值。因此,在您释放锁之后,可能有一段时间该值实际上尚未写入。额外的睡眠有点可怕,但可能会有所帮助,有帮助吗?
  • 我不记得它是否有多大帮助,抱歉!在此之后不久,我们最终迁移到了一个新系统,其中生成唯一订单号已经融入了框架的较低级别部分。

标签: google-apps-script mutex


【解决方案1】:

这实际上是一个比想象的更简单的问题。问题在以下代码部分:

    prevValue = Number(scriptProperties.getProperty(propId));
    scriptProperties.setProperty(propId, prevValue + 1);
    newValue  = Number(scriptProperties.getProperty(propId));

您将属性设置为 prevValue + 1,然后立即将 newValue 分配给同一属性。在您的代码中,这两行代码同步执行,但 setProperty 方法本质上是异步的。在极少数情况下,向 Google 服务器上的 PropertiesService 写入数据可能会有所延迟。同时,您的代码继续执行。这可能会导致在上一行中实际更新 propId 之前分配 newValue。

这是解决此问题的简单解决方案:

function isTimeUp(startTime, milliSeconds){
  var now = new Date();
  return now.getTime() - startTime.getTime() > milliSeconds
}
.
.    
.
prevValue = Number(scriptProperties.getProperty(propId));
newValue = PrevValue + 1;
scriptProperties.setProperty(propId, newValue);
var start = new Date();
// run while loop for a maximum 30000 milliseconds = 30 seconds, to prevent endless loop
while (Number(scriptProperties.getProperty(propId))) != newValue &&
!isTimeUp(start, 30000)){};
.
.
.

直接从代码中的 prevValue 分配 newValue,而不是从属性中分配。没有更多的异步问题。
while 语句和isTimeUp() 函数用于确保在释放锁之前更新属性。这可能有点矫枉过正,但是您可以确保该函数在下一个用户运行它之前正确更新 propId,并且 isTimeUp() 函数确保在互联网故障写入 PropertiesService 的情况下不会出现无限循环(如果我是'写到最后不要懒惰,如果更新超时,我也会回滚任何效果。但现在我们开始陷入困境;)

【讨论】:

    猜你喜欢
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 2019-01-04
    • 1970-01-01
    • 2015-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多