Redis学习(1)

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 基本指令

  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. 判断一个键是否存在 存在返回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> 
  1. 获得键值的数据类型
  • 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 命令
  1. 赋值与取值 SET KEY value GET KEY

  2. 递增数字 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 应用
  1. 文章访问量统计 使用名为post:文章ID:page.view的key来存储文章的访问量,每次访问使用INCR操作增加。

提示:命名没有强制的要求,但规范是用“对象类型:对象ID:对象属性”来命名一个键,如使用user:1:freinds来存储ID为1的用户的好友列表。多个单词推荐使用.来分割

  1. 生成自增ID
  2. 存储文章数据

发布新文章时与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 命令拾遗
  1. 增加指定的整数 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
  1. 减少指定的整数 DECR key DECRBY key decrement

  2. 增加指定浮点数 INCRBYFLOAT KEY FLOAT

  3. 向尾部追加值 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!"
  1. 获取字符串的长度 STRLEN key
127.0.0.1:6379> strlen sayHi
(integer) 12
  1. 同时获取/设置多个键值 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"
  1. 位操作 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 命令
  1. 赋值与取值 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)

  1. 判断字段是否存在 HEXISTS key field 存在返回1,不存在返回0

  2. 当字段不存在时赋值 HSETNX key field value 原子操作,不必担心竞态条件

  3. 增加数字 HINCRBY key field increment

127.0.0.1:6379> HGET car1 price 
"150000"
127.0.0.1:6379> HINCRBY car1 price 100000
(integer) 250000
  1. 删除字段 HDEL key field [field ...] 删除一个或者多个字段,返回的是被删除的字段个数。
3.3.3 实践
  1. 存储文章数据 散列类型相较于字符串类型来说更加灵活,比如存储一篇文章的数据,如果使用字符串类型,那么整个文章对象作为一个整体,每次获取都需要整个获取。而使用散列类型,将文章中的title、content类型分离为不同的字段,在更新和查询操作时候更加的灵活,减少了竞态条件的发生,以及减少了数据量的产生。 当然也可以使用多个字符串存储文章数据,如下。 enter image description here

而采用散列结构,其更加直观,也更容易维护。 enter image description here

  1. 存储文章缩略名
3.3.4 命令拾遗
  1. 只获取字段名或者字段值 HKEYS key HVALS key
  2. 获取字段数量 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 命令
  1. 向列表两端增加元素 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"
  1. 从列表两端弹出元素 LPOP key RPOP key POP表示移除并返回,POP之后元素就不存在列表中了、
127.0.0.1:6379> LPOP numbers
"3"
127.0.0.1:6379> LPOP numbers
"2"
  1. 获取列表中元素的个数 LLEN key 键不存在会返回0,时间复杂度为O(1)。不需要像SELECT COUNT(*)那样遍历整个数据表。
  2. 获得列表片段 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"
  1. 删除列表中指定的值 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 实践
  1. 存储文章ID列表 此做法的好处是,删除文章时,性能更高,可以很轻松的统计出文章的总数。细枝末节在此不一一学习了。
  2. 存储评论列表
3.4.4 命令拾遗
  1. 获取/设置指定索引的元素值 LINDEX key index LSET key index value
  2. 只保留列表指定片段 LTRIM key start stop 如只保存近100条的日志记录,可以:
LPUSH logs $newLog
LTRIM logs 0 99
  1. 向列表中插入元素 LINSERT key BEFORE|AFTER pivot value//pivot:中心点 从左到右查找pivot值的元素,根据BEFORE还是AFTER决定插入value到前还是后。
  2. 将元素从一个列表转到另一个列表 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 命令
  1. 增加/删除元素 SADD key member [member ...] SREM key member [member ...] 返回成功添加或者删除的元素个数

  2. 获得集合中的所有元素 SMEMBERS key

  3. 判断元素是否在集合中 SISMEMBER key member 存在返回1,不存在返回0,复杂度为O(1)

  4. 集合间运算

  • SDIFF key [key ...] 差集,SDIFF setA setB表示在A中取出A、B的公共元素。支持同时传入多个键,从从左到右的顺序依次计算。
  • SINTER key [key ...] 取交集,SINTER setA setB表示取出A、B的公共元素。支持多个键同时传入。
  • SUNION key [key ...] 取并集,支持同时传入多个键。
