Redis

1.安装

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
1. 下载
2. tar
3. cd redis-3.xxxx/deps
4. make geohash-int hiredis jemalloc linenoise lua
-----------------
5. cd .. 退回到 redis 根目录
$redis-3.xxxx> make test
等待几分钟, 等待编译完成
会出现 `编译完成没有错误`的英文...
然后直接 make

6. 接下来就是执行 make install 安装了启动等脚本了
默认会自动安装到 /usr/local/bin
make 后面跟上 PREFIX=path 可以自己指定安装路径
这里安装到 ~/apps/ 下的 redis 目录中
$> sudo make PREFIX=/home/ap/apps/redis install

7.
# 复制配置文件到redis安装目录
$redis-3.xxxx> sudo cp redis.conf /home/ap/apps/redis

> vi redis.conf
-------------------------
# 修改 redis-server 后台启动(也可以不配, 用 nohup的方式后台启动)
daemonize yes

#开启aof日志,它会每次写操作都记录一条日志
appendonly yes

# 修改 protected-mode 为 no
protected-mode no

# 注释掉 bind 本机端口
# bind 127.0.0.1 ::1

# 配置环境变量
export REDIS_HOME=/home/ap/apps/redis
export PATH=$PATH:$REDIS_HOME/bin

# 把环境变量 .zshrc 和 存有 sh 命令的 redis 安装目录发送到其它机器

# source 要使用机器的 .zshrc配置

8.  启动试试看~
# 如果配置了后台启动, 并且加载了配置文件的话就不会出现以下图形了
$> redis-server ~/apps/redis/redis.conf
也可以直接 $> redis-server
这样就不会加载配置文件, 会出现下面的..
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 4.0.10 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 1034
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

出现一个这个吊的图像, 就说明启动成功了.....

# 也可以这样后台启动, 带 log
nohup /home/ap/apps/redis/bin/redis-server /home/ap/apps/redis/redis.conf 1>/home/ap/logs/redis_log/redis_std.log 2>/home/ap/logs/redis_log/redis_error.log &

7、启动客户端,执行命令:
[hadoop@hadoop02 ~]$ redis-cli

#  查看是否有进程
ps -ef | grep redis
--------
[ap@cs1]~% ps -ef | grep redis
ap 36260 35955 0 14:11 pts/3 00:00:00 redis-server *:6379
ap 36287 36267 0 14:11 pts/5 00:00:00 grep redis
>> 最开始的 36260 就是 pid


9.  连接
# redis/bin/redis-cli [-h localhost -p 6379 ]

# 如果是本机启动客户端
$> redis-cli

# 如果是从其他节点上链接 redis,那么可以这么做:
$cs2> redis-cli -h cs1 -p 6379
cs1:6379> ping
PONG

10. 停止服务
cli端: shutdown save|nosave

# --------------------------------
>>> 其它设置
1. 使用命令行客户的连接redis
redis-cli -p 6379
2. 关闭redis
redis-cli shutdown
17.配置redis密码
config set requirepass 123
密码登录>
auto 123

2.Redis 注意点

REmote DIctionary Server(Redis)

Redis 常被称作是一款 key-value 内 存存储系统或者内存数据库,同时由于它支持丰富的数据结构,又被称为一种数据结构服务 器(Data Structure Server)。

因为值(value)可以是字符串(String),哈希(Map),列表(list), 集合(sets)和有序集合(sorted sets)等类型。

与其他 key-value 缓存产品比较独有特点

1、Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载 进行使用。

2、Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据 结构的存储。

3、Redis 支持数据的备份,即 master-slave 模式的数据备份。

Redis优势

1、性能极高:Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s 。

2、丰富的数据类型:Redis 支持二进制案例的 String, List, Hash, Set 及 Sorted Set 数据类型操 作。

3、原子操作:Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子 性执行。

4、丰富的特性:Redis 还支持 Publish/Subscribe,通知 key 过期,支持高可用集群等等特性。

5、数据持久化机制

持久化机制有两种:

1、RDB 方式:定期将内存数据 dump 到磁盘

2、AOF(append only file)持久化机制:用记日志的方式记录每一条数据更新操作,一旦 出现灾难事件,可以通过日志重放来恢复整个数据库

Redis适用场景

1、TopN 需求:取最新的 n 个数据,如读取作家博客最新的 50 篇文章,通过 List 实现按时 间排序的数据的高效获取

2、排行榜应用:以特定条件为排序标准,将其设成 sorted set 的 score,进而实现高效获取

3、需要精准设定过期时间的应用:把 sorted set 的 score 值设置成过期时间的时间戳,那么 就可以简单地通过过期时间排序,定时清除过期数据了

4、计数器应用:Redis 的命令都是原子性的,可以轻松地利用 INCR,DECR 命令来构建计数 器系统。

5、去除大量数据中的重复数据:将数据放入 set 中,就能实现对重复数据的排除

6、构建队列系统:使用 list 可以构建队列系统,使用 sorted set 甚至可以构建有优先级的队 列系统。

