Почему это приложение ColdFusion 8 вызывает скачок производительности процессора?

Спасибо, что нашли время изучить этот вопрос .... Потерпи меня ....

На днях один из наших веб-серверов перестал обслуживать веб-страницы. Веб-сервер - это физический сервер под управлением Windows Server 2003 R2 Standard Edition Service Pack 2 с IIS 6. Он работает под управлением ColdFusion 8.0.1 Standard с Java 1.6.0_24. На этом сервере есть 3 общедоступных веб-сайта, которые практически не используются. Страницы на всех трех веб-сайтах истекали по таймауту или возвращали 500 ошибок.

Когда я зарегистрировался на сервере, чтобы узнать, в чем проблема, я ожидал увидеть, что служба JRUN использует тонну памяти. С памятью все было в порядке, но я заметил, что ЦП работал на 100% или почти на 100%. Около 50% использовалось JRUN, а остальные 50% - неконтролируемым процессом резервного копирования, который я убил. Но затем JRUN быстро съел процессор и стал использовать почти 100%.

Я просмотрел журналы ColdFusion и заметил несколько ошибок, связанных с пространством кучи Java.

Я просмотрел журналы IIS и заметил, что было множество запросов к приложению, которое позволяет одному из наших клиентов загружать несколько файлов изображений для своих продуктов с помощью uploadify. Приложение написано на ColdFusion и использует jQuery для вызова веб-службы, которая обрабатывает загрузку и изменяет размер загруженных изображений с помощью <CFIMAGE>.

Увидев эту информацию в журналах, я решил, что виновата какая-то часть этого приложения.

Я просто не могу найти, что именно вызвало ошибки пространства кучи Java и скачок ЦП. Есть предположения?

Метод CFC WebService:

<cffunction
    name="uploadFile"
    access="remote"
    returntype="Struct"
    output="false"
    returnformat="JSON">
    <cfargument name="FileName" type="String" default="" />
    <cfargument name="FileData" type="String" default="" />

    <cfscript>
        var _response = NewAPIResponse();
        var _tempFilePath = APPLICATION.TempDir & "\" & ARGUMENTS.FileName;
        var _qItem = QueryNew("");
        var _product = CreateObject("component", "cfc.Product");
        var _result = {};
        var _sku = "";

        /*
            Each file must be named [Part Number].[file extension], so, \
            parse the file name to get everything before the file extension
        */
        _sku =
            Trim(
                REQUEST.UDFLib.File.getFileNameWithoutExtension(
                    ARGUMENTS.FileName
                    )
                );
    </cfscript>

    <cfif Len(_sku) GT 20>
        <cfthrow
            message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
    </cfif>

    <cfset _qItem = _product.readSKU(_sku) />

    <cfif NOT _qItem.RECORDCOUNT>
        <cfthrow
            message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
    </cfif>

    <cfscript>
        FileCopy(ARGUMENTS.FileData, _tempFilePath);

        _aMessages =
            _product.setId(
                _qItem.SKU
                ).updateThumbnailImages(uploadFilePath = _tempFilePath);
    </cfscript>

    <cfif ArrayLen(_aMessages)>
        <cfthrow
            message="#ARGUMENTS.FileName#: #_aMessages[1].getText()#" />
    </cfif>

    <cfscript>
        _result["SKU"] = _product.getSKU();
        _result["SMALLIMAGESRC"] = _product.getSmallImageSrc();
        _result["LARGEIMAGESRC"] = _product.getLargeImageSrc();

        ArrayAppend(_response.data, _result);
    </cfscript>

    <cfreturn _response />
</cffunction>

Функция изменения размера изображения:

<cffunction name="updateThumbnailImages" returntype="Array" output="false">
    <cfargument name="uploadFilePath" type="String" required="true" />

    <cfset var _image = {} />

    <cfif FileExists(ARGUMENTS.uploadFilePath)>
        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 500,
                maxWidth = 700
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getLargeImagePath()#" />

        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 300,
                maxWidth = 300
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getMediumImagePath()#" />

        <cfset _image =
            REQUEST.UDFLib.Image.scale(
                imagePath = ARGUMENTS.uploadFilePath,
                maxHeight = 50,
                maxWidth = 50
                ) />

        <cfimage
            action="write"
            source="#_image#"
            overwrite="true"
            destination="#getSmallImagePath()#" />
    </cfif>
