1. 程式人生 > >Redis資料型別以及應用場景解析

Redis資料型別以及應用場景解析

 

前方高能,前排預警,大量圖文!!! 手機看帖的小夥子請注意你的流量套餐!!!

好了,切入正題。

在關係型資料庫一統天下的時候,有一個義大利人開發出了一種非關係型資料庫 Redis,用來解決關係型資料庫在面對大流量高併發產生的資料庫巨大讀寫壓力,Redis以高效的讀寫效率聞名,比起memecache, Redis擁有更多的資料型別,在面對複雜的應用場景時,有著更多更合適的型別選擇。所以為什麼我們要用Redis?因為傳統的結構嚴謹的關係型資料庫,其劣勢是在大量讀寫壓力之下,效能下降得非常明顯,這個時候專案的使用者體驗就會明顯下降,我們不得不引入Redis來充當程式跟資料庫之間的一個橋樑,幫助緩解關係型資料庫的壓力,但是Redis並不能取代傳統的關係型資料庫(比如mysql) ,因為大量的應用資料需要持久化寫入硬碟,而Redis的持久化並不十分理想(下面會給大家介紹),所以Redis起的是一個輔助作用。

首先回顧下Redis的基礎知識( 我們這裡不講拗口難懂的名詞 )

1 什麼是Redis?

答:一種nosql資料庫(非關係形資料庫)。

 2 為什麼要用Redis?

答:緩解資料庫壓力

3 Redis有哪幾種資料型別?

1 字串(string) 2 雜湊( hash ) 3 佇列(list) 4 集合(set) 5 有序集合(sorted set)

如果對於Redis資料型別不太瞭解的可以自行百度,本章不作科普,顯得太囉嗦。

知識這個東西,除了瞭解之外,必須加以實踐對不對?馬上給大家奉上乾貨,由於之前的開發的原始碼找不到了,在下只好重新簡單地寫了幾個demo ,旨在傳達思想,不作為具體的應用最佳實現,以下基於PHP向大家講解。

注意:Redis所有的資料型別的儲存都是基於key=>value的方式來儲存的

字串(string)

最簡單,應用最多的一個數據型別。

比如我們網站有一個需求是要求使用者請求簡訊介面,我們需要判斷使用者在一分鐘內不能再次發起請求,必須至少等待一分鐘。

那麼剖析一下這個需求,2個點 

1:一個使用者(其實就是手機號)

2:單位時間 (可以想到就是過期時間)

因為Redis的所有鍵都是可以設定過期時間的,那麼解決思路是把請求的使用者手機號+1分鐘的儲存時間

模擬程式碼:

<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
$mobile='137****7242';//任意一個手機號
$expire=60;
$keyName="message:".$mobile;
if($redis->get($keyName))
{
	die("一分鐘內不能頻繁請求");
}
$re=$redis->set($keyName,1,$expire);
if($re)
{
	echo '傳送成功';
}

 那麼在第一次執行此程式碼的時候,如下:

由於Redis中並沒有該使用者的資料,所以會往Redis中寫入該使用者的資訊

我們定義鍵名的時候要以:來間隔,這樣可以生成如圖所示的層級目錄,方便檢視管理。

可以看到我們定義的鍵名是message:137****7242 ,鍵值是1 ,TTL是過期時間,截圖的時候已經不足一分鐘

那麼我們再次執行此程式碼可以看到:

因為Redis中已經存在該使用者的傳送資訊了,那麼在一分鐘內再次請求都會被攔截下來

一分鐘後可以看到redis中的message:137****7242這個鍵已經不見了(過期清理):

這個時候我們再次請求:

可以發現又請求成功了

這也就實現了對使用者單位時間內請求次數的限制,結合實際專案可以做一些相應的擴充套件。

 

string型別最經常的情況下的是拿來做快取的。

像一些高頻訪問的資料,但是又不是更新很頻繁的,就適合快取起來。

比如首頁有個熱門菜譜這麼一個列表,顯示10條菜譜,那麼這個就是熱點資料,因為訪問頻率是最高的,如果採用去mysql中查詢的方式,那麼在菜譜表資料量一上來,過了百萬級,查詢效率就下降比較明顯了。另外一個是日後使用者量一增加,資料庫的吞吐量不足的話,也會使得查詢效率明顯降低,那麼我們可以選擇把這部分熱點資料給快取到Redis中去,之後的所有資料讀取都不經過資料庫,都會到Redis中去讀取資料

模擬程式碼:

<?php 
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();

$keyName="dishes:hot:top10";
//如果有快取
if($hotDishes=unserialize($redis->get($keyName)))
{
	echo '從快取中獲得這些資料:<br>';
	var_dump($hotDishes);
}
else
{
	//沒有快取,查詢資料庫
	$sql="select dishes_id,dishes_name,dishes_image from dishes where is_hot =1 order by dishes_id DESC limit 10";
	$hotDishes=$db->query($sql);
	if(!empty($hotDishes))
	{
		//寫入Redis
		$redis->set($keyName,serialize($hotDishes),3600*2);
	}
	var_dump($hotDishes);
}

