Hadoop 部署与运维

本文把 Hadoop 从概念到落地的内容合在一处:先用一节理清 HDFS、YARN、MapReduce 各自的角色,再给出两套完全不同的装法——用 Ambari + HDP 做向导式安装,或者用官方 tar 包手动安装(本地模式、单节点伪分布式、完全分布式 HA),最后是 JDK 11 升级踩到的坑和日常运维排错。

两套装法的操作系统层面准备工作是相同的,所以先集中写在「环境准备」一节,后面两章都从这里开始。HDFS 的 shell 命令细节见后面的 HDFS Shell 操作 一节;集群疑难问题和参数调优另见 大数据集群运维常见问题排查内存与 CPU 调优

概念速览

动手之前先把名词理顺,后面配置文件里的每一项基本都能对应到下面某个角色。

大数据与它的四个特征

大数据(Big Data)指的是无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,需要新的处理模式才能从中获得决策力、洞察发现力和流程优化能力。它有四个常被提到的特征:

  1. Volume(大量)
  2. Velocity(高速)
  3. Variety(多样):既有结构化数据,也有非结构化数据
  4. Value(低价值密度):单条数据的价值不高,如何快速把有价值的数据「提纯」出来,是大数据场景下的核心难题

Hadoop 是什么

HadoopApache 基金会开发的一套分布式系统基础架构,主要解决海量数据的存储分析计算两个问题。广义上说的 Hadoop 往往指的不是这几个进程,而是围绕它形成的整个 Hadoop 生态圈。

它被广泛使用的几个原因:

  1. 高可靠性:底层维护多个数据副本,某个计算元素或存储出现故障也不会丢数据
  2. 高扩展性:在集群间分配任务和数据,可以方便地扩展到数以千计的节点
  3. 高效性:在 MapReduce 的思想下并行工作,以加快任务处理速度
  4. 高容错性:能够自动将失败的任务重新分配

发行版本与版本演进

三大主流发行版本是 ApacheClouderaHortonworksApache Hadoop 是原始版本,CDHHDP 两个商业发行版在 Cloudera 与 Hortonworks 合并后也已经合并。

hadoop1x2x3x区别

  • 1.x:MapReduce 同时负责资源调度和计算
  • 2.x:拆出 YARN 专门做资源调度,MapReduce 只负责计算
  • 3.x:主要组成没有变化

HDFS

Hadoop Distributed File SystemHDFS)是一个分布式文件系统,非 HA 部署下有三类角色:

  • NameNode (nn):存储文件的元数据,如文件名、目录结构、文件属性(生成时间、副本数、文件权限),以及每个文件的块列表和块所在的 DataNode
  • DataNode (dn):在本地文件系统存储文件块数据以及块数据的校验和
  • Secondary NameNode (2nn):定期做 checkpoint——把 NameNode 的 fsimage 和 editlog 拉过来合并成新的 fsimage 再推回去,目的是控制 editlog 的长度、缩短 NN 重启时回放日志的时间。它不是热备:NN 挂了它顶不上,手里的 fsimage 也总是落后于 NN

如果按 HA 结构部署,就不再需要 Secondary NameNode,取而代之的是:

  • StandbyNameNodeNameNode 的备用节点,主节点出问题后主备倒换
  • JournalNode:主备 NameNode 之间共享数据的桥梁。NameNode 把元数据变动实时写入 JournalNodeStandbyNameNode 再实时读出来应用到自身,从而保持两边元数据同步

YARN

Yet Another Resource NegotiatorYARN)是 Hadoop 的资源管理器,有两类角色:

  • ResourceManager(RM):全局资源管理器,集群里只有一个(HA 下一主一备),负责整个系统的资源管理和分配,包括处理客户端请求、启动并监控 ApplicationMaster、监控 NodeManager、资源的分配与调度等。它主要由调度器(Scheduler)和应用程序管理器(ApplicationsManager)两个组件构成
  • NodeManager(NM):负责单个节点上 CPU 与内存资源的使用,接收并处理来自 ApplicationMasterContainer 启动、停止等请求,管理本节点上 Container 的整个生命周期,并定时向 ResourceManager 汇报本节点资源使用情况和各 Container 的运行状态。它只管理 Container 本身,不关心 Container 里跑的是什么任务

MapReduce 与三者关系

MapReduce 把计算过程分成两个阶段:Map 阶段并行处理输入数据,Reduce 阶段对 Map 的结果做汇总。

mapreduceyarnhdfs

生态体系一览

大数据生态体系

  1. Sqoop:用于在 HadoopHive 与传统数据库(MySQLOracle 等)之间传递数据,既能把关系库的数据导入 HDFS,也能把 HDFS 的数据导回关系库
  2. Flume:高可用、高可靠的分布式海量日志采集、聚合和传输系统,支持在日志系统中定制各类数据发送方
  3. Kafka:高吞吐量的分布式发布订阅消息系统
  4. Spark:流行的开源大数据内存计算框架,可以基于 Hadoop 上存储的数据做计算
  5. Flink:同为内存计算框架,用于实时计算的场景更多
  6. Oozie:管理 Hadoop 作业(job)的工作流调度系统
  7. HBase:分布式的、面向列的开源数据库,适合非结构化数据存储
  8. Hive:基于 Hadoop 的数据仓库工具,把结构化数据文件映射成一张表并提供类 SQL 查询,SQL 会被翻译成 MapReduce 任务执行,学习成本低,适合数据仓库的统计分析
  9. ZooKeeper:面向大型分布式系统的可靠协调系统,提供配置维护、名字服务、分布式同步、组服务等能力

环境准备

以下操作所有节点都要做,两种装法都以它为起点。部分服务器交付时可能已经做过,检查一遍即可。

全文用到两组实验机,命令里的主机名按自己的环境替换:

章节 主机名 IP
方式一(Ambari + HDP) hdp01 / hdp02 / hdp03 192.168.100.101 / .102 / .103
方式二(手动部署) hadoop01 / hadoop02 / hadoop03 192.168.2.241 / .242 / .243

关闭 selinux、防火墙与 swap

1
2
3
4
5
6
7
8
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
grep SELINUX= /etc/selinux/config | grep -v "#"
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
grep SELINUX= /etc/selinux/config | grep -v "#"
setenforce 0
systemctl stop firewalld
systemctl disable firewalld

时间同步

