以下是我準(zhǔn)備做的一次完整的php+redis的體驗(yàn)清單。
1、redis基礎(chǔ)命令
2、redis主從
3、redis哨兵
4、redis?cluster集群
5、phpredis的安裝和使用
6、cachecloud安裝和使用
7、搭配phpredis完成一次消息隊(duì)列任務(wù)
1?????redis基礎(chǔ)命令
keys?*??獲取所有或某通配符已存在的值?O(n)
dbsize?獲取所有key的總數(shù)??O(1)
exists???查找鍵?O(1)
expire??設(shè)置過期時間?O(1)
ttl?????????查找?剩余過期時間?O(1)
persist??清除過期時間?O(1)
type??????獲取某個key的類型,string,hash,list,set,zset,none
flushall?清空所有的緩存和鍵值
1.1?????字符串相關(guān)命令
set???????設(shè)置值????O(1)
get???????獲取值????O(1)
del???????刪除鍵,可批量操作????O(1)
incr??????自增1????O(1)
decr?????自減1????O(1)
incrby??num??增加num???如??incrby?test?num??=??test+num????O(1)
decrby?num?減少num????O(1)
setnx????判斷key不存在時才設(shè)置????O(1)
set?xx????判斷key存在時才設(shè)置????O(1)
mget????批量獲取key的值????O(n)
mset????批量設(shè)置key????O(n)
getset???對某個key設(shè)置一個新的值,并返回該key舊的值????O(1)
append?追加值到字符串后面????O(1)
strlen????獲取該key的值的長度????O(1)
incrbyfloat????自增浮點(diǎn)數(shù)????O(1)
getrange?start?end?獲取指定key的下標(biāo)所有的值????O(1)
setrange?offset??設(shè)置key某個下標(biāo)的值????O(1)
1.2?hash的相關(guān)命令
hget?key?field???獲取key對應(yīng)的列的值????O(1)
hset?key?field?value?設(shè)置key對應(yīng)的列的值????O(1)
hsetnx?key?field?value?當(dāng)key不存在時設(shè)置該值????O(1)
hdel?key?field????刪除key對應(yīng)的列值????O(1)
hexists?key?field?查找key是否存在該列????O(1)
hlen?key?????查找key的列總數(shù)????O(1)
hmget??key?fieldN..fieldM?批量獲取key的多個列?????O(n)?
hmset?key?fieldN?nVal?..?fieldM?mVal?批量設(shè)置key的字段值????O(n)?
hincrby?key?field?val?對key某一個字段做自增????O(1)
hvals?key?獲取該key所有的字段的值
hkeys?key?獲取該key所有的字段
hincrbyfloat?key?field?val?對key某一個字段做浮點(diǎn)數(shù)的自增????O(1)
1.3?????列表
rpush????從列表右端插入值(1-N個)????O(1~n)
lpush????從列表左端插入值(1-N個)????O(1~n)
linsert?key?before|after?val?newVal?在列表指定的值前|后插入newVal????O(n)
lpop????從列表左側(cè)彈出一個item????O(1)
rpop????從列表右側(cè)彈出一個item????O(1)
lrem????從列表中刪除所有相同value的項(xiàng)?O(n)
count>0,從左到右,刪除最多count個相同value的項(xiàng)
count<0,從右到左,刪除最多math.abs(count)個相同value的項(xiàng)
count=0,刪除所有相同value的項(xiàng)
ltrim?key?start?end???按照索引范圍修剪列表????O(n)
lrange?key?start?end????獲取列表指定索引范圍所有item?O(n)
lindex?key?index????獲取列表指定索引item?O(n)
llen?key????獲取列表長度????O(1)????
lset?key?index?newVal????設(shè)置列表指定索引值為newVal????O(n)
blpop?key?timeout????lpop阻塞版本,timeout是阻塞超時時間,timeout=0為永遠(yuǎn)不阻塞
brpop?key?timeout????rpop阻塞版本,timeout是阻塞超時時間,timeout=0為永遠(yuǎn)不阻塞

