一个小兔子的大数据见解1

  1. 离线阶段

   刚去公司的时候,做数据的迁移,写sqoop脚本,(注意:这里可能会问到sqoop增量导入数据的方式式,一般会用到append追加的模式)把数据从oracle数据库导入到hive当中(注意:

     a.这里我们使用是shell脚本的方式,写完以后提交到oozie或azkaban这种调度工具上定时执行。b.这里有技术的问题,实际上把数据放到hive中是放到了hadoop的hdfs上面),以方便后续数据分析人员做数据的分析,做了大概半年的时间,又去写hql语句,做数据的分析,分析 (1).订单量,例如近一星期,半个月,一个月。或这一个季度和上一个季度订单量的对比(还包含,成交订单,退货单等等。这个模块非常大可说的点也非常多,可自由发挥一下),

   (2).活跃用户度分析,例如近一个月与上个月比较活跃用户度,近半年的活跃用户分析

   (3).忠实用户度分析(可以写成会员用户分析),普通会员新增,忠实会员流失分析,忠实会员(忠实会员是近半年都在平台比较活跃的用户(这里可以单独拿出一个模块对活跃会员进行分析))

   (4).基于平台满意度的分析,用户反馈满意度(如评价,在各个商店app的打分,小程序的评分)分析

   (5).退货单分析(包含在订单分析当中:订单分析是一个非常大的模块):基于产品问题,用户自身问题,售后模块的问题,平台的更进度,客户服务满意度。

   (6).点击量分析:pv,uv等

   (7).新增用户数分析:按照唯一的id去分析新增用户数然后可以和之前的数据进行累加求取     总用户数。

   注意:我上面提到的模块可以分为很细,单个去做也是很复杂的,我这里只是举例子可以这样去说。

   离线数据分析模块能说的非常多,现在大数据分析都是基于离线分析的。可以想一想像淘宝这种平台,订单量,发货量,注册商家数,金牌商家,商户新增,这些全部会涉及到钱的方面。所以说可扩展的方面非常多,发挥自己的想想,围绕我们大数据离线处理去说。可以结合hive中的分区分桶。比如订单,可以按时间,按区域进行分区。注意说的时候一定把这些业务结合技术。

 

  1. 实时阶段

  实时阶段主要是用flume拿取数据(注意:a.拿去数据的数据源有很多,如:app,网站,小程序,第三方平等等b.拿去数据的策略,如每个小时拿取一次或半小时或两小时等看需求),然后运用kafka(注意:这里会问到kafka的吞吐量,分区等)传到sparkstreaming去做分析,

  (1).平台浏览量,这个很好理解,简单理解就是点击量。(这里可以对用户的浏览深度进行分析这就比较难了,对浏览器进行分析(a.这里可以单独拿出一个模块对流量进行分析,如果用户平均pv比较高,那么表示网站对用户的引起程度比较高;如果是值比较低,那么表示网站对用户的吸引程度比较低。这个时候,就可以通过跳出率等其他统计指标来找出网站的问题所在。b.对区域信息可以分析,按照国家,省份,城市,ip,用户id,时间)

  (2).注册用户,实时注册用户数量,这个也很好理解。

  (3).平台转化率:页面跳转流失率。

  (4).各客户端使用率分析:因为我们拿取得数据不是是来自多个数据源的。

  (5)广告点击分析:

   以上几个模块要注意你要拿取什么字段进行分析,ip,手机号,时间戳,地区id,sessionid,广告id,服务器信息,浏览器,手机型号/电脑型号

  注意以上提到的模块是非常大的模块,你要清楚的是你要拿取分析的字段,并且要放入的数库表。

  1. hbase,是大数据实时存储的非常主流的nosql数据库,一定要明白hbase架构和hbase数据库表的构建。(注意:hbasea构建二级索引。例如,与phoenix整合构建二级索引)
  2. 以上总结的东西有很多技术方面的,如

MR(这个要清楚hadoop的原理,hadoop的通信机制,上传下载文件的机制,MR的原理,shuffle机制)

hiveSql(join操作,分区,分桶,hive的优化,自定义udf与udaf函数,开窗函数,hive优化实际上是mr的优化,combiner函数,合并小文件,减少shuffle,分区设置(配置和代码中))

Sqoop(增量导入数据的方法,有两种常用的是append追加的方法)

shell脚本的编写(这个面试基本不会问)

Flume(配置与架构,多路复用,多路复制等(基本不会问,知道怎么写就行))

Kafka(这个问的比较多,吞吐量,分区)

Spark(要知道RDD,cache和persit的关系,checkpoit机制,lineage血统,宽窄依赖的划分(这几个都是概念性的东西说出来就行) spark-core,spark-streaming,spark-sql(原理也要知道,要能说出来)   blockmanger(spark内存处理的核心要知道) ,另外现在spark的通信机制是netty(2.0之前是akka),spark的shuffle机制)

Zookeeper:zk的架构,zk的作用

 

另外linux也要了解。

 

 

 

前人总结出来的,觉得挺好,首先要构建电商数据分析的基本指标体系,主要分为8个类指标,即:

 

总体运营指标:从流量、订单、总体销售业绩、整体指标进行把控,起码对运营的电商平台有个大致了解,到底运营的怎么样,是亏是赚。  2.网站流量指标:即对访问你网站的访客进行分析,基于这些数据可以对网页进行改进,以及对访客的行为进行分析等等。  \3. 销售转化指标:分析从下单到支付整个过程的数据,帮助你提升商品转化率。也可以对一些频繁异常的数据展开分析。  \4. 客户价值指标:这里主要就是分析客户的价值,可以建立RFM价值模型,找出那些有价值的客户,精准营销等等。  5.商品类指标:主要分析商品的种类,那些商品卖得好,库存情况,以及可以建立关联模型,分析那些商品同时销售的几率比较高,而进行捆绑销售,有点像啤酒喝尿布的故事。

 

  1. 市场营销活动指标,主要监控某次活动给电商网站带来的效果,以及监控广告的投放指标。

风控类指标:分析卖家评论,以及投诉情况,发现问题,改正问题

\8. 市场竞争指标:主要分析市场份额以及网站排名,进一步进行调整

 以上总共从8个方面来阐述如何对电商平台进行数据分析,当然,具体问题具体分析,每个公司的侧重点也有所差异,所以如何分析还需因地制宜。

商品推荐

• 基础数据包括很多维度,包括用户的访问、浏览、下单、收藏,用户的历史订单信息,评价信息等很多信息

• 在电商项目中,用户的数量远大于商品数量,所以基于商品的推荐的复杂度要低,而且也比较合理。�I?x�#

3、用户偏好收集****

用户和商品的之间的关系,什么样的数据能够表示用户对商品的喜好呢? 成为系统推荐效果最基础的决定因素。要从用户的行为和偏好中发现规律,并基于此给予推荐,如何收集用户的偏好信息,用户有很多方式向系统自己的偏好信息,而且不同的应用也可能大不相同,下面举例进行介绍:

 

7.2、简化实现****

 

在这里我们用MySQL数据库作为数据的存储,来展示推荐系统的实现思路。

• tb_user:有1000条用户数据;

• tb_item:有3089条商品数据;

• taste_preferences:有1000条偏好数据,其中用户id 1~50的用户数据要多一些。

• 用户行为的收集,不仅仅是评分一个维度,可以是多个维度,比如说收藏、浏览等。

• 根据用户浏览商品的推荐。

• 海量数据的离线计算。

• 推荐的结果数据需要进行缓存处理。

2、协同过滤算法****

迄今为止,在个性化推荐系统中,协同过滤技术是应用最成功的技术。目前国内外有许多大型网站应用这项技术为用户更加智能(个性化、千人千面)的推荐内容。

核心思想:

协同过滤一般是在海量的用户中发掘出一小部分和你品位比较类似的,在协同过滤中,这些用户成为邻居,然后根据他们喜欢的其他东西组织成一个排序的目彔作为推荐给你。

需求:根据用户指定的日期范围,统计各个区域下的最热门的top3商品

Spark作业接收taskid,查询对应的MySQL中的task,获取用户指定的筛选参数;统计出指定日期范围内的,各个区域的top3热门商品;最后将结果写入MySQL表中

技术方案设计:

1、查询task,获取日期范围,通过Spark SQL,查询user_visit_action表中的指定日期范围内的数据,过滤出,商品点击行为,click_product_id is not null;click_product_id != 'NULL';click_product_id != 'null';city_id,click_product_id

2、使用Spark SQL从MySQL中查询出来城市信息(city_id、city_name、area),用户访问行为数据要跟城市信息进行join,city_id、city_name、area、product_id,RDD,转换成DataFrame,注册成一个临时表

3、Spark SQL内置函数(case when),对area打标记(华东大区,A级,华中大区,B级,东北大区,C级,西北大区,D级),area_level

4、计算出来每个区域下每个商品的点击次数,group by area, product_id;保留每个区域的城市名称列表;自定义UDAF,group_concat_distinct()函数,聚合出来一个city_names字段,area、product_id、city_names、click_count

5、join商品明细表,hive(product_id、product_name、extend_info),extend_info是json类型,自定义UDF,get_json_object()函数,取出其中的product_status字段,if()函数(Spark SQL内置函数),判断,0 自营,1 第三方;(area、product_id、city_names、click_count、product_name、product_status)

6、开窗函数,根据area来聚合,获取每个area下,click_count排名前3的product信息;area、area_level、product_id、city_names、click_count、product_name、product_status

7、结果写入MySQL表中

8、Spark SQL的数据倾斜解决方案?双重group by、随机key以及扩容表(自定义UDF函数,random_key())、Spark SQL内置的reduce join转换为map join、提高shuffle并行度

9、本地测试和生产环境的测试

基础数据的准备和设计

1、MySQL表中,要有city_info,city_id、city_name、area 2、Hive表中,要有一个product_info表,product_id、product_name、extend_info 3、MySQL中,设计结果表,task_id、area、area_level、product_id、city_names、click_count、product_name、product_status

热门广告

1、实现实时的动态黑名单机制:将每天对某个广告点击超过100次的用户拉黑 2、基于黑名单的非法广告点击流量过滤机制: 3、每天各省各城市各广告的点击流量实时统计: 4、统计每天各省top3热门广告 5、统计各广告最近1小时内的点击量趋势:各广告最近1小时内各分钟的点击量 6、使用高性能方式将实时统计结果写入MySQL 7、实现实时计算程序的HA高可用性(Spark Streaming HA方案) 8、实现实时计算程序的性能调优(Spark Streaming Performence Tuning方案)

数据格式介绍:

timestamp 1450702800 province Jiangsu  city Nanjing userid 100001 adid 100001

技术方案设计:

1、实时计算各batch中的每天各用户对各广告的点击次数

2、使用高性能方式将每天各用户对各广告的点击次数写入MySQL中(更新)

3、使用filter过滤出每天对某个广告点击超过100次的黑名单用户,并写入MySQL中

4、使用transform操作,对每个batch RDD进行处理,都动态加载MySQL中的黑名单生成RDD,然后进行join后,过滤掉batch RDD中的黑名单用户的广告点击行为

5、使用updateStateByKey操作,实时计算每天各省各城市各广告的点击量,并时候更新到MySQL

6、使用transform结合Spark SQL,统计每天各省份top3热门广告:首先以每天各省各城市各广告的点击量数据作为基础,首先统计出每天各省份各广告的点击量;然后启动一个异步子线程,使用Spark SQL动态将数据RDD转换为DataFrame后,注册为临时表;最后使用Spark SQL开窗函数,统计出各省份top3热门的广告,并更新到MySQL中

7、使用window操作,对最近1小时滑动窗口内的数据,计算出各广告各分钟的点击量,并更新到MySQL中

8、实现实时计算程序的HA高可用性

9、对实时计算程序进行性能调优

数据设计

CREATE TABLE ad_user_click_count (  date varchar(30) DEFAULT NULL,  user_id int(11) DEFAULT NULL,  ad_id int(11) DEFAULT NULL,  click_count int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE ad_blacklist (  user_id int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE ad_stat (  date varchar(30) DEFAULT NULL,  province varchar(100) DEFAULT NULL,  city varchar(100) DEFAULT NULL,  ad_id int(11) DEFAULT NULL,  click_count int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE ad_province_top3 (  date varchar(30) DEFAULT NULL,  province varchar(100) DEFAULT NULL,  ad_id int(11) DEFAULT NULL,  click_count int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE ad_click_trend (  date varchar(30) DEFAULT NULL,  ad_id int(11) DEFAULT NULL,  minute varchar(30) DEFAULT NULL,  click_count int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8

广告点击流量实时统计模块

电商用户行为分析大数据平台里面来:

1、用户访问session分析模块:会话(session),用户的基础访问行为 2、页面单跳转化率模块:页面(page),用户的页面访问和页面跳转行为 3、各区域热门商品统计模块:商品(product),用户的商品点击行为 4、广告点击流量实时统计模块:广告(ad,advertisement),用户的广告点击行为

电商网站 / app(移动互联网),用户行为的方方面面

咱们这个项目课程,基本上,内容还是非常丰富的;无论是技术含量,还是业务含量;技术上来说,对实际项目进行过改造和整合,比一般的生产环境中的真实项目的技术含量,还要高;业务上来说,除了对一些数据格式和琐碎的业务细节,进行了必要的简化(对大家学习没有任何意义),表中的字段(需要使用的),脏数据(清洗),业务的真实性和复杂性是非常高的,基本上就是完全的完整的一个企业真实项目;

实际企业中的讲师做过的这个项目,模块会达到几十个(二三十个);各个模块之间,做多了会发现,就是业务上有一些不同;其实技术上,基本都涵盖在咱们这个项目课程中了;技术给掌握了,去做任何用户行为分析类的项目,或者是业务需求,都ok,都没有问题的;本项目课程中,涵盖的4个模块,都是非常经典的4个模块

广告点击流量实时统计模块

网站 / app,是不是通常会给一些第三方的客户,打一些广告;也是一些互联网公司的核心收入来源;广告在网站 / app某个广告位打出去,在用户来使用网站 / app的时候,广告会显示出来;此时,有些用户可能就会去点击那个广告。

广告被点击以后,实际上,我们就是要针对这种用户行为(广告点击行为),实时数据,进行实时的大数据计算和统计。

每次点击一个广告以后,通常来说,网站 / app中都会有埋点(前端的应用中,比如JavaScript Ajax;app中的socket网络请求,往后台发送一条日志数据);日志数据而言,通常,如果要做实时统计的话,那么就会通过某些方式将数据写入到分布式消息队列中(Kafka);

日志写入到后台web服务器(nginx),nginx产生的实时的不断增加 / 更新的本地日志文件,就会被日志监控客户端(比如flume agent),写入到消息队列中(kafka),我们要负责编写实时计算程序,去从消息队列中(kafka)去实时地拉取数据,然后对数据进行实时的计算和统计。

这个模块的意义在于,让产品经理、高管可以实时地掌握到公司打的各种广告的投放效果。以便于后期持续地对公司的广告投放相关的战略和策略,进行调整和优化;以期望获得最好的广告收益。

甲问:数仓建模时,涉及到uv(去重用户数)指标时,数据从哪里取,只能走ods原始数据层吗,汇总到dw层时,由于数据量太大,不能到用户粒度,所以在dw层中不包含用户粒度的数据,而且电商的报表分析,几乎每张报表都会涉及到uv指标,如果所有的uv指标都从ods层去获取,那样io是否会成为瓶颈,原始数据量也比较大。举例:比如说有个字段column_a,包含了几百个数据(1-500),统计时可能会统计条件为1-20的uv指标,或者5-50,10-200。。。等等这些uv指标。

乙答:反问三个问题:

1.DW层为何不存储用户粒度的数据? 2.DW层为何不做聚集? 3.UV既然是共性需求,为何不考以DM的方式提供?

甲答:1.原始数据是用户访问网站的情况,用户今天可能访问了多次,也可能只访问一两次,如果dw层汇总数据精确到用户粒度,那dw层相对于ods层,数据压缩比太低了,而且因为数据量也太大,汇总比较耗时的 2.dw层做的是不同维度的聚集,但是没有到用户维度这么细的粒度 3.不是很明白,dm层需要uv指标,目前dm层的数据从dw基础汇总层来的,然后dm层需要的uv从ods原始数据层统计的,就是这样感觉不合理

乙答:

1:感觉你此处的DW其实更像DM,只存储聚集&汇总的结果;通过ODS来存储最细粒度的数据;DW和ODS的数据存储模型还是有差异的,定位也不同,建议是DW层存储最明细粒度的的数据,即每一次的用户访问事件。压缩比的问题属于存储能力和技术问题,与存储模型无关。DW层存在的目的,是提供长周期,更易访问的数据能力,ODS的目的是缓冲区。 2.无论是ODS和DW,都会存在同样的问题,数据量大,汇总耗时,这个不会因为你通过ODS聚集还是通过DW聚集,而有什么不同,你要解决的其实是计算性能问题,与存储无关,建议从技术视角处理。

热门商品

模拟网站实时产生日志信息

Flume Agent2采集日志信息

Flume Agent3采集日志信息

Flume Agent1整合日志信息

Flume与Hbase的集成

Flume与Kafka的集成

Kafka与Spark集成完成数据实时处理

Spark与Mysql集成

离线分析:HIVE集成HBASE。

SpringMVC+Mybatis完成对mysql数据的查询

WebSocket实现全双工通信

Echarts完成前端界面展示

  • 1.5.1流量**数量指标**
  • 1.5.1.1浏览量(PV)
  1. 定义:页面浏览量即为PV(Page View),用户每打开一个页面就被记录 1 次。
  2. 技术说明:一个PV即电脑从网站下载一个页面的一次请求。当页面上的JS文件加载后,统计系统才会统计到这个页面的浏览行为,有如下情况需注意:
  •  1.用户多次打开同一页面,浏览量值累计。
  •  2.如果客户端已经有该缓冲的文档,甚至无论是不是真的有这个页面(比如JavaScript生成的一些脚本功能),都可能记录为一个PV。但是如果利用网站后台日志进行分析,因为缓存页面可能直接显示而不经过服务器请求,那么不会记录为一个 PV。
  1. 涵义:PV越多越说明该页面被浏览的越多。PV之于网站,就像收视率之于电视,已成为评估网站表现的基本尺度。
  • 1.5.1.2访问次数
  1. 定义:访问次数即Visit,访客在网站上的会话(Session)次数,一次会话过程中可能浏览多个页面。
  2. 技术说明:如果访客连续30分钟内没有重新打开和刷新网站的网页,或者访客关闭了浏览器,则当访客下次访问您的网站时,访问次数加1。反之,访客离开后半小时内再返回,则算同一个访次,以上对访客的判断均以 Cookie 为准。
  3. 涵义:页面浏览量(PV)是以页面角度衡量加载次数的统计指标,而访问次数(Visit)则是访客角度衡量访问的分析指标。如果网站的用户黏性足够好,同一用户一天中多次登录网站,那么访问次数就会明显大于访客数。
  • 1.5.1.3访客数(UV)
  1. 定义:访客数(UV)即唯一访客数,一天之内网站的独立访客数(以Cookie 为依据 ),一天内同一访客多次访问网站只计算 1 个访客。
  2. 技术说明:当客户端第一次访问某个网站服务器的时候,网站服务器会给这个客户端的电脑发一个Cookie,记录访问服务器的信息。当下一次再访问服务器的时候,服务器就可以直接找到上一次它放进去的这个Cookie,如果一段时间内,服务器发现两个访次对应的Cookie编号一样,那么这些访次一定就是来自一个 UV 了。
  3. 涵义:唯一访客数(UV)是访客维度看访客到达网站的数量。
  • 1.5.1.4新访客数
  1. 定义:一天的独立访客中,历史第一次访问网站的访客数。
  2. 涵义:新访客数可以衡量营销活动开发新用户的效果。
  • 1.5.1.5新访客比率
  1. 定义:新访客比率=新访客数/访客数。即一天中新访客数占总访客数的比例。
  2. 涵义:整体访客数不断增加,并且其中的新访客比例较高,能表现网站运营在不断进步。就像人体的血液循环一样,有新鲜的血液不断补充进来,充满活力。
  • 1.5.1.6 IP数
  1. 定义:一天之内,访问网站的不同独立IP个数加和。其中同一IP无论访问了几个页面,独立 IP 数均为 1。
  2. 涵义:从 IP 数的角度衡量网站的流量。
  • 1.5.2流量**质量指标**
  • 1.5.2.1跳出率
  1. 定义:只浏览了一个页面便离开了网站的访问次数占总的访问次数的百分比,即只浏览了一个页面的访问次数 / 全部的访问次数汇总。
  2. 涵义:跳出率是非常重要的访客黏性指标,它显示了访客对网站的兴趣程度:跳出率越低说明流量质量越好,访客对网站的内容越感兴趣,这些访客越可能是网站的有效用户、忠实用户。该指标也可以衡量网络营销的效果,指出有多少访客被网络营销吸引到宣传产品页或网站上之后,又流失掉了,可以说就是煮熟的鸭子飞了。比如,网站在某媒体上打广告推广,分析从这个推广来源进入的访客指标,其跳出率可以反映出选择这个媒体是否合适,广告语的撰写是否优秀,以及网站入口页的设计是否用户体验良好。
  • 1.5.2.2平均访问时长
  1. 定义:平均每次访问在网站上的停留时长,即平均访问时长等于总访问时长与访问次数的比值。
  2. 涵义:平均访问时间越长则说明访客停留在网页上的时间越长:如果用户对网站的内容不感兴趣,则会较快关闭网页,那么平均访问时长就短;如果用户对网站的内容很感兴趣,在网站停留了很长时间,平均访问时长就长。
  • 1.5.2.3平均访问页数
  1. 定义:平均每次访问浏览的页面数量,平均访问页数=浏览量/访问次数。
  2. 涵义:平均访问页数多说明访客对网站兴趣越大。而浏览信息多也使得访客对网站更加了解,这对网站市场信息的传递,品牌印象的生成,以至于将来的销售促进都是有好处的。一般来说,会将平均访问页数和平均访问时长这两个指标放在一起分析,进而衡量网站的用户体验情况。
  • 1.5.3流量**转化指标**
  • 1.5.3.1转化次数
  1. 定义:访客到达转化目标页面,或完成网站运营者期望其完成动作的次数。
  2. 涵义:转化就是访客做了任意一项网站管理者希望访客做的事。与网站运营者期望达到的推广目的和效果有关。
  • 1.5.3.2转化率
  1. 定义:转化率 = 转化次数 / 访问次数。
  2. 涵义:转化率即访问转化的效率,数值越高说明越多的访次完成了网站运营者希望访客进行的操作。
  •  
  •  用户 浏览某一个专题页、浏览加入购物车、下订单、支付
  •  a 专题页1 1 1 1
  •  b 专题页1 1 0 0
  •  c 专题页1 1 0 0
  •  d 专题页1 1 1 0
  •  e 专题页1 1 1 0

 

前提是有浏览过:

A转化:   3/5 = 60% 加入购物到下单的转化

B 转换:   1/3 = 33% 下单到支付的转化

支付的问题:支付密码太长,短信没有收到,支持的银行太少,不能货到付款,浏览器的兼容性

重复次数太多,30% 因为密码多次输入造成订单流失,提交点击行为获取

短信没收到,10% 因为短信验证码输入框没有输入,确定记录没有

支持的银行太少,5%,因支付银行列表中没有找到对应的银行

 

 spark算子

## reduce(binary_function)

reduce将RDD中元素前两个传给输入函数,产生一个新的return值,新产生的return值与RDD中下一个元素(第三个元素)组成两个元素,再被传给输入函数,直到最后只有一个值为止。

reduceByKey就是对元素为KV对的RDD中Key相同的元素的Value进行binary_function的reduce操作,因此,Key相同的多个元素的值被reduce为一个值,然后与原RDD中的Key组成一个新的KV对。

因此,在对大数据进行复杂计算时,reduceByKey优于groupByKey。

另外,如果仅仅是group处理,那么以下函数应该优先于 groupByKey

 

  • 一. 表连接优化

 

  1. 将大表放后头
  1.  
  • /*streamtable(table_name) */
  1. 使用相同的连接键
  • 3个或者更多个表进行join连接时,如果每个on子句都使用相同的连接键的话,那么只会产生一个MapReduce job。
  1. 尽量尽早地过滤数据
  • ,对于分区表要加分区,同时只选择需要使用到的字段。

尽量原子化操作

  • SQL包含复杂逻辑,可以使用中间表来完成复杂的逻辑

二. 用insert into替换union all

如果union all的部分个数大于2,或者每个union部分数据量大,应该拆成多个insert into 语句,实际测试过程中,执行时间能提升50%

 

如:

insert overwite table tablename partition (dt= ....)

select ..... from ( select ... from A

union all

select ... from B union all select ... from C ) R

where ...;

可以改写为:

insert into table tablename partition (dt= ....) select .... from A WHERE ...; insert into table tablename partition (dt= ....) select .... from B WHERE ...; insert into table tablename partition (dt= ....) select .... from C WHERE ...;

 

三.  order by & sort by

 

order by : 对查询结果进行全局排序,消耗时间长。需要 set hive.mapred.mode=nostrict

sort by : 局部排序,并非全局有序,提高效率。

```
3、distribute by
用distribute by 会对指定的字段按照hashCode值对reduce的个数取模,然后将任务分配到对应的reduce中去执行,就是在mapreduce程序中的patition分区过程,默认根据指定key.hashCode()&Integer.MAX_VALUE%numReduce 确定处理该任务的reduce。
4、cluster By
distribute by 和 sort by 合用就相当于cluster by,但是cluster by 不能指定排序为asc或 desc 的规则,只能是desc倒序排列。
```

 

四. transform+python

一种嵌入在hive取数流程中的自定义函数,通过transform语句可以把在hive中不方便实现的功能在python中实现,然后写入hive表中。

语法:

select transform({column names1})

using '**.py'

as {column names2}

from {table name}

如果除python脚本外还有其它依赖资源,可以使用ADD ARVHIVE

 

五. limit 语句快速出结果

一般情况下,Limit语句还是需要执行整个查询语句,然后再返回部分结果。

有一个配置属性可以开启,避免这种情况---对数据源进行抽样

hive.limit.optimize.enable=true --- 开启对数据源进行采样的功能

hive.limit.row.max.size --- 设置最小的采样容量

hive.limit.optimize.limit.file --- 设置最大的采样样本数

缺点:有可能部分数据永远不会被处理到

 

六. 本地模式

对于小数据集,为查询触发执行任务消耗的时间>实际执行job的时间,因此可以通过本地模式,在单台机器上(或某些时候在单个进程上)处理所有的任务。

> set oldjobtracker=${hiveconf:mapred.job.tracker};
>
> set mapred.job.tracker=local;
>
> set marped.tmp.dir=/home/edward/tmp; sql 语句  set mapred.job.tracker=${oldjobtracker};

-- 可以通过设置属性hive.exec.mode.local.auto的值为true,来让hve在适当的时候自动启动这个优化,也可以将这个配置写在$HOME/.hiverc文件中。

-- 当一个job满足如下条件才能真正使用本地模式:

1.job的输入数据大小必须小于参数:hive.exec.mode.local.auto.inputbytes.max(默认128MB)

2.job的map数必须小于参数:hive.exec.mode.local.auto.tasks.max(默认4)

3.job的reduce数必须为0或者1

可用参数hive.mapred.local.mem(默认0)控制child jvm使用的最大内存数。

 

七. 并行执行

hive会将一个查询转化为一个或多个阶段,包括:MapReduce阶段、抽样阶段、合并阶段、limit阶段等。默认情况下,一次只执行一个阶段。 不过,如果某些阶段不是互相依赖,是可以并行执行的。

set hive.exec.parallel=true,可以开启并发执行。

set hive.exec.parallel.thread.number=16; //同一个sql允许最大并行度,默认为8。

会比较耗系统资源。

 

八. 调整mapper和reducer的个数

1 Map阶段优化

map个数的主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(默认128M,不可自定义)。

举例:

a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数

b) 假设input目录下有3个文件a,b,c,大小分别为10m,20m,130m,那么hadoop会分隔成4个块(10m,20m,128m,2m),从而产生4个map数

即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。

map执行时间:map任务启动和初始化的时间+逻辑处理的时间。

1)减少map数

若有大量小文件(小于128M),会产生多个map,处理方法是:

> set mapred.max.split.size=100000000; set mapred.min.split.size.per.node=100000000; set mapred.min.split.size.per.rack=100000000;  

-- 前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的)进行合并

> set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; -- 执行前进行小文件合并 2)增加map数

当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。

> set mapred.reduce.tasks=?

2 Reduce阶段优化

调整方式:

-- set mapred.reduce.tasks=?

-- set hive.exec.reducers.bytes.per.reducer = ?

一般根据输入文件的总大小,用它的estimation函数来自动计算reduce的个数:reduce个数 = InputFileSize / bytes per reducer

九.严格模式

set hive.marped.mode=strict ------ 防止用户执行那些可能意想不到的不好的影响的查询

-- 分区表,必须选定分区范围

-- 对于使用order by的查询,要求必须使用limit语句。因为order by为了执行排序过程会将所有的结果数据分发到同一个reducer中进行处理。

-- 限制笛卡尔积查询:两张表join时必须有on语句

 

十.数据倾斜

表现:任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。

单一reduce的记录数与平均记录数差异过大,通常可能达到3倍甚至更多。 最长时长远大于平均时长。

原因

1)、key分布不均匀

2)、业务数据本身的特性

3)、建表时考虑不周

4)、某些SQL语句本身就有数据倾斜

| 关键词         | 情形                                        | 后果                                         |
| -------------- | ------------------------------------------- | -------------------------------------------- |
| join           | 其中一个表较小,但是key集中                 | 分发到某一个或几个Reduce上的数据远高于平均值 |
| join           | 大表与大表,但是分桶的判断字段0值或空值过多 | 这些空值都由一个reduce处理,灰常慢           |
| group by       | group by 维度过小,某值的数量过多           | 处理某值的reduce灰常耗时                     |
| count distinct | 某特殊值过多                                | 处理此特殊值reduce耗时                       |

解决方案:

参数调节

> hive.map.aggr=true

 

1.集群服务器大小

 

管理服务器:

 集团IT集采模型4-2,4路8核CPU,128G内存,2*3TBSATA硬盘,4千兆网口,集成RAID卡,DVD光驱,冗余电 源,1个管理口,含操作系统(4台)

存储服务器:

 集团IT集采模型2-4,2路8核CPU,64G内存,12*3TBSATA硬盘,4千兆网口,集成RAID卡,DVD光驱,冗余电源,1个管理口,含操作系统(6台)

1、Spark产生临时文件问题

spark产生两类临时文件,一种是work目录下的临时文件,另一种是/tmp下的_lock,_cache文件,前一种临时文件是spark运行程序时需要加载到内存中的数据。这两种文件不及时处理,能分分钟撑爆你的磁盘,对于work目录下的文件,只需在conf文件中加入export SPARK_WORKER_OPTS=”-Dspark.worker.cleanup.enabled=true”就能很好的处理,后一种就必须要人工定时去删了,可以写定时shell脚本,不过听说spark更高版本会对这个bug进行修复。

2.公司环境用的CDH还是开源(Hadoop)?

1.联网安装、升级,非常方便。当然你也可以下载rpm包到本地,使用Local Yum方式安装。
2.自动下载依赖软件包,比如要安装Hive,则会级联下载、安装Hadoop。
3.Hadoop生态系统包自动匹配,不需要你寻找与当前Hadoop匹配的Hbase,Flume,Hive等软件,Yum/Apt会根据
当前安装Hadoop版本自动寻找匹配版本的软件包,并保证兼容性。
4.自动创建相关目录并软链到合适的地方(如conf和logs等目录);自动创建hdfs,mapred用户,hdfs用户是HD
FS的最高权限用户,mapred用户则负责mapreduce执行过程中相关目录的权限

3.Flume相关问题

3.1使用Flume拉取文件到HDFS中会遇到将文件分散成多个1KB-5KB的小文件的问题:

需要注意的是如果遇到储到HDFSFlume会将拉取过来的文件分成很多份1KB-5KB的小文件存上,那么很可能是HDFS Sink的配置不正确,导致系统使用了默认配置。spooldir类型的source是将指定目录中的文件的每一行封装成一个event放入到channel中,默认每一行最大读取1024个字符。在HDFSSink端主要是通过rollInterval(默认30秒),rollSize(默认1KB),rollCount(默认10个event)3个属性来决定写进HDFS的分片文件的大小。rollInterval表示经过多少秒后就将当前.tmp文件(写入的是从channel中过来的events)下沉到HDFS文件系统中,rollSize表示一旦.tmp文件达到一定的size后,就下沉到HDFS文件系统中,rollCount表示.tmp文件一旦写入了指定数量的events就下沉到HDFS文件系统中。

3.2使用Flume拉取到HDFS中的文件格式错乱

这是因为HDFS Sink的配置中,hdfs.writeFormat属性默认为“Writable”会将原先的文件的内容序列化成HDFS的格式,应该手动设置成hdfs.writeFormat=“text”; 并且hdfs.fileType默认是“SequenceFile”类型的,是将所有event拼成一行,应该该手动设置成hdfs.fileType=“DataStream”,这样就可以是一行一个event,与原文件格式保持一致

ETL

ETL是数据抽取(Extract)、清洗(Cleaning)、转换(Transform)、装载(Load)的过程。是构建数据仓库的重要一环,用户从数据源抽取出所需的数据,经过数据清洗,最终按照预先定义好的数据仓库模型,将数据加载到数据仓库中去。

3.3Flume宕机断点续传

在source.type=spooldir时:Flume服务down掉的时候Spooling Directory Source能记录上一次读取到的位置,(而Exec Source则没有,需要用户自己去处理,当重启Flume服务器的时候如果处理不好就会有重复数据的问题。当然Spooling Directory Source也是有缺点的,会对读取过的文件重命名,所以多架一层FTP服务器也是为了避免Flume“污染”生产环境。)

在source.type=exec时:进行如下配置https://blog.csdn.net/smallboy2011/article/details/73163664

a1.sources.r1.type = exec a1.sources.r1.shell = /bin/bash -c a1.sources.r1.command = tail -n +$(tail -n1 /home/hadoop/flume_read/m) -F /home/hadoop/student.txt | awk 'ARGIND==1{i=$0;next}{i++;if($0~/^tail/){i=0};print  $0;print i >> "/home/hadoop/flume_read/m";fflush("")}' /home/hadoop/flume_read/m -

在source.type=taildir时:可以监控一个目录下的多个文件

HBase 宕机如何处理

答: 宕机分为 HMaster 宕机和 HRegisoner 宕机, 如果是 HRegisoner 宕机, HMaster 会将其所管理的 region 重新分布到其他活动的 RegionServer 上, 由于数据和日志都持久在 HDFS中, 该操作不会导致数据丢失。 所以数据的一致性和安全性是有保障的。 如果是 HMaster 宕机, HMaster 没有单点问题, HBase 中可以启动多个 HMaster, 通过Zookeeper 的 Master Election 机制保证总有一个 Master 运行。 即 ZooKeeper 会保证总会有一个 HMaster 在对外提供服务。

简述 Hbase filter 的实现原理是什么? 结合实际项目经验, 写出几个

使用 filter 的场景 HBase 为筛选数据提供了一组过滤器, 通过这个过滤器可以在 HBase 中的数 据的多个维度(行, 列, 数据版本) 上进行对数据的筛选操作, 也就是说过 滤器最终能够筛选的数据能够细化到具体的一个存储单元格上(由行键, 列 名, 时间戳定位)。 RowFilter、 PrefixFilter。。。 hbase 的 filter 是通过 scan 设置的, 所以是基于 scan 的查询结果进行过滤.

过滤器的类型很多, 但是可以分为两大类——比较过滤器, 专用过滤器

过滤器的作用是在服务端判断数据是否满足条件, 然后只将满足条件的数据返回给客户端; 如在进行订单开发的时候, 我们使用 rowkeyfilter 过滤出某个用户的所有订单

2、Hbase系统架构

有此可以看出,Hbase需要依赖于ZooKeeper和HDFS。

  • Zookeeper
    • 保证任何时候,集群中只有一个running master,避免单点问题;
    • 存贮所有Region的寻址入口,包括-ROOT-表地址、HMaster地址;
    • 实时监控Region Server的状态,将Region server的上线和下线信息,实时通知给Master;
    • 存储Hbase的schema,包括有哪些table,每个table有哪些column family。
  • Master
    • 可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行。
  • RegionServer
    • HBase中最核心的模块,主要负责响应用户I/O请求,向HDFS文件系统中读写数据。
      • 维护Master分配给它的region,处理对这些region的IO请求;
      • 负责切分在运行过程中变得过大的region。
  • HDFS
    • 负责存储数据。

4.HIve相关问题

4.1大致流程-pageView数据分析

 使用HIVE在数据仓库中创建PageViews的贴源数据表:  >> create table pageviews(session string,ip string,requestdate string,requesttime string,visitpage string, staytime string,step string) comment ‘this is the table for pageviews’ partitioned by(inputDate string) clustered by(session) sorted by(requestdate,requesttime) into 4 buckets row format delimited fields terminated by ‘ ’;

 将HDFS中的数据导入到HIVE的PageViews贴源数据表中  >> load data inpath ‘/clickstream/pageviews’ overwrite into table pageviews partition(inputDate=‘2016-05-17’); 如果没有标示是在’Local‘本地文件系统中,则会去HDFS中加载数据

 根据具体的业务分析逻辑创建ODS层的PageViews事实表,并从PageViews的贴源表中导入数据

 这里根据请求的页面URL来分组(clustered)是为了方便统计每个页面的PV  >> create table ods_pageviews(session string,ip string,viewtime string,visitpage string, staytime string,step string) partitioned by(inputDate string) clustered by(visitpage) sorted by(viewtime) into 4 buckets row format delimited fields terminated by ‘ ’;

 >> insert into table ods_pageviews partition(inputDate='2016-05-17') select pv.session,pv.ip,concat(pv.requestdate,"-",pv.requesttime),pv.visitpage,pv.staytime,pv.step from pageviews as pv where pv.inputDate='2016-05-17';

 创建PageViews事实表的时间维度表并从当天的事实表里导入数据

 >>create table ods_dim_pageviews_time(time string,year string,month string,day string,hour string,minutes string,seconds string) partitioned by(inputDate String) clustered by(year,month,day) sorted by(time) into 4 buckets row format delimited fields terminated by ' ';

 >> insert overwrite table ods_dim_pageviews_time partition(inputDate='2016-05-17') select distinct pv.viewtime, substring(pv.viewtime,0,4),substring(pv.viewtime,6,2),substring(pv.viewtime,9,2),substring(pv.viewtime,12,2),substring(pv.viewtime,15,2),substring(pv.viewtime,18,2) from ods_pageviews as pv;

 创建PageViews事实表的URL维度表并从当天的事实表里导入数据  >> create table ods_dim_pageviews_url(visitpage string,host string,path string,query string) partitioned by(inputDate string) clustered by(visitpage) sorted by(visitpage) into 4 buckets row format delimited fields terminated by ' ';

 >> insert into table ods_dim_pageviews_url partition(inputDate='2016-05-17') select distinct pv.visitpage,b.host,b.path,b.query from pageviews pv lateral view parse_url_tuple(concat('https://localhost',pv.visitpage),'HOST','PATH','QUERY') b as host,path,query;

 查询每天PV总数前20的页面  >> select op.visitpage as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' group by op.visitpage sort by num desc limit 20;

 

4.2大致流程-visit数据分析

 使用HIVE在数据仓库中创建Visits信息的贴源数据表:  >> create table visitsinfo(session string,startdate string,starttime string,enddate string,endtime string,entrypage string,leavepage string,viewpagenum string,ip string,referal string) partitioned by(inputDate string) clustered by(session) sorted by(startdate,starttime) into 4 buckets row format delimited fields terminated by ' ';

 将HDFS中的数据导入到HIVE的Visits信息贴源数据表中  >> load data inpath '/clickstream/visitsinfo' overwrite into table visitsinfo partition(inputDate='2016-05-18');

 

 根据具体的业务分析逻辑创建ODS层的Visits事实表,并从visitsinfo的贴源表中导入数据  >> create table ods_visits(session string,entrytime string,leavetime string,entrypage string,leavepage string,viewpagenum string,ip string,referal string) partitioned by(inputDate string) clustered by(session) sorted by(entrytime) into 4 buckets row format delimited fields terminated by ' ';

 >> insert into table ods_visits partition(inputDate='2016-05-18') select vi.session,concat(vi.startdate,"-",vi.starttime),concat(vi.enddate,"-",vi.endtime),vi.entrypage,vi.leavepage,vi.viewpagenum,vi.ip,vi.referal from visitsinfo as vi where vi.inputDate='2016-05-18';

 创建Visits事实表的时间维度表并从当天的事实表里导入数据

 >>create table ods_dim_visits_time(time string,year string,month string,day string,hour string,minutes string,seconds string) partitioned by(inputDate String) clustered by(year,month,day) sorted by(time) into 4 buckets row format delimited fields terminated by ' ';

 将“访问时间”和“离开时间”两列的值合并后再放入时间维度表中,减少数据的冗余

 insert overwrite table ods_dim_visits_time partition(inputDate='2016-05-18') select distinct ov.timeparam, substring(ov.timeparam,0,4),substring(ov.timeparam,6,2),substring(ov.timeparam,9,2),substring(ov.timeparam,12,2),substring(ov.timeparam,15,2),substring(ov.timeparam,18,2) from (select ov1.entrytime as timeparam from ods_visits as ov1 union select ov2.leavetime as timeparam from ods_visits as ov2) as ov;

 创建visits事实表的URL维度表并从当天的事实表里导入数据  >> create table ods_dim_visits_url(pageurl string,host string,path string,query string) partitioned by(inputDate string) clustered by(pageurl) sorted by(pageurl) into 4 buckets row format delimited fields terminated by ' ';

 将每个session的进入页面和离开页面的URL合并后存入到URL维度表中  >>insert into table ods_dim_visits_url partition(inputDate='2016-05-18') select distinct ov.pageurl,b.host,b.path,b.query from (select ov1.entrypage as pageurl from ods_visits as ov1 union select ov2.leavepage as pageurl from ods_visits as ov2 ) as ov lateral view parse_url_tuple(concat('https://localhost',ov.pageurl),'HOST','PATH','QUERY') b as host,path,query;

 将每个session从哪个外站进入当前网站的信息存入到URL维度表中  >>insert into table ods_dim_visits_url partition(inputDate='2016-05-18') select distinct ov.referal,b.host,b.path,b.query from ods_visits as ov lateral view parse_url_tuple(ov.referal,'HOST','PATH','QUERY') b as host,path,query;

 统计每个页面的跳出人数(事实上真正有价值的统计应该是统计页面的跳出率,但为了简单示范,作者在这里简化成统计跳出人数)  >> select ov.leavepage as jumpPage, count(*) as jumpNum from ods_visits as ov group by ov.leavepage order by jumpNum desc;

 

 业务页面转换率分析(漏斗模型)  Hive在创建表的时候无法实现某个字段自增长的关键字,得使用自定义函数(user-defined function)UDF来实现相应的功能。在查询的时候可以使用row_number()来显示行数,不过必须要在complete mode下才能使用,所以可以使用row_number() 函数配合开窗函数over(),具体示例如下。 为简单起见,这里我们创建一个临时表,并手动在里面插入要查看的业务页面链接以及该页面的PV总数,通过这几个参数来计算业务页面之间的转换率,也就是所谓的漏斗模型。 假设我们有“/index” -> “/detail” -> “/createOrder” ->”/confirmOrder” 这一业务页面转化流程

 首先我们要创建业务页面的PV的临时信息表,临时表和里面的数据会在session结束的时候清理掉

 create temporary table transactionpageviews(url string,views int) row format delimited fields terminated by ' ';

 先统计业务页面的总PV然后按转换步骤顺序插入每个页面的PV信息到transactionpageviews表中  >> insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/index' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/detail' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/createOrder' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/confirmOrder' group by opurl.path;

 计算业务页面之间的转换率

select row_number() over() as rownum,a.url as url, a.views as pageViews,b.views as lastPageViews,a.views/b.views as transferRation from (select row_number() over() as rownum,views,url from transactionpageviews) as a left join (select row_number() over() as rownum,views,url from transactionpageviews) as b on (a.rownum = b.rownum-1 );

 

参考资料:

1.Flume+Hadoop+Hive的离线分析系统基本架构(https://blog.csdn.net/ymh198816/article/details/51540715

2.source.type=exec时候的断电续传(https://blog.csdn.net/smallboy2011/article/details/73163664

3.

5月17号面试训练小结

1.spark streaming一个批次的处理时间和一个批次的数据量?

答:一个批次设置的处理时间是10s,每个批次数据量有配置处理上限,我们设置的是每个批次最多处理4000条数据,超过这个数据量的数据都在在kafka队列中等着。

2.spark高级API和低级API的区别?

答:

1.高级API:

KafkaUtils.createDstream(ssc, [zk], [group id], [per-topic,partitions] ) 使用了receivers接收器来接收数据,利用的是Kafka高层次的消费者api,对于所有的receivers接收到的数据将会保存在Spark executors中,然后通过Spark Streaming启动job来处理这些数据,默认会丢失,可启用WAL日志,它同步将接受到数据保存到分布式文件系统上比如HDFS。 所以数据在出错的情况下可以恢复出来 。

A、创建一个receiver接收器来对kafka进行定时拉取数据,这里产生的dstream中rdd分区和kafka的topic分区不是一个概念,故如果增加特定主体分区数仅仅是增加一个receiver中消费topic的线程数,并没有增加spark的并行处理的数据量。 B、对于不同的group和topic可以使用多个receivers创建不同的DStream  C、如果启用了WAL(spark.streaming.receiver.writeAheadLog.enable=true)

同时需要设置存储级别(默认StorageLevel.MEMORY_AND_DISK_SER_2),

通过这种方式实现,刚开始的时候系统正常运行,没有发现问题,但是如果系统异常重新启动sparkstreaming程序后,发现程序会重复处理已经处理过的数据,这种基于receiver的方式,是使用Kafka的高级API,topic的offset偏移量在ZooKeeper中。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据只被处理一次,可能会处理两次。因为Spark和ZooKeeper之间可能是不同步的。官方现在也已经不推荐这种整合方式,我们使用官网推荐的第二种方式kafkaUtils的createDirectStream()方式。

2.低级API:

这种方式不同于Receiver接收数据,它定期地从kafka的topic下对应的partition中查询最新的偏移量,再根据偏移量范围在每个batch里面处理数据,Spark通过调用kafka简单的消费者Api(低级api)读取一定范围的数据。

相比基于Receiver方式有几个优点:  A、简化并行

不需要创建多个kafka输入流,然后union它们,sparkStreaming将会创建和kafka分区数相同的rdd的分区数,而且会从kafka中并行读取数据,spark中RDD的分区数和kafka中的topic分区数是一一对应的关系。

B、高效,

第一种实现数据的零丢失是将数据预先保存在WAL中,会复制一遍数据,会导致数据被拷贝两次,第一次是接受kafka中topic的数据,另一次是写到WAL中。而没有receiver的这种方式消除了这个问题。  C、恰好一次语义(Exactly-once-semantics)

Receiver读取kafka数据是通过kafka高层次api把偏移量写入zookeeper中,虽然这种方法可以通过数据保存在WAL中保证数据不丢失,但是可能会因为sparkStreaming和ZK中保存的偏移量不一致而导致数据被消费了多次。EOS通过实现kafka低层次api,偏移量仅仅被ssc保存在checkpoint中,消除了zk和ssc偏移量不一致的问题。缺点是无法使用基于zookeeper的kafka监控工具。

3.AVRO做MR序列化框架?

4.Azkaban相关

本可以通过$%定义参数,动态修改传参,具备一定的代码容错性

5.Redis数据结构:、

redis可以存储键和五种不同类型的值之间的映射

String字符串、List列表、Set集合、Hash散列、Zset有序集合

6.Redis持久化机制:

rdb:

默认情况下,是快照rdb的持久化方式,将内存中的数据以快照的方式写入二进制文件中,默认的文件名是dump.rdb

redis.conf配置:

save 900 1

save 300 10

save 60 10000

 默认是如上配置:

900秒之内,如果超过1个key被修改,则发起快照保存;

300秒内,如果超过10个key被修改,则发起快照保存

1分钟之内,如果1万个key被修改,则发起快照保存

这种方式不能完全保证数据持久化,因为是定时保存,所以当redis服务down掉,就会丢失一部分数据,而且数据量大,写操作多的情况下,会引起大量的磁盘IO操作,会影响性能。

AOF:

使用aof做持久化,每一个写命令都通过write函数追加到appendonly.aof中.

配置方式:启动aof持久化的方式

7.布隆过滤器:

一种空间效率很高的数据集,可以准确判断数据不在集合内,但是不能准确判断数据在集合内(结果显示错误概率低)

8.外部表与内部表的区别

Hive创建内部表时,会将数据移动到数据仓库指向的路径,外部表是仅记录数据所在的路径,不对数据的位置进行改变.删除表的时候,内部表会带数据一起删除,外部表不会.外部表相对于更安全,数据组织也更加灵活,方便共享源数据

9.两个文件合并的问题

问题:给定a、b两个文件,各存放50亿个url,每个url各占用64字节,内存限制是4G,如何找出a、b文件共同的url?

① 可以估计每个文件的大小为5G*64=300G,远大于4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。

遍历文件a,对每个url求取hash(url)%1000,然后根据所得值将url分别存储到1000个小文件(设为a0,a1,...a999)当中。这样每个小文件的大小约为300M。遍历文件b,采取和a相同的方法将url分别存储到1000个小文件(b0,b1....b999)中。这样处理后,所有可能相同的url都在对应的小文件(a0 vs b0, a1 vs b1....a999 vs b999)当中,不对应的小文件(比如a0 vs b99)不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。

比如对于a0 vs b0,我们可以遍历a0,将其中的url存储到hash_map当中。然后遍历b0,如果url在hash_map中,则说明此url在a和b中同时存在,保存到文件中即可。

如果分成的小文件不均匀,导致有些小文件太大(比如大于2G),可以考虑将这些太大的小文件再按类似的方法分成小小文件即可

10.Hadoop优化

① 优化的思路可以从配置文件和系统以及代码的设计思路来优化

② 配置文件的优化:调节适当的参数,在调参数时要进行测试

③ 代码的优化:combiner的个数尽量与reducer的个数相同,数据的类型尽可能的保持一致,可以减少拆包和封包的进度

④ 系统的优化:可以设置Linux系统打开最大的文件数预计网络的带宽MTU的配置

⑤ 为job添加一个combiner,可以大大的减少shuffer阶段的maptask拷贝过来给远程的reduce task的数据量,一般而言combiner与reduce相同

⑥ 在开发中尽量使用stringBuffer而不是string,String的模式是read-only的,如果对它进行修改,会产生临时的对象,string Buffer是可修改的,不会产生临时对象

⑦ 修改配置文件:修改mapred-site.xml文件

1) 修改最大槽位数(默认是2)

2) 调整心跳间隔

3) 启动外带心跳

4) 配置多个磁盘

5) 配置RPC hander数目

6) 配置HTTP线程数目

7) 选择合适的压缩方式

1.现在情况,数据库中有很多数据,sqoop导出数据库中的数据时,如果断了怎么办?

答:网上没有断点之类的解决方案,我自己觉的可以有一些参考方案:sqoop导出数据的话一般不会失败,考虑到数据量巨大,可以根据数据中的时间戳字段进行where选择,将数据分成多个批次来进行导入(在sqoop导入的时候可以进行一些条件选择,还可以做一个数据存储的选择:是覆盖之前的,还是插入到之前导出结果的后面等等),当有某一个阶段导出数据不成功,就进行一个重新导出(会不会有数据重复的问题。。。),数据存入到hdfs中之后就不用再担心数据丢失之类的问题了...

2.将你重点介绍的项目的流程图画出来。

答:自己准备一下你的用户画像的流程图吧,我说的是我的实时的点击流报表展示,面试官问的也挺详细的,你介绍你的实时项目,最好别和我说重了,那就很尴尬的=。=

3.公司现在有个实际情况:总共有3000多W条订单数据,如果将数据存入到HDFS之后,当我的订单本身会有一个修改(用户拒收,订单取消,申请退款之类的问题),我该怎么将这些数据修改到大数据平台中

答:

  • HDFS只适合存储海量的数据,很难做到一个数据的修改。
  • 在这么多数据中,实际上只会有最近的一段时间内下的订单才有可能有一个状态的修改
  • 可以根据不同的业务指标,制定不同的方案去求出结果,比如说有这样的字段:表明当前订单的一个状态,是已经支付还是未支付,是已经签收还是未签收,当求指标实际总金额的时候,我们只累加已经支付,且已经签收的订单的金额
  • 对于后续订单情况修改以后:从运送-->签收,我们更新这个订单时间戳属性,然后当天导入数据的时候,根据时间戳进行一个选择,将这条(之前的订单,但是修改时间是今天的,可以进行当天金额累加的)数据再一次存到hadoop中,做一个今天总金额的累加(具体指标具体分析的方式,我之前没有说的这么详细,你自己也可以再想一些)

4.问了个为什么来上海,职业经历的是怎么过渡的

5.你在实际开发过程中,对于某一个指标,你是怎么去实现的

答:我说了一个在实际开发过程中,当前时间段的一个总成交金额指标的实现

  • 前端获取订单交易金额的数据:通过一个url传参的方式,获得当前用户的订单金额;通过在埋点过程中,自定义标签的形式,设置一个字段为成交金额,在计算时直接通过message信息获取到这个字段(不是订单的话设置为0,或者数据清洗的时候,也可以将为空的字段进行填充)
  • 判断当前message信息中是否有订单模块(url)的关键字,如果有关键字(order等),就获取它的(url传参或者自定义标签)订单金额做累加
  • 将计算结果存入到redis,然后写一个驱动类定期获取redis中的数据,将其增量存入到数据库中供前端做报表展示。

6.大致问的是为什么用redis(和mysql的区别)

为什么选择Redis

 1)Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。  2)Redis支持master-slave(主-从)模式应用  3)Redis支持数据持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。  4)Redis单个value的最大限制是1GB,memcached只能保存1MB的数据。

 

1.linux文件权限相关

rwxr-xr-x:

分三个三个来看:第一个是root :r是可读,w是可写,X是可执行,rwx意思 是可读可写可执行。第二个是一般用户:r-x 是可读可执行不可写,第三个是其他用户,r-x 是可读可执行不可写。

2.二叉树(详细见有道云/面试题/二叉树)

http://note.youdao.com/noteshare?id=47c2b304fd6d498de04aeba7536d7bf0&sub=40208DDF554E44AA8A1FF1538FE7B8FD

3.数据建模

  • 范式建模
  • Inmon提出的集线器的自上而下(EDW-DM)的数据仓库架构,分为第一范式、第二范式、第三范式
    • 1.同一份数据只存放在一个地方,因此只能从一个地方获取,没有数据冗余,保证了数据一致性;
    • 2.解耦(系统级与业务级),方便维护;
    • 3.开发周期较长,开发成本较高;
  • 维度建模
  • ods-dw-da),又分为星型模型,雪花模型,星座模型
    • 1.模型结构简单,星型模型为主;
    • 2.开发周期短,能够快速迭代;
    • 3.维护成本较高;

4.反向代理

指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

5.聚合函数 、where、hiving(执行流程f-w-g-h-o),开窗函数

  • 开窗函数
  • 聚 合函数一样,开窗函数也是对行集组进行聚合计算,但是它不像普通聚合函数那样每组只返回一个值,开窗函数可以为每组返回多个值,因为开窗函数所执行聚合计算的行集组是窗口。在ISO SQL规定了这样的函数为开窗函数,在 Oracle中则被称为分析函数,而在DB2中则被称为OLAP函数。
  • 聚合函数
  • SUM, COUNT, MAX, AVG等。这些函数和其它函数的根本区别就是它们一般作用在多条记录上。
  • where、hiving、group by
  1. 子句用来筛选 FROM 子句中指定的操作所产生的行。
  2. BY 子句用来分组 WHERE 子句的输出。
  3. 子句用来从分组的结果中筛选行。
  4.  

6.货物运送

你是山西的一个煤老板,你在矿区开采了有3000吨煤需要运送到市场上去卖,从你的矿区到市场有1000公里,有一列烧煤的火车,这个火车最多只能装1000吨煤,且其每一公里需要耗一吨煤.请问,最多能卖出多少煤?

答:(设置中转站,不要想着一次一次将1000吨煤从头运到尾)

7.正则表达式

(大致知道)

8.计算题

甲乙两个人答对一道题的概率分别为70%和60%,对于一道判断题,他们都选择了“正确”,问这道题正确的概率.请问这题怎么做

  •  
  • /(题目真甲乙都选真+题目假甲乙都选真)
  1. * 0.7 * 0.6)/[(0.5 * 0.7 * 0.6)+(0.5 * 0.3 * 0.4)]
  •  =62.5% FNNDP

有一个扔骰子得返现的游戏:你扔一个骰子,扔到多少就可以得到和点数相同的返现。例如你扔到3,可以得到3元返现;扔到1,可以得到1元返现。当你扔完第一次骰子,看到点数后,你需要做出如下选择:

  1.  
  2.  
  • __元。

 

  •  
  • 4,5,6,时,不进行第二次投掷,期望为(4+5+6)*(1/6)

 ;第一次为1,2,3时,可能性为1/2,并进行第二次投掷,期望为(1/2)*(1+2+3+4+5+6)*(1/6);所以最后期望为4.25

8.IP地址相关计算

  • 192.168.1.0/24 使用掩码255.255.255.240划分子网,其可用子网为14个,每个子网内可

第一步:255.255.255.255 表示成2进制是: 11111111.11111111.11111111.11111111,

这里的/24告诉你从左边取24个1, 其它的就全用0表示,

那么就是: 11111111.11111111.11111111.00000000.

再把这个换算成10进制子网遮罩就是: 255.255.255.0.

    • 子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。
    • 子网掩码的设定必须遵循一定的规则。与IP地址相同,子网掩码的长度也是32位,左边是网络位,用二进制数字“1”表示;右边是主机位,用二进制数字“0”表示。
    • 其中,“1”有24个,代表与此相对应的IP地址左边24位是网络号;“0”有8个,代表与此相对应的IP地址右边8位是主机号。
    • 这样,子网掩码就确定了一个IP地址的32位二进制数字中哪些是网络号、哪些是主机号。这对于采用TCP/IP协议的网络来说非常重要,只有通过子网掩码,才能表明一台主机所在的子网与其他子网的关系,使网络正常工作。
    • 掩码255.255.255.240,也就是255.255.255.1111 0000,前面4个1是子网位,后面4个0是主机位,
      所以可用的子网数是2^4=16,每个子网可用主机地址数是2^4-2=14。

1.HBase数据的一致性

https://blog.csdn.net/ruichaoo/article/details/78961770

这里要先提一下分布式系统的CAP原理:

Consistency(一致性), 数据一致更新,所有数据变动都是同步的

Availability(可用性), 好的响应性能

Partition tolerance(分区容错性) 可靠性

定理:任何分布式系统只可同时满足二点,没法三者兼顾。 忠告:架构师不要将精力浪费在如何设计能满足三者的完美分布式系统,而是应该进行取舍。

一开始非常迷惑于HBase的强一致性和HDFS的多副本是怎么协同的。这一块儿就需要对HBase和HDFS的读写数据流有个比较透彻的理解。先假设HDFS的副本存储策略,也就是dfs.replication的值为3(默认值就是3)这样所有存储在HDFS上的文件都有3个副本。那么,HBase的存储实例,也就是HFile也有3个副本。那么当某一个RegionServer崩溃时,并不用担心数据的丢失,因为数据是存储在HDFS上,哪怕崩溃的RegionServer所在的DataNode上有一个副本,在其他DataNode上也还有2个副本。

那么也许你要问,既然有3个副本,如何保证HBase的强一致性呢?

HFile是已经持久化在磁盘上了,而HFile是不能改变的(这个时候暂时把删除数据这个操作放到一边,相关内容请看下面的Note),一旦在某一个DataNode上生成一个HFile后就会异步更新到其他两个DataNode上,这3个HFile是一模一样的。

那也许你又要问,那我的数据是不断更新当中啊!

更新的数据是放在Memstore,只有当Memstore里的数据达到阈值,或者时间达到阈值,就会flush到磁盘上,生成HFile,而一旦生成HFile就是不可改变的(compaction,split就是后话啦)。

这里再提一下WAL的一致性

WAL是Write-Ahead logging,这个是Memstore里的数据在RegionServer崩溃时得以恢复的保证。WAL的实现是HLog,HLog也是存储在HDFS上的,所以HRegionServer崩溃了也不会导致HLog的丢失,它也有备份。

2.mysql存储过程

  • 什么是存储过程
  • SQL语句集,功能强大,可以实现一些比较复杂的逻辑功能,类似于JAVA语言中的方法;
  1.  
  • 有哪些特性
  • if/else, case,while等控制语句,通过编写存储过程,可以实现复杂的逻辑功能;
  •  
  •  

3.mysql5.6和mysql5.7的区别

安全性

  • 用户表 mysql.user 的 plugin字段不允许为空, 默认值是 mysql_native_password,而不是 mysql_old_password,不再支持旧密码格式;
  • 增加密码过期机制,过期后需要修改密码,否则可能会被禁用,或者进入沙箱模式;
  • 增加密码过期机制,过期后需要修改密码,否则可能会被禁用,或者进入沙箱模式;
  • 提供了更为简单SSL安全访问配置,并且默认连接就采用SSL的加密方式。

灵活性

  • MySQL数据库从5.7.8版本开始,也提供了对JSON的支持。
  • 可以混合存储结构化数据和非结构化数据,同时拥有关系型数据库和非关系型数据库的优点
  • 能够提供完整的事务支持
  • generated column是MySQL 5.7引入的新特性,所谓generated column,就是数据库中这一列由其他列计算而得

易用性

  • 在MySQL 5.7 之前,如果用户输入了错误的SQL语句,按下 ctrl+c ,虽然能够”结束”SQL语句的运行,但是,也会退出当前会话,MySQL 5.7对这一违反直觉的地方进行了改进,不再退出会话。
  • MySQL 5.7可以explain一个正在运行的SQL,这对于DBA分析运行时间较长的语句将会非常有用。
  • sys schema是MySQL 5.7.7中引入的一个系统库,包含了一系列视图、函数和存储过程, 该项目专注于MySQL的易用性。
  • SQL语句。

可用性

  • 在线设置 复制的过滤规则 不再需要重启MySQL,只需要停止SQLthread,修改完成以后,启动SQLthread。
  • 在线修改buffer pool的大小。
  • Online DDL MySQL 5.7支持重命名索引和修改varchar的大小,这两项操作在之前的版本中,都需要重建索引或表。
  • 在线开启GTID ,在之前的版本中,由于不支持在线开启GTID,用户如果希望将低版本的数据库升级到支持GTID的数据库版本,需要先关闭数据库,再以GTID模式启动,所以导致升级起来特别麻烦。

性能

  • 临时表的性能改进。

 临时表只在当前会话中可见

 临时表的生命周期是当前连接(MySQL宕机或重启,则当前连接结束)

  • 只读事务性能改进。
  1. 5.7通过 避免为只读事务分配事务ID ,不为只读事务分配回滚段,减少锁竞争等多种方式,优化了只读事务的开销,提高了数据库的整体性能。
  • 加速连接处理。
  • MySQL 5.7之前,变量的初始化操作(THD、VIO)都是在连接接收线程里面完成的,现在将这些工作下发给工作线程,以减少连接接收线程的工作量,提高连接的处理速度。这个优化对那些频繁建立短连接的应用,将会非常有用。
  • 复制性能的改进 (支持多线程复制(Multi-Threaded Slaves, 简称MTS)
  1. 5.7的并行复制的功能,我们需要将slave-parallel-type配置成LOGICAL_CLOCK。
  • 支持多源复制(Multi-source replication)

严格性改变

  • 默认启用 STRICT_TRANS_TABLES 模式。
  • 对 ONLY_FULL_GROUP_BY 模式实现了更复杂的特性支持,并且也被默认启用。
  • 其他被默认启用的sql mode还有 NO_ENGINE_SUBSTITUTION。

默认参数的改变

  • 默认binlog格式调整为ROW格式
  • 默认binlog错误后的操作调整为ABORT_SERVER
  • binlog_error_action=IGNORE_ERROR),如果一个错误发生,导致无法写入binlog,mysql-server会在错误日志中记录错误并强制关闭binlog功能。这会使mysql-server在不记录binlog的模式下继续运行,导致从库无法继续获取到主库的binlog。
  • 默认开启mysql崩溃时的binlog安全。
  • 默认调低slave_net_timeout。

安装不同

  • mysql_install_db已经不再推荐使用了,建议改成mysqld --initialize 完成实例初始化。如果 datadir 指向的目标目录下已经有数据文件,则会有[ERROR] Aborting;
  • 在初始化时如果加上 --initial-insecure,则会创建空密码的 root@localhost 账号,否则会创建带密码的 root@localhost 账号,密码直接写在 log-error 日志文件中;
  • 新用户登入后需要立刻修改密码,否则无法继续后续的工作。

4.数据之间的相关性,elk

  • ELK简介:
  1. , Logstash, Kibana , 它们都是开源软件。新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具。
  2.  
  3. 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。
  4. 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
  5.  

Packetbeat(搜集网络流量数据)

Topbeat(搜集系统、进程和文件系统级别的 CPU 和内存使用情况等数据)

Filebeat(搜集文件数据)

Winlogbeat(搜集 Windows 事件日志数据)

5.jvm内存占用

 

 

 

1.Jvm垃圾回收机制

垃圾回收器负责:

  • 分配内存
  • 保证所有正在被引用的对象还存在于内存中
  • 回收执行代码已经不再引用的对象所占的内存

Hotspot VM提供的垃圾回收器是一个分代垃圾回收器(Generational GC)[9,16,18]-将内存划分为不同的阶段,也就是说,不同的生命周期的对象放置在不同的地址池中。这样的设计是基于弱年代假设(Weak Generational Hypothesis):

1.越早分配的对象越容易失效;

2.老对象很少会引用新对象。

 

这种分代方式可以减少垃圾回收的停顿时间以及大范围对象的回收成本。Hotspot VM将其堆空间分为三个分代空间:

年轻代Young Generation

○ Java应用在分配Java对象时,这些对象会被分配到年轻代堆空间中去

○ 这个空间大多是小对象并且会被频繁回收

○ 由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率

年老代Old Generationn

○ 年轻代堆空间的长期存活对象会转移到(也许是永久性转移)年老代堆空间

○ 这个堆空间通常比年轻代的堆空间大,并且其空间增长速度较缓

○ 由于大部分JVM堆空间都分配给了年老代,因此其垃圾回收算法需要更节省空间,此算法需要能够处理低垃圾密度的堆空间

持久代Permanent Generation

○ 存放VM和Java类的元数据(metadata),以及interned字符串和类的静态变量

次收集(Minor GC)和全收集(Full GC)

当这三个分代的堆空间比较紧张或者没有足够的空间来为新到的请求分配的时候,垃圾回收机制就会起作用。有两种类型的垃圾回收方式:次收集和全收集。当年轻代堆空间满了的时候,会触发次收集将还存活的对象移到年老代堆空间。当年老代堆空间满了的时候,会触发一个覆盖全范围的对象堆的全收集。

 

次收集

  • 当年轻代堆空间紧张时会被触发
  • 相对于全收集而言,收集间隔较短

全收集

  • 当老年代或者持久代堆空间满了,会触发全收集操作
  • 可以使用System.gc()方法来显式的启动全收集
  • 全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。不过,如果全收集时间超过3到5秒钟,那就太长了[1]

2.Scala的option操作

在Scala中Option类型用样例类来表示可能存在或者可能不存在的值(Option的子类有Some和None)。Some包装了某个值,None表示没有值

package cn.itcast.cases


object OptionDemo {

  def main(args: Array[String]) {

    val map = Map("a" -> 1, "b" -> 2)

    val v = map.get("b") match {

      case Some(i) => i

      case None => 0

    }

    println(v)

    //更好的方式

    val v1 = map.getOrElse("c", 0)

    println(v1)

  }

}

3.Scala隐式转换和隐式参数

 允许你手动指定,将某种类型的对象转换成其他类型的对象或者是给一个类增加方法。

  • Scala的隐式转换
  • 方法,即implicit conversion function。定义的隐式转换方法,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换方法的签名,在程序中使用到隐式转换方法接收的参数类型定义的对象时,会自动将其传入隐式转换方法,转换为另外一种类型的对象并返回。这就是“隐式转换”。其中所有的隐式值和隐式方法必须放到object中。
  • Scala的隐式参数
  • implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的参数,即隐式值,并注入参数(同一类型的隐式值只允许出现一次,否则会报错)。
  1.  
  • 当前作用域内可见的val或var定义的隐式变量;
  • 一种是隐式参数类型的伴生对象内的隐式值;

4.Spark性能调优

  • 使用kyro优化序列化性能(可以减少序列化的内存和cpu的消耗,减少垃圾回收开销 )
  • 数据量大逻辑简单的可以将数据进行缓存,在presist中有丰富的缓存级别,MEMORY_AND_DISK_SER_2(存入内存,内存快满了存入磁盘,且对数据进行序列化,额外复制一份)
  • cup盈余可以增多core,但有限度,此时可以增execution,一般一个execution设置5个core,超过5个增多execution
  • 避免创建重复rdd
  • 尽可能复用同一rdd
  • 避免用shuffle类算子
  • 对多次使用的rdd持久化
  • 广播大变量(算子函数中是要到外部变量) 100M以上大集合
  • 数据处理并行度调优,举例:对于分布式的reduce操作,可以在reduceByKey等操作中,传入第二个参数,手动指定该操作的并行度,也可以调节全局的spark.default.parallelism参数。
  • 垃圾回收:DStream的持久化,清理旧数据,CMS垃圾回收器

 

1.说说hadoop的读写过程

(我简单说了一些,分区和备份之类的,主要还说了一些mr计算过程:shuffle等)

2.Kafka的offset值相关问题

  • Broker:消息中间件处理结点,一个Kafka节点就是一个broker,多个broker可以组成一个Kafka集群。
  • Topic:一类消息,例如page view日志、click日志等都可以以topic的形式存在,Kafka集群能够同时负责多个topic的分发。
  • Partition:topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。
  • Segment:partition物理上由多个segment组成。
  • offset:每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息。

(问的比较全,我说了offset存在哪个目录下的那个文件里,说了怎么通过offset值去查找segment段中消息的具体位置)

接下来弄清楚segment具体细节之后再说offset:

  • segment由index和data文件组成,两个文件成对出现,分别存储索引和数据。
  • segment文件命名规则:对于所有的partition来说,segment名称从0开始,之后的每一个segment名称为上一个segment文件最后一条消息的offset值。

那么对于分区中的一个offset例如等于345552怎么去查找相应的message呢?

  • 先找到该message所在的segment文件,通过二分查找的方式寻找小于等于345552的offset,假如叫S的segment符合要求,如果S等于345552则S上一个segment的最后一个message即为所求;如果S小于345552则依次遍历当前segment即可找到。

3.Kafka的消息存储机制

(每个segment按照文件索引位置来命名,分为.log和.index文件,每个segement大小大约是1G,为什么有这么多segment段—负责消息的临时存储,定期要进行删除...)

 kafka的消息是存储在磁盘的,所以数据不易丢失, 如上了解,partition是存放消息的基本单位,那么它是如何存储在文件当中的呢,如上:topic-partition-id,每个partition都会保存成一个文件,这个文件又包含两部分。 .index索引文件、.log消息内容文件。

index文件结构很简单,每一行都是一个key,value对 key 是消息的序号offset,value 是消息的物理位置偏移量. index索引文件 (offset消息编号-消息在对应文件中的偏移量)

 

比如:要查找绝对offset为7的Message:

首先是用二分查找确定它是在哪个LogSegment中,自然是在第一个Segment中。

打开这个Segment的index文件,也是用二分查找找到offset小于或者等于指定offset的索引条目中最大的那个offset。自然offset为6的那个索引是我们要找的,通过索引文件我们知道offset为6的Message在数据文件中的位置为9807。

打开数据文件,从位置为9807的那个地方开始顺序扫描直到找到offset为7的那条Message。

  • offset是有序的。索引文件被映射到内存中,所以查找的速度还是很快的。

 这是一种稀疏索引文件机制,并没有把每个消息编号和文件偏移量记录下来,而是稀疏记录一部分,这样可以方式索引文件占据过多空间。每次查找消息时,需要将整块消息读入内存,然后获取对应的消息。

 比如消息offset编号在36,37,38的消息,都会通过38找到对应的offset

4.Kafka的消费者消费方式

(重点是对于不同消费组,去消费同一个Topic,消费到的数据时相同的;对于某一个Topic中的多个partition,每个消费组中最多只能有一个消费成员能消费到一个partition中的数据)

5.Kafka中的isr队列

(我们的Kafka中的broker端消息不丢失机制,采用一个对每个分区中的数据进行一个备份的操作,当我们broker段接收到数据,会将数据发送给对应的leader,再进行一个备份,将数据发送给slaver,如果我们的slaver能长时间的处于一个alive状态且与leader保持联系,那我们就将当前副本加入到这样一个队列集合中,如果他们传输速率达不到要求,或者又出现挂掉的情况,就将这个副本从集合中删除。最终,当我们的leader挂掉之后,就会从isr队列集合中获取获取到一个副本,成为新的leader-----我自己描述的,你也可以看下资料)

 

6.Storm的大致介绍和Storm

(我主要说了我在项目中没有用过Storm,但我自身对他有过一些了解,主要是与Kafka进行整合,得到KafkaSpout,创建Bolt,以Tuple为最小数据单位进行传输之类的)

7.Storm与Spark的区别

(我主要说了几点:

  • 首先我们在用storm的时候还要去指定他的一个消费者分区数,还要将不同的过程分为Spout,bolt,在Spark(流式)中步骤就没有这么复杂,我们采用一个低级API的形式,会根据Kafka中当前Topic的分区数创建对应数量的消费者消费
  • 其次我们的一个消息不丢失机制,Storm中我们消息处理成功调用一个自定义ack方法,失败的话就调用一个fail方法,但是在Spark中我们会自动存一个offset值在集群上,保证他的一个消息不丢失和不会重复消费,更加简单高效
  • Storm和Spark主要有一个区别是Storm更加“实时”,他的延迟能达到50-200ms,而Spark通常来说在2-10s左右,对于某些时效性要求很高的场景,Storm可能更好
  • 代码复杂,我在之前学习写storm的时候,我们用的是一个Java的代码,相比与Scala的代码量,Java的却是复杂很多)

8.visit模型怎么获得

(在map阶段,以userIp为key,javabean为value,发送到reduce阶段,按照javabean中的时间先后进行排序(Collections.sort(beans, new Comparator(){@override..}),比较相邻两条记录中的时间差,如果时间差<30分钟,则该两条记录属于同一个session, 否则,就属于不同的session。最后得到一张visit。)

9.数据清洗相关问题

  • 我们自己总结的那几条清洗规则
  • 1、不完整的数据(比如如供应商的名称、分公司的名称、客户的区域信息缺失、业务系统中主表与明细表不能匹配等) 解决方法:补全 2、错误的数据(比如数值数据输成全角数字字符、字符串数据后面有一个回车操作、日期格式不正确、日期越界等) 解决方法:对错误进行分类,然后写SQL语句挑出来,再提交给业务部门修正,最后再重新抽取 3、重复的数据 解决方法:将重复数据记录的所有字段导出来,让客户确认并整理。
  • 2. 定义清洗规则
  •  * 空值的检查和处理  * 非法值的检测和处理  * 不一致数据的检测和处理  * 相似重复记录的检测和处理
  • 3. 执行数据清洗规则
    • 检查拼写错误
    • 去掉重复的(duplicate)记录
    • 补上不完全的(incomplete)记录
    • 解决不一致的(inconsistent)记录
    • 用测试查询来验证数据
    • 生成数据清晰报告
  • 4.清洗结果验证
  •  数据清洗过程中往往需要多次迭代的进行分析,设计和验证。
  • 3.属性清洗
  • 1. 属性清洗的内容
    • 错误或非法数据
    • 拼写错误
    • 空值
    • 异常数据
    • 不一致数据
    • 嵌入值。一个属性值包含多个组成成分,如,地址=”上海市东川路800号上海交通大学“。就可以分解成:地址=”上海市东川路800号“,学校=”上海交通大学“。
  • 2. 属性清洗的基本方法

空值数据的清洗

      • 空值数据的语义
        • 不存在型空值。即无法填入的值,如一个未婚者的配偶姓名。
        • 存在型空值。该类空值的实际值在当前是未知的,但它有确定性的一面,如它的实际值的确存在,总是落在一个可以确定的区间内。
        • 占位型空值。即无法确定是不存在型空值还是存在型空值。一般情况下,空值是指存在型空值。
      • 空值数据的处理方法
        • 删除包含空值的记录(空值占比重很小而不重要时可以采用)
        • 自动补全方法。通过统计学原理,根据数据集中记录的取值分布情况来对一个空值进行自动填充,可以用平均值,最大值,最小值等基于统计学的客观知识来填充字段。
        • 手工的补全缺失值。仅适用于非常重要的任务数据。
        • 对空值不正确的填充往往将新的噪声引入数据中,使知识获取产生错误的结果。当数据集的数量很大且有较多缺失值的情况下,效率很差。

不一致数据属性清洗

      • 不一致数据的语义
      • 冗余性不一致。相同的信息没有进行一致性的同步更新。
      • 故障性不一致。由于某种原因(硬件或软件故障)而造成数据丢失或数据损坏,系统进行恢复时,不能恢复到完全正确,完整,一致的状态。
      • 不一致数据处理的基本方法
      • 清洗方法主要在分析不一致产生原因的基础上,利用各种变换函数,格式化函数,汇总分解函数去实现清洗。

噪声数据的清洗

      • 噪声数据的语义  噪声数据就是除空值数据,不一致数据以外的其他不准确。不客观数据,该类噪声数据,可能会导致错误的数据分析结果。
      • 噪声数据的基本处理方法

   

   - 分箱:将存储的值分布到一些箱中,用箱中的数据值来局部平滑存储数据的值。包括按箱平均值平滑,按箱中值平滑和按箱边界值平滑。
   - 回归:找到恰当的回归函数来平滑数据。线性回归要找出适合两个变量的”最佳“直线,使得一个变量能预测另一个。多线性回归涉及多个变量,数据要适合一个多维面。
   - 计算机检查和人工检查相结合:可以通过计算机将被判定数据与已知的正常值比较,将差异程度大于某个阈值的模式输出到一个表中,人工审核后识别出噪声数据。
   - 聚类:将类似的值组成群或”聚类“,落在聚类集合之外的值被视为孤立点。孤立点可能是垃圾数据,**也可能是提供信息的重要数据**。垃圾数据将清除。

3. 属性清洗的过程

  • 分析数据集中的属性值
  • 定义属性值清洗规则
  • 执行属性清洗规则
  • 对于我们埋点采集的数据,可能会有一些数据他的不同字段的位置出现了错位(本来这个位置应该是IP,结果是用户ID之类),对于这样的数据,怎么去进行鉴别再清除?
  • 我们埋点时采集的数据,有很多是消息不全的,本来期望有十几个字段,但是只收集到几个字段,你认为这是什么原因引起的?

10.SparkStreaming的优化机制

11.SparkStreaming的窗口计算有用到么?

(我们有用到reduceByKeyAndWindow(ssc,Seconds(5),Seconds(5)),我把时间说成半个小时,然后把这些参数的作用也说了)

12.临时给我出了个题目,是分组TopN的sql怎么写(row_number)

 

13.临时出了一个题目,如何把visit模型应用于SparkStreaming上

14.SparkStreaming和kafka整合的低级API和高级API的区别

15.Spark运行过程的调度流程

(先划分为一个DAG有向无环图,然后将其交给DAGSchedular,将DAG切分为不同的Stage,将其放入一个Taskset集合中,一起传给TaskSchedular进行任务的调度,最后将具体的任务放到worker中进行执行)

hadoop的shuffle过程

一、Map端的shuffle

 Map端会处理输入数据并产生中间结果,这个中间结果会写到本地磁盘,而不是HDFS。每个Map的输出会先写到内存缓冲区中,当写入的数据达到设定的阈值时,系统将会启动一个线程将缓冲区的数据写到磁盘,这个过程叫做spill。

 在spill写入之前,会先进行二次排序,首先根据数据所属的partition进行排序,然后每个partition中的数据再按key来排序。partition的目是将记录划分到不同的Reducer上去,以期望能够达到负载均衡,以后的Reducer就会根据partition来读取自己对应的数据。接着运行combiner(如果设置了的话),combiner的本质也是一个Reducer,其目的是对将要写入到磁盘上的文件先进行一次处理,这样,写入到磁盘的数据量就会减少。最后将数据写到本地磁盘产生spill文件(spill文件保存在{mapred.local.dir}指定的目录中,Map任务结束后就会被删除)。

 最后,每个Map任务可能产生多个spill文件,在每个Map任务完成前,会通过多路归并算法将这些spill文件归并成一个文件。至此,Map的shuffle过程就结束了。

二、Reduce端的shuffle

 Reduce端的shuffle主要包括三个阶段,copy、sort(merge)和reduce。

 首先要将Map端产生的输出文件拷贝到Reduce端,但每个Reducer如何知道自己应该处理哪些数据呢?因为Map端进行partition的时候,实际上就相当于指定了每个Reducer要处理的数据(partition就对应了Reducer),所以Reducer在拷贝数据的时候只需拷贝与自己对应的partition中的数据即可。每个Reducer会处理一个或者多个partition,但需要先将自己对应的partition中的数据从每个Map的输出结果中拷贝过来。

 接下来就是sort阶段,也成为merge阶段,因为这个阶段的主要工作是执行了归并排序。从Map端拷贝到Reduce端的数据都是有序的,所以很适合归并排序。最终在Reduce端生成一个较大的文件作为Reduce的输入。

 最后就是Reduce过程了,在这个过程中产生了最终的输出结果,并将其写到HDFS上。

 

2.1 创建RDD的方式

在学习Spark核心编程的时候,我们需要做的第一件事情就是创建一个初始的RDD,这个RDD通常就代表和包含了Spark应用程序的输入源数据.然后在创建了初始的RDD之后,才可以通过Spark Core提供的transformation算子,对该RDD进行转换,来获取其他的RDD.Spark Core提供了三种创建RDD的方式,主要包括:

1) 使用程序中的集合创建RDD,该种方式主要是用来测试编写的Spark应用程序是否正确

2) 使用本地文件创建RDD,主要用于临时性地处理一些存储了大量数据的文件.

3) 使用HDFS文件创建RDD,此种方式在生产环境中是最常用的处理方式.

kafka的数据存在内存还是磁盘****

Kafka最核心的思想是使用磁盘,而不是使用内存,可能所有人都会认为,内存的速度一定比磁盘快,我也不例外。在看了Kafka的设计思想,查阅了相应资料再加上自己的测试后,发现磁盘的顺序读写速度和内存持平。 而且Linux对于磁盘的读写优化也比较多,包括read-ahead和write-behind,磁盘缓存等。如果在内存做这些操作的时候,一个是JAVA对象的内存开销很大,另一个是随着堆内存数据的增多,JAVA的GC时间会变得很长,使用磁盘操作有以下几个好处: 磁盘缓存由Linux系统维护,减少了程序员的不少工作。 磁盘顺序读写速度超过内存随机读写。 JVM的GC效率低,内存占用大。使用磁盘可以避免这一问题。 系统冷启动后,磁盘缓存依然可用。

**怎么解决kafka的数据丢失**

producer端: 宏观上看保证数据的可靠安全性,肯定是依据分区数做好数据备份,设立副本数。 broker端: topic设置多分区,分区自适应所在机器,为了让各分区均匀分布在所在的broker中,分区数要大于broker数。 分区是kafka进行并行读写的单位,是提升kafka速度的关键。 Consumer端 consumer端丢失消息的情形比较简单:如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。由于Kafka consumer默认是自动提交位移的,所以在后台提交位移前一定要保证消息被正常处理了,因此不建议采用很重的处理逻辑,如果处理耗时很长,则建议把逻辑放到另一个线程中去做。为了避免数据丢失,现给出两点建议: enable.auto.commit=false 关闭自动提交位移 在消息被完整处理之后再手动提交位移

记一次Kafka消费者拉取多个节点的数据,其中有1-2个节点出现拉取流量比较大问题,其原因是

我把consumer拉取的数据的自动提交配置设置成了false,而并没调用commit(offset)提交接口,推送导致是其原因由于没确认又重复的拉取数据了,

之后修改成自动确认的问题解决了。

1.在spark中,reduceByKey、groupByKey的区别

•reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义;

•groupByKey也是对每个key进行操作,但只生成一个sequence,groupByKey本身不能自定义函数,需要先用groupByKey生成RDD,然后才能对此RDD通过map进行自定义函数操作

8. 有一百个map,一个reduce怎么优化

在map端可以进行combine合并

设置reduce个数100个

处理前先把小文件进行合并一下可以,这样就可以减少map的数量

 

sparkStream如何实施日志监控告警

你可以用flume监控日志文件夹 吧?然后把kafka作为fume的 sink,后面就是sparkstream作为kafka 为的消费者就行了呀

9、如何调试hadoop\spark任务

本地调试模式

 本地debug模式;

服务器调试模式

 spark-submit提交jar包:将本地应用程序打成jar包放在服务器上,在该服务器上执行spark-submit--class com.zte.spark.example.WordCount --executor-memory 1G --total-executor-cores 3 /home/mr/SparkTest.jar /tmp/test.txt;

hadoop-2.X HA的基本原理

2 基本原理

hadoop2.0的HA 机制有两个namenode,一个是active namenode,状态是active;另外一个是standby namenode,状态是standby。两者的状态是可以切换的,但不能同时两个都是active状态,最多只有1个是active状态。只有active namenode提供对外的服务,standby namenode是不对外服务的。active namenode和standby namenode之间通过NFS或者JN(journalnode,QJM方式)来同步数据。

active namenode会把最近的操作记录写到本地的一个edits文件中(edits file),并传输到NFS或者JN中。standby namenode定期的检查,从NFS或者JN把最近的edit文件读过来,然后把edits文件和fsimage文件合并成一个新的fsimage,合并完成之后会通知active namenode获取这个新fsimage。active namenode获得这个新的fsimage文件之后,替换原来旧的fsimage文件。

这样,保持了active namenode和standby namenode的数据的实时同步,standby namenode可以随时切换成active namenode(譬如active namenode挂了)。而且还有一个原来hadoop1.0的secondarynamenode,checkpointnode,buckcupnode的功能:合并edits文件和fsimage文件,使fsimage文件一直保持更新。所以启动了hadoop2.0的HA机制之后,secondarynamenode,checkpointnode,buckcupnode这些都不需要了。

 

浅谈大数据处理工作中数据倾斜问题的解决方案

 

一 概述  本文整理了笔者再从事大数据处理工作中对于数据倾斜问题的各类解决方案

二 现象  在hadoop,spark等分布式计算框架中运行某个作业时,发现某个算子(可能是map,也可能是reduce)执行特别慢  在hadoop中还不算特别致命,就是慢点,最终往往还是能算出结果,但是在spark等基于内存的计算框架中则很可能是致命的  往往会导致内存溢出或频繁的jvm full gc

三 发生点  一般发生在shuffle阶段,尤其是有join的场合,但是有些情况下在map端也会发生数据倾斜

四 产生原因  A 数据key值分布不均匀,少数key下包含的数据占比过大  例如医疗就诊数据中,往往是少数几个三甲医院产生了大部分的门急诊量,而大量的小医院就诊量确很少  股市中少数几支热门股占据了大部分交易量  两张表join时某些关联key,所关联的数据,占比过大  B 数据在计算节点间分布不均匀  类似hadoop,spark等框架都遵循一个原则,“移动计算,而不移动数据”,也就是作业调度算法会尽可能的  在实际存储数据的节点上调度执行计算任务,这样的好处是避免了传输大数据所带来的磁盘io和网络开销,  但正因为如此,如果某个节点数据非常多,那么在这个节点就会执行大量任务,这也是map端发生数据倾斜的原因  一般现在大数据存储都是在hdfs上,可执行hadoop 的balancer命令,使得数据均匀分布

五 解决方案  笔者从实施角度,按先易后难的顺序罗列一下自己所掌握的解决方案,一个有趣的现象是,往往容易的方案是更有效的方案 1 过滤掉导致倾斜的数据  这是最简单的方法,分析业务,当发现导致倾斜的数据是不需要的,直接抛弃这部分数据  最经典的例子是null值,源数据有大量key值是null的数据,这些null值在进行shuffle时会分配给同一个reduce任务处理  那么当业务允许的情况下,直接删掉这些数据 2 在数据源头尽量进行聚合  例如某个医院A有以下10条门急诊数据:  A 数据1  A 数据2  A 数据3  A 数据4  A 数据5  A 数据6  A 数据7  A 数据8  A 数据9  A 数据10  在正式进行统计分析任务前,进行数据预处理任务,使用聚合操作,把以上数据聚合成如下形式  A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10 3 提高并行度,也就是增加reduce任务数量,摊薄reduce任务的压力  这种方案,只能缓解数据倾斜,不是根本解决,效果要看具体场景,之所以放在第3条,是因为实在是很简单 4 打散倾斜key,进行多次聚合  如果在数据源没有进行聚合,或者无法聚合,或者聚合后还是有倾斜key存在,则可以使用此方案  还是以上述A医院的数据为例,我们使用一个随机数,将A随机变换成A-1,....A-N形式,假设随机数取值为1至3  则以上数据会变成如下形式  A-1 数据1  A-2 数据2  A-3 数据3  A-1 数据4  A-2 数据5  A-3 数据6  A-1 数据7  A-2 数据8  A-3 数据9  A-1 数据10  针对这种形式的数据,我们进行两次聚合,第一次聚合结果如下  A-1 数据1,数据4,数据7,数据10  A-2 数据2,数据5,数据8  A-3 数据3,数据6,数据9  第二次聚合,去掉随机数再聚合,结果如下  A 数据1,数据2,数据3,数据4,数据5,数据6,数据7,数据8,数据9,数据10  本方案的局限性在于,不适用于join产生的数据倾斜 5 对于join产生的数据倾斜,优先考虑将reduce端的join,转换为map端的join  因为map端是一条条数据去join的,不存在归并的过程,也就自然没有了数据倾斜  首先讨论一下,为什么join操作,常规是用reduce算子来做  原因很简单,map reduce思想是分而治之的思想,map负责分,reduce负责合  同一套数据在map过程中会分给Nmap任务,最终按关联关系,将相同的key所对应的数据输出给同一个reduce任务  要做join则必须拿到join两端的所有数据,这只有在reduce的过程中才能拿到  从上述分析可见,map端做不了join是由于无法获得join两端的所有必须数据,那么如果我们提供一种方法  让map端能获得join两端的所有必须数据,则map端就能进行join了。好在框架已经为我们提供了这种方法。  在hadoop中可以用DistributedCache.addCacheFile  在spark中可以用broadcast广播变量  在hive中可以用/*+ MAPJOIN() */  以上代码的作用都是将join两端中的某一端(一般是数据量小的一端),广播出来,分发到所有运行map任务的节点  这样map端就可以执行join了  本方案的局限性在于,需要将join的一端,完全载入map任务节点的内存中,因此只适用于join的一端是小表的情况,  不适用于join两端都是大表的情况,不过对于大表join大表的场景,笔者曾经尝试在hadoop mapreduce程序中将大表预先  加载到redis等高并发缓存中,也取得不错的效果(这种方式其实是用增大网络流量的方式来换取节约内存空间,对网络传输速度有较高要求) 6 抽取分割倾斜数据,分别进行join后再归并  对于无法用map join方案的可尝试此方案  原理  预先分析源数据,将会发生数据倾斜的数据,单独拉出来;  用这个会倾斜的数据单独去join一下,这个时候,key对应的数据,可能就会分散到多个任务中去进行join操作。  例  数据表 A 包含倾斜key,内容为  key value  1 x1  1 x2  1 x3  1 x4  1 x5  1 x6  2 x7  3 x8  数据表 B 为要join的表,内容为  key value  1 y1  2 y2  3 y3  首先,分析数据表 A的倾斜key,拆分 A 为 A1(只含倾斜key) A2(不含倾斜key)  A1 内容为  key value  1 x1  1 x2  1 x3  1 x4  1 x5  1 x6  A2 内容为  key value  2 x7  3 x8  然后,对数据表 B也按照倾斜key 拆分出 B1(只含倾斜key)  B1 内容为  key value  1 y1  对A1,对key打上随机数前缀 得到 AP,这里的随机数取值范围为(1,2,3)  AP 内容为  key value  1-1 x1  2-1 x2  3-1 x3  1-1 x4  2-1 x5  3-1 x6  对B1,对key打上有序唯一数前缀 得到BP,这里有序唯一数取值范围必须覆盖上面的随机数范围(1,2,3)  BP 内容为  key value  1-1 y1  2-1 y1  3-1 y1  AP join BP 得到 ABP 这里可以结合提升reduce操作并行度获得更好的效果  ABP 内容为  key value  1-1 x1,y1  1-1 x4,y1  2-1 x2,y1  2-1 x5,y1  3-1 x3,y1  3-1 x6,y1  对ABP 去掉随机数前缀 得到 AB1  AB1 内容为  key value  1 x1,y1  1 x4,y1  1 x2,y1  1 x5,y1  1 x3,y1  1 x6,y1  用A2 join B 得到 AB2  AB2 内容为  key value  2 x7,y2  3 x8,y3  最终合并AB1 和AB2  key value  1 x1,y1  1 x4,y1  1 x2,y1  1 x5,y1  1 x3,y1  1 x6,y1  2 x7,y2  3 x8,y3  本方案适用于源数据中倾斜key只有少数几个的场景 7 随机扩容  对于join数据中有很多倾斜key,不能用方案6解决的,可以尝试本方案  选择一个join表,进行扩容,将每条数据,用有序唯一数作为前缀打散,映射为多条数据,通常选择10以内的有序唯一数,即最多扩容10倍。  将另外一个表,做普通的map映射操作,每条数据,都打上一个10以内的随机数前缀。  最后,将两个处理后的表,进行join操作  本方案只是对数据倾斜的缓解,而不是根本解决,可以看出本方案的本质是通过放大数据量来谋求数据的均衡,代价还是比较明显的,  因此只有前述方案都无法适用时,才应考虑使用本方案

 

面试题

1. hadoop

面试题

hadoop

【1】讲述一下MapReduce的shuffle过程?  答:mapReduce的shuffle分为map-shuffle和ruduce-shuffle。总体过程就是从mapper的输出数据开始到reduce接 收数据之前这个阶段!

首先mapper阶段默认通过FileInputFomat中的TextInputFormat获取输入分片,使用默认的RecordReader:       LineRecordReader将一个输入分片中的每一行按\n分割成key-value。key是偏移量 value是每一行的内容,每     读取一行调用一次map()方法,一个输入分片对应一个Maptask任务。hadoop2默认的分片大小是128M。

注意:分配的规则是byteRemaining/blockSize>=1.1,所以不会出现129M的文件被分成两个分片的现象

Map-Shuffle:
写入之前先进行分区Partition,用户可以自定义分区(就是继承Partitioner类,重写getPartition方法),如     果没有进行自定义分区,框架会使用默认的分区(HashPartitioner)对key去hash值之后,然后在对               numReduceTask进行取模(目的是为了平衡reduce的处理能力),然后决定由那个reduceTask来处理。
将分完区的结果开始序列化成字节数组,开始写入缓冲区。
随着map端的结果不断的输入缓冲区,缓冲区里的数据越来越多,缓冲区的默认大小是100M,当缓冲区大小达到阀值     时 默认是0.8【spill.percent:溢出比】(也就是80M),开始启动溢写线程,锁定这80M的内存执行溢写过程,     内存—>磁盘,此时map输出的结果继续由另一个线程往剩余的20M里写,两个线程相互独立,彼此互不干扰。
溢写spill线程启动后,开始对key进行排序(Sort)默认的是自然排序,也是对序列化的字节数组进行排序(先对     分区号排序,然后在对key进行排序)。
如果客户端自定义了Combiner之后(相当于map阶段的reduce),将相同的key的value相加,这样的好处就是减     少溢写到磁盘的数据量(Combiner使用一定得慎重,适用于输入key/value和输出key/value类型完全一致,而且     不影响最终的结果,例如:求中位数的就不适合使用Combiner)
每次溢写都会在磁盘上生成一个一个的小文件,因为最终的结果文件只有一个,所以需要将这些溢写文件归并到一       起,这个过程叫做Merge,最终结果就是一个group({“aaa”,[5,8,3]})
集合里面的值是从不同的溢写文件中读取来的。这时候Map-Shuffle就算是完成了。一个MapTask端生成一个结果     文件。

Reduce-Shuffle:
接下来开始进行Reduce-Shuffle 阶段。当MapTask完成任务数超过总数的5%后,开始调度执行ReduceTask任       务,然后ReduceTask默认启动5个copy线程到完成的MapTask任务节点上分别copy一份属于自己的数据(使用Http     的方式)。
这些拷贝的数据会首先保存到内存缓冲区中,当达到一定的阀值的时候,开始启动内存到磁盘的Merge,也就是溢     写过程,一致运行直到map端没有数据生成,最后启动磁盘到磁盘的Merge方式生成最终的那个文件。在溢写过程       中,然后锁定80M的数据,然后在延续Sort过程,然后记性group(分组)将相同的key放到一个集合中,然后在进     行Merge,然后就开始reduceTask就会将这个文件交给reduce()方法进行处理,执行相应的业务逻辑,最终使用     OutPutFormat将数据写出到文件中。

【2】讲述一下如何实现MapReduce二次排序?  答:mapReduce的shuffle阶段阶段,会对key进行默认字典排序,但是shuffle分组之后同一key值的value序列中的 顺序是不固定的,如果要想此时value的值也是排好序的,这个需求就是二次排序!

 有两种解决方法:
 1.直接在rdduce端对分组的values进行排序,使用treeSet或者arrayList都行,但是把排序任务都交给reduce        阶段,当values序列很多的时候,会对cpu和内存造成极大的负载,不推荐使用!
 2.将map端输出的中的key和value组合成一个新的key(称为newKey),value值不变。这里就变成        <(key,value),value>,在针对newKey排序的时候,如果key相同,就再对value进行排序。
   注意:需要自定义的地方
   自定义数据类型实现组合key
     实现方式:继承WritableComparable
   自定义partioner,形成newKey后保持分区规则任然按照key进行。保证不打乱原来的分区。
     实现方式:继承partitioner
   自定义分组,保持分组规则任然按照key进行。不打乱原来的分组
     实现方式:继承RawComparator

【3】请列出hadoop中最常用的InputFormats?  答:常见的InputFormat有FileInputFormat和KeyValueTextInputFormat

 TextInputFormat(默认):
 用于读取纯文本文件,key是每一行的位置偏移量,LongWritable类型的,value是每一行的内容,Text类型
 KeyValueTextInputFormat:
 同样用于读取文件,如果行被分隔符(缺省是tab)分割为两部分,第一部分为key,剩下的部分 为value;
                 如果没有分隔符,整行作为 key,value为空

【4】说说hadoop和hadoop生态系统的区别?  答:hadoop指hdfs,mapreduce和yarn(hadoop2.0之后有的)

  hadoop生态系统指围绕hadoop的一系列工具,有:                                                       hdfs,mapreduce,hive,hbase,pig,sqoop,flume,zookeeper,azkaban,mahout等等

【5】Cloudera的CDH和Apache的Hadoop的区别?

 1.CDH对Hadoop版本的划分非常清晰,只有两个系列的版本,分别是cdh3和cdh4,分别对应第一代                  Hadoop(Hadoop1.0)和第二代Hadoop(Hadoop 2.0),相比而言,Apache版本则混乱得多;比Apache            hadoop在兼容性,安全性,稳定性上有增强。
 2.CDH支持Kerberos安全认证,apache hadoop则使用简陋的用户名匹配认证

【6】相比于HDFS1.0 ,2.0最主要的改进在哪几个方面?

 引入一个新的资源管理系统YARN
 hadoop单点故障得以解决-HA
 hadoop 2.0的最大变化出现在内核(HDFS、MapReduce和YARN)

【7】yarn的工作流程?

 1.client向ResourceManager提交应用程序,中包括ApplicationMaster程序、启动ApplicationMaster命          令、用户程序等
 2.resourceManager为该应用程序分配第一个container,并与对应nodeManager通信,要求它在这个container中启动应用程序ApplicationMaster
 3.ApplicationMaster首先向ResourceManager注册,这样用户可以直接通过ResourceManager查看应用程序的       运行状态,然后它将为各个任务申请资源,并监控它的运行状态,直到运行结束,即重复4~7
 4.ApplicationMaster采用轮询的方式通过RPC协议向resourceManager申请和领取资源
 5.一旦ApplicationMaster申请到资源后,便与对应的Nodemanager通信,要求它启动任务
 6.NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本        中,并通过运行该脚本启动任务
 7.各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握        各个任务的运行状态,从而可以在任务失败时重新启动任务。在应用程序运行过程中,用户可随时通过RPC向          ApplicationMaster查询应用程序的当前运行状态
 8.应用程序运行完成后,ApplicationMaster向resourceManager注销并关闭自己

【8】yarn的调度器有哪几种,各自工作原理,默认是哪一种/

 有 FIFO Scheduler,Capacity Scheduler,Fair Scheduler
 FIFO:按照任务提交的顺序进行排队,先提交的先执行,等前面的执行完之后后面的才可以执行
 Capacity:多个组织共享集群,分成多个队列,每个组织获得一个队列,互不影响,但是每个单独的队列之间又是FIO的策略
 Fair:该调度器会给job动态调整系统资源,比如当一个较大的任务提交时,会分配给它整个集群的资源,当又有          小的任务提交的时候,该较大的任务会释放一部分资源来运行这个较小的任务
 默认是:FIFO Scheduler

【9】介绍hadoop的几个节点进程?

  hadoop 的集群是基于 master/slave 模式,namenode 和 jobtracker 属于 master,
  datanode 和 tasktracker 属 于 slave , master 只 有 一 个 , 而 slave 有多个
  SecondaryNameNode 内存需求和 NameNode 在一个数量级上,所以通常 secondary
  NameNode(运行在单独的物理机器上)和 NameNode 运行在不同的机器上。

【10】hdfs的默认副本数?

   3个

【11】hadoop的默认分配大小

   128M (hadoop 2..0)

【12】hadoop集群最主要的瓶颈?

   磁盘IO

【13】SecondaryNameNode的作用?

   帮助NameNode合并编辑日志,减少NameNode的启动时间

【14】Hadoop 运行的三种模式?

   单机,伪分布,全分布

【15】hdfs操作相关API

  上传文件:hadoop fs -put /  /
  读取文件:hadoop fs -cat /
  创建文件夹:hadoop fs -mkdir -p / /
  删除文件夹:hadoop -rm -r  /

【16】crontab定时:

  crontab -e 编辑
  crontab -l 列出定时任务
  crondab -r 删除所有的定数任务

【17】hive的仓库分层?

  ODS:源数据层,直接收集的外围数据,没有统一的格式,不利于分析
  DW:数据仓库层,进行格式的规整,统一格式
  DA:数据应用层,真正使用的数据

数据集市:也叫数据市场, 数据集市就是满足特定的部门或者用户的需求, 按照多维的方式
进行存储, 包括定义维度、 需要计算的指标、 维度的层次等, 生成面向决策分析需求的数据
立方体。

【18】hive的分桶开启命令:set hive.enforce.bucketing=true;

 指定分桶数:set mapreduce.job.reduces=N;

【19】hive内部表和外部表的区别?

  内部表需要加载文件到hdfs目录,而外部表不会
  内部包删除时,对应的格式化文件也会被删除,而外部表则不会

【20】UDF的开发流程?(hive中用户自定义方法)

  1.写一个java类,继承UDF,重写evaluate方法
  2.打成jar包上传到服务器
  3.将jar包添加到hive的classpath中
  4.创建临时函数与开发好的 java class 关联
  5.即可在 hql 中使用自定义的函数

【21】hive总的cluster by = distributed by+sort by(order by全局排序)

storm

1.讲一讲hbase的二级索引?

 一般HBase的查询都是通过RowKey(要把多条件组合查询的字段都拼接在RowKey中显然不太可能),所以需要构建二级 索引来进行多条件查询。  hbase的二级索引实际上就是构建各列值对应其行键的映射关系。通过某一列的值可以确定所对应的rowkey,然后荣 光rowkey就能够查找到其他的列,这样就能构建多条件的组合查询!

2.Spark Streaming和Storm有何区别?

 sparkSteamig的实时性没有storm高,storm是在100ms的间隔内,sparkStreaming是在0.5到2s  sparkSteaming的吞吐量比storm要大,因为SparkStreaming的单位是Dstream,由一系列的RDD构成

3.mllib支持的算法?

 四大类:分类、聚类、回归、协同过滤

4.akc消息不丢失机制?

 BaseRichBolt:  collector.ack(input)  collector.fail(input)  BaseBasicBolt:  自动调用ack,出现异常就抛出FailedException()

5.kafka

 producer,consumer,topic,broker

6.Hbase中的数据都是字节数组:byte[]

7.hbase的特点是什么?

 1.Hbase 一个分布式的基于列式存储的数据库,基于 Hadoop 的 hdfs 存储,zookeeper进行管理  2.Hbase适合存储半结构化或非结构化数据,对于数据结构字段不够确定或者杂乱无章很难按一个概念去抽取的据  3.Hbase 为 null 的记录不会被存储  4.基于的表包含 rowkey,时间戳,和列族。新写入数据时,时间戳更新,同时可以查询到以前的版本  5.hbase 是主从架构。HMaster 作为主节点,HRegionserver 作为从节点

8.hive和hbase的共同点和区别?

 共同点:  1.hbase 与 hive 都是架构在 hadoop 之上的。都是用 hadoop 作为底层存储  区别:  2.Hive 是建立在 Hadoop 之上为了减少 MapReducejobs 编写工作的批处理系统,

 HBase 是为了支持弥补 Hadoop 对实时操作的缺陷的项目

 3.想象你在操作 RMDB 数据库,如果是全表扫描,就用 Hive+Hadoop,如果是索引访问,就用 HBase+Hadoop  4.Hive query 就是 MapReduce jobs 可以从 5 分钟到数小时不止,HBase是非常高效的,肯定比 Hive高效多  5.Hive 本身不存储和计算数据,它完全依赖于 HDFS 和 MapReduce,Hive 中的表纯逻辑  6.hive 借用 hadoop 的 MapReduce 来完成一些 hive 中的命令的执行  7.hbase 是物理表,不是逻辑表,提供一个超大的内存 hash 表,搜索引擎通过它来存储索引,方便查询操作  8.hbase 是列存储  9.hdfs 作为底层存储,hdfs 是存放文件的系统,而 Hbase 负责组织文件  10.hive 需要用到 hdfs 存储文件,需要用到 MapReduce 计算框架

9.解释下hbase实时查询的原理?

 答 : 实时查询 ,可以认为是从内存中查询 ,一般响应时间在1秒内 。HBase的机制是数据先写入到内存中 , 当 数据量达到一定的量 (如128M ) , 再写入磁盘中,在内存中,是不进行数据的更新或合并操作的,只增加 数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O 的高性能!

10.Hbase 的rowKey 的设计原则/

1.长度原则:最多为64Kb,实际为10-100kb,建议越短越好 2.散列原则:如果 rowkey 按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey 的高位作为散 列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个 RegionServer,以实现 负载均衡的几率 3.唯一性原则:必须在设计上保证其唯一性,rowkey 是按照字典顺序排序存储的,因此,设计rowkey 的时候,要 充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块

spark

1.为什么会有spark?

 因为传统的并行计算模型无法有效的解决迭代计算(iterative)和交互式计算(interactive);而Spark的使命便 是解决这两个问题,这也是他存在的价值和理由。  RDD就是为了解决迭代计算的,因为它将所有的数据加载到内存中,进行迭代时计算,这是spark的核心:内存计算  spark是scala语言编写的,所以spark可以完美运行scala解释器,使用scala像操作本地集合一样轻松操作分布式数 据集

2.创建RDD有哪几种方式?

 可以由已有的大集合创建,使用sc.parallelize(Array(,,))  可以读取hdfs上的文件进行创建,sc.textFile("/)  可以由已经存在的RDD进行算子操作生成,xx.flatMap()

3.RDD的算子分类,举出常用的?

 transformation:根据数据集创建一个新的数据集,返回一个新的RDD  action:对RDD结果计算后返回一个value值给驱动程序  transformation:map、flatMap、fliter,join、union、groupByKey、sortByKey、reduceByKey  action:reduce、collect、count、first、take、foreach、saveAsTextFile

4.RDD的依赖分类?

 窄依赖:父RDD的一个partition最多只能被子RDD的一个partition依赖  宽依赖:父RDD的一个partition可以被多个子RDD的partition依赖

5.RDD的Lineage作用?

 会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢 失的数据分区。

6.RDD的缓存分类?

 persist和cache  但是并不是这两个方法被调用时立即缓存,而是触发后面的action时,该RDD将会被缓存在计算节点的内存中,并供 后面重用。cache最终也是调用了persist方法,默认的存储级别都是仅在内存存储一份!Spark的存储级别还有好多 种,存储级别在object StorageLevel中定义的。

7.介绍一下DAG?

 DAG,有向无环图,它是由原始RDD经过一系列转换形成的,根据RDD之间依赖关系的不同将DAG划分为不同的Stage

8.DAG换分Stage的过程?

 对于窄依赖,partition的转换处理在一个Stage中完成计算。  对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,  因此宽依赖是划分Stage的依据!!!!!!!!!!!!!!!!!!!!!!!!!!!  过程:

1.从最后一个rdd开始往前推,把当前的rdd加入到一个stage中,这个stage就是最后一个stage
2.遇到窄依赖就把该rdd加入到本stage,遇到宽依赖,这个时候就从宽依赖切开,最后一个stage就结束了
3.又重新分配一个stage,按照上面的规则继续往前推,一直到最开始的rdd,整个stage才算划分完成了

9.spark任务调度流程?

 1.首先RDD经过一些了的算子转换形成DAG有向无环图  2.DAGScheduler将DAG划分为不同的Stage,每一个Stage都是一个task,最终这些task存放到TakeSet集合中.然 后DAGScheduler将TaskSet提交给TaskScheduler  3.接着TaskScheduler将TaskSet中的每一个task提交给worker节点进行计算

10.spark的shuffle过程?

 在执行下面这些算子操作的时候会发生shuffle:  reduceByKey、GroupByKey、SortByKey、CountByKey、join、cogroup等。shuffle涉及到分片的分发

 shuffleManager的发展历史?  在 Spark 的源码中,负责 shuffle 过程的执行、计算和处理的组件主要就是ShuffleManager:shuffle管理器.  *在spark1.2以前,默认的shuffle计算引擎是HashShuffleManager,它有一个严重的弊端,就是会产生大量的中 间磁盘文件,进而由大量的磁盘IO操作影响了性能。  *在spark1.2以后的版本,默认的suffle计算引擎改成了SortShuffleManager。SortShuffleManager相对于  HashShuffleManager来说,有了一定的改进。主要就是在于,每一个Task在进行shuffle操作时,虽然也会产生较 多的临时磁盘文件,但是最后会将所有的临时文件合并(merge)成一个磁盘文件,因此每个Task就只有一个磁盘文 件。在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部分数据即可!

 shuffle的写过程: ShuffleMapTask会为每个ResultTask创建对应的Bucket,ShuffleMapTask产生的结果会根据设置的 partitioner得到对应的BucketId,然后填充到响应得到Bucket中去。每个ShuffleMapTask输出的结果可能包含所有的ResultTask所需要的数据,所以每个ShuffleMapTask创建的Bucket的数目和ResultTask的数目相等。 ShuffleMapTask创建的Bucket对应磁盘上的一个文件,用于存储结果,此文件也被称为BlockFile。如果在配置文件中设置了属性spark.shuffle.consolidateFiles为true的话,ShuffleMapTask所产生的Bucket就不一定单独对应一个文件了,而是对应文件的一部分,这样做会大量减少产生的BlockFile文件数量。 ShuffleMapTask在某个节点上第一次执行时,会被每个ResultTask创建一个输出文件,并把这些文件组织成ShuffleFileGroup,当这个ShuffleMapTask执行结束后,当前创建的ShuffleFileGroup可以被释放掉,进行循环使用,当又有ShuffleMapTask在这个节点执行时,不需要创建新的输出文件,而是在上次的ShuffleFileGroup中已经创建的文件里追加写一个Segment;如果当前的ShuffleMapTask还没执行完,此时又在此节点上启动了新的ShuffleMapTask,那么新的ShuffleMapTask只能又创建新的输出文件再组成一个ShuffleFileGroup来进行结果输出。  shuffle的读过程: Spark可以使用两种方式来读取数据,一种是普通的Socket方式,一种是使用Netty框架。使用Netty方式的话,可以通过配置属性spark.shuffle.use.netty为true来启动。 ResultTask读数据时,会通过BlockManager根据BlockID把相关的数据返回给ResultTask。如果使用是Netty框架,BlockManaget会创建ShuffleSender专门用于发送数据。如果ResultTask所需要的数据恰好在本节点,那就直接去磁盘上读即可,不在通过网络获取,这点比MapReduce做得更好,MapReduce取数据时,即使数据在本地还是要走一遍网络传输。 Spark默认的Shuffle过程中的数据都没有经过排序(Hash模式),这一点也要比MapReduce框架节省很多时间。ResultTask读取过来的数据首先存放到HashMap中,如果数据量比较小,占用内存空间不会太大,如果数据量比较大,那就需要较多内存,内存不足该如何解决? Spark提供了两种方式,根据spark.shuffle.spill的设置,当内存不够时,直接就失败。如果设置了可以Spill到磁盘,那就把内存中的数据溢写到磁盘中。写到磁盘前,先把内存中的HashMap排序,并且把内存缓冲区中的数据排序之后和写到磁盘上文件数据组成一个最小堆,每次从最小堆中读取最小的数据。

11.介绍一下RDD的容错机制checkpoint的原理?

 1.当RDD使用cache机制从内存中读取数据,如果数据没有读到,会使用checkpoint机制读取数据。此时如果没有 checkpoint机制,那么就需要找到父RDD重新计算数据了,因此checkpoint是个很重要的容错机制。checkpoint 就是对于一个RDD chain(链)如果后面需要反复使用某些中间结果RDD,可能因为一些故障导致该中间数据丢失, 那么就可以针对该RDD启动checkpoint机制,使用checkpoint首先需要调用sparkContext的setCheckpoint方 法,设置一个容错文件系统目录,比如hdfs,然后对RDD调用checkpoint方法。之后在RDD所处的job运行结束后, 会启动一个单独的job来将checkpoint过的数据写入之前设置的文件系统持久化,进行高可用。所以后面的计算在 使用该RDD时,如果数据丢失了,但是还是可以从它的checkpoint中读取数据,不需要重新计算。  2.persist或者cache与checkpoint的区别在于,前者持久化只是将数据保存在BlockManager中但是其lineage是不 变的,但是后者checkpoint执行完后,rdd已经没有依赖RDD,只有一个checkpointRDD,checkpoint之后,RDD 的lineage就改变了。persist或者cache持久化的数据丢失的可能性更大,因为可能磁盘或内存被清理,但是 checkpoint的数据通常保存到hdfs上,放在了高容错文件系统。

12.DataSet,DataFrame和RDD的比较?

 1.三种都是spark平台下的弹性分布式数据集,为处理超大型数据提供便利  2.三者都是惰性机制,执行transform操作时不会立即执行,执行Action时才会执行  3.三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出  4.三者都有partition的概念  5.三者有许多共同的函数,如map,filter等  6.DataFrame可以叫DataSet[Row],DataFrame中每一行的类型是Row,不解析不知道每一行的字段名和对应的字段 类型;DataSet中,每一行是什么类型是不一定的,自定义了case class之后就可以很自由的获取每一行的信息

13.创建DataFrame的几种方式?

 已经存在的RDD、结构化文件、外部数据库、Hive表

14.DataFrame和DataSet的相互转换?

 df.as[ElementType] DF-DS  ds.toDF() DS-DF

16.sparkStreaming的原理?

 输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。

17.sparkSteaming计算流程?

 Spark Streaming是将流式计算分解成一系列短小的批处理作业。这里的批处理引擎是Spark Core,也就是把 Spark Streaming的输入数据按照batch size(如1秒)分成一段一段的数据(Discretized Stream),每一段数 据都转换成Spark中的RDD(Resilient Distributed Dataset),然后将Spark Streaming中对DStream的 Transformation操作变为针对Spark中对RDD的Transformation操作,将RDD经过操作变成中间结果保存在内存 中。整个流式计算根据业务的需求可以对中间的结果进行缓存或者存储到外部设备。

18.cache 和 pesist 的区别?

 1.cache 和 persist 都是用于将一个 RDD 进行缓存的,这样在之后使用的过程中就不需要重新计算了,可以大大 节省程序运行时间;  2.cache 只有一个默认的缓存级别 MEMORY_ONLY ,cache 调用了 persist,而 persist 可以根据情况设置其它 的缓存级别;  3.executor 执行的时候,默认 60%做 cache,40%做 task 操作,persist 最根本的函数,最底层的函数

hive

117) 关于 where 和 join 的优化

优化前: select o* from order o join customer c on o.cid=c.id where o.time='2017-01-01' 优化后: select o.* from (select cid from order where time='2016-01-01')o join customer c on o.cid=c.id; 优化前是先 join 后再通过 where 进行过滤, 这样并没有减轻 reduce 的压力。 优化后是先在 map 端执行 where, 过滤数据, 然后在 join。 这样就会降低计算量

118) Hive group by 优化

1、 hive.groupby.skewindata=true; 如果 group by 过程出现倾斜, 应该设置为 true 2、 set hive.groupby.mapagger.checkinterval=100000; group 对应的键对应的记录条数超过这个值则会进行优化 这个仍然是启动两个 job

119) hive 的表优化

1、 分区 ‘表’相当于一个大目录, 分区就是在这个大目录下面的小目录 分区包括静态分区和动态分区 静态分区: 就是在建表的时候指定分区的 如果数据量量多, 一个分区需要一个 insert, 就会很麻烦, 所以可以使用动态分区 动态分区: set hive.exec.dynamic.partition=true; set hive.exec.dynamic.partition.mode=nonstrict; 默认值: strict 描述: strict 是避免全分区字段是动态的, 必须有至少一个分区字段是指定有值的

120) hive job 的优化 并行化执行

hive 执行过程中的 job 是按照默认的顺序来执行的, 如果没有太大的依赖关系, 最好并行执行, 减少执行的时间, 每个查询被 hive 转化成多个阶段, 有些阶段关联性不大, 则可以并行执行, 减少执行时间。 set hive.exec.parallel=true; set hive.exec.parallel.thread.numbe=16(默认 8) ;

121) hive job 的优化本地化执行

set hive.exec.mode.local.auto=true; 当一个 job 满足如下条件的时候才能真正使用本地模式; 1、 job 的输入数据大小必须小于参数: hive.exec.mode.local.auto.inputbytes.max(默认 128M) 2、 job 的 map 数必须小于参数: hive.exec.mode.local.auto.tasks.max(默认 4) 3、 job 的 reduce 数量必须是 0 或者 1

122) hive job 的优化 job 合并输入小文件

在集群中面临这样的问题, 集群中很多的小文件, 这样会启动很多的(FileInputFormat 会将 输入文件分割成 split。split 的个数决定了 map 的个数), 而且这些小文件的执行时间特别短, 造成集群的资源没有良好的利用 解决: set hive.input.format=oar.apache.hadoop.hive.ql.io.CombineHiveInputFormat 这样做后, 就会把多个 split 分片合并成一个。 合并的文件数由 mapred.max.split.size 限制的 大小决定

123) hive job 的优化 job 合并输出小文件

set hive.merge.smallfiles.avgsize=256000000;当输出文件平均大小小于该值, 启动新 job 合 并文件 set hive.merge.size.per.task=64000000;设定合并之后的文件大小

124) hive job 的优化 JVM 重利用

JVM 重用可以使得 JVM 实例在同一个 JOB 中重新使用 N 次 set mapred.job.reuse.jvm.num.tasks=10; JVM 重利用对 hive 的性能有很大的影响, 特别是对小文件的场景或者 task 特别多的场景, 可以有效减少执行时间。 当然, 这个值不能设置过大, 因为有一些 job 是有 reduce 任务的。 如果 reduce 任务没有完成, map 任务占用的资源不能都饿到释放, 这样其他作业就可能处于等待

125) hive job 的优化压缩数据

中间压缩就是处理 hive 查询的多个 job 之间的数据 中间压缩, 减少网络传输的数据量, 中间压缩, 不需要压缩效果特别好, 选择一个节省 CPU 耗时的压缩方式 set hive.exec.compress.intermediate=true; set hive.intermedia.compression.codec=org.apache.hadoop.io.compress.SnappyCode; set hive.intermedia.compression.type=BLOCK;(按照块进行压缩) hive 查询最终输出的压缩 这个阶段可以选择一个压缩效果比较明显的 , 这样可以降低集群存储的数据量, 占用较小的 空间 set hive.exec.compress.ouput=true; set mapred.output.compresssion.codec=org.apache.hadoop.io.compress.GzipCodec; set hive.intermedia.compression.type=BLOCK;

126) Hive 中的 order by, sort by , distribute by , cluster by 的区别

1.Order by

会将所有的数据在一个 reducer 上执行, 得到的结果是整体有序的。但是由于不能并发执行, 所以效率比较低。 2.sort by 排序操作在多个 reducer 上执行, 多以只能保证局部有序。 无法保证整体有序 而且, 使用 sort by 有多个 reduce 的情况下, 可能会造成 reduce 处理的数据范围可以重叠 3.distribute by 可以保证每个 reduce 处理数据范围不重叠。 但是不负责排序 4.cluster by Cluster by = distribute by + sort by133)

hive 有哪些方式保存元数据, 各有哪些特点

hive 的数据模型包括: database、 table、 partition 和 bucket。

1.Database: 相当于关系数据库里的命名空间(namespace), 它的作用是将用户和数据库 的应用隔离到不同的数据库或模式中 2.表(table): hive 的表逻辑上由存储的数据和描述表格中的数据形式的相关元数据组成。 Hive 里的表友两种类型一种叫托管表, 这种表的数据文件存储在 hive 的数据仓库里, 一种 叫外部表, 这种表的数据文件可以存放在 hive 数据仓库外部的分布式文件系统上, 也可以 放到 hive 数据仓库里(注意: hive 的数据仓库也就是 hdfs 上的一个目录, 这个目录是 hive 数据文件存储的默认路径, 它可以在 hive 的配置文件里进行配置, 最终也会存放到元数据 库里)。 3.分区(partition): hive 里分区的概念是根据“分区列”的值对表的数据进行粗略划分的机制, 在 hive 存储上就体现在表的主目录(hive 的表实际显示就是一个文件夹) 下的一个子目录, 这个文件夹的名字就是我们定义的分区列的名字, 没有实际操作经验的人可能会认为分区列 是表的某个字段, 其实不是这样, 分区列不是表里的某个字段, 而是独立的列, 我们根据这 个列存储表的里的数据文件。 4.桶(bucket): 上面的 table 和 partition 都是目录级别的拆分数据, bucket 则是对数据源数 据文件本身来拆分数据。

 

every part

java

1.String,StringBuffer,StringBuilder的比较?
  String是只读字符串,也就意味着String引用的字符串内容是不能被改变的
  StringBuffer/StringBuilder表示的字符串对象可以直接进行修改
  StringBuilder和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方法都没   有被 synchronized 修饰,因此它的效率理论上也比StringBuffer要高
2.java的基本数据类型?
  byte char short int long float double boolean
  1    2    2     4   8    4     8      1      (占用字节数)
3.List 的三个子类的特点?
  ArrayList  底层结构是可变数组,底层增删慢,查询快
  LinkedList 底层结构是双向链表型的,增删快,查询慢
  voctor 底层结构是数组 线程安全的,增删慢,查询慢
5.List 、Map、Set 的区别?
  List 和 Set 是单列集合,Map 是双列集合
  List 中存储的数据是有顺序,并且允许重复
  Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的       (Set 集合根据 hashcode 进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户       来说set 中的元素还是无序的)
  Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的
6.hashmap与hashtable区别?
  hashmap是线程不安全的,允许有null的键和值,效率高一点,方法不是Synchronize的,如果要对外提供同步,有      containsvalue和containsKey方法
  hashtabl是线程安全的,不允许有null的键和值,效率稍低、方法是Synchronize的,有contains方法
7.创建HashMap对象使用Map和HashMap接收的区别?
  使用Map接收:使用了面向接口的编程思想,利用父引用来执行实现类对象。这样如果以后需要改变业务,更改后面的   实现类,只需要修改后面的新建的实现类就行。而使用HashMap接收的话,需要同时修改前面和后面的类型
8.如何解决hashMap中的hash散列值冲突?
  通常是两种方法:链表法和开放地址法。
  链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;
  开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位
9.

linux

1.crontab 定时相关命令?
  crontab -e 编辑
  crontab -l 列出定时任务
  crondab -r 删除所有的定数任务
2.tail -f 和 tail -F 的区别?
  tail -f:监控的是文件的描述,如果文件修改名称了,或者删除了,就不能继续监控文件内容的变化了
  tail -F:监控的是文件名,即使文件修改了名称,一旦再由相同名称的文件出现,依然会监控新的文件内容变化

flume

1.flume作为常用的文件日志采集的工具,常见的数据源(source),下沉点(sink),channel(通道)有哪些?
  常见的source:
     Avro Source,Exec Source,NetCat,spooldir,tailDir,Kafka Source,HTTP Source
  常见的sink:
     HDFS Sink,Logger Sink,Avro Sink,Hive Sink,HBaseSinks,Kafka Sink,HTTP Sink
  常见的channel:
     Memory Channel,JDBC Channel,Kafka Channel,File Channel
2.flume三个组件的功能分别是什么?
  Source:采集源,用于跟数据源对接,以获取数据
  Sink:下沉地,采集数据的传送目的,用于往下一级 agent 传递数据或者往最终存储系统传递数据
  Channel:agent 内部的数据传输通道,用于从 source 将数据传递到 sink
3.flume的工作流程核心?
  flume把数据从数据源(source)收集过来,再将收集到的数据送到指定的目的地(sink),为了保证输送的过程一定成   功,在送到目的地(sink)之前,会先缓存数据(channel),待数据真正到达目的地(sink)后,flume 在删除自己缓存   在channel中的数据.
4.flume传输数据的最基本单元是什么?组成是怎样的?
  event
  event包括:event headers、event body、event信息,其中event 信息就是flume收集到的日记记录
5.flume如何自定义拦截器?
  定义拦截器的原因?
  根据实际业务的需求,为了更好的满足数据在应用层的处理,通过自定义 flume 拦截器,过滤掉不需要的字段,并   对指定字段加密处理等操作,将源数据进行预处理。减少了数据的传输量,降低了存储的开销!
  具体实现方式?
  【1】定义一个class,实现interceptor接口,实现intercept方法,将event的内容进行过滤处理,然后再放行
  【2】再定义一个class,实现Interceptor.Builder接口,实现build方法,作为拦截器的入口
  【3】将程序打成jar包,上传到flume的lib包中就可以在采集方案文件中使用自定义的拦截器了
6.flume的 负载均衡(load_balance) 和 高容错(failover)的区别?
  负载均衡:第二级的节点是同时工作的,可以使用轮训(round_robin)的方式或者随机(random)的方式接口数据
  高容错:第二级节点不是同时工作的,只有当正在工作的节点出问题停止服务的时候,其他的节点才会开始进行工作

hadoop

1.说说hadoop和hadoop生态系统的区别?
  hadoop 指 hdfs、mapreduce、yarn(hadoop2.0之后有的)
  hadoop生态系统指围绕hadoop的一系列工具,有:                                                       hdfs,mapreduce,hive,hbase,pig,sqoop,flume,zookeeper,azkaban,mahout等
2.Cloudera公司的CDH和Apache Hadoop的区别?
 【1】CDH对Hadoop版本的划分非常清晰,只有两个系列的版本,分别是cdh3和cdh4,分别对应第一代                   Hadoop(Hadoop1.0)和第二代Hadoop(Hadoop 2.0),相比而言,Apache版本则混乱得多
 【2】CDH支持Kerberos安全认证,apache hadoop则使用简陋的用户名匹配认证
 【3】CDH文档清晰,很多采用Apache版本的用户都会阅读CDH提供的文档,包括安装文档、升级文档等
 【4】CDH支持Yum/Apt包,Tar包,RPM包,CM安装,Apache hadoop只支持Tar包安装
3.CDH使用推荐的Yum/Apt包安装时,有以下几个好处:
  【1】联网安装、升级,非常方便
  【2】自动下载依赖软件包
  【3】Hadoop生态系统包自动匹配,不需要你寻找与当前Hadoop匹配的Hbase,Flume,Hive等软件,Yum/Apt会根据       当前安装Hadoop版本自动寻找匹配版本的软件包,并保证兼容性
  【4】自动创建相关目录并软链到合适的地方(如conf和logs等目录)
4.相比于HDFS1.0 ,2.0最主要的改进在哪几个方面?
  引入一个新的资源管理系统YARN
  hadoop单点故障得以解决-HA
  hadoop 2.0的最大变化出现在内核(HDFS、MapReduce和YARN)
5.讲讲yarn的工作流程?
 【1】client向ResourceManager提交应用程序,中包括ApplicationMaster程序、启动ApplicationMaster命           令、用户程序等
 【2】resourceManager为该应用程序分配第一个container,并与对应的nodeManager通信,要求它在这个        container中启动应用程序的ApplicationMaster
 【3】ApplicationMaster首先向ResourceManager注册,这样用户可以直接通过ResourceManager查看应用程序的 运行状态,然后它将为各个任务申请资源,并监控它的运行状态,直到运行结束,即重复4~7
 【4】ApplicationMaster采用轮询的方式通过RPC协议向resourceManager申请和领取资源
 【5】一旦ApplicationMaster申请到资源后,便与对应的Nodemanager通信,要求它启动任务
 【6】NodeManager为任务设置好运行环境(包括环境变量、JAR包、二进制程序等)后,将任务启动命令写到一个脚本       中,并通过运行该脚本启动任务
 【7】各个任务通过某个RPC协议向ApplicationMaster汇报自己的状态和进度,以让ApplicationMaster随时掌握        各个任务的运行状态,从而可以在任务失败时重新启动任务。在应用程序运行过程中,用户可随时通过RPC向          ApplicationMaster查询应用程序的当前运行状态
 【8】应用程序运行完成后,ApplicationMaster向resourceManager注销并关闭自己
8.yarn的调度器有哪几种,各自工作原理,默认是哪一种?
  有 FIFO Scheduler,Capacity Scheduler,Fair Scheduler
  FIFO:按照任务提交的顺序进行排队,先提交的先执行,等前面的执行完之后后面的才可以执行
  Capacity:多个组织共享集群,分成多个队列,每个组织获得一个队列,互不影响,但是每个单独的队列    之间又是FIO的策略
  Fair:该调度器会给job动态调整系统资源,比如当一个较大的任务提交时,会分配给它整个集群的资源,当又有     小的任务提交的时候,该较大的任务会释放一部分资源来运行这个较小的任务
  默认:FIFO Scheduler
9.hadoop的几个节点进程?
  hadoop 的集群是基于 master/slave 模式,
  namenode 和 jobtracker 属于 master,
  datanode 和 tasktracker 属 于 slave , master 只 有 一 个 , 而 slave 有多个
  SecondaryNameNode 内存需求和 NameNode 在一个数量级上,所以通常 secondaryNameNode和 NameNode 运行   在不同的机器上
10.hdfs的默认副本数?
   3个
11.hadoop的默认block分块大小?
   128M (hadoop 2..0)
12.hadoop集群最主要的瓶颈?
   磁盘IO
13.SecondaryNameNode的作用?
   帮助NameNode合并编辑日志,减少NameNode的启动时间
14.Hadoop 运行的三种模式?
   单机,伪分布,全分布

hdfs

1.hdfs安全模式手动启动和关闭命令?
  启动:hdfs dfsadmin -safemode enter
  关闭:hdfs dfsadmin -safemode leave
2.hdfs操作相关API
  上传文件:hadoop fs -put /  /
  读取文件:hadoop fs -cat /
  创建文件夹:hadoop fs -mkdir -p / /
  删除文件夹:hadoop -rm -r  /
3.

mapReduce

1.讲讲MapReduce的shuffle过程?
  MapReduce的shuffle过程是从mapper输出数据开始到reducer接收数据之前这个阶段!可以分为map-shuffle和     ruduce-shuffle
mapper-shuffle:
  mappper写出之前先进行partition分区,用户可以自定义分区(就是继承Partitioner类,重写getPartition方     法),如果没有进行自定义分区,框架会使用默认的分区(HashPartitioner)对key去hash值之后,然后在对       numReduceTask进行取模(目的是为了平衡reduce的处理能力),决定由哪个reduceTask来处理。然后将分完区的   结果开始序列化成字节数组,开始写入缓冲区。缓冲区的默认大小是100M,当缓冲区大小达到溢出比,默认0.8,也就   是80M时,开始启动溢写线程,从内存溢出到磁盘,此时map输出的结果继续由另一个线程往剩余的20M里写,两个线程   相互独立,彼此互不干扰。溢写线程启动后,开始对key进行排序,默认的是自然排序,如果客户端自定义了         Combiner之后(相当于map阶段的reduce),将相同的key的value相加,这样的好处就是减少溢写到磁盘的数据量   (Combiner使用一定得慎重,适用于输入key/value和输出key/value类型完全一致,而且不影响最终的结果,例      如:求中位数的就不适合使Combiner),最后需要将在磁盘上生成一个一个的小文件,merge合并为一个整的文件,   这时候Map-Shuffle就算是完成了,一个MapTask端生成一个结果文件;
reduce-shuffle:
  当MapTask完成任务数超过总数的5%后,开始调度执行ReduceTask任务,然后ReduceTask默认启动5个copy线程到   任务节点上分别copy一份属于自己的数据。这些拷贝的数据会首先保存到内存缓冲区中,当达到一定的阈值的时候,   开始启动内存到磁盘的溢写过程,一直运行到map端没有数据生成,最后启动磁盘到磁盘的Merge合并生成最终的那个   文件。在溢写过程中,会进行Sort排序,将相同的key的value放到同一个集合中,相同key的同一组value调用       reduce方法进行业务处理,最终调用OutputFormat输出数据到磁盘文件中。
2.mapReduce读取分片的过程?
  通过FileInputFomat中的TextInputFormat获取输入分片,使用默认的RecordReader:LineRecordReader将一个   输入分片中的每一行按\n分割成key-value。key是偏移量 value是每一行的内容,每读取一行调用一次map方法,一   个输入分片对应一个Maptask任务,hadoop2默认的分片大小是128M
3.分片规则?
  byteRemaining / blockSize >= 1.1,所以不会出现129M的文件被分成两个分片的现象
4.如何实现MapReduce的二次排序?
  mapReduce的shuffle阶段阶段,会对key进行默认自然排序,但是shuffle分组之后同一key值的value序列中的     顺序是不固定的,如果要想此时value的值也是排好序的,这个需求就是二次排序。
  有两种解决方法:
    【1】直接在reduce端对统一分组的values序列进行排序,使用treeSet或者arrayList都行,但是把排序任务都          交给reduce阶段,当values序列有很多的时候,会对cpu和内存造成极大的负载,不推荐使用!
    【2】将map端输出的中的key和value组合成一个新的key(称为newKey),value值不变。这样就变          成<(key,value),value>,然后重新实现排序方法,在框架默认对newkey进行排序的时候,先按照newkey    中的key部分排序,如果key部分相同,就按照value部分进行排序。这样最终同一组key中的value也就排好          序了。
     注意:需要自定义的地方
       自定义数据类型实现组合key,并且进行序列化和反序列化以及排序操作
         实现方式:继承WritableComparable
       自定义partioner,形成newKey后保持分区规则任然按照key进行,保证不打乱原来的分区
         实现方式:继承partitioner
5.请列出mapReduce中常用的InputFormat?
  有TextInputFormat和KeyValueTextInputFormat
  TextInputFormat(默认):
     用于读取纯文本文件,key是每一行的位置偏移量,LongWritable类型的,value是每一行的内容,Text类型
  KeyValueTextInputFormat:
     同样用于读取文件,如果行被分隔符(默认是tab)分割为两部分,第一部分为key,剩下的部分 为value,
     如果没有分隔符,整行作为 key,value为空
6.

hbase

1.讲一讲hbase的二级索引?
  HBase的一级索引就是rowkey,我们只能通过rowkey进行检索。如果我们相对hbase里面列族列进行一些组合查       询,就需要采用HBase的二级索引方案来进行多条件的查询!
  实现方式:
  hbase的二级索引的本质就是构建各列值对应其行键的映射关系。通过某一列的值可以确定所对应的rowkey,然后通   过rowkey就能够查找到其他的列,这样就能构建多列的组合查询。
2.Hbase中的数据类型?
  都是字节数组:byte[]
3.hbase的特点是什么?
 【1】Hbase是一个分布式的基于列式存储的数据库,基于Hadoop的hdfs存储,使用zookeeper进行管理
 【2】Hbase适合存储半结构化或非结构化数据,对于数据结构字段不够确定或者杂乱无章很难按一个概念去抽取的数据
 【3】Hbase 为 null 的记录不会被存储
 【4】基于的表包含 rowkey,时间戳,和列族。新写入数据时,时间戳更新,同时可以查询到以前的版本
 【5】hbase 是主从架构。HMaster 作为主节点,HRegionserver 作为从节点
4.讲讲hbase实时查询的原理?
  实时查询,可以认为是从内存中查询,一般响应时间在1秒内.
  HBase的机制是数据先写入到内存中 , 当数据量达到一定的量 (如128M),再写入磁盘中,在内存中,是不进行数   据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O 的高   性能!
5.Hbase 的rowKey 的设计原则?
 【1】长度原则:最多为64Kb,实际为10-100kb,建议越短越好
 【2】散列原则:如果 rowkey 按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey 的高位作为                散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个 RegionServer,以                实现负载均衡的几率
 【3】唯一性原则:必须在设计上保证其唯一性,rowkey 是按照字典顺序排序存储的,因此,设计rowkey 的时候,要                 充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块
6.hive和hbase的区别和关系?
  Hbase和Hive在大数据架构中处在不同位置,
  Hbase主要解决实时数据查询问题,Hive主要解决数据处理和计算问题,一般配合使用!
  区别:
  Hbase:基于Hadoop数据库,是一种NoSQL数据库,主要适用于海量明细数据(十亿、百亿)的随机实时查询,如日          志明细、交易清单、轨迹行为等。
  Hive:是Hadoop数据仓库,严格来说,不是数据库,主要是让开发人员能够通过SQL来计算和处理HDFS上的结构化数         据,适用于离线的批量数据计算。通过元数据来描述Hdfs上的结构化文本数据,通俗点来说,就是定义一张表         来描述HDFS上的结构化文本,包括各列数据名称,数据类型是什么等,方便我们处理数据,当前很多SQL ON         Hadoop的计算引擎均用的是hive的元数据,如Spark SQL、Impala等,基于第一点,通过SQL来处理和计算         HDFS的数据,Hive会将SQL翻译为Mapreduce来处理数据;
  关系:
  【1】通过ETL工具(flume)将数据源抽取到HDFS存储
  【2】通过Hive清洗、处理和计算原始数据
  【3】HIve清洗处理后的结果,如果是面向海量数据随机查询场景的可存入Hbase
  【4】数据应用从HBase查询数据
7.redis,传统数据库,hbase,hive 每个之间的区别
  redis:分布式缓存,强调缓存,内存中数据
  传统数据库:注重关系
  hbase:列式数据库,无法做关系数据库的主外键,用于存储海量数据,底层基于hdfs
  hive:数据仓库工具,底层是mapreduce。不是数据库,不能用来做用户的交互存储

hive

1.你们数据库怎么导入hive 的,有没有出现问题
解答:
在导入hive的时候,如果数据库中有blob或者text字段,会报错。有个参数limit
1.hive的仓库分层?
  ODS:源数据层,直接收集的外围数据,没有统一的格式,不利于分析
  DW:数据仓库层,进行格式的规整,统一格式
  DA:数据应用层,真正使用的数据
2.hive的分桶操作?
  开启分桶命令:set hive.enforce.bucketing = true
  指定分桶数量:set mapreduce.job.reduces = N
3. Hive 中的 UDF 函数有哪几种, 你写了哪些?
数学函数, 字符串函数
字段大小写转换操作
3.hive内部表和外部表的区别?
  Hive创建内部表时,会将数据移动到数据仓库指向的路径
  Hive创建外部表时,仅记录数据所在的路径,不对数据的位置做任何改变
  Hive在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据
4.UDF的开发流程?(hive中用户自定义方法)
 【1】写一个java类,继承UDF,重写evaluate方法
 【2】打成jar包上传到服务器
 【3】将jar包添加到hive的classpath中
 【5】创建临时函数与开发好的 java class 关联
 【6】在 hql 中使用自定义的函数
5.hive中的分桶排序?
  cluster by == distributed by + sort by(order by全局排序)
6.什么是ETL工作?
  ETL工作的实质就是从各个数据源提取数据,对数据进行转换,并最终加载填充数据到数据仓库维度建模后的表中。只   有当这些维度/事实表被填充好,ETL 工作才算完成!
7.常用的sql语句操作?
 【1】建内部表
create table student(Sno int,Sname string,Sex string,Sage int,Sdept string) row format delimited fields terminated by ',';
 【2】建外部表
create external table student_ext(Sno int,Sname string,Sex string,Sage int,Sdept string) row format delimited fields terminated by ',' location '/student';
 【3】导入数据:
load data local inpath '/root/hivedata/t_user.txt' into table t_user partition(country='China');
 【4】分桶操作
      默认分桶是关闭的,需要启分桶
      set hive.enforce.bucketing = true;
      设置分几桶(起始就是设置mr程序reduceTask的个数,因为sql最终会转变为mr程序执行)
      set mapreduce.job.reduces=4;
      分桶的字段必须来自于分桶表中的已有字段,也就是说安装那个字段进行分开,和分区不同!!!
创建桶表:
create table stu_buck(Sno int,Sname string,Sex string,Sage int,Sdept string)
clustered by(Sno)
into 4 buckets
row format delimited
fields terminated by ',';
创建一个stu_buck分桶的临时表student:
create table student(Sno int,Sname string,Sex string,Sage int,Sdept string)
row format delimited
fields terminated by ',';
分桶表导入数据:
insert overwrite table stu_buck
select * from student cluster by(Sno);
对某列进行分桶的同时,根据另一列进行排序:
insert overwrite table stu_buck
select * from student cluster by(Sno) sort by(Sage); 会报错,cluster 和 sort 不能共存,需要将cluster替换成distribute,如下
insert overwrite table stu_buck
select * from student distribute by(Sno) sort by(Sage asc);
注意:
cluster(分且排序,字段必须一样)== distribute(分) + sort(排序)(字段可以不一样)
把表(或者分区)组织成桶(Bucket)有两个理由:
1.获得更高的查询处理效率(john查询,针对john字段分区或分桶)
2.使取样(sampling)更高效
  【5】查询结果导出到文件系统
       local:表示本地文件系统,否则表示hdfs分布式文件系统
insert overwrite local directory '/root/downFromHive'
select * from t_user;

insert overwrite directory '/data/downFromHive'
select * from t_user;
  【6】分区操作
增加分区:
一次添加一个分区
alter table table_name add partition (dt='20170101') location '/user/hadoop/warehouse/table_name/dt=20170101';
一次添加多个分区
alter table table_name add
partition (dt='2008-08-08', country='us') location '/path/to/us/part080808'
partition (dt='2008-08-09', country='us') location '/path/to/us/part080809';
删除分区:
alter table table_name drop if exists partition (dt='2008-08-08');
alter table table_name drop if exists partition (dt='2008-08-08', country='us');
修改分区:
alter table table_name partition (dt='2008-08-08') rename to partition (dt='20080808');
  【7】一些显示命令
show tables;
显示当前数据库所有表

show databases | schemas;
显示所有数据库

show partitions table_name;
显示表分区信息,不是分区表执行报错

show functions;
显示当前版本 hive 支持的所有方法

desc extended table_name;
查看表信息

desc formatted table_name;
查看表信息(格式化美观,推荐这个命令)

describe database database_name;
查看数据库相关信息

kafka

1.kafka的相关概念?
  角色:producer、consumer、topic、broker
  分片:solrcloud中有提及到
当数据量非常大的时候,一个服务器存放不了,就将数据分成两个或者多个部分,存放在多台服务器上。每个服务器上的数据,叫做一个分片
  副本:solrcloud中有提及到
当数据只保存一份的时候,有丢失的风险。为了更好的容错和容灾,将数据拷贝几份,保存到不同的机器上,叫做一个副本
2.kafka消费者和分区的关系?
  同一个partition的数据可以被不同的消费组获取
  同一个组内的消费者不能同时消费同一个partition
  同一个组内的消费者可以消费不同的partition,也就是说,同一个组内的消费者数小于等于 partition数,不       能大于,如果同组内的消费者数大于partition数,那么一定会有消费者是空闲的
3.kafka的作用?
  作为缓冲,来异构、解耦系统

storm

1.Storm和Spark Streaming的区别?
  sparkSteamig的实时性没有storm高,为准实时,storm是在100ms的间隔内,sparkStreaming是在0.5到2s.
  sparkSteaming的吞吐量比storm要大,因为SparkStreaming的单位是Dstream,由一系列的RDD构成.
2.storm的akc消息不丢失机制?
  BaseRichBolt:
  collector.ack(input)
  collector.fail(input)
  BaseBasicBolt:
  自动调用ack,出现异常就抛出FailedException()
3.storm常用的分组策略?
  LocalOrShuffle:本地或随机分组
  partialKeyGrouping:部分关键字分组
4.storm可以整合的框架?
  jdbc & redis & kafka & hdfs & hbase
5.storm只负责计算,不负责存储
6.

spark

1.为什么会有spark?
  因为传统的并行计算模型无法有效的解决迭代计算和交互式计算,而Spark的使命便是解决这两个问题,这也是他存在   的价值和理由!
  RDD就是为了解决迭代计算的,因为它将所有的数据加载到内存中,进行迭代时计算,这是spark的核心:内存计算
  spark是scala语言编写的,所以spark可以完美运行scala解释器,使用scala像操作本地集合一样轻松操作分布式数   据集
2.spark任务调度流程?
  【1】首先RDD经过一系列的算子转换形成DAG有向无环图
  【2】DAGScheduler将DAG划分为不同的Stage,每一个Stage都包含一个TaskSet,每个taskSet都不同,然后            DAGScheduler将TaskSet提交给TaskScheduler
  【3】接着TaskScheduler将TaskSet中的每一个task提交Worker节点的在executor进程中运行
  DAGScheduler的介绍?
       【1】对DAG进行Stage划分
       【2】在一个负载的shuffle之后需要对RDD进行物化(cache),方便后面进行计算
       【3】重新提交shuffle输出丢失的stage给TaskScheduler
       【4】将Taskset传给底层调度器
            TaskScheduler
            YarnClusterScheduler
            YarnClientClusterScheduler
  TaskScheduler的介绍?
       【1】为每个TaskSet创建一个TaskSetManager来管理taskSet的生命周期
       【2】数据本地性决定每个Task的最佳位置
       【3】将TaskSet提交到集群的Worker节点运行并监控
       【4】推测执行,遇到计算缓慢的任务需要提交到其他的节点重试
       【5】将shuffle输出丢失的stage重新提交个DAGSscheduler
3.spark中的数据倾斜的现象、原因、后果?
   (1)、数据倾斜的现象
        多数task执行速度较快,少数task执行时间非常长,或者等待很长时间后提示你内存不足,执行失败
   (2)、数据倾斜的原因
        数据问题
        【1】key本身分布不均衡(包括大量的key为空)---有的节点对应key的数据量很大,而有的节点较少
        【2】key的设置不合理
        spark使用问题
        【1】shuffle时的并发度不够
        【2】计算方式有误
   (3)、数据倾斜的后果
        【1】spark中的stage的执行时间受限于最后那个执行完成的task,因此运行缓慢的任务会拖垮整个程序的运              行速度(分布式程序运行的速度是由最慢的那个task决定的!!!!)
        【2】过多的数据在同一个task中运行,将会把executor撑爆
4.如何定位数据倾斜的位置?
  Spark WEB UI来查看当前运行的stage各个task分配的数据量,从而进一步确定是不是task分配的数据不均匀导致了数据倾斜。知道数据倾斜发生在哪一个stage之后,接着我们就需要根据stage划分原理,推算出来发生倾斜的那个stage对应代码中的哪一部分,这部分代码中肯定会有一个shuffle类算子。通countByKey查看各个key的分布.
5.如何解决spark中的数据倾斜问题?
   【1】数据问题导致的数据倾斜
        找出异常的key,如果任务长时间卡在最后几个任务,首先要对key进行抽样分析,判断是哪些key造成的。
        经过分析,倾斜的数据主要有以下三种情况:
        (1) null(空值)或是一些无意义的信息()之类的,大多是这个原因引起
        (2) 无效数据,大量重复的测试数据或是对结果影响不大的有效数据
        (3) 有效数据,业务导致的正常数据分布
        解决办法:
          第1,2种情况,直接对数据进行过滤即可(因为该数据对当前业务不会产生影响)
          第3种情况则需要进行一些特殊操作,常见的有以下几种做法
          (1) 隔离执行,将异常的key过滤出来单独处理,最后与正常数据的处理结果进行union操作
          (2) 对key先添加随机值,进行操作后,去掉随机值,再进行一次操作
          (3) 使用reduceByKey 代替 groupByKey(reduceByKey用于对每个key对应的多个value进行merge操                 作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义)
   【2】spark使用不当导致的数据倾斜
        (1)提高shuffle并行度
           dataFrame和sparkSql可以设置spark.sql.shuffle.partitions参数控制shuffle的并发度,
           默认为200
           rdd操作可以设置spark.default.parallelism控制并发度,默认参数由不同的Cluster Manager控制
           局限性: 只是让每个task执行更少的不同的key。无法解决个别key特别大的情况造成的倾斜,如果某些                      key的大小非常大,即使一个task单独执行它,也会受到数据倾斜的困扰
        (2)使用map join 代替 reduce join
           这样可以使程序完全避免shuffle的过程,自然也就没有数据倾斜的困扰了
           局限性: 因为是先将小数据发送到每个executor上,所以数据量不能太大
6.spark的shuffle过程?
 【1】触发shuffle的一些算子操作?
      (1)涉及到分区的,分区都变了,肯定会重新shuffle---repartition
      (2)byKey之类的,reduceByKey、GroupByKey、SortByKey、CountByKey
      (3)关联之类的,join、cogroup
 【2】shuffleManager的发展历史?
      在spark1.2以前,默认的shuffle计算引擎是HashShuffleManager,它有一个严重的弊端,就是会产生大量的          中间磁盘文件,进而由大量的磁盘IO操作影响了性能。
      在spark1.2以后的版本,默认的suffle计算引擎改成了SortShuffleManager。SortShuffleManager相对于
      HashShuffleManager来说,有了一定的改进。主要就是在于,每一个Task在进行shuffle操作时,虽然也会产       生较多的临时磁盘文件,但是最后会将所有的临时文件合并(merge)成一个磁盘文件,因此每个Task就只有一       个磁盘文件。在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部       分数据即可!
      spark中的shuffleManager是负责shuffle过程的执行,计算和处理的组件,主要实现类有两个:
      HashShuffleManger(spark1.2之前,2.0之后就移除了)
      SortShuffleManger(spark1.2之后)
      HashShuffleManger分类:
      优化前的
      优化后的
      SortShuffleManger分类:
      普通运行机制
      bypass机制
  【3】不同模式下的shuffle过程?
          【1】HashShuffleManager未进行优化:
 [
     产生的文件数我M*R,造成大量磁盘IO操作,降低性能
 ]
               shuffleMapTask会为每个reuceTask创建一个文件保存task所要使用的数据,这样产生的文件就有                maptask个数乘以reducetask个数这么多。先写到内存缓冲区,之后在溢写到磁盘文件,虽然这样操   作数据非常简单,但是这样会造成磁盘IO操作过多,影响性能!所以只能适合小规模的数据,spark     不能运行在大规模的分布式集群上!
          【2】HashShuffleManager使用consolidate机制优化过的:
[
    产生文件数会减少为C*R,会使用shuffleFileGroup,一批task创建一个磁盘文件,而且shuffleFileGroup和磁     盘文件可以被后面批次的task重复利用,提升性能
]
               spark.shuffle.consolidateFiles 该参数默认值为false,将其设置为true即可开启优化机制.
               加入了consolidate机制之后,shuffle时产生的文件数量减少到了C*R个(C:mapper cores    R:rudce并行任务数不再是maptask个数和ruducetask个数的乘积了,但是如果Reduce Task的数量       依然很多,那么C乘以R也会很大。consolidate并没有降低并行度,只是Mapper端的内存消耗会变    少,OOM就会降低,另外一个方面就是磁盘的性能也会变的越来越好。
               开启consolidate机制之后,在shuffle write过程中,task就不是为下游stage的每个task创建一        个磁盘文件了。此时会出现shuffleFileGroup的概念,每个shuffleFileGroup会对应一批磁盘文         件,磁盘文件的数量与下游stage的task数量是相同的。一个Executor上有多少个CPU core,就可    以并行执行多少个task。而第一批并行执行的每个task都会创建一个shuffleFileGroup,并将数据  写入对应的磁盘文件内。当Executor的CPU core执行完一批task,接着执行下一批task时,下一批    task就会复用之前已有的shuffleFileGroup,包括其中的磁盘文件。也就是说,此时task会将数据  写入已有的磁盘文件中,而不会写入新的磁盘文件中。因此,consolidate机制允许不同的task复用        同一批磁盘文件,这样就可以有效将多个task的磁盘文件进行一定程度上的合并,从而大幅度减少磁         盘文件的数量,进而提升shuffle write的性能!
           【3】SortShuffleManager的普通运行机制:
[
   不同算子,不同数据结构,溢写数据到磁盘,之前会缓存,使用Java的BufferdOutputStream,产生多个临时文件,    最终会合并为一个完整文件,shuffle read根据index索引文件从最终的完整文件中拉取属于自己的数据
]
               在该模式下,数据会先写入一个内存数据结构中,此时根据不同的shuffle算子,可能选用不同的数                据结构。如果是reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进 行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写    入内存。接着,每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如   果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。
   在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序。排序过后,会分批将        数据写入磁盘文件。默认的batch数量是10000条,也就是说,排序好的数据,会以每批1万条数据的    形式分批写入磁盘文件。写入磁盘文件是通过Java的BufferedOutputStream实现的。    BufferedOutputStream是Java的缓冲输出流,首先会将数据缓冲在内存中,当内存缓冲满溢之后再  一次写入磁盘文件中,这样可以减少磁盘IO次数,提升性能。一个task将所有数据写入内存数据结构        的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件。最后会将之前所有的临时磁盘文件    都进行合并,这就是merge过程,此时会将之前所有临时磁盘文件中的数据读取出来,然后依次写入     最终的磁盘文件之中。此外,由于一个task就只对应一个磁盘文件,也就意味着该task为下游stage  的task准备的数据都在这一个文件中,因此还会单独写一份索引文件,其中标识了下游各个task的数 据在文件中的start offset与end offset。SortShuffleManager由于有一个磁盘文件merge的过    程,因此大大减少了文件数量!
           【4】SortShuffleManager的bypass运行机制:
 [
     过程和未优化的HashShuffleManager一样,但是最终会进行临时文件的合并,效率略有提升,但是有条件,不需      要进行排序,shufflemaptask数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认值为200)
 ]
                bypass运行机制的触发条件如下:
    shufflemaptask数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认值为200)
        不是排序类的shuffle算子(例如reduceByKey,sortByKey)
      该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的  磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经 优化的HashShuffleManager来说,shuffle read的性能会更好!
          而该机制与普通So rtShuffleManager运行机制的不同在于:
          第一:磁盘写机制不同;
          第二:不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行    数据的排序操作,也就节省掉了这部分的性能开销
7.spark-submit的三种提交方式?
  本地模式 & spark集群模式 &   yarn集群模式
8.spark中的共享变量?
  如果没有广播变量,那么数据在每个task都会有一份,这样会大大增加数据量,使用共享变量,只在一个executor保   存一份,里面的所有task共享该变量,减轻了数据量
9.

sparkCore (RDD)

1.mllib支持的算法?
  分类、聚类、回归、协同过滤
2.创建初始化RDD有哪几种方式?
  【1】使用并行化集合创建:sc.parallelize(Array(,,))
  【2】读取数据文件创建:sc.textFile("/)
3.RDD的算子分类,举出常用的?
  transformation:根据数据集创建一个新的数据集,返回一个新的RDD
  action:对RDD结果计算后返回一个value值给驱动程序
  transformation:map、flatMap、fliter,join、union、groupByKey、sortByKey、reduceByKey
  action:reduce、collect、count、first、take、foreach、saveAsTextFile
4.RDD的依赖分类?
  窄依赖:父RDD的一个partition最多只能被子RDD的一个partition依赖
  宽依赖:父RDD的一个partition可以被多个子RDD的partition依赖
5.RDD的Lineage(血统)作用?
  会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢       失的数据分区
7.介绍一下DAG?
  DAG,有向无环图,它是由原始RDD经过一系列转换形成的,根据RDD之间依赖关系的将DAG划分为不同的Stage
8.DAGSchedualer换分Stage的过程?
  对于窄依赖,partition的转换处理在一个Stage中完成计算。
  对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,
  因此宽依赖是划分Stage的依据!!!!!!!!!!!!!!!!!!!!!!!!!!!
  过程:
    【1】从最后一个rdd开始往前推,把当前的rdd加入到一个stage中,这个stage就是最后一个stage
    【2】遇到窄依赖就把该rdd加入到本stage,遇到宽依赖,这个时候就从宽依赖切开,最后一个stage就结束了
    【3】又重新分配一个stage,按照上面的规则继续往前推,一直到最开始的rdd,整个stage才算划分完成了
9.RDD的两种缓存方法 cache 和 pesist 的区别?
  【1】cache 和 persist 都是用于将一个 RDD 进行缓存的,这样在之后使用的过程中就不需要重新计算了,可以大        大节省程序运行时间
  【2】cache 只有一个默认的缓存级别 MEMORY_ONLY ,cache底层还是调用了 persist,而 persist 可以根据情        况设置其它的缓存级别
10.RDD详解------------------------------------------------------------------------------------
   RDD是一个不可变的,可分区的弹性分布式数据集!!!!
       不可变:一个文件
       可分区:以分区形式分布在不同的节点,不同分区的数据保存在不同的文件中
   RDD:弹性、分布式、数据集的解释?
       弹性:根据数据大小和内存情况,可以将RDD存储到内存或者磁盘
       分布式:RDD的分区分布在不同的节点上
       数据集:数据的集合
   RDD的高容错性解释?
       因为RDD前后是具有依赖关系的,就像RDD链(lineage),如果某个RDD的数据丢失,那么可以利用它依赖的前        面的RDD进行重新计算来获取,不需要从头开始计算!
   RDD的算子构成?
       Transformation & Action
       前者是lazy懒加载,调用不会立即执行,而只有遇到action操作时需要返回给驱动程序一个值的时候才会执行
   创建初始化RDD的两种方式?
       1.并行化集合创建初始化RDD(sc.parralize)
       2.读取数据文件创建初始化RDD(sc.textFile)
   RDD的五种特性?
       1.A list of partitions
         RDD是一个由多个partition组成的的list分区列表
       2.A function for computing each split
         RDD的每个partition上面都会有function,也就是函数应用
       3.A list of dependencies on other RDDs
         RDD会记录它的依赖 ,为了容错,也就是说在内存中的RDD操作时出错或丢失会进行重算
       4.Optionally,a Partitioner for Key-value RDDs
         可选项,如果RDD里面存的数据是key-value形式,则可以传递一个自定义的Partitioner进行重新分区
         默认的是HashPartitioner.必须是key-value形式,如reduceByKey,countByKey,SortByKey,union
       5.Optionally, a list of preferred locations to compute each split on
         最优的位置去计算,也就是数据的本地性
  RDD的缓存机制?
       为什么要进行缓存?
       在执行action算子之后,内存中的RDD就会被清除,下一次再执行action的时候,又会从读取文件开始执行          transformation,这样无疑会增加时间,降低效率。而一旦使用缓存的话,当action算子执行之后,就会将之        前的RDD进行缓存,下一次再执行action操作的时候就会从缓存中读取RDD数据,速度显然会很快。
       缓存的两种方式?
       persist和cache
       cache是persist的简写形式, 底层还是调用persist,而且cache方法缓存策略默认只为MEMORY_ONLY,            persst可以设置其他的缓存策略
 几种算子之间的比较?
      map和mapPartition之间的区别?
          map是作用于每一个元素的
          mapPartition是作用于整个分区的,虽然速度快,但是当分区很大的时候,就会导致内存溢出(OOM) -out of memory
      join和cogroup的区别?
          当出现相同Key时, join会出现笛卡尔积, 而cogroup的处理方式不同,没有的仍会出现,只是为空
      collect和foreach区别?
          两者都是遍历。
          collect是将集群中的数据拉到客户端进行遍历,会增加网络开销
          foreach是直接在集群中进行遍历,速度较快
      repartition和coalesce的区别?
          repartition可以增加和减少分区
          coalesce只能减少分区
          在读取文件或者使用并行集合创建RDD的时候可以指定分区的,也有默认的分区(2)
          sc.parallize(list,3)
          sc.textFile("D:\\Develop\\spark\\words.txt",3)
11.介绍一下RDD的容错机制checkpoint的原理?
    当RDD使用cache机制从内存中读取数据,如果数据没有读到,会使用checkpoint机制读取数据。此时如果没有       checkpoint机制,那么就需要找到父RDD重新计算数据了,因此checkpoint是个很重要的容错机制。checkpoint     就是对于一个RDD chain  ,如果后面需要反复使用某些中间结果RDD,可能因为一些故障导致该中间数据丢失,     那么就可以针对该RDD启动checkpoint机制,使用checkpoint首先需要调用sparkContext的setCheckpoint方       法,设置一个容错文件系统目录,比如hdfs,然后对RDD调用checkpoint方法。之后在RDD所处的job运行结束后,     会启动一个单独的job来将checkpoint过的数据写入之前设置的文件系统持久化,进行高可用。所以后面的计算在     使用该RDD时,如果数据丢失了,但是还是可以从它的checkpoint中读取数据,不需要重新计算.
12.cache或persist与checkpoint的区别?
   【1】cache或persist持久化只是将数据保存在BlockManager中,但是其lineage是不变的,但是checkpoint执行         完后,rdd已经没有依赖的RDD,只有一个checkpointRDD,RDD的lineage发生了改变
   【2】persist或者cache持久化的数据丢失的可能性更大,因为可能磁盘或内存被清理,但是checkpoint的数据通         常保存到hdfs上,高可靠,高容错,所以checkpiont是更可靠的数据持久化

sparkSql

1.创建DataFrame的几种方式?
  【1】可以通过已经存在的RDD
  【2】结构化文件
  【3】外部数据库
  【4】Hive表创建
2.创建DataSet有哪些方式?
  【1】sparkSession.createDataSet(已存在集合)
  【2】df.as[样例类]
  【3】sparkSession.createDataSet(已存在RDD)
  【4】集合.toDS
3.DataFrame和DataSet的相互转换?
  df.as[ElementType]      DF->DS
  ds.toDF()               DS->DF
4.RDD,DataFrame、DataSet 的比较?
  共同点:
  【1】三种都是spark平台下的弹性分布式数据集,为处理超大型数据提供便利
  【2】三者都是惰性机制,执行transform操作时不会立即执行,执行Action时才会执行
  【3】三者都会根据spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
  区别:
  【1】RDD是强类型的,编译时检查类型,不知道RDD中的具体数据类型,只知道是对象类型
  【2】DataFrame不是强类型的,不是编译时检测类型,具有schema元数据信息,知道里面具体的字段和类型,很容        易在提交运行时因类型错误而导致任务失败
  【3】DataSet是强类型的,具备RDD和DataFrame的双重优点,即可以在编译时检查类型,也具有schema元数据信息,而且是面向对象的编程接口
  【4】DataFrame可以叫DataSet[Row],是DataSet的子集,spark2.0之后,两者进行了统一
5.sparkSql是spark处理结构化数据的模块,具有哪两种编程风格?
  DSL风格 和 SQL风格(用的比较多)
  DSL:DF的相关方法-show、select、filter、count、groupBy...
  SQL: sparkSession.sql("sql语句"),返回DF
6.编写程序实现RDD转换dataFream的两种方式?
  第一种方法是通过反射推断Schema,推导包含某种类型的RDD,通过反射将其转换为指定类型的DataFrame,适用于提前知道case class
  第二种方法通过StructType直接指定Schema,使用于当case class不能提前定义好时
7.在spark2.0之后,可以直接使用SparkSession来操作各种context,如SparkContext,HiveContex,SqlContext

sparkStreaming

1.DStream是Spark Streaming的基础抽象,是一系列连续的RDD来表示,每个RDD含有一段时间间隔内的数据
2.sparkStreaming的原理?
  输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流-准实时.
3.sparkSteaming计算流程?
  sparkStreaming是将流式计算分解成一系列短小的批处理作业。这里的批处理引擎是Spark Core,也就是把         sparkStreaming的输入数据按照batch size(如1秒)分成一段一段的数据,每一段数据都转换成Spark中的       RDD,然后将sparkStreaming中对DStream的Transformation操作变为针对Spark中对RDD的Transformation操     作,将RDD经过操作变成中间结果保存在内存中。整个流式计算根据业务的需求可以对中间的结果进行缓存或者存储     到外部设备.
4.flume整合sparkStreaming问题?
  【1】如何实现sparkStreaming读取flume中的数据
       sparkStreaming整合flume有2种模式,一种是拉模式,一种是推模式
       推模式:Flume将数据Push推给Spark Streaming
       拉模式:Spark Streaming从flume中Poll拉取数据(一般采用的就是这种模式,数据不易丢失)
       步骤:
       val sparkConf = new SparkConf().setAppName("xxx").setMaster("local[2]")
       val sc  = new SparkContext(sparkConf)
       //Seconds():多久时间间隔一个批次(所示说SparkStreamig是准实时的,时间间隔批次进行处理)
       val ssc = new StreamingContext(sc,Seconds(5))
       拉模式: val pollStream = FlumeUtils.createPollingStream(ssc,"node1",8888)
       推模式: val pushStream = FlumeUtils.createStream(ssc,"192.168.23.41",8888,StorageLevel.MEMORY_AND_DISK_SER_2)
  【2】如何保证数据完整,稳定?
       设置checkpoint目录
        ssc.checkpoint("./flume")
5.kafka整合sparkStreaming问题?
 【1】在Spark1.3版本后,KafkaUtils里面提供了两个创建DStream的方法?
      (1)KafkaUtils.createDstream---采用kafka的java高级api,基于receiver(不推荐,消息可能重复消费)
      (2)KafkaUtils.createDirectStream---采用kafka的java低级api
        
 【2】采用低级api和采用高级api(基于receiver)的区别?
        (1)基于高级api的:
        通过这种方式实现,刚开始的时候系统正常运行,没有发现问题,但是如果系统异常重新启动sparkstreaming程序后,发现程序会重复处理已经处理过的数据,这种基于receiver的方式,是使用Kafka的高级API,topic的offset偏移量在ZooKeeper中。这是消费Kafka数据的传统方式。
这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据只被处理一次,可能会处理两次!
因为Spark和ZooKeeper之间可能是不同步的,当sparkStreaming消费过kafka中的数据之后,zookeeper还没来得及更新其中kafka消息的offset就发生了故障,那么下一次启动sparkStreaming时就会从新消费kafka的该消息!
官方现在也已经不推荐这种整合方式,我们使用官网推荐的第二种方式kafkaUtils的createDirectStream()方式。
        (2)基于低级api的:
        这种方式不同于Receiver接收数据,它定期地从kafka的topic下对应的partition中查询最新的偏移量,再根据偏移量范围在每个batch里面处理数据,Spark通过调用kafka简单的消费者Api(低级api)读取一定范围的数据!

###########相比基于Receiver方式有几个优点: 
A、简化并行
   不需要创建多个kafka输入流,然后union它们,sparkStreaming将会创建和kafka分区数相同的rdd的分区数,而    且会从kafka中并行读取数据,spark中RDD的分区数和kafka中的topic分区数是一一对应的关系
B、高效
   第一种实现数据的零丢失是将数据预先保存在WAL中,会复制一遍数据,会导致数据被拷贝两次,第一次是接受        kafka中topic的数据,另一次是写到WAL中。而没有receiver的这种方式消除了这个问题
C、恰好一次语义(Exactly-once-semantics)
   Receiver读取kafka数据是通过kafka高层次api把偏移量写入zookeeper中,虽然这种方法可以通过数据保存在WAL    中保证数据不丢失,但是可能会因为sparkStreaming和ZK中保存的偏移量不一致而导致数据被消费了多次。EOS通    过实现kafka低层次api,偏移量仅仅被ssc保存在checkpoint中,消除了zk和ssc偏移量不一致的问题。缺点是无    法使用基于zookeeper的kafka监控工具

原来公司的问题

1.集群服务器大小
  管理服务器:
集团IT集采模型4-2,4路8核CPU,128G内存,2*3TBSATA硬盘,4千兆网口,集成RAID卡,DVD光驱,冗余电 源,1个管理口,含操作系统(4台)
 存储服务器:
集团IT集采模型2-4,2路8核CPU,64G内存,12*3TBSATA硬盘,4千兆网口,集成RAID卡,DVD光驱,冗余电源,1个管理口,含操作系统(6台)
2.公司环境用的CDH还是开源(Hadoop)?---当然是CDH
  【1】联网安装、升级,非常方便
  【2】自动下载依赖软件包,比如要安装Hive,则会级联下载、安装Hadoop
  【3】Hadoop生态系统包自动匹配,不需要你寻找与当前Hadoop匹配的Hbase,Flume,Hive等软件,Yum/Apt会根据
      当前安装Hadoop版本自动寻找匹配版本的软件包,并保证兼容性
  【4】自动创建相关目录并软链到合适的地方(如conf和logs等目录);自动创建hdfs,mapred用户,hdfs用户是HD
      FS的最高权限用户,mapred用户则负责mapreduce执行过程中相关目录的权限
3.

 

java基础

1.String,StringBuffer,StringBuilder的比较?

 String 是只读字符串,也就意味着 String 引用的字符串内容是不能被改变的  StringBuffer/StringBuilder 表示的字符串对象可以直接进行修改  StringBuilder 是 jdk1.5 中引入的,它和 StringBuffer 的方法完全相同,区别在于它是在单线程环境下使用 的,因为它的所有方法都没有被 synchronized 修饰,因此它的效率理论上也比 StringBuffer 要高

2.java的基本数据类型?

 byte char short int long float double boolean  1 2 2 4 8 4 8 1 (字节数)

3.字符串和基本数据类型的转换?

 字符串->基本数据类型:包装类.parseXXX(String) 或 .valueOf(String)即可返回相应基本类型或包装类型  基本数据类型->字符串:使用"+"拼接"" 或者 调用String的valueOf反法

4.List 的三个子类的特点?

 ArrayList 底层结构是可变数组,底层增删慢,查询快。  LinkedList 底层结构是双向链表型的,增删快,查询慢。  voctor 底层结构是数组 线程安全的,增删慢,查询慢。

5.List 和 Map、Set 的区别?

 List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;  List 中存储的数据是有顺序,并且允许重复;  Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的;  Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置是固定的 (Set 集合根据 hashcode 进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的);

Flume+Hadoop+Hive的离线分析系统基本架构.

离线分析系统的结构图 

 整个离线分析的总体架构就是使用Flume从FTP服务器上采集日志文件,并存储在Hadoop HDFS文件系统上,再接着用Hadoop的mapreduce清洗日志文件,最后使用HIVE构建数据仓库做离线分析。任务的调度使用Shell脚本完成,当然大家也可以尝试一些自动化的任务调度工具,比如说AZKABAN或者OOZIE等。

 分析所使用的点击流日志文件主要来自Nginx的access.log日志文件,需要注意的是在这里并不是用Flume直接去生产环境上拉取nginx的日志文件,而是多设置了一层FTP服务器来缓冲所有的日志文件,然后再用Flume监听FTP服务器上指定的目录并拉取目录里的日志文件到HDFS服务器上(具体原因下面分析)。从生产环境推送日志文件到FTP服务器的操作可以通过Shell脚本配合Crontab定时器来实现。

1.网站点击流数据**

 一般在WEB系统中,用户对站点的页面的访问浏览,点击行为等一系列的数据都会记录在日志中,每一条日志记录就代表着上图中的一个数据点;而点击流数据关注的就是所有这些点连起来后的一个完整的网站浏览行为记录,可以认为是一个用户对网站的浏览session。比如说用户从哪一个外站进入到当前的网站,用户接下来浏览了当前网站的哪些页面,点击了哪些图片链接按钮等一系列的行为记录,这一个整体的信息就称为是该用户的点击流记录。这篇文章中设计的离线分析系统就是收集WEB系统中产生的这些数据日志,并清洗日志内容存储分布式的HDFS文件存储系统上,接着使用离线分析工具HIVE去统计所有用户的点击流信息。

 本系统中我们采用Nginx的access.log来做点击流分析的日志文件。access.log日志文件的格式如下:

 样例数据格式:

 124.42.13.230 - - [18/Sep/2013:06:57:50 +0000] "GET /shoppingMall?ver=1.2.1 HTTP/1.1" 200 7200 "http://www.baidu.com.cn" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; BTRS101170; InfoPath.2; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727)"

 格式分析:

 1、 访客ip地址:124.42.13.230

 2、访客用户信息: - -

 3、请求时间:[18/Sep/2013:06:57:50 +0000]

 4、请求方式:GET

 5、请求的url:/shoppingMall?ver=1.10.2

 6、请求所用协议:HTTP/1.1

 7、响应码:200

 8、返回的数据流量:7200

 9、访客的来源url:http://www.baidu.com.cn

 10、访客所用浏览器:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; BTRS101170; InfoPath.2; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727)

2. 收集用户数据

 网站会通过前端JS代码或服务器端的后台代码收集用户浏览数据并存储在网站服务器中。一般运维人员会在离线分析系统和真实生产环境之间部署FTP服务器,并将生产环境上的用户数据每天定时发送到FTP服务器上,离线分析系统就会从FTP服务上采集数据而不会影响到生产环境。  采集数据的方式有多种,一种是通过自己编写shell脚本或Java编程采集数据,但是工作量大,不方便维护,另一种就是直接使用第三方框架去进行日志的采集,一般第三方框架的健壮性,容错性和易用性都做得很好也易于维护。本文采用第三方框架Flume进行日志采集,Flume是一个分布式的高效的日志采集系统,它能把分布在不同服务器上的海量日志文件数据统一收集到一个集中的存储资源中,Flume是Apache的一个顶级项目,与Hadoop也有很好的兼容性。不过需要注意的是Flume并不是一个高可用的框架,这方面的优化得用户自己去维护。  Flume的agent是运行在JVM上的,所以各个服务器上的JVM环境必不可少。每一个Flume agent部署在一台服务器上,Flume会收集web server 产生的日志数据,并封装成一个个的事件发送给Flume Agent的Source,Flume Agent Source会消费这些收集来的数据事件并放在Flume Agent Channel,Flume Agent Sink会从Channel中收集这些采集过来的数据,要么存储在本地的文件系统中要么作为一个消费资源分发给下一个装在分布式系统中其它服务器上的Flume进行处理。Flume提供了点对点的高可用的保障,某个服务器上的Flume Agent Channel中的数据只有确保传输到了另一个服务器上的Flume Agent Channel里或者正确保存到了本地的文件存储系统中,才会被移除。

本系统中每一个FTP服务器以及Hadoop的name node服务器上都要部署一个Flume Agent;FTP的Flume Agent采集Web Server的日志并汇总到name node服务器上的Flume Agent,最后由hadoop name node服务器将所有的日志数据下沉到分布式的文件存储系统HDFS上面。

 需要注意的是Flume的Source在本文的系统中选择的是Spooling Directory Source,而没有选择Exec Source,因为当Flume服务down掉的时候Spooling Directory Source能记录上一次读取到的位置,而Exec Source则没有,需要用户自己去处理,当重启Flume服务器的时候如果处理不好就会有重复数据的问题。当然Spooling Directory Source也是有缺点的,会对读取过的文件重命名,所以多架一层FTP服务器也是为了避免Flume“污染”生产环境。Spooling Directory Source另外一个比较大的缺点就是无法做到灵活监听某个文件夹底下所有子文件夹里的所有文件里新追加的内容。关于这些问题的解决方案也有很多,比如选择其它的日志采集工具,像logstash等

 FTP服务器上的Flume配置文件如下:

这里有几个参数需要说明,Flume Agent Source可以通过配置deserializer.maxLineLength这个属性来指定每个Event的大小,默认是每个Event是2048个byte。Flume Agent Channel的大小默认等于于本地服务器上JVM所获取到的内存的80%,用户可以通过byteCapacityBufferPercentage和byteCapacity两个参数去进行优化。  需要特别注意的是FTP上放入Flume监听的文件夹中的日志文件不能同名,不然Flume会报错并停止工作,最好的解决方案就是为每份日志文件拼上时间戳。

 在Hadoop服务器上的配置文件如下:** **

round, roundValue,roundUnit三个参数是用来配置每10分钟在hdfs里生成一个文件夹保存从FTP服务器上拉取下来的数据。

 Troubleshooting

** 使用Flume拉取文件到HDFS中会遇到将文件分散成多个1KB-5KB的小文件的问题 **

 需要注意的是如果遇到Flume会将拉取过来的文件分成很多份1KB-5KB的小文件存储到HDFS上,那么很可能是HDFS Sink的配置不正确,导致系统使用了默认配置。spooldir类型的source是将指定目录中的文件的每一行封装成一个event放入到channel中,默认每一行最大读取1024个字符。在HDFS Sink端主要是通过rollInterval(默认30秒), rollSize(默认1KB), rollCount(默认10个event)3个属性来决定写进HDFS的分片文件的大小。rollInterval表示经过多少秒后就将当前.tmp文件(写入的是从channel中过来的events)下沉到HDFS文件系统中,rollSize表示一旦.tmp文件达到一定的size后,就下沉到HDFS文件系统中,rollCount表示.tmp文件一旦写入了指定数量的events就下沉到HDFS文件系统中。

** 使用Flume拉取到HDFS中的文件格式错乱

 这是因为HDFS Sink的配置中,hdfs.writeFormat属性默认为“Writable”会将原先的文件的内容序列化成HDFS的格式,应该手动设置成hdfs.writeFormat=“text”; 并且hdfs.fileType默认是“SequenceFile”类型的,是将所有event拼成一行,应该该手动设置成hdfs.fileType=“DataStream”,这样就可以是一行一个event,与原文件格式保持一致

使用Mapreduce清洗日志文件**

当把日志文件中的数据拉取到HDFS文件系统后,使用Mapreduce程序去进行日志清洗

第一步,先用Mapreduce过滤掉无效的数据

第二步,根据访问记录生成相应的Session**信息记录,假设Session的过期时间是30分钟**

第二次清理出来的Session信息结构如下:

时间

IP

SessionID

请求页面URL

Referal URL

2015-05-30 19:38:00

192.168.12.130

Session1

/blog/me

www.baidu.com

2015-05-30 19:39:00

192.168.12.130

Session1

/blog/me/details

www.mysite.com/blog/me

2015-05-30 19:38:00

192.168.12.40

Session2

/blog/me

www.baidu.com

第三步,清洗第二步生成的Session信息,生成PageViews信息表

第三次日志清洗产生的PageViews数据结构如下图:

SessionID

IP

访问时间

访问页面

停留时间

第几步

Session1

192.168.12.130

2016-05-30 15:17:30

/blog/me

30000

1

Session1

192.168.12.130

2016-05-30 15:18:00

/blog/me/admin

30000

2

Session1

192.168.12.130

2016-05-30 15:18:30

/home

30000

3

Session2

192.168.12.150

2016-05-30 15:16:30

/products

30000

1

Session2

192.168.12.150

2016-05-30 15:17:00

/products/details

30000

2

第四步,再次清洗Session日志,并生成Visits信息表

第四次清洗日志产生的访问记录表结构如下图:

SessionID

访问时间

离开时间

第一次访问页面

最后一次访问的页面

访问的页面总数

IP

Referal

Session1

2016-05-30 15:17:00

2016-05-30 15:19:00

/blog/me

/blog/others

5

192.168.12.130

www.baidu.com

Session2

2016-05-30 14:17:00

2016-05-30 15:19:38

/home

/profile

10

192.168.12.140

www.178.com

Session3

2016-05-30 12:17:00

2016-05-30 15:40:00

/products

/detail

6

192.168.12.150

www.78dm.net

 以上就是要进行日志清洗的所有MapReduce程序,因为只是一个简单的演示,方法并没有做很好的抽象。

 MapReduce Troubleshooting**

 指定某个文件夹路径下所有文件作为mapreduce的输入参数的解决方案。 1.hdfs的文件系统中的路径是支持正则表达式的 ** 

2.使用.setInputDirRecursive(job,true)方法,然后指定文件夹路径  
  在分布式环境下如何设置每个用户的SessionID  可以使用UUID,UUID是分布式环境下唯一的元素识别码,它由日期和时间,时钟序列,机器识别码(一般为网卡MAC地址)三部分组成。这样就保证了每个用户的SessionID的唯一性。

HIVE建立数据仓库
使用MapReduce清洗完日志文件后,我们就开始使用Hive去构建对应的数据仓库并使用HiveSql对数据进行分析。而在本系统里,我们将使用星型模型来构建数据仓库的ODS(OperationalData Store)层。下面的命令我们可以通过启动Hive的hiveserver2服务器并使用beeline客户端进行操作或者直接写脚本去定时调度。

 PageViews数据分析 PageViews的事实表和维度表结构

 

 

 

使用HIVE在数据仓库中创建PageViews的贴源数据表:  >> create table pageviews(session string,ip string,requestdate string,requesttime string,visitpage string, staytime string,step string) comment ‘this is the table for pageviews’ partitioned by(inputDate string) clustered by(session) sorted by(requestdate,requesttime) into 4 buckets row format delimited fields terminated by ‘ ’;

 将HDFS中的数据导入到HIVE的PageViews贴源数据表中  >> load data inpath ‘/clickstream/pageviews’ overwrite into table pageviews partition(inputDate=‘2016-05-17’); 如果没有标示是在’Local‘本地文件系统中,则会去HDFS中加载数据

 根据具体的业务分析逻辑创建ODS层的PageViews事实表,并从PageViews的贴源表中导入数据

 这里根据请求的页面URL来分组(clustered)是为了方便统计每个页面的PV  >> create table ods_pageviews(session string,ip string,viewtime string,visitpage string, staytime string,step string) partitioned by(inputDate string) clustered by(visitpage) sorted by(viewtime) into 4 buckets row format delimited fields terminated by ‘ ’;

 >> insert into table ods_pageviews partition(inputDate='2016-05-17') select pv.session,pv.ip,concat(pv.requestdate,"-",pv.requesttime),pv.visitpage,pv.staytime,pv.step from pageviews as pv where pv.inputDate='2016-05-17';

 创建PageViews事实表的时间维度表并从当天的事实表里导入数据

 >>create table ods_dim_pageviews_time(time string,year string,month string,day string,hour string,minutes string,seconds string) partitioned by(inputDate String) clustered by(year,month,day) sorted by(time) into 4 buckets row format delimited fields terminated by ' ';

 >> insert overwrite table ods_dim_pageviews_time partition(inputDate='2016-05-17') select distinct pv.viewtime, substring(pv.viewtime,0,4),substring(pv.viewtime,6,2),substring(pv.viewtime,9,2),substring(pv.viewtime,12,2),substring(pv.viewtime,15,2),substring(pv.viewtime,18,2) from ods_pageviews as pv;

 创建PageViews事实表的URL维度表并从当天的事实表里导入数据  >> create table ods_dim_pageviews_url(visitpage string,host string,path string,query string) partitioned by(inputDate string) clustered by(visitpage) sorted by(visitpage) into 4 buckets row format delimited fields terminated by ' ';

 >> insert into table ods_dim_pageviews_url partition(inputDate='2016-05-17') select distinct pv.visitpage,b.host,b.path,b.query from pageviews pv lateral view parse_url_tuple(concat('https://localhost',pv.visitpage),'HOST','PATH','QUERY') b as host,path,query;

 查询每天PV总数前20的页面  >> select op.visitpage as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' group by op.visitpage sort by num desc limit 20;

 运行结果:

 Visits数据分析  页面具体访问记录Visits的事实表和维度表结构

 使用HIVE在数据仓库中创建Visits信息的贴源数据表:  >> create table visitsinfo(session string,startdate string,starttime string,enddate string,endtime string,entrypage string,leavepage string,viewpagenum string,ip string,referal string) partitioned by(inputDate string) clustered by(session) sorted by(startdate,starttime) into 4 buckets row format delimited fields terminated by ' ';

 将HDFS中的数据导入到HIVE的Visits信息贴源数据表中  >> load data inpath '/clickstream/visitsinfo' overwrite into table visitsinfo partition(inputDate='2016-05-18');

 

 根据具体的业务分析逻辑创建ODS层的Visits事实表,并从visitsinfo的贴源表中导入数据  >> create table ods_visits(session string,entrytime string,leavetime string,entrypage string,leavepage string,viewpagenum string,ip string,referal string) partitioned by(inputDate string) clustered by(session) sorted by(entrytime) into 4 buckets row format delimited fields terminated by ' ';

 >> insert into table ods_visits partition(inputDate='2016-05-18') select vi.session,concat(vi.startdate,"-",vi.starttime),concat(vi.enddate,"-",vi.endtime),vi.entrypage,vi.leavepage,vi.viewpagenum,vi.ip,vi.referal from visitsinfo as vi where vi.inputDate='2016-05-18';

 创建Visits事实表的时间维度表并从当天的事实表里导入数据

 >>create table ods_dim_visits_time(time string,year string,month string,day string,hour string,minutes string,seconds string) partitioned by(inputDate String) clustered by(year,month,day) sorted by(time) into 4 buckets row format delimited fields terminated by ' ';

 将“访问时间”和“离开时间”两列的值合并后再放入时间维度表中,减少数据的冗余

insert overwrite table ods_dim_visits_time partition(inputDate='2016-05-18') select distinct ov.timeparam, substring(ov.timeparam,0,4),substring(ov.timeparam,6,2),substring(ov.timeparam,9,2),substring(ov.timeparam,12,2),substring(ov.timeparam,15,2),substring(ov.timeparam,18,2) from (select ov1.entrytime as timeparam from ods_visits as ov1 union select ov2.leavetime as timeparam from ods_visits as ov2) as ov;

 

 创建visits事实表的URL维度表并从当天的事实表里导入数据  >> create table ods_dim_visits_url(pageurl string,host string,path string,query string) partitioned by(inputDate string) clustered by(pageurl) sorted by(pageurl) into 4 buckets row format delimited fields terminated by ' ';

 将每个session的进入页面和离开页面的URL合并后存入到URL维度表中  >>insert into table ods_dim_visits_url partition(inputDate='2016-05-18') select distinct ov.pageurl,b.host,b.path,b.query from (select ov1.entrypage as pageurl from ods_visits as ov1 union select ov2.leavepage as pageurl from ods_visits as ov2 ) as ov lateral view parse_url_tuple(concat('https://localhost',ov.pageurl),'HOST','PATH','QUERY') b as host,path,query;

 将每个session从哪个外站进入当前网站的信息存入到URL维度表中  >>insert into table ods_dim_visits_url partition(inputDate='2016-05-18') select distinct ov.referal,b.host,b.path,b.query from ods_visits as ov lateral view parse_url_tuple(ov.referal,'HOST','PATH','QUERY') b as host,path,query;

 统计每个页面的跳出人数(事实上真正有价值的统计应该是统计页面的跳出率,但为了简单示范,作者在这里简化成统计跳出人数)  >> select ov.leavepage as jumpPage, count(*) as jumpNum from ods_visits as ov group by ov.leavepage order by jumpNum desc;

 

 业务页面转换率分析(漏斗模型)  Hive在创建表的时候无法实现某个字段自增长的关键字,得使用自定义函数(user-defined function)UDF来实现相应的功能。在查询的时候可以使用row_number()来显示行数,不过必须要在complete mode下才能使用,所以可以使用row_number() 函数配合开窗函数over(),具体示例如下。 为简单起见,这里我们创建一个临时表,并手动在里面插入要查看的业务页面链接以及该页面的PV总数,通过这几个参数来计算业务页面之间的转换率,也就是所谓的漏斗模型。 假设我们有“/index” -> “/detail” -> “/createOrder” ->”/confirmOrder” 这一业务页面转化流程

 首先我们要创建业务页面的PV的临时信息表,临时表和里面的数据会在session结束的时候清理掉

 create temporary table transactionpageviews(url string,views int) row format delimited fields terminated by ' ';

 先统计业务页面的总PV然后按转换步骤顺序插入每个页面的PV信息到transactionpageviews表中  >> insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/index' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/detail' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/createOrder' group by opurl.path;

insert into table transactionpageviews select opurl.path as path,count(*) as num from ods_pageviews as op join ods_dim_pageviews_url as opurl on (op.visitpage = opurl.visitpage) join ods_dim_pageviews_time as optime on (optime.time = op.viewtime) where optime.year='2013' and optime.month='09' and optime.day='19' and opurl.path='/confirmOrder' group by opurl.path;

 计算业务页面之间的转换率

select row_number() over() as rownum,a.url as url, a.views as pageViews,b.views as lastPageViews,a.views/b.views as transferRation from (select row_number() over() as rownum,views,url from transactionpageviews) as a left join (select row_number() over() as rownum,views,url from transactionpageviews) as b on (a.rownum = b.rownum-1 );

 

Shell脚本+Crontab定时器执行任务调度  执行initialEnv.sh脚本初始化系统环境,为了简单测试,作者只启动了单台服务器,下面的脚本是建立在Hadoop的standalone单节点模式,并且Hive也装在Hadoop服务器上

执行dataAnalyseTask.sh脚本,先启动MapReduce程序去清洗当日的日志信息,随后使用Hive去构建当日的ODS数据。需要注意的是,本脚本是建立在ODS层中事实表和维度表已经创建完毕的基础上去执行,所以脚本中不会有创建事实表和维度表的HIVE语句(创建语句见上一个章节的内容),并且为了节省篇幅,只列出了PageViews数据分析的脚本部分。

 

创建crontab文件,指定每天的凌晨01点整执行dataAnalyseTask.sh脚本,该脚本执行“使用MapReduce清理日志文件”和“使用HiveSql构建分析ODS层数据”两项任务,并将用户自定义的crontab文件加入到定时器中

2.2.** 流量分析常见分类 **

指标是网站分析的基础,用来记录和衡量访问者在网站自的各种行为。比如我们经常说的流量就是一个网站指标,它是用来衡量网站获得的访问量。在进行流量分析之前,我们先来了解一些常见的指标。

骨灰级指标

IP:1 天之内,访问网站的不重复 IP 数。一天内相同 IP 地址多次访问网站只被计算 1 次。曾经 IP 指标可以用来表示用户访问身份,目前则更多的用来获取访问者的地理位置信息。

PageView 浏览量: 即通常说的 PV 值,用户每打开 1 个网站页面,记录 1 个PV。用户多次打开同一页面 PV 累计多次。通俗解释就是页面被加载的总次数。

Unique PageView: 1 天之内,访问网站的不重复用户数(以浏览器 cookie 为依据),一天内同一访客多次访问网站只被计算 1 次。

基础级指标

访问次数:访客从进入网站到离开网站的一系列活动记为一次访问,也称会话(session),1 次访问(会话)可能包含多个 PV。

网站停留时间:访问者在网站上花费的时间。

页面停留时间:访问者在某个特定页面或某组网页上所花费的时间。

复合级指标

人均浏览页数:平均每个独立访客产生的 PV。人均浏览页数=浏览次数/独立访客。体现网站对访客的吸引程度。

跳出率:指某一范围内单页访问次数或访问者与总访问次数的百分比。其中跳出指单页访问或访问者的次数,即在一次访问中访问者进入网站后只访问了一个页面就离开的数量。

退出率:指某一范围内退出的访问者与综合访问量的百分比。其中退出指访问者离开网站的次数,通常是基于某个范围的。有了上述这些指标之后,就能结合业务进行各种不同角度的分类分析,主要

是以下几大方面:

基础分析(PV,IP,UV)

趋势分析:根据选定的时段,提供网站流量数据,通过流量趋势变化形态,为您分析网站访客的访问规律、网站发展状况提供参考。

对比分析:根据选定的两个对比时段,提供网站流量在时间上的纵向对比报表,帮您发现网站发展状况、发展规律、流量变化率等。

当前在线:提供当前时刻站点上的访客量,以及最近 15 分钟流量、来源、受访、访客变化情况等,方便用户及时了解当前网站流量状况。

访问明细:提供最近 7 日的访客访问记录,可按每个 PV 或每次访问行为(访客的每次会话)显示,并可按照来源、搜索词等条件进行筛选。 通过访问明细,用户可以详细了解网站流量的累计过程,从而为用户快速找出流量变动原因提供最原始、最准确的依据。

 

 

来源分析

来源分类:提供不同来源形式(直接输入、搜索引擎、其他外部链接、站内来源)、不同来源项引入流量的比例情况。通过精确的量化数据,帮助用户分析什么类型的来路产生的流量多、效果好,进而合理优化推广方案。

搜索引擎:提供各搜索引擎以及搜索引擎子产品引入流量的比例情况。

搜索词:提供访客通过搜索引擎进入网站所使用的搜索词,以及各搜索词引入流量的特征和分布。帮助用户了解各搜索词引入流量的质量,进而了解访客的兴趣关注点、网站与访客兴趣点的匹配度,为优化 SEO(搜索引擎优化)方案及 SEM(搜索引擎营销)提词方案提供详细依据。

最近 7 日的访客搜索记录,可按每个 PV 或每次访问行为(访客的每次会话)显示,并可按照访客类型、地区等条件进行筛选。为您搜索引擎优化提供最详细的原始数据。

来路域名:提供具体来路域名引入流量的分布情况,并可按“社会化媒体”、“搜索引擎”、

“邮箱”等网站类型对来源域名进行分类。 帮助用户了解哪类推广渠道产生的流量多、效果好,进而合理优化网站推广方案。

来路页面:提供具体来路页面引入流量的分布情况。 尤其对于通过流量置换、包广告位等方式从其他网站引入流量的用户,该功能可以方便、清晰地展现广告引入的流量及效果,为优化推广方案提供依据。

来源升降榜:提供开通统计后任意两日的 TOP10000 搜索词、来路域名引入流量的对比情况,并按照变化的剧烈程度提供排行榜。 用户可通过此功能快速找到哪些来路对网站流量的影响比较大,从而及时排查相应来路问题。

 

受访分析

受访域名:提供访客对网站中各个域名的访问情况。 一般情况下,网站不同域名提供的产品、内容各有差异,通过此功能用户可以了解不同内容的受欢迎程度以及网站运营成效。

受访页面:提供访客对网站中各个页面的访问情况。 站内入口页面为访客进入网站时浏览的第一个页面,如果入口页面的跳出率较高则需要关注并优化;站内出口页面为访客访问网站的最后一个页面,对于离开率较高的页面需要关注并优化。

受访升降榜:提供开通统计后任意两日的 TOP10000 受访页面的浏览情况对比,并按照变化的剧烈程度提供排行榜。 可通过此功能验证经过改版的页面是否有流量提升或哪些页面有巨大流量波动,从而及时排查相应问题。

热点图:记录访客在页面上的鼠标点击行为,通过颜色区分不同区域的点击热度;支持将一组页面设置为"关注范围",并可按来路细分点击热度。 通过访客在页面上的点击量统计,可以了解页面设计是否合理、广告位的安排能否获取更多佣金等。

用户视点:提供受访页面对页面上链接的其他站内页面的输出流量,并通过输出流量的高低绘制热度图,与热点图不同的是,所有记录都是实际打开了下一页面产生了浏览次数

(PV)的数据,而不仅仅是拥有鼠标点击行为。

 

 

 

 

 

访问轨迹:

提供观察焦点页面的上下游页面,了解访客从哪些途径进入页面,又流向了哪里。

 

通过上游页面列表比较出不同流量引入渠道的效果;通过下

 

游页面列表了解用户的浏览习惯,哪些页面元素、内容更吸引访客点击。

 

访客分析

地区运营商:提供各地区访客、各网络运营商访客的访问情况分布。 地方网站、下载站等与地域性、网络链路等结合较为紧密的网站,可以参考此功能数据,合理优化推广运营方案。

终端详情:提供网站访客所使用的浏览终端的配置情况。 参考此数据进行网页设计、开发,可更好地提高网站兼容性,以达到良好的用户交互体验。

新老访客:当日访客中,历史上第一次访问该网站的访客记为当日新访客;历史上已经访问过该网站的访客记为老访客。 新访客与老访客进入网站的途径和浏览行为往往存在差异。该功能可以辅助分析不同访客的行为习惯,针对不同访客优化网站,例如为制作新手导航提供数据支持等。

忠诚度:从访客一天内回访网站的次数(日访问频度)与访客上次访问网站的时间两个角度,分析访客对网站的访问粘性、忠诚度、吸引程度。 由于提升网站内容的更新频率、增强用户体验与用户价值可以有更高的忠诚度,因此该功能在网站内容更新及用户体验方面提供了重要参考。

活跃度:从访客单次访问浏览网站的时间与网页数两个角度,分析访客在网站上的活跃程度。 由于提升网站内容的质量与数量可以获得更高的活跃度,因此该功能是网站内容分析的关键指标之一。

 

转化路径分析

转化定义:

访客在您的网站完成了某项您期望的活动,记为一次转化,如注册、下载、购买。

目标示例:

·获得用户目标:在线注册、创建账号等。

·咨询目标:咨询、留言、电话等。

·互动目标:视频播放、加入购物车、分享等。

·收入目标:在线订单、付款等。

路径分析:

根据设置的特定路线,监测某一流程的完成转化情况,算出每步的转换率和流失率数据,

如注册流程,购买流程等。

 

1、 简答说一下hadoop的map-reduce编程模型

首先map task会从本地文件系统读取数据, 转换成key-value形式的键值对集合 使用的是hadoop内置的数据类型, 比如longwritable、 text等 将键值对集合输入mapper进行业务处理过程, 将其转换成需要的key-value在输出 之后会进行一个partition分区操作, 默认使用的是hashpartitioner, 可以通过重写hashpartitioner的getpartition方法来自定义分区规则 之后会对key进行进行sort排序, grouping分组操作将相同key的value合并分组输出, 在这里可以使用自定义的数据类型, 重写WritableComparator的 Comparator方法来自定义排序规则, 重写RawComparator的compara方法来自定义分组规则 之后进行一个combiner归约操作, 其实就是一个本地段的reduce预处理, 以减小后面shufle和reducer的工作量 reduce task会通过网络将各个数据收集进行reduce处理, 最后将数据保存或者显示, 结束整个job

2、 hadoop的TextInputFormat作用是什么, 如何自定义实现

InputFormat会在map操作之前对数据进行两方面的预处理 1是getSplits, 返回的是InputSplit数组, 对数据进行split分片, 每片交给map操作一次 2是getRecordReader, 返回的是RecordReader对象, 对每个split分片进行转换为key-value键值对格式传递给map 常用的InputFormat是TextInputFormat, 使用的是LineRecordReader对每个分片进行键值对的转换, 以行偏移量作为键, 行内容作为值 自定义类继承InputFormat接口, 重写createRecordReader和isSplitable方法 在createRecordReader中可以自定义分隔符

3、 hadoop和spark的都是并行计算, 那么他们有什么相同和区别

两者都是用mr模型来进行并行计算, hadoop的一个作业称为job, job里面分为map task和reduce task, 每个task都是在自己的进程中运行的, 当task结束 时, 进程也会结束 spark用户提交的任务成为application, 一个application对应一个sparkcontext, app中存在多个job, 每触发一次action操作就会产生一个job 这些job可以并行或串行执行, 每个job中有多个stage, stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的, 每个stage里面有多 个task, 组成taskset有TaskSchaduler分发到各个executor中执行, executor的生命周期是和app一样的, 即使没有job运行也是存在的, 所以task可以快速启 动读取内存进行计算 hadoop的job只有map和reduce操作, 表达能力比较欠缺而且在mr过程中会重复的读写hdfs, 造成大量的io操作, 多个job需要自己管理关系 spark的迭代计算都是在内存中进行的, API中提供了大量的RDD操作如join, groupby等, 而且通过DAG图可以实现良好的容错

4、 为什么要用flume导入hdfs, hdfs的构架是怎样的

flume可以实时的导入数据到hdfs中, 当hdfs上的文件达到一个指定大小的时候会形成一个文件, 或者超过指定时间的话也形成一个文件 文件都是存储在datanode上面的, namenode记录着datanode的元数据信息, 而namenode的元数据信息是存在内存中的, 所以当文件切片很小或者很多 的时候会卡死

5、 map-reduce程序运行的时候会有什么比较常见的问题

比如说作业中大部分都完成了, 但是总有几个reduce一直在运行 这是因为这几个reduce中的处理的数据要远远大于其他的reduce, 可能是因为对键值对任务划分的不均匀造成的数据倾斜 解决的方法可以在分区的时候重新定义分区规则对于value数据很多的key可以进行拆分、 均匀打散等处理, 或者是在map端的combiner中进行数据预处 理的操作

6、 简单说一下hadoop和spark的shuffle过程

hadoop: map端保存分片数据, 通过网络收集到reduce端 spark: spark的shuffle是在DAGSchedular划分Stage的时候产生的, TaskSchedule要分发Stage到各个worker的executor 减少shuffle可以提高性能 部分答案不是十分准确欢迎补充:-) ——-补充更新———

1、 Hive中存放是什么?

表。 存的是和hdfs的映射关系, hive是逻辑上的数据仓库, 实际操作的都是hdfs上的文件, HQL就是用sql语法来写的mr程序。

2、 Hive与关系型数据库的关系?

没有关系, hive是数据仓库, 不能和数据库一样进行实时的CURD操作。 是一次写入多次读取的操作, 可以看成是ETL工具。

3、 Flume工作机制是什么?

核心概念是agent, 里面包括source、 chanel和sink三个组件。 source运行在日志收集节点进行日志采集, 之后临时存储在chanel中, sink负责将chanel中的数据发送到目的地。 只有成功发送之后chanel中的数据才会被删除。 首先书写flume配置文件, 定义agent、 source、 chanel和sink然后将其组装, 执行flume-ng命令。

4、 Sqoop工作原理是什么?

hadoop生态圈上的数据传输工具。 可以将关系型数据库的数据导入非结构化的hdfs、 hive或者bbase中, 也可以将hdfs中的数据导出到关系型数据库或者文本文件中。 使用的是mr程序来执行任务, 使用jdbc和关系型数据库进行交互。 imp ort原理: 通过指定的分隔符进行数据切分, 将分片传入各个map中, 在map任务中在每行数据进行写入处理 没有reduce。 export原理: 根据要操作的表名生成一个Java类, 并读取其元数据信息和分隔符对非结构化的数据进行匹配, 多个map作业同时执行写入关系型数据 库

5、Hbase行健列族的概念, 物理模型, 表的设计原则?

行健: 是hbase表自带的, 每个行健对应一条数据。 列族: 是创建表时指定的, 为列的集合, 每个列族作为一个文件单独存储, 存储的数据都是字节数组, 其中的数据可以有很多, 通过时间戳来区 分。 物理模型: 整个hbase表会拆分为多个region, 每个region记录着行健的起始点保存在不同的节点上, 查询时就是对各个节点的并行查询, 当region很大 时使用.META表存储各个region的起始点, -ROOT又可以存储.META的起始点。 rowkey的设计原则: 各个列簇数据平衡, 长度原则、 相邻原则, 创建表的时候设置表放入regionserver缓存中, 避免自动增长和时间, 使用字节数组代 替string, 最大长度64kb, 最好16字节以内, 按天分表, 两个字节散列, 四个字节存储时分毫秒。 列族的设计原则: 尽可能少(按照列族进行存储, 按照region进行读取, 不必要的io操作) , 经常和不经常使用的两类数据放入不同列族中, 列族名 字尽可能短。

6、 Spark Streaming和Storm有何区别?

一个实时毫秒一个准实时亚秒, 不过storm的吞吐率比较低。

7、 mllib支持的算法?

大体分为四大类, 分类、 聚类、 回归、 协同过滤。

8、 简答说一下hadoop的map-reduce编程模型?

首先map task会从本地文件系统读取数据, 转换成key-value形式的键值对集合。 将键值对集合输入mapper进行业务处理过程, 将其转换成需要的key-value在输出。 之后会进行一个partition分区操作, 默认使用的是hashpartitioner, 可以通过重写hashpartitioner的getpartition方法来自定义分区规则。 之后会对key进行进行sort排序, grouping分组操作将相同key的value合并分组输出。 在这里可以使用自定义的数据类型, 重写WritableComparator的Comparator方法来自定义排序规则, 重写RawComparator的compara方法来自定义分组规 则。 之后进行一个combiner归约操作, 其实就是一个本地段的reduce预处理, 以减小后面shufle和reducer的工作量。 reduce task会通过网络将各个数据收集进行reduce处理, 最后将数据保存或者显示, 结束整个job。

9、 Hadoop平台集群配置、 环境变量设置?

zookeeper: 修改zoo.cfg文件, 配置dataDir, 和各个zk节点的server地址端口, tickTime心跳时间默认是2000ms, 其他超时的时间都是以这个为基础的整 数倍, 之后再dataDir对应目录下写入myid文件和zoo.cfg中的server相对应。 hadoop: 修改 hadoop-env.sh配置java环境变量 core-site.xml配置zk地址, 临时目录等 hdfs-site.xml配置nn信息, rpc和http通信地址, nn自动切换、 zk连接超时时间等 yarn-site.xml配置resourcemanager地址 mapred-site.xml配置使用yarn slaves配置节点信息 格式化nn和zk。 hbase: 修改 hbase-env.sh配置java环境变量和是否使用自带的zk hbase-site.xml配置hdfs上数据存放路径, zk地址和通讯超时时间、 master节点 regionservers配置各个region节点 zoo.cfg拷贝到conf目录下 spark: 安装Scala 修改spark-env.sh配置环境变量和master和worker节点配置信息 环境变量的设置: 直接在/etc/profile中配置安装的路径即可, 或者在当前用户的宿主目录下, 配置在.bashrc文件中, 该文件不用source重新打开shell窗 口即可, 配置在.bash_profile的话只对当前用户有效。

10、 Hadoop性能调优?

调优可以通过系统配置、 程序编写和作业调度算法来进行。 hdfs的block.size可以调到128/256(网络很好的情况下, 默认为64) 调优的大头: mapred.map.tasks、 mapred.reduce.tasks设置mr任务数(默认都是1) mapred.tasktracker.map.tasks.maximum每台机器上的最大map任务数 mapred.tasktracker.reduce.tasks.maximum每台机器上的最大reduce任务数 mapred.reduce.slowstart.completed.maps配置reduce任务在map任务完成到百分之几的时候开始进入 这个几个参数要看实际节点的情况进行配置, reduce任务是在33%的时候完成copy, 要在这之前完成map任务, (map可以提前完成) mapred.compress.map.output,mapred.output.compress配置压缩项, 消耗cpu提升网络和磁盘io 合理利用combiner 注意重用writable对象

11、 Hadoop高并发?

首先肯定要保证集群的高可靠性, 在高并发的情况下不会挂掉, 支撑不住可以通过横向扩展。 datanode挂掉了使用hadoop脚本重新启动。

12、 hadoop的TextInputFormat作用是什么, 如何自定义实现?

InputFormat会在map操作之前对数据进行两方面的预处理。 1是getSplits, 返回的是InputSplit数组, 对数据进行split分片, 每片交给map操作一次 。 2是getRecordReader, 返回的是RecordReader对象, 对每个split分片进行转换为key-value键值对格式传递给map。 常用的InputFormat是TextInputFormat, 使用的是LineRecordReader对每个分片进行键值对的转换, 以行偏移量作为键, 行内容作为值。 自定义类继承InputFormat接口, 重写createRecordReader和isSplitable方法 。 在createRecordReader中可以自定义分隔符。

13、 hadoop和spark的都是并行计算, 那么他们有什么相同和区别?

两者都是用mr模型来进行并行计算, hadoop的一个作业称为job, job里面分为map task和reduce task, 每个task都是在自己的进程中运行的, 当task结束 时, 进程也会结束。 spark用户提交的任务成为application, 一个application对应一个sparkcontext, app中存在多个job, 每触发一次action操作就会产生一个job。 这些job可以并行或串行执行, 每个job中有多个stage, stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的, 每个stage里面有多 个task, 组成taskset有TaskSchaduler分发到各个executor中执行, executor的生命周期是和app一样的, 即使没有job运行也是存在的, 所以task可以快速启 动读取内存进行计算。 hadoop的job只有map和reduce操作, 表达能力比较欠缺而且在mr过程中会重复的读写hdfs, 造成大量的io操作, 多个job需要自己管理关系。 spark的迭代计算都是在内存中进行的, API中提供了大量的RDD操作如join, groupby等, 而且通过DAG图可以实现良好的容错。

14、 为什么要用flume导入hdfs, hdfs的构架是怎样的?

flume可以实时的导入数据到hdfs中, 当hdfs上的文件达到一个指定大小的时候会形成一个文件, 或者超过指定时间的话也形成一个文件。 文件都是存储在datanode上面的, namenode记录着datanode的元数据信息, 而namenode的元数据信息是存在内存中的, 所以当文件切片很小或者很多 的时候会卡死。

15、 map-reduce程序运行的时候会有什么比较常见的问题?

比如说作业中大部分都完成了, 但是总有几个reduce一直在运行。 这是因为这几个reduce中的处理的数据要远远大于其他的reduce, 可能是因为对键值对任务划分的不均匀造成的数据倾斜。 解决的方法可以在分区的时候重新定义分区规则对于value数据很多的key可以进行拆分、 均匀打散等处理, 或者是在map端的combiner中进行数据预处 理的操作。

16、 简单说一下hadoop和spark的shuffle过程?

hadoop: map端保存分片数据, 通过网络收集到reduce端。 spark: spark的shuffle是在DAGSchedular划分Stage的时候产生的, TaskSchedule要分发Stage到各个worker的executor。 减少shuffle可以提高性能。

17、 RDD机制?

rdd分布式弹性数据集, 简单的理解成一种数据结构, 是spark框架上的通用货币。 所有算子都是基于rdd来执行的, 不同的场景会有不同的rdd实现类, 但是都可以进行互相转换。 rdd执行过程中会形成dag图, 然后形成lineage保证容错性等。 从物理的角度来看rdd存储的是block和node之间的映射。

18、 spark有哪些组件?

(1) master: 管理集群和节点, 不参与计算。 (2) worker: 计算节点, 进程本身不参与计算, 和master汇报。 (3) Driver: 运行程序的main方法, 创建spark context对象。 (4) spark context: 控制整个application的生命周期, 包括dagsheduler和task scheduler等组件。 (5) client: 用户提交程序的入口。

19、 spark工作机制?

用户在client端提交作业后, 会由Driver运行main方法并创建spark context上下文。 执行add算子, 形成dag图输入dagscheduler, 按照add之间的依赖关系划分stage输入task scheduler。 task scheduler会将stage划分为task set分发到各个节点的executor中执行。

20、 spark的优化怎么做?

通过spark-env文件、 程序中sparkconf和set property设置。 (1) 计算量大, 形成的lineage过大应该给已经缓存了的rdd添加checkpoint, 以减少容错带来的开销。 (2) 小分区合并, 过小的分区造成过多的切换任务开销, 使用repartition。

21、 kafka工作原理?

producer向broker发送事件, consumer从broker消费事件。 事件由topic区分开, 每个consumer都会属于一个group。 相同group中的consumer不能重复消费事件, 而同一事件将会发送给每个不同group的consumer。

22、 ALS算法原理?

答: 对于user-product-rating数据, als会建立一个稀疏的评分矩阵, 其目的就是通过一定的规则填满这个稀疏矩阵。 als会对稀疏矩阵进行分解, 分为用户-特征值, 产品-特征值, 一个用户对一个产品的评分可以由这两个矩阵相乘得到。 通过固定一个未知的特征值, 计算另外一个特征值, 然后交替反复进行最小二乘法, 直至差平方和最小, 即可得想要的矩阵。

23、 kmeans算法原理?

随机初始化中心点范围, 计算各个类别的平均值得到新的中心点。 重新计算各个点到中心值的距离划分, 再次计算平均值得到新的中心点, 直至各个类别数据平均值无变化。

24、 canopy算法原理?

根据两个阈值来划分数据, 以随机的一个数据点作为canopy中心。 计算其他数据点到其的距离, 划入t1、 t2中, 划入t2的从数据集中删除, 划入t1的其他数据点继续计算, 直至数据集中无数据。

25、 朴素贝叶斯分类算法原理?

对于待分类的数据和分类项, 根据待分类数据的各个特征属性, 出现在各个分类项中的概率判断该数据是属于哪个类别的。

26、 关联规则挖掘算法apriori原理?

一个频繁项集的子集也是频繁项集, 针对数据得出每个产品的支持数列表, 过滤支持数小于预设值的项, 对剩下的项进行全排列, 重新计算支持 数, 再次过滤, 重复至全排列结束, 可得到频繁项和对应的支持数。 作者: @小黑 以下是自己的理解, 如果有不对的地方希望各位大侠可以帮我指出来~:

1、 简答说一下hadoop的map-reduce编程模型

首先map task会从本地文件系统读取数据, 转换成key-value形式的键值对集合 使用的是hadoop内置的数据类型, 比如longwritable、 text等 将键值对集合输入mapper进行业务处理过程, 将其转换成需要的key-value在输出 之后会进行一个partition分区操作, 默认使用的是hashpartitioner, 可以通过重写hashpartitioner的getpartition方法来自定义分区规则 之后会对key进行进行sort排序, grouping分组操作将相同key的value合并分组输出, 在这里可以使用自定义的数据类型, 重写WritableComparator的 Comparator方法来自定义排序规则, 重写RawComparator的compara方法来自定义分组规则 之后进行一个combiner归约操作, 其实就是一个本地段的reduce预处理, 以减小后面shufle和reducer的工作量 reduce task会通过网络将各个数据收集进行reduce处理, 最后将数据保存或者显示, 结束整个job

2、 hadoop的TextInputFormat作用是什么, 如何自定义实现

InputFormat会在map操作之前对数据进行两方面的预处理 1是getSplits, 返回的是InputSplit数组, 对数据进行split分片, 每片交给map操作一次 2是getRecordReader, 返回的是RecordReader对象, 对每个split分片进行转换为key-value键值对格式传递给map 常用的InputFormat是TextInputFormat, 使用的是LineRecordReader对每个分片进行键值对的转换, 以行偏移量作为键, 行内容作为值 自定义类继承InputFormat接口, 重写createRecordReader和isSplitable方法 在createRecordReader中可以自定义分隔符

3、 hadoop和spark的都是并行计算, 那么他们有什么相同和区别

两者都是用mr模型来进行并行计算, hadoop的一个作业称为job, job里面分为map task和reduce task, 每个task都是在自己的进程中运行的, 当task结束 时, 进程也会结束 spark用户提交的任务成为application, 一个application对应一个sparkcontext, app中存在多个job, 每触发一次action操作就会产生一个job 这些job可以并行或串行执行, 每个job中有多个stage, stage是shuffle过程中DAGSchaduler通过RDD之间的依赖关系划分job而来的, 每个stage里面有多 个task, 组成taskset有TaskSchaduler分发到各个executor中执行, executor的生命周期是和app一样的, 即使没有job运行也是存在的, 所以task可以快速启 动读取内存进行计算 hadoop的job只有map和reduce操作, 表达能力比较欠缺而且在mr过程中会重复的读写hdfs, 造成大量的io操作, 多个job需要自己管理关系 spark的迭代计算都是在内存中进行的, API中提供了大量的RDD操作如join, groupby等, 而且通过DAG图可以实现良好的容错

4、 为什么要用flume导入hdfs, hdfs的构架是怎样的

flume可以实时的导入数据到hdfs中, 当hdfs上的文件达到一个指定大小的时候会形成一个文件, 或者超过指定时间的话也形成一个文件 文件都是存储在datanode上面的, namenode记录着datanode的元数据信息, 而namenode的元数据信息是存在内存中的, 所以当文件切片很小或者很多 的时候会卡死

5、 map-reduce程序运行的时候会有什么比较常见的问题

比如说作业中大部分都完成了, 但是总有几个reduce一直在运行 这是因为这几个reduce中的处理的数据要远远大于其他的reduce, 可能是因为对键值对任务划分的不均匀造成的数据倾斜 解决的方法可以在分区的时候重新定义分区规则对于value数据很多的key可以进行拆分、 均匀打散等处理, 或者是在map端的combiner中进行数据预处 理的操作

6、 简单说一下hadoop和spark的shuffle过程

hadoop: map端保存分片数据, 通过网络收集到reduce端 spark: spark的shuffle是在DAGSchedular划分Stage的时候产生的, TaskSchedule要分发Stage到各个worker的executor 减少shuffle可以提高性能

列举出hadoop中定义的最常用的InputFormats.哪个是默认的?

  1. 是LongWritable类型的, value是每一行的内容, 为Text类型 KeyValueTextInputFormat 同样用于读取文件, 如果行被分隔符(缺省是tab) 分割为两部分, 第一部分为key, 剩下的部分 为value; 如果没 有分隔符, 整行作为 key, value为空 SequenceFileInputFormat 用于读取sequence file。 sequence file是Hadoop用于存储数据自定义格式的binary文件。 它有 两个子类: SequenceFileAsBinaryInputFormat, 将 key和value以BytesWritable的类型读出; SequenceFileAsTextInputFormat, 将key和value以Text类型读出

TextInputFormat和KeyValueInputFormat类不同之处在于哪里?

  • TextInputFormat读取文本文件中的所有行, 提供了行的偏移作为Mapper的键, 实际的行作为 mapper的值。 KeyValueInputFormat读取文本文件, 解析所有行到中, 首个空格前的字符是mapper的key, 行的其余部分则是mapper的值。

Hadoop中InputSplit是什么?

  • InputSplit是指分片, 在MapReduce作业中, 作为map task最小输入单位。 分片是基于文件基础上出来的概念, 通俗的理解一个文件可 以切分为多少个片段, 每个片段包括了<文件名, 开始位置, 长度, 位于哪些主机>等信息。 在 MapTask 拿到这些分片后, 会知道从哪开 始读取数据。

Hadoop框架中文件拆分是如何被触发的?

  • 通过运行输入格式类中的getInputSplit()方法。

考虑一种情况: Map/Reduce系统中, HDFS块大小是64MB,输入格式FileInputFormat,有三个文件64K,65MB,127MB, 那么有hadoop框架会将输入划分成多少?

  • hadoop将会做5个拆分, 64K文件拆分1个, 65MB文件拆分2个, 127MB文件拆分2个。

hadoop中的RecordReader的目的是什么?

  • 1) 以怎样的方式从分片中读取一条记录, 每读取一条记录都会调用RecordReader类; (2) 系统默认的RecordReader是LineRecordReader, 如TextInputFormat; 而SequenceFileInputFormat的RecordReader是SequenceFileRecordReader; (3) LineRecordReader是用每行的偏移量作为map的key, 每行的内容作为map的value; (4) 应用场景: 自定义读取每一条记录的方式; 自定义读入key的类型, 如希望读取的key是文件的路径或名字而不是该行在文件中的偏移量。 系统默认的LineRecordReader是按照每行的偏移量做为map输出时的key值, 每行的内容作为map的value值, 默认的分隔符是 回车和换行。 现在要更改map对应的输入的值, key对应的文件的路径(或者是文件名) , value对应的是文件的内容 (content) 。 那么我们需要重写InputFormat和RecordReader, 因为RecordReader是在InputFormat中调用的, 当然重写RecordReader才是重点!

如果hadoop中没有定义定制分区, 那么如何在输出到reduce前执行数据分区?

  1. /override getPartition() 答: 默认的分区器为各个键计算一个哈希值, 并分配给基于这个结果的分区。

什么是Combiner?举个例子, 什么时候使用combiner,什么时候不用?

  • 当map生成的数据过大时, 带宽就成了瓶颈, 怎样精简压缩传给Reduce的数据, 有不影响最终的结果呢。 有一种方法就是使 用 Combiner, Combiner号称本地的Reduce, Reduce最终的输入, 是Combiner的输出 Combiner的作用是把一个map产生的多个合并成一个新的,然后再将新的作为reduce的输入; 在map函数与reduce函数之间多了一个combine函数, 目的是为了减少map输出的中间结果, 这样减少了reduce复制map输出的数据, 减少网络 传输负载; 并不是所有情况下都能使用Combiner, Combiner适用于对记录汇总的场景(如求和) , 但是, 求平均数的场景就不能使用Combiner了。 如果可以 使用Combiner, 一般情况下, 和我们的reduce函数是一致的。

什么是jobtracker? jobtracker有哪些特别的函数?

  • 1, JobTracker是整个MapReduce计算框架中的主服务, 相当于集群的“管理者”, 负责整个集群的作业控制和资源管理 2, main() 函数

什么是tasktracker? TaskTracker是JobTracker和Task之间的桥梁: 一方面, 从JobTracker接收并执行各种命令: 运行任务、 提交任务、 杀死任务 等; 另一方面, 将本地节点上各个任务的状态通过心跳周期性汇报给JobTracker。 TaskTracker与JobTracker和Task之间采用了 RPC协议进行通信。

hadoop中job和task之间是什么关系?

  • 概述: (1)Hadoop MapReduce采用Master/Slave结构。 *Master: 是整个集群的唯一的全局管理者, 功能包括: 作业管理、 状态监控和任务调度等, 即MapReduce中的JobTracker。 *Slave: 负责任务的执行和任务状态的回报, 即MapReduce中的TaskTracker。 二 JobTracker剖析: (1)概述: JobTracker是一个后台服务进程, 启动之后, 会一直监听并接收来自各个TaskTracker发送的心跳信息, 包括资源使用 情况和任务运行情况等信息。 (2)JobTracker的主要功能: 1.作业控制: 在hadoop中每个应用程序被表示成一个作业, 每个作业又被分成多个任务, JobTracker的作业控制模块则负责作业 的分解和状态监控。 *最重要的是状态监控: 主要包括TaskTracker状态监控、 作业状态监控和任务状态监控。 主要作用: 容错和为任务调度提供决 策依据。 2.资源管理。 三 TaskTracker剖析: (1)TaskTracker概述: TaskTracker是JobTracker和Task之间的桥梁: 一方面, 从JobTracker接收并执行各种命令: 运行任务、 提交 任务、 杀死任务等; 另一方面, 将本地节点上各个任务的状态通过心跳周期性汇报给JobTracker。 TaskTracker与JobTracker和 Task之间采用了RPC协议进行通信。 (2)TaskTracker的功能: 1.汇报心跳: Tracker周期性将所有节点上各种信息通过心跳机制汇报给JobTracker。 这些信息包括两部分: *机器级别信息: 节点健康情况、 资源使用情况等。 *任务级别信息: 任务执行进度、 任务运行状态等。 2.执行命令: JobTracker会给TaskTracker下达各种命令, 主要包括: 启动任务(LaunchTaskAction)、 提交任务 (CommitTaskAction)、 杀死任务(KillTaskAction)、 杀死作业(KillJobAction)和重新初始化(TaskTrackerReinitAction)。

假设hadoop一个job产生了100个task, 并且其中的一个task失败了, hadoop会如何处理?

  1. 任何failed task都不会引起整个job的失败, 因为所有失败的任务 都会被重新执行(reschedule execution) , 只有当重新执行的次数超过4次, 才会把这任务标记为失败, 导致整个job的失败。

通过划分多个节点上任务, hadoop实现了并行处理, 对少数慢节点可能会限制剩下其他程序的速率, 并拖慢了整个程序。 hadoop提供了什么机制防止这种情况的发生?

  1. execution。 举个简单的例子, 如果某个job有2000个map task, 已经完成了1999个, 只剩下一个task由于硬件比较慢而成为拖尾 任务, 为了减少拖尾任务对整个job运行时间的影响, jobtracker会重新启动一个一模一样的duplicate task和原有的task并行的执行, 这样有一 个task执行成功, 整个map过程就会结束。 speculative execution(推测执行)只有个处理拖尾任务的优化策略, 并不能提高系统的可靠性

hadoop推测执行是如何实现的?

  • Hadoop会为该task启动备份任务, 让speculative task与原始task同时处理一份数据, 哪个先运行完, 则将谁的结果作为最终结果, 并且在 运行完成后Kill掉另外一个任务。

Unix中使用命令行, 如何查看hadoop集群中的所有运行的任务? 或是kill掉任务?

  1.  

什么是hadoop streming?

  • Haoop支持用其他语言来编程, 需要用到名为Streaming的通用API, Streaming主要用于编写简单, 短小的MapReduce程序, 可以通过脚

 

解释hadoop和hadoop生态系统两个概念

  • Hadoop是一个能够对大量数据进行分布式处理的软件框架, Hadoop的核心是HDFS和Mapreduce, hadoop2.0还包括YARN hadoop生态系统: 包含 HDFS mapreduce hive hbase zookeeper sqoop flume pig mahout

说明hadoop2.0的基本构成

  1. MapReduce YARN

相比于HDFS1.0.2.0最主要的改进在哪几个方面?

  • YARN HDFS单点故障得以解决 Hadoop 2.0的最大变化出现在内核(HDFS、 MapReduce和YARN)

试使用步骤1, 步骤2, 步骤3.……说明YARN中运行应用程序的基本流程

  • 1 用户向YARN中提交应用程序, 其中包括ApplicationMaster程序、 启动ApplicationMaster的命令、 用户程序等。 步骤2 ResourceManager为该应用程序分配第一个Container, 并与对应的Node-Manager通信, 要求它在这个Container中启动应用程序的 ApplicationMaster。 步骤3 ApplicationMaster首先向ResourceManager注册, 这样用户可以直接通过ResourceManage查看应用程序的运行状态, 然后它将为各 个任务申请资源, 并监控它的运 行状态, 直到运行结束, 即重复步骤4~7。 步骤4 ApplicationMaster采用轮询的方式通过RPC协议向ResourceManager申请和领取资源。 步骤5 一旦ApplicationMaster申请到资源后, 便与对应的NodeManager通信, 要求它启动任务Task。 步骤6 NodeManager为任务Task设置好运行环境(包括环境变量、 JAR包、 二进制程序等) 后, 将任务启动命令写到一个脚本中, 并通过 运行该脚本启动任务Task。 步骤7 各个任务Task通过某个RPC协议向ApplicationMaster汇报自己的状态和进度, 以让ApplicationMaster随时掌握各个任务的运行状 态, 从而可以在任务失败时重新启动任务。 在应用程序运行过程中, 用户可随时通过RPC向ApplicationMaster查询应用程序的当前运行状态。 步骤8 应用程序运行完成后, ApplicationMaster向ResourceManager注销并关闭自己。

MapReducer2.0与Yarn是否等同, 尝试解释说明。

MapReducer2.0中, MRAppMaster主要作用是什么, MRappMaster如何实现任务容错的?

什么是MRAppMaster?

  • 在MRv1中, JobTracker存在诸多问题, 包括存在单点故障, 扩展受限等, 为了解决这些问题, Apache对MRv1进行了改进, 提 出了YARN, YARN将JobTracker中的作业控制和资源管理两个功能分开, 分别由两个不同的进程处理, 进而解决了原有JobTracker存在的问 题。 经过架构调整之后, YARN已经完全不同于MRv1, 它已经变成了一个资源管理平台, 或者说应用程序管理框架。 运行于YARN之上的计 算框架不只限于MapReduce一种, 也可以是其他流行计算框架, 比如流式计算、 迭代式计算等类型的计算框架。 为了将一个计算框架运行于 YARN之上, 用户需要开发一个组件—ApplicationMaster。 作为一个开始, YARN首先支持的计算框架是MapReduce, YARN为用户实现好了 MapReduce的ApplicationMaster, 也就是本文要介绍了MRAppMaster。

相比于JobTracker, MRAppMaster有什么不同?

  • MRAppMaster是由JobTracker衍化而来的, 那么是否将JobTracker的代码稍加修改, 就变成了MRAppMaster呢, 答案是否定的。 事实 上, YARN仅重用了MRv1中的少许代码, 基本可看做重写了MRAppMaster。 YARN采用了新的软件设计思想, 包括对象服务化、 事件驱动的异步编程模型的。 作为YARN的一部分, MRAppMaster的实现也采用了这 些设计思想。 下面简要介绍一下MRAppMaster的实现细节 在正式介绍MRAppMaster之前, 我们先回顾一下MRv1的实现。 我们都知道, MRv1主要由两种服务组成, 即: JobTracker和TaskTracker, 而在YARN中, TaskTracker已经由NodeManager代替, 因此, 我们在此重点分析JobTracker。 JobTracker包含资源管理和作业控制两个功能, 在YARN中, 作业管理由ResourceManager实现, 因此, 只剩下作业控制这一个功能(由MRAppMaster实现) 。 MRv1中每个作业由一个 JobInProgress控制, 每个任务由一个TaskInProgress控制, 由于每个任务可能有多个运行实例, 因此, TaskInProgress实际管理了多个运行实 例Task Attempt, 对于每个运行实例, 可能运行了一个MapTask或者ReduceTask, 另外, 每个Map Task或者Reduce Task会通过RPC协议将状态 汇报给TaskTracker, 再由TaskTracker进一步汇报给JobTracker 在MRAppMaster中, 它只负责管理一个作业, 包括该作业的资源申请、 作业运行过程监控和作业容错等。 MRAppMaster使用服务模型和 事件驱动的异步编程模型对JobInProgress和TaskInProgress进行了重写(分别对应JobImpl和TaskImpl) , 并让Map Task和Reduce Task(Map Task和Reduce Task重用了MRv1中的代码) 直接通过RPC将信息汇报给MRAppMaster。 此外, 为了能够运行于YARN之上, MRAppMaster还要 与ResourceManager和NodeManager两个新的服务通信(用到两个新的RPC协议) , 以申请资源和启动任务, 这些都使得MRAppMaster完全不 同于JobTracker。 在接下来几篇文章中, 我将重点剖析MRAppMaster的内部实现原理。

为什么会产生yarn,它解决了什么问题。 有什么优势?

job的运行流程(提交一个job的流程)

  1. dataInput-->split-->Mapper-->Combine-->(产出临时数据)-->Partition-->Sort-->Reducer-->最终数据

hadoop生态圈中各种框架的运用场景

hive中的压缩格式RCFile.TextFile.SequenceFile各有什么区别, 以上三种格式一样大的文件哪个占用空间大小

  1. 存储空间消耗比较大, 并且压缩的text 无法分割和合并 查询的效率最低,可以直接存储, 加载数据的速度最高 sequencefile 存储空间消耗最大,压缩的文件可以分割和合并 查询效率高, 需要通过text文件转化来加载 rcfile 存储空间最小, 查询的效率最高 , 需要通过text文件转化来加载, 加载的速度最低

hadoop中的一个HA压缩

Flume收集到的数据很多个小文件, 我而要写MR处理时将这些文件合并(是在MR中进行优化

  • 不让一个小文件一个MapReduce) ===============================

你们的集群规模?

  • 10台(8台可用) 8核cpu

你们的数据是用什么导入到数据库的? 导入到什么数据库?

  • 通过hadoop命令导入到hdfs文件系统 处理完成之后的导出: 利用hive处理完成之后的数据, 通过sqoop导出到mysql数据库中, 以供报表使用? 3. 你们的业务数据量多大? 有多少行数据? (面试三家, 都问) 开发时使用的是部分数据, 不是全量数据。 有将近一亿行(8. 9千万吧, 全体不详, 一般开发不是特别关心这个问题)

 

Hive内部表和外部表的区别

  1. 在导入数据到外部表, 数据并没有移动到自己的数据仓库目录下, 也就是说外部表中的数据并不是由它自己来管理的! 而 表则不一 样; 2、 在删除表的时候, Hive将会把属于表的元数据和数据全部删掉; 而删除外部表的时候, Hive仅仅删除外部表的元数据, 数 据 是不会 删除的! 那么, 应该如何选择使用哪种表呢? 在大多数情况没有太多的区别, 因此选择只是个人喜好的问题。 但是作为一个经验, 如 果所有处理 都需要由Hive完成, 那么你应该创建表, 否则使用外部表!

 

hive底层与数据库交互原理

  1. 一 概述: (1)Hadoop MapReduce采用Master/Slave结构。 *Master: 是整个集群的唯一的全局管理者, 功能包括: 作业管理、 状态监控和任务调度等, 即MapReduce中的JobTracker。 *Slave: 负责任务的执行和任务状态的回报, 即MapReduce中的TaskTracker。 二 JobTracker剖析: (1)概述: JobTracker是一个后台服务进程, 启动之后, 会一直监听并接收来自各个TaskTracker发送的心跳信息, 包括资源使用情况和任务 运行情况等信息。 (2)JobTracker的主要功能: 1.作业控制: 在hadoop中每个应用程序被表示成一个作业, 每个作业又被分成多个任务, JobTracker的作业控制模块则负责作业的分解和 状态监控。 *最重要的是状态监控: 主要包括TaskTracker状态监控、 作业状态监控和任务状态监控。 主要作用: 容错和为任务调度提供决策依据。 2.资源管理。 三 TaskTracker剖析: (1)TaskTracker概述: TaskTracker是JobTracker和Task之间的桥梁: 一方面, 从JobTracker接收并执行各种命令: 运行任务、 提交任务、 杀死 任务等; 另一方面, 将本地节点上各个任务的状态通过心跳周期性汇报给JobTracker。 TaskTracker与JobTracker和Task之间采用了RPC协议进 行通信。 (2)TaskTracker的功能: 1.汇报心跳: Tracker周期性将所有节点上各种信息通过心跳机制汇报给JobTracker。 这些信息包括两部分: *机器级别信息: 节点健康情况、 资源使用情况等。 *任务级别信息: 任务执行进度、 任务运行状态等。 2.执行命令: JobTracker会给TaskTracker下达各种命令, 主要包括: 启动任务(LaunchTaskAction)、 提交任务(CommitTaskAction)、 杀死任务
  • (KillTaskAction)、 杀死作业(KillJobAction)和重新初始化(TaskTrackerReinitAction)。
  • == 下面列出一些Hadoop组件, 不同的组件分别提供特定的服务。 Apache Hive: 数据仓库基础设施, 提供数据汇总和特定查询。 这个系统支持用户进行有效的查询, 并实时得到返回结果。 Apache Spark: Apache Spark是提供大数据集上快速进行数据分析的计算引擎。 它建立在HDFS之上, 却绕过了MapReduce使用自己的数据处 理框架。 Spark常用于实时查询、 流处理、 迭代算法、 复杂操作运算和机器学习。 Apache Ambari: Ambari用来协助管理Hadoop。 它提供对Hadoop生态系统中许多工具的支持, 包括Hive、 HBase、 Pig、 Spooq和ZooKeeper。 这个工具提供集群管理仪表盘, 可以跟踪集群运行状态, 帮助诊断性能问题。 Apache Pig: Pig是一个集成高级查询语言的平台, 可以用来处理大数据集。 Apache HBase: HBase是一个非关系型数据库管理系统, 运行在HDFS之上。 它用来处理大数据工程中稀疏数据集。
  • 其他常见的Hadoop项目还包括Avro、 Cassandra、 Chukwa, Mahout和ZooKeeper。
  • 1、 如果需要保持HDFS上的目录结构, 原封不动地复制下来, 采用下面的命令:
  1. hive.exec.compress.output='false'; INSERT OVERWRITE LOCAL DIRECTORY '/home/hesey/directory' select * from table;
  • 2、 如果想把表的所有数据都下载到一个文件中, 则采用下面的命令:
  1. dfs -getmerge hdfs://hdpnn:9000/hesey/hive/table /home/hesey/table.txt 这样所有文件会由Hadoop合并后下载到本地, 最后就只有/home/hesey/table.txt这一个文件 (User Defined Function) 。 1、 使用UDF (a) 如果是已经上传到Hive服务器的UDF, 可以直接用 create temporary function dosomething as 'net.hesey.udf.DoSomething'; 声明临时函数, 然后在下面的Hive QL中就可以调用dosomething这个方法了。 (b) 如果是自己编写的UDF, 需要在声明临时函数前再加一行: add jar /home/hesey/foo.jar 这样就可以把自定义的UDF加载进来, 然后和(a)一样声明临时函数就可以了。 2、 编写UDF 编写UDF十分简单, 引入hive-exec包, 继承org.apache.hadoop.hive.ql.exec.UDF类, 实现evaluate方法即可, 方法的输入和输出参数类型就是当 你在Hive中调用时的输入和返回值。 例如: public Text evaluate(final LongWritable number); (Text和LongWritable是org.apache.hadoop.io下面的类) 这样我们就可以定义自己的函数并方便地在Hive中调用, 而不需要写一个重量级的MapReduce。
  • 五、 笛卡尔积
  1. 不能用select T1., T2. from table_1, table_2这种语法。 但有时候确实需要用到笛卡尔积的时候, 可以用下面 的语法来实现同样的效果: select T1., T2. from (select * from table1) T1 join (select * from table2) T2 on 1=1; 其中on 1=1是可选的, 注意在Hive的Strict模式下不能用这种语法, 需要先用set hive.mapred.mode=nonstrict;设为非strict模式就可以用了。
  • 六、 join的规则
  • Hive做join运算时, join前面的表会被放入内存, 所以在做join时, 最好把小表放在前面, 有利于提高性能并防止OOM。
  • 七、 排序
  • SQL中排序通过ORDER by实现, Hive中也支持这种语法, 但是使用ORDER by时最终所有的数据会汇总到一个Reducer上进行排序, 可能 使得该Reducer压力非常大, 任务长时间无法完成。 如果排序只要求保证Value有序而Key可以无序, 例如要统计每个用户每笔的交易额从高到低排列, 只需要对每个用户的交易额排序, 而用 户ID本身不需要排序。 这种情况采用分片排序更好, 语法类似于: select user_id, amount from table distribute by user_id sort by user_id, amount 这里用到的不是ORDER by, 而是distribute by和sort by, distribute by标识Map输出时分发的Key。 这样最后排序的时候, 相同的user_id和amount在同一个Reducer上被排序, 不同的user_id可以同时分别在多个Reducer上排序, 相比ORDER by只能在一个Reducer上排序, 速度有成倍的提升。

kafka分区数的确定

  • 1、kafka分区
    • Kafka可以将主题划分为多个分区(Partition),会根据分区规则选择把消息存储到哪个分区中,只要如果分区规则设置的合理,那么所有的消息将会被均匀的分布到不同的分区中,这样就实现了负载均衡和水平扩展。
  • 2、各角色对分区的操作
    • kafka的生产者和消费者都可以多线程地并行操作,而每个线程处理的是一个分区的数据。因此分区实际上是调优Kafka并行度的最小单元。
    • 对于producer而言,它实际上是用多个线程并发地向不同分区所在的broker发起Socket连接同时给这些分区发送消息;
    • 而consumer呢,同一个消费组内的所有consumer线程都被指定topic的某一个分区进行消费;
    • 如果一个topic分区越多,理论上整个集群所能达到的吞吐量就越大。 但是分区是不是越多越好呢?显然不是,因为没个分区都有自己的资源占用开销,分区多了,资源占用同样也多了,反而更占资源了。
  • 3、分区的几个限制地方
    • 分区数,限制了consumer的并行度,即限制了并行consumer消息的线程数不能大于分区数;
    • 分区数,限制了producer发送消息是指定的分区(如创建topic时分区设置为1,producer发送消息时通过自定义的分区方法指定分区为2或以上的数都会出错的);
  • 4、分区与偏移量(Offset)、消费线程、消费者组(group.id)的关系
    • 一组(类)消息通常由某个topic来归类,我们可以把这组消息"分发"给若干个分区(partition),每个分区的消息各不相同;
    • 每个分区都维护着他自己的偏移量(Offset),记录着该分区的消息此时被消费的位置;
    • 一个消费线程可以对应若干个分区,但一个分区只能被具体某一个消费线程消费;
    • group.id用于标记某一个消费组,每一个消费组都会被记录他在某一个分区的Offset,即不同consumer group针对同一个分区,都有"各自"的偏移量。
    • kafka中的topic在消费时,整体上是无序的,而每个分区的内部消费是有顺序的,如果想要全局有序,可以设置一个分区,但是这样的话丢失了很多的性能。
  • 5、分区简单总结
    • 分区数限制了上下游的线程数,可以理解整体的并行度。
    • 分区数是为了实现负载均衡。
    • 分区设计需要考虑各角色的资源占用,副本数,文件句柄数等等
    • 6、如何合理的设置分区数
    • 一个topic分区的个数不是随意来来设置的,需要有些衡量的指标。
    • 可以使用如下方法进行测试: *(1)、创建只有一个分区数的topic *(2)、然后测试这个topic的producer和consumer吞吐量 *(3)、假设它们的值分别为Tp和Tc,单位可以是MB/s *(4)、然后假设总的目标吞吐量是Tt,那么分区数 = Tt / max(Tp, Tc)
    • 注意:
      • Tp表示producer的吞吐量。测试producer通常是很容易的,因为它的逻辑非常简单,就是直接发送消息到Kafka就好了。Tp=10m
      • Tc表示consumer的吞吐量。测试Tc通常与应用的关系更大, 因为Tc的值取决于你拿到消息之后执行什么操作,因此Tc的测试通常也要麻烦一些。Tc=5m
      • 总的目标吞吐量是Tt,可以理解为说业务数据每秒实际产生的数据量 100M
      •  

内存溢出解决方法:

1. map过程产生大量对象导致内存溢出:

 这种溢出的原因是在单个map中产生了大量的对象导致的,例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这肯定很容易产生内存溢出的问题。针对这种问题,在不增加内存的情况下,可以通过减少每个Task的大小,以便达到每个Task即使产生大量的对象Executor的内存也能够装得下。具体做法可以在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。例如:rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)。

 面对这种问题注意,不能使用rdd.coalesce方法,这个方法只能减少分区,不能增加分区,不会有shuffle的过程。

2.数据不平衡导致内存溢出:

 数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题,解决方法和上面说的类似,就是调用repartition重新分区。这里就不再累赘了。

3.coalesce调用导致内存溢出:

 这是我最近才遇到的一个问题,因为hdfs中不适合存小问题,所以Spark计算后如果产生的文件太小,我们会调用coalesce合并文件再存入hdfs中。但是这会导致一个问题,例如在coalesce之前有100个文件,这也意味着能够有100个Task,现在调用coalesce(10),最后只产生10个文件,因为coalesce并不是shuffle操作,这意味着coalesce并不是按照我原本想的那样先执行100个Task,再将Task的执行结果合并成10个,而是从头到位只有10个Task在执行,原本100个文件是分开执行的,现在每个Task同时一次读取10个文件,使用的内存是原来的10倍,这导致了OOM。解决这个问题的方法是令程序按照我们想的先执行100个Task再将结果合并成10个文件,这个问题同样可以通过repartition解决,调用repartition(10),因为这就有一个shuffle的过程,shuffle前后是两个Stage,一个100个分区,一个是10个分区,就能按照我们的想法执行。

4.shuffle后内存溢出:

 shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的。在Spark中,join,reduceByKey这一类型的过程,都会有shuffle的过程,在shuffle的使用,需要传入一个partitioner,大部分Spark中的shuffle操作,默认的partitioner都是HashPatitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions) , spark.default.parallelism参数只对HashPartitioner有效,所以如果是别的Partitioner或者自己实现的Partitioner就不能使用spark.default.parallelism这个参数来控制shuffle的并发量了。如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量。

5. standalone模式下资源分配不均匀导致内存溢出:

 在standalone的模式下如果配置了--total-executor-cores 和 --executor-memory 这两个参数,但是没有配置--executor-cores这个参数的话,就有可能导致,每个Executor的memory是一样的,但是cores的数量不同,那么在cores数量多的Executor中,由于能够同时执行多个Task,就容易导致内存溢出的情况。这种情况的解决方法就是同时配置--executor-cores或者spark.executor.cores参数,确保Executor资源分配均匀。

6.在RDD中,共用对象能够减少OOM的情况:

 这个比较特殊,这里说记录一下,遇到过一种情况,类似这样rdd.flatMap(x=>for(i <- 1 to 1000) yield ("key","value"))导致OOM,但是在同样的情况下,使用rdd.flatMap(x=>for(i <- 1 to 1000) yield "key"+"value")就不会有OOM的问题,这是因为每次("key","value")都产生一个Tuple对象,而"key"+"value",不管多少个,都只有一个对象,指向常量池。具体测试如下:

 

 这个例子说明("key","value")和("key","value")在内存中是存在不同位置的,也就是存了两份,但是"key"+"value"虽然出现了两次,但是只存了一份,在同一个地址,这用到了JVM常量池的知识.于是乎,如果RDD中有大量的重复数据,或者Array中需要存大量重复数据的时候我们都可以将重复数据转化为String,能够有效的减少内存使用.

优化:

 这一部分主要记录一下到spark-1.6.1版本,笔者觉得有优化性能作用的一些参数配置和一些代码优化技巧,在参数优化部分,如果笔者觉得默认值是最优的了,这里就不再记录。

代码优化技巧:

1.使用mapPartitions代替大部分map操作,或者连续使用的map操作:

 这里需要稍微讲一下RDD和DataFrame的区别。RDD强调的是不可变对象,每个RDD都是不可变的,当调用RDD的map类型操作的时候,都是产生一个新的对象,这就导致了一个问题,如果对一个RDD调用大量的map类型操作的话,每个map操作会产生一个到多个RDD对象,这虽然不一定会导致内存溢出,但是会产生大量的中间数据,增加了gc操作。另外RDD在调用action操作的时候,会出发Stage的划分,但是在每个Stage内部可优化的部分是不会进行优化的,例如rdd.map(+1).map(+1),这个操作在数值型RDD中是等价于rdd.map(_+2)的,但是RDD内部不会对这个过程进行优化。DataFrame则不同,DataFrame由于有类型信息所以是可变的,并且在可以使用sql的程序中,都有除了解释器外,都会有一个sql优化器,DataFrame也不例外,有一个优化器Catalyst,具体介绍看后面参考的文章。

 上面说到的这些RDD的弊端,有一部分就可以使用mapPartitions进行优化,mapPartitions可以同时替代rdd.map,rdd.filter,rdd.flatMap的作用,所以在长操作中,可以在mapPartitons中将RDD大量的操作写在一起,避免产生大量的中间rdd对象,另外是mapPartitions在一个partition中可以复用可变类型,这也能够避免频繁的创建新对象。使用mapPartitions的弊端就是牺牲了代码的易读性。

2.broadcast join和普通join:

 在大数据分布式系统中,大量数据的移动对性能的影响也是巨大的。基于这个思想,在两个RDD进行join操作的时候,如果其中一个RDD相对小很多,可以将小的RDD进行collect操作然后设置为broadcast变量,这样做之后,另一个RDD就可以使用map操作进行join,这样能够有效的减少相对大很多的那个RDD的数据移动。

3.先filter在join:

 这个就是谓词下推,这个很显然,filter之后再join,shuffle的数据量会减少,这里提一点是spark-sql的优化器已经对这部分有优化了,不需要用户显示的操作,个人实现rdd的计算的时候需要注意这个。

4.partitonBy优化:

 这一部分在另一篇文章《spark partitioner使用技巧 》有详细介绍,这里不说了。

5. combineByKey的使用:

 这个操作在Map-Reduce中也有,这里举个例子:rdd.groupByKey().mapValue(_.sum)比rdd.reduceByKey的效率低,原因如下两幅图所示(网上盗来的,侵删)

 

 

 上下两幅图的区别就是上面那幅有combineByKey的过程减少了shuffle的数据量,下面的没有。combineByKey是key-value型rdd自带的API,可以直接使用。

6. 在内存不足的使用,使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache():

 rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等价的,在内存不足的时候rdd.cache()的数据会丢失,再次使用的时候会重算,而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在内存不足的时候会存储在磁盘,避免重算,只是消耗点IO时间。

7.在spark使用hbase的时候,spark和hbase搭建在同一个集群:

 在spark结合hbase的使用中,spark和hbase最好搭建在同一个集群上上,或者spark的集群节点能够覆盖hbase的所有节点。hbase中的数据存储在HFile中,通常单个HFile都会比较大,另外Spark在读取Hbase的数据的时候,不是按照一个HFile对应一个RDD的分区,而是一个region对应一个RDD分区。所以在Spark读取Hbase的数据时,通常单个RDD都会比较大,如果不是搭建在同一个集群,数据移动会耗费很多的时间。

参数优化部分:

8. spark.driver.memory (default 1g):

 这个参数用来设置Driver的内存。在Spark程序中,SparkContext,DAGScheduler都是运行在Driver端的。对应rdd的Stage切分也是在Driver端运行,如果用户自己写的程序有过多的步骤,切分出过多的Stage,这部分信息消耗的是Driver的内存,这个时候就需要调大Driver的内存。

9. spark.rdd.compress (default false) :

 这个参数在内存吃紧的时候,又需要persist数据有良好的性能,就可以设置这个参数为true,这样在使用persist(StorageLevel.MEMORY_ONLY_SER)的时候,就能够压缩内存中的rdd数据。减少内存消耗,就是在使用的时候会占用CPU的解压时间。

10. spark.serializer (default org.apache.spark.serializer.JavaSerializer )

 建议设置为 org.apache.spark.serializer.KryoSerializer,因为KryoSerializer比JavaSerializer快,但是有可能会有些Object会序列化失败,这个时候就需要显示的对序列化失败的类进行KryoSerializer的注册,这个时候要配置spark.kryo.registrator参数或者使用参照如下代码:

val*conf*=newSparkConf().setMaster(...).setAppName(...) conf.registerKryoClasses(Array(classOf[MyClass1],classOf[MyClass2])) valsc =newSparkContext(conf)

11. spark.memory.storageFraction (default 0.5)

 这个参数设置内存表示 Executor内存中 storage/(storage+execution),虽然spark-1.6.0+的版本内存storage和execution的内存已经是可以互相借用的了,但是借用和赎回也是需要消耗性能的,所以如果明知道程序中storage是多是少就可以调节一下这个参数。

12.spark.locality.wait (default 3s):

 spark中有4中本地化执行level,PROCESS_LOCAL->NODE_LOCAL->RACK_LOCAL->ANY,一个task执行完,等待spark.locality.wait时间如果,第一次等待PROCESS的Task到达,如果没有,等待任务的等级下调到NODE再等待spark.locality.wait时间,依次类推,直到ANY。分布式系统是否能够很好的执行本地文件对性能的影响也是很大的。如果RDD的每个分区数据比较多,每个分区处理时间过长,就应该把 spark.locality.wait 适当调大一点,让Task能够有更多的时间等待本地数据。特别是在使用persist或者cache后,这两个操作过后,在本地机器调用内存中保存的数据效率会很高,但是如果需要跨机器传输内存中的数据,效率就会很低。

13. spark.speculation (default false):

 一个大的集群中,每个节点的性能会有差异,spark.speculation这个参数表示空闲的资源节点会不会尝试执行还在运行,并且运行时间过长的Task,避免单个节点运行速度过慢导致整个任务卡在一个节点上。这个参数最好设置为true。与之相配合可以一起设置的参数有spark.speculation.×开头的参数。参考中有文章详细说明这个参数。

1.sparkseaming

 

 

从图上可以看到,Batch Interval的间隔是5s,也就是说每经过5s,SparkStreaming会将这5s内的信息封装成一个DStream,然后提交到Spark集群进行计算

1.1执行流程  第一个 DStream 里面是 0-5s 的数据,在第6s的时候会触发 DStream 的job执行,这时会另启动一个线程执行这个job(假设这个job只需要3s),同时在6-10s期间继续接受数据,在第11s的时候会触发 DStream 的job的执行,这时会另启动一个线程执行这个job(假设这个job只需要3s),同时在11-15s期间继续接受数据...

*注意!  如果这个job执行的时间大于5s会有什么问题? 数据在5s内处理不完,又启动一个job,导致数据越积越多,从而导致 SparkStreaming down

1.2为什么需要有窗口操作?  比如别人要求能够实时看到此刻之前一段时间的数据情况,如果使用批处理的话,那么我们只能固定一个整段时间然后对这个整段时间进行spark core的计算,但是别人的要求是每一个时刻都需要有结果,那么就需要窗口操作?但是窗口操作肯定会有很多的重复计算,这里有一个优化的地方(这个优化也不是必须的,视具体情况而定,比如说我们要查看最近30分钟最热门的头条,我们在设计的时候不可能每隔30分钟计算一次,这里定义了滑动窗口时间是1分钟,然而计算量是30分钟内的数据,那么肯定会有29分钟重复的数据计算);但是优化的话就会有一个前提,必须要checkpoint

1.3 UpdateStateByKey updateStateByKey的使用需要checkpoint,隔几次记录一次到磁盘中 UpdateStateByKey的主要功能  1、Spark Streaming中为每一个Key维护一份state状态,这个state类型可以是任意类型的的, 可以是一个自定义的对象,那么更新函数也可以是任意类型的。  2、通过更新函数对该key的状态不断更新,对于每个新的batch而言,Spark Streaming会在使用updateStateByKey的时候为已经存在的key进行state的状态更新(对于每个新出现的key,会同样的执行state的更新函数操作)  3、如果要不断的更新每个key的state,就涉及到了状态的保存和容错,这个时候就需要开启checkpoint机制和功能 ( one.checkpoint(Durations.seconds(10)) //容错更好就需要牺牲性能,容错不需要太高,时间可以设置的长一些,(比如这里将checkpoint设置为10s,也就是每隔10s会将标记有checkpoint的RDD计算的结果持久化到磁盘,如果我们设置的Batch Interval = 5s, 在第15s时触发job进行计算, 但是在第17s时down, 这时候只能恢复到10s的数据,那么10s至17s的数据就丢失了,具体设置多少,视具体情况而定))

以上时从此处参考整理 http://www.cnblogs.com/haozhengfei/p/e353daff460b01a5be13688fe1f8c952.html 2.sparkStreaming监控 Spark UI中的以下两个指标尤为重要: 1.Processing Time 处理时间 - 处理每批数据的时间。 2.Scheduling Delay 计划延迟 - 批处理在队列中等待处理先前批处理完成的时间。 *注意:若Processing Time的值一直大于Scheduling Delay,或者Scheduling Delay的值持续增长,代表系统已经无法处理这样大的数据输入量了,这时就需要考虑各种优化方法来增强系统的负载或者减少批处理的时间

Spark UI中提供一些数据监控,包括实时输入数据、Scheduling Delay、处理时间以及总延迟的相关监控数据的趋势展现。另外除了提供上述数据监控外,Spark UI还提供了Active Batches以及Completed Batches相关信息。Active Batches包含当前正在处理的batch信息以及堆积的batch相关信息,而Completed Batches刚提供每个batch处理的明细数据,具体包括batch time、input size、scheduling delay、processing Time、Total Delay等

Spark Streaming能够提供如此优雅的数据监控,是因在对监听器设计模式的使用。如若Spark UI无法满足你所需的监控需要,用户可以定制个性化监控信息。Spark Streaming提供了StreamingListener特质,通过继承此方法,就可以定制所需的监控

3.SparkStreaming 支持的业务场景 目前而言SparkStreaming 主要支持以下三种业务场景

a.无状态操作:只关注当前的DStream中的实时数据,例如 只对当前DStream中的数据做正确性校验 无状态模型只关注当前新生成的DStream数据,所以的计算逻辑均基于该批次的数据进行处理。无状态模型能够很好地适应一些应用场景,比如网站点击实时排行榜、指定batch时间段的用户访问以及点击情况等。该模型由于没有状态,并不需要考虑有状态的情况,只需要根据业务场景保证数据不丢就行。此种情况一般采用Direct方式读取Kafka数据,并采用监听器方式持久化Offsets即可。 *注意:受网络、集群等一些因素的影响,实时程序出现长时失败,导致数据出现堆积。此种情况下是丢掉堆积的数据从Kafka largest处消费还是从之前的Kafka offsets处消费,这个取决具体的业务场景。

b.有状态操作:对有状态的DStream进行操作时,需要依赖之前的数据 例如 统计网站各个模块总的访问量。有状态模型是指DStreams在指定的时间范围内有依赖关系,具体的时间范围由业务场景来指定,可以是2个及以上的多个batch time RDD组成。Spark Streaming提供了updateStateByKey方法来满足此类的业务场景。因涉及状态的问题,所以在实际的计算过程中需要保存计算的状态,Spark Streaming中通过checkpoint来保存计算的元数据以及计算的进度。该状态模型的应用场景有网站具体模块的累计访问统计、最近N batch time 的网站访问情况以及app新增累计统计等等.

c.窗口操作:对指定时间段范围内的DStream数据进行操作,例如 需要统计一天之内网站各个模块的访问数量

4.spark优化  4.1算子优化 1在使用join的地方看是否可以使用map算子和广播变量的方式替代; 2使用高效的算子, 例如:使用reduceByKey/aggregateByKey来代替groupByKey,因为前者可以进行combiner操作,减少网络IO; 当进行联合规约操作时,避免使用 groupByKey。举个例子,rdd.groupByKey().mapValues(_ .sum) 与 rdd.reduceByKey(_ + _) 执行的结果是一样的,但是前者需要把全部的数据通过网络传递一遍, 3.使用MapPartition来代替Map操作, 尤其是在需要网络连接的地方; 4.使用foreachPartition代替foreach操作,可以对数据进行批量处理; 5.在filter操作后,可以使用colease操作,可以减少任务数; 6.序列化尽量使用Kyro方式, 其性能更好; 7.减少对复杂数据结构的使用,可以有效减少序列化时间; 8.对应简单的函数,最好使用闭合结构,可以有效减少网络IO; 9.使用Repartition操作可以有效增加任务的处理并行度;

 4.2参数调整优化部分  设置合理的资源;  Java垃圾回收器;  清理不必要的空间; 1.根据资源情况,可以添加Executor的个数来有效,参数为 spark.executor.instances 2.调整每个Executor的使用内核数, 参数为 spark.executor.cores 3.调整每个Executor的内存, 参数为 spark.executor.memory 4.shuffle write task的buffer大小, 参数为 spark.shuffle.file.buffer 5.shuffle read task的buffer大小, 参数为 spark.reducer.maxSizeInFlight 6.每一个stage的task的默认并行度, 默认为200, 建议修改为1000左右, 参数 spark.default.parallelism 7.用于RDD的持久化使用的内存比例,默认0.6, 参数 spark.storage.memoryFraction 8.用户shuffle使用的内存比例, 默认为0.2, 参数 spark.shuffle.memoryFraction 4.3其它优化 1.增加数据读取的并行度,比如读取Kafka的数据,可以增加topic的partition数量和executor的个数; 2.限制读取Kafka数据的速率,参数 spark.streaming.kafka.maxRatePerPartition 3.对于存在数据倾斜问题,有两类情况: 4.进行join操作,产生skew问题, 可以使用map+广播变量类进行处理; 5.对redece/aggregate等聚合操作,参数skew问题, 可以进行两次聚合的思想来解决,

  • 核心是先进行key进行随机数操作, 是数据分布均匀,并进行聚合,最后是剔除随机数据,用实际数据来进行聚合操作。 6.并行度调优设置: a.在数据进行混洗的时候,使用参数的方式为混洗后的RDD指定并行度 .对于已经存在的RDD可以进行重新分区来获取更多或更少的分区数。重新分区操作通过repartiton()实现,这个操作会把 RDD随机打乱并分成设定的分区数目。如果要减少RDD分区,可以使用coalesce()操作。由于没有打乱数据这个 操作比repartition()更为高效。
  •          eg:从S3上读取大量数据,进行filter()操作。filter()返回的RDD的分区数和父节点的一样,这样会造成很多空的 分区或只有很少数据的分区。这样的情况下可以通过合并得到分区更少的RDD来提高性能。

7.序列化:  a.spark通过网络传输数据,或者把数据溢写到磁盘上时,需要把数据序列化成二进制格式。序列化会在数据进行混洗的时 候发生。此时需要大量网络传输。默认情况下,spark使用java内部得序列化库。spark也支持第三方得序列化库, 可提供比java序列化工具更短得序列化时间和更高得压缩比例得二进制表示。

    b.要使用kryo序列化工具,需要设置spark.serializer为org.apache.spark.serializer.kryoserializer。还可 以像kryo注册你想要得序列化得类,这样可以让kryo把每个对象完整得类名写下来。要注册就得把 org.apache.spark.serializer.kryoserializer设置为true
 eg:

val conf = new sparkConf() conf.set("spark.serializer","org.apache.spark.serializer.kryoserializer") //注册类 conf.set("spark.kryo.registrationReauired","true") conf.registerKryoClasses(Array(classOf[MyClass],classOf[MyOtherClass]))

8.内存调优  a.spark会用60%得空间来存储RDD,20%存储数据混洗产生得数据,20%留给用户程序。用户可以自定义调节这些选项 来追求更好得性能。如果用户代码中分配了大量的对象,那么降低RDD存储和数据混洗存储所占得空间可以有效避 免内存不足得情况。 spark默认得cache()以memory_only的存储等级持久化数据。这意味着缓存新的RDD分区时空间不够,旧得分 区会被删除。等用到这些分区得时候在进行计算。 所以有时运用momory_and_disk存储级别调用 persist()方法会获得更好得效果 b.对于默认的缓存策略的另一个改进是缓存序列化后的对象而非直接缓存。可以通过memory_only_ser或者 memory_and_disk_ser得存储级别来实现。虽然缓存得时间边长了,但是可以显著减少jvm得垃圾回收时间。这 种场景 是你需要以对象的形式缓存大量的数据,或者注意到长时间的垃圾回收暂停。

4.4 sparkSQL 优化 1.广播JOIN表 spark.sql.autoBroadcastJoinThreshold,默认10485760(10M),在内存够用的情况下,提高其大小,可以将join中 的较小 的表广播出去,而不用进行网络数据传输

2.合理配置spark.sql.shuffle.partition;

缓存表 于一条SQL语句中可能多次使用到的表,可以对其进行缓存,使用SQLContext.cacheTable(tableName)或者DataFr ame.cache即可。Spark SQL会用内存 列存储的格式进行表的缓存。然后SparkSQL就可以仅仅扫描需要使用的列,并 且自动优化压缩,来最小化内存使用和GC开销。可以通过spark.sql.inMemoryColumnarStorage.batchSize这个参数, 默认10000,配置列存储单位 4.sparkSql参数调优 . Spark.sql.codegen 默认false 若设置为true,Spark SQL会将每个查询都编译为Java字节码。当查询量较大时, 这样的设置能够改进查询性能,但不适用于查询量小的情形 B. spark.sql.inMemoryColumnarStorage.compressed 默认true置为true时,会根据统计信息自动为每列选择压缩存储 .spark.sql.inMemoryColumnarStorage.batchSize 默认10000 需要视存储数据量而定,若此值设置过大,可能导 致内存溢出。  D .spark.sql.shuffle.partitions 默认200 配置连接或聚合数据移动数据时要使用的分区数。

  • spark.sql.parquet.compression.codec 默认snappy 可以设置多种编码方式,除了snappy外,还可以设置为 uncompressed,gzip和lzo  *注意 D 想是我之前给你们说过的数据的压缩方式,你们可以说gzip。sparkSql默认是snappy的方式。 这些选项可以放在配置文件中,也可以以编程方式设置SQLContext。例如:

val sqlContext = new SQLContext(sc) sqlContext.setConf("spark.sql.codegen", true)

 sqlContext.setConf("spark.sql.inMemoryColumnarStorage.batchSize", "10000")

当把spark.sql.codegen设置为true时,由于需要初始化编译器,在第一次执行查询时,可能会影响查询性能。

  5.基于SparlSql提供的优化器cataLyst进行优化。这是基于Scala的函数式编程元素。它提供的语法解析功能通过创建表达式树对 语法进行解析,每种数据类型和操作都可以视为表达式树的一种节点。这些节点皆继承自TreeNode抽象类。

6.spark.scheduler.mode 为 FAIR 模式,首先 spark.scheduler.mode 有 FIFO, FAIR 两种模式,FIFO 是说提交的job,都是 顺序执行的,后提交的 job 一定要等之前提交的 job 完全执行结束后才可以执行;FAIR 是说,如果之前提交的 job 没有 用完集群资源的话,后提交的job可以即刻开始运行。

BeanFactory和ApplicationContext的区别

        * BeanFactory                -- BeanFactory采取延迟加载,第一次getBean时才会初始化Bean
        * ApplicationContext        -- 在加载applicationContext.xml时候就会创建具体的Bean对象的实例

Hive应用

生产中我们经常把处理的逻辑sql写入到文件中,然后用hive –f filename.sql执行.

有时间需要动态传入一些变量信息,比如时间,表名.

  1. 使用env获取当前shell环境的环境变量
    1. eg: export datatime=’2017-11-10’
    2. select * from tabliname where datatime = ${env:datatime};
  2. 使用-hivevar方式传入
    1. hive -hivevar datatime =’ datatime’ -hivevar limit=10 -f filename.sql
    2. select * from tablename where datatime = ${hivevar:datatime} limit ${hivevar:limit}
  3. Order by和Sort by的区别?
    1. 使用order by会引发全局排序
      1. 如果在HADOOP上进行order by全排序,会导致所有的数据集中在一台reducer节点上,然后进行排序,这样很可能会超过单个节点的磁盘和内存存储能力导致任务失败。
      2. 使用distribute和sort进行分组排序
      3. select * from baofeng_click distribute by product_line sort by click desc;
      4. distribute by + sort by就是该替代方案,被distribute by设定的字段为KEY,数据会被HASH分发到不同的reducer机器上,然后sort by会对同一个reducer机器上的每组数据进行局部排序。

 

  1. 为什么hive导入数据很快?
    1. 将数据存到Hive的数据表时,Hive采用的是“读时模式”,意思是针对写操作不会做任何校验,只是简单的将文件复制到Hive的表对应的HDFS目录,存入数据的只是简单的文件复制和粘贴,所以导入数据速度非常的快。当读取、查询的时候,才会根据表模式来解释数据,这个时候如果遇到了不符合模式的数据,Hive会直接将数据解析成NULL。
    2. 好处?
      1. 向Hive表中新增数据非常的快,通常情况下我们直接用Hadoop命令将文件cp到一个HDFS目录,Hive就可以直接读这个目录下的数据;
      2. 好处是存储在Hive表中的数据跟Hive本身没有关系,这份数据也可以被其他工具比如Pig来处理;实现其他的数据分析.
  2. 如果Join表数据量小,使用MapJoin
    1. 如果确认用于Join的表数据量很小,比如只有100MB大小,可以使用/*+ MAPJOIN(a) */语法,这样Hive会先将小表分发到所有reducer节点的分布式缓存中并加载到内存,然后进行Join操作,由于减少了shuffle操作,性能有所提升。
    2. SELECT /*+ MAPJOIN(a) */ tablea.id, tableb.name FROM tablea join tableb on (tablea.id=tableb.id)
  3. NULL和数字相加的问题
    1. 如果有用到sum函数,但是发现sum的列中有NULL值,可以使用以下方法转换成0值:
    2. COALESCE(f, cast(0 AS bigint)),coalesce方法会返回列表中第一个不为NULL的字段,相当于如果第一个字段是NULL,就第二个字段。

hadoop面试相关

1、简述hdfs原理,以及各个模块的职责

  1. 客户端向namenode发送要上传文件的请求
  2. Namenode返回给用户是否能上传数据的状态
  3. 假如用户端需要上传一个1024M的文件,客户端会通过RPC请求namenode,并返回需要上传给哪些datanode(分配机器的距离以及空间的大小),namenode会选择就近原则分配机器
  4. 客户端请求建立block传输管道channel,上传数据
  5. 在上传时,datanode会与其他的机器建立连接,并把数据块传输到其他机器上
  6. Datanode向namenode汇报自己的存储情况以及自己的信息
  7. 当第一个块上传完后,再去执行其他的复制和传输

 

 

2、Mr的工作原理

  1. 当执行mr程序时,会执行一个job
  2. 客户端的jobclick会请求namenode的jobTracker要执行的任务
  3. Jobclick会去hdfs复制作业的资源文件
  4. 客户端的jobclick会向namenode提交作业,让namenode做准备
  5. Namenode的jobTracker会去初始化创建的对象
  6. Namenode会获取hdfs划分的分区
  7. Namenode去检查TaskTracker的心跳信息,查看存活的机器
  8. 当执行的datanode执行任务时,datanode会去hdfs获取作业资源的文件
  9. TaskTracker会去执行代码,并登陆jvm的执行渠道
  10. Jvm执行mapTask或者reduceTask
  11. 执行结束

 

 

3、fsimage和edit的区别

大家都知道namenode与secondary namenode 的关系,当他们要进行数据同步时叫做checkpoint时就用到了fsimage与edit,fsimage是保存最新的元数据的信息,当fsimage数据到一定的大小事会去生成一个新的文件来保存元数据的信息,这个新的文件就是edit,edit会回滚最新的数据

 

 

4、hdfs中的block默认保存几份

不管是hadoop1.x 还是hadoop2.x 都是默认的保存三份,可以通过参数dfs.replication进行修改,副本的数目要根据机器的个数来确定

 

 

 

 

 

  1. 列举几个配置文件优化

 

Core-site.xml文件优化

   

fs.trash.interval

默认值: 0

说明: 这个是开启hdfs文件删除自动转移到垃圾箱的选项,值为垃圾箱文件清除时间。一般开启这个会比较好,以防错误删除重要文件。单位是分钟

 

dfs.namenode.handler.count

默认值:10

说明:hadoop系统里启动的任务线程数,这里改为40,同样可以尝试该值大小对效率的影响变化进行最合适的值的设定

 

mapreduce.tasktracker.http.threads

默认值:40

说明:map和reduce是通过http进行数据传输的,这个是设置传输的并行线程数

 

 

  1. 数据倾斜如何发生,并给出优化方案

数据的倾斜主要是两个的数据相差的数量不在一个级别上,在只想任务时就造成了数据的倾斜,可以通过分区的方法减少reduce数据倾斜性能的方法,例如;抽样和范围的分区、自定义分区、数据大小倾斜的自定义侧

 

  1. hadoop安装步骤

1.创建 hadoop 帐户。

2.setup.改 IP。

3.安装 java,并修改/etc/profile 文件,配置 java 的环境变量。

4.修改 Host 文件域名。

5.安装 SSH,配置无密钥通信。

6.解压 hadoop。

7.配置 conf 文件下 hadoop-env.sh、core-site.sh、mapre-site.sh、hdfs-site.sh。

8.配置 hadoop 的环境变量。

9.Hadoop namenode -format

10.Start-all.sh

 

8.简单概述hadoop中的角色的分配以及功能

Namenode:负责管理元数据的信息

SecondaryNameNode:做namenode冷备份,对于namenode的机器宕掉后能快速切换到制定的SecondaryNameNode上

DateNode:主要做储存数据的。

JobTracker:管理任务,并把任务分配到taskTasker

TaskTracker:执行任务的

 

9.怎样快速的杀死一个job

1、执行hadoop  job -list  拿到job-id

2、Hadoop job kill hadoop-id

 

10.新增一个节点时怎样快速的启动

Hadoop-daemon.sh start datanode

 

11.简单概述hadoop的join的方法

Hadoop 常用的jion有reduce side join  , map side  join ,  SemiJoin 不过reduce side join 与 map side join 比较常用,不过都是比较耗时的

 

12.简单概述hadoop的combiner与partition的区别

combine和partition都是函数,中间的步骤应该只有shuffle, combine分为map端和reduce端,作用是把同一个key的键值对合并在一起,可以自定义的,partition是分割map每个节点的结果,按照key分别映射给不同的reduce,也是可以自定义的。这里其实可以理解归类

 

13.hdfs 的数据压缩算法

   Hadoop 的压缩算法有很多,其中比较常用的就是gzip算法与bzip2算法,都可以可通过CompressionCodec来实现

 

  1. hadoop的调度

Hadoop 的调度有三种:

fifo的调度:hadoop的默认的,这种方式是按照作业的优先级的高低与到达时间的先后执行

公平调度器:就是分配用户的公平获取共享集群

容量调度器:让程序都能获得到执行的能力,在队列中获得资源

 

  1. reduce后输出的数据量有多大

Reduce端输出的数据量取决于map端产生的数据量

 

  1. datanode 在什么情况下不会备份

Hadoop保存的三个副本如果不算备份的话,那就是在正常运行的情况下不会备份,也是就是在设置副本为1的时候不会备份,说白了就是单台机器呗!!还有datanode 在强制关闭或者非正常断电不会备份

 

  1. combiner出现在那个过程

MapReduce的map过程,可以联想一下wordcount案例,在map阶段组合相同的key,减少reduce阶段的数据传输量

 

 

 

 

  1. Hdfs的体系结构

HDFS有 namenode、secondraynamenode、datanode 组成。

namenode 负责管理 datanode 和记录元数据

secondraynamenode 负责合并日志

datanode 负责存储数据

 

  1. hadoop flush 的过程

将数据落到磁盘,把数据保存起来

 

  1. 什么是队列queue

队列的实现是链表,消费的顺序是先进先出

 

  1. 如果有三个 datanode,当有一个 datanode 出现错误会怎样

第一不会给存储带来影响,因为有其他的副本保存着,不过建议尽快修复

第二会影响运算的效率,机器少了,reduce在保存数据时选择就少了,一个数据的块就大了所以就会慢

 

  1. mapReduce 的执行过程

1、Map任务处理

1.1 读取HDFS中的文件。每一行解析成一个。每一个键值对调用一次map函数。                                 

1.2 覆盖map(),接收1.1产生的,进行处理,转换为新的输出。          

1.3 对1.2输出的进行分区。默认分为一个区。

1.4 对不同分区中的数据进行排序(按照k)、分组。分组指的是相同key的value放到一个集合中。

1.5 (可选)对分组后的数据进行归约(combiner)

 

2、Reduce任务处理

2.1 多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点上。(shuffle)

2.2 对多个map的输出进行合并、排序。覆盖reduce函数,接收的是分组后的数据,实现自己的业务逻辑,处理后,产生新的输出。

2.3 对reduce输出的写到HDFS中。

 

  1. hadoop 的机架感知

数据块会优先储存在离namenode近的机器或者说成离namenode机架近的机器上,正好是验证了那句话不走网络就不走网络,不用磁盘就不用磁盘

 

  1. datanode 首次加入 cluster 的时候,如果 log 报告不兼容文件版本,那需要namenode 执行格式化操作,这样处理的原因是

这样处理是不合理的,因为namenode 格式化操作,是对文件系统进行格式

化,namenode 格式化时清空 dfs/name 两个目录下的所有文件,之后,会在目

录 dfs.name.dir 下创建文件。

文本不兼容,有可能时 namenode 与 datanode 的 数据里的 namespaceID、

clusterID 不一致,找到两个 ID 位置,修改为一样即可解决

 

  1. MapReduce 中排序发生在哪几个阶段?这些排序是否可以避免?为什么

一个 MapReduce 作业由 Map 阶段和 Reduce 阶段两部分组成,这两阶段会对数

据排序,从这个意义上说,MapReduce 框架本质就是一个 Distributed Sort。

在 Map阶段,在 Map 阶段,Map Task 会在本地磁盘输出一个按照 key 排序(采用的是快速排序)的文件(中间可能产生多个文件,但最终会合并成一个)

在 Reduce 阶段,每个 Reduce Task 会对收到的数据排序,这样,数据便按照 Key 分成了若干组,之后以组为单位交给 reduce()处理。很多人的误解在 Map 阶段,如果不使用 Combiner便不会排序,这是错误的,不管你用不用 Combiner,Map Task 均会对产生的数据排序(如果没有 Reduce Task,则不会排序,实际上 Map 阶段的排序就是为了减轻 Reduce

端排序负载)。由于这些排序是 MapReduce 自动完成的,用户无法控制,因此,在

hadoop 1.x 中无法避免,也不可以关闭,但 hadoop2.x 是可以关闭的

 

  1. hadoop的shuffer的概念

Shuffer是一个过程,是在map端到reduce端,在调reduce数据之前都叫shuffer,主要是分区与排序,也就是内部的缓存分区以及分发(是reduce来拉数据的)和传输

 

  1. hadoop的优化

1、优化的思路可以从配置文件和系统以及代码的设计思路来优化

2、配置文件的优化:调节适当的参数,在调参数时要进行测试

3、代码的优化:combiner的个数尽量与reduce的个数相同,数据的类型保持一致,可以减少拆包与封包的进度

4、系统的优化:可以设置linux系统打开最大的文件数预计网络的带宽MTU的配置

5、为 job 添加一个 Combiner,可以大大的减少shuffer阶段的mapTask拷贝过来给远程的   reduce task的数据量,一般而言combiner与reduce相同。

6、在开发中尽量使用stringBuffer而不是string,string的模式是read-only的,如果对它进行修改,会产生临时的对象,二stringBuffer是可修改的,不会产生临时对象。

7、修改以下配置:

一是修改 mapred-site.xml 文件

修改最大槽位数

槽位数是在各个 tasktracker 上的 mapred-site.xml 上设置的,默认都是 2

mapred.tasktracker.map.tasks.maximum

Maptask的最大数

2

mapred.tasktracker.reduce.tasks.maximum

Reduceducetask 的最大数

2

 

 

调整心跳间隔

集群规模小于 300 时,心跳间隔为 300 毫秒

mapreduce.jobtracker.heartbeat.interval.min 心跳时间

mapred.heartbeats.in.second 集群每增加多少节点,时间增加下面的值

mapreduce.jobtracker.heartbeat.scaling.factor 集群每增加上面的个数,心跳增多少

 

启动带外心跳

mapreduce.tasktracker.outofband.heartbeat    默认是 false

 

配置多块磁盘

mapreduce.local.dir

 

配置 RPC hander 数目

mapred.job.tracker.handler.count 默认是 10,可以改成 50,根据机器的能力

 

配置 HTTP 线程数目

tasktracker.http.threads 默认是 40,可以改成 100 根据机器的能力

 

选择合适的压缩方式

以 snappy 为例:

mapred.compress.map.output

true

mapred.map.output.compression.codec

org.apache.hadoop.io.compress.SnappyCodec

 

  1. 怎样决定mapreduce的中的map以及reduce的数量

在mapreduce中map是由块的大小来决定的,reduce的数量可以按照用户的业务来配置

 

  1. 两个文件合并的问题

给定a、b两个文件,各存放50亿个url,每个url各占用64字节,内存限制是4G,如何找出a、b文件共同的url

 

主要的思想是把文件分开进行计算,在对每个文件进行对比,得出相同的URL,因为以上说是含有相同的URL所以不用考虑数据倾斜的问题。详细的解题思路为:

 

可以估计每个文件的大小为5G*64=300G,远大于4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法

 

 

遍历文件a,对每个url求取hash(url)%1000,然后根据所得值将url分别存储到1000个小文件(设为a0,a1,...a999)当中。这样每个小文件的大小约为300M。遍历文件b,采取和a相同的方法将url分别存储到1000个小文件(b0,b1....b999)中。这样处理后,所有可能相同的url都在对应的小文件(a0 vs b0, a1 vs b1....a999 vs b999)当中,不对应的小文件(比如a0 vs b99)不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。

 
     比如对于a0 vs b0,我们可以遍历a0,将其中的url存储到hash_map当中。然后遍历b0,如果url在hash_map中,则说明此url在a和b中同时存在,保存到文件中即可。 


     如果分成的小文件不均匀,导致有些小文件太大(比如大于2G),可以考虑将这些太大的小文件再按类似的方法分成小小文件即可

 

  1. 简述hadoop的sequencefile的格式,并说明什么是JAVA的序列化,如何实现JAVA的序列化

1、hadoop的序列化(sequencefile)是以二进制的形式来保存的

2、Java的序列化是讲将象的内容进行流化

3、实现序列化需要实现Serializable接口便可以了

 

  1. 简单概述一下hadoop1与hadoop2的区别

Hadoop2与hadoop1最大的区别在于HDFS的架构与mapreduce,而且速度上有很大的提升,hadoop2最主要的两个变化是:namenode可以集群的部署了,hadoop2中的mapreduce中的jobTracker中的资源调度器与生命周期管理拆分成两个独立的组件,并命名为YARN

 

  1. YARN的新特性

YARN是hadoop2.x之后才出的,主要是hadoop的HA(也就是集群),磁盘的容错,资源调度器

 

  1. hadoop join的原理

实现两个表的join首先在map端需要把表标示一下,把其中的一个表打标签,到reduce端再进行笛卡尔积的运算,就是reduce端进行的实际的链接操作

 

  1. hadoop的排序

所谓全排序就是不仅在每个分区上是有序的,它所有的分区按大小合并起来也是有序的。该案例是求取每年的最高气温。

实现全排序的方式有三种:
1、只定义一个reduce任务,容易产生数据倾斜。
2、自定义分区函数,即自行设置分解区间
3、使用hadoop采样器机制,通过采样器生成分区文件,结合hadoop的TotalOrderPartitioner进行分区划分分区。

 

 

 

 

  1. Hadoop的二次排序

 Key是可以排序的,需要对value排序。

 二次排序就是首先按照第一字段排序,然后再对第一字段相同的行按照第二字段排序,且不能破坏第一次排序的结果。

 

二次排序分为以下几个步骤:

    1、自定义组合key,即使得该原生数据的value能进行排序,原生的key-value组成一个key

    2、自定义分区类,按照年份分区

    3、定义分组对比器,使得同一组的数据调用同一个reduce方法

4、定义key排序对比器

 

  1. 请描述mapreduce中shuffer阶段的工作流程,如何优化shuffer阶段的?

Mapreduce的shuffer是处在map task到reduce task的这段过程中,首先会进入到copy过程,会通过http方式请求map task所在的task Tracker获取map task 的输出的文件,因此当map  task结束,这些文件就会落到磁盘中,merge是在map端的动作。只是在map拷贝过来的数值,会放到内存缓冲区中,给shuffer使用,reduce阶段,不断的merge后最终会把文件放到磁盘中

 

  1. mapreduce的combiner的作用是什么,什么时候不宜使用

Mapreduce中的Combiner就是为了避免map任务和reduce任务之间的数据传输量大而设置的,Hadoop允许用户针对map task的输出指定一个合并函数。即为了减少传输到Reduce中的数据量。它主要是为了削减Mapper的输出从而减少网络带宽和Reducer之上的负载。

在数据量较少时不宜使用

 

 

Spark面试相关

  1. 简述RDD及其特性

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合

Dataset:就是一个集合,用于存放数据的

Distributed:分布式,可以并行在集群计算

Resilient:表示弹性的

         弹性表示:

               RDD中的数据可以存储在内存或者是磁盘

                  RDD中的分区是可以改变的

 

五大特性:

A list of partitions

一个分区列表,RDD中的数据都存在一个分区列表里面

 

A function for computing each split

作用在每一个分区中的函数

 

A list of dependencies on other RDDs

一个RDD依赖于其他多个RDD,这个点很重要,RDD的容错机制就是依据这个特性而来的

 

Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)

可选的,针对于kv类型的RDD才具有这个特性,作用是决定了数据的来源以及数据处理后的去向

 

Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

可选项,数据本地性,数据位置最优

 

  1. 概述一下spark中的常用算子区别(map、mapPartitions、foreach、foreachPartition)

map:用于遍历RDD,将函数f应用于每一个元素,返回新的RDD(transformation算子

foreach:用于遍历RDD,将函数f应用于每一个元素,无返回值(action算子)

mapPartitions:用于遍历操作RDD中的每一个分区,返回生成一个新的RDD(transformation算子)

foreachPartition: 用于遍历操作RDD中的每一个分区。无返回值(action算子)

总结:一般使用mapPartitions或者foreachPartition算子比map和foreach更加高效,推荐使用

  1. 谈谈spark中的宽窄依赖

RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)

宽依赖:指的是多个子RDD的Partition会依赖同一个父RDD的Partition

窄依赖:指的是每一个父RDD的Partition最多被子RDD的一个Partition使用

 

  1. spark中如何划分stage
  1. Spark Application中可以因为不同的Action触发众多的job,一个Application中可以有很多的job,每个job是由一个或者多个Stage构成的,后面的Stage依赖于前面的Stage,也就是说只有前面依赖的Stage计算完毕后,后面的Stage才会运行 
  2. Stage划分的依据就是宽依赖,何时产生宽依赖,例如reduceByKey,groupByKey的算子,会导致宽依赖的产生
  3. 由Action(例如collect)导致了SparkContext.runJob的执行,最终导致了DAG 中的submitJob的执行,其核心是通过发送一个case class JobSubmitted对象给eventProcessLoop。
    eventProcessLoop是DAGSchedulerEventProcessLoop的具体实例,而DAGSchedulerEventProcessLoop是eventLoop的子类,具体实现EventLoop的onReceive方法,onReceive方法转过来回调doOnReceive 
  4. 在doOnReceive中通过模式匹配的方法把执行路由到
  5. 在handleJobSubmitted中首先创建finalStage,创建finalStage时候会建立父Stage的依赖链条

 

总结:依赖是从代码的逻辑层面上来展开说的,可以简单点说:先介绍什么是RDD中的宽窄依赖,然后在根据DAG有向无环图进行划分,从当前job的最后一个算子往前推,遇到宽依赖,那么当前在这个批次中的所有算子操作都划分成一个stage,然后继续按照这种方式在继续往前推,如在遇到宽依赖,又划分成一个stage,一直到最前面的一个算子。最后整个job会被划分成多个stage,而stage之间又存在依赖关系,后面的stage依赖于前面的stage

 

  1. spark-submit的时候如何引入外部jar包

在通过spark-submit提交任务时,可以通过添加配置参数来指定

–driver-class-path 外部jar包

–jars 外部jar包

 

 

 

 

 

 

  1. spark 如何防止内存溢出

driver端的内存溢出:

 

可以增大driver的内存参数:spark.driver.memory (default 1g)

这个参数用来设置Driver的内存。在Spark程序中,SparkContext,DAGScheduler都是运行在Driver端的。对应rdd的Stage切分也是在Driver端运行,如果用户自己写的程序有过多的步骤,切分出过多的Stage,这部分信息消耗的是Driver的内存,这个时候就需要调大Driver的内存

 

map过程产生大量对象导致内存溢出:

 

这种溢出的原因是在单个map中产生了大量的对象导致的,例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),这个操作在rdd中,每个对象都产生了10000个对象,这肯定很容易产生内存溢出的问题。针对这种问题,在不增加内存的情况下,可以通过减少每个Task的大小,以便达到每个Task即使产生大量的对象Executor的内存也能够装得下。具体做法可以在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。例如:rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)

 

面对这种问题注意,不能使用rdd.coalesce方法,这个方法只能减少分区,不能增加分区,不会有shuffle的过程

 

数据不平衡导致内存溢出:

 

数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题,解决方法和上面说的类似,就是调用repartition重新分区。这里就不再累赘了

 

shuffle后内存溢出:

 

shuffle内存溢出的情况可以说都是shuffle后,单个文件过大导致的。在Spark中,join,reduceByKey这一类型的过程,都会有shuffle的过程,在shuffle的使用,需要传入一个partitioner,大部分Spark中的shuffle操作,默认的partitioner都是HashPatitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions) , spark.default.parallelism参数只对HashPartitioner有效,所以如果是别的Partitioner或者自己实现的Partitioner就不能使用spark.default.parallelism这个参数来控制shuffle的并发量了。如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量

 

standalone模式下资源分配不均匀导致内存溢出:

在standalone的模式下如果配置了–total-executor-cores 和 –executor-memory 这两个参数,但是没有配置–executor-cores这个参数的话,就有可能导致,每个Executor的memory是一样的,但是cores的数量不同,那么在cores数量多的Executor中,由于能够同时执行多个Task,就容易导致内存溢出的情况。这种情况的解决方法就是同时配置–executor-cores或者spark.executor.cores参数,确保Executor资源分配均匀

 

使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache()

 

rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等价的,在内存不足的时候rdd.cache()的数据会丢失,再次使用的时候会重算,而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在内存不足的时候会存储在磁盘,避免重算,只是消耗点IO时间

 

  1. spark中cache和persist的区别

cache:缓存数据,默认是缓存在内存中,其本质还是调用persist

persist:缓存数据,有丰富的数据缓存策略。数据可以保存在内存也可以保存在磁盘中,使用的时候指定对应的缓存级别就可以了

 

  1. 简要描述Spark分布式集群搭建的步骤

主要是引入了zookeeper,搭建高可用的spark集群(HA)

 

  1. spark中的数据倾斜的现象、原因、后果

数据倾斜的现象

多数task执行速度较快,少数task执行时间非常长,或者等待很长时间后提示你内存不足,执行失败

 

数据倾斜的原因

数据问题

key本身分布不均衡(包括大量的key为空)

key的设置不合理

 

spark使用问题

shuffle时的并发度不够

计算方式有误

 

数据倾斜的后果

spark中的stage的执行时间受限于最后那个执行完成的task,因此运行缓慢的任务会拖垮整个程序的运行速度(分布式程序运行的速度是由最慢的那个task决定的)

 

过多的数据在同一个task中运行,将会把executor撑爆

 

 

 

 

 

  1. 如何解决spark中的数据倾斜问题

发现数据倾斜的时候,不要急于提高executor的资源,修改参数或是修改程序,首先要检查数据本身,是否存在异常数据

1、数据问题造成的数据倾斜

找出异常的key

如果任务长时间卡在最后最后1个(几个)任务,首先要对key进行抽样分析,判断是哪些key造成的。选取key,对数据进行抽样,统计出现的次数,根据出现次数大小排序取出前几个

比如:

df.select(“key”).sample(false,0.1).(k=>(k,1)).reduceBykey(+).map(k=>(k._2,k._1)).sortByKey(false).take(10)

如果发现多数数据分布都较为平均,而个别数据比其他数据大上若干个数量级,则说明发生了数据倾斜

 

经过分析,倾斜的数据主要有以下三种情况:

  1. null(空值)或是一些无意义的信息()之类的,大多是这个原因引起
  2. 无效数据,大量重复的测试数据或是对结果影响不大的有效数据
  3. 有效数据,业务导致的正常数据分布

 

解决办法:

  1. 第1,2种情况,直接对数据进行过滤即可(因为该数据对当前业务不会产生影响)
  2. 第3种情况则需要进行一些特殊操作,常见的有以下几种做法
  1. 隔离执行,将异常的key过滤出来单独处理,最后与正常数据的处理结果进行union操作
  2. 对key先添加随机值,进行操作后,去掉随机值,再进行一次操作
  3. 使用reduceByKey 代替 groupByKey(reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义.)
  4. 使用map join

 

[案例]

如果使用reduceByKey因为数据倾斜造成运行失败的问题。具体操作流程如下:

  1. 将原始的 key 转化为 key + 随机值(例如Random.nextInt)
  2. 对数据进行 reduceByKey(func)
  3. 将 key + 随机值 转成 key
  4. 再对数据进行 reduceByKey(func)

 

案例操作流程分析:

假设说有倾斜的Key,我们给所有的Key加上一个随机数,然后进行reduceByKey操作;此时同一个Key会有不同的随机数前缀,在进行reduceByKey操作的时候原来的一个非常大的倾斜的Key就分而治之变成若干个更小的Key,不过此时结果和原来不一样,怎么破?进行map操作,目的是把随机数前缀去掉,然后再次进行reduceByKey操作。(当然,如果你很无聊,可以再次做随机数前缀),这样我们就可以把原本倾斜的Key通过分而治之方案分散开来,最后又进行了全局聚合

注意1: 如果此时依旧存在问题,建议筛选出倾斜的数据单独处理。最后将这份数据与正常的数据进行union即可

注意2: 单独处理异常数据时,可以配合使用Map Join解决

 

2、spark使用不当造成的数据倾斜

提高shuffle并行度

(1)、dataFrame和sparkSql可以设置spark.sql.shuffle.partitions参数控制shuffle的并发度,默认为200

(2)、rdd操作可以设置spark.default.parallelism控制并发度,默认参数由不同的Cluster Manager控制

局限性: 只是让每个task执行更少的不同的key。无法解决个别key特别大的情况造成的倾斜,如果某些key的大小非常大,即使一个task单独执行它,也会受到数据倾斜的困扰

 

使用map join 代替reduce join

在小表不是特别大(取决于你的executor大小)的情况下使用,可以使程序避免shuffle的过程,自然也就没有数据倾斜的困扰了.(详细见http://blog.csdn.net/lsshlsw/article/details/50834858、http://blog.csdn.net/lsshlsw/article/details/48694893) 

局限性: 因为是先将小数据发送到每个executor上,所以数据量不能太大

 

  1. flume整合sparkStreaming问题
  1. 如何实现sparkStreaming读取flume中的数据

前期经过技术调研,查看官网相关资料,发现sparkStreaming整合flume有2种模式,一种是拉模式,一种是推模式,然后在简单的聊聊这2种模式的特点,以及如何部署实现,需要做哪些事情,最后对比两种模式的特点,选择那种模式更好

推模式:Flume将数据Push推给Spark Streaming

拉模式:Spark Streaming从flume 中Poll拉取数据

 

  1. 在实际开发的时候是如何保证数据不丢失的

flume那边采用的channel是将数据落地到磁盘中,保证数据源端安全性(可以在补充一下,flume在这里的channel可以设置为memory内存中,提高数据接收处理的效率,但是由于数据在内存中,安全机制保证不了,故选择channel为磁盘存储。整个流程运行有一点的延迟性)

sparkStreaming通过拉模式整合的时候,使用了FlumeUtils这样一个类,该类是需要依赖一个额外的jar包(spark-streaming-flume_2.10)

要想保证数据不丢失,数据的准确性,可以在构建StreamingConext的时候,利用StreamingContext.getOrCreate(checkpoint, creatingFunc: () => StreamingContext)来创建一个StreamingContext,使用StreamingContext.getOrCreate来创建StreamingContext对象,传入的第一个参数是checkpoint的存放目录,第二参数是生成StreamingContext对象的用户自定义函数。如果checkpoint的存放目录存在,则从这个目录中生成StreamingContext对象;如果不存在,才会调用第二个函数来生成新的StreamingContext对象。在creatingFunc函数中,除了生成一个新的StreamingContext操作,还需要完成各种操作,然后调用ssc.checkpoint(checkpointDirectory)来初始化checkpoint功能,最后再返回StreamingContext对象。
    这样,在StreamingContext.getOrCreate之后,就可以直接调用start()函数来启动(或者是从中断点继续运行)流式应用了。如果有其他在启动或继续运行都要做的工作,可以在start()调用前执行

 

流式计算中使用checkpoint的作用

保存元数据,包括流式应用的配置、流式没崩溃之前定义的各种操作、未完成所有操作的batch。元数据被存储到容忍失败的存储系统上,如HDFS。这种ckeckpoint主要针对driver失败后的修复

保存流式数据,也是存储到容忍失败的存储系统上,如HDFS。这种ckeckpoint主要针对window operation、有状态的操作。无论是driver失败了,还是worker失败了,这种checkpoint都够快速恢复,而不需要将很长的历史数据都重新计算一遍(以便得到当前的状态)

 

设置流式数据checkpoint的周期

对于一个需要做checkpoint的DStream结构,可以通过调用DStream.checkpoint(checkpointInterval)来设置ckeckpoint的周期,经验上一般将这个checkpoint周期设置成batch周期的5至10倍

 

使用write ahead logs功能

这是一个可选功能,建议加上。这个功能将使得输入数据写入之前配置的checkpoint目录。这样有状态的数据可以从上一个checkpoint开始计算。开启的方法是把spark.streaming.receiver.writeAheadLogs.enable这个property设置为true。另外,由于输入RDD的默认StorageLevel是MEMORY_AND_DISK_2,即数据会在两台worker上做replication。实际上,Spark Streaming模式下,任何从网络输入数据的Receiver(如kafka、flume、socket)都会在两台机器上做数据备份。如果开启了write ahead logs的功能,建议把StorageLevel改成MEMORY_AND_DISK_SER。修改的方法是,在创建RDD时由参数传入

 

使用以上的checkpoint机制,确实可以保证数据0丢失。但是一个前提条件是,数据发送端必须要有缓存功能,这样才能保证在spark应用重启期间,数据发送端不会因为spark streaming服务不可用而把数据丢弃。而flume具备这种特性,同样kafka也具备

 

  1. Spark Streaming的数据可靠性

有了checkpoint机制、write ahead log机制、Receiver缓存机器、可靠的Receiver(即数据接收并备份成功后会发送ack),可以保证无论是worker失效还是driver失效,都是数据0丢失。原因是:如果没有Receiver服务的worker失效了,RDD数据可以依赖血统来重新计算;如果Receiver所在worker失败了,由于Reciever是可靠的,并有write ahead log机制,则收到的数据可以保证不丢;如果driver失败了,可以从checkpoint中恢复数据重新构建

 

 

 

 

  1. kafka整合sparkStreaming问题
  1. 如何实现sparkStreaming读取kafka中的数据

可以这样说:在kafka0.10版本之前有二种方式与sparkStreaming整合,一种是基于receiver,一种是direct,然后分别阐述这2种方式分别是什么

receiver:是采用了kafka高级api,利用receiver接收器来接受kafka topic中的数据,从kafka接收来的数据会存储在spark的executor中,之后spark streaming提交的job会处理这些数据,kafka中topic的偏移量是保存在zk中的

基本使用:

val kafkaStream = KafkaUtils.createStream(

streamingContext,
[ZK quorum],

 [consumer group id],

[per-topic number of Kafka partitions to consume])

 

还有几个需要注意的点:

在Receiver的方式中,Spark中的partition和kafka中的partition并不是相关的,所以如果我们加大每个topic的partition数量,仅仅是增加线程来处理由单一Receiver消费的主题。但是这并没有增加Spark在处理数据上的并行度.

对于不同的Group和topic我们可以使用多个Receiver创建不同的Dstream来并行接收数据,之后可以利用union来统一成一个Dstream

在默认配置下,这种方式可能会因为底层的失败而丢失数据. 因为receiver一直在接收数据,在其已经通知zookeeper数据接收完成但是还没有处理的时候,executor突然挂掉(或是driver挂掉通知executor关闭),缓存在其中的数据就会丢失. 如果希望做到高可靠, 让数据零丢失,如果我们启用了Write Ahead Logs(spark.streaming.receiver.writeAheadLog.enable=true)该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中. 所以, 即使底层节点出现了失败, 也可以使用预写日志中的数据进行恢复. 复制到文件系统如HDFS,那么storage level需要设置成 StorageLevel.MEMORY_AND_DISK_SER,也就是KafkaUtils.createStream(…, StorageLevel.MEMORY_AND_DISK_SER) 

 

direct:在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,之后根据设定的maxRatePerPartition来处理每个batch。(设置spark.streaming.kafka.maxRatePerPartition=10000。限制每秒钟从topic的每个partition最多消费的消息条数)

 

对比这两种方式的优缺点:

采用receiver方式:这种方式可以保证数据不丢失,但是无法保证数据只被处理一次,WAL实现的是At-least-once语义(至少被处理一次),如果在写入到外部存储的数据还没有将offset更新到zookeeper就挂掉,这些数据将会被反复消费. 同时,降低了程序的吞吐量

 

 

 

采用direct方式:相比Receiver模式而言能够确保机制更加健壮. 区别于使用Receiver来被动接收数据, Direct模式会周期性地主动查询Kafka, 来获得每个topic+partition的最新的offset, 从而定义每个batch的offset的范围. 当处理数据的job启动时, 就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据

 

优点:

  1. 简化并行读取

如果要读取多个partition, 不需要创建多个输入DStream然后对它们进行union操作. Spark会创建跟Kafka partition一样多的RDD partition, 并且会并行从Kafka中读取数据. 所以在Kafka partition和RDD partition之间, 有一个一对一的映射关系

 

  1. 高性能

如果要保证零数据丢失, 在基于receiver的方式中, 需要开启WAL机制. 这种方式其实效率低下, 因为数据实际上被复制了两份, Kafka自己本身就有高可靠的机制, 会对数据复制一份, 而这里又会复制一份到WAL中. 而基于direct的方式, 不依赖Receiver, 不需要开启WAL机制, 只要Kafka中作了数据的复制, 那么就可以通过Kafka的副本进行恢复

 

  1. 一次且仅一次的事务机制

基于receiver的方式, 是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的. 这是消费Kafka数据的传统方式. 这种方式配合着WAL机制可以保证数据零丢失的高可靠性, 但是却无法保证数据被处理一次且仅一次, 可能会处理两次. 因为Spark和ZooKeeper之间可能是不同步的. 基于direct的方式, 使用kafka的简单api, Spark Streaming自己就负责追踪消费的offset, 并保存在checkpoint中. Spark自己一定是同步的, 因此可以保证数据是消费一次且仅消费一次。不过需要自己完成将offset写入zk的过程,在官方文档中都有相应介绍.
*简单代码实例: 
* messages.foreachRDD(rdd=>{
val message = rdd.map(_._2)      //对数据进行一些操作
message.map(method)          //更新zk上的offset (自己实现)
updateZKOffsets(rdd)
})
* sparkStreaming程序自己消费完成后,自己主动去更新zk上面的偏移量。也可以将zk中的偏移量保存在mysql或者redis数据库中,下次重启的时候,直接读取mysql或者redis中的偏移量,获取到上次消费的偏移量,接着读取数据

Hadoop相关调优

  • 应用程序编写规范
  1. 设置combiner

   对于一大批MapReduce程序,如果可以设置一个Combiner,那么对于提高作业性能是十分有帮助的。Combiner可减少Map Task中间输出的结果,从而减少各个Reduce Task的远程拷贝数据量,最终表现为Map Task和Reduce Task执行时间缩短。

 

  1. 选择合理的Writable类型

   在MapReduce模型中,Map Task和Reduce Task的输入和输出类型均为Writable。Hadoop本身已经提供了很多Writable实现,包括IntWritable、FloatWritable。为应用程序处理的数据选择合适的Writable类型可大大提升性能。比如处理整数类型数据时,直接采用IntWritable比先以Text类型读入在转换为整数类型要高效。如果输出整数的大部分可用一个或两个字节保存,那么直接采用VIntWritable或者VLongWritable,它们采用了变长整型的编码方式,可以大大减少输出数据量。

 

  • 作业级别参数调优
  1. 规划合理的任务数目

  在hadoop中,每个maptask处理一个input split,imput split的划分方式是由用户自定义的InputFormat决定的,默认情况下,由以下三个参数决定

  mapred.min.split.size :Input split的最小值,默认是1

  mapred.max.split.szie :Input Split的最大值

   dfs.block.size:HDFS 中一个block大小   默认值128MB

 

golsize:它是用户期望的input split数目 = total size / numsplit,其中total size为文件的总大小,numsplit为用户设置的map task 个数,默认是1

 

splitSize = max{minsize,min{golsize,blocksize}},如果想让inputSize尺寸大于block尺寸,直接增大配置参数mapred.min.split.size即可

      

  1. 增加输入文件的副本数

如果一个作业并行执行的任务数目非常多,那么这些任务共同的输入文件可能成为瓶颈。为防止多个任务并行读取一个文件内容造成瓶颈,用户可根据需要增加输入文件的副本数目

 

  1. 启动推测执行机制

推测执行是Hadoop对“拖后腿”的任务的一种优化机制,当一个作业的某些任务运行速度明显慢于同作业的其他任务时,Hadoop会在另一个节点上为“慢任务”启动一个备份任务,这样两个任务同时处理一份数据,而Hadoop最终会将优先完成的那个任务的结果作为最终结果,并将另一个任务杀掉

 

  1. 设置失败容忍度

 Hadoop运行设置任务级别和作业级别的失败容忍度。作业级别的失败容忍度是指Hadoop允许每个作业有一定比例的任务运行失败,这部分任务对应的输入数据将被忽略;

 任务级别的失败容忍度是指Hadoop允许任务失败后再在另外节点上尝试运行,如果一个任务经过若干次尝试运行后仍然运行失败,那么Hadoop才会最终认为该任务运行失败。

 用户应该根据应用程序的特点设置合理的失败容忍度,以尽快让作业运行完成和避免没必要的资源浪费。

 

  1. 适当打开JVM重用功能

   为了实现任务隔离,Hadoop将每个任务放到一个单独的JVM中执行,而对于执行时间较短的任务,JVM启动和关闭的时间将占用很大比例时间,为此,用户可以启用JVM重用功能,这样一个JVM可连续启动多个同类型的任务

 

  1. 设置任务超时时间

   如果一个任务在一定的时间内未汇报进度,则TaskTracker会主动将其杀死,从而在另一个节点上重新启动执行。用户可根据实际需要配置任务超时时间

 

  1. 合理使用DistributedCache

   一般情况下,得到外部文件有两种方法:一种是外部文件与应用程序jar包一起放到客户端,当提交作业时由客户端上传到HDFS的一个目录下,然后通过Distributed Cache分发到各个节点上;另一种方法是事先将外部文件直接放到HDFS上,从效率上讲,第二种方法更高效。第二种方法不仅节省了客户端上传文件的时间,还隐含着告诉DistributedCache:"请将文件下载到各个节点的pubic级别共享目录中”,这样,后续所有的作业可重用已经下载好的文件,不必重复下载

 

  1. 跳过坏记录

Hadoop为用户提供了跳过坏记录的功能,当一条或几条坏数据记录导致任务运行失败时,Hadoop可自动识别并跳过这些坏记录

 

  1. 提高作业优先级

所有Hadoop作业调度器进行任务调度时均会考虑作业优先级这一因素。作业的优先级越高,它能够获取的资源(slot数目)也越多。Hadoop提供了5种作业优先级,分别为VERY_HIGH、 HIGH、 NORMAL、 LOW、 VERY_LOW。

 注:在生产环境中,管理员已经按照作业重要程度对作业进行了分级,不同重要程度的作业允许配置的优先级不同,用户可以擅自进行调整

 

  1. 合理控制Reduce Task的启动时机

如果Reduce Task启动过早,则可能由于Reduce Task长时间占用Reduce slot资源造成"slot Hoarding"现象,从而降低资源利用率;反之,如果Reduce Task启动过晚,则会导致Reduce Task获取资源延迟,增加了作业的运行时间

 

 

 

  • 任务级别参数调优

 hadoop任务级别参数调优分两个方面: Map Task和Reduce Task

  1. Map Task调优

 map运行阶段分为:Read、Map、Collect、Spill、Merge五个阶段

map 任务执行会产生中间数据,但这些中间结果并没有直接IO到磁盘上,而是先存储在缓存(buffer)中,并在缓存中进行一些预排序来优化整个map的性能,存储map中间数据的缓存默认大小为100M,由io.sort.mb 参数指定。这个大小可以根据需要调整。当map任务产生了非常大的中间数据时可以适当调大该参数,使缓存能容纳更多的map中间数据,而不至于大频率的IO磁盘,当系统性能的瓶颈在磁盘IO的速度上,可以适当的调大此参数来减少频繁的IO带来的性能障碍

由于map任务运行时中间结果首先存储在缓存中,默认当缓存的使用量达到80%(或0.8)的时候就开始写入磁盘,这个过程叫做spill(也叫溢出),进行spill的缓存大小可以通过io.sort.spill.percent 参数调整,这个参数可以影响spill的频率。进而可以影响IO的频率

当map任务计算成功完成之后,如果map任务有输出,则会产生多个spill。接下来map必须将些spill进行合并,这个过程叫做merge, merge过程是并行处理spill的,每次并行多少个spill是由参数io.sort.factor指定的默认为10个。但是当spill的数量非常大的时候,merge一次并行运行的spill仍然为10个,这样仍然会频繁的IO处理,因此适当的调大每次并行处理的spill数有利于减少merge数因此可以影响map的性能

 

当map输出中间结果的时候也可以配置压缩

 

  1. Reduce Task调优

reduce 运行阶段分为shuflle(copy) merge sort   reduce write五个阶段

shuffle 阶段为reduce 全面拷贝map任务成功结束之后产生的中间结果,如果上面map任务采用了压缩的方式,那么reduce 将map任务中间结果拷贝过来后首先进行解压缩,这一切是在reduce的缓存中做的,当然也会占用一部分cpu。为了优化reduce的执行时间,reduce也不是等到所有的map数据都拷贝过来的时候才开始运行reduce任务,而是当job执行完第一个map任务时开始运行的。reduce 在shuffle阶段 实际上是从不同的并且已经完成的map上去下载属于自己的数据,由于map任务数很多,所有这个copy过程是并行的,既同时有许多个reduce去拷贝map,这个并行的线程是通过mapred.reduce.parallel.copies 参数指定,默认为5个,也就是说无论map的任务数是多少个,默认情况下一次只能有5个reduce的线程去拷贝map任务的执行结果。所以当map任务数很多的情况下可以适当的调整该参数,这样可以让reduce快速的获得运行数据来完成任务。

reduce线程在下载map数据的时候也可能因为各种各样的原因(网络原因、系统原因等),存储该map数据所在的datannode 发生了故障,这种情况下reduce任务将得不到该datanode上的数据了,同时该 download thread 会尝试从别的datanode下载,可以通过mapred.reduce.copy.backoff (默认为30秒)来调整下载线程的下载时间,如果网络不好的集群可以通过增加该参数的值来增加下载时间,以免因为下载时间过长reduce将该线程判断为下载失败

reduce 下载线程在map结果下载到本地时,由于是多线程并行下载,所以也需要对下载回来的数据进行merge,所以map阶段设置的io.sort.factor 也同样会影响这个reduce的。

同map一样 该缓冲区大小也不是等到完全被占满的时候才写入磁盘而是默认当完成0.66的时候就开始写磁盘操作,该参数是通过mapred.job.shuffle.merge.percent 指定的

当reduce 开始进行计算的时候通过mapred.job.reduce.input.buffer.percent 来指定需要多少的内存百分比来作为reduce读已经sort好的数据的buffer百分比,该值默认为0。Hadoop假设用户的reduce()函数需要所有的JVM内存,因此执行reduce()函数前要释放所有内存。如果设置了该值,可将部分文件保存在内存中(不必写到磁盘上)

总之,Map Task和Reduce Task调优的一个原则就是减少数据的传输量、尽量使用内存、减少磁盘IO的次数、增大任务并行数,除此之外还有根据自己集群及网络的实际情况来调优。

 

  1. 管理员角度调优

管理员负责为用户作业提供一个高效的运行环境。管理员需要从全局出发,通过调整一些关键参数提高系统的吞率和性能。总体上来看,管理员需从硬件选择、操作系统参数调优、JVM参数调优和Hadoop参数调优等四个角度入手,为Hadoop用户提供一个高效的作业运行环境

 

硬件选择

 Hadoop自身架构的基本特点决定了其硬件配置的选项。Hadoop采用了Master/Slave架构,其中,master维护了全局元数据信息,重要性远远大于slave。在较低Hadoop版本中,master存在单点故障问题,因此,master的配置应远远好于各个slave

 

操作系统参数调优

增大同时打开的文件描述符和网络连接上限

使用ulimit命令将允许同时打开的文件描述符数目上限增大至一个合适的值。同时调整内核参数net.core.somaxconn网络连接数目至一个足够大的值

 

 

补充:net.core.somaxconn的作用 

net.core.somaxconn是Linux中的一个kernel参数,表示socket监听(listen)的backlog上限。

什么是backlog呢?backlog就是socket的监听队列,当一个请求(request)尚未被处理或建立时,它会进入backlog。

而socket server可以一次性处理backlog中的所有请求,处理后的请求不再位于监听队列中。当server处理请求较慢,以至于监听队列被填满后,新来的请求会被拒绝。

   在Hadoop 1.0中,参数ipc.server.listen.queue.size控制了服务端socket的监听队列长度,即backlog长度,默认值是128。而Linux的参数net.core.somaxconn默认值同样为128。当服务端繁忙时,如NameNode或JobTracker,128是远远不够的。这样就需要增大backlog,例如我们的3000台集群就将ipc.server.listen.queue.size设成了32768,为了使得整个参数达到预期效果,同样需要将kernel参数net.core.somaxconn设成一个大于等于32768的值。

关闭swap分区

    避免使用swap分区,提供程序的执行效率。

     除此之外,设置合理的预读取缓冲区的大小、文件系统选择与配置及I/O调度器选择等

 

JVM参数调优

由于Hadoop中的每个服务和任务均会运行在一个单独的JVM中,因此,JVM的一些重要参数也会影响Hadoop性能。管理员可通过调整JVM FLAGS和JVM垃圾回收机制提高Hadoop性能

 

Hadoop参数调优

  1. 合理规划资源

设置合理的槽位数目

在Hadoop中,计算资源是用槽位表示的。slot分为两种:Map  Slot和Reduce Slot。每种slot代表一定量的资源,且同种slot是同质的,也就是说,同种slot代表的资源量是相同的。管理员需要根据实际需要为TaskTracker配置一定数目的Map Slot和Reduce Slot数目,从而限制每个TaskTracker上并发执行的Map Task和Reduce Task的数目

编写健康监测脚本

Hadoop允许管理员为每个TaskTracker配置一个节点健康状况监测脚本。TaskTracker中包含一个专门的线程周期性执行该脚本,并将脚本执行结果通过心跳机制汇报给JobTracker。一旦JobTracker发现某个TaskTracker的当前状况为“不健康”,则会将其加入黑名单,从此不再为它分配任务

 

  1. 调整心跳配置

调整心跳的间隔 因根据自己集群的规模适度的调整心跳间隔

启用带外心跳   为了减少任务分配延迟,Hadoop引入了带外心跳。带外心跳不同于常规心跳,它是任务运行结束或者任务运行失败时触发的,能够在出现空闲资源时第一时间通知JobTracker,以便它能够迅速为空闲资源分配新的任务

除此之外,还包括磁盘块配置、设置合理的RPC Handler和HTTP线程数目、慎用黑名单机制、启用批量任务调度、选择合适的压缩算法、启用预读取机制等

注:当一个集群的规模较小时,如果一定数量的节点被频繁的加入系统黑名单中,则会大大降低集群的吞吐率和计算能力。

  1. 小结

 Hadoop 性能调优是一项工程浩大的工作,它不仅涉及Hadoop本身的性能调优,还涉及更底层的硬件、操作系统和Java虚拟机等系统的调优。

 总体来说,提高作业运行效率需要Hadoop管理员和作业拥有者共同的努力,其中,管理员负责为用户提供一个高效的作业运行环境,而用户则根据自己作业的特点让它尽可能快地运行完成

Hbase面试相关

  1. Hbase 的 rowkey 怎么创建比较好?列族怎么创建比较好?

Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。在查找时有索引会加快速度。

 

Rowkey散列原则 、 Rowkey唯一原则 、 针对事务数据Rowkey设计 、 针对统计数据的Rowkey设计 、 针对通用数据的Rowkey设计、 支持多条件查询的RowKey设计

 

总结设计列族:

1、一般不建议设计多个列族

2、数据块的缓存的设计

3、激进缓存设计

4、布隆过滤器的设计(可以提高随机读取的速度)

5、生产日期的设计

6、列族压缩

7、单元时间版本

 

  1. Hbase 的实现原理

 

 

  1. hbase 过滤器实现原则

Hbase的过滤器有:RowFilter、PrefixFilter、KeyOnlyFilter、RandomRowFilter、InclusiveStopFilter、FirstKeyOnlyFilter、ColumnPrefixFilter、ValueFilter、ColumnCountGetFilter、SingleColumnValueFilter、SingleColumnValueExcludeFilter、WhileMatchFilter、FilterList 

比较常用的过滤器有:RowFilter 一看就知道是行的过滤器,来过滤行的信息。PrefixFilter前缀的过滤器,就是把前缀作为参数来查找数据呗!剩下的不解释了看过滤器的直接意思就OK了很简单

 

  1. 描述 HBase, zookeeper 搭建过程

   Zookeeper 的问题在这里就不说了,hbase 主要的配置文件有hbase.env.sh 主要配置的是JDK的路径以及是否使用外部的ZK,hbase-site.xml主要配置的是与HDFS的链接的路径以及zk的信息,修改regionservers的链接其他机器的配置

 

  1. hbase 写数据的原理

1. 首先,Client通过访问ZK来请求目标数据的地址。

2. ZK中保存了-ROOT-表的地址,所以ZK通过访问-ROOT-表来请求数据地址。

3. 同样,-ROOT-表中保存的是.META.的信息,通过访问.META.表来获取具体的RS。

4. .META.表查询到具体RS信息后返回具体RS地址给Client。

5. Client端获取到目标地址后,然后直接向该地址发送数据请求

 

  1. hbase宕机了如何处理

HBase的RegionServer宕机超过一定时间后,HMaster会将其所管理的region重新分布到其他活动的RegionServer上,由于数据和日志都持久在HDFS中,该操作不会导致数据丢失。所以数据的一致性和安全性是有保障的。但是重新分配的region需要根据日志恢复原RegionServer中的内存MemoryStore表,这会导致宕机的region在这段时间内无法对外提供服务。而一旦重分布,宕机的节点重新启动后就相当于一个新的RegionServer加入集群,为了平衡,需要再次将某些region分布到该server。 因此,Region Server的内存表memstore如何在节点宕机期间做到更高的可用,是HBase的一个较大的挑战

 

  1. Hbase 中的 metastore 用来做什么的

Hbase的metastore是用来保存数据的,其中保存数据的方式有有三种:

第一种与第二种是本地储存

第三种是远程储存这一种企业用的比较多

 

  1. hbase客户端在客户端怎样优化

Hbase使用JAVA来运算的,所以Java的优化也适用于hbase,在使用过滤器时记得开启bloomfilter可以是性能提高3-4倍,设置HBASE_HEAPSIZE设置大一些

 

  1. hbase是怎样预分区的

如何去进行预分区,可以采用下面三步:
    1.取样,先随机生成一定数量的rowkey,将取样数据按升序排序放到一个集合里
    2.根据预分区的region个数,对整个集合平均分割,即是相关的splitKeys.
    3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitKey,即是指定region间的rowkey临界值

 

  1. 怎样将 mysql 的数据导入到 hbase 中

不能使用 sqoop,速度太慢了,提示如下:

A、一种可以加快批量写入速度的方法是通过预先创建一些空的 regions,这样当

数据写入 HBase 时,会按照 region 分区情况,在集群内做数据的负载均衡。

B、hbase 里面有这样一个 hfileoutputformat 类,他的实现可以将数据转换成 hfile

格式,通过 new 一个这个类,进行相关配置,这样会在 hdfs 下面产生一个文件,这个

时候利用 hbase 提供的 jruby 的 loadtable.rb 脚本就可以进行批量导入

 

  1. 谈谈 HBase 集群安装注意事项

需要注意的地方是 ZooKeeper 的配置。这与 hbase-env.sh 文件相关,文件中

HBASE_MANAGES_ZK 环境变量用来设置是使用 hbase 默认自带的 Zookeeper 还

是使用独立的 ZooKeeper。HBASE_MANAGES_ZK=false 时使用独立的,为 true 时

使用默认自带的。

     某个节点的 HRegionServer 启动失败,这是由于这 3 个节点的系统时间不一致相

差超过集群的检查时间 30s

 

 

 

  1. 简述 HBase 的瓶颈

Hbase主要的瓶颈就是传输问题,在操作时大部分的操作都是需要对磁盘操作的

 

  1. Redis, 传统数据库,hbase,hive  每个之间的区别

Redis 是基于内存的数据库,注重实用内存的计算

hbase是列式数据库,无法创建主键,存储是基于HDFS的,每一行可以保存很多的列hive是数据仓库,是为了减轻mapreduce而设计的,不是数据库是用来与hdfs做交互

 

  1. Hbase 的特性,以及你怎么去设计 rowkey 和 columnFamily ,怎么去建一个 table

因为hbase是列式数据库,列非表schema的一部分,所以只需要考虑rowkey和columnFamily 即可,rowkey有维的相关性,最好数据库添加一个前缀,文件越小,查询速度越快,再设计列是有一个列簇,但是列簇不宜过多

 

  1. Hhase与hive的区别

Apache HBase是一种Key/Value系统,它运行在HDFS之上。和Hive不一样,Hbase的能够在它的数据库上实时运行,而不是运行MapReduce任务。Hive被分区为表格,表格又被进一步分割为列簇。列簇必须使用schema定义,列簇将某一类型列集合起来(列不要求schema定义)。例如,“message”列簇可能包含:“to”, ”from” “date”, “subject”, 和”body”. 每一个 key/value对在Hbase中被定义为一个cell,每一个key由row-key,列簇、列和时间戳。在Hbase中,行是key/value映射的集合,这个映射通过row-key来唯一标识。Hbase利用Hadoop的基础设施,可以利用通用的设备进行水平的扩展

 

  1. 描述hbase的scan和get功能以及实现的异同

HBase的查询实现只提供两种方式: 

  1. 按指定RowKey获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get)
  2. 按指定的条件获取一批记录,scan方法(org.apache.hadoop.hbase.client.Scan) 实   现条件查询功能使用的就是scan方式

 

  1. HBase scan setBatch和setCaching的区别

scan可以通过setCaching与setBatch方法提高速度(以空间换时间),

setCaching设置的值为每次rpc的请求记录数,默认是1;cache大可以优化性能,但是太大了会花费很长的时间进行一次传输。

setBatch设置每次取的column size;有些row特别大,所以需要分开传给client,就是一次传一个row的几个column

 

  1. hbase 中cell的结构

cell中的数据是没有类型的,全部是字节码形式存储

 

 

 

 

 

 

  1. hbase 中region太多和region太大带来的冲突

Hbase的region会自动split,当region太多时,regio太大时分布会不均衡,同时对于大批量的载入数据建议如下:

1、还是必须让业务方对rowkey进行预分片,对业务数据rowkey进行md5或者其他的hash策略,让数据尽量随机分布而不是顺序写入。

2、随时观察region的大小,是否出现大region的情况

 

 

 

 

Hbase架构原理

  • Hbase定义

HBase 是一个高可靠、高性能、面向列、可伸缩的分布式存储系统,利用Hbase技术可在廉价PC Server上搭建 大规模结构化存储集群

HBase 是Google Bigtable 的开源实现,与Google Bigtable 利用GFS作为其文件存储系统类似, HBase 利用Hadoop HDFS 作为其文件存储系统;Google 运行MapReduce 来处理Bigtable中的海量数据, HBase 同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable 利用Chubby作为协同服务, HBase 利用Zookeeper作为对应

 

  • Hbase特点
  1. 大:一个表可以有上亿行,上百万列
  2. 面向列:面向列表(簇)的存储和权限控制,列(簇)独立检索
  3. 稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏

 

  • Hbase访问接口

HBase 支持很多种访问,访问HBase的常见接口如下:

  1. Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据
  2. HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用
  3. Thrift Gateway,利用Thrift序列化技术,支持C++,PHP,Python等多种语言,适合其他异构系统在线访问HBase表数据
  4. REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制
  5. Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计
  6. Hive整合hbase,可以使用类似SQL语言来访问HBase

 

  • Hbase存储结构

 从HBase的架构图上可以看出,HBase中的存储包括HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,本课程统一介绍他们的作用即存储结构。 以下是 HBase 存储架构图:

 

HBase中的每张表都通过行键按照一定的范围被分割成多个子表(HRegion),默认一个HRegion超过256M就要被分割成两个,这个过程由HRegionServer管理,而HRegion的分配由HMaster管理

 

HMaster

  1. 为Region server分配region
  2. 负责Region server的负载均衡
  3. 发现失效的Region server并重新分配其上的region
  4. HDFS上的垃圾文件回收
  5. 处理schema更新请求

 

HRegionServer

  1. 维护master分配给他的region,处理对这些region的io请求
  2. 负责切分正在运行过程中变的过大的region

可以看到,client访问hbase上的数据并不需要master参与(寻址访问zookeeper和region server,数据读写访问region server),master仅仅维护table和region的元数据信息(table的元数据信息保存在zookeeper上),负载很低。 HRegionServer存取一个子表时,会创建一个HRegion对象,然后对表的每个列族创建一个Store实例,每个Store都会有一个MemStore和0个或多个StoreFile与之对应,每个StoreFile都会对应一个HFile, HFile就是实际的存储文件。因此,一个HRegion有多少个列族就有多少个Store。 一个HRegionServer会有多个HRegion和一个HLog

 

HRegion

table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元,即不同的region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个Region server上

 

 

Region按大小分隔,每个表一般是只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值(默认256M)时就会分成两个新的region

每个region由以下信息标识:

  1. < 表名,startRowkey,创建时间>
  2. 由目录表(-ROOT-和.META.)记录该region的endRowkey

 

HRegion定位:Region被分配给哪个Region Server是完全动态的,所以需要机制来定位Region具体在哪个region server

HBase使用三层结构来定位region:

  1. 通过zk里的文件/hbase/rs得到-ROOT-表的位置。-ROOT-表只有一个region
  2. 通过-ROOT-表查找.META.表的第一个表中相应的region的位置。其实-ROOT-表是.META.表的第一个region;.META.表中的每一个region在-ROOT-表中都是一行记录
  3. 通过.META.表找到所要的用户表region的位置。用户表中的每个region在.META.表中都是一行记录

 -ROOT-表永远不会被分隔为多个region,保证了最多需要三次跳转,就能定位到任意的region。client会将查询的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行6次网络来回,才能定位到正确的region,其中三次用来发现缓存失效,另外三次用来获取位置信息

 

table 和region 的关系

table 默认最初只有一个region,随着记录数的不断增加而变大,起初的region会逐渐分裂成多个region,一个region有【startkey,endkey】表示,不同的region会被master分配给相应的regionserver管理

region 是hbase分布式存储和负载均衡的最小单元,不同的region分不到不同的regionserver

 

注意:region 虽然是分布式存储的最小单元,但并不是存储的最小单元

 

region 是由一个或者多个store 组成的,每个store就是一个 column family

每个stroe 又由一个memstore 和 1至多个store file组成(memstore 到一个阈值会刷新,写入到storefile,有hlog 来保证数据的安全性,一个 regionserver 有且只有一个hlog)

 

Store

每一个region由一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者多个StoreFile组成。 HBase以store的大小来判断是否需要切分region

 

MemStore

 memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认64MB)时,memStore会被flush到文件,即生成一个快照。目前hbase 会有一个线程来负责memStore的flush操作

 

StoreFile

memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存

 

HFile

HBase中KeyValue数据的存储格式,是hadoop的二进制格式文件。 首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。Trailer中有指针指向其他数据块的起始点,FileInfo记录了文件的一些meta信息。 Data Block是hbase io的基本单元,为了提高效率,HRegionServer中有基于LRU的block cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定(默认块大小64KB),大号的Block有利于顺序Scan,小号的Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成,Magic内容就是一些随机数字,目的是防止数据损坏,结构如下

HFile结构图如下:

 

Data Block段用来保存表中的数据,这部分可以被压缩。 Meta Block段(可选的)用来保存用户自定义的kv段,可以被压缩。 FileInfo段用来保存HFile的元信息,不能被压缩,用户也可以在这一部分添加自己的元信息。 Data Block Index段(可选的)用来保存Meta Blcok的索引。 Trailer这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。 HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目标HFile的压缩支持两种方式:gzip、lzo

 

另外,针对目前针对现有HFile的两个主要缺陷:

  1. 占用过多内存
  2. 启动加载时间缓慢

基于此缺陷,提出了HFile Version2设计

 

HLog

其实HLog文件就是一个普通的Hadoop Sequence File, Sequence File的value是key时HLogKey对象,其中记录了写入数据的归属信息,除了table和region名字外,还同时包括sequence number和timestamp,timestamp是写入时间,sequence number的起始值为0,或者是最近一次存入文件系统中的sequence number。 Sequence File的value是HBase的KeyValue对象,即对应HFile中的KeyValue

 

HLog(WAL log):WAL意为write ahead log,用来做灾难恢复使用,HLog记录数据的所有变更,一旦region server 宕机,就可以从log中进行恢复

 

LogFlusher

前面提到,数据以KeyValue形式到达HRegionServer,将写入WAL之后,写入一个SequenceFile。看过去没问题,但是因为数据流在写入文件系统时,经常会缓存以提高性能。这样,有些本以为在日志文件中的数据实际在内存中。这里,我们提供了一个LogFlusher的类。它调用HLog.optionalSync(),后者根据 hbase.regionserver.optionallogflushinterval (默认是10秒),定期调用Hlog.sync()。另外,HLog.doWrite()也会根据 hbase.regionserver.flushlogentries (默认100秒)定期调用Hlog.sync()。Sync() 本身调用HLog.Writer.sync(),它由SequenceFileLogWriter实现

 

LogRoller

 Log的大小通过$HBASE_HOME/conf/hbase-site.xml 的 hbase.regionserver.logroll.period 限制,默认是一个小时。所以每60分钟,会打开一个新的log文件。久而久之,会有一大堆的文件需要维护。首先,LogRoller调用HLog.rollWriter(),定时滚动日志,之后,利用HLog.cleanOldLogs()可以清除旧的日志。它首先取得存储文件中的最大的sequence number,之后检查是否存在一个log所有的条目的“sequence number”均低于这个值,如果存在,将删除这个log。 每个region server维护一个HLog,而不是每一个region一个,这样不同region(来自不同的table)的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高table的写性能。带来麻烦的时,如果一个region server下线,为了恢复其上的region,需要将region server上的log进行拆分,然后分发到其他region server上进行恢复

 

 

 

  • HBase 设计

 HBase 中的每一张表就是所谓的 BigTable。BigTable 会存储一系列的行记录,行记录有三个基本类型的定义:Row Key、Time Stamp、Column

  1. Row Key 是行在 BigTable 中的唯一标识
  2. Time Stamp 是每次数据操作对应关联的时间戳,可以看做 SVN 的版本
  3. Column 定义为< family>:< label>,通过这两部分可以指定唯一的数据的存储列,family 的定义和修改需要 对 HBase 进行类似于 DB 的 DDL 操作,而 label ,不需要定义直接可以使用,这也为动态定制列提供了一种手段 。family 另一个作用体现在物理存储优化读写操作上,同 family 的数据物理上保存的会比较临近,因此在业务设计的过程中可以利用这个特性

 

数据模型

HBase 以表的形式存储数据。表由行和列组成。列划分为若干个列族(row family)

 

  1. Row Key

与 NoSQL 数据库一样,Row Key 是用来检索记录的主键。访问 HBase table 中的行,只有三种方式

  1. 通过单个 Row Key 访问
  2. 通过 Row Key 的 range 全表扫描
  3. Row Key 可以使任意字符串(最大长度是64KB,实际应用中长度一般为 10 ~ 100bytes),在HBase 内部,Row Key 保存为字节数组

  在存储时,数据按照 Row Key 的字典序(byte order)排序存储。设计 Key 时,要充分排序存储这个特性,将经常一起读取的行存储到一起(位置相关性)

注意 字典序对 int 排序的结果是 1,10,100,11,12,13,14,15,16,17,18,19,20,21,..., 9,91,92,93,94,95,96,97,98,99。要保存整形的自然序,Row Key 必须用 0 进行左填充

行的一次读写是原子操作(不论一次读写多少列)。这个设计决策能够使用户很容易理解程序在对同一个行进行并发更新操作时的行为

 

  1. 列族

HBase 表中的每个列都归属于某个列族。列族是表的 Schema 的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀,例如 courses:history、courses:math 都属于 courses 这个列族。

        访问控制、磁盘和内存的使用统计都是在列族层面进行的。在实际应用中,列族上的控制权限能帮助我们管理不同类型的应用, 例如,允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)

 

 

  1. 时间戳

HBase 中通过 Row 和 Columns 确定的一个存储单元称为 Cell。每个 Cell 都保存着同一份数据的多个版本。 版本通过时间戳来索引,时间戳的类型是 64 位整型。时间戳可以由HBase(在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也 可以由客户显示赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 Cell 中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面

 为了避免数据存在过多版本造成的管理(包括存储和索引)负担,HBase 提供了两种数据版本回收方式。 一是保存数据的最后 n 个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置

  1. Cell

 Cell 是由 {row key,column(=< family> + < label>),version} 唯一确定的单元。Cell 中的数据是没有类型的,全部是字节码形式存储

物理存储

Table 在行的方向上分割为多个HRegion,每个HRegion分散在不同的RegionServer中

 

每个HRegion由多个Store构成,每个Store由一个memStore和0或多个StoreFile组成,每个Store保存一个Columns Family

StoreFile以HFile格式存储在HDFS中

Kafka知识点

一、kafka 简介

  •   Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作数据流,这种动作(网页浏览,搜索和其他的用户行为)是现代网络上的许多社会功能的一个关键因素,这些数据通常由于吞吐量的要求而通过处理日志和日志聚合来解决

 

  • Kafka名词解释

Producer:生产者

Consumer:消费者

Topic:消息以topic为类别记录,kafka将消息(feed)种子分门别类,每一类的消息称之为一个主题(topic)

Broker:以集群的方式运行,可以由一个或者多个服务组成,每个服务叫做一个broker,消费者可以订阅一个或者多个topic,并从broker拉取数据,从而消费这些已经发布的消息

每个消息(也叫record记录)是由一个key,value和时间戳组成

 

  • Kafka四个核心API介绍
  1. 应用程序使用producer API发布消息到一个或者多个topic中
  2. 应用程序使用consumer API来订阅一个或者多个topic,并处理产生的消息
  3. 应用程序使用streams API充当一个流处理器,从一个或者多个topic消费输入流,并产生一个输出流到一个或者多个topic,有效的将输入流转换成输出流
  4. Connector允许构建可重复使用的生产者或者消费者,将topic链接到现有的应用程序或数据系统

 

  • Kafka基本原理

通常来讲,消息系统可以分为两种,队列和发布订阅式,队列的处理方式是一组消费者从服务器读取消息,一条消息只由其中的一个消费者来处理,在发布订阅模型中,消息被广播给所有的消费者,接收到消息的消费者都可以处理此消息,kafka为这两种模型提供了单一的消费者抽象模型:(consumer group)消费者组,消费者用一个消费者组名来标记自己

   一个发布在topic上的消息被分发给此消费者组中的一个消费者,假如所有的消费者都在一个组中,那么就变成了queue模型,假如所有的消费者都在不同的组中,那么就完全变成发布-订阅模型,更通用。我们可以创建一些消费者组作为逻辑上的消费者,每个组包含数目不等的消费者,一个组内的多个消费者可以用来提高性能和容错

   并且,kafka能够保证消息被发送到一个特定的topic的分区上,消息将会按照他们发送的顺序依次加入,也就是说,如果消息Message1和Message2由一个producer发送,Message1先发送,那么,Message1比Message2的offset低,并且优先的出现在日志中,消费者收到的消息也是此顺序,如果一个topic配置了复制因子(replication factor)为N,那么可以允许N-1服务器宕机而不会丢失任何已经提交(commited)的消息,此特性说明kafka比传统消息系统更强的顺序保证,但是相同的消费者组中不能有比分区更多的消费者,否则多出的消费者将会一直处于空等待,不会收到消息

 

  • Kafka应用场景

构建实时的流数据管道,可靠的获取系统和应用程序之间的数据

构建实时流的应用程序,对数据流进行转换或者反应

 

  • 主题和日志(topic和log)

每一个分区(partition)都是一个顺序的,不可变的消息队列,并且可以持续的添加,分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的,kafka集群保持所有的消息,直到它们过期,无论消息是否被消费

实际上消费者所持有的仅有的元数据就是这个偏移量,也就是消费者在这个log中的位置,这个偏移量由消费者控制,正常情况下,消费者消费消息时,偏移量也线性的增加,但是实际偏移量由消费者控制,消费者可以将偏移量重置为更老的一个偏移量,重新读取之前的消息,可以看到这种设计多消费者来说操作自如,一个消费者的操作不会影响其他消费者对此log的处理

Kafka中采用分区的设计有几个目的,一是可以处理更多的消息,不受单台服务器的限制。Topic拥有多个分区意味着他可以不受限的处理更多的数据,第二,分区可以作为并行处理的单元

 

  • 分布式(distribution)

Log的分区被分到集群的多个服务器上,每个服务器处理它分到的的分区,根据配置,每个分区还可以复制到其他服务器作为备份容错,每个分区有一个leader,零或者多个follow。Leader处理此分区的所有读写请求,而follow被动的复制数据。如果leader宕机,其他的一个follow会被推举为新的leader,一台服务器可能同时是一个分区的leader,另一个分区的follow,这样可以平衡负载,避免所有的请求都让一台或者几台服务器处理

   

    

Hive ETL

  • 数据清洗介绍
  • Hive数据清洗
  1. 本文使用的是一数据集为user.zip,包含了一个大规模数据集raw_user.csv(包含2000万条记录),和一个小数据集small_user.csv(只包含30万条记录)。小数据集small_user.csv是从大规模数据集raw_user.csv中抽取的一小部分数据。之所以抽取出一少部分记录单独构成一个小数据集,是因为,在第一遍跑通整个实验流程时,会遇到各种错误,各种问题,先用小数据集测试,可以大量节约程序运行时间。等到第一次完整实验流程都顺利跑通以后,就可以最后用大规模数据集进行最后的测试

 

解压后查看前五行

[hadoop@master dataset]$ head -5 raw_user.csv 

user_id,item_id,behavior_type,user_geohash,item_category,time

10001082,285259775,1,97lk14c,4076,2014-12-08 18

10001082,4368907,1,,5503,2014-12-12 12

10001082,4368907,1,,5503,2014-12-12 12

10001082,53616768,1,,9762,2014-12-02 15

 

  1. 可以看出,每行记录都包含5个字段,数据集中的字段及其含义如下:

 

user_id(用户id)

item_id(商品id)

behaviour_type(包括浏览、收藏、加购物车、购买,对应取值分别是1、2、3、4)

user_geohash(用户地理位置哈希值,有些记录中没有这个字段值,所以后面我们会用脚本做数据预处理时把这个字段全部删除)

item_category(商品分类)

time(该记录产生时间)

 

数据集的预处理

1.删除文件第一行记录,即字段名称

raw_user和small_user中的第一行都是字段名称,我们在文件中的数据导入到数据仓库Hive中时,不需要第一行字段名称,因此,这里在做数据预处理时,删除第一行

 

[hadoop@master dataset]$ sed -i '1d' raw_user.csv 

[hadoop@master dataset]$ sed -i '1d' small_user.csv

[hadoop@master dataset]$ head -5 raw_user.csv 

10001082,285259775,1,97lk14c,4076,2014-12-08 18

10001082,4368907,1,,5503,2014-12-12 12

10001082,4368907,1,,5503,2014-12-12 12

10001082,53616768,1,,9762,2014-12-02 15

10001082,151466952,1,,5232,2014-12-12 11

接下来的操作中,我们都是用small_user.csv这个小数据集进行操作,这样可以节省时间。等所有流程都跑通以后,你就可以使用大数据集raw_user.csv去测试一遍了

 

2.对字段进行预处理

下面对数据集进行一些预处理,包括为每行记录增加一个id字段(让记录具有唯一性)、增加一个省份字段(用来后续进行可视化分析),并且丢弃user_geohash字段(后面分析不需要这个字段)。

下面我们要建一个脚本文件pre_deal.sh,请把这个脚本文件放在dataset目录下,和数据集small_user.csv放在同一个目录下:

 

#!/bin/bash

#下面设置输入文件,把用户执行pre_deal.sh命令时提供的第一个参数作为输入文件名称

infile=$1

#下面设置输出文件,把用户执行pre_deal.sh命令时提供的第二个参数作为输出文件名称

outfile=$2

#注意!!最后的$infile > $outfile必须跟在}’这两个字符的后面

awk -F "," 'BEGIN{

        srand();

        id=0;

        Province[0]="山东";Province[1]="山西";Province[2]="河南";Province[3]="河北";Province[4]="陕西";Province[5]="内蒙古";Province[6]="上海市";

        Province[7]="北京市";Province[8]="重庆市";Province[9]="天津市";Province[10]="福建";Province[11]="广东";Province[12]="广西";Province[13]="云南";

        Province[14]="浙江";Province[15]="贵州";Province[16]="新疆";Province[17]="西藏";Province[18]="江西";Province[19]="湖南";Province[20]="湖北";

        Province[21]="黑龙江";Province[22]="吉林";Province[23]="辽宁"; Province[24]="江苏";Province[25]="甘肃";Province[26]="青海";Province[27]="四川";

        Province[28]="安徽"; Province[29]="宁夏";Province[30]="海南";Province[31]="香港";Province[32]="澳门";Province[33]="台湾";

    }

    {

        id=id+1;

        value=int(rand()*34);       

        print id"\t"$1"\t"$2"\t"$3"\t"$5"\t"substr($6,1,10)"\t"Province[value]

}' $infile > $outfile

 

上面的代码的基本形式是:

 

awk -F "," '处理逻辑' $infile > $outfile

使用awk可以逐行读取输入文件,并对逐行进行相应操作。其中,-F参数用于指出每行记录的不同字段之间用什么字符进行分割,这里是用逗号进行分割。处理逻辑代码需要用两个英文单引号引起来。

 

 $infile是输入文件的名称,我们这里会输入raw_user.csv,$outfile表示处理结束后输出的文件名称,我们后面会使用user_table.txt作为输出文件名称。

 

在上面的pre_deal.sh代码的处理逻辑部分,srand()用于生成随机数的种子,id是我们为数据集新增的一个字段,它是一个自增类型,每条记录增加1,这样可以保证每条记录具有唯一性。我们会为数据集新增一个省份字段,用来进行后面的数据可视化分析,为了给每条记录增加一个省份字段的值,这里,我们首先用Province[]数组用来保存全国各个省份信息,然后,在遍历数据集raw_user.csv的时候,每当遍历到其中一条记录,使用value=int(rand()*34)语句随机生成一个0-33的整数,作为Province省份值,然后从Province[]数组当中获取省份名称,增加到该条记录中

 

substr($6,1,10)这个语句是为了截取时间字段time的年月日,方便后续存储为date格式。awk每次遍历到一条记录时,每条记录包含了6个字段,其中,第6个字段是时间字段,substr($6,1,10)语句就表示获取第6个字段的值,截取前10个字符,第6个字段是类似”2014-12-08 18″这样的字符串(也就是表示2014年12月8日18时),substr($6,1,10)截取后,就丢弃了小时,只保留了年月日。

 

另外,在print id”\t”$1″\t”$2″\t”$3″\t”$5″\t”substr($6,1,10)”\t”Province[value]这行语句中,我们丢弃了每行记录的第4个字段,所以,没有出现$4。我们生成后的文件是“\t”进行分割,这样,后续我们去查看数据的时候,效果让人看上去更舒服,每个字段在排版的时候会对齐显示,如果用逗号分隔,显示效果就比较乱

 

最后,保存pre_deal.sh代码文件,退出vim编辑器

 

运行shell文件

[hadoop@master dataset]$ chmod +x ./predeal.sh

[hadoop@master dataset]$ ./predeal.sh ./user_log.csv ./small_user_log.csv

 

简单分析:
1、这里我们要在数据库dblab中创建一个外部表bigdata_user,它包含字段(id, uid, item_id, behavior_type, item_category, date, province),请在hive命令提示符下输入如下命令

 

hive> create database dblab;

 

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 xmu dblab!'

row format delimited fields terminated by '\t'

stored as textfile location '/bigdatacase/dataset';

 

hive> select * from bigdata_user limit 10;

1   10001082    285259775   1   4076    2014-12-08  浙江

2   10001082    4368907 1   5503    2014-12-12  浙江

3   10001082    4368907 1   5503    2014-12-12  江西

4   10001082    53616768    1   9762    2014-12-02  甘肃

5   10001082    151466952   1   5232    2014-12-12  辽宁

6   10001082    53616768    4   9762    2014-12-02  西藏

7   10001082    290088061   1   5503    2014-12-12  湖北

8   10001082    298397524   1   10894   2014-12-12  陕西

9   10001082    32104252    1   6513    2014-12-12  安徽

10  10001082    323339743   1   10894   2014-12-12  江西

 

hive> desc bigdata_user;

id                      int                                         

uid                     string                                      

item_id                 string                                      

behavior_type           int                                         

item_category           string                                      

visit_date              date                                        

province                string  

 

查看日志前10个交易日志的商品品牌

hive> select behavior_type from bigdata_user limit 10;

OK

1

1

1

1

1

4

1

1

1

1

 

如果要查出每位用户购买商品时的多种信息,输出语句格式为 select 列1,列2,….,列n from 表名;比如我们现在查询前20位用户购买商品时的时间和商品的种类

hive> select visit_date,item_category from bigdata_user limit 20;

2014-12-08  4076

2014-12-12  5503

2014-12-12  5503

2014-12-02  9762

2014-12-12  5232

2014-12-02  9762

2014-12-12  5503

2014-12-12  10894

2014-12-12  6513

2014-12-12  10894

2014-12-12  2825

2014-11-28  2825

2014-12-15  3200

2014-12-03  10576

2014-11-20  10576

2014-12-13  10576

2014-12-08  10576

2014-12-14  7079

2014-12-02  6669

2014-12-12  5232

Time taken: 0.713 seconds, Fetched: 20 row(s)

 

有时我们在表中查询可以利用嵌套语句,如果列名太复杂可以设置该列的别名,以简化我们操作的难度,以下我们可以举个例子

hive> select e.bh,e.it from (select behavior_type as bh,item_category as it from bigdata_user) as e limit 20;

1   4076

1   5503

1   5503

1   9762

1   5232

4   9762

1   5503

1   10894

1   6513

1   10894

1   2825

1   2825

1   3200

1   10576

1   10576

1   10576

1   10576

1   7079

1   6669

1   5232

Time taken: 0.547 seconds, Fetched: 20 row(s)

 

 

用聚合函数count()计算出表内有多少条行数据

hive> select count(*) from bigdata_user;

Total MapReduce CPU Time Spent: 0 msec

OK

300000

Time taken: 6.418 seconds, Fetched: 1 row(s)

 

在函数内部加上distinct,查出uid不重复的数据有多少条

hive> select count(distinct uid) from bigdata_user;

Total MapReduce CPU Time Spent: 0 msec

OK

270

Time taken: 4.689 seconds, Fetched: 1 row(s)

 

查询不重复的数据有多少条(为了排除客户刷单情况)

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;

 

Total MapReduce CPU Time Spent: 0 msec

OK

284183

Time taken: 16.05 seconds, Fetched: 1 row(s)

 

以关键字的存在区间为条件的查询
使用where可以缩小查询分析的范围和精确度,下面用实例来测试一下

 

查询2014年12月10日到2014年12月13日有多少人浏览了商品

hive> select count(*) from bigdata_user where behavior_type='1' and

visit_date <'2014-12-13' and visit_date>'2014-12-10';

Total MapReduce CPU Time Spent: 0 msec

OK

26329

Time taken: 3.058 seconds, Fetched: 1 row(s)

 

以月的第n天为统计单位,依次显示第n天网站卖出去的商品的个数

hive> select count(distinct uid),day(visit_date) from bigdata_user where behavior_type='4' group by day(visit_date);

OK

37  1

48  2

42  3

38  4

42  5

33  6

42  7

36  8

34  9

40  10

43  11

98  12

39  13

43  14

42  15

44  16

42  17

66  18

38  19

50  20

33  21

34  22

32  23

47  24

34  25

31  26

30  27

34  28

39  29

38  30

Time taken: 2.378 seconds, Fetched: 30 row(s)

 

关键字赋予给定值为条件,对其他数据进行分析

取给定时间和给定地点,求当天发出到该地点的货物的数量

hive> select count(*) from bigdata_user where visit_date='2014-12-12' and behavior_type='4' and province='江西';

Total MapReduce CPU Time Spent: 0 msec

OK

7

Time taken: 3.421 seconds, Fetched: 1 row(s)

 

根据用户行为分析

查询一件商品在某天的购买比例或浏览比例

select count(*) from bigdata_user where visit_date='2014-12-11' and behavior_type='4';

//查询有多少用户在2014-12-11购买了商品

 

select count(*) from bigdata_user where visit_date='2014-12-11'

//查询有多少用户在2014-12-11点击了该店

两个相除得到某件商品购买率

 

查询某个用户在某一天点击网站占该天所有点击行为的比例(点击行为包括浏览,加入购物车,收藏,购买

select count(*) from bigdata_user where uid=10001082 and visit_date='2014-12-12';

//查询用户10001082在2014-12-12点击网站的次数

 

给定购买商品的数量范围,查询某一天在该网站的购买该数量商品的用户id

select uid from bigdata_user where visit_date='2014-12-12' and behavior_type='4' group by uid having count(behavior_type='4')>5;

 

实时查询,某个地区的用户当天浏览网站的次数

create table scan(province string,scan int)

comment 'This is the search of bigdataday'

row format delimited fields terminated by '\t'

stored as textfile;

 //创建新的数据表

select * from scan;

 

创建临时表user_action

create table dblab.user_action(id string ,

uid string,

item_id string,

behavior_type string,

visit_date date,

province string)

comment 'Welcome to xmu dblab!'

row format delimited fields terminated by '\t'

stored as textfile;

 

这个命令执行完以后,Hive会自动在HDFS文件系统中创建对应的数据文件“/user/hive/warehouse/dblab.db/user_action”。
     我们可以新建一个终端,执行命令查看一下,确认这个数据文件在HDFS中确实被创建了,请在新建的终端中执行下面命令:

./bin/hdfs dfs -ls /user/hive/warehouse/dblab.db/user_action

-rwxr-xr-x   1 hadoop supergroup   15590786 2016-11-27 21:57 /user/hive/warehouse/dblab.db/user_action/000000_0

 

将bigdata_user表中的数据插入到user_action(执行时间:10秒左右)
我们已经在Hive中的dblab数据库中创建了一个外部表bigdata_user,下面把dblab.bigdata_user数据插入到dblab.user_action表中,命令如下

 

insert overwrite table dblab.user_action select * from dblab.bigdata_user;

select * from user_action limit 10;

 

使用sqoop将数据从Hive导入mysql

启动hadoop集群,Mysql服务

登录mysql

 

mysql> create database dblab;

 

请使用下面命令查看数据库的编码:

mysql> show variables like 'character%';

 

下面在MySQL的数据库dblab中创建一个新表user_action,并设置其编码为utf-8

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=utf-8;

 

导入数据(执行时间:20秒左右)

./bin/sqoop export

 --connect jdbc:mysql://localhost:3306/dblab

 --username root

 --password 123

 --table user_action

 --export-dir '/user/hive/warehouse/dblab.db/user_action'

 --fields-terminated-by '\t';

 

查看MySQL中user_action表数据

mysql> select * from user_action limit 10;

+--------+-----------+-----------+---------------+---------------+------------+-----------+

| id     | uid       | item_id   | behavior_type | item_category | visit_date | province  |

+--------+-----------+-----------+---------------+---------------+------------+-----------+

| 225653 | 102865660 | 164310319 | 1             | 5027          | 2014-12-08 | 香港      |

| 225654 | 102865660 | 72511722  | 1             | 1121          | 2014-12-13 | 天津市    |

| 225655 | 102865660 | 334372932 | 1             | 5027          | 2014-11-30 | 江苏      |

| 225656 | 102865660 | 323237439 | 1             | 5027          | 2014-12-02 | 广东      |

| 225657 | 102865660 | 323237439 | 1             | 5027          | 2014-12-07 | 山西      |

| 225658 | 102865660 | 34102362  | 1             | 1863          | 2014-12-13 | 内蒙古    |

| 225659 | 102865660 | 373499226 | 1             | 12388         | 2014-11-26 | 湖北      |

| 225660 | 102865660 | 271583890 | 1             | 5027          | 2014-12-06 | 山西      |

| 225661 | 102865660 | 384764083 | 1             | 5399          | 2014-11-26 | 澳门      |

| 225662 | 102865660 | 139671483 | 1             | 5027          | 2014-12-03 | 广东      |

+--------+-----------+-----------+---------------+---------------+------------+-----------+

10 rows in set (0.00 sec)

 

 

使用Sqoop将数据从MySQL导入HBase

启动Hadoop集群、MySQL服务、HBase服务

创建表user_action

create 'user_action' ,{NAME=>'f1',VERSIONS=>5}

 

HBase中创建了一个user_action表,这个表中有一个列族f1(你愿意把列族名称取为其他名称也可以,比如列族名称为userinfo),历史版本保留数量为5

 

导入数据

./bin/sqoop import

 --connect jdbc:mysql://localhost:3306/dblab

 --username root

 --password 123

 --table user_action

 --hbase-table user_action

 --column-family f1      #列簇名称

 --hbase-row-key id      #HBase 行键

 --hbase-create-table    #是否在不存在情况下创建表

 -m -1      #启动 Map 数量

 

查看HBase中user_action表数据

habse> scan 'user_action',{LIMIT=>10}  #只查询前面10行

 

 

 

 

 

 

 

 

 

create table t01(id int,name string,age int)

row format delimited fileds terminated by ',';

 

create table t02(id int ,location array)

row format delimited fileds terminated by '/t'

collection items terminated ',';

 

create table t03(id int ,name string,hobby map)

row format delimited fields terminated by ','

collection items terminated by '-'

map keys terminated by ':'

 

查询表信息

desc formatted 表名;

 

hive删除数据库

hive> drop database users;

FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. InvalidOperationException(message:Database users is not empty. One or more tables exist.)

DROP table IF EXISTS users ;

hive> DROP DATABASE IF EXISTS users CASCADE;

# 加入CASCADE关键字,可以强制删除一个数据库

---------------------

作者:memoryqiu

来源:CSDN up

原文:https://blog.csdn.net/sinat_25873421/article/details/80325579

版权声明:本文为博主原创文章,转载请附上博文链接!

 

默认分隔符

^A

 

单分区

create table t04 (id int, content string)partitioned by (dt string);

load data local inpath '/dat.txt' into table t04 partition (dt='2017-07-07');

 

双分区

create table t05 (id int ,content string)partitioned by (dt string,hour string);

load data local inpath '/data.txt' into table t05 partition(dt='2017-09-02',hour='08');

 

增删分区

drop table t06;

create table t06 ( id int , name string )

partitioned by (dt string)

row format delimited fields terminated by ',';

增加分区

alert table t06 add partition (dt='2017-06-06') location 'hdfs://node1:9000/data';

删除分区

alert table t06 drop partition (dt='2018-08-08');

 

#指定开启分桶

set hive.enforce.bucketing = true;

set mapreduce.job.reduces=4;

创建分通表

create table t07(sno int, sname string,sex string,sage int ,sdept string)

clustered by (sno)

sorted by(Sno DESC)

into 4 buckets

row format delimited

fields terminated by ',';

向分桶表插入数据

insert overwrite table t07 select * from t07_tmp cluster by (sno);

抽样调查

select * from t07 tablesample (bucket 1 out of 2 on sno);

 

外部表

create external table 08(id int , content string)

row format delimited fields terminated by ',' location '/stu';

 

添加列

alter table t09 add|replace columns (col_name string);

修改列名

alter table t09 change a a1 int; //修改a 字段名为 a1

 

重命名表

alter table t09 rename to t10;

 

like

create table t09 like t08;

 

hive join

 

+-------+---------+--+

| a.id  | a.name  |

+-------+---------+--+

| 1     | a       |

| 2     | b       |

| 3     | c       |

| 4     | d       |

| 7     | y       |

| 8     | u       |

+-------+---------+--+

 

 

+-------+---------+--+

| b.id  | b.name  |

+-------+---------+--+

| 2     | bb      |

| 3     | cc      |

| 7     | yy      |

| 9     | pp      |

+-------+---------+--+

 

** inner join 就是join

select * from a inner join b on a.id=b.id;

+-------+---------+-------+---------+--+

| a.id  | a.name  | b.id  | b.name  |

+-------+---------+-------+---------+--+

| 2     | b       | 2     | bb      |

| 3     | c       | 3     | cc      |

| 7     | y       | 7     | yy      |

+-------+---------+-------+---------+--+

 

**left join   

select * from a left join b on a.id=b.id;

+-------+---------+-------+---------+--+

| a.id  | a.name  | b.id  | b.name  |

+-------+---------+-------+---------+--+

| 1     | a       | NULL  | NULL    |

| 2     | b       | 2     | bb      |

| 3     | c       | 3     | cc      |

| 4     | d       | NULL  | NULL    |

| 7     | y       | 7     | yy      |

| 8     | u       | NULL  | NULL    |

+-------+---------+-------+---------+--+

 

**right join

select * from a right join b on a.id=b.id;

+-------+---------+-------+---------+--+

| a.id  | a.name  | b.id  | b.name  |

+-------+---------+-------+---------+--+

| 2     | b       | 2     | bb      |

| 3     | c       | 3     | cc      |

| 7     | y       | 7     | yy      |

| NULL  | NULL    | 9     | pp      |

+-------+---------+-------+---------+--+

 

**outer join

select * from a full outer join b on a.id=b.id;

+-------+---------+-------+---------+--+

| a.id  | a.name  | b.id  | b.name  |

+-------+---------+-------+---------+--+

| 1     | a       | NULL  | NULL    |

| 2     | b       | 2     | bb      |

| 3     | c       | 3     | cc      |

| 4     | d       | NULL  | NULL    |

| 7     | y       | 7     | yy      |

| 8     | u       | NULL  | NULL    |

| NULL  | NULL    | 9     | pp      |

+-------+---------+-------+---------+--+

 

**hive中的特别join

select * from a left semi join b on a.id = b.id

+-------+---------+--+

| a.id  | a.name  |

+-------+---------+--+

| 2     | b       |

| 3     | c       |

| 7     | y       |

+-------+---------+--+

 

导出数据

from t001 insert overwrite local directory '/root/aaa' select day

 insert overwrite local directory '/root/bbb' select ip ;

 

 

八、级联求和

create table t_access_times(username string,month string,salary int)

row format delimited fields terminated by ',';

 

create table t3(id int,nameid int,anum int)

row format delimited fields terminated by ',';

load data local inpath '/root/a.txt'

into table t3;

 

load data local inpath '/home/hadoop/t_access_times.dat'

into table t_access_times;

 

A,2015-01,5

A,2015-01,15

B,2015-01,5

A,2015-01,8

B,2015-01,25

A,2015-01,5

A,2015-02,4

A,2015-02,6

B,2015-02,10

B,2015-02,5

 

1、第一步,先求个用户的月总金额

select username,month,sum(salary) as salary

from t_access_times group by username,month

 

tmp

+-----------+----------+---------+--+

| username  |  month   | salary  |

+-----------+----------+---------+--+

| A         | 2015-01  | 33      |

| A         | 2015-02  | 10      |

| B         | 2015-01  | 30      |

| B         | 2015-02  | 15      |

+-----------+----------+---------+--+

 

2、第二步,将月总金额表 自己连接 自己

select A.*,B.* FROM

(select username,month,sum(salary) as salary from t_access_times group by username,month) A

inner join

(select username,month,sum(salary) as salary from t_access_times group by username,month) B on A.username=B.username

where B.month <= A.mont

 

+-------------+----------+-----------+-------------+----------+-----------+--+

| a.username  | a.month  | a.salary  | b.username  | b.month  | b.salary  |

+-------------+----------+-----------+-------------+----------+-----------+--+

| A           | 2015-01  | 33        | A           | 2015-01  | 33        |

| A           | 2015-02  | 10        | A           | 2015-01  | 33        |

| A           | 2015-02  | 10        | A           | 2015-02  | 10        |

| B           | 2015-01  | 30        | B           | 2015-01  | 30        |

| B           | 2015-02  | 15        | B           | 2015-01  | 30        |

| B           | 2015-02  | 15        | B           | 2015-02  | 15        |

+-------------+----------+-----------+-------------+----------+-----------+--+

 

select * from (select user_login.userid ,user_login.login_time from user_login)A inner join

(select new_user.userid ,new_user.create_time from new_user)B on A.userid=B.userid

where b.login_time -1=A.create_time

group by userid;

 

 

3、第三步,从上一步的结果中

进行分组查询,分组的字段是a.username a.month

求月累计值:  将b.month <= a.month的所有b.salary求和即可

select A.username,A.month,max(A.salary) as salary,sum(B.salary) as accumulate

 from  

(select username,month,sum(salary) as salary from t_access_times group by username,month) A  

inner join  

(select username,month,sum(salary) as salary from t_access_times group by username,month) B

 on

A.username=B.username

where B.month <= A.month

group by A.username,A.month

order by A.username,A.month;

 

+-------------+----------+---------+-------------+--+

| a.username  | a.month  | salary  | accumulate  |

+-------------+----------+---------+-------------+--+

| A           | 2015-01  | 33      | 33          |

| A           | 2015-02  | 10      | 43          |

| B           | 2015-01  | 30      | 30          |

| B           | 2015-02  | 15      | 45          |

+-------------+----------+---------+-------------+--+

 

 

 

九、制作宽表

drop table if exists ods_weblog_origin;

create table ods_weblog_origin(

valid string, --有效标识

remote_addr string, --来源IP

remote_user string, --用户标识

time_local string, --访问完整时间

request string, --请求的url

status string, --响应码

body_bytes_sent string, --传输字节数

http_referer string, --来源url

http_user_agent string) --客户终端标识

partitioned by (datestr string)

row format delimited

fields terminated by '\001';

 

 

建表——明细宽表 ods_weblog_detail

 

drop table ods_weblog_detail;

create table ods_weblog_detail(

valid           string, --有效标识

remote_addr     string, --来源IP

remote_user     string, --用户标识

time_local      string, --访问完整时间

daystr          string, --访问日期

timestr         string, --访问时间

month           string, --访问月

day             string, --访问日

hour            string, --访问时

request         string, --请求的url

status          string, --响应码

body_bytes_sent string, --传输字节数

http_referer    string, --来源url

ref_host        string, --来源的host

ref_path        string, --来源的路径

ref_query       string, --来源参数query

ref_query_id    string, --来源参数query的值

http_user_agent string --客户终端标识

)

partitioned by(datestr string);

-------------------------------------------------------------------------------

通过查询插入数据到明细宽表  ods_weblog_detail中

 

分步:

--抽取refer_url到中间表  t_ods_tmp_referurl

--也就是将来访url分离出host  path  query  query id

 

drop table if exists t_ods_tmp_referurl;

create table t_ods_tmp_referurl as

SELECT a.*,b.*

FROM ods_weblog_origin a

LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 'PATH','QUERY', 'QUERY:id') b as host, path, query, query_id;

 

 

 

--抽取转换time_local字段到中间表明细表 t_ods_tmp_detail

 

drop table if exists t_ods_tmp_detail;

create table t_ods_tmp_detail as

select b.*,

substring(time_local,1,10) as daystr,

substring(time_local,12) as timestr,

substring(time_local,6,2) as month,

substring(time_local,9,2) as day,

substring(time_local,12,2) as hour

From t_ods_tmp_referurl b;

 

以上语句可以改写成:

insert overwrite  table ods_weblog_detail partition(datestr='20130918')

select c.valid,c.remote_addr,c.remote_user,c.time_local,

substring(c.time_local,1,10) as daystr,

substring(c.time_local,12) as timestr,

substring(c.time_local,6,2) as month,

substring(c.time_local,9,2) as day,

substring(c.time_local,12,2) as hour,

c.request,c.status,c.body_bytes_sent,c.http_referer,c.ref_host,c.ref_path,c.ref_query,c.ref_query_id,c.http_user_agent

from

(SELECT

a.valid,a.remote_addr,a.remote_user,a.time_local,

a.request,a.status,a.body_bytes_sent,a.http_referer,a.http_user_agent,b.ref_host,b.ref_path,b.ref_query,b.ref_query_id

FROM ods_weblog_origin a LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 'PATH','QUERY', 'QUERY:id') b as ref_host, ref_path, ref_query, ref_query_id) c;

 

 

show partitions ods_weblog_detail;

 

TOPN

drop table dw_pvs_refhost_topn_everyhour; create table dw_pvs_refhost_topn_everyhour(

hour string, toporder string, ref_host string, ref_host_cnts string

)partitioned by(datestr string);

 

insert into table dw_pvs_refhost_topn_everyhour partition(datestr='20130918') select t.hour,t.od,t.ref_host,t.ref_host_cnts from

 (select ref_host,ref_host_cnts,concat(month,day,hour) as hour,

 row_number() over (partition by concat(month,day,hour) order by ref_host_cnts desc) as od  from dw_pvs_refererhost_everyhour) t where od<=3;

 

 

 

hbase

创建表

create 'user' , 'user_ibfo', 'login_info'

查看所有表

list

插入数据

put 'user','1001','user_info:name','张三'

put 'user', '1001', 'user_info:address', '上海'

put 'user', '1001', 'login_info:user_name', 'zhangsan'

put 'user', '1001', 'login_info:password', '123456'

 

查询数据

get 'user','1001'

查询全部数据

scan 'user'

查询一条数据

scan 'uset',{limit =>1}

#删除一行中的一列数据

delete 'user','1002', 'user_info:name'

#删除一行数据

deleteall 'user','1002'

#清空表

truncate 'user'

#修改用1001的密码为888888,直接put覆盖即可

put 'user', '1001', 'login_info:password', '888888'

 

#删除列族

alter 'user' , {NAME=>'user_info', METHOD => 'delete'}

 

#增加列族

alter 'user', '新列簇名'  #方式一

alter 'user', {NAME => 'user_info_2' , VERSIONS => 5} #方式二

 

#删除表之前先要禁用表,再删除

disable 'user'

drop 'user'

 

 

 

scala编写wordcount

import org.apache.spark.rdd.RDD

import org.apache.spark.{SparkConf, SparkContext}

 

object WordCountScalaLocal {

  def main(args: Array[String]): Unit = {

    //1:创建SparkConf对象

    val conf: SparkConf = new SparkConf().setAppName("WordCountScalaLocal")

    //2:创建SparkContext对象

    val sc: SparkContext = new SparkContext(conf)

    //3:读取文件

    val lines: RDD[String] = sc.textFile(args(0))

    //4:将每一行数据切分为单词

    val words: RDD[String] = lines.flatMap(line=>line.split(" "))

    //5:将出现的每一个单词记为1

    val pairs: RDD[(String, Int)] = words.map(word=>(word,1))

    //6:统计相同的单词出现的次数

    val result: RDD[(String, Int)] = pairs.reduceByKey((a, b)=>a+b)

    //7:遍历输出所有的结果

    result.foreach(wordCount=>{

      println(wordCount._1+"\t\t\t出现了\t\t\t"+wordCount._2+"\t\t次")

    })

    //8:释放资源

    sc.stop()

  }

}

 

public class WordCountJavaLocal {

    public static void main(String[] args) {

        //1:创建SparkConf对象,配置Spark应用程序得基本信息

        SparkConf sparkConf = new SparkConf().setAppName("WordCountJavaLocal").setMaster("local[2]");

        //2: 创建JavaSparkContext对象

        JavaSparkContext sc = new JavaSparkContext(sparkConf);

        //3:要针对输入源(hdfs文件,本地文件),创建一个初始得RDD

        JavaRDD lines = sc.textFile("F:\\data\\words.txt");

        //4: 对初始RDD进行transformation操作,也就是一些计算操作

        JavaRDD words = lines.flatMap(new FlatMapFunction() {

            @Override

            public Iterator call(String line) throws Exception {

                String[] split = line.split(" ");

                return Arrays.asList(split).iterator();

            }

        });

        //5:接着,需要将每一个单词,映射为(单词,1)得这种格式

        JavaPairRDD pairs = words.mapToPair(new PairFunction() {

            @Override

            public Tuple2 call(String word) throws Exception {

                return new Tuple2(word,1);

            }

        });

        //6: 接着,需要以单词作为key,统计每个单词出现得次数

        JavaPairRDD wordCounts = pairs.reduceByKey(new Function2() {

            @Override

            public Integer call(Integer v1, Integer v2) throws Exception {

                return v1 + v2;

            }

        });

        //7:到这里为止,我们通过几个Spark算子操作,已经统计出了单词的次数,将结果打印到控制台

        wordCounts.foreach(new VoidFunction>() {

            @Override

            public void call(Tuple2 wordCount) throws Exception {

                System.out.println(wordCount._1+"  出现了  "+wordCount._2+" 次");

            }

        });

        sc.close();

    }

}

 

 

假如有一张成绩表

create table t08(name string,kemu string,score int )

row format delimited fields terminated by ',';

mysql> select * from t08;

+------+----------+-------+

| name | kemu     | score |

+------+----------+-------+

| A    | chinese  |    90 |

| A    | math     |    96 |

| A    | english  |    79 |

| A    | computer |    89 |

| B    | computer |    93 |

| B    | english  |    92 |

| C    | english  |    77 |

| C    | chinese  |    78 |

+------+----------+-------+

 

现在我们想看下A同学的语文,数学,英语分数,希望能列在一行里。这时我们就需要把行转成列。

我们使用case when 语句

 

select name,sum(case kemu when 'chinese' then score end) as chinese,

sum(case kemu when 'math' then score end) as math,

sum(case kemu when 'english' then score end) as english,

sum(case kemu when 'computer' then score end) as computer

from t08 group by name;

 

+------+---------+------+---------+----------+

| name | chinese | math | english | computer |

+------+---------+------+---------+----------+

| A    |      90 |   96 |      79 |       89 |

| B    |    NULL | NULL |      92 |       93 |

| C    |      78 | NULL |      77 |     NULL |

+------+---------+------+---------+----------+

 

 

列转行

假如有一张成绩表为

create table t09(name string,chinese int,math int,english int,computer int)

row format delimited fields terminated by ',';

mysql> select * from t09;

+------+---------+------+---------+----------+

| name | chinese | math | english | computer |

+------+---------+------+---------+----------+

| A    |      90 |   96 |      79 |       89 |

| B    |    NULL | NULL |      92 |       93 |

| C    |      78 | NULL |      77 |     NULL |

+------+---------+------+---------+----------+

3 rows in set (0.00 sec)

 

我们将它行转列,使用union all

select name,'chinese' as kemu,chinese as score from t09 union all

select name,'math' as kemu,math as score from t09 union all

select name,'english' as kemu, english as score from t09 union all

select name,'computer' as kemu,computer as score from t09

 

  +------+----------+-------+

| name | kemu     | score |

+------+----------+-------+

| A    | chinese  |    90 |

| B    | chinese  |  NULL |

| C    | chinese  |    78 |

| A    | math     |    96 |

| B    | math     |  NULL |

| C    | math     |  NULL |

| A    | english  |    79 |

| B    | english  |    92 |

| C    | english  |    77 |

| A    | computer |    89 |

| B    | computer |    93 |

| C    | computer |  NULL |

+------+----------+-------+

12 rows in set (0.00 sec)

 

-- 分组聚合综合示例

-- 有如下数据

/*

192.168.33.3,http://www.edu360.cn/stu,2017-08-04 15:30:20

192.168.33.3,http://www.edu360.cn/teach,2017-08-04 15:35:20

192.168.33.4,http://www.edu360.cn/stu,2017-08-04 15:30:20

192.168.33.4,http://www.edu360.cn/job,2017-08-04 16:30:20

192.168.33.5,http://www.edu360.cn/job,2017-08-04 15:40:20

 

 

192.168.33.3,http://www.edu360.cn/stu,2017-08-05 15:30:20

192.168.44.3,http://www.edu360.cn/teach,2017-08-05 15:35:20

192.168.33.44,http://www.edu360.cn/stu,2017-08-05 15:30:20

192.168.33.46,http://www.edu360.cn/job,2017-08-05 16:30:20

192.168.33.55,http://www.edu360.cn/job,2017-08-05 15:40:20

 

 

192.168.133.3,http://www.edu360.cn/register,2017-08-06 15:30:20

192.168.111.3,http://www.edu360.cn/register,2017-08-06 15:35:20

192.168.34.44,http://www.edu360.cn/pay,2017-08-06 15:30:20

192.168.33.46,http://www.edu360.cn/excersize,2017-08-06 16:30:20

192.168.33.55,http://www.edu360.cn/job,2017-08-06 15:40:20

192.168.33.46,http://www.edu360.cn/excersize,2017-08-06 16:30:20

192.168.33.25,http://www.edu360.cn/job,2017-08-06 15:40:20

192.168.33.36,http://www.edu360.cn/excersize,2017-08-06 16:30:20

192.168.33.55,http://www.edu360.cn/job,2017-08-06 15:40:20

 

*/

-- 建表映射上述数据

create table t_access(ip string,url string,access_time string)

partitioned by (dt string)

row format delimited fields terminated by ',';

 

 

-- 导入数据

load data local inpath '/root/hivetest/access.log.0804' into table t_access partition(dt='2017-08-04');

load data local inpath '/root/hivetest/access.log.0805' into table t_access partition(dt='2017-08-05');

load data local inpath '/root/hivetest/access.log.0806' into table t_access partition(dt='2017-08-06');

 

-- 查看表的分区

show partitions t_access;

 

-- 求8月4号以后,每天http://www.edu360.cn/job的总访问次数,

-- 及访问者中ip地址中最大的

 

+-------------+---------------------------+------+----------------+--+

|     dt      |            url            | _c2  |      _c3       |

+-------------+---------------------------+------+----------------+--+

| 2017-08-05  | http://www.edu360.cn/job  | 2    | 192.168.33.55  |

| 2017-08-06  | http://www.edu360.cn/job  | 3    | 192.168.33.55  |

+-------------+---------------------------+------+----------------+--

 

select dt,'http://www.edu360.cn/job',count(1),max(ip)

from t_access

where url='http://www.edu360.cn/job'

group by dt having dt>'2017-08-04';

 

select dt,max(url),count(1),max(ip)

from t_access

where url='http://www.edu360.cn/job'

group by dt having dt>'2017-08-04';

 

select dt,url,count(1),max(ip)

from t_access

where url='http://www.edu360.cn/job'

group by dt,url having dt>'2017-08-04';

 

select dt,url,count(1),max(ip)

from t_access

where url='http://www.edu360.cn/job' and dt>'2017-08-04'

group by dt,url;

 

 

-- 求8月4号以后,每天每个页面的总访问次数,及访问者中ip地址中最大的

 

+-------------+---------------------------------+------+----------------+--+

|     dt      |               url               | _c2  |      _c3       |

+-------------+---------------------------------+------+----------------+--+

| 2017-08-05  | http://www.edu360.cn/job        | 2    | 192.168.33.55  |

| 2017-08-05  | http://www.edu360.cn/stu        | 2    | 192.168.33.44  |

| 2017-08-05  | http://www.edu360.cn/teach      | 1    | 192.168.44.3   |

| 2017-08-06  | http://www.edu360.cn/excersize  | 3    | 192.168.33.46  |

| 2017-08-06  | http://www.edu360.cn/job        | 3    | 192.168.33.55  |

| 2017-08-06  | http://www.edu360.cn/pay        | 1    | 192.168.34.44  |

| 2017-08-06  | http://www.edu360.cn/register   | 2    | 192.168.133.3  |

+-------------+---------------------------------+------+----------------+--+

select dt,url,count(1),max(ip)

from t_access

where dt>'2017-08-04'

group by dt,url;

 

-- 求8月4号以后,每天每个页面的总访问次数,及访问者中ip地址中最大的,

-- 且,只查询出总访问次数>2 的记录

-- 方式1:

+-------------+---------------------------------+-------+----------------+--+

|     dt      |               url               | cnts  |      _c3       |

+-------------+---------------------------------+-------+----------------+--+

| 2017-08-06  | http://www.edu360.cn/excersize  | 3     | 192.168.33.46  |

| 2017-08-06  | http://www.edu360.cn/job        | 3     | 192.168.33.55  |

+-------------+---------------------------------+-------+----------------+--+

select dt,url,count(1) as cnts,max(ip)

from t_access

where dt>'2017-08-04'

group by dt,url having cnts>2;

 

 

-- 方式2:用子查询

select dt,url,cnts,max_ip

from

(select dt,url,count(1) as cnts,max(ip) as max_ip

from t_access

where dt>'2017-08-04'

group by dt,url) tmp

where cnts>2;

 

 

+----------------+---------------------------------+-----------------------+--------------+--+

|  t_access.ip   |          t_access.url           | t_access.access_time  | t_access.dt  |

+----------------+---------------------------------+-----------------------+--------------+--+

 

| 192.168.33.46  | http://www.edu360.cn/job        | 2017-08-05 16:30:20   | 2017-08-05   |

| 192.168.33.55  | http://www.edu360.cn/job        | 2017-08-05 15:40:20   | 2017-08-05   |

 

| 192.168.33.55  | http://www.edu360.cn/job        | 2017-08-06 15:40:20   | 2017-08-06   |

| 192.168.33.25  | http://www.edu360.cn/job        | 2017-08-06 15:40:20   | 2017-08-06   |

| 192.168.33.55  | http://www.edu360.cn/job        | 2017-08-06 15:40:20   | 2017-08-06   |

+----------------+---------------------------------+-----------------------+--------------+

 

/*

HIVE 中的复合数据类型

 

*/

-- 数组

-- 有如下数据:

战狼2,吴京:吴刚:龙母,2017-08-16

三生三世十里桃花,刘亦菲:痒痒,2017-08-20

普罗米修斯,刘德华:张学友:郭富城,2017-09-17

美女与野兽,吴刚:加藤鹰,2017-09-17

 

-- 建表映射:

create table t_movie(movie_name string,actors array,first_show date)

row format delimited fields terminated by ','

collection items terminated by ':';

 

-- 导入数据

load data local inpath '/root/hivetest/actor.dat' into table t_movie;

load data local inpath '/root/hivetest/actor.dat.2' into table t_movie;

 

-- 查询

select movie_name,actors[0],first_show from t_movie;

 

select movie_name,actors,first_show

from t_movie where array_contains(actors,'吴刚');

 

select movie_name

,size(actors) as actor_number

,first_show

from t_movie;

 

 

-- 有如下数据:

1,zhangsan,father:xiaoming#mother:xiaohuang#brother:xiaoxu,28

2,lisi,father:mayun#mother:huangyi#brother:guanyu,22

3,wangwu,father:wangjianlin#mother:ruhua#sister:jingtian,29

4,mayun,father:mayongzhen#mother:angelababy,26

 

-- 建表映射上述数据

create table t_family(id int,name string,family_members map,age int)

row format delimited fields terminated by ','

collection items terminated by '#'

map keys terminated by ':';

 

-- 导入数据

load data local inpath '/root/hivetest/fm.dat' into table t_family;

 

+--------------+----------------+----------------------------------------------------------------+---------------+--+

| t_family.id  | t_family.name  |                    t_family.family_members                     | t_family.age  |

+--------------+----------------+----------------------------------------------------------------+---------------+--+

| 1            | zhangsan       | {"father":"xiaoming","mother":"xiaohuang","brother":"xiaoxu"}  | 28            |

| 2            | lisi           | {"father":"mayun","mother":"huangyi","brother":"guanyu"}       | 22            |

| 3            | wangwu         | {"father":"wangjianlin","mother":"ruhua","sister":"jingtian"}  | 29            |

| 4            | mayun          | {"father":"mayongzhen","mother":"angelababy"}                  | 26            |

+--------------+----------------+----------------------------------------------------------------+---------------+--+

 

-- 查出每个人的 爸爸、姐妹

select id,name,family_members["father"] as father,

family_members["sister"] as sister,age

from t_family;

 

-- 查出每个人有哪些亲属关系

select id,name,map_keys(family_members) as relations,age

from  t_family;

-- 查出每个人的亲人名字

select id,name,map_values(family_members) as relations,age

from  t_family;

 

-- 查出每个人的亲人数量

select id,name,size(family_members) as relations,age

from  t_family;

 

-- 查出所有拥有兄弟的人及他的兄弟是谁

-- 方案1:一句话写完

select id,name,age,family_members['brother']

from t_family  where array_contains(map_keys(family_members),'brother');

 

 

-- 方案2:子查询

select id,name,age,family_members['brother']

from

(select id,name,age,map_keys(family_members) as relations,family_members

from t_family) tmp

where array_contains(relations,'brother');

 

 

/*  hive数据类型struct

 

假如有以下数据:

1,zhangsan,18:male:深圳

2,lisi,28:female:北京

3,wangwu,38:male:广州

4,赵六,26:female:上海

5,钱琪,35:male:杭州

6,王八,48:female:南京

*/

 

-- 建表映射上述数据

 

drop table if exists t_user;

create table t_user(id int,name string,info struct)

row format delimited fields terminated by ','

collection items terminated by ':';

 

-- 导入数据

load data local inpath '/root/hivetest/user.dat' into table t_user;

 

-- 查询每个人的id name和地址sele

select id,name,info.addr

from t_user;

 

 

/*

    条件控制函数:case when

*/

 

0: jdbc:hive2://localhost:10000> select * from t_user;

+------------+--------------+----------------------------------------+--+

| t_user.id  | t_user.name  |              t_user.info               |

+------------+--------------+----------------------------------------+--+

| 1          | zhangsan     | {"age":18,"sex":"male","addr":"深圳"}    |

| 2          | lisi         | {"age":28,"sex":"female","addr":"北京"}  |

| 3          | wangwu       | {"age":38,"sex":"male","addr":"广州"}    |

| 4          | 赵六           | {"age":26,"sex":"female","addr":"上海"}  |

| 5          | 钱琪           | {"age":35,"sex":"male","addr":"杭州"}    |

| 6          | 王八           | {"age":48,"sex":"female","addr":"南京"}  |

+------------+--------------+----------------------------------------+--+

 

需求:查询出用户的id、name、年龄(如果年龄在30岁以下,显示年轻人,30-40之间,显示中年人,40以上老年人)

select id,name,

case

when info.age<30 then '青年'

when info.age>=30 and info.age<40 then '中年'

else '老年'

end

from t_user;

--- IF

0: jdbc:hive2://localhost:10000> select * from t_movie;

+---------------------+------------------------+---------------------+--+

| t_movie.movie_name  |     t_movie.actors     | t_movie.first_show  |

+---------------------+------------------------+---------------------+--+

| 战狼2                 | ["吴京","吴刚","龙母"]       | 2017-08-16          |

| 三生三世十里桃花            | ["刘亦菲","痒痒"]           | 2017-08-20          |

| 普罗米修斯               | ["bbbb","xxxx","zzzz"]  | 2017-09-17          |

| 美女与野兽               | ["吴刚","加藤鹰"]           | 2017-09-17          |

+---------------------+------------------------+---------------------+--+

 

-- 需求: 查询电影信息,并且如果主演中有吴刚的,显示好电影,否则烂片

 

select movie_name,actors,first_show,

if(array_contains(actors,'吴刚'),'好片儿','烂片儿')

from t_movie;

 

-- row_number() over() 函数

-- 造数据:

 

1,18,a,male

2,19,b,male

3,22,c,female

4,16,d,female

5,30,e,male

6,26,f,female

 

create table t_rn(id int,age int,name string,sex string)

row format delimited fields terminated by ',';

 

load data local inpath '/root/hivetest/rn.dat' into table t_rn;

 

+----------+-----------+------------+-----------+--+

| t_rn.id  | t_rn.age  | t_rn.name  | t_rn.sex  |

+----------+-----------+------------+-----------+--+

| 1        | 18        | a          | male      |

| 2        | 19        | b          | male      |

| 3        | 22        | c          | female    |

| 4        | 16        | d          | female    |

| 5        | 30        | e          | male      |

| 6        | 26        | f          | female    |

+----------+-----------+------------+-----------+--+

-- 分组标记序号

 

select *

from

(select id,age,name,sex,

row_number() over(partition by sex order by age desc) as rn

from t_rn) tmp

where rn<3

;

+---------+----------+-----------+----------+---------+--+

| tmp.id  | tmp.age  | tmp.name  | tmp.sex  | tmp.rn  |

+---------+----------+-----------+----------+---------+--+

| 6       | 26       | f         | female   | 1       |

| 3       | 22       | c         | female   | 2       |

| 5       | 30       | e         | male     | 1       |

| 2       | 19       | b         | male     | 2       |

+---------+----------+-----------+----------+---------+--+

 

-- 窗口分析函数  sum() over()  :可以实现在窗口中进行逐行累加

A,2015-01,33

A,2015-02,10

A,2015-03,20

B,2015-01,30

B,2015-02,15

B,2015-03,45

C,2015-01,30

C,2015-02,40

C,2015-03,30

 

create table t_access_amount(uid string,month string,amount int)

row format delimited fields terminated by ',';

 

load data local inpath '/root/access.dat'into table t_access_amount;

 

0: jdbc:hive2://localhost:10000> select * from  t_access_amount;

+----------------------+------------------------+-------------------------+--+

| t_access_amount.uid  | t_access_amount.month  | t_access_amount.amount  |

+----------------------+------------------------+-------------------------+--+

| A                    | 2015-01                | 33                      |

| A                    | 2015-02                | 10                      |

| A                    | 2015-03                | 20                      |

| B                    | 2015-01                | 30                      |

| B                    | 2015-02                | 15                      |

| B                    | 2015-03                | 45                      |

| C                    | 2015-01                | 30                      |

| C                    | 2015-02                | 40                      |

| C                    | 2015-03                | 30                      |

+----------------------+------------------------+-------------------------+--+

 

-- 需求:求出每个人截止到每个月的总额

 

select uid,month,amount,

sum(amount) over(partition by uid order by month

 rows between unbounded preceding and current row) as accumulate

from t_access_amount;

+------+----------+---------+-------------+--+

| uid  |  month   | amount  | accumulate  |

+------+----------+---------+-------------+--+

| A    | 2015-01  | 33      | 33          |

| A    | 2015-02  | 10      | 43          |

| A    | 2015-03  | 20      | 63          |

| B    | 2015-01  | 30      | 30          |

| B    | 2015-02  | 15      | 45          |

| B    | 2015-03  | 45      | 90          |

| C    | 2015-01  | 30      | 30          |

| C    | 2015-02  | 40      | 70          |

| C    | 2015-03  | 30      | 100         |

+------+----------+---------+-------------+--+

 

 

-- 自定义函

/*

有如下json数据:rating.json

{"movie":"1193","rate":"5","timeStamp":"978300760","uid":"1"}

{"movie":"661","rate":"3","timeStamp":"978302109","uid":"1"}

{"movie":"914","rate":"3","timeStamp":"978301968","uid":"1"}

{"movie":"3408","rate":"4","timeStamp":"978300275","uid":"1"}

 

需要导入hive中进行数据分析

*/

 

-- 建表映射上述数据

create table t_ratingjson(json string);

 

load data local inpath '/root/hivetest/rating.json' into table t_ratingjson;

 

想把上面的原始数据变成如下形式:

1193,5,978300760,1

661,3,978302109,1

914,3,978301968,1

3408,4,978300275,1

 

思路:如果能够定义一个json解析函数,则很方便了

create table t_rate

as

select myjson(json,1) as movie,cast(myjson(json,2) as int) as rate,myjson(json,3) as ts,myjson(json,4) as uid from t_ratingjson;

 

解决:

hive中如何定义自己的函数:

1、先写一个java类(extends UDF,重载方法public C evaluate(A a,B b)),实现你所想要的函数的功能(传入一个json字符串和一个脚标,返回一个值)

2、将java程序打成jar包,上传到hive所在的机器

3、在hive命令行中将jar包添加到classpath :    

hive>add jar /root/hivetest/myjson.jar;

4、在hive命令中用命令创建一个函数叫做myjson,关联你所写的这个java类

hive> create temporary function myjson as 'cn.edu360.hive.udf.MyJsonParser';

 

--仅展示部分数据

 select * from student;  

 

+--------------+----------------+--------------+--------------+----------------+--+

| student.sno  | student.sname  | student.sex  | student.sag  | student.sdept  |

+--------------+----------------+--------------+--------------+----------------+--+

| 95001        | 李勇             | 男            | 20           | CS             |

| 95002        | 刘晨             | 女            | 19           | IS             |

| 95003        | 王敏             | 女            | 22           | MA             |

| 95004        | 张立             | 男            | 19           | IS             |

| 95005        | 刘刚             | 男            | 18           | MA             |

| 95006        | 孙庆             | 男            | 23           | CS             |

|                                                                                 

 

 select * from sc;

+---------+---------+-----------+--+

| sc.sno  | sc.cno  | sc.grade  |               

+---------+---------+-----------+--+           

| 95001   | 4       | 70        |               

| 95002   | 2       | 90        |

| 95002   | 3       | 80        |

| 95002   | 4       | 71        |

| 95002   | 5       | 60        |

 

 

 select * from course;

+-------------+---------------+--+

| course.cno  | course.cname  |

+-------------+---------------+--+

| 1           | 数据库           |

| 2           | 数学            |

| 3           | 信息系统          |

| 4           | 操作系统          |

| 5           | 数据结构          |

| 6           | 数据处理          |

+-------------+---------------+--+

 

 

--查询全体学生的学号与姓名

hive> select Sno,Sname from student;

+--------+--------+--+

|  sno   | sname  |

+--------+--------+--+

| 95001  | 李勇     |

| 95002  | 刘晨     |

| 95003  | 王敏     |

| 95004  | 张立     |

 

 

查询选修了课程的学生姓名

hive> select distinct Sname from student inner join sc on student.Sno=Sc.Sno;

| sname  |

+--------+--+

| 冯伟     |

| 刘刚     |

| 刘晨     |

 

----hive的group by 和集合函数

 

--查询学生的总人数

hive> select count(distinct Sno) from student;

+-----+--+

| c0  |

+-----+--+

| 22  |

+-----+--+

 

--计算1号课程的学生平均成绩

hive> select avg(distinct Grade) from sc where Cno=1;

+--------------------+--+

|         c0         |

+--------------------+--+

| 82.18181818181819  |

+--------------------+--+

 

--查询各科成绩平均分

hive> select Cno,avg(Grade) from sc group by Cno;  

+------+--------------------+--+

| cno  |        _c1         |

+------+--------------------+--+

| 1    | 83.66666666666667  |

| 2    | 88.66666666666667  |

| 3    | 81.46153846153847  |

| 4    | 83.125             |

| 5    | 85.0               |

| 6    | 89.45454545454545  |

+------+--------------------+--+

--查询选修1号课程的学生最高分数

select Grade from sc where Cno=1 sort by Grade desc limit 1;

+--------+--+

| grade  |

+--------+--+

| 98     |

+--------+--+

 

(注意比较:select * from sc where Cno=1 sort by Grade desc;

+---------+---------+-----------+--+

| sc.sno  | sc.cno  | sc.grade  |

+---------+---------+-----------+--+

| 95008   | 1       | 98        |

| 95013   | 1       | 98        |

| 95018   | 1       | 95        |

| 95016   | 1       | 92        |

| 95015   | 1       | 91        |

| 95014   | 1       | 91        |

| 95003   | 1       | 82        |

| 95001   | 1       | 81        |

| 95012   | 1       | 81        |

| 95011   | 1       | 81        |

| 95004   | 1       | 80        |

| 95019   | 1       | 77        |

| 95006   | 1       | 72        |

| 95005   | 1       | 70        |

| 95020   | 1       | 66        |

+---------+---------+-----------+--+

 select Grade from sc where Cno=1 order by Grade desc;  

  

 

求各个课程号及相应的选课人数

hive> select Cno,count(1) from sc group by Cno;

+------+------+--+

| cno  | _c1  |

+------+------+--+

| 1    | 15   |

| 2    | 15   |

| 3    | 13   |

| 4    | 16   |

| 5    | 12   |

| 6    | 11   |

+------+------+--+

 

 

查询选修了3门以上的课程的学生学号

hive> select Sno from (select Sno,count(Cno) CountCno from sc group by Sno)a where a.CountCno>3;

或 hive> select Sno from sc group by Sno having count(Cno)>3;

+--------+--+

|  sno   |

+--------+--+

| 95001  |

| 95002  |

| 95004  |

| 95005  |

| 95006  |

| 95007  |

| 95011  |

| 95012  |

| 95013  |

| 95015  |

| 95018  |

| 95019  |

| 95022  |

+--------+--+

----hive的Order By/Sort By/Distribute By

-- Order By ,在strict 模式下(hive.mapred.mode=strict),order by 语句必须跟着limit语句,

--但是在nonstrict下就不是必须的,这样做的理由是必须有一个reduce对最终的结果进行排序,如果最后输出的行数过多,一个reduce需要花费很长的时间。

 

--查询学生信息,结果按学号全局有序

hive> set hive.mapred.mode=strict;   <默认nonstrict>

hive> select Sno from student order by Sno;

--FAILED: Error in semantic analysis: 1:33 In strict mode, if ORDER BY is specified, LIMIT must also be specified. Error encountered near token 'Sno'

--  Sort By,它通常发生在每一个redcue里,“order by” 和“sort by"的区别在于,前者能给保证输出都是有顺序的,

--    而后者如果有多个reduce的时候只是保证了输出的部分有序。set mapred.reduce.tasks=在sort by可以指定,

--在用sort by的时候,如果没有指定列,它会随机的分配到不同的reduce里去。distribute by 按照指定的字段对数据进行划分到不同的输出reduce中

--  此方法会根据性别划分到不同的reduce中 ,然后按年龄排序并输出到不同的文件中。

 

--查询学生信息,按性别分区,在分区内按年龄有序

hive> set mapred.reduce.tasks=2;

hive> insert overwrite local directory '/home/hadoop/out'

select * from student distribute by Sex sort by Sage;

 

----Join查询,join只支持等值连接

--查询每个学生及其选修课程的情况

hive> select student.*,sc.* from student join sc on (student.Sno =sc.Sno);

--查询学生的得分情况。

hive>select student.Sname,course.Cname,sc.Grade from student join sc on student.Sno=sc.Sno join course on sc.cno=course.cno;

 

--查询选修2号课程且成绩在90分以上的所有学生。

hive> select student.Sname,sc.Grade from student join sc on student.Sno=sc.Sno

where  sc.Cno=2 and sc.Grade>90;

 

----LEFT,RIGHT 和 FULL OUTER JOIN ,inner join, left semi join

--查询所有学生的信息,如果在成绩表中有成绩,则输出成绩表中的课程号

hive> select student.Sname,sc.Cno from student left outer join sc on student.Sno=sc.Sno;

 

--  如果student的sno值对应的sc在中没有值,则会输出student.Sname null.如果用right out join会保留右边的值,左边的为null。

Join 发生在WHERE 子句之前。如果你想限制 join 的输出,应该在 WHERE 子句中写过滤条件——或是在join 子句中写。

 

----LEFT SEMI JOIN  Hive 当前没有实现 IN/EXISTS 子查询,可以用 LEFT SEMI JOIN 重写子查询语句

 

--重写以下子查询为LEFT SEMI JOIN

  SELECT a.key, a.value

  FROM a

  WHERE a.key exist in

   (SELECT b.key

    FROM B);

--可以被重写为:

   SELECT a.key, a.val

   FROM a LEFT SEMI JOIN b on (a.key = b.key)

 

--查询与“刘晨”在同一个系学习的学生

hive> select s1.Sname from student s1 left semi join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

 

--注意比较:

select * from student s1 left join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

select * from student s1 right join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

select * from student s1 inner join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

select * from student s1 left semi join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

select s1.Sname from student s1 right semi join student s2 on s1.Sdept=s2.Sdept and s2.Sname='刘晨';

 

/*

HIVE 中的复合数据类型

 

*/

-- 数组

-- 有如下数据:

战狼2,吴京:吴刚:龙母,2017-08-16

三生三世十里桃花,刘亦菲:痒痒,2017-08-20

普罗米修斯,刘德华:张学友:郭富城,2017-09-17

美女与野兽,吴刚:加藤鹰,2017-09-17

 

-- 建表映射:

create table t_movie(movie_name string,actors array,first_show date)

row format delimited fields terminated by ','

collection items terminated by ':';

-- 导入数据

load data local inpath '/root/hivetest/actor.dat' into table t_movie;

load data local inpath '/root/hivetest/actor.dat.2' into table t_movie;

-- 查询

select movie_name,actors[0],first_show from t_movie;

 

select movie_name,actors,first_show

from t_movie where array_contains(actors,'吴刚');

 

select movie_name

,size(actors) as actor_number

,first_show

from t_movie;

第三章 DataFrame常用操作

3.1 DSL风格语法

DataFrame提供了一个特定领域语言(DSL)来操作结构化数据.接下来式DataFrame的一些案例

3.1.1 show

查看DataFrame中的内容,通过调用show方法

personDF.show

3.1.2 查看某一列内容

查看DataFrame部分列中的内容,查看name字段的数据

personDF.select(personDF.col("name")).show

 

查看name字段的另一种写法

personDF.select("name").show

查看name字段和age字段

personDF.select("name","age").show

3.1.3 打印DataFrame的Schema信息

personDF.printSchema

 

 

3.1.4 查询所有的name和age,并将age+1

//第一种方式

personDF.select(col("name"),col("age"),col("age")+1).show

//第二种方式

personDF.select($"name",$"age",$"age"+1).show

//第三种方式

personDF.select(personDF("name"),personDF("age"),personDF("age")+1).show

3.1.5 过滤age大于等于20的.

personDF.filter($"age">20).show

 

3.1.6 统计年龄大于20的人数

personDF.filter($"age">20).count()

 

3.1.7 按年龄进行分组并统计相同年龄的人数

personDF.groupBy("age").count().show

3.2 SQL风格语法

 DataFrame的一个强大之处就是我们可以将它看作是一个关系型数据表,然后可以通过在程序中使用spark.sql() 来执行SQL语句查询,结果返回一个DataFrame。

 如果想使用SQL风格的语法,需要将DataFrame注册成表,采用如下的方式:

//已经过时了

personDF.registerTempTable("tb_person")

//推荐使用这种方式

personDF.createOrReplaceTempView("tb_person")

3.2.1 查询年龄最大的前两名

spark.sql("select * from tb_person order by age desc limit 2").show

3.2.2 显示表的Schema信息

spark.sql("desc tb_person").show

 

 

3.2.3 查询年龄大于20的人的信息

spark.sql("select * from tb_person where age > 20").show

   4.41  COALESCE()函数 

主流数据库系统都支持COALESCE()函数,这个函数主要用来进行空值处理,其参数格

式如下: 

COALESCE ( expression,value1,value2……,valuen) 

  COALESCE()函数的第一个参数expression为待检测的表达式,而其后的参数个数不定。

COALESCE()函数将会返回包括expression在内的所有参数中的第一个非空表达式。如果

expression不为空值则返回expression;否则判断value1是否是空值,如果value1不为空值则返

回value1;否则判断value2是否是空值,如果value2不为空值则返回value3;……以此类推,

如果所有的表达式都为空值,则返回NULL。 

  我们将使用COALESCE()函数完成下面的功能,返回人员的“重要日期”:如果出生日

期不为空则将出生日期做为“重要日期”,如果出生日期为空则判断注册日期是否为空,如

果注册日期不为空则将注册日期做为“重要日期”,如果注册日期也为空则将“2008年8月8

日”做为“重要日期”。实现此功能的SQL语句如下: 

MYSQL、MSSQLServer、DB2: 

SELECT FName,FBirthDay,FRegDay, 

COALESCE(FBirthDay,FRegDay,'2008-08-08')  AS ImportDay  

FROM T_Person 

Oracle: 

SELECT FBirthDay,FRegDay,  

COALESCE(FBirthDay,FRegDay,TO_DATE('2008-08-08', 'YYYY-MM-DD HH24:MI:SS'))  

AS ImportDay  

FROM T_Person 

  执行完毕我们就能在输出结果中看到下面的执行结果: 

FName  FBirthDay  FRegDay  ImportDay 

Tom  1981-03-22 00:00:00  1998-05-01 00:00:00  1981-03-22 00:00:00 

Jim  1987-01-18 00:00:00  1999-08-21 00:00:00  1987-01-18 00:00:00 

Lily  1987-11-08 00:00:00  2001-09-18 00:00:00  1987-11-08 00:00:00 

Kelly  1982-07-12 00:00:00  2000-03-01 00:00:00  1982-07-12 00:00:00 

Sam  1983-02-16 00:00:00  1998-05-01 00:00:00  1983-02-16 00:00:00 

Kerry    1999-03-01 00:00:00  1999-03-01 00:00:00 

Smith      2008-08-08 

BillGates  1972-07-18 00:00:00  1995-06-19 00:00:00  1972-07-18 00:00:00 

  这里边最关键的就是Kerry和Smith这两行,可以看到这里的计算逻辑是完全符合我们的

需求的。

---------------------

作者:繁星满天24

来源:CSDN

原文:https://blog.csdn.net/u010031649/article/details/38781757

版权声明:本文为博主原创文章,转载请附上博文链接!

 

你可能感兴趣的:(小兔子,大数据)