详解 WordPress `wp_cache_get()` 和 `wp_cache_set()` 函数的源码:在没有外部缓存时的内部工作机制。

Alright, gather ’round, code wranglers! Let’s talk about WordPress’s internal caching mechanism when it’s flying solo, without any fancy external caching plugins. We’re diving deep into wp_cache_get() and wp_cache_set(), the unsung heroes of WordPress performance when no other cache provider is configured.

(Ahem, clears throat, adjusts imaginary glasses) Let’s begin!

The Stage: wp-includes/cache.php

This is where the magic happens, or rather, the basic magic happens. When you don’t have Memcached, Redis, or some other caching system set up, WordPress relies on its own internal, in-memory object cache. This cache is held in a global variable, $wp_object_cache.

The Players: wp_cache_get() and wp_cache_set()

These two functions are the workhorses. wp_cache_get() retrieves data from the cache, and wp_cache_set() stores data into it.

Scenario 1: No External Cache Provider

If you haven’t installed and activated a persistent object cache plugin (like Memcached Object Cache, Redis Object Cache, etc.), WordPress uses its default, non-persistent object cache. This means the cache exists only for the duration of a single request. Once the page is generated and sent to the user, the cache is wiped clean.

Under the Hood: $wp_object_cache

The $wp_object_cache variable is an instance of the WP_Object_Cache class. Let’s take a look at its structure.

/**
 * Core class used to implement an object cache.
 *
 * @since 2.0.0
 */
class WP_Object_Cache {

    /**
     * Holds the cached objects.
     *
     * @var array
     */
    private $cache = array();

    /**
     * The number of cache calls.
     *
     * @var int
     */
    public $cache_hits = 0;

    /**
     * Number of times cache missed.
     *
     * @var int
     */
    public $cache_misses = 0;

    /**
     * List of global groups.
     *
     * @var array
     */
    protected $global_groups = array();

    /**
     * List of non-persistent groups.
     *
     * @var array
     */
    protected $non_persistent_groups = array();

    /**
     * Holds the value if WordPress is running in network mode.
     *
     * @var bool
     */
    protected $multisite;

    /**
     * The blog ID.
     *
     * @var int
     */
    protected $blog_id;

    /**
     * Instantiate the cache.
     *
     * @since 2.0.0
     *
     * @global array $wp_db_versions
     */
    public function __construct() {
        global $wp_db_versions;

        $this->multisite = is_multisite();
        $this->blog_id   = (int) get_current_blog_id();

        if ( function_exists( 'wp_cache_add_global_groups' ) ) {
            wp_cache_add_global_groups( (array) $wp_db_versions );
        }
    }

    // ... (Other methods will be discussed later) ...
}

global $wp_object_cache;

if ( ! isset( $wp_object_cache ) ) {
    $wp_object_cache = new WP_Object_Cache();
}

Key things to note:

  • $cache: This is the heart of the cache. It’s a simple associative array where data is stored. The keys are constructed from the cache key and group.
  • $cache_hits and $cache_misses: These track how often the cache is successfully used (a "hit") versus how often it fails to find the requested data (a "miss"). Useful for debugging and performance analysis.
  • $global_groups and $non_persistent_groups: These are used to manage which groups of cached data should be shared across the entire WordPress installation (global) and which should be specific to a particular site or part of the application (non-persistent). In the context of the internal cache, these groups are generally less crucial than when using a persistent cache.

Diving into wp_cache_get()

Let’s dissect the wp_cache_get() function.

/**
 * Retrieves the cache contents, if it exists.
 *
 * The contents are first attempted to be retrieved from the cache. If the
 * cache is empty, then the callback function will be called to generate
 * the cache contents. The results of the callback function are stored in the
 * cache using the cache key.
 *
 * @since 2.0.0
 *
 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
 *
 * @param int|string $key Cache key.
 * @param string     $group Optional. Cache group. Default empty.
 * @param bool       $force Optional. Whether to force an update of the local
 *                            cache from the persistent cache. Default false.
 *
 * @return mixed|false Cached data on success, false on failure.
 */
function wp_cache_get( $key, $group = '', $force = false ) {
    global $wp_object_cache;

    return $wp_object_cache->get( $key, $group, $force );
}

It’s a simple wrapper around the WP_Object_Cache::get() method. Let’s look at that method:

/**
 * Retrieves the cache contents, if it exists.
 *
 * @since 2.0.0
 *
 * @param int|string $key   Cache key.
 * @param string     $group Optional. Cache group. Default empty.
 * @param bool       $force Optional. Whether to force an update of the local
 *                          cache from the persistent cache. Default false.
 *
 * @return mixed|false Cached data on success, false on failure.
 */
