Golang 开发高频面试题

本文详细介绍了Go语言的面试常见问题,涵盖了类型定义、缓存策略、Raft算法、进程线程的区别、Go的并发原语如context、sync.WaitGroup,以及TCP与UDP的区别。此外,还讨论了Go中的goroutine、协程、通道、并发控制等,并深入探讨了分布式一致性、数据结构与算法、操作系统原理等相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么时候才应该把参数定义成类型T,什么情况下定义成类型*T呢。

一般的判断标准是看副本创建的成本和需求。

  1. 不想变量被修改。 如果你不想变量被函数和方法所修改,那么选择类型T。相反,如果想修改原始的变量,则选择*T
  2. 如果变量是一个大的struct或者数组,则副本的创建相对会影响性能,这个时候考虑使用*T,只创建新的指针,这个区别是巨大的
  3. (不针对函数参数,只针对本地变量/本地变量)对于函数作用域内的参数,如果定义成T,Go编译器尽量将对象分配到栈上,而*T很可能会分配到对象上,这对垃圾回收会有影响

简单地比较Redis与Memcached的区别:
1、 Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。memcache数据结构单一,redis丰富一些,数据操作方面,redis更好一些,较少的网络IO次数
2、 Redis支持数据的备份,即master-slave模式的数据备份。
3、 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

4、存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);

5、灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;

memcache能接受的key的最大长度是250个字符。对iterm 的过期时间最大可达30天;最大能存储的单个iterm是1MB,大于1MB可以考虑在客户端压缩或拆分到多个key中。

缓存击穿和缓存穿透区别,如何解决?

缓存穿透

 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

      解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

缓存击穿

      缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

      解决方案:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,

缓存雪崩

      缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,        缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

     解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

一、raft算法

一个 etcd 集群,通常会由 3 个或者 5 个节点组成,多个节点之间通过 Raft 一致性算法的完成分布式一致性协同,算法会选举出一个主节点作为 leader,由 leader 负责数据的同步与数据的分发。当 leader 出现故障后系统会自动地选取另一个节点成为 leader,并重新完成数据的同步。客户端在多个节点中,仅需要选择其中的任意一个就可以完成数据的读写,内部的状态及数据协同由 etcd 自身完成

  • 简单:基于HTTP+JSON的API让你用url命令就可以轻松使用。
  • 安全:可选SSL客户认证机制。
  • 快速:每个实例每秒支持一千次写操作。
  • 可信:使用Raft算法充分实现了分布式。

leader选举的过程是:1、增加term号;2、给自己投票;3、重置选举超时计时器;4、发送请求投票的RPC给其它节点

Raft是一个用于管理日志一致性的协议它将分布式一致性分解为多个子问题:Leader选举(Leader election)、日志复制(Log replication)、安全性(Safety)、日志压缩(Log compaction)等同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选者(Candidate):

  • Leader:接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志

  • Follower:接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志

  • Candidate:Leader选举过程中的临时角色

1、Raft分为哪几个部分?

  主要是分为leader选举、日志复制、日志压缩、成员变更

2、Raft中任何节点都可以发起选举吗?

  Raft发起选举的情况有如下几种:

  • 刚启动时,所有节点都是follower,这个时候发起选举,选出一个leader;

  • 当leader挂掉后,时钟最先跑完的follower发起重新选举操作,选出一个新的leader。

  • 成员变更的时候会发起选举操作。

3、Raft中选举中给候选人投票的前提?

  Raft确保新当选的Leader包含所有已提交(集群中大多数成员中已提交)的日志条目。这个保证是在RequestVoteRPC阶段做的,candidate在发送RequestVoteRPC时,会带上自己的last log entry的term_id和index,follower在接收到RequestVoteRPC消息时,如果发现自己的日志比RPC中的更新,就拒绝投票。日志比较的原则是,如果本地的最后一条log entry的term id更大,则更新,如果term id一样大,则日志更多的更大(index更大)。

5、Raft数据一致性如何实现?

  主要是通过日志复制实现数据一致性,leader将请求指令作为一条新的日志条目添加到日志中,然后发起RPC 给所有的follower,

### Golang 常见面试题总结 Golang 是一种高效的编程语言,广泛应用于后端开发、分布式系统和微服务架构中。以下是常见的 Golang 面试题,涵盖基础概念、代码实现和设计模式等方面。 #### 1. 基础概念 - **Goroutine 和 Channel 的使用** Goroutine 是 Golang 的轻量级线程,Channel 是 Goroutine 之间的通信机制[^1]。例如,通过 Channel 实现生产者-消费者模式: ```go package main import ( "fmt" ) func producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } func consumer(ch <-chan int) { for v := range ch { fmt.Println(v) } } func main() { ch := make(chan int) go producer(ch) consumer(ch) } ``` - **垃圾回收(GC)机制** Go 的垃圾回收采用三色标记算法,存在 STW(Stop The World)阶段[^2]。STW 是指在某些特定时刻,所有用户级别的 Goroutine 都会被暂停以完成必要的 GC 操作。 - **GPM 调度模型** Golang 使用 GPM 调度模型(Goroutine, Processor, Machine),其中 M 表示操作系统线程,P 表示处理器,G 表示 Goroutine。调度器负责将 Goroutine 分配到 P 和 M 上运行[^2]。 #### 2. 代码实现 - **延迟队列的实现** 可以通过 `time.After` 或结合 Redis 实现延迟队列。以下是一个简单的基于 `time.After` 的延迟队列示例: ```go package main import ( "fmt" "sync" "time" ) type DelayQueue struct { tasks map[int]time.Time mu sync.Mutex } func NewDelayQueue() *DelayQueue { return &DelayQueue{tasks: make(map[int]time.Time)} } func (dq *DelayQueue) Add(id int, delay time.Duration) { dq.mu.Lock() defer dq.mu.Unlock() dq.tasks[id] = time.Now().Add(delay) } func (dq *DelayQueue) Process() { for { dq.mu.Lock() now := time.Now() for id, due := range dq.tasks { if due.Before(now) { fmt.Println("Processing task:", id) delete(dq.tasks, id) } } dq.mu.Unlock() time.Sleep(1 * time.Second) } } func main() { dq := NewDelayQueue() dq.Add(1, 5*time.Second) dq.Add(2, 10*time.Second) go dq.Process() time.Sleep(15 * time.Second) } ``` - **并发安全的数据结构** 使用 `sync.Mutex` 或 `sync.RWMutex` 实现并发安全的切片或 Map。例如: ```go package main import ( "fmt" "sync" ) type SafeMap struct { mu sync.RWMutex m map[string]int } func NewSafeMap() *SafeMap { return &SafeMap{m: make(map[string]int)} } func (sm *SafeMap) Set(key string, value int) { sm.mu.Lock() defer sm.mu.Unlock() sm.m[key] = value } func (sm *SafeMap) Get(key string) int { sm.mu.RLock() defer sm.mu.RUnlock() return sm.m[key] } func main() { sm := NewSafeMap() sm.Set("key1", 42) fmt.Println(sm.Get("key1")) } ``` #### 3. 设计模式 - **单例模式** 在 Golang 中可以通过函数闭包实现单例模式: ```go package main import "fmt" type Singleton struct{} var instance *Singleton var once sync.Once func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} }) return instance } func main() { s1 := GetInstance() s2 := GetInstance() fmt.Println(s1 == s2) // true } ``` - **工厂模式** 工厂模式用于创建对象而无需指定具体类: ```go package main import "fmt" type Product interface { Use() } type ConcreteProductA struct{} func (p *ConcreteProductA) Use() { fmt.Println("Using Product A") } type ConcreteProductB struct{} func (p *ConcreteProductB) Use() { fmt.Println("Using Product B") } func CreateProduct(name string) Product { switch name { case "A": return &ConcreteProductA{} case "B": return &ConcreteProductB{} default: return nil } } func main() { product := CreateProduct("A") product.Use() } ``` #### 4. 高频问题 - **什么是 CSP 并发模型?** CSP(Communicating Sequential Processes)是一种并发模型,强调通过消息传递而不是共享内存来实现并发[^2]。Go 的 Goroutine 和 Channel 是 CSP 模型的具体实现。 - **如何避免竞态条件?** 使用同步原语如 `sync.Mutex` 或 `sync.RWMutex`,或者通过 Channel 实现无锁的并发控制[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值