Hoey笔记

和有趣的人做尽有趣的事


  • Home

  • Archives

  • Sitemap

  • Search

DataHub元数据管理体验

Posted on 2023-11-08

Datahub

image-20231108174154093

开源地址:https://github.com/datahub-project/datahub

DataHub是由Linkedin开源的,官方Slogan:The Metadata Platform for the Modern Data Stack - 为现代数据栈而生的元数据平台。目的就是为了解决多种多样数据生态系统的元数据管理问题,它提供元数据检索、数据发现、数据监测和数据监管能力,帮助大家解决数据管理的复杂性。

DataHub基于Apache License 2开源,采用基于推送的数据收集架构(当然也支持pull拉取的方式),能够持续收集变化的元数据。当前版本已经集成了大部分流行数据生态系统接入能力,包括但不限于:Kafka, Airflow, MySQL, SQL Server, Postgres, LDAP, Snowflake, Hive, BigQuery。

Datahub的优点:

  • 名门开源,与Kafka同家庭。社区活跃,发展势头迅猛,版本更新迭代迅速。
  • 定位清晰且宏远,Slogan可以看出团队的雄心壮志及后期投入,且不断迭代更新的版本也应证了这一点。
  • 底层架构灵活先进,未扩展集成而生,支持推送和拉去模式,详见:https://datahubproject.io/docs/architecture/architecture/
  • UI界面简单易用,技术人员及业务人员友好
  • 接口丰富,功能全面

Datahub的不足:

  • 前端界面不支持国际化,界面的构建和使用逻辑不够中国化
  • 版更更新迭代快,使用后升级是个难题
  • 较多功能在建设中,例如Hive列级血缘
  • 部分功能性能还需要优化,例如SQL Profile
  • 中文资料不多,中文交流社群也不多

相关介绍:

https://mp.weixin.qq.com/s/74gK3hTt7-j1lTbKFagbTQ
https://mp.weixin.qq.com/s/iP6sc2DzPaeAKpSWNmf8hQ

选型建议:

1)如果有至少半个前端开发人员+后台开发人员;
2)如果需要用户体验较好的数据资产管理平台;
3)如果有需要扩展支持各种平台、系统的元数据。请把Datahub列为最高选择。
尽管列举了一些不足,但是开源产品中Datahub目前是相对最好的选择。笔者也在生产中使用,有问题的可以随时沟通交流。

商用版本: Metaphor(https://metaphor.io/)是Datahub的SaaS版本。

部署及安装

  1. 执行命令
1
2
3
python3 -m pip install --upgrade pip wheel setuptools
python3 -m pip install --upgrade acryl-datahub
datahub version

⚠️注: 在我们执行安装前,可以创建python虚拟环境

  1. 从docker上拉取镜像部署,datahub提供了自动拉取镜像、自动部署节点的脚本,让部署一键操作
1
datahub docker quickstart

⚠️注: 如果你的网络不佳,或者是国内网络,过程会特别的漫长,我就是等了一天才好

成功后terminal中展示:

image-20231108174029522

下面我们登陆:

http://localhost:9002/login?redirect_uri=%2F

user/pass: datahub/datahub

image-20231108174130857

部署细节不懂的,可以参考: https://datahubproject.io/docs/quickstart

下一章我们结合Datahub API + 血缘解析工具,简单说一下怎么构建企业知识图谱

Marquez元数据管理体验

Posted on 2023-11-08

Marquez

image-20231109105214566

开源地址:https://github.com/MarquezProject/marquez

Marquez的优点:

  • 界面美观,操作细节设计比较棒
  • 部署简单,代码简洁
  • 依靠底层OpenLineage协议,结构较好

Marquez的不足:

  • 聚焦数据资产/血缘的可视化,数据资产管理的一些功能,需要较多开发工作

相关介绍:https://mp.weixin.qq.com/s/OMm6QEk9-1bFdYKuimdxCw

安装及体验

  1. 克隆项目
1
git clone git@github-hoey94:hoey94/marquez.git
  1. 到项目根目录运行docker
1
DOCKER_BUILDKIT=1 ./docker/up.sh --seed

image-20231108111436255

  1. 访问页面

image-20231108113044028

  1. 简单实用

创建namespace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -X POST http://localhost:5000/api/v1/lineage \
-H 'Content-Type: application/json' \
-d '{
"eventType": "START",
"eventTime": "2020-12-28T19:52:00.001+10:00",
"run": {
"runId": "d46e465b-d358-4d32-83d4-df660ff614dd"
},
"job": {
"namespace": "my-namespace",
"name": "my-job"
},
"inputs": [{
"namespace": "my-namespace",
"name": "my-input"
}],
"producer": "https://github.com/OpenLineage/OpenLineage/blob/v1-0-0/client"
}'

