Redis 优化要点

http://www.imooc.com/article/3645
http://www.cnblogs.com/mushroom/p/4738170.html
http://linusp.github.io/2015/12/16/redis-performance-analysis.html
https://segmentfault.com/a/1190000002906345

#info 分析

## Memory
>实际缓存占用的内存和Redis自身运行所占用的内存(如元数据、lua)。
>它是由Redis使用内存分配器分配的内存,所以这个数据并没有把内存碎片浪费掉的内存给统计进去
>如果used_memory > 可用最大内存,那么操作系统开始进行内存与swap空间交换
>当 rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
>内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
>当 used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟

>used_memory:9892187056
>used_memory_human:9.21G

>从操作系统上显示已经分配的内存总量, 包括碎片
>used_memory_rss:11148713984
>used_memory_peak:11236792296
>used_memory_peak_human:10.47G
>used_memory_lua:35840

## 内存碎片率
>内存碎片率稍大于1是合理的,这个值表示内存碎片率比较低,也说明redis没有发生内存交换。
>但如果内存碎片率超过1.5,那就说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率
>若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。内存交换会引起非常明显的响应延迟
>mem_fragmentation_ratio:1.13
>mem_allocator:jemalloc-3.6.0

## stats
>total_commands_processed:105868 #总共处理的命令数
>instantaneous_ops_per_sec:0
>rejected_connections:0
>evicted_keys:0 #因为maxmemory限制导致key被回收删除的数量

# 性能分析

## 延迟

>redis-cli -h 127.0.0.1 -p 6379 --latency
>持续采样,结果单位是ms;
>redis-cli -h 127.0.0.1 -p 6379 -–latency-history
>间隔采样,结果单位是ms;

## bigkeys

>redis-cli -h 127.0.0.1 -p 6379 --bigkeys
>持续采样,实时输出当时得到的 value 占用空间最大的 key 值

## 慢日志

>默认配置
>slowlog-log-slower-than 10000
>slowlog-max-len 128

>获取慢日志
>slowlog get 3

## 统计

>redis-cli -h 127.0.0.1 -p 6379 info commandstats
>查看所有命令统计的快照,执行次数,所耗费的毫秒数,总时间和平均时间

## 调试

>redis-cli -h 127.0.0.1 -p 6379 monitor

## 内存占用分析

redis-rdb-tools
>pip install rdbtools
>rdb -c memory /var/redis/6379/dump.rdb > memory.csv

# 优化与禁忌

停止使用 KEYS *,如果避免不了,请使用scan命令
精简键名和键值,减小key长度,压缩value
设置 key 值的过期时间,避免长时间占用内存,缓解同步和持久化的压力
选择合适的回收策略,如果不能丢数据则建议使用 volatile-lru 策略,如果key可以自动重建则推荐allkeys-lru
业务层要考虑读写分离和主从模式
合理分配snapshot,aof,主上关闭aof和snapshot,在主从上开启snapshot和aof
如果数据不需要持久,可关闭所有的持久化方式可以获得最佳的性能以及最大的内存使用量
合理选择最优的数据结构解决实际问题,那样既可以提高效率又可以节省内存
合理使用长连接
maxmemory=8g, 不要让你的Redis所在机器物理内存使用超过实际内存总量的3/5
sysctl vm.overcommit_memory=1
关闭vm-enabled,3.0以后默认废弃

解决保存快照失败后redis无法写入的问题
>config set stop-writes-on-bgsave-error no

定期日志重写,减小aof重载时的时间开销
>auto-aof-rewrite-percentage 100
>auto-aof-rewrite-min-size 64mb

# 安全
使用普通用户启动服务,且禁止该用户登录
在可以保证内网安全的情况下,无密码性能最好
客户端可能会发送config命令,会有安全问题,建议禁用
客户端可能会发送flushall、flushdb命令,为避免误操作,建议禁用
客户端可能会发送save命令,会严重影响服务器的性能,建议禁用并使用bgsave替代

>rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command save ""

# 备份策略
主关闭aof, 开启rdb
每十分钟/10000变更生成一次rdb
每个小时保存一次最新的rdb到当前磁盘
每个小时保存一次

