>  기사  >  백엔드 개발  >  PHP 메모리 오버플로에 대한 생각을 이야기해 봅시다.

PHP 메모리 오버플로에 대한 생각을 이야기해 봅시다.

青灯夜游
青灯夜游앞으로
2020-04-04 09:21:002615검색

PHP 메모리 오버플로에 대한 생각을 이야기해 봅시다.

최근 대규모 데이터 내보내기, 데이터 가져오기를 할 때 PHP 메모리 오버플로 문제가 자주 발생합니다. 문제를 해결한 후 몇 가지 경험을 요약하여 기사로 정리했습니다.

최적화 포인트

  1. SQL 문을 최적화하고, 느린 쿼리를 방지하고, 인덱스를 합리적으로 구축하고, 쿼리 지정 필드는 여기에서 확장되지 않습니다.

  2. 쿼리 결과 집합이 대형 개체인 경우 배열로 변환할 수 있습니다. 일반적으로 프레임워크에는 Laravel의 toArray() 및 Yii2의 asArray()와 같은 메서드가 있습니다.

  3. 대규모 배열의 데이터 절단을 위해 PHP 함수에는 array_chunk() 및 array_slice()가 포함됩니다.

  4. 큰 문자열과 객체의 경우 참조별 전달 &를 사용하세요.

  5. 사용된 변수는 시간이 지나면 설정이 해제됩니다.

  6. 내보낸 파일 형식이 Excel에서 csv

  7. ini_set('memory_limit','')로 변경되어 프로그램에서 사용할 수 있는 메모리를 설정합니다(권장하지 않음).

Thinking

메모리 관리

PHP의 메모리는 어떻게 관리하나요? C 언어를 배울 때 개발자는 수동으로 메모리를 관리해야 합니다. PHP에서 Zend 엔진은 요청 관련 데이터를 처리하기 위한 특수 메모리 관리자를 제공합니다. 관련 데이터를 요청하려면 단일 요청만 제공하면 되며, 늦어도 요청이 끝나면 데이터가 공개됩니다.

PHP 메모리 오버플로에 대한 생각을 이야기해 봅시다.

위 사진은 공식홈페이지 설명 캡쳐입니다

메모리 누수를 방지하고 최대한 빠르게 모든 메모리를 해제하는 것은 메모리 관리에 있어 중요한 부분입니다. 보안상의 이유로 Zend 엔진은 위에서 언급한 API 잠금으로 할당된 모든 메모리를 해제합니다.

가비지 수집 메커니즘

간략한 설명:

PHP5.3 이전에는 참조 카운팅을 관리에 사용했습니다. PHP의 변수는 zval의 변수 컨테이너에 저장됩니다. 변수가 참조되면 참조 횟수는 +1입니다. 변수 참조 횟수가 0이면 PHP는 메모리에서 변수를 삭제합니다. 그러나 참조 카운트를 주기적으로 참조하면 참조 카운트가 0으로 줄어들지 않아 메모리 누수가 발생합니다.

PHP는 5.3 이후에 최적화되었습니다. 참조 횟수가 감소할 때마다 재활용 주기에 들어가지 않습니다. 가비지 수집은 루트 버퍼가 가득 찼을 때만 시작됩니다. 이를 통해 순환 참조 문제를 해결하고 전체 메모리 누수를 유지할 수 있습니다. 임계값 미만.

Code

phpexcel을 사용할 때 메모리 오버플로가 자주 발생하므로 csv 파일을 생성하는 코드는 다음과 같습니다.

<?php

namespace api\service;

class ExportService
{

    public static $outPutFile = &#39;&#39;;

