看完拉勾的讲义笔记后,如果还觉得不过瘾,推荐以下毒物:
1.书籍《Redis设计与实现》,该书基于redis3.0,但学习Redis底层设计是很不错的
2.掘金小册《Redis 深度历险:核心原理与应用实践》 这本小册,适合当课外读物,有点商务化,你懂得
3.如下博客清晰明了
redis02 五种类型底层数据结构(强力推荐,完美诠释了底层数据结构的应用)
语雀共享文档:https://www.yuque.com/docs/share/1fea47ff-9d21-4b0b-8bfa-f3487adf6b51?# 《Redis一问多答(必背篇)》
(欢迎兄弟们评论和点赞)
目录
拓展:为什么SDS最大存储44字节后,编码就从embstr转为raw?
5.什么是大Key和热Key,会造成什么问题,如何解决?系统在某个时刻访问量剧增(热点新闻),造成数据库压力剧增甚至崩溃,怎么办?
6.缓存和数据库数据是不一致时(双写不一致),会造成什么问题,如何解决?redis并发下脏读问题?
8.什么是缓存雪崩、缓存穿透和缓存击穿,会造成什么问题,如何解决?
10.redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?gossip 协议
11.dict扩容的负载因子0.75和hashmap扩容负载因子0.75
怎么理解redis的弱事务性,redis的事务存在两种情况:
17.redis分布式集群搭建、Redis哨兵和集群的原理及选择?
18.在多机Redis使用时,如何保证主从服务器的数据一致性?主从备份数据同步
20.分布式锁setnx/redssion、红锁redlock
21.如何保证 Redis 中的数据都是热点数据?Redis淘汰策略,LRU、LFU手写
23.redis 中有 1 亿 key,其中有 10w 个 key 是以某个固定已知前缀开头,如何将它们全部找出来?
1.Redis怎么使用?
1.做DB缓存、减轻DB压力
2.session分离
3.redis分布式锁
4.做乐观锁
同步锁、数据库的行锁、表锁都是悲观锁,性能差
高性能、高响应(秒杀)可以用乐观锁,watch+incr
2.redis为什么快?redis的线程模型
1.redis是单线程,基于内存存储
2.redis自己的数据结构,如SDS、字典、压缩表、跳跃表、hash对象等
3.redis的线程模型:IO多路复用,事件处理机制?
3.redis常用数据结构的底层
1.string
sds incr(乐观锁)setnx(悲观锁)
int:数字
embstr:短字符串 redisObj和SDS在同一块内存中
raw:长字符串 redisObj和SDS在各分配一块内存
embstr和raw的界限 3.0前是39位 3.0后是44位
2.list
redis 3.2前
ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于512
双向链表:不满足ziplist的情况(耗内存)
redis 3.2后
quicklist ziplist和双向链表的结合,每一个链表node都是ziplist。用于栈或队列、评论列表
这两种存储方式的优缺点
- 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
- ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。
3.Hash
ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于512
hashtable(dict):不满足ziplist的情况
4.set
intset:所有元素都是整数,个数在512
hashtable:不满足intset条件
5.zset
ziplist:列表对象所有字符串元素长度小于64字节,元素数量小于128
skiplist+dict:
不常见的 bizmap位图类型(签到)、geo地理类型(记录地理位置、计算距离、附近的人)
5.0版本:stream类型,用于消息队列
HyperLogLog、Geo、Pub/Sub
拓展:为什么SDS最大存储44字节后,编码就从embstr转为raw?
jemalloc分配内存64字节,redisObject占用16字节,尾标识占用1个字节,所以问题出在了sdshdr,
1.3.0之前sdshdr数据结构占用8个字节,所以16+8+1=25,64-25=39
2.3.0之后sdshdr优化,只占用3个字节,所以16+3+1=20,64-20=44
malloc 是一个统称, jemalloc, tcmalloc 他们俩是具体的实现
2. embstr 和 raw 的本质区别
内存分配上:
embstr调用1次malloc, 因此redisObject和SDS内存是连续分配的
raw需要调用2次malloc, 因此redisObject和SDS内存不连续分配
使用上:
embstr 整体 64 byte, 正好和cpu cache line 64byte 一样, 可以更好的使用缓存, 效率更高
4.缓存穿透如何解决?布隆过滤器
原理:
当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在
优点:
空间效率和查询时间都远远超过一般的算法
缺点:
- 存在误判,可能要查到的元素并没有在容器中,但是hash之后得到的k个位置上值都是1。如果bloom filter中存储的是黑名单,那么可以通过建立一个白名单来存储可能会误判的元素。
- 删除困难。一个放入容器的元素映射到bit数组的k个位置上是1,删除的时候不能简单的直接置为0,可能会影响其他元素的判断。可以采用Counting Bloom Filter
实现:
布隆过滤器有许多实现与优化,Guava中就提供了一种Bloom Filter的实现。
在使用bloom filter时,绕不过的两点是预估数据量n以及期望的误判率fpp,
在实现bloom filter时,绕不过的两点就是hash函数的选取以及bit数组的大小。
对于一个确定的场景,我们预估要存的数据量为n,期望的误判率为fpp,然后需要计算我们需要的Bit数组的大小m,以及hash函数的个数k,并选择hash函数
Big数组大小选择:根据预估数据量n以及误判率fpp,bit数组大小的m的计算方式:
哈希函数选择,由预估数据量n以及bit数组长度m,可以得到一个hash函数的个数k:
=lnfpp/ln2
要使用BloomFilter,需要引入guava包:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency>
BloomFilter一共四个create方法,不过最终都是走向第四个。看一下每个参数的含义:
funnel:数据类型(一般是调用Funnels工具类中的)
expectedInsertions:期望插入的值的个数
fpp 错误率(默认值为0.03)
strategy 哈希算法(我也不懂啥意思)Bloom Filter的应用
总结:
错误率越大,所需空间和时间越小,错误率越小,所需空间和时间越大
5.什么是大Key和热Key,会造成什么问题,如何解决?系统在某个时刻访问量剧增(热点新闻),造成数据库压力剧增甚至崩溃,怎么办?
Big Key
大key指的是存储的值(Value)非常大,常见场景:热门话题下的讨论、大V的粉丝列表、序列化后的图片、没有及时处理的垃圾数据
大key的影响:大key会大量占用内存,在集群中无法均衡、Redis的性能下降,主从复制异常、在主动删除或过期删除时会操作时间过长而引起服务阻塞
如何发现大key:
1、redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大
key。但如果Redis 的key比较多,执行该命令会比较慢
2、获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行
分析统计,根据size_in_bytes统计bigkey
大key的处理:
优化big key的原则就是string减少字符串长度,list、hash、set、zset等减少成员数。
1、string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。
如果必须用Redis存储,最好单独存储,不要和其他的key一起存储。采用一主一从或多从。
2、单个简单的key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值,这样
分拆的意义在于分拆单次操作的压力,将操作压力平摊到多次操作中,降低对redis的IO影响。
2、hash, set,zset,list 中存储过多的元素,可以将这些元素分拆。(常见)
以hash类型举例来说,对于field过多的场景,可以根据field进行hash取模,生成一个新的key,例如
原来的
hash_key:{filed1:value, filed2:value, filed3:value ...},可以hash取模后形成如下
key:value形式
hash_key:1:{filed1:value}
hash_key:2:{filed2:value}
hash_key:3:{filed3:value}
...
取模后,将原先单个key分成多个key,每个key filed个数为原先的1/N
3、删除大key时不要使用del,因为del是阻塞命令,删除时会影响性能。
4、使用 lazy delete (unlink命令)
删除指定的key(s),若key不存在则该key被跳过。但是,相比DEL会产生阻塞,该命令会在另一个线程中
回收内存,因此它是非阻塞的。 这也是该命令名字的由来:仅将keys从key空间中删除,真正的数据删
除会在后续异步操作。
redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> UNLINK key1 key2 key3
(integer) 2
拓展:DEL命令是阻塞命令?lazy delete(unlink命令)/CDN
Hot Key
当有大量的请求(几十万)访问某个Redis某个key时,由于流量集中达到网络上限,从而导致这个redis的
服务器宕机。造成缓存击穿,接下来对这个key的访问将直接访问数据库造成数据库崩溃,或者访问数
据库回填Redis再访问Redis,继续崩溃。
如何发现热key
1、预估热key,比如秒杀的商品、火爆的新闻等
2、在客户端进行统计,实现简单,加一行代码即可
3、如果是Proxy,比如Codis,可以在Proxy端收集
4、利用Redis自带的命令,monitor、hotkeys。但是执行缓慢(不要用)
5、利用基于大数据领域的流式计算技术来进行实时数据访问次数的统计,比如 Storm、Spark、Streaming、Flink,这些技术都是可以的。发现热点数据后可以写到zookeeper中
如何处理热Key:
1、变分布式缓存为本地缓存
发现热key后,把缓存数据取出后,直接加载到本地缓存中。可以采用Ehcache、Guava Cache都可
以,这样系统在访问热key数据时就可以直接访问自己的缓存了。(数据不要求时时一致)
2、在每个Redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到
每个Redis上。
3、利用对热点数据访问的限流熔断保护措施
每个系统实例每秒最多请求缓存集群读操作不超过 400 次,一超过就可以熔断掉,不让请求缓存集群,
直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。(首页不行,系统友好性差)
通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群。
6.缓存和数据库数据是不一致时(双写不一致),会造成什么问题,如何解决?redis并发下脏读问题?
缓存和DB的数据不一致的根源 : 数据源不一样
如何解决:强一致性很难,追求最终一致性(时间),互联网业务数据处理的特点:高吞吐量,低延迟,数据敏感性低于金融业
时序控制是否可行?先更新数据库再更新缓存或者先更新缓存再更新数据库,本质上不是一个原子操作,所以时序控制不可行,高并发情况下会产生不一致
保证数据的最终一致性(延时双删)
1、先更新数据库同时删除缓存项(key),等读的时候再填充缓存
2、2秒后再删除一次缓存项(key)