过滤器request参数中payload丢失问题

Java Spring 过滤器 request 参数中 payload 丢失问题

省流版:

org.springframework.boot:spring-boot:2.1.8.RELEASE && org.springframework:spring-web:5.1.9.RELEASE​版本中的org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal​方法读取HttpServletRequest.InputStream​流导致后续过滤器和拦截器无法再次读取InputStream​。

起因:

最近有一个需要在前端对接口数据进行加密,再后端接口进行解密的需求,所以打算用rsa,前端请求数据公钥加密后,后端在拦截器中解密,而当前端请求到达后端自定义拦截器处时,读取出InputStream​为空,只能通过request.getParameter​取出对应的参数值,但是无法取出body​中的数据,也就是即无法获取json​数据

排查过程:

  1. (此步方向错误)首先,对上一级的过滤器进行排查,初步怀疑是上一级过滤器中的request​包装类代码逻辑错误,在包装过程中没有正确存储InputStream​所以搭建了一个最小环境测试包装类(与发生问题的环境版本不同),发现在其他环境中包装类能正常重读InputStream​数据。
  2. 再者,对项目中其他的过滤器进行屏蔽,发现并无效果,依然无法读取InputStream​数据。
  3. 通过IDEA Debugger Frames​逐个方法反推,逐步定位发现如果发起的是POST请求(GET请求也无法传递body​)在org.springframework.web.filter.OncePerRequestFilter​的子类中org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal​方法执行了request.getParameter(this.methodParam);​导致InputStream​数据丢失。

解决方案:

  1. 屏蔽HiddenHttpMethodFilter​过滤器,使其不能读取InputStream​,但是可能存在一些安全性的问题

       a. 屏蔽方法有两种,第一种是通过代码配置,参考链接、参考代码如下。
       

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    public class FilterConfig {
    @Bean
    public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
    }
    }

       b. 通过配置关闭,在Spring boot​2.1.0版本后可以通过配置关闭该过滤器,在2.2​版本后默认关闭,屏蔽配置:​spring.mvc.hiddenmethod.filter.enabled: false​。

  2. 通过转换为ServletServerHttpRequest​使用getBody​方法获取body​数据,代码如下。

    1
    new String(StreamUtils.copyToByteArray(new ServletServerHttpRequest((HttpServletRequest) servletRequest).getBody()))
  3. 重新实现HiddenHttpMethodFilter​中的doFilterInternal​方法,在读取之前将request​进行包装(未实践)

  4. HiddenHttpMethodFilter​调用之前将request​进行包装(未实践)

参考:

  1. HiddenHttpMethodFilter consumes input stream of POST request
  2. ServletServerHttpRequest.getBody
  3. Disable auto-configuration of HiddenHttpMethodFilter by default
  4. Why is HttpServletRequest inputstream empty?

Hive 练习题解答

题目

在 Github 上面找了一些题目来练习
https://github.com/zzyb/Hive_interview-question

解答

answer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
 -- 第一道面试题
-- 编写Hive的HQL语句求出连续三天登陆的用户id
select distinct user_id
from (
select user_id,
lag(login_date,1) over (partition by user_id order by login_date) prev,
login_date now,
lead(login_date,1) over (partition by user_id order by login_date) next
from login_log) t where datediff(now,prev) = 1 and datediff(next,now)=1;

-- 第一道面试题
-- 编写Hive的HQL语句求出每 个店铺的当月销售额和累计到当月的总销售额

select name,mon,monMoney,accMonMoney
from(
select name,mon,sum(monmoney) over (partition by name,mon) monMoney,
sum(monmoney) over (partition by name order by mon) accMonMoney,
row_number() over (partition by name,mon order by mon) top
from fangwen2)t where top=1;

-- 第二道面试题
-- 编写Hive的HQL语句求出每个用户截止到每月为止的最大单月访问次数和累计到该月的总访问次数
select usr,mon,num,col1 monPv,col2 maxMonPv,col3 accMonPv from(
select usr,mon,num,sum(num) over (partition by usr,mon) col1,
max(num) over (partition by usr,mon) col2,
sum(num) over (partition by usr order by mon) col3,
row_number() over (partition by usr,mon order by mon) col4 from fangwen) t where col4=1;

select usr,mon,maxPv,totalPv from(
select usr,mon,num,
max(num) over (partition by usr,mon) maxPV,
sum(num) over (partition by usr order by mon) totalPv,
row_number() over (partition by usr,mon order by mon) top
from fangwen) t where top=1;


-- 第三道面试题
-- 编写Hive的HQL语句按照day和mac分组,求出每组的销量累计总和,追加到每条记录的后面
select day,mac,color,num,
sum(num) over (partition by day,mac)
from mac;


-- TopN
-- 第六道面试题

-- 1、求出每一年的最高温度(年份,最高温度)
select t.c_year,max(t.c_temp) from (
select year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) c_year,substr(line,9,2) c_temp from wendu
) t group by t.c_year;

select dt,year(dt),temp
from(
select concat_ws('-',substring(line,0,4),substring(line,5,2),substring(line,7,2)) dt,
substring(line,9,2) temp,
row_number() over (partition by substring(line,0,4) order by substring(line,9,2) DESC) top
from wendu) subT where top=1;

-- 2、求出每一年的最高温度是那一天(日期, 最高温度)

select dt,temp
from(
select concat_ws('-',substring(line,0,4),substring(line,5,2),substring(line,7,2)) dt,
substring(line,9,2) temp,
row_number() over (partition by substring(line,0,4) order by substring(line,9,2) DESC) top
from wendu) subT where top=1;

select t.c_year,t.c_date,t.c_max from
(select
year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) c_year,
from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd')) c_date,
row_number() over(partition by year(from_unixtime(unix_timestamp(substr(line,0,8),'yyyyMMdd'))) order by substr(line,9,2) DESC) as c_rank,
substr(line,9,2) c_max
from wendu) t where t.c_rank = 1;

-- 第四道面试题
-- 求出所有数学课程成绩 大于 语文课程成绩的学生的学号
-- 方法1 case when
select * from(
select sid,
max(case course when 'yuwen' then course end) yuwen,
max(case course when 'yuwen' then score end) yuwenScore,
max(case course when 'shuxue' then course end) shuxue,
max(case course when 'shuxue' then score end) shuxueScore,
max(case course when 'yingyu' then course end) yingyu,
max(case course when 'yingyu' then score end) yingyuScore
from chengji group by sid) t where t.shuxueScore > t.yuwenScore;

-- 方法2 join
select sid,t1score yuwen,t2score shuxue from(
select * from (
select sid,course,score t1score
from chengji
where course = 'yuwen'
) t1
join (
select sid,course,score t2score from chengji where course = 'shuxue'
) t2 on t1.sid = t2.sid) t3 where t3.t2score > t3.t1score;

