PHP file_put_contents Khóa tệp


9

Senario:

Bạn có một tệp có một chuỗi (giá trị câu trung bình) trên mỗi dòng. Đối với các đối số, hãy cho biết tệp này có kích thước 1Mb (hàng nghìn dòng).

Bạn có một tập lệnh đọc tệp, thay đổi một số chuỗi trong tài liệu (không chỉ nối thêm mà còn xóa và sửa đổi một số dòng) và sau đó ghi đè tất cả dữ liệu bằng dữ liệu mới.

Các câu hỏi:

  1. Liệu 'máy chủ' PHP, HĐH hoặc httpd, v.v. đã có sẵn các hệ thống để ngăn chặn các vấn đề như thế này (đọc / viết nửa chừng khi viết) chưa?

  2. Nếu có, vui lòng giải thích cách thức hoạt động và đưa ra ví dụ hoặc liên kết đến tài liệu liên quan.

  3. Nếu không, có những thứ tôi có thể kích hoạt hoặc thiết lập, chẳng hạn như khóa một tệp cho đến khi hoàn thành ghi và làm cho tất cả các lần đọc và / hoặc ghi khác bị lỗi cho đến khi tập lệnh trước viết xong?

Giả định của tôi và thông tin khác:

  1. Máy chủ được đề cập đang chạy PHP và Apache hoặc Lighttpd.

  2. Nếu tập lệnh được gọi bởi một người dùng và đang viết nửa chừng cho tệp và một người dùng khác sẽ đọc tệp vào đúng thời điểm đó. Người dùng đọc nó sẽ không nhận được tài liệu đầy đủ, vì nó chưa được viết. (Nếu giả định này là sai xin vui lòng sửa cho tôi)

  3. Tôi chỉ quan tâm đến việc viết và đọc PHP vào một tệp văn bản, và đặc biệt, các hàm "fopen" / "fwrite" và chủ yếu là "file_put_contents". Tôi đã xem tài liệu "file_put_contents" nhưng không tìm thấy mức độ chi tiết hoặc giải thích tốt về cờ "LOCK_EX" là gì hoặc làm gì.

  4. Kịch bản là một ví dụ về trường hợp xấu nhất mà tôi cho rằng những vấn đề này có nhiều khả năng xảy ra, do kích thước lớn của tệp và cách chỉnh sửa dữ liệu. Tôi muốn tìm hiểu thêm về các vấn đề này và không muốn hoặc không cần câu trả lời hoặc nhận xét như "sử dụng mysql" hoặc "tại sao bạn lại làm như vậy" bởi vì tôi không làm điều đó, tôi chỉ muốn tìm hiểu về đọc / ghi tệp với PHP và dường như không tìm kiếm đúng chỗ / tài liệu và vâng tôi hiểu PHP không phải là ngôn ngữ hoàn hảo để làm việc với các tệp theo cách này.


2
Tôi có thể nói với bạn từ kinh nghiệm rằng việc đọc và ghi vào các tệp lớn bằng PHP (1 MB không thực sự lớn như vậy, nhưng vẫn có thể khó khăn (và chậm). Bạn luôn có thể khóa tệp, nhưng có lẽ sẽ dễ dàng và an toàn hơn nếu chỉ sử dụng cơ sở dữ liệu.
NullUserException

Tôi biết sẽ tốt hơn nếu sử dụng DB. Vui lòng đọc câu hỏi (đoạn cuối số 4)
hozza

2
Tôi đã đọc câu hỏi; Tôi đang nói rằng đó không phải là một ý tưởng tuyệt vời và có những lựa chọn thay thế tốt hơn.
NullUserException

2
file_put_contents()chỉ là một trình bao bọc cho fopen()/fwrite()điệu nhảy, LOCKEXthực hiện giống như khi bạn gọi flock($handle, LOCKEX).
yannis

2
@hozza Đó là lý do tại sao tôi đăng bình luận, không phải là câu trả lời.
NullUserException

Câu trả lời:


4

1) Không 3) Không

Có một số vấn đề với cách tiếp cận được đề xuất ban đầu:

