1. make 和 new 的区别﹖
new只用于分配内存,返回一个指向地址的指针。它为每个新类型分配一片内存,初始化为0且返回类
型*T的内存地址,它相当于&T{}
make只可用于slice,map,channel的初始化,返回的是引用。
2. 了解过golang的内存管理吗?
golang内存管理基本是参考tcmalloc来进行的。go内存管理本质上是一个内存池,只不过
内部做了很多优化
3. 调用函数传入结构体时,应该传值还是指针﹖说出你的理由?
一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占
用内存较小的结构体,直接传值能够获得更好的性能。
4. 线程有几种模型?Goroutine的原理了解过吗,讲一下实现和优势?
线程的实现模型主要有3个,分别是:用户级线程模型、内核级线程模型和两级线程模型。
(参考https://blog.csdn.net/guoafite/article/details/114833136)
Goroutine是Go语言中并发的执行单位。 Goroutine底层是使用协程(coroutine)实现,
coroutine是一种运行在用户态的用户线程(参考操作系统原理:内核态,用户态)它可以由语
言和框架层调度。
一个Goroutine会以一个很小的栈启动2KB或4KB,当遇到栈空间不足时,栈会自动伸缩,
因此可以轻易实现成千上万个goroutine同时启动。并且上下文切换性能消耗比起操作线程更低。
5. Goroutine什么时候会发生阻塞?
用于原子、互斥量或通道操作导致goroutine阻塞,调度器将把当前阻塞的goroutine从
本地运行队列LRQ换出,并重新调度其它goroutine;
6. PMG模型中Goroutine有哪几种状态?
_Gidle:刚刚被分配并且还没有被初始化,值为0,为创建goroutine后的默认值
_Grunnable: 没有执行代码,没有栈的所有权,存储在运行队列中,可能在某个P的本地队列或全局队列中(如上图)。
_Grunning: 正在执行代码的goroutine,拥有栈的所有权(如上图)。
_Gsyscall:正在执行系统调用,拥有栈的所有权,与P脱离,但是与某个M绑定,会在调用结束后被分配到运行队列(如上图)。
_Gwaiting:被阻塞的goroutine,阻塞在某个channel的发送或者接收队列(如上图)。
_Gdead: 当前goroutine未被使用,没有执行代码,可能有分配的栈,分布在空闲列表gFree,可能是一个刚刚初始化的goroutine,也可能是执行了goexit退出的goroutine(如上图)。
_Gcopystac:栈正在被拷贝,没有执行代码,不在运行队列上,执行权在
_Gscan : GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在。
7. 每个线程/协程占用多少内存知道吗?
协程占用的内存小,大概2KB左右,线程占用内存大概1-2MB。
8. 如果Goroutine—直占用资源怎么办,PMG模型怎么解决的这个问题?
如果有个goroutine一直占用资源,那么GMP模型会从正常模式转变为饥饿模式(类似于mutex)
,允许其它goroutine使用work stealing抢占(禁用自旋锁)。
work stealing算法指,一个线程如果处于空闲状态,则帮其它正在忙的线程分担压力,从全局
队列取一个G任务来执行,可以极大提高执行效率。
9. 如果若干线程中一个线程OOM,会发生什么?如果是Goroutine 呢?
10. 项目中错误处理是怎么做的?
error,
goroutine中有panic需要recover,不然会中断程序
11. 如果若干个Goroutine,其中有一个panic,会发生什么?
导致程序主线程崩溃,切记要回收panic
12. defer可以捕获到其Goroutine的子Goroutine 的panic吗?
defer只能捕获本层的panic,不能捕获子Goroutine的panic
13. 开发用Gin框架吗?Gin怎么做参数校验?
Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误
处理,非常好的支持中间件和 json。
gin框架使用http://github.com/go-playground/validator进行参数校验 在 struct 结构体添加 binding tag,然后调用 ShouldBing 方法
14. 中间件使用过吗?怎么使用的。Gin的错误处理使用过吗?Gin中自定义校验规则知道怎么做吗?自定义校验器的返回值呢?
中间件middlewares使用use方法,gin的中间件其实就是一个HandlerFunc,只要我
们实现一个HandlerFunc,然后作为参数传递进去
Gin对请求参数自定义验证规则可以分三步:
自定义结构体验证绑定binding标签
针对该标签定义验证方法
再将该验证方法注册到validator验证器里面
15. golang中解析tag是怎么实现的?反射原理是什么?通过反射调用函数
反射
具体来说使用reflect.ValueOf方法获取其反射值,然后获取其Type属性,之后再通过Field(i)
获取第i+1个field,再.Tag获得Tag。
16. golang的锁机制了解过吗? Mutex的锁有哪几种模式,分别介绍一下? Mutex锁底层如何实现了解过吗?
互斥锁、读写锁
正常模式、饥饿模式
正常模式就是会没有公平性,新进的协程和自旋很久的线程谁抢到谁执行。
饥饿模式会采取队列模式,先到先得,有公平性。
17. channel、channel使用中需要注意的地方?
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或
者接收数据进行通讯(communication)。
它的操作符是箭头 <-
容易阻塞,考虑使用select解决或者使用waitgroup
18. 数据库用的什么?数据库锁有了解吗?mysql锁机制讲一下。mysql分库分表。
MySQL Redis
数据库锁的种类一般分为两种:一种是悲观锁,一种乐观锁。
悲观锁(Pessimistic Lock)具有强烈的独占和排他特性,它指的是对数据被外界修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制,大多是基于版本号( Version )记录机制实现,而不需要借助数据库的锁机制。
当一张表的数据达到千万级时甚至亿级时,查询一次所花的时间会变多,如果有联合查询的话可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。
分表:将一个表中的数据按照某种规则分拆到多张表中,降低锁粒度以及索引树,提升数据查询效率。
分库:将一个数据库中的数据按照某种规则分拆到多个数据库中,以缓解单服务器的压力(CPU、内存、磁盘、IO)。
1.垂直拆分:
1.1)垂直拆表
即大表拆小表,将一张表中数据不同”字段“分拆到多张表中,比如商品库将商品基本信息、商品库存、卖家 信息等分拆到不同库表中。
考虑因素有将不常用的,数据较大,长度较长(比如text类型字段)的拆分到“扩展表“,表和表之间通过”主键外键“进行关联。
好处:降低表数据规模,提升查询效率,也避免查询时数据量太大造成的“跨页”问题。
1.2)垂直拆库
垂直拆库则在垂直拆表的基础上,将一个系统中的不同业务场景进行拆分,比如订单表、用户表、商品表。
好处:降低单数据库服务的压力(物理存储、内存、IO等)、降低单机故障的影响面
2.水平拆分:
操作:将总体数据按照某种维度(时间、用户)等分拆到多个库中或者表中,典型特征不同的库和表结构完全一下,如订单按照(日期、用户ID、区域)分库分表。
2.1) 水平拆表
将数据按照某种维度拆分为多张表,但是由于多张表还是从属于一个库,其降低锁粒度,一定程度提升查询性能,但是仍然会有IO性能瓶颈。
2.2) 水平拆库
将数据按照某种维度分拆到多个库中,降低单机单库的压力,提升读写性能。
19. 讲一下redis分布式锁?redis主从模式和集群模式的区别了解过吗?redis的数据类型有哪些?redis持久化怎么做的?
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
1.SETNX + EXPIRE
即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。
2.SET的扩展命令(SET EX PX NX)
除了使用,使用Lua脚本,保证SETNX + EXPIRE两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数!(SET key value[EX seconds][PX milliseconds][NX|XX]),它也是原子性的!
问题一:「锁过期释放了,业务还没执行完」,可以考虑使用watch dog看门狗机制隔断时间延长expire
问题二:「锁被别的线程误删」
3.多机实现的分布式锁Redlock
1.获取当前时间,以毫秒为单位。
2.按顺序向5个master节点请求加锁。客户端设置网络连接和响应超时时间,并且超时时间要小于锁的失效时间。(假设锁自动失效时间为10秒,则超时时间一般在5-50毫秒之间,我们就假设超时时间是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。
3.客户端使用当前时间减去开始获取锁时间(即步骤1记录的时间),得到获取锁使用的时间。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都获得锁,并且使用的时间小于锁失效时间时,锁才算获取成功。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms)
如果取到了锁,key的真正有效时间就变啦,需要减去获取锁所使用的时间。
如果获取锁失败(没有在至少N/2+1个master实例取到锁,有或者获取锁时间已经超过了有效时间),客户端要在所有的master节点上解锁(即便有些master节点根本就没有加锁成功,也需要解锁,以防止有些漏网之鱼)。
简化下步骤就是:
按顺序向5个master节点请求加锁
根据设置的超时时间来判断,是不是要跳过该master节点。
如果大于等于3个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
如果获取锁失败,解锁!
redis的三种集群方案(主从复制、哨兵模式、集群模式)
主从复制是指一台redis服务器的数据复制到其他的redis服务器,前者为主节点(master),后者为从节点(slave)。数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台redis服务器都是主节点,一个主节点可以有0或者多个从节点,但从节点只能有一个主节点。
主节点提供读写服务,从节点提供读服务,多个从节点分担读负载,提高了redis的并发量。
主从复制的优缺点
优点:
实现了读写分离,缓解了主节点读操作的压力,提高了可用性,解决了单机故障问题
主从复制期间主节点与从节点都是非阻塞的方式,仍然可用
缺点:
如果主节点发生宕机,需要手动切换主节点
如果RDB文件过大,同步过程比较耗时
-------------------------------------------------------------
对于主从复制,当主节点服务宕机后,就需要手动把一个从节点切换为主节点,需要人工干预,redis从2.8版本正式提供的哨兵模式(Sentinel)解决了这个问题,保证了服务的可用性
哨兵模式的优缺点
优点:
哨兵模式基于主从模式,具备主从复制的优点
具备了自动主从切换和故障转移,有了更高的可用性
缺点:
只有主节点可以进行写操作,写操作收单机影响
在线扩容比较复杂
-------------------------------------------------------------
集群,即redis cluster,redis3.0正式推出的分布式存储方案,它是去中心化的,客户端可以连接任意一个可用节点
集群模式的优缺点
优点:
集群完全去中心化,多主多从(客户端不需要关注数据在存储在哪个节点,只关注集合整体)
数据通过虚拟哈希槽分布到多个节点,可动态调整数据分布
可以线性扩展(官方推荐不超过1000个)
高可用,可以进行主从切换
缺点:
客户端实现复杂(客户端在本地维护一份slot-node的映射关系,不需要重定向),需要实现smart client
数据通过异步复制,不保证数据的强一致性
多个业务使用同一套集群,无法根据统计区分冷热数据,资源隔离性较差
Redis数据类型有5种,分别是string(字符串),hash(哈希),list(列表),
set(集合)及zset(sorted set:有序集合)。
20. 编程题:你了解的负载均衡算法有什么?实现一个负载均衡算法。
略