[TOC]

0x00 Redis 介绍

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

它通常被称为数据结构服务器它有五种类型值(value): 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和有序集合(sorted sets)等类型。

Redis(K-V缓存产品)特点:

  • 非关系型数据库
  • 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • 支持数据的备份即master-slave主从模式的数据备份。

Redis优势:

  • 是一个开源的 key-value 存储系统,并且性能高体现在IO读写(R>W)
  • Redis非常轻量级,一个空Redis实例占用的内在只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。
  • 丰富得数据类型(String/Hash/List/sets/Sorted sets)
  • 所有得操作都是原子性得(要么成功要么失败完全不执行),且多个操作支持事务即原子性(通过MULTI和EXEC指令包起来)

Redis与其他K-V存储异同:

  • R有更为复杂得数据结构并提供事务处理机制(原子性操作)
  • R运行在内存中但是可以持久化到磁盘之中,在对数据集进行高速读写时需要权衡内存(数据量不能大于硬件内存)
  • 在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问


0x01 Redis 数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(Sorted set:有序集合)。

  1. Redis键(Key)
    描述:键命令用于管理 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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    127.0.0.1:6379> keys p*  #查找所有符合给定模式 pattern 的 key 
    1) "python"
    2) "php"

    127.0.0.1:6379> set test redis #设置键
    127.0.0.1:6379> del test #删除键
    (integer) 1

    127.0.0.1:6379> type php #返回 key 所储存的值的类型
    string
    127.0.0.1:6379> exists test #检查给定 key 是否存在。
    (integer) 1
    127.0.0.1:6379> dump test #序列化给定 key,返回序列化之后的值
    "\x00\x05redis\t\x00\x15\xa2\xf8=\xb6\xa9\xde\x90"
    127.0.0.1:6379> MOVE test 1 #将 test移动到数据库 1 中(select 1)
    (integer) 1


    127.0.0.1:6379> expire python 600 #expire test1 60 #设置 key 的过期时间,key 过期后将不再可用。单位以秒计
    (integer) 1
    127.0.0.1:6379> PTTL python #以毫秒为单位返回 key 的剩余的过期时间
    (integer) 592072
    127.0.0.1:6379> TTL python #以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)
    (integer) 583
    127.0.0.1:6379> EXPIREAT runoobkey 1293840000 #以 UNIX 时间戳(unix timestamp)格式设置 key 的过期时间
    (integer) 1
    127.0.0.1:6379> pexpire test 6000 #设置key过期毫秒数
    (integer) 1
    127.0.0.1:6379> PEXPIREAT runoobkey 1555555555005 #unix类型毫秒数
    (integer) 1
    127.0.0.1:6379> persist test #移除 key 的生存时间


    127.0.0.1:6379> randomkey #从当前数据库中随机返回一个 key 。 "php"
    127.0.0.1:6379> rename key python #修改 key 的名称 OK
    127.0.0.1:6379> renamenx key newksy #仅当 newkey 不存在时,将 key 改名为 newkey 。存在则返回0
    (integer) 1


  1. String(字符串)
    String 是 redis 最基本的类型(是二进制安全的),且字符串类型的值最大能存储 512MB。
    (1)SET key value #设置指定 key 的值
    (2)GET key #获取指定 key 的值。
    (3)GETRANGE key start end #返回 key 中字符串值的子字符
    (4)GETSET key value #将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
    (5)GETBIT key offset #对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
    (6)MGET key1 [key2..] #获取所有(一个或多个)给定 key 的值。
    (7)SETBIT key offset value #对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
    (8)SETEX key seconds value #将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
    (9)SETNX key value #只有在 key 不存在时设置 key 的值。
    (10)SETRANGE key offset value #用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
    (11)STRLEN key #返回 key 所储存的字符串值的长度。
    (12)MSET key value [key value …] #同时设置一个或多个 key-value 对。
    (13)MSETNX key value [key value …] #同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
    (14)PSETEX key milliseconds value #这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
    (15)INCR key #将 key 中储存的数字值增一。
    (16)INCRBY key increment #将 key 所储存的值加上给定的增量值(increment) 。
    (17)INCRBYFLOAT key increment #将 key 所储存的值加上给定的浮点增量值(increment) 。
    (18)DECR key #将 key 中储存的数字值减一。
    (19)DECRBY key decrement #key 所储存的值减去给定的减量值(decrement) 。
    (20)APPEND key value #如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
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
redis 127.0.0.1:6379> SET name "runoob"
redis 127.0.0.1:6379> GET name #"runoob"
127.0.0.1:6379> type name #string

127.0.0.1:6379> set test stringdemo #ok
127.0.0.1:6379> getrange test 0 4 #"strin" 截取字符串
127.0.0.1:6379> strlen test #该键得value字符串长度(integer) 10
127.0.0.1:6379> getset stringdemo stringdemo #"test" 获取值然后再设置键值

127.0.0.1:6379> setex demo 15 "value15seconds" #OK 设置键值15s后销毁 #"seconds15"
127.0.0.1:6379> psetex key4 10000 "value10seconds" #OK 设置键值10s后销毁 (毫秒)

127.0.0.1:6379> setnx demo1 demo1 # (integer) 1 只有在 key 不存在时设置 key 的值。
127.0.0.1:6379> setrange demo1 2 weiyigeek #(integer) 11 从0开始偏移插入在2处 "deweiyigeek"
127.0.0.1:6379> APPEND key1 valuakey1 #(integer) 15 在key得value后添加值
127.0.0.1:6379> get key1
"value1valuakey1"

127.0.0.1:6379> mset key1 value1 key2 value2 #设置多个键值 OK
127.0.0.1:6379> mget key1 key2 #获取多个键值
1) "value1"
2) "value2"

127.0.0.1:6379> msetnx key2 value2 key3 value3 #设置多个键值当键不存在则返回1,否则存在不建立键值并返回0

########## 字符串计算 ###########
127.0.0.1:6379> set key5 1 #OK
127.0.0.1:6379> incr key5
(integer) 2
127.0.0.1:6379> incr key5
(integer) 3
127.0.0.1:6379> incrby key5 3
(integer) 6
127.0.0.1:6379> incrbyfloat key5 4.5 #注意浮点类型计算只能在incrbyfloat才行
"10.5"

127.0.0.1:6379> set key6 10 #OK
127.0.0.1:6379> decr key6
(integer) 9
127.0.0.1:6379> decrby key6 5
(integer) 4
127.0.0.1:6379> strlen key1
(integer) 15


# getbit/setbit 用于对 key 所储存的字符串值,获取指定设置偏移量上的位(bit)。
# 对不存在的 key 或者不存在的 offset 进行 GETBIT, 返回 0
redis> EXISTS bit
(integer) 0
redis> GETBIT bit 10086
(integer) 0

# 对已存在的 offset 进行 GETBIT
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1


  1. Hash(哈希)
    描述:hash 是一个键值(key=>value)对集合,是一个string类型的 field 和 value 的映射表,hash 特别适合用于存储对象,每个 hash 可以存储 2^32 -1键值对(40多亿)。

