redis笔记

什么是redis

Redis是key-value型的内存数据库
Redis是单线程的
默认端口为6379

启动

redis-server /usr/local/redis/etc/redis.conf
如果需要后台运行,修改redis.conf中的daemonize为yes

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# redis.conf
# 守护进程执行
daemonize yes
# 配置运行的pid
pidfile /var/run/redis.pid
# 绑定ip,设置后只接受来自该ip的请求
bind 127.0.0.1
# 监听端口,默认6379
port 6379
# 客户端空闲N秒后断开连接(0为禁用该功能)
timeout 0
# 日志等级 4级 debug/verbose/notice/warning
loglevel notice
# 日志文件
logfile /dev/null
# 数据库数量
databases 16
########################快照(snapshoting)####################
# 设定保存数据库到磁盘的规则 可同时设定多条规则
save <seconds> <changes> # seconds秒内发生changes次写操作,执行save操作
save 900 1 # 900秒内发生1次写操作,执行save操作
save 300 10 # 300秒内发生10次写操作,执行save操作
save 60 10000 # 60秒内发生10000次写操作,执行save操作
# 工作目录 rdb及aof文件都将创建在这个目录
dir /usr/local/var/db/redis
########################append only mode#####################
# 开启AOF
appendonly no
# AOF文件名
appendfilename "appendonly.aof"
# fsync频率 fsync()操作告诉操作系统将数据写到磁盘
appendfsync no # 不指定fsync,是否写入磁盘取决于操作系统
appendfsync always # 每次写操作后执行fsync,最慢但是最安全
appendfsync everysec # 每秒执行一次fsync

数据类型

key命令

1
2
3
4
5
6
7
8
9
10
11
exists key 检查键key是否存在
type key 检查键key的值的类型
keys pattern 列出满足规则pattern的所有键名
del key1 key2 .. 删除键
expire key second 为键设置过期时间,默认过期时间是永久 所有类型都支持
randomkey 从数据库中随机取出一个key,如果当前数据库是空的,返回空串
rename oldkey newkey 重命名key 如果失败则可能是oldkey不存在或oldkey与newkey 相同
ttl key 返回key过期时间的剩余秒数
move key db-index 将key从当前数据库移动到指定数据库
monitor 实时打印出redis服务器接受到的命令,调试用
object encoding key 查看key的值对象的编码

String

set/get

1
2
3
4
5
6
7
8
9
10
11
12
set key value O(1)
set key value EX second 设置值并设置过期时间second,同setex O(1)
set key value NX key不存在时设置值为value,同setnx O(1)
set key value XX key存在时设置值为value O(1)
get key O(1)
getset key value 获取key的值再设置key的值 O(1)
mget key1 key2 key3 .. 获取多个key的值
mset key1 value1 key2 value2 .. 设置多个key 的值
strlen key 返回字符串长度 O(1)

示例: 锁的简单实现

1
set resource-name anystring NX EX max-lock-time

incr/decr

这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。

1
2
3
4
incr keykey的值加1,并返回新值。如果key不存在,以0为默认值加1,则设置key的值为1 O(1)
decr keykey的值减1,并返回新值。如果key不存在,以0为默认值减1,则设置key的值为-1
incrby key integer 和incr类似,不过加的值为integer O(1)
decrby key integer 和decr类似,不过减的值为integer

示例: 计数器

1
incr peter:2012.3.12

示例: 限速器
限速器用于限制一个操作可以被执行的速率

bitmap

1
2
3
4
5
6
7
8
9
10
11
12
13
setbit key offset value 设置或清除指定偏移量上的位并返回之前该位置的值 offset>=0,且小于2^32(key大小限制在了512M内) O(1)
getbit key offset 获取key的指定位置的值 O(1)
bitcount key <start> <end> 计算给定key中值为1的比特位的数量。 O(N)
bitop operation destkey key1 key2.. 对多个key进行运算并将结果存储到destkey中 O(N)
#示例: 使用bitcount统计用户上线次数
setbit user:x 1 1 标记用户x第一天上线过
setbit user:x 100 1 标记用户x第100天上线过
bitcount user:x 统计用户上线天数。bitmap占用空间很小,即使运行10年,也只占用10*365位空间,也就是456字节,所以命令执行也是非常快的
#bitop
bitop and destkey key1 key2

