你需要一个系统:操作系统&计算机网络&数据库

计算机网络

快速入门:网络编程懒人入门系列(转载自ruanyifeng.com)

网络编程懒人入门(一):快速理解网络通信协议(上篇)
个人总结:
实体层:传输0和1。
链路层:对01数据进行分组,称为“帧(Frame)”,范围在内网。头主要包含发送者和接受者的mac地址,数据类型等。怎么知道别人的mac地址:ARP。
网络层:

  • 不可能去保存世界上所有机器的mac地址,分子网;
  • 如果是同一个子网络,就采用广播方式发送,否则就采用"路由"方式发送。
  • IP头:长度、发送者和接受者的ip地址。
    传输层:TCP、UDP,指定端口。
    应用层:规定数据格式

网络编程懒人入门(二):快速理解网络通信协议(下篇)

以太网数据包的大小是固定的,最初MTU=1518,后面增加到1522,
其中1522=以太网头(22)+以太网数据包(1500),
而IP头最少为20,所以IP数据包最大为1480…
网络编程懒人入门(三):快速理解TCP协议一篇就够

网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门

网络编程懒人入门(七):深入浅出,全面理解HTTP协议


传输层

TCP和UDP

运输层主要使用以下两种协议

  • 传输控制协议 TCP(Transmisson Control Protocol)–提供面向连接的,可靠的数据传输服务。
  • 用户数据协议 UDP(User Datagram Protocol)–提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
  • TCP和UDP区别(面试常考)
  • 基于连接vs无连接:TCP是面向连接的协议,而UDP是无连接的协议。(所谓的不连接就是不需要进行三次握手,直接发送数据,但会导致数据接收顺序和数据发送顺序不一定一致)
  • 可靠性vs不可靠: TCP可靠,而UDP是不可靠的,数据报在运输途中可能会丢失。
  • 速度:TCP速度比较慢,而UDP速度比较快。
  • 对系统资源的要求:TCP较多,UDP少
  • 流模式(TCP)与数据报模式(UDP)
  • UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 直播,实时视频会议等);
  • TCP是一对一的,而 UDP 支持一对一、一对多、多对一和多对多的交互通信;
  • 使用场景:对实时性要求高场合下使用UDP;比如视频。
    对可靠性要求高的情况下使用TCP。

    udp用于直播时的上层协议:RTCP(Real-time Transport Control Protocol)实时传输控制协议。

    注:TCP为什么可靠

    因为其使用三次握手建立连接四次挥手关闭连接;并且有阻塞控制功能。

    TCP的三次握手、四次挥手(面试常考)

    TCP协议使用三次握手建立连接四次挥手关闭连接

    TCP三次握手 及状态变化

    个人理解:seq为序列号,可以理解为A和B自己的签名,而ack为确认。
    发送时,对方的信息使用ack,自己的信息使用seq。
    第一次握手:建立连接时,客户端A发送SYN包[SYN=1,ACK=0,seq=x](随机选择一个初始序号seq=x)到服务器B,等待服务器B确认,并进入SYN_SENT状态
    第二次握手:服务器B收到SYN包,必须确认客户A的SYN,同时自己也发送一个SYN包,即SYN+ACK包[SYN=1,ACK=1,seq=y,ack=x+1],并进入SYN_RECV状态
    第三次握手:客户端A收到服务器B的ACK包,向服务器B发送确认包ACK[ACK=1,seq=x+1,ack=y+1],A和B进入ESTABLISHED状态


    抓包数据:

    为什么需要三次握手而不是两次呢?(从超时方面去着手思考
    主要是为了防止已经失效的请求突然又到达了B这一现象。
    假设A的请求已经失效,B向A发送确认报文建立连接,但是A并不会理会B,导致B一直在等待,造成资源的浪费。

    四次挥手

    ack和seq与上同理。
    (1)A发现自己没有数据需要传输,此时向B发送[FIN = 1,seq=u](u等于前面已经发送过的数据的最后一个字节加一)数据。
    (2)B会向A发送ACK确认数据[ACK=1,seq=v,ack=u+1 ],此时A已经不会往B发送消息了(但是B还没关闭)
    (3)当B的应用进程发现自己也没有数据需要传送,此时向A发送[FIN=1,ACK=1,seq=w,ack=u+1]数据
    (4)A收到后,向B发送ACK确认数据[ACK =1,seq=u+1,ack=w+1],进行等待状态,等待2MSL(Maximum Segment Lifetime,报文最大生存时间)之后正常关闭连接

    为什么是四次挥手而不是三次?(三次指的是中间两次合并)

    个人理解:三次挥手的话,就是将中间两次合并,一次请求同时具备确认(ACK)和中断链接(FIN)两个动作。这个时候实际上B->A的数据可能还没有传输完。如果是等B->A的数据传输完再发ACK+FIN的话,那么第一次挥手到第二次的耗时并不能确定,可能会出现A因为等待时间太久进行超时重传等问题。

    TCP四次挥手客户端关闭链接为什么要等待2倍MSL?

    1) 如果Client直接CLOSED了,假设此时Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段,Server就无法按照正常的步骤进入CLOSED状态。
    2)2MSL时间,可以确保当前连接所产生的所有报文段都从网络中消失。这样即便立马开启新的连接,也不会出现上一次连接所残留的报文段。

    TCP如何保证可靠传输

    滑动窗口算法和拥塞窗口

    滑动窗口算法可看视频:https://www.bilibili.com/video/av47486689?p=119
    注:发送方的滑动窗口大小是由接收方来决定的,详见:https://blog.csdn.net/ligupeng7929/article/details/79597423

    拥塞窗口:https://www.zhihu.com/question/264518499
    两者的区别:个人理解,刚开始只有滑动窗口,它是flow controll,也就是由你的消化速度来控制我的发送速度。但是我和你中间道路的具体情况是未知的,有可能会有堵车,有可能会出车祸,等等,因此,还需要在滑动窗口的基础上加上一个拥塞窗口,发送速度不单纯由发送方来完全控制,而是两个窗口结合来进行控制

    慢开始和拥塞避免

    拥塞控制就是防止过多的数据注入到网络中。
    维持一个拥塞窗口cwnd和慢开始门限ssthresh
    -当cwnd<ssthresh时,使用慢开始算法。
    -当cwnd>ssthresh时,改用拥塞避免算法。
    -当cwnd=ssthresh时,慢开始与拥塞避免算法任意。
    -当出现阻塞,慢开始门限设置为出现拥塞时的发送窗口大小的一半,然后把拥塞窗口设置为1,执行慢开始算法。

    快重传和快恢复

    快重传:


    快恢复:
    当发送方连续收到三个重复确认时,把ssthresh门限减半,然后将cwnd设置为ssthresh的大小,执行拥塞避免算法。如下图:

    TCP拆包、粘包

    原文链接:https://blog.csdn.net/qq_37102984/article/details/124343718

    粘包/拆包 问题产生原因:(拆包两个、粘包两个)

    • 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
    • 待发送数据大于MSS(TCP报文长度 - TCP头部长度 > MSS最大报文长度),TCP在传输前将根据MSS大小进行拆包分段发送。
    • 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据包合并为一次发送,将发生粘包(Nagle算法优化,避免tcp报文头重脚轻的情况发生)
    • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

    解决 粘包/拆包 问题:解决问题关键在于:如何给每个数据包添加明确的边界信息用于区分不同数据包:

    • 消息分为tcp首部和tcp消息体,tcp首部中应保存数据包的长度(TCP的首部原本是没有表示数据长度的字段,因为可由IP层计算出:TCP数据包长度 = IP首部的数据包长度 - IP首部长度(20字节 )- TCP包首部长度(20字节 ))。这样接收端在接收到数据后,通过读取包首部的长度字段,就知道每个数据包的实际长度了。
    • 发送端在每个包的末尾使用固定的分隔符(如 \r\n),这样接收端通过这个边界就可以将不同的数据包拆分开(如 FTP协议)。
    • 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。不推荐这种方式,尤其是在高并发大流量的业务场景下,会消耗不必要的资源…

    应用层

    HTTP返回码

    200:请求被正常处理
    3xx:表示客户端被重定向到其他资源
    4xx:发出的请求可能出错,妨碍了服务器的处理。(如果遇到了400,则一定是请求的参数出了问题,一定要仔细比对每一个参数!血与泪的教训!
    400 (错误请求) 服务器不理解请求的语法。(404是个例外)
    5xx:服务器端发生内部错误
    其中301是永久重定向(抓取新的内容的同时也将旧的网址替换为了重定向之后的网址),
    302是临时重定向(抓取新的内容,但会保留旧的地址)

    HTTP 长连接、短连接(实际就是TCP的长连接、短链接)

    在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
    而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:Connection:keep-alive
    Keep-Alive不会永久保持连接,它有一个保持时间,由服务器端设定。

    HTTPS

    HTTPS是在HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

    我们要知道,https的安全,是需要分为两个方面来看待。一个是认证的安全,另一个是传输数据的安全。

    所谓认证的安全,解决的是“我是谁”这个问题。我们以百度作为例子,目前通用的认证方案是网站使用私钥签名,然后用户使用公钥来认证。但是这存在这两个问题,第一点是用户连我访问的是不是百度都不确定,更别说从百度那拿到公钥,第二点是用户不可能保存所有公司的公钥,因此需要一个权威的第三方,叫做CA认证中心。
    百度等合法的网站可以将自己的公钥以及公司信息发给CA认证中心,CA中心会去核实这个公司是否存在等,然后将百度的公钥、企业信息等封装成一个证书,并使用CA中心自己的私钥进行签字。而CA中心的公钥是人人都有的,这样百度可以将证书发送给访问的客户端,客户端借助CA的公钥来实现认证,也就是说,CA中心是一个权威中心,只要他能通过,就认为是没问题的,这样就能解决“我是谁”这一问题。

    接着是传输数据的安全,https的实现方法是,让客户端和服务端最终能拥有一个对称key,原理是通过三次通信,这个有点像三次握手,客户端和服务端的三次通信都会携带一个随机数,最后双方会采用第一次通信时定好的算法,将三个随机数变成一个对称key。

    详情请见:https://blog.csdn.net/u011779724/article/details/80776776

    聊到非对称加密。就得聊聊非对称加密的原理:
    非对称加密算法的数学解释

    HTTP请求和响应的结构

    HTTP请求报文:
    一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成

  • 请求行:请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。
    例如,GET /index.html HTTP/1.1。
  • 请求头部:由key:value对组成,每行一对。(面试常考)
    例如User-Agent:产生请求的浏览器类型。
    Accept:浏览器可接受的MIME类型。
    Connection:表示是否需要长连接。
    Host:请求的主机名。
  • 空行、请求数据(略)
  • HTTP响应报文:
    HTTP响应也由三个部分组成,分别是:状态行、头、空行和正文。
    可以看出,与请求报文的区别在于第一行变成了状态行。
    状态行格式:HTTP协议的版本,响应状态代码,描述
    例如:HTTP/1.1 200 OK

    附:使用chrome浏览器获取请求报文和响应报文的方法:HTTP入门(二):用Chrome开发者工具查看HTTP请求与响应

    HTTP请求方式:

    主要就是get、post和put

    • get:获取资源
    • post、put:提交数据
    • DELETE:删除
    • TRACE:回显请求

    get和post的区别

    (HTTP规定,当执行GET请求的时候,要给汽车贴上GET的标签(设置method为GET),而且要求把传送的数据放在车顶上(url中)以方便记录。如果是POST请求,就要在车上贴上POST的标签,并把货物放在车厢里。实际上,HTTP只是个行为准则。也就是说,由于TCP才是GET和POST怎么实现的基本,其实get往报文中传输也可以,post往url中传输也可以)

  • 传送方式:get通过地址栏传输,post通过报文传输。
  • 传送长度:get参数有长度限制(受限于url长度),而post无限制
  • 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
    而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
  • put 和 post的区别(了解)

    幂等:幂等是数学的一个用语,对于单个输入或者无输入的运算方法,如果每次都是同样的结果,则称其是幂等的。
    put是幂等的,例如一个账户对应一个账户二维码,因此适合使用put。
    post是非幂等,支付二维码每次打开都应该不同,因此适合使用post。

    cookie&session

    主要看这篇:你真的了解 Cookie 和 Session 吗

    个人总结
    1. 介绍cookie和session

    因为http协议是一个无状态的协议,为了记录稳定的状态信息,服务端就需要将信息以session的形式存储下来(生命周期为一个会话),并将这部分状态信息发送给客户端,客户端会将其保存到本地,称之为cookie。最常见的用途就是用户登陆场景。

    2. cookie和session的区别

    详见上文的 “第二层楼”。

    3. sessionId

    客户端请求服务端时,服务端会生成一个sessionId,然后会将sessionId返回给客户端,客户端会将sessionId放到cookie里面,下次请求时会将cookie信息带上。


    https://blog.csdn.net/chunqiuwei/article/details/23461995

    4. cookie被禁止掉该怎么办

    两种方式:

  • 将cookie中的sessionId从cookie转移到请求中。
  • 使用token来代替sessionId。
  • 5. 分布式 Session 问题
  • 共享session,例如spring session
  • nginx ip hash策略,通过nginx来确保同个客户端的请求一定会发到同一个服务端上
  • 6. 跨域问题

    个人认为使用spring session就能解决(问一下同事?)

    sessionId和token的区别

    这一篇讲的还可以,关注到了最核心的区别:https://segmentfault.com/a/1190000015881055

    HTTP1.0和2.0的区别

  • HTTP2.0的基本单位为二进制帧(HTTP1.0利用文本与服务器交互)
  • HTTP2.0采用多路复用,允许单一的连接发起多重的请求-响应消息。

  • HTTP2.0会将以前以文本形式传输的消息头进行压缩。

  • 浏览器中输入URL到页面返回的全过程(面试常考)

    第一步、浏览器中输入域名www.baidu.com
    第二步、域名解析:把域名解析成对应的IP
    第三步、浏览器与目标服务器建立TCP连接
    第四步、向目标服务器发送请求,(如果目标链接是https的话,需要在tcp/ip上封装一层ssl,获得证书,将公钥加密密钥发送给服务端,最后获得密钥加密的响应消息(详见下))
    第五步、服务器给出响应(有可能会重定向,此时浏览器会使用重定向的域名从头开始)
    第六步、TCP释放链接
    第七步、浏览器收到响应数据,对数据进行渲染然后显示到页面上

    其中域名解析涉及到缓存


    1、请求一旦发起,浏览器首先要做的事情就是解析这个域名,一般来说,浏览器会首先查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址。
    2、如果在本地的 hosts 文件没有能够找到对应的 ip 地址,浏览器会发出一个DNS请求到本地DNS服务器 。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。
    3、DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询。
    4、根DNS服务器没有记录具体的域名和IP地址的对应关系,而是给出本地DNS服务器对应的域服务器的地址,你可以到域服务器上去继续查询。
    5、本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是baidu.com域服务器。baidu.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
    6、最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。


    数据链路层

    ARP:第4章 ARP:地址解析协议
    ARP欺骗:视频https://www.bilibili.com/video/av47486689?p=76

    网络层

    IP首部:

    IP报文格式详解:https://blog.csdn.net/mary19920410/article/details/59035804
    关于首部校验和部分,可看视频https://www.bilibili.com/video/av47486689?p=81

    IP分片:

    【计算机网络】:IP分片详解及例题
    需要的总片数=(L-20)/1480

    IP选路(路由)(重点):

    知识点1-静态路由:https://blog.51cto.com/14464303/2427189
    或看视频:https://www.bilibili.com/video/av47486689?p=83
    知识点2-ICMP:https://blog.csdn.net/baidu_37964071/article/details/80514340
    知识点3-动态路由协议:

  • RIP协议:https://www.bilibili.com/video/av47486689?p=88
  • OSPF协议:https://www.bilibili.com/video/av47486689?p=92
  • NAT和PAT

    NAT用于访问内网,并且作为端口映射。
    详见视频:https://www.bilibili.com/video/av47486689?p=98
    以vmware为例进行NAT的示例:https://www.bilibili.com/video/av47486689?p=99

    NAT和路由模式的区别

    路由器NAT模式和路由模式的区别


    操作系统 (每一点都是常考)

    进程与线程

    进程和线程的区别
  • 线程是进程的一部分,一个进程可以有多个线程,同一进程的所有线程共享该进程的所有资源(但是每个线程也拥有自己的空间:栈)。
  • 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
  • 同一进程下的线程之间能够直接交换数据,但进程之间不行,必须使用进程通信的方式来交换数据。
  • 什么使用时候线程,什么时候使用进程
  • 需要频繁创建和销毁时使用线程,因为进程开销很大
  • 需要稳定和安全 ->进程; 需要速度 ->线程;
  • 多机分布式系统 ->使用多进程。
  • 想对进程和线程有更深入了解的,请见博客:一篇让你明白进程和线程之间的区别与联系:https://www.cnblogs.com/coder-programming/p/10595804.html

    协程
    入门:

    什么是协程?https://blog.csdn.net/zheng199172/article/details/88800275

    协程详解:

    什么是协程?https://zhuanlan.zhihu.com/p/172471249

    总结:

    多线程的瓶颈:在处理并发场景是,会使用多线程,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。

    协程的优势:因为协程是由代码来进行控制的,所以协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。这就能很好地解决二的问题。

    java为什么不使用协程

    这一篇讲的很好:为什么 Java 坚持多线程不选择协程? - 大宽宽的回答 - 知乎

    针对协程的以下优势一一作出解答:

    • 节省资源,轻量,具体就是:
      – 节省内存,每个线程需要分配一段栈内存,以及内核里的一些资源
      – 节省分配线程的开销(创建和销毁线程要各做一次syscall)
      – 节省大量线程切换带来的开销
    • 节省大量线程切换带来的开销与NIO配合实现非阻塞的编程,提高系统的吞吐
    • 使用起来更加舒服顺畅(async+await,跑起来是异步的,但写起来感觉上是同步的)
    上下文切换(知道概念就行)

    对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。

    为什么进程上下文切换比线程上下文切换代价高?

    因为每一个进程拥有自己独立的内存空间,而线程共享进程的内存空间。
    所以,线程上下文切换时,仍然使用的是相同的内存空间(因为都是属于自己的进程),而对于进程,切换时需要更换内存空间

    进程通信的方式

    首先不着急一上去就说通信方式,我们可以说一说为什么会有进程间通信???
    因为我们知道进程是一个结构体,进程与进程之间是相互独立的,而有时候我们需要在两个进程之间实现数据传输、进程控制等功能(debug进程可以控制其他进程的执行),因此需要让内核给我们提供一个公共资源让两进程都能访问从而实现通信。

  • 管道:管道的实质是一个内核缓冲区
    1.1 匿名管道:建立一个匿名管道时,它会创建两个文件描述符:fd[0]为读文件描述符,fd[1]为写文件描述符。匿名管道只能提供给父子进程访问。


    1.2 命名管道:所有进程都可以访问。


    管道的特点:1、只能承载字节流 2、半双工通信 3、生命周期随进程。

  • 消息队列:在内核中创建一个队列,进程A可以向队列中写数据(写满则不能写了,因为消息队列是固定的),队列中有数据了进程B就可以开始读数据了,读完了数据就不能读了(这也就能说明消息队列面向数据报)
    消息队列的特点:1、适用于任意进程 2、面向数据报 3、全双工通信(只要进程有读写权限就可以双向通信)4、生命周期随内核
    管道和消息队列的区别:
    1、管道发送的是字节流,而消息队列可以发送指定类型、有格式的数据块
    2、生命周期不同,全双工半双工

  • 共享内存:一个进程将同一块物理内存一块映射到不同的进程的虚拟地址空间中,实现不同进程间对同一资源的共享。我们知道内存有随机访问的优势,所以共享内存就成为了进程间通信最快的方式。
    共享内存的特点:1、适用于任意进程 2、全双工通信3、生命周期随内核
    消息队列和共享内存的区别:
    消息队列是:进程A从内存中读取数据 --> 把数据放到内核的队列中 --> 进程B从队列中获得数据 --> 操作
    共享内存是:B直接从内存中读取数据。(所以B最快)

  • 信号量(Semaphore):信号量准确的来说就没有通信,它用于控制多个线程对共享资源的访问,信号量的特点就是用于互斥!!!
    其实我个人觉得信号也应该属于进程间通信的一种方式,为什么呢?因为信号量用于互斥,也就是用于进程控制,而进程控制也是进程间通信的目的之一。

  • 此处防止有坑:面试官可能会问:那全局变量进程也都能看得到,是不是也能实现通信呢?这就得说到我们的虚拟地址向物理地址映射了,全局变量在映射的时候,物理地址分布不同,那么怎么能保证两个进程看到的是同一块空间,那就无法通信了。

    原文:https://blog.csdn.net/cx2479750196/article/details/81150955

    补充:跨机器的进程通信有

  • socket通信
  • RPC:thrift,dubbo等。
  • 孤儿进程、僵尸进程(了解)

    https://www.cnblogs.com/Anker/p/3271773.html

    用户态、内核态

    大致的意思就是,最早的时候,是没有分用户态和内核态的,这会造成普通应用能直接访问硬件(比如直接操作内存、网卡),这就容易直接把硬件搞损,因此为了系统安全而言,需要分为用户态和内核态。
    比较危险的硬件指令(比如格式化硬盘,直接操作内存、网卡等),这些指令只有内核态才能够去执行,而用户态想要执行这些指令,就必须得找OS这个大佬来获得批准才行。

    用户态和内核态:https://www.iteye.com/blog/wely-2332327

    linux零拷贝(零拷贝)技术

    Linux 中的零拷贝技术,第 1 部分:https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/
    Linux 中的零拷贝技术,第 2 部分:https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html
    个人理解:
    传统io:

  • 如果没有缓存,则先从磁盘中读出来放到操作系统内核缓冲区中:(DMA传输)
  • 从内核缓冲区拷贝到应用程序缓冲区(read()函数),然后对数据进行处理。
  • 从应用拷贝到与网络相关的内核缓冲区(socket缓冲区)中,( write() 函数)
  • 将数据发送到网卡上(DMA传输)

  • 零拷贝:

  • 直接I/O,应用程序可以直接访问存储硬件以及网络硬件接口。
  • mmap():DMA到页缓存的同时,应用程序也共享这个缓存,这样就少了一次read造成的损耗。
    缺点:当对文件进行了内存映射,然后调用 write() 系统调用,如果此时其他的进程截断了这个文件,那么 write() 系统调用将会被总线错误信号 SIGBUS 中断。
    解决方案:使用文件租借锁。
  • 带有 DMA 收集拷贝功能的 sendfile():前提是应用程序不需要对数据做任何处理,DMA到页缓存后,然后将带有文件位置和长度信息(数据可以是不连续的)的描述符发给socket缓冲区,接着通过 DMA 收集拷贝功能将所有的数据发给网卡。
  • 死锁
  • 死锁产生的原因:资源竞争、进程推进顺序不当
  • 死锁产生的四个必要条件:
    (1)互斥条件:一个资源一次只能被一个进程使用
    (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放
    (3)不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺
    (4)循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系
  • 处理死锁的方法:

  • 预防死锁(破坏必要条件234)(了解):
    (1)资源一次性分配:(破坏请求和保持条件)
    (2)可剥夺资源:(破坏不可剥夺条件)
    (3)资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
  • 避免死锁(银行家算法)(具体算法暂时不要求记下来,只需记住一句话即可):
    一句话:
    当一个进程申请使用资源的时候,银行家算法通过先试探分配给该进程资源,然后通过安全性算法判断分配后的系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待。


    举例:安全性算法:


  • 解除死锁
    常采用的方法有:
    (1)剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态。
    (2)撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用。

  • 内存管理的方式
  • 块式管理:把主存分为一大块一大块的,当所需的程序片段不在主存时就分配一块主存空间,就算所需的程序片段只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。
  • 页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,这种方法的空间利用率要比块式管理高很多
  • 段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多。缺点在于一个程序片段可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上。
  • 段页式管理:结合了段式管理和页式管理的优点。把主存先分成若干段,每个段又分成若干页。段页式管理每取一次数据,要访问3次内存。(访问段表得到页的地址–>访问页表得到想要的数据所在地址–>访问地址得到数据)
    分页和分段的区别:
    (1)页是信息的物理单位,段是信息的逻辑单位。
    (2)页的大小固定且由系统确定,段的长度却不固定。
  • 虚拟内存

    什么是虚拟内存?
    虚拟内存简称虚存,是一种内存管理技术,它会使程序认为自己拥有一块很大且连续的内存,然而,这个程序在内存中不是连续的,并且有些还会在磁盘上,在需要时进行数据交换。
    如何实现的“不连续代替连续”?
    虚拟内存技术的核心就是利用了局部性原理,把进程中常用的数据加载到内存中。不常用的部分以虚拟页的形式存放在磁盘中,使用时再从磁盘中调度到内存中。


    作业调度算法
  • 先来先服务(FCFS)
    算法简单,但效率低;对长作业比较有利,但对短作业不利;有利于CPU繁忙型作业,而不利于I/O繁忙型作业。

  • 短作业优先(SJF)平均等待时间、平均周转时间最少
    该算法对长作业不利。更严重的是,可能会导致长作业陷入“饥饿”。
    该算法完全未考虑作业的紧迫程度,因而不能保证紧迫性作业会被及时处理。

  • 时间片轮转
    时间片轮转调度算法主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列,FIFO,但每个只能运行一个时间片,如100ms。时间到了后会被剥夺然后放到队尾。
    在时间片轮转调度算法中,时间片的大小对系统性能的影响很大
    时间片太大 -->退化成了先来先服务。
    时间片太小 --> 进程间切换太频繁,开销增大,反而影响效率。
    因此时间片的大小应选择适当。

  • 优先级调度
    分为非剥夺式和剥夺式,区别在于:
    一个进程正在运行的同时有优先级更高的任务进入,
    非剥夺式 -----> 继续运行直到自己主动退出,才分配给高优先级。(任务管理系统的引擎执行使用的就是这个算法)
    剥夺式 ------> 立即暂停正在运行的,将处理机分配给更重要或紧迫的进程。

  • 多级反馈队列调度(剥夺式优先级+时间片轮转)
    (任务管理系统的任务池也使用了多级优先级队列)
    实现细节:
    (1)每个队列给的时间片大小不同,1<2<3<…
    (2)先执行第1级队列,如果时间片执行不完,则退出放到第2级队列,之后同理。
    (3)第1级队列为空时,才执行第2级,之后同理
    (4)当有更高级的进程进入,立即剥夺当前进程,丢回原队列。


  • 数据库

    mysql

    mysql数据库的整体调用图

    mysql数据库的整体调用图

    自增id和UUID

    自增id的好处是数据库自动生成,速度快;尤其是对于innoDb的聚簇索引,使用自增id效率远大于UUID。原因在于:innodb所以会对主键进行物理排序,使用自增id的话,新数据一定会放到最后,而UUID是无序的,插入时会造成叶子节点的行数据所在磁盘位置移动,所以插入时的效率低。
    个人认为好的解决方法:主键使用自增id,辅助键使用uuid
    自增id的坏处是多个系统融合或者分布式架构中,自增id容易冲突。


    PreparedStatement可以防止sql注入的原因

    sql注入的例子: where id = 03 or 1=1。
    PreparedStatement中sql语句是预编译的,参数都先用?表示,之后只能设置?的值,但不能改变sql的结构,因此or 1=1在此便行不通。

    sql语句查询慢的解决思路
  • 开个慢查询记录,定位到具体sql
  • mysql> show variables like 'long%';long_query_time | 10.000000mysql> set long_query_time=1;mysql> show variables like 'slow%';slow_launch_time | 2slow_query_log | ONslow_query_log_file | /tmp/slow.logmysql> set global slow_query_log='ON'
  • 使用explain,然后进行分析:
    比方说分析后发现该语句没有用到索引:a、建立索引 b、优化sql语句
    explain教程:https://blog.csdn.net/why15732625998/article/details/80388236

  • 如果最后确定是因为数据量太大导致,就分表分库

  • 引起全表查询的几个操作(应尽量避免)

    https://zhidao.baidu.com/question/693081430483912364.html


    Mysql两种常用存储引擎myisam和innodb
  • 区别:
    myisam 不支持事物,只支持表级锁,不支持外键,索引是索引,数据是数据。
    innodb 支持事务和行级锁,支持外键,索引即数据。

  • 索引不同(具体见后文):
    myisam(非聚簇):叶子节点存的是数据地址。
    innodb(聚簇):叶子节点存的是数据本身。

  • 什么时候用myisam什么时候用innodb?
    一般情况下用innodb,但是以下情况使用myisam:
    ①:表里存放着日志信息,因为日志信息需要频繁更新,如果使用innodb的话,会频繁挪动b+树的叶子节点位置,导致磁盘io过大。
    ②:MyISAM的设计理念是:你的数据库被查询得远远超过其更新,因此它执行非常快速的读取操作。如果你的读写比例(插入|更新)小于15%,则最好使用MyISAM。


  • 数据库如何应对大规模写入和读取?
  • 读写分离
    读写分离就是在主服务器上修改数据,数据会同步到从服务器,从服务器只能读取数据,不能写入。

  • 分库分表
    分表(解决单表数据量过大带来的查询效率下降):
    水平切分:不修改数据库表结构,通过对表中数据的拆分而达到分片的目的,一般水平拆分在查询数据库的时候可能会用到 union 操作(多个结果并)。
    可以根据hash或者日期进行进行分表。
    垂直切分:修改表结构,按照访问的差异将某些列拆分出去,一般查询数据的时候可能会用到 join 操作;把常用的字段放一个表,不常用的放一个表,把字段比较大的比如text的字段拆出来放一个表里面。
    分库(提升并发能力):采用关键字取模的方法

  • 使用nosql

  • 数据库事务

    课本上的知识,用于死记硬背

  • 特性:
    (1)原子性(atomicity) :表示一个事务内的所有操作都是一个整体,要么全部成功,要么全部失败。
    (2)一致性(consistency) :也就是不管事务有多少,系统的数据都会保持着一致(不管几个用户在转账,系统的总额是不变的)。
    (3)隔离性(isoation) :多个并发事务之间的操作不会干扰。
    (4)持久性(durability) :事务完成之后,它对于系统的影响是永久性的。
    数据库的ACID是一种强一致性模型,即写入了什么数据,就能读到什么数据。
    不同于数据库,分布式理论中BASE则不强求强一致性,而是选择通过牺牲强一致性来获得可用性。
    (此处可引导面试关让你讲分布式理论)
  • 隔离问题:
    脏读:一个事务读取了另一个事务没有提交的数据(这个数据可能会回滚)
    不可重复读:在一个事务里面读取了两次某个数据,读出来的数据不一致(update)
    虚读(幻读):两次读取,发现多了/少了一些数据(insert,delete)
  • 隔离级别:
    read uncommitted:读未提交。存在3个问题
    read committed:读已提交。解决脏读(写数据只会锁住相应的行),存在2个问题
    repeatable read:(mysql默认)可重复读。解决:脏读、不可重复读,存在1个问题。
    serializable :串行化。都解决,但并发效率很低。
  • 进阶
    不应该把ACID放在一块叫,AID是手段,C是最终的目的。所以应该这么来介绍本地事务:
    首先介绍事务的一致性:为了保证系统中所有的数据都是符合期望的(不管几个用户在转账,系统的总额是不变的)
    接着说:为了达成这个目标,需要从三个方面来努力:
    原子性(atomicity):同一个业务处理过程中,事务保证对多个数据的修改,要么全部成功,要么全部失败。
    隔离性(isoation) :不同的业务处理过程中,事务保证了各自业务的读写的数据互相独立,不会互相干扰
    持久性(durability) :保证事务提交后的数据能够被持久化到磁盘中,不会丢失数据。

    接着再介绍如何实现的A和D(这俩放在一块讲):为了保证A,就需要把所有操作放在一起,等事务提交之后统一刷到磁盘中,而为了防止崩溃引发所有数据丢失,就需要通过redo log来进行保证。同时,为了防止事务异常而导致
    详见hollis666的文章,或自行百度:

    • binlog、redolog和undolog区别
    • InnoDB的一次更新事务是怎么实现的?
    • buffer pool的读写过程是怎么样的?

    然后再介绍一下隔离性:并发场景下的隔离性就是通过各种锁来实现:

    行锁:锁的目标一定是唯一索引列或主键列
    排它锁和共享锁:

    首先说明:数据库的增删改操作默认都会加排他锁,而查询不会加任何锁
    注意:
    1.单纯的select语句不会加任何锁
    2.如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。

    |-- 共享锁(读锁):自己和其他人都可以同时查询,这个锁的意义在于加上共享锁查询的过程中,无法再加排它锁,语法为:

    select * from table lock in share mode

    |-- 排他锁(写锁):对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。语法为:

    select * from table for update (For update是行级锁)

    范围锁:间隙锁和临键锁(gap Lock 和 next-key lock):

    链接:http://t.zoukankan.com/igoodful-p-9192952.html
    总结:

    • 目的:解决可重复度级别下的幻读问题。
    • 生效范围:普通索引。
    • gap Lock:对一个区间范围加锁,select * from test where age>18 and age <24;
    • next-key lock:
      • gap lock+行记录锁,会对指定数据加行记录锁,左开右闭区间加gap lock。
      • 在select * … where number = xx for update 和 update … where number = xx 语句时,会把条件的上一个间隙区间和下一个间隙区间合并,对合并的区间范围加锁。
    • 注意:
      • 索引字段下的每行数据之间都会有一个间隙(即使的字段值相同也会有间隙)

    隔离级别、幻读、Gap Lock、Next-Key Lock

    面试回答:
    行锁、间隙锁、临键锁,都是mysql里面innodb引擎下去解决事务隔离性的一系列排它锁,其目的是为了解决可重复读这个事务隔离级别下幻读的问题,下面我分别介绍一下这三种锁:

    • 行锁也称为记录锁,当我们对主键或者唯一索引进行查询,mysql默认会对这一行数据增加行锁,避免其他事务对这一行进行修改。
    • 而间隙锁,顾名思义,就是锁定一个索引区间,我们知道索引是基于b+树的,那么两个叶子中间就会存在索引的一个区间,而间隙锁就是当你在事务中使用between等对索引列的进行范围查询,会自动触发一个间隙锁,锁定索引列的区间(左右开区间)。
    • 最后一个是临简缩next-key lock,他是行锁和间隙锁的组合,它的锁定范围既包含了索引记录,也包含了索引的一个左开右闭的区间,当我们使用非唯一索引进行select for update、或者update时,默认会锁定一个左开右闭的范围。
      所以总的来说,行锁、间隙锁、临键锁,它只是表示锁的一个范围。实际应用场景中,尽可能去使用唯一索引或者主键索引来进行一个数据查询,避免大面积的锁定造成的性能的影响。
    MVCC部分:

    全网最全一篇数据库MVCC详解,不全你打我

    事务的传播行为

    看完就明白_spring事务的7种传播行为

    mysql的存储原理:(mySql 索引)

    mysql索引使用了b+树这一数据结构。

    索引

    介绍b+树 (阅读之前需对二叉查找树有基础了解)

    首先介绍B树,B树事实上是一种平衡的多叉查找树。每个结点都相当于数据。


    而B+树相对于b树的差别在于:

    • 非叶结点仅具有索引作用(索引的大小较小,不放数据的话非叶结点就能存放更多的索引)。
    • 树的所有叶结点构成一个有序链表。

    附:
    生成b树的网址:
    https://www.cs.usfca.edu/~galles/visualization/BTree.html
    生成b+树的网址:http://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

    为什么不适用二叉查找树,而是使用B树(包括B+)

    1、B树包括B+树的设计思想都是尽可能的降低树的高度,以此降低磁盘IO的次数,因为一个索引节点就表示一个磁盘页,页的换入换出次数越多,表示磁盘IO次数越多,越低效。
    2、B树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点。

    磁盘预读:磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理: 当一个数据被用到时,其附近的数据也通常会马上被使用。 程序运行期间所需要的数据通常比较集中。

    具体深度能降到多少?
    具体见下面的“ b+树3层能存多少个元素?”

    为什么使用B+树而不使用B树

    前面提到了,B树能很好地解决红黑树的高度问题,以及拥有磁盘预读的功能。
    但是B树不能解决范围查找的问题,比方说上面的B树的图片中,如果要查询大于5的所有数据,那么一路向下查到5的具体位置后,8或12等数据的具体位置是没法很方便的获取的(因为那样的话得回父节点去递归,很麻烦)。
    B+树的所有叶结点构成一个有序链表,这样便可以很方便进行范围查找

    索引为什么都是选的B+树,而不选Hash?

    99%的情况下都会选择B+树,其原因在于:
    Hash的单条查询效率很快,但是有一个致命的缺点,就是Hash不适用于范围查找,而范围查找又是一个很常见的查询。

    b+树3层能存多少个元素?

    现在,我们假设一个磁盘页(每一个节点)有16K的大小。
    那么根节点就能存放16K/14B=1170个元素,然后第二层也有1170个元素。
    第三层的话,假设每个数据占1K(一般完全到不了1K这么大),那么一个磁盘页就有16个元素。
    此时3层便能存1170*1170*16 约=2200W

    innoDB和mysaim的索引区别

    InnoDB使用的是聚簇索引,行数据和主键B+树存储在一起,辅助键B+树只存储辅助键和主键。若使用"where id = 14"这样的条件查找主键,则在主索引B+树中获取id对应的数据。若对Name列进行条件搜索,则需要两个步骤:从辅助键b+树中获取name对应的id。第二步在主索引B+树中获取id对应的数据。
    MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树没什么不同,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,通过辅助键检索无需访问主键的索引树。

    索引的存储文件:

    mysaim:


    innodb:

    联合索引的最左匹配原则(即从左往右匹配)

    在联合索引中,从左往右进行匹配。
    例如建立(a,b,c,d)顺序的索引,那么匹配顺序是a,b,c,d
    KEY test_col1_col2_col3 on test(col1,col2,col3);
    联合索引 test_col1_col2_col3 实际建立了(col1)、(col1,col2)、(col,col2,col3)三个索引。
    SELECT * FROM test WHERE col1=“1” AND clo2=“2” AND clo4=“4”
    上面这个查询语句执行时会依照最左前缀匹配原则,检索时会使用索引(col1,col2)进行数据匹配。
    注:下面两条语句效果实际上是一样的,因为第二条语句会被mysql优化器调整第一条

    SELECT * FROM test WHERE col1=“1” AND clo2=“2”SELECT * FROM test WHERE col2=“2” AND clo1=“1”
    复合索引的底层结构

    结论:仍然是一个b+树,只不过会将多个字段融合成一个判断条件。
    详见知乎:https://www.zhihu.com/question/268703288


    索引覆盖、索引下推
  • 总体介绍:回表与覆盖索引,索引下推

  • 索引下推详细介绍:什么是索引下推


  • sql主从复制的原理(面试被问到过)

    一文搞懂MYSQL主从复制原理

    sql join

    注:mysql 没有 FULL OUTER JOIN,需要使用Left JOIN UNION RIGHT JOIN这一形式(本身外连接就很少用)。


    相关推荐

    相关文章