- Posted in: Networks
[lang_en]
Two weeks ago we have started new version of one of our primary web projects and have started very massive advertisement campaign to promote this web site. As the result of that advertisements, our outgoing traffic has been increased to 200-250Mbit/s from only one server! In this article I will describe, how to build stable and efficient web site with two-layer architecture (with frontend + backend web servers) or how to modify your current server configuration to get additional resources to handle more requests.
[/lang_en]
[lang_ru]
Две недели назад мы запустили новую версию одного из наших оcновных веб-проектов и начали массивную рекламную поддержку этого сайта. В результате рекламы, исходящий трафик только с одного сервера достиг 200-250Mbit/s! В данной статье я опишу, как построить стабильный и эффективный веб-сайт с двухуровневой архитектурой обработки запросов (с двумя веб-серверами: frontend и backend) или как модифицировать ваш текущий сервер, чтобы получить дополнительные ресурсы для обработки большего количества запросов.
[/lang_ru]
[lang_en]
First of all, let me describe general structure of web-server and how it handles clients requests:
- Client initiates request to your server.
- His browser connects to your server.
- Your server (as for Apache) creates new thread/process to handle request.
- If client requested dynamic content, web server spawns CGI process or executes dynamic content handling module (i.e. mod_php) and waits while request will be processed. When it receives result web-page, it sends it to client.
- If client asked for some static file, web server sends this file to client
- Client’s browser receives answer, closes connection to web server and displays content.
As you can see, when there are many requests coming to your server, your server needs to create many parallel threads/processes and keep them running while client will close connection. If client has slow connection, web server process will wait too long and resource consumption will increase very fast.
What we can do in such situation? Simple solution is to buy more memory and more CPUs for your server and wait while web server load will crash your server. But there is more efficient solution! You can simply put some small piece of software (nginx, for example) behind your big web server and let it handle all requests to static content and to pass all dynamic requests to primary web-server. With this solution your big server will spawn additional threads/processes only for static pages and it will return answers to small frontend very fast and then can free resources to use them to handle another queries. Small frontend can wait very long time while client will receive his content and will close connection – backend server will not consume resources for such long time!
Here you can see simple diagram of proposed web server configuration:
As additional benefit from such configuration you can get very useful feature of managed downloads that will be described below.
If your server contains some static resources, which can be downloaded not by all users (content provider can provide mp3 files only to users with positive balance or some site can provide downloads only to logged-in users), in generic configuration you need to create some script to handle this downloads and to create some ugly links like http://some.service.com/down.php?file=xxx.mp3 and additionally your users will not be able to resume downloads (except such cases when your script so complex, that it handles Ranges HTTP-header)…
In configuration with nginx frontend, you can create simple URL-rewriting rule, that will pass all requests to pretty URLs like http://your.cool-service.com/files/cool.mp3 to some simple script /down.php automatically and, if this script has returned X-Accel-Redirect header, will send requested file to user automatically with Ranges header support and when user will download his cool content, your backend server can handle other requests. Your users even will not know that your script controls their downloads. Simple diagram for described algorithm is following:
Let me bring your attention to interesting fact: If you only accelerate your site with described technique and do not want to create download control system, you do not need to modify any of your scripts on your backend server! They will work as in original configuration!
So, the last thing you need to boost your web server with nginx reverse proxying technique is following configuration file snipet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | server { listen 80; server_name some-server.com www.server-name.com; access_log logs/host.access.log main; # Main location location / { proxy_pass http://127.0.0.1:8080/; proxy_redirect off; proxy_set_header Host $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 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; } # Static files location location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ { root /spool/www/members_ng; } } |
Full version of sample config file you can get here.
Notice: If your backend scripts are using user IP addresses for some purposes, you will need to install mod_rpaf module to use X-Real-IP header provided by nginx instead of real user’s IP address.
That is all! Now you can simply install nginx on your server, configure it and your server will be able to handle more traffic with the less resources that it uses now! Everything will be done transparently for your currently written scripts and if you want, you will be able to provide download handling with simple trick, that I will describe in my next post 😉
If you have some questions, do not hesitate to ask them here in comments – I will try to answer all of them. If you liked this article, you can support author by taking a look at advertisements on this page or simply vote for it on digg.com.
[/lang_en]
[lang_ru]
Для начала, опишу типичный процесс обслуживания запроса к веб-серверу и структуру самого сервера:
- Клиент инициирует запрос к серверу.
- Его браузер устанавливает соединение с сервером.
- Ваш сервер (например, Apache) создает новый поток/процесс для обработки запроса.
- Если клиент запросил динамический контент (например, отправил запрос к php-скрипту), веб-сервер создает отдельный CGI-процесс или запускает модуль обработки скриптов (например, mod_php) и ждет, пока запрос будет обработан. Как только он получает результирующую web-страницу, то она отправляется клиенту.
- Если же клиент запросил статический файл, то сервер просто отправляет этот файл клиенту.
- Браузер клиента получает ответ, закрывает соединение с сервером и отображает “ответ”.
Как видите, если к серверу приходит очень много запросов, он должен создавать много параллельных потоков/процессов и держать их в памяти, пока клиент не закроет соединение. Если соединение у клиента не быстрое, то серверные процессы будут висеть в памяти достаточно долго и используемые ими ресурсы будут увеличиваться очень быстро.
Как же решить данную проблему? Простым решением может быть бесконечное увеличение объемов оперативной памяти на сервере и покупка дополнительных или более мощных процессоров в ожидании момента, когда сервер умрет под нагрузкой… Но существует более эффективное решение! Вы можете просто поместить небольшую программку (nginx, например) перед Вашим большим веб-сервером и дать ей возможность обслуживать запросы к статическим файлам, а запросы к динамике проксировать к главному серверу. При таком решении Ваш большой сервер не будет создавать дополнительных процессов для обработки статических страниц и файлов и будет отдавать результаты обработки динамических запросов маленькому frontend-серверу очень быстро, что позволит ему освободить ресурсы для использования в обработке других запросов. Маленький frontend же может ждать сколь угодно долго, пока клиент заберет свой “ответ” и закроет соединение, а backend не будет тратить ресурсы для этого!
Вот примерная диаграма предложенной конфигурации веб-сервера:
В дополнение к описанному, Вы получите еще очень удобную возможность так называемых контролируемых закачек, которая будет описана ниже.
Если Ваш сервер содержит какие-то статические ресурсы, которые можно скачивать только определенной части аудитории сайта (контент-провайдеры могут предоставлять возможность скачивания mp3-файлов только пользователям с положительным балансом; некоторые сайты дают скачивать файлы только зарегистрированным пользователям и т.п.), в типичном случае вам необходимо создать некий скрипт для обработки запросов на скачивание и создать набор жутких ссылок вида http://some.service.com/down.php?file=xxx.mp3… В дополнение к этому Ваши пользователи не будут иметь возможность докачки (исключая те случаи, когда Ваш скрипт настолько сложен, что понимает заголовок Ranges в HTTP-запросах)…
В конфигурации с использованием nginx как frontend-сервера, Вы имеете возможность создать простое правило для переписывания ссылок в запросах так, чтобы все красивые ссылки типа http://your.cool-service.com/files/cool.mp3 автоматически направлялись на некоторый скрипт /down.php и, если он вернет заголовок X-Accel-Redirect, файл автоматически отдавался клиенту с поддержкой Ranges и всех остальных прелестей раздачи статического контента с frontend-сервера. Backend-сервер в это время сможет обрабатывать другие запросы. Ваши пользователи могут даже не знать о том, что их закачки контролируются Вами. Примерная диаграмма описанного алгоритма приведена ниже:
Позвольте обратить Ваше внимание на важный факт: Если Вам нужно только увеличение производительности работы сайта с помощью описанной здесь техники, и вы не хотите использовать систему контроля за скачиванием, то Вам не нужно ничено менять в скриптах на Вашем сервере! Они будут работать так же, как и раньше!
Итак, последнее, чем я могу помочь Вам в тяжелом труде оптимизации использования ресурсов Вашего сервера, – это пример конфигурации для nginx, которая может быть использована Вами в качестве базовой при конфигурации Вашего сервера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | server { listen 80; server_name some-server.com www.server-name.com; access_log logs/host.access.log main; # Main location location / { proxy_pass http://127.0.0.1:8080/; proxy_redirect off; proxy_set_header Host $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 90; proxy_send_timeout 90; proxy_read_timeout 90; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; } # Static files location location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ { root /spool/www/members_ng; } } |
Полная версия конфигурационного файла лежит здесь.
Замечание: Если скрипты на Вашем backend-сервере используют IP-адреса клиентов в каких-то целях, то Вам необходимо установить на сервер модуль mod_rpaf module, чтобы он использовал передаваемый nginx заголовок X-Real-IP в качестве основного адреса пользователя.
Вот и все! Теперь Вы можете установить себе на сервер nginx, отконфигурировать его и получить возможность обслуживать большее количество клиентов при использовании меньшего количества ресурсов! Все будет работать абсолютно прозрачно для уже написанных скриптов и, если хотите, Вы сможете организивать контролируемое скачивание при помощи метода, который я опишу в одном из следующих постов.
Если у Вас есть какие-либо вопросы, задавайте их в комментариях не откладывая – я попробую ответить на них. Если эта статья понравилась Вам, можете обратить внимание на рекламные объявления на этой стратице или просто проголосуйте за нее на Digg.com.