1.4?????集合
sadd?key?element????向集合key添加element????O(1)
srem??key?element????向集合key刪除element?O(1)
scard?key????計(jì)算集合中元素總數(shù)?O(1)
sismember?key?????判斷元素是否該集合的元素????O(1)
srandmember?key????隨機(jī)取出聚合中所有的元素????O(1)
smembers?key????取出集合中所有元素????O(n)
spop?key?從集合中隨機(jī)彈出一個元素????O(1)
scan?--?
sdiff?key1?key2????取兩個集合的差集
sinter?key1?key2????取兩個集合的交集
sunion?key1?key2????取兩個集合的并集

1.5????有序集合
zadd?key?score?element????添加集合,分?jǐn)?shù)可以重復(fù)????O(logN)
zrem?key?element????刪除元素????O(1)
zscore?key?element????獲取元素的分?jǐn)?shù)????O(1)
zincrby?key?element????增加或減少元素的分?jǐn)?shù)????O(1)
zcard?key????獲取集合元素的總數(shù)????O(1)
zrank?key?element????獲取元素在集合中的排名????O(1)
zrange?key?start?end?(withscores)????獲取集合中某個范圍的元素????O(log(n)+m)
zrangebyscore?key?minScore?maxScore?獲取指定分?jǐn)?shù)范圍內(nèi)的元素????O(log(n)+m)
zcount?key?minScore?maxScore?獲取集合內(nèi)指定分?jǐn)?shù)范圍內(nèi)的元素個數(shù)????O(log(n)+m)
zremrangebyrank?key?start?end?刪除指定排名內(nèi)的升序元素????O(log(n)+m)
zremrangebyscore?key?minScore?maxScore?刪除指定分?jǐn)?shù)內(nèi)的升序元素????O(log(n)+m)
zrevrank????獲取元素在集合中從高到低的排名????O(1)
zrevrange????獲取集合中某個范圍的排名從高到低元素????O(log(n)+m)
zrevrangebyscore????獲取指定分?jǐn)?shù)范圍內(nèi)分?jǐn)?shù)從高到低的元素????O(log(n)+m)
zinterstore????有序集合的交集
zunionstore????有序集合的并集