那麼我們來看看效果先 ,一會再講解

第一次執行此程式碼的時候可以看到查出來10條資料:

以及在Redis中生成了快取:

 

快取的時間TTL是我們設定的兩個小時,注意因為Redis的string型別只能儲存字串,所以我們的陣列要經過serialize()序列化之後才可以儲存,同理取出的時候也要反序列化(見程式碼)。那麼在兩個小時之內我們都可以通過讀取快取來獲取這個資料,再次訪問:

這就是string 型別用於資料快取的基本用法。

 

雜湊型別(hash)

雜湊型別最典型的一個應用,就是作為Session的替換方案,在單機伺服器的時候我們可能沒有感覺到Session的潛在問題,可是如果日後業務發展加快,需要水平擴充套件伺服器的時候(負載均衡),Session共享就會成為一個問題,雖說nginx有根據IP分發請求的策略,但不是最優解,我們最理想的是可以實現所有Session的集中管理。

這裡我們只做最最簡單的演示 , 在實際的登陸中還有非常多的驗證:

<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();

$username=$_POST['username'];
$password=$_POST['password'];
$sql="select * from pr_user where user = '$username'";
$re=$db->query($sql);
if($re[0]['pass']==md5($password.'zc'))
{
	//生成SESSION
	$session_id=getRandomString(20);
	$session_redis_key="session:".date("Y-m-d",time()).":".$session_id;
	unset($re[0]['pass']);
	//寫入Redis
	$redis->hashSet($session_redis_key,$re[0],3600*2);
	//寫入資料庫
	$sql="update pr_user set session_id = '$session_id'";
	$setRe=$db->query($sql);
	if($setRe)
	{
		//設定cookie
		setCookie("session_id",$session_id);
		echo 'login successfully';
	}
}

function getRandomString($len, $chars=null)
{
    if (is_null($chars)){
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    }  
    mt_srand(10000000*(double)microtime());
    for ($i = 0, $str = '', $lc = strlen($chars)-1; $i < $len; $i++){
        $str .= $chars[mt_rand(0, $lc)];  
    }
    return $str;
}

我們要做的就是模擬session_id  用20位的隨機數來代表session_id,Redis鍵名用 'session:日期:session_id'  這樣的形式來儲存,儲存的值是使用者的基本資訊,然後向客戶端發起設定Cookie的請求,setCookie("session_id",$session_id),具體的登陸頁面請各位自行建立。

那麼我們在登陸頁面把使用者名稱密碼輸入之後,提交到這個檔案會得到:

可以看到登陸成功了,這個時候我們看一下資料庫:

我們已經把session_id 儲存到資料庫了,儲存下來的原因是方便後期對使用者的追蹤。

我們再看下Redis:

可以看到Redis中也生成了對應的雜湊值,過期時間2小時。

那麼設定好了要怎麼用呢?假設我使用者中心的程式碼如下:

<?php 
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");
$db = new Mysql();
$redis = new RedisService();

$session_id = $_COOKIE['session_id'];
$session_redis_key="session:".date("Y-m-d",time()).":".$session_id;


$session = $redis->hashGet($session_redis_key);

if(!$session)
{
	header("location:./1.html");
}
$_SESSION=$session;
echo '您已經登陸了,您的資訊是:<br>';
var_dump($_SESSION);

首先獲取客戶端傳過來的Cookie.session_id  ,比如 5LJyeAgA9LpZSQeJenBC

然後拼接成 session:2018-08-30:5LJyeAgA9LpZSQeJenBC,讀取Redis中對應的hash值,不同於string型別的是,hash值取出來的時候就是一個關聯陣列了,不需要反序列化。最終我們取得的資料是:

將這個陣列賦值給$_SESSION超全域性變數就可以在任何一處呼叫了,結合實際專案的話建議將這一層放在頂層的基類,這樣就基本完成了Redis代替SESSION的解決方案,好處是做水平擴充套件的時候只需要配置幾臺機器的Redis,就可以共用同一個Redis資料庫

 

 

佇列(List)

佇列算是Redis的一大核心特色吧,在短時間內流量聚集非常集中的時候(比如秒殺、搶購活動), 資料的入庫往往是個問題,短時間的資料幾種入庫分分鐘讓資料庫宕機,結合Redis的佇列,我們可以解決兩個問題:

1 產品超賣

2 資料庫宕機

先從產品超賣要怎麼防止這一點來討論

如果說我用這樣的程式碼來控制產品庫存

<?php
$store_all=10;//庫存最大數量
$sell_num=8;
$store_num=$store_all-$sell_num;//10-8=2
if($store_num>0)
{
	//建立訂單程式碼
	


	//減少庫存程式碼
}

