【问题标题】:AppEngine Memcache atomic get-and-deleteAppEngine Memcache 原子获取和删除
【发布时间】:2014-06-21 09:18:28
【问题描述】:

我想将身份验证挑战存储在我按随机整数索引的 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。

【问题讨论】:

    标签: java google-app-engine memcached


    【解决方案1】:

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

    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?)确实可以可靠地工作。

    【讨论】:

    • 要优化 4,只需忽略删除 - 它不会添加任何内容。随后的读取将变为空,并且您的检查意味着它将与缺失值完全相同。
    • @Greg 正确。但是,删除给我的是通过将挑战 ID 与 ADD_ONLY_IF_NOT_PRESENT-policy 放在一起来重用挑战 ID 的能力。如果我没有真正删除或过期该条目,我将无法重复使用它。
    • 4 号听起来不错。我认为这就是 putIfUntouched 函数的全部目的
    【解决方案2】:

    您在 memcache 中内置了两种机制来管理并发和竞争条件:

    1. increment:你可以围绕这个原子增量函数建立一个机制。当你开始处理一个值时简单地增加一个计数器,当你完成时减少一个计数器,并且在计数器增加时不允许任何其他线程工作。

    2. putIfUntouched:如果内存缓存值在您读取后发生更改,此函数将不允许您更改它。在这种情况下它将返回 false。

    【讨论】:

    • 这并不能以任何方式回答 OP 的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-23
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 2012-05-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多