选择一台从节点,开启rdb,开启aof,并定期重载日志
```shell
path=/opt/data/redis/
name=dump_6380.rdb
file=$(date -d "yesterday" +"%Y%m%d%H.rdb")
/bin/mv $path/$name $path/$file
echo "move file $path/$file"

file=$(date -d "-3 days" +"%Y%m%d%H.rdb")
/bin/rm -f $path/$file
```

Redis常用命令

Redis常用命令集
1)连接操作命令
quit:关闭连接(connection)
auth:简单密码认证
help cmd: 查看cmd帮助,例如:help quit

2)持久化
save:将数据同步保存到磁盘
bgsave:将数据异步保存到磁盘
lastsave:返回上次成功将数据保存到磁盘的Unix时戳
shundown:将数据同步保存到磁盘,然后关闭服务

3)远程服务控制
info:提供服务器的信息和统计
monitor:实时转储收到的请求
slaveof:改变复制策略设置
config:在运行时配置Redis服务器

4)对value操作的命令
exists(key):确认一个key是否存在
del(key):删除一个key
type(key):返回值的类型
keys(pattern):返回满足给定pattern的所有key
randomkey:随机返回key空间的一个
keyrename(oldname, newname):重命名key
dbsize:返回当前数据库中key的数目
expire:设定一个key的活动时间(s)
ttl:获得一个key的活动时间
select(index):按索引查询
move(key, dbindex):移动当前数据库中的key到dbindex数据库
flushdb:删除当前选择数据库中的所有key
flushall:删除所有数据库中的所有key

5)String
set(key, value):给数据库中名称为key的string赋予值value
get(key):返回数据库中名称为key的string的value
getset(key, value):给名称为key的string赋予上一次的value
mget(key1, key2,…, key N):返回库中多个string的value
setnx(key, value):添加string,名称为key,值为value
setex(key, time, value):向库中添加string,设定过期时间time
mset(key N, value N):批量设置多个string的值
msetnx(key N, value N):如果所有名称为key i的string都不存在
incr(key):名称为key的string增1操作
incrby(key, integer):名称为key的string增加integer
decr(key):名称为key的string减1操作
decrby(key, integer):名称为key的string减少integer
append(key, value):名称为key的string的值附加value
substr(key, start, end):返回名称为key的string的value的子串

6)List
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素
ltrim(key, start, end):截取名称为key的list
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值
lrem(key, count, value):删除count个key的list中值为value的元素
lpop(key):返回并删除名称为key的list中的首元素
rpop(key):返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout):lpop命令的block版本。
brpop(key1, key2,… key N, timeout):rpop的block版本。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部

7)Set
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :移到集合元素
scard(key) :返回名称为key的set的基数
sismember(key, member) :member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, (keys)) :求交集并将交集保存到dstkey的集合
sunion(key1, (keys)) :求并集
sunionstore(dstkey, (keys)) :求并集并将并集保存到dstkey的集合
sdiff(key1, (keys)) :求差集
sdiffstore(dstkey, (keys)) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素

8)Hash
hset(key, field, value):向名称为key的hash中添加元素field
hget(key, field):返回名称为key的hash中field对应的value
hmget(key, (fields)):返回名称为key的hash中field i对应的value
hmset(key, (fields)):向名称为key的hash中添加元素field
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
hvals(key):返回名称为key的hash中所有键对应的value
hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value

Redis高级应用
1、安全性
设置客户端连接后进行任何操作指定前需要密码,一个外部用户可以再一秒钟进行150W次访问,具体操作密码修改设置redis.conf里面的requirepass属性给予密码,当然我这里给的是primos
之后如果想操作可以采用登陆的时候就授权使用:
sudo /opt/java/redis/bin/redis-cli -a primos
或者是进入以后auth primos然后就可以随意操作了

2、主从复制
做这个操作的时候我准备了两个虚拟机,ip分别是192.168.15.128和192.168.15.133
通过主从复制可以允许多个slave server拥有和master server相同的数据库副本
具体配置是在slave上面配置slave
slaveof 192.168.15.128 6379
masterauth primos
如果没有主从同步那么就检查一下是不是防火墙的问题,我用的是ufw,设置一下sudo ufw allow 6379就可以了
这个时候可以通过info查看具体的情况

