八股文之Go股大全


[TOC]

Go


slice

切片和数组的区别?

数组: 长度是固定,大小不可改变。数组是值传递

切片:长度可以改变。切片是引用传递。切片有三个属性,指针,长度,容量 。

为什么切片的应用比数组多?

在日常业务上,更需要存储一个可变的序列。业务中有一个确定的长度序列的场景是比较少的,或者说要确定一个序列的长度是会需要做额外的操作损耗性能的。

切片的访问的复杂度?

切片的底层实现是一块连续的内存地址,所以能进行 O(1) 的随机访问元素。 具体的源码都是在编译时编译好(类似于 内存地址=元素类型*下标,不在此处细究。

切片的扩容机制?

当扩容的时候需要扩容的容量是现在容量的 2 倍以上时,会直接扩容需要扩容的容量。否则的话,如果现在的长度(1.16 改成了容量)是 1024 以下时,会直接翻倍扩容。但是如果超过 1024 时,会反复扩容 25 % 直到达到或超过需要扩容的容量

为什么切片底层不用链表?

  1. 设计上,切片的设计上允许随机访问,而随机访问对于链表是不友好的
  2. 容量上,切片的扩容机制无论从小切片是翻倍扩容,还是到大切片不断扩容 25 % 直至扩容到指定值的扩容机制,在容量上已经能覆盖大多数的场景内存上,使用链表会产生内存碎片,并且对于 Go 的垃圾回收算法(三色标记法)在递归根对象寻找可达对象的过程中不友好

map

map 的扩容机制?

分为了翻倍扩容、等量扩容。还有确定了要扩容后,使用的是渐进式扩容去避免性能抖动。

扩容的触发条件有两个,状态装载因子超过 6.5(现在的桶装在的元素超过 80 %),也就是元素太多了,或者溢出桶太多(逻辑是判断现在桶的总数是否超过 map 结构体中的设置值,如果超过说明太多溢出桶),也就是太多元素被删除,元素非常稀疏。 前者会使用翻倍扩容,会申请现有翻倍的空间。当有读操作时仍会访问旧桶,当有插入或者删除操作的时候会将旧桶分流到两个新桶。后者会使用等倍扩容,重新申请一块和现有大小相同的新桶,也是使用渐进式扩容在插入或者删除操作中进行。

map 的存储是有序的吗?

​ 存储结构上由于在扩容的时候会将 key 随机分流到另外两个桶,这导致了 key 的相对位置会发生改变,所以 key 是无序的。 遍历上来说,每次遍历是取一个随机数,随机从一个桶开始遍历。所以每次遍历出来的结果都是不一样的。额外一点是,当未发生扩容前,key 的相对位置是确定的。

map 是线程安全的吗?

不是的,当赋值和删除时是置写操作位。当置了之后有别的协程用任何操作进行来时否都会报错。如果想要 map 线程安全,解决方案一般是用 sync.map 或者 互斥锁+map

map 有缩容机制吗?

go 的 map 没有缩容的机制。map 内部的存储结构是基于拉链法的,里面的元素如果被大批量的删除后,会触发等量扩容。等量扩容时会申请原有大小一样的内存块,渐进式的扩容过去,让原有的 map 中因为很多元素被删除后导致元素排序稀疏的情况经过 rehash 后会排序会变得紧密,减少溢出桶的使用。


sync

sync.map 为什么是线程安全的

因为原有的 map 会在赋值和删除的置写标志,置了后别的协程来做任何操作都会报错。所以引出了 sync.map 解决多协程问题。

sync.map 使用了读写分离来去保证线程安全的,sync.map 的数据结构分为读 map、写 map、还有互斥锁以及一个记录穿透次数的值。具体实现是每个协程来读取时都会先读取读部分的 kv,没有则去读写部分的 kv(操作写部分时都会上锁)。当穿透到写部分的次数大于写部分的长度时就会将写部分同步到读部分并且把写部分清空。所以多协程下一般都会先打到无锁的读部分,这能保证读取性能

sync.mutex 作用

sync.mutex 是一把互斥锁,具体作用是锁住限定区域的代码逻辑,这段区域只能被一个协程占用。具体实现原理是,当一个协程去获取锁时(获取锁是 CAS 看锁的状态位),当获取不到会自选一定次数后加入到队列去休眠,当锁被释放时,正在自旋的协程 + 休眠中的任意一个协程会才能会被唤醒的会去抢锁,这是正常模式。还有一种模式是饥饿模式,由于被唤醒的协程有很大几率是抢不到正在自旋的协程的,所以当有协程超过 1 毫秒获取不到锁时将会进入饥饿模式。进入后会根据上面提到的队列中按照先进先出的模式依次获取锁,新来的协程直接进入队列中等待。当队列中已经清空或者队列中头个协程等待时间小于 1 毫秒后退化为正常模式。

sync.RWmutex 作用

sync.RWmutex 是读写锁,用于解决 reader / wirter 问题(保证一个写协程和其他所有协程互斥的同步问题)。也就是可以读锁可以被多个协程拥有,但是只能一个协程拥有写锁。读写操作互斥、写写操作互斥。当有协程获取写锁时,会阻塞所有新来获取读锁的所有协程,并且会等所有正在拥有读锁的协程释放后才能获取到

sync.singleflight 作用

sync.signleflight 能将对同一个资源访问 的多个请求合并为一个请求。常见的应用场景比如防缓冲击穿。具体的实现是使用了 map 对同一资源访问的请求进行去重,使用互斥锁让当个协程进入临界区后进行资源访问,其他线程阻塞等待资源访问完成后,共同拿到访问资源的结果并返回。


context 有什么作用

  1. 传递上下文。使用 context 传递业务参数是很不推荐的,但是比较常用的用来传递整个链路的 trace id 来作为链路追踪
  2. 协程间同步信息。使用 context 后能在多个协程组成的协程树传递取消信号(通过 Cancel,超时计数器调用 Cancel 等)

goroutine

进程、线程和协程的区别

进程是应用程序的实体,分配操作系统资源的最小单位,拥有自己的内存空间以及堆栈。

线程是内核操作系统调度CPU的基本单位,在进程的内存空间及堆栈上进行执行运行。

协程指的是用户态线程,由用户的应用程序进行调度。

协程的好处

  1. 切换上下文代价小,协程切换时不用进行系统调用,走内核态的指令。
  2. 没有多线程的竞争资源锁问题,多线程竞争资源还会锁内存走系统调用,而协程如果竞争资源直接在当前线程上的进行判断就可以了
  3. 内存占用小,只占用 2K,线程占用 2M

GMP 模型中的 GMP 指的是什么

  1. G 协程
  2. M 线程
  3. P 调度器
  4. 其中 M 可能会自旋,也有可能会休眠,GC 会把一些休眠的线程销毁。

GMP 模型中有什么组件

除开 GMP ,还有 P 的 G 本地队列, G 的全局队列,P 列表(MAXPROCS 决定),M 列表(最大限定 1W,runtime/debug 可以设置)

GMP 模型中为什么需要 P

假设没有 P 会发生什么事情,(没有 P 本地队列,没有 P 全局队列)

  1. 出现资源竞争,由于没有 P 的本地以及全局队列的多级缓存,所以 G 都会放在一起,多个 M 去获取时会出现资源竞争
  2. 协程切换资源消耗大,由于没有 P ,也没有 g0 去负责切换协程堆栈,相当于协程堆栈以及一些运行现场全部都在内核态去维护
  3. 系统调用阻塞时切换成本高,M 会经常被阻塞和解阻塞切换内核态和用户态(类似于进程间切换),消耗大

引入了 P 相当于解决了上面的大部分问题,甚至引入了新的特性去榨干 CPU 的性能

  1. 引入本地队列和全局队列做多级缓存,获 取 G 时都会从本地队列获取,没有竞争,就算去全局队列获取也比较少的几率出现大量 P 去获取,降低了资源竞争的概率
  2. 切换协程堆栈效率提高,使用了 g0 的协程负责去管理协程切换的堆栈以及保护现场的工作,进入内核态去切换的时候少了很多切换指令以及寄存器的使用,甚至引入了 g0 去负责做垃圾回收部分工作的职能
  3. 系统调用的曲线救国方案,当 G 和 M 发生了系统调用时,P 会解绑 M ,带着本地队列的 P 去找空闲的 M 或者新创建的 M 去继续剩下的工作 还引入了「工作窃取」的功能,让基于和 M 绑定的 P 更加灵活的让每个 M 都能够最大限度的运行 task,榨干 CPU

P 调度器的设计策略

  1. work stealing 机制:当 P 本地队列无运行 G 时,会去其他线程绑定的 P 窃取 G ,若其他 P 本地队列也没有时会去 G 全局队列进行窃取
  2. hand off 机制:当 G 因为系统调用阻塞时,P 会和 M 解绑,将 G 和 M 绑定,P 会和空闲的线程进行绑定
  3. 主动让出机制:当 G 占用了 CPU 超过 10MS 会主动让出(sysmon 轮询)
题目 归类 答案/链接 参考
TCP 和 UDP 的区别 TCP 面向连接&无连接,字节流&数据包,全双工&单播广播,可靠&不可靠
TCP 的可靠性指的是什么 TCP 从 RFC-793 中定义可知,可靠性指的是从网络 IO 缓冲中读出来的数据必须是无损的、无冗余的、有序的、无间隔的。翻译过来说要保证的可靠性的话,就要解决数据中出现的损坏,乱序,丢包,冗余这四个问题
TCP 的可靠性如何保证 TCP TCP 从三个维度去确保了可靠性,是「差错控制」「流量控制」「拥塞控制」 差错控制一定的程度上已经保证了基本的可靠性 1. 保证数据无损。 TCP 的传输报文段中使用了校验和 checksum,保证本次传输的报文是无损的 2. 保证有序和不冗余。在传输报文中使用了 seq 字段去解决乱序及冗余问题 3. 保证数据报文们无间隔。在传输报文中使用了 ack 字段,也就是确认应答机制(ACK 延迟确认+累计应答机制) + 超时重传机制(重传机制还细分为快速重传机制(发三个数据包都没有回复))去解决了丢包导致数据出现间隔的问题(流量控制也能够有效的预防丢包的机制之一)。 流量控制(用于接受者)是为了控制发送端不要一味的发送数据导致网络阻塞,和阻止发送方发送的数据不要超过接收方的最大负载,因为超过最大负载会导致接收方丢弃数据而进一步触发超时重传去加重网络阻塞。流量控制的主要手段是通过窗口去做的,每次接受方应答时,都会带一个 window 的字段(三次握手会确定初始的 window 字段),标识了现在接受方能够接受的最大数据量,发送方会根据这个 window 字段发送多个报文直到达到 window 的上限(停等协议的网络传输效率太低),这时应答报文里面的 window 会返回 0,之后发送方会停止发送报文一段时间,然后发送窗口探测的报文去查看接受方是否已经处理好之前发送的数据,更新窗口大小(更新窗口大小的机制也叫做滑动窗口机制) 拥塞控制(用于网络)主要是为了在发生网络拥堵后不进一步触发 TCP 的超时重传进制导致进一步的网络拥堵和网络性能下降。 发送方会自己维护一个拥堵窗口,默认为 1 MSS(最大长度报文段)。控制手段主要有慢启动、拥塞避免、快重传、快恢复。 1. 慢启动。思路是一开始不要传输大量的数据,而是先试探网络中的拥堵程度再去逐渐增加拥塞窗口大小(一般是指数规律增长)。 2. 拥塞避免。拥塞避免思路也和慢启动类似,只是按照线性规律去增加拥堵窗口的大小。 慢启动和拥塞避免一般会配合使用,有个慢启动阈值,这个阈值取值是可配置的。当小于慢启动阈值时则使用慢启动策略,大于则使用拥塞避免策略。当达到拥塞(超时)后,会把拥塞窗口重置为 1,慢启动阈值会设置为拥塞时刻窗口的一半,循环这个过程。但是超时的原因不一定是因为网络拥塞,也有可以发送方那一刻刚好丢包了。所以就有了快重传和快恢复进行优化, 3. 快重传。指的是使发送方尽快重传丢失报文,而不是等超时避免去触发慢启动。所以接受方要收到失序报文后马上发送重复确认以及发送方收到三个重复的接受报文要接受重发。快重传成功后,就会执行快恢复算法。 4. 快恢复。一般是将慢启动阈值和拥塞窗口都调整为现有窗口的一半,之后进行拥塞避免算法,也有实现是把调整为一半后,在增加3个MSS。
简述三次握手的过程 TCP 三次握手的目的在于让双方知道对方具备收和方的功能,交换双方的序列号,双方的数据滑动窗口,为之后的可靠性传输做准备 第一次握手,客户端从 close 变成 syn_send,生成自身的序列号发放给已经在 listen 的服务端 第二次握手,服务端收到后,状态变成 syn_recv,自己也生成序列号,加上客户端发过来的序列号返回过去。这一次握手相互交换同步了对方的序列号,之后的传输中会带上序列号去保证传输有序 第三次握手,客户端收到后,变成监听 established 状态,同时知会服务端已经收到了他的序列号,服务端也会进入监听状态 established 。之后开始传输。
三次握手中发送者和接受者的状态变化 TCP 1. 发送者:close -> sync_sent -> established 2.接受者:lisetn -> sync_recv - > established
为什么三次握手不能两次或四次 TCP 1. 因为 TCP 连接是全双工,客户端和服务端都需要具备收和发的功能,三次的握手就能够让双方都知道对方是具备收和发的功能的。 二次握手,会让客户端知道服务端都会收和发,但缺了一次握手会让服务端不知道客户端可以收。 2. 二次握手有可能会建立多余的连接,因为发起第一次握手时,有可能因为网络原因没有到达服务端处,会重试在发送一次建立连接,而前面的那次因为网络延迟又到达了服务端处,最后的结果会导致建立了两个连接,造成资源的浪费 四次握手,前面提到的,三次握手让双方都知道对方可以收和发,多了一次没有实际任何的意义
TCP 三次握手能否携带数据 TCP 可以,但是第一二次不能携带,因为双方还在建立连接的过程,第三次的时候双方已经基本建立起了连接。第三次握手时可以携带数据。 RFC 793 协议中也有说明。
TCP 的粘包是什么 TCP 首先 tcp 是面向字节流的协议,不存在粘包问题。而问题中的粘包,往往是应用层上体现的,数据读取出来时,因为在 tcp 传输的时候不会按照数据规则切割发送,而应用层也没有根据一定的规则去切割数据,导致展示数据时会出现前后缺失,或者前后有多余的数据段,比如发了 abc ,efg 两行,可能读出来是 ab,cefg。 造成的原因主要有两个(假设这里应用层没有用任何的协议(比如 http)、只是简单的使用 socket 接口进行流式读写) a. tcp 中的 nagle 算法机制。由于 tcp 为了提高网络传输的效率,如果发送方的 socket 缓冲区被写入了数据,是不会马上发送的,而是会等到一定的阈值才会把 socket 缓冲区的数据发送到接受方,这会导致接受方想要收到的数据有一部分先后,一部分后到,在应用层读取完数据后会产生 tcp 粘包的错觉,而实际上只是网络传输数据的先来后到问题 b. 读取数据不及时。由于 tcp 是面向字节流的协议,所以在读取 socket 缓冲区时,有可能有部分数据没有及时读取,导致在应用层上看到的数据是缺了一段,之后来了数据后再次读取又会变成后一段贴在了后面来的数据前面 解决方案 a. 在发送和接收前增加切割规则标识,增加封包和拆包的操作。发送方可以在要传输的数据前加上数据一共有多长,在接受方接收到之后根据数据头的长度去读取。读到了数据有多长后进行展示,未读完则等到数据到齐后才进行展示 关闭 tcp 中的 nagle 算法,关闭后 socket 缓冲区只要有数据就会发出去,但是仍然会出现粘包(因为发送方写进 socket 缓冲区的时候也需要一定时间,期间不断发送出去的话也会导致发送方收到的数据有先后顺序),不过能降低接收方拼数据段的时间,代价是有可能导致网络阻塞
什么是 SYN 攻击 TCP • 什么是 SYN 攻击 SYN 攻击是一种 Dos 攻击,目的是消耗目标服务器的所有可用资源导致无法处理合法的请求。具体实现是不断发送 SYN 数据包,使目标服务器上的半连接队列被占满,导致合法的请求没办法处理。 注:半连接队列用于维护「未完成握手的连接」 解决方案有很多比如: a. 增大半连接队列的容量 b. 加快半连接的过期时间 c.使用 cookies 维护半连接。取消半连接队列,将维护半连接的队列换成使用一个 cookies 去标识,等到三次握手成功后才分配缓冲区资源
简述四次挥手 TCP tcp 四次挥手是关闭 tcp 连接的过程,而因为 tcp 是全双工的,所以客户端和服务端要相互和对方传递自身要关闭的信息,需要两个两回 这里认为发起关闭连接的一端为客户端,另外为服务端 1. 客户端向服务端发起自身要关闭连接的 FIN 请求(客户端的状态变为 FIN_WAIT_1) 2. 服务端接受到了客户端要关闭的请求,同时返回 ACK 应答请求,之后服务端继续把一些要发给客户端的数据处理完发过去(服务端状态变为 close_wait 等待自身关闭,客户端收到后变成 FIN_WAIT_2) 3. 服务端发完了剩余的数据,自身也要开始关闭连接了,向客户端发送自身要关闭的 FIN 请求(服务端状态变成了 LAST_ACK(等待最后的确认应答报文状态),客户端收到后变成了 time_wait 状态,等待一定时间后向服务端发送应答报文) 客户端向服务端发送 ack 应答报文,表示收到服务端的关闭报文(服务端收到后马上变成 close 状态,客户端维持 time_wait 状态 再等待 2MSL 后 close)
tcp 四次挥手为什么要四次 TCP a. tcp 是全双工的连接,双方都需要告知对方关闭,告知时都需要经过一个来回 第二次和第三次挥手时,服务端的 ack + fin 有时不能一起回复。因为第一次挥手时,客户端告知了服务端要关闭连接,但是服务端可能还有数据要发给客户端处理,所以先回复一个 ack 的应答,再服务端主动关闭连接发起 fin 之前,还有剩余的数据包需要发送过
tcp 四次挥手中的第二次和第三次能不能合并 TCP 可以合并 tcp 中第一二次挥手是客户端向服务端传达自己要关闭的信息,第三四次是服务端向客户端传达自己要关闭的信息。其中的第二次和第三次期间,可能服务端还有数据要发给客户端,所以,要等到服务端没有数据发送后才会发送第三次挥手的信息。但是如果服务端接到第二次挥手的通知时,如果没有要继续发送的信息,也是可以合并第二次和第三次回收的请求一起发给客户端
tcp 四次挥手中双方的状态 TCP a. 发起者:established -> fin_wait_1 -> fin_wait_2 - > time_wait -> close 接受者:established -> close_wait -> last_ack -> close
简述 tcp 四次挥手中的 timewait 有什么作用 TCP timewait 是四次挥手中的客户端侧状态之一。 timewait 状态作用主要是防止旧 TCP 连接的数据包乱串,以及保证 TCP 连接能够正常关闭 1.防止旧 TCP 连接的数据包乱串。如果没有 timewait 的话,四次挥手后马上关闭了,但是有可能在四次挥手第一次发起的挥手包,在关闭当前连接后端口马上又被复用了,这会导致上一次连接的包串过来了,所以 timewait 时间还是需要的,并且还是需要 2MSL,一个数据包的最大存活时间。 2.保证 TCP 连接在服务端侧能够正常关闭。如果没有 timewait 的话,三次挥手后客户端马上关闭了连接,而有可能第三次挥手的包丢失了,导致服务端一直处于 LAST_ACK 状态,没办法正常关闭。有 timewait 后,就能够让服务端再次超时触发一次 FIN 进行最后一次挥手
客户端有大量 timewait 是什么原因 TCP ○ 原因 在高并发的场景下,存在大量 time_wait 状态的 TCP 连接是正常的,是因为 1. 有大量 tcp 短连接的存在。高并发下建立了许多 tcp 连接,完成业务处理后马上断开了连接,导致有大量处于 time_wait 状态的 tcp 连接 2. tcp 四次挥手的关闭机制。因为存在「延迟的数据包数据」和「最后一次握手可能会发生丢包」这两种情况,所以有 time_wait 的机制去保证 tcp 连接能够正常关闭 ○ 危害 大量 time_wait 存在可能导致连接数被用尽,无法创建新链接 ○ 解决方案 1. 客户端处,尽量建立 tcp 长连接进行复用 服务端处,允许 time_wait 状态的连接可以被复用和减少 time_wait 的时间(但是可能会存在旧连接数据包乱串和旧连接没有正常关闭的问题)
HTTP 是一个无状态协议,无状态指的是什么 HTTP 无状态指的是每次 HTTP 请求是相互独立隔离的,和其他 HTTP 请求没有上下文的关系。
为什么需要 https HTTPS • 为什么需要 https 因为 HTTP 是明文传输,在信息传输上没有安全保证。所以可能导致有三个问题 a. 篡改风险。可能会被截获并篡改内容,比如植入非法广告。 b. 冒充风险。截获后修改传输内容,比如修改传输内容中的一些 url,冒充成对应的 url 网站对用户进行欺骗 c.窃听风险。由于是明文传输的,可能会被截获并被监听。比如监听一些用户敏感信息,比如银行卡号,账号密码
https 和 http 的区别 HTTPS a. 传输信息是否安全。http 是明文传输,而 https 是经过加密进行传输的(还涉及到 CA 证书和一系列的加密算法) b. 连接建立的复杂度。http 只需要经过 TCP 三次握手建立了 TCP 连接即可传输,而 https 还除此之外还需要进行 SSL/TLS 握手 c. 默认端口不一。http 默认是 80,https 默认是 443
https 用是对称加密还是非对称加密,以及简述建立连接的过程 HTTPS https 在证书验证阶段使用的是非对称加密,在数据传输阶段使用对称加密。因为非对称加密效率低,所以在数据传输阶段不使用。 https 建立连接的过程是 a. 客户端向服务端发起 HTTPS 请求 b. 服务端返回 CA 证书给客户端(包含该阶段的非对称加密的公钥) c. 客户端验证 CA 证书是否合法 d. CA 证书验证合法后,客户端生成随机数 e. 使用公钥加密随机数后给服务端 f. 服务端使用私钥进行解密 g. 使用随机数构造对称加密算法 之后相互进行对称加密进行传输

文章作者: 千羽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 千羽 !
评论
  目录