数据如何产生?

使用脚本生成,模拟日志(并非真实的日志)

集群日志生成脚本

#!/bin/bash
for i in hadoop102 hadoop103; do
    echo "========== $i =========="
    ssh $i "cd /opt/module/applog/; java -jar gmall2020-mock-log-2021-01-22.jar >/dev/null 2>&1 &"
done 
# 最后一个&表示后台执行,2>&1表示 2>1>/dev/null

/dev/null:表示linux黑洞

Linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。
标准输入0:从键盘获得输入 /proc/self/fd/0
标准输出1:输出到屏幕(即控制台) /proc/self/fd/1
错误输出2:输出到屏幕(即控制台) /proc/self/fd/2

集群所有进程查看脚本

在脚本中编写:
#! /bin/bash
for i in hadoop102 hadoop103 hadoop104
do
    echo --------- $i ----------
    ssh $i "$*"
done
会执行xcall.sh 后传入的命令

tar -zxvf xxx.gz

使用Gzip 提取文件,-x是解开的意思,tar中使用-z这个参数来调用gzip;
c参数代表create(创建),x参数代表extract(解包),v参数代表verbose(详细信息),f参数代表filename(文件名),所以f后必须接文件名。

三台服务器上的集群规划

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source

LZO压缩配置

hadoop本身不支持lzo压缩,所以需要使用twitter提供的hadoop-lzo开源组件。hadoop-lzo需要依赖hadoop和lzo进行编译

  • 将编译好后的hadoop-lzo-0.4.20.jar 放入hadoop-3.1.3/share/hadoop/common
  • 同步hadoop-lzo-0.4.20.jar到hadoop103、hadoop104
  • core-site.xml增加配置支持LZO压缩
<property>
        <name>io.compression.codecs</name>
        <value>
            org.apache.hadoop.io.compress.GzipCodec,
            org.apache.hadoop.io.compress.DefaultCodec,
            org.apache.hadoop.io.compress.BZip2Codec,
            org.apache.hadoop.io.compress.SnappyCodec,
            com.hadoop.compression.lzo.LzoCodec,
            com.hadoop.compression.lzo.LzopCodec
        </value>
    </property>

    <property>
        <name>io.compression.codec.lzo.class</name>
        <value>com.hadoop.compression.lzo.LzoCodec</value>
    </property>

LZO创建索引

LZO压缩文件的可切片特性依赖于其索引,故我们需要手动为LZO压缩文件创建索引。若无索引,则LZO文件的切片只有一个。

hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /input/bigtable.lzo

安装Zookeeper

原因:要使用Kafka,就需要安装对应的Zookeeper来完成分布式协调

Zookeeper知识补充

顾名思义 zookeeper 就是动物园管理员,他是用来管 hadoop(大象)、Hive(蜜蜂)、pig(小猪)的管理员,是一个分布式的、开源的程序协调服务,是 hadoop 项目下的一个子项目。他提供的主要功能包括:配置管理、名字服务、分布式锁、集群管理

二、 ZooKeeper 的作用
1.1 配置管理

在我们的应用中除了代码外,还有一些就是各种配置。比如数据库连接等。一般我们都 是使用配置文件的方式,在代码中引入这些配置文件。当我们只有一种配置,只有一台服务 器,并且不经常修改的时候,使用配置文件是一个很好的做法,但是如果我们配置非常多, 有很多服务器都需要这个配置,这时使用配置文件就不是个好主意了。这个时候往往需要寻 找一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的 都可以获得变更。Zookeeper 就是这种服务,它使用 Zab 这种一致性协议来提供一致性。现 在有很多开源项目使用 Zookeeper 来维护配置,比如在 HBase 中,客户端就是连接一个 Zookeeper,获得必要的 HBase 集群的配置信息,然后才可以进一步操作。还有在开源的消息队列 Kafka 中,也使用 Zookeeper来维护broker的信息。在 Alibaba开源的 SOA 框架Dubbo 中也广泛的使用 Zookeeper 管理一些配置来实现服务治理。

1.2 名字服务

名字服务这个就很好理解了。比如为了通过网络访问一个系统,我们得知道对方的 IP 地址,但是 IP 地址对人非常不友好,这个时候我们就需要使用域名来访问。但是计算机是 不能是域名的。怎么办呢?如果我们每台机器里都备有一份域名到 IP 地址的映射,这个倒 是能解决一部分问题,但是如果域名对应的 IP 发生变化了又该怎么办呢?于是我们有了 DNS 这个东西。我们只需要访问一个大家熟知的(known)的点,它就会告诉你这个域名对应 的 IP 是什么。在我们的应用中也会存在很多这类问题,特别是在我们的服务特别多的时候, 如果我们在本地保存服务的地址的时候将非常不方便,但是如果我们只需要访问一个大家都 熟知的访问点,这里提供统一的入口,那么维护起来将方便得多了。

