Go实战面试备忘录

时间:2020-05-11 22:31:41   收藏:0   阅读:54

原文地址:https://blog.likeli.top/posts/面试/go面试备忘录/

一个小厂的面试,记录一下,答案不对的,请帮忙更正下

go部分

map底层实现

map底层通过哈希表实现

slice和array的区别

array是固定长度的数组,使用前必须确定数组长度

array特点:

slice特点:

区别:

struct和OOP使用中有什么区别

首先OOP的特点:继承、封装、多态

继承

概念:一个对象获得另一个对象的属性的过程

封装

概念:自包含的黑盒子,有私有和公有的部分,公有可以被访问,私有的外部不能访问

多态

概念:允许用一个接口在访问同一类动作的特性

聊聊你对channel的理解

channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channel可发送数据的类型。

channel有哪些状态

channel有三种状态:

  1. nil,未初始化的状态,只进行了声明,或者手动赋值为nil
  2. active,正常的channel,可读可写
  3. closed,已关闭

channel可进行三种操作:

  1. 关闭

这三种操作和状态可以组合出九种情况:

操作 nil的channel 正常channel 已关闭channel
<-ch (读) 阻塞 成功或阻塞 读到零值
ch<- (写) 阻塞 成功或阻塞 panic
close(ch) (关闭) panic 成功 panic

在并发状态下map如何保证线程安全

go的map并发访问是不安全的,会出现未定义行为,导致程序退出。

go1.6之前,内置的map类型是部分goroutine安全的,并发的读没有问题,并发的写可能有问题。go1.6之后,并发的读写map会报错。

对比一下Java的ConcurrentHashMap的实现,在map的数据非常大的情况下,一把锁会导致大并发的客户端争抢一把锁,Java的解决方案是shard,内部使用多个锁,每个区间共享一把锁,这样减少了数据共享一把锁的性能影响

go1.9之前,一般情况下通过sync.RWMutex实现对map的并发访问控制,或者单独使用锁都可以。

go1.9之后,实现了sync.Map,类似于Java的ConcurrentHashMap

sync.Map的实现有几个优化点:

  1. 空间换时间。通过冗余的两个数据结构(read,dirty),实现加锁对性能的影响
  2. 使用只读数据(read),避免读写冲突
  3. 动态调整,miss次数多了之后,将dirty数据提升为read
  4. double-checking
  5. 延迟删除。删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据
  6. 优先从read读取、更新、删除,因为对read的读取不需要锁

聊聊你对gc的理解

内存管理

go实现的内存管理简单的说就是维护一块大的全局内存,每个线程(go中为P)维护一块小的私有内存,私有内存不足再从全局申请。

更多说明参阅引用说明[1]

垃圾回收

常见的垃圾回收算法:

Go垃圾回收的三色标记法

三色标记法只是为了描述方便抽象出来的一种说法,实际上对象并没有颜色之分。这里的三色对应了垃圾回收过程中对象的三种状态:

垃圾回收优化[2]

写屏障(Write Barrier)

前面说过STW目的是防止GC扫描时内存变化而停掉goroutine,而写屏障就是让goroutine与GC同时运行的手段。虽然写屏障不能完全消除STW,但是可以大大减少STW的时间。

写屏障类似一种开关,在GC的特定时机开启,开启后指针传递时会把指针标记,即本轮不回收,下次GC时再确定。

GC过程中新分配的内存会被立即标记,用的并不是写屏障技术,也即GC过程中分配的内存不会在本轮GC中回收。

辅助GC(Mutator Assist)

为了防止内存分配过快,在GC执行过程中,如果goroutine需要分配内存,那么这个goroutine会参与一部分GC的工作,即帮助GC做一部分的工作,这个机制叫做Mutator Assist。

垃圾回收触发时机[3]

内存分配量达到阈值出发GC

每次内存分配时都会检查当前内存分配量是否已达到阈值,如果达到阈值则立即启动GC。

阈值 = 上次GC内存分配量 * 内存增长率

内存增长率由环境变量GOGC控制,默认为100,即每当内存扩大一倍时启动GC。

定期触发GC

默认情况下,最长2分钟触发一次GC,这个间隔在src/runtime/proc.go:forcegcperiod变量中被声明:

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
//
// This is a variable for testing purposes. It normally doesn‘t change.
var forcegcperiod int64 = 2 * 60 * 1e9
手动触发

