桂林理工大学
计算机科学与工程学院
大数据系统实习
实习报告
专 业 | 软件工程 |
班 级 | 2021级1班 |
学 号 | 3212052051354 |
学 生 姓 名 | 朱扬宇 |
指 导 教 师 | 张大明 |
实 习 时 间 | 2023~2024学年第2学期 |
2024.6.24至2024.7.12 | |
实 习 成 绩 |
目录
基础实习报告
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第十三章.大数据课程综合实验案例 日期 2024年 7月1日
一、实验目的:
(1)熟悉 Linux、MySQL、Hadoop、HBase、Hive、R、Eclipse等系统和软件的安装和使用。 (2)了解大数据处理的基本流程。
(3)熟悉数据预处理方法。
(4)熟悉在不同类型数据库之间进行数据相互导入导出。
(5)熟悉使用R 语言进行可视化分析。
(6)熟悉使用Eclipse 编写Java 程序操作HBase 、Hive和 MySQL。
二、实验环境:
(1)VMare虚拟机,Unbuntu22.04镜像文件
(2)MySQL8。
(3)Hadoop:3.1.3。
(4)HBase:2.2.2。
(5)Hive:3.1.2。
(6)R:3.2.3。
(7)Eclipse:3.8。
三、实验内容:
(写出主要的内容)
下面需要把user.zip 进行解压缩,建立一个用于运行本案例的目录 bigdatacase, 执行以 下命令:
cd /usr/local
ls
sudo mkdir bigdatacase
#这里会提示你输入当前用户(本教程是hadoop用户名)的密码
#下面给hadoop用户赋予针对bigdatacase目录的各种操作权限
sudo chown -R hadoop:hadoop ./bigdatacase
cd bigdatacase
#下面创建一个dataset目录,用于保存数据集
mkdir dataset
#下面就可以解压缩user.zip文件
cd ~ //表示进入hadoop用户的目录
cd 下载
ls
unzip user.zip -d /usr/local/bigdatacase/dataset
cd /usr/local/bigdatacase/dataset
ls
执行下面命令取出前面5条记录看一下:
head -5 raw_user.csv
1.删除文件第一行记录(即字段名称)
cd /usr/local/bigdatacase/dataset
#下面删除raw_user中的第1行
sed -i '1d' raw_user.csv
#上面的1d表示删除第1行,同理,3d表示删除第3行,nd表示删除第n行
#下面删除small_user中的第1行
sed -i '1d' small_user.csv
#下面再用head命令去查看文件的前5行记录,就看不到字段名称这一行了
head -5 raw_user.csv
head -5 small_user.csv
2.对字段进行预处理
cd /usr/local/bigdatacase/dataset
vim pre_deal.sh
上面使用vim 编辑器新建了一个pre_deal.sh 脚本文件,在这个脚本文件中加入以下代码:
执行pre_deal.sh脚本文件,对 small_user.csv 进行数据预处理,命令如下:
cd /usr/local/bigdatacase/dataset
bash ./pre_deal.sh small_user.csv user_table.txt
可以查看生成的 user_table.txt,但是,不要直接打开,因为,文件过大,直接打开会出错,可 以使用head命令查看前10行数据:
head -10 user_table.txt
1. 启动HDFS
cd /usr/local/hadoop
./sbin/start-dfs.sh
jps
2.把 user_table.txt 上传到 HDFS 中
cd /usr/local/hadoop
./bin/hdfs dfs -mkdir -p /bigdatacase/dataset
./bin/hdfs dfs -put /usr/local/bigdatacase/dataset/user_table.txt /bigdatacase/dataset
./bin/hdfs dfs -cat /bigdatacase/dataset/user_table.txt | head -10
3.在 Hive上创建数据库
sudo systemctl start mysql #可以在Linux的任何目录下执行该命令
cd /usr/local/hive
./bin/hive #启动Hive
hive> create database dblab;
hive> use dblab;
4.创建外部表
Hive>CREATE EXTERNAL TABLE dblab.bigdata_user (
id INT,
uid STRING,
item_id STRING,
behavior_type INT,
item_category STRING,
visit_date DATE,
province STRING
)
COMMENT 'Welcome to xmudblab!'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE
LOCATION '/bigdatacase/dataset';
5.查询数据
hive> use dblab; //使用dblab数据库
hive> show tables; //显示数据库中所有表
hive> show create table bigdata_user; //查看bigdata_user表的各种属性;
查看表的简单结构:hive> desc bigdata_user;
查询相关数据:
hive> select * from bigdata_user limit 10;
hive> select behavior_type from bigdata_user limit 10;
1 简单查询分析
hive> select behavior_type from bigdata_user limit 10; #查看前10位用户对商品的行为
查询前20位用户购买商品时的时间和商品的种类:
hive> select visit_date, item_category from bigdata_user limit 20;
有时在表中查询可以利用嵌套语句,如果列名太复杂可以设置该列的别名,以简化操作 的难度,举例如下:
hive> select e.bh, e.it from (select behavior_type as bh, item_category as it from bigdata_user) as e limit 20;
2 查询条数统计分析
①. 用聚合函数count()计算表内有多少行数据
hive> select count(*) from bigdata_user;
②. 在函数内部加上distinct, 查出 uid 不重复的数据有多少条
hive> select count(distinct uid) from bigdata_user;
③. 查询不重复的数据有多少条(为了排除客户刷单情况)
hive>select count(*) from (select uid,item_id,behavior_type,item_category,visit_date,province from bigdata_user group by uid,item_id,behavior_type,item_category,visit_date,province having count(*)=1)a;
3 关键字条件查询分析
①. 以关键字的存在区间为条件的查询
查询2014年12月10日~13日有多少人浏览了商品。
hive> select count(*) from bigdata_user where behavior_type='1' and visit_date<'2014-12-13' and visit_date>'2014-12-10';
以月的第n 天为统计单位,依次显示第n 天网站卖出去的商品的个数
hive> select count(distinct uid), day(visit_date) from bigdata_user where behavior_type='4' group by day(visit_date);
②. 关键字赋予给定值为条件,对其他数据进行分析
hive> select count(*) from bigdata_user where province='江西' and visit_date='2014-12-12' and behavior_type='4';
4 根据用户行为分析
hive> select count(*) from bigdata_user where visit_date='2014-12-11'and behavior_type='4';#查询有多少用户在2014-12-11购买了商品
hive> select count(*) from bigdata_user where visit_date ='2014-12-11';#查询有多少用户在2014-12-11点击了该店
hive> select count(*) from bigdata_user where uid=10001082 and visit_date='2014-12-12';#查询用户10001082在2014-12-12点击网站的次数
hive> select count(*) from bigdata_user where visit_date='2014-12-12';#查询所有用户在这一天点击该网站的次数
hive> select uid from bigdata_user where behavior_type='4' and visit_date='2014-12-12' group by uid having count(behavior_type='4')>5;#查询某一天在该网站购买商品超过5次的用户id
5 用户实时查询分析
hive> create table scan(province STRING,scan INT) COMMENT 'This is the search of bigdataday' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' STORED AS TEXTFILE;#创建新的数据表进行存储
hive> insert overwrite table scan select province,count(behavior_type) from bigdata_user where behavior_type='1' group by province;#导入数据
hive> select * from scan;#显示结果
1. Hive 预操作
①. 创建临时表user_action
hive> create table dblab.user_action(id STRING,uid STRING, item_id STRING, behavior_type STRING, item_category STRING, visit_date DATE, province STRING) COMMENT 'Welcome to XMU dblab! ' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' STORED AS TEXTFILE;
cd /usr/local/hadoop
./bin/hdfs dfs -ls /user/hive/warehouse/dblab.db/
②.将 bigdata_user 表中的数据插入user_action
hive> INSERT OVERWRITE TABLE dblab.user_action select * from dblab.bigdata_user;
hive> select * from user_action limit 10;
2. 使用Java API将数据从 Hive 导入MySQL
①将前面生成的临时表数据从 Hive 导 入MySQL
登录mysql
mysql -u root -p
创建数据库
mysql> show databases; #显示所有数据库
mysql> create database dblab; #创建dblab数据库
mysql> use dblab; #使用数据库
注意:使用下面命令查看数据库的编码:
mysql>show variables like "char%";
mysql> CREATE TABLE `dblab`.`user_action` (`id` varchar(50),`uid` varchar(50),`item_id` varchar(50),`behavior_type` varchar(10),`item_category` varchar(50), `visit_date` DATE,`province` varchar(20)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> exit;
在 Hadoop 的配置文件core-site.xml 中添加以下配置信息:
<property>
<name>hadoop.proxyuser.hadoop.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.hadoop.groups</name>
<value>*</value>
</property>
开启 Hadoop 以后,在目录/usr/local/hive 下执行以下命令开启hiveserver2, 并且设置 默认端口为10000。
cd /usr/local/hive
./bin/hive --service hiveserver2 -hiveconf hive.server2.thrift.port=10000
启动结束后,使用如下命令查看10000号端口是否已经被占用:
sudo netstat -anp|grep 10000
启动Eclipse, 建立Java 工程,通过Build Path 添加/usr/local/hadoop/share/Hadoop/
common/lib 下的所有JAR 包,并且添加/usr/local/hive/lib 下的所有 JAR 包。编写Java 程序HivetoMySQL.java, 把数据从Hive 加载到MySQL 中 ,HivetoMySQL.java 的具体代 码如下:
import java.sql.*;
import java.sql.SQLException;
public class HivetoMySQL {
private static String driverName = "org.apache.hive.jdbc.HiveDriver";
private static String driverName_mysql = "com.mysql.cj.jdbc.Driver";
public static void main(String[] args) throws SQLException {
try {
Class.forName(driverName);
}catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
}
Connection con1 = DriverManager.getConnection("jdbc:hive2://localhost:10000/default", "hive", "hive");//后两个参数是用户名密码
if(con1 == null)
System.out.println("连接失败");
else {
Statement stmt = con1.createStatement();
String sql = "select * from dblab.user_action";
System.out.println("Running: " + sql);
ResultSet res = stmt.executeQuery(sql);
//InsertToMysql
try {
Class.forName(driverName_mysql);
Connection con2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/dblab","root","123456");
String sql2 = "insert into user_action(id,uid,item_id,behavior_type,item_category,visit_date,province) values (?,?,?,?,?,?,?)";
PreparedStatement ps = con2.prepareStatement(sql2);
while (res.next()) {
ps.setString(1,res.getString(1));
ps.setString(2,res.getString(2));
ps.setString(3,res.getString(3));
ps.setString(4,res.getString(4));
ps.setString(5,res.getString(5));
ps.setDate(6,res.getDate(6));
ps.setString(7,res.getString(7));
ps.executeUpdate();
}
ps.close();
con2.close();
res.close();
stmt.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
con1.close();
}
}
select count(*)from user_action;
2. 查 看MySQL 中 user_action 表数据
教材第221页
mysql -u root -p
mysql> use dblab;
mysql> select * from user_action limit 10;
3.使用HBase Java API把数据从本地导入HBase中
①. 启 动Hadoop集群、HBase 服务
cd /usr/local/hadoop
./sbin/start-all.sh
cd /usr/local/hbase
./bin/start-hbase.sh
②.数据准备
cd /usr/local/bigdatacase/dataset
/usr/local/hadoop/bin/hdfs dfs -get /user/hive/warehouse/dblab.db/user_action .
#将HDFS上的user_action数据复制到本地当前目录,注意'.'表示当前目录
cat ./user_action/* | head -10 #查看前10行数据
cat ./user_action/00000* > user_action.output #将00000*文件复制一份重命名为user_action.output,*表示通配符
head user_action.output #查看user_action.output前10行
③.编写数据导入程序
这里采用Eclipse 编写Java 程序实现HBase 数据导入功能,具体代码如下:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
public class ImportHBase extends Thread {
public Configuration config;
public Connection conn;
public Table table;
public Admin admin;
public ImportHBase() {
config = HBaseConfiguration.create();
// config.set("hbase.master", "master:60000");
// config.set("hbase.zookeeper.quorum", "master");
try {
conn = ConnectionFactory.createConnection(config);
admin = conn.getAdmin();
table = conn.getTable(TableName.valueOf("user_action"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
if (args.length == 0) { //第一个参数是该jar所使用的类,第二个参数是数据集所存放的路径
throw new Exception("You must set input path!");
}
String fileName = args[args.length-1]; //输入的文件路径是最后一个参数
ImportHBase test = new ImportHBase();
test.importLocalFileToHBase(fileName);
}
public void importLocalFileToHBase(String fileName) {
long st = System.currentTimeMillis();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(
fileName)));
String line = null;
int count = 0;
while ((line = br.readLine()) != null) {
count++;
put(line);
if (count % 10000 == 0)
System.out.println(count);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
table.close(); // must close the client
} catch (IOException e) {
e.printStackTrace();
}
}
long en2 = System.currentTimeMillis();
System.out.println("Total Time: " + (en2 - st) + " ms");
}
@SuppressWarnings("deprecation")
public void put(String line) throws IOException {
String[] arr = line.split("\t", -1);
String[] column = {"id","uid","item_id","behavior_type","item_category","date","province"};
if (arr.length == 7) {
Put put = new Put(Bytes.toBytes(arr[0]));// rowkey
for(int i=1;i<arr.length;i++){
put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes(column[i]),Bytes.toBytes(arr[i]));
}
table.put(put); // put to server
}
}
public void get(String rowkey, String columnFamily, String column,
int versions) throws IOException {
long st = System.currentTimeMillis();
Get get = new Get(Bytes.toBytes(rowkey));
get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));
Scan scanner = new Scan(get);
scanner.readVersions(versions);
ResultScanner rsScanner = table.getScanner(scanner);
for (Result result : rsScanner) {
final List<Cell> list = result.listCells();
for (final Cell kv : list) {
System.out.println(Bytes.toStringBinary(kv.getValueArray()) + "\t"
+ kv.getTimestamp()); // mid + time
}
}
rsScanner.close();
long en2 = System.currentTimeMillis();
System.out.println("Total Time: " + (en2 - st) + " ms");
}
}
先在 Eclipse中编写上述代码,并打包成可执行JAR 包,命名为 ImportHBase.jar; 然后在/usr/local/bigdatacase/ 目录下新建一个hbase 子目录,用来存放 ImportHBase.jar。
④数据导入
打开HBase Shell窗口
create 'user_action',{NAME=>'f1',VERSIONS=>5}
然后
hbase> truncate 'user_action'
/usr/local/hadoop/bin/hadoop jar /usr/local/bigdatacase/hbase/ImportHBase.jar ImportHBase /usr/local/bigdatacase/dataset/user_action.output
⑤. 查 看HBase 中 user_action 表数据
habse> scan 'user_action',{LIMIT=>10} #只查询前面10行
1安装R
sudo vim /etc/apt/sources.list
把文件里的原始内容清空,在文件中添加阿里云的镜像源,即把如下内容添加到文件中:
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
执行如下命令更新软件源列表:
sudo apt-get update
其次,执行如下命令安装R 语言:
sudo apt-get install r-base
系统会提示“您希望继续执行吗? [y/n]”, 可以直接输入y, 就可以顺利安装结束。
安装结束后,可以执行下面命令启动R:
R
其中,>就是R 的命令提示符,可以在后面输入R 语言命令。
最后,可以执行下面命令退出R:
>q()
2 安装依赖库
启动R 进入R 命令提示符状态,执行如下命令安装RMySQL:
> install.packages('RMySQL')
sudo apt-get install libmariadb-dev解决安装失败的bug
安装成功后继续输入R
> install.packages('RMySQL')
RMySQL 安装成功以后,执行如下命令安装绘图包ggplot2:
> install.packages('ggplot2')
> install.packages('devtools')
sudo apt-get install libssl-dev
sudo apt-get install libssh2-1-dev
sudo apt-get install libcurl4-openssl-dev
sudo apt-get install libxml2-dev
> install.packages('pkgdown')
这里需要自己下载devtools_2.4.5.tar.gz,然后再
>install.packages("~/下载/devtools_2.4.5.tar.gz", repos = NULL, type = "source")
再一次install.packages('devtools')
> devtools::install_github('taiyun/recharts')
这里我用不了,会超时,所以我直接下载本地安装
install.packages("~/下载/recharts-master", repos = NULL, type = "source")
3 可视化分析
①. 连接 MySQL,并获取数据
sudo systemctl start mysql
mysql -u root -p
mysql> use dblab;
mysql> select * from user_action limit 10;
然后,切换到刚才已经打开的R 命令提示符终端窗口,使用如下命令让R 连接到 MySQL 数据库:
>library(DBI)
>library(RMySQL)
>conn <- dbConnect(MySQL(),dbname='dblab',username='root',password='123456',host="127.0.0.1",port=3306)
>user_action <- dbGetQuery(conn,'select * from user_action')
②.分析消费者对商品的行为
首先使用summary() 函数查看 MySQL 数据 库中表user_action的字段 behavior_type的类型,命令如下:
>summary(user_action$behavior_type)
可以看出,原来的MySQL 数据中,表user_action的字段behavior_type 的类型是字符 型。这样不方便做比较,需要把behavior_type的字段类型转换为数值型,命令如下:
>summary(as.numeric(user_action$behavior_type))
接下来,用柱状图展示消费者的行为类型的分布情况,命令如下:
>library(ggplot2)
>ggplot(user_action,aes(as.numeric(behavior_type)))+geom_histogram()
③.分析销量排名前十的商品及其销量
>temp <- subset(user_action,as.numeric(behavior_type)==4) # 获取子数据集
>count <- sort(table(temp$item_category),decreasing = T) #排序
>print(count[1:10]) # 获取第1到10个排序结果
④.分析每年的哪个月份销量最大
从 MySQL 直接获取的数据中visit_date变量都是2014年,并没有划分出具体的月份, 因此,需要在数据集中增加一列关于月份的数据,命令如下:
>month <- substr(user_action$visit_date,6,7) # visit_date变量中截取月份
>user_action <- cbind(user_action,month) # user_action增加一列月份数据
用柱状图展示消费者在一年的不同月份的购买量情况,命令如下:
>ggplot(user_action,aes(as.numeric(behavior_type),col=factor(month)))+geom_histogram()+facet_grid(.~month)
⑤.分析国内哪个省份的消费者最有购买欲望
可以使用如下语句来分析国内各省份的消费者的购买情况:
>library(recharts)
>rel <- as.data.frame(table(temp$province))
>provinces <- rel$Var1
>x = c()
>for(n in provinces){
>x[length(x)+1] = nrow(subset(temp,(province==n)))
>}
>mapData <- data.frame(province=rel$Var1,count=x, stringsAsFactors=F) # 设置地图信息
>eMap(mapData, namevar=~province, datavar = ~count) #画出中国地图
- 心得体会
通过此次大数据课程综合实验案例的学习与实践,我收获颇丰,不仅在理论知识上有了进一步的巩固和深化,更在实际操作中得到了宝贵的经验和技能提升。首先,我熟悉了Linux操作系统及其相关软件的安装和使用方法。在实验过程中,通过VMware虚拟机安装Ubuntu 22.04操作系统,并逐步掌握了Linux的基本命令和操作,为后续的大数据平台搭建奠定了基础。此外,我还学习了MySQL数据库的安装、配置及使用方法,通过实践熟练掌握了数据库的基本操作,包括创建数据库、表、插入数据、查询等。其次,在大数据处理技术方面,我深入了解并掌握了Hadoop、HBase、Hive等大数据处理框架的安装与使用方法。通过实验,我能够熟练地在Hadoop上进行数据存储和处理,利用Hive进行数据的查询和分析,并在HBase中进行数据的管理和操作。这些技术的掌握,使我对大数据处理流程有了全面的认识和理解。在数据预处理方面,我学习了如何对原始数据进行清洗和转换,以满足后续数据分析的需求。通过编写Shell脚本和使用Kettle工具,我能够高效地对数据进行预处理,解决了数据冗余和不一致的问题,提高了数据的质量和可用性。在数据分析与可视化方面,通过R语言的学习和实践,我能够使用R连接MySQL数据库,获取数据并进行可视化分析。通过ggplot2等绘图包,我实现了对消费者行为的分析和展示,包括行为类型分布、销量排名前十的商品及其销量、各月份的销售情况以及各省份的消费者购买欲望等。这些可视化分析不仅提高了数据的直观性,也为数据的解读和决策提供了有力支持。此外,通过使用Eclipse编写Java程序,我掌握了如何操作HBase、Hive和MySQL等不同类型的数据库,能够实现数据在不同数据库之间的导入导出。这一技能使我能够灵活处理和管理大规模数据,提升了数据处理的效率和灵活性。总的来说,这次实验让我在理论与实践结合的过程中,全面提升了大数据处理与分析的能力。从Linux系统的基础操作,到大数据框架的搭建与使用,再到数据的预处理、分析与可视化,每一个环节都让我受益匪浅。这些知识和技能的掌握,不仅为我今后的学习和研究打下了坚实的基础,也为我在大数据领域的发展提供了宝贵的经验和实践积累。我深刻体会到大数据技术在实际应用中的重要性,未来我将继续深入学习和研究,不断提升自己的专业水平,为今后的工作和研究打下坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
第十四章有8个实验,具体实验内容如下:
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验一:熟悉常用的 Linux操作和Hadoop操作 日期 2024年7月1日
一、实验目的:
Hadoop 运行在Linux 系统上,因此,需要学习实践一些常用的Linux 命令。本实验旨在熟悉常用的Linux 操作和Hadoop 操作,为顺利开展后续其他实验奠定基础。
二、实验环境:
(1)操作系统:Linux(Ubuntu 22.04)。
(2)Hadoop 版本:3.1.3。
三、实验内容:
1.熟悉常用的 Linux操作
1)cd 命令:切换目录
(1)切换到目录 /usr/local
cd /usr/local
(2)去到目前的上层目录
cd ..
(3)回到自己的主文件夹
cd ~
2)1s命令:查看文件与目录
查看目录/usr 下的所有文件和目录。
ls /usr
3)mkdir 命令:新建目录
(1)进入/tmp目录,创建一个名为a 的目录,并查看/tmp目录下已经存在哪些目录。
cd /tmp mkdir a ls | wc -l
(2)进入/tmp 目录,创建目录al/a2/a3/a4。
mkdir -p a1/a2/a3/a4
4)rmdir 命令:删除空的目录
(1)将上面创建的目录a(在/tmp 目录下面)删除。
rmdir /tmp/a
(2)删除上面创建的目录al/a2/a3/a4 (在/tmp目录下面),并查看/tmp目录下面存在哪些目录。
rm -r a1; ls | wc -l
5)cp 命令:复制文件或目录
(1)将当前用户的主文件夹下的文件.bashrc复制到目录/usr下,并重命名为bashrcl。
cp ~/.bashrc /usr/bashrc1
(2)在目录/tmp 下新建目录 test,再把这个目录复制到/usr目录下。
mkdir /tmp/test; cp -r /tmp/test /usr
6)mv 命令:移动文件与目录,或重命名
(1)将/usr目录下的文件bashrcl移动到/usr/test目录下。
mv /usr/bashrc1 /usr/test
(2)将/usr目录下的 test目录重命名为test2。
mv /tmp/test /tmp/test2
7)rm 命令:移除文件或目录
(1)将/usr/test2 目录下的bashrcl 文件删除。
rm /usr/bashrc1
(2)将/usr目录下的test2目录删除。
rm -r /tmp/test2
8)cat命令:查看文件内容
查看当前用户主文件夹下的.bashrc 文件的内容。
cat ~/.bashrc
9)tac 命令:反向查看文件内容
反向查看当前用户主文件夹下的.bashrc 文件的内容。
tac ~/.bashrc
10)more命令:一页一页翻动查看
翻页查看当前用户主文件夹下的.bashrc 文件的内容。
more ~/.bashrc
每次enter查看内容%会增加
11)head 命令:取出前面几行
(1)查看当前用户主文件夹下.bashrc文件的内容的前20行。
head -20 ~/.bashrc
(2)查看当前用户主文件夹下.bashrc 文件的内容,后面50行不显示,只显示前面几行。
head ~/.bashrc
只查看前20行:
后面50行不显示,只显示前面几行:
12)tail命令:取出后面几行
(1)查看当前用户主文件夹下.bashrc文件的内容的最后20行。
tail -20 ~/.bashrc
(2)查看当前用户主文件夹下.bashrc文件的内容,并且只列出50行以后的数据。
tail -n +50 ~/.bashrc
最后20行:
50行之后:
13)touch 命令:修改文件时间或创建新文件
(1)在/tmp 目录下创建一个空文件 hello,并查看文件时间。
touch /tmp/hello; ls -l /tmp/hello
(2)修改 hello文件,将文件时间调整为5天前。
touch -d '5 days ago' /tmp/hello; ls -l /tmp/hello
14)chown 命令:修改文件所有者的权限
将 hello文件所有者改为root 账号,并查看属性。
sudo chown root /tmp/hello; ls -l /tmp/hello
15)find 命令:文件查找
找出主文件夹下文件名为.bashrc 的文件。
find ~ -name .bashrc
16)tar 命令:压缩命令
(1)在根目录“/”下新建文件夹test,再在根目录“/”下打包成test.tar.gz。
mkdir /test; tar -czvf test.tar.gz /test
(2)把上面的test.tar.gz 压缩包解压缩到/tmp 目录。
tar -xzvf test.tar.gz -C /tmp
17)grep 命令:查找字符串
从~/.bashrc文件中查找字符串'examples'。
grep 'examples' ~/.bashrc
18)配置环境变量
(1)在~/.bashrc 中设置,配置Java 环境变量。
echo 'export JAVA_HOME=/path/to/java' >> ~/.bashrc
(2)查看JAVA_HOME 变量的值。
source ~/.bashrc; echo $JAVA_HOME
2.熟悉常用的Hadoop 操作
(1)使用 hadoop 用户登录Linux 系统,启动 Hadoop(Hadoop的安装目录为/usr/ local/hadoop), 为hadoop 用户在HDFS 中创建用户目录/user/hadoop。
cd/usr/local/hadoop;
./sbin/start-dfs.sh ;hdfs dfs -mkdir -p /user/hadoop
(2)在HDFS 的目录/user/hadoop 下,创建test 文件夹,并查看文件列表。
hdfs dfs -mkdir test; hdfs dfs -ls
(3)将Linux 系统本地的~/.bashrc 文件上传到 HDFS 的 test 文件夹中,并查看test。
hdfs dfs -put ~/.bashrc test;hdfs dfs -ls test
(4)将HDFS 文件夹 test 复制到 Linux 系统本地文件系统的/usr/local/hadoop目录下。
hdfs dfs -get test /usr/local/hadoop
四、心得体会:
在《大数据技术基础》课程的实验一中,我体验了从安装Linux虚拟机到操作Hadoop的全过程。实验开始于安装Ubuntu系统,通过在VMware上创建虚拟机,我逐步了解了虚拟机的设置和配置方法。虽然过程中遇到了虚拟机卡住或报错的问题,但通过调整虚拟机设置和检查ISO文件的完整性,我顺利完成了安装。接下来是创建Hadoop用户和环境配置。这一步骤让我熟悉了用户管理、权限设置和软件安装的基本命令。通过创建Hadoop用户、设置密码、配置SSH无密码登录等操作,我进一步加深了对Linux系统管理的理解。尽管SSH无密码登录配置时遇到了一些权限问题,但通过修改.ssh目录和文件的权限,最终成功解决了问题。安装JDK和Hadoop是实验的核心部分。在安装JDK时,通过解压、配置环境变量等操作,我熟悉了软件安装和环境变量配置的重要性和具体步骤。接下来,通过修改Hadoop的配置文件,并格式化NameNode,我成功启动了Hadoop,并通过Web界面验证了配置的正确性。虽然在启动过程中,曾遇到Hadoop无法识别NameNode或DataNode的问题,但通过查看日志和重新格式化NameNode,问题得以解决。在熟悉Linux基本操作的过程中,通过实践cd、ls、mkdir、rmdir等命令,我掌握了目录切换、文件查看、创建和删除目录等基本操作。这些基础命令的学习,使我对Linux系统的操作有了更全面的认识。此外,通过cp、mv、rm等命令的使用,我学会了文件和目录的复制、移动及删除操作,这为后续的文件管理打下了基础。最后,实验涉及了常用Hadoop操作。在启动Hadoop后,我在HDFS中创建了用户目录,并通过创建文件夹、上传文件等操作,熟悉了HDFS的基本使用方法。虽然在操作过程中,遇到了如未找到命令和SSH无密码登录配置不成功的问题,但通过逐步排查和调整配置文件,我解决了这些问题,并顺利完成了实验。总的来说,这次实验让我从理论走向实践,对Linux系统和Hadoop有了更深入的理解和掌握。尽管过程中遇到了不少问题,但通过不断尝试和解决,我不仅提高了自己的动手能力,也积累了宝贵的经验。这次实验不仅是一次技术上的锻炼,更是对我解决问题能力的一次考验和提升。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验二:熟悉常用的HDFS操作 日期 2024年7月1日
一、实验目的:
(1)理解 HDFS 在 Hadoop 体系结构中的角色。
(2)熟练使用HDFS 操作常用的 Shell 命令。
(3)熟悉 HDFS 操作常用的Java API。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3。
(3)JDK 版本:1.8。
(4)JavaIDE:Eclipse。
三、实验内容:
1.编程实现以下指定功能,并利用Hadoop提供的Shell命令完成相同任务:
①. 向HDFS中上传任意文本文件,如果指定的文件在HDFS中已经存在,由用户指定是追加到原有文件末尾还是覆盖原有的文件;
a.shell命令
先到Hadoop主文件夹,启动Hadoop服务
cd /usr/local/hadoop
./sbin/start-dfs.sh
创建两个任意文本文件用于实验
echo "hello world" > local.txt
echo "hello hadoop" >text.txt
检查文件是否存在
hadoop fs -test -e text.txt
echo $?
上传本地文件到HDFS系统
hadoop fs -put text.txt
追加到文件末尾的指令
hadoop fs -appendToFile local.txt text.txt
查看HDFS文件的内容
hadoop fs -cat text.txt
覆盖原有文件的指令(覆盖之后再执行一遍上一步)
hadoop fs -copyFromLocal -f local.txt text.txt
b.编程实现:
程序代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Hdfs1 {
public static boolean test(Configuration conf, String path) throws IOException {
FileSystem fs = FileSystem.get(conf);
return fs.exists(new Path(path));
}
public static void copyFromLocalFile(Configuration conf, String localFilePath, String remoteFilePath) throws IOException {
FileSystem fs = FileSystem.get(conf);
Path localPath = new Path(localFilePath);
Path remotePath = new Path(remoteFilePath);
fs.copyFromLocalFile(false, true, localPath, remotePath);
fs.close();
}
public static void appendToFile(Configuration conf, String localFilePath, String remoteFilePath) throws IOException {
FileSystem fs = FileSystem.get(conf);
Path remotePath = new Path(remoteFilePath);
FileInputStream in = new FileInputStream(localFilePath);
FSDataOutputStream out = fs.append(remotePath);
byte[] data = new byte[1024];
int read;
while ((read = in.read(data)) > 0) {
out.write(data, 0, read);
}
out.close();
in.close();
fs.close();
}
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.set("fs.default.name", "hdfs://localhost:9000");
String localFilePath = "/usr/local/hadoop/local.txt";
String remoteFilePath = "/user/hadoop/local.txt";
conf.set("dfs.client.block.write.replace-datanode-on-failure.policy", "NEVER");
conf.set("dfs.client.block.write.replace-datanode-on-failure.enable", "true");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入操作方式(append 或 overwrite):");
String choice = reader.readLine();
boolean fileExists = Hdfs1.test(conf, remoteFilePath);
if (fileExists) {
System.out.println(remoteFilePath + " 已存在.");
} else {
System.out.println(remoteFilePath + " 不存在.");
}
if (!fileExists) {
Hdfs1.copyFromLocalFile(conf, localFilePath, remoteFilePath);
System.out.println(localFilePath + " 已上传至 " + remoteFilePath);
} else if ("overwrite".equals(choice)) {
Hdfs1.copyFromLocalFile(conf, localFilePath, remoteFilePath);
System.out.println(localFilePath + " 已覆盖 " + remoteFilePath);
} else if ("append".equals(choice)) {
Hdfs1.appendToFile(conf, localFilePath, remoteFilePath);
System.out.println(localFilePath + " 已追加至 " + remoteFilePath);
} else {
System.out.println("无效的选择,程序退出。");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
hdfs开始时没有local.txt
再运行一次代码
②. 从HDFS中下载指定文件,如果本地文件与要下载的文件名称相同,则自动对下载的文件重命名;
a.shell命令
if $(./bin/hdfs dfs -test -e /usr/local/hadoop/text.txt);
> then $(./bin/hdfs dfs -copyToLocal text.txt ./text1.txt);
> else $(./bin/hdfs dfs -copyToLocal text.txt ./text2.txt);
> fi
b.编程实现:
代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class HDFSFileDownloader {
public static void main(String[] args) {
String hdfsUri = "hdfs://localhost:9000";
String hdfsFilePath = "/user/hadoop/text.txt";
String localDir = "~/下载/";
Configuration conf = new Configuration();
conf.set("fs.defaultFS", hdfsUri);
conf.set("dfs.client.block.write.replace-datanode-on-failure.policy", "NEVER");
conf.set("dfs.client.block.write.replace-datanode-on-failure.enable", "true");
try {
FileSystem fs = FileSystem.get(new URI(hdfsUri), conf);
Path srcPath = new Path(hdfsFilePath);
// 获取文件名
String fileName = hdfsFilePath.substring(hdfsFilePath.lastIndexOf("/") + 1);
File localFile = new File(localDir, fileName);
// 检查本地文件是否存在,若存在则重命名(新的文件名是原文件名加上时间戳)
if (localFile.exists()) {
System.out.println("本地已存在文件: " + localFile.getAbsolutePath());
String newFileName = fileName + "_" + System.currentTimeMillis();
localFile = new File(localDir, newFileName);
System.out.println("重命名为: " + localFile.getAbsolutePath());
}
Path destPath = new Path(localFile.getAbsolutePath());
// 从HDFS下载文件
fs.copyToLocalFile(srcPath, destPath);
System.out.println("文件已下载到: " + destPath.toString());
fs.close();
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
}
}
③. 将HDFS中指定文件的内容输出到终端中;
a.shell命令
./bin/hdfs dfs -cat text.txt
b.编程实现:
程序代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
public class HDFSFileReader {
public static void main(String[] args) {
String hdfsUri = "hdfs://localhost:9000";
String hdfsFilePath = "/user/hadoop/wordfile1.txt";
Configuration conf = new Configuration();
conf.set("fs.defaultFS", hdfsUri);
try {
// 获取HDFS文件系统
FileSystem fs = FileSystem.get(new URI(hdfsUri), conf);
Path path = new Path(hdfsFilePath);
// 打开HDFS文件输入流
BufferedReader br = new BufferedReader(new InputStreamReader(fs.open(path)));
// 读取文件内容并输出到终端
String line;
System.out.println("文件内容如下:");
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 关闭输入流
IOUtils.closeStream(br);
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
④. 显示HDFS中指定的文件的读写权限、大小、创建时间、路径等信息;
a.shell命令
./bin/hdfs dfs -ls -h text.txt
b.编程实现
代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
public class HDFSFileInfo {
public static void main(String[] args) {
String hdfsUri = "hdfs://localhost:9000";
String hdfsFilePath = "/user/hadoop/text.txt";
Configuration conf = new Configuration();
conf.set("fs.defaultFS", hdfsUri);
try {
// 获取HDFS文件系统
FileSystem fs = FileSystem.get(new URI(hdfsUri), conf);
Path path = new Path(hdfsFilePath);
// 获取文件状态信息
FileStatus fileStatus = fs.getFileStatus(path);
// 获取并显示文件信息
String permissions = fileStatus.getPermission().toString();
long fileSize = fileStatus.getLen();
long modificationTime = fileStatus.getModificationTime();
String owner = fileStatus.getOwner();
String group = fileStatus.getGroup();
String filePath = fileStatus.getPath().toString();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedModificationTime = dateFormat.format(modificationTime);
System.out.println("文件信息如下:");
System.out.println("路径: " + filePath);
System.out.println("权限: " + permissions);
System.out.println("大小: " + fileSize + " 字节");
System.out.println("修改时间: " + formattedModificationTime);
System.out.println("所有者: " + owner);
System.out.println("用户组: " + group);
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
⑤. 给定HDFS中某一个目录,输出该目录下的所有文件的读写权限、大小、创建时间、路径等信息,如果该文件是目录,则递归输出该目录下所有文件相关信息;
a.shell命令
./bin/hdfs dfs -ls -R -h /user/hadoop
b.编程实现
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
public class HDFSDirectoryReader {
public static void main(String[] args) {
String hdfsUri = "hdfs://localhost:9000";
String hdfsDirPath = "/user/hadoop";
Configuration conf = new Configuration();
conf.set("fs.defaultFS", hdfsUri);
try {
// 获取HDFS文件系统
FileSystem fs = FileSystem.get(new URI(hdfsUri), conf);
Path path = new Path(hdfsDirPath);
// 递归遍历目录
listFiles(fs, path);
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 递归遍历目录并输出文件信息
private static void listFiles(FileSystem fs, Path path) throws IOException {
FileStatus[] fileStatuses = fs.listStatus(path);
for (FileStatus fileStatus : fileStatuses) {
String permissions = fileStatus.getPermission().toString();
long fileSize = fileStatus.getLen();
long modificationTime = fileStatus.getModificationTime();
String owner = fileStatus.getOwner();
String group = fileStatus.getGroup();
String filePath = fileStatus.getPath().toString();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedModificationTime = dateFormat.format(modificationTime);
System.out.println("文件信息如下:");
System.out.println("路径: " + filePath);
System.out.println("权限: " + permissions);
System.out.println("大小: " + fileSize + " 字节");
System.out.println("修改时间: " + formattedModificationTime);
System.out.println("所有者: " + owner);
System.out.println("用户组: " + group);
System.out.println();
// 如果是目录,则递归遍历
if (fileStatus.isDirectory()) {
listFiles(fs, fileStatus.getPath());
}
}
}
}
⑥. 提供一个HDFS内的文件的路径,对该文件进行创建和删除操作。如果文件所在目录不存在,则自动创建目录;
if ./bin/hdfs dfs -test -d test; then
> ./bin/hdfs dfs -touchz test/text1.txt
> ./bin/hdfs dfs -rm test/.bashrc
> else ./bin/hdfs dfs -mkdir -p test
> fi
hdfs dfs -ls -h test
编程实现:
import org.apache.hadoop.conf.Configuration;
public class HDFSFileOperations {
public static boolean test(Configuration conf, String path) throws IOException {
FileSystem fs = FileSystem.get(conf);
return fs.exists(new Path(path));
}
public static boolean mkdir(Configuration conf, String remoteDir) throws IOException {
FileSystem fs = FileSystem.get(conf);
Path dirPath = new Path(remoteDir);
boolean result = fs.mkdirs(dirPath);
fs.close();
return result;
}
public static void touchz(Configuration conf, String remoteFilePath) throws IOException {
FileSystem fs = FileSystem.get(conf);
Path remotePath = new Path(remoteFilePath);
FSDataOutputStream outputStream = fs.create(remotePath);
outputStream.close();
fs.close();
}
public static boolean rm(Configuration conf, String remoteFilePath) throws IOException {
FileSystem fs = FileSystem.get(conf);
Path remotePath = new Path(remoteFilePath);
boolean result = fs.delete(remotePath, false);
fs.close();
return result;
}
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.set("fs.default.name", "hdfs://localhost:9000");
String remoteFilePath = "/user/hadoop/test/text1.txt";
String remoteDir = "/user/hadoop/test";
try {
/* 判断路径是否存在,存在则删除,否则进行创建 */
if (HDFSFileOperations.test(conf, remoteFilePath)) {
HDFSFileOperations.rm(conf, remoteFilePath); // 删除
System.out.println("删除路径: " + remoteFilePath);
} else {
if (!HDFSFileOperations.test(conf, remoteDir)) { // 若目录不存在,则进行创建
HDFSFileOperations.mkdir(conf, remoteDir);
System.out.println("创建文件夹: " + remoteDir);
}
HDFSFileOperations.touchz(conf, remoteFilePath);
System.out.println("创建路径: " + remoteFilePath);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
未运行之前:
⑦. 提供一个HDFS的目录的路径,对该目录进行创建和删除操作。创建目录时,如果目录文件所在目录不存在则自动创建相应目录;删除目录时,由用户指定当该目录不为空时是否还删除该目录;
a.shell
先将之前的目录删掉
hadoop fs -rm -r test
创建
if hadoop fs -test -d test;
> then hadoop fs -touchz /text1.txt
> else hadoop fs -mkdir -p test
> fi
hadoop fs -ls
删除
if hadoop fs -test -d test;
> then hadoop fs -rm -r test/text1.txt;
> else $(hadoop fs -rm -r test);
> fi
b.编程实现:
import org.apache.hadoop.conf.Configuration;
public class HDFSDirectoryManager {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入HDFS路径(例如:hdfs://localhost:9000/user/hadoop/dir):");
String hdfsPath = scanner.nextLine();
System.out.println("请选择操作:1. 创建目录 2. 删除目录");
int choice = scanner.nextInt();
Configuration conf= new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9000");
conf.set("dfs.client.block.write.replace-datanode-on-failure.policy", "NEVER");
conf.set("dfs.client.block.write.replace-datanode-on-failure.enable", "true");
try {
FileSystem fs = FileSystem.get(new URI("hdfs://localhost:9000"), conf);
Path path = new Path(hdfsPath);
switch (choice) {
case 1:
createDirectory(fs, path);
break;
case 2:
System.out.println("目录不为空时是否删除?(yes/no)");
String response = scanner.next();
boolean recursive = response.equalsIgnoreCase("yes");
deleteDirectory(fs, path, recursive);
break;
default:
System.out.println("无效的选择");
}
fs.close();
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
}
public static void createDirectory(FileSystem fs, Path path) throws IOException {
if (fs.exists(path)) {
System.out.println("目录已存在:" + path.toString());
} else {
if (fs.mkdirs(path)) {
System.out.println("目录创建成功:" + path.toString());
} else {
System.out.println("目录创建失败:" + path.toString());
}
}
}
public static void deleteDirectory(FileSystem fs, Path path, boolean recursive) throws IOException {
if (!fs.exists(path)) {
System.out.println("目录不存在:" + path.toString());
} else {
if (fs.delete(path, recursive)) {
System.out.println("目录删除成功:" + path.toString());
} else {
System.out.println("目录删除失败:" + path.toString());
}
}
}
}
⑧.向HDFS中指定的文件追加内容,由用户指定内容追加到原有文件的开头或结尾;
a.shell
hadoop fs -appendToFile local.txt test.txt
hadoop fs -cat text.txt
b.编程实现:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.Scanner;
public class HDFSAppendContent {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入HDFS文件路径(例如:hdfs://localhost:9000/user/hadoop/file.txt):");
String hdfsFilePath = scanner.nextLine();
System.out.println("请输入要追加的内容:");
String contentToAdd = scanner.nextLine();
System.out.println("选择追加位置:1. 开头 2. 结尾");
int choice = scanner.nextInt();
Configuration conf= new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9000");
conf.set("dfs.client.block.write.replace-datanode-on-failure.policy", "NEVER");
conf.set("dfs.client.block.write.replace-datanode-on-failure.enable", "true");
try {
FileSystem fs = FileSystem.get(new URI("hdfs://localhost:9000"), conf);
Path path = new Path(hdfsFilePath);
if (!fs.exists(path)) {
System.out.println("文件不存在:" + hdfsFilePath);
} else {
String originalContent = readFile(fs, path);
String newContent;
if (choice == 1) {
newContent = contentToAdd + originalContent;
} else if (choice == 2) {
newContent = originalContent + contentToAdd;
} else {
System.out.println("无效的选择");
fs.close();
return;
}
writeFile(fs, path, newContent);
System.out.println("内容追加成功!");
}
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static String readFile(FileSystem fs, Path path) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(fs.open(path)));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
br.close();
return sb.toString();
}
private static void writeFile(FileSystem fs, Path path, String content) throws Exception {
OutputStreamWriter osw = new OutputStreamWriter(fs.create(path, true)); // 覆盖写入
osw.write(content);
osw.close();
}
}
运行代码前
运行代码后
⑨. 删除HDFS中指定的文件;
a.shell
hadoop fs -rm text.txt
b.编程实现
代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
public class HDFSFileDeleter {
public static void main(String[] args) throws Exception {
String hdfsUri = "hdfs://localhost:9000";
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI(hdfsUri), conf);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入要删除的HDFS文件路径 (例如: hdfs://localhost:9000/user/hadoop/text.txt) :");
String filePath = reader.readLine();
Path path = new Path(filePath);
if (fs.exists(path)) {
fs.delete(path, false);
System.out.println("文件删除成功: " + filePath);
} else {
System.out.println("文件不存在: " + filePath);
}
fs.close();
}
}
⑩.在HDFS中,将文件从源路径移动到目的路径。
a.shell
hadoop fs -mv test.txt test
hadoop fs -ls test/
b.编程实现:
代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.net.URI;
import java.util.Scanner;
public class HDFSFileMover {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件路径(例如:hdfs://localhost:9000/user/hadoop/wordfile1.txt):");
String sourcePath = scanner.nextLine();
System.out.println("请输入目的文件路径(例如:hdfs://localhost:9000/user/hadoop/test):");
String destinationPath = scanner.nextLine();
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://localhost:9000");
try {
FileSystem fs = FileSystem.get(new URI("hdfs://localhost:9000"), configuration);
Path srcPath = new Path(sourcePath);
Path dstPath = new Path(destinationPath);
if (!fs.exists(srcPath)) {
System.out.println("源文件不存在:" + sourcePath);
} else {
if (fs.rename(srcPath, dstPath)) {
System.out.println("文件移动成功:从 " + sourcePath + " 到 " + destinationPath);
} else {
System.out.println("文件移动失败");
}
}
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 编程实现一个类“MyFSDataInputStream”,该类继承“org.apache.hadoop.fs.FSDataInputStream”,要求如下:实现按行读取HDFS中指定文件的方法“readLine()”,如果读到文件末尾,则返回空,否则返回文件一行的文本。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class MyFsDataInputStream extends FSDataInputStream {
public MyFsDataInputStream(InputStream in) {
super(in);
}
public static String readLine(Configuration conf, String filename) throws IOException {
Path filePath = new Path(filename);
FileSystem fs = FileSystem.get(conf);
FSDataInputStream inStream = fs.open(filePath);
BufferedReader reader = new BufferedReader(new InputStreamReader(inStream));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
reader.close();
inStream.close();
return result.toString();
}
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://localhost:9000");
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
String filename = "/user/hadoop/wordfile1.txt";
System.out.println("读取文件:" + filename);
String content = MyFsDataInputStream.readLine(conf, filename);
System.out.println(content);
System.out.println("读取完成");
}
}
先看看有什么文件,这里选择wordfile1.txt
hdfs dfs -put wordfile1.txt
运行eclipse代码结果
3. 查看Java帮助手册或其它资料,用“java.net.URL”和“org.apache.hadoop.fs.FsURLStreamHandlerFactory”编程完成输出HDFS中指定文件的文本到终端中。
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.MalformedURLException;
import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;
import org.apache.hadoop.io.IOUtils;
public class FSUrl {
static {
URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());
}
public static void cat(String filename) throws MalformedURLException, IOException {
InputStream in = new URL("hdfs://localhost:9000" + filename).openStream();
try {
IOUtils.copyBytes(in, System.out, 4096, false);
} finally {
IOUtils.closeStream(in);
}
}
public static void main(String[] args) throws MalformedURLException, IOException {
String filename = "/user/hadoop/wordfile1.txt";
System.out.println("读取文件:" + filename);
FSUrl.cat(filename);
System.out.println("读取完成");
}
}
四、心得体会:
通过本次实验,我对HDFS的基本操作有了更深的理解,并掌握了如何使用Java编程来实现这些操作。具体来说,通过在Ubuntu上安装Eclipse并配置Hadoop环境,我成功地创建了一个能够与HDFS交互的Java应用程序。这个过程让我熟悉了HDFS的基本使用方法,包括文件的上传、下载、追加、删除以及目录的操作等。此外,通过实验中遇到的问题和解决的过程,我对Hadoop和Java的结合使用有了更深刻的认识。
在实验过程中,我遇到了多个问题,并通过查阅资料和调试代码逐一解决。首先,在编译和运行Java程序时,遇到了由于类名和文件名大小写不一致导致的错误。这个问题通过确保Java文件名与公共类名完全一致得以解决。其次,缺少必要的类库导致了运行时错误,通过下载并配置Woodstox库的JAR文件解决了这一问题。编程任务中类的实现逻辑错误问题则通过编写单元测试来验证readLine()方法的功能,确保其在读取文件时能够正确返回行内容,并在文件末尾时返回null。此外,还确保了资源在读取后能正确关闭。在使用Hadoop命令时,我错误地使用了本地文件路径而非HDFS路径,正确使用HDFS路径解决了文件存在性检查的问题。在尝试追加文件内容到HDFS文件时,如果文件不存在则操作会失败,为此,我在追加内容之前先检查文件是否存在,不存在则先创建文件。删除HDFS中的目录时,没有先检查目录是否为空直接执行了删除操作,导致可能会失败,这个问题通过在删除目录前先检查目录是否为空并根据用户需求决定是否删除非空目录解决。在使用java.net.URL和org.apache.hadoop.fs.FsURLStreamHandlerFactory时出现java.net.MalformedURLException,通过确保在创建URL对象时提供正确的协议名称、主机名和端口号解决了这一问题。尽管大部分问题都得到了有效解决,但在实现MyFsDataInputStream类中的readLine()方法时,可能在处理空文件或非常大的文件时仍存在一些边界条件问题,需要进一步测试和调试。此外,操作过程中可能会遇到网络不稳定或HDFS服务不可用的情况,这可能导致文件操作失败或程序异常退出,这些问题需要在实际应用中持续关注和改进。
通过此次实验,我不仅熟悉了HDFS的基本操作和Java编程的结合应用,还提升了解决实际问题的能力。这为我今后在大数据领域的学习和工作打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验三:熟悉常用的HBase 操作 日期 2024年7月1日
一、实验目的:
(1)理解 HBase 在 Hadoop 体系结构中的角色。
(2)熟练使用HBase 操作常用的 Shell 命令。
(3)熟悉 HBase 操作常用的JavaAPI。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3。
(3)HBase 版本:2.2.2。
(4)JDK 版本:1.8。
(5)JavaIDE:Eclipse。
三、实验内容:
1.编程实现以下指定功能,并用Hadoop提供的HBase Shell命令完成相同任务:
(1)列出HBase所有的表的相关信息,例如表名;
(2)在终端打印出指定的表的所有记录数据;
(3)向已经创建好的表添加和删除指定的列族或列;
(4)清空指定的表的所有记录数据;
(5)统计表的行数。
编程代码:
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;
public class HBaseOperations {
public static Configuration configuration;
public static Connection connection;
public static Admin admin;
public static void init() throws IOException {
configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "localhost");
connection = ConnectionFactory.createConnection(configuration);
admin = connection.getAdmin();
}
public static void close() throws IOException {
if (admin != null) admin.close();
if (connection != null) connection.close();
}
// 列出所有表
public static void listTables() throws IOException {
init();
HTableDescriptor[] tableDescriptors = admin.listTables();
System.out.println("HBase中的所有表:");
for (HTableDescriptor tableDescriptor : tableDescriptors) {
System.out.println(tableDescriptor.getNameAsString());
}
close();
}
// 打印指定表的所有记录
public static void printTableData(String tableName) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
System.out.println("表 " + tableName + " 中的所有记录:");
for (Result result : scanner) {
for (Cell cell : result.rawCells()) {
System.out.println("行键: " + Bytes.toString(CellUtil.cloneRow(cell)) +
", 列族: " + Bytes.toString(CellUtil.cloneFamily(cell)) +
", 列: " + Bytes.toString(CellUtil.cloneQualifier(cell)) +
", 值: " + Bytes.toString(CellUtil.cloneValue(cell)));
}
}
close();
}
// 向表中添加列族
public static void addColumnFamily(String tableName, String columnFamily) throws IOException {
init();
TableName table = TableName.valueOf(tableName);
HColumnDescriptor columnDescriptor = new HColumnDescriptor(columnFamily);
admin.addColumnFamily(table, columnDescriptor);
System.out.println("已添加列族: " + columnFamily);
close();
}
// 删除表中的列族
public static void deleteColumnFamily(String tableName, String columnFamily) throws IOException {
init();
TableName table = TableName.valueOf(tableName);
admin.deleteColumnFamily(table, Bytes.toBytes(columnFamily));
System.out.println("已删除列族: " + columnFamily);
close();
}
// 统计表的行数
public static void countRows(String tableName) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
int rowCount = 0;
for (Result result : scanner) {
rowCount++;
}
System.out.println("表 " + tableName + " 的行数: " + rowCount);
close();
}
// 清空表的所有记录
public static void truncateTable(String tableName) throws IOException {
init();
TableName table = TableName.valueOf(tableName);
admin.disableTable(table);
admin.truncateTable(table, true);
System.out.println("已清空表: " + tableName);
close();
}
public static void main(String[] args) throws IOException {
listTables();
printTableData("Test");
countRows("Test");
addColumnFamily("Test", "newcf");
deleteColumnFamily("Test", "newcf");
truncateTable("Test");
countRows("Test");
}
}
结果:
HbaseShell:
创建Test表create 'Test', 'id', 'key', 'value'
插入两条数据
put 'Test', 'row1', 'id:1', 'value1'
put 'Test', 'row1', 'key:101', 'value2'
put 'Test', 'row2', 'id:2', 'value3'
put 'Test', 'row2', 'key:102', 'value4'
(1)列出HBase所有的表的相关信息,例如表名;
list
(2)在终端打印出指定的表的所有记录数据;
scan 'Test'
补充统计行数
(3)向已经创建好的表添加和删除指定的列族或列;
alter 'Test', {NAME => 'newcf'}
alter 'Test', {NAME => 'newcf', METHOD => 'delete'}
(4)清空指定的表的所有记录数据;
truncate 'Test'
(5)统计表的行数。
count 'Test'
2.现有以下关系型数据库中的表和数据,要求将其转换为适合于HBase存储的表并插入数据:
学生表(Student)
学号(S_No) | 姓名(S_Name) | 性别(S_Sex) | 年龄(S_Age) |
2015001 | Zhangsan | male | 23 |
2015003 | Mary | female | 22 |
2015003 | Lisi | male | 24 |
课程表(Course)
课程号(C_No) | 课程名(C_Name) | 学分(C_Credit) |
123001 | Math | 2.0 |
123002 | Computer Science | 5.0 |
123003 | English | 3.0 |
选课表(SC)
学号(SC_Sno) | 课程号(SC_Cno) | 成绩(SC_Score) |
2015001 | 123001 | 86 |
2015001 | 123003 | 69 |
2015002 | 123002 | 77 |
2015002 | 123003 | 99 |
2015003 | 123001 | 98 |
2015003 | 123002 | 95 |
建Student、Course、SC表
create 'Student','S_No','S_Name','S_Sex','S_Age'
create 'Course','C_No','C_Name','C_Credit'
create 'SC','SC_Sno','SC_Cno','SC_Score'
插入数据:
hbase(main):004:0> put 'Student', '2015001', 'S_No', '2015001'
hbase(main):005:0> put 'Student', '2015001', 'S_Name', 'Zhangsan'
hbase(main):006:0> put 'Student', '2015001', 'S_Sex', 'male'
hbase(main):007:0> put 'Student', '2015001', 'S_Age', '23'
hbase(main):008:0> put 'Student', '2015002', 'S_No', '2015002'
hbase(main):009:0> put 'Student', '2015002', 'S_Name', 'Mary'
hbase(main):010:0> put 'Student', '2015002', 'S_Sex', 'female'
hbase(main):011:0> put 'Student', '2015002', 'S_Age', '22'
hbase(main):012:0> put 'Student', '2015003', 'S_No', '2015003'
hbase(main):013:0> put 'Student', '2015003', 'S_Name', 'Lisi'
hbase(main):014:0> put 'Student', '2015003', 'S_Sex', 'male'
hbase(main):015:0> put 'Student', '2015003', 'S_Age', '24'
hbase(main):016:0> put 'Course', '123001', 'C_No', '123001'
hbase(main):017:0> put 'Course', '123001', 'C_Name', 'Math'
hbase(main):018:0> put 'Course', '123001', 'C_Credit', '2.0'
hbase(main):019:0> put 'Course', '123002', 'C_No', '123002'
hbase(main):020:0> put 'Course', '123002', 'C_Name', 'Computer Science'
hbase(main):021:0> put 'Course', '123002', 'C_Credit', '5.0'
hbase(main):022:0> put 'Course', '123003', 'C_No', '123003'
hbase(main):023:0> put 'Course', '123003', 'C_Name', 'English'
hbase(main):024:0> put 'Course', '123003', 'C_Credit', '3.0'
hbase(main):025:0> put 'SC', '2015001_123001', 'SC_Sno', '2015001'
hbase(main):026:0> put 'SC', '2015001_123001', 'SC_Cno', '123001'
hbase(main):027:0> put 'SC', '2015001_123001', 'SC_Score', '86'
hbase(main):028:0> put 'SC', '2015001_123003', 'SC_Sno', '2015001'
hbase(main):029:0> put 'SC', '2015001_123003', 'SC_Cno', '123003'
hbase(main):030:0> put 'SC', '2015001_123003', 'SC_Score', '69'
hbase(main):031:0> put 'SC', '2015002_123002', 'SC_Sno', '2015002'
hbase(main):032:0> put 'SC', '2015002_123002', 'SC_Cno', '123002'
hbase(main):033:0> put 'SC', '2015002_123002', 'SC_Score', '77'
hbase(main):034:0> put 'SC', '2015002_123003', 'SC_Sno', '2015002'
hbase(main):035:0> put 'SC', '2015002_123003', 'SC_Cno', '123003'
hbase(main):036:0> put 'SC', '2015002_123003', 'SC_Score', '99'
hbase(main):037:0> put 'SC', '2015003_123001', 'SC_Sno', '2015003'
hbase(main):038:0> put 'SC', '2015003_123001', 'SC_Cno', '123001'
hbase(main):039:0> put 'SC', '2015003_123001', 'SC_Score', '98'
hbase(main):040:0> put 'SC', '2015003_123002', 'SC_Sno', '2015003'
hbase(main):041:0> put 'SC', '2015003_123002', 'SC_Cno', '123002'
hbase(main):042:0> put 'SC', '2015003_123002', 'SC_Score', '95'
同时,请编程完成以下指定功能:
(1)createTable(String tableName, String[] fields)
创建表,参数tableName为表的名称,字符串数组fields为存储记录各个域名称的数组。要求当HBase已经存在名为tableName的表的时候,先删除原有的表,然后再创建新的表。
(2)addRecord(String tableName, String row, String[] fields, String[] values)
向表tableName、行row(用S_Name表示)和字符串数组files指定的单元格中添加对应的数据values。其中fields中每个元素如果对应的列族下还有相应的列限定符的话,用“columnFamily:column”表示。例如,同时向“Math”、“Computer Science”、“English”三列添加成绩时,字符串数组fields为{“Score:Math”,”Score;Computer Science”,”Score:English”},数组values存储这三门课的成绩。
(3)scanColumn(String tableName, String column)
浏览表tableName某一列的数据,如果某一行记录中该列数据不存在,则返回null。要求当参数column为某一列族名称时,如果底下有若干个列限定符,则要列出每个列限定符代表的列的数据;当参数column为某一列具体名称(例如“Score:Math”)时,只需要列出该列的数据。
(4)modifyData(String tableName, String row, String column)
修改表tableName,行row(可以用学生姓名S_Name表示),列column指定的单元格的数据。
(5)deleteRow(String tableName, String row)
删除表tableName中row指定的行的记录。
程序代码:
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.client.Result;
import java.io.IOException;
import java.util.Scanner;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
public class HBaseManage {
private static Configuration config = HBaseConfiguration.create();
public static void createTable(String tableName, String[] fields) throws IOException {
try (Connection connection = ConnectionFactory.createConnection(config);
Admin admin = connection.getAdmin()) {
TableName tName = TableName.valueOf(tableName);
if (admin.tableExists(tName)) {
admin.disableTable(tName);
admin.deleteTable(tName);
System.out.println("表 " + tableName + " 已存在,删除旧表并创建新表。");
}
HTableDescriptor tableDescriptor = new HTableDescriptor(tName);
for (String field : fields) {
String[] parts = field.split(":");
String family = parts[0];
tableDescriptor.addFamily(new HColumnDescriptor(family));
}
admin.createTable(tableDescriptor);
System.out.println("表 " + tableName + " 创建成功。");
}
}
public static void addRecord(String tableName, String row, String[] fields, String[] values) throws IOException {
try (Connection connection = ConnectionFactory.createConnection(config);
Table table = connection.getTable(TableName.valueOf(tableName))) {
Put put = new Put(row.getBytes());
for (int i = 0; i < fields.length; i++) {
String[] parts = fields[i].split(":");
String family = parts[0];
String column = parts.length > 1 ? parts[1] : null;
if (column != null) {
put.addColumn(family.getBytes(), column.getBytes(), values[i].getBytes());
} else {
put.addColumn(family.getBytes(), null, values[i].getBytes());
}
}
table.put(put);
System.out.println("向表 " + tableName + " 添加记录:" + row + " 成功。");
}
}
public static void scanColumn(String tableName, String column) throws IOException {
try (Connection connection = ConnectionFactory.createConnection(config);
Table table = connection.getTable(TableName.valueOf(tableName))) {
Scan scan = new Scan();
String[] parts = column.split(":");
String family = parts[0];
String qualifier = parts.length > 1 ? parts[1] : null;
if (qualifier != null) {
scan.addColumn(family.getBytes(), qualifier.getBytes());
} else {
scan.addFamily(family.getBytes());
}
ResultScanner scanner = table.getScanner(scan);
System.out.println("扫描表 " + tableName + " 的列 " + column + " 数据:");
for (Result result : scanner) {
if (qualifier != null) {
byte[] value = result.getValue(family.getBytes(), qualifier.getBytes());
System.out.println("行: " + new String(result.getRow()) + ", 值: " + (value != null ? new String(value) : "null"));
} else {
for (Cell cell : result.listCells()) {
System.out.println("行: " + new String(result.getRow()) + ", 列族: " + new String(CellUtil.cloneFamily(cell)) + ", 列限定符: " + new String(CellUtil.cloneQualifier(cell)) + ", 值: " + new String(CellUtil.cloneValue(cell)));
}
}
}
}
}
public static void modifyData(String tableName, String row, String column, String newValue) throws IOException {
try (Connection connection = ConnectionFactory.createConnection(config);
Table table = connection.getTable(TableName.valueOf(tableName))) {
Put put = new Put(row.getBytes());
String[] parts = column.split(":");
String family = parts[0];
String qualifier = parts.length > 1 ? parts[1] : null;
if (qualifier != null) {
put.addColumn(family.getBytes(), qualifier.getBytes(), newValue.getBytes());
} else {
put.addColumn(family.getBytes(), null, newValue.getBytes());
}
table.put(put);
System.out.println("修改表 " + tableName + " 的行 " + row + " 的列 " + column + " 的数据成功。");
}
}
public static void deleteRow(String tableName, String row) throws IOException {
try (Connection connection = ConnectionFactory.createConnection(config);
Table table = connection.getTable(TableName.valueOf(tableName))) {
Delete delete = new Delete(row.getBytes());
table.delete(delete);
System.out.println("删除表 " + tableName + " 的行 " + row + " 成功。");
}
}
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要创建的表名:");
String tableName = scanner.nextLine();
System.out.println("请输入表的列族(以逗号分隔):");
String columns = scanner.nextLine();
String[] fields = columns.split(",");
createTable(tableName, fields);
while (true) {
System.out.println("请选择操作:\n1-添加记录 \n2-扫描列 \n3-修改数据 \n4-删除行 \n5-退出");
int choice = scanner.nextInt();
scanner.nextLine();
if (choice == 1) {
System.out.println("请输入行键(row key):");
String row = scanner.nextLine();
System.out.println("请输入字段(以逗号分隔,格式:列族:列限定符):");
String fieldInput = scanner.nextLine();
String[] addFields = fieldInput.split(",");
System.out.println("请输入对应的值(以逗号分隔):");
String valueInput = scanner.nextLine();
String[] values = valueInput.split(",");
addRecord(tableName, row, addFields, values);
} else if (choice == 2) {
System.out.println("请输入要扫描的列(格式:列族或列族:列限定符):");
String column = scanner.nextLine();
scanColumn(tableName, column);
} else if (choice == 3) {
System.out.println("请输入行键(row key):");
String row = scanner.nextLine();
System.out.println("请输入要修改的列(格式:列族:列限定符):");
String column = scanner.nextLine();
System.out.println("请输入新的值:");
String newValue = scanner.nextLine();
modifyData(tableName, row, column, newValue);
} else if (choice == 4) {
System.out.println("请输入要删除的行键(row key):");
String row = scanner.nextLine();
deleteRow(tableName, row);
} else if (choice == 5) {
break;
} else {
System.out.println("无效的选择,请重新输入。");
}
}
scanner.close();
}
}
结果:
3.利用HBase和MapReduce完成如下任务:
假设HBase有2张表,表的逻辑视图及部分数据如下所示:
表 逻辑视图及部分数据
书名(bookName) | 价格(price) |
Database System Concept | 30$ |
Thinking in Java | 60$ |
Data Mining | 25$ |
要求:从HBase读出上述两张表的数据,对“price”的排序,并将结果存储到HBase中。
create 'book','bookName'
put 'book','val_60$','bookName','Thingking in Java'
put 'book','val_20&','bookName','Database System Concept'
put 'book','val_30$','bookName','Data Mining'
四、心得体会:
在本次《大数据技术基础》课程的实验中,我通过实践操作,深入了解了HBase的安装、配置及常用操作,并成功实现了一些基本的编程任务。通过此次实验,我对HBase的基本架构、数据模型和操作方式有了更加全面的认识,也初步掌握了在HBase中进行数据存储和管理的方法。通过动手实践,我对HBase的理解变得更加深入。无论是安装配置,还是进行实际的数据操作,都让我对HBase有了更直观的认识。这些操作帮助我从理论转向实践,真正掌握了HBase的使用方法。HBase的配置涉及到许多文件和参数的调整,尤其是在伪分布式和分布式模式下。通过此次实验,我学会了如何正确配置HBase,以及如何处理配置过程中的各种问题。通过HBase Shell命令和Java编程的结合,我体会到了不同操作方式的优势。Shell命令简洁明了,适合快速测试和小规模数据操作;而编程方式则更加灵活,适合复杂的业务逻辑和大规模数据处理。在实验过程中,我遇到了一些问题,并通过查找资料和反复尝试,成功解决了这些问题。在验证HBase是否安装成功时,最初遇到了许多错误信息。我通过仔细检查配置文件,最终发现是由于hbase-env文件中的某些参数被注释掉了,导致配置不生效。解决这个问题后,我能够顺利启动HBase并进行相关操作。在执行大规模数据操作时,HBase的读写性能表现出不稳定的情况。潜在原因可能是由于HBase配置中的内存和资源分配不合理,或者Hadoop集群的网络带宽限制。通过优化HBase的配置文件,调整内存和资源分配,我部分缓解了这个问题,但在某些情况下仍然存在性能波动,这需要进一步的调整和优化。在设计HBase表的结构时,我意识到列族和列限定符的设计对数据管理和查询的复杂度有很大影响。由于对HBase的Schema设计缺乏经验,最初的设计导致了一些查询操作的复杂性增加。通过查阅相关资料和参考最佳实践,我重新审视了HBase表的设计,根据实际业务需求和数据访问模式进行了优化,提升了数据操作的效率和简便性。总的来说,通过本次实验,我不仅掌握了HBase的基本操作,还学会了如何应对实际操作中的各种问题。虽然在实验过程中遇到了一些挑战,但通过不断学习和尝试,我逐步解决了这些问题,提升了自己的技术能力和问题解决能力。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验四:NoSQL和关系数据库的操作比较 日期 2024年7月1日
一、实验目的:
1. 理解四种数据库(MySQL,HBase,Redis,MongoDB)的概念以及不同点;
2. 熟练使用四种数据库操作常用的Shell命令;
3. 熟悉四种数据库操作常用的Java API。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3
(3)MySQL版本:8.0
(4)HBase 版本:2.2.2。
(5)Redis版本: 5.0.5版本
(6)MongoDB版本: 6.0版本
(7)JDK版本:1.8
(8)Java IDE:Eclipse
三、实验内容:
1. MySQL数据库操作
Student学生表
Name | English | Math | Computer |
zhangsan | 69 | 86 | 77 |
lisi | 55 | 100 | 88 |
(1).根据上面给出的表格,利用MySQL8.0设计出student学生表格;
CREATE DATABASE school;
USE school;
CREATE TABLE student (
Name VARCHAR(50),
English INT,
Math INT,
Computer INT
);
INSERT INTO student (Name, English, Math, Computer) VALUES
('zhangsan', 69, 86, 77),
('lisi', 55, 100, 88);
a)设计完后,用select语句输出所有的相关信息,并给出截图;
SELECT * FROM student;
b)查询zhangsan的Computer成绩,并给出截图;
SELECT Computer FROM student WHERE Name = 'zhangsan';
c)修改lisi的Math成绩,改为95.给出截图.
UPDATE student SET Math = 95 WHERE Name = 'lisi';
(2).根据上面已经设计出的student表,用MySQL的JAVA客户端编程;
a)添加数据:English:45 Math:89 Computer:100
scofield | 45 | 89 | 100 |
b)获取scofield的English成绩信息
//--------Student.java-------//
public class Student {//创建一个Student实体类
private String Name;
private int English;
private int Math;
private int Computer;
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name=Name;
}
public int getEnglish() {
return English;
}
public void setEnglish(int English) {
this.English=English;
}
public int getMath() {
return Math;
}
public void setMath(int Math) {
this.Math=Math;
}
public int getComputer() {
return Computer;
}
public void setComputer(int Computer) {
this.Computer=Computer;
}
public Student() {
}
public Student(String Name, int English, int Math, int Computer) {
super();
this.Name = Name;
this.English = English;
this.Math = Math;
this.Computer = Computer;
}
}
//StudentDao.java//
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class StudentDao {
private static final String URL = "jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "123456";
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
public void insertStudent(String name, int english, int math, int computer) throws SQLException {
String sql = "INSERT INTO student (Name, English, Math, Computer) VALUES (?, ?, ?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, name);
pstmt.setInt(2, english);
pstmt.setInt(3, math);
pstmt.setInt(4, computer);
int rows = pstmt.executeUpdate();
System.out.println("Successfully inserted " + rows + " row(s).");
}
}
public int getEnglishScore(String name) throws SQLException {
String sql = "SELECT English FROM student WHERE Name = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, name);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return rs.getInt("English");
} else {
System.out.println("Student not found.");
return -1;
}
}
}
public static void main(String[] args) {
StudentDao dao = new StudentDao();
try {
dao.insertStudent("scofield", 45, 89, 100);
int englishScore = dao.getEnglishScore("scofield");
System.out.println("Scofield's English score: " + englishScore);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2 HBase数据库操作
Student学生表
name | score | |||
English | Math | Computer | ||
zhangsan | 69 | 86 | 77 | |
lisi | 55 | 100 | 88 |
(1).根据上面给出的表格,用Hbase Shell模式设计student学生表格。
create 'student', 'score'
put 'student', 'zhangsan', 'score:English', '69'
put 'student', 'zhangsan', 'score:Math', '86'
put 'student', 'zhangsan', 'score:Computer', '77'
put 'student', 'lisi', 'score:English', '55'
put 'student', 'lisi', 'score:Math', '100'
put 'student', 'lisi', 'score:Computer', '88'
a)设计完后,用scan指令浏览表的相关信息,给出截图。
scan 'student'
b)查询zhangsan 的Computer成绩,给出截图。
get 'student', 'zhangsan', 'score:Computer'
c)修改lisi的Math成绩,改为95,给出截图。
put 'student', 'lisi', 'score:Math', '95'
(2).根据上面已经设计出的student,用Hbase API编程。
a)添加数据:English:45 Math:89 Computer:100
scofield | 45 | 89 | 100 |
b)获取scofield的English成绩信息
代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;.
public class HBaseStudent {
public static void main(String[] args) {
Configuration config = HBaseConfiguration.create();
try (Connection connection = ConnectionFactory.createConnection(config);
Table table = connection.getTable(TableName.valueOf("student"))) {
// 添加scofield的数据
Put put = new Put(Bytes.toBytes("scofield"));
put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("English"), Bytes.toBytes("45"));
put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("Math"), Bytes.toBytes("89"));
put.addColumn(Bytes.toBytes("score"), Bytes.toBytes("Computer"), Bytes.toBytes("100"));
table.put(put);
// 获取scofield的English成绩
Get get = new Get(Bytes.toBytes("scofield"));
get.addColumn(Bytes.toBytes("score"), Bytes.toBytes("English"));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes("score"), Bytes.toBytes("English"));
System.out.println("scofield's English score: " + Bytes.toString(value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.Redis数据库操作
Student 键值对:
zhangsan:{
English: 69
Math: 86
Computer: 77
}
lisi:{
English: 55
Math: 100
Computer: 88
}
hadoop@zhuyangyu-virtual-machine:/usr/local/redis$ cd /usr/local/redis
./src/redis-server
再开一个窗口
./src/redis-cli
(1). 根据上面给出的键值对,用Redis的哈希结构设计出上述表格;(键值可以用student.zhangsan,student.lisi来表示两个键值属于同一个表格)
HSET student.zhangsan English 69 Math 86 Computer 77
HSET student.lisi English 55 Math 100 Computer 88
a)设计完之后,用hgetall命令分别输出zhangsan和lisi的成绩信息,并截图;
HGET student.zhangsan
b) 用hget命令查询zhangsan 的Computer成绩,给出截图。
HGET student.zhangsan Computer
c) 修改lisi的Math成绩,改为95,给出截图。
HSET student.lisi Math 95
HGETALL student.lisi
(2).根据上面已经设计出的student表格,用Redis的JAVA客户端编程(jedis)
a ) 添加数据:English:45 Math:89 Computer:100
scofield:{
English: 45
Math: 89
Computer: 100
}
b)获取scofield的English成绩信息
代码:
import redis.clients.jedis.Jedis;
public class RedisExample {
public static void main(String[] args) {
// 连接Redis服务器
Jedis jedis = new Jedis("localhost");
// 添加scofield的数据
jedis.hset("student.scofield", "English", "45");
jedis.hset("student.scofield", "Math", "89");
jedis.hset("student.scofield", "Computer", "100");
// 获取scofield的English成绩信息
String englishScore = jedis.hget("student.scofield", "English");
System.out.println("scofield's English score: " + englishScore);
// 关闭Redis连接
jedis.close();
}
}
4 MongoDB数据库操作
Student文档如下:
{
“name”: “zhangsan”,
“score”: {
“English”: 69,
“Math”: 86,
“Computer”: 77
}
}
{
“name”: “lisi”,
“score”: {
“English”: 55,
“Math”: 100,
“Computer”: 88
}
}
(1).根据上面给出的文档,用Mongo shell设计出student集合.
use school;
db.students.insertMany([
{
"name": "zhangsan",
"score": {
"English": 69,
"Math": 86,
"Computer": 77
}
},
{
"name": "lisi",
"score": {
"English": 55,
"Math": 100,
"Computer": 88
}
}
]);
a)设计完后,用find()方法输出两个学生的信息,给出截图;
db.students.find().pretty();
b) 用find函数查询zhangsan 的所有成绩(只显示score列),给出截图。
db.students.find(
{ "name": "zhangsan" },
{ "score": 1, "_id": 0 }
).pretty();
c) 修改lisi的Math成绩,改为95,给出截图。
db.students.updateOne(
{ "name": "lisi" },
{ $set: { "score.Math": 95 } }
);
// Verify the update
db.students.find(
{ "name": "lisi" },
{ "score": 1, "_id": 0 }
).pretty();
(2).根据上面已经设计出的student集合,用MongoDB的JAVA客户端编程
a)添加数据:English:45 Math:89 Computer:100
{
“name”: “scofield”,
“score”: {
“English”: 45,
“Math”: 89,
“Computer”: 100
}
}
b) 获取scofield的所有成绩成绩信息(只显示score列)
代码:
import java.util.ArrayList;
import java.util.List;
import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoCursor;
public class MongoDBClient {
public static MongoClient mongoClient;
public static MongoDatabase mongoDatabase;
public static MongoCollection<Document> collection;
public static void main(String[] args) {
init();
// 插入数据
test1();
// 查询数据
test2();
}
public static void test1() {
Document document = new Document("name", "scofield")
.append("score", new Document("English", 45)
.append("Math", 89)
.append("Computer", 100));
List<Document> documents = new ArrayList<>();
documents.add(document);
collection.insertMany(documents);
System.out.println("文档插入成功");
}
public static void test2() {
// 进行数据查找,查询条件为 name = scofield,对获取的结果集只显示 score 这个域
MongoCursor<Document> cursor = collection.find(new Document("name", "scofield"))
.projection(new Document("score", 1).append("_id", 0)).iterator();
while (cursor.hasNext()) {
System.out.println(cursor.next().toJson());
}
}
public static void init() {
// TODO Auto-generated method stub
// 实例化一个 mongo 客户端
mongoClient = new MongoClient("localhost", 27017);
// 实例化一个 mongo 数据库
mongoDatabase = mongoClient.getDatabase("Student");
// 获取数据库中某个集合
collection = mongoDatabase.getCollection("Student");
}
}
四、心得体会:
在本次《大数据技术基础》课程的实验中,我通过对NoSQL和关系数据库的操作进行了详细的实践和比较,深入了解了各类数据库的安装、配置及操作方法。本次实验涵盖了Redis、MySQL、MongoDB和HBase四种数据库的实际操作,从环境搭建到数据操作,均进行了全面的实验。在整个实验过程中,我充分体会到了不同类型数据库各自的特点和应用场景。首先,在Redis的安装和使用过程中,通过下载并编译redis-5.0.5.tar.gz版本,我学会了如何在Linux环境下进行软件的编译和安装,并解决了在安装过程中遇到的头文件冲突问题。具体来说,我将头文件sds.h中的SDS_NOINIT变量声明为extern,避免了多重定义错误,成功完成了Redis的安装和测试。
在MySQL的安装和配置过程中,我了解了如何使用apt命令来安装MySQL服务,并通过修改配置文件实现了远程访问功能。为了确保安全性和功能的完善,我设置了root用户的密码,并创建了新的用户和权限。在这个过程中,我遇到了关于MySQL8.0版本配置文件的拆分问题,通过修改mysqld.cnf文件中的bind-address属性,成功实现了所有IP的访问权限。
MongoDB的安装过程相对复杂,由于Ubuntu22.04使用的是openssl 3.0版本,而MongoDB需要1.1版本的支持,因此我采用了强行安装旧版本libssl的方法。尽管过程中遇到了包文件格式错误的问题,但通过删除旧的MongoDB配置文件、清理apt缓存并重新安装,最终成功解决了问题。这让我深刻理解了软件依赖管理和版本控制的重要性。
在实验的具体操作内容中,我分别对各类数据库进行了详细的操作和测试。在MySQL中,创建并操作了学生成绩表,使用Java编程实现了数据的增删改查;在HBase中,通过Shell命令和Java API完成了数据的存储和查询;在Redis中,使用哈希结构存储了学生成绩,并通过Java客户端进行数据操作;在MongoDB中,通过设计文档结构和Java编程,实现了数据的插入和查询操作。通过本次实验,我不仅掌握了NoSQL和关系数据库的基本操作,还学会了如何应对实际操作中的各种问题。无论是配置环境、解决安装问题,还是进行数据操作,我都积累了丰富的经验,并提高了自己的问题解决能力。总的来说,这次实验让我对大数据技术有了更深入的理解和实战经验,为今后的学习和工作打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验五:MapReduce初级编程实践 日期 2024年7月1日
一、实验目的:
1.通过实验掌握基本的MapReduce编程方法;
2.掌握用MapReduce解决一些常见的数据处理问题,包括数据去重、数据排序和数据挖掘等。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3。
(3)已经配置完成的Hadoop伪分布式环境。
三、实验内容:
1.编程实现文件合并和去重操作
对于两个输入文件,即文件A和文件B,请编写MapReduce程序,对两个文件进行合并,并剔除其中重复的内容,得到一个新的输出文件C。下面是输入文件和输出文件的一个样例供参考。
输入文件A的样例如下:
20150101 x 20150102 y 20150103 x 20150104 y 20150105 z 20150106 x |
输入文件B的样例如下:
20150101 y 20150102 y 20150103 x 20150104 z 20150105 y |
根据输入文件A和B合并得到的输出文件C的样例如下:
20150101 x 20150101 y 20150102 y 20150103 x 20150104 y 20150104 z 20150105 y 20150105 z 20150106 x |
程序代码:
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class MergeFiles {
public static class MergeMapper extends Mapper<LongWritable, Text, Text, Text> {
private Text outputKey = new Text();
private Text outputValue = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] parts = line.split("\\s+");
if (parts.length == 2) {
outputKey.set(parts[0]);
outputValue.set(parts[1]);
context.write(outputKey, outputValue);
}
}
}
public static class MergeReducer extends Reducer<Text, Text, Text, Text> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
Set<String> uniqueValues = new HashSet<>();
for (Text value : values) {
uniqueValues.add(value.toString());
}
for (String value : uniqueValues) {
context.write(key, new Text(value));
}
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: MergeFiles <input path> <output path>");
System.exit(-1);
}
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Merge Files");
job.setJarByClass(MergeFiles.class);
job.setMapperClass(MergeMapper.class);
job.setReducerClass(MergeReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// 设置输入路径
FileInputFormat.addInputPath(job, new Path(args[0]));
// 设置输出路径
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
运行前准备:
cd /usr/local/hadoop
./bin/hdfs dfs -rm -r input
./bin/hdfs dfs -rm -r output
hdfs dfs -mkdir input
hdfs dfs -put A.txt input
hdfs dfs -put B.txt input
打包运行程序:
./bin/hadoop jar ./myapp/MapReduce1.jar input output
hdfs dfs -mv /user/hadoop/output/part-r-00000 /user/hadoop/output/c.txt
//在Hadoop MapReduce中,输出文件的名称不是由用户直接控制的。使用命令将part-r-00000文件重命名为c.txt
hdfs dfs -cat /user/hadoop/output/c.txt
2. 编写程序实现对输入文件的排序
现在有多个输入文件,每个文件中的每行内容均为一个整数。要求读取所有文件中的整数,进行升序排序后,输出到一个新的文件中,输出的数据格式为每行两个整数,第一个数字为第二个整数的排序位次,第二个整数为原待排列的整数。下面是输入文件和输出文件的一个样例供参考。
输入文件example1.txt的样例如下:
33 37 12 40 |
输入文件example2.txt的样例如下:
4 16 39 5 |
输入文件example3.txt的样例如下:
1 45 25 |
根据输入文件1、2和3得到的输出文件如下:
1 1 2 4 3 5 4 12 5 16 6 25 7 33 8 37 9 39 10 40 11 45 |
程序代码:
import java.io.IOException;
import java.util.PriorityQueue;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class MapReduce2 {
public static class SortMapper extends Mapper<LongWritable, Text, IntWritable, IntWritable> {
private IntWritable number = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
number.set(Integer.parseInt(value.toString()));
context.write(number, number);
}
}
public static class SortReducer extends Reducer<IntWritable, IntWritable, IntWritable, IntWritable> {
private IntWritable rank = new IntWritable(1);
@Override
protected void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
for (IntWritable value : values) {
context.write(rank, value);
rank.set(rank.get() + 1);
}
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: SortNumbers <input path> <output path>");
System.exit(-1);
}
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Sort Numbers");
job.setJarByClass(MapReduce2.class);
job.setMapperClass(SortMapper.class);
job.setReducerClass(SortReducer.class);
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
运行前准备:
cd /usr/local/hadoop
./bin/hdfs dfs -rm -r input
./bin/hdfs dfs -rm -r output
# 创建输入目录
hdfs dfs -mkdir -p /user/hadoop/input
# 创建并写入example1.txt
echo -e "33\n37\n12\n40" | hdfs dfs -put - /user/hadoop/input/example1.txt
# 创建并写入example2.txt
echo -e "4\n16\n39\n5" | hdfs dfs -put - /user/hadoop/input/example2.txt
# 创建并写入example3.txt
echo -e "1\n45\n25" | hdfs dfs -put - /user/hadoop/input/example3.txt
# 验证文件内容
hdfs dfs -cat /user/hadoop/input/example1.txt
hdfs dfs -cat /user/hadoop/input/example2.txt
hdfs dfs -cat /user/hadoop/input/example3.txt
打包运行程序
./bin/hadoop jar ./myapp/MapReduce2.jar input output
hdfs dfs -ls output
hdfs dfs -mv /user/hadoop/output/part-r-00000 /user/hadoop/output/exampleTotal.txt
//在Hadoop MapReduce中,输出文件的名称不是由用户直接控制的。使用命令将part-r-00000文件重命名为c.txt
hdfs dfs -cat /user/hadoop/output/exampleTotal.txt
3. 对给定的表格进行信息挖掘
下面给出一个child-parent的表格,要求挖掘其中的父子辈关系,给出祖孙辈关系的表格。
输入文件内容如下:
child parent Steven Lucy Steven Jack Jone Lucy Jone Jack Lucy Mary Lucy Frank Jack Alice Jack Jesse David Alice David Jesse Philip David Philip Alma Mark David Mark Alma |
输出文件内容如下:
grandchild grandparent Steven Alice Steven Jesse Jone Alice Jone Jesse Steven Mary Steven Frank Jone Mary Jone Frank Philip Alice Philip Jesse Mark Alice Mark Jesse |
程序代码:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GrandChildRelationship {
public static class GrandChildMapper extends Mapper<LongWritable, Text, Text, Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.split("\\s+");
if (words.length == 2) {
String child = words[0];
String parent = words[1];
context.write(new Text(parent), new Text("child:" + child));
context.write(new Text(child), new Text("parent:" + parent));
}
}
}
public static class GrandChildReducer extends Reducer<Text, Text, Text, Text> {
private boolean headerWritten = false;
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
if (!headerWritten) {
context.write(new Text("grandchild"), new Text("grandparent"));
headerWritten = true;
}
List<String> children = new ArrayList<>();
List<String> parents = new ArrayList<>();
for (Text value : values) {
String val = value.toString();
if (val.startsWith("child:")) {
children.add(val.substring(6));
} else if (val.startsWith("parent:")) {
parents.add(val.substring(7));
}
}
for (String child : children) {
for (String parent : parents) {
context.write(new Text(child), new Text(parent));
}
}
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: GrandChildRelationship <input path> <output path>");
System.exit(-1);
}
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Grandchild Grandparent Relationship");
job.setJarByClass(GrandChildRelationship.class);
job.setMapperClass(GrandChildMapper.class);
job.setReducerClass(GrandChildReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
运行前准备:
cd /usr/local/hadoop
./bin/hdfs dfs -rm -r input
./bin/hdfs dfs -rm -r output
hdfs dfs -mkdir -p /user/hadoop/input
echo -e "child\tparent\nSteven\tLucy\nSteven\tJack\nJone\tLucy\nJone\tJack\nLucy\tMary\nLucy\tFrank\nJack\tAlice\nJack\tJesse\nDavid\tAlice\nDavid\tJesse\nPhilip\tDavid\nPhilip\tAlma\nMark\tDavid\nMark\tAlma" > child-parent.txt
hdfs dfs -put child-parent.txt input
打包运行程序:
./bin/hadoop jar ./myapp/MapReduce3.jar input output
查看结果
hdfs dfs -ls output
//重命名
hdfs dfs -mv /user/hadoop/output/part-r-00000 /user/hadoop/output/child-grandparent.txt
hdfs dfs -cat output/child-grandparent.txt
四、心得体会:
通过这次MapReduce初级编程实践,我对大数据处理技术有了更深入的理解,并掌握了Hadoop环境下的基本编程方法。在实验中,我通过具体的编程任务,例如文件合并与去重、数据排序以及信息挖掘等,逐步熟悉了MapReduce编程模型和实际操作。首先,在文件合并和去重操作中,我学会了如何利用MapReduce框架来处理海量数据。通过编写Mapper和Reducer类,我能够实现对两个输入文件的合并,并有效剔除重复内容。这不仅加深了我对MapReduce基本工作原理的理解,还让我体会到分布式计算在处理大数据时的高效性。其次,在对输入文件进行排序的任务中,我进一步理解了MapReduce在处理大规模数据排序时的优势。通过实验,我学会了如何在Mapper阶段读取数据并在Reducer阶段对数据进行排序。利用Hadoop强大的分布式处理能力,我能够轻松实现对多个输入文件的全局排序,并输出排序结果。这让我更加体会到MapReduce模型在数据处理和分析中的重要作用。最后,在信息挖掘实验中,我通过处理父子辈关系表格,挖掘出了祖孙辈关系。这一任务让我深刻认识到数据挖掘技术的重要性和实用性。在实际操作中,我掌握了如何通过MapReduce模型实现复杂数据关系的处理,并将挖掘结果输出为新的数据表格。这不仅增强了我的编程能力,也让我更好地理解了数据挖掘的基本方法和技术。在我看来,这次实验不仅帮助我掌握了MapReduce编程的基础知识和技能,还让我认识到了Hadoop分布式计算平台在大数据处理中的强大功能。在实验过程中,我遇到了一些问题和挑战,但通过不断地学习和实践,我逐步克服了这些困难,最终顺利完成了实验任务。这次实验经验对我今后的学习和工作都有很大的帮助,也让我对大数据技术产生了浓厚的兴趣。我深知在大数据时代,掌握先进的数据处理技术将是非常重要的,因此我会继续深入学习相关知识,不断提升自己的技术水平。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验六 熟悉 Hive 的基本操作 日期 2024年7月4日
二、实验目的:
(1)理解 Hive 作为数据仓库在 Hadoop 体系结构中的角色。
(2)熟练使用常用的 HiveQL。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3。
(3)JDK 版本:1.8。
(4)JavaIDE:Eclipse。
(5)Hive 版本:3.1.2。
(6)Mysql版本:8.0.
三、实验内容:
(1)创建一个内部表 stocks,字段分隔符为英文逗号,表结构如表14-11所示。
表14-11.stocks 表结构
col_name | data_type | col_name | data_type |
exchange | string | price low | float |
symbo | string | price close | float |
ymd | string | volume | int |
price_open | float | price_adj_close | float |
price_high | float |
操作语句如下:
create table if not exists stocks
(
`exchange` string,
`symbol` string,
`ymd` string,
`price_open` float,
`price_high` float,
`price_low` float,
`price_close` float,
`volume` int,
`price_adj_close` float
)
row format delimited fields terminated by ',';
(2)创建一个外部分区表dividends(分区字段为 exchange 和 symbol), 字段分隔符为 英文逗号,表结构如表14-12所示。
表14-12 dividends表结构
col_name | data_type | col_name | data type |
ymd | string | exchange | string |
dividend | float | symbol | string |
操作语句如下:
create external table if not exists dividends
(
`ymd` string,
`dividend` float
)
partitioned by(`exchange` string ,`symbol` string)
row format delimited fields terminated by ',';
(3)从stocks.csv 文件向 stocks 表中导入数据。
操作语句如下:
LOAD DATA LOCAL INPATH '/home/hadoop/下载/prog-hive-1st-ed-data/data/stocks/stocks.csv' INTO TABLE stocks;
(4)创建一个未分区的外部表dividends_unpartitioned,并从 dividends.csv向其中导入数据,表结构如表14-13所示。
表14-13 dividends_unpartitioned表结构
col_name | data_type | col_name | data_type |
ymd | string | exchange | string |
diyidend | float | symbo] | string |
操作语句如下:
create external table if not exists dividends_unpartitioned
(
`exchange` string ,
`symbol` string,
`ymd` string,
`dividend` float
)
row format delimited fields terminated by ',';
load data local inpath '/home/hadoop/下载/prog-hive-1st-ed-data/data/dividends/dividends.csv' overwrite into table dividends_unpartitioned;
(5)通过对dividends_unpartitioned的查询语句,利用Hive自动分区特性向分区表 dividends各个分区中插入对应数据。
操作语句如下:
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
set hive.exec.max.dynamic.partitions.pernode=1000;
insert overwrite table dividends partition(`exchange`,`symbol`) select `ymd`,`dividend`,`exchange`,`symbol` from dividends_unpartitioned;
(6)查询IBM公司(symbol=IBM)从2000年起所有支付股息的交易日(dividends表中有对应记录)的收盘价(price_close)。
操作语句如下:
select s.ymd,s.symbol,s.price_close
from stocks s
LEFT SEMI JOIN
dividends d
ON s.ymd=d.ymd and s.symbol=d.symbol
where s.symbol='IBM' and year(ymd)>=2000;
(7)查询苹果公司(symbol=AAPL)2008年10月每个交易日的涨跌情况,涨显示rise, 跌显示fall, 不变显示unchange。
操作语句如下:
select ymd,
case
when price_close-price_open>0 then 'rise'
when price_close-price_open<0 then 'fall'
else 'unchanged'
end as situation
from stocks
where symbol='AAPL' and substring(ymd,0,7)='2008-10';
(8)查询stocks表中收盘价(price_close)比开盘价(price_open)高得最多的那条记录的交易所(exchange)、股票代码(symbol)、日 期(ymd)、收盘价、开盘价及二者差价。
操作语句如下:
select `exchange`,symbol,ymd,price_close-price_open as `diff`
from
(
select *
from stocks
order by price_close-price_open desc
limit 1
)t;
(9)从stocks表中查询苹果公司(symbol=AAPL)年平均调整后收盘价(price_adj. close)大于50美元的年份及年平均调整后收盘价。
操作语句如下:
select
year(ymd) as `year`,
avg(price_adj_close) as avg_price from stocks
where `exchange`='NASDAQ' and symbol='AAPL'
group by year(ymd)
having avg_price > 50;
(10)查询每年年平均调整后收盘价(price_adj_close)前三名的公司的股票代码及年平均调整后收盘价。
操作语句如下:
select t2.`year`,symbol,t2.avg_price
from
(
select
*,row_number() over(partition by t1.`year` order by t1.avg_price desc) as `rank`
from
(
select
year(ymd) as `year`,
symbol,
avg(price_adj_close) as avg_price
from stocks
group by year(ymd),symbol
)t1
)t2
where t2.`rank`<=3;
四、心得体会:
通过此次实验,我深刻理解了Hive在Hadoop体系结构中的角色,掌握了常用的HiveQL操作。首先,我通过创建内部表和外部表,熟悉了表的创建和数据导入过程。实验中,特别是创建分区表和未分区表的操作,让我认识到分区表在处理大数据集时的优势,可以显著提高查询性能。通过对数据的查询和处理,掌握了基本的HiveQL语法和功能,如分区插入、条件查询、数据聚合等操作。实验中,我也遇到了一些挑战,比如处理数据导入时的路径和文件格式问题,以及在动态分区插入过程中对参数的配置要求。这些问题通过查阅资料和反复调试,最终得以解决,增强了我的问题解决能力和对Hive的实际操作经验。总的来说,此次实验不仅加深了我对Hive及其在大数据处理中的应用的理解,同时也提高了我在Linux环境下进行大数据处理的综合能力,为今后深入学习和应用大数据技术奠定了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验七:Spark 初级编程实践 日期 2024年7月4日
二、实验目的:
(1)掌握使用Spark 访问本地文件和HDFS 文件的方法。
(2)掌握Spark 应用程序的编写、编译和运行方法。
二、实验环境:
(1)操作系统:Linux (Ubuntu20.04)。
(2)Hadoop 版本:3.1.3。
(3)JDK 版本:1.8。
(4)JavaIDE:Eclipse。
(5)Spark版本:2.4.0。
(6)sbt-1.3.8。
三、实验内容:
1.Spark读取文件系统的数据
(1)在spark-shell中读取Linux 系统本地文件/home/hadoop/test.txt,,统计出文件的行数。
操作命令如下:
vim /home/hadoop/test.txt
cd /usr/local/spark
./bin/spark-shell
scala>val textFile=sc.textFile("file:///home/hadoop/test.txt")
scala>textFile.count()
(2)在spark-shell 中读取HDFS 文件/user/hadoop/test.txt (如果该文件不存在,先创建),统计出文件的行数。
操作命令如下:
cd /usr/local/hadoop
vim test.txt
hdfs dfs -put test.txt
scala>val textFile=sc.textFile("hdfs://localhost:9000/user/hadoop/test.txt")
scala>textFile.count()
(3)编写独立应用程序(推荐使用Scala 语言),读取HDFS 文件/user/hadoop/test.txt (如果该文件不存在,先创建),统计出文件的行数;通过sbt 工具将整个应用程序编译打包成JAR包,并将生成的JAR 包通过 spark-submit 提交到 Spark中运行命令。
操作过程如下:
在终端中执行如下命令创建一个文件夹 sparkapp 作为应用程序根目录:
cd ~ # 进入用户主文件夹
mkdir ./sparkapp # 创建应用程序根目录
mkdir -p ./sparkapp/src/main/scala # 创建所需的文件夹结构
vim ./sparkapp/src/main/scala/SimpleApp.scala
在./sparkapp/src/main/scala 下建立一个名SimpleApp.scala 的文件(vim ./sparkapp/src/main/scala/SimpleApp.scala),添加代码如下:
/* SimpleApp.scala */
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
object SimpleApp {
def main(args: Array[String]) {
val logFile = " hdfs://localhost:9000/user/hadoop/test.txt"
val conf = new SparkConf().setAppName("Simple Application")
val sc = new SparkContext(conf)
val logData = sc.textFile(logFile, 2)
val num = logData.count()
printf("The num of this file is %d", num)
}
}
在~/sparkapp这个目录中新建文件simple.sbt,命令如下:
cd ~/sparkapp
vim simple.sbt
在simple.sbt中添加如下内容,声明该独立应用程序的信息以及与 Spark 的依赖关系:
name := "Simple Project"
version := "1.0"
scalaVersion := "2.11.12"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.4.0"
为保证 sbt 能正常运行,先执行如下命令检查整个应用程序的文件结构:
cd ~/sparkapp
find .
接着,我们就可以通过如下代码将整个应用程序打包成 JAR(首次运行同样需要下载依赖包 ):
cd ~/sparkapp #一定把这个目录设置为当前目录
/usr/local/sbt/sbt package
/usr/local/spark/bin/spark-submit --class "SimpleApp" ~/sparkapp/target/scala-2.11/simple-project_2.11-1.0.jar
2.编写独立应用程序实现数据去重
对于两个输入文件 A 和 B,编写 Spark 独立应用程序(推荐使用 Scala 语言),对两个文件进行合并,并剔除其中重复的内容,得到一个新文件 C。下面是输入文件和输出文件的一个样例,供参考。
输入文件 A 的样例如下:
20170101 x
20170102 y
20170103 x
20170104 y
20170105 z
20170106 z
输入文件 B 的样例如下:
20170101 y
20170102 y
20170103 x
20170104 z
20170105 y
根据输入的文件 A 和 B 合并得到的输出文件 C 的样例如下:
20170101 x
20170101 y
20170102 y
20170103 x
20170104 y
20170104 z
20170105 y
20170105 z
20170106 z
操作过程如下:
在终端中执行如下命令创建一个文件夹 sparkapp 作为应用程序根目录:
cd ~ # 进入用户主文件夹
mkdir ./sparkapp # 创建应用程序根目录
mkdir -p ./sparkapp/src/main/scala # 创建所需的文件夹结构
vim ./sparkapp/src/main/scala/RemDupApp.scala
代码:
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
import org.apache.spark.HashPartitioner
object RemDup {
def main(args: Array[String]) {
// 创建 Spark 配置和上下文
val conf = new SparkConf().setAppName("RemDup").setMaster("local[*]")
val sc = new SparkContext(conf)
// 读取输入文件 A 和 B
val dataFileA = "file:///usr/local/hadoop/A.txt"
val dataFileB = "file:///usr/local/hadoop/B.txt"
val dataA = sc.textFile(dataFileA)
val dataB = sc.textFile(dataFileB)
// 合并文件 A 和 B
val data = dataA.union(dataB)
// 去重并排序
val res = data.filter(_.trim().length > 0)
.map(line => (line.trim, ""))
.partitionBy(new HashPartitioner(1))
.groupByKey()
.sortByKey()
.keys
// 保存结果到文件 C
res.saveAsTextFile("file:///usr/local/hadoop/result")
// 关闭 Spark 上下文
sc.stop()
}
}
在~/sparkapp这个目录中新建文件simple.sbt,命令如下:
cd ~/sparkapp
vim simple.sbt
在simple.sbt中添加如下内容,声明该独立应用程序的信息以及与 Spark 的依赖关系:
name := "Simple Project"
version := "1.0"
scalaVersion := "2.11.12"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.4.0"
接着,我们就可以通过如下代码将整个应用程序打包成 JAR:
/usr/local/sbt/sbt package
/usr/local/spark/bin/spark-submit --class "RemDup" ~/sparkapp/target/scala-2.11/simple-project_2.11-1.0.jar
cat /usr/local/hadoop/result/part-00000
3.编写独立应用程序实现求平均值问题
每个输入文件表示班级学生某个学科的成绩,每行内容由两个字段组成,第一个是学生名字,第二个是学生的成绩;编写 Spark 独立应用程序求出所有学生的平均成绩,并输出到一个新文件中。下面是输入文件和输出文件的一个样例,供参考。
Algorithm 成绩:
小明 92
小红 87
小新 82
小丽 90
Database 成绩:
小明 95
小红 81
小新 89
小丽 85
Python 成绩:
小明 82
小红 83
小新 94
小丽 91
平均成绩如下:
(小红,83.67)
(小新,88.33)
(小明,89.67)
(小丽,88.67)
操作过程如下:
hdfs dfs -put Algorithm.txt
hdfs dfs -put Database.txt
hdfs dfs -put Python.txt
cd ~
hadoop@zhuyangyu-virtual-machine:~$ mkdir ./sparkapp
hadoop@zhuyangyu-virtual-machine:~$ mkdir -p ./sparkapp/src/main/scala
hadoop@zhuyangyu-virtual-machine:~$ vim ./sparkapp/src/main/scala/AverageScore.scala
代码:
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
import org.apache.spark.SparkConf
object AverageScore {
def main(args: Array[String]) {
// 创建 Spark 配置和上下文
val conf = new SparkConf().setAppName("AverageScore").setMaster("local[*]")
val sc = new SparkContext(conf)
// 读取输入文件
val inputFiles = args.toList // 从命令行参数获取输入文件列表
val data = inputFiles.map(file => sc.textFile(file)).reduce((rdd1, rdd2) => rdd1.union(rdd2))
// 处理数据,计算平均成绩
val scores = data.map(line => {
val parts = line.split("\\s+")
(parts(0), parts(1).toDouble)
})
val scoreSumsAndCounts = scores.combineByKey(
(score: Double) => (score, 1),
(acc: (Double, Int), score) => (acc._1 + score, acc._2 + 1),
(acc1: (Double, Int), acc2: (Double, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
val averageScores = scoreSumsAndCounts.mapValues {
case (sum, count) => BigDecimal(sum / count).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble
}
// 将结果合并到一个分区并保存到文件
averageScores.coalesce(1).saveAsTextFile("hdfs:///user/hadoop/average_scores")
// 关闭 Spark 上下文
sc.stop()
}
}
hadoop@zhuyangyu-virtual-machine:~$ cd ~/sparkapp
hadoop@zhuyangyu-virtual-machine:~/sparkapp$ vim build.sbt
name := "AverageScore"
version := "1.0"
scalaVersion := "2.11.12"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.4.0"
hadoop@zhuyangyu-virtual-machine:~/sparkapp$ find .
/usr/local/sbt/sbt package
/usr/local/spark/bin/spark-submit --class "AverageScore" ~/sparkapp/target/scala-2.11/averagescore_2.11-1.0.jar hdfs:///user/hadoop/Algorithm.txt hdfs:///user/hadoop/Database.txt hdfs:///user/hadoop/Python.txt
hdfs dfs -ls /user/hadoop/average_scores
hdfs dfs -cat /user/hadoop/average_scores/part-00000
四、心得体会:
在这次实验中,我通过使用 Spark 对本地文件系统和 HDFS 文件系统进行了访问和操作,完成了多个任务,包括数据读取、去重和求平均值的计算。这次实验不仅让我更深入地了解了 Spark 的基础操作和应用程序编写流程,还提升了我在大数据处理方面的实践能力。首先,我通过在 Spark Shell 中读取本地文件和 HDFS 文件,掌握了基本的文件读取操作。接着,我编写了一个独立的 Scala 应用程序,从 HDFS 中读取文件并统计行数。这个过程让我熟悉了 Spark 应用程序的编写、编译和运行流程,包括如何配置 SparkConf、创建 SparkContext 以及使用 Spark 的 RDD API 进行数据处理。通过将应用程序打包成 JAR 文件并使用 spark-submit 提交运行,我深刻理解了 Spark 应用程序的部署和执行过程。在数据去重的任务中,我编写了一个 Scala 应用程序来合并两个输入文件,并剔除其中的重复内容。通过这次实验,我学会了如何使用 Spark 的 RDD 转换操作,如 union、filter、map 和 groupByKey 等,来实现数据的合并和去重。此外,我还掌握了如何使用 partitionBy 和 sortByKey 对数据进行分区和排序操作。这些操作让我对 Spark 的分布式数据处理模型有了更深入的理解。在最后一个任务中,我编写了一个 Scala 应用程序来计算班级学生的平均成绩。这次实验让我学习了如何使用 combineByKey 来实现复杂的聚合操作,并掌握了使用 BigDecimal 类对结果进行格式化处理,确保输出结果保留两位小数。在处理完数据后,我将结果保存到 HDFS 中,并检查了输出文件的正确性。总而言之,这次实验让我对 Spark 的基本操作和应用程序开发有了全面的了解。通过实践,我不仅掌握了如何使用 Spark 进行大规模数据处理,还提升了我的编程技能和问题解决能力。这些知识和技能对我未来在大数据领域的学习和工作将会有很大的帮助。我深刻认识到 Spark 作为一个强大的分布式数据处理框架,其在处理海量数据时的高效性和灵活性,使其成为大数据处理领域的重要工具。通过这次实验,我对 Spark 的应用有了更深入的理解,并为今后更复杂的应用开发打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者 无
实验名称 实验八:Flink 初级编程实践 日期 2024年7月5日
三、实验目的:
(1)通过实验掌握基本的Flink 编程方法。
(2)掌握用IntelliJ IDEA工具编写Flink 程序的方法。
二、实验环境:
(1)操作系统:Linux(Ubuntu 20.04)。
(2)Hadoop版本:3.1.3。
(2)Flink版本:1.9.1。
(3)IntelliJ IDEA。
(4)jdk:1.8。
三、实验内容:
1. 使 用IntelliJIDEA 工具开发WordCount程序
在 Linux 系统中安装IntelliJ IDEA,使用 IntelliJ IDEA工具开发WordCount程序,并打包成JAR 文件,提交到Flink中运行。
首先要启动Flink。启动进入IDEA,如下图所示,新建一个项目。填写GroupId和ArtifactId。这里的GroupId是dblab,ArtifactId是FlinkWordCount。设置Project name为FlinkWordCount。
打开pom.xml文件,输入如下内容:
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>
创建Package输入package的名称为“cn.edu.xmu”。再新建一个java class文件。输入文件名称WordCountData。WordCountData.java用于提供原始数据,其内容如下:
package cn.edu.xmu;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
public class WordCountData {
public static final String[] WORDS=new String[]{"To be, or not to be,--that is the question:--", "Whether \'tis nobler in the mind to suffer", "The slings and arrows of outrageous fortune", "Or to take arms against a sea of troubles,", "And by opposing end them?--To die,--to sleep,--", "No more; and by a sleep to say we end", "The heartache, and the thousand natural shocks", "That flesh is heir to,--\'tis a consummation", "Devoutly to be wish\'d. To die,--to sleep;--", "To sleep! perchance to dream:--ay, there\'s the rub;", "For in that sleep of death what dreams may come,", "When we have shuffled off this mortal coil,", "Must give us pause: there\'s the respect", "That makes calamity of so long life;", "For who would bear the whips and scorns of time,", "The oppressor\'s wrong, the proud man\'s contumely,", "The pangs of despis\'d love, the law\'s delay,", "The insolence of office, and the spurns", "That patient merit of the unworthy takes,", "When he himself might his quietus make", "With a bare bodkin? who would these fardels bear,", "To grunt and sweat under a weary life,", "But that the dread of something after death,--", "The undiscover\'d country, from whose bourn", "No traveller returns,--puzzles the will,", "And makes us rather bear those ills we have", "Than fly to others that we know not of?", "Thus conscience does make cowards of us all;", "And thus the native hue of resolution", "Is sicklied o\'er with the pale cast of thought;", "And enterprises of great pith and moment,", "With this regard, their currents turn awry,", "And lose the name of action.--Soft you now!", "The fair Ophelia!--Nymph, in thy orisons", "Be all my sins remember\'d."};
public WordCountData() {
}
public static DataSet<String> getDefaultTextLineDataset(ExecutionEnvironment env){
return env.fromElements(WORDS);
}
}
按照刚才同样的操作,创建第2个文件WordCountTokenizer.java。
WordCountTokenizer.java用于切分句子,其内容如下:
package cn.edu.xmu;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;
public class WordCountTokenizer implements FlatMapFunction<String, Tuple2<String,Integer>>{
public WordCountTokenizer(){}
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
String[] tokens = value.toLowerCase().split("\\W+");
int len = tokens.length;
for(int i = 0; i<len;i++){
String tmp = tokens[i];
if(tmp.length()>0){
out.collect(new Tuple2<String, Integer>(tmp,Integer.valueOf(1)));
}
}
}
}
按照刚才同样的操作,创建第3个文件WordCount.java。
WordCount.java提供主函数,其内容如下:
package cn.edu.xmu;
import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.AggregateOperator;
import org.apache.flink.api.java.utils.ParameterTool;
public class WordCount {
public WordCount(){}
public static void main(String[] args) throws Exception {
ParameterTool params = ParameterTool.fromArgs(args);
ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.getConfig().setGlobalJobParameters(params);
Object text;
//如果没有指定输入路径,则默认使用WordCountData中提供的数据
if(params.has("input")){
text = env.readTextFile(params.get("input"));
}else{
System.out.println("Executing WordCount example with default input data set.");
System.out.println("Use -- input to specify file input.");
text = WordCountData.getDefaultTextLineDataset(env);
}
AggregateOperator counts = ((DataSet)text).flatMap(new WordCountTokenizer()).groupBy(new int[]{0}).sum(1);
//如果没有指定输出,则默认打印到控制台
if(params.has("output")){
counts.writeAsCsv(params.get("output"),"\n", " ");
env.execute();
}else{
System.out.println("Printing result to stdout. Use --output to specify output path.");
counts.print();
}
}
}
项目目录结构如下图所示。
执行成功以后,可以看到词频统计结果。
下面要把代码进行编译打包,打包成jar包。为此,需要做一些准备工作。
如下图所示,进入设置界面。
如下图所示进入Project Structure界面。
如下图所示进行设置。
如下图所示进行设置。
如下图所示进行设置。
如下图所示进行设置。
如下图所示进行设置。在搜索框中输入“WordCount”就会自动搜索到主类,然后在搜索到的结果条上双击鼠标。
如下图所示,设置META-INF目录。
如下图所示进行设置。
如下图所示,进入编译打包菜单。
如下图所示,编译打包成功以后,可以看到生成的FlinkWordCount.jar文件。
最后,到Flink中运行FlinkWordCount.jar。这里一定要注意,要确认已经开启Flink系统。运行的命令和执行结果如下图所示。
/usr/local/flink/bin/flink run -class cn.edu.xmu.WordCount ~/IdeaProjects/FlinkWordCount/out/artifacts/FlinkWordCount_jar/FlinkWordCount.jar
2. 数据流词频统计
使用Linux 系统自带的 NC 程序模拟生成数据流,不断产生单词并发送出去。编写 Flink 程序对 NC 程序发来的单词进行实时处理,计算词频,并把词频统计结果输出。要求 先在IntelliJ IDEA中开发和调试程序,再打成JAR包部署到Flink 中运行。
仿照前面的 FlinkWordCount 项目的开发流程,在 IntelliJ IDEA 中新建一个项目,名称为“FlinkWordCount2”。新建一个pom.xm 文件,内容和前面的 FlinkWordCount 项目中的pom.xml 一样。新建一个代码文件 WordCount.java,内容如下:
package cn.edu.xmu;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
public class WordCount {
public static void main(String[] args) throws Exception {
//定义socket的端口号
int port;
try {
ParameterTool parameterTool = ParameterTool.fromArgs(args);
port = parameterTool.getInt("port");
} catch (Exception e) {
System.err.println("指定port参数,默认值为9000");
port = 9000;
}
//获取运行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//连接socket获取输入的数据
DataStreamSource<String> text = env.socketTextStream("127.0.0.1", port, "\n");
//计算数据
DataStream<WordWithCount> windowCount = text.flatMap(new FlatMapFunction<String, WordWithCount>() {
public void flatMap(String value, Collector<WordWithCount> out) throws Exception {
String[] splits = value.split("\\s");
for (String word : splits) {
out.collect(new WordWithCount(word, 1L));
}
}
})//打平操作,把每行的单词转为<word,count>类型的数据
.keyBy("word")//针对相同的word数据进行分组
.timeWindow(Time.seconds(2), Time.seconds(1))//指定计算数据的窗口大小和滑动窗口大小
.sum("count");
//把数据打印到控制台
windowCount.print()
.setParallelism(1);//使用一个并行度
//注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行
env.execute("streaming word count");
}
/**
* 主要为了存储单词以及单词出现的次数
*/
public static class WordWithCount {
public String word;
public long count;
public WordWithCount() {
}
public WordWithCount(String word, long count) {
this.word = word;
this.count = count;
}
@Override
public String toString() {
return "WordWithCount{" +
"word='" + word + '\'' +
", count=" + count +
'}';
}
}
}
仿照前面的 FlinkWordCount 项目进行编译打包,编译打包成功以后,可以看到生成的FlinkWordCount2.jar 文件。
首先启动 Flink 系统。然后打开一个 Linux 终端,使用如下命令启动 NC 程序:
nc -lk 9999
然后,再新建一个 Linux 终端,使用如下命令启动 FlinkWordCount2 词频统计程序:
cd /usr/local/flink
./bin/flink run --class cn.edu.xmu.WordCount /home/hadoop/IdeaProjects/FlinkWordCount2/out/artifacts/FlinkWordCount2_jar/FlinkWordCount2.jar --port 9999
这时可以到浏览器中查看结果。在 Linux 系统中打开一个浏览器,在里面输入“http://localhost:8081”,进入 Flink 的 WEB 管理页面,然后,点击左侧的“Task Managers”,会弹出右边的新页面,在页面中点击链接(如图 A-52 所示)。
四、心得体会:
在本次实验中,我深入学习并掌握了Flink的基本编程方法,尤其是在IntelliJ IDEA环境下开发Flink程序的步骤和技巧。这不仅提升了我对Flink技术的理解,也增强了我在大数据处理方面的实战能力。通过实验,我熟悉了如何在Linux环境下安装和配置Flink,以及如何启动和管理Flink集群。在编写WordCount程序的过程中,我学习了如何使用Flink进行数据流处理,从数据读取、数据转换到结果输出的整个流程。此外,通过实际操作,我体验到了Flink强大的流处理能力和其在大数据实时处理中的优势。在使用IntelliJ IDEA进行开发时,我学会了如何配置项目的依赖,创建和组织Java类文件,以及如何使用Maven进行项目的构建和打包。这些技能不仅对Flink程序的开发有帮助,也提升了我在Java开发中的综合能力。在实验的第二部分,我通过模拟数据流并实时计算词频的任务,进一步理解了Flink在实时数据处理中的应用。通过启动NC工具生成数据流,并利用Flink进行实时处理和统计,我看到了Flink在处理高吞吐量、低延迟数据流方面的优势。这对我未来在大数据领域的应用开发提供了很大的启示和帮助。总体而言,这次实验不仅让我掌握了Flink的基本使用方法和开发技巧,还增强了我在大数据处理和实时流处理方面的实际操作能力。通过不断地实践和探索,我对Flink的应用场景有了更深刻的理解,也为我未来的学习和工作打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第二章大数据实验环境搭建 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握Linux系统及相关软件的基本使用方法。
- 了解并掌握JDK、Scala、Hadoop、Spark和MySQL数据库的安装与基本使用。
- 能够在大数据环境下搭建并配置相关软件,为后续大数据处理和分析打下基础。
二、实验环境:
硬件环境:计算机:4核处理器,16GB内存,500GB硬盘
软件环境:
操作系统:Ubuntu 20.04
JDK版本:JDK 8
Scala版本:Scala 2.11.8
Hadoop版本:Hadoop 2.7.3
Spark版本:Spark 2.4.0
数据库:MySQL 8.0
其他工具:Vim编辑器
三、实验内容:
(写出主要的内容)
2.1 Linux 系统及相关软件使用方法
之前实验已经学习并安装,略。
2.2 JDK的安装
2.3 Scala的安装
到Scala 官方网站下载与Linux 系统对应的安装包scala-2.11.8.tgz, 或者也可以直接从本书官网的“下载专区”的“软件”目录中下载 scala-2.11.8.tgz, 这里下载后的文件被保存在“~/下载” 目录下。然后,执行如下命令进行安装。
$ cd ~
$ sudo tar -zxf ~/Downloads/scala-2.11.8.tgz -C /usr/local # 解压到 /usr/local 目录
$ cd /usr/local/
$ sudo mv ./scala-2.11.8/ ./scala # 将目录名改为 scala
$ sudo chown -R hadoop ./scala # 修改目录权限,让 hadoop 用户拥有对 scala 目录的操作权限
编辑 ~/.bashrc 文件,将 Scala 命令添加到 PATH 环境变量中。使用 vim 编辑器打开 .bashrc 文件:
$vim ~/.bashrc
在文件的开头位置增加以下语句:
export PATH=$PATH:/usr/local/scala/bin
保存文件并退出vim 编辑器。然后执行如下命令让该环境变量生效。
$source ~/.bashrc
设置好以后,可以使用scala命令来检验一下设置是否正确。
$scala
2.4 Hadoop的安装和基本使用方法
安装Hadoop
cd /usr/local/hadoop
./bin/hadoop version
HDFS操作常用Shell 命令
1. 目录操作
创建用户目录和普通目录
在首次使用 HDFS 前,需要为用户创建目录。假设用户为 hadoop,在 HDFS 中创建其用户目录:
cd /usr/local/hadoop
./bin/hdfs dfs -mkdir -p /user/hadoop
列出目录内容
列出当前用户(例如 hadoop)的根目录内容:
./bin/hdfs dfs -ls .
列出指定目录(例如 /user/hadoop)下的内容:
./bin/hdfs dfs -ls /user/hadoop
列出 HDFS 上所有目录的内容:
./bin/hdfs dfs -ls /*
创建目录
在当前用户的目录下创建 input 目录:
./bin/hdfs dfs -mkdir input
在 HDFS 根目录下创建 input 目录:
./bin/hdfs dfs -mkdir /input
删除目录
删除 HDFS 根目录下的 input 目录及其内容:
./bin/hdfs dfs -rm -r /input
2. 文件操作
上传文件
将本地文件系统中的 myLocalFile.txt 上传到 HDFS 的 input 目录下:
$ ./bin/hdfs dfs -put /home/hadoop/myLocalFile.txt input
查看文件
查看 HDFS 中 input 目录下的 myLocalFile.txt 文件内容:
$ ./bin/hdfs dfs -cat input/myLocalFile.txt
下载文件
将 HDFS 中的 input/myLocalFile.txt 文件下载到本地文件系统的 /home/hadoop/下载/ 目录:
$ ./bin/hdfs dfs -get input/myLocalFile.txt /home/hadoop/下载
查看下载的文件内容:
$ cd ~/下载
$ cat myLocalFile.txt
将 HDFS 中的 input/myLocalFile.txt 文件复制到 HDFS 的根目录下的 input 目录:
$ ./bin/hdfs dfs -cp input/myLocalFile.txt /input
2.5 Spark的安装和基本使用方法
2.5.1.Spark的安装
配置完成后就可以直接使用Spark, 不需要像Hadoop 那样运行启动命令。通过运行Spark 自 带的实例SparkPi, 可以验证 Spark 是否安装成功,命令如下。
$cd /usr/local/spark
$bin/run-example SparkPi
执行时会输出很多屏幕信息,不容易找到最终的输出结果,为了从大量的输出信息中快速找 到我们想要的执行结果,可以通过 grep 命令进行过滤。
$bin/run-example SparkPi 2>&1 I grep "Pi is roughly"
2.5.2 在spark-shell中运行代码
1.启动Spark Shell
cd /usr/local/spark
bin/spark-shell
2.加载text文件
spark创建sc,可以加载本地文件和HDFS文件创建RDD。这里用Spark自带的本地文件README.md文件测试。
val textFile = sc.textFile("file:///usr/local/spark/README.md")
加载HDFS文件和本地文件都是使用textFile,区别是添加前缀(hdfs://和file:///)进行标识。
3.简单RDD操作
//获取RDD文件textFile的第一行内容
textFile.first()
//获取RDD文件textFile所有项的计数
textFile.count()
//抽取含有“Spark”的行,返回一个新的RDD
val lineWithSpark = textFile.filter(line => line.contains("Spark"))
//统计新的RDD的行数
lineWithSpark.count()
可以通过组合RDD操作进行组合,可以实现简易MapReduce操作
//找出文本中每行的最多单词数
textFile.map(line => line.split(" ").size).reduce((a, b) => if (a > b) a else b)
退出Spark Shell
2.6 MySQL数据库的安装和基本使用方法
下载最新版mysql:sudo apt install -y mysql-server
启动MySQL服务
sudo systemctl start mysql
sudo systemctl enable mysql
sudo systemctl status mysql
sudo mysql
# 登录mysql,在默认安装时如果没有让我们设置密码,则直接回车就能登录成功。
mysql -uroot -p
# 设置密码 mysql8.0
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码';
配置8.0版本参考:我这里通过这种方式没有实现所有IP都能访问;我是通过直接修改配置文件才实现的,MySQL8.0版本把配置文件 my.cnf 拆分成mysql.cnf 和mysqld.cnf,我们需要修改的是mysqld.cnf文件:sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
修改 bind-address,保存后重启MySQL即可。
bind-address = 0.0.0.0
重启MySQL重新加载一下配置:
sudo systemctl restart mysql
2.6.2 MySQL 常用操作
显示数据库:
mysql> show databases;
默认情况下会显示 mysql 和 test 数据库。
显示数据库中的表:mysql> use mysql;
使用 mysql 数据库mysql> show tables;
显示数据表的结构:mysql> describe 表名;
查询表中的记录:mysql> select * from 表名;
创建数据库:mysql> create database 库名;
例如:
mysql> create database aaa;
创建表:
mysql> use 库名; -- 使用特定数据库
mysql> create table 表名(字段设定列表);
例如,在 aaa 数据库中创建 person 表:
mysql> use aaa;
mysql> create table person(id int(3) auto_increment not null primary key, xm varchar(10), xb varchar(2), csny date);
增加记录:
mysql> insert into person values (null, '张三', '男', '1997-01-02');
mysql> insert into person values (null, '李四', '女', '1996-12-02');
修改记录:
mysql> update person set csny = '1971-01-10' where xm = '张三';
删除记录:
mysql> delete from person where xm = '张三';
删除数据库和表:
mysql> drop database 库名;
mysql> drop table 表名;
查看 MySQL 版本:mysql> show variables like 'version';或者mysql> select version();
四、心得体会:
通过此次实验,我深刻理解了大数据环境的搭建及其重要性。在安装和配置过程中,我遇到了一些问题,但通过查阅资料和不断尝试,最终成功解决。这不仅提升了我的动手能力和问题解决能力,也让我对大数据相关技术有了更深入的认识。特别是在安装Hadoop和Spark时,我学会了如何配置环境变量以及如何在实际项目中使用这些工具进行大数据处理和分析。此外,通过安装和使用MySQL数据库,我掌握了数据库的基本操作,为以后的数据管理和应用开发奠定了基础。总之,这次实验为我后续的大数据学习和应用提供了宝贵的经验和技术支持。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第三章IntelliJIDEA开发工具的安装和使用方法 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握IntelliJ IDEA开发工具的安装和基本使用方法。
- 学习如何在IntelliJ IDEA中安装Scala插件并配置开发环境。
- 熟悉使用IntelliJ IDEA进行Scala开发,特别是WordCount程序的开发和运行。
- 通过实战操作,巩固对开发工具和大数据处理的理解和应用能力。
二、实验环境:
硬件环境:计算机:4核处理器,16GB内存,500GB硬盘
软件环境:
操作系统:Ubuntu 20.04
IntelliJ IDEA版本:IntelliJ IDEA 2024.1.2
JDK版本:JDK 8
Scala版本:Scala 2.11.8
Spark版本:Spark 2.4.0
其他工具:Vim编辑器
三、实验内容:
3.1 IntelliJ IDEA的安装
1.idea的安装
cd ~/下载
sudo tar -zxf ~/下载/ideaIC-2024.1.2.tar.gz -C /usr/local/
cd /usr/local
sudo mv ./idea-IC-241.17011.79 ./idea
sudo chown -R hadoop:hadoop ./idea
cd /usr/local/idea/bin
./idea.sh
2.配置IDEA环境变量
配置IDEA环境变量的目的是无论终端在哪个工作目录,都可以通过idea.sh指令启动IDEA,不用进入到IDEA的环境目录了。
我们通过vim指令对环境变量文件进行编辑:
vim ~/.bashrc
然后将如下的内容添加至~/.bashrc文件
export IDEA_HOME=/usr/local/idea
export PATH=:$PATH:${IDEA_HOME}/bin
然后通过:wq进行保存即可。
接着我们让系统重新加载我们修改好的环境变量:
source ~/.bashrc
之后我们就可以在任意目录下输入idea.sh启动IDEA图形化界面了。
3.给IDEA安装中文插件(可用可不用)
我们首先启动IDEA,认真阅读使用协议后,选择同意,并点击Continue.选择完毕后,就进入了IDEA的欢迎界面了,我们点击左侧的Plugins.在顶部的搜索框里面,输入Chinese,就可以找到中文的汉化插件。
3.2 Scala 插件安装
先打开idea,在线安装Scala插件,单击Scala下面的“Install”按钮。
在这里我发现一直没起效果,虚拟机有点卡,所以我手动安装了
如果已经启动进入了IDEA 开发窗口,也可以选择菜单“File”下的子菜单“Settings.. ” 进入 “Plugins”窗口。会弹出图所示的“Plugins”窗口,点击齿轮设置,单击窗口右下角的“Install plugin from disk.. ” 按钮。
成功了
3.3 使用IDEA 开发WordCount 程序
创建一个新项目WordCount,单击窗口中的 “CreateNew Project” 项,打开新建项目对话框,开始创建一个新项目。如果已经启动进入了IDEA 开发界面,也可以 通过菜单“File→New→Project” 打开一个新建项目对话框。
在弹出的窗口中,在“Groupld”对话框中填入“dblab”,在 “ArtifactId”对话 框中填入 “WordCount”, 然后,单击 “create” 按钮。
为WordCount 项目添加 Scala 框架支持
打开setting,点击Appearance &Behavior,选择Menus and Toolbars
再选择Project View Popup Menu
选择new,点击+号
Add Action 添加scala。
然后再导一下SDK,如下图
这样就成功了。
设置项目目录
在“src”目录下的“main”子目录上单击鼠标右键,选择“New > Directory”来创建一个名为“scala”的新目录。
右键单击“scala”目录,选择“Mark Directory as > Sources Root”将其标记为源代码目录。
右键单击“java”目录,选择“Delete...”删除该目录。
新建Scala 代码文件,右键创建新文件,在 scala 目录上单击鼠标右键,选择“New”,然后选择“Scala Class”。在弹出的窗口中(图3-37),输入类名 “WordCount”,并在 “Kind” 下拉框中选择 “Object”,然后单击 “OK”。
在生成的 WordCount.scala 文件中,输入以下代码:
上面代码是对Linux 系统中的“/usr/local/spark/mycode/wordcount word.txt”文件进行词频统计,统计出word.txt中每个单词出现的次数。
打开项目中的pom.xml文件,清空文件里面的内容,然后输入如下内容。
运行 WordCount程序
在 IntelliJ IDEA 中打包 WordCount 程序并生成 JAR 包:
在 IntelliJ IDEA 中,选择菜单栏中的“File”,然后选择“Project Structure...”。在弹出的项目结构窗口中,依次单击“Artifacts”、绿色加号、“JAR”和“From modules with dependencies...”。如下图所示,进入设置界面。
在 Output Layout 中,确保 WordCount.jar 和 WordCount compile output 是唯一保留的条目,删除其他多余的条目。得到:
最终得到:
把JAR 包提交到Spark中运行
cd /usr/local/spark
./bin/spark-submit --class "WordCount" ~/IdeaProjects/WordCount/out/artifacts/WordCount_jar/WordCount.jar
四、心得体会:
通过本次实验,我深刻体会到开发工具在编程过程中的重要性。IntelliJ IDEA作为一款强大的集成开发环境,不仅提供了丰富的插件支持,还具有高度的可配置性,使得开发过程更加高效和便捷。在安装和配置IntelliJ IDEA的过程中,我熟悉了环境变量的设置以及插件的安装方法,增强了对开发环境配置的理解。在实际开发过程中,通过WordCount项目的创建和运行,我掌握了如何在IDEA中进行Scala开发。特别是在Scala环境的配置、项目结构的管理以及代码编写和调试等方面,积累了宝贵的实践经验。此外,通过将生成的JAR包提交到Spark中运行,我进一步理解了大数据处理的流程和方法。此次实验不仅提升了我的技术能力,也让我认识到在开发过程中解决问题和自学的重要性。面对安装和配置中的各种问题,我学会了通过查阅资料和动手实践来解决,为以后的学习和工作打下了坚实的基础。总之,这次实验是一次非常有价值的学习体验,使我对大数据开发工具和技术有了更全面和深入的认识。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第四章ETL工具Kettle的安装和使用方法 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握ETL工具Kettle的安装与配置方法。
- 学习Kettle的基本操作及其在数据集成中的应用。
- 实现将本地文件通过Kettle加载到HDFS的过程,熟悉Kettle与Hadoop环境的集成。
- 提高对ETL流程的理解和实践能力,为后续的大数据处理工作打下基础。
二、实验环境:
硬件环境:计算机:4核处理器,16GB内存,500GB硬盘
软件环境:
操作系统:Ubuntu 20.04
Kettle版本:Kettle 8.2.0.0-342
Hadoop版本:Hadoop 3.2.1
MySQL驱动程序:MySQL Connector/J
其他工具:Vim编辑器
三、实验内容:
4.1.安装Kettle
下载data-integration.zip文件到本地, 这里假设保存到“~/下载”目录下。打开一个Linux终端,执行如下命令创建Kettle的安装目录
cd /usr/local
sudo mkdir kettle
sudo chown -R hadoop ./kettle #为用户赋予针对kettle目录的操作权限
把安装包pdi-ce-8.2.0.0-342.zip解压到安装目录,命令如下:
cd ~
unzip ~/下载/pdi-ce-8.2.0.0-342.zip -d /usr/local/kettle
复制MySQL数据库驱动程序JAR包到 /usr/local/ kettle/data-integration/lib
启动Kettle中的 Spoon
cd /usr/local/kettle/data-integration
chmod +x spoon.sh
sudo apt-get install libwebkitgtk-1.0
./spoon.sh
4.2 使用Kettle把数据加载到HDFS中
已经创建了一个Linux文件“~/word.txt”, 该文件里面包含了几行英文语句。
在将数据通过Kettle传输到HDFS之前,需要对Kettle进行配置。在Linux终端中输入如下命令。
cd /usr/local/kettle/data-integration/plugins/pentaho-big-data-plugin/hadoop-configurations/cdh514
vim config.properties
在 config.properties 文件的最后一行添加如下内容:
authentication.superuser,provider=NO_AUTH
首先,备份现有的配置文件:
cd /usr/local/kettle/data-integration/plugins/pentaho-big-data-plugin/hadoop-configurations/cdh514
mv core-site.xml ./core-site.xml.bak
mv mapred-site.xml ./mapred-site.xml.bak
mv yarn-site.xml ./yarn-site.xml.bak
然后,将 Hadoop 中的相关配置文件复制到 Kettle 中:
cp /usr/local/hadoop/etc/hadoop/core-site.xml ./
cp /usr/local/hadoop/etc/hadoop/mapred-site.xml ./
cp /usr/local/hadoop/etc/hadoop/yarn-site.xml ./
在Spoon界面的顶部菜单选择“文件 -> 新建 -> 作业”(也可以使用 Ctrl+Alt+N 组合键新建作业),然后保存文件名为“local_to_hdfs”。
选择Active Shim,在Spoon界面中,选择主菜单“工具” -> “Hadoop Distribution...”,在对话框中选择“Cloudera CDH 6.1.0”,如图3-2所示,点击OK按钮确定后重启Spoon。
在左侧项目栏的“主对象树”中,用鼠标右键单击“Hadoop clusters”,选择“New Cluster”项,会弹出设置界面(见图4-6),在“Cluster Name”项中填入“Hadoop local”,把“Hostname”设置为“localhost”,把“Port”设置为“9000”。需要注意的是,这里的端口号必须与Hadoop配置文件 core-site.xml 里面的设置相同,其他参数可以不用设置。
按照上述方法设置以后,可以单击“测试(T)” 按钮,测试是否可以正常连接HDFS(在测试 之前,需要确保已经启动了HDFS) 。如果可以正常连接HDFS, 则会出现图4-7所示的测试结果, 在 “Active ShimLoad”“Shim Configuration Verification”“Hadoop File System Connection”“User Home Directory Access”“Root Directory Access” 和 “Verify User Home Permissions” 等选项的前面都会出 现绿色的“ √ ”;注意,剩余的选项和MapReduce 、Oozie和 Zookeeper 等有关,因为暂时还用不到 这些组件的功能,没有进行配置,所以暂时都是“×”,可以在需要用到的时候再进行配置。
通过网络查询发现缺一个guava.jar包,hadoop-3.2.1(路径:hadoop\share\hadoop\common\lib)中该jar包为 guava-27.0-jre.jar。
将该包考到pdi-ce-8.2\data-integration\plugins\pentaho-big-data-plugin\hadoop-configurations\hdp30\lib\client下,重启spoon后问题解决。
在左侧项目栏的“核心对象”选项卡中,选择“通用”下面的 “START”,拖曳1个“START”控件到右侧的设计区域,如下:
在左侧项目栏的核心对象中,选择 “Big Data” 下面的 “Hadoop Copy Files” 控件,拖曳1个 “Hadoop Copy Files” 控件到右侧的设计区域,然后,在 “Hadoop Copy Files” 控件与 “START”控件之间建立连接如下:
在设计区域的“Hadoop Copy Files”控件图标上双击,将会弹出属性设置对话框(见图4-10)。 在 “Files” 选项卡中,在 “Source Environment” 下面的下拉列表中选择 “Local”, 在“源文件/ 目录”中设置数据源所在的目录,比如“file://home/hadqop/word.txt”,在“Destination Environment” 下面的下拉列表中选择 “Hadoop local”,在“目标文件/目录”中设置数据要上传到HDFS 的目录 信息,如“/input_spark” (注意,要事先在HDFS 中创建该目录)。最后,单击“确定”按钮,返 回设计区域,并对当前设计结果进行保存(可以直接按<Ctrl+S>组合键进行保存)。
hdfs dfs -mkdir /input_spark
在设计区域顶部快捷图标栏中,单击三角形“运行”按钮,开始运行作业。
执行后,在作业设计区域底部可以看到执行结果信息
cd /usr/local/hadoop
./bin/hdfs dfs -ls /input_spark
四、心得体会:
通过本次实验,我深入理解了ETL工具Kettle的安装和使用方法。首先,在安装和配置过程中,我学会了如何下载、解压和配置Kettle,以及如何将MySQL驱动程序集成到Kettle中。虽然中途遇到了一些依赖问题,但通过查阅资料并进行调整,最终成功解决了这些问题。在实际操作中,我掌握了如何使用Kettle将本地文件加载到HDFS中。在配置和操作Kettle的过程中,我了解了Kettle的界面和基本功能,并学会了如何通过图形化界面创建和管理ETL作业。此外,通过配置Hadoop集群和测试连接,我进一步熟悉了Hadoop环境的配置和使用方法。此次实验不仅增强了我对ETL工具的实际操作能力,也让我体会到ETL流程在数据集成和处理中的重要性。通过实验,我加深了对数据集成技术的理解,为今后在大数据处理和分析方面的应用奠定了坚实的基础。同时,这次实验也提高了我解决实际问题的能力,增加了我对数据处理工具的兴趣和信心。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第五章 使用Spark SQL读写MySQL数据库的方法 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握如何使用Spark SQL连接MySQL数据库,并进行数据读写操作。
- 学习在IntelliJ IDEA中编写独立的Spark应用程序,熟悉Scala编程语言及其在数据处理中的应用。
- 理解大数据处理的基本流程,增强对分布式计算框架Spark的理解和实际操作能力。
二、实验环境:
操作系统:Linux(如Ubuntu 20.04)
数据库:MySQL 8.0
大数据处理框架:Apache Spark 3.0.1
开发工具:IntelliJ IDEA 2021.1
编程语言:Scala 2.11.8
相关依赖包:MySQL JDBC Driver、Scala SDK
三、实验内容:
5.1 创建 MySQL 数据库
sudo systemctl start mysql
mysql -u root -p
-- 创建数据库 CREATE DATABASE spark;
-- 使用数据库
USE spark;
-- 创建表
CREATE TABLE student (
id INT(4),
name CHAR(20),
gender CHAR(4),
age INT(4)
);
-- 插入数据
INSERT INTO student VALUES (1, 'Xueqian', 'E', 23);
INSERT INTO student VALUES (2, 'Weiliang', 'M', 24);
-- 查询数据
SELECT * FROM student;
5.2.连接Spark Shell与MySQL并读写数据
将mysql驱动的JAR包复制到Spark的jars目录:
在Spark Shell中,使用以下Scala代码连接并读取MySQL数据库中的数据:
// 连接MySQL数据库并读取student表的数据
val jdbcDF = {
spark.read.format("jdbc")
.option("url", "jdbc:mysql://localhost:3306/spark")
.option("driver", "com.mysql.cj.jdbc.Driver")
.option("dbtable", "student")
.option("user", "root")
.option("password", "123456")
.load()
}
// 显示读取的数据
jdbcDF.show()
import java.util.Properties
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row
// 设置两条数据,表示两个学生的信息
val studentRDD = spark.sparkContext.parallelize(Array("3 Rongcheng M 26", "4 Guanhua M 27")).map(_.split(" "))
// 设置模式信息
val schema = StructType(List(
StructField("id", IntegerType, true),
StructField("name", StringType, true),
StructField("gender", StringType, true),
StructField("age", IntegerType, true)
))
// 创建 Row对象,每个Row对象都是rowRDD中的一行
val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim, p(3).toInt))
// 建立起Row对象和模式之间的对应关系,也就是把数据和模式对应起来
val studentDF = spark.createDataFrame(rowRDD, schema)
// 创建一个prop 变量用来保存 JDBC连接参数
val prop = new Properties()
prop.put("user", "root") // 表示数据库用户名是root
prop.put("password", "123456") // 表示数据库密码是 123456
prop.put("driver", "com.mysql.cj.jdbc.Driver") // 表示驱动程序是com.mysql.cj.jdbc.Driver
// 连接数据库,采用append模式,表示追加记录到数据库spark的 student 表中
studentDF.write.mode("append").jdbc("jdbc:mysql://localhost:3306/spark", "student", prop)
在MySQL Shell中查询数据:下面已经添加成功
USE spark;
SELECT * FROM student;
5.3在IntelliJ IDEA中编写并运行独立的Spark应用程序来读写MySQL数据库
新建项目,打开IntelliJ IDEA,选择File -> New -> Project。在新建项目对话框中,在弹出的窗口中,将Project name设置为SparkMySQL,选择Maven,将GroupId设置为dblab,ArtifactId设置为spark-mysql,然后单击create完成项目创建。
设置依赖包
在IntelliJ IDEA中,打开File -> Project Structure。在弹出的窗口中,左侧的Project Settings中选择Libraries,然后单击绿色的+按钮,选择Scala SDK。如果需要下载Scala SDK,单击Download按钮,选择下载的版本(如2.11.8),然后单击OK。
设置项目目录
在“src”目录下的“main”子目录上单击鼠标右键,选择“New > Directory”来创建一个名为“scala”的新目录。右键单击“java”目录,选择“Delete...”删除该目录。
右键单击“scala”目录,选择“Mark Directory as > Sources Root”将其标记为源代码目录。
新建Scala 代码文件
在src/main/scala目录上右键单击,选择New -> Scala Class。
在弹出的对话框中,将Name设置为SparkOperateMySQL,将Kind设置为Object,然后单击OK。
在生成的 SparkOperateMySQL.scala 文件中,输入以下代码:
打开项目中的pom.xml文件,清空文件里面的内容,然后输入如下内容。
运行SparkOperateMySQL程序
在 IntelliJ IDEA 中打包SparkOperateMySQL程序并生成 JAR 包:
在 IntelliJ IDEA 中,选择菜单栏中的“File”,然后选择“Project Structure...”。在弹出的项目结构窗口中,依次单击“Artifacts”、绿色加号、“JAR”和“From modules with dependencies...”。如下图所示,进入设置界面。
在 “Output Layout” 选项卡中,删除所有以Extracted开头的JAR 包,只保 留 SparkMySQLjar 和'SparkMySQL'compile output,最后单击 “OK” 按钮可。
最终得到:
把JAR 包提交到Spark中运行
cd /usr/local/spark
./bin/spark-submit --class SparkOperateMySQL ~/IdeaProjects/SparkMySQL/out/artifacts/SparkMySQL_jar/SparkMySQL.jar
四、心得体会:
通过本次实验,我深入学习了如何使用Spark SQL与MySQL数据库进行数据交互,掌握了在Spark Shell中连接MySQL并读写数据的具体方法。同时,我还学会了在IntelliJ IDEA中配置Scala开发环境并编写独立的Spark应用程序,实现对MySQL数据库的操作。这次实验不仅增强了我对Spark和Scala的理解,也提高了我在大数据处理和分布式计算方面的实践能力。通过实际操作,我认识到在大数据处理过程中,掌握多种工具和技术的协同应用是非常重要的,这为我今后的学习和工作打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第六章. 使用Spark MLlib实现协同过滤算法 日期 2024 年 7 月 5 日
一、实验目的:
- 学习和掌握使用Spark MLlib实现协同过滤算法的基本方法和步骤。
- 通过编写和运行ALS(交替最小二乘法)算法的独立应用程序,理解推荐系统的原理和实现方式。
- 熟悉IntelliJ IDEA的开发环境配置和Scala编程,提升在大数据处理和机器学习方面的实际操作能力。
二、实验环境:
操作系统:Linux(如Ubuntu 20.04)
开发工具:IntelliJ IDEA 2021.1
编程语言:Scala 2.11.8
大数据处理框架:Apache Spark 3.0.1
机器学习库:Spark MLlib
相关依赖包:Scala SDK、Spark Core、Spark MLlib
三、实验内容:
第六章实现:在IntelliJ IDEA中编写并运行ALS算法独立应用程序
1. 新建项目
打开IntelliJ IDEA,选择 File -> New -> Project。
新建一个Maven项目,将 GroupId 设置为 dblab,ArtifactId 设置为 SparkALS,并设置项目名称为 SparkALS,完成项目的创建。
2.设置依赖包
在IntelliJ IDEA中,打开File -> Project Structure。在弹出的窗口中,左侧的Project Settings中选择Libraries,然后单击绿色的+按钮,选择Scala SDK。如果需要下载Scala SDK,单击Download按钮,选择下载的版本(如2.11.8),然后单击OK。
3. 设置项目目录
删除src目录下的test子目录。
删除src/main目录下的resources子目录。
删除src/main目录下的java子目录。
在src/main目录下新建一个scala子目录。
把scala目录设置为源代码目录。
4. 新建Scala代码文件
在src/main/scala目录下新建代码文件MovieLensALS.scala,并输入以下代码:
打开项目中的pom.xml文件,清空文件中的内容,输入以下内容:
运行MovieLensALS程序
生成应用程序JAR包
然后把JAR包提交到Spark中运行
cd /usr/local/spark
./bin/spark-submit --class MovieLensALS ~/IdeaProjects/SparkALS/out/artifacts/SparkALS_jar/SparkALS.jar
四、心得体会:
通过本次实验,我深入了解了协同过滤算法特别是ALS算法在推荐系统中的应用。通过在IntelliJ IDEA中编写并运行独立的Spark应用程序,我不仅熟练掌握了Scala的编程技巧,还学会了如何配置开发环境和管理项目依赖。实践过程中,我体会到推荐系统在大数据处理中的重要性,并且理解了ALS算法的数学原理及其实现细节。这次实验为我未来在机器学习和大数据领域的学习和研究提供了宝贵的经验,也增强了我对Spark MLlib的实际操作能力。实验中遇到的各种问题和挑战,极大地锻炼了我的问题解决能力和自主学习能力。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第7章 Node.js的安装和使用方法 日期 2024 年 7 月 5 日
一、实验目的:
- 学习和掌握Node.js的安装和配置方法。
- 了解如何使用Node.js创建简单的HTTP服务器。
- 熟悉Express框架及Jade模板引擎的基本使用。
- 实现一个简单的用户注册和登录功能,了解如何与MySQL数据库进行交互。
- 掌握通过网页调用词频统计应用程序的实现方法。
二、实验环境:
操作系统:Linux(如Ubuntu 20.04)
开发工具:IntelliJ IDEA 2021.1
编程语言:JavaScript(Node.js)
框架:Express
模板引擎:Jade
数据库:MySQL 8.0
相关依赖包:NVM、Node.js、Express、Jade、MySQL驱动、body-parser
三、实验内容:
7.1 . Node.js的安装
在安装Node.js之前,需要安装NVM。
cd ~
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
安装的时候 或许会出现错误:# Failed to connect to raw.githubusercontent.com port 443
原因:由于某些因素,导致GitHub的raw.githubusercontent.com域名解析被污染了
解决办法:修改host文件即可,命令如下
sudo vim /etc/hosts
199.232.96.133 raw.githubusercontent.com
使用 vim 编辑器打开文件 ~/.bashrc:
sudo vim ~/.bashrc
在文件开头位置添加以下内容以配置 NVM:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
保存文件并退出 vim 编辑器。然后,在终端中输入以下命令使得配置立即生效:
source ~/.bashrc
完成NVM的安装以后,就可以使用NVM 安装Node js LTS(长期支持版本),命令如下。
nvm install --1ts
安装完成后,在终端中输入如下命令即可看到安装成功的Node.js的版本信息。
node -v
7.2 创建Node.js应用
打开 Linux 终端,在当前用户的主目录下,创建一个项目目录 mynodeapp,并在该目录下创建一个名为 server.js 的文件。具体命令如下:
$ cd ~
$ mkdir mynodeapp
$ cd mynodeapp
$ vim server.js
使用 http.createServer() 方法可以创建一个服务器。通过 request 和 response 参数来接收请求和发送响应数据。以下是创建一个服务器并监听 3000 端口的示例代码:
var http = require('http');
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值:200 : OK
// 内容类型:text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(3000);
console.log('Server running at http://127.0.0.1:3000/');
在 server.js 文件中添加上述代码并保存。
在终端中运行以下命令启动 HTTP 服务器:
$ node server.js
执行该命令后,屏幕上会出现提示信息 Server running at http://127.0.0.1:3000/。请保持当前终端窗口打开,如果关闭,HTTP 服务器将停止运行。若要退出 HTTP 服务器,可以按 <Ctrl+C> 组合键。在 Linux 系统中打开一个浏览器,输入网址 http://127.0.0.1:3000 或 http://localhost:3000,你将会在网页上看到 "Hello World" 字样。
7.3 使用Express框架和Jade 模板引擎
在 Linux 终端中,使用以下命令创建项目目录 expressjadeapp 并初始化项目:
$ cd ~
$ mkdir expressjadeapp
$ cd expressjadeapp
$ npm init //快速开发,直接按enter
在项目目录 expressjadeapp 中安装 Express:
$ npm install express --save
继续在项目目录中安装 Jade 模板引擎:
$ npm install jade --save
在项目目录 expressjadeapp 中,创建一个名为 expressjade.js 的文件,文件内容如下:
var express = require('express');
var http = require('http');
var app = express();
app.set('view engine', 'jade'); // 设置模板引擎
app.set('views', __dirname); // 设置模板相对路径(相对于当前路径)
app.get('/', function (req, res) {
// 调用当前路径下的 test.jade 模板
res.render('test', { title: 'Jade test', message: 'Database Lab of Xiamen University' });
});
var server = http.createServer(app);
server.listen(3000);
console.log('Server started on http://127.0.0.1:3000/');
在项目目录 expressjadeapp 中,创建一个模板文件 test.jade,内容如下:
html
head
title= title
body
h1= message
执行以下命令启动 HTTP 服务器:
$ cd ~/expressjadeapp
$ node expressjade.js
打开一个浏览器,在地址栏输入 http://localhost:3000
7.4 实例1:设计网页实现用户注册登录功能
首先,进入 MySQL Shell 并创建数据库和表:
$ mysql -u root -p
CREATE DATABASE userlogin;
USE userlogin;
CREATE TABLE user (
userid INT(20) NOT NULL AUTO_INCREMENT,
username CHAR(50),
password CHAR(50),
PRIMARY KEY(userid)
);
DESC user;
SELECT * FROM user;
在 Linux 终端中,创建项目目录 userloginapp 并初始化项目:
$ cd ~
$ mkdir userloginapp
$ cd userloginapp
$ npm init -y
在项目目录中安装 Express:
$ npm install express --save
安装 MySQL 驱动模块:
$ npm install mysql --save
在项目目录 userloginapp 中,创建一个名为 userlogin.js 的文件,内容如下:
在项目目录 userloginapp 中创建以下三个网页:index.html,register.html,ok.html
启动 HTTP 服务器:
$ cd ~/userloginapp
$ node userlogin.js
在浏览器中打开 http://localhost:3000,测试用户注册和登录功能。注册用户后,再尝试登录,验证用户名和密码是否正确。
在网页中单击“注册”,跳转到下图所示的注册网页。
在注册网页中输入用户名和密码,单击“提交”按钮,这个新注册用户的信息就会被写入 MySQL数据库,然后,返回到用户登录页面。在用户登录页面中,输入用户名和密码,单击“提交”按钮,如果用户名和密码正确,则会 返回登录成功信息。
7.5 实例2 :采用Jade模板引擎实现用户注册登录功能
在 Linux 终端中,创建项目目录 userloginjadeapp 并初始化项目:
$ cd ~
$ mkdir userloginjadeapp
$ cd userloginjadeapp
$ npm init -y
在项目目录中安装 Express:
$ npm install express --save
在项目目录中安装 Jade:
$ npm install jade --save
在项目目录中安装 MySQL 驱动模块:
$ npm install mysql --save
在项目目录中安装 body-parser 模块,以便解析 POST 请求的数据:
$ npm install body-parser --save
在项目目录 userloginjadeapp 中,创建一个名为 userloginjade.js 的文件,内容如下:
在项目目录 userloginjadeapp 中创建以下三个模板文件:
在 Linux 终端中,执行如下命令启动 HTTP 服务器:
$ cd ~/userloginjadeapp
$ node userloginjade.js
然后,在浏览器中打开 http://localhost:3000,测试用户注册和登录功能。注册用户后,再尝试登录,验证用户名和密码是否正确。
单击“注册”链接,跳转到注册页面
输入用户名 “zhuyangyu” 和密码“123456”以后,单击“注册”按钮,就会完成用户注册, 并跳转到欢迎用户页面
7.6 实例3:通过网页调用词频统计应用程序
在 Linux 终端中,创建项目目录 myapp 并初始化项目:
$ cd ~
$ mkdir myapp
$ cd myapp
$ npm init -y
在项目目录中安装 Express:
$ npm install express --save
在项目目录 myapp 中,创建一个名为 index.js 的文件,内容如下:
const express = require('express');
const app = express();
app.get('/', function(req, res) {
res.send('Hello World!');
});
const server = app.listen(3000, function() {
const host = server.address().address;
const port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
启动服务器:
$ cd ~/myapp
$ node index.js
在Linux系统中打开一个浏览器,输入网址“http://127.0.0.1:3000/”或者“http:/localhost:3000/”, 就可以看到网页上显示了 “HelloWorld”。然后,在Linux终端中可以按<Ctrl+C>组合键,停止该 HTTP服务器。
在项目目录中安装 Jade:
$ npm install jade --save
在 index.js 中设置 Jade 模板引擎:
const express = require('express');
const app = express();
app.set('views', './views');
app.set('view engine', 'jade');
app.get('/', function(req, res) {
res.render('index', { title: 'WordCount 测试', message: '厦门大学数据库实验室!' });
});
const server = app.listen(3000, function() {
const host = server.address().address;
const port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
在项目目录 myapp 下创建 views 子目录,并在 views 目录下创建 index.jade 文件:
mkdir views
cd ~/myapp/views
vim index.jade
在终端中重新执行如下命令运行该HTTP服务器。
cd ~/myapp
node index.js
在 Linux 系统中打开一个浏览器,再次访问网址 “localhost:3000”, 模板文件 index .jade就会被渲染成一个HTML网页
现在继续修改index.jade模板文件,增加form等标签,从而在网页中生成一个表单,用户可以在表单中输入相关数据以后提交网页。修改后的内容如下:
html
head
title= title
body
h1= message
form(action='/', method='post')
p 请在下面输入所需要进行词频统计文件的路径
p HDFS文件路径示例:hdfs://localhost:9000/user/hadoop/word.txt
p 本地文件路径示例:file:///home/hadoop/word.txt
br
input(name='path')
input(type='submit')
br
textarea(rows='40', cols='40')= result
为应用入口index.js 增加路由等功能.
安装 body-parser 模块以解析POST数据:
$ npm install body-parser --save
修改 index.js 文件:
const express = require('express')
const bodyParser = require('body-parser')
const exec = require('child_process').exec
const app = express();
// 设置模板引擎
app.set('views','./views')
app.set('view engine', 'jade')
// 添加body-parser解析post过来的数据
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
app.get('/', function (req, res) {
res.render('index', {title: 'WordCount测试', message: '厦门大学数据库实验室!'})
})
app.post('/',function(req, res){
const path = req.body.path.trim()
const jarStr = '/usr/local/spark/bin/spark-submit --class WordCount ~/IdeaProjects/WordCount/out/artifacts/WordCount_jar/WordCount.jar '+path+' /output'
const rmrStr = '/usr/local/hadoop/bin/hdfs dfs -rmr /output'
const catStr = '/usr/local/hadoop/bin/hdfs dfs -cat /output/*'
exec(rmrStr,function(err, stdout, stderr){
exec(jarStr, function(err, stdout, stderr){
// 判断路径是否存在,如果路径不存在,则显示错误信息
if(stderr){
res.render('index', {title: 'WordCount测试', message: '厦门大学数据库实验室!', result: stderr})
}
//执行成功后,显示统计结果。
exec(catStr,function(err, stdout, stderr){
res.render('index', {title: 'WordCount测试', message: '厦门大学数据库实验室!', result: stdout})
})
})
})
})
const server = app.listen(3000, function () {
const host = server.address().address;
const port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
编写并打包 WordCount 程序
import org.apache.spark.SparkContext
import org.apache.spark.SparkConf
object WordCount {
def main(args: Array[String]) {
val inputFile = args(0)
val conf = new SparkConf().setAppName("WordCount").setMaster("local")
val sc = new SparkContext(conf)
val textFile = sc.textFile(inputFile)
val wordCount = textFile.flatMap(line => line.split(" "))
.map(word => (word, 1))
.reduceByKey((a, b) => a + b)
wordCount.foreach(println)
wordCount.saveAsTextFile(args(1))
}
}
./bin/spark-submit --class "WordCount" ~/IdeaProjects/WordCount/out/artifacts/WordCount_jar/WordCount.jar
在终端中重新执行如下命令运行该HTTP 服务器。
cd ~/myapp
node index.js
在 Linux 系统中打开一个浏览器,再次访问网址 “localhost:3000”
在这个网页中包含了一个表单,可以在文本输入框中输入需要进行词频统计的文本文件的路径。如果文本文件是一个HDFS中的文件,可以采用“hdfs://localhost:9000/user/hadoop/word.txt” 这种形式(注意 hdfs 后面是两个斜杠);如果文本文件是一个 Linux 本地文件,则可以采用 “file:///home/hadoop/word.txt” 这种形式(注意file 后面是三个斜杠)。
在文本输入框中输入词频文件路径以后,单击“提交查询”按钮,网页提交的数据会传递给 body-parser进行解析,然后执行语句“exec(rmrStr,function(er,stdout,stderr))”删除HDFS文件系统中的“/output”目录(如果存在该目录,后面执行词频统计程序就会出错,因此必须先删除),再 执 行 语 句“exec(jarStr,function(err,stdout,stderr)” 进 行 Spark 词 频 统 计 得 到 统 计 结 果 , 写 入HDFS文件系统的“/output” 目录下,并执行语句 “exec(catStr,function(err,stdout,stderr)”, 把“/output” 目录下的词频统计结果读取出来,返回给网页端进行显示。最后,网页中的“文本区域”就会显示词频统计结果信息,效果如图所示。
本地文件word.txt:
HDFS文件word.txt:
四、心得体会:
通过本次实验,我系统地学习了Node.js的安装与使用方法,掌握了如何通过NVM管理Node.js版本。使用Node.js构建HTTP服务器让我对服务器端开发有了初步的认识。在此基础上,我进一步学习了Express框架和Jade模板引擎,成功实现了用户注册和登录功能,并与MySQL数据库进行了交互。这次实验让我认识到,Node.js由于其异步非阻塞的特性,特别适合用于I/O密集型的应用开发。此外,我通过实际操作,学会了如何将词频统计的应用程序与网页前端结合,体验了从前端数据输入到后端数据处理再到结果展示的完整流程。这一过程不仅巩固了我对Node.js及其相关技术的理解,还提高了我的全栈开发能力。这次实验为我未来在Web开发和大数据处理领域的学习和实践打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第八章电影推荐系统(基础版)的实现过程 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握如何将数据集加载到HDFS中,并进行数据预处理。
- 学习使用Spark编写推荐系统的程序,实现基础版的电影推荐功能。
- 熟悉如何在IntelliJ IDEA中配置和运行Spark程序。
- 了解如何使用Node.js创建网页,并在网页中展示电影推荐结果。
二、实验环境:
操作系统:Linux(如Ubuntu 20.04)
大数据处理框架:Apache Hadoop 3.2.1, Apache Spark 3.0.1
开发工具:IntelliJ IDEA 2021.1
编程语言:Scala 2.11.8, JavaScript(Node.js)
相关依赖包:NVM、Node.js、Express、Jade、body-parser
数据清洗工具:Kettle(Spoon)
三、实验内容:
8.1 把数据集加载到HDFS中
本案例采用的数据集为movie_recommend.zip, 假设下载后的文件被存放在“~/下载” 目录中。
使用如下命令对movie _recommend.zip文件进行解压。
$cd ~/下载
$unzip movie_recommend.zip
解压缩成功后,就可以在“~/下载”目录下看到3个数据集文件ratings.dat、personalRatings dat 和movies.dat。
启动Hadoop
cd /usr/local/hadoop
./sbin/start-dfs.sh
创建HDFS目录
cd /usr/local/hadoop
./bin/hdfs dfs -mkdir /input_spark
./bin/hdfs dfs -put /home/hadoop/下载/movie_recommend/ratings.dat /input_spark/
./bin/hdfs dfs -put /home/hadoop/下载/movie_recommend/personalRatings.dat /input_spark/
对于 movies.dat 文件内容,需要把电影名称里面的年份信息删除,只保留电影标题。所以使用Kettle工具清洗 movies.dat 中的数据并加载到HDFS中
新建转换并保存为“deleteyear”
在Spoon主界面中,选择“文件 -> 新建 -> 转换”,然后保存为“deleteyear”。
在“核心对象”中选择“输入->文本文件输入”,拖曳到设计区域。
双击控件,设置文件路径为file://home/hadoop/movies.dat。
在“内容”选项卡中,将分隔符改为“(”,去掉“头部”复选框的勾选。
在“字段”选项卡中,点击“获取字段”。
添加“剪切字符串”控件,在“核心对象”中选择“转换 -> 剪切字符串”,拖曳到设计区域并与“文本文件输入”控件连接。双击控件,设置“输入流字段”为“Field2”,“起始位置”为5,“结束位置”为1000。
添加“Hadoop File Output”控件,双击控件,设置“Hadoop cluster”为“Hadoop local”。在“文件”选项卡中,选择HDFS目录“input_spark/movies”,将扩展名改为dat。
在“内容”选项卡中,将“分隔符”去掉,取消“头部”复选框的勾选。
在“字段”选项卡中,点击“获取字段”和“最小宽度”。
执行转换单击Spoon主界面的运行按钮。进入执行转换界面,点击“启动”按钮。
查看HDFS中的数据
cd /usr/local/hadoop
./bin/hdfs dfs -cat /input_spark/movies.dat
8.2 编写Spark 程序实现电影推荐
使用IntelliJ IDEA创建一个新的Maven项目:打开IntelliJ IDEA,选择“File -> New -> Project”。
选择“Maven”,设置“GroupId”和“ArtifactId”都为“Spark_Recommend_Dataframe”,然后单击“Next”。
设置项目名称为“Spark_Recommend_Dataframe”,然后单击“create”。
在项目目录中找到pom.xml文件,并添加以下内容:
在IntelliJ IDEA中,选择“File -> Project Structure”,在“Project Settings”中选择“Libraries”,点击绿色的“+”按钮,选择“Scala SDK”,选择版本2.11.8。
删除src目录下的test子目录,删除src/main目录下的java和resources目录。在src/main目录下新建子目录recommend,并将其设置为源代码根目录。
在recommend目录下新建Scala类文件MovieLensALS.scala
选择Run -> Edit Configurations...。在Program arguments字段中输入以下内容:
/home/hadoop/下载/recommend /home/hadoop/下载/recommend/personalRatings.dat 10 50 10
运行结果:
生成应用程序JAR包
把JAR包提交到Spark中运行
cd /usr/local/spark
./bin/spark-submit --class recommend.MovieLensALS ~/IdeaProjects/Spark_Recommend_Dataframe/out/artifacts/Spark_Recommend_Dataframe_jar/Spark_Recommend_Dataframe.jar /input_spark ~/下载/recommend/personalRatings.dat 10 5 10
8.4使用Node.js在网页中展现结果
打开终端并执行以下命令,创建并初始化项目目录:
cd ~ # 进入当前Linux用户的主目录
mkdir mysparkapp # 创建一个目录
cd mysparkapp
npm init # 初始化项目,按Enter键接受默认配置
继续在终端中执行以下命令,安装Express、Jade和body-parser模块:
npm install express --save # 安装Express模块
npm install jade --save # 安装Jade模块
npm install body-parser --save # 安装body-parser模块
在项目目录mysparkapp中创建一个名为index.js的文件,输入以下内容:
在当前项目目录下创建名称为views的子目录,并在views目录下创建一个Jade模板文件index.jade:
在终端中输入如下命令启动HTTP服务器:
cd ~/mysparkapp
node index.js
打开浏览器,访问http://localhost:3000,会看到一个电影推荐系统的网页。
输入相关信息并提交查询,等待几分钟后可以得到推荐结果。
四、心得体会:
通过本次实验,我全面学习了如何构建一个基础版的电影推荐系统。首先,通过将数据集加载到HDFS中并进行预处理,我掌握了数据清洗和加载的基本步骤,体会到了数据预处理在大数据项目中的重要性。接着,通过使用Spark编写推荐系统的程序,我深入理解了协同过滤算法(ALS算法)的实现细节,并学会了如何在IntelliJ IDEA中配置和运行Spark程序。在完成推荐系统的基础上,我进一步学习了如何使用Node.js创建简单的Web应用,并将推荐结果展示在网页上。这一过程让我认识到全栈开发的重要性,从数据处理到算法实现再到结果展示,每一个环节都需要细致的理解和操作。通过本次实验,我不仅提升了对大数据技术和工具的掌握,还增强了我的编程能力和项目开发能力。这次实验为我今后在大数据和机器学习领域的学习和研究打下了坚实的基础。
桂 林 理 工 大 学
实 验 报 告
班级 软件工程2021-1班 学号 3212052051354 姓名 朱扬宇 同组实验者
实验名称 第九章 电影推荐系统(升级版)的设计与实现 日期 2024 年 7 月 5 日
一、实验目的:
- 掌握电影推荐系统的设计与实现方法,提升对推荐系统的理解和应用能力。
- 通过设计和实现升级版的电影推荐系统,熟悉大数据处理、分布式计算及Web开发的相关技术。
- 学习如何使用Spark进行数据处理与分析,并将结果存储到MySQL数据库中。
- 了解Node.js在构建动态Web应用中的作用,实现数据的展示与交互。
二、实验环境:
操作系统:Linux(如Ubuntu 20.04)
数据库:MySQL 8.0
大数据处理框架:Apache Hadoop 3.2.1, Apache Spark 3.0.1
开发工具:IntelliJ IDEA 2021.1
编程语言:Scala 2.11.8, JavaScript(Node.js)
相关依赖包:NVM、Node.js、Express、Jade、body-parser、MySQL驱动
数据清洗工具:Kettle(Spoon)
三、实验内容:
9.1 数据库的设计与实现
下载SQL脚本文件MovieRecommendDatabase.sql,保存到“~/下载”
启动MySQL服务后,执行如下命令进入MySQL Shell界面:
$ mysql -u root -p
在MySQL Shell中执行如下 SQL 语句实现movierecommend 数据库的创建和查看。
mysql>source ~/下载/MovieRecommendDatabase.sql;
mysql>use movierecommend;
mysql>select count(*)from movieinfo;
9.2 Spark 程序的设计与实现
打开IntelliJ IDEA,选择菜单 “File -> New -> Project”,选择“Maven”项目,将“GroupId”和“ArtifactId”均设置为“Film_Recommend_Dataframe”。设置项目名称为“Film_Recommend_Dataframe”。完成项目的创建。
导入Scala SDK:选择菜单“File -> Project Structure”,在左侧的“Project Settings”中单击“Libraries”,再单击绿色“+”,选择“Scala SDK”,选择自己现在的Scala版本2.11.8。
删除“src”目录下的“test”子目录,删除“src/main”目录下的“resources”和“java”子目录,在“src/main”目录下新建“recommend”子目录,并将其设置为源代码目录。
在“src/main/recommend”目录下新建以下4个代码文件:MovieLensALS.scala,DeleteFromMySQL.scala,InsertIntoMySQL.scala,ReadFromMySQL.scala
配置pom.xml 文件,清空pom.xml文件原来的内容,输入以下内容。
为了能够顺利运行程序,需要在用户评分表personalratings 中增加几条userid为1的用户的评分记录
USE movierecommend;
INSERT INTO personalratings VALUES (1, 3, 5, '978300275');
INSERT INTO personalratings VALUES (1, 4, 3, '978300288');
INSERT INTO personalratings VALUES (1, 5, 5, '978300298');
INSERT INTO personalratings VALUES (1, 6, 5, '978300301');
INSERT INTO personalratings VALUES (1, 7, 5, '978300303');
SELECT * FROM personalratings;
然后,单击项目界面顶部菜单的 “Run”, 在弹出的界面中选择 “Edit Configuration.…”。在弹出的界面中“Programarguments” 项右边的文本框中输入MovieLensALS程序运行所需要的两个参数,包括数据集目录(保存movies.dat和ratings.dat的目录)、用户的userid。已经把数据集movies.dat和ratings.dat存放在了Linux本地文件系统的“~/下载/movie_recommend1”目录下。因此,程序的运行参数设置为“/home/hadoop/下载/movie_recommend1 1”,其中,1是userid的值。
这时,可以到MySQL 数据库movierecommend 中查看推荐结果是否已经被写入recommendresult 表中。
mysql> SELECT * FROM recommendresult;
生成应用程序JAR 包
准备:
hdfs dfs -mkdir -p /input_spark
hdfs dfs -put ~/下载/movie_recommend1/movies.dat /input_spark/
hdfs dfs -put ~/下载/movie_recommend1/ratings.dat /input_spark/
把应用程序部署到Spark 环境中运行
./bin/spark-submit --class MovieLensALS ~/IdeaProjects/Film_Recommend_Dataframe/out/artifacts/Film_Recommend_Dataframe_jar/Film_Recommend_Dataframe.jar /input_spark 1
9.5系统网站的设计与实现
首先,创建项目目录,并初始化Node.js项目:
cd ~ # 进入当前用户主目录
mkdir movierecommendapp # 创建项目目录
cd movierecommendapp
npm init -y # 快速初始化项目
安装所需的Node.js模块:
npm install express --save
npm install jade --save
npm install body-parser --save
npm install mysql --save
在项目目录movierecommendapp中,创建一个名为movierecommend.js的文件。在当前项目目录下添加名称为 views 的子目录,在 views 目录下添加一个名称为 css 的子目录。添加模板文件内容views/index.jade,views/registerpage.jade,views/registersuccess.jade,views/loginpage.jade,views/personalratings.jade,views/userscoresuccess.jade,views/recommendresult.jade
在项目根目录下,运行以下命令启动服务器:node movierecommend.js
服务器启动后,你可以在浏览器中访问 http://localhost:3000 查看网站效果。
首页界面
登录界面
注册界面
登陆之后的用户评分界面
评分成功界面
电影推荐结果界面
四、心得体会:
通过本次实验,我深入学习了如何设计和实现一个升级版的电影推荐系统。在数据处理方面,我掌握了如何使用Spark读取和处理大数据,并通过协同过滤算法生成推荐结果。在数据库操作方面,我学会了如何将推荐结果存储到MySQL数据库中,并进行了相应的数据管理。在系统实现过程中,我通过Node.js创建了动态Web应用,完成了用户注册、登录、评分和推荐结果展示的功能。这个过程不仅提升了我的前端开发能力,还让我认识到全栈开发的重要性。此外,通过实验中遇到的问题和挑战,我进一步增强了自己的问题解决能力和自主学习能力。这次实验让我对大数据处理、分布式计算以及Web开发有了更深刻的理解,为我未来在相关领域的学习和工作奠定了坚实的基础。