柚子快报激活码778899分享:Golang八股文面试题

http://yzkb.51969.com/

1、golang 中 make 和 new 的区别?(必问)

1.作用变量类型不同,new可以给任意类型分配内

存,make给slice,map,channel分配内存.

2.返回类型不一样,new返回指向变量的指针,

make返回变量类型本身;

3.内存操作不一样,new分配空间后内存被清零。

make 分配空间后会进行内存初始化;

2、数组和切片的区别 (必问)

1.内部结构

数组在内存中是一段连续的内存空间,元素的类型

和长度都是固定的.切片在内存中由一个指向底层

数组的指针,长度和容量组成,长度表示切片当前

包含的元素个数,容量表示切片可以容纳的最多元

素个数

2.长度

数组的长度在创建时指定后不能变更.切片的长度

根据需要自由调整,可以动态扩展或收缩。

3.使用方式

数组在使用时需要明确指定下标访问元素,不能动

态生成.切片可以使用append函数向其末尾添加

元素,可以使用copy函数复制切片,也可以使用

make函数创建指定长度和容量的切片

3、for range 的时候它的地址会发生变化么?for 循环遍历 slice 有什么问题?

1.地址没有发生变化,循环出来的变量地址共用

2.使用 for range遍历切片或数组时,每次迭

代返回元素的副本,而不是元素地址.每次循环出

的元素的地址都是相同的即最初创建的.迭代变

量会被重复使用,而不是创建新变量,这有助于减

少内存分配和提高性能.

//错误的写法

for _, v := range studs {

gl[v.Name] = &v

}

//正确的写法

for _, v := range studs {

temp := v

gl[v.Name] = &temp

}

4、go defer,多个defer的顺序,defer 在什么时机会修改返回值?defer recover 的问题?(主要是能不能捕获)

1.defer用于延迟一个函数调用,确保在函数执

行结束后释放资源或执行清理操作.多个defer

按照后进先出的顺序执行,即最后一个defer语

句先执行,倒数第二个在倒数第一个之后执行.

2.defer中的函数是在包含它的函数执行完毕之

后才执行.

func example() (result int) {

defer func() {

result += 10

}()

return 5

}

最终的返回值是15。

3.defer与recover通常一起使用,用于处理函

数中的错误.recover只能捕获在同一个

goroutine中发生的panic,而且必须在defer

中调用.如果recover在没有发生panic时调用,

它会返回nil

4.defer和recover是处理资源释放和错误恢

复的重要机制。

5、 uint 类型溢出

通常发生在大量运算,大量循环,大数运算时,当

uint类型的值超过其最大值时,便发生溢出,然

后从该类型最小值开始循环,解决方案:

1.使用更大数据类型:如用uint64替换uint32

2.添加溢出检查:检查结果是否小于任一操作数

3.使用math/big包:对于非常大的数值,可以使

用math/big包中的Int类型,该类型可以处理任

意大小的数值,但是运算速度会慢一些。

6、介绍 rune 类型

1.相当int32,用来区分字符值和整数值,用来处

理unicode或utf-8字符

2.byte相当int8,用来处理ascii字符

3.golang中的字符串底层是通过byte数组实现,

中文字符在unicode下占2个字节,在utf-8占3个

字节,golang默认编码是utf-8

func main() {

str := "米虫 is cool"

fmt.Println("STR LEN - ", len([]rune(str)))

}

7、 golang 中解析 tag 是怎么实现的?反射原理是什么?

计算机程序在运行时(Run time)可以访问、检测

和修改它本身状态或行为的一种能力或动态知道给

定数据对象的类型和结构,并有机会修改它。

8、调用函数传入结构体时,应该传值还是指针?

1.结构体的大小:如果结构体非常大,使用指针传

递会更有效率,因为这样只复制指针值(一般是8字

节)而不是复制整个结构体。

2.是否需要修改原始结构体:如果需要在函数中修

改原始结构体,应该使用指针传递。

9、Slice 介绍

type slice struct {

array unsafe.Pointer

len int

cap int

}

*array 指向底层数组的指针

len 切片长度

cap 切片容量

2.make创建slice时可以指定其长度和容量,底

层会分配一个数组,数组的长度即容量.

slice = make([]int,5,10)表示该slice长

度为5,容量为10.使用数组创建slice时slice

与原数组共用一部分内存。

3.使用append向slice追加元素时,若slice空

间不足则发生扩容,扩容会重新分配一块更大的内

存,将原slice拷贝到新slice,再将数据追加进

去然后返回新slice.扩容操作只针对容量,扩容

后的slice长度不变

4.使用copy拷贝切片时,会将源切片的数据逐个

拷贝到目的切片指向的数组中,拷贝数量取两个切

片长度的最小值.copy不会发生扩容.

5.slice := array[start:end],该写法新生

成的切片没指定容量,新切片的容量从start开始

到array结束(注意不是到end)

slice := array[start:end:cap],其中的

cap为新切片的容量

6.创建切片时根据实际需要预分配容量,尽量避免

追加元素过程中发生扩容,这样有利于提升性能.

10、slice扩容

1.切片扩容的条件:

使用append函数向切片追加元素时,如果追加元

素后的切片长度超过了容量,切片就会进行扩容

2.切片扩容机制:

go1.18之前,若原切片容量小于1024的时候,切

片先两倍扩容,若两倍扩容后的容量不够,直接以

切片需要的容量作为新切片容量,大于1024时,

会反复地增加25%,直到新容量超过所需要的容量

go1.18之后,临界值换成了256,原切片容量小于

256和前面相同,大于256时切片的容量每次增加

(oldcap+3*256)/4,相对于1.18之前可以更

平滑过渡

10、go struct 能不能比较?

结构体(Struct)是否可以比较取决于其字段。

如果结构体的所有字段都是可比较的那么这个结

构体也是可比较的.意味着可以使用==或!=运算

符来比较两个结构体变量。

11、context 结构是什么样的?

type Context interface {

Done() <-chan struct{}

Err() error

Deadline()(deadline time.Time,ok bool)

Value(key interface{}) interface{}

}

4个方法,是幂等的.即连续多次调用同一个方法,

得到的结果都是相同的。

1.Done()返回一个channel,表示context是

否被取消或超时的信号:当该channel被关闭时,

