java - AppEngine Memcache 原子获取和删除

标签 java google-app-engine memcached

我想在 Google AppEngine 的 Memcache 中存储身份验证挑战,我通过随机整数对其进行索引。所以,例如我有这样的条目:

 5932 -> IUH#(*HKJSBOHFBAHV&EG*Y$#)*HF739r7fGA$74gflUSAB
11234 -> (*&YGy3g87gfGYJKY#GRO&Fta9p8yfdlhuH$UT#K&GAk&#G
-3944 -> 8yU#*&GfgKGF&Y@dy;0aU#(Y9fyhgyL$BVGAZD(O$fg($&G
   .
   :

如果客户端随后尝试对请求进行身份验证,它将向我发送质询 ID(例如 -3944)和相应的计算响应。

现在,我的服务器需要从列表中获取挑战编号 -3944 并将其标记为已使用或(更好)立即将其删除以防止重放攻击(第二个请求使用相同的挑战进行身份验证)。然后,服务器计算响应应该是什么,并根据(错误)匹配接受或拒绝身份验证。

出于性能和配额原因,我想避免必须使用 DataStore 来应对挑战。我将拥有一个允许客户请求更多挑战并重试请求的系统,这样 Memcache 被清除的罕见情况也不会成为问题。

有没有办法在 Memcache 中执行原子获取和删除操作,它将返回给定键的条目恰好一次,并在 (除非它已被重新设置)?对于从未设置的任何键,它还应该返回 null

PS:我正在使用 Java。

最佳答案

在上面睡了一晚上之后,我想出了几个方法。两者都不是特别优雅,但让我把它们放在这里作为思考的食物:

MemCache 确实提供(至少)4 个命令以某种方式原子地修改条目并返回有关其先前状态的一些信息(@Moshe Shaham 也指出了其中两个):

  1. delete : 删除条目并返回它是否存在
  2. increment : 递增条目并返回其新值
  3. put : 放置一个条目并返回它是否存在(如果与右 policy 一起使用)
  4. putIfUntouched : 如果它仍然匹配预期状态则放置一个条目(返回是否匹配)

让我为他们每个人提供一个可能的实现。前 3 个将特定于整数键,并将要求实际条目具有正键(因为它们将使用相应的负键作为标记条目)。

注意:下面给出的示例本质上是概念性的。其中一些可能仍然允许竞争条件,我还没有实际测试过它们中的任何一个。因此,请对它们持保留态度。

这里是:

<强>1。删除

当第一次存储条目时,与它一起存储一个标记(带有派生的对应键)。根据删除此标记的尝试是否成功来控制获取和删除。

public void putForAtomicGnD(MemcacheService memcache, int key, Object value) {
    assert key>=0;
    memcache.put(key, value);    // Put entry
    memcache.put(-key-1, null);  // Put a marker
}

public Object atomicGetAndDelete(MemcacheService memcache, int key) {
    assert key>=0;
    if (!memcache.delete(-key-1)) return null;  // Are we first to request it?
    Object result = memcache.get(key);          // Grab content
    memcache.delete(key);                       // Delete entry
    return result;
}

可能的竞争条件:putForAtomicGnD 可能会覆盖正在读取的值。可以通过使用 ADD_ONLY_IF_NOT_PRESENT 来避免看跌期权政策 millisNoReAdd用于删除。

可能的优化:使用putAll .

<强>2。增量

有一个读取计数器与控制获取和删除的每个条目相关联。

public Object atomicGetAndDelete(MemcacheService memcache, int key) {
    assert key>=0;
    if (memcache.increment(-key-1, 1L, 0L) != 1L) return null; // Are we 1st?
    Object result = memcache.get(key);                         // Grab content
    memcache.delete(key);                                      // Delete entry
    return result;
}

注意:如此处所示,此代码只允许每个键使用一次。要重新使用,需要删除相应的标记,这会导致竞争条件(同样可以通过 millisNoReAdd 解决)。

<强>3。把

有一个读取标志(不存在标记条目)与每个条目相关联,控制获取和删除。本质上与方法“1. 删除”相反。

public Object atomicGetAndDelete(MemcacheService memcache, int key) {
    assert key>=0;
    if (!memcache.put(-key-1, null, null, SetPolicy.ADD_ONLY_IF_NOT_PRESENT))
        return null;                    // Are we 1st?
    Object result = memcache.get(key);  // Grab content
    memcache.delete(key);               // Delete entry
    memcache.delete(-key-1, 10000);     // Delete marker with millisNoReAdd
    return result;
}

可能的竞争条件:另一个 put 可能会覆盖正在读取的值。同样,可使用 millisNoReAdd 解决。

可能的优化:使用deleteAll .

<强>4。 putIfUntouched

我目前最喜欢的:尝试在模拟事务中获取一个条目并将其设置为null,然后使用该条目的成功来进行删除。

public Object atomicGetAndDelete(MemcacheService memcache, Object key) {
    IdentifiableValue result = memcache.getIdentifiable(key);
    if (result==null) return null;                     // Entry didn't exist
    if (result.getValue()==null) return null;          // Someone else got it
    if (!memcache.putIfUntouched(key, result, null)) return null;  // Still 1st?
    memcache.delete(key);                              // Delete entry
    return result.getValue();
}

注意:这种方法几乎是完全通用的(对键类型没有限制),因为它不需要能够从给定的标记对象派生出键。它的唯一限制是它不支持实际的 null 条目,因为此值被保留以表示“条目已被采用”。

可能的竞争条件?我目前没有看到任何东西,但也许我遗漏了什么?

可能的优化:putIfUntouched 立即 Expiration ( find out how here! ) 而不是删除。

结论

似乎有很多方法可以做到这一点,但到目前为止我想出的方法都不是特别优雅。请主要将此处给出的示例用作思考的食物,让我们看看是否可以提出一个更简洁的解决方案,或者至少让我们自己相信上面的其中一个(putIfUntouched?)实际上会可靠地工作。

关于java - AppEngine Memcache 原子获取和删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23466255/

相关文章:

java - 在java中写入剪贴板和excel导出链接

java - 如何编写 JavaScript 函数来检查 JRE 版本

php - Google API PHP 客户端 autoload.php 是必需的但缺少?

java - 在 noSQL "counting QL"数据存储上运行大量 "GAE"

java - 如何在 BlobStore (Java) 中设置文件名属性?

php - 获取错误 324 (net::ERR_EMPTY_RESPONSE)。在 kohana 中使用 memcache 时

Windows Azure 特定版本?

java - 构建 SVM 训练集的困惑

php - 并发写入内存缓存

java - 如何打印像表格这样的二维数组