创建input和output

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
curl -X POST http://localhost:5000/api/v1/lineage \
-H 'Content-Type: application/json' \
-d '{
"eventType": "COMPLETE",
"eventTime": "2020-12-28T20:52:00.001+10:00",
"run": {
"runId": "d46e465b-d358-4d32-83d4-df660ff614dd"
},
"job": {
"namespace": "my-namespace",
"name": "my-job"
},
"outputs": [{
"namespace": "my-namespace",
"name": "my-output",
"facets": {
"schema": {
"_producer": "https://github.com/OpenLineage/OpenLineage/blob/v1-0-0/client",
"_schemaURL": "https://github.com/OpenLineage/OpenLineage/blob/v1-0-0/spec/OpenLineage.json#/definitions/SchemaDatasetFacet",
"fields": [
{ "name": "a", "type": "VARCHAR"},
{ "name": "b", "type": "VARCHAR"}
]
}
}
}],
"producer": "https://github.com/OpenLineage/OpenLineage/blob/v1-0-0/client"
}'

更多细节可以参考一下下面文章:https://blog.csdn.net/weixin_43947468/article/details/129593234

ES操作模版

Posted on 2023-11-08

下面是一些ES日常操作,记录下来方便查询

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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
#查看集群的健康情况
GET /_cat/health?v

#查看节点的情况
GET /_cat/nodes?v

#查询各个索引状态
GET /_cat/indices?v

#创建索引
PUT /movie_index

#查看某一个索引的分片情况
GET /_cat/shards/movie_index?v

#删除索引
DELETE /movie_index

#创建文档
PUT /movie_index/movie/1
{ "id":100,
"name":"operation red sea",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"zhang yi"},
{"id":2,"name":"hai qing"},
{"id":3,"name":"zhang han yu"}
]
}

PUT /movie_index/movie/2
{
"id":200,
"name":"operation meigong river",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"zhang han yu"}
]
}

PUT /movie_index/movie/3
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang san feng"}
]
}

#查询某一个索引中的全部文档
GET /movie_index/_search

#根据id查询某一个文档
GET /movie_index/movie/3

#根据文档id,删除某一个文档
DELETE /movie_index/movie/3

POST /_forcemerge

#put 对已经存在的文档进行替换(幂等性)
PUT /movie_index/movie/3
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang cuishan"}
]
}


#post 进行新增操作,无法保证幂等性
#根据主键保证幂等性
POST /movie_index/movie/
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang cuishan111"}
]
}


GET /movie_index/_search

GET /movie_index/movie/1

POST /movie_index/movie/1/_update
{
"doc": { "name": "新的字段值" }
}


POST /movie_index/_update_by_query
{
"query": {
"match":{
"actorList.id":1
}
},
"script": {
"lang": "painless",
"source":"for(int i=0;i<ctx._source.actorList.length;i++){if(ctx._source.actorList[i].id==3){ctx._source.actorList[i].name='tttt'}}"
}
}

GET /movie_index/_search

POST /movie_index/_delete_by_query
{
"query": {
"match_all": {}
}
}

#批量添加两个document
POST /movie_index/movie/_bulk
{"index":{"_id":66}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}
{"index":{"_id":88}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"zhang cuishan"}]}




POST /movie_index/movie/_bulk
{"update":{"_id":"66"}}
{"doc": { "name": "wudangshanshang" } }
{"delete":{"_id":"88"}}