incr decr incrby decrby只有当值为整数时可以使用
value加不加引号都作为字符串处理

List

  • list类型使用双向链表实现,不支持查找且不排重
  • 常用场景
    消息队列
  • 基本命令
1
2
3
4
5
6
7
8
9
10
11
12
lpush key string 向key对应的list的头部插入一个字符串元素 O(1) 返回添加完后的元素数
rpush key string 向key对应的list的尾部插入一个字符串元素 O(1) 返回添加完后的元素数
lpop key 删除并返回key对应list的头部元素 O(1)
rpop key 删除并返回key对应list的尾部元素 O(1)
lset key index value 设定key对应的list的第index个元素值为value O(N) index从0开始
llen key 返回key对应list的长度 O(1)
lindex key index 获取指定索引的元素 O(N)
lrange key start end O(S+N)S为偏移量 N为获取元素数 返回键key对应的list中索引start到end元素的值
ltrim key start end O(N) N为移除list的元素数量 截取其中索引start到end的元素,扔掉其他元素,返回剩余数
lrem key num value O(N) N是列表长度 在list中按顺序从头到尾的顺序删除num个值为value的元素
blpop key1 key2 .. keyn timeout 依次查找key1...keyn对应的list,找出第一个非空的list,然后对其执行lpop操作,并返回该键值对;如果全部为空,则阻塞timeout秒(timeout为0表示一直阻塞),阻塞时如果任何一个list有push操作出现,则超时解除
brpop key1 key2 .. keyn timeout 类似blpop不过是执行rpop

Set

  • 哈希表实现,支持查找且排重
  • 常用场景
    用于记录做过某些事情,如投票系统以日期为key,将已投用户的id存入。可以简单查询用户当前是否投票
  • 常用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sadd key member member2 .. 添加一个string元素到key对应的set集合中,成功返回1,如果元素在集合中,返回0key对应的set不存在返回错误 O(N) N是添加成员的数量
srem key member member2.. 从key对应的set中删除给定元素member,成功返回1 O(N) N是删除成员的数量
scard key 返回key对应的set的元素数 O(1)
smembers key member 返回key对应set中全部元素,结果无序 O(N)
sismember key member 判断member元素是否在key对应的set中 O(1)
spop key 随机删除并返回key对应set中的一个元素 O(1)
srandmember key 随机返回key对应set中的一个元素 注意不删除 O(1)
smove srckey dstkey member 将srckey对应set中的member元素移至dstkey对应set中 O(1)
sinter key1 key2 ... keyn 返回所有key对应set的交集 O(N*M) N为给定集合中最小的集合,M为给定集合的数量
sinterstore keyresult key1 key2 .. keyn 返回所有key对应set的交集,并存至keyresult中
sunion 并集 O(N) N是所有集合元素的数量
sunionstore
sdiff 差集 O(N) N是所有集合元素的数量
sdiffstore

Set没有阻塞式随机弹出bspop,不过我们可以借助辅助list实现类似功能

1
2
3
4
5
6
7
8
9
10
11
12
13
消费者代码:
LOOP forever
WHILE SPOP(key) returns elements
... process elements ...
END
BRPOP helper_key
END
添加元素的客户端(生产者)则执行以下代码:
MULTI
SADD key element
LPUSH helper_key x
EXEC

Sorted Set

  • 使用哈希表和跳跃表(skiplist)实现。跟无序集合相比,添加了score成员记录顺序,集合元素依旧唯一
  • 常用场景
    set可以做的zset都能实现,且还可完成更多需求,例如使用zset构建一个具有优先级的队列,又或实现排行榜应用按顶帖数排序,排序的值设定为score,具体帖子内容设置为相应的value,每次用户顶帖时,指定zincrby 去更新key的score即可
  • 常用命令
