redis学习和部署记

以下是我准备做的一次完整的php+redis的体验清单。

1、redis基础命令

2、redis主从

3、redis哨兵

4、redis cluster集群

5、phpredis的安装和使用

6、cachecloud安装和使用

7、搭配phpredis完成一次消息队列任务


1     redis基础命令

keys *  获取所有或某通配符已存在的值 O(n)

dbsize 获取所有key的总数  O(1)

exists   查找键 O(1)

expire  设置过期时间 O(1)

ttl         查找 剩余过期时间 O(1)

persist  清除过期时间 O(1)

type      获取某个key的类型,string,hash,list,set,zset,none

flushall 清空所有的缓存和键值

1.1     字符串相关命令

set       设置值    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不存在时才设置    O(1)

set xx    判断key存在时才设置    O(1)

mget    批量获取key的值    O(n)

mset    批量设置key    O(n)

getset   对某个key设置一个新的值,并返回该key旧的值    O(1)

append 追加值到字符串后面    O(1)

strlen    获取该key的值的长度    O(1)

incrbyfloat    自增浮点数    O(1)

getrange start end 获取指定key的下标所有的值    O(1)

setrange offset  设置key某个下标的值    O(1)

1.2 hash的相关命令

hget key field   获取key对应的列的值    O(1)

hset key field value 设置key对应的列的值    O(1)

hsetnx key field value 当key不存在时设置该值    O(1)

hdel key field    删除key对应的列值    O(1)

hexists key field 查找key是否存在该列    O(1)

hlen key     查找key的列总数    O(1)

hmget  key fieldN..fieldM 批量获取key的多个列     O(n) 

hmset key fieldN nVal .. fieldM mVal 批量设置key的字段值    O(n) 

hincrby key field val 对key某一个字段做自增    O(1)

hvals key 获取该key所有的字段的值

hkeys key 获取该key所有的字段

hincrbyfloat key field val 对key某一个字段做浮点数的自增    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    从列表左侧弹出一个item    O(1)

rpop    从列表右侧弹出一个item    O(1)

lrem    从列表中删除所有相同value的项 O(n)

count>0,从左到右,删除最多count个相同value的项

count<0,从右到左,删除最多math.abs(count)个相同value的项

count=0,删除所有相同value的项

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    设置列表指定索引值为newVal    O(n)

blpop key timeout    lpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞

brpop key timeout    rpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞

某些场景适用的列表组合命令

1.4     集合

sadd key element    向集合key添加element    O(1)

srem  key element    向集合key删除element O(1)

scard key    计算集合中元素总数 O(1)

sismember key     判断元素是否该集合的元素    O(1)

srandmember key    随机取出聚合中所有的元素    O(1)

smembers key    取出集合中所有元素    O(n)

spop key 从集合中随机弹出一个元素    O(1)

scan -- 

sdiff key1 key2    取两个集合的差集

sinter key1 key2    取两个集合的交集

sunion key1 key2    取两个集合的并集

适用场景

1.5    有序集合

zadd key score element    添加集合,分数可以重复    O(logN)

zrem key element    删除元素    O(1)

zscore key element    获取元素的分数    O(1)

zincrby key element    增加或减少元素的分数    O(1)

zcard key    获取集合元素的总数    O(1)

zrank key element    获取元素在集合中的排名    O(1)

zrange key start end (withscores)    获取集合中某个范围的元素    O(log(n)+m)

zrangebyscore key minScore maxScore 获取指定分数范围内的元素    O(log(n)+m)

zcount key minScore maxScore 获取集合内指定分数范围内的元素个数    O(log(n)+m)

zremrangebyrank key start end 删除指定排名内的升序元素    O(log(n)+m)

zremrangebyscore key minScore maxScore 删除指定分数内的升序元素    O(log(n)+m)

zrevrank    获取元素在集合中从高到低的排名    O(1)

zrevrange    获取集合中某个范围的排名从高到低元素    O(log(n)+m)