$store_num表示剩餘庫存,咋一看 if($store_num>0) 就給使用者下訂單,貌似沒有問題。

但是!如果是在高併發的場景下,當有三個人同時購買,經過if($store_num>0)這一步的時候都是有庫存的,但是最後卻三個人都下了訂單,也就是超賣

那這個怎麼解決呢?有沒有什麼方法讓判斷庫存的時候不要同時進行判斷呢?可不可以一個一個來?答案是可以的!

Redis的佇列  可以讓多個程序基於其原子性實現單一程序,一個一個過,一個一個來。

思路:比如我某個產品進行搶購,這個產品ID為1 庫存為10  那就建立一個長度為10的佇列,每個使用者搶購的時候就彈出一個,彈完了庫存也就沒了,沒庫存了就下不了訂單。

奉上示例:

搶購前,後臺先建立庫存佇列(預熱):

<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
//10個庫存
$store_all=10;
//商品ID為1
$goods_id=1;
$keyName="store:id:".$goods_id;
$element=1;//無實際意義
for($i=1;$i<=$store_all;$i++)
{
	$redis->push($keyName,$element);
}
echo 'finish push !';

執行:

對應Redis中也生成了一個佇列:

那麼接下來就是使用者在購買下單的時候,每次從這個隊中彈出一個,沒有元素可以彈出了就表示賣完了,彈出成功的話就建立另外一個order佇列,往佇列中push一個訂單,先不寫入資料庫,這個時候寫入資料庫壓力太大了。

<?php
include_once("./RedisService.class.php");
$redis = new RedisService();
//商品ID=1的庫存
$keyName="store:id:1";
$popResult=$redis->pop($keyName,'right');
//彈出成功,還有庫存
if($popResult)
{
	//建立訂單寫入訂單資訊
	$orderInfo['create_time']=date("Y-m-d H:i:s",time());
	$orderInfo['buyer_id']=rand(1000,9999);
	$orderInfo['address_id']=rand(10000,99999);
	//寫入Redis佇列
	$keyName="order:goods_id:1";
	$pushResult=$redis->push($keyName,serialize($orderInfo));
	if($pushResult)
	{
		echo '搶購成功';
	}
}
else
{
	echo '搶購結束';
}

這裡我運行了11次這個檔案,模擬11個使用者購買,因為這臺電腦上沒有併發測試工具,看官將就看,只是演示一個思路。

可以看到在第11次的時候就提示搶購結束了:

這個時候我們來看看Redis:

我們可以看到Redis中的庫存列表(store:id:1)不見了!只有一個訂單列表(order:goods_id:1)對應著之前搶購的10個產品

這是因為佇列在彈出完了之後就從記憶體中釋放了。沒有了庫存列表,自然無法繼續搶購下去,這樣就可以防止超賣。

在把訂單存入到Redis中後,就可以先返回給使用者搶購成功的提示了。具體的訂單資訊寫入資料庫,我們用非同步的指令碼來寫入。

示例程式碼:

<?php
include_once("./RedisService.class.php");
include_once("./Mysql.class.php");

$redis = new RedisService();
//讀取Redis訂單列表
while(true)
{
	$keyName = "order:goods_id:1";
	//出隊反序列化
	$orderInfo = unserialize($redis->pop($keyName));
	
	if($orderInfo)
	{
		//入庫
		$orderId="M192".rand(1000,9999).time().rand(100000,999999);
		$db = new Mysql();
		$sql="insert into pr_order (`id`,`order_id`,`buyer_id`,`goods_id`,`address_id`,`create_time`)  values (default,'$orderId',$orderInfo[buyer_id],1,$orderInfo[address_id],'$orderInfo[create_time]')";
		$re=$db->query($sql);
		if($re)
		{	
			echo '訂單寫入成功';
		}
	}
	
}

 

這裡我們簡單地用一個死迴圈來不停讀取Redis中的訂單佇列,每次訂單佇列彈出一個元素,把這個元素的資訊組織好後寫入資料庫,就是這麼簡單。

在我們執行這個指令碼之後,可以看到訂單都被寫入資料庫了:

這樣可以防止對資料庫的衝擊太大,防止宕機。

綜上就是提供這麼一個思路,利用佇列來對瞬間併發做這麼一個處理,可以有效防止超賣,將使用者的搶購流程和系統的訂單建立給分開了,使用者先搶購完了,我係統再慢慢地建立訂單。

佇列還有其他一些應用場景,大家在運用的時候多加思考。

 

集合(set)

集合相信大家都不會陌生了,什麼交集、並集、差集,都是這些基本原理的應用。

大家使用集合的時候心裡要有一個概念叫做 ‘範圍’。

比如說你微博關注的人,我微博關注的人,現在求我們倆共同的關注好友,怎麼求?

