【分布式系统】Ch 6 同步 Synchronization (时钟同步、互斥、选举)

【分布式系统】Ch 6 同步 Synchronization (时钟同步、互斥、选举)

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加



前言

同步化是分布式系统中的一个重要概念,主要解决的是排序问题。例如:多个线程不能同时操作一个变量,而是将多个线程使用锁或无锁结构进行同步,同步的目的就是将多个线程排序为一个操作时序对这个变量进行操作。

本章将对同步化中的三个重点概念进行梳理总结,分别是时钟同步、互斥、选举。

(注:文章灰底部分为引用部分,引用来源见参考部分链接。)


一、时钟同步

·基本概念

在单个计算机中,时间是明确的。当进程想要获取时间时,进程就进行一次系统调用,然后操作系统内核就会返回时间给这个进程。
但是在分布式系统中,每台计算机的时钟可能是一致的也可能是不一致的。即使分布式系统中的每台计算机中的时钟是一致的,那么对于这个分布式系统而言这个时钟也不是全局的。不是全局的时钟那么在分布式系统中可能会产生问题。

·物理时钟

物理时钟的实现

计算机中的时钟是一个物理硬件通常称为计时器,计算机的计时器通常是一个石英晶体管。石英晶体管以一定的频率振荡。然后有两个寄存器与每个石英晶体相联,一个是计数器,另一个是保持寄存器。石英晶体振荡使得计数器减1。当计数器为0时产生一个中断,然后计数器从保持寄存器中重新装入初始值。计数器产生的中断称为时钟滴答。当产生一个中断时,操作系统就会响应中断并调用中断处理程序将时钟存储器中的值加1。

在这里插入图片描述

物理时钟的问题

对于单个计算机而言,使用物理时钟是没有问题的,因为即使出现了时间偏移,不同进程的在不同时刻得到的时间还是不一样的。但对于分布式系统而言,大概率会出现在不同的计算机上的进程所使用物理时钟不准确的情况。

时钟偏移 与 时钟漂移(Clock Skew vs. Clock Drift)

对比两个进程的两个时钟:
时钟偏移 = 对应两个进程的时钟数值的相对差(e.g:就好像路上两辆车之间的距离差)
时钟漂移 = 对应两个进程的时钟频率的相对差(e.g:就好像路上两辆车之间的速度差)
当时钟偏移不为0,表示为不同步。
当时钟漂移不为0会最终导致时钟偏移的增加。

在分布式系统中,进程分布在不同的计算机上,这时每台计算机的时钟都发生了偏移,那么整个分布式系统中的时钟就是不同步的。整个分布式系统中的时钟不同步的话会导致依赖时钟同步的程序有问题。
注意:物理时钟同步有一个很重要的点就是即使当前计算机时钟大于标准时间或UTC时间,那么这台计算机的物理时钟也是不会回退的,因为回退会造成很多问题,甚至是致命的问题。通常的解决方式是如果物理时钟过快就增加保持寄存器的值从而增加了时钟的振荡周期;如果物理时钟过慢就减少保持寄存器的值从而减少了时钟的振荡周期;以上都是通过一个过渡期来慢慢调整物理时钟从而达到与标准时间或UTC时间一致的结果。
解决物理时钟不同步的方式主要是时钟同步算法。这些算法包括但不限于:网络时间协议、Berkeley算法、Critian算法。

外部同步 与 内部同步(External Synchronization vs. Internal Synchronization)

1.(External Synchronization)单独架设了一个无敌准的“时钟校准器”,让每个服务器隔一段时间来找他核对时间。

2.(Internal Synchronization)让那些服务器们自力更生,自己内部讨论一下按照谁的时钟作为标准来走。

物理时钟同步算法

1. 网络时间协议(NTP)

NTP最典型的授时方式是Client/Server方式。如下图1所示,客户机首先向服务器发送一个NTP 包,其中包含了该包离开客户机的时间戳T1,当服务器接收到该包时,依次填入包到达的时间戳T2、包离开的时间戳T3,然后立即把包返回给客户机。客户机在接收到响应包时,记录包返回的时间戳T4。客户机用上述4个时间参数就能够计算出2个关键参数:NTP包的往返延迟d和客户机与服务器之间的时钟偏差t。客户机使用时钟偏差来调整本地时钟,以使其时间与服务器时间一致。
在这里插入图片描述

