面试准备
2021-08-06
每次面试都需要做好充分的准备,这篇文章就是作为面试前准备的复习文档,每次面试前后都会不断优化迭代此文档,供以后使用
Golang #
go协程 #
- 协程之间的同步的几种方法
互斥锁: Mutex
channel (生产消费模型)
WaitGroup (add, done和wait)
- golang怎么管理调度协程的?
基于GPM模型实现
goroutine 并非传统意义上的协程,现在主流的线程模型分三种:内核级线程模型、用户级线程模型和两级线程模型(也称混合型线程模型),传统的协程库属于用户级线程模型,而 goroutine 和它的 Go Scheduler 在底层实现上其实是属于两级线程模型。
在 Go 语言中,每一个 goroutine 是一个独立的执行单元,相较于每个 OS 线程固定分配 2M 内存的模式,goroutine 的栈采取了动态扩容方式, 初始时仅为2KB,随着任务执行按需增长,最大可达 1GB(64 位机器最大是 1G,32 位机器最大是 256M),且完全由 golang 自己的调度器 Go Scheduler 来调度。此外,GC 还会周期性地将不再使用的内存回收,收缩栈空间。 因此,Go 程序可以同时并发成千上万个 goroutine 是得益于它强劲的调度器和高效的内存模型。
//-----------------------------------------------------------------------
GPM
G: 表示 Goroutine,每个 Goroutine 对应一个 G 结构体,G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重用。G 并非执行体,每个 G 需要绑定到 P 才能被调度执行。
P: Processor,表示逻辑处理器, 对 G 来说,P 相当于 CPU 核,G 只有绑定到 P(在 P 的 local runq 中)才能被调度。对 M 来说,P 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P 的数量决定了系统内最大可并行的 G 的数量(前提:物理 CPU 核数 >= P 的数量),P 的数量由用户设置的 GOMAXPROCS 决定,但是不论 GOMAXPROCS 设置为多大,P 的数量最大为 256。
M: Machine,OS 线程抽象,代表着真正执行计算的资源,在绑定有效的 P 后,进入 schedule 循环;而 schedule 循环的机制大致是从 Global 队列、P 的 Local 队列以及 wait 队列中获取 G,切换到 G 的执行栈上并执行 G 的函数,调用 goexit 做清理工作并回到 M,如此反复。M 并不保留 G 状态,这是 G 可以跨 M 调度的基础,M 的数量是不定的,由 Go Runtime 调整,为了防止创建过多 OS 线程导致系统调度不过来,目前默认最大限制为 10000 个。
每个 P 维护一个 G 的本地队列;
当一个 G 被创建出来,或者变为可执行状态时,就把他放到 P 的本地可执行队列中,如果满了则放入Global;
当一个 G 在 M 里执行结束后,P 会从队列中把该 G 取出;如果此时 P 的队列为空,即没有其他 G 可以执行, M 就随机选择另外一个 P,从其可执行的 G 队列中取走一半。
当通过 go 关键字创建一个新的 goroutine 的时候,它会优先被放入 P 的本地队列。为了运行 goroutine,M 需要持有(绑定)一个 P,接着 M 会启动一个 OS 线程,循环从 P 的本地队列里取出一个 goroutine 并执行。执行调度算法:当 M 执行完了当前 P 的 Local 队列里的所有 G 后,P 也不会就这么在那划水啥都不干,它会先尝试从 Global 队列寻找 G 来执行,如果 Global 队列为空,它会随机挑选另外一个 P,从它的队列里中拿走一半的 G 到自己的队列中执行。
用户态阻塞/唤醒
当 goroutine 因为 channel 操作阻塞时,对应的 G 会被放置到某个 wait 队列(如 channel 的 waitq),该 G 的状态由_Gruning 变为 _Gwaitting ,而 M 会跳过该 G 尝试获取并执行下一个 G,如果此时没有 runnable 的 G 供 M 运行,那么 M 将解绑 P,并进入 sleep 状态;当阻塞的 G 被另一端的 G2 唤醒时(比如 channel 的可读/写通知),G 被标记为 runnable,尝试加入 G2 所在 P 的 runnext,然后再是 P 的 Local 队列和 Global 队列。
- 主协程如何等其余协程完再操作
用channel控制
用sync.WaitGroup
- golang内存泄漏有哪些情况?
外部资源是不可以GC掉的,如:打开文件,数据库,http请求等
短时间内实例化过多对象等
- channel的使用
- go指针和c++指针的区别
c++指针可以做算术运算
- C++和golang的区别
- 数组和slice的区别
- map的底层实现
散列表,即哈希表
- 多协程读写map注意事项
加锁
- slice是否可以作为map键
map key 必须是可比较的类型,语言规范中定义了可比较的类型:boolean, numeric, string, pointer, channel, interface, 以及仅包含这些类型的struct和array 。不能作为map key的类型有:slice,map, function。
- new和make的区别
make针对slice,map, chan
new返回的是指向Type的指针。 make直接返回的是Type类型值
总结:
new会分配结构空间,并初始化为清空为零,不进一步初始化
new之后需要一个指针来指向这个结构
make会分配结构空间及其附属空间,并完成其间的指针初始化
make返回这个结构空间,不另外分配一个指针
- 面向对象通过什么实现(组合/继承/接口)
golang 使用组合的方式实现继承 ,从而实现面向对象
- defer输出顺序
FILO,后进先出,即下到上
最先声明的最后输出
- select用于干什么?
用于处理异步IO问题
- context包的用途
用于控制goroutine的生命周期。
当一个计算任务被goroutine承接了之后,由于某种原因(超时,或者强制退出)
我们希望中止这个goroutine的计算任务,那么就用得到这个Context了。
- sync.pool
为了复用已经使用过的对象,来达到优化内存使用和回收的目的
- 重写父类函数
- waitGroup死锁问题
- 项目进程间通信的协议HTTP还是socket
- s为字符串,是s[0]表示啥?
如果 s 是一个字符串,那么s[0] 表示 s 的第一个 byte,长度固定:1个字节。
- 同时广播消息给10万个socket
gin源码解读 #
Linux #
- 用什么命令查看Linux下一个端口的状态
答:
lsof -i :端口号 | grep "(LISTEN)" // 查看对应端口的占用情况
netstat -nultp // 查看所有端口的占用情况
netstat -anp | grep 端口号 // 查看对应端口的占用情况
- 怎么查找/替换指定目录下所有文件的指定内容
sed -i s/yyyy/xxxx/g `grep yyyy -rl --include="*.txt" ./`
MySQL #
- MySQL事务
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
事务用来管理 insert,update,delete 语句
// -----------------------------------------------------
原子性:
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- MySQL主键索引和辅助索引的区别
- inonedb为啥用b+树
为什么说B+树比B树更适合数据库索引?
1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
PS:我在知乎上看到有人是这样说的,我感觉说的也挺有道理的:
他们认为数据库索引采用B+树的主要原因是:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。
- 索引分析
- MySQL联合索引
- 分表(水平分表,垂直分表)
- 有一张表,有商店ID,既要按照商店查询,也要按照商品查询。求分表策略。
- MySQL深翻页问题如何解决, limit m, n
MongoDB #
- MongoDB索引
- 聚合
Redis #
Redis缓存 #
- 缓存穿透
- 缓存雪崩
- 缓存一致性问题
Redis队列 #
哈希表实现原理(Redis的哈希表扩容) #
Redis分布式锁 #
Redis zrange复杂度 #
TiDB #
RabbitMQ #
Elasticsearch #
Etcd #
Nginx #
TCP/IP #
- 与udp的区别
tcp传输的是数据流,而udp是数据包,tcp会进过三次握手,udp不需要
HTTP #
- http完整请求
- 错误码
400 bad request,请求报文存在语法错误
401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
403 forbidden,表示对请求资源的访问被服务器拒绝
404 not found,表示在服务器上没有找到请求的资源
WebSocket #
- websocket 不支持并发写
protobuf #
- protobuf和json比较的优势
rpc #
- gRpc的具体内容
- rpc通信的协议是HTTP还是socket
shell #
数据结构 #
红黑树 #
B树的性质 #
定义任意非叶子结点最多只有M个儿子,且M>2;
根结点的儿子数为[2, M];
除根结点以外的非叶子结点的儿子数为[M/2, M];
每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
非叶子结点的关键字个数=指向儿子的指针个数-1;
非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
所有叶子结点位于同一层;
B+树的性质(下面提到的都是和B树不相同的性质)
#
非叶子节点的子树指针与关键字个数相同;
非叶子节点的子树指针p[i],指向关键字值属于[k[i],k[i+1]]的子树.(B树是开区间,也就是说B树不允许关键字重复,B+树允许重复);
为所有叶子节点增加一个链指针;
所有关键字都在叶子节点出现(稠密索引). (且链表中的关键字恰好是有序的);
非叶子节点相当于是叶子节点的索引(稀疏索引),叶子节点相当于是存储(关键字)数据的数据层;
更适合于文件系统;
其他 #
解耦 (场景:同时生成很多张照片)【降低两者之间的联系】
#
稳定排序和不稳定排序 #
RBAC模型的分析与实现 #
RBAC全称为基于角色的访问控制模型(Role-based access control),在该模型中,通过让权限与角色关联来实现授权,给用户分配一系列的角色来让注册用户得到这些角色对应的权限,使系统权限分配更加方便。我们在一般的大型系统开发中,几乎都要考虑权限控制的相关问题,诸如:系统资源权限、用户行为权限等,此类问题的实现通常需要数据库在设计时做出考虑,经过IT届各种大佬的长期实践后,总结出了较为通用的针对权限控制的RBAC设计模型。RBAC不是某种技术框架,它类似于设计模式,是当下流行的一种数据库(表)设计的思想,它能通过为用户分配角色来实现权限的分配,只要你的数据库设计满足这种思想便可说你的数据库使用的是RBAC模型。
面试题 #
-
40亿个不同的数排序
桶排序,一个比特位一个桶
-
单链表反转
-
给定a,b两个文件,各存放50亿个url,每个url各占64字节,内存限制4G,让你找出a, b文件共同url