-- 第五道面试题
-- id_course.id id_course.course
-- 1 a
-- 1 b
-- 1 c
-- 1 e
-- 2 a
-- 2 c
-- 2 d
-- 2 f
-- 3 a
-- 3 b
-- 3 c
-- 3 e
-- 转换为
-- id a b b c d e f
-- 1 1 1 1 1 0 1 0
-- 2 1 0 0 1 1 0 1
-- 3 1 1 1 1 0 1 0

select id,
max(case course when 'a' then 1 ELSE 0 end) a,
max(case course when 'b' then 1 ELSE 0 end) b,
max(case course when 'b' then 1 ELSE 0 end) b,
max(case course when 'c' then 1 ELSE 0 end) c,
max(case course when 'd' then 1 ELSE 0 end) d,
max(case course when 'e' then 1 ELSE 0 end) e,
max(case course when 'f' then 1 ELSE 0 end) f
from id_course group by id;

-- 第七道面试题
-- 1,huangbo,45,a-c-d-f
-- 2,xuzheng,36,b-c-d-e
-- 3,huanglei,41,c-d-e
-- 4,liushishi,22,a-d-e
-- 5,liudehua,39,e-f-d
-- 6,liuyifei,35,a-d-e
-- 求出每种爱好中,年龄最大的人
select id,name,maxAge,favor
from (
select id, name, max(age) over (partition by favor) maxAge, favor,
row_number() over (partition by favor order by age DESC) top
from aihao lateral VIEW explode(split(favors, '-')) t1 as favor
)t2 where top=1;
-- 列出每个爱好年龄最大的两个人,并且列出名字。
select id,name,maxAge,favor
from (
select id, name, max(age) over (partition by favor) maxAge, favor,
row_number() over (partition by favor order by age DESC) top
from aihao lateral VIEW explode(split(favors, '-')) t1 as favor
)t2 where top<=2;

-- 第十道面试题
-- 日期,产品id,产品当日收入,产品当日成本
-- 2018-03-01,a,3000,2500
-- 2018-03-01,b,4000,3200
-- 2018-03-01,c,3200,2400
-- 2018-03-01,d,3000,2500
-- 2018-03-02,a,3000,2500
-- 2018-03-02,b,1500,800
-- 2018-03-02,c,2600,1800
-- 2018-03-02,d,2400,1000
-- 2018-03-03,a,3100,2400
-- 2018-03-03,b,2500,2100
-- 2018-03-03,c,4000,1200
-- 2018-03-03,d,2500,1900
-- 2018-03-04,a,2800,2400
-- 2018-03-04,b,3200,2700
-- 2018-03-04,c,2900,2200
-- 2018-03-04,d,2700,2500
-- 2018-03-05,a,2700,1000
-- 2018-03-05,b,1800,200
-- 2018-03-05,c,5600,2200
-- 2018-03-05,d,1200,1000
-- 2018-03-06,a,2900,2500
-- 2018-03-06,b,4500,2500
-- 2018-03-06,c,6700,2300
-- 2018-03-06,d,7500,5000
-- 2018-04-01,a,3000,2500
-- 2018-04-01,b,4000,3200
-- 2018-04-01,c,3200,2400
-- 2018-04-01,d,3000,2500
-- 2018-04-02,a,3000,2500
-- 2018-04-02,b,1500,800
-- 2018-04-02,c,4600,1800
-- 2018-04-02,d,2400,1000
-- 2018-04-03,a,6100,2400
-- 2018-04-03,b,4500,2100
-- 2018-04-03,c,6000,1200
-- 2018-04-03,d,3500,1900
-- 2018-04-04,a,2800,2400
-- 2018-04-04,b,3200,2700
-- 2018-04-04,c,2900,2200
-- 2018-04-04,d,2700,2500
-- 2018-04-05,a,4700,1000
-- 2018-04-05,b,3800,200
-- 2018-04-05,c,5600,2200
-- 2018-04-05,d,5200,1000
-- 2018-04-06,a,2900,2500
-- 2018-04-06,b,4500,2500
-- 2018-04-06,c,6700,2300
-- 2018-04-06,d,7500,5000

-- 源数据3月份与4月份日利润相同,添加一条数据使其不同。
insert into goods values ('2018-04-07','d','5000','3000');

-- 1. 输出每个产品,在2018年期间,每个月的净利润,日均成本。
select d_month,profit,avgCost from (
select concat(year(dt),'-',month(dt)) d_month,name,income,cost,
(sum(income) over(partition by concat(year(dt),'-',month(dt))) -
sum(cost) over(partition by concat(year(dt),'-',month(dt)))) profit,
avg(cost) over(partition by concat(year(dt),'-',month(dt))) avgCost,
row_number() over (partition by concat(year(dt),'-',month(dt)) order by name) top
from goods where year(dt) = '2018') t_month where t_month.top=1;

-- 2. 输出每个产品,在2018年3月中每一天与上一天相比,成本的变化。
select
dt,cost,
(cost - LAG(cost,1,cost) over(partition by concat(year(dt),'-',month(dt)) order by dt))
from goods where concat(year(dt),'-',month(dt)) = '2018-3';

-- 3. 输出2018年4月,有多少个产品总收入大于22000元,必须用一句SQL语句实现,且不允许使用关联表查询、子查询。
select concat(year(dt),'-',month(dt)),name,sum(income) sumIncome
from goods where concat(year(dt),'-',month(dt)) = '2018-4'
group by name,concat(year(dt),'-',month(dt)) having sumIncome>22000;

-- 4. 输出2018年4月,总收入最高的那个产品,每日的收入,成本,过程使用over()函数。
select g.dt,maxT.name,maxT.maxIncome,g.income,g.cost from(
select name,totalIncome,max(totalIncome) over() maxIncome from(
select
dt,name,income,cost,
sum(income) over (partition by name) totalIncome
from goods where concat(year(dt),'-',month(dt)) = '2018-4') t limit 1
) maxT join goods g on maxT.name=g.name where concat(year(dt),'-',month(dt)) = '2018-4';



Spark SQL 读写操作

使用方法

  • spark-shell

  • spark-sql

  • scala code

数据源

  • CSV

  • JSON

  • Parquet

  • ORC

  • JDBC/ODBC connections

  • Plain-text files

读取示例

1
2
3
4
5
6
7
8
9
10
// 格式
DataFrameReader.format(...).option("key", "value").schema(...).load()

// 示例
spark.read.format("csv")
.option("mode", "FAILFAST") // 读取模式
.option("inferSchema", "true") // 是否自动推断 schema
.option("path", "path/to/file(s)") // 文件路径
.schema(someSchema) // 使用预定义的 schema
.load()
读模式 描述
permissive 当遇到损坏的记录时,将其所有字段设置为 null,并将所有损坏的记录放在名为 _corruption t_record 的字符串列中
dropMalformed 删除格式不正确的行
failFast 遇到格式不正确的数据时立即失败

