Yii 框架中使用redis缓存数据

Yii框架中可以自由配置缓存方式,比如最常用的memcache/memcahed,只需要在config中添加如下配置信息即可。

'cache' => array (
		'class' => 'system.caching.CMemCache',
		'servers' => array (
					array (
						'host' => '127.0.0.1',
						'port' => 11211,
						'weight' => 60 
					) 
				),
				'hashKey' => false,
				'serializer' => false,
				'useMemcached' => true 
		)

使用方法也很方便。

<?php
	Yii::app()->cache->set('aaaa','value');

Radis 作为一个内存型数据库,我们通常会将它作为可长时间将数据存贮到内存中的缓存系统,事实上不能及时做持久化处理的存储系统都是不稳定的。对于传统的key-value缓存系统而言,radis又同时支持多种数据结构(String,List,Set,Map,StoredSet),有关支持的数据类型可以参考redis中文站点猛击我。 支持多种数据结构意味着我们能够利用这些数据结构特性,展开一些特定的业务,比如在之前的项目当中使用的时间轴。如果用传统数据库实现性能会相当低下,既要考虑多种表交叉重组,又要考虑重组后的分页问题。如果用非关系型数据库就会很方便的处理,利用List特性就可以方便的“压入”用户的历史数据。 先来看一下如何配置一个redis缓存。

'redis_cache' => array (
	'class' => 'system.caching.CRedisCache',
	'hostname' =>'127.0.0.1',
	'port'=>6379,
	'password'=>'123456',
	'database'=>0
) 
//使用方法
Yii::app()->redis_cache->set('aaaa','value');

利用Yii框架中的caching.CRedisCache类,password是部署服务器当中的auth配置。 使用方法和使用memcache/memcahced中的cache一样,直接set/get即可。 看一下CRedisCached这个实现类

<?php
/**
 * CRedisCache class file
 *
 * @author Carsten Brandt <mail@cebe.cc>
 * @link http://www.yiiframework.com/
 * @copyright 2008-2013 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

/**
 * CRedisCache implements a cache application component based on {@link http://redis.io/ redis}.
 *
 * CRedisCache needs to be configured with {@link hostname}, {@link port} and {@link database} of the server
 * to connect to. By default CRedisCache assumes there is a redis server running on localhost at
 * port 6379 and uses the database number 0.
 *
 * CRedisCache also supports {@link http://redis.io/commands/auth the AUTH command} of redis.
 * When the server needs authentication, you can set the {@link password} property to
 * authenticate with the server after connect.
 *
 * See {@link CCache} manual for common cache operations that are supported by CRedisCache.
 *
 * To use CRedisCache as the cache application component, configure the application as follows,
 * 
 * array(
 *     'components'=>array(
 *         'cache'=>array(
 *             'class'=>'CRedisCache',
 *             'hostname'=>'localhost',
 *             'port'=>6379,
 *             'database'=>0,
 *         ),
 *     ),
 * )
 * 
 *
 * The minimum required redis version is 2.0.0.
 *
 * @author Carsten Brandt <mail@cebe.cc>
 * @package system.caching
 * @since 1.1.14
 */
class CRedisCache extends CCache
{
	/**
	 * @var string hostname to use for connecting to the redis server. Defaults to 'localhost'.
	 */
	public $hostname='localhost';
	/**
	 * @var int the port to use for connecting to the redis server. Default port is 6379.
	 */
	public $port=6379;
	/**
	 * @var string the password to use to authenticate with the redis server. If not set, no AUTH command will be sent.
	 */
	public $password;
	/**
	 * @var int the redis database to use. This is an integer value starting from 0. Defaults to 0.
	 */
	public $database=0;
	/**
	 * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
	 */
	public $timeout=null;
	/**
	 * @var resource redis socket connection
	 */
	private $_socket;

	/**
	 * Establishes a connection to the redis server.
	 * It does nothing if the connection has already been established.
	 * @throws CException if connecting fails
	 */
	protected function connect()
	{
		$this->_socket=@stream_socket_client(
			$this->hostname.':'.$this->port,
			$errorNumber,
			$errorDescription,
			$this->timeout ? $this->timeout : ini_get("default_socket_timeout")
		);
		if ($this->_socket)
		{
			if($this->password!==null)
				$this->executeCommand('AUTH',array($this->password));
			$this->executeCommand('SELECT',array($this->database));
		}
		else
			throw new CException('Failed to connect to redis: '.$errorDescription,(int)$errorNumber);
	}

	/**
	 * Executes a redis command.
	 * For a list of available commands and their parameters see {@link http://redis.io/commands}.
	 *
	 * @param string $name the name of the command
	 * @param array $params list of parameters for the command
	 * @return array|bool|null|string Dependend on the executed command this method
	 * will return different data types:
	 * <ul>
	 *   <li><code>true</code> for commands that return "status reply".</li>
	 *   <li><code>string</code> for commands that return "integer reply"
	 *       as the value is in the range of a signed 64 bit integer.</li>
	 *   <li><code>string</code> or <code>null</code> for commands that return "bulk reply".</li>
	 *   <li><code>array</code> for commands that return "Multi-bulk replies".</li>
	 * </ul>
	 * See {@link http://redis.io/topics/protocol redis protocol description}
	 * for details on the mentioned reply types.
	 * @trows CException for commands that return {@link http://redis.io/topics/protocol#error-reply error reply}.
	 */
	public function executeCommand($name,$params=array())
	{
		if($this->_socket===null)
			$this->connect();

		array_unshift($params,$name);
		$command='*'.count($params)."\r\n";
		foreach($params as $arg)
			$command.='$'.strlen($arg)."\r\n".$arg."\r\n";

		fwrite($this->_socket,$command);

		return $this->parseResponse(implode(' ',$params));
	}