#------------------查询操作--------------------
#查询出当前索引中的全部数据
GET /movie_index/_search

GET /movie_index/_search?q=_id:66

#查询全部
GET /movie_index/_search
{
"query": {
"match_all": {}
}
}

#根据电影的名称进行查询
GET /movie_index/_search
{
"query": {
"match": {
"name": "operation red sea"
}
}
}

GET /movie_index

#按分词进行查询
GET /movie_index/_search
{
"query": {
"match": {
"actorList.name": "zhang han yu"
}
}
}

#按短语查询 相当于like
GET /movie_index/_search
{
"query": {
"match_phrase": {
"actorList.name": "zhang han yu"
}
}
}

#不分词 通过精准匹配进行查询 term精准匹配
GET /movie_index/_search
{
"query": {
"term": {
"actorList.name.keyword":"zhang han yu"
}
}
}


#容错匹配
GET /movie_index/_search
{
"query": {
"fuzzy": {
"name": "radd"
}
}
}

#先匹配 再过滤
GET /movie_index/_search
{
"query": {
"match": {
"name": "red"
}
},
"post_filter": {
"term": {
"actorList.id": "3"
}
}
}


#匹配和过滤同时
GET /movie_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "red"
}
}
],
"filter": {
"term": {
"actorList.id": "3"
}
}
}
}
}

#范围过滤 ,将豆瓣评分在6到9的文档查询出来
GET /movie_index/_search
{
"query": {
"range": {
"doubanScore": {
"gte": 6,
"lte": 9
}
}
}
}


#按照豆瓣评分降序排序
GET /movie_index/_search
{
"query": {
"match": {
"name": "red"
}
},
"sort": [
{
"doubanScore": {
"order": "asc"
}
}
]
}

#分页查询
GET /movie_index/_search
{
"from": 0,
"size": 2
}

#查询指定字段
GET /movie_index/_search
{
"_source": ["name", "doubanScore"]
}

#高亮显示
GET /movie_index/_search
{
"query": {
"match": {
"name": "red"
}
},
"highlight": {
"fields": {"name":{} },
"pre_tags": "<a>",
"post_tags": "</a>"
}
}


#需求1:取出每个演员共参演了多少部电影
#aggs 聚合
#term 精准匹配
#terms 聚合操作,相当于groupBy
GET /movie_index/_search
{
"aggs": {
"myAggs": {
"terms": {
"field": "actorList.name.keyword",
"size": 10
}
}
}
}

#需求2:每个演员参演电影的平均分是多少,并按评分排序

GET /movie_index/_search
{
"aggs": {
"groupByName": {
"terms": {
"field": "actorList.name.keyword",
"size": 10,
"order": {
"avg_score": "asc"
}
},
"aggs": {
"avg_score": {
"avg": {
"field": "doubanScore"
}
}
}
}
}
}


#分词
#英文默认分词规则
GET /_analyze
{
"text": "hello world"
}

#中文默认分词规则
GET /_analyze
{
"text": "蓝瘦香菇",
"analyzer": "ik_smart"
}

GET /_analyze
{
"text": "蓝瘦香菇",
"analyzer": "ik_max_word"
}


GET /movie_index



#自动定义mapping
PUT /movie_chn_1/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_1/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}

PUT /movie_chn_1/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张三丰"}
]
}

GET /movie_chn_1/_search
{
"query": {
"match": {
"name": "海行"
}
}
}

GET /movie_chn_1/_mapping


#自定义mapping
DELETE movie_chn_2

PUT movie_chn_2
{
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "keyword"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}


PUT /movie_chn_2/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn_2/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}

PUT /movie_chn_2/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张三丰"}
]
}

GET /movie_chn_2/_mapping

GET /movie_chn_2/_search
{
"query": {
"match": {
"name": "海行"
}
}
}

POST /_reindex
{
"source": {}
, "dest": {}
}

#创建索引 并指定别名
PUT movie_chn_3
{
"aliases": {
"movie_chn_3_aliase": {}
},
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}


GET /_cat/indices



GET /_cat/aliases