</cffunction>

Пользовательские функции масштабирования изображения:

<cffunction name="getDimensionsToEnlarge" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="minWidth" type="Numeric" required="true" />
    <cfargument name="minHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        if  (
                ARGUMENTS.minHeight > 0
            &&  ARGUMENTS.minWidth > 0
            &&  imageHeight < ARGUMENTS.minHeight
            &&  imageWidth < ARGUMENTS.minWidth
            ) {
            dimensions.width = ARGUMENTS.minWidth;
            dimensions.height = ARGUMENTS.minHeight;
        }

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="getDimensionsToShrink" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="maxWidth" type="Numeric" required="true" />
    <cfargument name="maxHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        if  (
                ARGUMENTS.maxHeight > 0
            &&  ARGUMENTS.maxWidth > 0
            &&  (
                    imageHeight > ARGUMENTS.maxHeight
                ||  imageWidth > ARGUMENTS.maxWidth
                )
            ) {
            dimensions.width = ARGUMENTS.maxWidth;
            dimensions.height = ARGUMENTS.maxHeight;
        }

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="getDimensionsToFit" returntype="Struct" output="false">
    <cfargument name="imageWidth" type="Numeric" required="true" />
    <cfargument name="imageHeight" type="Numeric" required="true" />
    <cfargument name="minWidth" type="Numeric" required="true" />
    <cfargument name="minHeight" type="Numeric" required="true" />
    <cfargument name="maxWidth" type="Numeric" required="true" />
    <cfargument name="maxHeight" type="Numeric" required="true" />

    <cfscript>
        var dimensions = {
            width = -1,
            height = -1
            };

        dimensions =
            getDimensionsToEnlarge(
                imageHeight = ARGUMENTS.imageHeight,
                imageWidth = ARGUMENTS.imageWidth,
                minWidth = ARGUMENTS.minWidth,
                minHeight = ARGUMENTS.minHeight
                );

        if (dimensions.width < 0 && dimensions.height < 0)
            dimensions =
                getDimensionsToShrink(
                    imageHeight = ARGUMENTS.imageHeight,
                    imageWidth = ARGUMENTS.imageWidth,
                    maxWidth = ARGUMENTS.maxWidth,
                    maxHeight = ARGUMENTS.maxHeight
                    );

        return dimensions;
    </cfscript>
</cffunction>

<cffunction name="scale" returntype="Any" output="false">
    <cfargument name="imagePath" type="String" required="true" />
    <cfargument name="action" type="String" default="fit" hint="shrink, enlarge, or fit"/>
    <cfargument name="minWidth" type="Numeric" default="-1" />
    <cfargument name="minHeight" type="Numeric" default="-1" />
    <cfargument name="maxWidth" type="Numeric" default="-1" />
    <cfargument name="maxHeight" type="Numeric" default="-1" />

    <cfscript>
        var scaledDimensions = {
                width = -1,
                height = -1
            };
        var scaledImage = ImageNew();

        scaledImage = ImageNew(ARGUMENTS.imagePath);

        switch (ARGUMENTS.action) {
            case "shrink":
                scaledDimensions =
                    getDimensionsToShrink(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        maxWidth = ARGUMENTS.maxWidth,
                        maxHeight = ARGUMENTS.maxHeight
                    );

                break;
            case "enlarge":
                scaledDimensions =
                    getDimensionsToEnlarge(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        minWidth = ARGUMENTS.minWidth,
                        minHeight = ARGUMENTS.minHeight
                    );

                break;
            default:
                scaledDimensions =
                    getDimensionsToFit(
                        imageHeight = scaledImage.getHeight(),
                        imageWidth = scaledImage.getWidth(),
                        minWidth = ARGUMENTS.minWidth,
                        minHeight = ARGUMENTS.minHeight,
                        maxWidth = ARGUMENTS.maxWidth,
                        maxHeight = ARGUMENTS.maxHeight
                    );

                break;
        }

        if (scaledDimensions.width > 0 && scaledDimensions.height > 0) {
            // This helps the image quality
            ImageSetAntialiasing(scaledImage, "on");

            ImageScaleToFit(
                scaledImage,
                scaledDimensions.width,
                scaledDimensions.height
                );
        }

        return scaledImage;
    </cfscript>
