基于Hadoop+HBase+Spring Boot+Vue的天气数据分析与可视化系统
基于Hadoop+HBase+Spring Boot+Vue的天气数据分析与可视化系统
一、项目概述
本项目是一个基于大数据技术的天气数据分析与可视化系统,采用Hadoop分布式计算框架处理海量天气数据,使用HBase进行高性能数据存储,通过Spring Boot构建RESTful API服务,并利用Vue.js+ECharts实现数据可视化展示。系统支持全国多个城市的天气数据采集、存储、分析和可视化展示,为气象研究和决策提供数据支持。
1.1 项目架构
weather-analysis-system/
├── weather-mapreduce/ # MapReduce数据分析模块
├── weather-backend/ # Spring Boot后端API服务
├── weather-frontend/ # Vue.js前端可视化
└── weather-data-service/ # 数据服务模块
1.2 技术栈
后端技术栈:
- Spring Boot 2.7.18:Web框架
- Hadoop 3.1.0:分布式计算框架
- HBase 2.1.0:分布式NoSQL数据库
- MyBatis 3.5.13:持久层框架
- Spring Security 5.7.12:安全框架
- Log4j2:日志框架
前端技术栈:
- Vue 3.3.4:渐进式JavaScript框架
- Vue Router 4.2.5:路由管理
- Vuex 4.1.0:状态管理
- ECharts 5.4.3:数据可视化库
- Element Plus 2.4.0:UI组件库
- Axios 0.27.2:HTTP客户端
大数据组件:
- HDFS:分布式文件系统,存储原始数据和分析结果
- HBase:分布式列式数据库,存储天气数据和统计结果
- MapReduce:分布式计算框架,进行数据分析
二、核心功能模块
2.1 数据采集与存储
系统支持CSV格式天气数据的导入,数据包含以下字段:
- 城市信息:城市代码、城市名称、省份、国家
- 温度数据:温度、体感温度
- 湿度数据:湿度
- 风力数据:风速、风向、风力等级
- 气压数据:气压
- 能见度数据:能见度
- 紫外线数据:紫外线指数
- 其他数据:日出日落时间、天气提示等
2.2 MapReduce数据分析
系统实现了多个MapReduce作业,用于分析不同维度的天气数据:
温度分析:
- Mapper:提取城市代码和温度数据
- Reducer:计算平均温度、最高温度、最低温度、标准差
- 输出格式:JSON格式的统计数据
湿度分析:
- Mapper:提取城市代码和湿度数据
- Reducer:计算平均湿度、最高湿度、最低湿度
风速分析:
- Mapper:提取城市代码和风速数据
- Reducer:计算平均风速、最大风速、最小风速
气压分析:
- Mapper:提取城市代码和气压数据
- Reducer:计算平均气压、最高气压、最低气压
能见度分析:
- Mapper:提取城市代码和能见度数据
- Reducer:计算平均能见度、最高能见度、最低能见度
紫外线指数分析:
- Mapper:提取城市代码和紫外线指数数据
- Reducer:计算平均紫外线指数、最高紫外线指数
体感温度差异分析:
- Mapper:提取城市代码、实际温度和体感温度
- Reducer:计算体感温度与实际温度的平均差异、最大差异
2.3 RESTful API服务
系统提供完整的RESTful API接口,包括:
天气数据接口:
GET /weather/raw/{rowKey}:获取指定行键的原始天气数据GET /weather/raw/city/{cityCode}:获取指定城市的原始天气数据列表GET /weather/stats/{cityCode}/{dataType}:获取指定城市的统计数据POST /weather/raw:插入原始天气数据POST /weather/stats:插入统计数据PUT /weather/raw/{rowKey}:更新原始天气数据PUT /weather/stats/{rowKey}:更新统计数据DELETE /weather/raw/{rowKey}:删除原始天气数据DELETE /weather/stats/{rowKey}:删除统计数据GET /weather/history/{cityCode}:获取指定城市的历史天气数据
统计接口:
GET /stats/dashboard:获取仪表板统计数据GET /stats/national/average:获取全国平均天气数据GET /stats/national/extremes:获取全国极值天气数据GET /stats/all/{statsType}:获取所有城市的特定类型统计数据
2.4 数据可视化
系统提供丰富的数据可视化功能:
仪表盘:
- 数据统计卡片:平均温度、平均湿度、平均风速、原始数据量、统计数据量
- 全国极值数据:最高温度、最低温度、最高湿度、最低湿度、最大风速、最小风速
- 全国平均数据:平均温度、平均湿度、平均风速
- 城市数据详情表格:支持温度、湿度、风速三个维度的数据展示
- 多种图表:温度分布柱状图、湿度分布柱状图、风速分布柱状图、城市数据量环形图、温度湿度散点图、城市天气雷达图、全国天气指标折线图、城市数据词云图
温度分析:
- 温度统计卡片:平均温度、最高温度、最低温度
- 温度趋势图表:展示温度随时间的变化趋势
- 温度分布图表:展示温度在不同区间的分布情况
- 城市温度对比图表:对比不同城市的平均温度
- 温度详细数据表格:展示具体的温度数据
三、核心源码分析
3.1 HBase连接管理
HBaseUtil工具类负责管理HBase连接,提供连接池和基本操作方法:
@Component
public class HBaseUtil {
private static Connection connection = null;
private static final String HBASE_QUORUM = "192.168.101.101";
private static final String HBASE_PORT = "2181";
public static synchronized Connection getConnection() throws IOException {
if (connection == null || connection.isClosed()) {
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", HBASE_QUORUM);
config.set("hbase.zookeeper.property.clientPort", HBASE_PORT);
config.set("hbase.master", HBASE_QUORUM + ":16000");
config.set("hbase.security.authentication", "simple");
config.set("hadoop.security.authentication", "simple");
config.set("hbase.client.scanner.timeout.period", "600000");
config.set("hbase.rpc.timeout", "600000");
config.set("zookeeper.session.timeout", "600000");
config.set("zookeeper.recovery.retry.max", "3");
connection = ConnectionFactory.createConnection(config);
}
return connection;
}
}
技术要点:
- 使用单例模式管理HBase连接,避免频繁创建和销毁连接
- 配置了连接超时和重试机制,提高系统的稳定性
- 禁用了安全认证,简化开发环境配置
3.2 数据访问层
HBaseWeatherDao实现了数据的CRUD操作,支持缓存机制:
@Repository
@CacheConfig(cacheNames = "hbaseWeatherCache")
public class HBaseWeatherDao {
@Autowired
private HBaseUtil hbaseUtil;
private static final String TABLE_RAW = "weather_raw";
private static final String TABLE_STATS = "weather_stats";
private static final String FAMILY_RAW = "info";
private static final String FAMILY_STATS = "stats";
@Cacheable(key = "'raw_' + #rowKey")
public Map<String, String> getRawDataByRowKey(String rowKey) throws IOException {
return HBaseUtil.getDataByRowKey(TABLE_RAW, rowKey);
}
@Cacheable(key = "'stats_' + #statsType + '_' + #cityCode")
public Map<String, String> getStatsData(String cityCode, String statsType) throws IOException {
return HBaseUtil.getStatsData(statsType, cityCode);
}
public Map<String, Object> getDashboardStats() throws IOException {
Map<String, Object> dashboardStats = new HashMap<>();
List<Map<String, String>> resultList = HBaseUtil.scanTable(TABLE_STATS, null, null);
double totalTemp = 0.0;
double totalHumidity = 0.0;
double totalWindSpeed = 0.0;
double maxTemp = Double.MIN_VALUE;
double minTemp = Double.MAX_VALUE;
int tempCityCount = 0;
for (Map<String, String> rowData : resultList) {
String rowKey = rowData.get("rowKey");
if (rowData.containsKey("stats:temperature_stats")) {
String tempJson = rowData.get("stats:temperature_stats");
double avgTemp = extractValueFromJson(tempJson, "avgValue");
double maxCityTemp = extractValueFromJson(tempJson, "maxValue");
double minCityTemp = extractValueFromJson(tempJson, "minValue");
totalTemp += avgTemp;
maxTemp = Math.max(maxTemp, maxCityTemp);
minTemp = Math.min(minTemp, minCityTemp);
tempCityCount++;
}
}
dashboardStats.put("averageTemperature", totalTemp / tempCityCount);
dashboardStats.put("highestTemperature", maxTemp);
dashboardStats.put("lowestTemperature", minTemp);
return dashboardStats;
}
}
技术要点:
- 使用Spring Cache注解实现缓存,提高查询性能
- 实现了JSON数据的解析和统计计算
- 支持批量数据扫描和聚合计算
3.3 MapReduce温度分析
TemperatureMapper负责解析CSV数据并提取温度信息:
public class TemperatureMapper extends Mapper<LongWritable, Text, Text, DoubleWritable> {
private Text cityKey = new Text();
private DoubleWritable temperature = new DoubleWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
if (key.get() == 0) {
return;
}
String line = value.toString();
if (line.isEmpty()) {
return;
}
line = line.replace("\"", "");
String[] parts = line.split(",");
if (parts.length >= 9) {
String cityCode = parts[1];
String tempStr = parts[8];
if (tempStr != null && !tempStr.isEmpty()) {
try {
double temp = Double.parseDouble(tempStr);
cityKey.set(cityCode);
temperature.set(temp);
context.write(cityKey, temperature);
} catch (NumberFormatException e) {
context.getCounter("TemperatureMapper", "INVALID_TEMP").increment(1);
}
}
}
}
}
技术要点:
- 跳过CSV表头,只处理数据行
- 使用城市代码作为Map的Key,温度作为Value
- 添加计数器统计无效数据
TemperatureReducer负责计算温度统计值:
public class TemperatureReducer extends Reducer<Text, DoubleWritable, Text, Text> {
@Override
protected void reduce(Text key, Iterable<DoubleWritable> values, Context context) throws IOException, InterruptedException {
double sum = 0;
double max = Double.MIN_VALUE;
double min = Double.MAX_VALUE;
long count = 0;
List<Double> tempList = new ArrayList<>();
for (DoubleWritable value : values) {
double temp = value.get();
sum += temp;
count++;
if (temp > max) {
max = temp;
}
if (temp < min) {
min = temp;
}
tempList.add(temp);
}
double avg = sum / count;
double stdDev = 0;
for (Double temp : tempList) {
stdDev += Math.pow(temp - avg, 2);
}
stdDev = Math.sqrt(stdDev / count);
String output = String.format("{\"statsType\":\"temp\",\"cityCode\":\"%s\",\"avgValue\":%.2f,\"maxValue\":%.2f,\"minValue\":%.2f,\"stdDev\":%.2f,\"count\":%d}",
key.toString(), avg, max, min, stdDev, count);
context.write(key, new Text(output));
}
}
技术要点:
- 计算平均值、最大值、最小值、标准差等统计指标
- 输出JSON格式的结果,便于后续处理
- 使用List存储温度值用于标准差计算
3.4 REST API控制器
WeatherController提供天气数据的RESTful API:
@RestController
@RequestMapping("/weather")
public class WeatherController {
@Autowired
private HBaseWeatherDao weatherDao;
@GetMapping("/raw/{rowKey}")
public ResponseEntity<?> getRawDataByRowKey(@PathVariable String rowKey) {
try {
Map<String, String> data = weatherDao.getRawDataByRowKey(rowKey);
if (data.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("天气数据不存在");
}
return ResponseEntity.ok(data);
} catch (IOException e) {
throw new WeatherServiceException("获取天气数据失败: " + e.getMessage());
}
}
@GetMapping("/raw/city/{cityCode}")
public ResponseEntity<?> getRawDataByCityCode(
@PathVariable String cityCode,
@RequestParam(defaultValue = "10") int limit) {
try {
List<Map<String, String>> dataList = weatherDao.getRawDataByCityCode(cityCode, limit);
if (dataList.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("该城市天气数据不存在");
}
return ResponseEntity.ok(dataList);
} catch (IOException e) {
throw new WeatherServiceException("获取城市天气数据失败: " + e.getMessage());
}
}
}
技术要点:
- 使用Spring MVC注解定义RESTful接口
- 统一的异常处理机制
- 支持分页查询
3.5 前端数据可视化
Dashboard.vue实现了数据可视化仪表盘:
export default {
name: 'Dashboard',
setup() {
const dashboardStats = ref(null)
const nationalExtremes = ref(null)
const nationalAverage = ref(null)
const citiesTemperature = ref([])
const citiesHumidity = ref([])
const citiesWindSpeed = ref([])
const fetchWeatherStats = async () => {
loading.value = true
error.value = false
try {
const [statsResponse, extremesResponse, averageResponse, temperatureResponse, humidityResponse, windSpeedResponse] = await Promise.all([
axios.get('/stats/dashboard'),
axios.get('/stats/national/extremes'),
axios.get('/stats/national/average'),
axios.get('/stats/all/temperature'),
axios.get('/stats/all/humidity'),
axios.get('/stats/all/windSpeed')
])
dashboardStats.value = statsResponse
nationalExtremes.value = transformedExtremes
nationalAverage.value = transformedAverage
citiesTemperature.value = transformedTemperature
citiesHumidity.value = transformedHumidity
citiesWindSpeed.value = transformedWindSpeed
updateAllCharts()
} catch (err) {
error.value = true
ElMessage.error('数据加载失败')
} finally {
loading.value = false
}
}
const initTemperatureChart = () => {
temperatureChart = echarts.init(chartDom)
const option = {
title: { text: '城市温度分布', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: cities },
yAxis: { type: 'value', name: '温度 (°C)' },
series: [{
name: '平均温度',
type: 'bar',
data: temperatures,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#ff6b6b' },
{ offset: 1, color: '#ff8e8e' }
])
}
}]
}
temperatureChart.setOption(option)
}
}
}
技术要点:
- 使用Vue 3 Composition API
- 使用ECharts实现多种图表类型
- 并行请求多个API,提高性能
- 响应式数据绑定和状态管理
3.6 前端HTTP客户端
axios.js配置了HTTP请求拦截器:
import axios from 'axios'
const service = axios.create({
baseURL: '/api',
timeout: 30000
})
service.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('响应错误:', error)
return Promise.reject(error)
}
)
export default service
技术要点:
- 创建axios实例,统一配置baseURL和超时时间
- 使用响应拦截器简化数据处理
- 统一的错误处理机制
四、技术亮点
4.1 大数据处理能力
系统采用Hadoop MapReduce框架进行分布式计算,能够处理海量天气数据:
- 支持百万级数据量的处理
- 分布式并行计算,提高处理效率
- 可扩展性强,支持集群部署
4.2 高性能数据存储
使用HBase作为数据存储层,具有以下优势:
- 分布式列式存储,适合海量数据
- 支持快速随机读写
- 良好的扩展性,支持水平扩展
- 支持数据版本控制
4.3 缓存机制
系统实现了多层缓存机制:
- Spring Cache注解实现方法级缓存
- HBase连接池管理
- 前端数据缓存和状态管理
4.4 可视化展示
系统提供了丰富的数据可视化功能:
- 支持多种图表类型:柱状图、折线图、散点图、雷达图、词云图等
- 交互式图表,支持缩放、筛选等功能
- 响应式设计,适配不同屏幕尺寸
4.5 RESTful API设计
系统遵循RESTful API设计规范:
- 统一的接口风格
- 清晰的资源命名
- 标准的HTTP方法使用
- 完善的错误处理机制
五、部署架构
5.1 系统架构图
┌─────────────┐
│ 前端Vue │
│ (Nginx) │
└──────┬──────┘
│ HTTP
┌──────▼─────────────────┐
│ Spring Boot后端 │
│ (RESTful API) │
└──────┬─────────────────┘
│
┌──────▼─────────────────┐
│ HBase │
│ (NoSQL数据库) │
└──────┬─────────────────┘
│
┌──────▼─────────────────┐
│ Hadoop HDFS │
│ (分布式文件系统) │
└────────────────────────┘
5.2 环境配置
虚拟机环境:
- 操作系统:CentOS 7
- 内存:4GB
- 处理器:2核
- 磁盘空间:20GB
- 节点:192.168.101.101
大数据环境:
- Hadoop 3.3.4(192.168.101.101:8020)
- Zookeeper 3.8.0
- HBase 2.4.13
- MySQL 8.0.33
数据库配置:
- 主机:192.168.101.101
- 端口:3306
- 用户名:root
- 密码:123456
六、总结
本项目实现了一个完整的天气数据分析与可视化系统,具有以下特点:
- 技术先进:采用Hadoop+HBase+Spring Boot+Vue等主流技术栈
- 功能完善:涵盖数据采集、存储、分析、可视化等完整流程
- 性能优越:支持海量数据处理,具有良好的扩展性
- 可视化丰富:提供多种图表类型,直观展示数据分析结果
- 架构清晰:前后端分离,模块化设计,便于维护和扩展
该系统为气象数据分析和决策提供了有力的技术支持,具有广阔的应用前景。
更多推荐



所有评论(0)