(1) HDEL key field1 [field2] #删除一个或多个哈希表字段
(2) HEXISTS key field #查看哈希表 key 中,指定的字段是否存在。
(3) HGET key field #获取存储在哈希表中指定字段的值。
(4) HGETALL key #获取在哈希表中指定 key 的所有字段和值
(5) HINCRBY key field increment #为哈希表 key 中的指定字段的整数值加上增量 increment 。
(6) HINCRBYFLOAT key field increment #为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
(7) HKEYS key #获取所有哈希表中的字段
(8) HLEN key #获取哈希表中字段的数量
(9) HMGET key field1 [field2] #获取所有给定字段的值
(10)HMSET key field1 value1 [field2 value2 ] #同时将多个 field-value (域-值)对设置到哈希表 key 中。
(11)HSET key field value #将哈希表 key 中的字段 field 的值设为 value 。
(12)HSETNX key field value #只有在字段 field 不存在时,设置哈希表字段的值。
(14)HVALS key #取哈希表中所有值
(15)HSCAN key cursor [MATCH pattern] [COUNT count]迭代哈希表中的键值对。

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
#Redis HMSET, HGET 命令,HMSET 设置了两个 field=>value 对, HGET 获取对应 field 对应的 value。
127.0.0.1:6379> HSET hash1 field1 "hello" #(integer) 1
127.0.0.1:6379> HMSET hash1 field2 "world" field3 "redis" #OK
127.0.0.1:6379> hkeys hash1 #获取键里面得 field字段
1) "field1"
2) "field2"
3) "field3"
127.0.0.1:6379> hlen hash1 #(integer) 3 键里得字段数量

127.0.0.1:6379> hexists hash1 field1 #(integer) 1
127.0.0.1:6379> hget hash1 field1 #"hello" 查看键里面得字段值
127.0.0.1:6379> hmget hash1 field1 field2 #获取在哈希表中指定 key与字段得值
1) "hello"
2) "world"
127.0.0.1:6379> HSETNX hash1 field4 "study" #(integer) 1 不存在则建立存在则报错
127.0.0.1:6379> hvals hash1 #获取哈希表中所有值
1) "hello"
2) "world"
3) "redis"
4) "study"
127.0.0.1:6379> hgetall hash1 #获取在哈希表中指定 key 的所有字段和值
1) "field1"
2) "hello"
3) "field2"
4) "world"
5) "field3"
6) "redis"


127.0.0.1:6379> hdel hash1 field4 #删除hash表键中的字段
(integer) 1
127.0.0.1:6379> hdel hash1 field5
(integer) 0


############# 哈希表的计算 #############
127.0.0.1:6379> hset hash2 field1 1
(integer) 1
127.0.0.1:6379> hincrby hash2 field1 3
(integer) 4
127.0.0.1:6379> hincrbyfloat hash2 field1 3
"7"
127.0.0.1:6379> hincrbyfloat hash2 field1 3.5
"10.5"
  1. List(列表)
    Redis 列表是简单的字符串列表,按照插入顺序排序(类似于栈);你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

(1) BLPOP key1 [key2 ] timeout #移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
(2) BRPOP key1 [key2 ] timeout #移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
(3) BRPOPLPUSH source destination timeout #从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
(4) LINDEX key index #通过索引获取列表中的元素
(5) LINSERT key BEFORE|AFTER pivot value #在列表的元素前或者后插入元素
(6) LLEN key #获取列表长度
(7) LPOP key #移出并获取列表的第一个元素
(8) LPUSH key value1 [value2] #将一个或多个值插入到列表头部 (后进排头)
(9) LPUSHX key value #将一个值插入到已存在的列表头部
(10) LRANGE key start stop #获取列表指定范围内的元素
(11) LREM key count value #移除列表元素
(12) LSET key index value #通过索引设置列表元素的值
(13) LTRIM key start stop #对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
(14) RPOP key #移除列表的最后一个元素,返回值为移除的元素。
(15) RPOPLPUSH source destination #移除列表的最后一个元素,并将该元素添加到另一个列表并返回
(16) RPUSH key value1 [value2] #在列表中添加一个或多个值 (按前后顺序添加)
(17) RPUSHX key value #为已存在的列表添加值

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
127.0.0.1:6379[1]> lpush list1 1 2 3  #每次从头开始插入元素 向右边移动 头部添加
(integer) 3
127.0.0.1:6379[1]> lpushx list1 0
(integer) 4
127.0.0.1:6379[1]> llen list1 #列表长度
(integer) 4

127.0.0.1:6379[1]> rpush list2 0 1 2 3 4 #按顺序插入
(integer) 5
127.0.0.1:6379[1]> rpushx list2 5 #在尾部添加
(integer) 6
127.0.0.1:6379[1]> lset list2 5 7 #将第六的一个元素值改成7 (注意这里索引修改必须是存在)
OK

#下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,
#使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379[1]> lrange list1 0 4
1) "0"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379[1]> lrange list2 0 5
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
6) "7"


127.0.0.1:6379[1]> lpop list1 #弹出头部元素
"0"
127.0.0.1:6379[1]> rpop list1 #弹出尾部元素
"1"
127.0.0.1:6379[1]> blpop list1 10 #移出并获取列表的第一个元素
1) "list1"
2) "3"
127.0.0.1:6379[1]> brpop list1 10 #移出并获取列表的最后一个元素
1) "list1"
2) "2"

#通过索引获取列表中的元素
127.0.0.1:6379[1]> lindex list2 0
"0"
127.0.0.1:6379[1]> lindex list2 5
"5"


#从列表中取出最后一个元素,并插入到另外一个列表的头部
redis> BRPOPLPUSH list1 list2 500 # msg非空列表
"hello moto" # 弹出元素的值
(3.38s) # 等待时长
redis> LLEN list2
(integer) 1
redis> LRANGE list2 0 0
1) "hello moto"
redis> BRPOPLPUSH msg list2 1 #msg为空列表
(nil)
(1.34s)

127.0.0.1:6379[1]> rpoplpush list2 list3 #移除列表的最后一个元素,并将该元素添加到另一个列表并返回
"4"

#【重点】在列表的元素前或者后插入元素
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"


#[重点]让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 。
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
redis 127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2
redis 127.0.0.1:6379> RPUSH mylist "foo"
(integer) 3
redis 127.0.0.1:6379> RPUSH mylist "bar"
(integer) 4
redis 127.0.0.1:6379> LTRIM mylist 1 -1
OK
redis 127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "foo"
3) "bar"


#[重点]移除列表元素 返回被移除元素的数量。列表不存在时返回 0 。
# COUNT 的值可以是以下几种:
# count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
# count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
# count = 0 : 移除表中所有与 VALUE 相等的值。
redis> RPUSH mylist "hello"
(integer) 1
redis> RPUSH mylist "hello"
(integer) 2
redis> RPUSH mylist "foo"
(integer) 3
redis> RPUSH mylist "hello"
(integer) 4
redis> LREM mylist -2 "hello"
(integer) 2
redis> LRANGE mylist 0 -1
1) "hello"
2) "foo"
  1. Set(集合)
    Set是string类型的无序集合,集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1),集合成员是唯一的所以集合中不能出现重复的数据; 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