7、实时系统,反垃圾系统:通过上面说到的 set 功能,你可以知道一个终端用户是否进行 了某个操作,可以找到其操作的集合并进行分析统计对比等。

8、Publish/SubScribe 构建实时消息系统

9、缓存(会话,商品列表,评论列表,经常查询的数据等)

3.Redis基本操作

具体见文档

也可以查询网站

3.1 字符串

3.1.1 String shell

String 类型是二进制安全的。意思是 Redis 的 String 可以包含任何数据。比如 jpg 图片或者序 列化的对象 。

String 类型是 Redis 最基本的数据类型,一个键最大能存储 512MB。

image-20180703231528195

image-20180704153111079

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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
如果给定了 NX 选项,那么命令仅在键 key 不存在的情况下,才进行设置操作;如果键 key 已经存 在,那么 SET ... NX 命令不做动作(不会覆盖旧值)。===> not exist

如果给定了 XX 选项,那么命令仅在键 key 已经存在的情况下,才进行设置操作;如果键 key 不存 在,那么 SET ... XX 命令不做动作(一定会覆盖旧值)。

# 简单存取
----------------------------------------------------------------------
复杂度为 O(1) 。
redis> SET msg "hello world"
OK
redis> GET msg
hello world

# 删除
del key1 key2 ... Keyn
--------------------------------------------------------------------
作用: 删除1个或多个键
返回值: 不存在的key忽略掉,返回真正删除的key的数量

# 给key 设置新值
rename key newkey
------------------------------------------------------------------------
作用: 给key赋一个新的key名
注:如果newkey已存在,则newkey的原值被覆盖


# multi 存取
----------------------------------------------------------------------
# 中间的分隔符是自己指定的
127.0.0.1:6379> mset ss::name "airpoet" ss::age "a8" ss::like "ycy"
OK
127.0.0.1:6379> mget ss::name ss::age ss::like
1) "airpoet"
2) "a8"
3) "ycy"

127.0.0.1:6379> mset ss.age "18" ss.teacher "noone"
OK
127.0.0.1:6379> mget ss.age ss::age
1) "18"
2) "a8"

# msetnx
----------------------------------------------------------------------
只有在所有给定键都不存在的情况下, MSETNX 会为所有给定键设置值,效果和同时执行多个 SETNX 一样。如果给定的键至少有一个是存在的 ,那么 MSETNX 将不执行任何设置操作。
redis> MSETNX nx-1 "hello" nx-2 "world" nx-3 "good luck" 1
redis> SET ex-key "bad key here"
OK
redis> MSETNX nx-4 "apple" nx-5 "banana" ex-key "cherry" nx-6 "durian"
0


# 设置新值并返回旧值
GETSET key new-value
------------------------------------------------------------------------------------
# 先设置
redis> SET getset-str "i'm old value"
OK
# 设置新的值, 返回旧值
redis> GETSET getset-str "i'm new value"
i'm old value
# 重新 get 一下, 发现已是新值
redis> GET getset-str
i'm new value


#追加内容到字符串末尾
APPEND key value
------------------------------------------------------------------------------------
redis> SET myPhone "nokia"
OK
redis> APPEND myPhone "-1110" (integer)
10
redis> GET myPhone
"nokia-1110"

#返回值的长度
------------------------------------------------------------------------------------
redis> SET msg "hello"
OK
redis> STRLEN msg
(integer) 5 redis>

APPEND msg " world"
(integer) 11
redis> STRLEN msg (integer) 11

==========================================================================================
# 索引
-------------------------------------------------------------------------------------
$ 如果字符串长度为 n
从左到右>> 0 ~ n-1
从右到做>> -1 ~ -n

# 范围设置
SETRANGE key index value
-------------------------------------------------------------------------------------
从索引 index 开始,用 value 覆写(overwrite)给定键 key 所储存的字符串值。只接受正数索引。
redis> SET msg "hello"
OK
redis> SETRANGE msg 1 "appy"
(integer) 5
redis> GET msg
"happy"

# 范围取值
GETRANGE key start end
-------------------------------------------------------------------------------------
返回键 key 储存的字符串值中,位于 start 和 end 两个索引之间的内容(闭区间,start 和 end 会被包括 在内)。和 SETRANGE 只接受正数索引不同, GETRANGE 的索引可以是正数或者 负数。
redis> SET msg "hello world"
OK
redis> GETRANGE msg 0 4
"hello"
# 从后往前取范围, 依然是从左往右(从前往后)读的
redis> GETRANGE msg -5 -1
"world"

==========================================================================================

# 数字操作
-------------------------------------------------------------------------------------

只要储存在字符串键里面的值可以被解释为 64 位整数,或者 IEEE-754 标准的 64 位浮点数,
那么用户就可以对这个字符串键执行针对数字值的命令。

# 增加或者减少数字的值
INCRBY, DECRBY
可实现案例: 计数器(counter), id 生成器
# 键 num 不存在,命令先将 num 的值初始化为 0 ,然后再执行加 100 操作
redis> INCRBY num 100
(integer) 100
redis> DECRBY num 50
(integer) 50


