2010-02-18 27 views
27

Tôi biết câu hỏi này đã được hỏi ở đây một vài lần, nhưng không có câu trả lời nào phù hợp với tôi. Điều này là bởi vì hầu như tất cả chúng liên quan đến một quá trình đọc/ghi lớn liên quan đến cơ sở dữ liệu, mà tôi muốn tránh bằng mọi giá.Diễn đàn PHP - cách đối phó với các cuộc thảo luận chưa đọc/chủ đề/bài đăng

Giới thiệu về thảo luận chưa đọc/chủ đề/bài đăng, có rất nhiều điều để suy nghĩ. Tôi không biết các hệ thống diễn đàn như thế nào như MyBB, vBulletin, Invision Power Board, Vanilla, phpBB, v.v., đối phó với vấn đề đó, vì vậy tôi muốn đọc từ các bạn kinh nghiệm của bạn về điều đó. Tôi biết rằng việc sử dụng một bảng cơ sở dữ liệu chỉ vì đó là cách đơn giản nhất, nhưng điều đó sẽ liên quan đến việc đọc/ghi lớn khi cộng đồng có hơn 10.000 thành viên và 1000 chủ đề mới mỗi tháng. Thật khó, nhưng phải có một cách để tránh quá tải của máy chủ.

Vì vậy, bạn thấy điều gì là thực tiễn tốt nhất cho vấn đề này, cũng như cách các hệ thống diễn đàn khác đối phó với vấn đề này?

Trả lời

15

Không có nhiều lựa chọn.

  1. đánh dấu từng chuỗi đọc của từng người dùng.

    • Nhược điểm: rất nhiều hàng trên các diễn đàn rất tích cực
    • Ưu điểm: Mỗi người sử dụng biết với bài đã đọc hay không.
  2. đánh dấu từng chuỗi chưa đọc của từng người dùng.

    • Nhược điểm: rất nhiều không gian với hàng "unreaded" nếu có không hoạt động của rất nhiều người dùng
    • Giải pháp: thêm một dấu thời gian cuộc đời và xóa các bản ghi cũ với một cron
    • Ưu điểm: Mỗi người sử dụng biết với bài viết đã đọc hay chưa.
  3. sử dụng dấu thời gian để xác định xem có hiển thị thời gian chưa đọc hay không.

    • Nhược điểm: Người sử dụng không biết có là những chủ đề thực chưa đọc, các dấu chỉ hiển thị các "trheads mới" kể từ lần đăng nhập cuối cùng
    • Ưu điểm: Tiết kiệm không gian

Cách khác là trộn các giải pháp, nghĩa là,

1 và 3) hiển thị chuỗi là "chưa đọc" nếu chúng không lớn hơn X ngày và không có hàng được đánh dấu là đã đọc cho người dùng. Các hàng "đọc" có thể bị xóa khi chúng lớn hơn X ngày mà không ảnh hưởng gì cả.

Ưu

  • ít cách nhau dùng để xác định chủ đề chưa đọc

Nhược

  • tạo một cron mà giữ hệ thống sạch
  • Người dùng không biết nếu họ đọc chủ đề cũ hơn x ngày.

Ưu

  • Mỗi người sử dụng biết mà "bài viết mới" đã đọc hay không.
+0

Tôi vẫn nghĩ rằng phải có một cách dễ dàng hơn để làm điều đó. Tôi nghĩ về việc sử dụng MemCache, nhưng nó dựa vào bộ nhớ, và tôi vẫn đang nghĩ về APC. Nếu tôi có thể có một số loại tập tin bộ nhớ cache để làm việc với, có lẽ sẽ giúp đỡ. – yoda

1

Tại sao bạn quan tâm?

Tôi không thấy sự cố với bất kỳ I/O nào để nhận chuỗi chưa đọc. Nó không phải sống. Độ trễ 15 phút dựa trên giá trị bộ nhớ cache sẽ hoạt động.

Vì vậy, đối đề chưa đọc bạn chỉ

Pseudo mã ..

$result = SELECT id,viewcount from my_forum_threads 

$cache->setThreads($result['id'],$result['viewcount']); 

Sau đó, trong một lần tải trang web mà bạn chỉ nhận được những giá trị bộ nhớ cache chứ không phải là truy vấn cơ sở dữ liệu một lần nữa. Nó thực sự không phải là một vấn đề lớn chút nào.

Trang trung bình trên trang web của tôi có 20 truy vấn mysql. Khi tôi cache nó chỉ là hai đến bốn truy vấn.

+0

Chủ đề của từng người dùng được xem/mở là gì? Những gì bạn đề nghị dường như ngụ ý rằng tôi lưu trữ mọi thông tin người dùng ... – yoda