POST /_aliases
{
"actions": [
{
"add": {
"index": "movie_chn_3",
"alias": "movie_chn_3_a2"
}
}
]
}


GET /movie_chn_3/_search

GET /movie_chn_3_a2/_search

GET /_cat/aliases

POST /_aliases
{
"actions": [
{
"remove": {"index": "movie_chn_3","alias": "movie_chn_3_a2"}
}
]
}


GET /movie_chn_1/_search

GET /movie_chn_2/_search



POST _aliases
{
"actions": [
{ "add": { "index": "movie_chn_1", "alias": "movie_chn_query" }},
{ "add": { "index": "movie_chn_2", "alias": "movie_chn_query" }}
]
}


GET /movie_chn_query/_search


POST _aliases
{
"actions": [
{
"add":
{
"index": "movie_chn_1",
"alias": "movie_chn_1_sub_query",
"filter": {
"term": { "actorList.id": "4"}
}
}
}
]
}

GET /movie_chn_1_sub_query/_search


POST /_aliases
{
"actions": [
{ "remove": { "index": "movie_chn_1", "alias": "movie_chn_query" }},
{ "remove": { "index": "movie_chn_2", "alias": "movie_chn_query" }},
{ "add": { "index": "movie_chn_3", "alias": "movie_chn_query" }}
]
}