3.5.4 命令拾遗
  1. 获取元素个数 SCARD key

  2. 进行集合运算并将结果存储 SDIFFSTORE destination key [key ...] SINTERSTORE destination key [key ...]//insterction SUNIONSTORE destination key [key ...]

  3. 随机获取元素 SRANDMEMBER letters SRANDMEMBER key count 随机获取元素,count表示元素的数量

  • count为正,从集合取count个不重复的元素
  • count为负,可能重复的元素。
  1. 从集合中弹出一个元素 SPOP key 弹出后会从集合中去除

3.6 有序集合类型

3.6.1 介绍

有序集合在集合类型的基础上为集合中的每个元素关联了一个分数,按照分数进行排序。

有序集合在某些方面和列表类型有些相似: (1)二者都是有序的。 (2)都可以获得某一范围的元素 差异: (1)列表类型通过双线链表实现,获取靠近两端的数据速度极快,访问中间数据的速度慢,既增删快,访问慢。更加适合如“新鲜事”或“日志”这样很少访问中间元素的应用。 (2)有序集合使用的是散列表和跳跃表实现,即时访问中间的数据速度也很快。 (3)列表中不能简单的调整某个元素的位置,但是有序集合可以,只要更改这个元素的分数即可。 (4)有序集合比列表类型更加消耗内存

3.6.2 命令
  1. 增加元素 ZADD key score member [score member ...] 向有序集合中添加一个元素和该元素的分数,如果已经存在该元素,则更新。返回的是添加进集合的元素个数,不包括更新的元素。
127.0.0.1:6379> ZADD zset 86 mike 88 peter 89 jason#score可以是整数也可以是双精度浮点型,重复添加起到了更新数据的作用。
  1. 获取元素的分数 ZSCORE key member
127.0.0.1:6379> zscore zset peter
"88"
  1. 获取排名在某个范围的元素列表 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"
  1. 增加某个元素的分数 ZINCRBY key increment member 返回的是更改之后分数 负数表示减分。
3.6.3 实践
  1. 实现按点击量排序
  2. 改进按时间排序
3.6.4 命令拾遗
  1. 获取集合中元素的数量 ZCARD key
  2. 获取指定分数段的元素个数 ZCOUNT key min max **“(”**表示不包含,inf表示无穷大
  3. 删除一个或者多个元素 ZREM key member [member ...]
  4. 按照排名范围删除元素 ZREMRANGEBYCOUNT key start stop 排名从0开始。返回到是被删除的元素的个数
  5. 按照分数范围删除元素 ZREMRANGEBYSCORE key min max
  6. 获取元素的排名 ZRANK key member ZREVRANK key member
  7. 计算有序集合的交集 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 概述

MULTIEXEC指令

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
  1. 语法错误 执行之前会被发现,2.6之后版本,所有的语句将不会被记录
  2. 运行错误 运行时才发现,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外,GETGETSET指令也能修改过期时间为永久。此外重复的EXPIRE操作也能刷新生命周期。

其他的键INCRLPUSHHSETZREM均不会影响到键的生命周期。

PEXPIRE:毫秒单位的生命周期。

提示:生命到期并不会改变WATCH的监视状态。

4.2.2 实现访问频率限制之一

通过EXPIRE来实现,大概思路是:维护一个访问次数的变量rate:limiting:用户ID ,第一次访问设置生命周期为60秒,在这60秒内的访问次数不能大于设定的值。

4.2.4 实现缓存

redis有不同的缓存策略,可以通过修改配置文件中。maxmemorymaxmemory-policy参数确定删除哪一个键,从而降低内存的消耗。

Redis的缓存策略有:LRURANDOMTTLNOEVICTION

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"

DESCASC在此处同样适用,只需要加载语句的最后即可。

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:要返回到元素个数。

  1. 尽可能的减少待排序中元素的数量(减小n)
  2. 使用LIMIT参数只获取需要的数据(减小M)
  3. 如果要排序的数量较大,尽可能使用STORE参数将结果缓存

4.4 消息通知

4.4.1 任务队列

生产者-消费者模式。 任务队列的好处:

  1. 松耦合

  2. 易于扩展

4.4.2 使用Redis实现任务队列

指令: BRPOPBLPOP 阻塞弹出,当没有元素时一直阻塞,可以设置阻塞时间。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 优先队列

优先队列用于优先执行某一方面的任务。

BRPOPBLPOP可以传多个列表键,根据传入的先后顺序保证优先级。 比如:

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 发布/订阅模式

SUBSCRIBEPUBLISH

一个客户端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退订指定规则的频道,没有参数会退订所有的频道。

未完待续

转载于:https://my.oschina.net/vsimple/blog/1438942

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值