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.
睡了一晚后,我提出了几种方法.其中一个都不是特别优雅,但让我把它们放在这里作为思考的食物:
MemCache提供(至少)4个命令,以某种方式原子地修改条目并返回有关其先前状态的一些信息(其中两个也由@Moshe Shaham指出):
让我为每个人提供一个可能的实施方案.前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?)实际上可靠地工作.
| 归档时间: |
|
| 查看次数: |
898 次 |
| 最近记录: |