Nginx - > Unicorn - 资源暂时不可用

时间:2017-11-03 12:25:57

标签: ruby nginx sinatra unicorn

我们在生产中有Sinatra应用程序。

该应用程序具有后端和后端。前端部分。
后端& frond-end紧密耦合并代表整体 Sinatra应用程序的后端主要是简单代理"真正的"后端写在Java / Sping / Mongo上。 "真实"后端可以通过一组休息访问。

因此,从最终用户到后端的请求的完整路径如下:
UI - > Nginx - >独角兽 - > Sinatra - > Java / Spring后端 - > Sinatra - >独角兽 - > Nginx - > UI。

最近,我们推出了一项功能,允许用户每5秒钟在浏览器打开时从服务器接收消息。该功能通过

实现
    setInterval(function() {
        if (self.tab_visibility() == 'visible') {
            if (!self.osdDisplayed) {
                self.getOsdMessage();
            }
        }
    }, 5000);

这种方法非常愚蠢,应该重写,以支持从服务器到前端的推送通知。但目前它通过setInterval工作。

所以我们开始遇到的问题是Nginx无法打开与Unicorn的套接字连接。

在Nginx日志文件中出现错误:

  

unix:/tmp/unicorn.sock失败(11:资源暂时不可用)   连接到上游时

最终用户无法打开网站 - 错误503!
但同时在Unicorn日志中我看到很多成功完成的请求,包含200个响应代码。

问题:为什么我能够在根据Unicorn日志成功处理其他用户的同时打开网站?

Nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

/etc/nginx/conf.d/web_client.conf

log_format compression  '$remote_addr - $remote_user [$time_local] '
                        '"$request" $status $bytes_sent '
                        '"$http_referer" "$http_user_agent" "$gzip_ratio"';

limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=1000r/s;

limit_conn_status 405;
limit_req_status 405;

upstream v-web {
  server unix:/tmp/unicorn.sock fail_timeout=0;
}