3、事务处理
redis对事务的支持还比较简单,redis只能保证一个client发起的事务中的命令可以连续执行,而中间不会插入其他client的命令。当一个client在一个连接中发出multi命令时,这个连接会进入一个事务的上下文,连接后续命令不会立即执行,而是先放到一个队列中,当执行exec命令时,redis会顺序的执行队列中的所有命令。
比如我下面的一个例子
set age 100
multi
set age 10
set age 20
exec
get age --这个内容就应该是20
multi
set age 20
set age 10
exec
get age --这个时候的内容就成了10,充分体现了一下按照队列顺序执行的方式
discard 取消所有事务,也就是事务回滚
不过在redis事务执行有个别错误的时候,事务不会回滚,会把不错误的内容执行,错误的内容直接放弃,目前最新的是2.6.7也有这个问题的
乐观锁
watch key如果没watch的key有改动那么outdate的事务是不能执行的

4、持久化机制
redis是一个支持持久化的内存数据库
snapshotting快照方式,默认的存储方式,默认写入dump.rdb的二进制文件中,可以配置redis在n秒内如果超过m个key被修改过就自动做快照
append-only file aof方式,使用aof时候redis会将每一次的函 数都追加到文件中,当redis重启时会重新执行文件中的保存的写命
令在内存中。
5、发布订阅消息 sbusribe publish操作,其实就类似linux下面的消息发布
6、虚拟内存的使用
可以配置vm功能,保存路径,最大内存上线,页面多少,页面大小,最大工作线程
临时修改ip地址ifconfig eth0 192.168.15.129

redis-cli参数
Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]
-h Server hostname (default: 127.0.0.1)
-p Server port (default: 6379)
-s Server socket (overrides hostname and port)
-a Password to use when connecting to the server
-r Execute specified command N times
-i When -r is used, waits seconds per command.
It is possible to specify sub-second times like -i 0.1
-n Database number
-x Read last argument from STDIN
-d Multi-bulk delimiter in for raw formatting (default: \n)
-c Enable cluster mode (follow -ASK and -MOVED redirections)
--raw Use raw formatting for replies (default when STDOUT is not a tty)
--latency Enter a special mode continuously sampling latency
--slave Simulate a slave showing commands received from the master
--pipe Transfer raw Redis protocol from stdin to server
--bigkeys Sample Redis keys looking for big keys
--eval Send an EVAL command using the Lua script at
--help Output this help and exit
--version Output version and exit

Examples:
cat /etc/passwd | redis-cli -x set mypasswd
redis-cli get mypasswd
redis-cli -r 100 lpush mylist x
redis-cli -r 100 -i 1 info | grep used_memory_human:
redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3
(Note: when using --eval the comma separates KEYS[] from ARGV[] items)

常用命令:
1) 查看keys个数
keys * // 查看所有keys
keys prefix_* // 查看前缀为"prefix_"的所有keys

2) 清空数据库
flushdb // 清除当前数据库的所有keys
flushall // 清除所有数据库的所有keys

php redis client 稳定性测试

一直有个想法,做一个只有redis作数据库的系统,一直对redis的功能比较赞赏,但不知道redis的单线程稳定性如何,之前测试过100万Key的写入锁,今天又测试了一下正常读写下redis cli的get稳定性。

结果看起来还可以,2万次左右的单次读取锁0.001秒,如果每次展示需要读取300次cache,整体来说,在redis上的时间损失也不超过0.1秒,跟mysql动则0.1秒的查询效率来看,还是快得多了。

但还不会做redis db集群和数据恢复,学习ing。
官方网站 http://redis.io/
Redis命令 http://redis.io/commands
Redis协议 http://redis.io/topics/protocol
NoSQLFan Redis资料汇总专题 http://blog.nosqlfan.com/html/3537.html
继续阅读php redis client 稳定性测试

Redis主从结构备忘

redis的主从复制配置
http://www.cnblogs.com/weafer/archive/2011/09/20/2182750.html

Redis 设计与实现
https://redisbook.readthedocs.org/en/latest/

以前有想过用 Memcache 实现M/S架构的负载均衡方案,直到听说了 Redis 后才发现它做得更好。
发了几天时间研究了一下 Redis ,感觉真的很不错,特整理一下!

以下操作都是在 SUSE Linux 11 下安装了好了PHP为例子,如果安装过程中有提示缺少的包可以使用
zypper search rpm-name 进行查询,然后用 zypper install rpm-name 安装

一:memcache 和 Redis 对比总结