(1) SADD key member1 [member2] #向集合添加一个或多个成员
(2) SCARD key #获取集合的成员数(类似于llen)
(3) SDIFF key1 [key2] #返回给定所有集合的差集
(4) SDIFFSTORE destination key1 [key2] #返回给定所有集合的差集并存储在 destination 中
(5) SINTER key1 [key2] #返回给定所有集合的交集
(6) SINTERSTORE destination key1 [key2] #返回给定所有集合的交集并存储在 destination 中
(7) SISMEMBER key member #判断 member 元素是否是集合 key 的成员
(8) SMEMBERS key #返回集合中的所有成员
(9) SMOVE source destination member #将 member 元素从 source 集合移动到 destination 集合
(10) SPOP key #移除并返回集合中的一个随机元素
(11) SRANDMEMBER key [count] #返回集合中一个或多个随机数
(12) SREM key member1 [member2] #移除集合中一个或多个成员
(13) SUNION key1 [key2] #返回所有给定集合的并集
(14) SUNIONSTORE destination key1 [key2] #所有给定集合的并集存储在 destination 集合中
(15) SSCAN key cursor [MATCH pattern] [COUNT count] #迭代集合中的元素

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
#sadd key member #添加一个 string 元素到 key 对应的 set 集合中成功返回1,如果元素已经在集合中返回 0,如果 


127.0.0.1:6379[2]> sadd set1 1 2 3 4 5 6 #(integer) 添加 6 个元素
127.0.0.1:6379[2]> sadd set2 1 3 5 7 9 #(integer) 添加 5 个元素

127.0.0.1:6379[2]> scard set1 #集合中元素的个数
(integer) 6
127.0.0.1:6379[2]> sismember set1 1 #判断元素是不是在该集合中
(integer) 1
127.0.0.1:6379[2]> sismember set1 0
(integer) 0


127.0.0.1:6379[2]> sdiff set1 set2 #差集
1) "2"
2) "4"
3) "6"
127.0.0.1:6379[2]> sdiffstore set3 set1 set2 # 将set1与set2差集存入set3中 (integer) 3

127.0.0.1:6379[2]> sinter set1 set2 #交集
1) "1"
2) "3"
3) "5"
127.0.0.1:6379[2]> sinterstore set4 set1 set2 # 将set1与set2交集存入set4中 (integer) 3

127.0.0.1:6379[2]> smove set1 set4 2 #将集合set1中的元素为2的移动到set4集合中 (integer) 1
127.0.0.1:6379[2]> smembers set4 #显示集合中的元素
1) "1"
2) "2"
3) "3"
4) "5"

127.0.0.1:6379[2]> spop set1 #移除并返回集合中的一个随机元素 "4"
127.0.0.1:6379[2]> srandmember set1 10 #显示集合中指定的个数的元素值
1) "1"
2) "3"
3) "5"
4) "6"
127.0.0.1:6379[2]> srandmember set1 1
1) "6"


127.0.0.1:6379[2]> srem set1 1 3 #删除set1集合中的1和3两个元素 (integer) 2

127.0.0.1:6379[2]> sunion set1 set2 #并集
1) "1"
2) "3"
3) "5"
4) "6"
5) "7"
6) "9"
127.0.0.1:6379[2]> sunionstore set5 set1 set2 #(integer) 6 并集 同上

127.0.0.1:6379[2]> sscan set5 1 match * #迭代集合中的元素
1) "0"
2) 1) "1"
2) "3"
3) "5"
4) "6"
5) "7"
6) "9"


  1. zset(sorted set:有序集合)
    Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员,集合是通过哈希表实现的;
  • 不同的是每个元素都会关联一个double类型的分数
  • redis正是通过分数来为集合中的成员进行从小到大的排序
  • zset的成员是唯一的,但分数(score)却可以重复
  • 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

其实在redis sorted sets里面当items内容大于64的时候同时使用了hash和skiplist两种设计实现。这也会为了排序和查找性能做的优化,所以如上可知:

  • 添加和删除都需要修改skiplist,所以复杂度为O(log(n))。
  • 但是如果仅仅是查找元素的话可以直接使用hash,其复杂度为O(1)
  • 其他的range操作复杂度一般为O(log(n))
  • 当然如果是小于64的时候,因为是采用了ziplist的设计,其时间复杂度为O(n)

(1) ZADD key score1 member1 [score2 member2] #向有序集合添加一个或多个成员,或者更新已存在成员的分数
(2) ZCARD key #获取有序集合的成员数
(3) ZCOUNT key min max #计算在有序集合中指定区间分数的成员数
(4) ZINCRBY key increment member #有序集合中对指定成员的分数加上增量 increment
(5) ZINTERSTORE destination numkeys key [key …] #计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
(6) ZLEXCOUNT key min max #在有序集合中计算指定字典区间内成员数量
(7) ZRANGE key start stop [WITHSCORES] #通过索引区间返回有序集合成指定区间内的成员,[分数从低到高]
(8) ZRANGEBYLEX key min max [LIMIT offset count] #通过字典区间返回有序集合的成员
(9) ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] #通过分数返回有序集合指定区间内的成员
(10)ZRANK key member #返回有序集合中指定成员的索引
(11)ZREM key member [member …] #移除有序集合中的一个或多个成员
(12)ZREMRANGEBYLEX key min max #移除有序集合中给定的字典区间的所有成员
(13)ZREMRANGEBYRANK key start stop #移除有序集合中给定的排名区间的所有成员
(14)ZREMRANGEBYSCORE key min max #移除有序集合中给定的分数区间的所有成员
(15)ZREVRANGE key start stop [WITHSCORES] #返回有序集中指定区间内的成员,通过索引,[分数从高到底]
(16)ZREVRANGEBYSCORE key max min [WITHSCORES] #返回有序集中指定分数区间内的成员,分数从高到低排序
(17)ZREVRANK key member #返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
(18)ZSCORE key member #返回有序集中,成员的分数值
(19)ZUNIONSTORE destination numkeys key [key …] #计算给定的一个或多个有序集的并集,并存储在新的 key 中
(20)ZSCAN key cursor [MATCH pattern] [COUNT count] #迭代有序集合中的元素(包括元素成员和元素分值)

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#有序集合(重点)
127.0.0.1:6379[3]> zadd zset 1 redis 1 python 1 php # (integer) 3 建立有序集合
127.0.0.1:6379[3]> zadd zset 2 java 2 javascript 2 node.js #继续添加有序集合
(integer) 3
127.0.0.1:6379[3]> zcard zset #获取有序集合的成员数 (integer) 6
127.0.0.1:6379[3]> zcount zset 0 2 #计算在有序集合中指定区间分数的成员数 (integer) 6

127.0.0.1:6379[3]> zincrby zset 1 redis # increment "2" 有序集合中对指定成员的分数加上增量
127.0.0.1:6379[3]> zrank zset redis #(integer) 5 返回有序集合中指定成员的索引

127.0.0.1:6379[3]> zincrby zset 7 redis #redis元素分数继续+7 "9"
127.0.0.1:6379[3]> zlexcount zset 0 6 #这时有序集合分数区间只有 (integer) 5

