Redis学习
参考书籍:
Redis实战,Redis设计与实现,Redis开发与运维
数据结构
这点和常规的范围索引不同
Redis的索引以0为开始,在进行范围访问时,范围的终点(endpoint)默认也包含在这个范围之内。
字符串
在Redis里面,字符串可以存储以下三种类型的值:
字符串,整数,浮点数
- 自增命令和自减命令
incr decr incrby decrby incrbyfloat
incrbyfloat
注意浮点数相加返回的是字符串,如果值是一个整数,和浮点数相加时,精度不准确
如果用户对一个不存在的键或者一个保存了空串的键执行自增或者自减操作,那么Redis在执行操作时会将这个键的值当作是0
来处理。
如果用户尝试对一个值无法被解释为整数或者浮点数的字符串键执行自增或者自减操作,那么Redis将向用户返回一个错误。
- 处理子串和二进制位的命令
append getrange setrange
getbit setbit bitcount
bitop 'bitop op dest-key key-name[..]'
Redis的索引以0为开始,在进行范围访问时,范围的终点(endpoint)默认也包含在这个范围之内。
带有range的命令都有偏移量,start , end
setrange , setbit 意思是在对应的偏移量设置值
带有bit命令,将字符串用二进制表示,可以设置相关的偏移量为0,1;bitcount 命令统计比特1的数量,可以设置偏移量;bitop以比特的形式进行逻辑运算。
列表
Redis的列表允许用户从序列的两端推入或者弹出元素、获取元素,执行各种常见的列表操作。
列表这个由多个字符串值组成的有序序列结构进行介绍。
- 常用列表命令
rpush lpush rpop lpop
lindex lrange ltrim
llen
注意push,pop都是以堆的形式操作的,列表可以看作两边都通的堆。
lpush li 1 2 3 4 5 ; rpush li 7 8 9 ; lrange li 0 7
结果是 5 4 3 2 1 7 8 9
lindex返回列表中偏移量为offset
的元素, lindex li 1
return 4 ; 对于超出列表范围的偏移量,返回nil
LTRIM key-name start end——对列表进行修剪,只保留从
start偏移量到
end偏移量范围内的元素,包括
start和
end
- 元素从一个列表移动到另一个列表,或者阻塞(block)执行命令的客户端直到有其他客户端给列表添加元素为止
blpop brpop
rpoplpush
brpoplpush
blpop key[..] timeout
从第一个非空列表中弹出位于最左端的元素,或者在timeout
秒之内阻塞并等待可弹出的元素出现
阻塞弹出命令和弹出并推入命令,最常见的用例就是消息传递(messaging)和任务队列(task queue)的开发。
集合
Redis的集合以无序的方式来存储多个各不相同的元素,用户可以快速地对集合执行添加元素、移除元素以及检查一个元素是否存在于集合里的操作。
- 常用集合命令
sadd srem sismember # set_add set_remove set_is_member,contain
scard smembers #return number return members
srangemember
spop #return and remove a item
smove #
SRANDMEMBER key-name [count]
——从集合里面随机地返回一个或多个元素。当count
为正数时,命令返回的随机元素不会重复; 当count
为负数时,命令返回的随机元素可能会出现重复
SMOVE source-key dest-key item
——如果集合source-key
包含元素item
, 那么从集合source-key
里面移除元素item
,并将元素item
添加到集合dest-key
中; 如果item
被成功移除,那么命令返回1,否则返回0
- 组合和处理多个集合的命令
sdiff sinter sunion 集合的差集,交集,并集运算,返回运算结果的所以元素
sdiffstore sinterstore sunionstore 将运算结果存储到第一个集合里面
散列
Redis为散列值提供了一些和字符串值相同的特性,使得散列非常适用于将一些相关的数据存储在一起。
- 常用命令
hset hget
hmget hmset
hdel
hlen
- 高级命令
hexists
hkeys hvals
hgetall #return all key and all values 可能会阻塞redis
hincrby hincrbyfloat
hscan 渐进式遍历哈希,推荐
有序集合
有序集合也存储着成员与分值之间的映射,并且提供了分值处理命令,以及根据分值大小有序地获取(fetch)或扫描(scan)成员和分值的命令。
score是浮点数类型
有序集合有两个键值,组成了一个对子,即 score 和 member ,分值和成员
- 常用命令
zadd zrem zadd key score member[..]
zcard zcount
zincrby
zrank #return member's rank
zscore #return member's score
zrange #return member stard end[ withscores]
有序集合的范围查找是根据 分值,浮点数的范围,不是常规的0,1,2
127.0.0.1:6379> zadd z 100 a 120 d 99 c 678 h 87 i 80 u 567 o 230 p
(integer) 8
127.0.0.1:6379> zcount z 100 400
(integer) 3
127.0.0.1:6379> zcard z
(integer) 8
127.0.0.1:6379> zrank z a #排名从0开始,从小到大排序
(integer) 3
127.0.0.1:6379> zscore z a
"100"
127.0.0.1:6379> zincrby z 300 a
"400"
127.0.0.1:6379> zscore z a
"400"
127.0.0.1:6379> zrange z 0 3 #从小到大排序
1) "u"
2) "i"
3) "c"
4) "d"
127.0.0.1:6379> zrange z 0 3 withscores
1) "u"
2) "80"
3) "i"
4) "87"
5) "c"
6) "99"
7) "d"
8) "120"
127.0.0.1:6379> zrem z a
(integer) 1
- 有序集合的范围型数据获取命令和范围型数据删除命令,以及并集命令和交集命令
zrevrank zrevrange
zrangebyscore
zrevrangebyscore
zremrangebyrank
zinterstore
zunionstore
127.0.0.1:6379> zrevrank z h #按照从分值大到小排序,返回成员的排名,从0开始,rev--reverse
(integer) 0
127.0.0.1:6379> zrank z h
(integer) 6
127.0.0.1:6379> zrevrange z 0 3 #按照从分值从大到小,返回成员
1) "h"
2) "o"
3) "p"
4) "d"
127.0.0.1:6379> zrevrange z 0 3 withscores
1) "h"
2) "678"
3) "o"
4) "567"
5) "p"
6) "230"
7) "d"
8) "120"
127.0.0.1:6379> zrevrangebyscore z 900 400 #按照分值的大小,从大到小, 返回max和min之间的成员
1) "h"
2) "o"
127.0.0.1:6379> zrevrangebyscore z 900 400 withscores
1) "h"
2) "678"
3) "o"
4) "567"
127.0.0.1:6379> zrevrangebyscore z 900 400 limit 0 1
1) "h"
127.0.0.1:6379> zrangebyscore z 400 900 #同zrevrangebyscore,从小到大排序
1) "o"
2) "h"
127.0.0.1:6379> zremrangebyscore z 400 900 #移除min.max之间的成员
(integer) 2
#范围移除有序集合的所以元素,通过rank,score
127.0.0.1:6379> zremrangebyscore z 400 900
(integer) 2
127.0.0.1:6379> zremrangebyrank z 0 100
(integer) 5
127.0.0.1:6379> zcard z
(integer) 0
发布订阅
subscribe
unsubscribe
publish
PSUBSCRIBE 订阅与给定模式相匹配的所有频道,
PUNSUBSCRIBE
全局命令
可以根据字符串、列表、集合、有序集合、散列这5种键里面存储着的数据,对列表、集合以及有序集合进行排序
- 排序
SORT source-key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE dest-key]
后三个可选项:默认降序,字典顺序,存储到目标
127.0.0.1:6379> rpush input 23 15 7 98 2 100
(integer) 6
127.0.0.1:6379> sort input #按照数字大小排序
1) "2"
2) "7"
3) "15"
4) "23"
5) "98"
6) "100"
127.0.0.1:6379> sort input alpha #按照字母的字典排序
1) "100"
2) "15"
3) "2"
4) "23"
5) "7"
6) "98"
- 键的过期时间
del 命令,可以删除所有数据类型的键
persist 移除键的过期时间
ttl pttl 返回还有多少秒过去,pttl有前缀p修饰返回的是有多少毫秒过期
expire expireat 设置键的过期时间,秒为单位,unix时间戳为单位
pexpire pexpireat 毫秒级
Redis的过期时间(expiration)特性来让一个键在给定的时限(timeout)之后自动被删除
但是,键也有些缺陷,只有少数几个命令可以原子地为键设置过期时间,并且对于列表、集合、散列和有序集合这样的容器(container)来说,我们只能为整个键设置过期时间,而没办法为键里面的单个元素设置过期间,在redis实战一书中使用了根据时间戳进行排序的有序集合来处理单个元素的原因。
- 密码命令
用于检测给定的密码和配置文件中的密码是否相符
AUTH password
可以在redis在配置文件/etc/redis.conf文件下设置配置文件,添加 requirepass password
或者通过命令 config set requirepass password
设置,使用命令config get requirepass
查看密码
- 选中数据库 select index 命令
select 这个命令,受到了弱化
flushdb/flushall命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库
- 键操作命令
type key 键的类型
exists 判断键是否存在
keys pattern keys *
生产环境禁止使用
dbsize 键的数量
- 迁移键
move key db #在同一个redis实例中迁移到不同的数据库
dump key , restore key ttl value #在不同的redis实例中
migrate #具有原子性
dump key这个命令将键值序列化成为字符串,在同一个redis实例中,采用restore key ttl value,这个命令把序列化的内容存储到数据库中,value即序列化的字符串,ttl设置键的生产时间
migrate这个操作可以直接在源redis上操作,通过远程服务,直接把键迁移,并且具有原子性,实际上就是上面dump,restore两个命令的结合。
- 遍历键
keys #生产上禁止使用
scan #渐进式遍历键,避免阻塞
- redis事务
Redis的基本事务(basic transaction)需要用到MULTI
命令和EXEC
命令,这种事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。和关系数据库那种可以在执行的过程中进行回滚(rollback)的事务不同,在Redis里面,被MULTI
命令和EXEC
命令包围的所有命令会一个接一个地执行,直到所有命令都执行完毕为止。当一个事务执行完毕之后,Redis才会处理其他客户端的命令。
即使事务当中命令出错,也不会回滚事务。
Redis有5个命令可以让用户在不被打断(interruption)的情况下对多个键执行操作,它们分别是:WATCH
、MULTI
、EXEC
、UNWATCH
和DISCARD
服务器命令
shutdown nosave|save
数据结构的内部编码
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。
查看数据结构内部编码的命令
object encoding key_name
一个列表和集合最多存储2的32次方减1的元素
字符串的值最多不能超过512M
字符串的内部编码:
int 8个字节的长整型
embstr 小于等于39个字节的字符串
raw 大于39个字节的字符串
哈希
ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
hashtable(哈希表):当希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)
列表
ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
集合
intset(整数集合):当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
有序集合
有序集合中的元素不能重复,但是score可以重复
ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
redis的单线程架构
每一条命令的执行都会经历三个过程,1 发送命令 2执行命令 3 返回结果
命令排序执行
速度快原因 1纯内存访问 2非阻塞IO,使用epoll实现多路复用技术,redis自身的事件处理模型将epoll中的连接,读写,关闭转换为事件 3 单线程避免了线程的切换和竞争态产生的消耗
redis的每个命令的时间复杂度不相同,如果要优化,可以查看命令的时间复杂度
如果需要插入多个键,使用批量操作命令,更加减少耗时。
使用场景
使用redis的场景:缓存,排序,排行榜,计数器应用,社交网络的点赞拉踩,消息队列,限速。
不适合redis场景:大量的数据,冷数据,不需要频繁地读取的数据
博客,文章
1. 存储文章,视频等相关信息
使用散列Hash结构存储相关的文章信息,比如
hmset article:9239 title 'this is me!' link url poster user time 123422 votes 23
- 文章或者小说,视频等按照时间或者评分显示
利用Redis的有序集合 Zset,建立两个有序集合,键值可以是文章的唯一标识符ID,而分值score是Unix时间,也可以是评分。
从系统获取unix时间是非常容易的事,而分值越高的,排在最前面的时间是最新的。
- 对文章进行投票,或者点赞等增加数量的操作
建立一个Set集合,如果是投票可以设置时间限制,为了避免长时间存储在内存空间节省内存;如果是点赞数等需要持久化操作集合,需要把点赞数和集合持久化到关系型数据库。
如何避免用户的重复性投票?
为每篇文章建立一个 已投票的集合,存储投票用户的ID。
对于已经投票的用户,重新投票,调用集合命令 sadd set1 user_ID
返回的数值是0,如果是客户端可能返回布尔值,
- 文章ID生成器,建立一个字符串,article: 123
每次读取这个字符串的值,然后使用incr('article:') 函数,将值加一,然后获取到最新的文章ID号。
或者可以使用随机字符串生产文章ID
- 文章分类,建立一个集合,存储在同一个分类的文章ID,组内可以进行按照某种规律排序
Redis的ZINTERSTORE
命令可以接受多个集合和多个有序集合作为输入,找出所有同时存在于集合和有序集合的成员,并以几种不同的方式来合并(combine)这些成员的分值(所有集合成员的分值都会被视为是1)。
构建WEB应用
- 会话cookie和记录用户最近浏览过的商品,共享Session
使用一个散列来存储登录cookie令牌与已登录用户之间的映射。要检查一个用户是否已经登录,需要根据给定的令牌来查找与之对应的用户,并在用户已经登录的时候,返回该用户的ID。
大部分复杂的工作都是在更新令牌时完成的:用户每次浏览页面的时候,程序都会对用户存储在登录散列里面的信息进行更新,并将用户的令牌和当前时间戳添加到记录最近登录用户的有序集合里面;如果用户正在浏览的是一个商品页面,那么程序还会将这个商品添加到记录这个用户最近浏览过的商品的有序集合里面,并在被记录商品的数量超过25个时,对这个有序集合进行修剪。
-
实现购物车,每个用户的购物车都是一个散列,这个散列存储了商品ID与商品订购数量之间的映射。
-
用redis缓存页面,模板语言编写页面
-
商品促销,数据行缓存的作用
商品页面通常只会从数据库里面载入一两行数据:包括已登录用户的用户信息(这些信息可以通过AJAX动态地载入,所以不会对页面缓存造成影响)和商品本身的信息。
活动每天都会推出一些特价商品供用户抢购,所有特价商品的数量都是限定的,卖完即止。
在这种情况下,网站是不能对整个促销页面进行缓存的,因为这可能会导致用户看到错误的特价商品剩余数量,但是每次载入页面都从数据库里面取出特价商品的剩余数量的话,又会给数据库带来巨大的压力,并导致我们需要花费额外的成本来扩展数据库。
对数据行进行缓存,具体的做法是:编写一个持续运行的守护进程函数,让这个函数将指定的数据行缓存到Redis里面,并不定期地对这些缓存进行更新。缓存函数会将数据行编码(encode)为JSON字典并存储在Redis的字符串里面,其中,数据列(column)的名字会被映射为JSON字典的键,而数据行的值则会被映射为JSON字典的值。
程序使用了两个有序集合来记录应该在何时对缓存进行更新:第一个有序集合为调度(schedule)有序集合,它的成员为数据行的行ID,而分值则是一个时间戳,这个时间戳记录了应该在何时将指定的数据行缓存到Redis里面;第二个有序集合为延时(delay)有序集合,它的成员也是数据行的行ID,而分值则记录了指定数据行的缓存需要每隔多少秒更新一次。
为了让缓存函数定期地缓存数据行,程序首先需要将行ID和给定的延迟值添加到延迟有序集合里面,然后再将行ID和当前时间的时间戳添加到调度有序集合里面。实际执行缓存操作的函数需要用到数据行的延迟值,如果某个数据行的延迟值不存在,那么这个调度商品将会被移除。如果想移除数据行已有的缓存,并且缓存函数也不再缓存这个数据行,那么可以把这个数据行的延迟值设置为值小于或等于0。
负责缓存数据行的函数会尝试读取调度有序集合的第一个元素以及该元素的分值,如果调度有序集合没有包含任何元素,或者分值存储的时间戳所指定的时间尚未来临,那么函数会先休眠50毫秒,然后再重新进行检查。当缓存函数发现一个需要立即进行更新的数据行时,缓存函数会检查这个数据行的延迟值:如果数据行的延迟值小于或者等于0,那么缓存函数会从延迟有序集合和调度有序集合里面移除这个数据行的ID,并从缓存里面删除这个数据行已有的缓存,然后再重新进行检查;对于延迟值大于0的数据行来说,缓存函数会从数据库里面取出这些行,将它们编码为JSON格式并存储到Redis里面,然后更新这些行的调度时间。
数据行下一次缓存的时间是,有序调度集合中的当前时间加上延迟集合的延迟值,并且有序更新调度集合的当前时间。
- 网页分析,寻找最热销的商品
在代表商品散列函数,每次浏览网页,商品的有序集合都会增加1.
限速
对于发送短信的场景,每次60S后才能重新发送短信,可以通过设置生存时间60S的字符串判断,如果字符串键存在,不会重新发生短信。
列表的场景
lpush+lpop=栈
lpush+ rpop=队列
lpush+ltrim=有限集合
lpush+brpop=消息队列
时间轴
集合
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,
除此之外,集合还能随机数生产,约等于抽奖
sadd+sinter =社交需求
sinter 集合的交集运算
有序集合
排行榜系统,如点赞榜