AppEngine Memcache原子获取和删除

Mar*_* A. 2 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
   .
   :
Run Code Online (Sandbox Code Playgroud)

如果客户端然后尝试验证请求,它将向我发送质询ID(例如-3944)和相应的计算响应.

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

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

有没有办法在Memcache中执行原子获取和删除操作,它将返回给定键的条目一次,并在之后为同一个键的任何请求返回null(除非已经再次设置)?对于从未设置的任何键,它也应该返回null.

PS:我正在使用Java.

Mar*_* A. 5

睡了一晚后,我提出了几种方法.其中一个都不是特别优雅,但让我把它们放在这里作为思考的食物:

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

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

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

注意:下面给出的示例本质上是概念性的.他们中的一些人可能仍然允许竞争条件,我实际上没有测试任何一个.所以,带上一粒盐.

开始:

删除

首次存储条目时,将标记与其一起存储(使用派生的相应密钥).根据尝试删除此标记的成功,对获取和删除进行门控.

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;
}
Run Code Online (Sandbox Code Playgroud)

可能的竞争条件:putForAtomicGnD可能会覆盖正在读取的值.可以通过使用put和millisNoReAdd的ADD_ONLY_IF_NOT_PRESENT策略进行删除来避免.

可能的优化:使用putAll.

增量

有一个与每个条目相关联的读取计数器,用于取消和删除.

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;
}
Run Code Online (Sandbox Code Playgroud)

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

有一个与标记为get-and-delete的每个条目相关联的读标志(不存在标记条目).基本上是方法的反转"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;
}
Run Code Online (Sandbox Code Playgroud)

可能的竞争条件:另一个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();
}
Run Code Online (Sandbox Code Playgroud)

注意:这种方法几乎完全通用(对键类型没有限制),因为它不需要从给定的标记对象派生标记对象的键.它唯一的限制是它不支持实际的条目,因为该值被保留以表示"已经进入的条目".

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

可能的优化:putIfUntouched with Immediate Expiration(找出如何!)而不是delete.

结论

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