图中(来源自网络):T1为客户发送NTP请求时间戳(以客户时间为参照);T2为服务器收到NTP请求时间戳(以服务器时间为参照);T3为服务器回复NTP请求时间戳(以服务器时间为参照);T4为客户收到NTP回复包时间戳(以客户时间为参照);d1为NTP请求包传送延时,d2为NTP回复包传送延时;t为服务器和客户端之间的时间偏差,d为NTP包的往返时间。
现已经T1、T2、T3、T4,希望求得t以调整客户方时钟:
在这里插入图片描述 (1)
假设NPT请求和回复包传送延时相等,即d1=d2,则可解得
在这里插入图片描述…(2)
根据式(1),t也可表示为:t=(T2-T1)+d1=(T2-T1)+d/2. …式(3)
可以看出,t、d只与T2、T1差值及T3、T4差值相关,而与T2、T3差值无关,即最终的结果与服务器处理请求所需的时间无关。因此,客户端即可通过T1、T2、T3、T4计算出时差t去调整本地时钟。

2. Berkeley算法 (Internal Synchronization)

Berkeley 算法 适用于无线电时钟(radio clock)不可用的分布式系统,此类系统无法得知真实时间,只能通过维护一个全局的平均时间作为标准时间。一台时间服务器会周期性地获取各个客户端上的时间,将其平均处理后,回传每个客户端的时间与平均时间的偏移,以达到统一使用此平均时间的目的。此算法适用于不仅时间可能不一致,时钟速率也可能不一致的系统。如果一个客户端的时间偏移过大,超出了容忍值,则通常不会参与平均时间的计算。如此可以防止系统的时间被单个异常的时钟过度影响。

在这里插入图片描述

3. Cristian 算法 (External Synchronization)

利用RTT时间来帮助校准。
如下图(来自知乎用户,见水印)所示,服务器P向“校准器”(该“校准器”时间绝对精准)发送时间询问消息。RTT = (T1-T0)返回消息包含当前查询的精准时间为t,那么P再接受到消息后将自己时间设置为 t+tp = t + (T1-T0)/2。
但是这是不准确的做法,因为其假设了来回的时间是对称的。
图片来自知乎用户
可以进一步精确建模在这里插入图片描述

Tmin1是P到S的时间,Tmin2是S到P的时间。我们可以推测出,P收到的时间应该是介于t+Tmin2和t+(RTT-Tmin2)之间。
那么取个平均值,就是t + (RTT-Tmin1+Tmin2)/2,也就是tp = (RTT-Tmin1+Tmin2)/2

·逻辑时钟

什么是逻辑时钟?

逻辑时钟是为了区分现实中的物理时钟提出来的概念,一般情况下我们提到的时间都是指物理时间,但实际上很多应用中,只要所有机器有相同的时间就够了,这个时间不一定要跟实际时间相同。更进一步,如果两个节点之间不进行交互,那么它们的时间甚至都不需要同步。因此问题的关键点在于节点间的交互要在事件的发生顺序上达成一致,而不是对于时间达成一致。
综上,逻辑时钟指的是分布式系统中用于区分事件的发生顺序的时间机制。从某种意义上讲,现实世界中的物理时间其实是逻辑时钟的特例。

为什么需要逻辑时钟?

时间是在现实生活中是很重要的概念,有了时间我们就能比较事情发生的先后顺序。单个计算机内执行的事务,很容易通过时间戳来区分先后。
但在分布式系统中也通过时间戳的方式来区分先后却不太行。
因为在分布式系统中,不同节点间保持它们的时钟一致是一件不容易的事情。因为每个节点的CPU都有自己的计时器,而不同计时器之间会产生时间偏移,最终导致不同节点上面的时间不一致。
那么是否可以通过某种方式来同步不同节点的物理时钟呢?答案是有的,NTP就是常用的时间同步算法,但是即使通过算法进行同步,总会有误差,这种误差在某些场景下(金融分布式事务)是不能接受的。
因此,Lamport提出逻辑时钟就是为了解决分布式系统中的时序问题,即如何定义a在b之前发生。值得注意的是,并不是说分布式系统只能用逻辑时钟来解决这个问题,如果以后有某种技术能够让不同节点的时钟完全保持一致,那么使用物理时钟来区分先后是一个更简单有效的方式。