能连外网的集群不用管这一步。内网环境挑一台机器(这里用 hdp01,192.168.100.101)做 ntp 服务端,其余节点指向它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yum -y install ntp

# 服务端 192.168.100.101
cat >/etc/ntp.conf<<eof
server 127.127.1.0
fudge 127.127.1.0 stratum 10
eof

# 其余节点
cat >/etc/ntp.conf<<eof
server 192.168.100.101
eof

# 所有机器
systemctl enable --now ntpd.service
ntpq -p

主机名与网络

按实际情况修改网卡配置,文件名与 NAMEDEVICE 要对应上,例如 /etc/sysconfig/network-scripts/ifcfg-ens32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static # 修改的地方
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens32 # 和文件对应
UUID=40ae9247-96ad-43d0-8c22-974897f62a64
DEVICE=ens32 # 和文件对应
ONBOOT=yes # 修改的地方
## 下方为添加的地方
IPADDR=192.168.100.101
GATEWAY=192.168.100.1
DNS1=8.8.8.8
DNS2=114.114.114.114

改完重启网络、写 hosts、设主机名:

1
2
3
4
5
6
7
8
9
10
11
12
service network restart

cat >/etc/hosts<<eof
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.100.101 hdp01
192.168.100.102 hdp02
192.168.100.103 hdp03
eof

hostnamectl set-hostname hdp01 # 或者直接改 /etc/hostname

建立互信

每个节点生成密钥,再对所有节点(含自己)分发公钥:

1
2
3
4
5
6
rm -rf ~/.ssh
ssh-keygen -f ~/.ssh/id_rsa -N '' -t rsa -q -b 2048

ssh-copy-id -i ~/.ssh/id_rsa.pub hdp01
ssh-copy-id -i ~/.ssh/id_rsa.pub hdp02
ssh-copy-id -i ~/.ssh/id_rsa.pub hdp03

安装 JDK

下载地址:https://www.oracle.com/java/technologies/downloads/ 。这里用软链接 current 指向真实版本,后面换 JDK 只改软链接,不用动环境变量。

1
2
3
4
5
6
7
8
9
10
11
mkdir /opt/bigdata/java
tar zxf jdk-*.tar.gz -C /opt/bigdata/java
cd /opt/bigdata/java
ln -s jdk* current
cat >/etc/profile.d/java_env.sh<<eof
export JAVA_HOME=/opt/bigdata/java/current
export CLASSPATH=.:\$JAVA_HOME/jre/lib/rt.jar:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar
export PATH=\$JAVA_HOME/bin:\$PATH
eof
source /etc/profile
java -version

用 Ambari 安装时,JDK 和数据库都可以交给 Ambari 自动下载,这一步以及后面的 MySQL 安装都可以跳过;只有需要自定义版本时才手动装。

方式一:Ambari + HDP 向导式部署

Ambari 负责把 HDP 里的各个组件推到所有节点上安装并统一管理,适合快速拉起一整套生态。这里用的是 hdp-3.1.5 的本地离线包、jdk8 最新版,数据库选 MySQL。

⚠️ 注:Hortonworks 被 Cloudera 收购后,HDP 与 Ambari 的公开仓库已经关闭,新版本需要订阅账号才能下载,本节流程仅适用于手上已有离线包的情况。新集群更建议用原生 Apache 版本(见「方式二」)。

准备本地 yum 源

把四个离线 tar 包解开,生成指向本地目录的 repo 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
mkdir HDP3.1.5
cd HDP3.1.5;
install_dirname=`pwd`
cat >ambari.repo<<eof
[HDP-3.1-repo-1]
name=HDP-3.1-repo-1
baseurl=file://$install_dirname/HDP/centos7/3.1.5.0-152

path=/
enabled=1
gpgcheck=0
[HDP-3.1-GPL-repo-1]
name=HDP-3.1-GPL-repo-1
baseurl=file://$install_dirname/HDP-GPL/centos7/3.1.5.0-152

path=/
enabled=1
gpgcheck=0
[HDP-UTILS-1.1.0.22-repo-1]
name=HDP-UTILS-1.1.0.22-repo-1
baseurl=file://$install_dirname/HDP-UTILS/centos7/1.1.0.22

path=/
enabled=1
gpgcheck=0
[ambari-2.7.5-repo-1]
name=ambari-2.7.5-repo-1
baseurl=file://$install_dirname/ambari/centos7/2.7.5.0-72

path=/
enabled=1
gpgcheck=0
eof

cp ambari.repo /etc/yum.repos.d/
echo "unzip packege, may take a little time"
tar -zxf ../ambari-*-centos7.tar.gz -C $install_dirname
tar -zxf ../HDP-3*-centos7-rpm.tar.gz -C $install_dirname
tar -zxf ../HDP-GPL-3.1.5.0-*-gpl.tar.gz -C $install_dirname
tar -zxf ../HDP-UTILS-1.1.0.22-centos7.tar.gz -C $install_dirname

yum clean all
yum makecache
yum repolist

安装 MySQL

数据库只装在一台机器上即可(这里是 192.168.100.101)。yum 源 rpm 包可在 https://dev.mysql.com/downloads/repo/yum/ 找到:

1
2
3
4
rpm -ivh mysql*.noarch.rpm
yum install -y mysql-server mysql
systemctl enable --now mysqld
# 临时密码在 /var/log/mysqld.log

Ambari 自带的建库 SQL 在 8.0 上有语法问题,这里先用 mysql-5.7,包可在 https://downloads.mysql.com/archives/community/ 下载:

1
2
3
4
5
6
7
8
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-community-server-5.7.36-1.el7.x86_64.rpm
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-community-client-5.7.36-1.el7.x86_64.rpm
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-community-common-5.7.36-1.el7.x86_64.rpm
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-community-libs-5.7.36-1.el7.x86_64.rpm
rpm -ivh mysql-community-common-5.7.36-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.36-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.36-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.36-1.el7.x86_64.rpm

初始化 MySQL

/var/log/mysqld.log 里的临时密码进入初始化向导:

1
/usr/bin/mysql_secure_installation

向导会依次问这些问题,按下面的答法即可:

  1. Enter password for user root:填日志里的临时密码,随后被要求设置新密码(validate_password 组件开启时密码强度不能太低)
  2. Change the password for root ?y,再输入两遍新密码
  3. Remove anonymous users?y,匿名账号只适合测试环境
  4. Disallow root login remotely?n,Ambari 要从其他节点连过来;生产环境应改成 y 并单独建远程账号
  5. Remove test database and access to it?y
  6. Reload privilege tables now?y