這個時候可以理一下思路,把問題轉化成,求你跟我關注的好友”範圍“的交集。

那不就是兩個集合的交集?

這個具體的應用不是特別多,這裡這是簡單介紹一下思路,具體應用的時候多想想集合的交集、並集、差集就可以了。

利用集合中的元素都是唯一的不重複的,像一些活動每人每天只能參加一次之類的,把參加過活動的使用者扔到一個集合裡,今天參加過這個活動沒有就只需要看這個使用者在不在這個集合裡就可以知道了把,activity:10 (參加過活動ID為10的使用者的ID的集合) ,我這麼說大家能理解吧。

想這樣就可以表示活動id為10的參加使用者有 11、33、36

 

 

有序集合(sorted set)

這個用得可能要比集合(set)要多一點,它的好處在於集合中每一個成員都有一個分數(score)

像這樣:

 

 

那麼我們可以想到,分數是不是有高低啊?分數是不是可以排序啊?這個就類似排行榜之類的

比如一場考試,可以把每個人的成績都放進去,最後找出來前10名的學生可以吧?

同樣是考試,考3科,語文數學英語,那就可以三個科目各自一個集合,然後求學生的總成績只要拿這三個集合做一個交集然後存到另外一個新的集合裡,這個新的集合的成員是之前集合的成員交集,分數是之前集合交整合員的分數總和,這不就是一個三科的總分?

這裡我就不貼程式碼了。在嘗試應用的時候只要把思路理清了並不難。

 

可以看到Redis的五種基礎資料型別各有特色,可以輕鬆使用於不同的場景需求,比起單一的string型別的Memecache ,Redis必然成為大熱之選。

好了,今天就先討論到這裡,後續會將更多的技術分享給大家。

如果你覺得還不錯的話,可以轉個貼分享給小夥伴,但是拒絕複製刪帖,還是應該支援原創的。-.- 

 

上次問我要Redis封裝類庫的小夥伴這裡說一下,我的類庫是自己封裝的,因為我不太喜歡擴充套件裡邊的方法命名跟引數順序,所以就自己封裝了一個,符合我自己的使用習慣,不知道你們喜不喜歡,先貼上吧。

<?php 
class RedisService
{
	//預設配置引數,根據專案自行讀取配置值
	private static $_connectInfo = array(
		'host'=>'127.0.0.1',
		'port'=>6379,
		'auth'=>'123123'
	);
	//報錯設定,根據專案讀取配置值
	private static $debug = true;

	//redis物件的儲存變數
	private static  $redisObj;

	/**
	 * 例項化自動連線Redis服務
	 * @param array $connectInfo [自定義連線引數陣列]
	 */
	public function __construct($connectInfo=array())
	{
		if(!empty($connectInfo))
		{
			self::$_connectInfo = $connectInfo;
		}
		self::RedisConnect();
	}

	/**
	 * Redis連線方法
	 */
	private static function RedisConnect()
	{
			
		$redis = new Redis();
		$connectResult=$redis->connect(self::$_connectInfo['host'],self::$_connectInfo['port']);
		if(!$connectResult)
		{
			self::error('Redis connect fail,please check the configure');
		}
		$authVerify=$redis->auth(self::$_connectInfo['auth']);
		if(!$authVerify)
		{
			self::error('The wrong auth ');
		}
		self::$redisObj = $redis;
		
	}



	/**
	 * [rangeToArray 工具函式:範圍字串轉換成陣列]
	 * @param  [string] $range [範圍字串]
	 * @return [array]        [範圍陣列]
	 */
	public function rangeToArray($range)
	{
		if(!substr_count($range,'->')||substr_count($range,'->')>1||strpos($range,'->') ==strlen($range)-1)
		{
			self::error("The range need to like a->b");
		}
		$rangeArr = explode('->',$range);
		return $rangeArr;
	}

	
	/**
	 * [is_array 是否為陣列檢查方法]
	 * @param  [array]  $var       [檢查物件]
	 * @param  [string]  $methodName [請求方法]
	 * @return [boolean]             [檢查結果]
	 */
	private function arrayCheck($var,$methodName)
	{	
		if(!is_array($var))
		{
			self::error("function $methodName : the param must be an array");
		}
	}


	/**
	 * [stringCheck 是否為字串檢查方法]
	 * @param  [number] $var        [檢查物件]
	 * @param  [string] $methodName [請求方法]
	 * @return [boolean]             [檢查結果]
	 */
	private function stringCheck($var,$methodName)
	{
		if(!is_string($var))
		{
			self::error("function $methodName : the param must be string type ");
		}
	}