什么是时序关系?

通过前面的讨论我们知道通过物理时钟(即绝对参考系)来区分先后顺序的前提是所有节点的时钟完全同步,但目前并不现实。因此,在没有绝对参考系的情况下,在一个分布式系统中,你无法判断事件A是否发生在事件B之前,除非A和B存在某种依赖关系,即分布式系统中的事件仅仅是部分有序的。
在分布式系统中,两个事件可以建立因果(时序)关系的前提是:两个事件之间是否发生过信息传递
在分布式系统中,进程间通信的手段(共享内存、消息发送等)都属于信息传递,如果两个进程间没有任何交互,实际上他们之间内部事件的时序也无关紧要。但是有交互的情况下,特别是多个节点的要保持同一副本的情况下,事件的时序非常重要。

1. Lamport 逻辑时钟

分布式系统中按是否存在节点交互可分为三类事件,一类发生于节点内部,二是发送事件,三是接收事件。注意:以下文章中提及的时间戳如无特别说明,都指的是Lamport 逻辑时钟的时间戳,不是物理时钟的时间戳

逻辑时钟定义
Clock Condition.对于任意事件𝑎, 𝑏:如果𝑎→𝑏(→表示a先于b发生),那么𝐶(𝑎)<𝐶(𝑏), 反之不然, 因为有可能是并发事件
C1.如果𝑎和𝑏都是进程𝑃𝑖里的事件,并且𝑎在𝑏之前,那么𝐶𝑖(𝑎)<𝐶𝑖(𝑏)
C2.如果𝑎是进程𝑃𝑖里关于某消息的发送事件,𝑏是另一进程𝑃𝑗里关于该消息的接收事件,那么𝐶𝑖(𝑎)<𝐶𝑗(𝑏)

在这里插入图片描述

每个事件对应一个Lamport时间戳,初始值为0
如果事件在节点内发生,本地进程中的时间戳加1
如果事件属于发送事件,本地进程中的时间戳加1并在消息中带上该时间戳
如果事件属于接收事件,本地进程中的时间戳 = Max(本地时间戳,消息中的时间戳) + 1
假设有事件𝑎、𝑏,𝐶(𝑎)、𝐶(𝑏)分别表示事件𝑎、𝑏对应的Lamport时间戳,如果𝑎发生在𝑏之前(happened before),记作 𝑎→𝑏,则有𝐶(𝑎)<𝐶(𝑏),例如图1中有 𝐶1→𝐵1,那么 𝐶(𝐶1)<𝐶(𝐵1)。通过该定义,事件集中Lamport时间戳不等的事件可进行比较,我们获得事件的偏序关系(partial order)。注意:如果𝐶(𝑎)<𝐶(𝑏),并不能说明𝑎→𝑏,也就是说𝐶(𝑎)<𝐶(𝑏)是𝑎→𝑏的必要不充分条件

如果𝐶(𝑎)=𝐶(𝑏),那𝑎、𝑏事件的顺序又是怎样的?值得注意的是当𝐶(𝑎)=𝐶(𝑏)的时候,它们肯定不是因果关系,所以它们之间的先后其实并不会影响结果,我们这里只需要给出一种确定的方式来定义它们之间的先后就能得到全序关系。注意:Lamport逻辑时钟只保证因果关系(偏序)的正确性,不保证绝对时序的正确性。

一种可行的方式是利用给进程编号,利用进程编号的大小来排序。假设𝑎、𝑏分别在节点𝑃、𝑄上发生,𝑃𝑖、𝑄𝑗分别表示我们给𝑃、𝑄的编号,如果 𝐶(𝑎)=𝐶(𝑏) 并且 𝑃𝑖<𝑄𝑗,同样定义为𝑎发生在𝑏之前,记作 𝑎⇒𝑏(全序关系)。假如我们对图1的𝐴、𝐵、𝐶分别编号𝐴𝑖=1、𝐵𝑗=2、𝐶𝑘=3,因 𝐶(𝐵4)=𝐶(𝐶3) 并且 𝐵𝑗<𝐶𝑘,则 𝐵4⇒𝐶3。

