Back
Featured image of post 题目集锦

题目集锦

1. 计算机网络

1.1. HTTPS 和 http 区别。是如何保证安全的

  1. 表现不同
    1. 端口不同,http80https443
    2. http因为缺少了加密的步骤,所以响应速度比https快
    3. https 需要用到证书(CA)
    4. http明文,https秘文
  2. 原理不同
    1. https是ssl/tls之上的http协议,所以更耗费服务器资源
    2. https = http(tcp 三次握手三个包) + ssl(9个包)

1.2. 浏览器崩溃了、是客户端还是服务端断开连接的

1.3. 在浏览器输入一个网址,数据是如何到服务端的。经历了哪些过程

1.4. https的通讯过程

  1. 客户端请求服务端证书公钥
  2. 服务端返回公钥&证书
  3. 客户端验证证书
    1. 验证证书所有者有效期
    2. 查找本地CA是否匹配获取的CA
    3. 通过CA的公钥签名对比
  4. 客户端加密证随机key
  5. 服务端根据私钥解析出key
  6. 使用随机key进行对称加密

1.5. tcp

1.6. client 是如何实现长连接的

  1. 主要通过tcp的心跳和业务心跳,参考链接https://www.cnblogs.com/sunsky303/p/10414146.html

1.7. 三次握手

  1. 第一次,客户端发(sync=1 seq x ack=0) 状态变成sync_send
  2. 第二次,服务端发(sync=x+1 seq y ack=x+1) 状态 (closed->listen->sync_received)
  3. 第三次,客户端发(sync=2 seq x+1 ack=y+1) 状态(established)
  4. 补充:
    1. 也不是所有的都是三次,在双方同时发sync时候就是四次了(三次的只是合并了一次确认sync和发送sync的步骤)
    2. TCP 连接的初始化序列号不固定,主要是因为是长链接不是一问一答形式的
    3. SYNC 连接超时问题,client 请求SYNC后挂了,服务端响应SYNC_ACK,会出线 1s 2s 4s 8s 16s 分别进行重试,第五次需要等32s确认超时,需要63秒才会断开
    4. 补充链接:https://blog.csdn.net/qzcsu/article/details/72861891

1.8. 四次挥手

  1. 第一次发送 fin(seq=x+2,ack=y+1),客户端状态变成FIN_WAIT1
  2. 第二次服务端响应 ack(ack=X+3) 状态变成close_wait 客户端收到后变成FIN_WAIT2,
  3. 第三次发送也是服务端发送 fin(seq=y+1) 状态变成LAST_ACK
  4. 第四次客户端发送后变成 TIME_WAIT
  5. 补充
    1. tcp的peer同时断开的话,两端会出现 FIN_WAIT1->CLOING->TIME_WAIT
    2. 四次挥手能不能变成三次呢?答案是可以的,只需要在第一次收到FIN包时候发现没有数据传输了,这个时候可以合并FIN&ACK变成一个包
    3. 主动关闭方会进入time_wait一遍能够重发丢掉的被动方的FIN的ack
    4. time——wait的必要性
      1. 关于connection reset by peer,一般是主动关闭方未存在time——wait的话,被动方未收到fin的Ack,出现被动方重新发送FIN的情况,主动方就会回复一个RST,如果收到RST后还写入数据就是BROKEN_PIPE
      2. 防止已经断开的连接终止掉新链接
    5. time_wait的问题
      1. 服务器会占据大量的资源,客户端会占据大量的端口

2. 数据库

2.1. redis

2.1.1. redis数据结构分类

  1. string(字符串):最基础的类型,最大能存储512m 一个 key 对应一个 value。适用缓存更新不频繁,读取场景较多
  2. hash(哈希):适用于缓存更新频繁,每次只想去json数据少数几个健,提供hget hmget 无需二次处理
  3. list(列表):
  4. set(集合)
  5. zset(sorted set:有序集合)。

2.1.2. zset如何实现

2.1.3. 为什么不实现map

2.1.4. redis持久化机制

2.1.5. redis优缺点

2.1.6. 项目里面redis使用的持久化方式

2.1.7. 多路复用机制

2.2. mysql

2.2.1. 主从同步原理

2.2.2. 主从延迟如何解决

2.2.3. 聚簇索引特点