说明context被取消了.该channel是只读类型,

另外读一个关闭的channel会读出对应类型的零

值.而源码中并没有向这个channel里发送值.因

此在子协程里读这个channel,除非被关闭,否则

读不出任何数据.利用这一点,子协程从channel

里读出值(零值)后,则通知context相关函数停

止工作,然后返回

2.Err()返回一个错误,表示channel被关闭的

原因例如是被取消,还是超时。

3.Deadline()方法返回一个time.Time和ok

分别表示当前Context截止时间,是否有结束时间,

4.Value()获取之前设置的key对应的value。

12、context 使用场景和用途?(基本必问)

context提供在不同goroutine之间传递取消信

号,截止时间,截止日期等元数据的方法.context

主要用于在大规模的并发或分布式系统中进行取消

操作、截止时间管理、跟踪请求以及传递其他请求

范围的数据。context的常见使用场景和用途:

1.控制请求的超时和取消:context包允许在请求

超时或取消时通知Goroutine停止执行,以避免资

源泄漏和长时间阻塞.通过使用

context.WithTimeout或

context.WithCancel创建一个带有超时或取消

功能的上下文,可以在超时或取消时终止相关操作

2.传递请求范围的值:context包允许在请求范

围内传递值.这对于在请求处理流程中共享上下文

信息非常有用,通过使用 context.WithValue

创建一个带有键值对的上下文,可以将该值与上

下文关联,并在整个请求处理过程中访问这些值。

3.多个Goroutine间传递上下文信息:context

包允许在多个Goroutine间传递上下文信息.当

启动一个Goroutine并希望它能访问请求的上下

文时,可将上下文作为参数传递给Goroutine或

使用context.WithValue创建一个新的上下文。

4.中止并发操作:当需要中止一组并发操作时,可

使用context包实现.通过创建一个带取消功能

的上下文,并将该上下文传递给多个Goroutine,

可在需要时取消或终止这些Goroutine的执行

13、channel 是否线程安全?锁用在什么地方?

channel本身是线程安全.在Go的并发模型中,

channel用于不同Goroutine之间数据通信,

当一个goroutine向channel发送数据时,直

到另一个goroutine接收这个数据之前,该

goroutine将会被一直阻塞.这种机制保证了

channel的数据在Goroutine间传递时的安全

性.在对buf中的数据进行入队和出队操作时,为

当前channel使用了互斥锁,防止多个线程并发

修改数据

14、go channel 的底层实现原理 (数据结构)

type hchan struct {

//channel 中元素个数

qcount uint

//channel 缓冲区的大小

dataqsiz uint

//channel 缓冲区数据指针

buf unsafe.Pointer

//channel 发送操作处理到的位置

sendx uint

//channel 接收操作处理到的位置

recvx uint

//channel 中操作的元素大小

elemsize uint16

//channel 中操作的元素类型

elemtype *_type

// 是否已close

closed uint32

//缓冲区不足而阻塞等待接受数据的

//goroutine队列

recvq waitq

//缓冲区不足而阻塞等待发送数据的

//goroutine队列

sendq waitq

// 互斥锁,保护所有字段

lock mutex

}

1.dataqsiz(缓冲区大小)对于无缓冲的

channel,dataqsiz字段的值为0,表示没有分

配数据缓冲区,数据的发送和接收是直接进行的,

需要发送和接收Goroutine同时准备好,否则会

阻塞.对于有缓冲的channel,dataqsiz字段的

值表示分配的数据缓冲区的大小.缓冲区的大小

决定了channel可以存储的元素数量.当缓冲区

填满时,继续向channel发送数据会阻塞发送

Goroutine,当缓冲区为空时,尝试从channel

接收数据会阻塞接收 Goroutine。

2.buf(缓存区指针)存储channel实际数据的

缓冲区.对于无缓冲的channel,buf字段为nil,

表示没有分配缓冲区,数据发送和接收操作是直

接阻塞的.对于有缓冲的channel,buf字段指向

分配的内存地址,用于存储元素值

3.recvq(接收队列)用于存储等待接收数据的

Goroutine队列.当一个Goroutine尝试从空

channel接收数据时,它会被放置在recvq中等

待其他Goroutine向channel发送数据.一旦

有数据可被接收,被阻塞的Goroutine就会从

recvq中被唤醒,并继续执行接收动作

4.sendq(发送队列)用于存储等待发送数据的

Goroutine队列.当一个Goroutine尝试向已

满的channel发送数据时,它会被放置在sendq

中等待其他Goroutine从channel接收数据.

一旦channel有足够的空间可以接收数据,被阻

塞的Goroutine就会从sendq中被唤醒,并继续

执行发送动作。

5.waitq(等待队列)是一个用于存储等待在

channel上进行发送或接收操作的Goroutine

队列.当一个Goroutine尝试向已满的channel

发送数据或从空的channel接收数据时,它会被

放置在waitq中等待其他Goroutine执行相反

的操作,从而使发送或接收操作能够进行.一旦

有对应的发送或接收操作完成,等待的

Goroutine就会从waitq中被唤醒,并继续执行

15、 nil、关闭的 channel 再进行读、写、关闭会怎么样

1.nil channel:未初始化channel,未经make

2.closed channel:执行了closed的channel

3.对nil channel的读写会永久block

4.关闭nil channel,发生panic

5.向closed channel写入会发生panic

6.从closed channel读取仍然可以读取剩余的

数据,直到数据全部读取完成立即读出零值

16、channel的主要用途?

1.向通道(channel)发送数据和从通道读取数据

是Go语言实现并发通信的重要机制。

2.通道阻塞的机制确保了goroutine之间的同步

和通信

3.同步机制,数据共享,并发模式简化,信号通知,

流程控制,优雅地处理结束场景

17、map 使用注意的点,并发安全?

1.map是引用类型,如果两个map同时指向一个底

层,那么一个map的变动会影响到另一个map。

2.map的零值是nil,对nil map进行任何添加元

素的操作都会触发运行时错误(panic)因此,使用

前必须使用make先创建map

m := make(map[string]int)。

3.map在遍历时结果顺序可能不一样

4.map进行的所有操作,读取,写入,删除,都是不

安全的

5.map不是并发安全的.并发情况下,对map的读

和写操作需要加锁,否则可能因为并发操导致程

