2012-03-28 69 views
6

我们注意到很多重复记录正在我们的数据库中的各种表格中创建,但是为什么会发生这种情况而不知所措。有趣的是,虽然记录是重复的(甚至是created_at邮票!),但在我们的用户表中,每条记录上的密码salt和hash不同 - 这让我相信Rails以某种方式运行事务/保存操作两次。很明显,我们在应用程序代码中不会多次调用savecreate什么可能导致Rails创建重复记录?

对于保存在数据库中的每条记录,似乎都没有发生这种重复,而且我们似乎还无法推断出模式。在用户模型上还有一个validates_uniqueness_of验证(虽然表格上还没有唯一的密钥;我们需要清理所有重复项以便能够做到这一点) - 所以如果记录已经存在,Rails应该停止自己,但是如果请求同时触发这是一种竞争条件。

我们目前在我们的应用程序服务器(当前是2个)上运行Passenger 3.0.11/nginx后面的Rails 3.2.2,并且有一个中央nginx web服务器,它将请求上传到应用程序服务器。难道这个设置会导致进程被复制或者什么?请求没有被锁定到一个上游服务器(例如,如果一个用户请求包含静态内容的页面,比如图像,可以使用一个或两个应用服务器),请问这很重要吗? (我觉得这是抓住吸管,但我想涵盖所有可能性)

还有什么可能导致这种情况发生?

更新:作为一个示例,今天创建了一个用户,其中有重复的记录。两者都有created_at2012-03-28 16:48:11,除hashed_passwordsalt以外的所有列是相同的。从请求日志,我可以看到以下内容:

应用服务器1:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:47:19 -0400 
[2012-03-28 12:47:19] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:47:20] INFO : Rendered apply/new_user.html.erb within layouts/template (192.8ms) 

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:48:11] INFO : Redirected to apply/initialize_job_application/3517 
[2012-03-28 12:48:11] INFO : /app/controllers/apply_controller.rb:263:in `block (2 levels) in create_user' 

应用服务器2:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 

Web服务器:

1.2.3.4 - - [28/Mar/2012:12:48:10 -0400] "POST /en/apply/create_user HTTP/1.1" 499 0 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 
1.2.3.4 - - [28/Mar/2012:12:48:11 -0400] "POST /en/apply/create_user HTTP/1.1" 302 147 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 

所以创建操作被击中了三次(可能是第一次由于错误返回到表单),并且每台服务器至少一次。后两者都被网络服务器注册为单独的请求,但第一个获得状态代码499 Client Closed Request(根据维基百科的一个nginx扩展),第二个得到如预期的302。 499会造成这里的问题吗?

+0

你看过你的应用程序日志文件?当存在一个重复的用户在数据库中可以找到2个请求在你的日志文件对应的点或只有一个? – 2012-03-28 19:13:48

+0

我们在生产中没有完整的rails查询日志,只有请求日志。然而,插入存在于mysql bin-log中,并且create action在请求日志中不止一次显示至少有一个例子le(尽管这可能不会导致多个*确切的重复*记录,不是?) – 2012-03-28 19:22:15

+0

但是,你能看到匹配te重复记录或只有单个请求的请求对吗? – 2012-03-28 19:25:06

回答

5

想到两种可能性。

当用作负载均衡器时,第一个是Nginx的一个奇怪的(而不是RFC)行为。它将重试针对下一个后端的任何失败请求。 RFC允许仅用于安全方法(例如GETHEAD)。这样做的结果是,如果您的nginx认为某个请求由于某种原因失败了,那么它可能会重新发送到下一个服务器。如果两个服务器都完成了他们的交易,那么你有一个重复记录。从您的网络服务器日志(以及Nginx用来表示用户单击浏览器中止的499状态代码)来看,这看起来是最可能的原因。

第二种可能性是您的用户双击发送按钮。在合适的时机下,他们的浏览器几乎可以同时发送两个完整的请求。

要确保您的用户记录真的是唯一的,您应该在您的数据库上创建唯一索引。事实上,这些都是确保的(虽然与ActiveRecord检查相比,错误信息更糟)。因此,您应该始终在数据库架构和模型上定义您的唯一性约束。前端nginx与更符合的负载均衡器,我建议haproxy

+0

在用户中止请求并且web服务器nginx将请求标记为“499”之前,请求是否可以使其从Web服务器上游到应用服务器是否合理?在这种情况下,即使Web服务器知道它已被终止,请求是否仍将存在于应用程序服务器中? – 2012-03-28 20:36:21

+0

我这么认为。一旦请求被分派到Rack,它不会中止。因此,尽管你的app-server-nginx可能检测到连接中止,你的rails堆栈并不知道,也无法做任何事情。它完成了它的交易,而nginx放弃了答案。所有您的前端负载均衡器已经错误地重新分配请求。 – 2012-03-28 20:44:33

+0

另请参阅http://www.ruby-forum.com/topic/1674379 – 2012-03-28 20:48:55

0

它确实看起来像一个竞争条件。确保在请求之间锁定。很容易发生的情况是,有一两个请求会不时重复。交换物品时没有交易也会发生同样的情况,所以请确保您的请求之间没有竞争。

+0

我会锁定什么?我相信(纠正我,如果我错了),Rails和/或mysql锁定事务中使用的表 - 我们之前遇到过mysql锁错误,因为表被锁定时间过长。 – 2012-03-28 20:26:14

+0

是的,但我不是在谈论交易。我正在谈论发送到服务器的请求。如果您的请求管理器没有正确地将请求分配给外部服务器,则可能很有可能得到竞争状态,从而导致条目重复。这取决于你如何设置经理,但这是一个很有可能的想法。 – Spyros 2012-03-28 20:35:18