AppEngine Memcache 原子获取和删除

2024-01-07

我想将身份验证质询存储在 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对于之后对相同密钥的任何请求(除非已再次设置)?它也应该返回null对于任何从未设置过的键。

PS:我用的是Java。


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

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

  1. delete https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#delete%28java.lang.Object%29:                 删除条目并返回该条目是否存在
  2. 增量 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#increment%28java.lang.Object,%20long%29:           增加一个条目并返回其新值
  3. put https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#put%28java.lang.Object,%20java.lang.Object,%20com.google.appengine.api.memcache.Expiration,%20com.google.appengine.api.memcache.MemcacheService.SetPolicy%29:                      放置一个条目并返回它是否存在(如果与右侧一起使用)policy https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService.SetPolicy)
  4. putIfUntouched https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#putIfUntouched%28java.lang.Object,%20com.google.appengine.api.memcache.MemcacheService.IdentifiableValue,%20java.lang.Object%29:   如果条目仍然与预期状态匹配则放置一个条目(返回是否匹配)

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

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

开始:

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 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService.SetPolicy#ADD_ONLY_IF_NOT_PRESENT看跌期权和毫秒无重新添加 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#deleteAll%28java.util.Collection,%20long%29用于删除。

可能的优化:使用putAll https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#putAll%28java.util.Map%29.

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. put

具有与门控获取和删除的每个条目相关联的读取标志(不存在标记条目)。本质上与“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 来解决。

可能的优化:使用删除所有 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/MemcacheService#deleteAll%28java.util.Collection,%20long%29.

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-entries,因为该值被保留以表示“条目已被占用”。

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

可能的优化: putIfUntouched 与立即到期日 https://developers.google.com/appengine/docs/java/javadoc/com/google/appengine/api/memcache/Expiration (在这里了解如何! https://stackoverflow.com/questions/23478588/appengine-memcache-expiration-policies/23479048#23479048) 而不是删除。

结论

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AppEngine Memcache 原子获取和删除 的相关文章

随机推荐