序崩溃.并发环境下使用map,可使用sync包中

的sync.RWMutex读写锁,或者使用sync.Map

6.map哈希函数的选择:在程序启动时,会检测

cpu是否支持aes,如果支持,则使用aes hash

否则使用 memhash

18、map 循环是有序的还是无序的?

for range循环map是无序的,因为map扩容⽽

重新哈希时,各键值项存储位置都可能会发生改

变,顺序自然也没法保证了,所以官方为了避免依

赖顺序,直接打乱处理.在进行循环遍历的时候,

生成了一个随机数作为遍历开始的位置,可以for

range循环map取出所有的key,sort.Strings

(keys)排序所有的keys再循环所有的keys.

19、 map 中删除一个 key,它的内存会释放么?

1.如果删除的元素是值类型,如int,float,

bool,string以及数组和struct,map的内

存不会自动释放

2.如果删除的元素是引用类型,如指针,slice,

map,chan等,map的内存会自动释放,但释放的

内存是子元素应用类型的内存占用

3.将map设置为nil后,内存被回收

20、怎么处理对 map 进行并发访问?有没有其他方案?

1.对整个map加上读写锁sync.RWMutex虽然

解决问题但是锁粒度大,影响性能

2.1操作会导致整个map被锁住,导致性能降低

所以提出了分片思想,将一个map分成几个片,

按片加锁。第三方包实现:

github.com/orcaman/concurrent-map

3.标准库中的sync.Map是专为append-only

场景设计的。sync.Map在读多写少性能比较好

否则并发性能很差。

21、 nil map 和空 map 有何不同?

1.初始化状态:

Nil Map:没有初始化分配内存的map,默认值为

nil `var m map[keyType]valueType`

Empty Map:已分配内存但没有任何键值对的map

`m := make(map[keyType]valueType)`

2.写入操作:

Nil Map:不能向nil map写入键值对.否则会

导致运行时错误(panic)

Empty Map:可以向空map中安全地写入键值对.

3.读取操作:

Nil Map 和 Empty Map:都可以安全地进行

读取操作.如果键不存在,都会返回值类型的零值

4.长度:

Nil Map 和 Empty Map:两者的长度都是 0

22、map 的数据结构是什么?是怎么实现扩容?

type hmap struct {

// 元素数量

count int

//状态标识,比如被写,被遍历等

flags uint8

//桶(bmap)数量-2^B个

B uint8

//溢出的bucket个数

noverflow uint16

//哈希种子,增加哈希函数的随机性

hash0 uint32

//指向数组buckets的指针

buckets unsafe.Pointer

//扩容时保存原buckets的指针

oldbuckets unsafe.Pointer

//表示扩容进度-已迁移的个数

nevacuate uintptr

extra *mapextra

}

1.触发扩容有两种情况:

第一种:负载因子超过了默认值6.5,就是正常元素

过多造成的扩容-增量扩容

负载因子=元素数量除桶数量

第二种:overflow的bucket数量过多,就是因为

元素不断的进行增删造成溢出桶很多元素很少,没

有满足负载因子的默认值,但效率很低--等量扩容

2.golang通过拉链发解决哈希冲突(开放寻址法)

3.map扩容是渐进式的,即整个扩容过程拆散在每

一次的写操作里面,这样的好处是保证每一次map

的读写操作时间复杂度都是稳定的。

23、map 取一个 key,然后修改这个值,原 map 数据的值会不会变化

在Go语言中,map为引用类型,所以在修改map内

的值时,原数据也会随之变化,因此当你通过一个

key 取出一个值并对其进行修改时,将会影响到

原map 中该key 对应的值。

24、什么是 GMP?(必问)调度过程是什么样的?

1.G - Goroutine也就是协程,是用户态

轻量级线程,一个goroutine大概需要2k

内存,在 64 位的机器上,可以轻松创建

几百万goroutine

2.M - Machine Thread,也就是操作系

统线程,go runtime 最多允许创建1万个

操作系统线程,超过了就会抛出异常

3.P - Processor逻辑处理器,默认数量

等于cpu核心数,通过环境变量GOMAXPROCS

改变其数量

4.全局队列(GlobalQueue)存放等待运行

的G,P还有个本地队列(也称为LRQ):存放等

待运行的G,但G的数量不超过256个。新建的

goroutine优先保存在P的本地队列中,如果

P的本地队列已满,则会保存到全局队列中

5.P包含了运行G所需要的资源,M想要运行

goroutine必须先获取P然后从P的本地队列

获取Goroutine,P队列为空的时候,M也会

尝试从全局队列拿一批Goroutine放到P的

本地队列,或者从其他P的本地队列偷取一半

放到自己P的本地队列,M不断通过P获取

Goroutine并执行,不断重复下去。如果M

阻塞或者不够了,会创建新的M来支持。比如

所有的M都被阻塞住了,而P中还有很多的就

绪任务,调度器就会去寻找空闲的M。找不

到的话,就会去创建新的M,总而言之一个M

阻塞P就会去创建或者切换另一个M,

25、进程、线程、协程有什么区别?(必问)

1.进程:是应用程序的启动实例,每个进程

都有独立的内存空间,不同的进程通过管道,

信号量,共享内存等方式来通信。

2.线程:从属于进程,每个进程至少包含一

个线程,线程是CPU调度的基本单位,多线程

之间可以共享进程的资源,并通过共享内存

等方式来通信。

3.协程:轻量级线程,与线程相比,协程不受

操作系统的调度,协程的调度器由用户程序

提供,协程调度器按照调度策略把协程调度

到线程中运行

26、抢占式调度是如何抢占的?

操作系统负责线程的调度,Go的runtime负

责goroutine的调度.现代操作系统调度线

程都是抢占式,不依赖代码主动让出CPU,或

者因为IO,锁等待而让出,这样会造成调度的

不公平.基于经典时间片算法,当线程的时间

片用完之后,会被时钟中断,调度器会将当前

线程的执行上下文进行保存,然后恢复下一

个线程的上下文,分配新的时间片令其开始

执行.这种抢占对于线程本身是无感知的,系

统底层支持,不需要开发人员特殊处理.基于

时间片的抢占式调度有个明显的优点,能够

避免CPU资源持续被少数线程占用,从而使其