通过以上定义,我们可以对所有事件排序,获得事件的全序关系(total order)。上图例子,我们可以进行排序:𝐶1⇒𝐵1⇒𝐵2⇒𝐴1⇒𝐵3⇒𝐴2⇒𝐶2⇒𝐵4⇒𝐶3⇒𝐴3⇒𝐵5⇒𝐶4⇒𝐶5⇒𝐴4
观察上面的全序关系你可以发现,从时间轴来看𝐵5是早于𝐴3发生的,但是在全序关系里面我们根据上面的定义给出的却是𝐴3早于𝐵5,可以发现Lamport逻辑时钟是一个正确的算法,即有因果关系的事件时序不会错,但并不是一个公平的算法,即没有因果关系的事件时序不一定符合实际情况。

校正例子
在这里插入图片描述
图a说明了使用物理时钟得到的时间分配;可以看到,进程P3发送m3到进程P2并达到P2的时钟值就出现了问题,因为消息发送的时刻应该小于消息达到的时刻。
图b说明了使用Lamport逻辑时钟得到的时间分配。消息m3在时刻60离开P2,那么消息m3到达进程P1的时刻应该在61时刻或者更晚。所以每个消息应该根据发送者时钟的发送时间来调整自己的时钟值。图b中进程P3在收到m3消息后发现自己的时钟小于m3消息的时钟,进程P2根据消息m3的时钟调整了自己的时钟为61,进程P1也做了同样的时钟值调整为70。

2. 向量逻辑时钟

Lamport 逻辑时钟中我们知道Lamport 逻辑时钟帮助我们得到了分布式系统中的事件全序关系,但是对于同时发生的关系却不能很好的描述,导致无法描述事件的因果关系。向量时钟是在 Lamport 时间戳基础上演进的另一种逻辑时钟方法,它通过向量结构不但记录本节点的 Lamport 时间戳,同时也记录了其他节点的 Lamport 时间戳,因此能够很好描述同时发生关系以及事件的因果关系。

为什么需要向量时钟?

首先我们来回顾一下 Lamport 逻辑时钟算法,它提供了一种判断分布式系统中事件全序关系的方法:如果 a -> b,那么 C(a) < C(b),但是 C(a) < C(b) 并不能说明 a -> b (a -> b表示了事件的因果关系,a发送,b接收)。也就是说C(a) < C(b) 是 a -> b 的必要不充分条件,我们不能通过 Lamport 时间戳对事件 a、b 的因果关系进行判断。

向量时钟算法是在 Lamport 逻辑时钟的基础上进行了改良,用于在分布式系统中描述事件因果关系的算法。那么为什么叫向量时钟呢?向量时钟设定每个进程都能够知道其他所有进程的时间,就能够通过计算得到事件的因果关系。向量时钟算法利用了向量这种数据结构将全局各个进程的逻辑时间戳广播给各个进程:每个进程发送事件时都会将当前进程已知的所有进程时间写入到一个向量中,附带在消息中。这就是向量时钟命名的由来。

向量时钟的实现

假设分布式系统中有 N 个进程,每个进程都有一个本地的向量时间戳 Ti,向量时钟算法实现如下:
对于进程 i 来说,Ti[i] 是进程 i 本地的逻辑时间
当进程 i 当有新的事件发生时,Ti[i] = Ti[i] + 1
当进程 i 发送消息时将它的向量时间戳(MT=Ti)附带在消息中。
接受消息的进程 j 更新本地的向量时间戳:Tj[k] = max(Tj[k], MT[k]) for k = 1 to N。(MT即消息中附带的向量时间戳)

在这里插入图片描述

假设有事件 a、b 分别在节点 P、Q 上发生,向量时钟分别为 Ta、Tb,如果 Tb[Q] > Ta[Q] 并且 Tb[P] >= Ta[P],则a发生于b之前,记作 a -> b,此时说明事件 a、b 有因果关系; 反之,如果 Tb[Q] > Ta[Q] 并且 Tb[P] < Ta[P],则认为a、b同时发生,记作 a <-> b。例如上图中节点 B 上的第 4 个事件 (A:2,B:4,C:1) 与节点 C 上的第 2 个事件 (B:3,C:2) 没有因果关系,属于同时发生事件。


二、互斥

单机达到互斥的方法有互斥锁,条件变量,读写锁,共享内存,信号量,内存序,内存屏障等,这些方法在分布式系统中通通不可用,在分布式系统中消息传递是实现互斥的唯一的方法.

