PHP file_put_contents 파일 잠금


9

Senario :

각 줄에 문자열 (평균 문장 가치)이있는 파일이 있습니다. 인수의 경우이 파일의 크기가 1Mb (수천 줄)라고 말할 수 있습니다.

파일을 읽고, 문서 내에서 일부 문자열을 변경하고 (일부 추가 및 제거 및 수정) 스크립트를 사용하여 모든 데이터를 새 데이터로 덮어 씁니다.

질문 :

  1. '서버'PHP, OS 또는 httpd 등이 이미 이와 같은 문제를 막을 수있는 시스템을 가지고 있습니까 (읽기 / 쓰기의 절반 정도).

  2. 그렇다면 작동 방식을 설명하고 관련 문서에 대한 예 또는 링크를 제공하십시오.

  3. 그렇지 않은 경우 쓰기가 완료 될 때까지 파일을 잠그고 이전 스크립트가 쓰기를 마칠 때까지 다른 모든 읽기 및 / 또는 쓰기에 실패하는 등 활성화하거나 설정할 수있는 것이 있습니까?

내 가정 및 기타 정보 :

  1. 문제의 서버가 PHP와 Apache 또는 Lighttpd를 실행 중입니다.

  2. 한 사용자가 스크립트를 호출하고 파일 쓰기 도중에 중간에 다른 사용자가 해당 순간에 파일을 읽는 경우. 이 문서를 읽은 사용자는 아직 작성되지 않았으므로 전체 문서를받지 못합니다. (이 가정이 잘못되면 저를 정정하십시오)

  3. PHP는 텍스트 파일을 읽고 쓰는 것, 특히 "fopen"/ "fwrite"및 주로 "file_put_contents"함수에만 관심이 있습니다. "file_put_contents"문서를 보았지만 "LOCK_EX"플래그가 무엇인지 또는 무엇인지에 대한 자세한 설명이나 좋은 설명을 찾지 못했습니다.

  4. 시나리오는 파일 크기가 크고 데이터가 편집되는 방식으로 인해 이러한 문제가 발생할 가능성이 높다고 가정하는 최악의 시나리오의 예입니다. 나는이 문제에 대해 더 배우고 싶어하고 "mysql 사용"또는 "왜 그렇게하고 있습니까?"와 같은 답변이나 의견을 원하지 않거나 필요로하지 않습니다. PHP를 사용하면 올바른 위치 / 문서를 보지 않는 것 같습니다. 그렇습니다 .PHP는 이러한 방식으로 파일 작업에 완벽한 언어가 아니라는 것을 알고 있습니다.


2
PHP를 사용하여 큰 파일을 읽고 쓰는 경험 (1MB는 실제로 크지는 않지만 여전히)이 까다 롭고 느릴 수 있다는 경험을들 수 있습니다. 항상 파일을 잠글 수 있지만 데이터베이스를 사용하는 것이 더 쉽고 안전 할 것입니다.
NullUserException

DB를 사용하는 것이 낫다는 것을 알고 있습니다. 질문을 읽으십시오 (마지막 단락 번호 4)
hozza

2
나는 그 질문을 읽었다. 나는 그것이 좋은 생각이 아니며 더 나은 대안이 있다고 말하고 있습니다.
NullUserException

2
file_put_contents()fopen()/fwrite()춤 의 포장지 일뿐 입니다. LOCKEX전화하는 것과 같습니다 flock($handle, LOCKEX).
yannis

2
@hozza 그래서 답변이 아닌 의견을 게시했습니다.
NullUserException

답변:


4

1) 아니오 3) 아니오

원래 제안 된 접근 방식에는 몇 가지 문제가 있습니다.

첫째, Linux와 같은 일부 UNIX 유사 시스템에는 잠금 지원이 구현되지 않을 수 있습니다. OS는 기본적으로 파일을 잠그지 않습니다. syscall이 NOP (no-operation) 인 것을 보았지만 몇 년 전입니다. 따라서 응용 프로그램 인스턴스에서 설정 한 잠금이 다른 인스턴스에 의해 존중되는지 확인해야합니다. (즉, 2 명의 동시 방문자). 잠금이 여전히 구현되지 않은 경우 (아마도), OS를 통해 해당 파일을 덮어 쓸 수 있습니다.