	/**
	 * Reads the result from socket and parses it
	 * @return array|bool|null|string
	 * @throws CException socket or data problems
	 */
	private function parseResponse()
	{
		if(($line=fgets($this->_socket))===false)
			throw new CException('Failed reading data from redis connection socket.');
		$type=$line[0];
		$line=substr($line,1,-2);
		switch($type)
		{
			case '+': // Status reply
				return true;
			case '-': // Error reply
				throw new CException('Redis error: '.$line);
			case ':': // Integer reply
				// no cast to int as it is in the range of a signed 64 bit integer
				return $line;
			case '$': // Bulk replies
				if($line=='-1')
					return null;
				$length=$line+2;
				$data='';
				while($length>0)
				{
					if(($block=fread($this->_socket,$length))===false)
						throw new CException('Failed reading data from redis connection socket.');
					$data.=$block;
					$length-=(function_exists('mb_strlen') ? mb_strlen($block,'8bit') : strlen($block));
				}
				return substr($data,0,-2);
			case '*': // Multi-bulk replies
				$count=(int)$line;
				$data=array();
				for($i=0;$i<$count;$i++)
					$data[]=$this->parseResponse();
				return $data;
			default:
				throw new CException('Unable to parse data received from redis.');
		}
	}

	/**
	 * Retrieves a value from cache with a specified key.
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key a unique key identifying the cached value
	 * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
	 */
	protected function getValue($key)
	{
		return $this->executeCommand('GET',array($key));
	}

	/**
	 * Retrieves multiple values from cache with the specified keys.
	 * @param array $keys a list of keys identifying the cached values
	 * @return array a list of cached values indexed by the keys
	 */
	protected function getValues($keys)
	{
		$response=$this->executeCommand('MGET',$keys);
		$result=array();
		$i=0;
		foreach($keys as $key)
			$result[$key]=$response[$i++];
		return $result;
	}

	/**
	 * Stores a value identified by a key in cache.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return boolean true if the value is successfully stored into cache, false otherwise
	 */
	protected function setValue($key,$value,$expire)
	{
		if ($expire==0)
			return (bool)$this->executeCommand('SET',array($key,$value));
		return (bool)$this->executeCommand('SETEX',array($key,$expire,$value));
	}

	/**
	 * Stores a value identified by a key into cache if the cache does not contain this key.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return boolean true if the value is successfully stored into cache, false otherwise
	 */
	protected function addValue($key,$value,$expire)
	{
		if ($expire == 0)
			return (bool)$this->executeCommand('SETNX',array($key,$value));

		if($this->executeCommand('SETNX',array($key,$value)))
		{
			$this->executeCommand('EXPIRE',array($key,$expire));
			return true;
		}
		else
			return false;
	}

	/**
	 * Deletes a value with the specified key from cache
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key the key of the value to be deleted
	 * @return boolean if no error happens during deletion
	 */
	protected function deleteValue($key)
	{
		return (bool)$this->executeCommand('DEL',array($key));
	}

	/**
	 * Deletes all values from cache.
	 * This is the implementation of the method declared in the parent class.
	 * @return boolean whether the flush operation was successful.
	 */
	protected function flushValues()
	{
		return $this->executeCommand('FLUSHDB');
	}
}

可以看出Yii并没有使用其他的redis扩展,现在流行的PHP redis连接方式主要有两种。其他参见其他client- predis:一个不需要安装PHP扩展的连接Redis的组件包

  • PHP-redis:一个redis的php扩展

看源码可以看到,Yii使用的是stream_socket_client这种方式连接的redis服务器。我们模拟一下看看

$socket=@stream_socket_client(
		'127.0.0.1:6379',
		$errorNumber,
		$errorDescription,
		300
);
var_dump($socket);

使用这种方法可以顺利的连接到远程socket端口,其实以前如果用过的话就能知道,其实MySql也是可以用这种方式获得连接的。但不提倡,暂时先用这种方法,有时间再来利用扩展。 已经很强大了,可这满足不了我们目前的需要。该类集成自CCache只实现了其中的set get方法,其他的并没有实现,而更多地是需要我们借助executeCommand($name,$params=array())这个方法来完成我们想要的操作。所以我们需要对该方法做进一步封装,利用executeCommand。 我们首先扩展一个List左侧压入一个数据的操作

<?php
/**
 * @author Abel
 * email:abel.zhou@hotmail.com
 * 2014-1-17
 * UTF-8
 */
class ZRedisCache extends CRedisCache{

    /**
     * 左侧压入一个数据
     * @param String $key
     * @param String $value
     * @return int
     */
    public function lpush($key,$value){
        if(!is_string($value)){
            throw new Exception('The value type is not a string.If u want to push an array data.You can use JSON.');
        }
        $num = $this->executeCommand('LPUSH',array($key,$value));
        return intval($num);
    }
}

可以看到数据都可以顺利的压入列队当中。

Categories: ,

Updated: