Learn Zend Memcache to work with tags
In a project where I am the developer, the cache is used. Just want to mention, the project is heavily loaded, about two thousand people a day. A convenient solution to relieve the load on the database was the use memcache. Since the project on Zend Framework the implementation of the cache accordingly took it. But as it turned out not a good implementation, because the completely missing work with tags, it does not give us the opportunity to clean the cache selectively.
How to implement a custom cache? It's very simple, create a container in which are recorded the names of the tags, they will be keys for other containers, they will store our hashes aydishnikov cached methods. For the cache frontend has been taken Zend_Cache_Frontend_Class. He generates hashes of data keys cached methods.
It looks as follows:

Especially not going into a description of the implementation ready solution.
Create a file backend My_Cache_Backend_Memcached
the
How to use?
Here is the example code, I warn you configure the backend and the frontend can pass in a different way, given the code only to understand how to use it.
the
Instead of the standard backend "Memcached" you need to pass your class backend "My_Cache_Backend_Memcached", you must also specify $customBackendNaming = true this is the 6th parameter in the call to the factory Zend_Cache::factory.
Default_Models_MemcacheData1 and Default_Models_MemcacheData2 is our cached classes, they are completely identical. Here is an example of one of them:
the
As you can see from the code each time you call the method cachedMethod we need to receive the random value.
the
When you run the code we get something similar to the following:
Check the cache in action by running the following code
the
The situation will change, we will receive approximately the following data:
In the subsequent the situation will not change.
For clearing cache I use the following code
the
Make sure that the cache be cleaned according to the transmitted
The problem of selective clearing cache was solved, there was a performance increase.
* thanks homm for the comments. Now life time for tags is computed based on the settings or if the passed parameter $specificLifetime more then he does. Also added check for changing the General container, if the tags have changed.
p.s. I want to warn the php developers to use this code is at your own risk. This code can be a panacea for particular cases. And is not explosive.
Using this code, remember that the tag names are also key, as are the passed id.
Article based on information from habrahabr.ru
How to implement a custom cache? It's very simple, create a container in which are recorded the names of the tags, they will be keys for other containers, they will store our hashes aydishnikov cached methods. For the cache frontend has been taken Zend_Cache_Frontend_Class. He generates hashes of data keys cached methods.
It looks as follows:

