一个PHP高级工程师 需要具备哪些知识?
准备
- 自我介绍: 说上家公司负责的项目或者浓缩简历。
- 简历项目经验层次
- 利用什么技术
- 实现了什么功能
- 遇到了什么问题
- 达到了什么结果
- 期望薪资
- 应用型的问题:要记得站高看远、架构分层
- 及管理经验及自身对项目管理的理解
知识点列表
PHP
- 代码解释过程(大多的非编译语言)
lexical
词法分析, 输入为源代码, 输出为token
- 语法分析工具为文法(
LALR
), 输出为表达式, 7.0为AST
, 涉及- 注释
- 分号 & 分隔符
- 变量
- 常量
- 操作数
- 类型检查、关键字处理、导入, 输出为中间代码.工具为选定的编译器优化工具
- 中间代码生成(
Opcodes
) - 机器码生成 (编译语言)
- 中间代码生成(
Session
共享配置PHPUnit
用法Cookie
购物车和Session
购物车的实现- 弱类型实现
zval
(不仅是变量名) &zend_val
变量值
- 代码规范
- 自动化:
sonarquebe
+jenkins
- 单元测试
- 自动化:
- PHP进程间如何通信
- 信号量(消息同步|互斥)
- 信号(信号触发事件)(
pcntl_signal
,pcntl_wait*
) - 消息队列(
msg_*
) - 管道(
pipe
) socket
|unix_*.sock
- 共享内存(
shm_
,shmop_
)
- PHP并发模型
- PHP执行流程
- 变量底层存储结构
- 常用的数组函数(列出10个)
array_combine
(前面数组作为其键,后面数组做为其值)array_merge
(合并两个数组,后面覆盖前面,但数字索引会重新索引,不会覆盖)- …
- PHP垃圾回收机制(gc)
zend.enable_gc
(php.ini
)gc_enable()
(function
)- 引入计数(
zval
指向zend_value
个数为0
时) + 写时拷贝 (copy on write
) - 循环引用问题 (
array
、object
引用自身成员), 垃圾回收器将收集于一个buffer
(_zend_gc_global
->gc_root_buffer
) 后启动垃圾鉴定程序
- 把
Session
放入Redis
里面还会触发类似文件的state session
session.gc_probability
(default 1)session.gc_divisor
(default 100)session.gc_maxlifetime
(单位秒)session.cookie_lifetime
(单位秒, 0表示直到关闭浏览器)session.save_path
session.write_close
(显示关闭, 后期使用需要显示开启)
- 内存模型
- 整型、浮点、
bool
、NULL
、内部字符串、不可变数组都是通过zval
直接保存,不会用到引用计数 string
、array
都会使用引入计数(支持复制cow),object
、resource
本身可以理解为引用
- 整型、浮点、
- fpm三种配置及场景
dynamic
pm.start_servers
pm.max_children
pm.max_spare_servers
pm.min_spare_servers
static
pm.max_children
ondaemon
pm.process_idle_timeout
- 数组底层
- 如何保证有序: 又加了一层映射表 (与bucket大小相同)
- 如何解决hash冲突: 拉链法(头插)
- 扩容: 逻辑删除 (考虑
unset
内存情况, 是否需要重建索引)
MySQL
- 索引
- 物理存储
- 聚簇索引
- 非聚簇索引
- 数据结构
B + 树
hash
fulltext
R-tree
- 逻辑角度
- 唯一索引
unique
- 普通索引
index
- 主键索引
primary key
- 全文索引
full index
(myisam) - 复合索引 (最左前缀原则)
- 类似 where a and b and c a b c 问题
- 联合索引(a,b,c) 能够正确使用索引的有 (a=1), (a=1 and b=1), (a=1 and b=1 and c=1) (b=1 and c =1)
- 唯一索引
- 物理存储
- 引擎类型
myisam
innodb
- 区别
- myisam采用非聚簇索引, innodb采用聚簇索引
- myisam索引myi与数据myd文件分离, 索引文件仅保存数据记录指针地址
- myisam的主索引与辅助索引在结构上没区别, 而innodb不一样: innodb的所有辅助索引都引用主索引为data域
- innodb支持事务, 行级锁, myisam不行
- innodb必须有主键, 而myisam可以没有
- 事务
- 原子性
atomicity
- 一致性
consistency
- 隔离性
isolation
- 持久性
durability
- 原子性
- 分表数量级
- 单表在500w左右, 性能最佳, BTREE索引树在3-5之间
- 隔离级别
- 事务的隔离性是数据库处理数据的基础之一,隔离级别是提供给用户在性能和可靠性做除选择和权衡的配置项目,以下四种情况都有一个前提(在同一个事务中)
read_uncommited
: 未提交读(脏读)read_commit
: 已提交读 (不可重复读)repeatable_read
: 可重复读serialize
: 可串行化
- 索引机制(算法)
hash
b+tree
b-tree
(不要念成b减tree,-只是个符号)
- 锁
- 种类
optimistic lock
乐观锁- 特点: 不会真的死锁
pessimistic lock
悲观锁- 为了保证事务的隔离性, 就需要一致性锁定读.读的时候要加锁,防止其他事务再次更改,修改的时候也要加锁,其他事务无法读取。主要就是依靠数据库的锁机制来实现,同时缺点很明显,就是会带来性能的开销,并发的减少
innodb
的MVCC(Multi-Version Concurrency Control)- 多版本并发控制, 适用于行锁的、事务性的数据库模型
- 适用于innodb的
rc
和rr
级别, 因为可串行化涉及到锁表 - 实现思想是在每行增加一个
create_verison
和delete_version
字段 update
是插入一个新行,先保存当前版本号到旧行的delete_version
,且新建行的new_create_version
也就是delete_version
delete
操作就是直接标记delete_version
insert
的时候,就是保存至create_version
select
的时候可以这样- 读
delete_version
为空的 - 大于当前事务版本号的
- 创建版本号 <= 当前事务版本号的
- 读
- 粒度划分
- 行锁
- 表锁
- 意向锁
intention lock
(表级锁)- 场景:A对表中一行进行修改,B对整个表修改。如果没有以下的两个锁,B将对全表扫描是否被锁定。反之,A可以对某行添加意向互斥锁(表级),然后再添加互斥锁(行级),然后B只需要等待意向互斥锁释放)
- 意向共享锁
- 意向互斥锁
- 共享锁
shard lock
读锁(行锁) - 排它锁
exclusive lock
写锁(行锁) - 关于innodb必须要知道的
- 可以通过
SELECT \* FROM products WHERE id='3' FOR UPDATE
进行锁,但是必须在事务中 - 上述语句必须是命中索引才会行锁,否则是
table lock
- 可以通过
- 锁的算法
record lock
:加到索引记录上的锁,如果通过where条件上锁,而不知道具体哪行,这样会锁定整个表gap lock
:间隙锁某个区间的锁定,对索引记录中的一段连续区域的锁。next-key lock
:行锁和GAP(间隙锁)的合并,next-key锁是解决RR级别中 幻读问题的主要方案。可以搜索关键字 快照读(snapshot read)和当前读(current read)去了解
- 分库分表
- 主从复制 读写分离
- ACID
- 覆盖索引(复合索引)
- 定义:包含两个或多个属性列的索引称为复合索引。如果查询字段是普通索引,或者是联合索引的最左原则字段,查询结果是联合索引的字段或者是主键。这种就不必通过主键(聚集索引再次查询)
- 目的: 减少磁盘IO, 不用回表
- b+树索引
- 种类
Nginx
Nginx
worker_connections
upstream weight
- 负责均衡实现方式
- 轮询
- IP哈希
- 指定权重
- 第三方
fair
url_hash
Linux
- Linux
epoll
select
netstat
查看tcp
udp
unixsock
网络- 查看负载
cat /proc/loadavg
|w
|top
df
lstat
: strace的时候常常可见它top
:shift+M
free
lsof
: 查看当前进程ID, 进程名等占用的文件描述符ipstat
strace
grep [-A ,-B, -C]'HTTP/1.1" 200' access.log | wc -l
socket
和管道(pipe
)的区别:socket
全双工,pipe
半双工*2awk
&sed
awk '{print $1}' access.log | sort | uniq | wc -l
Redis
- 类型/应用场景
string
: cache, incrhash
: key为key, value为Hashmapset
: 去重(中奖只一次sismember
), 交/并/差 (如微博社交关系), 内部实现为value为null的hashmapzset
: (sorted set), 既去重又能保证按照score排序, 比如按照帖子的关注个数排序,value为帖子id,个数为score。list
: (阻塞rpop
) 消息队列、列表旋转(常用于监控,rpoplpush
)HyperLogLog
: 大量统计 (非精确)bitmaps
OBJ_ENCODING
string
OBJ_ENCODING_RAW
, 代表sds, 原生string类型OBJ_ENCODING_INT
, long类型OBJ_ENCODING_EMBSTR
, 嵌入
OBJ_HASH
OBJ_ENCODING_HT
, 表示成dictOBJ_ENCODING_ZPILIST
, hash用ziplist表示
OBJ_SET
OBJ_ENCODING_INTSET
, 表示成intest- config
- set-max-intset-entries 512
OBJ_ZSET
OBJ_ENCODING_SKIPLIST
, 表示成skiplist
OBJ_LIST
OBJ_ENCODING_QUICKLIST
- config
- list-max-ziplist-size-2
- list-compress-depth 0
- 扩展问题
- zset如何根据两个属性排序? 比如根据id和age
- 可以用位操作, 把两个属性合成一个double
- 用zunionstore合并存储为新的key, 再zrange
- redis是如何保证原子性操作的?
- 因为它是单线程的! (MySQL是多线程)
- 在并发脚本中的 get set 等不是原子的
- 在并发的原子命令 incr setnx 等是原子的
- 事务是保证批量操作的原子性
- 主从复制过程
- 从服务器向主服务器发送sync
- 主服务器收到sync命令执行BGSAVE, 且在这期间新执行的命令到一个缓冲区
- 主执行(BGSAVE) 完毕后,将
.rdb
文件发送给从服务器, 从服务器将文件载入内存 - BGSAVE期间到缓冲区的命令会以redis命令协议的方式, 将内容发送给从服务器
- zset如何根据两个属性排序? 比如根据id和age
- 特性
- 单线程, 自实现(event driver库, 见下面四个io多路复用函数)
- io多路复用, 最常用调用函数: select (epoll, kquene, avport等), 同时监控多个文件描述符的可读可写
- reactor方式实现文件处理器 (每一个网络连接对应一个文件描述符), 同时监听多个fd的accept, read (from client), write (to client), close文件事件
- 备份与持久化
rdb
(fork进程dump到file)- 手动:
save
(阻塞) &bgsave
(fork 子进程), 但是这两个不会同时进行 - 自动触发:
conf:save 900 1 save 300 10 save 60 10000 dbfilename dump.rdb
- rdb优点: 对服务进程影响小, 记录原数据文件方式便于管理还原
- rdb缺点: 可能数据不完整
- rdb为纯文本文件, 可以用
od -c dump.rdb
分析
- 手动:
aof
(类似binlog)- 三种写入同步方式
appendfsync no
appendfsync everysec
(每个事件循环写入缓冲区, 但是每隔一秒同步到磁盘文件)appendfsync always
(每执行一个命令, 每个事件循环都会执行写入aof 缓冲区并同步到磁盘文件,效率最慢,但是最安全)
- aof优点: 数据最完整, 可以通过数据重写rewrite来减少体积, 存储内存为redis的纯文本协议
- aof缺点: 文件相对rdb更大, 导入速度比rdb慢
- 一般有了aof就不rdb, 因为aof更新频率更高
- 三种写入同步方式
- 过期策略
- 定时过期: 时间到了立即删除, cpu不友好, 内存友好
- 惰性过期: 访问时判断是否过期, cpu友好, 内存不友好
- 定期过期: expires dict中scan, 清除已过期的key, cpu和内存最优解
- 内存淘汰机制
noeviction
: 新写入时会报错allkeys-lru
: 移除最近最少使用的keyallkeys-random
: 随机移除某些keyvolatile-lru
: 设置了过期时间的key中, 移除最近最少使用volatile-random
: 随机移除某些keyvolatile-ttl
: 设置类过期时间的键中, 有更早过期时间的key优先移除
- redis队列特殊关注之处
- 队列可能丢东西
- 比如redis挂了, producer没有停止, 但是队列数据无法写入
- 队列的consumer需要手动处理commit协议
- 如果consumer处理完, 表示真正完成
- 如果没有处理完?放回队列?直接丢弃?
- 事件重放机制不支持
- 比如consumer消费错了, 那能不能将队列回放呢?再次处理呢?
- 队列最大长度及过期时间
- 如果producer远大于consumer, 撑爆了怎么办
- 如果comsumer一直没有处理, producer的数据如何处理
exactly once
- 单机锁
setnx
或者基于set
众多参数没有问题, 集群下可利用tag机制 - 如何保证业务执行时间超过锁的过期时间, 而引起误删除操作, 答案是可以加一个唯一标识
- 队列可能丢东西
vs memcache
memcached
- 优势
- 多线程(listen & worker), 利用多核
- round robin
- cas (check and set, compare and swap)
- 劣势
- cache coherency、 锁
- key大小有限制 (1M)
- 特点
- 内存预分配: slab + trunk
- 优势
redis
- 优势
- 自己封装了一个AEEvent (epoll + select + kqueue), io多路复用
- 丰富的数据结构 (对内 + 对外)
- 良好的持久化策略 (rdb + aof)
- 劣势
- 排序、聚会cpu密集操作会影响吞吐量
- key 大小最大为 1g
- 优势
- redis的事务机制
- 基于乐观锁的watch multi exec
- redis call lua 脚本 (比如get+del一起)
- 2.6.12后set命令支持(setnx + expire就不需要写lua script了)
- redis 下的分布式锁,当主从不同步或者主重新被选举需要多想想,主从情况下一般采用从节点的大多数 (es也是这样)
- redis 主从哨兵配置,copy三份redis.conf文件,以下设置一主二从一哨兵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16vim redis01.conf
port 63791
vim redis02.conf
port 63792
slaveof 127.0.0.1 63791
vim redis03.conf
port 63793
slaveof 127.0.0.1 63791
vim sentinel.conf
daemonize yes
port 26379
sentinel monitor mymaster 127.0.0.1 63791 1
// mymaster为自定义命名,127.0.0.1 63791为master,1为选举主节点的时候投票数目的同意个数,1代表有一个哨兵同意就行。 redis cluster
- 集群会将数据自动按照算法分割在不同节点负责的槽上 (data sharding)
算法&数据结构
- 最小堆: 根节点为最小值, 且节点比其他子节点小
- 平衡树 (avl红黑树)
- 最大堆: 根节点为最大值, 且节点比其他子节点大
sikplist
hash
hash
碰撞原因hash
碰撞解决方案- 拉链, 塞到链表里. 有点是相对简单, 但是需要附加空间
- 开发寻址, 有点是空间利用率高, 一直找
- 线性探测
- 二次探测再散列函数
- 伪随机数
- 给定数组n, 判断n是斐波那契数列的第几项? 写算法
- 反转列表如A->B->C->D 到 A->D->C->B
- 插入排序
- 数组与链表区别与联系
- 链表操作
- 单链表删除
- 单链表插入
- 快慢指针判断环路、找链表中点
- 应用问题
- 如何实现一个LRU功能 (双向链表)
- 如何实现浏览器前进后退功能 (两个栈)
网络协议
- HTTP
- 构成: 起始行(Get=>200), 首部头(Accept=>Content-Type), 主体(name => tongbo)
- 版本
- 1.0
- 1.1
- 2.0 多路复用, 流量控制
- 长连接
- 在一个连接上发送多个数据包
- 心跳、如何发送心跳
- HTTP DNS
- 定义: 用http协议代替元素的udp dns协议, 可以绕过运营商的local dns
- 解决问题: 避免local dns造成的域名劫持问题和调度不精确问题
- 其他解决方案
- 客户端dns缓存
- 热点域名解析
- 懒更新策略 (ttl过期后再同步)
- Post请求分割head和body
- Get vs Post
- Get
- 安全幂等, 请求实体资源
- 参数只能url编码, 且参数长度有限制
- 浏览器会自动加cache
- Post
- 附加请求实体于服务器
- 产生两个TCP数据包
- 数据支持多种编码格式
- Get
- Resultful
get
: 获取资源post
: 新建资源put
: 跟新完整资源delete
: 删除资源patch
: 更新部分资源
- RPC
- RPC框架涉及基本组件服务
- 客户端、服务端自动代码生成、多语言支持
- 消息序列化、反序列化
- 连接池、负载、故障、队列、超时、异步
- 常见协议
- soap (http jsonrpc)
- GRPC
- thrift(tcp)
- RPC框架涉及基本组件服务
- TCP
- 面向连接, 先建立(握手), 然后释放(挥手确认拜拜)
- 只能点对点
- 可靠交付, 全双工, 接收和发送端都设有发送和接收cache
- 面向字节流
- 特性协议
- 停等
- 超时重传
- 慢启动
- 滑动窗口
- 快速重传
- UDP
- 无连接、best effort、面向报文(不合并、不拆分、保留边界)
- 无拥塞控制、流量控制、首部开销小(8个字节, 而TCP有20个首部)
- 支持一对一、一对多、多对一
- 自定义协议
PHP框架
- laravel
- AppServerProvider register:服务提供者注册
- IocContainer: (IoC容器, 工厂模式的升华)
- 控制反转 (inversion of control) 可以降低计算机代码直接的耦合, 其中最常见的方式叫做依赖注入 (Dependence Injection), 还有一种方式叫依赖查找
- 实现方式
- 基于接口: 实现特定接口以供外部容器注入所依赖类型的对象
- 基于Set方法:
- 基于构造函数: 实现特定参数的构造函数
- 管理类依赖
- 执行 (依赖注入DI): 通过构造函数或者某些情况下通过Setter方法将类依赖注入到类中, 容器并不需要被告知如何构建对象,因为他会使用PHP的反射服务自动解析出具体的对象
- swoole
- thinkphp
- ci
- yii
- easyswoole
操作系统
- 操作系统
- 多线程
- 多进程
- 协程的理解
- socket和管道的区别
- 进程间通信手段
- 共享内存
- rpc
- 管道
- 线程间通信手段
- 读写进程数据段
设计模式
- 单例模式 (static, construct)
1 |
|
- 简单工厂 (switch case include new return)
1 |
|
- 门面模式
- 对客户屏蔽子系统组件, 减少子系统与客户之间的松耦合关系
- 依赖注入(
DI
) 和AOP
思想
大前端
- js
- 百度统计的实现
- 基于cookie, 引入js脚本及百度个人账户ID, 读取当前信息, 适当节点发送请求给百度服务
- 前后端分离
- 百度统计的实现
运维
- 运维&架构
- 服务器CPU99%如何分析
- MySQL占CPU如何分析
- PHP占CPU较高如何分析
- SSO实现方法
- MySQL优化方法
- 如何提高监测数据的准确性
- Dcoker原理及引用及编排管理
分布式|微服务
- 分布式
- redis分布式锁问题
- cap及常见应用关注cap哪两点
- 微服务
- 优点
- 相对于单体服务更简单, 注重单一功能
- 每个服务可以独立开发打包测试部署, 且语言环境无关
- 可以水平、高效扩展
- 缺点
- 运营成本, 服务发现, 治理, 降级, 熔断
- 网络信息传输、安全、延迟
- 服务调用排查追踪
- 最佳原则
- 高内聚: 修改了一个功能, 只需要改一个服务
- 低耦合: 修改了一个地方, 不需要改其他的地方
- 业务内原则
- 新服务用新的微服务, 确定无误后保留推进, 否则调整
- 老的保留, 直到新服务稳定再切换
- 必须的监控与日志 | 生产 - 订阅 - 消费模型
- 尝试对外不可见的服务先做是试点, 错误邮件, 日志, 系统内调用, API内部分成熟接口
- 优点
- 考虑问题
- 分布式数据一致性问题?CAP如何权衡
- 调用链追踪(基于OpenTracing协议的JeagerORZipkin)