1.6?????訂閱發(fā)布
publish?channel?message?針對某個頻道發(fā)布消息
subscribe?[channel]?訂閱一個或多個頻道的消息
unsubscribe?[channel]?取消訂單一個或多個頻道
psubscribe?[pattern]?按匹配的模式來訂閱
punsubscribe?[pattern]?退訂指定模式
pubsub?channels?列出至少有一個訂閱者的頻道
pubsub?numsub?[channel]?列出給定頻道的訂閱者數(shù)量
1.7?????hyperloglog(錯誤率0.81%)?用戶統(tǒng)計(jì)獨(dú)立用戶
pfadd?key?element????向hyperloglog添加元素
pfcount?key????計(jì)算hyperloglog的獨(dú)立總數(shù)
pfmerge?destkey?sourcekey????合并多個hyperloglog
1.8?????GEO地理位置
geoadd?key?longitude?latitude?member????添加地理位置信息
geopos?key?member????獲取地理位置信息
geodist?key?member1?member2?[unit]????獲取兩個地理位置的距離
georadius?key?longitude?latitude?radiusm|km|ft|mi?獲取指定位置的地理位置信息集合
georadiusbymember?key?member?獲取指定位置的地理位置信息集合
2?redis主從
2.1?slaveof命令
>?slaveof?192.168.0.111?6379
>?slaveof?no?one
2.2?修改配置
slaveof?ip?port
slave-read-only?yes
masterauth 如需要可以設(shè)置驗(yàn)證密碼
2.3?查看分片狀態(tài)
>?info?replication
3????redis哨兵
3.1?配置
對master的redis配置sentinel:
sentinel?monitor?mymaster?192.168.0.1?7000?2
sentinel?down-after-milliseconds?mymaster?30000??認(rèn)為斷線所需時間
sentinel?parallel-syncs?mymaster?1????最多可以有多少個從服務(wù)器同時對新的主服務(wù)器進(jìn)行同步
sentinel?failover-timeout?mymaster?180000?
啟動
> redis-sentinel?redis.conf
啟動成功后,sentinel會自動識別該master有多少個slave。
故障轉(zhuǎn)移流程
a 從slave節(jié)點(diǎn)中選出一個合適的節(jié)點(diǎn)作為新的master節(jié)點(diǎn)
a.a?選擇slave-priority(slave節(jié)點(diǎn)優(yōu)先級)最高的slave節(jié)點(diǎn),如果存在則返回,不存在則繼續(xù)
a.b?選擇復(fù)制偏移量最大的slave節(jié)點(diǎn)(復(fù)制的最完整),如果存在則返回,不存在則繼續(xù)。
a.c?選擇runId最小的slave節(jié)點(diǎn)。
b.對上面的slave節(jié)點(diǎn)執(zhí)行slaveof?on?one命令讓其成為master節(jié)點(diǎn)
c.向剩余的slave節(jié)點(diǎn)發(fā)送命令,讓它們成為新的master節(jié)點(diǎn)的slave節(jié)點(diǎn),復(fù)制規(guī)則和parallel-syncs參數(shù)有關(guān)。
d.更新對原來master節(jié)點(diǎn)配置為slave,并保持著對其關(guān)注,當(dāng)其恢復(fù)后命令它去復(fù)制新的master節(jié)點(diǎn)。
4????redis?cluster集群
4.1????原生安裝和配置
cluster-enables?yes???#是否開啟cluster
cluster-node-timeout?15000????#故障轉(zhuǎn)移的時間
cluster-config-file?nodes-cluster.conf
cluster-require-full-coverage?yes????表示所有節(jié)點(diǎn)都可以提供服務(wù)才可以使用redis
cluster?meet?命令,由主節(jié)點(diǎn)對各子節(jié)點(diǎn)進(jìn)行感知
cluster?addslots?命令,設(shè)置槽
cluster?replicate?命令,設(shè)置主從
握手
# redis-cli?-h?192.168.1.100?-p?6379?cluster?meet?192.168.1.101?6379
分配槽
# redis-cli?-h?192.168.1.100?-p?6379?cluster?addslots?{0..16383}
設(shè)置主從
# redis-cli?-h?192.168.1.100?-p?6379?cluster?replicate?${node-id-7000}
4.2?redis-trib命令安裝
創(chuàng)建集群
# redis-trib.rb? create ?--replicas?1 host:port...?hostN:portN
自動創(chuàng)建槽
# redis-trib.rb?reshard?host:port
加入集群
# redis-trib.rb?add-node?new_host:new_port?existing_host:existing_port?--slave?--master-id?
例:# redis-trib.rb?add-node?192.168.1.100:6380?192.168.1.100:6379
自動遷移槽
# redis-trib.rb?reshard?192.168.1.100?6379
主動遷移槽
# redis-trib.rb?reshard?--from?${node-id}?--to?${node-id}?--slots?1366?192.168.1.100:6379
下線節(jié)點(diǎn)
# redis-trib.rb?del-node?192.168.1.100:6379?${node-id}?
查看cluster的分片信息
# redis-trib.rb?check?192.168.1.100:6379

# redis-trib.rb?info?192.168.1.100:6379

