## 准备 1. 自我介绍:说上家公司负责的项目或者浓缩简历。 2. 简历(最优.pdf格式)项目经验层次【qǐ chéng zhuǎn hé】 3. 具体展开到某个项目(需要结合自身岗位定位,用[STAR](https://link.segmentfault.com/?enc=6VBKlZCPjpkPD2%2FcaykxKg%3D%3D.zpG1l%2ByinKvvJNB%2B4hjDrzpiQqG26PFw51O9%2BdSdF0HS9qzZKzMDr2zxqhjm8uU%2BMFQA9E2WS5gAhgsMxGYKKw%3D%3D)法则) * 项目背景 * 难点在哪 * 遇到什么问题及解决方案 * 工作内容(利用什么技术,实现了哪些功能) * 面向(对象oop,切面aop) * 设计模式(代理、工厂、单例、门面、观察者) * xx模块(socket,pdo,curl) * 中间件(codis,Mycat,mq,kafka) * 并发,锁,协程 * 容器及编排 * 涉及底层(e.g.:网络模型,内核调用,多进程/线程、算法) * 工作结果(尽量量化,比如减少了多少成本) * 项目周期及你的职责、角色 4. 应用型的问题:要记得站高看远、架构分层 5. 涉及管理经验及自身对相项目管理的理解 6. 如果问你还有什么要问的问题,可以参考以下几个点【注意顺序】 * 部门的技术栈?以及对用人的要求和考核? * 接下来的面试流程是什么,还有几轮(有没有机会)? * 我是来维护代码(填坑)的还是来针对新业务(挖坑)的? ## 知识点列表 ### 一、Redis 1. redis(这块说的太乱了,回头我好好整理下~) * 应用场景 * string:cache,incr(此时redisObject encoding为int) * hash :key为key,value为Hashmap(少时用redisObject encoding ziplist OR 增大时为ht) ```[sql] ```[sql] * set:去重(中奖只一次sismember),交,并,差(如微博社交关系),内部实现为value为null的hashmap ``` ``` * zset: 或者叫sorted set。既去重又能保证按照score排序,比如按照帖子的关注个数排序,value为帖子id,个数为score。面试中常问其实现机制(跳跃列表(skiplist):用多个指针分层串起来+hashmap(本身hashmap无法进行二分查找,所以必须加持skiplist机制)),另外还有一个应用可以是消息优先级。 * list:(阻塞rpop)消息队列、列表旋转(常用于监控,rpoplpush) * HyperLogLog:大量统计(非精确) * bitmaps * [内部数据结构](https://link.segmentfault.com/?enc=wL4Kk%2F0jlw2Ye5LbToUGiw%3D%3D.9btasfl4ocsZEtvo%2BZziwL6WfRmCZJwEYXy54vqp1JA%3D) * * 一图先胜个千言↓ * ![reids内部数据结构实现](http://120.77.67.133/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20220317133147.png "reids内部数据结构实现") * dict:底层数据库和hash结构都是key\[ redis string obj\] AND value \[redis obj\]组成的,其中 string是redis通过内部封装的SDS结构体实现,而value五种object对应redis中常见的五种数据类型。了解了dict是掌握redis数据结构的前提,见下图↓: * ![redis dict 数据结构](http://120.77.67.133/807548180-110db184ea08a7a7_fix732 "redis dict 数据结构") * Hash:dictht->dickEntry,dict是对dictht结构再次封装。装拉链法(单向链表)解决冲突(同php),采用头插的方法;装载因子(哈希表已保存节点数量 / 哈希表大小)超过预定值自动扩充内, 引发(增量式|渐进式)rehash * 根据ht\[0\]创建一个比原来size大一倍(也可能减少)的hashtable, * 重新计算hash和index值,根据rehashindex逐步操作 * 到达一定阈值(ht\[0\]为空)操作停止 * 期间响应客户端 * 新增写操作:新增的写到新的ht\[1\] * 删除、查找、更新操作:先读ht\[0\],再读ht\[1\] * sds:simple dynamic string * string的底层实现为sds,但是string存储数字的时候,执行incr decr的时候,内部存储就不是sds了。 * 二进制安全binary safe(5种类型的header+falgs得到具体类型,进而匹配len和alloc) * robj:redis object,为多种数据类型提供一种统一的表示方式,同时允许同一类型的数据采用不同的内部表示,支持对象共享和引用计数。 * sds * string * long * ziplist * quicklist * skiplist * ~~~c typedef struct redisObject { unsigned type:4;//OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH unsigned encoding:4/*上述type的OBJ-ENCODING _XXX常量,四个位说明同一个type可能是不同的encoding,或者说同一个数据类型,可能不同的内部表示*/; unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr//真正指向的数据; } robj ~~~ 2. OBJ\_ENCODING\_ * string * OBJ\_ENCODING\_RAW,代表sds ,原生string类型 * OBJ\_ENCODING\_INT,long类型 * OBJ\_ENCODING\_EMBSTR ,嵌入 * OBJ\_HASH * OBJ\_ENCODING\_HT,表示成dict * OBJ\_ENCODING\_ZIPLIST,hash用ziplist表示 * OBJ\_SET * OBJ\_ENCODING\_INTSET,表示成intest * config * set-max-intset-entries 512【是整型而且数据元素较少时,set使用intset;否则使用dict】 * OBJ\_ZSET * OBJ\_ENCODING\_[SKIPLIST](https://link.segmentfault.com/?enc=YRjpOzx02N3zwibkuIimxw%3D%3D.WaDvPlCtuHp3loFMSf1ypeRGMsj9ajysAuCRfRhGkz35rdlUISEjtrBYKyze5%2F%2Fsr5m5Kk%2FZ%2FKyTKNsjSSN%2F0g%3D%3D),表示成skiplist * 思想:多层链表,指针来回查找,插入和更新采取随机层数的方法来规避 * config * zset-max-ziplist-entries 128 * zset-max-ziplist-value 64 * OBJ\_LIST * OBJ\_ENCODING\_QUICKLIST * config * list-max-ziplist-size -2 * list-compress-depth 0 3. 内部结构实现(针对上面的再‘翻译一下’) * string * hash(两种encoding,根据下面的config) * ziplist * dict * config * hash-max-ziplist-entries 512【注意单位是“对儿”】 * hash-max-ziplist-value 64【单个value超过64】 * list * quicklist * 定义:是一个ziplist型的双向链表 * 压缩算法:LZF 4. 扩展问题: * zset如何根据两个属性排序?比如根据id和age * 可以用位操作,把两个属性合成一个double * 用zunionstore合并存储为新的key,再zrange * redis是如何保证原子性操作的? * 因为他是tm单线程的!(ps:mysql是多线程) * 在并发脚本中的get set等不是原子的~ * 在并发中的原子命令incr setnx等是原子的 * 事务是保证批量操作的原子性 * 主从复制过程: 1. 从服务器向主服务器发送sync 2. 主服务器收到sync命令执行BGSAVE,且在这期间新执行的命令保存到一个缓冲区 3. 主执行(BGSAVE)完毕后,将.rdb文件发送给从服务器,从服务器将文件载入内存 4. BGSAVE期间到缓冲区的命令会以redis命令协议的方式,将内容发送给从服务器。 5. 特性: * 单线程,自实现(event driver库,见下面四个io多路复用函数) * 在/src/ae.c中:宏定义的方式 * ~~~c /* Include the best multiplexing layer supported by this system. * The following should be ordered by performances, descending. */ #ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif ~~~ * io多路复用,最常用调用函数:select(epoll,kquene,avport等),同时监控多个文件描述符的可读可写 * reactor方式实现文件处理器(每一个网络连接对应一个文件描述符),同时监听多个fd的accept,read(from client),write(to client),close文件事件。 6. 备份与持久化 * 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 OS自主决定何时flush * appendfsync everysec(每个事件循环写入缓冲区,但是每隔一秒同步到磁盘文件) * appendfsync always (每执行一个命令,每个事件循环都会执行写入aof 缓冲区并同步到磁盘文件,效率最慢,但是最安全) * aof优点:数据最完整,可以通过`auto-aof-rewrite-percentage``auto-aof-rewrite-min-size`来fork新的进程来重写内存中的数据(),存储内容为redis的纯文本协议 * aof缺点:文件相对rdb更大,导入速度比rdb慢 * 一般有了aof就不rdb,因为aof更新频率更高 7. 过期策略: * 定时过期:时间到了立即删除,cpu不友好,内存友好。 * 惰性过期:访问时判断是否过期:cpu友好,内存不友好 * 定期过期:expires dict中scan,清除已过期的key。cpu和内存最优解 8. 内存淘汰机制 * ~~~apache 127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction" ~~~ * noeviction:新写入时回报错 * allkeys-lru:移除最近最少使用的key * allkeys-random:随机移除某些key * volatile-lru:设置了过期时间的key中,移除最近最少使用 * volatile-random:不解释 * volatile-ttl:设置类过期时间的键中,有更早过期时间的key优先移除 9. redis队列特殊关注(可能坑)之处 1. 队列可能丢东西 * 比如redis挂了,producer没有停止,但是队列数据无法写入(除非同步落地到mysql) 2. 队列的consumer 需要手动处理commit协议 * 如果consumer处理完,表示真正完成 * 如果没有处理完?放回队列?直接丢弃? 3. 事件重放机制不支持 * 比如consumer消费错了,那能不能将队列回放呢再次处理呢? 4. 队列最大长度及过期时间 * 如果producer远大于consumer,撑爆了怎么办 * 如果comsumer 一直没有处理,producer的数据如何处理 5. exactly once 6. 单机锁`sennx`或者基于`set`众多参数没问题,集群下可利用tag机制 7. 如何保证业务执行时间超过锁的过期时间,而引起误删除操作。答案是可以加一个唯一标识 10. [Redis vs Memcache](https://link.segmentfault.com/?enc=UFBpXQ3qkHSN%2BI%2BLu%2B%2FyTQ%3D%3D.mpk68Z6eUx7%2BzdOiJU%2BZqn3NsfR5KLOrGtCD%2BBN0f%2F5uN8OWuCtefnPGpgUKgl0elFKgajdQt87l%2FEqw6WE%2F3Q%3D%3D) * memcached * 优势 * 多线程(listen & woker),利用多核 * 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 11. more | other 13. * redis ziplist与普通双向链表的区别:普通的链表每一项都占用独立的一块内存,各项之间用地址指针(引用)连接起来,这样会导致大量碎片。而ziplist是将表中每项放在前后连续地址空间内,而且对值存储采取变长编码。 * redis msetnx对应的del,可以采取lua脚本保证get del的原子性 * redis 单线程如何实现阻塞队列? * 阻塞是阻塞client,又不是阻塞server,server不发数据,client不就阻塞住了,当client想要阻塞在某个key上,server会把这个client放到一个block list里,等key发生变化,就send数据给client。 * redis 阻塞队列的时间设置实现? * blocklist里只存了列表,这个timeout存在连接上,靠serverCron来遍历检测,每次遍历5个, * 高性能的方案是小堆或者红黑树或者时间轮实现的定时器结构,epoll wait那块timeout参数就设置成下次超时时间 * 每次poll loop里除了处理io事件,再把定时器的数据结构里处理下,堆和红黑只要检测到一个未超时就可以break了,时间轮这是当前槽都触发了就行 * 每次检测5个这种比较折中,因为他场景不是大量并发的服务器,rds cli的连接数量毕竟使用者内部可控,而且不需要精确打击,只要保障相对能及时清理就行,redis的网络部分相对比较简单,业务场景可控,足够了 * redis集群情况下如何做到两个key必hash到一个节点?用{} * redis 事务机制 * 基于乐观锁的multi exec|discard watch key unwatch * redis call lua脚本(比如get+del一起) * 2.6.12后set命令支持(setnx+expire就不需要写lua script了) * redis 下的分布式锁,当主从不同步或者主重新被选举需要多想想,主从情况下一般采用从节点的大多数(es也是这样) * redis 主从哨兵配置,copy三份redis.conf文件,以下设置一主二从一哨兵 ~~~smali vim 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) * [redis cluster官方文档简洁明了啦](https://link.segmentfault.com/?enc=GS8ReK2kWXyPCC9nIyS%2FNw%3D%3D.Qrif6tZkG2oU5Ffqu1gFF45gyhsyYZQOyzfcyH4VbJyRnotPRzYk%2BPuoG4Ssq8Sh) * 具体展开问题 * redis的过期删除策略有哪些? * Redis是单线程的?为什么还要加锁呢? * redis的缓存穿透和缓存击穿怎能解决? * 缓存穿透是指一杆到底,(即cache和db中都没有,比如请求一个id=-1的数据)穿了cache和db。可以加业务前台一层过滤器以及完善NULL empty等情况缓存。 * 缓存击穿不可避免(比如秒杀情况)。这样可以通过增加互斥锁回写缓存|缓存过期时间适当延长 * 缓存雪崩 cache崩完db崩,db崩完系统崩 * 原因:大量key同一时间失效|cache宕机,导致并发连锁崩 * 解决:[分级缓存](https://link.segmentfault.com/?enc=Hfd8N5ptB7nwiBWWTAP1FA%3D%3D.prDJMyCMrPsqeJhL8Ti6fnHLhx4SGQ%2B5KAzJWHiT2rk%3D) * 客户端分级缓存:需要结合业务场景自动探测发现+cache部署与一致性更新+命中与miss统计等核心点。 * 服务端分布式缓存:类似redis cluster * * * ### 二、MySql 1. 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 * 区别: 1. myisam采用非聚集索引,innodb采用聚集索引 2. myisam索引myi与数据myd文件分离,索引文件仅保存数据记录指针地址。 3. myisam的主索引与辅助索引在结构上没区别,而innodb不一样:innodb的所有辅助索引都引用主索引作为data域。 4. innodb支持事务,行级锁。myisam不行。 5. innodb必须有主键,而myisam可以没有。 * 相同点: * 都是b+tree 索引 * 存储 * innodb * 数据被逻辑的存在tablespace,extend(区)中有多个page,page(默认16kb)里放row(每一行,大概每个page放2-200行记录) * .frm:table's format * .ibd:table data & associated index data * ubunut的frm文件和ibd文件可在目录`root@udev:/var/lib/mysql# ls`中查看,下图为`innodb`行格式,由下到上,均向上兼容 * ![clipboard.png](https://segmentfault.com/img/bVbvola "clipboard.png") * Antelope 羚羊 对存放bolb或者大varchar存储极长的数据时,将行数据的前768字节存储在数据页中,后面通过偏移量指向溢出页。 * compact 紧凑的 * redundant 多余的 * Barracuda 梭鱼 * antelope对存放blob的数据完全溢出,在数据页中只存在20个字节的指针,实际数据放在bolb page * compressed * dynamic * [more](https://link.segmentfault.com/?enc=TwPymCRXah3aoeos7gw6%2Fw%3D%3D.RJUGi%2BJV%2FSlksNHv749cFe4xYCsEOla0BZxY4T8bAD7zdrZWTVuoyV7B7BBzd1yoQzAlaNkzIYUTPwiI3sERCk2RDgFuyEIDa83g8xQ47lU%3D) * myisam * frm(与innodb通用),在磁盘的datadir文件 * myi * myd * 事务 * 原子性atomicity * 一致性consistency * 隔离性lsolation * 持久性durability * 分表数量级 * 单表在500w左右,性能最佳。BTREE索引树 在3-5之间 * 隔离级别 * 事务的隔离性是数据库处理数据的基础之一,隔离级别是提供给用户在性能和可靠性做除选择和权衡的配置项目,以下四种情况都有一个前提 * 1. ![](https://segmentfault.com/img/remote/1460000023809723)![image.png](https://segmentfault.com/img/bVbL39y "image.png") * 1.read\_uncommited:脏读,未提交读。不加任何锁,能读取到其他会话中未提交事务修改的数据,主流数据库几乎没有这么用。的见下图 * ![clipboard.png](https://segmentfault.com/img/bVbvolk "clipboard.png") * 2.read\_commit:RC,不可重复读,已提交读。注意这个与1的区别是,读取不加锁,但是数据的写入、修改和删除是需要加锁的,而1是啥锁都不加。啥🔒都不加。只对记录加记录锁,而不会在记录间加间隙锁。所以允许新的记录插入到被锁定记录的附近,所以多次使用查询语句时,可能得到不同的结果,non\_repeatable\_read,见下图,第二个事务修改了id=1的数据后,第一个事务再次读取的时候结果不一样。这就是不可重复读,即两次结果不一样。 * ![image.png](https://segmentfault.com/img/bVbzOYs "image.png") * 3.repeatable\_read【默认级别】:RR,可重读,幻读,返回第一次的查询的快照(不会返回不同数据)。虽然事务2提交了更改,但是事务1仍然读到的是事务2未修改的状态。见下图 * ![image.png](https://segmentfault.com/img/bVbzOZJ "image.png") * 4.serialize:解决了幻读,可串行化。幻读和可重复度读的区别在于导致幻读的是其他事务的insert。这样会引起第一个事务莫名其妙的多出来一条数据。幻读主要是依靠读锁写锁相互独立(互斥),但是会极大的降低并发能力。 * 一句话总结: * 读未提交:B可以读到A未提交的事务,i.e A中未提交前所做的改变可以让其他任何事务看到。 * 读已经提交:B只能读到A提交之后的事务,i.e A中的更改只有在提交之后才能让其他事务看到。 * 可重复读:B执行过程中,其他数据想对来说都是静态的。也就是说看到A的数据是一致的(不管期间A提交了没提交) * 串行化:B和A读写都依次分别上锁 * 索引机制(算法) * * hash * b+tree(m阶b+tree) * ![](https://segmentfault.com/img/remote/1460000022762409) * 所有数据保存在叶子节点,有k个子树的中间节点包含有k个元素 * 所有叶子节点包含了全部的元素信息,及指向这些元素记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接 * 所有的中间节点元素都同时存在于子节点,在子节点元素中都是最大(或最小)元素(或者说:每一个父节点的元素都出现在子节点中,是子节点的最大或者最小元素) * 插入的元素,要始终保持最大元素在根节点中,再次说:所有的叶子节点包含了全量元素信息。每个叶子节点都带有指向下个节点的指针,形成了有序链表 * ![clipboard.png](https://segmentfault.com/img/bVbvolC "clipboard.png") * b-tree【不要念成b减tree,-只是个符号】 * ![](https://segmentfault.com/img/remote/1460000022762410) * 内存操作(单一节点数量很多时,注意并不比二叉查找树数量少,只是速度比硬盘要快) * 自平衡 * 左旋、右旋 * mongoDB用的是balance tree * 特点(m阶的B树) * 根节点至少有两个子女 * 每个中间节点都包括k-1个元素和k个孩子(m/2<=k<=m) * 每个叶子节点都包括k-1个元素,(m/2<=k<=m) * 所有的叶子节点位于同一层 * 每个节点中的元素从小到大排序,节点当中k-1个元素正好是k个孩子包含元素的值域划分 * b+与b-tree区别(为啥mysql中用Bplus,而mongo用B-tree?) * 说简单点就是业务场景,mysql一般设计where,group,order,join等操作,mongo更多是一把梭(也可以order range,但是你这么用容易造成姿势不对而导致的性能差后果) * 他俩都是m叉平衡查找树。B+中间节点没有数据(存储多个子节点指针),值在叶子节点存储。而b-Tree有具体数据(可以理解为key+data的二维数组,没有叶子节点的概念),所以前者同样大小可以容纳更多的节点元素。这样又导致了B+比b更“矮胖”,更进一步减少了io查询次数。很好的解释了下面这句话:在cluster index(聚集索引中),叶子节点直接包含卫星数据;在非聚集索引中nonclustered index中,叶子节点带有指向卫星数据的指针。 * b-只要查找到匹配元素,直接返回。而b+查询必须查找到叶子节,相对于b,b+无需返回上层节点重复遍历查找工作,所以得出b查找并不稳定,而b+是稳定的,b+树通过双向链表将叶子节点串联起来,基于空间换时间。目的就是支持倒叙和顺序查询。 * 针对范围和顺序查询,b需要n次中序遍历,而b+只需要通过叶子节点的双向链表指针遍历即可。 * b+更符合磁盘的局部预读原理,b只需要简历索引拿到想要的data就可以(mogno就是如此,范围、顺序、多索引查找等不适合用mongo) 2. 锁 * 种类 * optimistic lock乐观锁(并非真正的锁,是基于版本号基础的先尝试再更改,loop and try,可以参考状态机的状态) * 特点:不会真死锁,一定条件下有较高的冲突频率和重试成本,但是相对悲观可以有更好的并发量 * 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 * selete的时候可以这样 * 读delete\_version为空的 * 大于当前事务版本号的 * 创建版本号<=当前事务版本号的 * 粒度划分 * 实例锁 * 使用场景:全局数据备份 * 支持事务的(e.g. innodb),可以使用mysqldump –single-transaction,会自动设置隔离级别为repeated read,期间的dml的update和ddl将会被锁住。 * 不支持事务的(e.g. myisam,memory) * flush tables with read lock * lock tables * 行锁 * 表锁 * 意向锁 intention lock(表级锁) * 场景:A对表中一行进行修改,B对整个表修改。如果没有以下的两个锁,B将对全表扫描是否被锁定。反之,A可以对某行添加意向互斥锁(表级),然后再添加互斥锁(行级),然后B只需要等待意向互斥锁释放) * 意向共享锁 * 意向互斥锁 * 共享锁shard lock 读锁(行锁) * 排它锁exclusive lock 写锁(行锁) * 关于innodb必须要知道的 * 可以通过`SELECT \* FROM products WHERE id='3' FOR UPDATE`进行锁,但是必须在事务中 * 上述语句必须是命中索引才会行锁,否则是table lock * 两阶段锁(注意区分分布式事务的2pc) * 锁的算法 * record lock:加到索引记录上的锁,如果通过where条件上锁,而不知道具体哪行,这样会锁定整个表 * gap lock:间隙锁某个区间的锁定,对索引记录中的一段连续区域的锁。 * next-key lock:行锁和GAP(间隙锁)的合并,next-key锁是解决RR级别中 幻读问题的主要方案。可以搜索关键字 快照读(snapshot read)和当前读(current read)去了解~ * 死锁: ![clipboard.png](https://segmentfault.com/img/bVbvolF "clipboard.png") * 注意区分 deadlock VS lock wait timeout 3. 分库分表 * 方式:垂直、水平。按范围、按业务属性、取模 * 注意点:增加连表复杂性,某一个库宕机后导致数据查询不准确,分布式事务的问题 4. 主从复制的简单过程 及策略: * master log \[binlog event\] dump thread->slave IO thread\[Slave\_IO\_Running\] \[flush binlog event =>relay log\]->slave SQL thread\[Slave\_SQL\_Running\]->exec relay log * 策略: ~~~crmsh - 异步Asynchronous:不需要ack,通过 - 全同步:从库完全同步才可以,需要slave的ack发给slave。 - Semisynchronous Replication 半同步 复制:从库有部分ok就算ok,同样需要slave的ack发给slave ~~~ 5. mysql主从同步为什么会延迟,有什么解决办法? * 硬件资源瓶颈导致:网络、cpu,硬盘 * 业务量太大:分库分表 * 参考同步原理并优化 * 原因1:从库上单线程Slave\_SQL\_Running可能有ddl语句和查询造成lock,导致延迟。(主库ddl时为多线程) * 优化1:【5.7后可修改`set global slave_parallel_workers=10`】,用多个线程并行重放relay log【基于GTID中的commit\_id&sequence\_number等字段,可以保证多个从库和多线程最终的一致性】 * 原因2:内存flush到硬盘策略(redis的aof也是如此) * 优化2: * 考虑禁用salve端binlog * bin\_log落盘机制sync\_binlog=0的优化 6. ACID 7. 覆盖索引(复合索引) * 定义:包含两个或多个属性列的索引称为复合索引。如果查询字段是普通索引,或者是联合索引的最左原则字段,查询结果是联合索引的字段或者是主键。这种就不必通过主键(聚集索引再次查询) * 目的:减少磁盘io,**不用回表** * b+树索引 8. 聚集索引cluster index 一般为primary key * 定义:按照每张表主键构建一棵B+TREE,叶子节点放的整张表的行记录数据 * 与之相对应的是辅助索引(secondary index) * innodb存储引擎支持覆盖索引,即从辅助索引中可以查到查询记录,而不需要查询聚集索引中的记录。 * b平衡树+树索引 ![clipboard.png](https://segmentfault.com/img/bVbvolG "clipboard.png") * 上图对应的表结构: * ~~~sql CREATE TABLE users( id INT NOT NULL, first_name VARCHAR(20) NOT NULL, last_name VARCHAR(20) NOT NULL, age INT NOT NULL, PRIMARY KEY(id), KEY(last_name, first_name, age) KEY(first_name) ~~~ * 一张表一定包含一个聚集索引构成的b+树以及若干辅助索引构成的b+树 * 每次给字段建一个索引,字段中的数据就会被复制一份出来。用于生成索引,(考虑磁盘空间)。不管何种方式查表,最终都会利用主键通过聚集索引来定位到数据。聚集索引(主键)是通往真实数据的唯一出路。 9. 辅助索引:非聚集索引都可以被称作辅助索引,其叶子节点不包含行记录的全部数据,仅包含索引中的所有键及一个用于查找对应行记录的【书签(即主键或者说聚集索引)】,下面两个图为辅助索引(first\_name,age)以及通过主键再次查找的过程 * ![clipboard.png](https://segmentfault.com/img/bVbvolI "clipboard.png") * ![clipboard.png](https://segmentfault.com/img/bVbvolM "clipboard.png") 10. 联合索引:与覆盖索引没有区别,或者理解为覆盖索引是联合索引的最优解(无需通过主键回表)。 11. explain * extra * using index :condition(用了索引,但是回表了) * using where :uning index(查询的字段在索引中就能查到,无需回表) * using index condition:using filesort(重点优化:表明查到数据后需要再进行排序,尽量利用索引的有序性。) * using where:using index * type(连接类型:join type),以下逐步增大 * system 系统表,磁盘io忽略不计(有些数据就已经在内存中) * const 常量连接(加了where条件限制,命中主键pk或者唯一unique索引) * eq\_ref 主键索引或者非空唯一索引、等值连接;如果把唯一索引改为普通索引+等值匹配,可能type只为ref,因为可能一对多 * range 区间范围 between and;where in,gt lt;(注意必须是索引) * index 索引树扫描,即需要扫描索引上的全部数据,比如innodb的count * all 全表扫描 * select \* 与索引(看主要命中条数与总条数,如果相近,用不到索引,就全回表了,如果是一定范围,那就是range use indexcondition) * rows(粗略统计,不是精确) 12. 其他: 1. varchar为啥为65535?compact行记录的第一个字段为变长字段长度列表,为2个字节16位。[参考](https://link.segmentfault.com/?enc=8HFh%2BJLkHUsuLw0xSTK1lg%3D%3D.Se3e9F0zSSLIuqs7MjsB%2FdIkDH6%2BB%2B2GOYFVEjT3zl4bGeynIBlTKqWUPR5m5UmRYsEKRLo6uIlF0tbmrltF4Q%3D%3D) 2. 一个表最多多少行?1023,具体也是看行格式的数据结构即可,参考上文的参考链接。 3. 为什么建议给表加主键?主键的作用是把数据格式转为索引(平衡树) 4. 联合索引在b+树中如何存储? 5. 为什么索引不直接用二叉查找树,要用b树,b+树?主要考虑减少磁盘io(考虑磁盘物理原理及局部性与磁盘预读的特性:) 6. myisam和innodb必须有主键吗?innodb必须有,数据文件需要按照主键聚集,如果没有innodb会自动生成。 7. mysql 最大连接数默认多少(max\_connections)?答案是151。(这里可以引申到php-fpm的pconnect相关问题) 8. mvcc是怎么实现的? * mvcc 利用read view结构(包含rw\_trx\_ids、min\_trx\_id 、max\_trx\_id、 curr\_trx\_id,根据这四个字段的大小判断当前的事务执行是否可显示)和undo log实现。且只能在rr 和rc两个级别下生效【因为ru和serializ前者只读最新值,而后者会加读写锁,更新会直接阻塞,也不会存在多值的情况】 * 通过undo log记录,通过每行的三个隐藏字段 (隐藏的rowID+隐藏的事务ID+隐藏的回滚指针roll\_ptr)指向updo log表空间。 9. redo undo binlog是做什么用的?谈谈两阶段提交、两阶段锁的理解? * 两阶段锁一般用在innodb的行锁中,需要的时候加锁,commit|rollback之后释放【知识点是一个事务里的操作的先后顺序:把时间最长的放最后】。 * redo log事务(只有innodb存储引擎支持)重放日志,一般针对insert 和update时buffer pool的数据页与磁盘中不一致(没有flush|fsync到磁盘,脏页),而这时候crash。执行记录就被append到redo log中,但是redo log空间有限,会定期 * undo log 记录被insert和update之前的值,相当于ctrl+z(回撤,回滚)。 * 如果uodo insert,那么对应起回滚操作就是delete * 如果为undo delete ,那么对应回滚操作就是insert * 如果 是update,那么对应回滚操作就是逆向的update * binlog 是server层面的log,是类似append file的形式。 * 两阶段提交:指的是redo log【prepare+commit】和 binlog的同步措施 * 先写binlog,然后宕机,导致redolog没有写。这样binlog同步时会导致数据差异 * 先redo log commit后,宕机导致binlog写入失败。binlog同步时仍会导致数据差异 * 通过有prepare和binlog的场景,系统恢复后自动commit * 如果没有binlog,就直接回滚 10. 你还知道哪些数据库,和mysql相比有哪些具体的使用场景和优势? 11. 聚簇索引的结构是怎样的? 12. acid,四种隔离级别是如何实现的? 13. mysql binlog的几种同步方式? * 异步Asynchronous * 全同步 * Semisynchronous Replication 14. 一张表可以建立多少个索引? 15. 谈谈你对gtid的理解? 16. 索引下推IndexConditionCountDown和回表有什么区别和联系? * * * ### 三、算法&数据结构 * 总览(见下图): * ![image.png](https://segmentfault.com/img/bVbDFMf "image.png") * 查找算法【适用场景及其优缺点】 * 顺序查找 * 二分查找 * hash查找 * 平衡树 * 二叉搜索树 * B树 * B+ * 索引查找 * bfs&dfs * 红黑树 * 排序算法 * 应用问题 ~~~markdown - 如何实现一个LRU功能?【双向链表】 - 如何实现浏览器前进后退功能?【两个栈】 - 上台阶问题【递归斐波那契】 ~~~ * * * ### 四、设计模式 1. 设计模式 * 单例模式 (static ,consturct) ~~~php static private $instance; private $config; private funciton __construct($config){ $this->config=$config; } private funciton __clone(){ } static public function instance($config){ if(!self::$instance instanceof self){ self::$instance=new self($config); } return self::$instance; } } ~~~ * 简单工厂(switch case include new return ) ~~~php { public function makeModule($moduleName, $options) { switch ($moduleName) { case 'Fight': return new Fight($options[0], $options[1]); case 'Force': return new Force($options[0]); case 'Shot': return new Shot($options[0], $options[1], $options[2]); } } } # 使用工厂方式 001 class Superman { protected $power; public function __construct() { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 $this->power = $factory->makeModule('Fight', [9, 100]); // $this->power = $factory->makeModule('Force', [45]); // $this->power = $factory->makeModule('Shot', [99, 50, 2]); /* $this->power = array( $factory->makeModule('Force', [45]), $factory->makeModule('Shot', [99, 50, 2]) ); */ } } # 使用工厂方式 002 class Superman { protected $power; public function __construct(array $modules) { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 foreach ($modules as $moduleName => $moduleOptions) { $this->power[] = $factory->makeModule($moduleName, $moduleOptions); } } } // 创建超人 $superman = new Superman([ 'Fight' => [9, 100], 'Shot' => [99, 50, 2] ~~~ * 门面模式 * 对客户屏蔽子系统组件,减少子系统与客户之间的松耦合关系 * 设计原则 * 依赖注入(DI、AOP) * 开闭原则(open for extension, but closed for modification) * * * ### 五、正则表达式 1. 正则表达式 * 应用场景 * 范匹配 * 模版引擎 * 词法分析器(lex) * 常见正则 * * * ### 六、PHP 1. php * 代码解释过程(大多的非编译语言) * lexical词法分析,输入为源代码,输出为token * 语法分析 工具为文法(LALR),输出为表达式,7.0为AST,涉及: ~~~asciidoc - 注释 - 分号 & 分隔符 - 变量 - 常量 - 操作数 ~~~ * 类型检查、关键字处理、导入,输出为中间代码。工具为选定的的编译器优化工具 ~~~asciidoc - 中间代码生成(Opcodes) - 机器码生成(编译语言) ~~~ * session共享配置 * phpunit用法 * cookie购物车和session购物车的实现 * 弱类型实现 \-zval(不仅是变量名) & zend\_val变量值 * 代码规范 * 自动化:sonarquebe+jenkins * 单元测试 * php进程间如何通信 * 信号量(消息同步|互斥)(pv操作) sem\_\* * 信号(信号触发事件)(pcntl\_signal, pcntl\_wait\*) * 消息队列 msg\_\* * 管道(pipe) * socket |unix \_\*.sock * 共享内存(shm\_*,shmop\_*) * php并发模型 * php执行流程 * ![](https://segmentfault.com/img/remote/1460000021079262) * 变量底层存储结构 * zval结构了解多少 * 常用的数组函数(列出10个) * array\_combine(前面数组作为其键,后面数组做为其值) * array\_merage(合并两个数组,后面覆盖前面,但数字索引会重新索引,不会覆盖) * array\_multisort * php垃圾回收机制(gc) * zend.enable\_gc`php.ini` * gc\_enable()`funciton` * 引入计数(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,如果你真的想实现不同应用目录分层,一定去找下面这个文件(大概率不会有这种需求) * ![image.png](https://segmentfault.com/img/bVbBw38 "image.png") * session\_write\_close (显示关闭,后期使用需要显示开启) * 说明:因为php的语言特性,针对session的gc只能是session模块启动的时候去执行。默认的概率为1%,另外默认所有的站点都是在一个目录下,如果多站点在同一目录设置了不同的session时间,gc不会区分站点。只按照当前时间-文件创建|更新时间是否大于gc\_maxlifetime来判断。当然现在一般都放到redis实现了sessionhander接口,就不用gc了。(Not necessary, Redis takes care of that. * 内存模型 * 整型、浮点、bool、NULL、内部字符串、不可变数组都是通过zval直接保存,不会用到引用计数 * string、array都会使用引入计数(支持复制cow),object、resource本身可以理解为引用 * fpm三种配置及场景 * dynamic ~~~diff - pm.start_servers - pm.max_children - pm.max_spare_servers - pm.min_spare_servers ~~~ * static ~~~asciidoc - pm.max_children ~~~ * ondaemon ~~~asciidoc - pm.process_idle_timeout ~~~ * 数组底层 * 如何保证有序:又加了一层映射表(与bucket大小相同) * 如何解决hash冲突:拉链法(头插) * 扩容:逻辑删除(考虑unset内存情况、是否需要重建索引) * fpm 的mater worker模型与nginx的master模型有什么区别? * nginx的子进程基于epoll,是非阻塞的。可以同时监听多个请求,只处理活跃的fd * fpm 基于 fcntl封装的FCGI\_LOCK|UNLOCK函数,只能响应一个请求,是阻塞在当前进程的 * fpm 的master和worker之间如何通信?答:信号 signal,比如检测worker的超时时间 * fpm状态查看 ~~~apache curl localhost/php_fpm-status pool: www process manager: dynamic start time: 13/May/2017:09:50:43 +0800 start since: 986 accepted conn: 2 listen queue: 0 max listen queue: 0 listen queue len: 0 idle processes: 1 active processes: 1 total processes: 2 max active processes: 1 max children reached: 0 slow requests: 0 ~~~ * * * ### 七、操作系统 1. 操作系统 * 线程,进程,协程。并发,并行 * socket和管道的区别 * 进程间通信手段 * 共享内存 * rpc * 管道 * 线程间通信手段 * 读写进程数据段 * 说一下同步阻塞,异步非阻塞的关系、IO多路复用和异步IO了解多少? * * * ### 八、网络协议 1. 网络协议 * http * 构成:起始行(GET =>200),首部头 (ACCEPT=>CONTENT-TYPE),主体 name =》tongbo * 版本: * 1.0 * 1.1 * 2.0 :多路复用、流量控制 * WebSocket * 可以用wireshark抓包看下具体upgrade的流程 * 常见面试题 * ws server安全验证怎么做?(不能让谁都连吧) * 思路:handleshake,key * ws server的 Cluster怎么做? * 多节点的ws server 如何群发消息( 可以配合redis pubsub 、mq、kafka实现) * 长连接 * 在一个连接上发送多个数据包 * 心跳、如何发送心跳 * httpdns * 定义:用http协议代替原始的udp dns协议,可以绕过运营商的local dns * 解决问题:避免local dns造成的域名劫持问题和调度不精确问题(更多是在移动客户端) * 其他解决方案 * 客户端dns缓存 * 热点域名解析 * 懒更新策略(ttl过期后再同步) * post请求分割head 和body * get vs post: * get( * 安全幂等,请求实体资源 * 参数只能url编码,且参数长度有限制 * 浏览器会自动加cache * post * 附加请求实体于服务器 * 产生两个tcp数据包 * 数据支持多种编码格式 * resultful * get:获取资源 * post:新建资源 * put:更新完整资源 * delete:删除资源 * patch:更新部分资源 * RPC * 看张图↓ * ![image.png](https://segmentfault.com/img/bVbAD0t "image.png") * RPC框架涉及基本组件服务 * 客户端、服务端自动代码生成、多语言支持 * 消息序列化、反序列化 * 连接池、负载、故障、队列、超时、异步 * 以上可以引申出问题:RPC和http各自的优缺点? * 常见协议 * soap(http |jsonrpc) * GRPC:protocol buffer,(HTTP2 AND Tcp) * thrift(tcp) * [http vs rpc的缠绵关系](https://link.segmentfault.com/?enc=BoEuU2f9hvAanB0vDFGFmg%3D%3D.VL77t7TQ%2BTjdO9CSYwIgumfGYAL72paZcXaV9XciFfkVRllccRnS5I%2BdH0qPhXy5Zr9zkR0gJ4Qxq%2BHOkX0PbA%3D%3D) * [TCP](https://link.segmentfault.com/?enc=cOBwk2ABHpDcEXrzyhJwXw%3D%3D.HyHqz3UDXFfVe2VCpvEzoqn5K31YoAsaIGlNjiJas6P7MwXQhNw9tdnBnH6VGsmc) * * [三次握手](https://link.segmentfault.com/?enc=1DdpZG1PmVk6%2FmivuG8oEw%3D%3D.9zR24B8WJDhj80D3yiqQOfpouPMuuy4vMYEGnK0fNKWSUx%2F8Am%2Ft7AYa6p4VjIF2r0n3FFSbIL%2BuG3haMhE6rA%3D%3D) * ![三次握手gif](https://segmentfault.com/img/remote/1460000022145293 "三次握手gif") * 四次挥手 * ![四次挥手gif](https://segmentfault.com/img/remote/1460000022145297 "四次挥手gif") * 挥手过程: * 客户端:FIN\_WAIT 1,停止发送数据给服务端。等待服务端确认 * 服务端:ack ,进入CLOSE\_WAIT(关闭等待),此时如果服务端有数据要发送,客户端还可以接收。 * 客户端收到服务端确认后,进入FIN\_WAIT 2,等待服务器发出连接释放报文段。 * 此时如果服务端没有数据要发送,发送上步骤客户端等待的释放报文段,然后服务端进入LAST\_ACK * 客户端收到服务端的last\_ack后,发出确认,进入TIME\_WAIT,经过2MSL后,客户端关闭 * 服务端收到客户端报文段后,进入CLOSE * 以下这张图明确的指出了syn、ack的交互及对应的转换状态,值得收藏!! * ![image.png](https://segmentfault.com/img/bVbzYzg "image.png") * tcp特点 * 面向连接,先建立(握手),然后释放(挥手确认拜拜) * 只能点对点 * 可靠交付(相对来说),全双工,接收和发送端都设有发送和接收cache * 面向字节流(流:一连串,无结构的的信息流,流入到进程或从进程流出的字节序列,而一个报文段多少字节是根据窗口值和网络拥塞程度动态变化的) * 关于两个重要的队列:半连接队列syns-quene & 全连接队列accept quene,如下图 * ![image.png](https://segmentfault.com/img/bVbzYzH "image.png") * 关于**TIME\_WAIT**: * time\_wait是一种TCP状态,等待2msl可以保证客户端最后一个报文段能够到达服务器,如果未到达,服务器则会超时重传连接释放报文段。使得客户端、服务器都可以正常进入到CLOSE状态。 * 关于'粘包' * 分包:在一个消息体或一帧数据时,通过一定的处理,让接收方能从字节流中识别并截取(还原)出一个个消息体。 * 短连接tcp分包:发送方关闭连接,接收方read 0,就知道消息尾了 * 长连接TCP分包: * 消息长度固定or消息头中加长度字段 * 固定消息边界,比如http:rn * 利用消息本身格式,如`xml,json` * 关于syn flood * 基本思想:恶意像某个服务器端口发送大量SYN包,服务器会分配一个transmission control block,并返回ack。然后服务端转为syn-recv状态。系统保持这个资源 * 常见防攻击方法 * 入门级:系统监视半连开连接和不活动的连接。一视同仁,超过一个阈值后全部rst这些连接。 * 延缓tcp分配:当三次握手后再分配tcb,这样可以有效的减轻对服务器资源消耗(常见方法是使用syn cache,syn cookie) * syn cache * 保存对应的序列号和报文(这里特指半连接)到hash表中,直到收到正确的ack报文再分配tcp * syn cookie * 使用特殊算法生成sequence number,主要涉及到对方无法了解到的己方固定的一些信息(比如己方mss 时间等)。这样如果对方真的发送过来了ack报文,对其ack-1.如果相等,那么就分配这个tcb。没有或者不相等,那就不分配。 * syn proxy防火墙:我简单理解为是一层代理,中间有验证,或者涉及到序列号的修改。具体的不了解。。 2. 特性协议 * 拥塞控制(涉及4个核心算法,全局控制) * 1.慢开始 * 2.拥塞避免 * 3.快速重传 * 触发点:收到了3个相同的ack NO,会在定时器过期之前,重传这个丢失的NO. * 3.1选择重传select-ack * 是对快速重传的优化:需要在tcp '头部选项'中加sack。`/proc/sys/net/ipv4# cat tcp_sack >1`,这个参数默认是打开的。说白了就是接收端在发送ack给发送端的时候告诉他我已经收到了哪批数据,你只需要选择我没有的就好了。 * 3.2选择重传 duplicate select-sack * 主要是针对接收方丢失ack的情况,告知发送端是我的问题。这个段儿内的不要发了。 * 4.快速恢复 * 滑动窗口(点对点控制) * 断线重连 * 停等 * 超时重传 * 超时重传的间隔(Retransmission Timeout)是怎么计算的?答:有个计算公式,涉及【α = 0.125,β = 0.25, μ = 1,∂ = 4】,别问我怎么来的了。 3. udp * 无连接、best effort、面向报文(不合并、不拆分,保留边界) * 无拥塞控制、流量控制、首部开销小(8个字节,而tcp有20个首部) * 支持一对一,一对多,多对一 4. 自定义协议 5. rpc * * * ### 九、大前端 * js * 百度统计的实现 * 基于cookie,引入js脚本及baidu个人账户id,读取当前信息,适当节点发送请求给百度服务器 * 前后端分离 * jwt * IOS/Android/React/vue * Flutter * 弹性布局 * * * ### 十、中间件 1. 中间件 * **RabbitMQ** * 消息可靠性保证,靠分别保证以下四点及基本实现: 1. 保证生产者发送消息mq 正常 * 事务机制:publish 自身控制 * 发送方确认机制【包括发送失败及没有找到对应的exchange|queue并设置mandatory参数(是自动删除还是返回给publish失败)等都会触发mq回调,可以根据具体fail原因来分别处理】: mq 发送ack给publish`publisher-confirm-type: correlated`,根据一个CorrelationData(uuid),并设置一个setConfirmCallback回调函数来判断。 ~~~markdown 2.保证mq->exchange && - 3 exchange->queue正常 -3.5 - exchange 和queue的 durable 参数设置持久化 - 发送的消息默认为持久化 - 有必要的话引入分布式镜像队列,保证exchange queue msg的持久化。 3. 保证 consumer消费消息正常 - 保证消费者消费消息确认[acknowledge-mode: manual] - 注意可能出现重复消费,需要(分布式)幂等处理 4. EXTRA:可以先做一个基础数据落地+mq(publish+consumer)+定时任务做兜底,做到数据的100%处理。 ~~~ * * 核心组件:broker(virtual host(exchange(queue(message) bind)) (connection(channel))) * 消息如何限流? 自带QOS策略,在非自动确认消息`acknowledge-mode: manual`下,如果一定量的消息还未被消费,则不进行新消息的消费,`prefetch`参数 * 延迟消费怎么实现? 支持消息和queue的过期设置+DLE(比如下单后30分钟取消订单,取消动作由消费者对DLE中的队列消费【跳过已经付款状态的即可】) * 死信队列怎么理解? * 特殊的exchange 为Dead Line EXchange * 如果拒绝消费、消费超时、ttl过期、队列达到max length等,可设置自动转发到DLX * 重复消费怎么办 * 顺序消费怎么保证 * 高可用性怎么保证 * exchange 交换机的分发模式 * * fanout:将publish的消息发送到之绑定的所有queue * direct :将publish的消息发送到之绑定的所有queue && routeing key matching * topic :将publish的消息发送到之绑定的所有queue && routeing key fuzzy matching * **kafka** ~~~pgsql - 源自linkedin,用于处理活动流和运营数据。主要是同一消息为多个应用程序消费 - f分布式流处理,持久化,leader负责读写,follower负责数据同步,leader不可用后通过in-sync-replica来选择一个leader - broker(服务器),其中producer发消息到brokder,consumer向brokder读取 - topic:逻辑上消息类别 - partition:物理分区,一个topic可能存在多个partition上,一个分区中设置一个偏移量。具体数据存在partition的segment中,包括数据文件和索引 - consumer group:每个consumer可以指定特定group - 消费端消费确认(commit),支持换group的方式数据回放。默认保存7天,可以 frpm beginining again - [需要自己扩展的](https://www.cnblogs.com/qiaoyihang/p/9229854.html):丢数据(生产方or消费方)、数据重复(at-least-once)的场景 ~~~ * **Redis 队列** ~~~markdown - 支持优先级队列brpop - 延迟队列:sortSet+timestatmp+cron轮询(ZRANGEBYSCORE) - 不支持自动commit offset ~~~ * 近实时解决方案 * cancel->kafka-streamSet->kudu->hue|Davinci * * * ### 十一、php框架 1. php框架 * ci * ci大对象 * hyperf * 注解() * ioc&aop * co协程相关 * yii * laravel * 核心关键字: * Container * ServerProvider * facade &contraces * binding & make &reflection * AppServiceProvider register:服务提供者注册 * IocContainer:(工厂模式的升华:ioc容器) * 控制反转(inversion of control)可以降低计算机代码之间的耦合,其中最常见的方式叫做依赖注入。(Dependence Injection),还有一种方式为依赖查找。可以理解简单的工厂可以实现依赖的转移(简单理解不需要在A类中依次new ABCDE,只需要传递ABCDE这个参数(必要的时候循环就可以了),然后调用工厂的makeObj方法就可以) * 实现方式 * 基于接口:实现特定接口以供外部容器注入所依赖类型的对象。 * 基于set方法:就是一个属性 * 基于构造函数:实现特定参数(比如一个已经new出来的Object)的构造函数 * 管理类依赖 * 执行(依赖注入DI):通过构造函数或者某些情况下通过setter方法将类依赖注入到类中,容器并不需要被告知如何构建对象,因为他会使用php的反射服务自动解析出具体的对象。 * 中间件middare(装饰器模式在) |pipe实现 * 队列中涉及的redis lua脚本 * 另外可以参考spring的相关思想。 * swoole * * * ### 十二 、运维 1. 运维&架构 * 服务器cpu99%如何分析 * mysql 占cpu如何分析 * php占cpu较高如何分析 * sso实现方法 * mysql优化方法 * 如何提高监测数据的准确性 * docker 原理及引用及编排管理 * docker 工具 * rancher * harbor * * * ### 十三、golang * golang vs php * golang的协程和swoole的协程的区别是什么 * swoole的协程和php自带的yield的场景有哪些? * 引用和指针的区别和具体应用场景? * golang 核心特点 * 语言层面实现协程调度器scheduler【[gmp模型](https://link.segmentfault.com/?enc=kqLWmCn1WB1AV1MjvSsTBQ%3D%3D.QBIs2s%2B7yVU68iJKyTyFN0lAik%2BxS8nmftzIGBwRMx2nRZSglW3LqQKh1dKfynO3Lmaq5ZsUVM5oouIqKTWowg%3D%3D)】 * ![GMP模型](https://segmentfault.com/img/remote/1460000022905883 "GMP模型") * goroute和线程之间的数量关系 * 线程最大数默认和cpu和核数相等,goroutine跑在不同的cpu核对应的线程上。 * runtime 用户态协作式调度,channel通信 * 列举golang调度的触发时机 * I/O操作 * select|channel * 锁 * runtime.Gosched() * 显示的停止一个goroutine有哪些方法 * channel(类似发一个signal) * Context(withCancel、withDeadLine) * WaitGroup(Add、Done、Wait) * 调试工具 * go tool trace 【支持web】 * pprof【支持web】 * 微服务实现 go kit | go micro * 语法层面 * interface【多态】 * eface * iface * 具体问题 * golang的内存管理模型 * 基于ThreadCachingMalloc * golang的调度模型 * M:内核级线程(相对用用户态线程),相当于内核线程在go进程中的映射,与操作内核线程1:1对应 * P:更多的是M执行g时候所需要的上下文环境。M和p可以是m:n的关系,p由GOMAXPROCS变量决定 * g:即go语言层面实现的用户态线程,拥有独立的栈、状态和代码片段 * 简单说一下变量逃逸 variable escape * 变量逃逸和具体语法没关系,和编程效率有一定关系 * 一般来说 局部变量会分配到栈上 * 但是有局部变量被引用(return &),或者局部变量很大,大概率分配到heap上。 * 可以通过`go run -gcflags '-m -l' main.go`显示查看 * goloang的chanel实现原理 * golang的context有哪些?分别的应用场景是那哪些? * withcancle * withdeadline * withtimeout * withvalue * goroutie如何管理 * [channel通信的基本过程及原理](https://link.segmentfault.com/?enc=6mLfMCru32L5WinhyRyskA%3D%3D.35rlPlgV90I8Jaw5b5R3sr6bZR0XAWoA9QJDWXTe0sSibg5rihZdUUdTlVGcZJ24dxnTYGQQuLuWMychxpb4ULD6ERJry%2FwR0811BzppSmU%3D) * buf(循环链表)+recx|sendx边界(保证FIFO)+lock(保证读写原子性即发送方要复制&写入到channel,接收方要从channel复制&写入到recGo&channel删除) * 注意发送和接收阻塞的情况以及直接不通过buf(buf为0)发送、接收数据的情况 * 说一下go中的浅拷贝和深拷贝 * * * ### 十四、 Linux 1. linux 常用命令 * netstat 查看tcp udp unixsock网络 * 查看负载cat /proc/loadavg | w | top * df * lstat:strace的时候常常可见他 * top shift+M * top -Hp Pid * top -c Q * free * lsof 查看当前进程id,进程名等占用的文件描述符 * ipstat * strace * grep \[-A ,-B, -C\]'HTTP/1.1" 200' access.log |wc -l * socket和管道(pipe)的区别:socket全双工,pipe半双工\*2 * awk & sed * awk '{print $1}' access.log |sort |uniq |wc -l * * * ### 十五、nginx 1. nginx * 负责均衡实现方式 * fair 响应时间短的优先分配 * least\_conn 分配给连接数较少的后端机器 * hash(:ip\_hash)ip与hash绑定,可以解决session共享问题,url\_hash,更亲和与url缓存类场景 * consistent hash 一致性hash * robin 轮询(:upstream) 按照时间顺序,逐一分配(如果down,能自动剔除)。nginx默认方式 * 权重 (: upstream+weight) 和weight的值成正比,常用于后端每台机器资源侧重点或者硬件性能不一致 * 服务模型 * * * ### 十六、分布式 | 微服务 1. 分布式 * 理论基础 * 权衡 * acid * base * cap * 解决多个节点数据一致性的方案其实就是共识算法 * 分布式协议 * Paxos:Proposer, Acceptor, Learner * ZAB:Follower, Leader, Observer * raft:leader ,follower,candidate * ![clipboard.png](https://segmentfault.com/img/bVbvomM "clipboard.png") * 数据处理 * 数据分片:HASH|REGION * 数据冗余:主从复制|一致性协议(raft,paxos)|去中心化 * 分布式工具【注意区分工具实现的cp还是ap】 * zookeeper:zab(base paxos)protocol, * [etcd](https://link.segmentfault.com/?enc=s0MK8wV80mk9deDTwnGJJA%3D%3D.huVzDnhxVFTFyXxTFoVQ8m5nfwDsvdCV9Cr3oMXggcV3D0RDG7AyR09d8uSSR0SN): * raft protocol(mini PAXOS),k-v database * WAL(Write Ahead Log) * 具体 * 两个mysql的实例,一个存用户钱包,一个存用户积分。怎么实现分布式事务? * 如何安全的使用redis锁?[分布式锁问题](https://link.segmentfault.com/?enc=wsSQzqDf59BgG%2BqVcFTvPw%3D%3D.zXO1sPbUsvc1PIQed%2BXyOt3eC521gP3t3P8vmDJSamfnuTi6xFjWO11bS1brjSjEruHp3tWC7oAxA%2F6jNBqHbQ%3D%3D) * 现有设计参考 * HDFS||GFS(分布式文件系统) * Kafka ||Pulsr(分布式消息队列) * Redis Cluster || Codis(分布式缓存),属于AP模型,单机为CP^\_^ * MySQL (分库分表) * MongoDB || ElasticSearch ReplicaiSet&Sharding * zookeeper|etced|consul 2. 微服务 * 优点: * 相对于单体服务更简单,注重单一功能 * 每个服务可以独立开发打包测试部署,且语言环境无关 * 可以水平、高效扩展 * 缺点:(或者难点) * 运维成本,服务发现、治理、降级、熔断 * 网络消息传输、安全、延迟 * 服务调用排查追踪 * 最佳原则(Domain dirven design) * 高内聚:修改一个功能,只需要改一个服务 * 低耦合:修改了一个地方,不需要改其他的地方(下游消费者不受影响) * 业务内原则: * 新服务用新的微服务,确定无误后保留推进,否则调整 * 老的保留,直到新服务稳定再切换 * 必需的的监控与日志|生产-订阅—消费模型 * 尝试对外不可见的服务先做试点,错误邮件、日志、系统内调用、api内部分成熟接口 * 分层参考 * ~~~pgsql 业务层 【车源、零售、交易、物流、售后等】 应用(子)层【mail、sms、login、call、bi】& 数据架构层【数据模型】 技术架构层【网络拓扑、分布式】 ~~~ * 服务发现及相关工具 * ![image.png](https://segmentfault.com/img/bVbAD14 "image.png") * 服务发现的三种模式 1. 侵入式代理(正向代理),配合服务注册中心(服务端注册,客户端发现) 2. 集中式代理 nginx |f5,由代理解析并分发 3. 独立代理进,单机可复用代理,配合服务注册中心使用ServcieMesh * 考虑问题 * 部署 编排 持续集成|部署 自动化测试 服务注册发现 监控 调用链追踪 (基于OpenTracing协议的JeagerORZipkin)分布式事务 等开源工具应用 * 容器部署 1. 用户交互发起部署请求 2. 调度-打包-拉取-集成 3. 根据配置、选择对应runtime平台 4. 打包成docker iamge(放到镜像仓库harbor) 5. 生成、部署容器 * 考虑服务之间的依赖(迁一发而动全身.雪崩)及调用幂等、系统维护等成本 * 三板斧限流、熔断、降级的意义及如何实现? * 熔断:就是保险丝烧断了,但是这个保险丝可以自恢复。调用方控制,在出现异常及阈值(超时或者retry次数)后取兜底(mock值)或者去别的端口去取的逻辑。(closed->open->half open) * 限流:发起方、接收方都可以限制。发起方层面限制可以避免打垮上游服务,接收方可以避免自己被压垮。可以结合qps和qos来参考。 * 降级:丢卒保車,丢車保帅。人比人该死,货比货该扔。服务也是类似道理 * 与soa区别?soa配合enterprise service bus,相对微服务更重。 * 服务调用统一过程(IDL语言) * * * ### 十七、 DevOps 1. 简单串个图 ![image.png](https://segmentfault.com/img/bVbEYCd "image.png") ### 具体 1. 如何对一个大文件排序(装不进内存的) * 思路: * map reduce * 分割成小文件(临时文件) * 去重 * awk grep end for sort * 输入输出缓冲区 2. 快速排序代码 3. 冒泡排序代码 * 外层循环 0到n-1 //控制比较轮数 n 表示元素的个数 * 内层循环 0到n-i-1 //控制每一轮比较次数 * 两两比较做交换 * 外层循环开始声明 is\_switch flag为false,内层循环有交换为true,外层循环结束时判断无switch break 4. 归并排序代码 5. 二分查找binary search【主要是注意边界和细节】 * 无重复找其一 * 有重复找左第一次|右第一次 6. lru应用场景及实现 7. php * 两个不同长度数组(假设每个数组内有顺序),合并成一个数组并保持顺序 * php与nginx交互流程,php内部执行流程 8. mysql * 对订单表中按每天统计下单量(group by+date\_format+count) * 对订单表中按每天统计下单量,取出最早下单的两个(group\_cat) * `select date\_format(create\_time, '%Y-%m-%d'), group\_concat(id order by create\_time asc), substring\_index(group\_concat(id order by create\_time asc),',',2) from order group by date\_format(create\_time, '%Y-%m-%d') having count(id)>0;` ~~~markdown - 双写怎么保证近实时数据一致性和原子性? - 答案:引入MQ或者Kafka可以保证最终一致性;消费端ack||挂掉的话自己记录offset,again即可(解决原子性问题);引入Canel事件的中间件(解决近实时问题)。 ~~~ ## 面试完成后 1. 自身定位与公司给定技术评级 2. 没拿到offer不要慌,也许就是给HR刷kpi,面试就是照照自己。 3. 复盘,复盘,针对性的调整简历 4. 多说一句,作为一个程序员,数据结构+算法+设计模式才是基础内功。 ## 其他参考 1. [面个php要求怎么要求这么高?](https://link.segmentfault.com/?enc=A0QNoYwZVNm9PMrd7L1UlA%3D%3D.kjJbTSIT9lgUwZIvi7PMrlBL0HSZliDITSXVCsGeR8ICr7bK4Ve0wKDX2aI3QQHgPzZKvom8V2kw254DnMioUA%3D%3D) 2. [\[GO知识图谱](https://link.segmentfault.com/?enc=gxXrMCTQD0DruYoeFU1iCw%3D%3D.meAgY5kbuG36mj7ZoRkkHyuTJQSjjPziRRHeoFjMpUeR50apCIL03sieeigExCo2AKq0iKRz%2Bk1nz8PbLTAuqt8PiBx0m9sNdVIOxPhG1%2FQ%3D)\]