Ruby Nginx->;独角兽-资源暂时不可用
我们在生产中使用了Sinatra 应用程序有后端和前端部分。Ruby Nginx->;独角兽-资源暂时不可用,ruby,nginx,sinatra,unicorn,Ruby,Nginx,Sinatra,Unicorn,我们在生产中使用了Sinatra 应用程序有后端和前端部分。 后端和前端紧密耦合,代表整体。 Sinatra应用程序的后端大多是Java/Sping/Mongo上编写的“真实”后端的简单代理。“真正的”后端可以通过一组REST访问 因此,请求从最终用户到后端的完整路径如下: UI->Nginx->Unicorn->Sinatra->Java/Spring-back-end->Sinatra->Unicorn->Nginx->UI 最近,我们引入了一项功能,允许用户在浏览器打开时每隔5秒从服务器接
后端和前端紧密耦合,代表整体。
Sinatra应用程序的后端大多是Java/Sping/Mongo上编写的“真实”后端的简单代理。“真正的”后端可以通过一组REST访问 因此,请求从最终用户到后端的完整路径如下:
UI->Nginx->Unicorn->Sinatra->Java/Spring-back-end->Sinatra->Unicorn->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\u 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“real”后端的交互通过以下代码段进行处理:
连接池.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
######################################################################
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
######################################################################
#连接池
######################################################################
类连接池
包括独生子女
######################################################################
def初始化
@mutex=mutex.new
@condition=ConditionVariable.new
@连接=[]
@limit=settings.api\u pool\u limit
结束
######################################################################
def新连接
handle=HTTPClient.new
handle.follow\u redirect\u count=settings.api\u redirect\u limit
handle.connect\u timeout=settings.api\u timeout
handle.send\u timeout=settings.api\u timeout
handle.receive\u timeout=settings.api\u timeout
handle.ssl\u config.verify\u mode=OpenSSL::ssl::verify\u NONE
{
:handle=>handle,
:locked=>false
}
结束
######################################################################
def get_连接
@mutex.do
连接=从\u池中获取\u连接\u
如果是连接,则返回连接
如果@connections.length<@限制
连接=新连接
连接[:锁定]=真
@连接。推(连接)
回路连接
结束
环道
@条件。等待(@mutex)
连接=从\u池中获取\u连接\u
如果是连接,则返回连接
结束
结束
结束
######################################################################
def从_池获取_连接_
@连接。每个do |连接|
下一个if连接[:锁定]
连接[:锁定]=真
回路连接
结束
归零
结束
######################################################################
def启动
返回,除非给出block_?
连接=获取连接
开始
屈服连接[:手柄]
确保
@mutex.do
连接[:锁定]=错误
@条件信号
结束
结束
结束
结束
连接.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
######################################################################
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
######################################################################
#联系
######################################################################
类连接
include::CustomLogger
include::Sinatra::Helpers
属性读取器:会话
######################################################################
def初始化(请求、会话)
@session=SessionStore.new(会话)
@会话。从\u cookies读取\u凭据\u(请求)
@cookieManager=WebAgent::cookieManager.new
rescue Dalli::RingError,Timeout::Error=>e
日志错误(“连接初始化:memcache不可访问35;{e.message}\n”)
结束
######################################################################
def send(类型、相对url、选项={}、缓存)
\u缓存的区域设置\u=开始
选项[:标题][“x-vidmind-locale”]
营救
''
结束
url=“#{settings.wildfire_base_url}#{relative_url}”
结果=零
如果缓存和!发展?
结果=从\u缓存获取\u(相对\u url,用于\u缓存的区域设置\u)
返回结果,除非结果为空?
结束
ConnectionPool.instance.start do | http|
if@session.api\u cookie
cookie=查找会话cookie(@cookieManager.cookies)
除非是饼干
cookie=WebAgent::cookie.new
cookie.name='JSESSIONID'
cookie.url=URI.parse(settings.wildfire\u base\u url)
cookie.domain=cookie.url.host
cookie.path=cookie.url.path
结束
cookie.value=@session.api\u cookie
@cookieManager.cookies=[]
@cookieManager.add(cookie)
http.cookie_manager=@cookieManager
request|cookies=http.cookies.map{| i |{i.name=>i.value}
其他的
http.cookie\u manager=WebAgent::CookieManager.new
请求_cookies=[{'JSESSIONID'=>''}]
结束
标题={}
头,合并!(选项[:标题])如果选项[:标题]
头,合并!({'Content-Type'=>options[:data\u-Type]})如果选项[:data\u-Type]
标题['X-Forwarded-For']=settings.Forwarded\u For if settings.Forwarded\u For
time\u start=time.now
response=http.request(类型、url、{
:query=>options[:query],
:body=>options[:data],
:头