Go面试题
本文最后更新于 2024-09-22,文章内容可能已经过时。
mutex
Mutex互斥锁的运行机制保证了在任意时刻只有一个goroutine能够持有锁,从而实现了对共享资源的互斥访问。
在Golang中,Mutex互斥锁有两种模式:正常模式(Normal Mode)和饥饿模式(Starvation Mode)
type Mutex struct {
state int32
sema uint32
}
Mutex互斥锁内部通常包含以下几个主要的数据结构:
锁状态(Lock State):用于表示锁的当前状态,包括锁是否被加锁以及加锁的goroutine信息等。
等待队列(Wait Queue):用于管理等待锁的goroutine,通常是一个先进先出(FIFO)的队列。等待队列中保存着等待锁的goroutine的相关信息,如goroutine的标识符、状态等。
自旋计数器(Spin Counter):用于记录自旋的次数。当一个goroutine尝试获取锁但锁处于加锁状态时,会进行自旋操作。自旋计数器记录了自旋的次数,当自旋次数达到一定阈值时,会将goroutine放入等待队列中。
锁持有者(Lock Holder):用于记录当前持有锁的goroutine的信息,包括goroutine的标识符、状态等。只有锁持有者才能够解锁。
正常模式
正常模式是Mutex互斥锁的默认模式。在正常模式下,Mutex采用公平的先进先出策略。当一个goroutine尝试获取锁时,如果锁处于加锁状态,该goroutine会被放入等待队列中,等待锁的释放。当锁被解锁后,等待队列中的goroutine会按照先后顺序获取锁。
正常模式下,所有等待锁的 goroutine 按照 FIFO(先进先出)顺序等待。唤醒的goroutine 不会直接拥有锁,而是会和新请求锁的 goroutine 竞争锁。新请求锁的 goroutine 具有优势:它正在 CPU 上执行,而且可能有好几个,所以刚刚唤醒的 goroutine 有很大可能在锁竞争中失败,长时间获取不到锁,就会切换到饥饿模式.
饥饿模式
饥饿模式是一种非公平的模式。当某个goroutine连续多次尝试获取锁但一直失败时,Mutex可能会切换到饥饿模式。在饥饿模式下,Mutex不再采用公平的策略,而是采用非公平的策略。即当锁被解锁后,下一个获取锁的goroutine不一定是等待时间最长的goroutine,而是可能是最后一次尝试获取锁失败的goroutine。
在 Go 1.16 版本中,引入了 Mutex 饥饿模式的改进。在该版本中,饥饿模式的行为发生了一些变化,以更好地平衡公平性和性能。
具体来说,Go 1.16 中的 Mutex 饥饿模式改进包括以下几个方面:
自旋:在饥饿模式下,Mutex 会引入自旋操作。当一个 goroutine 尝试获取锁但锁处于加锁状态时,该 goroutine 会进行一定次数的自旋操作,尝试在短时间内获取到锁而不进入等待队列。这样可以减少等待队列的竞争,提高性能。
饥饿模式的切换:在 Go 1.16 中,饥饿模式的切换更加智能和平滑。当一个 goroutine 连续多次尝试获取锁但一直失败时,Mutex 会逐渐降低自旋次数,直到最后将该 goroutine 放入等待队列中。这样可以避免某个 goroutine 长时间占用锁,提高公平性。
公平性保证:尽管引入了自旋操作,Go 1.16 仍然保持了对公平性的关注。当一个 goroutine 进入等待队列后,它会等待一段时间,以确保其他 goroutine 有机会获取到锁。这样可以避免某个 goroutine 长时间自旋而导致其他 goroutine 等待过久。
关于进入饥饿模式的等待时间,具体的时间是由运行时系统自动管理的,取决于锁的状态和运行情况。
允许自旋的条件:
锁已被占用,并且锁不处于饥饿模式。
积累的自旋次数小于最大自旋次数(active_spin=4)。
cpu 核数大于 1。
有空闲的 P。
当前 goroutine 所挂载的 P 下,本地待运行队列为空。
待整理
秒杀怎么实现的,为什么不使用原子操作
redis数据类型
lnmp请求流程
nginx反向代理
跨域怎么解决
nginx负载均衡
http状态码
mysql优化-隐式转换-用不到索引的情况-索引类型-
go micro
go字符串处理函数
map相关
错误处理 定义错误类型 panic recover
事物隔离级别
git pull--rebase
mysql规范
索引主键以pk_开头,唯一uk uq,普通idx
创建表显式制定字符集为utf8或者utf8bm4
表中所有字段必须是not null
反范式设计。
IP地址推荐用int不建议用char
字段类型选择的时候多用tinyint smallint,不推荐使用enum或者set,变更不方便
金钱使用int*100
时间尽量使用timestamp占用4字节。datetime占用8字节。当然可以存时间戳更好
单个表索引不超过7个,多考虑联合索引
join表确保有走索引,小表驱动大表,
分库分表,分库不超过1024 分表不超过4096,单表不超过500w行,ibd大小不超过2g
日志报表等建议日期分表
select禁止*,in的数量限制在500,减少底层扫描
不可预知结果limit
insert 写明具体字段 values不超过5000个,不然会造成主从延迟
union all 取代union
事务里批量更新需要控制数量,并且要进行必要的sleep,一般建议5-10秒,少量多次
少用or,优化成union all
limit 起始值比较大,要通过where限制
尽量使用到覆盖索引
禁止关联子查询
新代码不建议使用model操作数据库,建议还是通过sql,可能orm框架生成的sql非常复杂,且model层自己做的强制类型转换比较差
mysql-redis链接需要失败尝试机制,并且 如果有连接池,需要配置最小最大 回收 超时机制等,避免资源耗尽
尽量贴出sql或者redis底层的报错方便排查
select 满足多个case怎么执行
随机执行,没有满足时阻塞等待,已关闭的chan不会被执行
为什么grpc传输比json快
二进制编码
gRPC 使用 Protocol Buffers 进行数据编码,它以二进制格式存储数据,相比 JSON 的文本格式更加紧凑。二进制编码减少了数据的存储空间,在网络传输中能够更快地发送和接收数据。
例如,一个包含多个字段的复杂数据结构,用 Protocol Buffers 编码后可能只占用几十字节,而用 JSON 编码可能需要几百字节甚至更多。
高效的编码和解码算法
Protocol Buffers 的编码和解码算法经过高度优化,能够快速地将数据转换为二进制格式和从二进制格式转换回数据结构。这些算法通常比 JSON 编码和解码算法更快,尤其是在处理大量数据时。
HTTP/2 支持
gRPC 通常运行在 HTTP/2 协议之上,HTTP/2 具有多路复用、头部压缩等特性,可以更有效地利用网络连接。
对象存储和文件存储的区别
对象存储和文件存储是两种不同的存储方式,它们在多个方面存在区别:
一、数据组织方式
对象存储
对象存储以对象为基本单元来存储数据。每个对象通常包含数据本身、元数据和唯一标识符。
元数据可以描述对象的各种属性,如创建时间、大小、访问权限等。对象通过其唯一标识符进行访问,而不是像传统文件系统那样通过文件路径。
例如,在一个对象存储系统中,一张图片可以被视为一个对象,它的图像数据是对象的主体,而图片的尺寸、拍摄时间等信息可以作为元数据。
文件存储
文件存储以文件和文件夹的层次结构来组织数据。文件存储系统使用目录树来管理文件,通过文件路径来定位和访问文件。
文件通常由数据和文件属性组成,文件属性包括文件名、创建时间、修改时间、文件大小等。
例如,在传统的文件系统中,文件存储在不同的文件夹中,通过文件路径如 “/documents/report.pdf” 来访问特定的文件。
二、访问方式
对象存储
对象存储通常通过 HTTP 或 HTTPS 协议进行访问,使用 RESTful API 或特定的对象存储客户端库。
这种访问方式使得对象存储可以在不同的网络环境中轻松访问,并且具有良好的可扩展性和跨平台性。
例如,可以使用编程语言中的对象存储客户端库,通过发送 HTTP 请求来上传、下载和管理对象。
文件存储
文件存储通常通过文件系统协议进行访问,如 NFS(Network File System)、SMB(Server Message Block)等。
用户可以像访问本地文件系统一样,通过挂载文件存储到本地操作系统,或者通过网络文件系统客户端来访问远程文件存储。
例如,在 Windows 操作系统中,可以通过映射网络驱动器的方式访问远程的文件存储,在文件资源管理器中进行文件的创建、删除、修改等操作。
三、扩展性和性能
对象存储
对象存储具有高度的可扩展性,可以轻松应对大规模数据存储和高并发访问的需求。
对象存储系统通常可以扩展到数百 PB 甚至 EB 的规模,并且可以通过添加更多的存储节点来实现线性扩展。
对象存储的性能主要取决于网络带宽和存储节点的处理能力,对于大量小文件的存储和访问可能不如文件存储高效。
例如,一些大型的云存储服务提供商如 Amazon S3、阿里云 OSS 等都是基于对象存储技术,可以为用户提供海量的存储容量和高可靠的服务。
文件存储
文件存储的扩展性相对有限,尤其是在大规模分布式环境中。文件存储系统的性能可能会受到文件系统结构、网络带宽、存储设备性能等因素的影响。
对于大量小文件的存储和访问,文件存储系统可能会出现性能瓶颈,因为文件系统需要维护大量的文件目录和文件元数据。
例如,在一个企业内部的文件服务器中,如果存储的文件数量过多,可能会导致文件系统性能下降,影响用户的访问体验。
四、适用场景
对象存储
对象存储适用于大规模数据存储、云存储、多媒体内容存储、备份和归档等场景。
例如,互联网公司可以使用对象存储来存储用户上传的图片、视频等多媒体内容;企业可以将备份数据存储在对象存储中,以实现数据的长期保存和灾难恢复。
文件存储
文件存储适用于传统的文件共享、办公文档存储、数据库备份等场景。
例如,企业内部的员工可以通过文件存储共享文件,进行协同办公;数据库管理员可以将数据库备份存储在文件存储中,以便在需要时进行恢复。
综上所述,对象存储和文件存储在数据组织方式、访问方式、扩展性和性能以及适用场景等方面存在明显的区别。在选择存储方式时,需要根据具体的应用需求和场景来进行综合考虑
Innodb中的锁类型
InnoDB存储引擎中的锁机制是其实现事务隔离和并发控制的核心部分。InnoDB支持多种类型的锁,包括但不限于以下几种:
1. 共享锁(S)和排他锁(X):共享锁允许事务读取一行数据,而排他锁则允许事务更新或删除一行数据。多个事务可以同时持有同一行的共享锁,但排他锁在同一行上一次只能由一个事务持有。
2. 意向锁(Intention Locks):这是表级别的锁,用于表示事务即将对表中的某些行加共享或排他锁。意向锁分为意向共享锁(IS)和意向排他锁(IX)。
3. 记录锁(Record Locks):这种锁锁定索引记录,用于阻止其他事务对特定行进行插入、更新或删除操作。
4. 间隙锁(Gap Locks):锁定索引记录之间的间隔,或第一条索引记录之前或最后一条索引记录之后的间隔,用于防止幻读。
5. 临键锁(Next-Key Locks):是记录锁和间隙锁的组合,用于锁定一个索引记录及其前面的间隙。
6. 插入意向锁(Insert Intention Locks):当多个事务尝试在同一位置插入行时,插入意向锁会介入以避免冲突。
7. 自增锁(AUTO-INC Locks):这是一种特殊的表级别锁,用于管理带有AUTO_INCREMENT属性的列,确保生成的自增ID值是连续的。
8. 空间索引谓词锁(Predicate Locks for Spatial Indexes):这种锁用于空间索引,以支持空间数据类型的查询。
在实际操作中,InnoDB的行锁是基于索引实现的,如果没有使用索引,锁可能会退化为表锁。此外,InnoDB的锁机制与事务隔离级别密切相关,不同的隔离级别下锁的行为也会有所不同。例如,在可重复读(Repeatable Read)隔离级别下,InnoDB使用临键锁来避免幻读问题。
了解这些锁的类型和行为对于数据库的优化和并发控制至关重要,可以帮助开发者设计出更高效、更稳定的数据库操作。