5????phpredis的安裝和使用
# git?clone?https://github.com/phpredis/phpredis.git
# cd?phpredis
# phpsize
# ./configure?
# make
# make install
redis實(shí)例的簡單應(yīng)用,代碼片段如下:
$redis?=?new?Redis();
$con?=?$redis->connect('127.0.0.1',?6379,?2.5);
print_r($redis->info());
$redis->set('php','hello');
var_dump($redis->dbsize());
print_r($redis->get('php'));
這里直接跑一下sentinel和cluster,看看可高用在reids擴(kuò)展這里是如何實(shí)現(xiàn)的。
5.1?sentinel
首先是sentinel的使用,phpredis并沒有sentinel專門的封裝函數(shù),不過支持redis原生命令的使用,只要簡單的幾個原生命令即可以獲取到sentinel提供的主從地址。
以下是代碼片段
function?index()?{
????????$redis?=?new?Redis();
????????//填入sentinel配置好的主機(jī)名稱
????????$master_name?=?'';
????????//連接sentinel服務(wù)
????????$redis->connect('192.168.1.182',?'26379');?
????????//獲取sentinel信息
????????$result?=?$this->parseArrayResult($redis->rawCommand('SENTINEL',?'masters'));
????????//通過result得到master-name?
????????if($result){
????????????$master_name?=?current($result)['name'];
????????}
????????//獲取主庫redis名稱以及對應(yīng)的信息
????????$result_master?=?$this->parseArrayResult($redis->rawCommand('SENTINEL',?'master',?$master_name));
????????//獲取從庫redis名稱以及對應(yīng)從庫列表信息
????????$result_slave?=?$this->parseArrayResult($redis->rawCommand('SENTINEL',?'slaves',?$master_name));
????????//獲取主庫的地址和端口號
????????$result_addr?=?$this->parseArrayResult($redis->rawCommand('SENTINEL',?'get-master-addr-by-name',?$master_name));
????????print_r($result_master);print_r($result_slave);print_r($result_addr);exit();
}
獲取到主從地址后,即可以通過工廠或單例地模式高可用的使用redis。
sentinel代碼引用的原文鏈接?https://segmentfault.com/a/1190000011185598
對于sentinel原生的操作的文章?https://redis.io/topics/sentinel
5.2 cluster
這是cluster的應(yīng)用
https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#readme
首先將cluster啟動起來,?以下是我的集群信息。