	/**
	 * [numberCheck description]
	 * @param  [number] $var        [檢查物件]
	 * @param  [string] $methodName [請求方法]
	 * @return [boolean]             [檢查結果]
	 */
	private static function numberCheck($var,$methodName)
	{

		$preg="/^[0-9]+([.]{1}[0-9]+){0,1}$/";
		$matchResult=preg_match($preg,$var);
		if(!$matchResult)
		{
			self::error("function $methodName: the value for current key is not a number");
		}
	}



	/**
	 * [error 報錯方法]
	 * @param  [string] $msg [報錯資訊]
	 */
	private static function error($msg)
	{
		if(self::$debug)
		{
			echo $msg;
		}

		die();
	}
	/**
	 * [set 字串型別 key->value設定]
	 * @param [string] $key   [鍵名]
	 * @param [string] $value [鍵值]
	 * @param [int] $expire [過期時間]
	 * 
	 */
	public function set($key,$value,$expire=null)
	{
		if(!isset($key)||!isset($value))
		{
			self::error('function set lack of params');
		}
		self::stringCheck($key,__FUNCTION__)&&self::stringCheck($value,__FUNCTION__);
		if(!$expire)
		{
			return self::$redisObj->set($key,$value);
		}
		else
		{
			return self::$redisObj->setex($key,$expire,$value);
		}
	}


	/**
	 * [get 字串型別 key->value獲取]
	 * @param  [string] $key [鍵名]
	 * @return [string]      [返回值]
	 */
	public function get($key)
	{
		return self::$redisObj->get($key);
	}

	/**
	 * [mset 字串型別 同時設定多個]
	 * @param  [array] $data [鍵值對陣列]
	 * @return [boolean]       [返回值]
	 */
	public function mset($data)
	{
		self::arrayCheck($data,__FUNCTION__);
		foreach($data as $k => $v)
		{
			 $childResult = $this->set($k,$v);
			 if(!$childResult)
			 {
			 	self::error($k."->".$v."write fail,the program ends");
			 }
		}
		return true;
	}


	/**
	 * [mget 字串型別根據多個鍵名獲取值]
	 * @param  [array] $keys [key陣列]
	 * @return [array]       [key=>value陣列]
	 */
	public function mget($keys)
	{
		self::arrayCheck($keys,__FUNCTION__);
		$return=array();
		foreach($keys as $key)
		{
			if(!is_string($key))
			{
				self::error("function ".__FUNCTION__.": elements  all should be string type");
			}
			$value= $this->get($key);
			if($value)
			{
				$return[$key] = $value;
			}
		}
		return $return;
	}

	/**
	 * [delete 刪除鍵統一操作函式]
	 * @param  [string] $key [鍵名]
	 * @return [int]      [受影響行]
	 */
	public function deleteKey($key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->delete($key);
	}


	/**
	 * [alterNumber 字串型別修改數字鍵值]
	 * @param  [string] $key   [鍵名]
	 * @param  [int] $range [修改範圍]
	 * @return [boolean]        [是否修改成功]
	 */
	public function alterNumber($key,$range=0)
	{
		self::stringCheck($key,__FUNCTION__);
		//變更範圍不為0
		if(!$range)
		{
			self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
		}
		$value=$this->get($key);
		if($value===false)
		{
			self::error("function ".__FUNCTION__.":the value for key ".$key." is no exist");
		}
		//是不是數字型別
		self::numberCheck(abs($value),__FUNCTION__);
		//統一使用incrByFloat
		$result=self::$redisObj->incrByFloat($key,$range);
		return boolval($result);
	}


	/**
	 * [hashSet 雜湊型別設定函式]
	 * @param  [string] $key    [鍵名]
	 * @param  [array] $array  [hash array]
	 * @param  [int] $expire [過期時間,不傳之預設永久]
	 * @return [boolean]         [是否成功]
	 */
	public function hashSet($key,$array,$expire=null)
	{
		self::stringCheck($key,__FUNCTION__);
		if(count($array)>=1)
		{	
			 $flag=true;
			 $keySet=self::$redisObj->hmset($key,$array);
			 if($expire)
			 {
			 	$flag=self::$redisObj->setTimeOut($key,$expire);
			 }
			 return $keySet&&$flag;
		}
	}

	/**
	 * [hashGet 獲取雜湊物件函式]
	 * @param  [type] $key        [鍵名]
	 * @param  array  $fieldArray [field陣列 不傳值獲取全部]
	 * @return [type]             [結果陣列]
	 */
	public function hashGet($key,$fieldArray=array())
	{
		self::stringCheck($key,__FUNCTION__);
		if(empty($fieldArray))
		{
			return self::$redisObj->hGetAll($key);
		}
		else
		{
			if(is_array($fieldArray)&&!empty($fieldArray))
			{
				return self::$redisObj->hmget($key,$fieldArray);
			}
		}
		
	}


