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. สถานการณ์เป็นตัวอย่างของสถานการณ์กรณีที่เลวร้ายที่สุดที่ฉันจะถือว่าปัญหาเหล่านี้มีแนวโน้มที่จะเกิดขึ้นเนื่องจากไฟล์มีขนาดใหญ่และวิธีการแก้ไขข้อมูล ฉันต้องการที่จะเรียนรู้เพิ่มเติมเกี่ยวกับปัญหาเหล ด้วย PHP และดูเหมือนจะไม่ถูกมองในสถานที่ที่เหมาะสม / เอกสารและใช่ฉันเข้าใจ PHP ไม่ใช่ภาษาที่สมบูรณ์แบบสำหรับการทำงานกับไฟล์ด้วยวิธีนี้


2
ฉันสามารถบอกคุณได้จากประสบการณ์ว่าการอ่านและการเขียนไปยังไฟล์ขนาดใหญ่ด้วย PHP (1 MB ไม่ได้มีขนาดใหญ่มาก แต่ก็ยังคงเป็นเรื่องยาก) คุณสามารถล็อคไฟล์ได้ตลอดเวลา แต่อาจจะง่ายกว่าและปลอดภัยกว่าเพียงแค่ใช้ฐานข้อมูล
NullUserException

ฉันรู้ว่ามันจะดีกว่าถ้าใช้ DB โปรดอ่านคำถาม (วรรคสุดท้าย 4)
hozza

2
ฉันอ่านคำถาม ฉันกำลังบอกว่ามันไม่ใช่ความคิดที่ดีและมีทางเลือกที่ดีกว่า
NullUserException

2
file_put_contents()เป็นเพียงเสื้อคลุมสำหรับการfopen()/fwrite()เต้นไม่เหมือนกันเช่นถ้าคุณต้องการโทรLOCKEX flock($handle, LOCKEX)
yannis

2
@hozza นั่นเป็นเหตุผลที่ฉันโพสต์ความคิดเห็นไม่ใช่คำตอบ
NullUserException

คำตอบ:


4

1) ไม่ 3) ไม่

มีปัญหาหลายประการเกี่ยวกับวิธีที่แนะนำดั้งเดิม:

ประการแรกระบบที่คล้าย UNIX บางระบบเช่น Linux อาจไม่มีการสนับสนุนการล็อค ระบบปฏิบัติการไม่ล็อคไฟล์ตามค่าเริ่มต้น ฉันเห็นว่า syscalls เป็น NOP (ไม่ได้ใช้งาน) แต่นั่นเป็นเวลาไม่กี่ปีที่ผ่านมาดังนั้นคุณต้องตรวจสอบว่าล็อคที่กำหนดโดยอินสแตนซ์ของแอปพลิเคชันของคุณเป็นที่เคารพนับถือจากอินสแตนซ์อื่นหรือไม่ (นั่นคือผู้เยี่ยมชมพร้อมกัน 2 คน) หากการล็อคยังไม่ได้ดำเนินการ [เป็นไปได้มาก] ระบบปฏิบัติการจะให้คุณเขียนทับไฟล์นั้น

