一、基本类型及底层实现
1.String
1)使用场景:简单字符串存储、分布式锁、计数器、全局唯一ID
2)数据结构:C语言中String用char[]表示,源码中用SDS封装char[],这是Redis存储的最小单元,一个SDS最大可以存储512M信息。
Redis对SDS再次封装成RedisObject,核心作用有两个:
①说明是5种类型的哪一种②里面有指针用来指向SDS
Redis对SDS有如下优化:
①SDS修改后大小大于1M,系统会多分配空间来进行空间预分配。
②SDS是惰性释放空间的,你free了空间,系统会把数据记录下来,下次想用可直接使用。不用新申请空间。
2.List
1)使用场景:栈、队列、集合、简单的消息队列
2)数据结构:底层就是一个双端链表,可以根据先进后出和先进先出的操作组成栈和队列。
3.Hash
1)使用场景:相关数据存储,比如用户的购物车
2)数据结构:Hash的底层主要是采用dict字典的结构,整体呈现层层封装。
渐进式扩容:dictht有两个是为了在扩容时不影响前端的CRUD,全部转移完成之后将互换使用。扩容之后的数组大小为大于used*2的2的n次方最小值。对对象操作时如果是新增,则直接插入第二个数组。如果是删除、修改、查询,则先查找第一个数组,没有查到再查找第二个数组。
4.Set
1)使用场景:无序集合数据
2)数据结构:Set是Hash的简化版,这里你可以认为是没有Value的Dict。
5.ZSet
1)使用场景:有序集合数据如积分排行榜、延时队列
2)数据结构:ZSet用的是可以和二叉树媲美的跳跃表来实现的。跳表是多层链表的结合体,每一层的数据是有序的,上一层是下一层的子集,层次越高跳跃性越大,每一层都可以看作是下一层的索引,这些索引的意义就是为了加快跳表的查询速度。
二、高级数据结构及特性
1.Redis GEO
它的核心思想是把地球近似看作一个球体,GEO利用GeoHash将二维的经纬度转换为字符串(字符串相似标记距离相近),来实现位置的划分跟指定区域的查询。
2.HyperLogLog
它是一种概率数据结构,它使用一种概率算法来统计集合的近似基数(一个集合中不同值的个数)。算法的基本原则是伯努利方程+分桶+调和平均数。它在误差允许范围内,做基数统计的时候非常有用。
3.Bitmap
它用一个bit位来映射某个元素的状态(每个比特位只能表示0和1两种状态),优势是大量的节省内存空间。
4.Bloom Filter
它的基本思想是:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组的K个点(有效降低概率冲突),把它们置为1。当我们检索时,就看这些点是不是都是1,就能知道集合中有没有它了;如果这些点中有一个为0,则这个元素一定不存在。如果这些点都是1,则这个元素很可能存在。
5.发布订阅
发布者向指定的频道发布消息,订阅这个频道的每个客户端都可以收到消息。
三、持久化和事务
1.RDB是Redis中的数据执行周期性的持久化
优点:压缩后的二进制文,适合冷备份和全量复制,数据文件小,数据备份和恢复远快于AOF。
缺点:RDB是周期性快照,在备份时宕机会导致数据完整性和一致性不高。
2.AOF以每条写入命令为日志
优点:因为是只追加模式,所以没有任何磁盘寻址的开销,一秒一次的通过后台子线程操作,数据一致性比较高。
缺点:AOF同步效率高但是运行效率往往低于RDB,恢复慢于RDB。
建议同时配置RDB和AOF,出现问题时可以第一时间进行RDB恢复,然后用AOF补全数据。
3.Redis事务
事务是指一次性,顺序性,排他性的执行一个队列中的一系列命令。
Redis没有隔离级别的概念,批量操作在EXEC之前被放入缓存队列并不会被实际执行。
Redis不保证原子性,单条命令是原子执行的,但事务不保证原子性。
Redis编译型错误事务中所有代码均不执行,运行时异常错误命令导致异常不影响其它命令正常执行。
Redis的watch指令类似于乐观锁,被监视的某些key被其它客户端修改了,事务队列将不会被执行。
四、过期策略和淘汰策略
1.三种过期策略:处理过期数据
定时过期
每个设置过期时间的key都需要创建一个定时器,到期就会立即对key进行删除。这种策略对内存很友好,但会占用大量CPU资源处理过期数据。
惰性过期
只有当访问一个key时,才去判断是否过期,过期则删除。这种策略可以最大化的节省CPU资源,但对内存非常不友好。
定期过期
每隔一定的时间去扫描一定数量的数据的expires字典中一定数量的key,并删除已过期的key。通过调整扫描的时间间隔和每次扫描的限定耗时,使得CPU和内存资源达到最佳的平衡效果。
redis采用惰性删除+定期过期。
2.六种淘汰策略:内存不足需要额外申请时
Redis的内存淘汰策略是指在Redis的内存不足时,怎么处理需要新写入且需要额外申请空间的数据。
volatile-lru:从已设置过期时间的数据集中挑选最近最小使用的数据淘汰。
volatile-ttl:从已设置过期时间的数据集中挑选即将过期的数据淘汰。
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
allkeys-lru:从数据集中挑选最近最小使用的数据淘汰。
allkeys-random:从数据集中任意选择数据淘汰。
no-enviction:禁止驱逐,不删除数据的意思。
五、集群高可用
模式 | 优点 | 缺点 |
单机 | 架构简单,部署方便 | 机器故障、容量瓶颈、QPS瓶颈 |
主从 | 高可靠性,读写分离 | 故障恢复复杂,主库的写跟存受单机限制 |
哨兵 | 部署简单,高可用 | Slave存在资源浪费,不能解决读写分离 |
集群 | 数据动态存储solt,可扩展高可用 | 客户端动态感知后端操作,批量操作支持查 |
1.redis主从同步
Redis全量复制一般发生在Slave初始化阶段,这时需要将Master上所有数据复制一份。
Redis增量复制是Slave初始化后正常工作时master发生的写操作同步到slave的过程。
Redis会把指令放在一个环形队列中,指令同步即是主库指令在从库重放的过程。因为内存容量有限,如果备机一直未同步,指令可能被覆盖。
2.高可用之哨兵模式
Redis-sentinel是一个独立运行的进程,一般sentinel集群最少需要三个节点且为奇数个。Sentinel可以监视任意多个主服务器及主服务器下的多个从服务器,在被监视的主服务器下线时,自动执行故障转移操作。
一个哨兵发现master故障为主观下线,多个哨兵抉择发现达到quorum数时开始进行切换视为客观下线。
3.Redis Cluster
Gossip协议不要求任何中心节点,所有节点都可以是对等的,任何一个节点无需知道整个网络的状况,只要网络是通的,任何一个节点就可以把消息散播到全网。
集群部署至少要3台以上的master节点,集群中超过一半master挂掉或者集群中的master没有slave节点,集群将会进入fail状态。
七、常见问题
1.Redis为什么那么快?
1)基于内存实现:相比操作磁盘速率快百倍。
2)高效的数据结构:多种数据结构支持不同的数据类型。
3)丰富而合理的编码:五种类型根据长度和元素个数适用不同的编码格式。
4)合适的线程模型:单线程+IO多路复用避免上下文切换。
5)Redis6.0后引入多线程提速:读写网络的系统耗时远大于Redis的执行耗时,Redis的瓶颈主要在网络的IO消耗上。多线程可以充分利用多核,多线程任务可以分摊Redis同步IO读写负荷。
值得注意的是,Redis6.0默认多线程是关闭的,多线程可以使性能翻倍,但多线程只是处理网络数据的读取和协议解析,执行命令仍然是单线程顺序执行。
2.缓存雪崩是怎么发生的?
Redis中大批量Key在同一时间统一失效导致所有请求都达到MYSQL,而MYSQL扛不住压力而崩溃。
解决方案:①缓存数据过期时间加一个随机数,防止同一时间集体过期。
②如果缓存数据库是分布式部署,将热点数据分布在不同的缓存数据库。
③设置热点数据永不过期。
3.缓存穿透是什么
缓存穿透是指访问缓存和数据库中都没有的数据,可能导致数据库压力过大。
解决办法:①接口增加鉴权校验或参数校验。
②单个IP每秒访问次数超出阈值拉黑或者关小黑屋一段时间。
③从缓存和数据库中取不到的数据,设置key-null过期时间为15秒防止恶意攻击。
④用布隆过滤器特性也可以。
4.缓存击穿呢
集中对某一个热点Key进行访问,Key失效的瞬间,大量请求进入数据库。
解决办法:设置热点数据永不过期。
5.双写如何保证缓存和数据库数据的一致性?
1)先更新数据库再更新缓存,两个线程交替进行数据库和缓存的修改导致出现脏读现象。
2)先删除缓存,再更新数据库,其中一个线程在没有查到缓存时将数据库查询到的旧值写回缓存导致数据不一致的现象。
延时双删:先淘汰缓存;再写数据库;休眠1秒,再次淘汰缓存;
CaChe Aside Pattern:更新时先把数据保存到数据库,再让缓存失效。
查询时先从缓存中获取,取到后返回,未取到从数据库中查询,成功后放入缓存。
6.什么是脑裂?
脑裂是因为网络原因,导致master节点,slave节点和sentinel集群处在不同的网络分区,此时sentinel集群无法感知master节点的存在,所以salve节点将提升为master节点,此时存在两个不同的master节点就像一个大脑分裂成了两个。
集群脑裂问题中,如果客户端还在基于原来的master节点写入数据,那么新的master节点将无法同步这些数据。当网络问题解决后,sentinel集群将原来的master节点降为slave节点,此时再从新的master中同步数据将造成大量的数据丢失。
解决办法:如果连接到master的slave数量小于表示连接到master的最小数量(min-replicas-to-write)且ping的延迟时间大于表示slave连接到master的最大延迟时间,那么master就会拒绝写请求,这样集群脑裂出现后会将原来master的客户端请求拒绝可以减少数据同步之后的数据丢失。
7.redis如何实现分布式锁,它有哪些优缺点?
主要是利用redis单线程执行互斥命令的特性,根据setnx或者exist命令是否返回1来判断是否枷锁成功,然后处理好锁释放的逻辑就可以了。
优点是相比于zookeeper加锁效率高,缺点是无法重入,而且还可能存在死锁问题。
zookeeper实现锁的过程:通过创建临时顺序节点实现锁功能,优点是可重入,缺点是频繁创建节点相较redis性能比较低下。
Redission解决续期和死锁问题:
Redission加锁和解锁流程大致如下:
文章来源:https://www.toymoban.com/news/detail-478027.html
RLock是Redission分布式锁最核心的接口,集成了concurrent包的Lock接口和自己的RLockAsync接口。文章来源地址https://www.toymoban.com/news/detail-478027.html
到了这里,关于Redis底层原理深入学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!