iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
WordPress

從 0 到 100:WordPress 開發者的實戰手冊系列 第 23

Day 23 - 洞悉 WordPress 的物件快取 (Object Cache) 機制

  • 分享至 

  • xImage
  •  

在開始今天的主題之前,先來一個問答。

WooCommerce 是 WordPress 的電子商務外掛,它使用 wp_posts 資料表作為訂單,而 wp_postmeta 資料表存放訂單資料。以下是一個模擬的 WooCommerce 訂單資料。

模擬的 WooCommerce 訂單資料
圖:模擬的 WooCommerce 訂單資料

當我們想取得付款人的地址時,會使用 get_post_meta 函式,想當然耳,讀取的方式如下程式碼。

$address = get_post_meta( 1275725, '_billing_address_1', true );

取得 $address 的值為「鐵人市鐵人路 128 號」。
接著,再取得付款人的名字。

$name = get_post_meta( 1275725, '_billing_first_name', true );

取得 $name 的值為「測試者」。

今天的問題來了。你覺得以上的程式碼總共會有幾次資料庫請求?

答案是:一次

這是為什麼呢?這就是今天要探討的主題,帶大家洞悉 WordPress 的物件快取 (Object Cache) 機制。

什麼是物件快取

在 WordPress中,當你使用 get_post_meta、get_posts 等函式,或者使用 WP_Query 這種高階查詢類別,WordPress會利用其內建的快取機制,稱為物件快取,來節省對資料庫的查詢次數。物件快取預設是存放在 PHP 管理的記憶體中,當次的 PHP 請求結束後,即刻失效。

原理解析

讓我們以使用 get_post_meta 函式當作例子。

當第一次使用 get_post_meta 於一個尚未讀取過的 ID,它的程式執行過程從位於 wp_includes 目錄裡的 post.php 開始。

流程 1

post.php 檔案 → get_post_meta 函式
圖:post.php 檔案:get_post_meta 函式

這個函式只是提供一個函式名稱直覺的使用方式,內部轉用 get_metadata 函式。

流程 2

meta.php 檔案 → get_metadata 函式
圖:meta.php 檔案 :get_metadata 函式

執行的過程進入到了位於 wp-includes 目錄中的 meta.php。

第 574 行:函式的參數完整不動地轉接到 get_metadata_raw 函式。
第 575 行:如果從 get_metadata_raw 函式取值,且該值不為 null,則返回拿到的資料。
第 579 行:走到了這一次,代表沒有資料,返回預設值。如果 $single 為 true,則返回空字串,如果 $single 為 flase,則返回空陣列。

流程 3

meta.php 檔案:get_metadata_raw 函式
圖:meta.php 檔案:get_metadata_raw 函式

這邊來到了 get_metadata_raw 函式的邏輯處理,重點來啦。

第 644 行:取得該 Post 類型、Post ID 的物件快取。
第 647 行:如果物件快取不存在,則執行 update_meta_cache 函式。

update_meta_cache 函式可接受的是 ID 集合的陣列,即使只有查詢一個 ID,也要將它放進陣列中傳給 update_meta_cache 函式。

流程 4

meta.php 檔案:update_meta_cache 函式
圖:meta.php 檔案:update_meta_cache 函式

這邊是 update_meta_cache 函式的下半部程式碼。

第 1177 行:由於並沒有找到物件快取,從資料庫讀取該 wp_postmeta 資料表關於輸入 ID 的所有資料。
第 1205 行:將從資料庫取出的資料放到物件快取中,由於一次儲存多筆,採用 wp_cache_add_multiple 函式。
第 1207 行:由於這是第一次讀取資料庫,返回從資料庫取出的資料。

流程 5

因為 WordPress 該文章的所有自定義欄位一次性地載入到快取中。接下來經由 get_post_meta 函式讀取相同 ID 的任何資料,都不會再讀取資料庫,而是從物件快取中取出,回傳給呼叫的函式。

類別:WP_Object_Cache

單純使用 wpdb 類別來操作資料庫並不會存進物件快取喔。例如,WordPress 中的許多內建函式,它們在背後使用 wpdb 進行資料庫查詢,但它們的結果會被放入物件快取,這是因為這些內建函式有經過設計,將結果放進物件快取中,這是為了讓開發者在不同的地方呼叫相同的函式要求同樣的資料時,不必再向資料庫要一次。

WP_Object_Cache 類別是負責物件快取的邏輯實作,$wp_object_cache 是此類別經過實例化後的物件存放的全域變數。WordPress 又把它包裝成易使用的函式。

因此,當我們在開發 WordPress 程式時,如果想要將資料放到物件快取中,都是使用物件快取相關函式進行處理。

相關函式

函式名稱 說明
wp_cache_get 透過鍵值從物件快取中取得資料。
wp_cache_set 在物件快取中儲存資料。
wp_cache_delete 從物件快取中刪除資料。
wp_cache_flush 清空物件快取。
wp_cache_add 僅在快取中不存在指定鍵值時,才將資料加入到物件快取中。
wp_cache_replace 僅當指定的鍵值存在於物件快取中時,替換其資料。
wp_cache_add_global_groups 新增全域群組以供所有部落格使用。
wp_cache_add_non_persistent_groups 新增非持久群組。
wp_cache_get_multiple 一次取得多個鍵值的資料。
wp_cache_set_multiple 一次儲存多個鍵值的資料。
wp_cache_delete_multiple 一次刪除多個鍵值的資料。
wp_cache_add_multiple 一次新增多個鍵值的資料(只有當它們不存在時)。
wp_cache_flush_group 清空指定群組的物件快取。
wp_cache_flush_runtime 清空執行期間的物件快取。

