面试造火箭 上班拧螺丝
“茴香豆的『回』字有几种写法你知道么”
php
php-fpm 的生命周期,创建进程方式,各自的优缺点(腾讯 百度 滴滴 陌陌)
PHP-FPM是一种多进程模型,主要由Master进程以及Worker进程组成,所有的cgi请求都会交由Worker进程处理。Master进程主要维护worker进程。
而worker进程的工作方式是抢占/竞争的方式,当一个accept请求过来的时候,谁先拿到算谁的,拿到后转化为FastCGIRequest,交由脚本处理。
php生命周期
数据初始化(MINT) => 请求初始化(RINT) => 编译脚本 (RSHUTDOWN) => 执行代码(MSHUTDOWN)
static 静态模式
启动的时候创建固定数量的worker进程,实际请求大于worker进程的时候 包warning
ondemand 按需分配模式
启动的时候不会创建worker进程, 根据需要创建,释放在idle_timeout之后
这样不能及时的释放连接和建立连接需要消耗资源
dynamic 动态模式(默认)
启动的是创建指定数量的worker进程, 根据情况合理的worker,定期检测worker,关闭闲置连接
php 数组遍历为什么能保证有序(滴滴)
为了实现插入与遍历的顺序一致性,在PHP7中,增加了一个中间映射层,它的大小与哈希表相同,存储了元素在bucket中最终存储的位置,我们把它叫做映射表。
php 怎么实现的弱类型,怎么实现一个扩展(腾讯)
实现php弱类型变量
- 通过Zend引擎用C实现弱类型,在ZE中用结构体zval来保存
- 通过Zend引擎是判别、存储PHP中的多种数据类型,根据type来选择获取【zvalue_value】的值
实现一个扩展
常见魔术方法和函数(腾讯 滴滴)
__construct()
: 类的构造函数__destruct()
: 类的析构函数__call()
: 当调用一个未定义或不可达方法时, __call () 方法将被调用。__callStatic()
: 当调用一个未定义或不可达的静态方法时, __callStatic () 方法将被调用。__get()
: 当获取一个类的成员变量时, __get () 方法将被调用。__set()
: 当赋值一个类的成员变量时, __set () 方法将被调用。__isset()
: 当调用 isset () 或 empty () 对一个未定义或不可达的成员赋值时, __isset () 方法将被调用。__unset()
: 当调用 reset () 对一个未定义或不可达的成员更新时, __unset () 方法将被调用。__sleep()
: 当执行序列化 serialize () 时,__sleep () 方法将首先被调用。__wakeup()
: 当执行反序列化 deserialization () 时, __wakeup () 方法将首先被调用。__toString()
: 当使用 echo 方法直接输出显示对象时,__toString () 方法首先被调用。__invoke()
: 使用调用函数(function)访问一个对象时, __invoke () 方法将首先被调用。__set_state()
: 当调用 var_export () 方法时,__set_state () 方法将被调用。__clone()
: 当对象被复制赋值时,__clone () 方法将被调用。__autoload()
: 试图载入一个未定义的类时调用。__debugInfo()
: 输出 debug 信息。
mysql
有哪些事务隔离级别,Mysql 的事务隔离级别是怎么实现的?(每家都问)
事务的隔离性由锁来实现。 原子性、一致性、持久性通过数据库的redo log和undo log来实现。 redo log称为重做日志,用来保证事务的原子性和持久性,undo log用来保证事务的一致性。 MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化
未提交读(RU)
提交读(RC)
可重复读(RR)
序列化读(S)
索引原理(每家都问)
索引的本质是一种排好序的数据结构
索引的分类
Hash索引
二叉树
B树
B+树 (MySQL 中最常用的索引的数据结构是 B+ 树)
分库分表的策略,如果要按照分表字段以外的字段作为查询条件怎么办(每家都问)
唯一主键
常见的分布式生成唯一ID的方式很多,最常见的雪花算法Snowflake、滴滴Tinyid、美团Leaf。以雪花算法举例来说,一毫秒可以生成4194304多个ID。
MVCC 和间隙锁原理(滴滴 字节 百度)
多版本并发控制(MVCC)是一种解决读-写冲突的无锁并发控制
每一行记录都有两个隐藏列:创建版本号和回滚指针。事务开启后存在一个事务id。多个并发事务同时操作某行,不同的事务对该行update操作会产生多个版本,然后通过回滚指针组成undo log链。而MVCC的快照读正是通过事务id和创建版本号从而实现的快照读。
注意:只有RR隔离级别才存在间隙锁。
explain 的 type 字段有哪些(知乎)
system
: 系统表,少量数据,往往不需要进行磁盘IOconst
: 常量连接eq_ref
: 主键索引(primary key)或者非空唯一索引(unique not null)等值扫描ref
: 非主键非唯一索引等值扫描range
: 范围扫描index
: 索引树扫描ALL
: 全表扫描(full table scan)
type
扫描方式由快到慢
system
>const
>eq_ref
>ref
>range
>index
>ALL
update 语句的执行流程,binlog 的作用和几种格式(滴滴)
binlog 的作用和几种格式
binlog一般情况下分为三种格式,分别是row
格式、statement
格式、mixed
格式
row格式
: 此格式不记录sql语句上下文相关信息,仅保存哪条记录被修改。statement
: 该格式下每一条会修改数据的sql都会记录在binlog中。mixed
: 该格式是以上两种level的混合使用,一般的语句修改使用statement格式保存binlog,当statement无法完成主从复制的操作时(设计一些函数时),则采用Row格式保存binlog
主从同步的原理和问题(字节 滴滴 陌陌)
主从复制原理
- 在主库上把数据更改记录到二进制日志binary log中,具体是在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中去,Mysql会按照事务提交的顺序来记录二进制日志的。日志记录好之后,主库通知存储引擎提交事务。
- 从库会启动一个IO线程,该线程会连接到主库。而主库上的binlog dump线程会去读取主库本地的binlog日志文件中的更新事件。发往从库,从库接收到日志之后会将其记录到本地的中继日志relay-log当中。
- 从库中的SQL线程读取中继日志relay-log中的事件,将其重放到从库中。(在5.6版本之前SQL线程是单线程的,使得主从之间延迟更大)
延迟问题
延迟的产生:
- 当主库的TPS并发较高时,由于主库上面是多线程写入的,而从库的SQL线程是单线程的,导致从库SQL可能会跟不上主库的处理速度(生产者比消费者快,导致商品堆积)。
延迟的解决:
- 网络方面:将从库分布在相同局域网内或网络延迟较小的环境中。
- 硬件方面:从库配置更好的硬件,提升随机写的性能
- 配置方面:从库配置sync_binlog=0,innodb_flush_log_at_trx_commit=2,logs-slave-updates=0,增大innodb_buffer_pool_size,让更多操作在Mysql内存中完成,减少磁盘操作。或者升级Mysql5.7版本使用并行复制
- 架构方面:比如在事务当中尽量对主库读写,其他非事务中的读在从库。消除一部分延迟带来的数据库不一致。增加缓存降低一些从库的负载。
发生死锁的原因以及如何解决(滴滴 顺丰)
发生死锁的原因
- 互斥条件
- 不可剥夺条件(不可抢占)
- 部分分配
- 循环等待
死锁处理方法
- 通过协议来预防或避免死锁,确保系统不会进入死锁状态。
- 可以允许系统进入死锁状态,然后检测它,并加以恢复。
- 可以忽视这个问题,认为死锁不可能在系统内发生
如何优化大 offset(陌陌)
当offset特别大时,这条语句的执行效率会明显减低,而且效率是随着offset的增大而降低的。
MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,当offset特别大,然后单条数据也很大的时候,每次查询需要获取的数据就越多,自然就会很慢。
- 解决方法
先获取主键列表,再通过主键查询目标数据,即使offset很大,也是获取了很多的主键,而不是所有的字段数据,相对而言效率会提升很多。
1 | select 需要的字段 |
redis
缓存如何保证一致性(每家都问)
- 执行顺序的问题:先更新缓存还是先更新数据库?
- 更新缓存的策略问题:当缓存中的内容变化时,是选择修改缓存(update),还是直接淘汰缓存(delete)?
针对这两点问题,一共可以分为四种方案:
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先淘汰缓存,再更新数据库 (适用于对一致性要求高的业务)
- 先更新数据库,再淘汰缓存
用过 redis 哪些数据结构,使用场景是什么(每家都问)
string
(字符串): 常见的key-value存储list
(列表):- 消息队列:
lpop
和rpush
或者反过来,lpush
和rpop
)能实现队列的功能 - 朋友圈点赞列表, 评论列表, 排行榜
- 消息队列:
hash
(字典):- 购物车:
hset [key] [field] [value]
命令, 可以实现以用户Id,商品Id为field,商品数量为value,恰好构成了购物车的3个要素。 - 存储对象:hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。
- 购物车:
set
(集合):- 好友、关注、粉丝、感兴趣的人集合
sinter
命令可以获得A和B两个用户的共同好友;sismember
命令可以判断A是否是B的好友;scard
命令可以获取好友数量;- 关注时,
smove
命令可以将B从A的粉丝集合转移到A的好友集合
- 首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set类型适合存放所有需要展示的内容,而
srandmember
命令则可以从中随机获取几个 - 存储某活动中中奖的用户ID ,因为有去重功能,可以保证同一个用户不会中奖两次。
- 好友、关注、粉丝、感兴趣的人集合
zset
(有序集合):zset
可以用做排行榜,但是和list不同的是zset它能够实现动态的排序,例如: 可以用来存储粉丝列表,value 值是粉丝的用户 ID,score 是关注时间,我们可以对粉丝列表按关注时间进行排序。
redis 的 connect 和 pconnect 的区别,pconnect 有什么问题(滴滴 陌陌)
connect
: 脚本结束之后连接就释放了pconnect
: 脚本结束之后连接不释放,连接保持在php-fpm进程中
pconnect 有什么问题
- 当使用pconnect时,连接会被重用,连接的生命周期是fpm进程的生命周期,而非一次php的执行
- 如果代码中使用pconnect, close的作用仅是使当前php不能再进行redis请求,但无法真正关闭redis长连接,连接在后续请求中仍然会被重用,直至fpm进程生命周期结束。
redis 如何实现分布式锁,有什么问题(陌陌)
利用 Redis 的 SETNX
命令,此命令同样是原子性操作,只有在 key 不存在的情况下,才能 set 成功。而基于 Redis 多机实现的分布式锁 Redlock
,是 Redis 的作者 antirez 为了规范 Redis 分布式锁的实现,提出的一个更安全有效的实现机制
不管是哪种实现方式,均需要实现加锁、解锁、锁超时这三个分布式锁的核心要素
redis 为什么用跳表实现有序集合?原理,用有序集合的场景(字节 滴滴)
- 按照区间来查找数据这个操作,红黑树的效率没有跳表高
- 跳表更容易代码实现
- 跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗
跳表数据结构
- 跳表使用空间换时间的设计思路,通过构建多级索引来提高查询的效率,实现是基于“二分查找”的链表操作
- 跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 O(logn)
- 跳表的空间复杂度是 O(n),不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
- 虽然跳表的代码实现并不简单,但是作为一种动态数据结构,比起红黑树来说,实现要简单多了。所以很多时候,我们为了代码的简单、易读,比起红黑树,我们更倾向用跳表。
主从同步的原理,哨兵和集群的区别(滴滴)
- 主从复制: 读写分离,备份,一个Master可以有多个Slaves。
- 哨兵: 监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。
- 集群: 为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
哨兵(sentinel)着眼于高可用; 集群(cluster)提高并发量
redis cluster 用的什么协议同步数据,哨兵的选举呢(陌陌)
Gossip协议 又称 epidemic 协议(epidemic protocol)
哨兵选举
主节点被标记为 Fail 后,对应的从节点会发起投票,竞争升主。历经从节点拉票、主节点投票、投票裁决等环节,最终完成选举。
- 从节点拉票
- 拉票优先级
- 主节点投票
- 根据投票结果决策
- 选举失败
- 选举算法 (选举新主节点的算法是基于 Raft 算法的 Leader Election 方法来实现的)
rdb 和 aof 的原理(滴滴 高德)
RDB
: 生成指定时间间隔内的 Redis 内存中数据快照,是一个二进制文件 dump.rdbAOF
: 记录 Redis 除了查询以外的所有写命令,并在Redis 服务启动时,通过重新执行这些命令来还原数据。
区别
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式追加记录,可以打开文件看到详细的操作记录。
数据过期和淘汰策略(滴滴 高德 字节)
数据过期
Redis中主要使用 定期删除 + 惰性删除 两种数据过期清除策略
- 定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。
- 惰性删除:定期删除可能导致很多过期的key 到了时间并没有被删除掉。这时就要使用到惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且过期了,是的话就删除。
淘汰策略
Redis共提供了8中缓存淘汰策略,其中 volatile-lfu 和 allkeys-lfu 是Redis 4.0版本新增的
通常情况下推荐优先使用 allkeys-lru 策略。
noeviction
: 不进行淘汰数据volatile-ttl
: 在设置了过期时间的键值对中,移除即将过期的键值对volatile-random
: 在设置了过期时间的键值对中,随机移除某个键值对volatile-lru
: 在设置了过期时间的键值对中,移除最近最少使用的键值对volatile-lfu
: 在设置了过期时间的键值对中,移除最近最不频繁使用的键值对allkeys-random
: 在所有键值对中,随机移除某个keyallkeys-lru
: 在所有的键值对中,移除最近最少使用的键值对。allkeys-lfu
: 在所有的键值对中,移除最近最不频繁使用的键值对
缓存雪崩 击穿 穿透(滴滴 陌陌)
elasticsearch
深度分页会有什么问题(滴滴 百度 陌陌)
深度分页问题大致可以分为两类
- 随机深度分页: 随机跳转页面
- 滚动深度分页: 只能一页一页往下查询
from/size
es 目前支持最大的 skip 值是 max_result_window ,默认 为 10000 。也就是当 from + size > max_result_window 时,es 将返回错误
scroll
使用scroll,每次只能获取一页的内容,然后会返回一个scrollid,根据scrollid可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景。但是在真正的使用场景中,第10000条数据已经是很后面的数据了,可以“折衷”一下,不提供跳转页面功能,只能下一页的翻页。
Scroll方式通过一次查询请求后维护一个临时的索引快照的search context
此后的增删查改操作并不会影响这个快照数据信息,后续的查询只需要根据游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。效率比较高。在5.x之后,还可以通过slice分片来实现并行导出。
search_after
searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
它的缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序(注:每个文档具有一个唯一值的字段应该用作排序规范的仲裁器。否则,具有相同排序值的文档的排序顺序将是未定义的。建议的方法是使用字段_id,它肯定包含每个文档的一个唯一值)。
倒排索引的原理(字节 高德)
倒排索引由两个部分组成:单词词典和倒排文件。
- 单词词典: 单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
- 倒排文件: 所有单词的倒排列表顺序的存储在磁盘的某个文件里,这个文件即被称为倒排文件,倒排文件是存储倒排索引的物理文件。
lsm 树原理(字节)
LSM树(Log Structured Merge Tree,结构化合并树)的思想非常朴素,就是将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘(由此提升了写性能),是一种基于硬盘的数据结构,与B-tree相比,能显著地减少硬盘磁盘臂的开销。
kafka
kafka 的架构,大致储存结构(高德 字节 滴滴)
如果消费者数超过分区数会怎么样?(顺丰 滴滴)
怎么保证数据的可靠投递?(陌陌 字节)
消费者的 offset 存在哪里?(字节 腾讯 陌陌)
如何通过 offset 定位消息?(字节)
时间轮的原理(陌陌 顺丰)
kafka 写入高性能的原因,sendfile 和 mmap 原理,为什么不用 splice(滴滴)
网络
https 原理,tls 握手需要几个 rtt?(滴滴 百度)
HTTPS是在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版。
SSL(Secure Socket Layer 安全套接字层) / TLS(Transport Layer Security 传输层安全协议)
RTT(Round Trip Time 往返时间)
TLS 握手就需要消耗两个 RTT
在 TLS 1.2 中,我们需要 2-RTT 才能建立 TLS 连接10,但是 TLS 1.3 通过优化协议,将两次往返延迟降低至一次
总结一下 HTTPS 协议需要 9 倍时延才能完成通信的原因:
- TCP 协议需要通过三次握手建立 TCP 连接保证通信的可靠性(1.5-RTT)
- TLS 协议会在 TCP 协议之上通过四次握手建立 TLS 连接保证通信的安全性(2-RTT)
- HTTP 协议会在 TCP 和 TLS 上通过一次往返发送请求并接收响应(1-RTT)
浏览器访问某个网址的详细过程,四次挥手(腾讯 滴滴)
- 获得域名所对应的IP地址,若DNS缓存中没有相关数据,则浏览器向DNS服务器发出DNS请求,以获取域名所对应的IP地址。
- 浏览器与域名地址建立TCP连接,三次握手
- HTTP访问
- 断开TCP链接, 四次挥手
http2 和 quic 原理(字节)
HTTP2
- 二进制分帧(Binary Format)
- 多路复用(Multiplexing)/连接共享
- 头部压缩(Header Compression)
- 压缩原理
- 请求优先级(Request Priorities)
- 服务端推送(Server Push)
Quic
Quic (Quick Udp Internet Connection 快速UDP互联网连接)
Quic 相比现在广泛应用的 http2+tcp+tls 协议有如下优势
- 减少了 TCP 三次握手及 TLS 握手时间
- 改进的拥塞控制
- 避免队头阻塞的多路复用
- 连接迁移
- 前向冗余纠错