阶段5:流式计算storm
storm:实时数据计算框架
hadoop包含两个框架:hdfs和mapreduce
storm和hadoop的区别在于storm只负责计算不负责存储
storm框架如何获取数据:spout。spout从任何地方取数据,比如文件,数据库,reids等
hadoop用textinputformat获取数据
hadoop用mapreduce计算数据,storm用Bolt计算数据
storm继承baserichspout类读取数据。实现里面的三个方法:
open(初始化方法),nexttuple(循环调用的方法),declareOutputFields(定义字段)
storm继承basebasicbolt类处理数据,实现了execute和declareOutputFields方法
hadoop的map和reduce之间有shuffle,把单词放到迭代器里面。而storm没有,直接就发到下一个bolt去了
topologyBuilder负责生成storm的应用程序(用shuffleGrouping方法)
storm任务的提交分为本地模式和集群模式
pom.xml中的plugin标签可以把依赖的jar包打成一个包
storm集群上运行的命令为storm jar jar包 驱动类全路径
storm程序中的某一个环节计算能力跟不上的时候,需要增加多个线程。这个概念叫并行度。在setBolt里面增加参数。
storm分组的概念:数据怎么分发,分发给谁:轮询;哈希;随机;指定发送给某个线程(直接分组);广播发送(一式多份);
local or shuffle grouping(local指的是当前jvm,shuffle指的是集群)如果storm程序中的并行度总数太多,就放到多个jvm中运行。可以按照字段分组。
为了减少jvm之间数据传输的开销,如果数据分发没有严格的业务含义,考虑spout数据只发给当前jvm的下游bolt。
config.setNumWorkers可以控制在多个jvm之间分发数据
一般将多个并行度中的实例叫做task。默认情况下一个bolt的并行度是4,分组默认为1。
storm集群中的三个角色:nimbus主节点,supervisor:每个机器上的管理者,UI界面
storm集群启动多少个worker是由用户配置的,每个supervisor启动多少个worker是由nimbus决定的。
storm提交任务的过程:用户提交任务---计算任务分配信息---将任务写入zookeeper---zookeeper的watch机制通知各个supervisor(相当于yarn的nodemaneger)----supervisor下载任务并启动worker
baseBasicBolt里面的prepare方法可以做初始化redis连接等初始化操作。
kafka和storm都依赖zookeeper,可能发生冲突,要在pom文件中排除掉一个。
从kafka中获取的数据是一个field名字为bytes的比特数组
启动storm的命令:storm nimbus,storm supervisor
默认情况下一个supervisor节点会启动4个worker进程
每个worker进程会启动1个executer线程
每个executer会启动1个task
maptask输出时的排序规则
自定义partitioner
将partitioner中getpartition方法的第三个参数设置成numPartitions可以使得每个相同的key分到一个单独的reduceTask
job.setCombinerClass传入reducetask类即可
setgroupingComparatorClass(继承writableComparator)解决topN问题,解决分区之后再按其他指标重新排名(将流量统计结果按照不同省份输出到不同文件)。
setgroupingComparatorClass()(传入的参数类要继承writableComparator)的作用是对reduce端接收数据分组时的key的比较逻辑进行自定义。的应用场景举例:
需求是求每一个订单中成交金额最大的topn。在map阶段我们就要完成分区和按照id和金额排序,因此OrderEntity的compareTo方法先比较订单Id后比较订单金额,
但是这会导致reduce分组时每个id和金额相同的订单会分为一组(map排序和reduce分组都依赖compareTo),而不是id相同的订单分为一组,阻止了我们在每一个分组中求topn。
然而compareTo方法只能有一个,所以我们就需要setgroupingComparatorClass来自定义reduce端接收数据时的分组方式。
setgroupingComparatorClass的用法是里面有一个构造方法和一个compare方法。
自定义outPutFormat(继承fileOutPutFormat)(传入setOutPutClass方法)可以根据不同情况分别将生成的文件输出到不同的路径.
该类里面有一个自定义的getRecordWriter方法返回你的自定义类,和一个RecordWriter的子类做内部类(也就是你的自定义类)(里面有write方法里面是业务代码,close方法在程序执行完之后执行)
(注释:new Path("xxx").write(xxx)方法可以将数据写入某个hdfs路径)
全局计数器Counter c = context.getCounter("全局计数器的组","全局计数器的值");c.increment(1)
job串联ControlledJob.setjob;addDependingJob;jobControl.addJob;将jobControl放入多线程
mapreduce数据压缩(map和reduce都可以压缩)
ha下java操作hdfs的api变化(hadoop的HA包含hdfs的双namenode消除单点故障和yarn的ResourceManager的ha):
hdfs地址指定的是hdfs的nameservice,和core-site.xml保持一致,core-site.xml里面配置了nameservice下面的多个namenode及其通信地址
hive的特点:自由扩展集群规模不需要重启;支持用户自定义函数;节点容错
hive有内部表外部表的概念(内部表文件都存在于user/hive/warehouse,外部表建表时指明location)
row format delimited:使用hadoop默认分隔符
fields terminated by ','指定列分隔符
hive加载数据的方式:上传文件,load data inpath''into table xxx,
hive的元数据库在mysql中
建表partitioned by 分区表load data inpath''into table xxx partition(分区字段=xx)。建分区方便查询,只查询当前分区,效率高
stored as textfile存储格式
SEQUENCEFILE|TEXTFILE|RCFILE
如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE。
SEQUENCEFILE不能通过load来加载数据,只能查询其他表后插入
创建分区表:partitioned by (pt_d string)
增删分区alter table add/drop partition()
重命名ALTER TABLE table_name RENAME TO new_table_name
增加更新列:ALTER TABLE table_name ADD|REPLACE/change COLUMNS
注意,分区并不是一个字段,而是一个文件。
1、order by 会对输入做全局排序,因此只有一个reducer,会导致当输入规模较大时,需要较长的计算时间。
2、sort by不是全局排序,其在数据进入reducer前完成排序。因此,如果用sort by进行排序,并且设置mapred.reduce.tasks>1,则sort by只保证每个reducer的输出有序,不保证全局有序。
3、distribute by(字段)根据指定的字段将数据分到不同的reducer,且分发算法是hash散列。
4、Cluster by(字段) 除了具有Distribute by的功能外,还会对该字段进行排序。
因此,如果分桶和sort字段是同一个时,此时,cluster by = distribute by + sort by
分桶表的作用:最大的作用是用来提高join操作的效率避免查询全表 Clustered by(字段) into 5 buckets(哈希分桶)
(区分Cluster by和Clustered by,前者是sql语句处理数据用的,后者是建表时用的)
设置属性hive.mapred.mode 为strict能够阻止以下三种类型的查询:
1、 除非在where语段中包含了分区过滤,否则不能查询分区了的表。这是因为分区表通常保存的数据量都比较大,没有限定分区查询会扫描所有分区,耗费很多资源。
不允许:select *from logs;
允许:select * from logs where day=20151212;
2、 包含order by,但没有limit子句的查询。因为order by 会将所有的结果发送给单个reducer来执行排序,这样的排序很耗时。
3、 笛卡尔乘积;简单理解就是JOIN没带ON,而是带where的
分桶:CLUSTERED BY (`campaignid`, `mediaid` ) INTO 100 BUCKETS
create table student4(sno int,sname string,sex string,sage int, sdept string) clustered by(sno) into 3 buckets
对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive也是 针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。
把表(或者分区)组织成桶(Bucket)有两个理由:
(1)获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。
具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接 (Map-side join)高效的实现。比如JOIN操作。
对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。
(2)使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。
分桶机制默认关闭,需要开启。
分桶的表不能load插数据,只能查询其他表插数据
桶表抽样查询
Select * from student tablesample(bucket 1 out of 2 on id)
tablesample是抽样语句,语法:TABLESAMPLE(BUCKET x OUT OF y)
y必须是table总bucket数的倍数或者因子。hive根据y的大小,决定抽样的比例.
如,table总共分了64份,当y=32时,抽取(64/32=)2个bucket的数据,当y=128时,抽取(64/128=)1/2个bucket的数据。
x表示从哪个bucket开始抽取。例如,table总bucket数为32,tablesample(bucket 3 out of 16),表示总共抽取(32/16=)2个bucket的数据,分别为第3个bucket和第(3+16=)19个bucket的数据。
Hive 支持等值连接(equality joins)、外连接(outer joins)和(left/right joins)。Hive 不支持非等值的连接,因为非等值连接非常难转化到 map/reduce 任务。
另外,Hive 支持多于 2 个表的连接。
reducer 会缓存 join 序列中除了最后一个表的所有表的记录,再通过最后一个表将结果序列化到文件系统。
这一实现有助于在 reduce 端减少内存的使用量。实践中,应该把最大的那个表写在最后(否则会因为缓存浪费大量内存)。例如:
SELECT a.val, b.val, c.val FROM a
JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)
这里用了 2 次 map/reduce 任务。第一次缓存 a 表,用 b 表序列化;第二次缓存第一次 map/reduce 任务的结果,然后用 c 表序列化。
LEFT SEMI JOIN是IN/EXISTS的高效实现。
Hive参数配置有以下三种设定方式:
配置文件
命令行参数
参数声明
hive集合类型主要包括:array,map,struct等
UDF 作用于单个数据行,产生一个数据行作为输出。
1、先开发一个java类,继承UDF,并重载evaluate方法
2、打成jar包上传到服务器
3、将jar包添加到hive的classpath
hive>add JAR /home/hadoop/udf.jar;
4、创建临时函数与开发好的java class关联
Hive>create temporary function tolowercase as 'cn.itcast.bigdata.udf.ToProvince';
5、即可在hql中使用自定义的函数tolowercase ip
Select tolowercase(name),age from t_test;
Hive的 TRANSFORM 关键字提供了在SQL中调用自写脚本的功能
适合实现Hive中没有的功能又不想写UDF的情况
SELECT
TRANSFORM (movieid , rate, timestring,uid)
USING 'python weekday_mapper.py'
AS (movieid, rating, weekday,userid)
FROM t_rating;
Hive对文件中的分隔符默认情况下只支持单字节分隔符,,默认单字符是\001。
hive处理特殊分隔符的方式:
1.使用RegexSerDe通过正则表达式来抽取字段
create table t_bi_reg(id string,name string)
row format serde 'org.apache.hadoop.hive.serde2.RegexSerDe'
with serdeproperties(
'input.regex'='(.*)\\|\\|(.*)',
'output.format.string'='%1$s%2$s'
)
stored as textfile;
2.自定义inputformat
1.打包成jar,放到$HIVE_HOME/lib下
2.建表指明自定义的inputformat
create table t_lianggang(id string,name string)
row format delimited
fields terminated by '|'
stored as inputformat 'cn.itcast.bigdata.hive.inputformat.BiDelimiterInputFormat'
outputformat 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat(hive内置的)';
hive不适合做正删改查,hbase适合
hbase是典型的key/value系统。是建立在hdfs之上,实时读写的介于关系型数据库和nosql的数据库系统。主要用于海量结构化和半结构化数据存储。
它介于nosql和RDBMS之间,仅能通过主键(row key)和主键的range来检索数据,仅支持单行事务(可通过hive支持来实现多表join等复杂操作)。
Hbase查询数据功能很简单,不支持join等复杂操作,不支持复杂的事务(行级的事务)
与hadoop一样,Hbase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。
HBase中的表一般有这样的特点:
大:一个表可以有上十亿行,上百万列
无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列;
面向列:面向列(族)的存储和权限控制,列(族)独立检索。
稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳
数据类型单一:Hbase中的数据都是字节数组 byte[]。
hbase的行键按照字典顺序排列。
hbase表中的每个列,都归属与某个列族。列族是表的schema的一部分(而列不是),必须在使用表之前定义。
列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。
访问控制、磁盘和内存的使用统计都是在列族层面进行的。
列族越多,在取一行数据时所要参与IO、搜寻的文件就越多,所以,如果没有必要,不要设置太多的列族
Cell
由{row key, column( =<family> + <label>), version} 唯一确定的单元。
cell中的数据是没有类型的,全部是字节码形式存贮。
hbase删除表之前要先disable table
hbase的java api中的各种过滤器和分页查询
flume可以监控路径,文件。生成的文件可以自动切分。
flume拦截器,自定义拦截器,agent串联
flume内置拦截器有四种,时间拦截器,主机拦截器,静态拦截器,正则过滤拦截器。前三种是为了添加报头,第四种是为了过滤消息。
拦截器起作用于source和channel之间,一个拦截器只能拦截一个source的消息。
flume日志分类采集基于拦截器,在source中配置拦截器,在sink中把拦截器字段作为变量。
自定义Flume拦截器:过滤掉不需要的字段,并对指定字段加密处理,将源数据进行预处理。减少了数据的传输量,降低了存储的开销。
自定义拦截器的使用方法为:
编写一个java类打成jar包放到flume/lib。然后在flume配置文件中指明类的路径,数据的分隔符,分割之后的传入的参数的索引等配置项。
一个source可以配置多个拦截器,同时起作用。
Interceptor(hive拦截器)
用于Source的一组Interceptor,按照预设的顺序在必要地方装饰和过滤events。
内建的Interceptors允许增加event的headers比如:时间戳、主机名、静态标记等等
定制的interceptors(改变和处理日志中的内容)可以通过内省event payload(读取原始日志),实现自己的业务逻辑(很强大)
自定义拦截器实现了Interceptor接口,里面的final变量是通过配置文件传进来的变量。里面有builder内部类实现了Interceptor.Builder。
在Intercept方法里接收event集合,解析出每一个event的每一行再进行进一步处理。
将打好的har包上传到flume的lib文件夹
sqoop导入到hive的本质是先导入hdfs
数据导出必须现在mysql上创建了表。默认模式是insert,更新模式是update
sqoop作业:将命令封装在作业里面(不是调用脚本,就是执行shell命令),执行job的时候会调用
bin/sqoop job --create myimportjob
sqoop job --list查看作业列表
sqoop job --exec myjob执行作业
Sqoop的原理其实就是将导入导出命令转化为mapreduce程序来执行,sqoop在接收到命令后,都要生成mapreduce程序
使用sqoop的代码生成工具可以方便查看到sqoop所生成的java代码,并可在此基础之上进行深入定制开发(用得少)
1)基础分析(PV,IP,UV)
PV(访问量):即Page View, 即页面浏览量或点击量,用户每次刷新即被计算一次。
IP(独立IP):指独立IP数。00:00-24:00内相同IP地址之被计算一次。
UV(独立访客):即UniQue Visitor,访问您网站的一台电脑客户端为一个访客。 00:00-24:00内相同的客户端只被计算一次。
storm:实时数据计算框架,不负责存储数据,由spout获取数据,从数据库,文件,redis中等拿数据,bolt计算数据
hadoop离线计算框架,存储数据,由inputformat获取数据,MapReduce计算数据
storm中用TopologyBuilder来生成job
storm的spout中有open,nextTuple(collector.emit方法传递数据到bolt)和field方法,bolt中有execute(input获取的数据的格式是byte[])和field方法
storm可以本地提交或集群提交
storm提交任务的时候要指定jar包和驱动类(TopologyBuilder类)
某个环节计算跟不上的时候,需要多个线程同时执行,叫做并行度,在TopologyBuilder类里面设置setNumTasks(4)
分组:数据分发给哪个线程(轮询,哈希取模,随机(默认,即为shufflegrouping方法,该方法用于指定处理的先后顺序),指定线程,广播,全局分组,local or shuffle grouping)
local or shuffle grouping(必须在没有业务含义的情况下使用)(为了减少jvm之间传输的开销):
如果有一个或多个task在同一个jvm进程(每个jvm都有spout和bolt),就发给这些task,否则就如同shuffle grouping
config.setNumWorkers()可以设置jvm的数量。worker就是jvm。不指定的话默认是一个jvm,一个线程。
多个并行度中的实例,称为task.task基本上是平均分配在worker上。
实时日志监控告警的架构:flume,kafka,storm,数据库手机邮件等
后台管理系统设置appId,用于flume过滤,然后运维再启动flume
定时更新规则的实现方式为在bolt的execute中判断是否到了更新配置规则的时间
通过将告警appid和规则id存进redis并设置过期时间来避免重复告警
通过多线程可以设置每个worker只执行一次每隔十分钟规则改变,通过变量控制可以避免一分钟内大量重新加载规则。
大数据风控系统的预警:storm实时获取activemq传送过来的订单信息,解析并匹配规则(规则从规则库中定时加载),匹配成功就通知相关业务员
scala编程.doc 复习scala语法
akka和actor编程还复习
sparkcontext:所有计算的开头
sparkconf:设置配置信息,app名字,设置集群运行模式和本地运行模式
提交到集群运行时,sc.textfile默认是两个分区。本地运行的分区数是自己设置的
spark-submit提交任务
设置cache的地方:小任务遇到shuffle,大任务
shuffle宽依赖最好设置checkpoint
cache放到内存中
checkpoint放到本地磁盘或hdfs
sparksql是spark中操作结构化数据的模块,可以对接文本文件,hive,mysql
sparksql相当于hive
书写代码的两种方式是dataframe和sql
sql:spaeksql集成sql的语法:
1.通过sc加载任意类型的数据
2.创建表结构(case class比class,全部变量都是val,多了tostring,hashcode,equal方法)
3,将数据添加到表结构
4转化成df
5.将df注册成表
6.sqlcontext.sql操作注册的表
map:操作单行
flatmap:压平多行
Spark SQL的应运而生,它是将Spark SQL转换成dataframe
与RDD类似,DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表格,
除了数据以外,还记录数据的结构信息,即schema。同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。
从API易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。
sparksql可以从mysql中读写数据,可以集成hive使用(加载hive中的数据到内存)
sparkstreaming对比storm
flume+sparkstreaming
kafka+sparkstreaming
将数据写入到MySQL中(api):
//创建Properties存储数据库相关属性
val prop = new Properties()
prop.put("user", "root")
prop.put("password", "123456")
//将数据追加到数据库
sqlcontext.sql("xxx").write.mode("append").jdbc("jdbc:mysql://192.168.10.1:3306/bigdata", "bigdata.person", prop)
从mysql中加载数据(命令行)
val jdbcDF = sqlContext.read.format("jdbc").options(Map("url" -> "jdbc:mysql://192.168.10.1:3306/bigdata", "driver" -> "com.mysql.jdbc.Driver", "dbtable" -> "person", "user" -> "root", "password" -> "123456")).load()
执行查询
jdbcDF.show()
spark对接hive只需要把hive的配置文件hive-site.xml拿过来,告诉spark hive的mysql元数据表的位置
val conf=new SparkConf().setAppName("Spark-Hive").setMaster("local")
val sc=new SparkContext(conf)
//create hivecontext
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)
sqlContext.sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' ") //这里需要注意数据的间隔符
sqlContext.sql("LOAD DATA INPATH '/user/liujiyu/spark/kv1.txt' INTO TABLE src ");
sqlContext.sql(" SELECT * FROM jn1").collect().foreach(println)
sc.stop()
sparkStream是spark流式处理的框架。可以从kafka,flume,hdfs中读数据,可以将数据写入到hdfs和传统数据库或redis里
sparkStream将流式数据封装成DStream,变成批处理。DStream是一系列连续的RDD,每个RDD含有一段时间间隔内的数据
DStream的操作和RDD的操作相同
//创建SparkConf并设置为本地模式运行
//注意local[2]代表开两个线程(两个线程一个用于收集数据一个用于处理)
val conf = new SparkConf().setMaster("local[2]").setAppName("NetworkWordCount")
//设置DStream批次时间间隔为2秒)(设置多长时间计算一次)
val ssc = new StreamingContext(conf, Seconds(2))
//通过网络读取数据)(注册监听)
val lines = ssc.socketTextStream("192.168.10.101", 9999)
//将读到的数据用空格切成单词
val words = lines.flatMap(_.split(" "))
...
//开始计算
ssc.start()
//等待停止
ssc.awaitTermination()
updateStateByKey用来解决reduceByKey的不累加性
WINDOWS操作:reduceByKeyAndWindow(窗口显示时间,窗口移动时间):设置多长时间移动和显示一次。用于绘制k线图,每秒计算流量统计。
val result: DStream[(String, Int)] = textStream.flatMap(_.split(" ")).map((_,1)).reduceByKeyAndWindow((a:Int,b:Int) => (a + b),Seconds(5),Seconds(5))
result.print()
spark和sparkStreaming的区别:sparkStreaming是小批量的rdd处理方式
sparkStreaming从flume获取数据有pull和push两种
用户画像看1348
反外挂看1,复习elk
impala看1.10.11.12
用户画像的流程:数据收集,行为建模,用户画像
静态信息缺少的可以通过建模来判断
电商大数据应用之用户画像.docx
hive整合hbase.doc
Hive整合HBase后的使用场景:
(一)通过Hive把数据加载到HBase中,数据源可以是文件也可以是Hive中的表。
(二)通过整合,让HBase支持JOIN、GROUP等SQL查询语法。
(三)通过整合,不仅可完成HBase的数据实时查询,也可以使用Hive查询HBase中的数据完成复杂的数据分析。
官方网站将日志生产到日志采集服务器nginx中,flume和logstash再将日志采集到kafka中供离线分析和实时分析
京东通过log.gif来访问日志服务器
和传统的日志处理方案相比,ELK Stack 具有如下几个优点:
处理方式灵活。Elasticsearch 是实时全文索引,不需要像 storm 那样预先编程才能使用;
配置简易上手。Elasticsearch 全部采用 JSON 接口,Logstash 是 Ruby DSL 设计,都是目前业界最通用的配置语法设计;
检索性能高效。虽然每次查询都是实时计算,但是优秀的设计和实现基本可以达到全天数据查询的秒级响应;
集群线性扩展。不管是 Elasticsearch 集群还是 Logstash 集群都是可以线性扩展的;
前端操作炫丽。Kibana 界面上,只需要点击鼠标,就可以完成搜索、聚合功能,生成炫丽的仪表板。
Kafka采用zookeeper作为管理,记录了producer到broker的信息,
以及consumer与broker中partition的对应关系。因此,生产者可以直接把数据传递给broker,
broker通过zookeeper进行leader-->followers的选举管理;消费者通过zookeeper保存读取的位置offset以及读取的topic的partition分区信息。
由于上面的架构设计,使得生产者与broker相连;消费者与zookeeper相连。有了这样的对应关系,就容易部署logstash-->kafka-->logstash的方案了。
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。
Kibana 允许使用者采用 Lucene Query String 语法搜索 Elasticsearch 中的数据。请求可以在页面顶部的请求输入框中书写。
在请求框中输入如下内容。然后查看表格中的前几行内容。friends, romans, countrymen
logstash的filter中可以根据type字段等进行判断和过滤,对数据进行操作和处理,自定义参数并赋值value等
impala是一个高效的sql查询工具,快于hive(mr)和sparkrdd(rdd)
impala是基于hive并使用内存进行计算,兼容hive的绝大多数语法,可以完全替代hive兼顾数据仓库
impala使用的是hive的元数据(但是他是找hive不找mysql),因此使用之前需要启动hive的metadata服务
impala缺点:内存消耗大,每个节点128内存
impala当中数据加载的四种方式:
1.从hdfs load inpath到impala的文件夹
2.create table as select
insert into
insert into select
公司一般是用脚本封装impala命令,少数用java api(使用的是hive驱动类,jdbc方式执行查询并遍历查询结果)
一共要复习九个原理
day03笔记-zookeeper.docx
zookeeper是分布式系统的协调服务,只要有半数以上节点存活就能正常使用
zookeeper应用场景:
主节点的主备切换(主节点挂掉之后zookeeper会将他删除,其他机器监听zookeeper会知道挂了)
主节点的选举
分布式共享锁(进程之间的锁不能用多线程)(每个进程在zookeeper上按照序号从小到大注册和删除自己的锁sequential znode,id最小的可以获得锁,其他的等待)
Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理(统一管理配置文件)、分布式共享锁、统一名称服务(dubbo哪些服务器有某个名称的服务)……
虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能:
a、管理(存储,读取)用户程序提交的数据;
b、并为用户程序提供数据节点监听服务;
zookeeper特性
1、Zookeeper:一个leader,多个follower组成的集群
2、全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
3、分布式读写,更新请求转发,由leader实施
4、更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
5、数据更新原子性,一次数据更新要么成功(半数以上节点成功),要么失败
6、实时性,在一定时间范围内,client能读到最新数据(一致性同步的时间延迟:毫秒之内。是实时的)
读数据的时候会找最新的数据版本,原理为paxos《paxos一致性算法原理》一致性算法和流言
zookeeper不适合做大量数据的存储,都是保存的小数据。每个znode数据要小于一兆(配置文件里面设置),最好是在1k之内
Znode有两种类型:
短暂(ephemeral)(客户端断开连接自己删除,常用)
持久(persistent)(客户端断开连接不删除)
创建 ZooKeeper 节点 (znode路径 : zoo2, 数据: myData2 ,权限: OPEN_ACL_UNSAFE(一般只用这个开放的权限) ,节点类型: Persistent");
zk.create("/zoo2", "myData2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
初始化zkclient的时候创建的监听可以监听到zk的所有变化(需要在getdata中将监听参数设置为true),可以筛选自己关心的进行处理event.getpath和gettype
zk.getData("/zoo2", true, null);返回结果是字节数组
设置所有版本的数据:zk.setData("/zoo2", "shenlan211314".getBytes(), -1);
监听子节点:getChildren(path,watch?)监听的事件是:节点下的子节点增减变化事件
删除某路径所有版本的数据:zk.delete(this.thisPath, -1);
Stat s = zk.exists("/zoo2", true)布尔值代表是否持续监听,监听的话也可以传入Watcher。Stat是元数据,不存在时为null。
zookeeper的选举机制(zk的数据一致性核心算法paxos)
以一个简单的例子来说明整个选举的过程(根据id来选举).
假设有五台服务器组成的zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的.假设这些服务器依序启动,来看看会发生什么.
1) 服务器1启动,此时只有它一台服务器启动了,它发出去的报没有任何响应,所以它的选举状态一直是LOOKING状态(第一个服务器选举自己)
2) 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,
但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1,2还是继续保持LOOKING状态.
3) 服务器3启动,根据前面的理论分析,服务器3成为服务器1,2,3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的leader.
4) 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1,2,3,4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了.
5) 服务器5启动,同4一样,当小弟.
非全新集群的选举机制(数据恢复,需要考虑数据version,选大的)
那么,初始化的时候,是按照上述的说明进行选举的,但是当zookeeper运行了一段时间之后,有机器down掉,重新选举时,选举过程就相对复杂了。
需要加入数据version、leader id和逻辑时钟。
数据version:数据新的version就大,数据每次更新都会更新version。
Leader id:就是我们配置的myid中的值,每个机器一个。
逻辑时钟:这个值从0开始递增,每次选举对应一个值,也就是说: 如果在同一次选举中,那么这个值应该是一致的 ; 逻辑时钟值越大,说明这一次选举leader的进程更新.
选举的标准就变成:
1、逻辑时钟小的选举结果被忽略,重新投票
2、统一逻辑时钟后,数据id大的胜出
3、数据id相同的情况下,leader id大的胜出
根据这个规则选出leader。
redis中的value内部可以支持各种数据结构类型,比如可以存入一个普通的string,还可以存list,set,hashmap,sortedSet(有序的set)
redis应用场景
A、用来做缓存(ehcache/memcached)——redis的所有数据是放在内存中的(内存数据库)
B、可以在某些特定应用场景下替代传统数据库——比如社交类的应用
C、在一些大型系统中,巧妙地实现一些特定的功能:session共享、购物车
只要你有丰富的想象力,redis可以用在可以给你无限的惊喜…….
redis的特性
1、redis数据访问速度快(数据在内存中)
2、redis有数据持久化机制(持久化机制有两种:1、定期将内存数据dump到磁盘;2、aof(append only file)持久化机制——用记日志的方式记录每一条数据更新操作,
一旦出现灾难事件,可以通过日志重放来恢复整个数据库)
3、redis支持集群模式(容量可以线性扩展)
4、redis相比其他缓存工具(ehcach/memcached),有一个鲜明的优势:支持丰富的数据结构
jedis是redis的客户端(api和命令行代码很相似)
reids本质上是存储字节数组。
存储对象要将对象转化成字节数组。也可以将对象转换成json串存储进value
jedis.lpush(“”,“”)从左边插入list
List功能演示
#从头部(左边)插入数据
redis>LPUSH key value1 value2 value3
#从尾部(右边)插入数据
redis>RPUSH key value1 value2 value3
#读取list中指定范围的values
redis>LRANGE key start end
redis> lrange task-queue 0 -1 读取整个list
#从头部弹出一个元素
LPOP key
#从尾部弹出一个元素
RPOP key
#从一个list的尾部弹出一个元素插入到另一个list
RPOPLPUSH key1 key2 ## 这是一个原子性操作
Redis中的Hashes类型可以看成具有String Key和String Value的map容器
redis 127.0.0.1:6379> hset user001:zhangsan iphone 6
(integer) 1
取出hash数据中一个指定field的值
redis 127.0.0.1:6379> hget user001:zhangsan xiaomi
"8"
Set数据结构功能
集合的特点:无序、无重复元素
插入一条set数据:
redis 127.0.0.1:6379> sadd frieds:zhangsan bingbing baby fengjie furong ruhua tingting
(integer) 6
获取一条set数据的所有members:
redis 127.0.0.1:6379> smembers frieds:zhangsan
02_离线计算系统_第2天(HDFS详解).docx
hadoop集群分为hdfs集群和yarn集群
一条hdfs元数据是150字节
namenode checkpoint过程:
1.通知secondary namenode准备checkpoint
2.切换日志文件,往新的edits文件里面写日志
3.secondary namenode下载日志文件和image镜像
4.secondary namenode将镜像文件加载到内存,然后将edits文件与之合并
5.将secondary namenode内存中的文件恢复成镜像文件回传给namenode并替换原来的image
reducetask数量比partition分区数少的时候会报错。但是reducetask为1时partition组件会失效
使用combiner组件要保证不能影响业务
maptask机制:
maptask通过inputformat.getrecordreader来获得recordreader对象
recordreader调用nextkey和nextvalue方法从切片中读取key value,然后将值传入map方法
context.write将map()处理后得到的key value用mapoutputCollect调用collect方法将数据传入环形缓冲区(默认大小100m)。环形缓冲区默认溢出比为80%。
环形缓冲区溢出到spiller时会排序,用compareTo。
溢出组件spiller调用spill方法将数据写入磁盘(溢出可能有多次,但至少有一次)
写入磁盘的过程中会分区.分区getPartition方法
多次溢出的各个磁盘文件要进行合并,两两分区排序,最终合并成一个。文件两两之间合并时使用归并排序,combiner程序在此时进行数据合并
为了方便reducetask来读取数据,还会生成一个索引文件,记录每个分区的起始偏移量
mrappmaster协调mapreduce,例如哪个reduce处理哪个分片的数据
reducetask机制:
reducetask去每个maptask拉取相应分区的数据(http下载)
合并成一个有序文件,归并排序
用groupingComparator组件判断key是否相等,分批调用reduce方法。
outputformat.getrecordwriter获取recordwrite调用write方法输出数据
shuffle过程机制:指的是从maptask将数据输出到磁盘开始,到reducetask调用reduce方法读取数据为止。包含数据的缓存,分区排序,数据的分发传输等
03_离线计算系统_第3天(MAPREDUCE详解).docx
maptask并行度机制:
切片是一个逻辑概念,默认切片大小是hdfs的block size,128m。切片是在客户端的jobSubmiter,fileinputformat.getsplit进行的
将计算好的切片filesplit序列化到job.split文件中,将该文件提交给mrappmaster,一起提交的还有程序和配置等,由 mrappmaster决定启动多少个maptask
小文件场景下切片机制会让大量的maptask处理很少数据,效率低下,处理方法:
1.文件上传到hdfs之前进行文件合并(常用)
2.上传hdfs之后合并
3.修改getsplit源码(难)
通过分析源码,在FileInputFormat中,计算切片大小的逻辑:
Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定
minsize:默认值:1
配置参数: mapreduce.input.fileinputformat.split.minsize
maxsize:默认值:Long.MAXValue
配置参数:mapreduce.input.fileinputformat.split.maxsize
blocksize
因此,默认情况下,切片大小=blocksize
maxsize(切片最大值):
参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值
minsize (切片最小值):
参数调的比blockSize大,则可以让切片变得比blocksize还大
但是,不论怎么调参数,都不能让多个小文件“划入”一个split
选择并发数的影响因素:
1、运算节点的硬件配置
2、运算任务的类型:CPU密集型还是IO密集型
3、运算任务的数据量
map并行度的经验之谈
如果硬件配置为2*12core + 64G,恰当的map并行度是大约每个节点20-100个map,最好每个map的执行时间至少一分钟(这个要测试才能确定处理多少数据才能运行一分钟)。
如果job的每个map或者 reduce task的运行时间都只有30-40秒钟,那么就减少该job的map或者reduce数,每一个task(map|reduce)的setup和加入到调度器中进行调度,
这个中间的过程可能都要花费几秒钟,所以如果每个task都非常快就跑完了,就会在task的开始和结束的时候浪费太多的时间。
配置task的JVM重用可以改善该问题:
(mapred.job.reuse.jvm.num.tasks,默认是1,表示一个JVM上最多可以顺序执行的task
数目(属于同一个Job)是1。也就是说一个task启一个JVM)
如果input的文件非常的大,比如1TB,可以考虑将hdfs上的每个block size设大,比如设成256MB或者512MB
尽量不要运行太多的reduce task。对大多数job来说,最好reduce的个数最多和集群中的reduce持平,或者比集群的 reduce slots小。这个对于小集群而言,尤其重要。
mapreduce和yarn交互过程
分片完之后用yarn的rpc客户端yarnClientImpl(注意他不是yarn),他去读取配置文件中的mapreduce.framework.name
如果设置的是local,会启动localJobRunner(本地mr模拟器)
如果设置的是yarn,就读取yarn.resourcemanager.hostname中的ip和端口,找到yarn的老大resource manager,请求运行一个程序
resource manager会返回一个jobid和一个用于提交资源的路径
客户端提交资源(job.split,jar,xml)到指定的路径
yarn通知客户端提交资源完毕
初始化任务信息,加入yarn的任务调度队列
node manager从resource manager的队列里面领取到任务
node manager根据任务信息创建一个容器container
客户端发送shell命令到Node manager的容器container中启动mrappmaster
mrappmaster根据提交的资源向resourcemanager请求资源节点,得到分配的其他node manager,这些node manager里面也有container容器
在每个container里面启动maptask和reducetask(他们的进程名字都是yarnChild)
全部处理完之后,container向resource manager注销自己
yarn只做资源的分配和调度,mr程序,storm程序,spark程序都可以运行在yarn集群上
yarn是在hadoop2.0出现的,在此之前资源的调度在mr的内部进行,使用jobTracker(只有一个)(相当于resource manager+mrappmaster)和Task tracker(node manager+task)
yarn的三种资源调度器:
先进先出(缺点是大任务会阻塞其他小任务)
容量调度器(两个队列,留一个给小任务,缺点是预留一块内存影响大任务的执行)
公平调度器(动态调整系统资源,取上面两种的优点)
//通过动态设置自定义计数器加1(全局计数器)(第一个参数是计数器的组,第二个参数是计数器的名字)
context.getCounter("counterGroupa", "countera").increment(1);
mapreduce数据压缩是mapreduce的一种优化策略:通过压缩编码对mapper或者reducer的输出进行压缩,以减少磁盘IO,提高MR程序运行速度(但相应增加了cpu运算负担)
1、Mapreduce支持将map输出的结果或者reduce输出的结果进行压缩,以减少网络IO或最终输出数据的体积
2、压缩特性运用得当能提高性能,但运用不当也可能降低性能
3、基本原则:
运算密集型的job,少用压缩
IO密集型的job,多用压缩
1、Flume分布式系统中最核心的角色是agent,flume采集系统就是由一个个agent所连接起来形成
2、每一个agent相当于一个数据传递员,内部有三个组件:
a)Source:采集源,用于跟数据源对接,以获取数据
b)Sink:下沉地,采集数据的传送目的,用于往下一级agent传递数据或者往最终存储系统传递数据
c)Channel:angent内部的数据传输通道,用于从source将数据传递到sink
sqoop工作机制:将导入或导出命令翻译成mapreduce程序来实现
Hbase基本组件说明:
Client:
包含访问Hbase的接口,并维护cache来加快对Hbase的访问,比如region的位置信息。
HMaster:
是hbase集群的主节点,可以配置多个,用来实现HA
为RegionServer分配region
负责RegionServer的负载均衡
发现失效的RegionServer并重新分配其上的region
RegionServer:
Regionserver维护region,处理对这些region的IO请求
Regionserver负责切分在运行过程中变得过大的region
Region:
分布式存储的最小单元。
Zookeeper作用:
通过选举,保证任何时候,集群中只有一个HMaster,HMaster与RegionServers 启动时会向ZooKeeper注册
存贮所有Region的寻址入口
实时监控Region server的上线和下线信息。并实时通知给HMaster
存储HBase的schema和table元数据
Zookeeper的引入使得HMaster不再是单点故障(意思是可以实现HA)
hbase权威指南
hbase中regionserver的范围按照rowkey划分指定
regionserver和datanode尽量部署在一起,防止io操作
day15_HBASE课程学习课件.docx里面有hbase的原理
写操作先写入memstore(每个store有一个memstore),当memstore中的数据量达到某个阈值,Hregionserver启动flashcache进程写入storefile,每次写入形成单独一个storefile
每个storefile当中有一个布隆过滤器(是一种空间效率很高的随机数据结构),假如通过对比数组的每一位检查通过,
那么被检查的元素很可能存在,否则不存在。假如不存在,再继续检索其他文件,只是效率低了不影响正确性。
一个storefile中包含多个rowkey记录。
hbase删除数据的时候并没有从hdfs中删除,只是打上了一个标签使其不能被查询到。可以通过compact操作真正删除。
compact分为大合并和小合并,删除数据是在小合并。小合并合并几个storefile,大合并合并整个region,会做大量磁盘io消耗性能。
memstore是内存,为了防止丢失,使用hlog。当向region中写入数据的时候,会异步的将日志写入hlog存储在hdfs上。hlog属于region的一部分
总结:client缓存的是region的位置信息等元数据信息,memstore缓存的是具体数据
client访问hbase上数据的过程并不需要master参与(寻址访问zookeeper和region server,数据读写访问regione server),master仅仅维护者table和region的元数据信息,负载很低。
region按大小分割的(默认10G),每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,Hregion就会等分会两个新的Hregion。
当table中的行不断增多,就会有越来越多的Hregion。
Hregion是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的Hregion可以分布在不同的HRegion server上。但一个Hregion是不会拆分到多个regionserver上的。
HRegion虽然是负载均衡的最小单元,但并不是物理存储的最小单元。
事实上,HRegion由一个或者多个Store组成,每个store保存一个column family。
每个Strore又由一个memStore和0至多个StoreFile组成。
hfile的底层是索引和指针
寻址流程
现在假设我们要从Table2里面查询一条RowKey是RK10000的数据。那么我们应该遵循以下步骤:
1. 从.META.表里面查询哪个Region包含这条数据。
2. 获取管理这个Region的RegionServer地址。
3. 连接这个RegionServer, 查到这条数据。
系统如何找到某个row key (或者某个 row key range)所在的region
bigtable 使用三层类似B+树的结构来保存region位置。
第一层: 保存zookeeper里面的文件,它持有root region的位置。
第二层:root region是.META.表的第一个region其中保存了.META.表其它region的位置。通过root region,我们就可以访问.META.表的数据。
第三层: .META.表它是一个特殊的表,保存了hbase中所有数据表的region 位置信息。
说明:
(1) root region永远不会被split,保证了最需要三次跳转,就能定位到任意region 。
(2).META.表每行保存一个region的位置信息,row key 采用表名+表的最后一行编码而成。
(3) 为了加快访问,.META.表的全部region都保存在内存中。
(4) client会将查询过的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行最多6次网络来回,才能定位到正确的region
(其中三次用来发现缓存失效,另外三次用来获取位置信息)。
StoreFile以HFile格式保存在HDFS上。
读请求过程:
1 客户端通过zookeeper以及root表和meta表找到目标数据所在的regionserver
2 联系regionserver查询目标数据
3 regionserver定位到目标数据所在的region,发出查询请求
4 region先在memstore中查找,命中则返回
5 如果在memstore中找不到,则在storefile中扫描(可能会扫描到很多的storefile----bloomfilter)
补充:布隆过滤器参数类型有2种:
Row、row+col
2、写请求过程:
1 client向region server提交写请求
2 region server找到目标region
3 region检查数据是否与schema一致
4 如果客户端没有指定版本,则获取当前系统时间作为数据版本
5 将更新写入WAL log
6 将更新写入Memstore
7 判断Memstore的是否需要flush为StoreFile文件。
master上线
master启动进行以下步骤:
1 从zookeeper上获取唯一一个代表active master的锁,用来阻止其它master成为master。
2 扫描zookeeper上的server父节点,获得当前可用的region server列表。
3 和每个region server通信,获得当前已分配的region和region server的对应关系。
4 扫描.META.region的集合,计算得到当前还未分配的region,将他们放入待分配region列表。
master下线
由于master只维护表和region的元数据,而不参与表数据IO的过程,master下线仅导致所有元数据的修改被冻结
(无法创建删除表,无法修改表的schema,无法进行region的负载均衡,无法处理region 上下线,无法进行region的合并,
唯一例外的是region的split可以正常进行,因为只有region server参与),
表的数据读写还可以正常进行。因此master下线短时间内对整个hbase集群没有影响。
storm集群启动过程分析
手动启动nimbus(主节点)和supervisor(从节点)
storm启动过程:
1.判断参数是否足够(storm nimbus和storm supervisor)(参数代表了执行命令)
2.根据不同的参数,调用不同的方法
3.方法中解析conf/storm.yaml的配置信息,nimbus和supervisor分别解析各自的jvm参数
4.拼装日志路径和参数,启动java程序。
supervisor启动worker过程:
nimbus分配任务之后,将任务信息写入到zk
supervisor通过zk的watch机制感知到指定目录的变动
supervisor判断是否有自己的任务,有则启动worker
supervisor启动worker需要告诉worker运行哪个Jar,这个jar包的名字,supervisor的id,worker的端口号,supervisor给worker分配的id
client提交任务过程(从代码提交类中就可以看出来):
storm提交jar包
stormtopologydriver驱动类中的main方法被执行。main方法会准备任务信息,生成stormtopology对象
向集群或者本地提交任务
nimbus任务分配过程:
将client提交的任务封装到一个队列中
反序列化topology应用程序配置文件,知道topology需要几个worker,有几个executor,有几个task,默认情况下executor=task
nimbus需要知道有多少个supervisor,每个supervisor有多少个worker,有哪些worker空闲
将spouttask和bolttask平均分配给worker
将分配好的任务信息写入zk,目录名称即为topology的id,由nimbus生成。
storm任务分配的几种策略:
新任务提交
monitor容错机制任务重新提交
rebalance负载均衡
worker启动task过程:(前提条件是一个worker可以启动多个task,且可以执行spout和bolt两种类型的task)
worker获得任务信息,知道自己启动哪几个task
循环启动task
启动时判断启动的是spout还是bolt,分别启动spoutthread和boltthread
storm的ack容错机制(storm消息容错指的是数据在处理中出现异常时,需要保证消息被完整处理):
期望:一个环节出现异常时,spout能够重新发送一份数据
问题:storm如何知道消息的处理状态:baseRichSpout里面有两个方法,分别是成功和失败后调用:
成功:collector.ack()
失败:collector.fail(msgid)
如果忘记手动ack或fail的话,storm会等待反馈直到超时,然后直接fail
如果忘记标识锚点或血缘关系,那么storm不再关心后面阶段的处理状况
baseBasicBolt不再需要手动ack和指定锚点和血缘关系。失败了之后直接抛异常failedException即可
ack机制的实现原理(异或算法,两个相同的结果为0,不同的结果为1):
发送数据时,同时发给spout/bolt和ack,一式两份,将两个状态进行对比异或,成功则为0
Tuple里面有ROOtID锚点(标识消息)id(每个bolt不一样)(两个id都是64位长整型)和spoutTask。最后将rootid变回用户的msgid(开始时将rootid和msgid用map做了映射)
spout如何知道一条消息的处理状态:
成功:ack(Object msgid)
失败:fail(Object msgid)
bolt如何将消息处理的状态告知spout:
collector.emit(new value())
collector.ack(Object msgid)消息处理成功时
collector.fail(Object msgid)处理失败时
spout重新发送数据并不是自动的,需要手动在fail方法里重新调用collector.emit方法
spout发送数据的时候,需要定制msgid,他是一个object。当消息处理成功或者失败的时候,storm框架会将msgid传回来进行重发等操作。
bolt的execute方法中进行了两个操作:
1.发送数据时,指定血缘关系(锚点)collector.emit(父tuple,new 子tuple)
2.当execute处理完业务逻辑的时候,需要告诉storm框架当前阶段的处理状态collector.ack(tuple)
spout和bolt之间互相发消息携带的信息为spout task和<rootid,锚点id>,rootid为系统生成的storm集群中的唯一标识64位随机长整型。由于用户在发送数据的时候指定了msgid,所以在发送数据的时候创建了map<rootid,msgid>。
spout和bolt之间传递信息的同时都会向acl bolt传送<rootid,锚点id>,在下游bolt处理完之后会返回<rootid,ackvalue>,根据rootid将锚点id与ackvalue异或并将结果发回给spout。
注意,发送给ackbolt的数据没有spouttask,只有<rootid,锚点id>
storm通信机制(worker与worker的跨进程通信用socket,worker内部的各个executor的通信):
每一个task对象都有输入队列--执行--输出队列
同一个worker内部的task之间用map<taskid,queue>来传递数据
跨worker将数据序列化之后用netty传输数据,由taskid得到worker的ip+端口。从map<ip+端口,nettyclient>拿到nettyclient然后发送数据。然后nettyserver接受数据。然后就是内部传输数据了。
nettyserver收到数据之后按照数据要发送的taskid先放进各个反序列化队列disruptorQueue(每秒钟处理600万消息,因为没有锁),有单独的线程去队列里拿数据给每一个task
(nettyclient在每个task中都有一个)
storm数据分发机制(也就是数据流是如何串联起来的):
shufflegrouping(随机/hash算法分发给一个bolt的不同task),然后携带目标taskid进行数据传递。源码里为MKGroup类在实现数据分发机制。
Kafka核心组件
Topic :消息根据Topic进行归类
Producer:发送消息者
Consumer:消息接受者
broker:每个kafka实例(server)
Zookeeper:依赖集群保存meta信息。
zookeeper在kafka中的作用是:
1存储集群相关信息
2生产的元数据信息
3消费的元数据信息
kafka的partition:
创建topic的时候可以指定partition的数量,决定了该topic的目录(里面有原始数据和索引文件)存在于多少台机器上
kafka partition分组策略:默认为hash partition,还可以轮询,随机等。
kafka中每个partition都可以设置副本数,解决某个broker挂掉的数据丢失问题
如果设置多个副本,生产数据的时候选出并发给leader,由leader接受和提供消费数据,并同步到其他机器的副本中
kafka的producer端负责数据的partition分发,设置分发的方式为props.put("partitioner.class","")
自定义partition:写一个类实现partitioner接口(该类必须加构造器否则报错)
producer。send方法中必须指定key。另外三个参数是partitionkey,topicid和消息内容。如果不指定,会用当前的key代替partitionkey
如果自定义partitioner一直往一个机器里面发送数据,会每十分钟切换一次。
partition的segment段:
到一定大小就分割文件。各个segment段的消息数量不一定相同
index文件存储的是消息id和日志文件中的日志记录存储在磁盘的位置,日志文件存储的是消息。所以两者结合可以找到某个索引的消息。
kafka的速度为什么这么快(为什么消息吞吐量大):
顺序写入磁盘,大大快于随机写入(600m每秒:100k每秒)
依赖操作系统底层提供的pagecache,当写操作时,操作系统只是把数据写入了pagecache。当读操作时,首先从pagecache读,没有才从磁盘中读。pagecache是把空闲内存当成了磁盘缓存。
(kafka重启的话pagecache的缓存不会失效)
sendfile技术:从前发送数据是磁盘--系统缓存--应用缓存--网络缓存--发送,sendfile是磁盘--系统缓存--网络缓存--发送,减少了一个数据拷贝的过程
consumergroup之间消费的数据互不干扰
每个consumer对应一个或多个分片,一个分片在同一个consumergroup中只能属于一个consumer
kafka配置文件解析:
生产者集群:partitioner,压缩方式,序列化机制,ack确认机制(0不等待确认;1leader收到消息后发送ack;-1所有follower都同步成功之后再发送ack),
异步模式缓存数据时间条数以及数据发不出去时的处理方式(停止接受信息或抛弃缓存信息)
kafka集群:线程数,segment时间阈值大小阈值,检查大小是否超过阈值的间隔
消费者集群:zk配置,配置判断是否挂点的时间,每次取多少条数据多大数据,多久更新一次offset,zk没有offset信息时是选择smallest还是largest(默认)
数据从kafka生产到storm消费阶段的不丢失和重复消费的分析:
生产者使用同步模式时,将确认机制设置为-1
生产者使用异步模式时,将数据缓存设置为无超时限制,不丢弃数据
数据消费时,在storm开启ack机制
如果不使用storm,确认数据处理完成之后,再更新offset值
数据重复消费如何处理:
将消费的唯一标识保存到外部介质,每次判断
不管也可以
akka(实现多个进程之间通信,编写并发的分布式的应用程序)(负载均衡,高并发。可扩展,高可用):
rpc就是不同进程之间的方法调用
除了akka之外,通信框架还有jetty,netty等
akka的底层是scala
akka的老大叫ActorSystem,用于创建和监控actor
可以有多个进程,一个进程里面有多个actor
不同ActorSystem下面的actor要进行通信,首先ActorSystem之间要建立连接
看到了akka通信小例子,见myrpc05
actor的prestart方法在构造函数之后执行,receive之前执行
context.actorSelection("akka.tcp://actorsystemname@ip:port/user/actorname")获取actor引用用于通信
akka底层封装了消息的序列化和反序列化
akka的模式匹配,免去了java的反射和动态代理的操作
rdd定义为弹性的分布式数据集,包含了只读的(不能修改数据),分区的(每个rdd算子有多个分区),分布式计算(每个分区上的rdd都可以分别计算)的概念
学习spark源码的作用:根据原理调整参数,进行性能调优
spark的worker向master汇报心跳和核数,内存等
master启动一个调度器周期检查worker的健康情况,移除不可用的worker
start-all.sh是整个spark集群启动的入口
spark-submit.sh是程序启动后的入口
start-all.sh中启动了spark-config.sh(加载配置),start-master.sh,start-slaves.sh
start-master.sh调用了start-daemon.sh org....master ip port,start-daemon.sh调用spark-class,spark-class调用java -cp org....master,也就是执行了master类的main方法
start-slaves.sh调用start-slave.sh做了和start-master.sh一样的事情启动了worker
启动Master的流程:
执行main方法,创建actorsystem,启动actor
在master的主构造器中,初始化了很多变量
执行actor中的prestart,创建调度线程每隔1分钟执行检查过期worker的操作。若超时,将该map<id,workerinfo>和map<address,workerinfo>的信息从内存删除,
移除该worker上执行的任务,将该worker的持久化信息删除,留下hashset<workerinfo>。
worker没有心跳后,master会等待16*60=16分钟(16个超时时间)然后将hashset<workerinfo>中的该worker信息删除。如果在这个时间内worker重新发送心跳,
master会发送消息重启worker调度器发送心跳。若worker超过了16分钟时间再发送心跳,master不予理会。
启动worker的流程:
执行main方法,创建actorsystem,启动actor
在worker的主构造器中,封装了一些变量和集合(id--workerinfo,address--workerinfo),供以后使用
执行actor中的prestart,向master注册自己的信息。然后master将worker的信息存入内存。注册成功之后worker更新master的信息,启动定时线程,向master发送心跳,每隔15秒
master判断是否已经注册过和worker是否是dead状态/standby状态,若都不是,则将该workerinfo持久化。
等收到master的返回消息,worker开启一个调度线程,定期发送心跳。
假如master判断每注册过节点或者为dead,就重新注册这个worker,并通知worker更新master信息
master的schedule()调度方法是来调度任务的,集群刚启动的时候没有任务,所以暂不执行。这个方法的作用是提交任务后,触发任务的分配
spark任务提交后执行前的逻辑:
1.spark-submit脚本提交任务,会通过反射的方式调用到我们提交的类的main方法。
2.在自己的代码中new sparkcontext()
3.创建actorsystem,并创建属于他的两个actor,clientactor和driveractor
4.创建三个任务,一个分发任务(taskscheduler),一个调度任务(sparkdeployschedulerbackend),一个划分任务(DAGScheduler)()
5.clientactor向master注册app(job jar)信息,并传递driveractorUrl
6.master将app保存起来
7.通过worker的心跳和注册,知道集群有多少资源
8.对比app需要的资源和集群现有的资源,进行资源分配
9.通知worker启动executor并传递driveractorUrl
10.driveractor针对worker进行任务的监听和接收
11.当executor接收任务后,将任务反序列化调用rdd算子执行,将结果保存到内存中
12.将计算结果持久化
其中划分任务是当action触发后,进行stage划分,并创建一个线程池,然后从一个阻塞队列里面取值(有任务就读取,没任务就等待,他不死);
划分之后提交给任务分发的类,将stage封装成taskset;然后执行调度任务,并通知driveractor向driver循环发送任务,将task序列化之后提交给worker的actor
集群提交任务后调用spark-submit脚本的流程(到调用我们提交的类的main方法之前)
1.在spark-submit脚本中启动了org.apache.spark.deploy.SparkSubmit类,调用了他的main方法
2.在main方法中匹配任务的提交形式,是submit还是kill等,如果是submit,则调用submit方法
3.在submit方法中调用dorunmain方法,然后调用runmain方法
4.在runmain方法中通过Class.forname反射方式实例化了自己提交的类
5.调用自己实现的类的main方法
任务分发调度和任务调度详解(在自己提交的程序中的逻辑 ):
1.通过SparkConf设置程序需要的参数(setmaster,setappname),最终将参数保存在ConcurrentHashMap[string,string]()中
2.new sparkContext(),这个类是程序的入口,非常重要
3.在sparkContext类的主构造器中,创建了sparkDriver的actorSystem,并且启动
4.通过sparkContext.createTaskScheduler创建了TaskSchedulerImpl(任务分发),然后创建调度并将其设置为任务分发的成员变量
5.创建DAGScheduler,划分整个DAG(job)
6.先后调用任务分发任务和任务调度任务的start方法
7.创建DriverActor,然后启动DriverActor,执行其主构造方法和prestart方法,从任务列表中拿出任务并分发任务
8.封装整个job的描述
9.创建AppClient,封装参数,调用其start方法创建clientActor??????clientActor现在才创建??不是actorsystem创建的????
10.执行clientActor主构造器,封装参数,然后执行其prestart方法??????????????
master收到clientactor任务后,进行worker资源分配的流程:
12.在prestart方法中调用registerWithMaster,然后调用tryRegisterWithMasters
13.在tryRegisterWithMasters中拿到master的连接地址,然后拿到master的actor,向master发送消息
14.master收到消息后创建app的描述,放入内存
15.注册app(将app信息记录在内存),并且持久化app
16.给clientActor发送消息,使得client记录appid,设置注册状态为true,改变masterurl,对任务设置监听
17.master执行调度方法schedule,通过计算app需要的资源和集群worker剩余的资源,进行计算分配(主打散分配,副集中分配),并记录资源分配情况
任务提交之后的准备阶段总结:
1.任务提交给master之后,master进行了worker资源的分配
2.任务提交之后,client启动了任务的监听功能(阻塞队列,触发action的时候任务会提交到阻塞队列)
master执行调度方法schedule通知worker启动executor的流程:
1通过计算app需要的资源和集群worker剩余的资源,进行资源分配,并将分配的资源记录在assigned中
2开始记录executor的执行资源和状态
3.向worker发送启动executor的任务信息(worker创建工作目录,创建executorRunner,创建接收和执行任务的executor,修改worker的可用资源量,向master发送修改executor的状态信息)
4.向clientActor???????????发送一个executor的添加信息,设置监听和分配任务
代码的action执行后,触发的任务提交流程(提交之后的流程):
1.DAGScheduler进行stage的划分(shuffle宽依赖之前切一刀)
2.将划分好的stage提交给taskSchedulerImpl这个任务分发的类
3.taskSchedulerImpl将stage封装成taskset
4.调用调度任务进行任务的分发调度
5.调度任务通知driverActor循环发送任务
6.driverActor将任务序列化之后提交给executor
7.executor将任务反序列化执行,调用rdd算子,将结果保存到内存中
8.将结果持久化
任务开始触发的流程(注意,transformation算子可以是宽依赖也可以是窄依赖):
1.读取hadoop文件sc.textFile()生成了两个rdd,一个hadooprdd,一个mappartitionrdd(以后的每一步transformation都是mappartitionrdd)
2.执行reduceByKey时,生成shufflerdd(hadooprdd和mappartitionrdd是窄依赖,shufflerdd是宽依赖)
(提交任务时,最后保存到硬盘的时候sc.saveAsTextFile的时候又创建了一个mappartitionrdd,他是action)
3.任务提交,提交之后放入任务队列
4.监听该队列的线程进行匹配,识别不同类型的任务
5.任务划分(提交之后的流程,见上面)
如何划分stage:
先从后往前遇到宽依赖就砍一刀,然后以递归的方式创建stage
(获取所有父类的stage,不断往前获取,再返回来),依次赋值stage和parentStage,最后的形式为new stage(new stage(new stage()))
老师总结:
将stage封装到taskset,提交给executor流程:
1.将finalstage拆分成多个stage(用递归的方式:拿到并判断有没有父stage,若有继续判断父stage有没有父stage。
假如没有,把第一个stage和jobid提交到任务分发任务,如此依次从前到后顺序提交stage到runningstage队列。)
2.逐个将分好的stage依据分区数分为多个task放进taskset,以备下发到每一个分区进行计算(程序发送到数据所在机器,避免了数据的io传递)
3.将taskset放进先进先出队列
4.开始发送任务到driverActor
5.driverActor取出taskset并发送给worker进行计算(需要序列化和反序列化)(是worker向driverActor发送的请求要求分发任务,在的master的命令下)
executor(进程名和全名叫CoarseGrainedExecutorBackend)执行任务的流程:
1.反序列化任务
2.创建taskRunner线程并将它加入队列,标记已经执行了task,不要再重复下发任务
3.将taskRunner线程放进线程池(该线程池是executor在主构造器中创建的)
4.调用线程的run方法
5.反序列化任务(因为包了两层序列化,第一层是taskset的序列化,第二层是包了一层buffer方便io)拿到所有task
6.执行task的run方法,解析出task中的rdd
7.最终执行rdd中的方法,拿到运行结果返回给executor
8.将结果记录,写入各种存储或内存
将stage提交给集群进行运算(老师写的,从划分完stage开始写)的流程:
1finalstage创建成功之后,提交他
2从后往前找stage,找到第一个stage然后顺序提交stage到任务分发任务TaskSchedulerImpl
3.将stage封装成两种,shufflemaptask和resulttask
4.将task封装成taskset,让任务分发任务去分发任务
5.将taskset发送到driveractor
6.循环taskset中的task,将每个task进行两次序列化,然后提交到worker集群
7.executor反序列化消息
8.调用executor的launchtask方法提交任务
9.在launchtask中,通过线程池启动线程,在线程中运行rdd算子,并保存结果
10.继续提交下一个stage到任务分发任务TaskSchedulerImpl,直到任务结束
scala Actor的作用是并发编程,开发多线程应用程序。他底层封装了并发(比java的手动并发编程方便)。
scala在2.11.x版本中将akka加入其中作为其默认actor并废弃了老版本actor
actor是一种编程规范,其他语言也有
mapreduce读取多个文件:
FileInputFormat.setInputPaths(job, new Path[] { new Path("hdfs://192.168.9.13:8020/gradeMarking"),
new Path("hdfs://192.168.9.13:8020/implyCount") });
HDFS的HA机制详解
通过双namenode消除单点故障
双namenode协调工作的要点:
A、元数据管理方式需要改变:
内存中各自保存一份元数据
Edits日志只能有一份,只有Active状态的namenode节点可以做写操作
两个namenode都可以读取edits
共享的edits放在一个共享存储中管理(qjournal和NFS两个主流实现)
B、需要一个状态管理功能模块
实现了一个zkfailover,常驻在每一个namenode所在的节点
每一个zkfailover负责监控自己所在namenode节点,利用zk进行状态标识
当需要进行状态切换时,由zkfailover来负责切换
切换时需要防止brain split现象的发生
你们数据库怎么导入 hive 的,有没有出现问题
在导入 hive 的时候,如果数据库中有 blob 或者 text 字段,会报错,解决方案在 sqoop 笔
记中????
上千万或上亿数据(有重复),统计其中出现次数最多的钱 N 个数据:
先用红黑树统计出现次数,再将键值对反过来,插入二叉堆,取得前n个数据。复杂度为1亿*logN
怎么在海量数据中找出重复次数最多的⼀个?
⽅案 1:先做 hash,然后求模映射为小⽂件,求出每个小⽂件中重复次数最多的⼀个,并记录重复
次数。然后找出上⼀步求出的数据中重复次数最多的⼀个就是所求(二叉堆取topn)
海量日志数据,提取出某日访问百度次数最多的那个 IP:
首先是这一天,并且是访问百度的日志中的 IP 取出来,逐个写入到一个大文件中。
注意到 IP 是 32 位的,最多有 个 IP。同样可以采用映射的方法,比如模 1000,把整个
大文件映射为 1000 个小文件,再找出每个小文中出现频率最大的 IP(可以采用 hash_map
进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这 1000 个最大的 IP
中,找出那个频率最大的 IP,即为所求。
海量数据分布在 100 台电脑中,想个办法高校统计出这批数据的 TOP10:
首先用在每个电脑上构造二叉堆取出top10,再合并成1000个数据用同样方法取出top10。
倒排索引:
基本原理及要点:为何叫倒排索引?⼀种索引⽅法,被用来存储在全⽂搜索下某个单词在⼀个⽂档或
者⼀组⽂档中的存储位置的映射。
以英⽂为例,下面是要被索引的⽂本: T0 = "it is what it is" T1 = "what is it" T2 = "it is a banana"
我们就能得到下面的反向⽂件索引:
"a": {2} "banana": {2} "is": {0, 1, 2} "it": {0, 1, 2} "what": {0, 1}
检索的条件"what","is"和"it"将对应集合的交集。
问题实例:⽂档检索系统,查询那些⽂件包含了某单词,比如常见的学术论⽂的关键字搜索。
logstash可以从文件,kafka等数据源读取数据
azkaban与oozie使用上的区别总结:
azkaban要创建job文件,附上依赖文件,还要写明每个job之间的依赖关系,打成zip包上传到创建的project计划中,azkaban可以设置执行时间,更改文件内容等。
oozie主要分为workflow工作流和schedule调度,前者可以在hue界面创建各种任务,书写配置项并设置依赖关系,后者调度前面的工作流并设置执行时间,开始结束时间等。
sqoop从mysql导出到hdfs命令:
bin/sqoop export \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--table emp2 \
--export-dir /user/hadoop/emp/
导入到hdfs:
$bin/sqoop import \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--table emp --m 1
导入到hive:
bin/sqoop import --connect jdbc:mysql://hdp-node-01:3306/test --username root --password root --table emp --hive-import --m 1
导入到hdfs指定目录:
bin/sqoop import \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--target-dir /queryresult \
--table emp --m 1
导入数据的子集:
bin/sqoop import \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--where "city ='sec-bad'" \
--target-dir /wherequery \
--table emp_add --m 1
按需导入:
bin/sqoop import \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--target-dir /wherequery2 \
--query 'select id,name,deg from emp WHERE id>1207 and $CONDITIONS' \
--split-by id \
--fields-terminated-by '\t' \
--m 1
增量导入是仅导入新添加的表中的行的技术。
它需要添加‘incremental’, ‘check-column’, 和 ‘last-value’选项来执行增量导入。
下面的语法用于Sqoop导入命令增量选项。
bin/sqoop import \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--table emp --m 1 \
--incremental append \
--check-column id \
--last-value 1205
导出到mysql:
bin/sqoop export \
--connect jdbc:mysql://hdp-node-01:3306/test \
--username root \
--password root \
--table emp2 \
--export-dir /user/hadoop/emp/
创建sqoop作业(就比直接执行任务多了--create xxxjob):
bin/sqoop job --create myimportjob -- import --connect jdbc:mysql://hdp-node-01:3306/test --username root --password root --table emp --m 1
hive创建分区表:
create table student_p(Sno int,Sname string,Sex string,Sage int,Sdept string) partitioned by(part string) row format delimited fields terminated by ','stored as textfile;
增删分区:
alter table student_p add/drop partition(part='a') partition(part='b');
重命名表:
ALTER TABLE table_name RENAME TO new_table_name
增加和更新列:
ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name data_type [COMMENT col_comment], ...)
显示命令:
show tables;
show databases;
show partitions table_name;
show functions;
desc extended table_name;
desc formatted table_name;
load数据:
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO
TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
OVERWRITE 关键字:
如果使用了 OVERWRITE 关键字,则目标表(或者分区)中的内容会被删除,然后再将 filepath 指向的文件/目录中的内容添加到表/分区中。
如果目标表(分区)已经有一个文件,并且文件名和 filepath 中的文件名冲突,那么现有的文件会被新文件所替代。
插入数据:
INSERT OVERWRITE [INTO] TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1 FROM from_statement
查找数据:
SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table_reference
JOIN table_other ON expr
[WHERE where_condition]
[GROUP BY col_list [HAVING condition]]
[CLUSTER BY col_list
| [DISTRIBUTE BY col_list] [SORT BY| ORDER BY col_list]
]