go面试题总结

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
}
最终的返回值是153.deferrecover通常一起使用,用于处理函数中的错误,
recover只能捕获在同一个goroutine中发生的panic,而且必须在defer中调用,
如果recover在没有发生panic时调用,它会返回nil
4.deferrecover是处理资源释放和错误恢复的重要机制。

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-83个字节,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 发送数据,造成永远阻塞
4.从一个 nil channel 接收数据,造成永远阻塞
5.给一个已经关闭的channel发送数据,引起panic
6.从一个已经关闭的channel读取数据,仍然可以读取剩余的数据,直到数据全部读取完成立即读出零值
7.无缓冲的channel是同步的,而有缓冲的channel是非同步的

16、channel的主要用途

1.向通道(channel)发送数据和从通道读取数据是Go语言实现并发通信的重要机制。
2.通道阻塞的机制确保了goroutine之间的同步和通信。
3.同步机制,数据共享,并发模式简化,信号通知,流程控制,优雅地处理结束场景。

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

1.map是引用类型,如果两个map同时指向一个底层,那么一个map的变动会影响到另一个map2.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以及数组和structmap的内存不会自动释放。
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.线程:线程是操作系统调度的基本单位,也是程序执行的基本单位。
线程与进程不同,它共享进程的资源(如内存、打开的文件等),但拥有自己独立的执行栈和程序计数器。
由于多个线程共享同一个进程的地址空间,因此线程间的通信和数据共享变得容易,同时线程间的切换也比进程间切换要快得多。
线程是轻量级的,创建和销毁一个线程的开销相对较小。
然而,线程间的同步和互斥是一个需要特别注意的问题,以避免数据不一致和其他并发问题。
3.协程:协程是一种用户态的轻量级线程,它的调度完全由用户控制。
协程拥有自己的寄存器上下文和栈,但在切换时,只需将寄存器上下文和栈保存到其他地方,
在切回来的时候恢复先前保存的寄存器上下文和栈,因此协程的切换非常快,且没有内核切换的开销。
协程是非抢占式的,需要用户自己释放使用权来切换到其他协程。
协程并不是取代线程,而是抽象于线程之上,协程需要线程来承载运行,线程是协程的资源

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

操作系统负责线程的调度,Go的runtime负责goroutine的调度,现代操作系统调度线程都是抢占式,
不依赖代码主动让出CPU,或者因为IO,锁等待而让出,这样会造成调度的不公平。
基于经典时间片算法,当线程的时间片用完之后,会被时钟中断,调度器会将当前线程的执行上下文进行保存,
然后恢复下一个线程的上下文,分配新的时间片令其开始执行。
这种抢占对于线程本身是无感知的,系统底层支持,不需要开发人员特殊处理。
基于时间片的抢占式调度有个明显的优点,能够避免CPU资源持续被少数线程占用,从而使其他线程长时间处于饥饿状态。
goroutine的调度器也用到了时间片算法。
只是整个Go程序都是运行在用户态的,所以不能像操作系统那样利用时钟中断来打断运行中的goroutine。
也得益于完全在用户态实现,goroutine的调度切换更加轻量。

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

1.信号量(Semaphore),通道(Channel),上下文(Context)
2.协程池的实现:
2.1>定义池结构:需要定义一个结构体来表示goroutine池,
该结构体通常会包含一个任务队列(用于存储待执行的任务)和一个信号量(用于控制同时运行的goroutine数量)2.2>定义任务类型:通常是一个函数签名,例如type Task func()2.3>创建池:实现一个函数来创建新的goroutine池,包括初始化任务队列和启动一定数量的goroutine来处理任务。
2.4>任务分发:编写逻辑来接收新任务并将它们加入任务队列。
2.5>执行任务:goroutine从队列中获取任务并执行。
2.6>优雅关闭:提供一种方式来优雅地关闭goroutine池,等待所有正在执行的任务完成后再退出。
3.go-playground/pool,ants(推荐)

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

1.原子操作就是不可中断的操作。原子操作执行时,CPU绝对不会再去执行其他针对该值的操作。
2.包sync/atomic提供了原子操作(Load读取Store写入)
原子操作与互斥锁的区别:
1)互斥锁是一种数据结构,用来让一个线程执行程序的关键部分,完成互斥的多个操作。
2)原子操作是针对某个值的单个互斥操作。

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

1.悲观锁:当要对数据库中的一条数据进行修改时,为了避免同时被其他人修改,直接对该数据进行加锁以防止并发。
行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。适合多写场景。
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 > 14)p 上本地 goroutine 队列为空。
mutex会让当前的goroutine去空转CPU,在空转完后再次调用CAS方法去尝试性的占有锁资源,
直到不满足自旋条件,则最终会加入到等待队列里。

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

1.起初所有的对象都是白色的;
2.从根对象出发扫描所有可达对象,标记为灰色,放入待处理队列;
3.从待处理队列中取出灰色对象,将其引用的对象标记为灰色并放入待处理队列中,自身标记为黑色;
4.重复步骤(3),直到待处理队列为空,此时白色对象即为不可达的“垃圾”,回收白色对象;
5.屏障机制-STW(Stop The World)垃圾回收过程中为了保证准确性防止无止境的内存增长等问题,
需要停止赋值器进一步操作对象图以完成垃圾回收。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--添加元素,map.Load--检索元素,map.Delete--删除元素,map.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、解释以下命令的作用?

