使用标准 php 库使多个 memcache 键无效的最佳方法?

Posted

技术标签:

【中文标题】使用标准 php 库使多个 memcache 键无效的最佳方法?【英文标题】:Best way to invalidate a number of memcache keys using standard php libraries? 【发布时间】:2011-09-07 19:42:46 【问题描述】:

我有一个包含文件的数据库,可以在多个服务器上搜索、浏览并拥有多个副本。

我缓存搜索、浏览页面和服务器位置 (url)。假设我删除了一个文件,有什么好方法可以使所有搜索、浏览该文件的数据和 url 无效?或者如果文件服务器出现故障,我需要使所有指向该服务器的 url 无效?

基本上我正在寻找类似于memcache-tags 的东西,但具有标准的memcache 和php 组件。 (无需更改 Web 服务器本身的任何内容)。我需要在键之间建立某种多对多关系(一个服务器有很多文件,一个文件有多个服务器),但似乎无法找到实现此目的的好方法。在某些情况下,陈旧的缓存是可以接受的(小更新等),但在某些情况下(通常是删除和服务器关闭),我需要使所有包含对它的引用的缓存项无效。

我看过的一些方法:

Namespaces

$ns_key = $memcache->get("foo_namespace_key");
// if not set, initialize it
if($ns_key===false) $memcache->set("foo_namespace_key", rand(1, 10000));
// cleverly use the ns_key
$my_key = "foo_".$ns_key."_12345";
$my_val = $memcache->get($my_key);

//To clear the namespace do:
$memcache->increment("foo_namespace_key");
将缓存键限制为单个命名空间

Item caching approach

$files = array('file1','file2');
// Cache all files as single entries
foreach ($files as $file) 
  $memcache->set($file.'_key');

$search = array('file1_key','file2_key');
// Retrieve all items found by search (typically cached as file ids)
foreach ($search as $item) 
  $memcache->get($item);

如果文件服务器宕机会出现问题,并且所有包含指向该服务器的 url 的键都应该失效(例如,需要大量的小缓存项,这反过来又需要对缓存的大量请求)- 破坏任何缓存完整对象和结果集的机会

Tag implemenation

class KeyEnabled_Memcached extends Zend_Cache_Backend_Memcached


    private function getTagListId()
    
        return "MyTagArrayCacheKey";
    

    private function getTags()
    
        if(!$tags = $this->_memcache->get($this->getTagListId()))
        
            $tags = array();
        
        return $tags;
    

    private function saveTags($id, $tags)
    
        // First get the tags
        $siteTags = $this->getTags();

        foreach($tags as $tag)
        
            $siteTags[$tag][] = $id;
        
        $this->_memcache->set($this->getTagListId(), $siteTags);        
    

    private function getItemsByTag($tag)
    
        $siteTags = $this->_memcache->get($this->getTagListId());
        return isset($siteTags[$tag]) ? $siteTags[$tag] : false;
    

    /**
     * Save some string datas into a cache record
     *
     * Note : $data is always "string" (serialization is done by the
     * core not by the backend)
     *
     * @param  string $data             Datas to cache
     * @param  string $id               Cache id
     * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
     * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
     * @return boolean True if no problem
     */
    public function save($data, $id, $tags = array(), $specificLifetime = false)
    
        $lifetime = $this->getLifetime($specificLifetime);
        if ($this->_options['compression']) 
            $flag = MEMCACHE_COMPRESSED;
         else 
            $flag = 0;
        
        $result = $this->_memcache->set($id, array($data, time()), $flag, $lifetime);
        if (count($tags) > 0) 
            $this->saveTags($id, $tags);
        
        return $result;
    

    /**
     * Clean some cache records
     *
     * Available modes are :
     * 'all' (default)  => remove all cache entries ($tags is not used)
     * 'old'            => remove too old cache entries ($tags is not used)
     * 'matchingTag'    => remove cache entries matching all given tags
     *                     ($tags can be an array of strings or a single string)
     * 'notMatchingTag' => remove cache entries not matching one of the given tags
     *                     ($tags can be an array of strings or a single string)
     *
     * @param  string $mode Clean mode
     * @param  array  $tags Array of tags
     * @return boolean True if no problem
     */
    public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
    
        if ($mode==Zend_Cache::CLEANING_MODE_ALL) 
            return $this->_memcache->flush();
        
        if ($mode==Zend_Cache::CLEANING_MODE_OLD) 
            $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend");
        
        if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) 
            $siteTags = $newTags = $this->getTags();
            if(count($siteTags))
            
                foreach($tags as $tag)
                
                    if(isset($siteTags[$tag]))
                    
                        foreach($siteTags[$tag] as $item)
                        
                            // We call delete directly here because the ID in the cache is already specific for this site
                            $this->_memcache->delete($item);
                        
                        unset($newTags[$tag]);
                    
                
                $this->_memcache->set($this->getTagListId(),$newTags);
            
        
        if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) 
            $siteTags = $newTags = $this->getTags();
            if(count($siteTags))
            
                foreach($siteTags as $siteTag => $items)
                
                    if(array_search($siteTag,$tags) === false)
                    
                        foreach($items as $item)
                        
                            $this->_memcache->delete($item);
                        
                        unset($newTags[$siteTag]);
                    
                
                $this->_memcache->set($this->getTagListId(),$newTags);
            
        
    

