diff --git a/.gitignore b/.gitignore index 723ef36f4e4f32c4560383aa5987c575a30c6535..11f3e51dc748a76a2d1fbf2af413b01b75105c50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ -.idea \ No newline at end of file +.idea +.settings/ +.DS_Store +.project +.metadata +*.properties +.classpath +.settings/ +.loadpath +.buildpath +*.swp +composer.lock diff --git a/ThinkPHP/Common/functions.php b/ThinkPHP/Common/functions.php index 92a427fe174be2d517edc2c54b60501c22a1848a..96678811a31ca8d18c89d4ab7d44e4a7c0ec9436 100644 --- a/ThinkPHP/Common/functions.php +++ b/ThinkPHP/Common/functions.php @@ -1639,6 +1639,8 @@ function load_ext_file($path) $file = is_file($config) ? $config : $path . 'Conf/' . $config . CONF_EXT; if (is_file($file)) { is_numeric($key) ? C(load_Config($file)) : C($key, load_Config($file)); + }elseif(is_file($file = CONF_PATH.$config.CONF_EXT)) { + is_numeric($key)?C(load_config($file)):C($key,load_config($file)); } } } diff --git a/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php b/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php index b0bb97f0cd3775b3d4215fdebabcc0e37ca38699..0f0a00304717b11fbc9dbb63526d8b81c6f0ff22 100644 --- a/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php +++ b/ThinkPHP/Library/Think/Cache/Driver/Redis.class.php @@ -113,5 +113,15 @@ class Redis extends Cache { return $this->handler->flushDB(); } + + /** + * 魔术方法,phpRedis中所包含的所有操作均可以通过魔术方法直接调用 + * @access public + * @return mixed + */ + function __call($method, $args) + { + return call_user_func_array(array($this->handler, $method) , $args); + } } diff --git a/ThinkPHP/Library/Think/Cache/Driver/Redisd.class.php b/ThinkPHP/Library/Think/Cache/Driver/Redisd.class.php new file mode 100644 index 0000000000000000000000000000000000000000..7c9e08e079b561b8266783b67747d56d577c73d5 --- /dev/null +++ b/ThinkPHP/Library/Think/Cache/Driver/Redisd.class.php @@ -0,0 +1,302 @@ +'Redisd', //默认动态缓存为Redis + 'DATA_CACHE_PREFIX' =>'Redis_', //缓存前缀 + 'DATA_CACHE_TIME' =>0, //默认过期时间,默认0为永不过期 + 'REDISD_HOST' =>'192.168.1.11,192.168.1.12', //redis服务器ip,多台用逗号隔开;读写分离开启时,第一台负责写,其它[随机]负责读 + 'REDISD_MASTER_FAILOVER' =>'192.168.1.12', //当主发生故障时,从12断从升级为主,读写均在12 + 'REDISD_PORT' =>6379, //端口号 + 'REDISD_TIMEOUT' =>10, //连接超时时间 + 'REDISD_PERSISTENT' =>true, //是否长连接 false=短连接,推荐长连接 + 'REDISD_AUTH' =>'', //AUTH认证密码,当redis服务直接暴露在外网时需要 + */ + +/** + $redis = \Think\Cache::getInstance(); + $redis->master(true); + $redis->get($redis->getOptions('prefix').'key'); + */ + +namespace Think\Cache\Driver; + +use Think\Cache; + +class Redisd extends Cache +{ + static $redis_rw_handler; + static $redis_err_pool; + + /** + * 为了在单次php请求中复用redis连接,第一次获取的options会被缓存,第二次使用不同的$options,将会无效 + * + * @param array $options 缓存参数,来自于S函数和\Think\Cache::getInstance("redisd", $options); + * @access public + */ + public function __construct($options = array()) + { + if (! extension_loaded('redis' )) { + throw_exception(L('_NOT_SUPPERT_') . ':redis'); + } + + $default = array ( + 'host' => C('REDISD_HOST') ? C('REDISD_HOST') : '127.0.0.1', + 'port' => C('REDISD_PORT') ? C('REDISD_PORT') : 6379, + 'timeout' => C('REDISD_TIMEOUT') ? C('REDISD_TIMEOUT') : 0, + 'persistent' => C('REDISD_PERSISTENT'), + 'auth' => C('REDISD_AUTH'), + 'server_master_failover' => C('REDISD_MASTER_FAILOVER'), + ); + + $options = array_merge($default, $options); + + $this->options = $options; + $this->options ['expire'] = isset($options ['expire']) ? $options ['expire'] : C('DATA_CACHE_TIME'); + $this->options ['prefix'] = isset($options ['prefix']) ? $options ['prefix'] : C('DATA_CACHE_PREFIX'); + $this->options ['length'] = isset($options ['length']) ? $options ['length'] : 0; + $this->options ['func'] = $options ['persistent'] ? 'pconnect' : 'connect'; + + $host = explode(",", trim($this->options ['host'], ",")); + $host = array_map("trim", $host); + + $this->options ["servers"] = count($host); + $this->options ["server_master"] = array_shift($host); + $this->options ["server_master_failover"] = explode(",", trim($this->options ['server_master_failover'], ",")); + $this->options ["server_slave"] = (array)$host; + } + + /** + * 主从选择器,配置多个Host则自动启用读写分离,默认主写,随机从读 + * 随机从读的场景适合读频繁,且php与redis从位于单机的架构,这样可以减少网络IO + * 一致Hash适合超高可用,跨网络读取,且从节点较多的情况,本业务不考虑该需求 + * + * @access public + * @param bool $master true 默认主写 + */ + public function master($master = false) + { + if (isset(self::$redis_rw_handler[$master])) { + return $this->handler = self::$redis_rw_handler[$master]; + } + + //如果不为主,则从配置的host剔除主,并随机读从,失败以后再随机选择从 + //另外一种方案是根据key的一致性hash选择不同的node,但读写频繁的业务中可能打开大量的文件句柄 + if(!$master && $this->options["servers"] > 1) { + shuffle($this->options["server_slave"]); + $host = array_shift($this->options["server_slave"]); + }else{ + $host = $this->options["server_master"]; + } + + $this->handler = new \Redis(); + $func = $this->options ['func']; + + $parse = parse_url($host); + $host = isset($parse['host']) ? $parse['host'] : $parse['path']; + $port = $parse['port'] ? $parse['port'] : $this->options ['port']; + + $this->handler->$func($host, $port, $this->options ['timeout']); + + if ($this->options ['auth'] != null) { + $this->handler->auth($this->options ['auth']); + } + + //发生错误则摘掉当前节点 + try { + $error = $this->handler->getLastError(); + } catch (\RedisException $e) { + //phpredis throws a RedisException object if it can't reach the Redis server. + //That can happen in case of connectivity issues, if the Redis service is down, or if the redis host is overloaded. + //In any other problematic case that does not involve an unreachable server + //(such as a key not existing, an invalid command, etc), phpredis will return FALSE. + + \Think\Log::write(sprintf("redisd->%s:%s:%s", $master ? "master" : "salve", $host, $e->getMessage()), \Think\Log::WARN); + + //主节点挂了以后,尝试连接主备,断开主备的主从连接进行升主 + if($master) { + if(! count($this->options["server_master_failover"])) { + E("redisd master: no more server_master_failover. {$host} : ".$e->getMessage()); + return false; + } + + $this->options["server_master"] = array_shift($this->options["server_master_failover"]); + $this->master(); + + \Think\Log::write(sprintf("master is down, try server_master_failover : %s", $this->options["server_master"]), \Think\Log::WARN); + + //如果是slave,断开主从升主,需要手工同步新主的数据到旧主上 + //目前这块的逻辑未经过严格测试 + //$this->handler->slaveof(); + } else { + //尝试failover,如果有其它节点则进行其它节点的尝试 + foreach ($this->options["server_slave"] as $k=>$v) + { + if (trim($v) == trim($host)) + unset($this->options["server_slave"][$k]); + } + + //如果无可用节点,则抛出异常 + if(! count($this->options["server_slave"])) { + \Think\Log::write("已无可用Redis读节点", \Think\Log::EMERG); + E("redisd slave: no more server_slave. {$host} : ".$e->getMessage()); + return false; + } else { + \Think\Log::write("salve {$host} is down, try another one.", \Think\Log::EMERG); + return $this->master(false); + } + } + } catch(\Exception $e) { + E($e->getMessage(), $e->getCode()); + } + + self::$redis_rw_handler[$master] = $this->handler; + } + + /** + * 读取缓存 + * + * @access public + * @param string $name 缓存key + * @return mixed + */ + public function get($name) + { + N('cache_read', 1); + $this->master(false); + + try { + $value = $this->handler->get($this->options ['prefix'] . $name); + } catch (\RedisException $e) { + unset(self::$redis_rw_handler[0]); + $this->master(); + $this->get($name); + } catch (\Exception $e) { + \Think\Log::write($e->getMessage(), \Think\Log::WARN); + } + + $jsonData = null; + //如果是对象则进行反转 + if (!empty($value) && false !== strpos("[{", $value[0])) { + $jsonData = $value ? json_decode($value, true) : $value; + } + + return ($jsonData === NULL) ? $value : $jsonData; + } + + /** + * 写入缓存 + * + * @access public + * @param string $name 缓存key + * @param mixed $value 缓存value + * @param integer $expire 过期时间,单位秒 + * @return boolen + */ + public function set($name, $value, $expire = null) + { + N('cache_write', 1); + $this->master(true); + + if (is_null($expire )) { + $expire = $this->options ['expire']; + } + $name = $this->options ['prefix'] . $name; + + /** + * 兼容历史版本 + * Redis不支持存储对象,存入对象会转换成字符串 + * 但在这里,对所有数据做json_decode会有性能开销 + */ + $value = (is_object($value) || is_array($value )) ? json_encode($value) : $value; + + if ($value === null) { + return $this->handler->delete($this->options ['prefix'] . $name); + } + + // $expire < 0 则等于ttl操作,列为todo吧 + try { + if (is_int($expire) && $expire) { + $result = $this->handler->setex($name, $expire, $value); + } else { + $result = $this->handler->set($name, $value); + } + } catch (\RedisException $e) { + unset(self::$redis_rw_handler[1]); + $this->master(true); + $this->set($name, $value, $expire); + } catch (\Exception $e) { + \Think\Log::write($e->getMessage(), \Think\Log::WARN); + } + + return $result; + } + + /** + * 返回句柄对象 + * 需要先执行 $redis->master() 连接到 DB + * + * @access public + * @return object + */ + function handler() + { + return $this->handler; + } + + /** + * 删除缓存 + * + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) + { + N('cache_write', 1); + $this->master(true); + return $this->handler->delete($this->options ['prefix'] . $name); + } + + /** + * 清除缓存 + * + * @access public + * @return boolen + */ + public function clear() + { + N('cache_write', 1); + $this->master(true); + return $this->handler->flushDB (); + } + + /** + * 析构释放连接 + * + * @access public + */ + public function __destruct() + { + //该方法仅在connect连接时有效 + //当使用pconnect时,连接会被重用,连接的生命周期是fpm进程的生命周期,而非一次php的执行。 + //如果代码中使用pconnect, close的作用仅是使当前php不能再进行redis请求,但无法真正关闭redis长连接,连接在后续请求中仍然会被重用,直至fpm进程生命周期结束。 + + try { + if(method_exists($this->handler, "close")) + $this->handler->close (); + } catch (\Exception $e) { + } + } +} \ No newline at end of file diff --git a/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php b/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php index 36e0c64b3f87a7f62f7b9d24ee77315863d2b793..d69d7a6dcc71667d63862f0a2d3256032a64082a 100644 --- a/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php +++ b/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php @@ -131,6 +131,7 @@ class Mongo extends Driver } } N('db_write', 1); // 兼容代码 + $this->model = $options['model']; $this->executeTimes++; try { if ($this->config['debug']) { @@ -298,6 +299,7 @@ class Mongo extends Driver if (isset($options['table'])) { $this->switchCollection($options['table']); } + $this->executeTimes++; N('db_write', 1); // 兼容代码 $this->model = $options['model']; @@ -389,6 +391,16 @@ class Mongo extends Driver if (isset($options['table'])) { $this->switchCollection($options['table'], '', false); } + + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } + $this->model = $options['model']; $this->queryTimes++; N('db_query', 1); // 兼容代码 @@ -440,6 +452,11 @@ class Mongo extends Driver $this->debug(false); $this->_cursor = $_cursor; $resultSet = iterator_to_array($_cursor); + + if($cache) { + S($key,$resultSet,$cache['expire'],$cache['type']); + } + return $resultSet; } catch (\MongoCursorException $e) { E($e->getMessage()); @@ -470,6 +487,15 @@ class Mongo extends Driver if (isset($options['table'])) { $this->switchCollection($options['table'], '', false); } + + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } $this->model = $options['model']; $this->queryTimes++; N('db_query', 1); // 兼容代码 @@ -483,6 +509,11 @@ class Mongo extends Driver $this->debug(true); $count = $this->_collection->count($query); $this->debug(false); + + if($cache) { + S($key,$count,$cache['expire'],$cache['type']); + } + return $count; } catch (\MongoCursorException $e) { E($e->getMessage()); diff --git a/ThinkPHP/Library/Think/Model/MongoModel.class.php b/ThinkPHP/Library/Think/Model/MongoModel.class.php index 67afdb481c6fe6bf47927241f06a26726e4ec53b..5e5d6980c48a3ea8af4aea4789f86a3dd46035b0 100644 --- a/ThinkPHP/Library/Think/Model/MongoModel.class.php +++ b/ThinkPHP/Library/Think/Model/MongoModel.class.php @@ -495,4 +495,17 @@ class MongoModel extends Model { return $this->db->getCollection(); } + + /** + * 设置查询超时时间,-1为不限时间 + * @param int $timeout 毫秒 + * @access public + * @return object + */ + public function timeout($timeout=-1) { + if(class_exists('MongoCursor')) { + MongoCursor::$timeout = $timeout; + } + return $this; + } } diff --git a/ThinkPHP/Library/Think/View.class.php b/ThinkPHP/Library/Think/View.class.php index f4d13e175fedae0785e6abcbb66e8b9ab47ce48d..d44619d4970abef8d9aca57b62d0ae074fdd4792 100644 --- a/ThinkPHP/Library/Think/View.class.php +++ b/ThinkPHP/Library/Think/View.class.php @@ -102,7 +102,6 @@ class View // 网页字符编码 header('Content-Type:' . $contentType . '; charset=' . $charset); header('Cache-control: ' . C('HTTP_CACHE_CONTROL')); // 页面缓存控制 - header('X-Powered-By:ThinkPHP'); // 输出模板文件 echo $content; }