Nginx на стероидах — расширяем функционал с помощью LUA

    Для обеспечения работы всех наших внешних продуктов мы используем популярный nginx. Это быстро и это надежно. Проблем с ним почти нет. Наши продукты также постоянно развиваются, появляются новые сервисы, добавляется новый функционал, расширяется старый. Аудитория и нагрузка только растет. Сейчас мы хотим рассказать о том, как мы ускорили разработку, неплохо увеличили производительность и упростили добавление в наши сервисы этого нового функционала, при этом сохранив доступность и отказоустойчивость затронутых приложений. Речь пойдет о концепции “nginx as web application”.
    А именно, о сторонних модулях (в основном LUA), позволяющих делать совершенно магические вещи быстро и надежно.
    image


    Проблемы и решение

    Основная задумка довольно простая. Возьмем следующие факторы:
    — сложность логики приложения,
    — количество компонентов приложения,
    — размер аудитории.
    С определенного момента становится довольно сложно поддерживать приложение отзывчивым и быстрым, иногда даже и работоспособным. Продукт становится многокомпонентным, географически распределенным. И им пользуются все больше людей. При этом существуют требования бизнеса по отзывчивости и отказоустойчивости, которые надо в первую очередь соблюдать.
    Путей решения этой проблемы несколько. Можно все вообще сломать и переделать на другие технологии. Безусловно, этот вариант работает, но нам он не очень понравился и мы решили переделывать постепенно. За основу была взята сборка openresty (nginx+LUA). Почему LUA. Без помощи cgi, fastcgi и других cgi прямо в конфигурационном файле nginx можно заскриптовать мощный, красивый и быстрый функционал. Все работает асинхронно. Причем не только с клиентами, но и с бекендами. При этом не вмешиваясь в event loop вебсервера, без callbacks, полностью используя имеющийся функционал nginx.

    На данный момент доступны следующие бекенды:
    — Redis
    — Memcache
    — MySQL
    — PostgreSQL
    В дополнении можно подключить еще модули для использования, например RabbitMQ и ZeroMQ.
    Это работает довольно быстро. Во всяком случае быстрее, чем php-fpm ))

    Логичный вопрос, а почему бы все вообще не переписать на C? Писать на LUA сильно проще и быстрее. И мы сразу избавлены от проблем, связанных с асинхронностью и nginx event loop.

    Примеры. Идеи

    Мы, как обычно, не будем приводить полный код, только основные части. Эти все штуки раньше были сделаны на php.

    1. Эту часть придумал и сделал наш коллега AotD. Есть хранилище картинок. Их надо показывать пользователям, причем желательно производить при этом некоторые операции, например, resize. Картинки мы храним в ceph, это аналог Amazon S3. Для обработки картинок используется ImageMagick. На ресайзере есть каталог с кэшем, туда складываются обработанные картинки.
    Парсим запрос пользователя, определяем картинку, нужное ему разрешение и идем в ceph, затем на лету обрабатываем и показываем.
    serve_image.lua
    require "config"
    local function return_not_found(msg)
        ngx.status = ngx.HTTP_NOT_FOUND
        if msg then
            ngx.header["X-Message"] = msg
        end
        ngx.exit(0)
    end
    
    local name, size, ext = ngx.var.name, ngx.var.size, ngx.var.ext
    if not size or size == '' then
        return_not_found()
    end
    if not image_scales[size] then
        return_not_found('Unexpected image scale')
    end
    
    local cache_dir =  static_storage_path .. '/' .. ngx.var.first .. '/' .. ngx.var.second .. '/'
    local original_fname = cache_dir .. name .. ext
    local dest_fname = cache_dir .. name .. size .. ext
    
    -- make sure the file exists
    local file = io.open(original_fname)
    if not file then
        -- download file contents from ceph
        ngx.req.read_body()
        local data = ngx.location.capture("/ceph_loader", {vars = { name = name .. ext }})
        if data.status == ngx.HTTP_OK and data.body:len()>0 then
            os.execute( "mkdir -p " .. cache_dir )
            local original = io.open(original_fname, "w")
            original:write(data.body)
            original:close()
        else
            return_not_found('Original returned ' .. data.status)
        end
    end
                                                                                                                                                                                                                                     
    local magick = require("imagick")                                                                                                                                                                                                 
    magick.thumb(original_fname, image_scales[size], dest_fname)                                                                                                                                                                     
    ngx.exec("@after_resize")
    


    Подключаем биндинг imagic.lua. Должен быть доступен LuaJIT.

    nginx_partial_resizer.conf.template
    # Old images
    location ~ ^/(small|small_wide|medium|big|mobile|scaled|original|iphone_(preview|retina_preview|big|retina_big|small|retina_small))_ {
        rewrite /([^/]+)$ /__CEPH_BUCKET__/$1 break;
        proxy_pass __UPSTREAM__;
    }
    # Try get image from ceph, then from local cache, then from scaled by lua original
    # If image test.png is original, when user wants test_30x30.png:
    # 1) Try get it from ceph, if not exists
    # 2) Try get it from /cache/t/es/test_30x30.ong, if not exists
    # 3) Resize original test.png and put it in /cache/t/es/test_30x30.ong
    location ~ ^/(?<name>(?<first>.)(?<second>..)[^_]+)((?<size>_[^.]+)|)(?<ext>\.[a-zA-Z]*)$ {
        proxy_intercept_errors on;
        rewrite /([^/]+)$ /__CEPH_BUCKET__/$1 break;
        proxy_pass __UPSTREAM__;
        error_page 404 403 = @local;
    }
    # Helper failover location for upper command cause you can't write
    # try_files __UPSTREAM__ /cache/$uri @resizer =404;
    location @local {
        try_files /cache/$first/$second/$name$size$ext @resize;
    }
    
    # If scaled file not found in local cache resize it with lua magic!
    location @resize {
    #    lua_code_cache off;
        content_by_lua_file "__APP_DIR__/lua/serve_image.lua";
    }
    
    # serve scaled file, invoked in @resizer serve_image.lua
    location @after_resize {
        try_files /cache/$first/$second/$name$size$ext =404;
    }
    
    # used in @resizer serve_image.lua to download original image
    # $name contains original image file name
    location =/ceph_loader {
        internal;
        rewrite ^(.+)$ /__CEPH_BUCKET__/$name break;
        proxy_set_header Cache-Control no-cache;
        proxy_set_header If-Modified-Since "";
        proxy_set_header If-None-Match "";
        proxy_pass __UPSTREAM__;
    }
    
    location =/favicon.ico {
        return 404;
    }
    
    location =/robots.txt {}
    


    2. Firewall для API. Валидация запроса, идентификация клиента, контроль rps и шлагбаум для тех, кто нам не нужен.
    Firewall.lua
    module(..., package.seeall);
    local function ban(type, element)
        CStorage.banPermanent:set(type .. '__' .. element, 1);
        ngx.location.capture('/postgres_ban', { ['vars'] = { ['type'] = type, ['value'] = element} });
    end
    local function checkBanned(apiKey)
        -- init search criteria
        local searchCriteria = {};
        searchCriteria['key'] = apiKey;
        if ngx.var.remote_addr then
            searchCriteria['ip'] = ngx.var.remote_addr;
        end;
        -- search in ban lists
        for type, item in pairs(searchCriteria) do
            local storageKey = type .. '__' .. item;
            if CStorage.banPermanent:get(storageKey) then
                ngx.exit(444);
            elseif CStorage.banTmp:get(storageKey) then
                -- calculate rps and check is our client still bad boy 8-)
                local rps = CStorage.RPS:incr(storageKey, 1);
                if not(rps) then
                    CStorage.RPS:set(storageKey, 1, 1);
                    rps=1;
                end;
                if rps then
                    if rps > config.app_params['ban_params']['rps_for_ip_to_permanent_ban'] then
                        CStorage.RPS:delete(storageKey);
                        ban(type, item);
                        ngx.exit(444);
                    elseif config.app_params['ban_params']['rps_for_ip_to_tmp_ban'] > 0 and rps == config.app_params['ban_params']['rps_for_ip_to_tmp_ban'] then
                        local attemptsCount = CStorage.banTmp:incr(storageKey, 1) - 1;
                        if attemptsCount > config.app_params['ban_params']['tmp_ban']['max_attempt_to_exceed_rps'] then
                            -- permanent ban
                            CStorage.banTmp:delete(storageKey);
                            ban(type, item);
                        end;
                    end;
                end;
                ngx.exit(444);
            end;
        end;
    end;
    
    local function checkTemporaryBlocked(apiKey)
        local blockedData = CStorage.tmpBlockedDemoKeys:get(apiKey);
        if blockedData then
            --storage.tmpBlockedDemoKeys:incr(apiKey, 1); -- think about it.
            return CApiException.throw('tmpDemoBlocked');
        end;
    end;
    
    local function checkRPS(apiKey)
        local rps = nil;
        -- check rps for IP and ban it if it's needed
        if ngx.var.remote_addr then
            local ip = 'ip__' .. tostring(ngx.var.remote_addr);
            rps = CStorage.RPS:incr(ip, 1);
            if not(rps) then
                CStorage.RPS:set(ip, 1, 1);
                rps = 1;
            end;
            if rps > config.app_params['ban_params']['rps_for_ip_to_permanent_ban'] then
                ban('ip', tostring(ngx.var.remote_addr));
                ngx.exit(444);
            elseif config.app_params['ban_params']['rps_for_ip_to_tmp_ban'] > 0 and rps > config.app_params['ban_params']['rps_for_ip_to_tmp_ban'] then
                CStorage.banTmp:set(ip, 1, config.app_params['ban_params']['tmp_ban']['time']);
                ngx.exit(444);
            end;
        end;
    
        local apiKey_key_storage = 'key_' .. apiKey['key'];
        -- check rps for key
        rps = CStorage.RPS:incr(apiKey_key_storage, 1);
        if not(rps) then
            CStorage.RPS:set(apiKey_key_storage, 1, 1);
            rps = 1;
        end;
        if apiKey['max_rps'] and rps > tonumber(apiKey['max_rps']) then
            if apiKey['mode'] == 'demo' then
                CApiKey.blockTemporary(apiKey['key']);
                return CApiException.throw('tmpDemoBlocked');
            else
                CApiKey.block(apiKey['key']);
                return CApiException.throw('blocked');
            end;
        end;
    
        -- similar check requests per period (RPP) for key
        if apiKey['max_request_count_per_period'] and apiKey['period_length'] then
            local rpp = CStorage.RPP:incr(apiKey_key_storage, 1);
            if not(rpp) then
                CStorage.RPP:set(apiKey_key_storage, 1, tonumber(apiKey['period_length']));
                rpp = 1;
            end;
    
            if rpp > tonumber(apiKey['max_request_count_per_period']) then
                if apiKey['mode'] == 'demo' then
                    CApiKey.blockTemporary(apiKey['key']);
                    return CApiException.throw('tmpDemoBlocked');
                else
                    CApiKey.block(apiKey['key']);
                    return CApiException.throw('blocked');
                end;
            end;
        end;
    end;
    
    function run()
        local apiKey = ngx.ctx.REQUEST['key'];
        if not(apiKey) then
            return CApiException.throw('unauthorized');
        end;
        apiKey = tostring(apiKey)
        -- check permanent and temporary banned
        checkBanned(apiKey);
        -- check api key
        apiKey = CApiKey.getData(apiKey);
    
        if not(apiKey) then
            return CApiException.throw('forbidden');
        end;
        apiKey = JSON:decode(apiKey);
        if not(apiKey['is_active']) then
            return CApiException.throw('blocked');
        end;
    
        apiKey['key'] = tostring(apiKey['key']);
        -- check is key in tmp blocked list
        if apiKey['mode'] == 'demo' then
            checkTemporaryBlocked(apiKey['key']);
        end;
    
        -- check requests count per second and per period
        checkRPS(apiKey);
        -- set apiKey's json to global parameter; in index.lua we send it through nginx to php application
        ngx.ctx.GLOBAL['api_key'] = JSON:encode(apiKey);
    end;
    


    Validator.lua
    module(..., package.seeall);
    
    local function checkApiVersion()
        local apiVersion = '';
        if not (ngx.ctx.REQUEST['version']) then
            local nginx_request = tostring(ngx.var.uri);
            local version = nginx_request:sub(2,4);
            if tonumber(version:sub(1,1)) and tonumber(version:sub(3,3)) then
                apiVersion = version;
            else
                return CApiException.throw('versionIsRequired');
            end;
        else
            apiVersion = ngx.ctx.REQUEST['version'];
        end;
    
        local isSupported = false;
        for i, version in pairs(config.app_params['supported_api_version']) do
            if apiVersion == version then
                isSupported = true;
            end;
        end;
    
        if not (isSupported) then
            CApiException.throw('unsupportedVersion');
        end;
    
        ngx.ctx.GLOBAL['api_version'] = apiVersion;
    end;
    
    local function checkKey()
        if not (ngx.ctx.REQUEST['key']) then
            CApiException.throw('unauthorized');
        end;
    end;
    
    function run()
        checkApiVersion();
        checkKey();
    end;
    


    Apikey.lua
    module ( ..., package.seeall )
    
    function init()
        if not(ngx.ctx.GLOBAL['CApiKey']) then
            ngx.ctx.GLOBAL['CApiKey'] = {};
        end
    end;
    
    function flush()
        CStorage.apiKey:flush_all();
        CStorage.apiKey:flush_expired();
    end;
    
    function load()
        local dbError = nil;
        local dbData = ngx.location.capture('/postgres_get_keys');
        dbData = dbData.body;
        dbData, dbError = rdsParser.parse(dbData);
        if dbData ~= nil then
            local rows = dbData.resultset
            if rows then
                for i, row in ipairs(rows) do
                    local cacheKeyData = {};
                    for col, val in pairs(row) do
                        if val ~= rdsParser.null then
                            cacheKeyData[col] = val;
                        else
                            cacheKeyData[col] = nil;
                        end
                    end
                    CStorage.apiKey:set(tostring(cacheKeyData['key']),JSON:encode(cacheKeyData));
                end;
            end;
        end;
    end;
    
    function checkNotEmpty()
        if not(ngx.ctx.GLOBAL['CApiKey']['loaded']) then
            local cnt = CHelper.tablelength(CStorage.apiKey:get_keys(1));
            if cnt == 0 then
                load();
            end;
            ngx.ctx.GLOBAL['CApiKey']['loaded'] = 1;
        end;
    end;
    
    function getData(key)
        checkNotEmpty();
        return CStorage.apiKey:get(key);
    end;
    
    function getStatus(key)
            key = getData(key);
            local result = '';
            if key ~= nil then
                key = JSON:decode(key);
                if key['is_active'] ~= nil and  key['is_active'] == true then
                    result = 'allowed';
                else
                    result = 'blocked';
                end;
            else
                result = 'forbidden';
            end;
            return result;
    end;
    
    function blockTemporary(apiKey)
        apiKey = tostring(apiKey);
        local isset = getData(apiKey);
        if isset then
            CStorage.tmpBlockedDemoKeys:set(apiKey, 1, config.app_params['ban_params']['time_demo_apikey_block_tmp']);
        end;
    end;
    
    function block(apiKey)
        apiKey = tostring(apiKey);
        local keyData = getData(apiKey);
        if keyData then
            ngx.location.capture('/redis_get', { ['vars'] = { ['key'] = apiKey } });
            keyData['is_active'] = false;
            CStorage.apiKey:set(apiKey,JSON:encode(cacheKeyData));
        end;
    end;
    


    Storages.lua
    module ( ..., package.seeall )
    
    apiKey = ngx.shared.apiKey;
    RPS = ngx.shared.RPS;
    RPP = ngx.shared.RPP;
    banPermanent = ngx.shared.banPermanent;
    banTmp = ngx.shared.banTmp;
    tmpBlockedDemoKeys = ngx.shared.tmpBlockedDemoKeys;
    


    3. Дополнительные сервисы, например межкомпонентное взаимодействие по протоколу AMQP. Пример здесь.

    4. Как я уже писал. Модуль самодиагностики приложения с возможностью “умного” управления маршрутами прохождения запроса через бекенды. Еще в разработке.

    5. Адаптеры для интерфейсов API. В некоторых случаях необходимо подправить, дополнить или расширить имеющиеся методы. Чтобы все не переписывать, LUA поможет. Например, json<->xml conversion на лету.

    6…. идей еще много.

    Бенчмарков как таковых не будет. Продукты слишком сложны и рпс после бенча сильно зависит от многих факторов. Однако, для наших продуктов, мы добились 20-кратного увеличения производительности для затронутого функционала, а в некоторых случаях все стало быстрее до ~200 раз.

    Плюсы и минусы

    Ощутимые плюсы. Все, что раньше было 5 мегабайтами кода на php, превращается в 100кб файл на lua.
    — скорость разработки,
    — скорость работы приложения,
    — надежность,
    — асинхронная работа с клиентами и бекендами, не ломающая при этом event loop nginx,
    — LUA sugar feel good! Корутины, shared dictionary для всех форков nginx, сабреквесты, куча биндингов.

    Неощутимые минусы.
    — делать надо все аккуратно и помнить про асинхронность и event loop nginx.
    — фронтенд работает настолько быстро, что это может не понравиться бекенду. Между ними прямая связь, без прослоек. Я, например, уверен, что 10000 запросов в секунду LUA на фронтенде прожует легко. Но, если при этом оно захочет пойти в базу, тут могут возникнуть проблемы.
    — довольно непросто отладить, если что-то пойдет не так.

    Кстати, пока пишется эта статья, прямо в этот момент наш программист рассказывает про все это в подробностях на highload.

    С удовольствием ответим на вопросы в комментариях.

    Напоследок, здесь можно найти небольшую подборку информации по теме.

    2ГИС

    169,00

    Карта города и справочник предприятий

    Поделиться публикацией

    Похожие публикации

    Комментарии 40
      0
      Программист еще не рассказывает :) Выступление в 15 часов. Один из докладов, которые с интересом послушаю. Особенно интересно сравнить с докладом про логику на LUA в Redis.
        0
        — фронтенд работает настолько быстро, что это может не понравиться бекенду. Между ними прямая связь, без прослоек. Я, например, уверен, что 10000 запросов в секунду LUA на фронтенде прожует легко. Но, если при этом оно захочет пойти в базу, тут могут возникнуть проблемы.

        Какие именно проблемы возникали? Как решали? Какие базы используете?
          +1
          Postgresql в основном. Он просто не успевает )) Искусственно ограничили количество одновременных запросов в локацию, где дергается база данных и воткнули пулеры, например pgpool2. Для redis и memcache тоже есть пулер, twemproxy. Если мощностей не хватает, добавляем еще бекендов с базой, оно все умеет балансироваться.
            0
            А как эта проблема проявляется для конечного пользователя? 503?
              0
              Вызов сабреквеста по факту в итоге возвратит нам какой-либо статус. Что с ним потом делать, решать тоже нам. Например,

              content_by_lua '
                              local res = ngx.location.capture("/some_other_location")
                              if res.status == 503 then
                              ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
                              end';
              


              Если логично будет отдать 503, можно отдавать )) Если нужно что-то еще, не проблема тоже.
                0
                В самом модуле, который ходит в базу, мы тоже можем определить все что угодно для любого события.

          –7
          Сделайте карту покрытия домашних провайдеров. Чтобы при клике на дом было видно какие провайдеры там есть.
            +1
            Интересный был доклад.
            И я был весьма удивилен такой высокой скорости в 1мс на 16000RPS на довольно сложной логике (по словам выступающего :)).

              +1
              Если все прикрыто кэшами, везде включен keepalive, то работает очень быстро. И что самое главное, не надо по каждому чиху поднимать и заставлять работать медленный комбайн, как в случае если все сделано на php.
                0
                Мы у себя уже недели 2-3 обдумываем как раз, а не попробовать ли lua в подобном решении. А тут аж два выступления на эту тему: ваше плюс вчера было, пусть и не столь удачное, про redis+lua.
                Полюбому теперь будем пробовать :)

                Спасибо еще раз за материал!
              0
              А чем не угодил ресайз в nginx без lua?
                0
                Использовать только image_filter_module всё равно не вышло бы из-за навернутой логики промежуточных хранилищ. Использовать его совместно с lua — был вариант, но ngx_http_image_filter_module использует gd, с которым мы чуть-чуть понатерпели так что к нему у меня предвзятое отношение.

                imagemagick — безумный комбайн, способный перемолоть всё =)

                Суть решения: сохраняем в S3-подобное хранилище оригинал изображения, а на стороне клиента можем использовать произвольные разрешения (test.jpg -> test_15_14.jpg, test_iphone_retina_small.jpg, ...), которые после уменьшения так же попадают в хранилище и в последствии отдаются напрямую.
                  0
                  Главная причина — resize не единственное, что было заложено в требованиях к сервису, imagemagick все же надо было оставить.
                  Также, файл нам надо надо сначала достать из хранилища и это на пару с самой пережимкой все блокирует nginx-овский воркер. Плюс сам модуль весьма прожорлив под нагрузкой.
                    0
                    Доставание из хранилища неблокирующее всё же
                      0
                      Окей, вопрос про блокировки тут наверное и не приоритетный. Два остальные вопроса не решены совсем )
                      Навскидку, если даже брать только resize, в данном конкретном случае у меня получается 25-30 локаций и довольно сложно что-то сделать, если с файлом что-то не так или его нет. На lua у нас возможности шире и легко дописать именно то, что необходимо. Бизнес штука такая, необходимость у них часто резкая и часто мечется в разных плоскостях )))
                        +1
                        Да, еще немаловажный момент. У нас бы появилась целая ферма ресайзеров со временем, которые ничего больше не умеют ) А тут штука многофункциональная и в случае недогруза может заняться чем-то другим, рассказав перед этим об этом своим соседям по кластеру.
                  +1
                  Да, луа это очень удобная технология. Использую во многих проектах. Если есть возможность, то переписываю всю внешнюю часть проекта целиком на луа. Если же такой возможности нету, то луа может использоваться как минимум для быстрого взятия нужных страниц из редиса или другого подобного хранилища (в тех случаях, где взятие идет не просто по ключу, а требуется какая-то дополнительная логика).
                    +2
                    1) Луа — это таки язык а не технология. Технология есть использование связки lua + nginx
                    2) Не нужно пытаться писать всю сложную логику на луа в nginx. У иначе Вас получится nodejs ;) По причине однопоточности nginx использование воркеров, или внешней логики на много эффективнее.
                      0
                      >>Луа — это таки язык а не технология. Технология есть использование связки lua + nginx

                      Да, это я и имел ввиду ;)

                      >>Не нужно пытаться писать всю сложную логику на луа в nginx. У иначе Вас получится nodejs ;) По причине однопоточности nginx использование воркеров, или внешней логики на много эффективнее.

                      Но луа позволяет написать практически любое приложение и это работает намного быстрее пхп. Для сложных вещей вроде зипования можно использовать внешние си модули, которые можно подключать через ffi — luajit.org/ext_ffi.html.
                        0
                        >>По причине однопоточности nginx использование воркеров, или внешней логики на много эффективнее.
                        Я пока мало разобрался, но в описании nginx-lua говорится о subrequests и корутинах самой lua.
                          0
                          Да, это все есть в луа. Можно подробнее почитать об этом на wiki.nginx.org/HttpLuaModule.

                          В моем случае пока не возникало необходимости это использовать. Все работает и так очень быстро. По сравнению с пхп просто фантастика.

                          Есть правда и проблемы с луа, иногда может при некоторых условиях возникать утечка памяти. Отследить источники проблем не так-то и просто. Можно решить с помощью костыля — перезапуском nginx.
                            +1
                            Примерно по этой доке и разбираюсь) Правда взял вариант с github самого разработчика.
                            Как я понял, перекладывание большого количества логики на lua не понравилось rozhik из-за однопоточности nginx. Но синхронный Lua код выполняется только на стадии обработки конфигов (в ngix master), потом код встраивается в nginx event loop и все, при адекватном коде, должно работать очень быстро (еще и в каждом nginx worker, а не как с Perl все в том же мастере).
                    0
                    Lua спас мой сервер от ошибок 502/504. Буквально пара строчек (пусть и костыль) — и сервер автоматически перезапускает сбоящую подсистему, которую чинить нет времени — да теперь уже и необходимости тоже.
                      0
                      как говорят, нет ничего более вечного, чем временное =)
                      +2
                      Всёж ØMQ
                      не 0MZ
                        0
                        Да, конечно же. Спасибо, поправил.
                        –3
                        Может здесь кто-то из компании читает :) Написал вам на sales хотел купить платную версию. Только мне нужно было ее использовать для спец-задачи и интегрировать в текущий код сервера на objC. Контактная персона промямлила что-то про fastCGI, который я попробовал завести и конечно он не завелся :) Написал человеку про это, попросил просто пример интеграции и он исчез с концами. Я конечно понимаю что может вы настолько богаты что не нужны деньги :) Но если нет — отпишите кого-то вменяемого мне в личку и продолжим разговор.
                          0
                          М… а под компанией вы кого подразумеваете? 2Гис, nginx или lua? =)
                          Подозреваю что таки nginx, тогда это вам к тов. Сысоеву
                            0
                            Такое пишут в рассылку, например, но никак не сюда.
                            0
                            Вопрос — зачем вы каждую строку заканчиваете точкой с запятой? Обычно в Lua так не принято — точку с запятой можно применять, но ее ставят для однозначности, в случае двух действий в одной строке.
                              0
                              Привычка с других ЯП. Самодисциплина в каком-то смысле… Это как с Javascript-ом в общем-то.
                              Я сам тоже ставлю точки с запятой в Луа. Позволяет не теряться в вопросе — «а тут ставить надо? А там?»
                                0
                                Ну если в JavaScript есть разные неоднозначные моменты у точки с запятой, то в Lua, как в Python — используется только для однострочных записей. Хотя в этом смысле Lua еще более свободный, чем Python — можно писать «a = 1 b = 2» в одну строку :)
                                0
                                Ахаха. Не обращал внимания )) Подозреваю, что это php виновато ) Часть кода писал программист-пхпшник
                                  +2
                                  Еще пару фишек, который, возможно вам будут полезны:
                                  1. Взятие из таблицы по ключу можно делать через точку, как у атрибута: myTable['myKey'] и
                                  myTable.myKey идентичны
                                  2. Ключ в таблице не обязательно заключать в такую конструкцию -> ['myKey'], это необходимо только, если ключом будет число. К примеру, таблицу { ['vars'] = { ['key'] = apiKey } } можно записать так {vars = {key = apiKey}}, что выглядит намного лаконичней.
                                  3. Storages.lua можно вообще переписать так:
                                  module ( ..., package.seeall )

                                  return ngx.shared

                                  Как это будет работать? Если мы импортируем модуль Storage (пример: local Storages = require('Storages')), то после этого переменной Storages будет присвоено значение переменной, которая указана после return. В данном случае — это ngx.shared
                                  Соответственно, Storages.banTmp будет обращаться к ngx.shared.banTmp
                                    0
                                    Спасибо!
                              • НЛО прилетело и опубликовало эту надпись здесь
                                  +1
                                  Ну, то есть, что LUA «жует» вас не смутило )) Как жаргон допустимо же?
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                  +1
                                  Жаль не дошли до этого Вашего доклада. Посетили только рассказ про графовые БД.

                                  У нас вот тоже связка nginx+Lua довольно успешно работает. Как раз писали про свой опыт недавно тут:
                                  freepcrf.com/2013/10/10/pcrf-experience-dynamic-script-based-policy-decision-maker-or-what-do-we-need-lua-for/
                                  Там с описанием кейса и цифрами производительности, кому интересно.
                                    0
                                    На PyCon.ru 2013 товарищ Протасов довольно подробно с рабочим примером написанным на лету рассказал про связку Nginx+Lua, при этом уложившись в пять(!) минут. Есть даже видеозапись этого выступления. Всё довольно просто и понятно, как и всё в Nginx.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое