学到微服务了嘛?
啊?
能独立写项目嘛?
啊?
有项目经验嘛?
啊?
实习过嘛?
什么公司?
对前端有了解嘛?
算法怎么样?
工作没找就结束了?
之前不是学点就行吗?
没找工作呢哪有工作经验?
学校也没说求职要问这些啊
计算机不是人均大厂嘛?
人家不是也一起玩怎么面试他都会
大学毕业不是月薪过万?
够狠,工作爱情一个都不给!
很多兄弟在面试的时候是不是也很迷茫?那要准备到什么程度才能去找日常实习?
其实实习面试的问题都差不多,八股+项目+算法,都必须要做好准备,只是说实习面试要求可能不会太严格,比如你实习的算法,即时没写出来,能说出大概的思路,其实也是能过的,秋招的话,可能没写出来算法,大概率就凉了。
针对后端这一块的话,编程语言+MySQL+Redis+网络协议+操作系统+后端项目+算法,这几大块都需要好好准备一波,后端面试都是围绕这几大块知识进行考察。
虽然后端技术栈还包含消息队列、分布式、微服务,但是这些针对校招来说不是必须要掌握的内容,时间有限的情况下,可以延后学习。
因为对于实习岗位来讲,你能够独立的把单体项目的三层架构写出来、能够自己独立的设计数据库表,这样的能力比你去了解微服务要重要得多。
网络 http和https的区别是什么? HTTP 与 HTTPS 网络层
HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。 HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。 HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。 域名和ip地址如何对映的? 客户端向本地DNS服务器发送一个DNS请求,询问"www.server.com"的IP地址。 本地DNS服务器接收到客户端的请求后,首先会检查自己的缓存中是否有"www.server.com"的IP地址。如果有,则直接返回IP地址给客户端。 如果本地DNS服务器的缓存中没有"www.server.com"的IP地址,它会向根域名服务器发送请求,询问该域名的顶级域名服务器的地址。根域名服务器是最高层次的服务器,虽然不直接进行域名解析,但它可以指示一个路径。 根域名服务器收到本地DNS服务器的请求后,发现后置是".com",于是告诉本地DNS服务器:"www.server.com"这个域名归".com"区域管理,我给你".com"顶级域名服务器的地址,你去问问它吧。 本地DNS服务器收到".com"顶级域名服务器的地址后,再次发起请求,询问该顶级域名服务器:"www.server.com"对应的IP地址是多少? ".com"顶级域名服务器告诉本地DNS服务器:"www.server.com"的权威DNS服务器的地址是什么,你去问问它应该能得到答案。 本地DNS服务器向权威DNS服务器发送请求,询问:"www.server.com"对应的IP地址是多少?权威DNS服务器是域名解析结果的原始来源,它拥有该域名的解析权。 权威DNS服务器查询后将对应的IP地址(X.X.X.X)告诉本地DNS服务器。 本地DNS服务器将IP地址返回给客户端,客户端和目标服务器建立连接。 介绍一下tcp 建立连接的三次握手过程 TCP是面向连接的协议,所以在使用TCP之前需要先建立连接。这个连接的建立是通过三次握手来完成的。 一开始客户端和服务端都处于CLOSE状态。服务端处于LISTEN状态,等待客户端的连接请求。 客户端随机选择一个初始序列号(client_isn),并将该序列号放入TCP首部的序列号字段。同时,客户端将SYN标志位置为1,表示发送了一个SYN报文段。然后,客户端将这个SYN报文段发送给服务端,表示客户端想要建立连接。此时,客户端进入SYN-SENT状态。 服务端收到客户端发送的SYN报文段后,首先会随机选择一个初始序列号(server_isn)。服务端将这个序列号放入TCP首部的序列号字段,并将确认应答号字段设置为client_isn + 1。此外,服务端也将SYN和ACK标志位都设置为1,表示已经收到客户端的请求,并同意建立连接。接着,服务端将这个带有SYN和ACK标志位的报文段发送给客户端,表示服务端接受了连接请求。此时,服务端进入SYN-RCVD状态。 客户端收到服务端的SYN-ACK报文段后,会向服务端发送一个确认报文段。客户端将ACK标志位设置为1,并将确认应答号字段设置为server_isn + 1。此外,客户端还可以携带数据发送给服务端。这样,客户端告诉服务端连接已经建立成功。客户端进入ESTABLISHED状态。 服务端收到客户端发送的确认报文段后,也进入ESTABLISHED状态。至此,连接建立完成,双方可以开始进行通信。 通过三次握手,客户端和服务端确认彼此的通信能力,并建立起了可靠的连接。这种方式可以防止已失效的连接请求报文段再次出现,确保了连接的可靠性。 为什么不是 2 次或者 4 次握手? 三次握手的原因:三次握手才可以阻止重复历史连接的初始化(主要原因) 三次握手才可以同步双方的初始序列号 三次握手才可以避免资源浪费 原因一:防止历史连接TCP使用三次握手的主要原因是为了防止旧的重复连接初始化造成混乱。举个例子,假设客户端发送了一个SYN(seq = 90)报文,但这个报文在网络中被阻塞了,服务端没有收到。然后客户端宕机了,重启后又向服务端发送了一个SYN(seq = 100)报文。在网络拥堵的情况下,如果服务端先收到了旧的SYN报文,就会回复一个SYN + ACK报文给客户端,确认号为91(90+1)。客户端收到后,发现期望收到的确认号应该是101,而不是91,于是回复一个RST报文断开连接。服务端接收到RST报文后,释放连接。最后,当最新的SYN抵达服务端时,客户端和服务端才能正常地完成三次握手。 三次握手图解
采用两次握手连接无法阻止历史连接,主要是因为在两次握手的情况下,服务端没有中间状态来阻止历史连接,这可能导致服务端建立一个无效的历史连接,从而浪费资源。 原因二:同步双方初始序列号TCP通信双方都必须维护一个序列号,序列号在可靠传输中起着关键作用。通过序列号,接收方可以去除重复的数据、按序接收数据包,并确定哪些数据包已经被对方收到。为了保证序列号的可靠同步,当客户端发送携带初始序列号的SYN报文时,服务端需要回复一个ACK应答报文来确认接收到客户端的SYN报文。同样地,当服务端发送初始序列号给客户端时,也需要得到客户端的应答。通过这种一来一回的交互,双方的初始序列号才能可靠地同步。四次握手也可以实现双方初始序列号的同步,但由于第二步和第三步可以优化成一步,所以实际上成了“三次握手”。 原因三:避免资源浪费如果只有两次握手,当客户端发送的SYN报文在网络中被阻塞,客户端没有收到ACK报文,就会重新发送SYN。由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的ACK报文,所以服务端每收到一个SYN就只能主动建立一个连接。这会导致以下情况:如果客户端发送的SYN报文在网络中被阻塞,重复发送多次SYN报文,那么服务端就会建立多个冗余的无效连接,造成资源浪费。 get和post的区别? 根据 RFC 规范,GET 请求的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。比如,当你在搜索引擎中输入关键词并点击搜索按钮时,浏览器会发送一个 GET 请求给搜索引擎服务器,其中包含了你输入的关键词作为请求参数。服务器收到这个请求后会返回与关键词相关的搜索结果页面,然后浏览器会展示这些搜索结果给你。GET 请求是用来获取资源的,它不会对服务器上的数据进行修改操作。 根据 RFC 规范,POST 请求的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。比如,当你在网上购物时,当你点击添加商品到购物车时,浏览器就会执行一次 POST 请求,把你所选的商品信息放进了请求体里,然后拼接好 POST 请求头,通过 TCP 协议发送给服务器。服务器会根据请求体中的信息对购物车进行更新,然后返回更新后的购物车信息。因为 POST 请求能够携带任意格式的数据,所以它非常适合用于向服务器提交数据,比如表单数据或上传文件。
操作系统 已知一个进程名,如何杀掉这个进程? 在Linux 操作系统,可以使用kill命令来杀死进程。
首先,使用ps命令查找进程的PID(进程ID),然后使用kill命令加上PID来终止进程。例如:
ps -ef | grep <进程名> // 查找进程的PID
kill // 终止进程
kill -9 使用的是哪种? fork创建子进程有哪些特点? 创建新的子进程:调用fork后,会创建一个新的子进程。这个子进程几乎与父进程完全相同,包括代码、数据和打开文件等。子进程从fork调用的位置开始执行,而父进程和子进程在fork调用之后的代码处继续执行。 资源继承:子进程继承了父进程的大部分资源,包括打开的文件、文件描述符、信号处理器等。但是有些资源(如互斥锁和定时器)可能需要进行特殊处理,以避免竞争条件或资源泄漏。 内存独立:父进程和子进程拥有独立的虚拟内存空间,每个进程都有自己的内存映射表。子进程通过写时复制(copy-on-write)机制与父进程共享物理内存,只有在需要修改内存内容时才会进行复制。 父子关系判断:父进程可以通过fork的返回值来判断是否为子进程。具体地,父进程的fork返回子进程的PID,而子进程的fork返回0。这样可以根据返回值的不同,在父子进程中执行不同的逻辑。 Copy On Write技术是什么? Copy On Write技术是一种内存管理机制。在创建子进程时,操作系统会将父进程的页表复制一份给子进程,记录虚拟地址和物理地址的映射关系。但并不复制物理内存,即父子进程共享同一份物理内存数据。为了节约物理内存资源,对应的页表项被标记为只读权限。
当父进程或子进程需要对共享内存进行写操作时,CPU会触发写保护中断,这是因为违反了权限规定。操作系统会在写保护中断处理函数中进行物理内存的复制,并重新设置内存映射关系,将父子进程的内存读写权限设置为可读写。然后才能执行写操作。这个过程被称为写时复制(Copy On Write)。
写时复制的意思是,在发生写操作时才会复制物理内存。这样做是为了避免在fork创建子进程时由于物理内存数据复制的时间过长而导致父进程长时间阻塞。
Java ArrayList和LinkedList的区别? 底层数据结构:ArrayList使用数组作为底层数据结构,而LinkedList使用双向链表作为底层数据结构 随机访问性能:ArrayList支持通过索引直接访问元素,因为底层数组的连续存储特性,所以时间复杂度为O(1)。而LinkedList需要从头或尾部开始遍历链表,时间复杂度为O(n)。 插入和删除操作:ArrayList在尾部插入和删除元素的时间复杂度为O(1),因为它只需要调整数组的长度即可。但在中间或头部插入和删除元素时,需要将后续元素进行移动,时间复杂度为O(n)。而LinkedList在任意位置插入和删除元素的时间复杂度为O(1),因为只需要调整节点的指针即可。 内存占用:ArrayList在每个元素中都存储了实际的数据,而LinkedList在每个节点中存储了数据和前后节点的指针。因此,相同数量的元素情况下,LinkedList通常比ArrayList占用更多的内存空间。 在使用时候,这两者的应用场景是什么? 如果需要频繁进行随机访问操作,而对插入和删除操作要求不高,可以选择ArrayList。 如果需要频繁进行插入和删除操作,而对随机访问操作要求不高,可以选择LinkedList。 HashMap的底层数据结构是什么? HashMap 的底层数据结构在 JDK 1.7 版本之前是数组和链表。它通过哈希算法将元素的键(Key)映射到数组中的槽位(Bucket)。如果多个键映射到同一个槽位,它们会以链表的形式存储在同一个槽位上。然而,由于链表的查询时间复杂度为 O(n),所以当一个索引上的链表很长时,效率就会降低。
为了解决这个问题,在 JDK 1.8 版本中进行了优化。当链表的长度超过8时,HashMap 会将该链表转换成红黑树结构,用于提高查找性能,因为红黑树的查询时间复杂度为 O(log n)。然而,当元素数量较少时(小于6),红黑树的额外开销可能会超过链表的开销,因此 HashMap 会将红黑树再次转换回链表结构。这样的转换策略旨在在不同元素数量范围内平衡性能和内存开销。
HashMap会产生哪些并发安全? 线程安全性:HashMap不是线程安全的数据结构,多个线程同时对HashMap进行修改操作时,可能会导致数据的不一致性或损坏。 竞态条件:如果多个线程同时对HashMap进行插入、删除或更新操作,可能会导致竞态条件的发生,即最终结果依赖于线程执行的顺序,而不是预期的结果。 数据丢失:在JDK 1.7中,当HashMap进行扩容时,存在Entry链表出现死循环和数据丢失的问题。这可能会导致一些键值对在扩容过程中丢失。 数据覆盖:在JDK 1.8中,虽然采用了红黑树来优化HashMap的性能,但在多线程环境下,仍可能出现put方法导致数据覆盖的情况。即多个线程同时执行put操作,可能会导致某些键值对被覆盖,造成数据丢失或错误。 解决措施:
使用线程安全的替代类:例如,可以使用ConcurrentHashMap来替代HashMap,它提供了更好的并发访问控制,并保证线程安全性。 使用同步机制:可以使用synchronized关键字或锁来保证对HashMap的并发访问的互斥性,确保线程安全。 使用并发工具类:可以使用并发工具类如ReadWriteLock或ConcurrentHashMap的并发特性来确保对HashMap的安全访问。 说一下ConcurrentHashMap是如何实现的线程安全的? 在 JDK 1.7 中它使用的是数组加链表的形式实现的,而数组又分为:大数组 Segment 和小数组 HashEntry。Segment 是一种可重入锁(ReentrantLock),在 ConcurrentHashMap 里扮演锁的角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素。
分段锁技术将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现。
JDK 1.8 ConcurrentHashMap 主要通过 volatile + CAS 或者 synchronized 来实现的线程安全的。
添加元素时首先会判断容器是否为空:
如果为空则使用 volatile 加 CAS 来初始化,如果容器不为空,则根据存储的元素计算该位置是否为空。 如果根据存储的元素计算结果为空,则利用 CAS 设置该节点; 如果根据存储的元素计算结果不为空,则使用 synchronized ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。 如果把上面的执行用一句话归纳的话,就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度。
除了ConcurrentHashMap,还可以如何将HashMap变为线程安全的? 将 HashMap 替换成HashTable,HashTable是线程安全的。
使用Collections.synchronizedMap方法:可以使用Collections.synchronizedMap方法创建一个线程安全的HashMap,它会返回一个包装后的线程安全的Map对象。该方法使用了内部的同步机制来保证线程安全,但在高并发环境下可能性能较低。
Redis Redis6.0之前为什么一直不使用多线程? 使用Redis过程中,CPU不是瓶颈,是非常受制于内存、网络。在Redis使用过程中,数据储存在内存,如果对Redis进行快读或存网络开销很大。
提高Redis的性能,可以用到Pipeline(命令的批量处理),进行批量处理后,Redis可以轻松达到每秒100万个请求。把很多的请求包装到Pipeline里面,CPU的开销并不高。
如果使用单线程,Redis的内部维护成本相对来说比较低。也不用考虑多线程安全,虽然说多线程某些方面表现得非常优秀,但是如果引入会涉及到安全问题、也会涉及到命令的执行顺序的不确定性,Redis会经常对它进行读写,这样会涉及到并发读写,增加了系统的复杂度。
如果是多线程,会涉及到线程切换。对共享的读写还要加锁、解锁,在这方面开销也非常大甚至还可能导致死锁问题。
单线程可以使其内部机制复杂度大大降低,同时Redis内部有一个惰性的Rehash(渐进性式的Rehash),可以减少访问时的阻塞。
介绍RDB一下持久化机制的详细过程 为了解决AOF日志记录的操作命令无法直接恢复数据且可能导致恢复操作缓慢的问题,Redis引入了RDB持久化机制。RDB快照是指记录某一瞬间的内存数据,并将实际数据保存到文件中,与AOF记录命令操作的日志不同。
当需要恢复数据时,RDB的恢复效率通常比AOF高,因为它只需要将RDB文件读入内存,而不需要执行额外的命令操作。
为了实现自动执行RDB快照的功能,Redis提供了配置选项。通过在配置文件中设置save选项,可以定期执行bgsave命令生成RDB快照文件。具体的配置项如下:
save 900 1
save 300 10
save 60 10000
这些配置项意味着:
在900秒内,如果数据库至少修改了1次,则执行bgsave命令。 在300秒内,如果数据库至少修改了10次,则执行bgsave命令。 在60秒内,如果数据库至少修改了10000次,则执行bgsave命令。 需要注意的是,Redis的快照是全量快照,即每次执行快照都会将内存中的所有数据记录到磁盘中。因此,执行快照是一项较为耗时的操作。如果快照频率过高,可能会对Redis的性能产生影响;而如果频率过低,在服务器故障时,会导致数据丢失的风险增加。
在执行bgsave命令期间,Redis仍然可以继续处理操作命令。这得益于写时复制技术(Copy-On-Write, COW)。
当执行bgsave命令时,Redis通过fork()创建子进程。子进程和父进程共享同一片内存数据,但是如果主线程执行写操作,被修改的数据会复制一份副本,然后bgsave子进程将该副本数据写入RDB文件。在此过程中,主线程仍然能够直接修改原始数据。
RDB持久化会阻塞主线程嘛? Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。它们的执行方式有所不同:
使用 save 命令时,Redis 会在主线程中执行生成 RDB 文件的操作。因为这个操作和执行其他命令一样都在主线程中进行,所以如果生成 RDB 文件的时间过长,可能会导致主线程被阻塞。 使用 bgsave 命令时,Redis 会创建一个子进程来执行生成 RDB 文件的操作。这样可以避免主线程的阻塞,因为生成 RDB 文件的任务由子进程负责,主线程可以继续处理其他请求。 需要注意的是,无论是使用 save 还是 bgsave 命令,生成 RDB 文件的过程都会对 Redis 的性能产生一定影响。尤其是在生成大型的 RDB 文件时,会造成一定的 CPU 和内存消耗。
MySQL MySQL的char和varchar有什么区别? 空间占用:char类型的存储空间是固定的,它会根据指定的长度来存储数据。即使存储的实际数据长度小于指定长度,char类型也会以指定长度进行存储,因此可能存在空间浪费的情况。 varchar类型的存储空间是可变的,它会根据存储的实际数据长度进行动态调整,不会浪费空间。此外,varchar类型需要额外存储字符串长度信息,因此在存储空间上可能稍微多一些。 存储方式:char类型存储定长字符串,会在末尾补齐空格以达到指定长度。当存储的字符数小于指定长度时,会填充空格;当存储的字符数大于指定长度时,会被截断为指定长度。 varchar类型存储变长字符串,不会填充空格,存储的实际字符数与指定长度相等。 应用场景:char适用于存储长度固定的字符串,例如存储国家代码、电话号码等。由于存储空间固定,char类型在一些特定场景下可以提高查询效率。 varchar适用于存储长度不固定的字符串,例如存储用户的姓名、地址等。由于不会浪费存储空间,varchar类型在存储大量变长字符串的场景下更加灵活和高效。 索引有哪些分类嘛? 数据结构:主要分为B树索引和哈希索引。B树索引是一种基于平衡树的索引结构,常见的有B+树、B*树等。哈希索引则是通过哈希函数将关键字映射到桶中,并在桶内进行查找。 物理存储:主要分为聚集索引和非聚集索引。聚集索引的叶子节点存储了整个表的数据行,因此表的物理顺序与索引顺序一致;而非聚集索引的叶子节点仅包含指向数据行的指针,因此索引的物理顺序与表的物理顺序不一定一致。 字段特征:主要分为单列索引和组合索引。单列索引只包含单个字段,适用于单个字段的查询;组合索引则包含多个字段,适用于多个字段的查询。 字段个数:主要分为单值索引和多值索引。单值索引只包含单个值,适用于精确查找;多值索引则包含多个值,适用于范围查找或匹配多个条件的查询。 索引优化的方式有哪些? 前缀索引优化:前缀索引顾名思义就是使用某个字段中字符串的前几个字符建立索引,使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。特别适用于大字符串字段作为索引的场景。 覆盖索引优化:覆盖索引是指 SQL 中 query 的所有字段,在索引 B+Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。 主键索引最好是自增的;主键最好采用自增方式,避免插入新数据时发生页分裂,保持索引结构紧凑。如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。 防止索引失效:用上了索引并不意味着查询的时候会使用到索引,所以我们要避免编写导致索引失效的查询语句,以保证索引得到有效利用。 算法