Java Mongo DuplicateKey错误,尽管没有重叠

Java Mongo DuplicateKey错误,尽管没有重叠,java,mongodb,Java,Mongodb,我有一个记录良好的java服务器池,它位于一个F5负载平衡器后面(经过专业管理,它不会向>1个主机发送流量),运行Tomcat并安装了我的应用程序,连接到一个分片的mongo集群。我使用主自然密钥的base64编码SHA-1散列作为_id。当要创建新记录时,我会执行一个非常基本的操作: BasicDBObject query = new BasicDBObject(); query.put("userId", userId); query.put("_id", id); DBObject use

我有一个记录良好的java服务器池,它位于一个F5负载平衡器后面(经过专业管理,它不会向>1个主机发送流量),运行Tomcat并安装了我的应用程序,连接到一个分片的mongo集群。我使用主自然密钥的base64编码SHA-1散列作为_id。当要创建新记录时,我会执行一个非常基本的操作:

BasicDBObject query = new BasicDBObject();
query.put("userId", userId);
query.put("_id", id);
DBObject user = getUsersCollection().findOne(query);

if (user == null) {
  getUsersCollection().insert(new UserObject(userId));
}
这是简化的。事实上,对该用户的存在性有多个检查,包括一个应该抛出自定义异常的检查,并且没有任何检查被触发。流量日志表示一个传入的create请求,下面是一个示例:

2014-01-19 20:03:45,167  [http-bio-7950-exec-827]:[...] : ERROR FATAL [...] - Internal server error
[...]: com.mongodb.MongoException$DuplicateKey: { "serverUsed" : "[...]" , "singleShard" : "replicaset_2/host1:27017,host2:27017,host3:27017" , "err" : "E11000 duplicate key error index: Users.$_id_  dup key: { : \"HASH\" }" , "code" : 11000 , "n" : 0 , "lastOp" : { "$ts" : 1390190614 , "$inc" : 1} , "connectionId" : 335764 , "ok" : 1.0}
但在“我的用户”集合中,已创建了以下记录:

db.Users.findOne({u id:“HASH”}):

{
  "_id" : "HASH",
  "createDate" : ISODate("2014-01-20T04:03:45.161Z"),
  ...
}
由于时间戳的原因,我将此粘贴为重要内容。我们有一个时区问题,但撇开它不谈,我将6ms的差异解释为mongo集群和我的应用服务器之间的时钟偏差。没有关于这个传入流量的其他记录(它在服务器之间跳转时被记录下来,甚至没有其他记录!),因此我99.999%相信我的单个合法插入呼叫正在插入并抛出错误


任何关于如何/为什么会发生这种情况的理论都将不胜感激。如果需要,我将运行跟踪程序和示例,以回答包含更多信息的问题。

您正在使用
\u id
userId
字段搜索用户。试着注释掉这一行:
query.put(“\id”,id)

代码中不清楚Java变量
userId
来自何处。还不清楚
UserObject
如何设置
\u id

总的来说,您搜索用户的方式和创建用户的方式似乎不匹配,即,是什么定义了该用户上的唯一键

可以使用一个修复程序来替换这些线路:

query.put("userId", userId);
query.put("_id", id);
与:


\u id
字段设置为您的
用户id

看起来像是将字面单词
散列设置为
\u id
而不是真正的散列。那只是伪代码。哎呀,我忘了在我的描述中更明确地说明这一点,因为我认为可能会出现。我不得不编辑代码片段以减少暴露性,而且我在演示实际上不存在的不匹配时犯了一个错误。通过测试数以百万计的随机生成的ID,我非常确定在_idvs userId上没有冲突的风险,而且在Mongo中不可能存在一个用户有一个没有另一个,没有API可以做到这一点。但是,我会看看我是否能把它应用到测试中,100%确定。谢谢您不必按原样使用默认的
\u id
。您可以将其设置为任何标量类型,如ObjectID、Int或String。实际上,在查找用户和创建新用户之间存在一点竞争条件。如果你想创建一个不存在的用户,你可以使用upsert,它将是原子的,事实证明这是真的。我的日志记录被我所想的拒绝了,比赛条件也受到了影响,在同一毫秒内,甚至在不同的主机之间,有几个神秘的双重呼叫!因此,我需要更多的错误处理和查找原因,但至少我不必担心Mongo Java驱动程序;哦。非常感谢,阿列克西!我真的以为我一直在努力,希望没有什么不愉快的感觉。
query.put("_id", userId);