1
2
3
4
5
6
7
8
9
10
11
12
zadd key score member 向集合中添加元素member(其中score为排名), 如果member已存在于key中,则更新score O(M*logN) M是新增成员数量
zrem key memberkey中删除元素member O(M*logN) M是删除成员的数量
zcard key 返回key对应集合中的元素数 O(1)
zrank key member 返回memberkey中的排名,可用于判定member是否存在于key中 O(logN)
zincrby key incr member 增加对应member的score值,并重新排序,返回更新后的score值 O(logN)
zrange key start end 返回索引start到end的元素(按score排序从小到大顺序) O(logN+M) N为有序集基数 M为结果集基数
zrevrange key start end zrange 按照score倒序版
zrangebyscore key min max 返回score在minmax之间的元素(按score排序) O(logN+M) N为有序集基数 M为结果集基数
zscore key member 返回key对应集合中成员的score O(1)
zcount key min max 返回key对应集合中score在minmax之间的元素数 O(logN)
zunionstore
zinterstore

Hash

  • 哈希类型每个Key对应一个哈希表,哈希表中使用field=value这种键值对存储。
  • 常用场景
    存储对象,如存储用户信息
  • 常用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
hset key field value 设置key对应hash对象中指定域field的值 O(1)
hsetnx key field value 设置key对应hash对象中指定域field的值(仅当field不存在) O(1)
hget key field 获取key对象hash对象中指定域的值 O(1)
hmset key field1 value1 field2 value2 ... 设置key对应hash对象中多个指定域的值 O(N) N为field-value对的数量
hmget key field1 field2... O(N) N为给定域的数量 获取key对应hash对象中多个指定域的值
hexists key field 判断在 key对应hash对象中指定域field是否存在 O(1)
hdel key field .. 删除key对应hash对象中指定域field O(N) N为要删除field的数量
hlen key 返回key对应hash对象中域的个数 O(1)
hkeys key 返回key对应hash对象中域的的名字 O(N) N为哈希表大小
hvals key 返回key对应hash对象中域的值 O(N) N为哈希表大小
hgetall key 返回key对应hash对象中所有域的名字和值 O(N) N为哈希表大小
hstrlen key 返回key对象hash对象中域值的字符串长度
hincrby key field increment 为key的指定整数值字段增加increment O(1)
hincrbyfloat key field increment 为可以的指定浮点值字段增加increment

列表list与有序集合
列表内部使用双向链表实现,所以访问两端数据速度快,访问中间内容速度慢,更适用于新鲜事、日志这样这样很少访问中间元素的应用
有序集合使用散列表和跳跃表实现,读取任何位置的数据都很快(O(log(N))
列表中不能自由调整某元素的位置,但有序集合可以通过调整分数实现
有序集合比列表耗费更多内存
有序集合数据自动排重,列表数据可以重复

排序

redis支持对list、set、sorted set排序
sort key [by pattern] [limit start count] [asc|desc] [alptha] [store dstkey]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lpush mylist 2
lpush mylist 1
lpush mylist 3
//对mylist排序
sort mylist
//对mylist从大到小
sort mylist desc
//对mylist反向以字符形式排序
sort mylist desc alpha
//排序mylist且取位置0开始的1个值
sort mylist limit 0 1
//排序mylist并存储至cachelist列表中
sort mylist store cachelist
type cachelist
llen cachelist
lrange cachelist 0 2

练习

数据类型练习

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 阻塞地保存当前数据库数据到磁盘
save
# 后台异步保存当前数据库数据到磁盘
# bgsave命令执行后立即返回ok,然后redis fork出一个新的进程,原来的redis进程(父进程)继续处理客户端请求,而子进程负责将数据保存到磁盘,然后退出
# 客户端可以通过lastsave命令查看相关信息,判断bgsave命令是否执行成功
# 时间复杂度为O(N) N为要保存的key的数量
bgsave
# 返回最近一次成功将数据保存到磁盘的时间,以unix时间戳表示
# 时间复杂度为O(1)
lastsave
# 获取服务器信息及统计数据
info
- connected_clients 已连接客户端数(不包括通过从服务器连接的客户端)
- used_memory 由redis分配器分配的内存总量
- used_memory_human used_memory友好版本
- used_memory_rss_human 操作系统角度分配的内存总量
rss>used,表示存在内存碎片;
used>rss,表示redis开始使用swap空间,这种情况下,操作可能产生延迟
- role 主从复制角色
- connected_slaves 连接的从库数量
- master_sync_left_bytes距离同步完成剩余的字节数
# 执行AOF文件重写操作
# 即使bgrewriteof失败也不会造成任何数据丢失,重写会创建一个新的AOF文件,在bgrewriteof完成之前不会覆盖旧文件。
# 重写操作只会在没有其他持久化工作在后台执行时触发。
# 时间复杂度为O(N) N为要追加到AOF文件中的数据数量。
bgrewriteof
# 获取当前所有连接信息
client list
# 断开指定连接
client kill <ip:port>
# 返回当前数据库的key的数量
dbsize
# 清空所有数据库的数据
flushall
# 清空当前数据库中的数据
flushdb
# 实时打印出redis服务器接收到的命令[调试用]
monitor
# 关闭服务器 可以保证不丢失数据
shutdown
# 慢日志
CONFIG SET slowlog-log-slower-than 100 # 让slowlog记录所有执行时间大于100微妙的命令
CONFIG SET slowlog-max-len 1000 # slowlog最大记录1000条记录
slowlog get # 返回所有slowlog
slowlog get 10 # 返回10条slowlog
1) 1) (integer) 12 # 唯一性(unique)的日志标识符
2) (integer) 1324097834 # 被记录命令的执行时间点,以 UNIX 时间戳格式表示
3) (integer) 16 # 查询执行时间,以微秒为单位
4) 1) "CONFIG" # 执行的命令,以数组的形式排列
2) "GET" # 这里完整的命令是 CONFIG GET slowlog-log-slower-than
3) "slowlog-log-slower-than"
2) 1) (integer) 11
2) (integer) 1324097825
3) (integer) 42
4) 1) "CONFIG"
2) "GET"
3) "*"