分布式互斥算法可以分为集中式算法(Centralized Algorithm)、非集中式算法、分布式算法(Distributed Algorithm),其中分布式算法又可以分为基于令牌的算法(Token Based Algorithm)和基于请求的算法(Permission Based Algorithm).

1. 集中式算法

一个协调者负责所有节点申请资源请求的调度.每个节点想要申请资源需要向协调者发送请求,如果当前节点没有人使用的话,协调者就授权这个节点进行访问.对于协调者来说维护一个队列,用先来后到的顺序为没有申请到资源的节点排序,当得到资源的节点执行完毕以后返回一个消息代表使用完毕,这个时候协调者再次分配.

在这里插入图片描述
一次申请资源的交互中需要三次消息交互:

1.节点向协调者请求资源. 2.协调者给予权限. 3.节点完成以后返回释放消息.

优点,即通信成本较低,且直观,简单,所有的节点只需要于协调者通信.
缺点,所有的压力全部落在了协调者身上, 容易产生单点失效和效率问题。

2. 非集中式算法

假设共享资源副本被复制了n次,每个副本有其自身协作者控制访问;如果某个进程要访问共享资源,主要获得m>n/2个协作者投票允许即可。
如图所示,如果进程0要发起访问请求,只要8个协作者中有5个(包括其自身)投票允许即可。

在这里插入图片描述

但是该算法也有自身的缺陷,即当某个协作者崩溃时,它将忘记之前投过的票,可能在恢复后又投了重复的票给其他请求者;比如已经允许了p进程访问共享资源,之后该协作者重置了,又允许了q进程去访问共享资源,此时可能会出现不一致问题。
为了防止有很多个进程同时发起访问请求导致谁也无法访问共享资源,发起访问的进程可以把请求带上时间戳,其他协作者按照时间戳的先后顺序进行投票允许,同时请求者还需要在访问结束后通知所有其他协作者访问已释放。

3. 分布式算法— 基于令牌

如下图所示,所有程序构成一个逻辑上的环结构,令牌按照顺时针(或逆时针)方向在程序之间传递,收到令牌的程序有权访问临界资源,访问完成后将令牌传送到下一个程序.若该程序不需要访问临界资源,则直接把令牌传送给下一个程序.
在这里插入图片描述

这样每一个节点在适用资源之前就不需要向每一个节点请求了,只需要传递令牌即可,得到令牌的节点获取资源,这样在一个周期内全部节点都有机会适用资源,不需要的话直接转发就可以了.当然这也带来一些无效的通信成本,且降低了实时性.因为就算只有一个节点请求资源,全部的节点都需要进行信息传递,而且考虑到节点宕机,每个节点也不能仅存储自己的下一位节点.令牌环算法非常适合通信模式为令牌环方式的分布式系统.

4. 分布式算法 — 基于请求

这里介绍Ricart-Agrawala算法,其他基于请求的算法大都在这个基础上修改.如果把一次选举的leader看做是资源的话,选举的过程其实也是一个分布式互斥算法的实现.这样看来这种基于请求的算法其实比较常见,也就是所有的节点之间互相连接,形成网状的网络拓扑结构,在每次使用资源的时候向其他节点发起资源的申请,如果获取全票同意的话则获取资源使用权,在使用的过程中也可能有其他节点同样请求资源,发送请求的双方都可以得到对方使用资源的消息,这个时候对比出时间戳最小的(基于逻辑时钟而不是物理时间戳,见另一篇文章 不可靠的时钟),即最先发起请求的获取资源访问权,得到资源访问权的节点也要维护一个队列,存储其他申请请求,在完成资源访问以后向其他申请资源的节点发送同意使用资源的消息,一般不申请资源的节点在收到其他申请资源的请求时直接同意.

从以上流程不难看出,信息的交互过程是这样的,首先我们假设集群有N个节点:

向其他N-1个节点发送请求资源消息.需要N-1次交互.
需要接收N-1个节点的回复消息,需要N-1次交互.
这样每个节点每次申请资源需要2(N-1)此信息交互.而且当集群内每个节点都进行资源申请的时候会有2N(N-1)次交互,也就是说随着集群的扩展,通信成本是平方上涨的.