public function get( $key, $group = '', $force = false ) {
    $key = (string) $key;
    $group = (string) $group;

    if ( isset( $this->cache[ $group ][ $key ] ) ) {
        $this->cache_hits++;
        if ( is_object( $this->cache[ $group ][ $key ] ) ) {
            return clone $this->cache[ $group ][ $key ];
        } else {
            return $this->cache[ $group ][ $key ];
        }
    }

    $this->cache_misses++;
    return false;
}

Here’s the breakdown:

  1. Type Casting: The $key and $group parameters are cast to strings. This ensures consistent key handling.
  2. Cache Lookup: The code checks if the data exists in the $cache array using $this->cache[ $group ][ $key ]. The cache is organized in a two-level structure: first by group, then by key within that group.
  3. Cache Hit: If the data is found, $this->cache_hits is incremented. The data is then returned. Note the clone operation when dealing with objects. This prevents modifications to the cached object from affecting the original data.
  4. Cache Miss: If the data is not found, $this->cache_misses is incremented, and false is returned.

Example wp_cache_get() Usage:

$my_data = wp_cache_get( 'my_unique_key', 'my_group' );

if ( false === $my_data ) {
    // Data not found in cache.  Let's fetch it from the database or calculate it.
    $my_data = expensive_operation(); // Replace with your actual data fetching code

    // Store the data in the cache for future use.
    wp_cache_set( 'my_unique_key', $my_data, 'my_group' );
}

// Now $my_data contains either the cached data or the newly fetched/calculated data.
echo "My data: " . $my_data;

The Star of the Show: wp_cache_set()

Now, let’s explore wp_cache_set().

/**
 * Sets the data contents into the cache.
 *
 * The cache key and group are used to name the cache and to group the cache
 * contents.
 *
 * @since 2.0.0
 *
 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
 *
 * @param int|string $key    Cache key.
 * @param mixed      $data   Data to store in the cache.
 * @param string     $group  Optional. Cache group. Default empty.
 * @param int        $expire Optional. When to expire the cache contents, in seconds.
 *                           Default 0 (no expiration).
 *
 * @return bool False if not an object. True on success.
 */
function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
    global $wp_object_cache;

    return $wp_object_cache->set( $key, $data, $group, $expire );
}

Again, this is a wrapper for the WP_Object_Cache::set() method:

/**
 * Sets the data contents into the cache.
 *
 * @since 2.0.0
 *
 * @param int|string $key    Cache key.
 * @param mixed      $data   Data to store in the cache.
 * @param string     $group  Optional. Cache group. Default empty.
 * @param int        $expire Optional. When to expire the cache contents, in seconds.
 *                           Default 0 (no expiration).
 *
 * @return bool False if not an object. True on success.
 */
public function set( $key, $data, $group = '', $expire = 0 ) {
    $key = (string) $key;
    $group = (string) $group;

    if ( is_object( $data ) ) {
        $this->cache[ $group ][ $key ] = clone $data;
    } else {
        $this->cache[ $group ][ $key ] = $data;
    }

    return true;
}

The process:

  1. Type Casting: $key and $group are cast to strings, similar to wp_cache_get().
  2. Data Storage: The $data is stored in the $cache array at the location specified by the $group and $key: $this->cache[ $group ][ $key ] = $data;. Again, if $data is an object, it’s cloned to prevent modification of the original.
  3. Return Value: The function always returns true to indicate success. (Historically, it returned false if the data was not an object, but this behavior has changed.)

Important Considerations with the Internal Cache

  • Request-Bound: The internal cache is not persistent. Data stored in the cache is only available during the current request. Once the page is generated and sent to the user, the cache is destroyed.
  • Expiration: The $expire parameter in wp_cache_set() is ignored when using the internal cache. Expiration only comes into play with persistent object caches.
  • Memory Limits: The internal cache is limited by the PHP memory allocated to WordPress. Caching large amounts of data can lead to memory exhaustion errors. Be mindful of what you’re caching.
  • Object Cloning: Both wp_cache_get() and wp_cache_set() clone objects before returning or storing them. This is a good practice to prevent accidental modification of the cached data.

Global vs. Non-Persistent Groups

Let’s look at how $global_groups and $non_persistent_groups are used.

/**
 * Adds a group to the list of global groups.
 *
 * @since 2.9.0
 *
 * @param string|array $groups A group or an array of groups to add.
 */
