分布式存储

分布式存储简介:

  • 分布式存储是指一种独特的系统架构,它由一组网络进行通信、为了完成共同的任务而协调工作的计算机节点组成
  • 分布式系统是为了用廉价的、普通的机器完成单个计算无法完成的计算、存储任务
  • 其目的是利用更多的机器,处理更多的数据

很多人可能对分布式存储耳熟能详,但是,大多数人对其概念或者知识点却了解得都过于分散,看了很多却“只见树木,不见森林”,学了很多往往只能“知其然,却不能知其所以然”。因此,有必要对分布式存储的概念、问题和矛盾进行一下分析和解读。

为什么需要分布式存储?

很多人可能从来没有考虑过这个问题,为什么需要分布式存储?实际上,使用分布式存储是“被迫”的,因为随着互联网的飞速发展、应用越来越丰富、用户数量越来越多、数据也成几何级增长,海量数据的存储给本地存储带了巨大压力,存储系统已经不堪重负,处于崩溃的边缘,因此,必须通过其他手段分散存储系统压力,分布式存储和分布式文件系统应运而生。

实际上,如果可能,应该是尽量不使用分布式的,因为这会增加系统的复杂度和管理难度,然而,虽然是这样,但这些终归是可以通过其他技术来解决,而如果不使用分布式,系统的可用性、稳定性都无法保证,更谈不上系统的高性能了。因此,说分布式是被迫使用的,一点也不夸张。

不难发现,分布式的目的就在于追求高性能与高可用这两个特性。分布式系统中遇到的各种理论、技术以及设计方案,其本质上就是为了解决这两个关键性问题而已。在想清楚了这个观点之后,那所谓的分布式也并没有这么高大上,深不可测。它无非是提出一些技术方法来解决遇到的一堆问题。

如何保证分布式存储的高性能与高可用:

那么问题来了,如何保证分布式存储系统的高性能与高可用性呢?

大家可能想到的是,除了传统架构里面的备份、Hot Standby、双活、多活这种架构之外,对于保证分布式存储系统的高可靠和高可用,数据在系统中一般存储多个副本。当某个存储节点出故障时,系统能够自动将服务切换到其他的副本,从而实现自动容错。分布式存储系统通过复制协议将数据同步到多个存储节点,并确保多个副本之间的数据一致性。同一份数据有多个副本,仅有一个为主副本 Primary,其他的副本为备份副本 Backup,数据从主副本复制到备份副本,采用最终一致性来保证数据和事物的完整。

但这些措施可能只是治标不治本,只能满足一般的要求和不时之需。

而实际上,高性能与高可用是矛盾的,比如要设计一个分布式存储系统,出于对性能的考虑,记录数据时先写一个份数据到某个机器上并立即返回,然后异步发起多个数据备份过程(副本)。这种设计的性能最好,但存在“容错性”的风险,即写完数据后,目标机器立即发生故障,会导致数据丢失!如果同时写多个副本,每个副本写成功以后再返回,则又导致性能下降,因为这个过程取决于最慢的那台机器的性能。这就是高性能与高可用之间的矛盾。

而要真正从根本上克服这些矛盾,解决分布式存储的高性能和高可用问题,更有效的是对于分布式文件系统和分布式存储系统架构进行优化和改进,从而从源头解决这些问题。

不过,要对分布式文件系统和分布式存储系统架构进行优化和改进,就必须对分布式文件系统和分布式存储有更深入的了解,可是很多人却不知道该从哪里学习这些知识。恰好,最近UCloud将在武汉举办UCan下午茶活动,邀请数位在分布式存储和分布式文件领域重量级的技术大咖重点讲解分布式存储和分布式文件系统,帮您了解分布式存储和分布式文件系统的真相。对此感兴趣的朋友们可千万不要错过啊!

分布式存储的六大优点

分布式存储往往采用分布式的系统结构,利用多台存储服务器分担存储负荷,利用位置服务器定位存储信息。它不但提高了系统的可靠性、可用性和存取效率,还易于扩展,将通用硬件引入的不稳定因素降到最低。优点如下:

1.高性能

一个具有高性能的分布式存户通常能够高效地管理读缓存和写缓存,并且支持自动的分级存储。分布式存储通过将热点区域内数据映射到高速存储中,来提高系统响应速度;一旦这些区域不再是热点,那么存储系统会将它们移出高速存储。而写缓存技术则可使配合高速存储来明显改变整体存储的性能,按照一定的策略,先将数据写入高速存储,再在适当的时间进行同步落盘。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.支持分级存储

由于通过网络进行松耦合链接,分布式存储允许高速存储和低速存储分开部署,或者任意比例混布。在不可预测的业务环境或者敏捷应用情况下,分层存储的优势可以发挥到最佳。解决了目前缓存分层存储最大的问题是当性能池读不命中后,从冷池提取数据的粒度太大,导致延迟高,从而给造成整体的性能的抖动的问题。

3.多副本的一致性

与传统的存储架构使用RAID模式来保证数据的可靠性不同,分布式存储采用了多副本备份机制。在存储数据之前,分布式存储对数据进行了分片,分片后的数据按照一定的规则保存在集群节点上。为了保证多个数据副本之间的一致性,分布式存储通常采用的是一个副本写入,多个副本读取的强一致性技术,使用镜像、条带、分布式校验等方式满足租户对于可靠性不同的需求。在读取数据失败的时候,系统可以通过从其他副本读取数据,重新写入该副本进行恢复,从而保证副本的总数固定;当数据长时间处于不一致状态时,系统会自动数据重建恢复,同时租户可设定数据恢复的带宽规则,最小化对业务的影响。

4.容灾与备份

在分布式存储的容灾中,一个重要的手段就是多时间点快照技术,使得用户生产系统能够实现一定时间间隔下的各版本数据的保存。特别值得一提的是,多时间点快照技术支持同时提取多个时间点样本同时恢复,这对于很多逻辑错误的灾难定位十分有用,如果用户有多台服务器或虚拟机可以用作系统恢复,通过比照和分析,可以快速找到哪个时间点才是需要回复的时间点,降低了故障定位的难度,缩短了定位时间。这个功能还非常有利于进行故障重现,从而进行分析和研究,避免灾难在未来再次发生。多副本技术,数据条带化放置,多时间点快照和周期增量复制等技术为分布式存储的高可靠性提供了保障。

5.弹性扩展

得益于合理的分布式架构,分布式存储可预估并且弹性扩展计算、存储容量和性能。分布式存储的水平扩展有以下几个特性:

  1. 节点扩展后,旧数据会自动迁移到新节点,实现负载均衡,避免单点过热的情况出现;

  2. 水平扩展只需要将新节点和原有集群连接到同一网络,整个过程不会对业务造成影响;

  3. 当节点被添加到集群,集群系统的整体容量和性能也随之线性扩展,此后新节点的资源就会被管理平台接管,被用于分配或者回收。

6.存储系统标准化

随着分布式存储的发展,存储行业的标准化进程也不断推进,分布式存储优先采用行业标准接口(SMI-S或OpenStack Cinder)进行存储接入。在平台层面,通过将异构存储资源进行抽象化,将传统的存储设备级的操作封装成面向存储资源的操作,从而简化异构存储基础架构的操作,以实现存储资源的集中管理,并能够自动执行创建、变更、回收等整个存储生命周期流程。基于异构存储整合的功能,用户可以实现跨不同品牌、介质地实现容灾,如用中低端阵列为高端阵列容灾,用不同磁盘阵列为闪存阵列容灾等等,从侧面降低了存储采购和管理成本。

这篇博客主要来总结一下分布式存储系统的历史,发展以及特性,从而对分布式存储系统有一个大概的了解,主要从一下几个部分来介绍分布式存储:

  1. 分布式存储概念
  2. 分布式文件系统的发展
  3. 分布式存储系统的分类
  4. 分布式存储系统的特性

分布式存储概念

分布式存储系统顾名思义就是将大量的普通服务器,通过网络互联,对外作为一个整体提供存储服务。具有可扩展性、可用性、可靠性、 高性能、易维护、低成本等特性。

分布式文件系统的发展

分布式文件系统发展

**80年代 **

  • 代表:AFS、NFS、Coda
  • AFS:1983年 Carnegine Mellon大学和IBM共同合作开发Andrew文件系统(Andrew File System, AFS),AFS设计目标是将至少7000个工作站连接起来,为每个用户提供一个共享的文件系统,将高扩展性、网络安全性放在首位,客户端高速缓存,即使网络断开,可以对部分数据缓存。
  • NFS:1985年Sun公司基于UDP开发了网络共享文件系统(Network File System, NFS),NFS由一系列NFS命令和进程组成的客户机/服务器(C/S)模式。NFS第三版,加入了基于TCP传输,第三版发布六年后,NFS成为Linux中的稳定版本。
  • Coda:1987年 Carnegine Mellon大学在基于AFS的基础上开发了Coda文件系统,它为Linux工作站组成的大规模分布式计算环境设计的文件系统,通过两种互补机制为服务器和网络故障提供了容错机制,服务器复制机制,一个文件拷贝到多个服务器上,无连接操作机制,将缓存端暂时作为服务端的执行模式,Coda注重可靠性和性能优化,它提供了高度的一致性。

**90年代 **

  • 代表:xFS、Tiger Shark 、SFS…
  • 背景:进入九十年代,随着Windows的问世,极大促进了微处理器的发展和PC的广泛普及,互联网和多媒体技术也犹如雨后春笋般发展起来,一方面:对多媒体数据的实时传输需和应用越来越流行,另一方面:大规模并行计算技术的发展和数据挖掘技术应用,迫切需要能支持大容量和高速的分布式存储系统。
  • xFS:UC Berkeley参照当时高性能多处理器领域的设计思想开发了xFS文件系统,xFS克服了以往分布式文件系统只适用局域网,而不适用于广域网和大数据存储问题,提出广域网进行缓存较少网络流量设计思想,采用层次命名结构,减少Cache一致性状态和无效写回Cache一致性协议,从而减少了网络负载。

**20世纪末 **

  • 代表:SAN、NAS、GFS、HDFS、GPFS…
  • 背景:到了二十世纪末,计算机技术和网络技术得到了飞速发展,磁盘存储成本不断降低,磁盘容量和数据总线带宽的增长速度无法满足应用需求,海量数据的存储逐渐成为互联网技术发展急需解决的问题,对于分布式存储系统技术的研究越来越成熟,基于光纤通道的存储区域网络(Storage Area Network)技术和网络附连存储(Network Attached Storage)技术得到了广泛应用。
  • SAN:设计目标是通过将磁盘存储系统或者磁带机和服务器直接相连的方式提供一个易扩展、高可靠的存储环境,高可靠的光纤通道交换机和光纤通道网络协议保证各个设备间链接的可靠性和高效性,设备间的连接接口主要是采用FC或者SCSI,光纤通道交换机主要是为服务器和存储设备的链接提供一个称为“SAN fabric”的网状拓扑结构。

SAN

  • NAS:通过基于TCP/IP协议的各种上层应用(NFS等)在各工作站和服务器之间进行文件访问,直接在工作站客户端和NAS文件共享设备之间建立连接,NAS隐藏了文件系统的底层实现,注重上层的文件服务实现,具有良好扩展性,网络阻塞,NAS性能受影响。

NAS

  • GFS:Google为大规模分布式数据密集型应用设计的可扩展的分布式文件系统,Google将一万多台廉价PC机连接成一个大规模的Linux集群,它具有高性能,高可靠性,易扩展性,超大存储容量等优点。Google文件系统采用单Master多Chunk Server来实现系统间的交互,Master中主要保存命名空间到文件映射、文件到文件块的映射、文件块到Chunk Server的映射,每个文件块对应到3个Chunk Server。

GFS

**现在 **

  • 代表:HBase、Cassadra、MongoDB、DynamoDB…
  • HBase:列存储数据库,擅长以列为单位读取数据,面向列存储的数据库具有高扩展性,即使数据大量增加也不会降低相应的处理速度,特别是写入速度。
  • MongoDB:文档型数据库它同键值(Key-Value)型的数据库类似,键值型数据库的升级版,允许嵌套键值,Value值是结构化数据,数据库可以理解Value的内容,提供复杂的查询,类似于RDBMS的查询条件。
  • DynamoDB:Amazon 公司的一个分布式存储引擎,是一个经典的分布式Key-Value 存储系统,具备去中心化,高可用性,高扩展性的特点,达到这个目标在很多场景中牺牲了一致性,Dynamo在Amazon中得到了成功的应用,能够跨数据中心部署于上万个结点上提供服务,它的设计思想也被后续的许多分布式系统借鉴。

分布式存储系统的特性:

  • 高可用性:指分布式存储系统在面对各种异常时可以提供正常服务的能力,系统的可用性可以用系统停服务的时间和正常服务时间的比例来衡量,例如4个99的可用性(99.99%)要求一年停机的时间不能超过3652460/10000 = 53分钟。

  • **高可靠性:**重点指分布式系统数据安全方面的指标,数据可靠不丢失,主要用多机冗余、单机磁盘RAID等措施。

  • 高扩展性:指分布式存储系统通过扩展集群服务器规模从而提高系统存储容量、计算和性能的能力,业务量增大,对底层分布式存储系统的性能要求越来越高,自动增加服务器来提升服务能力,分为Scale Up与Scale Out,前者指通过增加和升级服务器硬件,或者指通过增加服务器数量。衡量可扩展性的要求集群具有线性的可扩展性,系统整体性能与服务器数量呈线性关系。

  • 数据一致性:分布式存储系统多个副本之间的数据一致性,有强一致性,弱一致性,最终一致性,因果一致性,顺序一致性。

  • 高安全性:指分布式存储系统不受恶意访问和攻击,保护存储数据不被窃取,互联网是开放的,任何人在任何时间任何地点通过任何方式都可以访问网站,针对现存的和潜在的各种攻击与窃取手段,要有相应的应对方案。

  • 高性能:衡量分布式存储系统性能常见的指标是系统的吞吐量和系统的响应延迟,系统的吞吐量是在一段时间内可以处理的请求总数,可以用QPS(Query Per Second)和TPS(Transaction Per second)衡量。系统的响应延迟是指某个请求发出到接收到返回结果所消耗的时间,通常用平均延迟来衡量。这两个指标往往是矛盾的,追求高吞吐量,比较难做到低延迟,追求低延迟,吞吐量会受影响。

  • 高稳定性:这是一个综合指标,考核分布式 存储系统的整体健壮性,任何异常,系统都能坦然面对,系统稳定性越高越好。

一:存储类型

一般情况下,我们将存储分成了4种类型,基于本机的DAS和网络的NAS存储、SAN存储、对象存储。对象存储是SAN存储和NAS存储结合后的产物,汲取了SAN存储和NAS存储的优点。

我们来了解一下应用是怎么样获取它想要的存在存储里的某个文件信息,并用大家熟悉的Windows来举例,如图1。

  • 1、应用会发出一个指令“读取本目录下的readme.txt 文件的前1K数据”。
  • 2、通过内存通信到目录层,将相对目录转换为实际目录,“读取C:\ test\readme.txt文件前1K数据”
  • 3、通过文件系统,比如FAT32,通过查询文件分配表和目录项,获取文件存储的LBA地址位置、权限等信息。

文件系统先查询缓存中有没有数据,如果有直接返回数据;没有,文件系统通过内存通信传递到下一环节命令“读取起始位置LBA1000,长度1024的信息”。

  • 4、卷(LUN)管理层将LBA地址翻译成为存储的物理地址,并封装协议,如SCSI协议,传递给下一环节。
  • 5、磁盘控制器根据命令从磁盘中获取相应的信息。

如果磁盘扇区大小是4K,实际一次I/O读取的数据是4K,磁头读取的4K数据到达服务器上的内容后,有文件系统截取前1K的数据传递给应用,如果下次应用再发起同样的请求,文件系统就可以从服务器的内存中直接读取。

不管是DAS、NAS还是SAN,数据访问的流程都是差不多的。DAS将计算、存储能力一把抓,封装在一个服务器里。大家日常用的电脑,就是一个DAS系统,如图1。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果将计算和存储分离了,存储成为一个独立的设备,并且存储有自己的文件系统,可以自己管理数据,就是NAS,如图2。

计算和存储间一般采用以太网络连接,走的是CIFS或NFS协议。服务器们可以共享一个文件系统,也就是说,不管服务器讲的是上海话还是杭州话,通过网络到达NAS的文件系统,都被翻译成为普通话。

所以NAS存储可以被不同的主机共享。服务器只要提需求,不需要进行大量的计算,将很多工作交给了存储完成,省下的CPU资源可以干更多服务器想干的事情,即计算密集型适合使用NAS。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图3

计算和存储分离了,存储成为一个独立的设备,存储只是接受命令不再做复杂的计算,只干读取或者写入文件2件事情,叫SAN,如图3。

因为不带文件系统,所以也叫“裸存储”,有些应用就需要裸设备,如数据库。存储只接受简单明了的命令,其他复杂的事情,有服务器端干了。再配合FC网络,这种存储数据读取/写入的速度很高。

但是每个服务器都有自己的文件系统进行管理,对于存储来说是不挑食的只要来数据我就存,不需要知道来的是什么,不管是英语还是法语,都忠实记录下来的。

但是只有懂英语的才能看懂英语的数据,懂法语的看懂法语的数据。所以,一般服务器和SAN存储区域是一夫一妻制的,SAN的共享性不好。当然,有些装了集群文件系统的主机是可以共享同一个存储区域的。

从上面分析,我们知道,决定存储的快慢是由网络和命令的复杂程度决定的。

内存通信速度>总线通信>网络通信

网络通信中还有FC网络和以太网络。FC网络目前可以实现8Gb/s,但以太网络通过光纤介质已经普及10Gb/s,40Gb/s的网卡也在使用了。也就是说传统以太网络已经不是存储的瓶颈了。除了FCSAN,IPSAN也是SAN存储的重要成员。

对存储的操作,除了熟悉的读/写以外,其实还有创建、打开、获取属性、设置属性、查找等等。

对于有大脑的SAN存储来说,除了读/写以外的命令,都可以在本地内存中完成,速度极快。

而NAS存储缺乏大脑,每次向存储传递命令,都需要IP封装并通过以太网络传递到NAS服务器上,这个速度就远远低于内存通信了。

  • DAS特点是速度最快,但只能自己用;
  • NAS的特点速度慢点但共享性好;
  • SAN的特点是速度快,但共享性差。

总体上来讲,对象存储同兼具SAN高速直接访问磁盘特点及NAS的分布式共享特点。

NAS存储的基本单位是文件,SAN存储的基本单位是数据块,而对象存储的基本单位是对象,对象可以认为是文件的数据+一组属性信息的组合,这些属性信息可以定义基于文件的RAID参数、数据分布和服务质量等。

采取的是“控制信息”和“数据存储”分离的模式,客户端用对象ID+偏移量作为读写的依据,客户端先从“控制信息”获取数据存储的真实地址,再直接从“数据存储”中访问。

对象存储大量使用在互联网上,大家使用的网盘就是典型的对象存储。对象存储有很好的扩展性,可以线性扩容。并可以通过接口封装,还可以提供NAS存储服务和SAN存储服务。

VMware的vSAN本质就是一个对象存储。分布式对象存储就是SRVSAN的一种,也存在安全隐患。因为这个隐患是X86服务器带来的。

二:文件系统

计算机的文件系统是管理文件的“账房先生”。

  • 首先他要管理仓库,要知道各种货物都放在哪里;
  • 然后要控制货物的进出,并要确保货物的安全。

如果没有这个“账房先生”,让每个“伙计”自由的出入仓库,就会导致仓库杂乱无章、货物遗失。

就像那年轻纺城机房刚启用的时候,大家的货物都堆在机房里,没有人统一管理,设备需要上架的时候,到一大堆货物中自行寻找,安装后的垃圾也没有人打扫,最后连堆积的地方都找不到,有时自己的货物找不到了,找到别人的就使用了……。

大家都怨声载道,后来建立了一个仓库,请来了仓库管理员,用一本本子记录了货物的归宿和存储的位置,建立货物的出入库制度,问题都解决了,这就是文件系统要做的事情。

文件系统管理存取文件的接口、文件的存储组织和分配、文件属性的管理(比如文件的归属、权限、创建事件等)。

**每个操作系统都有自己的文件系统。**比如windows就有常用的FAT、FAT32、NTFS等,Linux用ext1-4的等。

存储文件的仓库有很多中形式,现在主要用的是(机械)磁盘、SSD、光盘、磁带等等。

拿到这些介质后,首先需要的是“格式化”,格式化就是建立文件存储组织架构和“账本”的过程。比如将U盘用FAT32格式化,我们可以看到是这样架构和账本(如图4):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图4

**主引导区:**记录了这个存储设备的总体信息和基本信息。比如扇区的大小,每簇的大小、磁头数、磁盘扇区总数、FAT表份数、分区引导代码等等信息。

分区表:,即此存储的账本,如果分区表丢失了,就意味着数据的丢失,所以一般就保留2份,即FAT1和FAT2。分区表主要记录每簇使用情况,当这位置的簇是空的,就代表还没有使用,有特殊标记的代表是坏簇,位置上有数据的,是指示文件块的下一个位置。

**目录区:**目录和记录文件所在的位置信息。

**数据区:**记录文件具体信息的区域。

通过以下的例子来帮助理解什么是FAT文件系统。

假设每簇8个扇区组成一个簇,大小是512*8=4K。根目录下的readme.txt文件大小是10K,如图5:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图5

  • 1、在目录区找到根目录下文件readme.txt在FAT表中的位置是0004
  • 2、在0004位置对应簇的8个扇区读取相应文件块readme(1)保存在内存,并获取下一个数据块的位置0005。
  • 3、在0005位置对应簇的8个扇区读取相应文件块readme(2)保存在内存,并获取下一个数据块的位置0008。
  • 4、在0005位置对应簇的4个扇区读取相应文件块readme(3)保存在内存,并获得结束标志。
  • 5、将readme(1)、readme(2)、readme(3)组合成为readme文件。

在这个例子中,我们看到在FAT文件系统,是通过查询FAT表和目录项来确定文件的存储位置,文件分布是以簇为单位的数据块,通过“链条”的方式来指示文件数据保存的文字。

当要读取文件时,必须从文件头开始读取。这样的方式,读取的效率不高。

不同的Linux文件系统大同小异,一般都采取ext文件系统,如图6.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图6

启动块内是服务器开机启动使用的,即使这个分区不是启动分区,也保留。

超级块存储了文件系统的相关信息,包括文件系统的类型,inode的数目,数据块的数目

Inodes块是存储文件的inode信息,每个文件对应一个inode。包含文件的元信息,具体来说有以下内容:

文件的字节数

文件拥有者的User ID

文件的Group ID

文件的读、写、执行权限

文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。

链接数,即有多少文件名指向这个inode

文件数据block的位置

当查看某个目录或文件时,会先从inode table中查出文件属性及数据存放点,再从数据块中读取数据。

数据块:存放目录和文件数据。

通过读取\var\readme.txt文件流程,来理解ext文件系统,如图7。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图7

  • 1、根目录A所对应的inode节点是2,inode1对应的数据块是d1。
  • 2、在检索d1内容发现,目录var对应的inode=28,对应的数据块是d5。
  • 3、检索d5内容发现readme.txt对应的是inode=70。
  • 4、Inode70指向数据区d2、d3、d6块。读取这些数据块,在内存中组合d2、d3、d6数据块。

硬盘格式化的时候,操作系统自动将硬盘分成两个区域。

  • 一个是数据区,存放文件数据;
  • 另一个是inode区,存放inode所包含的信息。

当inode资源消耗完了,尽管数据区域还有空余空间,都不能再写入新文件。

总结:Windows的文件系统往往是“串行”的,而linux的文件系统是“并行”的。

再来看分布式的文件系统。

如果提供持久化层的存储空间不是一台设备,而是多台,每台之间通过网络连接,数据是打散保存在多台存储设备上。也就是说元数据记录的不仅仅记录在哪块数据块的编号,还要记录是哪个数据节点的。

这样,元数据需要保存在每个数据节点上,而且必须实时同步。做到这一点其实很困难。如果把元数据服务器独立出来,做成“主从”架构,就不需要在每个数据节点维护元数据表,简化了数据维护的难度,提高了效率。

Hadoop的文件系统HDFS就是一个典型的分布式文件系统。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图8

  • 1、Client将FileA按64M分块。分成两块,block1和Block2。
  • 2、Client向nameNode发送写数据请求,如图紫色虚线1。
  • 3、NameNode节点,记录block信息。并返回可用的DataNode给客户端,如图红色虚线2。

Block1: host11,host22,host31

Block2: host11,host21,host32

  • 4、client向DataNode发送block1;发送过程是以流式写入。

流式写入过程:

1)将64M的block1按64k的package划分;

2)然后将第一个package发送给host11;

3)host11接收完后,将第一个package发送给host22,同时client想host11发送第二个package;

4)host22接收完第一个package后,发送给host31,同时接收host11发来的第二个package。

5)以此类推,如图黑色虚线3所示,直到将block1发送完毕。

6)host11,host22,host31向NameNode和 Client发送通知,说“消息发送完了”。

7)client收到发来的消息后,向namenode发送消息,说我写完了。这样就真完成了。

8)发送完block1后,再向host11,host21,host32发送block2,如图蓝色虚线4所示。

……….

HDFS是分布式存储的雏形,分布式存储将在以后详细介绍。

三:存储介质

仓库有很多种存储的介质,现在最常用的是磁盘和SSD盘,还有光盘、磁带等等。磁盘一直以性价比的优势占据了霸主的地位。

圆形的磁性盘片装在一个方的密封盒子里,运行起来吱吱的响,这就是我们常见的磁盘。磁片是真正存放数据的介质,每个磁片正面和背面上都“悬浮”着磁头。

磁盘上分割为很多个同心圆,每个同心圆叫做磁道,每个磁道又被分割成为一个个小扇区,每个扇区可以存储512B的数据。当磁头在磁片上高速转动和不停换道,来读取或者写入数据。

其实磁片负责高速转动,而磁头只负责在磁片上横向移动。决定磁盘性能的主要是磁片的转速、磁头的换道、磁盘、每片磁片的容量和接口速度决定的。转速越高、换道时间越短、单片容量越高,磁盘性能就越好。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图9

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图10

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图11

衡量磁盘性能主要参考 IOPS 和吞吐量两个参数。

IOPS就是一秒钟内磁盘进行了多少次的读写。

吞吐量就是读出了多少数据。

其实这些指标应该有前提,即是大包(块)还是小包(块),是读还是写,是随机的还是连续的。一般我们看到厂家给的磁盘IOPS性能一般是指小包、顺序读下的测试指标。这个指标一般就是最大值。

目前在X86服务器上我们常使用的 SATA、SAS磁盘性能:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图12

实际生产中估算,SATA 7200转的磁盘,提供的IOPS为60次左右,吞吐量在70MB/s。

我们2014年首次使用的裸容量2P的SRVSAN存储的数据持久化层采用57台X86服务器,内置12块SATA7200 3TB硬盘。共684块磁盘,大约只提供41040次IOPS和47.88GB/s。

这些指标显然是不能满足存储需要的,需要想办法“加速”。

机械磁盘其实也做了很多优化,比如扇区地址的编号不是连续的。

因为磁片转的够快(7200转/分钟即1秒钟转120转,转一圈是8.3毫秒,也就是在读写同一个磁道最大时延是8.3秒),防止磁头的读写取错过了,所以扇区的地址并不是连续的,而是跳跃编号的,比如2:1的交叉因子(1、10、2、11、3、12……)。

同时磁盘也有缓存,具有队列,并不是来一个I/O就读写一个,而是积累到一定I/O,根据磁头的位置和算法完成的。I/O并不是一定是“先到先处理”,而是遵守效率。

**加速最好的办法就是使用SSD盘。**磁盘的控制部分是由机械部分+控制电路来构成,机械部分的速度限制,使磁盘的性能不可能有大的突破。而SSD采用了全电子控制可以获得很好的性能。

SSD是以闪存作为存储介质再配合适当的控制芯片组成的存储设备。目前用来生产固态硬盘的NAND Flash有三种:

单层式存储(SLC,存储1bit数据)

二层式存储(MLC,存储4bit数据)

三层式存储(TLC,存储8bit数据)

SLC成本最高、寿命最长、但访问速度最快,TLC成本最低、寿命最短但访问速度最慢。为了降低成本,用于服务器的企业级SSD都用了MLC,TLC可以用来做U盘。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图13

SSD普及起来还有一点的障碍,比如成本较高、写入次数限制、损坏时的不可挽救性及当随着写入次数增加或接近写满时候速度会下降等缺点。

对应磁盘的最小IO单位扇区,page是SSD的最小单位。

比如每个page存储512B的数据和218b的纠错码,128个page组成一个块(64KB),2048个块,组成一个区域,一个闪存芯片有2个区域组成。Page的尺寸越大,这个闪讯芯片的容量就越大。

但是SSD有一个坏习惯,就是在修改某1个page的数据,会波及到整块。需要将这个page所在的整块数据读到缓存中,然后再将这个块初始化为1,再从缓存中读取数据写入。

对于SSD来说,速度可能不是问题,但是写的次数是有限制的,所以块也不是越大越好。当然对于机械磁盘来说也存在类似问题,块越大,读写的速度就越快,但浪费也越严重,因为写不满一块也要占一块的位置。

不同型号不同厂家的SSD性能差异很大,下面是我们的分布式块存储作为缓存使用的SSD参数:

采用PCIe 2.0接口,容量是1.2T,综合读写IOPS(4k小包)是260000次,读吞吐量1.55GB/s,写吞吐量1GB/s。

在1台SRVSAN的服务器配置了一块SSD作为缓存和12块7200转 3T SATA盘,磁盘只提供1200次、1200M的吞出量。

远远小于缓存SSD提供的能力,所以直接访问缓存可以提供很高的存储性能,SRVSAN的关键是计算出热点数据的算法,提高热点数据的命中率。

用高成本的SSD做为缓存,用廉价的SATA磁盘作为容量层。

分布式存储分类:

数据类型三大类:

  1. 非结构化数据:指其字段长度不等,并且每个字段的记录又可以由可重复或不可重复的子字段构成,没有规律,比如文本、图像、声音、影视等等。
  2. 半结构化数据:介于完全结构化数据(如关系型数据库、面向对象数据库中的数据)和完全无结构的数据(如声音、图像文件等)之间的数据,HTML文档就属于半结构化数据。它一般是自描述的,数据的结构和内容混在一起,没有明显的区分。
  3. 结构化数据:结构化数据即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据,数据模式和内容是分开的,数据的模式需要预先定义。

**分布式存储类型 **

  1. 分布式文件系统:存储大量的文件、图片、音频、视频等非结构化数据,这些数据以对象的形式组织,对象之间没有关系,这数据都是二进制数据,例如GFS、HDFS等。
  2. 分布式Key-Value系统:用于存储关系简单的半结构化数据,提供基于Key的增删改查操作,缓存、固化存储,例如Memached、Redis、DynamoDB等。
  3. 分布式数据库系统: 存储结构化数据,提供SQL关系查询语言,支持多表关联,嵌套子查询等,例如MySQL Sharding集群、MongoDB等等。

主流分布式存储技术的对比分析与应用

随着数字化转型的深入,海量数据对存储提出了新的要求。传统存储虽然有技术成熟、性能良好、可用性高等优点,但面对海量数据,其缺点也越来越明显:如扩展性差、成本高等。为了克服上述缺点,满足海量数据的存储需求,市场上出现了分布式存储技术。

分布式存储系统,通常包括主控服务器、存储服务器,以及多个客户端组成。其本质是将大量的文件,均匀分布到多个存储服务器上。

当前,分布式存储有多种实现技术,如HDFS、Ceph、GFS、Switf等。在实际工作中,为了更好地引入分布式存储技术,我们需了解各种分布式存储技术的特点,以及各种技术的适用场景。

本文拟通过对Ceph、HDFS、Swift、GFS、Luster等几种主流的分布式存储技术实现原理的阐述,并总结其各自的特点和合适的使用场景,以帮助架构师在进行存储架构规划时,选择合适的存储技术。

关键词:传统存储技术 分布式存储技术 块存储 文件存储 对象存储 Ceph GFS HDFS Swift Luster

存储根据其类型,可分为块存储,对象存储和文件存储。在主流的分布式存储技术中,HDFS/GPFS/GFS属于文件存储,Swift属于对象存储,而Ceph可支持块存储、对象存储和文件存储,故称为统一存储。

CEPh:

Ceph简介:

Ceph最早起源于Sage就读博士期间的工作、成果于2004年发表,并随后贡献给开源社区。经过多年的发展之后,已得到众多云计算和存储厂商的支持,成为应用最广泛的开源分布式存储平台。
Ceph根据场景可分为对象存储、块设备存储和文件存储。Ceph相比其它分布式存储技术,其优势点在于:它不单是存储,同时还充分利用了存储节点上的计算能力,在存储每一个数据时,都会通过计算得出该数据存储的位置,尽量将数据分布均衡。同时,由于采用了CRUSH、HASH等算法,使得它不存在传统的单点故障,且随着规模的扩大,性能并不会受到影响。

CEPH网站: https://ceph.com/

Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。

Ceph特点:
  • Ceph支持对象存储、块存储和文件存储服务,故 称为统一存储。
  • 采用CRUSH算法,数据分布均衡,并行度高,不需要维护固定的元数据结构;
  • 数据具有强一致,确保所有副本写入完成才返回确认,适合读多写少场景;
  • 去中心化,MDS之间地位相同,无固定的中心节点

1、具有高扩展、高可用、高性能的特点

  • 高性能
    • 摒弃了传统的集中式存储元数据寻址的方案,采用CRUSH算法,数据分布均衡,并行度高。 b.考虑了容灾域的隔离,能够实现各类负载的副本放置规则,例如跨机房、机架感知等。
    • 能够支持上千个存储节点的规模,支持TB到PB级的数据。
  • 高可用性
    • 副本数可以灵活控制。
    • 支持故障域分隔,数据强一致性。
    • 多种故障场景自动进行修复自愈。
    • 没有单点故障,自动管理。

2、高可扩展性

  • 去中心化。
  • 扩展灵活。
  • 随着节点增加而线性增长。

3、特性丰富

  • Ceph可以提供对象存储、块存储、文件系统存储
  • 支持自定义接口,支持多种语言驱动。

4、Ceph可以提供PB级别的存储空间(PB→TB→GB)

5、软件定义存储(Software Defined Storage)作为存储行业的一大发展趋势,已经越来越受到市场的认可

6、过半原则:必须有一半以上的服务器正常,才能使用,搭建集群通常用奇数

Ceph存在一些缺点:

去中心化的分布式解决方案,需要提前做好规划设计,对技术团队的要求能力比较高。
Ceph扩容时,由于其数据分布均衡的特性,会导致整个存储系统性能的下降

Ceph的主要架构:

Ceph提供了RADOS、OSD、MON、Librados、RBD、RGW和Ceph FS等功能组件,但其底层仍然使用RADOS存储来支撑上层的那些组件;
核心组件:Ceph OSD,Ceph Monitor,Ceph MDS。

Ceph OSD:全称是Object Storage Device,主要功能包括存储数据,处理数据的复制、恢复、回补、平衡数据分布,并将一些相关数据提供给Ceph Monitor;

Ceph Monitor:Ceph的监控器,主要功能是维护整个集群健康状态,提供一致性的决策,包含了Monitor map、OSD map、PG(Placement Group)map和CRUSH map;

Ceph MDS:全称是Ceph Metadata Server,主要保存的是Ceph文件系统(File System)的元数据(metadata);

Managers: Ceph Manager守护进程(ceph-mgr)负责跟踪运行时指标和Ceph集群的当前状态,包括存储利用率,当前性能指标和系统负载。Ceph Manager守护进程还托管基于python的插件来管理和公开Ceph集群信息,包括基于Web的Ceph Manager Dashboard和 REST API。高可用性通常至少需要两个管理器。

提示:Ceph的块存储和Ceph的对象存储都不需要Ceph MDS。Ceph MDS为基于POSIX文件系统的用户提供了一些基础命令,例如ls、find等命令。

提供对象存储RADOSGW(Reliable 可靠的、Autonomic、Distributed 分布式、Object Storage Gateway)、块存储RBD(Rados Block Device)、文件系统存储Ceph FS(Ceph File System)

这里写图片描述

LIBRADOS模块是客户端用来访问RADOS对象存储设备的
RADOSGW功能特性基于LIBRADOS之上,提供当前流行的RESTful协议的网关,并且兼容,S3和Swift接口,作为对象存储,可以对接网盘类应用以及HLS流媒体应用等
RBD(Rados Block Device)功能特性也是基于LIBRADOS之上,通过LIBRBD创建一个块设备,通过QEMU/KVM附加到VM上,作为传统的块设备来用。目前OpenStack、CloudStack等,都是采用这种方式来为VM提供块设备,同时也支持快照、COW(Copy On Write)等功能
Ceph FS(Ceph File System)功能特性是基于RADOS来实现分布式的文件系统,引入了MDS(Metadata Server),主要为兼容POSIX文件系统提供元数据。一般都是当做文件系统来挂载。

在这里插å
¥å›¾ç‰‡æè¿°

Ceph的最底层是RADOS(分布式对象存储系统),它具有可靠、智能、分布式等特性,实现高可靠、高可拓展、高性能、高自动化等功能,并最终存储用户数据。RADOS系统主要由两部分组成,分别是OSD和Monitor。
RADOS之上是LIBRADOS,LIBRADOS是一个库,它允许应用程序通过访问该库来与RADOS系统进行交互,支持多种编程语言,比如C、C++、Python等。
基于LIBRADOS层开发的有三种接口,分别是RADOSGW、librbd和MDS。
RADOSGW是一套基于当前流行的RESTFUL协议的网关,支持对象存储,兼容S3和Swift。
librbd提供分布式的块存储设备接口,支持块存储。
MDS提供兼容POSIX的文件系统,支持文件存储。

Ceph的功能模块:

在这里插å
¥å›¾ç‰‡æè¿°

Ceph的核心组件包括Client客户端、MON监控服务、MDS元数据服务、OSD存储服务,各组件功能如下:

  • Client客户端:负责存储协议的接入,节点负载均衡
  • MON监控服务:负责监控整个集群,维护集群的健康状态,维护展示集群状态的各种图表,如OSD Map、Monitor Map、PG Map和CRUSH Map
  • MDS元数据服务:负责保存文件系统的元数据,管理目录结构
  • OSD存储服务:主要功能是存储数据、复制数据、平衡数据、恢复数据,以及与其它OSD间进行心跳检查等。一般情况下一块硬盘对应一个OSD。
Ceph的资源划分

Ceph采用crush算法,在大规模集群下,实现数据的快速、准确存放,同时能够在硬件故障或扩展硬件设备时,做到尽可能小的数据迁移,其原理如下:
当用户要将数据存储到Ceph集群时,数据先被分割成多个object,(每个object一个object id,大小可设置,默认是4MB),object是Ceph存储的最小存储单元。
由于object的数量很多,为了有效减少了Object到OSD的索引表、降低元数据的复杂度,使得写入和读取更加灵活,引入了pg(Placement Group ):PG用来管理object,每个object通过Hash,映射到某个pg中,一个pg可以包含多个object。
Pg再通过CRUSH计算,映射到osd中。如果是三副本的,则每个pg都会映射到三个osd,保证了数据的冗余。

在这里插å
¥å›¾ç‰‡æè¿°

Ceph的数据写入:

Ceph数据的写入流程

  1. 数据通过负载均衡获得节点动态IP地址;
  2. 通过块、文件、对象协议将文件传输到节点上;
  3. 数据被分割成4M对象并取得对象ID;
  4. 对象ID通过HASH算法被分配到不同的PG;
  5. 不同的PG通过CRUSH算法被分配到不同的OSD

在这里插å
¥å›¾ç‰‡æè¿°

RADOS架构:

基于RADOS,使用Ceph作为存储架构;RADOS系统主要由两个部分组成,对于RADOS系统,节点组织管理和数据分发策略均由内部的Mon全权负责;

1)OSD:由数目可变的大规模OSD(Object Storage Devices)组成的集群,负责存储所有的Objects数据。
2)Monitor:由少量Monitors组成的强耦合、小规模集群,负责管理Cluster Map。其中,Cluster Map是整个RADOS系统的关键数据结构,管理集群中的所有成员关系和属性等信息以及数据的分发。

(1)Monitor

Ceph Monitor是负责监视整个群集的运行状况的,这些信息都是由维护集群成员的守护程序来提供的,如各个节点之间的状态、集群配置信息。Ceph monitor map包括OSD Map、PG Map、MDS Map和CRUSH等,这些Map被统称为集群Map;

1)Monitor Map。Monitor Map包括有关monitor节点端到端的信息,其中包括Ceph集群ID,监控主机名和IP地址和端口号,它还存储了当前版本信息以及最新更改信息

#ceph mon dump

2)OSD Map。OSD Map包括一些常用的信息,如集群ID,创建OSD Map的版本信息和最后修改信息,以及pool相关信息,pool的名字、pool的ID、类型,副本数目以及PGP,还包括OSD信息,如数量、状态、权重、最新的清洁间隔和OSD主机信息。

#ceph osd dump

3)PG Map。PG Map包括当前PG版本、时间戳、最新的OSD Map的版本信息、空间使用比例,以及接近占满比例信息,同时,也包括每个PG ID、对象数目、状态、OSD的状态以及深度清理的详细信息

#ceph pg dump

4)CRUSH Map。CRUSH Map包括集群存储设备信息,故障域层次结构和存储数据时定义失败域规则信息;

#ceph osd crush dump 

5)MDS Map。MDS Map包括存储当前MDS Map的版本信息、创建当前Map的信息、修改时间、数据和元数据POOL ID、集群MDS数目和MDS状态;

#ceph mds dump

Ceph Monitor的主要作用是维护集群映射的主副本。Ceph Monitors还提供身份验证和日志记录服务。Ceph监视器将监视器服务中的所有更改写入单个Paxos实例,Paxos将更改写入键/值存储以实现强一致性。Ceph监视器可以在同步操作期间查询最新版本的集群映射。Ceph Monitors利用键/值存储的快照和迭代器(使用leveldb)来执行存储范围的同步

这里写图片描述

集群运行图是多个图的组合,包括监视器图、 OSD 图、归置组图和元数据服务器图。集群运行图追踪几个重要事件:哪些进程在集群里( in );哪些进程在集群里( in )是 up 且在运行、或 down ;归置组状态是 active 或 inactive 、 clean 或其他状态;和其他反映当前集群状态的信息,像总存储容量、和使用量。

Ceph在集群中与另一个Ceph Monitor时对Ceph监视器要严格的一致性要求,然而,Ceph客户端和其他Ceph守护进程使用Ceph配置文件来发现监视器,监视器使用监视器映射(monmap)发现彼此,而不是Ceph配置文件。
当Ceph存储集群中发现其他Ceph监视器时,Ceph监视器总是引用monmap的本地副本。使用 monmap而不是Ceph配置文件可以避免可能破坏集群的错误(例如,在ceph.conf指定监视器地址或端口时出现拼写错误)。由于监视器使用monmaps进行发现,并且它们与客户端和其他Ceph守护进程共享monmaps,因此monmap为监视器提供了一致的保证,即他们的共识是有效的。
严格的一致性也适用于monmap的更新。与Ceph Monitor上的任何其他更新一样,对monmap的更改始终通过名为Paxos的分布式一致性算法运行。Ceph监视器必须就monmap的每次更新达成一致,例如添加或删除Ceph监视器,以确保仲裁中的每个监视器都具有相同版本的monmap。monmap的更新是增量的,因此Ceph Monitors具有最新的商定版本和一组先前版本。维护历史记录使得具有旧版monmap的Ceph Monitor能够赶上Ceph存储集群的当前状态。

Monitor同步:

当你集群中使用多个Monitor时,各监视器都要检查邻居是否有集群运行图的最新版本的群集映射,(例如,相邻监视器中的一个映射,其中一个或多个纪元数字高于最大值即时监视器地图中的当前时期)。群集中的一个监视器可能会定期从其他监视器后面移动到必须离开仲裁的位置,同步以检索有关群集的最新信息,然后重新加入仲裁。出于同步的目的,监视器可以采用以下三种角色之一;
(1)Leader 领导者:领导者是第一台实现最新Paxos版本群集地图的监视器。
(2)Provider 提供者:提供者是具有最新版本群集映射的监视器,但不是第一个获得最新版本的监视器。
(3)Requester 请求者:一个请求者是下降落后领先者,并且必须以检索有关集群的最新信息,然后才能重新加入仲裁同步监控。
这些角色使领导者能够将同步职责委派给提供者,从而防止同步请求超载领导者改进性能。在下图中,请求者已经了解到它已落后于其他监视器。请求者要求领导者进行同步,领导者告诉请求者与提供者同步。
这里写图片描述

(2)OSD
Ceph OSD是Ceph存储集群最重要的组件,Ceph OSD将数据以对象的形式存储到集群中每个节点的物理磁盘上,完成存储用户数据的工作绝大多数都是由OSD deamon进程来实现的。
Ceph集群一般情况都包含多个OSD,对于任何读写操作请求,Client端从Ceph Monitor获取Cluster Map之后,Client将直接与OSD进行I/O操作的交互,而不再需要Ceph Monitor干预。这使得数据读写过程更为迅速。
Ceph的核心功能特性包括高可靠、自动平衡、自动恢复和一致性,
对于Ceph OSD而言,基
于配置的副本数,Ceph提供通过分布在多节点上的副本来实现,使得Ceph具有高可用性以及容错性。在OSD中的每个对象都有一个主副本,若干个从副本,这些副本默认情况下是分布在不同节点上的,这就是Ceph作为分布式存储系统的集中体现。每个OSD都可能作为某些对象的主OSD,与此同时,它也可能作为某些对象的从OSD,从OSD受到主OSD的控制,然而,从OSD在某些情况也可能成为主OSD。在磁盘故障时,Ceph OSD Deamon的智能对等机制将协同其他OSD执行恢复操作。在此期间,存储对象副本的从OSD将被提升为主OSD,与此同时,新的从副本将重新生成,这样就保证了Ceph的可靠和一致。

CRUSH:

对象存储中一致性Hash和Ceph的CRUSH算法是使用比较多的数据分布算法。在Aamzon的Dyanmo键值存储系统中采用一致性Hash算法,并且对它做了很多优化。OpenStack的Swift对象存储系统也使用了一致性Hash算法。
CRUSH(Controlled Replication Under Scalable Hashing)是一种基于伪随机控制数据分布、复制的算法。Ceph是为大规模分布式存储系统(PB级的数据和成百上千台存储设备)而设计的,在大规模的存储系统里,必须考虑数据的平衡分布和负载(提高资源利用率)、最大化系统的性能,以及系统的扩展和硬件容错等。
Ceph条带化之后,将获得N个带有唯一oid(即object的id)。Object id是进行线性映射生成的,即由file的元数据、Ceph条带化产生的Object的序号连缀而成。此时Object需要映射到PG中,该映射包括两部分。
1)由Ceph集群指定的静态Hash函数计算Object的oid,获取到其Hash值。
2)将该Hash值与mask进行与操作,从而获得PG ID
由PG映射到数据存储的实际单元OSD中,该映射是由CRUSH算法来确定的,将PG ID作为该算法的输入,获得到包含N个OSD的集合,集合中第一个OSD被作为主OSD,其他的OSD则依次作为从OSD。N为该PG所在POOL下的副本数目,在生产环境中N一般为3;OSD集合中的OSD将共同存储和维护该PG下的Object。需要注意的是,CRUSH算法的结果不是绝对不变的,而是受到其他因素的影响。其影响因素主要有以下两个。
一是当前系统状态 。也就是上文逻辑结构中曾经提及的Cluster Map(集群映射)。当系统中的OSD状态、数量发生变化时,Cluster Map可能发生变化,而这种变化将会影响到PG与OSD之间的映射。
二是存储策略配置 。这里的策略主要与安全相关。利用策略配置,系统管理员可以指定承载同一个PG的3个OSD分别位于数据中心的不同服务器乃至机架上,从而进一步改善存储的可靠性。

四、ceph数据的存储过程

这ceph数据的存储过程

Ceph FS文件系统:

Ceph FS需要使用Metadata Server(简称MDS)来管理文件系统的命名空间以及客户端如何访问到后端OSD数据存储中。MDS类似于ceph-mon,是一个服务进程,在使用Ceph FS前首先要安装和启动ceph-mds服务;MDS(Metadata Server)以一个Daemon进程运行一个服务,即元数据服务器,主要负责Ceph FS集群中文件和目录的管理,Ceph文件系统主要依赖MDS守护进程提供服务。

Ceph是一个可以按对象/块/文件方式存储的开源分布式文件系统,其设计之初,就将单点故障作为首先要解决的问题,因此该系统具备高可用性、高性能及可 扩展等特点。该文件系统支持目前还处于试验阶段的高性能文件系统BTRFS(B-Tree文件系统),同时支持按OSD方式存储,因此其性能是很卓越的, 因为该系统处于试商用阶段,需谨慎引入到生产环境

特性

1)Ceph底层存储是基于RADOS(可靠的、自动的分布式对象存储),它提供了LIBRADOS/RADOSGW/RBD/CEPH FS方式访问底层的存储系统,如下图所示
2)通过FUSE,Ceph支持类似的POSIX访问方式;Ceph分布式系统中最关键的MDS节点是可以部署多台,无单点故障的问题,且处理性能大大提升
3)Ceph通过使用CRUSH算法动态完成文件inode number到object number的转换,从而避免再存储文件metadata信息,增强系统的灵活性

优点

1)支持对象存储(OSD)集群,通过CRUSH算法,完成文件动态定位, 处理效率更高
2)支持通过FUSE方式挂载,降低客户端的开发成本,通用性高
3)支持分布式的MDS/MON,无单点故障
4)强大的容错处理和自愈能力5)支持在线扩容和冗余备份,增强系统的可靠性

缺点

1)目前处于试验阶段,系统稳定性有待考究

应用场景

1)全网分布式部署的应用
2)对实时性、可靠性要求比较高官方宣传,存储容量可轻松达到PB级别

源码路径:https://github.com/ceph/ceph

GFS:

GFS是google的分布式文件存储系统,是专为存储海量搜索数据而设计的,2003年提出,是闭源的分布式文件系统。适用于大量的顺序读取和顺序追加,如大文件的读写。注重大文件的持续稳定带宽,而不是单次读写的延迟。

GFS的主要架构:

GFS 架构比较简单,一个 GFS 集群一般由一个 master 、多个 chunkserver 和多个 clients 组成。
在 GFS 中,所有文件被切分成若干个 chunk,每个 chunk 拥有唯一不变的标识(在 chunk 创建时,由 master 负责分配),所有 chunk 都实际存储在 chunkserver 的磁盘上。
为了容灾,每个 chunk 都会被复制到多个 chunkserve。

GFS的功能模块:

在这里插å
¥å›¾ç‰‡æè¿°

  • GFS client客户端:为应用提供API,与POSIX API类似。同时缓存从GFS master读取的元数据chunk信息;
  • GFS master元数据服务器:管理所有文件系统的元数据,包括命令空间(目录层级)、访问控制信息、文件到chunk的映射关系,chunk的位置等。同时 master 还管理系统范围内的各种活动,包括chunk 创建、复制、数据迁移、垃圾回收等;
  • GFS chunksever存储节点:用于所有 chunk的存储。一个文件被分割为多个大小固定的chunk(默认64M),每个chunk有全局唯一的chunk ID。

GFS的写入流程

  1. Client 向 master 询问要修改的 chunk在哪个 chunkserver上,以及 该chunk 其他副本的位置信息。
  2. Master 将Primary、secondary的相关信息返回给 client。
  3. Client 将数据推送给 primary 和 secondary;。
  4. 当所有副本都确认收到数据后,client 发送写请求给 primary,primary 给不同 client 的操作分配序号,保证操作顺序执行。
  5. Primary 把写请求发送到 secondary,secondary 按照 primary 分配的序号顺序执行所有操作
  6. 当 Secondary 执行完后回复 primary 执行结果。
  7. Primary 回复 client 执行结果。

在这里插å
¥å›¾ç‰‡æè¿°

GFS特点:

  • 适合大文件场景的应用,特别是针对GB级别的大文件,适用于数据访问延时不敏感的搜索类业务
  • 中心化架构,只有1个master处于active状态
  • 缓存和预取,通过在client端缓存元数据,尽量减少与master的交互,通过文件的预读取来提升并发性能
  • 高可靠性,master需要持久化的数据会通过操作日志与checkpoint的方式存放多份,故障后master会自动切换重启。

GFS在进行写数据时,有如下特点:

  • GFS在数据读写时,数据流与控制流是分开的,并通过租约机制,在跨多个副本的数据写入中, 保障顺序一致性;
  • Master将chunk租约发放给其中一个副本,这个副本称为主副本,由主副本确定chunk的写入顺序,次副本则遵守这个顺序,这样就保障了全局顺序一致性
  • Master返回客户端主副本和次副本的位置信息,客户端缓存这些信息以备将来使用,只有当主副本所在chunkserver不可用或返回租约过期了,客户端才需要再次联系Master;
  • GFS采用链式推送,以最大化利用每个机器的网络带宽,避免网络瓶颈和高延迟连接,最小化推送延迟;
  • GFS使用TCP流式传输数据,以最小化延迟。

HDFS:

HDFS是一个分布式文件系统

HDFS(Hadoop Distributed File System),是一个适合运行在通用硬件(commodity hardware)上的分布式文件系统,是Hadoop的核心子项目,是基于流数据模式访问和处理超大文件的需求而开发的。该系统仿效了谷歌文件系统(GFS),是GFS的一个简化和开源版本。

HDFS简介:

HDFS是Google公司的 GFS论文 思想的实现,它由NameNode(名称节点)、DataNode(数据节点)、SecondaryNameNode(第二名称节点)组成。其中, NameNode 相当于论文中的 GFS Master , DataNode 相当于论文中的 GFS Chunk Server 。

HDFS 是 Hadoop Distribute File System 的简称,意为:Hadoop 分布式文件系统。是 Hadoop 核心组件之一,作为最底层的分布式存储服务而存在。

GFS 是一个可扩展的分布式文件系统设计思想,用于设计针对大型的、分布式的、对大量数据进行访问的文件系统。

HDFS的设计适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。

HDFS是基于 流数据 访问模式的 分布式文件系统 ,其设计建立在 “一次写入、多次读取” 的基础上,提供高吞吐量、高容错性的数据访问,能很好地解决海量数据的存储问题。

流数据是指数千个数据源 持续生成 的数据,可以理解为随时间延续而 无限增长 的动态数据集合。
通俗点说,如果把数据比如成一个水库,那么流进去的水,就是流数据(就像我们听的音乐,属于音乐流;而看到的文字、图片这些较为固定的,一次性下载的,形成不了流)。

HDFS产生背景:

随着数据量越来越大,在一个操作系统管辖的范围内存不下了,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种。

HDFS概念:

HDFS(Hadoop Distributed File System),它是一个分布式文件管理系统,用于存储文件,通过目录树来定位文件,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。

在Hadoop生态圈中,HDFS属于底层基础,负责存储文件。
在这里插å
¥å›¾ç‰‡æè¿°

文件块大小

为什么要把文件抽象为Block块存储?

  1. block的拆分使得单个文件大小可以大于整个磁盘的容量,构成文件的Block可以分布在整个集群, 理论上,单个文件可以占据集群中所有机器的磁盘。

  2. Block的抽象也简化了存储系统,对于Block,无需关注其权限,所有者等内容(这些内容都在文件级别上进行控制)。

  3. Block作为容错和高可用机制中的副本单元,即以Block为单位进行复制。

HDFS中的文件在物理内存中分块存储(Block),块的大小在Hadoop2.x版本中默认为128M,在老版本中为64M,那么为什么为128M呢?

其实,HDFS的块的大小的设置主要取决于磁盘传输速率,如下:

  1. 如果在HDFS中,寻址时间为10ms,即查找到目标Block的时间为10ms
  2. 专家说操作的最佳状态为:寻址时间为传输时间的1%,因此传输时间为1s
  3. 而目前磁盘的传输速率普遍为100M/s

为什么块大小不能设置太小,也不能设置太大?

  1. HDFS的块设置太小,会增加寻址时间,使得程序可能一直在寻找块的开始位置

  2. 如果设置的太大,从磁盘传输数据的时间会明显大于定位这个块所需的寻址时间,导致程序处理这块数据时会非常慢

HDFS的特点(Vs GFS):

  • 分块更大,每个数据块默认128MB;
  • 不支持并发,同一时刻只允许一个写入者或追加者;
  • 过程一致性,写入数据的传输顺序与最终写入顺序一致;
  • Master HA,2.X版本支持两个NameNode,(分别处于Active和Standby状态),故障切换时间一般几十秒到数分钟

HDFS 默认保存 3 份副本。

第一个副本:放置在 上传文件 的数据节点(第一个副本如果是在 集群外 提交,则随机挑选一个 CPU 比较空闲 、 磁盘不太满 的节点);
第二个副本:放置在与 第一个副本 不同 的机架的节点上;
第三个副本:放在与 第二个副本 相同 的机架的其他节点上。

HDFS的优点:
  1. 高容错性。提供了容错和恢复机制,副本丢失后,自动恢复。
  2. 高可靠性。数据自动保存多个副本,通过多副本提高可靠性。
  3. 适合大数据处理。可以处理超大文件,比如 TB级甚至PB级 的文件。
  4. 适合批处理。移动计算而非移动数据;数据位置暴露给计算框架。
  5. 支持流式数据访问。一次性写入,多次读取(一个数据集一旦生成,就会被复制分发到不同的存储节点,各节点可以进行读取/访问);保证数据一致性。
  6. 低成本运行。可以运行在低成本的硬件之上。

1)高容错性

(1)数据自动保存多个副本。它通过增加副本的形式,提高容错性;

(2)某一个副本丢失以后,它可以自动恢复。

2)适合大数据处理

(1)数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据;

(2)文件规模:能够处理百万规模以上的文件数量,数量相当之大。

3)可构建在廉价机器上。

HDFS的缺点:
  1. 不适合处理 低延迟 的数据访问。比如用户 要求时间比较短 的低延迟应用(主要处理高数据吞吐量的应用)。
  2. 不适合处理 大量的小 文件。会造成寻址时间超过读取时间;会占用NameNode大量内存,因为NameNode把文件系统的元数据存放在内存中(文件系统的容量由NameNode的大小决定),小文件太多会消耗NameNode的内存。
  3. 不适合 并发写入。一个文件只能有一个写入者,HDFS暂不支持多个用户对同一个文件的写操作。
  4. 不适合 任意修改 文件。仅支持append(附加),不支持在文件的任意位置进行修改。

1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。

2)无法高效的对大量小文件进行存储。

(1)存储大量小文件的话,它会占用NameNode大量的内存来存储文件、目录和块信息。这样是不可取的,因为NameNode的内存总是有限的;

(2)小文件存储的寻址时间会超过读取时间。

3)不支持并发写入、文件随机修改。

(1)一个文件只能有一个写,不允许多个线程同时写;

(2)仅支持数据append(追加),不支持文件的随机修改。

HDFS应用场景:

适合的应用场景:

  • 适用于大文件、大数据处理,处理数据达到 GB、TB、甚至PB级别的数据。
  • 适合流式文件访问,一次写入,多次读取。
  • 文件一旦写入不能修改,只能追加。

不适合的场景:

  • 低延时数据访问。
  • 小文件存储
  • 并发写入、文件随机修改

设计目标:

  • 存储非常大的文件:这里非常大指的是几百M、G、或者TB级别。实际应用中已有很多集群存储的数据达到PB级别。根据Hadoop官网,Yahoo!的Hadoop集群约有10万颗CPU,运行在4万个机器节点上。更多世界上的Hadoop集群使用情况,参考Hadoop官网.
  • 采用流式的数据访问方式: HDFS基于这样的一个假设:最有效的数据处理模式是一次写入、多次读取数据集经常从数据源生成或者拷贝一次,然后在其上做很多分析工作
    分析工作经常读取其中的大部分数据,即使不是全部。 因此读取整个数据集所需时间比读取第一条记录的延时更重要。
  • 运行于商业硬件上: Hadoop不需要特别贵的、reliable的(可靠的)机器,可运行于普通商用机器(可以从多家供应商采购) ,商用机器不代表低端机器。在集群中(尤其是大的集群),节点失败率是比较高的HDFS的目标是确保集群在节点失败的时候不会让用户感觉到明显的中断。

HDFS不适合的应用类型:

有些场景不适合使用HDFS来存储数据。下面列举几个:

1) 低延时的数据访问
对延时要求在毫秒级别的应用,不适合采用HDFS。HDFS是为高吞吐数据传输设计的,因此可能牺牲延时HBase更适合低延时的数据访问。

2)大量小文件
文件的元数据(如目录结构,文件block的节点列表,block-node mapping)保存在NameNode的内存中, 整个文件系统的文件数量会受限于NameNode的内存大小。
经验而言,一个文件/目录/文件块一般占有150字节的元数据内存空间。如果有100万个文件,每个文件占用1个文件块,则需要大约300M的内存。因此十亿级别的文件数量在现有商用机器上难以支持。

3)多方读写,需要任意的文件修改
HDFS采用追加(append-only)的方式写入数据。不支持文件任意offset的修改。不支持多个写入器(writer)。

HDFS架构:

在这里插å
¥å›¾ç‰‡æè¿°

  • HDFS Client(客户端):从NameNode获取文件的位置信息,再从DataNode读取或者写入数据。此外,client在数据存储时,负责文件的分割;
  • NameNode(元数据节点):管理名称空间、数据块(Block)映射信息、配置副本策略、处理客户端读写请求;
  • DataNode(存储节点):负责执行实际的读写操作,存储实际的数据块,同一个数据块会被存储在多个DataNode上
  • Secondary NameNode:定期合并元数据,推送给NameNode,在紧急情况下,可辅助NameNode的HA恢复。

1)Client:就是客户端。

(1)文件切分。文件上传HDFS的时候,Client将文件切分成一个一个的Block,然后进行存储;

(2)与NameNode交互,获取文件的位置信息;

(3)与DataNode交互,读取或者写入数据;

(4)Client提供一些命令来管理HDFS,比如启动或者关闭HDFS;

(5)Client可以通过一些命令来访问HDFS;

2)NameNode:就是Master,它是一个主管、管理者。

(1)管理HDFS的名称空间;namespace

(2)管理数据块(Block)映射信息;

(3)配置副本策略(默认);3

(4)处理客户端读写请求。

3) DataNode:就是Slave。NameNode下达命令,DataNode执行实际的操作。

(1)存储实际的数据块;

(2)执行数据块的读/写操作。

4) SecondaryNameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。

(1)辅助NameNode,分担其工作量;

(2)定期合并Fsimage和Edits,并推送给NameNode;

(3)在紧急情况下,可辅助恢复NameNode。

HDFS的组成架构图及各部分功能如下所示:

在这里插å
¥å›¾ç‰‡æè¿°

Client客户端:
  1. 文件的切分,在上传HDFS之前,client将文件切分为一个一个的Block,然后一个一个进行上传
  2. 与namenode交互,获取文件的datanode信息
  3. 与datanode交互,读取或写入数据
  4. client提供一些命令来管理HDFS,比如namenode的格式化
  5. client通过一些命令来访问HDFS,比如对HDFS的增删查改等
NameNode节点:

当用户访问数据文件时,为了保证能够读取到每一个数据块, HDFS有一个专门 负责保存文件属性信息的节点,这个节点就是 NameNode 节点(即 名称节点 )。

namenode(nn):就是Master,是一个管理者,存放元数据

  1. 管理HDFS的名称空间
  2. 配置副本策略
  3. 管理数据块的映射信息
  4. 处理客户端的读写请求

节点职责:
NameNode节点 是HDFS的管理者,负责保存和管理HDFS的元数据。

其职责有以下三个方面:
① 管理维护HDFS的命名空间
NameNode管理HDFS系统的命名空间,维护文件系统树以及文件系统树中所有文件的元数据。管理这些信息的的文件分别是 edits(操作日志文件) 和 fsimage(命名空间镜像文件) 。

Editlog(操作日志):在NameNode启动的情况下,对HDFS进行的各种操作进行记录。(HDFS客户端执行的所有操作都会被记录到editlog文件中,这些文件由edits文件保存)

fsimage:包含HDFS中的元信息(比如修改时间、访问时间、数据块信息等)。

② 管理DataNode上的数据块
负责管理数据块上所有的元数据信息(管理DataNode上数据块的均衡,维持副本数量)。

③ 接收客户端的请求
接收客户端文件上传、下载、创建目录等的请求。

DataNode节点:

HDFS首先把大文件切分成若干个小的数据块,再把这些数据块写入不同的节点,这个 负责保存文件数据的节点就是 DataNode 节点(即 数据节点 )。

datanode(dn):就是slave,真正存储文件的地方

  1. 存储实际的数据块
  2. 执行数据块的读写操作

节点职责:
DataNode节点 负责存储数据,把Block(数据块)以Linux文件的形式保存在磁盘上,并根据Block标识和字节范围来读写块数据。

其职责有以下三个方面:
① 保存数据块
一个数据块会在多个DataNode进行冗余备份(在某一个DataNode最多只有一个备份)。

② 负责客户端对数据块的IO请求
在客户端执行写操作时,DataNode之间会相互通信,保证写操作的一致性。

③ 定期和NameNode进行心跳通信,接受NameNode的指令
如果NameNode节点10分钟没有收到DataNode的心跳信息,就会将其上的数据块复制到其他DataNode节点。

因此,NameNode节点上并不会永久保存DataNode节点上的数据块信息,而是通过与DataNode节点心跳联系的方式,来更新节点上的映射表,以此减轻负担。

问题:HDFS数据块默认大小为128M(Hadoop2.2之前为64M),将HDFS的数据块设置得很大的目的是什么?(传统数据块只有512个字节)

答:为了减少寻址开销,让HDFS的文件传输时间由传输速率决定(如果块设置得足够大,从磁盘 传输数据的时间 会明显大于 定位这个块开始位置 所需的时间)。

SecondaryNameNode节点:

HDFS有一个定期创建命名空间的检查点(CheckPoint)操作的节点,也就是SecondaryNameNode节点(即 第二名称节点)。

出于可靠性考虑,SecondaryNameNode节点与NameNode节点通常运行在不同的机器上,且SecondaryNameNode节点与NameNode节点的内存要一样大。

(如果想了解 SecondaryNameNode 的工作流程,可以参考这篇文章:浅析 SecondaryNameNode 的工作流程 )

问题:一般情况下,一个集群中的SecondaryNameNode节点也是只有一个的原因是什么?

答:因为如果多的话,会增加NameNode的压力,使其忙于元数据的传输/接收、日志的传输/切换,从而导致性能下降;同时,NameNode节点也不支持做并发检查点。

secondarynamenode(2nn):并非namenode的热备,当namenode挂掉的时候,并不能马上替换namenode并提供服务

  1. 作为namenode的辅助,分担其工作量,比如定期合并Fsimage和Edits(文章后边会讲到这两个东西),并推送给namenode
  2. 在紧急情况下,可辅助恢复namenode,但是只能恢复部分,而不能全部恢复

节点职责:

SecondaryNameNode节点 定期把NameNode的 fsimage 和 edits 下载到本地,再将它们加载到内存并进行合并,最后把合并后新的 fsimage 返回NameNode (这个过程称为检查点)。

经典问题:NameNode与SecondaryNameNode有没有关系?

在这里插å
¥å›¾ç‰‡æè¿°

SecondaryNameNode节点的工作流程可以参考这篇文章:

其职责有以下两个方面:
① 防止edits过大
定期合并 fsimage 和 edits 文件,使 edits 大小保持在限制范围内。这样做减少了重新启动NameNode时合并 fsimage 和 edits 耗费的时间,从而减少了NameNode启动的时间。
在这里插å
¥å›¾ç‰‡æè¿°

② 做冷备份
对一定范围内数据做快照性备份,在NameNode失效时能恢复部分 fsimage 。

HDFS的Master-Slave结构

HDFS使用Master和Slave结构对集群进行管理。一般一个 HDFS 集群只有一个 Namenode 和一定数目的Datanode 组成。Namenode 是 HDFS 集群主节点,Datanode 是 HDFS 集群从节点,两种角色各司其职,共同协调完成分布式的文件存储服务。

在这里插å
¥å›¾ç‰‡æè¿°

1)HDFS集群包括,NameNode和DataNode以及Secondary Namenode。
2)NameNode负责管理整个文件系统的元数据,以及每一个路径(文件)所对应的数据块信息。
3)DataNode 负责管理用户的文件数据块,每一个数据块都可以在多个datanode上存储多个副本。
4)Secondary NameNode用来监控HDFS状态的辅助后台程序,每隔一段时间获取HDFS元数据的快照。
最主要作用是辅助namenode管理元数据信息

HDFS角色作用简介:

在这里插å
¥å›¾ç‰‡æè¿°

NameNode(Master)管理者 - 只负责管理,管理集群内各个节点。
SecondaryNameNode 辅助管理 – 只负责辅助NameNode管理工作。
DataNode(Slave) 工作者,是负责工作,周期向NameNode汇报,进行读写数据。

HDFS 分块存储:

HDFS 将所有的文件全部抽象成为block块来进行存储,不管文件大小,
全部一视同仁都是以block块的统一大小和形式进行存储,方便我们的分布式文件系统对文件的管理

所有的文件都是以block块的方式存放在HDFS文件系统当中,在Hadoop1当中,文件的block块默认大小是64M,
Hadoop2当中,文件的block块大小默认是128M,block块的大小可以通过hdfs-site.xml当中的配置文件进行指定

	<property>
        <name>dfs.block.size</name>
        <value>块大小 以字节为单位</value>//只写数值就可以
    </property>

在这里插å
¥å›¾ç‰‡æè¿°

数据超过128M,便进行切分,如果没有超过128M,就不用切分,有多少算多少,不足128M的也是一个快。这个快的大小就是100M,没有剩余28M这个概念。

抽象成数据块的好处

  1. 一个文件有可能大于集群中任意一个磁盘
    20T/128 = xxx块,这些block块属于一个文件
  2. 使用块抽象而不是文件,可以简化存储子系统。
  3. 块非常适合用于数据备份进而提供数据容错能力和可用性

块缓存

通常DataNode从磁盘中读取块,但对于访问频繁的文件,其对应的块可能被显示的缓存在DataNode的内存中,

以堆外块缓存的形式存在。默认情况下,一个块仅缓存在一个DataNode的内存中,当然可以针对每个文件配置
DataNode的数量。作业调度器通过在缓存块的DataNode上运行任务,可以利用块缓存的优势提高读操作的性能。

HDFS 副本机制:

HDFS视硬件错误为常态,硬件服务器随时有可能发生故障。
为了容错,文件的所有 block 都会有副本。每个文件的 block 大小和副本系数都是可配置的。

应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。
数据副本默认保存三个副本,我们可以更改副本数以提高数据的安全性
在hdfs-site.xml当中修改以下配置属性,即可更改文件的副本数

<property>
      <name>dfs.replication</name>
      <value>3</value>
</property>

低版本Hadoop副本节点选择
第一个副本在client所处的节点上。如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于不相同机架的随机节点上。
第三个副本和第二个副本位于相同机架,节点随机。

在这里插å
¥å›¾ç‰‡æè¿°

Hadoop2.7.2副本节点选择
第一个副本在client所处的节点上。如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于相同机架,随机节点。
第三个副本位于不同机架,随机节点。

在这里插å
¥å›¾ç‰‡æè¿°

名字空间(NameSpace)

我们把目录结构及文件分块位置信息叫做元数据。Namenode 负责维护整个hdfs文件系统的目录树结构,

以及每一个文件所对应的 block 块信息(block 的id,及所在的datanode 服务器)。

Namenode节点负责确定指定的文件块到具体的Datanode结点的映射关系。在客户端与数据节点之间共享数据。

在这里插å
¥å›¾ç‰‡æè¿°

管理Datanode结点的状态报告,包括Datanode结点的健康状态报告和其所在结点上数据块状态报告,

以便能够及时处理失效的数据结点。

总结:
NameNode 的功能
1.维护目录树。
2.Namenode节点负责确定指定的文件块到具体的Datanode结点的映射关系。
3.管理Datanode结点的状态报告。

DataNode 功能

文件的各个 block 的具体存储管理由 datanode 节点承担。每一个 block 都可以在多个datanode 上。

Datanode 需要定时向 Namenode 汇报自己持有的 block信息。 存储多个副本(副本数量也可以通过参数设置
dfs.replication,默认是 3)。

向Namenode结点报告状态。每个Datanode结点会周期性地向Namenode发送心跳信号和文件块状态报告。

心跳是每3秒一次,心跳返回结果带有namenode给该datanode的命令如复制块数据到另一台机器,

或删除某个数据块。如果超过10分钟没有收到某个datanode的心跳,则认为该节点不可用。

DataNode启动后向namenode注册,通过后,周期性(1小时)的向namenode上报所有的块信息。

执行数据的流水线复制。当文件系统客户端从Namenode服务器进程获取到要进行复制的数据块列表后,

完成文件块及其块副本的流水线复制。
一个数据块在datanode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,
一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。

在这里插å
¥å›¾ç‰‡æè¿°

总结:
DataNode 的功能
1.负责管理它所在结点上存储的数据的读写,及存储数据。
2.向Namenode结点报告状态。
3.执行数据的流水线复制。

机架感知原理

机架感知需要人为进行配置,编写Python脚本“RackAware.py”。内容为服务器IP与交换机的对应关系。(开源hadoop,使用RackAware.sh)

#!/usr/bin/python  
#-*-coding:UTF-8 -*-  
import sys  
  
rack = {  

        "12.12.3.1":"SW6300-1",  
        "12.12.3.2":"SW6300-1",  
        "12.12.3.3":"SW6300-1",  
        "12.12.3.4":"SW6300-1",  
        "12.12.3.5":"SW6300-1",  
        "12.12.3.6":"SW6300-1",  

        "12.12.3.25":"SW6300-2",  
        "12.12.3.26":"SW6300-2",  
        "12.12.3.27":"SW6300-2",  
 
        "12.12.3.49":"SW6300-3",  
        "12.12.3.50":"SW6300-3",  
        "12.12.3.51":"SW6300-3",  
     
        "12.12.3.73":"SW6300-4",  
        "12.12.3.74":"SW6300-4",  
        "12.12.3.75":"SW6300-4",  
		}  
if __name__=="__main__":  
    print "/" + rack.get(sys.argv[1],"SW6300-1-2")  

使用以下命令验证

[root@node01 sbin]# python RackAware.py 12.12.3.1
/SW6300-1 
[root@node01 sbin]# python RackAware.py 12.12.3.25
/SW6300-2
[root@node01 sbin]# python RackAware.py 12.12.3.75
/SW6300-4
[root@node01 sbin]# python RackAware.py 12.12.3.100
/SW6300-1-2

编辑core-site.xml配置文件,将脚本配置为topology.script.file.name的值

<property>
<name>topology.script.file.name</name>
<value>/home/bigdata/apps/hadoop/etc/hadoop/RackAware.py </value>
</property>

HDFS使用典型的master-slave结构:

HDFS设计思想

img

img

Active Namenode: 主Master(只有一个)
管理 HDFS的名称空间
管理数据块映射信息
配置副本策略
处理客户端读写请求
Standby Namenode:NameNode的热备;
定期合并fsimage和fsedits,推送给NameNode;
当Active NameNode出现故障时,快速切换为新的 Active NameNode。
Datanode: Slave(有多个)
存储实际的数据块
执行数据块读/写
Client: 文件切分
与NameNode交互,获取文件位置信息;
与DataNode交互,读取或者写入数据;
管理HDFS;
访问HDFS。

HDFS数据块(block):

​ 文件被切分成固定大小的数据块
​ 默认数据块大小为64MB,可配置
​ 若文件大小不到64MB,则单独存成一个block
​ 为何数据块如此之大
​ 数据传输时间超过寻道时间(高吞吐率)
​ 一个文件存储方式
​ 按大小被切分成若干个block,存储到不同节点上
​ 默认情况下每个block有三个副本

HDFS工作机制及原理:

一、 机架感知

1.1 设计机架感知的目的

机架感知的设计,考虑到两个方面:

不同节点之间的通信,希望在同一机架内进行(Hadoop集群会分布在很多机架上),而不是跨机架;
为了提高 容错 能力,NameNode (名称节点)会尽可能把 数据块的副本 放在多个机架上。

1.2 通过网络拓扑图分析

1.2.1 网络拓扑图介绍

DataNode 的网络拓扑图如下:

在这里插å
¥å›¾ç‰‡æè¿°

如上图,D1、R1是交换机,最底层是 DataNode 。可执行脚本文件返回各 DataNode 的机架 ID ,即 RackID(比如,H1 的 parent 是R1,R1的 parent 是D1,则 H1 的 RackID=/D1/R1/H1)。

有了这些 RackID 信息,就可以计算任意两台 DataNode 之间的距离了:

distance(/D1/R1/H1,/D1/R1/H1)=0    //相同的 DataNode
distance(/D1/R1/H1,/D1/R1/H2)=2    //同一个 Rack 下不同的 DataNode
distance(/D1/R1/H1,/D1/R1/H4)=4    //同一 IDC 下不同的 DataNode
distance(/D1/R1/H1,/D2/R3/H7)=6    //不同 IDC 下的 DataNode

IDC是互联网数据中心,可以理解为机房。

1.2.2 功能分析

默认情况下,HDFS 不能 自动判断 集群中各个 DataNode 的网络拓扑情况,集群 默认都 处在同一个机架名为 /default-rack的机架上(在这种情况下,任何一台 DataNode 机器,不管在物理上是否是属于同一个机架,都会被认为是在同一个机架下)。

通常,我们通过 外在脚本 实现机架感知,需要配置 net.topology.script.file.name属性(属性值一般是一个可执行脚本文件的路径)。脚本接收一个值,再输出一个值(一般都是接收 IP地址 ,输出这个地址所对应的 机架信息 )。

二、 副本冗余存储策略

HDFS 上的文件对应的数据块保存有多个副本(默认保存3个副本),且提供 容错机制 ,副本 丢失 或 宕机(即 死机) 时自动恢复。

2.1 策略的介绍

下面,以保存 3个副本 为例:

在这里插å
¥å›¾ç‰‡æè¿°

  1. 第一个副本(副本一):放置在 上传文件 的数据节点上(若是在 集群外 提交,则随机挑选一个 CPU比较空闲、磁盘不太满 的节点)。
  2. 第二个副本(副本二):放置在与第一个副本 不同 的机架的节点上。
  3. 第三个副本(副本三):放置在与第二个副本 相同 机架的其他节点上。
  4. 如果有 更多 副本,那么这些副本 随机选择 节点存放。

需要注意的是,副本并不都是均匀分布在不同的机架上。

2.2 策略的优点

副本冗余存储策略,主要有三个优点:

  1. 减少了机架间的 数据传输 ,提高了写操作的效率。(不会影响数据的可靠性和可用性,因为机架的错误远远比节点的错误小)
  2. 减少了 读取数据 时所需的网络传输总带宽。(因为数据块只放在两个不同的机架上)
  3. 在不损害数据 可靠性 和 读取性能 的情况下,改进了写操作的性能。(一个副本在一个机架的一个节点上,另外两个副本在另一个机架的不同节点上,其他副本则 均匀分布 在剩下的机架中。如 2.1 所介绍。)

NameNode和SecondaryNameNode工作机制:

img

5.1 NN和2NN工作机制

NN和2NN工作机制,如图所示

img

  1. 第一阶段:NameNode启动

(1)第一次启动NameNode格式化后,创建fsimage和edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。

(2)客户端对元数据进行增删改的请求。

(3)NameNode记录操作日志,更新滚动日志。

(4)NameNode在内存中对数据进行增删改查。

  1. 第二阶段:Secondary NameNode工作

(1)Secondary NameNode询问NameNode是否需要checkpoint。直接带回NameNode是否检查结果。

(2)Secondary NameNode请求执行checkpoint。

(3)NameNode滚动正在写的edits日志。

(4)将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。

(5)Secondary NameNode加载编辑日志和镜像文件到内存,并合并。

(6)生成新的镜像文件fsimage.chkpoint。

(7)拷贝fsimage.chkpoint到NameNode。

(8)NameNode将fsimage.chkpoint重新命名成fsimage。

NN和2NN工作机制详解:

Fsimage:namenode内存中元数据序列化后形成的文件。

Edits:记录客户端更新元数据信息的每一步操作(可通过Edits运算出元数据)。

namenode启动时,先滚动edits并生成一个空的edits.inprogress,然后加载edits(归档后的)和fsimage(最新的)到内存中,此时namenode内存就持有最新的元数据信息。client开始对namenode发送元数据的增删改查的请求,这些请求的操作首先会被记录的edits.inprogress中(查询元数据的操作不会被记录在edits中,因为查询操作不会更改元数据信息),如果此时namenode挂掉,重启后会从edits中读取元数据的信息。然后,namenode会在内存中执行元数据的增删改查的操作。

由于edits中记录的操作会越来越多,edits文件会越来越大,导致namenode在启动加载edits时会很慢,所以需要对edits和fsimage进行合并(所谓合并,就是将edits和fsimage加载到内存中,照着edits中的操作一步步执行,最终形成新的fsimage)。secondarynamenode的作用就是帮助namenode进行edits和fsimage的合并工作。

secondarynamenode首先会询问namenode是否需要checkpoint(触发checkpoint需要满足两个条件中的任意一个,定时时间到和edits中数据写满了)。直接带回namenode是否检查结果。secondarynamenode执行checkpoint操作,首先会让namenode滚动edits并生成一个空的edits.inprogress,滚动edits的目的是给edits打个标记,以后所有新的操作都写入edits.inprogress,其他未合并的edits和fsimage会拷贝到secondarynamenode的本地,然后将拷贝的edits和fsimage加载到内存中进行合并,生成fsimage.chkpoint,然后将fsimage.chkpoint拷贝给namenode,重命名为fsimage后替换掉原来的fsimage。namenode在启动时就只需要加载之前未合并的edits和fsimage即可,因为合并过的edits中的元数据信息已经被记录在fsimage中。

5.2 Fsimage和Edits解析

1.概念

namenode被格式化之后,将在/opt/module/hadoop-2.7.2/data/tmp/dfs/name/current目录中产生如下文件

fsimage_0000000000000000000

fsimage_0000000000000000000.md5

seen_txid

VERSION

(1)Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件的序列化信息。

(2)Edits文件:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到edits文件中。

(3)seen_txid文件保存的是一个数字,就是最后一个edits_的数字

(4)每次NameNode启动的时候都会将fsimage文件读入内存,并edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以看成NameNode启动的时候就将fsimage和edits文件进行了合并。

2.oiv查看fsimage文件

(1)查看oiv和oev命令

[root@hadoop101 current]$ hdfs
oiv  apply the offline fsimage viewer to an fsimage
oev  apply the offline edits viewer to an edits file

(2)基本语法

hdfs oiv -p 文件类型 -i镜像文件 -o 转换后文件输出路径

(3)案例实操

[root@hadoop101 current]$ pwd
/opt/module/hadoop-2.7.2/data/tmp/dfs/name/current
[root@hadoop101 current]$ hdfs oiv -p XML -i fsimage_0000000000000000025 -o /opt/module/hadoop-2.7.2/fsimage.xml
[root@hadoop101 current]$ cat /opt/module/hadoop-2.7.2/fsimage.xml

将显示的xml文件内容拷贝到Idea中创建的xml文件中,并格式化。部分显示结果如下。Xml参数必须大写

<inode>
<id>16386</id>
<type>DIRECTORY</type>
<name>user</name>
<mtime>1512722284477</mtime>
<permission>root:supergroup:rwxr-xr-x</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16387</id>
<type>DIRECTORY</type>
<name>root</name>
<mtime>1512790549080</mtime>
<permission>root:supergroup:rwxr-xr-x</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16389</id>
<type>FILE</type>
<name>wc.input</name>
<replication>3</replication>
<mtime>1512722322219</mtime>
<atime>1512722321610</atime>
<perferredBlockSize>134217728</perferredBlockSize>
<permission>root:supergroup:rw-r--r--</permission>
<blocks>
<block>
<id>1073741825</id>
<genstamp>1001</genstamp>
<numBytes>59</numBytes>
</block>
</blocks>
</inode >

3.oev查看edits文件

(1)基本语法

hdfs oev -p 文件类型 -i编辑日志 -o 转换后文件输出路径

(2)案例实操

[root@hadoop101 current]$ hdfs oev -p XML -i edits_0000000000000000012-0000000000000000013 -o /opt/module/hadoop-2.7.2/edits.xml

[root@hadoop101 current]$ cat /opt/module/hadoop-2.7.2/edits.xml

将显示的xml文件内容拷贝到Idea中创建的xml文件中,并格式化。显示结果如下。

<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
<EDITS_VERSION>-63</EDITS_VERSION>
<RECORD>
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>129</TXID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD</OPCODE>
<DATA>
<TXID>130</TXID>
<LENGTH>0</LENGTH>
<INODEID>16407</INODEID>
<PATH>/hello7.txt</PATH>
<REPLICATION>2</REPLICATION>
<MTIME>1512943607866</MTIME>
<ATIME>1512943607866</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME>DFSClient_NONMAPREDUCE_-1544295051_1</CLIENT_NAME>
<CLIENT_MACHINE>192.168.1.5</CLIENT_MACHINE>
<OVERWRITE>true</OVERWRITE>
<PERMISSION_STATUS>
<USERNAME>root</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
<RPC_CLIENTID>908eafd4-9aec-4288-96f1-e8011d181561</RPC_CLIENTID>
<RPC_CALLID>0</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
<DATA>
<TXID>131</TXID>
<BLOCK_ID>1073741839</BLOCK_ID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_SET_GENSTAMP_V2</OPCODE>
<DATA>
<TXID>132</TXID>
<GENSTAMPV2>1016</GENSTAMPV2>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_ADD_BLOCK</OPCODE>
<DATA>
<TXID>133</TXID>
<PATH>/hello7.txt</PATH>
<BLOCK>
<BLOCK_ID>1073741839</BLOCK_ID>
<NUM_BYTES>0</NUM_BYTES>
<GENSTAMP>1016</GENSTAMP>
</BLOCK>
<RPC_CLIENTID></RPC_CLIENTID>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<OPCODE>OP_CLOSE</OPCODE>
<DATA>
<TXID>134</TXID>
<LENGTH>0</LENGTH>
<INODEID>0</INODEID>
<PATH>/hello7.txt</PATH>
<REPLICATION>2</REPLICATION>
<MTIME>1512943608761</MTIME>
<ATIME>1512943607866</ATIME>
<BLOCKSIZE>134217728</BLOCKSIZE>
<CLIENT_NAME></CLIENT_NAME>
<CLIENT_MACHINE></CLIENT_MACHINE>
<OVERWRITE>false</OVERWRITE>
<BLOCK>
<BLOCK_ID>1073741839</BLOCK_ID>
<NUM_BYTES>25</NUM_BYTES>
<GENSTAMP>1016</GENSTAMP>
</BLOCK>
<PERMISSION_STATUS>
<USERNAME>root</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>
</EDITS >

5.3checkpoint时间设置

(1)通常情况下,SecondaryNameNode每隔一小时执行一次。如果修改在hdfs-site中

默认值在[hdfs-default.xml]

<property>
  <name>dfs.namenode.checkpoint.period</name>
  <value>3600</value>
</property>

(2)一分钟检查一次操作次数,当操作次数达到1百万时,SecondaryNameNode执行一次。

<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
<description>操作动作次数</description>
</property>

<property>
  <name>dfs.namenode.checkpoint.check.period</name>
  <value>60</value>
<description>1分钟检查一次操作次数</description>
</property >

5.4 集群安全模式

1.概述

NameNode启动时,首先将映像文件(fsimage)载入内存,并执行编辑日志(edits)中的各项操作。一旦在内存中成功建立文件系统元数据的映像,则创建一个新的fsimage文件和一个空的编辑日志。此时,NameNode开始监听DataNode请求。但是此刻,NameNode运行在安全模式,即NameNode的文件系统对于客户端来说是只读的。

系统中的数据块的位置并不是由NameNode维护的,而是以块列表的形式存储在DataNode中。在系统的正常操作期间,NameNode会在内存中保留所有块位置的映射信息。在安全模式下,各个DataNode会向NameNode发送最新的块列表信息,NameNode了解到足够多的块位置信息之后,即可高效运行文件系统。

如果满足“最小副本条件”,NameNode会在30秒钟之后就退出安全模式。所谓的最小副本条件指的是在整个文件系统中99.9%的块满足最小副本级别(默认值:dfs.replication.min=1)。在启动一个刚刚格式化的HDFS集群时,因为系统中还没有任何块,所以NameNode不会进入安全模式。

1.基本语法

集群处于安全模式,不能执行重要操作(写操作)。集群启动完成后,自动退出安全模式。

(1)bin/hdfs dfsadmin -safemode get (功能描述:查看安全模式状态)

(2)bin/hdfs dfsadmin -safemode enter (功能描述:进入安全模式状态)

(3)bin/hdfs dfsadmin -safemode leave (功能描述:离开安全模式状态)

(4)bin/hdfs dfsadmin -safemode wait (功能描述:等待安全模式状态,监控安全模式)

3.案例

模拟等待安全模式

(1)先进入安全模式

[root@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode enter

(2)执行下面的脚本

编辑一个脚本

#!/bin/bash

bin/hdfs dfsadmin -safemode wait(安全模式关闭)

bin/hdfs dfs -put ~/hello.txt /root/hello.txt

(3)再打开一个窗口,执行

[root@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode leave

第6章 DataNode工作机制

6.1DataNode工作机制

DataNode工作机制,如图所示

img

1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。

2)DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。

3)心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。

HDFS典型的物理拓扑结构:

img

HDFS Block副本放置策略:

​ 副本1: 同Client的节点上
​ 副本2: 不同机架中的节点上
​ 副本3: 与第二个副本同一机架的另一个节点上
​ 其他副本:随机挑选

img

HDFS可靠性策略:

img

HDFS访问方式:

HDFS Shell命令 :和linux命令很像
HDFS Java API :org.apache.hadoop.fs,很简单
HDFS REST API
HDFS Fuse:实现了fuse协议
HDFS lib hdfs:C/C++访问接口
HDFS 其他语言编程API
使用thrift实现
支持C++、Python、php、C#等语言

img

HDFS2.0新特性(还没有完全实现,谨慎使用):

NameNode HA
NameNode Federation
HDFS 快照(snapshot)
HDFS 缓存(in-memory cache)
HDFS ACL
异构层级存储结构(Heterogeneous Storage hierarchy)

网络拓扑—节点距离计算:

在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据,那么这个最近距离是怎么计算的呢?

结论:两个节点到达最近的共同祖先的距离总和,即为节点距离。

img

如上图所示:

  • 同一节点上的进程节点距离为0
  • 同一机架上不同节点的距离为两个节点到共同机架r1的距离总和,为2
  • 同一数据中心不同机架的节点距离为两个节点到共同祖先集群d1的距离之和,为4
  • 不同数据中心的节点距离为两个节点到达共同祖先数据中心的距离之和,为6

机架感知(副本存储的节点选择):

副本的数量我们可以从配置文件中设置,那么HDFS是怎么选择副本存储的节点的呢?

img

如上图所示,为了提高容错性,有如下设置,加入现在有3个副本:

  • 第一个副本在Client所在的节点上,如果客户端在集群外,则随机选一个
  • 第二个副本和第一个副本位于相同机架,随机节点
  • 第三个副本位于不同机架,随机节点

这样做的目的就是为了提高容错性。

文件传输过程:

在 HDFS 中 读写数据 的过程都是通过数据流完成的。HDFS 提供了数据流的 I/O操作类(包括 FSDataInputStream 和 FSDataOutputStream )

读数据:

HDFS读数据流程:

HDFS读数据流程

  1. 客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
  2. 挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
  3. DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
  4. 客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。
<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
<script>
const btw = new BTWPlugin();
btw.init({
	id: 'container',
	blogId: '15971-1569464559619-703',
	name: '进击的python',
	qrcode: 	'https://mmbiz.qpic.cn/mmbizjpg/hlLEC0QP5QW5cORDqeg5gYp19JQwupY7O7gLWJxkribPxFUL7Iv4hsS3oQZ8icWFoEfUiajObQxQFCmgWPOpBiaAcg/0?wxfmt=jpeg',
	keyword: 'vip',
});
</script>

img

HDFS的读数据流程,如图所示

img

1)客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。

2)挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。

3)DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以packet为单位来做校验)。

4)客户端以packet为单位接收,先在本地缓存,然后写入目标文件。

读取过程分析

HDFS 文件读取(即 数据下载)过程如图所示:

在这里插å
¥å›¾ç‰‡æè¿°

⑴ HDFS 客户端通过 DistributeFileSystem 对象的 open() 方法打开需要读取的文件。

⑵ DistributeFileSystem 向远程的 NameNode 节点发起 RPC调用 ,得到文件的数据块信息,返回数据块列表。(对于每个数据块,NameNode 返回该数据块的 DataNode 地址)

⑶ DistributeFileSystem 返回一个 FSDataInputStream 对象给客户端,客户端调用 FSDataInputStream 对象的 read() 方法读取数据。

⑷ 通过对数据流反复调用 read() 方法,把数据从数据节点传输到客户端。

⑸ 当一个节点的数据读取完毕时, DFSInputStream 对象会关闭与此数据节点的连接,然后连接此文件 下一个数据块 的最近数据节点。

⑹ 当客户端读取完数据时,调用 FSDataInputStream 对象的 close() 方法关闭输入流。

3.1.2 FSDataInputStream 类介绍

FSDataInputStream 输入流类的常用方法:

方法名返回值作用
read(ByteBuffer buf)int读取并写入 buf 缓冲区,返回所读的字节数
read(long pos,byte[] buf,int offset,int len)int从输入流的指定位置开始,把数据读入缓冲区。
readFully(long pos,byte[] buf)void从指定位置开始,读取所有数据到缓冲区
seek(long offset)void指向输入流的第 offset 字节
releaseBuffer(ByteBuffer buf)void删除指定的缓冲区

pos 指定从输入流中读取数据的位置;offset 指定数据写入缓冲区的位置(偏移量);len 指定读操作的最大字节数。

读数据流程:

在这里插å
¥å›¾ç‰‡æè¿°

磁盘故障
多个副本策略
namenode故障宕机
简单方案:secondarynamenode取出fsimage文件copy到namenode的元数据存储目录下
完美解决:在namenode上挂多块磁盘,配置fs.namenode.name.dir(用,分割磁盘 )

CheckPoint
触发条件:

  1. 事务达到1000000条(默认)
  2. 1小时(默认)
<property>
  <name>dfs.namenode.checkpoint.dir</name>
  <value>/hadoop/data/name</value>
</property>
<!--日志文件edits的检测目录-->
<property>
  <name>dfs.namenode.checkpoint.edits.dir</name>
  <value>/hadoop/data/edits</value>
</property>
<!--时间一小时-->
<property>
  <name>dfs.namenode.checkpoint.period</name>
  <value>3600</value>
</property>
<!--事物达到1000000-->
<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
</property>	

在这里插å
¥å›¾ç‰‡æè¿°

注:namenode存储元数据,secondarynamenode执行checkpoint的时候去namenode下载edits和fsimage
注意的问题
客户端和服务器端解释

  1. 客户端的配置文件决定副本数量,而不是服务器
  2. 文件存储以块的形式存储在服务器上(客户端决定文件切分,块大小)
写数据:

在HDFS中,文件使用FileSystem类的create方法及其重载形式来创建,create方法返回一个输出流FSDataOutputStream,可以调用返回输出流的getPos方法查看当前文件的位移,但是不能进行seek操作,HDFS仅支持追加操作。

HDFS写数据流程**

img

  1. 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
  2. NameNode返回是否可以上传。
  3. 客户端请求第一个 Block上传到哪几个DataNode服务器上。
  4. NameNode返回3个DataNode节点,分别为dn1、dn2、dn3, 如果有多个节点,返回实际的副本数量,并根据距离及负载情况计算
  5. 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
  6. dn1、dn2、dn3逐级应答客户端。
  7. 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
  8. 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。

img

创建时,可以传递一个回调接口Peofressable,获取进度信息

append(Path f)方法用于追加内容到已有文件,但是并不是所有的实现都提供该方法,例如Amazon的文件实现就没有提供追加功能。

下面是一个例子:

String localSrc =  args[0];
String dst = args[1];
 
InputStream in = new BufferedInputStream(new FileInputStream(localSrc));
 
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(dst),conf);
 
OutputStream out = fs.create(new Path(dst), new Progressable(){
    public vid progress(){
        System.out.print(.);
    }
});
 
IOUtils.copyBytes(in , out, 4096,true);

6.3 目录操作

使用mkdirs()方法,会自动创建没有的上级目录

HDFS中元数据封装在FileStatus类中,包括长度、block size,replicaions,修改时间、所有者、权限等信息。使用FileSystem提供的getFileStatus方法获取FileStatus。exists()方法判断文件或者目录是否存在;

列出文件(list),则使用listStatus方法,可以查看文件或者目录的信息

  public abstract FileStatus[] listStatus(Path f) throws FileNotFoundException, 
                                                         IOException;

Path是个文件的时候,返回长度为1的数组。FileUtil提供的stat2Paths方法用于将FileStatus转化为Path对象。

globStatus则使用通配符对文件路径进行匹配:

public FileStatus[] globStatus(Path pathPattern) throws IOException

PathFilter用于自定义文件名过滤,不能根据文件属性进行过滤,类似于java.io.FileFilter。例如下面这个例子排除到给定正则表达式的文件:

public interfacePathFilter{
    boolean accept(Path path);
}

6.4 删除数据

使用FileSystem的delete()方法

public boolean delete(Path f , boolean recursive) throws IOException;

recursive参数在f是个文件的时候被忽略。如果f是文件并且recursice为true,则删除整个目录,否则抛出异常.

4.1.1剖析文件写入

HDFS写数据流程,如图所示

img

1)客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。

2)NameNode返回是否可以上传。

3)客户端请求第一个 block上传到哪几个datanode服务器上。

4)NameNode返回3个datanode节点,分别为dn1、dn2、dn3。

5)客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。

6)dn1、dn2、dn3逐级应答客户端。

7)客户端开始往dn1上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,dn1收到一个packet就会传给dn2,dn2传给dn3;

8)当一个block传输完成之后,客户端再次请求NameNode上传第二个block的服务器。(重复执行3-7步)。

写入过程分析

HDFS 文件写入(即 数据上传)过程如图所示:

在这里插å
¥å›¾ç‰‡æè¿°

⑴ 客户端调用 DistributedFileSystem 对象的 create() 方法创建一个文件输出流对象。

⑵ DistributedFileSystem 对象远程的 NameNode 节点发起一次 RPC调用 ,NameNode 检查这个文件 是否存在 ,以及客户端 是否有权限 新建文件。

⑶ 客户端调用 FSDataOutputStream 对象的 write() 方法写数据(数据鲜卑写入缓冲区,再被切分为一个个数据包)。

⑷ 每个数据包被发送到由 NameNode 节点分配的一组数据节点中的一个数据节点上,在这组数据节点组成的管道上依次传输数据包。

⑸ 管道上的节点按反向顺序返回确认信息,最终由管道的第一个数据节点将整条管道的确认信息发送给客户端。

⑹ 客户端完成写入,调用 close() 方法关闭文件输出流。

⑺ 通知 NameNode 文件写入成功。

3.2.2 FSDataOutputStream 类介绍

FSDataOutputStream 输入流类的常用方法:

方法名返回值作用
write(byte[] b)void将数组 b 中的所有字节写入输出流
write(byte[] buf,int off,int len)void将字节组写入底层输出流,写入的字节从 off 偏移量开始,写入长度为 len
flush()void刷新数据输出流(缓冲区内容被强制写入流中)

len 指定读操作的最大字节数。

写数据流程:

在这里插å
¥å›¾ç‰‡æè¿°

节点服务器传输数据方式:网络传输,以package包的形式(第8步,上传数据的时候会把package先放到缓存队列,如果此时package出错的话,会默认重传 4次)
这里的话,追加一下一些问题(分布式系统之间可能故障,而且网络的不可靠性都是设计人员需要考虑的问题):socket(长连接),http(短连接),还有其他的方式,比如管道、FIFO、消息队列(kafka。。。)
为什么使用长链接?
最简单分布式系统是一直存在的,很少是短时间的访问,维持心跳机制
什么是心跳机制?
namenode启动的时候,会有一个加载元数据(数据的数据,类似于表的索引)和块报告(datanode会定时(可以再配置文件中设置,所以一定要时间同步)对块信息进行统计)的过程,namenode通过心跳机制维护整个集群的可用性。如果块报告上传失败,namenode不会更新元数据,在块报告的时候就会将其删除掉。
安全模式
什么时候进入安全模式?
刚刚启动(namenode加载元数据的时候(先加载元数据镜像到内存中,在将edits日志的操作在内存中执行一遍,namenode进入安全模式,进行块报告,阈值安全的话30秒退出安全模式))
阈值低于0.999f(默认)
datanode存活数量小于0

怎么解除安全模式?

1.格式化集群(需要删除namenode.dir的配置路径)
基本不会采用这种方式

2.强制离开安全模式
hdfs dfsadmin -safemode leave

3.检测集群文件、节点、块是否出现问题

hdfs fsck /
#删除损坏块的block
hdfs fsck / -delete

4.调低阈值(在配置文件 safemode)

数据流(读写流程):

接下来详细介绍HDFS读写数据的流程,以及一致性模型相关的一些概念。

7.1 读文件

大致读文件的流程如下:

这里写图片描述

1)客户端传递一个文件Path给FileSystem的open方法

2)DFS采用RPC远程获取文件最开始的几个block的datanode地址。Namenode会根据网络拓扑结构决定返回哪些节点(前提是节点有block副本),如果客户端本身是Datanode并且节点上刚好有block副本,直接从本地读取。

3)客户端使用open方法返回的FSDataInputStream对象读取数据(调用read方法)

4)DFSInputStream(FSDataInputStream实现了改类)连接持有第一个block的、最近的节点,反复调用read方法读取数据

5)第一个block读取完毕之后,寻找下一个block的最佳datanode,读取数据。如果有必要,DFSInputStream会联系Namenode获取下一批Block 的节点信息(存放于内存,不持久化),这些寻址过程对客户端都是不可见的。

6)数据读取完毕,客户端调用close方法关闭流对象

在读数据过程中,如果与Datanode的通信发生错误,DFSInputStream对象会尝试从下一个最佳节点读取数据,并且记住该失败节点, 后续Block的读取不会再连接该节点
读取一个Block之后,DFSInputStram会进行检验和验证,如果Block损坏,尝试从其他节点读取数据,并且将损坏的block汇报给Namenode。
客户端连接哪个datanode获取数据,是由namenode来指导的,这样可以支持大量并发的客户端请求,namenode尽可能将流量均匀分布到整个集群。
Block的位置信息是存储在namenode的内存中,因此相应位置请求非常高效,不会成为瓶颈。

7.2 写文件

这里写图片描述

步骤分解
1)客户端调用DistributedFileSystem的create方法

2)DistributedFileSystem远程RPC调用Namenode在文件系统的命名空间中创建一个新文件,此时该文件没有关联到任何block。 这个过程中,Namenode会做很多校验工作,例如是否已经存在同名文件,是否有权限,如果验证通过,返回一个FSDataOutputStream对象。 如果验证不通过,抛出异常到客户端。

3)客户端写入数据的时候,DFSOutputStream分解为packets(数据包),并写入到一个数据队列中,该队列由DataStreamer消费。

4)DateStreamer负责请求Namenode分配新的block存放的数据节点。这些节点存放同一个Block的副本,构成一个管道。 DataStreamer将packet写入到管道的第一个节点,第一个节点存放好packet之后,转发给下一个节点,下一个节点存放 之后继续往下传递。

5)DFSOutputStream同时维护一个ack queue队列,等待来自datanode确认消息。当管道上的所有datanode都确认之后,packet从ack队列中移除。

6)数据写入完毕,客户端close输出流。将所有的packet刷新到管道中,然后安心等待来自datanode的确认消息。全部得到确认之后告知Namenode文件是完整的。 Namenode此时已经知道文件的所有Block信息(因为DataStreamer是请求Namenode分配block的),只需等待达到最小副本数要求,然后返回成功信息给客户端。

Namenode如何决定副本存在哪个Datanode?

HDFS的副本的存放策略是可靠性、写带宽、读带宽之间的权衡。默认策略如下:

  • 第一个副本放在客户端相同的机器上,如果机器在集群之外,随机选择一个(但是会尽可能选择容量不是太慢或者当前操作太繁忙的)
  • 第二个副本随机放在不同于第一个副本的机架上。
  • 第三个副本放在跟第二个副本同一机架上,但是不同的节点上,满足条件的节点中随机选择。
  • 更多的副本在整个集群上随机选择,虽然会尽量避免太多副本在同一机架上。
    副本的位置确定之后,在建立写入管道的时候,会考虑网络拓扑结构。下面是可能的一个存放策略:

这里写图片描述

这样选择很好滴平衡了可靠性、读写性能

  • 可靠性:Block分布在两个机架上
  • 写带宽:写入管道的过程只需要跨越一个交换机
  • 读带宽:可以从两个机架中任选一个读取

7.3 一致性模型

一致性模型描述文件系统中读写操纵的可见性。HDFS中,文件一旦创建之后,在文件系统的命名空间中可见:

Path p = new Path("p");
fs.create(p);
assertTaht(fs.exists(p),is(true));

但是任何被写入到文件的内容不保证可见,即使对象流已经被刷新。

Path p = new Path(“p”); 
OutputStream out = fs.create(p); 
out.write(“content”.getBytes(UTF-8)); 
out.flush(); 
assertTaht(fs.getFileStatus(p).getLen,0L); // 为0,即使调用了flush

如果需要强制刷新数据到Datanode,使用FSDataOutputStream的hflush方法强制将缓冲刷到datanode

hflush之后,HDFS保证到这个时间点为止写入到文件的数据都到达所有的数据节点。

Path p = new Path("p");
OutputStream out = fs.create(p);
out.write("content".getBytes("UTF-8"));
out.flush();
assertTaht(fs.getFileStatus(p).getLen,is(((long,"content".length())));

关闭对象流时,内部会调用hflush方法,但是hflush不保证datanode数据已经写入到磁盘,只是保证写入到datanode的内存, 因此在机器断电的时候可能导致数据丢失,如果要保证写入磁盘,使用hsync方法,hsync类型与fsync()的系统调用,fsync提交某个文件句柄的缓冲数据。

FileOutputStreamout = new FileOutPutStream(localFile);
out.write("content".getBytes("UTF-8"));
out.flush();
out.getFD().sync();
assertTaht(localFile.getLen,is(((long,"content".length())));

使用hflush或hsync会导致吞吐量下降,因此设计应用时,需要在吞吐量以及数据的健壮性之间做权衡。

另外,文件写入过程中,当前正在写入的Block对其他Reader不可见。

7.4 Hadoop节点距离

在读取和写入的过程中,namenode在分配Datanode的时候,会考虑节点之间的距离。HDFS中,距离没有
采用带宽来衡量,因为实际中很难准确度量两台机器之间的带宽。
Hadoop把机器之间的拓扑结构组织成树结构,并且用到达公共父节点所需跳转数之和作为距离。事实上这是一个距离矩阵的例子。下面的例子简明地说明了距离的计算:

这里写图片描述

同一数据中心,同一机架,同一节点距离为0

同一数据中心,同一机架,不同节点距离为2

同一数据中心,不同机架,不同节点距离为4

不同数据中心,不同机架,不同节点距离为6

这里写图片描述

Hadoop集群的拓扑结构需要手动配置,如果没配置,Hadoop默认所有节点位于同一个数据中心的同一机架上。

数据容错:

HDFS 能够在出错的情况下,保证 数据存储 的可靠性。常见的出错情况有 NameNode 节点出错、DataNode 节点出错 和 数据出错 这三种情况。

4.1 分析 NameNode 节点出错

HDFS 中所有元数据都保存在 NameNode (名称节点)上,NameNode 节点维护 edits 和 fsimage 这两个文件。(如果这两个文件损坏,HDFS 就会 失效 )

Hadoop 提供了两个机制,来确保 NameNode 的安全:

  1. 把 NameNode 节点上的元数据信息同步存储到其他文件系统(比如 NFS ),当 NameNode 出现故障时,HDFS 自动切换到备用的 NameNode 上(HDFS HA ,就是采用共享存储系统来存储 edits 的)。
  2. 运行一个 SecondaryNameNode 节点,当 NameNode 宕机时,利用 SecondaryNameNode 的元数据信息进行系统恢复(仍然会有 部分数据 丢失)。
    通常,这 两个方法 结合使用。

4.2 分析 DataNode 节点出错

NameNode 通过 心跳信号 来检测近期不发送心跳信号的 DataNode,并将其标志为 宕机 (每个 DataNode 周期性地向 NameNode 发送心跳信号),不再发送 新的 I/O请求 给它们。

数据块需要重新复制的情况:

  1. 某个 DataNode 节点丢失;
  2. DataNode 上的硬盘出错;
  3. 某个副本损坏;
  4. 某个数据块的副本系数低于设定值。

4.3 分析 数据出错

从 DataNode 获取的数据块,有可能本身就是损坏的(比如可能是因为 网络错误 、软件bug 或者 DataNode的存储设备错误)。

HDFS 使用 校验和来判断数据块是否损坏。HDFS 的每个 DataNode 节点,保存了检测校验的日志(客户端的每一次检验都会被记录)。

Hadoop HDFS-基本介绍:

01 Hadoop组成

Hadoop  HDFS:一个高可靠、高吞吐量的分布式文件系统,对海量数据的存储。
Hadoop  MapReduce:一个分布式的资源调度和离线并行计算框架。	
adoop  	Yarn:基于HDFS,用于作业调度和集群资源管理的框架。
Hadoop  Common:Hadoop工具包,支持其他模块的工具模块(Configuration、RPC、序列化机制、日志操作)

02 Hadoop的文件系统介绍

HDFS 是 Hadoop Distribute File System 的简称,意为:Hadoop 分布式文件系统。是 Hadoop
核心组件之一,作为最底层的分布式存储服务而存在。
分布式文件系统解决的问题就是大数据存储。它们是横跨在多台计算机上的存储系统。分布式文件系统在大数据时代有着广泛的应用前景,它们为存储和处理超大规模数据提供所需的扩展能力。

在这里插å
¥å›¾ç‰‡æè¿°

HDFS使用Master和Slave结构对集群进行管理。一般一个 HDFS 集群只有一个 Namenode 和一定数目的Datanode
组成。Namenode 是 HDFS 集群主节点,Datanode 是 HDFS
集群从节点,两种角色各司其职,共同协调完成分布式的文件存储服务。

在这里插å
¥å›¾ç‰‡æè¿°

  • NameNode(Master)管理者 - 只负责管理,管理集群内各个节点。
  • SecondaryNameNode 辅助管理 – 只负责辅助NameNode管理工作。
  • DataNode(Slave) 工作者,是负责工作,周期向NameNode汇报,进行读写数据。

1)HDFS集群包括,NameNode和DataNode以及Secondary Namenode。
2)NameNode负责管理整个文件系统的元数据,以及每一个路径(文件)所对应的数据块信息。
3)DataNode负责管理用户的文件数据块,每一个数据块都可以在多个datanode上存储多个副本。
4)SecondaryNameNode用来监控HDFS状态的辅助后台程序,每隔一段时间获取HDFS元数据的快照。最主要作用是辅助namenode管理元数据信息

在这里插å
¥å›¾ç‰‡æè¿°

03 HDFS分块存储

hdfs将所有的文件全部抽象成为block块来进行存储,不管文件大小,全部一视同仁都是以block块的统一大小和形式进行存储,方便我们的分布式文件系统对文件的管理

所有的文件都是以block块的方式存放在HDFS文件系统当中,在Hadoop1当中,文件的block块默认大小是64M,Hadoop2当中,文件的block块大小默认是128M,block块的大小可以通过hdfs-site.xml当中的配置文件进行指定

<property>
    <name>dfs.block.size</name>
    <value>块大小 以字节为单位</value>//只写数值就可以
</property>

在这里插å
¥å›¾ç‰‡æè¿°

一个文件100M,上传到HDFS占用几个快? 一个块128M,剩余的28M怎么办?

事实上,128只是个数字,数据超过128M,便进行切分,如果没有超过128M,就不用切分,有多少算多少,不足128M的也是一个快。这个快的大小就是100M,没有剩余28M这个概念。

抽象成数据块的好处:

  1. 一个文件有可能大于集群中任意一个磁盘 20T/128 = xxx块,这些block块属于一个文件
  2. 使用块抽象而不是文件,可以简化存储子系统。
  3. 块非常适合用于数据备份进而提供数据容错能力和可用性

块缓存:

通常DataNode从磁盘中读取块,但对于访问频繁的文件,其对应的块可能被显示的缓存在DataNode的内存中,以堆外块缓存的形式存在。默认情况下,一个块仅缓存在一个DataNode的内存中,当然可以针对每个文件配置DataNode的数量。作业调度器通过在缓存块的DataNode上运行任务,可以利用块缓存的优势提高读操作的性能。

05 HDFS副本机制

HDFS视硬件错误为常态,硬件服务器随时有可能发生故障。 为了容错,文件的所有 block 都会有副本。每个文件的 block

大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。

数据副本默认保存三个副本,我们可以更改副本数以提高数据的安全性 在hdfs-site.xml当中修改以下配置属性,即可更改文件的副本数
在hdfs-site.xml当中修改以下配置属性,即可更改文件的副本数

<property>
      <name>dfs.replication</name>
      <value>3</value>
</property>

低版本Hadoop副本节点选择

第一个副本在client所处的节点上。如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于不相同机架的随机节点上。
第三个副本和第二个副本位于相同机架,节点随机。

在这里插å
¥å›¾ç‰‡æè¿°

Hadoop2.7.2副本节点选择:
第一个副本在client所处的节点上。如果客户端在集群外,随机选一个。
第二个副本和第一个副本位于相同机架,随机节点。
第三个副本位于不同机架,随机节点。

在这里插å
¥å›¾ç‰‡æè¿°

06 名字空间(NameSpace)

HDFS
支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。

Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被Namenode 记录下来。

HDFS会给客户端提供一个统一的目录树,客户端通过路径来访问文件,形如:hdfs://namenode:port/dir-a/dir-b/dir-c/file.data。

07 Namenode 功能

1、 维护目录树,维护命名空间。
2、 负责确定指定的文件块到具体的Datanode结点的映射关系。(在客户端与Datanode之间共享数据)
3、管理Datanode结点的状态报告

08 DataNode的作用

1、 负责管理它所在结点上存储的数据的读写,及存储数据。
2、 向Namenode结点报告DataNode节点的状态。
3、 通过流水线复制实现三份数据副本

09 机架感知

机架感知需要人为进行配置,编写Python脚本“RackAware.py”。内容为服务器IP与交换机的对应关系。(开源hadoop,使用RackAware.sh)

#!/usr/bin/python  
#-*-coding:UTF-8 -*-  
import sys  
  
rack = {  

        "12.12.3.1":"SW6300-1",  
        "12.12.3.2":"SW6300-1",  
        "12.12.3.3":"SW6300-1",  
        "12.12.3.4":"SW6300-1",  
        "12.12.3.5":"SW6300-1",  
        "12.12.3.6":"SW6300-1",  

        "12.12.3.25":"SW6300-2",  
        "12.12.3.26":"SW6300-2",  
        "12.12.3.27":"SW6300-2",  
 
        "12.12.3.49":"SW6300-3",  
        "12.12.3.50":"SW6300-3",  
        "12.12.3.51":"SW6300-3",  
     
        "12.12.3.73":"SW6300-4",  
        "12.12.3.74":"SW6300-4",  
        "12.12.3.75":"SW6300-4",  
		}  
if __name__=="__main__":  
    print "/" + rack.get(sys.argv[1],"SW6300-1-2") 

使用以下命令验证

[root@node01 sbin]# python RackAware.py 12.12.3.1/SW6300-1 

编辑core-site.xml配置文件,将脚本配置为topology.script.file.name的值

<property>
<name>topology.script.file.name</name>
<value>/home/bigdata/apps/hadoop/etc/hadoop/RackAware.py </value>
</property>

深入理解Hadoop HDFS

1. 介绍

在现代的企业环境中,单机容量往往无法存储大量数据,需要跨机器存储。统一管理分布在集群上的文件系统称为分布式文件系统。而一旦在系统中,引入网络,就不可避免地引入了所有网络编程的复杂性,例如挑战之一是如果保证在节点不可用的时候数据不丢失。

传统的网络文件系统(NFS)虽然也称为分布式文件系统,但是其存在一些限制。由于NFS中,文件是存储在单机上,因此无法提供可靠性保证,当很多客户端同时访问NFS Server时,很容易造成服务器压力,造成性能瓶颈。另外如果要对NFS中的文件进行操作,需要首先同步到本地,这些修改在同步到服务端之前,其他客户端是不可见的。某种程度上,NFS不是一种典型的分布式系统,虽然它的文件的确放在远端(单一)的服务器上面。

这里写图片描述

这里写图片描述

从NFS的协议栈可以看到,它事实上是一种VFS(操作系统对文件的一种抽象)实现。

HDFS,是Hadoop Distributed File System的简称,是Hadoop抽象文件系统的一种实现。Hadoop抽象文件系统可以与本地系统、Amazon S3等集成,甚至可以通过Web协议(webhsfs)来操作。HDFS的文件分布在集群机器上,同时提供副本进行容错及可靠性保证。例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力。

如果你从零开始搭建一个完整的集群,参考[Hadoop集群搭建详细步骤(2.6.0)](http://blog.csdn.net/bingduanlbd/article/details/51892750)

HDFS核心概念:

HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M。

3.1 Blocks

物理磁盘中有块的概念,磁盘的物理Block是磁盘操作最小的单元,读写操作均以Block为最小单元,一般为512 Byte。文件系统在物理Block之上抽象了另一层概念,文件系统Block物理磁盘Block的整数倍。通常为几KB。Hadoop提供的df、fsck这类运维工具都是在文件系统的Block级别上进行操作。

HDFS的Block块比一般单机文件系统大得多,默认为128M。HDFS的文件被拆分成block-sized的chunk,chunk作为独立单元存储。比Block小的文件不会占用整个Block,只会占据实际大小。例如, 如果一个文件大小为1M,则在HDFS中只会占用1M的空间,而不是128M。

HDFS的Block为什么这么大?
是为了最小化查找(seek)时间,控制定位文件与传输文件所用的时间比例。假设定位到Block所需的时间为10ms,磁盘传输速度为100M/s。如果要将定位到Block所用时间占传输时间的比例控制1%,则Block大小需要约100M。
但是如果Block设置过大,在MapReduce任务中,Map或者Reduce任务的个数 如果小于集群机器数量,会使得作业运行效率很低。

Block抽象的好处
block的拆分使得单个文件大小可以大于整个磁盘的容量,构成文件的Block可以分布在整个集群, 理论上,单个文件可以占据集群中所有机器的磁盘。
Block的抽象也简化了存储系统,对于Block,无需关注其权限,所有者等内容(这些内容都在文件级别上进行控制)。
Block作为容错和高可用机制中的副本单元,即以Block为单位进行复制。

3.2 Namenode & Datanode

整个HDFS集群由Namenode和Datanode构成master-worker(主从)模式。Namenode负责构建命名空间,管理文件的元数据等,而Datanode负责实际存储数据,负责读写工作。

Namenode

Namenode存放文件系统树及所有文件、目录的元数据。元数据持久化为2种形式:

  • namespcae image
  • edit log

但是持久化数据中不包括Block所在的节点列表,及文件的Block分布在集群中的哪些节点上,这些信息是在系统重启的时候重新构建(通过Datanode汇报的Block信息)。
在HDFS中,Namenode可能成为集群的单点故障,Namenode不可用时,整个文件系统是不可用的。HDFS针对单点故障提供了2种解决机制:
1)备份持久化元数据
将文件系统的元数据同时写到多个文件系统, 例如同时将元数据写到本地文件系统及NFS。这些备份操作都是同步的、原子的。

2)Secondary Namenode
Secondary节点定期合并主Namenode的namespace image和edit log, 避免edit log过大,通过创建检查点checkpoint来合并。它会维护一个合并后的namespace image副本, 可用于在Namenode完全崩溃时恢复数据。下图为Secondary Namenode的管理界面:

这里写图片描述

Secondary Namenode通常运行在另一台机器,因为合并操作需要耗费大量的CPU和内存。其数据落后于Namenode,因此当Namenode完全崩溃时,会出现数据丢失。 通常做法是拷贝NFS中的备份元数据到Second,将其作为新的主Namenode。
在HA(High Availability高可用性)中可以运行一个Hot Standby,作为热备份,在Active Namenode故障之后,替代原有Namenode成为Active Namenode。

Datanode

数据节点负责存储和提取Block,读写请求可能来自namenode,也可能直接来自客户端。数据节点周期性向Namenode汇报自己节点上所存储的Block相关信息。

3.3 Block Caching

DataNode通常直接从磁盘读取数据,但是频繁使用的Block可以在内存中缓存。默认情况下,一个Block只有一个数据节点会缓存。但是可以针对每个文件可以个性化配置。
作业调度器可以利用缓存提升性能,例如MapReduce可以把任务运行在有Block缓存的节点上。
用户或者应用可以向NameNode发送缓存指令(缓存哪个文件,缓存多久), 缓存池的概念用于管理一组缓存的权限和资源。

3.4 HDFS Federation

我们知道NameNode的内存会制约文件数量,HDFS Federation提供了一种横向扩展NameNode的方式。在Federation模式中,每个NameNode管理命名空间的一部分,例如一个NameNode管理/user目录下的文件, 另一个NameNode管理/share目录下的文件。
每个NameNode管理一个namespace volumn,所有volumn构成文件系统的元数据。每个NameNode同时维护一个Block Pool,保存Block的节点映射等信息。各NameNode之间是独立的,一个节点的失败不会导致其他节点管理的文件不可用。
客户端使用mount table将文件路径映射到NameNode。mount table是在Namenode群组之上封装了一层,这一层也是一个Hadoop文件系统的实现,通过viewfs:协议访问。

3.5 HDFS HA(High Availability高可用性)

在HDFS集群中,NameNode依然是单点故障(SPOF: Single Point Of Failure)。元数据同时写到多个文件系统以及Second NameNode定期checkpoint有利于保护数据丢失,但是并不能提高可用性。
这是因为NameNode是唯一一个对文件元数据和file-block映射负责的地方, 当它挂了之后,包括MapReduce在内的作业都无法进行读写。

当NameNode故障时,常规的做法是使用元数据备份重新启动一个NameNode。元数据备份可能来源于:

  • 多文件系统写入中的备份
  • Second NameNode的检查点文件

启动新的Namenode之后,需要重新配置客户端和DataNode的NameNode信息。另外重启耗时一般比较久,稍具规模的集群重启经常需要几十分钟甚至数小时,造成重启耗时的原因大致有:
1) 元数据镜像文件载入到内存耗时较长。
2) 需要重放edit log
3) 需要收到来自DataNode的状态报告并且满足条件后才能离开安全模式提供写服务。

Hadoop的HA方案

采用HA的HDFS集群配置两个NameNode,分别处于Active和Standby状态。当Active NameNode故障之后,Standby接过责任继续提供服务,用户没有明显的中断感觉。一般耗时在几十秒到数分钟。
HA涉及到的主要实现逻辑有

1) 主备需共享edit log存储。
主NameNode和待命的NameNode共享一份edit log,当主备切换时,Standby通过回放edit log同步数据。
共享存储通常有2种选择

  • NFS:传统的网络文件系统
  • QJM:quorum journal manager

QJM是专门为HDFS的HA实现而设计的,用来提供高可用的edit log。QJM运行一组journal node,edit log必须写到大部分的journal nodes。通常使用3个节点,因此允许一个节点失败,类似ZooKeeper。注意QJM没有使用ZK,虽然HDFS HA的确使用了ZK来选举主Namenode。一般推荐使用QJM。

2)DataNode需要同时往主备发送Block Report
因为Block映射数据存储在内存中(不是在磁盘上),为了在Active NameNode挂掉之后,新的NameNode能够快速启动,不需要等待来自Datanode的Block Report,DataNode需要同时向主备两个NameNode发送Block Report。

3)客户端需要配置failover模式(失效备援模式,对用户透明)

Namenode的切换对客户端来说是无感知的,通过客户端库来实现。客户端在配置文件中使用的HDFS URI是逻辑路径,映射到一对Namenode地址。客户端会不断尝试每一个Namenode地址直到成功。

4)Standby替代Secondary NameNode
如果没有启用HA,HDFS独立运行一个守护进程作为Secondary Namenode。定期checkpoint,合并镜像文件和edit日志。

如果当主Namenode失败时,备份Namenode正在关机(停止 Standby),运维人员依然可以从头启动备份Namenode,这样比没有HA的时候更省事,算是一种改进,因为重启整个过程已经标准化到Hadoop内部,无需运维进行复杂的切换操作。

NameNode的切换通过代failover controller来实现。failover controller有多种实现,默认实现使用ZooKeeper来保证只有一个Namenode处于active状态。

每个Namenode运行一个轻量级的failover controller进程,该进程使用简单的心跳机制来监控Namenode的存活状态并在Namenode失败时触发failover。Failover可以由运维手动触发,例如在日常维护中需要切换主Namenode,这种情况graceful(优雅的) failover,非手动触发的failover称为ungraceful failover。

在ungraceful failover的情况下,没有办法确定失败(被判定为失败)的节点是否停止运行,也就是说触发failover后,之前的主Namenode可能还在运行。QJM一次只允许一个Namenode写edit log,但是之前的主Namenode仍然可以接受读请求。Hadoop使用fencing来杀掉之前的Namenode。Fencing通过收回之前Namenode对共享的edit log的访问权限、关闭其网络端口使得原有的Namenode不能再继续接受服务请求。使用STONITH技术也可以将之前的主Namenode关机。

最后,HA方案中Namenode的切换对客户端来说是不可见的,前面已经介绍过,主要通过客户端库来完成。

命令行接口:

HDFS提供了各种交互方式,例如通过Java API、HTTP、shell命令行的。命令行的交互主要通过hadoop fs来操作。例如:

hadoop fs -copyFromLocal // 从本地复制文件到HDFS
hadoop fs mkdir // 创建目录
hadoop fs -ls  // 列出文件列表

Hadoop中,文件和目录的权限类似于POSIX模型,包括读、写、执行3种权限:

  • 读权限(r):用于读取文件或者列出目录中的内容
  • 写权限(w):对于文件,就是文件的写权限。目录的写权限指在该目录下创建或者删除文件(目录)的权限。
  • 执行权限(x):文件没有所谓的执行权限,被忽略。对于目录,执行权限用于访问器目录下的内容。

每个文件或目录都有owner,group,mode三个属性,owner指文件的所有者,group为权限组。mode
由所有者权限、文件所属的组中组员的权限、非所有者非组员的权限组成。下图表示其所有者root拥有读写权限,supergroup组的组员有读权限,其他人有读权限。

这里写图片描述

文件权限是否开启通过dfs.permissions.enabled属性来控制,这个属性默认为false,没有打开安全限制,因此不会对客户端做授权校验,如果开启安全限制,会对操作文件的用户做权限校验。特殊用户superuser是Namenode进程的标识,不会针对该用户做权限校验。

最后看一下ls命令的执行结果:

这里写图片描述

这个返回结果类似于Unix系统下的ls命令,第一栏为文件的mode,d表示目录,紧接着3种权限9位。 第二栏是指文件的副本数,这个数量通过dfs.replication配置,目录则使用-表示没有副本一说。其他诸如所有者、组、更新时间、文件大小跟Unix系统中的ls命令一致。

如果需要查看集群状态或者浏览文件目录,可以访问Namenode暴露的Http Server查看集群信息,一般在namenode所在机器的50070端口。

这里写图片描述

这里写图片描述

这里写图片描述

1.基本语法

bin/hadoop fs 具体命令

2.命令大全

[root@hadoop101 hadoop-2.7.2]$ bin/hadoop fs

[-appendToFile <localsrc> ... <dst>]
        [-cat [-ignoreCrc] <src> ...]
        [-checksum <src> ...]
        [-chgrp [-R] GROUP PATH...]
        [-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
        [-chown [-R] [OWNER][:[GROUP]] PATH...]
        [-copyFromLocal [-f] [-p] <localsrc> ... <dst>]
        [-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
        [-count [-q] <path> ...]
        [-cp [-f] [-p] <src> ... <dst>]
        [-createSnapshot <snapshotDir> [<snapshotName>]]
        [-deleteSnapshot <snapshotDir> <snapshotName>]
        [-df [-h] [<path> ...]]
        [-du [-s] [-h] <path> ...]
        [-expunge]
        [-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
        [-getfacl [-R] <path>]
        [-getmerge [-nl] <src> <localdst>]
        [-help [cmd ...]]
        [-ls [-d] [-h] [-R] [<path> ...]]
        [-mkdir [-p] <path> ...]
        [-moveFromLocal <localsrc> ... <dst>]
        [-moveToLocal <src> <localdst>]
        [-mv <src> ... <dst>]
        [-put [-f] [-p] <localsrc> ... <dst>]
        [-renameSnapshot <snapshotDir> <oldName> <newName>]
        [-rm [-f] [-r|-R] [-skipTrash] <src> ...]
        [-rmdir [--ignore-fail-on-non-empty] <dir> ...]
        [-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
        [-setrep [-R] [-w] <rep> <path> ...]
        [-stat [format] <path> ...]
        [-tail [-f] <file>]
        [-test -[defsz] <path>]
        [-text [-ignoreCrc] <src> ...]
        [-touchz <path> ...]
        [-usage [cmd ...]]

3.常用命令实操

(0)启动Hadoop集群(方便后续的测试)

[root@hadoop101 hadoop-2.7.2]$ sbin/start-dfs.sh

[root@hadoop102 hadoop-2.7.2]$ sbin/start-yarn.sh

(1)-help:输出这个命令参数

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -help rm

(2)-ls: 显示目录信息

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -ls /

(3)-mkdir:在hdfs上创建目录

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mkdir -p /sanguo/shuguo

(4)-moveFromLocal从本地剪切粘贴到hdfs

[root@hadoop101 hadoop-2.7.2]$ touch kongming.txt

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo

(5)-appendToFile :追加一个文件到已经存在的文件末尾

[root@hadoop101 hadoop-2.7.2]$ touch liubei.txt

[root@hadoop101 hadoop-2.7.2]$ vim liubei.txt

输入

san gu mao lu

[root@hadoop102 hadoop-2.7.2]$ hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt

(6)-cat:显示文件内容

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -cat /sanguo/shuguo/kongming.txt

(7)-tail:显示一个文件的末尾

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -tail /sanguo/shuguo/kongming.txt

(8)-chgrp 、-chmod、-chown:linux文件系统中的用法一样,修改文件所属权限

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -chown root:root /sanguo/shuguo/kongming.txt

(9)-copyFromLocal:从本地文件系统中拷贝文件到hdfs路径去

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -copyFromLocal README.txt /

(10)-copyToLocal:从hdfs拷贝到本地

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./

(11)-cp :从hdfs的一个路径拷贝到hdfs的另一个路径

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt

(12)-mv:在hdfs目录中移动文件

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mv /zhuge.txt /sanguo/shuguo/

(13)-get:等同于copyToLocal,就是从hdfs下载文件到本地

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -get /sanguo/shuguo/kongming.txt ./

(14)-getmerge :合并下载多个文件,比如hdfs的目录 /aaa/下有多个文件:log.1, log.2,log.3,…

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -getmerge /sanguo/shuguo/* ./zaiyiqi.txt

(15)-put:等同于copyFromLocal

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -put ./zaiyiqi.txt /sanguo/shuguo/

(16)-rm:删除文件或文件夹

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -rm /user/root/test/jinlian2.txt

(17)-rmdir:删除空目录(了解)

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mkdir /test

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -rmdir /test

(18)-du统计文件夹的大小信息

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -du -s -h /user/root/test

2.7 K /user/root/test

[root@hadoop102 hadoop-2.7.2]$ hadoop fs -du -h /user/root/test

1.3 K /user/root/test/README.txt

15 /user/root/test/jinlian.txt

1.4 K /user/root/test/zaiyiqi.txt

(19)-setrep:设置hdfs中文件的副本数量

[root@hadoop101 hadoop-2.7.2]$ hadoop fs -setrep 10 /sanguo/shuguo/kongming.txt

这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看DataNode的数量。因为目前只有3台设备,最多也就3个副本,只有节点数的增加到10台时,副本数才能达到10。

Hadoop文件系统:

前面Hadoop的文件系统概念是抽象的,HDFS只是其中的一种实现。Hadoop提供的实现如下图:

这里写图片描述

这里写图片描述

简单介绍一下,Local是对本地文件系统的抽象,hdfs就是我们最常见的,两种web形式(webhdfs,swebhdfs)的实现通过HTTP提供文件操作接口。har是Hadoop体系下的压缩文件,当文件很多的时候可以压缩成一个大文件,可以有效减少元数据的数量。viewfs就是我们前面介绍HDFS Federation张提到的,用来在客户端屏蔽多个Namenode的底层细节。ftp顾名思义,就是使用ftp协议来实现,对文件的操作转化为ftp协议。s3a是对Amazon云服务提供的存储系统的实现,azure则是微软的云服务平台实现。

前面我们提到了使用命令行跟HDFS交互,事实上还有很多方式来操作文件系统。例如Java应用程序可以使用org.apache.hadoop.fs.FileSystem来操作,其他形式的操作也都是基于FileSystem进行封装。我们这里主要介绍一下HTTP的交互方式。
WebHDFS和SWebHDFS协议将文件系统暴露HTTP操作,这种交互方式比原生的Java客户端慢,不适合操作大文件。通过HTTP,有2种访问方式,直接访问和通过代理访问

直接访问
直接访问的示意图如下:

这里写图片描述

Namenode和Datanode默认打开了嵌入式web server,即dfs.webhdfs.enabled默认为true。webhdfs通过这些服务器来交互。元数据的操作通过namenode完成,文件的读写首先发到namenode,然后重定向到datanode读取(写入)实际的数据流。

通过HDFS代理

这里写图片描述

采用代理的示意图如上所示。 使用代理的好处是可以通过代理实现负载均衡或者对带宽进行限制,或者防火墙设置。代理通过HTTP或者HTTPS暴露为WebHDFS,对应为webhdfs和swebhdfs URL Schema。

代理作为独立的守护进程,独立于namenode和datanode,使用httpfs.sh脚本,默认运行在14000端口

除了FileSystem直接操作,命令行,HTTTP外,还有C语言API,NFS,FUSER等方式,这里不做过多介绍。

Java接口:

实际的应用中,对HDFS的大多数操作还是通过FileSystem来操作,这部分重点介绍一下相关的接口,主要关注HDFS的实现类DistributedFileSystem及相关类。

6.1 读操作

可以使用URL来读取数据,或者直接使用FileSystem操作。

从Hadoop URL读取数据

java.net.URL类提供了资源定位的统一抽象,任何人都可以自己定义一种URL Schema,并提供相应的处理类来进行实际的操作。hdfs schema便是这样的一种实现。

InputStream in = null;
try {
 in = new URL("hdfs://master/user/hadoop").openStream();
}finally{
 IOUtils.closeStream(in);
}

为了使用自定义的Schema,需要设置URLStreamHandlerFactory,这个操作一个JVM只能进行一次,多次操作会导致不可用,通常在静态块中完成。下面的截图是一个使用示例:

这里写图片描述

这里写图片描述

使用FileSystem API读取数据

1) 首先获取FileSystem实例,一般使用静态get工厂方法

public static FileSystem get(Configuration conf) throws IOException
public static FileSystem get(URI uri , Configuration conf) throws IOException
public static FileSystem get(URI uri , Configuration conf,String user) throws IOException

如果是本地文件,通过getLocal获取本地文件系统对象:

public static LocalFileSystem getLocal(COnfiguration conf) thrown IOException

2)调用FileSystem的open方法获取一个输入流:

public FSDataInputStream open(Path f) throws IOException
public abstarct FSDataInputStream open(Path f , int bufferSize) throws IOException

默认情况下,open使用4KB的Buffer,可以根据需要自行设置。

3)使用FSDataInputStream进行数据操作
FSDataInputStream是java.io.DataInputStream的特殊实现,在其基础上增加了随机读取、部分读取的能力

public class FSDataInputStream extends DataInputStream
    implements Seekable, PositionedReadable, 
      ByteBufferReadable, HasFileDescriptor, CanSetDropBehind, CanSetReadahead,
      HasEnhancedByteBufferAccess

随机读取操作通过Seekable接口定义:

public interface Seekable {
    void seek(long pos) throws IOException;
    long getPos() throws IOException;
}

seek操作开销昂贵,慎用。

部分读取通过PositionedReadable接口定义:

public interface PositionedReadable{
    public int read(long pistion ,byte[] buffer,int offser , int length) throws IOException;
    public int readFully(long pistion ,byte[] buffer,int offser , int length) throws IOException;
    public int readFully(long pistion ,byte[] buffer) throws IOException;
}

HDFS客户端环境准备

1.创建一个Maven工程HdfsClientDemo

导入相应的依赖坐标+日志添加

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>

注意:如果eclipse/idea打印不出日志,在控制台上只显示

1.log4j:WARN No appenders could be found for logger (org.apache.hadoop.util.Shell)

2.log4j:WARN Please initialize the log4j system properly

3.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info

需要在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入

log4j.rootLogger=debug, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

2.创建包名:com.hadoop.hdfs

3.创建HdfsClient类

public class HdfsClient{
@Test
public void testMkdirs() throws IOException, InterruptedException, URISyntaxException{undefined
// 1 获取文件系统
Configuration configuration = new Configuration();
// 配置在集群上运行
// configuration.set("fs.defaultFS", "hdfs://hadoop101:9000");
// FileSystem fs = FileSystem.get(configuration);
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");
// 2 创建目录
fs.mkdirs(new Path("/2019"));
// 3 关闭资源
fs.close();
}
}

3.2 HDFS的API操作

3.2.1HDFS文件上传

1.编写源代码

@Test
public void testCopyFromLocalFile() throws IOException, InterruptedException, URISyntaxException {
// 1 获取文件系统
Configuration configuration = new Configuration();
configuration.set("dfs.replication", "2");
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");
// 2 上传文件
fs.copyFromLocalFile(new Path("e:/hello.txt"), new Path("/hello.txt"));
// 3 关闭资源
fs.close();
System.out.println("over");
}

2.将hdfs-site.xml拷贝到项目的根目录下

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>

<configuration>
<property>
<name>dfs.replication</name>
        <value>1</value>
</property>
</configuration>

3.参数优先级

参数优先级排序: (1)客户端代码中设置的值 >(2)classpath下的用户自定义配置文件 >(3)然后是服务器的默认配置

3.2.2HDFS文件下载

@Test
public void testCopyToLocalFile() throws IOException, InterruptedException, URISyntaxException{undefined
// 1 获取文件系统
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");
// 2 执行下载操作
// boolean delSrc 指是否将原文件删除
// Path src 指要下载的文件路径
// Path dst 指将文件下载到的路径
// boolean useRawLocalFileSystem 是否开启文件校验
fs.copyToLocalFile(false, new Path("/hello1.txt"), new Path("e:/hello1.txt"), true);
// 3 关闭资源
fs.close();
}

3.2.3HDFS文件夹删除

@Test
public void testDelete() throws IOException, InterruptedException, URISyntaxException{undefined
// 1 获取文件系统
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");
// 2 执行删除
fs.delete(new Path("/1108/"), true);
// 3 关闭资源
fs.close();
}

3.2.4HDFS文件名更改

@Test
public void testRename() throws IOException, InterruptedException, URISyntaxException{undefined
// 1 获取文件系统
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");
// 2 修改文件名称
fs.rename(new Path("/hello.txt"), new Path("/hello6.txt"));
// 3 关闭资源
fs.close();
}

3.2.5HDFS文件和文件夹判断

@Test

public void testListStatus() throws IOException, InterruptedException, URISyntaxException{undefined

 

// 1 获取文件配置信息

Configuration configuration = new Configuration();

FileSystem fs = FileSystem.get(new URI("hdfs://hadoop101:9000"), configuration, "root");

 

// 2 判断是文件还是文件夹

FileStatus[] listStatus = fs.listStatus(new Path("/"));

 

for (FileStatus fileStatus : listStatus) {undefined

 

// 如果是文件

if (fileStatus.isFile()) {undefined

System.out.println("f:"+fileStatus.getPath().getName());

}else {undefined

System.out.println("d:"+fileStatus.getPath().getName());

}

}

 

// 3 关闭资源

fs.close();

}

相关运维工具:

8.1 使用distcp并行复制

前面的关注点都在于单线程的访问,如果需要并行处理文件,需要自己编写应用。Hadoop提供的distcp工具用于并行导入数据到Hadoop或者从Hadoop导出。一些例子:

hadoop distcp file1 file2  //可以作为fs -cp命令的高效替代
hadoop distcp dir1 dir2
hadoop distcp -update dir1 dir2 #update参数表示只同步被更新的文件,其他保持不变

distcp是底层使用MapReduce实现,只有map实现,没有reduce。在map中并行复制文件。 distcp尽可能在map之间平均分配文件。map的数量可以通过-m参数指定:

hadoop distcp -update -delete -p hdfs://master1:9000/foo hdfs://master2/foo 

这样的操作常用于在两个集群之间复制数据,update参数表示只同步被更新过的数据,delete会删除目标目录中存在,但是源目录不存在的文件。p参数表示保留文件的全校、block大小、副本数量等属性。

如果两个集群的Hadoop版本不兼容,可以使用webhdfs协议:

hadoop distcp webhdfs://namenode1:50070/foo webhdfs://namenode2:50070/foo

8.2 平衡HDFS集群

在distcp工具中,如果我们指定map数量为1,不仅速度很慢,每个Block第一个副本将全部落到运行这个唯一map的节点上,直到磁盘溢出。因此使用distcp的时候,最好使用默认的map数量,即20.
HDFS在Block均匀分布在各个节点上的时候工作得最好,如果没有办法在作业中尽量保持集群平衡,例如为了限制map数量(以便其他节点可以被别的作业使用),那么可以使用balancer工具来调整集群的Block分布。

Swift:

Swift 最初是由Rackspace公司开发的分布式对象存储服务, 2010 年贡献给 OpenStack 开源社区。作为其最初的核心子项目之一,为其 Nova 子项目提供虚机镜像存储服务。

Swift架构:

Swift 采用完全对称、面向资源的分布式系统架构设计,所有组件都可扩展,避免因单点失效而影响整个系统的可用性。

在这里插å
¥å›¾ç‰‡æè¿°

Swift特点:

 原生的对象存储,不支持实时的文件读写、编辑功能
 完全对称架构,无主节点,无单点故障,易于大规模扩展,性能容量线性增长
 数据实现最终一致性,不需要所有副本写入即可返回,读取数据时需要进行数据副本的校验
 是OpenStack的子项目之一,适合云环境的部署
 Swift的对象存储与Ceph提供的对象存储区别:客户端在访问对象存储系统服务时,Swift要求客户端必须访问Swift网关才能获得数据。而Ceph可以在每个存储节点上的OSD(对象存储设备)获取数据信息; 在数据一致性方面,Swift的数据是最终一致,而Ceph是始终跨集群强一致性)

Swift 组件:

 代理服务(Proxy Server):对外提供对象服务 API,转发请求至相应的账户、容器或对象服务
 认证服务(Authentication Server):验证用户的身份信息,并获得一个访问令牌(Token)
 缓存服务(Cache Server):缓存令牌,账户和容器信息,但不会缓存对象本身的数据
 账户服务(Account Server):提供账户元数据和统计信息,并维护所含容器列表的服务
 容器服务(Container Server):提供容器元数据和统计信息,并维护所含对象列表的服务
 对象服务(Object Server):提供对象元数据和内容服务,每个对象会以文件存储在文件系统中
 复制服务(Replicator):检测本地副本和远程副本是否一致,采用推式(Push)更新远程副本
 更新服务(Updater):对象内容的更新
 审计服务(Auditor):检查对象、容器和账户的完整性,如果发现错误,文件将被隔离
 账户清理服务(Account Reaper):移除被标记为删除的账户,删除其所包含的所有容器和对象

Swift数据模型:

**Swift的数据模型采用层次结构,共设三层:**Account/Container/Object(即账户/容器/对象),每层节点数均没有限制,可以任意扩展。数据模型如下:

在这里插å
¥å›¾ç‰‡æè¿°

一致性散列函数:

Swift是基于一致性散列技术,通过计算将对象均匀分布到虚拟空间的虚拟节点上,在增加或删除节点时可大大减少需移动的数据量;
为便于高效的移位操作,虚拟空间大小通常采用 2 n;通过独特的数据结构 Ring(环),再将虚拟节点映射到实际的物理存储设备上,完成寻址过程。如下图所示:

在这里插å
¥å›¾ç‰‡æè¿°

散列空间4 个字节(32为),虚拟节点数最大为232,如将散列结果右移 m 位,可产生 2(32-m)个虚拟节点,(如上图中所示,当m=29 时,可产生 8 个虚拟节点)。

环的数据结构:

Swift为账户、容器和对象分别定义了的环。
环是为了将虚拟节点(分区)映射到一组物理存储设备上,并提供一定的冗余度而设计的,环的数据信息包括存储设备列表和设备信息、分区到设备的映射关系、计算分区号的位移(即上图中的m)。
账户、容器和对象的寻址过程。(以对象的寻址过程为例):

  1. 以对象的层次结构 account/container/object 作为键,采用 MD5 散列算法得到一个散列值;
  2. 对该散列值的前 4 个字节进行右移操作(右移m位),得到分区索引号;
  3. 在分区到设备映射表里,按照分区索引号,查找该对象所在分区对应的所有物理设备编号。如下图:

在这里插å
¥å›¾ç‰‡æè¿°

Swift的一致性设计:

Swift 采用 Quorum 仲裁协议
 定义:N:数据的副本总数;W:写操作被确认接受的副本数量;R:读操作的副本数量
 强一致性:R+W>N, 就能保证对副本的读写操作会产生交集,从而保证可以读取到最新版本;
 弱一致性:R+W<=N,读写操作的副本集合可能不产生交集,此时就可能会读到脏数据;
Swift 默认配置是N=3,W=2,R=2,即每个对象会存在 3 个副本,至少需要更新 2 个副本才算写成功;如果读到的2个数据存在不一致,则通过检测和复制协议来完成数据同步。
如R=1,就可能会读到脏数据,此时,通过牺牲一定的一致性,可提高读取速度,(而一致性可以通过后台的方式完成同步,从而保证数据的最终一致性)
Quorum 协议示例如下所示:

在这里插å
¥å›¾ç‰‡æè¿°

TFS:

TFS(Taobao File System)是由淘宝开发的一个分布式文件系统,其内部经过特殊的优化处理,适用于海量的小文件存储,目前已经对外开源;

TFS采用自有的文件系统格式存储,因此需要专用的API接口去访问,目前官方提供的客户端版本有:C++/JAVA/PHP。

特性:

1)在TFS文件系统中,NameServer负责管理文件元数据,通过HA机制实现主备热切换,由于所有元数据都是在内存中,其处理效率非常高效,系统架构也非常简单,管理也很方便;
2)TFS的DataServer作为分部署数据存储节点,同时也具备负载均衡和冗余备份的功能,由于采用自有的文件系统,对小文件会采取合并策略,减少数据碎片,从而提升IO性能;
3)TFS将元数据信息(BlockID、FileID)直接映射至文件名中,这一设计大大降低了存储元数据的内存空间;

优点:

1)针对小文件量身定做,随机IO性能比较高;
2)支持在线扩容机制,增强系统的可扩展性;
3)实现了软RAID,增强系统的并发处理能力及数据容错恢复能力;
4)支持主备热倒换,提升系统的可用性;
5)支持主从集群部署,其中从集群主要提供读/备功能;

缺点:

1)TFS只对小文件做优化,不适合大文件的存储;
2)不支持POSIX通用接口访问,通用性较低;
3)不支持自定义目录结构,及文件权限控制;
4)通过API下载,存在单点的性能瓶颈;
5)官方文档非常少,学习成本高;

应用场景:

1)多集群部署的应用
2)存储后基本不做改动
3)海量小型文件
根据目前官方提供的材料,对单个集群节点,存储节点在1000台以内可以良好工作,如存储节点扩大可能会出现NameServer的性能瓶颈,目前淘宝线上部署容量已达到1800TB规模(2009年数据)

安装及使用

安装指导

TFS_配置使用

源代码路径:http://code.taobao.org/p/tfs/src/

FastDFS:

FastDFS是一个分布式文件系统

官方网址:https://github.com/happyfish100/fastdfs

FastDFS开源地址:https://github.com/happyfish100

FastDFS是一个开源的轻量级分布式文件系统,为互联网应用量身定做,简单、灵活、高效,采用C语言开发,由阿里巴巴开发并开源。

FastDFS是一款类Google FS的开源分布式文件系统,它用纯C语言实现,支持Linux、FreeBSD、AIX等UNIX系统。它只能通过 专有API对文件进行存取访问,不支持POSIX接口方式,不能mount使用。准确地讲,Google FS以及FastDFS、mogileFS、 HDFS、TFS等类Google FS都不是系统级的分布式文件系统,而是应用级的分布式文件存储服务。

FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。

FastDFS简介:

FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。
特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS服务端有两个角色:跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。
存储节点存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,FastDFS同时对文件的metadata进行管理。所谓文件的meta data就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的key为width,value为1024。文件metadata是文件属性列表,可以包含多个键值对。
跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
FastDFS中的文件标识分为两个部分:卷名和文件名,二者缺一不可。

FastDFS对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载、文件删除)等,解决了大容量文件存储的问题,特别适合以文件为载体的在线服务,如相册网站、文档网站、图片网站、视频网站等等。

FastDFS充分考虑了冗余备份、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

系统中存在三种节点:Client、Tracker、Storage,在底层存储上通过逻辑的分组概念,使得通过在同组内配置多个Storage,从而实现软RAID10,提升并发IO的性能、简单负载均衡及数据的冗余备份;同时通过线性的添加新的逻辑存储组,从容实现存储容量的线性扩容。

文件下载上,除了支持通过API方式,目前还提供了apache和nginx的插件支持,同时也可以不使用对应的插件,直接以Web静态资源方式对外提供下载。

目前FastDFS(V4.x)代码量大概6w多行,内部的网络模型使用比较成熟的libevent三方库,具备高并发的处理能力。

常见术语:

  • tracker:追踪者服务器,主要用于协调调度,可以起到负载均衡的作用,记录storage的相关状态信息。
  • storage:存储服务器,用于保存文件以及文件的元数据信息。
  • group:组,同组节点提供冗余备份,不同组用于扩容。
  • mata data:文件的元数据信息,比如长宽信息,图片后缀,视频的帧数等。

FastDFS特性:

1)在上述介绍中Tracker服务器是整个系统的核心枢纽,其完成了访问调度(负载均衡),监控管理Storage服务器,由此可见Tracker的作用至关重要,也就增加了系统的单点故障,为此FastDFS支持多个备用的Tracker,虽然实际测试发现备用Tracker运行不是非常完美,但还是能保证系统可用。
2)在文件同步上,只有同组的Storage才做同步,由文件所在的源Storage服务器push至其它Storage服务器,目前同步是采用Binlog方式实现,由于目前底层对同步后的文件不做正确性校验,因此这种同步方式仅适用单个集群点的局部内部网络,如果在公网上使用,肯定会出现损坏文件的情况,需要自行添加文件校验机制。
3)支持主从文件,非常适合存在关联关系的图片,在存储方式上,FastDFS在主从文件ID上做取巧,完成了关联关系的存储。

FastDFS优缺点:

优点:

1)系统无需支持POSIX(可移植操作系统),降低了系统的复杂度,处理效率更高
2)支持在线扩容机制,增强系统的可扩展性
3)实现了软RAID,增强系统的并发处理能力及数据容错恢复能力
4)支持主从文件,支持自定义扩展名
5)主备Tracker服务,增强系统的可用性

缺点:

1)不支持POSIX通用接口访问,通用性较低
2)对跨公网的文件同步,存在较大延迟,需要应用做相应的容错策略
3)同步机制不支持文件正确性校验,降低了系统的可用性
4)通过API下载,存在单点的性能瓶颈

FastDFS应用场景:

1)单集群部署的应用
2)存储后基本不做改动
3)小中型文件根据
目前官方提供的材料,现有的使用FastDFS系统存储容量已经达到900T,物理机器已经达到100台(50个组)

安装指导_FastDFS

源码路径:https://github.com/happyfish100/fastdfs

FastDFS的发展历史:

  • 2008年4月项目正式启动
  • 2008年7月推出V1.00
  • 2010年8月推出V2.00
  • 2011年6月推出V3.00
  • 2012年10月推出V4.00
  • 2013年12月推出V5.00
  • 2019年10月推出V6.00

FastDFS系统架构从第一个版本发布后一直没有大的调整,高版本完全兼容低版本的数据,可以做到平滑升级,推荐更新升级到最新版本。

FastDFS的整体架构:

FastDFS文件系统由两大部分构成,一个是客户端,一个是服务端。

客户端通常指我们的程序,比如我们的Java程序去连接FastDFS、操作FastDFS,那我们的Java程序就是一个客户端,FastDFS提供专有API访问,目前提供了C、Java和PHP几种编程语言的API,用来访问FastDFS文件系统。

服务端由两个部分构成:一个是跟踪器(tracker),一个是存储节点(storage)。

跟踪器(tracker)主要做调度工作,在内存中记录集群中存储节点storage的状态信息,是前端Client和后端存储节点storage的枢纽。因为相关信息全部在内存中,Tracker server的性能非常高,一个较大的集群(比如上百个group)中有3台就足够了。

存储节点(storage)用于存储文件,包括文件和文件属性(meta data)都保存到存储服务器磁盘上,完成文件管理的所有功能:文件存储、文件同步和提供文件访问等。

FastDFS架构包括 Tracker server和Storage server。客户端请求Tracker server进行文件上传、下载,通过Trackerserver调度最终由Storage server完成文件上传和下载。

FastDFS 系统有三个角色:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。

Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。通过Trackerserver在文件上传时可以根据一些策略找到Storageserver提供文件上传服务。

Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。Storage server没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。

Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。

img

1.2.1 Tracker 集群

​ FastDFS集群中的Tracker server可以有多台,Trackerserver之间是相互平等关系同时提供服务,Trackerserver不存在单点故障。客户端请求Trackerserver采用轮询方式,如果请求的tracker无法提供服务则换另一个tracker。

1.2.2 Storage 集群

为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷由一台或多台存储服务器组成,卷内的Storage server之间是平等关系,不同卷的Storageserver之间不会相互通信,同卷内的Storageserver之间会相互连接进行文件同步,从而保证同组内每个storage上的文件完全一致的。一个卷的存储容量为该组内存储服务器容量最小的那个,由此可见组内存储服务器的软硬件配置最好是一致的。卷中的多台存储服务器起到了冗余备份和负载均衡的作用

在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。

采用分组存储方式的好处是灵活、可控性较强。比如上传文件时,可以由客户端直接指定上传到的组也可以由tracker进行调度选择。一个分组的存储服务器访问压力较大时,可以在该组增加存储服务器来扩充服务能力(纵向扩容)。当系统容量不足时,可以增加组来扩充存储容量(横向扩容)。

1.2.3 Storage状态收集

​ Storage server会连接集群中所有的Tracker server,定时向他们报告自己的状态,包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息。

1.2.4 FastDFS的上传过程

FastDFS向使用者提供基本文件访问接口,比如upload、download、append、delete等,以客户端库的方式提供给用户使用。

Storage Server会定期的向Tracker Server发送自己的存储信息。当Tracker Server Cluster中的Tracker Server不止一个时,各个Tracker之间的关系是对等的,所以客户端上传时可以选择任意一个Tracker。

当Tracker收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的group,当选定了group后就要决定给客户端分配group中的哪一个storage server。当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录。然后为文件分配一个fileid,最后根据以上的信息生成文件名存储文件。

img

客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

img

组名:文件上传后所在的storage组名称,在文件上传成功后有storage服务器返回,需要客户端自行保存。

虚拟磁盘路径:storage配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1则是M01,以此类推。

数据两级目录:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

1.2.5 FastDFS的文件同步

写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由后台线程将文件同步至同group内其他的storage server。

每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。

storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。

1.2.6 FastDFS的文件下载

客户端uploadfile成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。

img

跟upload file一样,在downloadfile时客户端可以选择任意tracker server。tracker发送download请求给某个tracker,必须带上文件名信息,tracke从文件名中解析出文件的group、大小、创建时间等信息,然后为该请求选择一个storage用来服务读请求。

tracker根据请求的文件路径即文件ID 来快速定义文件。

比如请求下边的文件:
img

1.通过组名tracker能够很快的定位到客户端需要访问的存储服务器组是group1,并选择合适的存储服务器提供客户端访问。

2.存储服务器根据“文件存储虚拟磁盘路径”和“数据文件两级目录”可以很快定位到文件所在目录,并根据文件名找到客户端需要访问的文件。

**FastDFS服务有三个角色:跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client): **

tracker server: 跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳, Tracker根据storage心跳信息,建立group—>[storage server list]的映射表;tracker管理的元数据很少,会直接存放在内存;tracker 上的元信息都是由 storage 汇报的信息生成的,本身不需要持久化任何数据,tracker 之间是对等关系,因此扩展 tracker 服务非常容易,之间增加 tracker服务器即可,所有tracker都接受stroage心跳信息,生成元数据信息来提供读写服务(与 其他 Master-Slave 架构的优势是没有单点,tracker 也不会成为瓶颈,最终数据是和一个可用的 Storage Server进行传输的)
 storage server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以包含多台storage server,数据互为备份,存储容量空间以group内容量最小的storage为准;建 议group内的storage server配置相同;以group为单位组织存储能够方便的进行应用隔离、负载均衡和副本数定制;缺点是 group 的容量受单机存储容量的限制,同时 group 内机器坏掉,数据 恢复只能依赖 group 内其他机器重新同步(坏盘替换,重新挂载重启 fdfs_storaged 即可)

多个group之间的存储方式有3种策略:round robin(轮询)、load balance(选择最大剩余空 间的组上传文件)、specify group(指定group上传)

group 中 storage 存储依赖本地文件系统,storage 可配置多个数据存储目录,磁盘不做 raid, 直接分别挂载到多个目录,将这些目录配置为 storage 的数据目录即可
storage 接受写请求时,会根据配置好的规则,选择其中一个存储目录来存储文件;为避免单 个目录下的文件过多,storage 第一次启时,会在每个数据存储目录里创建 2 级子目录,每级 256 个,总共 65536 个,新写的文件会以 hash 的方式被路由到其中某个子目录下,然后将文件数据直 接作为一个本地文件存储到该目录中

这里写图片描述

总结:1.高可靠性:无单点故障 2.高吞吐性:只要Group足够多,数据流量是足够分散的

FastDFS的使用用户:

  • UC(http://www.uc.cn/)
  • 支付宝(http://www.alipay.com/)
  • 京东商城(http://www.jd.com/)
  • 淘淘搜(http://www.taotaosou.com/)
  • 飞信(http://feixin.10086.cn/)
  • 赶集网(http://www.ganji.com/)
  • 淘米网(http://www.61.com/)
  • 迅雷(http://www.xunlei.com/)
  • 蚂蜂窝(http://www.mafengwo.cn/)
  • 5173(http://www.5173.com/)
  • 华师京城教育云平台(http://www.hsjdy.com.cn/)
  • 视友网(http://www.cuctv.com/)
  • 搜道网(http://www.sodao.com/)
  • 58同城(http://www.58.com/)
  • 商务联盟网(http://www.biz72.com/)
  • 中青网(http://www.youth.cn/)
  • 保利威视(http://www.freeovp.com/)
  • 梦芭莎(http://www.moonbasa.com/)
  • 51CTO(http://www.51cto.com/)
  • 搜房网(http://www.soufun.com/)

FastDFS部署:

FastDFS单实例部署:

2.1、环境准备

虚拟机的版本:VMware-workstation-full-15.5.6-16341506.exe
系统镜像版本:CentOS-6.10-x86_64-bin-DVD1.iso,全新安装,桌面版,可上网
系统内存大小:512MB,这里修改小点,方便后边集群复制
系统硬盘大小:20GB
连接工具版本:SecureCRTSecureFX_HH_x64_7.0.0.326.zip

2.2、安装依赖

[root@caochenlei ~]# yum install -y gcc gcc-c++ perl perl-devel openssl openssl-devel pcre pcre-devel zlib zlib-devel libevent libevent-devel

2.3、安装libfastcommon库

下载:

[root@caochenlei ~]# wget https://github.com/happyfish100/libfastcommon/archive/V1.0.43.tar.gz

解压:

[root@caochenlei ~]# tar -zxvf V1.0.43.tar.gz

切换:

[root@caochenlei ~]# cd libfastcommon-1.0.43

编译:

[root@caochenlei libfastcommon-1.0.43]# ./make.sh

安装:

[root@caochenlei libfastcommon-1.0.43]# ./make.sh install
[root@caochenlei libfastcommon-1.0.43]# cd ~

2.4、安装FastDFS

下载:

[root@caochenlei ~]# wget https://github.com/happyfish100/fastdfs/archive/V6.06.tar.gz

解压:

[root@caochenlei ~]# tar -zxvf V6.06.tar.gz

切换:

[root@caochenlei ~]# cd fastdfs-6.06

编译:

[root@caochenlei fastdfs-6.06]# ./make.sh

安装:

[root@caochenlei fastdfs-6.06]# ./make.sh install

查看可执行文件:

[root@caochenlei fastdfs-6.06]# ll /usr/bin/fdfs*

image-20200905183808335

查看配置文件

[root@caochenlei fastdfs-6.06]# ll /etc/fdfs/

image-20200905184005284

拷贝其它配置:

[root@caochenlei fastdfs-6.06]# cd conf

[root@caochenlei conf]# cp http.conf /etc/fdfs/
[root@caochenlei conf]# cp mime.types /etc/fdfs/

[root@caochenlei conf]# cd /etc/fdfs/

[root@caochenlei fdfs]# mv client.conf.sample client.conf
[root@caochenlei fdfs]# mv storage.conf.sample storage.conf
[root@caochenlei fdfs]# mv storage_ids.conf.sample storage_ids.conf
[root@caochenlei fdfs]# mv tracker.conf.sample tracker.conf

2.5、配置FastDFS

配置 tracker :

修改tracker.conf的以下几项配置项:vi tracker.conf

  • #配置tracker存储数据的目录
    • base_path = /opt/fastdfs/tracker

创建相对应的文件夹:

[root@caochenlei fdfs]# mkdir -p /opt/fastdfs/tracker

配置 storage :

修改storage.conf的以下几项配置项:vi storage.conf

  • #storage存储数据目录

    • base_path = /opt/fastdfs/storage
  • #真正存放文件的目录

    • store_path0 = /opt/fastdfs/storage/files
  • #注册当前存储节点的跟踪器地址

    • tracker_server = 192.168.239.128:22122

创建相对应的文件夹:

[root@caochenlei fdfs]# mkdir -p /opt/fastdfs/storage
[root@caochenlei fdfs]# mkdir -p /opt/fastdfs/storage/files

2.6、启动FastDFS

启动 tracker :

[root@caochenlei fdfs]# fdfs_trackerd /etc/fdfs/tracker.conf

启动 storage :

[root@caochenlei fdfs]# fdfs_storaged /etc/fdfs/storage.conf

查看启动情况:

[root@caochenlei fdfs]# ps -ef | grep fdfs

image-20200905201134490

检查监控信息:

[root@caochenlei fdfs]# fdfs_monitor /etc/fdfs/storage.conf

查看数据目录:

[root@caochenlei fdfs]# ls /opt/fastdfs/storage/files/data/

2.7、重启FastDFS

重启 tracker :

[root@caochenlei fdfs]# fdfs_trackerd /etc/fdfs/tracker.conf restart
waiting for pid [32335] exit ...
starting ...

重启 storage :

[root@caochenlei fdfs]# fdfs_storaged /etc/fdfs/storage.conf restart
waiting for pid [32375] exit ...
starting ...

查看启动情况:

[root@caochenlei fdfs]# ps -ef | grep fdfs

image-20200905202624605

2.8、测试FastDFS

配置 client :

修改client.conf的以下几项配置项:vi client.conf

  • #client存储数据目录

    • base_path = /opt/fastdfs/client
  • #注册当前存储节点的跟踪器地址

    • tracker_server = 192.168.239.128:22122

创建相对应的文件夹:

[root@caochenlei fdfs]# mkdir -p /opt/fastdfs/client

创建 a.txt :

[root@caochenlei fdfs]# echo "Hello,FastDFS" > a.txt

上传 a.txt :

格式:fdfs_test /etc/fdfs/client.conf upload 文件路径

[root@caochenlei fdfs]# fdfs_test /etc/fdfs/client.conf upload /etc/fdfs/a.txt
This is FastDFS client test program v6.06

Copyright (C) 2008, Happy Fish / YuQing

FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.fastken.com/ 
for more detail.

[2020-09-05 20:39:01] DEBUG - base_path=/opt/fastdfs/client, connect_timeout=5, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0

tracker_query_storage_store_list_without_group: 
        server 1. group_name=, ip_addr=192.168.239.128, port=23000

group_name=group1, ip_addr=192.168.239.128, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806.txt
source ip address: 192.168.239.128
file timestamp=2020-09-05 20:39:01
file size=14
file crc32=3519913659
example file url: http://192.168.239.128/group1/M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806.txt
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt
source ip address: 192.168.239.128
file timestamp=2020-09-05 20:39:01
file size=14
file crc32=3519913659
example file url: http://192.168.239.128/group1/M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt

注意:group_name、remote_filename

地址格式,举例如下图:

image-20200905205221928

查看上传后的数据文件:

[root@caochenlei fdfs]# ll /opt/fastdfs/storage/files/data/00/00/
总用量 16
-rw-r--r--. 1 root root 14 9月   5 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt
-rw-r--r--. 1 root root 49 9月   5 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt-m
-rw-r--r--. 1 root root 14 9月   5 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806.txt
-rw-r--r--. 1 root root 49 9月   5 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806.txt-m

删除 a.txt :

格式:fdfs_delete_file /etc/fdfs/client.conf (group_name)/(remote_filename)

[root@caochenlei fdfs]# fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806.txt
[root@caochenlei fdfs]# ll /opt/fastdfs/storage/files/data/00/00/
总用量 8
-rw-r--r--. 1 root root 14 95 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt
-rw-r--r--. 1 root root 49 95 20:39 wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt-m

[root@caochenlei fdfs]# fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKjvgF9ThuWAGUrIAAAADtHNnrs806_big.txt
[root@caochenlei fdfs]# ll /opt/fastdfs/storage/files/data/00/00/
总用量 0

2.9、关闭FastDFS

关闭 tracker :

[root@caochenlei fdfs]# fdfs_trackerd /etc/fdfs/tracker.conf stop
waiting for pid [32441] exit ...
pid [32441] exit.

关闭 storage :

[root@caochenlei fdfs]# fdfs_storaged /etc/fdfs/storage.conf stop
waiting for pid [32453] exit ...
pid [32453] exit.

查看启动情况:

[root@caochenlei fdfs]# ps -ef | grep fdfs

注意问题:

  1. 没有搭建集群默认只有一个组group1
  2. 后缀名包含-m的为属性文件(meta)
  3. 在Linux中并没有磁盘一说,M00是虚拟的,它其实就是data目录
  4. 现在FastDFS已经安装完成,但现在外部还不能访问,有两大原因:一是防火墙未关闭、二是FastDFS默认不支持外部访问,想要访问需要继续往下学习

2.10、开启FastDFS外部访问

启动 tracker :

[root@caochenlei fdfs]# fdfs_trackerd /etc/fdfs/tracker.conf

启动 storage :

[root@caochenlei fdfs]# fdfs_storaged /etc/fdfs/storage.conf

关闭防火墙:

[root@caochenlei fdfs]# service iptables stop
[root@caochenlei fdfs]# chkconfig iptables off

回退根目录:

[root@caochenlei fdfs]# cd ~

Nginx依赖:

[root@caochenlei ~]# yum install -y gcc gcc-c++ make libtool wget pcre pcre-devel zlib zlib-devel openssl openssl-devel

Nginx下载:

[root@caochenlei ~]# wget http://nginx.org/download/nginx-1.18.0.tar.gz

Nginx解压:

[root@caochenlei ~]# tar -zxvf nginx-1.18.0.tar.gz

fastdfs-nginx-module下载:

[root@caochenlei ~]# wget https://github.com/happyfish100/fastdfs-nginx-module/archive/V1.22.tar.gz

fastdfs-nginx-module解压:

[root@caochenlei ~]# tar -zxvf V1.22.tar.gz

#查看一下拓展模块所在路径,后边会用到这个路径
[root@caochenlei ~]# cd fastdfs-nginx-module-1.22/src/
[root@caochenlei src]# pwd
/root/fastdfs-nginx-module-1.22/src

#回退到根目录,方便接下来的一系列安装
[root@caochenlei src]# cd ~

Nginx及fastdfs-nginx-module安装:

注意:因为这个模块必须在Nginx的安装的过程中才能添加,所有我们需要重新安装一个Nginx,为了进行区分,我们把新安装的Nginx取名为nginx_fdfs

[root@caochenlei ~]# cd nginx-1.18.0
[root@caochenlei nginx-1.18.0]# ./configure --prefix=/usr/local/nginx_fdfs --add-module=/root/fastdfs-nginx-module-1.22/src
[root@caochenlei nginx-1.18.0]# make && make install

注意:安装完成后的路径为:/usr/local/nginx_fdfs

Nginx命令(此处了解,先不要敲呢,跳过这步):

  • 普通启动服务:/usr/local/nginx_fdfs/sbin/nginx
  • 配置文件启动:/usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf
  • 暴力停止服务:/usr/local/nginx_fdfs/sbin/nginx -s stop
  • 优雅停止服务:/usr/local/nginx_fdfs/sbin/nginx -s quit
  • 检查配置文件:/usr/local/nginx_fdfs/sbin/nginx -t
  • 重新加载配置:/usr/local/nginx_fdfs/sbin/nginx -s reload
  • 查看相关进程:ps -ef | grep nginx

FastDFS的Nginx访问配置:

将 /root/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf 拷贝到 /etc/fdfs/ 目录下,这样才能正常启动Nginx

[root@caochenlei nginx-1.18.0]# cp /root/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf /etc/fdfs/

修改mod_fastdfs.conf配置文件:

修改mod_fastdfs.conf的以下几项配置项:vi /etc/fdfs/mod_fastdfs.conf

  • #mod_fastdfs存储数据目录

    • base_path=/opt/fastdfs/nginx_mod
  • #注册当前存储节点的跟踪器地址

    • tracker_server=192.168.239.128:22122
  • #路径中是否包含分组名称,我们为true

    • url_have_group_name=true
  • #真正存放文件的目录

    • store_path0=/opt/fastdfs/storage/files

创建相对应的文件夹:

[root@caochenlei nginx-1.18.0]# mkdir -p /opt/fastdfs/nginx_mod

配置Nginx的拓展模块请求转发:

[root@caochenlei nginx-1.18.0]# vi /usr/local/nginx_fdfs/conf/nginx.conf
location ~ /group[1-9]/M0[0-9] {	
     ngx_fastdfs_module;  
}

注意:ngx_fastdfs_module; #这个指令不是Nginx本身提供的,是扩展模块提供的,根据这个指令找到FastDFS提供的Nginx模块配置文件,然后找到Tracker,最终找到Stroager

启动带有Fastdfs模块的Nginx:

[root@caochenlei nginx-1.18.0]# /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf
ngx_http_fastdfs_set pid=35500

上传一个文件进行测试验证:

[root@caochenlei fdfs]# fdfs_test /etc/fdfs/client.conf upload /etc/fdfs/a.txt
This is FastDFS client test program v6.06

Copyright (C) 2008, Happy Fish / YuQing

FastDFS may be copied only under the terms of the GNU General
Public License V3, which may be found in the FastDFS source kit.
Please visit the FastDFS Home Page http://www.fastken.com/ 
for more detail.

[2020-09-05 21:41:43] DEBUG - base_path=/opt/fastdfs/client, connect_timeout=5, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0

tracker_query_storage_store_list_without_group: 
        server 1. group_name=, ip_addr=192.168.239.128, port=23000

group_name=group1, ip_addr=192.168.239.128, port=23000
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
source ip address: 192.168.239.128
file timestamp=2020-09-05 21:41:43
file size=14
file crc32=3519913659
example file url: http://192.168.239.128/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395_big.txt
source ip address: 192.168.239.128
file timestamp=2020-09-05 21:41:43
file size=14
file crc32=3519913659
example file url: http://192.168.239.128/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395_big.txt

在虚拟机外部浏览器访问上传的文件:

注意:直接输入example file url: http://192.168.239.128/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt

image-20200905215213209

FastDFS高可用集群:

3.1、环境准备

将第二章的单实例版本复制7个(关闭FastDFS服务,再关闭系统,退出虚拟机),然后依次启动,选择我已复制虚拟机,这样就会为每一个虚拟机重新分配一块网卡,这样每天虚拟机就可以正常上网了。如果你要是有精力,你也可以按照第二章的方法,重新安装这几台机器,但是这里为了省事,直接复制了。

学习这章电脑的环境:

内存至少8G+

硬盘至少255G+

关闭服务及系统命令:

[root@caochenlei ~]# /usr/local/nginx_fdfs/sbin/nginx -s quit
[root@caochenlei ~]# fdfs_trackerd /etc/fdfs/tracker.conf stop
[root@caochenlei ~]# fdfs_storaged /etc/fdfs/storage.conf stop
[root@caochenlei ~]# poweroff

复制完的效果如下图:

image-20200906090522061

依次打开启动虚拟机:

image-20200906090721672

注意:启动后使用ifconfig查看当前虚拟机的IP地址

CentOS 6 64 bit - 副本 (1):192.168.239.136

CentOS 6 64 bit - 副本 (2):192.168.239.137

CentOS 6 64 bit - 副本 (3):192.168.239.138

CentOS 6 64 bit - 副本 (4):192.168.239.139

CentOS 6 64 bit - 副本 (5):192.168.239.140

CentOS 6 64 bit - 副本 (6):192.168.239.141

CentOS 6 64 bit - 副本 (7):192.168.239.142

使用连接工具进行连接:

image-20200906095358182

image-20200906095658625

3.2、架构目标

image-20200906095053224

3.3、tracker配置

其实tracker已经在单实例版本的时候就配置好了,跳过就行了。

3.4、storage配置

每两个storage server为一组,共两个组(group1 和 group2),一个组内有两个storage server。

修改group1的storage1的storage.conf:vi /etc/fdfs/storage.conf

  • group_name = group1
  • tracker_server = 192.168.239.136:22122
  • tracker_server = 192.168.239.137:22122

修改group1的storage2的storage.conf:vi /etc/fdfs/storage.conf

  • group_name = group1
  • tracker_server = 192.168.239.136:22122
  • tracker_server = 192.168.239.137:22122

修改group2的storage3的storage.conf:vi /etc/fdfs/storage.conf

  • group_name = group2
  • tracker_server = 192.168.239.136:22122
  • tracker_server = 192.168.239.137:22122

修改group2的storage4的storage.conf:vi /etc/fdfs/storage.conf

  • group_name = group2
  • tracker_server = 192.168.239.136:22122
  • tracker_server = 192.168.239.137:22122

3.5、mod_fastdfs配置

修改group1的nginx1的mod_fastdfs.conf:vi /etc/fdfs/mod_fastdfs.conf

  • tracker_server = 192.168.239.136:22122

  • tracker_server = 192.168.239.137:22122

  • group_name = group1

  • group_count = 2

  • 在末尾增加2个组的具体信息:

[group1]
group_name=group1
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files

[group2]
group_name=group2
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files=

修改group2的nginx3的mod_fastdfs.conf:vi /etc/fdfs/mod_fastdfs.conf

  • tracker_server = 192.168.239.136:22122

  • tracker_server = 192.168.239.137:22122

  • group_name = group2

  • group_count = 2

  • 在末尾增加2个组的具体信息:

[group1]
group_name=group1
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files

[group2]
group_name=group2
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files

修改group2的nginx4的mod_fastdfs.conf:vi /etc/fdfs/mod_fastdfs.conf

tracker_server = 192.168.239.136:22122

tracker_server = 192.168.239.137:22122

group_name = group2

group_count = 2

在末尾增加2个组的具体信息:

[group1]
group_name=group1
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files

[group2]
group_name=group2
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files

3.6、安装两个tracker上的nginx

Nginx删除:

注意:之前已经下载了nginx安装包并解压安装了,我们先删除以前安装的,然后再安装

[root@caochenlei ~]# rm -rf /usr/local/nginx_fdfs

注意:之前已经下载了nginx安装包并解压安装了,我们先删除以前解压的,然后再解压

Nginx解压:

[root@caochenlei ~]# rm -rf nginx-1.18.0
[root@caochenlei ~]# tar -zxvf nginx-1.18.0.tar.gz

Nginx安装:

[root@caochenlei ~]# cd nginx-1.18.0
[root@caochenlei nginx-1.18.0]# ./configure
[root@caochenlei nginx-1.18.0]# make && make install

注意:安装完成后的路径为:/usr/local/nginx

Nginx配置:

[root@caochenlei nginx-1.18.0]# vi /usr/local/nginx/conf/nginx.conf
#配置负载均衡
upstream fastdfs_storage_server {
    server 192.168.239.138:80;
    server 192.168.239.139:80;
    server 192.168.239.140:80;
    server 192.168.239.141:80;
}

#配置请求路径
location ~ /group[1-9]/M0[0-9] {
    proxy_pass http://fastdfs_storage_server;
}

Nginx命令:

  • 普通启动服务:/usr/local/nginx/sbin/nginx
  • 配置文件启动:/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
  • 暴力停止服务:/usr/local/nginx/sbin/nginx -s stop
  • 优雅停止服务:/usr/local/nginx/sbin/nginx -s quit
  • 检查配置文件:/usr/local/nginx/sbin/nginx -t
  • 重新加载配置:/usr/local/nginx/sbin/nginx -s reload
  • 查看相关进程:ps -ef | grep nginx

3.7、启动所有服务进行测试

启动两个tracker:

[root@caochenlei nginx-1.18.0]# fdfs_trackerd /etc/fdfs/tracker.conf

启动四个storage:

[root@caochenlei ~]# fdfs_storaged /etc/fdfs/storage.conf

启动四个storage上的nginx:

[root@caochenlei ~]# /usr/local/nginx_fdfs/sbin/nginx -c /usr/local/nginx_fdfs/conf/nginx.conf

启动两个tracker上的nginx:

[root@caochenlei nginx-1.18.0]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

打开浏览器,输入以下几个地址进行测试:

注意:wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt这个文件是我们之前测试的文件,已经上传上去了

两个tracker上的访问:

http://192.168.239.136/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
http://192.168.239.137/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt

四个storage上的访问:

http://192.168.239.138/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
http://192.168.239.139/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
http://192.168.239.140/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt
http://192.168.239.141/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt

3.8、统一访问路径

我们需要使用一台单独的nginx为两台tracker进行负载均衡,以此达到统一访问路径。这回配置的是192.168.239.142这台机器

Nginx删除:

注意:之前已经下载了nginx安装包并解压安装了,我们先删除以前安装的,然后再安装

[root@caochenlei ~]# rm -rf /usr/local/nginx_fdfs

注意:之前已经下载了nginx安装包并解压安装了,我们先删除以前解压的,然后再解压

Nginx解压:

[root@caochenlei ~]# rm -rf nginx-1.18.0
[root@caochenlei ~]# tar -zxvf nginx-1.18.0.tar.gz

Nginx安装:

[root@caochenlei ~]# cd nginx-1.18.0
[root@caochenlei nginx-1.18.0]# ./configure
[root@caochenlei nginx-1.18.0]# make && make install

注意:安装完成后的路径为:/usr/local/nginx

Nginx配置:

[root@caochenlei nginx-1.18.0]# vi /usr/local/nginx/conf/nginx.conf
#配置负载均衡
upstream fastdfs_tracker_server {
    server 192.168.239.136:80;
    server 192.168.239.137:80;
}

#配置请求路径
location ~ /group[1-9]/M0[0-9] {
    proxy_pass http://fastdfs_tracker_server;
}

Nginx命令:

  • 普通启动服务:/usr/local/nginx/sbin/nginx
  • 配置文件启动:/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
  • 暴力停止服务:/usr/local/nginx/sbin/nginx -s stop
  • 优雅停止服务:/usr/local/nginx/sbin/nginx -s quit
  • 检查配置文件:/usr/local/nginx/sbin/nginx -t
  • 重新加载配置:/usr/local/nginx/sbin/nginx -s reload
  • 查看相关进程:ps -ef | grep nginx

Nginx启动:

[root@caochenlei nginx-1.18.0]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
[root@caochenlei nginx-1.18.0]# ps -ef | grep nginx
root       6309      1  0 11:20 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
nobody     6310   6309  0 11:20 ?        00:00:00 nginx: worker process                                          
root       6312   3016  0 11:20 pts/1    00:00:00 grep nginx

Nginx测试:

打开浏览器,输入http://192.168.239.142/group1/M00/00/00/wKjvgF9TlZeAZpeTAAAADtHNnrs395.txt,如果看到内容证明环境已经搭建完毕!

3.9、如何保证主nginx高可用

请您参考:学习Nginx这一篇就够了中的高可用集群如何搭建的。

原理说明:准备两台Nginx,一主一备,每一台上安装一个keepalived,由keepalived负责监测当前机器上的nginx是否可用(其实是调用一个shell脚本不停的查看进程信息),同时也会在两个keepalived之间进行心跳监测,如果主节点宕机,则从节点自动跟上,访问是通过虚拟IP进行访问的。

安装FastDFS:

2.1 环境准备

操作环境:CentOS7 X64,以下操作都是单机环境。

服务列表:

hostnameipNode Typeusername
l1192.168.0.200tracker server,storage serverroot
l2192.168.0.201tracker server,storage serverroot

如果采用主机名,需做如下配置

由于 集群内部有时需要通过主机名来进行相互通信,因此我们需要保证每一台机器的主机名都不相同。
具体操作参见:https://blog.csdn.net/prcyang/article/details/84787480

先做一件事,修改hosts,将文件服务器的ip与域名映射(单机TrackerServer环境),因为后面很多配置里面都需要去配置服务器地址,ip变了,就只需要修改hosts即可。

我把所有的安装包下载或上传到 /usr/local/src/下,并解压到当前目录。

如果要在本机访问虚拟机,在C:\Windows\System32\drivers\etc\hosts中同样增加一行

2.2 CentOS安装GCC

安装FastDFS需要先将官网下载的源码进行编译,编译依赖gcc环境

[root@localhost ~]# yum -y install gcc-c++

ps:检查gcc-c++是否已经安装(如果已安装,执行 yum -y install gcc-c++ 也会提示)

[root@localhost src]# whereis gcc   
gcc:[root@localhost src]#        # 未安装输出
gcc: /usr/bin/gcc /usr/lib/gcc /usr/libexec/gcc /usr/share/man/man1/gcc.1.gz        #已安装输出

2.3 安装libevent

FastDFS依赖libevent库,需要安装:

[root@localhost ~]# yum -y install libevent

2.4 安装libfastcommon

libfastcommon是FastDFS官方提供的,libfastcommon包含了FastDFS运行所需要的一些基础库。

下载地址: https://github.com/happyfish100/libfastcommon/releases 选择合适的版本

[root@localhost ~]# cd /usr/local/src/    #切换到下载目录
[root@localhost src]# wget -O libfastcommon-1.0.39.tar.gz  https://codeload.github.com/happyfish100/libfastcommon/tar.gz/V1.0.39 #下载(如果下载慢 可以将下载好的文件上传到此目录)
[root@localhost src]# tar -zxvf libfastcommon-1.0.39.tar.gz      #解压
[root@localhost src]# cd libfastcommon-1.0.39/
# 安装
[root@localhost libfastcommon-1.0.39]# ./make.sh 

[root@localhost libfastcommon-1.0.39]# ./make.sh  install

2.5 安装FastDFS

下载地址:https://github.com/happyfish100/fastdfs/releases 选择合适的版本

[root@localhost libfastcommon-1.0.39]# cd /usr/local/src/      #切换到下载目录

#下载(如果下载慢 可以将下载好的文件上传到此目录)
[root@localhost src]# wget -O fastdfs-5.11.tar.gz https://codeload.github.com/happyfish100/fastdfs/tar.gz/V5.11
[root@localhost src]# tar -zxvf fastdfs-5.11.tar.gz   #解压
[root@localhost src]# cd fastdfs-5.11/
#安装
[root@localhost fastdfs-5.11]# ./make.sh 
[root@localhost fastdfs-5.11]# ./make.sh  install

默认安装方式安装后的相应文件与目录

A、服务脚本:

/etc/init.d/fdfs_storaged
/etc/init.d/fdfs_trackerd

B、配置文件(这三个是作者给的样例配置文件)

/etc/fdfs/client.conf.sample
/etc/fdfs/storage.conf.sample
/etc/fdfs/tracker.conf.sample

C、命令工具在 /usr/bin/ 目录下:

fdfs_appender_test
fdfs_appender_test1
fdfs_append_file
fdfs_crc32
fdfs_delete_file
fdfs_download_file
fdfs_file_info
fdfs_monitor
fdfs_storaged
fdfs_test
fdfs_test1
fdfs_trackerd
fdfs_upload_appender
fdfs_upload_file
stop.sh
restart.sh

2.6 配置FastDFS跟踪器(Tracker)

配置文件详细说明参考:FastDFS 配置文件详解

  • 进入 /etc/fdfs,复制 FastDFS 跟踪器样例配置文件 tracker.conf.sample,并重命名为 tracker.conf。
[root@localhost fastdfs-5.11]# cd /etc/fdfs/
[root@localhost fdfs]# cp tracker.conf.sample tracker.conf
[root@localhost fdfs]#  vim tracker.conf
  • 编辑tracker.conf ,标红的需要修改下,其它的默认即可。
# 配置文件是否不生效,false 为生效
disabled=false

# 提供服务的端口
port=22122

# Tracker 数据和日志目录地址(根目录必须存在,子目录会自动创建)
base_path=/fastdfs/tracker

# HTTP 服务端口 默认8080 ,建议修改 防止冲突
http.server_port=9080
  • 创建tracker基础数据目录,即base_path对应的目录
[root@localhost fdfs]# mkdir -p /fastdfs/tracker
  • 防火墙中打开跟踪端口(默认的22122)
# vim /etc/sysconfig/iptables

添加如下端口行:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT

重启防火墙:
# service iptables restart
  • 启动Tracker

初次成功启动,会在 /fdfsdfs/tracker/ (配置的base_path)下创建 data、logs 两个目录。

[root@localhost fdfs]# /etc/init.d/fdfs_trackerd start
[root@localhost fdfs]# service fdfs_trackerd start
[root@localhost fdfs]# systemctl start fdfs_trackerd

查看 FastDFS Tracker 是否已成功启动 ,

# systemctl status fdfs_trackerd  # 查看服务状态 运行状态则算成功
 fdfs_trackerd.service - LSB: FastDFS tracker server
   Loaded: loaded (/etc/rc.d/init.d/fdfs_trackerd; bad; vendor preset: disabled)
   Active: active (exited) since 四 2019-05-09 08:57:18 CST; 6min ago
     Docs: man:systemd-sysv-generator(8)
  Process: 130913 ExecStop=/etc/rc.d/init.d/fdfs_trackerd stop (code=exited, status=2)
  Process: 131030 ExecStart=/etc/rc.d/init.d/fdfs_trackerd start (code=exited, status=0/SUCCESS)

# netstat -tulnp|grep fdfs   # 22122端口正在被监听,则算是Tracker服务安装成功
   tcp        0      0 0.0.0.0:22122           0.0.0.0:*               LISTEN      27492/fdfs_trackerd 
  • 关闭Tracker命令:
[root@localhost fdfs]# service fdfs_trackerd stop
[root@localhost fdfs]# systemctl stop fdfs_trackerd #centos7 推荐
[root@localhost fdfs]# /etc/init.d/fdfs_trackerd stop
  • 设置Tracker开机启动
# chkconfig fdfs_trackerd on#systemctl enable fdfs_trackerd.service
或者:
# vim /etc/rc.d/rc.local
加入配置:
/etc/init.d/fdfs_trackerd start
  • tracker server 目录及文件结构

Tracker服务启动成功后,会在base_path下创建data、logs两个目录。目录结构如下:

${base_path}
|__data
| |__storage_groups.dat:存储分组信息
| |__storage_servers.dat:存储服务器列表
|__logs
| |__trackerd.log: tracker server 日志文件

2.7 配置 FastDFS 存储 (Storage)

配置文件详细说明参考:FastDFS 配置文件详解

  • 进入 /etc/fdfs 目录,复制 FastDFS 存储器样例配置文件 storage.conf.sample,并重命名为 storage.conf
# cd /etc/fdfs
# cp storage.conf.sample storage.conf
# vim storage.conf
  • 编辑storage.conf

标红的需要修改,其它的默认即可。

# 配置文件是否不生效,false 为生效
disabled=false 

# 指定此 storage server 所在 组(卷)
group_name=group1

# storage server 服务端口
port=23000

# 心跳间隔时间,单位为秒 (这里是指主动向 tracker server 发送心跳)
heart_beat_interval=30

# Storage 数据和日志目录地址(根目录必须存在,子目录会自动生成)  (注 :这里不是上传的文件存放的地址,之前版本是的,在某个版本后更改了)
base_path=/fastdfs/storage/base

# 存放文件时 storage server 支持多个路径。这里配置存放文件的基路径数目,通常只配一个目录。
store_path_count=1


# 逐一配置 store_path_count 个路径,索引号基于 0。
# 如果不配置 store_path0,那它就和 base_path 对应的路径一样。
store_path0=/fastdfs/storage

# FastDFS 存储文件时,采用了两级目录。这里配置存放文件的目录个数。 
# 如果本参数只为 N(如: 256),那么 storage server 在初次运行时,会在 store_path 下自动创建 N * N 个存放文件的子目录。
subdir_count_per_path=256

# tracker_server 的列表 ,会主动连接 tracker_server
# 有多个 tracker server 时,每个 tracker server 写一行
tracker_server=192.168.0.200:22122
tracker_server=192.168.0.201:22122

# 允许系统同步的时间段 (默认是全天) 。一般用于避免高峰同步产生一些问题而设定。
sync_start_time=00:00
sync_end_time=23:59
# 访问端口 默认80  建议修改 防止冲突
http.server_port=9888
  • 创建Storage基础数据目录,对应base_path目录
# 对应base_path
# mkdir -p /fastdfs/storage/base
 
# 这是配置的store_path0路径,有多个要创建多个
# mkdir -p /fastdfs/storage/
  • 防火墙中打开存储器端口(默认的 23000)
# vim /etc/sysconfig/iptables

#添加如下端口行:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT

#重启防火墙:
 service iptables restart
  • 启动 Storage

启动Storage前确保Tracker是启动的。初次启动成功,会在 /fastdfs/storage/base(base_path) 目录下创建 data、 logs 两个目录。

可以用这种方式启动
# /etc/init.d/fdfs_storaged start
# service fdfs_storaged start
# systemctl start fdfs_storaged  #centos7 推荐

查看 Storage 是否成功启动,

# netstat -unltp|grep fdfs #23000 端口正在被监听,就算 Storage 启动成功。
tcp        0      0 0.0.0.0:23000           0.0.0.0:*               LISTEN      28834/fdfs_storaged 
# systemctl status fdfs_storaged # 查看服务状态
● fdfs_storaged.service - LSB: FastDFS storage server
   Loaded: loaded (/etc/rc.d/init.d/fdfs_storaged; bad; vendor preset: disabled)
   Active: active (running) since 四 2019-05-09 09:22:53 CST; 2s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 23015 ExecStart=/etc/rc.d/init.d/fdfs_storaged start (code=exited, status=0/SUCCESS)
    Tasks: 1
   Memory: 184.0K
   CGroup: /system.slice/fdfs_storaged.service
           └─23023 /usr/bin/fdfs_storaged /etc/fdfs/storage.conf
 
5月 09 09:22:53 localhost.localdomain systemd[1]: Starting LSB: FastDFS storage server...
5月 09 09:22:53 localhost.localdomain fdfs_storaged[23015]: Starting FastDFS storage server:
5月 09 09:22:53 localhost.localdomain systemd[1]: Started LSB: FastDFS storage server.
  • 关闭Storage
[root@localhost fdfs]# service fdfs_storaged stop
[root@localhost fdfs]# systemctl stop fdfs_storaged #centos7 推荐
[root@localhost fdfs]# /etc/init.d/fdfs_storaged stop
  • 查看Storage和Tracker是否在通信:
# /usr/bin/fdfs_monitor /etc/fdfs/storage.conf
[2019-05-09 11:34:09] DEBUG - base_path=/fastdfs/storage/base, connect_timeout=30, network_timeout=60, tracker_server_count=2, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0
 
server_count=2, server_index=0
 
tracker server is 192.168.0.200:22122
 
group count: 1
 
Group 1:
group name = group1
disk total space = 51175 MB
disk free space = 14251 MB
trunk free space = 0 MB
storage server count = 2
active server count = 1
storage server port = 23000
storage HTTP port = 9888
store path count = 1
subdir count per path = 256
current write server index = 0
current trunk file id = 0
 
    Storage 1:
        id = 192.168.0.200
        ip_addr = 192.168.0.200 (localhost.localdomain)  ACTIVE
        。。。
     Storage 2:
        id = 192.168.0.201
        ip_addr = 192.168.0.201  WAIT_SYNC
  • 设置 Storage 开机启动
# chkconfig fdfs_storaged on
# chkconfig fdfs_storaged on#systemctl enable fdfs_storaged.service  (推荐)
或者:
# vim /etc/rc.d/rc.local
加入配置:
/etc/init.d/fdfs_storaged  start 

Storage 目录

同 Tracker,Storage 启动成功后,在base_path 下创建了data、logs目录,记录着 Storage Server 的信息。
在 store_path0/data 目录下,创建了N*N个子目录:

[root@localhost ~]# ls /fastdfs/storage/data/
00  05  0A  0F  14  19  1E  23  28  2D  32  37  3C  41  46  4B  50  55  5A  5F  64  69  6E  73  78  7D  82  87  8C  91  96  9B  A0  A5  AA  AF  B4  B9  BE  C3  C8  CD  D2  D7  DC  E1  E6  EB  F0  F5  FA  FF
01  06  0B  10  15  1A  1F  24  29  2E  33  38  3D  42  47  4C  51  56  5B  60  65  6A  6F  74  79  7E  83  88  8D  92  97  9C  A1  A6  AB  B0  B5  BA  BF  C4  C9  CE  D3  D8  DD  E2  E7  EC  F1  F6  FB
02  07  0C  11  16  1B  20  25  2A  2F  34  39  3E  43  48  4D  52  57  5C  61  66  6B  70  75  7A  7F  84  89  8E  93  98  9D  A2  A7  AC  B1  B6  BB  C0  C5  CA  CF  D4  D9  DE  E3  E8  ED  F2  F7  FC
03  08  0D  12  17  1C  21  26  2B  30  35  3A  3F  44  49  4E  53  58  5D  62  67  6C  71  76  7B  80  85  8A  8F  94  99  9E  A3  A8  AD  B2  B7  BC  C1  C6  CB  D0  D5  DA  DF  E4  E9  EE  F3  F8  FD
04  09  0E  13  18  1D  22  27  2C  31  36  3B  40  45  4A  4F  54  59  5E  63  68  6D  72  77  7C  81  86  8B  90  95  9A  9F  A4  A9  AE  B3  B8  BD  C2  C7  CC  D1  D6  DB  E0  E5  EA  EF  F4  F9  FE

2.8 上传测试

  • 修改 Tracker 服务器中的客户端配置文件
# cd /etc/fdfs
# cp client.conf.sample client.conf
# vim client.conf
  • 修改如下配置即可,其它默认。
# Client 的数据和日志目录
base_path=/fastdfs/client
 
# Tracker端口
tracker_server=192.168.0.200:22122

② 上传测试

在linux内部执行如下命令上传 namei.jpeg 图片

# /usr/bin/fdfs_upload_file /etc/fdfs/client.conf namei.jpeg

上传成功后返回文件ID号:group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg

img

返回的文件ID由group、存储目录、两级子目录、fileid、文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。

img

三、安装配置Nginx ,http访问文件

上面将文件上传成功了,但我们无法下载。因此安装Nginx作为服务器以支持Http方式访问文件。同时,后面安装FastDFS的Nginx模块也需要Nginx环境。

Nginx只需要安装到StorageServer所在的服务器即可,用于访问文件

安装nginx 参见:https://blog.csdn.net/prcyang/article/details/90032781

配置nginx

# vim /usr/local/nginx/conf/nginx.conf
配置如下
server {
        listen       8081;
        server_name  192.168.0.200;
 
        location /group1/M00{
        alias /fastdfs/storage/data/;
         autoindex on;
 
       }
 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   html;
       }
    }

在浏览器访问之前上传的图片 http://192.168.0.200:8081/group1/M00/00/00/wKgz6lnduTeAMdrcAAEoRmXZPp870.jpeg

FastDFS配置Nginx模块:

4.1 安装配置fastdfs-nginx-module模块

  • fastdfs-nginx-module 模块说明

FastDFS 通过 Tracker 服务器,将文件放在 Storage 服务器存储, 但是同组存储服务器之间需要进行文件复制, 有同步延迟的问题。

假设 Tracker 服务器将文件上传到了 192.168.51.128,上传成功后文件 ID已经返回给客户端。

此时 FastDFS 存储集群机制会将这个文件同步到同组存储 192.168.51.129,在文件还没有复制完成的情况下,客户端如果用这个文件 ID 在 192.168.51.129 上取文件,就会出现文件无法访问的错误。

而 fastdfs-nginx-module 可以重定向文件链接到源服务器取文件,避免客户端由于复制延迟导致的文件无法访问错误。

  • 下载fastdfs-nginx-module
#cd /usr/local/src
#wget -O fastdfs-nginx-module-1.20.tar.gz  https://codeload.github.com/happyfish100/fastdfs-nginx-module/tar.gz/V1.20
# tar -zxvf fastdfs-nginx-module-1.20.tar.gz 
  • 编辑 fastdfs-nginx-module-1.20/src/config 文件

必须做如下修改 ,否则编译nginx时报致命错误,参见 https://blog.csdn.net/zzzgd_666/article/details/81911892
vim /usr/local/src/fastdfs-nginx-module-1.20/src/config
修改为如下内容

ngx_addon_name=ngx_http_fastdfs_module
 
if test -n "${ngx_module_link}"; then
ngx_module_type=HTTP
ngx_module_name=$ngx_addon_name
ngx_module_incs="/usr/include/fastdfs /usr/include/fastcommon/"
ngx_module_libs="-lfastcommon -lfdfsclient"
ngx_module_srcs="$ngx_addon_dir/ngx_http_fastdfs_module.c"
ngx_module_deps=
CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='2561024' -DFDFS_MOD_CONF_FILENAME='"/etc/fdfs/mod_fastdfs.conf"'"
. auto/module
else
HTTP_MODULES="$HTTP_MODULES ngx_http_fastdfs_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fastdfs_module.c"
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -lfastcommon -lfdfsclient"
CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE='2561024' -DFDFS_MOD_CONF_FILENAME='"/etc/fdfs/mod_fastdfs.conf"'"
fi

改变的文件内容
ngx_module_incs=“/usr/include/fastdfs /usr/include/fastcommon/”
CORE_INCS=“$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/”

  • 配置nginx,添加fastdfs-nginx-module 模块
# 先停掉nginx服务 如果没有配置为服务 则使用 # /usr/local/nginx/sbin/nginx -s stop
# systemctl stop nginx     
#进入nginx源码目录
# cd /usr/local/src/nginx-1.16.0/   
#添加fastdfs-nginx-module 模块
#[root@localhost nginx-1.16.0]#./configure  --add-module=/usr/local/src/fastdfs-nginx-module-1.20/src
#重新编译安装nginx
#[root@localhost nginx-1.16.0]# make
#[root@localhost nginx-1.16.0]# make install
 
#验证是否加载fastdfs-nginx-module模块是否 ,有如下标红部分表示成功
# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.16.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) 
configure arguments: --add-module=/usr/local/src/fastdfs-nginx-module-1.20/src
  • 复制 fastdfs-nginx-module 源码中的配置文件 mod_fastdfs.conf 到/etc/fdfs 目录, 并修改

cp /usr/local/src/fastdfs-nginx-module-1.20/src/mod_fastdfs.conf /etc/fdfs/

修改如下配置,其它默认

# 连接超时时间
connect_timeout=10
 
# Tracker Server
tracker_server=192.168.0.200:22122
tracker_server=192.168.0.201:22122
 
# StorageServer 默认端口
storage_server_port=23000
 
# 如果文件ID的uri中包含/group**,则要设置为true
#url_have_group_name = true
 
# Storage 配置的store_path0路径,必须和storage.conf中的一致
store_path0=/fastdfs/storage
  • 复制 FastDFS 的部分配置文件到/etc/fdfs 目录
#cd /usr/local/src/fastdfs-5.11/conf/
[root@localhost conf]# cp anti-steal.jpg http.conf mime.types /etc/fdfs/
  • 配置nginx,修改nginx.conf
# vim /usr/local/nginx/conf/nginx.conf
修改配置,其它的默认
在80端口下添加fastdfs-nginx模块
location ~/group([0-9])/M00 {
    ngx_fastdfs_module;
}

完整配置如下 供参考

user root;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
 
    server {
 
        listen      9888 ;
        server_name  192.168.0.200;
 
       # location /group1/M00{
         # alias /fastdfs/storage/data/;
        # autoindex on;
       #}
 location ~/group[0-9]/ {
                ngx_fastdfs_module;
            }
 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   html;
       }
    }
 
}

注意:

listen 9888端口值是要与 /etc/fdfs/storage.conf 中的 http.server_port=9888 (前面改成9888了)相对应。如果改成其它端口,则需要统一,同时在防火墙中打开该端口。

location 的配置,如果有多个group则配置location ~/group([0-9])/M00 ,没有则不用配group。

  • 在 /fastdfs/storage 文件存储目录下创建软连接,将其链接到实际存放数据的目录,这一步可以省略。

ln -s /fastdfs/storage/data/ /fastdfs/storage/data/M00

  • 启动nginx

# systemctl start nginx

  • 在地址栏访问。

能下载文件就算安装成功。注意和之前直接使用nginx路由访问不同的是,这里配置 fastdfs-nginx-module 模块,可以重定向文件链接到源服务器取文件。

最终部署结构图:

可以按照下面的结构搭建环境。

img

FastDFS的操作:

FastDFS的JavaAPI:

注意:我们进行JavaAPI测试采用的是单实例版本,所以要将之前的单实例版本启动并把所有服务开启,这一步请参考第二章。还有一个问题需要注意,虚拟机的IP地址可能变化,如果一旦发生变化,那么之前配置的FastDFS就肯定不会启动成功,虽然我们会配置了,但是我们为了偷懒,临时把IP地址修改为之前的那个IP地址,命令如下(命令生效,请重启一个窗口连接):

ifconfig eth0 192.168.239.128 netmask 255.255.255.0

4.1、工程搭建

工程名称:FastDFSDemo

项目依赖:

  • fastdfs-client-java-1.28-SNAPSHOT.jar
  • log4j-1.2.17.jar
  • slf4j-api-1.7.26.jar
  • slf4j-log4j12-1.7.26.jar

单元测试:Junit 4

测试包名:com.caochenlei.fastdfs.demo

配置文件:在src中创建 fdfs_client.conf

connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
http.anti_steal_token = no
http.secret_key = FastDFS1234567890

tracker_server = 192.168.239.128:22122

connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000

4.2、上传文件

FastDFSDemo.java(全路径:/FastDFSDemo/src/com/caochenlei/fastdfs/demo/FastDFSDemo.java)

/**
 * 上传文件
 */
@Test
public void fileUpload() {
	try {
		// 1.加载配置文件,默认去classpath下加载
		ClientGlobal.init("fdfs_client.conf");
		// 2.创建TrackerClient对象
		TrackerClient trackerClient = new TrackerClient();
		// 3.创建TrackerServer对象
		TrackerServer trackerServer = trackerClient.getTrackerServer();
		// 4.创建StorageServler对象
		StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
		// 5.创建StorageClient对象,这个对象完成对文件的操作
		StorageClient storageClient = new StorageClient(trackerServer, storageServer);
		// 6.上传文件(第一个参数:本地文件路径、第二个参数:上传文件的后缀、第三个参数:文件信息)
		String[] uploadArray = storageClient.upload_file("a.txt", "txt", null);
		for (String str : uploadArray) {
			System.out.println(str);
		}
	} catch (IOException e) {
		e.printStackTrace();
	} catch (MyException e) {
		e.printStackTrace();
	}
}

运行结果:

group1
M00/00/00/wKjvgF9UbIGAV0ioAAAAHjXTgjs092.txt

4.3、下载文件

FastDFSDemo.java(全路径:/FastDFSDemo/src/com/caochenlei/fastdfs/demo/FastDFSDemo.java)

/**
 * 下载文件
 */
@Test
public void fileDownload() {
	try {
		// 1.加载配置文件,默认去classpath下加载
		ClientGlobal.init("fdfs_client.conf");
		// 2.创建TrackerClient对象
		TrackerClient trackerClient = new TrackerClient();
		// 3.创建TrackerServer对象
		TrackerServer trackerServer = trackerClient.getTrackerServer();
		// 4.创建StorageServler对象
		StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
		// 5.创建StorageClient对象,这个对象完成对文件的操作
		StorageClient storageClient = new StorageClient(trackerServer, storageServer);
		// 6.下载文件(返回0表示成功,其它均表示失败)
		int num = storageClient.download_file("group1", "M00/00/00/wKjvgF9UbIGAV0ioAAAAHjXTgjs092.txt", "b.txt");
		System.out.println(num);
	} catch (IOException e) {
		e.printStackTrace();
	} catch (MyException e) {
		e.printStackTrace();
	}
}

运行结果:

0

4.4、删除文件

FastDFSDemo.java(全路径:/FastDFSDemo/src/com/caochenlei/fastdfs/demo/FastDFSDemo.java)

/**
 * 删除文件
 */
@Test
public void fileDelete() {
	try {
		// 1.加载配置文件,默认去classpath下加载
		ClientGlobal.init("fdfs_client.conf");
		// 2.创建TrackerClient对象
		TrackerClient trackerClient = new TrackerClient();
		// 3.创建TrackerServer对象
		TrackerServer trackerServer = trackerClient.getTrackerServer();
		// 4.创建StorageServler对象
		StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
		// 5.创建StorageClient对象,这个对象完成对文件的操作
		StorageClient storageClient = new StorageClient(trackerServer, storageServer);
		// 6.删除文件(返回0表示成功,其它均表示失败)
		int num = storageClient.delete_file("group1", "M00/00/00/wKjvgF9UbIGAV0ioAAAAHjXTgjs092.txt");
		System.out.println(num);
	} catch (IOException e) {
		e.printStackTrace();
	} catch (MyException e) {
		e.printStackTrace();
	}
}

运行结果:

0
FastDFS上传过程:
  1. Storage会定时的向Tracker发送心跳,告诉Tracker自己还还活着,这样Fastdfs就可以工作了
  2. 客户端发送上传请求给Tracker,Tracker会检查是否有可用Storage
  3. 如果有可用的,客户端就可以上传文件数据到Storage上
  4. Storage将文件写入磁盘后,会返回路径信息给客户端
  5. 客户端就可以根据这个路径信息找到上传的文件

在这里插å
¥å›¾ç‰‡æè¿°

FastDFS下载过程:
  1. Storage会定时的向Tracker安装发送心跳,告诉Tracker自己还还活着,这样Fastdfs就可以工作了
  2. 客户端发送下载请求到Tracker上,Tracker查找到存储的Storage地址后返回给客户端
  3. 客户端拿到Storage地址后,去Storage上找到文件
  4. 把文件返回给客户端

在这里插å
¥å›¾ç‰‡æè¿°

Linux上FastDFS安装:

注:一定要先启动Tracker,在启动Storage

9.1 基础环境安装

注:如果Tracker和Storage是配置在不同的服务器上,那么基础环境要在两个服务器上都安装。

1.下载安装包**:**

  • libfatscommon:FastDFS分离出的一些公用函数包
  • FastDFS:FastDFS本体
  • fastdfs-nginx-module:FastDFS和nginx的关联模块
  • nginx:发布访问服务

2.安装基础环境

yum install -y gcc gcc-c++ 
yum -y install libevent
yum -y install zlib zlib-devel pcre pcre-devel gcc gcc-c++ openssl openssl-devel libevent libevent-devel perl unzip net-tools wget  
  1. 安装libfatscommon函数库
tar -zxvf libfastcommon-1.0.42.tar.gz

4.进入libfastcommon文件夹,编译并且安装

./make.sh 
./make.sh install
  1. 安装fastdfs主程序文件
tar -zxvf fastdfs-6.04.tar.gz
  1. 安装fastdfs
./make.sh
./make.sh install    

7.拷贝配置文件
cp /home/software/FastDFS/fastdfs-6.04/conf/* /etc/fdfs/

8.停止tracker
/usr/bin/stop.sh /etc/fdfs/tracker.conf

9.2 配置tracker服务

注:tracker和storage都是同一个fastdfs的主程序的两个不同概念,配置不同的配置文件就可以设定为tracker或者storage

1.进入到配置文件目录

cd /etc/fdfs

2.修改tracker配置文件

vim tracker.conf

#修改tracker配置文件,此为tracker的工作目录,保存数据以及日志
base_path=/usr/local/fastdfs/tracker

3.创建工作目录

mkdir /usr/local/fastdfs/tracker -p

4.启动进程

/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf

9.3 配置storage服务

1.进入到配置文件目录

cd /etc/fdfs

2.修改storage配置文件

vim storage.conf

# 修改组名 可以不修改
group_name=test 
# 修改storage的工作空间 
base_path=/usr/local/fastdfs/storage 
# 修改storage的存储空间 
store_path0=/usr/local/fastdfs/storage 
# 修改tracker的地址和端口号,用于心跳 
tracker_server=192.168.1.153:22122 
# 后续结合nginx的一个对外服务端口号 
http.server_port=8888

3.创建工作目录

mkdir /usr/local/fastdfs/storage -p

4.启动进程

前提:必须首先启动tracker
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf

9.4 利用client测试上传

1.进入到配置文件目录

cd /etc/fdfs

2.修改client配置文件

vim client.conf

# 修改client的工作空间 
 base_path=/usr/local/fastdfs/client 
 # 修改tracker的地址和端口号,用于心跳 
 tracker_server=192.168.1.153:22122

3.创建工作目录

mkdir /usr/local/fastdfs/client

4.随便找一张图片测试测试

cd /usr/bin/
./fdfs_test /etc/fdfs/client.conf upload /home/logo.png

在这里插å
¥å›¾ç‰‡æè¿°

9.5 配置 nginx fastdfs 实现文件服务器

注:fastdfs安装好以后是无法通过http访问的,这个时候就需要借助nginx了,所以需要安装fastdfs的第三方模块到nginx中,就能使用了。
注:nginx需要和storage在同一个节点。

1.解压nginx的fastdfs压缩包

tar -zxvf fastdfs-nginx-module-1.22.tar.gz

2.复制配置文件:

cd /fastdfs-nginx-module-1.22/src
cp mod_fastdfs.conf /etc/fdfs

3.修改/fastdfs-nginx-module/src/config文件,主要是修改路径,把local删除,因为fastdfs安装的时候我们没有修改路径,原路径是/usr:
在这里插å
¥å›¾ç‰‡æè¿°

4.安装nginx

1:安装依赖环境

(1)安装gcc环境
yum install gcc-c++
(2)安装PCRE库,用于解析正则表达式
yum install -y pcre pcre-devel
(3)zlib压缩和解压缩依赖,
yum install -y zlib zlib-devel
(4)SSL 安全的加密的套接字协议层,用于HTTP安全传输,也就是https
yum install -y openssl openssl-devel
2.解压,需要注意,解压后得到的是源码,源码需要编译后才能安装
tar -zxvf nginx-1.16.1.tar.gz
3.编译之前,先创建nginx临时目录,如果不创建,在启动nginx的过程中会报错
mkdir /var/temp/nginx -p

2:配置改一下

./configure 
–prefix=/usr/local/nginx 
–pid-path=/var/run/nginx/nginx.pid 
–lock-path=/var/lock/nginx.lock 
–error-log-path=/var/log/nginx/error.log 
–http-log-path=/var/log/nginx/access.log 
–with-http_gzip_static_module 
–http-client-body-temp-path=/var/temp/nginx/client 
–http-proxy-temp-path=/var/temp/nginx/proxy 
–http-fastcgi-temp-path=/var/temp/nginx/fastcgi 
–http-uwsgi-temp-path=/var/temp/nginx/uwsgi 
–http-scgi-temp-path=/var/temp/nginx/scgi 
–add-module=/home/software/fdfs/fastdfs-nginx-module-1.22/src

5.make编译
make

6.安装
make install

7.进入sbin目录启动
nginx./nginx

5. 修改 mod_fastdfs.conf 配置文件

cd /etc/fdfs
# 修改mod_fastdfs的工作空间 
base_path=/usr/local/fastdfs/tmp 
 # 修改tracker的地址和端口号,用于心跳 
tracker_server=192.168.1.153:22122 
# 修改组名 可以不修改
group_name=imooc 
# 在url上拼接组名
url_have_group_name = true 
# 文件存储空间
store0_path=/usr/local/fastdfs/storage

6.创建工作空间

mkdir /usr/local/fastdfs/tmp

7. 修改nginx.conf,添加如下虚拟主机

server {
listen 8888;
server_name localhost;
#如果自己修改组名了,这块配自己修改的组名
location /group1/M00
{undefined
ngx_fastdfs_module;
}
}

8.启动nginx

9.浏览器访问

在这里插å
¥å›¾ç‰‡æè¿°

Java客户端:

前面文件系统平台搭建好了,现在就要写客户端代码在系统中实现上传下载,这里只是简单的测试代码。

1、首先需要搭建 FastDFS 客户端Java开发环境

① 项目中使用maven进行依赖管理,可以在pom.xml中引入如下依赖即可:

<dependency>
   <groupId>net.oschina.zcx7878</groupId>
   <artifactId>fastdfs-client-java</artifactId>
   <version>1.27.0.0</version>
</dependency>

其它的方式,参考官方文档:https://github.com/happyfish100/fastdfs-client-java

② 引入配置文件

可直接复制包下的 fastdfs-client.properties.sample 或者 fdfs_client.conf.sample,到你的项目中,去掉.sample。
img

我这里直接复制 fastdfs-client.properties.sample 中的配置到项目配置文件 config.properties 中,修改tracker_servers。只需要加载这个配置文件即可

img

2、客户端API

个人封装的FastDFS Java API以同步到github:https://github.com/bojiangzhou/lyyzoo-fastdfs-java.git

权限控制:

前面使用nginx支持http方式访问文件,但所有人都能直接访问这个文件服务器了,所以做一下权限控制。

FastDFS的权限控制是在服务端开启token验证,客户端根据文件名、当前unix时间戳、秘钥获取token,在地址中带上token参数即可通过http方式访问文件。

① 服务端开启token验证

修改http.conf
# vim /etc/fdfs/http.conf
 
设置为true表示开启token验证
http.anti_steal.check_token=true
 
设置token失效的时间单位为秒(s)
http.anti_steal.token_ttl=1800
 
密钥,跟客户端配置文件的fastdfs.http_secret_key保持一致
http.anti_steal.secret_key=FASTDFS1234567890
 
如果token检查失败,返回的页面
http.anti_steal.token_check_fail=/ljzsg/fastdfs/page/403.html

记得重启服务。

② 配置客户端

客户端只需要设置如下两个参数即可,两边的密钥保持一致。

# token 防盗链功能
fastdfs.http_anti_steal_token=true
# 密钥
fastdfs.http_secret_key=FASTDFS1234567890

③ 客户端生成token

访问文件需要带上生成的token以及unix时间戳,所以返回的token是token和时间戳的拼接。

之后,将token拼接在地址后即可访问:file.ljzsg.com/group1/M00/00/00/wKgzgFnkaXqAIfXyAAEoRmXZPp878.jpeg?token=078d370098b03e9020b82c829c205e1f&ts=1508141521

   /**
    * 获取访问服务器的token,拼接到地址后面
    *
    * @param filepath 文件路径 group1/M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg
    * @param httpSecretKey 密钥
    * @return 返回token,如: token=078d370098b03e9020b82c829c205e1f&ts=1508141521
    */
   public static String getToken(String filepath, String httpSecretKey){
       // unix seconds
       int ts = (int) Instant.now().getEpochSecond();
       // token
       String token = "null";
       try {
           token = ProtoCommon.getToken(getFilename(filepath), ts, httpSecretKey);
       } catch (UnsupportedEncodingException e) {
           e.printStackTrace();
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
       } catch (MyException e) {
           e.printStackTrace();
       }

       StringBuilder sb = new StringBuilder();
       sb.append("token=").append(token);
       sb.append("&ts=").append(ts);

       return sb.toString();
   }

④ 注意事项

如果生成的token验证无法通过,请进行如下两项检查:
  A. 确认调用token生成函数(ProtoCommon.getToken),传递的文件ID中没有包含group name。传递的文件ID格式形如:M00/00/00/wKgzgFnkTPyAIAUGAAEoRmXZPp876.jpeg

B. 确认服务器时间基本是一致的,注意服务器时间不能相差太多,不要相差到分钟级别。

⑤ 对比下发现,如果系统文件隐私性较高,可以直接通过fastdfs-client提供的API去访问即可,不用再配置Nginx走http访问。配置Nginx的主要目的是为了快速访问服务器的文件(如图片),如果还要加权限验证,则需要客户端生成token,其实已经没有多大意义。

FastDFS原理和过程

前言:
(1)每次上传文件后都会返回一个地址,用户需要自己保存此地址。

(2)为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。

网摘1
FastDFS是一个开源的轻量级分布式文件系统,由跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。

这里写图片描述

Storage server

Storage server(后简称storage)以组(卷,group或volume)为单位组织,一个group内包含多台storage机器,数据互为备份,存储空间以group内容量最小的storage为准,所以建议group内的多个storage尽量配置相同,以免造成存储空间的浪费。

以group为单位组织存储能方便的进行应用隔离、负载均衡、副本数定制(group内storage server数量即为该group的副本数),比如将不同应用数据存到不同的group就能隔离应用数据,同时还可根据应用的访问特性来将应用分配到不同的group来做负载均衡;缺点是group的容量受单机存储容量的限制,同时当group内有机器坏掉时,数据恢复只能依赖group内地其他机器,使得恢复时间会很长。

group内每个storage的存储依赖于本地文件系统,storage可配置多个数据存储目录,比如有10块磁盘,分别挂载在/data/disk1-/data/disk10,则可将这10个目录都配置为storage的数据存储目录。

storage接受到写文件请求时,会根据配置好的规则(后面会介绍),选择其中一个存储目录来存储文件。为了避免单个目录下的文件数太多,在storage第一次启动时,会在每个数据存储目录里创建2级子目录,每级256个,总共65536个文件,新写的文件会以hash的方式被路由到其中某个子目录下,然后将文件数据直接作为一个本地文件存储到该目录中。

Tracker server

Tracker是FastDFS的协调者,负责管理所有的storage server和group,每个storage在启动后会连接Tracker,告知自己所属的group等信息,并保持周期性的心跳,tracker根据storage的心跳信息,建立group==>[storage server list]的映射表。

Tracker需要管理的元信息很少,会全部存储在内存中;另外tracker上的元信息都是由storage汇报的信息生成的,本身不需要持久化任何数据,这样使得tracker非常容易扩展,直接增加tracker机器即可扩展为tracker cluster来服务,cluster里每个tracker之间是完全对等的,所有的tracker都接受stroage的心跳信息,生成元数据信息来提供读写服务。

Upload file

FastDFS向使用者提供基本文件访问接口,比如upload、download、append、delete等,以客户端库的方式提供给用户使用。 这里写图片描述

选择tracker server

当集群中不止一个tracker server时,由于tracker之间是完全对等的关系,客户端在upload文件时可以任意选择一个trakcer。

选择存储的group

当tracker接收到upload file的请求时,会为该文件分配一个可以存储该文件的group,支持如下选择group的规则: 1. Round robin,所有的group间轮询 2. Specified group,指定某一个确定的group 3. Load balance,剩余存储空间多多group优先

选择storage server

当选定group后,tracker会在group内选择一个storage server给客户端,支持如下选择storage的规则: 1. Round robin,在group内的所有storage间轮询 2. First server ordered by ip,按ip排序 3. First server ordered by priority,按优先级排序(优先级在storage上配置)

选择storage path

当分配好storage server后,客户端将向storage发送写文件请求,storage将会为文件分配一个数据存储目录,支持如下规则: 1. Round robin,多个存储目录间轮询 2. 剩余存储空间最多的优先
生成Fileid

选定存储目录之后,storage会为文件生一个Fileid,由storage server ip、文件创建时间、文件大小、文件crc32和一个随机数拼接而成,然后将这个二进制串进行base64编码,转换为可打印的字符串。

选择两级目录

当选定存储目录之后,storage会为文件分配一个fileid,每个存储目录下有两级256*256的子目录,storage会按文件fileid进行两次hash(猜测),路由到其中一个子目录,然后将文件以fileid为文件名存储到该子目录下。
生成文件名

当文件存储到某个子目录后,即认为该文件存储成功,接下来会为该文件生成一个文件名,文件名由group、存储目录、两级子目录、fileid、文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。

这里写图片描述

文件同步

写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由后台线程将文件同步至同group内其他的storage server。

每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。

storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。

比如一个group内有A、B、C三个storage server,A向C同步到进度为T1 (T1以前写的文件都已经同步到B上了),B向C同步到时间戳为T2(T2 > T1),tracker接收到这些同步进度信息时,就会进行整理,将最小的那个做为C的同步时间戳,本例中T1即为C的同步时间戳为T1(即所有T1以前写的数据都已经同步到C上了);同理,根据上述规则,tracker会为A、B生成一个同步时间戳。

Download file

客户端upload file成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。

这里写图片描述

跟upload file一样,在download file时客户端可以选择任意tracker server。

tracker发送download请求给某个tracker,必须带上文件名信息,tracke从文件名中解析出文件的group、大小、创建时间等信息,然后为该请求选择一个storage用来服务读请求。由于group内的文件同步时在后台异步进行的,所以有可能出现在读到时候,文件还没有同步到某些storage server上,为了尽量避免访问到这样的storage,tracker按照如下规则选择group内可读的storage。

  1. 该文件上传到的源头storage - 源头storage只要存活着,肯定包含这个文件,源头的地址被编码在文件名中。 2. 文件创建时间戳==storage被同步到的时间戳 且(当前时间-文件创建时间戳) > 文件同步最大时间(如5分钟) - 文件创建后,认为经过最大同步时间后,肯定已经同步到其他storage了。 3. 文件创建时间戳 < storage被同步到的时间戳。 - 同步时间戳之前的文件确定已经同步了 4. (当前时间-文件创建时间戳) > 同步延迟阀值(如一天)。 - 经过同步延迟阈值时间,认为文件肯定已经同步了。
    小文件合并存储

将小文件合并存储主要解决如下几个问题:

  1. 本地文件系统inode数量有限,从而存储的小文件数量也就受到限制。 2. 多级目录+目录里很多文件,导致访问文件的开销很大(可能导致很多次IO) 3. 按小文件存储,备份与恢复的效率低
    FastDFS在V3.0版本里引入小文件合并存储的机制,可将多个小文件存储到一个大的文件(trunk file),为了支持这个机制,FastDFS生成的文件fileid需要额外增加16个字节
  2. trunk file id 2. 文件在trunk file内部的offset 3. 文件占用的存储空间大小 (字节对齐及删除空间复用,文件占用存储空间>=文件大小)
    每个trunk file由一个id唯一标识,trunk file由group内的trunk server负责创建(trunk server是tracker选出来的),并同步到group内其他的storage,文件存储合并存储到trunk file后,根据其offset就能从trunk file读取到文件。

文件在trunk file内的offset编码到文件名,决定了其在trunk file内的位置是不能更改的,也就不能通过compact的方式回收trunk file内删除文件的空间。但当trunk file内有文件删除时,其删除的空间是可以被复用的,比如一个100KB的文件被删除,接下来存储一个99KB的文件就可以直接复用这片删除的存储空间。

HTTP访问支持

FastDFS的tracker和storage都内置了http协议的支持,客户端可以通过http协议来下载文件,tracker在接收到请求时,通过http的redirect机制将请求重定向至文件所在的storage上;除了内置的http协议外,FastDFS还提供了通过apache或nginx扩展模块下载文件的支持。

这里写图片描述

其他特性

FastDFS提供了设置/获取文件扩展属性的接口(setmeta/getmeta),扩展属性以key-value对的方式存储在storage上的同名文件(拥有特殊的前缀或后缀),比如/group/M00/00/01/some_file为原始文件,则该文件的扩展属性存储在/group/M00/00/01/.some_file.meta文件(真实情况不一定是这样,但机制类似),这样根据文件名就能定位到存储扩展属性的文件。

以上两个接口作者不建议使用,额外的meta文件会进一步“放大”海量小文件存储问题,同时由于meta非常小,其存储空间利用率也不高,比如100bytes的meta文件也需要占用4K(block_size)的存储空间。

FastDFS还提供appender file的支持,通过upload_appender_file接口存储,appender file允许在创建后,对该文件进行append操作。实际上,appender file与普通文件的存储方式是相同的,不同的是,appender file不能被合并存储到trunk file。

问题讨论

从FastDFS的整个设计看,基本上都已简单为原则。比如以机器为单位备份数据,简化了tracker的管理工作;storage直接借助本地文件系统原样存储文件,简化了storage的管理工作;文件写单份到storage即为成功、然后后台同步,简化了写文件流程。但简单的方案能解决的问题通常也有限,FastDFS目前尚存在如下问题(欢迎探讨)。

数据安全性

写一份即成功:从源storage写完文件至同步到组内其他storage的时间窗口内,一旦源storage出现故障,就可能导致用户数据丢失,而数据的丢失对存储系统来说通常是不可接受的。
缺乏自动化恢复机制:当storage的某块磁盘故障时,只能换存磁盘,然后手动恢复数据;由于按机器备份,似乎也不可能有自动化恢复机制,除非有预先准备好的热备磁盘,缺乏自动化恢复机制会增加系统运维工作。
数据恢复效率低:恢复数据时,只能从group内其他的storage读取,同时由于小文件的访问效率本身较低,按文件恢复的效率也会很低,低的恢复效率也就意味着数据处于不安全状态的时间更长。
缺乏多机房容灾支持:目前要做多机房容灾,只能额外做工具来将数据同步到备份的集群,无自动化机制。

存储空间利用率

单机存储的文件数受限于inode数量
每个文件对应一个storage本地文件系统的文件,平均每个文件会存在block_size/2的存储空间浪费。

负载均衡
group机制本身可用来做负载均衡,但这只是一种静态的负载均衡机制,需要预先知道应用的访问特性;同时group机制也导致不可能在group之间迁移数据来做动态负载均衡。
网摘2

FastDFS 工作流程:

上传
FastDFS 提供基本的文件访问接口,如 upload、download、append、delete 等

这里写图片描述

选择tracker server
集群中 tracker 之间是对等关系,客户端在上传文件时可用任意选择一个 tracker

选择存储 group
当tracker接收到upload file的请求时,会为该文件分配一个可以存储文件的group,目前支持选择 group 的规则为:
1.Round robin,所有 group 轮询使用
2.Specified group,指定某个确定的 group
3.Load balance,剩余存储空间较多的 group 优先

选择storage server
当选定group后,tracker会在group内选择一个storage server给客户端,目前支持选择server 的规则为:
1.Round robin,所有 server 轮询使用(默认)
2.根据IP地址进行排序选择第一个服务器(IP地址最小者)
3.根据优先级进行排序(上传优先级由storage server来设置,参数为upload_priority)
选择storage path(磁盘或者挂载点)
当分配好storage server后,客户端将向storage发送写文件请求,storage会将文件分配一个数据存储目录,目前支持选择存储路径的规则为:
1. round robin,轮询(默认)
2.load balance,选择使用剩余空间最大的存储路径
选择下载服务器
目前支持的规则为:
1.轮询方式,可以下载当前文件的任一storage server
2.从源storage server下载

生成 file_id
选择存储目录后,storage 会生成一个 file_id,采用 Base64 编码,包含字段包括:storage server ip、文件创建时间、文件大小、文件 CRC32 校验码和随机数;每个存储目录下有两个 256*256 个子目录,storage 会按文件 file_id 进行两次 hash,路由到其中一个子目录,然后将文件以 file_id 为文件名存储到该子目录下,最后生成文件路径:group 名称、虚拟磁盘路径、数据两级目录、file_id

其中,
组名:文件上传后所在的存储组的名称,在文件上传成功后由存储服务器返回,需要客户端自行保存
虚拟磁盘路径:存储服务器配置的虚拟路径,与磁盘选项 store_path*参数对应
数据两级目录:存储服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件

同步机制
1.新增tracker服务器数据同步问题
由于 storage server 上配置了所有的 tracker server. storage server 和 trackerserver 之间的通信是由 storage server 主动发起的,storage server 为每个 tracker server 启动一个线程进行通信;在通信过程中,若发现该 tracker server 返回的本组 storage server列表比本机记录少,就会将该tracker server上没有的storage server 同步给该 tracker,这样的机制使得 tracker 之间是对等关系,数据保持一致
2.新增storage服务器数据同步问题
若新增storage server或者其状态发生变化,tracker server都会将storage server列表同步给该组内所有 storage server;以新增 storage server 为例,因为新加入的 storage server 会主动连接 tracker server,tracker server 发现有新的 storage server 加入,就会将该组内所有的 storage server 返回给新加入的 storage server,并重新将该组的storage server列表返回给该组内的其他storage server;

  1. 组内storage数据同步问题
    组内storage server之间是对等的,文件上传、删除等操作可以在组内任意一台storageserver 上进行。文件同步只能在同组内的 storage server 之间进行,采用 push 方式, 即源服务器同步到目标服务器
    A. 只在同组内的storage server之间进行同步
    B. 源数据才需要同步,备份数据不再同步
    C. 特例:新增storage server时,由其中一台将已有所有数据(包括源数据和备份数据)同步到新增服务器

storage server的7种状态:
通过命令 fdfs_monitor /etc/fdfs/client.conf 可以查看 ip_addr 选项显示 storage server 当前状态
INIT : 初始化,尚未得到同步已有数据的源服务器
WAIT_SYNC : 等待同步,已得到同步已有数据的源服务器
SYNCING : 同步中
DELETED : 已删除,该服务器从本组中摘除
OFFLINE :离线
ONLINE : 在线,尚不能提供服务
ACTIVE : 在线,可以提供服务

组内增加storage serverA状态变化过程:
1.storage server A 主动连接 tracker server,此时 tracker server 将 storageserverA 状态设置为 INIT
2.storage server A 向 tracker server 询问追加同步的源服务器和追加同步截止时间点(当前时间),若组内只有storage server A或者上传文件数为0,则告诉新机器不需要数据同步,storage server A 状态设置为 ONLINE ;若组内没有 active 状态机器,就返回错误给新机器,新机器睡眠尝试;否则 tracker 将其状态设置为 WAIT_SYNC
3.假如分配了 storage server B 为同步源服务器和截至时间点,那么 storage server B会将截至时间点之前的所有数据同步给storage server A,并请求tracker设置 storage server A 状态为SYNCING;到了截至时间点后,storage server B向storage server A 的同步将由追加同步切换为正常 binlog 增量同步,当取不到更多的 binlog 时,请求tracker将storage server A设置为OFFLINE状态,此时源同步完成
4.storage server B 向 storage server A 同步完所有数据,暂时没有数据要同步时, storage server B请求tracker server将storage server A的状态设置为ONLINE
5.当 storage server A 向 tracker server 发起心跳时,tracker sercer 将其状态更改为 ACTIVE,之后就是增量同步(binlog)

这里写图片描述

注释:
1.整个源同步过程是源机器启动一个同步线程,将数据 push 到新机器,最大达到一个磁盘的 IO,不能并发
2.由于源同步截止条件是取不到 binlog,系统繁忙,不断有新数据写入的情况,将会导致一直无法完成源同步过程

下载

client 发送下载请求给某个 tracker,必须带上文件名信息,tracker 从文件名中解析出文件的 group、大小、创建时间等信息,然后为该请求选择一个 storage 用于读请求;由于 group 内的文件同步在后台是异步进行的,可能出现文件没有同步到其他storage server上或者延迟的问题, 后面我们在使用 nginx_fastdfs_module 模块可以很好解决这一问题

这里写图片描述 这里写图片描述

文件合并原理

小文件合并存储主要解决的问题:
1.本地文件系统inode数量有限,存储小文件的数量受到限制
2.多级目录+目录里很多文件,导致访问文件的开销很大(可能导致很多次IO)
3.按小文件存储,备份和恢复效率低

FastDFS 提供合并存储功能,默认创建的大文件为 64MB,然后在该大文件中存储很多小文件; 大文件中容纳一个小文件的空间称作一个 Slot,规定 Slot 最小值为 256 字节,最大为 16MB,即小于 256 字节的文件也要占用 256 字节,超过 16MB 的文件独立存储;

为了支持文件合并机制,FastDFS生成的文件file_id需要额外增加16个字节;每个trunk file 由一个id唯一标识,trunk file由group内的trunk server负责创建(trunk server是tracker 选出来的),并同步到group内其他的storage,文件存储合并存储到trunk file后,根据其文件偏移量就能从trunk file中读取文件

MooseFS:

MooseFS是一个高可用的故障容错分布式文件系统,它支持通过FUSE方式将文件挂载操作,同时其提供的web管理界面非常方便查看当前的文件存储状态。

特性:

1)从下图中我们可以看到MooseFS文件系统由四部分组成:Managing Server 、Data Server 、Metadata Backup Server 及Client
2)其中所有的元数据都是由Managing Server管理,为了提高整个系统的可用性,Metadata Backup Server记录文件元数据操作日志,用于数据的及时恢复
3)Data Server可以分布式部署,存储的数据是以块的方式分布至各存储节点的,因此提升了系统的整体性能,同时Data Server提供了冗余备份的能力,提升系统的可靠性
4)Client通过FUSE方式挂载,提供了类似POSIX的访问方式,从而降低了Client端的开发难度,增强系统的通用性

元数据服务器(master):负责各个数据存储服务器的管理,文件读写调度,文件空间回收以及恢复

元数据日志服务器(metalogger):负责备份master服务器的变化日志文件,以便于在master server出问题的时候接替其进行工作

数据存储服务器(chunkserver):数据实际存储的地方,由多个物理服务器组成,负责连接管理服务器,听从管理服务器调度,提供存储空间,并为客户提供数据传输;多节点拷贝;在数据存储目录,看不见实际的数据

优点:

1)部署安装非常简单,管理方便
2)支持在线扩容机制,增强系统的可扩展性
3)实现了软RAID,增强系统的 并发处理能力及数据容错恢复能力
4)数据恢复比较容易,增强系统的可用性5)有回收站功能,方便业务定制

缺点:

1)存在单点性能瓶颈及单点故障
2)MFS Master节点很消耗内存
3)对于小于64KB的文件,存储利用率较低

应用场景:

1)单集群部署的应用

2)中、大型文件

GlusterFS:

GlusterFS是Red Hat旗下的一款开源分布式文件系统,它具备高扩展、高可用及高性能等特性,由于其无元数据服务器的设计,使其真正实现了线性的扩展能力,使存储总容量可 轻松达到PB级别,支持数千客户端并发访问;对跨集群,其强大的Geo-Replication可以实现集群间数据镜像,而且是支持链式复制,这非常适用 于垮集群的应用场景

特性:

1)目前GlusterFS支持FUSE方式挂载,可以通过标准的NFS/SMB/CIFS协议像访问本体文件一样访问文件系统,同时其也支持HTTP/FTP/GlusterFS访问,同时最新版本支持接入Amazon的AWS系统
2)GlusterFS系统通过基于SSH的命令行管理界面,可以远程添加、删除存储节点,也可以监控当前存储节点的使用状态
3)GlusterFS支持集群节点中存储虚拟卷的扩容动态扩容;同时在分布式冗余模式下,具备自愈管理功能,在Geo冗余模式下,文件支持断点续传、异步传输及增量传送等特点
Yuyj GlusterFS.png

优点:

1)系统支持POSIX(可移植操作系统),支持FUSE挂载通过多种协议访问,通用性比较高
2)支持在线扩容机制,增强系统的可扩展性
3)实现了软RAID,增强系统的 并发处理能力及数据容错恢复能力
4)强大的命令行管理,降低学习、部署成本
5)支持整个集群镜像拷贝,方便根据业务压力,增加集群节点
6)官方资料文档专业化,该文件系统由Red Hat企业级做维护,版本质量有保障

缺点:

1)通用性越强,其跨越的层次就越多,影响其IO处理效率
2)频繁读写下,会产生垃圾文件,占用磁盘空间

应用场景:

1)多集群部署的应用
2)中大型文件根据目前官方提供的材料,现有的使用GlusterFS系统存储容量可轻松达到PB

术语:

brick:分配到卷上的文件系统块;
client:挂载卷,并对外提供服务;
server:实际文件存储的地方;
subvolume:被转换过的文件系统块;
volume:最终转换后的文件系统卷。

MogileFS :

开发语言:perl

开源协议:GPL

依赖数据库

Trackers(控制中心):负责读写数据库,作为代理复制storage间同步的数据

Database:存储源数据(默认mysql)

Storage:文件存储

除了API,可以通过与nginx集成,对外提供下载服务

源码路径:https://github.com/mogilefs

TiDB :

TiDB是一个分布式数据库

Tidb和mysql几乎完全兼容,所以我们的程序没有任何改动就完成了数据库从mysql到TiDb的转换,TiDB 是一个分布式 NewSQL (SQL 、 NoSQL 和 NewSQL 的优缺点比较 )数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。

官网:https://pingcap.com/index.html

TiDB可以理解为是MySQL的加强版/分布式MySQL/MySQLPlus。

TiDB简介:

TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytical Processing) 数据库,结合了传统的 RDBMS 和NoSQL 的最佳特性。TiDB 兼容 MySQL,支持无限的水平扩展,具备强一致性和高可用性。TiDB 的目标是为 OLTP(Online Transactional Processing) 和 OLAP (Online Analytical Processing) 场景提供一站式的解决方案。

TiDB 是 PingCAP 公司基于 Google Spanner / F1 论文实现的开源分布式 NewSQL 数据库。实现了自动的水平伸缩,强一致性的分布式事务,基于 Raft 算法的多副本复制等重要 NewSQL 特性。 TiDB 结合了 RDBMS 和 NoSQL 的优点,部署简单,在线弹性扩容和异步表结构变更不影响业务, 真正的异地多活及自动故障恢复保障数据安全,同时兼容 MySQL 协议,使迁移使用成本降到极低。

TiDB 具备如下 NewSQL 核心特性:

  • SQL支持 (TiDB 是 MySQL 兼容的)
  • 水平线性弹性扩展
  • 分布式事务
  • 跨数据中心数据强一致性保证
  • 故障自恢复的高可用

TiDB 的设计目标是 100% 的 OLTP 场景和 80% 的 OLAP 场景。

TiDB 对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分库分表等 Sharding 方案。同时它也让开发运维人员不用关注数据库 Scale 的细节问题,专注于业务开发,极大的提升研发的生产力。

TiDB的现在和未来:

img

大家好,我是黄东旭,是 PingCAP 的联合创始人和 CTO,这是 PingCAP 成立以来的第一次发布会,我想跟大家简单聊聊 TiDB 在产品和技术上的更新。考虑到线上的很多观众不一定是有很强的技术背景,我将尽我所能将技术的部分说得让大家都能够理解。

在讲正题之前有一个小故事,我们做基础软件的产品经理去跟客户聊需求的时候,客户经常都会说:对于数据库,我的要求特别简单、特别基础、非常朴素,我不要求很多功能,安全稳定是必须的,最好能高可用,性能一定要好,如果数据量大了,能实现弹性伸缩就更好了;另外,最好别让我学太多新东西,用起来跟过去使用的产品差不多,这就是一款完美的数据库产品。

就像大家在家里用自来水一样,我们对自来水的需求就是拧开水龙头水就能出来,但是背后自来水厂是怎么处理的大家不用知道,我们只需要根据实际情况使用冷水或者热水就好。但是从技术的角度来说,刚才类似冷热水这个非常朴素的基础需求,类比一下放到数据库的世界这就是一个图灵奖级别的基础需求,稍微解释一下图灵奖是计算机行业学术界最顶级的,相当于计算机界的诺贝尔奖。

这里有两位行业泰斗级的人物,左边 Leslie Lamport 在 2013 年研究相关问题拿了图灵奖,右边这位跟我们挺有缘的,发型跟(我们的 CEO)刘奇同学挺像,他是 UC 伯克利分校的一名教授,也是著名 CAP 定理的提出者,PingCAP 中的 CAP 就是来源于此。虽然看上去这个需求是一个很朴素的需求,但是这是一个值得去花很长时间研究,在技术领域很有挑战,也是一个很前沿的研究领域。
img

在聊数据库之前,我想带大家回顾一下十年前的电子产品,回顾一下我们当年的生活,大家回想一下十年前我们手上的数码产品有哪些,比如我们打电话有诺基亚,拍照有数码相机,也有用来做导航的独立设备 GPS,听歌用的 MP3 等等,种类繁多。

我们再来回顾一下这十年,这些东西好像在我们的生活中渐渐消失掉了,一台智能手机把很多这些碎片化的东西统一了起来,我觉得这背后一个很重要的点就是我们对于统一用户体验的追求驱动了整个科技界产品发生翻天覆地的变革,现在一台智能手机基本解决了我们生活中百分之七八十的数字化生活场景的需求。
img

TiDB 是一个 HTAP 系统
接下来进入正题,PingCAP 是做数据库的厂商,如果我们拉一条数轴来看,左边的业务是更偏实时在线的业务,如果这条数轴的右边是离线业务的话,按照这个数轴来看,数据库这个产品大家可能印象中是在左边的,一些典型场景,比如像 Hadoop 或者一些数据仓库、报表是在右边。
img

再看一个具体的业务场景,假设是一个公司要打造电商平台的 IT 系统,梳理一下现在电商平台内部有各种各样的应用和场景,我们按照这些场景放到这个数轴上,左边是在线,右边是离线,我们看到比如交易、订单管理、明细查询,这可能是偏在线的业务,用户用手机随时可以打开看;右边离线业务更像是内部运营人员,比如老板查看去年赚了多少钱,这种报表可能是一个更偏离线的业务。
img

大家有没有发现中间有一些实时的报表,实时的促销调价,热销产品的推荐,你放在左边不合适,放在右边好像也不太对,所以中间部分是一个比较模糊的状态,这是一个业务的语言,比如我们把这个业务放到这条轴上去看,比如说我是电商平台的技术人员,业务人员告诉我,我们上面这些需求,这些需求翻译成技术的语言会变成什么样子呢?

就变成了各种各样的 OLTP 数据库和 OLAP 数据库和数据仓库,比如像 ClickHouse、Greenplum,像离线的数据仓库 Hadoop、HIVE,有很多同学不了解这些名词没关系,我只是想展现一下,业务需求翻译成技术语言,通常需要一系列复杂的数据技术栈来支撑。
img

可能有很多观众学过计算机技术,我记得我在上大学的时候,我们有一门课是叫数据库系统,老师上课的时候教我数据库就是增删改查,就是存数据、取数据的一个系统、一个软件,几个关键的命令 INSERT\SELECT\UPDATE\DELETE,我回忆了一下好象也没有教哪些场景是 OLTP 的场景,哪些是 OLAP 的系统,并没有这么复杂。

数据库应该就是存数据、取数据天经地义,就像水龙头一样一拧开就出水,我还特地查了一下 database 的定义,在维基百科上面的定义其实并没有说 OLTP 的 database 或者 OLAP 的 database。

我知道这可能是一个细分的领域,但是从数据库这个词的本源来看,本质上像一个容器一样,存储数据和取数据的一个系统,好像也没什么复杂。

为什么今天很多工程师,很多用户就觉得这个数据库或者这个场景一定是个 OLTP,或者是个 OLAP ,要有一个泾渭分明的分类。就像刚才电商的例子,其实有大量中间的场景很难说到底是一个 OLTP 还是 OLAP 。但是现在的现实是对于很多 IT 系统、业务系统来说,对于实时性的要求越来越高,为了解决这个问题,我们构建了各种各样的数据孤岛,构建了各种各样的烟囱式系统。
img

所以过去这种泾渭分明式的分类方式到底适不适用现在有越来越多实时性要求的时代呢?

回过头来思考这个分类是不是有问题的时候,作为一个理工男或者作为一个学理科出身的工程师,我们特别喜欢寻找一个定义或者寻找一个分类。我们找遍了各种各样的定义,从学术界、工业界、到各类咨询机构,发现 HTAP 是一个更加符合或者说更加适合现在 TiDB 应用场景的一个定义。
img

TiDB 的定位是一个 Real-Time 的 HTAP 系统,有很多朋友后来问我,TiDB 是一个 HTAP 系统,是不是就意味着你不是一个 OLTP 系统,或者说你到底是一个 OLTP 还是 OLAP ?

我们回到智能手机的那个例子,首先智能手机一定是一个 100% 的手机,肯定能打电话,在打电话的基础上再加上很多其他的常用功能,比如相机、GPS、MP3等在一个系统里面搞定。我想强调的是 TiDB 的定位就是 Real-Time HTAP 系统,首先是一个 100% 的 OLTP 系统,同时还能支持一些 Real-Time OLAP Query。
img

讲到 TiDB,我其实很感慨,我一直看着这个产品一步步成长起来,最近这一年成长速度尤其快,现在我很高兴地看到 TiDB 4.0 已经成为社区的一个主流版本。

在 4.0 发布的时候,我当时很热情洋溢的发表了一段话,说这是一个非常具有里程碑意义的一个版本,事实上 TiDB 4.0 的表现我觉得是不负众望的,现在大家都非常喜欢,成为了主流。
img

展望 TiDB 5.0
我想跟大家展望一下 TiDB 5.0,在讲 5.0 之前我想稍微强调一下 TiDB 做产品的思路。我们都是工程师出身,也比较接地气,不说什么高大上的,用大白话来说就四点:稳、快、好用和用着放心。前面提到过用户对于一个数据库产品的朴素需求,我们也是希望按照这种方式来做产品。

但在 TiDB 5.0 里面我们真正把一个具体的目标放到了产品规划的方向里面,那就是 TiDB 要走向各行各业的核心场景。之前,TiDB 从 2.0 到 3.0 和 4.0,已经开始慢慢地走向了各行各业,慢慢地渗透到一些对稳定性、对性能要求非常极致的场景,包括金融、银行的一些核心业务系统。

在 TiDB 5.0 这个版本,我们第一次明确地提出,至少在产品层面上要达到各行业核心场景对于数据库性能和稳定性的要求。
img

接下来具体谈谈 TiDB 5.0 在这几个方面的进展。

首先第一点稳定性,我经常说一句话:把一个东西做对其实是很难的,把一个数据库做出来不难,做对却很难,所以我们构建了各种各样的正确性的测试系统。现在 TiDB 已经做出来了,下一个阶段是更稳,但是要使得 TiDB 更加稳定还有很多的工作要做,这方面没有捷径,道理谁都懂,魔鬼在细节,只有做得越来越深,越来越细,我很高兴的看到,在 TiDB 5.0 中我们和用户一起把 TiDB 的稳定性又往前推进了一个级别。

下图是 5.0 在某个券商机构的测试结果,在 48 小时高压力的测试场景里面 TiDB 5.0 的系统抖动一直小于 5%,同时在性能上跟 4.0 相比有明显的提升。我想强调的是在做稳定性这件事情上,已经开始进入改革深水区,低垂的果实已经摘的差不多了,剩下就是一些场景和技术上比较硬的难题等着我们去攻克,我们很高兴地看到 5.0 对于 4.0 在稳定性上有比较出色的表现。
img

第二,性能快。天下武功,唯快不破,尤其是对于数据库这样的基础软件来说,特别是在一些核心应用场景,比如像银行的一些核心交易系统,一个毫秒的额外延迟可能就会对整体的系统和用户体验造成影响。

在 TiDB 5.0 里面我们很高兴地看到,对比 4.0 延迟几乎成倍地下降。这让我想到跟开发团队经常开的玩笑,说 TiDB 每个版本有点像摩尔定律,每一个版本比上一个版本性能提升一倍、延迟下降一倍、成本不变,未来成本还会持续下降。我很高兴看到 5.0 还是保持着这个规律,所以,性能快与延迟降低在 5.0 里面会是一个很重要的标签。
img

快的另外一个方面,首先熟悉 TiFlash 的同学看到标题肯定会心一笑,我们之前有提到 TiDB 是一个 Real-Time HTAP 架构,AP 这部分是由 TiFlash 分析加速引擎来提供服务的。在 5.0 里面 TiFlash 将支持分布式的聚合和分布式的计算(MPP),能让整个 TiDB 的 OLAP 能力真正延伸到更多的应用场景,应对更多更复杂的 JOIN 场景。
img

好用,这个方面我想强调的有两点。

首先,大家看到跨数据中心、跨地域一个部署,很多做分布式系统的同学会觉得很兴奋,TiDB 终于可以支持做 Geo-Partition(多地多活跨地域数据分布) 。我稍微解释一下,TiDB 是一个分布式数据库,所以有很多用户、很多开发者希望 TiDB 可以实现真正的跨长距离或者跨多个数据中心的部署,可以在全国或者全球都组成一个大的网络。

其次,打通多个流行数据处理栈生态和提供全链路追踪系统,也将使得 TiDB 5.0 变得更加好用。
img

提到 Geo-Partion,实际上,有很多客户有这样的场景需求,特别是对于一些海外客户,比如一家欧洲公司的业务遍布全球,这时候如果有一个数据库系统,能够支持全球跨长距离的区域部署,能够在不同的国家、不同的位置随时提供服务,运维方面也省去了部署多套技术栈和数据库系统的成本,这对于客户来说就是一个理想之选。

举个例子,我们刚才提到多地部署,怎么降低本地的延迟,如果是一个欧洲公司,大家知道欧洲有很严格的 GDPR ,企业的数据不能出境,这种场景下 Geo-Partition 就是一个非常实用的特性。
img

如果是现在的 TiDB 去做这件事情,比如说在欧洲、中国、美国同时提供服务,在一些场景下的数据库性能和延迟可能会不太理想,因为需要到一个中央的服务器上去处理,去拿时间戳,然后才能提供服务。

在 TiDB 5.0 里面,我们将中央的授时服务改成了一个分布式授时的服务,能够让这个系统在本地或者在多数据中心场景里面的性能表现更佳。
img

第二个方面是 TiDB 的“朋友”越来越多,作为一个基础软件,虽然 TiDB 的目标是尽可能的把很多场景统一,但我觉得 TiDB 跟业界其他生态技术栈的互联互通也是一个非常重要的方面。

现在对于 TiDB 来说,已经能够与 Flink、Kafka、Spark,包括 Hadoop、AWS S3 以及 MySQL、Oracle 这些传统的数据库进行互联互通。
img

举一个具体用户的例子,这是一个在线的实时数仓业务,线上的业务持续在做交易,在做写入,同时这些数据通过 TiDB TiCDC 增量订阅模块输出到 Kafka,同步到 Flink 流式计算引擎去做归纳、聚合与分析,然后再重新写回到 TiDB。写入到 TiDB 的这些数据再去对在线业务进行补充,TiDB 结合 Kafka、Flink 组成了一个简单易用的实时数仓架构。
img

说到安全,对于数据库来说,安全其实是非常重要的,在我们的产品规划里面,安全是放在很高优先级的特性。

我想强调,在 4.0 里面 TiKV 存储层已经支持全链路的数据透明加密。在 DBaaS 平台里面,我们正在引入 RBAC,就是基于用户身份的鉴权系统,同时我们会跟 AWS 的身份认证系统进行打通,给用户提供更加安全、更加可靠的产品与服务保障。

在安全合规方面,随着 TiDB 越来越多地应用在国内、国外一些关键的业务场景,不同的国家,不同的应用场景对于合规的要求呈现多样化特点, TiDB 已经通过了 SOC2Type1 认证,这是在美国金融行业里面非常认可的合规标准,未来我们将在合规上面投入更多的精力。
img

未来的数据库是什么样子?

前面是对于 TiDB 5.0 在现在这个时间点的进展同步。但是未来在哪里?

我们每一个分享最后的部分会讲讲到底未来应该是什么样子的,我觉得在比较近的未来,TiDB 一个重要的方向是更加云化。为什么云如此重要?数据库云化的背后我觉得可以展开几点:

  • 一个是 Severless;

  • 二是智能的调度能力;

  • 第三是利用云的基础设施降低成本。

img

为什么说云如此重要?大家可以看到右边这张图,横轴是数据量和业务需求,纵轴是企业在 IT 上投入的成本,在云诞生之前我们在去做面向不确定性的业务,面向暴涨的数据量,如果采用传统的 IDC 部署方式,成本和投入的曲线跟实际的需求不能完全吻合,其实存在很多资源的浪费。

首先,只有云真正把整个基础软件的商业模式变成了pay as you go,尽可能地贴合业务的增长曲线,对于数据库这样很重要的基础软件来说,在云上利用云的弹性能够去更合理地满足业务的实际需求。

第二,云很好地屏蔽了底层基础架构实现的复杂性,对于做基础软件的人来说,这具有划时代的意义。比如说利用云的弹性调度能力以及一些 AI 新技术,使得这个系统更好地理解业务的需求,更加智能地规划该把数据放置在什么地方,该建立什么样的索引。

第三,云是很好的基础设施,面向云时代的基础设施如何去设计下一代的基础软件,我觉得是一个很重要的课题。最近关注技术圈的朋友,Snowflake 的消息令大家非常振奋,Snowflake 在技术上的选择也带给我很多启发,怎么通过云的基础设施来打造新一代的基础软件。
img

TiDB特点:

  • **高度兼容 MySQL。**大多数情况下,无需修改代码即可从 MySQL 轻松迁移至 TiDB,分库分表后的 MySQL 集群亦可通过 TiDB 工具进行实时迁移。

  • **水平弹性扩展。**通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。

  • **分布式事务。**TiDB 100% 支持标准的 ACID 事务。

  • **真正金融级高可用。**相比于传统主从 (M-S) 复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复 (auto-failover),无需人工介入。

  • **一站式 HTAP 解决方案。**TiDB 作为典型的 OLTP 行存数据库,同时兼具强大的 OLAP 性能,配合 TiSpark,可提供一站式 HTAP解决方案,一份存储同时处理OLTP & OLAP(OLAP、OLTP的介绍和比较 )无需传统繁琐的 ETL 过程。

  • **云原生 SQL 数据库。**TiDB 是为云而设计的数据库,同 Kubernetes (十分钟带你理解Kubernetes核心概念 )深度耦合,支持公有云、私有云和混合云,使部署、配置和维护变得十分简单。

TiDB 的设计目标是 100% 的 OLTP 场景和 80% 的 OLAP 场景,更复杂的 OLAP 分析可以通过 TiSpark 项目来完成。 TiDB 对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分库分表等 Sharding 方案。同时它也让开发运维人员不用关注数据库 Scale 的细节问题,专注于业务开发,极大的提升研发的生产力.

核心特性:

1、水平扩展
无限水平扩展是 TiDB 的一大特点,这里说的水平扩展包括两方面:计算能力和存储能力。TiDB Server 负责处理 SQL 请求,随着业务的增长,可以简单的添加 TiDB Server 节点,提高整体的处理能力,提供更高的吞吐。TiKV 负责存储数据,随着数据量的增长,可以部署更多的 TiKV Server 节点解决数据 Scale 的问题。PD 会在 TiKV 节点之间以 Region 为单位做调度,将部分数据迁移到新加的节点上。所以在业务的早期,可以只部署少量的服务实例(推荐至少部署 3 个 TiKV, 3 个 PD,2 个 TiDB),随着业务量的增长,按照需求添加 TiKV 或者 TiDB 实例。

2、高可用
高可用是 TiDB 的另一大特点,TiDB/TiKV/PD 这三个组件都能容忍部分实例失效,不影响整个集群的可用性。下面分别说明这三个组件的可用性、单个实例失效后的后果以及如何恢复。

3、TiDB

TiDB 是无状态的,推荐至少部署两个实例,前端通过负载均衡组件对外提供服务。当单个实例失效时,会影响正在这个实例上进行的 Session,从应用的角度看,会出现单次请求失败的情况,重新连接后即可继续获得服务。单个实例失效后,可以重启这个实例或者部署一个新的实例。

4、PD

PD 是一个集群,通过 Raft 协议保持数据的一致性,单个实例失效时,如果这个实例不是 Raft 的 leader,那么服务完全不受影响;如果这个实例是 Raft 的 leader,会重新选出新的 Raft leader,自动恢复服务。PD 在选举的过程中无法对外提供服务,这个时间大约是3秒钟。推荐至少部署三个 PD 实例,单个实例失效后,重启这个实例或者添加新的实例。

5、TiKV

TiKV 是一个集群,通过 Raft 协议保持数据的一致性(副本数量可配置,默认保存三副本),并通过 PD 做负载均衡调度。单个节点失效时,会影响这个节点上存储的所有 Region。对于 Region 中的 Leader 结点,会中断服务,等待重新选举;对于 Region 中的 Follower 节点,不会影响服务。当某个 TiKV 节点失效,并且在一段时间内(默认 10 分钟)无法恢复,PD 会将其上的数据迁移到其他的 TiKV 节点上。

TiDb整体架构:

TiDB 集群主要分为三个组件:

1、TiDB Server
TiDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。 TiDB Server是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(如LVS、HAProxy 或F5)对外提供统一的接入地址。

2、PD Server
Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader的迁移等);三是分配全局唯一且递增的事务 ID。   
PD 是一个集群,需要部署奇数个节点,一般线上推荐至少部署 3 个节点。

3、TiKV Server
TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range (从 StartKey 到EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region 。TiKV 使用 Raft协议做复制,保持数据的一致性和容灾。副本以 Region 为单位进行管理,不同节点上的多个 Region 构成一个 RaftGroup,互为副本。数据在多个 TiKV 之间的负载均衡由 PD 调度,这里也是以 Region 为单位进行调度。

要深入了解 TiDB 的水平扩展和高可用特点,首先需要了解 TiDB 的整体架构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TiDB 到底有什么用:

**编辑推荐:**本文主要介绍了 TiDB 的四个主要应用场景以及TiDB 产品的整体架构用例如何让使用 TiDB 替换 MySQL,希望对您的学习有所帮助。 本文来自于企鹅号 - 编程沉思录,由火龙果软件Alice编辑、推荐。如今硬件的性价比越来越高,网络传输速度越来越快,数据库分层的趋势逐渐显现,人们已经不再强求用一个解决方案来解决所有的存储问题,而是通过分层,让缓存与数据库负责各自擅长的业务场景。当前数据库领域面临各种问题,如在缩放、一致性、大数据分析、与云基础架构集成等方面均存在诸多问题,现有的数据库解决方案和大数据分析引擎解决方案基本处于割裂的状态,由于 Oracle、MySQL 数据库并不是面向分布式环境而设计,因此即使勉强通过分库、分表或中间件的方式,在数据库层面做了分片,从本质上看也只是复制了相同的堆栈,而非针对分布式系统进行存储和计算优化,这正是进行跨业务查询或跨物理机查询和写入十分繁琐的本质原因。NoSQL 虽然解决了数据库弹性扩展的难题,但是却放弃了数据的强一致性以及对 ACID 事务的支持,带来了新的问题。为了解决这一问题,TiDB 在架构上将计算和存储层进行高度的抽象和分离,对混合负载的场景通过 IO 优先级队列,智能副本调度,行列混合存储等技术使其变为可能。TiDB 作为开源的分布式关系数据库,其特点是几乎可以 100% 兼容 MySQL 接口,也兼容 MySQL 的语法和协议,在保证不丧失 ACID 事务的前提下,能够弹性伸缩,高可用,可以同时处理 OLTP 和 OLAP 工作负载,不再需要 ETL。

img

TiDB整体架构图:

TiDB 产品的整体架构是高度分层的,由分布式 SQL 层(TiDB)、分布式 KV 存储引擎(TiKV)以及管理整个集群的 PD 模块组成。无限水平扩展是 TiDB 的一大特点,这里所说的水平扩展包括两方面:计算能力和存储能力。

HTAP 给开发者提供了一个实时数据分析方面的新思路,不需要再去维护另一个离线的数据仓库,既减轻了 ETL 的工作,又能节省很大一部分建立数据仓库所用到的存储和计算成本,HTAP 将是未来的重要趋势。黄东旭介绍了 TiDB 的四个主要应用场景,一是 MySQL 分片与合并;二是直接替换 MySQL;三是用做数据仓库;四是作为其他系统的一个模块。

用例1:MySQL分片与合并

img img

Syncer

TiDB 应用的第一类场景是 MySQL 的分片与合并。对于已经在用 MySQL 的业务,分库、分表、分片、中间件是常用手段,随着分片的增多,跨分片查询是一大难题。TiDB 在业务层兼容 MySQL 的访问协议,PingCAP 做了一个数据同步的工具——Syncer,它可以把 TiDB 作为一个 MySQL Slave,将 TiDB 作为现有数据库的从库接在主 MySQL 库的后方,在这一层将数据打通,可以直接进行复杂的跨库、跨表、跨业务的实时 SQL 查询。黄东旭提到,“过去的数据库都是一主多从,有了 TiDB 以后,可以反过来做到多主一从。”

用例2:直接替换MySQL

img

第二类场景是用 TiDB 直接去替换 MySQL。如果你的IT架构在搭建之初并未考虑分库分表的问题,全部用了 MySQL,随着业务的快速增长,海量高并发的 OLTP 场景越来越多,如何解决架构上的弊端呢?

在一个 TiDB 的数据库上,所有业务场景不需要做分库分表,所有的分布式工作都由数据库层完成。TiDB 兼容 MySQL 协议,所以可以直接替换 MySQL,而且基本做到了开箱即用,完全不用担心传统分库分表方案带来繁重的工作负担和复杂的维护成本,友好的用户界面让常规的技术人员可以高效地进行维护和管理。另外,TiDB 具有 NoSQL 类似的扩容能力,在数据量和访问流量持续增长的情况下能够通过水平扩容提高系统的业务支撑能力,并且响应延迟稳定。

黄东旭在演讲中提到了摩拜单车的案例,摩拜早期的数据库全部用 MySQL,随着业务的快速增长,MySQL 的弊端逐渐显现,摩拜单车于 2017 年初开始使用 TiDB 替换 MySQL。如今,摩拜的 IT 系统中已部署了数套 TiDB 集群,近百个节点,承载着数十 TB 的各类数据。

用例3:数据仓库

img img

TiDB 本身是一个分布式系统,第三种使用场景是将 TiDB 当作数据仓库使用。TPC-H 是数据分析领域的一个测试集,TiDB 2.0 在 OLAP 场景下的性能有了大幅提升,原来只能在数据仓库里面跑的一些复杂的 Query,在 TiDB 2.0 里面跑,时间基本都能控制在 10 秒以内。当然,因为 OLAP 的范畴非常大,TiDB 的 SQL 也有搞不定的情况,为此 PingCAP 开源了 TiSpark,TiSpark 是一个 Spark 插件,用户可以直接用 Spark SQL 实时地在 TiKV 上做大数据分析。

用例4:作为其他系统的模块

img

TiDB 是一个传统的存储跟计算分离的项目,其底层的 Key-Value 层,可以单独作为一个 HBase 的 Replacement 来用,它同时支持跨行事务。TiDB 对外提供两个 API 接口,一个是 ACID Transaction 的 API,用于支持跨行事务;另一个是 Raw API,它可以做单行的事务,换来的是整个性能的提升,但不提供跨行事务的 ACID 支持。用户可以根据自身的需求在两个 API 之间自行选择。例如有一些用户直接在 TiKV 之上实现了 Redis 协议,将 TiKV 替换一些大容量,对延迟要求不高的 Redis 场景。

TiDb安装部署:

Tidb安装部署,可能比较麻烦,一步步照着做,如果公司有专门的运维,这个工作可以由运维来搞,但是大多数的中小公司是没有的,都是开发者兼职运维,所以作为一个开发者,还是了解下比较好。

TiDB软硬件环境:

TiDB 作为一款开源分布式 NewSQL 数据库,可以很好的部署和运行在 Intel 架构服务器环境及主流虚拟化环境,并支持绝大多数的主流硬件网络。作为一款高性能数据库系统,TiDB 支持主流的 Linux 操作系统环境。

TiDB 支持部署和运行在 Intel x86-64 架构的 64 位通用硬件服务器平台。对于开发,测试,及生产环境的服务器硬件配置有以下要求和建议:

  • 开发及测试环境
组件CPU内存本地存储网络实例数量(最低要求)
TiDB16核+16 GB+SAS, 200 GB+千兆网卡1
PD16核+16 GB+SAS, 200 GB+千兆网卡-
TiKV16核+32 GB+SAS, 200 GB+千兆网卡3
服务器总计4
  • 生产环境
组件CPU内存硬盘类型硬盘数量单块硬盘大小网络实例数量(最低要求)
TiDB32核+128 GB+SSD最低2块500 GB+2块+ 万兆网卡2
PD16核+32 GB+SSD最低2块200 GB+2块+ 万兆网卡3
TiKV32核+128 GB+SSD最低2块200~500 GB2块+ 万兆网卡3
监控16核+32 GB+SAS最低4块200 GB+2块+ 千兆网卡1
服务器总计9

对于生产环境,这个配置要求还是挺高的,光硬件费用都是一笔不小的开销。再说目前TiDB还属于小步慢跑阶段,一般拿来都是在非核心业务使用,积累运维经验,所以这个配置对于现实环境来说还是有点高了,所以刚开始试水也是可以折中。

比如,因 TiDB 和 PD 对磁盘 IO 要求不高,所以只需要普通磁盘即可。TiKV 对磁盘 IO 要求较高,可以选择 SSD,另官方建议 TiKV 硬盘大小建议不超过 500G,以防止硬盘损害时,数据恢复耗时过长。其TiDB 节点和 PD 节点也可以部署在同台服务器上,而 TiKV 节点独立部署在服务器上,最少 3 台,保持 3 副本,根据容量大小进行扩展。 如对性能和可靠性有更高的要求,应尽可能分开部署。强烈建议使用万兆网卡。

  • 网络环境

TiDB其正常运行需要网络环境提供如下的网络端口配置要求,管理员可根据实际环境中 TiDB 组件部署的方案,在网络侧和主机侧启用相关端口:

组件默认端口说明
TiDB4000应用及 DBA 工具访问通信端口
TiDB10080TiDB 状态信息上报通信端口
TiKV20160TiKV 通信端口
PD2379提供 TiDB 和 PD 通信端口
PD2380PD 集群节点间通信端口
Prometheus9090Prometheus 服务通信端口
Pushgateway9091TiDB, TiKV, PD 监控聚合和上报端口
Node_exporter9100TiDB 集群每个节点的系统信息上报通信端口
Grafana3000Web 监控服务对外服务和客户端(浏览器)访问端口
TiDB部署方案:

Ansible 是一款自动化运维工具,TiDB-Ansible 是 PingCAP 基于 Ansible playbook 功能编写的集群部署工具。使用 TiDB-Ansible 可以快速部署一个完整的 TiDB 集群(包括 PD、TiDB、TiKV 和集群监控模块)。

本部署工具可以通过配置文件设置集群拓扑,一键完成以下各项运维工作:

  • 初始化操作系统,包括创建部署用户、设置 hostname 等
  • 部署组件
  • 滚动升级,滚动升级时支持模块存活检测
  • 数据清理
  • 环境清理
  • 配置监控模块

TiDB-Ansible 工具还支持扩容及缩容操作。另外, TiDB也同样支持二进制部署、Docker部署、跨机房部署等不同方案。

TiDB监控方案:

Pincap 团队给 TiDB 提供了一整套监控的方案,他们使用开源时序数据库 Prometheus 作为监控和性能指标信息存储方案,使用 Grafana 作为可视化组件进行展示。具体如下图,在 client 端程序中定制需要的 Metric 。Push GateWay 来接收 Client Push 上来的数据,统一供 Prometheus 主服务器抓取。AlertManager 用来实现报警机制,使用 Grafana 来进行展示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Grafana 是一个开源的 metric 分析及可视化系统。使用 Grafana 来展示 TiDB 的各项性能指标 。如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TiSpark助力OLAP:

TiSpark 是 PingCAP 为解决用户复杂 OLAP 需求而推出的产品。借助 Spark 平台,同时融合 TiKV 分布式集群的优势,和 TiDB 一起为用户一站式解决 HTAP (Hybrid Transactional/Analytical Processing)需求。 TiSpark 依赖于 TiKV 集群和 Placement Driver(PD)。当然,TiSpark 也需要您搭建一个 Spark 集群。

TiSpark 是将 Spark SQL 直接运行在分布式存储引擎 TiKV 上的 OLAP 解决方案。其架构图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • TiSpark 深度整合了 Spark Catalyst 引擎, 可以对计算提供精确的控制,使 Spark 能够高效的读取 TiKV 中的数据,提供索引支持以实现高速的点查。
  • 通过多种计算下推减少 Spark SQL 需要处理的数据大小,以加速查询;利用 TiDB 的内建的统计信息选择更优的查询计划。
  • 从数据集群的角度看,TiSpark + TiDB 可以让用户无需进行脆弱和难以维护的 ETL,直接在同一个平台进行事务和分析两种工作,简化了系统架构和运维。
  • 除此之外,用户借助 TiSpark 项目可以在 TiDB 上使用 Spark 生态圈提供的多种工具进行数据处理。例如使用 TiSpark 进行数据分析和 ETL;使用 TiKV 作为机器学习的数据源;借助调度系统产生定时报表等等。
TiDB周边工具:
  • mydumper/loader

备份恢复工具,使用 mydumper 从 TiDB 导出数据进行备份,然后用 loader 将其导入到 TiDB 里面进行恢复。虽然 TiDB 也支持使用 MySQL 官方的 mysqldump 工具来进行数据的备份恢复工作,但相比于 mydumper / loader,性能会慢很多,大量数据的备份恢复会花费很多时间,因此并不推荐。

其mydumper/myloader是Percona开源产品,多线程MySQL逻辑备份和恢复工具。那为什么PingCAP还开发loader工具呢?官方是这么说的:在使用过程中,mydumper 问题不大,但是 myloader 由于缺乏出错重试、断点续传这样的功能,使用起来很不方便。所以我们开发了 loader,能够读取 mydumper 的输出数据文件,通过 mysql protocol 向 TiDB/MySQL 中导入数据。

  • syncer

根据MySQL binlog增量同步工具,Syncer 可以部署在任一台可以连通对应的 MySQL 和 TiDB 集群的机器上,推荐部署在 TiDB 集群。

架构图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • TiDB-Binlog

TiDB-Binlog 用于收集 TiDB 的 Binlog,并提供实时备份和同步功能的商业工具。

TiDB-Binlog 支持以下功能场景:

数据同步: 同步 TiDB 集群数据到其他数据库

实时备份和恢复: 备份 TiDB 集群数据,同时可以用于 TiDB 集群故障时恢复

  • PD Control

PD Control 是 PD 的命令行工具,用于获取集群状态信息和调整集群。

TiDB官方:https://pingcap.com

TiDB原理与实现:

三篇文章了解 TiDB 技术内幕:

TiDB 架构是 SQL 层和 KV 存储层分离,相当于 InnoDB 插件存储引擎与 MySQL 的关系。从下图可以看出整个系统是高度分层的,最底层选用了当前比较流行的存储引擎 RocksDB,RockDB 性能很好但是是单机的,为了保证高可用所以写多份(一般为 3 份),上层使用 Raft 协议来保证单机失效后数据不丢失不出错。保证有了比较安全的 KV 存储的基础上再去构建多版本,再去构建分布式事务,这样就构成了存储层 TiKV。有了 TiKV,TiDB 层只需要实现 SQL 层,再加上 MySQL 协议的支持,应用程序就能像访问 MySQL 那样去访问 TiDB 了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里还有个非常重要的概念叫做 Region。MySQL 分库分表是将大的数据分成一张一张小表然后分散在多个集群的多台机器上,以实现水平扩展。同理,分布式数据库为了实现水平扩展,就需要对大的数据集进行分片,一个分片也就成为了一个 Region。数据分片有两个典型的方案:一是按照 Key 来做 Hash,同样 Hash 值的 Key 在同一个 Region 上,二是 Range,某一段连续的 Key 在同一个 Region 上,两种分片各有优劣,TiKV 选择了 Range partition。TiKV 以 Region 作为最小调度单位,分散在各个节点上,实现负载均衡。另外 TiKV 以 Region 为单位做数据复制,也就是一个 Region 保留多个副本,副本之间通过 Raft 来保持数据的一致。每个 Region 的所有副本组成一个 Raft Group, 整个系统可以看到很多这样的 Raft groups。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后简单说一下调度。 TiKV 节点会定期向 PD 汇报节点的整体信息,每个 Region Raft Group 的 Leader 也会定期向 PD 汇报信息,PD 不断的通过这些心跳包收集信息,获得整个集群的详细数据,从而进行调度,实现负载均衡。

TiDB的使用:

最近这几个月,特别是 TiDB RC1 发布后,越来越多的用户已经开始测试起来,也有很多朋友已经在生产环境中使用,我们这边也陆续的收到了很多用户的测试和使用反馈。非常感谢各位小伙伴和早期用户的厚爱,而且看了这么多场景后,也总结出了一些 TiDB 的使用实践 (其实 Spanner 的最佳实践大部分在 TiDB 中也是适用的,MySQL 最佳实践也是),也是借着 Google Cloud Spanner 发布的东风,看了一下 Spanner 官方的一些最佳实践文档,写篇文章讲讲 TiDB 以及分布式关系型数据库的一些正确的使用姿势,当然,时代也在一直发展,TiDB 也在不停的进化,这篇文章基本上只代表近期的一些观察。

首先谈谈 Schema 设计的一些比较好的经验。由于 TiDB 是一个分布式的数据库,可能在表结构设计的时候需要考虑的事情和传统的单机数据库不太一样,需要开发者能够带着「这个表的数据会分散在不同的机器上」这个前提,才能做更好的设计。

和 Spanner 一样,TiDB 中的一张表的行(Rows)是按照主键的字节序排序的(整数类型的主键我们会使用特定的编码使其字节序和按大小排序一致),即使在 CREATE TABLE 语句中不显式的创建主键,TiDB 也会分配一个隐式的。
有四点需要记住:

  1. 按照字节序的顺序扫描的效率是比较高的;
  2. 连续的行大概率会存储在同一台机器的邻近位置,每次批量的读取和写入的效率会高;
  3. 索引是有序的(主键也是一种索引),一行的每一列的索引都会占用一个 KV Pair,比如,某个表除了主键有 3 个索引,那么在这个表中插入一行,对应在底层存储就是 4 个 KV Pairs 的写入:数据行以及 3 个索引行。
  4. 一行的数据都是存在一个 KV Pair 中,不会被切分,这点和类 BigTable 的列式存储很不一样。

表的数据在 TiDB 内部会被底层存储 TiKV 切分成很多 64M 的 Region(对应 Spanner 的 Splits 的概念),每个 Region 里面存储的都是连续的行,Region 是 TiDB 进行数据调度的单位,随着一个 Region 的数据量越来越大和时间的推移,Region 会分裂/合并,或者移动到集群中不同的物理机上,使得整个集群能够水平扩展。

建议:

  1. 尽可能批量写入,但是一次写入总大小不要超过 Region 的分裂阈值(64M),另外 TiDB 也对单个事务有大小的限制。
  2. 存储超宽表是比较不合适的,特别是一行的列非常多,同时不是太稀疏,一个经验是最好单行的总数据大小不要超过 64K,越小越好。大的数据最好拆到多张表中。
  3. 对于高并发且访问频繁的数据,尽可能一次访问只命中一个 Region,这个也很好理解,比如一个模糊查询或者一个没有索引的表扫描操作,可能会发生在多个物理节点上,一来会有更大的网络开销,二来访问的 Region 越多,遇到 stale region 然后重试的概率也越大(可以理解为 TiDB 会经常做 Region 的移动,客户端的路由信息可能更新不那么及时),这些可能会影响 .99 延迟;另一方面,小事务(在一个 Region 的范围内)的写入的延迟会更低,TiDB 针对同一个 Region 内的跨行事务是有优化的。另外 TiDB 对通过主键精准的点查询(结果集只有一条)效率更高。

关于索引

除了使用主键查询外,TiDB 允许用户创建二级索引以加速访问,就像上面提到过的,在 TiKV 的层面,TiDB 这边的表里面的行数据和索引的数据看起来都是 TiKV 中的 KV Pair,所以很多适用于表数据的原则也适用于索引。和 Spanner 有点不一样的是,TiDB 只支持全局索引,也就是 Spanner 中默认的 Non-interleaved indexes。全局索引的好处是对使用者没有限制,可以 scale 到任意大小,不过这意味着,索引信息不一定和实际的数据在一个 Region 内。

建议:

对于大海捞针式的查询来说 (海量数据中精准定位某条或者某几条),务必通过索引。

当然也不要盲目的创建索引,创建太多索引会影响写入的性能。

反模式 (最好别这么干!)

其实 Spanner 的白皮书已经写得很清楚了,我再赘述一下:

第一种,过度依赖单调递增的主键,AUTO INCREMENT ID
在传统的关系型数据库中,开发者经常会依赖自增 ID 来作为 PRIMARY KEY,但是其实大多数场景大家想要的只是一个不重复的 ID 而已,至于是不是自增其实无所谓,但是这个对于分布式数据库来说是不推荐的,随着插入的压力增大,会在这张表的尾部 Region 形成热点,而且这个热点并没有办法分散到多台机器。TiDB 在 GA 的版本中会对非自增 ID 主键进行优化,让 insert workload 尽可能分散。

建议:

如果业务没有必要使用单调递增 ID 作为主键,就别用,使用真正有意义的列作为主键(一般来说,例如:邮箱、用户名等)

使用随机的 UUID 或者对单调递增的 ID 进行 bit-reverse (位反转)

第二种,单调递增的索引 (比如时间戳)

很多日志类型的业务,因为经常需要按照时间的维度查询,所以很自然需要对 timestamp 创建索引,但是这类索引的问题本质上和单调递增主键是一样的,因为在 TiDB 的内部实现里,索引也是一堆连续的 KV Pairs,不断的插入单调递增的时间戳会造成索引尾部的 Region 形成热点,导致写入的吞吐受到影响。

建议:

因为不可避免的,很多用户在使用 TiDB 存储日志,毕竟 TiDB 的弹性伸缩能力和 MySQL 兼容的查询特性是很适合这类业务的。另一方面,如果发现写入的压力实在扛不住,但是又非常想用 TiDB 来存储这种类型的数据,可以像 Spanner 建议的那样做 Application 层面的 Sharding,以存储日志为例,原来的可能在 TiDB 上创建一个 log 表,更好的模式是可以创建多个 log 表,如:log_1, log_2 … log_N,然后业务层插入的时候根据时间戳进行 hash ,随机分配到 1…N 这几个分片表中的一个。

相应的,查询的时候需要将查询请求分发到各个分片上,最后在业务层汇总结果。

查询优化

TiDB 的优化分为基于规则的优化(Rule Based Optimization)和基于代价的优化(Cost Based Optimization), 本质上 TiDB 的 SQL 引擎更像是一个分布式计算框架,对于大表的数据因为本身 TiDB 会将数据分散到多个存储节点上,能将查询逻辑下推,会大大的提升查询的效率。

TiDB 基于规则的优化有:

谓词下推

谓词下推会将 where/on/having 条件推到离数据表尽可能近的地方,比如:

select * from t join s on t.id = s.id where t.c1 < 10

可以被 TiDB 自动改写成

select * from (select * from t where t.c1 < 10) as t join s on t.id = s.id

关联子查询消除

关联子查询可能被 TiDB 改写成 Join,例如:

select * from t where t.id in (select id from s where s.c1 < 10 and s.name = t.name)

可以被改写成:

select * from t semi join s on t.id = s.id and s.name = t.name and s.c1 < 10

聚合下推
聚合函数可以被推过 Join,所以类似带等值连接的 Join 的效率会比较高,例如:

select count(s.id) from t join s on t.id = s.t_id

可以被改写成:

select sum(agg0) from t join (select count(id) as agg0, t_id from s group by t_id) as s on t.id = s.t_id

基于规则的优化有时可以组合以产生意想不到的效果,例如:

select s.c2 from s where 0 = (select count(id) from t where t.s_id = s.id)

在TiDB中,这个语句会先通过关联子查询消除的优化,变成:

select s.c2 from s left outer join t on t.s_id = s.id group by s.id where 0 = count(t.id)

然后这个语句会通过聚合下推的优化,变成:

select s.c2 from s left outer join (select count(t.id) as agg0 from t group by t.s_id) t on t.s_id = s.id group by s.id where 0 = sum(agg0)

再经过聚合消除的判断,语句可以优化成:

select s.c2 from s left outer join (select count(t.id) as agg0 from t group by t.s_id) t on t.s_id = s.id where 0 = agg0

基于代价的优化有:

读取表时,如果有多条索引可以选择,我们可以通过统计信息选择最优的索引。例如:

select * from t where age = 30 and name in ( ‘小明’, ‘小强’)

对于包含 Join 的操作,我们可以区分大小表,TiDB 的对于一个大表和一个小表的 Join 会有特殊的优化。

例如

select * from t join s on s.id = t.id

优化器会通过对表大小的估计来选择 Join 的算法:即选择把较小的表装入内存中。
对于多种方案,利用动态规划算法选择最优者,例如:

(select * from t where c1 < 10) union all (select * from s where c2 < 10) order by c3 limit 10

t 和 s 可以根据索引的数据分布来确定选择索引 c3 还是 c2。

总之正确使用 TiDB 的姿势,或者说 TiDB 的典型的应用场景是:

大数据量下,MySQL 复杂查询很慢;

大数据量下,数据增长很快,接近单机处理的极限,不想分库分表或者使用数据库中间件等对业务侵入性较大,架构反过来约束业务的 Sharding 方案;

大数据量下,有高并发实时写入、实时查询、实时统计分析的需求;

有分布式事务、多数据中心的数据 100% 强一致性、auto-failover 的高可用的需求。

如果整篇文章你只想记住一句话,那就是数据条数少于 5000w 的场景下通常用不到 TiDB,TiDB 是为大规模的数据场景设计的。如果还想记住一句话,那就是单机 MySQL 能满足的场景也用不到 TiDB。

HBase :

HBase是一个分布式数据库

HBase是一种构建在HDFS之上的分布式、面向列的存储系统。在需要实时读写、随机访问超大规模数据集时,可以使用HBase。

尽管已经有许多数据存储和访问的策略和实现方法,但事实上大多数解决方案,特别是一些关系类型的,在构建时并没有考虑超大规模和分布式的特点。许多商家通过复制和分区的方法来扩充数据库使其突破单个节点的界限,但这些功能通常都是事后增加的,安装和维护都和复杂。同时,也会影响RDBMS的特定功能,例如联接、复杂的查询、触发器、视图和外键约束这些操作在大型的RDBMS上的代价相当高,甚至根本无法实现。

HBase从另一个角度处理伸缩性问题。它通过线性方式从下到上增加节点来进行扩展。HBase不是关系型数据库,也不支持SQL,但是它有自己的特长,这是RDBMS不能处理的,HBase巧妙地将大而稀疏的表放在商用的服务器集群上。

HBase 是Google Bigtable 的开源实现,与Google Bigtable 利用GFS作为其文件存储系统类似, HBase 利用Hadoop HDFS 作为其文件存储系统;Google 运行MapReduce 来处理Bigtable中的海量数据, HBase 同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable 利用Chubby作为协同服务, HBase 利用Zookeeper作为对应。

HBase 简介:

官网:http://hbase.apache.org/

HBase 是 BigTable 的开源(源码使用 Java 编写)版本。是 Apache Hadoop 的数据库,是建立在 HDFS 之上,被设计用来提供高可靠性、高性能、列存储、可伸缩、多版本的 NoSQL的分布式数据存储系统,实现对大型数据的实时、随机的读写访问。

HBase 依赖于 HDFS 做底层的数据存储,BigTable 依赖 Google GFS 做数据存储
HBase 依赖于 MapReduce 做数据计算,BigTable 依赖 Google MapReduce 做数据计算
HBase 依赖于 ZooKeeper 做服务协调,BigTable 依赖 Google Chubby 做服务协调

与 Hadoop 一样,HBase 目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。所以,HBase 是一个通过大量廉价机器解决海量数据的高速存储和读取的分布式数据库解决方案

NoSQL = NO SQL
NoSQL = Not Only SQL:会有一些把 NoSQL 数据的原生查询语句封装成 SQL,比如 HBase 就有 Phoenix 工具

关系型数据库 和 非关系型数据库的典型代表:
NoSQL:HBase, Redis, MongoDB
RDBMS:MySQL, Oracle, SQL Server, DB2

**适合场景 **:单表超千万,上亿,且高并发。

不适合场景:主要需求是数据分析,比如做报表。数据量规模不大,对实时性要求高。

HBase的发展史:

2006年底由PowerSet 的Chad Walters和Jim Kellerman 发起,2008年成为Apache Hadoop的一个子项目。现已作为产品在多家企业被使用,如:

  1. WorldLingo
  2. Streamy.com
  3. OpenPlaces
  4. Yahoo!
  5. Adobe
  6. 淘宝
  7. Facebook
  8. Twitter
  9. Trend Micro

HBase特点:

  1. 大:一个表可以有上亿行,上百万列。
  2. 面向列:面向列表(簇)的存储和权限控制,列(簇)独立检索。
  3. 稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
  4. 无模式:每一行都有一个可以排序的主键和任意多的列,列可以根据需要动态增加,同一张表中不同的行可以有截然不同的列。
  5. 数据多版本:每个单元中的数据可以有多个版本,默认情况下,版本号自动分配,版本号就是单元格插入时的时间戳。
  6. 数据类型单一:HBase中的数据都是字符串,没有类型。

海量存储:HBase适合存储PB级别的海量数据,在PB级别的数据以及采用廉价PC存储的情况下,能在几十到百毫秒内返回数据。这与HBase的极易扩展性息息相关。正式因为HBase良好的扩展性,才为海量数据的存储提供了便利。

列式存储:这里的列式存储其实说的是列族存储,HBase是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。

极易扩展:HBase的扩展性主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)。通过横向添加RegionSever的机器,进行水平扩展,提升HBase上层的处理能力,提升Hbsae服务更多Region的能力。

高并发:由于目前大部分使用HBase的架构,都是采用的廉价PC,因此单个IO的延迟其实并不小,一般在几十到上百ms之间。这里说的高并发,主要是在并发的情况下,HBase的单个IO延迟下降并不多。能获得高并发、低延迟的服务。

稀疏:稀疏主要是针对HBase列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。

结构化:数据结构字段含义确定,清晰,典型的如数据库中的表结构
半结构化:具有一定结构,但语义不够确定,典型的如 HTML 网页,有些字段是确定的(title),有些不确定(table)
非结构化:杂乱无章的数据,很难按照一个概念去进行抽取,无规律性

HBase 中的表特点:

1、大:一个表可以有上十亿行,上百万列
2、面向列:列可以灵活指定,面向列(族)的存储和权限控制,列(簇)独立检索。
3、稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
4、无严格模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列

关于存储系统的模式介绍:

读模式:在读取数据的时候做模式校验,比如数据仓库 Hive
写模式:在写入数据进入存储系统的时候做模式校验,比如 RDBMS

1.3、表结构逻辑视图
1、RDBMS 完全可以抽象成是一张二维表格,表由行和列组成。由行和列确定一个唯一的值
2、HBase 本质是 key-value 数据库,key 是行健 rowkey,value 是所有真实 key-value 的集合
3、HBase 可以抽象成为一张四维表格,四维分别由行健RowKey,列簇Column Family,列Column和时间戳Timestamp 组成。
4、其中,一张 HBase 的所有列划分为若干个列簇 (Column Family)

img

1.3.1、行键(RowKey)

与 NoSQL 数据库们一样,RowKey 是用来检索记录的主键。访问 HBase Table 中的行,只有三种方式:
1、通过单个 row key 访问
2、通过 row key 的 range
3、全表扫描
RowKey 行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),最好是 16。在 HBase 内部,RowKey 保存为字节数组。HBase 会对表中的数据按照 rowkey 排序(字典顺序)

存储时,数据按照 RowKey 的字典序(byte order)排序存储。设计 Key 时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

注意:
字典序对 int 排序的结果是
1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。
要保持整形的自然序,行键必须用 0 作左填充。
行的一次读写是原子操作(不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

1.3.2、列簇(Column Family)
HBase 表中的每个列,都归属与某个列簇。列簇是表的 Schema 的一部分(而列不是),必须在创建表的时候指定。指定好了列簇就不能更改。列簇可以增加或者删除,删除的时候会删除这个列簇中的所有数据。

列名都以列簇作为前缀。例如 courses:history,courses:math 都属于 courses 这个列簇。访问控制、磁盘和内存的使用统计等都是在列簇层面进行的。

列簇越多,在取一行数据时所要参与 IO、搜寻的文件就越多,所以,如果没有必要,不要设置太多的列簇,官网推荐是小于等于 3(最好就一个列簇)。

1.3.3、时间戳(TimeStamp)
HBase 中通过 RowKey 和 Column 确定的为一个存储单元称为 Cell。每个 Cell 都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64 位整型。时间戳可以由 HBase (在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个Cell 中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。HBase 在查询的时候,默认返回最新版本/最近的数据。如果需要读取旧版本的数据,可以指定时间戳。

为了避免数据存在过多版本造成的的管理(包括存储和索引)负担,HBase 提供了两种数据版本回收方式:
保存数据的最后 n 个版本
保存最近一段时间内的版本(设置数据的生命周期 TTL)。
用户可以针对每个列簇进行设置。

1.3.4、单元格(Cell)
由{RowKey, Column( = + ), Version} 唯一确定的单元。Cell 中的数据是没有类型的,全部是字节码形式存储。

1.4、HBase应用场景

1、半结构化或非结构化数据
对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用 HBase。而且 HBase 是面向列的,HBase 支持动态增加字段

2、记录非常稀疏
RDBMS 的行有多少列是固定的,为 null 的列浪费了存储空间。而 HBase 为 null 的 Column是不会被存储的,这样既节省了空间又提高了读性能。

3、多版本数据
对于需要存储变动历史记录的数据,使用 HBase 就再合适不过了。HBase 根据 Row key 和Column key 定位到的 Value 可以有任意数量的版本值。

4、超大数据量的随机、实时读写
当数据量越来越大,RDBMS 数据库撑不住了,就出现了读写分离策略,通过一个 Master 专门负责写操作,多个 Slave 负责读操作,服务器成本倍增。随着压力增加,Master 撑不住了,这时就要分库了,把关联不大的数据分开部署,一些 join 查询不能用了,需要借助中间层。随着数据量的进一步增加,一个表的记录越来越大,查询就变得很慢,于是又得搞分表,比如按 ID 取模分成多个表以减少单个表的记录数。经历过这些事的人都知道过程是多么的折腾。采用 HBase 就简单了,只需要加机器即可,HBase 会自动水平切分扩展,跟 Hadoop 的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性(MapReduce)。

5、查询简单
不涉及到复杂的 Join 查询,基于 RowKey 或者 RowKey 的范围查询

HBase优缺点:

HBase的优点:
  • HDFS有高容错,高扩展的特点,而Hbase基于HDFS实现数据的存储,因此Hbase拥有与生俱来的超强的扩展性和吞吐量。
  • HBase采用的是Key/Value的存储方式,这意味着,即便面临海量数据的增长,也几乎不会导致查询性能下降。

HBase是一个列式数据库,相对于于传统的行式数据库而言。当你的单张表字段很多的时候,可以将相同的列(以regin为单位)存在到不同的服务实例上,分散负载压力。

HBase的缺点:
  • 架构设计复杂,且使用HDFS作为分布式存储,因此只是存储少量数据,它也不会很快。在大数据量时,它慢的不会很明显。
  • Hbase不支持表的关联操作,因此数据分析是HBase的弱项。常见的 group by或order by只能通过编写MapReduce来实现。
  • Hbase部分支持了ACID

HBase产生背景:

自 1970 年以来,关系数据库用于数据存储和维护有关问题的解决方案。大数据的出现后,好多公司实现处理大数据并从中受益,并开始选择像 Hadoop 的解决方案。Hadoop 使用分布式文件系统,用于存储大数据,并使用 MapReduce 来处理。Hadoop 擅长于存储各种格式的庞大的数据,任意的格式甚至非结构化的处理。

Hadoop 的限制

Hadoop 只能执行批量处理,并且只以顺序方式访问数据。这意味着必须搜索整个数据集,即使是最简单的搜索工作。当处理结果在另一个庞大的数据集,也是按顺序处理一个巨大的数据集。在这一点上,一个新的解决方案,需要访问数据中的任何点(随机访问)单元。

Hadoop随机存取数据库

应用程序,如 HBase,Cassandra,CouchDB,Dynamo 和 MongoDB 都是一些存储大量数据和以随机方式访问数据的数据库。

Hadoop 的特点:

对于任意格式的庞大数据集,Hadoop 可以做到安全存储但是对于需要在庞大数据集做针对于单条记录的增删改查是做不到的。

Hive 的特点:

对于存储在 HDFS 上的结构化的数据,如果增加一些描述这些数据的元数据信息,那么我们可以把存储在 HDFS 上的数据抽象成一张二维表格,使用 Hive 进行各种 Insert/Select 操作。但是 Hive 还是天生不支持对于单条记录的增删改查,也不是设计用来做单条记录的增删改查的。

总结:

1)海量数据量存储成为瓶颈,单台机器无法负载大量数据
2)单台机器 IO 读写请求成为海量数据存储时候高并发大规模请求的瓶颈
3)随着数据规模越来越大,大量业务场景开始考虑数据存储横向水平扩展,使得存储服务可以增加/删除,而目前的关系型数据库更专注于一台机器。

重要问题探讨:如何设计一个能存储庞大数据集,同时也能做到实时增删改查的数据库系统?相当于设计一个 MySQL 的分布式版本。
先解决这个问题,再来学习别人的成果,这是一种较好的学习方式。而不是一上来便学习别人的成功。须知:已掌握的知识会限制我们的思维方式。

类似问题:

1、如何设计一个分布式文件系统?
2、如何设计一个分布式计算框架?
3、如何设计一个分布式数据库?
他们的难点是什么?

思路引爆点:

1、怎样快速判断一个元素在不在一个数据集中?布隆过滤器
2、是否能找到一种合适的数据结构和搜索算法能快速从一个数据集中找出一个元素?二分查找
3、如何设计分布式系统?网络编程模型(NIO RPC Netty)
4、是否可以提前过滤不参与查询的数据,提高查询效率?列裁剪

HBase集群:

img img

Region:是 HBase 将一个表中的所有数据按照 RowKey 的不同范围进行切割的逻辑单元,每个 Region 负责一定范围数据的读写访问。Region 由 RegionServer 负责管理。HBase 中的 Region的概念就和 HDFS 中的数据块的概念差不多,Region 是 HBase 表切分出来的一个分片。数据块是 HDFS 中的一个大文件切分出来的一个分片。

HMaster:HBase 的主节点,负责整个集群的状态感知、负载分配、负责用户表的元数据(schema)管理(可以配置多个用来实现 HA),HMaster 负载压力相对于 HDFS 的 NameNode会小很多。HBase 的 HMaster 其实就算是宕机一段时间也可以正常对外提供服务的(要搞清楚为什么)。

RegionServer:HBase 中真正负责管理 Region 的服务器,也就是负责为客户端进行表数据读写的服务器。每一台 RegionServer 会管理很多的 Region,一个 RegionServer 上面管理的所有的region不属于同一张表。负责Region的拆分,负责和底层的HDFS的存储交互,负责StoreFile的合并。

ZooKeeper:整个 HBase 中的主从节点协调,元数据的入口,主节点之间的选举,集群节点之间的上下线感知……都是通过 ZooKeeper 来实现

HDFS:用来存储 HBase 的系统文件,或者表的 Region 文件

Client:Client 包含了访问 HBase 的接口,另外 Client 还维护了对应的 Cache 来加速 HBase 的访问,比如 Cache 的.META.元数据的信息。

HBase集群搭建:

4.1、安装步骤

1、 安装 zookeeper 集群,此处略

2、 找到官网下载 hbase 安装包 hbase-1.2.6-bin.tar.gz,这里给大家提供一个下载地址:
http://mirrors.hust.edu.cn/apache/hbase/
对应版本的官方文档:http://hbase.apache.org/1.2/book.html

3、 上传安装包到服务器,并解压到对应的安装目录
[hadoop@hadoop02 apps]# tar -zxvf hbase-1.2.6-bin.tar.gz -C /home/hadoop/apps/

4、 修改配置文件

<!--1、修改运行环境配置环境-->
[hadoop@hadoop02 conf]# vi hbase-env.sh
修改两个两地方:
export JAVA_HOME=/usr/local/java/jdk1.8.0_73,表示修改为自己的 jdk 目录
export HBASE_MANAGES_ZK=false,表示不引用 hbase 自带的 zookeeper,用我们自己
安装的
保存退出

 

<!--2、修改集群配置文件:hbase-site.xml-->
增加以下配置:
<configuration>
 <property>
<!-- 指定 hbase 在 HDFS 上存储的路径 -->
 <name>hbase.rootdir</name>
 <value>hdfs://myha01/hbase</value>
 </property>
 <property>
<!-- 指定 hbase 是分布式的 -->
 <name>hbase.cluster.distributed</name>
 <value>true</value>
 </property>
 <property>
<!-- 指定 zk 的地址,多个用“,”分割 -->
 <name>hbase.zookeeper.quorum</name>
 <value>hadoop03:2181,hadoop04:2181,hadoop05:2181</value>
 </property>
</configuration>
保存退出

 

<!--3、修改 regionservers-->
vi regionservers

hadoop02
hadoop03
hadoop04
hadoop05

<!--4、修改 backup-masters(自行创建),指定备用的主节点-->
该文件是不存在的,先自行创建:vi backup-masters

hadoop05
<!--5、拷贝 hadoop 的核心配置文件过来-->
最重要一步,要把 hadoop 的 hdfs-site.xml 和 core-site.xml 放到 hbase-1.2.6/conf 下
cp ~/apps/hadoop-2.7.5/etc/hadoop/core-site.xml ~/apps/hbase-1.2.6/conf/
cp ~/apps/hadoop-2.7.5/etc/hadoop/hdfs-site.xml ~/apps/hbase-1.2.6/conf/

5、 分发安装到各节点
scp -r hbase-1.2.6 hadoop03:/home/hadoop/apps/
scp -r hbase-1.2.6 hadoop04:/home/hadoop/apps/
scp -r hbase-1.2.6 hadoop05:/home/hadoop/apps/

6、 别忘了同步时间!!!!!!!!
HBase 集群对于时间的同步要求的比 HDFS 严格,所以,集群启动之前千万记住要进行时间同步,要求相差不要超过 30s

7、 配置环境变量
vi ~/.bashrc
添加两行:
export HBASE_HOME=/home/hadoop/apps/hbase-1.2.6
export PATH= P A T H : PATH: PATH:HBASE_HOME/bin
保存退出!!!别忘了执行 source ~/.bashrc,使配置生效

8、 启动(顺序别搞错了)

1、 先启动 zookeeper 集群
zkServer.sh start

2、 启动 hdfs 集群
start-dfs.sh

3、 启动 hbase
保证 ZooKeeper 集群和 HDFS 集群启动正常的情况下启动 HBase 集群
启动命令:start-hbase.sh

img

观看启动日志可以看到:
1、首先在命令执行节点启动 master
2、然后分别在 hadoop02,hadoop03,hadoop04,hadoop05 启动 regionserver
3、然后在 backup-masters 文件中配置的备节点上再启动了一个 master 主进程

9、 查看启动是否正常,是否成功

img

按照对应的配置信息各个节点应该要启动的进程如上图所示

2、 通过访问浏览器页面,格式为”主节点:16010”
http://hadoop02:16010/

10、如果有节点相应的进程没有启动,那么可以手动启动
hbase-daemon.sh start master
hbase-daemon.sh start regionserver

HBase安装:

3.1 准备工作

  • 安装部署Zookeeper并且启动
  • 安装部署Hadoop并且启动
  • 将HBase压缩包解压到指定目录下

3.2 HBase配置

  • 修改conf目录下的hbase-env.sh文件
export JAVA_HOME=jdk安装目录
export HBASE_MANAGES_ZK=false
  • 修改hbase-site.xml
<configuration>
	<property>
		<name>hbase.rootdir</name>
		<value>hdfs://master:9000/HBase</value>
	</property>
 
	<property>
		<name>hbase.cluster.distributed</name>
		<value>true</value>
	</property>
 
   <!-- 0.98后的新变动,之前版本没有.port,默认端口为60000 -->
	<property>
		<name>hbase.master.port</name>
		<value>16000</value>
	</property>
 
    <!-- zookeeper主机 -->
	<property>
		<name>hbase.zookeeper.quorum</name>
	     <value>master:2181,slave01:2181,slave02:2181,slave03:2181</value>
	</property>
 
    <!-- zookeeper的数据存储目录 -->
	<property>
		<name>hbase.zookeeper.property.dataDir</name>
	     <value>/opt/software/apache-zookeeper-3.5.7-bin/data</value>
	</property>
</configuration>
  • 配置regionservers用于群起hbase集群
有几个主机写几个一个一行
  • 将配置好的hbase分发到每个节点

HBase的基本使用:

4.1 启动

  • 启动master
hbase-daemon.sh start master
  • 启动regionserver(单点启动,每个节点都要输入一次)
hbase-daemon.sh start regionserver
  • 停止regionserver(每个节点都要执行一次)
hbase-daemon.sh stop regionserver
  • 停止master
hbase-daemon.sh stop master
  • 群起regionserver,需要配置regionservers文件
hbase-daemons.sh start regionserver
  • 群停regionserver,需要配置regionservers文件
hbase-daemons.sh stop regionserver
  • 群起hbase,需要配置regionservers文件
start-hbase.sh
  • 群停hbase,需要配置regionservers文件
stop-hbase.sh

4.2 查看HBase页面

  • 启动成功后,可以通过“host:port”的方式来访问HBase管理页面,http://host:16010

4.3 HBase Shell操作

  • 进入shell界面
hbase shell
  • 查看集群状态
status
  • 查看版本
version
  • 查看操作用户及组信息
whoami
  • 查看表操作信息
table help
  • 查看具体命令的帮助
help ‘命令名’
  • HBase的其他操作可以通过help命令来查看,需要注意的是删除表之前需要先禁用表
hbase(main):016:0> help
HBase Shell, version 1.3.1, r930b9a55528fe45d8edce7af42fef2d35e77677a, Thu Apr  6 19:36:54 PDT 2017
Type 'help "COMMAND"', (e.g. 'help "get"' -- the quotes are necessary) for help on a specific command.
Commands are grouped. Type 'help "COMMAND_GROUP"', (e.g. 'help "general"') for help on a command group.
 
COMMAND GROUPS:
  Group name: general
  Commands: status, table_help, version, whoami
 
  Group name: ddl
  Commands: alter, alter_async, alter_status, create, describe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, locate_region, show_filters
 
  Group name: namespace
  Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables
 
  Group name: dml
  Commands: append, count, delete, deleteall, get, get_counter, get_splits, incr, put, scan, truncate, truncate_preserve
 
  Group name: tools
  Commands: assign, balance_switch, balancer, balancer_enabled, catalogjanitor_enabled, catalogjanitor_run, catalogjanitor_switch, close_region, compact, compact_rs, flush, major_compact, merge_region, move, normalize, normalizer_enabled, normalizer_switch, split, splitormerge_enabled, splitormerge_switch, trace, unassign, wal_roll, zk_dump
 
  Group name: replication
  Commands: add_peer, append_peer_tableCFs, disable_peer, disable_table_replication, enable_peer, enable_table_replication, get_peer_config, list_peer_configs, list_peers, list_replicated_tables, remove_peer, remove_peer_tableCFs, set_peer_tableCFs, show_peer_tableCFs
 
  Group name: snapshots
  Commands: clone_snapshot, delete_all_snapshot, delete_snapshot, delete_table_snapshots, list_snapshots, list_table_snapshots, restore_snapshot, snapshot
 
  Group name: configuration
  Commands: update_all_config, update_config
 
  Group name: quotas
  Commands: list_quotas, set_quota
 
  Group name: security
  Commands: grant, list_security_capabilities, revoke, user_permission
 
  Group name: procedures
  Commands: abort_procedure, list_procedures
 
  Group name: visibility labels
  Commands: add_labels, clear_auths, get_auths, list_labels, set_auths, set_visibility
 
SHELL USAGE:
Quote all names in HBase Shell such as table and column names.  Commas delimit
command parameters.  Type <RETURN> after entering a command to run it.
Dictionaries of configuration used in the creation and alteration of tables are
Ruby Hashes. They look like this:
 
  {'key1' => 'value1', 'key2' => 'value2', ...}
 
and are opened and closed with curley-braces.  Key/values are delimited by the
'=>' character combination.  Usually keys are predefined constants such as
NAME, VERSIONS, COMPRESSION, etc.  Constants do not need to be quoted.  Type
'Object.constants' to see a (messy) list of all constants in the environment.
 
If you are using binary keys or values and need to enter them in the shell, use
double-quote'd hexadecimal representation. For example:
 
  hbase> get 't1', "key\x03\x3f\xcd"
  hbase> get 't1', "key\003\023\011"
  hbase> put 't1', "test\xef\xff", 'f1:', "\x01\x33\x40"
 
The HBase shell is the (J)Ruby IRB with the above HBase-specific commands added.
For more on the HBase Shell, see http://hbase.apache.org/book.html

HBase命令行演示:

5.1、HBase命令使用初准备

概述:大数据生态里的各种软件,基本都会给出 shell 命令行操作。所以在拿到新上手的软件的时候先找到怎么进入命令行,然后相应要想到 help 命令,查看命令帮助。别着急一股脑儿扎进去敲各种命令,这就是思路,思路很重要。

下面按照我的思路来进行练习:

1、先进入 hbase shell 命令行
在你安装的随意台服务器节点上,执行命令:hbase shell,会进入到你的 hbase shell 客户端
[root@hadoop01 ~]# hbase shell

img

2、进入之后先别着急,先看一下提示。其实是不是有一句很重要的话:
HBase Shell; enter ‘help<RETURN>’ for list of supported commands.
Type “exit<RETURN>” to leave the HBase Shell
意在告诉怎么获得帮助,怎么退出客户端
help 获取帮助
help 获取所有命令提示
help “dml” 获取一组命令的提示
help “put” 获取一个单独命令的提示帮助
exit 退出 hbase shell 客户端
重点关注类操作:DDL, DML

3、语法规则

名称命令表达式
创建表create ‘表名称’,‘列名称1’,‘列名称2’,‘列名称N’
添加记录put ‘表名称’,‘行名称’,‘列名称:’,‘值’
查看记录get ‘表名称’,‘行名称’
查看表中的记录总数count ‘表名称’
删除记录delete ‘表名’,‘行名称’,‘列名称’
删除一张表先要屏蔽该表,才能对表进行删除操作,第一步disable ‘表名称’ 第二步 drop ‘表名称’
查看所有记录scan ‘表名称’
查看某个表某个列中所有数据scan ‘表名称’,[‘列名称:’]
更新记录就是重写一遍进行覆盖

5.2、HBase命令使用实战

1、显示 hbase 中的表列表:list

2、创建表

创建一张 hbase 表,表名叫做 user,该表有 info 和 data 两个列簇,注意,创建表的时候不 用指定列的信息,插入数据的时候才需要指定 key-value 的信息,这个 key 就是列
create 'user', 'info', 'data'
也可以这样写:
create 'user',{NAME=>'info'},{NAME=>'data'}
创建一张表叫做 user_info,包含两个列簇 base_info 和 extra_info,并且分别指定这两个列簇 的数据的版本数为 31
create 'user_info',{NAME=>'base_info',VERSIONS=>3 },{NAME=>'extra_info',VERSIONS=>1}
create 'user',{NAME=>'info',VERSIONS=>3 },{NAME=>'data',VERSIONS=>2}

3、查看表的详细信息:desc 和 describe

desc “user_info” 或者 describe “user_info”

4、往表中插入数据:put

user 表中插入信息,row key 为 rk0001,列簇 info 中添加 name 列标示符,值为 zhangsan
put 'user', 'rk0001', 'info:name', 'zhangsan'user 表中插入信息,row key 为 rk0001,列簇 info 中添加 gender 列标示符,值为 female
put 'user', 'rk0001', 'info:gender', 'female'user 表中插入信息,row key 为 rk0001,列簇 info 中添加 age 列标示符,值为 20
put 'user', 'rk0001', 'info:age', 20user 表中插入信息,row key 为 rk0001,列簇 data 中添加 pic 列标示符,值为 picture
put 'user', 'rk0001', 'data:pic', 'picture'

再插入几条其他数据:
put 'user', 'rk0002', 'info:name', 'fanbingbing'
put 'user', 'rk0002', 'info:gender', 'female'
put 'user', 'rk0002', 'info:nationality', '中国'

插入一堆示例数据,以便后面测试:
put 'user_info', 'user0000', 'base_info:name', 'luoyufeng'
put 'user_info', 'user0000', 'base_info:age', '18'
put 'user_info', 'user0000', 'base_info:gender', 'female'
put 'user_info', 'user0000', 'extra_info:size', '34'
put 'user_info', 'user0001', 'base_info:name', 'zhangsan'
put 'user_info', 'user0001', 'base_info:name', 'luoyufeng'
put 'user_info', 'user0001', 'base_info:name', 'zhangsan'
put 'user_info', 'zhangsan_20150701_0001', 'base_info:name', 'zhangsan1'
put 'user_info', 'zhangsan_20150701_0002', 'base_info:name', 'zhangsan2'
put 'user_info', 'zhangsan_20150701_0003', 'base_info:name', 'zhangsan3'
put 'user_info', 'zhangsan_20150701_0004', 'base_info:name', 'zhangsan4'
put 'user_info', 'zhangsan_20150701_0005', 'base_info:name', 'zhangsan5'
put 'user_info', 'zhangsan_20150701_0006', 'base_info:name', 'zhangsan6'
put 'user_info', 'zhangsan_20150701_0007', 'base_info:name', 'zhangsan7'
put 'user_info', 'zhangsan_20150701_0008', 'base_info:name', 'zhangsan8'
put 'user_info', 'zhangsan_20150701_0001', 'base_info:age', '21'
put 'user_info', 'zhangsan_20150701_0002', 'base_info:age', '22'
put 'user_info', 'zhangsan_20150701_0003', 'base_info:age', '23'
put 'user_info', 'zhangsan_20150701_0004', 'base_info:age', '24'
put 'user_info', 'zhangsan_20150701_0005', 'base_info:age', '25'
put 'user_info', 'zhangsan_20150701_0006', 'base_info:age', '26'
put 'user_info', 'zhangsan_20150701_0007', 'base_info:age', '27'
put 'user_info', 'zhangsan_20150701_0008', 'base_info:age', '28'
put 'user_info', 'zhangsan_20150701_0001', 'extra_info:Hobbies', 'music'
put 'user_info', 'zhangsan_20150701_0002', 'extra_info:Hobbies', 'sport'
put 'user_info', 'zhangsan_20150701_0003', 'extra_info:Hobbies', 'music'
put 'user_info', 'zhangsan_20150701_0004', 'extra_info:Hobbies', 'sport'
put 'user_info', 'zhangsan_20150701_0005', 'extra_info:Hobbies', 'music'
put 'user_info', 'zhangsan_20150701_0006', 'extra_info:Hobbies', 'sport'
put 'user_info', 'zhangsan_20150701_0007', 'extra_info:Hobbies', 'music'
put 'user_info', 'baiyc_20150716_0001', 'base_info:name', 'baiyc1'
put 'user_info', 'baiyc_20150716_0002', 'base_info:name', 'baiyc2'
put 'user_info', 'baiyc_20150716_0003', 'base_info:name', 'baiyc3'
put 'user_info', 'baiyc_20150716_0004', 'base_info:name', 'baiyc4'
put 'user_info', 'baiyc_20150716_0005', 'base_info:name', 'baiyc5'
put 'user_info', 'baiyc_20150716_0006', 'base_info:name', 'baiyc6'
put 'user_info', 'baiyc_20150716_0007', 'base_info:name', 'baiyc7'
put 'user_info', 'baiyc_20150716_0008', 'base_info:name', 'baiyc8'
put 'user_info', 'baiyc_20150716_0001', 'base_info:age', '21'
put 'user_info', 'baiyc_20150716_0002', 'base_info:age', '22'
put 'user_info', 'baiyc_20150716_0003', 'base_info:age', '23'
put 'user_info', 'baiyc_20150716_0004', 'base_info:age', '24'
put 'user_info', 'baiyc_20150716_0005', 'base_info:age', '25'
put 'user_info', 'baiyc_20150716_0006', 'base_info:age', '26'
put 'user_info', 'baiyc_20150716_0007', 'base_info:age', '27'
put 'user_info', 'baiyc_20150716_0008', 'base_info:age', '28'
put 'user_info', 'baiyc_20150716_0001', 'extra_info:Hobbies', 'music'
put 'user_info', 'baiyc_20150716_0002', 'extra_info:Hobbies', 'sport'
put 'user_info', 'baiyc_20150716_0003', 'extra_info:Hobbies', 'music'
put 'user_info', 'baiyc_20150716_0004', 'extra_info:Hobbies', 'sport'
put 'user_info', 'baiyc_20150716_0005', 'extra_info:Hobbies', 'music'
put 'user_info', 'baiyc_20150716_0006', 'extra_info:Hobbies', 'sport'
put 'user_info', 'baiyc_20150716_0007', 'extra_info:Hobbies', 'music'
put 'user_info', 'baiyc_20150716_0008', 'extra_info:Hobbies', 'sport'

5、查询数据:get

获取 user 表中 row key 为 rk0001 的所有信息
get 'user', 'rk0001'

获取 user 表中 row key 为 rk0001,info 列簇的所有信息
get 'user', 'rk0001', 'info'

获取 user 表中 row key 为 rk0001,info 列簇的 name、age 列标示符的信息
get 'user', 'rk0001', 'info:name', 'info:age'

获取 user 表中 row key 为 rk0001,info 和 data 列簇的信息
get 'user', 'rk0001', 'info', 'data'
get 'user', 'rk0001', {COLUMN => ['info', 'data']}
get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

获取 user 表中 row key 为 rk0001,列簇为 info,版本号最新 5 个的信息
get 'user', 'rk0001', {COLUMN => 'info', VERSIONS => 2}
get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5}
get 'user', 'rk0001', {COLUMN => 'info:name', VERSIONS => 5, TIMERANGE => [1392368783980, 1392380169184]}
get 'user_info', 'user0001', {COLUMN => 'base_info:name', VERSIONS => 3}

获取 user 表中 row key 为 rk0001,cell 的值为 zhangsan 的信息
get 'user', 'rk0001', {FILTER => "ValueFilter(=, 'binary:图片')"}

获取 user 表中 row key 为 rk0001,列标示符中含有 a 的信息
get 'user', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

6、查询数据:scan

查询 user_info 表中的所有信息
scan 'user_info'

查询 user_info 表中的指定列簇的所有信息
scan 'user_info', {COLUMNS => 'base_info'}
scan 'user_info', {COLUMNS => 'base_info:name'}
scan 'user_info', {COLUMNS => ['base_info', 'extra_info']}

查询 user_info 表中的指定列簇为 base_info 的所有版本信息
scan 'user_info', {COLUMNS => 'base_info'} #只查询最新值
scan 'user_info', {COLUMNS => 'base_info', VERSIONS => 5}

查询 user 表中列簇为 info 和 data 的信息
scan 'user', {COLUMNS => ['info', 'data']}
scan 'user', {COLUMNS => 'info:name'}
scan 'user', {COLUMNS => ['info:name', 'data:pic']}

查询 user_info 表中列簇为 base_info、列标示符为 name 的信息,并且版本最新的 5 个
scan 'user_info', {COLUMNS => 'base_info:name', VERSIONS => 5}

查询 user 表中列簇为 info 和 data 且列标示符中含有 a 字符的信息
scan 'user', {COLUMNS => ['info', 'data']}
scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

查询表名为 user_info 表中列簇为 base_info,rowkey 的起始偏移范围是[baiyc_20150716_0003, baiyc_20150716_0006)的数据
scan 'user_info', {COLUMNS => 'base_info', STARTROW => 'baiyc_20150716_0003', ENDROW => 'baiyc_20150716_0006'}

查询 user 表中 rowkey 以 rk 字符开头的
scan 'user', {FILTER=>"PrefixFilter('rk')"}

查询 user_info 表中指定时间戳范围的数据
scan 'user_info', {TIMERANGE => [1540882871681,1540882888540]}

7、删除数据:delete

删除记录
delete 'user', 'rk0001' #不能一口气删除一个 rowkey 所对应的所有 key-value

删除字段
delete 'user', 'rk0001', 'info:name'

删除 user_info 表 rowkey 为 user0000,列标示符为 info:name 的数据
get 'user_info', 'user0000', 'base_info:name'
delete 'user_info', 'user0000', 'base_info:name' #实现删除,其他两句为辅助测试语句

put 'user_info', 'user0000', 'base_info:name', 'luoyufeng'

删除 user 表 rowkey 为 rk0001,列标示符为 info:name,timestamp1392383705316 的数据
delete 'user', 'rk0001', 'info:name', 1392383705316

8、修改表结构:alter

添加两个列簇 f2 和 f3
alter 'user_info', NAME => 'f2'
alter 'user_info', NAME => 'f3'

删除一个列簇 f2:
alter 'user_info', NAME => 'f2', METHOD => 'delete'alter 'user_info', 'delete' => 'f2'

添加列簇 f1 同时删除列簇 f3
alter 'user_info', {NAME => 'f1'}, {NAME => 'f3', METHOD => 'delete'}

将 user_info 表的 base_info 列簇版本号改为 5
alter 'user_info', NAME => 'base_info', VERSIONS => 5

9、修改数据

严格来说,HBase 没有修改数据的显示操作,重复插入就相当于是修改操作

10、清空表:truncate

truncate ‘user’ #清空 user 表中的数据

11、停用表/启用表:disable 和 enable

disable ‘user’ #首先停用 user 表

enable ‘user’ #启用表

12、删除表

disable ‘user’ #删除前,先停用表

drop ‘user’ #停用表之后才能删除

13、过滤器操作

过滤器
get 'user', 'rk0001', {FILTER => "ValueFilter(=, 'binary:中国')"}
get 'user', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}
scan 'user', {COLUMNS => 'info:name'}
scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}
scan 'user', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}
scan 'user', {COLUMNS => 'info', STARTROW => '20140201', ENDROW => '20140301'}
scan 'user', {COLUMNS => 'info:name', TIMERANGE => [1395978233636, 1395987769587]}

HBase使用:

HBase Java API 代码开发:

几个主要HBase API类和数据模型之间的对应关系:

java类HBase数据模型
HBaseAdmin数据库(DataBase)
HBaseConfiguration数据库(DataBase)
HTable表(Table)
HTableDescriptor列簇(Column Family)
HColumnDescriptor列簇(Column Family)
Put列修饰符(Column Qualifier)
Get列修饰符(Column Qualifier)
Delete列修饰符(Column Qualifier)
Result列修饰符(Column Qualifier)
Scan列修饰符(Column Qualifier)
ResultScanner列修饰符(Column Qualifier)

1、HBaseAdmin

关系:org.apache.hadoop.hbase.client.HBaseAdmin
作用:提供了一个接口来管理 HBase 数据库的表信息。它提供的方法包括:创建表,删除表, 列出表项,使表有效或无效,以及添加或删除表列簇成员等。

void addColumn(String tableName, HColumnDescriptor column) #向一个已经存在的表添加列
void checkHBaseAvailable(HBaseConfiguratio n conf) #静态函数,查看 HBase是否处于运行状态
void createTable(HTableDescriptor desc) #创建一个表,同步操作
void deleteTable(byte[] tableName) #删除一个已经存在的表
void enableTable(byte[] tableName) #使表处于有效状态
void disableTable(byte[] tableName) #使表处于无效状态
HTableDescriptor[] listTables() #列出所有用户空间表项
void modifyTable(byte[] tableName, HTableDescriptor htd) #修改表的模式,是异步的 操作,可能需要花费一定 的时间
boolean tableExists(String tableName) #检查表是否存在

用法示例:
HBaseAdmin admin = new HBaseAdmin(config);
admin.disableTable("tablename")

2、HBaseConfiguration

关系:org.apache.hadoop.hbase.HBaseConfiguration

作用:对 HBase 进行配置

void addResource(Path file) #通过给定的路径所指的文件来添加资源
void clear() #清空所有已设置的属性
String get(String name) #获取属性名对应的值 
String getBoolean(String name, boolean defaultValue) #获取为 boolean 类型的属性值,如果其属 性值类型部位 boolean,则返回默认属性值
void set(String name, String value) #通过属性名来设置值
void setBoolean(String name, boolean value) #设置 boolean 类型的属性值


用法示例:
HBaseConfiguration hconfig = new HBaseConfiguration();
hconfig.set("hbase.zookeeper.property.clientPort","2181");
该 方 法 设 置 了 "hbase.zookeeper.property.clientPort" 的 端 口 号 为 2181 。 一 般 情 况 下 ,HBaseConfiguration 会使用构造函数进行初始化,然后在使用其他方法。

3、HTableDescriptor

关系:org.apache.hadoop.hbase.HTableDescriptor

作用:包含了表的名字极其对应表的列簇

void addFamily(HColumnDescriptor) #添加一个列簇
HColumnDescriptor removeFamily(byte[] column) #移除一个列簇
byte[] getName() #获取表的名字
byte[] getValue(byte[] key) #获取属性的值
void setValue(String key, String value) #设置属性的值

用法示例:
HTableDescriptor htd = new HTableDescriptor(table);
htd.addFamily(new HcolumnDescriptor("family"));
在上述例子中,通过一个 HColumnDescriptor 实例,为 HTableDescriptor 添加了一个列簇:family

4、HColumnDescriptor

关系:org.apache.hadoop.hbase.HColumnDescriptor

作用:维护着关于列簇的信息,例如版本号,压缩设置等。它通常在创建表或者为表添加列簇的时候使用。列簇被创建后不能直接修改,只能通过删除然后重新创建的方式。列簇被删除的时候,列簇里面的数据也会同时被删除。

byte[] getName() #获取列簇的名字
byte[] getValue(byte[] key) #获取对应的属性的值
void setValue(String key, String value) #设置对应属性的值

用法示例:
HTableDescriptor htd = new HTableDescriptor(tablename);
HColumnDescriptor col = new HColumnDescriptor("content:");
htd.addFamily(col);
此例添加了一个 content 的列簇

5、HTable

关系:org.apache.hadoop.hbase.client.HTable
作用:可以用来和 HBase 表直接通信。此方法对于更新操作来说是非线程安全的。

void checkAdnPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) #自动的检查row/family/qualifier 是否与给定的值匹配
void close() #释放所有的资源或挂起内部缓冲区中的更新
Boolean exists(Get get) #检查 Get 实例所指定的值是否存在于 HTable 的列中
Result get(Get get) #获取指定行的某些单元格所对 应的值
byte[][] getEndKeys() #获取当前一打开的表每个区域的结束键值
ResultScanner getScanner(byte[] family) #获取当前给定列簇的 scanner实例
HTableDescriptor getTableDescriptor() #获取当前表的HTableDescriptor 实例
byte[] getTableName() #获取表名
static boolean isTableEnabled(HBaseConfiguration conf, String tableName) #检查表是否有效
void put(Put put) #向表中添加值

用法示例:
HTable table = new HTable(conf, Bytes.toBytes(tablename));
ResultScanner scanner = table.getScanner(family);

6、Put

关系:org.apache.hadoop.hbase.client.Put

作用:用来对单个行执行添加操作

Put add(byte[] family, byte[] qualifier, byte[] value) #将指定的列和对应的值添加到Put 实例中
Put add(byte[] family, byte[] qualifier, long ts, byte[] value) #将指定的列和对应的值及时间 戳添加到 Put 实例中
byte[] getRow() #获取 Put 实例的行
RowLock getRowLock() #获取 Put 实例的行锁
long getTimeStamp() #获取 Put 实例的时间戳
boolean isEmpty() #检查 familyMap 是否为空
Put setTimeStamp(long timeStamp) #设置 Put 实例的时间戳

用法示例:
HTable table = new HTable(conf,Bytes.toBytes(tablename));
Put p = new Put(brow);//为指定行创建一个 Put 操作
p.add(family,qualifier,value);
table.put(p);

7、Get

关系:org.apache.hadoop.hbase.client.Get

作用:用来获取单个行的相关信息

Get addColumn(byte[] family, byte[] qualifier) #获取指定列簇和列修饰符对应的列
Get addFamily(byte[] family) #通过指定的列簇获取其对应的所有列
Get setTimeRange(long minStamp,long maxStamp) #获取指定取件的列的版本号
Get setFilter(Filter filter) #当执行 Get 操作时设置服务器端的过滤器

用法示例:
HTable table = new HTable(conf, Bytes.toBytes(tablename));
Get g = new Get(Bytes.toBytes(row));

8、Delete

关系:org.apache.hadoop.hbase.client.Delete

作用:用来封装一个要删除的信息

9、Scan

关系:org.apache.hadoop.hbase.client.Scan

作用:用来封装一个作为查询条件的信息

10、Result

关系:org.apache.hadoop.hbase.client.Result
作用:存储 Get 或者 Scan 操作后获取表的单行值。使用此类提供的方法可以直接获取值或者各种 Map 结构(key-value 对)

boolean containsColumn(byte[] family, byte[] qualifier) #检查指定的列是否存在
NavigableMap<byte[],byte[] > getFamilyMap(byte[] family) #获取对应列簇所包含的修 饰符与值的键值对
byte[] getValue(byte[] family, byte[] qualifier) #获取对应列的最新值

11、ResultScanner

关系:org.apache.hadoop.hbase.client.ResultScanner

作用:存储 Scan 操作后获取表的单行值

6.1、基本增删改查实现

查看hbase-01中的资料!操作要认真写一遍。

6.2、过滤器查询
过滤器的类型很多,但是可以分为两大类——比较过滤器,专用过滤器。过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端;

hbase 过滤器的比较运算符:

LESS <

LESS_OR_EQUAL <=

EQUAL =
NOT_EQUAL <>

GREATER_OR_EQUAL >=

GREATER >
NO_OP 排除所有

HBase 过滤器的比较器(指定比较机制):

BinaryComparator 按字节索引顺序比较指定字节数组,采用 Bytes.compareTo(byte[])
BinaryPrefixComparator 跟前面相同,只是比较左端的数据是否相同
NullComparator 判断给定的是否为空
BitComparator 按位比较
RegexStringComparator 提供一个正则的比较器,仅支持 EQUAL 和非 EQUAL
SubstringComparator 判断提供的子串是否出现在 value 中。

比较过滤器:

行键过滤器 RowFilter
Filter filter1 = new RowFilter(CompareOp.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("user0000")));
scan.setFilter(filter1);

列簇过滤器 FamilyFilter
Filter filter1 = new FamilyFilter(CompareOp.LESS, new BinaryComparator(Bytes.toBytes("base_info")));
scan.setFilter(filter1);
 
列过滤器 QualifierFilter
Filter filter = new QualifierFilter(CompareOp.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("name")));
scan.setFilter(filter1);

值过滤器 ValueFilter
Filter filter = new ValueFilter(CompareOp.EQUAL, new SubstringComparator("zhangsan") );
scan.setFilter(filter1);

时间戳过滤器 TimestampsFilter
List<Long> tss = new ArrayList<Long>();
tss.add(1495398833002l);
Filter filter1 = new TimestampsFilter(tss);
scan.setFilter(filter1);

专用过滤器:

单列值过滤器 SingleColumnValueFilter----会返回满足条件的整行
SingleColumnValueFilter filter = new SingleColumnValueFilter( Bytes.toBytes("colfam1"),Bytes.toBytes("col-5"), CompareFilter.CompareOp.NOT_EQUAL, new SubstringComparator("val-5"));
filter.setFilterIfMissing(true); //如果不设置为 true,则那些不包含指定 column 的行也会返回
scan.setFilter(filter1);

单列值排除器 SingleColumnValueExcludeFilter -----返回排除了该列的结果与上面的结果相反

前缀过滤器 PrefixFilter----针对行键
Filter filter = new PrefixFilter(Bytes.toBytes("row1"));
scan.setFilter(filter1);

列前缀过滤器 ColumnPrefixFilter
Filter filter = new ColumnPrefixFilter(Bytes.toBytes("qual2"));
scan.setFilter(filter1);

分页过滤器 PageFilter
查看hbase-01中的资料!操作要认真写一遍。
HBase结合MapReduce:

为什么需要用 MapReduce 去访问 HBase 的数据?——加快分析速度和扩展分析能力。

MapReduce 访问 HBase 数据作分析一定是在离线分析的场景下应用。

img

1.1、HBaseToHDFS

从 HBase 中读取数据,分析之后然后写入 HDFS,代码实现:

package com.aura.mazh.hbase126.mapreduce;
 
import java.io.IOException; import java.util.List;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 
/**
* 描述: 编写 mapreduce 程序从 hbase 读取数据,然后存储到 hdfs */
public class HBaseDataToHDFSMR {
 
    public static final String ZK_CONNECT = "hadoop02:2181,hadoop03:2181,hadoop04:2181";
    public static final String ZK_CONNECT_KEY = "hbase.zookeeper.quorum"; public static final String HDFS_CONNECT = "hdfs://myha01/";
    public static final String HDFS_CONNECT_KEY = "fs.defaultFS"; public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create(); conf.set(ZK_CONNECT_KEY, ZK_CONNECT); conf.set(HDFS_CONNECT_KEY, HDFS_CONNECT);
        System.setProperty("HADOOP_USER_NAME", "hadoop"); 
        Job job = Job.getInstance(conf);
        // 输入数据来源于 hbase 的 user_info 表
        Scan scan = new Scan(); TableMapReduceUtil.initTableMapperJob("user_info", scan,HBaseDataToHDFSMRMapper.class, Text.class, NullWritable.class, job);
        // RecordReader --- TableRecordReader
        // InputFormat ----- TextInputFormat // 数据输出到 hdfs
        FileOutputFormat.setOutputPath(job, new Path("/hbase2hdfs/output2"));
        boolean waitForCompletion = job.waitForCompletion(true);
        System.exit(waitForCompletion ? 0 : 1);
    }
 
    /**
    * mapper的输入key-value类型是:ImmutableBytesWritable, Result * mapper的输出key-value类型就可以由用户自己制定
    */
    static class HBaseDataToHDFSMRMapper extends TableMapper<Text, NullWritable> {
        
        /**
         * keyType: LongWritable -- ImmutableBytesWritable:rowkey
         * ValueType: Text -- Result:hbase 表中某一个 rowkey 查询出来的所有的 key-value 对 */
        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
 
 
            // byte[] rowkey = Bytes.copy(key, 0, key.getLength());
            String rowkey = Bytes.toString(key.copyBytes()); List<Cell> listCells = value.listCells();
            Text text = new Text();
            // 最后输出格式是: rowkye, base_info:name-huangbo, base-info:age-34 
            for (Cell cell : listCells) {
                String family = new String(CellUtil.cloneFamily(cell)); 
                String qualifier = new String(CellUtil.cloneQualifier(cell));                             
                String v = new String(CellUtil.cloneValue(cell));
                long ts = cell.getTimestamp();
                text.set(rowkey + "\t" + family + "\t" + qualifier + "\t" + v + "\t" + ts);
                context.write(text, NullWritable.get()); }
 
 
        }
        
    }
 
}

1.2、HDFSToHBase

从 HDFS 从读入数据,处理之后写入 HBase,代码实现:

package com.aura.mazh.hbase126.mapreduce; import java.io.IOException;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableReducer; import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
* 需求:读取 HDFS 上的数据。插入到 HBase 库中 *
* 程序运行之前,要先做两件事:
* 1、把 student.txt 文件放入:/bigdata/student/input/目录中 
* 2、创建好一张 hbase 表:create "student", "info"
*/
public class HDFSDataToHBaseMR extends Configured implements Tool{
 
 
 
    public static void main(String[] args) throws Exception {
        int run = ToolRunner.run(new HDFSDataToHBaseMR(), args);
        System.exit(run); 
    }
 
 
    @Override
    public int run(String[] arg0) throws Exception {
  
        Configuration config = HBaseConfiguration.create();
        config.set("hbase.zookeeper.quorum", "hadoop02:2181,hadoop03:2181,hadoop04:2181");
        System.setProperty("HADOOP_USER_NAME", "hadoop");
        Job job = Job.getInstance(config, "HDFSDataToHBaseMR");
        job.setJarByClass(HDFSDataToHBaseMR.class);
        job.setMapperClass(HBaseMR_Mapper.class);             
        job.setMapOutputKeyClass(Text.class);         
        job.setMapOutputValueClass(NullWritable.class);
        // 设置数据的输出组件
        TableMapReduceUtil.initTableReducerJob("student", HBaseMR_Reducer.class, job);
        job.setOutputKeyClass(NullWritable.class);     
        job.setOutputValueClass(Put.class);
        FileInputFormat.addInputPath(job, new Path("/bigdata/student/input"));
        boolean isDone = job.waitForCompletion(true);
        return isDone ? 0: 1; 
 
    }
 
 
 
    public static class HBaseMR_Mapper extends Mapper<LongWritable, Text, Text, NullWritable>{
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            context.write(value, NullWritable.get()); 
        }
    }
 
 
    public static class HBaseMR_Reducer extends TableReducer<Text, NullWritable, NullWritable>{
        @Override
        protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            String[] split = key.toString().split(","); 
            Put put = new  Put(split[0].getBytes());
            put.addColumn("info".getBytes(), "name".getBytes(), split[1].getBytes());             
            put.addColumn("info".getBytes(), "sex".getBytes(), split[2].getBytes());         
            put.addColumn("info".getBytes(), "age".getBytes(), split[3].getBytes());         
            put.addColumn("info".getBytes(), "department".getBytes(),split[4].getBytes()); 
            context.write(NullWritable.get(), put);
        } 
 
    }
 
 
}
HBase和MySQL数据互导:

2.1、MySQL数据导入到HBase

下面是命令:

sqoop import \
--connect jdbc:mysql://hadoop02/bigdata \

--username root \
--password root \
满足条件的整行
SingleColumnValueFilter filter = new SingleColumnValueFilter( Bytes.toBytes("colfam1"),Bytes.toBytes("col-5"), CompareFilter.CompareOp.NOT_EQUAL, new SubstringComparator("val-5"));
filter.setFilterIfMissing(true); //如果不设置为 true,则那些不包含指定 column 的行也会返回
scan.setFilter(filter1);

单列值排除器 SingleColumnValueExcludeFilter -----返回排除了该列的结果与上面的结果相反

前缀过滤器 PrefixFilter----针对行键
Filter filter = new PrefixFilter(Bytes.toBytes("row1"));
scan.setFilter(filter1);

列前缀过滤器 ColumnPrefixFilter
Filter filter = new ColumnPrefixFilter(Bytes.toBytes("qual2"));
scan.setFilter(filter1);

分页过滤器 PageFilter
查看hbase-01中的资料!操作要认真写一遍。
HBase结合MapReduce:

为什么需要用 MapReduce 去访问 HBase 的数据?——加快分析速度和扩展分析能力。

MapReduce 访问 HBase 数据作分析一定是在离线分析的场景下应用。

img

1.1、HBaseToHDFS

从 HBase 中读取数据,分析之后然后写入 HDFS,代码实现:

package com.aura.mazh.hbase126.mapreduce;
 
import java.io.IOException; import java.util.List;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 
/**
* 描述: 编写 mapreduce 程序从 hbase 读取数据,然后存储到 hdfs */
public class HBaseDataToHDFSMR {
 
    public static final String ZK_CONNECT = "hadoop02:2181,hadoop03:2181,hadoop04:2181";
    public static final String ZK_CONNECT_KEY = "hbase.zookeeper.quorum"; public static final String HDFS_CONNECT = "hdfs://myha01/";
    public static final String HDFS_CONNECT_KEY = "fs.defaultFS"; public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create(); conf.set(ZK_CONNECT_KEY, ZK_CONNECT); conf.set(HDFS_CONNECT_KEY, HDFS_CONNECT);
        System.setProperty("HADOOP_USER_NAME", "hadoop"); 
        Job job = Job.getInstance(conf);
        // 输入数据来源于 hbase 的 user_info 表
        Scan scan = new Scan(); TableMapReduceUtil.initTableMapperJob("user_info", scan,HBaseDataToHDFSMRMapper.class, Text.class, NullWritable.class, job);
        // RecordReader --- TableRecordReader
        // InputFormat ----- TextInputFormat // 数据输出到 hdfs
        FileOutputFormat.setOutputPath(job, new Path("/hbase2hdfs/output2"));
        boolean waitForCompletion = job.waitForCompletion(true);
        System.exit(waitForCompletion ? 0 : 1);
    }
 
    /**
    * mapper的输入key-value类型是:ImmutableBytesWritable, Result * mapper的输出key-value类型就可以由用户自己制定
    */
    static class HBaseDataToHDFSMRMapper extends TableMapper<Text, NullWritable> {
        
        /**
         * keyType: LongWritable -- ImmutableBytesWritable:rowkey
         * ValueType: Text -- Result:hbase 表中某一个 rowkey 查询出来的所有的 key-value 对 */
        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
 
 
            // byte[] rowkey = Bytes.copy(key, 0, key.getLength());
            String rowkey = Bytes.toString(key.copyBytes()); List<Cell> listCells = value.listCells();
            Text text = new Text();
            // 最后输出格式是: rowkye, base_info:name-huangbo, base-info:age-34 
            for (Cell cell : listCells) {
                String family = new String(CellUtil.cloneFamily(cell)); 
                String qualifier = new String(CellUtil.cloneQualifier(cell));                             
                String v = new String(CellUtil.cloneValue(cell));
                long ts = cell.getTimestamp();
                text.set(rowkey + "\t" + family + "\t" + qualifier + "\t" + v + "\t" + ts);
                context.write(text, NullWritable.get()); }
 
 
        }
        
    }
 
}

1.2、HDFSToHBase

从 HDFS 从读入数据,处理之后写入 HBase,代码实现:

package com.aura.mazh.hbase126.mapreduce; import java.io.IOException;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableReducer; import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
* 需求:读取 HDFS 上的数据。插入到 HBase 库中 *
* 程序运行之前,要先做两件事:
* 1、把 student.txt 文件放入:/bigdata/student/input/目录中 
* 2、创建好一张 hbase 表:create "student", "info"
*/
public class HDFSDataToHBaseMR extends Configured implements Tool{
 
 
 
    public static void main(String[] args) throws Exception {
        int run = ToolRunner.run(new HDFSDataToHBaseMR(), args);
        System.exit(run); 
    }
 
 
    @Override
    public int run(String[] arg0) throws Exception {
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值