写入示例

1
2
3
4
5
6
7
8
9
// 格式
DataFrameWriter.format(...).option(...).partitionBy(...).bucketBy(...).sortBy(...).save()

//示例
dataframe.write.format("csv")
.option("mode", "OVERWRITE") //写模式
.option("dateFormat", "yyyy-MM-dd") //日期格式
.option("path", "path/to/file(s)")
.save()
Scala/Java 描述
SaveMode.ErrorIfExists 如果给定的路径已经存在文件,则抛出异常,这是写数据默认的模式
SaveMode.Append 数据以追加的方式写入
SaveMode.Overwrite 数据以覆盖的方式写入
SaveMode.Ignore 如果给定的路径已经存在文件,则不做任何操作

常用读写示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 简写法
spark.read.格式("路径")
spark.read.json("/usr/file/json/emp.json")

// 读取csv
spark.read.format("csv")
.option("header", "false") // 文件中的第一行是否为列的名称
.option("mode", "FAILFAST") // 是否快速失败
.option("inferSchema", "true") // 是否自动推断 schema
.load("/usr/file/csv/dept.csv")
.show()
// 写入csv,指定分隔符\t
df.write.format("csv").mode("overwrite").option("sep", "\t").save("/tmp/csv/dept2")

// json 读
spark.read.format("json").option("mode", "FAILFAST").load("/usr/file/json/dept.json").show(5)

// json 写
df.write.format("json").mode("overwrite").save("/tmp/spark/json/dept")

// Parquet 读
spark.read.format("parquet").load("/usr/file/parquet/dept.parquet").show(5)

// Parquet 写
df.write.format("parquet").mode("overwrite").save("/tmp/spark/parquet/dept")

// 数据库读
spark.read
.format("jdbc")
.option("driver", "com.mysql.jdbc.Driver") //驱动
.option("url", "jdbc:mysql://127.0.0.1:3306/mysql") //数据库地址
.option("dbtable", "help_keyword") //表名
.option("user", "root").option("password","root").load().show(10)

// 数据库写
val df = spark.read.format("json").load("/usr/file/json/emp.json")
df.write
.format("jdbc")
.option("url", "jdbc:mysql://127.0.0.1:3306/mysql")
.option("user", "root").option("password", "root")
.option("dbtable", "emp")
.save()

Hbase Shell 常用操作

help

1
help 'create'

hbase shell 操作

HBase是没有schema的,就是在创建表的时候不需要指定表中有哪些列,只需要指定有多少个列蔟

1
2
# 创建订单表,表名为ORDER_INFO,该表有一个列蔟为C1
create "ORDER_INFO","C1"
1
2
# 查看表
list
1
2
3
4
5
# 删除表
# 1.disable "TableName"
# 2.drop "TableName"
disable "ORDER_INFO"
drop "ORDER_INFO"

增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 添加一条数据
# put '表名','ROWKEY','列蔟名:列名','值'
# 数据示例
# ID STATUS PAY_MONEY PAYWAY USER_ID OPERATION_DATE CATEGORY
# 000001 已提交 4070 1 4944191 2020-04-25 手机
put "ORDER_INFO", "000001", "C1:STATUS", "已提交"
put "ORDER_INFO", "000001", "C1:PAY_MONEY", 4070
put "ORDER_INFO", "000001", "C1:PAYWAY", 1
put "ORDER_INFO", "000001", "C1:USER_ID", "4944191"
put "ORDER_INFO", "000001", "C1:OPERATION_DATE", "2020-04-25"
put "ORDER_INFO", "000001", "C1:CATEGORY", "手机"

# 查询数据
# get 查询一行数据
# get '表名',‘ROWKEY’
get 'ORDER_INFO','000001'

# 正确显示中文
get "ORDER_INFO", "000001", {FORMATTER => 'toString'}

# 显示多版本
get 'cx_table_stu01','20200001',{COLUMNS=>'cf1',VERSIONS=>5}
# 如果只能显示一个版本,查看表结构的version是不是为1
desc 'cx_table_stu01'

# 除了列(COLUMNS)修饰词外HBase还支持
# Limit(限制查询结果行数)
# STARTROW (ROWKEY起始行。会先根据这个key定位到region,再向后扫描)
# STOPROW(结束行)
# TIMERANGE(限定时间戳范围)
# VERSIONS(版本数)
# FILTER(按条件过滤行)

# 更新数据
# put '表名','ROWKEY','列蔟名:列名','更改值'
# 将订单ID为000001的状态,更改为「已付款」
put "ORDER_INFO","000001", "C1:STATUS", "已付款"

# 删除数据
# 删除列
# 如果有多个版本只会删除当前版本
# delete "表名", "ROWKEY", "列蔟名:列名"
# 将订单ID为000001的状态列删除。
delete "ORDER_INFO", "000001", "C1:STATUS"

# 删除行
# deleteall '表名','ROWKEY'
# 将订单ID为000001的信息全部删除(删除所有的列)
deleteall "ORDER_INFO", "000001"

# 增加/删除列族
# 增加
# alter '表名', '列族名'
alter 'Student', 'teacherInfo'

# 删除
# alter '表名', {NAME => '列族名', METHOD => 'delete'}
alter 'Student', {NAME => 'teacherInfo', METHOD => 'delete'}

# 修改表
# alter '表名',{name=>"列族",要修改的内容=>xxx}
alter 'PERSON3',{NAME=>'cf1', VERSIONS=>5}

删除数据的时候,其实 HBase 不是真的直接把数据删除掉,而是给某个列设置一个标志,然后查询数据的时候,有这个标志的数据,就不显示出来
什么时候真正的删除数据呢?

  • 后台进程,专门来执行删除数据的操作

执行 delete 的时候

  • 如果表中的某个列有对一个的几次修改,它会删除最近的一次修改
  • 默认是保存 1 个保存的时间戳
  • 有一个 version 属性

计数器

数据量小/测试环境中计数

1
2
3
# 统计表行数
# count "表名"
count "ORDER_INFO"

count 执行效率非常低,适用于百万级以下的小表 RowCount 统计

数据量大/生成环境中计数

利用 hbase.RowCounter 包执行 MR 任务

  • 启动yarn集群
  • 启动mr-historyserver
1
2
# 在 shell 中执行
hbase org.apache.hadoop.hbase.mapreduce.RowCounter '表名'

扫描操作

scan操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 全表扫描:scan "表名"(慎用,效率很低)
# 查询订单所有数据
scan "ORDER_INFO", {FORMATTER => 'toString'}

# 只查询列族”cf1”
scan 'cx_table_stu01',{COLUMNS=>'cf1'}
# 只查询列族”cf1”下的”name”信息
scan 'cx_table_stu01',{COLUMNS=>'cf1:name'}