准备 JDBC 驱动

需要 mysql-connector-java.jar,下载链接 https://dev.mysql.com/downloads/connector/j/ 。也可以直接 yum 装,但版本受限,推荐下 jar 包自己放:

1
2
yum install -y mysql-connector-java
# 会生成文件 /usr/share/java/mysql-connector-java.jar

创建 ambari / hive / oozie 库与账号

如果密码策略挡住了简单密码,可以先临时放宽(生产环境不建议):

1
2
3
SHOW VARIABLES LIKE 'validate_password%';
set global validate_password_policy=LOW;
set global validate_password_length=4;

Ambari 自身的库。因为 Ambari 会分别以 %localhost、主机名三种形式连接,三个授权都要建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create database ambari character set utf8mb4;
CREATE USER 'ambari'@'%' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'ambari'@'%';
CREATE USER 'ambari'@'localhost' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'ambari'@'localhost';
CREATE USER 'ambari'@'hdp01' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'ambari'@'hdp01';
FLUSH PRIVILEGES;

use ambari;
source /var/lib/ambari-server/resources/Ambari-DDL-MySQL-CREATE.sql;
show tables;
use mysql;
select host,user from user where user='ambari';

Hive 的库(用 MySQL 时必须先手工建好,PostgreSQL 内嵌模式下 Ambari 会自动处理):

1
2
3
4
5
6
7
8
9
CREATE DATABASE hive character set utf8mb4;
use hive;
CREATE USER 'hive'@'%' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'hive'@'%';
CREATE USER 'hive'@'localhost' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'hive'@'localhost';
CREATE USER 'hive'@'hdp01' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'hive'@'hdp01';
FLUSH PRIVILEGES;

Oozie 的库,同样的套路:

1
2
3
4
5
6
7
8
9
CREATE DATABASE oozie character set utf8mb4;
use oozie;
CREATE USER 'oozie'@'%' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'oozie'@'%';
CREATE USER 'oozie'@'localhost' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'oozie'@'localhost';
CREATE USER 'oozie'@'hdp01' IDENTIFIED BY '<CHANGE_ME>';
GRANT ALL PRIVILEGES ON *.* TO 'oozie'@'hdp01';
FLUSH PRIVILEGES;

安装 ambari-server 与 ambari-agent

管理节点(192.168.100.101)装 server:

1
2
3
yum -y install ambari-server
ambari-server setup # 交互式初始化,见下一节
ambari-server start

所有节点装 agent:

1
2
yum -y install ambari-agent
systemctl start ambari-agent

ambari-server setup 交互过程

下面是选用自定义 JDK + 外部 MySQL 时的完整交互,注意最后的 DDL 提示:选 MySQL 时必须自己去数据库里 source 一遍建表 SQL(上一节已做),内嵌 PostgreSQL 则会自动完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
[root@hdp01 ~]# ambari-server setup
Using python /usr/bin/python
Setup ambari-server
Checking SELinux...
SELinux status is 'enabled'
SELinux mode is 'permissive'
WARNING: SELinux is set to 'permissive' mode and temporarily disabled.
OK to continue [y/n] (y)? y
Customize user account for ambari-server daemon [y/n] (n)? y
Enter user account for ambari-server daemon (root):ambari
Adjusting ambari-server permissions and ownership...
Checking firewall status...
Checking JDK...
Do you want to change Oracle JDK [y/n] (n)? y
[1] Oracle JDK 1.8 + Java Cryptography Extension (JCE) Policy Files 8
[2] Custom JDK
==============================================================================
Enter choice (1): 2
WARNING: JDK must be installed on all hosts and JAVA_HOME must be valid on all hosts.
WARNING: JCE Policy files are required for configuring Kerberos security. If you plan to use Kerberos,please make sure JCE Unlimited Strength Jurisdiction Policy Files are valid on all hosts.
Path to JAVA_HOME: /usr/java/default
Validating JDK on Ambari Server...done.
Check JDK version for Ambari Server...
JDK version found: 8
Minimum JDK version is 8 for Ambari. Skipping to setup different JDK for Ambari Server.
Checking GPL software agreement...
Completing setup...
Configuring database...
Enter advanced database configuration [y/n] (n)? y
Configuring database...
==============================================================================
Choose one of the following options:
[1] - PostgreSQL (Embedded)
[2] - Oracle
[3] - MySQL / MariaDB
[4] - PostgreSQL
[5] - Microsoft SQL Server (Tech Preview)
[6] - SQL Anywhere
[7] - BDB
==============================================================================
Enter choice (3):
Hostname (localhost):
Port (3306):
Database name (ambari):
Username (ambari):
Enter Database Password (ambari):
Configuring ambari database...
Should ambari use existing default jdbc /usr/share/java/mysql-connector-java.jar [y/n] (y)? y
Configuring remote database connection properties...
WARNING: Before starting Ambari Server, you must run the following DDL directly from the database shell to create the schema: /var/lib/ambari-server/resources/Ambari-DDL-MySQL-CREATE.sql
Proceed with configuring remote database connection properties [y/n] (y)? y
Extracting system views...
ambari-admin-2.7.5.0.72.jar
....
Ambari repo file doesn't contain latest json url, skipping repoinfos modification
Adjusting ambari-server permissions and ownership...
Ambari Server 'setup' completed successfully.

网页向导安装

server 与 agent 都起来后,就可以在页面上完成剩下的工作。登录地址 http://hdp01:8080 ,默认管理员账号 admin,密码 admin(登录后立刻改掉)。向导过程中报错就看 /var/log/ambari-server//var/log/ambari-agent/ 下的日志。

HDP1
HDP2
HDP3
HDP4
HDP5
HDP6
HDP7
HDP8
HDP9
HDP10
HDP11
HDP12
HDP13

HDP 版本定义文件

