noSQL = not only sql !!! (不仅仅是SQL)
关系型数据库:行+列,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的形式,并且可以横向拓展。
RDBMS
NoSQL
整体的存储结构是一个大的hashmap
dictEntry由key-value组成,其中的value指的是redisObject
redisObject:具体指向redis数据类型 ptr-数据结构的地址 type-对象的类型
在int数据结构范围内使用long
小于44字节使用embstr,redisObject和sds存储在一起,形成一个连续的内存块
大于44字节使用raw,redisObject和sds分别分配内存
简单动态字符串,是redis数据库中用于存储字符串数据的自定义数据结构。embstr和raw都为sds编码。
embstr(短字符串)使用最小的一个sdshdr8
redisObject=16字节 sdshdr8=4字节
初始最小分配为64字节 所以 使用embstr的时候64-16-4=44 要小于44字节,如果大于44使用raw(长字符串)
3.2之前使用ziplist+linkedlist:满足以下两个条件时 使用ziplist
list中保存的元素小于64字节
列表中数据小于512
3.2之后使用quicklist:基于ziplist的双向链表
附加空间相对太高,通常每个指针占用8个字节,prev(前节点)和next(后节点)就占用16个字节,而且每一个节点都是单独分配,会加剧内存碎片化,影响内存管理效率。
压缩列表,存储在连续的内存区域中,当列表元素不大,每个元素也不大时,采用ziplist存储。
当数据量过大时ziplist就不好用了,ziplist是连续的内存存储,新增或更新元素时,分配不足容纳新数据,整个列表可能需要重新内存分配,这在大数据量操作成本较高且可能导致频繁的内存碎片
快速列表。结合了ziplist和linkedlist的优点。为列表类型提供更好的空间效率和性能。
基本思想是将多个小的ziplist片段通过双向链表链接起来。每个ziplist片段存储一部分连续的数据元素。
数据项比较少的情况下,使用ziplist,随着数据的增加,底层的ziplist可能会转成dict(hashtable-ht)!
dict结构有两个hashtable,但是通常情况下只有一个是有指的。在dict扩容或缩容的时候,需要重新分配hashtable。然后进行rehash(渐进式搬迁)搬迁结束后旧的删除,新的代替。
主要应用在扩容和缩容操作,当哈希表需要进行大小调整时,如果一次性将所有键值对重新计算哈希并移动到新的哈希表,消耗大量的CPU。
在渐进式Rehash过程中:
内部实现相当于一个特殊的字典,字典中所有的value都是一个值null。底层编码包括hashtable和intset
满足以下条件时,使用intset,否则使用hashtable
一个有序集合(升序)查找元素O(logN)二分法,但插入时不一定是O(logN),因为有可能涉及到升级操作,比如当集合里全是int16_t型整数,此时插入一个int32_t,那么为了维持集合中数据类型的 一直,所有的数据都会转换成32类型,涉及到内存的重新分配,这时插入的复杂度为O(N)。intset不支持降级操作
给每个元素设置了一个分数,作为排序的依据。
数据结构使用了ziplist和skiplist
每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(score)
跳跃表是一种随机化的数据,跳跃表以有序的方式在层次化的链表中保存元素。
在了解跳跃表之前,我们先了解一下有序链表。有序链表是所有元素以递增或递减方式有序排列的数据结构,其中每个节点都指向下个节点的next指针,最后一个节点的next指针指向NULL。
使用单有序链表,如果要查询31的元素,需要从第一个元素开始依次向后查询、比较才可以找到,找到顺序为1->8->11->12->26->31,共6次比较,时间复杂度为O(N)
使用分层有序链表,比如我们查找值为31的节点时,查找步骤如下:
header:指向跳跃表头节点,头节点时跳跃表的特殊节点,他的level是固定数组元素个数未32个
tail:指向跳跃表尾节点
length:跳跃表长度,表示第0层除头节点以外的所有节点总数
level:跳跃表高度
事务本质:一组命令的事务
数据库事务-通过ACID(原子性,一致性,隔离性,持久性)来完成。
Redis事务-将多个命令打包,然后一次性按顺序地执行的机制,并且事务在执行的期间不会主动中断,服务器在执行完事务中的所有命令之后 才会继续处理其他客户端的其他命令
Redis 是一种内存型数据库,一旦服务器进程退出,数据库的数据就会丢失,为了解决这个问题 Redis 供了两种持久化的方案,将内存中的数据保存到磁盘中,避免数据的丢失
经过压缩的二进制文件,这个文件保存到磁盘,可以手动执行,也可以定时执行
手动触发的分为save(同步),bgsave(异步)
已日志的方式只记录写操作,不会记录读操作。只允许追加文件不可修改文件。redis在启动的时候会调用aof文件执行指令记录
缓存穿透是指缓存和数据库中都没有数据,而用户不断发送请求,在流量大的时候容易导致宕机。
解决方案:
在接口层校验访问的key是否合法,例如校验id>0
缓存和数据库都找不到值的情况下,把value设置null值
使用布隆过滤器类似于hahs set 用于快速判断某个元素是否存在于集合中,如果没有直接返回。
缓存中没有数据,但是数据库中有(一般大量数据到期)这时并发用户特别多,同时读缓存没有导致读大量请求数据库,导致数据库压力剧增.(并发查同一数据)
解决方案:
大量数据同时到期,而查询量巨大,引起数据库数量过大或宕机。和缓存击穿不同的时,缓存击穿是并发查同一条数据,而缓存雪崩是不同的数据都过期了
解决方案:
LRU(Least Recently Used):侧重于最近的历史访问记录 适用于大多数情况下通用缓存
LFU(Least Frequently Used):侧重于历史访问的总频率 使用于访问模式与访问频次紧密相关的场景
主从复制,是指将redis一台服务器复制到另一台redis服务器上,前者为主节点(master)后者为从节点 (slave);数据的复制是单向的,只能主节点到从节点,默认通常情况下每一台redis服务器都是一个主节点,且一个主节点可以有多个从节点或没有,一个从节点只能有一个主节点。
主从复制的作用:
全量复制:首次创建从节点并连接主节点时,会触发全量复制,主节点生成当前所有数据的rdb文件,让从节点执行。这个过程会清除原从节点的数据。
增量复制:是在主从节点已经建立了复制关系且保持更新的情况下进行数据同步的方式。redis在完成初始化全量同步之后,会切换到增量复制模式。主节点在接受到命令后会将这些命令按照顺序发送给从节点执行。
1.主从库建立连接与协商同步:新的从节点加入集群时,主动向主节点发送PSYNC命令。提供自己的runid和偏移量(作用数据是否同步完整)首次没有的话执行全量复制。
2.主节点生成并发送RDB快照给从节点:创建RDB文件,这个文件包含了全部的数据集并发送给从节点。
3.主节点发送缓冲区中的命令流:在主节点实现节点2的时候,可能会产生新增的数据,这个阶段已增量命令的形式发送从节点来保证完整数据。
哨兵的核心功能是主节点的自动故障迁移。
哨兵实现功能:
RMQ是一个二方库,基于Redis实现的消息队列的功能。
RMQ有消息合并,消息优先级,定时消息等特性。
可以用于异步解耦,削峰填谷,亿级数据堆积。
发送消息时需要定义一个时间区间,消息延迟改时间区间长度后会消费消息,在改时间区间内如果发送消息,重复消息将会被合并。如果消息在Redis服务端发生推积,重复到来的消息依然会被合并处理。改类型消息适用于消息重复率较高且希望重复消息合并处理的场景,对重复消息进行合并可以减少下游消费系统的压力,减少不必要的资源消耗,将有限的资源最大化的利用,提升消费效率。
支持给消息设置任意等级的优先级,优先级高的消息会被优先消费,相同优先级的消息被随机消费,如果消息在redis服务端发生堆积,重复的消息将合并处理,合并后消息的优先级等于最后存储的消息的优先级,该类型消息适用于希望重复消息合并处理且设置优先级的场景,下游消费者资源有限时,合并重复消息且优先处理优先级高的消息将可以合理利用有限资源。
支持给消息设置任意消费时间,只有消费时间到了之后消息才被消费,消费时间可精确到秒,消息到期后没有及时被消费,消费之将按照时间由远及近进行消费。如果消息在redis服务端发生堆积,重复的消息将被合并处理,合并后消息的消费时间等于最存储的消费的消费时间,该类型消息适用于希望重复合并并处理且需要定时消费的 场景定时消息应用场景非常丰富,比如定时打标去标,活动结束后清理动作,订单超时关闭等。
基于持久化aop的优化方式,也称为aof分块持久化或AOF子部分持久化。
优化后,不在对整个文件进行追加,而是将AOF文件分割成多个部分(part),每次只重写其中一部分,并且可以并行执行重写和追加新的命令操作。
费。如果消息在redis服务端发生堆积,重复的消息将被合并处理,合并后消息的消费时间等于最存储的消费的消费时间,该类型消息适用于希望重复合并并处理且需要定时消费的 场景定时消息应用场景非常丰富,比如定时打标去标,活动结束后清理动作,订单超时关闭等。
基于持久化aop的优化方式,也称为aof分块持久化或AOF子部分持久化。
优化后,不在对整个文件进行追加,而是将AOF文件分割成多个部分(part),每次只重写其中一部分,并且可以并行执行重写和追加新的命令操作。