127.0.0.1:6379[3]> zrange zset 0 -1 withscores #显示有序列表中的所有元素 withscores(显示分数)
1) "php"
2) "1"
3) "python"
4) "1"
5) "java"
6) "2"
7) "javascript"
8) "2"
9) "node.js"
10) "2"
11) "redis"
12) "9"

127.0.0.1:6379[3]> zrangebyscore zset 0 2 #通过分数返回有序集合指定区间内的成员
1) "php"
2) "python"
3) "java"
4) "javascript"
5) "node.js"
127.0.0.1:6379[3]> zrangebyscore zset 9 9
1) "redis"

127.0.0.1:6379[3]> zadd zset 0 php 1 php2 2 php3 3 php4 #添加有序集合元素(integer) 4
127.0.0.1:6379[3]> zrevrange zset 0 5 withscores ##返回有序集中指定区间内的成员,通过索引,分数从高到底
1) "php4"
2) "3"
3) "php3"
4) "2"
5) "node.js"
6) "2"
7) "php2"
8) "1"
9) "php"
10) "0"
127.0.0.1:6379[3]> zrevrangebyscore zset 0 -1 #返回有序集中指定分数区间内的成员,分数从高到低排序
1) "php"

127.0.0.1:6379[3]> zrevrank zset php #返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
(integer) 4
127.0.0.1:6379[4]> zscore zset node.js #返回有序集中,成员的分数值
"2"

127.0.0.1:6379[3]> zrem zset redis #删除有序列表中指定元素 返回1 (integer) 1
127.0.0.1:6379[3]> zrem zset redis #不存在列表中返回0 (integer) 0

127.0.0.1:6379[3]> zremrangebyrank zset 0 1 #移除有序集合中给定的字典区间的所有成员 (integer) 2
127.0.0.1:6379[3]> zremrangebyrank zset 0 1 #(integer) 2 后面的成员回向前面补齐


""""
Zlexcount 命令在计算有序集合中指定字典区间内成员数量
""""
redis 127.0.0.1:6379> ZADD myzset 0 a 0 b 0 c 0 d 0 e
(integer) 5
redis 127.0.0.1:6379> ZADD myzset 0 f 0 g
(integer) 2
redis 127.0.0.1:6379> ZLEXCOUNT myzset - +
(integer) 7
redis 127.0.0.1:6379> ZLEXCOUNT myzset [b [f
(integer) 5


""""
[重点]:通过字典区间返回有序集合的成员。
""""
redis 127.0.0.1:6379> ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g
(integer) 7
redis 127.0.0.1:6379> ZRANGEBYLEX myzset - [c
1) "a"
2) "b"
3) "c"
redis 127.0.0.1:6379> ZRANGEBYLEX myzset - (c
1) "a"
2) "b"
redis 127.0.0.1:6379> ZRANGEBYLEX myzset [aaa (g
1) "b"
2) "c"
3) "d"
4) "e"
5) "f"


"""
[重点]:给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 。
"""
# 有序集 mid_test
redis 127.0.0.1:6379> ZADD mid_test 70 "Li Lei"
(integer) 1
redis 127.0.0.1:6379> ZADD mid_test 70 "Han Meimei"
(integer) 1
redis 127.0.0.1:6379> ZADD mid_test 99.5 "Tom"
(integer) 1

# 另一个有序集 fin_test
redis 127.0.0.1:6379> ZADD fin_test 88 "Li Lei"
(integer) 1
redis 127.0.0.1:6379> ZADD fin_test 75 "Han Meimei"
(integer) 1
redis 127.0.0.1:6379> ZADD fin_test 99.5 "Tom"
(integer) 1

redis 127.0.0.1:6379> ZINTERSTORE sum_point 2 mid_test fin_test # 交集 (integer) 3
# 显示有序集内所有成员及其分数值
redis 127.0.0.1:6379> ZRANGE sum_point 0 -1 WITHSCORES
1) "Han Meimei"
2) "145"
3) "Li Lei"
4) "158"
5) "Tom"
6) "199"

总结应用场景:

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
# 列表可以用来存储用户UID
# hash用来存储用户的信息
# set集合用来查看共同好友(唯一性)

# 字符串 #
127.0.0.1:6379> set Weiyi Geek #OK
127.0.0.1:6379> get Weiyi #"Geek"

# HASH #
127.0.0.1:6379> hmset hash love1 "Python" love2 "C++" #注意键不能同名 OK

# 列表 #
27.0.0.1:6379> lpush list redis
(integer) 1
127.0.0.1:6379> lpush list python
(integer) 2
127.0.0.1:6379> lrange list 0 10
1) "python"
2) "redis"

# 集合 #
127.0.0.1:6379> sadd run redis
(integer) 1
127.0.0.1:6379> sadd run redis1
(integer) 1
127.0.0.1:6379> sadd run redis2
(integer) 1
127.0.0.1:6379> smembers run
1) "redis2"
2) "redis1"
3) "redis"

# 有序集合 #
127.0.0.1:6379> zadd test 0 redis
(integer) 1
127.0.0.1:6379> zadd test 0 python
(integer) 1
127.0.0.1:6379> zrangebyscore runoob 0 1000 #当请求没有键不会报错(注意)
(empty list or set)
127.0.0.1:6379> zrangebyscore test 0 1000
1) "python"
2) "redis"

WeiyiGeek.数据类型应用场景

WeiyiGeek.数据类型应用场景

0x02 Redis 基础学习

Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。

Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。
这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。

  • 每个数据库对外都是一个从0开始的递增数字命名,
  • Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。
  • 客户端与Redis建立连接后会自动选择0号数据库,
  • 可以随时使用SELECT命令更换数据库,
1
2
3
4
redis> SELECT 1  #选择1号数据库
OK
redis [1] > GET foo
(nil)

然而这些以数字命名的数据库又与我们理解的数据库有所区别:

  • 首先Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。
  • 另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。
  • 最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。

综上所述redis数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。
比如:可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据


(0) 基础操作
描述:即链接上redis后在127.0.0.1:6379>中执行的内置命令,首先我们需要使用redis-cli连接redis数据库,如果有认证则需要auth命令进行认证;
PS:

  • 内置命令不区分大小写
  • 默认至多16号仓库即(db0~db15)

内置命令基础使用:

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
#1.连接Redis数据库
redis-cli -h 127.0.0.1
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required.

#2.Redis 登陆验证
127.0.0.1:6379> auth weiyigeek.top
OK

#3.ping 测试链接是否存活
127.0.0.1:6379> ping
PONG

#4.select 选择redis数据库号(db0~15),可以选择任意一个仓库进行数据库存取
127.0.0.1:6379> select 1
127.0.0.1:6379> select 16
(error) ERR DB index is out of range


#5.dbsize 返回当前数据库中的Key数目
127.0.0.1:6379> select 1
127.0.0.1:6379[1]> DBSIZE
(integer) 2
127.0.0.1:6379[1]> dbsize
(integer) 2


#4.info 获取服务器统计信息
127.0.0.1:6379[1]> info
# Server
redis_version:5.0.8
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:ce75a617c591114f
redis_mode:standalone
os:Linux 4.18.0-147.5.1.el8_1.x86_64 x86_64


