!> Redis 就数据结构 内存淘汰机制 单线程 高可用 缓存应用
对象类型 | 编码类型 | 要求 | 编码转换 | 图例 | 集合命令实现方法 |
---|---|---|---|---|---|
字符串对象 | int | 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr/属性里面(将void*转换成long),并将字符串对象的编码设置为int。 | |||
int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。 | |||||
可以用long double类型表示的浮点数在Redis中也是作为字符串值来保存的。 | |||||
在有需要的时候,程序会将保存在字符串对象里面的字符串值转换回浮点数值,执行某些操作,然后再将执行操作所得的浮点数值转换回字符串值,并继续保存在字符串对象里面。 |
因为Redis没有为embstr编码的字符串对象编写任何相应的修改程序(只有int编码的字符串对象和raw编码的字符串对象有这些程序),所以embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。因为这个原因,embstr编码的字符串对象在执行修改命令之后,总会变成一个raw编码的字符串对象。 | | | | 字符串对象 | content 2 | | | | | | 字符串对象 | content 2 | | | | | | 字符串对象 | content 2 | | | | |
面试题列表:
Redis数据结构的两种表现形式:
Redis使用的字符串有什么特点: Redis为什么不直接使用C字符串:直接答出SDS的特性
Redis的hash table是如何实现的
使用拉链法解决冲突,并且冲突的时候会将元素加入到表头,并且redis采用Murmurhash2,该算法效率高,随机性好,可以减少冲突可能。
TODO:了解golang的渐进式哈希的过程
特色:rehash过程(对比一下golang的rehash过程)
类似问题:
ziplist是如何实现的?有什么用? 三个要点:连续内存,数据移动,连锁更新
在列表中的表现: 在字典中的表现: 在有序集合中的表现:
什么时候会触发连锁更新:增删改都会触发
redis的整数集合是什么?有什么特色 特色:升级不降级
总结:
因为小公司项目 Redis 用的是 String 数据结构。 面试官其实想考察的是 Redis 的数据结构,这个时候就应该主动告诉面试官自己知道 Redis 的数据结构! 类似问题:redis的数据类型
5种基本数据结构:string,list,hash,set,zset
3种高级数据结构:bitmap、geo、hyperloglog
redis5.0引入的数据结构 streams,这是Redis5.0引入的全新数据结构,用一句话概括Streams就是Redis实现的内存版kafka。而且,Streams也有Consumer Groups的概念。通过Redis源码中对stream的定义我们可知,streams底层的数据结构是radix tree:
有序集合是什么:我说了排序,然后问怎么排序的 然后又问有序集合zset的时间复杂度 zset实现原理:参考、 【推荐】参考 zset如何实现有序:图来自于<<redis设计与实现>>8.6。 zset的时间复杂度 Redis 跳表 Redis HashTable实现,什么时候退化成ziplist ziplist和HashTable之间区别
maxmemory <bytes>
,并重新启动// 获取设置的Redis能使用的最大内存大小
127.0.0.1:6379> config get maxmemory
3) "maxmemory"
4) "0"
// 设置Redis最大占用内存大小为100M
127.0.0.1:6379> config set maxmemory 100mb
127.0.0.1:6379> config get maxmemory-policy
3) "maxmemory-policy"
4) "noeviction"
127.0.0.1:6379> config set maxmemory-policy allkeys-lru
随着时间的推移,内存会逐渐飙升直到内存满为止。内存满的时候会根据内存淘汰机制进行淘汰,如果淘汰之后依然是满的将会拒绝写入。
用于当内存快要满的时候如何释放部分内存。
Redis 内存淘汰策略共有八种,这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。
noeviction
(Redis 3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,会报错通知禁止写入,不淘汰任何数据,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。volatile-random
:随机淘汰设置了过期时间的任意键值(当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key)volatile-ttl
:优先淘汰更早过期的键值(当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除)volatile-lru
(Redis 3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值(当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key)volatile-lfu
(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;allkeys-random
:随机淘汰任意键值(当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key)allkeys-lru
:淘汰整个键值中最久未使用的键值;(当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(最常用))allkeys-lfu
(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。!> Tips:eviction是驱逐的意思。volatile在英文中表示易挥发,在Redis中表示设置了过期时间的key
!> Tips:LFU(Least Frequently Used):最少频率使用,通过统计访问频率,将访问频率最少得键值对淘汰
!> Tips:LRU(Least Recently Used):最近最少使用,将最近最少使用的数据淘汰
拓展问题
注意事项:Redis的淘汰算法实际实现上并非针对所有key,而是抽样一部分并且从中选出被淘汰的key
这里主要探讨的是如何处理Redis中过期的key,Redis中选择【惰性删除+定期删除】两种策略配和使用
过期删除策略:
参考
两种策略:定期删除和惰性删除
redis过期处理两种方式:
从服务器上不可能删除自己的key的,因为没有办法通知主服务器也删除这个key。因此当读请求打到主服务器上的时候,如果主服务器发现这个key过期就会删除这个key,但是当读请求打到从服务器上的时候,这个时候就要区分,如果在redis3.2版本之前,从库发现过期也会直接返回,在3.2之后,发现过期直接返回Null,但是需要注意即便是在3.2之后,从库虽然对于过期的key返回null,但是依然不会删除,因为需要等待主库删除之后才会删除这个过期的key。总结就是,从头到尾,从库一直都是等主库的删除才删除,3.2之前不管有没有过期从库都是直接返回值,3.2之后从库如果判断出是过期的key会直接返回Null,但是注意不会删除哦,此时我们需要使用ttl
命令查看是否过期
扩展问题:
!> 我们通常说,Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。所以,严格来说,Redis并不是单线程,但是我们一般把Redis称为单线程高性能,这样显得“酷”些。接下来,我也会把Redis称为单线程模式。而且,这也会促使你紧接着提问:“为什么用单线程?为什么单线程能这么快?”
扩展问题
redis采用的是IO多路复用模型,核心分为四个组件:
但是其实还有一个组件叫做套接字队列:
具体流程图:
所谓的建立连接以及套接字之类的,表现形式就是文件描述符,IO多路复用会挑出准备好(数据已经发过来或者我准备写数据了)的文件描述符,丢过去给套接字队列,事件分发器会从套接字队列里面拿到我们的套接字,之后分发器会挑具体的事件处理器,这个图看懂了,那么redis的IO多路复用就可以答出来了。
建议采用一个实例去记,到时候也好解释
扩展点一:从redis 6.0的多线程模型去讲解
扩展点二:memcache
的IO模型 IO模型本质上是多路复用。与redis不同的是,memcache
中的IO多路复用是多线程的,并且命令的执行也是多线程的,memcache
的acceptor线程
监听到套接字事件之后,丢给workers线程
,线程负责读写数据并且执行命令
扩展点三:比较redis与memcache 从redis6.0之后,两者差别不大,根本的差距在于redis只有一个主线程执行命令,但是memcache是各自的线程执行各自的命令
引申出的问题:
如何引导:
memcache
和redis
的区别什么是Redis缓存雪崩、穿透、击穿,十分钟给你讲的明明白白 缓存雪崩:大量的redis缓存在同一时间内过期,导致请求直接打到数据库上造成数据库崩溃 缓存
应用 | 描述 | 解决措施 | title 4 |
---|---|---|---|
内容1 | content 2 | ||
行3 | line3 | column 3 |
正常的缓存流程:
解决方案:
缓存穿透:指的是缓存和数据库都没有的数据,一般常见于黑客攻击,比如用请求id=-1的数据,这种数据直接穿透缓存,打到数据库上,导致数据库挂掉。
解决方案:
缓存击穿:某一个非常热点的key失效,一瞬间大量该key的请求打到数据库上,造成数据库挂掉。
解决方案:
分布式锁原理:首先大量的用户访问redis请求数据,如果有的话,就会返回给用户,如果redis为空的话,就会去数据库请求数据,我们就在这个数据库请求这一步上锁,那么这个时候只有一个线程能抢到这个锁,所以也就只有一个线程能操作这个数据库,这时候对数据库压力比较小,当查询到数据之后直接将数据缓存到redis里面,其他没有抢到锁的线程让它先睡几毫秒,然后再去redis里面查询,因为我们前面有一个线程抢到了锁并将数据缓存到redis里面,所以其他线程访问redis的时候直接可以获取到。
分布式锁实现方式比较多,比如zookeepeer,还有redis实现,会单独拿出视频来讲
思考题:一个项目可以分为如下3个阶段,