Tags_Container is the global name of the container, we set it constant.
MemcacheTag1, MemcacheTag2 and MemcacheTagN is the names of the saved tags will be in Tags_Container in an array, they will be hashed containers a aydishnik.
Especially not going into a description of the implementation ready solution.
Create a file backend My_Cache_Backend_Memcached
the
class Zend_Cache_Backend_Memcached extends My_Cache_Backend_Memcached
{
/**
* @const string
*/
const TAGS_CONTAINER_NAME = 'Tags_Container';
/**
* @return array
*/
protected function _getTagsContainer()
{
$tagsContainer = $this->load(self::TAGS_CONTAINER_NAME);
if (false === $tagsContainer) {
$tagsContainer = array();
}
if (is_string($tagsContainer)) {
$tagsContainer = array($tagsContainer);
}
return $tagsContainer;
}
/**
* @param $tagName
*
* @return array
*/
protected function _getIdsByTag($tagName)
{
$tagIds = $this->load($tagName);
if (false === $tagIds) {
$tagIds = array();
}
if (is_string($tagIds)) {
$tagIds = array($tagIds);
}
return $tagIds;
}
/**
* 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 bool|int $specificLifetime If != false, set a specific lifetime
* for this cache record (null => infinite lifetime)
* @return bool True if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime = false)
{
$lifetime = $this->getLifetime($specificLifetime);
$tagsLifetime = $this->getLifetime(false);
if ($lifetime > $tagsLifetime) {
$tagsLifetime = $lifetime;
}
if ($this->_options['compression']) {
$flag = MEMCACHE_COMPRESSED;
} else {
$flag = 0;
}
$result = true;
if (count($tags) > 0) {
$tagsContainer = $this->_getTagsContainer();
$containerChanged = false;
foreach($tags as $tagName) {
if ($tagName == self::TAGS_CONTAINER_NAME) {
Zend_Cache::throwException('Incorrect name tag "' . $tagName . '"');
}
if (in_array($id, $tagsContainer)) {
Zend_Cache::throwException('The key with id = "' . $id . '" already used in the tags');
}
if (!in_array($tagName, $tagsContainer)) {
$containerChanged = true;
$tagsContainer[] = $tagName;
}
$tagIds = $this->_getIdsByTag($tagName);
if (!in_array($id, $tagIds)) {
$tagIds[] = $id;
}
$result = $result && @$this- > _memcache- > set(
$tagName, array($tagIds), $flag, $tagsLifetime
);
}
if ($containerChanged) {
$result = $result && @$this- > _memcache- > set(
self::TAGS_CONTAINER_NAME,
array($tagsContainer), $flag, $tagsLifetime
);
}
}
// ZF-8856: using set because add needs a second request if item already exists
$result = $result && @$this- > _memcache- > set(
$id, array($data, time(), $lifetime), $flag, $lifetime
);
return $result;
}
/**
* @param string $mode
* @param array $tags
*
* @return array
*/
protected function _get($mode, $tags = array())
{
if (is_string($tags)) {
$tags = array($tags);
}
$tagNames = $this->_getTagsContainer();
switch($mode) {
case 'ids':
break;
case 'tags':
$tagNames = array_intersect($tagNames, $tags);
break;
case 'matching':
$tagNames = array_intersect($tagNames, $tags);
break;
case 'notMatching':
$tagNames = array_diff($tagNames, $tags);
break;
default:
Zend_Cache::throwException('Invalid mode for _get() method');
break;
}
$ids = array();
foreach($tagNames as $tagName) {
$ids = array_merge($this->_getIdsByTag($tagName), $ids);
}
return $ids;
}
/**
* Return an array of stored cache ids
*
* @return array
public function getIds()
{
return $this->_get('ids', array());
}
/**
* Return an array of stored tags
*
* @return array
*/
public function getTags()
{
return $this->_get('tags', array());
}
/**
* Return an array of stored cache ids which match given tags
*
* In case of multiple tags, a logical AND is made between tags
*
* @param array $tags array of tags
*
* @return array
*/
public function getIdsMatchingTags($tags = array())
{
return $this->_get('matching', $tags);
}
/**
* Return an array of stored cache ids which don't match given tags
*
* In case of multiple tags, a logical OR is made between tags
*
* @param array $tags array of tags
*
* @return array
*/
public function getIdsNotMatchingTags($tags = array())
{
return $this->_get('notMatching', $tags);
}
/**
* Return an associative array of capabilities (booleans) of the backend
*
* @return array associative of with capabilities
*/
public function getCapabilities()
{
$capabilities = parent::getCapabilities();
$capabilities['tags'] = true;
return $capabilities;
}
/**
* @param string $mode
* @param array $tags
*
* @return bool
*/
protected function _clean($mode, $tags)
{
$result = false;
switch ($mode) {
case Zend_Cache::CLEANING_MODE_ALL:
$result = $this- > _memcache- > flush();
break;
case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
$ids = $this->getIdsMatchingTags($tags);
$result = true;
foreach($ids as $id) {
$result = $result && $this->remove($id);
}
break;
case Zend_Cache::CLEANING:
$ids = $this->getIdsNotMatchingTags($tags);
$result = true;
foreach($ids as $id) {
$result = $result && $this->remove($id);
}
break;
case Zend_Cache::CLEANING_MODE_OLD:
case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
$this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND);
break;
default:
Zend_Cache::throwException('Invalid mode for clean() method');
break;
}
return $result;
}
/**
* @param string $mode
* @param array $tags
*
* @return mixed
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
return $this- > _clean($mode, $tags);
}
}
How to use?
Here is the example code, I warn you configure the backend and the frontend can pass in a different way, given the code only to understand how to use it.
the
$frontendName = 'Class';
$backendName = 'My_Cache_Backend_Memcached';
$frontendOptions = array();
$memcacheDataObject1 = new Default_Models_MemcacheData1;
$memcacheDataObject2 = new Default_Models_MemcacheData2;
$backendOptions = array(
'servers' => array(
array(
'host' => '127.0.0.1',
'port' => '11211',
'persistent' => 1,
'weight' = > 5,
'timeout' => 5,
'retry_interval' = > 15
)
)
);
$frontendOptions['cached_entity'] = $memcacheDataObject1;
$cachedObject1 = Zend_Cache::factory(
$frontendName,
$backendName,
$frontendOptions,
$backendOptions,
false,
true
);
$cachedObject1->setTagsArray(array('Memcached_Tag1'));
$frontendOptions['cached_entity'] = $memcacheDataObject2;
$cachedObject2 = Zend_Cache::factory(
$frontendName,
$backendName,
$frontendOptions,
$backendOptions,
false,
true
);
$cachedObject2->setTagsArray(array('Memcached_Tag2'));
Instead of the standard backend "Memcached" you need to pass your class backend "My_Cache_Backend_Memcached", you must also specify $customBackendNaming = true this is the 6th parameter in the call to the factory Zend_Cache::factory.
Default_Models_MemcacheData1 and Default_Models_MemcacheData2 is our cached classes, they are completely identical. Here is an example of one of them:
the
class Default_Models_MemcacheData1
{
public function cachedMethod()
{
return rand(111, 999);
}
}
As you can see from the code each time you call the method cachedMethod we need to receive the random value.
the
for ($i = 0; $i < 3; $i++)
{
Zend_Debug::dump($memcacheDataObject1- > cachedMethod(), 'cached data:');
}
for ($i = 0; $i < 3; $i++)
{
Zend_Debug::dump($memcacheDataObject2- > cachedMethod(), 'cached data:');
}
When you run the code we get something similar to the following:
cached data: int(468)
cached data: int(676)
сached data: int(721)
сached data: int(182)
cached data: int(414)
cached data: int(561)
Check the cache in action by running the following code
the
for ($i = 0; $i < 3; $i++)
{
Zend_Debug::dump($cachedObject1- > cachedMethod(), 'cached data:');
}
for ($i = 0; $i < 3; $i++)
{
Zend_Debug::dump($cachedObject2- > cachedMethod(), 'cached data:');
}
The situation will change, we will receive approximately the following data:
cached data: int(901)
cached data: int(901)
cached data: int(901)
cached data: int(865)
cached data: int(865)
cached data: int(865)
In the subsequent the situation will not change.
For clearing cache I use the following code
the
$cachedClass1->clean(
Zend_Cache::CLEANING_MODE_MATCHING_TAG,
array('Memcached_Tag1')
);
Make sure that the cache be cleaned according to the transmitted
The problem of selective clearing cache was solved, there was a performance increase.
* thanks homm for the comments. Now life time for tags is computed based on the settings or if the passed parameter $specificLifetime more then he does. Also added check for changing the General container, if the tags have changed.
p.s. I want to warn the php developers to use this code is at your own risk. This code can be a panacea for particular cases. And is not explosive.
Using this code, remember that the tag names are also key, as are the passed id.
Комментарии
Отправить комментарий