处理过期数据

Redis通过两个步骤进行过期处理
步骤1: 每0.1秒主动监测并删除部分过期数据
步骤2: 每次访问数据时,判断数据是否已经过期

内存淘汰

当内存不足时启用内存淘汰策略,redis有3种策略,通过maxmemory-policy设定
1.随机淘汰算法: 随机删除一个key
2.LRU淘汰算法: 删除一个最近最少访问的key
3.TTL淘汰算法: 删除一个最快过期的key

[内存淘汰](http://ifeve.com/redis-lru/)

持久化

快照(snapshot)

RDB是redis默认的持久化策略。

RDB是一个经过压缩的二进制文件,采用RDB持久化时服务器只会保存一个RDB文件。

内部实际上是一个定时器事件,每隔固定时间去检查当前数据改变的次数与时间是否满足配置的持久化触发条件,如果满足则自动执行bgsave保存数据

在执行bgsave期间,客户端发送的save、bgsave会被服务器拒绝(防止产生竞争)

1
2
3
4
5
6
7
8
########################快照(snapshoting)####################
# 设定保存数据库到磁盘的规则 可同时设定多条规则
save <seconds> <changes> # seconds秒内发生changes次写操作,执行save操作
save 900 1 # 900秒内发生1次写操作,执行save操作
save 300 10 # 300秒内发生10次写操作,执行save操作
save 60 10000 # 60秒内发生10000次写操作,执行save操作

日志追加(AOF)

默认redis异步地通过快照方式向磁盘持久化数据。当redis进程出现问题或者机器断电发生时,会导致丢失一段时间的数据(取决于snapshot配置)。

AOF是另一种可选的更可靠的持久化策略。出现上述问题时,redis只会丢失一秒或一个写操作的数据(取决于aof配置)。

1
2
3
4
5
6
7
8
9
10
11
12
########################append only mode#####################
# 开启AOF
appendonly no
# AOF文件名
appendfilename "appendonly.aof"
# fsync频率 fsync()操作告诉操作系统将数据写到磁盘
appendfsync no # 不指定fsync,是否写入磁盘取决于操作系统
appendfsync always # 每次写操作后执行fsync,最慢但是最安全
appendfsync everysec # 每秒执行一次fsync,这种方案

AOF方式通过write函数把写语句(写或修改)追加到文件尾部(默认是appendonly.aof)。

redis重启时会读取appendonly.aof文件中所有命令并依次执行,从而将数据写入到内存中。

AOF重写

appendonly.aof文件随着命令执行次数增加而不断膨胀,而其中有很多相同效果的语句,如调用set name xk 100次,那么appendonly.aof中就有100条。
这个问题,可以通过使用bgrewriteaof命令重写AOF日志解决

AOF重写缓冲区

redis是单线程的,为了在重写时不阻塞服务,redis使用子进程方式进行AOF重写-bgrewriteaof

所谓的“重写”其实是一个有歧义的词语, 实际上, AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。

但是这里产生了另外一个问题: 子进程在AOF重写时,父进程还在继续处理请求,这些请求有可能对数据库进行修改,这导致服务器当前状态和重写后的AOF文件所保存的服务器不一致。为此redis引入了AOF重写缓冲区。

AOF重写期间的命令会同时写到当前AOF文件及AOF重写缓冲区,当AOF重写完成后,会向父进程发送一个信号,父进程收到信号后会阻塞当前服务,将AOF重写缓冲区中的写命令写入到重写的AOF文件中(保证了AOF文件数据库和当前数据库一致),之后使用重写的AOF文件覆盖原有AOF文件。

AOF

持久化策略选择

默认redis使用snapshot方式持久化数据。

当服务器载入RDB时,REDIS一直处于阻塞状态,直到载入完毕。

可以同时开启snapshot和aof策略,服务器启动时会优先加载AOF文件,没有则去加载RDB文件。

如果服务器初始使用rdb方式,之后开启aof,这时重启redis会根据aof进行载入,原来rdb中的数据无法加载到数据库中。

集群

主从

redis实现主从很简单,在slave服务器上配置
slaveof 192.168.1.1 6379 #指定master的IP和端口
* 搭建主从后,从服务器系统不允许写操作
* 可使用info命令查看主从是否配置成功
* master配置了密码,slave同样需要指明master密码 masterauth password

主从同步流程

1.从服务器连接主服务器,发送sync命令
2.主服务器执行bgsave,并使用缓冲区记录bgsave之后执行的所有写命令
3.主服务器bgsave执行完毕,向从服务器发送快照文件,从服务器丢弃所有旧数据,开始载入快照文件
4.主服务器快照发送完毕,开始向从服务器发送缓冲区里的写命令

redis-cluster

redis-cluster

访问控制

#设置登录密码(/etc/redis.conf中)
requirepass xxxx

#使用密码登陆
redis-cli -h 127.0.0.1 -p 6379 -a password
或
redis-cli登录后使用auth xxx认证

发布/订阅

说明
    适用于消息分发,不推荐作为队列使用
作用
    解耦系统,如系统AB使用不同语言
命令
    subscribe channel1 channel2
    unsubscribe channel1 channel2
    psubscribe news.* tweet.*
    punsubscribe news.* tweet.*
    publish channel1 msg

php实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--------------------pub.php------------------------
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$redis->auth('sunxiangke');
$redis->publish('c1','hello world');
--------------------sub.php------------------------
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$redis->auth('sunxiangke');
$redis->subscribe(array('c1','c2'),'dealData');
function dealData($redis,$channel,$msg){
var_dump($redis,$channel,$msg);
}

事务

背景

有客户端AB A中有一系列操作需要顺序完成,且中间不允许其他客户端对数据修改,这时候就用到了事务

解析

事务将多个有序命令打包成原子操作,服务器在执行那碗事务的所有命令后,才会处理其他客户端的其他命令

事务中某条/某些命令执行失败了,事务队列中的其他命令仍然会继续执行

redis事务只原子性的,没有回滚机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MULTI
开启事务
事务中的所有操作都会进入一个队列(queue)
EXEC
执行事务
执行队列中的所有操作
DISCARD
清空事务队列并放弃执行事务
WATCH
watch是一个乐观锁,有利于减少并发中的冲突,提高吞吐量
监视指定key,检查这些键是否被更改过了,如果至少一个被监视的键在EXEC执行之前被修改了,那么整个事务会被取消,exec返回空多条批量回复(null mmulti-bulk reply)来表示事务已经失败,exec后会自动unwatch
乐观锁(Optimistic Lock)又叫共享锁(S锁),每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。乐观锁大多数是基于版本记录机制实现。
悲观锁(Pessimistic Lock)又叫排他锁(X锁),每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,都是在做操作之前先上锁。
例:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
不断重复这段代码直至没有碰撞为止

应用场景

数据缓存

手机验证码
定期更新的内容

访问频率限制

思路:

方法1
    使用string实现
    FUNCTION LIMIT_API_CALL(ip)
    ts = CURRENT_UNIX_TIME()
    keyname = ip+":"+ts
    current = GET(keyname)

    IF current != NULL AND current > 10 THEN
        ERROR "too many requests per second"
    END

    IF current == NULL THEN
        MULTI
            INCR(keyname, 1)
            EXPIRE(keyname, 1)
        EXEC
    ELSE
        INCR(keyname, 1)
    END

    PERFORM_API_CALL()

方法2 
    使用List实现
    FUNCTION LIMIT_API_CALL(ip)
    current = LLEN(ip)
    IF current > 10 THEN
        ERROR "too many requests per second"
    ELSE
        IF EXISTS(ip) == FALSE
            MULTI
                RPUSH(ip,ip)
                EXPIRE(ip,1)
            EXEC
        ELSE
            RPUSHX(ip,ip)
        END
        PERFORM_API_CALL()
    END
参考
    http://redisdoc.com/string/incr.html

取最新N条文章

使用lpush   lastest.posts id将文章id插入到列表中
使用ltrim   lastest.posts 0 5000,使列表永远保存最新的5000条数据
使用lrange  lastest.posts 0 num 获取最新num条数据

取TOP N/排行榜

score表示热度
zadd    postlist 0 1
zincrby postlist 1 1
zincrby postlist 1 1
zincrby postlist 1 1
zadd    postlist 1 2
zadd    postlist 2 3
zrevrange postlist 0 N 查出top N

计数器

如商品喜欢数、评论数、浏览数,用户关注数、粉丝数、未读消息数等
hset    product:1 like 1
hincrby product:1 like 1    喜欢书+1

存储社交关系

譬如将用户的好友/粉丝/关注,可以存一个sorted set中,score可以是timestamp,这样求两个人的共同好友的操作,只需要用求交集的命令即可
zadd user:1:follow  1456999063  3
zadd user:1:follow  1456999063  4
zadd user:1:follow  1456999063  5
zadd user:2:follow  1456999063  4
zadd user:2:follow  1456999063  5
zinterstore out:1:2 user:1:follow user:2:follow
zrange out:1:2 0 -1

队列

模式
    生产者消费者模式
        多个消费者监听队列,谁先抢到消息谁就会从队列中取走消息,即每个消息最多被1个消费者接收
    发布者订阅者模式
        多个消费者监听队列且会收到相同的一份消息,即每个消息可以被多个消费者接收

消息分发与消息队列       
    可以为每个接收方设置一个信箱(list or set),发送消息就是把消息加到接收方的信箱,接受消息就是获取自己信箱中的内容,分发的方式采用PUB/SUB,队列实现使用brpop()

生产者消费者模式使用
    使用list构建普通队列
        rpush入列,lpop出列
    使用sorted set实现优先级队列
        生产者消费者模式
        zadd mysset 1 'one'
        zadd mysset 1 'two'
        zadd mysset 2 'three'
        假定score越高优先级越高,使用zrevrange mysset 0 0取出score最大的一条的值为value
        使用zrem mysset value消费value

发布者订阅者模式使用
    使用pub/sub模式
    * 队列不建议使用该方式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-------------------发布文章队列使用示例----------------------------
//发布文章时
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$uid = getUid();
$content = getContent();
$timestamp = time();
$data = [
'uid'=>$uid,
'content'=>$content,
'timestamp'=>$timestamp
];
$redis->lpush('weibo_list',json_encode($data));
//计划任务中
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$weibo = new Weibo();
while(true){
if($redis->lsize('weibo_list')>0){
info = $redis->rpop('weibo_list');
$info = json_decode($info,true);
$weibo->post($info->uid,$info->content,$info->timestamp);
}
}else{
sleet(1);
}
$redis->close();
总结:
计划任务中使用while循环调用队列,如无数据则空隙1秒。其实这样是有问题的,例如在刚睡眠时队列中来了新数据,那么该条数据1秒后才会被读取。可以开始多个crontab来减小这种概率,但治标不治本。可以考虑事件通知的模型。

用作缓存替代memcacahe

存储session

方式1 
    使用phpredis扩展,直接修改session配置
    session.save_handler = redis
    session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeout=2.5, tcp://host3:6379?weight=2"
    redis中的内容 PHPREDIS_SESSION:kv8nkmshk0v3umi2f9s2v4nn91

方式2
    使用session_set_save_handler修改session处理机制
    session_set_save_handler(callback open,callback close,callback read,callback write,callback destroy,callback gc);
    我们需要处理其中的read,write,destroy这三个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
//session_manager.php
/**
* redis实现的session管理类
**/
class RedisSessionManager{
private $redis;
private $sessionSavePath;
private $sessionExpireTimestamp = 300;
public function __construct(){
$this->redis = new Redis();
$this->redis->connect('127.0.0.1',6379);
$this->redis->auth('sunxiangke');
session_set_save_handler(
[$this,'open'],
[$this,'close'],
[$this,'read'],
[$this,'write'],
[$this,'destroy'],
[$this,'gc']
);
session_start();
}
public function open(){
return true;
}
public function close(){
return true;
}
public function read($sessionId){
if($value = $this->redis->get($sessionId)){
return $value;
}else{
return '';
}
}
public function write($sessionId,$data){
if($this->redis->set($sessionId,$data)){
$this->redis->expire($sessionId,$this->sessionExpireTimestamp);
return true;
}
return false;
}
public function destroy($sessionId){
if($this->redis->delete($sessionId)){
return true;
}
return false;
}
public function gc(){
return true;
}
public function __destruct(){
session_write_close();
}
}
//index.php
<?php
require './redis/session_manager.php';
new RedisSessionManager();
$_SESSION['username'] = 'sunxiangke';
exit;
phpinfo();

参考

Redis高级特性及应用场景

FAQ

1.数据结构设计

blog的关系型表
    post
        content
        author
        date
    author
        name
    comment
        name
        email
        date
解析: redis中的key很占空间,不建议以post:[post_id]:author这样来建立key
    建议使用字段名作为key,id作为字段
    HASH post:author
            1   32
            2   58
    HASH post:name
            1   sxk
            2   lxp
    HASH post:date
            1   20324123412
            2   20324123412
    HASH post:comments
            1   2,3,4 (序列化)
            2   3,4,5
    .....
参考资料:
    https://www.v2ex.com/t/15712

2.redis是单线程程序,为什么这么快?

1.纯内存操作
2.因为是单线程,无需考虑上下文切换和竞争
3.使用epoll实现多路复用IO,不在io上浪费时间

练习

数据类型练习

---------string-------------
set name sxk
setnx name sxk
get name sxk
getset name sxk2
mset age 1 sex 1
mget age sex
incr age
decr age
incrby age 2
decrby age 2

---------list--------------
lpush mylist one
rpop mylist one
lpush mylist one 
lpush mylist two
lpush mylist three
llen mylist
lrange mylist 0 3

---------set--------------
sadd myset one 
sadd myset two
sadd myset three
smembers myset
srem myset two
sismember myset one
spop mysset
srandmember myset
smove myset2 myset1 four
sinter myset1 myset2

---------sorted set-------
zadd mysset 1 one
zadd mysset 2 two
zadd mysset 3 three
zadd mysset 4 four
zscore mysset one
zrem mysset four
zcard mysset 
zrange 0 3
zcount mysset 2 3
zincrby mysset 2 two
zrank mysset one
#获取0<score<=3的key和值
zrangebyscore mysset (0 3 withscores
#获取 负无穷<score<正无穷的key和值
zrangebyscore mysset -inf +inf withscores
#以score从小到大的顺序获取mysset的全部value
zrange mysset 0 -1 withscores

---------hash-----------
hsetnx user name sxk
hsetnx user age 18
hsetnx user sex 1
hgetnx user name
hexists user name
hlen user
hdel user name
hkeys user
hvals user

FAQ

1.redis中incr自加有没有上限?
答: incr操作最大值值限制在64位有符号数字表示之内,超过后incr报错
image_1bvsgrvubdih1h3i1qiq1ip41a2u9.png-46.1kB

没超过64位之前值编码为int,超过后值编码为ember
image_1bvsgvt0hds51al4101ciepunfm.png-20kB

2.

redis持久化——RDB、AOF