2.2.4. innodb为什么选择b+树

3. 操作系统

3.1. 基础问题

3.1.1. Linux 到底是怎么收发网络包的

3.1.2. ssh 都机器上去了,断网了,然后连接网络还能保持连接。其中的工作原理(我遇到的为啥都是断开连接了)

4. golang语言

4.1. 基础问题

4.1.1. string 和 byte 是啥关系

  1. string为一个bytes数组的指针+长度
  2. string不为nil,无法修改值的内容
  3. bytes可以修改值的内容,比较灵活

4.1.2. byte 和 rune 区别

  1. byte 一个字节,八个比特位,rune四个字节,32个比特位,前者处理 ASCII 码,后者处理中英文字符
  2. byte 和 uint8 没有区别rune 和 uint32 没有区别

4.1.3. channel是否不需要关闭

  1. 默认可以不关闭,会被GC回收
  2. channel关闭主要是为了通知下游不会再读写数据

4.1.4. 互斥锁如何实现公平

  1. 参考链接https://www.jianshu.com/p/ce1553cc5b4f

4.1.5. new和make区别

  1. new返回的是指针,
  2. make()仅支持特定类型参数(slice/map/channel)

4.1.6. map

  1. map的value是不可变的,如果需要变更,需要value变成指针
  2. sync.map 是没有len的方法的

4.1.7. defer执行顺序

  1. defer为定义顺序的倒序,
  2. defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn

4.1.8. range关键字

  1. 出现range的时候,不要引用变量的指针,因为变量只有一个,指针是不会发生变化的

4.1.9. 调度器

  1. 从runtime的源码可以看到,当创建一个G时,会优先放入到下一个调度的runnext字段上作为下一次优先调度的G。因此,最先输出的是最后创建的G,也就是9.

4.1.10. 字符串

  1. 字符串为不可变量,不能赋值为nil也不能和nil比较
  2. 字符串不产生内存拷贝的复制( slice本身比字符串多了cap)
    1. unsafe.Pointer(&a) 得到a的内存地址
    2. (*reflect.StringHeader)(unsafe.Pointer(&a)) 可以把字符串a转成底层结构的形式
    3. (*[]byte)(unsafe.Pointer(&ssh)) 可以把ssh底层结构体转成byte的切片的指针。
    4. 再通过 *转为指针指向的实际内容。

4.1.11. slice

  1. 切片是可变的
  2. 切片之间不能比较,数组可以

4.1.12. sync.Mutex 与 sync.RWMutex 区别

  1. 前者为互斥锁,读写只能一个锁
  2. 后者为读写锁,多读单写
  3. 加锁后复制变量,会将锁的状态也复制,所以mu1 其实是已经加锁状态,再加锁会死锁。

4.1.13. channel在关闭情况下读写

  1. 读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
    1. 如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。
    2. 如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。
  2. 写已经关闭的 chan 会 panic

4.1.14. 内存逃逸

  1. 在方法内把局部变量指针返回 局部变量原本应该在栈中分配,在栈中回收。但是由于返回时被外部引用,因此其生命周期大于栈,则溢出。
  2. 发送指针或带有指针的值到 channel 中。 在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。
  3. 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string 。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。
  4. slice 的背后数组被重新分配了,因为 append 时可能会超出其容量( cap )。 slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。
  5. 在 interface 类型上调用方法。 在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片b 的背后存储都逃逸掉,所以会在堆上分配。
  6. 使用 -gcflags=-m 参数参看内存逃逸情况

gotoutine泄漏

  1. 虽然执行了 6 次循环,而且每次都没有执行 Body.Close() ,就是因为执行了ioutil.ReadAll()把内容都读出来了,连接得以复用,因此只泄漏了一个读goroutine和一个写goroutine,最后加上main goroutine,所以答案就是3个gorouti

5. 其他补充

5.1. 自旋锁

  1. 概念:不断循环查询资源是否可以访问,获取锁,循环加锁,等待,避免了内核态和用户态的切换,减少了资源消耗
  2. 解决办法:
    1. 同一动作一次或者多次操作结果相同,不会因为用户重复请求而造成结果不同
    2. 解决方案有以下几种(本质上都是拿唯一锁)
    3. token机制,第一次请求时候先获取token,然后释放
    4. 状态机
    5. 对外调用提供seq+source组合索引