程序代码中也可以使用runtime.GC()来手动触发GC,这主要用于GC性能测试和统计。

GC性能优化

GC性能与对象数量负相关,对象越多GC性能越差,对程序影响越大。

所以GC性能优化的思路之一就是减少对象分配个数,比如对象复用或使用大对象组合多个小对象等等。

另外,由于内存逃逸现象,有些隐式的内存分配也会产生,也有可能成为GC的负担。

内存逃逸现象[4]:变量分配在栈上需要能在编译器确定它的作用域,否则就会被分配到堆上。而堆上动态分配内存比栈上静态分配内存,开销大很多。

go通过go build -gcflags=m命令来观察变量逃逸情况[5]

更多逃逸场景:逃逸场景

逃逸分析的作用:

  1. 逃逸分析的好处是为了减少GC的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要GC标记清除。
  2. 逃逸分析完后可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好(逃逸的局部变量会分配在堆上,而没有发生逃逸的则由编译器分配到栈上)
  3. 同步消除,如果你定义的对象在方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行

逃逸总结

go方法传参比起python、java有什么区别

参考文档:go的参数传递细节

go中的函数的参数传递采用的是值传递

gin

聊聊你对gin的理解

gin是一个go的微框架,封装优雅,API友好。快速灵活。容错方便等特点。

其实对于go而言,对web框架的依赖远比Python、Java之类的小。本身的net/http足够简单,而且性能也非常不错,大部分的框架都是对net/http的高阶封装。所以gin框架更像是一些常用函数或者工具的集合。使用gin框架发开发,可以提升效率,并同意团队的编码风格。

gin的路由组件为什么高性能

路由树

gin使用高性能路由库httprouter[6]

在Gin框架中,路由规则被分成了9课前缀树,每一个HTTP Method对应一颗前缀树,树的节点按照URL中的 / 符号进行层级划分

gin.RouterGroup

RouterGroup是对路由树的包装,所有的路由规则最终都是由它来进行管理。Engine结构体继承了RouterGroup,所以Engine直接具备了RouterGroup所有的路由管理功能。

gin数据绑定

gin提供了很方便的数据绑定功能,可以将用户传过来的参数自动跟我们定义的结构体绑定在一起。

这是也我选择用gin的重要原因。

gin数据验证

在上面数据绑定的基础上,gin还提供了数据校验的方法。gin的数据验证是和数据绑定结合在一起的。只需要在数据绑定的结构体成员变量的标签添加binding规则即可。这又省了大量的验证工作,对用惯AspCoreMVC、Spring MVC的程序员来说是完美的替代框架。

gin的中间件

gin中间件利用函数调用栈后进先出的特点,完成中间件在自定义处理函数完成后的处理操作。

redis

为什么redis高性能

为什么redis要采用单线程

官方答复:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器的内存大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章的采用单线程方案了

多路I/O复用模型,非阻塞IO

Linux下的select、poll和epoll就是干这个的。目前最先进的就是epoll。将用户socket对应的fd注册进epoll,然后epoll帮你监听那些socket上有消息到达,这样就避免了大量的无用操作。此时的socket采用非阻塞的模式。这样,这个过程只在调用epoll的时候才会阻塞,收发客户端消息是不会阻塞的,整个进程或线程就被充分利用起来,这也就是事件驱动。

常用的5数据结构

redis作为消息队列的可靠性如何保证

参照RabbitMQ的ACK机制,消费端提供消费回馈。

RabbitMQ

技术分享图片

RabbitMQ如何保证消息的可靠性

生产端

有两种方案:事务消息、消息确认

事务消息会严重损耗RabbitMQ的性能,所以基本不会使用。所以一般使用异步的消息确认方式保证发送的消息一定到达RabbitMQ

消费端

消息确认(ACK),当Customer使用autoAck=true的方式订阅RabbitMQ节点消息的时候,可能由于网络原因也可能由于Customer处理消息的时候出现异常,亦或是服务器宕机,都有可能丢失消息。

而当autoAck=true的时候,RabbitMQ会自动把发出去的消息设置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正的消费到了这些消息。

为了避免这种情况下丢失消息,RabbitMQ提供了消费端确认的方式处理消息,所以需要设置autoAck=false

MQ本身