#创建模板
PUT _template/template_movie0523
{
"index_patterns": ["movie_test*"],
"settings": {
"number_of_shards": 1
},
"aliases" : {
"{index}-query": {},
"movie_test-query":{}
},
"mappings": {
"_doc": {
"properties": {
"id": {
"type": "keyword"
},
"movie_name": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
}
}

POST movie_test_202010/_doc
{
"id":"333",
"name":"zhang3"
}

GET /movie_test_202010-query/_mapping


GET /_cat/templates

GET /_template/template_movie*

Flink Timer定时器

Posted on 2023-07-24

在使用processFunction实现两张事实表的JOIN操作时,接触到了Timer,下面对Flink定时器的核心知识做一个简单总结:

1.1 Timers支持使用在KeyedStream

因为 Timer 是基于每个键即 key 注册并触发,所以 KeyedStream 是 Timer 在 Flink 中使用的先决条件

1
ctx.timerService.deleteEventTimeTimer(timeStamp)

1.2 Timers的唯一性

TimerService 会自动消除计时器的重复数据,始终保持每个键 key 最多只有一个计时器,当一个键 key 注册多个 Timer 计时器时,onTimer 方法只会调用一次,重复注册会覆盖之前的 timer 注册

1.3 Timers支持checkpoint

ValueState 可以通过 checkpoint 进行检查点保存和恢复,同理 Timer 也可以由 checkpoint 托管,从 Flink checkpoint 检查点恢复任务时,将立即启动恢复前应启动的处于恢复状态的每个已注册计时器,这也提高了 Timer 的容错性

1.4 Timers支持被删除

从 Flink 1.6.x 开始,计时器可以暂停和删除,提供更便捷的 Timer 处理方式

文章发布自:[Flink Timer 与 TimerService 源码分析与详解](https://it.cha138.com/ios/show-36808.html#2.1 注册 Timer)

写的十分好,推荐阅读,除此之外推荐阅读:

Flink Timer 机制原理,源码整理

Flink的定时器EventTime和ProcessTime

Flink使用CoProcessFunction完成实时对账、基于时间的双流join_flink实时对账

mac下的开发包管理工具-sdk

Posted on 2023-07-16

SDK可以帮助我们维护mac下的程序包,比如想安装jdk1.8、又想安装jdk11,比如想安装maven等等,它都可以通过指令进行安装和管理。下面是一些常用的指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装
curl -s "https://get.sdkman.io" | bash
# 设置path
source "$HOME/.sdkman/bin/sdkman-init.sh"

# 查看sdk版本
sdk version
# 查看安装maven安装情况
sdk list maven

# 安装maven 3.6.4
sdk install maven 3.6.3
# maven被安装到的目录
cd ~/.sdkman/candidates

家庭网络拓扑

Posted on 2023-07-11

前段时间618新入手了几块硬盘,买回来组一个NAS,下面是家里的网络拓扑逻辑图

image-20230711130249774

在其中完成了以下内容:

  1. 网络统一管理,包括网络分割如划分子网;网络的优化如实现拦截、翻墙、过滤广告、加速;内外网穿透。
  2. ESXI虚拟化,对物理机的CPU计算、存储和网络资源高效利用,EXSI高效的横向拓展能力和简易的操作都非常的优秀。
  3. NAS(网络附加存储),数据共享与存储、备份与恢复、远程访问、横向磁盘扩容。
  4. 边缘计算,网络资源共享,提升网络三大运营商CDN网络加速。

得到的好处:

  1. 全端的无差异实时备份,包括Mac、Windows、IPad、IPhone等设备
  2. 现在市面上各大云盘,某度云、某里云等等,现在统统废弃,全部用NAS就可以解决,相当于买了个所有云盘的VIP
  3. 网络方面的国内外无差异化上网体验。
  4. 给老婆刷刷剧,看看电影都很方便。

http://192.168.1.1/cu.html

CUAdmin32216618

使用反向代理解决SSL认证

Posted on 2023-05-04

自己手里有很多私有服务,并且域名还都不一样,例如下面这些:

应用 ip
NAS 192.168.30.5:5000
Jellyfin 192.168.30.5:8096
私人笔记 hoey94.github.io
私人网盘 192.168.30.5:9000
chatgpt chat.github.io
… …

这些应用管理起来十分不方便,于是就想将这些网站整合在一起,并且对外提供统一域名访问方式。我自身是有一个免费的顶级域名的hoey.tk,那现在的需求就很明确了,其实就是想得到下面这样的效果:

应用 ip
NAS nas.hoey.tk
Jellyfin jellyfin.hoey.tk
私人笔记 notes.hoey.tk
私人网盘 pan.hoey.tk
chatgpt chat.hoey.tk
… …

最近找到了一款挺不错的软件Nginx Project Manager,并且还能提供免费的SSL,虽然3个月到期以后还要手动续期,不过白嫖的东西还要什么自行车。它的页面像这样:

image-20230504205632731

配置完以后,访问的时候只需要使用二级域名访问即可,十分方便,维护起来也十分傻瓜。

前置准备

  • VPS任意,我的是在国外自购的一台服务器,为什么自购国外,大家懂得都懂,现在是科学时代。
  • 自建家用机服务器,我的服务器主要插上8T*2机械硬盘组磁盘阵列,搞成NAS存储服务器做个私有云。另外结合esxi或者是docker能跑很多好玩的东西,这里就不一一列举了,感兴趣的小伙伴可以搜索一下自建NAS服务器 ALL IN ONE。
  • tk域名是白嫖的,在DNS服务商那里却不被支持(这就是白嫖的代价吧)。自己又穷,买不起域名哈哈哈,就搞了个垃圾域名做中转,4块钱人民币一年哈哈哈。

网络拓扑图

下面这个是结合自己家庭网络拓扑图和已有的一些服务的网络拓扑图,用这个实现二级域名访问+SSL。

image-20230504212114306

从最右边开始一层一层解释所做的事情:

  1. 首先联系联通宽带,给到公网IP,将自己内网服务,在软路由内部通过端口映射的方式开放到外网。如果没有公网IP,也可以用frp等工具实现内网穿透。

  2. 接下来要解决公网IP变动的问题(联通给的公网IP是动态的,每隔一段时间就会变)。DNS服务商我选择的是Cloudflare,实现DDNS这块是参考开源项目,改写了一个脚本,其主要功能是:以轮训的方式,动态监测公网IP变更,将A记录通过API的方式更新到Cloudflare以实现DNS解析,具体脚本参考cloudflare-api-dns,当然网上也有很多类似以封装好的工具实现DDNS,比如ddns-go

  3. 在自己的VPS上搭建好Nginx Project Manager(后面我们简称NPM),用来整合自己所有的服务,并添加SSL认证。

  4. 在Cloudflare创建记录将tk域名映射到VPS上,反向代理接受到请求,转发到4块钱买的域名(hoey.asia)上,哈哈哈。

在CentOS 7上 安装 NPM

  1. 确保安装了docker 和docker-compose
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
# docker
yum remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo yum makecache fast
yum install -y docker-ce
systemctl start docker.service
systemctl enable docker.service
docker -v

# docker-compose
curl -L https://github.com/docker/compose/releases/download/v2.3.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version


# 开启ipv6防止日志文件过大 (可选)
cat > /etc/docker/daemon.json <<EOF
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "3"
},
"ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef:c0::/80",
"experimental":true,
"ip6tables":true
}
EOF

systemctl restart docker
  1. 使用docker-compose 安装npm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mkdir -p /data/docker_data/npm
cd /data/docker_data/npm
vim docker-compose.yml
# 将内容粘贴进去 start
version: '3'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80' # 冒号左边可以改成自己服务器未被占用的端口
- '81:81' # 冒号左边可以改成自己服务器未被占用的端口
- '443:443' # 冒号左边可以改成自己服务器未被占用的端口
volumes:
- ./data:/data # 冒号左边可以改路径,现在是表示把数据存放在在当前文件夹下的 data 文件夹中
- ./letsencrypt:/etc/letsencrypt # 冒号左边可以改路径,现在是表示把数据存放在在当前文件夹下的 letsencrypt 文件夹中
# 将内容粘贴进去 end
docker-compose up -d
  1. 更新NPM
1
2
3
4
5
6
7
8
9
10
11
cd /root/data/docker_data/npm

docker-compose down

cp -r /data/docker_data/npm /data/docker_data/npm.archive # 万事先备份,以防万一

docker-compose pull

docker-compose up -d # 请不要使用 docker-compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker-compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。

docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像
  1. 卸载NPM
1
2
3
4
5
cd /root/data/docker_data/npm

docker-compose down

rm -rf /root/data/docker_data/npm # 完全删除映射到本地的数据

结合Cloudflare 配置NPM

NPM默认账号密码: admin@example.com/changeme,登录后修改

登录Cloudflare,到自己的域名中添加A记录,指向VPS服务器(我的VPS是110.123.11.1,域名是hoey.tk)

image-20230504222932398

在NPM中添加对应的反向代理配置,并打开SSL,(下图是以nas这条记录为例,描述NPM的配置)

image-20230504214014283

之后开启SSL认证

image-20230504214052552

点击保存即可。

总结

配置完成以后,就可以使用nas.hoey.tk访问nas服务了,当用户访问nas.hoey.tk时,在网络中它的链路应该如下图所示:

image-20230504214857962

以上

Jellyfin 添加字体库解决中文乱码

Posted on 2023-04-30

最近使用Jellyfin搭建家庭影音出现中文乱码问题,下面是我搜到的一些方案,我用的是扩展中文字体库

  • 使用nyanmisaka/jellyfin 套件
  • 使用actime转字幕
  • 扩展中文字体库

下面是一些指令,针对小白用的,大佬无视

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 远程登录到NAS并切换最高权限
ssh admin@192.168.30.5 -p 22
sudo -i

# 进入到NAS自己创建的目录,这个目录自己改
cd /volume1/media/fonts
pwd

# 使用docker命令查看jellyfin 的id
docker ps

# 进入到容器内部查看目录文件结构1538f09eaa85要替换成自己的id
docker exec -it 1538f09eaa85 /bin/bash
cd /usr/share/fonts/
exit

# 复制宿主机的文件到容器内
docker cp fz.tar.gz 1538f09eaa85:/usr/share/fonts/

# 到容器目录中解压
docker exec -it 1538f09eaa85 /bin/bash
cd /usr/share/fonts/
tar -zxvf fz.tar.gz

链接:https://share.weiyun.com/5Wg55FF5 密码:nhqe76

视频已经传到B站,欢迎观看Jellyfin中文字幕乱码解决_哔哩哔哩_bilibili

Doris使用踩坑总结

Posted on 2023-03-28

环境

  • MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
  • [Doris](部署 Docker 集群 - Apache Doris):v1.12.1
  • JDK: v1.8.x
  • IDEA开发环境

一、 Docker部署

官方提供文档Deploy Docker cluster

但部署完以后发现集群无法识别BE节点,查看日志发现是因为没有设置vm.max_map_count参数

避坑1: 需要设置sysctl -w vm.max_map_count=2000000

避坑2: 在启动容器时需要添加privileged: true

二、Flink 写入Doris

官方提供了文档Flink Doris Connector

这里选用的是RowData的方式写入到Doris,提前到Doris中创建表代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE DATABASE dwd;
DROP TABLE IF EXISTS dwd.TICKET;
CREATE TABLE IF NOT EXISTS dwd.TICKET (
event VARCHAR(256),
newval VARCHAR(256),
oldval VARCHAR(256),
stamp VARCHAR(256),
subevent VARCHAR(256),
uptm VARCHAR(256),
uptmms VARCHAR(256),
ver VARCHAR(256)
)DISTRIBUTED BY HASH(stamp) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

然后是使用Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
Properties prop = new Properties();
prop.setProperty("format", "json");
prop.setProperty("strip_outer_array", "true");

env.fromElements("{\"event\":\"CheckIn\",\"newval\":\"90\",\"oldval\":\"0\",\"stamp\":\"67100c4f-0355-4dba-8a76-2a94202132d1\",\"subevent\":\"\",\"uptm\":\"20230319094822\",\"uptmms\":\"20230319094822031\",\"ver\":\"v1.0\"}")
.addSink(DorisSink.sink(
DorisReadOptions.builder().build(),
DorisExecutionOptions.builder().setBatchSize(3)
.setBatchIntervalMs(0L)
.setMaxRetries(3)
.setStreamLoadProp(prop)
.build(),
DorisOptions.builder().setFenodes("localhost:8030")
.setTableIdentifier("dwd.TICKET")
.setUsername("admin")
.setPassword("").build())
);
env.execute("pss-format-job");
}