# 浮点数的自增和自减
INCRBYFLOAT key increment
减的话 用负数
-------------------------------------------------------------------------------------
redis> SET num 10
OK
redis> INCRBYFLOAT num 3.14
"13.14"
redis> INCRBYFLOAT num -2.04
"11.1"

即使字符串键储存的是数字值,它也可以执行 APPEND、STRLEN、SETRANGE 和 GETRANGE
当用户针对一个数字值执行这些命令的时候,Redis 会先将数字值转换为字符串,然后再执行命令。


==========================================================================================

# 二进制的操作
索引是从右往左递增
具体查看
应用案例: 实现在线人数统计
应用案例: 使用 Redis 缓存热门图片(二进制)
https://app.yinxiang.com/shard/s37/nl/7399077/92dcb5fb-c005-45dc-bcc5-d00863c08f84/


==========================================================================================


# 储存中文时的注意事项
--------------------------------------------
一个英文字符只需要使用 单个字节来储存,而一个中文字符却需要使用多个字 节来储存。
STRLEN、SETRANGE 和 GETRANGE 都是为英文设置的,它们只会在字符为单个字节的情况下正常 工作,而一旦我们储存的是类似中文这样的多字节字符,那么这三个命令就不再适用了。

# 在 redis-cli 中使用中文时,必须打开 --raw 选项,才能正常显示中文
$ redis-cli --raw

$ redis> SET msg "世界你好"
OK
$ redis> GET msg
世界你好
# strlen 只能获取字节
$redis> STRLEN msg
12


==========================================================================================

# 移动库
move key db
(注意: 一个redis进程,打开了不止一个数据库, 默认打开16个数据库,从0到15编号,如果想打开更多数据库,可以从配置文件修改)
---------------------------------------------------------------------------
redis 127.0.0.1:6379[1]> select 2
OK
redis 127.0.0.1:6379[2]> keys *
(empty list or set)
redis 127.0.0.1:6379[2]> select 0
OK
redis 127.0.0.1:6379> keys *
1) "name"
2) "cc"
3) "a"
4) "b"

# 移动key 到其它的库
redis 127.0.0.1:6379> move cc 2
(integer) 1
redis 127.0.0.1:6379> select 2
OK
redis 127.0.0.1:6379[2]> keys *
1) "cc"
redis 127.0.0.1:6379[2]> get cc
"3"


# 插入和读取一条 String 类型的数据
127.0.0.1:6379> set name huangbo
OK
127.0.0.1:6379> get name
"huangbo"
127.0.0.1:6379>
------------------------
#对 string 类型数据进行增减(前提是这条数据的 value 可以看成数字)
127.0.0.1:6379> set number 10
OK
127.0.0.1:6379> incr number
(integer) 11
127.0.0.1:6379> decr number
(integer) 10
127.0.0.1:6379> incrby number 10
(integer) 20
127.0.0.1:6379> decrby number 5
(integer) 15
------------------------
# 一次性插入或者获取多条数据
127.0.0.1:6379> mset name huangbo age 18 facevalue 99
OK
127.0.0.1:6379> mget name age facevalue
1) "huangbo"
2) "18"
3) "99"
------------------------
# 在插入一条 string 类型数据的同时为它指定一个存活期限
解释: 表示 yu 的 jiyi 只有 7s,7s 之后会自动清除这个 key-value
127.0.0.1:6379> setex yu 7 jiyi
OK
127.0.0.1:6379> get yu
"jiyi"
127.0.0.1:6379> get yu
(nil)

3.1.2 String 使用案例

见 github

3.2 list

image-20180704194800589

3.2.1 List常用操作示例

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
# 从头部(左边left)插入数据
127.0.0.1:6379> lpush names huangbo xuzheng wangbaoqiang
(integer) 3
127.0.0.1:6379> lrange names 0 -1
1) "wangbaoqiang"
2) "xuzheng"
3) "huangbo"

# 从尾部(右边right)插入数据
127.0.0.1:6379> rpush names ycy cy y
(integer) 6

# 读取 list 中指定范围的 values
==> 好像不支持负数索引 ??
==> 索引是闭区间
127.0.0.1:6379> lrange names 0 -1
1) "wangbaoqiang"
2) "xuzheng"
3) "huangbo"
4) "ycy"
5) "cy"
6) "y"

127.0.0.1:6379> LRANGE names 0 0
1) "wangbaoqiang"

# 从头部(前面)弹出一个元素
127.0.0.1:6379> lpop names
"wangbaoqiang"

127.0.0.1:6379> LRANGE names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"
4) "cy"
5) "y"

# 从尾部(最后right)弹出一个元素
127.0.0.1:6379> rpop names
"y"
127.0.0.1:6379> LRANGE names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"
4) "cy"

