-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
1300 lines (1300 loc) · 712 KB
/
search.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
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
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Linux设置Swap分区]]></title>
<url>%2F2018%2F03%2F14%2FLinux%E8%AE%BE%E7%BD%AESwap%E5%88%86%E5%8C%BA%2F</url>
<content type="text"><![CDATA[1. 验证不存在交换分区free -m 输出如下: total used free shared buffers cached Mem: 995 947 47 0 46 142 -/+ buffers/cache: 758 236 Swap: 0 0 0 如果swap选项total是0则表示没有交换分区,开始下一步 2. 创建swap分区使用dd命令选择swap分区目录以及大小,在此我们给他放到根目录,创建的是2G的虚拟内存,可以根据自己需要选择大小。 dd if=/dev/zero of=/swapfile count=2048 bs=1M 接下来验证根目录是否存在swapfile ls / | grep swapfile 不出意外的话你将会看到swapfile 3. 激活swap分区交换分区不会自动激活,你需要告诉服务器如何格式化文件,使它作为一个有效的交换分区。 出于安全考虑,交交换区权限设置成600 chmod 600 /swapfile 使用mkswap命令来设置交换文件: mkswap /swapfile 4.开启swap分区swapon /swapfile 再次使用 free -m 查看内存使用情况,输出如下: total used free shared buffers cached Mem: 1840 1754 86 16 23 1519 -/+ buffers/cache: 210 1630 Swap: 2047 0 2047 5. 设置允许开机启用swap分区sudo vi /etc/fstab 在后面加上 /swapfile none swap sw 0 0 6. 如何配置Swappiness操作系统内核可以通过称为swappiness的配置参数调整它依赖交换的频率。 要查找当前的swappiness设置,请键入:cat / proc / sys / vm / swappiness Swapiness可以是0到100之间的值。Swappiness接近100意味着操作系统会经常更换,通常太快。虽然交换提供额外的资源,但RAM比交换空间快得多。任何时候有些东西都从RAM移到交换位置,它就会慢下来。 swappiness值为0意味着操作只会在绝对需要时才依靠交换。我们可以用sysctl命令调整swappiness : sysctl vm.swappiness = 10 为了让您的VPS每次启动时自动应用此设置,您可以将设置添加到/etc/sysctl.conf文件中: 1sudo nano /etc/sysctl.conf #搜索vm.swappiness设置。取消注释并根据需要进行更改。 vm.swappiness = 10]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Swap</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Intellij IDEA搭建hadoop开发环境后系列问题]]></title>
<url>%2F2017%2F10%2F12%2FIntellij%20IDEA%E6%90%AD%E5%BB%BAhadoop%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E5%90%8E%E7%B3%BB%E5%88%97%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[准备工作 1,安装JDK 1.8并配置(配置环境变量) 2,下载和安装IntelliJ IDEA(无需配置环境变量) 3,下载hadoop并且解压缩 实验: 1, 创建maven项目: 提醒:SDK如果没有自动搜索到,可以点‘New’然后自己指定JDK的安装路径 2, 在路径src下任意folder创建java class文件WordCount,我这里用src.main.java, 3,copy 如下code到WordCount 4, 不用修改pom.xml的dependanc.这是很多错误。我们可以把hadoop相关的include进来, 点击file->project structure 如上图添加新的Jars or folder,去hadoop解压路径分别选择和添加如下5个文件夹: 6, 配置 Run - > Edit Configurations, 新建Application 设置Mainclass: WordCount Program Argument : input/ output/ 7, 尝试run下:遇到如下系列错误。 错误1:HADOOP_HOME and hadoop.home.dir are unset 解决:没有设置HADOOP_HOME,所以给hadoop配置环境变量 <img width=”568” height=”105” title=”1517386176939361.png” style=”width: 568px; height: 105px;” alt=”图片.png” src=”/upload/58/47/p> HADOOP_HOME: hadopp解压路径 path: 加入 %HADOOP_HOME%\bin 错误2:Exception in thread “main” java.lang.NullPointerException atjava.lang.ProcessBuilder.start(Unknown Source) 解决:在Hadoop2后官方包里就没有winutils.exe 文件,让windows模拟hadoop环境来进行测试,下载一个放到hadopp解压缩路径的bin目录下面。 具体还想了解的可以访问https://wiki.apache.org/hadoop/WindowsProblems 下载的话可以直接去github上下载自己所需要的版本:https://github.com/steveloughran/winutils 错误3:Error: Exception in thread “main”java.lang.UnsatisfiedLinkError:org.apache.**hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z** 解决:C:\Windows\System32下缺少hadoop.dll,把这个文件拷贝到C:\Windows\System32下面即可。这个文件可以在“问题2”的github链接上找到下载。]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>IDEA</tag>
<tag>Windows</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十七篇:上传文件]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%B8%83%E7%AF%87%EF%BC%9A%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023752本文出自方志朋的博客 这篇文章主要介绍,如何在springboot工程作为服务器,去接收通过http 上传的multi-file的文件。 构建工程为例创建一个springmvc工程你需要spring-boot-starter-thymeleaf和 spring-boot-starter-web的起步依赖。为例能够上传文件在服务器,你需要在web.xml中加入标签做相关的配置,但在sringboot 工程中,它已经为你自动做了,所以不需要你做任何的配置。 1234567891011121314151617<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency></dependencies> 创建文件上传controller直接贴代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152@Controllerpublic class FileUploadController { private final StorageService storageService; @Autowired public FileUploadController(StorageService storageService) { this.storageService = storageService; } @GetMapping("/") public String listUploadedFiles(Model model) throws IOException { model.addAttribute("files", storageService .loadAll() .map(path -> MvcUriComponentsBuilder .fromMethodName(FileUploadController.class, "serveFile", path.getFileName().toString()) .build().toString()) .collect(Collectors.toList())); return "uploadForm"; } @GetMapping("/files/{filename:.+}") @ResponseBody public ResponseEntity<Resource> serveFile(@PathVariable String filename) { Resource file = storageService.loadAsResource(filename); return ResponseEntity .ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+file.getFilename()+"\"") .body(file); } @PostMapping("/") public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { storageService.store(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/"; } @ExceptionHandler(StorageFileNotFoundException.class) public ResponseEntity handleStorageFileNotFound(StorageFileNotFoundException exc) { return ResponseEntity.notFound().build(); }} 这个类通过@Controller注解,表明自己上一个Spring mvc的c。每个方法通过@GetMapping 或者@PostMapping注解表明自己的 http方法。 GET / 获取已经上传的文件列表 GET /files/{filename} 下载已经存在于服务器的文件 POST / 上传文件给服务器 创建一个简单的 html模板为了展示上传文件的过程,我们做一个界面:在src/main/resources/templates/uploadForm.html 1234567891011121314151617181920212223242526<html xmlns:th="http://www.thymeleaf.org"><body> <div th:if="${message}"> <h2 th:text="${message}"/> </div> <div> <form method="POST" enctype="multipart/form-data" action="/"> <table> <tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr> <tr><td></td><td><input type="submit" value="Upload" /></td></tr> </table> </form> </div> <div> <ul> <li th:each="file : ${files}"> <a th:href="${file}" th:text="${file}" /> </li> </ul> </div></body></html> 上传文件大小限制如果需要限制上传文件的大小也很简单,只需要在springboot 工程的src/main/resources/application.properties 加入以下: 12spring.http.multipart.max-file-size=128KBspring.http.multipart.max-request-size=128KB 测试测试情况如图: 参考资料https://spring.io/guides/gs/uploading-files/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第十篇 高可用的服务注册中心]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%8D%81%E7%AF%87%20%E9%AB%98%E5%8F%AF%E7%94%A8%E7%9A%84%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70183572本文出自方志朋的博客 文章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 介绍了服务注册与发现,其中服务注册中心Eureka Server,是一个实例,当成千上万个服务向它注册的时候,它的负载是非常高的,这在生产环境上是不太合适的,这篇文章主要介绍怎么将Eureka Server集群化。 一、准备工作 Eureka can be made even more resilient and available by running multiple instances and asking them to register with each other. In fact, this is the default behaviour, so all you need to do to make it work is add a valid serviceUrl to a peer, e.g. 摘自官网 Eureka通过运行多个实例,使其更具有高可用性。事实上,这是它默认的熟性,你需要做的就是给对等的实例一个合法的关联serviceurl。 这篇文章我们基于第一篇文章的工程,来做修改。 二、改造工作在eureka-server工程中resources文件夹下,创建配置文件application-peer1.yml: 1234567891011server: port: 8761spring: profiles: peer1eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2:8769/eureka/ 并且创建另外一个配置文件application-peer2.yml: 1234567891011server: port: 8769spring: profiles: peer2eureka: instance: hostname: peer2 client: serviceUrl: defaultZone: http://peer1:8761/eureka/ 这时eureka-server就已经改造完毕。 ou could use this configuration to test the peer awareness on a single host (there’s not much value in doing that in production) by manipulating /etc/hosts to resolve the host names. 按照官方文档的指示,需要改变etc/hosts,linux系统通过vim /etc/hosts ,加上: 12127.0.0.1 peer1127.0.0.1 peer2 windows电脑,在c:/windows/systems/drivers/etc/hosts 修改。 这时需要改造下service-hi: 123456789eureka: client: serviceUrl: defaultZone: http://peer1:8761/eureka/server: port: 8762spring: application: name: service-hi 三、启动工程启动eureka-server: java -jar eureka-server-0.0.1-SNAPSHOT.jar - -spring.profiles.active=peer1 java -jar eureka-server-0.0.1-SNAPSHOT.jar - -spring.profiles.active=peer2 > 启动service-hi: java -jar service-hi-0.0.1-SNAPSHOT.jar 访问:localhost:8761,如图: 你会发现注册了service-hi,并且有个peer2节点,同理访问localhost:8769你会发现有个peer1节点。 client只向8761注册,但是你打开8769,你也会发现,8769也有 client的注册信息。 个人感受:这是通过看官方文档的写的demo ,但是需要手动改host是不是不符合Spring Cloud 的高上大? Prefer IP AddressIn some cases, it is preferable for Eureka to advertise the IP Adresses of services rather than the hostname. Set eureka.instance.preferIpAddress to true and when the application registers with eureka, it will use its IP Address rather than its hostname. 摘自官网 eureka.instance.preferIpAddress=true是通过设置ip让eureka让其他服务注册它。也许能通过去改变去通过改变host的方式。 此时的架构图: Eureka-eserver peer1 8761,Eureka-eserver peer2 8769相互感应,当有服务注册时,两个Eureka-eserver是对等的,它们都存有相同的信息,这就是通过服务器的冗余来增加可靠性,当有一台服务器宕机了,服务并不会终止,因为另一台服务存有相同的数据。 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter10 四、参考文献high_availability_zones 相关文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第十四篇 服务注册(consul)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AF%87%20%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C(consul)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70245644本文出自方志朋的博客 这篇文章主要介绍 spring cloud consul 组件,它是一个提供服务发现和配置的工具。consul具有分布式、高可用、高扩展性。 一、consul 简介consul 具有以下性质: 服务发现:consul通过http 方式注册服务,并且服务与服务之间相互感应。 服务健康监测 key/value 存储 多数据中心 consul可运行在mac windows linux 等机器上。 二、consul安装linux 12345$ mkdir -p $GOPATH/src/github.com/hashicorp && cd $!$ git clone https://github.com/hashicorp/consul.git$ cd consul$ make bootstrap$ make bootstrap windows下安装:见consul怎么在windows下安装 三、构建工程构建一个consul-miya的springboot工程,导入依赖pring-cloud-starter-consul-discovery,其依赖文件: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566<?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>com.forezp</groupId> <artifactId>consul-miya</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>consul-miya</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> 在其入口文件ConsulMiyaApplication加入注解@EnableDiscoveryClient,开启服务发现: 1234567891011121314@SpringBootApplication@EnableDiscoveryClient@RestControllerpublic class ConsulMiyaApplication { @RequestMapping("/hi") public String home() { return "hi ,i'm miya"; } public static void main(String[] args) { new SpringApplicationBuilder(ConsulMiyaApplication.class).web(true).run(args); }} 在其配置文件application.yml指定consul服务的端口为8500: 12345678910111213spring: cloud: consul: host: localhost port: 8500 discovery: healthCheckPath: ${management.contextPath}/health healthCheckInterval: 15s instance-id: consul-miya application: name: consul-miyaserver: port: 8502 启动工程,访问localhost:8500,可以发现consul-miya被注册了。 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter14 四、参考资料HashiCorp/consul Spring Cloud Consul consul.io]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第九篇 服务链路追踪(Spring Cloud Sleuth)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%B9%9D%E7%AF%87%20%E6%9C%8D%E5%8A%A1%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA(Spring%20Cloud%20Sleuth)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70162074本文出自方志朋的博客 这篇文章主要讲述服务追踪组件zipkin,Spring Cloud Sleuth集成了zipkin组件。 一、简介 Add sleuth to the classpath of a Spring Boot application (see below for Maven and Gradle examples), and you will see the correlation data being collected in logs, as long as you are logging requests. —— 摘自官网 Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可。 二、服务追踪分析微服务架构上通过业务来划分服务的,通过REST调用,对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败。随着业务的不断扩张,服务之间互相调用会越来越复杂。 随着服务的越来越多,对调用链的分析会越来越复杂。它们之间的调用关系也许如下: 三、术语 Span:基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。 Trace:一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能需要创建一个trace。 Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束 cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始 sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟 ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间 cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间将Span和Trace在一个系统中使用Zipkin注解的过程图形化: 将Span和Trace在一个系统中使用Zipkin注解的过程图形化: 四、构建工程基本知识讲解完毕,下面我们来实战,本文的案例主要有三个工程组成:一个server-zipkin,它的主要作用使用ZipkinServer 的功能,收集调用数据,并展示;一个service-hi,对外暴露hi接口;一个service-miya,对外暴露miya接口;这两个service可以相互调用;并且只有调用了,server-zipkin才会收集数据的,这就是为什么叫服务追踪了。 4.1 构建server-zipkin建一个spring-boot工程取名为server-zipkin,在其pom引入依赖: 123456789101112131415161718192021222324252627282930313233343536373839<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-server</artifactId> </dependency> <dependency> <groupId>io.zipkin.java</groupId> <artifactId>zipkin-autoconfigure-ui</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> 在其程序入口类, 加上注解@EnableZipkinServer,开启ZipkinServer的功能: 12345678@SpringBootApplication@EnableZipkinServerpublic class ServerZipkinApplication { public static void main(String[] args) { SpringApplication.run(ServerZipkinApplication.class, args); }} 在配置文件application.yml指定服务端口为: 1server.port=94111 4.2 创建service-hi在其pom引入起步依赖spring-cloud-starter-zipkin,代码如下: 12345678910111213141516171819202122232425262728293031<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--compile('org.springframework.cloud:spring-cloud-starter-zipkin')--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement> 在其配置文件application.yml指定zipkin server的地址,头通过配置“spring.zipkin.base-url”指定: 123server.port=8988spring.zipkin.base-url=http://localhost:9411spring.application.name=service-hi 通过引入spring-cloud-starter-zipkin依赖和设置spring.zipkin.base-url就可以了。 对外暴露接口: 12345678910111213141516171819202122232425262728293031323334353637@SpringBootApplication@RestControllerpublic class ServiceHiApplication { public static void main(String[] args) { SpringApplication.run(ServiceHiApplication.class, args); } private static final Logger LOG = Logger.getLogger(ServiceHiApplication.class.getName()); @Autowired private RestTemplate restTemplate; @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } @RequestMapping("/hi") public String callHome(){ LOG.log(Level.INFO, "calling trace service-hi "); return restTemplate.getForObject("http://localhost:8989/miya", String.class); } @RequestMapping("/info") public String info(){ LOG.log(Level.INFO, "calling trace service-hi "); return "i'm service-hi"; } @Bean public AlwaysSampler defaultSampler(){ return new AlwaysSampler(); }} 4.3 创建service-miya创建过程痛service-hi,引入相同的依赖,配置下spring.zipkin.base-url。 对外暴露接口: 12345678910111213141516171819202122232425262728293031@SpringBootApplication@RestControllerpublic class ServiceMiyaApplication { public static void main(String[] args) { SpringApplication.run(ServiceMiyaApplication.class, args); } private static final Logger LOG = Logger.getLogger(ServiceMiyaApplication.class.getName()); @RequestMapping("/hi") public String home(){ LOG.log(Level.INFO, "hi is being called"); return "hi i'm miya!"; } @RequestMapping("/miya") public String info(){ LOG.log(Level.INFO, "info is being called"); return restTemplate.getForObject("http://localhost:8988/info",String.class); } @Autowired private RestTemplate restTemplate; @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); }} 4.4 启动工程,演示追踪依次启动上面的三个工程,打开浏览器访问:http://localhost:9411/,会出现以下界面: 访问:http://localhost:8989/miya,浏览器出现: i’m service-hi 再打开http://localhost:9411/的界面,点击Dependencies,可以发现服务的依赖关系: 点击find traces,可以看到具体服务相互调用的数据: 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter9 五、参考资料spring-cloud-sleuth 利用Zipkin对Spring Cloud应用进行服务追踪分析 Spring Cloud Sleuth使用简介 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第八篇 消息总线(Spring Cloud Bus)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%85%AB%E7%AF%87%20%E6%B6%88%E6%81%AF%E6%80%BB%E7%BA%BF(Spring%20Cloud%20Bus)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70148235本文出自方志朋的博客 Spring Cloud Bus 将分布式的节点用轻量的消息代理连接起来。它可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控。本文要讲述的是用Spring Cloud Bus实现通知微服务架构的配置文件的更改。 一、准备工作本文还是基于上一篇文章来实现。按照官方文档,我们只需要在配置文件中配置 spring-cloud-starter-bus-amqp ;这就是说我们需要装rabbitMq,点击rabbitmq下载。至于怎么使用 rabbitmq,搜索引擎下。 二、改造config-client在pom文件加上起步依赖spring-cloud-starter-bus-amqp,完整的配置文件如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101<?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>com.forezp</groupId> <artifactId>config-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>config-client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在配置文件application.properties中加上RabbitMq的配置,包括RabbitMq的地址、端口,用户名、密码,代码如下: 1234spring.rabbitmq.host=localhostspring.rabbitmq.port=5672# spring.rabbitmq.username=# spring.rabbitmq.password= 如果rabbitmq有用户名密码,输入即可。 依次启动eureka-server、confg-cserver,启动两个config-client,端口为:8881、8882。 访问http://localhost:8881/hi 或者http://localhost:8882/hi 浏览器显示: foo version 3 这时我们去代码仓库将foo的值改为“foo version 4”,即改变配置文件foo的值。如果是传统的做法,需要重启服务,才能达到配置文件的更新。此时,我们只需要发送post请求:http://localhost:8881/bus/refresh,你会发现config-client会重现肚脐配置文件 重新读取配置文件: 这时我们再访问http://localhost:8881/hi 或者http://localhost:8882/hi 浏览器显示: foo version 4 另外,/bus/refresh接口可以指定服务,即使用”destination”参数,比如 “/bus/refresh?destination=customers:**” 即刷新服务名为customers的所有服务,不管ip。 三、分析此时的架构图: 当git文件更改的时候,通过pc端用post 向端口为8882的config-client发送请求/bus/refresh/;此时8882端口会发送一个消息,由消息总线向其他服务传递,从而使整个微服务集群都达到更新配置文件。 四、其他扩展(可忽视)可以用作自定义的Message Broker,只需要spring-cloud-starter-bus-amqp, 然后再配置文件写上配置即可,同上。 Tracing Bus Events:需要设置:spring.cloud.bus.trace.enabled=true,如果那样做的话,那么Spring Boot TraceRepository(如果存在)将显示每个服务实例发送的所有事件和所有的ack,比如:(来自官网) 123456789101112131415161718192021222324252627282930{ "timestamp": "2015-11-26T10:24:44.411+0000", "info": { "signal": "spring.cloud.bus.ack", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "stores:8081", "destination": "*:**" } }, { "timestamp": "2015-11-26T10:24:41.864+0000", "info": { "signal": "spring.cloud.bus.sent", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "customers:9000", "destination": "*:**" } }, { "timestamp": "2015-11-26T10:24:41.862+0000", "info": { "signal": "spring.cloud.bus.ack", "type": "RefreshRemoteApplicationEvent", "id": "c4d374b7-58ea-4928-a312-31984def293b", "origin": "customers:9000", "destination": "*:**" }} 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter8 五、参考资料spring_cloud_bus]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第十一篇docker部署spring cloud项目]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AF%87docker%E9%83%A8%E7%BD%B2spring%20cloud%E9%A1%B9%E7%9B%AE%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70198649本文出自方志朋的博客 一、docker简介Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。Docker通常用于如下场景: web应用的自动化打包和发布; 自动化测试和持续集成、发布; 在服务型环境中部署和调整数据库或其他的后台应用; 从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境。 Docker 的优点 1、简化程序:Docker 让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,便可以实现虚拟化。Docker改变了虚拟化的方式,使开发者可以直接将自己的成果放入Docker中进行管理。方便快捷已经是 Docker的最大优势,过去需要用数天乃至数周的 任务,在Docker容器的处理下,只需要数秒就能完成。 2、避免选择恐惧症:如果你有选择恐惧症,还是资深患者。Docker 帮你 打包你的纠结!比如 Docker 镜像;Docker 镜像中包含了运行环境和配置,所以 Docker 可以简化部署多种应用实例工作。比如 Web 应用、后台应用、数据库应用、大数据应用比如 Hadoop 集群、消息队列等等都可以打包成一个镜像部署。 3、节省开支:一方面,云计算时代到来,使开发者不必为了追求效果而配置高额的硬件,Docker 改变了高性能必然高价格的思维定势。Docker 与云的结合,让云空间得到更充分的利用。不仅解决了硬件管理的问题,也改变了虚拟化的方式。 上面文字参考了相关文章;另,关于docker 的安装和基本的使用见相关教程。 二、准备工作环境条件: linux系统,不建议windows docker最新版本 jdk 1.8 maven3.0 本文采用的工程来自第一篇文章的工程,采用maven的方式去构建项目,并采用docker-maven-plugin去构建docker镜像。 三、改造工程、构建镜像改造eureka-server工程在pom文件加上插件: 1234567891011121314151617181920212223242526<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- tag::plugin[] --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.3</version> <configuration> <imageName>${docker.image.prefix}/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <!-- end::plugin[] --> </plugins> </build> Spotify 的 docker-maven-plugin 插件是用maven插件方式构建docker镜像的。 imageName指定了镜像的名字,本例为 forep/eureka-server dockerDirectory指定 Dockerfile 的位置 resources是指那些需要和 Dockerfile 放在一起,在构建镜像时使用的文件,一般应用 jar 包需要纳入。 修改下配置文件: 12345678server: port: 8761eureka: instance: prefer-ip-address: true client: registerWithEureka: false fetchRegistry: false 编写dockerfile文件:123456FROM frolvlad/alpine-oraclejdk8:slimVOLUME /tmpADD eureka-server-0.0.1-SNAPSHOT.jar app.jar#RUN bash -c 'touch /app.jar'ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]EXPOSE 8761 docker file编写指令: FROM 123FROM <image>FROM <image>:<tag>FROM <image> <digest> FROM指令必须指定且需要在Dockerfile其他指令的前面,指定的基础image可以是官方远程仓库中的,也可以位于本地仓库。后续的指令都依赖于该指令指定的image。当在同一个Dockerfile中建立多个镜像时,可以使用多个FROM指令。 VOLUME 格式为: 1VOLUME ["/data"]1 使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器。当容器中的应用有持久化数据的需求时可以在Dockerfile中使用该指令。 ADD 从src目录复制文件到容器的dest。其中src可以是Dockerfile所在目录的相对路径,也可以是一个URL,还可以是一个压缩包 ENTRYPOINT 指定Docker容器启动时执行的命令,可以多次设置,但是只有最后一个有效。 EXPOSE 为Docker容器设置对外的端口号。在启动时,可以使用-p选项或者-P选项。 构建镜像执行构建docker镜像maven命令: 12mvn cleanmvn package docker:build 构建eureka-server镜像成功。 同理构建service-hi镜像 pom文件导入同eurek-server 修改下配置文件: 123456789eureka: client: serviceUrl: defaultZone: http://eureka-server:8761/eureka/ # 这个需要改为eureka-serverserver: port: 8763spring: application: name: service-hi 在这里说下:defaultZone发现服务的host改为镜像名。 dockefile 编写同eureka-server 构建镜像: 12mvn cleanmvn package docker:build 这时我们运行docke的eureka-server 和service-hi镜像: 12docker run -p 8761: 8761 -t forezp/eureka-serverdocker run -p 8763: 8763 -t forezp/service-hi 访问localhost:8761 四、采用docker-compose启动镜像Compose 是一个用于定义和运行多容器的Docker应用的工具。使用Compose,你可以在一个配置文件(yaml格式)中配置你应用的服务,然后使用一个命令,即可创建并启动配置中引用的所有服务。下面我们进入Compose的实战吧。 采用docker-compose的方式编排镜像,启动镜像: 12345678910111213version: '3'services: eureka-server: image: forezp/eureka-server restart: always ports: - 8761:8761 service-hi: image: forezp/service-hi restart: always ports: - 8763:8763 输入命令: docker-compose up 发现2个镜像按照指定的顺序启动了。 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter11 五、采用docker-compose编排并启动镜像docker-compose也可以构建镜像,现在我们采用docker-compose的方式构建镜像。 现在以eureka-server为例:将Dockerfile移到eureka-server的主目录,改写ADD的相对路径: 123456FROM frolvlad/alpine-oraclejdk8:slimVOLUME /tmpADD ./target/eureka-server-0.0.1-SNAPSHOT.jar app.jar#RUN bash -c 'touch /app.jar'ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]EXPOSE 8761 同理修改service-hi目录; 编写构建镜像docker-compose-dev文件: 1234567891011version: '3'services: eureka-server: build: eureka-server ports: - 8761:8761 service-hi: build: service-hi ports: - 8763:8763 命令构建镜像并启动: 1docker-compose -f docker-compose.yml -f docker-compose-dev.yml up 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter11-2 六、参考文献docker教程 用 Docker 构建、运行、发布一个 Spring Boot 应用 docker-compose 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第六篇 分布式配置中心(Spring Cloud Config)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%85%AD%E7%AF%87%20%E5%88%86%E5%B8%83%E5%BC%8F%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83(Spring%20Cloud%20Config)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70037291本文出自方志朋的博客在上一篇文章讲述zuul的时候,已经提到过,使用配置服务来保存各个服务的配置文件。它就是Spring Cloud Config。 一、简介在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。 二、构建Config Server创建一个spring-boot项目,取名为config-server,其pom.xml: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879<?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>com.forezp</groupId> <artifactId>config-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>config-server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在程序的入口Application类加上@EnableConfigServer注解开启配置服务器的功能,代码如下: 12345678@SpringBootApplication@EnableConfigServerpublic class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); }} 需要在程序的配置文件application.properties文件配置以下: 123456789spring.application.name=config-serverserver.port=8888spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/spring.cloud.config.server.git.searchPaths=respospring.cloud.config.label=masterspring.cloud.config.server.git.username=your usernamespring.cloud.config.server.git.password=your password spring.cloud.config.server.git.uri:配置git仓库地址 spring.cloud.config.server.git.searchPaths:配置仓库路径 spring.cloud.config.label:配置仓库的分支 spring.cloud.config.server.git.username:访问git仓库的用户名 spring.cloud.config.server.git.password:访问git仓库的用户密码 如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写,本例子是公开仓库,放心使用。 远程仓库https://github.com/forezp/SpringcloudConfig/ 中有个文件config-client-dev.properties文件中有一个属性: foo = foo version 3 启动程序:访问http://localhost:8888/foo/dev 12{"name":"foo","profiles":["dev"],"label":"master","version":"792ffc77c03f4b138d28e89b576900ac5e01a44b","state":null,"propertySources":[]} 证明配置服务中心可以从远程程序获取配置信息。 http请求地址和资源文件映射如下: /{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties 三、构建一个config client重新创建一个springboot项目,取名为config-client,其pom文件: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778<?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>com.forezp</groupId> <artifactId>config-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>config-client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 其配置文件bootstrap.properties: 12345spring.application.name=config-clientspring.cloud.config.label=masterspring.cloud.config.profile=devspring.cloud.config.uri= http://localhost:8888/server.port=8881 spring.cloud.config.label 指明远程仓库的分支 spring.cloud.config.profile dev开发环境配置文件 test测试环境 pro正式环境 spring.cloud.config.uri= http://localhost:8888/ 指明配置服务中心的网址。 程序的入口类,写一个API接口“/hi”,返回从配置中心读取的foo变量的值,代码如下: 123456789101112131415@SpringBootApplication@RestControllerpublic class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } @Value("${foo}") String foo; @RequestMapping(value = "/hi") public String hi(){ return foo; }} 打开网址访问:http://localhost:8881/hi,网页显示: foo version 3 这就说明,config-client从config-server获取了foo的属性,而config-server是从git仓库读取的,如图: 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter6 四、参考资料spring_cloud_config 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第二篇 服务消费者(rest+ribbon)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%BA%8C%E7%AF%87%20%E6%9C%8D%E5%8A%A1%E6%B6%88%E8%B4%B9%E8%80%85%EF%BC%88rest%2Bribbon%EF%BC%89%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/69788938本文出自方志朋的博客 在上一篇文章,讲了服务的注册和发现。在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。在这一篇文章首先讲解下基于ribbon+rest。 一、ribbon简介 Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies. —–摘自官网 ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。 ribbon 已经默认实现了这些配置bean: IClientConfig ribbonClientConfig: DefaultClientConfigImpl IRule ribbonRule: ZoneAvoidanceRule IPing ribbonPing: NoOpPing ServerList ribbonServerList: ConfigurationBasedServerList ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer 二、准备工作这一篇文章基于上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为8762;将service-hi的配置文件的端口改为8763,并启动,这时你会发现:service-hi在eureka-server注册了2个实例,这就相当于一个小的集群。访问localhost:8761如图所示: 三、建一个服务消费者重新新建一个spring-boot工程,取名为:service-ribbon;在它的pom.xml文件分别引入起步依赖spring-cloud-starter-eureka、spring-cloud-starter-ribbon、spring-boot-starter-web,代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081<?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>com.forezp</groupId> <artifactId>service-ribbon</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-ribbon</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在工程的配置文件指定服务的注册中心地址为http://localhost:8761/eureka/,程序名称为 service-ribbon,程序端口为8764。配置文件application.yml如下: 123456789eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/server: port: 8764spring: application: name: service-ribbon123456789 在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册;并且向程序的ioc注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。 123456789101112131415@SpringBootApplication@EnableDiscoveryClientpublic class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run(ServiceRibbonApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); }} 写一个测试类HelloService,通过之前注入ioc容器的restTemplate来消费service-hi服务的“/hi”接口,在这里我们直接用的程序名替代了具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名,代码如下: 1234567891011@Servicepublic class HelloService { @Autowired RestTemplate restTemplate; public String hiService(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class); }} 写一个controller,在controller中用调用HelloService 的方法,代码如下: 123456789101112131415/** * Created by fangzhipeng on 2017/4/6. */@RestControllerpublic class HelloControler { @Autowired HelloService helloService; @RequestMapping(value = "/hi") public String hi(@RequestParam String name){ return helloService.hiService(name); }} 在浏览器上多次访问http://localhost:8764/hi?name=forezp,浏览器交替显示: hi forezp,i am from port:8762 hi forezp,i am from port:8763 这说明当我们通过调用restTemplate.getForObject(“http://SERVICE-HI/hi?name=“+name,String.class)方法时,已经做了负载均衡,访问了不同的端口的服务实例。 四、此时的架构 一个服务注册中心,eureka server,端口为8761 service-hi工程跑了两个实例,端口分别为8762,8763,分别向服务注册中心注册 sercvice-ribbon端口为8764,向服务注册中心注册 当sercvice-ribbon通过restTemplate调用service-hi的hi接口时,因为用ribbon进行了负载均衡,会轮流的调用service-hi:8762和8763 两个端口的hi接口; 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter2 五、参考资料本文参考了以下: spring-cloud-ribbon springcloud ribbon with eureka 服务消费者]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第五篇 路由网关(zuul)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%BA%94%E7%AF%87%20%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3(zuul)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/69939114本文出自方志朋的博客 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简答的微服务系统如下图: 注意:A服务和B服务是可以相互调用的,作图的时候忘记了。并且配置服务也是注册到服务注册中心的。 在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。 一、Zuul简介Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。 zuul有以下功能: Authentication Insights Stress Testing Canary Testing Dynamic Routing Service Migration Load Shedding Security Static Response handling Active/Active traffic management 二、准备工作继续使用上一节的工程。在原有的工程上,创建一个新的工程。 三、创建service-zuul工程其pom.xml文件如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879<?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>com.forezp</groupId> <artifactId>service-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-zuul</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能: 123456789@EnableZuulProxy@EnableEurekaClient@SpringBootApplicationpublic class ServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(ServiceZuulApplication.class, args); }} 加上配置文件application.yml加上以下的配置代码: 1234567891011121314151617eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/server: port: 8769spring: application: name: service-zuulzuul: routes: api-a: path: /api-a/** serviceId: service-ribbon api-b: path: /api-b/** serviceId: service-feign 首先指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8769,服务名为service-zuul;以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务; 依次运行这五个工程;打开浏览器访问:http://localhost:8769/api-a/hi?name=forezp ;浏览器显示: hi forezp,i am from port:8762 打开浏览器访问:http://localhost:8769/api-b/hi?name=forezp ;浏览器显示: hi forezp,i am from port:8762 这说明zuul起到了路由的作用 四、服务过滤zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程; 123456789101112131415161718192021222324252627282930313233343536373839@Componentpublic class MyFilter extends ZuulFilter{ private static Logger log = LoggerFactory.getLogger(MyFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if(accessToken == null) { log.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); }catch (Exception e){} return null; } log.info("ok"); return null; }} filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下: pre:路由之前 routing:路由之时 post: 路由之后 error:发送错误调用 filterOrder:过滤的顺序 shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。 run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。 这时访问:http://localhost:8769/api-a/hi?name=forezp ;网页显示: token is empty 访问 http://localhost:8769/api-a/hi?name=forezp&token=22 ;网页显示: hi forezp,i am from port:8762 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter5 五、参考资料:router_and_filter_zuul 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第十三篇 断路器聚合监控(Hystrix Turbine)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%8D%81%E4%B8%89%E7%AF%87%20%E6%96%AD%E8%B7%AF%E5%99%A8%E8%81%9A%E5%90%88%E7%9B%91%E6%8E%A7(Hystrix%20Turbine)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70233227本文出自方志朋的博客 上一篇文章讲述了如何利用Hystrix Dashboard去监控断路器的Hystrix command。当我们有很多个服务的时候,这就需要聚合所以服务的Hystrix Dashboard的数据了。这就需要用到Spring Cloud的另一个组件了,即Hystrix Turbine。 一、Hystrix Turbine简介看单个的Hystrix Dashboard的数据并没有什么多大的价值,要想看这个系统的Hystrix Dashboard数据就需要用到Hystrix Turbine。Hystrix Turbine将每个服务Hystrix Dashboard数据进行了整合。Hystrix Turbine的使用非常简单,只需要引入相应的依赖和加上注解和配置就可以了。 二、准备工作本文使用的工程为上一篇文章的工程,在此基础上进行改造。因为我们需要多个服务的Dashboard,所以需要再建一个服务,取名为service-lucy,它的基本配置同service-hi,具体见源码,在这里就不详细说明。 三、创建service-turbine引入相应的依赖: 1234567891011121314151617181920<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies> 在其入口类ServiceTurbineApplication加上注解@EnableTurbine,开启turbine,@EnableTurbine注解包含了@EnableDiscoveryClient注解,即开启了注册服务。 123456789@SpringBootApplication@EnableTurbinepublic class ServiceTurbineApplication { public static void main(String[] args) { new SpringApplicationBuilder(ServiceTurbineApplication.class).web(true).run(args); }} 配置文件application.yml: 1234567891011121314151617spring: application.name: service-turbineserver: port: 8769security.basic.enabled: falseturbine: aggregator: clusterConfig: default # 指定聚合哪些集群,多个使用","分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问 appConfig: service-hi,service-lucy ### 配置Eureka中的serviceId列表,表明监控哪些服务 clusterNameExpression: new String("default") # 1. clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称 # 2. 当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default # 3. 当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: ABC,则需要配置,同时turbine.aggregator.clusterConfig: ABCeureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ 配置文件注解写的很清楚。 四、Turbine演示依次开启eureka-server、service-hi、service-lucy、service-turbine工程。 打开浏览器输入:http://localhost:8769/turbine.stream,界面如下: 依次请求: http://localhost:8762/hi?name=forezp http://localhost:8763/hi?name=forezp 打开:http://localhost:8763/hystrix,输入监控流http://localhost:8769/turbine.stream 点击monitor stream 进入页面: 可以看到这个页面聚合了2个service的hystrix dashbord数据。 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter13 五、参考文献hystrix_dashboard turbine 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第三篇 服务消费者(Feign)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%B8%89%E7%AF%87%20%E6%9C%8D%E5%8A%A1%E6%B6%88%E8%B4%B9%E8%80%85%EF%BC%88Feign%EF%BC%89%2F</url>
<content type="text"><![CDATA[上一篇文章,讲述了如何通过RestTemplate+Ribbon去消费服务,这篇文章主要讲述如何通过Feign去消费服务。 一、Feign简介Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。 简而言之: Feign 采用的是基于接口的注解 Feign 整合了ribbon 二、准备工作继续用上一节的工程, 启动eureka-server,端口为8761; 启动service-hi 两次,端口分别为8762 、8773. 三、创建一个feign的服务新建一个spring-boot工程,取名为serice-feign,在它的pom文件引入Feign的起步依赖spring-cloud-starter-feign、Eureka的起步依赖spring-cloud-starter-eureka、Web的起步依赖spring-boot-starter-web,代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081<?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>com.forezp</groupId> <artifactId>service-feign</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-feign</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在工程的配置文件application.yml文件,指定程序名为service-feign,端口号为8765,服务注册地址为http://localhost:8761/eureka/ ,代码如下: 123456789eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/server: port: 8765spring: application: name: service-feign 在程序的启动类ServiceFeignApplication ,加上@EnableFeignClients注解开启Feign的功能: 123456789@SpringBootApplication@EnableDiscoveryClient@EnableFeignClientspublic class ServiceFeignApplication { public static void main(String[] args) { SpringApplication.run(ServiceFeignApplication.class, args); }} 定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务。比如在代码中调用了service-hi服务的“/hi”接口,代码如下: 12345678/** * Created by fangzhipeng on 2017/4/6. */@FeignClient(value = "service-hi")public interface SchedualServiceHi { @RequestMapping(value = "/hi",method = RequestMethod.GET) String sayHiFromClientOne(@RequestParam(value = "name") String name);} 在Web层的controller层,对外暴露一个”/hi”的API接口,通过上面定义的Feign客户端SchedualServiceHi 来消费服务。代码如下: 12345678910@RestControllerpublic class HiController { @Autowired SchedualServiceHi schedualServiceHi; @RequestMapping(value = "/hi",method = RequestMethod.GET) public String sayHi(@RequestParam String name){ return schedualServiceHi.sayHiFromClientOne(name); }} 启动程序,多次访问http://localhost:8765/hi?name=forezp,浏览器交替显示: hi forezp,i am from port:8762 hi forezp,i am from port:8763 Feign源码解析:http://blog.csdn.net/forezp/article/details/73480304 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter3 五、参考资料spring-cloud-feign 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第四篇断路器(Hystrix)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%9B%9B%E7%AF%87%E6%96%AD%E8%B7%AF%E5%99%A8%EF%BC%88Hystrix%EF%BC%89%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/69934399本文出自方志朋的博客 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。 为了解决这个问题,业界提出了断路器模型。 一、断路器简介 Netflix has created a library called Hystrix that implements the circuit breaker pattern. In a microservice architecture it is common to have multiple layers of service calls. . —-摘自官网 Netflix开源了Hystrix组件,实现了断路器模式,SpringCloud对这一组件进行了整合。 在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图: 较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystric 是5秒20次) 断路器将会被打开。 断路打开后,可用避免连锁故障,fallback方法可以直接返回一个固定值。 二、准备工作这篇文章基于上一篇文章的工程,首先启动上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为8762。 三、在ribbon使用断路器改造serice-ribbon 工程的代码,首先在pox.xml文件中加入spring-cloud-starter-hystrix的起步依赖: 1234<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId></dependency> 在程序的启动类ServiceRibbonApplication 加@EnableHystrix注解开启Hystrix: 12345678910111213141516@SpringBootApplication@EnableDiscoveryClient@EnableHystrixpublic class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run(ServiceRibbonApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); }} 改造HelloService类,在hiService方法上加上@HystrixCommand注解。该注解对该方法创建了熔断器的功能,并指定了fallbackMethod熔断方法,熔断方法直接返回了一个字符串,字符串为”hi,”+name+”,sorry,error!”,代码如下: 123456789101112131415@Servicepublic class HelloService { @Autowired RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "hiError") public String hiService(String name) { return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class); } public String hiError(String name) { return "hi,"+name+",sorry,error!"; }} 启动:service-ribbon 工程,当我们访问http://localhost:8764/hi?name=forezp,浏览器显示: hi forezp,i am from port:8762 此时关闭 service-hi 工程,当我们再访问http://localhost:8764/hi?name=forezp,浏览器会显示: hi ,forezp,orry,error! 这就说明当 service-hi 工程不可用的时候,service-ribbon调用 service-hi的API接口时,会执行快速失败,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞。 四、Feign中使用断路器Feign是自带断路器的,在D版本的Spring Cloud中,它没有默认打开。需要在配置文件中配置打开它,在配置文件加以下代码: feign.hystrix.enabled=true 基于service-feign工程进行改造,只需要在FeignClient的SchedualServiceHi接口的注解中加上fallback的指定类就行了: 12345@FeignClient(value = "service-hi",fallback = SchedualServiceHiHystric.class)public interface SchedualServiceHi { @RequestMapping(value = "/hi",method = RequestMethod.GET) String sayHiFromClientOne(@RequestParam(value = "name") String name);} SchedualServiceHiHystric需要实现SchedualServiceHi 接口,并注入到Ioc容器中,代码如下: 1234567@Componentpublic class SchedualServiceHiHystric implements SchedualServiceHi { @Override public String sayHiFromClientOne(String name) { return "sorry "+name; }} 启动四servcie-feign工程,浏览器打开http://localhost:8765/hi?name=forezp,注意此时service-hi工程没有启动,网页显示: sorry forezp 打开service-hi工程,再次访问,浏览器显示: > hi forezp,i am from port:8762 这证明断路器起到作用了。 五、Hystrix Dashboard (断路器:Hystrix 仪表盘) 基于service-ribbon 改造,Feign的改造和这一样。 首选在pom.xml引入spring-cloud-starter-hystrix-dashboard的起步依赖: 123456789<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId></dependency> 在主程序启动类中加入@EnableHystrixDashboard注解,开启hystrixDashboard: 1234567891011121314151617@SpringBootApplication@EnableDiscoveryClient@EnableHystrix@EnableHystrixDashboardpublic class ServiceRibbonApplication { public static void main(String[] args) { SpringApplication.run(ServiceRibbonApplication.class, args); } @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); }} 打开浏览器:访问http://localhost:8764/hystrix,界面如下: 点击monitor stream,进入下一个界面,访问:http://localhost:8764/hi?name=forezp 此时会出现监控界面: 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter4 六、参考资料circuit_breaker_hystrix feign-hystrix hystrix_dashboard 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第一篇: 服务的注册与发现(Eureka)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%B8%80%E7%AF%87%EF%BC%9A%20%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0%EF%BC%88Eureka%EF%BC%89%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/69696915本文出自方志朋的博客 一、spring cloud简介spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。它运行环境简单,可以在开发人员的电脑上跑。另外说明spring cloud是基于springboot的,所以需要开发中对springboot有一定的了解,如果不了解的话可以看这篇文章:2小时学会springboot。另外对于“微服务架构” 不了解的话,可以通过搜索引擎搜索“微服务架构”了解下。 二、创建服务注册中心在这里,我们需要用的的组件上Spring Cloud Netflix的Eureka ,eureka是一个服务注册和发现模块。 2.1 首先创建一个maven主工程。 2.2 然后创建2个model工程:一个model工程作为服务注册中心,即Eureka Server,另一个作为Eureka Client。 下面以创建server为例子,详细说明创建过程: 右键工程->创建model-> 选择spring initialir 如下图: 下一步->选择cloud discovery->eureka server ,然后一直下一步就行了。 创建完后的工程的pom.xml文件如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<?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>com.forezp</groupId> <artifactId>eurekaserver</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eurekaserver</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--eureka server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <!-- spring boot test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 2.3 启动一个服务注册中心,只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加: 12345678@EnableEurekaServer@SpringBootApplicationpublic class EurekaserverApplication { public static void main(String[] args) { SpringApplication.run(EurekaserverApplication.class, args); }} 2.4 eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。eureka server的配置文件appication.yml: 1234567891011server: port: 8761eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server. 2.5 eureka server 是有界面的,启动工程,打开浏览器访问:http://localhost:8761 ,界面如下: No application available 没有服务被发现 ……^_^因为没有注册服务当然不可能有服务被发现了。 三、创建一个服务提供者 (eureka client)当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除。 创建过程同server类似,创建完pom.xml如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<?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>com.forezp</groupId> <artifactId>service-hi</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-hi</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 通过注解@EnableEurekaClient 表明自己是一个eurekaclient. 1234567891011121314151617@SpringBootApplication@EnableEurekaClient@RestControllerpublic class ServiceHiApplication { public static void main(String[] args) { SpringApplication.run(ServiceHiApplication.class, args); } @Value("${server.port}") String port; @RequestMapping("/hi") public String home(@RequestParam String name) { return "hi "+name+",i am from port:" +port; }} 仅仅@EnableEurekaClient是不够的,还需要在配置文件中注明自己的服务注册中心的地址,application.yml配置文件如下: 123456789eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/server: port: 8762spring: application: name: service-hi 需要指明spring.application.name,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个name 。启动工程,打开http://localhost:8761 ,即eureka server 的网址: 你会发现一个服务已经注册在服务中了,服务名为SERVICE-HI ,端口为8762 这时打开 http://localhost:8762/hi?name=forezp ,你会在浏览器上看到 : hi forezp,i am from port:8762 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter1 四、参考资料springcloud eureka server 官方文档 springcloud eureka client 官方文档 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第七篇 高可用的分布式配置中心(Spring Cloud Config)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E4%B8%83%E7%AF%87%20%E9%AB%98%E5%8F%AF%E7%94%A8%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83(Spring%20Cloud%20Config)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70037513本文出自方志朋的博客 上一篇文章讲述了一个服务如何从配置中心读取文件,配置中心如何从远程git读取配置文件,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如下: 一、准备工作继续使用上一篇文章的工程,创建一个eureka-server工程,用作服务注册中心。 在其pom.xml文件引入Eureka的起步依赖spring-cloud-starter-eureka-server,代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475<?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>com.forezp</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories></project> 在配置文件application.yml上,指定服务端口为8889,加上作为服务注册中心的基本配置,代码如下: 1234567891011server: port: 8889eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 入口类: 12345678@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }} 二、改造config-server在其pom.xml文件加上EurekaClient的起步依赖spring-cloud-starter-eureka,代码如下: 1234567891011121314151617<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency></dependencies> 配置文件application.yml,指定服务注册地址为http://localhost:8889/eureka/,其他配置同上一篇文章,完整的配置如下: 123456789spring.application.name=config-serverserver.port=8888spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/spring.cloud.config.server.git.searchPaths=respospring.cloud.config.label=masterspring.cloud.config.server.git.username= your usernamespring.cloud.config.server.git.password= your passwordeureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/ 最后需要在程序的启动类Application加上@EnableEureka的注解。 三、改造config-client将其注册微到服务注册中心,作为Eureka客户端,需要pom文件加上起步依赖spring-cloud-starter-eureka,代码如下: 12345678910111213141516171819<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency> 配置文件bootstrap.properties,注意是bootstrap。加上服务注册地址为http://localhost:8889/eureka/ 123456789spring.application.name=config-clientspring.cloud.config.label=masterspring.cloud.config.profile=dev#spring.cloud.config.uri= http://localhost:8888/eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/spring.cloud.config.discovery.enabled=truespring.cloud.config.discovery.serviceId=config-serverserver.port=8881 spring.cloud.config.discovery.enabled 是从配置中心读取文件。 spring.cloud.config.discovery.serviceId 配置中心的servieId,即服务名。 这时发现,在读取配置文件不再写ip地址,而是服务名,这时如果配置服务部署多份,通过负载均衡,从而高可用。 依次启动eureka-servr,config-server,config-client访问网址:http://localhost:8889/ 访问http://localhost:8881/hi,浏览器显示: foo version 3 本文源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter7 四、参考资料spring_cloud_config 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第一篇:构建第一个SpringBoot工程]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%E7%AC%AC%E4%B8%80%E7%AF%87%EF%BC%9A%E6%9E%84%E5%BB%BA%E7%AC%AC%E4%B8%80%E4%B8%AASpringBoot%E5%B7%A5%E7%A8%8B%2F</url>
<content type="text"><![CDATA[简介spring boot 它的设计目的就是为例简化开发,开启了各种自动装配,你不想写各种配置文件,引入相关的依赖就能迅速搭建起一个web工程。它采用的是建立生产就绪的应用程序观点,优先于配置的惯例。 可能你有很多理由不放弃SSM,SSH,但是当你一旦使用了springboot ,你会觉得一切变得简单了,配置变的简单了、编码变的简单了,部署变的简单了,感觉自己健步如飞,开发速度大大提高了。就好比,当你用了IDEA,你会觉得再也回不到Eclipse时代一样。另,本系列教程全部用的IDEA作为开发工具。 建构工程你需要: 15分钟 jdk 1.8或以上 maven 3.0+ Idea 打开Idea-> new Project ->Spring Initializr ->填写group、artifact ->钩上web(开启web功能)->点下一步就行了。 工程目录创建完工程,工程的目录结构如下: 1234567891011- src -main -java -package -SpringbootApplication -resouces - statics - templates - application.yml -test- pom pom文件为基本的依赖管理文件 resouces 资源文件 statics 静态资源 templates 模板资源 application.yml 配置文件 SpringbootApplication程序的入口。 pom.xml的依赖: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950<?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>com.forezp</groupId> <artifactId>springboot-first-application</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-first-application</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project> 其中spring-boot-starter-web不仅包含spring-boot-starter,还自动开启了web功能。 功能演示说了这么多,你可能还体会不到,举个栗子,比如你引入了Thymeleaf的依赖,spring boot 就会自动帮你引入SpringTemplateEngine,当你引入了自己的SpringTemplateEngine,spring boot就不会帮你引入。它让你专注于你的自己的业务开发,而不是各种配置。 再举个栗子,建个controller: 123456789101112import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;@RestControllerpublic class HelloController { @RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; }} 启动SpringbootFirstApplication的main方法,打开浏览器localhost:8080,浏览器显示: Greetings from Spring Boot! 神奇之处: 你没有做任何的web.xml配置。 你没有做任何的sping mvc的配置; springboot为你做了。 你没有配置tomcat ;springboot内嵌tomcat. 启动springboot 方式cd到项目主目录: 12mvn clean mvn package 编译项目的jar mvn spring-boot: run 启动 cd 到target目录,java -jar 项目.jar 来看看springboot在启动的时候为我们注入了哪些bean在程序入口加入: 1234567891011121314151617181920212223@SpringBootApplicationpublic class SpringbootFirstApplication { public static void main(String[] args) { SpringApplication.run(SpringbootFirstApplication.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> { System.out.println("Let's inspect the beans provided by Spring Boot:"); String[] beanNames = ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { System.out.println(beanName); } }; }} 程序输出: Let’s inspect the beans provided by Spring Boot:basicErrorControllerbeanNameHandlerMappingbeanNameViewResolvercharacterEncodingFiltercommandLineRunnerconventionErrorViewResolverdefaultServletHandlerMappingdefaultViewResolverdispatcherServletdispatcherServletRegistrationduplicateServerPropertiesDetectorembeddedServletContainerCustomizerBeanPostProcessorerrorerrorAttributeserrorPageCustomizererrorPageRegistrarBeanPostProcessor ….…. 在程序启动的时候,springboot自动诸如注入了40-50个bean. 单元测试通过@RunWith() @SpringBootTest开启注解: 123456789101112131415161718192021222324@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class HelloControllerIT { @LocalServerPort private int port; private URL base; @Autowired private TestRestTemplate template; @Before public void setUp() throws Exception { this.base = new URL("http://localhost:" + port + "/"); } @Test public void getHello() throws Exception { ResponseEntity<String> response = template.getForEntity(base.toString(), String.class); assertThat(response.getBody(), equalTo("Greetings from Spring Boot!")); }} 运行它会先开启sprigboot工程,然后再测试,测试通过 ^.^]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第四篇:SpringBoot 整合JPA]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%9B%9B%E7%AF%87%EF%BC%9ASpringBoot%20%E6%95%B4%E5%90%88JPA%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70545038本文出自方志朋的博客 JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。 JPA 的目标之一是制定一个可以由很多供应商实现的API,并且开发人员可以编码来实现该API,而不是使用私有供应商特有的API。 JPA是需要Provider来实现其功能的,Hibernate就是JPA Provider中很强的一个,应该说无人能出其右。从功能上来说,JPA就是Hibernate功能的一个子集。 添加相关依赖添加spring-boot-starter-jdbc依赖: 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa </artifactId></dependency> 添加mysql连接类和连接池类: 12345<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency> 配置数据源,在application.properties文件配置:1234567891011spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: 123456 jpa: hibernate: ddl-auto: update # 第一次简表create 后面用update show-sql: true 注意,如果通过jpa在数据库中建表,将jpa.hibernate,ddl-auto改为create,建完表之后,要改为update,要不然每次重启工程会删除表并新建。 创建实体类通过@Entity 表明是一个映射的实体类, @Id表明id, @GeneratedValue 字段自动生成 12345678910@Entitypublic class Account { @Id @GeneratedValue private int id ; private String name ; private double money;... 省略getter setter} Dao层数据访问层,通过编写一个继承自 JpaRepository 的接口就能完成数据访问,其中包含了几本的单表查询的方法,非常的方便。值得注意的是,这个Account 对象名,而不是具体的表名,另外Interger是主键的类型,一般为Integer或者Long 12public interface AccountDao extends JpaRepository<Account,Integer> {} Web层在这个栗子中我简略了service层的书写,在实际开发中,不可省略。新写一个controller,写几个restful api来测试数据的访问。 12345678910111213141516171819202122232425262728293031323334353637383940414243@RestController@RequestMapping("/account")public class AccountController { @Autowired AccountDao accountDao; @RequestMapping(value = "/list", method = RequestMethod.GET) public List<Account> getAccounts() { return accountDao.findAll(); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Account getAccountById(@PathVariable("id") int id) { return accountDao.findOne(id); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT) public String updateAccount(@PathVariable("id") int id, @RequestParam(value = "name", required = true) String name, @RequestParam(value = "money", required = true) double money) { Account account = new Account(); account.setMoney(money); account.setName(name); account.setId(id); Account account1 = accountDao.saveAndFlush(account); return account1.toString(); } @RequestMapping(value = "", method = RequestMethod.POST) public String postAccount(@RequestParam(value = "name") String name, @RequestParam(value = "money") double money) { Account account = new Account(); account.setMoney(money); account.setName(name); Account account1 = accountDao.save(account); return account1.toString(); }} 通过postman请求测试,代码已经全部通过测试。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料accessing-data-jpa 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十篇: 用spring Restdocs创建API文档]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E7%AF%87%EF%BC%9A%20%E7%94%A8spring%20Restdocs%E5%88%9B%E5%BB%BAAPI%E6%96%87%E6%A1%A3%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023510本文出自方志朋的博客 这篇文章将带你了解如何用spring官方推荐的restdoc去生成api文档。本文创建一个简单的springboot工程,将http接口通过Api文档暴露出来。只需要通过 JUnit单元测试和Spring的MockMVC就可以生成文档。 准备工作 你需要15min Jdk 1.8 maven 3.0+ idea 创建工程引入依赖,其pom文件: 123456789101112131415161718<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.restdocs</groupId> <artifactId>spring-restdocs-mockmvc</artifactId> <scope>test</scope> </dependency></dependencies> 通过@SpringBootApplication,开启springboot 1234567@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }} 在springboot通常创建一个controller: 123456789@RestControllerpublic class HomeController { @GetMapping("/") public Map<String, Object> greeting() { return Collections.singletonMap("message", "Hello World"); }} 启动工程,访问localhost:8080,浏览器显示: {“message”:”Hello World”} 证明接口已经写好了,但是如何通过restdoc生存api文档呢 Restdoc,通过单元测试生成api文档restdocs是通过单元测试生存snippets文件,然后snippets根据插件生成htm文档的。 建一个单元测试类: 123456789101112131415@RunWith(SpringRunner.class)@WebMvcTest(HomeController.class)@AutoConfigureRestDocs(outputDir = "target/snippets")public class WebLayerTest { @Autowired private MockMvc mockMvc; @Test public void shouldReturnDefaultMessage() throws Exception { this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()) .andExpect(content().string(containsString("Hello World"))) .andDo(document("home")); }} 其中,@ AutoConfigureRestDocs注解开启了生成snippets文件,并指定了存放位置。 启动单元测试,测试通过,你会发现在target文件下生成了一个snippets文件夹,其目录结构如下: 1234567└── target └── snippets └── home └── httpie-request.adoc └── curl-request.adoc └── http-request.adoc └── http-response.adoc 默认情况下,snippets是Asciidoctor格式的文件,包括request和reponse,另外其他两种httpie和curl两种流行的命令行的http请求模式。 到目前为止,只生成了Snippets文件,需要用Snippets文件生成文档。 怎么用Snippets创建一个新文件src/main/asciidoc/index.adoc : 1234567891011= 用 Spring REST Docs 构建文档This is an example output for a service running at http://localhost:8080:.requestinclude::{snippets}/home/http-request.adoc[].responseinclude::{snippets}/home/http-response.adoc[]这个例子非常简单,通过单元测试和一些简单的配置就能够得到api文档了。 adoc的书写格式,参考:http://docs.spring.io/spring-restdocs/docs/current/reference/html5/,这里不多讲解。 需要使用asciidoctor-maven-plugin插件,在其pom文件加上: 1234567891011121314151617181920<plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <executions> <execution> <id>generate-docs</id> <phase>prepare-package</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <sourceDocumentName>index.adoc</sourceDocumentName> <backend>html</backend> <attributes> <snippets>${project.build.directory}/snippets</snippets> </attributes> </configuration> </execution> </executions></plugin> 这时只需要通过mvnw package命令就可以生成文档了。在/target/generated-docs下有个index.html,打开这个html,显示如下,界面还算简洁: 结语通过单元测试,生存adoc文件,再用adoc文件生存html,只需要简单的几步就可以生成一个api文档的html文件,这个html文件你可以通网站发布出去。整个过程很简单,对代码无任何影响。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料restdocs http://docs.spring.io/spring-restdocs/docs/current/reference/html5/]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十六篇:用restTemplate消费服务]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E5%85%AD%E7%AF%87%EF%BC%9A%E7%94%A8restTemplate%E6%B6%88%E8%B4%B9%E6%9C%8D%E5%8A%A1%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023724本文出自方志朋的博客 这篇文章主要介绍怎么用消费一个 Restful的web服务。我将用restTemplate去消费一个服务: http://gturnquist-quoters.cfapps.io/api/random. 构架工程创建一个springboot工程,去消费RESTFUL的服务。这个服务是 http:///gturnquist-quoters.cfapps.io/api/random ,它会随机返回Json字符串。在Spring项目中,它提供了一个非常简便的类,叫RestTemplate,它可以很简便的消费服务。 消费服务通过RestTemplate消费服务,需要先context中注册一个RestTemplate bean。代码如下: 12345678910111213@Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } @Bean public CommandLineRunner run(RestTemplate restTemplate) throws Exception { return args -> { String quote = restTemplate.getForObject( "http://gturnquist-quoters.cfapps.io/api/random", String.class); log.info(quote.toString()); }; } 运行程序,控制台打印: {“type”: “success”,“value”: {“id”: 6,“quote”: “It embraces convention over configuration, providing an experience on par with frameworks that excel at early stage development, such as Ruby on Rails.”}} 参考资料https://spring.io/guides/gs/consuming-rest/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十四篇:在springboot中用redis实现消息队列]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AF%87%EF%BC%9A%E5%9C%A8springboot%E4%B8%AD%E7%94%A8redis%E5%AE%9E%E7%8E%B0%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023652本文出自方志朋的博客 这篇文章主要讲述如何在springboot中用reids实现消息队列。 准备阶段 安装redis,可参考我的另一篇文章,5分钟带你入门Redis。 java 1.8 maven 3.0 idea 环境依赖创建一个新的springboot工程,在其pom文件,加入spring-boot-starter-data-redis依赖: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency> 创建一个消息接收者REcevier类,它是一个普通的类,需要注入到springboot中。 123456789101112131415public class Receiver { private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class); private CountDownLatch latch; @Autowired public Receiver(CountDownLatch latch) { this.latch = latch; } public void receiveMessage(String message) { LOGGER.info("Received <" + message + ">"); latch.countDown(); }} 注入消息接收者1234567891011121314@Bean Receiver receiver(CountDownLatch latch) { return new Receiver(latch); } @Bean CountDownLatch latch() { return new CountDownLatch(1); } @Bean StringRedisTemplate template(RedisConnectionFactory connectionFactory) { return new StringRedisTemplate(connectionFactory); } 注入消息监听容器在spring data redis中,利用redis发送一条消息和接受一条消息,需要三样东西: 一个连接工厂 一个消息监听容器 Redis template 上述1、3步已经完成,所以只需注入消息监听容器即可: 123456789101112131415@Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.addMessageListener(listenerAdapter, new PatternTopic("chat")); return container; } @Bean MessageListenerAdapter listenerAdapter(Receiver receiver) { return new MessageListenerAdapter(receiver, "receiveMessage"); } 测试在springboot入口的main方法: 12345678910111213public static void main(String[] args) throws Exception{ ApplicationContext ctx = SpringApplication.run(SpringbootRedisApplication.class, args); StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class); CountDownLatch latch = ctx.getBean(CountDownLatch.class); LOGGER.info("Sending message..."); template.convertAndSend("chat", "Hello from Redis!"); latch.await(); System.exit(0); } 先用redisTemplate发送一条消息,接收者接收到后,打印出来。启动springboot程序,控制台打印: 2017-04-20 17:25:15.536 INFO 39148 — [ main] com.forezp.SpringbootRedisApplication : Sending message…2017-04-20 17:25:15.544 INFO 39148 — [ container-2] com.forezp.message.Receiver : 》Received 源码下载:https://github.com/forezp/SpringBootLearning 参考资料messaging-redis 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十五篇:Springboot整合RabbitMQ]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AF%87%EF%BC%9ASpringboot%E6%95%B4%E5%90%88RabbitMQ%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023692本文出自方志朋的博客 这篇文章带你了解怎么整合RabbitMQ服务器,并且通过它怎么去发送和接收消息。我将构建一个springboot工程,通过RabbitTemplate去通过MessageListenerAdapter去订阅一个POJO类型的消息。 准备工作 15min IDEA maven 3.0 在开始构建项目之前,机器需要安装rabbitmq,你可以去官网下载,http://www.rabbitmq.com/download.html ,如果你是用的Mac(程序员都应该用mac吧),你可以这样下载: 1brew install rabbitmq 安装完成后开启服务器: 1rabbitmq-server 开启服务器成功,你可以看到以下信息: 1234567 RabbitMQ 3.1.3. Copyright (C) 2007-2013 VMware, Inc.## ## Licensed under the MPL. See http://www.rabbitmq.com/## ############ Logs: /usr/local/var/log/rabbitmq/[email protected]###### ## /usr/local/var/log/rabbitmq/[email protected]########## Starting broker... completed with 6 plugins. 构建工程构架一个SpringBoot工程,其pom文件依赖加上spring-boot-starter-amqp的起步依赖: 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>12345 创建消息接收者在任何的消息队列程序中,你需要创建一个消息接收者,用于响应发送的消息。 123456789101112131415@Componentpublic class Receiver { private CountDownLatch latch = new CountDownLatch(1); public void receiveMessage(String message) { System.out.println("Received <" + message + ">"); latch.countDown(); } public CountDownLatch getLatch() { return latch; }} 消息接收者是一个简单的POJO类,它定义了一个方法去接收消息,当你注册它去接收消息,你可以给它取任何的名字。其中,它有CountDownLatch这样的一个类,它是用于告诉发送者消息已经收到了,你不需要在应用程序中具体实现它,只需要latch.countDown()就行了。 创建消息监听,并发送一条消息在spring程序中,RabbitTemplate提供了发送消息和接收消息的所有方法。你只需简单的配置下就行了: 需要一个消息监听容器 声明一个quene,一个exchange,并且绑定它们 一个组件去发送消息 代码清单如下: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package com.forezp;import com.forezp.message.Receiver;import org.springframework.amqp.core.Binding;import org.springframework.amqp.core.BindingBuilder;import org.springframework.amqp.core.Queue;import org.springframework.amqp.core.TopicExchange;import org.springframework.amqp.rabbit.connection.ConnectionFactory;import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;@SpringBootApplicationpublic class SpringbootRabbitmqApplication { final static String queueName = "spring-boot"; @Bean Queue queue() { return new Queue(queueName, false); } @Bean TopicExchange exchange() { return new TopicExchange("spring-boot-exchange"); } @Bean Binding binding(Queue queue, TopicExchange exchange) { return BindingBuilder.bind(queue).to(exchange).with(queueName); } @Bean SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setQueueNames(queueName); container.setMessageListener(listenerAdapter); return container; } @Bean MessageListenerAdapter listenerAdapter(Receiver receiver) { return new MessageListenerAdapter(receiver, "receiveMessage"); } public static void main(String[] args) { SpringApplication.run(SpringbootRabbitmqApplication.class, args); }} 创建一个测试方法: 1234567891011121314151617181920212223@Componentpublic class Runner implements CommandLineRunner { private final RabbitTemplate rabbitTemplate; private final Receiver receiver; private final ConfigurableApplicationContext context; public Runner(Receiver receiver, RabbitTemplate rabbitTemplate, ConfigurableApplicationContext context) { this.receiver = receiver; this.rabbitTemplate = rabbitTemplate; this.context = context; } @Override public void run(String... args) throws Exception { System.out.println("Sending message..."); rabbitTemplate.convertAndSend(Application.queueName, "Hello from RabbitMQ!"); receiver.getLatch().await(10000, TimeUnit.MILLISECONDS); context.close(); }} 启动程序,你会发现控制台打印: 12Sending message...Received <Hello from RabbitMQ!> 总结恭喜!你刚才已经学会了如何通过spring raabitmq去构建一个消息发送和订阅的程序。 这仅仅是一个好的开始,你可以通过spring-rabbitmq做更多的事,点击这里。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料https://spring.io/guides/gs/messaging-rabbitmq/ 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Springboot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十二篇:springboot集成apidoc]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AF%87%EF%BC%9Aspringboot%E9%9B%86%E6%88%90apidoc%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023579本文出自方志朋的博客 首先声明下,apidoc是基于注释来生成文档的,它不基于任何框架,而且支持大多数编程语言,为了springboot系列的完整性,所以标了个题。 一、apidoc简介apidoc通过在你代码的注释来生成api文档的。它对代码没有侵入性,只需要你写好相关的注释即可,并且它仅通过写简单的配置就可以生成高颜值的api接口页面。它基于node.js,所以你需要安装node.js环境。node.js安装,点击这里。这里就不介绍。 二、准备工作安装完node.js安装api.doc,它的项目源码:https://github.com/apidoc/apidoc 。 通过命令安装: npm install apidoc -g 三、注释怎么写 @api 12345@api {method} path [title]method:请求方法,path:请求路径 title(可选):标题 @apiDescription 12@apiDescription texttext说明 @apiError 123456@apiError [(group)] [{type}] field [description](group)(可选):参数将以这个名称分组,不设置的话,默认是Error 4xx {type}(可选):返回值类型,例如:{Boolean}, {Number}, {String}, {Object}, {String[]} field:返回值字段名称 descriptionoptional(可选):返回值字段说明 @apiGroup 12@apiGroup namename:组名称,也是导航的标题 更多注释,参见官方文档:http://apidocjs.com/#params 四、写给栗子首先写配置文件在项目的主目录新建一个apidoc.json文件: 12345{ "name": "example", "version": "0.1.0", "description": "A basic apiDoc example"} 更多配置参考:http://apidocjs.com/#configuration 写个注释:1234567891011121314151617/** * @api {POST} /register 注册用户 * @apiGroup Users * @apiVersion 0.0.1 * @apiDescription 用于注册用户 * @apiParam {String} account 用户账户名 * @apiParam {String} password 密码 * @apiParam {String} mobile 手机号 * @apiParam {int} vip = 0 是否注册Vip身份 0 普通用户 1 Vip用户 * @apiParam {String} [recommend] 邀请码 * @apiParamExample {json} 请求样例: * ?account=sodlinken&password=11223344&mobile=13739554137&vip=0&recommend= * @apiSuccess (200) {String} msg 信息 * @apiSuccess (200) {int} code 0 代表无错误 1代表有错误 * @apiSuccessExample {json} 返回样例: * {"code":"0","msg":"注册成功"} */ 用apidoc命令生成文档界面先cd到工程的外层目录,并在外层目建个输出文档的目录,我建的是docapi。 输命令: apidoc -i chapter4/ -o apidoc/ -i 输入目录 -o 输出目录 chapter4是我的工程名。 可以看到在apidoc目录生成了很多文件: 打开index.html,可以看到文档页面: 五、参考资料apidoc apidocjs.com 使用apidoc 生成Restful web Api文档]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十八篇: 定时任务(Scheduling Tasks)]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E5%85%AB%E7%AF%87%EF%BC%9A%20%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%EF%BC%88Scheduling%20Tasks%EF%BC%89%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023783本文出自方志朋的博客 这篇文章将介绍怎么通过spring去做调度任务。 构建工程创建一个Springboot工程,在它的程序入口加上@EnableScheduling,开启调度任务。 12345678@SpringBootApplication@EnableSchedulingpublic class SpringbootSchedulingTasksApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSchedulingTasksApplication.class, args); }} 创建定时任务创建一个定时任务,每过5s在控制台打印当前时间。 123456789101112@Componentpublic class ScheduledTasks { private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class); private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); @Scheduled(fixedRate = 5000) public void reportCurrentTime() { log.info("The time is now {}", dateFormat.format(new Date())); }} 通过在方法上加@Scheduled注解,表明该方法是一个调度任务。 @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行 @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行 @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次 @Scheduled(cron=” /5 “) :通过cron表达式定义规则,什么是cro表达式,自行搜索引擎。 测试启动springboot工程,控制台没过5s就打印出了当前的时间。 2017-04-29 17:39:37.672 INFO 677 — [pool-1-thread-1] com.forezp.task.ScheduledTasks : The time is now 17:39:372017-04-29 17:39:42.671 INFO 677 — [pool-1-thread-1] com.forezp.task.ScheduledTasks : The time is now 17:39:422017-04-29 17:39:47.672 INFO 677 — [pool-1-thread-1] com.forezp.task.ScheduledTasks : The time is now 17:39:472017-04-29 17:39:52.675 INFO 677 — [pool-1-thread-1] com.forezp.task.ScheduledTasks : The time is now 17:39:52 总结在springboot创建定时任务比较简单,只需2步: 1.在程序的入口加上@EnableScheduling注解。 2.在定时方法上加@Scheduled注解。 参考资料https://spring.io/guides/gs/scheduling-tasks/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十九篇: 验证表单信息]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%B9%9D%E7%AF%87%EF%BC%9A%20%E9%AA%8C%E8%AF%81%E8%A1%A8%E5%8D%95%E4%BF%A1%E6%81%AF%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023817本文出自方志朋的博客 这篇文篇主要简述如何在springboot中验证表单信息。在springmvc工程中,需要检查表单信息,表单信息验证主要通过注解的形式。 构建工程创建一个springboot工程,由于用到了 web 、thymeleaf、validator、el,引入相应的起步依赖和依赖,代码清单如下: 12345678910111213141516171819202122232425<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> </dependency></dependencies> 创建一个PresonForm的Object类1234567891011121314151617181920212223242526272829303132333435363738package com.forezp.entity;import javax.validation.constraints.Min;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;/** * Created by fangzhipeng on 2017/4/19. */public class PersonForm { @NotNull @Size(min=2, max=30) private String name; @NotNull @Min(18) private Integer age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String toString() { return "Person(Name: " + this.name + ", Age: " + this.age + ")"; }} 这个实体类,在2个属性:name,age.它们各自有验证的注解: @Size(min=2, max=30) name的长度为2-30个字符 @NotNull 不为空 @Min(18)age不能小于18 创建 web Controller1234567891011121314151617181920212223@Controllerpublic class WebController extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/results").setViewName("results"); } @GetMapping("/") public String showForm(PersonForm personForm) { return "form"; } @PostMapping("/") public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "form"; } return "redirect:/results"; }} 创建form表单src/main/resources/templates/form.html: 123456789101112131415161718192021<html> <body> <form action="#" th:action="@{/}" th:object="${personForm}" method="post"> <table> <tr> <td>Name:</td> <td><input type="text" th:field="*{name}" /></td> <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td> </tr> <tr> <td>Age:</td> <td><input type="text" th:field="*{age}" /></td> <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</td> </tr> <tr> <td><button type="submit">Submit</button></td> </tr> </table> </form> </body></html> 注册成功的页面src/main/resources/templates/results.html: 12345html> <body> Congratulations! You are old enough to sign up for this site. </body></html> 演示启动工程,访问http://localhost:8080/: 如果你输入A和15,点击 submit: 如果name 输入N, age为空: 如果输入:forezp. 18 参考资料https://spring.io/guides/gs/validating-form-input/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十三篇:springboot集成spring cache]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%B8%89%E7%AF%87%EF%BC%9Aspringboot%E9%9B%86%E6%88%90spring%20cache%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023614本文出自方志朋的博客 本文介绍如何在springboot中使用默认的spring cache, 声明式缓存Spring 定义 CacheManager 和 Cache 接口用来统一不同的缓存技术。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redis 等。在使用 Spring 集成 Cache 的时候,我们需要注册实现的 CacheManager 的 Bean。 Spring Boot 为我们自动配置了 JcacheCacheConfiguration、 EhCacheCacheConfiguration、HazelcastCacheConfiguration、GuavaCacheConfiguration、RedisCacheConfiguration、SimpleCacheConfiguration 等。 默认使用 ConcurrenMapCacheManager在我们不使用其他第三方缓存依赖的时候,springboot自动采用ConcurrenMapCacheManager作为缓存管理器。 环境依赖在pom文件引入spring-boot-starter-cache环境依赖: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId></dependency> 创建一个book数据访问层先创建一个实体类“` public class Book { 1234567private String isbn;private String title;public Book(String isbn, String title) { this.isbn = isbn; this.title = title;} ….getter….setter } 创建一个数据访问接口12345public interface BookRepository { Book getByIsbn(String isbn);} 这个你可以写一个很复杂的数据查询操作,比如操作mysql、nosql等等。为了演示这个栗子,我只做了一下线程的延迟操作,当作是查询数据库的时间。 实现接口类: 123456789101112131415161718192021@Componentpublic class SimpleBookRepository implements BookRepository { @Override public Book getByIsbn(String isbn) { simulateSlowService(); return new Book(isbn, "Some book"); } // Don't do this at home private void simulateSlowService() { try { long time = 3000L; Thread.sleep(time); } catch (InterruptedException e) { throw new IllegalStateException(e); } }} 测试类1234567891011121314151617181920212223@Componentpublic class AppRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(AppRunner.class); private final BookRepository bookRepository; public AppRunner(BookRepository bookRepository) { this.bookRepository = bookRepository; } @Override public void run(String... args) throws Exception { logger.info(".... Fetching books"); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); }} 启动程序,你会发现程序在控制台依次打印了: 2014-06-05 12:15:35.783 … : …. Fetching books 2014-06-05 12:15:40.783 … : isbn-1234 –> >Book{isbn=’isbn-1234’, title=’Some book’} 2014-06-05 12:15:43.784 … : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’} 2014-06-05 12:15:46.786 … : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’} 你会发现程序依次3s打印一行日志。这时还没开启缓存技术。 开启缓存技术在程序的入口中加入@ EnableCaching开启缓存技术: 12345678@SpringBootApplication@EnableCachingpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }} 在需要缓存的地方加入@Cacheable注解,比如在getByIsbn()方法上加入@Cacheable(“books”),这个方法就开启了缓存策略,当缓存有这个数据的时候,会直接返回数据,不会等待去查询数据库。 123456789101112131415161718192021@Componentpublic class SimpleBookRepository implements BookRepository { @Override @Cacheable("books") public Book getByIsbn(String isbn) { simulateSlowService(); return new Book(isbn, "Some book"); } // Don't do this at home private void simulateSlowService() { try { long time = 3000L; Thread.sleep(time); } catch (InterruptedException e) { throw new IllegalStateException(e); } }} 这时再启动程序,你会发现程序打印: isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}2017-04-23 18:17:09.479 INFO 8054 — [ main] forezp.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}2017-04-23 18:17:09.480 INFO 8054 — [ main] forezp.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}2017-04-23 18:17:09.480 INFO 8054 — [ main] forezp.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}2017-04-23 18:17:09.481 INFO 8054 — [ main] forezp.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}2017-04-23 18:17:09.481 INFO 8054 — [ main] forezp.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’} 只有打印前面2个数据,程序等了3s,之后的数据瞬间打印在控制台上了,这说明缓存起了作用。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料caching Spring Boot 揭秘与实战(二) 数据缓存篇 - 快速入门 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第六篇:springboot整合mybatis]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%85%AD%E7%AF%87%EF%BC%9Aspringboot%E6%95%B4%E5%90%88mybatis%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70768477本文出自方志朋的博客 本文主要讲解如何在springboot下整合mybatis,并访问数据库。由于mybatis这个框架太过于流行,所以我就不讲解了。 引入依赖在pom文件引入mybatis-spring-boot-starter的依赖: 12345<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version></dependency> 引入数据库连接依赖: 12345678910<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version></dependency> 引入数据源application.properties配置文件中引入数据源: 1234spring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driver 这样,springboot就可以访问数据了。 创建数据库表建表语句: 1234567891011-- create table `account`# DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;INSERT INTO `account` VALUES ('1', 'aaa', '1000');INSERT INTO `account` VALUES ('2', 'bbb', '1000');INSERT INTO `account` VALUES ('3', 'ccc', '1000'); 具体实现这篇文篇通过注解的形式实现。 创建实体:12345678910111213public class Account { private int id ; private String name ; private double money; setter… getter… } dao层123456789101112131415161718@Mapperpublic interface AccountMapper { @Insert("insert into account(name, money) values(#{name}, #{money})") int add(@Param("name") String name, @Param("money") double money); @Update("update account set name = #{name}, money = #{money} where id = #{id}") int update(@Param("name") String name, @Param("money") double money, @Param("id") int id); @Delete("delete from account where id = #{id}") int delete(int id); @Select("select id, name as name, money as money from account where id = #{id}") Account findAccount(@Param("id") int id); @Select("select id, name as name, money as money from account") List<Account> findAccountList();} service层123456789101112131415161718192021@Servicepublic class AccountService { @Autowired private AccountMapper accountMapper; public int add(String name, double money) { return accountMapper.add(name, money); } public int update(String name, double money, int id) { return accountMapper.update(name, money, id); } public int delete(int id) { return accountMapper.delete(id); } public Account findAccount(int id) { return accountMapper.findAccount(id); } public List<Account> findAccountList() { return accountMapper.findAccountList(); }} controller层,构建restful API12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667package com.forezp.web;import com.forezp.entity.Account;import com.forezp.service.AccountService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;/** * Created by fangzhipeng on 2017/4/20. */@RestController@RequestMapping("/account")public class AccountController { @Autowired AccountService accountService; @RequestMapping(value = "/list", method = RequestMethod.GET) public List<Account> getAccounts() { return accountService.findAccountList(); } @RequestMapping(value = "/{id}", method = RequestMethod.GET) public Account getAccountById(@PathVariable("id") int id) { return accountService.findAccount(id); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT) public String updateAccount(@PathVariable("id") int id, @RequestParam(value = "name", required = true) String name, @RequestParam(value = "money", required = true) double money) { int t= accountService.update(name,money,id); if(t==1) { return "success"; }else { return "fail"; } } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public String delete(@PathVariable(value = "id")int id) { int t= accountService.delete(id); if(t==1) { return "success"; }else { return "fail"; } } @RequestMapping(value = "", method = RequestMethod.POST) public String postAccount(@RequestParam(value = "name") String name, @RequestParam(value = "money") double money) { int t= accountService.add(name,money); if(t==1) { return "success"; }else { return "fail"; } }} 通过postman测试通过。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料mybatis MyBatis整合 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第五篇:springboot整合 beatlsql]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%94%E7%AF%87%EF%BC%9Aspringboot%E6%95%B4%E5%90%88%20beatlsql%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70662983本文出自方志朋的博客 BeetSql是一个全功能DAO工具, 同时具有Hibernate 优点 & Mybatis优点功能,适用于承认以SQL为中心,同时又需求工具能自动能生成大量常用的SQL的应用。 beatlsql 优点 开发效率 无需注解,自动使用大量内置SQL,轻易完成增删改查功能,节省50%的开发工作量 数据模型支持Pojo,也支持Map/List这种快速模型,也支持混合模型 SQL 模板基于Beetl实现,更容易写和调试,以及扩展 维护性 SQL 以更简洁的方式,Markdown方式集中管理,同时方便程序开发和数据库SQL调试。 可以自动将sql文件映射为dao接口类 灵活直观的支持支持一对一,一对多,多对多关系映射而不引入复杂的OR Mapping概念和技术。 具备Interceptor功能,可以调试,性能诊断SQL,以及扩展其他功能 其他 内置支持主从数据库支持的开源工具 支持跨数据库平台,开发者所需工作减少到最小,目前跨数据库支持mysql,postgres,oracle,sqlserver,h2,sqllite,DB2. 引入依赖123456789101112131415161718192021222324<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency><dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>2.3.2</version></dependency><dependency> <groupId>com.ibeetl</groupId> <artifactId>beetlsql</artifactId> <version>2.3.1</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.0.5</version></dependency> 这几个依赖都是必须的。 整合阶段由于springboot没有对 beatlsql的快速启动装配,所以需要我自己导入相关的bean,包括数据源,包扫描,事物管理器等。 在application加入以下代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364@Bean(initMethod = "init", name = "beetlConfig") public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); ResourcePatternResolver patternResolver = ResourcePatternUtils.getResourcePatternResolver(new DefaultResourceLoader()); try { // WebAppResourceLoader 配置root路径是关键 WebAppResourceLoader webAppResourceLoader = new WebAppResourceLoader(patternResolver.getResource("classpath:/templates").getFile().getPath()); beetlGroupUtilConfiguration.setResourceLoader(webAppResourceLoader); } catch (IOException e) { e.printStackTrace(); } //读取配置文件信息 return beetlGroupUtilConfiguration; } @Bean(name = "beetlViewResolver") public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfig") BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) { BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver(); beetlSpringViewResolver.setContentType("text/html;charset=UTF-8"); beetlSpringViewResolver.setOrder(0); beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration); return beetlSpringViewResolver; } //配置包扫描 @Bean(name = "beetlSqlScannerConfigurer") public BeetlSqlScannerConfigurer getBeetlSqlScannerConfigurer() { BeetlSqlScannerConfigurer conf = new BeetlSqlScannerConfigurer(); conf.setBasePackage("com.forezp.dao"); conf.setDaoSuffix("Dao"); conf.setSqlManagerFactoryBeanName("sqlManagerFactoryBean"); return conf; } @Bean(name = "sqlManagerFactoryBean") @Primary public SqlManagerFactoryBean getSqlManagerFactoryBean(@Qualifier("datasource") DataSource datasource) { SqlManagerFactoryBean factory = new SqlManagerFactoryBean(); BeetlSqlDataSource source = new BeetlSqlDataSource(); source.setMasterSource(datasource); factory.setCs(source); factory.setDbStyle(new MySqlStyle()); factory.setInterceptors(new Interceptor[]{new DebugInterceptor()}); factory.setNc(new UnderlinedNameConversion());//开启驼峰 factory.setSqlLoader(new ClasspathLoader("/sql"));//sql文件路径 return factory; } //配置数据库 @Bean(name = "datasource") public DataSource getDataSource() { return DataSourceBuilder.create().url("jdbc:mysql://127.0.0.1:3306/test").username("root").password("123456").build(); } //开启事务 @Bean(name = "txManager") public DataSourceTransactionManager getDataSourceTransactionManager(@Qualifier("datasource") DataSource datasource) { DataSourceTransactionManager dsm = new DataSourceTransactionManager(); dsm.setDataSource(datasource); return dsm; } 在resouces包下,加META_INF文件夹,文件夹中加入spring-devtools.properties: 12restart.include.beetl=/beetl-2.3.2.jarrestart.include.beetlsql=/beetlsql-2.3.1.jar 在templates下加一个index.btl文件。 加入jar和配置beatlsql的这些bean,以及resources这些配置之后,springboot就能够访问到数据库类。 举个restful的栗子初始化数据库的表12345678910# DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;INSERT INTO `account` VALUES ('1', 'aaa', '1000');INSERT INTO `account` VALUES ('2', 'bbb', '1000');INSERT INTO `account` VALUES ('3', 'ccc', '1000'); bean12345678910public class Account { private int id ; private String name ; private double money; getter... setter... } 数据访问dao层12345public interface AccountDao extends BaseMapper<Account> { @SqlStatement(params = "name") Account selectAccountByName(String name);} 接口继承BaseMapper,就能获取单表查询的一些性质,当你需要自定义sql的时候,只需要在resouses/sql/account.md文件下书写文件: 12345selectAccountByName===*根据name获account select * from account where name= #name# 其中“=== ”上面是唯一标识,对应于接口的方法名,“* ”后面是注释,在下面就是自定义的sql语句,具体的见官方文档。 web层这里省略了service层,实际开发补上。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051@RestController@RequestMapping("/account")public class AccountController { @Autowired AccountDao accountDao; @RequestMapping(value = "/list",method = RequestMethod.GET) public List<Account> getAccounts(){ return accountDao.all(); } @RequestMapping(value = "/{id}",method = RequestMethod.GET) public Account getAccountById(@PathVariable("id") int id){ return accountDao.unique(id); } @RequestMapping(value = "",method = RequestMethod.GET) public Account getAccountById(@RequestParam("name") String name){ return accountDao.selectAccountByName(name); } @RequestMapping(value = "/{id}",method = RequestMethod.PUT) public String updateAccount(@PathVariable("id")int id , @RequestParam(value = "name",required = true)String name, @RequestParam(value = "money" ,required = true)double money){ Account account=new Account(); account.setMoney(money); account.setName(name); account.setId(id); int t=accountDao.updateById(account); if(t==1){ return account.toString(); }else { return "fail"; } } @RequestMapping(value = "",method = RequestMethod.POST) public String postAccount( @RequestParam(value = "name")String name, @RequestParam(value = "money" )double money) { Account account = new Account(); account.setMoney(money); account.setName(name); KeyHolder t = accountDao.insertReturnKey(account); if (t.getInt() > 0) { return account.toString(); } else { return "fail"; } }} 通过postman 测试,代码已全部通过。 个人使用感受,使用bealsql做了一些项目的试验,但是没有真正用于真正的生产环境,用起来非常的爽。但是springboot没有提供自动装配的直接支持,需要自己注解bean。另外使用这个orm的人不太多,有木有坑不知道,在我使用的过程中没有遇到什么问题。另外它的中文文档比较友好。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料BeetlSQL2.8中文文档 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[第十二篇 断路器监控(Hystrix Dashboard)]]></title>
<url>%2F2017%2F10%2F06%2F%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AF%87%20%E6%96%AD%E8%B7%AF%E5%99%A8%E7%9B%91%E6%8E%A7(Hystrix%20Dashboard)%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70217283本文出自方志朋的博客 在我的第四篇文章断路器讲述了如何使用断路器,并简单的介绍了下Hystrix Dashboard组件,这篇文章更加详细的介绍Hystrix Dashboard。 一、Hystrix Dashboard简介在微服务架构中为例保证程序的可用性,防止程序出错导致网络阻塞,出现了断路器模型。断路器的状况反应了一个程序的可用性和健壮性,它是一个重要指标。Hystrix Dashboard是作为断路器状态的一个组件,提供了数据监控和友好的图形化界面。 二、准备工作本文的的工程栗子,来源于第一篇文章的栗子,在它的基础上进行改造。 三、开始改造service-hi在pom的工程文件引入相应的依赖: 12345678910111213<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId></dependency> 其中,这三个依赖是必须的,缺一不可。 在程序的入口ServiceHiApplication类,加上@EnableHystrix注解开启断路器,这个是必须的,并且需要在程序中声明断路点HystrixCommand;加上@EnableHystrixDashboard注解,开启HystrixDashboard 1234567891011121314151617181920212223@SpringBootApplication@EnableEurekaClient@RestController@EnableHystrix@EnableHystrixDashboardpublic class ServiceHiApplication { public static void main(String[] args) { SpringApplication.run(ServiceHiApplication.class, args); } @Value("${server.port}") String port; @RequestMapping("/hi") @HystrixCommand(fallbackMethod = "hiError") public String home(@RequestParam String name) { return "hi "+name+",i am from port:" +port; } public String hiError(String name) { return "hi,"+name+",sorry,error!"; }} 运行程序: 依次开启eureka-server 和service-hi. 四、Hystrix Dashboard图形展示打开http://localhost:8762/hystrix.stream,可以看到一些具体的数据: 打开locahost:8762/hystrix 可以看见以下界面: 在界面依次输入:locahost:8762/hystrix.stream 、2000 、miya;点确定。 在另一个窗口输入: http://localhost:8762/hi?name=forezp 重新刷新hystrix.stream网页,你会看到良好的图形化界面: 源码下载:https://github.com/forezp/SpringCloudLearning/tree/master/chapter12 五、参考资料hystrix-dashboard 优秀文章推荐: 史上最简单的 SpringCloud 教程 | 终章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringCloud</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第十一篇:springboot集成swagger2,构建优雅的Restful API]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%8D%81%E4%B8%80%E7%AF%87%EF%BC%9Aspringboot%E9%9B%86%E6%88%90swagger2%EF%BC%8C%E6%9E%84%E5%BB%BA%E4%BC%98%E9%9B%85%E7%9A%84Restful%20API%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71023536本文出自方志朋的博客 swagger,中文“拽”的意思。它是一个功能强大的api框架,它的集成非常简单,不仅提供了在线文档的查阅,而且还提供了在线文档的测试。另外swagger很容易构建restful风格的api,简单优雅帅气,正如它的名字。 一、引入依赖 1234567891011<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version></dependency> 二、写配置类 12345678910111213141516171819202122@Configuration@EnableSwagger2public class Swagger2 { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.forezp.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("springboot利用swagger构建api文档") .description("简单优雅的restfun风格,http://blog.csdn.net/forezp") .termsOfServiceUrl("http://blog.csdn.net/forezp") .version("1.0") .build(); }} 通过@Configuration注解,表明它是一个配置类,@EnableSwagger2开启swagger2。apiINfo()配置一些基本的信息。apis()指定扫描的包会生成文档。 三、写生产文档的注解 swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。 @Api:修饰整个类,描述Controller的作用 @ApiOperation:描述一个类的一个方法,或者说一个接口 @ApiParam:单个参数描述 @ApiModel:用对象来接收参数 @ApiProperty:用对象接收参数时,描述对象的一个字段 @ApiResponse:HTTP响应其中1个描述 @ApiResponses:HTTP响应整体描述 @ApiIgnore:使用该注解忽略这个API @ApiError :发生错误返回的信息 @ApiParamImplicitL:一个请求参数 @ApiParamsImplicit 多个请求参数 现在通过一个栗子来说明: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475package com.forezp.controller;import com.forezp.entity.Book;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import org.springframework.ui.ModelMap;import org.springframework.web.bind.annotation.*;import springfox.documentation.annotations.ApiIgnore;import java.util.*;/** * 用户创建某本图书 POST /books/ * 用户修改对某本图书 PUT /books/:id/ * 用户删除对某本图书 DELETE /books/:id/ * 用户获取所有的图书 GET /books * 用户获取某一图书 GET /Books/:id * Created by fangzhipeng on 2017/4/17. * 官方文档:http://swagger.io/docs/specification/api-host-and-base-path/ */@RestController@RequestMapping(value = "/books")public class BookContrller { Map<Long, Book> books = Collections.synchronizedMap(new HashMap<Long, Book>()); @ApiOperation(value="获取图书列表", notes="获取图书列表") @RequestMapping(value={""}, method= RequestMethod.GET) public List<Book> getBook() { List<Book> book = new ArrayList<>(books.values()); return book; } @ApiOperation(value="创建图书", notes="创建图书") @ApiImplicitParam(name = "book", value = "图书详细实体", required = true, dataType = "Book") @RequestMapping(value="", method=RequestMethod.POST) public String postBook(@RequestBody Book book) { books.put(book.getId(), book); return "success"; } @ApiOperation(value="获图书细信息", notes="根据url的id来获取详细信息") @ApiImplicitParam(name = "id", value = "ID", required = true, dataType = "Long",paramType = "path") @RequestMapping(value="/{id}", method=RequestMethod.GET) public Book getBook(@PathVariable Long id) { return books.get(id); } @ApiOperation(value="更新信息", notes="根据url的id来指定更新图书信息") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "图书ID", required = true, dataType = "Long",paramType = "path"), @ApiImplicitParam(name = "book", value = "图书实体book", required = true, dataType = "Book") }) @RequestMapping(value="/{id}", method= RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody Book book) { Book book1 = books.get(id); book1.setName(book.getName()); book1.setPrice(book.getPrice()); books.put(id, book1); return "success"; } @ApiOperation(value="删除图书", notes="根据url的id来指定删除图书") @ApiImplicitParam(name = "id", value = "图书ID", required = true, dataType = "Long",paramType = "path") @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { books.remove(id); return "success"; } @ApiIgnore//使用该注解忽略这个API @RequestMapping(value = "/hi", method = RequestMethod.GET) public String jsonTest() { return " hi you!"; }} 通过相关注解,就可以让swagger2生成相应的文档。如果你不需要某接口生成文档,只需要在加@ApiIgnore注解即可。需要说明的是,如果请求参数在url上,@ApiImplicitParam 上加paramType = “path” 。 启动工程,访问:http://localhost:8080/swagger-ui.html ,就看到swagger-ui: 整个集成过程非常简单,但是我看了相关的资料,swagger没有做安全方面的防护,可能需要我们自己做相关的工作。 四、参考资料swagger.io Spring Boot中使用Swagger2构建强大的RESTful API文档]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第八篇:springboot整合mongodb]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E5%85%AB%E7%AF%87%EF%BC%9Aspringboot%E6%95%B4%E5%90%88mongodb%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70941577本文出自方志朋的博客 这篇文章主要介绍springboot如何整合mongodb。 准备工作 安装 MongoDB jdk 1.8 maven 3.0 idea 环境依赖在pom文件引入spring-boot-starter-data-mongodb依赖: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> 数据源配置如果mongodb端口是默认端口,并且没有设置密码,可不配置,sprinboot会开启默认的。 1spring.data.mongodb.uri=mongodb://localhost:27017/springboot-db mongodb设置了密码,这样配置: 1spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/dbname 定义一个简单的实体mongodb 12345678910111213141516171819202122232425262728package com.forezp.entity;import org.springframework.data.annotation.Id;public class Customer { @Id public String id; public String firstName; public String lastName; public Customer() {} public Customer(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Override public String toString() { return String.format( "Customer[id=%s, firstName='%s', lastName='%s']", id, firstName, lastName); }} 数据操作dao层123456public interface CustomerRepository extends MongoRepository<Customer, String> { public Customer findByFirstName(String firstName); public List<Customer> findByLastName(String lastName);} 写一个接口,继承MongoRepository,这个接口有了几本的CURD的功能。如果你想自定义一些查询,比如根据firstName来查询,获取根据lastName来查询,只需要定义一个方法即可。注意firstName严格按照存入的mongodb的字段对应。在典型的java的应用程序,写这样一个接口的方法,需要自己实现,但是在springboot中,你只需要按照格式写一个接口名和对应的参数就可以了,因为springboot已经帮你实现了。 测试123456789101112131415161718192021222324252627282930313233343536373839@SpringBootApplicationpublic class SpringbootMongodbApplication implements CommandLineRunner { @Autowired private CustomerRepository repository; public static void main(String[] args) { SpringApplication.run(SpringbootMongodbApplication.class, args); } @Override public void run(String... args) throws Exception { repository.deleteAll(); // save a couple of customers repository.save(new Customer("Alice", "Smith")); repository.save(new Customer("Bob", "Smith")); // fetch all customers System.out.println("Customers found with findAll():"); System.out.println("-------------------------------"); for (Customer customer : repository.findAll()) { System.out.println(customer); } System.out.println(); // fetch an individual customer System.out.println("Customer found with findByFirstName('Alice'):"); System.out.println("--------------------------------"); System.out.println(repository.findByFirstName("Alice")); System.out.println("Customers found with findByLastName('Smith'):"); System.out.println("--------------------------------"); for (Customer customer : repository.findByLastName("Smith")) { System.out.println(customer); } } 在springboot的应用程序,加入测试代码。启动程序,控制台打印了: Customers found with findAll():——————————-Customer[id=58f880f589ffb696b8a6077e, firstName=’Alice’, lastName=’Smith’]Customer[id=58f880f589ffb696b8a6077f, firstName=’Bob’, lastName=’Smith’]Customer found with findByFirstName(‘Alice’):——————————–Customer[id=58f880f589ffb696b8a6077e, firstName=’Alice’, lastName=’Smith’]Customers found with findByLastName(‘Smith’):——————————–Customer[id=58f880f589ffb696b8a6077e, firstName=’Alice’, lastName=’Smith’]Customer[id=58f880f589ffb696b8a6077f, firstName=’Bob’, lastName=’Smith’] 测试通过。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料accessing-data-mongodb 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十二篇: 创建含有多module的springboot工程]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E4%BA%8C%E7%AF%87%EF%BC%9A%20%E5%88%9B%E5%BB%BA%E5%90%AB%E6%9C%89%E5%A4%9Amodule%E7%9A%84springboot%E5%B7%A5%E7%A8%8B%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71024153本文出自方志朋的博客 这篇文章主要介绍如何在springboot中如何创建含有多个module的工程,栗子中含有两个 module,一个作为libarary. 工程,另外一个是主工程,调用libary .其中libary jar有一个服务,main工程调用这个服务。 创建根工程创建一个maven 工程,其pom文件为: 12345678910111213<?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>com.forezp</groupId> <artifactId>springboot-multi-module</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging> <name>springboot-multi-module</name> <description>Demo project for Spring Boot</description></project> 需要注意的是packaging标签为pom 属性。 创建libary工程libary工程为maven工程,其pom文件的packaging标签为jar 属性。创建一个service组件,它读取配置文件的 service.message属性。 12345678910111213141516@ConfigurationProperties("service")public class ServiceProperties { /** * A message for the service. */ private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; }} 提供一个对外暴露的方法: 12345678@Configuration@EnableConfigurationProperties(ServiceProperties.class)public class ServiceConfiguration { @Bean public Service service(ServiceProperties properties) { return new Service(properties.getMessage()); }} 创建一个springbot工程引入相应的依赖,创建一个web服务: 123456789101112131415161718192021@SpringBootApplication@Import(ServiceConfiguration.class)@RestControllerpublic class DemoApplication { private final Service service; @Autowired public DemoApplication(Service service) { this.service = service; } @GetMapping("/") public String home() { return service.message(); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }} 在配置文件application.properties中加入: 1service.message=Hello World 打开浏览器访问:http://localhost:8080/;浏览器显示: Hello World 说明确实引用了libary中的方法。 参考资料https://spring.io/guides/gs/multi-module/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二篇:Spring Boot配置文件详解]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E7%AF%87%EF%BC%9ASpring%20Boot%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70437576本文出自方志朋的博客 springboot采纳了建立生产就绪Spring应用程序的观点。 Spring Boot优先于配置的惯例,旨在让您尽快启动和运行。在一般情况下,我们不需要做太多的配置就能够让spring boot正常运行。在一些特殊的情况下,我们需要做修改一些配置,或者需要有自己的配置属性。 一、自定义属性当我们创建一个springboot项目的时候,系统默认会为我们在src/main/java/resources目录下创建一个application.properties。个人习惯,我会将application.properties改为application.yml文件,两种文件格式都支持。 在application.yml自定义一组属性: 123my: name: forezp age: 12 如果你需要读取配置文件的值只需要加@Value(“${属性名}”): 1234567891011121314@RestControllerpublic class MiyaController { @Value("${my.name}") private String name; @Value("${my.age}") private int age; @RequestMapping(value = "/miya") public String miya(){ return name+":"+age; }} 启动工程,访问:localhost:8080/miya,浏览器显示: forezp:12 二、将配置文件的属性赋给实体类当我们有很多配置属性的时候,这时我们会把这些属性作为字段来创建一个javabean,并将属性值赋予给他们,比如: 12345678my: name: forezp age: 12 number: ${random.int} uuid : ${random.uuid} max: ${random.int(10)} value: ${random.value} greeting: hi,i'm ${my.name} 其中配置文件中用到了${random} ,它可以用来生成各种不同类型的随机值。 怎么讲这些属性赋于给一个javabean 呢,首先创建一个javabean : 12345678910111213@ConfigurationProperties(prefix = "my")@Componentpublic class ConfigBean { private String name; private int age; private int number; private String uuid; private int max; private String value; private String greeting; 省略了getter setter.... 需要加个注解@ConfigurationProperties,并加上它的prrfix。另外@Component可加可不加。另外spring-boot-configuration-processor依赖可加可不加,具体原因不详。 12345<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency> 另外需要在应用类或者application类,加EnableConfigurationProperties注解。 12345678910@RestController@EnableConfigurationProperties({ConfigBean.class})public class LucyController { @Autowired ConfigBean configBean; @RequestMapping(value = "/lucy") public String miya(){ return configBean.getGreeting()+" >>>>"+configBean.getName()+" >>>>"+ configBean.getUuid()+" >>>>"+configBean.getMax(); } 启动工程,访问localhost:8080/lucy,我们会发现配置文件信息读到了。 三、自定义配置文件上面介绍的是我们都把配置文件写到application.yml中。有时我们不愿意把配置都写到application配置文件中,这时需要我们自定义配置文件,比如test.properties: 12com.forezp.name=forezpcom.forezp.age=12 怎么将这个配置文件信息赋予给一个javabean呢? 1234567891011121314151617181920212223@Configuration@PropertySource(value = "classpath:test.properties")@ConfigurationProperties(prefix = "com.forezp")public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }} 在最新版本的springboot,需要加这三个注解。 @Configuration@PropertySource(value = “classpath:test.properties”)@ConfigurationProperties(prefix = “com.forezp”); 在1.4版本需要PropertySource加上location。 12345678910111213141516171819@RestController@EnableConfigurationProperties({ConfigBean.class,User.class})public class LucyController { @Autowired ConfigBean configBean; @RequestMapping(value = "/lucy") public String miya(){ return configBean.getGreeting()+" >>>>"+configBean.getName()+" >>>>"+ configBean.getUuid()+" >>>>"+configBean.getMax(); } @Autowired User user; @RequestMapping(value = "/user") public String user(){ return user.getName()+user.getAge(); }} 启动工程,打开localhost:8080/user;浏览器会显示: forezp12 四、多个环境配置文件在现实的开发环境中,我们需要不同的配置环境;格式为application-{profile}.properties,其中{profile}对应你的环境标识,比如: application-test.properties:测试环境 application-dev.properties:开发环境 application-prod.properties:生产环境 怎么使用?只需要我们在application.yml中加: 123spring: profiles: active: dev 其中application-dev.yml: 12server: port: 8082 启动工程,发现程序的端口不再是8080,而是8082。 想要使用对应的环境,只需要在application.properties中使用spring.profiles.active属性来设置,值对应上面提到的{profile},这里就是指dev、prod这2个。当然你也可以用命令行启动的时候带上参数: 1java -jar xxx.jar --spring.profiles.active=dev 源码下载:https://github.com/forezp/SpringBootLearning 五、参考文献spring-boot-reference-guide-zh pring Boot干货系列:(二)配置文件解析 Spring Boot属性配置文件详解 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十四篇: springboot整合docker]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E5%9B%9B%E7%AF%87%EF%BC%9A%20springboot%E6%95%B4%E5%90%88docker%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71024219本文出自方志朋的博客 这篇文篇介绍,怎么为 springboot程序构建一个docker镜像。docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。 准备工作环境: linux环境或mac,不要用windows jdk 8 maven 3.0 docker 对docker一无所知的看docker教程。 创建一个springboot工程引入web的起步依赖,创建一个 Controler: 123456789101112@SpringBootApplication@RestControllerpublic class SpringbootWithDockerApplication { @RequestMapping("/") public String home() { return "Hello Docker World"; } public static void main(String[] args) { SpringApplication.run(SpringbootWithDockerApplication.class, args); }} 将springboot工程容器化Docker有一个简单的dockerfile文件作为指定镜像的图层。让我们先创建一个 dockerFile文件: src/main/docker/Dockerfile: 123456FROM frolvlad/alpine-oraclejdk8:slimVOLUME /tmpADD springboot-with-docker-0.0.1-SNAPSHOT.jar app.jarRUN sh -c 'touch /app.jar'ENV JAVA_OPTS=""ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ] 我们通过maven 构建docker镜像。 在maven的pom目录,加上docker镜像构建的插件 1234567891011121314151617181920212223<properties> <docker.image.prefix>springio</docker.image.prefix></properties><build> <plugins> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.11</version> <configuration> <imageName>${docker.image.prefix}/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> </plugins></build> 注意:${docker.image.prefix} 为你在 docker官方仓库的用户名,如果你不需要上传镜像,随便填。 通过maven 命令: 第一步:mvn clean 第二步: mvn package docker:bulid ,如下: 镜像构建成功。查看镜像: docker images 显示: forezp/springboot-with-docker latest 60fdb5c61692 About a minute ago 195 MB 启动镜像: $ docker run -p 8080:8080 -t forezp/springboot-with-docker 打开浏览器访问 localhost:8080;浏览器显示:Hello Docker World。说明docker 的springboot工程已部署。 停止镜像: docker stop 60fdb5c61692 删除镜像: docker rm 60fdb5c61692 参考资料https://docs.docker.com/engine/reference/builder/)) http://www.runoob.com/docker/docker-tutorial.html 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十篇: 处理表单提交]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E7%AF%87%EF%BC%9A%20%E5%A4%84%E7%90%86%E8%A1%A8%E5%8D%95%E6%8F%90%E4%BA%A4%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71024024本文出自方志朋的博客 springboot对JMS提供了很好的支持,对其做了起步依赖。 构架工程创建一个springboot工程,在其pom文件加入: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId></dependency> 添加配置123456spring.mail.host=smtp.163.comspring.mail.username=miles02@163.comspring.mail.password=spring.mail.port=25spring.mail.protocol=smtpspring.mail.default-encoding=UTF-8 在password 中填写自己的邮箱密码。 测试发邮件测试代码清单如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133package com.forezp;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.core.io.FileSystemResource;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSenderImpl;import org.springframework.mail.javamail.MimeMessageHelper;import org.springframework.test.context.junit4.SpringRunner;import javax.mail.internet.MimeMessage;import java.io.File;@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootJmsApplicationTests { @Test public void contextLoads() { } @Autowired private JavaMailSenderImpl mailSender; /** * 发送包含简单文本的邮件 */ @Test public void sendTxtMail() { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); // 设置收件人,寄件人 simpleMailMessage.setTo(new String[] {"[email protected]"}); simpleMailMessage.setFrom("[email protected]"); simpleMailMessage.setSubject("Spring Boot Mail 邮件测试【文本】"); simpleMailMessage.setText("这里是一段简单文本。"); // 发送邮件 mailSender.send(simpleMailMessage); System.out.println("邮件已发送"); } /** * 发送包含HTML文本的邮件 * @throws Exception */ @Test public void sendHtmlMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } /** * 发送包含内嵌图片的邮件 * @throws Exception */ @Test public void sendAttachedImageMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>"); // cid为固定写法,imageId指定一个标识 sb.append("<img src=\"cid:imageId\"/></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置imageId FileSystemResource img = new FileSystemResource(new File("E:/1.jpg")); mimeMessageHelper.addInline("imageId", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } /** * 发送包含附件的邮件 * @throws Exception */ @Test public void sendAttendedFileMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8"); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置附件 FileSystemResource img = new FileSystemResource(new File("E:/1.jpg")); mimeMessageHelper.addAttachment("image.jpg", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }} 测试已全部通过,没有坑。 参考资料http://blog.720ui.com/2017/springboot_07_othercore_javamail/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十一篇: springboot集成JMS]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E4%B8%80%E7%AF%87%EF%BC%9A%20springboot%E9%9B%86%E6%88%90JMS%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71024024本文出自方志朋的博客 springboot对JMS提供了很好的支持,对其做了起步依赖。 构架工程创建一个springboot工程,在其pom文件加入: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId></dependency> 添加配置123456spring.mail.host=smtp.163.comspring.mail.username=miles02@163.comspring.mail.password=spring.mail.port=25spring.mail.protocol=smtpspring.mail.default-encoding=UTF-8 在password 中填写自己的邮箱密码。 测试发邮件测试代码清单如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133package com.forezp;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.core.io.FileSystemResource;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSenderImpl;import org.springframework.mail.javamail.MimeMessageHelper;import org.springframework.test.context.junit4.SpringRunner;import javax.mail.internet.MimeMessage;import java.io.File;@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootJmsApplicationTests { @Test public void contextLoads() { } @Autowired private JavaMailSenderImpl mailSender; /** * 发送包含简单文本的邮件 */ @Test public void sendTxtMail() { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); // 设置收件人,寄件人 simpleMailMessage.setTo(new String[] {"[email protected]"}); simpleMailMessage.setFrom("[email protected]"); simpleMailMessage.setSubject("Spring Boot Mail 邮件测试【文本】"); simpleMailMessage.setText("这里是一段简单文本。"); // 发送邮件 mailSender.send(simpleMailMessage); System.out.println("邮件已发送"); } /** * 发送包含HTML文本的邮件 * @throws Exception */ @Test public void sendHtmlMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } /** * 发送包含内嵌图片的邮件 * @throws Exception */ @Test public void sendAttachedImageMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>"); // cid为固定写法,imageId指定一个标识 sb.append("<img src=\"cid:imageId\"/></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置imageId FileSystemResource img = new FileSystemResource(new File("E:/1.jpg")); mimeMessageHelper.addInline("imageId", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); } /** * 发送包含附件的邮件 * @throws Exception */ @Test public void sendAttendedFileMail() throws Exception { MimeMessage mimeMessage = mailSender.createMimeMessage(); // multipart模式 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8"); mimeMessageHelper.setTo("[email protected]"); mimeMessageHelper.setFrom("[email protected]"); mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】"); StringBuilder sb = new StringBuilder(); sb.append("<html><head></head>"); sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>"); sb.append("</html>"); // 启用html mimeMessageHelper.setText(sb.toString(), true); // 设置附件 FileSystemResource img = new FileSystemResource(new File("E:/1.jpg")); mimeMessageHelper.addAttachment("image.jpg", img); // 发送邮件 mailSender.send(mimeMessage); System.out.println("邮件已发送"); }} 测试已全部通过,没有坑。 参考资料http://blog.720ui.com/2017/springboot_07_othercore_javamail/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十三篇: 异步方法]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E4%B8%89%E7%AF%87%EF%BC%9A%20%E5%BC%82%E6%AD%A5%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/71024169本文出自方志朋的博客 这篇文章主要介绍在springboot 使用异步方法,去请求github api. 创建工程在pom文件引入相关依赖: 12345678910111213<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId></dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId></dependency> 创建一个接收数据的实体: 12345678910111213141516171819202122232425262728@JsonIgnoreProperties(ignoreUnknown=true)public class User { private String name; private String blog; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBlog() { return blog; } public void setBlog(String blog) { this.blog = blog; } @Override public String toString() { return "User [name=" + name + ", blog=" + blog + "]"; }} 创建一个请求的 githib的service: 12345678910111213141516171819202122@Servicepublic class GitHubLookupService { private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class); private final RestTemplate restTemplate; public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); } @Async public Future<User> findUser(String user) throws InterruptedException { logger.info("Looking up " + user); String url = String.format("https://api.github.com/users/%s", user); User results = restTemplate.getForObject(url, User.class); // Artificial delay of 1s for demonstration purposes Thread.sleep(1000L); return new AsyncResult<>(results); }} 通过,RestTemplate去请求,另外加上类@Async 表明是一个异步任务。 开启异步任务: 1234567891011121314151617181920@SpringBootApplication@EnableAsyncpublic class Application extends AsyncConfigurerSupport { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(2); executor.setMaxPoolSize(2); executor.setQueueCapacity(500); executor.setThreadNamePrefix("GithubLookup-"); executor.initialize(); return executor; }} 通过@EnableAsync开启异步任务;并且配置AsyncConfigurerSupport,比如最大的线程池为2. 测试测试代码如下: 12345678910111213141516171819202122232425262728293031323334@Componentpublic class AppRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(AppRunner.class); private final GitHubLookupService gitHubLookupService; public AppRunner(GitHubLookupService gitHubLookupService) { this.gitHubLookupService = gitHubLookupService; } @Override public void run(String... args) throws Exception { // Start the clock long start = System.currentTimeMillis(); // Kick of multiple, asynchronous lookups Future<User> page1 = gitHubLookupService.findUser("PivotalSoftware"); Future<User> page2 = gitHubLookupService.findUser("CloudFoundry"); Future<User> page3 = gitHubLookupService.findUser("Spring-Projects"); // Wait until they are all done while (!(page1.isDone() && page2.isDone() && page3.isDone())) { Thread.sleep(10); //10-millisecond pause between each check } // Print results, including elapsed time logger.info("Elapsed time: " + (System.currentTimeMillis() - start)); logger.info("--> " + page1.get()); logger.info("--> " + page2.get()); logger.info("--> " + page3.get()); }} 启动程序,控制台会打印: 2017-04-30 13:11:10.351 INFO 1511 — [ GithubLookup-1] com.forezp.service.GitHubLookupService : Looking up PivotalSoftware2017-04-30 13:11:10.351 INFO 1511 — [ GithubLookup-2] com.forezp.service.GitHubLookupService : Looking up CloudFoundry2017-04-30 13:11:13.144 INFO 1511 — [ GithubLookup-2] com.forezp.service.GitHubLookupService : Looking up Spring-Projects 耗时:3908 分析:可以卡的前面2个方法分别在GithubLookup-1 和GithubLookup-2执行,第三个在GithubLookup-2执行,注意因为在配置线程池的时候最大线程为2.如果你把线程池的个数为3的时候,耗时减少。 如果去掉@Async,你会发现,执行这三个方法都在main线程中执行。耗时总结,如下: 2017-04-30 13:13:00.934 INFO 1527 — [ main] com.forezp.service.GitHubLookupService : Looking up PivotalSoftware2017-04-30 13:13:03.571 INFO 1527 — [ main] com.forezp.service.GitHubLookupService : Looking up CloudFoundry2017-04-30 13:13:04.865 INFO 1527 — [ main] com.forezp.service.GitHubLookupService : Looking up Spring-Projects 耗时:5261 通过这一个小的栗子,你应该对异步任务有了一定的了解。 参考资料https://spring.io/guides/gs/async-method/ 源码下载https://github.com/forezp/SpringBootLearning 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第二十五篇: 2小时学会springboot]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%BA%8C%E5%8D%81%E4%BA%94%E7%AF%87%EF%BC%9A%202%E5%B0%8F%E6%97%B6%E5%AD%A6%E4%BC%9Aspringboot%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/61472783本文出自方志朋的博客 一.什么是spring boot Takes an opinionated view of building production-ready Spring applications. Spring Boot favors convention over configuration and is designed to get you up and running as quickly as possible. 摘自官网 翻译:采纳了建立生产就绪Spring应用程序的观点。 Spring Boot优先于配置的惯例,旨在让您尽快启动和运行。 spring boot 致力于简洁,让开发者写更少的配置,程序能够更快的运行和启动。它是下一代javaweb框架,并且它是spring cloud(微服务)的基础。 二、搭建第一个sping boot 程序可以在start.spring.io上建项目,也可以用idea构建。本案列采用idea. 具体步骤: 1new prpject -> spring initializr ->{name :firstspringboot , type: mavenproject,packaging:jar ,..} ->{spring version :1.5.2 web: web } -> .... 应用创建成功后,会生成相应的目录和文件。 其中有一个Application类,它是程序的入口: 1234567@SpringBootApplicationpublic class FirstspringbootApplication { public static void main(String[] args) { SpringApplication.run(FirstspringbootApplication.class, args); }} 在resources文件下下又一个application.yml文件,它是程序的配置文件。默认为空,写点配置 ,程序的端口为8080,context-path为 /springboot: 123server: port: 8080 context-path: /springboot 写一个HelloController: 123456789@RestController //等同于同时加上了@Controller和@ResponseBodypublic class HelloController { //访问/hello或者/hi任何一个地址,都会返回一样的结果 @RequestMapping(value = {"/hello","/hi"},method = RequestMethod.GET) public String say(){ return "hi you!!!"; }} 运行 Application的main(),呈现会启动,由于springboot自动内置了servlet容器,所以不需要类似传统的方式,先部署到容器再启动容器。只需要运行main()即可,这时打开浏览器输入网址:localhost:8080/springboot/hi ,就可以在浏览器上看到: hi you!!! 三.属性配置在appliction.yml文件添加属性: 12345678server: port: 8080 context-path: /springbootgirl: name: B age: 18 content: content:${name},age:${age} 在java文件中,获取name属性,如下: 12@Value("${name}") private String name; 也可以通过ConfigurationProperties注解,将属性注入到bean中,通过Component注解将bean注解到spring容器中: 1234567891011121314151617181920212223@ConfigurationProperties(prefix="girl")@Componentpublic class GirlProperties { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }} 另外可以通过配置文件制定不同环境的配置文,具体见源码: 123spring: profiles: active: prod 四.通过jpa方式操作数据库导入jar ,在pom.xml中添加依赖: 123456789<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency>123456789 在appilication.yml中添加数据库配置: 1234567891011121314spring: profiles: active: prod datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/dbgirl?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: 123 jpa: hibernate: ddl-auto: create show-sql: true 这些都是数据库常见的一些配置没什么可说的,其中ddl_auto: create 代表在数据库创建表,update 代表更新,首次启动需要create ,如果你想通过hibernate 注解的方式创建数据库的表的话,之后需要改为 update. 创建一个实体girl,这是基于hibernate的: 123456789101112131415161718192021222324252627282930313233343536@Entitypublic class Girl { @Id @GeneratedValue private Integer id; private String cupSize; private Integer age; public Girl() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getCupSize() { return cupSize; } public void setCupSize(String cupSize) { this.cupSize = cupSize; }} 创建Dao接口, springboot 将接口类会自动注解到spring容器中,不需要我吗做任何配置,只需要继承JpaRepository 即可: 123//其中第二个参数为Id的类型public interface GirlRep extends JpaRepository<Girl,Integer>{ } 创建一个GirlController,写一个获取所有girl的api和添加girl的api ,自己跑一下就可以了: 12345678910111213141516171819202122232425262728293031@RestControllerpublic class GirlController { @Autowired private GirlRep girlRep; /** * 查询所有女生列表 * @return */ @RequestMapping(value = "/girls",method = RequestMethod.GET) public List<Girl> getGirlList(){ return girlRep.findAll(); } /** * 添加一个女生 * @param cupSize * @param age * @return */ @RequestMapping(value = "/girls",method = RequestMethod.POST) public Girl addGirl(@RequestParam("cupSize") String cupSize, @RequestParam("age") Integer age){ Girl girl = new Girl(); girl.setAge(age); girl.setCupSize(cupSize); return girlRep.save(girl); } } 如果需要事务的话,在service层加@Transaction注解即可。已经凌晨了,我要睡了. 源码;http://download.csdn.net/detail/forezp/9778235 关注我的专栏《史上最简单的 SpringCloud 教程 》http://blog.csdn.net/column/details/15197.html]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第三篇:SpringBoot用JdbcTemplates访问Mysql]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%B8%89%E7%AF%87%EF%BC%9ASpringBoot%E7%94%A8JdbcTemplates%E8%AE%BF%E9%97%AEMysql%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70477821本文出自方志朋的博客 本文介绍springboot通过jdbc访问关系型mysql,通过spring的JdbcTemplate去访问。 准备工作 jdk 1.8 maven 3.0 idea mysql 初始化mysql: 1234567891011-- create table `account`DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;INSERT INTO `account` VALUES ('1', 'aaa', '1000');INSERT INTO `account` VALUES ('2', 'bbb', '1000');INSERT INTO `account` VALUES ('3', 'ccc', '1000'); 创建工程引入依赖:在pom文件引入spring-boot-starter-jdbc的依赖: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency> 引入mysql连接类和连接池: 1234567891011<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version></dependency> 开启web: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency> 配置相关文件在application.properties文件配置mysql的驱动类,数据库地址,数据库账号、密码信息。 1234spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456 通过引入这些依赖和配置一些基本信息,springboot就可以访问数据库类。 具体编码实体类12345678public class Account { private int id ; private String name ; private double money;....省略了getter. setter} dao层1234567891011public interface IAccountDAO { int add(Account account); int update(Account account); int delete(int id); Account findAccountById(int id); List<Account> findAccountList();} 具体的实现类: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758package com.forezp.dao.impl;import com.forezp.dao.IAccountDAO;import com.forezp.entity.Account;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;import java.util.List;/** * Created by fangzhipeng on 2017/4/20. */@Repositorypublic class AccountDaoImpl implements IAccountDAO { @Autowired private JdbcTemplate jdbcTemplate; @Override public int add(Account account) { return jdbcTemplate.update("insert into account(name, money) values(?, ?)", account.getName(),account.getMoney()); } @Override public int update(Account account) { return jdbcTemplate.update("UPDATE account SET NAME=? ,money=? WHERE id=?", account.getName(),account.getMoney(),account.getId()); } @Override public int delete(int id) { return jdbcTemplate.update("DELETE from TABLE account where id=?",id); } @Override public Account findAccountById(int id) { List<Account> list = jdbcTemplate.query("select * from account where id = ?", new Object[]{id}, new BeanPropertyRowMapper(Account.class)); if(list!=null && list.size()>0){ Account account = list.get(0); return account; }else{ return null; } } @Override public List<Account> findAccountList() { List<Account> list = jdbcTemplate.query("select * from account", new Object[]{}, new BeanPropertyRowMapper(Account.class)); if(list!=null && list.size()>0){ return list; }else{ return null; } }} service层1234567891011121314public interface IAccountService { int add(Account account); int update(Account account); int delete(int id); Account findAccountById(int id); List<Account> findAccountList();} 具体实现类: 1234567891011121314151617181920212223242526272829@Servicepublic class AccountService implements IAccountService { @Autowired IAccountDAO accountDAO; @Override public int add(Account account) { return accountDAO.add(account); } @Override public int update(Account account) { return accountDAO.update(account); } @Override public int delete(int id) { return accountDAO.delete(id); } @Override public Account findAccountById(int id) { return accountDAO.findAccountById(id); } @Override public List<Account> findAccountList() { return accountDAO.findAccountList(); }} 构建一组restful api来展示1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859package com.forezp.web;import com.forezp.entity.Account;import com.forezp.service.IAccountService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;/** * Created by fangzhipeng on 2017/4/20. */@RestController@RequestMapping("/account")public class AccountController { @Autowired IAccountService accountService; @RequestMapping(value = "/list",method = RequestMethod.GET) public List<Account> getAccounts(){ return accountService.findAccountList(); } @RequestMapping(value = "/{id}",method = RequestMethod.GET) public Account getAccountById(@PathVariable("id") int id){ return accountService.findAccountById(id); } @RequestMapping(value = "/{id}",method = RequestMethod.PUT) public String updateAccount(@PathVariable("id")int id , @RequestParam(value = "name",required = true)String name, @RequestParam(value = "money" ,required = true)double money){ Account account=new Account(); account.setMoney(money); account.setName(name); account.setId(id); int t=accountService.update(account); if(t==1){ return account.toString(); }else { return "fail"; } } @RequestMapping(value = "",method = RequestMethod.POST) public String postAccount( @RequestParam(value = "name")String name, @RequestParam(value = "money" )double money){ Account account=new Account(); account.setMoney(money); account.setName(name); int t= accountService.add(account); if(t==1){ return account.toString(); }else { return "fail"; } }} 可以通过postman来测试,具体的我已经全部测试通过,没有任何问题。注意restful构建api的风格。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料relational-data-access 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第九篇: springboot整合Redis]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%B9%9D%E7%AF%87%EF%BC%9A%20springboot%E6%95%B4%E5%90%88Redis%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70991675本文出自方志朋的博客 这篇文章主要介绍springboot整合redis,至于没有接触过redis的同学可以看下这篇文章:5分钟带你入门Redis。 引入依赖:在pom文件中添加redis依赖: 1234<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency> 配置数据源123456789spring.redis.host=localhostspring.redis.port=6379#spring.redis.password=spring.redis.database=1spring.redis.pool.max-active=8spring.redis.pool.max-wait=-1spring.redis.pool.max-idle=500spring.redis.pool.min-idle=0spring.redis.timeout=0 如果你的redis有密码,配置下即可。经过上述两步的操作,你可以访问redis数据了。 数据访问层dao通过redisTemplate来访问redis. 12345678910111213141516@Repositorypublic class RedisDao { @Autowired private StringRedisTemplate template; public void setKey(String key,String value){ ValueOperations<String, String> ops = template.opsForValue(); ops.set(key,value,1, TimeUnit.MINUTES);//1分钟过期 } public String getValue(String key){ ValueOperations<String, String> ops = this.template.opsForValue(); return ops.get(key); }} 单元测试12345678910111213141516171819@RunWith(SpringRunner.class)@SpringBootTestpublic class SpringbootRedisApplicationTests { public static Logger logger= LoggerFactory.getLogger(SpringbootRedisApplicationTests.class); @Test public void contextLoads() { } @Autowired RedisDao redisDao; @Test public void testRedis(){ redisDao.setKey("name","forezp"); redisDao.setKey("age","11"); logger.info(redisDao.getValue("name")); logger.info(redisDao.getValue("age")); }} 启动单元测试,你发现控制台打印了: forezp 11 单元测试通过; 源码下载:https://github.com/forezp/SpringBootLearning 参考资料messaging-redis 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Springboot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SpringBoot非官方教程 第七篇:springboot开启声明式事务]]></title>
<url>%2F2017%2F10%2F06%2FSpringBoot%E9%9D%9E%E5%AE%98%E6%96%B9%E6%95%99%E7%A8%8B%20%20%E7%AC%AC%E4%B8%83%E7%AF%87%EF%BC%9Aspringboot%E5%BC%80%E5%90%AF%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1%2F</url>
<content type="text"><![CDATA[转载请标明出处:http://blog.csdn.net/forezp/article/details/70833629本文出自方志朋的博客 springboot开启事务很简单,只需要一个注解@Transactional 就可以了。因为在springboot中已经默认对jpa、jdbc、mybatis开启了事事务,引入它们依赖的时候,事物就默认开启。当然,如果你需要用其他的orm,比如beatlsql,就需要自己配置相关的事物管理器。 准备阶段以上一篇文章的代码为例子,即springboot整合mybatis,上一篇文章是基于注解来实现mybatis的数据访问层,这篇文章基于xml的来实现,并开启声明式事务。 环境依赖在pom文件中引入mybatis启动依赖: 12345<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version></dependency> 引入mysql 依赖 12345678910<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version></dependency> 初始化数据库脚本1234567891011-- create table `account`# DROP TABLE `account` IF EXISTSCREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;INSERT INTO `account` VALUES ('1', 'aaa', '1000');INSERT INTO `account` VALUES ('2', 'bbb', '1000');INSERT INTO `account` VALUES ('3', 'ccc', '1000'); 配置数据源123456spring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Drivermybatis.mapper-locations=classpath*:mybatis/*Mapper.xmlmybatis.type-aliases-package=com.forezp.entity 通过配置mybatis.mapper-locations来指明mapper的xml文件存放位置,我是放在resources/mybatis文件下的。mybatis.type-aliases-package来指明和数据库映射的实体的所在包。 经过以上步骤,springboot就可以通过mybatis访问数据库来。 创建实体类123456789public class Account { private int id ; private String name ; private double money; getter.. setter.. } 数据访问dao 层接口: 123public interface AccountMapper2 { int update( @Param("money") double money, @Param("id") int id);} mapper: 12345678910<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.forezp.dao.AccountMapper2"> <update id="update"> UPDATE account set money=#{money} WHERE id=#{id} </update></mapper> service层12345678910111213@Servicepublic class AccountService2 { @Autowired AccountMapper2 accountMapper2; @Transactional public void transfer() throws RuntimeException{ accountMapper2.update(90,1);//用户1减10块 用户2加10块 int i=1/0; accountMapper2.update(110,2); }} @Transactional,声明事务,并设计一个转账方法,用户1减10块,用户2加10块。在用户1减10 ,之后,抛出异常,即用户2加10块钱不能执行,当加注解@Transactional之后,两个人的钱都没有增减。当不加@Transactional,用户1减了10,用户2没有增加,即没有操作用户2 的数据。可见@Transactional注解开启了事物。 结语springboot 开启事物很简单,只需要加一行注解就可以了,前提你用的是jdbctemplate, jpa, mybatis,这种常见的orm。 源码下载:https://github.com/forezp/SpringBootLearning 参考资料managing-transactions/ 优秀文章推荐: 更多springboot 教程:springBoot非官方教程 | 文章汇总 更多springcoud 教程:史上最简单的 SpringCloud 教程 | 文章汇总]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>SpringBoot</tag>
</tags>
</entry>
<entry>
<title><![CDATA[centOS7安装mysql5.7]]></title>
<url>%2F2017%2F09%2F02%2FMerkleTree%2F</url>
<content type="text"><![CDATA[MerkleTree介绍 Merkle Tree,通常也被称作Hash Tree,顾名思义,就是存储hash值的一棵树。Merkle树的叶子是数据块(例如,文件或者文件的集合)的hash值。非叶节点是其对应子节点串联字符串的hash。 Hash Hash是一个把任意长度的数据映射成固定长度数据的函数2。例如,对于数据完整性校验,最简单的方法是对整个数据做Hash运算得到固定长度的Hash值,然后把得到的Hash值公布在网上,这样用户下载到数据之后,对数据再次进行Hash运算,比较运算结果和网上公布的Hash值进行比较,如果两个Hash值相等,说明下载的数据没有损坏。可以这样做是因为输入数据的稍微改变就会引起Hash运算结果的面目全非,而且根据Hash值反推原始输入数据的特征是困难的。如果从一个稳定的服务器进行下载,采用单一Hash是可取的。但如果数据源不稳定,一旦数据损坏,就需要重新下载,这种下载的效率是很低的。 HashList 在点对点网络中作数据传输的时候,会同时从多个机器上下载数据,而且很多机器可以认为是不稳定或者不可信的。为了校验数据的完整性,更好的办法是把大的文件分割成小的数据块(例如,把分割成2K为单位的数据块)。这样的好处是,如果小块数据在传输过程中损坏了,那么只要重新下载这一快数据就行了,不用重新下载整个文件。 怎么确定小的数据块没有损坏哪?只需要为每个数据块做Hash。BT下载的时候,在下载到真正数据之前,我们会先下载一个Hash列表。那么问题又来了,怎么确定这个Hash列表本事是正确的哪?答案是把每个小块数据的Hash值拼到一起,然后对这个长字符串在作一次Hash运算,这样就得到Hash列表的根Hash(Top Hash or Root Hash )。 下载数据的时候,首先从可信的数据源得到正确的根Hash,就可以用它来校验Hash列表了,然后通过校验后的Hash列表校验数据块。 HashTree Merkle Tree可以看做Hash List的泛化(Hash List可以看作一种特殊的Merkle Tree,即树高为2的多叉Merkle Tree)。 在最底层,和哈希列表一样,我们把数据分成小的数据块,有相应地哈希和它对应。但是往上走,并不是直接去运算根哈希,而是把相邻的两个哈希合并成一个字符串,然后运算这个字符串的哈希,这样每两个哈希就结婚生子,得到了一个”子哈希“。如果最底层的哈希总数是单数,那到最后必然出现一个单身哈希,这种情况就直接对它进行哈希运算,所以也能得到它的子哈希。于是往上推,依然是一样的方式,可以得到数目更少的新一级哈希,最终必然形成一棵倒挂的树,到了树根的这个位置,这一代就剩下一个根哈希了,我们把它叫做 Merkle Root。 在p2p网络下载网络之前,先从可信的源获得文件的Merkle Tree树根。一旦获得了树根,就可以从其他从不可信的源获取Merkle tree。通过可信的树根来检查接受到的Merkle Tree。如果Merkle Tree是损坏的或者虚假的,就从其他源获得另一个Merkle Tree,直到获得一个与可信树根匹配的Merkle Tree。 Merkle Tree和Hash List的主要区别是,可以直接下载并立即验证Merkle Tree的一个分支。因为可以将文件切分成小的数据块,这样如果有一块数据损坏,仅仅重新下载这个数据块就行了。如果文件非常大,那么Merkle tree和Hash list都很到,但是Merkle tree可以一次下载一个分支,然后立即验证这个分支,如果分支验证通过,就可以下载数据了。而Hash list只有下载整个hash list才能验证。 Merkle Tree的特点 MT是一种树,大多数是二叉树,也可以多叉树,无论是几叉树,它都具有树结构的所有特点; Merkle Tree的叶子节点的value是数据集合的单元数据或者单元数据HASH。 非叶子节点的value是根据它下面所有的叶子节点值,然后按照Hash算法计算而得出的。 通常,加密的hash方法像SHA-2和MD5用来做hash。但如果仅仅防止数据不是蓄意的损坏或篡改,可以改用一些安全性低但效率高的校验和算法,如CRC。 Merkle Tree的操作###创建Merkle Tree 创建Merckle Tree 加入最底层有9个数据块。 (红色线)对数据块做hash运算,Node0i = hash(Data0i), i=1,2,…,9。 (橙色线)相邻两个hash块串联,然后做hash运算,Node1((i+1)/2) = hash(Node0i+Node0(i+1)), i=1,3,5,7;对于i=9, Node1((i+1)/2) = hash(Node0i) (黄色线)重复step3 (绿色线)重复step3 (蓝色线)重复step3,生成Merkle Tree Root 检索数据块为了更好理解,我们假设有A和B两台机器,A需要与B相同目录下有8个文件,文件分别是f1 f2 f3 ….f8。这个时候我们就可以通过Merkle Tree来进行快速比较。假设我们在文件创建的时候每个机器都构建了一个Merkle Tree。具体如下图: 从上图可得知,叶子节点node7的value = hash(f1),是f1文件的HASH;而其父亲节点node3的value = hash(v7, v8),也就是其子节点node7 node8的值得HASH。就是这样表示一个层级运算关系。root节点的value其实是所有叶子节点的value的唯一特征。 假如A上的文件5与B上的不一样。我们怎么通过两个机器的merkle treee信息找到不相同的文件? 这个比较检索过程如下: Step1. 首先比较v0是否相同,如果不同,检索其孩子node1和node2. Step2. v1 相同,v2不同。检索node2的孩子node5 node6; Step3. v5不同,v6相同,检索比较node5的孩子node 11 和node 12 Step4. v11不同,v12相同。node 11为叶子节点,获取其目录信息。 Step5. 检索比较完毕。 以上过程的理论复杂度是Log(N)。过程描述图如下:从上图可以得知整个过程可以很快的找到对应的不相同的文件。 Merkle Tree的应用数字签名 最初Merkle Tree目的是高效的处理Lamport one-time signatures。 每一个Lamport key只能被用来签名一个消息,但是与Merkle tree结合可以来签名多条Merkle。这种方法成为了一种高效的数字签名框架,即Merkle Signature Scheme。###P2P网络 在P2P网络中,Merkle Tree用来确保从其他节点接受的数据块没有损坏且没有被替换,甚至检查其他节点不会欺骗或者发布虚假的块。大家所熟悉的BT下载就是采用了P2P技术来让客户端之间进行数据传输,一来可以加快数据下载速度,二来减轻下载服务器的负担。BT即BitTorrent,是一种中心索引式的P2P文件分分析通信协议。 要进下载必须从中心索引服务器获取一个扩展名为torrent的索引文件(即大家所说的种子),torrent文件包含了要共享文件的信息,包括文件名,大小,文件的Hash信息和一个指向Tracker的URL[8]。Torrent文件中的Hash信息是每一块要下载的文件内容的加密摘要,这些摘要也可运行在下载的时候进行验证。大的torrent文件是Web服务器的瓶颈,而且也不能直接被包含在RSS或gossiped around(用流言传播协议进行传播)。一个相关的问题是大数据块的使用,因为为了保持torrent文件的非常小,那么数据块Hash的数量也得很小,这就意味着每个数据块相对较大。大数据块影响节点之间进行交易的效率,因为只有当大数据块全部下载下来并校验通过后,才能与其他节点进行交易。 就解决上面两个问题是用一个简单的Merkle Tree代替Hash List。设计一个层数足够多的满二叉树,叶节点是数据块的Hash,不足的叶节点用0来代替。上层的节点是其对应孩子节点串联的hash。Hash算法和普通torrent一样采用SHA1。其数据传输过程和第一节中描述的类似。 BitCoin和Ethereum Merkle Proof最早的应用是Bitcoin,它是由中本聪在2009年描述并创建的。Bitcoin的Blockchain利用Merkle proofs来存储每个区块的交易]]></content>
<categories>
<category>区块链</category>
</categories>
<tags>
<tag>区块链</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Netty学习]]></title>
<url>%2F2017%2F08%2F09%2Fjava-netty%2F</url>
<content type="text"><![CDATA[java-nettynetty常用API学习netty简介 Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来使开发网络应用程序,这种新的方式使得它很容易使用和有很强的扩展性。Netty的内部实现时很复杂的,但是Netty提供了简单易用的api从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。 网络应用程序通常需要有较高的可扩展性,无论是Netty还是其他的基于Java NIO的框架,都会提供可扩展性的解决方案。Netty中一个关键组成部分是它的异步特性. netty的helloworld下载netty包 下载netty包,下载地址http://netty.io/-N/ 服务端启动类123456789101112131415161718192021222324252627282930313233343536373839404142434445464748import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.Channel;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;/** * • 配置服务器功能,如线程、端口 • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么 * */public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup eventLoopGroup = null; try { //创建ServerBootstrap实例来引导绑定和启动服务器 ServerBootstrap serverBootstrap = new ServerBootstrap(); //创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等 eventLoopGroup = new NioEventLoopGroup(); //指定通道类型为NioServerSocketChannel,设置InetSocketAddress让服务器监听某个端口已等待客户端连接。 serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress("localhost",port).childHandler(new ChannelInitializer<Channel>() { //设置childHandler执行所有的连接请求 @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new EchoServerHandler()); } }); // 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。 ChannelFuture channelFuture = serverBootstrap.bind().sync(); System.out.println("开始监听,端口为:" + channelFuture.channel().localAddress()); channelFuture.channel().closeFuture().sync(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(20000).start(); }} 服务端回调方法1234567891011121314151617181920212223242526272829303132333435363738394041import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import java.util.Date;public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server 读取数据……"); //读取数据 ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("接收客户端数据:" + body); //向客户端写数据 System.out.println("server向client发送数据"); String currentTime = new Date(System.currentTimeMillis()).toString(); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("server 读取数据完毕.."); ctx.flush();//刷新后才将数据发出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }} 客户端启动类12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import java.net.InetSocketAddress;/** * • 连接服务器 • 写数据到服务器 • 等待接受服务器返回相同的数据 • 关闭连接 * */public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws Exception { EventLoopGroup nioEventLoopGroup = null; try { //创建Bootstrap对象用来引导启动客户端 Bootstrap bootstrap = new Bootstrap(); //创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据 nioEventLoopGroup = new NioEventLoopGroup(); //创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址 bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer<SocketChannel>() { //添加一个ChannelHandler,客户端成功连接服务器后就会被执行 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler()); } }); // • 调用Bootstrap.connect()来连接服务器 ChannelFuture f = bootstrap.connect().sync(); // • 最后关闭EventLoopGroup来释放资源 f.channel().closeFuture().sync(); } finally { nioEventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoClient("localhost", 20000).start(); }} netty中handler的执行顺序 Handler在netty中,无疑占据着非常重要的地位。Handler与Servlet中的filter很像,通过Handler可以完成通讯报文的解码编码、拦截指定的报文、统一对日志错误进行处理、统一对请求进行计数、控制Handler执行与否。一句话,没有它做不到的只有你想不到的。 Netty中的所有handler都实现自ChannelHandler接口。按照输出输出来分,分为ChannelInboundHandler、ChannelOutboundHandler两大类。ChannelInboundHandler对从客户端发往服务器的报文进行处理,一般用来执行解码、读取客户端数据、进行业务处理等;ChannelOutboundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码、发送报文到客户端。 Netty中,可以注册多个handler。ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行,如下图所示,按照注册的先后顺序对Handler进行排序,request进入Netty后的执行顺序为 代码示例 server 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.Channel;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;/** * • 配置服务器功能,如线程、端口 • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么 */public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup eventLoopGroup = null; try { //server端引导类 ServerBootstrap serverBootstrap = new ServerBootstrap(); //连接池处理数据 eventLoopGroup = new NioEventLoopGroup(); serverBootstrap.group(eventLoopGroup) .channel(NioServerSocketChannel.class)//指定通道类型为NioServerSocketChannel,一种异步模式,OIO阻塞模式为OioServerSocketChannel .localAddress("localhost",port)//设置InetSocketAddress让服务器监听某个端口已等待客户端连接。 .childHandler(new ChannelInitializer<Channel>() {//设置childHandler执行所有的连接请求 @Override protected void initChannel(Channel ch) throws Exception { // 注册两个InboundHandler,执行顺序为注册顺序,所以应该是InboundHandler1 InboundHandler2 // 注册两个OutboundHandler,执行顺序为注册顺序的逆序,所以应该是OutboundHandler2 OutboundHandler1 ch.pipeline().addLast(new EchoInHandler1()); ch.pipeline().addLast(new EchoInHandler2()); ch.pipeline().addLast(new EchoOutHandler1()); ch.pipeline().addLast(new EchoOutHandler2()); } }); // 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。 ChannelFuture channelFuture = serverBootstrap.bind().sync(); System.out.println("开始监听,端口为:" + channelFuture.channel().localAddress()); channelFuture.channel().closeFuture().sync(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(20000).start(); }} EchoInHandler1 1234567891011121314151617181920212223public class EchoInHandler1 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("in1"); // 通知执行下一个InboundHandler ctx.fireChannelRead(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush();//刷新后才将数据发出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }} EchoInHandler2 1234567891011121314151617181920212223242526272829303132333435363738394041import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import java.util.Date;import cn.itcast_03_netty.sendobject.bean.Person;public class EchoInHandler2 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("in2"); ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("接收客户端数据:" + body); //向客户端写数据 System.out.println("server向client发送数据"); String currentTime = new Date(System.currentTimeMillis()).toString(); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush();//刷新后才将数据发出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }} EchoOutHandler1 123456789101112131415161718192021import java.util.Date;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelOutboundHandlerAdapter;import io.netty.channel.ChannelPromise;public class EchoOutHandler1 extends ChannelOutboundHandlerAdapter { @Override // 向client发送消息 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("out1"); /*System.out.println(msg);*/ String currentTime = new Date(System.currentTimeMillis()).toString(); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); ctx.flush(); }} EchoOutHandler2 12345678910111213141516import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelOutboundHandlerAdapter;import io.netty.channel.ChannelPromise;public class EchoOutHandler2 extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("out2"); // 执行下一个OutboundHandler /*System.out.println("at first..msg = "+msg); msg = "hi newed in out2";*/ super.write(ctx, msg, promise); }} 总结 在使用Handler的过程中,需要注意: ChannelInboundHandler之间的传递,通过调用ctx.fireChannelRead(msg)实现;调用ctx.write(msg) 将传递到ChannelOutboundHandler。 ctx.write()方法执行后,需要调用flush()方法才能令它立即执行。 流水线pipeline中outhandler不能放在最后,否则不生效 Handler的消费处理放在最后一个处理。 netty发送对象简介 Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,例如http协议中,就是通过HttpRequestDecoder对ByteBuf数据流进行处理,转换成http的对象。基于这个思路,我自定义一种通讯协议:Server和客户端直接传输java对象。 实现的原理是通过Encoder把java对象转换成ByteBuf流进行传输,通过Decoder把ByteBuf转换成java对象进行处理,处理逻辑如下图所示: 代码 bean 1234567891011121314import java.io.Serializable;public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private String sex; private int age; public String toString() { return "name:" + name + " sex:" + sex + " age:" + age; }get/set...} 序列化 1234567891011121314151617181920import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.MessageToByteEncoder;import cn.itcast_03_netty.sendobject.bean.Person;import cn.itcast_03_netty.sendobject.utils.ByteObjConverter; /** * 序列化 * 将object转换成Byte[] * */public class PersonEncoder extends MessageToByteEncoder<Person> { @Override protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception { //工具类:将object转换为byte[] byte[] datas = ByteObjConverter.objectToByte(msg); out.writeBytes(datas); ctx.flush(); }} 反序列化 1234567891011121314151617181920212223242526import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.handler.codec.ByteToMessageDecoder;import java.util.List;import cn.itcast_03_netty.sendobject.utils.ByteBufToBytes;import cn.itcast_03_netty.sendobject.utils.ByteObjConverter; /** * 反序列化 * 将Byte[]转换为Object * */public class PersonDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { //工具类:将ByteBuf转换为byte[] ByteBufToBytes read = new ByteBufToBytes(); byte[] bytes = read.read(in); //工具类:将byte[]转换为object Object obj = ByteObjConverter.byteToObject(bytes); out.add(obj); } } 转换工具类 123456789101112131415import io.netty.buffer.ByteBuf;public class ByteBufToBytes { /** * 将ByteBuf转换为byte[] * @param datas * @return */ public byte[] read(ByteBuf datas) { byte[] bytes = new byte[datas.readableBytes()];// 创建byte[] datas.readBytes(bytes);// 将ByteBuf转换为byte[] return bytes; }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;public class ByteObjConverter { /** * 使用IO的inputstream流将byte[]转换为object * @param bytes * @return */ public static Object byteToObject(byte[] bytes) { Object obj = null; ByteArrayInputStream bi = new ByteArrayInputStream(bytes); ObjectInputStream oi = null; try { oi = new ObjectInputStream(bi); obj = oi.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { try { bi.close(); } catch (IOException e) { e.printStackTrace(); } try { oi.close(); } catch (IOException e) { e.printStackTrace(); } } return obj; } /** * 使用IO的outputstream流将object转换为byte[] * @param bytes * @return */ public static byte[] objectToByte(Object obj) { byte[] bytes = null; ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = null; try { oo = new ObjectOutputStream(bo); oo.writeObject(obj); bytes = bo.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { bo.close(); } catch (IOException e) { e.printStackTrace(); } try { oo.close(); } catch (IOException e) { e.printStackTrace(); } } return bytes; }} ServerHandler 123456789101112131415161718192021222324252627282930import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import cn.itcast_03_netty.sendobject.bean.Person;public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Person person = (Person) msg; System.out.println(person.getName()); System.out.println(person.getAge()); System.out.println(person.getSex()); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("server 读取数据完毕.."); ctx.flush();//刷新后才将数据发出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }} server 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.Channel;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;import cn.itcast_03_netty.sendobject.coder.PersonDecoder;/** * • 配置服务器功能,如线程、端口 • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么 * * */public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup eventLoopGroup = null; try { //创建ServerBootstrap实例来引导绑定和启动服务器 ServerBootstrap serverBootstrap = new ServerBootstrap(); //创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等 eventLoopGroup = new NioEventLoopGroup(); //指定通道类型为NioServerSocketChannel,一种异步模式,OIO阻塞模式为OioServerSocketChannel //设置InetSocketAddress让服务器监听某个端口已等待客户端连接。 serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress("localhost",port) .childHandler(new ChannelInitializer<Channel>() { //设置childHandler执行所有的连接请求 @Override protected void initChannel(Channel ch) throws Exception { //注册解码的handler ch.pipeline().addLast(new PersonDecoder()); //IN1 反序列化 //添加一个入站的handler到ChannelPipeline ch.pipeline().addLast(new EchoServerHandler()); //IN2 } }); // 最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。 ChannelFuture channelFuture = serverBootstrap.bind().sync(); System.out.println("开始监听,端口为:" + channelFuture.channel().localAddress()); channelFuture.channel().closeFuture().sync(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(20000).start(); }} clientHandler 12345678910111213141516171819202122232425262728293031323334353637383940import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import cn.itcast_03_netty.sendobject.bean.Person;public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { // 客户端连接服务器后被调用 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Person person = new Person(); person.setName("angelababy"); person.setSex("girl"); person.setAge(18); ctx.write(person); ctx.flush(); } // • 从服务器接收到数据后调用 @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client 读取server数据.."); // 服务端返回消息后 ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("服务端数据为 :" + body); } // • 发生异常时被调用 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("client exceptionCaught.."); // 释放资源 ctx.close(); }} client 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;import java.net.InetSocketAddress;import cn.itcast_03_netty.sendobject.coder.PersonEncoder;/** * • 连接服务器 • 写数据到服务器 • 等待接受服务器返回相同的数据 • 关闭连接 * */public class EchoClient { private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws Exception { EventLoopGroup nioEventLoopGroup = null; try { // 创建Bootstrap对象用来引导启动客户端 Bootstrap bootstrap = new Bootstrap(); // 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据 nioEventLoopGroup = new NioEventLoopGroup(); // 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址 bootstrap.group(nioEventLoopGroup)// .channel(NioSocketChannel.class)// .remoteAddress(new InetSocketAddress(host, port))// .handler(new ChannelInitializer<SocketChannel>() {// // 添加一个ChannelHandler,客户端成功连接服务器后就会被执行 @Override protected void initChannel(SocketChannel ch) throws Exception { // 注册编码的handler ch.pipeline().addLast(new PersonEncoder()); //out //注册处理消息的handler ch.pipeline().addLast(new EchoClientHandler()); //in } }); // • 调用Bootstrap.connect()来连接服务器 ChannelFuture f = bootstrap.connect().sync(); // • 最后关闭EventLoopGroup来释放资源 f.channel().closeFuture().sync(); } finally { nioEventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoClient("localhost", 20000).start(); }}]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Nio</tag>
<tag>Netty</tag>
</tags>
</entry>
<entry>
<title><![CDATA[日志采集框架Flume]]></title>
<url>%2F2017%2F07%2F12%2F%E6%97%A5%E5%BF%97%E9%87%87%E9%9B%86%E6%A1%86%E6%9E%B6Flume%2F</url>
<content type="text"><![CDATA[Flume介绍概述 Flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。 Flume可以采集文件,socket数据包等各种形式源数据,又可以将采集到的数据输出到HDFS、hbase、hive、kafka等众多外部存储系统中 一般的采集需求,通过对flume的简单配置即可实现 Flume针对特殊场景也具备良好的自定义扩展能力,因此,flume可以适用于大部分的日常数据采集场景 运行机制 Flume分布式系统中最核心的角色是agent,flume采集系统就是由一个个agent所连接起来形成 每一个agent相当于一个数据传递员,内部有三个组件: Source:采集源,用于跟数据源对接,以获取数据 Sink:下沉地,采集数据的传送目的,用于往下一级agent传递数据或者往最终存储系统传递数 Channel:angent内部的数据传输通道,用于从source将数据传递到sink Flume采集系统结构图 简单结构单个agent采集数据 复杂结构多级agent之间串联 Flume实战案例Flume的安装部署 Flume的安装非常简单,只需要解压即可,当然,前提是已有hadoop环境 上传安装包到数据源所在节点上然后解压 tar -zxvf apache-flume-1.6.0-bin.tar.gz然后进入flume的目录,修改conf下的flume-env.sh,在里面配置JAVA_HOME 根据数据采集的需求配置采集方案,描述在配置文件中(文件名可任意自定义) 指定采集方案配置文件,在相应的节点上启动flume agent 先用一个最简单的例子来测试一下程序环境是否正常 先在flume的conf目录下新建一个文件 vi netcat-logger.conf 123456789101112131415161718192021# 定义这个agent中各组件的名字a1.sources = r1a1.sinks = k1a1.channels = c1# 描述和配置source组件:r1a1.sources.r1.type = netcata1.sources.r1.bind = localhosta1.sources.r1.port = 44444# 描述和配置sink组件:k1a1.sinks.k1.type = logger# 描述和配置channel组件,此处使用是内存缓存的方式a1.channels.c1.type = memorya1.channels.c1.capacity = 1000a1.channels.c1.transactionCapacity = 100# 描述和配置source channel sink之间的连接关系a1.sources.r1.channels = c1a1.sinks.k1.channel = c1 启动agent去采集数据 bin/flume-ng agent -c conf -f conf/netcat-logger.conf -n a1 -Dflume.root.logger=INFO,console 123-c conf 指定flume自身的配置文件所在目录-f conf/netcat-logger.con 指定我们所描述的采集方案-n a1 指定我们这个agent的名字 测试 先要往agent采集监听的端口上发送数据,让agent有数据可采随便在一个能跟agent节点联网的机器上 telnet anget-hostname port 如:(telnet localhost 44444) 采集案例采集文件到HDFS 采集需求:比如业务系统使用log4j生成的日志,日志内容不断增加,需要把追加到日志文件中的数据实时采集到hdfs 根据需求,首先定义以下3大要素 采集源,即source——监控文件内容更新 : exec ‘tail -F file’ 下沉目标,即sink——HDFS文件系统 : hdfs sink Source和sink之间的传递通道——channel,可用file channel 也可以用 内存channel 配置文件编写: vi conf/tail-hdfs.conf 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152# Name the components on this agenta1.sources = r1a1.sinks = k1a1.channels = c1#exec 指的是命令# Describe/configure the sourcea1.sources.r1.type = exec#F根据文件名追中, f根据文件的nodeid追中a1.sources.r1.command = tail -F /home/hadoop/log/test.loga1.sources.r1.channels = c1# Describe the sink#下沉目标a1.sinks.k1.type = hdfsa1.sinks.k1.channel = c1#指定目录, flum帮做目的替换a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/#文件的命名, 前缀a1.sinks.k1.hdfs.filePrefix = events-#10 分钟就改目录a1.sinks.k1.hdfs.round = truea1.sinks.k1.hdfs.roundValue = 10a1.sinks.k1.hdfs.roundUnit = minute#文件滚动之前的等待时间(秒)a1.sinks.k1.hdfs.rollInterval = 3#文件滚动的大小限制(bytes)a1.sinks.k1.hdfs.rollSize = 500#写入多少个event数据后滚动文件(事件个数)a1.sinks.k1.hdfs.rollCount = 20#5个事件就往里面写入a1.sinks.k1.hdfs.batchSize = 5#用本地时间格式化目录a1.sinks.k1.hdfs.useLocalTimeStamp = true#下沉后, 生成的文件类型,默认是Sequencefile,可用DataStream,则为普通文本a1.sinks.k1.hdfs.fileType = DataStream# Use a channel which buffers events in memorya1.channels.c1.type = memorya1.channels.c1.capacity = 1000a1.channels.c1.transactionCapacity = 100# Bind the source and sink to the channela1.sources.r1.channels = c1a1.sinks.k1.channel = c1 启动命令 bin/flume-ng agent -c conf -f conf/tail-hdfs.conf -n a1 创建文件夹 mkdir /home/hadoop/log 编写脚本 12345while truedoecho 111111 >> /home/hadoop/log/test.logsleep 0.5done 更多source和sink组件Flume支持众多的source和sink类型,详细手册可参考官方文档http://flume.apache.org/FlumeUserGuide.html]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
<tag>Flume</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Sqoop数据迁移]]></title>
<url>%2F2017%2F07%2F12%2Fsqoop%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%2F</url>
<content type="text"><![CDATA[概述 sqoop是apache旗下一款“Hadoop和关系数据库服务器之间传送数据”的工具。 导入数据:MySQL,Oracle导入数据到Hadoop的HDFS、HIVE、HBASE等数据存储系统; 导出数据:从Hadoop的文件系统中导出数据到关系数据库 工作机制 将导入或导出命令翻译成mapreduce程序来实现 在翻译出的mapreduce中主要是对inputformat和outputformat进行定制 sqoop实战及原理sqoop安装 安装sqoop的前提是已经具备java和hadoop的环境 1. 下载并解压 最新版下载地址http://ftp.wayne.edu/apache/sqoop/1.4.6/ 2. 修改配置文件 $ cd $SQOOP_HOME/conf $ mv sqoop-env-template.sh sqoop-env.sh 打开sqoop-env.sh并编辑下面几行: 123export HADOOP_COMMON_HOME=/home/hadoop/apps/hadoop-2.6.1/ export HADOOP_MAPRED_HOME=/home/hadoop/apps/hadoop-2.6.1/export HIVE_HOME=/home/hadoop/apps/hive-1.2.1 3. 加入mysql的jdbc驱动包 cp ~/app/hive/lib/mysql-connector-java-5.1.28.jar $SQOOP_HOME/lib/ 4. 验证启动 $ cd $SQOOP_HOME/bin$ sqoop-version 预期的输出: 12315/12/17 14:52:32 INFO sqoop.Sqoop: Running Sqoop version: 1.4.6Sqoop 1.4.6 git commit id 5b34accaca7de251fc91161733f906af2eddbe83Compiled by abe on Fri Aug 1 11:19:26 PDT 2015 到这里,整个Sqoop安装工作完成。 Sqoop的数据导入 “导入工具”导入单个表从RDBMS到HDFS。表中的每一行被视为HDFS的记录。所有记录都存储为文本文件的文本数据(或者Avro、sequence文件等二进制数据) 语法 下面的语法用于将数据导入HDFS。 $ sqoop import (generic-args) (import-args) 示例表数据 在mysql中有一个库userdb中三个表:emp, emp_add和emp_contact 表emp: id name deg salary dept 1201 gopal manager 50,000 TP 1202 manisha Proof reader 30,000 TP 1203 khalil php dev 30,000 AC 1204 prasanth php dev 20,000 AC 1205 kranthi admin 20,000 TP 表emp_add: id hno street city 1201 288A vgiri jublee 1202 108I aoc sec-bad 1203 144Z pgutta hyd 1204 78B old city sec-bad 1205 720X hitec sec-bad 表emp_conn: id phno email 1201 2356742 [email protected] 1202 1661663 [email protected] 1203 8887776 [email protected] 1204 9988774 [email protected] 1205 1231231 [email protected] 导入表表数据到HDFS 下面的命令用于从MySQL数据库服务器中的emp表导入HDFS。 123456$ bin/sqoop import \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--table emp \--m 1 如果成功执行,那么会得到下面的输出。 123456789101114/12/22 15:24:54 INFO sqoop.Sqoop: Running Sqoop version: 1.4.514/12/22 15:24:56 INFO manager.MySQLManager: Preparing to use a MySQL streaming resultset.INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-hadoop/compile/cebe706d23ebb1fd99c1f063ad51ebd7/emp.jar-----------------------------------------------------O mapreduce.Job: map 0% reduce 0%14/12/22 15:28:08 INFO mapreduce.Job: map 100% reduce 0%14/12/22 15:28:16 INFO mapreduce.Job: Job job_1419242001831_0001 completed successfully----------------------------------------------------------------------------------------------------------14/12/22 15:28:17 INFO mapreduce.ImportJobBase: Transferred 145 bytes in 177.5849 seconds (0.8165 bytes/sec)14/12/22 15:28:17 INFO mapreduce.ImportJobBase: Retrieved 5 records. 为了验证在HDFS导入的数据,请使用以下命令查看导入的数据 $ $HADOOP_HOME/bin/hadoop fs -cat /user/hadoop/emp/part-m-00000 emp表的数据和字段之间用逗号(,)表示。 123451201, gopal, manager, 50000, TP1202, manisha, preader, 50000, TP1203, kalil, php dev, 30000, AC1204, prasanth, php dev, 30000, AC1205, kranthi, admin, 20000, TP 导入关系表到HIVE bin/sqoop import --connect jdbc:mysql://hdp-node-01:3306/test --username root --password root --table emp --hive-import --m 1 导入到HDFS指定目录 在导入表数据到HDFS使用Sqoop导入工具,我们可以指定目标目录。 以下是指定目标目录选项的Sqoop导入命令的语法。 --target-dir <new or exist directory in HDFS> 下面的命令是用来导入emp_add表数据到’/queryresult’目录。 123456bin/sqoop import \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--target-dir /queryresult \--table emp --m 1 下面的命令是用来验证 /queryresult 目录中 emp_add表导入的数据形式。 $HADOOP_HOME/bin/hadoop fs -cat /queryresult/part-m-* 它会用逗号(,)分隔emp_add表的数据和字段。 123451201, 288A, vgiri, jublee1202, 108I, aoc, sec-bad1203, 144Z, pgutta, hyd1204, 78B, oldcity, sec-bad1205, 720C, hitech, sec-bad 导入表数据子集 我们可以导入表的使用Sqoop导入工具,”where”子句的一个子集。它执行在各自的数据库服务器相应的SQL查询,并将结果存储在HDFS的目标目录。 where子句的语法如下。 --where <condition> 下面的命令用来导入emp_add表数据的子集。子集查询检索员工ID和地址,居住城市为:Secunderabad 1234567bin/sqoop import \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--where "city ='sec-bad'" \--target-dir /wherequery \--table emp_add --m 1 按需导入 123456789bin/sqoop import \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--target-dir /wherequery2 \--query 'select id,name,deg from emp WHERE id>1207 and $CONDITIONS' \--split-by id \--fields-terminated-by '\t' \--m 1 下面的命令用来验证数据从emp_add表导入/wherequery目录 $HADOOP_HOME/bin/hadoop fs -cat /wherequery/part-m-* 它用逗号(,)分隔 emp_add表数据和字段。 1231202, 108I, aoc, sec-bad1204, 78B, oldcity, sec-bad1205, 720C, hitech, sec-bad 增量导入 增量导入是仅导入新添加的表中的行的技术。 它需要添加incremental, check-column, 和 ast-value选项来执行增量导入。 下面的语法用于Sqoop导入命令增量选项。 --incremental <mode> --check-column <column name> --last value <last check column value> 假设新添加的数据转换成emp表如下: 1206, satish p, grp des, 20000, GR 下面的命令用于在EMP表执行增量导入。 12345678bin/sqoop import \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--table emp --m 1 \--incremental append \--check-column id \--last-value 1208 以下命令用于从emp表导入HDFS emp/ 目录的数据验证。 $ $HADOOP_HOME/bin/hadoop fs -cat /user/hadoop/emp/part-m-* 它用逗号(,)分隔 emp_add表数据和字段。 1234561201, gopal, manager, 50000, TP1202, manisha, preader, 50000, TP1203, kalil, php dev, 30000, AC1204, prasanth, php dev, 30000, AC1205, kranthi, admin, 20000, TP1206, satish p, grp des, 20000, GR 下面的命令是从表emp 用来查看修改或新添加的行 $ $HADOOP_HOME/bin/hadoop fs -cat /emp/part-m-*1 这表示新添加的行用逗号(,)分隔emp表的字段。 1206, satish p, grp des, 20000, GR Sqoop的数据导出 将数据从HDFS导出到RDBMS数据库 导出前,目标表必须存在于目标数据库中。 默认操作是从将文件中的数据使用INSERT语句插入到表中 更新模式下,是生成UPDATE语句更新表数据 语法 以下是export命令语法。 $ sqoop export (generic-args) (export-args) 示例 数据是在HDFS 中“EMP/”目录的emp_data文件中。所述emp_data如下: 1234561201, gopal, manager, 50000, TP1202, manisha, preader, 50000, TP1203, kalil, php dev, 30000, AC1204, prasanth, php dev, 30000, AC1205, kranthi, admin, 20000, TP1206, satish p, grp des, 20000, GR 首先需要手动创建mysql中的目标表 12345678$ mysqlmysql> USE db;mysql> CREATE TABLE employee ( id INT NOT NULL PRIMARY KEY, name VARCHAR(20), deg VARCHAR(20), salary INT, dept VARCHAR(10)); 然后执行导出命令 123456bin/sqoop export \--connect jdbc:mysql://hdp-node-01:3306/test \--username root \--password root \--table employee \--export-dir /user/hadoop/emp/ 验证表mysql命令行。 mysql>select * from employee; 如果给定的数据存储成功,那么可以找到数据在如下的employee表。 12345678910+------+--------------+-------------+-------------------+--------+| Id | Name | Designation | Salary | Dept |+------+--------------+-------------+-------------------+--------+| 1201 | gopal | manager | 50000 | TP || 1202 | manisha | preader | 50000 | TP || 1203 | kalil | php dev | 30000 | AC | 1204 | prasanth | php dev | 30000 | AC || 1205 | kranthi | admin | 20000 | TP || 1206 | satish p | grp des | 20000 | GR |+------+--------------+-------------+-------------------+--------+ Sqoop的原理概述 Sqoop的原理其实就是将导入导出命令转化为mapreduce程序来执行,sqoop在接收到命令后,都要生成mapreduce程序 使用sqoop的代码生成工具可以方便查看到sqoop所生成的java代码,并可在此基础之上进行深入定制开发 代码定制 以下是Sqoop代码生成命令的语法: $ sqoop-codegen (generic-args) (codegen-args) $ sqoop-codegen (generic-args) (codegen-args) 示例:以USERDB数据库中的表emp来生成Java代码为例。 下面的命令用来生成导入 12345$ sqoop-codegen \--import--connect jdbc:mysql://localhost/userdb \--username root \ --table emp 如果命令成功执行,那么它就会产生如下的输出。 123456714/12/23 02:34:40 INFO sqoop.Sqoop: Running Sqoop version: 1.4.514/12/23 02:34:41 INFO tool.CodeGenTool: Beginning code generation……………….14/12/23 02:34:42 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /usr/local/hadoopNote: /tmp/sqoop-hadoop/compile/9a300a1f94899df4a9b10f9935ed9f91/emp.java uses or overrides a deprecated API.Note: Recompile with -Xlint:deprecation for details.14/12/23 02:34:47 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-hadoop/compile/9a300a1f94899df4a9b10f9935ed9f91/emp.jar 验证: 查看输出目录下的文件 12 如果想做深入定制导出,则可修改上述代码文件]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
<tag>Sqoop</tag>
</tags>
</entry>
<entry>
<title><![CDATA[工作流调度器azkaban]]></title>
<url>%2F2017%2F07%2F12%2F%E5%B7%A5%E4%BD%9C%E6%B5%81%E8%B0%83%E5%BA%A6%E5%99%A8azkaban%2F</url>
<content type="text"><![CDATA[概述为什么需要工作流调度系统 一个完整的数据分析系统通常都是由大量任务单元组成: shell脚本程序,java程序,mapreduce程序、hive脚本等 各任务单元之间存在时间先后及前后依赖关系 为了很好地组织起这样的复杂执行计划,需要一个工作流调度系统来调度执行; 例如,我们可能有这样一个需求,某个业务系统每天产生20G原始数据,我们每天都要对其进行处理,处理步骤如下所示: 通过Hadoop先将原始数据同步到HDFS上; 借助MapReduce计算框架对原始数据进行转换,生成的数据以分区表的形式存储到多张Hive表中; 需要对Hive中多个表的数据进行JOIN处理,得到一个明细数据Hive大表; 将明细数据进行复杂的统计分析,得到结果报表信息; 需要将统计分析得到的结果数据同步到业务系统中,供业务调用使用。 工作流调度实现方式 简单的任务调度:直接使用linux的crontab来定义; 复杂的任务调度:开发调度平台 或使用现成的开源调度系统,比如ooize、azkaban等 常见工作流调度系统 市面上目前有许多工作流调度器 在hadoop领域,常见的工作流调度器有Oozie, Azkaban,Cascading,Hamake等 各种调度工具特性对比 下面的表格对上述四种hadoop工作流调度器的关键特性进行了比较,尽管这些工作流调度器能够解决的需求场景基本一致,但在设计理念,目标用户,应用场景等方面还是存在显著的区别,在做技术选型的时候,可以提供参考 特性 Hamake Oozie Azkaban Cascading 工作流描述语言 XML XML (xPDL based) text file with key/value pairs Java API 依赖机制 data-driven explicit explicit explicit 是否要web容器 No Yes Yes No 进度跟踪 console/log messages web page web page Java API Hadoop job调度支持 no yes yes yes 运行模式 command line utility daemon daemon API Pig支持 yes yes yes yes 事件通知 no no no no 需要安装 no yes yes no 支持的hadoop版本 0.18+ 0.20+ currently unknown 0.18+ 重试支持 no workflownode evel yes yes 运行任意命令 yes yes yes yes Amazon EMR支持 yes no currently unknown yes Azkaban与Oozie对比 对市面上最流行的两种调度器,给出以下详细对比,以供技术选型参考。总体来说,ooize相比azkaban是一个重量级的任务调度系统,功能全面,但配置使用也更复杂。如果可以不在意某些功能的缺失,轻量级调度器azkaban是很不错的候选对象。 详情如下: 功能 两者均可以调度mapreduce,pig,java,脚本工作流任务 两者均可以定时执行工作流任务 工作流定义 Azkaban使用Properties文件定义工作流 Oozie使用XML文件定义工作流 工作流传参 Azkaban支持直接传参,例如${input} Oozie支持参数和EL表达式,例如${fs:dirSize(myInputDir)} 定时执行 Azkaban的定时执行任务是基于时间的 Oozie的定时执行任务基于时间和输入数据 资源管理 Azkaban有较严格的权限控制,如用户对工作流进行读/写/执行等操作 Oozie暂无严格的权限控制 工作流执行 Azkaban有两种运行模式,分别是solo server mode(executor server和web server部署在同一台节点)和multi server mode(executor server和web server可以部署在不同节点) Oozie作为工作流服务器运行,支持多用户和多工作流 工作流管理 Azkaban支持浏览器以及ajax方式操作工作流 Oozie支持命令行、HTTP REST、Java API、浏览器操作工作流 Azkaban介绍 Azkaban是由Linkedin开源的一个批量工作流任务调度器。用于在一个工作流内以一个特定的顺序运行一组工作和流程。Azkaban定义了一种KV文件格式来建立任务之间的依赖关系,并提供一个易于使用的web用户界面维护和跟踪你的工作流。 它有如下功能特点: Web用户界面 方便上传工作流 方便设置任务之间的关系 调度工作流 认证/授权(权限的工作) 能够杀死并重新启动工作流 模块化和可插拔的插件机制 项目工作区 工作流和任务的日志记录和审计 Azkaban安装部署准备工作 Azkaban Web服务器 azkaban-web-server-2.5.0.tar.gz Azkaban执行服务器 azkaban-executor-server-2.5.0.tar.gz MySQL 目前azkaban只支持 mysql,需安装mysql服务器,本文档中默认已安装好mysql服务器,并建立了 root用户,密码 root. 下载地址:http://azkaban.github.io/downloads.html 安装 将安装文件上传到集群,最好上传到安装 hive、sqoop的机器上,方便命令的执行 在当前用户目录下新建 azkabantools目录,用于存放源安装文件.新建azkaban目录,用于存放azkaban运行程序 azkaban web服务器安装 解压azkaban-web-server-2.5.0.tar.gz 命令: tar -zxvf azkaban-web-server-2.5.0.tar.gz 将解压后的azkaban-web-server-2.5.0 移动到 azkaban目录中,并重新命名 webserver 命令: mv azkaban-web-2.5.0/ azkaban/server azkaban 执行服器安装 解压azkaban-executor-server-2.5.0.tar.gz 命令:tar -zxvf azkaban-executor-server-2.5.0.tar.gz 将解压后的azkaban-executor-server-2.5.0 移动到 azkaban目录中,并重新命名 executor 命令:mv azkaban-executor-2.5.0/ azkaban/executor azkaban脚本导入 解压: azkaban-sql-script-2.5.0.tar.gz 命令:tar -zxvf azkaban-sql-script-2.5.0.tar.gz 将解压后的mysql 脚本,导入到mysql中: 进入mysql 1234mysql> create database azkaban;mysql> use azkaban;Database changedmysql> source /home/hadoop/azkaban-2.5.0/create-all-sql-2.5.0.sql; 创建SSL配置 参考地址: http://docs.codehaus.org/display/JETTY/How+to+configure+SSL 命令: keytool -keystore keystore -alias jetty -genkey -keyalg RSA 运行此命令后,会提示输入当前生成 keystor的密码及相应信息,输入的密码请劳记,信息如下: 输入keystore密码: 再次输入新密码: 您的名字与姓氏是什么? [Unknown]: 您的组织单位名称是什么? [Unknown]: 您的组织名称是什么? [Unknown]: 您所在的城市或区域名称是什么? [Unknown]: 您所在的州或省份名称是什么? [Unknown]: 该单位的两字母国家代码是什么 [Unknown]: CN CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CN 正确吗? [否]: y 输入\的主密码 (如果和 keystore 密码相同,按回车): 再次输入新密码: 完成上述工作后,将在当前目录生成 keystore 证书文件,将keystore 考贝到 azkaban web服务器根目录中. 如:cp keystore azkaban/server 配置文件 注:先配置好服务器节点上的时区 先生成时区配置文件Asia/Shanghai,用交互式命令 tzselect 即可 拷贝该时区文件,覆盖系统本地时区配置 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime azkaban web服务器配置 进入azkaban web服务器安装目录 conf目录 修改azkaban.properties文件 命令vi azkaban.properties 内容说明如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647#Azkaban Personalization Settingsazkaban.name=Test #服务器UI名称,用于服务器上方显示的名字azkaban.label=My Local Azkaban #描述azkaban.color=#FF3601 #UI颜色azkaban.default.servlet.path=/index #web.resource.dir=web/ #默认根web目录default.timezone.id=Asia/Shanghai #默认时区,已改为亚洲/上海 默认为美国 #Azkaban UserManager classuser.manager.class=azkaban.user.XmlUserManager #用户权限管理默认类user.manager.xml.file=conf/azkaban-users.xml #用户配置,具体配置参加下文 #Loader for projectsexecutor.global.properties=conf/global.properties # global配置文件所在位置azkaban.project.dir=projects # database.type=mysql #数据库类型mysql.port=3306 #端口号mysql.host=localhost #数据库连接IPmysql.database=azkaban #数据库实例名mysql.user=root #数据库用户名mysql.password=root #数据库密码mysql.numconnections=100 #最大连接数 # Velocity dev modevelocity.dev.mode=false# Jetty服务器属性.jetty.maxThreads=25 #最大线程数jetty.ssl.port=8443 #Jetty SSL端口jetty.port=8081 #Jetty端口jetty.keystore=keystore #SSL文件名jetty.password=123456 #SSL文件密码jetty.keypassword=123456 #Jetty主密码 与 keystore文件相同jetty.truststore=keystore #SSL文件名jetty.trustpassword=123456 #SSL文件密码 # 执行服务器属性executor.port=12321 #执行服务器端口# 邮件设置[email protected] #发送邮箱mail.host=smtp.163.com #发送邮箱smtp地址mail.user=xxxxxxxx #发送邮件时显示的名称mail.password=********** #邮箱密码[email protected] #任务失败时发送邮件的地址[email protected] #任务成功时发送邮件的地址lockdown.create.projects=false #cache.directory=cache #缓存目录 azkaban 执行服务器executor配置 进入执行服务器安装目录conf,修改azkaban.properties vi azkaban.properties 1234567891011121314151617181920212223#Azkabandefault.timezone.id=Asia/Shanghai #时区 # Azkaban JobTypes 插件配置azkaban.jobtype.plugin.dir=plugins/jobtypes #jobtype 插件所在位置 #Loader for projectsexecutor.global.properties=conf/global.propertiesazkaban.project.dir=projects #数据库设置database.type=mysql #数据库类型(目前只支持mysql)mysql.port=3306 #数据库端口号mysql.host=192.168.20.200 #数据库IP地址mysql.database=azkaban #数据库实例名mysql.user=root #数据库用户名mysql.password=root #数据库密码mysql.numconnections=100 #最大连接数 # 执行服务器配置executor.maxThreads=50 #最大线程数executor.port=12321 #端口号(如修改,请与web服务中一致)executor.flow.threads=30 用户配置 进入azkaban web服务器conf目录,修改azkaban-users.xml vi azkaban-users.xml 增加 管理员用户 1234567<azkaban-users> <user username="azkaban" password="azkaban" roles="admin" groups="azkaban" /> <user username="metrics" password="metrics" roles="metrics"/> <user username="admin" password="admin" roles="admin,metrics" /> <role name="admin" permissions="ADMIN" /> <role name="metrics" permissions="METRICS"/></azkaban-users> 启动web服务器 在azkaban web服务器目录下执行启动命令 bin/azkaban-web-start.sh 注:在web服务器根目录运行 或者启动到后台 nohup bin/azkaban-web-start.sh 1>/tmp/azstd.out 2>/tmp/azerr.out & 执行服务器 在执行服务器目录下执行启动命令 bin/azkaban-executor-start.sh 注:只能要执行服务器根目录运行 启动完成后,在浏览器(建议使用谷歌浏览器)中输入https://服务器IP地址:8443 ,即可访问azkaban服务了.在登录中输入刚才新的户用名及密码,点击 login. Azkaban实战 Azkaba内置的任务类型支持command、java Command类型单一job示例 创建job描述文件 vi command.job 123#command.jobtype=command command=echo 'hello' 将job资源文件打包成zip文件 zip command.job 通过azkaban的web管理平台创建project并上传job压缩包 首先创建project 上传zip包 启动执行该job Command类型多job工作流flow 创建有依赖关系的多个job描述 第一个job:foo.job 123#foo.jobtype=commandcommand=echo foo 第二个job:bar.job依赖foo.job 1234#bar.jobtype=commanddependencies=foocommand=echo bar 将所有job资源文件打到一个zip包中 在azkaban的web管理界面创建工程并上传zip包 启动工作流flow HDFS操作任务 创建job描述文件 123#fs.jobtype=commandcommand=/home/hadoop/apps/hadoop-2.6.1/bin/hadoop fs -mkdir /azaz 将job资源文件打包成zip文件 通过azkaban的web管理平台创建project并上传job压缩包 启动执行该job MAPREDUCE任务 Mr任务依然可以使用command的job类型来执行 创建job描述文件,及mr程序jar包(示例中直接使用hadoop自带的example jar) 123#mrwc.jobtype=commandcommand=/home/hadoop/apps/hadoop-2.6.1/bin/hadoop jar hadoop-mapreduce-examples-2.6.1.jar wordcount /wordcount/input /wordcount/azout 将所有job资源文件打到一个zip包中 在azkaban的web管理界面创建工程并上传zip包 启动job HIVE脚本任务 创建job描述文件和hive脚本 Hive脚本: test.sql 123456use default;drop table aztest;create table aztest(id int,name string) row format delimited fields terminated by ',';load data inpath '/aztest/hiveinput' into table aztest;create table azres as select * from aztest;insert overwrite directory '/aztest/hiveoutput' select count(1) from aztest; Job描述文件:hivef.job 123hivef.jobtype=commandcommand=/home/hadoop/apps/hive/bin/hive -f 'test.sql' 将所有job资源文件打到一个zip包中 在azkaban的web管理界面创建工程并上传zip包 启动job]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
<tag>Azkaban</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MAPREDUCE学习]]></title>
<url>%2F2017%2F07%2F02%2FMAPREDUCE%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[MAPREDUCE原理篇(1)为什么要MAPREDUCE 海量数据在单机上处理因为硬件资源限制,无法胜任 而一旦将单机版程序扩展到集群来分布式运行,将极大增加程序的复杂度和开发难度 引入mapreduce框架后,开发人员可以将绝大部分工作集中在业务逻辑的开发上,而将分布式计算中的复杂性交由框架来处理 设想一个海量数据场景下的wordcount需求: 单机版:内存受限,磁盘受限,运算能力受限 分布式: 文件分布式存储(HDFS) 运算逻辑需要至少分成2个阶段(一个阶段独立并发,一个阶段汇聚) 运算程序如何分发 程序如何分配运算任务(切片) 两阶段的程序如何启动?如何协调? 整个程序运行过程中的监控?容错?重试? 可见在程序由单机版扩成分布式时,会引入大量的复杂工作。为了提高开发效率,可以将分布式程序中的公共功能封装成框架,让开发人员可以将精力集中于业务逻辑。 而mapreduce就是这样一个分布式程序的通用框架,其应对以上问题的整体结构如下: MRAppMaster(mapreduce application master) MapTask ReduceTask MAPREDUCE框架结构及核心运行机制结构 一个完整的mapreduce程序在分布式运行时有三类实例进程: MRAppMaster:负责整个程序的过程调度及状态协调 mapTask:负责map阶段的整个数据处理流程 ReduceTask:负责reduce阶段的整个数据处理流程 MR程序运行流程 流程示意图 流程解析 1. 一个mr程序启动的时候,最先启动的是MRAppMaster,MRAppMaster启动后根据本次job的描述信息,计算出需要的maptask实例数量,然后向集群申请机器启动相应数量的maptask进程 2. maptask进程启动之后,根据给定的数据切片范围进行数据处理,主体流程为: 1. 利用客户指定的inputformat来获取RecordReader读取数据,形成输入KV对 2. 将输入KV对传递给客户定义的map()方法,做逻辑运算,并将map()方法输出的KV对收集到缓存 3. 将缓存中的KV对按照K分区排序后不断溢写到磁盘文件 3. MRAppMaster监控到所有maptask进程任务完成之后,会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区) 4. Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上获取到若干个maptask输出结果文件,并在本地进行重新归并排序,然后按照相同key的KV为一个组,调用客户定义的reduce()方法进行逻辑运算,并收集运算输出的结果KV,然后调用客户指定的outputformat将结果数据输出到外部存储 MapTask并行度决定机制 maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度 那么,mapTask并行实例是否越多越好呢?其并行度又是如何决定呢? mapTask并行度的决定机制 一个job的map阶段并行度由客户端在提交job时决定 而客户端对map阶段并行度的规划的基本逻辑为: 将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个split),然后每一个split分配一个mapTask并行实例处理 这段逻辑及形成的切片规划描述文件,由FileInputFormat实现类的getSplits()方法完成,其过程如下图: FileInputFormat切片机制 切片定义在InputFormat类中的getSplit()方法 FileInputFormat中默认的切片机制: 简单地按照文件的内容长度进行切片 切片大小,默认等于block大小 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片 比如待处理数据有两个文件: file1.txt 320M file2.txt 10M 经过FileInputFormat的切片机制运算后,形成的切片信息如下: file1.txt.split1– 0~128 file1.txt.split2– 128~256 file1.txt.split3– 256~320 file2.txt.split1– 0~10M FileInputFormat中切片的大小的参数配置 通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定 minsize:默认值:1 配置参数: mapreduce.input.fileinputformat.split.minsize maxsize:默认值:Long.MAXValue 配置参数:mapreduce.input.fileinputformat.split.maxsize blocksize 因此,默认情况下,切片大小=blocksize maxsize(切片最大值): 参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值 minsize (切片最小值): 参数调的比blockSize大,则可以让切片变得比blocksize还大 选择并发数的影响因素: 运算节点的硬件配置 运算任务的类型:CPU密集型还是IO密集型 运算任务的数 map并行度的经验之谈 如果硬件配置为2*12core + 64G,恰当的map并行度是大约每个节点20-100个map,最好每个map的执行时间至少一分钟。 如果job的每个map或者 reduce task的运行时间都只有30-40秒钟,那么就减少该job的map或者reduce数,每一个task(map|reduce)的setup和加入到调度器中进行调度,这个中间的过程可能都要花费几秒钟,所以如果每个task都非常快就跑完了,就会在task的开始和结束的时候浪费太多的时间。 配置task的JVM重用(JVM重用技术不是指同一Job的两个或两个以上的task可以同时运行于同一JVM上,而是排队按顺序执行。)可以改善该问题:(mapred.job.reuse.jvm.num.tasks,默认是1,表示一个JVM上最多可以顺序执行的task数目(属于同一个Job)是1。也就是说一个task启一个JVM) 如果input的文件非常的大,比如1TB,可以考虑将hdfs上的每个block size设大,比如设成256MB或者512MB ReduceTask并行度的决定 reducetask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数由切片数决定不同,Reducetask数量的决定是可以直接手动设置: 12//默认值是1,手动设置为4job.setNumReduceTasks(4); 如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜 注意: reducetask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全局汇总结果,就只能有1个reducetask 尽量不要运行太多的reduce task。对大多数job来说,最好rduce的个数最多和集群中的reduce持平,或者比集群的 reduce slots小。这个对于小集群而言,尤其重要。 MAPREDUCE程序初体验 Hadoop的发布包中内置了一个hadoop-mapreduce-example-2.4.1.jar,这个jar包中有各种MR示例程序,可以通过以下步骤运行: 启动hdfs,yarn 然后在集群中的任意一台服务器上启动执行程序(比如运行wordcount): hadoop jar /home/hadoop/apps/hadoop-2.9.0/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.9.0.jar wordcount /wordcount/in /wordcount/out hadoop jar /home/hadoop/apps/hadoop-2.6.4/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.4.jar wordcount :运行程序 /wordcount/data:需要统计的文件(HDFS的路径) /wordcount/out:统计后的结果输出文件(HDFS的路径) hadoop fs -ls /wordcount/out MAPREDUCE实践篇(1)MAPREDUCE 示例编写及编程规范编程规范 用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行mr程序的客户端) Mapper的输入数据是KV对的形式KV的类型可自定义 Mapper的输出数据是KV对的形式KV的类型可自定义 Mapper中的业务逻辑写在map()方法中 map()方法maptask进程对每一个\<K,V>调用一次 Reducer的输入数据类型对应Mapper的输出数据类型,也是KV Reducer的业务逻辑写在reduce()方法中 Reducetask进程对每一组相同k的\<k,v>组调用一次reduce()方法 用户自定义的Mapper和Reducer都要继承各自的父类 整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象 wordcount示例编写 需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数 定义一个mapper类 1234567891011121314151617181920212223242526272829303132333435363738394041import java.io.IOException;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Mapper;/** * KEYIN: 默认情况下,是mr框架所读到的一行文本的起始偏移量,Long, * 但是在hadoop中有自己的更精简的序列化接口,所以不直接用Long,而用LongWritable * * VALUEIN:默认情况下,是mr框架所读到的一行文本的内容,String,同上,用Text * * KEYOUT:是用户自定义逻辑处理完成之后输出数据中的key,在此处是单词,String,同上,用Text * VALUEOUT:是用户自定义逻辑处理完成之后输出数据中的value,在此处是单词次数,Integer,同上,用IntWritable * * @author * */public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{ /** * map阶段的业务逻辑就写在自定义的map()方法中 * maptask会对每一行输入数据调用一次我们自定义的map()方法 */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将maptask传给我们的文本内容先转换成String String line = value.toString(); //根据空格将这一行切分成单词 String[] words = line.split(" "); //将单词输出为<单词,1> for(String word:words){ //将单词作为key,将次数1作为value,以便于后续的数据分发,可以根据单词分发,以便于相同单词会到相同的reduce task context.write(new Text(word), new IntWritable(1)); } }} 定义一个reducer类 12345678910111213141516171819202122232425262728293031323334353637import java.io.IOException;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Reducer;/** * KEYIN, VALUEIN 对应 mapper输出的KEYOUT,VALUEOUT类型对应 * * KEYOUT, VALUEOUT 是自定义reduce逻辑处理结果的输出数据类型 * KEYOUT是单词 * VLAUEOUT是总次数 * @author * */public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{ /** * <angelababy,1><angelababy,1><angelababy,1><angelababy,1><angelababy,1> * <hello,1><hello,1><hello,1><hello,1><hello,1><hello,1> * <banana,1><banana,1><banana,1><banana,1><banana,1><banana,1> * 入参key,是一组相同单词kv对的key */ @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int count=0; /*Iterator<IntWritable> iterator = values.iterator(); while(iterator.hasNext()){ count += iterator.next().get(); }*/ for(IntWritable value:values){ count += value.get(); } context.write(key, new IntWritable(count)); }} 定义一个主类,用来描述job并提交job 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;/** * 相当于一个yarn集群的客户端 * 需要在此封装我们的mr程序的相关运行参数,指定jar包 * 最后提交给yarn * @author * */public class WordcountDriver { public static void main(String[] args) throws Exception { if (args == null || args.length == 0) { args = new String[2]; args[0] = "hdfs://master:9000/wordcount/input/wordcount.txt"; args[1] = "hdfs://master:9000/wordcount/output8"; } Configuration conf = new Configuration(); //设置的没有用! ??????// conf.set("HADOOP_USER_NAME", "hadoop");// conf.set("dfs.permissions.enabled", "false"); /*conf.set("mapreduce.framework.name", "yarn"); conf.set("yarn.resoucemanager.hostname", "mini1");*/ Job job = Job.getInstance(conf); /*job.setJar("/home/hadoop/wc.jar");*/ //指定本程序的jar包所在的本地路径 job.setJarByClass(WordcountDriver.class); //指定本业务job要使用的mapper/Reducer业务类 job.setMapperClass(WordcountMapper.class); job.setReducerClass(WordcountReducer.class); //指定mapper输出数据的kv类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //指定最终输出的数据的kv类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); //指定job的输入原始文件所在目录 FileInputFormat.setInputPaths(job, new Path(args[0])); //指定job的输出结果所在目录 FileOutputFormat.setOutputPath(job, new Path(args[1])); //将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行 /*job.submit();*/ boolean res = job.waitForCompletion(true); System.exit(res?0:1); }} MAPREDUCE程序运行模式本地运行模式 mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行 而处理的数据及输出结果可以在本地文件系统,也可以在hdfs上 怎样实现本地运行?写一个程序,不要带集群的配置文件本质是你的mr程序的conf中是否有mapreduce.framework.name=local以及yarn.resourcemanager.hostname参数. 本地模式非常便于进行业务逻辑的debug,只要在eclipse中打断点即可 如果在windows下想运行本地模式来测试程序逻辑,需要在windows中配置环境变量: %HADOOP_HOME% = d:/hadoop-2.6.1 %PATH% = %HADOOP_HOME%\bin 并且要将d:/hadoop-2.6.1的lib和bin目录替换成windows平台编译的版本 集群运行模式 将mapreduce程序提交给yarn集群resourcemanager,分发到很多的节点上并发执行 处理的数据和输出结果应该位于hdfs文件系统 提交集群的实现步骤: 将程序打成JAR包,然后在集群的任意一个节点上用hadoop命令启动 hadoop jar wordcount.jar cn.demo.bigdata.mrsimple.WordCountDriver inputpath outputpath 直接在linux的eclipse中运行main方法 项目中要带参数:mapreduce.framework.name=yarn以及yarn的两个基本配置. 如果要在windows的eclipse中提交job给集群,则要修改YarnRunner类 mapreduce程序在集群中运行时的大体流程 MAPREDUCE中的Combiner 因为combiner在mapreduce过程中可能调用也肯能不调用,可能调一次也可能调多次. 所以:combiner使用的原则是:有或没有都不能影响业务逻辑 combiner是MR程序中Mapper和Reducer之外的一种组件 combiner组件的父类就是Reducer combiner和reducer的区别在于运行的位置: Combiner是在每一个maptask所在的节点运行 Reducer是接收全局所有Mapper的输出结果; combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量具体实现步骤:1、自定义一个combiner继承Reducer,重写reduce方法2、在job中设置: job.setCombinerClassCustomCombiner.class) combiner能够应用的前提是不能影响最终的业务逻辑 而且,combiner的输出kv应该跟reducer的输入kv类型要对应起来 MAPREDUCE原理篇(2)mapreduce的shuffle机制概述: mapreduce中,map阶段处理的数据如何传递给reduce阶段,是mapreduce框架中最关键的一个流程,这个流程就叫shuffle; shuffle: 洗牌、发牌——(核心机制:数据分区,排序,缓存); 具体来说:就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序; 主要流程: Shuffle缓存流程: shuffle是MR处理流程中的一个过程,它的每一个处理步骤是分散在各个map task和reduce task节点上完成的,整体来看,分为3个操作: 分区partition Sort根据key排序 Combiner进行局部value的合并 详细流程 maptask收集我们的map()方法输出的kv对,放到内存缓冲区中 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件 多个溢出文件会被合并成大的溢出文件 在溢出过程中,及合并的过程中,都要调用partitioner进行分组和针对key进行排序 reducetask根据自己的分区号,去各个maptask机器上取相应的结果分区数据 reducetask会取到同一个分区的来自不同maptask的结果文件,reducetask会将这些文件再进行合并(归并排序) 合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法) Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快缓冲区的大小可以通过参数调整, 参数:io.sort.mb 默认100M 详细流程示意图 MAPREDUCE中的序列化概述 Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系。。。。),不便于在网络中高效传输; 所以,hadoop自己开发了一套序列化机制(Writable),精简,高效 Jdk序列化和MR序列化之间的比较简单代码验证两种序列化机制的差别: 12345678910111213141516171819202122232425262728293031323334353637383940414243public class TestSeri { public static void main(String[] args) throws Exception { //定义两个ByteArrayOutputStream,用来接收不同序列化机制的序列化结果 ByteArrayOutputStream ba = new ByteArrayOutputStream(); ByteArrayOutputStream ba2 = new ByteArrayOutputStream(); //定义两个DataOutputStream,用于将普通对象进行jdk标准序列化 DataOutputStream dout = new DataOutputStream(ba); DataOutputStream dout2 = new DataOutputStream(ba2); ObjectOutputStream obout = new ObjectOutputStream(dout2); //定义两个bean,作为序列化的源对象 ItemBeanSer itemBeanSer = new ItemBeanSer(1000L, 89.9f); ItemBean itemBean = new ItemBean(1000L, 89.9f); //用于比较String类型和Text类型的序列化差别 Text atext = new Text("a"); // atext.write(dout); itemBean.write(dout); byte[] byteArray = ba.toByteArray(); //比较序列化结果 System.out.println(byteArray.length); for (byte b : byteArray) { System.out.print(b); System.out.print(":"); } System.out.println("-----------------------"); String astr = "a"; // dout2.writeUTF(astr); obout.writeObject(itemBeanSer); byte[] byteArray2 = ba2.toByteArray(); System.out.println(byteArray2.length); for (byte b : byteArray2) { System.out.print(b); System.out.print(":"); } }} 自定义对象实现MR中的序列化接口如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序,此时,自定义的bean实现的接口应该是: public class FlowBean implements WritableComparable<FlowBean> 需要自己实现的方法: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051import java.io.DataInput;import java.io.DataOutput;import java.io.IOException;import org.apache.hadoop.io.Writable;public class FlowBean implements WritableComparable<FlowBean>{ private long upFlow; private long dFlow; private long sumFlow; //反序列化时,需要反射调用空参构造函数,所以要显示定义一个 public FlowBean(){} public FlowBean(long upFlow, long dFlow) { this.upFlow = upFlow; this.dFlow = dFlow; this.sumFlow = upFlow + dFlow; } get/set..... /** * 反序列化的方法,反序列化时,从流中读取到的各个字段的顺序应该与序列化时写出去的顺序保持一致 */ @Override public void readFields(DataInput in) throws IOException { upflow = in.readLong(); dflow = in.readLong(); sumflow = in.readLong(); } /** * 序列化的方法 */ @Override public void write(DataOutput out) throws IOException { out.writeLong(upflow); out.writeLong(dflow); //可以考虑不序列化总流量,因为总流量是可以通过上行流量和下行流量计算出来的 out.writeLong(sumflow); } @Override public int compareTo(FlowBean o) { //实现按照sumflow的大小倒序排序 return sumflow>o.getSumflow()?-1:1; } MapReduce与YARNYARN概述 Yarn是一个资源调度平台,负责为运算程序提供服务器运算资源,相当于一个分布式的操作系统平台,而mapreduce等运算程序则相当于运行于操作系统之上的应用程序 YARN的重要概念 yarn并不清楚用户提交的程序的运行机制 yarn只提供运算资源的调度(用户程序向yarn申请资源,yarn就负责分配资源) yarn中的主管角色叫ResourceManager yarn中具体提供运算资源的角色叫NodeManager 这样一来,yarn其实就与运行的用户程序完全解耦,就意味着yarn上可以运行各种类型的分布式运算程序(mapreduce只是其中的一种),比如mapreduce. storm程序,spark程序,tez …… 所以,spark. storm等运算框架都可以整合在yarn上运行,只要他们各自的框架中有符合yarn规范的资源请求机制即可 Yarn就成为一个通用的资源调度平台,从此,企业中以前存在的各种运算集群都可以整合在一个物理集群上,提高资源利用率,方便数据共享 Yarn中运行运算程序的示例mapreduce程序的调度过程,如下图: MAPREDUCE实践篇(2)Mapreduce中的排序初步需求对日志数据中的上下行流量信息汇总,并输出按照总流量倒序排序的结果数据如下: 1363157985066 13726230503(手机号) 00-FD-07-A4-72-B8:CMCC 120.196.100.82 24 27 2481(上行流量) 24681(下行流量) 200 1363157995052 13826544101 5C-0E-8B-C7-F1-E0:CMCC 120.197.40.4 4 0 264 0 200 1363157991076 13926435656 20-10-7A-28-CC-0A:CMCC 120.196.100.99 2 4 132 1512 200 1363154400022 13926251106 5C-0E-8B-8B-B1-50:CMCC 120.197.40.4 4 0 240 0 200 分析 基本思路:实现自定义的bean来封装流量信息,并将bean作为map输出的key来传输 MR程序在处理数据的过程中会对数据排序(map输出的kv对传输到reduce之前,会排序),排序的依据是map输出的key 所以,我们如果要实现自己需要的排序规则,则可以考虑将排序因素放到key中,让key实现接口:WritableComparable 然后重写key的compareTo方法 实现1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283import java.io.IOException;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;public class FlowCount { static class FlowCountMapper extends Mapper<LongWritable, Text, Text, FlowBean>{ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将一行内容转成string String line = value.toString(); //切分字段 String[] fields = line.split("\t"); //取出手机号 String phoneNbr = fields[1]; //取出上行流量下行流量 long upFlow = Long.parseLong(fields[fields.length-3]); long dFlow = Long.parseLong(fields[fields.length-2]); context.write(new Text(phoneNbr), new FlowBean(upFlow, dFlow)); } } static class FlowCountReducer extends Reducer<Text, FlowBean, Text, FlowBean>{ //<183323,bean1><183323,bean2><183323,bean3><183323,bean4>....... @Override protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException { long sum_upFlow = 0; long sum_dFlow = 0; //遍历所有bean,将其中的上行流量,下行流量分别累加 for(FlowBean bean: values){ sum_upFlow += bean.getUpFlow(); sum_dFlow += bean.getdFlow(); } FlowBean resultBean = new FlowBean(sum_upFlow, sum_dFlow); context.write(key, resultBean); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); //指定本程序的jar包所在的本地路径 job.setJarByClass(FlowCount.class); //指定本业务job要使用的mapper/Reducer业务类 job.setMapperClass(FlowCountMapper.class); job.setReducerClass(FlowCountReducer.class); //指定mapper输出数据的kv类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(FlowBean.class); //指定最终输出的数据的kv类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowBean.class); //指定job的输入原始文件所在目录 FileInputFormat.setInputPaths(job, new Path(args[0])); //指定job的输出结果所在目录 FileOutputFormat.setOutputPath(job, new Path(args[1])); //将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行 /*job.submit();*/ boolean res = job.waitForCompletion(true); System.exit(res?0:1); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869package cn.peanut.mr.flowsum;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;public class FlowSumSortDriver { static class FlowSumMapper extends Mapper<LongWritable, Text, FlowBean, Text> { private Text keyText = new Text(); private FlowBean flowBean = new FlowBean(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] split = value.toString().split("\t"); String phone = split[0]; String upFlow = split[split.length - 3]; String dFlow = split[split.length - 2]; flowBean.set(Long.parseLong(upFlow), Long.parseLong(dFlow)); keyText.set(phone); context.write(flowBean, keyText); } } static class FlowSumReducer extends Reducer<FlowBean, Text, Text, FlowBean> { @Override protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException { context.write(values.iterator().next(), key); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); //指定本程序的jar包所在的本地路径 job.setJarByClass(FlowSumDriver.class); //指定本业务job要使用的mapper/Reducer业务类 job.setMapperClass(FlowSumSortDriver.FlowSumMapper.class); job.setReducerClass(FlowSumSortDriver.FlowSumReducer.class); //指定mapper输出数据的kv类型 job.setMapOutputKeyClass(FlowBean.class); job.setMapOutputValueClass(Text.class); //指定最终输出的数据的kv类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowBean.class); //指定job的输入原始文件所在目录 FileInputFormat.setInputPaths(job, new Path("output/flowoutput")); //指定job的输出结果所在目录 FileOutputFormat.setOutputPath(job, new Path("output/flowsortoutput")); boolean res = job.waitForCompletion(true); System.exit(res ? 0 : 1); }} Mapreduce中的分区Partitioner需求 根据归属地输出流量统计数据结果到不同文件,以便于在查询统计结果时可以定位到省级范围进行 分析 Mapreduce中会将map输出的kv对,按照相同key分组,然后分发给不同的reducetask 默认的分发规则为:根据key的hashcode%reducetask数来分发 所以:如果要按照我们自己的需求进行分组,则需要改写数据分发(分组)组件Partitioner 自定义一个CustomPartitioner继承抽象类:Partitioner 然后在job对象中,设置自定义partitioner: job.setPartitionerClass(CustomPartitioner.class) 实现12345678910111213141516171819202122232425262728import java.util.HashMap;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Partitioner;/** * K2 V2 对应的是map输出kv的类型 * @author * */public class ProvincePartitioner extends Partitioner<Text, FlowBean>{ public static HashMap<String, Integer> proviceDict = new HashMap<String, Integer>(); static{ proviceDict.put("136", 0); proviceDict.put("137", 1); proviceDict.put("138", 2); proviceDict.put("139", 3); } @Override public int getPartition(Text key, FlowBean value, int numPartitions) { String prefix = key.toString().substring(0, 3); Integer provinceId = proviceDict.get(prefix); return provinceId==null?4:provinceId; }} mapreduce数据压缩概述 这是mapreduce的一种优化策略:通过压缩编码对mapper或者reducer的输出进行压缩,以减少磁盘IO,提高MR程序运行速度(但相应增加了cpu运算负担) Mapreduce支持将map输出的结果或者reduce输出的结果进行压缩,以减少网络IO或最终输出数据的体积 压缩特性运用得当能提高性能,但运用不当也可能降低性能 基本原则: 运算密集型的job,少用压缩 IO密集型的job,多用压缩 MR支持的压缩编码 Reducer输出压缩 在配置参数或在代码中都可以设置reduce的输出压缩 在配置参数中设置 mapreduce.output.fileoutputformat.compress=false mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.DefaultCodec mapreduce.output.fileoutputformat.compress.type=RECORD 在代码中设置 123Job job = Job.getInstance(conf);FileOutputFormat.setCompressOutput(job, true);FileOutputFormat.setOutputCompressorClass(job, (Class<? extends CompressionCodec>) Class.forName("")); Mapper输出压缩 在配置参数或在代码中都可以设置reduce的输出压缩 在配置参数中设置 mapreduce.map.output.compress=false mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.DefaultCodec 在代码中设置: 12conf.setBoolean(Job.MAP_OUTPUT_COMPRESS, true);conf.setClass(Job.MAP_OUTPUT_COMPRESS_CODEC, GzipCodec.class, CompressionCodec.class); 压缩文件的读取 Hadoop自带的InputFormat类内置支持压缩文件的读取,比如TextInputformat类,在其initialize方法中: 1234567891011121314151617181920212223242526272829303132333435363738394041public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException { FileSplit split = (FileSplit) genericSplit; Configuration job = context.getConfiguration(); this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE); start = split.getStart(); end = start + split.getLength(); final Path file = split.getPath(); // open the file and seek to the start of the split final FileSystem fs = file.getFileSystem(job); fileIn = fs.open(file); //根据文件后缀名创建相应压缩编码的codec CompressionCodec codec = new CompressionCodecFactory(job).getCodec(file); if (null!=codec) { isCompressedInput = true; decompressor = CodecPool.getDecompressor(codec); //判断是否属于可切片压缩编码类型 if (codec instanceof SplittableCompressionCodec) { final SplitCompressionInputStream cIn = ((SplittableCompressionCodec)codec).createInputStream( fileIn, decompressor, start, end, SplittableCompressionCodec.READ_MODE.BYBLOCK); //如果是可切片压缩编码,则创建一个CompressedSplitLineReader读取压缩数据 in = new CompressedSplitLineReader(cIn, job, this.recordDelimiterBytes); start = cIn.getAdjustedStart(); end = cIn.getAdjustedEnd(); filePosition = cIn; } else { //如果是不可切片压缩编码,则创建一个SplitLineReader读取压缩数据,并将文件输入流转换成解压数据流传递给普通SplitLineReader读取 in = new SplitLineReader(codec.createInputStream(fileIn, decompressor), job, this.recordDelimiterBytes); filePosition = fileIn; } } else { fileIn.seek(start); //如果不是压缩文件,则创建普通SplitLineReader读取数据 in = new SplitLineReader(fileIn, job, this.recordDelimiterBytes); filePosition = fileIn; } 更多MapReduce编程案例reduce端join算法实现1、需求: 订单数据表t_order: id date pid amount 1001 20150710 P0001 2 1002 20150710 P0001 3 1002 20150710 P0002 3 商品信息表t_product id name category_id price P0001 小米5 C01 2 P0002 锤子T1 C01 3 假如数据量巨大,两表的数据是以文件的形式存储在HDFS中,需要用mapreduce程序来实现一下SQL查询运算: select a.id,a.date,b.name,b.category_id,b.price from t_order a join t_product b on a.pid = b.id 2、实现机制: 通过将关联的条件作为map输出的key,将两表满足join条件的数据并携带数据所来源的文件信息,发往同一个reduce task,在reduce中进行数据的串联 1234567891011121314151617181920212223242526272829303132333435363738394041public class OrderJoin { static class OrderJoinMapper extends Mapper<LongWritable, Text, Text, OrderJoinBean> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 拿到一行数据,并且要分辨出这行数据所属的文件 String line = value.toString(); String[] fields = line.split("\t"); // 拿到itemid String itemid = fields[0]; // 获取到这一行所在的文件名(通过inpusplit) String name = "你拿到的文件名"; // 根据文件名,切分出各字段(如果是a,切分出两个字段,如果是b,切分出3个字段) OrderJoinBean bean = new OrderJoinBean(); bean.set(null, null, null, null, null); context.write(new Text(itemid), bean); } } static class OrderJoinReducer extends Reducer<Text, OrderJoinBean, OrderJoinBean, NullWritable> { @Override protected void reduce(Text key, Iterable<OrderJoinBean> beans, Context context) throws IOException, InterruptedException { //拿到的key是某一个itemid,比如1000 //拿到的beans是来自于两类文件的bean // {1000,amount} {1000,amount} {1000,amount} --- {1000,price,name} //将来自于b文件的bean里面的字段,跟来自于a的所有bean进行字段拼接并输出 } }} 缺点:这种方式中,join的操作是在reduce阶段完成,reduce端的处理压力太大,map节点的运算负载则很低,资源利用率不高,且在reduce阶段极易产生数据倾斜 解决方案: map端join实现方式 map端join算法实现 原理阐述 适用于关联表中有小表的情形; 可以将小表分发到所有的map节点,这样,map节点就可以在本地对自己所读到的大表数据进行join并输出最终结果,可以大大提高join操作的并发度,加快处理速度 实现示例 先在mapper类中预先定义好小表,进行join 引入实际场景中的解决方案:一次加载数据库或者用distributedcache 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778public class TestDistributedCache { static class TestDistributedCacheMapper extends Mapper<LongWritable, Text, Text, Text>{ FileReader in = null; BufferedReader reader = null; HashMap<String,String> b_tab = new HashMap<String, String>(); String localpath =null; String uirpath = null; //是在map任务初始化的时候调用一次 @Override protected void setup(Context context) throws IOException, InterruptedException { //通过这几句代码可以获取到cache file的本地绝对路径,测试验证用 Path[] files = context.getLocalCacheFiles(); localpath = files[0].toString(); URI[] cacheFiles = context.getCacheFiles(); //缓存文件的用法——直接用本地IO来读取 //这里读的数据是map task所在机器本地工作目录中的一个小文件 in = new FileReader("b.txt"); reader =new BufferedReader(in); String line =null; while(null!=(line=reader.readLine())){ String[] fields = line.split(","); b_tab.put(fields[0],fields[1]); } IOUtils.closeStream(reader); IOUtils.closeStream(in); } @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //这里读的是这个map task所负责的那一个切片数据(在hdfs上) String[] fields = value.toString().split("\t"); String a_itemid = fields[0]; String a_amount = fields[1]; String b_name = b_tab.get(a_itemid); // 输出结果 1001 98.9 banan context.write(new Text(a_itemid), new Text(a_amount + "\t" + ":" + localpath + "\t" +b_name )); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(TestDistributedCache.class); job.setMapperClass(TestDistributedCacheMapper.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(LongWritable.class); //这里是我们正常的需要处理的数据所在路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //不需要reducer job.setNumReduceTasks(0); //分发一个文件到task进程的工作目录 job.addCacheFile(new URI("hdfs://hadoop-server01:9000/cachefile/b.txt")); //分发一个归档文件到task进程的工作目录// job.addArchiveToClassPath(archive); //分发jar包到task节点的classpath下// job.addFileToClassPath(jarfile); job.waitForCompletion(true); }} web日志预处理 需求: 对web访问日志中的各字段识别切分 去除日志中不合法的记录 根据KPI统计需求,生成各类访问请求过滤数据 实现代码: 定义一个bean,用来记录日志数据中的各数据字段 1234567891011121314151617181920212223242526272829public class WebLogBean { private String remote_addr;// 记录客户端的ip地址 private String remote_user;// 记录客户端用户名称,忽略属性"-" private String time_local;// 记录访问时间与时区 private String request;// 记录请求的url与http协议 private String status;// 记录请求状态;成功是200 private String body_bytes_sent;// 记录发送给客户端文件主体内容大小 private String http_referer;// 用来记录从那个页面链接访问过来的 private String http_user_agent;// 记录客户浏览器的相关信息 private boolean valid = true;// 判断数据是否合法 get/set..... @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.valid); sb.append("\001").append(this.remote_addr); sb.append("\001").append(this.remote_user); sb.append("\001").append(this.time_local); sb.append("\001").append(this.request); sb.append("\001").append(this.status); sb.append("\001").append(this.body_bytes_sent); sb.append("\001").append(this.http_referer); sb.append("\001").append(this.http_user_agent); return sb.toString(); }} 定义一个parser用来解析过滤web访问日志原始记录 1234567891011121314151617181920212223242526272829303132public class WebLogParser { public static WebLogBean parser(String line) { WebLogBean webLogBean = new WebLogBean(); String[] arr = line.split(" "); if (arr.length > 11) { webLogBean.setRemote_addr(arr[0]); webLogBean.setRemote_user(arr[1]); webLogBean.setTime_local(arr[3].substring(1)); webLogBean.setRequest(arr[6]); webLogBean.setStatus(arr[8]); webLogBean.setBody_bytes_sent(arr[9]); webLogBean.setHttp_referer(arr[10]); if (arr.length > 12) { webLogBean.setHttp_user_agent(arr[11] + " " + arr[12]); } else { webLogBean.setHttp_user_agent(arr[11]); } if (Integer.parseInt(webLogBean.getStatus()) >= 400) {// 大于400,HTTP错误 webLogBean.setValid(false); } } else { webLogBean.setValid(false); } return webLogBean; } public static String parserTime(String time) { time.replace("/", "-"); return time; }} mapreduce程序 12345678910111213141516171819202122232425262728293031323334353637public class WeblogPreProcess { static class WeblogPreProcessMapper extends Mapper<LongWritable, Text, Text, NullWritable> { Text k = new Text(); NullWritable v = NullWritable.get(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); WebLogBean webLogBean = WebLogParser.parser(line); if (!webLogBean.isValid()) return; k.set(webLogBean.toString()); context.write(k, v); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(WeblogPreProcess.class); job.setMapperClass(WeblogPreProcessMapper.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); job.waitForCompletion(true); }}]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MapReduce遇到问题]]></title>
<url>%2F2017%2F07%2F02%2FMapReduce%E9%81%87%E5%88%B0%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[连接reduceManager错误错误信息 12345678910111213[root@fk01 mapreduce]# hadoop jar hadoop-mapreduce-examples-2.2.0.jar wordcount /input /ouput314/08/21 10:41:18 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable14/08/21 10:41:18 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:803214/08/21 10:41:19 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 0 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:20 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 1 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:21 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 2 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:22 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 3 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:23 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 4 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:24 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 5 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:25 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 6 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:26 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 7 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:27 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 8 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS)14/08/21 10:41:28 INFO ipc.Client: Retrying connect to server: 0.0.0.0/0.0.0.0:8032. Already tried 9 time(s); retry policy is RetryUpToMaximumCountWithFixedSleep(maxRetries=10, sleepTime=1 SECONDS) 解决办法:在yare-site.xml里添加如下信息之后问题得到解决 123456789101112<property> <name>yarn.resourcemanager.address</name> <value>master:8032</value> </property> <property> <name>yarn.resourcemanager.scheduler.address</name> <value>master:8030</value> </property> <property> <name>yarn.resourcemanager.resource-tracker.address</name> <value>master:8031</value> </property>]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MAPREDUCE进阶学习]]></title>
<url>%2F2017%2F07%2F02%2Fmapreduce%E8%BF%9B%E9%98%B6%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[自定义inputFormat需求无论hdfs还是mapreduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案 分析小文件的优化无非以下几种方式: 在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS 在业务处理之前,在HDFS上使用mapreduce程序对小文件进行合并 在mapreduce处理时,可采用combineInputFormat提高效率 实现 程序的核心机制: 自定义一个InputFormat 改写RecordReader,实现一次读取一个完整文件封装为KV 在输出时使用SequenceFileOutPutFormat输出合并文件 代码如下: 自定义InputFromat 1234567891011121314151617public class WholeFileInputFormat extends FileInputFormat<NullWritable, BytesWritable> { //设置每个小文件不可分片,保证一个小文件生成一个key-value键值对 @Override protected boolean isSplitable(JobContext context, Path file) { return false; } @Override public RecordReader<NullWritable, BytesWritable> createRecordReader( InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException { WholeFileRecordReader reader = new WholeFileRecordReader(); reader.initialize(split, context); return reader; }} 自定义RecordReader 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455class WholeFileRecordReader extends RecordReader<NullWritable, BytesWritable> { private FileSplit fileSplit; private Configuration conf; private BytesWritable value = new BytesWritable(); private boolean processed = false; @Override public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException { this.fileSplit = (FileSplit) split; this.conf = context.getConfiguration(); } @Override public boolean nextKeyValue() throws IOException, InterruptedException { if (!processed) { byte[] contents = new byte[(int) fileSplit.getLength()]; Path file = fileSplit.getPath(); FileSystem fs = file.getFileSystem(conf); FSDataInputStream in = null; try { in = fs.open(file); IOUtils.readFully(in, contents, 0, contents.length); value.set(contents, 0, contents.length); } finally { IOUtils.closeStream(in); } processed = true; return true; } return false; } @Override public NullWritable getCurrentKey() throws IOException, InterruptedException { return NullWritable.get(); } @Override public BytesWritable getCurrentValue() throws IOException, InterruptedException { return value; } @Override public float getProgress() throws IOException { return processed ? 1.0f : 0.0f; } @Override public void close() throws IOException { // do nothing }} 定义mapreduce处理流程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748public class SmallFilesToSequenceFileConverter extends Configured implements Tool { static class SequenceFileMapper extends Mapper<NullWritable, BytesWritable, Text, BytesWritable> { private Text filenameKey; @Override protected void setup(Context context) throws IOException, InterruptedException { InputSplit split = context.getInputSplit(); Path path = ((FileSplit) split).getPath(); filenameKey = new Text(path.toString()); } @Override protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException { context.write(filenameKey, value); } } @Override public int run(String[] args) throws Exception { Configuration conf = new Configuration(); System.setProperty("HADOOP_USER_NAME", "hdfs"); String[] otherArgs = new GenericOptionsParser(conf, args) .getRemainingArgs(); if (otherArgs.length != 2) { System.err.println("Usage: combinefiles <in> <out>"); System.exit(2); } Job job = Job.getInstance(conf,"combine small files to sequencefile");// job.setInputFormatClass(WholeFileInputFormat.class); job.setOutputFormatClass(SequenceFileOutputFormat.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(BytesWritable.class); job.setMapperClass(SequenceFileMapper.class); return job.waitForCompletion(true) ? 0 : 1; } public static void main(String[] args) throws Exception { int exitCode = ToolRunner.run(new SmallFilesToSequenceFileConverter(), args); System.exit(exitCode); }} 自定义outputFormat需求现有一些原始日志需要做增强解析处理,流程: 从原始日志文件中读取数据 根据日志中的一个URL字段到外部知识库中获取信息增强到原始日志 如果成功增强,则输出到增强结果目录;如果增强失败,则抽取原始数据中URL字段输出到待爬清单目录 分析程序的关键点是要在一个mapreduce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义outputformat来实现 实现实现要点: 在mapreduce中访问外部资源 自定义outputformat,改写其中的recordwriter,改写具体输出数据的方法write() 代码实现如下: 数据库获取数据的工具 12345678910111213141516171819202122232425262728293031323334353637383940414243public class DBLoader { public static void dbLoader(HashMap<String, String> ruleMap) { Connection conn = null; Statement st = null; ResultSet res = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://hdp-node01:3306/urlknowledge", "root", "root"); st = conn.createStatement(); res = st.executeQuery("select url,content from urlcontent"); while (res.next()) { ruleMap.put(res.getString(1), res.getString(2)); } } catch (Exception e) { e.printStackTrace(); } finally { try{ if(res!=null){ res.close(); } if(st!=null){ st.close(); } if(conn!=null){ conn.close(); } }catch(Exception e){ e.printStackTrace(); } } } public static void main(String[] args) { DBLoader db = new DBLoader(); HashMap<String, String> map = new HashMap<String,String>(); db.dbLoader(map); System.out.println(map.size()); }} 自定义一个outputformat 123456789101112131415161718192021222324252627282930313233343536373839404142434445public class LogEnhancerOutputFormat extends FileOutputFormat<Text, NullWritable>{ @Override public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext context) throws IOException, InterruptedException { FileSystem fs = FileSystem.get(context.getConfiguration()); Path enhancePath = new Path("hdfs://hdp-node01:9000/flow/enhancelog/enhanced.log"); Path toCrawlPath = new Path("hdfs://hdp-node01:9000/flow/tocrawl/tocrawl.log"); FSDataOutputStream enhanceOut = fs.create(enhancePath); FSDataOutputStream toCrawlOut = fs.create(toCrawlPath); return new MyRecordWriter(enhanceOut,toCrawlOut); } static class MyRecordWriter extends RecordWriter<Text, NullWritable>{ FSDataOutputStream enhanceOut = null; FSDataOutputStream toCrawlOut = null; public MyRecordWriter(FSDataOutputStream enhanceOut, FSDataOutputStream toCrawlOut) { this.enhanceOut = enhanceOut; this.toCrawlOut = toCrawlOut; } @Override public void write(Text key, NullWritable value) throws IOException, InterruptedException { //有了数据,你来负责写到目的地 —— hdfs //判断,进来内容如果是带tocrawl的,就往待爬清单输出流中写 toCrawlOut if(key.toString().contains("tocrawl")){ toCrawlOut.write(key.toString().getBytes()); }else{ enhanceOut.write(key.toString().getBytes()); } } @Override public void close(TaskAttemptContext context) throws IOException, InterruptedException { if(toCrawlOut!=null){ toCrawlOut.close(); } if(enhanceOut!=null){ enhanceOut.close(); } } }} 自定义GroupingComparator需求有如下订单数据 订单id 商品id 成交金额 Order_0000001 Pdt_01 222.8 Order_0000001 Pdt_05 25.8 Order_0000002 Pdt_03 522.8 Order_0000002 Pdt_04 122.4 Order_0000003 Pdt_01 222.8 现在需要求出每一个订单中成交金额最大的一笔交易 分析 利用“订单id和成交金额”作为key,可以将map阶段读取到的所有订单数据按照id分区,按照金额排序,发送到reduce 在reduce端利用groupingcomparator将订单id相同的kv聚合成组,然后取第一个即是最大值 实现 自定义groupingcomparator 123456789101112131415161718192021/** * 用于控制shuffle过程中reduce端对kv对的聚合逻辑 * @author [email protected] * */public class ItemidGroupingComparator extends WritableComparator { protected ItemidGroupingComparator() { super(OrderBean.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { OrderBean abean = (OrderBean) a; OrderBean bbean = (OrderBean) b; //将item_id相同的bean都视为相同,从而聚合为一组 return abean.getItemid().compareTo(bbean.getItemid()); }} 定义订单信息bean 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960/** * 订单信息bean,实现hadoop的序列化机制 * @author [email protected] * */public class OrderBean implements WritableComparable<OrderBean>{ private Text itemid; private DoubleWritable amount; public OrderBean() { } public OrderBean(Text itemid, DoubleWritable amount) { set(itemid, amount); } public void set(Text itemid, DoubleWritable amount) { this.itemid = itemid; this.amount = amount; } public Text getItemid() { return itemid; } public DoubleWritable getAmount() { return amount; } @Override public int compareTo(OrderBean o) { int cmp = this.itemid.compareTo(o.getItemid()); if (cmp == 0) { cmp = -this.amount.compareTo(o.getAmount()); } return cmp; } @Override public void write(DataOutput out) throws IOException { out.writeUTF(itemid.toString()); out.writeDouble(amount.get()); } @Override public void readFields(DataInput in) throws IOException { String readUTF = in.readUTF(); double readDouble = in.readDouble(); this.itemid = new Text(readUTF); this.amount= new DoubleWritable(readDouble); } @Override public String toString() { return itemid.toString() + "\t" + amount.get(); }} 编写mapreduce处理流程 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364/** * 利用secondarysort机制输出每种item订单金额最大的记录 * @author [email protected] * */public class SecondarySort { static class SecondarySortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable>{ OrderBean bean = new OrderBean(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line = value.toString(); String[] fields = StringUtils.split(line, "\t"); bean.set(new Text(fields[0]), new DoubleWritable(Double.parseDouble(fields[1]))); context.write(bean, NullWritable.get()); } } static class SecondarySortReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable>{ //在设置了groupingcomparator以后,这里收到的kv数据 就是: <1001 87.6>,null <1001 76.5>,null .... //此时,reduce方法中的参数key就是上述kv组中的第一个kv的key:<1001 87.6> //要输出同一个item的所有订单中最大金额的那一个,就只要输出这个key @Override protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException { context.write(key, NullWritable.get()); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(SecondarySort.class); job.setMapperClass(SecondarySortMapper.class); job.setReducerClass(SecondarySortReducer.class); job.setOutputKeyClass(OrderBean.class); job.setOutputValueClass(NullWritable.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //指定shuffle所使用的GroupingComparator类 job.setGroupingComparatorClass(ItemidGroupingComparator.class); //指定shuffle所使用的partitioner类 job.setPartitionerClass(ItemIdPartitioner.class); job.setNumReduceTasks(3); job.waitForCompletion(true); }} Mapreduce中的DistributedCache应用Map端join案例需求实现两个“表”的join操作,其中一个表数据量小,一个表很大,这种场景在实际中非常常见,比如“订单日志” join “产品信息” 分析 原理阐述 适用于关联表中有小表的情形; 可以将小表分发到所有的map节点,这样,map节点就可以在本地对自己所读到的大表数据进行join并输出最终结果 可以大大提高join操作的并发度,加快处理速度 示例:先在mapper类中预先定义好小表,进行join 并用distributedcache机制将小表的数据分发到每一个maptask执行节点,从而每一个maptask节点可以从本地加载到小表的数据,进而在本地即可实现join 实现12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879public class TestDistributedCache { static class TestDistributedCacheMapper extends Mapper<LongWritable, Text, Text, Text>{ FileReader in = null; BufferedReader reader = null; HashMap<String,String> b_tab = new HashMap<String, String>(); String localpath =null; String uirpath = null; //是在map任务初始化的时候调用一次 @Override protected void setup(Context context) throws IOException, InterruptedException { //通过这几句代码可以获取到cache file的本地绝对路径,测试验证用 Path[] files = context.getLocalCacheFiles(); localpath = files[0].toString(); URI[] cacheFiles = context.getCacheFiles(); //缓存文件的用法——直接用本地IO来读取 //这里读的数据是map task所在机器本地工作目录中的一个小文件 in = new FileReader("b.txt"); reader =new BufferedReader(in); String line =null; while(null!=(line=reader.readLine())){ String[] fields = line.split(","); b_tab.put(fields[0],fields[1]); } IOUtils.closeStream(reader); IOUtils.closeStream(in); } @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //这里读的是这个map task所负责的那一个切片数据(在hdfs上) String[] fields = value.toString().split("\t"); String a_itemid = fields[0]; String a_amount = fields[1]; String b_name = b_tab.get(a_itemid); // 输出结果 1001 98.9 banan context.write(new Text(a_itemid), new Text(a_amount + "\t" + ":" + localpath + "\t" +b_name )); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(TestDistributedCache.class); job.setMapperClass(TestDistributedCacheMapper.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(LongWritable.class); //这里是我们正常的需要处理的数据所在路径 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //不需要reducer job.setNumReduceTasks(0); //分发一个文件到task进程的工作目录 job.addCacheFile(new URI("hdfs://hadoop-server01:9000/cachefile/b.txt")); //分发一个归档文件到task进程的工作目录// job.addArchiveToClassPath(archive); //分发jar包到task节点的classpath下// job.addFileToClassPath(jarfile); job.waitForCompletion(true); }} Mapreduce的其他补充计数器应用在实际生产代码中,常常需要将数据处理过程中遇到的不合规数据行进行全局计数,类似这种需求可以借助mapreduce框架中提供的全局计数器来实现 示例代码如下: 12345678910111213141516171819public class MultiOutputs { //通过枚举形式定义自定义计数器 enum MyCounter{MALFORORMED,NORMAL} static class CommaMapper extends Mapper<LongWritable, Text, Text, LongWritable> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] words = value.toString().split(","); for (String word : words) { context.write(new Text(word), new LongWritable(1)); } //对枚举定义的自定义计数器加1 context.getCounter(MyCounter.MALFORORMED).increment(1); //通过动态设置自定义计数器加1 context.getCounter("counterGroupa", "countera").increment(1); } } 多job串联一个稍复杂点的处理逻辑往往需要多个mapreduce程序串联处理,多job的串联可以借助mapreduce框架的JobControl实现 1234567891011121314151617181920212223242526ControlledJob cJob1 = new ControlledJob(job1.getConfiguration()); ControlledJob cJob2 = new ControlledJob(job2.getConfiguration()); ControlledJob cJob3 = new ControlledJob(job3.getConfiguration()); // 设置作业依赖关系 cJob2.addDependingJob(cJob1); cJob3.addDependingJob(cJob2); JobControl jobControl = new JobControl("RecommendationJob"); jobControl.addJob(cJob1); jobControl.addJob(cJob2); jobControl.addJob(cJob3); cJob1.setJob(job1); cJob2.setJob(job2); cJob3.setJob(job3); // 新建一个线程来运行已加入JobControl中的作业,开始进程并等待结束 Thread jobControlThread = new Thread(jobControl); jobControlThread.start(); while (!jobControl.allFinished()) { Thread.sleep(500); } jobControl.stop(); return 0; mapreduce参数优化资源相关参数 mapreduce.map.memory.mb: 一个Map Task可使用的资源上限(单位:MB),默认为1024。如果Map Task实际使用的资源量超过该值,则会被强制杀死。 mapreduce.reduce.memory.mb: 一个Reduce Task可使用的资源上限(单位:MB),默认为1024。如果Reduce Task实际使用的资源量超过该值,则会被强制杀死。 mapreduce.map.java.opts: Map Task的JVM参数,你可以在此配置默认的java heap size等参数, e.g.“-Xmx1024m -verbose:gc -Xloggc:/tmp/@[email protected]” (@taskid@会被Hadoop框架自动换为相应的taskid), 默认值: “” mapreduce.reduce.java.opts: Reduce Task的JVM参数,你可以在此配置默认的java heap size等参数, e.g.“-Xmx1024m -verbose:gc -Xloggc:/tmp/@[email protected]”, 默认值: “” mapreduce.map.cpu.vcores: 每个Map task可使用的最多cpu core数目, 默认值: 1 mapreduce.map.cpu.vcores: 每个Reduce task可使用的最多cpu core数目, 默认值: 1 yarn.scheduler.minimum-allocation-mb 1024 yarn.scheduler.maximum-allocation-mb 8192 yarn.scheduler.minimum-allocation-vcores1 yarn.scheduler.maximum-allocation-vcores32 容错相关参数 mapreduce.map.maxattempts: 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 mapreduce.reduce.maxattempts: 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 mapreduce.map.failures.maxpercent: 当失败的Map Task失败比例超过该值为,整个作业则失败,默认值为0. 如果你的应用程序允许丢弃部分输入数据,则该该值设为一个大于0的值,比如5,表示如果有低于5%的Map Task失败(如果一个Map Task重试次数超过mapreduce.map.maxattempts,则认为这个Map Task失败,其对应的输入数据将不会产生任何结果),整个作业扔认为成功。 mapreduce.reduce.failures.maxpercent: 当失败的Reduce Task失败比例超过该值为,整个作业则失败,默认值为0. mapreduce.task.timeout: Task超时时间,经常需要设置的一个参数,该参数表达的意思为:如果一个task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该task处于block状态,可能是卡住了,也许永远会卡主,为了防止因为用户程序永远block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是300000。如果你的程序对每条输入数据的处理时间过长(比如会访问数据库,通过网络拉取数据等),建议将该参数调大,该参数过小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。 本地运行mapreduce 作业设置以下几个参数: mapreduce.framework.name=local mapreduce.jobtracker.address=local fs.defaultFS=local 效率和稳定性相关参数 mapreduce.map.speculative: 是否为Map Task打开推测执行机制,默认为false mapreduce.reduce.speculative: 是否为Reduce Task打开推测执行机制,默认为false mapreduce.job.user.classpath.first & mapreduce.task.classpath.user.precedence:当同一个class同时出现在用户jar包和hadoop jar中时,优先使用哪个jar包中的class,默认为false,表示优先使用hadoop jar中的class。 mapreduce.input.fileinputformat.split.minsize: 每个Map Task处理的数据量(仅针对基于文件的Inputformat有效,比如TextInputFormat,SequenceFileInputFormat),默认为一个block大小,即 134217728。]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hive问题集]]></title>
<url>%2F2017%2F06%2F26%2Fhive%E9%97%AE%E9%A2%98%E9%9B%86%2F</url>
<content type="text"><![CDATA[FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org问题: 起因是我重装了mysql数据库。 安装之后 把访问权限都配置好 : 1234GRANT ALL PRIVILEGES ON*.* TO 'hive'@'%' Identified by 'hive'; GRANT ALL PRIVILEGES ON*.* TO 'hive'@'localhost' Identified by 'hive'; GRANT ALL PRIVILEGES ON*.* TO 'hive'@'127.0.0.1' Identified by 'hive'; 本机地址: 192.168.103.43 机器名字:192-168-103-43 flush privileges;启动hive 发生下面的错误: 12hive> show tables;FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClient FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTaskFAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClientFAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask 123cd ${HIVE_HOME}/bin ./hive -hiveconf hive.root.logger=DEBUG,consolehive> show tables; 得到如下的错误信息(当然 不同的问题所产生的日志是不同的): 1234567891011121314151617181920212223242526272829303132333435Caused by: javax.jdo.JDOFatalDataStoreException: Access denied for user 'hive'@'192-168-103-43' (using password: YES) NestedThrowables: java.sql.SQLException: Access denied for user 'hive'@'192-168-103-43' (using password: YES) at org.datanucleus.jdo.NucleusJDOHelper.getJDOExceptionForNucleusException(NucleusJDOHelper.java:298) at org.datanucleus.jdo.JDOPersistenceManagerFactory.freezeConfiguration(JDOPersistenceManagerFactory.java:601) at org.datanucleus.jdo.JDOPersistenceManagerFactory.createPersistenceManagerFactory(JDOPersistenceManagerFactory.java:286) at org.datanucleus.jdo.JDOPersistenceManagerFactory.getPersistenceManagerFactory(JDOPersistenceManagerFactory.java:182) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at javax.jdo.JDOHelper$16.run(JDOHelper.java:1958) at java.security.AccessController.doPrivileged(Native Method) at javax.jdo.JDOHelper.invoke(JDOHelper.java:1953) at javax.jdo.JDOHelper.invokeGetPersistenceManagerFactoryOnImplementation(JDOHelper.java:1159) at javax.jdo.JDOHelper.getPersistenceManagerFactory(JDOHelper.java:803) at javax.jdo.JDOHelper.getPersistenceManagerFactory(JDOHelper.java:698) at org.apache.hadoop.hive.metastore.ObjectStore.getPMF(ObjectStore.java:262) at org.apache.hadoop.hive.metastore.ObjectStore.getPersistenceManager(ObjectStore.java:291) at org.apache.hadoop.hive.metastore.ObjectStore.initialize(ObjectStore.java:224) at org.apache.hadoop.hive.metastore.ObjectStore.setConf(ObjectStore.java:199) at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:62) at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:117) at org.apache.hadoop.hive.metastore.RetryingRawStore.<init>(RetryingRawStore.java:62) at org.apache.hadoop.hive.metastore.RetryingRawStore.getProxy(RetryingRawStore.java:71) at org.apache.hadoop.hive.metastore.HiveMetaStore$HMSHandler.newRawStore(HiveMetaStore.java:413) at org.apache.hadoop.hive.metastore.HiveMetaStore$HMSHandler.getMS(HiveMetaStore.java:401) at org.apache.hadoop.hive.metastore.HiveMetaStore$HMSHandler.createDefaultDB(HiveMetaStore.java:439) at org.apache.hadoop.hive.metastore.HiveMetaStore$HMSHandler.init(HiveMetaStore.java:325) at org.apache.hadoop.hive.metastore.HiveMetaStore$HMSHandler.<init>(HiveMetaStore.java:285) at org.apache.hadoop.hive.metastore.RetryingHMSHandler.<init>(RetryingHMSHandler.java:53) at org.apache.hadoop.hive.metastore.RetryingHMSHandler.getProxy(RetryingHMSHandler.java:58) at org.apache.hadoop.hive.metastore.HiveMetaStore.newHMSHandler(HiveMetaStore.java:4102) at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.<init>(HiveMetaStoreClient.java:121) ... 28 more 原因: 发现数据库的权限 HIVE需要的是 ‘hive‘@’192-168-103-43’ 这个IP地址 解决: 然后试着在mysql中加上权限: 12GRANT ALL PRIVILEGES ON*.* TO 'hive'@'192-168-103-43' Identified by 'hive'; flush privileges; 再次登录hive hive> show tables;OK Hive出现异常 FAILED: Error In Metadata: Java.Lang.RuntimeException: Unable To Instan问题: 在公司的虚拟机上运行hive计算,因为要计算的数据量较大,频繁,导致了服务器负载过高,mysql也出现无法连接的问题,最后虚拟机出现The remote system refused the connection.重启虚拟机后,进入hive。 hive> show tables;出现了下面的问题: 12FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClient FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTaskFAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClientFAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask 原因: 用下面的命令,重新启动hive 12./hive -hiveconf hive.root.logger=DEBUG,consolehive> show tables; 能够看到更深层次的原因的是: 12345678910Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.apache.hadoop.hive.metastore.MetaStoreUtils.newInstance(MetaStoreUtils.java:1076) … 23 moreCaused by: javax.jdo.JDODataStoreException: Exception thrown obtaining schema column information from datastore NestedThrowables: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘hive.DELETEME1370713761025′ doesn’t exist 根据提示的信息,登陆mysql或者mysql客户端查看hive的数据库的表信息 12345678910111213141516171819202122232425262728293031323334353637383940414243mysql> use hive; mysql> show tables; +—————————+| Tables_in_hive |+—————————+| BUCKETING_COLS || CDS || COLUMNS_V2 || DATABASE_PARAMS || DBS || DELETEME1370677637267 || DELETEME1370712928271 || DELETEME1370713342355 || DELETEME1370713589772 || DELETEME1370713761025 || DELETEME1370713792915 || IDXS || INDEX_PARAMS || PARTITIONS || PARTITION_KEYS || PARTITION_KEY_VALS || PARTITION_PARAMS || PART_COL_PRIVS || PART_COL_STATS || PART_PRIVS || SDS || SD_PARAMS || SEQUENCE_TABLE || SERDES || SERDE_PARAMS || SKEWED_COL_NAMES || SKEWED_COL_VALUE_LOC_MAP || SKEWED_STRING_LIST || SKEWED_STRING_LIST_VALUES || SKEWED_VALUES || SORT_COLS || TABLE_PARAMS || TAB_COL_STATS || TBLS || TBL_COL_PRIVS || TBL_PRIVS |+—————————+36 rows in set (0.00 sec) 能够看到“DELETEME1370713792915”这个表,问题明确了,由于计算的压力过大,服务器停止响应,mysql也停止了响应,mysql进程被异常终止,在运行中的mysql表数据异常,hive的元数据表异常。 解决问题的办法有两个: 1.直接在mysql中drop 异常提示中的table;mysql>drop table DELETEME1370713761025;2.保守的做法,根据DELETEME*表的结构,创建不存在的表CREATE TABLE DELETEME1370713792915 ( UNUSED int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1;通过实践,第一个方法就能够解决问题,如果不行可以尝试第二个方法。 hive错误show tables无法使用 : Unable to instantiate rg.apache.hadoop.hive.metastore.问题: hive异常show tables无法使用:Unable to instantiate rg.apache.hadoop.hive.metastore.HiveMetaStoreClient异常: 123hive> show tables; FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate rg.apache.hadoop.hive.metastore.HiveMetaStoreClient FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask 原因: 在其他shell 开了hive 没有关闭 解决: 使用 ps -ef | grep hive kill -9杀死进程 FAILED: Error in metadata: java.lang.RuntimeException: Unable to in(2)(08-52-23)问题:安装配置Hive时报错: 12FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.metastore.HiveMetaStoreClient FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask 用调试模式报错如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899[root@hadoop1 bin]# hive -hiveconf hive.root.logger=DEBUG,console13/10/09 16:16:27 DEBUG common.LogUtils: Using hive-site.xml found on CLASSPATH at /opt/hive-0.11.0/conf/hive-site.xml 13/10/09 16:16:27 DEBUG conf.Configuration: java.io.IOException: config() at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:227) at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:214) at org.apache.hadoop.hive.conf.HiveConf.<init>(HiveConf.java:1039) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:636) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.hadoop.util.RunJar.main(RunJar.java:156)13/10/09 16:16:27 DEBUG conf.Configuration: java.io.IOException: config() at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:227) at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:214) at org.apache.hadoop.mapred.JobConf.<init>(JobConf.java:330) at org.apache.hadoop.hive.conf.HiveConf.initialize(HiveConf.java:1073) at org.apache.hadoop.hive.conf.HiveConf.<init>(HiveConf.java:1040) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:636) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.hadoop.util.RunJar.main(RunJar.java:156)Logging initialized using configuration in file:/opt/hive-0.11.0/conf/hive-log4j.properties 13/10/09 16:16:27 INFO SessionState: Logging initialized using configuration in file:/opt/hive-0.11.0/conf/hive-log4j.properties 13/10/09 16:16:27 DEBUG parse.VariableSubstitution: Substitution is on: hive Hive history file=/tmp/root/hive_job_log_root_4666@hadoop1_201310091616_1069706211.txt 13/10/09 16:16:27 INFO exec.HiveHistory: Hive history file=/tmp/root/hive_job_log_root_4666@hadoop1_201310091616_1069706211.txt 13/10/09 16:16:27 DEBUG conf.Configuration: java.io.IOException: config() at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:227) at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:214) at org.apache.hadoop.security.UserGroupInformation.ensureInitialized(UserGroupInformation.java:187) at org.apache.hadoop.security.UserGroupInformation.isSecurityEnabled(UserGroupInformation.java:239) at org.apache.hadoop.security.UserGroupInformation.getLoginUser(UserGroupInformation.java:438) at org.apache.hadoop.security.UserGroupInformation.getCurrentUser(UserGroupInformation.java:424) at org.apache.hadoop.hive.shims.HadoopShimsSecure.getUGIForConf(HadoopShimsSecure.java:491) at org.apache.hadoop.hive.ql.security.HadoopDefaultAuthenticator.setConf(HadoopDefaultAuthenticator.java:51) at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:62) at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:117) at org.apache.hadoop.hive.ql.metadata.HiveUtils.getAuthenticator(HiveUtils.java:365) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:270) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:670) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.hadoop.util.RunJar.main(RunJar.java:156)13/10/09 16:16:27 DEBUG security.Groups: Creating new Groups object 13/10/09 16:16:27 DEBUG security.Groups: Group mapping impl=org.apache.hadoop.security.ShellBasedUnixGroupsMapping; cacheTimeout=300000 13/10/09 16:16:27 DEBUG security.UserGroupInformation: hadoop login 13/10/09 16:16:27 DEBUG security.UserGroupInformation: hadoop login commit 13/10/09 16:16:27 DEBUG security.UserGroupInformation: using local user:UnixPrincipal锛?root 13/10/09 16:16:27 DEBUG security.UserGroupInformation: UGI loginUser:root 13/10/09 16:16:27 DEBUG security.Groups: Returning fetched groups for 'root' 13/10/09 16:16:27 DEBUG security.Groups: Returning cached groups for 'root' 13/10/09 16:16:27 DEBUG conf.Configuration: java.io.IOException: config(config) at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:260) at org.apache.hadoop.hive.conf.HiveConf.<init>(HiveConf.java:1044) at org.apache.hadoop.hive.ql.security.authorization.DefaultHiveAuthorizationProvider.init(DefaultHiveAuthorizationProvider.java:30) at org.apache.hadoop.hive.ql.security.authorization.HiveAuthorizationProviderBase.setConf(HiveAuthorizationProviderBase.java:108) at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:62) at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:117) at org.apache.hadoop.hive.ql.metadata.HiveUtils.getAuthorizeProviderManager(HiveUtils.java:339) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:272) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:670) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.hadoop.util.RunJar.main(RunJar.java:156)13/10/09 16:16:27 DEBUG conf.Configuration: java.io.IOException: config() at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:227) at org.apache.hadoop.conf.Configuration.<init>(Configuration.java:214) at org.apache.hadoop.mapred.JobConf.<init>(JobConf.java:330) at org.apache.hadoop.hive.conf.HiveConf.initialize(HiveConf.java:1073) at org.apache.hadoop.hive.conf.HiveConf.<init>(HiveConf.java:1045) at org.apache.hadoop.hive.ql.security.authorization.DefaultHiveAuthorizationProvider.init(DefaultHiveAuthorizationProvider.java:30) at org.apache.hadoop.hive.ql.security.authorization.HiveAuthorizationProviderBase.setConf(HiveAuthorizationProviderBase.java:108) at org.apache.hadoop.util.ReflectionUtils.setConf(ReflectionUtils.java:62) at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:117) at org.apache.hadoop.hive.ql.metadata.HiveUtils.getAuthorizeProviderManager(HiveUtils.java:339) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:272) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:670) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:614) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.hadoop.util.RunJar.main(RunJar.java:156) 原因: 这个错误应该是你集成了mysql,从而报错 解决: 修改hive-site.xml,参照: 12345<property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://192.168.1.101:3306/hive?createDatabaseIfNotExist=true</value> <description>JDBC connect string for a JDBC metastore</description> </property> Hive的–auxpath使用相对路径遇到的一个奇怪的异常 问题: 在使用Hive的–auxpath过程中,如果我使用的是相对路径(例如,–auxpath=abc.jar),会产生下面的一个异常:1234567java.lang.IllegalArgumentException: Can not create a Path from an empty string at org.apache.hadoop.fs.Path.checkPathArg(Path.java:91) at org.apache.hadoop.fs.Path.<init>(Path.java:99) at org.apache.hadoop.fs.Path.<init>(Path.java:58) at org.apache.hadoop.mapred.JobClient.copyRemoteFiles(JobClient.java:619) at org.apache.hadoop.mapred.JobClient.copyAndConfigureFiles(JobClient.java:724) at org.apache.hadoop.mapred.JobClient.copyAndConfigureFiles(JobClient.java:648) 原因: 从异常的内容来看,是由于使用了一个空字符串来创建一个Path对象。 经过分析发现,使用”–auxpath=abc.jar”来启动Hive时,Hive会自动在abc.jar前面补上”file://“。也就是说Hive最后使用的路径是”file://abc.jar”。 当我们使用”file://abc.jar”来生成一个Path时,调用这个Path的getName将会返回””(空字符串)。而Hive在提交MapReduce的Job时,会使用getName来获取文件名,并创建一个新的Path对象。下面的示例代码演示了一下这个过程,会抛出上文提到的异常。 1234Path path = new Path("file://abc.jar"); System.out.println("path name:" + path.getName()); System.out.println("authority:" + path.toUri().getAuthority()); Path newPath = new Path(path.getName()); 上文的代码输出 path name:authority:abc.jar并抛出了异常”Can not create a Path from an empty string” 那么为什么”file://abc.jar”生成的Path的getName返回的是””而不是”abc.jar”呢,而且”abc.jar”却成了authority?在Path中的处理代码如下: 12345678if (pathString.startsWith("//", start) && (pathString.length()-start > 2)) { // has authority int nextSlash = pathString.indexOf('/', start+2); int authEnd = nextSlash > 0 ? nextSlash : pathString.length(); authority = pathString.substring(start+2, authEnd); start = authEnd;}// uri path is the rest of the string -- query & fragment not supportedString path = pathString.substring(start, pathString.length()); pathString就是传进去的”file://abc.jar”,由于我们只有两个”/“因此,从第二个”/“到结尾的字符串(”abc.jar”)都被当成了authority,path(内部的成员)则设置成了””而getName返回的就是path,因此也就为””了。 解决: 如果使用Hive的–auxpath来设置jar,必须使用绝对路径,或者使用”file:///.abc.jar”这样的表示法。这个才是Hadoop的Path支持的方式。事实上,hadoop许多相关的Path的设置,都存在这个问题,所以在无法确定的情况下,就不要使用相对路径了。 启动hive hwi服务时出现 HWI WAR file not found错误问题: 1234hive --service hwi [niy@niy-computer /]$ $HIVE_HOME/bin/hive --service hwi13/04/26 00:21:17 INFO hwi.HWIServer: HWI is starting up 13/04/26 00:21:18 FATAL hwi.HWIServer: HWI WAR file not found at /usr/local/hive/usr/local/hive/lib/hive-hwi-0.12.0-SNAPSHOT.war 原因: 可以看出/usr/local/hive/usr/local/hive/lib/hive-hwi-0.12.0-SNAPSHOT.war肯定不是正确路径,真正路径是/usr/local/hive/lib/hive-hwi-0.12.0-SNAPSHOT.war,断定是配置的问题 解决:将hive-default.xml中关于 hwi的设置拷贝到hive-site.xml中即可 123456789101112131415<property> <name>hive.hwi.war.file</name> <value>lib/hive-hwi-0.12.0-SNAPSHOT.war</value> <description>This sets the path to the HWI war file, relative to ${HIVE_HOME}. </description> </property> <property> <name>hive.hwi.listen.host</name> <value>0.0.0.0</value> <description>This is the host address the Hive Web Interface will listen on</description> </property> <property> <name>hive.hwi.listen.port</name> <value>9999</value> <description>This is the port the Hive Web Interface will listen on</description> </property> 问题:当启动Hive的时候报错:1234Caused by: javax.jdo.JDOException: Couldnt obtain a new sequence (unique id) : Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENTandat least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED. NestedThrowables: java.sql.SQLException: Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT andat least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED. 原因: 这个问题是由于hive的元数据存储MySQL配置不当引起的 解决办法: mysql> setglobal binlog_format='MIXED';8、问题: 当在Hive中创建表的时候报错: 12create table years (year string, eventstring) row format delimited fields terminated by'\t'; FAILED: Execution Error, return code 1from org.apache.hadoop.hive.ql.exec.DDLTask. MetaException(message:For direct MetaStore DB connections, we don't support retries at the client level.) 原因: 这是由于字符集的问题,需要配置MySQL的字符集: 解决办法: mysql> alter database hive character set latin1;9、问题: 当执行Hive客户端时候出现如下错误: WARN conf.HiveConf: HiveConf of name hive.metastore.localdoesnot exist原因:这是由于在0.10 0.11或者之后的HIVE版本 hive.metastore.local 属性不再使用。 解决办法: 将该参数从hive-site.xml删除即可。 问题:在启动Hive报如下错误: (Permission denied: user=anonymous, access=EXECUTE, inode=”/tmp”:hadoop:supergroup:drwx——原因:这是由于Hive没有hdfs:/tmp目录的权限, 解决办法: 赋权限即可:hadoop dfs -chmod -R 777 /tmp Hive查询数据库时,出现null。无数据显示。问题如下: Hive问题集 解决办法: LOAD DATA LOCAL INPATH '/tmp/sanple.txt' overwrite into table animal FIELDS TERMINATED BY '\t';解释: 数据分隔符的问题,定义表的时候需要定义数据分隔符, FIELDS TERMINATED BY '\t'这个字段就说明了数据分隔符是tab。 具体分割符请以自己的文化中具体的情况来定。 ERROR beeline.ClassNameCompleter: Fail to parse the class name from the Jar file due to在使用beeline链接Hive服务的时候,报了下面的这个错误: 1234567beeline> !connect jdbc:hive2//h2slave1:10000 scan complete in 1ms 16/07/27 11:40:54 [main]: ERROR beeline.ClassNameCompleter: Fail to parse the class name from the Jar file due to the exception:java.io.FileNotFoundException: minlog-1.2.jar (没有那个文件或目录) 16/07/27 11:40:54 [main]: ERROR beeline.ClassNameCompleter: Fail to parse the class name from the Jar file due to the exception:java.io.FileNotFoundException: objenesis-1.2.jar (没有那个文件或目录) 16/07/27 11:40:54 [main]: ERROR beeline.ClassNameCompleter: Fail to parse the class name from the Jar file due to the exception:java.io.FileNotFoundException: reflectasm-1.07-shaded.jar (没有那个文件或目录) scan complete in 596ms No known driver to handle "jdbc:hive2//h2slave1:10000" 解决: 其实这个问题是由于jdbc协议地址写错造成的,在hive2之后少了个“:” 改成以下这个形式即可: beeline> !connect jdbc:hive2://h2slave1:1000013、Missing Hive Execution Jar: /…/hive-exec-.jar 运行hive时显示Missing Hive Execution Jar: /usr/hive/hive-0.11.0/bin/lib/hive-exec-.jar 运行hive时显示Missing Hive Execution Jar: /usr/hive/hive-0.11.0/bin/lib/hive-exec-*.jar 细细分析这个目录/bin/lib,在hive安装文件夹中这两个目录是并列的,而系统能够找到这样的链接,说明hive在centos系统配置文件中的路径有误,打开 /etc/profile会发现hive的配置路径为 export PATH=$JAVA_HOME/bin:$PATH:/usr/hive/hive-0.11.0/bin明显可以看出是路径配置的问题,这样的配置系统会在hive安装文件夹中的bin目录下寻找它所需要的jar包,而bin和lib文件夹是并列的,所以我们需要在centos系统配置文件中将hive路径配置为文件夹安装路径,即 export PATH=$JAVA_HOME/bin:$PATH:/usr/hive/hive-0.11.0注意:这种问题一般都是出在环境变量上面的配置。请认真检查etc/profile跟你hive的安装路径。 hive启动报错: Found class jline.Terminal, but interface was expected错误如下: 12345678910111213141516[ERROR] Terminal initialization failed; falling back to unsupportedjava.lang.IncompatibleClassChangeError: Found class jline.Terminal, but interface was expected at jline.TerminalFactory.create(TerminalFactory.java:101) at jline.TerminalFactory.get(TerminalFactory.java:158) at jline.console.ConsoleReader.<init>(ConsoleReader.java:229) at jline.console.ConsoleReader.<init>(ConsoleReader.java:221) at jline.console.ConsoleReader.<init>(ConsoleReader.java:209) at org.apache.hadoop.hive.cli.CliDriver.getConsoleReader(CliDriver.java:773) at org.apache.hadoop.hive.cli.CliDriver.executeDriver(CliDriver.java:715) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:675) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:615) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.apache.hadoop.util.RunJar.main(RunJar.java:212) 原因: jline版本冲突不一致导致的,hadoop目录下存在老版本jline:/hadoop-2.6.0/share/hadoop/yarn/lib: jline-0.9.94.jar解决: 把hive中的新版本jline拷贝一份到hadoop的share/hadoop/yarn/lib即可 同时要把那个老版本的给删除 cp /hive/apache-hive-1.1.0-bin/lib/jline-2.12.jar /hadoop-2.5.2/share/hadoop/yarn/lib15、MySQLSyntaxErrorException: Specified key was too long; max key length is 767 bytes 在使用hive时,使用mysql存储元数据的时候,遇到下面的错误: 123456com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Specified key was too long; max key length is 767 bytes at sun.reflect.GeneratedConstructorAccessor31.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at com.mysql.jdbc.Util.handleNewInstance(Util.java:377) at com.mysql.jdbc.Util.getInstance(Util.java:360) 解决办法: 用mysql做元数据,要修改数据字符集 alter database hive character set latin1]]></content>
<categories>
<category>Hive</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Hadoop</tag>
<tag>Hive</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hive学习]]></title>
<url>%2F2017%2F06%2F22%2FHive%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[Hive基本概念Hive简介什么是Hive Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能。 为什么使用Hive 直接使用hadoop所面临的问题 人员学习成本太高 项目周期要求太短 MapReduce实现复杂查询逻辑开发难度太大 为什么要使用Hive 操作接口采用类SQL语法,提供快速开发的能力。 避免了去写MapReduce,减少开发人员的学习成本。 扩展功能很方便。 Hive的特点 可扩展 Hive可以自由的扩展集群的规模,一般情况下不需要重启服务。 延展性 Hive支持用户自定义函数,用户可以根据自己的需求来实现自己的函数。 容错 良好的容错性,节点出现问题SQL仍可完成执行。 Hive架构架构图 Jobtracker是hadoop1.x中的组件,它的功能相当于: Resourcemanager+AppMaster TaskTracker 相当于: Nodemanager + yarnchild 基本组成 用户接口:包括 CLI、JDBC/ODBC、WebGUI。 元数据存储:通常是存储在关系数据库如 mysql , derby中。 解释器、编译器、优化器、执行器。 各组件的基本功能 用户接口主要由三个:CLI、JDBC/ODBC和WebGUI。其中,CLI为shell命令行;JDBC/ODBC是Hive的JAVA实现,与传统数据库JDBC类似;WebGUI是通过浏览器访问Hive。 元数据存储:Hive 将元数据存储在数据库中。Hive 中的元数据包括表的名字,表的列和分区及其属性,表的属性(是否为外部表等),表的数据所在目录等。 解释器、编译器、优化器完成 HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在 HDFS 中,并在随后有 MapReduce 调用执行。 Hive与Hadoop的关系 Hive利用HDFS存储数据,利用MapReduce查询数据 Hive与传统数据库对比 总结:hive具有sql数据库的外表,但应用场景完全不同,hive只适合用来做批量数据统计分析 Hive的数据存储 Hive中所有的数据都存储在 HDFS 中,没有专门的数据存储格式(可支持Text,SequenceFile,ParquetFile,RCFILE等) 只需要在创建表的时候告诉 Hive 数据中的列分隔符和行分隔符,Hive 就可以解析数据。 Hive 中包含以下数据模型:DB、Table,External Table,Partition,Bucket。 db:在hdfs中表现为${hive.metastore.warehouse.dir}目录下一个文件夹 table:在hdfs中表现所属db目录下一个文件夹 external table:与table类似,不过其数据存放位置可以在任意指定路径 partition:在hdfs中表现为table目录下的子目录 bucket:在hdfs中表现为同一个表目录下根据hash散列之后的多个文件 Hive基本操作使用方式Hive交互shell bin/hive Hive thrift服务 启动方式,(假如是在hadoop01上): 启动为前台:bin/hiveserver2 启动为后台:nohup bin/hiveserver2 1>/var/log/hiveserver.log 2>/var/log/hiveserver.err & 启动成功后,可以在别的节点上用beeline去连接 方式(1) hive/bin/beeline 回车,进入beeline的命令界面 输入命令连接hiveserver2 beeline> !connect jdbc:hive2//mini1:10000 (hadoop01是hiveserver2所启动的那台主机名,端口默认是10000) 方式(2) 或者启动就连接: bin/beeline -u jdbc:hive2://mini1:10000 -n hadoop 接下来就可以做正常sql查询了 Hive命令 命令执行 hive -e ‘sql’ DDL操作创建表建表语法123456789CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name data_type [COMMENT col_comment], ...)] [COMMENT table_comment] [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] [ROW FORMAT row_format] [STORED AS file_format] [LOCATION hdfs_path] 说明: CREATE TABLE 创建一个指定名字的表。如果相同名字的表已经存在,则抛出异常;用户可以用 IF NOT EXISTS 选项来忽略这个异常。 EXTERNAL关键字可以让用户创建一个外部表,在建表的同时指定一个指向实际数据的路径(LOCATION),Hive 创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径,不对数据的位置做任何改变。在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据。 12345ROW FORMAT DELIMITED [FIELDS TERMINATED BY char] [COLLECTION ITEMS TERMINATED BY char] [MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char] | SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value, property_name=property_value, ...)]用户在建表的时候可以自定义 SerDe 或者使用自带的 SerDe。如果没有指定 ROW FORMAT 或者 ROW FORMAT DELIMITED,将会使用自带的 SerDe。在建表的时候,用户还需要为表指定列,用户在指定表的列的同时也会指定自定义的 SerDe,Hive通过 SerDe 确定表的具体的列的数据。 123STORED AS SEQUENCEFILE|TEXTFILE|RCFILE如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCEFILE CLUSTERED BY 对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分。Hive也是 针对某一列进行桶的组织。Hive采用对列值哈希,然后除以桶的个数求余的方式决定该条记录存放在哪个桶当中。把表(或者分区)组织成桶(Bucket)有两个理由: 获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接 (Map-side join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。 使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。 具体实例 创建内部表mytable。 创建外部表pageview。 创建分区表invites。 create table student_p(Sno int,Sname string,Sex string,Sage int,Sdept string) partitioned by(part string) row format delimited fields terminated by ','stored as textfile; 、 创建带桶的表student。 修改表增加/删除分区 语法结构 1234ALTER TABLE table_name ADD [IF NOT EXISTS] partition_spec [ LOCATION 'location1' ] partition_spec [ LOCATION 'location2' ] ...partition_spec:: PARTITION (partition_col = partition_col_value, partition_col = partiton_col_value, ...)ALTER TABLE table_name DROP partition_spec, partition_spec,... 具体实例 alter table student_p add partition(part='a') partition(part='b'); 重命名表 语法结构 ALTER TABLE table_name RENAME TO new_table_name 具体实例 增加/更新列 语法结构 ALTER TABLE table_name ADD|REPLACE COLUMNS (col_name data_type [COMMENT col_comment], ...) 注:ADD是代表新增一字段,字段位置在所有列后面(partition列前),REPLACE则是表示替换表中所有字段。 ALTER TABLE table_name CHANGE [COLUMN] col_old_name col_new_name column_type [COMMENT col_comment][FIRST|AFTER column_name] 具体实例 显示命令 show tables show databases show partitions show functions desc extended t_name; desc formatted table_name; DML操作Load 语法结构 LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)] 说明: Load 操作只是单纯的复制/移动操作,将数据文件移动到 Hive 表对应的位置。 filepath: 相对路径,例如:project/data1 绝对路径,例如:/user/hive/project/data1 包含模式的完整 URI,列如: hdfs://namenode:9000/user/hive/project/data1 LOCAL关键字 如果指定了 LOCAL, load 命令会去查找本地文件系统中的 filepath。 如果没有指定 LOCAL 关键字,则根据inpath中的uri查找文件 OVERWRITE 关键字 如果使用了 OVERWRITE 关键字,则目标表(或者分区)中的内容会被删除,然后再将 filepath 指向的文件/目录中的内容添加到表/分区中。 如果目标表(分区)已经有一个文件,并且文件名和 filepath 中的文件名冲突,那么现有的文件会被新文件所替代。 具体实例 加载相对路径数据。 加载绝对路径数据。 加载包含模式数据。 OVERWRITE关键字使用。 Insert 将查询结果插入Hive表 语法结构 INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1 FROM from_statement Multiple inserts: FROM from_statement INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1 [INSERT OVERWRITE TABLE tablename2 [PARTITION ...] select_statement2] ... Dynamic partition inserts: INSERT OVERWRITE TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) select_statement FROM from_statement 具体实例 基本模式插入。 多插入模式。 自动分区模式。 导出表数据 语法结构 INSERT OVERWRITE [LOCAL] DIRECTORY directory1 SELECT ... FROM ... multiple inserts: FROM from_statementINSERT OVERWRITE [LOCAL] DIRECTORY directory1 select_statement1[INSERT OVERWRITE [LOCAL] DIRECTORY directory2 select_statement2] ... 具体实例 导出文件到本地。 说明: 数据写入到文件系统时进行文本序列化,且每列用^A来区分,\n为换行符。用more命令查看时不容易看出分割符,可以使用: sed -e 's/\x01/|/g' filename来查看。 导出数据到HDFS。 SELECT 基本的Select操作 语法结构 12345678SELECT [ALL | DISTINCT] select_expr, select_expr, ... FROM table_reference[WHERE where_condition] [GROUP BY col_list [HAVING condition]] [CLUSTER BY col_list | [DISTRIBUTE BY col_list] [SORT BY| ORDER BY col_list] ] [LIMIT number] 注: order by 会对输入做全局排序,因此只有一个reducer,会导致当输入规模较大时,需要较长的计算时间。 sort by不是全局排序,其在数据进入reducer前完成排序。因此,如果用sort by进行排序,并且设置mapred.reduce.tasks>1,则sort by只保证每个reducer的输出有序,不保证全局有序。 distribute by(字段)根据指定的字段将数据分到不同的reducer,且分发算法是hash散列。 Cluster by(字段) 除了具有Distribute by的功能外,还会对该字段进行排序。 因此,如果分桶和sort字段是同一个时,此时,cluster by = distribute by + sort by 分桶表的作用:最大的作用是用来提高join操作的效率; 思考这个问题:select a.id,a.name,b.addr from a join b on a.id = b.id; 如果a表和b表已经是分桶表,而且分桶的字段是id字段 做这个join操作时,还需要全表做笛卡尔积吗? 具体实例 获取年龄大的3个学生。 查询学生信息按年龄,降序排序。 按学生名称汇总学生年龄。 Hive Join 语法结构 1234join_table: table_reference JOIN table_factor [join_condition] | table_reference {LEFT|RIGHT|FULL} [OUTER] JOIN table_reference join_condition | table_reference LEFT SEMI JOIN table_reference join_condition Hive 支持等值连接(equality joins)、外连接(outer joins)和(left/right joins)。 Hive 不支持非等值的连接,因为非等值连接非常难转化到 map/reduce 任务。 另外,Hive 支持多于 2 个表的连接。 写 join 查询时,需要注意几个关键点: 只支持等值join 例如: SELECT a.* FROM a JOIN b ON (a.id = b.id) SELECT a.* FROM a JOIN b ON (a.id = b.id AND a.department = b.department) 是正确的,然而: SELECT a.* FROM a JOIN b ON (a.id>b.id) 是错误的。 可以 join 多于 2 个表。 例如: SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2) 如果join中多个表的 join key 是同一个,则 join 会被转化为单个 map/reduce 任务,例如: SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1) 被转化为单个 map/reduce 任务,因为 join 中只使用了 b.key1 作为 join key。 SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2) 而这一 join 被转化为 2 个 map/reduce 任务。因为 b.key1 用于第一次 join 条件,而 b.key2 用于第二次 join。 join 时,每次 map/reduce 任务的逻辑: reducer 会缓存 join 序列中除了最后一个表的所有表的记录,再通过最后一个表将结果序列化到文件系统。这一实现有助于在 reduce 端减少内存的使用量。实践中,应该把最大的那个表写在最后(否则会因为缓存浪费大量内存)。例如: SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key1) 所有表都使用同一个 join key(使用 1 次 map/reduce 任务计算)。Reduce 端会缓存 a 表和 b 表的记录,然后每次取得一个 c 表的记录就计算一次 join 结果,类似的还有: SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2) 这里用了 2 次 map/reduce 任务。第一次缓存 a 表,用 b 表序列化;第二次缓存第一次 map/reduce 任务的结果,然后用 c 表序列化。 LEFT,RIGHT 和 FULL OUTER 关键字用于处理 join 中空记录的情况 例如: SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key) 对应所有 a 表中的记录都有一条记录输出。输出的结果应该是 a.val, b.val,当 a.key=b.key 时,而当 b.key 中找不到等值的 a.key 记录时也会输出: a.val, NULL 所以 a 表中的所有记录都被保留了; a RIGHT OUTER JOIN b会保留所有 b 表的记录。 Join 发生在 WHERE 子句之前。 如果你想限制 join 的输出,应该在 WHERE 子句中写过滤条件——或是在 join 子句中写。这里面一个容易混淆的问题是表分区的情况: SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key) WHERE a.ds='2009-07-07' AND b.ds='2009-07-07' 会 join a 表到 b 表(OUTER JOIN),列出 a.val 和 b.val 的记录。WHERE 从句中可以使用其他列作为过滤条件。但是,如前所述,如果 b 表中找不到对应 a 表的记录,b 表的所有列都会列出 NULL,包括 ds 列。也就是说,join 会过滤 b 表中不能找到匹配 a 表 join key 的所有记录。这样的话,LEFT OUTER 就使得查询结果与 WHERE 子句无关了。解决的办法是在 OUTER JOIN 时使用以下语法: SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key AND b.ds='2009-07-07' ANDa.ds='2009-07-07') 这一查询的结果是预先在 join 阶段过滤过的,所以不会存在上述问题。这一逻辑也可以应用于 RIGHT 和 FULL 类型的 join 中。 Join 是不能交换位置的。 无论是 LEFT 还是 RIGHT join,都是左连接的。 SELECT a.val1, a.val2, b.val, c.val FROM JOIN b ON (a.key = b.key) LEFT OUTER JOIN c ON (a.key = c.key) 先 join a 表到 b 表,丢弃掉所有 join key 中不匹配的记录,然后用这一中间结果和 c 表做 join。这一表述有一个不太明显的问题,就是当一个 key 在 a 表和 c 表都存在,但是 b 表中不存在的时候:整个记录在第一次 join,即 a JOIN b 的时候都被丢掉了(包括a.val1,a.val2和a.key),然后我们再和 c 表 join 的时候,如果 c.key 与 a.key 或 b.key 相等,就会得到这样的结果:NULL, NULL, NULL, c.val 具体实例 获取已经分配班级的学生姓名。 获取尚未分配班级的学生姓名。 LEFT SEMI JOIN是IN/EXISTS的高效实现。 Hive Shell参数Hive命令行 语法结构 hive [-hiveconf x=y]* [<-i filename>]* [<-f filename>|<-e query-string>][-S] 说明: -i 从文件初始化HQL。 -e从命令行执行指定的HQL -f 执行HQL脚本 -v 输出执行的HQL语句到控制台 -p connect to Hive Server on port number -hiveconf x=y Use this to set hive/hadoop configuration variables. 具体实例 运行一个查询。 运行一个文件。 运行参数文件。 Hive参数配置方式 Hive参数大全: https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties 开发Hive应用时,不可避免地需要设定Hive的参数。设定Hive的参数可以调优HQL代码的执行效率,或帮助定位问题。然而实践中经常遇到的一个问题是,为什么设定的参数没有起作用?这通常是错误的设定方式导致的。 对于一般参数,有以下三种设定方式: 配置文件 命令行参数 参数声明 配置文件:Hive的配置文件包括 用户自定义配置文件:$HIVE_CONF_DIR/hive-site.xml 默认配置文件:$HIVE_CONF_DIR/hive-default.xml 用户自定义配置会覆盖默认配置。 另外,Hive也会读入Hadoop的配置,因为Hive是作为Hadoop的客户端启动的,Hive的配置会覆盖Hadoop的配置。 配置文件的设定对本机启动的所有Hive进程都有效。 命令行参数:启动Hive(客户端或Server方式)时,可以在命令行添加-hiveconf param=value来设定参数,例如: bin/hive -hiveconf hive.root.logger=INFO,console 这一设定对本次启动的Session(对于Server方式启动,则是所有请求的Sessions)有效。 参数声明:可以在HQL中使用SET关键字设定参数,例如: set mapred.reduce.tasks=100; 这一设定的作用域也是session级的。 上述三种设定方式的优先级依次递增。即参数声明覆盖命令行参数,命令行参数覆盖配置文件设定。注意某些系统级的参数,例如log4j相关的设定,必须用前两种方式设定,因为那些参数的读取在Session建立以前已经完成了。 Hive函数内置运算符内容较多,见《Hive官方文档》 内置函数内容较多,见《Hive官方文档》https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF 测试各种内置函数的快捷方法: 创建一个dual表 create table dual(id string); load一个文件(一行,一个空格)到dual表 select substr(‘angelababy’,2,3) from dual; Hive自定义函数和Transform 当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数(UDF:user-defined function)。 自定义函数类别 UDF 作用于单个数据行,产生一个数据行作为输出。(数学函数,字符串函数) UDAF(用户定义聚集函数):接收多个输入数据行,并产生一个输出数据行。(count,max) UDF开发实例 简单UDF示例 先开发一个java类,继承UDF,并重载evaluate方法 12345678910package cn.itcast.bigdata.udfimport org.apache.hadoop.hive.ql.exec.UDF;import org.apache.hadoop.io.Text;public final class Lower extends UDF{ public Text evaluate(final Text s){ if(s==null){return null;} return new Text(s.toString().toLowerCase()); }} 打成jar包上传到服务器 将jar包添加到hive的classpath hive>add JAR /home/hadoop/udf.jar; 创建临时函数与开发好的java class关联 Hive>create temporary function tolowercase as 'cn.itcast.bigdata.udf.ToProvince'; 5、即可在hql中使用自定义的函数strip Transform实现 Hive的 TRANSFORM 关键字提供了在SQL中调用自写脚本的功能 适合实现Hive中没有的功能又不想写UDF的情况 使用示例1:下面这句sql就是借用了weekday_mapper.py对数据进行了处理. 12345678910111213141516CREATE TABLE u_data_new ( movieid INT, rating INT, weekday INT, userid INT)ROW FORMAT DELIMITEDFIELDS TERMINATED BY '\t';add FILE weekday_mapper.py;INSERT OVERWRITE TABLE u_data_newSELECT TRANSFORM (movieid , rate, timestring,uid) USING 'python weekday_mapper.py' AS (movieid, rating, weekday,userid)FROM t_rating; 其中weekday_mapper.py内容如下 123456789#!/bin/pythonimport sysimport datetimefor line in sys.stdin: line = line.strip() movieid, rating, unixtime,userid = line.split('\t') weekday = datetime.datetime.fromtimestamp(float(unixtime)).isoweekday() print '\t'.join([movieid, rating, str(weekday),userid]) Hive实战Hive 实战案例1——数据ETL需求: 对web点击流日志基础数据表进行etl(按照仓库模型设计) 按各时间维度统计来源域名top10 已有数据表 “t_orgin_weblog” : 12345678910111213+------------------+------------+----------+--+| col_name | data_type | comment |+------------------+------------+----------+--+| valid | string | || remote_addr | string | || remote_user | string | || time_local | string | || request | string | || status | string | || body_bytes_sent | string | || http_referer | string | || http_user_agent | string | |+------------------+------------+----------+--+ 数据示例: 123| true|1.162.203.134| - | 18/Sep/2013:13:47:35| /images/my.jpg | 200| 19939 | "http://www.angularjs.cn/A0d9" | "Mozilla/5.0 (Windows || true|1.202.186.37 | - | 18/Sep/2013:15:39:11| /wp-content/uploads/2013/08/windjs.png| 200| 34613 | "http://cnodejs.org/topic/521a30d4bee8d3cb1272ac0f" | "Mozilla/5.0 (Macintosh;| 实现步骤: 对原始数据进行抽取转换 将来访url分离出host path query query_id 1234drop table if exists t_etl_referurl;create table t_etl_referurl asSELECT a.,b.FROM t_orgin_weblog a LATERAL VIEW parse_url_tuple(regexp_replace(http_referer, "\"", ""), 'HOST', 'PATH','QUERY', 'QUERY:id') b as host, path, query, query_id 从前述步骤进一步分离出日期时间形成ETL明细表“t_etl_detail” 12345678drop table if exists t_etl_detail;create table t_etl_detail as select b.*,substring(time_local,0,11) as daystr,substring(time_local,13) as tmstr,substring(time_local,4,3) as month,substring(time_local,0,2) as day,substring(time_local,13,2) as hourfrom t_etl_referurl b; 对etl数据进行分区(包含所有数据的结构化信息) 123456789101112131415161718192021drop table t_etl_detail_prt;create table t_etl_detail_prt(valid string,remote_addr string,remote_user string,time_local string,request string,status string,body_bytes_sent string,http_referer string,http_user_agent string,host string,path string,query string,query_id string,daystr string,tmstr string,month string,day string,hour string) partitioned by (mm string,dd string); 导入数据 12345insert into table t_etl_detail_prt partition(mm='Sep',dd='18')select * from t_etl_detail where daystr='18/Sep/2013';insert into table t_etl_detail_prt partition(mm='Sep',dd='19')select * from t_etl_detail where daystr='19/Sep/2013'; 分个时间维度统计各referer_host的访问次数并排序 12create table t_refer_host_visit_top_tmp asselect referer_host,count(*) as counts,mm,dd,hh from t_display_referer_counts group by hh,dd,mm,referer_host order by hh asc,dd asc,mm asc,counts desc; 来源访问次数topn各时间维度URL 取各时间维度的referer_host访问次数topn 1select * from (select referer_host,counts,concat(hh,dd),row_number() over (partition by concat(hh,dd) order by concat(hh,dd) asc) as od from t_refer_host_visit_top_tmp) t where od<=3; Hive实战案例3——级联求和需求: 有如下访客访问次数统计表 t_access_times 123456789101112访客 月份 访问次数A 2015-01 5A 2015-01 15B 2015-01 5A 2015-01 8B 2015-01 25A 2015-01 5A 2015-02 4A 2015-02 6B 2015-02 10B 2015-02 5…… …… …… 需要输出报表:t_access_times_accumulate 1234567访客 月份 月访问总计 累计访问总计A 2015-01 33 33A 2015-02 10 43……. ……. ……. …….B 2015-01 30 30B 2015-02 15 45……. ……. ……. ……. 实现步骤 可以用一个hql语句即可实现: 12345678910select A.username,A.month,max(A.salary) as salary,sum(B.salary) as accumulatefrom (select username,month,sum(salary) as salary from t_access_times group by username,month) A inner join (select username,month,sum(salary) as salary from t_access_times group by username,month) BonA.username=B.usernamewhere B.month <= A.monthgroup by A.username,A.monthorder by A.username,A.month;]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
<tag>Hive</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hive安装配置]]></title>
<url>%2F2017%2F06%2F21%2FHive%E5%AE%89%E8%A3%85%2F</url>
<content type="text"><![CDATA[Hive是基于Hadoop构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式来分析存储在Hadoop 分布式文件系统中的数据。其在Hadoop的架构体系中承担了一个SQL解析的过程,它提供了对外的入口来获取用户的指令然后对指令进行分析,解析出一个MapReduce程序组成可执行计划,并按照该计划生成对应的MapReduce任务提交给Hadoop集群处理,获取最终的结果。元数据——如表模式——存储在名为metastore的数据库中。 系统环境123192.168.186.128 hadoop-master192.168.186.129 hadoop-slaveMySQL安装在master机器上,hive服务器也安装在master上 Hive下载下载源码包,最新版本可自行去官网下载 1234[hadoop@hadoop-master ~]$ wget http://mirrors.cnnic.cn/apache/hive/hive-1.2.1/apache-hive-1.2.1-bin.tar.gz[hadoop@hadoop-master ~]$ tar -zxf apache-hive-1.2.1-bin.tar.gz [hadoop@hadoop-master ~]$ lsapache-hive-1.2.1-bin apache-hive-1.2.1-bin.tar.gz dfs hadoop-2.7.1 Hsource tmp 配置环境变量 12345[root@hadoop-master hadoop]# vi /etc/profileHIVE_HOME=/home/hadoop/apache-hive-1.2.1-binPATH=$PATH:$HIVE_HOME/binexport HIVE_NAME PATH[root@hadoop-master hadoop]# source /etc/profile Metastoremetastore是Hive元数据集中存放地。它包括两部分:服务和后台数据存储。有三种方式配置metastore:内嵌metastore、本地metastore以及远程metastore。本次搭建中采用MySQL作为远程仓库,部署在hadoop-master节点上,hive服务端也安装在hive-master上,hive客户端即hadoop-slave访问hive服务器。 MySQL安装安装依赖包1# yum install gcc gcc-c++ ncurses-devel -y 安装cmake12345# wget http://www.cmake.org/files/v2.8/cmake-2.8.12.tar.gz# tar zxvf cmake-2.8.12.tar.gz# cd cmake-2.8.12# ./bootstrap # make && make install 创建用户的相应目录12345# groupadd mysql# useradd -g mysql mysql# mkdir -p /data/mysql/# mkdir -p /data/mysql/data/# mkdir -p /data/mysql/log/ 获取MySQL安装包并安装1234567891011121314151617# wget http://dev.mysql.com/get/downloads/mysql/mysql-5.6.25.tar.gz# tar zxvf mysql-5.6.25.tar.gz# cd mysql-5.6.25# cmake \-DCMAKE_INSTALL_PREFIX=/data/mysql \-DMYSQL_UNIX_ADDR=/data/mysql/mysql.sock \-DDEFAULT_CHARSET=utf8 \-DDEFAULT_COLLATION=utf8_general_ci \-DWITH_INNOBASE_STORAGE_ENGINE=1 \-DWITH_ARCHIVE_STORAGE_ENGINE=1 \-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \-DMYSQL_DATADIR=/data/mysql/data \-DMYSQL_TCP_PORT=3306 \-DENABLE_DOWNLOADS=1如果报错找不到CMakeCache.txt则说明没安装ncurses-devel # make && make install 修改目录权限1234# chmod +w /data/mysql/# chown -R mysql:mysql /data/mysql/# ln -s /data/mysql/lib/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.18# ln -s /data/mysql/mysql.sock /tmp/mysql.sock 初始化数据库123# cp /data/mysql/support-files/my-default.cnf /etc/my.cnf# cp /data/mysql/support-files/mysql.server /etc/init.d/mysqld# /data/mysql/scripts/mysql_install_db --user=mysql --defaults-file=/etc/my.cnf --basedir=/data/mysql --datadir=/data/mysql/data 启动MySQL服务123# chmod +x /etc/init.d/mysqld# service mysqld start#ln –s /data/mysql/bin/mysql /usr/bin/ 初始化密码12#mysql -uroot -h127.0.0.1 -pmysql> SET PASSWORD = PASSWORD('123456'); 创建Hive用户123mysql>CREATE USER 'hive' IDENTIFIED BY 'hive';mysql>GRANT ALL PRIVILEGES ON *.* TO 'hive'@'hadoop-master' WITH GRANT OPTION;mysql>flush privileges; Hive用户登录12[hadoop@hadoop-master ~]mysql -h hadoop-master -uhivemysql>set password = password('hive'); 创建Hive数据库1mysql>create database hive; 配置Hive修改配置文件进入到hive的配置文件目录下,找到hive-default.xml.template,cp份为hive-default.xml另创建hive-site.xml并添加参数 1234567891011121314151617181920212223242526[hadoop@hadoop-master conf]$ pwd/home/hadoop/apache-hive-1.2.1-bin/conf[hadoop@hadoop-master conf]$ vi hive-site.xml<configuration> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://hadoop-master:3306/hive?createDatabaseIfNotExist=true</value> <description>JDBC connect string for a JDBC metastore</description> </property> <property> <name>javax.jdo.option.ConnectionDriverName</name> <value>com.mysql.jdbc.Driver</value> <description>Driver class name for a JDBC metastore</description> </property> <property> <name>javax.jdo.option.ConnectionUserName</name> <value>hive</value> <description>username to use against metastore database</description> </property> <property> <name>javax.jdo.option.ConnectionPassword</name> <value>hive</value> <description>password to use against metastore database</description> </property> </configuration> JDBC下载1234[hadoop@hadoop-master ~]$ wget http://cdn.mysql.com/Downloads/Connector-J/mysql-connector-java-5.1.36.tar.gz[hadoop@hadoop-master ~]$ lsapache-hive-1.2.1-bin dfs hadoop-2.7.1 Hsource tmp[hadoop@hadoop-master ~]$ cp mysql-connector-java-5.1.33-bin.jar apache-hive-1.2.1-bin/lib/ Hive客户端配置12345678[hadoop@hadoop-master ~]$ scp -r apache-hive-1.2.1-bin/ hadoop@hadoop-slave:/home/hadoop[hadoop@hadoop-slave conf]$ vi hive-site.xml<configuration> <property> <name>hive.metastore.uris</name> <value>thrift://hadoop-master:9083</value> </property></configuration> Hive服务器端访问1234567891011121314151617181920[hadoop@hadoop-master ~]$ hiveLogging initialized using configuration in jar:file:/home/hadoop/apache-hive-1.2.1-bin/lib/hive-common-1.2.1.jar!/hive-log4j.propertieshive> show databases;OKdefaultsrcTime taken: 1.332 seconds, Fetched: 2 row(s)hive> use src;OKTime taken: 0.037 secondshive> create table test1(id int);OKTime taken: 0.572 secondshive> show tables;OKabctesttest1Time taken: 0.057 seconds, Fetched: 3 row(s)hive> 好了,测试完毕,已经安装成功了。 安装问题纠错Hive数据库编码问题错误描述:hive进入后可以创建数据库,但是无法创建表 12hive>create table table_test(id string,name string);FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask.MetaException(message:javax.jdo.JDODataStoreException: An exception was thrown while adding/validating class(es) : Specified key was too long; max key length is 767 bytes 解决办法:登录mysql修改下hive数据库的编码方式 1mysql>alter database hive character set latin1; 防火墙问题Hive服务器开启了iptables服务,hive本机可以访问hive服务,hive的客户端hadoop-slave访问报错 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566[hadoop@hadoop-slave conf]$ hiveLogging initialized using configuration in jar:file:/home/hadoop/apache-hive-1.2.1-bin/lib/hive-common-1.2.1.jar!/hive-log4j.propertiesException in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:522) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:677) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:621) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.apache.hadoop.util.RunJar.run(RunJar.java:221) at org.apache.hadoop.util.RunJar.main(RunJar.java:136)Caused by: java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient at org.apache.hadoop.hive.metastore.MetaStoreUtils.newInstance(MetaStoreUtils.java:1523) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.<init>(RetryingMetaStoreClient.java:86) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.getProxy(RetryingMetaStoreClient.java:132) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.getProxy(RetryingMetaStoreClient.java:104) at org.apache.hadoop.hive.ql.metadata.Hive.createMetaStoreClient(Hive.java:3005) at org.apache.hadoop.hive.ql.metadata.Hive.getMSC(Hive.java:3024) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:503) ... 8 moreCaused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:408) at org.apache.hadoop.hive.metastore.MetaStoreUtils.newInstance(MetaStoreUtils.java:1521) ... 14 moreCaused by: MetaException(message:Could not connect to meta store using any of the URIs provided. Most recent failure: org.apache.thrift.transport.TTransportException: java.net.NoRouteToHostException: No route to host at org.apache.thrift.transport.TSocket.open(TSocket.java:187) at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.open(HiveMetaStoreClient.java:420) at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.<init>(HiveMetaStoreClient.java:236) at org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient.<init>(SessionHiveMetaStoreClient.java:74) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:408) at org.apache.hadoop.hive.metastore.MetaStoreUtils.newInstance(MetaStoreUtils.java:1521) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.<init>(RetryingMetaStoreClient.java:86) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.getProxy(RetryingMetaStoreClient.java:132) at org.apache.hadoop.hive.metastore.RetryingMetaStoreClient.getProxy(RetryingMetaStoreClient.java:104) at org.apache.hadoop.hive.ql.metadata.Hive.createMetaStoreClient(Hive.java:3005) at org.apache.hadoop.hive.ql.metadata.Hive.getMSC(Hive.java:3024) at org.apache.hadoop.hive.ql.session.SessionState.start(SessionState.java:503) at org.apache.hadoop.hive.cli.CliDriver.run(CliDriver.java:677) at org.apache.hadoop.hive.cli.CliDriver.main(CliDriver.java:621) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:483) at org.apache.hadoop.util.RunJar.run(RunJar.java:221) at org.apache.hadoop.util.RunJar.main(RunJar.java:136)Caused by: java.net.NoRouteToHostException: No route to host at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at org.apache.thrift.transport.TSocket.open(TSocket.java:182) ... 22 more) at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.open(HiveMetaStoreClient.java:466) at org.apache.hadoop.hive.metastore.HiveMetaStoreClient.<init>(HiveMetaStoreClient.java:236) at org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient.<init>(SessionHiveMetaStoreClient.java:74) ... 19 more 解决办法:比较粗暴直接关掉了防火墙 12345[root@hadoop-master hadoop]# service iptables stopiptables: Flushing firewall rules: [ OK ]iptables: Setting chains to policy ACCEPT: filter [ OK ]iptables: Unloading modules: [ OK ][root@hadoop-master hadoop]#]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Hadoop</tag>
<tag>Hive</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Let's Encrypt 使用教程,免费的 SSL 证书]]></title>
<url>%2F2017%2F06%2F21%2FLet's%20Encrypt%20%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B%EF%BC%8C%E5%85%8D%E8%B4%B9%E7%9A%84%20SSL%20%E8%AF%81%E4%B9%A6%2F</url>
<content type="text"><![CDATA[Let’s Encrypt 简介Let’s Encrypt 是国外一个公共的免费SSL项目,由 Linux 基金会托管,它的来头不小,由 Mozilla、思科、Akamai、IdenTrust 和 EFF 等组织发起,目的就是向网站自动签发和管理免费证书,以便加速互联网由 HTTP 过渡到 HTTPS,目前 Facebook 等大公司开始加入赞助行列。 Let’s Encrypt 已经得了 IdenTrust 的交叉签名,这意味着其证书现在已经可以被 Mozilla、Google、Microsoft 和 Apple 等主流的浏览器所信任,你只需要在 Web 服务器证书链中配置交叉签名,浏览器客户端会自动处理好其它的一切,Let’s Encrypt 安装简单,使用非常方便。 Certbot 简介Certbot 为 Let’s Encrypt 项目发布了一个官方的客户端 Certbot ,利用它可以完全自动化的获取、部署和更新安全证书,并且 Certbot 是支持所有 Unix 内核的操作系统。 安装 Certbot 客户端12$ yum install certbot # centos$ # apt install certbot # ubuntu Certbot 的两种使用方式webroot 方式: certbot 会利用既有的 web server,在其 web root 目录下创建隐藏文件,Let’s Encrypt 服务端会通过域名来访问这些隐藏文件,以确认你的确拥有对应域名的控制权。standalone 方式: Certbot 会自己运行一个 web server 来进行验证。如果我们自己的服务器上已经有 web server 正在运行 (比如 Nginx 或 Apache ),用 standalone 方式的话需要先关掉它,以免冲突。获取证书 webroot 模式使用这种模式会在 web root 中创建 .well-known 文件夹,这个文件夹里面包含了一些验证文件,Certbot 会通过访问 example.com/.well-known/acme-challenge 来验证你的域名是否绑定的这个服务器,所以需要编辑 nginx 配置文件确保可以访问刚刚创建的 .well-known 文件夹及里边存放的验证文件,以便在生成证书时进行验证: 使用一下命令查看 nginx 配置文件地址: 123$ sudo nginx -tnginx: the configuration file /usr/local/nginx/nginx.conf syntax is ok nginx: configuration file /usr/local/nginx/nginx.conf test is successful 编辑 /usr/local/nginx/nginx.conf 配置 12345678server { ... location /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/example; } ...} 重启 nginx 服务 $ nginx -s reload获取证书,–email 为申请者邮箱,–webroot 为 webroot 方式,-w 为站点目录,-d 为要加 https 的域名,以下命令会为 example.com 和 www.example.com 这两个域名生成一个证书: $ certbot certonly --email [email protected] --webroot -w /var/www/example -d example.com -d www.example.comstandalone 模式获取证书 但是有些时候我们的一些服务并没有根目录,例如一些微服务,这时候使用 webroot 模式就走不通了。这时可以使用模式 standalone 模式,这种模式不需要指定网站根目录,他会自动启用服务器的443端口,来验证域名的归属。我们有其他服务(例如nginx)占用了443端口,就必须先停止这些服务,在证书生成完毕后,再启用。 $ certbot certonly --email [email protected] --standalone -d example.com -d www.example.comnginx 开启 https 证书生成完成后可以到 /etc/letsencrypt/live/ 目录下查看对应域名的证书文件。编辑 nginx 配置文件监听 443 端口,启用 SSL,并配置 SSL 的公钥、私钥证书路径: 1234567891011server { listen 443; server_name example.com; root /var/www/example; index index.html index.htm; ssl on; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; charset utf-8; ...} 添加 HTTP 跳转到 HTTPS: 12345server { listen 80; server_name example.com; return 301 https://$server_name$request_uri;} 重启 nginx 服务,访问 https://example.com 查看是否配置成功 自动续期 Let’s Encrypt 提供的证书只有90天的有效期,所以我们要在在证书到期之前重新获取这些证书,Certbot 提供了一个方便的命令 certbot renew,我们可以先使用 –dry-run 测试是否可用: $ certbot renew --dry-runlinux 系统上有 cron 可以来搞定这件事情,使用一下命令新建任务: $ crontab -e写入一下任务内容。这段内容的意思就是 每隔 两个月的 凌晨 2:15 执行 更新操作 15 2 * */2 * certbot renew --quiet --renew-hook "service nginx restart"参数 表述 –quiet 执行时屏蔽错误以外的所有输出,也可以使用 -q –pre-hook 执行更新操作之前要做的事情 –pre-hook 执行更新操作之前要做的事情 –post-hook 执行更新操作完成后要做的事情 取消证书 可以使用一下命令取消刚刚生成的密匙,也就是以上的反操作: 12$ certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem$ certbot delete --cert-name example.com 至此,整个网站升级到 HTTPS 就完成了。 七牛静态资源也能使用 HTTPS 如果博客里面没有用到外部的一些静态图片等资源,那这时候访问网站,应该会看到地址栏有一把小绿锁了。但是我的博客一直使用的是七牛的免费图床,一直都是 HTTP 外链,所以地址栏的小绿锁也没有显示。 结束语 如果加上 HTTPS 之后😊锁不够绿的话,检查下站点加载的资源(比如 js、css、照片等)是不是有 HTTP 的,有的话就会导致小锁变为灰色。 在我们使用 HTTP 的时候,打开网页总会遇到第三方偷偷加的一些脚本广告,很是烦人,升级 HTTPS 后他们就无从下手了,欧耶。]]></content>
<categories>
<category>Https</category>
</categories>
<tags>
<tag>Https</tag>
<tag>Let's Encrypt</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hadoop安装教程单机/伪分布式配置Hadoop2.6.0/Ubuntu14.04]]></title>
<url>%2F2017%2F06%2F15%2Fubuntu%E5%AE%89%E8%A3%85%E4%BC%AA%E5%88%86%E5%B8%83%E5%BC%8Fhadoop%2F</url>
<content type="text"><![CDATA[当开始着手实践 Hadoop 时,安装 Hadoop 往往会成为新手的一道门槛。尽管安装其实很简单,书上有写到,官方网站也有 Hadoop 安装配置教程,但由于对 Linux 环境不熟悉,书上跟官网上简略的安装步骤新手往往 Hold 不住。加上网上不少教程也甚是坑,导致新手折腾老几天愣是没装好,很是打击学习热情。 本教程适合于原生 Hadoop 2,包括 Hadoop 2.6.0, Hadoop 2.7.1 等版本,主要参考了官方安装教程,步骤详细,辅以适当说明,相信按照步骤来,都能顺利安装并运行Hadoop。另外有Hadoop安装配置简略版方便有基础的读者快速完成安装。此外,希望读者们能多去了解一些 Linux 的知识,以后出现问题时才能自行解决。本教程由给力星出品,转载请注明。 环境本教程使用 Ubuntu 14.04 64位 作为系统环境(Ubuntu 12.04 也行,32位、64位均可),请自行安装系统。 如果用的是 CentOS/RedHat 系统,请查看相应的CentOS安装Hadoop教程_单机伪分布式配置。 本教程基于原生 Hadoop 2,在 Hadoop 2.6.0 (stable) 版本下验证通过,可适合任何 Hadoop 2.x.y 版本,例如 Hadoop 2.4.1。 Hadoop版本 Hadoop 有两个主要版本,Hadoop 1.x.y 和 Hadoop 2.x.y 系列,比较老的教材上用的可能是 0.20 这样的版本。Hadoop 2.x 版本在不断更新,本教程均可适用。如果需安装 0.20,1.2.1这样的版本,本教程也可以作为参考,主要差别在于配置项,配置请参考官网教程或其他教程。 新版是兼容旧版的,书上旧版本的代码应该能够正常运行(我自己没验证,欢迎验证反馈)。 装好了 Ubuntu 系统之后,在安装 Hadoop 前还需要做一些必备工作。 创建hadoop用户如果你安装 Ubuntu 的时候不是用的 “hadoop” 用户,那么需要增加一个名为 hadoop 的用户。 首先按 ctrl+alt+t 打开终端窗口,输入如下命令创建新用户 : 1sudo useradd -m hadoop -s /bin/bash 这条命令创建了可以登陆的 hadoop 用户,并使用 /bin/bash 作为 shell。 Ubuntu终端复制粘贴快捷键 在Ubuntu终端窗口中,复制粘贴的快捷键需要加上 shift,即粘贴是 ctrl+shift+v。 接着使用如下命令设置密码,可简单设置为 hadoop,按提示输入两次密码: 1sudo passwd hadoop 可为 hadoop 用户增加管理员权限,方便部署,避免一些对新手来说比较棘手的权限问题: 1sudo adduser hadoop sudo 最后注销当前用户(点击屏幕右上角的齿轮,选择注销),在登陆界面使用刚创建的 hadoop 用户进行登陆。 更新apt用 hadoop 用户登录后,我们先更新一下 apt,后续我们使用 apt 安装软件,如果没更新可能有一些软件安装不了。按 ctrl+alt+t 打开终端窗口,执行如下命令: 1sudo apt-get update 若出现如下 “Hash校验和不符” 的提示,可通过更改软件源来解决。若没有该问题,则不需要更改。 Ubuntu更新软件源时遇到Hash校验和不符的问题 点击查看:如何更改软件源 后续需要更改一些配置文件,我比较喜欢用的是 vim(vi增强版,基本用法相同),建议安装一下(如果你实在还不会用 vi/vim 的,请将后面用到 vim 的地方改为 gedit,这样可以使用文本编辑器进行修改,并且每次文件更改完成后请关闭整个 gedit 程序,否则会占用终端): 1sudo apt-get install vim 安装软件时若需要确认,在提示处输入 y 即可。 通过命令行安装软件 安装SSH、配置SSH无密码登陆集群、单节点模式都需要用到 SSH 登陆(类似于远程登陆,你可以登录某台 Linux 主机,并且在上面运行命令),Ubuntu 默认已安装了 SSH client,此外还需要安装 SSH server: 1sudo apt-get install openssh-server 安装后,可以使用如下命令登陆本机: 1ssh localhost 此时会有如下提示(SSH首次登陆提示),输入 yes 。然后按提示输入密码 hadoop,这样就登陆到本机了。 SSH首次登陆提示 但这样登陆是需要每次输入密码的,我们需要配置成SSH无密码登陆比较方便。 首先退出刚才的 ssh,就回到了我们原先的终端窗口,然后利用 ssh-keygen 生成密钥,并将密钥加入到授权中: 12exit # 退出刚才的 ssh localhostcd ~/.ssh/ # 若没有该目录,请先执行一次ssh localhostssh-keygen -t rsa # 会有提示,都按回车就可以cat ./id_rsa.pub >> ./authorized_keys # 加入授权](javascript:void(0);) ~的含义 在 Linux 系统中,~ 代表的是用户的主文件夹,即 “/home/用户名” 这个目录,如你的用户名为 hadoop,则 ~ 就代表 “/home/hadoop/”。 此外,命令中的 # 后面的文字是注释。 此时再用 ssh localhost 命令,无需输入密码就可以直接登陆了,如下图所示。 SSH无密码登录 安装Java环境Java环境可选择 Oracle 的 JDK,或是 OpenJDK,按http://wiki.apache.org/hadoop/HadoopJavaVersions中说的,新版本在 OpenJDK 1.7 下是没问题的。为图方便,这边直接通过命令安装 OpenJDK 7。 12sudo apt-get install openjdk-7-jre openjdk-7-jdk](javascript:void(0);) JRE和JDK的区别 JRE(Java Runtime Environment,Java运行环境),是运行 Java 所需的环境。JDK(Java Development Kit,Java软件开发工具包)即包括 JRE,还包括开发 Java 程序所需的工具和类库。 安装好 OpenJDK 后,需要找到相应的安装路径,这个路径是用于配置 JAVA_HOME 环境变量的。执行如下命令: 1dpkg -L openjdk-7-jdk | grep '/bin/javac' 该命令会输出一个路径,除去路径末尾的 “/bin/javac”,剩下的就是正确的路径了。如输出路径为 /usr/lib/jvm/java-7-openjdk-amd64/bin/javac,则我们需要的路径为 /usr/lib/jvm/java-7-openjdk-amd64。 接着配置 JAVA_HOME 环境变量,为方便,我们在 ~/.bashrc 中进行设置(扩展阅读: 设置Linux环境变量的方法和区别): 1vim ~/.bashrc 在文件最前面添加如下单独一行(注意 = 号前后不能有空格),将“JDK安装路径”改为上述命令得到的路径,并保存: 1export JAVA_HOME=JDK安装路径 如下图所示(该文件原本可能不存在,内容为空,这不影响): 配置JAVA_HOME变量 接着还需要让该环境变量生效,执行如下代码: 1source ~/.bashrc # 使变量设置生效 设置好后我们来检验一下是否设置正确: 1echo $JAVA_HOME # 检验变量值java -version$JAVA_HOME/bin/java -version # 与直接执行 java -version 一样 如果设置正确的话,$JAVA_HOME/bin/java -version 会输出 java 的版本信息,且和 java -version 的输出结果一样,如下图所示: 成功配置JAVA_HOME变量 这样,Hadoop 所需的 Java 运行环境就安装好了。 安装 Hadoop 2Hadoop 2 可以通过 http://mirror.bit.edu.cn/apache/hadoop/common/ 或者 http://mirrors.cnnic.cn/apache/hadoop/common/ 下载,一般选择下载最新的稳定版本,即下载 “stable” 下的 hadoop-2.x.y.tar.gz 这个格式的文件,这是编译好的,另一个包含 src 的则是 Hadoop 源代码,需要进行编译才可使用。 下载时强烈建议也下载 hadoop-2.x.y.tar.gz.mds 这个文件,该文件包含了检验值可用于检查 hadoop-2.x.y.tar.gz 的完整性,否则若文件发生了损坏或下载不完整,Hadoop 将无法正常运行。 本文涉及的文件均通过浏览器下载,默认保存在 “下载” 目录中(若不是请自行更改 tar 命令的相应目录)。另外,本教程选择的是 2.6.0 版本,如果你用的不是 2.6.0 版本,则将所有命令中出现的 2.6.0 更改为你所使用的版本。 1cat ~/下载/hadoop-2.6.0.tar.gz.mds | grep 'MD5' # 列出md5检验值# head -n 6 ~/下载/hadoop-2.7.1.tar.gz.mds # 2.7.1版本格式变了,可以用这种方式输出md5sum ~/下载/hadoop-2.6.0.tar.gz | tr "a-z" "A-Z" # 计算md5值,并转化为大写,方便比较 若文件不完整则这两个值一般差别很大,可以简单对比下前几个字符跟后几个字符是否相等即可,如下图所示,如果两个值不一样,请务必重新下载。 检验文件完整性 我们选择将 Hadoop 安装至 /usr/local/ 中: 1sudo tar -zxf ~/下载/hadoop-2.6.0.tar.gz -C /usr/local # 解压到/usr/local中cd /usr/local/sudo mv ./hadoop-2.6.0/ ./hadoop # 将文件夹名改为hadoopsudo chown -R hadoop ./hadoop # 修改文件权限 Hadoop 解压后即可使用。输入如下命令来检查 Hadoop 是否可用,成功则会显示 Hadoop 版本信息: 1cd /usr/local/hadoop./bin/hadoop version 相对路径与绝对路径的区别 请务必注意命令中的相对路径与绝对路径,本文后续出现的 ./bin/...,./etc/... 等包含 ./ 的路径,均为相对路径,以 /usr/local/hadoop 为当前目录。例如在 /usr/local/hadoop 目录中执行 ./bin/hadoop version 等同于执行 /usr/local/hadoop/bin/hadoop version。可以将相对路径改成绝对路径来执行,但如果你是在主文件夹 ~ 中执行 ./bin/hadoop version,执行的会是 /home/hadoop/bin/hadoop version,就不是我们所想要的了。 Hadoop单机配置(非分布式)Hadoop 默认模式为非分布式模式,无需进行其他配置即可运行。非分布式即单 Java 进程,方便进行调试。 现在我们可以执行例子来感受下 Hadoop 的运行。Hadoop 附带了丰富的例子(运行 ./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0.jar 可以看到所有例子),包括 wordcount、terasort、join、grep 等。 在此我们选择运行 grep 例子,我们将 input 文件夹中的所有文件作为输入,筛选当中符合正则表达式 dfs[a-z.]+ 的单词并统计出现的次数,最后输出结果到 output 文件夹中。 1cd /usr/local/hadoopmkdir ./inputcp ./etc/hadoop/*.xml ./input # 将配置文件作为输入文件./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep ./input ./output 'dfs[a-z.]+'cat ./output/* # 查看运行结果 执行成功后如下所示,输出了作业的相关信息,输出的结果是符合正则的单词 dfsadmin 出现了1次 Hadoop单机模式运行grep的输出结果 注意,Hadoop 默认不会覆盖结果文件,因此再次运行上面实例会提示出错,需要先将 ./output 删除。 1rm -r ./output Hadoop伪分布式配置Hadoop 可以在单节点上以伪分布式的方式运行,Hadoop 进程以分离的 Java 进程来运行,节点既作为 NameNode 也作为 DataNode,同时,读取的是 HDFS 中的文件。 Hadoop 的配置文件位于 /usr/local/hadoop/etc/hadoop/ 中,伪分布式需要修改2个配置文件 core-site.xml 和 hdfs-site.xml 。Hadoop的配置文件是 xml 格式,每个配置以声明 property 的 name 和 value 的方式来实现。 修改配置文件 core-site.xml (通过 gedit 编辑会比较方便: gedit ./etc/hadoop/core-site.xml),将当中的 1<configuration></configuration> XML 修改为下面配置: 1<configuration> <property> <name>hadoop.tmp.dir</name> <value>file:/usr/local/hadoop/tmp</value> <description>Abase for other temporary directories.</description> </property> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property></configuration> XML 同样的,修改配置文件 hdfs-site.xml: 1<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:/usr/local/hadoop/tmp/dfs/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:/usr/local/hadoop/tmp/dfs/data</value> </property></configuration> XML Hadoop配置文件说明 Hadoop 的运行方式是由配置文件决定的(运行 Hadoop 时会读取配置文件),因此如果需要从伪分布式模式切换回非分布式模式,需要删除 core-site.xml 中的配置项。 此外,伪分布式虽然只需要配置 fs.defaultFS 和 dfs.replication 就可以运行(官方教程如此),不过若没有配置 hadoop.tmp.dir 参数,则默认使用的临时目录为 /tmp/hadoo-hadoop,而这个目录在重启时有可能被系统清理掉,导致必须重新执行 format 才行。所以我们进行了设置,同时也指定 dfs.namenode.name.dir 和 dfs.datanode.data.dir,否则在接下来的步骤中可能会出错。 配置完成后,执行 NameNode 的格式化: 1./bin/hdfs namenode -format 成功的话,会看到 “successfully formatted” 和 “Exitting with status 0” 的提示,若为 “Exitting with status 1” 则是出错。 执行namenode格式化 如果在这一步时提示 Error: JAVA_HOME is not set and could not be found. 的错误,则说明之前设置 JAVA_HOME 环境变量那边就没设置好,请按教程先设置好 JAVA_HOME 变量,否则后面的过程都是进行不下去的。 接着开启 NameNode 和 DataNode 守护进程。 1./sbin/start-dfs.sh 若出现如下SSH提示,输入yes即可。 启动Hadoop时的SSH提示 启动时可能会出现如下 WARN 提示:WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable。该 WARN 提示可以忽略,并不会影响正常使用(该 WARN 可以通过编译 Hadoop 源码解决)。 启动 Hadoop 时提示 Could not resolve hostname 如果启动 Hadoop 时遇到输出非常多“ssh: Could not resolve hostname xxx”的异常情况,如下图所示: 启动Hadoop时的异常提示 这个并不是 ssh 的问题,可通过设置 Hadoop 环境变量来解决。首先按键盘的 ctrl + c 中断启动,然后在 ~/.bashrc 中,增加如下两行内容(设置过程与 JAVA_HOME 变量一样,其中 HADOOP_HOME 为 Hadoop 的安装目录): 1export HADOOP_HOME=/usr/local/hadoopexport HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native Shell 保存后,务必执行 source ~/.bashrc 使变量设置生效,然后再次执行 ./sbin/start-dfs.sh 启动 Hadoop。 启动完成后,可以通过命令 jps 来判断是否成功启动,若成功启动则会列出如下进程: “NameNode”、”DataNode” 和 “SecondaryNameNode”(如果 SecondaryNameNode 没有启动,请运行 sbin/stop-dfs.sh 关闭进程,然后再次尝试启动尝试)。如果没有 NameNode 或 DataNode ,那就是配置不成功,请仔细检查之前步骤,或通过查看启动日志排查原因。 通过jps查看启动的Hadoop进程 Hadoop无法正常启动的解决方法 一般可以查看启动日志来排查原因,注意几点: 启动时会提示形如 “DBLab-XMU: starting namenode, logging to /usr/local/hadoop/logs/hadoop-hadoop-namenode-DBLab-XMU.out”,其中 DBLab-XMU 对应你的机器名,但其实启动日志信息是记录在 /usr/local/hadoop/logs/hadoop-hadoop-namenode-DBLab-XMU.log 中,所以应该查看这个后缀为 .log 的文件; 每一次的启动日志都是追加在日志文件之后,所以得拉到最后面看,对比下记录的时间就知道了。 一般出错的提示在最后面,通常是写着 Fatal、Error、Warning 或者 Java Exception 的地方。 可以在网上搜索一下出错信息,看能否找到一些相关的解决方法。 此外,若是 DataNode 没有启动,可尝试如下的方法(注意这会删除 HDFS 中原有的所有数据,如果原有的数据很重要请不要这样做): 1# 针对 DataNode 没法启动的解决方法./sbin/stop-dfs.sh # 关闭rm -r ./tmp # 删除 tmp 文件,注意这会删除 HDFS 中原有的所有数据./bin/hdfs namenode -format # 重新格式化 NameNode./sbin/start-dfs.sh # 重启 成功启动后,可以访问 Web 界面 http://localhost:50070 查看 NameNode 和 Datanode 信息,还可以在线查看 HDFS 中的文件。 Hadoop的Web界面 运行Hadoop伪分布式实例上面的单机模式,grep 例子读取的是本地数据,伪分布式读取的则是 HDFS 上的数据。要使用 HDFS,首先需要在 HDFS 中创建用户目录: 1./bin/hdfs dfs -mkdir -p /user/hadoop 接着将 ./etc/hadoop 中的 xml 文件作为输入文件复制到分布式文件系统中,即将 /usr/local/hadoop/etc/hadoop 复制到分布式文件系统中的 /user/hadoop/input 中。我们使用的是 hadoop 用户,并且已创建相应的用户目录 /user/hadoop ,因此在命令中就可以使用相对路径如 input,其对应的绝对路径就是 /user/hadoop/input: 1./bin/hdfs dfs -mkdir input./bin/hdfs dfs -put ./etc/hadoop/*.xml input 复制完成后,可以通过如下命令查看文件列表: 1./bin/hdfs dfs -ls input 伪分布式运行 MapReduce 作业的方式跟单机模式相同,区别在于伪分布式读取的是HDFS中的文件(可以将单机步骤中创建的本地 input 文件夹,输出结果 output 文件夹都删掉来验证这一点)。 1./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar grep input output 'dfs[a-z.]+' 查看运行结果的命令(查看的是位于 HDFS 中的输出结果): 1./bin/hdfs dfs -cat output/* 结果如下,注意到刚才我们已经更改了配置文件,所以运行结果不同。 Hadoop伪分布式运行grep结果 我们也可以将运行结果取回到本地: 1rm -r ./output # 先删除本地的 output 文件夹(如果存在)./bin/hdfs dfs -get output ./output # 将 HDFS 上的 output 文件夹拷贝到本机cat ./output/* Hadoop 运行程序时,输出目录不能存在,否则会提示错误 “org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://localhost:9000/user/hadoop/output already exists” ,因此若要再次执行,需要执行如下命令删除 output 文件夹: 1./bin/hdfs dfs -rm -r output # 删除 output 文件夹 运行程序时,输出目录不能存在 运行 Hadoop 程序时,为了防止覆盖结果,程序指定的输出目录(如 output)不能存在,否则会提示错误,因此运行前需要先删除输出目录。在实际开发应用程序时,可考虑在程序中加上如下代码,能在每次运行时自动删除输出目录,避免繁琐的命令行操作: 1Configuration conf = new Configuration();Job job = new Job(conf); /* 删除输出目录 */Path outputPath = new Path(args[1]);outputPath.getFileSystem(conf).delete(outputPath, true); Java 若要关闭 Hadoop,则运行 1./sbin/stop-dfs.sh 注意 下次启动 hadoop 时,无需进行 NameNode 的初始化,只需要运行 ./sbin/start-dfs.sh 就可以! 启动YARN(伪分布式不启动 YARN 也可以,一般不会影响程序执行) 有的读者可能会疑惑,怎么启动 Hadoop 后,见不到书上所说的 JobTracker 和 TaskTracker,这是因为新版的 Hadoop 使用了新的 MapReduce 框架(MapReduce V2,也称为 YARN,Yet Another Resource Negotiator)。 YARN 是从 MapReduce 中分离出来的,负责资源管理与任务调度。YARN 运行于 MapReduce 之上,提供了高可用性、高扩展性,YARN 的更多介绍在此不展开,有兴趣的可查阅相关资料。 上述通过 ./sbin/start-dfs.sh 启动 Hadoop,仅仅是启动了 MapReduce 环境,我们可以启动 YARN ,让 YARN 来负责资源管理与任务调度。 首先修改配置文件 mapred-site.xml,这边需要先进行重命名: 1mv ./etc/hadoop/mapred-site.xml.template ./etc/hadoop/mapred-site.xml 然后再进行编辑,同样使用 gedit 编辑会比较方便些 gedit ./etc/hadoop/mapred-site.xml : 1<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration> XML 接着修改配置文件 yarn-site.xml: 1<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property></configuration> XML 然后就可以启动 YARN 了(需要先执行过 ./sbin/start-dfs.sh): 1./sbin/start-yarn.sh # 启动YARN./sbin/mr-jobhistory-daemon.sh start historyserver # 开启历史服务器,才能在Web中查看任务运行情况 开启后通过 jps 查看,可以看到多了 NodeManager 和 ResourceManager 两个后台进程,如下图所示。 开启YARN 启动 YARN 之后,运行实例的方法还是一样的,仅仅是资源管理方式、任务调度不同。观察日志信息可以发现,不启用 YARN 时,是 “mapred.LocalJobRunner” 在跑任务,启用 YARN 之后,是 “mapred.YARNRunner” 在跑任务。启动 YARN 有个好处是可以通过 Web 界面查看任务的运行情况:http://localhost:8088/cluster,如下图所示。 开启YARN后可以查看任务运行信息 但 YARN 主要是为集群提供更好的资源管理与任务调度,然而这在单机上体现不出价值,反而会使程序跑得稍慢些。因此在单机上是否开启 YARN 就看实际情况了。 不启动 YARN 需重命名 mapred-site.xml 如果不想启动 YARN,务必把配置文件 mapred-site.xml 重命名,改成 mapred-site.xml.template,需要用时改回来就行。否则在该配置文件存在,而未开启 YARN 的情况下,运行程序会提示 “Retrying connect to server: 0.0.0.0/0.0.0.0:8032” 的错误,这也是为何该配置文件初始文件名为 mapred-site.xml.template。 同样的,关闭 YARN 的脚本如下: 1./sbin/stop-yarn.sh./sbin/mr-jobhistory-daemon.sh stop historyserver 自此,你已经掌握 Hadoop 的配置和基本使用了。 附加教程: 配置PATH环境变量在这里额外讲一下 PATH 这个环境变量(可执行 echo $PATH 查看,当中包含了多个目录)。例如我们在主文件夹 ~ 中执行 ls 这个命令时,实际执行的是 /bin/ls 这个程序,而不是 ~/ls 这个程序。系统是根据 PATH 这个环境变量中包含的目录位置,逐一进行查找,直至在这些目录位置下找到匹配的程序(若没有匹配的则提示该命令不存在)。 上面的教程中,我们都是先进入到 /usr/local/hadoop 目录中,再执行 sbin/hadoop,实际上等同于运行 /usr/local/hadoop/sbin/hadoop。我们可以将 Hadoop 命令的相关目录加入到 PATH 环境变量中,这样就可以直接通过 start-dfs.sh 开启 Hadoop,也可以直接通过 hdfs 访问 HDFS 的内容,方便平时的操作。 同样我们选择在 ~/.bashrc 中进行设置(vim ~/.bashrc,与 JAVA_HOME 的设置相似),在文件最前面加入如下单独一行: 1export PATH=$PATH:/usr/local/hadoop/sbin:/usr/local/hadoop/bin 添加后执行 source ~/.bashrc 使设置生效,生效后,在任意目录中,都可以直接使用 hdfs 等命令了,读者不妨现在就执行 hdfs dfs -ls input 查看 HDFS 文件试试看。 安装Hadoop集群在平时的学习中,我们使用伪分布式就足够了。如果需要安装 Hadoop 集群,请查看Hadoop集群安装配置教程。 相关教程 使用Eclipse编译运行MapReduce程序: 使用 Eclipse 可以方便的开发、运行 MapReduce 程序,还可以直接管理 HDFS 中的文件。 使用命令行编译打包运行自己的MapReduce程序: 有时候需要直接通过命令来编译、打包 MapReduce 程序。 参考资料 http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/SingleCluster.html http://www.cnblogs.com/xia520pi/archive/2012/05/16/2503949.html http://www.micmiu.com/bigdata/hadoop/hadoop-2x-ubuntu-build/]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Hadoop</tag>
<tag>Ubuntu14</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用Eclipse编译运行MapReduce程序 Hadoop2.6.0_Ubuntu/CentOS]]></title>
<url>%2F2017%2F06%2F15%2F%E4%BD%BF%E7%94%A8eclipse%E8%BF%90%E8%A1%8CMapReduce%2F</url>
<content type="text"><![CDATA[本教程介绍的是如何在 Ubuntu/CentOS 中使用 Eclipse 来开发 MapReduce 程序,在 Hadoop 2.6.0 下验证通过。虽然我们可以使用命令行编译打包运行自己的MapReduce程序,但毕竟编写代码不方便。使用 Eclipse,我们可以直接对 HDFS 中的文件进行操作,可以直接运行代码,省去许多繁琐的命令。本教程由厦门大学数据库实验室出品,转载请注明。 环境本教程在 Hadoop 2.6.0 下验证通过,适用于 Ubuntu/CentOS 系统,理论上可用于任何原生 Hadoop 2 版本,如 Hadoop 2.4.1,Hadoop 2.7.1。 本教程主要测试环境: Ubuntu 14.04 Hadoop 2.6.0(伪分布式) Eclipse 3.8 此外,本教材在 CentOS 6.4 系统中也验证通过,对 Ubuntu 与 CentOS 的不同配置之处有作出了注明。 安装 Eclipse在 Ubuntu 和 CentOS 中安装 Eclipse 的方式有所不同,但之后的配置和使用是一样的。 在 Ubuntu 中安装 Eclipse,可从 Ubuntu 的软件中心直接搜索安装,在桌面左侧任务栏,点击“Ubuntu软件中心”。 Ubuntu软件中心 在右上角搜索栏中搜索 eclipse,在搜索结果中单击 eclipse,并点击安装。 安装Eclipse 等待安装完成即可,Eclipse 的默认安装目录为:/usr/lib/eclipse。 在 CentOS 中安装 Eclipse,需要下载安装程序,我们选择 Eclipse IDE for Java Developers 版: 32位: http://eclipse.bluemix.net/packages/mars.1/?JAVA-LINUX32 64位: http://eclipse.bluemix.net/packages/mars.1/?JAVA-LINUX64 下载后执行如下命令,将 Eclipse 安装至 /usr/lib 目录中: 1sudo tar -zxf ~/下载/eclipse-java-mars-1-linux-gtk*.tar.gz -C /usr/lib Shell 命令**;)**;) 解压后即可使用。在 CentOS 中可以为程序创建桌面快捷方式,如下图所示,点击桌面右键,选择创建启动器,填写名称和程序位置(/usr/lib/eclipse/eclipse): 安装Eclipse 安装 Hadoop-Eclipse-Plugin要在 Eclipse 上编译和运行 MapReduce 程序,需要安装 hadoop-eclipse-plugin,可下载 Github 上的 hadoop2x-eclipse-plugin(备用下载地址:http://pan.baidu.com/s/1i4ikIoP)。 下载后,将 release 中的 hadoop-eclipse-kepler-plugin-2.6.0.jar (还提供了 2.2.0 和 2.4.1 版本)复制到 Eclipse 安装目录的 plugins 文件夹中,运行 eclipse -clean 重启 Eclipse 即可(添加插件后只需要运行一次该命令,以后按照正常方式启动就行了)。 1unzip -qo ~/下载/hadoop2x-eclipse-plugin-master.zip -d ~/下载 # 解压到 ~/下载 中sudo cp ~/下载/hadoop2x-eclipse-plugin-master/release/hadoop-eclipse-plugin-2.6.0.jar /usr/lib/eclipse/plugins/ # 复制到 eclipse 安装目录的 plugins 目录下/usr/lib/eclipse/eclipse -clean # 添加插件后需要用这种方式使插件生效 Shell 命令**;)**;) 配置 Hadoop-Eclipse-Plugin在继续配置前请确保已经开启了 Hadoop。 启动 Eclipse 后就可以在左侧的Project Explorer中看到 DFS Locations(若看到的是 welcome 界面,点击左上角的 x 关闭就可以看到了。CentOS 需要切换 Perspective 后才能看到,即接下来配置步骤的第二步)。 安装好Hadoop-Eclipse-Plugin插件后的效果 插件需要进一步的配置。 第一步:选择 Window 菜单下的 Preference。 打开Preference 此时会弹出一个窗体,窗体的左侧会多出 Hadoop Map/Reduce 选项,点击此选项,选择 Hadoop 的安装目录(如/usr/local/hadoop,Ubuntu不好选择目录,直接输入就行)。 选择 Hadoop 的安装目录 第二步:切换 Map/Reduce 开发视图,选择 Window 菜单下选择 Open Perspective -> Other(CentOS 是 Window -> Perspective -> Open Perspective -> Other),弹出一个窗体,从中选择 Map/Reduce 选项即可进行切换。 切换 Map/Reduce 开发视图 第三步:建立与 Hadoop 集群的连接,点击 Eclipse软件右下角的 Map/Reduce Locations 面板,在面板中单击右键,选择 New Hadoop Location。 建立与 Hadoop 集群的连接 在弹出来的 General 选项面板中,General 的设置要与 Hadoop 的配置一致。一般两个 Host 值是一样的,如果是伪分布式,填写 localhost 即可,另外我使用的Hadoop伪分布式配置,设置 fs.defaultFS 为 hdfs://localhost:9000,则 DFS Master 的 Port 要改为 9000。Map/Reduce(V2) Master 的 Port 用默认的即可,Location Name 随意填写。 最后的设置如下图所示: Hadoop Location 的设置 Advanced parameters 选项面板是对 Hadoop 参数进行配置,实际上就是填写 Hadoop 的配置项(/usr/local/hadoop/etc/hadoop中的配置文件),如我配置了 hadoop.tmp.dir ,就要进行相应的修改。但修改起来会比较繁琐,我们可以通过复制配置文件的方式解决(下面会说到)。 总之,我们只要配置 General 就行了,点击 finish,Map/Reduce Location 就创建好了。 在 Eclipse 中操作 HDFS 中的文件配置好后,点击左侧 Project Explorer 中的 MapReduce Location (点击三角形展开)就能直接查看 HDFS 中的文件列表了(HDFS 中要有文件,如下图是 WordCount 的输出结果),双击可以查看内容,右键点击可以上传、下载、删除 HDFS 中的文件,无需再通过繁琐的 hdfs dfs -ls 等命令进行操作了。 使用Eclipse查看HDFS中的文件内容 如果无法查看,可右键点击 Location 尝试 Reconnect 或重启 Eclipse。 Tips HDFS 中的内容变动后,Eclipse 不会同步刷新,需要右键点击 Project Explorer中的 MapReduce Location,选择 Refresh,才能看到变动后的文件。 在 Eclipse 中创建 MapReduce 项目点击 File 菜单,选择 New -> Project…: 创建Project 选择 Map/Reduce Project,点击 Next。 创建MapReduce项目 填写 Project name 为 WordCount 即可,点击 Finish 就创建好了项目。 填写项目名 此时在左侧的 Project Explorer 就能看到刚才建立的项目了。 项目创建完成 接着右键点击刚创建的 WordCount 项目,选择 New -> Class 新建Class 需要填写两个地方:在 Package 处填写 org.apache.hadoop.examples;在 Name 处填写 WordCount。 填写Class信息 创建 Class 完成后,在 Project 的 src 中就能看到 WordCount.java 这个文件。将如下 WordCount 的代码复制到该文件中。 1package org.apache.hadoop.examples; import java.io.IOException;import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.Job;import org.apache.hadoop.mapreduce.Mapper;import org.apache.hadoop.mapreduce.Reducer;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import org.apache.hadoop.util.GenericOptionsParser; public class WordCount { public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{ private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context ) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } } public static class IntSumReducer extends Reducer<Text,IntWritable,Text,IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context ) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); if (otherArgs.length != 2) { System.err.println("Usage: wordcount <in> <out>"); System.exit(2); } Job job = new Job(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(otherArgs[0])); FileOutputFormat.setOutputPath(job, new Path(otherArgs[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); }} Java**;)**;) 通过 Eclipse 运行 MapReduce在运行 MapReduce 程序前,还需要执行一项重要操作(也就是上面提到的通过复制配置文件解决参数设置问题):将 /usr/local/hadoop/etc/hadoop 中将有修改过的配置文件(如伪分布式需要 core-site.xml 和 hdfs-site.xml),以及 log4j.properties 复制到 WordCount 项目下的 src 文件夹(~/workspace/WordCount/src)中: 1cp /usr/local/hadoop/etc/hadoop/core-site.xml ~/workspace/WordCount/srccp /usr/local/hadoop/etc/hadoop/hdfs-site.xml ~/workspace/WordCount/srccp /usr/local/hadoop/etc/hadoop/log4j.properties ~/workspace/WordCount/src Shell 命令**;)**;) 没有复制这些文件的话程序将无法正确运行,本教程最后再解释为什么需要复制这些文件。 复制完成后,务必右键点击 WordCount 选择 refresh 进行刷新(不会自动刷新,需要手动刷新),可以看到文件结构如下所示: WordCount项目文件结构 点击工具栏中的 Run 图标,或者右键点击 Project Explorer 中的 WordCount.java,选择 Run As -> Run on Hadoop,就可以运行 MapReduce 程序了。不过由于没有指定参数,运行时会提示 “Usage: wordcount “,需要通过Eclipse设定一下运行参数。 右键点击刚创建的 WordCount.java,选择 Run As -> Run Configurations,在此处可以设置运行时的相关参数(如果 Java Application 下面没有 WordCount,那么需要先双击 Java Application)。切换到 “Arguments” 栏,在 Program arguments 处填写 “input output” 就可以了。 设置程序运行参数 或者也可以直接在代码中设置好输入参数。可将代码 main() 函数的 String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); 改为: 1// String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();String[] otherArgs=new String[]{"input","output"}; /* 直接设置输入参数 */ Java**;)**;) 设定参数后,再次运行程序,可以看到运行成功的提示,刷新 DFS Location 后也能看到输出的 output 文件夹。 WordCount 运行结果 至此,你就可以使用 Eclipse 方便的进行 MapReduce程序的开发了。 在 Eclipse 中运行 MapReduce 程序会遇到的问题在使用 Eclipse 运行 MapReduce 程序时,会读取 Hadoop-Eclipse-Plugin 的 Advanced parameters 作为 Hadoop 运行参数,如果我们未进行修改,则默认的参数其实就是单机(非分布式)参数,因此程序运行时是读取本地目录而不是 HDFS 目录,就会提示 Input 路径不存在。 1Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: file:/home/hadoop/workspace/WordCountProject/input 所以我们要么修改插件参数,要么将配置文件复制到项目中的 src 目录来覆盖参数,才能让程序能够正确运行。 此外,log4j 用于记录程序的输出日记,需要 log4j.properties 这个配置文件,如果没有复制该文件到项目中,运行程序后在 Console 面板中会出现警告提示: 123log4j:WARN No appenders could be found for logger (org.apache.hadoop.metrics2.lib.MutableMetricsFactory).log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 虽然不影响程序的正确运行的,但程序运行时无法看到任何提示消息(只能看到出错信息)。 参考资料 http://www.cnblogs.com/xia520pi/archive/2012/05/20/2510723.html http://www.blogjava.net/LittleRain/archive/2006/12/31/91165.html]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDFS学习]]></title>
<url>%2F2017%2F06%2F12%2FHDFS%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[HDFS前言 设计思想 分而治之:将大文件、大批量文件,分布式存放在大量服务器上,以便于采取分而治之的方式对海量数据进行运算分析; 在大数据系统中作用: 为各类分布式运算框架(如:mapreduce,spark,tez,……)提供数据存储服务 重点概念:文件切块,副本存放,元数据 HDFS的概念和特性 首先,它是一个文件系统,用于存储文件,通过统一的命名空间——目录树来定位文件 其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色; 重要特性如下: HDFS中的文件在物理上是分块存储(block),块的大小可以通过配置参数( dfs.blocksize)来规定,默认大小在hadoop2.x版本中是128M,老版本中是64M HDFS文件系统会给客户端提供一个统一的抽象目录树,客户端通过路径来访问文件,形如:hdfs://namenode:port/dir-a/dir-b/dir-c/file.data 目录结构及文件分块信息(元数据)的管理由namenode节点承担——namenode是HDFS集群主节点,负责维护整个hdfs文件系统的目录树,以及每一个路径(文件)所对应的block块信息(block的id,及所在的datanode服务器) 文件的各个block的存储管理由datanode节点承担—- datanode是HDFS集群从节点,每一个block都可以在多个datanode上存储多个副本(副本数量也可以通过参数设置dfs.replication) HDFS是设计成适应一次写入,多次读出的场景,且不支持文件的修改 (注:适合用来做数据分析,并不适合用来做网盘应用,因为,不便修改,延迟大,网络开销大,成本太高) HDFS的shell(命令行客户端)操作HDFS命令行客户端使用操作 hadoop fs -ls / 常用命令参数介绍 命令 功能 示例 -help 输出这个命令参数手册 -ls 显示目录信息 hadoop fs -ls / -mkdir 在hdfs上创建目录 hadoop fs -mkdir -p /aaa/bbb/cc/dd -moveFromLocal 从本地剪切粘贴到hdfs hadoop fs -moveFromLocal /home/hadoop/a.txt /aaa/bbb/cc/dd -moveToLocal 从hdfs剪切粘贴到本地 hadoop fs -moveToLocal /aaa/bbb/cc/dd /home/hadoop/a.txt -appendToFile 追加一个文件到已经存在的文件末尾 Hadoop fs -appendToFile ./hello.txt /hello.txt -cat 显示文件内容 hadoop fs -cat /hello.txt -tail 显示一个文件的末尾 hadoop fs -tail /weblog/access_log.1 -text 以字符形式打印一个文件的内容 hadoop fs -text /weblog/access_log.1 -chgrp linux文件系统中的用法一样,对文件所属权限 -chmod linux文件系统中的用法一样,对文件所属权限 hadoop fs -chmod 666 /hello.txt -chown linux文件系统中的用法一样,对文件所属权限 hadoop fs -chown someuser:somegrp /hello.txt -copyFromLocal 从本地文件系统中拷贝文件到hdfs路径去 hadoop fs -copyFromLocal ./jdk.tar.gz /aaa/ -copyToLocal 从hdfs拷贝到本地 hadoop fs -copyToLocal /aaa/jdk.tar.gz -cp 从hdfs的一个路径拷贝hdfs的另一个路径 hadoop fs -cp /aaa/jdk.tar.gz /bbb/jdk.tar.gz.2 -mv 在hdfs目录中移动文件 hadoop fs -mv /aaa/jdk.tar.gz / -get 等同于copyToLocal,就是从hdfs下载文件到本地 hadoop fs -get /aaa/jdk.tar.gz -getmerge 合并下载多个文件 hadoop fs -getmerge /aaa/log.* ./log.sum -put 等同于copyFromLocal hadoop fs -put /aaa/jdk.tar.gz /bbb/jdk.tar.gz.2 -rm 删除文件或文件夹 hadoop fs -rm -r /aaa/bbb/ -rmdir 删除空目录 hadoop fs -rmdir /aaa/bbb/ccc -df 统计文件系统的可用空间信息 hadoop fs -df -h / -du 统计文件夹的大小信息 hadoop fs -du -s -h /aaa/* -count 统计一个指定目录下的文件节点数量 hadoop fs -count /aaa/ -setrep 设置hdfs中文件的副本数量 `hadoop fs -setrep 3 /aaa/jdk.tar.gz HDFS原理篇概述 HDFS集群分为两大角色:NameNode、DataNode (Secondary Namenode) NameNode负责管理整个文件系统的元数据 DataNode 负责管理用户的文件数据块 文件会按照固定的大小(blocksize)切成若干块后分布式存储在若干台datanode上 每一个文件块可以有多个副本,并存放在不同的datanode上 Datanode会定期向Namenode汇报自身所保存的文件block信息,而namenode则会负责保持文件的副本数量 HDFS的内部工作机制对客户端保持透明,客户端请求访问HDFS都是通过向namenode申请来进行 HDFS写数据流程 客户端要向HDFS写数据,首先要跟namenode通信以确认可以写文件并获得接收文件block的datanode,然后,客户端按顺序将文件逐个block传递给相应datanode,并由接收到block的datanode负责向其他datanode复制block的副本 详细步骤图 详细步骤解析 根namenode通信请求上传文件,namenode检查目标文件是否已存在,父目录是否存在 namenode返回是否可以上传 client请求第一个 block该传输到哪些datanode服务器上 namenode返回3个datanode服务器ABC client请求3台dn中的一台A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会继续调用B,然后B调用C,将真个pipeline建立完成,逐级返回客户端 client开始往A上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,A收到一个packet就会传给B,B传给C;A每传一个packet会放入一个应答队列等待应答 当一个block传输完成之后,client再次请求namenode上传第二个block的服务器。 HDFS读数据流程 客户端将要读取的文件路径发送给namenode,namenode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应datanode逐个获取文件的block并在客户端本地进行数据追加合并从而获得整个文件 详细步骤图 详细步骤解析 跟namenode通信查询元数据,找到文件块所在的datanode服务器 挑选一台datanode(就近原则,然后随机)服务器,请求建立socket流 datanode开始发送数据(从磁盘里面读取数据放入流,以packet为单位来做校验) 客户端以packet为单位接收,现在本地缓存,然后写入目标文件 NAMENODE工作机制问题场景: 集群启动后,可以查看文件,但是上传文件时报错,打开web页面可看到namenode正处于safemode状态,怎么处理? Namenode服务器的磁盘故障导致namenode宕机,如何挽救集群及数据? Namenode是否可以有多个?namenode内存要配置多大?namenode跟集群数据存储能力有关系吗? 文件的blocksize究竟调大好还是调小好? NAMENODE职责 NAMENODE职责: 负责客户端请求的响应 元数据的管理(查询,修改) 元数据管理 namenode对数据的管理采用了三种存储形式: 内存元数据(NameSystem) 磁盘元数据镜像文件 数据操作日志文件(可通过日志运算出元数据) 元数据存储机制 内存中有一份完整的元数据(内存meta data) 磁盘有一个“准完整”的元数据镜像(fsimage)文件(在namenode的工作目录中) 用于衔接内存metadata和持久化元数据镜像fsimage之间的操作日志(edits文件)注:当客户端对hdfs中的文件进行新增或者修改操作,操作记录首先被记入edits日志文件中,当客户端操作成功后,相应的元数据会更新到内存meta.data中 元数据手动查看 可以通过hdfs的一个工具来查看edits中的信息 bin/hdfs oev -i edits -o edits.xml bin/hdfs oiv -i fsimage_0000000000000000087 -p XML -o fsimage.xml 元数据的checkpoint 每隔一段时间,会由secondary namenode将namenode上积累的所有edits和一个最新的fsimage下载到本地,并加载到内存进行merge(这个过程称为checkpoint) checkpoint的详细过程图 checkpoint操作的触发条件配置参数 dfs.namenode.checkpoint.check.period=60 #检查触发条件是否满足的频率,60秒 dfs.namenode.checkpoint.dir=file://${hadoop.tmp.dir}/dfs/namesecondary #以上两个参数做checkpoint操作时,secondary namenode的本地工作目录 dfs.namenode.checkpoint.edits.dir=${dfs.namenode.checkpoint.dir} dfs.namenode.checkpoint.max-retries=3 #最大重试次数dfs.namenode.checkpoint.period=3600 #两次checkpoint之间的时间间隔3600秒dfs.namenode.checkpoint.txns=1000000 #两次checkpoint之间最大的操作记录 checkpoint的附带作用 namenode和secondary namenode的工作目录存储结构完全相同,所以,当namenode故障退出需要重新恢复时,可以从secondary namenode的工作目录中将fsimage拷贝到namenode的工作目录,以恢复namenode的元数据 元数据目录说明 在第一次部署好Hadoop集群的时候,我们需要在NameNode(NN)节点上格式化磁盘\$HADOOP_HOME/bin/hdfs namenode -format格式化完成之后,将会在$dfs.namenode.name.dir/current目录下如下的文件结构: current/ |– VERSION |– edits_*_ _|– fsimage_0000000000008547077 |– fsimage_0000000000008547077.md5 |– seen_txid 其中的dfs.name.dir是在hdfs-site.xml文件中配置的,默认值如下: 1234<property> <name>dfs.name.dir</name> <value>file://${hadoop.tmp.dir}/dfs/name</value></property> hadoop.tmp.dir是在core-site.xml中配置的,默认值如下: 12345<property> <name>hadoop.tmp.dir</name> <value>/tmp/hadoop-${user.name}</value> <description>A base for other temporary directories.</description></property> dfs.namenode.name.dir属性可以配置多个目录 如:/data1/dfs/name,/data2/dfs/name,/data3/dfs/name,….。各个目录存储的文件结构和内容都完全一样,相当于备份,这样做的好处是当其中一个目录损坏了,也不会影响到Hadoop的元数据,特别是当其中一个目录是NFS(网络文件系统Network File System,NFS)之上,即使你这台机器损坏了,元数据也得到保存。 下面对$dfs.namenode.name.dir/current/目录下的文件进行解释。 VERSION文件是Java属性文件,内容大致如下: #Fri Nov 15 19:47:46 CST 2013 namespaceID=934548976clusterID=CID-cdff7d73-93cd-4783-9399-0a22e6dce196cTime=0storageType=NAME_NODEblockpoolID=BP-893790215-192.168.24.72-1383809616115layoutVersion=-47 namespaceID是文件系统的唯一标识符,在文件系统首次格式化之后生成的; storageType说明这个文件存储的是什么进程的数据结构信息(如果是DataNode,storageType=DATA_NODE); cTime表示NameNode存储时间的创建时间,由于我的NameNode没有更新过,所以这里的记录值为0,以后对NameNode升级之后,cTime将会记录更新时间戳; layoutVersion表示HDFS永久性数据结构的版本信息, 只要数据结构变更,版本号也要递减,此时的HDFS也需要升级,否则磁盘仍旧是使用旧版本的数据结构,这会导致新版本的NameNode无法使用; clusterID是系统生成或手动指定的集群ID,在-clusterid选项中可以使用它;如下说明 使用如下命令格式化一个Namenode: $HADOOP_HOME/bin/hdfs namenode -format [-clusterId <cluster_id>]选择一个唯一的cluster_id,并且这个cluster_id不能与环境中其他集群有冲突。如果没有提供cluster_id,则会自动生成一个唯一的ClusterID。 使用如下命令格式化其他Namenode $HADOOP_HOME/bin/hdfs namenode -format -clusterId <cluster_id> 升级集群至最新版本。在升级过程中需要提供一个ClusterID,例如: $HADOOP_CONF_DIR -upgrade -clusterId <cluster_ID>如果没有提供ClusterID,则会自动生成一个ClusterID。 blockpoolID:是针对每一个Namespace所对应的blockpool的ID,上面的这个BP-893790215-192.168.24.72-1383809616115就是在我的ns1的namespace下的存储块池的ID,这个ID包括了其对应的NameNode节点的ip地址。 \$dfs.namenode.name.dir/current/seen_txid非常重要,是存放transactionId的文件,format之后是0,它代表的是namenode里面的edits_*文件的尾数,namenode重启的时候,会按照seen_txid的数字,循序从头跑edits_0000001~到seen_txid的数字。所以当你的hdfs发生异常重启的时候,一定要比对seen_txid内的数字是不是你edits最后的尾数,不然会发生建置namenode时metaData的资料有缺少,导致误删Datanode上多余Block的资讯。 $dfs.namenode.name.dir/current目录下在format的同时也会生成fsimage和edits文件,及其对应的md5校验文件。 文件中记录的是edits滚动的序号,每次重启namenode时,namenode就知道要将哪些edits进行加载edits DATANODE的工作机制问题场景: 集群容量不够,怎么扩容? 如果有一些datanode宕机,该怎么办? datanode明明已启动,但是集群中的可用datanode列表中就是没有,怎么办? 概述Datanode工作职责 存储管理用户的文件块数据 定期向namenode汇报自身所持有的block信息(通过心跳信息上报) (这点很重要,因为,当集群中发生某些block副本失效时,集群如何恢复block初始副本数量的问题) 12345<property> <name>dfs.blockreport.intervalMsec</name> <value>3600000</value> <description>Determines block reporting interval in milliseconds.</description></property> #####Datanode掉线判断时限参数 datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。HDFS默认的超时时长为10分钟+30秒。如果定义超时时间为timeout,则超时时长的计算公式为: 123timeout = 2 * heartbeat.recheck.interval + 10 * dfs.heartbeat.interval。而默认的heartbeat.recheck.interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。所以,举个例子,如果heartbeat.recheck.interval设置为5000(毫秒),dfs.heartbeat.interval设置为3(秒,默认),则总的超时时间为40秒。 12345678<property> <name>heartbeat.recheck.interval</name> <value>2000</value></property><property> <name>dfs.heartbeat.interval</name> <value>1</value></property> #####观察验证DATANODE功能 上传一个文件,观察文件的block具体的物理存放情况: 在每一台datanode机器上的这个目录中能找到文件的切块: /home/hadoop/app/hadoop-2.4.1/tmp/dfs/data/current/BP-193442119-192.168.2.120-1432457733977/current/finalized HDFS应用开发篇HDFS的java操作hdfs在生产应用中主要是客户端的开发,其核心步骤是从hdfs提供的api中构造一个HDFS的访问客户端对象,然后通过该客户端对象操作(增删改查)HDFS上的文件 搭建开发环境 引入依赖 12345<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.6.4</version></dependency> 注:如需手动引入jar包,hdfs的jar包—-hadoop的安装目录的share下 window下开发的说明 建议在linux下进行hadoop应用的开发,不会存在兼容性问题。如在window上做客户端应用开发,需要设置以下环境:A. 在windows的某个目录下解压一个hadoop的安装包B. 将安装包下的lib和bin目录用对应windows版本平台编译的本地库替换C. 在window系统中配置HADOOP_HOME指向你解压的安装包D. 在windows系统的path变量中加入hadoop的bin目录 获取api中的客户端对象 在java中操作hdfs,首先要获得一个客户端实例 12Configuration conf = new Configuration()FileSystem fs = FileSystem.get(conf) 而我们的操作目标是HDFS,所以获取到的fs对象应该是DistributedFileSystem的实例; get方法是从何处判断具体实例化那种客户端类呢? 从conf中的一个参数 fs.defaultFS的配置值判断; 如果我们的代码中没有指定fs.defaultFS,并且工程classpath下也没有给定相应的配置,conf中的默认值就来自于hadoop的jar包中的core-default.xml,默认值为: file:///,则获取的将不是一个DistributedFileSystem的实例,而是一个本地文件系统的客户端对象 DistributedFileSystem实例对象所具备的方法 HDFS客户端操作数据代码示例文件的增删改查123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145import java.net.URI;import java.util.Iterator;import java.util.Map.Entry;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.BlockLocation;import org.apache.hadoop.fs.FileStatus;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.LocatedFileStatus;import org.apache.hadoop.fs.Path;import org.apache.hadoop.fs.RemoteIterator;import org.junit.Before;import org.junit.Test;/** * * 客户端去操作hdfs时,是有一个用户身份的 * 默认情况下,hdfs客户端api会从jvm中获取一个参数来作为自己的用户身份:-DHADOOP_USER_NAME=hadoop * * 也可以在构造客户端fs对象时,通过参数传递进去 * @author * */public class HdfsClientDemo { FileSystem fs = null; Configuration conf = null; @Before public void init() throws Exception{ conf = new Configuration();// conf.set("fs.defaultFS", "hdfs://mini1:9000"); conf.set("dfs.replication", "5"); //拿到一个文件系统操作的客户端实例对象 fs = FileSystem.get(conf); //可以直接传入 uri和用户身份 fs = FileSystem.get(new URI("hdfs://mini1:9000"),conf,"hadoop"); } /** * 上传文件 * @throws Exception */ @Test public void testUpload() throws Exception { fs.copyFromLocalFile(new Path("c:/access.log"), new Path("/access.log.copy")); fs.close(); } /** * 下载文件 * @throws Exception */ @Test public void testDownload() throws Exception { fs.copyToLocalFile(new Path("/access.log.copy"), new Path("d:/")); } /** * 打印参数 */ @Test public void testConf(){ Iterator<Entry<String, String>> it = conf.iterator(); while(it.hasNext()){ Entry<String, String> ent = it.next(); System.out.println(ent.getKey() + " : " + ent.getValue()); } } @Test public void testMkdir() throws Exception { boolean mkdirs = fs.mkdirs(new Path("/testmkdir/aaa/bbb")); System.out.println(mkdirs); } @Test public void testDelete() throws Exception { boolean flag = fs.delete(new Path("/testmkdir/aaa"), true); System.out.println(flag); } /** * 递归列出指定目录下所有子文件夹中的文件 * @throws Exception */ @Test public void testLs() throws Exception { RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true); while(listFiles.hasNext()){ LocatedFileStatus fileStatus = listFiles.next(); System.out.println("blocksize: " +fileStatus.getBlockSize()); System.out.println("owner: " +fileStatus.getOwner()); System.out.println("Replication: " +fileStatus.getReplication()); System.out.println("Permission: " +fileStatus.getPermission()); System.out.println("Name: " +fileStatus.getPath().getName()); System.out.println("------------------"); BlockLocation[] blockLocations = fileStatus.getBlockLocations(); for(BlockLocation b:blockLocations){ System.out.println("块起始偏移量: " +b.getOffset()); System.out.println("块长度:" + b.getLength()); //块所在的datanode节点 String[] datanodes = b.getHosts(); for(String dn:datanodes){ System.out.println("datanode:" + dn); } } } } @Test public void testLs2() throws Exception { FileStatus[] listStatus = fs.listStatus(new Path("/")); for(FileStatus file :listStatus){ System.out.println("name: " + file.getPath().getName()); System.out.println((file.isFile()?"file":"directory")); } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://mini1:9000"); //拿到一个文件系统操作的客户端实例对象 FileSystem fs = FileSystem.get(conf); fs.copyFromLocalFile(new Path("c:/access.log"), new Path("/access.log.copy")); fs.close(); } } 通过流的方式访问hdfs12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485/** * 相对那些封装好的方法而言的更底层一些的操作方式 * 上层那些mapreduce spark等运算框架,去hdfs中获取数据的时候,就是调的这种底层的api * @author * */public class StreamAccess { FileSystem fs = null; @Before public void init() throws Exception { Configuration conf = new Configuration(); fs = FileSystem.get(new URI("hdfs://hdp-node01:9000"), conf, "hadoop"); } /** * 通过流的方式上传文件到hdfs * @throws Exception */ @Test public void testUpload() throws Exception { FSDataOutputStream outputStream = fs.create(new Path("/angelababy.love"), true); FileInputStream inputStream = new FileInputStream("c:/angelababy.love"); IOUtils.copy(inputStream, outputStream); } @Test public void testDownLoadFileToLocal() throws IllegalArgumentException, IOException{ //先获取一个文件的输入流----针对hdfs上的 FSDataInputStream in = fs.open(new Path("/jdk-7u65-linux-i586.tar.gz")); //再构造一个文件的输出流----针对本地的 FileOutputStream out = new FileOutputStream(new File("c:/jdk.tar.gz")); //再将输入流中数据传输到输出流 IOUtils.copyBytes(in, out, 4096); } /** * hdfs支持随机定位进行文件读取,而且可以方便地读取指定长度 * 用于上层分布式运算框架并发处理数据 * @throws IllegalArgumentException * @throws IOException */ @Test public void testRandomAccess() throws IllegalArgumentException, IOException{ //先获取一个文件的输入流----针对hdfs上的 FSDataInputStream in = fs.open(new Path("/iloveyou.txt")); //可以将流的起始偏移量进行自定义 in.seek(22); //再构造一个文件的输出流----针对本地的 FileOutputStream out = new FileOutputStream(new File("c:/iloveyou.line.2.txt")); IOUtils.copyBytes(in,out,19L,true); } /** * 显示hdfs上文件的内容 * @throws IOException * @throws IllegalArgumentException */ @Test public void testCat() throws IllegalArgumentException, IOException{ FSDataInputStream in = fs.open(new Path("/iloveyou.txt")); IOUtils.copyBytes(in, System.out, 1024); }} 场景编程在mapreduce 、spark等运算框架中,有一个核心思想就是将运算移往数据,或者说,就是要在并发计算中尽可能让运算本地化,这就需要获取数据所在位置的信息并进行相应范围读取以下模拟实现:获取一个文件的所有block位置信息,然后读取指定block中的内容 123456789101112131415161718192021222324252627282930 @Test public void testCat() throws IllegalArgumentException, IOException{ FSDataInputStream in = fs.open(new Path("/wordcount/data")); //拿到文件信息 FileStatus[] listStatus = fs.listStatus(new Path("/wordcount/data")); //获取这个文件的所有block的信息 BlockLocation[] fileBlockLocations = fs.getFileBlockLocations(listStatus[0], 0L, listStatus[0].getLen()); //第一个block的长度 long length = fileBlockLocations[0].getLength(); //第一个block的起始偏移量 long offset = fileBlockLocations[0].getOffset(); System.out.println(length); System.out.println(offset); //获取第一个block写入输出流// IOUtils.copyBytes(in, System.out, (int)length); byte[] b = new byte[4096]; FileOutputStream os = new FileOutputStream(new File("d:/block0")); while(in.read(offset, b, 0, 4096)!=-1){ os.write(b); offset += 4096; if(offset>=length) return; }; os.flush(); os.close(); in.close(); }]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
<tag>HDFS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[linux安装zookeeper集群]]></title>
<url>%2F2017%2F06%2F10%2Flinux%E5%AE%89%E8%A3%85zookeeper%E9%9B%86%E7%BE%A4%2F</url>
<content type="text"><![CDATA[安装zookeeper集群 下载zookeeper 解压zookeeper tar -zxvf zookeeper-3.4.5.tar.gz 修改配置文件 cd zookeeper-3.4.5/conf cp zoo_sample.cfg zoo.cfg 1vi zoo.cfg 修改dataDir路径 配置集群地址 server.1 : 服务器编号 192.168.127.63:服务器地址 2888:集群沟通端口号 3888:集群选举端口号 在dataDir路径下创建文件名为:myid 内容为本台服务器的编号 echo “编号” > myid 将集群下发到其他机器上 scp -r zookeeper-3.4.5 机器IP:/root 同样在其他机器上dataDir路径下创建文件名为:myid 内容为本台服务器的编号 启动zookeeper /root/zookeeper/bin/zkServer.sh start 查看zookeeper状态 /root/zookeeper/bin/zkServer.sh status]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[IDEA部署hadoop运行环境]]></title>
<url>%2F2017%2F06%2F10%2FIDEA%E9%83%A8%E7%BD%B2hadoop%E8%BF%90%E8%A1%8C%E7%8E%AF%E5%A2%83%2F</url>
<content type="text"><![CDATA[背景 前段时间配置好了基于mac的hadoop完全分布式环境,一直想着怎么样去用编译器写程序然后直接在hadoop环境中运行呢,经过一番摸索,写下此文章分享交流。 环境介绍 os x EI Capitan 10.11.5 虚拟机:Parallels Desktop ubuntukylin 14.04 64bit * 2 hadoop 2.6.2 os x上的jdk版本:1.8.0_73,ubuntu上的jdk版本:1.8.0_91(不同机器上的jdk版本不要求一样) IntelliJ IDEA 16.1 如果没有配置好hadoop的环境,请参考本人另一篇博文:http://blog.csdn.net/wk51920/article/details/51686038下载及安装IntelliJ IDEA 16.1 1. 登录官网:https://www.jetbrains.com/idea/下载最新版本的编译器。 2. 安装编译器。 创建hadoop工程 1. 打开IntelliJ IDEA编译器,创建一个新的工程,点击next 2. 输入工程的名字,点击Finish 3. 创建如下图的包结构及WordCount.java文件: 4. 添加JDK,如果已经配置了JDK可以省略此步骤:在侧边栏右击选择“Open Module Settings” 点击“news”选择jdk的根目录即可。 5. 添加hadoop各种jar包 打开“Module Settings”点击左侧边栏中的Libraries。 点击右侧的“+”号,并选择“Java”,在路径中选择hadoop的各种jar包,我的hadoop jar包路劲为:/usr/hadoop/hadoop-2.6.2/share/hadoop下的所有目录中的jar包。另外还需要特别将/usr/hadoop/hadoop-2.6.2/share/hadoop/common/lib下的所有jar包添加进工程,并点击“Apply”或“OK”。 配置编译环境 1. 点击右上角的中的下拉箭头,点击“Edit Configurations…”,并点击左上角的“+”号,并选择“Application” 2. 在“Main class”中填写我们需要运行的程序:com.wk51920.WordCount; 在“Program arguments”中填写程序需要的“输入文件”和“输出文件”。在此程序中,“输入文件”即需要用WordCount去统计单词的源文件的位置,“输出文件”即保存统计结果的文件。此栏的完整内容为:hdfs://master:8020/input/README.txt hdfs://master:8020/output/。这两个路径根据你配置的hadoop环境相关,即core-site.xml配置文件中的fs.defaultFS属性的值,即是HDFS系统的根目录。 在“Name”处改为:hadoop,点击“Apply”。 填写WordCount代码 选择WordCount文件,写入如下代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061package com.wk51920;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapred.*;import java.io.IOException;import java.util.Iterator;import java.util.StringTokenizer;/** * Created by wk51920 on 16/6/3. */public class WordCount { public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable>{ private final static IntWritable one = new IntWritable(1); private Text word = new Text(); @Override public void map(LongWritable longWritable, Text text, OutputCollector<Text, IntWritable> outputCollector, Reporter reporter) throws IOException { String line = text.toString(); StringTokenizer tokenizer = new StringTokenizer(line); while(tokenizer.hasMoreTokens()){ word.set(tokenizer.nextToken()); outputCollector.collect(word,one); } } } public static class Reduce extends MapReduceBase implements Reducer<Text,IntWritable,Text, IntWritable>{ @Override public void reduce(Text text, Iterator<IntWritable> iterator, OutputCollector<Text, IntWritable> outputCollector, Reporter reporter) throws IOException { int sum= 0; while(iterator.hasNext()){ sum += iterator.next().get(); } outputCollector.collect(text, new IntWritable(sum)); } } public static void main(String[] args) throws Exception{ JobConf conf = new JobConf(WordCount.class); conf.setJobName("wordCount"); conf.setOutputKeyClass(Text.class); conf.setOutputValueClass(IntWritable.class); conf.setMapperClass(Map.class); conf.setReducerClass(Reduce.class); conf.setInputFormat(TextInputFormat.class); conf.setOutputFormat(TextOutputFormat.class); FileInputFormat.setInputPaths(conf,new Path(args[0])); FileOutputFormat.setOutputPath(conf, new Path(args[1])); JobClient.runJob(conf); }}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 将文件放入HDFS系统 如果没有启动hadoop服务,先启动服务。 1. 在hdfs系统中建立input文件夹: 1hadoop fs -mkdir /input 1 2. 将本地的README.txt放入HDFS中的input文件夹下: 1hadoop fs -copyFromLocal README.txt /input1 注意运行此程序前,在HDFS系统上不能存在output文件夹,如果存在请删除再运行程序。运行WordCount程序 选择WordCount文件,并在代码区右击,选择“Run ‘hadoop’”]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>IDEA</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Zookeeper简单使用]]></title>
<url>%2F2017%2F06%2F10%2FZookeeper%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[zookeeper命令行操作 运行 zkCli.sh –server <ip>进入命令行工具 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容: ls / 创建一个新的 znode ,使用 create /zk myData 。这个命令创建了一个新的 znode 节点“ zk ”以及与它关联的字符串: create /zk "myData" 我们运行 get 命令来确认 znode 是否包含我们所创建的字符串: get /zk 监听这个节点的变化,当另外一个客户端改变/zk时,它会打出下面的 get /zk watch WATCHER::atchedEvent state:SyncConnected type:NodeDataChanged path:/zk 下面我们通过 set 命令来对 zk 所关联的字符串进行设置: set /zk "zsl" 下面我们将刚才创建的 znode 删除: delete /zk 删除节点: rmr /zk ##zookeeper-api应用 功能 描述 create 在本地目录树中创建一个节点 delete 删除一个节点 exists 测试本地是否存在目标节点 get/set data 从目标节点上读取 / 写数据 get/set ACL 获取 / 设置目标节点访问控制列表信息 get children 检索一个子节点上的列表 sync 等待要被传送的数据 ###demo代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import java.io.IOException;import java.util.List;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooDefs.Ids;import org.apache.zookeeper.ZooKeeper;import org.apache.zookeeper.data.Stat;import org.junit.Before;import org.junit.Test;public class SimpleZkClient { private static final String connectString = "192.168.127.61:2181,192.168.127.62:2181,192.168.127.63:2181"; private static final int sessionTimeout = 2000; ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() { @Override public void process(WatchedEvent event) { // 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑) System.out.println(event.getType() + "---" + event.getPath()); try { zkClient.getChildren("/", true); } catch (Exception e) { } } }); } /** * 数据的增删改查 * * @throws InterruptedException * @throws KeeperException */ // 创建数据节点到zk中 public void testCreate() throws KeeperException, InterruptedException { // 参数1:要创建的节点的路径 参数2:节点大数据 参数3:节点的权限 参数4:节点的类型 String nodeCreated = zkClient.create("/eclipse", "hellozk".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //上传的数据可以是任何类型,但都要转成byte[] } //判断znode是否存在 @Test public void testExist() throws Exception{ Stat stat = zkClient.exists("/eclipse", false); System.out.println(stat==null?"not exist":"exist"); } // 获取子节点 @Test public void getChildren() throws Exception { List<String> children = zkClient.getChildren("/", true); for (String child : children) { System.out.println(child); } Thread.sleep(Long.MAX_VALUE); } //获取znode的数据 @Test public void getData() throws Exception { byte[] data = zkClient.getData("/eclipse", false, null); System.out.println(new String(data)); } //删除znode @Test public void deleteZnode() throws Exception { //参数2:指定要删除的版本,-1表示删除所有版本 zkClient.delete("/eclipse", -1); } //删除znode @Test public void setData() throws Exception { zkClient.setData("/app1", "imissyou angelababy".getBytes(), -1); byte[] data = zkClient.getData("/app1", false, null); System.out.println(new String(data)); } } ##Zookeeper的监听器工作机制 监听器是一个接口,我们的代码中可以实现Wather这个接口,实现其中的process方法,方法中即我们自己的业务逻辑 监听器的注册是在获取数据的操作中实现: getData(path,watch?)监听的事件是:节点数据变化事件 getChildren(path,watch?)监听的事件是:节点下的子节点增减变化事件 ###分布式共享锁的简单实现 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495import java.util.Collections;import java.util.List;import java.util.Random;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.Watcher.Event.EventType;import org.apache.zookeeper.ZooDefs.Ids;import org.apache.zookeeper.ZooKeeper;public class DistributedClientLock { // 会话超时 private static final int SESSION_TIMEOUT = 2000; // zookeeper集群地址 private String hosts = "mini1:2181,mini2:2181,mini3:2181"; private String groupNode = "locks"; private String subNode = "sub"; private boolean haveLock = false; private ZooKeeper zk; // 记录自己创建的子节点路径 private volatile String thisPath; /** * 连接zookeeper */ public void connectZookeeper() throws Exception { zk = new ZooKeeper(hosts, SESSION_TIMEOUT, new Watcher() { public void process(WatchedEvent event) { try { // 判断事件类型,此处只处理子节点变化事件 if (event.getType() == EventType.NodeChildrenChanged && event.getPath().equals("/" + groupNode)) { //获取子节点,并对父节点进行监听 List<String> childrenNodes = zk.getChildren("/" + groupNode, true); String thisNode = thisPath.substring(("/" + groupNode + "/").length()); // 去比较是否自己是最小id Collections.sort(childrenNodes); if (childrenNodes.indexOf(thisNode) == 0) { //访问共享资源处理业务,并且在处理完成之后删除锁 doSomething(); //重新注册一把新的锁 thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } } } catch (Exception e) { e.printStackTrace(); } } }); // 1、程序一进来就先注册一把锁到zk上 thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); // wait一小会,便于观察 Thread.sleep(new Random().nextInt(1000)); // 从zk的锁父目录下,获取所有子节点,并且注册对父节点的监听 List<String> childrenNodes = zk.getChildren("/" + groupNode, true); //如果争抢资源的程序就只有自己,则可以直接去访问共享资源 if (childrenNodes.size() == 1) { doSomething(); thisPath = zk.create("/" + groupNode + "/" + subNode, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } } /** * 处理业务逻辑,并且在最后释放锁 */ private void doSomething() throws Exception { try { System.out.println("gain lock: " + thisPath); Thread.sleep(2000); // do something } finally { System.out.println("finished: " + thisPath); //释放锁 zk.delete(this.thisPath, -1); } } public static void main(String[] args) throws Exception { DistributedClientLock dl = new DistributedClientLock(); dl.connectZookeeper(); Thread.sleep(Long.MAX_VALUE); }}]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Java</tag>
<tag>Zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hadoop内置RPC API的使用]]></title>
<url>%2F2017%2F06%2F09%2Fhadoop%E5%86%85%E7%BD%AERPC%20API%E7%9A%84%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[client1234567891011121314151617import java.net.InetSocketAddress;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.ipc.RPC;import cn.demo.bigdata.hadooprpc.protocol.ClientNamenodeProtocol;public class MyHdfsClient { public static void main(String[] args) throws Exception { ClientNamenodeProtocol namenode = RPC.getProxy(ClientNamenodeProtocol.class, 1L, new InetSocketAddress("localhost", 8888), new Configuration()); String metaData = namenode.getMetaData("/angela.mygirl"); System.out.println(metaData); }} Protocol1234public interface ClientNamenodeProtocol {// public static final long versionID=1L; public String getMetaData(String path);} Server12345678910import cn.demo.bigdata.hadooprpc.protocol.ClientNamenodeProtocol;public class MyNameNode implements ClientNamenodeProtocol{ //模拟namenode的业务方法之一:查询元数据 @Override public String getMetaData(String path){ return path+": 3 - {BLK_1,BLK_2} ...."; }} Publish123456789101112131415161718192021import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.ipc.RPC;import org.apache.hadoop.ipc.RPC.Builder;import org.apache.hadoop.ipc.RPC.Server;import cn.demo.bigdata.hadooprpc.protocol.ClientNamenodeProtocol;import cn.demo.bigdata.hadooprpc.protocol.IUserLoginService;public class PublishService { public static void main(String[] args) throws Exception { Builder builder = new RPC.Builder(new Configuration()); builder.setBindAddress("localhost") .setPort(8888) .setProtocol(ClientNamenodeProtocol.class) .setInstance(new MyNameNode()); Server server = builder.build(); server.start(); }}]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hadoop学习遇到问题]]></title>
<url>%2F2017%2F06%2F08%2FHadoop%E5%AD%A6%E4%B9%A0%E9%81%87%E5%88%B0%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[Hadoop集群警告查看集群状态 hdfs dfsadmin -report 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364Configured Capacity: 55306051584 (51.51 GB)Present Capacity: 46777233408 (43.56 GB)DFS Remaining: 46777159680 (43.56 GB)DFS Used: 73728 (72 KB)DFS Used%: 0.00%Under replicated blocks: 0Blocks with corrupt replicas: 0Missing blocks: 0-------------------------------------------------Live datanodes (3):Name: 192.168.127.63:50010 (mini3)Hostname: mini3Decommission Status : NormalConfigured Capacity: 18435350528 (17.17 GB)DFS Used: 24576 (24 KB)Non DFS Used: 2847330304 (2.65 GB)DFS Remaining: 15587995648 (14.52 GB)DFS Used%: 0.00%DFS Remaining%: 84.55%Configured Cache Capacity: 0 (0 B)Cache Used: 0 (0 B)Cache Remaining: 0 (0 B)Cache Used%: 100.00%Cache Remaining%: 0.00%Xceivers: 1Last contact: Tue Oct 24 00:10:37 CST 2017Name: 192.168.127.62:50010 (mini2)Hostname: mini2Decommission Status : NormalConfigured Capacity: 18435350528 (17.17 GB)DFS Used: 24576 (24 KB)Non DFS Used: 2847182848 (2.65 GB)DFS Remaining: 15588143104 (14.52 GB)DFS Used%: 0.00%DFS Remaining%: 84.56%Configured Cache Capacity: 0 (0 B)Cache Used: 0 (0 B)Cache Remaining: 0 (0 B)Cache Used%: 100.00%Cache Remaining%: 0.00%Xceivers: 1Last contact: Tue Oct 24 00:10:37 CST 2017Name: 192.168.127.64:50010 (mini4)Hostname: mini4Decommission Status : NormalConfigured Capacity: 18435350528 (17.17 GB)DFS Used: 24576 (24 KB)Non DFS Used: 2834305024 (2.64 GB)DFS Remaining: 15601020928 (14.53 GB)DFS Used%: 0.00%DFS Remaining%: 84.63%Configured Cache Capacity: 0 (0 B)Cache Used: 0 (0 B)Cache Remaining: 0 (0 B)Cache Used%: 100.00%Cache Remaining%: 0.00%Xceivers: 1Last contact: Tue Oct 24 00:10:37 CST 2017 警告的解决 警告1 Java HotSpot(TM) Client VM warning: You have loaded library /home/hujian/apps/hadoop-2.6.4/lib/native/libhadoop.so.1.0.0 which might have disabled stack guard. The VM will try to fix the stack guard now. It’s highly recommended that you fix the library with ‘execstack -c ‘, or link it with ‘-z noexecstack’. 解决方案: 在/etc/profile下添加如下两条环境变量 export HADOOP_COMMON_LIB_NATIVE_DIR=${HADOOP_HOME}/lib/native export HADOOP_OPTS=”-Djava.library.path=$HADOOP_HOME/lib” 然后: source /etc/profile 再启动hadoop 警告2: WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable 解决方案: 直接在log4j日志中去除告警信息。在hadoop的安装目录 /apps/hadoop-2.5.2/etc/hadoop/log4j.properties文件中添加 log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR 操作HDFS报错15/10/08 21:04:19 INFO hdfs.DFSClient: Exception in createBlockOutputStreamjava.io.IOException: Got error, status message , ack with firstBadLink as 192.168.1.114:50010at org.apache.hadoop.hdfs.protocol.datatransfer.DataTransferProtoUtil.checkBlockOpStatus(DataTransferProtoUtil.java:140)at org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.createBlockOutputStream(DFSOutputStream.java:1334)at org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer.nextBlockOutputStream(DFSOutputStream.java:1237) 原因是由于centos7的防火墙引起与datanode无法通讯造成。 centos7 关闭防火墙 service firewalld stop]]></content>
<categories>
<category>Hadoop</category>
</categories>
<tags>
<tag>Hadoop</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HADOOP集群搭建]]></title>
<url>%2F2017%2F06%2F07%2FHADOOP%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%2F</url>
<content type="text"><![CDATA[HADOOP集群搭建集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据的存储,集群中的角色主要有 NameNode / DataNode YARN集群: 负责海量数据运算时的资源调度,集群中的角色主要有 ResourceManager /NodeManager (那mapreduce是什么呢?它其实是一个应用程序开发包) 服务器准备本案例使用虚拟机服务器来搭建HADOOP集群, 服务器节点数量:3 所用软件及版本: Vmware 11.0 Centos 6.5 64bit 网络环境准备 采用NAT方式联网 网关地址:192.168.127.1 3个服务器节点IP地址:192.168.127.61、192.168.127.62、192.168.127.63 子网掩码:255.255.255.0 服务器系统设置 添加HADOOP用户 useradd hadoop 为HADOOP用户分配sudoer权限vi /etc/sudoers hadoop ALL=(ALL) ALL 同步时间 设置主机名 mini1 mini2 mini3 配置内网域名映射 vi /etc/hosts 192.168.127.61 mini1 192.168.127.62 mini2 192.168.127.63 mini3 配置ssh免密登陆 配置防火墙 JDK环境安装 上传jdk安装包 解压安装包 配置环境变量 /etc/profile HADOOP安装部署 上传HADOOP安装包(Hadoop需要编译。Hadoop是使用Java语言开发的,但是有一些需求和操作并不适合使用java,所以就引入了本地库(Native Libraries)的概念) 从http://hadoop.apache.org/releases.html找到一个你想要的版本,下载binary包 规划安装目录 /home/hadoop/apps/hadoop-2.9.0 解压安装包 tar -zxvf hadoop-2.9.0.tar.gz 修改 $HADOOP_HOME/etc/hadoop/下的配置文件 最简化配置如下: vi hadoop-env.sh # The java implementation to use. export JAVA_HOME=/home/hadoop/apps/jdk1.7.0_65 vi core-site.xml 123456789101112<configuration> <!-- 指定HADOOP所使用的文件系统schema(URI),HDFS的老大(NameNode)的地址 --> <property> <name>fs.defaultFS</name> <value>hdfs://mini1:9000</value> </property> <!-- 指定hadoop运行时产生文件的存储目录 --> <property> <name>hadoop.tmp.dir</name> <value>/home/hadoop/hdpdata</value> </property></configuration> vi hdfs-site.xml 1234567<configuration> <!-- 指定HDFS副本的数量 --> <property> <name>dfs.replication</name> <value>2</value> </property></configuration> vi mapred-site.xml 1234567<configuration><!-- 指定mr运行在yarn上 --> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property></configuration> vi yarn-site.xml 12345678910111213<configuration> <!-- Site specific YARN configuration properties --> <!-- 指定YARN的老大(ResourceManager)的地址 --> <property> <name>yarn.resourcemanager.hostname</name> <value>mini1</value> </property> <!-- reducer获取数据的方式 --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property></configuration> vi slaves mini2 mini3 启动集群 将配置好的hadoop文件夹复制到其他节点机器 12scp -r ~/apps hadoop@mini2:~/scp -r ~/apps hadoop@mini3:~/ 初始化HDFS(mini1机器) /home/hadoop/apps/hadoop-2.9.0/bin/hadoop namenode -format 启动HDFS(mini1机器) /home/hadoop/apps/hadoop-2.9.0/sbin/start-dfs.sh jps命令检查是否启动成功 2136 SecondaryNameNode 2257 Jps 1792 NameNode 启动YARN(mini1机器) /home/hadoop/apps/hadoop-2.6.4/sbin/start-yarn.sh jps命令检查是否启动成功 2306 ResourceManager 2621 Jps 2408 NodeManager 2136 SecondaryNameNode 1792 NameNode 访问控制台http://mini1:50070]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Hadoop</tag>
</tags>
</entry>
<entry>
<title><![CDATA[已经编译安装好的nginx,添加未被编译的模块]]></title>
<url>%2F2017%2F05%2F18%2F%E5%B7%B2%E7%BB%8F%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85%E5%A5%BD%E7%9A%84nginx%EF%BC%8C%E6%B7%BB%E5%8A%A0%E6%9C%AA%E8%A2%AB%E7%BC%96%E8%AF%91%E7%9A%84%E6%A8%A1%E5%9D%97%2F</url>
<content type="text"><![CDATA[配置完nginx重启的时候,报了如下错误: 1nginx: [emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginx/conf/nginx.conf:37 原因也很简单,nginx缺少httpsslmodule模块,编译安装的时候带上–with-httpsslmodule配置就行了,但是现在的情况是我的nginx已经安装过了,怎么添加模块,其实也很简单,往下看: 做个说明:我的nginx的安装目录是/usr/local/nginx这个目录,我的源码包在/usr/local/src/nginx-1.6.2目录 步骤开始: 切换到源码包: cd /usr/local/src/nginx-1.6.2 查看nginx原有的模块 /usr/local/nginx/sbin/nginx -V在configure arguments:后面显示的原有的configure参数如下: --prefix=/usr/local/nginx --with-http_stub_status_module那么我们的新配置信息就应该这样写: 1./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module 运行上面的命令即可,等配置完 配置完成后,运行命令 make这里不要进行make install,否则就是覆盖安装 然后备份原有已安装好的nginx cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak然后将刚刚编译好的nginx覆盖掉原有的nginx(这个时候nginx要停止状态) cp ./objs/nginx /usr/local/nginx/sbin/提示是否覆盖,输入yes即可 然后启动nginx,仍可以通过命令查看是否已经加入成功 /usr/local/nginx/sbin/nginx -V]]></content>
<categories>
<category>Nginx</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Nginx</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux操作文件或目录常用命令]]></title>
<url>%2F2017%2F04%2F12%2FLinux%E6%93%8D%E4%BD%9C%E6%96%87%E4%BB%B6%E6%88%96%E7%9B%AE%E5%BD%95%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[操作文件或目录常用命令 touch: 创建空文件 mkdir: 创建目录(make directoriy) -p 父目录不存在情况下先生成父目录 (parents) 例如 : 在根目录下创建一个itcast的文件夹 mkdir /itcast cp:复制文件或目录(copy) -r 递归处理,将指定目录下的文件与子目录一并拷贝(recursive) 查看一下文件内容 cat test.txt more test.txt less test.txt mv: 移动文件或目录、文件或目录改名(move) wc 统计文本的行数、字数、字符数(word count) -m 统计文本字符数 -w 统计文本字数 -l 统计文本行数 find 在文件系统中查找指定的文件 find /etc/ -name “aaa” grep 在指定的文本文件中查找指定的字符串 ln 建立链接文件(link) -s 对源文件建立符号连接,而非硬连接(symbolic) df 显示文件系统磁盘空间的使用情况 du 显示指定的文件(目录)已使用的磁盘空间的总 -h文件大小以K,M,G为单位显示(human-readable) -s只显示各档案大小的总合(summarize)]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[awk一个强大的文本分析工具]]></title>
<url>%2F2017%2F04%2F08%2Fawk%E4%B8%80%E4%B8%AA%E5%BC%BA%E5%A4%A7%E7%9A%84%E6%96%87%E6%9C%AC%E5%88%86%E6%9E%90%E5%B7%A5%E5%85%B7%2F</url>
<content type="text"><![CDATA[简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。 awk有3个不同版本: awk、nawk和gawk,未作特别说明,一般指gawk,gawk 是 AWK 的 GNU 版本。 awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。 使用方法 语法:awk '{pattern + action}' {filenames} 尽管操作可能会很复杂,但语法总是这样,其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。 pattern就是要表示的正则表达式,用斜杠括起来。 awk语言的最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。 通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。 调用awk 有三种方式调用awk 1.命令行方式awk [-F field-separator] ‘commands’ input-file(s)其中,commands 是真正awk命令,[-F域分隔符]是可选的。 input-file(s) 是待处理的文件。在awk中,文件的每一行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F域分隔符的情况下,默认的域分隔符是空格。 2.shell脚本方式将所有的awk命令插入一个文件,并使awk程序可执行,然后awk命令解释器作为脚本的首行,一遍通过键入脚本名称来调用。相当于shell脚本首行的:#!/bin/sh可以换成:#!/bin/awk 3.将所有的awk命令插入一个单独文件,然后调用:awk -f awk-script-file input-file(s)其中,-f选项加载awk-script-file中的awk脚本,input-file(s)跟上面的是一样的。 入门实例 语句:last -n 5 结果:只取前5行 root pts/1 192.168.1.100 Tue Feb 10 11:21 still logged inoot pts/1 192.168.1.100 Tue Feb 10 00:46 - 02:28 (01:41)oot pts/1 192.168.1.100 Mon Feb 9 11:41 - 18:30 (06:48)mtsai pts/1 192.168.1.100 Mon Feb 9 11:41 - 11:41 (00:00)oot tty1 Fri Sep 5 14:09 - 14:10 (00:01) 语句:last -n 5 | awk '{print $1}' 结果:如果只是显示最近登录的5个帐号 rootrootrootdmtsairoot 分析: awk工作流程是这样的:读入有’\n’换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域。默认域分隔符是”空白键” 或 “[tab]键”,所以$1表示登录用户,$3表示登录用户ip,以此类推。 语句:cat /etc/passwd |awk -F ':' '{print $1}' 结果:只是显示/etc/passwd的账户 rootdaemonbinsys 分析: 这种是awk+action的示例,每行都会执行action{print $1}。-F指定域分隔符为’:’。 语句:cat /etc/passwd |awk -F ':' '{print $1"\t"$7}' 结果:只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以tab键分割 root /bin/bashaemon /bin/shin /bin/shys /bin/sh 语句:cat /etc/passwd |awk -F ':' 'BEGIN {print "name,shell"} {print $1","$7} END {print "blue,/bin/nosh"}' 结果:只是显示/etc/passwd的账户和账户对应的shell,而账户与shell之间以逗号分割,而且在所有行添加列名name,shell,在最后一行添加”blue,/bin/nosh”。 name,shellroot,/bin/bashdaemon,/bin/shbin,/bin/shsys,/bin/sh….blue,/bin/nosh 分析: awk工作流程是这样的:先执行BEGING,然后读取文件,读入有/n换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,$0则表示所有域,$1表示第一个域,$n表示第n个域,随后开始执行模式所对应的动作action。接着开始读入第二条记录······直到所有的记录都读完,最后执行END操作。 语句:awk -F: '/root/' /etc/passwd 结果:搜索/etc/passwd有root关键字的所有行 root:x:0:0:root:/root:/bin/bash 分析:这种是pattern的使用示例,匹配了pattern(这里是root)的行才会执行action(没有指定action,默认输出每行的内容)。 搜索支持正则,例如找root开头的: awk -F: ‘/^root/‘ /etc/passwd 语句:awk -F: '/root/{print $7}' /etc/passwd 结果:搜索/etc/passwd有root关键字的所有行,并显示对应的shell /bin/bash 分析:这里指定了action{print $7} awk编程awk内置变量 awk有许多内置变量用来设置环境信息,这些变量可以被改变,下面给出了最常用的一些变量。 ARGC 命令行参数个数 ARGV 命令行参数排列 ENVIRON 支持队列中系统环境变量的使用 FILENAME awk浏览的文件名 FNR 浏览文件的记录数 FS 设置输入域分隔符,等价于命令行 -F选项 NF 浏览记录的域的个数 NR 已读的记录数 OFS 输出域分隔符 ORS 输出记录分隔符 RS 控制记录分隔符 此外,\$0变量是指整条记录。\$1表示当前行的第一个域,\$2表示当前行的第二个域,……以此类推。 入门实例 语句:awk -F ':' '{print "filename:" FILENAME ",linenumber:" NR ",columns:" NF ",linecontent:"$0}' /etc/passwd 结果:统计/etc/passwd:文件名,每行的行号,每行的列数,对应的完整行内容 filename:/etc/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bashfilename:/etc/passwd,linenumber:2,columns:7,linecontent:daemon:x:1:1:daemon:/usr/sbin:/bin/shfilename:/etc/passwd,linenumber:3,columns:7,linecontent:bin:x:2:2:bin:/bin:/bin/shfilename:/etc/passwd,linenumber:4,columns:7,linecontent:sys:x:3:3:sys:/dev:/bin/sh 分析:使用printf替代print,可以让代码更加简洁,易读awk -F ':' '{printf("filename:%s,linenumber:%s,columns:%s,linecontent:%s\n",FILENAME,NR,NF,$0)}' /etc/passwd print和printf区别 awk中同时提供了print和printf两种打印输出的函数。 其中print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。 printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。 变量和赋值 除了awk的内置变量,awk还可以自定义变量。 举例:统计/etc/passwd的账户人数 语句:awk '{count++;print $0;} END{print "user count is ", count}' /etc/passwd 结果: root:x:0:0:root:/root:/bin/bash……ser count is 40 分析:count是自定义变量。之前的action{}里都是只有一个print,其实print只是一个语句,而action{}可以有多个语句,以;号隔开。 这里没有初始化count,虽然默认是0,但是妥当的做法还是初始化为0: 1awk 'BEGIN {count=0;print "[start]user count is ", count} {count=count+1;print $0;} END{print "[end]user count is ", count}' /etc/passwd [start]user count is 0root:x:0:0:root:/root:/bin/bash…end]user count is 40 举例:统计某个文件夹下的文件占用的字节数 语句:ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size}' [end]size is 8657198 举例:如果以M为单位显示: 语句:ls -l |awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ", size/1024/1024,"M"}' [end]size is 8.25889 M 注意,统计不包括文件夹的子目录。 ###条件语句 awk中的条件语句是从C语言中借鉴来的,见如下声明方式: 12345678910111213141516171819if (expression) { statement; statement; ... ...}if (expression) { statement;} else { statement2;}if (expression) { statement1;} else if (expression1) { statement2;} else { statement3;} 举例:统计某个文件夹下的文件占用的字节数,过滤4096大小的文件(一般都是文件夹): 语句: 1ls -l |awk 'BEGIN {size=0;print "[start]size is ", size} {if($5!=4096){size=size+$5;}} END{print "[end]size is ", size/1024/1024,"M"}' [end]size is 8.22339 M 循环语句 awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。 数组 因为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。 举例:显示/etc/passwd的账户 语句: 1awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd 0 rootdaemonbinsyssyncgames…… 这里使用for循环遍历数组 awk编程的内容极多,这里只罗列简单常用的用法,更多请参考 http://www.gnu.org/software/gawk/manual/gawk.html]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>awk</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Sed命令]]></title>
<url>%2F2017%2F04%2F08%2FSed%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Sed简介 sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。以下介绍的是Gnu版本的Sed 3.02。##定址 可以通过定址来定位你所希望编辑的行,该地址用数字构成,用逗号分隔的两个行数表示以这两行为起止的行的范围(包括行数表示的那两行)。如1,3表示1,2,3行,美元符号($)表示最后一行。范围可以通过数据,正则表达式或者二者结合的方式确定 。 Sed命令调用sed命令有两种形式: sed [options] 'command' file(s) sed [options] -f scriptfile file(s)###[option] 参数 a\ 在当前行后面加入一行文本。 b lable 分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾。 c\ 用新的文本改变本行的文本。 d 从模板块(Pattern space)位置删除行。 D 删除模板块的第一行。 i\ 在当前行上面插入文本。 h拷贝模板块的内容到内存中的缓冲区。 H追加模板块的内容到内存中的缓冲区 g获得内存缓冲区的内容,并替代当前模板块中的文本。 G获得内存缓冲区的内容,并追加到当前模板块文本的后面。 l列表不能打印字符的清单。 n读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。 N追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。 p打印模板块的行。 P(大写)打印模板块的第一行。 q退出Sed。 r file从file中读行。 t labelif分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。 T label错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。 w file写并追加模板块到file末尾。 W file写并追加模板块的第一行到file末尾。 !表示后面的命令对所有没有被选定的行发生作用。 s/re/string用string替换正则表达式re。 =打印当前行号码。 #把注释扩展到下一个换行符以前。 以下的是替换标记 g表示行内全面替换。 p表示打印行。 w表示把行写入一个文件。 x表示互换模板块中的文本和缓冲区中的文本。 y表示把一个字符翻译为另外的字符(但是不用于正则表达式) 选项 -e command, –expression=command允许多台编辑。 -h, –help打印帮助,并显示bug列表的地址。 -n, –quiet, –silent取消默认输出。 -f, –filer=script-file引导sed脚本文件名。 -V, –version打印版本和版权信息。 元字符集 1^ 锚定行的开始 如:/^sed/匹配所有以sed开头的行。 1$ 锚定行的结束 如:/sed$/匹配所有以sed结尾的行。 1. 匹配一个非换行符的字 如:/s.d/匹配s后接一个任意字符,然后是d。 1* 匹配零或多个字符 如:/*sed/匹配所有模板是一个或多个空格后紧跟sed的行。 1[] 匹配一个指定范围内的字符,如/[Ss]ed/匹配sed和Sed。 1[^] 匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。 1\(..\) 保存匹配的字符,如s/(love)able/\1rs,loveable被替换成lovers。 1& 保存搜索字符用来替换其他字符,如s/love/&/,love这成love。 1\< 锚定单词的开始,如:/\<love/匹配包含以love开头的单词的行。 1\> 锚定单词的结束,如/love>/匹配包含以love结尾的单词的行。 1x\{m\} 重复字符x,m次,如:/o{5}/匹配包含5个o的行。 1x\{m,\} 重复字符x,至少m次,如:/o{5,}/匹配至少有5个o的行。 1x\{m,n\} 重复字符x,至少m次,不多于n次,如:/o{5,10}/匹配5–10个o的行。 实例12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970删除:d命令$ sed '2d' example-----删除example文件的第二行。$ sed '2,$d' example-----删除example文件的第二行到末尾所有行。$ sed '$d' example-----删除example文件的最后一行。$ sed '/test/'d example-----删除example文件所有包含test的行。替换:s命令$ sed 's/test/mytest/g' example-----在整行范围内把test替换为mytest。如果没有g标记,则只有每行第一个匹配的test被替换成mytest。$ sed -n 's/^test/mytest/p' example-----(-n)选项和p标志一起使用表示只打印那些发生替换的行。也就是说,如果某一行开头的test被替换成mytest,就打印它。$ sed 's/^192.168.0.1/&localhost/' example-----&符号表示替换换字符串中被找到的部份。所有以192.168.0.1开头的行都会被替换成它自已加 localhost,变成192.168.0.1localhost。$ sed -n 's/\(love\)able/\1rs/p' example-----love被标记为1,所有loveable会被替换成lovers,而且替换的行会被打印出来。$ sed 's#10#100#g' example-----不论什么字符,紧跟着s命令的都被认为是新的分隔符,所以,“#”在这里是分隔符,代替了默认的“/”分隔符。表示把所有10替换成100。选定行的范围:逗号$ sed -n '/test/,/check/p' example-----所有在模板test和check所确定的范围内的行都被打印。$ sed -n '5,/^test/p' example-----打印从第五行开始到第一个包含以test开始的行之间的所有行。$ sed '/test/,/check/s/$/sed test/' example-----对于模板test和west之间的行,每行的末尾用字符串sed test替换。多点编辑:e命令$ sed -e '1,5d' -e 's/test/check/' example-----(-e)选项允许在同一行里执行多条命令。如例子所示,第一条命令删除1至5行,第二条命令用check替换test。命令的执 行顺序对结果有影响。如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果。$ sed --expression='s/test/check/' --expression='/love/d' example-----一个比-e更好的命令是--expression。它能给sed表达式赋值。从文件读入:r命令$ sed '/test/r file' example-----file里的内容被读进来,显示在与test匹配的行后面,如果匹配多行,则file的内容将显示在所有匹配行的下面。写入文件:w命令$ sed -n '/test/w file' example-----在example中所有包含test的行都被写入file里。追加命令:a命令$ sed '/^test/a\\--->this is a example' example '----->this is a example'被追加到以test开头的行后面,sed要求命令a后面有一个反斜杠。插入:i命令$ sed '/test/i\\new line-------------------------' example如果test被匹配,则把反斜杠后面的文本插入到匹配行的前面。下一个:n命令$ sed '/test/{ n; s/aa/bb/; }' example-----如果test被匹配,则移动到匹配行的下一行,替换这一行的aa,变为bb,并打印该行,然后继续。变形:y命令$ sed '1,10y/abcde/ABCDE/' example-----把1--10行内所有abcde转变为大写,注意,正则表达式元字符不能使用这个命令。退出:q命令$ sed '10q' example-----打印完第10行后,退出sed。保持和获取:h命令和G命令$ sed -e '/test/h' -e '$G example-----在sed处理文件的时候,每一行都被保存在一个叫模式空间的临时缓冲区中,除非行被删除或者输出被取消,否则所有被处理的行都将 打印在屏幕上。接着模式空间被清空,并存入新的一行等待处理。在这个例子里,匹配test的行被找到后,将存入模式空间,h命令将其复制并存入一个称为保 持缓存区的特殊缓冲区内。第二条语句的意思是,当到达最后一行后,G命令取出保持缓冲区的行,然后把它放回模式空间中,且追加到现在已经存在于模式空间中 的行的末尾。在这个例子中就是追加到最后一行。简单来说,任何包含test的行都被复制并追加到该文件的末尾。保持和互换:h命令和x命令$ sed -e '/test/h' -e '/check/x' example -----互换模式空间和保持缓冲区的内容。也就是把包含test与check的行互换。 脚本 Sed脚本是一个sed的命令清单 启动Sed时以-f选项引导脚本文件名。 Sed对于脚本中输入的命令非常挑剔,在命令的末尾不能有任何空白或文本,如果在一行中有多个命令,要用分号分隔。以#开头的行为注释行,且不能跨行。]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Sed</tag>
</tags>
</entry>
<entry>
<title><![CDATA[安装GCC]]></title>
<url>%2F2017%2F04%2F02%2F%E5%AE%89%E8%A3%85GCC%2F</url>
<content type="text"><![CDATA[gcc 4.8 安装 [root@DS-VM-Node239 ~]`# curl -Lks http://www.hop5.in/yum/el6/hop5.repo > /etc/yum.repos.d/hop5.repo` [root@DS-VM-Node239 ~]`# yum install gcc gcc-g++ -y` [root@DS-VM-Node239 ~]`# gcc –version` gcc (GCC) 4.8.2 20131212 (Red Hat 4.8.2-8) Copyright © 2013 Free Software Foundation, Inc. 本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保; 包括没有适销性和某一专用目的下的适用性担保。 [root@DS-VM-Node239 ~]`# g++ –version` g++ (GCC) 4.8.2 20131212 (Red Hat 4.8.2-8) Copyright © 2013 Free Software Foundation, Inc. 本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保; 包括没有适销性和某一专用目的下的适用性担保。 [root@DS-VM-Node211 ~]`#` gcc 4.9 安装 [root@DS-VM-Node239 ~]`# yum install centos-release-scl -y` [root@DS-VM-Node239 ~]`# yum install devtoolset-3-toolchain -y` [root@DS-VM-Node239 ~]`# scl enable devtoolset-3 bash` [root@DS-VM-Node239 ~]`# gcc –version` gcc (GCC) 4.9.2 20150212 (Red Hat 4.9.2-6) Copyright (C) 2014 Free Software Foundation, Inc. This is `freesoftware; see the sourceforcopying conditions. There is NO` warranty; not even `forMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.` [root@DS-VM-Node239 ~]`# g++ –version` g++ (GCC) 4.9.2 20150212 (Red Hat 4.9.2-6) Copyright (C) 2014 Free Software Foundation, Inc. This is `freesoftware; see the sourceforcopying conditions. There is NO` warranty; not even `forMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.` [root@DS-VM-Node239 ~]`# gfortran –version` GNU Fortran (GCC) 4.9.2 20150212 (Red Hat 4.9.2-6) Copyright (C) 2014 Free Software Foundation, Inc. GNU Fortran comes with NO WARRANTY, to the extent permitted by law. You may redistribute copies of GNU Fortran under the terms of the GNU General Public License. For `moreinformation about these matters, see the filenamed COPYING` [root@DS-VM-Node239 ~]`#` gcc 5.2 安装 [root@DS-VM-Node239 ~]`# yum install centos-release-scl -y` [root@DS-VM-Node239 ~]`# yum install devtoolset-4-toolchain -y` [root@DS-VM-Node239 ~]`# scl enable devtoolset-4 bash` [root@DS-VM-Node239 ~]`# gcc –version` gcc (GCC) 5.2.1 20150902 (Red Hat 5.2.1-2) Copyright (C) 2015 Free Software Foundation, Inc. This is `freesoftware; see the sourceforcopying conditions. There is NO` warranty; not even `forMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.` [root@DS-VM-Node239 ~]`# g++ –version` g++ (GCC) 5.2.1 20150902 (Red Hat 5.2.1-2) Copyright (C) 2015 Free Software Foundation, Inc. This is `freesoftware; see the sourceforcopying conditions. There is NO` warranty; not even `forMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.` [root@DS-VM-Node239 ~]`#`]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Gcc</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java-Nio学习]]></title>
<url>%2F2017%2F03%2F02%2FJava-nio%2F</url>
<content type="text"><![CDATA[Java-nionio原理学习nio简介 nio 是New IO 的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。 传统的I/O 使用传统的I/O程序读取文件内容, 并写入到另一个文件(或Socket), 如下程序: File.read(fileDesc, buf, len); Socket.send(socket, buf, len); 会有较大的性能开销, 主要表现在一下两方面: 上下文切换(context switch), 此处有4次用户态和内核态的切换 Buffer内存开销, 一个是应用程序buffer, 另一个是系统读取buffer以及socket buffer其运行示意图如下 先将文件内容从磁盘中拷贝到操作系统buffer 再从操作系统buffer拷贝到程序应用buffer 从程序buffer拷贝到socket buffer 从socket buffer拷贝到协议引擎. NIO NIO技术省去了将操作系统的read buffer拷贝到程序的buffer, 以及从程序buffer拷贝到socket buffer的步骤, 直接将 read buffer 拷贝到 socket buffer. java 的 FileChannel.transferTo() 方法就是这样的实现, 这个实现是依赖于操作系统底层的sendFile()实现的. publicvoid transferTo(long position, long count, WritableByteChannel target); 他的底层调用的是系统调用sendFile()方法 sendfile(int out_fd, int in_fd, off_t *offset, size_t count); 如下图 传统socket和socket nio代码 传统socket server 12345678910111213141516171819202122232425262728public static void main(String args[]) throws Exception { // 监听端口 ServerSocket server_socket = new ServerSocket(2000); System.out.println("等待,端口为:" + server_socket.getLocalPort()); while (true) { // 阻塞接受消息 Socket socket = server_socket.accept(); // 打印链接信息 System.out.println("新连接: " + socket.getInetAddress() + ":" + socket.getPort()); // 从socket中获取流 DataInputStream input = new DataInputStream(socket.getInputStream()); // 接收数据 byte[] byteArray = new byte[4096]; while (true) { int nread = input.read(byteArray, 0, 4096); System.out.println(new String(byteArray, "UTF-8")); if (-1 == nread) { break; } } socket.close(); System.out.println("Connection closed by client"); } } client 123456789101112131415161718192021222324252627public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); // 创建socket链接 Socket socket = new Socket("localhost", 2000); System.out.println("Connected with server " + socket.getInetAddress() + ":" + socket.getPort()); // 读取文件 FileInputStream inputStream = new FileInputStream("C:/sss.txt"); // 输出文件 DataOutputStream output = new DataOutputStream(socket.getOutputStream()); // 缓冲区4096K byte[] b = new byte[4096]; // 传输长度 long read = 0, total = 0; // 读取文件,写到socketio中 while ((read = inputStream.read(b)) >= 0) { total = total + read; output.write(b); } // 关闭 output.close(); socket.close(); inputStream.close(); // 打印时间 System.out.println("bytes send--" + total + " and totaltime--" + (System.currentTimeMillis() - start)); } socket nio 代码 server 1234567891011121314151617181920212223242526272829303132public static void main(String[] args) throws IOException { // 创建socket channel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ServerSocket ss = serverSocketChannel.socket(); ss.setReuseAddress(true);// 地址重用 ss.bind(new InetSocketAddress("localhost", 9026));// 绑定地址 System.out.println("监听端口 : " + new InetSocketAddress("localhost", 9026).toString()); // 分配一个新的字节缓冲区 ByteBuffer dst = ByteBuffer.allocate(4096); // 读取数据 while (true) { SocketChannel channle = serverSocketChannel.accept();// 接收数据 System.out.println("Accepted : " + channle); channle.configureBlocking(true);// 设置阻塞,接不到就停 int nread = 0; while (nread != -1) { try { nread = channle.read(dst);// 往缓冲区里读 byte[] array = dst.array();//将数据转换为array //打印 String string = new String(array, 0, dst.position()); System.out.print(string); dst.clear(); } catch (IOException e) { e.printStackTrace(); nread = -1; } } } } client 1234567891011121314151617181920212223242526272829public static void main(String[] args) throws IOException { long start = System.currentTimeMillis(); // 打开socket的nio管道 SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("localhost", 9026));// 绑定相应的ip和端口 sc.configureBlocking(true);// 设置阻塞 // 将文件放到channel中 FileChannel fc = new FileInputStream("C:/sss.txt").getChannel();// 打开文件管道 //做好标记量 long size = fc.size(); int pos = 0; int offset = 4096; long curnset = 0; long counts = 0; //循环写 while (pos<size) { curnset = fc.transferTo(pos, 4096, sc);// 把文件直接读取到socket chanel中,返回文件大小 pos+=offset; counts+=curnset; } //关闭 fc.close(); sc.close(); //打印传输字节数 System.out.println(counts); // 打印时间 System.out.println("bytes send--" + counts + " and totaltime--" + (System.currentTimeMillis() - start)); } ]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>Nio</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Shell编程基础]]></title>
<url>%2F2017%2F02%2F08%2FShell%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[什么是Shell Shell是用户与内核进行交互操作的一种接口,目前最流行的Shell称为bash Shell Shell也是一门编程语言<解释型的编程语言>,即shell脚本 一个系统可以存在多个shell,可以通过cat/etc/shells命令查看系统中安装的shell,不同的shell可能支持的命令语法是不相同的 Shell脚本的执行方式 第一种:输入脚本的绝对路径或相对路径首先要赋予+x权限/root/helloWorld.sh./helloWorld.sh或者,不用赋予+x权限,而用解释器解释执行sh helloworld.sh 第二种:bash或sh +脚本sh /root/helloWorld.shsh helloWorld.sh 第三种:在脚本的路径前再加”. “. /root/helloWorld.sh. ./helloWorld.sh区别:第一种和第二种会新开一个bash,不同bash中的变量无法共享 Shell中的变量 Linux Shell中的变量分为“系统变量”和“用户自定义变量”,可以通过set命令查看那系统变量 系统变量:$HOME、$PWD、$SHELL、$USER等等 显示当前shell中所有变量 : set 定义变量 变量=值 (例如STR=abc) 等号两侧不能有空格 变量名称一般习惯为大写 双引号和单引号有区别, 双引号仅将空格脱意,单引号会将所有特殊字符脱意 STR=”hello world”A=9unset A 撤销变量 Areadonly B=2 声明静态的变量 B=2 ,不能 unsetexport 变量名 可把变量提升为全局环境变量,可供其他shell程序使用 将命令的返回值赋给变量 A= ls -la 反引号,运行里面的命令,并把结果返回给变量A A=$(ls -la) 等价于反引号 Shell中的特殊变量12345678910$? 表示上一个命令退出的状态$$ 表示当前进程编号$0 表示当前脚本名称$n 表示n位置的输入参数(n代表数字,n>=1)$# 表示参数的个数,常用于循环$*和$@ 都表示参数列表$*与$@区别$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号" "包含时,都以$1 $2 … $n 的形式输出所有参数当它们被双引号" "包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数 运算符格式 :expr m + n 或$((m+n))注意expr运算符间要有空格例如计算(2 +3 )×4 的值 1234561 .分步计算 S=`expr 2 + 3` expr $S \* 42.一步完成计算 expr `expr 2 + 3 ` \* 4 echo `expr \`expr 2 + 3\` \* 4` 或 $(((2+3)*4)) for循环1234567891011121314151617第一种:for N in 1 2 3do echo $Ndone或for N in 1 2 3; do echo $N; done或for N in {1..3}; do echo $N; done第二种:for ((i = 0; i <= 5; i++))do echo "welcome $i times"done或for ((i = 0; i <= 5; i++)); do echo "welcome $i times"; done while循环1234567891011121314第一种while expressiondocommand…done第二种i=1while ((i<=3))do echo $i let i++done case语句12345678910case $1 instart) echo "starting" ;;stop) echo "stoping" ;;*) echo "Usage: {start|stop} “esac read命令 read 1-p(提示语句)-n(字符个数) -t(等待时间) 举例:read -p "please input your name: " NAME if判断语法: 12345678if condition thenstatements[elif conditionthen statements. ..][elsestatements ]fi 例如: 123456789101112#!/bin/bashread -p "please input your name:" NAME#printf '%s\n' $NAMEif [ $NAME = root ] then echo "hello ${NAME}, welcome !" elif [ $NAME = romon ] then echo "hello ${NAME}, welcome !" else echo "SB, get out here !"fi 判断语句123456789[ condition ] (注意condition前后要有空格)#非空返回true,可使用$?验证(0为true,>1为false)[ romon ]#空返回false[ ][ condition ] && echo OK || echo notok 条件满足,执行后面的语句 常用判断条件 = 字符串比较-lt 小于-le 小于等于-eq 等于-gt 大于-ge 大于等于-ne 不等于-r 有读的权限-w 有写的权限-x 有执行的权限-f 文件存在并且是一个常规的文件-s 文件存在且不为空-d 文件存在并是一个目录-b文件存在并且是一个块设备-L 文件存在并且是一个链接 Shell自定义函数语法: 1234567 [ function ] funname [()]{ action; [return int;]}function start() / function start / start() 例子: 12345678910#!/bin/bashfSum 3 2; function fSum() { echo $1,$2; return $(($1+$2)); } fSum 5 7; total=$?; echo $total,$?; 注意: 必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其它语言一样先预编译 函数返回值,只能通过$? 系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255) 脚本调试 sh -vx helloWorld.sh 或者在脚本中增加set -x]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Shell</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Nginx + keepalived]]></title>
<url>%2F2017%2F02%2F02%2FNginx%20%2B%20keepalived%2F</url>
<content type="text"><![CDATA[Nginx相关概念反向代理 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。 负载均衡 负载均衡,英文名称为Load Balance,是指建立在现有网络结构之上,并提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。其原理就是数据流量分摊到多个服务器上执行,减轻每台服务器的压力,多台服务器共同完成工作任务,从而提高了数据的吞吐量。 Nginx的安装下载nginx 官网:http://nginx.org/ 上传并解压nginx命令:tar -zxvf nginx-1.8.1.tar.gz -C /usr/local/src 编译nginx 进入到nginx源码目录cd /usr/local/src/nginx-1.8.1 检查安装环境,并指定将来要安装的路径./configure --prefix=/usr/local/nginx 缺包报错 ./configure: error: C compiler cc is not found 使用YUM安装缺少的包yum -y install gcc pcre-devel openssl openssl-devel 编译安装make && make install 安装完后测试是否正常:/usr/loca/nginx/sbin/nginx 查看端口是否有ngnix进程监听netstat -ntlp | grep 80 配置nginx配置反向代理 修改nginx配置文件 1vim /usr/local/nginx/conf/nginx.conf 123456789server { listen 80; server_name demo.nginx.com; #nginx所在服务器的主机名#反向代理的配置location / { #拦截所有请求 root html; proxy_pass http://192.168.0.21:8080; #这里是代理走向的目标服务器:tomcat }} 启动配置的tomcat 启动nginx ./nginx 重启nginx ./nginx -s reload 关闭 查询nginx主进程号 ps -ef | grep nginx 从容停止kill -QUIT 主进程号 快速停止kill -TERM 主进程号 强制停止kill -9 nginx所有进程 若nginx.conf配置了pid文件路径,如果没有,则在logs目录下 kill -信号类型 ‘/usr/local/nginx/logs/nginx.pid’ 动静分离12345678#动态资源 index.jsplocation ~ .*\.(jsp|do|action)$ { proxy_pass http://tomcat-01.itcast.cn:8080;}#静态资源location ~ .*\.(html|js|css|gif|jpg|jpeg|png)$ { expires 3d;} 负载均衡 在http这个节下面配置一个叫upstream的,后面的名字可以随意取,但是要和location下的proxy_pass http://后的保持一致。 123456789101112http { 是在http里面的, 已有http, 不是在server里,在server外面 upstream tomcats { server shizhan02:8080 weight=1;#weight表示权重 数字越大越高 server shizhan03:8080 weight=1; server shizhan04:8080 weight=1; }#卸载server里location ~ .*\.(jsp|do|action) { proxy_pass http://tomcats; #tomcats是后面的tomcat服务器组的逻辑组号 }} 利用keepalived实现高可靠高可靠概念 HA(High Available), 高可用性集群,是保证业务连续性的有效解决方案,一般有两个或两个以上的节点,且分为活动节点及备用节点。 高可靠软件keepalived keepalive是一款可以实现高可靠的软件,通常部署在2台服务器上,分为一主一备。Keepalived可以对本机上的进程进行检测,一旦Master检测出某个进程出现问题,将自己切换成Backup状态,然后通知另外一个节点切换成Master状态。 keepalived安装 下载keepalived 官网:http://keepalived.org 安装 将keepalived解压到/usr/local/src目录下ar -zxvf keepalived-1.2.19.tar.gz -C /usr/local/src进入到/usr/local/src/keepalived-1.2.19目录d /usr/local/src/keepalived-1.2.19开始configure/configure –prefix=/usr/local/keepalived#编译并安装ake && make install 将keepalived添加到系统服务中 拷贝执行文件cp /usr/local/keepalived/sbin/keepalived /usr/sbin/ 将init.d文件拷贝到etc下,加入开机启动项cp /usr/local/keepalived/etc/rc.d/init.d/keepalived/etc/init.d/keepalived 将keepalived文件拷贝到etc下cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/ 创建keepalived文件夹mkdir -p /etc/keepalived 将keepalived配置文件拷贝到etc下cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf 添加可执行权限chmod +x /etc/init.d/keepalived 以上所有命令一次性执行(绝对路径):12345678cp /usr/local/keepalived/sbin/keepalived /usr/sbin/cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/keepalivedcp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/ mkdir -p /etc/keepalivedcp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.confchmod +x /etc/init.d/keepalivedchkconfig --add keepalived chkconfig keepalived on 添加keepalived到开机启动chkconfig --add keepalivedchkconfig keepalived on 配置keepalived虚拟IP 修改配置文件: /etc/keepalived/keepalived.conf 1234567891011121314151617181920212223242526272829303132333435#MASTER节点global_defs {}vrrp_instance VI_1 { state MASTER #指定A节点为主节点 备用节点上设置为BACKUP即可 interface eth0 #绑定虚拟IP的网络接口 virtual_router_id 51 #VRRP组名,两个节点的设置必须一样,以指明各个节点属于同一VRRP组 priority 100 #主节点的优先级(1-254之间),备用节点必须比主节点优先级低 advert_int 1 #组播信息发送间隔,两个节点设置必须一样 authentication { #设置验证信息,两个节点必须一致 auth_type PASS auth_pass 1111 } virtual_ipaddress { #指定虚拟IP, 两个节点设置必须一样 192.168.33.60/24 #如果两个nginx的ip分别是192.168.33.61,,...62,则此处的虚拟ip跟它俩同一个网段即可 }}#BACKUP节点global_defs {}vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 51 priority 99 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.33.60/24 }} 分别启动两台机器上的keepalived 启动命令:service keepalived start 测试: 杀掉master上的keepalived进程,你会发现,在slave机器上的eth0网卡多了一个ip地址 查看ip地址的命令: ip addr 配置keepalived心跳检查 原理: Keepalived并不跟nginx耦合,它俩完全不是一家人但是keepalived提供一个机制:让用户自定义一个shell脚本去检测用户自己的程序,返回状态给keepalived就可以了 MASTER节点1234567891011121314151617181920212223242526272829303132global_defs {}vrrp_script chk_health { script "[[ `ps -ef | grep nginx | grep -v grep | wc -l` -ge 2 ]] && exit 0 || exit 1" interval 1 #每隔1秒执行上述的脚本,去检查用户的程序ngnix weight -2}vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 1 priority 100 advert_int 2 authentication { auth_type PASS auth_pass 1111 } track_script { chk_health } virtual_ipaddress { 10.0.0.10/24 } notify_master "/usr/local/keepalived/sbin/notify.sh master" notify_backup "/usr/local/keepalived/sbin/notify.sh backup" notify_fault "/usr/local/keepalived/sbin/notify.sh fault"} 添加切换通知脚本 1vi /usr/local/keepalived/sbin/notify.sh 123456789101112131415161718192021#!/bin/bashcase "$1" in master) /usr/local/nginx/sbin/nginx exit 0 ;;backup) /usr/local/nginx/sbin/nginx -s stop /usr/local/nginx/sbin/nginx exit 0 ;; fault) /usr/local/nginx/sbin/nginx -s stop exit 0 ;; *) echo 'Usage: notify.sh {master|backup|fault}' exit 1 ;;esac 添加执行权限 chmod +x /usr/local/keepalived/sbin/notify.sh 1234567891011121314151617181920212223242526272829303132global_defs {}vrrp_script chk_health { script "[[ `ps -ef | grep nginx | grep -v grep | wc -l` -ge 2 ]] && exit 0 || exit 1" interval 1 weight -2}vrrp_instance VI_1 { state BACKUP interface eth0 virtual_router_id 1 priority 99 advert_int 1 authentication { auth_type PASS auth_pass 1111 } track_script { chk_health } virtual_ipaddress { 10.0.0.10/24 } notify_master "/usr/local/keepalived/sbin/notify.sh master" notify_backup "/usr/local/keepalived/sbin/notify.sh backup" notify_fault "/usr/local/keepalived/sbin/notify.sh fault"} 在第二台机器上添加notify.sh脚本分别在两台机器上启动keepalivedservice keepalived startchkconfig keepalived on]]></content>
<categories>
<category>Linux</category>
<category>Nginx</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Nginx</tag>
<tag>keepalived</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java并发编程学习的一些总结]]></title>
<url>%2F2016%2F08%2F26%2Fjava%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AD%A6%E4%B9%A0%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[1.不应用线程池的缺点 有些开发者图省事,遇到需要多线程处理的地方,直接new Thread(…).start(),对于一般场景是没问题的,但如果是在并发请求很高的情况下,就会有些隐患: 新建线程的开销。线程虽然比进程要轻量许多,但对于JVM来说,新建一个线程的代价还是挺大的,决不同于新建一个对象 资源消耗量。没有一个池来限制线程的数量,会导致线程的数量直接取决于应用的并发量,这样有潜在的线程数据巨大的可能,那么资源消耗量将是巨大的 稳定性。当线程数量超过系统资源所能承受的程度,稳定性就会成问题 2.制定执行策略 在每个需要多线程处理的地方,不管并发量有多大,需要考虑线程的执行策略 任务以什么顺序执行 可以有多少个任务并发执行 可以有多少个任务进入等待执行队列 系统过载的时候,应该放弃哪些任务?如何通知到应用程序? 一个任务的执行前后应该做什么处理 3.线程池的类型 不管是通过Executors创建线程池,还是通过Spring来管理,都得清楚知道有哪几种线程池: FixedThreadPool:定长线程池,提交任务时创建线程,直到池的最大容量,如果有线程非预期结束,会补充新线程 CachedThreadPool:可变线程池,它犹如一个弹簧,如果没有任务需求时,它回收空闲线程,如果需求增加,则按需增加线程,不对池的大小做限制 SingleThreadExecutor:单线程。处理不过来的任务会进入FIFO队列等待执行 SecheduledThreadPool:周期性线程池。支持执行周期性线程任务 其实,这些不同类型的线程池都是通过构建一个ThreadPoolExecutor来完成的,所不同的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory这么几个参数。具体可以参见JDK DOC。 4.线程池饱和策略 由以上线程池类型可知,除了CachedThreadPool其他线程池都有饱和的可能,当饱和以后就需要相应的策略处理请求线程的任务,比如,达到上限时通过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy几种策略,具体差异可见JDK DOC 5.线程无依赖性 多线程任务设计上尽量使得各任务是独立无依赖的,所谓依赖性可两个方面: 线程之间的依赖性。如果线程有依赖可能会造成死锁或饥饿 调用者与线程的依赖性。调用者得监视线程的完成情况,影响可并发量当然,在有些业务里确实需要一定的依赖性,比如调用者需要得到线程完成后结果,传统的Thread是不便完成的,因为run方法无返回值,只能通过一些共享的变量来传递结果,但在Executor框架里可以通过Future和Callable实现需要有返回值的任务,当然线程的异步性导致需要有相应机制来保证调用者能等待任务完成。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java的JMS技术]]></title>
<url>%2F2016%2F08%2F26%2Fjava%E7%9A%84JMS%E6%8A%80%E6%9C%AF%2F</url>
<content type="text"><![CDATA[什么是JMS JMS即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。 JMS是一种与厂商无关的 API,用来访问消息收发系统消息。它类似于JDBC(Java Database Connectivity):这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。许多厂商都支持 JMS,包括 IBM 的 MQSeries、BEA的 Weblogic JMS service和 Progress 的 SonicMQ,这只是几个例子。 JMS 使您能够通过消息收发服务(有时称为消息中介程序或路由器)从一个 JMS 客户机向另一个 JMS客户机发送消息。消息是 JMS 中的一种类型对象,由两部分组成:报头和消息主体。报头由路由信息以及有关该消息的元数据组成。消息主体则携带着应用程序的数据或有效负载。根据有效负载的类型来划分,可以将消息分为几种类型,它们分别携带:简单文本(TextMessage)、可序列化的对象 (ObjectMessage)、属性集合 (MapMessage)、字节流 (BytesMessage)、原始值流 (StreamMessage),还有无有效负载的消息 (Message)。 JMS规范专业技术规范 JMS(Java Messaging Service)是Java平台上有关面向消息中间件(MOM)的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发,翻译为Java消息服务。 体系架构 JMS由以下元素组成。 JMS提供者provider:连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。 JMS客户:生产或消费基于消息的Java的应用程序或对象。 JMS生产者:创建并发送消息的JMS客户。 JMS消费者:接收消息的JMS客户。 JMS消息:包括可以在JMS客户之间传递的数据的对象 JMS队列:一个容纳那些被发送的等待阅读的消息的区域。与队列名字所暗示的意思不同,消息的接受顺序并不一定要与消息的发送顺序相同。一旦一个消息被阅读,该消息将被从队列中移走。 JMS主题:一种支持发送消息给多个订阅者的机制。 Java消息服务应用程序结构支持两种模型点对点或队列模型 在点对点或队列模型下,一个生产者向一个特定的队列发布消息,一个消费者从该队列中读取消息。这里,生产者知道消费者的队列,并直接将消息发送到消费者的队列。 这种模式被概括为:只有一个消费者将获得消息生产者不需要在接收者消费该消息期间处于运行状态,接收者也同样不需要在消息发送时处于运行状态。每一个成功处理的消息都由接收者签收 发布者/订阅者模型 发布者/订阅者模型支持向一个特定的消息主题发布消息。0或多个订阅者可能对接收来自特定消息主题的消息感兴趣。在这种模型下,发布者和订阅者彼此不知道对方。这种模式好比是匿名公告板。 这种模式被概括为: 多个消费者可以获得消息 在发布者和订阅者之间存在时间依赖性。发布者需要建立一个订阅(subscription),以便客户能够订阅。订阅者必须保持持续的活动状态以接收消息,除非订阅者建立了持久的订阅。在那种情况下,在订阅者未连接时发布的消息将在订阅者重新连接时重新发布。 常用的JMS实现 要使用Java消息服务,你必须要有一个JMS提供者,管理会话和队列。既有开源的提供者也有专有的提供者。 开源的提供者包括: Apache ActiveMQBoss 社区所研发的 HornetQJoramCoridan的MantaRayhe OpenJMS Group的OpenJMS 专有的提供者包括: BEA的BEA WebLogic Server JMSIBCO Software的EMSigaSpaces Technologies的GigaSpacesoftwired 2006的iBusONA Technologies的IONA JMSeeBeyond的IQManager(2005年8月被Sun Microsystems并购)ebMethods的JMS+ -my-channels的Nirvanaonic Software的SonicMQSwiftMQ的SwiftMQBM的WebSphere MQ]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>JMS</tag>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[多个git账号之间的切换]]></title>
<url>%2F2016%2F08%2F06%2F%E5%A4%9A%E4%B8%AAgit%E8%B4%A6%E5%8F%B7%E4%B9%8B%E9%97%B4%E7%9A%84%E5%88%87%E6%8D%A2%2F</url>
<content type="text"><![CDATA[介绍所谓多个git账号,可能有两种情况: 我有多个github的账号,不同的账号对应不同的repo,需要push的时候自动区分账号 我有多个git的账号,有的是github的,有的是bitbucket的,有的是单位的gitlab的,不同账号对应不同的repo,需要push的时候自动区分账号 这两种情况的处理方法是一样的,分下面几步走: 处理 先假设我有两个账号,一个是github上的,一个是公司gitlab上面的。先为不同的账号生成不同的ssh-key ssh-keygen -t rsa -f ~/.ssh/id_rsa_work -C [email protected] 然后根据提示连续回车即可在~/.ssh目录下得到id_rsa_work和id_rsa_work.pub两个文件,id_rsa_work.pub文件里存放的就是我们要使用的key ssh-keygen -t rsa -f ~/.ssh/id_rsa_github -C [email protected] 然后根据提示连续回车即可在~/.ssh目录下得到id_rsa_github和id_rsa_github.pub两个文件,id_rsa_gthub.pub文件里存放的就是我们要使用的key 把id_rsa_xxx.pub中的key添加到github或gitlab上,这一步在github或gitlab上都有帮助,不再赘述 编辑 ~/.ssh/config,设定不同的git 服务器对应不同的key 123456789101112# Default github user([email protected]),注意User项直接填git,不用填在github的用户名Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa_github# second user([email protected])# 建一个gitlab别名,新建的帐号使用这个别名做克隆和更新Host 172.16.11.11 HostName 172.16.11.11 User work IdentityFile ~/.ssh/id_rsa_work 编辑完成后可以使用命令 ssh -vT [email protected] 看看是不是采用了正确的id_rsa_github.pub文件 这样每次push的时候系统就会根据不同的仓库地址使用不同的账号提交了 从上面一步可以看到,ssh区分账号,其实靠的是HostName这个字段,因此如果在github上有多个账号,很容易的可以把不同的账号映射到不同的HostName上就可以了。比如我有A和B两个账号, 先按照步骤一生成不同的key文件,再修改~/.ssh/config 内容应该是这样的。 123456789101112# Default github user([email protected]),注意User项直接填git,不用填在github的用户名Host A.github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa_github_A# second user([email protected])# 建一个gitlab别名,新建的帐号使用这个别名做克隆和更新Host A.github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa_github_B 同时你的github的repo ssh url就要做相应的修改了,比如根据上面的配置,原连接地址是: [email protected]:testA/gopkg.git 那么根据上面的配置,就要把github.com换成A.github.com, 那么ssh解析的时候就会自动把testA.github.com 转换为 github.com,修改后就是 [email protected]:testA/gopkg.git 直接更改 repo/.git/config 里面的url即可 这样每次push的时候系统就会根据不同的仓库地址使用不同的账号提交了 一些题外话我有一个repo,想要同时push到不同的仓库该如何设置?很简单, 直接更改 repo/.git/config 里面的url即可,把里面对应tag下的url增加一个就可以了。例: 123456789101112131415[remote "GitHub"] url = [email protected]:elliottcable/Paws.o.git fetch = +refs/heads/*:refs/remotes/GitHub/*[branch "Master"] remote = GitHub merge = refs/heads/Master[remote "Codaset"] url = [email protected]:elliottcable/paws-o.git fetch = +refs/heads/*:refs/remotes/Codaset/*[remote "Paws"] url = [email protected]:Paws/Paws.o.git fetch = +refs/heads/*:refs/remotes/Paws/*[remote "Origin"] url = [email protected]:Paws/Paws.o.git url = [email protected]:elliottcable/paws-o.git 上面这个立即就是有4个远端仓库,不同的tag表示不同的远端仓库,最后的Origin标签写法表示默认push到github和codaset这两个远端仓库去。当然,你可以自己随意定制tag和url 我有一个github的repo,clone没有问题,push的时候总是报错:error: The requested URL returned error: 403 while accessing xxx这个问题也困扰了我一段时间,后来发现修改 repo/.git/config 里面的url,把https地址替换为ssh就好了。 例如 1url=https://[email protected]/derekerdmann/lunch_call.git 替换为 1url=ssh://[email protected]/derekerdmann/lunch_call.git 参考http://stackoverflow.com/questions/7438313/pushing-to-git-returning-error-code-403-fatal-http-request-failed http://stackoverflow.com/questions/849308/pull-push-from-multiple-remote-locations/3195446#3195446]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CentOS 7开放端口和关闭防火墙]]></title>
<url>%2F2016%2F07%2F18%2FCentOS%207%E5%BC%80%E6%94%BE%E7%AB%AF%E5%8F%A3%E5%92%8C%E5%85%B3%E9%97%AD%E9%98%B2%E7%81%AB%E5%A2%99%2F</url>
<content type="text"><![CDATA[开放端口永久的开放需要的端口 12sudo firewall-cmd --zone=public --add-port=3306/tcp --permanentsudo firewall-cmd --reload 之后检查新的防火墙规则 1firewall-cmd --list-all 关闭防火墙由于只是用于开发环境,所以打算把防火墙关闭掉 12345678910111213//临时关闭防火墙,重启后会重新自动打开systemctl restart firewalld//检查防火墙状态firewall-cmd --statefirewall-cmd --list-all//Disable firewallsystemctl disable firewalldsystemctl stop firewalldsystemctl status firewalld//Enable firewallsystemctl enable firewalldsystemctl start firewalldsystemctl status firewalld]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Centos 7</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Centos升级Python 2.7.12并安装最新pip]]></title>
<url>%2F2016%2F06%2F15%2FCentos%E5%8D%87%E7%BA%A7Python%202.7.12%E5%B9%B6%E5%AE%89%E8%A3%85%E6%9C%80%E6%96%B0pip%2F</url>
<content type="text"><![CDATA[Centos升级Python 2.7.12并安装最新pipCentos系统一般默认就安装有Python2.6.6版本,不少软件需要2.7以上的,通过包管理工具安装不了最新的版本,通过源码编译可以方便安装指定版本,只需要把下面版本的数字换成你想要的版本号。 1.安装步骤下载源码 wget http://www.python.org/ftp/python/2.7.12/Python-2.7.12.tgz 在下载目录解压源码 tar -zxvf Python-2.7.12.tgz 进入解压后的文件夹 cd Python-2.7.12 在编译前先在/usr/local建一个文件夹python2.7.12(作为python的安装路径,以免覆盖老的版本,新旧版本可以共存的) mkdir /usr/local/python2.7.12 ,编译前需要安装下面依赖,否则下面安装pip就会出错 yum install openssl openssl-devel zlib-devel gcc -y 安装完依赖后执行下面命令 vim ./Modules/Setup 找到#zlib zlibmodule.c -I$(prefix)/include -L$(exec_prefix)/lib -lz去掉注释并保存(即去掉井号) 在解压缩后的目录下编译安装 123./configure --prefix=/usr/local/python2.7.12 --with-zlibmake make install 此时没有覆盖老版本,再将原来/usr/bin/python链接改为别的名字 mv /usr/bin/python /usr/bin/python2.6.6 再建立新版本python的软链接 ln -s /usr/local/python2.7.12/bin/python2.7 /usr/bin/python 这个时候输入 python 就会显示出python的新版本信息 123Python 2.7.12 (default, Oct 13 2016, 03:17:14) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)] on linux2Type “help”, “copyright”, “credits” or “license” for more information. 2.修改yum配置文件之所以要保留旧版本,因为yum依赖Python2.6,改下yum的配置文件,指定旧的Python版本就可以了。 vim /usr/bin/yum, 将第一行的#!/usr/bin/python修改成#!/usr/bin/python2.6.6 3.安装最新版本的pipwget https://bootstrap.pypa.io/get-pip.py python get-pip.py 找到pip2.7的路径 find / -name "pip*" 上面的命令输出 /root/.cache/pip 这里省略一堆输出 123456/usr/local/python2.7.12/bin/pip/usr/local/python2.7.12/bin/pip2/usr/local/python2.7.12/bin/pip2.7 #就是这个/usr/bin/pip/usr/bin/pip2/usr/bin/pip2.6 为其创建软链作为系统默认的启动版本(之前有旧版本的话就先删掉rm -rf /usr/bin/pip) ln -s /usr/local/python2.7.12/bin/pip2.7 /usr/bin/pip 看下pip的版本 pip -V 1pip 8.1.2 from /usr/local/python2.7.12/lib/python2.7/site-packages (python 2.7) pip安装完毕,现在可以用它下载安装各种包了 我把上面的所有写成下面简单的脚本,一键就可以升级好。 wget http://7xpt4s.com1.z0.glb.clouddn.com/update-python2.7.12.sh && bash update-python2.7.12.sh 参考: https://ruter.github.io/2015/12/03/Update-python/ https://blog.phpgao.com/pip-easy_install-setuptool.html 原文地址:https://blog.fazero.me/2016/10/13/centos-update-python/]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CentOS64位下python2.6升级到2.7的详细教程]]></title>
<url>%2F2016%2F06%2F15%2FCentOS64%E4%BD%8D%E4%B8%8Bpython2.6%E5%8D%87%E7%BA%A7%E5%88%B02.7%E7%9A%84%E8%AF%A6%E7%BB%86%E6%95%99%E7%A8%8B%2F</url>
<content type="text"><![CDATA[1)安装devtoolset 1yum groupinstall "Development tools" 2) 安装编译Python需要的包 12345yum install zlib-develyum install bzip2-develyum install openssl-develyum install ncurses-develyum install sqlite-devel 3)下载并解压Python 2.7.9的源代码 1234cd /optwget --no-check-certificate https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tar.xztar xf Python-2.7.9.tar.xzcd Python-2.7.9 4)编译与安装Python 2.7.9 12./configure --prefix=/usr/localmake && make altinstall 5)配置软连接,让系统pyhon默认指向python2.7.9 mv /usr/bin/python /usr/bin/python2.6_temp ln -s /usr/local/bin/python2.7 /usr/bin/python python -V # 查看Python 2.7.9 6)yum与python2.7不兼容,修改yum文件 vi /usr/bin/yum 将”!/usr/bin/python”改为”!/usr/bin/python2.6” 7)配置环境变量 vi /etc/profile # 添加如下内容: PY_HOME=/usr/local/bin/python2.7 export PATH=\$PY_HOME/bin:$PATH 8)启动环境变量配置 source /etc/profile # 当前终端生效,reboot后才会完全生效 echo $PATH #查看环境变量中是否包含python路径 9)完美升级完成]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Centos 6</tag>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java多线程-线程安全]]></title>
<url>%2F2016%2F05%2F18%2Fjava%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%2F</url>
<content type="text"><![CDATA[什么是线程安全和线程不安全 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 java内置的线程锁机制关键字synchronized synchronized是java中的一个关键字,也就是说是Java语言内置的特性。 实现方式 加同步格式: 12345> synchronized( 需要一个任意的对象(锁) ){> 代码块中放操作共享数据的代码。> }>> synchronized的缺陷 如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 获取锁的线程执行完了该代码块,然后线程释放对锁的占有; 线程执行发生异常,此时JVM会让线程自动释放锁。 例子1: 如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。 例子2: 当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。 但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。 另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。 总的来说,也就是说Lock提供了比synchronized更多的功能。 Lock 首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口 1234567public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); } Lock接口中每个方法的使用: lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的。 unLock()方法是用来释放锁的。 四个获取锁方法的区别: lock()方法 是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。 由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。 tryLock()方法 是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 tryLock(long time, TimeUnit unit)方法 和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。 lockInterruptibly()方法 比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。 因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。 而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。 Lock和synchronized的区别 Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问; Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。 ReentrantLock 直接使用lock接口的话,我们需要实现很多方法,不太方便,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”。 lock()的使用方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import java.util.ArrayList;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class MyLockTest { private static ArrayList<Integer> arrayList = new ArrayList<Integer>(); static Lock lock = new ReentrantLock(); // 注意这个地方 public static <E> void main(String[] args) { new Thread() { public void run() { Thread thread = Thread.currentThread(); lock.lock(); try { System.out.println(thread.getName() + "得到了锁"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "释放了锁"); lock.unlock(); } }; }.start(); new Thread() { public void run() { Thread thread = Thread.currentThread(); lock.lock(); try { System.out.println(thread.getName() + "得到了锁"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "释放了锁"); lock.unlock(); } }; }.start(); }} tryLock()的使用方法12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import java.util.ArrayList;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 观察现象:一个线程获得锁后,另一个线程取不到锁,不会一直等待 * @author * */public class MyTryLock { private static ArrayList<Integer> arrayList = new ArrayList<Integer>(); static Lock lock = new ReentrantLock(); // 注意这个地方 public static void main(String[] args) { new Thread() { public void run() { Thread thread = Thread.currentThread(); boolean tryLock = lock.tryLock(); System.out.println(thread.getName()+" "+tryLock); if (tryLock) { try { System.out.println(thread.getName() + "得到了锁"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "释放了锁"); lock.unlock(); } } }; }.start(); new Thread() { public void run() { Thread thread = Thread.currentThread(); boolean tryLock = lock.tryLock(); System.out.println(thread.getName()+" "+tryLock); if (tryLock) { try { System.out.println(thread.getName() + "得到了锁"); for (int i = 0; i < 5; i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception } finally { System.out.println(thread.getName() + "释放了锁"); lock.unlock(); } } }; }.start(); }} lockInterruptibly()响应中断的使用方法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 观察现象:如果thread-0得到了锁,阻塞。。。thread-1尝试获取锁,如果拿不到,则可以被中断等待 * @author * */public class MyInterruptibly { private Lock lock = new ReentrantLock(); public static void main(String[] args) { MyInterruptibly test = new MyInterruptibly(); MyThread thread0 = new MyThread(test); MyThread thread1 = new MyThread(test); thread0.start(); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread1.interrupt(); System.out.println("====================="); } public void insert(Thread thread) throws InterruptedException{ lock.lockInterruptibly(); //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出 try { System.out.println(thread.getName()+"得到了锁"); long startTime = System.currentTimeMillis(); for( ; ;) { if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插入数据 } } finally { System.out.println(Thread.currentThread().getName()+"执行finally"); lock.unlock(); System.out.println(thread.getName()+"释放了锁"); } } } class MyThread extends Thread { private MyInterruptibly test = null; public MyThread(MyInterruptibly test) { this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+"被中断"); } }} ReadWriteLock读写锁 ReadWriteLock也是一个接口,在它里面只定义了两个方法. public interface ReadWriteLock {/** Returns the lock used for reading.* @return the lock used for reading.*/Lock readLock(); /** Returns the lock used for writing.* @return the lock used for writing.*/Lock writeLock();} 一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。 ReentrantReadWriteLock ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768import java.util.concurrent.locks.ReentrantReadWriteLock;/** * 使用读写锁,可以实现读写分离锁定,读操作并发进行,写操作锁定单个线程 * * 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。 * 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。 * @author * */public class MyReentrantReadWriteLock { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final MyReentrantReadWriteLock test = new MyReentrantReadWriteLock(); new Thread(){ public void run() { test.get(Thread.currentThread()); test.write(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.get(Thread.currentThread()); test.write(Thread.currentThread()); }; }.start(); } /** * 读操作,用读锁来锁定 * @param thread */ public void get(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在进行读操作"); } System.out.println(thread.getName()+"读操作完毕"); } finally { rwl.readLock().unlock(); } } /** * 写操作,用写锁来锁定 * @param thread */ public void write(Thread thread) { rwl.writeLock().lock();; try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在进行写操作"); } System.out.println(thread.getName()+"写操作完毕"); } finally { rwl.writeLock().unlock(); } }} 注意事项 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。 Lock和synchronized的选择 Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现; synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁; Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断; 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到. Lock可以提高多个线程进行读操作的效率。 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[git 提交前强制检查各个项目用户名邮箱设置]]></title>
<url>%2F2016%2F05%2F12%2Fgit%20%E6%8F%90%E4%BA%A4%E5%89%8D%E5%BC%BA%E5%88%B6%E6%A3%80%E6%9F%A5%E5%90%84%E4%B8%AA%E9%A1%B9%E7%9B%AE%E7%94%A8%E6%88%B7%E5%90%8D%E9%82%AE%E7%AE%B1%E8%AE%BE%E7%BD%AE%2F</url>
<content type="text"><![CDATA[保证提交日志的准确性在提交时,user.name, user.email会进入日志。这些信息,是追踪代码变更的关键。 我们公司为了保证这些信息的准确性,在push时,强制检查,如果user.name和user.email信息不正确,则拒绝push。 全局配置 如果我们工作中只涉及一个git服务器,用一个全局配置就可以搞定了: 12git config --global user.name "huqiu.lhq"git config --global user.email "[email protected]" 工作在多个git项目但是我们可能同时工作在多个项目中,公司内部用自有的git管理项目,我们在github上还有自己的项目。 对于使用不同的用户身份,需要设置不用的sshkey,具体的配置可以看这里:多个sshkey配置 这个时候,对于user.name和user.email我们不能采用全局的配置。而是要对各个项目单独配置。 项目配置 12git config user.name "huqiu.lhq"git config user.email "[email protected]" 忘记了做配置对于项目配置,有时我们会忘记在git init或者git clone之后,配置user.name以及user.email。 如果有全局配置,则使用全局配置。如果没全局配置,报错。 12345678910111213[huqiu@srain test]$ git ci -a -m 'commit for testing no user.name empty'*** Please tell me who you are.Rungit config --global user.email "[email protected]"git config --global user.name "Your Name"to set your account's default identity.Omit --global to set the identity only in this repository.fatal: empty ident name (for <[email protected]>) not allowed 报错能够及时纠正我们的错误,最糟糕的情况是: 没有项目单独配置,提交的时候,自动采用全局配置。在发现问题之后需要对日志进行修复。 强制检查强制检查可以在服务器端push的时候检查,也可以在客户端进行检查,这里介绍使用`pre-commit钩子进行检查。 全局钩子的配置,可以参见这里: git全局钩子 如何确定配置正确 确定没有全局配置 确定有项目配置 pre-commit hook 1234567891011121314global_email=$(git config --global user.email)global_name=$(git config --global user.name)repository_email=$(git config user.email)repository_name=$(git config user.name)if [ -z "$repository_email" ] || [ -z "$repository_name" ] || [ -n "$global_email" ] || [ -n "$global_name" ]; then # user.email is empty echo "ERROR: [pre-commit hook] Aborting commit because user.email or user.name is missing. Configure them for this repository. Make sure not to configure globally." exit 1else # user.email is not empty exit 0fi 直接可用的代码上面谈到的: pre-commit 的源码 自动设置全局钩子的脚本(其中包含了这个强制检查的pre-commit钩子),并对git做一些易用配置: update-git-config.sh]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux mini安装常见问题]]></title>
<url>%2F2016%2F04%2F09%2FLinux%20mini%E5%AE%89%E8%A3%85%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[yum安装网络不可达原因 我们来查看一下,当前用来上网的网卡eth1的信息: [root@promote ~]# ifconfig eth1th1 Link encap:Ethernet HWaddr 00:0C:29:D4:EA:E2 12345678910> inet addr:192.168.1.115 Bcast:255.255.255.255 Mask:255.255.255.0> inet6 addr: fe80::20c:29ff:fed4:eae2/64 Scope:Link> UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1> RX packets:4599 errors:0 dropped:0 overruns:0 frame:0> TX packets:2628 errors:0 dropped:0 overruns:0 carrier:0> collisions:0 txqueuelen:1000 > RX bytes:575396 (561.9 KiB) TX bytes:278925 (272.3 KiB)> Interrupt:19 Base address:0x2000 >> 我们清楚地看见了inet6 addr: fe80::20c:29ff:fed4:eae2/64 Scope:Link,这一行信息,这个正是影响我们使用yum的罪魁祸首!!!下面我们就来关闭网卡的IPV6。 临时解决方法 临时关闭网卡的IPV6,并重启网络服务: [root@promote ~]# echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6root@promote ~]# echo 1 > /proc/sys/net/ipv6/conf/default/disable_ipv6root@promote ~]# service network restart在关闭接口 eth1: [确定]闭环回接口: [确定]出环回接口: [确定]出界面 eth1:在决定 eth1 的 IP 信息…完成。[确定] 此时,再查看网卡eth1的信息: [root@promote ~]# ifconfig eth1th1 Link encap:Ethernet HWaddr 00:0C:29:D4:EA:E2inet addr:192.168.1.115 Bcast:255.255.255.255 Mask:255.255.255.0UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:5563 errors:0 dropped:0 overruns:0 frame:0TX packets:3388 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:676945 (661.0 KiB) TX bytes:414358 (404.6 KiB)Interrupt:19 Base address:0x2000 没错,我们发现inet6 addr: fe80::20c:29ff:fed4:eae2/64 Scope:Link这一行信息没有了,此时yum就可以正常使用了; 永久生效方法 需要在文件/etc/sysctl.conf中,添加如下的内容: #shutdown IPv6et.ipv6.conf.all.disable_ipv6 = 1et.ipv6.conf.default.disable_ipv6 = 1 minimal最小化安装 eth0默认没有自启用 修改配置文件 1vi /etc/sysconfig/network-scripts/ifcfg-eth0 onboot=true 修改静态地址后发现无法ping外网 需要设置网关(该命令只是临时有效) route add default gw 192.168.33.1(网关地址 参考vmware的网关地址) 添加nameserver vi /etc/resolv.conf nameserver 192.168.33.1 #vmware的网关地址 解决克隆后eth0不见的问题 直接修改 /etc/sysconfig/network-script/ifcfg-eth0 删掉UUID HWADDR 配置静态地址 然后: rm -rf /etc/udev/rules.d/70-persistent-net.rules 然后 reboot 挂载光驱 创建需要挂载的目录 mkdir /mnt/cdrom 挂载光驱 mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom/ 配置开机自动挂载 vim /etc/fstab /dev/cdrom /mnt/cdrom iso9660 defaults 0 0 安装scp yum install -y openssh-clients]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linxu RPM包管理]]></title>
<url>%2F2016%2F04%2F07%2FLinxu%20RPM%E5%8C%85%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[RPM软件包管理 RPM是RedHat Package Manager(RedHat软件包管理工具)的缩写,这一文件格式名称虽然打上了RedHat的标志,但是其原始设计理念是开放式的,现在包括RedHat、CentOS、SUSE等Linux的分发版本都有采用,可以算是公认的行业标准了。RPM文件在Linux系统中的安装最为简便 RPM命令使用 rpm的常用参数 i:安装应用程序(install) e:卸载应用程序(erase) vh:显示安装进度;(verbose hash) U:升级软件包;(update) qa: 显示所有已安装软件包(query all) 结合grep命令使用 例子:rmp -ivh gcc-c++-4.4.7-3.el6.x86_64.rpm 批量自动删除rpm包 rpm -qa | grep mysql | while read c; do rpm -e $c --nodeps; done]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux基本学习]]></title>
<url>%2F2016%2F04%2F02%2FLinux%E5%9F%BA%E6%9C%AC%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[Linux目录结构 Linux命令的分类内部命令:属于Shell解析器的一部分 cd 切换目录(change directory) pwd 显示当前工作目录(print working directory) help 帮助 echo : 输出到控制台 外部命令:独立于Shell解析器之外的文件程序 ls 显示文件和目录列表(list) mkdir 创建目录(make directoriy) cp 复制文件或目录(copy) 查看帮助文档 内部命令:help + 命令(help cd) 外部命令:man + 命令(man ls) free 显示当前内存和交换空间的使用情况 关机/重启命令 shutdown系统关机 -r 关机后立即重启 -h 关机后不重新启动 halt 关机后关闭电源 shutdown -h reboot 重新启动 shutdown -r 学习Linux的好习惯 善于查看man page(manual)等帮助文档 利用好Tab键 掌握好一些快捷键 ctrl + c(停止当前进程) ctrl + r(查看命令历史) ctrl + l(清屏,与clear命令作用相同)]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux 后台服务管理]]></title>
<url>%2F2016%2F04%2F02%2FLinux%20%E5%90%8E%E5%8F%B0%E6%9C%8D%E5%8A%A1%E7%AE%A1%E7%90%86%2F</url>
<content type="text"><![CDATA[后台服务管理 service network status 查看指定服务的状态 service network stop 停止指定服务 service network start 启动指定服务 service network restart 重启指定服务 service –status-all 查看系统中所有的后台服务 设置后台服务的自启配置 chkconfig 查看所有服务器自启配置 chkconfig iptables off 关掉指定服务的自动启动 chkconfig iptables on 开启指定服务的自动启动]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux sort 排序命令&&uniq去重复行]]></title>
<url>%2F2016%2F04%2F02%2FLinux%20sort%20%E6%8E%92%E5%BA%8F%E5%91%BD%E4%BB%A4%26%26uniq%E5%8E%BB%E9%87%8D%E5%A4%8D%E8%A1%8C%2F</url>
<content type="text"><![CDATA[sort命令 sort 命令对 File 参数指定的文件中的行排序,并将结果写到标准输出。如果 File 参数指定多个文件,那么 sort 命令将这些文件连接起来,并当作一个文件进行排序。 sort语法 sort [-fbMnrtuk] [file or stdin]选项与参数:f :忽略大小写的差异,例如 A 与 a 视为编码相同;b :忽略最前面的空格符部分;M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;n :使用『纯数字』进行排序(默认是以文字型态来排序的);r :反向排序;u :就是 uniq ,相同的数据中,仅出现一行代表;t :分隔符,默认是用 [tab] 键来分隔;k :以那个区间 (field) 来进行排序的意思 举个栗子 对/etc/passwd 的账号进行排序 语句:cat /etc/passwd | sort 结果:sort 是默认以第一个数据来排序,而且默认是以字符串形式来排序,所以由字母 a 开始升序排序。 adm:x:3:4:adm:/var/adm:/sbin/nologinapache:x:48:48:Apache:/var/www:/sbin/nologinbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologin 以 : 来分隔的,以第三栏来排序 语句:cat /etc/passwd | sort -t ':' -k 3 结果: root:x:0:0:root:/root:/bin/bashuucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologinoperator:x:11:0:operator:/root:/sbin/nologinbin:x:1:1:bin:/bin:/sbin/nologingames:x:12:100:games:/usr/games:/sbin/nologin 默认是以字符串来排序的,如果想要使用数字排序: 语句:cat /etc/passwd | sort -t ':' -k 3n 结果: root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/bin/shbin:x:2:2:bin:/bin:/bin/sh 默认是升序排序,如果要倒序排序,如下 语句:/etc/passwd | sort -t ':' -k 3nr 结果: nobody:x:65534:65534:nobody:/nonexistent:/bin/shntp:x:106:113::/home/ntp:/bin/falsemessagebus:x:105:109::/var/run/dbus:/bin/falsesshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin 如果要对/etc/passwd,先以第六个域的第2个字符到第4个字符进行正向排序,再基于第一个域进行反向排序。 语句:cat /etc/passwd | sort -t':' -k 6.2,6.4 -k 1r 结果: sync:x:4:65534:sync:/bin:/bin/syncproxy:x:13:13:proxy:/bin:/bin/shbin:x:2:2:bin:/bin:/bin/shsys:x:3:3:sys:/dev:/bin/sh 查看/etc/passwd有多少个shell:对/etc/passwd的第七个域进行排序,然后去重 语句:/etc/passwd | sort -t':' -k 7 -u 结果: root:x:0:0:root:/root:/bin/bashsyslog:x:101:102::/home/syslog:/bin/falsedaemon:x:1:1:daemon:/usr/sbin:/bin/shsync:x:4:65534:sync:/bin:/bin/syncsshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin uniq命令 uniq命令可以去除排序过的文件中的重复行,因此uniq经常和sort合用。也就是说,为了使uniq起作用,所有的重复行必须是相邻的。 uniq语法 语法:uniq [-icu] 选项与参数: -i :忽略大小写字符的不同; -c :进行计数; -u :只显示唯一的行; 举个栗子 testfile文件内容 helloworldfriendhelloworldhello 语句:uniq testfile 结果:直接删除未经排序的文件,将会发现没有任何行被删除 helloworldfriendhelloworldhello 语句:cat testfile | sort |uniq 结果:排序文件,默认是去重 friendhelloworld 语句:sort testfile | uniq -c 结果:排序之后删除了重复行,同时在行首位置输出该行重复的次数 1 friendhelloworld 语句:sort testfile | uniq -dc 结果:仅显示存在重复的行,并在行首显示该行重复的次数 3 helloworld 语句:sort testfile | uniq -u 结果:仅显示不重复的行 friend]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>