向导里选择 stack 版本时会用到版本定义文件,离线安装可以把它保存成 HDP-3.1.5.0-152.xml 后手动指定,其中的 baseurl 换成自己的本地源地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0"?>
<repository-version xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="version_definition.xsd">
<release>
<type>STANDARD</type>
<stack-id>HDP-3.1</stack-id>
<version>3.1.5.0</version>
<build>152</build>
<compatible-with>3\.\d+\.\d+\.\d+</compatible-with>
<release-notes>http://example.com</release-notes>
<display>HDP-3.1.5.0</display>
</release>
<manifest>
<service id="ACCUMULO-170" name="ACCUMULO" version="1.7.0"/>
<service id="ATLAS-200" name="ATLAS" version="2.0.0"/>
<service id="DRUID-0121" name="DRUID" version="0.12.1"/>
<service id="HDFS-311" name="HDFS" version="3.1.1"/>
<service id="YARN-311" name="YARN" version="3.1.1"/>
<service id="MAPREDUCE2-311" name="MAPREDUCE2" version="3.1.1"/>
<service id="HBASE-216" name="HBASE" version="2.1.6"/>
<service id="HIVE-310" name="HIVE" version="3.1.0"/>
<service id="KAFKA-200" name="KAFKA" version="2.0.0"/>
<service id="KNOX-100" name="KNOX" version="1.0.0"/>
<service id="OOZIE-431" name="OOZIE" version="4.3.1"/>
<service id="PIG-0160" name="PIG" version="0.16.0"/>
<service id="RANGER-120" name="RANGER" version="1.2.0"/>
<service id="RANGER_KMS-120" name="RANGER_KMS" version="1.2.0"/>
<service id="SPARK2-232" name="SPARK2" version="2.3.2"/>
<service id="SQOOP-147" name="SQOOP" version="1.4.7"/>
<service id="STORM-121" name="STORM" version="1.2.1"/>
<service id="TEZ-091" name="TEZ" version="0.9.1"/>
<service id="ZEPPELIN-080" name="ZEPPELIN" version="0.8.0"/>
<service id="ZOOKEEPER-346" name="ZOOKEEPER" version="3.4.6"/>
</manifest>
<available-services/>
<repository-info>
<os family="redhat7">
<package-version>3_1_5_0_*</package-version>
<repo>
<baseurl>https://archive.cloudera.com/p/HDP/centos7/3.x/updates/3.1.5.0</baseurl>
<repoid>HDP-3.1</repoid>
<reponame>HDP</reponame>
<unique>true</unique>
</repo>
<repo>
<baseurl>https://archive.cloudera.com/p/HDP-GPL/centos7/3.x/updates/3.1.5.0</baseurl>
<repoid>HDP-3.1-GPL</repoid>
<reponame>HDP-GPL</reponame>
<unique>true</unique>
<tags>
<tag>GPL</tag>
</tags>
</repo>
<repo>
<baseurl>https://archive.cloudera.com/p/HDP-UTILS-1.1.0.22/repos/centos7</baseurl>
<repoid>HDP-UTILS-1.1.0.22</repoid>
<reponame>HDP-UTILS</reponame>
<unique>false</unique>
</repo>
</os>
</repository-info>
</repository-version>

方式二:官方 tar 包手动部署

这一节用原生 Apache Hadoop,按 本地运行模式 → 单节点伪分布式 → 完全分布式 HA 的顺序逐步加码:三种模式的软件安装完全一样,区别只在配置文件和启动哪些进程。实验机是 hadoop01-hadoop03(192.168.2.241-243),/etc/hosts 相应写成:

1
2
3
4
5
6
7
8
cat >/etc/hosts<<eof
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.2.241 hadoop01
192.168.2.242 hadoop02
192.168.2.243 hadoop03
eof

安装 Hadoop 与本地运行模式

下载地址 https://archive.apache.org/dist/hadoop/common/ 。解压后不改任何配置就是本地运行模式:没有任何守护进程,读写本地文件系统,用来验证包和环境变量是否正确。配置文件单独放到 /etc/hadoop/conf 并用 HADOOP_CONF_DIR 指过去,升级换包时配置不用搬:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
useradd hadoop
mkdir -p /opt/bigdata/hadoop
wget --no-check-certificate https://archive.apache.org/dist/hadoop/common/hadoop-3.3.1/hadoop-3.3.1.tar.gz

tar -zxvf hadoop-*.tar.gz -C /opt/bigdata/hadoop
cd /opt/bigdata/hadoop
ln -s hadoop-* current
chown -R hadoop:hadoop /opt/bigdata/hadoop

mkdir /etc/hadoop # 配置文件路径
cp -r /opt/bigdata/hadoop/current/etc/hadoop /etc/hadoop/conf
chown -R hadoop:hadoop /etc/hadoop

cat >/etc/profile.d/hadoop_env.sh<<-EOF
#HADOOP_HOME
export HADOOP_HOME=/opt/bigdata/hadoop/current
export HADOOP_MAPRED_HOME=\${HADOOP_HOME}
export HADOOP_COMMON_HOME=\${HADOOP_HOME}
export HADOOP_HDFS_HOME=\${HADOOP_HOME}
export HADOOP_YARN_HOME=\${HADOOP_HOME}
export CATALINA_BASE=\${HTTPFS_CATALINA_HOME}
export HADOOP_CONF_DIR=/etc/hadoop/conf
export HTTPFS_CONFIG=/etc/hadoop/conf
export PATH=\$PATH:\$HADOOP_HOME/bin:\$HADOOP_HOME/sbin
EOF

source /etc/profile
hadoop version

验证本地运行模式

用自带的 wordcount 例子跑一遍,输入输出都在本地目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
su - hadoop
cd /opt/bigdata/hadoop/current
mkdir wcinput

cat > /opt/bigdata/hadoop/current/wcinput/demo.txt <<-EOF
Linux Unix windows
hadoop Linux spark
hive hadoop Unix
MapReduce hadoop Linux hive
windows hadoop spark
EOF

hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount wcinput wcoutput

结果:

1
2
3
4
5
6
7
8
9
10
11
# ls wcoutput/
part-r-00000 _SUCCESS

# cat wcoutput/part-r-00000
Linux 3
MapReduce 1
Unix 2
hadoop 4
hive 2
spark 2
windows 2

输出目录里两个文件的含义:_SUCCESS 是任务完成标识,表示执行成功;part-r-00000 才是结果文件,其中带 m 标识的是 mapper 输出、带 r 标识的是 reduce 输出,00000reduce task(分区)编号——有 N 个 reducer 就有 part-r-00000part-r-0000(N-1),job id 根本不出现在输出文件名里。

单节点伪分布式

伪分布式和完全分布式的步骤基本一致,差别只在配置文件,所以先在单节点上把各个进程的启动顺序走通。节点 192.168.2.231(hadoop231),前置操作和上面的安装已完成,配置基本用默认值,只把 fs.defaultFS 指到本机。

