介紹
緩存是提升應(yīng)用性能的常用手段,為框架中最通用的功能,每個(gè)框架也都推出專屬的、功能多
樣的緩存庫(kù)。這些差別使得開發(fā)人員不得不學(xué)習(xí)多種系統(tǒng),而很多可能是他們并不需要的功能。
此外,緩存庫(kù)的開發(fā)者同樣面臨著一個(gè)窘境,是只支持有限數(shù)量的幾個(gè)框架還是創(chuàng)建一堆龐
大的適配器類。
一個(gè)通用的緩存系統(tǒng)接口可以解決掉這些問題。庫(kù)和框架的開發(fā)人員能夠知道緩存系統(tǒng)會(huì)按照他們所
預(yù)期的方式工作,緩存系統(tǒng)的開發(fā)人員只需要實(shí)現(xiàn)單一的接口,而不用去開發(fā)各種各樣的適配器。
目標(biāo)
本 PSR 的目標(biāo)是:創(chuàng)建一套通用的接口規(guī)范,能夠讓開發(fā)人員整合到現(xiàn)有框架和系統(tǒng),而不需要去
開發(fā)框架專屬的適配器類。
關(guān)于「能愿動(dòng)詞」的使用
為了避免歧義,文檔大量使用了「能愿動(dòng)詞」,對(duì)應(yīng)的解釋如下:
-
必須 (MUST):絕對(duì),嚴(yán)格遵循,請(qǐng)照做,無條件遵守; -
一定不可 (MUST NOT):禁令,嚴(yán)令禁止; -
應(yīng)該 (SHOULD):強(qiáng)烈建議這樣做,但是不強(qiáng)求; -
不該 (SHOULD NOT):強(qiáng)烈不建議這樣做,但是不強(qiáng)求; -
可以 (MAY)和可選 (OPTIONAL):選擇性高一點(diǎn),在這個(gè)文檔內(nèi),此詞語(yǔ)使用較少;
參見:RFC 2119
定義
調(diào)用類庫(kù) (Calling Library) - 調(diào)用者,使用緩存服務(wù)的類庫(kù),這個(gè)類庫(kù)調(diào)用緩存服務(wù),調(diào)用的
是此緩存接口規(guī)范的具體「實(shí)現(xiàn)類庫(kù)」,調(diào)用者不需要知道任何「緩存服務(wù)」的具體實(shí)現(xiàn)。實(shí)現(xiàn)類庫(kù) (Implementing Library) - 此類庫(kù)是對(duì)「緩存接口規(guī)范」的具體實(shí)現(xiàn),封裝起來的緩存服務(wù),供「調(diào)用類庫(kù)」使用。實(shí)現(xiàn)類庫(kù) 必須 提供 PHP 類來實(shí)現(xiàn)
Cache\\CacheItemPoolInterface和Cache\\CacheItemInterface接口。
實(shí)現(xiàn)類庫(kù) 必須 支持最小的如下描述的 TTL 功能,秒級(jí)別的精準(zhǔn)度。生存時(shí)間值 (TTL - Time To Live) - 定義了緩存可以存活的時(shí)間,以秒為單位的整數(shù)值。
-
過期時(shí)間 (Expiration) - 定義準(zhǔn)確的過期時(shí)間點(diǎn),一般為緩存存儲(chǔ)發(fā)生的時(shí)間點(diǎn)加上 TTL 時(shí)
間值,也可以指定一個(gè) DateTime 對(duì)象。假如一個(gè)緩存項(xiàng)的 TTL 設(shè)置為 300 秒,保存于 1:30:00 ,那么緩存項(xiàng)的過期時(shí)間為 1:35:00。
實(shí)現(xiàn)類庫(kù) 可以 讓緩存項(xiàng)提前過期,但是 必須 在到達(dá)過期時(shí)間時(shí)立即把緩存項(xiàng)標(biāo)示為
過期。如果調(diào)用類庫(kù)在保存一個(gè)緩存項(xiàng)的時(shí)候未設(shè)置「過期時(shí)間」、或者設(shè)置了null作為過期
時(shí)間(或者 TTL 設(shè)置為null),實(shí)現(xiàn)類庫(kù) 可以 使用默認(rèn)自行配置的一個(gè)時(shí)間。如果沒
有默認(rèn)時(shí)間,實(shí)現(xiàn)類庫(kù) 必須把存儲(chǔ)時(shí)間當(dāng)做永久性存儲(chǔ),或者按照底層驅(qū)動(dòng)能支持的
最長(zhǎng)時(shí)間作為保持時(shí)間。 鍵 (KEY) - 長(zhǎng)度大于 1 的字串,用作緩存項(xiàng)在緩存系統(tǒng)里的唯一標(biāo)識(shí)符。實(shí)現(xiàn)類庫(kù)
必須 支持「鍵」規(guī)則A-Z,a-z,0-9,_, 和.任何順序的 UTF-8 編碼,長(zhǎng)度
小于 64 位。實(shí)現(xiàn)類庫(kù) 可以 支持更多的編碼或者更長(zhǎng)的長(zhǎng)度,不過 必須 支持至少以上指定
的編碼和長(zhǎng)度。實(shí)現(xiàn)類庫(kù)可自行實(shí)現(xiàn)對(duì)「鍵」的轉(zhuǎn)義,但是 必須 保證能夠無損的返回「鍵」字串。以下
的字串作為系統(tǒng)保留:{}()/\\@:,一定不可 作為「鍵」的命名支持。命中 (Hit) - 一個(gè)緩存的命中,指的是當(dāng)調(diào)用類庫(kù)使用「鍵」在請(qǐng)求一個(gè)緩存項(xiàng)的時(shí)候,在緩存
池里能找到對(duì)應(yīng)的緩存項(xiàng),并且此緩存項(xiàng)還未過期,并且此數(shù)據(jù)不會(huì)因?yàn)槿魏卧虺霈F(xiàn)錯(cuò)誤。調(diào)用類
庫(kù) 應(yīng)該 確保先驗(yàn)證下isHit()有命中后才調(diào)用get()獲取數(shù)據(jù)。未命中 (Miss) - 一個(gè)緩存未命中,是完全的上面描述的「命中」的相反。指的是當(dāng)調(diào)用類庫(kù)使用「鍵」在請(qǐng)求一個(gè)緩存項(xiàng)的時(shí)候,在緩存池里未能找到對(duì)應(yīng)的緩存項(xiàng),或者此緩存項(xiàng)已經(jīng)過期,或者此數(shù)據(jù)因?yàn)槿魏卧虺霈F(xiàn)錯(cuò)誤。一個(gè)過期的緩存項(xiàng),必須 被當(dāng)做
未命中來對(duì)待。延遲 (Deferred) - 一個(gè)延遲的緩存,指的是這個(gè)緩存項(xiàng)可能不會(huì)立刻被存儲(chǔ)到物理緩存池里。一個(gè)
緩存池對(duì)象 可以 對(duì)一個(gè)指定延遲的緩存項(xiàng)進(jìn)行延遲存儲(chǔ),這樣做的好處是可以利用一些緩存服務(wù)器提供
的批量插入功能。緩存池 必須 能對(duì)所有延遲緩存最終能持久化,并且不會(huì)丟失。可以 在調(diào)用類庫(kù)還未發(fā)起保存請(qǐng)求之前就做持久化。當(dāng)調(diào)用類庫(kù)調(diào)用commit()方法時(shí),所有的延遲緩存都 必須
做持久化。實(shí)現(xiàn)類庫(kù) 可以 自行決定使用什么邏輯來觸發(fā)數(shù)據(jù)持久化,如對(duì)象的析構(gòu)方法 (destructor)內(nèi)、調(diào)用save()時(shí)持久化、倒計(jì)時(shí)保存或者觸及最大數(shù)量時(shí)保存等。當(dāng)請(qǐng)求一個(gè)延遲
緩存項(xiàng)時(shí),必須 返回一個(gè)延遲,未持久化的緩存項(xiàng)對(duì)象。
數(shù)據(jù)
實(shí)現(xiàn)類庫(kù) 必須 支持所有的可序列化的 PHP 數(shù)據(jù)類型,包含:
- 字符串 - 任何大小的 PHP 兼容字符串
- 整數(shù) - PHP 支持的低于 64 位的有符號(hào)整數(shù)值
- 浮點(diǎn)數(shù) - 所有的有符號(hào)浮點(diǎn)數(shù)
- 布爾 - true 和 false.
-
Null -
null值 - 數(shù)組 - 各種形式的 PHP 數(shù)組
-
對(duì)象(Object) - 所有的支持無損序列化和反序列化的對(duì)象,如:
$o == unserialize(serialize($o))。對(duì)象 可以
使用 PHP 的Serializable接口,__sleep()或者__wakeup()魔術(shù)方法,或者在合適的情況下,使用其他類似的語(yǔ)言特性。
所有存進(jìn)實(shí)現(xiàn)類庫(kù)的數(shù)據(jù),都 必須 能做到原封不動(dòng)的取出。連類型也 必須 是完全一致,如果
存進(jìn)緩存的是字符串 5,取出來的卻是整數(shù)值 5 的話,可以算作嚴(yán)重的錯(cuò)誤。實(shí)現(xiàn)類庫(kù) 可以 使用 PHP 的「serialize()/unserialize() 方法」作為底層實(shí)現(xiàn),不過不強(qiáng)迫這樣做。對(duì)于他們的兼容性,以能支持所有數(shù)據(jù)類型作為基準(zhǔn)線。
實(shí)在無法「完整取出」存入的數(shù)據(jù)的話,實(shí)現(xiàn)類庫(kù) 必須 把「緩存丟失」標(biāo)示作為返回,而不是損壞了的數(shù)據(jù)。
主要概念
緩存池 Pool
緩存池包含緩存系統(tǒng)里所有緩存數(shù)據(jù)的集合。緩存池邏輯上是所有緩存項(xiàng)存儲(chǔ)的倉(cāng)庫(kù),所有存儲(chǔ)進(jìn)去的數(shù)據(jù),
都能從緩存池里取出來,所有的對(duì)緩存的操作,都發(fā)生在緩存池子里。
緩存項(xiàng) Items
一條緩存項(xiàng)在緩存池里代表了一對(duì)「鍵/值」對(duì)應(yīng)的數(shù)據(jù),「鍵」被視為每一個(gè)緩存項(xiàng)主鍵,是緩存項(xiàng)的
唯一標(biāo)識(shí)符,必須 是不可變更的,當(dāng)然,「值」可以 任意變更。
錯(cuò)誤處理
緩存對(duì)應(yīng)用性能起著至關(guān)重要的作用,但是,無論在任何情況下,緩存 一定不可 作為應(yīng)用程序不
可或缺的核心功能。
緩存系統(tǒng)里的錯(cuò)誤 一定不可 導(dǎo)致應(yīng)用程序故障,所以,實(shí)現(xiàn)類庫(kù) 一定不可 拋出任何除了
此接口規(guī)范定義的以外的異常,并且 必須 捕捉包括底層存儲(chǔ)驅(qū)動(dòng)拋出的異常,不讓其冒泡至超
出緩存系統(tǒng)內(nèi)。
實(shí)現(xiàn)類庫(kù) 應(yīng)該 對(duì)此類錯(cuò)誤進(jìn)行記錄,或者以任何形式通知管理員。
調(diào)用類庫(kù)發(fā)起刪除緩存項(xiàng)的請(qǐng)求,或者清空整個(gè)緩沖池子的請(qǐng)求,「鍵」不存在的話 必須 不能
當(dāng)成是有錯(cuò)誤發(fā)生。后置條件是一樣的,如果取數(shù)據(jù)時(shí),「鍵」不存在的話 必須 不能當(dāng)成是有錯(cuò)誤發(fā)生
接口
CacheItemInterface
CacheItemInterface 定義了緩存系統(tǒng)里的一個(gè)緩存項(xiàng)。每一個(gè)緩存項(xiàng) 必須 有一個(gè)「鍵」與之相
關(guān)聯(lián),此「鍵」通常是通過 Cache\CacheItemPoolInterface 來設(shè)置。
Cache\CacheItemInterface 對(duì)象把緩存項(xiàng)的存儲(chǔ)進(jìn)行了封裝,每一個(gè) Cache\CacheItemInterface 由一個(gè) Cache\CacheItemPoolInterface 對(duì)象生成,CacheItemPoolInterface 負(fù)責(zé)一些必須的設(shè)置,并且給對(duì)象設(shè)置具有 唯一性 的「鍵」。
Cache\CacheItemInterface 對(duì)象 必須 能夠存儲(chǔ)和取出任何類型的,在「數(shù)據(jù)」章節(jié)定義的 PHP 數(shù)值。
調(diào)用類庫(kù) 一定不可 擅自初始化「CacheItemInterface」對(duì)象,「緩存項(xiàng)」只能使用「CacheItemPoolInterface」對(duì)象的 getItem() 方法來獲取。調(diào)用類庫(kù) 一定不可 假設(shè)
由一個(gè)實(shí)現(xiàn)類庫(kù)創(chuàng)建的「緩存項(xiàng)」能被另一個(gè)實(shí)現(xiàn)類庫(kù)完全兼容。
namespace Psr\\Cache;
/**
* CacheItemInterface 定了緩存系統(tǒng)里對(duì)緩存項(xiàng)操作的接口
*/
interface CacheItemInterface
{
/**
* 返回當(dāng)前緩存項(xiàng)的「鍵」
*
* 「鍵」由實(shí)現(xiàn)類庫(kù)來加載,并且高層的調(diào)用者(如:CacheItemPoolInterface)
* **應(yīng)該** 能使用此方法來獲取到「鍵」的信息。
*
* @return string
* 當(dāng)前緩存項(xiàng)的「鍵」
*/
public function getKey();
/**
* 憑借此緩存項(xiàng)的「鍵」從緩存系統(tǒng)里面取出緩存項(xiàng)。
*
* 取出的數(shù)據(jù) **必須** 跟使用 `set()` 存進(jìn)去的數(shù)據(jù)是一模一樣的。
*
* 如果 `isHit()` 返回 false 的話,此方法必須返回 `null`,需要注意的是 `null`
* 本來就是一個(gè)合法的緩存數(shù)據(jù),所以你 **應(yīng)該** 使用 `isHit()` 方法來辨別到底是
* "返回 null 數(shù)據(jù)" 還是 "緩存里沒有此數(shù)據(jù)"。
*
* @return mixed
* 此緩存項(xiàng)的「鍵」對(duì)應(yīng)的「值」,如果找不到的話,返回 `null`
*/
public function get();
/**
* 確認(rèn)緩存項(xiàng)的檢查是否命中。
*
* 注意: 調(diào)用此方法和調(diào)用 `get()` 時(shí) **一定不可** 有先后順序之分。
*
* @return bool
* 如果緩沖池里有命中的話,返回 `true`,反之返回 `false`
*/
public function isHit();
/**
* 為此緩存項(xiàng)設(shè)置「值」。
*
* 參數(shù) $value 可以是所有能被 PHP 序列化的數(shù)據(jù),序列化的邏輯
* 需要在實(shí)現(xiàn)類庫(kù)里書寫。
*
* @param mixed $value
* 將被存儲(chǔ)的可序列化的數(shù)據(jù)。
*
* @return static
* 返回當(dāng)前對(duì)象。
*/
public function set($value);
/**
* 設(shè)置緩存項(xiàng)的準(zhǔn)確過期時(shí)間點(diǎn)。
*
* @param \\DateTimeInterface $expiration
*
* 過期的準(zhǔn)確時(shí)間點(diǎn),過了這個(gè)時(shí)間點(diǎn)后,緩存項(xiàng)就 **必須** 被認(rèn)為是過期了的。
* 如果明確的傳參 `null` 的話,**可以** 使用一個(gè)默認(rèn)的時(shí)間。
* 如果沒有設(shè)置的話,緩存 **應(yīng)該** 存儲(chǔ)到底層實(shí)現(xiàn)的最大允許時(shí)間。
*
* @return static
* 返回當(dāng)前對(duì)象。
*/
public function expiresAt($expiration);
/**
* 設(shè)置緩存項(xiàng)的過期時(shí)間。
*
* @param int|\\DateInterval $time
* 以秒為單位的過期時(shí)長(zhǎng),過了這段時(shí)間后,緩存項(xiàng)就 **必須** 被認(rèn)為是過期了的。
* 如果明確的傳參 `null` 的話,**可以** 使用一個(gè)默認(rèn)的時(shí)間。
* 如果沒有設(shè)置的話,緩存 **應(yīng)該** 存儲(chǔ)到底層實(shí)現(xiàn)的最大允許時(shí)間。
*
* @return static
* 返回當(dāng)前對(duì)象
*/
public function expiresAfter($time);
}
CacheItemPoolInterface
Cache\CacheItemPoolInterface 的主要目的是從調(diào)用類庫(kù)接收「鍵」,然后返回對(duì)應(yīng)的 Cache\CacheItemInterface 對(duì)象。
此接口也是作為主要的,與整個(gè)緩存集合交互的方式。所有的配置和初始化由實(shí)現(xiàn)類庫(kù)自行實(shí)現(xiàn)。
namespace Psr\\Cache;
/**
* CacheItemPoolInterface 生成 CacheItemInterface 對(duì)象
*/
interface CacheItemPoolInterface
{
/**
* 返回「鍵」對(duì)應(yīng)的一個(gè)緩存項(xiàng)。
*
* 此方法 **必須** 返回一個(gè) CacheItemInterface 對(duì)象,即使是找不到對(duì)應(yīng)的緩存項(xiàng)
* 也 **一定不可** 返回 `null`。
*
* @param string $key
* 用來搜索緩存項(xiàng)的「鍵」。
*
* @throws InvalidArgumentException
* 如果 $key 不是合法的值,\\Psr\\Cache\\InvalidArgumentException 異常會(huì)被拋出。
*
* @return CacheItemInterface
* 對(duì)應(yīng)的緩存項(xiàng)。
*/
public function getItem($key);
/**
* 返回一個(gè)可供遍歷的緩存項(xiàng)集合。
*
* @param array $keys
* 由一個(gè)或者多個(gè)「鍵」組成的數(shù)組。
*
* @throws InvalidArgumentException
* 如果 $keys 里面有哪個(gè)「鍵」不是合法,\\Psr\\Cache\\InvalidArgumentException 異常
* 會(huì)被拋出。
*
* @return array|\\Traversable
* 返回一個(gè)可供遍歷的緩存項(xiàng)集合,集合里每個(gè)元素的標(biāo)識(shí)符由「鍵」組成,即使即使是找不到對(duì)
* 的緩存項(xiàng),也要返回一個(gè)「CacheItemInterface」對(duì)象到對(duì)應(yīng)的「鍵」中。
* 如果傳參的數(shù)組為空,也需要返回一個(gè)空的可遍歷的集合。
*/
public function getItems(array $keys = array());
/**
* 檢查緩存系統(tǒng)中是否有「鍵」對(duì)應(yīng)的緩存項(xiàng)。
*
* 注意: 此方法應(yīng)該調(diào)用 `CacheItemInterface::isHit()` 來做檢查操作,而不是
* `CacheItemInterface::get()`
*
* @param string $key
* 用來搜索緩存項(xiàng)的「鍵」。
*
* @throws InvalidArgumentException
* 如果 $key 不是合法的值,\\Psr\\Cache\\InvalidArgumentException 異常會(huì)被拋出。
*
* @return bool
* 如果存在「鍵」對(duì)應(yīng)的緩存項(xiàng)即返回 true,否則 false
*/
public function hasItem($key);
/**
* 清空緩沖池
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function clear();
/**
* 從緩沖池里移除某個(gè)緩存項(xiàng)
*
* @param string $key
* 用來搜索緩存項(xiàng)的「鍵」。
*
* @throws InvalidArgumentException
* 如果 $key 不是合法的值,\\Psr\\Cache\\InvalidArgumentException 異常會(huì)被拋出。
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function deleteItem($key);
/**
* 從緩沖池里移除多個(gè)緩存項(xiàng)
*
* @param array $keys
* 由一個(gè)或者多個(gè)「鍵」組成的數(shù)組。
*
* @throws InvalidArgumentException
* 如果 $keys 里面有哪個(gè)「鍵」不是合法,\\Psr\\Cache\\InvalidArgumentException 異常
* 會(huì)被拋出。
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function deleteItems(array $keys);
/**
* 立刻為「CacheItemInterface」對(duì)象做數(shù)據(jù)持久化。
*
* @param CacheItemInterface $item
* 將要被存儲(chǔ)的緩存項(xiàng)
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function save(CacheItemInterface $item);
/**
* 稍后為「CacheItemInterface」對(duì)象做數(shù)據(jù)持久化。
*
* @param CacheItemInterface $item
* 將要被存儲(chǔ)的緩存項(xiàng)
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function saveDeferred(CacheItemInterface $item);
/**
* 提交所有的正在隊(duì)列里等待的請(qǐng)求到數(shù)據(jù)持久層,配合 `saveDeferred()` 使用
*
* @return bool
* 成功返回 true,有錯(cuò)誤發(fā)生返回 false
*/
public function commit();
}
CacheException
此異常用于緩存系統(tǒng)發(fā)生的所有嚴(yán)重錯(cuò)誤,包括但不限制于 緩存系統(tǒng)配置,如連接到緩存服務(wù)器出錯(cuò)、錯(cuò)
誤的用戶身份認(rèn)證等。
所有的實(shí)現(xiàn)類庫(kù)拋出的異常都 必須 實(shí)現(xiàn)此接口。
namespace Psr\\Cache;
/**
* 被所有的實(shí)現(xiàn)類庫(kù)拋出的異常繼承的「異常接口」
*/
interface CacheException
{
}
InvalidArgumentException
namespace Psr\\Cache;
/**
* 傳參錯(cuò)誤拋出的異常接口
*
* 當(dāng)一個(gè)錯(cuò)誤或者非法的傳參發(fā)生時(shí),**必須** 拋出一個(gè)繼承了
* Psr\\Cache\\InvalidArgumentException 的異常
*/
interface InvalidArgumentException extends CacheException
{
}