Redis学习(1)
REmote DIctionary Server:远程字典服务器,键值对存储,TCP协议传输。
1.2.1 存储结构
支持的键值对数据类型:
- 字符串类型
- 散列类型
- 列表类型
- 集合类型
- 有序集合类型
不同于MySql的二维表形式存储结构,数据在Redis中的存储形式和程序中存储方式很接近。
1.2.2 内存存储与持久化
同时存储于内存和硬盘中,解决了速度与持久化的问题。
Chapter 2 安装
偶数版本为稳定版:2.8、3.0
安装:
$ wget http://download.redis.io/releases/redis-4.0.0.tar.gz
$ tar xzf redis-4.0.0.tar.gz
$ cd redis-4.0.0
$ make
make install:将可运行指令复制到/usr/local/bin目录中,这样就不需要每次运行都进入安装目录了,相当于windows中的配置path路径。此操作需要sudo。
基本操作:
- redis-server:开启服务
- redis-cli:redis自带的命令行客户端
- redis-cli SHUTDOWN:正确的关闭方式
**注:**还可以通过修改配置文件的方式使redis随系统启动自动启动。
开始使用
redis-cli进入命令行环境。
wch@ubuntu:~/redis-4.0.0/src$ redis-cli
127.0.0.1:6379>
表示已经进入到redis的命令环境了。输入相应的命令即可。
wch@ubuntu:~/redis-4.0.0/src$ redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> echo hi
"hi"
127.0.0.1:6379>
数据库结构
首先应该明白,redis实例、数据库、变量的区别。
**redis实例:**每开启一次redis-cli相当于开启了一个redis实例。实例与实例之间相互隔离,redis实例相当轻量,一个redis实例之占1MB左右的内存。不同应用的实例确保保存在不同的redis实例中。
**数据库:**每个redis实例中默认可以支持16个数据库,可以通过配置参数databases修改这个数量。通过SELECT 0、1、2、3。。15来切换不同的数据库。不同的数据库之间并不是完全隔离,比如FLUSHALL指令能够删除统一redis实例中的所有数据库中的数据。
127.0.0.1:6379> config get databases#查看databases的配置,第一行是key。第二行是value
1) "databases"
2) "16"
127.0.0.1:6379> select 0#切换到第一个数据库,并set变量foo的值为test1
OK
127.0.0.1:6379> set foo test1
OK
127.0.0.1:6379> get foo
"test1"
127.0.0.1:6379> select 1#切换了数据库,此时再查看foo变量的值,显示的是nil,既空结果。
OK
127.0.0.1:6379[1]> get foo
(nil)
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> flushall#刷新了所有内存中的数据,此操作对所以数据库起作用,但对不同的redis实例不起作用。
OK
127.0.0.1:6379[1]> get foo
(nil)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> get foo
(nil)
127.0.0.1:6379>
**变量:**redis采用的是键值对的存储方式,既key-value对。
Chapter 3 入门
之前学习了如何安装以及一些基本的操作,下面将学习主要的数据类型以及相应的命令。
目前redis的应用主要是缓存和队列为主,此过程还是不可省略的,首相,通过redis-cli命令开启一个redis实例进行学习。
3.1 基本指令
- 获取符合规则的键名列表
- KEYS pattern:使用glob风格的通配符格式。
127.0.0.1:6379[1]> keys *#表示获取该数据库中的的所有数据的key,当前数据库有bar、foo两个数据
1) "bar"
2) "foo"
127.0.0.1:6379[1]>
注:redis中是不区分大小写的。
- 判断一个键是否存在 存在返回1,不存在返回0
127.0.0.1:6379> exists foo
(integer) 1
127.0.0.1:6379> exists faa
(integer) 0
3、删除键 可以删除一个或者多个,不支持通配符,**但是可以通过Linux的管道和xargs命令自己实现删除所有符合规则的键。
返回值是删除的键的个数
127.0.0.1:6379> keys *
1) "bar3"
2) "bar5"
3) "bar4"
4) "foo"
127.0.0.1:6379> del bar3 bar4
(integer) 2
127.0.0.1:6379> del bar5
(integer) 1
127.0.0.1:6379> del bar5
(integer) 0
127.0.0.1:6379> keys *
1) "foo"
127.0.0.1:6379>
- 获得键值的数据类型
- TYPE key
127.0.0.1:6379> set foo 1
OK
127.0.0.1:6379> type foo
string
127.0.0.1:6379> lpush bar 1
(integer) 1
127.0.0.1:6379> type bar
list
lpush表示向一个列表类型键中添加一个元素,如果键不存在中则创建它。
TYPE返回的类型一共有:
- string(字符串)
- hash(散列)
- list(列表)
- set(集合)
- zset(有序集合)
3.2 字符串类型
一些特质:
- reids中最基本的数据类型
- 存储任何形式的字符串,包括二进制数据,图片
- 一个字符串类型键允许存储的数据的最大容量是512M。
3.2.2 命令
-
赋值与取值
SET KEY value
GET KEY
-
递增数字
INCR key
此操作为原子操作(atomic operation)既多个客户端同时操作一个数据时,不会出现错误。
127.0.0.1:6379> set foo num
OK
127.0.0.1:6379> incr foo#foo为string类型,会报错。INCR操作只支持数字形式的字符串。
(error) ERR value is not an integer or out of range
127.0.0.1:6379> incr num1#如果不存在key,则创建一个,初始化为0,因此首次递增之后是1
(integer) 1
127.0.0.1:6379> incr num2
(integer) 1
3.2.3 应用
- 文章访问量统计 使用名为
post:文章ID:page.view
的key来存储文章的访问量,每次访问使用INCR
操作增加。
提示:命名没有强制的要求,但规范是用“对象类型:对象ID:对象属性”来命名一个键,如使用user:1:freinds来存储ID为1的用户的好友列表。多个单词推荐使用
.
来分割
- 生成自增ID
- 存储文章数据
发布新文章时与redis操作相关的伪代码:
#首先获取新文章的ID
$postID = INCR posts:count
#将博客文章的诸多元素序列化成字符串
$serializedPost = serialize($title,$content,$author,$time)#serialize为PHP中将对象转为字符串的操作。相当于Javascript中的stringify操作。
#把序列化候的字符串存入一个字符串类型键中
SET post:$postID:data, $serializedPost
获取文章数据的伪代码如下
#从redis中读取文章数据
$serializedPost = GET post:42:data
#将文章数据反序列化成文章的各个元素
$title, $content, $author, $time = unserialize($serializedPost)
#获取并递增文章的访问数量
$count = INCR post:42:page.view
除了使用序列化函数将文章的多个元素存入一个字符串类型键中外,还可以对每个元素使用一个字符串类型键来存储,这种方法将在3.3.3节
中进行学习。
3.2.4 命令拾遗
- 增加指定的整数
INCRBY key increment
127.0.0.1:6379> get num2
"4"
127.0.0.1:6379> incrby num2 5#将num2 = 4再加上5,等于9
(integer) 9
-
减少指定的整数
DECR key
DECRBY key decrement
-
增加指定浮点数
INCRBYFLOAT KEY FLOAT
-
向尾部追加值
APPEND key value
127.0.0.1:6379> set sayHi hello
OK
127.0.0.1:6379> append sayHi " world!"
(integer) 12
127.0.0.1:6379> get sayHi
"hello world!"
- 获取字符串的长度
STRLEN key
127.0.0.1:6379> strlen sayHi
(integer) 12
- 同时获取/设置多个键值
MGET KEY [KEY ...]
MSET key value [key value ...]
127.0.0.1:6379> mset key1 v1 key2 v2 key3 v3
OK
127.0.0.1:6379> get key2
"v2"
127.0.0.1:6379> mget key1 key2 key3
1) "v1"
2) "v2"
3) "v3"
- 位操作
GETBIT key offset
SETBIT key offset value
BITCOUNT key [start] [end]
//获取字符串类型键中值是1的二进制位个数BITOP operation destkey key [key ...]
比如:foo = “bar”的二进制存储是:01100010 01100001 01110010
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> getbit foo 0
(integer) 0
127.0.0.1:6379> getbit foo 1
(integer) 1
127.0.0.1:6379> getbit foo 2
(integer) 1
127.0.0.1:6379> getbit foo 3
(integer) 0
127.0.0.1:6379> getbit foo 4
(integer) 0
127.0.0.1:6379> getbit foo 5
(integer) 0
127.0.0.1:6379> getbit foo 6
(integer) 1
127.0.0.1:6379> getbit foo 7
(integer) 0
127.0.0.1:6379> bitcount foo
(integer) 10
BITOP: 位操作,AND、OR、XOR、NOT
127.0.0.1:6379> SET foo1 bar
OK
127.0.0.1:6379> SET foo2 aar
OK
127.0.0.1:6379> BITOP OR result foo1 foo2
(integer) 3
127.0.0.1:6379> GET result
"car"
BITPOS:redis2.8.7之后引入的操作,获取第一个二进制只0或1的位置。
127.0.0.1:6379> GET result
"car"
127.0.0.1:6379> BITPOS result 1
(integer) 1
127.0.0.1:6379> BITPOS result 0
(integer) 0
利用位操作可以非常紧凑地存储布尔值,记录100万个用户的性别只需要占用100kb多的空间,而且由于GETBIT和SETBIT的时间复杂度是O(1),性能也很好。
3.3 散列类型
3.3.1 介绍
散列类型的值又是一种字典结构,存储了字段(field)和字段值的映射。值得注意的是:字段值只能是字符串,不支持其他的数据类型,既散列类型不能嵌套其他的类型数据。一个散列类型键可以包含2^32-1个字段(属性)。
提示:除了散列类型,Redis的其他数据类型同样不支持数据类型的嵌套。比如集合类型的每个元素只能是字符串,不能是另一个集合或散列表等。
散列类型的适用于:一个实体,有多重属性,每个属性名和字段对应,每个属性值和字段值对应。相较于关系型数据库,同一类的实体,其属性结构更加灵活,不存在字段冗余。
3.3.2 命令
- 赋值与取值
HSET key field value
HGET key field
HMSET key field value [field value ...]
HMGET key field [field ...]
HGETALL key
需求:car1对象,具有color、price、name属性。car2对象,具有color、price、name、engine属性。
127.0.0.1:6379> HSET car1 color red price 100000 name BMW
(integer) 3
127.0.0.1:6379> HSET car2 color black price 200000 name BENZ engine turbine
(integer) 4
127.0.0.1:6379> HGETALL car1
1) "color"
2) "red"
3) "price"
4) "100000"
5) "name"
6) "BMW"
127.0.0.1:6379> HGETALL car2
1) "color"
2) "black"
3) "price"
4) "200000"
5) "name"
6) "BENZ"
7) "engine"
8) "turbine"
HSET操作不区分插入和更新操作,在返回值上进行区别(插入返1,更新返0)
-
判断字段是否存在
HEXISTS key field
存在返回1,不存在返回0 -
当字段不存在时赋值
HSETNX key field value
原子操作,不必担心竞态条件 -
增加数字
HINCRBY key field increment
127.0.0.1:6379> HGET car1 price
"150000"
127.0.0.1:6379> HINCRBY car1 price 100000
(integer) 250000
- 删除字段
HDEL key field [field ...]
删除一个或者多个字段,返回的是被删除的字段个数。
3.3.3 实践
- 存储文章数据 散列类型相较于字符串类型来说更加灵活,比如存储一篇文章的数据,如果使用字符串类型,那么整个文章对象作为一个整体,每次获取都需要整个获取。而使用散列类型,将文章中的title、content类型分离为不同的字段,在更新和查询操作时候更加的灵活,减少了竞态条件的发生,以及减少了数据量的产生。 当然也可以使用多个字符串存储文章数据,如下。
而采用散列结构,其更加直观,也更容易维护。
- 存储文章缩略名
3.3.4 命令拾遗
- 只获取字段名或者字段值
HKEYS key
HVALS key
- 获取字段数量
HLEN key
127.0.0.1:6379> HKEYS car1
1) "color"
2) "price"
3) "name"
127.0.0.1:6379> HVALS car1
1) "black"
2) "250000"
3) "BMW"
127.0.0.1:6379> HLEN car1
(integer) 3
127.0.0.1:6379> HLEN car2
(integer) 4
3.4 列表类型
3.4.1 介绍
- 有序的字符串列表
- 双向链表结构,常用的操作是在头尾添加和删除,或者获取列表某一片段的元素。
- 一个列表类型键最多容纳2^32-1个元素。
3.4.2 命令
- 向列表两端增加元素
LPUSH key value [value ...]
向列表左端增加元素RPUSH key value [value ...]
向列表右端增加元素 同时还支持一次性添加多个元素
127.0.0.1:6379> LPUSH numbers 1#左添加1
(integer) 1
127.0.0.1:6379> LPUSH numbers 2 3#左添加2,3 先添加2,再左添加3.列表中为3,2
(integer) 3
127.0.0.1:6379> RPUSH numbers 4 5#右添加4,5 先添加4,再右添加5。列表中为4,5
(integer) 5
127.0.0.1:6379> LRANGE numbers 0 8#列表中的元素顺序是:3,2,1,4,5
1) "3"
2) "2"
3) "1"
4) "4"
5) "5"
- 从列表两端弹出元素
LPOP key
RPOP key
POP表示移除并返回,POP之后元素就不存在列表中了、
127.0.0.1:6379> LPOP numbers
"3"
127.0.0.1:6379> LPOP numbers
"2"
- 获取列表中元素的个数
LLEN key
键不存在会返回0,时间复杂度为O(1)。不需要像SELECT COUNT(*)那样遍历整个数据表。 - 获得列表片段
LRANGE key start stop
起始从0开始,返回某一段的元素列表。包头且包尾。
127.0.0.1:6379> LLEN numbers
(integer) 3
127.0.0.1:6379> LRANGE numbers 0 2
1) "1"
2) "4"
3) "5"
127.0.0.1:6379> LRANGE numbers -2 -1
1) "4"
2) "5"
也支持负索引,负数表示从左边开始计数。-1表示最后一个,-2表示倒数第二个,以此类推。 获取整个列表的方法。 LRANGE key 0 -1
,或者stop序列取大于长度的值
127.0.0.1:6379> LRANGE numbers 0 -1
1) "1"
2) "4"
3) "5"
- 删除列表中指定的值
LREM key count value
删除列表中前count个值中值为value的元素,返回值是实际删除的元素的个数。根据count的值的不同,LREM的执行方式略有差异:
- count>0时,从左边开始删除。
- count<0时,从右边开始删除
- count=0时,删除整个列表中value的值。
127.0.0.1:6379> LRANGE nums 0 -1
1) "0"
2) "1"
3) "1"
4) "1"
5) "1"
6) "1"
7) "2"
8) "2"
9) "2"
10) "2"
11) "2"
12) "2"
127.0.0.1:6379> LREM nums 4 1#从左删除5个(从0开始,因此是5个)中值为1的元素,一共有4个为1,因此删除了4个
(integer) 4
127.0.0.1:6379> LRANGE nums 0 -1
1) "0"
2) "1"
3) "2"
4) "2"
5) "2"
6) "2"
7) "2"
8) "2"
127.0.0.1:6379> LREM nums -5 2#从右起删除5个(-1到-5,一共5个)元素中值为2的元素
(integer) 5
127.0.0.1:6379> LRANGE nums 0 -1
1) "0"
2) "1"
3) "2"
127.0.0.1:6379> LRANGE nums 0 -1
1) "1"
2) "2"
127.0.0.1:6379> LREM nums 0 1#删除整个列表中值为1 的元素
(integer) 1
127.0.0.1:6379> LRANGE nums 0 -1
1) "2"
3.4.3 实践
- 存储文章ID列表 此做法的好处是,删除文章时,性能更高,可以很轻松的统计出文章的总数。细枝末节在此不一一学习了。
- 存储评论列表
3.4.4 命令拾遗
- 获取/设置指定索引的元素值
LINDEX key index
LSET key index value
- 只保留列表指定片段
LTRIM key start stop
如只保存近100条的日志记录,可以:
LPUSH logs $newLog
LTRIM logs 0 99
- 向列表中插入元素
LINSERT key BEFORE|AFTER pivot value
//pivot:中心点 从左到右查找pivot值的元素,根据BEFORE还是AFTER决定插入value到前还是后。 - 将元素从一个列表转到另一个列表
RPOPLPUSH source destination
RPOP+LPUSH。先右弹出元素,在左添加到目标列表。(一次只对一个元素进行操作)
127.0.0.1:6379> rpush source 0
(integer) 1
127.0.0.1:6379> rpush source 1
(integer) 2
127.0.0.1:6379> rpush source 2
(integer) 3
127.0.0.1:6379> lrange source 0 -1
1) "0"
2) "1"
3) "2"
127.0.0.1:6379> rpush des 3
(integer) 1
127.0.0.1:6379> rpush des 4
(integer) 2
127.0.0.1:6379> rpush des 5
(integer) 3
127.0.0.1:6379> lrange des 0 -1
1) "3"
2) "4"
3) "5"
127.0.0.1:6379> rpoplpush source des#一次只对一个元素进行了操作,并返回这个元素的值
"2"
127.0.0.1:6379> lrange des 0 -1
1) "2"
2) "3"
3) "4"
4) "5"
**注意:**当source和destination相同时会发生什么? 将队尾的元素不断放置到队首。
127.0.0.1:6379> lrange des 0 -1
1) "2"
2) "3"
3) "4"
4) "5"
127.0.0.1:6379> rpoplpush des des
"5"
127.0.0.1:6379> rpoplpush des des
"4"
127.0.0.1:6379> rpoplpush des des
"3"
127.0.0.1:6379> lrange des 0 -1
1) "3"
2) "4"
3) "5"
4) "2"
3.5 集合类型
集合类型:无序,唯一,2^32-1个元素。 内部使用了值为空的散列表,既字段值为空,因此添加和删除的复杂度为O(1)。
3.5.2 命令
-
增加/删除元素
SADD key member [member ...]
SREM key member [member ...]
返回成功添加或者删除的元素个数 -
获得集合中的所有元素
SMEMBERS key
-
判断元素是否在集合中
SISMEMBER
key member 存在返回1,不存在返回0,复杂度为O(1) -
集合间运算
SDIFF key [key ...]
差集,SDIFF setA setB
表示在A中取出A、B的公共元素。支持同时传入多个键,从从左到右的顺序依次计算。SINTER key [key ...]
取交集,SINTER setA setB
表示取出A、B的公共元素。支持多个键同时传入。SUNION key [key ...]
取并集,支持同时传入多个键。
3.5.4 命令拾遗
-
获取元素个数
SCARD key
-
进行集合运算并将结果存储
SDIFFSTORE destination key [key ...]
SINTERSTORE destination key [key ...]
//insterctionSUNIONSTORE destination key [key ...]
-
随机获取元素
SRANDMEMBER letters
SRANDMEMBER key count
随机获取元素,count表示元素的数量
- count为正,从集合取count个不重复的元素
- count为负,可能重复的元素。
- 从集合中弹出一个元素
SPOP key
弹出后会从集合中去除
3.6 有序集合类型
3.6.1 介绍
有序集合在集合类型的基础上为集合中的每个元素关联了一个分数,按照分数进行排序。
有序集合在某些方面和列表类型有些相似: (1)二者都是有序的。 (2)都可以获得某一范围的元素 差异: (1)列表类型通过双线链表实现,获取靠近两端的数据速度极快,访问中间数据的速度慢,既增删快,访问慢。更加适合如“新鲜事”或“日志”这样很少访问中间元素的应用。 (2)有序集合使用的是散列表和跳跃表实现,即时访问中间的数据速度也很快。 (3)列表中不能简单的调整某个元素的位置,但是有序集合可以,只要更改这个元素的分数即可。 (4)有序集合比列表类型更加消耗内存。
3.6.2 命令
- 增加元素
ZADD key score member [score member ...]
向有序集合中添加一个元素和该元素的分数,如果已经存在该元素,则更新。返回的是添加进集合的元素个数,不包括更新的元素。
127.0.0.1:6379> ZADD zset 86 mike 88 peter 89 jason#score可以是整数也可以是双精度浮点型,重复添加起到了更新数据的作用。
- 获取元素的分数
ZSCORE key member
127.0.0.1:6379> zscore zset peter
"88"
- 获取排名在某个范围的元素列表
ZRANGE key start stop [WITHSCORES]
//正序ZREVRANGE key start stop [WIHSCORES]
//逆序 如需获取排名的分数。加上withscores参数。
时间复杂度是O(logN+M),N为有序集合的元素数,M为返回的元素个数。分数相同按照自然顺序排序,中文按照具体的编码方式。 4. 获取指定分数范围的元素 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> zrangebyscore zset 88 89 withscores
1) "peter"
2) "88"
3) "jason"
4) "89"
127.0.0.1:6379> zrangebyscore zset 88 (89 withscores#加(表示不包括该分数
1) "peter"
2) "88"
127.0.0.1:6379> zrangebyscore zset (88 89 withscores
1) "jason"
2) "89"
score还支持无穷大,inf表示无穷大
127.0.0.1:6379> zrangebyscore zset -inf +inf#正无穷到负无穷
1) "mike"
2) "peter"
3) "jason"
0 -1 表示获取全部的集合元素,LIMIT 1 3表示获取符合条件的第2个(1)元素开始的3(3)个元素。
127.0.0.1:6379> zrange zset 0 -1
1) "mike"
2) "peter"
3) "jason"
127.0.0.1:6379> zrangebyscore zset -inf +inf limit 1 3
1) "peter"
2) "jason"
- 增加某个元素的分数
ZINCRBY key increment member
返回的是更改之后分数 负数表示减分。
3.6.3 实践
- 实现按点击量排序
- 改进按时间排序
3.6.4 命令拾遗
- 获取集合中元素的数量
ZCARD key
- 获取指定分数段的元素个数
ZCOUNT key min max
**“(”**表示不包含,inf表示无穷大 - 删除一个或者多个元素
ZREM key member [member ...]
- 按照排名范围删除元素
ZREMRANGEBYCOUNT key start stop
排名从0开始。返回到是被删除的元素的个数 - 按照分数范围删除元素
ZREMRANGEBYSCORE key min max
- 获取元素的排名
ZRANK key member
ZREVRANK key member
- 计算有序集合的交集
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
- destination:目标有序集合
- numskey:参与运算的键的个数
- key:键名
- WEIGHTS:每个键的权重,既每个集合中的元素在运算中所占的权重
- AGGREGATE:运算方式,默认为SUM求和运算。其次是最小值和最大值
127.0.0.1:6379> zadd ss1 1 a 2 b 3 c
(integer) 3
127.0.0.1:6379> zadd ss2 10 a 20 b 30 c
(integer) 3
127.0.0.1:6379> zinterstore ss3 2 ss1 ss2 weight 0.1 1
(error) ERR syntax error
127.0.0.1:6379> zinterstore ss3 3 ss1 ss2 weight 0.1 1
(error) ERR syntax error
127.0.0.1:6379> zinterstore ss3 2 ss1 ss2 weights 0.1 1
(integer) 3
127.0.0.1:6379> zrange 0 -1 ss3
(error) ERR value is not an integer or out of range
127.0.0.1:6379> zrange ss3 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379> zrange ss3 0 -1 withscores
1) "a"
2) "10.1"
3) "b"
4) "20.199999999999999"
5) "c"
6) "30.300000000000001"
127.0.0.1:6379> zinterstore ss3 2 ss1 ss2 weights 1 0.1
(integer) 3
127.0.0.1:6379> zrange ss3 0 -1 withscores
1) "a"
2) "2"
3) "b"
4) "4"
5) "c"
6) "6"
Chapter 4 进阶
4.1 事务
利用Redis解决关注与被关注的伪代码逻辑
127.0.0.1:6379> sadd user:1:following 2
(integer) 1
127.0.0.1:6379> sadd user:2:follower 1
(integer) 1
如果在执行第一段代码中,程序被终止。则会出现用户1关注了2,但是在2的列表中并没有1的情况发生,解决此类问题的办法就是事务。
4.1.1 概述
MULTI、EXEC指令
127.0.0.1:6379> multi#进入事务
OK
127.0.0.1:6379> sadd user:1:following 2
QUEUED
127.0.0.1:6379> sadd user:2:follower 1
QUEUED
127.0.0.1:6379> exec#跳出事务
1) (integer) 0
2) (integer) 0
两个操作进入queued队列中等待exec指令之后,依次执行。保证了执行的连续性。
发生错误怎么办?
MULTI
sentenceA
sentenceB
sentenceC
EXEC
- 语法错误 执行之前会被发现,2.6之后版本,所有的语句将不会被记录
- 运行错误 运行时才发现,redis不提供回滚机制(简洁、快速),正确的语句被执行。需要开发人员手动进行纠错。因此在执行事务时候应尽量避免运行错误。
4.1.3 WATCH命令介绍
WATCH
命令可以监控一个或多个键,一旦其中一个键被修改(或删除),之后的事务就不会再执行了。监控一直持续到EXEC
命令执行、
127.0.0.1:6379> SET key 1
OK
127.0.0.1:6379> WATCH key#监控key的值的变化
OK
127.0.0.1:6379> SET key 2#发生修改,之后事务不起作用,直至EXEC指令,结束监控
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key 3
QUEUED
127.0.0.1:6379> EXEC#事务不起作用,返回空
(nil)
127.0.0.1:6379> GET key#此时key值仍然是2
"2"
127.0.0.1:6379> MULTI#事务再次修改,此时已经跳出监控状态,修改成功
OK
127.0.0.1:6379> SET key 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
127.0.0.1:6379> GET key
"3"
提示:由于WATCH命令只是当被监控的键值被修改后阻止之后的一个事务的执行,而不能保证其他客户端不修改这一键值,所以我们需要在EXEC执行失败之后重新执行整个函数。既重新进行监控。
如果不需要监控某个键值的值,可以使用UNWATCH
指令来保证下一个事务不收到影响。
4.2 过期时间
4.2.1 命令介绍
EXPIRE
指令设置一个键值的过期时间。到期redis自动删除他。 EXPIRE key seconds
单位是秒,例如如下代码:
127.0.0.1:6379> SET session user
OK
127.0.0.1:6379> EXPIRE session 60#设置过期时间60秒,返回1
(integer) 1
127.0.0.1:6379> ttl session#返回剩余生命周期,秒
(integer) 55
127.0.0.1:6379> ttl session
(integer) 51
127.0.0.1:6379> SET session2 user2
OK
127.0.0.1:6379> ttl session2#返回-1表示,永远不过期
(integer) -1
127.0.0.1:6379> ttl session#已过期,返回-2
(integer) -2
PERSIST
表示将键重新设置为永久的,成功返回1,否则返回0(键不存在或者本身就是永久的。)
127.0.0.1:6379> persist session
(integer) 0
127.0.0.1:6379> persist session2
(integer) 0
127.0.0.1:6379> expire session2 20
(integer) 1
127.0.0.1:6379> persist session2
(integer) 1
127.0.0.1:6379> ttl session2
(integer) -1
除了PERSIST外,GET
、GETSET
指令也能修改过期时间为永久。此外重复的EXPIRE
操作也能刷新生命周期。
其他的键INCR
、LPUSH
、HSET
、ZREM
均不会影响到键的生命周期。
PEXPIRE
:毫秒单位的生命周期。
提示:生命到期并不会改变WATCH的监视状态。
4.2.2 实现访问频率限制之一
通过EXPIRE来实现,大概思路是:维护一个访问次数的变量rate:limiting:用户ID
,第一次访问设置生命周期为60秒,在这60秒内的访问次数不能大于设定的值。
4.2.4 实现缓存
redis有不同的缓存策略,可以通过修改配置文件中。maxmemory
、maxmemory-policy
参数确定删除哪一个键,从而降低内存的消耗。
Redis的缓存策略有:LRU
、RANDOM
、TTL
、NOEVICTION
4.3 排序
4.3.1 有序集合的集合操作
有序集合只有ZINTERSTORE和ZUNIONSTORE,没有ZINTER和ZUNION。
可以自己实现ZINTER(既直接返回结果)
MULTI
ZINTERSTORE tempkey ...
ZRANGE tempkey ...
DEL tempkey
EXEC
4.3.2 SORT命令
SORT
命令可以对列表类型、集合类型、有序集合类型键进行排序
对有序集合排序时会忽略元素的分数,而直接对元素的自然顺序进行排序。
对字符串排序需要加入ALPHA
参数
127.0.0.1:6379> sort myzset alpha
1) "abandon"
2) "jaki"
3) "john"
4) "kury"
5) "lucy"
6) "mik"
7) "miki"
8) "shane"
9) "tomson"
DESC
和ASC
在此处同样适用,只需要加载语句的最后即可。
LIMIT
在此处也同样适用,当需要分页时可以使用。LIMIT n m
表示跳过之前的n个数据,取m个数据出来
127.0.0.1:6379> sort myzset alpha asc
1) "abandon"
2) "jaki"
3) "john"
4) "kury"
5) "lucy"
6) "mik"
7) "miki"
8) "shane"
9) "tomson"
127.0.0.1:6379> sort myzset alpha asc limit 2 5
1) "john"
2) "kury"
3) "lucy"
4) "mik"
5) "miki"
4.3.3 BY参数
当需要按照某个指定的属性进行排序时,SORT BY
就登场了。
语法:BY参考键。参考键可以是字符串类型或者是散列类型键 的某个字段(表示为键名->字段名),如果提供了BY参数键,SORT将不再按照元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中第一个“*”并获取其值,然后依据该值对元素排序。
BY参数的语法为“BY参考键”。其中参考键可以是字符串类型键或者是散列类型键的某个字段(表示为键名—>字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。 应该时刻记住,除了hash类型外,其余类型不支持类型嵌套。
4.3.4 GET参数
SORT命令的GET参数,使得排序之后不再返回元素的值,而是可以GET
元素的某个属性,GET和BY参数一样,支持字符串类型和散列类型的键,并使用“*”作为占位符。
*记住一句话,是占位符,排序的时候将被替换掉。
多个GET还可以叠加使用,就好像select a.name b.name from table1 a, table b sort by a.time
一样。
在设定了GET参数之后还可以通过GET #
表示返回元素本身的值。
SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET #
4.3.5 STORE参数
STORE参数表示将排序结果保存到sort.result中。 SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # STORE sort.result
保存后的类型为列表类型
4.3.6 性能优化
SOTR的时间复杂度是O(n+mlogm),n:需要排序的元素个数。m:要返回到元素个数。
- 尽可能的减少待排序中元素的数量(减小n)
- 使用LIMIT参数只获取需要的数据(减小M)
- 如果要排序的数量较大,尽可能使用STORE参数将结果缓存。
4.4 消息通知
4.4.1 任务队列
生产者-消费者模式。 任务队列的好处:
-
松耦合
-
易于扩展
4.4.2 使用Redis实现任务队列
指令: BRPOP
、BLPOP
阻塞弹出,当没有元素时一直阻塞,可以设置阻塞时间。0表示一直阻塞。 如:
127.0.0.1:6379> BLPOP queue 5#没有元素,则阻塞5秒后返回
(nil)
(5.04s)
127.0.0.1:6379> BLPOP queue 0#一直阻塞的状态,直到另一个redis-cli实例向任务队列添加元素,才会输出。
1) "queue"
2) "hello"
(20.21s)
4.4.3 优先队列
优先队列用于优先执行某一方面的任务。
BRPOP、BLPOP可以传多个列表键,根据传入的先后顺序保证优先级。 比如:
127.0.0.1:6379> lpush queue1 queue1
(integer) 1
127.0.0.1:6379> lpush queue2 queue2
(integer) 1
127.0.0.1:6379> brpop queue2 queue1 0#先弹出的是queue2的元素,因为它在BRPOP参数的前面。
1) "queue2"
2) "queue2"
4.4.4 发布/订阅模式
SUBSCRIBE
和PUBLISH
一个客户端SUBSCRIBE
一个频道之后,进入接受消息的状态。一个客户端可同时订阅多个channel
另一客户端可以通过PUBLISH
向指定的channel发送消息,所有订阅该channel的客户端会受到该消息。
使用UNSUBSCRIBE
取消订阅。
客户端1: SUBSCRIBE channel.1
客户端2: PUBLISH channel.1 hi#向频道发送消息,此时订阅了该频道的客户端会接受到该message类型的消息。
客户端3: SUBSCRIBE channel.1#此时再订阅之前的消息并不会读取,只有再次有客户端向该频道发送消息时才会显示。 UNSUSCRIBE channel.1#取消订阅该频道
4.4.5 按照规则订阅
PSUBSCRIBE,订阅指定的规则,规则支持glob风格通配符格式。
127.0.0.1:6379> psubscribe channel.?*#该条语句订阅了无数个频道,规则是只要是channel.开头的频道都可以。因此不管向那个符合规则的频道发布消息,都会被接收到。
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "channel.?*"
3) (integer) 1
1) "pmessage"
2) "channel.?*"
3) "channel.2"
4) "hi"
1) "pmessage"
2) "channel.?*"
3) "channel.233"
4) "ho"
1) "pmessage"
2) "channel.?*"
3) "channel.2883738"
4) "nihao"
1) "pmessage"
2) "channel.?*"
3) "channel.23738"
4) "dajiahao"
PUNSUBSCRIBE
退订指定规则的频道,没有参数会退订所有的频道。
未完待续