server {
  listen       8008;
  set_real_ip_from  10.0.0.0/8;
  real_ip_header    X-Forwarded-For;
  real_ip_recursive on;
  root /opt/v/Web-Client-http_auth/public;
  access_log  /var/log/nginx/nginx.access.log compression buffer=32k;
  error_log  /var/log/nginx/nginx.error.log warn;

  limit_conn conn_limit_per_ip 10;
  limit_req zone=req_limit_per_ip burst=1200 nodelay;

  keepalive_timeout  75;
  sendfile        on;
  tcp_nopush     on;
  open_file_cache           max=1000 inactive=20s;
  open_file_cache_valid     30s;
  open_file_cache_min_uses  2;
  open_file_cache_errors    on;
  gzip              on;
  gzip_http_version 1.0;
  gzip_disable      "MSIE [1-6]\.(?!.*SV1)";
  gzip_buffers 4 16k;
  gzip_comp_level 2;
  gzip_min_length 0;
  gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
  gzip_proxied expired no-cache no-store private auth;

  location / {
    try_files $uri @v-web;
    index  index.html index.htm;
  }

  location @v-web {
    proxy_pass http://v-web;
    proxy_redirect     off;
    proxy_set_header   Host             $http_host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    client_max_body_size       10m;
    client_body_buffer_size    128k;

    proxy_connect_timeout      60;
    proxy_send_timeout         60;
    proxy_read_timeout         60;

    proxy_buffer_size          16k;
    proxy_buffers              8 64k;
    proxy_busy_buffers_size    128k;
    proxy_temp_file_write_size 128k;
  }

  error_page  404              /404.html;
  location = /404.html {
    root   /usr/share/nginx/html;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
  error_page   405 /custom.html;
  location = /custom.html {
    root   /usr/share/nginx/html;
  }


  location /nginx_status {
       stub_status on;
       access_log   off;
       allow 127.0.0.1;
       deny all;
  }
}

Sinatra应用程序与Java的交互"真实"通过以下代码片段来处理后端:

connection_pool.rb

######################################################################
# ConnectionPool
######################################################################
class ConnectionPool
  include Singleton

  ######################################################################
  def initialize
    @mutex = Mutex.new
    @condition = ConditionVariable.new

    @connections = []
    @limit = settings.api_pool_limit
  end

  ######################################################################
  def new_connection
    handle = HTTPClient.new
    handle.follow_redirect_count = settings.api_redirect_limit
    handle.connect_timeout = settings.api_timeout
    handle.send_timeout = settings.api_timeout
    handle.receive_timeout = settings.api_timeout
    handle.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
    {
      :handle => handle,
      :locked => false
    }
  end

  ######################################################################
  def get_connection
    @mutex.synchronize do
      connection = get_connection_from_pool
      return connection if connection

      if @connections.length < @limit
        connection = new_connection
        connection[:locked] = true

        @connections.push(connection)

        return connection
      end

      loop do
        @condition.wait(@mutex)

        connection = get_connection_from_pool
        return connection if connection
      end
    end
  end

  ######################################################################
  def get_connection_from_pool
    @connections.each do |connection|
      next if connection[:locked]

      connection[:locked] = true
      return connection
    end
    return nil
  end


  ######################################################################
  def start
    return unless block_given?

    connection = get_connection
    begin
      yield connection[:handle]
    ensure
      @mutex.synchronize do
        connection[:locked] = false
        @condition.signal
      end
    end
  end
end

connection.rb

######################################################################
# Connection
######################################################################
class Connection
  include ::CustomLogger
  include ::Sinatra::Helpers
  attr_reader :session

  ######################################################################
  def initialize(request, session)
    @session = SessionStore.new(session)
    @session.read_credentials_from_cookies(request)
    @cookieManager = WebAgent::CookieManager.new
  rescue Dalli::RingError, Timeout::Error => e
    log_error("Connection initialize: memcache is unreachable #{e.message} \n")
  end

  ######################################################################
  def send(type, relative_url, options = {}, cache)
    locale_for_cache = begin
      options[:headers]["x-vidmind-locale"]
    rescue
      ''
    end
    url = "#{settings.wildfire_base_url}#{relative_url}"
    result = nil
    if cache and !development?
      result = get_from_cache(relative_url, locale_for_cache)
      return result unless result.blank?
    end
    ConnectionPool.instance.start do |http|
      if @session.api_cookie

        cookie = find_session_cookie(@cookieManager.cookies)
        unless cookie
          cookie = WebAgent::Cookie.new
          cookie.name = 'JSESSIONID'
          cookie.url = URI.parse(settings.wildfire_base_url)
          cookie.domain = cookie.url.host
          cookie.path = cookie.url.path
        end
        cookie.value = @session.api_cookie

        @cookieManager.cookies = []
        @cookieManager.add(cookie)
        http.cookie_manager = @cookieManager
        request_cookies = http.cookies.map{ |i| {i.name => i.value} }
      else
        http.cookie_manager = WebAgent::CookieManager.new
        request_cookies = [{ 'JSESSIONID' => '' }]
      end

      headers = {}
      headers.merge!(options[:headers]) if options[:headers]
      headers.merge!({ 'Content-Type' => options[:data_type] }) if options[:data_type]
      headers['X-Forwarded-For'] = settings.forwarded_for if settings.forwarded_for

      time_start = Time.now
      response = http.request(type, url, {
        :query => options[:query],
        :body => options[:data],
        :header => headers,
        :follow_redirect => false
      })
      # httpclient doesn't know Post/Redirect/Get pattern
      if invalid_http_status?(response.status)
        url = response.header['location'][0]

        response = http.request(:get, url, {
          :header => options[:headers],
          :follow_redirect => true
        })
      end

      time_end = Time.now
      json = process_response(response.content)

      log({
        request_cookies: request_cookies,
        response_cookies: http.cookies.map{ |i| {i.name => i.value}},
        time_start: time_start,
        time_end: time_end,
        url: relative_url,
        type: type,
        status: response.status,
        body: json,
        options: options,
        rk: response.content,
        encoding: response.body_encoding,
        fingerprint: headers['x-vidmind-device-id']
      })

      result = {
        success: response_success?(response.status, json),
        code: response.status,
        json: json
      }
      cookie = find_session_cookie(http.cookies)
      @session.api_cookie = cookie.value if cookie
      count_api_requests(time_end, time_start)
    end
    if cache and result[:success] and !development?
      unless result.blank?
        set_cache(relative_url, locale_for_cache, result)
      end
    end
    result
    rescue Exception => e
      log_error " raised for url: #{relative_url} #{e} "
      log_error   " \r\nrequest_cookies #{@session.api_cookie} \r\n"
      raise e if e.is_a?(WildfireError)
  end

  ######################################################################
  def find_session_cookie(cookies)
    cookies.each do |cookie|
      return cookie if 'JSESSIONID'.casecmp(cookie.name) == 0
    end if cookies
    nil
  end

  ######################################################################
  protected

    def process_response(response)
      begin
        response.empty? ? {} : JSON.parse(response)
      rescue
        {'error' => response}
      end
    end

    def response_error?(json)
      res = (json.kind_of?(Hash) && json.has_key?('error')) ||
      (json.kind_of?(Array) && json.first && json.first.has_key?('error')) || json.kind_of?(String)
      res
    end

    def response_success?(status, json)
      !(status < 200 or status >= 300) && !response_error?(json)
    end

    def invalid_http_status?(status)
      if status == 503
        raise WildfireConnectionError
      elsif status == 403
        raise WFSessionExpirationException
      end
      status >= 300 and status < 400
    end
end

0 个答案:

没有答案