以上都是应用级别保证消息的可靠性,虽然已经极大的提高了应用的安全性,但是当RabbitMQ节点重启、宕机等情况依旧会导致消息丢失,所以还需要设置队列的持久性。消息的持久性,保证节点宕机或者重启后能恢复消息。

如果出现单点问题,消息还是会丢失。所以可以对于关键的消息设置镜像队列和集群保证消息服务的高可用。

MongoDB

MongoDB是一个通用的、面向文档的分布式数据库

MongoDB索引的数据结构

MongoDB的默认引擎WiredTiger使用B树做为索引底层的数据结构,但是除了B树外,还支持LSM树做为可选的底层数据存储结构。

MongoDB索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

MongoDB为什么默认选择B树而不是MySQL默认的B+树

首先是应用场景:

MySQL中使用的B+树是因为B+树只有叶结点会存储数据,将树种的每一个叶结点通过指针连接起来就能实现顺序遍历,而遍历数据库在关系型数据库中非常常见

MongoDB和MySQL在多个不同数据结构之间选择的最终目的就是减少查询需要的随机IO次数,MySQL认为遍历数据的查询是非常常见的,所以它选择B+树作为底层数据结构。而MongoDB认为查询单个数据记录远比遍历数据更加常见,由于B树的非叶结点也可以存储数据,所以查询一条数据所需要的平均随机IO次数会比B+树少,使用B树的MongoDB在类似的场景中的查询速度就会比MySQL快。这里并不是说MongoDB并不能对数据进行遍历,我们在MongoDB中也可以使用范围查询来查询一批满足对应条件的记录,只是需要的时间会比MySQL长一些。

MongoDB作为非关系型的数据库,它从集合的设计上就使用了完全不同的方法,如果我们仍然使用传统的关系型数据库的表设计思路来思考MongoDB中集合的设计,写出的查询可能会带来相对比较差的性能。

MongoDB中推荐的设计方法,是使用嵌入文档[8]

MongoDB的索引有哪些,区别是什么

MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场景。

Nginx

文档:http://www.aosabook.org/en/nginx.html

技术分享图片

引用文章[9]

为什么Nginx高性能

Nginx运行过程

  1. 多进程:一个 Master 进程、多个 Worker 进程
  2. Master 进程:管理 Worker 进程
  3. 对外接口:接收外部的操作(信号)
  4. 对内转发:根据外部的操作的不同,通过信号管理 Worker
  5. 监控:监控 worker 进程的运行状态,worker 进程异常终止后,自动重启 worker 进程
  6. Worker 进程:所有 Worker 进程都是平等的
  7. 实际处理:网络请求,由 Worker 进程处理;
  8. Worker 进程数量:在 nginx.conf 中配置,一般设置为核心数,充分利用 CPU 资源,同时,避免进程数量过多,避免进程竞争 CPU 资源,增加上下文切换的损耗。

HTTP连接建立和请求处理过程

TCP/UDP

TCP

Tcp三次握手

技术分享图片

图片来自于《图解HTTP》

技术分享图片

所以需要三次握手才能确认双方收发功能都正常。

Tcp四次挥手

技术分享图片

断开一个TCP连接则需要“四次挥手”:

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认或进入半关闭状态。

当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。

UDP

UDP在传送数据之前不需要先建立连接,远程主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP是一种最有效的工作方式(一般用于即时通信,比如:QQ语言、QQ视频、直播等等)

长连接/短连接[10]

TCP本身没有长短连接的区别,长短与否,取决于我们怎么用它。


  1. https://rainbowmango.gitbook.io/go/chapter04/4.1-memory_alloc#4-zong-jie ??

  2. https://rainbowmango.gitbook.io/go/chapter04/4.2-garbage_collection#4-la-ji-hui-shou-you-hua ??

  3. https://rainbowmango.gitbook.io/go/chapter04/4.2-garbage_collection#5-la-ji-hui-shou-chu-fa-shi-ji ??

  4. Go 内存逃逸详细分析 ??

  5. Go内存逃逸分析 ??

  6. httprouter ??

  7. redis的单线程指的是网络请求模块使用了一个线程,即一个线程处理所有的网络请求,其他模块仍用了多个线程 ??

  8. 为什么MongoDB使用B树 ??

  9. https://segmentfault.com/a/1190000022328064 ??

  10. https://www.cnkirito.moe/tcp-talk/ ??

原文:https://www.cnblogs.com/likeli/p/12872355.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!