</cffunction>

person Eric Belair    schedule 08.08.2014    source источник
comment
Я не эксперт по серверам, но не могли бы вы попробовать разделить сайты на их собственные пулы приложений в IIS, чтобы изолировать ресурсы между ними? Тогда, возможно, вы сможете определить, какой из трех вызывает больше всего проблем.   -  person Leeish    schedule 08.08.2014
comment
Головное пространство Java или пространство кучи Java? Кроме того, все эти запросы страниц поступали с одного и того же IP-адреса? Может быть, у вас был очень нетерпеливый пользователь. Такого рода вещи случались с нами.   -  person Dan Bracuk    schedule 08.08.2014
comment
@DanBracuk куча, извините. Да, тот же IP-адрес, но этого и следовало ожидать, поскольку приложение обрабатывает несколько загрузок.   -  person Eric Belair    schedule 09.08.2014
comment
@Leeish не требуется, так как я знаю сайт, на котором возникает проблема. Я просто не могу понять, почему это происходит.   -  person Eric Belair    schedule 09.08.2014
comment
Можете ли вы определить размер загруженных изображений? Возможно, кто-то пытался загрузить очень большой файл? Я имею в виду, что ваш код не делает ничего принципиально нового   -  person Leeish    schedule 09.08.2014
comment
Предлагаю посмотреть на саму форму. Посмотрите, есть ли способ помешать счастливым людям кликнуть. Часто работает скрытие кнопки отправки при отправке формы.   -  person Dan Bracuk    schedule 09.08.2014
comment
@DanBracuk Нет формы. Это подключаемый модуль flash, который использует одну кнопку, с помощью которой пользователь выбирает файлы, подключаемый модуль flash вызывает веб-службу для каждого выбранного файла, а веб-служба выполняет сохранение файлов и манипулирование изображениями. Кроме того, в журналах каждый из запросов содержал существенно разное количество байтов, поэтому я знаю, что это не один и тот же запрос, отправленный несколько раз.   -  person Eric Belair    schedule 09.08.2014
comment
@Leeish количество байтов, отправленных по каждому запросу, варьировалось от примерно 200 КБ до примерно 1500 КБ (1,5 МБ). Большой, но не огромный.   -  person Eric Belair    schedule 09.08.2014
comment
Да, этого недостаточно, чтобы вызывать проблемы, если только не было много запросов за очень короткий промежуток времени, например, от бота или чего-то еще.   -  person Leeish    schedule 12.08.2014
comment
@EricBelair Я столкнулся с той же проблемой, используя веб-сервисы coldfusion. Проблема заключается в утечке памяти в Java 1.6 при создании объектов внутри веб-службы. Мне удалось воспроизвести проблему на нашем промежуточном сервере с помощью load ui. В Java 1.6 я смог добиться лучших результатов, создав объект один раз в функции инициализации и установив его в область видимости переменных. Затем я использую запрос получения каждый раз, когда мне нужно использовать объект, который создает дубликат объекта. т.е. дубликат (переменные.объект). Это в сочетании с обновлением Java до версии 1.7 остановило наш сервер от сбоев.   -  person Alan Bullpitt    schedule 12.08.2014
comment
@AlanBullpitt, не могли бы вы опубликовать фрагмент кода с тем, что вы сделали? Хотя я в замешательстве. Если это была утечка памяти, почему не произошло скачка памяти? Только CPU зашил? В конце концов мы обновим Java до версии 1.7, но сейчас я не могу этого сделать.   -  person Eric Belair    schedule 12.08.2014
comment
@EricBelair Я не могу ответить, почему это так резко. Я просто знаю, что мы могли воспроизводить результаты снова и снова с помощью нагрузочного тестирования исключительно нашего веб-сервиса. Я отправлю свой пример кода ниже.   -  person Alan Bullpitt    schedule 12.08.2014


Ответы (1)


Ниже приведен пример кода, о котором я говорил в разделе комментариев. Это повысило надежность для нас, но при экстремальной нагрузке мы обнаружили, что проблема все еще возникает, поэтому мы перешли на Java 1.7. Однако у 1.7 были свои проблемы, так как производительность резко падает, как указано здесь. Надеюсь, поможет.