+0

Bạn tải nó một lần khi phiên bắt đầu hoặc sau một khoảng thời gian nhất định đã trôi qua. Bộ nhớ cache nằm trong phiên, không phải là bộ nhớ cache vĩnh viễn lớn. – jmucchiello

1

Hầu hết mọi diễn đàn mà tôi biết sẽ sử dụng một số loại dấu thời gian tham chiếu để xác định xem một chuỗi/thư có nên được coi là "chưa đọc" hay không. Dấu thời gian này thường là ngày/giờ của hành động cuối cùng mà bạn đã thực hiện trong lần truy cập trước đó của mình vào diễn đàn.

Vì vậy, bạn giữ nguyên. một last_last_action & dấu thời gian last_action trong bảng người dùng của bạn, last_action được cập nhật trên mọi hành động của người dùng, cột previous_last_action được đặt một lần thành last_action khi đăng nhập (hoặc khi tạo phiên mới - nếu bạn có chức năng "nhớ tôi"). Để xác định xem một chuỗi/thư chưa đọc, bạn sẽ so sánh dấu thời gian tạo chuỗi (hoặc cập nhật) đó với giá trị trong previous_last_action cho người dùng hiện đang đăng nhập.

+0

Phần mềm diễn đàn tốt hơn sẽ cung cấp cho bạn tối thiểu dữ liệu đọc PER diễn đàn. Phần mềm diễn đàn cực đoan thực sự theo dõi những gì bạn đã đọc. – jmucchiello

+0

Không có gì mới, xin lỗi. Thing là, hoạt động của người dùng cuối sẽ làm lại diễn đàn lỗ, không phải mỗi luồng, có nghĩa là để làm việc đúng cách tôi vẫn cần phải theo dõi dấu thời gian liên quan đến mỗi luồng. – yoda

+4

Diễn đàn lỗ là gì? Đó có phải là '/ dev/null' không? –

8

Có ... khác.

Một cách khác để lưu trữ dữ liệu đọc/chưa đọc chi tiết cho cấu trúc diễn đàn phân cấp (bảng> phần> chuỗi, v.v.). Nó làm như vậy mà không có a) phải điền trước thông tin đã đọc/chưa đọc và b) mà không cần lưu trữ nhiều hơn các hàng U * (M/2) trong trường hợp xấu nhất của nó, trong đó U là số người dùng và M là tổng số bài đăng trong cơ sở dữ liệu (và thường là nhiều, ít hơn nhiều so với điều này)

Tôi đã nghiên cứu chủ đề này cách đây không lâu. Tôi thấy SMF/phpBB "lừa gạt" một chút trong cách lưu trữ lịch sử đọc của người dùng.Lược đồ của họ hỗ trợ lưu trữ dấu thời gian hoặc ID thư cuối cùng được đánh dấu là đã đọc trong bảng, diễn đàn, thư mục con, chủ đề (hoặc được xem trực tiếp bởi trình duyệt), như sau:

[user_id, board, last_msg_id, last_timestamp]

[user_id, hội đồng quản trị, diễn đàn, last_msg_id, last_timestamp]

[user_id, hội đồng quản trị, diễn đàn, subforum, last_msg_id, last_timestamp]

[user_id, hội đồng quản trị, diễn đàn, subforum, chủ đề, last_msg_id , last_timestamp]

Điều này cho phép người dùng đánh dấu các bảng, diễn đàn, chủ đề cụ thể, v.v., là "đã đọc". Nó đòi hỏi, tuy nhiên, hoặc là hành động trên một phần của người dùng (hoặc bằng cách đọc, hoặc tích cực bấm vào "đánh dấu là đã đọc"), và trong trường hợp của phpBB, không cung cấp cho bạn mức độ chi tiết để nói "Tôi đã thấy điều này cụ thể nhưng không phải là thông điệp cụ thể đó. " Bạn cũng có được tình huống mà bạn đọc tin nhắn cuối cùng trong một chủ đề đầu tiên (xem hoạt động mới nhất trong một chủ đề), và bạn ngay lập tức giả định đã đọc phần còn lại của chủ đề.

Nó hoạt động cho SMF và phpBB để lưu trữ những thứ như thế này vì hiếm khi bạn chỉ xem một bài đăng (chế độ xem mặc định được thiết lập cho hơn 20 bài đăng ở trang cuối cùng của chủ đề). Tuy nhiên, đối với các diễn đàn có nhiều luồng hơn (đặc biệt là các diễn đàn mà bạn đang xem thư một lúc), điều này ít hơn lý tưởng. Người dùng hệ thống này có thể sẽ chăm sóc rất nhiều nếu họ đã đọc một tin nhắn nhưng không phải là một thư khác, và có thể xem xét nó cồng kềnh để chỉ có thể đánh dấu toàn bộ phần là đã đọc, khi thực sự họ chỉ muốn một số được đánh dấu là đã đọc.

Bạn lưu trữ tin nhắn trong các bộ như thế này: [user_id, lower_msg_id, upper_msg_id]

Nhật ký lịch sử sử dụng được duy trì như sau:

Sau khi xem trang, một chức năng trông để xem nếu user_id có bản ghi nơi current_msg_id nằm giữa lower_msg_id và upper_msg_id. Nếu có, thì trang này được đọc và không cần thực hiện hành động nào. Nếu không, thì truy vấn khác phải được phát hành, lần này xác định nếu current_msg_id thấp hơn lower_msg_id (current_msg_id == lower_msg_id-1) hoặc nhiều hơn upper_msg_id (current_msg_id == upper_msg_id +1). Đây là trường hợp chúng ta phát triển ranh giới "đọc" hoặc "nhìn thấy" của chúng tôi bằng 1. Nếu chúng ta là một trong những từ xa_msg_id hoặc uppper_msg_id, thì chúng ta sẽ tăng tuple lên 1 theo hướng đó. Nếu chúng ta không tăng phạm vi tuple, thì chúng ta chèn một tuple mới, [user_id, current_msg_id, current_msg_id].

Trường hợp góc là khi hai dãy tuple tiếp cận lẫn nhau. Trong trường hợp này, khi tìm kiếm giữa ranh giới tuple thấp hơn và ranh giới trên tuple, hợp nhất hai ranh giới bằng cách thiết lập ranh giới trên của tuple thấp hơn đến ranh giới trên của tuple trên, và xóa tuple trên.

Mã ví dụ trong PHP:

function seen_bounds($usr_id, $msg_id) { 

    # mysql escape 
    $usr_id = mres($usr_id); 
    $msg_id = mres($msg_id); 

    $seen_query = " 
     SELECT 
      msb.id, 
      msb.lower_msg_id, 
      msb.upper_msg_id 
     FROM 
      msgs_seen_bounds msb 
     WHERE 
      $msg_id BETWEEN msb.lower_msg_id AND msb.upper_msg_id AND 
      msb.usr_id = $usr_id 
     LIMIT 1; 
    "; 

    # See if this post already exists within a given 
    # seen bound. 
    $seen_row = query($seen_query, ROW); 

    if($seen_row == 0) { 
     # Has not been seen, try to detect if we're "near" 
     # another bound (and we can grow that bound to include 
     # this post). 
     $lower_query = " 
      SELECT 
       msb.id, 
       msb.lower_msg_id, 
       msb.upper_msg_id 
      FROM 
       msgs_seen_bounds msb 
      WHERE 
       msb.upper_msg_id = ($msg_id - 1) AND 
       msb.usr_id = $usr_id 
      LIMIT 1; 
     "; 

     $upper_query = " 
      SELECT 
       msb.id, 
       msb.lower_msg_id, 
       msb.upper_msg_id 
      FROM 
       msgs_seen_bounds msb 
      WHERE 
       msb.lower_msg_id = ($msg_id + 1) AND 
       msb.usr_id = $usr_id 
      LIMIT 1; 
     "; 

     $lower = query($lower_query, ROW); 
     $upper = query($upper_query, ROW); 

     if($lower == 0 && $upper == 0) { 
      # No bounds exist for or near this. We'll insert a single-ID 
      # bound 

      $saw_query = " 
       INSERT INTO 
        msgs_seen_bounds 
       (usr_id, lower_msg_id, upper_msg_id) 
       VALUES 
       ($usr_id, $msg_id, $msg_id) 
       ; 
      "; 

      query($saw_query, NONE); 
     } else { 
      if($lower != 0 && $upper != 0) { 
       # Found "near" bounds both on the upper 
       # and lower bounds. 

       $update_query = ' 
        UPDATE msgs_seen_bounds 
        SET 
         upper_msg_id = ' . $upper['upper_msg_id'] . ' 
        WHERE 
         msgs_seen_bounds.id = ' . $lower['id'] . ' 
        ; 
       '; 

       $delete_query = ' 
        DELETE FROM msgs_seen_bounds 
        WHERE 
         msgs_seen_bounds.id = ' . $upper['id'] . ' 
        ; 
       '; 

       query($update_query, NONE); 
       query($delete_query, NONE); 
      } else { 
       if($lower != 0) { 
        # Only found lower bound, update accordingly. 
        $update_query = ' 
         UPDATE msgs_seen_bounds 
         SET 
          upper_msg_id = ' . $msg_id . ' 
         WHERE 
          msgs_seen_bounds.id = ' . $lower['id'] . ' 
         ; 
        '; 

        query($update_query, NONE); 
       } 

       if($upper != 0) { 
        # Only found upper bound, update accordingly. 
        $update_query = ' 
         UPDATE msgs_seen_bounds 
         SET 
          lower_msg_id = ' . $msg_id . ' 
         WHERE 
          msgs_seen_bounds.id = ' . $upper['id'] . ' 
         ; 
        '; 

        query($update_query, NONE); 
       } 
      } 
     } 
    } else { 
     # Do nothing, already seen. 
    } 

} 

Đang tìm kiếm các bài viết chưa đọc là tìm nơi current_msg_id không tồn tại giữa bất kỳ lower_msg_id và upper_msg_id cho một người dùng nhất định (NOT EXISTS truy vấn về SQL). Nó không phải là hiệu quả nhất của các truy vấn khi thực hiện trong một cơ sở dữ liệu quan hệ, nhưng có thể được giải quyết bằng cách lập chỉ mục tích cực. Ví dụ, sau đây là một truy vấn SQL để đếm bài viết chưa đọc cho một người dùng nhất định, nhóm của khu vực thảo luận ("item") mà bài viết đang ở:

$count_unseen_query = " 
    SELECT 
     msgs.item as id, 
     count(1) as the_count 
    FROM msgs 
    WHERE 
    msgs.usr != " . $usr_id . " AND 
    msgs.state != 'deleted' AND 
    NOT EXISTS (
     SELECT 1 
     FROM 
      msgs_seen_bounds msb 
     WHERE 
      msgs.id BETWEEN msb.lower_msg_id AND msb.upper_msg_id 
      AND msb.usr_id = " . $usr_id . " 
    ) 
    GROUP BY msgs.item 
    ; 

Người sử dụng hơn đọc trên diễn đàn này, các rộng hơn các giới hạn được đánh dấu là đã đọc bởi mỗi bộ dữ liệu và các bộ dữ liệu ít hơn phải được lưu trữ. Người dùng có thể nhận được số lượt đọc chính xác so vớichưa đọc và có thể dễ dàng được tổng hợp để xem được đọc và chưa đọc trong mỗi diễn đàn, tiểu thuyết, chủ đề, v.v.

Với một diễn đàn nhỏ về khoảng 2000 bài đăng, sau đây là thống kê sử dụng về số lượng bộ nhớ được lưu trữ , được sắp xếp theo số lần người dùng đã đăng nhập (hoạt động gần đúng của người dùng). Cột "num_bounds" là số lượng bộ cần thiết để lưu trữ lịch sử xem "num_posts_read" của người dùng.

id num_log_entries num_bounds num_posts_read num_posts 
479    584   11   2161  228 
118    461   6   2167  724 
487    119   34   2093  199 
499    97   6   2090  309 
476    71  139   481  82 
480    33   92   167  26 
486    33  256   757  154 
496    31  108   193  51 
490    31   80   179  61 
475    28  129   226  47 
491    22   22   1207  24 
502    20  100   232  65 
493    14   73   141   5 
489    14   12   1517  22 
498    10   72   132  17 

Tôi chưa thấy triển khai cụ thể này trong bất kỳ diễn đàn nào, nhưng tùy chỉnh của riêng tôi và đó là một phần nhỏ ở đó. Tôi muốn được quan tâm nếu có ai khác đã thực hiện, hoặc nhìn thấy điều này được thực hiện ở nơi khác, đặc biệt là trong một diễn đàn lớn và/hoặc hoạt động.

Kính trọng,

Kaiden

+0

Tôi muốn xem xét triển khai thực tế về điều đó, nếu có thể :) – yoda

+0

@yoda, không chắc chắn bạn đang yêu cầu điều gì. Tôi đã có mã số làm việc, nhưng cho dù đó là bằng chứng SQL-injection hay không, không chắc chắn tôi muốn để lộ các trường hợp chạy đến horribleness của Internet. Tiền tố được đặt trên StackOverflow bao gồm mã là gì? Nó có được dự kiến ​​là một phần của câu trả lời, hoặc trong các ý kiến ​​(mà là quá ngắn), hoặc off-site trong một dịch vụ khác như paste.ie? Lemme biết. – Kaiden

+0