他线程长时间处于饥饿状态.goroutine的

调度器也用到了时间片算法.只是整个Go程序

都是运行在用户态的,所以不能像操作系统那

样利用时钟中断来打断运行中的goroutine.

也得益于完全在用户态实现,goroutine的调

度切换更加轻量。

27、怎么控制并发数?如何优雅的实现一个 goroutine 池

1.信号量(Semaphore),通道(Channel),

上下文(Context)

2.协程池的实现:

1>定义池结构:需要定义一个结构体来表示

goroutine池,该结构体通常会包含一个任

务队列(用于存储待执行的任务)和一个信号

量(用于控制同时运行的goroutine数量).

2>定义任务类型:通常是一个函数签名,例如

type Task func().

3>创建池:实现一个函数来创建新的

goroutine池,包括初始化任务队列和启动

一定数量的goroutine来处理任务.

4>任务分发:编写逻辑来接收新任务并将它

们加入任务队列.

5>执行任务:goroutine从队列中获取任

务并执行.

6>优雅关闭:提供一种方式来优雅地关闭

goroutine池,等待所有正在执行的任务

完成后再退出.

3.go-playground/pool, ants(推荐)

29、Go 如何实现原子操作?

1.原子操作就是不可中断的操作.原子操作执行时,

CPU绝对不会再去执行其他针对该值的操作

2.包sync/atomic提供了原子操作(Load读取

Store写入)

原子操作与互斥锁的区别

1)互斥锁是一种数据结构,用来让一个线程执行

程序的关键部分,完成互斥的多个操作。

2)原子操作是针对某个值的单个互斥操作。

30、悲观锁、乐观锁是什么?

1.悲观锁:当要对数据库中的一条数据进行修改

时,为了避免同时被其他人修改,直接对该数据进

行加锁以防止并发.行锁,表锁等,读锁,写锁等,

都是在做操作之前先上锁.适合多写场景.Mutex

2.乐观锁:认为更新数据不会造成冲突,但更新的

时候会检测数据是否被修改,可以使用版本号机制

和CAS算法实现.乐观锁适用于读多写少的场景,

可以提高程序的吞吐量

31、Mutex 有几种模式?

1.正常模式:sync.Mutex的默认模式.等待获

取锁的goroutines会形成一个队列.当锁释放

时,队列中的第一个goroutine不一定获得锁,

其他新到的goroutines也有可能先获得锁.

这种情况可能导致某些goroutines长时间得

不到锁产生"饥饿"现象

2.饥饿模式

在饥饿模式下Mutex的拥有者将直接把锁交给队

列第一个goroutine,新来的goroutine不会参

与获取锁,它会加入到等待队列的尾部.

3.下面情况,会把这个Mutex转换成正常模式:

(1)当一个goroutine等待锁时间小于1毫秒.

(2)当前队列只剩下一个goroutine的时候.

32、goroutine 的自旋

协程加锁时,如果当前mutex的state字段的

locked位为1,说明已有其他协程持有该锁,尝

试加锁的协程并不是马上转入阻塞,而是持续探

测locked位是否变为0,

即为自旋.自旋的条件如下:

1)还没自旋超过 4 次,

2)多核处理器,

3)GOMAXPROCS > 1,

4)p 上本地 goroutine 队列为空。

mutex会让当前的goroutine去空转CPU,在空转

完后再次调用CAS方法去尝试性的占有锁资源,直

到不满足自旋条件,则最终会加入到等待队列里。

33、go三色标记法(必问)

1.起初所有的对象都是白色的;

2.从根对象出发扫描所有可达对象,标记为灰色,

放入待处理队列;

3.从待处理队列中取出灰色对象,将其引用的对

象标记为灰色并放入待处理队列中,自身标记为

黑色;

4.重复步骤(3),直到待处理队列为空,此时

白色对象即为不可达的“垃圾”,回收白色对象;

5.屏障机制-STW垃圾回收过程中为了保证准确

性防止无止境的内存增长等问题,需要停止赋值

器进一步操作对象图以完成垃圾回收。STW时间

越长,对用户代码造成的影响越大

34、知道哪些 sync 同步原语?各有什么作用?

1.sync.Mutex

它允许在共享资源上互斥访问(不能同时访问)

2.sync.RWMutex

是一个读写互斥锁

3.sync.WaitGroup

sync.WaitGroup拥有一个内部计数器.当计数

器等于0时,则Wait()方法会立即返回.否则它

将阻塞执行Wait()方法的goroutine直到计数

器等于0时为止.要增加计数器,使用Add().使

用Done()减少,也可以传递负数给Add方法把计

数器减少指定大小,Done()方法底层就是通过

Add(-1)实现的.

4.sync.Map

是并发的map.Store-添加元素.Load-检索元素

Delete-删除元素.LoadOrStore检索或添加之

前不存在的元素.如果键之前在map中存在,则返

回的布尔值为true.使用Range遍历元素.

5.sync.Pool

是一个并发池,负责安全地保存一组对象.Get()

用来从并发池中取出元素.Put将一个对象加入并

发池。

6.sync.Once

可确保一个函数仅执行一次

7.sync.Cond

它用于发出信号(一对一)或广播信号(一对多)到

goroutine

35、介绍golang的内存, 什么情况下会发生内存逃逸?

1)本该分配到栈上的变量,跑到了堆上,这就导

致了内存逃逸。

2)栈上的变量,函数结束后会被回收掉,不会有

额外性能的开销.堆上变量的回收,需要进行gc,

gc存在性能开销.变量逃逸会导致性能开销变大.

内存逃逸的情况如下:

1)方法内返回局部变量指针。

2)在闭包中引用包外的值

3)向 channel 发送指针数据。

4)在 slice 或 map 中存储指针。

5)切片(扩容后)长度太大。

36、K8s 含有哪些重要组成部分

1.Master节点:

API Server(API服务器):提供了Kubernetes

API,用于与Kubernetes集群进行通信,

接受和处理各种命令。

Controller Manager(控制器管理器):负责运

行控制器(如ReplicaSet Controller、

DeploymentController),监控集群状态

并根据所需状态进行调整。

Scheduler(调度器):负责将新创建的Pod调度

到集群中的Node上,考虑资源需求,约束条

件等因素。

2.Node节点:

Kubelet:负责在Node上管理容器,包括启动、

停止、重启容器等。

Container Runtime(容器运行时):实际运行

容器的软件,如Docker,containerd等。

Kube Proxy:负责维护网络规则并将网络流量

转发到正确的容器服务。

3.Etcd:

分布式键值存储,用于保存集群的配置信息,状

态信息等,被Master和Node节点共同使用。

4.Pod:

最小部署单元,包含一个或多个容器.Pod中的容

器共享网络和存储,通常是紧密耦合的应用组件

5.Service:

提供了一种抽象,用于定义一组Pod的访问方式.

Service可以保证一组Pod的稳定网络访问。

6.Volume:

用于在Pod中持久化数据.Volume可以连接到

Node上的物理存储、网络存储等。

7.Namespace:

用于将Kubernetes集群划分为多个虚拟集群,

以便在同一集群中运行多个不同的应用,环境等

8.ConfigMap 和 Secret:

用于保存配置信息和敏感数据,可以在Pod中被

挂载为文件或环境变量。

9.Ingress:

提供HTTP和HTTPS路由到服务的规则,允许外部

流量访问Kubernetes集群中的服务。

10.StatefulSet:

用于部署有状态应用,确保Pod有唯一的标识和

稳定的网络标识标题

37、

38、内存泄露和解决方法

内存泄漏是由于程序中的数据结构不再需要时未

能被垃圾回收器(GC)回收造成的

1.全局变量的过度使用:

全局变量会一直占用内存除非显式地将它们设

置为nil

解决方法:避免不必要的全局变量或在不再需要

时将其设置为 nil

2.协程泄漏:

未正确结束的协程可能导致内存泄漏,特别是那

些无限循环或阻塞等待的协程.

解决方法:确保协程能正确退出,使用context

来管理和取消协程.

3.未关闭的通道和其他资源:

未关闭的通道,文件句柄,数据库连接等资源

可能导致内存泄漏.

解决方法:使用defer关键字确保资源被释放

4.循环引用:

特别是在使用指针和接口时,循环引用可能导

致GC无法回收相关对象.

解决方法:避免创建循环引用,如果必须使用,

考虑使用弱引用。

5.大量未释放的临时对象:

频繁创建且长时间不释放的临时对象可能导

致内存泄漏.

解决方法:优化代码逻辑,避免不必要的临时

对象创建,或及时释放不再需要的对象.

6.内部数据结构过大:

巨大的map或slice在使用后未被缩减或清理。

解决方法:在不再需要大量数据时,及时清理或

重新分配这些数据结构.

7.缓存未及时清理:

长期运行的缓存如果没有适当的过期策略,可

能会不断增长.

解决方法:实现缓存清理策略,例如使用过期

时间或大小限制.

解决内存泄漏的通用步骤包括:

1.性能监控:定期监控应用程序的内存使用情况。

分析和调试:使用pprof工具进行内存分析,找出

内存占用异常的部分.

2.代码审查:定期审查代码,找出潜在的内存泄漏

39、CAS算法

CAS(Compare-And-Swap)算法:是重要的并发

编程算法,用于实现无锁编程和原子操作.是实现

多线程同步的关键技术之一,尤其是在构建锁和其

他并发控制结构时.CAS操作包含三个主要参数:

1.内存位置(Memory Location):需要更新的

变量的内存地址.

2.预期原值(Expected Value):变量预期的值

3.新值(New Value):希望设置的新值。

CAS 的基本工作原理如下:

它首先检查目标内存位置的当前值是否与预期原

值相同.若相同,CAS会自动将该位置的值更新为

新值若不同,说明在检查和操作之间有其他线程

修改了该变量,CAS操作失败.CAS操作是原子性

的,意味着在执行过程中不会被其他线程中断。

CAS的优点是避免了使用传统锁所带来的开销和

潜在的死锁问题,因为它不会阻塞线程.但是CAS

可能出现活锁问题和ABA问题:

1.活锁:线程不断尝试更新操作,但总是因为其他

线程的干扰而失败,导致无限循环.

2.ABA问题:一个值原先是A,被另一个线程改为B,

然后又改回A,CAS会错误地认为该值没有被更改过

40、协程池

1.在文生图项目中,处理从redis中循环出来的数据,使用了字节开源的

协程池库

1.资源复用,控制并发数量,避免内存泄漏,任务调度和均衡

41、 MySQL 中的 B 树和 B+ 树区别

1.存储方式: B树和B+树的存储方式不同.在B

树中,每个节点存储键和对应的值,而在B+树中,

只有叶子节点存储键和指向数据的指针,非叶子

节点只存储键.这意味着B+树的内部节点可以容

纳更多的键,减少了树的高度,从而减少了磁盘访

问次数,提高了查询性能

2.叶子节点结构:B树和B+树的叶子节点结构也

不同.在B树中,叶子节点包含了数据的实际值,

而在B+树中,叶子节点只包含键和指向数据的

指针.这使得B+树的叶子节点可以形成一个有

序链表,通过在链表上进行顺序遍历,可以高效

地获取范围内的数据.而B树则需要在不同的层

级进行跳跃,性能相对较低

4.适用场景:B树适用于需要随机访问的场景,

例如数据库索引.而B+树更适合范围查询和顺

序访问的场景,例如文件系统索引

42、Mysql 常见sql优化

1、在where及order by涉及的列上建立索引。

2、不要在where子句中进行的操作:

1>使用!=或<>操作符,

2>对字段进行null值判断

3>使用or来连接条件

4>使用参数

5>对字段进行表达式操作,函数操作

6>对“=”左边进行函数、算术运算或其他表达式运算

7>in和not in的使用,用between或exists代替in

导致索引失效,扫描全表

3、like '%abc%' 导致索引失效

4、不要使用 select * from t

6、不要在有大量数据重复的列上建立索引

7、一个表的索引数不要超过6个,索引提高

select 的效率,同时也降低了insert 及

update 的效率,因为可能会重建索引

8、使用varchar代替char,因为变长字段存

储空间小,可以节省存储空间,其次对于查询

来说,在一个相对较小的字段内搜索效率

显然要高些。

9、在使用索引字段作为条件时,如果该索引是

复合索引,那么必须使用到该索引中的第一个字

段作为条件时才能保证系统使用该索引,否则该

