大家好,又见面了,我是全栈君。

解答题:go相关

go的调度

答:

go的调度原理是基于GMP模型,G代表一个goroutine,不限制数量;M=machine,代表一个线程,最大1万,所有G任务还是在M上执行;P=processor代表一个处理器,每一个允许的M都会绑定一个G,默认与逻辑CPU数量相等(通过runtime.GOMAXPROCS(runtime.NumCPU())设置)。

go调用过程:

一次性转移(全局G个数/P个数)个,再去其它P中找(一次性转移一半)

go struct能不能比较

答:

可以能,也可以不能。

因为go存在不能使用==判断类型:map、slice,如果struct包含这些类型的字段,则不能比较。

这两种类型也不能作为map的key。

go defer(for defer)

答:

类似栈操作,后进先出。

return i

select可以用于什么

答:

  1. goroutine超时设置,防止goroutine一直执行导致内存不释放等问题。
  2. 判断channel是否已满或空。如实现一个池线程,当channel已被写满,暂无空闲worker在进行读取,进入default,返回一个暂无可分配资源错误。

select的case的表达式必须是一个channel类型,所有case都会被求值,求值顺序自上而下,从左至右。如果多个case可以完成,则会随机执行一个case,如果有default分支,则执行default分支语句。如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。

break关键字可跳出select的执行。

context包的用途

答:

goroutine管理、信息传递。context的意思是上下文,在线程、协程中都有这个概念,它指的是程序单元的一个运行状态、现场、快照,包含。context在多个goroutine中是并发安全的。

应用场景:

  1. 超时:
  1. 通信并达到取消并发goroutine继续执行的目的。
  2. 数据传递
  3. client如何实现长连接

主协程如何等其余协程完再操作

waitgroup

channel

slice,len,cap,共享,扩容

答:

len:切片的长度,访问时间复杂度为O(1),go的slice底层是对数组的引用。

cap:切片的容量,扩容是以这个值为标准。默认扩容是2倍,当达到1024的长度后,按1.25倍。

扩容:每次扩容slice底层都将先分配新的容量的内存空间,再将老的数组拷贝到新的内存空间,因为这个操作不是并发安全的。所以并发进行append操作,读到内存中的老数组可能为同一个,最终导致append的数据丢失。

共享:slice的底层是对数组的引用,因此如果两个切片引用了同一个数组片段,就会形成共享底层数组。当sliec发生内存的重新分配(如扩容)时,会对共享进行隔断。详细见下面例子:

make([]Type,len,cap)

map如何顺序读取

答:

map的底层是hash table(hmap类型),对key值进行了hash,并将结果的低八位用于确定key/value存在于哪个bucket(bmap类型)。再将高八位与bucket的tophash进行依次比较,确定是否存在。出现hash冲撞时,会通过bucket的overflow指向另一个bucket,形成一个单向链表。每个bucket存储8个键值对。

如果要实现map的顺序读取,需要使用一个slice来存储map的key并按照顺序进行排序。

实现set

答:

利用map,如果要求并发安全,就用sync.map

delete(map)

实现消息队列(多生产者,多消费者)

答:

使用sync.Map来管理topic,用channel来做队列。

参考:

大文件排序

多路归并法:

<pre class=”vditor-reset” placeholder=”” contenteditable=”true” spellcheck=”false”><p data-block=”0″>(1)假设有K路<a href=”https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E6%B5%81/3002243″>数据流</a>,流内部是有序的,且流间同为升序或降序; </p><p data-block=”0″>(2)首先读取每个流的第一个数,如果已经EOF,pass; </p><p data-block=”0″>(3)将有效的k(k可能小于K)个数比较,选出最小的那路mink,输出,读取mink的下一个; </p><p data-block=”0″>(4)直到所有K路都EOF。 </p></pre>

假设文件又1个G,内存只有256M,无法将1个G的文件全部读到内存进行排序。

第一步:

可以分为10段读取,每段读取100M的数据并排序好写入硬盘。

假设写入后的文件为A,B,C…10

第二步:

将A,B,C…10的第一个字符拿出来,对这10个字符进行排序,并将结果写入硬盘,同时记录被写入的字符的文件指针P。

第三步:

将刚刚排序好的9个字符再加上从指针P读取到的P+1位数据进行排序,并写入硬盘。

重复二、三步骤。

go读文件的两种方式

go文件读写参考:

基本排序,哪些是稳定的

答:

保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同的排序叫稳定排序。

快速排序、希尔排序、堆排序、直接选择排序不是稳定的排序算法。

基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

参考:

网络、架构相关

http get跟head

答:

head只请求页面的首部。多用来判断网页是否被修改和超链接的有效性。

get请求页面信息,并返回实例的主体。

参考:

http 401,403

401:未授权的访问。

403: 拒绝访问。

http keep-alive

普通的http连接是客户端连接上服务端,然后结束请求后,由客户端或者服务端进行http连接的关闭。下次再发送请求的时候,客户端再发起一个连接,传送数据,关闭连接。这么个流程反复。但是一旦客户端发送connection:keep-alive头给服务端,且服务端也接受这个keep-alive的话,两边对上暗号,这个连接就可以复用了,一个http处理完之后,另外一个http数据直接从这个连接走了。减少新建和断开TCP连接的消耗。这个可以在Nginx设置,

这个keepalive_timout时间值意味着:一个http产生的tcp连接在传送完最后一个响应后,还需要hold住keepalive_timeout秒后,才开始关闭这个连接。

