简介
Hadoop 是耳熟能详的卓越开源分布式文件存储及处理框架,它能让用户轻松地开发处理海量数据的应用程序,其主要优点有:
高可靠性: Hadoop 按位存储和处理数据的能力值得人们信赖。
高扩展性: Hadoop 在可用的计算机集簇间分配数据并完成计算任务的,这些集簇可以方便地扩展到数以干计的节点中。
高效性: Hadoop能够在节点之间动态地移动数据,并保证各个节点的动态平衡,因此处理速度非常快。
高容错性 :Hadoop自动保存数据的多个副本,并自动将失败任务重分配。
低成本: 与一体机、商用数据仓库比,Hadoop 开源软件的成本更低。
随着版本的演进,Hadoop 实现了较好的资源隔离并增加其他特性,这里比较一下Hadoop 1.0和2.0版本的特性差异,Hadoop 1.0由HDFS和MapReduce两个系统组成,存在以下几个缺点:
静态资源配置: 即每个节点实现配置好可用的slot总数,这些slot数目一旦启动后无法再动态修改;
资源无法共享: 将slot分为Map slot和Reduce slot两种,且不允许共享;
资源划分粒度过大: 基于无类别slot的资源划分方法的划分粒度仍过于粗糙,往往会造成节点资源利用率过高或者过低;
无有效资源隔离机制: 采用基于jvm的资源隔离机制,过于粗糙,很多资源,如CPU无法进行隔离,这会造成同一个节点上的任务之间干扰严重。
Hadoop 2.0由HDFS、MapReduce和YARN三个系统组成,其中YARN是一个资源管理系统,负责集群资源管理和调度,2.0中YAR允许每个节点(NodeManager)配置可用的CPU和内存资源总量,而中央调度器则会根据这些资源总量分配给应用程序。
HDFS
HDFS(Hadoop Distributed File System) ,Hadoop上的分布式文件系统,适合PB级大量数据的存储,扩展性强,容错性高(默认3副本)。
如图所示HDFS是Master/Slave结构,有NameNode、Secondary NameNode、DataNode这几个角色,理解其架构及工作原理需要弄清的概念:
NameNode : Master节点,管理数据块映射;处理客户端的读写请求;配置副本策略;管理HDFS的名称空间;
Secondary NameNode : 分担namenode工作量;是NameNode的冷备份;合并fsimage和fsedits然后再发给namenode。
DataNode : Slave节点,负责存储client发来的数据块block;执行数据块的读写操作。
热备份: b是a的热备份,如果a坏掉。那么b马上运行代替a的工作。
冷备份: b是a的冷备份,如果a坏掉。那么b不能马上代替a工作。但是b上存储a的一些信息,减少a坏掉之后的损失。
Fsimage : 元数据镜像文件(文件系统的目录树。)
edits : 元数据的操作日志(针对文件系统做的修改操作记录)
机架: HDFS集群由分布在多个机架上的大量DataNode组成,不同机架之间节点通过交换机通信,HDFS通过机架感知策略,使NameNode能够确定每个DataNode所属的机架ID,使用副本存放策略,来改进数据的可靠性、可用性和网络带宽的利用率。
数据块 (block) : HDFS最基本存储单元,默认128M,用户可自行设置。
元数据: 指HDFS文件系统中,文件和目录的属性信息。HDFS实现时采用镜像文件(Fsimage) + 日志文件(EditLog)的备份机制。文件的镜像文件中内容包括:修改时间、访问时间、数据块大小、组成文件的数据块的存储位置信息。目录的镜像文件内容包括:修改时间、访问控制权限等信息。日志文件记录的是:HDFS的更新操作。NameNode启动的时候,会将镜像文件和日志文件的内容在内存中合并。把内存中的元数据更新到最新状态。
HDFS读文件
1)客户端显式调用open()函数打开文件。
2)后台通过RPC调用NN服务,获取欲打开文件的文件块信息和文件所在的数据节点。
3)客户端显式调用read()函数,从第一个数据块开始读取数据,并选择离客户端最近的那个副本。
4)选择离客户端最近的副本后,客户端直接从DN读取数据。
5)当前数据块读完后继续连接此文件下一个数据块最近副本所在的DN。
6)读完数据时,客户端显式调用close()函数。相对于读取本地文件系统数据,HDFS读取数据流程较复杂,但对客户端而言,需显式调用的函数仅为open()、read()和close(),与读取本地文件系统数据的方法基本相同。
HDFS写文件
1)客户端调用create()来创建文件。
2)后台通过RPC调用NN服务,在文件系统的命名空间中创建新文件。
3)客户端开始写入数据。先将数据写到本地临时文件中,当累积到1个数据块大小时,客户端会从NN获取1个DN列表,同时后台会将该文件块切分成多个数据包(packet)。
4)每个packet 以流水线方式写入到NN 返回的DN。
5)最后一个DN 针对每个packet,朝着写入流水线的反方向返回ACK,确认packet成功写入所有DN。
6)客户端调用close()函数关闭文件,剩余所有数据将写入DN,并关闭与DN的连接。
7)通知NN写入完毕。
HDFS写流程虽同样复杂,但客户端仅需操作create()、write()和close()函数,与写入本地文件系统数据方法类似。
Mapreduce
MapReduce非常简单,易于实现且扩展性强。可以通过它轻易地编写出同时在多台主机上运行的程序,可以使用Ruby、Python、PHP和C++等非Java类语言编写map和reduce程序。MapReduce适合于处理大量的数据集,因为它会同时被多台主机一起处理,这样通常会有较快的速度。
在Hadoop中,用于执行MapReduce任务的机器角色有两个:一个是JobTracker;另一个是TaskTracker。JobTracker是用于调度工作的,TaskTracker是用于执行工作的。一个Hadoop集群中只有一台JobTracker。在hadoop中,每个MapReduce任务都被初始化为一个Job,每个Job又可以分为两个阶段:map阶段和reduce阶段。
Hadoop 中数据处理核心是MapReduce 程序设计模型。一个Map/Reduce 作业(job) 通常会把输入的数据集切分为若干数据块,对于独立的数据块,由 map任务(task)以完全并行的方式处理它们。框架会对map的输出先进行排序, 然后把结果输入给reduce任务。通常作业的输入和输出都会被存储在文件系统中。因此,编程主要是 mapper阶段和reducer阶段。
图. MapReduce控制流和数据流
图. MapReduce数据流
单词计数
计算出文件中各个单词的频数。输出结果按照单词的字母顺序进行排序。
数据去重
file1.txt
file2.txt
MapReduce 去重后的结果
2017-12-9 a
2017-12-9 b
2017-12-9 a
2017-12-10 b
2017-12-10 a
2017-12-9 b
2017-12-11 c
2017-12-11 b
2017-12-10 a
2017-12-12 d
2017-12-12 d
2017-12-10 b
2017-12-13 a
2017-12-13 a
2017-12-11 b
2017-12-14 b
2017-12-14 c
2017-12-11 c
2017-12-15 c
2017-12-15 d
2017-12-12 d
2017-12-11 c
2017-12-11 c
2017-12-13 a
2017-12-14 b
2017-12-14 c
2017-12-15 c
reduce的输入应该以数据作为key,而对value-list则没有要求。当reduce接收到一个
时就直接将key复制到输出的key中,并将value设置成空值。所以map阶段要完成的任务就是在采用Hadoop默认的作业输入方式之后,将value设置成key,并直接输出(这里输出中的value为空值)。
单表关联
输入数据
输出数据
child
parent
grandchild
grandparent
Tom
Lucy
Tom
Jesse
Tom
Jack
Tom
Alice
Jone
Lucy
Jone
Jesse
Jone
Jack
Jone
Alice
Lucy
Mary
Jone
Ben
Lucy
Ben
Jone
Mary
Jack
Alice
Tom
Ben
Jack
Jesse
Tom
Mary
Terry
Alice
Philip
Alice
Terry
Jesse
Philip
Jesse
Philip
Terry
Mark
Alice
Philip
Alma
Mark
Jesse
Mark
Terry
需求:根据 child 和 parent 关系找到相应的祖孙关系
Mark
Alma
在Map阶段,将父子关系与相反的子父关系,同时在各个value前补上前缀-与+标识此key-value中的value是正序还是逆序产生的,之后进入context。MapReduce会自动将同一个key的不同的value值,组合在一起,推到Reduce阶段。在value数组中,根据前缀,我们可以轻松得知,哪个是grandparent,哪个是child。
多表关联
输入数据
输出数据
table1
table2
id
name
id
statyear
num
key
value
1
北京
1
2010
1962
1
北京 2011 2019
2
天津
1
2011
2019
1
北京 2010 1962
3
河北
2
2010
1299
2
天津 2011 1355
4
山西
2
2011
1355
2
天津 2010 1299
5
内蒙古
4
2011
3574
4
山西 2011 3593
6
辽宁
4
2011
3593
4
山西 2010 3574
7
吉林
9
2010
2303
需求:就是以 id 为 key 做 join 操作
8
黑龙江
9
2011
2347
在Map阶段,把所有数据标记成
的形式,其中key是id,value则根据来源不同取不同的形式:来源于A的记录,value的值为"a#"+name;来源于B的记录,value的值为"b#"+score。
在reduce阶段,先把每个key下的value列表拆分为分别来自表A和表B的两部分,分别放入两个向量中。然后遍历两个向量做笛卡尔积,形成一条条最终的结果。
Hadoop流
Hadoop流提供给了API允许用户使用任何脚本语言编写map函数或reduce函数。Hadoop流的关键是,它使用UNIX标准流作为程序与Hadoop之间的接口。因此,任何程序只要可以从标准输入流中读取数据,并且可以写入数据到标准输出流,那么就可以通过Hadoop流使用其他语言编写MapReduce程序的map函数或reduce函数。
bin/Hadoop jar contrib/streaming/Hadoop-0.20.2-streaming.jar –input input –output output –mapper /bin/cat –reducer usr/bin/wc
从这个例子中可以看到,Hadoop流引入的包是Hadoop-0.20.2-streaming.jar,并且具有如下命令:
-input 指明输入文件路径
-output 指明输出文件路径
-mapper 制定map函数
-reducer 指定reduce函数
Hadoop流的工作原理
当一个可执行文件作mapper时,每一个map任务会以一个独立的进程启动这个可执行文件,然后在map任务运行时,会把输入切分成行提供给可执行文件,并作为它的标准输入(stdin)内容。当可执行文件运行出结果时,map从标准输出(stdout)中收集数据,并将其转化为
对,作为map输出。
reduce与map相同,如果可执行文件作为reducer时,reduce任务会启动这个可执行文件,并且将
转化为行来作为这个可执行文件的标准输入(stdin)。然后reduce会收集这个可执行文件的标准输出(stdout)内容,并把每一行转化为
对,作为reduce输出。
map与reduce将输出转化为
的默认方法是,将每行的第一个tab符号(制表符)之前的内容作为key,之后的内容作为value。如果没有tab符号,那么这一行的所有内容作为key,而value值为null,可以更改。
Hadoop流的命令
Hadoop流命令的具体内容如下表所示:
参数
可选 / 必选
参数解释
-input
必选
输入数据路径。指定作业输入, path 可以是文件或者目录,可以使用 * 通配符, -input 选项可以使用多次指定多个文件或目录作为输入。
-output
必选
输出数据路径。指定作业输出目录, path 必须 不存在 ,而且执行作业的用户必须有创建该目录的权限, -output 只能使用一次。
-mapper
必选
mapper 可执行程序或 Java 类,必须指定且唯一。
-reducer
必选
reducer 可执行程序或 Java 类,必须指定且唯一。
-file
可选
分发本地文件
-cacheFile
可选
分发 HDFS 文件
-cacheArchive
可选
分发 HDFS 压缩文件
-numReduceTasks
可选
指定 reduce 任务个数。如果设置 -numReduceTasks 0 或者 -reducer NONE 则没有 reducer 程序, mapper 的输出直接作为整个作业的输出。
-jobconf | -D NAME=VALUE
可选
作业配置参数。指定作业参数, NAME 是参数名, VALUE 是参数值,可以指定的参数参考 hadoop-default.xml 。特别建议用 -jobconf mapred.job.name='My Job Name' 设置作业名,使用 -jobconf mapred.job.priority=VERY_HIGH | HIGH | NORMAL | LOW | VERY_LOW 设置作业优先级,使用 -jobconf mapred.job.map.capacity=M 设置同时最多运行 M 个 map 任务,使用 -jobconf mapred.job.reduce.capacity=N 设置同时最多运行 N 个 reduce 任务。
-combiner
可选
Combiner Java 类
-partitioner
可选
Partitioner ,输出文件的处理方法 Java 类
-inputformat
可选
InputFormat ,输入文件的处理方法 Java 类
-outputformat
可选
OutputFormat Java 类
-inputreader
可选
InputReader 配置
-cmdenv
=
可选
传给 mapper 和 reducer 的环境变量
-mapdebug
可选
mapper 失败时运行的 debug 程序
-reducedebug
可选
reducer 失败时运行的 debug 程序
-verbose
可选
详细输出模式
Hadoop流通用命令是用来配置Hadoop流的Job的。需要注意的是,如果使用这部分配置,则必须将其置于流命令配置之前,否则命令会失败。
参数
可选 / 必选
描述
-archives
可选
用逗号分隔计算中未归档的文件。 仅仅针对 JOB 。
-conf
可选
制定应用程序的配置文件 Specify an application configuration file.
-D
=
可选
使用给定的属性值。
-files
可选
用逗号分隔的文件 , 拷贝到 Map reduce 机器,仅仅针对 JOB
-jt
or
可选
指定一个 ResourceManager. 仅仅针对 JOB 。
-libjars
可选
将用逗号分隔的 jar 路径包含到 classpath 中去,仅仅针对 JOB 。
Hadoop Streaming 的优缺点
优点:
可以使用自己喜欢的语言来编写 MapReduce 程序(不必非得使用 Java)
不需要像写 Java 的 MR 程序那样 import 一大堆库,在代码里做很多配置,很多东西都抽象到了 stdio 上,代码量显著减少。
因为没有库的依赖,调试方便,并且可以脱离 Hadoop 先在本地用管道模拟调试。
缺点:
只能通过命令行参数来控制 MapReduce 框架,不像 Java 的程序那样可以在代码里使用 API,控制力比较弱。
因为中间隔着一层处理,效率会比较慢。
Hadoop Streaming 比较适合做一些简单的任务,比如用 Python 写只有一两百行的脚本。如果项目比较复杂,或者需要进行比较细致的优化,使用 Streaming 就容易出现一些束手束脚的地方。
Yarn
在没有YARN之前,Hadoop 1.0版本时候, MapReduce做很多的事情,Job Tracker(作业跟踪者)既做资源管理又做任务调度/监控,Task Tracker资源划分过于粗,MapReduce 实现任务分配、资源分配、批量计算的框架图如下:
首先用户程序 (JobClient) 提交了一个 job,job 的信息会发送到Job Tracker 中,Job Tracker 是 Map-reduce 框架的中心,他首先做任务分配,需要知道数据分布在哪里,这意味着要和HDFS Metadata Server通讯;其次要根据数据分布,分配任务给实际机器,这里又基本有两个步骤,先确定有哪些机器是存活的、资源还剩余多少;另外,根据他们和数据的分布关系,做出任务分配策略;然后开始分配任务,要将MapReduce逻辑分发到各台机器上;然后,要监控各个机器上Mapper、Reducer实例的任务进度,如果失败要回收资源并重新分配资源,指定数据索引,重新执行
总结下:他需要与集群中的机器定时通信 (heart beat),需要管理哪些程序应该跑在哪些机器上,需要管理所有 job 失败、重启等操作,还需要和数据元数据中心通讯,了解数据分布等等。
TaskTracker 是 Map-reduce 集群中每台机器都有的一个部分,他做的事情主要是监视自己所在机器的资源情况。TaskTracker 同时监视当前机器的 tasks 运行状况。TaskTracker 需要把这些信息通过 heart beat发送给JobTracker,JobTracker 会搜集这些信息以给新提交的 job 分配运行在哪些机器上。
风险: JobTracker 是 Map-reduce 的集中处理点,管理事情多,存在单点故障,状态一致性不便保障,很难做到Secondary Standby,从而不便做HA,JobTracker 完成太多的任务造成过多的资源消耗,当 MR job 非常多的时候,会造成很大的内存开销,也增加了 JobTracker failed 风险,这也是业界普遍总结出老 Hadoop 的 Map-Reduce 只能支持 4000 节点主机的上限。
在 TaskTracker 端,以 map/reduce task 数目作为资源衡量标准过于简单,没有考虑到 CPU/内存的占用,如果两个大内存消耗的 task 被调度到了一块,很容易出现 OOM。在 TaskTracker 端,把资源强制划分为 map task slot 和 reduce task slot, 如果当系统中只有 map task 或者只有 reduce task 的时候,会造成资源的浪费,也就是前面提过的集群资源利用的问题。
源代码层面分析的时候,会发现代码非常的难读,常常因为一个 class 做了太多的事情,代码量达 3000 多行造成 class 的任务不清晰,增加 bug 修复和版本维护的难度。
从操作的角度看MapReduce 框架在有任何重要/不重要的变化 ( 例如 bug 修复,性能提升和特性化 ) 时,都会强制进行系统级别的升级更新。更糟的是,强制让分布式集群系统的每一个用户端同时更新。这些更新会让用户为了验证他们之前的应用程序是不是适用新的 Hadoop 版本而浪费大量时间,长期以往这就要从优秀的开源系统名单中除名。
Hadoop 2.0中的MapReduce则专门处理数据分析,YARN做为资源管理器存在,YARN主要由Resource Manager、Node Manager、Application Master和Container等组件构成,它是一个master/slave结构,如图:
Resource manager是master,Node manager是slave节点。Resource manager负责对各个node manger上资源进行统一管理和调度。当用户提交一个应用程序时,需要提供一个用以跟踪和管理这个程序的Application Master,它负责向Resource Manager申请资源,并要求Node Manger启动可以占用一定资源的任务。由于不同的Application Master被分布到不同的节点上,因此它们之间不会相互影响。
Resource Manager是Master上一个独立运行的进程,负责集群统一的资源管理、调度、分配等;Node Manager是Slave上一个独立运行的进程,负责上报节点的状态;App Master和Container是运行在Slave上的组件,Container是yarn中分配资源的一个单位,包涵内存、CPU等资源,yarn以Container为单位分配资源。
Client向Resource Manager提交的每一个应用程序都必须有一个Application Master,它经过Resource Manager分配资源后,运行于某一个Slave节点的Container中,具体做事情的Task,同样也运行与某一个Slave节点的Container中。RM,NM,AM乃至普通的Container之间的通信,都是用RPC机制。
Resource manager
RM是一个全局的资源管理器,集群中只有一个该角色,负责整个系统的资源管理和分配,包括处理客户端请求、启动/监控APP master、监控node manager等。它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Applications Manager,ASM)。
调度器
调度器根据容量、队列等限制条件(如每个队列分配一定的资源,最多执行一定数量的作业等),将系统中的资源分配给各个正在运行的应用程序。需要注意的是,该调度器是一个“纯调度器”,它不再从事任何与具体应用程序相关的工作,比如不负责监控或者跟踪应用的执行状态,也不负责重启因应用执行失败或硬件故障产生的失败任务,这些均交由应用程序相关的Application Master完成。
调度器仅根据各个应用程序的资源需求进行资源分配,而资源分配单位用一个抽象概念“资源容器”(Resource Container,简称Container)表示,Container是一个动态资源分配单位,它将内存、CPU、磁盘、网络等资源封装在一起,从而限定每个任务使用的资源量。此外,该调度器是一个可插拔的组件,用户可根据自己的需要设计新的调度器,YARN提供了多种直接可用的调度器,比如Fair Scheduler和Capacity Scheduler等。
应用程序管理器( Application Master)
应用程序管理器负责管理整个系统中所有应用程序,包括应用程序提交、与调度器协商资源以启动Application Master、监控Application Master运行状态并在失败时重新启动它等。
Application Master(AM)
Node Manager(NM)
Node manager整个集群有多个,负责每个节点上的资源和使用。负责单个节点上的资源管理和任务,处理来自于resource manager的命令,处理来自域app master的命令。Node manager管理着抽象容器,这些抽象容器代表着一些特定程序使用针对每个节点的资源。Node manager定时地向RM汇报本节点上的资源使用情况和各个Container的运行状态(CPU和内存等资源)
Container
Container是YARN中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等,当AM向RM申请资源时,RM为AM返回的资源便是用Container表示的。YARN会为每个任务分配一个Container,且该任务只能使用该Container中描述的资源。需要注意的是,Container不同于MR v1中的slot,它是一个动态资源划分单位,是根据应用程序的需求动态生成的。目前为止,YARN仅支持CPU和内存两种资源,且使用了轻量级资源隔离机制Cgroups进行资源隔离;
Zookeeper
https://blog.csdn.net/gs80140/article/details/51496925
在大数据时代,应用服务由很多个独立的程序组成, 这些独立的程序运行在形形色色、千变万化硬件上,如何让一个应用中多个独立的程序协同工作是一件困难的事情。开发这样的应用容易让开发人员陷入如何使多个程序协同工作的逻辑中,最后导致没时间思考和实现应用程序逻辑;又或者开发人员对协同逻辑关注不够,开发了简单脆弱的主协调器,导致不可靠的单一失效点。
ZooKeeper是分布式系统/软件的协调者,其设计保证分布式程序的健壮性,使得应用开发人员可以更多关注应用本身逻辑,而不是协同工作,ZK是集群的管理者,监视着集群各节点状态,并根据节点的反馈进行下一步合理操作,最终将简单易用的接口和性能高效、功能稳定的系统提供给用户。
分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列等功能。
Hadoop中使用Zookeeper的事件处理确保整个集群只有一个NameNode,存储配置信息等;HBase使用Zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机,存储访问控制列表等;Kafka使用ZK监控节点状态、存储偏移量,并选择主broker。
角色
构成
简单的说,zookeeper=文件系统+通知机制。
文件系统: Zookeeper维护一个类似文件系统的数据结构:
每个子目录项如 NameService 都被称作为 znode,和文件系统一样,能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的,znode有四种类型的:
客户端与zookeeper断开连接后,该节点依旧存在
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
客户端与zookeeper断开连接后,该节点被删除
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号;
通知机制: 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。
配置
ZK通过配置文件来进行控制管理(zoo.cfg配置文件),某些参数是可选的,某些参数是必须的。这些必须的参数就构成了ZooKeeper配置文档的最低配置要求,原生的 conf 目录中没有 zoo.cfg ;
Zoo.cfg的配置:
# the port at which the clients will connect
//zookeeper 对外通信端口,默认不用修改
clientPort=2181
# The number of milliseconds of each tick
//Zookeeper服务器心跳时间,单位毫秒
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
//投票选举新leader的初始化时间
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
//Leader与Follower之间的最大响应时间单位,响应超过syncLimit*tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
//数据持久化目录,也保存有节点的ID信息,需要自己创建指定
dataDir=/home/xxxx/zookeeperxxxx/data
//日志保存路径,这个目录必须手工创建指定,否则启动报错。
dataLogDir=/home/xxx/zookeeper/logs
//Session超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。默认的Session超时时间是在2 *tickTime ~ 20 * tickTime这个范围
maxSessionTimeout=120000
# The number of snapshots to retain in dataDir
//这个参数和下面的参数搭配使用,这个参数指定了需要保留的文件数目。默认是保留3个。(No Java system property)New in 3.4.0
autopurge.snapRetainCount=2
# Purge task interval in hours
# Set to "0" to disable auto purge feature
//在上文中已经提到,3.4.0及之后版本,ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能,但可以运行bin/zkCleanup.sh来手动清理zk日志。
autopurge.purgeInterval=3
//配置zookeepe集群各节点之间通信和选举的端口,其中2888端口号是zookeeper服务之间通信的监听端口,而3888是zookeeper选举通信端口。server.N N代表这个节点的ID编号,需要用户手工指定各节点对应的编号,编号不能有重复;
server.1=namenode:2888:3888
server.2=datanode1:2888:3888
server.3=datanode2:2888:3888
配置集群节点编号myid
新建文件myid(在zoo.cfg配置的dataDir目录下,此处为/home/xxx/zookeeperxxx/data),使得myid中的值与server的编号相同,比如namenode上的myid:1;datanode1上的myid:2,以此类推;
配置log4j.properties:
在~/zookeeper/conf/路径下有log4j.properties文件,需要修改host及其他的log路径配置信息;
zoo.cfg 优化
https://www.cnblogs.com/fanweiwei/p/4517012.html
1:默认jvm没有配置Xmx、Xms等信息,可以在conf目录下创建java.env文件
export JVMFLAGS="-Xms512m -Xmx512m $JVMFLAGS"
2:log4j配置,由于zk是通过nohup启动的,会有一个zookeeper.out日志文件,该文件中记录的是输出到console的日志。log4j中只要配置输出到console即可,zookeeper.out日积月累会不断变大,要放在容量大的磁盘上。
3:zoo.cfg文件中,dataDir是存放快照数据的,dataLogDir是存放写前日志的。这两个目录不要配置成一个路径,要配置到不同的磁盘上。如果磁盘是使用了raid,系统就一块磁盘,那配置到一块磁盘上也可以。写前日志的部分对写请求的性能影响很大,保证dataLogDir所在磁盘性能良好。
4:zoo.cfg文件中skipACL=yes,忽略ACL验证,可以减少权限验证的相关操作,提升一点性能。
5:zoo.cfg文件中forceSync=no,这个对写请求的性能提升很有帮助,是指每次写请求的数据都要从pagecache中固化到磁盘上,才算是写成功返回。当写请求数量到达一定程度的时候,后续写请求会等待前面写请求的forceSync操作,造成一定延时。如果追求低延时的写请求,配置forceSync=no,数据写到pagecache后就返回。但是机器断电的时候,pagecache中的数据有可能丢失。
6:zk的dataDir和dataLogDir路径下,如果没有配置zk自动清理,会不断的新增数据文件。可配置成zk系统自动清理数据文件,但是最求系统最高性能的话,建议人工手动清理文件:zkCleanup.sh -n 3 这样保留三份文件。
7:查看zk节点状态。重新启动zk节点前后,一定要查看状态
echo ruok | nc host port
echo stat | nc host port
8:配置fsync.warningthresholdms=20,单位是毫秒,在forceSync=yes的时候,如果数据固化到磁盘的操作fsync超过20ms的时候,将会在zookeeper.out中输出一条warn日志。这个目前zk的3.4.5和3.5版本有bug,在zoo.cfg中配置不生效。我的做法是在conf/java.env中添加java系统属性:
export JVMFLAGS="-Dfsync.warningthresholdms=20 $JVMFLAGS"
zkcli的使用
由于zookeeper类似文件系统的特性,因此,zkCli的操作也类似文件系统中的常用操作:增删改查、资源管理、权限控制等等。
建立会话连接:./zkCli.sh -timeout 0 -r -server ip:port
-timeout:指定当前会话的超时时间。zookeeper依靠与客户端之间的心跳判断一个会话是否有效,timeout指服务器如果在timeout指定的时间内没有收到客户端的心跳包,就认为这个客户端失效。单位毫秒。
-r:read-only。zookeeper的只读模式指zookeeper的服务器如果和集群中半数或半数以上的服务器失去连接以后,该服务器就不在处理客户端的请求了,但这种故障发生时,机器可以向外提供读服务,情况下可以使用只读模式。
-server: 表示想要连接的服务器地址和端口。
进入界面后开始使用zkClient,按h查看使用帮助:可知,zkClient常用操作和文件系统大致相同,包括查看、新增、修改、删除、配额、权限控制等。
zkClient的查询包括节点的数据和节点的状态。主要有使用stat列出节点的状态;使用get获得节点的数据;使用ls列出节点的子节点列表;使用ls2同时列出子节点的列表和节点的状态;
获取节点的状态,使用方法:stat path
在zookeeper中,每一次对数据节点的写操作(如创建一个节点)被认为是一次事务,对于每一个事务系统都会分配一个唯一的id来标识这个事务,cZxid就表示事务id,表示该节点是在哪个事务中创建的;
ctime:表示节点创建的时间;
mZxid:最后一次更新时的事务id;
mtime:最后一次更新时的时间;
pZxid: 表示该节点的子节点列表最后一次被修改的事务的id(为当前节点添加子节点,从当前节点的子节点中删除一个或多个子节点都会引起节点的子节点的列表的改变,而修改节点的数据内容则不在此列);
cversion = -1,dataVersion = 0,aclVersion = 0在第一篇博客中已经有过介绍,分别表示子节点列表的版本,数据内容的版本,acl版本;
ephemeralOwner:用于临时节点,表示创建该临时节点的事务id,如果当前节点是永久节点,这个值是固定的,为0;
datalength表示当前节点存放的数据的长度;
numChildren表示当前节点拥有的子节点的个数;
获取节点的子节点列表及stat该节点,使用方法:ls path或ls2 path
获取节点的数据,其结果是当前节点的值和stat该路径的值放在一起。使用方法:get path
使用delete path删除无子节点的node,删除含有子节点的节点:rmr path;
ACL权限管控
ACL全称为Access Control List 即访问控制列表,用于控制资源的访问权限。ZK 类似文件系统,Client 可以在上面进行创建、更新、删除等权限控制,5种操作权限为:CREATE、READ、WRITE、DELETE、ADMIN ,即增、删、改、查、管理权限,这5种权限简写为crwda(每个单词的首字符缩写);
在传统的文件系统中,文件或子目录默认会继承自父目录的ACL。而在Zookeeper中,znode的ACL是没有继承关系的,每个znode的权限都是独立控制的,只有客户端满足znode设置的权限要求时,才能完成相应的操作。
Zookeeper的ACL,分为三个维度:scheme、id、permission,通常表示为scheme:id:permission,schema代表授权策略,id表用户,permission表权限。
Scheme
scheme对应于采用哪种方案来进行权限管理,zookeeper的scheme的分类如下:
world : 默认方式,相当于全世界都能访问;
auth : 代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest : 即用户名:密码这种方式认证,这也是业务系统中最常用的;
ip : 使用Ip地址认证
1)增加一个认证用户
addauth digest 用户名:密码明文
例:addauth digest user1:password1
2)设置权限
setAcl /path auth:用户名:密码明文:权限
例:setAcl /test auth:user1:password1:cdrwa
3)查看Acl设置:getAcl /path
id
id是验证模式,不同的scheme,id的值也不一样。scheme为ip时,id的值为客户端的ip地址。scheme为world时,id的值为anyone。scheme为digest时,id的值为:username:BASE64(SHA1(password))。
permission
zookeeper目前支持下面一些权限:
CREATE(c): 创建权限,可以在在当前node下创建child node,即对子节点Create操作
DELETE(d): 删除权限,可以删除当前的node,即对子节点Delete操作
READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes,即对本节点GetChildren和GetData操作
WRITE(w): 写权限,可以向当前node写数据,即对本节点SetData操作
ADMIN(a): 管理权限,可以设置当前node的permission,即对本节点setAcl操作
Hbase
HBase是一个构建在HDFS上的分布式列存储系统,主要用于海量结构化数据存储,从逻辑上讲,HBase将数据按照表,列,行进行存储。
HBase与HDFS对比
两者都具有良好的容错性和扩展性,都可以扩展到成百上千个节点。HDFS适合批处理场景,不支持数据的随机查找,不适合增量数据处理,不支持数据更新;
HBase特点
大: 一个表可以有数十亿行,上百万列。
无模式: 每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同列。
面向列: 面向列(族)的存储和权限控制,列(族)独立检索。
稀疏: 对于空(null)的列,并不占用存储空间,表可以设计的非常稀疏。
数据多版本: 每个单元中的数据可以有多个存储版本,默认情况下版本号自动分配,是单元格插入时间的时间戳。
数据类型单一: HBase中的数据都是字符串,没有类型。
HBase架构
Client : 包含访问HBase的接口,并维护cache来加快对HBase的访问;
Zookeeper : 保证任何时候集群中只有一个Master,存贮所有Region的寻址入口,实时监控Region server的上线和下线信息,并通知给Master,存储HBase的schema和table元数据;
Master : 为Region server分配region;负责Region server的负载均衡,发现失效的Region server并重新分配其上的region,管理用户对table的增删改查操作;
Region server : Region server维护region,处理对这些region的IO请求;Region server负责切分在运行过程中变的过大的region;
HBase数据模型
HBase是基于Google BigTable模型开发的典型的key/value系统。
HBase逻辑视图
RowKey : 是Byte array,是表中每条记录的“主键”,方便快速查找,Rowkey的设计非常重要;
Column Family : 列族,拥有一个名称(string),包含一个或多个相关列;
Column : 属于某一个columnfamily,familyName:columnName,每条记录可动态添加;
Version Number : 类型为Long,默认值是系统时间戳,可由用户自定义;
Value(Cell) : Byte array。
表 webtable包含两行(com.cnn.www 和 com.example.www)以及名为contents、anchor 和 people 的三个列族。对于第一行(com.cnn.www),anchor 包含两列(anchor:cssnsi.com,anchor:my.look.ca),并且 contents 包含一列(contents:html)。本
行键 com.cnn.www 的行有5个版本, com.example.www 的行有一个版本。contents:html列限定符包含给定网站的整个 HTML。anchor列族的限定符每个包含与该行所表示的站点链接的外部站点以及它在其链接的锚点(anchor)中使用的文本。people列族代表与该网站相关的人员。
按照约定,列名由其列族前缀和限定符组成。例如,列内容: html 由列族contents和html限定符组成。冒号字符(:)从列族限定符分隔列族。
webtable表如下所示:
此表中显示为空的单元格在 HBase 中不占用空间或实际上存在。这正是使 HBase “稀疏”的原因。表格视图并不是查看 HBase 数据的唯一可能的方法,甚至是最准确的。
HBase物理视图
每个column family存储在HDFS上的一个单独文件中,空值不会被保存。Key 和 Version number在每个column family中均有一份;HBase为每个值维护了多级索引,即:
;表在行的方向上分割为多个Region;Region是Hbase中分布式存储和负载均衡的最小单元,不同Region分布到不同RegionServer上。Region按大小分割的,随着数据增多,Region不断增大,当增大到一个阀值的时候,Region就会分成两个新的Region;
Region虽然是分布式存储的最小单元,但并不是存储的最小单元。每个Region包含着多个Store对象。每个Store包含一个MemStore或若干StoreFile,StoreFile包含一个或多个HFile。MemStore存放在内存中,StoreFile存储在HDFS上。
尽管在 HBase 逻辑视图中,表格被视为一组稀疏的行的集合,但它们是按列族进行物理存储的。可以随时将新的列限定符(column_family:column_qualifier)添加到现有的列族。
ColumnFamily anchor表:
ColumnFamily contents 表:
HBase 逻辑视图中的空单元不存储。因此对时间戳为 t8 的 contents:html 列值的请求将不返回任何值。同样,在时间戳为 t9 中一个anchor:my.look.ca 值的请求也不会返回任何值。但是,如果未提供时间戳,则会返回特定列的最新值。给定多个版本,最近的也是第一个找到的,因为时间戳按降序存储。因此,如果没有指定时间戳,则对行 com.cnn.www 中所有列的值的请求将是: 时间戳 t6 中的 contents:html,时间戳 t9 中 anchor:cnnsi.com 的值,时间戳 t8 中 anchor:my.look.ca 的值。
HBase表、行与列族
HBase 中表是在 schema 定义时被预先声明的,可以使用以下的命令来创建一个表,在这里必须指定表名和列族名。在 HBase shell 中创建表的语法如下所示:
HBase中的行是逻辑上的行,物理模型上行是按列族(colomn family)分别存取的,行键是未解释的字节,行是按字母顺序排序的,最低顺序首先出现在表中。空字节数组用于表示表命名空间的开始和结束。
Apache HBase 中的列被分组为列族。列族的所有列成员具有相同的前缀。例如,courses:history 和 courses:math 都是 courses 列族的成员。冒号字符(:)从列族限定符中分隔列族。列族前缀必须由可打印字符组成。限定尾部,列族限定符可以由任意字节组成。必须在 schema 定义时提前声明列族,而列不需要在 schema 时定义,但可以在表启动并运行时动态地变为列。
在物理上,所有列族成员一起存储在文件系统上。由于调音(tunings)和存储(storage)规范是在列族级完成的,因此建议所有列族成员具有相同的一般访问模式和大小特征。
由{row key, column( =
+
), version} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。
ROOT表和META表
HBase的所有Region元数据被存储在.META.表中,随着Region的增多,.META.表中的数据也会增大,并分裂成多个新的Region。为了定位.META.表中各个Region的位置,把.META.表中所有Region的元数据保存在-ROOT-表中,最后由Zookeeper记录-ROOT-表的位置信息。所有客户端访问用户数据前,需要首先访问Zookeeper获得-ROOT-的位置,然后访问-ROOT-表获得.META.表的位置,最后根据.META.表中的信息确定用户数据存放的位置,如下图所示。
-ROOT-表永远不会被分割,它只有一个Region,这样可以保证最多只需要三次跳转就可以定位任意一个Region。为了加快访问速度,.META.表的所有Region全部保存在内存中。客户端会将查询过的位置信息缓存起来,且缓存不会主动失效。如果客户端根据缓存信息还访问不到数据,则询问相关.META.表的Region服务器,试图获取数据的位置,如果还是失败,则询问-ROOT-表相关的.META.表在哪里。最后,如果前面的信息全部失效,则通过ZooKeeper重新定位Region的信息。所以如果客户端上的缓存全部是失效,则需要进行6次网络来回,才能定位到正确的Region。
HBase数据模型操作
HBase 中有四个主要的数据操作,分别是:Get、Put、Scan 和 Delete。
Get (读取): Get 指定行的返回属性。读取通过 Table.get 执行。
Get 操作的语法如下所示:
在以下的 get 命令示例中,我们扫描了 emp 表的第一行:
读取指定列:下面给出的是使用 get 操作读取指定列语法:
在下面给出的示例表示用于读取 HBase 表中的特定列:
Put (写): Put 可以将新行添加到表中(如果该项是新的)或者可以更新现有行(如果该项已经存在)。Put 操作通过 Table.put(non-writeBuffer)或 Table.batch(non-writeBuffer)执行。
Put 操作的命令如下所示,在该语法中,你需要注明新值:
新给定的值将替换现有的值,并更新该行。
Put操作示例:假设 HBase 中有一个表 EMP 拥有下列数据:
以下命令将员工名为“raju”的城市值更新为“Delhi”
更新后的表如下所示:
Scan (扫描): Scan 允许在多个行上对指定属性进行迭代。Scan 操作的语法如下:
Delete (删除): Delete 操作用于从表中删除一行。Delete 通过 Table.delete 执行。HBase 不会修改数据,因此通过创建名为 tombstones 的新标记来处理 Delete 操作。这些 tombstones,以及没用的价值,都在重大的压实中清理干净。使用 Delete 命令的语法如下:
下面是一个删除特定单元格的例子:
删除表的所有单元格:使用 “deleteall” 命令,可以删除一行中所有单元格。下面给出是 deleteall 命令的语法:
这里是使用“deleteall”命令删除 emp 表 row1 的所有单元的一个例子。
使用 Scan 命令验证表。表被删除后的快照如下:
HBase读写流程
HBase使用MemStore和StoreFile存储对表的更新。数据在更新时首先写入HLog和MemStore。MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到Flush队列,由单独的线程Flush到磁盘上,成为一个StoreFile。与此同时,系统会在Zookeeper中记录一个CheckPoint,表示这个时刻之前的数据变更已经持久化了。当系统出现意外时,可能导致MemStore中的数据丢失,此时使用HLog来恢复CheckPoint之后的数据。
StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定阈值后,就会进行一次合并操作,将对同一个key的修改合并到一起,形成一个大的StoreFile。当StoreFile的大小达到一定阈值后,又会对 StoreFile进行切分操作,等分为两个StoreFile。
写操作
1、Client通过Zookeeper的调度,向RegionServer发出写数据请求,在Region中写数据。
2、数据被写入Region的MemStore,直到MemStore达到预设阈值。
3、MemStore中的数据被Flush成一个StoreFile。
4、随着StoreFile文件的不断增多,当其数量增长到一定阈值后,触发Compact合并操作,将多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除。
5、StoreFiles通过不断的Compact合并操作,逐步形成越来越大的StoreFile。
6、单个StoreFile大小超过一定阈值后,触发Split操作,把当前Region Split成2个新的Region。父Region会下线,新Split出的2个子Region会被HMaster分配到相应的RegionServer上,使得原先1个Region的压力得以分流到2个Region上。
可以看出HBase只有增添数据,所有的更新和删除操作都是在后续的Compact历程中举行的,使得用户的写操作只要进入内存就可以立刻返回,实现了HBase I/O的高机能。
读操作
1、Client访问Zookeeper,查找-ROOT-表,获取.META.表信息。
2、从.META.表查找,获取存放目标数据的Region信息,从而找到对应的RegionServer。
3、通过RegionServer获取需要查找的数据。
4、Regionserver的内存分为MemStore和BlockCache两部分,MemStore主要用于写数据,BlockCache主要用于读数据。读请求先到MemStore中查数据,查不到就到BlockCache中查,再查不到就会到StoreFile上读,并把读的结果放入BlockCache。
5、寻址过程:client-->Zookeeper-->-ROOT-表-->.META.表-->RegionServer-->Region-->client
Hive
架构
Hive 是建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具用来数据提取转化加载(ETL),这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言,称为 HQL,它允许熟悉 SQL 的用户查询数据。同时,这个语言也允许熟悉 MapReduce 开发者的开发自定义的 mapper 和 reducer 来处理内建的 mapper 和 reducer 无法完成的复杂的分析工作。
Hive 的结构可以分为以下几部分:
① 用户接口: 包括 CLI, Client, WUI
②元数据存储: 通常是存储在关系数据库如 mysql, derby 中
③ 解释器、编译器、优化器、执行器
④ Hadoop : 用 HDFS 进行存储,利用 MapReduce 进行计算
① 用户接口主要有三个:CLI,Client 和 WUI。其中最常用的是 CLI,Cli 启动的时候,会同时启动一个 Hive 副本。Client 是 Hive 的客户端,用户连接至 Hive Server。在启动 Client 模式的时候,需要指出 Hive Server 所在节点,并且在该节点启动 Hive Server。WUI 是通过浏览器访问 Hive。
② Hive 将元数据存储在数据库中,如 mysql、derby。Hive 中的元数据包括表的名字,表的列和分区及其属性,表的属性(是否为外部表等),表的数据所在目录等。
③ 解释器、编译器、优化器完成 HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在 HDFS 中,并在随后有 MapReduce 调用执行。
④ Hive 的数据存储在 HDFS 中,大部分的查询由 MapReduce 完成(包含 * 的查询,比如 select * from tbl 不会生成 MapRedcue 任务)。
Hive和RDB异同
查询语言:由于 SQL 被广泛的应用在数据仓库中,因此,专门针对 Hive 的特性设计了类 SQL 的查询语言 HQL。熟悉 SQL 开发的开发者可以很方便的使用 Hive 进行开发。
数据存储位置:Hive 是建立在 Hadoop 之上的,所有 Hive 的数据都是存储在 HDFS 中的。而数据库则可以将数据保存在块设备或者本地文件系统中。
数据格式:Hive 中没有定义专门的数据格式,数据格式可以由用户指定,用户定义数据格式需要指定三个属性:列分隔符(通常为空格、”\t”、”\x001″)、行分隔符(”\n”)以及读取文件数据的方法(Hive 中默认有三个文件格式 TextFile,SequenceFile 以及 RCFile)。由于在加载数据的过程中,不需要从用户数据格式到 Hive 定义的数据格式的转换,因此,Hive 在加载的过程中不会对数据本身进行任何修改,而只是将数据内容复制或者移动到相应的 HDFS 目录中。而在数据库中,不同的数据库有不同的存储引擎,定义了自己的数据格式。所有数据都会按照一定的组织存储,因此,数据库加载数据的过程会比较耗时。
数据更新:由于 Hive 是针对数据仓库应用设计的,而数据仓库的内容是读多写少的。因此,Hive 中不支持对数据的改写和添加,所有的数据都是在加载的时候中确定好的。而数据库中的数据通常是需要经常进行修改的,因此可以使用 INSERT INTO ... VALUES 添加数据,使用 UPDATE ... SET 修改数据。
索引:之前已经说过,Hive 在加载数据的过程中不会对数据进行任何处理,甚至不会对数据进行扫描,因此也没有对数据中的某些 Key 建立索引。Hive 要访问数据中满足条件的特定值时,需要暴力扫描整个数据,因此访问延迟较高。由于 MapReduce 的引入, Hive 可以并行访问数据,因此即使没有索引,对于大数据量的访问,Hive 仍然可以体现出优势。数据库中,通常会针对一个或者几个列建立索引,因此对于少量的特定条件的数据的访问,数据库可以有很高的效率,较低的延迟。由于数据的访问延迟较高,决定了 Hive 不适合在线数据查询。
执行:Hive 中大多数查询的执行是通过 Hadoop 提供的 MapReduce 来实现的(类似 select * from tbl 的查询不需要 MapReduce)。而数据库通常有自己的执行引擎。
执行延迟:之前提到,Hive 在查询数据的时候,由于没有索引,需要扫描整个表,因此延迟较高。另外一个导致 Hive 执行延迟高的因素是 MapReduce 框架。由于 MapReduce 本身具有较高的延迟,因此在利用 MapReduce 执行 Hive 查询时,也会有较高的延迟。相对的,数据库的执行延迟较低。当然,这个低是有条件的,即数据规模较小,当数据规模大到超过数据库的处理能力的时候,Hive 的并行计算显然能体现出优势。
可扩展性:由于 Hive 是建立在 Hadoop 之上的,因此 Hive 的可扩展性是和 Hadoop 的可扩展性是一致的(世界上最大的 Hadoop 集群在 Yahoo!,2009年的规模在 4000 台节点左右)。而数据库由于 ACID 语义的严格限制,扩展行非常有限。目前最先进的并行数据库 Oracle 在理论上的扩展能力也只有 100 台左右。
数据规模:由于 Hive 建立在集群上并可以利用 MapReduce 进行并行计算,因此可以支持很大规模的数据;对应的,数据库可以支持的数据规模较小。
Redis
Redis是一个开源的,使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。用作数据库、缓存和消息代理。它通常被称为数据结构服务器,Redis支持存储的Value类型包括String(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型),这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
Redis具有内置复制、Lua脚本、LRU收回、事务和不同级别的磁盘上持久性,并通过Redis Sentinel和Redis群集的自动分区提供高可用性。
Redis持久化
Redis有两种持久化方案:RDB和AOF;
RDB : 按照一定的时间间隔对数据集创建基于时间点的快照。 RDB 持久化是把当前 Redis 内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。默认情况下, Redis 保存数据集快照到磁盘,名为 dump.rdb 的二进制文件。恢复时是将快照文件直接读到内存里。
AOF : 比快照方式有更好的持久化性,由于在使用 aof 持久化方式时 Redis 会将每一个收到的写命令都通过 write 函数追加到文件中 ( 默认是 appendonly.aof) 。当 Redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于 os 会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉 Redis 我们想要通过 fsync 函数强制 os 写入到磁盘的时机。
Value数据类型
Redis的Key是字符串类型,由于Key不是binary safe的字符串,所以像“my key”和“mykey\n”这样包含空格和换行的Key是不允许的。 设计Key的几条规则:
长度: Key 不要太短,会影响易读性。也不要太长,不仅仅因为占内存,而且在数据中查找这类键值的计算成本很高;
生命周期:如果是当小型 db 去使用,可能需要考虑的是数据的持久性和一致性,但只是将 Redis 作为缓存来使用的话,那么就一定要对相关的 Key 做生命周期的管理;
前缀:建议以业务名为前缀,以冒号分割来构造一定规则的 Key 名。比如业务名 : 表名 :ID ;
字符:禁止包含特殊字符,如空格、换行、单双引号及其他转义字符。
String
最常规的 set/get 操作, Value 可以是 String 也可以是数字,一般做一些复杂的计数功能的缓存。
hash
这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以 cookieId 作为 Key ,设置 30 分钟为缓存过期时间,能很好的模拟出类似 session 的效果。
list
使用 List 的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适 --- 取行情信息。就也是个生产者和消费者的场景。 LIST 可以很好的完成排队,先进先出的原则。
set
因为 set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用 JVM 自带的 Set 进行去重?因为我们的系统一般都是集群部署,使用 JVM 自带的 Set ,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
sorted set
sorted set 多了一个权重参数 score ,集合中的元素能够按 score 进行排列。可以做排行榜应用,取 TOP N 操作。
过期策略
Redis 的过期策略就是指当 Redis 中缓存的 Key 过期了, Redis 如何处理。过期策略通常有以下三种:
定时过期: 每个设置过期时间的 Key 都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期: 只有当访问一个 Key 时,才会判断该 Key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。极端情况可能出现大量的过期 Key 没有再次被访问,从而不会被清除,占用大量内存。
定期过期: 每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 Key ,并清除其中已过期的 Key 。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。
expires 字典会保存所有设置了过期时间的 Key 的过期时间数据,其中, Key 是指向键空间中的某个键的指针, Value 是该键的毫秒精度的 UNIX 时间戳表示的过期时间。键空间是指该 Redis 集群中保存的所有键。
Redis 中同时使用了惰性过期和定期过期两种过期策略。
过期策略存在的问题 :由于 Redis 定期删除是随机抽取检查,不可能扫描清除掉所有过期的 Key 并删除,然后一些 Key 由于未被请求,惰性删除也未触发。这样 Redis 的内存占用会越来越高。此时就需要内存淘汰机制 。
内存淘汰策略
Redis 的内存淘汰策略是指 Redis 用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
最大内存的设置是通过设置 maxmemory 来完成的,格式为 maxmemory bytes ,当目前使用的内存超过了设置的最大内存,就要进行内存释放了,当需要进行内存释放的时候,需要用某种策略对保存的的对象进行删除。 Redis 有六种策略。
Redis 中当内存超过限制时,按照配置的策略,淘汰掉相应的 Key-Value ,使得内存可以继续留有足够的空间保存新的数据。 Redis 确定驱逐某个键值对后,会删除这个数据并将这个数据变更消息发布到本地( AOF 持久化)和从机(主从连接)。
volatile-lru : 使用 LRU 算法进行数据淘汰,只淘汰设定了有效期的 Key ;( LRU 算法 : Least Recently Used 的缩写 , 淘汰上次使用时间最早的,且使用次数最少的 Key )
allkeys-lru : 使用 LRU 算法进行数据淘汰,所有的 Key 都可以被淘汰;
volatile-random : 随机淘汰数据,只淘汰设定了有效期的 Key ;
allkeys-random : 随机淘汰数据,所有的 Key 都可以被淘汰;
volatile-ttl : 淘汰剩余有效期最短的 Key ;
no-enviction : 不删除任意数据 ( 但 Redis 还会根据引用计数器进行释放 ), 这时如果内存不够时,会直接返回错误 。
Redis集群
Redis 在 3.0 版本之前是不支持集群的, 3.0 版本之前想要搭建 Redis 集群需要中间件来找到存值和取值的对应节点。 Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 Key-Value 时,Redis 先对 Key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 Key 都会对应一个编号在 0-16383 之间的哈希槽,Redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
Redis 集群中有多台 Redis 服务器不可避免会有服务器挂掉。 Redis 集群服务器之间通过互相的 ping-pong 判断是否节点可以连接上。如果有一半以上的节点去 ping 一个节点的时候没有回应,集群就认为这个节点宕机了。
主从结构
Redis 支持三种主从结构,分别是:
一主对一从: 常用于写请求量很大,并且需要持久化时,只在从节点开启 AOF 持久化,这样既保证了主节点的性能又保证了数据的安全性;但是当重启主节点时需要注意先断开从节点的复制关系,否则当主节点重启后由于没有持久化数据,所以主节点的数据为空,而此时从节点再同步主节点的数据就会丢失之前持久化的数据。
一主对多从: 多用于读请求很高的情况,通过读写分离把读请求交给从节点来分担主节点压力;同时对于开发中的一些危险或耗时的操作也可以在从节点上执行;弊端:当从节点过多时,会导致主节点的一份数据要发给很多从节点,所以会导致主节点负载与带宽消耗较大。
树状主从结构: 这种结构很好的解决了上面提到的从节点过多时主节点带宽消耗过大的问题,主节点把数据写给较少的从节点,然后从节点再同步给其自己的从节点。
主从复制
要实现分布式数据库的更大的存储容量和承受高并发访问量,我们会将原来集中式数据库的数据分别存储到其他多个网络节点上。 Redis 为了解决这个单一节点的问题,也会把数据复制多个副本部署到其他节点上进行复制,实现 Redis 的高可用,实现对数据的冗余备份,从而保证数据和服务的高可用。
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点 (master) ,后者称为从节点 (slave) ,数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台 Redis 服务器都是主节点;且一个主节点可以有多个从节点 ( 或没有从节点 ) ,但一个从节点只能有一个主节点,主从复制的作用:
数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
读写分离: 可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量。
高可用基石: 除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。
从节点开启主从复制,有 3 种方式:
1 、配置文件: 在从服务器的配置文件中加入:
slaveof
。
2 、启动命令: redis-server 启动命令后加入:
slaveof
。
3 、客户端命令: Redis 服务器启动后,直接通过客户端执行命令:
slaveof
,则该 Redis 实例成为从节点。
通过 info replication 命令可以看到复制的一些信息。
主从复制过程大体可以分为 3 个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段。在从节点执行 slaveof 命令后,复制过程便开始运作,下面图示可以看出复制过程大致分为 6 个过程。
主从配置之后的日志记录也可以看出这个流程。
1 、保存主节点( master )信息: 执行 slaveof 后 Redis 会打印如下日志:
2 、从节点与主节点建立网络连接
从节点( slave )内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。
从节点与主节点建立网络连接。从节点会建立一个 socket 套接字,从节点建立了一个端口为 51234 的套接字,专门用于接受主节点发送的复制命令。从节点连接成功后打印如下日志:
如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行 slaveofnoone 取消复制。关于连接失败,可以在从节点执行 info replication 查看 master_link_down_since_seconds 指标,它会记录与主节点连接失败的系统时间。从节点连接主节点失败时也会每秒打印如下日志;
3 、发送 ping 命令: 连接建立成功后从节点发送 ping 请求进行首次通信, ping 请求主要目的如下:
检测主从之间网络套接字是否可用。
检测主节点当前是否可接受处理命令。
如果发送 ping 命令后,从节点没有收到主节点的 pong 回复或者超时,比如网络超时或者主节点正在阻塞无法响应命令,从节点会断开复制连接,下次定时任务会发起重连。
从节点发送的 ping 命令成功返回, Redis 打印日志,并继续后续复制流程:
4 、权限验证 :如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证。如果验证失败复制将终止,从节点重新发起复制流程。
5 、同步数据集: 主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。
6 、命令持续复制 :当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。