运行时发现网络不通问题,无法成功写入Dois,具体错误如图下图所示:

image-20230328131103501

分析宿主节点和docker集群的关系

image-20230328131607049

由架构图得知,是因为sink写入的时候通过外网地址的fe取到be地址是内网地址。

解决思路:

  1. 程序上传到FE运行,但是我属于本地开发环境,这种方式不适合

  2. 配置Docker network,使得在宿主节点能够访问容器 BE的IP。这块我也尝试搜索了很多资料,最终以失败告终。

    • 尝试一: 在宿主节点修改路由表 sudo route add -net 172.20.80.0/24 172.20.80.1
    • 尝试二: 以Host方式运行FE和BE
  3. 修改源代码: 找到涉及到“第二步:将BE内网IP返回给主程序”代码,并修改

其中我是用第三种方法,成功在本地开发环境摄入数据到Doris,在这里简单介绍一下快速修改源码的方法。

第一步: 先找到涉及到代码的相关方法,在该场景中对应的是org.apache.doris.flink.rest.RestService类的randomBackend()。

1
2
3
4
5
6
7
8
9
10
11
12
13
@VisibleForTesting
public static String randomBackend(DorisOptions options, DorisReadOptions readOptions, Logger logger) throws DorisException, IOException {
List<BackendRowV2> backends = getBackendsV2(options, readOptions, logger);
logger.trace("Parse beNodes '{}'.", backends);
if (backends != null && !backends.isEmpty()) {
Collections.shuffle(backends);
BackendRowV2 backend = (BackendRowV2)backends.get(0);
return backend.getIp() + ":" + backend.getHttpPort();
} else {
logger.error("argument '{}' is illegal, value is '{}'.", "beNodes", backends);
throw new IllegalArgumentException("beNodes", String.valueOf(backends));
}
}

