# interview **Repository Path**: istjl/interview ## Basic Information - **Project Name**: interview - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-20 - **Last Updated**: 2025-07-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 索引类型 存储结构: Btree索引,Hash索引,full-index全文索引,R-tree索引 应用层: 普通索引,唯一索引,复合索引,聚镞索引,非聚镞索 # 底层实现 Hash索引: 基于hash表实现,只能精准匹配索引所有列的查询才有效,否则无法命中索引。 B-tree索引: 能加快数据的访问,存储引擎不再需要全表扫描获取数据,数据分布在各个节点上 # 区别: B+tree: 磁盘读写代价更低,更适合区间查询 Hash: 可以快速定位,但是没有顺序,并且io复杂度高,只有memory存储引擎显式支持哈希索引,适合等值查询,如(=,in,<=>),不支持范围查询,如果大量重复键值的情况,哈希索引的效率会很低 二叉树: 树的高度不均匀,不能自平衡,查询效率跟数据有关,且io代价高 红黑树: 树的高度随着数据量增加而增加,io代价高 # mysql支持的存储引擎 InnoDB: 支持事务,支持外键 MyISAM: 全文索引 # sql的约束 Not Null,Unique, Primary Key, Foreign Key, Check, Default, Auto_Increment # 事务的四个特征 原子性:事务是一个不可分割的工作单位,事务中的操作要么都做,要么都不做 一致性:事务执行前和执行后数据库的约束条件保持一致 隔离性: 多个事务并发执行时,一个事务的操作不应被其他事务干扰,各事务如同串行执行 持久性: 事务一旦提交,其对数据的修改就是永久性的,即使系统故障也不会丢失 # 隔离级别 读未提交:脏读 读已提交: 幻读 可重复读:幻读,默认隔离级别 可串行化:性能低,但是所有事务都必须排队执行 # 事务原理 基于日志文件(redo log)和回滚文件(undo log) # channel原理 基于csp模型的原生实现,与传统内存共享相比,它以通信为中心,使得goroutine的并发协作更简单,更安全。它即是同步点,也是值传递工具 底层由一个hchan结构体实现,包含了环形队列,发送队列和接收队列,goroutine,互斥锁,关闭标志 ```go type hchan struct{ qcount uint // 队列中的总元素个数 dataqsiz uint // 队列中元素的最大个数 buf unsafe.Pointer // 环形队列指针 elemsize uint16 // 每个元素的大小 sendx uint // 发送索引 recvx uint // 接收索引 recvq waitq // 接收等待队列 sendq waitq // 发送等待队列 lock mutex // 互斥锁 } ``` 关键组件: 等待队列 recvq: 等待从channel接收数据的goroutine队列 sendq: 等待向channel发送数据的goroutine队列 互斥锁 保护channel的所有字段 # map 底层实现 是一种哈希表结构的引用类型,用于对存储键值对,具有快速查找,插入,删除的特性。 底层实现: 基于哈希表实现,其底层结构由数组、哈希函数和桶组成,每个桶内最多容纳8个键值对,并通过tophash优化加速查找过程,冲突通过链式桶加开放寻址联合解决; 当负载因子过高或bucket填满时触发扩容,并采用渐进式rehash方式避免长时间阻塞,从而实现高效的插入、查找和删除操作 ```go type hmap struct{ count int // 当前map中键值对个数 flags uint8 B uint8 // 2^B表示桶的数量 noverflow uint16 // 溢出桶数量的近似值 hash0 uint32 // 哈希种子 buckets unsafe.Pointer // 指向bucket数组的指针 oldbuckets unsafe.Pointer // 扩容时的旧buckets nevacuate uintptr // 扩容迁移进度 extra *mapextra // 存放溢出桶等额外信息 } ``` 哈希表实现: 对每个键使用哈希函数计算哈希值 哈希函数是运行时根据CPU特性选择的,可以说AES哈希或memhash 使用hash0作为随机种子,防止哈希碰撞 查找过程: 计算键的哈希值 取哈希值的低位确定桶位置 取哈希值的高8位在桶内查找 如果当前桶没找到,检查溢出桶 找到匹配的tophash后,比较实际的键值 找到则返回对应之,否则返回零值 哈希表结构: 桶(bucket): 扩容机制: 新哈希表的大小通常是当前大小的两倍 # Get和post的区别 Get: 请求从服务器获取资源,安全且密等,只读操作,不会对服务器上的数据造成影响 Post: 向指定的url指定的资源提交数据,数据方法放在body里,新增或提交数据的操作,会修改服务器资源,相对于操作来说是不安全的,不是幂等 # TCP和UDP的区别 TCP: 面向连接,可靠传输,使用流量控制和拥塞控制,有序,消息在传输过程中可能会乱序,TCP会重新排序,传输速度相对于UDP慢,只能一对一通信, UDP: 无连接,不可靠传输,不使用流量控制和拥塞控制,无序的,传输速度快,面向报文,支持一对一,一对多,多对一和多对多交互通信,适用于(ip电话,视频会议,直播denied) # http和https的区别 http: 默认端口80,不安全,明文传输,不支持https加密,不支持https认证,不支持https压缩,不支持https缓存,不支持https代理,不支持 https: 默认端口443,安全,加密传输,支持https加密,支持https认证,支持https压缩,支持https缓存,支持https代理,支持https # slice原理 ```go type slice struct { array unsafe.Pointer // 指向底层数组的指针 len int // 当前长度 cap int // 容量 } ``` 扩容策略: 新容量通常为原容量的2倍,当容量>=1024时,每次扩容1.25倍 # CSP模型 CSP模型是以“通信的方式来共享内存”,不同于传统的多线程通过共享内存来通信,用于描述两个独立的并发实体通过共享的通讯channel(管道)进行通信的并发模型 # GPM模型 G(goroutine): go协程,每个go关键字都会创建一个协程 M(Machine): 工作线程,在go中称为Machine,数量对应真实的CPU数(真正干活的对象) P(Processor): 处理器(go中定义的一个概念,非GPU),包含go代码的必要资源,用力啊调度G和M之间的关联关系,可以通过GOMAXPROCS()来设置,默认为核心数 M必须拥有P才执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行 # Goroutine调度策略 1. 队列轮转: P会周期性的将G调度到M中执行,执行一段时间后,保存上下文,将G放到队列尾部,然后再从队列中再取出一个G进行调度,除此之外,P还会周期性的查看全局队列额是否有G等待调度到M中执行 2. 系统调用: 当G0即将进入系统调用时,M0将释放P,进而某个空闲的M1获取P,继续执行P队列中剩下的G,M1到来源由可能是M的缓存池,也可能是新建的 3. 当G0系统调用结束后,如果有空闲的P,则获取一个P,继续执行G0,如果没有,则将G0放入全局队列,等待被其他的P调度,然后M0将进入缓存池睡眠 # 逃逸分析 1. 指针逃逸 2. 栈空间不足逃逸 3. 动态类型逃逸 4. 闭包引用逃逸 # new和make的区别 1. make仅用来分配及初始化类型为slice,map,chan的数据,返回引用类型, 2. new可以分配任意数据类型,根据传入的类型申请一块内存,返回指向这块内存的指针 # goroutine和线程的区别 1. 一个线程可以有多个协程 2. 协程可以保留上一次调用的状态,当过程重入时,相当于进入了上一次的调用状态 3. 线程,进程都是同步机制,协程是异步的 4. 协程是需要线程来承载运行的,所以协程不能取代线程 # gin框架为啥快 1. 基于高性能的httpRouter实现 使用基数树(radix tree)实现路由匹配,查找速度快 没有反射开销,所有路由都是基于编译时确定的 支持高效的路径参数解析 2. 极简设计 最小化框架开销,只提供核心功能 避免不必要的抽象层 减少中间件调用链的深度 3. 零内存分配优化 大量使用对象池(sync.pool)来重用对象 减少垃圾回收压力 优化上下文(context)对象的复用 4. 高效的中间件处理 中间键链是预编译的,运行时没有额外开销 中间键采用函数组合而非反射 # mysql的锁 粒度 表级锁: 共享锁:运行多个事务同时读取同一个资源 排他锁: 独占资源,阻止其他事务读写 行级锁: 表锁:锁定整张表 行锁:只锁一行,精细复杂 临键锁:锁住行与行之间的空隙,防止有人插队 并发控制策略 乐观锁:基于版本检查(先假定没人抢,最后提交时检查一下) 悲观锁:基于锁机制(先假定有人抢,先锁住再说) # redis为什么快 内存存储: redis将数据存储在内存中,实现了快速读写操作 高效的数据结构: redis内部使用了高效的数据结构 # redis 哨兵机制 sentinel: 主进程,用来监控redis节点的状态,并执行故障转移操作 monitor: 哨兵进程,用来监控redis的主节点和从节点是否正常工作,并在需要时通知其他哨兵进程和客户端 judge: 哨兵进程,用于对节点健康状态进行评估,并根据预定义的阈值决定是否要降一个不健康的节点标记为“下线” failover: 哨兵进程,负责执行故障转移操作,将主节点故障时选举出来的从节点晋升为新的主节点,并通知其他redis节点更新配置信息 # redis 事务 multi: 开启一个事务,multi执行之后,客户端可以向服务器发送任意多条命令,这些命令不会立即执行,而是被放到一个队列中 exec: 执行队列中所有的命令 discard: 中断当前事务,然后清空事务队列并放弃执行事务 # mysql 索引失效 使用 != 或者 <>导致索引失效 类型不一致导致索引失效 函数导致索引失效 运算符,or,模糊搜索,not in,not exists # go的垃圾回收机制 基础: 使用非分代、并发、三色标记的垃圾回收器,1.5之后低延迟,并发式gc 三色标记法: 白色: 尚未访问的对象 灰色: 已被标记为可达,但其引用的对象未标记 黑色: 自身和其引用的对象都已经被标记为可达 工作流程: 初始化所有对象为白色 从root(全局变量、栈等)开始,将其引用对象标记为灰色 扫描灰色对象,将其引用的对象也标记为灰色,自己转为黑色 扫描完成后,扔为白色对象即为不可达对象,将回收 写屏障:在并发标记的过程中,如果程序还在运行,会不断有新的引用关系建立,gc通过写操作中加屏障,记录这种变化,确保不会漏标对象 # goroutine调度策略 基于gmp模型的用户态调用器,通过将成千上万的goroutine映射到少量操作系统线程上执行, 并结合本地队列优先,全局队列协作、工作窃取及自go1.14引入的抢占式调度机制,实现高效的并发执行, 同时发生系统调用阻塞时自动将线程与调度器解绑,保证系统调度流畅且低延迟运行 工作窃取: 如果一个P的本地队列为空,调度器会尝试从其他P的队列中窃取goroutine 抢占式调度: go调度器会周期性的进行抢占,每当一个goroutine执行一定时间后,调度器就会检查是否要切换到另一个goroutine # 话单生成流程 根据交换机采集数据生成最近几分钟的带宽指标,再通过定时任务区分ecn,异网,推送kafka,再通过消费者去获取kafka的消息写入数据库,做了一个分表,按照月分,没有按年查的需求,使用预处理动态生成查询 ```sql -- 使用预处理语句动态生成查询 PREPARE yearly_query(text, text) AS SELECT * FROM orders_$1 WHERE created_at BETWEEN $2 AND $3 UNION ALL SELECT * FROM orders_$4 WHERE created_at BETWEEN $2 AND $3 ``` # 黑词过滤 DFA算法,将敏感词构建成一颗trie树(字典树),通过多字符匹配的方式快速检测文本中是否包含敏感词 # 集群健康度 查询k8s物理机状态,裸金属物理机状态,以及集群中各种类型的指标,如果不存在给默认值(根据默认值生成评分),根据值生成评分,最终根据不同类型的权重生成不同的分数,生成指标并保存到数据库 # 内存管理 第一层MCache: 是每个P的本地缓存,用于快速分配小对象,线程私有,无需加锁,极大提升了并发性能 第二层MCentral: 是个多线程共享的结构,用于协调和回收span,对应不同大小的size class 第三层MHeap: 全局堆管理器,负责真正的物理内存分配和页管理 go会把32kb以下的对象归为小对象,按照大约70多个size class分类管理,并使用按块切分和向上对其策略,防止内存碎片。大对象则直接向MHeap分配整页内存 在垃圾回收时,go使用三色标记清除算法,未使用的span会被合并回收。整体来说go的分配器设计配合其高效的GC,实现了自动化、并发友好且低延迟的内存管理策略 # go 内存管理 内存分配器:通过Mcache,MCentral,MHeap三级结构提供span,gc负责标记哪些对象还活着,哪些可以清理 清理完成后,gc会把空闲的span归还给内存管理分配器,用于下次分配 整体来说gc是保证内存不会泄露,分配器是保证分配够快