精华内容
下载资源
问答
  • 你预估你的并发是多少,然后测试r+m是否支持。 还有要纠正你下,缓存的目的是为了应对普通对象数据库的读写限制,依托与nosql的优势进行高速读写。 redis本身也有并发瓶颈。 所以你要把读写和并发区分开来处理。 ...

    处理并发问题的重点不在于你的设计是怎样的
    而在于你要评估你的并发,并在并发范围内处理。
    你预估你的并发是多少,然后测试r+m是否支持。

    还有要纠正你下,缓存的目的是为了应对普通对象数据库的读写限制,依托与nosql的优势进行高速读写。

    redis本身也有并发瓶颈。
    所以你要把读写和并发区分开来处理。

    1:峰值并发,最小并发,最高并发,读写
    你的峰值并发应该在你设计的最小并发和最高并发之间寻求平衡
    缓存更多处理读写问题,并发并不是主要目的。

    分布处理并发
    缓存处理读写

    50个并发只有50次读写,优化mysql就够了。
    50个并发10万次读写,就必须用nosql缓存
    200以上并发200读写,分库分表或者读写分离
    200以上并发百万级读写,就需要redis或者其他nosql进行分布式来提高实时性能。

    还有考虑只读的特殊性

    以上的50和200只是距离,具体单一服务器单一部署的支撑上限,也要看压力测试的结果

    mysql高手可以做到1500并发的我见过,200并发就超时的redis我也见过

    2:维护性
    要考虑不同系统进行部署实施运维的难易程度
    3:根据具体业务
    只读业务是不是可以用mysql分布做只读库和只读表,进行读写分离+库分布
    拆库拆表不能搞定再考虑上多级缓存
    任何设计,你外面套一层,就多一倍的维护成本,缓存不是万金油

    链接:https://www.zhihu.com/question/53819414/answer/136758516
    展开全文
  • 面试题顺序:ActiveMQ+redis+Spring+高并发多线程+JVM ActiveMQ消息中间件面试专题 什么是ActiveMQ? ActiveMQ服务器宕机怎么办? 丢消息怎么办? 持久化消息非常慢怎么办? 消息的不均匀消费怎么办? 死信...

    给大家分享我收集的面试资料, 希望你们领取以后都能拿到BAT的offer!

    面试题顺序:ActiveMQ+redis+Spring+高并发多线程+JVM

    ActiveMQ消息中间件面试专题

    • 什么是ActiveMQ?
    • ActiveMQ服务器宕机怎么办?
    • 丢消息怎么办?
    • 持久化消息非常慢怎么办?
    • 消息的不均匀消费怎么办?
    • 死信队列怎么办?
    • ActiveMQ中的消息重发时间间隔和重发次数吗?

    ActiveMQ消息中间件面试专题解析拓展:

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

     


    redis面试专题及答案

    • 支持一致性哈希的客户端有哪些?
    • Redis与其他key-value存储有什么不同?
    • Redis的内存占用情况怎么样?
    • 都有哪些办法可以降低Redis的内存使用情况呢?
    • 查看Redis使用情况及状态信息用什么命令?
    • Redis的内存用完了会发生什么?
    • Redis是单线程的,如何提高多核CPU的利用率?

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

     


    Spring面试专题及答案

    • 谈谈你对 Spring 的理解
    • Spring 有哪些优点?
    • Spring 中的设计模式
    • 怎样开启注解装配以及常用注解
    • 简单介绍下 Spring bean 的生命周期

    Spring面试答案解析拓展

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

     


    高并发多线程面试专题

    • 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
    • Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
    • Java 中 wait 和 sleep 方法有什么区别?
    • 如何在 Java 中实现一个阻塞队列?
    • 如何在 Java 中编写代码解决生产者消费者问题?
    • 写一段死锁代码。你在 Java 中如何解决死锁?

    高并发多线程面试解析与拓展

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

     


    jvm面试专题与解析

    • JVM 由哪些部分组成?
    • JVM 内存划分?
    • Java 的内存模型?
    • 引用的分类?
    • GC什么时候开始?

    JVM面试专题解析与拓展!

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

     

     

    BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

    展开全文
  • Redis作为高并发程序的必备神器,基本是必不可少的一款中间件,在日常使用中,对Redis的增删改查的过程中,你有没有想过每条命令在Redis中是如何执行的,不同的数据类型是通过什么样的数据结构保存在Redis的内存中呢...

    Redis作为高并发程序的必备神器,基本是必不可少的一款中间件,在日常使用中,对Redis的增删改查的过程中,你有没有想过每条命令在Redis中是如何执行的,不同的数据类型是通过什么样的数据结构保存在Redis的内存中呢?什么!不知道? 没关系,看完这篇文章你就知道了~

    Redis在日常开发中常用的五种数据结构,分别为StringListHashSetZset,不同的应用场景不同的数据结构来支持,下面就一起看看到底是什么样的数据结构让Redis这么快,开干!

    干

    对象

    Redis数据库中,我们常用的五种数据类型并不是由某种数据结构直接实现,而基于数据结构构建了对象系统,包含字符串对象列表对象哈希对象集合对象有序集合对象,每种对象都至少使用到2中数据结构进行实现!

    • 对象的结构

      typeof struct redisObject {
          // 对象类型
          unsigned type:4;
          // 编码
          unsigned encoding:4;
          // 指向底层具体数据结构的指针
          void *ptr;
      }
      
      • 类型:一共有5中类型,分别为字符串对象列表对象哈希对象集合对象有序集合对象

        type key – 查看key的类型

      • **编码:**编码标识对象使用了什么样的编码,即具体的底层数据结构是什么(不同的编码,则不同的数据结构来实现),并且不同编码之间会根据具体保存的数据转换。

      • **指针:**执行对象的底层数据结构的指针

    字符串对象String

    String在Redis中准确的应该是字符串对象,字符串对象的底层实现数据结构分别有:intrawembstrsds等。

    int、raw、embstr编码格式

    这三种实现分别是字符串的不同编码格式,当不同类型的字符串时,使用不同的编码格式进行存储,最大程度的节约内存和提升效率。当字符串中保存的都是整数,会使用int类型的编码,当保存的字符串较短时,则使用rawembstr进行保存。

    SDS编码格式

    众所周知,Redis是使用C语言写的,但是其中的字符串并没有使用C语言中的字符串,而是自己实现了字符串,名为简单动态字符串(SDS)

    Redis中不仅将SDS作为String的底层数据结构的实现,同时SDS还应用于各种场景,例如:列表、Redis的key等。

    SDS的实现

    既然Redis舍弃了C语言中的字符串, 单独实现了SDS作为字符串的实现,那么SDS有哪些优点呢

    • SDS的结构

      struct sdshdr {
          // 记录sds字符串的长度,相当于buf数组中已使用的长度
          int len;
          // 记buf数组中还没有使用的长度,可用长度
          int free;
          // 字符数组,保存字符串
          char buf[];
      }
      

      首先可以看到sds的结构定义,记录了自身的字符串的长度,没有使用的内存长度,相对于C字符串可以提升较大的使用效率

    • 与C字符串区别

      • 当使用命令strlen时:C字符串并不记录自身的长度,如果需要获取自身长度,那么需要遍历整个字符串,时间复杂度为O(N),而SDS的结构中已经记录了当前字符串的长度,如果需要统计长度,那么可以直接获取到长度,时间复杂度为O(1)。

      • 当需要对字符串拼接时,由于C字符串不记录自身的长度,则在拼接时可能会由于剩余内存不足导致缓冲区溢出,而SDS保存了自身的字符串长度,并且还记录当前未使用的空间大小,在进行字符串拼接时,会先检查当前的内存空间是否满足当前操作,如果不满足则进行扩容,不会出现缓冲区溢出的问题。

      • C字符串在频繁拼接或缩短字符串的操作中,会频繁的进行系统调用进行内存的重分配,非常消耗性能。SDS在在空间重分配方面采取了空间预分配策略惰性释放空间策略来优化性能。

        • 空间预分配

          当需要进行内存分配时,不仅会分配指定的内存大小,还会多分配额外的内存空间保证在频繁字符串拼接的过程中,不会出现连续内存分配的情况

          • 当SDS的长度小于1M的时候,会额外分配和len属性相同大小的额外空间
          • 当SDS大于1M时,额外分配的内存空间为1M
        • 空间惰性释放

          当字符串进行缩短时,SDS并不会立即回收这部分需要释放的内存空间,而是使用free属性记录当前多出来的字节长度,等待将来进行字符串拼接时使用。

          当然,是以使用更多的内存为代价来提升效率低。以空间换时间。SDS也提供了API可以手动的释放内存空间

        • 二进制安全

          C语言的字符串,字符串总是以\0结尾,这就导致C语言的字符串无法保证输入的字符串和输出的字符串是完全相等的数据,并且C语言的字符串需要符合某种编码,所以C语言的字符串不能保存图片、音频、视频等二进制数据。而Redis中的SDS是二进制安全的,因为SDS不会根据特殊的标志进行字符串的区分。

          例如:123\0456,在C语言中保存为:123\0,而Redis中保存的是:123\0456

          二进制安全可以简单理解为,字符串不是根据某种特殊标志进行解析的,原始的输入和输出是相同的,不会根据某种特殊格式处理。

      • 当然,SDS也兼容了部分C语言字符串特性,在有需要时,SDS可以直接使用C字符串的部分函数

    编码转换

    int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下会被转换为raw编码的字符串对象

    • 当字符串对象保存的字符串时整数时,使用int编码,否则使用raw编码
    • 当对字符串对象进行修改时,会将embstr编码转换为raw编码,因为embstr编码的对象并没有提供修改的函数

    列表对象List

    列表作为一种常用的数据结构,Redis也重新实现了链表,而没有使用C中的链表,这也是Redis链表可以高效的一个原因。列表对象的编码可以是ziplist(压缩列表)或者linkedlist*(双端链表)

    压缩列表

    压缩列表

    压缩列表是Redis为了节约内存开发的一个连续内存块组成的顺序性数据结构,一个压缩里诶便可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。

    • 数据结构
    • zlbytes:记录整个压缩列表占用的内存字节数
      • zltail:记录压缩列表的起始地址到尾节点的字节长度
      • zllen:记录压缩列表中包含的节点数量
      • entry:压缩列表中的节点,每个节点中保存的是对应的数据
      • zllend:用于标记压缩列表的末端
    双端链表

    Redis中List结构的链表包含了前指针和后指针,可以快速的获取每个节点的前后节点,并且可以通过头指针和尾指针从双端开始遍历链表数据,这也是链表结构可以实现队列的因素。并且链表中保存了长度计数器,可以通过O(1)的时间复杂度获取到链表的长度。

    双端链表

    • 数据结构
      • **head:**头指针,指向链表节点的头结点
      • **tail:**尾指针,指向链表节点的尾节点
      • **len:**链表节点的长度,一共有多少个链表的节点
      • **dup:**dup函数用于复制链表节点中的值
      • **free:**free函数用于释放链表节点中的值
      • **match:**match函数用于对比链表节点中保存的值和另一个输入值是否相等
    编码转换

    当列表对象可以同时满足下面的条件时,列表对象使用ziplist作为底层的编码格式,否则使用链表作为底层实现

    • 列表对象保存的所有字符串元素的长度都小于64字节
    • 列表对象保存的元素数量小于512个

    上面的两个条件中的上限值都是可以通过参数进行调整。

    当满足上述的条件时,压缩列表中的元素就会被迁移并保存到双端链表中

    哈希对象Hash

    Redis中的哈希对象同样也是用了两种编码格式实现,在不同的场景下使用不同的数据结构。哈希对象的编码可以是ziplisthashtable

    ziplist

    压缩列表

    • 和列表对象相同的是,哈希对象也使用ziplist作为底层实现之一,当数据量较少的时候,压缩列表保存数据时可以节省内存。保存的结构是key和value是相邻的两个元素,保存了同一个键值的两个节点总是紧挨在一起,并且保存键的节点在前,保存值的节点在后。

      在压缩列表中查找值的时候,也是通过先找到对应的键,然后通过将指针向后移动一位,找到该键对应的值

    hashtable

    hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对保存

    • 字典

      字典结构

      Redis中的字典使用哈希表作为底层实现,哈希表就是常用的数组+链表的实现方式来保存键值对。

      // 字典结构
      typedef struct dict {
          // 类型特定函数
      	dictType *type;
          // 私有数据
          void *privdata;
          // 哈希表
          dicht ht[2];
          // rehash索引
          int trehashindx;
      
      } dict;
      
      // 哈希表结构
      typedef struct dictht {
          //哈希表数组
          dictEntry **table;
          //哈希表大小
          unsigned long size;
          // 哈希表大小掩码,总是等于size-1,用于计算索引的值
          unsigned long sizemask;
          // 哈希表已有节点数量
          unsigned long used;
      } dictht;
      
      // 哈希表节点结构
      typedef struct dictEntry {
          //键
          void *key;
          //值
          union {
              void *val;
              uint64_t u64;
              int64_t s64;
          } v;
          // 指向下一个哈希表节点,形成链表
          struct dictEntry *next;
      } dictEntry;
      
      • 属性说明:
        • dicht ht[2]:ht属性是包含两个元素的数组,并且都是哈希表的类型元素,一般情况下,字典只会使用**ht[0]**的哈希表作为数据的保存,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用
        • type:包含特定的哈希表需使用到的函数,例如:计算哈希值的函数、对比键的函数、删除键的函数、删除值的函数等
    • 哈希冲突

      因为字典采用了数组+链表的数据结构,所以尽管Redis的哈希算法可以给出很好的随机分布性,但是仍然会有部分哈希冲突存在。Redis的哈希表使用链地址法来解决哈希冲突,通过每个哈希表的next指针来构建为单项链表。

      哈希冲突

    • rehash

      熟悉Java中的HashMap的小伙伴都知道,HashMap中的数据超过负载因子的长度时,HashMap就会进行扩容,在扩容的过程中会进行rehash操作,而Redis中的哈希表也是相同的原理,所以在哈希表中的键值对过多或者过少的时候,会触发哈希表的扩容和缩容,这个过程中就会执行rehash操作。

      • rehash的步骤:

        • 对ht[1]的哈希表分配内存空间,哈希表的大小取决于要执行的操作,以及ht[0]中包含的键值对的数量

          • 对于扩展操作,那么扩展的哈希表的大小为第一个大于等于ht[0].used*2的2的n次方
          • 对于收缩操作,那么收缩哈希表的大小为第一个大于等于ht[0].used的2的n次方的长度
        • 接下来将所有保存在ht[0]中的键值对rehash到ht[1]上面

          重新计算所有键的哈希值、数组索引值

        • 将ht[0]中的所有的键值对迁移到ht[1]之后,会将ht[0]的空间释放,并交换指针,将ht[1]设置为ht[0],并给ht[1]建立一个新的哈希表,为下次rehash做准备

      • 哈希表的扩容和收缩

        当下面的条件任意一个满足时,就会进行哈希表的扩容或者收缩:

        • 当服务器没有执行BGSAVE或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1

        • 服务器正在执行BGSAVE或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5

          负载因子 = 哈希表已保存的节点数量 / 哈希表大小

        因为BGSAVE命令和BGREWRITEAOF命令在执行时,都需要使用额外的内存空间,BGSAVE命令是生成内存快照RDB文件时,采用COW机制,通过子进程对内存的当前数据做快照保存,所以内存会有额外的内存使用。BGREWRITEAOF命令是对AOF文件的重写时,会使用到额外的AOF重写缓冲区。Redis在执行这两个命令时,将负载因子增大的目的是为了节约内存,最好不要在内存使用较大的时机进行rehash操作。

    • 渐进式rehash

      在哈希表进行rehash的过程中,并不是一次性的将所有的键值对都rehash到ht[1]中,因为当键值对的数量过大,那么在rehash过程中的计算量是非常大的,redis在这个过程中是无法提供服务的。所以Redis采用了渐进式rehash操作,将所有的键通过分批次、渐进式的完成迁移。

      • 渐进式rehash操作的步骤:

        • 为ht[1]分配内存空间,让字典同时持有ht[0]和ht[1]两个哈希表

        • 将字典中的索引计数器变量rehashidx设置为0,表示当前正在rehash

        • rehash的过程中,每次对字典执行添加、删除、查找或更新操作时,程序除了执行原本的指令操作之外,还会将ht[0]哈希表中的部分键值对进行rehash

          部分键值对并不是随机查找的,而是通过rehashidx对应的哈希表的索引上面的键值对进行rehash,并且在一次rehash完成之后,将rehashidx自增

        • 在rehash完成之后,再讲rehashidx设置为-1,并交换ht[0]和ht[1]的指针

        rehash的优点就在于分而治之的思想,将大量的操作平均到每个小的操作过程中,防止集中的操作带来的较大的计算量

      • 渐进式rehash过程中哈希表操作

        在渐进式rehash过程中,字典会同时拥有ht[0]和ht[1]两个哈希表,所以在执行查找、删除、更新操作时,程序会现在ht[0]中查找,如果没有找到的话,那么就需要在ht[1]中查找。并且在rehash的过程中,新添加的键值对都会直接添加到ht[1]中,所以ht[0]中的键值对是只减不增,那么最终会迁移完成。

    集合对象Set

    集合对象可以保存的键的特点为无序、不重复,所以这一点和字典非常像,同样的集合对象也有两种编码格式:intset或者hashtable

    intset

    当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合对象的底层实现,目的也是为了节约内存。

    • 整数集合的结构

      typedef struct intset {
          // 编码方式
          uint32_t encoding;
          // 集合包含的元素数量
          uint32_t length;
          // 保存元素的数组
          int8_t contents[];
      } intset;
      

      属性说明:

      • encoding:整数集合的编码方式有3种
        • INTSET_ENC_INT16:表示数组为int16_t类型的数组,每一项都是一个int16_t类型的整数值(short的范围:-32768 — 32767),每一项占用16位内存空间
        • INTSET_ENC_INT32:表示数组为int32_t类型的数组,每一项都是int32_t类型的整数值(int的范围:-2147483648 — 2147483467),每一项占用32为内存空间
        • INTSET_ENC_INT64:表示数组为int64_t类型的数组,每一项都是int64_t类型的整数值(long的范围:-9223372036854775808 — 9223372036854775807),每一项占用64位内存空间
      • length:数组中保存的元素数量
      • contents:保存元素的数组,虽然类型为int8_t,但是数组中元素类型是通过encoding的编码方式决定。
    • 整数集合的升级

      整数集合的数组的编码方式是固定的,所以数组的编码方式只能是同一种,当数组中添加的新元素类型比现有集合中所有的元素类型都要长时,那么整数集合就需要升级,并且数组中的每个元素的类型都会转换为最大的类型。

      • 升级的步骤:

        • 根据新元素的类型,扩展整数集合的底层数组的空间大小,并未新元素分配内存空间
        • 将数组中的所有元素都转换为新元素的类型,并将转换之后的元素放置到正确的位置上
        • 将新元素添加到数组中
      • 升级的好处

        • 提升灵活性:为了避免类型错误,不会将两种不同的类型放入到一个数组中,而通过升级,让数组中的所有元素的类型保持一致
        • 最大程度节约内存:避免类型错误,不会再同一个数组中使用不同的类型元素,而如果直接使用int64_t的类型,那么如果这个数组不会保存int64_t的元素类型,那么就会出现内存浪费,所以通过数组类型升级的方式可以避免内存浪费,节约内存。
      • 整数集合的降级

        整数集合不支持降级,一旦数组升级,那么数组的编码格式不会改变

    hashtable

    hashtable编码的集合对象使用字典作为底层数据结构,字典的每个键都是一个集合元素,而字典的值全部默认设置为NULL。

    集合对象

    编码转换

    intset或者hashtable在一定的条件下都是可以相互转换的数据类型

    当符合下面的条件时,集合对象使用intset作为底层编码:

    • 集合对象保存的所有元素都是整数值
    • 集合对象保存的元素数量小于512个

    当元素中含有非整数值或元素的数量大于512个,那么就会使用hashtable编码作为集合的底层实现

    有序集合对象

    有序集合的特点是根据score自动进行排序、保证key的唯一性。有序集合的底层编码可以是ziplistskiplist两种

    ziplist

    压缩列表

    有序集合底层使用压缩列表实现当然也是为了在适当的数量时最大程度的节约内存空间

    压缩列表在多个redis对象中均有使用,列表对象、哈希对象、有序集合对象等,不过每个对象在使用压缩列表保存数据时,根据每个对象的结构的特性不同保存数据的方式也有些不同

    • 有序集合在保存集合元素时,会使用两个紧挨在一起的压缩列表的节点进行保存数据,第一个节点保存的是元素的成员(key),第二个节点保存的是元素的分值(score),这样就可以根据key找到相应的score,并且在压缩列表中集合元素按照分值进行排序,分值较小的元素被放置在靠近表头的位置,而分值较大的元素则被放置在靠近表尾的位置。
    skiplist

    使用了skiplist编码实现的有序集合,底层使用的数据结构为zset,一个zset结构,同时包含一个字典跳跃表

    • 跳跃表

      跳跃表数据结构

      skiplist编码实现新增的数据结构为跳跃表,跳跃表在有序集合的数据结构中,主要作用是对score进行排序,在使用ZRANKZRANGE命令时,就是通过基于跳跃表的API实现,可以让命令执行的更快,提高效率。

      • redis中跳跃表的数据结构

        /* 跳跃表的节点对象 */
        typedef struct zskiplistNode {
            // value
            sds ele;
            // 分值
            double score;
            // 后退指针
            struct zskiplistNode *backward;
            // 层
            struct zskiplistLevel {
                // 前进指针
                struct zskiplistNode *forward;
                // 跨度
                unsigned long span;
            } level[];
        } zskiplistNode;
        
        /* 跳跃表的对象,内部维护跳跃表的节点对象 */
        typedef struct zskiplist {
            // 跳跃表头指针
            struct zskiplistNode *header, *tail;
            // 表中节点的数量
            unsigned long length;
            // 表中层数最大的节点的层数
            int level;
        } zskiplist;
        

      redis中的跳跃表和数据结构中的跳跃表稍微有些不同,因为Redis中的跳跃表最大的层数为32层,至于为什么默认只有32层,查阅了相关资料,并没有说明,不过根据猜测可能是32层就完全够用了, 因为根据概率进行计算,32层的元素的概率较低,当数据量较大的时候才会有32层的的数据

      为什么不用链表、数组,而是用跳跃表

      因为需要随机插入和删除,而如果使用链表和数组,那么时间复杂度会是O(N),跳跃表的增删改查都是O(logN)的时间复杂度

    • zset的数据结构

      zset的数据结构中包含了字典和跳跃表

      zset数据结构

      typedef struct zset {
          zskiplist *zsl;
          dict *dict;
      } zset;
      
      • zskiplist:通过跳跃表实现,主要是作为score的排序,对成员进行范围查询
      • dict:通过字典保存了score和成员之间的映射关系,key是成员,value是score
    • zset的执行原理

      zset中既包含字典、也包含跳跃表,不过两者的作用不同,分工明确。

      • 字典

        zset结构中的字典保存的是成员到分值的映射关系,可以通过成员以O(1)的时间复杂度获取到相应的分值,其中ZSCORE命令就是根据字典的特性实现

      • 跳跃表

        跳跃表中将成员的分数按照从小到大的顺序已经自动排序,每个跳跃表的节点既保存了成员对象也保存了成员对象的分值,通过跳跃表,可以对有序集合范围型操作,并且时间复杂度较低,例如ZRANGEZRANK等命令就是通过跳跃表实现

      因为跳跃表虽然能够快速的范围性查找,但是对于根据成员查找分值这种特定的操作,跳跃表查询的时间复杂度为O(logN),而哈希表的查找时间复杂度为O(1)。哈希表在顺序性的范围查找的时间复杂度为O(NlogN),而跳跃表的时间复杂度为O(logN)。

      虽然Redis内存同时使用了跳跃表和哈希表两种数据结构,但是这两种数据结构会通过指针来共享相同元素的成员和分值,所以尽管Redis使用了两种数据结构保存成员和分值,但是不会占用额外的内存,不会造成内存浪费的情况。

    编码转换

    ziplistskiplist在适当的条件下会进行编码的转码,同时底层的数据结构也会进行改变,在节省内存的同时,也会同时保证命令的高效执行

    当有序集合对象满足下面的条件时,对象会使用ziplist编码:

    • 有序集合保存的元素数量小于128个
    • 有序集合保存的元素成员的长度都小于64字节

    如果不满足上面的条件,那么有序集合将采用skiplist编码格式实现

    总结

    对于Redis中的字符串、列表、哈希、集合、有序集合,每种类型的对象至少都有两种或两种以上的编码方式,不同的编码方式在底层数据结构的实现上可能完全不同,使用不同的编码方式在不同的场景上优化对象的使用效率,这也是Redis之所以可以这么快的一个主要原因之一。

    参考资料

    《Redis 设计与实现》 - http://redisbook.com/

    《Redis 深度历险》 - https://book.douban.com/subject

    《Redis(2)——跳跃表》- https://zhuanlan.zhihu.com/p/109946103

    公众号
    微信公众号「指尖上的代码」,欢迎关注~

    原创不易, 点个赞再走呗~ 欢迎关注,给你带来更精彩的文章!

    你的点赞关注是写文章最大的动力~

    展开全文
  • Redis用作缓存,主要两个用途:高性能,高并发,因为内存天然支持高并发 应用场景 分布式锁(string) setnx key value,当key不存在时,将 key 的值设为 value ,返回1。若给定的 key 已经存在,则setnx不做任何...

    前言

    redis是键值对的数据库,常用的五种数据类型为字符串类型(string),散列类型(hash),列表类型(list),集合类型(set),有序集合类型(zset)

    Redis用作缓存,主要两个用途:高性能,高并发,因为内存天然支持高并发

    应用场景

    分布式锁(string)

    setnx key value,当key不存在时,将 key 的值设为 value ,返回1。若给定的 key 已经存在,则setnx不做任何动作,返回0。

    当setnx返回1时,表示获取锁,做完操作以后del key,表示释放锁,如果setnx返回0表示获取锁失败,整体思路大概就是这样,细节还是比较多的,有时间单开一篇来讲解

    计数器(string)

    如知乎每个问题的被浏览器次数

    Redis的n种妙用,不仅仅是缓存

     

    set key 0
    incr key // incr readcount::{帖子id} 每阅读一次
    get key // get readcount::{帖子id} 获取阅读量

    分布式全局唯一id(string)

    分布式全局唯一id的实现方式有很多,这里只介绍用redis实现

    Redis的n种妙用,不仅仅是缓存

     

    每次获取userId的时候,对userId加1再获取,可以改进为如下形式

    Redis的n种妙用,不仅仅是缓存

     

    直接获取一段userId的最大值,缓存到本地慢慢累加,快到了userId的最大值时,再去获取一段,一个用户服务宕机了,也顶多一小段userId没有用到

    set userId 0
    incr usrId //返回1
    incrby userId 1000 //返回10001

    消息队列(list)

    在list里面一边进,一边出即可

    # 实现方式一
    # 一直往list左边放
    lpush key value 
    # key这个list有元素时,直接弹出,没有元素被阻塞,直到等待超时或发现可弹出元素为止,上面例子超时时间为10s
    brpop key value 10 
    # 实现方式二
    rpush key value
    blpop key value 10

    Redis的n种妙用,不仅仅是缓存

     

    新浪/Twitter用户消息列表(list)

    Redis的n种妙用,不仅仅是缓存

     

    假如说小编li关注了2个微博a和b,a发了一条微博(编号为100)就执行如下命令

    lpush msg::li 100

    b发了一条微博(编号为200)就执行如下命令:

    lpush msg::li 200

    假如想拿最近的10条消息就可以执行如下命令(最新的消息一定在list的最左边):

    # 下标从0开始,[start,stop]是闭区间,都包含
    lrange msg::li 0 9 

    抽奖活动(set)

    # 参加抽奖活动
    sadd key {userId} 
    # 获取所有抽奖用户,大轮盘转起来
    smembers key 
    # 抽取count名中奖者,并从抽奖活动中移除
    spop key count 
    # 抽取count名中奖者,不从抽奖活动中移除
    srandmember key count

    实现点赞,签到,like等功能(set)

    Redis的n种妙用,不仅仅是缓存

     

    # 1001用户给8001帖子点赞
    sadd like::8001 1001
    # 取消点赞
    srem like::8001 1001
    # 检查用户是否点过赞
    sismember like::8001 1001 
    # 获取点赞的用户列表
    smembers like::8001 
    # 获取点赞用户数
    scard like::8001 

    实现关注模型,可能认识的人(set)

    Redis的n种妙用,不仅仅是缓存

     

    seven关注的人

    sevenSub -> {qing, mic, james}

    青山关注的人

    qingSub->{seven,jack,mic,james}

    Mic关注的人

    MicSub->{seven,james,qing,jack,tom}

    # 返回sevenSub和qingSub的交集,即seven和青山的共同关注
    sinter sevenSub qingSub -> {mic,james}
    # 我关注的人也关注他,下面例子中我是seven
    # qing在micSub中返回1,否则返回0
    sismember micSub qing
    sismember jamesSub qing
    # 我可能认识的人,下面例子中我是seven
    # 求qingSub和sevenSub的差集,并存在sevenMayKnow集合中
    sdiffstore sevenMayKnow qingSub sevenSub -> {seven,jack}

    电商商品筛选(set)

    Redis的n种妙用,不仅仅是缓存

     

    每个商品入库的时候即会建立他的静态标签列表如,品牌,尺寸,处理器,内存

    # 将拯救者y700P-001和ThinkPad-T480这两个元素放到集合brand::lenovo
    sadd brand::lenovo 拯救者y700P-001 ThinkPad-T480
    sadd screenSize::15.6 拯救者y700P-001 机械革命Z2AIR
    sadd processor::i7 拯救者y700P-001 机械革命X8TIPlus
    # 获取品牌为联想,屏幕尺寸为15.6,并且处理器为i7的电脑品牌(sinter为获取集合的交集)
    sinter brand::lenovo screenSize::15.6 processor::i7 -> 拯救者y700P-001

    排行版(zset)

    redis的zset天生是用来做排行榜的、好友列表, 去重, 历史记录等业务需求

    Redis的n种妙用,不仅仅是缓存

     

    # user1的用户分数为 10
    zadd ranking 10 user1
    zadd ranking 20 user2
    # 取分数最高的3个用户
    zrevrange ranking 0 2 withscores

    过期策略

    定期删除

    redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的 key。

    定期删除策略

    Redis 默认会每秒进行十次过期扫描(100ms一次),过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

    从过期字典中随机 20 个 key;

    删除这 20 个 key 中已经过期的 key;

    如果过期的 key 比率超过 1/4,那就重复步骤 1;

    惰性删除

    除了定期遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。

    定期删除是集中处理,惰性删除是零散处理。

    为什么要采用定期删除+惰性删除2种策略呢?

    如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了

    但是问题是,定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

    并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下

    通过上述两种手段结合起来,保证过期的key一定会被干掉。

    所以说用了上述2种策略后,下面这种现象就不难解释了:数据明明都过期了,但是还占有着内存

    内存淘汰策略

    这个问题可能有小伙伴们遇到过,放到Redis中的数据怎么没了?

    因为Redis将数据放到内存中,内存是有限的,比如redis就只能用10个G,你要是往里面写了20个G的数据,会咋办?当然会干掉10个G的数据,然后就保留10个G的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了

    Redis提供的内存淘汰策略有如下几种:

    1. noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
    2. volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。(这个是使用最多的)
    3. volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
    4. volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
    5. allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
    6. allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。

    持久化策略

    Redis的数据是存在内存中的,如果Redis发生宕机,那么数据会全部丢失,因此必须提供持久化机制。

    Redis 的持久化机制有两种,第一种是快照(RDB),第二种是 AOF 日志。快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。AOF 日志在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。

    RDB是通过Redis主进程fork子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化,AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。即RDB记录的是数据,AOF记录的是指令

    RDB和AOF到底该如何选择?

    1. 不要仅仅使用 RDB,因为那样会导致你丢失很多数据,因为RDB是隔一段时间来备份数据
    2. 也不要仅仅使用 AOF,因为那样有两个问题,第一,通过 AOF 做冷备没有RDB恢复速度快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug
    3. 用RDB恢复内存状态会丢失很多数据,重放AOP日志又很慢。Redis4.0推出了混合持久化来解决这个问题。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

    缓存雪崩和缓存穿透

    缓存雪崩是什么?

    假设有如下一个系统,高峰期请求为5000次/秒,4000次走了缓存,只有1000次落到了数据库上,数据库每秒1000的并发是一个正常的指标,完全可以正常工作,但如果缓存宕机了,每秒5000次的请求会全部落到数据库上,数据库立马就死掉了,因为数据库一秒最多抗2000个请求,如果DBA重启数据库,立马又会被新的请求打死了,这就是缓存雪崩。

    Redis的n种妙用,不仅仅是缓存

     

    如何解决缓存雪崩

    事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃

    事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死

    事后:redis持久化,快速恢复缓存数据

    缓存穿透是什么?

    假如客户端每秒发送5000个请求,其中4000个为黑客的恶意攻击,即在数据库中也查不到。举个例子,用户id为正数,黑客构造的用户id为负数,

    如果黑客每秒一直发送这4000个请求,缓存就不起作用,数据库也很快被打死。

    Redis的n种妙用,不仅仅是缓存

     

    如何解决缓存穿透

    查询不到的数据也放到缓存,value为空,如set -999 “”

    总而言之,缓存雪崩就是缓存失效,请求全部全部打到数据库,数据库瞬间被打死。缓存穿透就是查询了一个一定不存在的数据,并且从存储层查不到的数据没有写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义

    推荐阅读

    黑客攻防:缓存穿透终极解决方案——布隆过滤器

    展开全文
  • 小伙伴们对redis应该不陌生,redis是系统必备的分布式缓存中间件,主要用来解决高并发下分担DB资源的负载,从而提升系统吞吐量。 redis支持多种数据类型,String(字符串)、list(列表)、hash(哈希)、set(集合...
  • 享学课堂特邀作者:老顾前言小伙伴们对redis应该不陌生,redis是系统必备的分布式缓存中间件,主要用来解决高并发下分担DB资源的负载,从而提升系统吞吐量。redis支持多种数据类型,String(字符串)、list(列表)...
  • redisStudy.zip

    2019-10-28 02:09:29
    缓存穿透指的是使用不存在的key进行大量的高并发查询,这导致缓存无法命中,每次请求都要穿透到后端数据库系统进行查询,数据库压力过大。 常用解决方案:将空值缓存起来。 其他解决方案:使用布隆过滤器(guava ...
  • NoSQL之_Redis介绍

    2021-05-30 19:45:18
    NoSQL之_Redis01.NoSQL简介:...高并发的读写 海量数据的读写操作 高可扩展性 速度快 不适用的场景 需要事务支持 基于sql的结构化查询存储吗, 处理复杂的关系, 需要即席查询(用户自定义查询条件的查询) 2).常见的NoSQ
  • 请教大家个问题,公司让做一个方案,要求是支持事物,最大并发支持2000/秒,平时也就是几百的并发。前面有nginx做负载,我只要负责mysql db, 后台我是这么设计的,读写分离用amoeba(阿里的变形虫)+主主相互复制,...
  • Bree是和JavaScript的最佳工作计划程序,它具有 , ,... 已经创建了但由于核心问题(并得到Redis支持),它并不是完成此工作的最佳工具。 如果解决了核心问题, 可能还可以,但是由于它使用因此不应将其用于作业队列。
  • 前言 一次偶然,从朋友那里...并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linu
  • Redis嘛,就是一种运行速度很快,并发很强的跑在内存上的NoSql数据库,支持键到五种数据类型的映射。 来来来,讲一讲为什么Redis这么快? 首先,采用了多路复用io阻塞机制 然后,数据结构简单,操作节省时间 ...
  • 如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么? Redis 主从架构是怎样的? Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层...
  • 『玩转Redis实战篇』高并发场景下,到底先更新缓存还是先更新数据库?:+1::+1: 『玩转Redis实战篇』经理让我复盘上次Redis缓存雪崩事故 『玩转Redis实战篇』还在用单机版?教你用Docker+Redis搭建主从复制多实例...
  • Redis如何助力高并发秒杀系统?看完这篇我彻底懂了!! 面试官:Java中提供了synchronized,为什么还要提供Lock呢? 一文搞懂PV、UV、VV、IP及其关系与计算 学好并发编程,需要掌握这些核心知识体系!! 高并发秒杀...
  • 其实在日常的工作中,redis的缓存作用真的解决了不少的麻烦,比如对于web来说,是用户量和访问量支持项目技术的更迭和前进。随着服务用户提升。可能会出现以下的一些状况: 页面并发量和访问量并不多,mysql足以...
  • 高并发】Java高并发解决方案 148 HTML静态化 149 图片服务器分离 149 数据库集群和库表散列 150 缓存 151 镜像 151 负载均衡 152 【网络】说说你对Http协议和Socket协议的理解 153 http协议 153 Tcp协议 154 ...
  • 3.2.3 如何设计一个高并发的系统? 3.2.4 两条相交的单向链表,如何求他们的第一个公共节点? 3.2.5 求单向局部循环链表的环入口? 3.2.6 IP地址如何在数据库中存储? 3.2.7 new/delete和malloc/free的底层实现? ...
  • Spring boot Redis 实体对象类型支持 springboot 多数据源配置 springboot定时任务 spring boot 异步调用 spring boot 整合 spring security 登录认证 Spring boot 集成rabitmq SpringBoot系列五 热部署 ...
  • advanced-java :互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 miaosha : 秒杀系统设计与实现.互联网工程师进阶与分析。 architect-awesome :后端架构师...
  • 并发编程 golang面试题:对已经关闭的的chan进行读写,会怎么样?为什么? golang面试题:对未初始化的的chan进行读写,会怎么样?为什么? sync.map 的优缺点和使用场景 sync.Map的优化点 高级特性 golang面试...
  • --服务端高并发分布式架构演进之路 Apache Flink 在快手的过去、现在和未来 分析 BAT 互联网巨头在大数据方向布局及大数据未来发展趋势 结合公司业务分析离线数仓建设 网易云音乐数仓维度建模实践-模型设计篇 Apache...
  • 高并发 消息队列 消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: 消息队列常见问题总结。 RabbitMQ : RabbitMQ 入门 RocketMQ : RocketMQ 入门、RocketMQ 的几个简单问题与答案 Kafka :Kafka 常见...
  • Java高并发秒杀系统【观后总结】 阅读SSM项目之scm 移动商城第一篇【搭建项目环境+数据模型】 移动商城第二篇(品牌管理模块)【文件上传、数据校验、CRUD】 移动商城第三篇(商品管理)【查询商品、添加商品】 移动...
  • 高并发 消息队列 消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: 消息队列常见问题总结。 RabbitMQ : RabbitMQ 入门 RocketMQ : RocketMQ 入门、RocketMQ 的几个简单问题与答案 Kafka :Kafka 常见...
  • 高并发 消息队列 消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: 消息队列常见问题总结。 RabbitMQ : RabbitMQ 入门 RocketMQ : RocketMQ 入门、RocketMQ 的几个简单问题与答案 Kafka :Kafka 常见...
  • FAQ(持续更新)

    2021-01-08 12:27:51
    帮助用户建立通讯与计算关系非常复杂的性能服务。但同时用户也可以只把它当成简易的异步网络引擎或并行计算框架来使用。 如何开始使用 以Linux系统为例: ~~~sh $ git clone ...
  • 什么是NoSQL数据库?

    2014-07-21 23:24:20
    NoSQL的出现是为了弥补SQL数据库因为事务等机制带来的对海量数据、高并发请求的处理的性能上的欠缺。 NoSQL不是为了替代SQL而出现的,它是一种替补方案,而不是解决方案的首选。 绝大多数的NoSQL产品都是基于大...
  • Node.js技术栈 本文档是作者从事Node.js ...专注于Node.js相关技术栈的研究分享,包括基础知识、Nodejs、Consul、Redis、微服务、消息中间件等,如果大家感兴趣可以给予关注支持! 未完待续,持续更新中。。。

空空如也

空空如也

1 2
收藏数 36
精华内容 14
关键字:

redis支持高并发吗

redis 订阅