第二步:现在希望返回值并非BE的内网IP,因此我们需要将return的内容写死return "localhost:8040";

第三步:在代码中我们创建与类相同的包,并将源码原封不动粘贴进去,如果报错,可以修改。紧接着修改return "localhost:8040";即可

image-20230328133408579

总结:

我这里是本地开发环境遇到的问题,并非生产环境;如果生产环境部署基于Doris的应用程序,则需要注意下下面几点:

  1. 运行的代码所在的机器,必须与Doris所在的网络保持通常。
  2. 基于RowData的摄入方式不太友好,现在仍要寻找一种更好的编码方式。

ksqlDB

Posted on 2023-03-14

目前需要对Kafka Topic中的数据进行分析,查询了一下KSQLDB挺不错,它是一个流处理引擎,主要用于处理实时数据流,并支持 SQL 查询和流处理操作。KSQLDB 可以运行在 Apache Kafka 平台之上,它不需要额外的基础设施,因此可以方便地与 Kafka 进行集成。KSQLDB 可以实现流数据的可视化、数据的清洗和去重、流式计算等。KSQLDB 的主要特点包括易于使用、接近实时的处理速度、强大的 SQL 查询功能以及灵活的流处理操作。

环境准备

  • MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
  • Docker & Docker compose

下面代码用于部署Standalone

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
---
version: '2'

