网络
OSI网络七大层
TCP三次握手[连接]
####TCP flags
URG:紧急指针标志
ACK:确认序号标志
PSH:push标志
RST:重置连接标志
SYN:同步序号,用于建立连接过程
FIN:finsh标志,用于释放连接
####TCP建立三次握手
三次握手的流程
第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手;
三次握手里存在的问题
# 为什么需要三次握手才能建立起连接
为了初始化Sequence Number的初始值
# 首次握手的隐患--SYN超时
问题起因:
Server收到Client的SYN,回复SYN-ACK的时候未收到ACK确认
Server不断重试直至超时,Linux默认等待63秒才断开连接
# 针对SYN Flood的防护措施
SYN队列满后,linux下通过tcp_syncookies参数会发SYN Cookie
若为正常连接则Client会回发SYN Cookie,直接建立连接
# 建立连接后,Client出现故障怎么办
保活机制
向对方发送保活探测报文,如果未收到响应则继续发送
尝试次数达到保活探测数仍未收到响应则中断连接
TCP四次挥手[断开]
####TCP四次挥手
TCP四次挥手流程
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态;
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序列号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态;
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态;
第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,
Server进入CLOSED状态,完成四次挥手。
TCP四次挥手里的问题
# 为什么会有TIME_WAIT状态,即Client等待2MSL才关闭
确保有足够的时间让对方收到ACK包
避免新旧连接混淆
# 为什么需要四次握手才能断开连接
因为TCP全双工,发送发和接收方都需要FIN报文和ACK报文
# 服务器出现大量CLOSE_WAIT状态的原因
对方关闭socket连接,我方忙于读或写,没有及时关闭链接
检查代码,特别是释放资源的代码
检查配置,特别是处理请求的线程配置
UDP的特点
TCP和UDP的区别
面向连接 vs 无连接
可靠性(TCP)
有序性(TCP)
速度(UDP)
量级,TCP数据包报头有20个字节(重量级),UDP数据包报头只有8个字节(轻量级)
TCP的滑动窗口
# RTT和RTO
RTT:发动一个数据包到收到对应的ACK,所花费的时间
RTO:重传时间间隔
# TCP使用滑动窗口做流量控制与乱序重排
保证TCP的可靠性
保证TCP的流控特性
HTTP
HTTP的特点
支持客户/服务器模式
简单快速
灵活
无连接
无状态
请求/响应的步骤
客户端连接到web服务器
发送HTTP请求
服务器接受请求并返回HTTP响应
释放TCP连接
客户端浏览器解析HTML内容
# 在浏览器地址栏输入URL,按下回车之后经历的流程
1、DNS解析,找到ip
2、TCP连接
3、发送HTTP请求
4、服务器处理请求返回HTTP报文
5、浏览器解析渲染页面
6、连接结束
HTTP状态码
# 五种可能的取值
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见的状态码
200:正常返回信息
400:客户端请求语法错误,不能被服务器所理解
401:请求为经授权,这个状态码必须和WWW-Authenticate报头域一起使用
403:服务器收到请求,但是拒绝提供服务
404:请求资源不存在,eg,输入了错误的URL
500:服务器发送不可预期的错误
503:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
GET和POST区别
从三个层面来解答:
HTTP报文层面:GET请求消息放在URL栏里(有长度限制),POST放在报文体中
数据库层面:GET符合幂等性和安全性(查询不修改数据库数据),POST不符合(修改数据库的数据)
其他层面:GET可以被缓存、被存储,而POST不行
cookie
是由服务器发给客户端的特殊信息,以文本的形式存放在客户端
客户端再次请求的时候,会把cookie回发
服务器接收到后,会解析cookie生成与客户端相对应的内容
session
服务器端的机制,在服务器上保存的信息
解析客户端请求并操作session id,按需保存状态信息
# session的实现方式
使用cookie来实现,JSESSIONID
使用URL会写来实现
cookie和session的区别
cookie数据存放在客户的浏览器上,session数据放在服务器上
session相对于cookie更安全,别人可以使用cookie欺骗
若考虑减轻服务器负担,应当使用cookie
# 客户端存储
cookie不适合大量数据的存储,因为它们由每个对服务器的请求来传递,这使得cookie速度很慢而且效率也不高
localStorage-没有时间限制的数据存储
sessionStorage-针对一个session的数据存储
JWT Token
由三部分组成:
第一部分是头信息Header
第二部分是载荷Payload
第三部分是签名信息Signature
HTTPS
SSL
SSL(Security Sockets Layer,安全套接层)
为网络通信提供安全及数据完整性的一种安全协议
是操作系统对外的API,SSL3.0后更名为TLS
采用身份验证和数据加密保证网络通信的安全和数据的完整性
加密的方式
对称加密:加密和解密都使用同一个密钥
非对称加密:加密使用的密钥和解密使用的密钥是不相同的(RSA)
哈希算法:将任意长度的信息转换为固定长度的值,算法不可逆(MD5,sha1,sha2,base64(可逆向))
数字签名:证明某个消息或者文件是某人发出/认同的
HTTPS数据传输流程
浏览器将支持的加密算法信息发送给服务器
服务器选择一套浏览器支持的加密算法,以证书的形式回发浏览器
浏览器验证证书合法性,并结合证书公钥加密信息发送给服务器
服务器使用私钥解密信息,验证哈希,加密响应消息回发浏览器
浏览器解密响应消息,并对消息进行验证,之后进行加密交互数据
HTTP和HTTPS
HTTPS需要到CA申请证书,HTTP不需要
HTTPS密文传输,HTTP明文传输
连接方式不同,HTTPS默认使用443端口,HTTP使用80端口
HTTPS=HTTP+加密+认证+完整性保护,较HTTP安全
Socket
socket是对TCP/IP协议的抽象,是操作系统对外开放的接口
socket通信流程
数据库
索引
索引的数据结构
建立二叉查找树树进行二分查找
建立B-Tree结构进行查找
建立B+Tree结构进行查找
建立Hash结构进行查找
B-树
B±树
B+树是B树的变体,其定义基本与B树相同,除了:
1.非叶子节点的子树指针与关键字个数相同
2.非叶子节点的子树指针P[i],指向关键字值[k[i],k[i+1])的子树
3.非叶子节点仅用来索引,数据都保存在叶子节点中
4.所有叶子节点均有一个链指针指向下一个叶子结点
结论
# B+Tree更适合用来做存储索引
B+树的磁盘读写代价更低
B+树的查询效率更稳定
B+树更有利于对数据库的扫描
Hash索引
Hash索引的缺点
仅仅能满足“=”,“IN”,不能使用范围查询
无法被用来避免数据的排序操作
不能利用部分索引查询(例如组合索引,id_num_time,不能用到id和num的部分索引)
不能避免表扫描
遇到大量Hash值相等的情况后性能并不一定就会B—Tree索引高
定位并优化sql
# 根据慢日志定位慢查询sql
SHOW VARIABLES LIKE '%quer%' # 查询慢日志的配置信息
SHOW STATUS LIKE '%slow_queries%' # 查询慢日志的条数
SET GLOBAL slow_query_log = ON; # 开启慢日志
SET GLOBAL long_query_time = 1; # 设置,执行的sql超过1秒就会被记录
# explain执行计划
锁
# MyISAM与InnoDB关于锁方面的区别是什么
MyISAM默认用的是表级锁,不支持行级锁
InnoDB默认用的是行级锁,也支持表级锁,还支持事务
# 表锁(偏读)
1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作
2、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。
# 行锁(偏写)
# 行锁注意点:
无索引行锁升级为表锁
间隙锁危害
间隙锁:锁住了不存在的资源,消耗了性能;
# 对行锁的优化建议:
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的方位
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
引擎
# MyISAM适合的场景
频繁执行全表count语句
对数据进行增删改的频率不高,查询非常频繁
没有事务
# InnoDB适合的场景
数据增删改查都相当频繁
可靠性要求比较高,要求支持事务
# MyISAM
查询快,但增删性能不行
# 所有表必须使用Innodb存储引擎(5.6以后的默认引擎)
支持事务,行级锁,更好的恢复性,高并发下性能更好
数据库锁的分类
按锁的粒度划分,可分为表级锁、行级锁、页级锁
按锁级别划分,可分为共享锁(读锁)、排它锁(写锁)
按加锁方式划分,可分为自动锁、显式锁
按操作划分,可分为DML锁(数据)、DDL锁(操作库和表结构)
按使用方式划分,可分为乐观锁(不是真正的锁,使用数据的版本号或者时间戳)、悲观锁(使用数据库锁的机制,排它锁的机制)
悲观锁和乐观锁
# 悲观锁(写多读少),执行的时候,只有一个线程可以操作,其他线程进行等待
顾名思义,悲观,认为来操作数据库,都会修改里面的数据,只有拿到锁的才能操作,等到它释放了,下一个才能操作
机制:使用数据库锁的机制,排它锁的机制(写锁)
# 乐观锁(写少读多),提高吞吐量
顾名思义和乐观,认为来操作数据库的,都不会修改里面的数据,当修改数据时,它就会把当前记录表的版本号与数据库表中的版本号进行比较,如果一致,就允许修改,不一致就不允许,重新更新一下即可
机制:不是真正的锁,使用数据的版本号或者时间戳来进行比较,当前查到的版本号与当前数据库数据版本号比较
乐观锁适用于写比较少的情况下(多读场景)
数据库的事务
原子性(Atomic):一个事务不可再分割,要么都执行要么都不执行
一致性(Consistency):一个事务执行会是数据从一个一致状态切换到另外一个一致状态
隔离线(Isolation):一个事务的执行不受其他事务的干扰
持久性(Durability):一个事务一旦提交,则会永久的改变数据库的数据
事务隔离级别
# 事务并发访问引起的问题以及如何避免
更新丢失——mysql所有事务隔离级别在数据库层面上均可避免
# 脏读
所谓脏读是指一个事务中访问到了另外一个事务未提交的数据
解决方法:脏读——READ-COMMITTED事务隔离级别以上可避免
# 不可重复读
所谓不可重复读是指在一个事务内根据同一个条件对行记录进行多次查询,但是搜出来的结果却不一致。发生不可重复读的原因是在多次搜索期间查询条件覆盖的数据被其他事务修改了;
解决方法:不可重复读——REPEATABLE-READ事务隔离级别以上可避免
# 幻读
所谓幻读是指同一个事务内多次查询返回的结果集不一样(比如增加了或者减少了行记录)。比如同一个事务A内第一次查询时候有n条记录,但是第二次同等条件下查询却又n+1条记录,这就好像产生了幻觉,为啥两次结果不一样那。其实和不可重复读一样,发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据。不同在于不可重复读是同一个记录的数据内容被修改了,幻读是数据行记录变多了或者少了:
解决方法:幻读——SERIALIZABLE事务隔离级别可避免
锁模块常见问题
MyISAM与InnoDB关于锁方面的区别是什么
数据库事务的四大特性
事务隔离级别以及各级别下的并发访问问题
InnoDB可重复读隔离级别下如何避免幻读
RC、RR级别下的InonoDB的非阻塞读如何实现(了解)
三范式
# 第一范式
没有重复的列
# 第二范式
属性完全依赖于主键【消除部分子函数依赖】,唯一性
# 第三范式
满足第三范式,必须先满足第二范式
属性不依赖于其他非主属性【消除传递依赖】,消除冗余
第一范式就是无重复的列
第二范式就是非主属性依赖于主关键字
第三范式就是属性不依赖于其它非主属性。(消除冗余)
# 优点
消除了数据的冗余、更新异常、插入异常、删除异常
面试中的介绍
站在码农的角度介绍项目,专注技术指标以及解决思路
自信,脉路要清晰:项目用途->自己的角色->如何解决难题
项目若找不到难点,则谈谈改进,前提是熟悉相关涉及的知识点
事前用图形将你的项目勾画清楚
缓存中间件
Memcache
Memcache:代码层次类似Hash
支持简单数据类型
不支持数据持久化存储
不支持主从
不支持分片
Redis
Redis:
数据类型丰富(string,list,hash,set,zset、Bitmap、HyperLogLog(统计总数,0.81%错误率)、GEO(地理信息))
支持数据磁盘持久化存储(RDB,AOF)
支持主从
支持分片
为什么redis能这么快?
10万+QPS(QPS即query per second,每秒内查询次数)
完全基于内存,绝大部分请求时纯粹的内存操作,执行效率高
数据结构简单,对数据操作也简单
采用单线程,单线程也能处理高并发请求,像多核也可启动多实例
使用多路I/O复用模型,非阻塞IO
多路I/O复用模型
# Redis采用的I/O多路复用函数:epoll/kqueue/evport/select
因地制宜
优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现
以时间复杂度为O(n)的select作为保底
基于react设计模式监听I/O事件
同步阻塞,阻塞的是单个线程那一块,不是读写那一块
# 多路IO复用
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力。
在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,当前线程就从阻塞状态中被唤醒,然后去轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
redis的五大存储类型
String:最基本的数据类型,二进制安全
Hash:String元素组成的字典,适合用于存储对象
List:列表,按照String元素插入顺序排序
Set:String元素组成的无序集合,通过哈希表实现,不允许重复
ZSet:通过分数来为集合中的成员进行从小到大的排序
HyperLogLog:用于统计,错误率%0.81
Geo:存储地理位置信息
BitMap(位图)
Redis的数据类型
底层数据类型基础:
1.简单动态字符串,sds
2.链表,list
3.字典。dict,hash
4.跳跃表,zskiplist
5.整数集合,intset
6.压缩列表,ziplist
7.对象
redis里的问题
# 从海量key里查询出某一个固定前缀的key
如果使用keys k1*,这个是把所有满足都查询出来,会出现卡顿,不好;
如果使用scan cursor [MATCH pattern] [COUNT count],就会好很多:
基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历
不保证每次只需都返回某个给定数据的元素,支持模糊查询
一次返回的数据不可控,只能是大概率复合count参数
scan 0 match k1* count 10 # 0表示的是位置,从哪个位置开始匹配
scan 10090 match k1* count 10
# 如何通过redis实现分布式锁
set key value [EX secondes] [PX millisecondes] [NX][XX]
EX secondes:设置键的过期时间为second秒
PX millisecondes:设置键的过期时间为milliseconde毫秒
NX:只在键不存在时,才对键进行设置操作
XX:只在键已经存在时,才对键进行设置操作
SET操作成功完成时,返回OK,否则返回nil
# 集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象
解决方案:在设置key的过期时间的时候,给每个key加上随机值
# 如何使用Redis做异步队列
1.使用List作为队列,RPUSH生产消息,LPOP消费消息
缺点:没有等待队列里有值就直接消费
弥补:可以通过在应用层引入Sleep机制取调用LPOP重试
2.BLPOP key [key ...] timeout:阻塞知道队列有消息或者超时
缺点:只能提供一下消费者消费
3.publish/subscribe:主题订阅者模式
发送者(publish)发送消息,订阅者(subscribe)接收消息
订阅者可以订阅任意数量的频道即(topic)
例如:
客户端1:subscribe mytopic01 # 客户端1订阅了mytopic01频道
客户端2:publish mytopic01 "hello" # 客户端2在mytopic01频道发送消息,此时客户端1就收到消息了
缺点:消息的发布是无状态的,无法保证可达,例如,客户端1此时断了,此时客户端2发送消息,客户端1就收不到消息
持久化存储
# RDB
检测key的变化,在一定时间内,key变化的次数达到了,就把数据存储到文件中
缺点:
1.内存数据的全量同步,数据量大会由于I/O而严重影响性能
2.可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据
# AOF
根据定义的时间,来记录操作的执行语句,存到文件中
例如:记录每一分钟内的所有执行语句,存到文件中
# RDB(快照)持久化:保存某个时间点的全量数据快照
SAVE:阻塞redis的服务进程,直到RDB文件被创建完毕
BGSAVE:Fork出一个子进程来创建RDB文件,不阻塞服务器进程
# 自动化触发RDB持久化的方式
根据redis.conf配置里的SAVE m n 定时触发(用的是BGSAVE)
主从复制时,主节点自动触发
执行Debug Reload
执行Shutdown且没有开启AOF持久化
# BGSAVE原理
系统调用fork():创建子进程,实现了Copy-on-Write
Copy-on-Write:
如果有多个调用者同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变
# AOF(Append-Only-File)持久化:保存写状态
记录下除了查询以外的所有变更数据库状态的指令
以append的形式追加保存到AOF文件中(增量),AOF重写,缩小文件的大小
# 日志重写解决AOF文件大小不断增大的问题,原理如下:
调用fork(),创建一个子进程
子进程把新的AOF写到一个临时文件里,不依赖原来的AOF文件
主进程持续将新的变动同时写到内存和原来的AOF里
主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动
使用新的AOF文件替换掉旧的AOF文件
# RDB和AOF的优缺点
RDB优点:全量数据快照,文件小,恢复快
RDB缺点:无法保存最近一次快照之后的数据
AOF优点:可读性高,适合保存增量数据,数据不易丢失
AOF缺点:文件体积大,恢复时间长
# RDB-AOF混合持久化方式
BGSAVE做镜像全量持久化,AOF做增量持久化
Pipeline
# 使用Pipeline的好处
Pipeline和Linux的管道类似
Redis基于请求/响应模型,单个请求处理需要一一应答
Pipeline批量执行指令,节省多次IO往返的时间,建议批量查询的时候这样操作,修改数据不建议
有顺序依赖的指令建议分批发送
redis的同步机制【主从同步】
# 全同步过程
Salve发送sync(同步)命令到Master
Master启动一个后台进程,将Redis中的数据快照保存到文件中
Master将保存数据快照期间接收到的写命令缓存起来
Master完成写文件操作后,将该文件发送给Salve
使用新的AOF文件替换掉旧的AOF文件
Master将这期间收集的增量写命令发送给Salve端
# 增量同步过程
Master接收到用户的操作指令,判断是否需要传播到Slave
将操作记录到追加AOF文件
将操作传播到其他Slave:1、对齐主从库;2、往响应缓存写入指令
将缓存中的数据发送给Slave
Redis Sentinel(哨兵)
redis Sentinel的作用
# 解决主从同步Master宕机后的主从切换问题:
监控:检查主从服务器是否允许正常
提醒:通过API向管理员或者其他应用程序发送故障通知
自动故障迁移:主从切换
流言协议Gossip
# 在杂乱无章中寻求一致
每个节点都随机地与对方通信,最终所有节点的状态达成一致
种子节点定期随机向其他节点发送节点列表以及需要传播的消息
不保证消息一定会传递给所有节点,但是最终会趋于一致
Redis集群原理
# 如何从海量数据里快速找到所需?
分片:按照某种规则去划分数据,分散存储在多个节点上
常规的按照哈希划分无法实现节点的动态增减
一致性哈希算法:对2^32取模,将哈希值空间组织成虚拟的圆环
将数据key使用相同的函数Hash计算出哈希值
Hash还的数据倾斜问题
引入虚拟节点解决数据倾斜的问题
Linux
Linux的体系结构
体系结构主要分为用户态(用户上层活动)和内核态
内核:本质是一段管理计算机硬件设备的程序
系统调用:内核的访问接口,是一种能再简化的操作
公用函数库:系统调用的组合拳
Linux命令
find / -iname nginx.conf # 不区分大小写
gerp 管道操作符 | awk sed