#5.flushdb 删除当前选择数据库中的所有key
127.0.0.1:6379[1]> FLUSHDB
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 0

#6.flushall 删除所有仓库中所有key
127.0.0.1:6379[1]> FLUSHALL
OK

#7.echo 指定字符串输出
echo "WeiyigGeek"
"WeiyigGeek"

#8.move 将Redis键移植到其它库中
# 将db0中的mykey键移动到1号仓库,然后进入db1进行查看键
> move mykey 1
> select 1
> keys *


(1) HyperLogLog
在2.8.9版本添加了HyperLogLog结构是是用来做基数统计的算法;

Q:什么是基数?
答:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5;基数估计就是在误差可接受的范围内,快速计算基数。

HyperLogLog 的优点:

  • 输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • 每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数
  • 根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素

1)PFADD key element [element …] #添加指定元素到 HyperLogLog 中。
2)PFCOUNT key [key …] #返回给定 HyperLogLog 的基数估算值。
3)PFMERGE destkey sourcekey [sourcekey …] #将多个 HyperLogLog 合并为一个 HyperLogLog

1
2
3
4
5
6
7
8
9
10
11
12
13

127.0.0.1:6379[5]> pfadd hyper "redis" "mongodb" "mysql" "python"
(integer) 1
127.0.0.1:6379[5]> pfadd log "java" "javascript" "web"
(integer) 1
127.0.0.1:6379[5]> pfcount hyper log #基数估算值
(integer) 7
127.0.0.1:6379[5]> pfcount hyper #基数估算值
(integer) 4
127.0.0.1:6379[5]> pfmerge descLog hyper log #将两个keys进行合并到descLog
OK
127.0.0.1:6379[5]> pfcount descLog
(integer) 7


(2) Redis发布订阅
什么是pub/sub?
答:Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能,基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式;
同样Redis的pub/sub是一种消息通信模式,主要的目的是解除消息发布者和消息订阅者之间的耦合,Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。

描述:类似于MQTT协议一样,Redis也支持发布订阅(pub/sub)消息通信模式(发送者(pub)发送消息,订阅者(sub)接收消息),Redis客户端可以订阅任意数量的频道

WeiyiGeek.redis发布订阅

WeiyiGeek.redis发布订阅

基础命令:

  • (1) PSUBSCRIBE pattern [pattern …] #订阅一个或多个符合给定模式的频道。(可以采用通配符)
  • (5) SUBSCRIBE channel [channel …] #订阅给定的一个或多个频道的信息。

  • (2) PUBSUB subcommand [argument [argument …]] #查看订阅与发布系统状态。

  • (3) PUBLISH channel message #将信息发送到指定的频道。

  • (6) UNSUBSCRIBE [channel [channel …]] #指退订给定的频道。

  • (4) PUNSUBSCRIBE [pattern [pattern …]] #退订所有给定模式的频道。

基础实例:

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
# 终端1 (订阅频道)
127.0.0.1:6379[6]> subscribe redisChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
1) "message"
2) "redisChat"
3) "Redis pub/sub"
1) "message"
2) "redisChat"
3) "Hello World!MQTT"

# 终端2 (发布频道)
192.168.1.100:6379> publish redisChat "Redis pub/sub"
(integer) 1
192.168.1.100:6379> publish redisChat "Hello World!MQTT"
(integer) 1
192.168.1.100:6379> publish redisChat "Hello World!Redis Channel"
(integer) 1

192.168.1.100:6379> pubsub CHANNELS redis* #命令用于查看订阅与发布系统状态
1) "redisChat"

# 终端3 (订阅频道)
127.0.0.1:6379[6]> psubscribe redis* #通配符(匹配redis以及redis字符串拼接的发布频道)
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redis*"
3) (integer) 1
"Hello World!Redis Channel"


(3) Redis事务
描述:事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务

以下命令是实现事务的基石
(1)MULTI 标记一个事务块的开始 == BEGIN TRANSACTION
(2)EXEC 提交执行所有事务块内的命令 == COMMIT
(3)DISCARD 取消事务,放弃执行事务块内的所有命令 == ROLLBACK
(4)WATCH key [key …] 监视一个(或多个) key,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
(5)UNWATCH 取消 WATCH 命令对所有 key 的监视。


Redis事务特征:

  • 1.在事务执行中所有命令都将会被串行化的顺序执行(效率低但是由于操作的是内存所以忽略不计),并且在该事务执行期间Redis不会再为其它客户端的请求提供任何服务(保证了事务中的所有命令被原子执行);
  • 2.在事务执行失败后其后面的命令任然会继续执行,此点与关系型数据库事务相比有些许不同
  • 3.在事务执行中命令MULTI可以看做关系型数据库中BEGIN TRANSACTION,而EXEC与DISCARD命令来提交/回滚事务内的所有操作等同于COMMIT 与 ROLLBACK 语句;
  • 4.在事务执行过程中如果Client与Server出现通讯故障并导致网络断开,其后所执行的语句将不会被服务器指向,但是如果网络中断事件发生在EXEC命令之后则任然执行;
  • 5.在事务执行中使用Append-Only模式此时Redis会调用系统函数write将该事务内的所有写操作在本次调用中全部写入硬盘;但是如果在写入过程中出现系统崩溃导致数据写不完整,此时在Redis重启后会进行一致性检测如果发现问题将会提示;

PSTips:

  • 当Redis进行错误提示我们可以利用Redis-check-aof工具帮助我们定位到数据不一致的错误,并将已经写入部分数据进行回滚,之后重启数据库即可;


示例1.它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
192.168.1.100:6379[7]> multi  #开启事务
192.168.1.100:6379[7]> set book-name "redis 入门到放弃"
QUEUED
192.168.1.100:6379[7]> get book-name
QUEUED
192.168.1.100:6379[7]> sadd tag "redis" "数据库" "基础入门"
QUEUED
192.168.1.100:6379[7]> smembers tag
QUEUED
192.168.1.100:6379[7]> exec #触发事务(将建立的k-v写入)
1) OK
2) "redis \xe5\x85\xa5\xe9\x97\xa8\xe5\x88\xb0\xe6\x94\xbe\xe5\xbc\x83"
3) (integer) 3
4) 1) "\xe6\x95\xb0\xe6\x8d\xae\xe5\xba\x93"
2) "redis"
3) "\xe5\x9f\xba\xe7\xa1\x80\xe5\x85\xa5\xe9\x97\xa8\xef\xbc\xe2\x80"


示例2.事务回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> set name WeiyiGeek
OK
127.0.0.1:6379[1]> get name
"WeiyiGeek"
127.0.0.1:6379[1]> MULTI
OK
127.0.0.1:6379[1]> set name Redis #在事务中重新设置name键值
QUEUED
127.0.0.1:6379[1]> get name
QUEUED
127.0.0.1:6379[1]> DISCARD #不提交事务即回滚
OK
127.0.0.1:6379[1]> get name #显示事务开启前的name键值
"WeiyiGeek"


示例3.失败命令自动剔除执行案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> get num
"10"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCRBY num 5
QUEUED
127.0.0.1:6379> INCRBY num x #异常加值
QUEUED
127.0.0.1:6379> INCRBY num 5 # 15 + 5
QUEUED
127.0.0.1:6379> exec
1) (integer) 15
2) (error) ERR value is not an integer or out of range #当事务提交后执行所有操作当操作异常时候将被忽略;
3) (integer) 20
127.0.0.1:6379> get num
"20"


0x03 后端语言连接Redis

(0) Redis 脚本

Redis 脚本使用 Lua 解释器来执行脚本,通过内嵌支持 Lua 环境,执行脚本的常用命令为 EVAL。

  1. EVAL script numkeys key [key …] arg [arg …] #执行 Lua 脚本。
  • script:Lua 脚本程序
  • numkeys:用于指定键名参数的个数。
  • key [key …]:从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
  • arg [arg …]: 附加参数
  1. EVALSHA sha1 numkeys key [key …] arg [arg …] #根据给定的 sha1 校验码,执行缓存在服务器中的脚本
  • sha1:通过 SCRIPT LOAD 生成的 sha1 校验码。
  1. SCRIPT EXISTS script [script …] 查看指定的脚本是否已经被保存在缓存当中。
  2. SCRIPT FLUSH 从脚本缓存中移除所有脚本。
  3. SCRIPT KILL 杀死运行的Lua脚本,主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限循环的脚本
  4. SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
192.168.1.100:6379[7]>  EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

127.0.0.1:6379> SCRIPT LOAD "return 'hello moto'" #将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本
"232fd51614574cf0867b83d384a5e898cfd24e5a" #给定脚本的 SHA1 校验和

127.0.0.1:6379> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a #校验指定的脚本是否已经被保存在缓存当
1) (integer) 1

127.0.0.1:6379> EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 0
"hello moto"

127.0.0.1:6379> SCRIPT FLUSH # 清空缓存OK
127.0.0.1:6379> SCRIPT Kill # 杀死当前正在运行的 Lua 脚本


(1) php连接使用redis

扩展官网:https://pecl.php.net/package/redis //下载与php对应得版本(非常注意操作系统得位数)
环境:WINDOW7/XMAPP/PHP7.2
安装扩展:

1
2
3
4
5
6
7
8
9
#Step1.下载TS版本且32位系统
PHP 7.2 Thread Safe (TS) x86
#Step2.解压php_redis.dll到php根目录中ext目录中
#Step3.在php.ini配置启用redis扩展;extension = php_redis
#Step4.执行php -m 查看是否有该模块

@Linux下安装redis(百度即可)
PHP redis 驱动:下载地址为:https://github.com/phpredis/phpredis/releases
pipize/php-config

php连接使用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
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
<?php
/***
* 功能:实现redis连接以及5种数据类型元素建立
*/

//Step1.实例化redis对象设置连接参数
$redis = new \Redis();
try{
$link = $redis->pconnect('127.0.0.1',6379,1,NULL,100); #设置超时时间并且重试(返回Boolean)
if(!$link){
throw new Exception("!Redis服务器连接失败"); //抛出异常
}else{
echo "Redis连接成功!<br>";
}
}catch(Exception $e){
print_r($e);
exit(0);
}

//Step2.进行redis认证
if($redis->auth('weiyigeek'))
{
echo "Redis AUTH 认证成功!<br>";
}else{
echo "Redis AUTH 认证失败!";
}

//Step3.String 类型
$redis->set('name','Weiyigeek'); //设置String类型键值
$redis->mSet(['id'=>'Weiyi','age'=>22]);
$redis->setEx('key',120,'value'); //存活2分钟

$strLen = $redis->strlen('name'); //获取字符串长度
$type = $redis->type('age'); //返回其类型
$redis->incrBy('age',8); //String类型进行计算
$getValue = $redis->mGet(['name','id','age']); //获取String

echo "name键的字符串长度:".$strLen."<br>";
echo "<pre>";
var_dump($type);
var_dump($getValue);
echo "</pre> <br>";

######### 执行结果 ###########
// Redis连接成功!
// Redis AUTH 认证成功!
// name键的字符串长度:9

// int(1)
// array(3) {
// [0]=>
// string(9) "Weiyigeek"
// [1]=>
// string(5) "Weiyi"
// [2]=>
// string(2) "30"
// }




//Step2.List 类型
$redis->delete('list1');
$redis->lpush('list1','A','C'); //直接插入多个元素 从左边开始插入 ->
$redis->rpush('list1','B');

$redis->delete('key1');
$redis->rPush('key1', 'A','B','C','D'); // returns 3 从右边开始插<-

$lcount = $redis->lLen('key1'); //元素个数

echo "key1元素个数:".$lcount."<br>";
echo "<pre>";
var_dump($redis->lRange('list1', 0, -1));
var_dump($redis->lRange('key1', 0, -1));
echo "</pre> <br>";

######### 执行结果 ###########
// key1元素个数:4

// array(3) {
// [0]=>
// string(1) "C"
// [1]=>
// string(1) "A"
// [2]=>
// string(1) "B"
// }
// array(4) {
// [0]=>
// string(1) "A"
// [1]=>
// string(1) "B"
// [2]=>
// string(1) "C"
// [3]=>
// string(1) "D"
// }




//Step3.Hash 类型
$redis->delete('driver1');
$redis->hSet('driver1','name','张伟');
$redis->hSet('driver1','age','22');
$redis->hSet('driver1','gender',0);
$redis->hSet('driver1','love','Play Computer!');

$hcount = $redis->hLen('driver1'); //散列个数
$hlen = $redis->hStrLen('driver1','love'); //散列种键值的长度
$var = $redis->hGet('driver1','name'); //获取HASH种的键值
$var1 = $redis->hMGet('driver1',array('age','gender','love'));

echo "散列driver1的长度为:".$hcount.", Love字段值的长度:".$hlen."<br><pre>";
var_dump($var);
var_dump($var1);
echo "</pre><br>";

######### 执行结果 ###########
// 散列driver1的长度为:4, Love字段值的长度:14

// string(6) "张伟"
// array(3) {
// ["age"]=>
// string(2) "22"
// ["gender"]=>
// string(1) "0"
// ["love"]=>
// string(14) "Play Computer!"
// }



//Step4.set类型
$redis->sRem('set1','A','B','C','D'); //删除集合种的元素
$redis->sAdd('set1','B','A','C','D'); //添加集合元素
$redis->sAdd('set2','B','D','E','F');

$sCount = $redis->sCard('set1'); //获取集合种的值
$sinter = $redis->sInter('set1','set2'); //集合set1与set2的交集

$diffCount = $redis->sDiffStore('set3', 'set1', 'set2'); //将集合set1 - set2的差集存(A,C)入set3种

echo "集合set1的长度为:".$sCount.", set1与set2差集元素的个数为:".$diffCount."<br><pre>";
var_dump($sinter); //交
var_dump($redis->sMembers('set3')); //差集
echo "</pre><br>";

######### 执行结果 ###########
// 集合set1的长度为:4, set1与set2差集元素的个数为:2

// array(2) {
// [0]=>
// string(1) "B"
// [1]=>
// string(1) "D"
// }
// array(2) {
// [0]=>
// string(1) "C"
// [1]=>
// string(1) "A"
// }