[memecache 特点]
1:速度最快(没有自测,但网上有详细的测试用例)
2:支持水平扩展,可以任意添加节点

[redis 特点]
1:速度没有memcache快
2:支持M/S的主从备份
3:可以支持多数据库
4:操作指令很丰富
4:支持异步数据持久化(以文件保存)

总结:
1:如果是简单的数据缓存建议使用MEMCACHE。
2:如果要对单一操作的数据量非常的大则使用MEMCACHE
3: 如果想做性能很好的缓存集群可以用Redis(M/S读写分离,如weibo中的排行榜等)
4: 如果在高并发下又想保存数据则可以用Redis (如更新热门文章浏览次数,memcachedb也不错)

二:Redis 的详细安装,

将 redis 安装到 /usr/local/webserver/redis

1:从 http://redis.io/download 上下载安装包

wget http://redis.googlecode.com/files/redis-2.2.15.tar.gz
tar zxvf redis-2.2.15.tar.gz
cd redis-2.2.15
make

mkdir -p /usr/local/webserver/redis/conf
mkdir -p /usr/local/webserver/redis/run
mkdir -p /usr/local/webserver/redis/db
cp redis.conf /usr/local/webserver/redis/conf/
cd src
# 将 src 目录下所有可执行文件复制到安装目录
cp redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server mkreleasehdr.sh /usr/local/webserver/redis/

2:修改配置文件中的如下选项
------- vi /usr/local/webserver/redis/conf/redis.conf --------
daemonize yes
pidfile /usr/local/webserver/redis/run/redis.pid
dir /usr/local/webserver/redis/db
--------------------------------------------------------------

3:创建服务脚本

------- vi /usr/local/webserver/redis/start.sh ---------
#!/bin/bash
/usr/local/webserver/redis/redis-server /usr/local/webserver/redis/conf/redis.conf
--------------------------------------------------------

------- vi /usr/local/webserver/redis/stop.sh ---------
#!/bin/bash
kill `cat /usr/local/webserver/redis/run/redis.pid`
--------------------------------------------------------

chmod a+x /usr/local/webserver/redis/start.sh /usr/local/webserver/redis/stop.sh

4:启动 redis 服务
/usr/local/webserver/redis/start.sh
验证证服务是否成功:
netstat -nlpt | grep 6379

5:启动客户端验证
/usr/local/webserver/redis/redis-cli
>set key1 val1
>get key1

三:Redis Master/Slave 主从集群配置

这里我们以本机配置 1台Master + 2台Slave 为例子,其中:

Master IP:127.0.0.1 PORT:6379
Slave1 IP:127.0.0.1 PORT:63791
Slave2 IP:127.0.0.1 PORT:63792

1:复制两个 Slave 目录,方便管理
cp -r /usr/local/webserver/redis /usr/local/webserver/redis-slave1
cp -r /usr/local/webserver/redis /usr/local/webserver/redis-slave2

2:修改 redis-slave1 的配置文件和服务脚本
-- vi /usr/local/webserver/redis-slave1/conf/redis.conf --
port 63791
pidfile /usr/local/webserver/redis-slave1/run/redis.pid
dir /usr/local/webserver/redis-slave1/db
slaveof 127.0.0.1 6379
----------------------------------------------------------

-- vi /usr/local/webserver/redis-slave1/start.sh --
#!/bin/bash
/usr/local/webserver/redis-slave1/redis-server /usr/local/webserver/redis-slave1/conf/redis.conf
---------------------------------------------------

------- vi /usr/local/webserver/redis-slave1/stop.sh ---------
#!/bin/bash
kill `cat /usr/local/webserver/redis-slave1/run/redis.pid`
--------------------------------------------------------

3:修改 redis-slave2 的配置文件和服务脚本
-- vi /usr/local/webserver/redis-slave2/conf/redis.conf --
port 63792
pidfile /usr/local/webserver/redis-slave2/run/redis.pid
dir /usr/local/webserver/redis-slave2/db
slaveof 127.0.0.1 6379
----------------------------------------------------------

-- vi /usr/local/webserver/redis-slave2/start.sh --
#!/bin/bash
/usr/local/webserver/redis-slave2/redis-server /usr/local/webserver/redis-slave2/conf/redis.conf
---------------------------------------------------

