首先面试都是从小公司到大公司的过程,小公司主要为了练手,熟悉面试节奏,后面才去面大公司。尽量不要一开始就奔着大公司去,容易出现准备不足的情况。。。另外,算法是真的难!遇到的面试题也都记了下来,主要是php+go的部分面试题。部分问题附带答案,希望对大家找工作能有帮助。你要做的就是每天进步一点点。。。
微信公众号:码农编程进阶笔记
关注可获得更多的视频教程及面试技巧。问题或建议,请公众号留言!
~~~~~1.不知名小公司A~~~~~
1.k8s的服务注册
答:参照k8s笔记
2.rabbitmq的消息确认机制,项目里面怎么确认的
消息确认:
1)生产者到消息队列,这个是利用消息队列
的confirm机制和持久化机制,持久话之后就给生产者发送一个ack确认
生产者只能设置为事务,或者confirm,不能共用
2)消息队列,内部有唯一的msg_id,会先根据该id判断消息是否重复发送,mq再决定是
否接收该消息
3)消费端,消费成功则发送ack给消费队列,消费队列才会删除该消息
$q->nack($message->getDeliveryTag()); // deliveryTag 可以用来回传告诉 rabbitmq 这个消息处理成功 清除此消息
避免重复消费:
1)业务需要有一个唯一的id,避免消息队列重复下发消息。一般可以跟db的唯一字段对应起来,或者用redis来实现过滤
消息分发策略:
1)轮训分发模式。 每个消费者收到的消息数量是一样的
2) 公平分发,会考虑到消费者的当前消费能力等
3.rabbitmq实时性怎么实现?
答:维护一个常驻进程,实时读取队列消费即可,一般使用的维护工具是:
3.redis和mysql的一致性,项目里面怎么用的
查询:
数据分为静态数据和动态数据。
静态数据:直接读redis,不存在则返回默认值
动态数据:直接读redis,不存在则返回默认值
更新:
1)旧数据缓存的映射(删除key),更新缓存映射关系(Set)
2)mq异步更新db
问题:redis断电,不去读db,直接返回默认值吗?
答:是的,因为有集群的高可用,最多出问题几秒就重新拉起来一个。
而且数据是最终一致性的。
3)创建数据,开启事务,先写入到db,后更新到redis,事务提交。
4) 每个数据都有默认值处理,防止缓存查询失败,返回无数据的情况
4)redis结构:hash结构存储, hmget ,hgetall
5)初始化呢,缓存中无数据怎么办? 写的有脚本,遍历数据写入到redis
6)mq里面的数据在哪消费的,在udc.job
4.高并发秒杀场景设计,redis怎么设计秒杀的
参照redis部分,已经设计了一个秒杀系统
5.redis大key存储,value是怎么存储的
1)拆分为多个key-value,用multi事务去组合查询。分解单次操作压力
2)使用hash存储,然后每个field代表一个属性。查询的时候查询部分属性,
存储的话也可以按照属性存储。本质上还是拆分
3)存储的时候,对key做取模拆分,分配到不同的key上面
6.redis集群同步数据
(1) 【所有的redis节点彼此互联(PING-PONG机制)】,内部使用二进制协议优化
传输速度和带宽。
(2) 节点的fail是通过集群中【超过半数的节点检测失效】时才生效。
(3) 客户端与redis节点直连,不需要中间代理层。客户端不需要连接集群所有节点,
【客户端连接集群中任何一个可用节点即可】。
(4)Redis集群预分好16384个桶,当需要在Redis集群中放置一个key-value 时,
计算key属于哪个桶,然后存放value
(5)集群正常工作至少需要3个主节点,一共就需要6个节点,其中3个为主节点,
3个为从节点
---查询
(1)计算key所在槽,在本节点上,就直接返回数据
(2)不在本节点上,则执行move,指引client转向负责对应槽的节点,
并客户端需要再次发送想要执行的和key相关的命令
7.对mysql架构的理解
答:客户端和服务端组成。
客户端进程向服务器进程发送MySQL语句,服务器进程处理后再向客户端进程发送处理结果
客户端:对应配置为:[client],[mysql],[mysqladmin]
服务端:对应配置为:[server],[mysqld],[mysqld_safe]
引擎部分:mysql中具体与文件打交道的子系统,是官方提供的文件访问层的
一个抽象接口来定制一种文件访问机制
执行过程:
(1)客户端连接服务端,mysql-uxxx -pxxx
(2)服务端进行查询缓存,不过5.7不建议使用,8.0废弃
(3)服务端语法解析,判断请求的语法是否正确,然后从文本中将要查询的表等
(4)查询优化:生成一个执行计划,这个执行计划表明了应该使用哪些索引进行查询,表之间的连接顺序是啥样的
(5)存储引擎:MySQL server完成了查询优化后,只需按照生成的执行计划调用
底层存储引擎提供的API,获取到数据后返回给客户端就好了。
8.为什么项目里面是用curl来调度服务的,怎么不用rpc,差距在哪?
答:https://blog.csdn.net/AlbenXie/article/details/105230018
(1)http的报文header头占用空间太多了,rpc一般会优化这块
(2)rpc是基于http2.0的,减少rtt,长连接方面有优势
(3)rpc传输的序列化反序列化可以是protobuf
(4)rpc框架包含了重试机制,路由策略,负载均衡策略,高可用策略,
流量控制策略等等能用在消息处理上的功能
9.php的桶结构
(1)bucket桶结构,实际的数据存储在这里,用链地址法防止冲突。(redis也是)
数据是链表连接的,foreach就是根据赋值顺序,找到下一个元素的指针,依次遍历
(2)一个hashtable默认分配8个bucket,如果存储的元素大于8个会自动扩容,
扩容后的大小为2的倍数。一般是先看看删除的元素是否到达阈值,到达的话则重建
索引。没有到达阈值则扩容,*2
(3)php的forach比for快,就是因为forach直接拿首个bucket的指针开始遍历,省去了
计算key的hash值的过程,同样的,next(),prev()等方法也是直接在hashtable上就能取到值
(4)php7之后,是先通过计算key得到value的位置,然后把key存到中间表,中间表
主要存储key和value的映射关系。扩容的时候,中间表也要重新计算
(5)php删除数组的中的元素,并不是立刻删除的,只是给标识为IS_UNDEF,扩容的时候
才会真正的删除掉
(6)查找时先在散列表中映射到nIndex,得到value在Bucket数组的位置idx,
再从Bucket数组中取出元素。
~~~~~2.不知名小公司B~~~~
1.include和require的区别
答:
1)报错
include 引入文件的时候,如果碰到错误,会给出提示,并继续运行下边的代码。
require 引入文件的时候,如果碰到错误,会给出提示,并停止运行下边的代码。
2)文件引用方式
include() 执行时需要引用的文件每次都要进行读取和评估,
require() 执行时需要引用的文件只处理一次
3)include_once 函数和include类似,只不过只会引入一次
2.composer insall和update的区别
答:install读取lock文件,没有的话,则读取json文件,并生成lock
update会读取json,拉取最新依赖,把新的版本写入lock
也就是说,当本地没有lock文件的时候,install和update是一样的
3.cookie和session
答:服务端生成cookie返回给客户端,客户端请求带着cookie,
服务端获取cookie和session_id,
然后读取session文件,就可以对比客户端的cookie了。
session是依附于cookie的,需要cookie来存储session_id。当禁用cookie的时候,
通过url重写或者表单隐藏域来提交session_id
4.sql注入,xss,csrf
答:sql注入,用户输入sql命令或者sql注释,拼接sql的时候,会查出所有的
用户信息。
防范:是过滤用户输入,使用预处理来拼接sql
xss跨站脚本:网页中注入恶性脚本。持久型是存入到数据库,读出的时候弹出恶意代码,
反射型是通过电子邮件等,引导用户点击恶意链接。
防范:用户输入过滤,cookie加密
csrf:跨站请求伪造。拿到A的cookie,访问恶意网站b,b就可以拿着a的cookie去访问a网站。
防范:token机制,验证referer
5.git pull和git fetch的区别
答:都是更新远程代码到本地
(1)git pull相当于暴力合并,直接拉取代码,并合并,相当于git fetch + git merge
(2)git fetch(下载)拉取代码后,一般需要手动合并下代码
6.线程是什么,线程的上下文切换
答: 上下文切换:上下文切换就是从当前执行任务切换到另一个任务执行的过程。但是,
为了确保下次能从正确的位置继续执行,在切换之前,会保存上一个任务的状态。
进程切换:
(1)切换页目录以使用新的地址空间
(2)切换内核栈(函数)和硬件(寄存器)上下文
寄存器:cpu内部的元件,可以保存数据,保存地址,指令等
7.trait的好处
伪多继承。php中一个类能继承多个接口,但只能继承一个父类。
使用trait,可以实现继承多个父类,避免复用代码
8.负载均衡原理
Lvs的nat:
客户端 --> Load Balancer --> RS --> Load Balancer --> 客户端
1)LB可以修改客户端发来的ip头,tcp头,定位带rs服务器群
2)服务器响应后,会发送给LB网关,LB再修改ip和tcp报文,发送给客户端
LVS的DR:
客户端 --> Load Balancer --> RS --> 客户端
1)Rs公用一个ip,LB对外服务,拿到请求后,分配给rs
2)rs直接返回数据包给客户端
9.mysql的长连接和短连接,都有什么特点,框架里有使用吗?
答:长连接指在一个连接上可以连续发送多个数据包,在连接保持期间,
如果没有数据包发送,需要双方发链路检测包。
mysql的长连接如果长期闲置,mysql会8小时后(默认时间)主动断开该连接。
10.线程池的大概设计
(1)要设置最大连接和最大连接空闲数。小于最空闲数则使用完入池。大于最大空闲数则释放
(2)需要多一个队列,来表示等待队列。当请求没有 数据库连接空闲,则进入队列。设置有默认的超时时间,超时报错
(3)当一个链接使用完,要判断是否有等待队列需求,有的话直接返回给等待中的需求,没有的话就入池
(4)均衡和保活。均衡可采用队列先进先出的方式保持 。保活的话,类似于发送心跳,保持连接活性
11.php的数组扩容
我们知道,数组存储需要连续的内存空间,那么扩容的时候呢,是虚拟内存的方式,
还是直接申请一大块内存呢?
答:一个hashtable默认分配8个bucket,如果存储的元素大于8个会自动扩容,
扩容后的大小为2的倍数。
~~~~3.马蜂窝一面~~~~
1.python的切片了解吗
答:切片操作基本表达式:object[start_index:end_index:step]
(1)冒号':'可以省略,step默认为1.
(2)step的正负代表切片的方向
2.okr和kpi的区别
(1)OKR 强调全员思考;KPI 强调管理层思考。
(2)OKR 强调自我驱动;KPI 强调外在驱动。
(3)KPI 只能让驴使劲走,而 OKR 用于保证驴头朝正确的方向。
3.es数据超过一亿,有没有做过什么优化
答:首先es数据在磁盘上,每次查询也是去查询缓存,不存在缓存
则去磁盘查找,刷新到缓存。缓存一般占机器内存的50%
(1)热数据单独建索引,类似于mysql的分表。
可以hash%64这样,减小索引大小。
(2)查询部分不要使用复杂的join,parent-child这种
其次是filter查询效率比query高,而且会缓存数据,方便下次查询。
(3)分页不要太大,es每次分页都会向所有节点查询数据,然后
返回给node1,node1最终返回数据,所以分页小点好。
4.mysql插入数据,断电重启之后,数据会丢失吗,为什么
答:靠的是redo log,事务每次执行会先写入到缓冲区,通过两段提交方式,
保证恢复已经commit的数据。
checkpoint:记录被刷新到磁盘的redo log的id,mysql重启之后会从上一次的
checkpoint开始恢复,加快恢复的速度。
(1)事务执行的几个阶段
① InnoDB)prepare redo log
② Server)write binlog
③ InnoDB)commit redo log
(2)从上个checkpoint开始恢复,如果redo log有两个状态,则直接提交。
如果redo 只有prepare,则拿些事务id去查询binlog,binlog有写入则提交,
binlog无写入则回滚该事务。
5.tcp的三次握手是特有的吗,udp会有吗,了解udp吗?
(1)tcp和udp的区别
1)tcp可靠,udp不可靠
2)tcp需要先建立连接,udp不需要
3)tcp是一对一,udp可以1对多
4)tcp效率低,udp效率高
5)TCP 有滑动窗口可以用来控制流量,而 UDP 则不具备流量控制的能力
6)TCP 是面向字节流的传输层协议,而 UDP 是面向报文的传输层协议;
7)TCP 的应用场景是对消息准确性和顺序要求较高的场景,
而 UDP 则是应用于对通信效率较高、准确性要求相对较低的场景。
(2)面向字节流和面向报文的区别
面向字节:TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,
当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。
如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后
再构成报文段发送出去
面向报文:送方的UDP对应用层交下来的报文,不合并,不拆分,
只是在其上面加上首部(最小8字节)后就交给了下面的网络层。
(3)tcp粘包
答:发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,
后一包数据的头紧接着前一包数据的尾。
半包:数据包比较大,tcp每次发送只能发送一半
注意:udp不存在粘包,不会合并小包。
造成粘包原因:
1)发送方合并多个小分组,在一个确认到来时一起发送
2)接收方接收数据到缓存,程序去缓存中读取。当程序读取速度<接收速度,
就可能粘包。
解决方案:
1)发送方使用TCP_NODELAY选项来关闭Nagle算法
2)发送的时候把长度也发送过去。程序收到之后,根据长度确认
包的大小,然后进行分割。
(4)tcp其实没有包的概念
TCP是字节流协议,确实没有包的概念,包是应用层的概念,只是概念叫做
粘包。
(5)ip包分片
1)最大传输单元:数据链路层对数据帧的长度都有一个限制,也就是链路层所能
承受的最大数据长度,这个值称为最大传输单元,即MTU。
通常是1500字节。
2)在IP包头中,以16位来描述IP包的长度。一个IP包,最长可能是65535字节
3)当ip包大于MTU,则要进行分片,分为多个小包传输。如果设置
不可分片,则数据包被丢弃,出现报错。
4)TCP的选项字段中,有一个最大报文段长度(MSS),一般是1024字节
5)ip头长度,tcp头长度都是固定20字节。
6)IP包头中,用了三个标志来描述一个分片包,分别是:
分片标志(0/1),分片偏移标志(分片在原包的位置),不允许分片标志
6. mysql和redis如何保证数据一致性
从应用场景分析。读多写少,和读少写多,可以了解下缓存策略。
缓存策略:
读多写少:
(1)缓存为主,不存在则返回默认值
(2)更新的时候更新缓存,队列异步更新db
(3)数据预热,启动系统之前先用脚本去跑缓存
读少写多:
(1)每次读取,没有缓存就写入缓存
(2)更新的时候,更新数据,删除缓存。
(3)写多读少的话,会减小缓存的更新消耗。
7.php7.0对于引用计数的优化有哪些?
答:
1)当对整型,浮点型,静态字符串赋值时,引用计数是0
动态字符串就是用函数生成的字符串,这种的会有引用计数。
因为php7的引用计数value 中而不是 zval_struct,当数据类型简单的时候,
value可以直接存下。
2)引用&之后,refcount 为2
3)不可变数组,就是直接赋值固定内容的数组,初始计数是2.
动态数组初始计数是1.
4)循环引用会造成内存泄露。比如:
// 数组循环引用
$arrA['arrA'] = &$arrA;
~~~~4.马蜂窝二面~~~~
1. rabbitmq是分布式的吗,大概架构是怎么样的?
答:是分布式的。
主备集群模式,通过备用实现高可用。
镜像模式,一个节点的数据会同步到3个其他节点上,保证
数据不丢失。
2.kafka,会丢数据吗,丢数据在哪一步,怎么处理?
答:会丢的,主要从生产者,服务器,消费者几个方向来处理。
(1)Broker:broker存储topic的数据。如果某topic有N个partition,
集群有N个broker,那么每个broker存储该topic的一个partition。
kafka采用了批量刷盘的做法,数据存在缓冲区。
只能通过调整刷盘机制的参数缓解该情况。比如,减少刷盘间隔,
减少刷盘数据量大小。时间越短,性能越差,可靠性越好(尽可能可靠)。
这是一个选择题
(2)生产端:设置及ack为-1或者all.
ack=0:只负责发送,效率最高。
ack=1:保证leader能收到
ack=-1:保证leader和ISR列表都能收到,注意isr列表不能设置的太小
生产端也是异步批量发送数据到broker的,要保证数据不丢失,可以设置
同步发送,扩大Buffer的容量配置
(3)消费端
消费端手动提交offset
(4)每个partition都有leader和floower.
生产者发布消息时根据消息是否有键,采用不同的分区策略。消息没有键时,
通过轮询方式进行客户端负载均衡;消息有键时,根据分区语义(例如hash)
确保相同键的消息总是发送到同一分区
(5)Rebalance
Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer
如何达成一致,来分配订阅 Topic 的每个分区
触发方式:
组成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组。
订阅的 Topic 个数发生变化。
订阅 Topic 的分区数发生变化。
3.kafka发现消息积压了怎么办?增加消费者有用吗?
答:一个分区最多被一个消费者消费,消费者多了之后没用的。
我们可以在消费者中只做不耗时的操作,耗时的操作打入到二级队列,
二级队列多做几个分区,这样消费能力跟得上
4.redis多个master怎么平均分配数据进去,会不会出现有的负载很高的情况
答:不管是codis还是redis官方集群,都是hash算法计算key,找到对应的槽,
最后找到节点,也就是master了。codis是1024个槽
5.redis的bitmap存储的key是什么?为什么不用redis提供的命令来求交集并集?
答:(1)key存储的是用户id
(2)redis提供的命令非常耗费cpu性能,自己程序做位操作好一些。
6. redis的分布式锁,过期时间如何续约?
答:https://segmentfault.com/a/1190000022436625
(1)redis的setnx和set都是针对单机redis的。如果是主从或者集群redis,
当matser宕机,锁还没到slave.slave成为新master的时候,锁会失效。
(2)redis的redlock是分布式集群锁,总体思想是尝试锁住所有节点,当有
一半以上节点被锁住就代表加锁成功 .
(3)zookeeper的分布式锁
1)一个ZooKeeper分布式锁,首先需要创建一个父节点,尽量是持久节点
(PERSISTENT类型),然后每个要获得锁的线程,都在这个节点下创建个
临时顺序节点。由于ZK节点,是按照创建的次序,依次递增的。
2)判断逻辑就是序号最小的先加锁,其他的阻塞。前一个锁
释放之后,会通知后面的节点。
3)可重入性,同一个线程可以重复加锁。
(4)zookeeper和redis的优劣势
(1)基于ZooKeeper的分布式锁,适用于高可靠(高可用)而并发量不是太大
的场景;
(2)基于Redis的分布式锁,适用于并发量很大、性能要求很高的、而可靠性
问题可以通过其他方案去弥补的场景
~~~~~5.得物A部门一面~~~~
1.lru算法的大概实现,go怎么实现lru算法
答:步骤如下:
(1)当访问的数据命中缓存,遍历得到这个数据对应的结点,并将其从原来的
位置删除,然后再插入到链表的头部;(修改链表指针O(1))
(2) 如果此数据没有在缓存链表中,分为两种情况:
(3) 如果此时缓存未满,则将此结点直接插入到链表的头部;
(4)如果此时缓存已满,则遍历至链表尾结点将其删除,将新的数据结点
插入链表的头部。
php采用:数组+单链表的方式实现
golang采用:map+结构体链表的方式实现
2. mysql的主从不一致怎么解决
答:
(1)如何避免主从不一致:
1、主库binlog采用ROW格式
2、主从实例数据库版本保持一致
3、主库做好账户权限把控,主库不可以停止写binlog
4、从库开启只读 read_only=ON,不允许人为写入
5、定期进行主从一致性检验
(2)解决方案
1、将从库重新实现
2.使用percona-toolkit工具辅助(最佳方案)
3、手动重建不一致的表
4.另起脚本做一致性校验,不一致的话报警
3.go的协程为什么比线程更轻量级
答:线程切换需要切换上下文,寄存器,堆栈等
(1)go协程也叫用户态线程,协程之间的切换发生在用户态。
在用户态没有时钟中断,系统调用等机制,因此效率高。
(2)就协程是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。
占用内存小,一般是2kb,线程需要8M
4.kafka怎么防止重复消费?kafka的消费ack跟rabbitmq有什么区别
答:(1)在断电或者重平衡的时候,有可能消费者还没提交offset,导致
重复消费问题。一般是业务唯一id,数据库唯一键或者用redis存储消费过的id,
做一次判断过滤。
(2)一个是提交ack之后删除数据
kafka是提交offset之后不删除数据,数据可以重复消费
5.go怎么实现的锁
答:(1)读写锁sync.RWMutex 的 RLock())和写锁 Lock()
(2)互斥锁sync.Mutex
(3)atomic 包操作保证原子性
6.你认为项目最有亮点的地方说一下
7.mysql分库的场景,如何连表查询?
(1)相同mysql下的不同库join查询。
可以带上库名,比如a.demo 和b.demo
(2)不同mysql下的查询
可以通过mysql的federated引擎,创建的表只是在本地有表定义文件,
数据文件则存在于远程数据库中
往期精选