索引将不会被使用,并且应尽可能的让字段顺序

与索引顺序相一致。

10、尽量使用数字型字段,若只含数值信息的

字段尽量不要设计为字符型

11、尽量使用表变量来代替临时表。如果表变量

包含大量数据,请注意索引非常有限(只有主键索引)。

12、避免频繁创建和删除临时表,以减少系统

表资源的消耗。

13、临时表并不是不可使用,适当地使用它们

可以使某些例程更有效,例如,当需要重复引

用大型表或常用表中的某个数据集时。但是,

对于一次性事件,最好使用导出表。

14、在新建临时表时,如果一次性插入数据量

很大,那么可以使用 select into 代替

create table,避免造成大量 log ,以

提高速度;如果数据量不大,为了缓和系统表

的资源,应先create table,然后insert。

15、如果使用到了临时表,在存储过程的最后

务必将所有的临时表显式删除,先 truncate

table ,然后 drop table ,这样可以避免

系统表的较长时间锁定。

16、尽量避免使用游标,因为游标的效率较差,

如果游标操作的数据超过1万行,那么就应该考

虑改写。

43、mysql 事务实现原理

通过日志的记录和重做、回滚日志的使用、锁的

管理和控制,并发控制机制的应用实现。这些机

制共同确保了事务的原子性、一致性、隔离性和

持久性,保证了数据库的数据完整性和并发操作

的正确性

1. 日志(Log):

重做日志(Redo Log)**: 在事务进行修改操

作时,MySQL 将修改的内容记录到重做日志中,

以确保事务的修改可以持久化到磁盘。重做日志

是顺序写入的,因此具有较高的写入性能。

回滚日志(Undo Log)**: 在事务进行修改操

作之前,MySQL 将当前数据的副本记录到回滚日

志中。如果事务需要回滚,MySQL 可以使用回滚

日志将数据恢复到事务开始之前的状态。

2. 锁(Lock):

行级锁(Row-Level Locking)**: MySQL

支持行级锁,允许在事务中对数据行进行加锁。

这样可以控制并发事务对同一数据的访问,确保

数据的一致性和隔离性。

锁粒度(Lock Granularity)**: MySQL

的锁粒度可以根据具体情况调整,包括表级锁、

页级锁和行级锁。较小的锁粒度可以提高并发

性能,但也会增加锁管理的开销。

3. 隔离级别(Isolation Level)**:

MySQL 支持多个隔离级别,例如:

读未提交(Read Uncommitted)、

读已提交(Read Committed)、

可重复读(Repeatable Read)

串行化(Serializable)。

隔离级别定义了事务之间的隔离程度,

决定了事务读取数据的一致性和并发性。

4. 并发控制(Concurrency Control)**:

MySQL 使用并发控制机制来处理多个并发

事务对数据库的访问。这包括锁的获取与释

放、冲突检测和解决,以及事务的调度和执行。

44、mysql中 explain工具的使用

type 字段可能的值

ALL < index < range ~ index_merge < ref < eq_ref < const < system

45、Go 的 select 特性

1.select提供了多路IO复用机制,用于检测

是否有读写事件ready.select结构组成是

case语句和default语句和对应的执行函数

2.case中的表达式必须是Channel收发操作

3.多个case同时被触发,会随机执行其中一个

4.select同时监听多个case的channel是

否可执行,如果都不能执行,则运行default

5.要判断一个channel是否写满数据或为空,

可以使用select语句结合default分支.当

channel满或者为空时,执行default分支,

否则执行对应的case分支

46、单引号,双引号,反引号的区别?

1.单引号:表示rune类型,对应int32类型

2.双引号:是字符串,实际上是字符数组。

3.反引号:表示字符串字面量,但不支持任何转义

序列

47、redis 常见的数据类型

1、string(字符串);

2、hash(哈希);

3、list(列表);

4、set(集合);

5、sort set (有序集合)

48、Redis 的pipeline

管道(pipeline)可以一次性发送多条命令给

服务端,处理完毕后,再通过一条响应一次性将

结果返回,pipeline通过减少客户端与服务端

的通信次数来降低网络往返时延,减少read和

write的系统调用以及进程上下文切换次数,以

提升程序的执行效率与性能,Pipeline实现的

原理是队列,而队列原理是先进先出,这样就保

证数据的顺序性

49、 Redis 的雪崩,击穿,穿透

1.缓存雪崩:大量缓存数据在同一时间过期或者

Redis故障宕机时,如果此时有大量的用户请求,

都无法在Redis中处理,于是全部请求都直接访

问数据库,从而导致数据库的压力骤增,严重的会

造成数据库宕机,从而形成一系列连锁反应,造成

整个系统崩溃

2.缓存击穿:如果缓存中的某个热点数据过期了,

此时大量的请求访问了该热点数据,就无法从缓

存中读取,直接访问数据库,数据库很容易就被高

并发的请求冲垮

3.缓存穿透:当用户访问的数据,既不在缓存中,

也不在数据库中,导致请求在访问缓存时,发现

缓存缺失,再去访问数据库时,发现数据库中也

没有要访问的数据,没办法构建缓存数据,来服

务后续的请求.那么当有大量这样的请求到来时,

数据库的压力骤增

50、基于Redis实现分布式锁

SET lock_key unique_value NX PX 10000

1.lock_key 就是 key 键;

2.unique_value 是客户端生成的唯一的标识,

区分来自不同客户端的锁操作;

3.NX 代表lock_key不存在时,对lock_key设置

才能成功(返回1,失败返回0)

4.PX 10000表示设置lock_key过期时间为10s

避免客户端发生异常而无法释放锁。

51、Redis 获取指定前缀

1.keys feng*该命令会阻塞redis执行其他

命令,禁止生产使用,因为它采用的是遍历的形

式,而且redis是单线程的,顺序执行指令,当

查找的key的量特别多的时候,会一直在查找,

其他的命令就无法执行,导致阻塞或者超时报

错等

2.scan 0 match feng* count 10

游标从0开始扫描,匹配以feng开头的key,

遍历槽位10个.返回下一次遍历的游标开始

值,如果最后结果返回0,表示遍历完了

52、Redis对于多数据库的支持

Redis支持多数据库,在Redis中数据库的概念

是通过数据库索引(Database Index)来实现