特别注意TCP层的keep alive和http不是一个意思。TCP的是指:tcp连接建立后,如果客户端很长一段时间不发送消息,当连接很久没有收到报文,tcp会主动发送一个为空的报文(侦测包)给对方,如果对方收到了并且回复了,证明对方还在。如果对方没有报文返回,重试多次之后则确认连接丢失,断开连接。

tcp的keep alive可通过

net.ipv4.tcp_keepalive_intvl = 75 // 当探测没有确认时,重新发送探测的频度。缺省是75秒。 net.ipv4.tcp_keepalive_probes = 9 //在认定连接失效之前,发送多少个TCP的keepalive探测包。缺省值是9。这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之后可以有多少时间没有回应 net.ipv4.tcp_keepalive_time = 7200 //当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。一般设置为30分钟1800

修改:

http能不能一次连接多次请求,不等后端返回

可以

tcp与udp区别,udp优点,适用场景

tcp是面向连接的,upd是无连接状态的。

udp相比tcp没有建立连接的过程,所以更快,同时也更安全,不容易被攻击。upd没有阻塞控制,因此出现网络阻塞不会使源主机的发送效率降低。upd支持一对多,多对多等,tcp是点对点传输。tcp首部开销20字节,udp8字节。

udp使用场景:视频通话、im聊天等。

time-wait的作用

time-wait表示客户端等待服务端返回关闭信息的状态,closed_wait表示服务端得知客户端想要关闭连接,进入半关闭状态并返回一段TCP报文。

time-wait作用:

  1. 防止上一次连接的包迷路后重复出现,影响新连接
  2. 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。

解决办法:

close_wait:

被动关闭,通常是由于客户端忘记关闭tcp连接导致。

数据库如何建索引

根据业务来啊~

重要指标是cardinality(不重复数量),这个数量/总行数如果过小(趋近于0)代表索引基本没意义,比如sex性别这种。

另外查询不要使用select *,根据select的条件+where条件做组合索引,尽量实现覆盖索引,避免回表。

孤儿进程,僵尸进程

僵尸进程:

即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。

但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。

死锁条件,如何避免

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。 (2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 (3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 (4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 避免方法:

  1. 为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增加。
  2. 多个程序尽量约定以相同的顺序访问表(这也是解决并发理论中哲学家就餐问题的一种思路),以固定的顺序访问表和行。比如两个更新数据的事务,事务A更新数据的顺序为1,2;事务B更新数据的顺序为 2 ,1,这样更可能会造成死锁。
  3. 同一个事务尽可能做到一次锁定所需要的所有资源。

linux命令,查看端口占用,cpu负载,内存占用,如何发送信号给一个进程

端口占用:lsof -i:端口号 或者 nestat

cpu、内存占用:top

发送信号:kill -l 列出所有信号,然后用 kill [信号变化] [进程号]来执行。如kill -9 453。强制杀死453进程

git文件版本,使用顺序,merge跟rebase

git log:查看提交记录

git diff :查看变更记录

git merge:目标分支改变,而源分支保持原样。优点:保留提交历史,保留分支结构。但会有大量的merge记录

git rebase:将修改拼接到最新,复杂的记录变得优雅,单个操作变得(revert)很简单;缺点:

  • Rebase 后 feature 分支间的上下文模糊了
  • 在团队里 rebasing 公共分支是高风险的事
  • 工作变多了:feature 分支需要经常更新

git revert:反做指定版本,会新生成一个版本

git reset:重置到某个版本,中间版本全部丢失

项目实现爬虫的流程

爬虫如何做的鉴权吗

怎么实现的分布式爬虫

电商系统图片多会造成带宽过高,如何解决

micro服务发现

mysql底层有哪几种实现方式

channel底层实现

java nio和go 区别

读写锁底层是怎么实现的

go-micro 微服务架构怎么实现水平部署的,代码怎么实现

micro怎么用

怎么做服务发现的

etcd、Consul

raft算法是那种一致性算法,有什么特点

当go服务部署到线上了,发现有内存泄露,该怎么处理

pprof

go的GC原理

mysql

mysql索引为什么要用B+树?

节省空间(非叶子节点不存储数据,相对b tree的优势),减少I/O次数(节省的空间全部存指针地址,让树变的矮胖),范围查找方便(相对hash的优势)。

mysql语句性能评测?

explain

其他的见:

程序代码题

1.以下代码的打印结果和为什么

runtime2.go 中关于 p 的定义: 其中 runnext 指针决定了下一个要运行的 g,根据英文的注释大致意思是说:

  • 1 . 如果 runnext 不为空且是就绪的,那么应该优先运行该 runnext 指向的 g 而不是运行队列中到达运行时间的 g。
  • 2 . 此时要被运行的 g 将继承当前 g 剩余的时间片继续执行。
  • 3 . 之所以这样做是为了消除将最后的 g 添加到队列末尾导致的潜在的调度延迟。

所以当设置 runtime.GOMAXPROCS(1) 时,此时只有一个 P,创建的 g 依次加入 P, 当最后一个即 i==9 时,加入的最后 一个 g 将会继承当前主 goroutinue 的剩余时间片继续执行,所以会先输出 9, 之后再依次执行 P 队列中其它的 g。

写sql问题

按半小时统计日志数量

删除除了主键id不相同,其他字段均相同的数据。

获取每科分数均高于80分的学生姓名

行转列

方法一:

方法二:

温度高低问题

[图片上传失败…(image-4ef445-1594976286098)]

方法1:to_days,返回给的日期从0开始算的天数。

方法2:data_add。向日期添加指定时间间隔

mysql查出数据表中连续出现三次或三次以上的数据

[图片上传失败…(image-b67b10-1594976286098)]

golang 100题