以下是代碼片段:
????// 第一個參數(shù)是結(jié)點(diǎn)名稱,需要配置redis.ini
????// 第二個參數(shù)是結(jié)點(diǎn)IP和端口的數(shù)組
????// 第三個參數(shù)是連接超時時間
????// 第四個參數(shù)是讀超時時間
????// 第五個參數(shù)是指是否連接所有的結(jié)點(diǎn)
????$obj_cluster?=?new?RedisCluster(NULL,?Array('192.168.1.220:7000',?'192.168.1.220:7005',?'192.168.1.182:7004'),1.5,?1.5,true);
????$query?=?$obj_cluster->set("php",?"This?is?a?test.");
????var_dump($query);
????$result?=?$obj_cluster->get("php");
????var_dump($result);
????//這里輸出集群中所有的主服務(wù)器
????foreach?($obj_cluster->_masters()?as?$arr_master)?{
? ? ????print_r($arr_master);
????}
運(yùn)行后直接就可以設(shè)置和獲取值了,?集群做好封裝,連分片都不可見??梢援?dāng)成一個redis做開發(fā)。
默認(rèn)情況?下,RedisCluster只會向主節(jié)點(diǎn)發(fā)送命令,如果需要,可以對只讀命令進(jìn)行不同的配置。
默認(rèn)配置如下:
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER,?RedisCluster::FAILOVER_NONE);
始終隨機(jī)地將只讀命令分發(fā)給從站
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);
斷線的測試。
1、干掉一個從結(jié)點(diǎn)7006,再獲取數(shù)據(jù)發(fā)現(xiàn)正常讀取數(shù)據(jù),同時設(shè)置數(shù)據(jù)也沒有問題。
2、干掉一個主結(jié)點(diǎn)7000,發(fā)現(xiàn)獲取數(shù)據(jù)和寫數(shù)據(jù)都沒有問題,而且集群還保持著三臺主結(jié)點(diǎn)。
3、干掉包含兩個主結(jié)點(diǎn)和一個從結(jié)束的服務(wù)器,這個時間可用的結(jié)點(diǎn)僅剩下兩個,分別為主從結(jié)點(diǎn),但是集群已經(jīng)停止,無法再進(jìn)行數(shù)據(jù)讀寫,可能是因?yàn)槭O聝蓚€結(jié)點(diǎn),已經(jīng)無法滿足原來三個主結(jié)點(diǎn)的集群。
4、嘗試恢復(fù)服務(wù)器的啟動,并且啟動服務(wù)器所有的redis集群服務(wù)。發(fā)現(xiàn)在啟動服務(wù)后,集群很快就恢復(fù)了使用。
5、當(dāng)所有結(jié)點(diǎn)都可以使用的時候,干掉包含一個主結(jié)點(diǎn)和兩個從結(jié)點(diǎn)的服務(wù)器時,發(fā)現(xiàn)集群正常使用,且另一個服務(wù)器的所有結(jié)點(diǎn)都為主結(jié)點(diǎn)。
結(jié)論:
1、集群主結(jié)點(diǎn)必須是3個,?如果斷線后,剩余的結(jié)點(diǎn)不足3個或達(dá)不到創(chuàng)建結(jié)點(diǎn)時部署的主結(jié)點(diǎn)個數(shù),則集群就掛掉了。?
2、如果掛掉的服務(wù)器所包含的主結(jié)點(diǎn)超過主結(jié)點(diǎn)的半數(shù)(包括半數(shù)),則集群無法正常使用。
6、cachecloud安裝和使用
下載cachecloud包
# git?clone?https://github.com/sohutv/cachecloud.git
安裝cachecloud之前需要安裝maven,我直接通過yum來安裝
# yum?search?maven
# yum?install?maven?-y
安裝成功后需要編輯cachecloud的數(shù)據(jù)庫配置文件
# vim?cachecloud-open-web/src/main/swap/local.properties
編輯的內(nèi)容包括:
cachecloud.db.url?=?jdbc:mysql://127.0.0.1:3306/cachecloud
cachecloud.db.user?=?root
cachecloud.db.password?=?123456
如果數(shù)據(jù)庫配置跟本地不一致,則安裝會失敗。
接下來在cachecloud根目錄下運(yùn)行
# mvn?clean?compile?install?-Plocal
在cachecloud-open-web模塊下運(yùn)行
# mvn?spring-boot:run
安裝和啟動一般都會成功,我遇到的問題則是數(shù)據(jù)庫配置失敗的問題。
啟動后訪問:http://127.0.0.1:9999????賬號密碼都是admin。
cachecloud提供的資料非常豐富,包括
文檔:https://github.com/sohutv/cachecloud/wiki/3.%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%8E%A5%E5%85%A5%E6%96%87%E6%A1%A3
視頻:http://pan.baidu.com/s/1c2mET5e
我這里記錄一下我接入redis-cluster的流程。
進(jìn)入cachecloud后,首先在后臺管理中把我的redis機(jī)器加入。

兩臺機(jī)器都添加完成后,再進(jìn)行集群的導(dǎo)入。

實(shí)例詳情的說明:?IP:PORT:Maxmemory
Maxmemory是最大內(nèi)存,計(jì)算方式是10M?*?1024?*?1024?
導(dǎo)入前必須檢查格式,檢查格式通過后即可以開始導(dǎo)入。
導(dǎo)入成功后即可以查看非常完善的集群信息。