    /**
     * 导出文件
     * @param string $fileName
     * @param $data
     * @param array $formFields
     * @return mixed
     */
    public static function exportData($fileName = &#39;&#39;, $data, $formFields = [])
    {
        $fileArr = [];
        $tmpPath = \Yii::$app->params[&#39;excelSavePath&#39;];

        foreach (array_chunk($data, 10000) as $key => $value) {
            self::$outPutFile = &#39;&#39;;
            $subject          = !empty($fileName) ? $fileName : &#39;data_&#39;;
            $subject          .= date(&#39;YmdHis&#39;);
            if (empty($value) || empty($formFields)) {
                continue;
            }

            self::$outPutFile = $tmpPath . $subject . $key . &#39;.csv&#39;;
            if (!file_exists(self::$outPutFile)) {
                touch(self::$outPutFile);
            }
            $index  = array_keys($formFields);
            $header = array_values($formFields);
            self::outPut($header);

            foreach ($value as $k => $v) {
                $tmpData = [];
                foreach ($index as $item) {
                    $tmpData[] = isset($v[$item]) ? $v[$item] : &#39;&#39;;
                }
                self::outPut($tmpData);
            }
            $fileArr[] = self::$outPutFile;
        }
        
        $zipFile = $tmpPath . $fileName . date(&#39;YmdHi&#39;) . &#39;.zip&#39;;
        $zipRes = self::zipFile($fileArr, $zipFile);
        return $zipRes;
    }

    /**
     * 向文件写入数据
     * @param array $data
     */
    public static function outPut($data = [])
    {
        if (is_array($data) && !empty($data)) {
            $data = implode(&#39;,&#39;, $data);
            file_put_contents(self::$outPutFile, iconv("UTF-8", "GB2312//IGNORE", $data) . PHP_EOL, FILE_APPEND);
        }
    }

    /**
     * 压缩文件
     * @param $sourceFile
     * @param $distFile
     * @return mixed
     */
    public static function zipFile($sourceFile, $distFile)
    {
        $zip = new \ZipArchive();
        if ($zip->open($distFile, \ZipArchive::CREATE) !== true) {
            return $sourceFile;
        }

        $zip->open($distFile, \ZipArchive::CREATE);
        foreach ($sourceFile as $file) {
            $fileContent = file_get_contents($file);
            $file        = iconv(&#39;utf-8&#39;, &#39;GBK&#39;, basename($file));
            $zip->addFromString($file, $fileContent);
        }
        $zip->close();
        return $distFile;
    }
    
        /**
     * 下载文件
     * @param $filePath
     * @param $fileName
     */
    public static function download($filePath, $fileName)
    {
        if (!file_exists($filePath . $fileName)) {
            header(&#39;HTTP/1.1 404 NOT FOUND&#39;);
        } else {
            //以只读和二进制模式打开文件
            $file = fopen($filePath . $fileName, "rb");

            //告诉浏览器这是一个文件流格式的文件
            Header("Content-type: application/octet-stream");
            //请求范围的度量单位
            Header("Accept-Ranges: bytes");
            //Content-Length是指定包含于请求或响应中数据的字节长度
            Header("Accept-Length: " . filesize($filePath . $fileName));
            //用来告诉浏览器,文件是可以当做附件被下载,下载后的文件名称为$file_name该变量的值
            Header("Content-Disposition: attachment; filename=" . $fileName);

            //读取文件内容并直接输出到浏览器
            echo fread($file, filesize($filePath . $fileName));
            fclose($file);
            exit();
        }
    }
}

코드 불러오기

$fileName = "库存导入模板";
$stockRes = []; // 导出的数据
$formFields = [
    &#39;store_id&#39;  => &#39;门店ID&#39;,
    &#39;storeName&#39; => &#39;门店名称&#39;,
    &#39;sku&#39;       => &#39;SKU编码&#39;,
    &#39;name&#39;      => &#39;SKU名称&#39;,
    &#39;stock&#39;     => &#39;库存&#39;,
    &#39;reason&#39;    => &#39;原因&#39;
];
$fileRes    = ExportService::exportData($fileName, $stockRes, $formFields);
$tmpPath    = \Yii::$app->params[&#39;excelSavePath&#39;]; // 文件路径
$fileName   = str_replace($tmpPath, &#39;&#39;, $fileRes);

// 下载文件
ExportService::download($tmpPath, $fileName);

추천 학습: PHP 비디오 튜토리얼

위 내용은 PHP 메모리 오버플로에 대한 생각을 이야기해 봅시다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 github.io에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제