การอ่านไฟล์ขนาดใหญ่ทีละบรรทัดนั้นไม่สามารถทำได้เนื่องจากเหตุผลด้านประสิทธิภาพ ฉันขอแนะนำให้ใช้ file_get_contents () เพื่อโหลดไฟล์ทั้งหมดลงในหน่วยความจำแล้วจึงระเบิด () เพื่อให้ได้เส้น หรือใช้ 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. สร้างคิว แทนที่จะประมวลผลชื่อไฟล์เดียวให้ใช้ฐานข้อมูลหรือกลไกอื่น ๆ เพื่อจัดเก็บชื่อไฟล์ที่ไม่ซ้ำที่ใดที่หนึ่งในระหว่างดำเนินการ / และประมวลผลใน / ประมวลผล วิธีนี้ไม่มีอะไรถูกเขียนทับ ฐานข้อมูลจะมีประโยชน์สำหรับการจัดเก็บข้อมูลเพิ่มเติมเช่นข้อมูลเมตาเวลาประทับที่เชื่อถือได้ผลการประมวลผลและอื่น ๆ

  2. สำหรับไฟล์ที่มีขนาดไม่เกิน MB ให้อ่านไฟล์ทั้งหมดในหน่วยความจำแล้วประมวลผล (file_get_contents () + explode () + foreach ())

  3. สำหรับไฟล์ขนาดใหญ่ให้อ่านไฟล์ในบล็อก (เช่น 1024 ไบต์) และประมวลผล + เขียนแบบเรียลไทม์แต่ละบล็อกเป็นการอ่าน (ระวังเกี่ยวกับบรรทัดสุดท้ายที่ไม่ได้ลงท้ายด้วย \ n ต้องประมวลผลในชุดถัดไป)


1
"ฉันเคยเห็นตึกระฟ้าที่เป็น NOP (ไม่ทำงาน) ... " เคอร์เนลตัวไหน
Massimo

1
"การอ่านไฟล์ขนาดใหญ่ทีละบรรทัดไม่เป็นไปได้ด้วยเหตุผลด้านประสิทธิภาพฉันแนะนำให้ใช้ file_get_contents () เพื่อโหลดไฟล์ทั้งหมดลงในหน่วยความจำ ... " นี่มันไม่สมเหตุสมผล ฉันสามารถพูดได้: สำหรับเหตุผลด้านประสิทธิภาพอย่าอ่านไฟล์ขนาดใหญ่ในหน่วยความจำ ... สิ่งที่ต้องทำขึ้นอยู่กับปัจจัยอื่น ๆ
Massimo

4

ฉันรู้ว่านี่เป็นวัย แต่ในกรณีที่มีคนวิ่งเข้ามา วิธีที่จะไปเกี่ยวกับมันเป็นเช่นนี้:

1) เปิดไฟล์ต้นฉบับ (เช่น original.txt) โดยใช้ file_get_contents ('original.txt')

2) ทำการเปลี่ยนแปลง / แก้ไขของคุณ

3) ใช้ file_put_contents ('original.txt.tmp') และเขียนลงในไฟล์ temp original.txt.tmp

4) จากนั้นย้ายไฟล์ tmp ไปยังไฟล์ต้นฉบับแทนที่ไฟล์ต้นฉบับ สำหรับสิ่งนี้คุณใช้การเปลี่ยนชื่อ ('original.txt.tmp', 'original.txt')

ข้อดี: ในขณะที่ไฟล์กำลังถูกประมวลผลและเขียนลงในไฟล์จะไม่ถูกล็อคและอื่น ๆ ยังสามารถอ่านเนื้อหาเก่าได้ อย่างน้อยใน Linux / Unix กล่องเปลี่ยนชื่อคือการดำเนินการของอะตอม การขัดจังหวะระหว่างการเขียนไฟล์ไม่ได้สัมผัสไฟล์ต้นฉบับ เมื่อไฟล์ถูกเขียนลงดิสก์อย่างสมบูรณ์แล้วเท่านั้นที่จะถูกย้าย อ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ในความคิดเห็นที่http://php.net/manual/th/function.rename.php

แก้ไขที่อยู่ที่มอบหมาย (สำหรับความคิดเห็นด้วย):

/programming/7054844/is-rename-atomicมีการอ้างอิงเพิ่มเติมถึงสิ่งที่คุณอาจต้องทำหากคุณทำงานข้ามระบบไฟล์

ในการล็อคที่ใช้ร่วมกันสำหรับการอ่านฉันไม่แน่ใจว่าทำไมจึงจำเป็นต้องใช้ในการดำเนินการนี้ไม่มีการเขียนไปยังไฟล์โดยตรง ฝูง PHP (ซึ่งใช้ในการรับล็อค) เป็นเพียงเล็กน้อย แต่ไม่น่าเชื่อถือและสามารถละเว้นโดยกระบวนการอื่น ๆ นั่นเป็นเหตุผลที่ฉันแนะนำให้ใช้การเปลี่ยนชื่อ

