以下是我准备做的一次完整的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
# 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
导入前必须检查格式,检查格式通过后即可以开始导入。
导入成功后即可以查看非常完善的集群信息。
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 完成队列的任务。