val组 * @var null */ protected static $clientarr = null; /* * redis connect参数 * @var null */ protected static $connectParams = null; /** * 链接map 编号对应配置$connectParams * @var null */ protected static $connectMap = null; /** * 初始化 连接池 * @access public * @return \Redis */ public static function instance() { if (is_null(self::$instance)) { self::$instance = new static(); } return self::$instance; } /** * 初始化book链接redis地址的数组 */ public static function buildArr(){ if (is_null(self::$clientarr)) { $bookArr = []; $config = Config::get('redis'); $clientArr = explode(';', $config['changebook']); if ($config['changebooknew']) { $clientArrNew = explode(';', $config['changebooknew']); } else { $clientArrNew = null; } //Log::write(json_encode($clientArr),'cctest'); for ($i = 0; $i < count($clientArr); $i++) { $cluster = $clientArr[$i]; try { $numReps = 160; for ($m = 0; $m < $numReps / 4; $m++) { $digest = md5($cluster . '-' . $m, true); for ($h = 0; $h < 4; $h++) { $k = (ord($digest[3 + $h * 4]) & 0xFF) << 24 | (ord($digest[2 + $h * 4]) & 0xFF) << 16 | (ord($digest[1 + $h * 4]) & 0xFF) << 8 | ord($digest[$h * 4]) & 0xFF; if ($clientArrNew) { $bookArr[$k] = $clientArrNew[$i]; } else { $bookArr[$k] = $cluster; } } } } catch (Exception $e) { Log::error('生成redis连接对应关系数组失败'); } } //Log::write(json_encode($bookArr),'cctest'); ksort($bookArr); self::$clientarr = $bookArr; } return self::$clientarr; } /** * 初始化 redis book 书籍信息 * * @access public * @return \Redis */ public static function instanceBookChange($ppval) { $buildArr = self::buildArr(); $conInfo = ''; foreach($buildArr as $key=>$val){ if($key>$ppval){ $conInfo = $val; break; } } //如果取不到,取第一条 if(empty($conInfo)){ $conInfo = reset($buildArr); } //Log::write('书籍链接的reids地址:'.$conInfo,'cctest'); $con = explode(':',$conInfo); self::$glory = new \Redis(); $config = Config::get('redis'); //Log::info('RedisChange连接 ' . $con[0] . ':' . $con[1]); if ($config['pconnect']) { self::$glory->pconnect($con[0], $con[1], $config['timeout']); } else { self::$glory->connect($con[0], $con[1], $config['timeout']); } self::$glory->auth($config['password']); return self::$glory; } /** * 初始化 redis auto 用户id自增 * * @access public * @return \Redis */ public static function instanceAuto() { if(isset(self::$connectMap['auto'])){ return self::getRedisConnect('auto'); }else{ $config = Config::get('redis'); $con = explode(':', $config['auto']); if (count($con) < 3) { Log::error('用户自增id redis配置错误!'); throw new HttpException(500, '请刷新页面重新访问!'); } self::loadRedisConfig('auto',$con,$config['pconnect'],$config['timeout']); return self::getRedisConnect('auto'); } } /** * 初始化 redis book 书籍信息 * * @access public * @return \Redis */ public static function instanceBook() { if (is_null(self::$book)) { $config = Config::get('redis'); $list = explode(';', $config['book']); $arr = []; foreach ($list as $key => $con) { if ($con) { $c = explode(':', $con); if (count($c) >= 3) { $arr[] = $c; } } } unset($con); if (empty($arr)) { Log::error('书籍信息 redis配置错误!'); throw new HttpException(500, '请刷新页面重新访问!'); } shuffle($arr); $con = array_pop($arr); self::$book = new \Redis(); Log::info('Redis连接 ' . $con[0] . ':' . $con[1]); if ($config['pconnect']) { self::$book->pconnect($con[0], $con[1], $config['timeout']); } else { self::$book->connect($con[0], $con[1], $config['timeout']); } if (count($con) >= 3 && $con[2]) { self::$book->auth($con[2]); } if (count($con) >= 4 && is_numeric($con[3])) { self::$book->select($con[3]); } } return self::$book; } /** * 初始化 连接池 并 返回第一个连接,用于 easywechat&redis lock 使用 * @access public * @return \Redis */ public static function instanceCache() { if (is_null(self::$instance)) { self::$instance = new static(); } return self::getRedisConnect(0); } /** * 构造函数 redis连接池 长连接方式 * @throws */ public function __construct() { $config = Config::get('redis'); $list = explode(';', $config['list']); //共n个redis foreach ($list as $item) { $con = explode('=', $item); // 0 编号 1 配置 if (count($con) >= 2) { $c = explode(':', $con[1]); //配置 0IP 1端口 2密码 3库编号 self::loadRedisConfig($con[0],$c,$config['pconnect'],$config['timeout']); } } $modKeyList = explode(';', $config['modkeylist']); foreach ($modKeyList as $item) { $con = explode('=', $item); // 0 编号 1 配置 if (count($con) >= 2) { $c = explode(':', $con[1]); //配置 0IP 1端口 2密码 3库编号 self::loadRedisConfig($con[0], $c, $config['pconnect'], $config['timeout']); } } if(count(self::$connectParams) == 0){ throw new \Exception('redis config error!'); } } /** * 按规则拆分将key归属到响应的redis编号上 * @param array $keys * @return array 返回数组的key为redis的编号,返回数组的value为需要在当前编号redis上操作的key的数组 */ public static function splitKeysByRule(array $keys) { $redisIndex = []; foreach ($keys as $key) { $redisIdx = self::getRedisIndex($key); $redisIndex[$redisIdx][] = $key; } return $redisIndex; } private static function buildRedisRules() { if (is_null(self::$rules)) { $rules = Config::get('redis.rules'); $list = explode(';', $rules); foreach ($list as $item) { $con = explode('=', $item); // 0 编号 1 配置 if (count($con) >= 2) { $c = explode(',', $con[1]); //规则 foreach ($c as $it) { if ($it) { self::$rules[$con[0]][] = $it; } } } } } } /** * 根据key获取redis的编号 * @param $key * @return int|mixed|string */ public static function getRedisIndex($key) { self::buildRedisRules(); #region 从rule中匹配对应的redis编号 if ($key && count(self::$rules)) { foreach (self::$rules as $idx => $item) { foreach ($item as $v) { if (strpos($key, $v) === 0) { //查找到,且必须为第一个0 return $idx; } } } } #endregion #region 构造modkey规则 if (is_null(self::$modKeyRules)) { $modKeyRules = Config::get('redis.modkeyrules'); self::$modKeyRules = explode(',', $modKeyRules); } #endregion #region 从modkeyrule中匹配对应的redis编号 if ($key && count(self::$modKeyRules)) { foreach (self::$modKeyRules as $modKeyRule) { if (strlen($modKeyRule) > 0 && strpos($key, $modKeyRule) === 0) { $modKeyListStr = Config::get('redis.modkeylist'); $modKeyList = explode(';', $modKeyListStr); $modKeyCount = count($modKeyList); $modNumber = hash_code($key) % $modKeyCount; $modNumber = abs($modNumber); $modKeyServer = $modKeyList[$modNumber]; $aModKeyServer = explode('=', $modKeyServer);// 0 编号 1 配置 return $aModKeyServer[0]; } } } #endregion #region 没有找到符合的规则,使用默认redis服务器 $default = Config::get('redis.default'); if (isset(self::$connectMap[$default])) { return $default; } else { return 0; } #endregion } /** * 根据key区分redis连接 * @param string $key * @return [int,\Redis] */ private static function getRedis($key = '') { $idx = self::getRedisIndex($key); return [$idx, self::getRedisConnect($idx)]; } /** * 载入Redis配置 * @param $index * @param $config * @param $isPconnect * @param $timeout */ private static function loadRedisConfig($index,$config,$isPconnect = false,$timeout = 2){ if (count($config) >= 2) { //设置链接Map $val = $config[0].':'.$config[1]; if (count($config) >= 4 && is_numeric($config[3])) { $val = $val . ':' . $config[3]; } self::$connectMap[$index] = $val; //是否是长连接 self::$connectParams[$val]['isLongConnection'] = $isPconnect; //IP地址 self::$connectParams[$val]['ip'] = $config[0]; //端口 self::$connectParams[$val]['port'] = $config[1]; //超时时间 self::$connectParams[$val]['timeout'] = $timeout; //是否进行权限认证 if (count($config) >= 3 && $config[2]) { self::$connectParams[$val]['auth'] = $config[2]; }else{ self::$connectParams[$val]['auth'] = false; } //选择库 if (count($config) >= 4 && is_numeric($config[3])) { self::$connectParams[$val]['db'] = $config[3]; }else{ self::$connectParams[$val]['db'] = false; } } } /** * 获取Redis链接 * @param $index * @param bool $isNo 是否是根据编号获取 * @return mixed|\Redis */ public static function getRedisConnect($index, $isNo = true) { //根据编号获取对应的IP和端口 $mapKey = $index; if($isNo){ if(!isset(self::$connectMap[$index])){ Log::info("Redis Connect Error: Get Redis No {$index} Map Fail"); return null; }else{ $mapKey = self::$connectMap[$index]; } } //检查链接是否已存在 if(isset(self::$connections[$mapKey])){ return self::$connections[$mapKey]; } //不存在时创建链接 if(!$redisConfig = self::$connectParams[$mapKey]){ Log::info("Redis Connect Error: Get Redis MapKey {$mapKey} Params Fail"); return null; } try{ $redis = new \Redis(); //判断长连短连 if($redisConfig['isLongConnection']){ $redis->pconnect($redisConfig['ip'],$redisConfig['port'],$redisConfig['timeout']); }else{ $redis->connect($redisConfig['ip'],$redisConfig['port'],$redisConfig['timeout']); } //校验 if($redisConfig['auth']){ $redis->auth($redisConfig['auth']); } //库 if($redisConfig['db']){ $redis->select($redisConfig['db']); } self::$connections[$mapKey] = $redis; Log::info("Redis Connect Info: Ip:{$redisConfig['ip']} Port:{$redisConfig['port']} No:{$index}"); $clients = $redis->info(); if (!is_array($clients) || $clients['connected_clients'] > 3000 || $clients['instantaneous_ops_per_sec'] > 100000) { Log::error("redis 连接数超出最大限制!client:{$clients['connected_clients']} ops:{$clients['instantaneous_ops_per_sec']}"); $file = sys_get_temp_dir() . "/cps_redis_error"; @file_put_contents($file, time() + 2); http_response_code(502); header("Content-Type: text/html;charset=utf-8"); echo ''; echo ''; echo '502 Sorry, HTTP ERROR~'; echo '502 Sorry, HTTP ERROR~'; echo "


"; echo '点我重试一次'; die(); } return self::$connections[$mapKey]; }catch (\Exception $e){ Log::error("Redis Connect Error: Ip:{$redisConfig['ip']} Port:{$redisConfig['port']} No:{$index} Message:{$e->getMessage()}"); http_response_code(502); header("Content-Type: text/html;charset=utf-8"); echo ''; echo ''; echo '502 Sorry, HTTP ERROR~'; echo '502 Sorry, HTTP ERROR~'; echo "


"; echo '点我重试一次'; die(); } } /** * 批量删除正则keys * * @param string $pattern * @throws \Exception */ public static function delScan($pattern) { set_time_limit(0); self::instance(); $key = substr($pattern, 0, strpos($pattern, ':') + 1); self::buildRedisRules(); $matchIdxs = []; #region 从rule规则中匹配 if ($key && count(self::$rules)) { foreach (self::$rules as $idx => $item) { foreach ($item as $v) { if (strpos($v, $key) === 0) { $matchIdxs[] = $idx; break; } } } } #endregion #region 构造modkey规则 if (is_null(self::$modKeyRules)) { $modKeyRules = Config::get('redis.modkeyrules'); self::$modKeyRules = explode(',', $modKeyRules); } #endregion #region 从modkey中匹配规则,如果匹配成功,那么所有modlist服务器做一次删除 if ($key && count(self::$modKeyRules)) { foreach (self::$modKeyRules as $modKeyRule) { if (strlen($modKeyRule) > 0 && strpos($modKeyRule, $key) === 0) { $modKeyListStr = Config::get('redis.modkeylist'); $modKeyList = explode(';', $modKeyListStr); foreach ($modKeyList as $modKeyServerParam) { $aModKeyServer = explode('=', $modKeyServerParam); $matchIdxs[] = $aModKeyServer[0]; } break; } } } #endregion //默认redis服务器也需要做一次删除 $matchIdxs[] = Config::get('redis.default'); #region 匹配的服务器做删除操作 foreach ($matchIdxs as $matchIdx) { $redis = self::getRedisConnect($matchIdx); $cursor = null; while ($cursor !== 0) { $keys = $redis->scan($cursor, $pattern, 500); if (is_array($keys) && count($keys)) { $redis->del($keys); //批量删除key } } } #endregion } // public function pfAdd($name,$val){ // try{ // if(!is_array($val)){ // $val = [$val]; // } // $redis = self::getRedis($name); // return $redis->pfAdd($name,$val); // }catch (\Exception $e){ // Log::error("Redis Call Function Error: Name:pfAdd Key:{$name} Args:".var_export($val,true)); // } // } function __call($name, $arguments) { try{ if (count($arguments)) { list($idx,$redis) = self::getRedis($arguments[0]); //参数1不一定就是key } else { list($idx,$redis) = self::getRedis(); } if (strtolower($name) == 'pfadd' && !is_array($arguments[1])) { $arguments[1] = [$arguments[1]]; } if (strtolower($name) == 'delete') { $name = 'del'; } // $t = microtime(true); $ret = call_user_func_array([$redis, $name], $arguments); // Log::info("Redis Call Function Idx:{$idx} Name:{$name} Args:" . json_encode($arguments, // JSON_UNESCAPED_UNICODE) . " Time:" . number_format(microtime(true) - $t, 6)); return $ret; }catch (\Exception $e){ Log::error("Redis Call Function Error: Name:{$name} Args:" . json_encode($arguments, true) . " Msg:" . $e->getMessage()); } } }