一份热乎乎的字节面试真题(一)

tony_ybliu
发布于 2022-5-27 17:54
浏览
0收藏

 

前言 
大家好,我是捡田螺的小男孩,有位伙伴面试了字节(四年半工作经验),分享下面试真题,大家一起加油哈。

  1. 说说Redis为什么快
  2. Redis有几种数据结构,底层分别是怎么存储的
  3. Redis有几种持久化方式
  4. 多线程情况下,如何保证线程安全?
  5. 用过volatile吗?底层原理是?
  6. MySQL的索引结构,聚簇索引和非聚簇索引的区别
  7. MySQL有几种高可用方案,你们用的是哪一种
  8. 说说你做过最有挑战性的项目
  9. 秒杀采用什么方案
  10. 聊聊分库分表,需要停服嘛
  11. redis挂了怎么办?
  12. 你怎么防止优惠券有人重复刷?
  13. 抖音评论系统怎么设计
  14. 怎么设计一个短链地址
  15. 有一个数组,里面元素非重复,先升序再降序,找出里面最大的值

 

1.说说Redis为什么快 
 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
1.1 基于内存存储实现
内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,相对于数据存在磁盘的MySQL数据库,省去磁盘I/O的消耗。

1.2 高效的数据结构
Mysql索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。先看下Redis的数据结构&内部编码图:

 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
1.2.1 SDS简单动态字符串
 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区

  • 字符串长度处理:Redis获取字符串长度,时间复杂度为O(1),而C语言中,需要从头开始遍历,复杂度为O(n);
  • 空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS修改和空间扩充,会额外分配未使用的空间,减少性能损耗。
  • 惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,直接使用free中记录的空间,减少分配。
    1.2.2 字典
    Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。

1.2.3 跳跃表
 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区

  • 跳跃表是Redis特有的数据结构,就是在链表的基础上,增加多级索引提升查找效率。
  • 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
    1.3 合理的数据编码
    Redis 支持多种数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样编码,是redis设计者总结优化的结果。
  • String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
  • List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
  • Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
  • Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。
  • Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码
    1.4 合理的线程模型
    I/O 多路复用

 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且,Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

2. Redis有几种数据结构,底层分别是怎么存储的 
常用的,Redis有以下这五种基本类型:

  • String(字符串)
  • Hash(哈希)
  • List(列表)
  • Set(集合)
  • zset(有序集合) 它还有三种特殊的数据结构类型
  • Geospatial
  • Hyperloglog
  • Bitmap
    2.1 Redis 的五种基本数据类型
     一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
    String(字符串)
  • 简介:String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M
  • 简单使用举例: set key value、get key等
  • 应用场景:共享session、分布式锁,计数器、限流。
  • 内部编码有3种,int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)
    Hash(哈希)
  • 简介:在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
  • 简单使用举例:hset key field value 、hget key field
  • 内部编码:ziplist(压缩列表) 、hashtable(哈希表)
  • 应用场景:缓存用户信息等。
    List(列表)
  • 简介:列表(list)类型是用来存储多个有序的字符串,一个列表最多可以存储2^32-1个元素。
  • 简单实用举例:lpush key value [value ...] 、lrange key start end
  • 内部编码:ziplist(压缩列表)、linkedlist(链表)
  • 应用场景:消息队列,文章列表
    Set(集合)
  • 简介:集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素
  • 简单使用举例:sadd key element [element ...]、smembers key
  • 内部编码:intset(整数集合)、hashtable(哈希表)
  • 应用场景:用户标签,生成随机数抽奖、社交需求。
    有序集合(zset)
  • 简介:已排序的字符串集合,同时元素不能重复
  • 简单格式举例:zadd key score member [score member ...],zrank key member
  • 底层内部编码:ziplist(压缩列表)、skiplist(跳跃表)
  • 应用场景:排行榜,社交需求(如用户点赞)。
    2.2 Redis 的三种特殊数据类型
  • Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
  • HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
  • Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组
    3. Redis有几种持久化方式 
    Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。

Redis提供了RDB和AOF两种持久化机制,它持久化文件加载流程如下:

 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
3.1 RDB
RDB,就是把内存数据以快照的形式保存到磁盘上。

什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。
RDB持久化,是指在指定的时间间隔内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的持久化方式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来恢复数据。RDB触发机制主要有以下几种:

 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
RDB 的优点

适合大规模的数据恢复场景,如备份,全量复制等

RDB缺点

没办法做到实时持久化/秒级持久化。
新老版本存在RDB格式兼容问题
3.2 AOF
AOF(append only file) 持久化,采用日志的形式来记录每个写操作,追加到文件中,重启时再重新执行AOF文件中的命令来恢复数据。它主要解决数据持久化的实时性问题。默认是不开启的。

AOF的工作流程如下:

 一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
AOF的优点

数据的一致性和完整性更高

AOF的缺点

AOF记录的内容越多,文件越大,数据恢复变慢。

4. 多线程情况下,如何保证线程安全? 
加锁,比如悲观锁select for update,sychronized等,如,乐观锁,乐观锁如CAS等,还有redis分布式锁等等。

5. 用过volatile吗?它是如何保证可见性的,原理是什么 
volatile关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符, 用来修饰变量。它保证变量对所有线程可见性,禁止指令重排,但是不保证原子性。

我们先来看下java内存模型(jmm):

  • Java虚拟机规范试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台上都能达到一致的内存访问效果。
  • Java内存模型规定所有的变量都是存在主内存当中,每个线程都有自己的工作内存。这里的变量包括实例变量和静态变量,但是不包括局部变量,因为局部变量是线程私有的。
  • 线程的工作内存保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接操作操作主内存。并且每个线程不能访问其他线程的工作内存。 

一份热乎乎的字节面试真题(一)-鸿蒙开发者社区
volatile变量,保证新值能立即同步回主内存,以及每次使用前立即从主内存刷新,所以我们说volatile保证了多线程操作变量的可见性。

volatile保证可见性跟内存屏障有关。我们来看一段volatile使用的demo代码:

public class Singleton {  
    private volatile static Singleton instance;  
    private Singleton (){}  
    public static Singleton getInstance() {  
    if (instance == null) {  
        synchronized (Singleton.class) {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        }  
    }  
    return instance;  
    }  
}  

 

编译后,对比有volatile关键字和没有volatile关键字时所生成的汇编代码,发现有volatile关键字修饰时,会多出一个lock addl $0x0,(%esp),即多出一个lock前缀指令,lock指令相当于一个内存屏障

lock指令相当于一个内存屏障,它保证以下这几点:

  1. 重排序时不能把后面的指令重排序到内存屏障之前的位置
  2. 将本处理器的缓存写入内存
  3. 如果是写入动作,会导致其他处理器中对应的缓存无效。
    第2点和第3点就是保证volatile保证可见性的体现嘛

6. MySQL的索引结构,聚簇索引和非聚簇索引的区别 

  • 一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多个。
  • 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
  • 聚集索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序

 

文章转自公众号:小白debug

分类
标签
已于2022-5-27 17:54:16修改
收藏
回复
举报
回复
    相关推荐