它还有以下问题:
当系统内需要访问临界资源的程序增多时,容易产生“信令风暴”,也就是程序收到的请求完全超过了自己的处理能力,而导致自己正常的业务无法开展.
因为采用的是全部节点同意,为了防止只有两个节点请求,它们之间只有一个可以得到N-1个投票剩下一个得到N-2个投票,这意味着一个节点故障的话整个系统就处于停滞状态.
这种基于请求的分布式互斥算法适合与节点数目少,变动不频繁的系统,且适用与P2P结构,因为每个程序之间都需要互相交互.HDFS就是一个典型的运用场景.


三、选举

·引入

为了解决集群中多个节点之间协调和管理的问题,解决方法是选出一个“领导”来复杂调度和管理其他节点。这个所谓的“领导”,在分布式中叫做“主节点”,而选“领导”的过程叫做“分布式选举”。

总的来说,选举的作用就是选出一个主节点,由它来协调和管理其他节点,以保证集群有序运行和节点间数据的一致性。

·主要选举算法

1. Ring Election

节点在环上击鼓传花式的通信。规则如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
感兴趣的可以搜索一下算法是怎么运行的,简单来讲就是会分为两个阶段。
Election:会从起始点一直到最大值点,并在环走一圈回到最大值点,以确认Election对象。
比如传递值从起始点开始分别是3(N3)-》32(N32)-〉32(N5)-》80(第一次到达N80)-〉80(N6)-》80(N12)-〉80(N3)-》80(N32)-〉80(N5)-》80(第二次到达N80)-〉结束Election
Elected:整个环从N80开始再走一圈回到N80,标记圈上所有点elected=80,通知结果。

有最好和最坏情况,传递次数不同,感兴趣可以算。

2. Bully Algorithm

当任何一个进程发现协调者不再响应请求时,它就发起一次选举,进程P按照如下过程主持一次选举:
(1)进程P向所有编号比它大的进程发送一个ELECTION消息
(2)如果无人响应,P获胜成为协调者
(3)如果有编号比它大的进程响应,则由响应者接管选举工作。P的工作完成。
触发选举流程的事件包括:
(1)当进程P从错误中恢复
(2)检测到Leader失败

在任何时刻,一个进程只能从编号比它小的进程得到一个ELECTION消息,当消息到达时,接受者发回一个OK消息给发送者,表明它仍在运行,并且接管选举工作。然后接收者主持一个选举。最终,除了一个进程外,其他所有进程都将放弃,那个进程就是协调者。它将选举获胜的消息发送给所有进程,通知它们自己是新的协调者。
当一个以前崩溃了的进程现在恢复过来时,它将主持一次选举。如果该进程正好是当前正在运行的进程中进程号最大的进程,它将赢得这次选举,接管协调者的工作,这样,最大的进程总是取胜,故称为“欺负算法”。

在这里插入图片描述

在图中,可以看到欺负算法工作的例子,图中是编号从0到7的8个进程组成的进程组,以前进程7是协调者,但是它崩溃了。进程4第一个注意到这一点,准备发起选举,所以它发ELECTION消息给所有比它大的进程,即进程5,6,7,进程5,6都用OK消息进行应答。进程4接到第一个应答就知道它的工作已经结束了,它知道有进程号大的进程将接管它的工作成为协调者。它就等着看谁将是获胜者。

进程5和进程6都主持选举,每个进程只将消息发送给比自己进程号大的进程,进程6告诉进程5它将接管成为协调者,此时进程6知道进程7已经崩溃了,进程6将是获胜者。如果从磁盘或其他地方可以获得一些表明原有的协调者在哪里失效的状态信息,那么此时进程6就必须承担所需工作。当进程6准备好接管时,它向所有正在运行着的进程发送一个COORDINATOR消息,当进程4接收到这个消息后,它就可以继续,当发现进程7崩溃了时它正要做的工作,但是这次是以进程6作为协调者了,这样,进程7的故障得到了处理,工作也可以继续了,如果进程7重新启动,它将向其他所有进程发送一个COORDINATIR消息,让它们服从自己的协调。


总结

本文对分布式系统中同步化的三大重点内容进行梳理,分别是时钟同步、互斥、选举。有些内容尚未完全详尽,有机会再完善。

本文旨在复习总结,因此内容较多,许多部分来自于各个博客的重点提炼。感谢参考链接中的博文作者。

(有时间再写详细总结,太累了。)


参考

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值