Thứ nhất, một số hệ thống giống UNIX như Linux có thể không hỗ trợ khóa được triển khai. Hệ điều hành không khóa các tập tin theo mặc định. Tôi đã thấy các tòa nhà chọc trời là NOP (không hoạt động), nhưng đó là một vài năm trước, vì vậy bạn cần xác minh xem một khóa được thiết lập bởi ứng dụng của bạn có được tôn trọng bởi một ví dụ khác hay không. (tức là 2 khách truy cập đồng thời). Nếu khóa vẫn chưa được thực hiện [rất có thể là vậy], HĐH cho phép bạn ghi đè lên tệp đó.

Đọc từng tệp lớn từng dòng là không khả thi vì lý do hiệu suất. Tôi đề nghị sử dụng file_get_contents () để tải toàn bộ tệp vào bộ nhớ và sau đó phát nổ () nó để lấy các dòng. Hoặc, sử dụng fread () để đọc tệp theo khối. Mục đích là để giảm thiểu số lượng cuộc gọi đọc.

Liên quan đến khóa tập tin:

LOCK_EX có nghĩa là một khóa độc quyền (thường để viết). Chỉ một quá trình có thể giữ một khóa độc quyền cho một tệp nhất định tại một thời điểm nhất định. LOCK_SH là một khóa được chia sẻ (thường để đọc), Nhiều hơn một quá trình có thể giữ một khóa chung cho một tệp nhất định tại một thời điểm nhất định. LOCK_UN mở khóa tệp. Việc mở khóa được thực hiện tự động trong trường hợp bạn sử dụng file_get_contents () http://en.wikipedia.org/wiki/File_locking#In_Unix-like_systems

Giải pháp thanh lịch

PHP hỗ trợ các bộ lọc luồng dữ liệu nhằm xử lý dữ liệu trong các tệp hoặc từ các đầu vào khác. Bạn có thể muốn tạo một bộ lọc như vậy đúng cách bằng API tiêu chuẩn. http://php.net/manual/en/feft.stream-filter-register.php http://php.net/manual/en/filters.php

Giải pháp thay thế (trong 3 bước):

  1. Tạo một hàng đợi. Thay vì xử lý một tên tệp, hãy sử dụng cơ sở dữ liệu hoặc cơ chế khác để lưu trữ tên tệp duy nhất ở đâu đó trong trạng thái chờ xử lý / và được xử lý trong / xử lý. Cách này không có gì bị ghi đè. Cơ sở dữ liệu cũng sẽ hữu ích để lưu trữ thông tin bổ sung, chẳng hạn như siêu dữ liệu, dấu thời gian đáng tin cậy, kết quả xử lý và khác.

  2. Đối với các tệp có dung lượng tối đa vài MB, hãy đọc toàn bộ tệp vào bộ nhớ và sau đó xử lý tệp đó (file_get_contents () + explode () + foreach ())

  3. Đối với các tệp lớn hơn, hãy đọc tệp theo khối (ví dụ 1024 byte) và xử lý + ghi theo thời gian thực từng khối khi đọc (cẩn thận về dòng cuối cùng không kết thúc bằng \ n. Nó cần được xử lý trong đợt tiếp theo)


1
"Tôi đã thấy các tòa nhà chọc trời là NOP (không hoạt động) ..." hạt nhân nào?
Massimo

1
"Đọc từng tệp lớn từng dòng là không khả thi vì lý do hiệu suất. Tôi khuyên bạn nên sử dụng file_get_contents () để tải toàn bộ tệp vào bộ nhớ ..." Đây là điều không hợp lý. Tôi có thể nói: vì lý do hiệu suất, đừng đọc các tệp lớn vào bộ nhớ ... Việc cần làm phụ thuộc vào nhiều yếu tố khác.
Massimo

4

Tôi biết điều này đã cũ, nhưng trong trường hợp ai đó gặp phải điều này. IMHO cách để đi về nó là như thế này:

1) Mở tệp gốc (ví dụ: tệp gốc) bằng tệp_get_contents ('original.txt').

2) Thực hiện các thay đổi / chỉnh sửa của bạn.

3) Sử dụng file_put_contents ('original.txt.tmp') và ghi nó vào tệp tạm thời original.txt.tmp.

4) Sau đó di chuyển tệp tmp sang tệp gốc, thay thế tệp gốc. Đối với điều này, bạn sử dụng đổi tên ('original.txt.tmp', 'original.txt').