zrevrangebyscore    获取指定分数范围内分数从高到低的元素    O(log(n)+m)

zinterstore    有序集合的交集

zunionstore    有序集合的并集

有序集合的命令类型总结

1.6     订阅发布

publish channel message 针对某个频道发布消息

subscribe [channel] 订阅一个或多个频道的消息

unsubscribe [channel] 取消订单一个或多个频道

psubscribe [pattern] 按匹配的模式来订阅

punsubscribe [pattern] 退订指定模式

pubsub channels 列出至少有一个订阅者的频道

pubsub numsub [channel] 列出给定频道的订阅者数量

1.7     hyperloglog(错误率0.81%) 用户统计独立用户

pfadd key element    向hyperloglog添加元素

pfcount key    计算hyperloglog的独立总数

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 如需要可以设置验证密码

2.3 查看分片状态

> info replication


3    redis哨兵

3.1 配置

对master的redis配置sentinel:

sentinel monitor mymaster 192.168.0.1 7000 2

sentinel down-after-milliseconds mymaster 30000  认为断线所需时间

sentinel parallel-syncs mymaster 1    最多可以有多少个从服务器同时对新的主服务器进行同步

sentinel failover-timeout mymaster 180000 

启动

> redis-sentinel redis.conf

启动成功后,sentinel会自动识别该master有多少个slave。

故障转移流程

a 从slave节点中选出一个合适的节点作为新的master节点

a.a 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续

a.b 选择复制偏移量最大的slave节点(复制的最完整),如果存在则返回,不存在则继续。

a.c 选择runId最小的slave节点。

b.对上面的slave节点执行slaveof on one命令让其成为master节点

c.向剩余的slave节点发送命令,让它们成为新的master节点的slave节点,复制规则和parallel-syncs参数有关。

d.更新对原来master节点配置为slave,并保持着对其关注,当其恢复后命令它去复制新的master节点。


4    redis cluster集群

4.1    原生安装和配置

cluster-enables yes   #是否开启cluster

cluster-node-timeout 15000    #故障转移的时间

cluster-config-file nodes-cluster.conf

cluster-require-full-coverage yes    表示所有节点都可以提供服务才可以使用redis

cluster meet 命令,由主节点对各子节点进行感知

cluster addslots 命令,设置槽

cluster replicate 命令,设置主从

握手

# 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}

设置主从

# redis-cli -h 192.168.1.100 -p 6379 cluster replicate ${node-id-7000}

4.2 redis-trib命令安装

创建集群

# redis-trib.rb  create  --replicas 1 host:port... hostN:portN

自动创建槽

# 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

下线节点

# 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

结点的信息

5    phpredis的安装和使用

# git clone https://github.com/phpredis/phpredis.git

# cd phpredis

# phpsize

# ./configure 

# make

# make install

redis实例的简单应用,代码片段如下:

$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扩展这里是如何实现的。

5.1 sentinel

首先是sentinel的使用,phpredis并没有sentinel专门的封装函数,不过支持redis原生命令的使用,只要简单的几个原生命令即可以获取到sentinel提供的主从地址。

以下是代码片段

function index() {

        $redis = new Redis();

        //填入sentinel配置好的主机名称

        $master_name = '';

        //连接sentinel服务

        $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名称以及对应的信息

        $result_master = $this->parseArrayResult($redis->rawCommand('SENTINEL', 'master', $master_name));

        //获取从库redis名称以及对应从库列表信息

        $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的应用

https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#readme

首先将cluster启动起来, 以下是我的集群信息。

集群分片信息

以下是代码片段:

    // 第一个参数是结点名称,需要配置redis.ini

    // 第二个参数是结点IP和端口的数组

    // 第三个参数是连接超时时间

    // 第四个参数是读超时时间

    // 第五个参数是指是否连接所有的结点

    $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);

    //这里输出集群中所有的主服务器

    foreach ($obj_cluster->_masters() as $arr_master) {

        print_r($arr_master);

    }