# 从前面 list 的尾部(right)弹出(pop)一个元素 压入(push)到 后面 list的 头部(left)
-----------------------------------
127.0.0.1:6379> LRANGE names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"
4) "cy"
127.0.0.1:6379> LRANGE nickname 0 -1
1) "YangChaoyue"
2) "WuXuanyi"
3) "MengMeiqi"

127.0.0.1:6379>  RPOPLPUSH names nickname 
"cy"
127.0.0.1:6379> LRANGE nickname 0 -1
1) "cy"
2) "YangChaoyue"
3) "WuXuanyi"
4) "MengMeiqi"
127.0.0.1:6379> LRANGE names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"

# 求 list 的长度

3.2.2 list应用案例

代码见 github

3.3 Set 集合

3.3.1 Set 的 shell 使用

Redis 的 Set 是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

image-20180704191032216

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
# 插入 set 数据 & 查询 set 数据
127.0.0.1:6379> sadd bigdata hadoop spark hive
(integer) 3
127.0.0.1:6379> scard bigdata
(integer) 3
127.0.0.1:6379> smembers bigdata
1) "hadoop"
2) "spark"
3) "hive"

# 判断一个成员是否属于某条指定的 set 数据
127.0.0.1:6379> sismember bigdata hbase
(integer) 0
127.0.0.1:6379> sismember bigdata hive
(integer) 1

# 求两个 set 数据的差集
$ setdiff [set1] [set2]
> 删除 set1 中 set1,set2的交集, 返回删除操作后的 set1(对 set1 的真实存储不影响)
127.0.0.1:6379> SMEMBERS bigdata
1) "hadoop"
2) "spark"
3) "hive"
127.0.0.1:6379> SMEMBERS bigdata2
1) "hbase"
2) "spark"
3) "storm"
127.0.0.1:6379> sdiff bigdata bigdata2
1) "hadoop"
2) "hive"

# 将上面2者的差集, 存入一个新的 set
127.0.0.1:6379> sdiffstore bigdatadiff bigdata bigdata2
(integer) 2
127.0.0.1:6379> SMEMBERS bigdatadiff
1) "hadoop"
2) "hive"


# 求交集 & 存入新的集合
127.0.0.1:6379> SMEMBERS bigdata
1) "hadoop"
2) "spark"
3) "hive"
127.0.0.1:6379> SMEMBERS bigdata2
1) "hbase"
2) "spark"
3) "storm"
127.0.0.1:6379> SINTER bigdata bigdata2
1) "spark"

127.0.0.1:6379> SINTERSTORE interdb bigdata bigdata2
(integer) 1
127.0.0.1:6379> SMEMBERS interdb
1) "spark"


# 求并集 sunion
127.0.0.1:6379> SMEMBERS bigdata
1) "hadoop"
2) "spark"
3) "hive"
127.0.0.1:6379> SMEMBERS bigdata2
1) "hbase"
2) "spark"
3) "storm"

# 返回并集
127.0.0.1:6379> sunion bigdata bigdata2
1) "hadoop"
2) "spark"
3) "hive"
4) "storm"
5) "hbase"

# 存储并集
127.0.0.1:6379> SUNIONSTORE uniondb bigdata bigdata2
(integer) 5
127.0.0.1:6379> SMEMBERS uniondb
1) "hadoop"
2) "spark"
3) "hive"
4) "storm"
5) "hbase"

3.3.2 set 使用案例

见 github

3.4 ZSet–有序集合

Zset 和 Set 一样也是 String 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的成员 进行从小到大的排序。

Zset 的成员是唯一的,但分数(score)却可以重复。

image-20180704194727065

3.4.1 zset shell操作

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 库中插入一条 sortedset 数据
# 注意: sco在前面(float), 可重复, 'value'不可重复
$ zadd [zset名] sco 'value'
127.0.0.1:6379> zadd yanzhi 70 huangbo 90 xuzheng 80 wangbaoqiang
(integer) 3

# 升序排列
127.0.0.1:6379> zrange yanzhi 0 4
1) "huangbo"
2) "wangbaoqiang"
3) "xuzheng"

# 降序排列
127.0.0.1:6379> zrevrange yanzhi 0 4
1) "xuzheng"
2) "wangbaoqiang"
3) "huangbo"


# 查询某个成员的名次
127.0.0.1:6379> zrevrange yanzhi 0 4
1) "xuzheng"
2) "wangbaoqiang"
3) "huangbo"
127.0.0.1:6379> zrank yanzhi huangbo
(integer) 0
127.0.0.1:6379> zrank yanzhi wangbaoqiang
(integer) 1
127.0.0.1:6379> zrank yanzhi xuzheng
(integer) 2

#修改成员的分数
zincrby yanzhi 50 huangbo
"120"

127.0.0.1:6379> ZRANGE yanzhi 0 -1
1) "wangbaoqiang"
2) "xuzheng"
3) "huangbo"

$ zrevrank 返回 huangbo 的 yanzhi 排名
$ 'value' huangbo 不能重复, 对应的分数可以重复
127.0.0.1:6379> zrevrank yanzhi huangbo
(integer) 0

3.4.2 案例

Lol 盒子英雄数据排行榜:

1、在 redis 中需要一个榜单所对应的 sortedset 数据

2、玩家每选择一个英雄打一场游戏,就对 sortedset 数据的相应的英雄分数+1

3、Lol 盒子上查看榜单时,就调用 zrange 来看榜单中的排序结果

代码见 github

3.5 散列 – Hash– 哈希表

一个散列由多个域值对(field-value pair)组成,散列的键和值都可以是文字、整数、浮点数或者二 进制数据。

同一个散列里面的各个域必 须是独一无二的,但不同域的 值可以是重复的。

尽量使用散列键而不是字符串键来储存键值对数据,因为散列键管理方便、能够避免键名冲突、并且还能够节约内存。

Redis Hash 是一个键值对集合。

Redis Hash 类型可以看成具有 String Key 和 String Value 的 map 容器

Redis Hash 是一个 String 类型的 field 和 value 的映射表,Hash 特别适合用于存储对象。

image-20180703231738040

image-20180704203528166

3.5.1 shell 操作

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
# 散列(hash)
类似于 HashMap
一个散列由多个域 值对(field-value pair)组成,散列的域和值都可以是文字、整数、浮点数或者二 进制数据
同一个散列里面的每个域必 须是独一无二、各不相同 的,而域的值则没有这一要求,换句话说,不同域的值 可以是重的。

#关联域值对
HSET key field value
HGET key field
:TODO 真正的 kv 是 f v, 那设置这个 key 干嘛的? ? 👌
===> key 是散列名称, 比如 散列 message 的 field-value pair 是"sender" "peter"
-------------------------------------------------------------------------------------
redis> HSET message "id" 10086
(integer) 1
redis> HSET message "sender" "peter"
(integer) 1
redis> HSET message "receiver" "jack"
(integer) 1


==========================================================================================


#一次设置或获取散列中的多个域值对
HMSET key field value [field value ...]
HMGET key field [field ...]

redis> HMSET message "id" 10086 "sender" "peter" "receiver" "jack"
OK
redis> HMGET message "id" "sender" "receiver"
1) "10086"
2) "peter"
3) "jack"



#获取散列包含的所有域、值、或者域值对
HKEYS key => 返回散列键 key 包含的所有域。
HVALS key => 返回散列键 key 中,所有域的值。
HGETALL key => 返回散列键 key 包含的所有域值对。

redis> HKEYS message 1) "id"
2) "sender"
3) "receiver"
4) "date"
5) "content"

redis> HVALS message
1) "10086"
2) "peter"
3) "jack"
4) "2014-8-3 3:25 p.m."
5) "Good morning, jack!"


redis> HGETALL message
1) "id" #域
2) "10086" #值
3) "sender" #域
4) "peter" #值
5) "receiver"
6) "jack"
7) "date"
8) "2014-8-3 3:25 p.m." 9) "content"
10) "Good morning, jack!"

================================================================


# hset , hget , hgetall
127.0.0.1:6379> hset shouji iphone 8
(integer) 1
127.0.0.1:6379> hset shouji xiaomi 7
(integer) 1
127.0.0.1:6379> hset shouji huawei p20
(integer) 1
127.0.0.1:6379> hgetall shouji
1) "iphone"
2) "8"
3) "xiaomi"
4) "7"
5) "huawei"
6) "p20"
127.0.0.1:6379> hget shouji huawei
"p20"


# 取出 hash 数据中所有 fields / values
127.0.0.1:6379> hkeys shouji
1) "iphone"
2) "xiaomi"
3) "huawei"
127.0.0.1:6379> hvals shouji
1) "8"
2) "7"
3) "p20"


# 为 hash 数据中指定的一个 field 的值进行增减
127.0.0.1:6379> hincrby shouji xiaomi 10
(integer) 17
127.0.0.1:6379> hget shouji xiaomi
"17"


# 从 hash 数据中删除一个字段 field 及其值
127.0.0.1:6379> HGETALL shouji
1) "iphone"
2) "8"
3) "xiaomi"
4) "17"
5) "huawei"
6) "p20"
127.0.0.1:6379> hdel shouji iphone
(integer) 1
127.0.0.1:6379> HGETALL shouji
1) "xiaomi"
2) "17"
3) "huawei"
4) "p20"

3.5.2 案例: 实现购物车

需求点:

加入购物车

查询购物车

修改购物车

清空购物车

代码见 github

3.6 键值相关命令

image-20180704205133934

shell简单实用

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
# 查询所有 cart 开头的 key
127.0.0.1:6379> keys cart*
1) "cart:huangbo"
2) "cart:xuzheng"
3) "cart:wangbaoqiang"

# 查询所有 key
127.0.0.1:6379> keys *
1) "bigdata:hadoop"
2) "uniondb"
3) "myset"
4) "cart:huangbo"
5) "bigdata:spark"
.....

# 判断一个 key 是否存在 exists
127.0.0.1:6379> EXISTS bigdata
(integer) 1

# del 删除一个或多个 key
127.0.0.1:6379> del facevalue
(integer) 1

# type 查看类型, 便于针对类型操作
127.0.0.1:6379> type bigdata
set
127.0.0.1:6379> SMEMBERS bigdata
1) "hadoop"
2) "spark"
3) "hive"

# expire [key] second 设置一个 key 的过期时间, 单位是秒
127.0.0.1:6379> type names
list
127.0.0.1:6379> lrange names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"
$ 127.0.0.1:6379> expire names 5
(integer) 1
127.0.0.1:6379> lrange names 0 -1
1) "xuzheng"
2) "huangbo"
3) "ycy"
$ 127.0.0.1:6379> lrange names 0 -1
(empty list or set)

# expireat [key] timestamp 设置在时间戳 timestamp 过期

# ttl [key] 查询 key 的有效时长, 不存在或没有超时设置, 返回 -1
127.0.0.1:6379> ttl bigdata
(integer) -1

# move [key] database 将当前库中的 key 移动到其它数据库中
# 默认是有16个 databases 0-15
127.0.0.1:6379> move bigdata 2
(integer) 1
127.0.0.1:6379> type bigdata
none
127.0.0.1:6379> SMEMBERS bigdata
(empty list or set)
127.0.0.1:6379> SELECT 2
OK
127.0.0.1:6379[2]> keys *
1) "bigdata"
127.0.0.1:6379[2]> SMEMBERS bigdata
1) "hadoop"
2) "spark"
3) "hive"

# 移除给定key 的过期时间
127.0.0.1:6379[2]> keys *
1) "bigdata"
127.0.0.1:6379[2]> expire bigdata 100
(integer) 1
127.0.0.1:6379[2]> ttl bigdata
(integer) 96
127.0.0.1:6379[2]> persist bigdata
(integer) 1
127.0.0.1:6379[2]> ttl bigdata
(integer) -1

# 随机获取 key空间中的一个
127.0.0.1:6379> randomkey
"student_tang"

# 重命名 key
127.0.0.1:6379> zrange yanzhi 0 -1
1) "wangbaoqiang"
2) "xuzheng"
3) "huangbo"
127.0.0.1:6379> rename yanzhi shuiMa
OK
127.0.0.1:6379> zrange shuiMa 0 -1
1) "wangbaoqiang"
2) "xuzheng"
3) "huangbo"

# renamenx [key] newkey 如果 newkey 存在, 则失败返回0

# type [key] 查询 key 的类型

3.7 服务器相关命令

image-20180704205206183

简单测试

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 默认数据库编号 0-15)
127.0.0.1:6379> select 3
OK

# quit
退出

# echo msg 发现不能有空格
127.0.0.1:6379[3]> echo 要吐了
"\xe8\xa6\x81\xe5\x90\x90\xe4\xba\x86"

127.0.0.1:6379[3]> echo iFellSick
"iFellSick"

# dbsize 返回当期那数据库中的 key 的条数
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> DBSIZE
(integer) 23

# flushdb 删除当前选择数据库中的所有key
127.0.0.1:6379[2]> dbsize
(integer) 1
127.0.0.1:6379[2]> keys *
1) "bigdata"
127.0.0.1:6379[2]> flushdb
OK
127.0.0.1:6379[2]> keys *
(empty list or set)

# flushall 删除所有数据库中的所有的 key

3.8 pom 文件

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.rox</groupId>
<artifactId>Redis_Demo</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

</dependencies>

</project>

5. Redis高可用

Redis 中,实现高可用的技术主要包括持久化、复制、哨兵和集群

第一:持久化

持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份, 即将数据存储在硬盘,保证数据不会因进程退出而丢失。

第二:主从复制

复制是高可用 Redis 的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现 了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无 法自动化;写操作无法负载均衡;存储能力受到单机的限制。

第三:哨兵

哨兵在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存 储能力受到单机的限制。

第四集群

通过集群,Redis 解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现 了较为完善的高可用方案。

5.1 Redis 持久化

Redis 持久化分为 RDB 持久化AOF 持久化

RDB:将当前数据保存到硬盘

AOF:将每次执行的写命令保存到硬盘(类似于 MySQL 的 binlog)

由于 AOF 持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此 AOF 是目前主 流的持久化方式,不过 RDB 持久化仍然有其用武之地。

5.1.1 RDB
  • 手动方式

    • save
      • save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止
    • bgsave
      • bgsave 命令会创建一个子进程,由子进程来负责创建 RDB 文件,父进程(即 Redis 主 进程)则继续处理请求。
  • 自动触发

    • 在配置文件redis.conf中配置 save m n

    • image-20180704212733947

    • save 900 1

      save 300 10

      save 60 10000

      #900 秒内如果超过 1 个 key 被修改,则发起快照保存

      #300 秒内容如超过 10 个 key 被修改,则发起快照保存

      #60 秒内容如超过 10000 个 key 被修改,则发起快照保存

5.1.2 AOF 方式

