Как стать автором
Обновить

36 млн запросов в час, 10000+ постоянно работающих клиентов, на одном сервере, nginx+mysql

Время на прочтение5 мин
Количество просмотров115K
Сложилась ситуация, что участвую в проекте, который работает с достаточно большой нагрузкой. Как уже написал — 36 млн запросов в час. Я много чего прочитал и перепробовал за последний месяц, настраивая сервер; хотелось бы просто сжато и компактно выдать тезисно то, что работает хорошо в такой конфигурации.

Первое, что я заметил — множество советов как все настроить под большую нагрузку. Читайте их внимательно, обычно в тексте найдете, что речь про «высокую нагрузку» в 15-20 тысяч клиентов в сутки. У нас клиентов примерно миллион, активных, ежедневных.

У нас нет денег и мы все делаем за свой счет, поэтому экономим. Итог — весь миллион клиентов обслуживается на одном сервере, вот на таком — EX-60 на hetzner.

Мы случайно сделали себе аналог DDoS через своих клиентов и в результате настроек, когда было по 4000 php процессов, загрузка ОС так же под 4000, я успел перепробовать множество конфигураций и найти наиболее работающие. С ошибкой в софте справились, теперь эти 10-12 тысяч запросов в секунду обрабатываются с загрузкой load average: 3,92, 3,22, 2,85. Не единичка, конечно, но для одного сервера считаю хорошим результатом.

Операционка — CentOS 7.1, 64 бита. Минимальная инсталляция, плюс iptables, nginx, php-fpm, mysql. Ядро 4-й версии, из kernel-ml.

Тюнинг настроек ядра под большой напор tcp коннектов:

/etc/sysctl.conf
fs.file-max = 1000000
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.tcp_max_orphans = 65536
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_mem = 50576 64768 98152
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_orphan_retries = 0
net.ipv4.tcp_syncookies = 0
net.ipv4.netfilter.ip_conntrack_max = 1048576
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_congestion_control = htcp
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.route.flush=1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.lo.accept_source_route = 0
net.ipv4.conf.eth0.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.ip_forward = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 1000
net.core.rmem_default=65536
net.core.wmem_default=65536
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

Тюнинг лимитов по файлам, так как ни каких юзеров на сервере нет, то особо не заморачиваемся:

/etc/security/limits.conf
* soft nproc 65535
* hard nproc 65535
* soft nofile 100000
* hard nofile 100000
root soft nofile unlimited
root hard nofile unlimited

Монстры знают, но я давно не администрировал и был не в курсе, что * не работает на root, и его надо отдельно тюнинговать.

На этом с ядром всё.

Настройки мускула. Установлена percona-56.

Выбор был сделан в итоге на InnoDB, пробовали TokuDb, но на больших объемах постоянных инсертов, а их у нас 95% из 36 млн в час. InnoDB ведет себя лучше, тесты самой перконы говорят о том же.

Настройки mysql:

/etc/my.cnf
[mysql]
port = 3306
socket = /var/lib/mysql/mysql.sock
[mysqld]
user = mysql
default-storage-engine = InnoDB
socket = /var/lib/mysql/mysql.sock
pid-file = /var/lib/mysql/mysql.pid
key-buffer-size = 32M
myisam-recover = FORCE,BACKUP
max-allowed-packet = 16M
max-connect-errors = 1000000
skip-name-resolve
datadir = /var/lib/mysql/
tmp-table-size = 32M
max-heap-table-size = 32M
query-cache-type = 0
query-cache-size = 0
max-connections = 15000
thread-cache-size = 5000
open-files-limit = 150000
table-definition-cache = 1024
table-open-cache = 50000
innodb-flush-method = O_DIRECT
innodb-log-files-in-group = 2
innodb-log-file-size = 2G
innodb-file-per-table = 1
innodb-buffer-pool-size = 10G
innodb_flush_log_at_trx_commit = 0
log-error = /var/log/mysql/mysql-error.log
log-queries-not-using-indexes = 0
slow-query-log = 1
slow-query-log-file = /var/log/mysql/mysql-slow.log

Обязательно отключаем при такой нагрузке query-cache. Он будет реально тормозить всю систему. Впрочем, поиграйтесь, возможно в вашем случае нет, но во многих тестах и текстах встречал этот момент, проверил у себя — так и есть, с отключенным работает быстрее.

skip-name-resolve тоже дает хороший прирост.

Дополнительные настройки по отношению к стандартным для nginx:

fastcgi_params
fastcgi_param REDIRECT_STATUS 200;
fastcgi_buffer_size 4K;
fastcgi_buffers 64 4k;

nginx тюним под наши нужды:

nginx.conf
user nginx;
worker_processes 8;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

worker_rlimit_nofile 150000;

events {
worker_connections 8000;
multi_accept on;
use epoll;
}

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

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;

gzip off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
reset_timedout_connection on;
server_tokens off;
client_body_buffer_size 128k;

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

}

Ядер 8, поэтому и worker-процессов 8, по 8000 на брата, все-равно больше 64к не обслужить за раз. Будет небольшая очередь, если будет больше одновременных коннектов.

В сайте с php-fpm общаемся через сокеты:
/etc/nginx/conf.d/site.conf
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_send_timeout 180s;
fastcgi_read_timeout 180s;

Основное по конфигурации php-fpm:

/etc/php-fpm.d/www.conf
listen = /var/run/php-fpm/php-fpm.sock
pm = ondemand
pm.max_children = 4000
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_requests = 0

ondemand мало где описан, но он лучше чем dynamic под большой нагрузкой. А static — это, конечно, убийство для сервера, не понравилось сильно.

ondemand начинает с 5, при необходимости наращивается, но в отличии от dynamic с уменьшением нагрузки не убивает процессы, чтобы потом опять наращивать, а просто на пике фиксирует значение и переводит ненужный в режим ожидания. И если вдруг нагрузка опять растет — процессы уже готовы, никого не надо запускать с нуля.

pm.max_requests = 0 помогает боротся с утечками памяти, в стороннем софте.

Собственно, так мы и обслуживаем 36 млн в час, из которых 95 процентов — передача к нам данных и запись их в БД. На 2.8 миллиарда запросов у нас сейчас от 10 до 16 slow_query, каждый не больше 10 секунд, причем все они — селекты с джойнами по многим полям и таблицам. Остальные запросы отрабатывают моментально.

Вместо php-fpm компилировал и использовал hhvm одно время, действительно работает шикарно, значительно быстрее php-fpm, но есть беда — каждые 30-40 минут падает, причем наглухо.

В git разработчикам написал, пока ничем не смогли помочь, причин не знают. В итоге сидим на php-fpm, версия 5.6.

Весь софт ставится через yum, ни каких билдов с сорцов с мегатюнингом не используется.

Думаю, кому-то будет полезна эта информация о настройках вся в одном месте.
Теги:
Хабы:
Всего голосов 111: ↑96 и ↓15+81
Комментарии102

Публикации

Истории

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань