Предположим у вас есть данные, которые вы хотите кэшировать и отдавать не используя тяжелые языки, как php, при этом проверяя, что пользователь аутентифицирован и имеет право на доступ к данным. Сегодня я расскажу как, используя связку nginx lua redis, выполнить эту задачу, снять нагрузку с сервера и увеличить скорость отдачи информации сервером в десятки раз.
Для начала необходимо собрать nginx с модулем nginx_lua_module.
Скачаем luaJit и соберем его
make && sudo make install
Для сборки nginx с nginx devel kit необходим http_rewrite_module, а тот с свою очередь требует библиотеку pcre. Поэтому установим ее
sudo apt-get update sudo apt-get install libpcre3 libpcre3-dev
Скачаем зависимые модули и сам nginx
nginx devel kit
nginx lua module
nginx
Сконфигурируем и установим nginx
export LUAJIT_LIB=/usr/local/lib // путь к библиотеке lua export LUAJIT_INC=/usr/local/include/luajit-2.1 //путь к luaJit ./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-ld-opt="-Wl,-rpath,/path/to/lua/lib" // путь к библиотеке Lua --add-module=/path/to/ngx_devel_kit //путь к nginx devel kit --add-module=/path/to/lua-nginx-module // путь к nginx lua module --without-http_gzip_module make -j2 sudo make install
Скачаем библиотеку lua для работы с redis lua redis lib и скопируем ее в папку библиотек lua командой
sudo make install
Подключим библиотеку lua redis в конфигурацию nginx
http { ... lua_package_path lua_package_path "/path/to/lib/lua/resty/redis.lua;;"; // путь к библиотеке lua redis ... }
Все. Теперь можно писать скрипы на lua, которые будут исполнятся nginx
Чтобы бысто и эффективно отдавать кэшированные данные, мы положим самые часто используемые из них в redis сразу при прогреве кэша, а меннее используемые будем класть по запросу. Отдавать данные будем с помошью lua на стороне nginx. В этой связке не будет участвовать php, что в разые ускорит выдачу данных и будет занимать намного меньше памяти у сервера.
Для этого напишем Lua скрипт
local string = ngx.var.arg_string -- получим параметр из GET запроса if string == nil then ngx.exec("/") -- если параметра нет, то сделаем редирект end local path = "/?string=" .. string local redis = require "resty.redis" -- подключим библиотеку по работе с redis local red = redis:new() red:set_timeout(1000) -- 1 sec local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.exec(path) -- если нельзя подключиться к redis, то сделаем редирект end res, err = red:get("search:" .. string); -- получим данные из redis if res == ngx.null then ngx.exec(path) -- если данных нет, то сделаем редирект else ngx.header.content_type = 'application/json' ngx.say(res) -- если данные есть, то отдадим их end
Подключим этот файл в nginx.conf и перезагрузим nginx
location /search-by-string { content_by_lua_file lua/search.lua; }
Теперь при запросе /search-by-string?string=smth lua подключится к redis и попробует найти данные по ключу search:smth. Если данных не окажется, то запрос обработает php. Но если данные уже закэшированы и лежат в redis, то они будут сразу же отданы пользователю.
Но что, если нам нужно отдавать данные, только если пользователь аутентифицирован и при этом имеет определенную роль?
В таком случае можно хранить сессию в redis и перед тем, как отдавать контент, проверять роль пользователя по данным сессии.
Т.к. я работаю с фрэймворком Symfony2, то для него был написан небольшой бандл nginx-session-handler, с помощью которого можно хранить сессию в redis именно так, как нам удобно.
В redis данные будут хранится в качестве хэш значения:
phpsession — префикс ключа для сессии
php-session — сама сессия php
user-role — роль пользователя.
Теперь нужно написать lua скрипт для обработки этих данных:
local redis = require "resty.redis" -- подключаем библиотеку по работе с redis local red = redis:new() red:set_timeout(1000) -- 1 sec local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) -- если не удалось подключиться, end -- то возвращаем ответ со статусом 500 local phpsession = ngx.var.cookie_PHPSESSID -- получаем id сессии из cookie пользователя local ROLE_ADMIN = "ROLE_ADMIN" -- роль, которой нужно предоставить доступ if phpsession == ngx.null then ngx.exit(ngx.HTTP_FORBIDDEN) -- если в cookie нет сессии(пользователь не аутентифицированн), end -- то возвращаем ответ со статусом 403 local res, err = red:hget("phpsession:" .. phpsession, "user-role") -- получаем роль пользователя -- из redis по id сессии if res == ngx.null or res ~= ROLE_ADMIN then ngx.exit(ngx.HTTP_FORBIDDEN) -- если сессии нет(закончилось время жизни сессии) или end -- у пользователя не та роль, что нам нужна, -- то возвращаем ответ со статусом 403
Мы достаем id сессии пользователя из cookie, пытаемся получить роль пользователя по его id сессии из redis по запросу HGET phpsession:id user-role. Если у пользователя истекло время жизни сессии, он не аутенитифицированн или у него не роль ROLE_ADMIN, то сервер вернет код 403.
Дописываем этот скрипт обработки сессии перед нашим скриптом получения данных и теперь данные могут получить только аутентифицированные пользователи с ролью ROLE_ADMIN.
На деле скрипт обработки сессии будет необходим для нескольких location nginx. Чтобы не писать один и тот же код в разных местах, мы будум подключать этот файл там, где нам нужно.
Для начала немного перепишем наш скрипт обработки сессии.
local _M = {} --добавили переменную function _M.handle() -- записали в нее функцию и поместили в нее весь предыдущий код local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 sec local ok = red:connect("127.0.0.1", 6379) if not ok then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end local phpsession = ngx.var.cookie_PHPSESSID local ROLE_ADMIN = "ROLE_ADMIN" if phpsession == ngx.null then ngx.exit(ngx.HTTP_FORBIDDEN) end local res = red:hget("phpsession:" .. phpsession, "user-role") if res == ngx.null or res ~= ROLE_ADMIN then ngx.exit(ngx.HTTP_FORBIDDEN) end end return _M -- вернули переменную с функцией
Теперь необходимо собрать session.o файл из session.lua с помощью компилятора luaJit и собрать nginx c этим файлом.
Соберем session.o файл, выполнив команду компилятора lua
/path/to/luajit/bin/luajit -bg session.lua session.o
Добавим в конфигурацию для сборки nginx строку
--with-ld-opt="/path/to/session.o"
и соберем nginx(как собрать nginx описано выше)
После этого можно подключать файл в любой lua скрипт и вызывать функцию handle() для обработки сессии пользователя
local session = require "session" session.handle()
В конце небольшой тест для сравнения.
Memory: 7.9 GiB
Запрос, который с помощью php достает данные из redis
Concurrency Level: 100
Time taken for tests: 3.869 seconds
Complete requests: 100
Failed requests: 0
Requests per second: 25.85 [#/sec] (mean)
Time per request: 3868.776 [ms] (mean)
Time per request: 38.688 [ms] (mean, across all concurrent requests)
Transfer rate: 6.66 [Kbytes/sec] received
Connection Times (ms)
min mean[±sd] median max
Connect: 1 3 1.1 3 5
Processing: 155 2116 1053.7 2191 3863
Waiting: 155 2116 1053.7 2191 3863
Total: 160 2119 1052.6 2194 3864
Percentage of the requests served within a certain time (ms)
50% 2194
66% 2697
75% 3015
80% 3159
90% 3504
95% 3684
98% 3861
99% 3864
100% 3864 (longest request)
Concurrency Level: 100
Time taken for tests: 0.022 seconds
Complete requests: 100
Failed requests: 0
Requests per second: 4549.59 [#/sec] (mean)
Time per request: 21.980 [ms] (mean)
Time per request: 0.220 [ms] (mean, across all concurrent requests)
Transfer rate: 688.66 [Kbytes/sec] received
Connection Times (ms)
min mean[±sd] median max
Connect: 2 4 0.9 4 6
Processing: 3 13 1.6 13 14
Waiting: 3 13 1.6 13 14
Total: 9 17 1.3 18 18
Percentage of the requests served within a certain time (ms)
50% 18
66% 18
75% 18
80% 18
90% 18
95% 18
98% 18
99% 18
100% 18 (longest request)
По этим запросам выходит разница «количества запросов в секунду» в 175 раз.
Надеюсь моя статья была полезна. Если у вас есть пожелания, замечания или вы знаете как сделать лучше — пишите.
ссылка на оригинал статьи http://habrahabr.ru/post/270463/
Добавить комментарий