AOF 比快照方式有更好的持久化性,是由于在使用 AOF 持久化方式时,Redis 会将每一个收 到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。当 Redis 重启时会通过 重新执行文件中保存的写命令来在内存中重建整个数据库的内容

我们可以通过配置文件告诉 redis 我们想要 通过 fsync 函数强制 os 写入到磁盘的时机。

三种方式如下(默认是:每秒 fsync 一次)

image-20180704213033435

5.2 Redis 主从复制

Redis 主从复制配置和使用都非常简单。通过主从复制可以允许多个 slave server 拥有和 master server 相同的数据库副本。下面是关于

redis 主从复制的一些特点:

1、master 可以有多个 slave

2、除了多个 slave 连到相同的 master 外,slave 也可以连接其他 slave 形成图状结构

3、主从复制不会阻塞 master。也就是说当一个或多个 slave 与 master 进行初次同步数据时, master 可以继续处理 client 发来的请求。相反 slave 在初次同步数据时则会阻塞不能处理 client 的请求。

4、主从复制可以用来提高系统的可伸缩性,我们可以用多个 slave 专门用于 client 的读请求, 比如 sort 操作可以使用 slave 来处理。也可以用来做简单的数据冗余

5、可以在 master 禁用数据持久化,只需要注释掉 master 配置文件中的所有 save 配置,然 后只在 slave 上配置数据持久化。

主从复制的过程

当设置好 slave 服务器后,slave 会建立和 master 的连接,然后发送 sync 命令。

无论是第一 次同步建立的连接还是连接断开后的重新连接,master 都会启动(fork)一个后台进程,将数 据库快照保存到文件中(fork 一个进程入内在也被复制了,即内存会是原来的两倍),同时 master 主进程会开始收集新的写命令并缓存起来。

后台进程完成写文件后,master 就发送 文件给 slave,slave 将文件保存到磁盘上,然后加载到内存恢复数据库快照到 slave 上。

接着 master 就会把缓存的命令转发给 slave。

而且后续 master 收到的写命令都会通过开始建立 的连接发送给 slave。

从 master 到 slave 的同步数据的命令和从 client 发送的命令使用相同的 协议格式。

当 master 和 slave 的连接断开时 slave 可以自动重新建立连接。

如果 master 同时 收到多个 slave 发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给 所有 slave。

配置 slave 服务器只需要在配置文件中加入如下配置:

slaveof cs1 6379 #指定 master 的 ip 和端口

注意:主节点不用加!!!

5.3 Redis 集群

参考文档

1
2
3
4
5
6
7
8
9
10
1. 拿到本机的 ip
ifconfig eth0 | grep "inet addr" | awk -F : '{print $2}' | awk '{print $1}'

sed -i "s/# bind 127.0.0.1/bind $HOST/" /home/ap/apps/redis/redis.conf
# 此处发现另一个地方也改动了, 注意

sed -i 's/# cluster-enabled yes/cluster-enabled yes/' /home/ap/apps/redis/redis.conf
sed -i 's/appendonly no/appendonly yes/' /home/ap/apps/redis/redis.conf
sed -i 's/# cluster-node-timeout 15000/cluster-node-timeout 5000/' /home/ap/apps/redis/redis.conf
sed -i 's/# cluster-node-timeout 15000/cluster-node-timeout 5000/' /home/ap/apps/redis/redis.conf

5.4 开源 redis GUI 管理界面

推荐使用的可视化工具为TreeSoft,其官方网站地址为:TreeSoft地址,你可以在其官网上下载相应的软件,然后解压完有相应的使用说明文件,详细如下:

默认用户名:treesoft,密码:treesoft ,用户:admin,密码:treesoft

由于其内嵌了Tomcat,很是不方便,我想使用自己部署的Tomcat来运行此服务,则可以将webapps目录下treenms项目拷贝之后放到我们自己Tomcatwebapps目录,然后浏览器输入http://cs1:8080/treenms/treesoft 即可

6.Redis.conf配置文件参数说明

redis.conf 配置项说明如下:

1、Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进 程

daemonize no

2、当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入/var/run/redis.pid 文件,可以 通过 pidfile 指定

pidfile /var/run/redis.pid

3、指定 Redis 监听端口,默认端口为 6379,作者在自己的一篇博文中解释了为什么选用 6379 作为默认端口,因为 6379 在手机按键上 MERZ 对应的号码,而 MERZ 取自意大利歌 女 Alessia Merz 的名字

port 6379

4、绑定的主机地址

bind 127.0.0.1

5、当客户端闲置多长时间后关闭连接,如果指定为 0,表示关闭该功能

timeout 300

6、指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默 认为 verbose

loglevel verbose

7、日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配 置为日志记录方式为标准输出,则日志将会发送给/dev/null

logfile stdout

8、设置数据库的数量,默认数据库为 0,可以使用 SELECT 命令在连接上指定数据 库 id

databases 16

9、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配 合

save \<seconds> \<changes>

Redis 默认配置文件中提供了三个条件:

save 900 1

save 300 10

save 60 10000

