我们在生产中有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