------- vi /usr/local/webserver/redis-slave2/stop.sh ---------
#!/bin/bash
kill `cat /usr/local/webserver/redis-slave2/run/redis.pid`
--------------------------------------------------------

4:启动 M/S 服务
/usr/local/webserver/redis/start.sh
/usr/local/webserver/redis-slave1/start.sh
/usr/local/webserver/redis-slave2/start.sh
验证服务是否正常: netstat -nlpt | grep redis-server 有3个端口存在证明成功了
0 0.0.0.0:6379
0 0.0.0.0:63791
0 0.0.0.0:63792

5:验证M/S服务是否生效
/usr/local/webserver/redis/redis-cli [默认连接端口:6379 的 Master服务]
>set key1 val1
>quit

/usr/local/webserver/redis/redis-cli -p 63791 [连接 Slave1 服务]
>get key1
"val1" (数据成功同步了)

/usr/local/webserver/redis/redis-cli -p 63792 [连接 Slave2 服务]
>get key1
"val1" (数据成功同步了)

四:安装 Redis PHP 扩展

Redis所有的客户端在 http://redis.io/clients [PHP选项卡] 基于性能选择安装 phpredis

在 https://github.com/nicolasff/phpredis/downloads 上下载源代码包到本地然后上传到服务器.

tar zxvf nicolasff-phpredis-2.1.3-167-ga5e53f1.tar.gz
cd nicolasff-phpredis-a5e53f1
phpize
./configure
make && make install

查看输出信息会告诉你 redis.so 的那个目录下,把它复制到PHP的扩展目录下
我的系统上是 /usr/lib/php5/extensions/

用 php --ini 找到 php.ini 的位置,加入以下行后重启WEB服务器
=========================
extension=redis.so
=========================

五:PHP负载开发方案

<?php
/**
 * Redis 操作,支持 Master/Slave 的负载集群
 *
 * @author V哥
 */
class RedisCluster{

    // 是否使用 M/S 的读写集群方案
    private $_isUseCluster = false;

    // Slave 句柄标记
    private $_sn = 0;

    // 服务器连接句柄
    private $_linkHandle = array(
        'master'=>null,// 只支持一台 Master
        'slave'=>array(),// 可以有多台 Slave
    );

    /**
     * 构造函数
     *
     * @param boolean $isUseCluster 是否采用 M/S 方案
     */
    public function __construct($isUseCluster=false){
        $this->_isUseCluster = $isUseCluster;
    }

    /**
     * 连接服务器,注意:这里使用长连接,提高效率,但不会自动关闭
     *
     * @param array $config Redis服务器配置
     * @param boolean $isMaster 当前添加的服务器是否为 Master 服务器
     * @return boolean
     */
    public function connect($config=array('host'=>'127.0.0.1','port'=>6379), $isMaster=true){
        // default port
        if(!isset($config['port'])){
            $config['port'] = 6379;
        }
        // 设置 Master 连接
        if($isMaster){
            $this->_linkHandle['master'] = new Redis();
            $ret = $this->_linkHandle['master']->pconnect($config['host'],$config['port']);
        }else{
            // 多个 Slave 连接
            $this->_linkHandle['slave'][$this->_sn] = new Redis();
            $ret = $this->_linkHandle['slave'][$this->_sn]->pconnect($config['host'],$config['port']);
            ++$this->_sn;
        }
        return $ret;
    }

    /**
     * 关闭连接
     *
     * @param int $flag 关闭选择 0:关闭 Master 1:关闭 Slave 2:关闭所有
     * @return boolean
     */
    public function close($flag=2){
        switch($flag){
            // 关闭 Master
            case 0:
                $this->getRedis()->close();
            break;
            // 关闭 Slave
            case 1:
                for($i=0; $i<$this->_sn; ++$i){
                    $this->_linkHandle['slave'][$i]->close();
                }
            break;
            // 关闭所有
            case 1:
                $this->getRedis()->close();
                for($i=0; $i<$this->_sn; ++$i){
                    $this->_linkHandle['slave'][$i]->close();
                }
            break;
        }
        return true;
    }