7 搭配phpredis完成一次消息隊(duì)列任務(wù)
消息隊(duì)列典型的應(yīng)用是完成秒殺任務(wù)。下面示例會通過高并發(fā)完成秒殺訂單的操作,然后通過crontab來完成訂單入庫操作。
首先模擬一下可以完成高并發(fā)下訂單的程序,下面是代碼片段。每件秒殺商品僅僅允許10個訂單,達(dá)到10個訂單后不能再往該商品隊(duì)列中加入新的元素。
public?function?submitorder(){
????????$goods_id?=?$_GET['goods_id'];
????????$user_id?=?rand(0,9999);
????????$add_time?=?date('Y-m-d?H:i:s');
????????$obj_cluster?=?new?RedisCluster(NULL,?Array('192.168.1.182:7002',?'192.168.1.220:7000'),?1.5,?1.5,?true);
????????$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER,?RedisCluster::FAILOVER_ERROR);
????????$result?=?$this->setvalues($obj_cluster,?$goods_id,?$user_id,?$add_time);
????????var_dump($result);
}
private?function?setvalues($obj_cluster,?$goods_id,?$user_id,?$add_time){
????????$cont?=?0;
????????$result?=?false;
????????$arry?=?array(
????????????'goods_id'=>$goods_id,
????????????'user_id'?=>$user_id,
????????????'add_time'=>$add_time,
????????);
????????if($obj_cluster->llen('order_list_'.$goods_id)?<?10){
????????????$result?=?$obj_cluster->lpush('order_list_'.$goods_id,json_encode($arry));
????????}
????????unset($arry);
????????return?$result;
}
接下來完成入庫程序,入庫的程序我會使用crontab做定時任務(wù),按每分鐘讀取緩存訂單數(shù)據(jù)到真正的訂單表中。
# crontab?-??> */1?*?*?*?*?php?auto_submit_order.php
下面是自動下單的程序。
$config_ecm?=?include?ROOT_PATH.'/data/config.inc.php';
$mysql_config_str?=?$config_ecm['DB_CONFIG'];
$mysql_config?=?parse_url($mysql_config_str);
$dbname?=?substr($mysql_config['path'],1);
$mysql_config['pass']?=?urldecode($mysql_config['pass']);
$mysql?=?mysql_connect($mysql_config['host'],$mysql_config['user'],$mysql_config['pass']);
mysql_set_charset('utf8');
mysql_select_db($dbname,$mysql);
$obj_cluster?=?new?RedisCluster(NULL,?Array('192.168.1.182:7002',?'192.168.1.220:7000'),?1.5,?1.5,?true);
//始終隨機(jī)地將只讀命令分發(fā)給從站
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER,?RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);
//列出參與秒殺而且沒有結(jié)束的商品
$subquery?=??mysql_query('select?*?from?goods?where?miaosha_flag?=?1?and?is_done?=?0',$mysql);
while?($goods?=?mysql_fetch_array($subquery,MYSQL_ASSOC))
{
????$len?=?$obj_cluster->llen('order_list_'.$goods['goods_id']);
????for($i=0;?$i<=$len;?$i++){
????????$info?=?$obj_cluster->rpop('order_list_'.$goods['goods_id']);
????????if($info){
????????????$query?=?submit_order($info);
????????????if($query){
????????????????logger('創(chuàng)建訂單成功');
????????????}else{
????????????????logger('創(chuàng)建訂單失敗,失敗返回:'.$query);
????????????????return?false;
????????????}
????????}
????}
????if($obj_cluster->llen('order_list_'.$goods['goods_id'])?==?0){??//隊(duì)列讀取完成后標(biāo)識為該商品已完成秒殺任務(wù)
????????mysql_query('update?goods?set?is_done?=?1?where?goods_id?=?'.$goods['goods_id'],?$mysql);
????}
}
function?submit_order($string){
????//這里執(zhí)行訂單提交程序
????file_put_contents(ROOT_PATH?.?'/data/file_logs/order.log',?date("Y-m-d?H:i:s",?time()).'-----------'.$string."\n\n",FILE_APPEND);????????
????return?true;
}
function?logger($content){
????file_put_contents(ROOT_PATH?.?'/data/file_logs/order_error.log',?date("Y-m-d?H:i:s",?time()).'-----------'.$content."\n\n",FILE_APPEND);????????
}
在多次的秒殺程序中可以看到cachecloud的統(tǒng)計(jì)信息,以及商品秒殺工作的準(zhǔn)確性。



至此,redis學(xué)習(xí)和部署記學(xué)習(xí)完畢,后面會接著學(xué)習(xí)rabbitMQ?完成隊(duì)列的任務(wù)。