public function add_global_groups( $groups ) {
    if ( ! is_array( $groups ) ) {
        $groups = (array) $groups;
    }

    $this->global_groups = array_merge( $this->global_groups, $groups );
    $this->global_groups = array_unique( $this->global_groups );
}

/**
 * Adds a group to the list of non-persistent groups.
 *
 * @since 3.5.0
 *
 * @param string|array $groups A group or an array of groups to add.
 */
public function add_non_persistent_groups( $groups ) {
    if ( ! is_array( $groups ) ) {
        $groups = (array) $groups;
    }

    $this->non_persistent_groups = array_merge( $this->non_persistent_groups, $groups );
    $this->non_persistent_groups = array_unique( $this->non_persistent_groups );
}

/**
 * Resets internal cache keys and properties.
 *
 * @since 2.0.0
 */
public function reset() {
    $this->cache       = array();
    $this->cache_hits  = 0;
    $this->cache_misses = 0;
}

/**
 * Removes the contents of the cache key in the group.
 *
 * @since 2.0.0
 *
 * @param int|string $key   What the key of the cache to delete.
 * @param string     $group Optional. Which group to delete from. Default empty.
 *
 * @return bool Always returns true.
 */
public function delete( $key, $group = '' ) {
    $key   = (string) $key;
    $group = (string) $group;

    unset( $this->cache[ $group ][ $key ] );
    return true;
}

These functions help manage how the cache is handled, especially in a multisite environment or with persistent caching. add_global_groups ensures certain data is consistent across all sites in a network, while add_non_persistent_groups keeps data separate. reset clears the cache, and delete removes specific items.

Why Use wp_cache_get() and wp_cache_set() Even Without a Persistent Cache?

Even though the internal cache is short-lived, using wp_cache_get() and wp_cache_set() is still beneficial:

  • Code Consistency: Using these functions prepares your code for easy integration with a persistent object cache later on. You won’t have to rewrite your code to use a different caching API.
  • Reduced Redundant Operations: Within a single request, you might need the same data multiple times. The internal cache prevents you from performing the same database query or complex calculation repeatedly.
  • Testing and Debugging: You can easily simulate caching behavior during development and testing.

Code Example: Optimizing a Loop

Let’s say you have a loop that needs to retrieve the same post meta data for each post.

// Without caching
$posts = get_posts( array( 'numberposts' => 10 ) );
foreach ( $posts as $post ) {
    $meta_value = get_post_meta( $post->ID, 'my_custom_field', true );
    echo $meta_value . '<br>';
}

// With caching
$posts = get_posts( array( 'numberposts' => 10 ) );
foreach ( $posts as $post ) {
    $cache_key = 'post_meta_' . $post->ID . '_my_custom_field';
    $meta_value = wp_cache_get( $cache_key, 'my_meta_group' );

    if ( false === $meta_value ) {
        $meta_value = get_post_meta( $post->ID, 'my_custom_field', true );
        wp_cache_set( $cache_key, $meta_value, 'my_meta_group' );
    }

    echo $meta_value . '<br>';
}

In the second example, the get_post_meta() function is only called once per post per request. Subsequent calls retrieve the data from the cache.

When to Consider a Persistent Cache

The internal cache is a good starting point, but for high-traffic sites, a persistent object cache (Memcached, Redis, etc.) is essential. Here’s when you should consider upgrading:

  • Slow Page Load Times: If your site is consistently slow, especially for logged-in users or dynamic content, a persistent cache can significantly improve performance.
  • High Database Load: If your database server is under heavy load, caching can reduce the number of database queries.
  • Scalability: A persistent cache helps your site handle more traffic without requiring more server resources.

Troubleshooting

If you’re not seeing the expected caching behavior, here are some things to check:

  • Typos: Double-check the cache keys and group names in wp_cache_get() and wp_cache_set().
  • Data Changes: Make sure the data you’re caching isn’t being modified elsewhere in your code.
  • Object Cloning Issues: If you’re caching objects, ensure that the cloning is working correctly and that you’re not accidentally modifying the original cached object.

In Conclusion

While not as powerful as persistent caching solutions, the internal WordPress object cache provides a basic level of caching functionality. Understanding how wp_cache_get() and wp_cache_set() work under the hood is crucial for optimizing your WordPress site’s performance and preparing it for future scalability. Remember, caching is all about reducing redundant operations and serving data more efficiently. Use these functions wisely, and your WordPress site will thank you for it!

Now, go forth and cache all the things! (Responsibly, of course.)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注