的.默认情况下,Redis有16个数据库,索引从0

到15.通过SELECT命令来切换当前使用的数据库

53、Innodb与Myisam引擎的区别

1.InnoDB 支持事务,外键 MyISAM 不支持

2.innodb使用聚簇索引存储数据,表数据和索引

数据就存储在一个索引树结构中.查询数据时直接

通过索引就可以找到数据行。

myisam采用非聚簇索引存储数据,表数据和索引

数据分离存储.索引中保存的是数据在磁盘上的位

置,即指针.聚簇索引的叶子节点就是数据节点,而

非聚簇索引的叶子节点是指向对应数据块的指针

3.InnoDB 不保存表的具体行数,MyISAM保存了

整个表的行数

4.InnoDB 最小的锁粒度是行锁,

MyISAM是表锁

54、什么时候触发行级锁,表级锁

1.执行DDL语句去修改表结构时,会使用表级锁。

alter table,drop table,trunchte table

或者对应的SQL就没有使用索引,没指定查询列

2.当需要修改或读取表中某一行数据,当增删改查

匹配到索引时,Innodb会使用行级锁

55、mysql 中JOIN操作类型

1.INNER JOIN:它返回两个表中匹配的行.如

果在一个表中没有匹配的行,那么这些行就不会

出现在结果集中

2.LEFT JOIN:返回左表中的所有行,以及右表

中与左表中匹配的行.如果在右表中没有匹配的

行,结果集中将包含NULL值

3.RIGHT JOIN:返回右表中的所有行,以及左

表中与右表中匹配的行.如果在左表中没有匹配

的行,结果集中将包含NULL值

4.FULL JOIN:返回两个表中所有的行,无论是

否有匹配.如果在一个表中没有匹配的行,结果集

中将包含NULL值

56、Golang中连接字符串的方式有哪些

1.使用`+`操作符,或者`strings.Join()函数`

循环中连接大量字符串,使用`strings.Join()`

通常更有效率,因为它会预分配足够的空间来避免

多次分配

result := str1 + str2

strs := []string{"Hello", "World!"}

result := strings.Join(strs, " ")

57、Gin框架限流-令牌桶

1.基于官方库实现

golang.org/x/time/rate

r := gin.Default()

l := rate.NewLimiter(rate.Every(time.Second), 200)

r.Use(func(c *gin.Context) {

// 尝试获取一个令牌

if l.Allow() {

...

c.Next() // 继续处理请求

} else {

c.JSON(http.StatusTooManyRequests,

gin.H{"error": "请求过于频繁,请稍后重试"})

c.Abort() // 终止请求处理

}

})

58、TCP可靠传输依靠

TCP实现可靠传输依靠的有序列号,自动重传,滑动窗口,确认应答等机制

59、http三次握手-建立Tcp连接和四次挥手-释放tcp链接

0.ISN(Initial Sequence Number):存入

seq字段的值名称:初始化序列号

1.序号(sequence number):seq序号,占32

位,表示这个tcp包的序列号.tcp协议拼

凑接收到的数据包时,根据seq来确定顺序,

且能确定是否有数据包丢失

2.确认序号(acknowledgement number):

ack序号,占32位,只有ACK标志位为1时,

确认序号字段才有效,ack=seq+1.

3.标志位(Flags):共6个,如下:

URG:紧急指针(urgent pointer)有效.

ACK:确认序号有效(为了与确认号ack区

分开,用大写表示)

PSH:接收方应尽快将该报文交给应用层.

RST:重置连接.

SYN:发起一个新连接.

FIN:释放一个连接.

seq序号,ack序号:用于确认数据是否准确,是

否正常通信.

标志位:用于确认/更改连接状态.

第一次握手:客户端-服务端(SYN=1,seq=x)

第二次握手:(SYN=1,ACK=1,seq=y,ACKnum=

x+1),服务器端进入SYN_RCVD状态。

第三次握手:(ACK=1,ACKnum=y+1)

第一次挥手:(FIN=1,seq=x),客户端状态为

FIN_WAIT_1

第二次挥手:(ACK=1,ACKnum=x+1),服务器

状态为CLOSE_WAIT,客户端状态为

FIN_WAIT_2

第三次挥手:(FIN=1,seq=y):服务器端进入

LAST_ACK状态,等待客户端的最后一个ACK。

第四次挥手:(ACK=1,ACKnum=y+1),客户端进

入TIME_WAIT状态.服务器端进入CLOSED状态.

客户端等待一段时间之后,没有收到服务器端的

ACK,认为服务器端已经正常关闭连接,于是自己

也关闭连接,进入CLOSED状态.

60、tcp 和 http 的区别

HTTP 是无状态的短连接,是应用层协议

定义传输数据的内容规范

TCP是有状态的长连接,是传输层协议

定义数据传输和连接方式的规范

61、TCP粘包

1.粘包:指发送方发送的若干数据包到达接收方

时粘成了一个包,导致接收方无法正常解析数据,

因数据包的边界不明确

2.TCP粘包的原因:

(1)TCP传输协议默认使用Nagle算法(主要为减

少网络中TCP段的数量,每个TCP段中至少装载了

40个字节的标记和首部),Nagle算法主要做了:

上一个分组得到确认,才发送下一个分组;收集多

个小分组,在收到确认时将其一起发送.Nagle算

法可能造成发送方出现粘包问题

(2)TCP接收到数据包时,应用层并不会立即处理.

而是将其保存到接收缓存里,然后应用程序主动从

缓存读取收到的数据.如果TCP接收数据包到缓存

的速度大于应用程序从缓存中读取数据包的速度,

多个包就会被缓存堆积,应用程序可能读取到首尾

粘到一起的包.

3.解决粘包方法:

(1)对于发送方造成的粘包问题,可以通过关闭

Nagle算法来解决,用TCP_NODELAY关闭算法

(2)应用层处理.

格式化数据:每条数据有固定的格式(开始符,结

束符).选择开始符和结束符时一定要确保每条数

据的内部不包含开始符和结束符;

发送长度:发送每条数据时,将数据的长度一并

发送,应用层在处理时可以根据长度来判断每个

分组的开始和结束位置

62、UDP会不会产生粘包问题

TCP为了保证可靠传输并减少额外的开销(每次

发包都要验证),采用了基于流的传输,基于流的