    /**
     * 得到 Redis 原始对象可以有更多的操作
     *
     * @param boolean $isMaster 返回服务器的类型 true:返回Master false:返回Slave
     * @param boolean $slaveOne 返回的Slave选择 true:负载均衡随机返回一个Slave选择 false:返回所有的Slave选择
     * @return redis object
     */
    public function getRedis($isMaster=true,$slaveOne=true){
        // 只返回 Master
        if($isMaster){
            return $this->_linkHandle['master'];
        }else{
            return $slaveOne ? $this->_getSlaveRedis() : $this->_linkHandle['slave'];
        }
    }

    /**
     * 写缓存
     *
     * @param string $key 组存KEY
     * @param string $value 缓存值
     * @param int $expire 过期时间, 0:表示无过期时间
     */
    public function set($key, $value, $expire=0){
        // 永不超时
        if($expire == 0){
            $ret = $this->getRedis()->set($key, $value);
        }else{
            $ret = $this->getRedis()->setex($key, $expire, $value);
        }
        return $ret;
    }

    /**
     * 读缓存
     *
     * @param string $key 缓存KEY,支持一次取多个 $key = array('key1','key2')
     * @return string || boolean  失败返回 false, 成功返回字符串
     */
    public function get($key){
        // 是否一次取多个值
        $func = is_array($key) ? 'mGet' : 'get';
        // 没有使用M/S
        if(! $this->_isUseCluster){
            return $this->getRedis()->{$func}($key);
        }
        // 使用了 M/S
        return $this->_getSlaveRedis()->{$func}($key);
    }

    /**
     * 条件形式设置缓存,如果 key 不存时就设置,存在时设置失败
     *
     * @param string $key 缓存KEY
     * @param string $value 缓存值
     * @return boolean
     */
    public function setnx($key, $value){
        return $this->getRedis()->setnx($key, $value);
    }

    /**
     * 删除缓存
     *
     * @param string || array $key 缓存KEY,支持单个健:"key1" 或多个健:array('key1','key2')
     * @return int 删除的健的数量
     */
    public function remove($key){
        // $key => "key1" || array('key1','key2')
        return $this->getRedis()->delete($key);
    }

    /**
     * 值加加操作,类似 ++$i ,如果 key 不存在时自动设置为 0 后进行加加操作
     *
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int 操作后的值
     */
    public function incr($key,$default=1){
        if($default == 1){
            return $this->getRedis()->incr($key);
        }else{
            return $this->getRedis()->incrBy($key, $default);
        }
    }

    /**
     * 值减减操作,类似 --$i ,如果 key 不存在时自动设置为 0 后进行减减操作
     *
     * @param string $key 缓存KEY
     * @param int $default 操作时的默认值
     * @return int 操作后的值
     */
    public function decr($key,$default=1){
        if($default == 1){
            return $this->getRedis()->decr($key);
        }else{
            return $this->getRedis()->decrBy($key, $default);
        }
    }

    /**
     * 添空当前数据库
     *
     * @return boolean
     */
    public function clear(){
        return $this->getRedis()->flushDB();
    }

    /* =================== 以下私有方法 =================== */

    /**
     * 随机 HASH 得到 Redis Slave 服务器句柄
     *
     * @return redis object
     */
    private function _getSlaveRedis(){
        // 就一台 Slave 机直接返回
        if($this->_sn <= 1){
            return $this->_linkHandle['slave'][0];
        }
        // 随机 Hash 得到 Slave 的句柄
        $hash = $this->_hashId(mt_rand(), $this->_sn);
        return $this->_linkHandle['slave'][$hash];
    }

    /**
     * 根据ID得到 hash 后 0~m-1 之间的值
     *
     * @param string $id
     * @param int $m
     * @return int
     */
    private function _hashId($id,$m=10)
    {
        //把字符串K转换为 0~m-1 之间的一个值作为对应记录的散列地址
        $k = md5($id);
        $l = strlen($k);
        $b = bin2hex($k);
        $h = 0;
        for($i=0;$i<$l;$i++)
        {
            //相加模式HASH
            $h += substr($b,$i*2,2);
        }
        $hash = ($h*1)%$m;
        return $hash;
    }

}// End Class

// ================= TEST DEMO =================

// 只有一台 Redis 的应用
$redis = new RedisCluster();
$redis->connect(array('host'=>'127.0.0.1','port'=>6379));
$redis->set('id',35);
var_dump($redis->get('id'));