	/**
	 * [hashAlterNumber hash數字型別修改函式]
	 * @param  [string]  $key   [鍵名]
	 * @param  [string]  $field [修改欄位]
	 * @param  integer/float $range [修改範圍]
	 * @return [boolean]         [是否修改成功]
	 */
	public function hashAlterNumber($key,$field,$range=0)
	{
		//變更範圍不為0
		if(!$range)
		{
			self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
		}
		$valueArray=$this->hashGet($key,array($field));
		//是否有值
		if($valueArray[$field]===false)
		{
			self::error("function ".__FUNCTION__.":the field <b>".$field."</b> is no exist in hash key <b>".$key."</b>");
		}
		//值是不是數字型別
		self::numberCheck(abs($valueArray[$field]),__FUNCTION__);
		//統一使用hIncrByFloat
		$result=self::$redisObj->hIncrByFloat($key,$field,$range);
		return boolval($result);
	}


	/**
	 * [RemoveHashField 雜湊型別移除欄位函式]
	 * @param [string] $key   [鍵名]
	 * @param [string] $field [欄位名]
	 * @return [boolean] [是否移除成功]
	 */
	public function RemoveHashField($key,$field)
	{
		$valueArray=$this->hashGet($key,array($field));
		//是否有值
		if($valueArray[$field]===false)
		{
			self::error("function ".__FUNCTION__.":the field <b>".$field."</b> is no exist in hash key <b>".$key."</b>");
		}
		$result=self::$redisObj->hdel($key,$field);
		return boolval($result);
	}


	/**
	 * [push 列表新增函式]
	 * @param  [string] $key      [列表鍵名]
	 * @param  [string] $li       [元素值]
	 * @param  string $pushType [left/right]
	 * @return [boolean]           [是否新增成功]
	 */
	public function push($key,$li,$pushType='left')
	{
		self::stringCheck($key,__FUNCTION__);
		$action=$pushType=='right'?'rPush':'lPush';
		$result=self::$redisObj->$action($key,$li);
		return boolval($result);
	}	

	/**
	 * [getList 列表獲取函式]
	 * @param  [string]  $key   [鍵名]
	 * @param  integer $range [索引範圍]
	 * @return [array]         [結果陣列]
	 */
	public function getList($key,$range="0->-1",$isSerialize=false)
	{
		$rangeArray=$this->rangeToArray($range);
		if(!$isSerialize)
		{
			return self::$redisObj->lrange($key,$rangeArray[0],$rangeArray[1]);
		}
		else
		{
			$result = self::$redisObj->lrange($key,$rangeArray[0],$rangeArray[1]);
			foreach($result as $value)
			{
				$return[]=unserialize($value);
			}
			return $return;
		}
		
	} 	

	/**
	 * [pop 列表彈出函式]
	 * @param  [string] $key     [鍵名]
	 * @param  string $popType [彈出型別 right/left]
	 * @return [string]          [彈出元素]
	 */
	public function pop($key,$popType="right",$waitingTime=false)
	{
		self::stringCheck($key,__FUNCTION__);
		if($waitingTime===false)
		{
			$action=$popType=='left'?'lPop':'rPop';
			return self::$redisObj->$action($key);
		}
		else
		{
			$action=$popType=='left'?'blPop':'brPop';
			return self::$redisObj->$action($key,$waitingTime);
		}
	}
	/**
	 * [getListLen 列表長度獲取函式]
	 * @param  [string] $key [鍵名]
	 * @return [int]      [長度]
	 */
	public function getListLen($key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->llen($key);
	}