传输不认为消息是一条一条的,是无保护消息边

界的(保护消息边界:指传输协议把数据当做一

条独立的消息在网上传输,接收端一次只能接受

一条独立的消息).

UDP则是面向消息传输的,是有保护消息边界的,

接收方一次只接受一条独立的信息,所以不存在

粘包问题

63 、TCP(传输控制协议)滑动窗口

1.TCP滑动窗口机制的主要作用是进行流量控制和

拥塞控制,确保数据传输的可靠性和效率

2.如果不存在发送窗口的话,TCP发送一个数据包

后会等待ACK包,因为必须要保存对应的数据包,

数据包很有可能需要重新发送.这样的话发送效率

会很慢.大部分时间都在等待.引入窗口后,只要处

于发送窗口范围中的数据包都可以被发送,不需要

等待前面数据包的ack包.

发送窗口在操作系统中开辟的一块缓冲区中,用来

存放当前需要发送的数据.本质是一个循环数组的

实现.利用三个指针来维护相关的区域.发送窗口

就是一个循环利用的缓冲区,应用层发送数据,就

是往缓冲区中写入数据.收到ACK后,就相当于从

缓冲区中移除数据(只需后移对应的指针即可)

应用层会将数据写入到缓冲区中,当超过缓冲区的

最大地址后,就循环利用头部.覆盖头部的数据.

发送缓冲区分为四个部分:

1>已经收到ack包的数据

已经收到ack包的数据,代表接收窗口已经接收了

对应的数据,可以被新数据覆盖.

2>已经发送还未接收到ack包的数据

已经发送出去,但是还未收到接收方对应的ack包

3>允许发送但是还未发送的数据

允许发送但是还未发送的数据.

4>不允许发送的数据.

发送窗口之外的数据,排队等待后续发送。

区间2和区间3构成了发送窗口,两个区间的大小

总和对应着发送窗口的大小

指针1:指向第一个已发送但是未接收到ack的字节

指针2:指向第一个允许发送但是还未发送的字节

指针3:发送窗口的大小

1.接收窗口也是存在于操作系统中开辟的一块缓冲

区,用于接收数据.缓冲区本质是一个循环数组的

实现.利用两个指针来维护相关的区域.

2.接收窗口存在于一个循环利用的缓冲区,接收数

据就是往缓冲区中写入数据.应用层读取数据后,

就相当于从缓冲区中移除数据,不过并不会真正

移除数据,只需要后移对应的指针就可以了.

3.当数据写入超过缓冲区的最大地址后,就循环利

用头部,覆盖头部的数据.

缓冲区分为三部分:

1、应用层已经读取的数据

已经接收到的数据,并且已经发送了ack包,并且已

经被应用层读取.

2、接收窗口中的数据

接收窗口中存储的是当前允许被接收的数据.接收

窗口允许无序接收数据包,所以接收窗口中有一部

分数据接收到了,一部分没接收到,将无序的数据

包直接缓存到接收窗口中.因为无序的接收数据包,

接收窗口中是存在空隙的,因为先发送的数据包由

于网络原因,反而可能会后到接收方.当数据包缓

存到接收窗口中,就会返回ack包,ack包中会携带

SACK信息,也就是选择重选信息.

3、还未收到的数据

还不能接收数据的区域,也就是接收窗口之外的数

据.接收窗口由一个RCV_NEXT和接收窗口大小WND

来维护.

68、基于Gin框架封装自己框架

1.基于Uber开源的zap日志库

2.基于官方x/time/rate库封装令牌桶限流器

3.gopkg.in/yaml.v3包来解析配置文件

4.gorm封装全局dbclient

5.go-redis封装全局redisclient

6.swagger接口文档

7.uuid雪花算法

8.跨域中间件,登陆态校验中间件,

唯一的请求RequestID中间件,超时中间件,

日志中间件,recover中间件

69、golang暴改php实现的登陆态校验-体验go和php的不同

1.go没有类似php的一些便捷函数:serialize

和unserialize,go需要自己实现

2.go没有php三目运算符便捷语法糖,

3.php函数相结合使用,或者php函数结合语法糖,加速逻辑开发

4.php弱类型语言,变量可以随意赋值,但是go强类型语言赋值

后,不可以变更其类型.

70、go和php 的区别:

1.设计目的与应用场景

PHP:主要用于Web开发,特别是创建动态网页

和服务器端脚本方面.与HTML结合紧密,是Web

开发者的首选

Go:简化构建可靠,高效的软件.广泛应用于系

统/网络编程,并发处理,云服务和微服务架构

等领域.

2.性能

PHP:解释型语言,PHP在运行时编译,其性能通

常不如编译型语言.

Go:编译型语言,提供更优的性能和资源利用率,

尤其适合处理高并发和大规模数据

3.并发处理

PHP:自身不擅长并发处理,需要借助外部框架

Go:并发是Go的特性之一,通过Goroutines

(轻量级线程)和Channels来优雅地处理并发.

4.语法和易用性

PHP:语法较为灵活,易于上手,但也因此可能

导致代码不规范.

Go:语法简洁,清晰,注重代码的规范性,易于

阅读和维护.

5.内存管理

PHP:自动内存管理,不需要手动处理内存.

Go:自动垃圾回收,但其内存效率和性能更优.

6.类型系统

PHP:动态类型语言,类型检查在运行时进行.

Go:静态类型语言,编译时进行类型检查

73、为什么使用go重构php网站

1.公司2个主站+8个引流小站使用php渲染页面

的方式开发,即前端和php代码柔和在一起未做

前后端分离,对于php和前端来说开发难度大,也

是造成公司人员流动的一个主要原因

2.相比较于php的需要借助swoole,go原生支持

协程并发

3.希望做成模块化,独立服务的形式,方便后续需

求的快速实现,方向是微服务、

72、go编译核心过程

1.词法分析:解析源码,将文件中字符串序列

转为Token序列

2.语法分析:将Token序列转为有意义的结构

树,即语法树

3.类型检查:检查类型错误和不匹配,改写内

置函数

4.中间代码生成:SSA-静态单赋值

5.代码优化

6.机器码生成

73、docker常用命令

柚子快报激活码778899分享:Golang八股文面试题

http://yzkb.51969.com/

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: