看盘古系统杂记

时间:2021-10-30 16:12:36

看视频 盘古:阿里云飞天分布式存储系统实践 过程中记的乱七八糟的东西。仅是个人笔记供以后查阅,没有参考价值,各位看官还是各自散去~

分布式和集群的区别:
分布式是指将不同的业务分布在不同的地方。 而集群指的是将几台服务器集中在一起,实现同一业务。
分布式中的每一个节点,都可以做集群。 而集群并不一定就是分布式的。
分布式的每一个节点,都完成不同的业务,一个节点垮了,哪这个业务就不可访问了。
简单说,分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。

块存储,典型设备:磁盘阵列,硬盘,虚拟硬盘。这种接口需要实现Linux的Block Device的接口。
文件存储,典型设备:FTP、NFS服务器,SamBa。通常意义是支持POSIX接口,它跟传统的文件系统如Ext4是一个类型的。
对象存储,典型设备:内置大容量硬盘的分布式服务器。也就是通常意义的键值存储。

分布式系统下的纠删码技术 – Erasure Code (EC)
为了数据丢失后能恢复,除了存放几份副本,还有另一种做法,就是利用纠删码把丢失的数据计算出来。与副本相比,纠删码的优点在于节省存储空间,缺点在于有计算开销而且修复需要一定时间。
例如,有k个数据块,则如果用副本的方式,至少就需要保存2k个数据块。而使用纠错码的方式,由于k元多项式需要k个方程就能解出来,因此只需要最少k+1个数据块(当然还需要用于计算的编码矩阵)。
现有的EC库:Jerasure库、Intel EC库、Hadoop 3.0的EC编码。其中Jerasure库是线程不安全的。
http://blog.csdn.net/u011026968/article/details/52295666 中关于求出丢失数据和code的例子(矩阵乘法、逆矩阵)。
定义:erasure code是一种技术,它可以将n份原始数据,增加m份数据(用来存储erasure编码),并能通过n+m份中的任意n份数据,还原为原始数据。

RAID,为Redundant Arrays of Independent Disks的简称,中文为廉价冗余磁盘阵列。RAID阵列技术允许将一系列磁盘分组,以实现为数据保护而必需的数据冗余,以及为提高读写性能而形成的数据条带分布。
一共有0~6一共7种,这其中RAID 0、RAID1、RAID 5和RAID 6比较常用。由于RAID0完全没有数据安全保障(只加快读写性能,把数据分布在多个盘上,在读写时是以并行的方式对各硬盘同时进行操作),RAID1存储效率低(50%),而RAID5/RAID6也就分别支持最多1块和2块磁盘失效。因此还是使用EC纠错码比较高效可用。
而RAID 5/6由于复杂的算法,读取和写入速度测试时,表现会比其他RAID要差。

打开文件的O_APPEND标记:
fd = open(“./test.c”, O_WRONLY | O_APPEND);
在写文件时,无论先lseek到哪里,write的时候都是在文件结尾append内容。
在读文件时,lseek仍然有效果。因此对于刚打开的文件,seek到SEEK_CUR其实是0位置,而不是文件结尾。
实现apend only还可以用模拟fifo的方式。
常见的apend only的例子是syslog。

aka: also known as

写入存储时,根据是否有IO缓存,分两种方式:
Write-through(直写模式)在数据更新时,同时写入缓存Cache和后端存储。此模式的优点是操作简单;缺点是因为数据修改需要同时写入存储,数据写入速度较慢。
Write-back(回写模式)在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。

Write-through方式,在读的时候不用检查缓存是否dirty,命中后可直接读。
Write-back方式,在读的时候要判断是否dirty。另外读和写结束后还要判断是否需要更新脏位标记(例如读后要标记非脏,写后要标记脏了来通知回写)。
具体流程参考 http://witmax.cn/cache-writing-policies.html

RPC(Remote Procedure Call)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据。

以JSON-RPC举两个例子:
–> {“jsonrpc”: “2.0”, “method”: “subtract”, “params”: {“subtrahe
nd”: 23, “minuend”: 42}, “id”: 3}
<– {“jsonrpc”: “2.0”, “result”: 19, “id”: 3}

–> {“jsonrpc”: “2.0”, “method”: 1, “params”: “bar”}
<– {“jsonrpc”: “2.0”, “error”: {“code”: -32600, “message”: “Inva
lid Request”}, “id”: null}

对一个软件做性能测试时需要关注那些性能呢?
从用户角度上:
1. 保证核心功能的效率和使用。附加功能可做取舍,或通过增加操作步骤来降低性能瓶颈(例如将一些按钮置灰)。
2. 用户体验,主要体现在操作的响应时间,例如web和GUI操作的响应时间。
从开发者角度上:
1. 代码存在潜在性能问题,比如无休止且无睡眠的轮询、频繁memcpy大块内存。
2. 不合理的内存使用。
3. 线程同步问题,是否会导致长时间阻塞。是否产生资源竞争(各功能资源隔离)。

IOPS:衡量随机读写性能。即I/O per second,即每秒读写(I/O)操作的次数。
机械硬盘的连续读写性很好,但随机读写性能很差。这是因为磁头移动至正确的磁道上需要时间,随机读写时,磁头不停的移动,时间都花在了磁头寻道上,所以性能不高。
随即读写测试:读取10000个1KB文件,用时10秒 Throught(吞吐量)=1MB/s ,IOPS=1000 追求IOPS
顺序读写测试:读取1个10MB文件,用时0.2秒 Throught(吞吐量)=50MB/s, IOPS=5 追求吞吐量
通常计算方法:IOPS = 1000 ms/ (寻道时间 + 旋转延迟)。可以忽略数据传输时间(因为通常远小于寻道时间和旋转延迟)。
硬盘转速:7200rpm是指每分钟盘片能旋转7200转。

可以一次性喂给设备更多的IO请求,让电梯算法和设备有机会来安排合并以及内部并行处理,提高总体效率,可以用队列深度表示磁盘一次能处理的请求数量。但大多数的软件都是属于同步I/O软件(一次IO操作要等上次IO操作完成),这时队列深度为1。因此在测试时,队列深度为1是主要指标。

–Master–
防打死:“打死”是指,一个master上可能会跑多个用户的多个业务,但如果一个用户恶意操作或出了bug,导致大量地去访问master,就叫做把master流量给打死了。那整个集群无法给所有用户提供服务。解决方式 —— 用户隔离(云计算中的一项基础技术)。

盘古的federation特性:通过paxos group增加master的水平扩展,每个paxos group都包含一个primary master节点和多个secondary master节点,来保证一致性。可以增加paxos group存放不同volumes,以及内存紧致排列来缓解内存压力(即盘古的federation特性)。
后面讲监控+自愈时,master发现自身有问题可以自我切换primary(将某个良好的secondary变成primary)。
不过有的时候master总是认为自己是好的,但已经是半死不活了,因此盘古允许client来发起master的primary切换,当client发现一个paxos group已经无法提供服务或服务质量很差,就可以主动发起master切换。
另外,为什么每个paxos group里的master数据必须是奇数的??

盘古的单组master可达到读15W的QPS,写5W的QPS。

要周期性的检查集群和机架的环境分布正确性。

–Chunk Server–
RACK,即故障域:是保证三副本的依据,也就是一个故障涉及的范围。最简单的划分依据是副本都放在不同的机架上。那么,我看到还有两个副本放到同一个chunkserver的,这个故障域是怎么确定的呢?
通过数据后面的CRC来判断数据是否损坏,损坏就拷贝一个新副本。
例子中RACK1由于断电或断网失效,那后续如果恢复了,怎么将其数据置位无效?索引和数据有分开吗?
使用纠错码技术,来代替三副本,可降低存储空间。

PB = 1000TB

E2E: end to end

磁盘要512B或4KB等对齐,来提高写入性能。

写入存储时,由“数据->内存->硬盘”改为“数据->闪存->硬盘”,这样可以防掉电且获得高IOPS(相比硬盘的IOPS)。还可以延长闪存寿命。
UPS技术:不间断电源(Uninterruptable Power Supply),可防止异常断电造成的数据丢失,但成本较高。

flash/eMMC/固态硬盘的差别和优劣。三者寿命比较。

如何预先甄别一台机器坏与不坏?或已经处在半死不活的状态,即故障了?

记忆并规避故障机器:
写副本时发现故障了:停止写,且记住以后写其他副本时也不在这台机器上写。
读副本时发现故障了:停止读,且后续如果读其他副本时发现也在这台机器上就直接绕过。

–Client–
用C++和Python写的。
多线程编程简单,但切换代价太大。
用异步代替多线程,少数线程去执行task,但是编码难度高。
用协程代替异步,既不切线程(是单线程的),又能编码简单。需要注意的点:1.不能有阻塞操作和同步原语,不然所有task就都阻塞了。2.最好不要使用C++的异常。
用线程同步原语来同时支持协程和非协程同时存在。(怎么做到的??)

协程和select的区别,它们都是单线程处理多任务。
select是去监听多个任务有没有数据要处理,每个任务收到数据必须处理完才能继续监听和处理后面的任务。
协程更加广义,不仅监听文件数据,可以是被放进来的任何任务,协程去为每个任务决定处理顺序,因此是“主动”的,也就是说协程可能事先就知道有多少个任务待处理。这些任务一般是没有优先级顺序的,所有协程顺序地处理任务,但是当一个任务执行IO等操作需要等待(当然不能忙等)时,就可以切换到其他任务。
因此,协程和select的区别是,协程在给任务分配执行时间时更灵活,一次处理过程可能执行不完一个任务,因此在协程中要注意共享变量的
访问问题,并且不能加锁,因为这是单线程,加锁就锁住了~。比较麻烦的是,协程要自己实现这种任务切换机制。
协程其实和非抢占内核很类似,一个线程执行时,只要不阻塞(sleep等),就一直执行,而调用阻塞函数(sleep等函数)或主动让出CPU(schedule等函数)后,调度器会尝试进行线程切换。

client的自我容错:client是位于客户机的lib,那如果出问题,如何确定是用户程序的问题还是lib的问题呢?因此盘古在client端做了一些检测,如果是自己的问题,就及时容错。

UT: 单元测试。盘古的代码,UT是生产代码的1.5倍。
FT: 功能测试。
SLB: server load balance。
OSS: object storage service。
AZ: Availability Zone。

视频最后对象存储的例子:
AZ可以简单理解为一组节点的集合,这组节点具有独立的电力供应设备,比如一个个独立供电的机房,一个个独立供电的机架都可以被划分成AZ。所以,AZ主要是通过冗余来解决可用性问题。
region更像是一个地理上的概念,例如中国区、北美区等。一个region里可以有多个AZ,不然一个AZ坏了,这个区就没法提供服务了。

GFS:
设计一个分布式文件系统应考虑的:可伸缩性(水平扩展?)、性能、容错能力、可用性。
GFS主要目的是数据密集型的分布式存储。
高性能:处理客户端请求的效率高
高可用:不能随便就挂掉了
编程简单:基于此服务器进行业务开发需要足够简单
可扩展:可方便的扩展功能
可伸缩:可简单的通过部署的方式进行容量的伸缩,也就是服务需要无状态

热点:一个server称为热点,意思是说同时有大量的clients并发访问这个server。例如,大量clients都定位到自己需要的资源在这个server上,导致系统局部过载。需要做负载均衡。

GFS中第7章的下面一段话啥意思,我的理解对不对???
Another Linux problem was a singler eader-writer lock which any t
hread in an address space must hold when it pages in from disk(re
ader lock) or modifies the address space in an mmap() call(writer
lock). We saw transient timeouts in our system under light load a
nd looked hard for resource bottlenecks or sporadic hardware fail
ures. Eventually, we found that this single lock blocked the prim
ary network thread from mapping new data into memory while the di
sk threads were paging in previously mapped data. Since we are ma
inly limited by the network interface rather than by memory copy
bandwidth, we worked around this by replacing mmap() with pread()
at the cost of an extra copy.
我的理解是,开始的做法是读和写都是通过mmap,并且无论读磁盘还是写磁盘都用的一个读写锁(读的时候加锁是为了防止要读的地址空间被写线程mmap到其他地址或覆盖了原来的数据,写的时候加锁是为了防止其他读和写线程的抢占),因此正在读(page in)的时候不能写。
这就导致网络线程在写的时候阻塞了。解决方法是,读的时候由mmap改为pread,这样读就不用加锁了,因为内核会帮你拷贝好,而写仍然用mmap。

page in: 页的换入,即磁盘到内存。
page out:页的换出,即内存到磁盘。
但我们通常说的都是文件页和匿名页的换入换出,不知道GFS说的是不是相同的意思。

把client放在客户机上的话,会不会增加被破解的风险?比如通过抓包分析并模拟client和server的交互过程,来绕过cient lib库这一层。这样,一些lib库里做的限制就失效了。

longcall: 长跳转指令。

ARM的紧致内存:一些ARM的SoC有一个称为TCM (Tightly-Coupled Memory)的东西,这是通常只有4-64KB大小的处理器内置ARM。由于嵌到CPU中,TCM也是哈佛结构的,即分为ITCM和DTCM。DTCM不能包含任何指令,而ITCM可以包含指令和数据。你的机器有没有TCM内存,就看有没有打开CONFIG_HAVE_TCM内核选项。
使用TCM的代码需要include asm/tcm.h,函数和变量定义方式如下:
int __tcmfunc foo(int bar); //函数
int __tcmlocalfunc foo(int bar); //为防止longcall,且可以在TCM内部调用
int __tcmdata foo; //变量
int __tcmconst foo; //const变量
用汇编语言的话,就相应的放在”.tcm.text”或”.tcm.data”段中即可。
TCM的应用领域:可预测的实时处理(中断处理)、避免缓存分析(加密算法)、或单纯的性能提高(处理器侧编解码)等。比cache的好处就是具有可预测性的缓存。

BIO: 阻塞IO
NIO: 非阻塞IO
同步IO
异步IO

都说select是轮询,那它多久轮询一次呢?如何做到有数据就立即返回呢,难道轮询时间间隔很短?答:在内核里do_select如果没到timeout就一遍一遍地轮询,到了timeout或有fd的监听操作被触发或发生错误或被信号中断,就返回用户态。多久轮询一次是用户态代码决定的。如果你将select()的timeout参数设置为0s,和下次select之间也不睡觉,那select相当于死循环就CPU 100%了。

Reactor: 同步事件驱动IO,相当于select,只是Java里才有的概念。
Proactor: 异步事件驱动IO。难道是aio(同步阻塞BIO、同步非阻塞NIO、异步非阻塞AIO)?
NIO或reactor即我们通常说的IO多路复用,即select/epoll机制。其实并不是完全的非阻塞,因为如果把select过
程也算作读取数据的一部分,等待事件触发的过程仍然是阻塞的。另外,在select返回后handle事件时,要等handle完了才能再次select,这个过程可能是耗时的,可以通过任务队列的方式,select返回后将read的数据即fd信息放到队列中,然后继续select,而另外开一个task来处理队列中的任务。

在设计多线程程序时往往有很多性能指标,例如低延迟(latency),高吞吐量(throughput),高响应度(responsiv
eness)等。对于多任务,以GUI为例主要是处理各种按钮点击事件,可有三种通常做法:
1. 所有任务在一个线程中同步执行。响应度不高,后面任务要等前面任务执行完才能执行。
2. 开一个单独的执行任务的线程,主线程通过sendMsg等方式调用任务线程,然后继续进行后续操作。而任务线程串行地执行收到的任务,执行完一个任务之后,通知主线程该任务的finish状态。对于任务线程,仍存在阻塞的情况。
3. 为每个任务开一个线程,吞吐量最好,但消耗太大。调用和通知机制同上。
4. 线程池,开n个线程等待任务,n可调整。调用和通知机制同上。这样就让线程数可控,对于大于n的并发任务,放到等待队列中。这个队列可以做额外的设计,例如每个线程一个队列,每个队列上的任务是顺序执行的,对于必须顺序执行的任务,可以放到同一个队列中。
可参考苹果的GCD的实现,开源的:
https://github.com/apple/swift-corelibs-libdispatch
5. 抛开GUI,对于文件/设备的数据处理,就用select。和1差不多,只是1是主动发送事件,select是等待事件到来。

以select和tcp socket为例,所谓可读事件,具体的说是指以下事件:
1 socket内核接收缓冲区中的可用字节数大于或等于其低水位SO_RCVLOWAT;
2 socket通信的对方关闭了连接,这个时候在缓冲区里有个文件结束符EOF,此时读操作将返回0;
3 监听socket的backlog队列有已经完成三次握手的连接请求,可以调用accept;
4 socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误。
所谓可写事件,则是指:
1 socket的内核发送缓冲区的可用字节数大于或等于其低水位SO_SNDLOWAIT;
2 socket的写端被关闭,继续写会收到SIGPIPE信号;
3 非阻塞模式下,connect返回之后,发起连接成功或失败;
4 socket上有未处理的错误,此时可以用getsockopt来读取和清除该错误。

内部碎片和外部碎片:
对于内存而言,内部碎片是指已经分配给某个进程,但没有没利用也没有被释放。外部碎片是指系统中空闲的内存却无法被分配了。
我们通常说的内存碎片就是指外部碎片,也就是系统还剩很多空闲内存,但很碎,比如空闲内存全是4K的页了,那么我想申请一个8K的就会失败。使用内存池(如tcmalloc)可以将一个进程的内存变得集中,避免了频繁向系统申请很小且分散的内存页。
而内部碎片很严重时,表现就是内存泄漏。除了代码bug申请的内存不释放之外,还有可能是内存池的释放策略不佳,或者malloc/free产生的内存空洞。
对于磁盘而言,内部碎片是指为一个文件分配空间时,最后一个簇没占满,又必须全部分配给这个文件。外部碎片是指文件之间的空闲空间,同时,组成一个文件的所有簇也可能是散落在磁盘的不同位置,这也可能是由于外部碎片导致的(写一个文件时,两个现有文件之间的空间装不下,只能分开存)。做磁盘碎片整理就是整理外部碎片,通过移动文件的簇的位置,将每个文件的簇集中起来且按照文件内容偏移量顺序排列,这样可以加快文件读写速度,也防止新建的文件碎片化。

惰性分配(lazy allocation): 当用到的时候再分配内存。例如C++中一个对象的指针成员my_ptr,在构造函数中先不
分配内存,而在get()函数中判断如果my_ptr==NULL再分配内存。也称作Lazy initialization?这种方式主要针对那些花销较大而不适宜在前面初始化的情况(例如可能增加用户等待时间,用这种方式来分散初始化的时间),或者是一些不太重要的功能,能够容忍初始化失败的情况(例如如果在一开始初始化可能抢占了更重要的功能的内存等)。
在多线程环境中,调用这种get()方法要考虑线程同步哦。

====盘古的视频====
4’32”
网络问题的解决方法是是什么呢?(通过端到端数据校验?)
NTP时间漂移是什么,网络抖动或本地晶振偏差导致的吗?不是网络时间吗。用温补可解决吗?
write back被改成write through,怎么探测和解决?

GFS:GFS下面为什么还要有一个Linux文件系统呢???有针对Linux文件系统对内核做过什么修改吗?

==================