1.3 分布式锁

其实在第一篇文章中已经介绍了 Zookeeper 是一个分布式协调服务。这样我们就可以利用 Zookeeper 来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠 性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服 务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进 行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即 fail over 到另外的服务。这在很多分布式系统 中都是这么做,这种设计有一个更好听的名字叫发Leader Election(leader 选举) 。比如 HBase 的 Master 就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所 以使用的时候要比同一个进程里的锁更谨慎的使用。

1.4 集群管理

在分布式的集群中,经常会由于各种原因,比如硬件故障,软件故障,网络问题,有些 节点会进进出出。有新的节点加入进来,也有老的节点退出集群。这个时候,集群中其他机 器需要感知到这种变化,然后根据这种变化做出对应的决策。比如我们是一个分布式存储系 统,有一个中央控制节点负责存储的分配,当有新的存储进来的时候我们要根据现在集群目 前的状态来分配存储节点。这个时候我们就需要动态感知到集群目前的状态。还有,比如一 个分布式的 SOA 架构中,服务是一个集群提供的,当消费者访问某个服务时,就需要采用 某种机制发现现在有哪些节点可以提供该服务(这也称之为服务发现,比如 Alibaba 开源的 SOA 框架 Dubbo 就采用了 Zookeeper 作为服务发现的底层机制)。还有开源的 Kafka 队列就 采用了 Zookeeper 作为 Cosnumer 的上下线管理。

Zookeeper 的存储结构

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_02

一些概念

java获取最近的一个整点小时的数据 java获取前一天零点时间_数据_03

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_04

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_05

ZK 集群中的角色

zookeeper 集群中公共有三种角色,分别是leader,follower,observer。

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_06


observer没有投票权,像是临时工,只有整个集群压力过大时才会增加几个临时工来获得性能的提升。ZK 集群的选举机制

拿 3 个节点的 zk 作一个简单选举的说明:

java获取最近的一个整点小时的数据 java获取前一天零点时间_数据_07


zk 会进行多轮的投票,直到某一个节点的票数大于或等于半数以上,在 3 个节点中,总共会进行 2 轮的投票:

第一轮,每个节点启动时投票给自己,那这样 zk1,zk2,zk3 各有一票。
第二轮,每个节点投票给大于自己 myid,那这样 zk2 启动时又获得一票。加上自己给自己投的那一票。总共有 2 票。2 票大于了当前节点总数的半数,所以投票终止。zk2 当选 leader。
有的童鞋会问,zk3 呢,因为 zk2 已经当选了,投票终止了。

所以 zk2 也不会投票给 zk3 了。

当然这是一个比较简单版的选举,其实真正的选举还要比较 zxid,这个后面会讲到。

zk 选举什么时候会被触发呢?

一是启动时会被触发,二是 leader 宕机时会被触发。

上面的例子中,如果节点 2 宕机,根据规则,那获得 leader 的就应该是 zk3 了。

投票下处理规则

  • 首先对比zxid。zxid大的服务器优先作为Leader
  • 若zxid相同,比如初始化的时候,每个Server的zxid都为0,就会比较myid,myid大的选出来做Leader。

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_08

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_09

节点宕机后的数据同步流程
当 zookeeper 集群中的 Leader 宕机后,会触发新的选举,选举期间,整个集群是没法对外提供服务的。

直到选出新的 Leader 之后,才能重新提供服务。

我们重新回到 3 个节点的例子,zk1,zk2,zk3,其中 z2 为 Leader,z1,z3 为 Follower,假设 zk2 宕机后,触发了重新选举,按照选举规则,z3 当选 Leader。

这时整个集群只整下 z1 和 z3,如果这时整个集群又创建了一个节点数据,接着 z2 重启。这时 z2 的数据肯定比 z1 和 z3 要旧,那这时该如何同步数据呢。

zookeeper 是通过 ZXID 事务 ID 来确认的,ZXID 是一个长度为 64 位的数字,其中低 32 位是按照数字来递增,任何数据的变更都会导致低 32 位数字简单加 1。