当由于内部 memcache 键丢弃而可能丢弃标记键,从而使大量实际有效键(仍存在)失效时,无法控制哪些键无效 写入并发问题

Two-step cache system

// Having one slow, and one fast cache mechanism where the slow cache is reliable storage  containing a copy of tag versions 
$cache_using_file['tag1'] = 'version1';
$cache_using_memcache['key'] = array('data' = 'abc', 'tags' => array('tag1' => 'version1');
使用磁盘/mysql 等进行慢速缓存的潜在瓶颈 写入并发问题

【问题讨论】:

我是命名空间的***粉丝。还要记住,键可以是 250 个字符长,所以如果你有一种生成命名空间的编程方式,你可以在一个键前面添加多个命名空间。 见***.com/questions/322709/php-memcache-design-patterns 请注意 David 的评论 - 您可以附加多个命名空间,例如:国家/地区 countrycode1235 的 id 为 pid1235 的某些产品的缓存键可以是 pid1235_PRODUCTVERSION_x_countrycode1235_COUNTRYVERSION_y。在此示例中,有两个版本:PRODUCTVERSION_x 和 COUNTRYVERSION_y。您可以通过更改 x(如果产品数据更改)和 y(如果与国家/地区相关的数据更改并且您的逻辑要求产品失效)来使密钥无效。 嗨乔恩!,我也有同样的问题。使用标记使 Memchached (***.com/questions/24162415/…) 失效的解决方法。我非常喜欢你的 KeyEnabled_Memcached 类。但是,自从您发布您的问题以来已经很长时间了,所以我想知道在将您的课程包含在我的代码中之前是否有任何更新我应该知道。谢谢!! 【参考方案1】:

见Organizing memcache keys

除非您可以“掌握关键”项目,否则没有理智的方法可以做到这一点。我的意思是“user4231-is_valid”之类的东西。您可以检查使用该用户数据的任何内容。否则,除非您正在跟踪引用您的文件的所有内容,否则您无法使所有这些文件无效。如果这样做,您仍然需要迭代所有可能性才能成功删除。

记录您的依赖关系,限制您的依赖关系,在您的代码中跟踪您的依赖关系以进行删除活动。

【讨论】:

【参考方案2】:

我没有使用 memcached 的经验,但我知道那里的 IO 很便宜。

我会选择你的标签实现,确保标签列表被频繁使用,并希望内部 mmcd' 逻辑会“认为”它太忙而不能删除:)

【讨论】:

您可以通过一些随机的健全性测试使其“模糊”,并在您发现不一致时使其失效...【参考方案3】:

看到评论here,它解释了驱逐现有密钥的逻辑,我相信可以通过PHP memcache design patterns中提到的版本标志方法可靠地实现标签

我实际上已经实现了一次这个逻辑,但是由于 memcache 在元素过期之前将其逐出,因此将其丢弃为不可靠。你可以找到我的初始实现here。但是,我相信这是一种可靠的标签模式,因为:

在缓存对象检索时,对象被移动到 LRU 堆栈的顶部 在缓存对象之后立即检索所有标签/标志 如果一个标志被驱逐,任何包含该标志的项目都将在之前被驱逐 增加标志会在同一操作中返回新值,从而避免写入并发。如果第二个线程在写入缓存对象之前递增相同的标志,则根据定义,它已经无效

如果我错了,请纠正我! :-)

【讨论】:

以上是关于使用标准 php 库使多个 memcache 键无效的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

memcache 简单入门应用

php开发memcached

markdown [php:SSO]单个登录在memcached共享会话的多个服务器上。 #php #cakephp #linux

添加分布式的多个memcached服务器

添加php的memcached扩展模块

php session memcache