	/**
	 * [getEleByListIndex description]
	 * @param  [string] $key   [鍵名]
	 * @param  [int] $index [索引]
	 * @return [string]        [元素值]
	 */
	public function getEleByListIndex($key,$index)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->lindex($key,$index);
	}

	/**
	 * [setEleByListIndex 根據索引設定list元素值函式]
	 * @param [string] $key   [鍵名]
	 * @param [int] $index [索引]
	 * @param [boolean] $value [是否設定成功]
	 */
	public function setEleByListIndex($key,$index,$value)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->lSet($key,$index,$value);
	}


	/**
	 * [removeListEle 列表元素移除函式]
	 * @param  [string] $key       [鍵名]
	 * @param  [int] $removeNum [刪除個數 >0:從表頭開始N個  <0:從表尾開始N個 =0:所有 ]
	 * @param  [string] $eleValue  [刪除元素的值]
	 * @return [int]            [實際刪除的個數]
	 */
	public function removeListEle($key,$removeNum,$eleValue)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->lrem($key,$eleValue,$removeNum);
	}

	/**
	 * [cutList 連結串列裁剪函式]
	 * @param  [string]  $key   [鍵名]
	 * @param  [integer]  $start [擷取開始索引]
	 * @param  integer $end   [擷取結束索引,預設-1擷取到最後]
	 * @return [boolean]         [擷取是否成功]
	 */
	public function cutList($key,$start,$end=-1)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->ltrim($key,$start,$end);
	}


	/**
	 * [addSet 集合新增成員函式]
	 * @param [string] $key    [鍵名]
	 * @param [string] $member [成員]
	 */
	public function addSet($key,$member)
	{
		self::stringCheck($key,__FUNCTION__);
		$result = self::$redisObj->sAdd($key,$member);
		return boolval($result);
	}



	/**
	 * [getSet 獲取集合成員函式]
	 * @param  [string] $key [鍵名]
	 * @return [array]      [成員陣列]
	 */
	public function getSet($key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->sMembers($key);
	}



	/**
	 * [isInSet description]
	 * @param  [string]  $members [成員]
	 * @param  [string]  $key     [鍵名]
	 * @return boolean          [是否在集合中]
	 */
	public function isInSet($members,$key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->sismember($key,$members);
	}



	/**
	 * [countSet 集合成員數量統計函式]
	 * @param  [string] $key [鍵名]
	 * @return [integer]      [成員數量]
	 */
	public function countSet($key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->scard($key);
	}


	/**
	 * [randMemberFromSet description]
	 * @param  [string]  $key     [鍵名]
	 * @param  integer $numbers [數量]
	 * @return [array]           [成員陣列]
	 */
	public function randMemberFromSet($key,$numbers=1)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->sRandMember($key,$numbers);
	}


	/**
	 * [removeMemberFromSet 從集合中移除成員]
	 * @param  [string] $key    [鍵名]
	 * @param  [string] $member [成員]
	 * @return [boolean]         [是否移除成功]
	 */
	public function removeMemberFromSet($key,$member)
	{
		self::stringCheck($key,__FUNCTION__);
		$result = self::$redisObj->srem($key,$member);
		return boolval($result);
	}	


	/**
	 * [moveSetMemberTo 成員移動函式]
	 * @param  [string] $member    [成員]
	 * @param  [string] $sourceSet [源集合]
	 * @param  [string] $targetSet [目標集合]
	 * @return [boolean]            [是否成功]
	 */
	public function moveSetMemberTo($member,$sourceSet,$targetSet)
	{
		self::stringCheck($sourceSet,__FUNCTION__);
		self::stringCheck($targetSet,__FUNCTION__);
		$result = self::$redisObj->smove($sourceSet,$targetSet,$member);
		return boolval($result);
	}


	/**
	 * [getDiffFromSets 獲取多個集合的差集函式]
	 * @param  [string] $sets [多個集合]
	 * @return [array]       [差集陣列]
	 */
	public function getDiffFromSets(...$sets)
	{
		return self::$redisObj->sDiff(...$sets);
	}


	/**
	 * [getInterFromSets 獲取多個集合交集函式]
	 * @param  [string] $sets [多個集合]
	 * @return [array]       [交集陣列]
	 */
	public function getInterFromSets(...$sets)
	{
		return self::$redisObj->sInter(...$sets);
	}


	/**
	 * [getUnionFromSets 獲取多個集合並集函式]
	 * @param  [string] $sets [多個集合]
	 * @return [array]       [並集陣列]
	 */
	public function getUnionFromSets(...$sets)
	{
		return self::$redisObj->sUnion(...$sets);
	}


	/**
	 * [addZset 有序集合成員新增/更新 函式]
	 * @param [string] $key       [鍵名]
	 * @param [array] $zsetArray  [陣列  鍵值=>分數]
	 */
	public function addZset($key,$zsetArray)
	{
		self::stringCheck($key,__FUNCTION__);
		$scoreArr=[$key];
		foreach($zsetArray as $k => $v)
		{
			array_push($scoreArr,$v,$k);
		}	
		$result = self::$redisObj->zAdd(...$scoreArr);
		return boolval($result);
	}

	/**
	 * [getZsetByIndexRange 根據索引獲取有序集合函式  預設順序 /倒序]
	 * @param  [string]  $key     [鍵名]
	 * @param  integer $range     [索引範圍]
	 * @param  boolean $scoreShow [是否顯示分數]
	 * @param  string  $order     [排序規則 預設順序]
	 * @return [type]             [成員陣列]
	 */
	public function getZsetByIndexRange($key,$range="0->-1",$scoreShow=false,$order="ASC")
	{
		self::stringCheck($key,__FUNCTION__);
		$rangeArray=$this->rangeToArray($range);
		$action = $order === 'ASC'? 'zRange':'zReverseRange';
		return self::$redisObj->$action($key,$rangeArray[0],$rangeArray[1],$scoreShow);
	}



	/**
	 * [getZsetByScoreRange 根據分數獲取有序集合成員函式 ]
	 * @param  [type]  $key       [鍵名]
	 * @param  [type]  $start     [開始分數]
	 * @param  [type]  $end       [結束分數]
	 * @param  boolean $scoreShow [是否顯示分數]
	 * @param  string  $order     [結果排序]
	 * @return [type]             [成員陣列]
	 */
	public function getZsetByScoreRange($key,$range,$scoreShow=true,$order="ASC")
	{
		self::stringCheck($key,__FUNCTION__);
		$rangeArray=$this->rangeToArray($range);
		$scoreShow=$scoreShow?array("withscores"=>true) : array("withscores"=>false);
		if($order==='ASC')
		{
			return self::$redisObj->zRangeByScore($key,$rangeArray[0],$rangeArray[1],$scoreShow);
		}
		else
		{
			return self::$redisObj->zRevRangeByScore($key,$rangeArray[1],$rangeArray[0],$scoreShow);
		}
	}


	/**
	 * [countZset 獲取有序集合總成員數量]
	 * @param  [string] $key [鍵名]
	 * @return [integer]      [成員數量]
	 */
	public function countZset($key)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->zCard($key);
	}



	/**
	 * [countZsetByScoreRange 獲取分數區間內成員數量]
	 * @param  [string] $key   [鍵名]
	 * @param  [string] $range [分數範圍]
	 * @return [integer]        [成員數量]
	 */
	public function countZsetByScoreRange($key,$range)
	{
		self::stringCheck($key,__FUNCTION__);
		$rangeArray=$this->rangeToArray($range);
		return self::$redisObj->zCount($key,$rangeArray[0],$rangeArray[1]);
	}



	/**
	 * [getZsetScore 根據成員獲取分數值]
	 * @param  [string] $key   [鍵名]
	 * @param  [string] $member [鍵值]
	 * @return [float]        [分數]
	 */
	public function getZsetScore($key,$member)
	{
		self::stringCheck($key,__FUNCTION__);
		return self::$redisObj->zScore($key,$member);
	}


	/**
	 * [getZsetIndex 獲取成員排名(索引)]
	 * @param  [string] $key   [鍵名]
	 * @param  [string] $member [鍵值]
	 * @param  [string] $order [排序]
	 * @return [integer]       [排名(索引)]
	 */
	public function getZsetRank($key,$member,$order="ASC")
	{
		self::stringCheck($key,__FUNCTION__);
		$action = $order==='ASC'?'zRank':'zRevRank';
		return self::$redisObj->$action($key,$member);
	}


	/**
	 * [removeZsetMember 刪除zSet成員函式]
	 * @param  [type] $key     [description]
	 * @param  [type] $members [description]
	 * @return [type]          [description]
	 */
	public function removeZsetMembers($key,$members)
	{
		self::stringCheck($key,__FUNCTION__);
		self::arrayCheck($members,__FUNCTION__);
		$memberArray[]=$key;
		foreach($members as $k => $v)
		{
			array_push($memberArray,$v);
		}
		$result = self::$redisObj->zRem(...$memberArray);
		return boolval($result);
	}


	/**
	 * [removeZsetMembersByScore zSet根據分數範圍刪除成員]
	 * @param  [string] $key        [鍵名]
	 * @param  [string] $scoreRange [分數範圍]
	 * @return [boolean]             [是否刪除成功]
	 */
	public function removeZsetMembersByScore($key,$scoreRange)
	{
		self::stringCheck($key,__FUNCTION__);
		$rangeArray=$this->rangeToArray($scoreRange);
		$result = self::$redisObj->zRemRangeByScore($key,$rangeArray[0],$rangeArray[1]);
		return boolval($result);
	}

	/**
	 * [alterZsetScore zSet分數操作函式]
	 * @param  [string] $key   [鍵名]
	 * @param  [string] $value [成員]
	 * @param  [integer] $range [變更範圍]
	 * @return [float]        [修改後結果]
	 */
	public function alterZsetScore($key,$value,$range)
	{
		self::stringCheck($key,__FUNCTION__);
		if(!$range)
		{
			self::error("function ".__FUNCTION__.": the changed range can not be 0 ");
		}
		$score=$this->getZsetScore($key,$value);
		//是不是數字型別
		self::numberCheck(abs($value),__FUNCTION__);
		return self::$redisObj->zIncrBy($key,$range,$value);
	}
	

	/**
	 * [storeInterFromZsets 多個zSet求交集並存儲,儲存結果的score是交集每個成員score的和]
	 * 呼叫提示:
	 * $redis->storeInterFromZsets('myZsetAll',array('myZset1','myZset2'));
	 */
	
	public function storeInterFromZsets(...$zSets)
	{
		return self::$redisObj->zinterstore(...$zSets);
	}
	

	/**
	 * [storeUnionFromZsets 多個zSet求並集並存儲,儲存結果的score是並整合員score的和]
	 * @param  [type] $zSets [description]
	 * 呼叫提示
	 * $redis->storeUnionFromZsets('myZsetAll',array('myZset1','myZset2'));
	 */
	public function storeUnionFromZsets(...$zSets)
	{
		return self::$redisObj->zunionstore(...$zSets);
	}
}