성능상의 이유로 큰 파일을 한 줄씩 읽는 것은 불가능합니다. file_get_contents ()를 사용하여 전체 파일을 메모리에로드 한 다음 explode ()하여 줄을 가져 오는 것이 좋습니다. 또는 fread ()를 사용하여 파일을 블록 단위로 읽습니다. 목표는 읽기 호출 수를 최소화하는 것입니다.

파일 잠금과 관련하여 :

LOCK_EX는 독점 잠금 (일반적으로 쓰기)을 의미합니다. 주어진 시간에 하나의 프로세스 만 주어진 파일에 대한 독점 잠금을 보유 할 수 있습니다. LOCK_SH는 공유 잠금 (일반적으로 읽기)이며, 둘 이상의 프로세스가 주어진 시간에 지정된 파일에 대한 공유 잠금을 보유 할 수 있습니다. LOCK_UN은 파일을 잠금 해제합니다. file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems 를 사용하는 경우 잠금 해제가 자동으로 수행됩니다 .

우아한 솔루션

PHP는 파일 또는 다른 입력에서 데이터를 처리하기위한 데이터 스트림 필터를 지원합니다. 표준 API를 사용하여 이러한 필터를 올바르게 만들 수 있습니다. http://php.net/manual/en/function.stream-filter-register.php http://php.net/manual/en/filters.php

대체 솔루션 (3 단계) :

  1. 대기열을 만듭니다. 하나의 파일 이름을 처리하는 대신 데이터베이스 또는 다른 메커니즘을 사용하여 보류중인 / 어딘가에 / processed에서 처리 된 고유 한 파일 이름을 저장하십시오. 이렇게하면 아무것도 덮어 쓰지 않습니다. 데이터베이스는 메타 데이터, 신뢰할 수있는 타임 스탬프, 처리 결과 및 기타와 같은 추가 정보를 저장하는 데에도 유용합니다.

  2. 최대 몇 MB 파일의 경우 전체 파일을 메모리로 읽어서 처리하십시오 (file_get_contents () + explode () + foreach ())

  3. 더 큰 파일의 경우 파일을 블록 단위 (예 : 1024 바이트)로 읽고 처리 + 실시간으로 각 블록을 읽습니다 (\ n으로 끝나지 않는 마지막 줄에주의하십시오. 다음 배치에서 처리해야 함).


1
"syscall이 NOP (no-operation) 인 것을 보았습니다 ..."어떤 커널입니까?
Massimo

1
"성능상의 이유로 큰 파일을 한 줄씩 읽는 것은 불가능합니다. 전체 파일을 메모리에로드하기 위해 file_get_contents ()를 사용하는 것이 좋습니다 ..."이것은 의미가 없습니다. 나는 말할 수 있습니다 : 성능상의 이유로 큰 파일을 메모리로 읽지 마십시오 ...해야 할 일은 다른 많은 요인에 달려 있습니다.
Massimo

4

나는 이것이 오래되었다는 것을 알고 있지만 누군가가 이것에 부딪 칠 경우를 대비하여. IMHO 방법은 다음과 같습니다.

1) file_get_contents ( 'original.txt')를 사용하여 원본 파일 (예 : original.txt)을 엽니 다.

2) 변경 / 편집하십시오.

3) file_put_contents ( 'original.txt.tmp')를 사용하여 임시 파일 original.txt.tmp에 씁니다.

4) 그런 다음 tmp 파일을 원본 파일로 옮기고 원본 파일을 바꿉니다. 이를 위해 rename ( 'original.txt.tmp', 'original.txt')을 사용합니다.

