Hive与Cassandra在数据处理中的应用及案例分析
1. Hive在不同业务场景中的应用
1.1 广告运营与产品开发
在广告运营方面,Hive可用于筛选历史数据以进行预测,并为广告定向定义配额。而产品开发团队是临时查询数量最多的群体。由于用户群体的细分会随时间变化和发展,Hive的重要性体现在它能对当前和历史数据进行A/B测试,从而在快速变化的用户环境中评估新产品的相关性。
1.2 运营管理与资源控制
在Photo - bucket,为用户提供一流的系统是最重要的目标。从运营角度看,Hive用于生成跨多个维度分区的汇总数据,了解最受欢迎的媒体、用户和推荐域名对公司各级都很重要。同时,控制费用对任何组织都至关重要,单个用户可能会迅速消耗大量系统资源,显著增加每月支出。Hive可用于识别和分析违规用户,确定哪些用户符合服务条款,哪些不符合。此外,运营部门还使用Hive进行A/B测试,以定义新的硬件需求并进行投资回报率(ROI)计算。Hive能让用户脱离底层的MapReduce代码,使得问题可以在数小时或数天内得到解答,而非数周。
2. SimpleReach的数据处理流程
2.1 数据存储与格式
SimpleReach使用Cassandra存储来自所有社交网络轮询的原始数据。行键的格式是账户ID(MongoDB ObjectId)和内容项ID(被跟踪内容项URL的MD5哈希值),两者用下划线分隔,在结果集中通过分割下划线来提供数据。行中的列是复合列,例如:
4e87f81ca782f3404200000a_8c825814de0ac34bb9103e2193a5b824
=> (column=meta:published - at, value=1330979750000, timestamp=1338919372934628)
=> (column=hour:1338876000000_digg - diggs, value=84, timestamp=1338879756209142)
=> (column=hour:1338865200000_googleplus - total, value=12, timestamp=1338869007737888)
2.2 数据查询与处理
为了对复合列进行查询,需要知道列名的十六进制值。例如,列名(meta:’published - at’)的十六进制等效值为:00046D65746100000C7075626C69736865642D617400 = meta:published - at。
将列名转换为十六进制格式后,就可以运行Hive查询。查询的第一部分是LEFT SEMI JOIN,用于模拟SQL子查询。所有对SUBSTR和INSTR的引用都是为了处理复合列的情况。由于预先知道“hour:*”列的第10 - 23个字符是时间戳,因此可以将其截取出来用于返回数据或进行匹配。INSTR用于匹配列名,确保结果集的输出中列的位置始终相同。SUBSTR作为Ruby函数的一部分用于匹配,它返回自纪元以来的时间戳(长整型,以毫秒为单位),而start_date和end_date也是以毫秒为单位的时间戳,这意味着传入的值可以与列名的一部分进行匹配。
查询的目标是将数据从Cassandra导出到CSV文件,为发布者提供聚合数据转储。这是通过Resque(离线)作业完成的,该作业通过Rails堆栈启动。为了得到完整的CSV文件,Hive查询中必须考虑标题中的所有列(即在没有数据的地方填充零),通过使用CASE语句将宽行转换为固定列的表来实现。
以下是用于生成CSV文件的HiveQL:
SELECT CAST(SUBSTR(r.column_name, 10, 13) AS BIGINT) AS epoch,
SPLIT(r.row_key, '_')[0] AS account_id,
SPLIT(r.row_key, '_')[1] AS id,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'pageviews - total') > 0
THEN r.value ELSE '0' END AS INT)) AS pageviews,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'digg - digg') > 0
THEN r.value ELSE '0' END AS INT)) AS digg,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'digg - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS digg_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'delicious - total') > 0
THEN r.value ELSE '0' END AS INT)) AS delicious,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'delicious - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS delicious_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'googleplus - total') > 0
THEN r.value ELSE '0' END AS INT)) AS google_plus,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'googleplus - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS google_plus_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'facebook - total') > 0
THEN r.value ELSE '0' END AS INT)) AS fb_total,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'facebook - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS fb_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'twitter - tweet') > 0
THEN r.value ELSE '0' END AS INT)) AS tweets,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'twitter - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS twitter_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'linkedin - share') > 0
THEN r.value ELSE '0' END AS INT)) AS linkedin,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'linkedin - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS linkedin_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'stumbleupon - total') > 0
THEN r.value ELSE '0' END AS INT)) AS stumble_total,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'stumbleupon - referrer') > 0
THEN r.value ELSE '0' END AS INT)) AS stumble_ref,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'social - actions') > 0
THEN r.value ELSE '0' END AS INT)) AS social_actions,
SUM(CAST(CASE WHEN INSTR(r.column_name, 'referrer - social') > 0
THEN r.value ELSE '0' END AS INT)) AS social_ref,
MAX(CAST(CASE WHEN INSTR(r.column_name, 'score - realtime') > 0
THEN r.value ELSE '0.0' END AS DOUBLE)) AS score_rt
FROM content_social_delta r
LEFT SEMI JOIN (SELECT row_key
FROM content
WHERE HEX(column_name) = '00046D65746100000C7075626C69736865642D617400'
AND CAST(value AS BIGINT) >= #{start_date}
AND CAST(value AS BIGINT) <= #{end_date}) c ON c.row_key = SPLIT(r.row_key, '_')[1]
WHERE INSTR(r.column_name, 'hour') > 0
AND CAST(SUBSTR(r.column_name, 10, 13) AS BIGINT) >= #{start_date}
AND CAST(SUBSTR(r.column_name, 10, 13) AS BIGINT) <= #{end_date}
GROUP BY CAST(SUBSTR(r.column_name, 10, 13) AS BIGINT),
SPLIT(r.row_key, '_')[0],
SPLIT(r.row_key, '_')[1];
查询的输出是逗号分隔值(CSV)文件,示例如下:
epoch,account_id,id,pageviews,digg,digg_ref,delicious,delicious_ref,
google_plus,google_plus_ref,fb_total,fb_ref,tweets,twitter_ref,
linkedin,linkedin_ref,stumble_total,stumble_ref,social_actions,social_ref,score_rt
1337212800000,4eb331eea782f32acc000002,eaff81bd10a527f589f45c186662230e,
39,0,0,0,0,0,0,0,2,0,20,0,0,0,0,0,22,0
1337212800000,4f63ae61a782f327ce000007,940fd3e9d794b80012d3c7913b837dff,
101,0,0,0,0,0,0,44,63,11,16,0,0,0,0,55,79,69.64308064
1337212800000,4f6baedda782f325f4000010,e70f7d432ad252be439bc9cf1925ad7c,
260,0,0,0,0,0,0,8,25,15,34,0,0,0,0,23,59,57.23718477
1337216400000,4eb331eea782f32acc000002,eaff81bd10a527f589f45c186662230e,
280,0,0,0,0,0,0,37,162,23,15,0,0,0,2,56,179,72.45877173
1337216400000,4ebd76f7a782f30c9b000014,fb8935034e7d365e88dd5be1ed44b6dd,
11,0,0,0,0,0,0,0,1,1,4,0,0,0,0,0,5,29.74849901
2.3 数据处理流程总结
以下是SimpleReach的数据处理流程的mermaid流程图:
graph LR
A[存储原始数据到Cassandra] --> B[获取列名十六进制值]
B --> C[运行Hive查询]
C --> D[导出数据到CSV文件]
3. 客户实际案例中的需求与解决方案
3.1 客户对Hive的需求
在与众多采用Hadoop并倾向于使用Hive的公司合作的18个月里,发现公司在计划并进入Hive的生产使用阶段时,希望Hive能具备一些增量功能,让基于Hive的Hadoop访问更易用、高效、强大,并能让更多人使用。具体需求包括:
- 更简单的数据摄入方式,能检测原始格式并创建元数据。
- 在集成的多用户环境中进行协作。
- 迭代式地探索和分析数据。
- 保留并可重复使用洞察路径。
- 对数据、表和列的安全性进行更细粒度的控制,以及对不同业务线进行分区访问。
- 业务用户无需SQL技能即可访问分析。
- 安排查询以自动生成结果并导出到非Hadoop数据存储。
- 与Microsoft Excel、Tableau、Spotfire等电子表格、报告系统、仪表盘和商业智能(BI)工具集成。
- 管理基于Hive的资产,包括查询、结果、可视化以及UDF和SerDes等标准Hive组件。
3.2 不同客户场景下的解决方案
3.2.1 最优数据格式
许多Hive用户会遇到数据格式的问题,虽然Hive支持多种数据格式,但一些自定义专有格式可能不被支持,或者即使支持,也存在如何从一行数据中提取单个组件的问题。有时,编写支持自定义数据格式的标准Hive SerDe是最佳方法;在其他情况下,使用现有的Hive分隔符并利用Hive UDF是最方便的解决方案。
例如,有一家公司使用Hadoop和Hive通过分析多个输入数据流来提供个性化服务,他们从一个数据提供商那里收到的日志文件格式难以拆分为列。数据包含顶部标题信息和多个详细信息,详细部分是嵌套在顶级对象中的JSON,类似如下数据集:
{
"top": [
{
"table": "user",
"data": {
"name": "John Doe",
"userid": "2036586",
"age": "74",
"code": "297994",
"status": 1
}
},
{
"table": "user",
"data": {
"name": "Mary Ann",
"userid": "14294734",
"age": "64",
"code": "142798",
"status": 1
}
},
{
"table": "user",
"data": {
"name": "Carl Smith",
"userid": "13998600",
"age": "36",
"code": "32866",
"status": 1
}
},
{
"table": "user",
"data": {
"name": "Anil Kumar",
"age": "69",
"code": "208672",
"status": 1
}
},
{
"table": "user",
"data": {
"name": "Kim Lee",
"userid": "10471190",
"age": "53",
"code": "79365",
"status": 1
}
}
]
}
为了帮助客户处理数据,使用
get_json_object
函数,操作步骤如下:
1. 创建表:
CREATE TABLE user (line string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION 'hdfs://hostname/user/uname/tablefolder/';
-
使用
get_json_object函数解析嵌套的JSON元素:
SELECT get_json_object(col0, '$.name') as name,
get_json_object(col0, '$.userid') as uid,
get_json_object(col0, '$.age') as age,
get_json_object(col0, '$.code') as code,
get_json_object(col0, '$.status') as status
FROM
(SELECT get_json_object(user.line, '$.data') as col0
FROM user
WHERE get_json_object(user.line, '$.data') is not null) temp;
查询结果以CSV文件保存,如下所示:
"name","uid","age","code","status"
"John Doe","2036586","74","297994","1"
"Mary Ann","14294734","64","142798","1"
"Carl Smith","13998600","36","32866","1"
"Kim Lee","10471190","53","79365","1"
3.2.2 分区与性能
当数据流式传输或定期添加到Hadoop时,使用分区是常见且有效的方式,可显著提高查询性能。理想情况下,设置分区的表,其文件应位于类似如下结构的目录中:
hdfs://user/uname/folder/"yr"=2012/"mon"=01/"day"=01/file1, file2, file3
/"yr"=2012/"mon"=01/"day"=02/file4, file5
…......
/"yr"=2012/"mon"=05/"day"=30/file100, file101
这样可以按年、月、日对表进行分区,查询时可使用
yr
、
mon
和
day
作为列,限制访问特定值的数据。
但在实际中,有一家高科技公司的文件夹没有明确的分区命名,且无法更改现有目录结构,其目录结构如下:
hdfs://user/uname/folder/2012/01/01/file1, file2, file3
/2012/01/02/file4, file5
…….
/2012/05/30/file100, file101
在这种情况下,可以通过
ALTER TABLE
语句显式添加绝对路径的位置来添加分区。可以编写一个简单的外部脚本,读取目录并将
yr=
、
mon=
、
day=
添加到
ALTER TABLE
语句中,并为其提供文件夹的值,脚本输出的一组Hive SQL语句可保存到文本文件中,例如:
ALTER TABLE tablename
ADD PARTITION (yr=2012, mon=01, day=01) location '/user/uname/folder/2012/01/01/';
ALTER TABLE tablename
ADD PARTITION (yr=2012, mon=01, day=02) location '/user/uname/folder/2012/01/02/';
...
ALTER TABLE tablename
ADD PARTITION (yr=2012, mon=05, day=30) location '/user/uname/folder/2012/05/30/';
执行这些语句后,指定目录中的数据将在使用
ALTER TABLE
语句创建的逻辑分区下可用。需要注意的是,表必须使用
PARTITIONED BY
列创建年、月、日分区。
3.2.3 文本分析
很多公司有文本分析的需求,从简单到复杂各不相同。理解和使用Hive的正则表达式函数、n - gram函数和其他字符串函数可以解决许多此类用例。
例如,一家大型制造企业有大量机器生成的压缩文本数据被摄入Hadoop,数据格式如下:
- 每个文件有多行数据,且有多个这样的文件按时间分区存储。
- 每行中有多个由
\r\n
分隔的段。
- 每个段是“名称: 值”对的形式。
用例需求是:
1. 读取每行数据,将各个段分离为名称 - 值对。
2. 聚焦特定段,查找单词计数和单词模式,以分析关键词和特定消息。
示例数据如下:
name:Mercury\r\ndescription:Mercury is the god of commerce, ...\r\ntype:Rocky planet
name:Venus\r\ndescription:Venus is the goddess of love...\r\ntype:Rocky planet
name:Earch\r\ndescription:Earth is the only planet ...\r\ntype:Rocky planet
name:Mars\r\ndescription: Mars is the god of War...\r\ntype:Rocky planet
name:Jupiter\r\ndescription:Jupiter is the King of the Gods...\r\ntype:Gas planet
name:Saturn\r\ndescription:Saturn is the god of agriculture...\r\ntype:Gas planet
name:Uranus\r\ndescription:Uranus is the God of the Heavens...\r\ntype:Gas planet
name:Neptune\r\ndescription:Neptune was the god of the Sea...\r\ntype:Gas planet
处理步骤如下:
1. 创建初始表:
CREATE TABLE planets (col0 string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION 'hdfs://hostname/user/uname/planets/';
-
使用
split函数将每行数据的各个部分分离为数组:
SELECT split(col0, '(\\r\\n)') AS splits FROM planets;
-
使用
LATERAL VIEW EXPLODE函数将数组展开为单独的行,并筛选以description开头的行:
SELECT ltrim(splits) AS pairs FROM planets
LATERAL VIEW EXPLODE(split(col0, '(\\r\\n)')) col0 AS splits
WHERE ltrim(splits) LIKE 'desc%';
- 将描述行分离为名称 - 值对,并选择值数据:
SELECT (split(pairs, ':'))[1] AS txtval FROM (
SELECT ltrim(splits) AS pairs FROM planets
LATERAL VIEW EXPLODE(split(col0, '(\\r\\n)')) col0 AS splits
WHERE ltrim(splits) LIKE 'desc%') tmp1;
-
使用
ngrams函数显示行星描述中排名前10的二元组(2 - gram)单词:
SELECT ngrams(sentences(lower(txtval)), 2, 10) AS bigrams FROM (
SELECT (split(pairs, ':'))[1] AS txtval FROM (
SELECT ltrim(splits) AS pairs FROM planets
LATERAL VIEW EXPLODE(split(col0, '(\\r\\n)')) col0 AS splits
WHERE ltrim(splits) LIKE 'desc%') tmp1) tmp2;
通过以上案例可以看出,Hive在不同的数据处理场景中都有广泛的应用,并且能够根据不同的需求和数据格式提供有效的解决方案。无论是广告运营、社交网络数据处理还是复杂的文本分析,Hive都展现出了强大的功能和灵活性。同时,在实际应用中,需要根据具体情况选择合适的方法和工具,以达到最佳的数据处理效果。
4. Hive应用案例总结与对比
4.1 不同应用场景对比
| 应用场景 | 数据特点 | 处理需求 | 解决方案 |
|---|---|---|---|
| 广告运营与产品开发 | 历史数据、用户细分变化 | 预测、A/B测试评估新产品相关性 | 使用Hive筛选历史数据,进行A/B测试 |
| 运营管理与资源控制 | 多维度业务数据、用户资源消耗数据 | 生成汇总数据、识别违规用户、计算ROI | Hive生成跨维度汇总数据,分析用户资源使用情况 |
| SimpleReach社交网络数据处理 | 复合列数据、时间戳信息 | 数据导出到CSV文件 | 将列名转换为十六进制,使用Hive查询,通过Resque作业导出数据 |
| 最优数据格式处理 | 嵌套JSON数据 | 解析JSON数据提取列信息 | 使用Hive的get_json_object函数解析JSON |
| 分区与性能优化 | 流式或定期添加的数据 | 提高查询性能 | 设置分区目录结构,或通过ALTER TABLE语句添加分区 |
| 文本分析 | 机器生成的压缩文本数据 | 分离名称 - 值对,分析关键词 | 使用Hive的split、LATERAL VIEW EXPLODE、ngrams等函数进行处理 |
4.2 解决方案对比
不同场景下的解决方案虽然各有特点,但都围绕着Hive的核心功能展开。在数据格式处理方面,对于简单的嵌套JSON数据,使用Hive内置函数即可完成解析;而对于复杂的复合列数据,则需要进行列名转换和特殊的查询处理。在性能优化方面,合理设置分区是提高查询效率的关键,无论是标准的分区目录结构还是通过ALTER TABLE语句添加分区,都能有效减少数据访问量。
5. Hive应用的关键要点与注意事项
5.1 数据格式处理要点
- 对于自定义数据格式,优先考虑现有的Hive分隔符和UDF,如果无法满足需求,再考虑编写自定义SerDe。
- 在使用Hive函数解析JSON数据时,要确保数据结构的一致性,避免出现空值或解析错误。
5.2 分区设置注意事项
- 表创建时要使用PARTITIONED BY列明确分区字段,如年、月、日等。
- 当目录结构不符合标准分区命名时,使用ALTER TABLE语句添加分区时要确保路径的准确性。
5.3 文本分析技巧
- 在使用split、LATERAL VIEW EXPLODE等函数时,要注意数据的分隔符和格式,避免出现数据分割错误。
- 使用ngrams等函数进行文本分析时,要根据具体需求调整参数,如n - gram的长度和显示数量。
6. 未来发展趋势与展望
6.1 功能扩展趋势
随着企业对数据处理和分析需求的不断增长,Hive可能会进一步扩展功能,例如支持更多的数据格式、提供更强大的文本分析工具、增强与其他数据存储和处理系统的集成能力。
6.2 性能优化方向
为了应对大规模数据处理的挑战,Hive可能会在性能优化方面进行更多的探索,如优化查询引擎、提高分区管理效率、减少资源消耗等。
6.3 用户体验提升
未来Hive可能会更加注重用户体验,提供更友好的界面和操作方式,降低业务用户使用Hive进行数据分析的门槛,让更多非技术人员能够轻松使用Hive进行数据探索和分析。
6.4 发展趋势的mermaid流程图
graph LR
A[功能扩展] --> B[支持更多数据格式]
A --> C[提供强大文本分析工具]
A --> D[增强系统集成能力]
E[性能优化] --> F[优化查询引擎]
E --> G[提高分区管理效率]
E --> H[减少资源消耗]
I[用户体验提升] --> J[友好界面]
I --> K[降低使用门槛]
综上所述,Hive在数据处理和分析领域具有广泛的应用前景和强大的功能。通过对不同应用场景的案例分析,我们可以看到Hive能够灵活应对各种数据格式和处理需求。在实际应用中,我们需要根据具体情况选择合适的解决方案,并注意数据格式处理、分区设置和文本分析等关键要点。随着技术的不断发展,Hive有望在功能扩展、性能优化和用户体验提升等方面取得更大的进步,为企业的数据驱动决策提供更有力的支持。
超级会员免费看
895

被折叠的 条评论
为什么被折叠?



