redis學(xué)習(xí)和部署記

以下是我準(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????有序集合的并集

有序集合的命令類型總結(jié)

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

cluster的分片信息

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

結(jié)點(diǎn)的信息

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ī)器都添加完成后,再進(jìn)行集群的導(dǎo)入。


加入集群

實(shí)例詳情的說明:?IP:PORT:Maxmemory

Maxmemory是最大內(nèi)存,計(jì)算方式是10M?*?1024?*?1024?

導(dǎo)入前必須檢查格式,檢查格式通過后即可以開始導(dǎo)入。

導(dǎo)入成功后即可以查看非常完善的集群信息。


Cachecloud的集群信息

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)確性。


命令統(tǒng)計(jì)信息
訂單信息
商品秒殺任務(wù)


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容