-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy path4、五种基本类型对象实现.md
299 lines (117 loc) · 13.5 KB
/
4、五种基本类型对象实现.md
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#### 字符串对象
字符串对象的编码可以是int、raw或者embstr
* 注意:listnode#value、dictEntry#key、zskipListnode#obj,里面的值对象都是字符串对象(即 redisObject 且 type = string)。字符串对象是Redis五种类型的对象中唯一一种会被其他四种类型对象嵌套的对象。
* 下面示例图片里的 “StringObject" 就是 redisObject 的 String 类型
##### embstr
* embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象
* 但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。

* embstr编码的字符串对象在执行命令时,产生的效果和raw编码的字符串对象执行命令时产生的效果是相同的,但使用embstr编码的字符串对象来保存短字符串值有以下好处:
* embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次。
* 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
* 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。
##### 选择
* 如果一个字符串对象保存的是整数值,并且这个整数值可以用int类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成int),并将字符串对象的编码设置为int。
* 如果一个字符串对象保存的是long、double,在Redis中也是作为字符串值来保存的。如果我们要保存一个浮点数到字符串对象里面,那么程序会先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值。
* 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。
* 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。
##### 编码的转换
int编码的字符串对象,在条件满足的情况下会被转换为raw编码的字符串对象。
* 对于int编码的字符串对象来说,如果我们向对象执行了一些命令,使得这个对象保存的不再是整数值,而是一个字符串值,那么字符串对象的编码将从int变为raw。
* 对int编码的字符串对象执行任何修改命令时,程序会先将对象的编码从int转换成raw,之后总会变成一个raw编码的字符串对象。
embstr编码的字符串对象,在条件满足的情况下会被转换为raw编码的字符串对象。
* 因为Redis没有为embstr编码的字符串对象编写任何相应的修改程序(只有int编码的字符串对象和raw编码的字符串对象有这些程序),所以embstr编码的字符串对象实际上是只读的。
* 当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。因为这个原因,embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象。
字符串命令及实现
* 因为字符串键的值为字符串对象,所以用于字符串键的所有命令都是针对字符串对象来构建的

#### 列表对象
列表对象的编码可以是ziplist或者linkedlist。
##### 实现方式
例如:`RPUSH numbers 1 "three" 5`
ziplist

linkedlist

##### 编码转化
当列表对象可以**同时满足**以下两个条件时,列表对象使用ziplist编码:
* 列表对象保存的所有字符串元素的长度都小于64字节(即 entry 的 encoding 占 1 个字节);
* 列表对象保存的元素数量小于512个;
* 注意:以上两个条件的上限值是可以修改的,具体请看配置文件中关于list-max-ziplist-value选项和list-max-ziplist-entries选项的说明。
不能满足这两个条件的列表对象需要使用linkedlist编码,原本保存在压缩列表里的所有列表元素都会被转移并保存到双端链表里面,对象的编码也会从ziplist变为linkedlist。
列表命令及实现
* 因为列表键的值为列表对象,所以用于列表键的所有命令都是针对列表对象来构建的

#### 哈希对象
哈希对象的编码可以是ziplist或者hashtable。
##### 实现方式
例如 `HMSET profile name "Tom" age 25 career "Programmer"`
zipList
* ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾,因此:
* 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;
* 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。


hashtable
* hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:
* 字典的每个键都是一个字符串对象,对象中保存了键值对的键;
* 字典的每个值都是一个字符串对象,对象中保存了键值对的值。

##### 编码转换
当哈希对象可以**同时满足**以下两个条件时,哈希对象使用ziplist编码:
* 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
* 哈希对象保存的键值对数量小于512个;不能满足这两个条件的哈希对象需要使用hashtable编码。
* 注意:这两个条件的上限值是可以修改的,具体请看配置文件中关于hash-max-ziplist-value选项和hash-max-ziplist-entries选项的说明。
当使用ziplist编码所需的两个条件的任意一个不能被满足时,对象的编码转换操作就会被执行,原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面,对象的编码也会从ziplist变为hashtable。
哈希命令及实现
* 因为哈希键的值为哈希对象,所以用于哈希键的所有命令都是针对哈希对象来构建的

#### 集合对象
集合对象的编码可以是intset或者hashtable。
##### 实现方式
intset
* 例如 `SADD numbers 1 3 5`

hashtable
* 例如`SADD Dfruits "apple" "banana" "cherry"`

##### 编码转换
当集合对象可以**同时满足**以下两个条件时,对象使用intset编码:
* 集合对象保存的所有元素都是整数值;
* 集合对象保存的元素数量不超过512个。
* 注意:第二个条件的上限值是可以修改的,具体请看配置文件中关于set-max-intset-entries选项的说明。
当使用intset编码所需的两个条件的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在整数集合中的所有元素都会被转移并保存到字典里面,并且对象的编码也会从intset变为hashtable。
集合命令及实现
* 因为集合键的值为集合对象,所以用于集合键的所有命令都是针对集合对象来构建的

#### 有序集合对象
有序集合的编码可以是ziplist或者skiplist。
##### 实现方式
例如:`ZADD price 8.5 apple 5.0 banana 6.0 cherry`
ziplist
* ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(score)。
* 压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。


zsl跳跃表
* zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:
* 跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。
* 通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的。
* 除此之外,zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:
* 字典的键保存了元素的成员,而字典的值则保存了元素的分值。
* 通过这个字典,程序可以用O(1)复杂度查找给定成员的分值,ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性。
* 注意:
* 有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数(浮点数也是 StringObject)。
* 值得一提的是,虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。
* 示例图:为了展示方便,在字典和跳跃表中重复展示了各个元素的成员和分值,但在实际中,字典和跳跃表会共享元素的成员和分值,所以并不会造成任何数据重复,也不会因此而浪费任何内存。

##### 编码转换
当有序集合对象可以**同时满足**以下两个条件时,对象使用ziplist编码:
* 有序集合保存的所有元素成员的长度都小于64字节
* 有序集合保存的元素数量小于128个;
* 注意:以上两个条件的上限值是可以修改的,具体请看配置文件中关于zset-max-ziplist-entries选项和zset-max-ziplist-value选项的说明。
当使用ziplist编码所需的两个条件中的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在压缩列表里的所有集合元素都会被转移并保存到zset结构里面,对象的编码也会从ziplist变为skiplist。
有序集合命令及实现
* 因为有序集合键的值为哈希对象,所以用于有序集合键的所有命令都是针对哈希对象来构建的