go env: #用于查看go的环境变量
go run: #用于编译并运行go源码文件
go build: #用于编译源码文件、代码包、依赖包
go get: #用于动态获取远程代码包
go install: #用于编译go文件,并将编译结构安装到bin、pkg目录
go clean: #用于清理工作目录,删除编译和安装遗留的目标文件
go version: #用于查看go的版本信息

38、内存泄露和解决方法

内存泄漏是由于程序中的数据结构不再需要时未能被垃圾回收器(GC)回收造成的。
1.全局变量的过度使用:
 全局变量会一直占用内存除非显式地将它们设置为nil。
 解决方法:避免不必要的全局变量或在不再需要时将其设置为nil2.协程泄漏:
 未正确结束的协程可能导致内存泄漏,特别是那些无限循环或阻塞等待的协程。
 解决方法:确保协程能正确退出,使用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树则需要在不同的层级进行跳跃,性能相对较低。

3.适用场景:
B树适用于需要随机访问的场景,例如数据库索引。
而B+树更适合范围查询和顺序访问的场景,例如文件系统索引。

42、Mysql 常见sql优化

1、在where及order by涉及的列上建立索引。 
2、不要在where子句中进行的操作:
2.1>使用!=<>操作符
2.2>对字段进行null值判断
2.3>使用or来连接条件
2.4>使用参数
2.5>对字段进行表达式操作、函数操作
2.6>对“=”左边进行函数、算术运算或其他表达式运算
2.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工具的使用

EXPLAIN 是 MySQL 中用于查询优化的工具,它用于分析查询语句的执行计划,帮助你理解 MySQL 是如何执行查询的。通过查看执行计划,你可以确定是否需要对查询、索引或表结构进行优化。

下面是一个简单的示例,演示如何使用 EXPLAIN

EXPLAIN SELECT * FROM your_table WHERE your_condition;

在这个查询中,你需要将 your_table 替换为实际的表名,your_condition 替换为实际的查询条件。

执行以上 EXPLAIN 查询后,MySQL 将返回一个描述查询执行计划的结果集。这个结果集包含了 MySQL 查询优化器对查询的分析,其中包括如何访问表、使用哪些索引以及连接表的顺序等信息。

以下是一些常见的 EXPLAIN 输出字段:

  • id: 查询的序列号,表示查询中执行步骤的顺序。
  • select_type: 查询的类型,例如 SIMPLE(简单查询)、PRIMARY(主键查询)等。
  • table: 涉及的表名。
  • type: 表示连接类型,包括 ALLindexrange 等。
  • possible_keys: 查询可能使用的索引。
  • key: 实际使用的索引。
  • key_len: 索引的长度。
  • ref: 显示索引的哪一列与表的哪一列进行比较。
  • rows: 估计要检索的行数。
  • Extra: 包含其他信息,如 Using where(表示使用了 WHERE 子句)、Using index(表示覆盖索引)等。

type最好到最差依次是

system > const > eq_ref > ref > range > index > all

例如,一个查询计划可能如下所示:

+----+-------------+----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key     | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | employees| NULL       | range | salary_index  | PRIMARY | 4       | NULL |  1000 |   100.00 | Using where |
+----+-------------+----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+

这是一个简单的 EXPLAIN 结果,你可以根据这些信息来判断查询的性能瓶颈,并作出相应的优化。在这个例子中,查询使用了 salary_index 索引,但是它只返回 1000 行(可能是因为使用了 WHERE 子句),并且使用了范围查询(type: range)。

45、Go 的 select 特性

1.select机制用来处理异步IO操作。
2.case中的表达式必须是Channel收发操作。
3.多个case同时被触发,会随机执行其中一个。
4.select同时监听多个case的channel是否可执行,如果都不能执行,则运行default5.要判断一个channel是否写满数据或为空,可以使用select语句结合default分支。
当channel满或者为空时,执行default分支,否则执行对应的case分支。

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

1.单引号:只能包含一个字符,默认是rune类型,对应int32类型。
2.双引号:是字符串,实际上是字符数组。
3.反引号:表示字符串字面量,但不支持任何转义序列。

47、redis 常见的数据类型

1string(字符串);
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个数据库,索引从015。
通过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.使用`+`操作符:代码更简短清晰,能获得比较好的可读性。
2.使用fmt.Sprintf():适用于如果需要拼接的不仅仅是字符串,还有数字之类的其他需求的话。
3.使用`strings.Join()函数`:适用于在已有字符串数组的场合。
4.使用strings.Builder:适用于性能要求较高的场合。
5.使用字节缓冲区(bytes.Buffer):适用于性能要求较高的场合。

性能排序:
strings.Join ≈ strings.Builder  >  bytes.Buffer >  "+"  >  fmt.Sprintf

57、Gin框架限流–令牌桶

1.基于官方库实现
import 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实现可靠传输依靠的有序列号,自动重传,滑动窗口,确认应答等机制
在这里插入图片描述

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+13.标志位(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常用命令

在这里插入图片描述

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值