functions.php 或建立 Must-Use Plugin。increment 避免競爭條件。<?php
/**
* WordPress 訪問計數器 - Memcache 版
* 放置於 wp-content/mu-plugins/visit-counter.php 或主題 functions.php
*/
// 初始化 Memcache 連線 (單例模式)
function wc_get_memcache(): Memcache {
static $mc = null;
if ($mc === null) {
$mc = new Memcache();
$host = '127.0.0.1';
$port = 11211;
// 生產環境建議加入連線池/持久連線
if (!$mc->connect($host, $port)) {
// 降級:無法連線 Memcache 時直接查資料庫
error_log('[VisitCounter] Memcache 連線失敗,降級至資料庫模式');
return false;
}
}
return $mc;
}
// 取得並遞增總訪問數
function wc_get_total_visits(): int {
$mc = wc_get_memcache();
$key = 'wc_total_visits';
if ($mc) {
// 原子遞增:若 key 不存在則從資料庫初始化
$visits = $mc->increment($key);
if ($visits === false) {
// Key 不存在,從資料庫讀取初始值
global $wpdb;
$row = $wpdb->get_row("SELECT option_value FROM {$wpdb->options} WHERE option_name = '$key'", ARRAY_A);
$initValue = $row ? (int)$row['option_value'] : 0;
$mc->set($key, $initValue + 1, 0, 86400); // 設定初始值+1,24小時過期(僅作備援)
$visits = $initValue + 1;
}
} else {
// 降級模式:直接資料庫原子更新
global $wpdb;
$wpdb->query("UPDATE {$wpdb->options} SET option_value = option_value + 1 WHERE option_name = '$key'");
$row = $wpdb->get_row("SELECT option_value FROM {$wpdb->options} WHERE option_name = '$key'", ARRAY_A);
$visits = $row ? (int)$row['option_value'] : 0;
}
return $visits;
}
// 定期同步至資料庫 (掛載於 shutdown 或 cron)
function wc_sync_visits_to_db(): void {
$mc = wc_get_memcache();
if (!$mc) return;
$key = 'wc_total_visits';
$visits = $mc->get($key);
if ($visits === false) return; // 無資料
// 僅每 100 次同步一次,減少資料庫寫入
if ($visits % 100 !== 0) return;
global $wpdb;
$wpdb->update(
$wpdb->options,
['option_value' => $visits],
['option_name' => $key],
['%d'],
['%s']
);
}
// 前台顯示短代碼 [total_visits]
add_shortcode('total_visits', function() {
$visits = wc_get_total_visits();
return number_format($visits);
});
// 每次頁面載入遞增計數 (排除後台、登入用戶、爬蟲)
add_action('wp_head', function() {
if (is_admin() || is_user_logged_in()) return;
// 簡易爬蟲過濾
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/bot|crawler|spider|slurp/i', $ua)) return;
wc_get_total_visits(); // 觸發遞增
}, 1); // 優先級 1,極早執行
// 關站/定時同步
register_shutdown_function('wc_sync_visits_to_db');
// 或使用 WP Cron 每小時同步
if (!wp_next_scheduled('wc_hourly_sync')) {
wp_schedule_event(time(), 'hourly', 'wc_hourly_sync');
}
add_action('wc_hourly_sync', 'wc_sync_visits_to_db');
// 文章閱讀數
function wc_get_post_views(int $postId): int {
$mc = wc_get_memcache();
$key = "wc_post_views_{$postId}";
if ($mc) {
$views = $mc->increment($key);
if ($views === false) {
$views = (int)get_post_meta($postId, 'views', true);
$mc->set($key, $views + 1, 0, 86400);
$views++;
}
} else {
$views = (int)get_post_meta($postId, 'views', true) + 1;
update_post_meta($postId, 'views', $views);
}
return $views;
}
// 同步文章閱讀數 (每 50 次)
function wc_sync_post_views(int $postId): void {
$mc = wc_get_memcache();
if (!$mc) return;
$key = "wc_post_views_{$postId}";
$views = $mc->get($key);
if ($views !== false && $views % 50 === 0) {
update_post_meta($postId, 'views', $views);
}
}
// 掛載於 single.php 或 the_content 過濾器
add_filter('the_content', function($content) {
if (is_singular('post') && !is_admin()) {
$postId = get_the_ID();
$views = wc_get_post_views($postId);
// 可選:在文章開頭顯示閱讀數
// $content = "閱讀數: {$views}
" . $content;
wc_sync_post_views($postId);
}
return $content;
});
| 項目 | 建議設定 |
|---|---|
| Memcache 記憶體 | 分配 64MB-256MB 視流量;啟用 LRU 淘汰 |
| 連線方式 | 持久連線 pconnect 或連線池 (php-memcached) |
| 金鑰命名 | 統一前綴 wc_,避免衝突 |
| 同步頻率 | 總計數 100 次/次,文章計數 50 次/次,可依流量調整 |
| 降級策略 | Memcache 不可用時自動切換資料庫模式,記錄錯誤日誌 |
| 監控告警 | 監控 Memcache 命中率、連線數、記憶體使用率 |
increment() 原生支援,無需 Lua 腳本。注意:若專案已使用 Redis 快取,建議統一使用 Redis INCR,減少元件依賴。