高 32 位是 leader 周期编号,每当选举出一个新的 Leader 时,新的 Leader 就从本地事务日志中取出 ZXID,然后解析出高 32 位的周期编号,进行加 1,再将低 32 位的全部设置为 0。

这样就保证了每次选举新的 Leader 后,保证了 ZXID 的唯一性而且是保证递增的。

如果整个集群数据为一致的,那么所有节点的 ZXID 应该一样。所以 zookeeper 就通过这个有序的 ZXID 来确保各个节点之间的数据的一致性,带着之前的问题,如果 Leader 宕机之后,再重启后,会去和目前的 Leader 去比较最新的 ZXID,如果节点的 ZXID 比最新 Leader 里的 ZXID 要小,那么就会去同步数据。

安装Kafka 进行消息缓存

如果按照教程出现Kafka秒挂的情况,多半是/opt/module/kafka/config/server.properties中的broker.id出现重复
修改之后需要将log.dirs目录下文件全部删除
具体的路径看config下的server.properties

我的是在:cd /opt/module/kafka/data

删除当前目录下的所有文件使用:

rm -f *

Kafka 机器数量计算

Kafka机器数量=2*(峰值生产速度*副本数/100)+1

Kafka 生产队列 消费队列的读写性能测试
Kafka Producer压力测试
同时设置batch.size和 linger.ms,就是哪个条件先满足就都会将消息发送出去
Kafka需要考虑高吞吐量与延时的平衡。
Kafka Consumer压力测试
吞吐量受网络带宽和fetch-size(指定每次fetch的数据的大小)的影响。

项目经验值Kafka分区数计算
(1)创建一个只有1个分区的topic
(2)测试这个topic的producer吞吐量和consumer吞吐量。
(3)假设他们的值分别是Tp和Tc,单位可以是MB/s。
(4)然后假设总的目标吞吐量是Tt,那么分区数 = Tt / min(Tp,Tc)
例如:producer吞吐量 = 20m/s;consumer吞吐量 = 50m/s,期望吞吐量100m/s;
分区数 = 100 / 20 = 5分区 分区数一般设置为:3-10个

Kafka 知识补充

什么是 Kafka?
Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系统。 作为数据管道和消息系统使用

客户端类型
Kafka 来说客户端有两种基本类型:生产者(Producer)和消费者(Consumer)。除此之外,还有用来做数据集成的 Kafka Connect API 和流式处理的 Kafka Streams 等高阶客户端,但这些高阶客户端底层仍然是生产者和消费者API,它们只不过是在上层做了封装。

主题(Topic)和分区(Partition)
在 Kafka 中,消息以主题(Topic)来分类,每一个主题都对应一个「消息队列」,这有点儿类似于数据库中的表。但是如果我们把所有同类的消息都塞入到一个“中心”队列中,势必缺少可伸缩性,无论是生产者/消费者数目的增加,还是消息数量的增加,都可能耗尽系统的性能或存储。所以我们现在引入分区(Partition)的概念,类似“允许多修几条道”的方式对我们的主题完成了水平扩展。

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_10

flume知识补充

Flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。

它可以采集文件,socket数据包等各种形式源数据,又可以将采集到的数据输出到HDFS、hbase、hive、kafka等众多外部存储系统中。结构如下:

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_11


Flume分布式系统中最核心的角色是agent,每一个agent相当于一个数据传递员,内部有三个组件:

Source: 采集源,用于跟数据源对接,以获取数据;

Channel : angent内部的数据传输通道,用于从source将数据传递到sink。

Sink::下沉地,采集数据的传送目的,用于往下一级agent传递数据或者往最终存储系统传递数据;

数据在flume内部以Event的封装形式存在。

flume的事务控制机制:
1、source到channel
2、channel到sink

Flume多个agent串联

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_12

flume 集群规划:

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_13

S、C、S的选择

java获取最近的一个整点小时的数据 java获取前一天零点时间_数据_14

java获取最近的一个整点小时的数据 java获取前一天零点时间_数据_15

file channel :数据存储在磁盘中,可靠性高,效率低
memory channel:数据存储在内存中,可靠性差,效率高
kafka channel:数据存储在Kafka中,存储在磁盘中,可靠性高,传输效率也高

本项目中日志采集Flume的配置:

Source:taildir source 好处:断点续传,可以实时监控文件的变换

Channel: 选择Kafka channel

项目经验之Flume组件选型

1)Source

(1)Taildir Source相比Exec Source、Spooling Directory Source的优势