修改 core-site.xml

/etc/hadoop/conf/core-site.xml,RPC 默认端口 8020:

1
2
3
4
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop231</value>
</property>

进程日志都在 /opt/bigdata/hadoop/current/logs/,起不来就先看日志。以下操作都用 hadoop 用户执行。

启动 HDFS 组件

格式化 NameNode 后启动它。格式化只在初次部署时做一次,重复格式化会导致 DataNode 认不出集群(见后文排错):

1
2
3
4
5
6
$ su - hadoop
$ cd /opt/bigdata/hadoop/current/bin
$ hdfs namenode -format
$ hdfs --daemon start namenode
$ jps|grep NameNode
27319 NameNode

打开 192.168.2.231:9870 就是 NameNode 的状态页面,此时 DataNode 还没起,页面上没有数据:

hadoop01

再启动 SecondaryNameNode(后面做 HA 的完全分布式集群不需要这个服务)和 DataNode

1
2
3
4
5
6
7
$ hdfs --daemon start secondarynamenode
$ jps|grep SecondaryNameNode
29705 SecondaryNameNode

$ hdfs --daemon start datanode
$ jps|grep DataNode
3876 DataNode

刷新 192.168.2.231:9870,可以看到已有活动的节点和容量信息:

hadoop02

启动 YARN 与 JobHistoryServer

先起 ResourceManager

1
2
3
$ yarn --daemon start resourcemanager
$ jps|grep ResourceManager
4726 ResourceManager

192.168.2.231:8088 是 ResourceManager 的状态页面。此时可用内存、CPU 资源数和活跃节点数都是 0,因为还没有 NodeManager

yarn界面

补上 NodeManager

1
2
3
$ yarn --daemon start nodemanager
$ jps|grep NodeManager
8853 NodeManager

再看页面,资源数就有了:

yarn界面2

最后是 JobHistoryServer,页面在 192.168.2.231:19888:

1
2
3
$ mapred --daemon start historyserver
$ jps|grep JobHistoryServer
1027 JobHistoryServer

JobHistory

让 MapReduce 跑在 YARN 上

以上是最简配置,此时 mapreduce 的运行环境仍然是 local(本地),要让它提交到 yarn,需要改两个文件。

mapred-site.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>

<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>

<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>

<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
</configuration>

yarn-site.xml

1
2
3
4
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>

两个文件都改完再重启 ResourceManagerNodeManager,漏改 yarn-site.xml 会导致任务失败(见后文排错):

1
2
3
4
5
yarn --daemon stop  resourcemanager
yarn --daemon stop nodemanager

yarn --daemon start resourcemanager
yarn --daemon start nodemanager

在 HDFS 上跑一次 wordcount

和本地模式的例子相同,只是输入数据先传到 HDFS,任务也会提交到 YARN:

1
2
3
4
5
6
7
8
9
10
11
12
13
cat > /tmp/demo.txt <<-EOF
Linux Unix windows
hadoop Linux spark
hive hadoop Unix
MapReduce hadoop Linux hive
windows hadoop spark
EOF

hadoop fs -mkdir /demo
hadoop fs -put /tmp/demo.txt /demo
hadoop jar /opt/bigdata/hadoop/current/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /demo /output

hadoop fs -ls /output

结果与本地模式一致:

1
2
3
4
5
6
7
8
$ hadoop fs -text /output/part-r-00000
Linux 3
MapReduce 1
Unix 2
hadoop 4
hive 2
spark 2
windows 2

这次任务能在 JobHistoryServer 里看到记录:

有历史记录

完全分布式(HDFS HA)

所有节点先完成「环境准备」与上面「安装 Hadoop 与本地运行模式」的全部操作,然后按下面的规划改配置。

节点规划

一般情况下的规划原则:

  1. NameNode 服务要独立部署
  2. DataNodeNodeManager 建议部署在同一台服务器上
  3. ResourceManager 要独立部署
  4. JobHistoryServer 一般和 ResourceManager 放一起
  5. ZooKeeperQuorumPeerMain)和 JournalNode 集群可以放一起
  6. zkfcDFSZKFailoverController)负责对 NameNode 做资源仲裁,必须和 NameNode 运行在同一台机器上

当前只有三台机器,按下表安排:

节点 进程
hadoop01 QuorumPeerMain, JournalNode, NameNode, DFSZKFailoverController, DataNode, NodeManager
hadoop02 QuorumPeerMain, JournalNode, NameNode, DFSZKFailoverController, DataNode, NodeManager
hadoop03 QuorumPeerMain, JournalNode, DataNode, ResourceManager, NodeManager, JobHistoryServer

安装 ZooKeeper

官方下载页 https://zookeeper.apache.org/releases.html#download 。所有节点执行:

1
2
3
4
5
6
wget --no-check-certificate https://archive.apache.org/dist/zookeeper/zookeeper-3.8.0/apache-zookeeper-3.8.0-bin.tar.gz

mkdir -p /opt/bigdata/zookeeper
tar -zxvf apache-zookeeper-*-bin.tar.gz -C /opt/bigdata/zookeeper
cd /opt/bigdata/zookeeper
ln -s apache-zookeeper-*-bin current

所有节点写配置并建目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat > /opt/bigdata/zookeeper/current/conf/zoo.cfg <<-EOF
tickTime=2000
initLimit=20
syncLimit=10
dataDir=/opt/bigdata/zookeeper/current/data
dataLogDir=/opt/bigdata/zookeeper/current/dataLogDir
clientPort=2181
quorumListenOnAllIPs=true
server.1=hadoop01:2888:3888
server.2=hadoop02:2888:3888
server.3=hadoop03:2888:3888
admin.serverPort=8081
EOF

mkdir -p /opt/bigdata/zookeeper/current/data
mkdir -p /opt/bigdata/zookeeper/current/dataLogDir

三个端口的用途:2181client 端提供服务,2888 集群内机器通信,3888 选举 leader

myid 每个节点不同,要和 zoo.cfgserver.N 的编号对应,最后统一授权:

1
2
3
4
5
echo 1 > /opt/bigdata/zookeeper/current/data/myid # hadoop01
echo 2 > /opt/bigdata/zookeeper/current/data/myid # hadoop02
echo 3 > /opt/bigdata/zookeeper/current/data/myid # hadoop03