# 限定只显示多少条: scan "表名", {LIMIT => XXX}
# 查询订单数据(只显示3条)
scan "ORDER_INFO", {FORMATTER => 'toString', LIMIT => 3}

# 指定查询某几个列: scan "表名", {LIMIT => XXX, COLUMNS => []}
# 只查询订单状态以及支付方式,并且只展示3条数据
scan "ORDER_INFO", {FORMATTER => 'toString', LIMIT => 3, COLUMNS => ['C1:STATUS', 'C1:PAYWAY']}

# 根据ROWKEY来查询:scan "表名", {LIMIT => XXX, COLUMNS => [], ROWPREFIXFILTER => 'ROWKEY'}
# 使用scan来根据rowkey查询数据,也是查询指定列的数据
scan "ORDER_INFO", {ROWPREFIXFILTER => '02602f66-adc7-40d4-8485-76b5632b5b53',FORMATTER => 'toString', LIMIT => 3, COLUMNS => ['C1:STATUS', 'C1:PAYWAY']}

过滤器

其实在hbase shell中,执行的ruby脚本,背后还是调用hbase提供的Java API

官方文档:https://hbase.apache.org/devapidocs/index.html

使用方法

1
2
3
4
5
# 查看内置过滤器
show_filters

# 示例
scan '表名',{Filter => "过滤器(比较运算符, '比较器表达式')"}
1
2
3
# 使用 RowFilter 查询指定订单ID的数据
# 查询订单的ID为:02602f66-adc7-40d4-8485-76b5632b5b53、订单状态以及支付方式
scan "ORDER_INFO", {FILTER => "RowFilter(=,'binary:02602f66-adc7-40d4-8485-76b5632b5b53')", COLUMNS => ['C1:STATUS', 'C1:PAYWAY'], FORMATTER => 'toString'}

RowFilter

通过上图,可以分析得到,RowFilter过滤器接受两个参数,

  • op——比较运算符

  • rowComparator——比较器

所以构建该Filter的时候,只需要传入两个参数即可

1
2
3
4
5
# 查询状态为「已付款」的订单
scan "ORDER_INFO",{FILTER => "SingleColumnValueFilter('C1', 'STATUS', = , 'binary:已付款')",COLUMNS => ['C1:STATUS','C1:PAYWAY'],FORMATTER => 'toString'}

# 查询支付方式为1,且金额大于3000的订单
scan "ORDER_INFO",{FILTER => "SingleColumnValueFilter('C1', 'PAYWAY', = , 'binary:1') AND SingleColumnValueFilter('C1', 'PAY_MONEY', > , 'binary:3000')",COLUMNS => ['C1:STATUS','C1:PAYWAY','C1:PAY_MONEY'],FORMATTER => 'toString'}

HBase shell中比较默认都是字符串比较,所以如果是比较数值类型的,会出现不准确的情况

例如:在字符串比较中4000是比100000大的

rowkey 过滤器

RowFilter 实现行键字符串的比较和过滤
PrefixFilter rowkey前缀过滤器
KeyOnlyFilter 只对单元格的键进行过滤和显示,不显示值
FirstKeyOnlyFilter 只扫描显示相同键的第一个单元格,其键值对会显示出来
InclusiveStopFilter 替代 ENDROW 返回终止条件行

列过滤器

FamilyFilter 列簇过滤器
QualifierFilter 列标识过滤器,只显示对应列名的数据
ColumnPrefixFilter 对列名称的前缀进行过滤
MultipleColumnPrefixFilter 可以指定多个前缀对列名称过滤
ColumnRangeFilter 过滤列名称的范围

值过滤器

ValueFilter 值过滤器,找到符合值条件的键值对
SingleColumnValueFilter 在指定的列蔟和列中进行比较的值过滤器
SingleColumnValueExcludeFilter 排除匹配成功的值

其他过滤器

ColumnPaginationFilter 对一行的所有列分页,只返回 [offset,offset+limit] 范围内的列
PageFilter 对显示结果按行进行分页显示
TimestampsFilter 时间戳过滤,支持等值,可以设置多个时间戳
ColumnCountGetFilter 限制每个逻辑行返回键值对的个数,在 get 方法中使用
DependentColumnFilter 允许用户指定一个参考列或引用列来过滤其他列的过滤器

比较器

比较器 描述
BinaryComparator 匹配完整字节数组
BinaryPrefixComparator 匹配字节数组前缀
BitComparator 匹配比特位
NullComparator 匹配空值
RegexStringComparator 匹配正则表达式
SubstringComparator 匹配子字符串

比较器表达式

比较器 表达式语言缩写
BinaryComparator binary:值
BinaryPrefixComparator binaryprefix:值
BitComparator bit:值
NullComparator null
RegexStringComparator regexstring:正则表达式
SubstringComparator substring:值

累加器(INCR)

incr可以实现对某个单元格的值进行原子性计数。语法如下:

incr ‘表名’,’rowkey’,’列蔟:列名’,累加值(默认累加1)

如果某一列要实现计数功能,必须要使用incr来创建对应的列

使用put创建的列是不能实现累加的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 通过 scan/get 无法直接查看cnt
hbase(main):004:0> scan "NEWS_VISIT_CNT", {FORMATTER => 'toString', LIMIT => 3}
ROW COLUMN+CELL
0000000001_00:00-01:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.890, value=

0000000001_00:00-01:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.153, value=00:00-01:00
0000000002_01:00-02:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.907, value=

