Memcached.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. namespace KIF\Cache;
  3. use Memcached as MemcachedResource;
  4. use KIF\Debug\Debug;
  5. use KIF\Verify;
  6. use KIF\Exception\ParamsException;
  7. use KIF\Core\Config;
  8. /**
  9. * 对php扩展 Memcached 的包装
  10. * @author gaoxiaogang@gmail.com
  11. *
  12. */
  13. class Memcached {
  14. /**
  15. * 存放已建立的 Memcached 实例
  16. * @var array
  17. */
  18. static private $objMemcacheds = array();
  19. /**
  20. *
  21. * 当前所使用的服务器集群
  22. * @var array
  23. */
  24. private $servers;
  25. /**
  26. *
  27. * 保存当前实例下到MemcachedResource的连接
  28. * @var MemcachedResource
  29. */
  30. private $objMemcached;
  31. public function __construct($cluster_flag = 'default') {
  32. $memcachedConfig = Config::getInstance()->get('memcached');
  33. if (!$memcachedConfig || !isset($memcachedConfig[$cluster_flag])) {
  34. throw new ParamsException("不存在的memcache集群标志 {$cluster_flag}");
  35. }
  36. $this->servers = $memcachedConfig[$cluster_flag];
  37. if (!isset(self::$objMemcacheds[$cluster_flag])) {
  38. $objMemcached = new MemcachedResource();
  39. # 设置socket连接的超时时间,单位是毫秒。这个值如果设置的太小,容易导致memcached操作失败率增加。
  40. $objMemcached->setOption(MemcachedResource::OPT_CONNECT_TIMEOUT, 1000);
  41. # 等待失败的连接重试的时间,单位秒
  42. // $objMemcached->setOption(MemcachedResource::OPT_RETRY_TIMEOUT, 0);
  43. $objMemcached->setOption(MemcachedResource::OPT_TCP_NODELAY, true);
  44. # 使用一致性分布算法
  45. $objMemcached->setOption(MemcachedResource::OPT_DISTRIBUTION, MemcachedResource::DISTRIBUTION_CONSISTENT);
  46. $objMemcached->setOption(MemcachedResource::OPT_LIBKETAMA_COMPATIBLE, true);
  47. $tmpResult = $objMemcached->addServers($this->servers);
  48. if (!$tmpResult) {
  49. return false;
  50. }
  51. self::$objMemcacheds[$cluster_flag] = $objMemcached;
  52. }
  53. $this->objMemcached = self::$objMemcacheds[$cluster_flag];
  54. }
  55. /**
  56. *
  57. * 给 $key 设置对应的值
  58. * @param string $key
  59. * @param mixed $value
  60. * @param int $expiration 过期时间 实际发送的值可以 是一个Unix时间戳,或者是一个从现在算起的以秒为单位的数字。对于后一种情况,这个 秒数不能超过60×60×24×30(30天时间的秒数);如果失效的值大于这个值, 服务端会将其作为一个真实的Unix时间戳来处理而不是 自当前时间的偏移。
  61. * 如果失效值被设置为0(默认),此元素永不过期(但是它可能由于服务端为了给其他新的元素分配空间而被删除)。
  62. * @return Boolean 成功:true;失败:false
  63. *
  64. */
  65. public function set($key, $value, $expiration = 0) {
  66. $begin_microtime = Debug::getTime();
  67. $tmpResult = $this->objMemcached->set($key, $value, $expiration);
  68. if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
  69. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'set');
  70. } else {
  71. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'set');
  72. }
  73. return $tmpResult;
  74. }
  75. /**
  76. *
  77. * 存储多个元素
  78. * @param array $items 存放在服务器上的键/值对数组。
  79. * @param int $expiration 到期时间,默认为 0
  80. * @return Boolean 有任何一个key设置失败,都会返回false,并且该key后的key不会再去存储;但是前面存储成功的key仍然有效。
  81. * !!! 所以,批量设置的行为,具有不可预料性
  82. */
  83. public function sets(array $items, $expiration = 0) {
  84. $begin_microtime = Debug::getTime();
  85. if (empty($items)) {
  86. throw new ParamsException('sets方法的参数items不能为空数组');
  87. }
  88. $tmpResult = $this->objMemcached->setMulti($items, $expiration);
  89. Debug::cache($this->servers, print_r($items, true), Debug::getTime() - $begin_microtime, $tmpResult, 'sets');
  90. return $tmpResult;
  91. }
  92. /**
  93. *
  94. * 获取 $key 对应的值
  95. * PS: 如果指定了 通用缓存回调函数$cache_cb,并且想取得$cas_token,会使用 匿名函数
  96. * 对 $cache_cb 进行包装。原因是 Memcached 扩展的 get 方法,在这种情况下是获取不到 $cas_token 的。
  97. * @param string $key
  98. * @param callback $cache_cb 通用缓存回调函数。如:'myfunc', array('MyClass', 'classMethod'),
  99. * array($obj, 'method')。
  100. * @param float $cas_token 引用传递
  101. * @return null | false | mixed 值不存在:返回 null;存在:返回值;其它原因导致的获取不到值:返回 false。
  102. * !!! 如果 $key set 的值本身是false,请调用 getResultCode 方法来与 失败原因导致的 false 相区分。
  103. *
  104. */
  105. public function get($key, $cache_cb = null, & $cas_token = null) {
  106. $begin_microtime = Debug::getTime();
  107. if (func_num_args() == 3 && !is_null($cache_cb)) {
  108. $cb_success = null;
  109. $tmpResult = $this->objMemcached->get($key, function ($objMemcached, $cacheKey, & $ref_value) use ($cache_cb, & $cb_success) {
  110. $cb_success = $cache_cb($objMemcached, $cacheKey, $ref_value);
  111. return $cb_success;
  112. }, $cas_token);
  113. if ($cb_success) {// 修复php \Memcached::get()的一个bug,当同时指定回调函数和$cas_token时,获取不到$cas_token。
  114. $this->objMemcached->get($key, null, $cas_token);
  115. }
  116. } else {
  117. $tmpResult = $this->objMemcached->get($key, $cache_cb, $cas_token);
  118. }
  119. if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
  120. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'get');
  121. return $tmpResult;
  122. }
  123. if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
  124. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'get');
  125. return null;// 值不存在,返回 null
  126. } else {
  127. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'get');
  128. return false;
  129. }
  130. }
  131. /**
  132. *
  133. * 批量获取 $keys 对应的值
  134. * @param array $keys
  135. * @param array $cas_tokens 引用传值
  136. * @return false | array 失败:返回false,获取其中任何一个key出错,都会返回false;
  137. * 成功:array,值不存在的key,对应的值是null。
  138. */
  139. public function gets(array $keys, array & $cas_tokens = null) {
  140. if (empty($keys)) {
  141. return array();
  142. }
  143. $begin_microtime = Debug::getTime();
  144. $tmpResult = $this->objMemcached->getMulti($keys, $cas_tokens, MemcachedResource::GET_PRESERVE_ORDER);
  145. Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, $tmpResult, 'gets');
  146. return $tmpResult;
  147. }
  148. /**
  149. *
  150. * 执行一个“检查并设置”的操作,因此,它仅在当前客户端最后一次取值后,该key 对应的值没有被其他客户端修改的情况下
  151. * ,才能够将值写入。检查是通过cas_token参数进行的, 这个参数是Memcach指定给已经存在的元素的一个唯一的64位值
  152. * ,怎样获取这个值请查看 Memcached::get*() 系列方法的文档
  153. * 。注意:这个值作为double类型是因为PHP的整型空间限制。
  154. * PS:这是Memcached扩展比Memcache扩展一个非常重要的优势,在这样一个系统级(Memcache自身提供)
  155. * 的冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
  156. *
  157. * @param float $cas_token 与已存在元素关联的唯一的值,由Memcache生成。
  158. * @param string $key 用于存储值的键名。
  159. * @param mixed $value 存储的值
  160. * @param int $expiration 到期时间,默认为 0
  161. * @return null | boolean 成功:true;失败:false;$cas_token检查不通过:null
  162. * 如果在元素尝试存储时发现在本客户端最后一次获取后被其他客户端修改(即$cas_token检查不通过)
  163. * ,Memcached::getResultCode() 将返回Memcached::RES_DATA_EXISTS。
  164. */
  165. public function cas($cas_token, $key, $value, $expiration = 0) {
  166. $begin_microtime = Debug::getTime();
  167. $tmpResult = $this->objMemcached->cas($cas_token, $key, $value, $expiration);
  168. if ($tmpResult) {
  169. Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, $tmpResult, 'cas');
  170. return true;
  171. }
  172. if ($this->getResultCode() == MemcachedResource::RES_DATA_EXISTS) {
  173. Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'cas');
  174. return null;
  175. }
  176. Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'cas');
  177. return false;
  178. }
  179. /**
  180. *
  181. * 删除一个元素
  182. * 从服务端删除key对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
  183. * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
  184. * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
  185. * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
  186. * 在这段时间过去后, 元素最终被从服务端内存删除。
  187. * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
  188. * @param string $key 要删除的key
  189. * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
  190. * @return null | boolean 成功:true;失败:false;key不存在:null
  191. * 如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
  192. */
  193. public function delete($key, $time = 0) {
  194. $begin_microtime = Debug::getTime();
  195. $tmpResult = $this->objMemcached->delete($key, $time);
  196. if ($tmpResult) {
  197. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, true, 'delete');
  198. return true;
  199. }
  200. if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
  201. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'delete');
  202. return null;
  203. }
  204. Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'delete');
  205. return false;
  206. }
  207. /**
  208. *
  209. * !!!!
  210. * !! 这个方法与 delete有些区别,delete了cache里不存在的key时,会返回false;但deletes却会返回true。
  211. * !!!!
  212. * 删除一批元素
  213. * 从服务端删除keys对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
  214. * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
  215. * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
  216. * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
  217. * 在这段时间过去后, 元素最终被从服务端内存删除。
  218. * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
  219. * @param array $keys 要删除的keys
  220. * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
  221. * @return null | boolean 成功:true;失败:false;key不存在:null
  222. * 如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
  223. */
  224. public function deletes(array $keys, $time = 0) {
  225. $begin_microtime = Debug::getTime();
  226. $tmpResult = $this->objMemcached->deleteMulti($keys, $time);
  227. if ($tmpResult) {
  228. Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, true, 'deletes');
  229. return true;
  230. }
  231. if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
  232. Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, null, 'deletes');
  233. return null;
  234. }
  235. Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, false, 'deletes');
  236. return false;
  237. }
  238. /**
  239. *
  240. * 向一个新的key下面增加一个元素
  241. * @param string $key 用于存储值的键名。
  242. * @param mixed $value 存储的值
  243. * @param int $expiration 到期时间,默认为 0
  244. * @return null | boolean 成功:true;失败:false;key存在:null
  245. * 如果key已经存在, Memcached::getResultCode()方法将会返回Memcached::RES_NOTSTORED。
  246. */
  247. public function add($key, $value, $expiration = 0) {
  248. $begin_microtime = Debug::getTime();
  249. $tmpResult = $this->objMemcached->add($key, $value, $expiration);
  250. if ($tmpResult) {
  251. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'add');
  252. return true;
  253. }
  254. if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
  255. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'add');
  256. return null;
  257. }
  258. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'add');
  259. return false;
  260. }
  261. /**
  262. *
  263. * 替换已存在key下的元素
  264. * 如果 服务端不存在key,操作将失败。
  265. * @param string $key 用于存储值的键名。
  266. * @param mixed $value 存储的值
  267. * @param int $expiration 到期时间,默认为 0
  268. * @return null | boolean 成功:true;失败:false;key不存在:null
  269. */
  270. public function replace($key, $value, $expiration = 0) {
  271. $begin_microtime = Debug::getTime();
  272. $tmpResult = $this->objMemcached->replace($key, $value, $expiration);
  273. if ($tmpResult) {
  274. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'replace');
  275. return true;
  276. }
  277. if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
  278. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'replace');
  279. return null;
  280. }
  281. Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'replace');
  282. return false;
  283. }
  284. /**
  285. *
  286. * 将一个数值元素增加参数offset指定的大小。如果元素的值不是数值类型,返回false
  287. * 经实验发现,Memcached 扩展的increment方法存在bug,这里对其做了修复。
  288. * @param string $key 要增加值的元素的key。
  289. * @param int $offset 要将元素的值增加的大小。
  290. * @return null | int | false 成功:int(递增后的值);失败:false;key不存在:null
  291. * 如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
  292. * !!! 如果 $key 对应的值不是 int 型:通过 $this->get 方法返回的值不是 int 型
  293. * a、如果不是 boolean 型:结果是0,并且 Memcached::getResultCode() 返回 Memcached::RES_SUCCESS
  294. * b、如果是 boolean 型:会以0为基准递增值。
  295. * !!! 所以,请尽量尽量不要对非 int 型的值使用 increment 方法
  296. */
  297. public function increment($key, $offset = 1) {
  298. $begin_microtime = Debug::getTime();
  299. $tmpGetResult = $this->get($key);
  300. if (is_null($tmpGetResult)) {// $key 对应的值不存在
  301. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'increment');
  302. return null;
  303. }
  304. if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
  305. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
  306. return false;
  307. }
  308. if (!Verify::naturalNumber($tmpGetResult)) {
  309. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
  310. return false;
  311. }
  312. $tmpResult = $this->objMemcached->increment($key, $offset);
  313. if (Verify::unsignedInt($tmpResult)) {
  314. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'increment');
  315. return $tmpResult;
  316. }
  317. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
  318. return false;
  319. }
  320. /**
  321. *
  322. * 减小一个数值元素的值,减小多少由参数offset决定。如果元素的值不是数值类型,返回false。
  323. * 如果减小后的值小于0,则新的值被设置为0。
  324. * 如果元素不存在,返回 null。
  325. *
  326. * 经实验发现,Memcached 扩展的decrement方法存在bug,这里对其做了修复。
  327. * @param string $key 要减少值的元素的key。
  328. * @param int $offset 要将元素的值减少的大小。
  329. * @return null | int | false 成功:int(减少后的值,有可能是 0);失败:false;key不存在:null
  330. * 如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
  331. */
  332. public function decrement($key, $offset = 1) {
  333. $begin_microtime = Debug::getTime();
  334. $tmpGetResult = $this->get($key);
  335. if (is_null($tmpGetResult)) {// $key 对应的值不存在
  336. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'decrement');
  337. return null;
  338. }
  339. if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
  340. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
  341. return false;
  342. }
  343. if (!Verify::naturalNumber($tmpGetResult)) {
  344. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
  345. return false;
  346. }
  347. $tmpResult = $this->objMemcached->decrement($key, $offset);
  348. if (Verify::naturalNumber($tmpResult)) {
  349. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'decrement');
  350. return $tmpResult;
  351. }
  352. Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
  353. return false;
  354. }
  355. /**
  356. *
  357. * 返回最后一次操作的结果代码
  358. * 返回Memcached::RES_*系列常量中的一个来表明最后一次执行Memcached方法的结果
  359. *
  360. * 比如常用的几个常量是:
  361. * 1、Memcached::RES_SUCCESS 表示操作成功
  362. * 2、Memcached::RES_NOTFOUND 元素未找到(通过get或cas操作时)
  363. * 3、Memcached::RES_NOTSTORED 元素没有被存储,但并不是因为一个错误。
  364. * 这通常表明add(元素已存在)或replace(元素不存在)方式存储数据失败或者元素已经在一个删除序列中(延时删除)。
  365. *
  366. * @return int
  367. */
  368. public function getResultCode() {
  369. return $this->objMemcached->getResultCode();
  370. }
  371. /**
  372. *
  373. * 返回一个字符串来描述最后一次Memcached方法执行的结果。
  374. * @return string
  375. */
  376. public function getResultMessage() {
  377. return $this->objMemcached->getResultMessage();
  378. }
  379. }