ไฟล์เปลี่ยนชื่อควรตั้งชื่อโดยไม่ซ้ำกับกระบวนการที่ทำการเปลี่ยนชื่อเพื่อให้แน่ใจว่ากระบวนการที่ 2 ไม่ทำสิ่งเดียวกัน แต่หลักสูตรนี้ไม่ได้ป้องกันการแก้ไขไฟล์เดียวกันโดยมากกว่าหนึ่งคนในเวลาเดียวกัน แต่อย่างน้อยไฟล์นั้นจะยังคงอยู่เหมือนเดิม (แก้ไขล่าสุดชนะ)

ขั้นตอนที่ 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

หากต้องการจริงๆรับประกันชื่อ tempfile ไม่ซ้ำกันคุณยังสามารถใช้ฟังก์ชั่นซึ่งอะตอมสร้างไฟล์และผลตอบแทนที่ชื่อไฟล์ tempnam
Matthijs Kooijman

1

ในเอกสารประกอบ PHP สำหรับfile_put_contents ()คุณสามารถค้นหาตัวอย่างการใช้งานLOCK_EXได้ใน# 2โดยใส่เพียง:

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

LOCK_EXเป็นค่าคงที่ที่มีจำนวนเต็มมูลค่ากว่าสามารถนำมาใช้ในการทำงานบางอย่างในค่าที่เหมาะสม

นอกจากนี้ยังมีฟังก์ชั่นเฉพาะเพื่อควบคุมการล็อคไฟล์: ลักษณะฝูง ()


ในขณะที่สิ่งนี้น่าสนใจและอาจมีประโยชน์ในบางสถานการณ์เมื่ออ่านแก้ไขและเขียนไฟล์ใหม่ควรทำการล็อคก่อนที่คุณจะอ่านและเก็บรักษาไว้จนกว่าจะถูกเขียนใหม่ทั้งหมด (มิฉะนั้นกระบวนการอื่นอาจอ่านสำเนาเก่าและเปลี่ยนมัน กลับมาหลังจากกระบวนการของคุณเสร็จสิ้น) file_get/put_contentsผมไม่เชื่อว่านี้สามารถทำได้ด้วย
จูลส์

0

ปัญหาที่คุณไม่ได้กล่าวถึงว่าคุณต้องระวังคือสภาวะการแข่งขันที่สคริปต์ของคุณทำงานสองอินสแตนซ์ในเวลาใกล้เคียงกันตัวอย่างเช่นลำดับเหตุการณ์นี้:

  1. สคริปต์อินสแตนซ์ 1: อ่านไฟล์
  2. สคริปต์อินสแตนซ์ 2: อ่านไฟล์
  3. สคริปต์อินสแตนซ์ 1: เขียนการเปลี่ยนแปลงไฟล์
  4. สคริปต์อินสแตนซ์ 2: เขียนทับการเปลี่ยนแปลงของไฟล์แรกของอินสแตนซ์สคริปต์ด้วยการเปลี่ยนแปลงของตัวเอง (เนื่องจาก ณ จุดนี้การอ่านเริ่มเก่าแล้ว)

ดังนั้นเมื่ออัปเดตไฟล์ขนาดใหญ่คุณต้อง LOCK_EX ไฟล์นั้นก่อนที่คุณจะอ่านและไม่ปล่อยการล็อกจนกว่าจะมีการเขียน ในตัวอย่างนี้ฉันเชื่อว่าจะทำให้อินสแตนซ์สคริปต์ตัวที่สองหยุดสักครู่ขณะที่รอการเข้าถึงไฟล์ แต่สิ่งนี้ดีกว่าข้อมูลที่หายไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.