Java AppEngine Memcache原子获取和删除
我想将身份验证挑战存储在Google 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 . : 如果客户端随后尝试验证请求,它将向我发送质
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确实提供(至少)4个命令,以某种方式对条目进行原子修改,并返回有关其先前状态的一些信息(其中两个命令也是@Moshe Shaham指出的):
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();
}