Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Nginx + Lua

12,099 views

Published on

Published in: Technology

Nginx + Lua

  1. 1. NGINX + LUA.When you need it to be fast.
  2. 2. • Something different • Not to replace your PHP app • But a valuable add-on for specific use cases • Another tool for your development toolbox THE NEXT HOUR.
  3. 3. • Victor Welling • ~9 years as a Developer and Architect at Coolblue • Then I took an error to the knee, now a Developer Evangelist ME.
  4. 4. • One of the largest e-commerce companies in the Benelux • 1,1k employees, 332 webshops, 7 physical stores and 2 warehouses • All mission-critical software developed in-house • 100% Growth ever 20 months, so scalability matters COOLBLUE.
  5. 5. • Built to address the C10k challenge • Web server, reverse proxy, load balancer and HTTP cache • High concurrency and low, predictable memory consumption • Uses asynchronous, event-driven architecture NGINX.
  6. 6. • Lightweight scripting language, designed to be embedded • Fast execution and low memory consumption • Gentle learning curve • Been around since ’93, currently at version 5.2 LUA.
  7. 7. -- FizzBuzz in Lua for i = 1, 100 do local text = i if i % 3 == 0 and i % 5 == 0 then text = "FizzBuzz" elseif i % 3 == 0 then text = "Fizz" elseif i % 5 == 0 then text = "Buzz" end print(text) end fizzbuzz.lua - 1/1
  8. 8. • PHP-FPM provides pool of workers • Shared nothing architecture • Synchronous code, blocking I/O PHP-FPM.
  9. 9. • Ngx_lua module embeds Lua VM in nginx event loop • Allows for shared data between requests • Synchronous code, but non-blocking I/O NGINX + LUA.
  10. 10. PERFORMANCE.
  11. 11. • There’s lies, damned lies and benchmarks • This is not scientific • Your mileage may vary SIDENOTE.
  12. 12. • “Hello World” in Lua and PHP • VirtualBox with 2 CPU's • PHP-FPM (PHP 5.6.4) with static pool of 2 workers • OPcache enabled • Nginx 1.7.9 with two workers HELLO WORLD.
  13. 13. # ab -n 250 -c 3 -k http://127.0.0.1/hello.php [...] Document Path: /hello.php Document Length: 12 bytes Concurrency Level: 3 Time taken for tests: 0.225 seconds Complete requests: 250 Failed requests: 0 Write errors: 0 Keep-Alive requests: 250 Total transferred: 41750 bytes HTML transferred: 3000 bytes Requests per second: 1112.97 [#/sec] (mean) Time per request: 2.695 [ms] (mean) Time per request: 0.898 [ms] (mean, across all concurrent requests) Transfer rate: 181.51 [Kbytes/sec] received [...] shell - 1/1
  14. 14. # ab -n 250 -c 3 -k http://127.0.0.1/hello.lua [...] Document Path: /hello.lua Document Length: 12 bytes Concurrency Level: 3 Time taken for tests: 0.056 seconds Complete requests: 250 Failed requests: 0 Write errors: 0 Keep-Alive requests: 250 Total transferred: 41750 bytes HTML transferred: 3000 bytes Requests per second: 4491.56 [#/sec] (mean) Time per request: 0.668 [ms] (mean) Time per request: 0.223 [ms] (mean, across all concurrent requests) Transfer rate: 732.51 [Kbytes/sec] received [...] shell - 1/1
  15. 15. • Lua outperforms PHP 4 to 1 in req/sec • Not a real-world scenario • Shows impact of processing overhead RESULTS.
  16. 16. • Same set-up • Now with blocking I/O • Redis BLPOP with 1 second timeout BLOCKING I/O.
  17. 17. # ab -n 10 -c 10 -k http://127.0.0.1/blocking-io.php [...] Document Path: /blocking-io.php Document Length: 12 bytes Concurrency Level: 10 Time taken for tests: 9.968 seconds Complete requests: 10 Failed requests: 0 Write errors: 0 Keep-Alive requests: 10 Total transferred: 1670 bytes HTML transferred: 120 bytes Requests per second: 1.00 [#/sec] (mean) Time per request: 9967.910 [ms] (mean) Time per request: 996.791 [ms] (mean, across all concurrent requests) Transfer rate: 0.16 [Kbytes/sec] received [...] shell - 1/1
  18. 18. # ab -n 10 -c 10 -k http://127.0.0.1/blocking-io.lua [...] Document Path: /blocking-io.lua Document Length: 12 bytes Concurrency Level: 10 Time taken for tests: 1.142 seconds Complete requests: 10 Failed requests: 0 Write errors: 0 Keep-Alive requests: 10 Total transferred: 1670 bytes HTML transferred: 120 bytes Requests per second: 8.76 [#/sec] (mean) Time per request: 1142.199 [ms] (mean) Time per request: 114.220 [ms] (mean, across all concurrent requests) Transfer rate: 1.43 [Kbytes/sec] received [...] shell - 1/1
  19. 19. • Clearly shows concurrency restriction by worker pool size • Lua benefits from non-blocking I/O • Handles all requests (almost) simultaneously RESULTS.
  20. 20. # ab -n 10 -c 10 -k http://127.0.0.1/blocking-io.php [...] Document Path: /blocking-io.php Document Length: 12 bytes Concurrency Level: 10 Time taken for tests: 1.231 seconds Complete requests: 10 Failed requests: 0 Write errors: 0 Keep-Alive requests: 10 Total transferred: 1670 bytes HTML transferred: 120 bytes Requests per second: 8.13 [#/sec] (mean) Time per request: 1230.761 [ms] (mean) Time per request: 123.076 [ms] (mean, across all concurrent requests) Transfer rate: 1.33 [Kbytes/sec] received [...] shell - 1/1
  21. 21. • Larger worker pool removes concurrency restriction • Req/sec difference becomes negligible • Eventually you’ll still run out of workers RESULTS.
  22. 22. GETTING STARTED.
  23. 23. • Use OpenResty to get started right away • Or do it yourself • nginx • ngx_devel_kit • ngx_lua • LuaJIT • Lua CJSON BUILD IT.
  24. 24. • Rewrite • Access • Content • Log • …and a few others PROCESS PHASES.
  25. 25. PROCESS PHASES. Access Content Log Process Request Respond FastCGI Nginx PHP-FPM
  26. 26. • Nginx environment accessible through API • Control request flow • Modify request and response messages • Perform sub-requests • Create log entries NGINX API.
  27. 27. • Redis • Memcached • MySQL • WebSockets MODULES.
  28. 28. • Foreign Function Interface • Call C functions and use C data structures in pure Lua • With great power comes great responsibility LUAJIT FFI.
  29. 29. • Shared key-value store, e.g. Redis • Headers or environment variables • Serialise your session data through json_encode() instead of serialize() PHP + LUA.
  30. 30. PUT IT TO USE.
  31. 31. • Application meets infrastructure • I/O intensive tasks • Highly cacheable operations USE CASES.
  32. 32. • Front controller, request routing • URL rewriting • Exposing back-end API’s to AJAX calls • WebSockets APPLICATION.
  33. 33. • Intelligent load balancing • A/B testing, canary releases • Logging and metrics • Abuse protection • SSI/ESI alternative INFRASTRUCTURE.
  34. 34. CODE.
  35. 35. ABUSE PROTECTION.
  36. 36. http { lua_package_path '/usr/lib/lua/?.lua;;'; lua_shared_dict 'blacklist' 1m; server { access_by_lua_file 'blacklist.lua'; } } nginx.conf - 1/1
  37. 37. local blacklist = ngx.shared.blacklist local last_modified = blacklist:get("last_modified") if not last_modified or last_modified < (ngx.now() - 10) then local redis_client = require "resty.redis" local redis = redis_client:new() redis:set_timeout(100) local ok = redis:connect("127.0.0.1", 6379) if ok then local updated_blacklist = redis:smembers("blacklist") if updated_blacklist then blacklist:flush_all() for _, ip in ipairs(updated_blacklist) do blacklist:set(ip, true) end [...] blacklist.lua - 1/2
  38. 38. [...] blacklist:set("last_modified", ngx.now()) end end end if blacklist:get(ngx.var.remote_addr) then ngx.log(ngx.WARN, "Blacklist match, blocked access to " .. ngx.var.remote_addr) return ngx.exit(ngx.HTTP_FORBIDDEN) end blacklist.lua - 2/2
  39. 39. # curl -sI 127.0.0.1 | grep ^HTTP HTTP/1.1 200 OK # redis-cli sadd blacklist 127.0.0.1 (integer) 1 # curl -sI 127.0.0.1 | grep ^HTTP HTTP/1.1 403 Forbidden # redis-cli del blacklist (integer) 1 # curl -sI 127.0.0.1 | grep ^HTTP HTTP/1.1 200 OK # grep "Blacklist match" error.log 2015/01/01 00:00:00 [...] Blacklist match, blocked access to 127.0.0.1 [...] shell - 1/1
  40. 40. <?php if ($userIsMisbehaving) { $redis = new Redis(); $redis->connect("127.0.0.1", 6379); $redis->sadd("blacklist", $_SERVER["REMOTE_ADDR"]); } banhammer.php - 1/1
  41. 41. • Prevent abusive clients from reaching your PHP worker pool • Filter out malformed or harmful requests BENEFITS.
  42. 42. LOGGING & METRICS.
  43. 43. http { log_format 'json' $log_line; server { set $log_line ''; access_log 'access.json' 'json'; log_by_lua_file 'logger.lua'; } } nginx.conf - 1/1
  44. 44. local cjson = require "cjson" local json = cjson.new() local log_line = {} log_line.request = {} log_line.request.timestamp = ngx.var.time_iso8601 log_line.request.remoteAddress = ngx.var.remote_addr log_line.request.protocol = ngx.var.server_protocol log_line.request.method = ngx.req.get_method() log_line.request.url = {} log_line.request.url.host = ngx.var.host log_line.request.url.uri = ngx.var.request_uri log_line.request.url.query = ngx.req.get_uri_args() log_line.request.headers = ngx.req.get_headers() log_line.request.cookies = {} if log_line.request.headers.cookie then local cookies = log_line.request.headers.cookie [...] logger.lua - 1/2
  45. 45. [...] if type(cookies) ~= "table" then cookies = {cookies} end for _, cookie in ipairs(cookies) do for name, value in string.gmatch(cookie, "([%w_-]+)=([^;]+)") do log_line.request.cookies[name] = value end end end log_line.response = {} log_line.response.status = ngx.status log_line.response.bytesSent = tonumber(ngx.var.bytes_sent) log_line.response.processingTime = tonumber(ngx.var.request_time) log_line.response.headers = ngx.resp.get_headers() ngx.var.log_line = json.encode(log_line) logger.lua - 2/2
  46. 46. { "request": { "timestamp": "2015-01-01T00:00:00+01:00", "method": "GET", "cookies": { "cookie": "value" }, "url": { "host": "localhost", "uri": "/?parameter=value", "query": { "parameter": "value" } }, "protocol": "HTTP/1.1", "headers": { "host": "localhost", "accept-language": "en-us", "cache-control": "max-age=0", "connection": "keep-alive", [...] access.json - 1/2
  47. 47. [...] "accept": “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "accept-encoding": "gzip, deflate", "dnt": “1", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5
 (KHTML, like Gecko) Version/8.0.2 Safari/600.2.5" }, "remoteAddress": "127.0.0.1" }, "response": { "status": 200, "bytesSent": 310, "processingTime": 0.002, "headers": { "content-length": "162", "content-type": "text/html", "connection": "close" } } } access.json - 2/2
  48. 48. • Capture all request and response headers • Users of ELK will love this • Optionally send it straight to Redis • Expand your log with custom metrics BENEFITS.
  49. 49. WRAPPING UP.
  50. 50. • High performance, highly scalable • Fits well in a DevOps culture • Already using nginx? Might as well try it! SUMMARY.
  51. 51. • Online book “Programming in Lua” at lua.org is a good place to start • Also, plenty of examples and tutorials to be found at
 lua-users.org LUA 101.
  52. 52. • Nginx • OpenResty • HttpLuaModule • LuaJIT • Lua CJSON GOOGLE IT.
  53. 53. THANK YOU. Any questions?
  54. 54. • victor@coolblue.nl • @victorwelling • linkedin.com/in/victorwelling • slideshare.net/victorwelling CALL ME MAYBE.

×