Q:context作用,原理,超时控制
A: golang context的理解,context主要用于父子任务之间的同步取消信号,本质上是一种协程调度的方式。另外在使用context时有两点值得注意:上游任务仅仅使用context通知下游任务不再需要,但不会直接干涉和中断下游任务的执行,由下游任务自行决定后续的处理操作,也就是说context的取消操作是无侵入的;context是线程安全的,因为context本身是不可变的(immutable),因此可以放心地在多个协程中传递使用。
Q:切片和数组区别
A: 数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度。
注意:和C中的数组相比,又是有一些不同的
1. Go中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份
2. 如果Go中的数组作为函数的参数,那么实际传递的参数是一份数组的拷贝,而不是数组的指针。这个和C要区分开。因此,在Go中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
3. array的长度也是Type的一部分,这样就说明[10]int和[20]int是不一样的。array的结构用图示表示是这样的:
[ len|...]
len表示数组的长度,后面的int储存的是实际数据
与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片中有两个概念:一是len长度,二是cap容量,长度是指已经被赋过值的最大下标+1,可通过内置函数len()获得。容量是指切片目前可容纳的最多元素个数,可通过内置函数cap()获得。切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
切片可以通过数组来初始化,也可以通过内置函数make()初始化 .初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容。
Q:channel关闭阻塞问题,goroutine如何调度,gopark是怎么回事?
PMG模型描述,谁创建的PMG,runtime是怎么个东西,怎么启动第一个goroutine
A: golang CPS并发模型和PMG模型的理解。
Q:go逃逸分析怎么回事,内存什么时候栈分配什么时候堆分配
A: 为什么要逃逸分析
C/C++中动态分配的内存需要我们手动释放,导致猿们平时在写程序时,如履薄冰。这样做有他的好处:程序员可以完全掌控内存。但是缺点也是很多的:经常出现忘记释放内存,导致内存泄露。所以,很多现代语言都加上了垃圾回收机制。
Go的垃圾回收,让堆和栈对程序员保持透明。真正解放了程序员的双手,让他们可以专注于业务,“高效”地完成代码编写。把那些内存管理的复杂机制交给编译器,而程序员可以去享受生活。
逃逸分析这种“骚操作”把变量合理地分配到它该去的地方,“找准自己的位置”。即使你是用new申请到的内存,如果我发现你竟然在退出函数后没有用了,那么就把你丢到栈上,毕竟栈上的内存分配比堆上快很多;反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现在退出函数之后还有其他地方在引用,那我就把你分配到堆上。
如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销(占用CPU容量的25%)。
堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”,分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块,之后要通过垃圾回收才能释放。
通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提高程序的运行速度。
逃逸分析如何完成
Go逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸。
简单来说,编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。
Go语言里没有一个关键字或者函数可以直接让变量被编译器分配到堆上,相反,编译器通过分析代码来决定将变量分配到何处。
对一个变量取地址,可能会被分配到堆上。但是编译器进行逃逸分析后,如果考察到在函数返回后,此变量不会被引用,那么还是会被分配到栈上。
简单来说,编译器会根据变量是否被外部引用来决定是否逃逸:
1)如果函数外部没有引用,则优先放到栈中;
2) 如果函数外部存在引用,则必定放到堆中;
针对第一条,可能放到堆上的情形:定义了一个很大的数组,需要申请的内存过大,超过了栈的存储能力。
Q:sync.Map实现原理,适用的场景
A:go 1.9 官方提供sync.Map 来优化线程安全的并发读写的map。该实现也是基于内置map关键字来实现的。
这个实现类似于一个线程安全的 map[interface{}]interface{} . 这个map的优化主要适用了以下场景:
(1)给定key的键值对只写了一次,但是读了很多次,比如在只增长的缓存中;
(2)当多个goroutine读取、写入和覆盖的key值不相交时。
Q:go语言有什么优点和缺点
A: 优势:容易学习,生产力,并发,动态语法。劣势:包管理,错误处理,缺乏框架。
Q:Go框架用过哪些,有看源码吗
A: 优势:beego,go-micro,gin等
Q:Go GC算法,三色标记法描述
Q:Go内存模型(tcmalloc)
A:tcmalloc是线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数
Q:行列都是有序的二维数组,查找k是否存在,时间复杂度
1 3 5 7 9
3 5 7 9 11
4 6 8 10 12
A:二分查找:O(log2(max(m,n)))
Q:有序数组,有2N+1个数,其中N个数成对出现,仅1个数单独出现,找出那个单独出现的数.,时间复杂度
1,1,2,2,3,4,4,5,5,6,6,7,7
答案为3
A: O(log2(2N))二分查找,查找中间位置的数相等值是在左边还是右边?左边则再左子数组继续查找,右边则在右子数组继续查找。
Q:100亿个数求top100,时间复杂度
Q:100亿个数和100亿个数求交集,时间复杂度
A: 全排列问题,自己找去
2、linux,操作系统
Q:Select/epoll,IO多路复用,底层数据结构,epoll的几个函数,两种模式
A: Select/epoll 问题,网上很多
Q:抢占式调度是什么回事
A: 进程优先级和时间分片等方面理解
Q:用户态和内核态
A: 系统态(内核态),操作系统在系统态运行——运行操作系统程序
用户态(也称为目态),应用程序只能在用户态运行——运行用户程序
Q:B+树和B树区别,优缺点
A:B树每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null。只有叶子节点存储data,叶子节点包含了这棵树的所有键值,叶子节点不存储指针,顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针。
Q:B树和二叉查找树或者红黑色区别
A:基础数据结构问题
Q:聚簇索引什么特点,为什么这样,顺序查询的实现,回表查询,联合索引特性
A:
- 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
- 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因
Q:大表分页查询,10亿行数据,查找第N页数据,怎么优化
A: 根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询。
Q:悲观锁和乐观锁,mysql相关锁说一下
A:
乐观锁( Optimistic Locking):对加锁持有一种乐观的态度,即先进行业务操作,不到最后一步不进行加锁,"乐观"的认为加锁一定会成功的,在最后一步更新数据的时候再进行加锁。
悲观锁(Pessimistic Lock):悲观锁对数据加锁持有一种悲观的态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
Q:如何分库分表
A:
1)垂直分表
也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
2)垂直分库
垂直分库针对的是一个系统中的不同业务进行拆分,比如用户User一个库,商品Producet一个库,订单Order一个库。切分后,要放在多个服务器上,提高性能。
3)水平分库分表
将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
4、redis
Q:几种数据结构(list,set,zset,geohash,bitmap)实现原理
Q:pipline用来干嘛
A:pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。
Q:事务
A: redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Q:备份(aof/rdb)原理,哪些参数可调
A:RDB是根据指定的规则定时将内存中的数据备份到硬盘上,AOF是在每次执行命令后命令本身记录下来,所以RDB的备份文件是一个二进制文件,而AOF的备份文件是一个文本文件。
after 900 sec (15 min) if at least 1 key changed
900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
after 300 sec (5 min) if at least 10 keys changed
300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
after 60 sec if at least 10000 keys changed
60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000
AOF有3种方式将操作命令存入AOF文件
1. appendfsync no 不保存
WHRITESAVESAVE
2. appendfsync everysec 每秒钟保存一次
SAVESAVE
WRITESAVESAVE
Q:网络模型,为什么单线程能hold住10万QPS
A:网络模型,I/O复用,Reactor 设计模式,参考:https://blog.csdn.net/ligupeng7929/article/details/90742578。
Q:热点key怎么处理
A:1、热key加载到系统内存中,直接从系统内存中取,而不走到redis层。
2、redis集群,热点备份分布到集群中,避免单台redis集中访问。
Q:一致性hash解决什么问题
A:redis集群和负载均衡
Q:redis集群(主从,高可用,扩展节点)
5、kafka相关
Q:消息是否按照时间有序,kafka分区的数据是否有序,如何保证有序
A:不保证按时间有序,主题在单个分区是有序的。
如何保证有序?kafka topic 只设置一个分区,或者producer将消息发送到指定分区
Q:Kafka为什么吞吐量高
A:
1)顺序读写
kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利用磁盘的顺序读写性能,顺序读写不需要硬盘磁头的寻道时间,只需很少的扇区旋转时间,所以速度远快于随机读写。
2)零拷贝
利用Linux kernel"零拷贝(zero-copy)"系统调用机制,就是跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”。
3)分区
kafka中的topic中的内容可以被分为多分区存在,每个分区又分为多个段,所以每次操作都是针对一小部分做操作,很轻便,并且增加并行操作的能力。
4)批量发送
kafka允许进行批量发送消息,producter发送消息的时候,可以将消息缓存在本地,等到了固定条件发送到kafka
等消息条数到固定条数,一段时间发送一次。
5)数据压缩
Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩
压缩的好处就是减少传输的数据量,减轻对网络传输的压力
Q:kafka的存储模型
A:Kafka一个Topic可以有多个Partition,多个线程,每个线程负责一个Partition进行读写
每个Paratition可以有多个LogSegment,每个LogSegment文件包括一个日志数据文件和两个索引文件(偏移量索引文件和消息时间戳索引文件)。
以上红字部分就是他的存储模型
其中,每个LogSegment中的日志数据文件大小均相等(该日志数据文件的大小可以通过在Kafka Broker的config/server.properties配置文件的中的“log.segment.bytes”进行设置,默认为1G大小(1073741824字节),在顺序写入消息时如果超出该设定的阈值,将会创建一组新的日志数据和索引文件
Kafka的索引文件是采用稀疏索引的方式,每隔一定的字节数建立了一条索引
所以在添加数据时,如果还没有LogSegment,就会建第一个LogSegment,然后把数据顺序写在该LogSegment的日志数据文件里,然后再把索引加到偏移量索引文件里去
偏移量索引文件的每条索引由offset和position组成,每个索引条目可以唯一确定在各个分区数据文件的一条消息
在查找数据时
先根据Position定位到LogSegment,再根据Position和offset在logSegment找到日志数据文件中对应数据的位置
原理就是这样。
Q:Kafka消费者多个group消费同一个topic,会重复消费吗?
A:不会。
8、项目问题
Q:遇到过内存溢出吗?怎么解决
A:主要了解有没有处理过内存泄漏导致的问题,C/C++定位内存泄漏问题;Golang和JAVA主要与GC的工作机制有关,堆内存一直增长,导致应用内存溢出等。
Q:布隆过滤器怎么设置m,n,k的值,怎么合理安排key(用户和item越来越多,怎么保证内存不会爆)
A:m,n,k 网上有实践经验,可参考。item越来越多的话,进行item的拆分,拆分本质是不要将 Hash(Key) 之后的请求分散在多个节点的多个小 bitmap 上,而是应该拆分成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在这一个小 bitmap 上。
Q:服务雪崩怎么处理,怎么解决保证不影响线上
A:限流,降级,熔断方面措施,结合后端系统架构阐述,如网关的限流和快速失败。
Q:redis和mysql数据一致性怎么保证
A:重点考虑业务逻辑上写和数据的流程(异常和错误处理等),结合MQ做异步重试处理。
Q:分布式锁应用场景,哪些坑
A:锁过期了,业务还没执行完;分布式锁,redis主从同步的坑;获取到锁后,线程异常。