C++ 后端
Redis 的五种数据结构 + 使用场景
String(字符串)
在 Redis 中,字符串可以被修改,称为简单动态字符串 (SDS) 或字符串,但它的结构更像数组列表,维护字节数组并在其中预分配空间。减少频繁的内存分配。
Redis 的内存分配机制如下:
- 当字符串长度小于 1MB 时,每次扩容会使现有空间翻倍。
- 如果字符串长度超过 1MB,则每次只扩展 1MB 空间。
- 这确保了足够的内存空间,并且不会浪费内存。字符串的最大长度是 512MB.
struct SDS{T capacity; //数组容量
T len; //实际长度
byte flages; //标志位,低三位表示类型
byte[] content; //数组内容
}
上面是字符串的基本结构,其中 content 保存字符串的内容,0x\0 作为结束字符不计算在 len中。
capacity 和 len 属性都是泛型的,为什么不直接使用 int 呢? Redis 中有很多优化方案。为了更合理地使用内存,不同长度的字符串用不同的数据类型表示。此外,len 在创建字符串时与capacity 一样大,因此不会产生多余的空间。所以 String 值可以是字符串、数字(整数、浮点数)或二进制。
应用场景:
存储 key-value 键值对
list (列表)
当数据量较小时,它的底层存储结构是一个连续的内存,称为 ziplist(压缩列表),它将所有的元素彼此相邻地存储,分配的是一个连续的内存;当数据量较大时,将其转换为快速列表结构。
然而,简单的链表也有缺陷。链表前后的指针 prev 和 next 将占用更多的内存,浪费更多的空间,并加剧内存碎片。redis 3.2 之后,采用了 ziplist + 链表 的混合结构,称为 quicklist (快速链表)。
应用场景:
-
由于 list 是按插入顺序排序的列表,因此有许多应用场景。例如:
-
消息队列: lpop 和 rpush (反之亦然,lpush 和 rpop) 可以实现队列的功能
-
像朋友圈的列表、评论列表和排行榜: lpush 命令和 lrange 命令可以实现最新列表的功能,每次通过 lpush 命令插入新的元素到列表中,再通过 lrange 命令读取最新的元素列表。
hash (字典)
数组 + 列表结构,当发生散列冲突时将元素添加到列表中。值得注意的是,Redis Hash 值只能是字符串。
Hash 和 String 都可以用来存储用户信息,但不同点在于 Hash 可以分别存储用户信息的每个字段。存储所有用户信息的序列化字符串。如果要修改用户字段,必须先查询所有用户信息字符串,解析为对应的用户信息对象,修改后再序列化为字符串。但是,哈希只可以修改某个字段,从而节省网络流量。但是,哈希的内存占用比 String 大,这是哈希的缺点。
应用场景:
- 购物车:hset [key] [field] [value] 命令可以实现用户 Id、商品 Id 为字段、商品数量为值,正好构成了购物车的三个要素。
- 散列类型 (键、字段、值) 的结构类似于对象 (对象id、属性、值) 的结构,也可以用来存储对象。
set (集合)
它内部的键值对是无序的、唯一 的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值 NULL。当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。
应用场景:
散列类型(键、字段、值)的结构类似于对象(对象id、属性、值)的结构,也可以用来存储对象。
- 收集朋友,追随者,粉丝和感兴趣的人:
1)使用 sinter 命令可以获取用户 A 和 B 的共同好友;
- sismember 命令可以判断 A 是否是 B 的朋友;
- scard 命令可以获取好友数量;
4)注意时,smove 命令可以将 B 从 A 的朋友集转移到 A 的朋友集
-
首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set 类型适合存放所有需要展示的内容,而 srandmember 命令则可以从中随机获取几个。
-
存储某活动中中奖的用户 ID ,因为有去重功能,可以保证同一个用户不会中奖两次。
zset (有序集合)
zset 也称为 SortedSet。一方面,它是一个集合,它保证了内部值的唯一性。另一方面,它可以为每个值分配一个分数,表示该值的排序权重。它的内部实现使用一种称为“跳转列表”的数据结构。
应用场景:
zset 可以用作排序,但与 list 不同的是,它可以实现动态排序。例如,zset 可以用来存储风扇列表。value 为球迷的用户ID, score 为接下来的时间。
zset 还可以用来存储学生的成绩,其中 value 是学生的ID, score 是他的考试成绩。我们可以按分数给他打分。
来源参考知乎《Redis 5种数据结构 及使用场景分析》
Redis 内存淘汰策略
定期删除
Redis会将每个设置了过期日期的键放入一个单独的字典中,并定期遍历字典以删除过期的键。
默认情况下,Redis每秒执行10次过期扫描(每100ms一次)。过期扫描不是遍历过期字典中的所有键,而是使用一个简单的贪婪策略。
-
从过期字典中随机抽取20个键;
-
从20个密钥中删除过期的密钥。
3.如果过期密钥的比例超过1/4,请重复执行步骤1。
redis 默认每 100ms 随机抽取一些设置了过期时间的密钥,检查密钥是否过期,如果过期则删除密钥。注意,这是一个随机选择。为什么随机?如果你认为redis节省了数十万个键,并每100ms迭代所有设置过期时间的键,这将给CPU带来很大的负载。惰性删除
当客户端访问密钥时,redis检查密钥的过期日期并删除它而不返回任何内容。
定期删除可能会导致许多过期的密钥在到期时无法删除。这就是惰性删除的用武之地。如果您有一个过期的密钥没有通过常规删除删除,并且仍然在内存中,它将不会通过redis删除,直到您的系统检查该密钥。这被称为延迟删除,这意味着当您主动检查过期的密钥时,如果您发现密钥已经过期,您将立即删除它而不返回任何内容。
总结:定期删除是集中处理,延迟删除是分散处理。
来源参考知乎《彻底弄懂Redis的内存淘汰策略》
MySQL 和 Redis 数据一致性问题
1.什么是数据的一致性
“数据一致”一般指的是:缓存中有数据,缓存的数据值 = 数据库中的值。
但根据缓存中是有数据为依据,则”一致“可以包含两种情况:
- 缓存中有数据,缓存的数据值 = 数据库中的值(需均为最新值,本文将“旧值的一致”归类为“不一致状态”)
- 缓存中本没有数据,数据库中的值 = 最新值(有请求查询数据库时,会将数据写入缓存,则变为上面的“一致”状态)
”数据不一致“:缓存的数据值 ≠ 数据库中的值;缓存或者数据库中存在旧值,导致其他线程读到旧数据
2.数据不一致情况及应对策略
根据是否接收写请求,可以把缓存分成读写缓存和只读缓存。
只读缓存:只在缓存进行数据查找,即使用 “更新数据库+删除缓存” 策略;
读写缓存:需要在缓存中对数据进行增删改查,即使用 “更新数据库+更新缓存”策略。
来源参考知乎《认识 MySQL 和 Redis 的数据一致性问题》
Mysql 里面为什么用 B+ 树?
哈希范围查询很慢。链表要遍历。剩下的就是树。广为人知的,二叉搜索树,AVL 树,红黑树,B 树等等。
- 二叉搜索树会退化为链表,层数可能也会很多
- AVL树层数依然过多
- 红黑树只是优化了插入、更新,弱化了平衡,在更新和搜索中取了折中。但层数过多的问题没有解决。
- B 树,层数变少了,但如果访问下一页需要回到父节点到兄弟节点
- B+ 树,将叶子节点用链表串联起来了, 还有就是子节点中包含了父节点的信息,解决了 B 树的问题
什么是事务
事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务。事务一般都是与数据库打交道的操作。
数据库的隔离级别,分别怎么解决可能出现的问题?
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
讲讲 MVCC
多版本并发控制 (MVCC) 通过保持某个时间点的数据快照来实现。也就是说,无论事务执行多长时间,事务中看到的数据都不会受到其他事务的影响。根据事务的开始时间,每个事务可能在同一时间看到同一表的不同数据。
简单来说,多版本并发控制的思想就是保存数据的历史版本,通过数据行的多版本控制来实现数据库的并发控制。通过这种方式,我们可以比较版本号,以确定是否显示数据,并在不锁定事务隔离效果的情况下读取数据。
多版本并发控制 (MVCC) 可以被视为行级锁定的变体,但它在许多情况下避免了锁定,因此成本更低。尽管实现机制各不相同,但大多数实现了非阻塞读,而且只锁定必要的行。
大多数 MySQL 的事务性存储引擎都不实现简单的行级锁。为了提高并发性能,它们通常同时实现多版本并发控制 (MVCC)。不仅 MySQL,其他数据库系统如 Oracle 和 PostgreSQL 也实现了 MVCC。然而,MVCC 的实现机制千差万别。MVCC 中有许多悲观悲观并发控制系统。
来源参考思否《MySQL的多版本并发控制(MVCC)是什么?》
既然用了 MVCC 版本查看,为什么还会出现幻读?
MVCC 利用版本链,undo log,Read View可以在快照读模式下解决幻读问题,在当前读的模式下,仅仅依靠 MVCC 不能解决幻读问题,因为当前读必须获取最新数据。
乐观锁和悲观锁
悲观锁:在所有操作前都上锁
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。
乐观锁和悲观锁的使用场景
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
进程和线程的区别
1.基本概念:
进程是运行时程序的封装,是系统资源调度和分配的基本单位,实现了操作系统的并发性;
线程是进程的子任务,是CPU调度的基本单位,用来保证程序的实时性,实现进程内部的并发性。线程是操作系统所识别的执行和调度的最小单位。每个线程占用它自己的虚拟处理器:它自己的寄存器库、指令计数器和处理器状态。每个线程执行不同的任务,但共享相同的地址空间(即相同的动态内存、映射文件、目标代码等)、打开的文件队列和其他内核资源。
2.区别:
- 一个线程只能属于一个进程,一个进程可以有多个线程,但至少有一个线程。线程的存在取决于进程。
- 进程在执行过程中有一个独立的内存单元,多个线程共享进程的内存。将资源分配给一个进程,同一进程的所有线程共享该进程的所有资源。同一个进程中的多个线程共享代码段 (代码和常量)、数据段 (全局变量和静态变量) 和扩展段 (堆存储)。但是每个线程都有自己的堆栈段,称为运行时,它包含所有的局部变量和临时变量。
- 进程是资源分配的最小单位,线程是CPU调度的最小单位;
- 开销:当一个进程被创建或撤销时,系统为它分配或回收资源,例如内存空间和 I/O 设备。因此,操作系统产生的开销比创建或撤消线程产生的开销大得多。类似地,进程切换包括保存当前进程的整个 CPU 环境,并设置新调度进程的 CPU 环境。然而,线程切换只保存和设置少量寄存器的内容,不涉及内存管理操作。如您所见,进程切换的开销也比线程切换的开销大得多。
- 通信:由于同一进程中的多个线程具有相同的地址空间,因此它们之间的同步和通信也更容易。进程间通信 (IPC),线程可以直接读写进程数据段 (如全局变量) 进行通信——需要进程同步和互斥来确保数据一致性。在某些系统中,线程可以在不需要操作系统内核干预的情况下进行切换、同步和通信
- 进程编程和调试简单可靠,但创建和销毁成本高;线程则相反,具有低开销、快速切换,但相对复杂的编程和调试。
- 进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉
- 进程适应于多核、多机分布;线程适用于多核
来源参考腾讯云《进程和线程的概念、区别及进程线程间通信》
进程线程都怎么通信
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
计算机内核态和用户态概念
用户态指非特权状态。在此状态下,执行的代码被硬件限定,不能进行某些操作,比如写入其他 进程 的存储空间,以防止给 操作系统 带来安全隐患。在 操作系统 的设计中,用户态也类似,指非特权的执行状态。 内核 禁止此状态下的代码进行潜在危险的操作,比如写入系统配置文件、杀掉其他用户的 进程 、重启系统等。
虚拟地址怎么映射到物理地址
CPU在获得虚拟地址之后,需要通过MMU将虚拟地址翻译为物理地址。MMU根据虚拟地址在页表中寻址
死锁的解决方法
死锁的产生是在这样一种环境中:比如我们有两个进程AB,他们都需要资源1和资源2,当进程A持有资源1,进线程B持有资源2的时候,他们都需要对方手上的进程,而一般操作系统又不允许抢占,这个时候就发生了死锁。
从这个例子中其实可以总结出死锁的几个必要条件:
- 一个资源只能被一个进程所占有,不能共享
- 一个线程请求资源失败时,它会等待而不是释放
- 一个线程在释放资源之前其他进程不能抢夺资源
- 循环等待
从死锁产生的原因未明可以设计一些方法去避免死锁的发生
- 静态分配资源,一开始就把一个进程所需的全部资源都分配给它,但这样会降低资源的使用效率
- 允许抢占,需要设置进程的不同优先级,高优先级的进程可以抢占低优先级的进程的资源
- 把资源进行编号,申请资源必须按照资源的编号顺序来申请
如果死锁已经发生了,就需要去解开死锁,其本质思想就是分配资源打破循环等待
- 可以运行抢占,从一个或多个进程中抢出资源来给其他进程
- 也可以终止一些进程,来达到释放资源的目的
简述进程切换的流程
如果想要从A进程切换到 B 进程,必定要先从用户态切换到内核态,因为这个切换的工作你不能让用户进程去实现,不然当 CPU 在用户进程手上的时候,他可以选择一直执行,不让出 CPU,这肯定是不允许的。所以操作系统需要先挂起正在占用 CPU 的 A 进程,才能切换到 B 进程。
由于从用户态切换到内核态的时候,CPU 是在用户进程手中,所以这个是通过硬中断来实现的。在从用户态切换到内核态之前需要保存用户进程的上下文,以便下一次执行时可以继续之前的工作。
这个上下文就是进程执行的环境,包括所有的寄存器变量,进程打开的文件、内存信息等。一个进程的上下文可以分为用户级上下文,寄存器上下文,系统级上下文。用户级上下文存储的是用户进程的内存数据以及堆栈数据等;寄存器上下文是一些通用寄存器;系统级上下文是内核栈、PCB (进程控制块)等。
简述 IO 多路复用
IO 多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出 cpu。IO 是指网络 IO,多路指多个 TCP 连接(即 socket 或者 channel),复用指复用一个或几个线程。意思说一个或一组线程处理多个 TCP 连接。最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。IO 多路复用的三种实现方式:select、poll、epoll。
计算机网络输入URL到看到网页
- URL 输入
- DNS 解析
- 建立 TCP 连接
- 发送 HTTP / HTTPS 请求(建立 TLS 连接)
- 服务器响应请求
- 浏览器解析渲染页面
- HTTP 请求结束,断开 TCP 连接
TCP 三次握手的目的是什么?为什么不用两次和四次?
TCP 三次握手的主要目的是防止失效的连接请求报文被服务端接受。
如果只有两次握手,假设当客户端发送第一次连接请求由于网络拥塞的原因,迟迟未到服务端,客户端没接收到确认报文,认为服务端没有收到,于是重新发送请求报文并与服务端建立连接,等这次连接断开了,之前滞留的那个请求报文又到达了服务端,就会让服务端与客户端再次连接成功,这时服务端就会一直等待客户端发送请求,造成了资源的浪费。
两次握手只能保证单向链路是可以通信的,理论上来说,要保证双向链路可以通信需要四次握手,但实际上服务端给客户端的 SYN 和 ACK 数据包可以合为一次握手,所以实际上只需要三次握手即可。
加问:那挥手为什么需要四次呢
答:挥手阶段中服务端的 ACK 和 FIN 数据包不能合为一次。因为挥手阶段的流程为客户端发送FIN数据包表示自己发完了,服务端立即回复 ACK 数据包表示自己知道了,此时客户端到服务端的连接已经释放了,客户端不会再发送数据了,但服务端还可以继续向客户端发送数据,等到服务端也完成了数据发送,才会发送 FIN,这时客户端回复 ACK,就可以结束通信了。
加问:TCP 在四次挥手的过程中为什么客户端最后还要等待 2MSL(Maximum Segme
答:因为客户端要保证他的 ACK 包顺利到达服务端,如果客户端的ACK数据包丢失,则服务端或重新发送 FIN 包到客户端,而这两个过程的最长时间为 1MSL,加起来为 2MSL,如果 2MSL 后客户端还没有收到服务端重发的 FIN 包,则说明 ACK 包顺利到达,可以关闭连接了。
TCP 长连接和短连接有什么区别?
TCP 短连接是指客户端与服务端连接后只进行一次读写就关闭连接,一般是客户端关闭。
而长连接则是指在进行完一次读写后不关闭连接,直到服务端压力过大则选择关闭一些长时间为进行读写的连接。
TCP 短连接的优点在于管理简单,而且不会对服务端造成太大的压力,而缺点是每次读写都需要连接耗时较长。
TCP 长连接的优点是可以迅速进行多次读写,缺点是对服务端压力大,且容易被恶意连接影响服务。
长短连接的区别就在于客户端和服务端选择的关闭策略不同,具体需要根据应用场景来选择合适的策略。
TCP 通过哪些方式来保证数据的可靠性?
TCP 保证数据可靠性的方式大致可以分为三类:
- 在数据包层面:校验和
- 在数据包传输层面:序列号、确认应答、超时重传
- 在流量控制层面:拥塞控制
- 校验和
计算方式:在数据传输的过程中,将发送的数据段都当做一个 16 位的整数。将这些整数加起来。并且加上进位,最后取反,得到校验和。
TCP 与 UDP 校验方式相同
序列号、确认应答、超时重传
在数据包传输的过程中,每个数据包都有一个序列号,当数据到达接收方时,接收方会发出一个确认应答,表示收到该数据包,并会说明下一次需要接收到的数据包序列号(32 位确认序列号)。如果发送端在一段时间内(2RTT 没有收到确认应答,则说明可能是发送的数据包丢失或者确认应答包丢失,此时发送端会进行数据包重传。
但发送端并不是一定要等到接收到上一个数据包的确认应答再发送下一个数据包,TCP 会利用窗口控制来提高传输速度,在一个发送窗口大小内,不用一定要等到应答才能发送下一段数据,发送窗口大小就是无需等待确认而可以继续发送数据的最大值。而发送窗口的大小是由接收端的接受窗口的剩余大小和拥塞窗口来决定的。(TCP 会话的双方都各自维护一个发送窗口和一个接收窗口)
拥塞控制
发送端维持一个叫做拥塞窗口 cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送端让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于拥塞窗口。
TCP 的拥塞控制主要是采用慢启动以及增性加,乘性减的机制,TCP一开始将拥塞窗口设置的很小,在逐渐经过一段时间的指数增长后超过门限,进入增性加阶段,此时窗口大小的增长是线性的,比之前的指数增长要慢很多,而当发生网络拥塞时,拥塞窗口大小直接减半(乘性减)。
Redis 怎么统计在线用户
- 统计在线人数的方法有很多,像redis中比较常见的,HyperLogLog基数统计,位图,集合,有序集合都可以实现统计在线人数的方法
- 使用集合或者有序集合都可以存储具体的在线用户名单,但是会消耗大量的内存。 而使用 HyperLogLog 虽然能够有效地减少统计在线用户所需的内存 ,但是它的准确性比较低,也就是说它没有办法准确的记录在线用户名单。
- Redis的位图就是一个由二进制位组成的数组, 通过将数组中的每个二进制位与用户 ID 进行一一对应, 我们可以使用位图去记录每个用户是否在线。
- 当一个用户上线时, 我们就使用 SETBIT 命令, 将这个用户对应的二进制位设置为 1
- 通过使用 GETBIT 命令去检查一个二进制位的值是否为 1 , 我们可以知道指定的用户是否在线
- 通过 BITCOUNT 命令, 我们可以统计出位图中有多少个二进制位被设置成了 1 , 也即是有多少个用户在线
- 用户也能够对多个位图进行聚合计算 —— 通过 BITOP 命令, 用户可以对一个或多个位图执行逻辑并、逻辑或、逻辑异或或者逻辑非操作
所有排序方法,分析复杂度
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
直接选择排序 | O(n²) | O(n²) | O(n) | O(1) | 不稳定 |
直接插入排序 | O(n²) | O(n²) | O(n) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n²) | O(nlogn) | O(nlogn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
希尔排序 | O(nlogn) | O(ns) | O(n) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(N*M) | O(N*M) | O(N*M) | O(M) | 稳定 |
请简单说明一下 DNS 负载均衡的优点
- DNS 负载均衡最大的优点就是配置简单。服务器集群的调度工作完全由 DNS 服务器承担,那么就可以把精力放在后端服务器上,保证他们的稳定性与吞吐量。而且完全不用担心DNS服务器的性能,即便是使用了轮询策略,它的吞吐率依然卓越。
- DNS 负载均衡具有较强了扩展性,完全可以为一个域名解析较多的 IP ,而且不用担心性能问题。
请写出进程间通信主要有哪些方法
进程之间的通信方式主要有六种,包括:
- 管道
- 信号量
- 消息队列
- 信号
- 共享内存
- 套接字
请简述 epoll 中 ET 和 LT 的区别
epoll 有 2 种工作方式: LT 和 ET 。
LT (level triggered) 是缺省的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的 select/poll 都是这种模型的代表。
ET (edge-triggered) 是高速工作方式,只支持 no-block socket 。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是如果一直不对这个 fd 作 IO 操作 (从而导致它再次变成未就绪) ,内核不会发送更多的通知 (only once) ,不过在 TCP 协议中, ET 模式的加速效用仍需要更多的 benchmark 确认。
请简述设计模式 6 大原则分别是?
- 单一职责原则
- 里氏替换原则
- 依赖倒置原则
- 接口依赖原则
- 开闭原则OCR
- 迪米特原则
请说下你对 MySQL 架构的了解?
MySQL的体系结构可以分为两层,MySQL Server层和存储引擎层。
在MySQL Server层中又包括连接层和SQL层。
应用程序通过接口( 如ODBC、JDBC)来连接MySQL。
最先连接处理的是连接层,连接层包括通信协议、线程处理、用户名密码认证三个部分。
通信协议负责检测客户端版本是否兼容MySQL服务端。
线程处理是指每一个连接请求都会分配一个对应的线程,
相当于一条SQL对应一个线程,一个线程对应一个逻辑 CPU,并会在多个逻辑CPU之间进行切换。
用户名密码认证验证创建的账号和密码,以及host主机授权是否可以连接到MySQL服务器。
SQL层包含权限判断、查询缓存、解析器、预处理、查询优化器、缓存和执行计划。
权限判断可以审核用户有没有访问某个库、某个表,或者表里某行的权限。
查询缓存通过 Query Cache 进行操作,如果数据在 Query Cache 中,则直接返回结果给客户端。
查询解析器针对 SQL 语句进行解析,判断语法是否正确。预处理器对解析器无法解析的语义进行处理。
优化器对 SQL 进行改写和相应的优化,并生成最优的执行计划,就可以调用程序的 API 接口,通过存储引擎层访问数据。
存储引擎层也是 MySQL 数据库区别于其他数据库最核心的一点。
来源参考博客园《MySQL体系结构与存储引擎 》
一条 SQL 语句在数据库框架中的执行流程?
- 如果是查询语句,首先查询缓存,查询直接命中返回,否则进入下一步 (MySQL得到查询请求,将首先查询缓存,看看该语句之前是否执行过。) 以前执行的语句及其结果作为键-值对直接缓存在内存中。Key为查询语句,value为查询结果。如果查询可以直接在缓存中找到键,则值将直接返回给客户端。
- 分析器对SQL 语句做解析。分析器先会做“词法分析”(比如"select"这个关键字识别出来)、“语法分析”(语法分析根据语法规则,判断输入的这个 SQL语句是否满足 MySQL 的语法规范)
- 优化器的处理。优化器是在表里有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。(逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案)
- 执行器阶段,开始执行语句。打开表,根据表的引擎定义,去使用这个引擎提供的接口,存取数据。
- 返回
参考知乎《一条 SQL 语句在数据库框架中的执行流程?》
数据库的三范式是什么?
第一范式:要求有主键,并且要求每一个字段原子性不可再分
第二范式:要求所有非主键字段完全依赖主键,不能产生部分依赖
第三范式:所有非主键字段和主键字段之间不能产生传递依赖
char 和 varchar 的区别?
- char的长度是不可变的,而varchar的长度是可变的。
- char因为其长度固定,方便程序的存储与查找。
- char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。varchar的存储方式是:对每个英文字符占用2个字节,汉字也占用2个字节。
- 两者的存储数据都非unicode的字符数据。
- nchar和nvarchar是存储的unicode字符串数据
varchar(10) 和 varchar(20) 的区别?
暂用磁盘空间一样,但涉及到文件排序或者基于磁盘的临时表时,更长的列会消耗更多的内存
索引的底层使用的是什么数据结构?
B+ 树,InnoDB注意到某些索引值被使用得非常频繁时,会在内存中基于B+树索引之上再创建一个哈希索引。
聚簇索引
聚簇索引:将数据存储与节点放到了一块,找到节点也就找到了数据
非聚簇索引:将数据存储与节点分开,索引结构的叶子节点指向了数据的对应行
谈谈你对哈希索引的理解?
hash索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。
- 哈希索引只保存哈希码和指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过访问内存中的行速度非常快(因为是MEMORY引擎),所以对性能影响并不大
- 哈希索引数据并不是按照索引值顺序存储的,所以无法用于排序
- 哈希索引不支持部分索引列查找,因为哈希索引始终是使用索引列的全部内容来计算哈希码。
如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该哈希索引 - 哈希索引只支持等值比较查询,包括=、IN()、<=>,不支持范围查询,如where price > 100
- 哈希冲突(不同索引列会用相同的哈希码)会影响查询速度,此时需遍历索引中的行指针,逐行进行比较。
参考腾讯云《「Mysql索引原理(三)」Mysql中的Hash索引原理》
谈谈你对覆盖索引的认识?
因为非主键索引通过索引找到数据的主键以后需要再前往主键索引根据主键查找对应数据。
而覆盖索引从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。
谈谈你对最左前缀原则的理解?
最左前缀原则:即当你创建了一个联合索引,该索引的任何最左前缀都可以用于查询。比如当你有一个联合索引 (col1, col2, col3),该索引的所有前缀为 (col1)、(col1, col2)、(col1, col2, col3),包含这些列的所有查询都会使用该索引进行查询。
怎么知道创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?
使用 Explain 命令来查看语句的执行计划
InnoDB 和 MyISAM 的比较?
- InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
- InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
- InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
- InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
- InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;
来源参考知乎《Mysql 中 MyISAM 和 InnoDB 的区别有哪些?》
谈谈你对水平切分和垂直切分的理解?
垂直拆分:原来一个表的信息,拆分到两个或者多个表中,通过主键来进行关联。(垂直拆分列,列数据拆分到不同表中)
垂直切分的优点
- 数据库的拆分简单明了,拆分规则明确;
- 应用程序模块清晰明确,整合容易;
- 数据维护方便易行,容易定位;
垂直切分的缺点
- 部分表关联无法在数据库级别完成,需要在程序中完成;
- 单表大数据量仍然存在性能瓶颈;
- 事务处理相对更为复杂;
- 切分达到一定程度之后,扩展性会遇到限制;
水平切分:把一个表的数据按照某种规则划分到不同表或数据库里。(水平拆分行,行数据拆分到不同表中)
水平切分的优点
- 解决单表大数据量性能遇到瓶颈的问题;
- 应用程序端整体架构改动相对较少;
- 事务处理相对简单;
- 只要切分规则能够定义好,基本上较难遇到扩展性限制;
水平切分的缺点
- 切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则;
- 后期数据的维护难度有所增加,人为手工定位数据更困难;
- 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
主从复制中涉及到哪三个线程?
- 主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的 binlog中;
- 从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;
- 从:sql执行线程——执行relay log中的语句;
主从同步的延迟原因及解决办法?
架构方面
1) 业务的持久层采用分库架构,mysql 服务能力水平扩展,分散压力
2) 单个库读写分离,一主多从,主写读从,分散压力。这样从库比主库压力高,保护主库
3) 服务在业务和DB之间加入 memcache 和 redis 的cache层,降低读的压力
4) 不同业务的 mysql 放在不同的物理机,降低压力
5) 使用比主库更好的硬件设备,Mqsql 压力小,延迟就减少了
硬件方面
1) 采用好服务器,比如 4u 比 2u 性能明显好,2u 比 1u 性能明显好。
2) 存储用 ssd 或者盘阵或者 san,提升随机写的性能。
3) 主从间保证处在同一个交换机下面,并且是万兆环境。
Mysql 主从同步加速
1) sync_binlog 在 Slave 端设置为 0。
2) log-slave-updates 从服务器从主服务器接受的更新日志不计入二进制日志
3) 直接禁用 Slave 的 binlog
4) Slave 端,如果存储引擎是 innodb,innodb_flush_log_at_trx_commit =2
5) 同步参数调整主库是写,对数据安全性较高,比如 sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置是需要的而 slave 则不需要这么高的数据安全,完全可以讲 sync_binlog 设置为 0 或者关闭 binlog,innodb_flushlog 也可以设置为 0 来提高 sql 的执行效率
来源参考简书:《MySQL 主从同步延迟的原因及解决办法》
MySQL 默认的隔离级别是什么?
RR
1.数据库默认隔离级别: mysql ---repeatable,oracle,sql server ---read commited
2.mysql binlog的格式三种:statement,row,mixed
3.为什么mysql用的是repeatable而不是read committed:在 5.0之前只有statement一种格式,而主从复制存在了大量的不一致,故选用repeatable
4.为什么默认的隔离级别都会选用read commited 原因有二:repeatable存在间隙锁会使死锁的概率增大,在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
2.在RC级用别下,主从复制用什么binlog格式:row格式,是基于行的复制!
InnoDB 存储引擎的锁的算法有哪些?
InnoDB 存储引擎有三种行锁的算法,其分别是:
- Record Lock:单个行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:Record Lock + Gap Lock,锁定一个范围,并且锁定记录本身
Redis 为什么这么快?
- 基于内存,非常快速。
- 数据结构简单,Redis中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路I/O复用模型,非阻塞IO;
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
什么是缓存雪崩?怎么解决?
当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。
解决方案:
- 在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。
如果真的发生了缓存雪崩,有没有什么兜底的措施? - 使用熔断机制。当流量到达一定的阈值时,就直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上。至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。
- 提高数据库的容灾能力,可以使用分库分表,读写分离的策略。
- 为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。
什么是缓存穿透?该如何解决?
我们使用Redis大部分情况都是通过Key查询对应的值,假如发送的请求传进来的key是不存在Redis中的,那么就查不到缓存,查不到缓存就会去数据库查询。假如有大量这样的请求,这些请求像“穿透”了缓存一样直接打在数据库上,这种现象就叫做缓存穿透。
解决方案:
- 把无效的Key存进Redis中。如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。这种处理方式肯定是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
- 使用布隆过滤器。布隆过滤器的作用是某个 key 不存在,那么就一定不存在,它说某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回。
请说明信号处理的方式有哪些?
- 执行默认动作
- 忽略
- 捕捉
来源:LB 《岗位面试模拟题集》
Redis 持久化有几种方式?
- RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
- AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
Redis 淘汰策略有哪些?
noeviction表示不淘汰数据,当缓存数据满了,有新的写请求进来,Redis不再提供服务,而是直接返回错误。
根据过期时间的淘汰策略
volatile-random、volatile-ttl、volatile-lru、volatile-lfu 四种策略是针对已经设置了过期时间的键值对。到键值对的到期时间到了或者Redis内存使用量达到了maxmemory阈值,Redis会根据这些策略对键值对进行淘汰;
- volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
- volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
- volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
- volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。
所有数据范围内的淘汰策略
allkeys-lru、allkeys-random、allkeys-lfu 这三种策略淘汰的数据范围扩大到所有的键值对,无论这些键值对是否设置了过期时间,筛选数据进行淘汰的规则是:
- allkeys-random 策略,从所有键值对中随机选择并删除数据;
- allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。
- allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。
来源参考掘金:《Redis的八种淘汰策略(五)》
Redis 常见性能问题和解决方案?
- Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
- Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。
- Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
- Redis主从复制的性能问题,第一次Slave向Master同步的实现是:Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发送给各个Slave。不管什么原因导致Slave和Master断开重连都会重复以上过程。Redis的主从复制是建立在内存快照的持久化基础上,只要有Slave就一定会有内存快照发生。虽然Redis宣称主从复制无阻塞,但由于Redis使用单线程服务,如果Master快照文件比较大,那么第一次全量传输会耗费比较长时间,且文件传输过程中Master可能无法提供服务,也就是说服务会中断,对于关键服务,这个后果也是很可怕的。
以上1.2.3.4根本问题的原因都离不开系统io瓶颈问题,也就是硬盘读写速度不够快,主进程 fsync()/write() 操作被阻塞。 - 单点故障问题,由于目前Redis的主从复制还不够成熟,所以存在明显的单点故障问题,这个目前只能自己做方案解决,如:主动复制,Proxy实现Slave对Master的替换等,这个也是Redis作者目前比较优先的任务之一
TCP 与 UDP 有哪些区别?各自应用场景?
-
TCP 是面向连接的协议,UDP 是无连接协议
TCP 发送数据前使用三次握手建立连接,UDP 发送数据前不需要建立连接。 -
TCP 可靠,UDP 不可靠
TCP 丢包会自动重传,UDP 不会(任何必需的可靠性必须由应用层来提供)。 TCP 可靠性由三个机制保证:1. 序号(TCP 报文的序号)2. 确认(ACK 机制)3. 重传(超时或者冗余的 ACK) -
TCP 有序,UDP 无序
消息在传输过程中可能会乱序,后发送的消息可能会先到达,TCP 会对其进行重新排序,UDP 不会。
4.TCP 无界,UDP 有界
TCP 通过字节流传输,UDP 中每一个包都是单独的。 -
TCP 有流量控制(拥塞控制),UDP 没有
TCP 协议的流量控制是基于滑窗协议实现的。 拥塞控制和流量控制不同,流量控制是点对点的通信量抑制,抑制发送端发送速率,使得接收端来得及接收。 -
TCP 传输慢,UDP 传输快;
因为 TCP 需要建立连接、保证可靠性和有序性,所以比较耗时。 这就是为什么视频流、广播电视、在线多媒体游戏等选择使用 UDP。 -
TCP 是重量级的,UDP 是轻量级的
TCP 要建立连接、保证可靠性和有序性,就会传输更多的信息,如 TCP 的包头比较大。 -
TCP 的 头部比 UDP 大
HTTP1.0,1.1,2.0 的版本区别
1、HTTP1.0与HTTP1.1主要区别
- 长连接
HTTP 1.0需要keep-alive参数来通知服务器要建立持久连接。HTTP1.1默认支持持久连接,这减少了创建连接的开销并提高了效率。
HTTP是基于TCP/IP协议的。创建一个TCP连接需要三次握手,有一定的开销。如果每次通信都需要重新建立连接,则会影响性能。因此,最好保持长连接,这样可以用来发送多个请求。
- 节约带宽
HTTP 1.1 支持只发送头部信息 (不发送任何主体信息),如果服务器认为客户端有请求服务器的权限,则返回 100,否则返回 401。如果客户端接收到 100,则请求正文将被发送到服务器。这样,当服务器返回 401时,客户端不需要发送请求体,节省了带宽。HTTP 还支持发送部分内容。通过这种方式,当客户端已经拥有一部分资源时,它只需要向服务器请求另一部分资源。这是支持可恢复文件的基础。 - HOST域
现在有了像tomat这样的网络服务器,建立虚拟站点就变得非常普遍了,也就是说,一个网络服务器上的多个虚拟站点可以共享相同的ip和端口。HTTP1.0没有host字段。HTTP1.1支持此参数。
2、HTTP1.1 HTTP 2.0主要区别 - 多路复用
HTTP2.0使用了多路复用技术,这样同一个连接就可以并发处理多个请求,并发请求的数量比HTTP1.1大了好几个数量级,当然HTTP1.1也可以建立多个TCP连接,以支持处理更多的并发请求,但是TCP连接的创建本身也很昂贵。TCP连接有一个预热和保护过程。首先检查数据传输是否成功,然后慢慢提高传输速度。因此,对于即时并发连接,服务器的响应将会变慢。最好使用能够支持瞬时并发请求的良好连接。 - 数据压缩
HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。 - 服务器推送
当我们从支持HTTP2.0的web服务器请求数据时,服务器会将客户端所需的一些资源推送给客户端,从而阻止客户端创建连接并向服务器发送请求。这是加载静态资源的好方法。这些由服务器推送的资源实际上存在于客户端的某个地方,客户端可以直接从本地加载这些资源,而不需要通过网络,速度自然要快得多。
POST 和 GET 有哪些区别?各自应用场景?
区别:
- GET请求的数据放在HTTP报头的URL之后。
Post将提交的数据放在HTTP主体中;GET的参数以查询字符串的形式出现在URL中,而POST的参数则存储在实体主体中。 - GET提交的数据少,最多可达1024B,而POST可以传输更多的数据;
- Post的安全性比Get高,因为使用Get时参数数据以明文形式传输,使用Get可能会导致跨站伪造请求攻击。POST数据可以加密,但GET可能更快;
- POST请求是不安全和幂等的,因为它们通过添加或提交数据来修改服务器上的资源。多个提交创建多个资源;GET请求是安全和幂等的
场景:
GET 用于获取资源,查询数据,而 POST 用于传输实体主体,修改数据。
HTTP 哪些常用的状态码及使用场景?
其中 HTTP 状态码则代表了当前请求的状态,常见的有以下这些:
- 1xx 消息
这类状态码,代表请求已被服务端接受,但服务端还要继续进行处理。 - 100 Continue
继续。
服务端收到请求,并表示可以继续。在客户端准备推送较大的数据时,可以用 100 表示允许。 - 101 Switching Protocols
切换协议。
比较常见的是响应 WebSockets 连接,浏览器会先发送 HTTP 请求,在请求头里带上希望升级为 WebSockets 协议的相关信息。
服务端收到后,如果支持 WebSockets,就会返回这个状态码 101,进行 HTTP 到 WebSockets 的协议切换。 - 2xx 成功
这类状态码代表服务已被正常处理。 - 200 OK
成功。
200 是用得最多的状态码,前端请求接口,数据正常返回,就会拿到这个状态码。这是大家最喜欢看到的状态码。 - 3xx 重定向
代表客户端需要进行进一步操作才能完成请求。 - 301 Moved Permanently
永久重定向。
请求的资源被永久移动到其他位置。 - 302 Found
临时重定向。
原始描述短语为 Moved Temporarily
适合一些临时的重定向操作,比如因为服务器崩溃导致一些页面无法提供正确的访问,在修复期间,则可以通过 302 先暂时地重定向到一个新的地址。修复完后再让用户继续访问原链接。 - 304 Not Modified
没有改变。
304 指的是资源相比上次没有发生变化,客户端可以继续使用之前缓存的资源,以减轻浏览器负担。
这里涉及到了 HTTP 缓存中的强缓存和协商缓存知识。协商缓存中会用到 304 状态码。 - 4xx 客户端错误
客户端的问题。 - 400 Bad Request
错误请求。
通用的客户端错误,表示问题是出在客户端身上的。比如传的参数不对。
在前端和后端对接接口时,比较常看到这个错误,因为容易写错参数名,或者后端新增或修改了参数。 - 404 Not Found
不存在。
代表 url 对应资源不存在。 - 405 Method Not Allowed
方法不被允许。 - 5xx服务器错误
服务端的问题,快去找后端,别老抓着前端一顿问。 - 500 Internal Server Error
内部服务错误。 - 502 Bad Gateway
网关错误。 - 504 Gateway Timout
网关超时。
HTTP 状态码 301 和 302 的区别,都有哪些用途?
301与302的区别:
- 301 重定向是永久的重定向,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。
- 302 重定向是临时的重定向,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回 302 代码,搜索引擎认为新的网址只是暂时的。
常见应用场景: - 场景一 :想换个域名,旧的域名不用啦,这样用户访问旧域名时用 301 就重定向到新的域名。其实也是告诉搜索引擎收录的域名需要对新的域名进行收录。
- 场景二 :登录后重定向到指定的页面,这种场景比较常见就是登录成功跳转到具体的系统页面。
- 场景三 :有时候需要自动刷新页面,比如5秒后回到订单详细页面之类。
- 场景四 :有时系统进行升级或者切换某些功能时,需要临时更换地址。
- 场景五 :像微博之类的使用短域名,用户浏览后需要重定向到真实的地址之类。
多态的实现原理
多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中。
- 在类中用 virtual 关键字声明的函数叫做虚函数;
- 存在虚函数的类都有一个虚函数表,当创建一个该类的对象时,该对象有一个指向虚函数表的虚表指针(虚函数表和类对应的,虚表指针是和对象对应);
- 当基类指针指向派生类对象,基类指针调用虚函数时,该基类指针指的虚表指针实际指向派生类虚函数表,通过遍历虚表,寻找相应的虚函数然后调用执行。
基类的虚函数表如下图所示:
派生类的对象虚函数表如下:
简单解释:当基类的指针指向派生类的对象时,通过派生类的对象的虚表指针找到虚函数表(派生类的对象虚函数表),进而找到相应的虚函数 Derive::f() 进行调用。
更多后端面试考点,可学习《C++ 面试突破》
虚函数的实现机制
实现机制:
虚函数通过虚函数表来实现。虚函数的地址保存在虚函数表中,在类的对象所在的内存空间中,保存了指向虚函数表的指针(称为“虚表指针”),通过虚表指针可以找到类对应的虚函数表。虚函数表解决了基类和派生类的继承问题和类中成员函数的覆盖问题,当用基类的指针来操作一个派生类的时候,这张虚函数表就指明了实际应该调用的函数。
- 每个使用虚函数的类(或者从使用虚函数的类派生)都有自己的虚函数表。该表是编译器在编译时设置的静态数组,一般我们称为 vtable。虚函数表包含可由该类调用的虚函数,此表中的每个条目是一个函数指针,指向该类可访问的虚函数。
- 每个对象在创建时,编译器会为对象生成一个指向该类的虚函数表的指针,我们称之为 vptr。vptr 在创建类实例时自动设置,以便指向该类的虚拟表。如果对象(或者父类)中含有虚函数,则编译器一定会为其分配一个 vptr;如果对象不包含(父类也不含有),此时编译器则不会为其分配 vptr。与 this 指针不同,this 指针实际上是编译器用来解析自引用的函数参数,vptr 是一个真正的指针。
更多后端面试考点,可学习《C++ 面试突破》
如何避免拷贝
最直观的想法是:将类的拷贝构造函数和赋值运算符重载声明为私有 private,但对于类的成员函数和友元函数依然可以调用,达不到完全禁止类的对象被拷贝的目的,而且程序会出现错误,因为未对函数进行定义。
1.声明一个基类,具体做法如下:
- 定义一个基类,将其中的拷贝构造函数和赋值运算符重载声明为私有 private。
- 派生类以私有 private 的方式继承基类。
class Uncopyable
{
public:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable &); // 拷贝构造函数
Uncopyable &operator=(const Uncopyable &); // 赋值运算符
};
class A : private Uncopyable // 注意继承方式
{
};
简单解释:
- 能够保证,在派生类 A 的成员函数和友元函数中无法进行拷贝操作,因为无法调用基类 Uncopyable 的拷贝构造函数或赋值运算符重载。同样,在类的外部也无法进行拷贝操作。
- 拷贝构造函数 =delete 修饰:
C++ 11 支持 delete 直接禁用类的成员函数调用。
class Uncopyable
{
public:
Uncopyable() {}
~Uncopyable() {}
Uncopyable(const Uncopyable &) = delete; // 禁用拷贝构造函数
Uncopyable &operator=(const Uncopyable &) = delete; // 禁用赋值运算符
};
更多后端面试考点,可学习《C++ 面试突破》
IP 地址有哪些分类?
IP 地址由四段组成,每个字段是一个字节,8位,最大值是255,
IP 地址由两部分组成,即网络地址和主机地址。网络地址表示其属于互联网的哪一个网络,主机地址表示其属于该网络中的哪一台主机。二者是主从关系。
IP 地址的四大类型标识的是网络中的某台主机。IPv4 的地址长度为32位,共4个字节,但实际中我们用点分十进制记法。
IP 地址根据网络号和主机号来分,分为A、B、C三类及特殊地址D、E。 全0和全1的都保留不用。
- A类:(1.0.0.0-126.0.0.0)(默认子网掩码:255.0.0.0或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类IP地址的最前面为“0”,所以地址的网络号取值于1~126之间。一般用于大型网络。
- B类:(128.0.0.0-191.255.0.0)(默认子网掩码:255.255.0.0或0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类IP地址的最前面为“10”,所以地址的网络号取值于128~191之间。一般用于中等规模网络。
- C类:(192.0.0.0-223.255.255.0)(子网掩码:255.255.255.0或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类IP地址的最前面为“110”,所以地址的网络号取值于192~223之间。一般用于小型网络。
- D类:是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户[1] 。
- E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的网络号取值于240~255之间。
请简述 Kafka 的 ack 的三种机制
request.required.acks有三个值0 1 -1(all)
- 0:生产者不会等待 broker 的 ack ,这个延迟最低但是存储的保证最弱当 server 挂掉的时候就会丢数据。
- 1:服务端会等待 ack 值,leader 副本确认接收到消息后发送 ack ,但是如果 leader 挂掉后,他不确保是否复制完成新 leader ,也会导致数据丢失。
- -1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的 ack ,这样数据不会丢失。
谈谈你对滑动窗口的了解?
TCP这个协议是网络中使用的比较广泛,他是一个面向连接的可靠的传输协议。既然是一个可靠的传输协议就需要对数据进行确认。TCP协议里窗口机制有2种:一种是固定的窗口大小;一种是滑动的窗口。这个窗口大小就是我们一次传输几个数据。对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。
TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。TCP使用肯定确认技术,其确认号指的是下一个所期待数据包的序列号。 假定发送方设备以每一次三个数据包的方式发送数据,也就是说,窗口大小为3。发送方发送序列号为1、2、3的三个数据包,接收方设备成功接收数据包,用序列号4确认。发送方设备收到确认,继续以窗口大小3发送数据。当接收方设备要求降低或者增大网络流量时,可以对窗口大小进行减小或者增加,本例降低窗口大小为2,每一次发送两个数据包。当接收方设备要求窗口大小为0,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。发送方接收到携带窗口号为0的确认,停止这一方向的数据传输。
谈一谈 XSS 攻击,举个例子?
XSS 是一种跨站点脚本攻击,缩写为 XSS 是为了避免与级联样式表 (CSS) 的缩写混淆。恶意攻击者在Web 页面中插入恶意 Script 代码。当用户浏览页面时,将执行嵌入在 Web 页面中的 Script 代码来攻击用户
一个例子是留言板。我们知道留言板的常见任务是显示用户留言的内容。通常情况下,用户评论使用正常的语言,留言板没有显示任何错误。但是,如果有人不遵守规则,请在消息中添加一行
<script>alert(“hey!you are attacked”)</script>
那么留言板界面的网页代码就会变成形如以下:
<html>
<head>
<title>留言板</title>
</head>
<body>
<div id=”board”
<script>alert(“hey!you are attacked”)</script>
</div>
</body>
</html>
来源参考知乎:《浅谈XSS攻击的那些事(附常用绕过姿势)》
讲一下网络五层模型,每一层的职责?
- 第五层——应用层(application layer)
应用层(application layer):是体系结构中的最高。直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。
在因特网中的应用层协议很多,如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议,DNS,POP3,SNMP,Telnet等等。 - 第四层——运输层(transport layer)
运输层(transport layer):负责向两个主机中进程之间的通信提供服务。由于一个主机可同时运行多个进程,因此运输层有复用和分用的功能
复用,就是多个应用层进程可同时使用下面运输层的服务。
分用,就是把收到的信息分别交付给上面应用层中相应的进程。
运输层主要使用以下两种协议:
(1) 传输控制协议TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。
(2) 用户数据包协议UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。 - 第三层——网络层(network layer)
网络层(network layer)主要包括以下两个任务:
(1) 负责为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。在TCP/IP体系中,由于网络层使用IP协议,因此分组也叫做IP数据报,或简称为数据报。
(2) 选中合适的路由,使源主机运输层所传下来的分组,能够通过网络中的路由器找到目的主机。
协议:IP,ICMP,IGMP,ARP,RARP - 第二层——数据链路层(data link layer)
数据链路层(data link layer):常简称为链路层,我们知道,两个主机之间的数据传输,总是在一段一段的链路上传送的,也就是说,在两个相邻结点之间传送数据是直接传送的(点对点),这时就需要使用专门的链路层的协议。
在两个相邻结点之间传送数据时,数据链路层将网络层交下来的IP数据报组装成帧(framing),在两个相邻结点之间的链路上“透明”地传送帧中的数据。
每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错控制等)。典型的帧长是几百字节到一千多字节。
注:”透明”是一个很重要的术语。它表示,某一个实际存在的事物看起来却好像不存在一样。”在数据链路层透明传送数据”表示无力什么样的比特组合的数据都能够通过这个数据链路层。因此,对所传送的数据来说,这些数据就“看不见”数据链路层。或者说,数据链路层对这些数据来说是透明的。
(1)在接收数据时,控制信息使接收端能知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提取出数据部分,上交给网络层。
(2)控制信息还使接收端能检测到所收到的帧中有无差错。如发现有差错,数据链路层就简单地丢弃这个出了差错的帧,以免继续传送下去白白浪费网络资源。如需改正错误,就由运输层的TCP协议来完成。 - 第一层——物理层(physical layer)
物理层(physical layer):在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。
来源参考cndn:《五层网络协议,各层功能,各层协议》
简单说下 HTTPS 和 HTTP 的区别
HTTP 属于超文本传输协议,用来在 Internet 上传送超文本,而 HTTPS 为安全超文本传输协议,在 HTTPS 基础上拥有更强的安全性,简单来说 HTTPS 是 HTTP 的安全版,是使用 TLS/SSL 加密的 HTTP 协议。
HTTP(Hypertext Transfer Protocol)超文本传输协议是用来在 Internet 上传送超文本的传送协议,它可以使浏览器更加高效,使网络传输减少。但 HTTP 协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险。
HTTPS(Secure Hypertext Transfer Protocol) 安全超文本传输协议是一个安全的通信通道,它基于 HTTP 开发,用于在客户计算机和服务器之间交换信息。HTTPS 使用安全套接字层(SSL)进行信息交换,简单来说 HTTPS 是 HTTP 的安全版,是使用 TLS/SSL 加密的 HTTP 协议。
超文本传输协议 HTTP 协议被用于在 Web 浏览器和网站服务器之间传递信息。HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了 Web 浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此 HTTP 协议不适合传输一些敏感信息,比如信用卡号、密码等。
为了解决 HTTP 协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议 HTTPS。为了数据传输的安全,HTTPS 在 HTTP 的基础上加入了 SSL 协议,SSL 依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
HTTPS 和 HTTP 的区别主要为以下四点:
- https 协议需要到 ca 申请证书,目前市面上的免费证书也不少,收费的也都比较贵。
- http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
来源参考泪雪网:《HTTP 和 HTTPS 有什么区别》
对称加密与非对称加密的区别
含义:
- 对称加密: 加密和解密的秘钥使用的是同一个。
- 非对称加密: 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。
区别:
1、加密和解密过程不同
对称加密的加密过程和解密过程使用的同一个密钥,加密过程相当于用原文+密钥可以传输出密文,同时解密过程用密文-密钥可以推导出原文。但非对称加密采用了两个密钥,一般使用公钥进行加密,使用私钥进行解密。
2、加密解密速度不同
对称加密解密的速度比较快,适合数据比较长时的使用。非对称加密和解密花费的时间长、速度相对较慢,只适合对少量数据的使用。
3、传输的安全性不同
对称加密的过程中无法确保密钥被安全传递,密文在传输过程中是可能被第三方截获的,如果密码本也被第三方截获,则传输的密码信息将被第三方破获,安全性相对较低。
非对称加密算法中私钥是基于不同的算法生成不同的随机数,私钥通过一定的加密算法推导出公钥,但私钥到公钥的推导过程是单向的,也就是说公钥无法反推导出私钥。所以安全性较高。
来源参考html中文网:《对称加密与非对称加密的区别是什么?》
请简述同步回调和异步回调的不同有哪些?
- 同步回调:
在原始线程中执行,所以自身不需要关心线程安全。
在类 C/C++ 语言中,可以读取存储在栈上的数据,比如说本地变量。
在任一语言中,他们可以访问绑定到当前线程上的数据。
可以假定某些应用程序的状态不变,比如说对象存在、定时器没有被触发、 IO 访问没有发生,或者任一一个程序关联的结构的状态。 - 异步回调:
可能会在另一个线程(在基于线程的异步架构中)中被调用,所以应用需要使回调同步访问的任意一个资源。
不能访问任意一个原始堆栈或者线程,比如说局部变量或者 thread-local 类型数据。
如果原始线程中持有锁,那么回调应在锁的外面被调用。
需要假定其他的线程或者事件可能已经修改了应用程序的状态。
ARP 协议的工作原理?
ARP 攻击就是通过伪造 IP 地址和 MAC 地址实现 ARP 欺骗,能够在网络中产生大量的 ARP 通信量使网络阻塞,攻击者只要持续不断的发出伪造的 ARP 响应包就能更改目标主机 ARP 缓存中的 IP-MAC 条目,造成网络中断或中间人攻击。
ARP 攻击主要是存在于局域网网络中,局域网中若有一个人感染 ARP 木马,则感染该 ARP 木马的系统将会试图通过“ ARP 欺骗”手段截获所在网络内其它计算机的通信信息,并因此造成网内其它计算机的通信故障。
来源参考cnblog: 《简述arp协议的工作原理》
Java后端
接口和抽象类的区别
接口 | 抽象类 | |
---|---|---|
方法 | 抽象方法 | 既可以有抽象方法,也可以有普通方法 |
关键字修饰 | interface | abstract |
定义常量变量 | 只能定义静态常量 | 成员变量 |
子类方法 | 所有方法必须实现 | 实现所有的抽象方法 |
子类继承 | 多继承 | 单继承 |
构造方法 | 不能有构造方法 | 可以有构造方法 |
接口实现 | 只能继承接口,不能实现接口 | 可以实现接口,并且不实现接口中的方法 |
Java 中的继承和 C++ 有什么不同
C++ 中可以实现多继承,而 Java 只能实现单继承。
Java 中有哪些数据结构?用过 HashMap 吗,说一下 HashMap 底层实现。
数据结构有数组、链表、栈、队列、堆、树、Map 等。
HashMap 在 JDK1.7 之前采用数组加链表的形式实现,在 1.8 之后使用了红黑树:通过底层数组存储对象节点,采用链地址法,把新增对象节点连接在当前地址的节点下面,且规定当链表长度大于 8 时,将链表转换成红黑树结构。
来源参考 CSDN《2022届校招Java面试题汇总(含题解)》
Java 中用的是值传递还是引用传递?
值传递。
面向过程和面向对象有什么区别?
面向过程是一种以过程为中心的编程思想,在处理某件事的时候,以正在进行什么为主要目标,分步骤完成目标。而面向对象的思想是将事物抽象为对象,赋予其属性和方法,通过每个对象执行自己的方法来完成目标。面向过程效率更高,而面向对象耦合低(易复用),扩展性强,易维护。
final、finally、finalize 的区别?
- final 用于修饰属性、类、方法,修饰的变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
- finally 用于异常处理,只能⽤在 try/catch 中,finally 后的代码总会被执行。
- finalize 是 java.lang.Object 类中的⽅法,每⼀个对象都会继承这个方法,再垃圾回收机制执行的时候会被调用,允许回收未被使用的内存垃圾。
什么是序列化?什么是反序列化?
序列化其实就是将对象转化成字节序列格式,使其可储存可传输。
反序列化就是将字节序列格式转换成对象,是序列化的补集。
什么是不可变类
不可变类是指实例的属性不能被修改的类。一个不可变类的实例对象从被创建出来,它的成员变量就不能被修改。Java 平台的类库中包含许多的不可变类,比如 String、基本类型的包装类等。不可变类比一般的更加安全。
为什么 Java 中 String 是不可变类?
- String 类中包含 char 数组 value、整形的 offset 和 count 三个属性,这三个属性都是 private 的,且没有提供方法修改数值,因此在初始化后无法从外部改变。
- String 类中的这三个属性都是被 final 修饰的,无法从内部进行改变;
- 方法区有一块特殊存储区域 String Pool,当创建 String 时,如果在 String Pool 中找到相同的字符串值,则会返回一个已存在 String 的引用而不会新建一个对象。假设 String 是可变的,则会导致其他引用这个字符串值的 String 的值发生变化。
参考来源 programcreek《Why String is immutable in Java?》
API 和 SPI 的区别
API 和 SPI 都是制定接口传输数据。API 是由实现方负责定义和实现,调用方只负责调用的 API。而 SPI 是指由调用方制定的接口,这个接口由实现方针对接口进行不同的实现,再由调用方选择实现方。例如在 JDBC 连接数据库时,针对不同的数据库需要不同的驱动实现,JDBC 提供了驱动接口,由不同的实现方进行实现了这些不同的驱动,然后我们就可以在 JDK 中引入这些实现了的驱动包进行使用。
参考来源 CSDN 《API 和 SPI》
线程和进程的区别
进程 | 线程 | |
---|---|---|
定义 | 操作系统进行资源分配和调度的基本单位 | 操作系统能够进行运算调度的最小单位 |
从属关系 | 运行程序的实例 | 进程的一个执行流 |
资源共享 | 进程间不能共享资源 | 线程间可以共享资源 |
上下文切换 | 切换速度慢 | 切换速度快 |
操纵方 | 操作系统 | 编程人员 |
线程和协程的区别?什么场景下用到协程?
- 线程与协程的区别
线程 | 协程 | |
---|---|---|
定义 | 操作系统最小的执行单元 | 操作系统最小的资源管理单元 |
从属关系 | 一个进程可以有多个线程 | 一个线程可以有多个协程 |
同步异步 | 同步机制 | 异步机制 |
资源消耗 | MB 级,更大 | KB 级,更小 |
-
什么场景下用到协程
- 高并发服务,如秒杀系统、RPC 服务器等。
- 爬虫开发。
- 即时通信服务,如聊天室、游戏服务器等等。
怎么理解容器的线程安全与线程不安全?里面具体做了什么样的实现?
-
线程安全
在拥有共享数据的多条线程并行执行的程序中,代码可以通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据错误等意外情况,称为线程安全。 -
线程不安全
不提供机制保护,出现多个线程先后更改数据造成得到脏数据的可能。 -
线程安全里面具体做了什么样的实现
-
互斥同步
通过 synchronized 关键字编译后,在同步块的前后生产 monitorenter 和 monitorexit 两个字节码指令,需要明确的 reference 对象进行解锁。获取 reference 对象后当前线程可以操作代码块,当锁的持有数归 0 锁释放后,可以在被其他线程获取。 -
非阻塞同步
对互斥同步的优化,对于获取锁失败的线程,将不再让其挂起,而是自旋等待一段时间,若还剩无法获取锁才将其挂起。
- 无同步方案
- 可重入代码
不允许任何进程进行修改,在运行的任何时刻中断去执行其他代码,在合理范围内(多次重入等)继续执行。 - 线程本地储存
每个线程都有一个副本,对线程所做的修改都是对副本修改,不会对其他副本造成影响。
-
参考资料来源 CSDN 《线程安全的实现方法》
Java 如何实现线程安全
- 使用 Atomic 类,通过 CAS 保证操作不会在执行过程中被中断
- 使用 synchronized、volatile 进行加锁
需要对集合容器内部的方法进行加锁,以 map 为例,由于 hashMap 内部没有锁,所以它会导致线程不安全,而 而如果我们使用 hashTable,由于其中使用 synchronized 关键字进行了加锁,就可以保证线程安全。 - 使用 TLS 避免资源竞争,提供线程的副本进行同时访问和维护。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
并发类库提供的线程池实现有哪些?
- newCachedThreadPool()
- newFixedThreadPool(int nThreads)
- newSingleThreadExecutor()
- newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize)
- newWorkStealingPool(int parallelism)
参考来源 cnblog 《Java并发类库提供的线程池有哪几种?分别有什么特点?》
线程池中的几个参数,比如核心线程数、最大线程数,如果让你定,你会怎么样去设定这些值?基于什么样
一般多线程执行的任务类型可以分为 CPU 密集型和 I/O 密集型,根据不同的任务类型,我们计算线程数的方法也不一样。
假设 N 为 CPU 核心数,WT 为线程等待时间,ST 为线程时间运行时间
- CPU 密集型任务:主要消耗 CPU 资源,可以额外设置一个线程,一旦任务暂停,CPU 就会处于空闲状态,额外的线程可以充分利用 CPU 的空闲时间,线程数为 N+1
- I/O 密集型任务: I/O 交互的处理消耗较大,而线程在处理 I/O 的适合不会占用 CPU 来处理,因此在可以多配置一些线程,线程数 2N。
- 在一般的业务应用场景中,我们可以用下列公式计算线程数:
线程数 = N * (1 + WT / ST)
我们可以根据实际业务场景,从 “N+1” 和 “2N” 中选出一个适合的,计算出一个大概的线程数,之后通过实际压测进行增大或减小线程数调整,然后观察具体的运行时间变化,最终确定一个具体的线程数。
参考来源 CSDN《线程池核心数与最大线程数设置》
Java 中有哪些锁
怎么理解乐观锁和悲观锁?
乐观锁:在操作数据时非常的乐观,认为别人不会修改数据,因此不加锁,在执行数据更新时采用比较判断的方式进行操作,如果当前数据被修改过,则放弃操作,否则就执行操作。乐观锁默认不会上锁。
典型的乐观锁包括 CAS 机制和版本号机制。
-
CAS 机制操作过程包括 compare 和 set,通过内存位置、预期值和拟写入的新值三个数据,先去比较待修改对象是否为它自身所持有的对象,然后比较该对象的数据是否等于期望数据,如果都为是,那就将该对象数据修改为新的数据。
-
版本号机制: 在表中增加一个 version 字段,每更新一次数据时将此值加一。当读取数据时,连同 version 字段一起读取。当提交更新时,会将数据库表中对应记录的当前版本号与之前取出来的版本号比较,如果一致则执行更新,如果不一致则表示是过期数据放弃操作。
悲观锁: 在操作数据时非常悲观,总是认为别人会修改数据,因此需要加锁,在更新数据时直到操作执行完毕才释放资源。悲观锁默认直接上锁。
典型的悲观锁实现方式为 synchronized 关键字、ReentrantLock 独占锁和 MySQL 中的排他锁。
- synchronized 关键字在多线程访问共享数据时同一时刻只能由单个线程抢到资源去执行。且在线程切换时,涉及到操作系统内核态与用户态的切换,这些操作会消耗额外的资源,因此效率较低。
参考来源 CSDN《乐观锁与悲观锁的区别》
怎么理解自旋锁?为什么还会有自旋锁?
-
自旋锁
线程的阻塞和唤醒需要 CPU 从用户态转为核心态,频繁的阻塞和唤醒对 CPU 来说负担较大,会给系统的并发性能带来很大的压力。 且对象锁的锁状态的持续时间很短,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的,因此引入了自旋锁。自旋锁让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。
为了达成这个等待,自旋锁会执行一段无意义的循环,这个过程称为自旋。
自旋等待不能替代阻塞,虽然自旋可以避免线程切换带来的开销,但是却占用了处理器的时间。 如果持有锁的线程很快就释放了锁,那么自旋的价值就非常高。否则,自旋的线程就会白白占用处理的资源,不会做任何有意义的工作,反而浪费了性能。 因此,自旋必须要有一个限度,如果自旋超过了设定的时长仍然没有获取到锁,则需要被挂起。自旋锁在 JDK 1.4.2 中引入,默认关闭,可以使用 -XX:+UseSpinning 开启,在 JDK1.6 中改为默认开启。同时自旋的默认次数为 10 次,可以通过修改参数 -XX:PreBlockSpin 来调整。
但是通过调整 -XX:preBlockSpin 来调整自旋次数,会导致很多意外情况。比如刚退出线程锁就被释放。因此 JDK1.6 引入了自适应的自旋锁。 -
适应自旋锁:
自适应自旋锁的自旋的次数不再是固定的,它由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。线程如果自旋成功了,那么下次自旋的次数会更多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。
参考来源 CSDN《什么是自旋锁》
间隙锁是什么
间隙是对于键值在条件范围内但并不存在的记录。
间隙锁是指在索引数据时,使用的是范围条件而不是相等条件,并请求共享或排他锁时, InnoDB 会给符合条件的已有数据记录的索引项进行加锁。
间隙锁是开区间,间隙锁 + 行锁合称 next-key lock,每个 next-key lock 是左开右闭区间。间隙锁和 next-key lock 的引入可以解决幻读问题。
讲一讲你对 TCP/IP 模型的认识
TCP/IP 分层模型被称作因特网分层模型、因特网参考模型。
TCP/IP 协议被组织成四个概念层,其中有三层对应于 ISO 参考模型中的相应层。ICP/IP 协议族并不包含物理层和数据链路层,因此它不能独立完成整个计算机网络系统的功能,必须与许多其他的协议协同工作。
TCP/IP 分层模型的四个协议层分别完成以下的功能:
- 第一层 网络接口层
网络接口层包括用于协作IP数据在已有网络介质上传输的协议。
协议:ARP,RARP - 第二层 网间层
网间层对应于 OSI 七层参考模型的网络层。负责数据的包装、寻址和路由。同时还包含网间控制报文协议 (Internet Control Message Protocol,ICMP)用来提供网络诊断信息。
协议:本层包含 IP 协议、RIP 协议,ICMP 协议。 - 第三层 传输层
传输层对应于 OSI 七层参考模型的传输层,它提供两种端到端的通信服务。
其中 TCP 协议提供可靠的数据流运输服务,UDP 协议提供不可靠的用户数据报服务。 - 第四层 应用层
应用层对应于 OSI 七层参考模型的应用层和表达层。
因特网的应用层协议包括Finger、Whois、FTP、Gopher、HTTP、Telent、SMTP、IRC、NNTP等。
参考来源 CSDN《OSI七层模型详解》
TCP 和 UDP 有什么区别?
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
是否有序 | 无序 有序,消息在传输过程中可能会乱序,TCP | 会重新排序 |
传输速度 | 快 | 慢 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
TCP 保证可靠性的方式
- 检验和
- 序列号
- 确认应答机制
- 超时重传机制
- 连接管理机制
- 流量控制
- 拥塞控制
HTTP 常用的请求方式?
方法 | 作用 |
---|---|
GET | 获取资源 |
POST | 传输实体主体 |
PUT | 上传文件 |
DELETE | 删除文件 |
HEAD | 和GET方法类似,但只返回报文首部,不返回报文实体主体部分 |
PATCH | 对资源进行部分修改 |
OPTIONS | 查询指定的URL支持的方法 |
CONNECT | 要求用隧道协议连接代理 |
TRACE | 服务器会将通信路径返回给客户端 |
HTTP 和 HTTPS 有什么区别?HTTPS 是如何保证传输安全的?
HTTP和HTTPS的区别:
- HTTPS 协议需要到 CA 申请证书,一般免费证书较少,因而需要一定费用;
- HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
HTTPS 的安全性保证:
HTTPS 是在HTTP的基础上增加了SSL的加密算法。
在传输数据时,首先服务端会先将自己的公钥拿给第三方的 CA 机构,CA 会用自己的私钥对服务端的公钥签名,生成证书。服务端将 CA 签名的证书发送给浏览器,浏览器通过内嵌的 CA 机构的公钥对证书解密,得到服务端的公钥,这一过程采用非对称加密的方式。
然后浏览器会随机生成一个密码信息,通过解密得到的服务端公钥对这个随机密钥进行加密,然后发送给服务端,服务端通过自己的私钥可以解密这个密文,从而拿到浏览器随机生成的密钥。
最后浏览器个服务器双方通过这个密钥,对需要发送的数据进行对称加密。由此便保证了数据传输的安全性。
由于非对称加密的安全性更高,所以采用非对称加密的方式对密钥进行加密;但是在传输数据时,由于非对称加密的效率较低,所以采用对称加密的方式对数据进行加密。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
TCP 中有一个 TIME_WAIT 状态有了解吗?
TIME_WAIT 是在 TCP 中,主动调用 close() 发起断开连接请求的一方,在发送了最后一个 ACK 包之后所进入的状态,在这个状态下,主动方会保持一个 2MSL 的等待时间后才断开连接回到起始 CLOSED 状态。其中的 MSL 是 Maximum Segment Lifetime,最大报文生存寿命,指一个数据包在网络中的最大生存时间。之前要保持 2MSL 的等待时间,有以下两方面原因:
- 实现 TCP 全双工连接的可靠释放
假如主动发起关闭的一方(通常来说是客户端)在最后一次挥手过程中发送的 ACK 包丢失了,那么由于 TCP 的重传机制,被动关闭一方(通常是服务端)会再次发送 FIN 包,以提醒客户端。如果没有这个 2MSL 时间,那么就算客户端最后的 ACK 包丢失,它也无法收到服务端的重传消息,也就不能实现 TCP 的可靠性传输了,同时,由于客户端在发完 ACK 之后就早早地断开了,这时服务端重发 FIN 包之后,会收到客户端传来的 RST 包响应,服务端会认为出现了异常。因此,客户端在发送完最后一个 ACK 包后,要有一个 2MSL 的等待时间,当发生丢包后,能够在这个时间段内重新收到服务端的 FIC 包,然后再给服务端重发一次 ACK 包,再次经过 2MSL 时间,如果没有收到服务端的 FIN,就认为此次断开连接成功,回到 CLOSED 状态。 - 使旧数据包在网络中因过期而消失
我们知道,TCP 连接由一个四元组(local_ip, local_port, remote_ip, remote_port)唯一标识,假如此时因为某些原因,TCP 连接断开了,接着又很快以相同的四元组建立了连接,这时因为出现过终端,两次的四元组实际上已经属于两个不同的连接了,但是对于 TCP 协议栈来说,它无法感知这样的中断,就会认为第二次的四元组依然是上一次的连接,倘或此时上一次连接中的旧数据包没有在网络中消失,而由客户端再次发送给了服务端,这时服务端的 TCP 传输层会把它当成是正常的数据来接收并向上传送到应用层,从而引起数据混乱。因此为了避免这种情况的发生,需要在一次连接结束时设置这样一个大于 MSL 的时间,来让本次连接过程中的所有数据包失效。
但是,在高并发短连接的 TCP 服务器上,当服务器处理完请求后立刻主动正常关闭连接,这种情况下会出现大量 socket 处于 TIME_WAIT 状态,如果客户端的并发量持续很高,此时又没有空闲的端口可供 TCP 连接,部分客户端就会显示连接不上,从而影响服务器的响应请求。因此,如果我们想避免 TIME_WAIT 状态,可以设置 SO_REUSERADDR 套接字选项来通知内核,告知它,如果端口忙但 TCP 连接又位于 TIME_WAIT 状态的话,可以重用端口。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
TCP 协议的三次握手和四次挥手
- 三次握手
刚开始客户端处于 Closed 的状态,服务器处于 Listen 状态。
客户端发送到服务器。客户端发送 SYN 报文给服务器,并且指明客户端初始化序列号为 ISN(c),即以 SYN=1, seq=x 的形式发送过去。此时客户端处于 SYN_SEND 状态。
服务器发送给客户端。服务器收到客户端的 SYN 和 ISN(c),也发送一个 SYN 回去,同时设置 ACK = ISN(c) + 1 以及指明服务器初始化序列号为 ISN(s),即以 SYN=1, ACK=x+1, seq=y 的形式发送给客户端。
客户端发送到服务器。客户端收到服务器发送的消息后,设置 ACK = ISN(s) + 1,将自身的 ISN(c) + 1,即以 ACK=y+1, seq=x+1 的形式发送给服务器。此时客户端处于 ESTABLISHED 阶段,服务器收到报文,也处于 ESTABLISHED 阶段,双方建立了连接。 - 四次挥手
客户端发送给服务器。客户端以 FIN=1, seq=u 的形式发送给服务器,表示需要关闭客户端和服务器的数据传输。此时客户端处于 FIN_WAIT 状态。
服务器发送给客户端。服务器收到信息,先返回 ACK 给客户端,即以 ACK=1, seq=v, ack=u+1 的形式返回给客户端,表示收到客户端报文了。此时服务器处于 CLOST_WAIT 状态。
服务器发送给客户端。服务器等待一会,看客户端还有没有数据没发过来,等处理完这些数据之后,也想断开连接了,于是发送 FIN 给客户端,即以 FIN=1, ACK=1, seq=w, ack=u+1 的形式发送给客户端。此时服务器处于 LAST_ACK 状态。
客户端发送给服务器。客户端收到 FIN 之后,返回 ACK 报文作为应答,即以 ACK=1, seq=w+1 的形式发送给服务器。此时客户端处于 TIME_WAIT 状态。
过一阵子之后,客户端确保服务器收到自己的 ACK 报文了,则变成 CLOSED 状态。服务器接受到 ACK 报文之后,就也处于 CLOSED 状态了。
TCP 的滑动窗口机制有了解过吗?
流量控制就是控制发送方的发送速率,以保证接收方有时间接收。
TCP 流量控制主要是通过滑动窗口协议实现的。
站在发送方的角度,滑动窗口可以分为四个部分:
-
发送并确认,此部分已经发送,可以忽略;
-
已发送但未确认,这部分可能在网络中丢失,数据必须保留以便必要时重传;
-
没有发送但可以发送,这部分接收缓冲区有空间可以保存,可以发送;
-
未发送且暂不可发送,此部分已超过接收端缓冲区存储空间,即使发送也无意义;
第2部分和第3部分加起来正好是接收方窗口的大小,它指定当前发送方可以发送的最大数据量。在收到确认回复消息之前,发送方必须将发送的报文段保存在窗口中(因为报文段可能在网络中丢失,所以必须将未确认的报文段保存在窗口中,以便在必要时重传)。如果在指定的时间间隔内收到接收方的确认应答报文,这些段将从窗口中清除。
当发送方收到接收方的确认回复后,清空窗口中的确认消息,窗口向右移动,如下图所示:
随着双方通信的进行,窗口将不断向右移动,因此被形象地称为滑动窗口(Sliding Window)
对于 TCP 的接收方,窗口稍微简单点,分为三个部分:
- 已接收
- 未接收准备接收 (也即接收窗口,再强调一遍,接收窗口的大小决定发送窗口的大小)
- 未接收并未准备接收
由于 ACK 直接由 TCP 协议栈回复,默认无应用延迟,不存在 “已接收未回复 ACK”
综上,举个例子,假设发送方需要发送的数据总长度为 400 字节,分成 4 个报文段,每个报文段长度是 100 字节:
1)三次握手连接建立时接收方告诉发送方,我的接收窗口大小(rwnd) 是 300 字节
此时的接收方滑动窗口如下:
此时的发送方滑动窗口如下:
2)发送方发送第一个报文段(序号 1 - 100),还能再发送 200 个字节
3)发送方发送第二个报文段(序号 101 - 200),还能再发送 100 个字节
4)发送方发送第三个报文段(序号 201 - 300),还能再发送 0 个字节
此时,发送方的窗口中存了三个报文段了
此时的发送方滑动窗口如下:
5)接收方接收到了第一个报文段和第三个报文段,中间第二个报文段丢失。此时接收方返回一个报文段 ack = 101, rwnd = 200(假设这里发生流量控制,把窗口大小降到了 200,允许发送方继续发送起始序号为 101,长度为 200 的报文)
此时的接收方滑动窗口如下(本来窗口右端应该右移,但是这里发生了流量控制,接收方希望缩小窗口大小,所以正好,这里就不需要向右扩展了):
发送方收到第一个包段的确认,并从窗口中删除第一个包段
此时的发送方滑动窗口如下:
6)发送端没有收到第二个报文段的确认回复,等待超时后重新发送第二个报文段。(序号 101 - 200)
7)接收端成功接收到第二个包段(窗口中有第二个和第三个包段),并返回一个包段ack = 301, rwnd = 100给发送端(假设这里发生了流控制,将窗口大小减少到100)
此时的接收方滑动窗口如下:(本来窗口右端应该右移,但是这里发生了流量控制,接收方希望缩小窗口大小,所以正好,这里就不需要向右扩展了)
发送方收到了第二个和第三个报文段的确认,从窗口中移除掉这俩报文段
8)发送方发送第四个报文段(序号 301 - 400)
此时的发送方滑动窗口如下:
参考来源 LeetCode《【大厂面试必备系列】滑动窗口协议》
什么是慢开始门限?
慢开始:如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。因此不要一开始就发送大量的数据,应由小到大逐渐增加拥塞窗口的大小。
synchronized 关键字
Java 中的锁分为显示锁和隐式锁。
隐式锁由 synchronized 关键字实现。
显示锁由 Lock 接口和 AQS 框架等等类来实现。
Java 中的每⼀个对象都可以作为锁,有三种加锁的⽅式:
(1)对于普通同步⽅法,锁是当前实例对象。
(2)对于静态同步⽅法,锁是当前类的 Class 对象
(3)对于同步⽅法块,锁是 Synchonized 括号⾥配置的对象。
数据库是什么?
数据库是通过一个文件或一组文件对有组织的数据进行保存的容器,是通过数据库管理系统创建和操纵的容器。
更多后端面试考点,可学习数据库知识手册
数据库有哪几种类型?
两种,关系型数据库和非关系型数据库。
更多后端面试考点,可学习数据库知识手册
第三范式(3NF)与第二范式的区别是什么?
在第二范式的基础上,非主键列只直接依赖于主键,不依赖于其他非主键。
更多后端面试考点,可学习数据库知识手册
触发器的使用场景有哪些?
- 需要通过数据库中的相关表实现级联更改;
- 需要实时监控某张表中的某个字段的更改情况,并需要做出相应的处理。
更多后端面试考点,可学习数据库知识手册
数据库索引根据结构分为哪几类?
三类。B 树索引、Hash 索引和位图索引。
更多后端面试考点,可学习数据库知识手册
B+ Tree 与 B-Tree 的结构很像,但是也有自己的特性,它的有哪些?
- 所有的非叶子结点只存储关键字信息;
- 所有具体数据都存在叶子结点中;
- 所有的叶子结点中包含了所有元素的信息;
- 所有叶子节点之间都有一个链指针。
更多后端面试考点,可学习数据库知识手册
Hash 索引和 B+ 树索引哪个不支持模糊查询以及多列索引的最左前缀匹配?为什么?必须回表查
Hash 索引。因为 Hash 函数的不可预测。
更多后端面试考点,可学习数据库知识手册
添加索引时需要注意哪些原则?
- 在查询中很少使用,或进行参考的列,不要对其创建索引。
- 只有很少数据值的列也不应该增加索引。
- 定义为 text、image 和 bit 数据类型的列不应该增加索引。
- 当修改性能远远大于检索性能时,不应该创建索引。
- 定义有外键的数据列一定要创建索引。
更多后端面试考点,可学习数据库知识手册
什么是数据库事务?
数据库的事务是一种机制,或者说一个操作序列,它包含了一组数据库操作命令,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
更多后端面试考点,可学习数据库知识手册
事务具有哪 4 个特性?
原子性、一致性、隔离性、持久性。
更多后端面试考点,可学习数据库知识手册
平常是用的 MySQL 数据库吗?说一下四个隔离级别。
- Read Uncommitted(读取未提交内容)
- Read Committed(读取提交内容)
- Repeatable Read(可重读)
- Serializable(可串行化)
什么是 MVCC?
MVCC, 即多版本并发控制。MVCC 通过保存数据在某个时间点的快照来实现,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
MVCC 机制具体是怎么样的?
对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:
- ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
- 事务 ID:记录最后一次修改该记录的事务 ID。
- 回滚指针:指向这条记录的上一个版本。
请简述 Redis 和 MySQL 功能上的区别。
MySQL 主要用于持久化地将数据存储到硬盘,功能强大,但是读取速度较慢,资源消耗较大。
而 Redis 将使用频繁的数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是一般在使用中,Redis 缓存的数据保存时间是有限的。
更多后端面试考点,可学习数据库知识手册
请简述 Redis 和 MongoDB 内存及存储方面的区别。
- MongoDB 适合 大数据量的存储,依赖操作系统虚拟做内存管理,采用镜像文件存储,内存占有率比较高
- Redis 2.0 后增加 虚拟内存特性,突破物理内存限制,数据可以设置时效性。
更多后端面试考点,可学习数据库知识手册
redis 的优缺点
Redis 本质上是一个 Key-Value 类型的内存数据库,整个数据库加载在内存当中操作,定期通过异步操作把数据库中的数据 flush 到硬盘上进行保存。
因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。
优点:
- 读写性能极高, Redis 能读的速度是 110000次/s,写的速度是 81000次/s。
- 支持数据持久化,支持 AOF 和 RDB 两种持久化方式。
- 支持事务, Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。
- 数据结构丰富,除了支持 string 类型的 value 外,还支持 hash、set、zset、list 等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
- 丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等特性。
缺点:
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。
参考来源 稀土掘金《Redis在游戏服务器的使用,看看战力排行榜的实现》
redis 有哪几种数据结构?
有五种常用数据类型:String、Hash、Set、List、SortedSet。
以及三种特殊的数据类型:Bitmap、HyperLogLog、Geospatial ,
其中HyperLogLog、Bitmap的底层都是 String 数据类型,
Geospatial 的底层是 Sorted Set 数据类型。
redis 为什么这么快?
-
内存存储
Redis 是使用内存(in-memeroy)存储,数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1)。 -
单线程实现(Redis 6.0 以前)
Redis 使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。注意:单线程是指的是在核心网络模型中,网络请求模块使用一个线程来处理。 -
非阻塞 IO
Redis 使用多路复用 I/O 技术,将 epoll 作为 I/O 多路复用技术的实现,再加上 Redis 自身的事件处理模型将 epoll 中的连接、读写、关闭都转换为事件,不在网络 I/O 上浪费过多的时间。 -
优化的数据结构
Redis 有很多可以直接使用的优化数据结构的方法,应用层可以直接使用原生的数据结构提升性能。 -
使用底层模型不同
由于一般的系统调用系统函数会浪费一定的时间去移动和请求,Redis 直接自己构建了虚拟内存机制 。
参考来源 CSDN《Redis 为啥这么快?》
redis 的缓存淘汰策略?
从高并发上来说:
- 直接操作缓存能够承受的请求是远远大于直接访问数据库的,因此可以考虑把数据库中的部分数据转移到缓存中去,将用户的一部分请求会直接存储到缓存而不用经过数据库。
从高性能上来说:
- 用户第一次访问数据库中的某些数据,从硬盘上读取的过程比较慢。将该用户访问的数据存在缓存中,下一次再访问这些数据的时候就可以直接从缓存中获取了,速度很快。如果数据库中的对应数据改变的之后,则会同步改变缓存中相应的数据。
参考来源 CSDN《Redis大厂面试题》
redis 如何持久化数据?
为了能够重用 Redis 数据,且防止系统故障,我们需要将 Redis 中的数据写入到磁盘空间中,即持久化。
Redis 提供了两种不同的持久化方法可以将数据存储在磁盘中,一种叫快照 RDB,另一种叫只追加文件 AOF。
-
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时将快照文件直接读到内存里。
- 优势:适合大规模的数据恢复;对数据完整性和一致性要求不高
- 劣势:在一定间隔时间做一次备份,所以如果 Redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。
-
AOF
以日志的形式来记录每个写操作,将 Redis 执行过的所有写指令记录下来(除读取操作外),只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据。当 Redis 重启时,会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF 采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时, Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集.。
- 优势
- 每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
- 每秒同步:appendfsync everysec 异步操作,每秒记录,如果一秒内宕机,有数据丢失
- 不同步:appendfsync no 从不同步
- 劣势
- 相同数据集的数据而言 aof 文件要远大于 rdb 文件,恢复速度慢于 rdb
- aof运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和 rdb 相同
参考来源 CSDN 《Redis中间件学习笔记(三)(面试重点)》
Dubbo 是什么?
Dubbo 是阿里巴巴基于 java 的开源高性能 RPC 分布式服务框架,已成为 Apache 基金会的孵化项目。
其核心组成部分包括:
- 集群容错
提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡、容错、地址路由、动态配置等集群支持。 - tele 传态
提供各种基于长连接的 NIO 框架的抽象封装,包括多线程模型、序列化和请求 - 响应模式下的信息交换。 - 自动发现
基于注册表目录服务,服务使用者可以动态搜索服务提供者,使地址透明,使服务提供者可以顺利增加或减少机器。
参考来源 CSDN《八股文--Dubbo》
Dubbo 如何做负载均衡?
在 rpc 客户端上实现负载均衡功能,以适应随时变化的外部环境,更好地发挥硬件的作用。客户端的负载平衡自然避免了单点问题。定制有自己的定制优势和劣势。
它可以从配置文件中指定,也可以在管理后台进行配置。
事实上,它同时支持服务器服务/方法级别和客户端服务/方法级别的负载平衡配置。
参考来源 cnblogs 《负载均衡--rpc服务端》
Dubbo超时设置有哪些方式?
有两种方法来设置 Dubbo 超时:
- 服务提供商侧设置超时时间。在 Dubbo 的用户文档中,建议在服务器端进行尽可能多的配置,因为服务提供者比使用者更了解它所提供的服务特征。
- 配置消费者端的超时时间。如果在消费者端设置超时时间,则服务优先级更高。这是因为服务调用方在设置超时控制方面具有更大的灵活性。如果使用者超时,则服务器线程不进行自定义并生成警告。
参考来源 CSDN 《Dubbo 超时时间怎样设置?》
Dubbo 如何实现异步调用的?
Dubbo 的底层 I/O 操作都是异步的。当消费者被调用时,得到一个 Future 对象。对于同步调用,服务线程使用 Future#get(timeout) 来阻塞并等待提供者返回结果。timeout 是消费者定义的超时。当返回结果时,设置Future并唤醒阻塞的业务线程。如果在超时时间后没有返回结果,业务线程将返回一个异常。
基于底层 Dubbo 的异步 NIO 来实现异步调用是生产者响应时间较长的场景所必需的,它可以有效地利用消费者端的资源,相比于消费者端使用多线程的成本较小。
无论是同步的还是异步的,将以 exchang# send 方法结束,然后将以HeaderExchangeChannel#request 方法结束,这是一个异步方法并返回 ResponseFuture 对象。
dubbo 中的同步调用也是通过异步调用实现的,但是在启动同步调用之后,直接调用 future#get 方法来同步等待结果的返回,而异步调用只返回 future Response,并在用户需要关心结果时调用 get 方法。
参考来源 CSDN《微服务框架(十八)Dubbo领域模型、调用链及调用方式》
RocketMq 如何保证高可用的?
RocketMQ 分布式集群是通过 Master 和 Slave 的配合达到高可用性的。
Master 和 Slave 的区别:在 Broker 的配置文件中,参数 brokerId 的值为 0 表明这个 Broker 是 Master,大于 0 表明这个 Broker 是 Slave,同时 brokerRole 参数也会说明这个 Broker 是 Master 还是 Slave。
Master 角色的 Broker 支持读和写,Slave 角色的 Broker 仅支持读,也就是 Producer 只能和Master 角色的 Broker 连接写入消息;Consumer 可以连接 Master 角色的 Broker,也可以连接Slave 角色的 Broker 来读取消息。
- 消息消费的高可用性
在 Consumer 配置文件中,不需要设置从 Master 或 Slave 读取的数据。当主服务器不可用或繁忙时,消费服务器将自动切换到从服务器。使用 Consumer 的自动切换机制,当 Master 的机器出现故障时,Consumer 仍然可以从 Slave 读取消息,而不影响Consumer程序。这是消费者端的高可用性。 - 消息发送的高可用性
当创建一个Topic时,将在多个 Broker 组上创建该 Topic 的消息队列(具有相同Broker 名称和不同 Brokerid 的机器组成一个 Broker 组)。这样,当一个 Broker 组的 Master 不可用时,另一个组的 Master 仍然可用,Producer 仍然可以发送消息。RocketMQ 目前不支持从服务器到主服务器的自动转换。如果由于机器资源不足需要将 Slave 转换为 Master,请手动停止 Slave Broker,更改配置文件,并使用新的配置文件启动 Broker。 - 消息主从复制
如果 Broker 组有一个 Master 和 Slave,则需要将消息从 Master 复制到 Slave(同步和异步)。
参考来源 CSDN《RocketMQ(五)RocketMQ高可用性机制》
RocketMq 如何保证高吞吐的?
-
路由中心(NameServer)
NameServer 通过集群部署,为追求简单高效 NameServer 彼此之间互不通信。
NameServer 与每台 Broker 服务器保持长连接,并间隔 30s 检测 Broker 是否存活,如果检测到 Broker 宕机,则从路由注册表中将其移除。但是路由变化不会马上通知消息生产者,降低NameServer 实现的复杂性,在消息发送端提供容错机制来保证消息发送的高可用性。 - 消息生产者(Producer)
消息生产者集群部署。
本地缓存 topic 路由信息,如果本地路由表中未缓存 topic 的路由信息,向 NameServer 发送获取路由信息请求,更新本地路由信息表,并且消息生产者每隔 30s 从 NameServer 更新路由表。
消息发送通过负载均衡策略选择 Broker,避免单个服务器压力过大。
消息发送异常机制,消息发送高可用主要通过两个手段:重试与 Broker 规避。Broker 规避就是在一次消息发送过程中发现错误,在某一时间段内,消息生产者不会选择该 Broker 上的消息队列,提高发送消息的成功率。
批量消息发送,支持将同一主题下的多条消息一次性发送到消息服务端。 - 消息消费者(Consumer)
消息消费者集群部署。
消费者消费消息时负载均衡。由 RebalanceService 线程默认每隔 20s 进行一次消息队列负载,根据当前消费组内消费者个数与主题队列数量按照某一种负载算法进行队列分配,分配原则为同一个消费者可以分配多个消息消费队列,同一个消息消费队列同一时间只会分配给一个消费者。
消息拉取由 PullMessageService 线程根据 RebalanceService 线程创建的拉取任务进行拉取,默认一批拉取 32 条消息,提交给消费者消费线程池后继续下一次的消息拉取。如果消息消费过慢产生消息堆积会触发消息消费拉取流控。 - 服务器(Broker)
零拷贝技术。
服务端的高并发读写主要利用 Linux 操作系统的 PageCache 特性,通过 Java 的 MappedByteBuffer 直接操作PageCache。MappedByteBuffer 能直接将文件映射到内存,通过顺序写盘(Commitlog),预读数据来尽量命中 PageCahe,从而大大减少磁盘 IO。Commitlog,消息存储文件,RocketMQ 为了保证消息发送的高吞吐量,采用单一文件存储所有主题的消息,保证消息存储是完全的顺序写,但这样给文件读取同样带来了不便,为此 RocketMQ 为了方便消息消费构建了消息消费队列文件(ConsumerQueue),基于主题与队列进行组织,同时 RocketMQ 为消息实现了 Hash 索引文件(IndexFile),可以为消息设置索引键,根据索引能够快速从 Commitog 文件中检索消息。当消息到达 Commitlog 文件后,会通过ReputMessageService 线程接近实时地将消息转发给消息消费队列文件与索引文件。为了安全起见,RocketMQ 引人 abort 文件,记录 Broker 的停机是正常关闭还是异常关闭,在重启 Broker 时为了保证 Commitlog 文件、消息消费队列文件与 Hash 索引文件的正确性,分别采取不同的策略来恢复文件。
参考来源 CSDN《RocketMQ高并发高吞吐量的原理》
RocketMq 的消息是有序的吗?
是的。
消息有序指的是,消费者端消费消息时,需按照消息的发送顺序来消费,即先发送的消息,需要先消费(FIFO)。
RocketMq 的消息局部顺序是如何保证的?
RocketMQ 采用局部顺序一致性的机制,实现了单个队列中消息的有序性,使用 FIFO 顺序提供有序消息。简而言之,我们的消息要保证有序,就必须把一组消息存放在同一个队列,然后由 Consumer 进行逐一消费。
RocketMQ 给的解决方案是按照业务去划分不同的队列,然后并行消费,提高消息的处理速度的同时避免消息堆积。
RocketMQ 可以严格的保证消息有序,可以分为分区有序或者全局有序
- 全局有序:全局顺序时使用一个 queue;
- 分区有序:局部顺序时多个 queue 并行消费;
参考来源 CSDN《RocketMQ(九)RocketMQ顺序消息》
RocketMq 事务消息的实现机制?
RocketMQ 提供了事务消息,通过事务消息就能达到分布式事务的最终一致,从而实现了可靠消息服务。
- 发送事务消息的过程
- 发送方将半事务消息发送到 RocketMQ 服务器。
- 在 RocketMQ 服务器保存消息之后,它向发送方返回一个 ACK,以确认消息已成功发送。因为该消息是半事务消息,所以在从生产者接收到该消息的第二次确认之前,它被标记为“尚未可交付”。
- 发送方开始执行本地事务逻辑。
- 发送方根据本地事务执行结果向服务器提交辅助确认(提交或回滚)。在收到 Commit 状态后,服务器将半事务消息标记为可交付消息,订阅者最终将收到该消息。服务器在收到 Rollback 状态时删除半事务消息,订阅者将不接受该消息。
- 事务消息回调步骤
- 当网络断开或应用程序重新启动时,如果在步骤4中提交的第二个确认没有到达服务器,服务器将在一段固定的时间后检查消息。
- 发送方收到消息后,需要检查与该消息对应的本地事务执行的最终结果。
3.发送方根据检查获得的本地事务的最终状态提交第二次确认,服务器继续按照步骤4操作半事务消息。
参考来源 CSDN《RocketMQ事务消息机制》
RocketMq 支持什么级别的延迟消息?如何实现的?
RocketMQ 提供了延迟消息类型,这意味着生产者指定消息发送的延迟时间,然后才能将消息传递给消费者。
- Broker 检查收到的消息是否延迟。如果它们是延迟消息,代理将使用 SCHEDULE_TOPIC_XXXX作为 持久主题名。实现方法在 CommitLog的putMessage 中。如果 msg 延迟标准大于 0,则重新设置消息的主题名和队列 id。之后,将消息的 SCHEDULE_TOPIC_XXXX 作为主题,并将延迟时间的 queueId 持久化到提交日志文件中。
- Broker 持久化一个名称为 SCHEDULE_TOPIC_XXXX 的延迟消息作为主题。让我们看看代理如何在延迟消息到达后恢复消息。
- ScheduleMessageService RocketMQ 提供 ScheduleMessageService 服务。SCHEDULE_TOPIC_XXXX 和 queueId 从 SCHEDULE_TOPIC_XXXX 中读取,用于消息恢复。消息恢复后,使用者可以提取消息。
- 一个定时任务 DeliverDelayedMessageTimerTask 每个消费级别。在 DeliverDelayedMessageTimerTask 中的名称和时间延迟根据 SCHEDULE_TOPIC_XXXX 级别对应的 queueId 访问消息队列,然后从提交日志中读取消息,恢复消息的原始信息(消息的原始主题信息)并将消息持久化到提交日志文件中,以便消费者可以提取消息。
参考来源 CSDN《RocketMQ原理学习--延时消息实现原理》
讲一讲 JVM 启动时都有哪些参数
- 「堆内存相关」
- -Xms :设置初始堆的大小
- -Xmx :设置最大堆的大小
- -Xmn :设置年轻代大小,相当于同时配置 -XX:NewSize和 -XX:MaxNewSize 为一样的值
- -Xss :每个线程的堆栈大小
- -XX:NewSize :设置年轻代大小(for 1.3/1.4)
- -XX:MaxNewSize :年轻代最大值(for 1.3/1.4)
- -XX:NewRatio :年轻代与年老代的比值(除去持久代)
- -XX:SurvivorRatio :Eden区与Survivor区的的比值
- -XX:PretenureSizeThreshold :当创建的对象超过指定大小时,直接把对象分配在老年代
- -XX:MaxTenuringThreshold :设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代
- 「垃圾收集器相关」
- -XX:+UseParallelGC :选择垃圾收集器为并行收集器
- -XX:ParallelGCThreads=20 :配置并行收集器的线程数
- -XX:+UseConcMarkSweepGC :设置年老代为并发收集
- -XX:CMSFullGCsBeforeCompaction=5 :由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行5次GC以后对内存空间进行压缩、整理
- -XX:+UseCMSCompactAtFullCollection :打开对年老代的压缩。可能会影响性能,但是可以消除碎片
- 「辅助信息相关」
- -XX:+PrintGCDetails :打印GC详细信息
- -XX:+HeapDumpOnOutOfMemoryError :让JVM在发生内存溢出的时候自动生成内存快照,用于排查问题
- -XX:+DisableExplicitGC禁止系统System.gc() :防止手动误触发FGC造成问题.
- -XX:+PrintTLAB :查看TLAB空间的使用情况
红黑树的查询效率为什么高?
因为红黑树是一种特化的平衡二叉搜索树,其查询过程类似于二分查找,只与树的高度有关,因此查询的事件复杂度可以做到 O(log N)。
PS: 在插入和删除时通过特定操作保持二叉搜索树的相对平衡(红黑树与 AVL 树不同,AVL 是绝对平衡),从而也能获得较高的性能。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
除了用红黑树,还能用什么方法解决 hash 冲突(链过长的情况)?
解决散列冲突有四种方法:打开地址、重新散列、链地址和创建公共溢出区域。
- 打开地址法
此方法也称为重哈希法,其基本思想是:当密钥 p=H(key) 的哈希地址冲突时,根据 p 生成另一个哈希地址 p1,如果 p1 仍然冲突,根据p生成另一个哈希地址 p2…,直到找到一个不冲突的散列地址 PI,并将相应的元素存储在其中。例如,线性探测通过将元素放入冲突地址的下一个地址来重新哈希,如果仍然存在冲突,它将继续搜索,直到到达一个空地址。 - Rehash:
该方法是同时构造几个不同的哈希函数。 - 链地址法
该方法的基本思想是将哈希地址为i的所有元素组成一个单链表,称为同义词链,单链表的头指针存储在哈希表的第i个单元中,因此搜索、插入和删除主要在同义词链中进行。当插入和删除频繁时,链地址方法很有用。 - 公共溢出区域的建立
这种方法的基本思想是将哈希表分为两部分:基础表和溢出表。所有与基本表冲突的元素都将被填充到溢出表中。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
hashmap 什么时候会触发扩容?
HashMap 在添加元素的时候,如果发现当前元素个数超过了加载因子(load factor)与当前容量的乘积,就会触发扩容操作。加载因子是 HashMap 在创建时指定的一个参数,表示容器在什么时候需要进行扩容,默认为 0.75。也就是说,当 HashMap 中的元素个数达到了容器大小的 75% 时,就会自动扩容。
在进行扩容时,HashMap 会创建一个新的数组,并将原有的元素重新分配到新的数组中。这个过程会涉及到重新计算哈希值、重新分配位置等操作,因此扩容会比较耗费时间,但可以保证 HashMap 的性能和容量。
需要注意的是,如果 HashMap 中的元素个数非常多,而加载因子又比较小,那么就会出现频繁扩容的情况,影响程序的性能。因此,在创建 HashMap 时,需要根据具体的业务场景合理设置加载因子的大小,以达到最优的性能表现。
MySQL 中不同事务隔离级别分别会加哪些锁?
- 在 Read Uncommitted 级别,共享锁不需要读取数据,这样它们就不会与修改数据上的排他锁发生冲突
- 在 Read Committed 级别是为读操作添加共享锁,执行完后释放共享锁。
- 在 Repeatable Read 级别下,共享锁需要添加到读操作中,但直到事务提交后才释放,即必须等待事务执行完毕。
- SERIALIZABL E是最严格的隔离级别,因为该级别锁定整个键范围,并持有锁,直到事务完成。
DBMS 采用什么来实现事务的隔离性?
锁机制。
更多后端面试考点,可学习数据库知识手册
内连接有哪几种?并写出 SQL 语句。
- 等值连接:
ON A.id = B.id
- 不等值连接:
ON A.id > B.id
- 自连接:
SELECT * FROM A T1 INNER JOIN A T2 ON T1.id = T2.pid
redis 的常用场景有哪些?
- 缓存
缓存现在几乎所有的大中型网站都在使用 kill 技术,合理的使用缓存不仅可以提高网站的访问速度,还可以大大减轻数据库的压力。Redis 提供了密钥过期功能,也提供了灵活的密钥报废策略,所以 Redis 现在在缓存的情况下使用的非常多。 - 游戏排行榜
很多网站都有排名应用,比如京东的月度销售排行榜,新的商品时间排名。Redis提供的有序集数据类构造可以实现各种复杂的排行榜应用。 - 计数器
什么是专柜,比如电子商务网站商品页面浏览量、视频网站视频播放次数等。为了保证数据的时效性,每次浏览都要给 +1,在并发性高的情况下,每次都要求数据库操作无疑是一种挑战和压力。Redis 提供了 incr 命令来实现计数功能,内存操作,性能非常好,非常适合这些计数场景。 - 分布式会话
在集群模式下,当应用程序较少时,可以满足容器提供的会话复制功能。在应用越来越复杂的系统中,一般会构建像 Redis 这样以内存数据库为中心的会话服务,不再由容器来管理会话。相反,它是由会话服务和内存数据库管理的。 - 分布式锁
许多互联网公司都采用了分布式技术,分布式技术带来的技术挑战是对同一资源的并发访问,如全局 ID、库存减少、秒杀等场景。数据库的悲观锁和乐观锁在低并发性的场景下可以使用,但在高并发性的情况下,使用数据库锁来控制资源的并发访问并不理想,并且会极大地影响数据库的性能。Redis 的 setnx 函数可以用来写分布式锁。如果返回1,表示锁获取成功。否则,获取锁失败。 - 社交网络
“喜欢”、“关注”、“被关注”、“共同好友”是社交网站的基本功能。社交网站的访问量通常比较大,传统的关系数据库类型不适合存储这类数据。Redis 提供的 hash、set 等数据结构可以很方便地实现这些功能。例如,微博上的共同好友可以通过 Redis set 轻松获得。 - 最新榜单
Redis 列表结构,LPUSH 可以在列表的头部插入一个内容 ID 作为关键字,LTRIM 可以用来限制列表的数量,这样列表总是 N 个 ID,不需要查询最新的列表,直接根据 ID 到相应的内容页面就可以了。 - 消息传递系统
消息队列是大型网站的必备中间件,如 ActiveMQ、RabbitMQ、Kafka 等流行的消息队列中间件,主要用于低实时性服务的业务解耦、流量峰值切割和异步处理。Redis 提供了发布/订阅和阻塞队列功能,可以实现一个简单的消息队列系统。此外,这是无法与专业消息传递中间件相比的。
常见的排序算法排序有哪些?你最熟悉哪个?解释一下堆排序?
构建堆有两种方法,一种是典型的自顶向下,另一种是自底向上。对于自顶向下的堆构建,它主要用于逐个给出用户数据的情况。例如,在下一个问题中,从一些元素中找到最大或最小的元素(为下一个问题保留)。对于自底向上,这是用户一次提供所有数据的情况,也就是说,已经有一个无序数组需要进行堆排序。
假设现在我们有一个 10,000 个元素的数组,我们想要按升序对其排序。整个思路是:通过数组回到前一次,调整为一个大根堆,然后交换头元素与最后一个元素,然后改变第一个数组中元素的个数-1个元素为大根堆,然后将头元素和倒数第二元素互换,再前面数组长度-2个元素调整为大根堆,循环交换元素和调整堆结构的过程,最后就能够对数组完成排序。
参考来源 CSDN《2022届校招Java面试题汇总(含题解)》
非聚簇索引分为哪两类?分别是什么意思?
- 覆盖索引:索引中包含 SELECT 数据字段
- 复合索引:索引中包含多个 WHERE 条件字段