0000000002_01:00-02:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.165, value=01:00-02:00
0000000003_02:00-03:00 column=C1:CNT, timestamp=2021-11-19T23:12:42.912, value={
0000000003_02:00-03:00 column=C1:TIME_RANGE, timestamp=2021-11-19T23:12:43.171, value=02:00-03:00
3 row(s)
Took 0.1625 seconds
# 需要使用 get_counter
# get_counter '表','ROWKEY',"列蔟名:列名"
get_counter 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'
1
2
3
# 对0000000020新闻01:00 - 02:00访问计数+1
# incr '表','ROWKEY',"列蔟名:列名"
incr 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'

实用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 显示服务器状态
status

# 显示HBase当前用户
whoami

# 查看表结构
describe 'TableName'

# 查看表是否存在
exists 'TableName'

# 查看表是否禁用/启用
is_enabled 'TableName'
is_disabled 'TableName'

# 新增列蔟
alter 'TableName', 'C3'
# 删除列蔟
alter 'TableName', 'delete' => 'C3'

# 清空表数据
truncate 'TableName'

执行 hbase shell 脚本

1
2
# hbase shell 'script file path'
hbase shell ~/ORDER_INFO.txt

tips

web默认端口16010

hive HQL 常用操作

资料

https://blog.csdn.net/u013411339/article/details/120795941

远程连接hive

https://blog.csdn.net/qq_44065303/article/details/105820435

服务端要开启 hiveserver2

建表方式

1
2
3
4
5
6
7
8
9
10
11
12
-- 1.正常建表
-- 建表后无数据
create table tableName(col1 int,col2 int);

-- 2.查询建表 as
-- 建表后有数据,数据为查询结果
-- 并不会带原表的分区(分区丢失),包扣一些字段的约束等
create table tableName as select * from tableA;

-- 3.结构建表 like
-- 建表后无数据,表结构从 tableA 中复制
create table tableName like tableA;

函数查询

1
2
3
4
5
6
7
8
9
10
11
12
-- 查看内置函数
SHOW FUNCTIONS;

-- 查看month相关的函数
SHOW FUNCTIONS LIKE '*month*';

-- 查看函数用法
DESC FUNCTION function_name;

-- 查看 add_months 函数的详细说明并举例
DESC FUNCTION EXTENDED add_months;

CTE

1
2
3
4
-- 公用表表达式
-- 创建一个临时结果集
with tableName as (select * from tableB)
select * from tableName;

时间转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
语法: unix_timestamp()
返回值: bigint
说明: 获得当前时区的UNIX时间戳
hive> select unix_timestamp() from tableName;
1616906976


语法: from_unixtime(bigint unixtime[, string format])
返回值: string
说明: 转化UNIX时间戳(从1970-01-01 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式
hive> select from_unixtime(1616906976,'yyyyMMdd') from tableName;
20210328

语法: unix_timestamp(string date)
返回值: bigint
说明: 转换格式为"yyyy-MM-dd HH:mm:ss"的日期到UNIX时间戳。如果转化失败,则返回0
hive> select unix_timestamp('2021-03-08 14:21:15') from tableName;
1615184475

语法: unix_timestamp(string date, string pattern)
返回值: bigint
说明: 转换pattern格式的日期到UNIX时间戳。如果转化失败,则返回0
hive> select unix_timestamp('2021-03-08 14:21:15','yyyyMMdd HH:mm:ss') from tableName;
1615184475

语法: to_date(string timestamp)
返回值: string
说明: 返回日期时间字段中的日期部分。
hive> select to_date('2021-03-28 14:03:01') from tableName;
2021-03-28

语法: year(string date)
返回值: int
说明: 返回日期中的年。
hive> select year('2021-03-28 10:03:01') from tableName;
2021
hive> select year('2021-03-28') from tableName;
2021

日期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
语法: datediff(string enddate, string startdate)
返回值: int
说明: 返回结束日期减去开始日期的天数。
hive> select datediff('2020-12-08','2012-05-09') from tableName;
213

语法: date_add(string startdate, int days)
返回值: string
说明: 返回开始日期startdate增加days天后的日期。
hive> select date_add('2020-12-08',10) from tableName;
2020-12-18

语法: date_sub (string startdate, int days)
返回值: string
说明: 返回开始日期startdate减少days天后的日期。
hive> select date_sub('2020-12-08',10) from tableName;
2020-11-28

窗口函数

窗口聚合函数

sum

1
2
3
4
5
-- 共4种用法
sum(...) over() -- 相当于对全表进行求和
sum(...) over(order by ...) -- 连续累积求和,排序,不分组
sum(...) over(partition by ...) -- 同组内所行求和,只分组不排序
sum(...) over(partition by ... order by ...) -- 在每个分组内,连续累积求和,分组+排序

partition by

partition by

order by

order by

窗口表达式

窗口表达式提供了一种控制行范围的能力如往前,往后n行

可以写多个列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 默认从第一行到当前行
sum(...) over(partition by ... order by ...)
sum(...) over(partition by ... order by ... , ...) -- 按多列分区
sum(...) over(partition by ... order by ... rows between unbounded preceding and current row)

-- 向前3行至当前行
sum(...) over(partition by ... order by ... rows between 3 preceding and current row)

-- 向前3行,向后1行
sum(...) over(partition by ... order by ... rows between 3 preceding and 1 following)

-- 当前行到最后一行
sum(...) over(partition by ... order by ... rows between current row and unbounded following)

-- 第一行至最后一行,即分组内所有行
sum(...) over(partition by ... order by ... rows between unbounded preceding and unbounded following)

窗口排序函数

1
2
3
4
-- row_number家族,在每个分组中,为每行分配一个从1开始的唯一序号
row_number -- 不考虑重复
rank -- 考虑重复,挤占后续位置
dense_rank -- 考虑重复,不挤占后续位置
1
2
3
4
5
6
7
-- 窗口排序函数 ntile
将每个分组内的数据分为指定的若干个桶内,并且为每一个桶分配一个桶编号

select cookieid,createtime,pv
ntile(3) over(partition by cookieid order by createtime)
from website_pv_info
order by cookieid,createtime;

抽样函数

随机抽样 random

随机,但是速度慢

1
2
select * from website_pv_info order by rand() limit 2;
select * from website_pv_info distribute by rand() sort by rand() limit 2;

block 抽样

速度快,但是不随机

1
2
3
4
5
6
-- 按行数抽样
select * from website_pv_info tablesample (1 rows);
-- 根据数据大小百分比抽样
select * from website_pv_info tablesample (50 percent);
-- 根据数据大小抽样,支持b,k,m,g
select * from website_pv_info tablesample (1B);

bucket table 基于分桶表抽样

速度快+随机

tablesample(BUCKET x OUT OF y [ON colname])
tablesample

1
2
3
-- 根据整行数据进行抽样
select * from website_pv_info tablesample ( bucket 1 out of 2 on rand());

类型转换

1
2
3
4
5
6
7
8
9
-- CAST可以转换的类型
-- 1.隐式类型转换规则如下
-- (1)任何整数类型都可以隐式地转换为一个范围更广的类型,如 TINYINT 可以转换成 INT,INT 可以转换成 BIGINT。
-- (2)所有整数类型、FLOAT 和 STRING 类型都可以隐式地转换成 DOUBLE。
-- (3)TINYINT、SMALLINT、INT 都可以转换为 FLOAT。
-- (4)BOOLEAN 类型不可以转换为任何其它的类型。
CAST('1' AS INT) -- 可以正常执行
CAST('X' AS INT) -- 执行失败返回 NULL

concat 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- CONCAT 函数用于将多个字符串连接成一个字符串。
-- CONCAT(str1,str2,…)
SELECT CONCAT(id,',',name) AS con FROM info LIMIT 1;返回结果为
+----------+
| con |
+----------+
| 1,BioCyc |
+----------+

-- CONCAT_WS() 指定参数之间的分隔符
SELECT CONCAT_WS('_',id,name) AS con_ws FROM info LIMIT 1;
+----------+
| con_ws |
+----------+
| 1_BioCyc |
+----------+

case when

1
2
3
4
5
6
7
8
9
-- 简单 case
CASE sex
WHEN '1' THEN '男'
WHEN '2' THEN '女'
ELSE '其他' END
-- 搜索 case
CASE WHEN sex = '1' THEN '男'
WHEN sex = '2' THEN '女'
ELSE '其他' END

UDTF(explode)和侧视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- explode 
-- 接收 array 和 map 转换成多行
select explode(`array`(1,2,3,4,5));
-- Result:
-- col
-- 1
-- 2
-- 3
-- 4
-- 5
select explode(`map`("name","xxx",'sex','男','age','13'));
-- Result:
-- key value
-- name xxx
-- sex 男
-- age 13

-- 侧视图结合 explode
-- 侧视图语法
select ... from tableA lateral view UDTF(xxx) 表别名 as 列别名1,列别名2,列别名3;

行列转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
-- 多行转多列
-- 利用case when
-- 原表
-- students.id students.name students.subject students.score
-- 1 小明 语文 87
-- 2 张三 语文 27
-- 3 王五 语文 69
-- 4 李四 语文 99
-- 5 小明 数学 86
-- 6 马六 数学 33
-- 7 李四 数学 44
-- 8 小红 数学 50
-- 转换后
-- name chinese math
-- 小明 87 86
-- 小红 0 50
-- 张三 27 0
-- 李四 99 44
-- 王五 69 0
-- 马六 0 33

select name,
max(case subject when '语文' then score else 0 end) chinese,
max(case subject when '数学' then score else 0 end) math
from students group by name;

-- 多行转单列
-- 转换前
-- cookie.cookieid cookie.createtime cookie.pv
-- cookie1 2015-04-11 5
-- cookie1 2015-04-12 7
-- cookie1 2015-04-10 1
-- cookie1 2015-04-13 3
-- cookie1 2015-04-14 2
-- cookie1 2015-04-15 4
-- cookie1 2015-04-16 4
-- cookie2 2015-04-10 2
-- cookie2 2015-04-11 3
-- cookie2 2015-04-12 5
-- cookie2 2015-04-13 6
-- cookie2 2015-04-14 3
-- cookie2 2015-04-15 9
-- cookie2 2015-04-16 7
-- 转换后
-- cookieid createtimes pvs
-- cookie1 ["2015-04-10","2015-04-11","2015-04-12","2015-04-13","2015-04-14","2015-04-15","2015-04-16"] [1,5,7,3,2,4,4]
-- cookie2 ["2015-04-10","2015-04-11","2015-04-12","2015-04-13","2015-04-14","2015-04-15","2015-04-16"] [2,3,5,6,3,9,7]


select cookieid,collect_list(createtime) createtimes,collect_list(pv) pvs from cookie group by cookieid;
-- 其他函数
COLLECT_LIST(col) -- 多行合并成一行,不去重
COLLECT_SET(col) -- 多行合并成一行,将某字段的值进行去重汇总
CONCAT([string|col],[string|col],[string|col],....) -- 返回输入字符串连接后的结果,支持任意个输入字符串
CONCAT_WS('分隔符',[array|map]) -- 指定分隔符


-- 多列转多行
-- 主要使用 union 关键字
-- 转换前
-- rows2cols.col1 rows2cols.col2 rows2cols.col3 rows2cols.col4
-- a 1 2 3
-- b 4 5 6
-- 转换后
-- _u1.col1 _u1.col2 _u1.col3
-- a c 1
-- a d 1
-- a e 1
-- b c 4
-- b d 4
-- b e 4

select col1,'c' as col2,col2 as col3 from rows2cols
union all
select col1,'d' as col2,col2 as col3 from rows2cols
union all
select col1,'e' as col2,col2 as col3 from rows2cols;

-- 单列转多行
-- 转换前
-- aihao.id aihao.name aihao.age aihao.favors
-- 1 huangbo 45 a-c-d-f
-- 2 xuzheng 36 b-c-d-e
-- 3 huanglei 41 c-d-e
-- 4 liushishi 22 a-d-e
-- 5 liudehua 39 e-f-d
-- 6 liuyifei 35 a-d-e
-- 转换后
-- id name age favorslist
-- 1 huangbo 45 a
-- 1 huangbo 45 c
-- 1 huangbo 45 d
-- 1 huangbo 45 f
-- 2 xuzheng 36 b
-- 2 xuzheng 36 c
-- 2 xuzheng 36 d
-- 2 xuzheng 36 e
-- 3 huanglei 41 c
-- 3 huanglei 41 d
-- 3 huanglei 41 e
-- 4 liushishi 22 a
-- 4 liushishi 22 d
-- 4 liushishi 22 e
-- 5 liudehua 39 e
-- 5 liudehua 39 f
-- 5 liudehua 39 d
-- 6 liuyifei 35 a
-- 6 liuyifei 35 d
-- 6 liuyifei 35 e

select id,name,age,t.col1 as favorsList from aihao lateral VIEW explode(split(favors,'-')) t as col1;

-- lateral VIEW + explode

常见案例

查看前百分之N

1
2
3
4
5
6
7
8
9
10
11
/*
NTILE(n):把有序分区中的行分发到指定数据的组中,各个组有编号,编号从1开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型
ntile(n)和where sorter = m 构成 n/m,如:ntile(2)和where sorted = 1 构成显示所有列的1/2
20% = 1/5 ==> ntile(5),where sorted = 1
*/
SELECT *
FROM (
SELECT name, orderdate, cost, NTILE(5) OVER (ORDER BY orderdate DESC) sorted
FROM business
) t
WHERE sorted = 1;

TOPN 问题

1
2
3
select * from (
select ROW_NUMBER() over(partition by ... order by ... DESC) as rank from tableName
) t where t.rank < 3;

与前一天相比

1
2
3
4
5
LAG(列名,前几行,'默认值')
LAG(name, 1)
LAG(name, 1,'default')
-- 表示取前一条记录的name的值。
-- LEAD()函数与此类似,不过它是查询某字段的后N条记录的值。

连续登录/连续问题

连续登录/连续问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-- 原数据
1,2020-01-01
1,2020-01-02
1,2020-01-07
1,2020-01-08
1,2020-01-09
1,2020-01-10
2,2020-01-01
2,2020-01-02
2,2020-01-03
2,2020-01-04
3,2020-01-02
3,2020-01-03
3,2020-01-04

-- 两种方案
-- 1.自连接,构建笛卡尔积
-- 2. 窗口函数

-- 自连接
-- 两天的连续登录问题使用自连接比较好做,大于2天不适合用自连接
-- 1.自连接
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b;

-- 2.查找用户id相同并且时间差为1的记录
select *
from (
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b
) t where t.a_id=t.b_id and datediff(t.a_dt,t.b_dt)=1;
-- 提取id,并去重
select distinct t1.a_id
from (
select *
from (
select a.user_id a_id,a.login_date a_dt,
b.user_id b_id,b.login_date b_dt
from login_log a,login_log b
) t where t.a_id=t.b_id and datediff(t.a_dt,t.b_dt)=1) t1;


-- 窗口函数
-- 适合于连续N天的问题

select distinct user_id from (
select user_id,login_date,
date_add(login_date,4-1) as nextday,
lead(login_date,4-1) over (partition by user_id order by login_date) as nextlogin
from login_log
) t where nextday = nextlogin;

-- nextday,手动算出 N 天后的时间
-- nextlogin,向下取 N 行进行验证


级联/累加求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- 编写Hive的HQL语句求出每个用户截止到每月为止的最大单月访问次数和累计到该月的总访问次数
-- 原表
usr,mon,num
A,2015-01,5
A,2015-01,15
B,2015-01,5
A,2015-01,8
B,2015-01,25
A,2015-01,5
A,2015-02,4
A,2015-02,6
B,2015-02,10
B,2015-02,5
A,2015-03,16
A,2015-03,22
B,2015-03,23
B,2015-03,10
B,2015-03,11
-- 两种方法
-- 1.group by 加自连接
-- 2.窗口函数

-- 1.group by 加自连接
-- 过于复杂,不写了
-- https://www.bilibili.com/video/BV1L5411u7ae?p=126

-- 方法2
-- 窗口函数

-- 关键点是
sum(num) over (partition by usr order by mon) accPV

with t as (
select usr,mon,num,
row_number() over (partition by usr,mon order by num DESC) top,
sum(num) over (partition by usr order by mon) accPV
from fangwen
)select usr,mon,num,accPV from t where top=1;

EFCore数据库反向工程

之前的大数据比赛取消了,就去弄了另外一个项目。

程序是师兄写的,但是后台数据库不见了,只能自己生成一个数据库来测试,有一个特点是程序运行时会自动生成空的数据表,这样就不用很麻烦的对着java的代码复现数据表结构(ef真香),在写爬虫找数据的时候就想着,能不能用程序把这些表变成实体类呢,网上找了一番,找到了解决方法就重新整理一下记录下了。

源链接:https://www.cnblogs.com/qidakang/p/11302327.html

打开VS,新建一个工程,然后安装如下的包

反向MySQL数据库就安装:

1
2
3
4
MySql.Data.EntityFrameworkCore
Pomelo.EntityFrameworkCore.MySql
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design

反向SqlServer数据库就安装:

1
2
3
4
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design

安装好后打开[程序包控制台],接着输入

MySQL版本:

1
2
3
4
第一次生成实体类:
Scaffold-DbContext "Server=127.0.0.1;port=3306;Database=db; User=root;Password=root;"Pomelo.EntityFrameworkCore.MySql -OutputDir Models
更新实体类:
Scaffold-DbContext "Server=127.0.0.1;port=3306;Database=db; User=root;Password=root;" Pomelo.EntityFrameworkCore.MySql -OutputDir Models -Force

SqlServer版本:

1
2
3
4
第一次生成实体:
Scaffold-DbContext "Server=127.0.0.1;port=3306;Database=db; User=root;Password=root;"Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
更新实体类:
Scaffold-DbContext "Server=127.0.0.1;port=3306;Database=db; User=root;Password=root;"Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models -Force

运行后会多出一个Models文件夹,里面就是这个数据库的所有表

Windows10 下使用 IDEA 配置 Spark 的 Debug 环境

之前报名了学校的一些大数据比赛,做的笔记整理一下发出来,供以后查看
第一次用 ide 和 scala,不足之处还请批评指正

  • idea 的安装就不赘述了。
  • spark 在本地不需要安装。
  • jdk = 1.8
  • spark version = 2.0.0,比较老,但是是比赛的要求。
  • hadoop version = 2.6.0
  • scala version = 2.11.0
  1. 在 idea 中建立一个 maven 工程,注意路径不能包括中文

  2. 添加 pom.xml,示例在 Source\Task3\example\pom.xml 中

    pom.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <spark.version>2.0.0</spark.version>
    <scala.version>2.11</scala.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_${scala.version}</artifactId>
    <version>${spark.version}</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.4</version>
    </dependency>
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming_${scala.version}</artifactId>
    <version>${spark.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_${scala.version}</artifactId>
    <version>${spark.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-hive_${scala.version}</artifactId>
    <version>${spark.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-mllib_${scala.version}</artifactId>
    <version>${spark.version}</version>
    </dependency>

    </dependencies>

    <build>
    <plugins>

    <plugin>
    <groupId>org.scala-tools</groupId>
    <artifactId>maven-scala-plugin</artifactId>
    <version>2.15.2</version>
    <executions>
    <execution>
    <goals>
    <goal>compile</goal>
    <goal>testCompile</goal>
    </goals>
    </execution>
    </executions>
    </plugin>

    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
    </plugin>

    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19</version>
    <configuration>
    <skip>true</skip>
    </configuration>
    </plugin>

    </plugins>
    </build>
    </project>
  3. 在 Idea 左侧 Project 窗口中选中 src\main 右键添加文件夹 scala

  4. 选中 scala 右键 make directory as -> Sources Root

  5. scala 变成蓝色后右键 new -> scala class ->选择 object,输入名称

  6. 我们先写一个最简单的 CsvShow 程序让他能在本地运行并且 debug

    CsvShow Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.example.spark.scala

    import org.apache.spark.sql.{DataFrame, SparkSession}
    import org.apache.spark.{SparkConf, SparkContext}

    object CsvShow {
    def main(args:Array[String]):Unit= {
    val spark: SparkSession = SparkSession.builder()
    .master("local[*]")
    .appName("CsvShow")
    .config("spark.sql.warehouse.dir", "file:/")
    .getOrCreate()
    val csv: DataFrame = spark.read.csv("../../../../data/mysql.csv")
    csv.show()
    val pdf: DataFrame = csv.toDF()
    pdf.show()
    spark.stop()
    }
    }
  7. 解压 Hadoop,在没有安装 hadoop 的情况下是不能进行 spark 程序的本地 debug

  • 我们先下载如下两个文件 hadoop-2.6.0.tar.gz 和 hadooponwindows-master.zip

  • 先将 hadoop 解压出来

  • 再解压 hadooponwindows 到 hadoop 的根目录下,提示覆盖选择全覆盖

  • 添加如下环境变量

    1. 变量名=HADOOP_HOME, 变量值=你解压的路径
    1. 在 path 中添加 %HADOOP_HOME%\bin 变量
  • 打开 cmd,输入 hadoop version,显示版本说明 hadoop 环境变量配置正确

  1. 配置 Hadoop 环境
  • 修改 etc\hadoop\hadoop-env.cmd 中的 JAVA_HOME,如 JAVA_HOME=C:\PROGRA~1\Java\jdk1.8.0_241,路径中不能有空格

  • 在根目录下添加 datanode、namenode 和 tmp 文件夹

  • 修改 etc\hadoop\hdfs-site.xml,添加如下 xml, 注意/D:/hadoop-2.6.0/namenode 和/D:/hadoop-2.6.0/datanode 这两个值需要时之前创建文件夹的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/D:/hadoop-2.6.0/namenode</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/D:/hadoop-2.6.0/datanode</value>
</property>
</configuration>
  • 修改 etc\hadoop\core-site.xml
1
2
3
4
5
6
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
  • 修改 etc\hadoop\mapred-site.xml
1
2
3
4
5
6
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
  • 修改 etc\hadoop\yarn-site.xml
1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
<value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>
</configuration>
  1. 以管理员身份打开 cmd 输入 hdfs namenode -format 来执行格式化 hdfs,执行后 namenode 文件里会自动生成一个 current 文件
  2. cd 到根目录下 sbin 文件夹,输入 start-all.cmd 启动全部的 hadoop 服务
  3. 在相应的方法中右键 Debug “*”,就可以运行本地 debug 调试

JS 实现 URL 变换

前言

今天无聊找东西玩翻书签翻到了 http://glench.com/hash/
就想着找源码搬到 blog 上,转了半天发现作者没开源,就有了下文。

演示

警告!请使用无痕模式再点击下面的按钮,不然会有一堆的历史记录。
表情循环:
滚动条位置动画:
滚动条动画演示

代码

ver0.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//修改 url 的方法
//sub: 数组下标
//arr: 表情数组
async function setUrl(text) {
if (!!(window.history && history.pushState)) {
history.replaceState(null, null, window.location.href.split('#')[0] = text);
}
}
//修改 url 为默认 url
async function cleanUrl(url) {
if (!!(window.history && history.pushState)) {
history.replaceState(null, null, window.location.href.split('#')[0] = url);
}
}

function getPageHeight() {
let body = document.body,
html = document.documentElement;

let height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
return height;
}

function getWindowHeight() {
let height = window.innerHeight;
return height;
}

//循环动画使用的表情
var kaomoji = [
"(`・ω・)",
"(〃∀〃)",
"w(゚Д゚)w",
"_(:з」∠)_"
]

var emoji = [
"🌶️",
"💉",
"💦",
"🐂",
"🍺"
]

var bar = [
"-",
"0"
]

var defUrl = window.location.href.split('#')[0]; //默认 url

var scrollProgressIntervalId = null;

function scrollBarClick() {
UrlProgress(bar, 0, 1, 50, -1);
let e = window.event;
if (e != undefined) {
obj = e.target || e.srcElement;
}
if (e.path[0].innerText == "click") {
e.path[0].innerText = "stop";
window.addEventListener('scroll', e => scrollProgress(bar), true);
} else {
alert("需要停止请刷新!");
e.path[0].innerText = "click";
window.removeEventListener('scroll', scrollProgress(bar), true);
}

}

function scrollProgress(arr) {
let now;

let scrollAvail = getPageHeight() - getWindowHeight(); // 可滚动的高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;

let length = 50;
now = (scrollTop / scrollAvail) * length;
now = parseInt(now);
UrlProgress(arr, 0, 1, 50, now);
}
//arr 对应前景,背景数组
//subBack 进度条背景
//subFore 进度条前景
//length 进度条总长
//now 当前进度
function UrlProgress(arr, subBack, subFore, length, now) {
let args = "";
for (let i = 0; i < length; i++) {
args = args + arr[subBack]
}

if (now >= 0) {
args = args.split('');
args.splice(now, 1, arr[subFore]);
args = args.join('');
}

setUrl(args);
}

//循环动画
var UrlLoopIntervalId = null;

function UrlLoop(speed, arr, defUrl) {
var e = window.event;
if (e != undefined) {
obj = e.target || e.srcElement;
e.path[0].innerText = "stop";
}
//上面:
//选择触发方法的元素

if (UrlLoopIntervalId == null) {
this.speed = speed || '1000';
var i = 0;
UrlLoopIntervalId = setInterval(function () {
setUrl(arr[i]);
i++;
if (i == arr.length) {
i = 0;
}
}, speed);
} else {
clearInterval(UrlLoopIntervalId);
cleanUrl(defUrl);
UrlLoopIntervalId = null;
e.path[0].innerText = "click";
}
}
ver0.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//修改 url 的方法
//sub: 数组下标
//arr: 表情数组
async function setUrl(text) {
if (!!(window.history && history.pushState)) {
history.replaceState(null, null, window.location.href.split('#')[0] = text);
}
}
//修改 url 为默认 url
async function cleanUrl(url) {
if (!!(window.history && history.pushState)) {
history.replaceState(null, null, window.location.href.split('#')[0] = url);
}
}

function getPageHeight() {
let body = document.body,
html = document.documentElement;

let height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
return height;
}

function getWindowHeight() {
let height = window.innerHeight;
return height;
}

//循环动画使用的表情
var kaomoji = [
"(`・ω・)",
"(〃∀〃)",
"w(゚Д゚)w",
"_(:з」∠)_"
]

var emoji = [
"🌶️",
"💉",
"💦",
"🐂",
"🍺"
]

var bar = [
"-",
"0"
]

var defUrl = window.location.href.split('#')[0]; //默认 url

var scrollProgressIntervalId = null;
window.addEventListener('scroll', e => scrollProgress(bar), true)

function scrollProgress(arr) {
let now;

let scrollAvail = getPageHeight() - getWindowHeight(); // 可滚动的高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset;

let length = 50;
now = (scrollTop / scrollAvail) * length;
now = parseInt(now);
UrlProgress(arr, 0, 1, 50, now);
}
//arr 对应前景,背景数组
//subBack 进度条背景
//subFore 进度条前景
//length 进度条总长
//now 当前进度
function UrlProgress(arr, subBack, subFore, length, now) {
let args = "";
for (let i = 0; i < length; i++) {
args = args + arr[subBack]
}

if (now >= 0) {
args = args.split('');
args.splice(now, 1, arr[subFore]);
args = args.join('');
}

setUrl(args);
}

//循环动画
var UrlLoopIntervalId = null;

function UrlLoop(speed, arr, defUrl) {
var e = window.event;
if (e != undefined) {
obj = e.target || e.srcElement;
e.path[0].innerText = "stop";
}
//上面:
//选择触发方法的元素

if (UrlLoopIntervalId == null) {
this.speed = speed || '1000';
var i = 0;
UrlLoopIntervalId = setInterval(function () {
setUrl(arr[i]);
i++;
if (i == arr.length) {
i = 0;
}
}, speed);
} else {
clearInterval(UrlLoopIntervalId);
cleanUrl(defUrl);
UrlLoopIntervalId = null;
e.path[0].innerText = "click";
}
}

TODO

  • 加一个进度条动画获取滚动位置改变进度。
  • 同时打开多个弹窗,实现播放动画。
  • 按钮解绑滚动条事件
  • 在进度条的末端加进度的百分比