chown -R hadoop:hadoop /opt/bigdata/zookeeper

启动并确认角色,日志在 /opt/bigdata/zookeeper/current/logs

1
2
3
4
5
6
$ cd /opt/bigdata/zookeeper/current/bin
$ ./zkServer.sh start
$ jps
23097 QuorumPeerMain

$ ./zkServer.sh status

修改 Hadoop 配置文件

配置文件路径 /etc/hadoop/conf所有节点都要改成一致。

core-site.xmlfs.defaultFS 指向 HA 的逻辑名 bigdata 而不是某台具体主机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<configuration>

<property>
<name>fs.defaultFS</name>
<value>hdfs://bigdata</value>
</property>

<property>
<name>hadoop.tmp.dir</name>
<value>/var/tmp/hadoop-${user.name}</value>
</property>


<property>
<name>ha.zookeeper.quorum</name>
<value>hadoop01,hadoop02,hadoop03</value>
</property>

<property>
<name>fs.trash.interval</name>
<value>60</value>
</property>
</configuration>

hdfs-site.xml,两个 NameNode(nn1/nn2)、三个 JournalNode、自动故障转移,以及两块盘的数据目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<configuration>
<property>
<name>dfs.nameservices</name>
<value>bigdata</value>
</property>

<property>
<name>dfs.ha.namenodes.bigdata</name>
<value>nn1,nn2</value>
</property>


<property>
<name>dfs.namenode.rpc-address.bigdata.nn1</name>
<value>hadoop01:9000</value>
</property>


<property>
<name>dfs.namenode.rpc-address.bigdata.nn2</name>
<value>hadoop02:9000</value>
</property>


<property>
<name>dfs.namenode.http-address.bigdata.nn1</name>
<value>hadoop01:50070</value>
</property>

<property>
<name>dfs.namenode.http-address.bigdata.nn2</name>
<value>hadoop02:50070</value>
</property>


<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://hadoop01:8485;hadoop02:8485;hadoop03:8485/bigdata</value>
</property>


<property>
<name>dfs.ha.automatic-failover.enabled.bigdata</name>
<value>true</value>
</property>

<property>
<name>dfs.client.failover.proxy.provider.bigdata</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>


<property>
<name>dfs.journalnode.edits.dir</name>
<value>/data1/hadoop/dfs/jn</value>
</property>


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


<property>
<name>dfs.ha.fencing.methods</name>
<value>shell(/bin/true)</value>
</property>


<property>
<name>dfs.namenode.name.dir</name>
<value>file:///data1/hadoop/dfs/name,file:///data2/hadoop/dfs/name</value>
<final>true</final>
</property>


<property>
<name>dfs.datanode.data.dir</name>
<value>file:///data1/hadoop/dfs/data,file:///data2/hadoop/dfs/data</value>
<final>true</final>
</property>


<property>
<name>dfs.block.size</name>
<value>134217728</value>
</property>


<property>
<name>dfs.permissions</name>
<value>true</value>
</property>


<property>
<name>dfs.permissions.supergroup</name>
<value>supergroup</value>
</property>


<property>
<name>dfs.hosts</name>
<value>/etc/hadoop/conf/hosts</value>
</property>


<property>
<name>dfs.hosts.exclude</name>
<value>/etc/hadoop/conf/hosts-exclude</value>
</property>

</configuration>

mapred-site.xmlJobHistoryServer 放在 hadoop03:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<configuration>

<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>

<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop03:10020</value>
</property>

<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop03:19888</value>
</property>

</configuration>

yarn-site.xmlResourceManager 在 hadoop03,末尾两项按单节点可分配的内存与核数填:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<configuration>

<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop03</value>
</property>

<property>
<name>yarn.resourcemanager.scheduler.address</name>
<value>hadoop03:8030</value>
</property>

<property>
<name>yarn.resourcemanager.resource-tracker.address</name>
<value>hadoop03:8031</value>
</property>

<property>
<name>yarn.resourcemanager.address</name>
<value>hadoop03:8032</value>
</property>

<property>
<name>yarn.resourcemanager.admin.address</name>
<value>hadoop03:8033</value>
</property>

<property>
<name>yarn.resourcemanager.webapp.address</name>
<value>hadoop03:8088</value>
</property>

<property>
<name>yarn.nodemanager.aux-services</name>
<!-- aux-services.<name>.class 只对已经列进这个清单的名字生效;
漏了 spark_shuffle,下面那条 class 就是死配置,external shuffle service 根本没开 -->
<value>mapreduce_shuffle,spark_shuffle</value>
</property>

<property>
<name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name>
<value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>

<property>
<name>yarn.nodemanager.aux-services.spark_shuffle.class</name>
<value>org.apache.spark.network.yarn.YarnShuffleService</value>
</property>
<!-- 另外还要把 spark-<version>-yarn-shuffle.jar 放进 NodeManager 的 classpath,否则 NM 起不来 -->

<property>
<name>yarn.nodemanager.local-dirs</name>
<value>file:///data1/hadoop/yarn/local,file:///data2/hadoop/yarn/local</value>
</property>

<property>
<name>yarn.nodemanager.log-dirs</name>
<value>file:///data1/hadoop/yarn/logs,file:///data2/hadoop/yarn/logs</value>
</property>


