一、HIVE
over中的partition by与group by
name orderdate cost
jack 2017-01-01 10
jack 2017-02-03 23
jack 2017-01-05 46
jack 2017-04-06 42
jack 2017-01-08 55
mart 2017-04-08 62
mart 2017-04-09 68
mart 2017-04-11 75
mart 2017-04-13 94
=======================gruop by=======================
select
name,
count(*)
from
overdemo
where
date_format(orderdate,'yyyy-MM')='2017-04'
group by
name;
--------结果---------
name _c1
jack 1
mart 4
=======================partition by========================
select
name,
count(*) over(partition by name)
from
overdemo
where
date_format(orderdate,'yyyy-MM')='2017-04';
--------结果---------
name count_window_0
jack 1
mart 4
mart 4
mart 4
mart 4
- 差别:partition by 将所有符合过滤条件的列分组选中,故group by 可看成partition by 的去重版
- 执行顺序:group by -> over(),故group by分组去重,再partition by结合聚合函数(count/sum)分区计算
=====同时有group by name和over(partition by name)=====
select
name,
count(*) over(partition by name)
from
overdemo
where
date_format(orderdate,'yyyy-MM')='2017-04'
group by
name;
---------结果--------
name count_window_0
jack 1
mart 1
=====同时有group by name和over()=====over()无参数时结合聚合函数对全量数据进行运算
select
name,
count(*) over()
from
overdemo
where
date_format(orderdate,'yyyy-MM')='2017-04'
group by
name;
----------结果-------
name count_window_0
mart 2
jack 2
- 当over()无参时,结合聚合函数 count(),对得到的数据进行计数,得到的结果=2,并且会在mart和Jack后显示2(因为没有进行partition by name,mart和Jack是在同一个分区的)
- ②当over(partition by name)之后,group by得到的数据会再次进行分区,分为mart和jack,两个分区中进行count()计算,都只有1个,所以在两个分区中分别显示为1
- 此时,再解释上面只使用over(partition by name)时,为什么mart会显示4次,而且count()也是4。因为符合过滤条件的mart有4条,都被partition by分到了一个分区,所以count()的结果就是4,而且over(partition by name)并不会去重,所以显示了4次。
- 综上,over(partition by)可以视为group by的升级版:①会保留所有进入分区的数据,不去重(没有数据损失);②在group by之后执行,可以进行更为复杂个性化的操作
hive 函数分类
关系|数学|逻辑|数值|日期|条件|字符串|集合统计|…
关系运算
1.1 1、等值比较: =
1.2 2、不等值比较:
1.3 3、小于比较: <
1.4 4、小于等于比较: <=
1.5 5、大于比较: >
1.6 6、大于等于比较: >=
1.7 7、空值判断: IS NULL
1.8 8、非空判断: IS NOT NULL
1.9 9、LIKE比较: LIKE
1.10 10、JAVA的LIKE操作: RLIKE
1.11 11、REGEXP操作: REGEXP
2.1 1、加法操作: +
2.2 2、减法操作: -
2.3 3、乘法操作: *
2.4 4、除法操作: /
2.5 5、取余操作: %
2.6 6、位与操作: &
2.7 7、位或操作: |
2.8 8、位异或操作: ^
2.9 9.位取反操作: ~
逻辑运算:
3.1 1、逻辑与操作: AND
3.2 2、逻辑或操作: OR
3.3 3、逻辑非操作: NOT
数值计算
1、取整函数: round
语法: round(double a)
返回值: BIGINT
说明: 返回double类型的整数值部分 (遵循四舍五入)
hive> ``select` `round(3.1415926) ``from` `iteblog;``3``hive> ``select` `round(3.5) ``from` `iteblog;``4``hive> ``create` `table` `iteblog ``as` `select` `round(9542.158) ``from` `iteblog;``hive> describe iteblog;``_c0 ``bigint
2、指定精度取整函数: round
语法: round(double a, int d)
返回值: DOUBLE
说明: 返回指定精度d的double类型
hive> ``select` `round(3.1415926,4) ``from` `iteblog;``3.1416
3、向下取整函数: floor
语法: floor(double a)
返回值: BIGINT
说明: 返回等于或者小于该double变量的最大的整数
hive> ``select` `floor(3.1415926) ``from` `iteblog;``3``hive> ``select` `floor(25) ``from` `iteblog;``25
4、向上取整函数: ceil
语法: ceil(double a)
返回值: BIGINT
说明: 返回等于或者大于该double变量的最小的整数
hive> ``select` `ceil(3.1415926) ``from` `iteblog;``4``hive> ``select` `ceil(46) ``from` `iteblog;``46
5、向上取整函数: ceiling
语法: ceiling(double a)
返回值: BIGINT
说明: 与ceil功能相同
hive> ``select` `ceiling(3.1415926) ``from` `iteblog;``4``hive> ``select` `ceiling(46) ``from` `iteblog;``46
6、取随机数函数: rand
语法: rand(),rand(int seed)
返回值: double
说明: 返回一个0到1范围内的随机数。如果指定种子seed,则会等到一个稳定的随机数序列
hive> ``select` `rand() ``from` `iteblog;``0.5577432776034763``hive> ``select` `rand() ``from` `iteblog;``0.6638336467363424``hive> ``select` `rand(100) ``from` `iteblog;``0.7220096548596434``hive> ``select` `rand(100) ``from` `iteblog;``0.7220096548596434
7、自然指数函数: exp
语法: exp(double a)
返回值: double
说明: 返回自然对数e的a次方
hive> ``select` `exp(2) ``from` `iteblog;``7.38905609893065``<strong>自然对数函数</strong>: ln``<strong>语法</strong>: ln(``double` `a)``<strong>返回值</strong>: ``double``<strong>说明</strong>: 返回a的自然对数``1``hive> ``select` `ln(7.38905609893065) ``from` `iteblog;``2.0
8、以10为底对数函数: log10
语法: log10(double a)
返回值: double
说明: 返回以10为底的a的对数
hive> ``select` `log10(100) ``from` `iteblog;``2.0
9、以2为底对数函数: log2
语法: log2(double a)
返回值: double
说明: 返回以2为底的a的对数
hive> ``select` `log2(8) ``from` `iteblog;``3.0
10、对数函数: log
语法: log(double base, double a)
返回值: double
说明: 返回以base为底的a的对数
hive> ``select` `log(4,256) ``from` `iteblog;``4.0
11、幂运算函数: pow
语法: pow(double a, double p)
返回值: double
说明: 返回a的p次幂
hive> ``select` `pow(2,4) ``from` `iteblog;``16.0
12、幂运算函数: power
语法: power(double a, double p)
返回值: double
说明: 返回a的p次幂,与pow功能相同
hive> ``select` `power(2,4) ``from` `iteblog;``16.0
13、开平方函数: sqrt
语法: sqrt(double a)
返回值: double
说明: 返回a的平方根
hive> ``select` `sqrt(16) ``from` `iteblog;``4.0
14、二进制函数: bin
语法: bin(BIGINT a)
返回值: string
说明: 返回a的二进制代码表示
hive> ``select` `bin(7) ``from` `iteblog;``111
15、十六进制函数: hex
语法: hex(BIGINT a)
返回值: string
说明: 如果变量是int类型,那么返回a的十六进制表示;如果变量是string类型,则返回该字符串的十六进制表示
hive> ``select` `hex(17) ``from` `iteblog;``11``hive> ``select` `hex(‘abc’) ``from` `iteblog;``616263
16、反转十六进制函数: unhex
语法: unhex(string a)
返回值: string
说明: 返回该十六进制字符串所代码的字符串
hive> ``select` `unhex(‘616263’) ``from` `iteblog;``abc``hive> ``select` `unhex(‘11’) ``from` `iteblog;``-``hive> ``select` `unhex(616263) ``from` `iteblog;``abc
17、进制转换函数: conv
语法: conv(BIGINT num, int from_base, int to_base)
返回值: string
说明: 将数值num从from_base进制转化到to_base进制
hive> ``select` `conv(17,10,16) ``from` `iteblog;``11``hive> ``select` `conv(17,10,2) ``from` `iteblog;``10001
18、绝对值函数: abs
语法: abs(double a) abs(int a)
返回值: double int
说明: 返回数值a的绝对值
hive> ``select` `abs``(-3.9) ``from` `iteblog;``3.9``hive> ``select` `abs``(10.9) ``from` `iteblog;``10.9
19、正取余函数: pmod
语法: pmod(int a, int b),pmod(double a, double b)
返回值: int double
说明: 返回正的a除以b的余数
hive> ``select` `pmod(9,4) ``from` `iteblog;``1``hive> ``select` `pmod(-9,4) ``from` `iteblog;``3
20、正弦函数: sin
语法: sin(double a)
返回值: double
说明: 返回a的正弦值
hive> ``select` `sin(0.8) ``from` `iteblog;``0.7173560908995228
21、反正弦函数: asin
语法: asin(double a)
返回值: double
说明: 返回a的反正弦值
hive> ``select` `asin(0.7173560908995228) ``from` `iteblog;``0.8
22、余弦函数: cos
语法: cos(double a)
返回值: double
说明: 返回a的余弦值
hive> ``select` `cos(0.9) ``from` `iteblog;``0.6216099682706644
23、反余弦函数: acos
语法: acos(double a)
返回值: double
说明: 返回a的反余弦值
hive> ``select` `acos(0.6216099682706644) ``from` `iteblog;``0.9
24、positive函数: positive
语法: positive(int a), positive(double a)
返回值: int double
说明: 返回a
hive> ``select` `positive(-10) ``from` `iteblog;``-10``hive> ``select` `positive(12) ``from` `iteblog;``12
25、negative函数: negative
语法: negative(int a), negative(double a)
返回值: int double
说明: 返回-a
hive> ``select` `negative(-5) ``from` `iteblog;``5``hive> ``select` `negative(8) ``from` `iteblog;``-8
日期函数
1、UNIX时间戳转日期函数: from_unixtime
语法: from_unixtime(bigint unixtime[, string format])
返回值: string
说明: 转化UNIX时间戳(从1970-01-01 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式
hive> ``select` `from_unixtime(1323308943,``'yyyyMMdd'``) ``from` `iteblog;``20111208
2、获取当前UNIX时间戳函数: unix_timestamp
语法: unix_timestamp()
返回值: bigint
说明: 获得当前时区的UNIX时间戳
hive> ``select` `unix_timestamp() ``from` `iteblog;``1323309615
3、日期转UNIX时间戳函数: unix_timestamp
语法: unix_timestamp(string date)
返回值: bigint
说明: 转换格式为"yyyy-MM-dd HH:mm:ss"的日期到UNIX时间戳。如果转化失败,则返回0。
hive> ``select` `unix_timestamp(``'2011-12-07 13:01:03'``) ``from` `iteblog;``1323234063
4、指定格式日期转UNIX时间戳函数: unix_timestamp
语法: unix_timestamp(string date, string pattern)
返回值: bigint
说明: 转换pattern格式的日期到UNIX时间戳。如果转化失败,则返回0。
hive> ``select` `unix_timestamp(``'20111207 13:01:03'``,``'yyyyMMdd HH:mm:ss'``) ``from` `iteblog;``1323234063
5、日期时间转日期函数: to_date
语法: to_date(string timestamp)
返回值: string
说明: 返回日期时间字段中的日期部分。
hive> ``select` `to_date(``'2011-12-08 10:03:01'``) ``from` `iteblog;``2011-12-08
6、日期转年函数: year
语法: year(string date)
返回值: int
说明: 返回日期中的年。
hive> ``select` `year``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``2011``hive> ``select` `year``(``'2012-12-08'``) ``from` `iteblog;``2012
7、日期转月函数: month
语法: month (string date)
返回值: int
说明: 返回日期中的月份。
hive> ``select` `month``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``12``hive> ``select` `month``(``'2011-08-08'``) ``from` `iteblog;``8
8、日期转天函数: day
语法: day (string date)
返回值: int
说明: 返回日期中的天。
hive> ``select` `day``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``8``hive> ``select` `day``(``'2011-12-24'``) ``from` `iteblog;``24
9、日期转小时函数: hour
语法: hour (string date)
返回值: int
说明: 返回日期中的小时。
hive> ``select` `hour``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``10
10、日期转分钟函数: minute
语法: minute (string date)
返回值: int
说明: 返回日期中的分钟。
hive> ``select` `minute``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``3
11、日期转秒函数: second
语法: second (string date)
返回值: int
说明: 返回日期中的秒。
hive> ``select` `second``(``'2011-12-08 10:03:01'``) ``from` `iteblog;``1
12、日期转周函数: weekofyear
语法: weekofyear (string date)
返回值: int
说明: 返回日期在当前的周数。
hive> ``select` `weekofyear(``'2011-12-08 10:03:01'``) ``from` `iteblog;``49
13、日期比较函数: datediff
语法: datediff(string enddate, string startdate)
返回值: int
说明: 返回结束日期减去开始日期的天数。
hive> ``select` `datediff(``'2012-12-08'``,``'2012-05-09'``) ``from` `iteblog;``213
14、日期增加函数: date_add
语法: date_add(string startdate, int days)
返回值: string
说明: 返回开始日期startdate增加days天后的日期。
hive> ``select` `date_add(``'2012-12-08'``,10) ``from` `iteblog;``2012-12-18
15、日期减少函数: date_sub
语法: date_sub (string startdate, int days)
返回值: string
说明: 返回开始日期startdate减少days天后的日期。
hive> ``select` `date_sub(``'2012-12-08'``,10) ``from` `iteblog;``2012-11-28
条件函数
1、If函数: if
语法: if(boolean testCondition, T valueTrue, T valueFalseOrNull)
返回值: T
说明: 当条件testCondition为TRUE时,返回valueTrue;否则返回valueFalseOrNull
hive> ``select` `if(1=2,100,200) ``from` `iteblog;``200``hive> ``select` `if(1=1,100,200) ``from` `iteblog;``100
2、非空查找函数: COALESCE
语法: COALESCE(T v1, T v2, …)
返回值: T
说明: 返回参数中的第一个非空值;如果所有值都为NULL,那么返回NULL
hive> ``select` `COALESCE``(``null``,``'100'``,'50′) ``from` `iteblog;``100
3、条件判断函数:CASE
语法: CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END
返回值: T
说明:如果a等于b,那么返回c;如果a等于d,那么返回e;否则返回f
hive> ``Select` `case` `100 ``when` `50 ``then` `'tom'` `when` `100 ``then` `'mary'` `else` `'tim'` `end` `from` `iteblog;``mary``hive> ``Select` `case` `200 ``when` `50 ``then` `'tom'` `when` `100 ``then` `'mary'` `else` `'tim'` `end` `from` `iteblog;``tim
4、条件判断函数:CASE
语法: CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END
返回值: T
说明:如果a为TRUE,则返回b;如果c为TRUE,则返回d;否则返回e
hive> ``select` `case` `when` `1=2 ``then` `'tom'` `when` `2=2 ``then` `'mary'` `else` `'tim'` `end` `from` `iteblog;``mary``hive> ``select` `case` `when` `1=1 ``then` `'tom'` `when` `2=2 ``then` `'mary'` `else` `'tim'` `end` `from` `iteblog;``tom
字符串函数
1、字符串长度函数:length
语法: length(string A)
返回值: int
说明:返回字符串A的长度
hive> ``select` `length(``'abcedfg'``) ``from` `iteblog;``7
2、字符串反转函数:reverse
语法: reverse(string A)
返回值: string
说明:返回字符串A的反转结果
hive> ``select` `reverse(abcedfg’) ``from` `iteblog;``gfdecba
3、字符串连接函数:concat
语法: concat(string A, string B…)
返回值: string
说明:返回输入字符串连接后的结果,支持任意个输入字符串
hive> ``select` `concat(‘abc’,``'def’,'``gh’) ``from` `iteblog;``abcdefgh
4、带分隔符字符串连接函数:concat_ws
语法: concat_ws(string SEP, string A, string B…)
返回值: string
说明:返回输入字符串连接后的结果,SEP表示各个字符串间的分隔符
hive> ``select` `concat_ws(``','``,``'abc'``,``'def'``,``'gh'``) ``from` `iteblog;``abc,def,gh
5、字符串截取函数:substr,substring
语法: substr(string A, int start),substring(string A, int start)
返回值: string
说明:返回字符串A从start位置到结尾的字符串
hive> ``select` `substr(``'abcde'``,3) ``from` `iteblog;``cde``hive> ``select` `substring``(``'abcde'``,3) ``from` `iteblog;``cde``hive> ``select` `substr(``'abcde'``,-1) ``from` `iteblog; (和ORACLE相同)``e
6、字符串截取函数:substr,substring
语法: substr(string A, int start, int len),substring(string A, int start, int len)
返回值: string
说明:返回字符串A从start位置开始,长度为len的字符串
hive> ``select` `substr(``'abcde'``,3,2) ``from` `iteblog;``cd``hive> ``select` `substring``(``'abcde'``,3,2) ``from` `iteblog;``cd``hive>``select` `substring``(``'abcde'``,-2,2) ``from` `iteblog;``de
7、字符串转大写函数:upper,ucase
语法: upper(string A) ucase(string A)
返回值: string
说明:返回字符串A的大写格式
hive> ``select` `upper``(``'abSEd'``) ``from` `iteblog;``ABSED``hive> ``select` `ucase(``'abSEd'``) ``from` `iteblog;``ABSED
8、字符串转小写函数:lower,lcase
语法: lower(string A) lcase(string A)
返回值: string
说明:返回字符串A的小写格式
hive> ``select` `lower``(``'abSEd'``) ``from` `iteblog;``absed``hive> ``select` `lcase(``'abSEd'``) ``from` `iteblog;``absed
9、去空格函数:trim
语法: trim(string A)
返回值: string
说明:去除字符串两边的空格
hive> ``select` `trim(``' abc '``) ``from` `iteblog;``abc
10、左边去空格函数:ltrim
语法: ltrim(string A)
返回值: string
说明:去除字符串左边的空格
hive> ``select` `ltrim(``' abc '``) ``from` `iteblog;``abc
11、右边去空格函数:rtrim
语法: rtrim(string A)
返回值: string
说明:去除字符串右边的空格
hive> ``select` `rtrim(``' abc '``) ``from` `iteblog;``abc
12、正则表达式替换函数:regexp_replace
语法: regexp_replace(string A, string B, string C)
返回值: string
说明:将字符串A中的符合java正则表达式B的部分替换为C。注意,在有些情况下要使用转义字符,类似oracle中的regexp_replace函数。
hive> ``select` `regexp_replace(``'foobar'``, ``'oo|ar'``, ``''``) ``from` `iteblog;``fb
13、正则表达式解析函数:regexp_extract
语法: regexp_extract(string subject, string pattern, int index)
返回值: string
说明:将字符串subject按照pattern正则表达式的规则拆分,返回index指定的字符。
hive> ``select` `regexp_extract(``'foothebar'``, ``'foo(.*?)(bar)'``, 1) ``from` `iteblog;``the``hive> ``select` `regexp_extract(``'foothebar'``, ``'foo(.*?)(bar)'``, 2) ``from` `iteblog;``bar``hive> ``select` `regexp_extract(``'foothebar'``, ``'foo(.*?)(bar)'``, 0) ``from` `iteblog;``foothebar``strong>注意,在有些情况下要使用转义字符,下面的等号要用双竖线转义,这是java正则表达式的规则。``select` `data_field,`` ``regexp_extract(data_field,``'.*?bgStart\\=([^&]+)'``,1) ``as` `aaa,`` ``regexp_extract(data_field,``'.*?contentLoaded_headStart\\=([^&]+)'``,1) ``as` `bbb,`` ``regexp_extract(data_field,``'.*?AppLoad2Req\\=([^&]+)'``,1) ``as` `ccc `` ``from` `pt_nginx_loginlog_st `` ``where` `pt = ``'2012-03-26'` `limit 2;
14、URL解析函数:parse_url
语法: parse_url(string urlString, string partToExtract [, string keyToExtract])
返回值: string
说明:返回URL中指定的部分。partToExtract的有效值为:HOST, PATH, QUERY, REF, PROTOCOL, AUTHORITY, FILE, and USERINFO.
hive> ``select` `parse_url(``'https://www.iteblog.com/path1/p.php?k1=v1&k2=v2#Ref1'``, ``'HOST'``) ``from` `iteblog;``facebook.com``hive> ``select` `parse_url(``'https://www.iteblog.com/path1/p.php?k1=v1&k2=v2#Ref1'``, ``'QUERY'``, ``'k1'``) ``from` `iteblog;``v1
15、json解析函数:get_json_object
语法: get_json_object(string json_string, string path)
返回值: string
说明:解析json的字符串json_string,返回path指定的内容。如果输入的json字符串无效,那么返回NULL。
hive> ``select` `get_json_object(``'{"store":``> {"fruit":\[{"weight":8,"type":"apple"},{"weight":9,"type":"pear"}],``> "bicycle":{"price":19.95,"color":"red"}``> },``> "email":"amy@only_for_json_udf_test.net",``> "owner":"amy"``> }``> '``,``'$.owner'``) ``from` `iteblog;``amy
16、空格字符串函数:space
语法: space(int n)
返回值: string
说明:返回长度为n的字符串
hive> ``select` `space``(10) ``from` `iteblog;``hive> ``select` `length(``space``(10)) ``from` `iteblog;``10
17、重复字符串函数:repeat
语法: repeat(string str, int n)
返回值: string
说明:返回重复n次后的str字符串
hive> ``select` `repeat(``'abc'``,5) ``from` `iteblog;``abcabcabcabcabc
18、首字符ascii函数:ascii
语法: ascii(string str)
返回值: int
说明:返回字符串str第一个字符的ascii码
hive> ``select` `ascii(``'abcde'``) ``from` `iteblog;``97
19、左补足函数:lpad
语法: lpad(string str, int len, string pad)
返回值: string
说明:将str进行用pad进行左补足到len位
hive> ``select` `lpad(``'abc'``,10,``'td'``) ``from` `iteblog;``tdtdtdtabc``注意:与GP,ORACLE不同,pad 不能默认
20、右补足函数:rpad
语法: rpad(string str, int len, string pad)
返回值: string
说明:将str进行用pad进行右补足到len位
hive> ``select` `rpad(``'abc'``,10,``'td'``) ``from` `iteblog;``abctdtdtdt
21、分割字符串函数: split
语法: split(string str, string pat)
返回值: array
说明: 按照pat字符串分割str,会返回分割后的字符串数组
hive> ``select` `split(``'abtcdtef'``,``'t'``) ``from` `iteblog;``[``"ab"``,``"cd"``,``"ef"``]
22、集合查找函数: find_in_set
语法: find_in_set(string str, string strList)
返回值: int
说明: 返回str在strlist第一次出现的位置,strlist是用逗号分割的字符串。如果没有找该str字符,则返回0
hive> ``select` `find_in_set(``'ab'``,``'ef,ab,de'``) ``from` `iteblog;``2``hive> ``select` `find_in_set(``'at'``,``'ef,ab,de'``) ``from` `iteblog;``0
集合统计函数
1、个数统计函数: count
语法: count(), count(expr), count(DISTINCT expr[, expr_.])
返回值: int
说明: count()统计检索出的行的个数,包括NULL值的行;count(expr)返回指定字段的非空值的个数;count(DISTINCT expr[, expr_.])返回指定字段的不同的非空值的个数
hive> ``select` `count``(*) ``from` `iteblog;``20``hive> ``select` `count``(``distinct` `t) ``from` `iteblog;``10
2、总和统计函数: sum
语法: sum(col), sum(DISTINCT col)
返回值: double
说明: sum(col)统计结果集中col的相加的结果;sum(DISTINCT col)统计结果中col不同值相加的结果
hive> ``select` `sum``(t) ``from` `iteblog;``100``hive> ``select` `sum``(``distinct` `t) ``from` `iteblog;``70
3、平均值统计函数: avg
语法: avg(col), avg(DISTINCT col)
返回值: double
说明: avg(col)统计结果集中col的平均值;avg(DISTINCT col)统计结果中col不同值相加的平均值
hive> ``select` `avg``(t) ``from` `iteblog;``50``hive> ``select` `avg` `(``distinct` `t) ``from` `iteblog;``30
4、最小值统计函数: min
语法: min(col)
返回值: double
说明: 统计结果集中col字段的最小值
hive> ``select` `min``(t) ``from` `iteblog;``20
5、最大值统计函数: max
语法: maxcol)
返回值: double
说明: 统计结果集中col字段的最大值
hive> ``select` `max``(t) ``from` `iteblog;``120
6、非空集合总体变量函数: var_pop
语法: var_pop(col)
返回值: double
说明: 统计结果集中col非空集合的总体变量(忽略null)
7、非空集合样本变量函数: var_samp
语法: var_samp (col)
返回值: double
说明: 统计结果集中col非空集合的样本变量(忽略null)
8、总体标准偏离函数: stddev_pop
语法: stddev_pop(col)
返回值: double
说明: 该函数计算总体标准偏离,并返回总体变量的平方根,其返回值与VAR_POP函数的平方根相同
9、样本标准偏离函数: stddev_samp
语法: stddev_samp (col)
返回值: double
说明: 该函数计算样本标准偏离
10.中位数函数: percentile
语法: percentile(BIGINT col, p)
返回值: double
说明: 求准确的第pth个百分位数,p必须介于0和1之间,但是col字段目前只支持整数,不支持浮点数类型
11、中位数函数: percentile
语法: percentile(BIGINT col, array(p1 [, p2]…))
返回值: array
说明: 功能和上述类似,之后后面可以输入多个百分位数,返回类型也为array,其中为对应的百分位数。
select` `percentile(score,<0.2,0.4>) ``from` `iteblog; 取0.2,0.4位置的数据
12、近似中位数函数: percentile_approx
语法: percentile_approx(DOUBLE col, p [, B])
返回值: double
说明: 求近似的第pth个百分位数,p必须介于0和1之间,返回类型为double,但是col字段支持浮点类型。参数B控制内存消耗的近似精度,B越大,结果的准确度越高。默认为10,000。当col字段中的distinct值的个数小于B时,结果为准确的百分位数
13、近似中位数函数: percentile_approx
语法: percentile_approx(DOUBLE col, array(p1 [, p2]…) [, B])
返回值: array
说明: 功能和上述类似,之后后面可以输入多个百分位数,返回类型也为array,其中为对应的百分位数。
14、直方图: histogram_numeric
语法: histogram_numeric(col, b)
返回值: array<struct {‘x’,‘y’}>
说明: 以b为基准计算col的直方图信息。
hive> ``select` `histogram_numeric(100,5) ``from` `iteblog;``[{``"x"``:100.0,``"y"``:1.0}]
复合类型构建操作
1、Map类型构建: map
语法: map (key1, value1, key2, value2, …)
说明:根据输入的key和value对构建map类型
hive> ``Create` `table` `iteblog ``as` `select` `map(``'100'``,``'tom'``,``'200'``,``'mary'``) ``as` `t ``from` `iteblog;``hive> describe iteblog;``t map<string ,string>``hive> ``select` `t ``from` `iteblog;``{``"100"``:``"tom"``,``"200"``:``"mary"``}
2、Struct类型构建: struct
语法: struct(val1, val2, val3, …)
说明:根据输入的参数构建结构体struct类型
hive> ``create` `table` `iteblog ``as` `select` `struct(``'tom'``,``'mary'``,``'tim'``) ``as` `t ``from` `iteblog;``hive> describe iteblog;``t struct<col1:string ,col2:string,col3:string>``hive> ``select` `t ``from` `iteblog;``{``"col1"``:``"tom"``,``"col2"``:``"mary"``,``"col3"``:``"tim"``}
3、array类型构建: array
语法: array(val1, val2, …)
说明:根据输入的参数构建数组array类型
hive> ``create` `table` `iteblog ``as` `select` `array(``"tom"``,``"mary"``,``"tim"``) ``as` `t ``from` `iteblog;``hive> describe iteblog;``t array<string>``hive> ``select` `t ``from` `iteblog;``[``"tom"``,``"mary"``,``"tim"``]
复杂类型访问操作
1、array类型访问: A[n]
语法: A[n]
操作类型: A为array类型,n为int类型
说明:返回数组A中的第n个变量值。数组的起始下标为0。比如,A是个值为[‘foo’, ‘bar’]的数组类型,那么A[0]将返回’foo’,而A[1]将返回’bar’
hive> ``create` `table` `iteblog ``as` `select` `array(``"tom"``,``"mary"``,``"tim"``) ``as` `t ``from` `iteblog;``hive> ``select` `t[0],t[1],t[2] ``from` `iteblog;``tom mary tim
2、map类型访问: M[key]
语法: M[key]
操作类型: M为map类型,key为map中的key值
说明:返回map类型M中,key值为指定值的value值。比如,M是值为{‘f’ -> ‘foo’, ‘b’ -> ‘bar’, ‘all’ -> ‘foobar’}的map类型,那么M[‘all’]将会返回’foobar’
hive> ``Create` `table` `iteblog ``as` `select` `map(``'100'``,``'tom'``,``'200'``,``'mary'``) ``as` `t ``from` `iteblog;``hive> ``select` `t[``'200'``],t[``'100'``] ``from` `iteblog;``mary tom
3、struct类型访问: S.x
语法: S.x
操作类型: S为struct类型
说明:返回结构体S中的x字段。比如,对于结构体struct foobar {int foo, int bar},foobar.foo返回结构体中的foo字段
hive> ``create` `table` `iteblog ``as` `select` `struct(``'tom'``,``'mary'``,``'tim'``) ``as` `t ``from` `iteblog;``hive> describe iteblog;``t struct<col1:string ,col2:string,col3:string>``hive> ``select` `t.col1,t.col3 ``from` `iteblog;``tom tim
复杂类型长度统计函数
1.Map类型长度函数: size(Map<k .V>)
语法: size(Map<k .V>)
返回值: int
说明: 返回map类型的长度
hive> ``select` `size``(map(``'100'``,``'tom'``,``'101'``,``'mary'``)) ``from` `iteblog;``2
2.array类型长度函数: size(Array)
语法: size(Array)
返回值: int
说明: 返回array类型的长度
hive> ``select` `size``(array(``'100'``,``'101'``,``'102'``,``'103'``)) ``from` `iteblog;``4
3.类型转换函数
类型转换函数: cast
语法: cast(expr as )
返回值: Expected “=” to follow “type”
说明: 返回转换后的数据类型
hive> ``select` `cast``(1 ``as` `bigint``) ``from` `iteblog;``1
二、spark
Spark为什么比MR快
1、Spark基于内存计算
Spark vs MapReduce ≠ 内存 vs 磁盘
其实Spark和MapReduce的计算都发生在内存中,区别在于:
MapReduce通常需要将计算的中间结果写入磁盘,然后还要读取磁盘,从而导致了频繁的磁盘IO。
Spark则不需要将计算的中间结果写入磁盘,这得益于Spark的RDD(弹性分布式数据集,很强大)和DAG(有向无环图),其中DAG记录了job的stage以及在job执行过程中父RDD和子RDD之间的依赖关系。中间结果能够以RDD的形式存放在内存中,且能够从DAG中恢复,大大减少了磁盘IO。
2、多进程模型 vs 多线程模型的区别
MapReduce采用了多进程模型,而Spark采用了多线程模型。多进程模型的好处是便于细粒度控制每个任务占用的资源,但每次任务的启动都会消耗一定的启动时间。就是说MapReduce的Map Task和Reduce Task是进程级别的,而Spark Task则是基于线程模型的,就是说mapreduce 中的 map 和 reduce 都是 jvm 进程,每次启动都需要重新申请资源,消耗了不必要的时间(假设容器启动时间大概1s,如果有1200个block,那么单独启动map进程事件就需要20分钟)
Spark则是通过复用线程池中的线程来减少启动、关闭task所需要的开销。(多线程模型也有缺点,由于同节点上所有任务运行在一个进程中,因此,会出现严重的资源争用,难以细粒度控制每个任务占用资源)
Yarn Container
mapreduce on yarn :
- Application Master 向Resource Manager申请 Container , 每个MapTask/ReduceTask 申请一个Container。
每一个Task就是一个进程。也是一个Container 资源
进程的名字叫做:YarnChild
MapTask/ReduceTask 的资源申请是Container资源范围内。
那么:对于 Container 里面的task是单线程在运行的?
spark on yarn :
- Application Master 向Resource Manager申请 Container , 每个Executor申请一个Container。
每一个Executor是一个JVM进程。也是一个Container 资源
在spark on yarn 中 Executor 的进程 名字叫 CoarseGrainedExecutorBackend 类似于Hadoop MapReduce中的YarnChild, 一个CoarseGrainedExecutorBackend进程 有且仅有一个 executor对象,它负责将Task包装成taskRunner,并从线程池中抽取出一个空闲线程运行Task。
一个Yarn的container就是一个Spark的CoarseGrainedExecutorBackend,也就是常说的Spark的Executor进程,每个executor可以并行执行多个task。CoarseGrainedExecutorBackend是Executor的RPC endpoint服务,具体执行task是CoarseGrainedExecutorBackend持有的Executor对象的launchTask方法启动.
Executor 的资源申请是Container资源范围内。
- 在 Executor 中并行执行多个 Task
把executor的jvm进程看做task执行池,每个executor最大有 spark.executor.cores / spark.task.cpus 个执行槽( solt ), 一个执行槽可以同时执行一个task。
– spark.task.cpus 是指每个任务需要的 CPU 核数, 大部分应用使用默认值 1
– spark.executor.cores 是指每个executor的核数。
- Application --> 多个job --> 多个stage --> 多个task
1.Spark的driver端只是用来请求资源获取资源的,executor端是用来执行代码程序,=>application在driver端,job 、stage 、task在executor端。
2.在executor端划分job、划分stage、划分task。
**job : ** 程序遇见 action算子 划分一次,
**stage : ** 每个 job遇见 shuffle(或者宽依赖) 划分一次,
task数量 : 每个stage中 最后一个rdd的分区(分片)数 就是。
YarnCluster-Spark 执行流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UCVnVuTa-1620912702339)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507092846530.png)]
SparkStreaming
数据源API:
ReceiverAPI:需专门Executor接受数据,发送给其他处理数据的Executor
- 在速度上 : 接收数据 >> 处理数据 的Executor,导致计算节点内存溢出
DirectAPI:由计算Executor 主动消费kafka数据,速度自身控制
spark-submit常用参数:
–master 指定任务提交到哪个资源调度器
local/local[N]/local[*] 本地执行
spark://master主机名1:7077,master主机名2:7077,. 提交到spark内置的调度器执行
yarn 提交到yarn执行
–deploy-mode 指定部署模式[client/cluster]
client模式: 不管是提交到standalone还是yarn,Driver所在的位置都在Spark-Submit进程中
cluster模式:
如果是任务提交到standalone中,Driver在其中一个Worker中。
如果任务是提交到yarn中,Driver在ApplicationMaster所在进程中
–class 指定要执行的主类的全类名[带有main方法的object]
–driver-memory 指定Driver内存大小
–executor-memory 指定每个executor内存大小
–total-executor-cores 指定所有的executor cpu的总核数 [仅用于standalone模式]
–executor-cores 指定每个executor cpu核数[仅用于 standalone yarn模式]
–num-executors 指定executor的个数[仅用于yarn模式]
–queue 指定当前任务提交到哪个队列中[仅用于yarn模式]=================
bin/spark-submit
–master yarn \ --指定主服务器地址
–deploy-mode cluster \ --运行模式
–num-executors 80 \ 每台机器3-12个
–driver-memory 6g
–executor-memory 6g \ --总内存给数据量的8倍以上
–executor-cores 3 \ --每个exe给1-2个
–queue root.default \ --队列
–class com.atguigu.spark.WordCount \ --主类名
–conf spark.yarn.executor.memoryOverhead=2048
–conf spark.core.connect.ack.wait.timeout=300
/usr/local/spark/spark.jar
常规性能调优
调优1:RDD持久化
一般有RDD复用2次及以上,最好cache,因为不用的话 需要cpu资源计算,cache的话用的内存
调优2:并行度调节
spark默认并行度200
spark推荐,task并行度数量应该设置为spark作业总CPUcore数量的2-3倍
set("spark.default.parallelism,“500”)
调优3:广播大变量
广播变量1-2G
调优4:Kryo序列化
从spark2.0开始,简单类型、简单类型数组、字符串类型的Shuffle RDDs
以上 已经默认使用Kryo序列化
//使用Kryo序列化库,如果要使用Java序列化库,需要把该行屏蔽掉
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
//在Kryo序列化库中注册自定义的类集合,如果要使用Java序列化库,需要把该行屏蔽掉
conf.set("spark.kryo.registrator", "atguigu.com.MyKryoRegistrator");
调优5:调节本地化等待时长
数据本地化,当节点资源不足是会迁移到其他节点计算,迁移时间太长不合适;故需要设置迁移级别和迁移超时时间
set("spark.locality.wait","6");//秒级别
算子调优
mapPartition/foreachPartition
集群资源充足的情况下,可以适当使用,提高效率,当然也容易OOM
foreachPartition 适合 与外部存储建立连接
filter与coalesce结合使用
过滤后再进行分区缩小,以较少reduceTask数量
repartition 解决sparkSql低并行度问题
常规调优对并行度调节策略:设置spark.default.parallelism参数指定并行度,在sparkSql中是没有效果的,只有在 没有sparkSql 的stage中生效。
sparkSql的并行度无法手动调节,在sparkSql查询出来的RDD后,使用repratition增大分区数的方式增加Task的数量,并行度也就增加了
reduceByKey预聚合
map端数据量变小,减小磁盘IO和占用
下个stage 拉取 的数据量变小,减小网络IO
reduce端缓存占用减小,聚合的数据量减小
Shuffle调优
调节map端缓冲区大小-32K
shuffle的map 处理的数据量较大,但map端缓冲区大小默认32k,可能会出现map端缓冲区数据频繁溢出磁盘文件,性能降低
通过调节map缓冲区大小,避免频繁的磁盘IO
spark.shuffle.file.buffer
reduce端拉取数据缓冲区调节-默认48m
spark shuffle过程中,shuffle reduce task 的 shuffle缓冲区(默认48M) 决定了reduce task每次缓冲(拉取)的数据量,适当增大可以减少拉取次数/网络传输次数
spark.reducer.maxSizeInFlight
reduce端拉取数据重试次数-默认3次
网络异常等 导致拉取失败自动重试(默认3次)
对于耗时shuffle作业,建议增加重试次数(60),避免由于JVM的full gc或者网络异常导致拉取失败
spark.shuffle.io.maxRetries
reduce端拉取数据等待间隔-默认5s
spark.shuffle.io.retryWait
SortShuffle->byPass-默认<200
当使用SortShuffle时,分区数大于200 ,又不想排序,可以设置byPass阈值
spark.shuffle.sort.bypassMergeThreshold
JVM调优
fullGC/minorGC 导致JVM线程停止工作
减低cache操作内存占比
1. Spark静态内存管理机制,堆内存被划分为了两块,Storage和Execution
Storage主要用于缓存RDD数据和broadcast数据。Storage占系统内存的60%
Execution主要用于缓存在shuffle过程中产生的中间数据。Execution占系统内存的20%,并且两者完全独立。
在一般情况下,Storage的内存都提供给了cache操作,但是如果在某些情况下cache操作内存不是很紧张,而task的算子中创建的对象很多,Execution内存又相对较小,这回导致频繁的minor gc,甚至于频繁的full gc,进而导致Spark频繁的停止工作,性能影响会很大。
在Spark UI中可以查看每个stage的运行情况,包括每个task的运行时间、gc时间等等,如果发现gc太频繁,时间太长,就可以考虑调节Storage的内存占比,让task执行算子函数式,有更多的内存可以使用。
Storage内存区域可以通过spark.storage.memoryFraction参数进行指定,默认为0.6,即60%,可以逐级向下递减,如代码清单所示:
val conf = new SparkConf().set(“spark.storage.memoryFraction”, “0.4”)
2.Spark统一内存管理机制,堆内存被划分为了两块,Storage和Execution
Storage主要用于缓存数据,
Execution主要用于缓存在shuffle过程中产生的中间数据,
两者所组成的内存部分称为统一内存,Storage和Execution各占统一内存的50%,由于动态占用机制的实现,shuffle过程需要的内存过大时,会自动占用Storage的内存区域,因此无需手动进行调节。
调节Executor堆外内存
Executor的堆外内存主要用于程序的共享库、Perm Space、 线程Stack和一些Memory mapping等, 或者类C方式allocate object。
有时,如果你的Spark作业处理的数据量非常大,达到几亿的数据量,此时运行Spark作业会时不时地报错,例如shuffle output file cannot find,executor lost,task lost,out of memory等,这可能是Executor的堆外内存不太够用,导致Executor在运行的过程中内存溢出。
stage的task在运行的时候,可能要从一些Executor中去拉取shuffle map output文件,但是Executor可能已经由于内存溢出挂掉了,其关联的BlockManager也没有了,这就可能会报出shuffle output file cannot find,executor lost,task lost,out of memory等错误,此时,就可以考虑调节一下Executor的堆外内存,也就可以避免报错,与此同时,堆外内存调节的比较大的时候,对于性能来讲,也会带来一定的提升。
默认情况下,Executor堆外内存上限大概为300多MB,在实际的生产环境下,对海量数据进行处理的时候,这里都会出现问题,导致Spark作业反复崩溃,无法运行,此时就会去调节这个参数,到至少1G,甚至于2G、4G。
Executor堆外内存的配置需要在spark-submit脚本里配置,如代码清单所示:
–conf spark.yarn.executor.memoryOverhead=2048
以上参数配置完成后,会避免掉某些JVM OOM的异常问题,同时,可以提升整体Spark作业的性能。
调节连接等待时长
在Spark作业运行过程中,Executor优先从自己本地关联的BlockManager中获取某份数据,如果本地BlockManager没有的话,会通过TransferService远程连接其他节点上Executor的BlockManager来获取数据。
如果task在运行过程中创建大量对象或者创建的对象较大,会占用大量的内存,这回导致频繁的垃圾回收,但是垃圾回收会导致工作线程全部停止,也就是说,垃圾回收一旦执行,Spark的Executor进程就会停止工作,无法提供相应,此时,由于没有响应,无法建立网络连接,会导致网络连接超时。
在生产环境下,有时会遇到file not found、file lost这类错误,在这种情况下,很有可能是Executor的BlockManager在拉取数据的时候,无法建立连接,然后超过默认的连接等待时长60s后,宣告数据拉取失败,如果反复尝试都拉取不到数据,可能会导致Spark作业的崩溃。这种情况也可能会导致DAGScheduler反复提交几次stage,TaskScheduler返回提交几次task,大大延长了我们的Spark作业的运行时间。
此时,可以考虑调节连接的超时时长,连接等待时长需要在spark-submit脚本中进行设置,设置方式如代码清单所示:
–conf spark.core.connection.ack.wait.timeout=300
调节连接等待时长后,通常可以避免部分的XX文件拉取失败、XX文件lost等报错
Spark数据倾斜
主要指shuffle过程中出现的数据倾斜问题,是由于不同的key对应的数据量不同导致的不同task所处理的数据量不同的问题。
例如,reduce点一共要处理100万条数据,第一个和第二个task分别被分配到了1万条数据,计算5分钟内完成,第三个task分配到了98万数据,此时第三个task可能需要10个小时完成,这使得整个Spark作业需要10个小时才能运行完成,这就是数据倾斜所带来的后果。
注意,要区分开数据倾斜与数据量过量这两种情况,数据倾斜是指少数task被分配了绝大多数的数据,因此少数task运行缓慢;数据过量是指所有task被分配的数据量都很大,相差不多,所有task都运行缓慢。
数据倾斜的表现:
Ø Spark作业的大部分task都执行迅速,只有有限的几个task执行的非常慢,此时可能出现了数据倾斜,作业可以运行,但是运行得非常慢;
Ø Spark作业的大部分task都执行迅速,但是有的task在运行过程中会突然报出OOM,反复执行几次都在某一个task报出OOM错误,此时可能出现了数据倾斜,作业无法正常运行。
定位数据倾斜问题:
Ø 查阅代码中的shuffle算子,例如reduceByKey、countByKey、groupByKey、join等算子,根据代码逻辑判断此处是否会出现数据倾斜;
Ø 查看Spark作业的log文件,log文件对于错误的记录会精确到代码的某一行,可以根据异常定位到的代码位置来明确错误发生在第几个stage,对应的shuffle算子是哪一个;
倾斜指标:
-
会员/vip等级(数值越小规模越大):人数,消费金额、客单价、浏览数。。。
-
商品分类/类名(生活日用(倾斜)/奢侈品(少)):销售金额、浏览、收藏、点赞、评论;
-
活动时间维度:双十一是一周的活动,统计每天:销售额、浏览、收藏。。。
-
地区(广东多,新疆少),(男女-T恤/羽绒服):统计服饰销量、浏览、点赞。。。
三、Kafka
kafka写数据:
- 顺序写,往磁盘上写数据时,就是追加数据,没有随机写的操作。经验: 如果一个服务器磁盘达到一定的个数,磁盘也达到一定转数,往磁盘里面顺序写(追加写)数据的速度和写内存的速度差不多
生产者生产消息,经过kafka服务先写到os cache 内存中,然后经过sync顺序写到磁盘上
消费者读取数据流程:
- 普通
- 消费者发送请求给kafka服务
- kafka服务去os cache缓存读取数据(缓存没有就去磁盘读取数据)
- 从磁盘读取了数据到os cache缓存中
- os cache复制数据到kafka应用程序中
- kafka将数据(复制)发送到socket cache中
- socket cache通过网卡传输给消费者
- 零拷贝(页缓存:os cache,有os管理)
1.消费者发送请求给kafka服务
2.kafka服务去os cache缓存读取数据(缓存没有就去磁盘读取数据)
3.从磁盘读取了数据到os cache缓存中
4.os cache直接将数据发送给网卡
5.通过网卡将数据传输给消费者
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9dk5r82p-1620912702341)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507093741624.png)]
kafka 数据直接持久化到pageCache中
好处:
I/O Scheduler 会将连续的小块写组装成大块的物理写 从而提升性能
I/O Scheduler 会尝试将一些写操作重新按顺序排序,从而减少磁头的移动时间
充分利用所有的空间内存(非JVM内存),若使用引用层Cache(JVM堆内存)会增加GC负担
读操作可直接在PageCache内进行,若消费和生产速度相当,甚至不需要通过物理磁盘(直接通过Page Cache )交换数据
若进程重启,JVM内的Cache会失效,但PageCache仍可用【宕机导致PageCache数据丢失,但Repatition机制解决。若为保数据不丢而强制刷写磁盘,反而会降低性能】
Kafka日志分段保存
Kafka中一个主题,一般会设置分区;比如创建了一个topic_a
,然后创建的时候指定了这个主题有三个分区。其实在三台服务器上,会创建三个目录。服务器1(kafka1)创建目录topic_a-0:。目录下面是我们文件(存储数据),kafka数据就是message,数据存储在log文件里。.log结尾的就是日志文件,在kafka中把数据文件就叫做日志文件 。一个分区下面默认有n多个日志文件(分段存储),一个日志文件默认1G。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CV9nKfU-1620912702342)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507093928761.png)]
Kafka二分查找定位数据
消息体对应offset -> 在对应 index文件(稀疏索引4k一个) -> 定位log文件对应的消息体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CVeofDAT-1620912702343)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507094404722.png)]
副本冗余保证高可用
0.8以前没有副本机制
leader:1.读写操作;2.维护ISR列表
ISR:{a,b,c}若一follower分区 超过10秒 没向leader partition拉取数据,这个分区就从ISR列表里移除。
follower:向leader同步数据
框架总结
**高可用:**多副本机制
**高并发:**网络架构设计 三层架构:多selector -> 多线程 -> 队列的设计(NIO)
高性能:
A 写数据:
- 把数据先写入到OS Cache
- 写到磁盘上面是顺序写,性能很高
B 读数据:
- 根据稀疏索引,快速定位到要消费的数据
- 零拷贝机制 减少数据的拷贝 减少了应用程序与操作系统上下文切换
专有名词
**QPS(Query Per Second):**每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
**吞吐量(Throughput) (TPS):**吞吐量是指系统在单位时间内处理请求的数量。
**响应时间(RT) :**响应时间是指系统对请求作出响应的时间。
**并发用户数 :**并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。
生产环境分析
需求分析:
pqs/day:每天10亿,高峰期3h处理6亿请求:qps6亿 / 3h = 5.5万/s pqs
数据量:10亿 * 50kb/条 * 2副本 * 3天 = 276T
注: (50kb/条 )我司因在生产端封装了数据,然后多条数据合并,故一个请求才会有这么大
物理机数:
评估机器: 按照高峰期 * 4倍 = 20万pqs
每台机器 承受 4万请求 -> 5台机器
磁盘选择:
SSD硬盘:性能好,指它 随机读写性能 ** 较好,但顺序写跟SAS盘差不多,适合MySql集群**
SAS盘:某方面性能不是很好,但比较便宜。
kafka的理解:就是用的顺序写。所以我们就用普通的【
机械硬盘
】就可以了。每台机器:276T / 5台 = 60T/台
现有配置:11块硬盘/机器 * 7T/块硬盘 = 77 T/机器
**总结:**10亿请求,5台机器,每个机器 11(SAS) * 7T
内存评估:
kafka读写流程 都基于os cache => 尽可能多的内存资源要给 os cache
Kafka中核心代码是scala,客户端java。都是基于jvm => 部分的内存给jvm
Kafka的设计,没有把很多数据结构都放在jvm里面 =>jvm不需太大内存
根据经验:>=10G
假设10个请求,拥有100Topic => 总partition数 = 100Topic * 5 partition * 2副本 = 1000partition
**kafka使用内存/机器:**1000partition * 1G /.log 文件 * 25%常驻内存 / 5机器 = 50G
机器总内存: kafka(50G) + JVM(10G)+ OS(10G-5G) = 64G (128G更好)
CPU压力评估
评估需要多少个cpu (线程依托cpu运行)服务有多少线程ing
线程多,cpu core少,会导致机器负载高性能差
kafka 启动线程数:
Acceptor线程 1 ,processor线程 3, 6~9个线程 处理请求线程 8个, 32个线程 定时清理的线程,拉取数据的线程,定时检查ISR列表的机制 等等。
所以大概一个Kafka的服务启动起来以后,会有一百多个线程。
机器cpu 线程数:
- cpu core = 4个,几十个线程,就肯定把cpu 打满了。
- cpu core = 8个,轻松支持几十个线程。如是100多,或差不多200个,那么8 个 cpu core是搞不定的。
- 所以我们这儿建议:CPU core = 16个。如果可以的话,能有32个cpu core 那就最好。
- 结论:kafka集群,最低也要给16个cpu core,如果能给到32 cpu core那就更好。2cpu * 8 =16 cpu core 4cpu * 8 = 32 cpu core
总结:10亿请求,5台物理机,11(SAS) * 7T ,需64G内存(128G更好),需16cpu core(32更好)
网络需求评估:
千兆的网卡(1G/s),万兆的网卡(10G/s)
网络请求流量:5.5w pqs / 5机器 * 50kb/条 * 2副本 = 976m/s
注: 网卡的带宽是达不到极限的,70%左右
集群规划:
主从式的架构:controller -> 通过zk集群来管理整个集群的元数据。
注:kafka集群 理论上来讲,不应该把kafka的服务与zk的服务安装在一起
运维:
场景一:topic数据量太大,要增加topic数
kafka-topics.sh --alter --zookeeper hdp1:2181,hdp2:2181,hdp3:2181 --partitions 2 --topic test6
场景二:核心topic增加副本因子
- 核心业务数据需要增加副本因子 vim test.json脚本,将下面一行json脚本保存
{
“version”:1,
“partitions”:[
{“topic”:“test6”,“partition”:0,“replicas”:[0,1,2]},
{“topic”:“test6”,“partition”:1,“replicas”:[0,1,2]},
{“topic”:“test6”,“partition”:2,“replicas”:[0,1,2]}
]
}
- 执行上面json脚本:
kafka-reassign-partitions.sh \
--zookeeper hadoop1:2181,hadoop2:2181,hadoop3:2181 \
--reassignment-json-file test.json \
--execute
场景三:负载不均衡的topic,手动迁移vi topics-to-move.json
{“topics”: [{“topic”: “test01”}, {“topic”: “test02”}], “version”: 1} // 把你所有的topic都写在这里
- 执行:
kafka-reassgin-partitions.sh \
--zookeeper hadoop1:2181,hadoop2:2181,hadoop3:2181 \
--topics-to-move-json-file topics-to-move.json \
--broker-list “5,6” \
--generate
把你所有的包括新加入的broker机器都写在这里,就会说是把所有的partition均匀的分散在各个broker上,包括新进来的broker此时会生成一个迁移方案,可以保存到一个文件里去:expand-cluster-reassignment.json
kafka-reassign-partitions.sh \
--zookeeper hadoop01:2181,hadoop02:2181,hadoop03:2181 \
--reassignment-json-file expand-cluster-reassignment.json \
--execute
kafka-reassign-partitions.sh \
--zookeeper hadoop01:2181,hadoop02:2181,hadoop03:2181 \
--reassignment-json-file expand-cluster-reassignment.json \
--verify
这种数据迁移操作一定要在晚上低峰的时候来做,因为他会在机器之间迁移数据,非常的占用带宽资源
–generate: 根据给予的Topic列表和Broker列表生成迁移计划。generate并不会真正进行消息迁移,而是将消息迁移计划计算出来,供execute命令使用。
–execute: 根据给予的消息迁移计划进行迁移。
–verify: 检查消息是否已经迁移完成。
场景四:如果某个broker leader partition过多
正常情况下,我们的leader partition在服务器之间是负载均衡,现在各个业务方可以自行申请创建topic,分区数量都是自动分配和后续动态调整的, kafka本身会自动把leader partition均匀分散在各个机器上,这样可以保证每台机器的读写吞吐量都是均匀的
如果某些broker宕机,会导致leader partition过于集中在其他少部分几台broker上, 这会导致少数几台broker的读写请求压力过高,其他宕机的broker重启之后都是follower partition,读写请求很低造成集群负载不均衡
=> 有一个参数,auto.leader.rebalance.enable,默认是true, 每隔300秒(leader.imbalance.check.interval.seconds)检查leader负载是否平衡
如果一台broker上的不均衡的leader超过了10%,leader.imbalance.per.broker.percentage(每个broker允许的不平衡的leader的比率)
=> 就会对这个broker进行选举 配置参数:auto.leader.rebalance.enable 默认是true 。
leader.imbalance.check.interval.seconds( leader不平衡检查间隔时间):默认值300秒
kafka生产者
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tt3OE0dN-1620912702344)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507131016827.png)]
如何提升吞吐量
参数一:
buffer.memory
:设置发送消息的缓冲区,默认值是33554432,就是32MB参数二:
compression.type
:默认是none,不压缩,但是也可以使用lz4压缩,效率还是不错的,压缩之后可以减小数据量,提升吞吐量,但是会加大producer端的cpu开销参数三:
batch.size
:设置batch的大小,
- 如果batch太小,会导致频繁网络请求,吞吐量下降;
- 如果batch太大,会导致一条消息需要等待很久才能被发送出去,而且会让内存缓冲区有很大压力,过多数据缓冲在内存里,默认值是:16384,就是16kb,也就是一个batch满了16kb就发送出去,
- 一般在实际生产环境,这个batch的值可以增大一些来提升吞吐量,如果一个批次设置大了,会有延迟。一般根据一条消息大小来设置。如果我们消息比较少。配合使用的参数linger.ms,这个值默认是0,意思就是消息必须立即被发送,但是这是不对的,一般设置一个100毫秒之类的,这样的话就是说,这个消息被发送出去后进入一个batch,如果100毫秒内,这个batch满了16kb,自然就会发送出去。
处理异常
- LeaderNotAvailableException:这个就是如果某台机器挂了,此时leader副本不可用,会导致你写入失败,要等待其他follower副本切换为leader副本之后,才能继续写入,此时可以重试发送即可;如果说你平时重启kafka的broker进程,肯定会导致leader切换,一定会导致你写入报错,是LeaderNotAvailableException。
- NotControllerException:这个也是同理,如果说Controller所在Broker挂了,那么此时会有问题,需要等待Controller重新选举,此时也是一样就是重试即可。
- NetworkException:网络异常 timeout
- a. 配置retries参数,他会自动重试的
- b. 但是如果重试几次之后还是不行,就会提供Exception给我们来处理了,我们获取到异常以后,再对这个消息进行单独处理。我们会有备用链路。发送不成功的消息发送到Redis或者写到文件系统中,甚至是丢弃。
重试机制
- 消息会重复有的时候一些leader切换之类的问题,需要进行重试,设置retries即可,但是消息重试会导致,重复发送的问题,
- 比如说网络抖动一下导致他以为没成功,就重试了,其实人家都成功了.
- 消息乱序消息重试是可能导致消息的乱序的,因为可能排在你后面的消息都发送出去了。所以可以使用"
max.in.flight.requests.per.connection
"参数设置为1, 这样可以保证producer同一时间只能发送一条消息
。两次重试的间隔默认是100毫秒,用"retry.backoff.ms"来进行设置 基本上在开发过程中,靠重试机制基本就可以搞定95%的异常问题。
Kafka消费者
偏移量管理
- 每个consumer内存里数据结构保存对每个topic的每个分区的消费offset,定期会提交offset,老版本是写入zk,但是那样高并发请求zk是不合理的架构设计,zk是做分布式系统的协调的,轻量级的元数据存储,不能负责高并发读写,作为数据存储。
- 新版本提交offset发给kafka内部topic:__consumer_offsets,提交KV结构
- key:group.id+topic+分区号,
- value:当前offset的值,
- 每隔一段时间,kafka内部会对这个topic进行compact(合并),也就是每个group.id+topic+分区号就保留最新数据。
- __consumer_offsets可能会接收高并发的请求,所以默认分区50个(leader partitiron -> 50 kafka),这样如果你的kafka部署了一个大的集群,比如有50台机器,就可以用50台机器来抗offset提交的请求压力.
偏移量监控工具介绍
- web页面管理的一个管理软件(kafka Manager) 修改bin/kafka-run-class.sh脚本,第一行增加JMX_PORT=9988 重启kafka进程
- 另一个软件:主要监控的consumer的偏移量。就是一个jar包 java -cp KafkaOffsetMonitor-assembly-0.3.0-SNAPSHOT.jar com.quantifind.kafka.offsetapp.OffsetGetterWeb –offsetStorage kafka \(根据版本:偏移量存在kafka就填kafka,存在zookeeper就填zookeeper) –zk hadoop1:2181 –port 9004 –refresh 15.seconds –retain 2.days。
消费异常感知
heartbeat.interval.ms:consumer心跳时间间隔,必须得与coordinator保持心跳才能知道consumer是否故障了, 如果故障之后,就会通过心跳下发rebalance的指令给其他的consumer通知他们进行rebalance的操作
session.timeout.ms:默认是10秒,kafka多长时间感知不到一个consumer就认为他故障了,
max.poll.interval.ms:如果在两次poll操作之间,超过了这个时间,那么就会认为这个consume处理能力太弱了,会被踢出消费组,分区分配给别人去消费
一般来说结合业务处理的性能来设置就可以了。
核心参数解释
fetch.max.bytes:获取一条消息最大的字节数,一般建议设置大一些,默认是1M 其实我们在之前多个地方都见到过这个类似的参数,意思就是说一条信息最大能多大?
- Producer 发送的数据,一条消息最大多大, -> 10M
- Broker 存储数据,一条消息最大能接受多大 -> 10M
- Consumer max.poll.records: 一次poll返回消息的最大条数,默认是500条
- connection.max.idle.ms:consumer跟broker的socket连接如果空闲超过了一定的时间,此时就会自动回收连接,但是下次消费就要重新建立socket连接,这个建议设置为-1,不要去回收
- enable.auto.commit: 开启自动提交偏移量
- auto.commit.interval.ms: 每隔多久提交一次偏移量,默认值5000毫秒
- _consumer_offset auto.offset.reset:
- earliest
- 当各分区下有已提交的offset时,从提交的offset开始消费;
- 无提交的offset时,从头开始消费 topica -> partition0:1000 partitino1:2000
- latest
- 当各分区下有已提交的offset时,从提交的offset开始消费;
- 无提交的offset时,消费新产生的该分区下的数据 none topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
group coordinator原理
- 面试题:消费者是如何实现rebalance的?答:根据coordinator实现
什么是coordinator
- 每个consumer group都会选择一个broker作为自己的coordinator,他是负责监控这个消费组里各个消费者的心跳,以及判断是否宕机,然后开启rebalance的
如何选择coordinator机器
- 首先对groupId进行hash(数字),
- 接着对__consumer_offsets的分区数量取模,默认是50,_consumer_offsets的分区数可以通过offsets.topic.num.partitions来设置
- 找到分区以后,这个分区所在的broker机器就是coordinator机器。
- eg:比如说:groupId,“myconsumer_group” -> hash值(数字)-> 对50取模 -> 8 __consumer_offsets 这个主题的8号分区在哪台broker上面,那一台就是coordinator 就知道这个consumer group下的所有的消费者提交offset的时候是往哪个分区去提交offset,
运行流程
1)每个consumer都发送JoinGroup请求到Coordinator, 然后Coordinator从一个consumer group中选择一个consumer作为leader,(选择leader)
2)把consumer group情况发送给leader,接着leader会负责制定消费方案, (leader定制消费方案)
3)通过SyncGroup发给Coordinator ,Coordinator就把消费方案下发给各个consumer,他们会从指定的分区的 leader broker,进行socket连接以及消费消息**(Coordinator把消费方案下发各个consumer)**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-561vRVG9-1620912702345)(C:\Users\hason\AppData\Roaming\Typora\typora-user-images\image-20210507142400943.png)]
rebalance策略
consumer group靠coordinator实现了Rebalance,rebalance的策略:range、round-robin、sticky
- **sticky策略:**尽可能保证在rebalance的时候,让原本属于这个consumer 的分区还是属于他们,然后把多余的分区再均匀分配过去,这样尽可能维持原来的分区分配的策略
Broker管理
LEO、HW:
在kafka里面,无论leader partition还是follower partition统一都称作副本(replica)。
LEO:每次partition接收到一条消息,都会更新自己的LEO,也就是log end offset,LEO其实就是最新的offset + 1
HW:高水位 LEO有一个很重要的功能就是更新HW,如果follower和leader的LEO同步了,此时HW就可以更新 HW之前的数据对消费者是可见,消息属于commit状态。HW之后的消息消费者消费不到。
controller如何管理整个集群
竞争controller的 /controller/id
controller服务监听的目录:
- /broker/ids/ 用来感知 broker上下线
- /broker/topics/ 创建主题,我们当时创建主题命令,提供的参数,ZK地址。
- /admin/reassign_partitions 分区重分
延时任务(扩展知识)
第一类延时的任务:
- 比如说producer的acks=-1,必须等待leader和follower都写完才能返回响应。有一个超时时间,默认是30秒(request.timeout.ms)。所以需要在写入一条数据到leader磁盘之后,就必须有一个延时任务,
- 30秒之后,到期时间是30秒延时任务 放到**DelayedOperationPurgatory(延时管理器)**中。
- 假如在30秒之前
- 如果所有follower都写入副本到本地磁盘了,那么这个任务就会被自动触发苏醒,就可以返回响应结果给客户端了, 否则的话,这个延时任务自己指定了最多是30秒到期,
- 如果到了超时时间,都没等到,就直接超时返回异常。
第二类延时的任务:
- follower往leader拉取消息的时候,如果发现是空的,此时会创建一个延时拉取任务 延时时间到了之后(比如到了100ms),就给follower返回一个空的数据,然后follower再次发送请求读取消息,
- 但是如果延时的过程中(还没到100ms),leader写入了消息(不为空),这个任务就会自动苏醒,自动执行拉取任务。
海量的延时任务,需要去调度。
时间轮机制
时间轮说白其实就是一个数组。
Kafka内部有很多延时任务,没有基于JDK Timer来实现,那个插入和删除任务的时间复杂度是O(nlogn), 而是基于了自己写的时间轮来实现的,时间复杂度是O(1),依靠时间轮机制,延时任务插入和删除,O(1)
**tickMs:**时间轮间隔 1ms
**wheelSize:**时间轮大小 20
**interval:**timckMS * whellSize,一个时间轮的总的时间跨度。20ms
**currentTime:**当时时间的指针。
1. 因为时间轮是一个数组,所以要获取里面数据的时候,靠的是index,时间复杂度是O(1)
2. 数组某个位置上对应的任务,用的是双向链表存储的,往双向链表里面插入,删除任务,时间复杂度也是O(1)
举例:插入一个8ms以后要执行的任务 19ms
多层级的时间轮 :
- 比如:要插入一个110毫秒以后运行的任务。
- tickMs:时间轮间隔 20ms
- wheelSize:时间轮大小 20
- interval:timckMS * whellSize,一个时间轮的总的时间跨度。20ms
- currentTime:当时时间的指针。
- 第一层时间轮:1ms * 20 ;第二层时间轮:20ms * 20 ;第三层时间轮:400ms * 20
四、HIVE
解析/编译/优化/执行器
- 解析器 SQL Parser:将SQL字符串转换为 抽象语法树AST(依赖第三方antlr库),对AST进行语法分析(例如:表、字段是否存在,sql语义是否有误)
- 编译器Physical Plan:将AST编译成逻辑执行计划
- 优化器Query Optimizer:对逻辑执行计划进行优化
- 执行器Executor:把逻辑执行计划转换成可以执行的物理计划(MR程序)
五、sqoop
参数
sqoop
--import
--connect 数据库URL
--username
--passwd
#目标路径及删除
--target-dir
--delete-target-dir
#空值处理
#mysql->hdfs 导入
--null-string '\\N'
--null-non-string '\\N'
#hdfs->mysql 导出
--input-null-string '\\N'
--input-null-non-string '\\N'
#指定分割符
--fields-terminated-dir '\t'
#sql
--query "select .. from table ...and$CONDITIONS"
-----以下可选参数------
#切分字段、map数
--split-by id
--num-mappers 100
-----同步策略--------
全量:sql后加 where 1=1
新增:sql后加 where 创建时间=昨天 or 操作时间=昨天
--increment append (用在直接导入hive表的时候)
特殊:只到一次
新增及变化:用户表(拉链)
场景一:导入mysql表数据中,若出现问题(BUG、中断、出错)导致导入数据不正确
- 临时表,完成后再导入正式表,出问题就清空临时表再重新导入正式表
–staging-table
–clear-staging-table
场景二:sqoop导数据慢
- 可将表进行指定字段切分
split-by 指定字段【指定字段最好不是String字符串类型】
- 增加map个数(默认4map)
–mappers 100
场景三:1G 需要多久跑完
几分钟
场景四:ads层的Parquet格式表 导mysql ,报错
方案一: 在hive中将该表 加载到 textfile格式的临时表 再导入mysql
方案二: ads不要建parquet格式表
六、Azkaban
自定义报警onealter睿象云
微信、钉钉、微信、电话等等
电话需要调用电信运营商接口 付费
使用第三方运维平台 onealter睿象云 集成多种组件(ZABBIX、阿里云、restAPI) 付费
- 购买相应服务后,获取通知API。然后进行MailAlter二次开发
异常处理
优先重跑:1 自动重跑,2 手动重跑 【注】重跑还失败:看日志
yarn logs -applicationId "appId" | less
=》 shift+g
- 【注】 “| less ” 为一页一页翻着看;G:跳到最后一页
七、Hbase
Hbase中Region/Store/StoreFile/Hfile之间的关系
Table: Hbase的数据是以表的形式存在
ColumnFamily列簇: 相当于mysql Table的字段,是Hbase的表结构
- Column: 列, hbase的列 = 列簇:列限定符
- Cell: 根据 rowkey+列簇:列限定符+时间戳 确定唯一一个cell
- Row: 一行数据,主键相同的为一行数据
列限定符: 放在列簇下,是数据的形式存在
version: 在创建表的时候再列簇上指定的版本号,version代表列簇下的列限定符中最多可以保存多少个历史数据
namespace: 命名空间,相当于Mysql的库
table 在行的方向分割为多个region。region是Hbase中分布式存储 和 负载均衡 的最小单元
即不同的 region可以分别在不同 的 regionServer上,但是同一个 region是不会拆分到多个 server上
Store:每一个region有一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里,即为每个columnFamily建一个store(即有几个columnFamily 就有几个store)。一个store由一个memStore和0或多个storeFile组成
- HBase以store的大小来判断是否需要切分region。
MemStore:内存,KV数据,刷写阈值默认64M -> 一个快照。专属线程负责刷写操作
StoreFile:memStore每次刷写后形成新StoreFile文件(以HFile格式保存)
HFile:KV数据,是hadoop二进制格式文件,一个StoreFile对应着一个HFile。而HFile是存储在HDFS之上的。
- HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。Trailer中又指针指向其他数据块的起始点,FileInfo记录了文件的一些meta信息。
Hbase架构
- Master:
- 1、负责表的创建、删除、修改
- 2、监控regionserver的状态,一旦regionserver宕机,会将宕机的regionserver中的region进行故障转移
- 3、负责region的分配
- Regionserver:
- 1、负责客户端的数据的增删改查
- 2、负责region split、storeFile compact等操作
- region: region保存在regionserver中
- Store: store的个数 = 列簇的个数
- memstore: 是一块内存区域,后续数据会先写入memstore中,等达到一定的条件之后,memstore会flush,flush的时候会生成storeFile
- storeFile: memstore flush生成,每 flush一次会生成一个storeFile,最终以HFile这种文件格式保存在HDFS
- HLog: 预写日志,因为memstore是内存区域m,所以数据如果直接写到memstore中,可能因为regionserver造成数据丢失,所以后续写入的时候会将数据写入日志中,等到memstore数据丢失之后,可以从日志中回复数据【一个regionserver只有一个Hlog】
- Zookeeper: master监控regionserver状态需要依赖zk
Hbase原理
1、Hbase元数据: hbase:meta
hbase元数据表中数据的rowkey = 表名,region起始rowkey,时间戳.region名称
hbase元数据中保存的有region的起始rowky,结束rowkey,所处于哪个regionserver
hbase元数据表只有一个region
2、写入流程
1、client会向zookeeper发起请求,获取元数据处于哪个regionserver
2、zookeeper会将元数据所在regionserver返回给client
3、client会向元数据所在的regionserver发起请求,获取元数据
4、元数据所在的regionserver返回元数据信息给client,client会缓存元数据信息
5、client会根据元数据得知数据应该写到哪个region,region处于哪个regionserver,会region所在的regionserver发起数据写入请求
6、数据首先写入HLog,然后再写入region的store中的memstore中
7、返回信息告知client写入完成
3、读取流程:
1、client会向zookeeper发起请求,获取元数据处于哪个regionserver
2、zookeeper会将元数据所在regionserver返回给client
3、client会向元数据所在的regionserver发起请求,获取元数据
4、元数据所在的regionserver返回元数据信息给client,client会缓存元数据信息
5、client会根据元数据得知数据应该写到哪个region,region处于哪个regionserver,会region所在的regionserver发起数据读取请求
6、首先会从block cache中获取数据,如果block cache中没有或者只有一部分数据,会再从memstore和storeFile中获取数据
从storeFile中读取数据的时候会根据布隆过滤器判断数据可能存在与哪些storeFile中,然后再根据HFile文件格式中的数据索引获取数据。
布隆过滤器特性: 如果判断存在不一定存在,如果判断不存在则一定不存在
7、获取到最终数据之后,如果需要缓存结果的话,会将当前查询结果缓存到block cache中,返回数据给客户端
blockCache 块缓存 内存存储 的 数据淘汰机制 基于 LRU算法
算法认为最近使用的数据是热门数据,下一次很大概率将会再次被使用。而最近很少被使用的数据,很大概率下一次不再用到。当缓存容量的满时候,优先淘汰最近很少使用的数据。
4、memstore flush的触发条件
memstore flush的最小单位是region,不会只单独flush某个memstore,所以如果region中有多个store的话,可能在flush的时候会生成大量的小文件,所以工作中创建表的时候列簇的个数一般设置为1个,最多不超过两个
1、region中某个memstore的大小达到128M,会触发flush
2、region中所有的memstore的总大小达到128*4,会触发flush
3、regionserver中所有的region中的所有的memstore的总大小达到javaHeap * 0.4 *0.95,会根据region中所有的memstore占用的内存大小排序,优先flush占用空间大的region
4、当写入特别频繁的时候,第三个触发条件可能会适当的延迟,延迟到regionserver中所有的region中的所有的memstore的总大小达到javaHeap * 0.4,会阻塞client的写入,会根据region中所有的memstore占用的内存大小排序,优先flush占用空间大的region,flush到占用总空间<=javaHeap * 0.4 *0.95的时候会停止flush,允许client写入
5、regionserver对应WAL日志文件数量达到32的时候,会flush
6、region距离上一次flush的时间超过一个小时,会自动flush
7、手动flush: flush ‘表名’
memStore级别: 某个memStore 达到 xxxx.flush.size = 128M
Region级别: region内部的所有memStore总和达到了 128M * 4 , 阻塞写
RegionServer级别: 公式 =》 堆内存 * 0.4 * 0.95 ,超过这个阈值,开始刷写, 由大到小依次刷写
刷写到什么时候: 小于 堆内存 * 0.4 * 0.95
HLog数量: 现在不用手动配置,上限 32个
定期刷写: 默认1小时,最后一次编辑时间
手动刷写: 执行命令
5、storeFile compact
原因: memstore每flush一次会生成一个storeFile,storeFile文件越来越多会影响查询性能,所以需要将storeFile文件合并
minor compact:
触发条件: 符合文件数>=3 的时候会自动合并
符合文件数: 判断当前文件是否需要合并, 当前文件大小 <= sum(小于当前文件大小的所有文件) * 比例,如果满足该条件,则代表当前文件需要合并
合并的过程: 单纯的将小文件合并成大文件,在合并的过程中不会删除无效数据【无效版本数据、标记删除数据】
合并结果: 会有多个大文件
major compact:
触发条件: 7天一次
合并过程: 在合并的过程中会删除无效数据【无效版本数据、标记删除数据】
合并结果: 合并之后,store中只有一个文件
为什么要合并: 每次刷写都生成一个新的 HFile ,时间久了很多小文件 小合并:相邻的几个HFile,合并成一个新的大的HFile,原先的小HFile删除掉 不会删除 被标记为删除、过期的数据 大合并:所有的HFile,合并成一个大的HFile 删除 被标记为删除、过期的数据 注意:大合并会影响正常使用
LSM Tree(Log-structured merge-tree)起源于1996年的一篇论文:The log-structured merge-tree (LSM-tree)。当时的背景是:为一张数据增长很快的历史数据表设计一种存储结构,使得它能够解决:在内存不足,磁盘随机IO太慢下的严重写入性能问题。
ck tree是一个有序的树状结构,数据的写入流转从C0 tree 内存开始,不断被合并到磁盘上的更大容量的Ck tree上。由于内存的读写速率都比外存要快非常多,因此数据写入的效率很高。并且数据从内存刷入磁盘时是预排序的,也就是说,LSM树将原本的随机写操作转化成了顺序写操作,写性能大幅提升。不过它牺牲了一部分读性能,因为读取时需要将内存中的数据和磁盘中的数据合并。
6、region split
原因: 表在创建的时候默认只有一个region,后续所有的读写请求,都会落在该region上,随着时间流逝,region中保存的数据越来越多,导致后续region所在的regionserver可能出现读写负载的不均衡,所以需要将region切分,分配到不同的regionserver,从而分担负载压力
切分规则:
0.9版本之前: 当region中某个store中storeFile总大小达到10G的时候,该region会切分为两个
0.9版本-2.0版本:
当region中某个store中storeFile总大小达到 [N == 0 || N>100 ? 10G: min(10G,2128MN^3) ]的时候,该region会切分为两个
N代表region所属表在当前regionserver上的region个数
2.0版本之后:
当region中某个store中storeFile总大小达到 [N == 1 ? 2 *128M : 10G ]的时候,该region会切分为两个
N代表region所属表在当前regionserver上的region个数
Hbase优化
1、预分区:
原因:Hbase在创建表的时候默认只有一个region,客户端如果读取/写入的并发量比较大,会造成regionserver负载压力,所以需要偶在创建表的时候多指定几个region,从而分担负载压力
如何预分区?
1、create ‘表名’,‘列簇名’,…,SPLITS=>[‘rowkey1’,‘rowkey2’,…]
2、create ‘表名’,‘列簇名’,…,{NUMREGIONS=>region个数,SPLITLGO=>‘HexStringSplit’}
3、create ‘表名’,‘列簇名’,SPLIT_FILE=>‘分区文件’
4、通过Api建表预分区
byte[][] splitKeys = {“rowkey”.getBytes(),…}
1、admin.createTable(table,splitKeys)
byte[] startRow;
byte[] stopRow;
numRegions指表的region个数
2、admin.createTable(table,startRow,stopRow,numRegions)
指定分区键:
建表语句,指定 splits => { 001|,002|,003|}
为什么给 | ,因为 rowkey是 按位比较,按照 ASC码, _比较小的值, |比较大的值注意:即使指定了分区键,后续还是会进行自动切分 直接按照10G切 -∞ ~ 001 ====》 -∞ ~ 中间rowkey , 中间rowkey ~ 001 001 ~ 002 002 ~ +∞
2、rowkey设计
原则:
1、唯一性原则: 两条数据的rowkey不能相同
2、长度原则: 一般保持在16字节以下
hbase rowkey不能太长,太长会导致rowkey占用过多的存储空间,client会缓存元数据,如果rowkey过长,而clint缓存空间不太足,就会导致缓存的元数据相对比较少
3、hash原则: 保证数据均衡分配在不同的region中
如果rowkey设计不合理可能会导致数据全部聚在一个region中,从而出现数据热点问题
如何解决数据热点问题?
1、在原来的rowkey基础上添加随机数
2、将rowkey反转
3、将原来rowkey hashcode值作为新rowkey
3、内存优化:
一般会给hbase节点分配16-48G的内存
4、基础优化
1、允许HDFS追加内容
Hbase写入数据到HDFS的时候是以追加的形式写入,HDFS默认就是允许的
2、调整datanode最大文件打开数
HBase读取数据与写入数据都需要打开HDFS文件,如果同一时间有大量的读写请求,就可能导致HDFS文件打开数不够造成请求需要排队的情况
3、调整HDFS数据写入的超时时间
Hbase写入数据的时候,如果网络不是太好,可能造成超时导致数据写入失败
4、调整HDFS数据写入效率
如果数据量相对比较大,此时可以将数据压缩之后写入HDFS,能够提高效率
5、调整RPC监听数
Hbase内部有一个线程池专门用来处理client的请求,如果同一时间client有大量的请求,此时肯能造成线程池中的线程不够用,从而出现请求排队的情况
6、调整HStore的文件大小
HStore文件的大小如果过小,可能导致频繁的split,如果过大,可能导致region里面存放大量数据,负载不均衡
7、调整Client缓存
client在获取元数据之后,会缓存在客户端,后续在获取元数据的时候可以直接从缓存中获取,如果缓存的大小比较小,会导致client只能缓存一部分元数据,后续在获取元数据的时候可能就不能从缓存中直接获取到,需要从regionserver中获取
8、flush、compact、split参数值调整
hbase.regionserver.global.memstore.size.lower.limit一般需要调整,调整为0.7/0.8
9、scan扫描的数据条数
scan是全表扫描,如果扫描的数据条数比较大会占用过多的内存空间
5、数据倾斜与数据热点
数据倾斜:存储位置、大小
热点:数据本身分布不均
Phoenix
1、shell使用
1、查询所有表: !tables
2、创建表
1、hbase有表[在phoenix中创建表与hbase的表建立映射关系]
1、通过create table建表与hbase的表建立映射关系
create table创建的表在删除的时候会同步删除hbase的表
create table创建的表可以增删改查数据
CREATE TABLE hbase表名(
字段名 类型 primary key, --映射hbase的rowkey
列簇名.列限定符 类型, --映射hbase表中column
...
)COLUM_ENCODED_BYTES=0
2、通过create view建视图与hbase的表建立映射关系
create view创建的视图在删除的时候不会删除hbase的表
create view创建的视图只能查询数据
CREATE VIEW hbase表名(
字段名 类型 primary key, --映射hbase的rowkey
列簇名.列限定符 类型, --映射hbase表中column
...
)COLUM_ENCODED_BYTES=0
注意: 如果列簇名与列限定符以及表名是小写需要用""括起来
2、hbase没有表[在phoenix中创建表的同时会在hbase中创建一个同名表]
单主键:
CREATE TABLE 表名(
字段名 字段类型 primary key,
...
)COLUM_ENCODED_BYTES=0
复合主键:
CREATE TABLE 表名(
字段名 字段类型,
...
CONSTRAINT 主键名 primary key(字段名,..)
)COLUM_ENCODED_BYTES=0
注意事项:
1、COLUM_ENCODED_BYTES=0 是指在创建hbase表的时候列限定符不进行编码
2、在创建表的时候phoenix会默认将小写转成大写[不管是字段还是表名],如果想要保持表名/字段小写需要将表名/字段名用""括起来
3、插入/修改数据: upsert into 表名(字段名,..) values(值,..) [如果表名/字段名是小写需要用""括起来]
4、查询: select .. from .. where .. [如果表名/字段名是小写需要用""括起来]
5、如果想要映射命名空间的表
1、在hbase/conf/hbase-site.xml中配置两个参数
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
2、在client端配置两个参数
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
3、重启hbase
4、创建一个schema,schema的名称必须与命名空间的名称一致
5、在phoenix中创建表与命名空间的表建立映射关系
CREATE TABLE schema名称.hbase表名(
字段名 类型 primary key, --映射hbase的rowkey
列簇名.列限定符 类型, --映射hbase表中column
...
)COLUM_ENCODED_BYTES=0
3、hbase二级索引
原因: hbase使用get查询的是可以通过rowkey+元数据知道数据处于哪个region,该region处于哪个regionserver,所以能够很快定位到数据。但是如果是根据value值来查询数据的时候使用的scan查询,scan是扫描全表,所以查询速度比较慢
1、全局二级索引
1、语法: create index 索引名 on schema名称.表名([列簇名.]字段名,…) [include([列簇名.]字段名,…)]
2、原理:
给某个/某几个字段建索引之后,会在hbase中新创建一个索引表,后续根据索引字段查询数据的时候,此时是从索引表中查询数据
新创建的索引表:
rowkey = 索引字段值1_索引字段值2_…_原来的rowkey
3、注意事项:
在查询数据的时候,查询条件中必须包含第一个索引字段,select与from中的查询字段必须在索引表中能够全部找到,不然就是全部扫描
2、本地二级索引
1、语法:create local index 索引名 on schema名称.表名([列簇名.]字段名,…)
2、原理: 给某个/某几个字段建索引之后,会在原表中插入对应的数据[数据的rowkey=_索引字段值1_索引字段值2…_原来的rowkey],后续在根据索引字段查询数据的时候会首先扫描索引对应的region获取原来的rowkey,再通过原来的rowkey查询原始数据
3、协处理器(二级索引基于协处理器)
协处理器相当于是一个触发器,会监控表,一旦client做出对应的操作,会触发对应的动作
步骤:
1、需要继承两个接口
2、重写一个对应的方法
3、重写一个对应的动作方法
4、打包,上传到hdfs
5、禁用表
6、加载协处理器
7、启动表Phoenix (或ES):基于协处理器(写之后) 全局索引: 索引表和 数据表 不在一个地方 create index 索引表名 ON 数据表名 (索引列) include(其他列) 索引表的id = 索引列_原始rowkey ,如果有include的列,也会在里面 本地索引: 索引数据 和 数据表 在同一个region CREATE LOCAL INDEX 索引表名 ON 数据表名 (索引列) include(其他列); 在原表中插入索引数据: 分区键_索引列_原始rowkey, 多加一列IDX : 1表示对应数据在, 0表示对应数据过期了 如果直接往HBase写数据,Phoenix获取不到 补救:往Phoenix再写一份 建议:写的时候通过Phoenix写
与hive集成
1、内部表
在hive创建表的时候会同步在hbase中创建表,如果hbase的表已经存在则报错
删除hive内部表的时候会同步删除Hbase的表
CREATE TABLE student_hive_2(id string,name string,age string)
STORED BY ‘org.apache.hadoop.hive.hbase.HBaseStorageHandler’
WITH SERDEPROPERTIES (“hbase.columns.mapping” = “:key,f1:name,f1:age”)
TBLPROPERTIES (“hbase.table.name” = “student_hbase”);
:key 指hbase的主键
hbase.columns.mapping: 指定hive的字段与hbase的column的映射
hbase.table.name: 指定创建的hbase的表名
2、外部表[与hbase的表建立映射关系]
在hive创建表的时候要求hbase的表必须已经存在,如果hbase的表不存在则报错
删除hive外部表的时候不会删除hbase的表
CREATE EXTERNAL TABLE student_hive_2(id string,name string,age string)
STORED BY ‘org.apache.hadoop.hive.hbase.HBaseStorageHandler’
WITH SERDEPROPERTIES (“hbase.columns.mapping” = “:key,f1:name,f1:age”)
TBLPROPERTIES (“hbase.table.name” = “student_hbase”);
:key 指hbase的主键
hbase.columns.mapping: 指定hive的字段与hbase的column的映射
hbase.table.name: 指定hbase的表名
八、Flink
编程模型
- source -> transform -> sink
keyBy:
两次hash:第一次hashcode,第二次murmurhash
然后 跟最大并行度 计算 =》 得到keyGroupbyId(键组对,一个键多个value)
计算公式:id * 下游并行度 / 最大并行度
【最大并行度 默认128M,env.setMaxParallelism(128)可修改】
intervalJoin:
底层 : connect+keyBy
两条流都存在一个Map类型 的状态 数据存放里边,key是ts,value是数据的集合
每条流的数据来,都遍历对方的Map状态
到达一定条件,会清理状态
slot与并行度的关系
env.readtextfile.flatmap.keyby.sum.print
同一个算子的子任务,不能存在于同一个slot中,
不同算子的子任务,可以存在于同一个slot中
slot可以共享,slotSharingGroup
JM/TM内存分配
设置依据:顶住洪峰就可以
百万日活:百万qps 洪峰 600/700万每秒
solt核数
最好与机器核数1:1,若计算逻辑简单 可以 1核:2slot
数仓
权限控制range/sentry
区别:
range:以资源为中心(为每个库/表 选择可以操作的用户)
sentry:以用户为中心(为每个用户 选择可操作的 库/表)
数仓分层
ODS:Operation Data Store 原始数据层
DWD:data warehouse datail 明细数据层
DWS:data warehouse service 服务数据层
DWT:data warehouse Topic 数据主题层
ADS:Application data store 数据应用层
flume
source
taildir:拉取数据 ,同时会将拉取的最后位置offset 持久化一份在磁盘上,下次拉取会先读offset 再去拉取offset后的数据
【注】kafka宕机重启会导致少量数据重复
Clickhouse
联机分析(OLAP)的列式数据库管理系统(DBMS)。能够使用SQL查询实时生成分析数据报告。它同样拥有优秀的数据存储能力。
优点:OLAP 查询快
缺点:join不友好
Hbase | Kudu | Clickhouse | |
---|---|---|---|
数据存储 | Zookeeper保存元数据,数据写入HDFS(非结构化数据) | master保存元数据,数据及副本存储在tserver(强类型数据) | Zookeeper保存元数据,数据存储在本地,且会压缩 |
查询 | 查询比较麻烦,Phoenix集成之后比较好点 | 查询比较麻烦,集成Impala之后表现优秀 | 高效的查询能力 |
数据读写 | 支持随机读写,删除。更新操作是插入一条新timestamp的数据 | 支持读写,删除,更新 | 支持读写,但不能删除和更新 |
维护 | 需要同时维护HDFS、Zookeeper和Hbase(甚至于Phoenix) | CDH版本维护简单,Apache版需要单独维护,额外还有Impala | 额外维护Zookeeper |
-
Hbase更适合非结构化的数据存储;
-
在既要求随机读写又要求实时更新的场景,Kudu+Impala可以很好的胜任,当然再结合CDH就更好了,瓶颈并不在Kudu,而在Impala的Apache部署,特别麻烦。详见 Apache集群安装Impala;
-
如果只要求静态数据的极速查询能力,Clickhouse则更好。
优点:
1,为了高效的使用CPU,数据不仅仅按列存储,同时还按向量进行处理;
2,数据压缩空间大,减少IO;处理单查询高吞吐量每台服务器每秒最多数十亿行;
3,索引非B树结构,不需要满足最左原则;只要过滤条件在索引列中包含即可;即使在使用的数据不在索引中,由于各种并行处理机制ClickHouse全表扫描的速度也很快;
4,写入速度非常快,50-200M/s,对于大量的数据更新非常适用。
缺点:
1,不支持事务,不支持真正的删除/更新;
2,不支持高并发,官方建议qps为100,可以通过修改配置文件增加连接数,但是在服务器足够好的情况下;
3,SQL满足日常使用80%以上的语法,join写法比较特殊;最新版已支持类似SQL的join,但性能不好;
4,尽量做1000条以上批量的写入,避免逐行insert或小批量的insert,update,delete操作,因为ClickHouse底层会不断的做异步的数据合并,会影响查询性能,这个在做实时数据写入的时候要尽量避开;
5,Clickhouse快是因为采用了并行处理机制,即使一个查询,也会用服务器一半的CPU去执行,所以ClickHouse不能支持高并发的使用场景,默认单查询使用CPU核数为服务器核数的一半,安装时会自动识别服务器核数,可以通过配置文件修改该参数。
全量数据导入:数据导入临时表 -> 导入完成后,将原表改名为tmp1 -> 将临时表改名为正式表 -> 删除原表
增量数据导入: 增量数据导入临时表 -> 将原数据除增量外的也导入临时表 -> 导入完成后,将原表改名为tmp1-> 将临时表改成正式表-> 删除原数据表
优化:
1,关闭虚拟内存,物理内存和虚拟内存的数据交换,会导致查询变慢。
2,为每一个账户添加join_use_nulls配置,左表中的一条记录在右表中不存在,右表的相应字段会返回该字段相应数据类型的默认值,而不是标准SQL中的Null值。
3,JOIN操作时一定要把数据量小的表放在右边,ClickHouse中无论是Left Join 、Right Join还是Inner Join永远都是拿着右表中的每一条记录到左表中查找该记录是否存在,所以右表必须是小表。
4,批量写入数据时,必须控制每个批次的数据中涉及到的分区的数量,在写入之前最好对需要导入的数据进行排序。无序的数据或者涉及的分区太多,会导致ClickHouse无法及时对新导入的数据进行合并,从而影响查询性能。
5,尽量减少JOIN时的左右表的数据量,必要时可以提前对某张表进行聚合操作,减少数据条数。有些时候,先GROUP BY再JOIN比先JOIN再GROUP BY查询时间更短。
6,ClickHouse的分布式表性能性价比不如物理表高,建表分区字段值不宜过多,防止数据导入过程磁盘可能会被打满。
7,CPU一般在50%左右会出现查询波动,达到70%会出现大范围的查询超时,CPU是最关键的指标,要非常关注。
性能情况
1,单个查询吞吐量:如果数据被放置在page cache中,则一个不太复杂的查询在单个服务器上大约能够以2-10GB/s(未压缩)的速度进行处理(对于简单的查询,速度可以达到30GB/s)。如果数据没有在page cache中的话,那么速度将取决于你的磁盘系统和数据的压缩率。例如,如果一个磁盘允许以400MB/s的速度读取数据,并且数据压缩率是3,则数据的处理速度为1.2GB/s。这意味着,如果你是在提取一个10字节的列,那么它的处理速度大约是1-2亿行每秒。对于分布式处理,处理速度几乎是线性扩展的,但这受限于聚合或排序的结果不是那么大的情况下。
2,处理短查询的延时时间:数据被page cache缓存的情况下,它的延迟应该小于50毫秒(最佳情况下应该小于10毫秒)。 否则,延迟取决于数据的查找次数。延迟可以通过以下公式计算得知: 查找时间(10 ms) * 查询的列的数量 * 查询的数据块的数量。
3,处理大量短查询:ClickHouse可以在单个服务器上每秒处理数百个查询(在最佳的情况下最多可以处理数千个)。但是由于这不适用于分析型场景。建议每秒最多查询100次。
4,数据写入性能:建议每次写入不少于1000行的批量写入,或每秒不超过一个写入请求。当使用tab-separated格式将一份数据写入到MergeTree表中时,写入速度大约为50到200MB/s。如果您写入的数据每行为1Kb,那么写入的速度为50,000到200,000行每秒。如果您的行更小,那么写入速度将更高。为了提高写入性能,您可以使用多个INSERT进行并行写入,这将带来线性的性能提升。
count: 千万级别,500毫秒,1亿 800毫秒 2亿 900毫秒 3亿 1.1秒
group: 百万级别 200毫米,千万 1秒,1亿 10秒,2亿 20秒,3亿 30秒
join:千万-10万 600 毫秒, 千万 -百万:10秒,千万-千万 150秒ClickHouse并非无所不能,查询语句需要不断的调优,可能与查询条件有关,不同的查询条件表是左join还是右join也是很有讲究的。
其他补充:
1,MySQL单条SQL是单线程的,只能跑满一个core,ClickHouse相反,有多少CPU,吃多少资源,所以飞快;
2,ClickHouse不支持事务,不存在隔离级别。ClickHouse的定位是分析性数据库,而不是严格的关系型数据库。
3,IO方面,MySQL是行存储,ClickHouse是列存储,后者在count()这类操作天然有优势,同时,在IO方面,MySQL需要大量随机IO,ClickHouse基本是顺序IO。
有人可能觉得上面的数据导入的时候,数据肯定缓存在内存里了,这个的确,但是ClickHouse基本上是顺序IO。对IO基本没有太高要求,当然,磁盘越快,上层处理越快,但是99%的情况是,CPU先跑满了(数据库里太少见了,大多数都是IO不够用)。