5.2. zero copy相关

5.2.1. 概念

  1. 系统调用:连接用户模式和内核模式的接口
  2. 内核空间:内核数据和代码
  3. 用户空间:用户数据和代码

5.2.2. vfs包含内容

  1. 文件元信息模块
  2. 文件inode模块
  3. 文件列表模块
  4. 文件权限模块
  5. 文件描述符模块
  6. address_space模块

5.2.3. io 缓冲区

  1. buffer
    1. 主要是内存的缓存,主要面向文件系统的快
  2. cache
    1. 这个是指处理器的cache,主要面向的是内存,虚拟内存结构,缓存的是page

5.2.4. zero copy 方案

5.2.4.1. mmap方案(读取)

将文件直接映射到进程的空间,这样只需要把文件拷贝到系统缓冲区,而不需要再从缓冲区拷贝到用户,因为直接走了映射

5.2.4.2. sendfile(读取+发送)

传统的方式是读取两次(文件到内核缓冲区+缓冲区到用户态) 写入(用户态到socket缓冲区+socket缓冲区到网卡),sendfile方案是直接去除到用户态的步骤减少了一次io和两次上下文切换

5.3. io

5.3.1. 同步io

5.3.2. blocking

请求后阻塞,直至返回

5.3.3. none bloking

请求后,内核返回错误,然后一直轮询,一直等到成功

5.3.4. IO multiplexing

  1. 请求后注册事件,负责事件的poll 或者epoll不断轮询自己管理的io,当发现好的时候,回调函数,通知进程读取数据,然后进程再读取数据
  2. 原则是一个事件监听多个描述符,

分为以下几类

  1. select
    1. 存在单进程1024限制
  2. poll
    1. 不存在单进程fd的最大限制,但是多了效率会下降
  3. epoll
    1. 不轮询,采用回调
slect poll epoll
事件集合 通过三个参数分别传入感兴趣的可读 可写 异常等事件。内核通过对这些参数的在线修改来反馈其中的就绪事件这使得用户每次调用select都需要重置这三个参数 统一处理所有事件模型,因此只需要一个事件集参数。用户通过pollfd.events 传入感兴趣的事件,内核通过修改 pollfd.events 反馈其中就绪的事件。 内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll wait时候无需反复传入用户感兴趣的事件。epollwait系统调用的参数events仅用来反馈就绪的事件
内核实现和工作效率 采用轮询方式来检测就绪事件,事件复杂度O(n) 采用轮询方式检测就绪事件,时间复杂度O(n) 采用回调方式检测就绪事件,时间复杂度O(1)
最大连接数 1024 无上限 无上限
工作模式 LT LT ET&LT
fd拷贝 每次调用每次拷贝 每次调用每次拷贝 通过mmmap内存映射技术,降低拷贝的资源消耗

5.3.4.1. ET&LT

  1. ET: 边缘出发,仅当socket缓冲区状态发生变化时候触发事件,效率高
  2. LT: 水平触发,仅仅是反馈当前socket缓冲区的状态,

5.3.5. 异步io

请求后立即返回,内核等待数据准备好了之后,然后拷贝数据到用户态,然后通知用户

5.4. 负载均衡算法

算法名称 算法描述 复杂度 优缺点
WrrSticky 生成随机数,落入区间的节点 O(1) 不够平滑
WrrSimple 保持一个当前的权重,当前权重等于所有权重的和,每次减少1,同事每次往下移动一个单位,选举下个backend作为服务节点,直至所有的当前权重完全使用完毕,重置权重 O(1) 会导致某个时候请求流量全部负载到一个节点
WrrSmooth 先选取最大权重的节点,然后所有节点当前权重加上自己的权重,然后最大节点减去权重总和 O(n) 节点比较多时候,需要每次循环n次才能选取到想要的节点
VNSWRR 虚拟出N个节点,选取顺序按照swrr的顺序,使用完毕后再生成N个虚拟节点 O(1) 坚固simple和smooth的优点,兼顾平滑和复杂度,同时也避免了权重调高后流量不平滑的问题

权限系统设计

1. ACL 
2. RBAC(Role-Based Access Control)
3. ABAC(Attribute-Based Access Control) CBAC(Policy-Based Access Control PBAC)