Unicorn+Nginx并发与数据去重

Unicorn+Nginx concurrency and data duplication

我有 4 个 Nginx worker 和 4 个 unicorn worker。我们在一些验证唯一名称的模型中遇到了并发问题。当我们在同一个资源上同时发送多个请求时,我们会得到重复的名称。例如,如果我们发送大约 10 个创建许可证的请求,我们会得到重复 serial_numbers...

这是一些背景信息:

模型(简化)

class License < ActiveRecord::Base
  validates :serial_number, :uniqueness => true
end

Unicorn.rb

APP_PATH = '.../manager'

worker_processes 4
working_directory APP_PATH # available in 0.94.0+

listen ".../manager/tmp/sockets/manager_rails.sock", backlog: 1024
listen 8080, :tcp_nopush => true # uncomment to listen to TCP port as well

timeout 600
pid "#{APP_PATH}/tmp/pids/unicorn.pid"
stderr_path "#{APP_PATH}/log/unicorn.stderr.log"
stdout_path "#{APP_PATH}/log/unicorn.stdout.log"

preload_app true

GC.copy_on_write_friendly = true if GC.respond_to?(:copy_on_write_friendly=)

check_client_connection false

run_once = true

before_fork do |server, worker|
  ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
  MESSAGE_QUEUE.close
end

after_fork do |server, worker|
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
end

Nginx.conf(简体)

worker_processes 4;

events {
  multi_accept off;
  worker_connections 1024;
  use epoll;
  accept_mutex off;
}

upstream app_server {
  server unix:/home/blueserver/symphony/manager/tmp/sockets/manager_rails_write.sock fail_timeout=0;
}

try_files $uri @app;

location @app {
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Host $http_host;
  proxy_redirect off;
  proxy_connect_timeout      600;
  proxy_send_timeout         600;
  proxy_read_timeout         600;
  proxy_pass http://app_server;
}

每次我发送多个请求(超过 4 个)来创建许可证时,我都会得到一些副本。我明白为什么。这是因为每个 unicorn 进程还没有创建 serial_number 的资源。因此,它允许多次创建它...

ActiveRecord 正在进程级别而不是数据库级别验证字段的唯一性。一种解决方法是将验证移至数据库(但这将非常麻烦且难以维护)。

另一种解决方法是将写入请求 (POST/PUT/DELETE) 限制为仅一个独角兽,并让多个独角兽回复读取请求 (GET)。在 Nginx 的位置中有这样的东西...

# 4 unicorn workers for GET requests
proxy_pass http://app_read_server;

# 1 unicorn worker for POST/PUT/DELETE requests
limit_except GET {
  proxy_pass http://app_write_server;
}

我们目前正在使用它。它修复了并发问题。但是,一个写入服务器不足以在高峰时间进行回复,这会造成瓶颈。

有什么办法解决Nginx+Unicorn的并发和扩展问题吗?

看看事务隔离。例如,PostgreSQL - http://www.postgresql.org/docs/current/static/transaction-iso.html.

通常情况下,您可以采用两种方式:

  • 对唯一键列使用唯一索引(通过迁移)并捕获适当的异常;

  • 以描述的方式维护数据库约束 here 并捕获适当的异常。

或使用具有隔离级别 "serialised" 的 PostureSQL 事务,这基本上是将并行翻译转换为连续翻译,正如 Andrey Kryachkov 早期所描述的那样。