TailDir Source:断点续传、多目录。Flume1.6以前需要自己自定义Source记录每次读取文件位置,实现断点续传。不会丢数据,但是有可能会导致数据重复。

Exec Source可以实时搜集数据,但是在Flume不运行或者Shell命令出错的情况下,数据将会丢失。

Spooling Directory Source监控目录,支持断点续传。

(2)batchSize大小如何设置?

答:Event 1K左右时,500-1000合适(默认为100)

2)Channel

采用Kafka Channel,省去了Sink,提高了效率。KafkaChannel数据存储在Kafka里面,所以数据是存储在磁盘中。

注意在Flume1.7以前,Kafka Channel很少有人使用,因为发现parseAsFlumeEvent这个配置起不了作用。也就是无论parseAsFlumeEvent配置为true还是false,都会转为Flume Event。这样的话,造成的结果是,会始终都把Flume的headers中的信息混合着内容一起写入Kafka的消息中,这显然不是我所需要的,我只是需要把内容写入即可。

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_16

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_17

Flume拦截器

在source和channel之间,主要用来做ETL数据清洗,判断json是否完整

自定义拦截器,需要写Maven项目,alibaba的fastjson用来判断json的完整性,写完之后使用Maven打包。(记得要写全类名,不然找不到类)

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_18

Flume采集完成后,进入到Kafka,然后由Flume进行消费。如果数据有问题,可能是flume拦截器写错了,或者是zookeeper/kafka没有正确工作,需要重启。

使用& 让进程在后台运行时,会由于关闭shell窗口而使得进程终止,此时需要使用nohup命令,进行不间断地运行。

java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_19

日志采集Flume启动停止脚本

#! /bin/bash

case $1 in
"start"){
        for i in hadoop102 hadoop103
        do
                echo " --------启动 $i 采集flume-------"
                ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/file-flume-kafka.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log1.txt 2>&1  &"
        done
};;	
"stop"){
        for i in hadoop102 hadoop103
        do
                echo " --------停止 $i 采集flume-------"
                ssh $i "ps -ef | grep file-flume-kafka | grep -v grep |awk  '{print \$2}' | xargs -n1 kill -9 "
        done

};;
esac

消费Flume的配置

java获取最近的一个整点小时的数据 java获取前一天零点时间_hadoop_20


本项目选择FileChannel

java获取最近的一个整点小时的数据 java获取前一天零点时间_java获取最近的一个整点小时的数据_21


java获取最近的一个整点小时的数据 java获取前一天零点时间_Source_22


HDFS Sink

java获取最近的一个整点小时的数据 java获取前一天零点时间_hadoop_23


时间拦截器解决的是:零点漂移问题,即前一天产生的日志传到hdfs/服务器时已超过零点,此时应该根据日志中产生时的时间戳来进行日志的划分。

Flume 时间戳拦截器(解决零点漂移问题)

由于Flume默认会用Linux系统时间,作为输出到HDFS路径的时间。如果数据是23:59分产生的。Flume消费Kafka里面的数据时,有可能已经是第二天了,那么这部门数据会被发往第二天的HDFS路径。我们希望的是根据日志里面的实际时间,发往HDFS的路径,所以下面拦截器作用是获取日志中的实际时间。
解决的思路:拦截json日志,通过fastjson框架解析json,获取实际时间ts。将获取的ts时间写入拦截器header头,header的key必须是timestamp因为Flume框架会根据这个key的值识别为时间,写入到HDFS。

HDFS sink控制小文件的生成

#控制生成的小文件
a1.sinks.k1.hdfs.rollInterval = 10
a1.sinks.k1.hdfs.rollSize = 134217728
a1.sinks.k1.hdfs.rollCount = 0

rollInterval
默认值:30
hdfs sink间隔多长将临时文件滚动成最终目标文件,单位:秒;
如果设置成0,则表示不根据时间来滚动文件;
注:滚动(roll)指的是,hdfs sink将临时文件重命名成最终目标文件,并新打开一个临时文件来写入数据;

rollSize
默认值:1024
当临时文件达到该大小(单位:bytes)时,滚动成目标文件;
如果设置成0,则表示不根据临时文件大小来滚动文件;

rollCount
默认值:10
当events数据达到该数量时候,将临时文件滚动成目标文件;
如果设置成0,则表示不根据events数据来滚动文件;

遇到hadoop集群无法关闭的情况

最简单的方法是 重启虚拟机
或者是使用ps -ef找到hadoop相关进程,然后kill掉