class.mucache.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <?php
  2. /**
  3. memcacheq只支持set,get,delete方法,单次设置不能超过64k(可以压缩)
  4. memcachedb不能使用CAS协议
  5. CAS协议不能同时使用分组备份机制
  6. 对于高精度数据,不要用分布式
  7. 对于开启压缩的key,大于100Byte的将会被压缩
  8. $config['memcache'][] = array( //Memcache,支持多个分布式(地址,端口,权重)
  9. array('127.0.0.1', '11211', 50),
  10. array('127.0.0.1', '11212', 50),
  11. );
  12. $config['memcache'][] = array( //Memcache,支持多个分布式(地址,端口,权重)
  13. array('127.0.0.1', '11213', 50),
  14. array('127.0.0.1', '11214', 50),
  15. );
  16. **/
  17. class mucache{
  18. private $arrOmem = array(); //分组式
  19. /**@var $arrServers Memcached[]*/
  20. private $arrServers = array(); //分组
  21. private $try = 2; //重试次数
  22. private $connected = false; //是否已经连接过
  23. private $persistent = false; //是否长连接
  24. private $prefix = ''; //Key前缀
  25. public function __construct( $arrServers, $persistent=false){ //构造函数
  26. $this->arrServers = $arrServers;
  27. $this->persistent = $persistent;
  28. if( ! class_exists( 'Memcached')){ //强制使用
  29. die('This Lib Requires The Memcached Extention!');
  30. }
  31. }
  32. /**
  33. * 支持多组连接.
  34. * @return void
  35. */
  36. private function connect(){
  37. if ( ! $this->connected){
  38. $this->connected = true; //标志已经连接过一次
  39. foreach ((array)$this->arrServers as $key => $arrSever){//创建多组
  40. $this->arrOmem[$key] = $this->persistent ? new Memcached( $this->genPool( $arrSever)) : new Memcached(); //根据是否持久连接
  41. $this->arrOmem[$key]->setOption(Memcached::OPT_TCP_NODELAY, true); //启用tcp_nodelay
  42. $this->arrOmem[$key]->setOption(Memcached::OPT_NO_BLOCK, true); //启用异步IO
  43. $this->arrOmem[$key]->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT); //分布式策略
  44. $this->arrOmem[$key]->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); //分布式服务组分散.推荐开启
  45. $this->arrOmem[$key]->setOption(Memcached::OPT_HASH, Memcached::HASH_CRC); //Key分布
  46. //$this->arrOmem[$key]->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 5); //
  47. //$this->arrOmem[$key]->setOption(Memcached::OPT_PREFIX_KEY, $this->prefix); //Key前缀
  48. //Memcached::HAVE_IGBINARY&&extension_loaded('igbinary')&&$this->arrOmem[$key]->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY); //序列化
  49. $this->persistent && count($this->arrOmem[$key]->getServerList()) ? '' : $this->arrOmem[$key]->addServers( $arrSever); //对于持久连接并且已经有服务端口的,不执行.控制每个进程只有一个持久连接
  50. }
  51. }
  52. }
  53. /**
  54. * 设置mem值 默认缓存24小时
  55. * @var key 键名,键值,过期时间,是否压缩. 后两个参数对Memcachedb透明
  56. * @return bool
  57. */
  58. public function set($key, $value, $expire=86400, $zip=false){
  59. $this->connect();
  60. $setOk = 999; //成功设置标志,必须强等
  61. foreach ( (array)$this->arrOmem as $oMem){
  62. $oMem->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false);
  63. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  64. $oMem->set($key, $value, $expire);
  65. $resultCode = $oMem->getResultCode();
  66. if( $resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  67. $setOk = $setOk===999 ? true : $setOk;
  68. break;
  69. }
  70. $setOk = false;
  71. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode , 'set');
  72. if(in_array($resultCode, array(Memcached::RES_BAD_KEY_PROVIDED))){
  73. break;
  74. }
  75. }
  76. }
  77. return $setOk===999 ? false : $setOk; //返回是否设置成功.没有成功由程序逻辑处理
  78. }
  79. /**
  80. * 添加mem值 默认缓存24小时
  81. * @var key 键名,键值,过期时间,是否压缩. 后两个参数对Memcachedb透明
  82. * @return 成功true,否则false
  83. */
  84. public function add($key, $value, $expire=86400, $zip=false){
  85. $this->connect();
  86. $setOk = 999; //成功设置标志,必须强等
  87. foreach ( (array)$this->arrOmem as $oMem){
  88. $oMem->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false);
  89. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  90. $oMem->add($key, $value, $expire);
  91. $resultCode = $oMem->getResultCode();
  92. if($resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  93. $setOk = $setOk===999 ? true : $setOk;
  94. break;
  95. }
  96. $setOk = false;
  97. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode, 'add');
  98. if(in_array($resultCode, array(Memcached::RES_NOTSTORED, Memcached::RES_BAD_KEY_PROVIDED))){ //值已经存在或者Key非法
  99. break;
  100. }
  101. }
  102. }
  103. return $setOk===999 ? false : $setOk;
  104. }
  105. /**
  106. * 替换mem值 默认缓存24小时
  107. * @var key 键名,键值,过期时间,是否压缩. 后两个参数对Memcachedb透明
  108. * @return 成功true,否则false
  109. */
  110. public function replace($key, $value, $expire=86400, $zip=false){
  111. $this->connect();
  112. $setOk = 999; //成功设置标志,必须强等
  113. foreach ( (array)$this->arrOmem as $oMem){
  114. $oMem->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false);
  115. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  116. $oMem->replace($key, $value, $expire);
  117. $resultCode = $oMem->getResultCode();
  118. if(in_array($resultCode, array(Memcached::RES_SUCCESS, Memcached::RES_NOTSTORED)) ){ //保存成功或者值没有存在则退出此层循环
  119. $setOk = $setOk===999 ? true : $setOk;
  120. break;
  121. }
  122. $setOk = false;
  123. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode, 'replace');
  124. }
  125. }
  126. return $setOk===999 ? false : $setOk;
  127. }
  128. /**
  129. * 设置多个mem值 默认缓存24小时
  130. * @var key array(键名=>键值),过期时间,是否压缩. 后两个参数对Memcachedb透明
  131. * @return 成功true,否则false
  132. */
  133. public function setMulti($items, $expire=86400, $zip=false){
  134. $this->connect();
  135. $setOk = 999; //成功设置标志,必须强等
  136. foreach ( (array)$this->arrOmem as $oMem){
  137. $oMem->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false);
  138. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  139. $oMem->setMulti($items, $expire);
  140. $resultCode = $oMem->getResultCode();
  141. if($resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  142. $setOk = $setOk===999 ? true : $setOk;
  143. break;
  144. }
  145. $setOk = false;
  146. $this->errorlog($items, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode, 'setMulti');
  147. }
  148. }
  149. return $setOk===999 ? false : $setOk;
  150. }
  151. /**
  152. * 加验证地存入一个值 注意不支持分组!!!
  153. * @return 成功true,否则false
  154. */
  155. public function cas($cas, $key, $value, $expire=0){
  156. $this->connect();
  157. $setOk = 999; //成功设置标志,必须强等
  158. foreach ( (array)$this->arrOmem as $oMem){
  159. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  160. $oMem->cas($cas, $key, $value, $expire);
  161. $resultCode = $oMem->getResultCode();
  162. if( $resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  163. $setOk = $setOk===999 ? true : $setOk;
  164. break;
  165. }
  166. $setOk = false;
  167. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode,'cas');
  168. if( in_array($resultCode, array(Memcached::RES_PROTOCOL_ERROR, Memcached::RES_DATA_EXISTS))){ //如果不支持cas协议或者没有该键或者数据被更改
  169. break;
  170. }
  171. }
  172. }
  173. return $setOk===999 ? false : $setOk;
  174. }
  175. /**
  176. * 追加字符串 @var $direct 0加到后面1加到前面 默认永久保存
  177. * @return 成功true,否则false
  178. */
  179. public function append($key, $value, $expire=0, $direct=0){
  180. $this->connect();
  181. $setOk = 999; //成功设置标志,必须强等
  182. foreach ( (array)$this->arrOmem as $oMem){
  183. $oMem->setOption(Memcached::OPT_COMPRESSION, false); //append不支持压缩
  184. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  185. $direct ? $oMem->prepend($key, $value) : $oMem->append($key, $value);
  186. if( $oMem->getResultCode() == Memcached::RES_NOTSTORED){
  187. $oMem->add($key, $value, $expire);
  188. }
  189. $resultCode = $oMem->getResultCode();
  190. if( $resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  191. $setOk = $setOk===999 ? true : $setOk;
  192. break;
  193. }
  194. $setOk = false;
  195. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode,'append');
  196. }
  197. }
  198. return $setOk===999 ? false : $setOk;
  199. }
  200. /**
  201. * 累加/减 默认是加 永久保存
  202. * @var $value 需要累加或减的值 注意不能减成负数,不能是负数
  203. * @var $direct 0加1减
  204. * @return 成功true,否则false
  205. */
  206. public function increment($key, $value, $expire=0, $direct=0){
  207. $this->connect();
  208. $setOk = 999; //成功设置标志,必须强等
  209. foreach ( (array)$this->arrOmem as $oMem){
  210. $oMem->setOption(Memcached::OPT_COMPRESSION, false); //对数字不压缩
  211. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试$this->try次
  212. $direct ? $oMem->decrement($key, $value) : $oMem->increment($key, $value);
  213. if( $oMem->getResultCode() == Memcached::RES_NOTFOUND){
  214. $oMem->add($key, $value, $expire);
  215. }
  216. $resultCode = $oMem->getResultCode();
  217. if($resultCode == Memcached::RES_SUCCESS ){ //保存成功则退出此层循环
  218. $setOk = $setOk===999 ? true : $setOk;
  219. break;
  220. }
  221. $setOk = false;
  222. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().$resultCode,'increment');
  223. }
  224. }
  225. return $setOk===999 ? false : $setOk;
  226. }
  227. /**
  228. * 获取单键 $zip 是否解压缩.对应set的压缩
  229. * @var $inCas 是否进行cas验证 如果是则返回一个数组[结果集,cas字串] 注意不支持分组!!!
  230. * @return mixed,否则false
  231. */
  232. public function get( $key, $zip=false, $inCas=false){
  233. $this->connect();
  234. $result = $success = false; //返回的结果
  235. $aNocached = array(); //缓存没有命中的列表
  236. shuffle($this->arrOmem);//打乱数组,随机获取
  237. foreach ( (array)$this->arrOmem as $oMem){//取到一个就返回
  238. $oMem->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false); //对于set是压缩的则解压缩取出
  239. for($try=0; $try<$this->try; $try++){ //确保在没有系统错误的情况下执行
  240. $result = $inCas ? @$oMem->get( $key, null, $cas) : @$oMem->get( $key); //抑制错误,如不能正常解压
  241. $resultCode = $oMem->getResultCode();
  242. if( $resultCode == Memcached::RES_SUCCESS){ //获取成功,给没有数据的组赋值,并退出循环
  243. $success = true;
  244. foreach ( (array)$aNocached as $oTempMem ){
  245. $oTempMem->set($key, $result, 24*3600);
  246. }
  247. break;
  248. }else{ //强制返回false
  249. $result = false;
  250. }
  251. if( $resultCode == Memcached::RES_NOTFOUND){ //获取要么成功,要么没有找到该KEY,否则出错
  252. $aNocached[] = $oMem;
  253. break;
  254. }
  255. $this->errorlog($key, $try, $oMem->getServerList(), $oMem->getResultMessage().':'.(string)$resultCode,'get');
  256. if( in_array($resultCode, array(Memcached::RES_PAYLOAD_FAILURE, Memcached::RES_BAD_KEY_PROVIDED, '4294966295'))){ //不能正常解压或者key不正确,删除
  257. $oMem->delete( $key, 0);
  258. break;
  259. }
  260. if( $resultCode == Memcached::RES_PROTOCOL_ERROR){ //如果不支持cas协议
  261. break;
  262. }
  263. }
  264. if( $success == true){
  265. break;
  266. }
  267. }
  268. return $inCas ? array($result, $cas) : $result;
  269. }
  270. /**
  271. * 获取多键.有缺陷,keys数组中的值必须都为字符串型.先要测试该方法是否可用,否则会引起致命错误
  272. * @return 成功返回结果,否则false
  273. */
  274. public function getMulti( $keys, $zip=false, $inCas=false){
  275. foreach ((array)$keys as $i => $key){
  276. $keys[$i] = (string)$key;
  277. }
  278. $this->connect();
  279. $num = array_rand((array)$this->arrOmem, 1);//随机到一组中获取
  280. $this->arrOmem[$num]->setOption(Memcached::OPT_COMPRESSION, $zip ? true : false); //对于set是压缩的则解压缩取出
  281. $result = $inCas ? @$this->arrOmem[$num]->getMulti($keys, $cas, Memcached::GET_PRESERVE_ORDER) : @$this->arrOmem[$num]->getMulti( $keys); //抑制错误,如不能正常解压
  282. $resultCode = $this->arrOmem[$num]->getResultCode();
  283. if( $resultCode != Memcached::RES_SUCCESS){ //没有成功,强制返回false,写日志
  284. $result = false;
  285. $this->errorlog($keys, 0, $this->arrOmem[$num]->getServerList(), $this->arrOmem[$num]->getResultMessage().':'.(string)$resultCode,'getMulti');
  286. }
  287. return $inCas ? array($result, $cas) : $result;
  288. }
  289. /**
  290. * $expire秒后删除
  291. * @return 成功为true,否则false
  292. */
  293. public function delete($key, $expire=0,$pd=false){
  294. $this->connect();
  295. $delOk = 999; //返回标志
  296. if(!$pd){//删除成功或者已经没有那个KEY
  297. $arr = array(Memcached::RES_SUCCESS, Memcached::RES_NOTFOUND);
  298. }else{//只有删除成功返回ture
  299. $arr = array(Memcached::RES_SUCCESS);
  300. }
  301. foreach ( (array)$this->arrOmem as $oMem){
  302. for($try=0; $try<$this->try; $try++){ //确保每个组都保存成功,重试N次
  303. $oMem->delete($key, (int)$expire);
  304. if( in_array($oMem->getResultCode(), $arr) ){
  305. $delOk = $delOk===999 ? true : $delOk;
  306. break;
  307. }
  308. $delOk = false;
  309. }
  310. }
  311. return $delOk===999 ? false : $delOk;;
  312. }
  313. /**
  314. * 把所有缓存设置为超时,以便其他key可以用
  315. */
  316. public function flush( $delay=0){
  317. $this->connect();
  318. $flushOk = 999; //返回标志
  319. foreach ( (array)$this->arrOmem as $oMem){
  320. $oMem->flush( $delay );
  321. if( in_array( $oMem->getResultCode(), array( Memcached::RES_SUCCESS))){
  322. $flushOk = $flushOk===999 ? true : $flushOk;
  323. }
  324. $flushOk = false;
  325. }
  326. return $flushOk===999 ? false : true;
  327. }
  328. /**
  329. * 获取运行状态.有缺陷,如果分布式中某点有故障,则获取不到记录
  330. * @return unknown
  331. */
  332. public function getStats(){
  333. $this->connect();
  334. foreach ( (array)$this->arrOmem as $oMem){
  335. $arrStatus[] = $oMem->getStats();
  336. }
  337. return $arrStatus;
  338. }
  339. /**
  340. * 获取长连接名前缀,保证相同的端口配置用同一个长连接
  341. */
  342. private function genPool( $arrSever){
  343. foreach ((array)$arrSever as $aServer){
  344. $aList[] = $aServer[0] . '-' . $aServer[1];
  345. }
  346. $aList = array_unique( (array)$aList);
  347. sort( $aList, SORT_STRING);
  348. return md5(implode('-', $aList));
  349. }
  350. /**
  351. * 错误日志
  352. * @param unknown_type $key
  353. * @param unknown_type $try
  354. * @param unknown_type $group
  355. * @param unknown_type $msg
  356. */
  357. private function errorlog($keys, $try, $group, $msg , $method){
  358. $error = date('Y-m-d H:i:s').":\nmethod:".$method.":\n".var_export( $group, true) . ";\nkeys:".var_export($keys, true).";\ntry:{$try};\nmsg:{$msg}\n\n";
  359. $file = dirname(__FILE__) . '/../deBUG/memcache.txt';
  360. @file_put_contents($file, "{$error}\n", @filesize($file)<512*1024 ? FILE_APPEND : null);
  361. }
  362. /**
  363. * 获取缓存当前配置
  364. */
  365. public function getCfg() {
  366. return $this->arrServers[0][0];
  367. }
  368. public function close($key){
  369. $this->arrServers[$key]->quit();
  370. }
  371. }