共计 7061 个字符,预计需要花费 18 分钟才能阅读完成。
序言
咱们晓得 Laravel 中能够通过 cache::tag("xxx")->flush() 实现批量删除。然而删除的底层命令是啥(这里以 redis 为缓存驱动)? 是会造成梗塞的 keys 命令, 还是 scan 命令又或者就是一般的 del 命令?
不钻研源码的能够间接到最上面看总结
底层代码剖析
一、设置 tag 缓存底层代码剖析
1) 设置带有 tag 的缓存代码如下:
Cache::tags('foo')->put('bar', 'value');
2) 通过编辑器的全局搜寻咱们找到 tags 办法代码, 如下:
/**
* Begin executing a new tags operation.
*
* @param array|mixed $names
* @return \Illuminate\Cache\RedisTaggedCache
*/
public function tags($names)
{
return new RedisTaggedCache($this, new TagSet($this, is_array($names) ? $names : func_get_args())
);
}
咱们先看 TagSet 类, TagSet 类构造函数如下, 咱们能够看到在 TagSet 这个类中设置 2 了个成员变量
/**
* Create a new TagSet instance.
*
* @param \Illuminate\Contracts\Cache\Store $store
* @param array $names
* @return void
*/
public function __construct(Store $store, array $names = [])
{
$this->store = $store;
$this->names = $names;
}
咱们再回过头来看 RedisTaggedCache 类的构造函数, RedisTaggedCache 类并没有本人的构造函数, 然而他继承了 TaggedCache 类, TaggedCache 类构造函数如下, 这里先执行 TaggedCache 父类构造方法, 再来设置了 tags 变量. 咱们再来看看 TaggedCache 的父类的构造方法, 如下
/**
* Create a new tagged cache instance.
*
* @param \Illuminate\Contracts\Cache\Store $store
* @param \Illuminate\Cache\TagSet $tags
* @return void
*/
public function __construct(Store $store, TagSet $tags)
{parent::__construct($store);
$this->tags = $tags;
}
/**
* Create a new cache repository instance.
*
* @param \Illuminate\Contracts\Cache\Store $store
* @return void
*/
public function __construct(Store $store)
{$this->store = $store;}
这里咱们能够看到, 都只是设置成员变量, 并没有 redis 相干操作.
3) 那咱们再来看 put 办法的代码. 因为 Cache::tags()返回的是 RedisTaggedCache 类的对象, 咱们间接在这个类找, put()办法底层代码如下
/**
* Store an item in the cache.
*
* @param string $key
* @param mixed $value
* @param \DateTimeInterface|\DateInterval|int|null $ttl
* @return bool
*/
public function put($key, $value, $ttl = null)
{if ($ttl === null) {return $this->forever($key, $value);
}
$this->pushStandardKeys($this->tags->getNamespace(), $key);
return parent::put($key, $value, $ttl);
}
这里咱们就以 $ttl = null 这个条件来剖析这个办法, 咱们间接看 forever 的办法, 代码如下
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value)
{$this->pushForeverKeys($this->tags->getNamespace(), $key);
return parent::forever($key, $value);
}
持续跟踪 getNamespace()办法, 代码如下
/**
* Get a unique namespace that changes when any of the tags are flushed.
*
* @return string
*/
public function getNamespace()
{return implode('|', $this->tagIds());
}
/**
* Get an array of tag identifiers for all of the tags in the set.
*
* @return array
*/
protected function tagIds()
{return array_map([$this, 'tagId'], $this->names);
}
getNamespace()办法会去调用 tagIds()办法, tagIds()办法通过 array_map()把 names 数组中的每一个成员调用 tagId 办法. 在本文中 names 就是 [“foo”] 数组.
咱们再来看 tagId 办法, 代码如下
/**
* Get the unique tag identifier for a given tag.
*
* @param string $name
* @return string
*/
public function tagId($name)
{return $this->store->get($this->tagKey($name)) ?: $this->resetTag($name);
}
/**
* Get the tag identifier key for a given tag.
*
* @param string $name
* @return string
*/
public function tagKey($name)
{return 'tag:'.$name.':key';}
这里首先会去判断这个 tag key 有没有被设置, 如果被设置了间接返回这个 tag key 的值. 如果没有设置, 再调用 resetTag()办法. resetTag()办法代码如下
/**
* Reset the tag and return the new tag identifier.
*
* @param string $name
* @return string
*/
public function resetTag($name)
{$this->store->forever($this->tagKey($name), $id = str_replace('.', '', uniqid('', true)));
return $id;
}
这里先通过 php 自带的 uniqid 函数生成惟一 Id, 再通过 forever 函数设置缓存, 最终在 redis 显示如下, 最终 getNamespace()办法返回的就是是这个 uniqid 函数生成的值
127.0.0.1:6379[8]> get xxx_cache:tag:foo:key
"s:22:\"61f213827059d213700903\";"
搞清楚 getNamespace()办法之后, 咱们再回过头进入 pushForeverKeys()办法中, 代码如下
/**
* Store forever key references into store.
*
* @param string $namespace
* @param string $key
* @return void
*/
protected function pushForeverKeys($namespace, $key)
{$this->pushKeys($namespace, $key, self::REFERENCE_KEY_FOREVER);
}
持续进入到 pushKeys 办法中, 代码如下
/**
* Store a reference to the cache key against the reference key.
*
* @param string $namespace
* @param string $key
* @param string $reference
* @return void
*/
protected function pushKeys($namespace, $key, $reference)
{$fullKey = $this->store->getPrefix().sha1($namespace).':'.$key;
foreach (explode('|', $namespace) as $segment) {$this->store->connection()->sadd($this->referenceKey($segment, $reference), $fullKey);
}
}
最终咱们能够看到, 下面通过 uniqid 返回的值再拼接一个固定值当做 redis set 汇合中的 key, 汇合中的成员为: 我的项目前缀 + sha1 值 + key, 最终在 redis 中显示如下
127.0.0.1:6379[8]> smembers cache:61f213827059d213700903:forever_ref
1) "xxx_cache:9af69b7ac2b2a27d0ab2666b3e21204d878885d3:bar"
到这里 pushForeverKeys()办法就执行结束, 最初再执行父类的 forever 办法设置缓存.
/**
* Store an item in the cache indefinitely.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public function forever($key, $value)
{$this->pushForeverKeys($this->tags->getNamespace(), $key);
return parent::forever($key, $value);
}
到这里 Cache::tags(‘foo’)->put(‘bar’, ‘value’)就执行结束了
搞清楚 cache::tag 是如何设置缓存的, 那么 cache::tag()->flush()批量删除缓存也就不难了.
二、批量删除 tag 缓存底层代码剖析
1) 批量删除 tag 缓存代码如下
Cache::tags('foo')->flush();
2) Cache::tags(‘foo’)办法咱们在下面曾经剖析过了, 返回的是 RedisTaggedCache 类的对象, 间接找到 flush 办法代码
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush()
{$this->deleteForeverKeys();
$this->deleteStandardKeys();
return parent::flush();}
这里咱们只须要看 deleteForeverKeys()即可, 因为这 2 个调用的办法是一样的, 只是传入的参数不一样, 代码如下
/**
* Delete all of the items that were stored forever.
*
* @return void
*/
protected function deleteForeverKeys()
{$this->deleteKeysByReference(self::REFERENCE_KEY_FOREVER);
}
/**
* Delete all standard items.
*
* @return void
*/
protected function deleteStandardKeys()
{$this->deleteKeysByReference(self::REFERENCE_KEY_STANDARD);
}
咱们持续进入到 deleteKeysByReference()办法中
/**
* Find and delete all of the items that were stored against a reference.
*
* @param string $reference
* @return void
*/
protected function deleteKeysByReference($reference)
{foreach (explode('|', $this->tags->getNamespace()) as $segment) {$this->deleteValues($segment = $this->referenceKey($segment, $reference));
$this->store->connection()->del($segment);
}
}
持续进入到 deleteValues()办法中
/**
* Delete item keys that have been stored against a reference.
*
* @param string $referenceKey
* @return void
*/
protected function deleteValues($referenceKey)
{$values = array_unique($this->store->connection()->smembers($referenceKey));
if (count($values) > 0) {foreach (array_chunk($values, 1000) as $valuesChunk) {call_user_func_array([$this->store->connection(), 'del'], $valuesChunk);
}
}
}
从下面的代码咱们能够看到, 先是用 $this->deleteValues() 删除这个 tag 汇合中每个成员的缓存, 再用 $this->store->connection()->del()删除这个 tag 汇合.
这里咱们须要留神, 在下面的 flush()办法最初一行代码, 调用了父类的 flush 办法, 父类的 flush 办法代码如下:
/**
* Remove all items from the cache.
*
* @return bool
*/
public function flush()
{$this->tags->reset();
return true;
}
持续进入到 reset()办法中
/**
* Reset all tags in the set.
*
* @return void
*/
public function reset()
{array_walk($this->names, [$this, 'resetTag']);
}
/**
* Reset the tag and return the new tag identifier.
*
* @param string $name
* @return string
*/
public function resetTag($name)
{$this->store->forever($this->tagKey($name), $id = str_replace('.', '', uniqid('', true)));
return $id;
}
从下面代码中能够看到, 会从新设置 tag 的 key 值, 如下
之前:
127.0.0.1:6379[8]> get xxx_cache:tag:foo:key
"s:22:\"61f213827059d213700903\";"
当初:
127.0.0.1:6379[8]> get xxx_cache:tag:foo:key
"s:22:\"62032a0620195884559057\";
三、总结
laravel 中带 tag 设置缓存代码流程如下:
这里以 Cache::tags('foo')->put('bar', 'value')为例
1) 先设置 tag 缓存, key 为本人定义的 tag(本文就是 foo), 值为 uniqid()函数生成的值
127.0.0.1:6379[8]> get xxx_cache:tag:foo:key
"s:22:\"61f213827059d213700903\";"
2) 通过下面的 uniqid 值, 找到这个 uniqid 值对应的汇合(没有则创立汇合),
127.0.0.1:6379[8]> type xxx_cache:61f213827059d213700903:forever_ref
set
3)把要设置的 key 值 (本文就是 bar) 退出到下面的这个汇合(不便后续的批量删除)
127.0.0.1:6379[8]> smembers xxx_cache:61f213827059d213700903:forever_ref
1) "xxx_cache:9af69b7ac2b2a27d0ab2666b3e21204d878885d3:bar"
4) 再设置 key 缓存
laravel 中带 tag 批量删除缓存代码流程如下:
1) 先删除 tag 汇合中每个成员的缓存
2) 再删除这个 tag 汇合