bạn chỉ có thể hiển thị cho tôi, nếu bạn đồng ý với điều đó và không muốn để lộ mã của bạn cho tất cả mọi người. Bạn có thể sử dụng pastebin.com và gửi mã đến aeon dot yoda tại gmail dot com. Tôi rất cảm kích! – yoda

0

Tôi đã đọc tất cả các câu trả lời và tôi đến với một ý tưởng mà bạn có thể kết hợp tốt nhất cho chủ đề này (không có mã mặc dù).
Ý tưởng này là sự kết hợp của tất cả ý tưởng của bạn và trải nghiệm nhỏ mà tôi có trong lập trình
Aprox 95% người dùng (thống kê từ quản trị diễn đàn và nhật ký diễn đàn) đọc chủ đề của diễn đàn ngay tới bài đăng cuối cùng (hoặc trang) và không quay trở lại, đọc các bài viết của các trang 1 (hoặc chỉ là bài đăng đầu tiên) và sau đó đi đến trang cuối cùng, hoặc họ đọc toàn bộ chuỗi từ đầu đến cuối và nếu họ quay lại chúng đã đọc phần đó rồi. Vì vậy, một giải pháp tốt sẽ hoạt động như thế này:
Tôi nghĩ Nếu chúng ta tạo một cửa hàng, cho mỗi người dùng, cho mỗi luồng, dấu thời gian của bài đăng cuối cùng mà người dùng đã xem (và, nếu có, bài đăng đầu tiên người dùng xem ngay cả khi có thể không hữu ích) chúng ta có thể đi đâu đó với cái này. Hệ thống này khá đơn giản và gần như là của phpbb. Nó cũng hữu ích để đánh dấu bài viết cuối cùng mà chúng ta đã thấy để tiếp tục trong bài viết sau (thay vì buộc phải xem xét tất cả các trang đó là đã đọc). Và, mỗi thread có id riêng của nó. Không cần phải tổ chức như phpbb.

1

Một câu trả lời nhanh về cách (tôi nghĩ) IPB làm nó:

Tất cả các bài đăng cũ hơn hơn số cấu hình (mặc định là 30 ngày) sẽ tự động được đánh dấu là đã đọc. Một cronjob prunes này từ mỗi người dùng để giữ cho kích thước quản lý được.

Tất cả bài đăng dưới 30 ngày được theo dõi dưới dạng mục nhập JSON cho từng danh mục ID người dùng +. Ví dụ: 12 danh mục với 1000 người dùng đang hoạt động = tối đa 12.000 hàng.

Có trường "số chưa đọc" để tìm kiếm nhanh, chẳng hạn như Trang chủ diễn đàn hoặc bất kỳ nơi nào khác chỉ cần số.

Tôi có thể hoàn toàn tắt lưu trữ MySQL thực tế. Tôi không thể tìm thấy tài liệu về điều này, nhưng tôi đào thông qua cơ sở dữ liệu và thấy một bảng mà/nhìn/như chủ đề đọc/chưa đọc (bảng: core_item_markers, để tham khảo). Nhưng tôi tích cực vào mô hình tuổi/mysql lai.

2

không chính xác một PHP-câu trả lời, nhưng dưới đây là cách chúng tôi làm điều đó trong chúng tôi asp.net-based forum (Tôi đang liên kết với sản phẩm này, tiết lộ rằng do các quy tắc)

  1. Chúng tôi sử dụng cookie, không phải là cơ sở dữ liệu.
    • Nhược điểm cookie - không phải là "cross-thiết bị" (quý khách đến thăm từ một máy tính khác cho thấy tất cả mọi thứ là chưa đọc)
    • Advantage - không có DB lớn lần đọc/viết. Và theo dõi hoạt động cho người dùng "khách" cũng vậy! Điều này thật tuyệt.
  2. Chúng tôi lưu trữ cookie với { topicID, lastReadMessageID } cặp cho mỗi chủ đề mà người dùng truy cập.
  3. Nếu dữ liệu cho một chủ đề cụ thể không tìm thấy trong cookie chúng tôi giả định chủ đề là một trong hai:
    • hoàn toàn chưa đọc (nếu tin nhắn cuối cùng của chủ đề lớn hơn MAX lastReadMessageID từ (2)
    • đầy đủ đọc (nếu khác)

này có một số sai sót nhỏ, nhưng nó không được công việc.

PS. Ngoài ra, một số người có thể nói rằng sử dụng cookie để lại rác trên máy tính của người dùng (cá nhân tôi ghét điều này), nhưng chúng tôi phát hiện ra rằng người dùng trung bình theo dõi khoảng 20 chủ đề, vì vậy phải mất khoảng 10 byte cho mỗi chủ đề. trên đĩa cứng của người dùng.

Các vấn đề liên quan