Ưu điểm: Mặc dù tệp đang được xử lý và ghi vào tệp không bị khóa và những người khác vẫn có thể đọc nội dung cũ. Ít nhất trên các hộp Linux / Unix đổi tên là một hoạt động nguyên tử. Bất kỳ gián đoạn nào trong quá trình viết tệp không chạm vào tệp gốc. Chỉ khi tệp đã được ghi đầy đủ vào đĩa thì nó mới được di chuyển. Thú vị hơn đọc về điều này trong các ý kiến ​​để http://php.net/manual/en/feft.rename.php

Chỉnh sửa để giải quyết các cam kết (quá bình luận):

/programming/7054844/is-rename-atomic có thêm tài liệu tham khảo về những gì bạn có thể cần làm nếu bạn đang vận hành trên các hệ thống tệp.

Trên khóa chia sẻ cho việc đọc tôi không chắc tại sao điều đó lại cần thiết vì trong triển khai này không có ghi trực tiếp vào tệp. Đàn của PHP (được sử dụng để lấy khóa) là một ít nhưng không đáng tin cậy và có thể bị bỏ qua bởi các quy trình khác. Đó là lý do tại sao tôi đề nghị sử dụng đổi tên.

Tệp đổi tên lý tưởng nên được đặt tên duy nhất cho quá trình thực hiện đổi tên để đảm bảo không có 2 quy trình làm điều tương tự. Nhưng điều này tất nhiên không ngăn cản việc chỉnh sửa cùng một tệp bởi nhiều người cùng một lúc. Nhưng ít nhất tập tin sẽ được giữ nguyên (lần chỉnh sửa cuối cùng sẽ thắng).

Bước 3) & 4) sau đó sẽ trở thành thế này:

$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

Chính xác những gì tôi muốn đề xuất là tốt. Nhưng tôi cũng sẽ có được một khóa chia sẻ trong khi đọc để ngăn chặn dữ liệu bị ghi đè.
d3L

Đổi tên là một hoạt động nguyên tử trên cùng một đĩa, không phải trên các đĩa khác nhau.
Xnoise

Để thực sự đảm bảo một tên tempfile duy nhất, bạn cũng có thể sử dụng cáctempnam hàm, nguyên tử tạo một tệp và trả về tên tệp.
Matthijs Kooijman

1

Trong tài liệu PHP cho file_put_contents () bạn có thể tìm thấy trong ví dụ # 2 cách sử dụng cho LOCK_EX , chỉ cần đặt:

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

Các LOCK_EX là một hằng số với một số nguyên giá trị hơn có thể được sử dụng trên một số chức năng trong một Bitwise .

Ngoài ra còn có một chức năng cụ thể để kiểm soát khóa cho các tệp: cách flock () .


Mặc dù điều này rất thú vị và có thể hữu ích trong một số trường hợp, khi đọc, sửa đổi và viết lại một tệp, khóa phải được lấy trước khi bạn đọc và duy trì cho đến khi nó được viết lại hoàn toàn (nếu không thì quá trình khác có thể đọc một bản sao cũ và thay đổi nó trở lại sau khi quá trình của bạn kết thúc). Tôi không tin rằng điều này có thể đạt được với file_get/put_contents.
Jules

0

Một vấn đề bạn không đề cập đến là bạn cũng cần cẩn thận là các điều kiện chủng tộc trong đó hai trường hợp tập lệnh của bạn đang chạy gần như cùng một lúc, ví dụ như thứ tự xuất hiện này:

  1. Script phiên bản 1: Đọc tệp
  2. Script dụ 2: Đọc tệp
  3. Script dụ 1: Viết các thay đổi vào tập tin
  4. Kịch bản tập lệnh 2: Ghi đè các thay đổi của tập lệnh đầu tiên vào tập tin bằng các thay đổi của chính nó (vì tại thời điểm này, phần đọc của nó đã trở nên cũ).

Vì vậy, khi cập nhật một tệp lớn, bạn cần LOCK_EX tệp đó trước khi bạn đọc và không giải phóng khóa cho đến khi ghi được thực hiện. Trong ví dụ này, tôi tin rằng điều đó sẽ khiến phiên bản script thứ hai bị treo trong một chút trong khi nó chờ đến lượt để truy cập tệp, nhưng điều này tốt hơn là mất dữ liệu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.