장점 : 파일이 처리되고 파일에 기록되는 동안 잠기지 않고 다른 사람들은 여전히 ​​이전 내용을 읽을 수 있습니다. 적어도 Linux / Unix 상자에서 이름 바꾸기는 원자 작업입니다. 파일을 쓰는 동안 중단이 발생해도 원본 파일을 건드리지 않습니다. 파일이 디스크에 완전히 기록 된 후에 만 ​​이동됩니다. http://php.net/manual/en/function.rename.php 에 대한 의견에서 이것에 대한 더 흥미로운 읽기

주석 을 처리하도록 편집 (댓글이 너무 큼) :

/programming/7054844/is-rename-atomic 에는 파일 시스템 전체에서 작업하는 경우 수행해야 할 작업에 대한 추가 참조가 있습니다.

읽기 공유 잠금 에서이 구현에서 파일에 직접 쓰기가 없으므로 왜 이것이 필요한지 잘 모르겠습니다. 잠금을 얻는 데 사용되는 PHP의 무리는 작지만 신뢰할 수 없으며 다른 프로세스에서 무시할 수 있습니다. 그래서 이름 바꾸기를 제안하고 있습니다.

이름 바꾸기 파일은 두 프로세스가 동일한 작업을 수행하지 않도록 이름 바꾸기를 수행하는 프로세스에 고유하게 이름을 지정해야합니다. 그러나 이것은 물론 두 사람 이상이 같은 파일을 동시에 편집하는 것을 막지는 않습니다. 그러나 적어도 파일은 그대로 남아 있습니다 (마지막 편집이 이깁니다).

그러면 3) & 4) 단계는 다음과 같습니다.

$tempfile = uniqid(microtime(true)); // make sure we have a unique name
file_put_contents($tempFile); // write temp file
rename($tempfile, 'original.txt'); // ideally on the same filesystem

정확히 내가 제안하고 싶었던 것. 그러나 데이터 클로버를 방지하기 위해 읽는 동안 공유 잠금을 얻습니다.
marco-a

이름 바꾸기는 다른 디스크가 아닌 동일한 디스크에서 원자 작업입니다.
Xnoise

위해 정말 고유 한 임시 파일 이름을 보장, 당신은 또한 사용할 수 있습니다 원자 파일을 반환 파일 이름을 생성 기능을. tempnam
Matthijs Kooijman

1

file_put_contents () PHP 문서 에서 예제 # 2 에서 LOCK_EX 사용법을 찾을 수 있습니다 .

file_put_contents('somefile.txt', 'some text', LOCK_EX);

LOCK_EX는 와 상수 정수 A의 일부 기능에 사용될 수있는 값보다 비트 .

파일 잠금을 제어하기위한 특정 기능도 있습니다 : flock () 방식.


이것은 흥미롭고 일부 상황에서 파일을 읽고, 수정하고, 다시 쓸 때 유용 할 수 있지만, 잠금은 파일을 읽고 완전히 다시 쓰여질 때까지 유지해야합니다 (그렇지 않으면 다른 프로세스가 이전 사본을 읽고 변경할 수 있음) 프로세스가 끝난 후 다시). 나는 이것이 달성 될 수 있다고 생각하지 않습니다 file_get/put_contents.
Jules

0

언급해야 할 문제는 스크립트의 두 인스턴스가 거의 동시에 실행되는 경쟁 조건입니다 (예 : 다음과 같은 순서).

  1. 스크립트 인스턴스 1 : 파일 읽기
  2. 스크립트 인스턴스 2 : 파일 읽기
  3. 스크립트 인스턴스 1 : 파일에 변경 사항을 씁니다.
  4. 스크립트 인스턴스 2 : 첫 번째 스크립트 인스턴스의 변경 사항을 자체 변경 사항으로 덮어 씁니다 (이 시점에서 읽기가 오래되었으므로).

따라서 큰 파일을 업데이트 할 때는 파일을 읽기 전에 해당 파일을 LOCK_EX해야하며 쓰기가 완료 될 때까지 잠금을 해제하지 않아야합니다. 이 예제에서는 파일에 액세스하기 위해 차례를 기다리는 동안 두 번째 스크립트 인스턴스가 약간 중단 될 것으로 생각하지만 데이터 손실보다 낫습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.