//Step5.sort set 有序集合类型
$redis->delete('zset1');
$redis->zAdd('zset1',1.0,'zhanghai');
$redis->zAdd('zset1',3.0,'Weiyigeek');
$redis->zAdd('zset1',7.5,'Chenghuiming');
$redis->zAdd('zset1',8.6,'Kangkang');

$zCount = $redis->zCard('zset1'); //有序集合键里有多个元素
$zCount1 = $redis->zCount('zset1',0,5.0); //score在0~5之间的个数
$desc = $redis->zRange('zset1',0,-1,true); //分数从小到大排列 (最后一个参数是显示分数)
$asc = $redis->zRevRange('zset1',0,-1,true); //分数从大到小排列

echo "有序集合zset1有 ".$zCount."个元素,且score分数在0~5之间的个数:".$zCount1."<br><pre>";
var_dump($desc);
var_dump($asc);
echo "</pre>";


######### 执行结果 ###########
// 有序集合zset1有 4个元素,且score分数在0~5之间的个数:2
// array(4) {
// ["zhanghai"]=>
// float(1)
// ["Weiyigeek"]=>
// float(3)
// ["Chenghuiming"]=>
// float(7.5)
// ["Kangkang"]=>
// float(8.6)
// }
// array(4) {
// ["Kangkang"]=>
// float(8.6)
// ["Chenghuiming"]=>
// float(7.5)
// ["Weiyigeek"]=>
// float(3)
// ["zhanghai"]=>
// float(1)
// }


(2) C#连接使用redis

高性能Redis协议封装,支持.Net Core,经过一年多日均80亿调用量验证
项目地址:http://git.newlifex.com/NewLife/NewLife.Redis

1)基础 Redis
Redis实现标准协议以及基础字符串操作,完整实现由独立开源项目NewLife.Redis提供;采取连接池加同步阻塞架构,具有超低延迟(200~600us)以及超高吞吐量的特点。
在物流行业大数据实时计算中广泛应有,经过日均100亿次调用量验证。

NewLife.Redis 一个完整的Redis协议的功能的实现,但是redis的核心功能并没有在这里面,Redis的核心功能的实现是在NewLife.Core里面,Redis的核心功能就是有这两个类实现:

  • 有一个NewLife.Caching的命名空间,里面有一个Redis类里面实现了Redis的基本功能
  • 另一个类是RedisClient是Redis的客户端(代表对服务器的一个连接)

Redis的封装有两层:

  • 一层是NewLife.Core里面的Redis以及RedisClient
  • 另一层就是NewLife.Redis,这里面的FullRedis是对Redis的实现了Redis的所有的高级功能(是Redis的一个扩展)
1
2
3
4
5
// 实例化Redis,默认端口6379可以省略,密码有两种写法
//var rds = Redis.Create("127.0.0.1", 7);
var rds = Redis.Create("[email protected]:6379", 7);
//var rds = Redis.Create("server=127.0.0.1:6379;password=pass", 7);
rds.Log = XTrace.Log; // 调试日志。正式使用时注释

2)C#案例

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
/**打开Program.css**/
using NewLife.Threading;
namespace Test
{
class Program
{
static void Main(String[] args)
{
Xtrace.UseConsole(); //向控制台输出日志,方便调试使用查看结果
FullRedis.Register(); //集合FULLREDIS,否则Redis.create会得到默认的redis对象
Test1(); //调用静态方法
Console.ReadKey()
}
}
}

static void Test1()
{
var ic = Redis.Create("[email protected]:6379", 7);//创建Redis实例,得到FullRedis对象
//var ic = new FullRedis();//另一种实例化的方式
//ic.Server = "127.0.0.1:6379";
//ic.Db = 3; //Redis中数据库
ic.Log = XTrace.Log;//显示日志,进行Redis操作把日志输出,生产环境不用输出日志

// 简单操作
Console.WriteLine("共有缓存对象 {0} 个", ic.Count);//缓存对象数量

ic.Set("name", "大石头");//Set K-V结构,Set第二个参数可以是任何类型
Console.WriteLine(ic.Get<String>("name"));//Get泛型,指定获取的类型

ic.Set("time", DateTime.Now, 1);//过期时间秒
Console.WriteLine(ic.Get<DateTime>("time").ToFullString());
Thread.Sleep(1100);
Console.WriteLine(ic.Get<DateTime>("time").ToFullString());

// 列表
var list = ic.GetList<DateTime>("list");
list.Add(DateTime.Now);
list.Add(DateTime.Now.Date);
list.RemoveAt(1);
Console.WriteLine(list[list.Count - 1].ToFullString());

// 字典
var dic = ic.GetDictionary<DateTime>("dic");
dic.Add("xxx", DateTime.Now);
Console.WriteLine(dic["xxx"].ToFullString());

// 队列
var mq = ic.GetQueue<String>("queue");
mq.Add(new[] { "abc", "g", "e", "m" });
var arr = mq.Take(3);
Console.WriteLine(arr.Join(","));

// 集合
var set = ic.GetSet<String>("181110_1234");
set.Add("xx1");
set.Add("xx2");
set.Add("xx3");
Console.WriteLine(set.Count);
Console.WriteLine(set.Contains("xx2"));

Console.WriteLine("共有缓存对象 {0} 个", ic.Count);
}

WeiyiGeek.C#案例

WeiyiGeek.C#案例

总结:

  • 数据库中不合法的时间处理,习惯大于小于号不习惯用等于号,这样可以处理很多意外的数据
  • Set的时候最好指定过期时间防止有些需要删除的数据
  • Redis异步尽量不用,因为Redis延迟本身很小,大概在100us-200us,再一个就是Redis本身是单线程的,异步任务切换的耗时比网络耗时还要大


3) Redis简单案例
网站搜索的热搜词,用.NET Core和StackExchange.Redis以及Jquery-ui 主要是用了里面的autocomplete;

搜索关键字的时候不存在则添加,存在则将利用有序集合中的zincrby进行分数排序;

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#AutoController
using AutoCompleteDemo.Common;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AutoCompleteDemo.Controllers
{
public class AutoController : Controller
{
private readonly IRedis _redis;
private readonly string _searchKey = "search";
public AutoController(IRedis redis)
{
_redis = redis;
}

public IActionResult Index()
{
//keys
IList<string> keys = new List<string>()
{
"kobe","johnson","jabbar","west","o'neal","baylor","mccann","worthy","gasol","chamberlain",
"fisher","odom","bynum","horry","rambis","riley","clarkson","Williams","young","Russell",
"ingram","randle","nance","brown","deng","yi","ariza","artest","walton","vujacic",
"james","paul","curry","park","yao","kevin","wade","rose","popovich","leonard",
"aldridge","ginobili","duncan","lavine","rubio","garnett","wiggins","westbrook","durant","ibaka",
"nowitzki","pierce","crawford","love","smith","iguodala","barnes","green","thompson","harden",
"lillard","mccollum","lin","jackson","nash","stoudemire","whiteside","dragic","Howard","batum"
};

//init
Random random = new Random();
var tran = _redis.GetTransaction();
for (int i = 0; i < 2000000; i++)
{
tran.SortedSetIncrementAsync(_searchKey, keys[random.Next(0, 70)], 1);
}
tran.ExecuteAsync();

return View();
}

public IActionResult GetHotKey(string key="")
{
if (string.IsNullOrEmpty(key))
{//default
var res = _redis.ZRevRange(_searchKey, 0, 9); //将分数从高到低排序 desc
var list = (from i in res select i.ToString());
return Json(list);
}
else
{//by user input
var res = _redis.ZRevRange(_searchKey, 0, -1);
var list = (from i in res select i.ToString()).Where(x => x.Contains(key)).Take(10).ToList();
return Json(list);
}
}

[HttpPost]
public IActionResult SetHotKey(string key)
{
if (!string.IsNullOrWhiteSpace(key))
{
_redis.ZIncrby(_searchKey,key);
//other
//...
return Json(new { code = "000", msg = "OK" });
}
else
{
return Json(new { code = "999", msg = "keyword can not be empty!" });
}
}
}
}