// 有一台 Master 和 多台Slave 的集群应用
$redis = new RedisCluster(true);
$redis->connect(array('host'=>'127.0.0.1','port'=>6379), true);// master
$redis->connect(array('host'=>'127.0.0.1','port'=>63791), false);// slave 1
$redis->connect(array('host'=>'127.0.0.1','port'=>63792), false);// slave 2
$redis->set('id',100);
for($i=1; $i< =100; ++$i){ var_dump($redis->get('id')).PHP_EOL;
}

// phpRedis 扩展的更多高级操作
$redis = new RedisCluster();
$redis->connect(array('host'=>'127.0.0.1','port'=>6379));
$ret = $redis->getRedis()->ping();// phpRedis 原始API
var_dump($ret);

[phpReadis API手册]
https://github.com/nicolasff/phpredis

[redis 配置说明--来自网络]

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
daemonize no
2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
4. 绑定的主机地址
bind 127.0.0.1
5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
loglevel verbose
7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
logfile stdout
8. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16
9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
11. 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
12. 指定本地数据库存放目录
dir ./
13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
slaveof
14. 当master服务设置了密码保护时,slav服务连接master的密码
masterauth
15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
requirepass foobared
16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
19. 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
20. 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
vm-enabled no
22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0
24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
vm-page-size 32
25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
vm-pages 134217728
26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
activerehashing yes
30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

主从环境的安全备份
前提:现在有主从结构,主库没有配置持久化,从库配置AOF。(主库用来备份和写服务,从库用来提供读服务)
场景:哪一天主库突然宕了,怎么办?
非常危险的动作:重新启动主库。
要知道这样一来,最坏情况数据将全部丢失。因为主库没有配置持久化,所以主库的data目录只有从库连接主库请求resync的时候做快照留下来的dump.rdb文件。如果重启主库,主库会按照data目录下的dump.rdb来恢复数据。因此,如果从库是从最开始就配置好了的而且没有发生过再次请求resync,那么此时的dump.rdb将是空的。主库按照空的dump.rdb恢复数据,自然数据全无,从库发现主库能够连接上时会自动请求resync,从库也将拷贝一份空白的数据。

phpRedis性能测试

http://redis.io/clients
redis官方推荐了两个php的redis客户端,Predis和phpredis。其中,Predis为纯php实现,phpredis为C语言的扩展。

Predis为php源码码,扩展方便,但只支持php5.3+,phpredis性能应该会好一些,写了一个测试脚本来测试。
测试的笔记本配置为i7 2630Q+10G

phpredis
https://github.com/nicolasff/phpredis

redis.php

<?php
class cache
{
	function __construct()
	{
		$this->_instance();
	}
	
	protected function _instance()
	{
		static $_instance;

		if(empty($_instance))
			$_instance = $this->connect();
		return $_instance;
	}
	
	function connect()
	{
		$this->db = new redis();
		$this->db->connect('127.0.0.1', '6379');
		return $this;
	}

	function __call($method, $args)
	{
		return call_user_func_array(array($this->db, $method) , $args);
	}
}

$redis = new cache();
$key_pre = time();
echo "pre : $key_pre\n";

//测试百万次循环
$s = microtime(true);
while($total < 1000000)
{
	$total++;
}
$e = microtime(true);
printf("1 000 000 loop cost %4f\n", $e-$s);

//测试百万次写入
$total = 0;
$s = microtime(true);
while($total <= 1000000)
{
	$redis->set("$key_pre:{$total}", $total);
	$total++;
}
$e = microtime(true);
printf("1 000 000 set cost %4f, lastkey: %s\n", $e-$s, "$key_pre:{$total}");

//测试百万次读取
$total = 0;
$s = microtime(true);
while($total <= 1000000)
{
	$redis->get( "$key_pre:{$total}" );
	$total++;
}
$e = microtime(true);
printf("1 000 000 get cost %4f, lastkey: %s\n", $e-$s, "$key_pre:{$total}");

//测试百万级 key 查找
$total = 0;
$s = microtime(true);
$redis->keys("$key_pre:*");
$e = microtime(true);
printf("1 000 000 key find cost %4f\n", $e-$s);

执行结果

shuhai@Aspire:/wwwroot$ php redis.php
pre : 1358331812
1 000 000 loop cost 0.039773
1 000 000 set cost 91.499741, lastkey: 1358331812:1000001
1 000 000 get cost 111.465971, lastkey: 1358331812:1000001
1 000 000 key find cost 1.173917