序言
咱们晓得 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_ref1) "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_refset
3)把要设置的key值(本文就是bar)退出到下面的这个汇合(不便后续的批量删除)
127.0.0.1:6379[8]> smembers xxx_cache:61f213827059d213700903:forever_ref1) "xxx_cache:9af69b7ac2b2a27d0ab2666b3e21204d878885d3:bar"
4) 再设置key缓存
laravel中带tag批量删除缓存代码流程如下:
1) 先删除tag汇合中每个成员的缓存
2) 再删除这个tag汇合