<property>
<description>Classpath for typical applications.</description>
<name>yarn.application.classpath</name>
<value>
$HADOOP_CONF_DIR,
$HADOOP_COMMON_HOME/*,$HADOOP_COMMON_HOME/lib/*,
$HADOOP_HDFS_HOME/*,$HADOOP_HDFS_HOME/lib/*,
$HADOOP_MAPRED_HOME/*,$HADOOP_MAPRED_HOME/lib/*,
$HADOOP_YARN_HOME/*,$HADOOP_YARN_HOME/lib/*,
$HADOOP_HOME/share/hadoop/common/*, $HADOOP_COMMON_HOME/share/hadoop/common/lib/*,
$HADOOP_HOME/share/hadoop/hdfs/*, $HADOOP_HOME/share/hadoop/hdfs/lib/*,
$HADOOP_HOME/share/hadoop/mapreduce/*, $HADOOP_HOME/share/hadoop/mapreduce/lib/*,
$HADOOP_HOME/share/hadoop/yarn/*, $HADOOP_YARN_HOME/share/hadoop/yarn/lib/*,
$HIVE_HOME/lib/*, $HIVE_HOME/lib_aux/*
</value>
</property>

<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>20480</value>
</property>

<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>8</value>
</property>

</configuration>

hdfs-site.xmldfs.hosts 指向的白名单文件也要建出来:

1
2
3
4
5
cat >/etc/hadoop/conf/hosts<<-EOF
hadoop01
hadoop02
hadoop03
EOF

创建数据目录

配置里用到 /data1/data2 两块盘,所有节点都建好并授权:

1
2
3
4
mkdir -p /data1/hadoop
mkdir -p /data2/hadoop
chown -R hadoop:hadoop /data1/hadoop
chown -R hadoop:hadoop /data2/hadoop

按顺序初始化并启动

首次启动的顺序不能随意调整,都用 hadoop 用户执行(su hadoop)。

  1. 启动 ZooKeeper 集群(所有节点)
1
2
3
4
$ cd /opt/bigdata/zookeeper/current/bin
$ ./zkServer.sh start
$ jps
2182 QuorumPeerMain
  1. 在 ZooKeeper 中格式化 HA 状态节点(仅 hadoop01)
1
hdfs zkfc -formatZK
  1. 启动 JournalNode 集群(所有节点),会生成 /data1/hadoop/dfs/jn
1
2
3
$ hdfs --daemon start journalnode
$ jps
15187 JournalNode
  1. 格式化并启动主 NameNode(hadoop01),clusterId 可自行指定
1
2
3
4
$ hdfs namenode -format -clusterId bigdataserver
$ hdfs --daemon start namenode
$ jps
11724 NameNode
  1. 备节点同步元数据后启动(hadoop02)
1
2
3
4
$ hdfs namenode -bootstrapStandby
$ hdfs --daemon start namenode
$ jps
1880 NameNode
  1. 启动 zkfc(先 hadoop01,再 hadoop02)
1
2
3
$ hdfs --daemon start zkfc
$ jps
1888 DFSZKFailoverController
  1. 启动 DataNode(所有节点)
1
2
3
$ hdfs --daemon start datanode
$ jps
2880 DataNode
  1. 启动 YARN 与 JobHistoryServer
1
2
3
4
5
6
7
$ yarn --daemon start resourcemanager  # hadoop03
$ yarn --daemon start nodemanager # 所有节点
$ mapred --daemon start historyserver # hadoop03
$ jps
7986 ResourceManager
8246 NodeManager
8405 JobHistoryServer

验证

重复前面「在 HDFS 上跑一次 wordcount」的步骤即可,输出内容相同。页面上应该能看到一主一备的 NameNode、三个活跃的 DataNode 以及刚提交的任务:

主namenode

备namenode

cluster_node

提交的任务

升级到 JDK 11

版本信息:jdk11、hadoop-3.4.0-arm、hive 4.0。因为 JDK 是用软链接 /opt/bigdata/java/current 引入的,升级本身只是把软链接换到新版本,但会连带出下面两个问题。

1
2
3
4
5
6
7
[root@test-63 java]# pwd
/opt/bigdata/java
[root@test-63 java]# ls -l
total 8
lrwxrwxrwx 1 root root 30 Jun 5 10:41 current -> /opt/bigdata/java/jdk-11.0.23
drwxr-xr-x 8 10143 10143 4096 Dec 16 2021 jdk1.8.0_321
drwxr-xr-x 8 root root 4096 Jun 5 10:19 jdk-11.0.23

ByteBuffer.position 报错

报错:java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer

这是 JDK 8 里没有该返回类型的方法导致的,升级到 JDK 11 即可解决,也正是这次升级的起因。

Hive 在 JDK 11 下无法启动

报错:Exception in thread "main" java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader

两种处理方式:

  1. 直接升级到 hive 4.0 及以上版本,原生支持 JDK 11
  2. 还在用 hive 3.x 的话,只能让 Hive 单独使用 JDK 8。Hive 读的是 Hadoop 侧的 Java 信息,手动改环境变量没用,仍然走 JDK 11,所以要在 /opt/bigdata/hive/current/bin/hive 开头插一段:进入时把 current 软链接临时切到 JDK 8,退出时再切回 JDK 11,用 HIVE_JAVA_SWITCHED 防止递归调用,trap 保证异常退出也能恢复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
if [ -z "$HIVE_JAVA_SWITCHED" ]; then
# 保存当前的软连接目标
CURRENT_JAVA=/opt/bigdata/java/jdk-11.0.23

# 创建指向 java2 的软连接
ln -sfn /opt/bigdata/java/jdk1.8.0_321 /opt/bigdata/java/current

# 检查软连接是否成功更新
if [ "$(readlink -f /opt/bigdata/java/current)" != "/opt/bigdata/java/jdk1.8.0_321" ]; then
echo "Failed to update the Java soft link to java2"
exit 1
fi

# 设置 JAVA_HOME 和 PATH
export JAVA_HOME=/opt/bigdata/java/current
export PATH=$JAVA_HOME/bin:$PATH
export HIVE_HOME=/opt/bigdata/hive/current
# 标记已切换 Java 版本,防止递归
export HIVE_JAVA_SWITCHED=true

# 定义恢复函数
function restore_java {
ln -sfn /opt/bigdata/java/jdk-11.0.23 /opt/bigdata/java/current

if [ "$(readlink -f /opt/bigdata/java/current)" != "/opt/bigdata/java/jdk-11.0.23" ]; then
echo "Failed to restore the Java soft link to the original version"
exit 1
fi
}

# 捕捉各种退出信号以便恢复 Java 软链接
trap restore_java EXIT HUP INT TERM

# 启动 Hive 并捕获其退出状态
$HIVE_HOME/bin/hive "$@"
HIVE_EXIT_STATUS=$?

# 明确调用恢复函数,恢复软链接
restore_java

# 退出脚本时返回 Hive 的退出状态
exit $HIVE_EXIT_STATUS
fi


# 下面是原有的 Hive 启动脚本内容...

⚠️ 注:这个软链接切换的做法是全局生效的,同一台机器上如果有其他作业正在用 current,会被短暂影响。多用户环境更建议给 Hive 单独设置 HADOOP_OPTS/JAVA_HOME,或者直接升级 Hive 版本。

HDFS Shell 操作

shell 操作

hadoop fs 等同于 hdfs dfs

HDFS 上的 Shell 操作命令跟 Linux 下的 Shell 命令基本类似,相关参数也基本相同

hdfs dfs -help : 查看命令的帮助信息

主要不同的点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## 从本地上传 /localdemo10.tar.gz 文件到 hdfs 的 /logs/demo
hadoop fs -copyFromLocal /localdemo10.tar.gz /logs/demo
hadoop fs -put /localdemo10.tar.gz /logs/demo # 与上面相同,一般用 put
hadoop fs -moveFromLocal /localdemo10.tar.gz /logs/demo # 移动,会删除本地文件

## 下载 HDFS 文件到本地系统磁盘 /data
hadoop fs -copyToLocal /logs/demo/localdemo10.tar.gz /data
hadoop fs -get /logs/demo/localdemo10.tar.gz /data # 与上面相同,一般用 get

## 删除
hadoop fs -rm -r -skipTrash /tmp/hive_test # -skipTrash 直接删除,不放入回收站
hadoop fs -expunge # 让 Trash 里超过 fs.trash.interval 的旧 checkpoint 永久删除,并新建一个 checkpoint;刚删的、没到期的文件不会被清掉。要立刻清空得 hadoop fs -rm -r -skipTrash /user/<user>/.Trash

## 追加 本地 aa1.log 内容 到 hdfs /logs/aa.log (hdfs 上内容不能修改,只能追加)
hadoop fs -appendToFile aa1.log /logs/aa.log

用户操作 将 root 加入 supergroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
groupadd supergroup

usermod -a -G supergroup root

## hdfs 用户是 supergroup
su - hdfs -s /bin/bash -c "hdfs dfsadmin -refreshUserToGroupsMappings"


## HA 下要对每个 NameNode 分别刷新(用户组映射由 NameNode 本地 OS 解析,和 NodeManager 无关;
## groupadd/usermod 也要在 NameNode 所在机器上做):
hdfs dfsadmin -fs hdfs://hadoop01:8020 -refreshUserToGroupsMappings
hdfs dfsadmin -fs hdfs://hadoop02:8020 -refreshUserToGroupsMappings

## 顺便确认一下哪台是 active(这条只查状态,不刷新任何缓存)
hdfs haadmin -getAllServiceState

## 验证 root 用户
hadoop fs -ls /user/hdfs

这两段示例背后的权限模型

这里有两个高频误区值得说清楚,否则照抄很容易得出”有时行有时不行”的结论。

一、进了 supergroup 就等于超级用户,后面的 chmod 是多余动作。 dfs.permissions.superusergroup 的默认值就是 supergroup,凡是属于这个组的用户,HDFS 的权限检查整体跳过——不看目录的 rwx 位,也不看属主。所以下面第二种写法里的 hadoop fs -chmod 770 /user/test 其实不起决定作用,真正让它能访问的是组身份。反过来说,如果你只想给某个用户开某个目录,就不该把他塞进 supergroup,那是给了全局权限,用 chown/chmod/ACL 才对。

二、用户和组的解析发生在 NameNode 端,不在客户端。 这是最容易白折腾半天的一点:NameNode 拿到请求里的用户名后,用自己本机id/getent group 去查这个用户属于哪些组(默认的 ShellBasedUnixGroupsMapping 就是 fork 一个 id -Gn)。所以

  • 在客户端机器上 groupadd supergroup && usermod -a -G supergroup root无效的,NameNode 根本不看客户端的 /etc/group
  • 必须在每个 NameNode 所在的机器上建组、加用户,HA 下两台都要做;
  • 改完之后还要 -refreshUserToGroupsMappings 刷掉 NameNode 里的缓存(缓存时长由 hadoop.security.groups.cache.secs 控制,默认 300 秒,不刷就得等它过期)。

想确认 NameNode 到底认为某个用户属于哪些组,直接问它:

1
hdfs groups root          # 由 NameNode 回答,比在客户端 id root 可靠

生产环境更常见的做法是把组解析接到 LDAP(hadoop.security.group.mapping 换成 LdapGroupsMapping),这样就不用在每台 NameNode 上维护本地组了。

另外一种

1
2
3
4
5
6
7
8
groupadd  supergroup
usermod -a -G supergroup root

su - test -s /bin/bash -c "hdfs dfsadmin -refreshUserToGroupsMappings"

su - test -s /bin/bash -c "hadoop fs -chmod 770 /user/test"

su - root -s /bin/bash -c "hadoop fs -ls /user/test"

运维与排错

这一节汇总日常会用到的启停命令、日志位置与端口,以及部署过程中实际踩到的两个坑。

服务启停与日志位置

手动部署下没有一键脚本,单个进程用下面三条命令管理(stop 换掉 start 即可):

1
2
3
hdfs   --daemon start namenode|secondarynamenode|datanode|journalnode|zkfc
yarn --daemon start resourcemanager|nodemanager
mapred --daemon start historyserver

排查问题时常看的几个位置:

  • Hadoop 各进程日志:/opt/bigdata/hadoop/current/logs/
  • ZooKeeper 日志:/opt/bigdata/zookeeper/current/logs
  • Ambari 日志:/var/log/ambari-server//var/log/ambari-agent/
  • MySQL 初始临时密码:/var/log/mysqld.log

常用 Web 与服务端口

组件 端口 说明
NameNode 9870 3.x 默认的 HTTP 页面端口,本文 HA 配置改成了 50070
NameNode 8020 / 9000 RPC,默认 8020,本文 HA 配置写成 9000
JournalNode 8485 主备之间同步 edits
ResourceManager 8088 YARN 页面
JobHistoryServer 19888 历史任务页面
ZooKeeper 2181 / 2888 / 3888 客户端 / 集群通信 / 选举
Ambari Server 8080 管理页面

NameNode 与 DataNode 的 VERSION 冲突

服务器重启后又执行了一次 hdfs namenode -format,NameNode 生成了新的 clusterID,而 DataNode 数据目录里的 VERSION 还是旧的,两边对不上,DataNode 起不来:

hadoop问题1

hadoop问题定位1

处理办法是备份后删除 DataNode 的 data、tmp 目录再启动。所以格式化命令只在初次部署时执行,之后重启集群直接 start 即可。

改用 YARN 后忘记同步 yarn-site.xml

mapred-site.xml 已经把 mapreduce.framework.name 改成 yarn,但 yarn-site.xml 里没有配 yarn.nodemanager.aux-services,任务提交后报错:

hadoop问题2

补上配置并重启 ResourceManagerNodeManager,重跑任务即恢复正常:

问题2