1. 项目背景与技术选型
健康饮食推荐系统是一个结合大数据技术与个性化推荐算法的综合平台,旨在为用户提供科学、精准的饮食建议。随着现代生活节奏加快,人们越来越关注饮食健康,但面对海量的营养信息和复杂的饮食搭配规则,普通用户往往难以做出合理选择。这正是本项目要解决的核心痛点。
技术架构上,系统采用前后端分离设计:
- 前端使用Vue.js+Element UI构建响应式界面
- 后端基于Spring Boot框架开发RESTful API
- 数据存储采用MySQL+HDFS混合方案
- 核心推荐算法运行在Hadoop集群上
这种架构选择主要基于三个考量:
- 性能需求:Hadoop的分布式计算能力可处理TB级营养数据
- 扩展性需求:微服务架构便于后续添加新的推荐算法
- 开发效率:Vue+Spring Boot的全栈组合能快速迭代
提示:实际部署时建议使用CDH(Cloudera Distribution)版本的Hadoop,其预集成了HDFS、YARN等组件,比原生Hadoop节省约40%的部署时间。
2. 环境搭建与依赖安装
2.1 基础环境准备
系统需要以下基础组件:
- JDK 1.8+
- Hadoop 3.3.4
- MySQL 8.0
- Node.js 14.x
- Python 3.7(用于数据爬虫)
Hadoop集群建议采用3节点配置(1个NameNode+2个DataNode),最低硬件要求:
# 每个节点配置 CPU: 4核+ 内存: 8GB+ 磁盘: 100GB+ (SSD推荐) 网络: 千兆网卡2.2 Hadoop集群配置
关键配置文件修改(以core-site.xml为例):
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://namenode:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop/tmp</value> </property> </configuration>需要特别注意的配置项:
- yarn.nodemanager.resource.memory-mb:建议设为物理内存的80%
- mapreduce.map.memory.mb:单Map任务内存,默认1024MB
- dfs.replication:数据副本数,生产环境建议3
2.3 数据库初始化
执行项目中的SQL脚本创建表结构:
CREATE TABLE `food_nutrition` ( `id` bigint NOT NULL AUTO_INCREMENT, `food_name` varchar(100) COLLATE utf8mb4_bin NOT NULL, `calories` decimal(10,2) DEFAULT NULL, `protein` decimal(10,2) DEFAULT NULL, `fat` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;3. 系统模块详解
3.1 数据采集层
采用Scrapy框架构建分布式爬虫,从权威营养网站抓取数据。爬虫关键组件:
class NutritionSpider(scrapy.Spider): name = 'usda' custom_settings = { 'ITEM_PIPELINES': { 'pipelines.HDFSPipeline': 300 } } def parse(self, response): item = NutritionItem() item['name'] = response.css('.food-name::text').get() item['calories'] = parse_energy(response.xpath('//td[contains(.,"Energy")]/following-sibling::td/text()').get()) yield item数据流向示意图:
- 爬虫抓取原始数据 → Kafka消息队列
- Spark Streaming清洗数据 → HDFS存储
- MapReduce作业计算营养指标 → MySQL维度表
3.2 推荐算法实现
核心采用改进的协同过滤算法,主要创新点:
- 引入时间衰减因子:近期饮食记录权重更高
- 营养均衡约束:避免推荐单一营养素的食物
- 冷启动处理:新用户采用基于内容的推荐
算法MapReduce实现关键类:
public class RecommendMapper extends Mapper<LongWritable, Text, Text, Text> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] parts = value.toString().split("\t"); String userId = parts[0]; String foodId = parts[1]; double rating = Double.parseDouble(parts[2]); // 时间衰减计算 long timestamp = Long.parseLong(parts[3]); double timeWeight = Math.exp(-0.0001*(System.currentTimeMillis()-timestamp)); context.write(new Text(userId), new Text(foodId + ":" + rating*timeWeight)); } }3.3 前后端交互设计
API接口规范示例:
// 获取推荐列表 GET /api/recommend?user_id=123&page=1&size=10 // 响应结构 { "code": 200, "data": { "recommendations": [ { "food_id": 456, "name": "西兰花炒虾仁", "match_score": 0.87, "nutrients": { "calories": 156, "protein": 18.2 } } ] } }前端采用Vuex管理状态,关键store模块:
const actions = { async fetchRecommendations({ commit }, userId) { const res = await api.getRecommendations(userId) commit('SET_RECOMMENDATIONS', res.data) // 记录曝光日志 logService.track('recommend_show', { items: res.data.map(item => item.food_id) }) } }4. 部署与调优指南
4.1 集群部署方案
推荐使用Ansible进行自动化部署,inventory文件示例:
[namenode] nn1 ansible_host=192.168.1.101 [datanode] dn1 ansible_host=192.168.1.102 dn2 ansible_host=192.168.1.103 [all:vars] ansible_user=hadoop java_home=/usr/lib/jvm/java-8-openjdk hadoop_version=3.3.4部署步骤:
- 安装基础依赖(JDK、SSH)
- 配置无密码登录
- 分发Hadoop安装包
- 同步配置文件
- 格式化HDFS
- 启动集群服务
4.2 性能调优经验
通过实际压测发现的优化点:
HDFS小文件问题:
- 现象:NameNode内存占用过高
- 解决方案:采用HAR归档历史数据
- 效果:内存占用降低62%
MapReduce数据倾斜:
- 现象:个别Reducer处理时间过长
- 解决方案:实现自定义Partitioner
public class NutritionPartitioner extends Partitioner<Text, Text> { @Override public int getPartition(Text key, Text value, int numPartitions) { String userId = key.toString(); // 均匀分散热门用户 return (userId.hashCode() & Integer.MAX_VALUE) % numPartitions; } }MySQL连接池配置:
spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000
4.3 监控方案设计
建议部署以下监控组件:
- Prometheus + Grafana:采集集群指标
- 关键指标:HDFS存储利用率、YARN资源使用率
- ELK:收集业务日志
- 重点监控:推荐响应时间、算法异常
- 自定义健康检查接口:
@RestController @RequestMapping("/health") public class HealthController { @GetMapping("/cluster") public ResponseEntity<Map<String, Object>> clusterHealth() { Map<String, Object> status = new HashMap<>(); status.put("hdfs", hdfsService.checkStatus()); status.put("yarn", yarnClient.getClusterStatus()); return ResponseEntity.ok(status); } }
5. 典型问题排查手册
5.1 数据不一致问题
现象:前端显示的营养数据与HDFS存储不一致
排查步骤:
- 检查Kafka消费延迟:
kafka-consumer-groups.sh --describe \ --group nutrition-group \ --bootstrap-server kafka:9092 - 验证Spark作业状态:
spark-submit --status <app-id> - 对比HDFS与MySQL数据:
SELECT count(*) FROM food_nutrition # 与hdfs dfs -cat /nutrition/stats/* | wc -l对比
常见原因:
- Spark处理逻辑变更未同步更新
- MySQL连接池配置过小导致写入超时
5.2 推荐结果异常
现象:用户收到明显不合理的推荐(如过敏食物)
检查清单:
- 确认用户画像数据是否完整
userProfileRepository.findById(userId) - 检查特征工程是否正常
# 检查特征向量维度 model.feature_importances_ - 验证算法输入数据范围
SELECT min(calories), max(calories) FROM food_nutrition
解决方案:
- 添加营养约束规则:
if (user.hasAllergy(food)) { return 0; // 一票否决 } - 引入人工审核机制
5.3 集群性能下降
现象:作业执行时间逐渐变长
诊断工具:
- YARN资源分析:
yarn application -list -appStates ALL - HDFS磁盘平衡:
hdfs balancer -threshold 10 - JVM堆栈分析:
jmap -histo:live <pid>
优化建议:
- 定期清理临时文件
hdfs dfs -expunge - 调整MapReduce内存参数
<property> <name>mapreduce.map.memory.mb</name> <value>2048</value> </property> - 启用HDFS缓存
hdfs cacheadmin -addPool cache_pool -mode 0777
在真实生产环境中,我们通过以上方法将平均推荐响应时间从2.3秒降低到780毫秒,集群资源利用率提升了35%。特别需要注意的是,Hadoop参数调优需要根据实际负载反复验证,建议建立基准测试套件持续监控性能变化。