耗时1个多月空闲出来的时间学习整理的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
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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
4391、什么时候会用到缓存?为什么要用这门技术?
单机mysql数据库在TPS超过1500的时候可能会宕机或者说无法提供服务,
redis缓存查询TPS在7到10w之间,所以要用redis缓存来保护"脆弱"的mysql单机数据库.
维护性
开源,核心代码大概32000多行,社区活跃,后续可维护性较好
扩展性
客户端支持多语言,比如:php、java、golang
可以搭集群(哨兵,分片),实现高可用
稳定性、成熟性
支持持久化(RDB / AOF)
大多公司在用(阿里巴巴,微博,腾讯等)
性能
每秒并发量能支持高达10万,速度快,用C语言实现,单线程模型避免上下文
资源切换及内部竞争条件(持久化会重新开条线程去处理)
支持协议格式执行命令,支持pipeline(管道),提高执行效率
丰富性
支持五种数据类型(string、list、set、zset、hash),而外还有bitmaps、
hyperLogLog、GEO。
注意:但数据量大访问频率低、联表多、要求有事务属性,不适合用redis。
2、这门技术相对于其他的同类技术,为什么选用这门技术?两者或者多者的比较?有哪些优缺点?
举例:
如自身的相关数值:数据量、TPS、响应时间。
缓存相关的有:本地缓存、分布式缓存(redis、mamcached)、客户端缓存、
数据库缓存、CDN缓存。
选型指标:容量、并发量、响应时间、使用成本、扩展性、容灾。
本地缓存不建议用在分布式服务中,一来是占其他服务内存,也带有不可控性,
二是导致资源浪费。(相同资源备份在多个服务中)。
客户端缓存可能会给用户带来不好的体验。
数据库缓存带不可操作性(一些计算的结果,或者是频繁访问的数据)。
CDN缓存。
Redis之与Memcached的比较
一、性能
由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储
小数据时比Memcached性能更高。而在100k以上的数据时,Memcached性能要高于Redis,
虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。
二、内存使用效率
使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结
构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。
三、数据备份恢复
memcached挂掉后,数据不可恢复;redis数据丢失后可以通过aof恢复,Redis支持数据
的备份,即master-slave主从模式的数据备份。redis支持持久化。
四、内存管理机制
对于像Redis和Memcached这种基于内存的数据库系统来说,内存管理的效率高低是影
响系统性能的关键因素。Memcached的内存管理制效率高,而且不会造成内存碎片,但
是它最大的缺点就是会导致空间浪费。因为每个Chunk都分配了特定长度的内存空间,
所以变长数据无法充分利用这些空间。比如将100个字节的数据缓存到128个字节的Chunk
中,剩余的28个字节就浪费掉了
Redis采用的是包装的malloc/free,会造成内存碎片。
五、集群、分布式存储
Memcached不支持分布式,只能在客户端通过像一致性哈希(内置)这样的分布式算法来
实现Memcached的分布式存储。Redis Cluster是一个实现了分布式且允许单点故障的
Redis高级版本,它没有中心节点,具有线性可伸缩的功能。
(优秀博客:https://mp.weixin.qq.com/s/4J3oM1j5hcLq4w4TdSEMPg)
使用场景上比较
mamcached多核,qps能上到几十万级别,但数据结构单一,仅仅支持key/value简单
的数据类型,无法持久化,水平扩展需要编写分布策略,无法进行数据水平复制。
redis单核,qps能上十万,数据结构丰富,支持持久化(aof文件),支持集群和哨
兵机制(master/slave),支持水平数据复制。
3、这门技术可以运用在什么场景上?
放热数据,提高查询效率
分布式锁
统计(如2亿用户,统计某个用户是否为活跃用户,用到bitmaps)、排行榜(zset)
分布式限流(根据ip地址)
推荐(共同好友或歌曲推荐求交集,此外,还有差集和并集)、点赞、收藏数(set命令)
消息队列(一般不会用)
session统一处理
4、如何运用这门技术?(常用命令、集群、分片、哨兵),怎么去优化??这门技术是怎么实现的?
redis用法及介绍:https://juejin.im/post/5a912b3f5188257a5c608729
redis集群哨兵搭建(亲自搭建过,过程会有些bug ,自行谷歌修复):
https://blog.csdn.net/shouhuzhezhishen/article/details/69221517
redis分片(将数据拆分几部分,按照一定的路由规则存到对应的redis节点中)
客户端分片:在客户端进行一致性哈希,选择路由到那一台redis机器上访问。
代理服务器分片:Twemproxy 是 Twitter 开发的代理服务器。
服务端分片:将所有的存储空间分成16384个slot,数据按照一定的路由规则存到这些
对应的slot中,可以理解成在master-slave上加一层,如果动态扩缩容,可以采
用预分片(32/64个节点,不用的节点没数据占内存空间1M左右)。客户端在查找
key的所在节点时也是通过CRC16校验后对16384取模。
redis优化:
一、尽量别用持久化(TODO:集群需要开启持久化,就是那三个save配置)。
二、不要设置过期时间,耗cpu和内存性能。
在 redis.conf 中有一行配置: maxmemory-policy volatile-lru
当内存不足时,自动移除最近最少使用的元素(默认策略)
三、keys,hgetall,smembers等长时间命令一般不再生产环境使用,
因为是单线程,容易阻塞
四、获取多个value, 能用批量执行命令就用批量执行命令(如mget),能减少多次访问
网络和执行命令的时间,或者用pipeline.
五、设置慢查询的值,定期查询慢查询日志
config set slowlog-max-len 1000 //1000微秒=1ms
config set slowlog-log-slower-than 1000
六、默认会进行内存压缩,用cpu换时间的方式。(集合中)如果某个值超过了配置文件中
设置的最大值,redis将自动把它(集合)转换为正常的散列表。
更改阈值的方法:
hash-max-zipmap-entries 64 (2.6以上使用hash-max-ziplist-entries)
hash-max-zipmap-value 512 (2.6以上使用hash-max-ziplist-value)
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
七、尽可能使用散列表(hashes),存储对象能用hashes就不用string,这样能能省接近90%内存。
TODO: redis内部具体实现原理。
Redis设计与实现(作者:黄建宏)
redis底层原理:https://blog.csdn.net/wcf373722432/article/details/78678504
/*
* Redis 对象
*/
typedef struct redisObject {
// 类型(五种,string、list、hash、set、zset)
unsigned type:4;
// 不使用(对齐位)
unsigned notused:2;
// 编码方式 (八种,long类型整数、embstr SDS、SDS、字典(映射)、压缩列表、
双端链表、跳跃表、整数集合)
unsigned encoding:4;
// LRU 时间(相对于 server.lruclock)
unsigned lru:22;
// 引用计数
int refcount;
// 指向对象的值
void *ptr;
} robj;
字符串对象的编码可以是int、raw或者embstr。
字符串对象的长度小于39字节,就用embstr对象。否则用传统的raw对象。
redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的
修改实际上是先转换为raw再进行修改。
embstr和raw的区别在于embstr更能充分的利用空间,不会预留空间,而raw是
预留空间的。
列表对象的编码是ziplist或linkedlist
ziplist是压缩列表,空间连续,能节省空间,对象元素不大时用ziplist,
但缺点也明显,插入复杂度是O(n),linkedlist是双端链表。
哈希对象的底层实现可以是ziplist或者hashtable。
ziplist中的哈希对象是按照key1,value1,key2,value2这样的顺序存放来存储的
hashtable的是由dict这个字典结构来实现的,dict包含两个指针,指向两个哈希表,
分别是dicht[0] 是用于真正存放数据,dicht[1]一般在哈希表元素过多进行
rehash的时候用于中转数据。
集合对象的编码可以是intset或者hashtable(保证快速新增、删除、查找)。
有序集合的编码可能两种,一种是ziplist,另一种是skiplist与dict(字典,保证score顺序)的结合。
ziplist作为集合和作为哈希对象是一样的,member和score顺序存放。按照score
从小到大顺序排列。
skiplist是一种跳跃表,它实现了有序集合中的快速查找。
想如果单一用dict(hashtable),那可以快速查找(主要)、添加和
删除元素,但没法保持集合的有序性。如果单一用skiplist,有序性
可以得到保障,但查找的速度太慢O(logN)。
redis各个配置参数详解:
https://blog.csdn.net/ljl890705/article/details/51540427
https://www.cnblogs.com/chenmh/p/5121849.html
redis的持久化
aof文件不会丢数据,rdb文件会丢数据。
aof文件大,恢复慢,rdb文件快。
redis架构
单线程模型避免上下文,纯内存操作响应时间大概是100纳秒,采用epoll多路复用的非阻塞io,在连接、读写、关闭上尽可能的节省时间。
redis回收策略
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑
选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑
选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)
中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数
据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
缓存更新策略
1、被动失效
缓存数据主要是服务读请求的,通常会设置一个过期时间,或者当数据库
状态改变时,通过一个简单的delete操作,使数据失效掉;当下次再去读
取时,如果发现数据过期了或者不存在了,那么就重新去数据库读取,
然后更新到缓存中,这即是所谓的被动失效策略。被动策略有一个很大的
风险,从缓存失效到数据再次被预热到cache这段时间,所有的读请求会
直接打到DB上,对于一个高访问量的系统,很容易被击垮。
2、主动更新
主动更新,很容易理解,就是数据库存储发生变化时,会直接同步更新到
Cache,主要是为了解决cache空窗期引发的问题。比如电商的卖家修改商
品详情,具有读多写少特点。但如果是读多写多,同样会带来另一个问题,
就是并发更新。多台应用服务器同时访问一份数据是很正常的,这样就会
存在一台服务器读取并修改了缓存数据,但是还没来得及写入的情况下,
另一台服务器也读取并修改旧的数据,这时候,后写入的将会覆盖前面的,
从而导致数据丢失。解决的方式可以有:锁控制,如redisson;版本号。
影响命中率的可能因素:
1、设置过期时间太短。
2、空间不足,频繁有效的缓存按照回收策略被剔除
3、key确实不存在
...
性能指标
缓存空间的使用率
topN 命令的执行次数
缓存的命中率
缓存的接口平均RT,最大RT,最小RT
缓存的QPS
网络出口流量
客户端连接数
key个数统计
演练结果(如果缓存服务全部或个别宕掉的情况)
5、如何保证高可用和一致性等等?
主从集群+哨兵模式来保证高可用。
先操作数据库,再操作redis ,来保证数据库和缓存之间的一致性。如果redis
操作失败,mysql就会回滚,
不会出现数据不一致的问题。并发问题通过互斥锁的形式解决。
6、用这门技术可能会有什么坑?亲身经历有什么坑?如何解决??有没备选或者
应急方案?一些知名的公司是怎么用这门技术的?它们的心得体会及经验是怎么样的?
举例:
https://github.com/aalansehaiyang/technology-talk/blob/master/system-architecture/architecture-experience.md
(可以看看缓存那一块)
坑一:
僵尸连接: 服务端连接数上限了,但是客户端连接数没有上限,线上部署的时候。
缓兵之计:通过脚本命令清除连接,不用担心客户端,客户端会重连。
我们重新给客户端设了最大超时,如果连接一直处于空闲状态,大概 5 分钟就会
断开与服务器之间的长连接,但奇怪的是服务端不承认客户端的断连状态,一直保
持该连接,结果从客户端的服务器看不到这种连接,但在 Redis 服务器上却看到
大量这种连接,最终导致服务端连接数被占满,无法再创建新连接对外提供服务。
原因:给服务端设置Timeout和客户端设置的Timeout不一致,服务端的时间更长一些。
坑二:
内存飙升
内存最大值(maxmemory)限制:起初缓存的数据比较少,一直没配最大内存限制。
这是一个小错误。系统除了报 Cluster down 外,并没更清晰的报错,当时我们一
脸迷惘,莫名其妙地查了 1 个多小时后才发现服务器内存被耗光了。后面作分片
处理。或者说部分不确定数据量大小的业务部门搭建自己的redis。
此外,通过查看监控和查询日志,还可能是客户端滥用monitor命令。
monitor的模型是这样的,它会将所有在Redis服务器执行的命令进行输出,通常来讲
Redis服务器的QPS是很高的,也就是如果执行了monitor命令,Redis服务器在Monitor
这个客户端的输出缓冲区又会有大量“存货”,也就占用了大量Redis内存。
预防方法:禁止掉一些危险的命令(shutdown,flushall, monitor, keys *)。
添加command-rename配置,将一些危险的命令(flushall, monitor, keys * , flushdb)
做rename。
坑三:
连接数过多
可以用netty重新写一个代理层
或者各个业务部门自己搭建缓存系统,存储各自的业务热数据。
各个业务部门都应该写个预热数据脚本。
坑四:
aof 文件占满磁盘空间,单机多实例可能存在Swap和OOM的隐患
(提示:aof重写,aof存各种执行命令,aof重写也叫去冗余命令,重新生成一个新的脚本)
此后每天执行BGREWRITEAOF指令脚本(异步操作,不会阻塞单线程)的同时并监控磁盘空
间,减少服务器上 Redis 的实例数并腾空一半内存,因为一台机上部署多个 Redis 实例
会有个隐患,万一多个实例扎堆做 AOF 重写会导致 swap 或者 oom,导致重写失败,这种
失败会不断重复,直至 aof 文件像滚雪球似的变大,最终塞满磁盘,另外重写体积较大的
aof 文件时,Redis 会进入 IO 阻塞状态,停止对外服务
坑五:
缓存穿透:指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到达数据库。
这是一种恶意操作的行为,往往表现在黑客或者懂相关技术的人去操作。
解决方案:
1、对这类请求进行过滤,用布隆过滤器,将数据库中所有的对应redis的key,进行hash,
存到redis的bitmaps存储结构中,
然后操作redis具体数据前先请求bitmap中有没有相关的数据,没有直接返回。
具体操作请自行搜索:"布隆过滤器 redis",
但用这个难免会有命中误差率,假设“abc”的hash值是587,而“19f”的hash值也是587,
那么如果19f在数据库中没有对应的数据,也是会穿透的,对于这类问题,
可以采用回设的方式,如果第一次数据库中没有查询到,设置一个默认值
存到redis中,第二次甚至往后的n次就可以直接在redis上catch住了。
2、对这些不存在的数据缓存一个空数据或者指定的默认值。
坑六:
缓存雪崩:指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效
(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。在有缓存的系统中,
系统非常依赖于缓存,缓存分担了很大一部分的数据请求。当发生缓存雪崩时,数据库
无法处理这么大的请求,导致数据库崩溃。这可以理解成是一种善意行为,是指用户请
求量大而使请求越过缓存跳向数据库导致数据库宕机(TPS:1500左右)的行为。
解决方案:
1、为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理
设置缓存过期时间(错隔时间,在某个时间内来个随机时间值)来实现;
2、为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一
个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
3、也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓
存雪崩。
坑七:
缓存 “无底洞” 现象:指的是为了满足业务要求添加了大量缓存节点,但是性能不但没有
好转反而下降了的现象。
解决方案:
1、优化批量数据操作命令,尽量不设置过期时间及持久化操作,单线程只处理内存操作即可;
2、减少网络通信次数;
3、降低接入成本,使用长连接 / 连接池,NIO 等。
坑八:
缓存并发:有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,
同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存
频繁更新的问题。
解决方法:互斥锁。我现在的想法是对缓存查询加锁,如果KEY不存在,就加锁,然后查DB
入缓存,然后解锁;
其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。
这种情况和刚才说的预先设定值问题有些类似,只不过利用锁的方式,会造成部分请求等待。
如何从零设计缓存服务?
尽量不用持久化(提前预热),过期时间(带时间戳,必要时起定时任务剔除)。
并发访问大的页面相关数据要提前预热。
修改keys,hgetall,smembers,shutdown,flushall, monitor,flushdb等长时
间命令,使其无法使用批量耗时操作命令。
设置慢查询的值,定期查询慢查询日志。
尽可能使用散列表,而不是将对象转换成json字符串的形式。
上线前演练如果缓存宕掉对系统的影响。
做好监控及告警:命中率、内存使用情况、客户端连接数、峰值,请求和响应时间。
搭建主从集群和哨兵模式环境。
设置连接超时时间和请求超时时间。
对数据库中不存在的数据回源给缓存一个空数据或者指定的默认值。
对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁。
做好缓存系统的设计和评审指标。
规范来限制各个应用使用的key有唯一的前缀。
缓存数据过大,建议序列化,或者拆分。
额外:
缓存系统的设计和评审指标
举例: 设计缓存系统要考虑的问题:容量评估、客户端长连接数、
并发量(平均、最高)、最慢响应时间、使用成本、容灾性、
缓存对象粒度大小(太大要考虑序列化,如果protobuf,kryo)
1、容量规划(根据容量评估的结果来申请和分配缓存资源,否则会造成资源浪费或者缓存空间不够)
缓存内容的数量、大小、数据结构、失效时间
淘汰策略
每秒的读、写峰值
2、性能优化
线程模型:配置成NIO形式
预热方法
缓存分片(尽量不用,比较复杂)
冷热数据的比例
3、高可用
4、缓存监控
缓存服务监控
缓存容量监控(内存使用)
缓存请求监控(大对象会影响请求响应。)
缓存响应时间监控(慢查询)
5、注意事项举例
是否有可能发生缓存穿透
是否有大对象
是否使用缓存实现分布式锁
是否避免了Race Condition(竞争条件,并发)
注意:
建议将使用缓存的业务进行分离,核心业务和非核心业务使用不同的
缓存实例,从物理上进行隔离,如果有条件,则请对每个业务使用单
独的实例或者集群,以减小应用之间互相影响的可能性。
如果缓存设置了超时时间,如果超时设置得较长,从而拖垮服务的线
程池,最终导致服务雪崩的情况。
如果多个业务共享一套缓存服务,我们得规范来限制各个应用使用的
key有唯一的前缀,并进行隔离设计,避免产生缓存互相覆盖的问题。
任何缓存的key都必须设定缓存失效时间,且失效时间不能集中在某
一点,否则会导致缓存占满内存或者缓存雪崩。
对于存储较多value的key,尽量不要使用HGETALL、keys等集合批量
命令操作(可用scan命令),该操作会造成请求阻塞,影响其他应用的访问。
缓存的数据不易过大,尤其是Redis,因为Redis使用的是单线程模型,
在单个缓存key的数据过大时,会阻塞其他请求的处理。
在使用本地缓存(如Ehcache)时,一定要严格控制缓存对象的个数及
声明周期。由于JVM的特性,过多的缓存对象会极大影响JVM的性能,
甚至导致内存溢出等。
在使用缓存时,一定要有降级处理,否则请求并发过高容易拖垮应用
服务。(代码层面如try catch,设置连接及访问redis的超时时间)
利益化:面试时会问什么样的问题?(高级开发,架构师,cto不同的角色)
TODO
通过在github上搜索"架构"或者"java面试" 里面会有对应的资源(redis)可以深入学习。
或者https://github.com/aalansehaiyang/technology-talk/blob/master/system-architecture/architecture-experience.md