AppEngine Memcache原子获取和删除

时间:2014-05-05 06:34:37

标签: 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 以查找相同键之后的任何请求(除非它已被重新设定)?对于任何从未设置过的键,它也应该返回 null

PS:我正在使用Java。

2 个答案:

答案 0 :(得分:5)

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

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

  1. delete:删除一个条目并返回它是否存在
  2. increment:递增一个条目并返回其新值
  3. put:输入一个条目并返回它是否存在(如果与右policy一起使用)
  4. putIfUntouched:如果条目仍符合预期状态(返回是否存在),则输入一个条目
  5. 让我为每个人提供一个可能的实施方案。前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 with immediate Expirationfind out how here!)而不是删除。

    <强>结论

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

答案 1 :(得分:1)

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

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

  2. putIfUntouched:如果因为您阅读后更改了内存缓存值,则此函数不会允许您更改内存缓存值。在这种情况下,它将返回false。