物件快取持久化

物件快取的預設存放空間在 PHP 的內部記憶體中,且其生命週期僅限於單一請求的執行期間。因此當一次 HTTP 請求完成後,該快取資料將被清除,並在下一次請求時重新產生。如果可以將物件快取的效期持久化,那麼每一次新的 HTTP 請求時,也不用再重 MySQL 資料庫發送查詢請求,這樣會大幅地降低 MySQL 伺服器的負荷。

物件快取持久化需要使用外部的快取解決方案,例如採用 Memcached、Redis 等等。使用這些方案,需要在系統安裝對應的服務以外, PHP 也要安裝搭配的擴充套件或者 Composer 套件,並且設計實作的 Drop-in,也就是 object-cache.php 這個檔案,並把它放到 wp-content 目錄底下,取代預設的物件快取機制 。

當使用 Memcached 或Redis 取代預設的 PHP 內部記憶體,物件快取資料可以超出單一請求的生命週期。這樣一來,跨請求的快取資料可以被重複使用,大大提高了效能唷!

使用第三方提供的外掛

一般來說,object-cache.php 這個 Drop-in 我們不需要自行設計,在官方的外掛目務中,已經有許多使用 Redis、Memcached 等方案的物件快取外掛,只要搜尋「object cache」就可以找到。

不過這只是 Drop-in 採用現成的方案,安裝對應的服務還是必須自行動手的,對於 Linux 系統的管理,需要基本的使用知識才行。

Transients API

WordPress 的 Transients API 是一系列用來存放有時間限制的資料的函式。而它的快取資料都是存放在 wp_options 資料表,透過 Options API 來包裝它的「有效期間」功能。

與 Options API 的用法差別

設定:

  • update_option( string $option, mixed $value, string|bool $autoload = null ): bool
  • set_transient( string $transient, mixed $value, int $expiration ): bool

用法和 Options API 類似,差別在第三個參數不同。

取值:

  • get_option( string $option, mixed $default_value = false ): mixed
  • get_transient( string $transient ): mixed

用法和 Options API 類似,get_option 可以指定找不到值時,回傳的預設值。但 get_transient 只有一個參數。沒有這個值或者值已過了有效期限,則返回 false。

和物件快取的關聯

option.php 檔案:get_transient 函式
圖:option.php 檔案:get_transient 函式

由於 Options API 本身就有實作物件快取,而 Transients API 是使用 Options API 來實現有效期間的功能,所以本身就使用了物件快取以減少資料庫讀取次數。

但是如果使用了 object-cache.php 這個 Drop-in 來實現物件快取持久化,則不會對 wp_options 資料表再進行存取,而是改用 Drop-in 實作的存取方法,而有效期間的功能在 Redis 或 Memcached 就已經有,不需要實作細節。

第 905 行:如果有啟用物件快取的 Drop-in,則直接透過 Drop-in 的方法取值。
第 908 行以後:透過 Options API 來包裝、實作有效時間功能。

函式

函式名稱 說明
set_transient() 設定一個臨時性的快取。
get_transient() 取得臨時性快取的值。
set_site_transient() 設定適用多站點的臨時性快取。
get_site_transient() 取得適用多站點的臨時性快取。
delete_transient() 刪除臨時性的快取。
delete_site_transient() 刪除適用多站點的臨時性快取。

總結

WordPress 的物件快取機制是為了在佈景主題或外掛中,任何地方使用內建的函式或類別方法來存取資料庫中的資料時,不會有多餘的查詢次數造成 MySQL 伺服器額外的負擔。不過也因為這樣,載入的資料到 PHP 內部記憶體的東西較多,以致於記憶體用量較高。相對於記憶體用量的問題,減少資料庫的查詢次數更為重要。

另外,安裝適當的物件快取外掛,讓物件快取可以持久化,對 WordPress 系統的效能,會有顯著的改善哦。


課後思考:

物件快取持久化,好處多多,不過有可能產生什麼意想不到的問題呢?

前篇解答參考:

只要 SQL 語句接受的值是來自於外部的輸入,則必須使用 prepare 方法。這是唯一有效可以提高系統安全性,過濾掉 SQL 注入攻擊的字串。不使用的話,短期之間可能不覺得有什麼影響,但當我們設計的外掛上架到 WordPress 官方外掛目錄之後,使用者變多後,自然會變成攻擊者的目標。另外,上架時,外掛審核小組發現有沒有使用 prepare 方法則會以安全理由退件的,也有可能無預警因為安全理由被下架。總之,養成使用 prepare 方法的習慣才是王道呀。


上一篇
Day 22 - 理解 WordPress 的資料庫操作類別 wpdb 的用法
下一篇
Day 24 - 自定義 WordPress 的文章類型、分類和標籤
系列文
從 0 到 100:WordPress 開發者的實戰手冊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言