services:
zookeeper:
image: confluentinc/cp-zookeeper:7.3.0
hostname: zookeeper
container_name: zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000

broker:
image: confluentinc/cp-kafka:7.3.0
hostname: broker
container_name: broker
depends_on:
- zookeeper
ports:
- "29092:29092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1

ksqldb-server:
image: confluentinc/ksqldb-server:0.28.3
hostname: ksqldb-server
container_name: ksqldb-server
depends_on:
- broker
ports:
- "8088:8088"
environment:
KSQL_LISTENERS: http://0.0.0.0:8088
KSQL_BOOTSTRAP_SERVERS: broker:9092
KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"
KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"

ksqldb-cli:
image: confluentinc/ksqldb-cli:0.28.3
container_name: ksqldb-cli
depends_on:
- broker
- ksqldb-server
entrypoint: /bin/sh
tty: true

启动KsqlDB服务:

1
docker-compose up

使用Docker链接到Cli中

1
docker exec -it ksqldb-cli

链接到cli中后,在终端运行命令链接:

1
[ksqldb-cli] docker exec -it ksqldb-cli ksql http://ksqldb-server:8088

可以运行命令查看topic

1
show topics;

如果你想链接远端Kafka集群,将 替换成自己的远程Kafka集群,像下面这个dockerfile一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
version: '2'

services:

ksqldb-server-juneyao:
image: confluentinc/ksqldb-server
hostname: ksqldb-server-juneyao
container_name: ksqldb-server-juneyao
ports:
- "8088:8088"
environment:
KSQL_LISTENERS: http://0.0.0.0:8088
KSQL_BOOTSTRAP_SERVERS: 172.22.17.28:9092
KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"
KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"

ksqldb-cli-juneyao:
image: confluentinc/ksqldb-cli
container_name: ksqldb-cli-juneyao
depends_on:
- ksqldb-server-juneyao
entrypoint: /bin/sh
tty: true

后面的操作和上面都一样了,启动的时候注意修改名称

image-20230314162231259

Example 1: 创建流表并查询

展示有多少Topic:

1
2
show topics;
show streams;

在cli链接后,我们创建一个riderLocations流,如果locations这个topic在kafka中不存在,则会对应创建。

1
2
3
4
5
CREATE STREAM riderLocations (
profileId VARCHAR,
latitude DOUBLE,
longitude DOUBLE
) WITH ( kafka_topic='locations', value_format='json', PARTITIONS =1 );

然后往topic中插入一批数据

1
2
3
4
kafka-console-producer --broker-list localhost:9092 --topic locations 
{"profileId":"1","latitude":2.0,"longitude":1.0}
{"profileId":"2","latitude":3.0,"longitude":3.0}
{"profileId":"3","latitude":1.0,"longitude":1.0}

就可以愉快的查询它了

1
2
SET 'auto.offset.reset'='earliest';
select * from riderLocations;

更多的详细用法可以参考网站ksqldb

<1…456>

58 posts
© 2025 Hoey