<cffunction name="init" access="private" output="false" returntype="void">
    <cfset variables.instance = StructNew() />
    <!--- create object and set to variables.instance --->
    <cfset setProductObject()>
</cffunction>

<cffunction name="setProductObject" access="private" returntype="void" output="false">
    <!--- create object once and set it to the variables.instance scope --->
    <cfset var product = createObject("component","cfc.Product")>
    <cfset variables.instance.product = product/>
    <cfset product = javacast('null', '')>
</cffunction>

<cffunction name="getProductObject" access="private" returntype="cfc.Product" output="false">
    <!--- instead of creating object each time use the one already in memory and duplicate it --->
    <cfset var productObj = duplicate(variables.instance.product)>
    <cfreturn productObj />
</cffunction>

<cffunction name="uploadFile" access="remote" returntype="struct" returnformat="JSON" output="false">
    <cfargument name="FileName" type="String" default="" />
    <cfargument name="FileData" type="String" default="" />
        <cfscript>
            var _response = NewAPIResponse();
            var _tempFilePath = APPLICATION.TempDir & "\" & ARGUMENTS.FileName;
            var _qItem = QueryNew("");
            var _product = "";
            var _result = {};
            var _sku = "";
            //check init() function has been run if not run it
            if NOT structKeyExists(variables,'instance') {
                init();         
            }

            _product = getProductObject();
            /*
                Each file must be named [Part Number].[file extension], so, \
                parse the file name to get everything before the file extension
            */
            _sku =
                Trim(
                    REQUEST.UDFLib.File.getFileNameWithoutExtension(
                        ARGUMENTS.FileName
                        )
                    );
        </cfscript>

        <cfif Len(_sku) GT 20>
            <cfthrow
                message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
        </cfif>

        <cfset _qItem = _product.readSKU(_sku) />

        <cfif NOT _qItem.RECORDCOUNT>
            <cfthrow
                message="#ARGUMENTS.FileName#: File Name does not correspond to an existing Part Number." />
        </cfif>

        <cfscript>
            FileCopy(ARGUMENTS.FileData, _tempFilePath);

            _aMessages =
                _product.setId(
                    _qItem.SKU
                    ).updateThumbnailImages(uploadFilePath = _tempFilePath);
        </cfscript>

        <cfif ArrayLen(_aMessages)>
            <cfthrow
                message="#ARGUMENTS.FileName#: #_aMessages[1].getText()#" />
        </cfif>

        <cfscript>
            _result["SKU"] = _product.getSKU();
            _result["SMALLIMAGESRC"] = _product.getSmallImageSrc();
            _result["LARGEIMAGESRC"] = _product.getLargeImageSrc();

            ArrayAppend(_response.data, _result);
        </cfscript>

        <cfreturn _response />
</cffunction>

person Alan Bullpitt    schedule 12.08.2014
comment
По сути, все, что это делает, - это перемещение переменной функции в область видимости VARIABLES, верно? Не понимаю, как это поможет. Разве каждый запрос к удаленному методу не является отдельным потоком и, таким образом, не запускает отдельный экземпляр CFC? - person Eric Belair; 20.08.2014
comment
@EricBelair - это то, о чем я изначально думал, но после тестирования обнаружил, что это не так. После загрузки функции в область видимости переменной она остается там до изменения файла или перезапуска Coldfusion. Мы провели нагрузочное тестирование с помощью LoadUI и Fusionreactor. После этих изменений мы заметили значительное увеличение производительности в количестве запросов в минуту, которые приложение могло обрабатывать, и резко снизилась скорость увеличения кучи памяти Java. - person Alan Bullpitt; 20.08.2014
comment
@EricBelair мы также создали новый application.cfc для каталога веб-сервисов с отключенным управлением сеансом. Каждый вызов создавал уникальный сеанс, и, прежде чем мы узнали об этом, у нас было 100000 сеансов в памяти, ожидающих тайм-аута. - person Alan Bullpitt; 20.08.2014
comment
хороший звонок при отключении управления сеансом! Я собираюсь реализовать это немедленно. - person Eric Belair; 02.09.2014