分别表示 900 秒(15 分钟)内有 1 个更改,300 秒(5 分钟)内有 10 个更改以及 60 秒 内有 10000 个更改。

10、指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了 节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大

rdbcompression yes

11、指定本地数据库文件名,默认值为 dump.rdb

dbfilename dump.rdb

12、指定本地数据库存放目录

dir ./

13、设置当本机为 slave 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它 会自动从 master 进行数据同步

slaveof \<masterip> \<masterport>

14、当 master 服务设置了密码保护时,slav 服务连接 master 的密码

masterauth \<master-password>

15、设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH \<password>命令提供密码,默认关闭

requirepass foobared

16、设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数 为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当 客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息

maxclients 128

17、指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后, Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置, 将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放 内存,Value 会存放在 swap 区

maxmemory \<bytes>

18、指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入 磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数 据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默 认为 no

appendonly no

19、指定更新日志文件名,默认为 appendonly.aof

appendfilename appendonly.aof

20、指定更新日志条件,共有 3 个可选值:

no:表示等操作系统进行数据缓存同步到磁盘(快)

always:表示每次更新操作后手动调用 fsync()将数据写到磁盘(慢,安全)

everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec

21、指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页 存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换 出到内存中(在后面的文章我会仔细分析 Redis 的 VM 机制)

vm-enabled no

22、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个 Redis 实例共享

vm-swap-file /tmp/redis.swap

23、将所有大于 vm-max-memory 的数据存入虚拟内存,无论 vm-max-memory 设置多小,所 有索引数据都是内存存储的(Redis 的索引数据 就是 keys),也就是说,当 vm-max-memory 设 置为 0 的时候,其实是所有 value 都存在于磁盘。默认值为 0

vm-max-memory 0

24、Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,vm-page-size 是要根据存储的 数据大小来设定的,作者建议如 果存储很多小对象,page 大小最好设置为 32 或者 64bytes;如果存储很大大对象,则可 以使用更大的 page,如果不 确定,就使用默认值

vm-page-size 32

25、设置 swap 文件中的 page 数量,由于页表(一种表示页面空闲或使用的 bitmap)是 在放在内存中的,,在磁盘上每 8 个 pages 将消耗 1byte 的内存。

vm-pages 134217728

26、设置访问 swap 文件的线程数,最好不要超过机器的核数,如果设置为 0,那么所有对 swap 文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为 4

vm-max-threads 4

27、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

glueoutputbuf yes

28、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算 法

hash-max-zipmap-entries 64

hash-max-zipmap-value 512

29、指定是否激活重置哈希,默认为开启(后面在介绍 Redis 的哈希算法时具体介绍)

activerehashing yes

30、指定包含其它的配置文件,可以在同一主机上多个 Redis 实例之间使用同一份配置文 件,而同时各个实例又拥有自己的特定配置文件

include /path/to/local.conf

7.其它需要了解的

添加注释的技巧:

最好的注释方式: 少而精

完成一段小逻辑的代码就加个注释

容易费解的地方加注释

拓展: 缓存淘汰算法 :TODO

NRU(Not recently used)

FIFO(First-in, first-out)

Second-chance

LRU(Least recently Used)

redis 的内存模型

Hadoop3.0 新特性机制

纠删码 机制

事务

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
# 基本操作
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> set c 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK

# 目前事务存在的问题
如果一条命令是因为语法错误, 事务整体执行不成功
但是如果没有语法错误, 而是类型判断错误, 事务里面能执行成功的会执行成功, 不能执行成功的就不会执行成功, 这样就破坏了事务的原子性
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set aa 1
QUEUED
127.0.0.1:6379> set bb 2
QUEUED
127.0.0.1:6379> SDIFF aa bb
QUEUED
127.0.0.1:6379> set cc 4
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
4) OK

监听

1
2
3
4
watch xx
unwatch
机制: 在事务之前监听一个变量的变化
如果一个变量有变化, 那之后的事务就取消执行

Redis 中的事务 :TODO

  • 参考 CZ 的 word 文档

8.Redis 错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 错误1
(error) MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.
127.0.0.1:6379> config set stop-writes-on-bgsave-error no
OK
127.0.0.1:6379> ping
PONG


# 错误2
cc: ../deps/hiredis/libhiredis.a: No such file or directory
cc: ../deps/lua/src/liblua.a: No such file or directory
cc: ../deps/geohash-int/geohash.o: No such file or directory
cc: ../deps/geohash-int/geohash_helper.o: No such file or directory
make[1]: * [redis-server] Error 1
make[1]: Leaving directory `/usr/local/src/redis-3.2.9/src'
make: * [all] Error 2
----------------
解决办法
进入源码包目录下的deps目录中执行
make geohash-int hiredis jemalloc linenoise lua
然后再进行../make编译就可以了
再 $> sudo make PREFIX=/usr/local/redis install

9.参考资料

文档PDF

Java / Scala 操作 Redis ===> Jedis

如果帮到你, 可以给我赞助杯咖啡☕️
0%