假期结束!上上强度!
时间过得真快啊,一眨眼假期就结束了,五天说长不长说短不短,不知道大家有没有出去旅游?还是呆在家里休息?(不会在公司加班吧??!!)
我反正是出去玩了一顿,不出去走走放松一下心情真的很容易把人憋坏。
正好,最近组织内的朋友给我分享了他在同程旅行一、二面的面经,想必大家放假的时候在学习上也懈怠了,我来给你们上上强度。

面经详解
一面
1. 介绍下什么是 #大对象小对象 #阈值是多少 区别在哪里 内存分配有什么不一样
大对象和小对象的划分是基于内存分配的粒度和策略。Go 的运行时系统(runtime)对不同大小的对象采用不同的内存分配方式,以优化性能和减少内存碎片。
阈值
小对象的阈值是 16B 到 32KB,而 大对象 是指大小超过 32KB 的对象。
小对象(16B~32KB):由 Go 的 mcache(线程缓存)分配。
大对象(>32KB):直接从堆(heap)中分配,绕过 mcache 和 mcentral,以避免碎片化。
区别
分配方式
小对象:通过 mcache(线程级缓存)分配,每个 P(逻辑处理器)对应一个 mcache,分配速度快,减少锁竞争。
大对象:直接从堆分配,由 Go 的 mheap(全局堆)管理,分配时可能触发 GC(垃圾回收)或调整堆大小。
内存碎片
小对象容易产生内存碎片,因为频繁分配和释放可能导致空闲内存块分散。
大对象通常一次性分配较大的连续内存块,减少碎片化风险。
性能优化
小对象的分配和回收效率较高,适合频繁创建和销毁的场景(如临时变量)。
大对象的分配成本较高,适合生命周期较长的对象(如大数组、大结构体)。
内存分配策略
Go 通过内存分级管理(mcache → mcentral → mheap)优化内存分配:
mcache:线程本地缓存,快速分配小对象。
mcentral:全局缓存,管理多个 mcache 的内存块,协调小对象分配。
mheap:全局堆,负责大对象分配和堆管理。
2. slice 的扩容机制
slice(切片)是一种动态数组,其底层依赖于数组和长度/容量信息。当 slice 的容量不足时,会触发扩容机制,具体规则如下:
扩容规则
容量小于 256:新容量为原容量的两倍。
容量大于等于 256:新容量会采用逐步增长策略,减少过度分配。
极端情况:如果扩容后仍不足,直接分配所需容量。
扩容过程
分配新的底层数组(大小为
newCap
)。将旧数组的数据复制到新数组。
更新 slice 的
ptr
(指向新数组)、len
(长度)和cap
(容量)。
3. 有缓冲 Channel 关闭后接受发送
有缓冲 Channel(buffered channel)关闭后:
发送操作
如果 Channel 已关闭,尝试发送数据会触发 panic(运行时错误)。
即使 Channel 仍有剩余容量,关闭后也不能再发送数据。
接收操作
关闭后仍可以接收 Channel 中剩余的数据。
当所有数据被接收后,后续接收操作会返回零值(zero value),并标记
ok
为false
。
4. 限流算法了解哪些
常见的限流算法包括以下几种:
令牌桶算法(Token Bucket)
原理:维护一个令牌桶,以固定速率向桶中添加令牌。请求需要消耗令牌,若无可用令牌则被拒绝。
优点:允许突发流量(令牌桶可积攒令牌)。
缺点:实现复杂度较高。
漏桶算法(Leaky Bucket)
原理:以固定速率处理请求,超出容量的请求会被丢弃或排队。
优点:平滑流量,防止突发流量冲击。
缺点:无法应对突发流量。
固定窗口计数器(Fixed Window Counter)
原理:在固定时间窗口内统计请求数量,超过阈值则限流。
缺点:存在临界点问题(如窗口边界处的请求突增)。
滑动窗口计数器(Sliding Window Counter)
原理:动态统计最近一段时间内的请求数量,解决固定窗口的临界问题。
优点:更精确地控制流量。
信号量(Semaphore)
原理:通过控制并发资源的数量实现限流。
优点:简单易用,适合资源池管理。
5. 说下令牌算法漏桶算法的优缺点
令牌桶算法和漏桶算法是两种经典的限流策略,它们的优缺点如下:
令牌桶算法
优点:允许突发流量,灵活性高。
缺点:实现复杂,需维护令牌数量和时间。
漏桶算法
优点:平滑流量,防止系统过载。
缺点:无法应对突发流量,可能造成请求堆积。
6. redis 怎么实现队列
Redis 可以通过其 List 数据结构实现队列,具体方法如下:
基础队列
入队:使用
RPUSH
命令将元素添加到队列尾部。出队:使用
LPOP
命令从队列头部取出元素。阻塞队列
阻塞出队:使用
BLPOP
或BRPOP
命令,若队列为空则阻塞等待。优先级队列
使用
ZSET
(有序集合)实现优先级队列,通过分数(score)控制优先级。分布式队列
结合 Redis 的分布式锁(如
SETNX
)实现多节点协作。
7. jwt 和传统 cookie、session 的区别
JWT 的适用场景
分布式系统(微服务架构)。
跨域认证(如 SPA 应用)。
传统 Session 的适用场景
单体应用或小型系统。
需要高安全性的场景(如银行系统)。
8. 说一下 etcd
etcd 是一个高可用的分布式键值存储系统,主要用于服务发现、配置管理、分布式锁等场景。
核心特性
强一致性:基于 Raft 协议实现分布式一致性。
高可用性:支持集群部署,容忍节点故障。
简单 API:提供 RESTful 接口和 Go 客户端库。
应用场景
服务注册与发现:存储服务实例的元数据。
分布式锁:通过租约实现锁的自动释放。
配置中心:集中管理分布式系统的配置。
二面
1. gc
Go 语言的**垃圾回收(GC)机制是基于标记-清除(Mark and Sweep)**算法的并发垃圾回收器,主要特点如下:
GC 流程
标记阶段:从根对象(全局变量、栈变量等)出发,遍历所有可达对象,并标记为“存活”。
清除阶段:回收未被标记的对象(垃圾)。
并发性
Go 的 GC 是并发执行的,与程序逻辑线程(M)并行运行,减少程序暂停时间。
使用三色标记法(白色、灰色、黑色)管理对象状态,避免标记错误。
低延迟设计
通过分代回收(Generational GC)优化,优先回收短生命周期对象。
写屏障(Write Barrier)确保标记阶段的准确性。
性能优化
STW(Stop-The-World):仅在标记阶段的初始和结束阶段短暂暂停程序。
内存分配:使用 mcache/mcentral/mheap 分级管理内存,减少 GC 压力。
2. 协程泄露
协程泄露(Goroutine Leak)是指 Goroutine 因未正确退出而持续占用资源,导致内存泄漏或资源耗尽。
常见原因
Channel 未关闭:
死循环未退出:
WaitGroup 未释放:
解决方案
使用 context.Context 控制 Goroutine 生命周期。
确保 Channel 在不再使用时关闭。
为死循环添加退出条件(如超时或外部信号)。
3. 三次握手四次挥手 为解决什么问题
三次握手和四次挥手是 TCP 协议建立和终止连接的核心机制,用于解决以下问题:
三次握手(建立连接)
目的:确保双方都能正常收发数据,避免因重复连接请求导致混乱。
流程:
SYN:客户端发送连接请求。
SYN-ACK:服务器确认请求并回复。
ACK:客户端确认连接成功。
四次挥手(终止连接)
目的:确保双方完成数据传输后安全关闭连接,避免半开连接。
流程:
FIN:主动关闭方发送结束请求。
ACK:被动关闭方确认。
FIN:被动关闭方发送结束请求。
ACK:主动关闭方确认。
关键问题
避免重复连接(三次握手)。
确保数据完整性(四次挥手)。
4. 粘包 原理及解决
粘包(Sticky Packet)是 TCP 协议中因流式传输导致的数据边界模糊问题。
原理
TCP 是流式协议,不维护消息边界。发送端连续发送的数据可能被接收端合并为一个或多个数据包。
示例:发送两个消息
"ABC"
和"DEF"
,接收端可能收到"ABCDEF"
(粘包)。解决方法
固定长度:每条消息固定长度(不足时填充空字符)。
自定义协议头:在消息前添加长度字段。
特殊分隔符:使用特定字符(如
\n
)分隔消息。
5. 自定义一个协议,怎么知道要分配多大一个内存去存放接收的数据包
在自定义协议中,确定接收数据包的内存分配策略需要结合协议设计:
步骤
解析协议头:在协议头中包含数据长度字段(如 4 字节的 BigEndian 整数)。
动态分配内存:根据长度字段分配缓冲区。
预分配缓冲区:对于固定长度的消息,预先分配固定大小的缓冲区。
注意事项
避免缓冲区溢出:限制最大消息长度(如
MAX_PACKET_SIZE
)。处理分片:如果数据包分多次到达,需缓存未接收部分。
校验数据完整性:通过校验和或 CRC 验证数据是否完整。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。
版权声明: 本文为 InfoQ 作者【王中阳Go】的原创文章。
原文链接:【http://xie.infoq.cn/article/6ae51257fae7fb3a4a18f9dba】。文章转载请联系作者。
评论