Java AppEngine Memcache原子获取和删除

Java AppEngine Memcache原子获取和删除,java,google-app-engine,memcached,Java,Google App Engine,Memcached,我想将身份验证挑战存储在Google AppEngine的Memcache中,我用一个随机整数对其进行索引。例如,我有如下条目: 5932->IUH(*HKJSBOHFBAHV&EG*Y$)*HF739r7fGA$74gflUSAB 11234->(*&YGy3g87gfGYJKY#GRO&FTA9P8YFDLHH$UT#K&GAk&G -3944->8yU#*&GfgKGF&Y@dy;0aU(Y9fyhgyL$BVGAZD(O$fg($&G . : 如果客户端随后尝试验证请求,它将向我发送质

我想将身份验证挑战存储在Google AppEngine的Memcache中,我用一个随机整数对其进行索引。例如,我有如下条目:

5932->IUH(*HKJSBOHFBAHV&EG*Y$)*HF739r7fGA$74gflUSAB
11234->(*&YGy3g87gfGYJKY#GRO&FTA9P8YFDLHH$UT#K&GAk&G
-3944->8yU#*&GfgKGF&Y@dy;0aU(Y9fyhgyL$BVGAZD(O$fg($&G
.
:
如果客户端随后尝试验证请求,它将向我发送质询ID(例如,-3944)和相应的计算响应

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

出于性能和配额方面的原因,我希望避免使用数据存储来应对挑战。我将建立一个允许客户端请求更多挑战并重试请求的系统,这样Memcache被清除的罕见情况也不会成为问题

是否有一种方法可以在Memcache中执行原子获取和删除操作,该操作将精确返回给定密钥的条目一次,并在之后(除非已再次设置)对同一密钥的任何请求返回null?对于从未设置的任何密钥,它还应返回null


PS:我正在使用Java。

为了管理并发性和竞争条件,memcache中内置了两种机制:

  • :您可以围绕此原子增量函数构建一种机制。只需在开始处理某个值时递增计数器,完成时递减,并且在计数器递增时不允许任何其他线程工作

  • :如果memcache值在读取后已更改,则此函数不允许您更改该值。在这种情况下,它将返回false


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

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

  • :  删除条目并返回它是否存在
  • :递增条目并返回其新值
  • :   放置一个条目并返回它是否存在(如果与右侧一起使用)
  • :  如果条目仍与预期状态匹配,则放置该条目(返回是否匹配)
  • 让我为它们中的每一个提供一个可能的实现。前3个将特定于整数键,并且将要求实际条目具有一个正键(因为它们将使用相应的负键作为标记条目)

    注意:下面给出的示例本质上是概念性的。其中一些示例可能仍然考虑了种族条件,而我实际上没有对其中任何一个进行测试。因此,请恕我直言

    下面是:

    1.删除

    第一次存储条目时,将一个标记与其一起存储(使用派生的对应键)。根据删除该标记的尝试是否成功,选择get和delete

    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可能会覆盖正在读取的值。可以通过对put和delete使用策略来避免

    可能的优化:使用

    2.增量

    将读取计数器与gates获取和删除的每个条目相关联

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

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

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

    可能的优化:使用

    4.未被触及

    我当前的最爱:在模拟事务中尝试获取一个条目,并将其设置为null,然后使用该条目的成功进行gate delete

    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-项,因为这个值被保留以表示“已获取的项”

    可能的比赛条件?我目前没有看到,但可能我错过了什么

    可能的优化:使用immediate()而不是delete进行putIfUntouched

    结论


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

    这并不能以任何方式回答OP的问题。要优化4,只需省略删除-它不会添加任何内容。后续读取将为null,您的检查意味着它将与缺少的值完全相同。@Greg Correct。不过,删除给我的是pu重新使用质询ID的能力如果不是当前策略,则使用ADD_ONLY_____PRESENT-policy(仅添加策略)。如果我不实际删除或使条目过期,我将无法重复使用它。第4个对我来说很好。我认为这就是函数PutifUntouch的全部目的
    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();
    }