-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfeed.xml
5346 lines (4980 loc) · 275 KB
/
feed.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"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>Jimersy Lee's Blog</title>
<description>这是Jimersy Lee的个人博客</description>
<link>/</link>
<atom:link href="//feed.xml" rel="self" type="application/rss+xml" />
<pubDate>2019-07-26 15:07:50</pubDate>
<lastBuildDate>2019-07-26 15:07:50</lastBuildDate>
<generator>Gitblog v1.0</generator>
<item>
<title>go-web程序的热更新</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2018-09-30
title: go-web程序的热更新
tags: GO
images:
category: go
status: publish
summary: 一直编译累死人啊,该偷懒就得偷懒
-->
<p>当使用go开发web程序时,修改点代码就得编译,虽然编译速度很快,但是也累啊,想起java的spring-boot有热更新插件,
php根本都不需要重启,go怎么可以落后。</p>
<p>一顿搜索后,找到了<a href="https://github.com/codegangsta/gin">gin</a>和<a href="https://github.com/pilu/fresh">fresh</a>,都挺好用的</p>
<h2>gin</h2>
<pre><code class="language-shell">cd path/to/app
gin run main.go
</code></pre>
<h2>fresh</h2>
<pre><code class="language-shell">cd path/to/app
fresh</code></pre>
<p>懒人有懒福~</p> </description>
<pubDate>2019-07-26 14:45:14</pubDate>
<link>//blog/go/go-hot-update.html</link>
<guid isPermaLink="true">//blog/go/go-hot-update.html</guid>
<category>go</category>
</item>
<item>
<title>Manjaro安装fusuma</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2018-09-12
title: Manjaro安装fusuma
tags: MANJARO,fusuma
images:
category: linux
status: publish
summary: 让gnome桌面环境的笔记本支持多指触控手势
-->
<p>环境:Manjaro</p>
<pre><code> ██████████████████ ████████ jimersylee@jimersylee-laptop
██████████████████ ████████ OS: Manjaro 17.1.12 Hakoila
██████████████████ ████████ Kernel: x86_64 Linux 4.14.67-1-MANJARO
██████████████████ ████████ Uptime: 3h 12m
████████ ████████ Packages: 1184
████████ ████████ ████████ Shell: zsh 5.5.1
████████ ████████ ████████ Resolution: 1920x1080
████████ ████████ ████████ DE: GNOME
████████ ████████ ████████ WM: GNOME Shell
████████ ████████ ████████ WM Theme: Adapta-Nokto-Maia
████████ ████████ ████████ GTK Theme: Adapta-Nokto-Maia [GTK2/3]
████████ ████████ ████████ Icon Theme: Papirus-Adapta-Maia
████████ ████████ ████████ Font: Noto Sans 11
████████ ████████ ████████ CPU: Intel Core i7-8550U @ 8x 4GHz [47.0°C]
GPU: Mesa DRI Intel(R) UHD Graphics 620 (Kabylake GT2)
RAM: 6157MiB / 15928MiB
</code></pre>
<h1>将用户加入输入组</h1>
<pre><code class="language-shell">sudo gpasswd -a $USER input
sudo reboot</code></pre>
<h1>安装ruby环境,安装fusuma包</h1>
<pre><code class="language-shell">#安装ruby
sudo pacman -S ruby
sudo gem install fusuma
#</code></pre>
<h1>配置</h1>
<pre><code class="language-shell">#将ruby程序目录加入路径
echo "export PATH=$PATH:/home/jimersylee/.gem/ruby/2.5.0/bin" &gt;&gt; ~/.profile
source ~/.profile
#人工启动
fusuma -d
#加入开机启动
/usr/share/applications 新建一个.desktop快捷方式,配置好
使用tweaks 添加 startup application </code></pre> </description>
<pubDate>2019-07-26 14:45:18</pubDate>
<link>//blog/linux/installFusumaOnManjaro.html</link>
<guid isPermaLink="true">//blog/linux/installFusumaOnManjaro.html</guid>
<category>linux</category>
</item>
<item>
<title>Manjaro安装Mariadb</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2018-09-09
title: Manjaro安装Mariadb
tags: MANJARO,MARIADB
images:
category: devops
status: publish
summary: 在manjaro系统上安装mysql与其他系统稍有不同
-->
<p>环境:Manjaro</p>
<pre><code> ██████████████████ ████████ jimersylee@jimersylee-laptop
██████████████████ ████████ OS: Manjaro 17.1.12 Hakoila
██████████████████ ████████ Kernel: x86_64 Linux 4.14.67-1-MANJARO
██████████████████ ████████ Uptime: 3h 12m
████████ ████████ Packages: 1184
████████ ████████ ████████ Shell: zsh 5.5.1
████████ ████████ ████████ Resolution: 1920x1080
████████ ████████ ████████ DE: GNOME
████████ ████████ ████████ WM: GNOME Shell
████████ ████████ ████████ WM Theme: Adapta-Nokto-Maia
████████ ████████ ████████ GTK Theme: Adapta-Nokto-Maia [GTK2/3]
████████ ████████ ████████ Icon Theme: Papirus-Adapta-Maia
████████ ████████ ████████ Font: Noto Sans 11
████████ ████████ ████████ CPU: Intel Core i7-8550U @ 8x 4GHz [47.0°C]
GPU: Mesa DRI Intel(R) UHD Graphics 620 (Kabylake GT2)
RAM: 6157MiB / 15928MiB
</code></pre>
<h1>安装</h1>
<pre><code class="language-shell">sudo pacman -S mariadb</code></pre>
<h1>配置</h1>
<pre><code class="language-shell">#初始化
sudo mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
#启动
sudo systemctl start mariadb
#设置密码
mysql_secure_installation</code></pre> </description>
<pubDate>2019-07-26 14:45:18</pubDate>
<link>//blog/devops/installMariadbOnManjaro.html</link>
<guid isPermaLink="true">//blog/devops/installMariadbOnManjaro.html</guid>
<category>devops</category>
</item>
<item>
<title><Go:build web application>的中文翻译版-第三章-连接数据</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2017-09-22
title: <Go:build web application>的中文翻译版-第三章-连接数据
tags: GO
images:
category: go
status: publish
summary:
在上一章中,我们探索了在web应用中如何处理URLS和指引他们转到不同的页面.同时,我们通过net/http中的handle创建了动态的链接和动态的结果.在这一章中,我们将学习以下几个主题,连接数据库,使用GUID美化URLs,处理404页面
-->
<p>在上一章中,我们探索了在web应用中如何处理URLS和指引他们转到不同的页面.同时,我们通过net/http中的handle创建了动态的链接和动态的结果.</p>
<p>通过实现和扩展Gorilla toolkit的mux路由,我们通过正则表达式扩展了路由的能力,使其给予我们的应用更大的灵活性.</p>
<p>其实这是一些最流行的web服务器的特性.比如说Apache和Nginx都在路由中提供了方法去解析正则表达式.</p>
<p>但是这仅仅是构成web应用的基石.为了更加深入,我们需要去看看如何引入数据.</p>
<p>前一章的例子中静态文件服务依赖于硬编码,这显然是过时的且难以控制的.</p>
<p>但是幸运的是,从90年代末期开始,网站变得动态化,数据库开始统治世界.虽然APIs,微服务和NoSQL在某些领域替代了这些架构,但是这个架构在当今的Web开发中还是万金油的角色.</p>
<p>所以,事不宜迟,让我们开始获取一些动态数据</p>
<p>在这一章中,我们将学习以下几个主题</p>
<ul>
<li>连接数据库</li>
<li>使用GUID美化URLs</li>
<li>处理404页面</li>
</ul>
<h2>连接一个数据库</h2>
<p>为了连接数据库,Go的SQL接口提供了一个非常简单且可信赖的方式去连接拥有驱动的不同种类的数据库服务器.</p>
<p>目前,大部分流行的数据库都支持-MySQL,Postgres,SQLite,MSSQL和相当多的实现了Go提供的database/sql接口的数据库驱动.</p>
<blockquote>
<p>Note:在本书中,我们将会把MySQL和Postgres数据库使用最好的实践运用在多个例子上.安装MySQL和Postgres在Nix,Windows,OS X 系统的机器上是相当基础的工作</p>
</blockquote>
<h2>创建MySQL数据库</h2>
<p>你可以选择设计任何你想要的应用,但是在这些例子中,我们将着手一个非常的简单的博客.</p>
<p>我们的目标是尽可能地在数据库中创建一些博客的入口,最好可以使用GUID在数据库中直接地获取数据和展示,如果博客的入口不存在,将展示错误页面.</p>
<p>为了实现这个需求,我们将创建一个包含了我们的页面的MySQL数据库.这个数据库将包含一个整数型的,自动递增的ID,一个全局唯一的标识,或者GUID,还有一些博客的初始数据.</p>
<p>简单起见,我们创建一个叫存储标题的page_title字段,存储页面内容的page_content字段,还有一个使用Unix时间戳的字段page_date.</p>
<pre><code>CREATE TABLE `pages` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`page_guid` varchar(256) NOT NULL DEFAULT '',
`page_title` varchar(256) DEFAULT NULL,
`page_content` mediumtext,
`page_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON
UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `page_guid` (`page_guid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf-8;
</code></pre>
<p>将page_guid标记为UNIQUE_KEY相当重要,如果我们允许出现重复的page_guid,在浏览某个网址的时候可能出现不准确的情况.
我们使用以下的语句插入一些blog数据</p>
<pre><code>INSERT INTO `pages` (`id`, `page_guid`, `page_title`,
`page_content`, `page_date`) VALUES (NULL, 'hello-world', 'Hello,
World', 'I\'m so glad you found this page! It\'s been sitting
patiently on the Internet for some time, just waiting for a
visitor.', CURRENT_TIMESTAMP);</code></pre>
<p>执行了上面的语句之后,我们就获得了初始数据
使用下面的代码来获得连接数据库的能力</p>
<pre><code>package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
)</code></pre>
<p>我们导入MySQL驱动包来完成需求.通常,这意味着驱动是另一个包的实现.你能注意到使用<em> 符号来导入包.你可能已经熟悉这点,作为一种快速且脏的方式去忽略类的实例的返回值.比如说x,</em> :=something() 允许你去忽略第二个返回值.这经常也被开发者用在计划去使用一个库,但是目前还没有用到的情况.通过这种方式准备包,它允许导入声明而不引起编译期报错.虽然这不是推荐的做法,但是在预载入方法中使用下划线_或者空白标识符,好处是这是很常见的做法也普遍被接受.
其实,这全部依赖于你怎样以及为何使用标识符.</p>
<pre><code>const (
DBHost = "127.0.0.1"
DBPort = ":3306"
DBUser = "root"
DBPass = "password!"
DBDbase = "cms"
)</code></pre>
<p>记得使用你自己的配置去替换以上值</p>
<pre><code>var database *sql.db</code></pre>
<p>为了避免大量重复代码,我们可以将数据库连接引用作为一个全局变量.为了清晰可见,我们将在代码开头定义.其实也没有什么事会阻止你把这个定义为一个常量,但是如果这样我们将会失去一定的灵活性,比如说添加多个数据库到单个应用中</p>
<pre><code>type Page struct {
Title string
Content string
Date string
}</code></pre>
<p>这个结构,跟我们的数据表结构非常匹配了,有标题,内容,时间.我们马上也将在本书中看到更好的数据结构设计.你需要确保你结构的字段是可以导出的或者公共的.任何小写的字段将不会被导出,因此也不能被模板化.我们将在后面讨论更多.</p>
<pre><code>func main(){
dbConn:=fmt.Sprintf("%s:%s@tcp(%s)/%s",DBUser,DBPass,DBHost,DBDbase)
db,err:=sql.Open("mysql",dbConn)
if err!=nil{
log.Println("Couldn't connect")
log.Println(err.Error())
}
log.Println("Connect successfully")
database=db
}</code></pre>
<p>正如我们之前提到的一样,这主要是脚手架.我们唯一想做的就是确保我们可以连接我们的数据库.如果出现一个错误,检查你的连接配置和输出的日志.
如果如我们期望的那样,我们使用上面的代码连接上数据库,我们就可以创建通用的路由代码来匹配请求中GUID,然后去数据库查询数据.</p>
<p>为了以上的目标,我们需要去重新实现Gorilla,创建单个路由,然后实现一个handler去匹配我们的数据库</p>
<p>看看下面的修改</p>
<pre><code>package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"log"
"net/http"
)</code></pre>
<p>比较大的变化是我们在项目中引入了Gorilla和net/http 库.很显然我们需要这些来构建服务</p>
<pre><code>const (
DBHost = "127.0.0.1"
DBPort = ":3306"
DBUser = "root"
DBPass = "password!"
DBDbase = "cms"
PORT = ":8080"
)</code></pre>
<p>我们增加了PORT常量,用来绑定HTTP 服务器端口</p>
<pre><code>var database *sql.DB
type Page struct{
Title string
Content string
Date string
}
/**
数据库连接测试
*/
func main(){
dbConn:=fmt.Sprintf("%s:%s@/%s",DBUser,DBPass,DBDbase)
db,err:=sql.Open("mysql",dbConn)
if err!=nil{
log.Println("Couldn't connect")
log.Println(err.Error())
}
log.Println("Connect successfully")
database=db
//设置路由
routes:=mux.NewRouter()
routes.HandleFunc("/page/{id:[0-9a-zA\\-]+",ServePage)
http.Handle("/",routes)
http.ListenAndServe(PORT,nil)
}
func ServePage(w http.ResponseWriter,r *http.Request){
vars:=mux.Vars(r)
pageID:=vars["id"]
thisPage:=Page{}
fmt.Println("pageID:"+pageID,"guid:"+pageGUID)
err:=database.QueryRow("select page_title,page_content,page_date from pages where id=?",pageID).Scan(&amp;thisPage.Title,&amp;thisPage.Content,&amp;thisPage.Date)
if err!=nil{
log.Println("Couldn't get page: +pageID")
log.Println(err.Error())
}
html:=`&lt;html&gt;&lt;head&gt;&lt;title&gt;` + thisPage.Title +
`&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;` + thisPage.Title + `&lt;/h1&gt;&lt;div&gt;` +
thisPage.Content + `&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;`
fmt.Fprintln(w,html)
}
</code></pre>
<p>ServePage()是一个从mux.Vars中获取id,然后查询数据库的方法.最简单的查询数据库的方法就是使用使用预处理语句,比如Query,QueryRow或者Prepare.利用其中任何一个包含可注入的变量的声明语句,可以避免手工构建查询语句的风险.</p>
<p>Scan方法获取结果然后解析成数据结构.在这个例子中,我们解析page_title,page_content,page_date到Page结构中的Title,Content,Date</p>
<pre><code>func main() {
dbConn := fmt.Sprintf("%s:%s@/%s", DBUser, DBPass, DBDbase)
fmt.Println(dbConn)
db, err := sql.Open("mysql", dbConn)
if err != nil {
log.Println("Couldn't connect to"+DBDbase)
log.Println(err.Error)
}
database = db
routes := mux.NewRouter()
routes.HandleFunc("/page/{id:[0-9]+}", ServePage)
http.Handle("/", routes)
http.ListenAndServe(PORT, nil)
}</code></pre>
<p>看看我们这的正则表达式:只获取数字</p>
<p>还记得们谈论过使用内置的GUID?我们将马上会用到,现在我们看看访问localhost:8080/page/1的结果</p>
<pre><code>Hello, World
I'm so glad you found this page! It's been sitting patiently on the Internet for some time, just waiting for a visitor.</code></pre>
<p>在前面的例子中,我们可以看到在数据库中的博客内容.</p>
<h2>使用GUID来美化URLs</h2>
<p>在本章的前几段我们讨论过使用GUID来作为所有请求的URL标识符.</p>
<p>我们需要去修改正则表达式和SQL语句</p>
<pre><code>routes.HandleFunc("/page/{id:[0-9a-zA\\-]+}", ServePage)</code></pre>
<p>修改为</p>
<pre><code>routes.HandleFunc("/page/{guid:[0-9a-zA\\-]+}", ServePage)</code></pre>
<p>修改相关方法和SQL</p>
<pre><code>func ServePage(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pageGUID := vars["guid"]
thisPage := Page{}
fmt.Println(pageGUID)
err := database.QueryRow("SELECT page_title,page_content,page_date
FROM pages WHERE page_guid=?",
pageGUID).Scan(&amp;thisPage.Title, &amp;thisPage.Content, &amp;thisPage.Date)</code></pre>
<p>在完成之后,我们访问 localhost:8080/page/hello-world,可以获得之前访问localhost:8080/page/1同样的结果,但是修改后的url可读性更高,而且对搜索引擎更加友好</p>
<h2>处理404</h2>
<p>我们之前的代码有个显而易见的问题就是它不处理无效的ID(或GUID)的请求.</p>
<p>真实的情况是,一个访问/page/999的请求,将返回空白页,控制台将会输出<em>Couldn't get page!</em> </p>
<p>解决这个问题最简单的方法就是使用合适的错误.在上一章中,我们探索过定制的404页面,在这里我们可以实现其中一种,但是在一个请求不能找到时最简单的方法就是直接返回一个HTTP 状态码,允许浏览器去处理.</p>
<p>在我们之前的代码中,我们有一个错误处理器,仅仅是写日志,让我们把它变得更加丰富</p>
<pre><code>err:=database.QueryRow("select page_title,page_content,page_date from pages where page_guid=?",pageGUID).Scan(&amp;thisPage.Title,&amp;thisPage.Content,&amp;thisPage.Date)
if err!=nil{
http.Error(w,http.StatusText(404),http.StatusNotFound)
log.Println("Couldn't get page: +pageID")
log.Println(err.Error())
return
}</code></pre>
<p>这样子的话当遇到错误页面的时候将会有一个友好的提示页面</p>
<pre><code>http://127.0.0.1:8080/page/hello-world22
Not Found</code></pre> </description>
<pubDate>2019-07-26 14:45:14</pubDate>
<link>//blog/go/go-build-web-app-chapter-3.html</link>
<guid isPermaLink="true">//blog/go/go-build-web-app-chapter-3.html</guid>
<category>go</category>
</item>
<item>
<title><Go:build web application>的中文翻译版本目录</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2017-09-20
title: <Go:build web application>的中文翻译版本目录
tags: GO
images:
category: go
status: publish
summary: <Go:build web application>的中文翻译版本
-->
<h2><Go:build web application>的中文翻译版本</h2>
<h1>目录</h1>
<h2>模块1:学习Go的Web开发</h2>
<h3>第一章:介绍和安装Go环境</h3>
<ul>
<li>安装Go</li>
<li>构建一个项目</li>
<li>引入包</li>
<li>介绍net包</li>
<li>你好,Web</li>
<li>本章总结</li>
</ul>
<h3>第二章:服务和路由</h3>
<ul>
<li>直接的文件服务</li>
<li>基本路由</li>
<li>使用Gorilla实现更复杂的路由</li>
<li>转发请求</li>
<li>处理基本的错误</li>
<li>本章总结</li>
</ul>
<h3>第三章:连接数据</h3>
<ul>
<li>连接一个数据库</li>
<li>使用GUID美化URLs</li>
<li>处理404错误</li>
<li>总结</li>
</ul>
<h3>第四章:使用模板</h3>
<ul>
<li>介绍模板,上下文和可视化</li>
<li>HTML模板和文本模板</li>
<li>渲染变量和安全性</li>
<li>使用逻辑和控制结构</li>
<li>本章总结</li>
</ul>
<h3>第五章:使用RESTful APIs进行前端集成</h3>
<ul>
<li>设置基本的API结构</li>
<li>RESTful架构和最佳实践</li>
<li>创建我们的第一个API</li>
<li>实现安全</li>
<li>使用POST创建数据</li>
<li>使用PUT更改数据</li>
<li>本章总结</li>
</ul>
<h3>第六章:Session和Cookies</h3>
<ul>
<li>设置cookies</li>
<li>存储用户的信息</li>
<li>初始化一个服务端的session</li>
<li>本章总结</li>
</ul>
<h3>第七章:微服务和通讯</h3>
<ul>
<li>介绍引入微服务</li>
<li>使用微服务的利弊</li>
<li>理解微服务的内核</li>
<li>微服务之间的通讯</li>
<li>在线上放一个信息</li>
<li>从其他服务读取信息</li>
<li>本章总结</li>
</ul>
<h3>第八章:记录日志和测试</h3>
<ul>
<li>介绍Go中的日志</li>
<li>记录日志到IO中</li>
<li>格式化你的输出</li>
<li>使用panics和fatal errors</li>
<li>介绍Go中的测试</li>
<li>本章总结</li>
</ul>
<h3>第九章:安全</h3>
<ul>
<li>在任何地方使用HTTPS-实现TLS</li>
<li>防止SQL注入</li>
<li>防范XSS攻击</li>
<li>防范CSRF跨站攻击</li>
<li>加密cookies</li>
<li>使用安全中间件</li>
<li>本章总结</li>
</ul>
<h3>第十章:缓存,代理和提高性能</h3>
<ul>
<li>确定瓶颈</li>
<li>实现反向代理</li>
<li>实现缓存</li>
<li>实现HTTP/2</li>
<li>本章总结</li>
</ul>
<h2>模块2:Go 编程蓝皮书</h2>
<h3>第一章:使用Web Sockets构建的聊天应用</h3>
<ul>
<li>一个简单的Web服务器</li>
<li>在服务器上建模一个聊天室和客户端</li>
<li>构建一个使用HTML和JavaScript的聊天客户端</li>
<li>跟踪代码获取内在的流程</li>
<li>本章总结</li>
</ul>
<h3>第二章:增加权限</h3>
<ul>
<li>拦截所有请求</li>
<li>创建一个社交化的登录页面</li>
<li>动态路径</li>
<li>OAuth2</li>
<li>把你的APP告诉权限提供者</li>
<li>实现额外的登录</li>
<li>本章总结</li>
</ul>
<h3>第三章:3个方法去实现文件缩略图</h3>
<ul>
<li>在权限服务器上的头像</li>
<li>实现Gravatar</li>
<li>上传头像图片</li>
<li>整合3种实现</li>
<li>本章总结</li>
</ul>
<h3>第四章:查询域名的命令行工具</h3>
<ul>
<li>命令行工具的管道设计</li>
<li>5个简单的程序</li>
<li>编写所有5个程序</li>
<li>本章总结</li>
</ul>
<h3>第五章:构建分布式系统,与复杂的数据交互</h3>
<ul>
<li>系统设计</li>
<li>安装环境</li>
<li>获取Twitter的投票</li>
<li>计算投票</li>
<li>运行我们的解决方案</li>
<li>本章总结</li>
</ul>
<h3>第六章:通过RESTful web数据接口对外提供数据和功能</h3>
<ul>
<li>RESTful API 设计</li>
<li>在处理器间共享数据</li>
<li>包装处理函数</li>
<li>响应</li>
<li>理解请求</li>
<li>一个简单的主函数去处理我们的API</li>
<li>处理节点</li>
<li>一个web客户端去消费API</li>
<li>运行解决方案</li>
<li>本章总结</li>
</ul>
<h3>第七章:随机推荐Web服务</h3>
<ul>
<li>项目预览</li>
<li>用代码展示数据</li>
<li>构造随机推荐</li>
<li>本章总结</li>
</ul>
<h3>第八章:文件备份系统</h3>
<ul>
<li>解决方案设计</li>
<li>备份包</li>
<li>用户命令行工具</li>
<li>备份守护工具</li>
<li>测试解决方案</li>
<li>本章总结</li>
</ul>
<h2>模块3:精通Go的高并发</h2>
<h3>第一章:介绍Go并发编程</h3>
<ul>
<li>介绍协程</li>
<li>实现延迟控制机制</li>
<li>理解协程与协同</li>
<li>实现通道</li>
<li>关闭与协程</li>
<li>构建一个使用协程和通道的爬虫</li>
<li>本章总结</li>
</ul>
<h3>第二章:理解并发模型</h3>
<ul>
<li>理解协程如何工作</li>
<li>同步和异步协程</li>
<li>可视化并发</li>
<li>RSS实战</li>
<li>CSP的一点介绍</li>
<li>Go和角色模型</li>
<li>面向对象</li>
<li>使用并发</li>
<li>管理线程</li>
<li>使用同步和互斥锁住数据</li>
<li>本章总结</li>
</ul>
<h3>第三章:开发并行策略</h3>
<ul>
<li>在复杂的并发中提高效率</li>
<li>使用竞争检查识别竞争条件</li>
<li>同步我们的并发操作</li>
<li>项目-多用户预约日历</li>
<li>一个多用户预约日历</li>
<li>风格说明</li>
<li>不变性说明</li>
<li>本章总结</li>
</ul>
<h3>第四章:应用中的数据完整性</h3>
<ul>
<li>深入理解互斥与同步</li>
<li>协程的代价</li>
<li>处理文件</li>
<li>更底层-实现C</li>
<li>分布式的Go</li>
<li>几种常见的一致性模型</li>
<li>使用memcached</li>
<li>本章总结</li>
</ul>
<h3>第五章:锁,区块和更好的通道</h3>
<ul>
<li>理解Go中的区块方法</li>
<li>清除协程</li>
<li>创建通道的通道</li>
<li>Pprof-一个令人惊叹的工具</li>
<li>处理死锁和错误</li>
<li>本章总结</li>
</ul>
<h3>第六章:C10K-Go中的一个无锁的Web服务器</h3>
<ul>
<li>攻克C10K问题</li>
<li>创建我们的C10K Web服务器</li>
<li>提供页面服务</li>
<li>多线程和利用多核</li>
<li>探索我们的Web服务器</li>
<li>本章总结</li>
</ul>
<h3>第七章:性能与可扩展性</h3>
<ul>
<li>Go的高性能</li>
<li>使用App Engine</li>
<li>分布式的Go</li>
<li>一些有用的库</li>
<li>内存维护</li>
<li>本章总结</li>
</ul>
<h3>第八章:并发程序架构</h3>
<ul>
<li>设计我们的并发程序</li>
<li>确定我们的需求</li>
<li>在Go中使用NoSQL作为数据存储</li>
<li>监控文件系统的变化</li>
<li>管理日志文件</li>
<li>处理配置文件</li>
<li>检测文件变化</li>
<li>备份文件</li>
<li>设计Web接口</li>
<li>还原文件的历史-命令行</li>
<li>检查服务器的健康度</li>
<li>本章总结</li>
</ul>
<h3>第九章:在Go中记录日志和测试并发</h3>
<ul>
<li>处理错误和记录日志</li>
<li>使用log4go包作为健壮的日志组件</li>
<li>使用runtime包作为细粒度的堆栈跟踪组件</li>
<li>本章总结</li>
</ul>
<h3>第十章:先进的并发和最佳实践</h3>
<ul>
<li>使用channels跨越基础</li>
<li>构建工作者</li>
<li>实现空通道区块</li>
<li>使用tomb实现对协程更多的细粒度的控制</li>
<li>使用通道定时</li>
<li>通过并发模式构建负载均衡器</li>
<li>选择单向和双向通道</li>
<li>使用泛型通道</li>
<li>使用Go的单元测试</li>
<li>使用Google的App Engine</li>
<li>使用最佳实践</li>
<li>本章总结</li>
</ul> </description>
<pubDate>2019-07-26 14:45:14</pubDate>
<link>//blog/go/go-build-web-app-catalog.html</link>
<guid isPermaLink="true">//blog/go/go-build-web-app-catalog.html</guid>
<category>go</category>
</item>
<item>
<title>PHP的Swoole扩展安装与学习</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2017-09-08
title: PHP的Swoole扩展安装与学习
tags: PHP,SWOOLE
images:
category: php
status: publish
summary: PHP的Swoole扩展安装与学习
-->
<h1>安装swoole</h1>
<pre><code>#直接使用pecl安装扩展
sudo pecl install swoole
#检测是否安装成功
php -m|grep swoole
#如果有swoole则安装成功,否则,在php.ini中增加扩展
#获取php.ini的绝对路径
php -i |grep php.ini
cd path
vim php.ini
#增加扩展
extension=swoole</code></pre>
<h1>跑测试例程</h1>
<pre><code>#创建文件
vim http_server.php
#输入代码
&lt;?
$http = new swoole_http_server("0.0.0.0", 9501);
$http-&gt;on('request', function ($request, $response) {
var_dump($request-&gt;get, $request-&gt;post);
$response-&gt;header("Content-Type", "text/html; charset=utf-8");
$response-&gt;end("&lt;h1&gt;Hello Swoole. #".rand(1000, 9999)."&lt;/h1&gt;");
});
$http-&gt;start();
#启动程序
php http_server.php
#看看成功没有
curl http://127.0.0.1:9501
#如果正确输出,那就启动成功啦
</code></pre> </description>
<pubDate>2019-07-26 14:45:18</pubDate>
<link>//blog/php/swoole.html</link>
<guid isPermaLink="true">//blog/php/swoole.html</guid>
<category>php</category>
</item>
<item>
<title>Redis高可用架构</title>
<description>
<!--
author: Jimersy Lee
head:
date: 2017-09-05
title: Redis高可用架构
tags: LINUX
images:
category: devops
status: publish
summary: Redis是一个高性能的key-value数据库,现时越来越多企业与应用使用Redis作为缓存服务器。下面楼主就带着大家从0开始,依次搭建:Redis单机服务器 -> Redis主从复制 ->Redis-Sentinel高可用->VIP漂移更换主机不换IP。逐步搭建出高可用的Redis缓存服务器。
-->
<h1>前言</h1>
<p><code>Redis</code>是一个高性能的<code>key-value</code>数据库,现时越来越多企业与应用使用<code>Redis</code>作为缓存服务器。楼主是一枚<code>JAVA</code>后端程序员,也算是半个运维工程师了。在<code>Linux</code>服务器上搭建<code>Redis</code>,怎么可以不会呢?下面楼主就带着大家从0开始,依次搭建:<code>Redis</code>单机服务器 -&gt; <code>Redis</code>主从复制 -&gt;<code>Redis-Sentinel高可用</code>。逐步搭建出高可用的Redis缓存服务器。</p>
<h1>搭建Redis</h1>
<h3>1. 下载并解压</h3>
<p>首先从<code>Redis</code>官网下载<code>Redis</code>并解压,楼主使用的版本是4.0.2。依次执行如下命令: </p>
<pre><code>cd /opt
wget http://download.redis.io/releases/redis-4.0.2.tar.gz
tar -zcvf redis-4.0.2.tar.gz</code></pre>
<p>如果没有安装<code>gcc</code>依赖包,则安装对应依赖包</p>
<pre><code>yum install -y gcc-c++ tcl</code></pre>
<h3>2. 编译并安装</h3>
<p>下载并解压完毕后,则对源码包进行编译安装,楼主的<code>Redis</code>安装路径为<code>/usr/local/redis</code>,同学们可以自行修改语句:<code>make install PREFIX=你想要安装的路径</code></p>
<pre><code>cd /opt/redis-4.0.2
make install PREFIX=/usr/local</code></pre>
<p>复制<code>Redis</code>相关命令到<code>/usr/sbin</code>目录下,这样就可以直接执行这些命令,不用写全路径</p>
<pre><code>cd /usr/local/redis/bin
sudo cp redis-* /usr/sbin</code></pre>
<h3>3. 建立Redis配置文件</h3>
<p>安装完成之后将 <code>Redis</code> 配置文件拷贝到系统配置目录<code>/etc/</code>下,<code>redis.conf</code> 是 <code>Redis</code> 的配置文件,<code>redis.conf</code> 在 <code>Redis</code> 源码目录,<code>port</code>默认 6379。</p>
<pre><code>cp /usr/local/redis-4.0.2/redis.conf /etc/</code></pre>
<p><code>Redis</code>配置文件主要参数解析参考</p>
<pre><code> daemonize no #redis进程是否以守护进程的方式运行,yes为是,no为否(不以守护进程的方式运行会占用一个终端)
pidfile /var/run/redis.pid #指定redis进程的PID文件存放位置
port 6379 #redis进程的端口号
bind 127.0.0.1 #绑定的主机地址
timeout 300 #客户端闲置多长时间后关闭连接,默认此参数为0即关闭此功能
loglevel verbose #redis日志级别,可用的级别有debug.verbose.notice.warning
logfile stdout #log文件输出位置,如果进程以守护进程的方式运行,此处又将输出文件设置为stdout的话,就会将日志信息输出到/dev/null里面去了
databases 16 #设置数据库的数量,默认为0可以使用select &lt;dbid&gt;命令在连接上指定数据库id
save &lt;seconds&gt;&lt;changes&gt; #指定在多少时间内刷新次数达到多少的时候会将数据同步到数据文件;
rdbcompression yes #指定存储至本地数据库时是否压缩文件,默认为yes即启用存储;
dbfilename dump.db #指定本地数据库文件名
dir ./ #指定本地数据问就按存放位置;
slaveof &lt;masterip&gt;&lt;masterport&gt; #指定当本机为slave服务时,设置master服务的IP地址及端口,在redis启动的时候他会自动跟master进行数据同步
masterauth &lt;master-password&gt; #当master设置了密码保护时,slave服务连接master的密码;
requirepass footbared #设置redis连接密码,如果配置了连接密码,客户端在连接redis是需要通过AUTH&lt;password&gt;命令提供密码,默认关闭
maxclients 128 #设置同一时间最大客户连接数,默认无限制;redis可以同时连接的客户端数为redis程序可以打开的最大文件描述符,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxmemory&lt;bytes&gt; #指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
appendonly no #指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendfilename appendonly.aof #指定跟新日志文件名默认为appendonly.aof
appendfsync everysec #指定更新日志的条件,有三个可选参数no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全), everysec:表示每秒同步一次(折衷,默认值);
</code></pre>
<h6>3.1 设置后端启动:</h6>
<p>由于<code>Redis</code>默认是前端启动,必须保持在当前的窗口中,如果使用<code>ctrl + c</code>退出,那么<code>Redis</code>也就退出,不建议使用。</p>
<pre><code> vi /etc/redis.conf</code></pre>
<p>修改<code>Redis</code>配置文件把旧值<code>daemonize no</code> 改为 新值<code>daemonize yes</code></p>
<h6>3.2 设置访问:</h6>
<p><code>Redis</code>默认只允许本机访问,可是有时候我们也需要 Redis 被远程访问。</p>
<pre><code>vi /etc/redis.conf</code></pre>
<p>找到 bind 那行配置,默认是: <code># bind 127.0.0.1</code></p>
<p>去掉<code>#</code>注释并改为: <code>bind 0.0.0.0</code> 此设置会变成允许所有远程访问。如果想指定限制访问,可设置对应的IP。</p>
<h6>3.3 配置Redis日志记录:</h6>
<p>找到<code>logfile</code>那行配置,默认是:<code>logfile ""</code>,改为<code>logfile /var/log/redis_6379.log</code></p>
<h6>3.4 设置 Redis 请求密码:</h6>
<pre><code>vi /etc/redis.conf</code></pre>
<p>找到默认是被注释的这一行:<code># requirepass foobared</code></p>
<p>去掉注释,把 <code>foobared</code> 改为你想要设置的密码,比如我打算设置为:<code>123456</code>,所以我改为:<code>requirepass "123456"</code></p>
<p>修改之后重启下服务</p>
<p>有了密码之后,进入客户端,就得这样访问:<code>redis-cli -h 127.0.0.1 -p 6379 -a 123456</code></p>
<h3>4. Redis常用操作</h3>
<h6>4.1 启动</h6>
<pre><code>/usr/local/redis/bin/redis-server /etc/redis.conf</code></pre>
<h6>4.2 关闭</h6>
<pre><code>/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379 shutdown</code></pre>
<h6>4.3 查看是否启动</h6>
<pre><code> ps -ef | grep redis</code></pre>
<h6>4.4 进入客户端</h6>
<pre><code> redis-cli
</code></pre>
<h6>4.5 关闭客户端</h6>
<pre><code>redis-cli shutdown</code></pre>
<h6>4.6 设置开机自动启动配置</h6>
<pre><code>echo "/usr/local/redis/bin/redis-server /etc/redis.conf" &gt;/etc/rc.local</code></pre>
<h6>4.7 开放防火墙端口</h6>
<pre><code>添加规则:iptables -I INPUT -p tcp -m tcp --dport 6379 -j ACCEPT
保存规则:service iptables save
重启 iptables:service iptables restart</code></pre>
<h3>5. 将Redis注册为系统服务</h3>
<p>在/etc/init.d目录下添加Redis服务的启动,暂停和重启脚本:</p>
<pre><code>```
vi /etc/init.d/redis
```
脚本内容如下:
```#!/bin/sh
#
# redis - this script starts and stops the redis-server daemon
#
# chkconfig: - 85 15
# description: Redis is a persistent key-value database
# processname: redis-server
# config: /usr/local/redis/bin/redis-server
# config: /etc/redis.conf
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] &amp;amp;&amp;amp; exit 0
redis="/usr/local/redis/bin/redis-server"
prog=$(basename $redis)
REDIS_CONF_FILE="/etc/redis.conf"
[ -f /etc/sysconfig/redis ] &amp;amp;&amp;amp; . /etc/sysconfig/redis
lockfile=/var/lock/subsys/redis
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;start() {
[ -x $redis ] || exit 5
[ -f $REDIS_CONF_FILE ] || exit 6
echo -n $"Starting $prog: "
daemon $redis $REDIS_CONF_FILE
retval=$?
echo
[ $retval -eq 0 ] &amp;amp;&amp;amp; touch $lockfile
return $retval
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;stop() {
echo -n $"Stopping $prog: "
killproc $prog -QUIT
retval=$?
echo
[ $retval -eq 0 ] &amp;amp;&amp;amp; rm -f $lockfile
return $retval
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;restart() {
stop
start
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;reload() {
echo -n $"Reloading $prog: "
killproc $redis -HUP
RETVAL=$?
echo
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;force_reload() {
restart
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;rh_status() {
status $prog
}
&lt;span class="hljs-function"&gt;&lt;span class="hljs-title"&gt;rh_status_q() {
rh_status &gt;/dev/null 2&gt;&amp;amp;1
}
case "$1" in
start)
rh_status_q &amp;amp;&amp;amp; exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart| reload|orce-reload}"
exit 2
esac
```
赋予脚本权限
```chmod 755 /etc/init.d/redis
```
启动、停止和重启:
```service redis start
service redis stop
service redis restart
```
至此,`Redis`单机服务器已搭建完毕,下面我们看看主从架构如何搭建。
# 搭建Redis主从架构
### 1. redis-server说明
```172.16.2.185:6379 主
172.16.2.181:6379 从
```</code></pre>
<h3>2. Redis主从架构配置</h3>
<ul>
<li>编辑从机的 <code>Redis</code> 配置文件,找到 210 行(大概),默认这一行应该是注释的: <code># slaveof &lt;masterip&gt; &lt;masterport&gt;</code></li>
<li>我们需要去掉该注释,并且填写我们自己的主机的 IP 和 端口,比如:<code>slaveof 172.16.2.185 6379</code>,如果主机设置了密码,还需要找到<code>masterauth &lt;master-password&gt;</code>这一行,去掉注释,改为<code>masterauth 主机密码</code>。</li>
<li>配置完成后重启从机<code>Redis</code> 服务</li>
<li>
<p>重启完之后,进入主机的 <code>redis-cli</code> 状态下<code>redis-cli -h 127.0.0.1 -p 6379 -a 123456</code>,输入:<code>INFO replication</code>
可以查询到当前主机的 <code>Redis</code>处于什么角色,有哪些从机已经连上主机。</p>
<p>主机信息<code>172.16.2.185</code></p>
<pre><code># Replication
role:master
connected_slaves:1
slave0:ip=172.16.2.181,port=6379,state=online,offset=28,lag=1
master_replid:625ae9f362643da5337835beaeabfdca426198c7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28</code></pre>
<p>从机信息<code>172.16.2.181</code></p>
<pre><code># Replication
role:slave
master_host:172.16.2.185
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:210
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:625ae9f362643da5337835beaeabfdca426198c7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:210
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:210</code></pre>
</li>
<li>此时已经完成了主从配置,我们可以测试下:
我们进入主机的 <code>redis-cli</code> 状态,然后 <code>set</code> 某个值,比如:<code>set myblog YouMeek.com</code></li>
<li>
<p>我们切换进入从机的 <code>redis-cli</code> 的状态下,获取刚刚设置的值看是否存在:<code>get myblog</code>,此时,我们可以发现是可以获取到值的。</p>
<h3>3. Redis主从架构总结</h3>
</li>
<li>需要注意的是:从库不具备写入数据能力,不然会报错。 从库只有只读能力。</li>
<li>主从架构的优点:除了减少主库连接的压力,还有可以关掉主库的持久化功能,把持久化的功能交给从库进行处理。</li>
<li>
<p>第一个从库配置的信息是连上主库,后面的第二个从库配置的连接信息是连上第一个从库, 假如还有第三个从库的话,我们可以把第三个从库的配置信息连上第二个从库上,以此类推。</p>
<h1>Redis Sentinel高可用架构搭建</h1>
<h3>1. 自动故障转移</h3>
</li>
<li>虽然使用主从架构配置<code>Redis</code>做了备份,看上去很完美。但由于<code>Redis</code>目前只支持主从复制备份(不支持主主复制),当主<code>Redis</code>挂了,从<code>Redis</code>只能提供读服务,无法提供写服务。所以,还得想办法,当主<code>Redis</code>挂了,让从<code>Redis</code>升级成为主<code>Redis</code>。</li>
<li>
<p>这就需要自动故障转移,<code>Redis Sentinel</code>带有这个功能,当一个主<code>Redis</code>不能提供服务时,<code>Redis Sentinel</code>可以将一个从<code>Redis</code>升级为主<code>Redis</code>,并对其他从<code>Redis</code>进行配置,让它们使用新的主<code>Redis</code>进行复制备份。</p>
<figure><svg xmlns='"http://www.w3.org/2000/svg"' version='"1.1"' width='"865"' height='"469"'></svg>" class="inited"&gt;<figcaption></figcaption></figure>
<p>注意:搭建<code>Redis Sentinel</code>推荐至少3台服务器,但由于楼主偷懒,下面用例只用了2台服务器。</p>
<p><code>Redis Sentinel</code>的主要功能如下:</p>
</li>
</ul>
<ol>
<li>监控:哨兵不断的检查<code>master</code>和<code>slave</code>是否正常的运行。</li>
<li>通知:当监控的某台<code>Redis</code>实例发生问题时,可以通过<code>API</code>通知系统管理员和其他的应用程序。</li>
<li>自动故障转移:如果一个<code>master</code>不正常运行了,哨兵可以启动一个故障转移进程,将一个<code>slave</code>升级成为<code>master</code>,其他的<code>slave</code>被重新配置使用新的<code>master</code>,并且应用程序使用<code>Redis</code>服务端通知的新地址。</li>
<li>
<p>配置提供者:哨兵作为<code>Redis</code>客户端发现的权威来源:客户端连接到哨兵请求当前可靠的<code>master</code>的地址。如果发生故障,哨兵将报告新地址。</p>
<p>默认情况下,每个<code>Sentinel</code>节点会以每秒一次的频率对<code>Redis</code>节点和其它的<code>Sentinel</code>节点发送<code>PING</code>命令,并通过节点的回复来判断节点是否在线。</p>
<p>如果在<code>down-after-millisecondes</code>毫秒内,没有收到有效的回复,则会判定该节点为主观下线。</p>
<p>如果该节点为<code>master</code>,则该<code>Sentinel</code>节点会通过<code>sentinel is-master-down-by-addr</code>命令向其它<code>sentinel</code>节点询问对该节点的判断,如果超过<code>&lt;quorum&gt;</code>个数的节点判定<code>master</code>不可达,则该<code>sentinel</code>节点会将<code>master</code>判断为客观下线。</p>
<p>这个时候,各个<code>Sentinel</code>会进行协商,选举出一个领头<code>Sentinel</code>,由该领头<code>Sentinel</code>对<code>master</code>节点进行故障转移操作。</p>
<p>故障转移包含如下三个操作:</p>
</li>
<li>在所有的<code>slave</code>服务器中,挑选出一个<code>slave</code>,并将其转换为<code>master</code>。</li>
<li>让其它<code>slave</code>服务器,改为复制新的<code>master</code>。</li>
<li>
<p>将旧<code>master</code>设置为新<code>master</code>的<code>slave</code>,这样,当旧的<code>master</code>重新上线时,它会成为新<code>master</code>的<code>slave</code>。</p>
<h3>2. 搭建Redis Sentinel高可用架构</h3>
<p>这里使用两台服务器,每台服务器上开启一个<code>redis-server</code>和<code>redis-sentinel</code>服务。</p>
<p>redis-server说明</p>
<pre><code>172.16.2.185:6379 主