运行后直接就可以设置和获取值了, 集群做好封装,连分片都不可见。可以当成一个redis做开发。

默认情况 下,RedisCluster只会向主节点发送命令,如果需要,可以对只读命令进行不同的配置。

默认配置如下:

$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_NONE);

始终随机地将只读命令分发给从站

$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);

断线的测试。

1、干掉一个从结点7006,再获取数据发现正常读取数据,同时设置数据也没有问题。

2、干掉一个主结点7000,发现获取数据和写数据都没有问题,而且集群还保持着三台主结点。

3、干掉包含两个主结点和一个从结束的服务器,这个时间可用的结点仅剩下两个,分别为主从结点,但是集群已经停止,无法再进行数据读写,可能是因为剩下两个结点,已经无法满足原来三个主结点的集群。

4、尝试恢复服务器的启动,并且启动服务器所有的redis集群服务。发现在启动服务后,集群很快就恢复了使用。

5、当所有结点都可以使用的时候,干掉包含一个主结点和两个从结点的服务器时,发现集群正常使用,且另一个服务器的所有结点都为主结点。

结论:

1、集群主结点必须是3个, 如果断线后,剩余的结点不足3个或达不到创建结点时部署的主结点个数,则集群就挂掉了。 

2、如果挂掉的服务器所包含的主结点超过主结点的半数(包括半数),则集群无法正常使用。

6、cachecloud安装和使用

下载cachecloud包

# git clone https://github.com/sohutv/cachecloud.git

安装cachecloud之前需要安装maven,我直接通过yum来安装

# yum search maven

# yum install maven -y

安装成功后需要编辑cachecloud的数据库配置文件

# vim cachecloud-open-web/src/main/swap/local.properties

编辑的内容包括:

cachecloud.db.url = jdbc:mysql://127.0.0.1:3306/cachecloud

cachecloud.db.user = root

cachecloud.db.password = 123456

如果数据库配置跟本地不一致,则安装会失败。

接下来在cachecloud根目录下运行

# mvn clean compile install -Plocal

在cachecloud-open-web模块下运行

# mvn spring-boot:run

安装和启动一般都会成功,我遇到的问题则是数据库配置失败的问题。

启动后访问: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的流程。

进入cachecloud后,首先在后台管理中把我的redis机器加入。


加入机器

两台机器都添加完成后,再进行集群的导入。


加入集群

实例详情的说明: IP:PORT:Maxmemory

Maxmemory是最大内存,计算方式是10M * 1024 * 1024 

导入前必须检查格式,检查格式通过后即可以开始导入。

导入成功后即可以查看非常完善的集群信息。


Cachecloud的集群信息

7 搭配phpredis完成一次消息队列任务

消息队列典型的应用是完成秒杀任务。下面示例会通过高并发完成秒杀订单的操作,然后通过crontab来完成订单入库操作。

首先模拟一下可以完成高并发下订单的程序,下面是代码片段。每件秒杀商品仅仅允许10个订单,达到10个订单后不能再往该商品队列中加入新的元素。

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做定时任务,按每分钟读取缓存订单数据到真正的订单表中。

# 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);

//始终随机地将只读命令分发给从站

$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);

//列出参与秒杀而且没有结束的商品

$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('创建订单成功');

            }else{

                logger('创建订单失败,失败返回:'.$query);

                return false;

            }

        }

    }

    if($obj_cluster->llen('order_list_'.$goods['goods_id']) == 0){  //队列读取完成后标识为该商品已完成秒杀任务

        mysql_query('update goods set is_done = 1 where goods_id = '.$goods['goods_id'], $mysql);

    }

}

function submit_order($string){

    //这里执行订单提交程序

    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的统计信息,以及商品秒杀工作的准确性。


命令统计信息
订单信息
商品秒杀任务


至此,redis学习和部署记学习完毕,后面会接着学习rabbitMQ 完成队列的任务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容