原文地址:https://blog.likeli.top/posts/面试/go面试备忘录/
一个小厂的面试,记录一下,答案不对的,请帮忙更正下
map底层通过哈希表实现
array是固定长度的数组,使用前必须确定数组长度
array特点:
slice特点:
区别:
首先OOP的特点:继承、封装、多态
继承
概念:一个对象获得另一个对象的属性的过程
封装
概念:自包含的黑盒子,有私有和公有的部分,公有可以被访问,私有的外部不能访问
多态
概念:允许用一个接口在访问同一类动作的特性
extends class
或implements interface
实现合约
方式实现,只要某个struct实现了某个interface中的所有方法,那么它就隐式的实现了该接口channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channel可发送数据的类型。
channel有三种状态:
channel可进行三种操作:
这三种操作和状态可以组合出九种情况:
操作 | nil的channel | 正常channel | 已关闭channel |
---|---|---|---|
<-ch (读) | 阻塞 | 成功或阻塞 | 读到零值 |
ch<- (写) | 阻塞 | 成功或阻塞 | panic |
close(ch) (关闭) | panic | 成功 | panic |
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
的实现有几个优化点:
go实现的内存管理简单的说就是维护一块大的全局内存,每个线程(go中为P)维护一块小的私有内存,私有内存不足再从全局申请。
更多说明参阅引用说明[1]
常见的垃圾回收算法:
三色标记法只是为了描述方便抽象出来的一种说法,实际上对象并没有颜色之分。这里的三色对应了垃圾回收过程中对象的三种状态:
前面说过STW目的是防止GC扫描时内存变化而停掉goroutine,而写屏障就是让goroutine与GC同时运行的手段。虽然写屏障不能完全消除STW,但是可以大大减少STW的时间。
写屏障类似一种开关,在GC的特定时机开启,开启后指针传递时会把指针标记,即本轮不回收,下次GC时再确定。
GC过程中新分配的内存会被立即标记,用的并不是写屏障技术,也即GC过程中分配的内存不会在本轮GC中回收。
为了防止内存分配过快,在GC执行过程中,如果goroutine需要分配内存,那么这个goroutine会参与一部分GC的工作,即帮助GC做一部分的工作,这个机制叫做Mutator Assist。
每次内存分配时都会检查当前内存分配量是否已达到阈值,如果达到阈值则立即启动GC。
阈值 = 上次GC内存分配量 * 内存增长率
内存增长率由环境变量GOGC
控制,默认为100,即每当内存扩大一倍时启动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的负担。
内存逃逸现象[4]:变量分配在栈上需要能在编译器确定它的作用域,否则就会被分配到堆上。而堆上动态分配内存比栈上静态分配内存,开销大很多。
go通过go build -gcflags=m
命令来观察变量逃逸情况[5]
更多逃逸场景:逃逸场景
逃逸分析的作用:
逃逸总结
参考文档:go的参数传递细节
go中的函数的参数传递采用的是值传递
gin是一个go的微框架,封装优雅,API友好。快速灵活。容错方便等特点。
其实对于go而言,对web框架的依赖远比Python、Java之类的小。本身的net/http
足够简单,而且性能也非常不错,大部分的框架都是对net/http
的高阶封装。所以gin框架更像是一些常用函数或者工具的集合。使用gin框架发开发,可以提升效率,并同意团队的编码风格。
gin使用高性能路由库httprouter
[6]
在Gin框架中,路由规则被分成了9课前缀树,每一个HTTP Method对应一颗前缀树,树的节点按照URL中的 / 符号进行层级划分
RouterGroup是对路由树的包装,所有的路由规则最终都是由它来进行管理。Engine结构体继承了RouterGroup,所以Engine直接具备了RouterGroup所有的路由管理功能。
gin提供了很方便的数据绑定功能,可以将用户传过来的参数自动跟我们定义的结构体绑定在一起。
这是也我选择用gin的重要原因。
在上面数据绑定的基础上,gin还提供了数据校验的方法。gin的数据验证是和数据绑定结合在一起的。只需要在数据绑定的结构体成员变量的标签添加binding
规则即可。这又省了大量的验证工作,对用惯AspCoreMVC、Spring MVC的程序员来说是完美的替代框架。
gin中间件利用函数调用栈后进先出
的特点,完成中间件在自定义处理函数完成后的处理操作。
官方答复:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器的内存大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章的采用单线程方案了
Linux下的select、poll和epoll就是干这个的。目前最先进的就是epoll。将用户socket对应的fd注册进epoll,然后epoll帮你监听那些socket上有消息到达,这样就避免了大量的无用操作。此时的socket采用非阻塞的模式。这样,这个过程只在调用epoll的时候才会阻塞,收发客户端消息是不会阻塞的,整个进程或线程就被充分利用起来,这也就是事件驱动。
参照RabbitMQ的ACK机制,消费端提供消费回馈。
有两种方案:事务消息、消息确认
事务消息会严重损耗RabbitMQ的性能,所以基本不会使用。所以一般使用异步的消息确认方式保证发送的消息一定到达RabbitMQ
消息确认(ACK),当Customer使用autoAck=true
的方式订阅RabbitMQ节点消息的时候,可能由于网络原因也可能由于Customer处理消息的时候出现异常,亦或是服务器宕机,都有可能丢失消息。
而当autoAck=true
的时候,RabbitMQ会自动把发出去的消息设置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正的消费到了这些消息。
为了避免这种情况下丢失消息,RabbitMQ提供了消费端确认的方式处理消息,所以需要设置autoAck=false
以上都是应用级别保证消息的可靠性,虽然已经极大的提高了应用的安全性,但是当RabbitMQ节点重启、宕机等情况依旧会导致消息丢失,所以还需要设置队列的持久性。消息的持久性,保证节点宕机或者重启后能恢复消息。
如果出现单点问题,消息还是会丢失。所以可以对于关键的消息设置镜像队列和集群保证消息服务的高可用。
MongoDB是一个通用的、面向文档的分布式数据库
MongoDB的默认引擎WiredTiger
使用B树
做为索引底层的数据结构,但是除了B树外,还支持LSM树做为可选的底层数据存储结构。
MongoDB索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
首先是应用场景:
MySQL中使用的B+树是因为B+树只有叶结点会存储数据,将树种的每一个叶结点通过指针连接起来就能实现顺序遍历,而遍历数据库在关系型数据库中非常常见
MongoDB和MySQL在多个不同数据结构之间选择的最终目的就是减少查询需要的随机IO次数,MySQL认为遍历数据的查询是非常常见的,所以它选择B+树作为底层数据结构。而MongoDB认为查询单个数据记录远比遍历数据更加常见,由于B树的非叶结点也可以存储数据,所以查询一条数据所需要的平均随机IO次数会比B+树少,使用B树的MongoDB在类似的场景中的查询速度就会比MySQL快。这里并不是说MongoDB并不能对数据进行遍历,我们在MongoDB中也可以使用范围查询来查询一批满足对应条件的记录,只是需要的时间会比MySQL长一些。
MongoDB作为非关系型的数据库,它从集合的设计上就使用了完全不同的方法,如果我们仍然使用传统的关系型数据库的表设计思路来思考MongoDB中集合的设计,写出的查询可能会带来相对比较差的性能。
MongoDB中推荐的设计方法,是使用嵌入文档[8]。
MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、文本索引等,每种类型的索引有不同的使用场景。
文档:http://www.aosabook.org/en/nginx.html
引用文章[9]
图片来自于《图解HTTP》
所以需要三次握手才能确认双方收发功能都正常。
断开一个TCP连接则需要“四次挥手”:
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认或进入半关闭状态。
当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
UDP在传送数据之前不需要先建立连接,远程主机在收到UDP报文后,不需要给出任何确认。虽然UDP不提供可靠交付,但在某些情况下UDP是一种最有效的工作方式(一般用于即时通信,比如:QQ语言、QQ视频、直播等等)
TCP本身没有长短连接的区别,长短与否,取决于我们怎么用它。
socket.close()
,这就是一般意义上的短连接。
https://rainbowmango.gitbook.io/go/chapter04/4.1-memory_alloc#4-zong-jie ??
https://rainbowmango.gitbook.io/go/chapter04/4.2-garbage_collection#4-la-ji-hui-shou-you-hua ??
https://rainbowmango.gitbook.io/go/chapter04/4.2-garbage_collection#5-la-ji-hui-shou-chu-fa-shi-ji ??
redis的单线程指的是网络请求模块使用了一个线程,即一个线程处理所有的网络请求,其他模块仍用了多个线程 ??
原文:https://www.cnblogs.com/likeli/p/12872355.html