#Index.cshtml
@{
ViewData["Title"] = "Auto Complete";
}
<div class="row">
<div class="col-md-6 col-md-offset-4" style="padding-top:50px;">
<input id="key" name="key" placeholder="search" class="form-control col-md-4">
<button class="btn btn-primary" type="button" id="searchSubmit">Search</button>
<div id="result"></div>
</div>
</div>
@section scripts{
<script type="text/javascript">
$(function () {
//show hot keyword
$("#key").autocomplete({
source: function (request, response) {
$.ajax({
url: "@Url.Action("GetHotKey", "Auto")", //关联上面的方法
dataType: "json",
data: {
key: request.term
},
success: function (data) {
response(data);
}
});
},
});

//search
$("#searchSubmit").click(function () {
$.ajax({
url: "@Url.Action("SetHotKey", "Auto")",
dataType: "json",
type: "POST",
data: { key: $("#key").val() },
success: function (data) {
if (data.code == "000") {
$("<p>search successful!</p>").appendTo("#result");
} else {
$("<p>"+data.msg+"</p>").appendTo("#result");
}
}
});
});
});
</script>
}
WeiyiGeek.搜索热词

WeiyiGeek.搜索热词


(3) java操作redis

描述:采用Jedis jar包进行利用java操作Redis,所以必须将其导入工程的lib中,下面简单演示一下使用;

config.properties

1
2
3
# Connection Redis Configure
RedisUrl=10.20.10.248:6379
RedisAuth=weiyigeek.top

基础实例:

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
import redis.clients.jedis.Jedis;
/**
* 采用 Java 进行 Redis 连接使用测试
* @author WeiyiGeek
*/
public class RedisDemo1 {
public static void main(String[] args) {
String[] RedisUrl=null;
String RedisAuth=null;
try {
//1.创建一个属性配置对象并打开配置文件
Properties prop = new Properties();
InputStream ins = RedisDemo1.class.getClassLoader().getResourceAsStream("config.properties");
prop.load(ins);
RedisUrl=prop.getProperty("RedisUrl").split(":");
RedisAuth=prop.getProperty("RedisAuth");
System.out.println("RedisServer:"+ prop.getProperty("RedisUrl") + "\nPassword:" + RedisAuth );

//2.连接到 redis 服务
Jedis jedis = new Jedis(RedisUrl[0],Integer.parseInt(RedisUrl[1]));
System.out.println("正在Redis认证连接...");
jedis.auth(RedisAuth);

//3.查看链接是否成功并且服务是否运行
System.out.println("服务正在运行: "+jedis.ping()+"\n");

//4.选择一号库
jedis.select(1);

//5.设置 redis 字符串数据与 获取存储的数据并输出
String key = "WeiyiGeek";
jedis.set(key, "www.weiyigeek.top");
jedis.set("count", "1");
jedis.setex("Key", 60, "60 Sec");
System.out.println("当前数据库总键数:"+jedis.dbSize());
if(jedis.exists(key)) {
System.out.println("Redis中WeyiGeek键存储的字符串为:"+ jedis.get(key));
System.out.println("其类型为 : " + jedis.type(key));
}
//value + 1 并返回其其值
System.out.println("incr key = " + jedis.incr("count"));
System.out.println("incrby key 5 = " + jedis.incrBy("count", 5));

//6.采用迭代器进行遍历所有键值
Set<String> keys = jedis.keys("*");
for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
String k = (String) iterator.next();
if (jedis.type(k).equalsIgnoreCase("string")) {
System.out.println(k + " - " + jedis.get(k));
}

}
System.out.println("");
//清空所有的key
jedis.flushAll();

//关闭释放jedis连接资源
jedis.disconnect();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
RedisServer:10.20.10.248:6379
Password:weiyigeek.top
正在Redis认证连接...
服务正在运行: PONG

当前数据库总键数:3
Redis中WeyiGeek键存储的字符串为:www.weiyigeek.top
其类型为 : string
incr key = 2
incrby key 5 = 7
count - 7
WeiyiGeek - www.weiyigeek.top
Key - 60 Sec


容灾备份

1.Redis配置数据恢复
描述:在系统删除了配置文件后以及用户账号后恢复方法

  • Step1.Redis账户数据恢复
    首先确定系统中是否还有redis用户。(如果拷贝过来的系统也安装了redis,那么肯定是会有redis账户)
  • Step2.如果发现有redis用户,以下步骤可以跳过
    1
    2
    3
    4
    echo "redis:x:996:994:Redis Database Server:/var/lib/redis:/sbin/nologin" >> /etc/passwd
    echo "redis:!!:17416::::::" >> /etc/shadow
    echo "redis:x:994:" >> /etc/group
    echo "redis:!::" >> /etc/gshadow
  • Step3.Redis配置文件恢复
    Redis的配置文件恢复相对简单一些,官方提供了CONFIG REWRITE命令重写redis.conf配置文件。
    1
    2
    3
    redis-cli
    127.0.0.1:6379> CONFIG REWRITE
    OK
  • Step4.修改配置文件权限
    1
    2
    touch /etc/redis.conf
    chown redis:redis /etc/redis.conf


2.Redis 数据库db迁移

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
#redis
# 把db2 的数据迁移到 db14 里
# 需求分析:
'''1、建立两个redis连接
2、获取所有的keys()
3、获取keys的类型:string hash'''

import redis
src_redis = redis.Redis(host='211.149.218.16',
password='123456',
port=6379,
db=2)
target_redis = redis.Redis(host='211.149.218.16',
password='123456',
port=6379,
db=14)

for key in src_redis.keys(): # redis获取的数据都是bytes类型的,所以key的类型是 bytes 类型
if src_redis.type(key) == b'string':# 也可以用decode() 把key转换成string,这样等号右边就不需要加b
v = src_redis.get(key) #先获取原来的数据
target_redis.set(key,v) #set到新的数据库里
else:
all_hash_data = src_redis.hgetall(key) # 获取hash类型里面所有的数据,获取出来的数据是字典格式的 但是有b,需要转换
for k,v in all_hash_data.items():# 因为获取到字典格式的hash类型的原数据有b,所以需要用for循环来进行转换后重新赋值给新的数据库
target_redis.hset(key,k,v) # key是外面的,k是里面的key,v是k对应的value