-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
744 lines (744 loc) · 120 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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[ES7.8.1 集群搭建踩坑记录]]></title>
<url>%2Felasticsearch-cluster%2F</url>
<content type="text"><![CDATA[时隔两年多时间,再次接触 elasticsearch,准备在测试环境搭建一套 ES 集群用来测试 之前使用ES还是在其1.4.x~2.0.x左右的时候,现在 ES 版本已经发展到了7.x以上了(中间经历过于kibana同步版本号,所以版本变动比较大) 单机安装大致安装步骤和之前的版本差别不大,依然保持了其开箱即用的特性,只需要很少的配置即可 单机很简单,修改集群名称,节点名称,数据及日志目录,以及监听绑定的ip,然后直接运行可执行文件即可 123456789注:下载的打包文件中,附带一个openjdk可以直接使用,如果使用 supervisor 来管理 es, 可以使用supervisor 的 environment 功能,来设置 JAVA_HOME 环境变量配置如下,ES_HOME 即 elasticsearch 解压后文件夹所在目录[es]command=$ES_HOME/bin/elasticsearchdirectory=$ES_HOMEenvironment=JAVA_HOME=$ES_HOME/jdk 单机没什么问题,很顺畅的就启动起来了,通过 curl http://ip:es_port 可以得到很熟悉的返回:1234567891011121314151617{ "name" : "node1", "cluster_name" : "cluster-name", "cluster_uuid" : "2c2jICSxTHK_jUcJjaBucw", "version" : { "number" : "7.8.1", "build_flavor" : "default", "build_type" : "tar", "build_hash" : "b5ca9c58fb664ca8bf9e4057fc229b3396bf3a89", "build_date" : "2020-07-21T16:40:44.668009Z", "build_snapshot" : false, "lucene_version" : "8.5.1", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search"} 组建集群既然单机的起来的,那如法炮制,将配置文件复制一份,修改了其 node name, scp到另外一台机器上,准备好 supervisor 配置,准备开始组建两个节点组成的集群 满以为不会有什么问题,启动之后,发现,第二个节点,怎么也加入不到第一个节点之中 问题出现了,看了看文档,发现自 es7.x版本以后,更新换代了一个集群协调的功能工具 zen, 简化了很多配置项,组建集群时,基本上就需要配置两个: discovery.seed_hosts, cluster.initial_master_nodes seed_hosts: 启动当前节点时,用于发现其他节点的初始列表initial_master_nodes: 初始的候选master节点列表 最开始的时候,这两个均配置了两台机器的ip,即[node1,node2],分别启动节点后,发现后启动的节点,怎么也加入不到之前启动的节点中,组成集群 两个节点,脑裂了~~~~ 网上搜了很多解决方案,绝大多数是说不该监听0或者0.0.0.0,遂调整配置,再次启动,发现依然无法解决两个节点脑裂问题 后来仔细研读了下,文档中对 initial_master_nodes 的说明,发现了问题: initial_master_nodes 中配置了当前节点,即意味着,当前节点也是可以候选的可用的master节点,但其实,当前节点都还没启动,它是没有资格去竞选成为master的,所以导致第二个节点启动后,自己独立成群,无法与已有节点组成集群 问题找到了,修改第二个节点的配置文件,initial_master_nodes 仅指定第一个节点为可用的候选 master节点 随后启动节点二,启动正常,节点二和节点1成功组成一个集群 后来,将 network.host 改为监听 0.0.0.0,两者依然可以组成集群,可见,网传的不能监听0.0.0.0的说法是不成立的]]></content>
<categories>
<category>elasticsearch</category>
</categories>
<tags>
<tag>elasticsearch</tag>
<tag>cluster</tag>
</tags>
</entry>
<entry>
<title><![CDATA[修改 git submodule]]></title>
<url>%2Fgit-submodule%2F</url>
<content type="text"><![CDATA[初始git 项目中,可以使用以下命令为项目添加submodule 1git submodule add -b branch_name [email protected]:javasgl/xxx.git module_path 执行以上命令后,会在当前项目下生成一个.gitmodules 文件,内容如下 1234[submodule "module_path"] path = module_path url = [email protected]:javasgl/xxx.git branch = branch_name 同时也将修改 .git/config文件123[submodule "module_path"] url = [email protected]:javasgl/xxx.git active = true 此时,执行以下命令即可从远程拉取相关代码到submodule中:123git submodule update --init// 可以使用 git submodule deinit 重新初始化 执行之后,会在 .git/modules/module_path/ 下看到 submodeule相关的代码 12.git/modules└── module_path 变更有时候,对于已经添加了submodule的项目,如果想更换 submodule 的地址,那个这就比较麻烦,目前git并没有直接提供相关命令,需要进行一系列的手工操作 简单记录如下,备查 比如,想把 submodule 的地址换为 github.com/xxx/xxx.git 将 submodule 移除 git 版本控制 1git rm module_path 移除 .git/config .gitmodules 中的配置 删除 .git/modules/下相关代码 1rm .git/modules/module_path 执行之前的初始化,可以重新add新的submodule 1git submodule add github.com/xxx/xxx.git xxx_path]]></content>
<categories>
<category>git</category>
</categories>
<tags>
<tag>git</tag>
<tag>submodule</tag>
</tags>
</entry>
<entry>
<title><![CDATA[缓存常见问题处理]]></title>
<url>%2Fhandle-cache-problems%2F</url>
<content type="text"><![CDATA[缓存使用过过程中常见问题基本是以下几个问题: 缓存穿透问题 缓存并发问题 热点数据问题 数据一致问题 这些问题不是独立的,一般都会存在因果关系或者同时存在 下面列举一下问题原因及基本解决思路 缓存穿透/过期缓存穿透问题是指请求数据在缓存中没有找到,导致请求直接打到数据库的情况 这种场景下,大致有两类原因可以导致这类问题: 1.缓存中没有命中,查询数据库,数据库无此数据,不会去更新缓存,这样导致下次同样的请求都会重复这样的请求,每次都会请求数据库 2.由于缓存过期时间设置不合理,导致瞬时大量缓存过期,大量请求直接打到数据库 原因 1 的解决方案:为数据设置默认值,即使从数据库没有查询到,也设置缓存,之后数据有值了去主动更新缓存 这样虽然可以避免查询一直不存在的数据而导致的缓存穿透问题,但是也容易引起恶意攻击,攻击方式:构造大量无效的条件去查询,这样导致缓存中大量的存在默认缓存,导致缓存占用量急剧增大而引起缓存驱逐,针对这种方式,可以为默认缓存设置一些比较短的过期时间来稍微缓解这一问题,只是缓解,无法完全解决这个攻击 原因 2 的解决方案: 不要设置统一的过期时间,为过期时间添加随机数,让数据过期时间分散开,不要集中在同一时间过期 缓存并发缓存并发问题是指多个请求请求同一个数据,而且都没有从缓存中获取到数据,并从数据库查询后都去更新缓存,导致缓存重复更新问题大多数场景下,这个问题影响并不大,只是会产生一些重复的数据库查询和重复更新缓存但是如果有些缓存不能重复更新,比如一些自动设置的时间、计数器等,那么重复更新会带来问题 这个问题的解决方案: 使用CAS方案,即设置缓存之前先检查是否已经设置了,如果设置过,则不作任何操作如果是使用redis,则可以直接使用其SETNX命令 热点数据绝大多数系统中,频繁访问的数据只是一小部分,而受硬件、成本等因素,把所有的数据都放在缓存中也不太现实 这个问题,可以使用日志分析、访问计数等方式统计热点数据,基于历史访问情况刷新TOP-N数据到缓存中,并且不定时的去更新这个TOP-N列表 数据一致问题这个问题一般比较难弄,只要使用缓存,则必然可能出现缓存不一致的问题,这个问题和使用缓存更新策略有很大的关系,具体问题需要根据不同的缓存更新方式来定制化解决,不能一概而论 这个问题基本思路是使用补偿机制,即使用定时、不定时巡检的方式,刷新缓存数据,保证数据一致性]]></content>
<categories>
<category>redis</category>
</categories>
<tags>
<tag>cache</tag>
<tag>redis</tag>
<tag>memcache</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mysql5.7/mariadb Generated Columns]]></title>
<url>%2Fmysql-generated-columns%2F</url>
<content type="text"><![CDATA[mysql 5.7 以后带来了一个新的功能,即 “Generated Columns”,字面意思可翻译为 “生成列”,顾名思义,这列的数据不能被直接插入或者更新,而是依据一定的规则自动生成。 Generated Columns 定义方式如下:1234col_name data_type [GENERATED ALWAYS] AS (expr) [VIRTUAL | STORED] [NOT NULL | NULL] [UNIQUE [KEY]] [[PRIMARY] KEY] [COMMENT 'string'] 注: 默认是 VIRTUAL,即 虚拟列,不实际占用存储空间,在读取数据时即时计算而来;STORED 则与之相反,它会生成真实的数据列,和正常的列无异,占用存储空间;两者在限制上各有细微区别,具体可查看文末 mysql/mariadb 链接阅读详情 以下样例,展示了自动生成指定时间字段的月份:c_month 的值通过对c_time进行内置函数month调用自动得到,插入语句中无需指定c_month的值12345678910111213141516171819202122mysql> CREATE TABLE generated_columns_test (c_time datetime,c_month int generated always as(month(c_time)));Query OK, 0 rows affected (0.19 sec)mysql> desc generated_columns_test;+---------+----------+------+-----+---------+-------------------+| Field | Type | Null | Key | Default | Extra |+---------+----------+------+-----+---------+-------------------+| c_time | datetime | YES | | NULL | || c_month | int(11) | YES | | NULL | VIRTUAL GENERATED |+---------+----------+------+-----+---------+-------------------+2 rows in set (0.01 sec)mysql> insert into generated_columns_test(c_time)values (now());Query OK, 1 row affected (0.00 sec)mysql> select * from generated_columns_test;+---------------------+---------+| c_time | c_month |+---------------------+---------+| 2020-02-18 09:33:42 | 2 |+---------------------+---------+1 row in set (0.00 sec) 是不是很熟悉?以前这样的需求,一般需要在程序中处理好月份后和 c_time 一并插入数据库,是 generated columns 则可以做自动生成,将程序逻辑后移到数据库层面了。 可以设想一下,generated columns 可用于哪些场景? 根据订单创建时间,自动填充默认过期时间? 根据发货时间,自动填充有效期? …… 当然,generated columns并非银弹,不是所有的业务逻辑都适合运用它,不应该把本该程序处理的部分业务交给数据库去处理,这将给系统维护带来隐患,这和不推荐使用存储过程和自定义函数一样的道理 总体来说,generated columns 虽然有诸多限制,但总体上使用起来还是很方便的,具体的文档可以查看: mysql generated columns mariadb generated columns]]></content>
<categories>
<category>mysql</category>
</categories>
<tags>
<tag>mysql</tag>
<tag>sql</tag>
<tag>mariadb</tag>
</tags>
</entry>
<entry>
<title><![CDATA[永久修改 mac mac files/proc 限制]]></title>
<url>%2Fmac-max-limit%2F</url>
<content type="text"><![CDATA[mac 升级后的某一天,本地跑程序时发现提示 open too many files, 竟然提示打开文件数过多。 使用ulimit -n 查看,最大的文件数限制为 256 ,这明显太小了。 使用launchctl limit 查看,结果如下: 12345678910~/codes/backupAnyThing/blog(master*) » launchctl limit cpu unlimited unlimited filesize unlimited unlimited data unlimited unlimited stack 8388608 67104768 core 0 unlimited rss unlimited unlimited memlock unlimited unlimited maxproc 1064 1064 maxfiles 256 256 解决方案大致上有三种: 命令行中执行 ulimit -n xxx, 但是仅对当前 session 生效,新开 session 或者重启后会恢复为系统值 在 .bashrc / .zshrc 等脚本中加上 ulimit -n xxxx, 这样每次新开 session 即可自动设置最大文件描述符显示 加入到 launchd 服务中, 永久修改 这里记录下第三种方案的步骤 创建两个 plist文件,民间名分别为 limit.maxproc.plist, limit.maxfiles.plist, 内容如下: file: limit.maxfiles.plist1234567891011121314151617181920<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>limit.maxfiles</string> <key>ProgramArguments</key> <array> <string>launchctl</string> <string>limit</string> <string>maxfiles</string> <string>200000</string> <string>200000</string> </array> <key>RunAtLoad</key> <true/> <key>ServiceIPC</key> <false/> </dict> </plist> file: limit.maxproc.plist 1234567891011121314151617181920 <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>limit.maxproc</string> <key>ProgramArguments</key> <array> <string>launchctl</string> <string>limit</string> <string>maxproc</string> <string>20480</string> <string>20480</string> </array> <key>RunAtLoad</key> <true /> <key>ServiceIPC</key> <false /> </dict> </plist> 将以上两个文件移动到 /Library/LaunchDaemons 目录下,确保两个文件的权限如下: 12-rw-r--r-- 1 root wheel 592B Sep 29 16:39 limit.maxfiles.plist-rw-r--r-- 1 root wheel 589B Sep 29 16:40 limit.maxproc.plist 重启系统,重启后自动生效。 注: 如果只需要修改 max files, 仅添加 limit.maxfiles.plist 文件即可。]]></content>
<categories>
<category>os</category>
</categories>
<tags>
<tag>mac</tag>
<tag>limit</tag>
</tags>
</entry>
<entry>
<title><![CDATA[angular ng-repeat 双向数据绑定实现动态增删DOM]]></title>
<url>%2Fangular-repeat-bind-object-of-arrays%2F</url>
<content type="text"><![CDATA[在实现一个需要动态增删的form表单需求时,MVVM的思维模式下,很容易就想到使用数组的添加和删除来实现DOM元素的动态添加和删除。 代码首先定义一个空数组,push几条初始数据(或者在相关按钮的click事件中去对这个数组进行增删),然后在页面上使用ng-repeat和ng-model在循环中绑定这个数组的数据。123456item= { name:'default value'}$scope.items= []$scope.items.push(item)$scope.items.push(item) html页面如下:123<div ng-repeat="item in items track by $index"> <input type="text" ng-model="item.name"></div> 问题以上代码看似没有问题,但是实际结果却并不预期。页面渲染完成后,修改两个input中的任何一个,数组中的两个元素的数据都保持了一致变动。尝试了一下几种方案之后均无法实现单独绑定:123456789方案1:<div ng-repeat="item in items track by $index"> <input type="text" ng-model="items[$index].name"></div>方案2:<div ng-repeat="item in items track by $index"> <input type="text" ng-model="$parent.items[$index].name"></div> 后来怀疑是代码中push(item)的问题,每次push的时候,添加的都是同一个item,这样数组中的每一个元素引用的都是内存中的同一个item的地址,所以修改任何元素,都会影响内存中的同一个item对象,从而导致所有的数组元素的值都同步一致修改了。 解决基于以上思路,产生了两种解决方案:方案一,每次push的时候,添加一个空对象到数组中,这样避免每个元素引用同一个内存对象1234$scope.items.push({})$scope.items.push({})$scope.items.push({})...... 方案二,每次push的时候,使用 angular.copy重新复制出一个新对象出来添加到数组中1234567item= { name:'default value'}$scope.items.push(angular.copy(item))$scope.items.push(angular.copy(item))$scope.items.push(angular.copy(item))......]]></content>
<categories>
<category>javascript</category>
</categories>
<tags>
<tag>angular</tag>
<tag>js</tag>
<tag>ng-repeat</tag>
<tag>mvvm</tag>
<tag>ng-model</tag>
</tags>
</entry>
<entry>
<title><![CDATA[用 Go Plugin 构建模块化系统]]></title>
<url>%2Fbuild-module-system-use-go-plugin%2F</url>
<content type="text"><![CDATA[自从 golang 1.8以后,提供了一个 Plugin 的机制使得 golang能够加载 so 动态链接库文件。这样可以对外发布动态链接库而不需要把源码共享给对方去进行编译,还可以做到按需加载相应的插件来实现动态开启插件相应的功能。 官方文档在此:https://golang.org/pkg/plugin/ 编写一个 Plugin 基本有以下几步: 1.Plguin 需要有自己的 main package2.编译的时候,使用 go build -buildmode=plugin file.go 来编译3.使用 plugin.Open(path string) 来打开.so文件,同一插件只能打开一次,重复打开会报错4.使用 plugin.LookUp(name string) 来获取插件中对外暴露的方法或者类型5.使用类型断言,断言后执行相应的方法 main.go1234567891011121314151617181920package mainimport ( "fmt" "plugin")func main() { p, err := plugin.Open("plugin.so") if err != nil { panic(err) } m, err := p.Lookup("GetProductBasePrice") if err != nil { panic(err) } res := m.(func(int) int)(30) fmt.Println(res)} plugin.go123456789101112package mainimport "fmt"func main() {}func GetProductBasePrice(basePrice int) int { return basePrice + 200} 注意几点问题: 插件中定义的 struct 无法暴露出来,可以让主程序和插件程序import公共的 package 来解决 私有方法、变量不会被暴露出来]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>plugin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[sync.Pool缓存池中对象回收时机]]></title>
<url>%2Fgo-sync-pool%2F</url>
<content type="text"><![CDATA[为了缓解GC压力,go标准库在sync包中提供了一个Pool,但是这个Pool和我们一般意义上的Pool不太一样,主要有以下几点区别:1.Pool无法设置大小,所以理论上只受限于系统内存大小。2.Pool中的对象不支持自定义过期时间及策略,究其原因,Pool并不是一个Cache.3.Pool的设计初衷是为了缓解GC压力,所以Pool中的对象会在GC开始前全部清除。 下面这段注释来源于pool.go:12345678910111213// A Pool is a set of temporary objects that may be individually saved and// retrieved.//// Any item stored in the Pool may be removed automatically at any time without// notification. If the Pool holds the only reference when this happens, the// item might be deallocated.//// A Pool is safe for use by multiple goroutines simultaneously.//// Pool's purpose is to cache allocated but unused items for later reuse,// relieving pressure on the garbage collector. That is, it makes it easy to// build efficient, thread-safe free lists. However, it is not suitable for all// free lists. 让我们用代码验证一下 sync.Pool中对象的回收时机:1234567891011121314151617181920212223242526package mainimport ( "fmt" "runtime" "sync")func main() { p := &sync.Pool{ New: func() interface{} { return 0 }, } a := p.Get().(int) p.Put(1) b := p.Get().(int) fmt.Println(a, b) a = p.Get().(int) p.Put(1) runtime.GC() //手动调用GC b = p.Get().(int) fmt.Println(a, b)} 执行结果分别打印出了: 0 1 0 0 可见,在手动调用GC之后,Pool中的对象被全部清除了,在Get的时候重新调用定义的New方法创建了新的对象]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>sync.Pool</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用go-svc安全退出程序]]></title>
<url>%2Fgo-svc%2F</url>
<content type="text"><![CDATA[阅读 NSQ 源码时,看到NSQ使用了 go-svc 来启动nsq的相关程序,使得在程序退出的时候可以做一些释放资源等收尾工作。 使用起来非常简单,定义一个Service,实现其 svc.Service中的接口即可。 简单使用12345678910111213141516171819202122232425262728293031323334353637383940414243package mainimport ( "fmt" "github.com/judwhite/go-svc/svc" "log" "syscall" "time")type program struct {}func main() { prg := &program{} if err := svc.Run(prg, syscall.SIGUSR1, syscall.SIGINT, syscall.SIGTERM); err != nil { log.Fatal(err) }}func (program) Init(env svc.Environment) error { fmt.Println("init.....") return nil}func (program) Start() error { fmt.Println("start.....") fmt.Println(syscall.Getpid()) go func() { ticker := time.NewTicker(2 * time.Second) for t := range ticker.C { fmt.Println("tick at", t) } }() return nil}func (program) Stop() error { fmt.Println("stop.....") return nil} 注意事项1.Start方法中不能只直接阻塞,需要在Start方法中新开goroutine去写需要阻塞的代码。2.svc.Run()方法的第二个参数可以指定需要程序监听的信号,默认情况下不指定的话,默认会监听 SIGINT和 SIGTERM两个。根据具体需要进行指定,比如本例中还监听了SIGUSR1. 实现原理其实go-svc代码实现很简单,使用了标准库中的os/signal的Notify方法。下面的代码截取于judwhite/go-svc/svc/svc-other.go:1234567891011121314151617181920212223242526// Run runs your Service.//// Run will block until one of the signals specified in sig is received.// If sig is empty syscall.SIGINT and syscall.SIGTERM are used by default.func Run(service Service, sig ...os.Signal) error { env := environment{} if err := service.Init(env); err != nil { return err } if err := service.Start(); err != nil { return err } if len(sig) == 0 { sig = []os.Signal{syscall.SIGINT, syscall.SIGTERM} } signalChan := make(chan os.Signal, 1) signalNotify(signalChan, sig...) //signalNotify方法其实就是 signal.Notify 方法 //var signalNotify = signal.Notify <-signalChan return service.Stop()} 标准库里面的 os/signal中的 Notfiy方法签名如下:1func Notify(c chan<- os.Signal, sig ...os.Signal) {}]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>go-svc</tag>
<tag>signal</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一个时区引发的`血案`]]></title>
<url>%2Fgo-time-zone%2F</url>
<content type="text"><![CDATA[项目中的一个单元测试,其中一个判断是比较两个时间是否相同。代码逻辑简化抽象如下:1234567time1:=time.Now()xorm.engine.NewSession().Update(time1)var time2 *time.Timexorm.engine.NewSession().Get(&time2)if time2.UTC().Format("2018-06-06") != time2.UTC().Format("2018-06-06"){ panic error} 介绍一下各组件时区设置: mysql 默认配置,SYSTEM xorm dsn 配置为 parseTime=true&loc=Asia%2FShanghai 以上代码,在本地机器上跑单元测试时无异常,能正常通过。但是,上了CI环境后,单元测试却失败了。通过观察CI的日志和自己打印的日志,发现入库前后,这两个时间相差了8个小时,很明显,这个时间受到时区的影响了。 后来调试发现,本地 mysql time_zone SYSTEM的实际值是 CST (中国标准时间),所以 dsn中指定loc为 Asia/Shanghai 并不会有问题。而CI环境中,mysql的 time_zone SYSTEM的实际值是 UTC.而此时代码中使用dsn却是Asia/Shanghai,这样就会导致 xorm.Get()的时候,将时间按照Asia/Shanghai来解析,所以,解析出来的结果和UTC时间相差了8个小时。 解决方案:将代码中使用的dsn改为 parseTime=true&loc=Local 。 最终总结,其实这个问题是可以避免的,起初在设计数据库的时候,时间字段如果没有特殊需求,不应该使用数据库的date,timestamp等类型,而是统一都使用 INT UNSIGNED NOT NULL。这样数字类型的时间戳,即可以避免时区问题,还可以用来直接比较大小和进行算数运算。 附上mysql查看时区配置的几种方式:1select @@global.time_zone,@@session.time_zone; 1show variables like %time_zone%;']]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>time</tag>
<tag>timezone</tag>
<tag>orm</tag>
<tag>sql</tag>
</tags>
</entry>
<entry>
<title><![CDATA[go覆盖测试率图形化分析]]></title>
<url>%2Fgo-cover-ui%2F</url>
<content type="text"><![CDATA[go test可以用来做单元测试,-cover参数可以显示测试覆盖率。1go test -v ./... -cover 结果如下:123...PASScoverage: 34.4% of statements 但是有时候需要提高覆盖测试率,就需要知道哪些代码被覆盖到了,而哪些代码没有被覆盖到。这时候可以借助go提供的一个工具来实现。 首先,执行单元测试时,添加 -coverprofile=profileName,生成 profile文件。 1go test -v ./... -coverprofile=aaaa 之后,使用go tool cover -html=profileName -o coverage.html来生成html文件后用浏览器打开。 12go tool cover -html=aaaa -o coverage.htmlopen -a /Applications/Google\ Chrome.app coverage.html 最终看到的效果是:红色表示没有覆盖到的代码,绿色表示覆盖到的代码,这样就能针对性的编写单元测试,提高测试覆盖率了。]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>UnitTest</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用 reindex 来修改 elasticsearch 索引mapping]]></title>
<url>%2Felastic-search-reindex%2F</url>
<content type="text"><![CDATA[elasticsearch索引一旦建立,就无法动态修改其字段的映射类型,有时候因为人为原因污染了索引的mapping,这个时候就只能通过重建索引来修改索引的mapping设置了。 在一次项目中,有一个字段结构如下:123456{ "logistics":{ "company":"string", "no":"string" }} 由于当初创建索引的时候,既没有给这个索引中的这个字段指定合适的类型,也没有通过动态模板来为这个字段指定类型,导致 elasticsearch 默认将这种结构的数据设置成了 Object 类型,而我们真正想要设置的类型却是 Nested 类型。 这种情况下,一般有两种解决方案。 第一种方案,给这个索引追加一个新的字段,同时给这个字段指定类型。12345678910111213141516PUT test/_mapping/test{ "properties": { "logisticsV2":{ "type": "nested", "properties": { "company":{ "type":"keyword" }, "no":{ "type":"keyword" } } } }} 执行之后,index的mapping中就是这样的了:12345678910111213141516171819202122232425262728293031323334353637383940414243{ "test":{ "mappings":{ "test":{ "properties":{ "logistics":{ "properties":{ "company":{ "type":"text", "fields":{ "keyword":{ "type":"keyword", "ignore_above":256 } } }, "no":{ "type":"text", "fields":{ "keyword":{ "type":"keyword", "ignore_above":256 } } } } }, "logisticsV2":{ "type":"nested", "properties":{ "company":{ "type":"keyword" }, "no":{ "type":"keyword" } } } } } } }} 其中 logisticsV2的类型是 nested 类型,之后依赖于 nested 类型的相关功能使用 logisticsV2字段来实现即可。这个中方式有一些弊端,比如数据冗余问题、数据一致性问题等 第二个方案,使用 elasticsearch 提供的 reindex api 来迁移数据,创建新的索引。首先创建好目标索引,并设置好mapping:1234567891011121314151617PUT test_newPUT test_new/_mapping/test{ "properties": { "logistics":{ "type": "nested", "properties": { "company":{ "type":"keyword" }, "no":{ "type":"keyword" } } } }} 之后,使用 reindex 将原来的索引重建到新的索引上:123456789POST _reindex{ "source": { "index": "test" }, "dest": { "index": "test_new" }} 等待 reindex 完成后, test_new 就结构就就是你想要的mapping了。 为了对线上业务做到无感,可以使用 alias 别名功能来实现,具体操作可以参考 使用 Elasticsearch alias 功能切换 indexl. 这里不再赘述.]]></content>
<categories>
<category>elasticsearch</category>
</categories>
<tags>
<tag>elasticsearch</tag>
<tag>reindex</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python的浅复制和深复制]]></title>
<url>%2Fpython-reference-value-md%2F</url>
<content type="text"><![CDATA[最近使用python过程中遇到了一个看似诡异的问题,由于惯性思维,这个问题花了一点时间一步步调试才找到。先来看看下面的代码的结果:12345a = [1,2,3]b = ab.append(4)print(b)print(a) 如果这段代码放在其他语言,Java 或者 PHP 中,结果大多是这样的:12[1,2,3,4][1,2,3] 但是在 python 中,实际执行的结果却是这样的:12[1,2,3,4][1,2,3,4] 修改 b 的值,却影响了 a 的值。这个就是 python 和其他语言有一点不一样的地方了,简单可以理解为:python 没有赋值,只有引用。如果以其他语言的方式来理解,那就是 a,b 都是指向同一块内存数据的地址,所以修改其中任何一个,都会引起彼此的变化,因为底层数据都是同一份。 现在,如果需要这段代码表现的和其他语言一致,该怎么写呢? 浅复制(shallow copy)对于list 列表,python 有 copy 方法:12345a = [1,2,3]b = a.copy()b.append(4)print(b)print(a) 代码执行结果就基本能达到预期了:12[1,2,3,4][1,2,3] 深复制(deep coopy)但是需要注意的是,这个 copy 方法是浅复制(shallow copy),那么它对于嵌套对象就无能为力了。如果需要复制嵌套对象,可以使用 import copy ,然后使用 copy.deepcoopy()来实现对嵌套对象的复制12345678import copya = [1,2,3,[4,5]]# b = a.copy()b = copy.deepcopy(a)b.append(7)b[3][1]=6print(b)print(a) 代码执行结果是这样的:12[1, 2, 3, [4, 6],7][1, 2, 3, [4, 5]] 可以看到 a 并没有因为 b 对嵌套内容的修改而改变。这里需要注意的一点是,如果修改的不是嵌套的内容,那么普通的copy也还是可以的,此例中,append(7) 时,使用 copy 或者 copy.deepcopy 的效果是一样的,a的值都不会受到影响。 deepcopy的本质是递归复制。 对于 list 数据结构而言,可以用 list(x) 来代替 copy, 不过它依然是浅复制,这点需要注意。]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>shallowcopy</tag>
<tag>deepcopy</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python monkey patch解决gevent block forever问题]]></title>
<url>%2Fpython-monkey-patch%2F</url>
<content type="text"><![CDATA[在 python 中使用 multiprocess 进行多线程或者多进程处理的时候,遇到了 gevent 问题:1gevent.hub.LoopExit:('This operation would block forever....') 这个问题可以通过打上 monkey patch(猴子补丁)来规避。 在导入 multiprocess 库的文件头部加入以下代码:123from gevent import monkeymonkey.patch_all() 注意,这两行代码需要放置在文件导入库的最前面. 我遇到的场景下, 导致 LoopExit 的原因是 multiprocess 的job中远程网络请求超时导致的,如果相应的job任务中没有远程请求的话,即使不用 pathch,代码也是能够正常运行的。由于避免加上超时处理的复杂逻辑,就简单的使用monkey patch来解决这个问题。]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>monkey</tag>
<tag>patch</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python asyncio 简单使用]]></title>
<url>%2Fuse-asyncio-in-python%2F</url>
<content type="text"><![CDATA[在python 3.4及以上的版本中,可以通过 asyncio 来实现协程(coroutine)。在 python 3.5以后可以使用 async/await 来简化代码,如果python版本是3.4的话,需要通过 @asyncio.coroutine注解 和 yield from 来实现。 一下代码是3.5+以后的写法,和3.4版本的写法主要区别在于: 用 async 代替了 @asyncio.coroutine 用 await 代替了 yield from 1234567891011121314import asyncioasync def task(n): print("do task:",n) await asyncio.sleep(1)tasks = [ task(_) for _ in range(10)]loop = asyncio.get_event_loop()res, _ = loop.run_until_complete(asyncio.wait(tasks))loop.close()for _ in res: print(_.result()) 参考: 廖雪峰的Python3.x教程-异步io]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>coroutine</tag>
<tag>asyncio</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python 多进程、多线程简单使用]]></title>
<url>%2Fuse-theading-in-python%2F</url>
<content type="text"><![CDATA[python 中可以使用 multiprocessing (多进程) 和 multiprocessing.dummy(多线程) 来实现并发操作。两者在使用方式上一样,只不过实现并发的方式不同。 以下简单使用案例以 multiprocessing.dummy 多线程为例:1234567891011121314151617from multiprocessing.dummy import Pooldef compute(param): return param*100params = [ _ for _ in range(10) ]pool = Pool()results = pool.map(compute, params)pool.close()pool.join()print(results) 注意:多进程(multiprocessing) 无法在 celery 等后台进程中使用,因为 celery 等后台进程不再允许生成子进程,这时候就只能使用多线程或者协程了。 1daemonic processes are not allowed to have children]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[go build 常见编译优化]]></title>
<url>%2Fgo-build-args%2F</url>
<content type="text"><![CDATA[一般情况下,go build 可以直接编译程序,无需额外的参数设定。但在编译生产环境下使用的可执行程序时候,go build 的一些参数还是很有用的。 减小编译后可执行程序的大小1go build -ldflags '-w -s' 说明: -w 禁止生成debug信息,注意使用该选项后,无法使用 gdb 进行调试 -s 禁用符号表可以使用 go tool link --help 查看 ldflags 各参数含义 禁止gc优化和内联1go build -gcflags '-N -l' 说明: -N 禁止编译优化 -l 禁止内联,禁止内联也可以一定程度上减小可执行程序大小可以使用 go tool compile --help 查看 gcflags 各参数含义]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title><![CDATA[golang同时监听TCP、HTTP端口提供服务]]></title>
<url>%2Fgolang-listen-tcp-and-http-port%2F</url>
<content type="text"><![CDATA[之前一个golang写的服务,是使用 TCP 方式进行通信的,但是并没很好的处理粘包问题(其实是根本就没有处理粘包问题)。项目需要添加新功能后迅速上线,所以准备先采用http来通信,避免粘包问题。 项目入口 main.go 中,之前是监听 TCP 连接,代码大致如下:12345678910111213141516171819func main(){ server,err:= net.Listen("tcp","host:port") if err!=nil{ return } defer server.Close() for{ conn,err:= server.Accept() if err!=nil{ continue } go handleConn(conn) }}func handleConn(conn net.Conn){ //do somethings} 现在需要在此基础之上监听 http 连接,由于 http.ListenAndServe() 方法是阻塞的,所以需要新开goroutine进行监听,代码示意如下:123456789101112131415161718192021222324func main(){ http.HandleFunc("/hi", Router) //因为会阻塞,所以需要新开goroutine进行监听 go http.ListenAndServe("host:http_port", nil) server,err:= net.Listen("tcp","host:tcp_port") if err!=nil{ return } defer server.Close() for{ conn,err:= server.Accept() if err!=nil{ continue } go handleConn(conn) }}func handleConn(conn net.Conn){ //do somethings}]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>http</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用 Elasticsearch alias 功能切换 index]]></title>
<url>%2Fuse-alias-migrate-index%2F</url>
<content type="text"><![CDATA[有时候,需要将已有的索引做些调整重建为另外一个索引,但是为了不影响线上的访问,需要无缝切换到新的索引上。 基本上有两种方案,一是改代码,使线上代码访问新的索引,二是使用 alias 别名功能。 第一种方案有弊端,在代码进行发布到具体机器之前的并不能保证所有的访问都是访问的新的索引。 这里选择通过 alias 来切换索引,因为 alias 中的多条命令是原子性。 1.创建索引 test 和 test_v2123PUT testPUT test_v2GET test* 结果如下:12345678910111213141516171819202122232425262728293031323334{ "test": { "aliases": {}, "mappings": {}, "settings": { "index": { "creation_date": "1514183636616", "number_of_shards": "5", "number_of_replicas": "1", "uuid": "oOgEhLzqS_usf7floIhNig", "version": { "created": "5030099" }, "provided_name": "test" } } }, "test_v2": { "aliases": {}, "mappings": {}, "settings": { "index": { "creation_date": "1514183638407", "number_of_shards": "5", "number_of_replicas": "1", "uuid": "o6x6VtmQSFaOpOB-OpIwBg", "version": { "created": "5030099" }, "provided_name": "test_v2" } } }} 2.现在需要将对 test 的访问无缝切换到 test_v2,使用 alias 功能即可做到,对线上服务、对用户无感12345678910111213141516POST _aliases{ "actions": [ { "add": { "index": "test_v2", "alias": "test" } }, { "remove_index": { "index": "test" } } ]} actions 中的多条命令是原子性的,所以可以做到无缝切换,不必担心切换过程中会存在访问不到 test 时候。 3.命令解释actions 中第一条命令:add 命令,给索引 test_v2 添加了一个别名 test。actions 中第二条命令:remove_index, 删除原来的 test 索引。执行之后,原来的 test索引会被删除,而对于访问方而言,访问的索引依然是 test,不过访问的是一个别名,底层实际访问的索引已经是 test_v2了 12345678910111213141516171819202122GET test*{ "test_v2": { "aliases": { "test": {} }, "mappings": {}, "settings": { "index": { "creation_date": "1514183638407", "number_of_shards": "5", "number_of_replicas": "1", "uuid": "o6x6VtmQSFaOpOB-OpIwBg", "version": { "created": "5030099" }, "provided_name": "test_v2" } } }} 从结果中可见,test索引已经不存在了,但是通过 GET test 还是能访问到,而现在 test_v2 多了一个 alias test。]]></content>
<categories>
<category>elasticsearch</category>
</categories>
<tags>
<tag>elasticsearch</tag>
<tag>es</tag>
<tag>alias</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python celery 强制使用root用户执行]]></title>
<url>%2Fpython-celery-force-root%2F</url>
<content type="text"><![CDATA[当 celery 使用 root 用户执行时,会报出如下警告:1RuntimeWarning: You're running the worker with superuser privileges: this is absolutely not recommended! 低版本的 celery 情况不清楚,但是 celery 4.1.0 版本中,这个警告是无法去除的。因为源码中是这样写的: 123456789101112131415161718192021222324def check_privileges(accept_content): uid = os.getuid() if hasattr(os, 'getuid') else 65535 gid = os.getgid() if hasattr(os, 'getgid') else 65535 euid = os.geteuid() if hasattr(os, 'geteuid') else 65535 egid = os.getegid() if hasattr(os, 'getegid') else 65535 if hasattr(os, 'fchown'): if not all(hasattr(os, attr) for attr in ['getuid', 'getgid', 'geteuid', 'getegid']): raise SecurityError('suspicious platform, contact support') if not uid or not gid or not euid or not egid: if ('pickle' in accept_content or 'application/x-python-serialize' in accept_content): if not C_FORCE_ROOT: try: print(ROOT_DISALLOWED.format( uid=uid, euid=euid, gid=gid, egid=egid, ), file=sys.stderr) finally: os._exit(1) warnings.warn(RuntimeWarning(ROOT_DISCOURAGED.format( uid=uid, euid=euid, gid=gid, egid=egid, ))) 很明显,这个 RuntimeWarning 是无法通过 C_FORCE_ROOT 设置来屏蔽的。当然了,这个只是一个警告,celery的运行并不受影响。 如果遇到某些版本的 celery 因为 root 用户而无法启动、无法执行的情况,可以通过改变 C_FORCE_ROOT=True 来规避这个问题。 主要有四种方式: 1.手动设置环境变量1export C_FORCE_ROOT="true" 2.使用 supervisor 管理 celery 时,可以用 environment 来设置环境变量.1environment=PYTHONPATH="xxx",C_FORCE_ROOT="true" 3.代码中硬编码指定.123from celery import platformsplatforms.C_FORCE_ROOT=True 4.写入 bashrc 或者其他系统启动脚本之中12file:.bashrcexport C_FORCE_ROOT="true"]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>celery</tag>
<tag>supervisor</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Virtualenv 使用]]></title>
<url>%2Fuse-python-with-virtualenv%2F</url>
<content type="text"><![CDATA[一般情况下,大多数系统上安装的 python 版本为 2.x 的。但是有时候我们需要同时使用 python3.x 和 python2.x,这里推荐使用 virtualenv 来管理两个甚至是多个不同版本的 python。 安装 python3mac系统安装python3,很简单, brew install python3, 升级也很简单,brew upgrade python3。注意可能需要 sudo。 安装 virtualenv可以用过 pip来安装,pip install virtualenv 创建一个python环境例如创建一个 python3 的环境,virtualenv --python=python3 /usr/local/python3 激活执行source /usr/local/python3/bin/activate即可]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>virtualenv</tag>
</tags>
</entry>
<entry>
<title><![CDATA[git 推送大文件buffer设置]]></title>
<url>%2Fgit-post-buffer%2F</url>
<content type="text"><![CDATA[git默认的 http.postBuffer大小为1M。所以偶尔遇到比较大的文件或者项目时,会报出以下错误:123error: RPC failed; result=22, HTTP code = 411fatal: The remote end hung up unexpectedlyfatal: The remote end hung up unexpectedly 这时可以通过设置 http.postBuffer来解决。下面设置为100M。1git config --global http.postBuffer=104857600]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>git</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Buffered Channel + sync.WaitGroup 实现对 goroutinue 并发数的限制]]></title>
<url>%2Fgoroutinue-limit%2F</url>
<content type="text"><![CDATA[写过爬虫的都应该考虑这个问题:不能毫无节制、毫无节操的无限制的并发爬取目标网站的内容,不然很容易被对方识别出来从而被封。 Go 的 goroutinue 机制使用起来非常简单,所以很多人比如我就喜欢用 Go 来写写爬虫。那么如何限制 goroutinue 并发数呢? 下面提供一种 Buffered Channel + sync.WaitGroup 实现对 goroutinue 并发数进行控制的思路。这种方式其实在 D&K 所著的《The Go Programing Language》一书中的第八章(goroutinue and channels) 中也有提到。 具体代码如下:123456789101112131415161718192021222324252627package mainimport ( "fmt" "sync" "time")func main() { wg := &sync.WaitGroup{} limiter := make(chan bool, 10) for i := 0; i < 100; i++ { wg.Add(1) limiter <- true go download(i, limiter, wg) } wg.Wait()}func download(index int, limiter chan bool, wg *sync.WaitGroup) { defer wg.Done() fmt.Println("start to download :", index) time.Sleep(1 * time.Second) <-limiter} 这段代码中,sync.WaitGroup 主要用来管理 goroutinue ,而 limiter 这个带有缓冲的通道则是用来控制并发数。 由于通道的阻塞特性,当并发数达到规定的阈值(创建通道时指定的缓冲容量)之后, limiter<-true就会阻塞,其后面的 go download(i,limiter,wg)就不会被执行到,达到了暂停产生 goroutinue 的效果,实现了对 goroutinue 并发数的控制。 当其中一个 download goroutinue 完成的时候,从 limiter 中释放了一个空位,此时,被 limiter 所阻塞的 for 循环得以继续执行,从而继续产生新的 goroutinue。]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>goroutinue</tag>
</tags>
</entry>
<entry>
<title><![CDATA[http压力测试工具--wrk]]></title>
<url>%2Fhttp-benckmarking-tool-wrk%2F</url>
<content type="text"><![CDATA[发现一个新的 http 压力测试工具 wrk ,简单好用。下载地址:https://github.com/wg/wrk 。类似于之前个人经常使用的一些压测工具,详细请移步 压力测试工具ab、webbench、http_load、siege简单使用。 wrk 使用非常简单,参数不多,但是足够自己简单压测一些 http 服务了。下面简单介绍下如何安装和使用。 安装需要自己把源码下载下来,自行编译。 123git clone --depth=1 https://github.com/wg/wrkcd wrkmake 编译完成之后,会在当前目录下生成一个 wrk的可执行文件。为方便以后使用,可以将其复制到系统 path 路径中:1cp wrk /usr/local/bin/. 使用使用 wrk 会提示使用方式: 123456789101112131415~ » wrkUsage: wrk <options> <url> Options: -c, --connections <N> Connections to keep open -d, --duration <T> Duration of test -t, --threads <N> Number of threads to use -s, --script <S> Load Lua script file -H, --header <H> Add header to request --latency Print latency statistics --timeout <T> Socket/request timeout -v, --version Print version details Numeric arguments may include a SI unit (1k, 1M, 1G) Time arguments may include a time unit (2s, 2m, 2h) 使用案例:1wrk -c300 -d10m -t8 http://google.com -c300 : 300连接-d10m : 测试10分钟-t8 : 使用8个线程 测试结果如下:12345678910~ » wrk -c300 -d5s -t8 http://baidu.comRunning 5s test @ http://baidu.com 8 threads and 300 connections Thread Stats Avg Stdev Max +/- Stdev Latency 474.98ms 177.40ms 1.44s 77.13% Req/Sec 47.61 41.14 180.00 79.78% 516 requests in 5.05s, 194.51KB read Socket errors: connect 0, read 2357, write 0, timeout 0Requests/sec: 102.18Transfer/sec: 38.52KB]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>http</tag>
<tag>benchmark</tag>
<tag>wrk</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python threading 多线程简单使用]]></title>
<url>%2Fpython-threading%2F</url>
<content type="text"><![CDATA[之前一个项目,使用 Flask 框架写了一个网络图片抓取的程序,当时时间紧急,多张图片下载的时候,就简简单单的使用 for in 循环去获取网络图片内容。后来有空了,看了下 python 的多线程的使用,挺简单的,就用 threading 重新改造了下这个程序,让其支持多线程。 python theading 多线程基本使用方式非常简单:12345678910111213141516171819202122import threadingdef hello(*args): print "hello"threads=[]i=0while i<10: i+=1 threads.append(threading.Thread(target=hello))for thread in threads: thread.setDaemon(True) thread.start()for thread in threads: thread.join() 当时使用 threading 的时候,遇到了一个问题: 如何获取子线程执行结果?网上搜索一番,基本上有两种方案: 继承 threading.Thread, 重载 run 方法。 使用外部资源,比如共享存储,队列等方式 项目比较简单,所以直接选择了第一种方式来实现,避免依赖于外部资源。自己封装了一个 CustomThreader 类,代码寥寥几行,非常简单:12345678910111213141516#!/usr/bin/env python# -*- coding: utf-8 -*-import threadingclass CustomThread(threading.Thread): def __init__(self, func, args=()): super(CustomThread, self).__init__() self.func = func self.args = args self.result = None def run(self): self.result = self.func(self.args) def getResult(self): return self.result 继承了 threading.Thread, 重载了 run 方法,在 run 方法里面讲执行结果复制给了成员变量 result, 然后对外提供了一个获取返回结果的方法 getResult。使用方式简单,只需要做一下改动即可:12345threads.append(CustomThread(func=hello,args=param))result=[]for thread in threads: result.append(thread.getResult())]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
<tag>thread</tag>
</tags>
</entry>
<entry>
<title><![CDATA[json-iterator--更快的json解析库]]></title>
<url>%2Fgo-json-iterator%2F</url>
<content type="text"><![CDATA[golang 中自带了非常方便解析json 的库 encoding/json,一般情况下也是使用这个库来作为 json 的 encode / decode。但是在有些场景下,比如解析大量json格式的日志数据的时候,json的解析性能问题就是一个需要特别关注的问题了。 滴滴出行平台技术部的开源json解析库 json-iterator,性能比原生的 encoding/json 快很多。 下面自己就简单测试了下两者的区别,测试代码很简单,就简单测试了下两者 Unmarshal 和 Marshal 的性能表现: 测试代码: json-benckmark_test.go 执行 go test -test.bench=.后结果:123456Benchmark_array_by_json-4 500000 2748 ns/op 456 B/op 14 allocs/opBenchmark_array_by_jsoniter-4 2000000 676 ns/op 144 B/op 3 allocs/opBenchmark_decode_by_json-4 500000 2804 ns/opBenchmark_decode_by_jsoniter-4 2000000 764 ns/opPASSok learnGo/json 7.217s 可见,json 和 jsoniter 在 Unmarshal 和 Marshal 方面的差别还是比较大的。 go 版本的 jsoniter/go 最近已经发布了 1.0 版本,Kubernetes V1.8 准备引入这个库,见 https://github.com/json-iterator/go/issues/154。 同时, jsoniter 也有 java 可以使用的库 jsoniter/java, 据官方测试,性能比我之前比较喜欢使用的 alibaba 的 fastjson 都还要好,以后有机会可以尝试下。]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>json</tag>
<tag>json-iterator</tag>
<tag>encoding</tag>
<tag>fastjson</tag>
</tags>
</entry>
<entry>
<title><![CDATA[codis dashboard 无法启动处理流程]]></title>
<url>%2Fcodis-dashboard%2F</url>
<content type="text"><![CDATA[使用 codis 过程中,当 codis dashboard 异常挂掉之后,重启 codis-dashboard的时候,无法启动,日志报错信息为:122017/08/31 14:43:46 dashboard.go:234: [PANIC] create zk node failed[error]: dashboard already exists: {"addr": "192.168.0.1:28087", "pid": 15716} 提示无法创建 zookeeper node,因为已经存在 dashboard node 了。 处理方案:连上 zookeeper, 删除相应的节点后重新启动 codis-dashboard。12/usr/local/zookeeper/bin/zkCli.sh -server 192.168.0.1:2181delete /zk/path/dashboard codis-dashboard 重新起来之后,发现还是无法连接 codis。123456782017/08/31 14:44:53 dashboard.go:160: [INFO] dashboard listening on addr: :280872017/08/31 14:44:53 dashboard.go:143: [INFO] dashboard node created: /zk/codis/path/dashboard, {"addr": "192.168.0.1:28087", "pid": 10261}2017/08/31 14:44:53 dashboard.go:144: [WARN] ********** Attention **********2017/08/31 14:44:53 dashboard.go:145: [WARN] You should use `kill {pid}` rather than `kill -9 {pid}` to stop me,2017/08/31 14:44:53 dashboard.go:146: [WARN] or the node resisted on zk will not be cleaned when I'm quiting and you must remove it manually2017/08/31 14:44:53 dashboard.go:147: [WARN] *******************************2017/08/31 14:45:09 proxy.go:191: [INFO] mark_offline, check proxy status:proxy_1<nil> zk: node does not exist2017/08/31 14:45:09 proxy.go:193: [INFO] shutdown proxy successful 这时候需要访问 http://192.192.168.0.1:28087/admin ,找到最后的 proxy status。将相应的 proxy Mark Online 即可。启动日志中也提到了,对于 kill -9 pid 停止 codis 的情况,需要手动删除 zookeeper 相应的节点。 zookeeper 常用命令,使用 help 查看:12345678910111213141516171819202122ZooKeeper -server host:port cmd args stat path [watch] set path data [version] ls path [watch] delquota [-n|-b] path ls2 path [watch] setAcl path acl setquota -n|-b val path history redo cmdno printwatches on|off delete path [version] sync path listquota path rmr path get path [watch] create [-s] [-e] path data acl addauth scheme auth quit getAcl path close connect host:port]]></content>
<categories>
<category>linux</category>
</categories>
<tags>
<tag>codis</tag>
<tag>zookeeper</tag>
</tags>
</entry>
<entry>
<title><![CDATA[可变参数使用]]></title>
<url>%2Fvariable-parameter%2F</url>
<content type="text"><![CDATA[很多语言都支持可变参数,下面记录下平时主要使用的几种语言的可变参数用法。主要有 go、php、python、java、js等语言的可变参数的基本使用。 go 可变参数go 可变参数只能是最后一个参数,参数接收后存放在一个 slice 中。123456789101112131415161718package mainimport ( "fmt" "reflect")func main() { hello("variable parameter", 1, 3, 4, 45, 56, 3) hello("second", []int{22, 33, 44}...)}func hello(b string, args ...int) { fmt.Println(b) fmt.Println(args) fmt.Println(reflect.TypeOf(args))} 输出:123456variable parameter[1 3 4 45 56 3][]intsecond[22 33 44][]int php 可变参数可变参数也只能是最后一个参数,参数接收后存放在一个 array 中。123456789function hello( $a , ...$args ){ print_r( $a ); echo PHP_EOL . '.....' . PHP_EOL; print_r( $args );}hello( 'first' , 1 , 2 , 'string' , 4 , "5" );hello( 'second' , ...[ 22 , 33 , 44 , 55 ] ); 输出:12345678910111213141516171819first.....Array( [0] => 1 [1] => 2 [2] => string [3] => 4 [4] => 5)second.....Array( [0] => 22 [1] => 33 [2] => 44 [3] => 55) python 可变参数python的可变参数有两个种方式 123456#!/usr/bin/env pythondef hello(a,b,*c,**kwargs): print a,b,c,kwargshello("first","second")hello("first","second","third","fourth",order="asc",group="admin") 输出:12first second () {}first second ('third', 'fourth') {'group': 'admin', 'order': 'asc'} python 中可变参数方式一:arg,这种可变参数传递时不需要指定参数的key,参数接收后存放在 tuple 中。python 中可变参数方式二:*kwargs,这种方式接收的参数需要指定参数的key,参数接收后存放在 dict 中。 java 可变参数Java 的可变参数只有能有一个,并且必须是方法的最后一个参数。1234567891011121314public class Hello{ public static void main(String[] args) { hello("first",2,"args1","args2"); } public static void hello(String a,int b,String... args){ System.out.println(a); System.out.println(b); for(String param :args){ System.out.println(param); } }} 输出:1234first2args1args2 可变参数存放于数组之中。 js 可变参数js 可变参数可以通过js内置的 arguments 来访问:1234567function hello(a){ console.log('a:'+a); for(i in arguments){ console.log(arguments[i]); }}hello("first",1,2,'string') 输出:12345a:firstfirst12string arguments 中也包含了其他非可变参数的内容。]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>php</tag>
<tag>python</tag>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[交叉编译 go 程序]]></title>
<url>%2Fgo-cross-complie%2F</url>
<content type="text"><![CDATA[相信大多数人和我一样,都是在 mac 或者 windows 上写 go 代码,但是最终上线运行却是在 Linux 系统之上。这就涉及到一个问题:由于操作系统架构的差异,编译过后的二进制程序能够在多个系统下正常运行么? go 不像 java 等运行于 JVM 之上的语言,也不像 php , python 等解释性语言,它需要编译后不依赖于其他环境就能执行,但是编译过程却依赖于系统架构,所以 go 需要交叉编译。 释义:交叉编译,就是在一个平台上生成另一个平台上的可执行代码。 go 非常方便的支持交叉编译。 我们先来看看不同环境下 go env 返回的结果。 Mac 下:123GOARCH="amd64"GOOS="darwin"CGO_ENABLED="1" Linux 下:123GOARCH="amd64"GOOS="linux"CGO_ENABLED="1" 架构均是 x64 架构,区别是 GOOS 。 比如,我在 mac 上开发,最终需要部署到 linux 上运行,在没有 ci(持续集成)的支持下,一般有两种方式: 将源代码复制到目标服务器上( scp 或者 git ),然后在目标机器上 go build 编译。 本地编译,只将最终编译过后的二进制文件复制到目标机器上。 大多数肯定倾向于第二种方式,那么这时候就需要交叉编译了。 编译成Linux下可执行文件1CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build 编译成Windows下可执行文件1CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build 编译过后,将二进制传送到相应系统即可运行。 但是,这种交叉编译有一点限制就是: 不支持CGO的交叉编译,如果程序中使用了 CGO , 则不能通过这种方式来交叉编译。 如果使用 GUN Make ,则可以将以上参数写到 Makefile 文件中:12345build-linux: export CGO_ENABLED=0 && export GOOS=linux && export GOARCH=amd64 && go buildbuild-wins: export CGO_ENABLED=0 && export GOOS=windows && export GOARCH=amd64 && go build 参考: cross-compilling-go]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>complie</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Elasticsearch 几种 Search Type]]></title>
<url>%2Fes-search-type%2F</url>
<content type="text"><![CDATA[Search Type 有助于理解 Elasticsearch 在多个分片(shard)的情况下,是如何搜索的,是如何决定返回数据数量、如何排序、如何算分的? 执行过程在多个分片情况下,查询 0-20 条记录,elasticsearch 会将请求转发到每个分片上,从每个分片上获取 20 条记录,然后返回给分发节点进行汇总,最后再从汇总的结果中取 0-20 数据返回给客户端。则就是 elasticsearch 分布式搜索的基本的执行过程。 Search TypeElasticsearch 中的 search type 主要有 6种:Query then fetch、Dfs Query then fetch、Count、Scan、Query and fetch、Dfs query and fetch。 执行请求的过程中,涉及到几个不同的 search type。 Query then fetch 查询主要分为两步: 第一步,查询节点首先将请求转发给所有参与的节点,每个节点依据自身的数据执行查询,并将必要的信息返回给查询节点,查询节点进行合并、重新排序等操作。 第二步,查询节点仅从相关的节点获取数据返回。 如果没有在请求中没有指定 search_type 参数这这是默认的方式。 Dfs Query then fetch 和 Query then fetch 一样,只不过在第一步转发请求的时候,它将计算分布式词频来获得更准确的得分。 Count 顾名思义,这个并不返回文档数据,只返回文档的数量。 Scan 适用于大量数据进行深度分页获取数据,禁用排序提升性能。 Query and fetch 这是 elasticsearch 内部的一种优化,不需要手动指定。当请求只作用在一个节点上面的是否,Query then fetch 的两步就合并为一步执行了。当我们有时候为了避免聚合(Aggregation)不准确而将 numbers_of_shards 设置为1个的时候,所有的请求就只在一个节点上处理。 Dfs Query and fetch 同 Dfs Query and fetch,也不需要手动执行,请求作用于单个节点时候的一种内部优化机制。]]></content>
<categories>
<category>elasticsearch</category>
</categories>
<tags>
<tag>elasticsearch</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用 redis-check-aof 命令修复 aof 文件]]></title>
<url>%2Fredis-check-aof%2F</url>
<content type="text"><![CDATA[偶然的一天,发现redis一个实例无法启动,启动的时候,从 aof 文件往内存中加载数据的时候,出现了错误: 1Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix filename 提示,需要使用 redis-check-aof 来修复 aof 文件。 按照其步骤,先备份再修复:123456[root@hostname 6379]# redis-check-aof --fix appendonly.aof0x 6a60a5cb: Expected prefix 'AOF analyzed: size=1784722401, ok_up_to=1784718795, diff=3606This will shrink the AOF from 1784722401 bytes, with 3606 bytes, to 1784718795 bytesContinue? [y/N]: ySuccessfully truncated AOF 修复完成后,重新启动 redis 就能正常启动了。 同样,redis 如果采用的 rdb 模式持久化数据的话,如果需要修复 rdb 文件,可以使用 redis-check-dump file.rdb 来修复。]]></content>
<categories>
<category>redis</category>
</categories>
<tags>
<tag>redis</tag>
<tag>aof</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Elasticsearch function 中 doc , _fields , _source 使用]]></title>
<url>%2Felasticsearch-function-doc%2F</url>
<content type="text"><![CDATA[面对复杂的查询或者算分逻辑,Elasticsearch 可以使用 Scrpits 脚本功能来实现,实际项目之中也用的非常频繁。在脚本中需要访问文档中的字段,此时有三种方式可以访问,分别是 doc , _fields , _source。这三个字段使用的时候还是有区别的。12345678910111213"filtered" : { "query" : { ... }, "filter" : { "script" : { "script" : "doc['num1'].value > param1" "params" : { "param1" : 5 } } }} 下面通过几个示例脚本来分别说明三者之间的区别。 范例文档示例 mapping 如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546{ "test": { "mappings": { "test": { "_all": { "enabled": false }, "properties": { "categories": { "type": "long" }, "name": { "type": "string" }, "name2": { "index": "not_analyzed", "type": "string" }, "price": { "type": "double", "store": true }, "skus": { "properties": { "id": { "type": "long" }, "name": { "type": "string" }, "price": { "type": "double" } } }, "status": { "type": "short" }, "updateTime": { "type": "long" } } } } }} 文档数据如下:12345678910111213141516171819202122232425262728293031{ "_id": "1", "_index": "test", "_source": { "categories": [ 1, 3, 4 ], "name": "宽松印花中袖香水瓶T恤", "name2": "宽松印花中袖香水瓶T恤", "price": 123.68, "skus": [ { "id": 1, "name": "红色", "price": 12.88 }, { "id": 2, "name": "蓝色", "price": 23.66 } ], "status": 1, "updateTime": 1503560261 }, "_type": "test", "_version": 2, "found": true} docdoc 可以很方便的使用文档中的字段,通过 doc[‘field_name’]来访问,访问速度比较快,因为相关的值已经加载到内存中了。但是它有一些使用限制: 访问的字段不能是 object 类型;只对 not-analyzed 字段有效 例如:对于上述的文档,doc[“skus”] 是无效的:1"script": "for(sku in doc['skus']){if(sku.price>5){return true;}};return false;" //No field found for [skus] in mapping! 而 doc[“categories”] 和 doc[“price”] 这是可以的:12"script": "for(category in doc['categories']){if(category>2){return true;}};return false;""script": "doc['price'])>100" doc[“name”] 无效是因为 name 为 analyzed 的字段:1"script": "doc['name'].value.length()>=10" //not found 虽然访问 doc[‘name’]无效,但是并不报错,其实对于这种分词的字段,访问 doc[‘name’] 其实访问的是分词过后的 token,所以不会报 no field found for [name] in mapping 错误。doc[‘name2’]有效,因为 name2 字段 是 not_analyzed 的(可以理解为不分词):1"script": "doc['name2'].value.length()>=10" //found _fields_fields 使用方式同 doc 一样的,不过需要注意的是 _fields 必须是 mapping 中 store 的字段,例如 price 字段:1"script": "_fields['price'].value<=param1", 同时性能会低于加到内存中的doc。默认情况下,创建 mapping 时候如果没有指定 store为 true 则 相应的字段是没有 stored 的。 _source如果创建 mapping 的时候没有禁用 source , 则在脚本中可以使用 _source 来访问文档内容。_source的内容实质上就是一段 json。所以可以通说 _source.obj2.obj1.field3 来访问。1"script": "for(sku in _source.skus){if(sku.price>22){return true;}};return false;", skus 字段是 object 类型的,可以用过 _source.skus 来访问。其实通过 _source[‘skus’]也是可以访问,这两者是否有区别暂不清楚。由于 _source是每个文档加载、解析再使用的,所以比已经加载到内存中的 doc 慢很多,不过和 _fields 来比则需要区分使用场景。 如果访问单个字段,_fields 比 _source 快 如果访问多个字段,_source 比 _fields 快]]></content>
<categories>
<category>elasticsearch</category>
</categories>
<tags>
<tag>elasticsearch</tag>
<tag>function</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux sort 命令使用]]></title>
<url>%2Flinux-sort%2F</url>
<content type="text"><![CDATA[Linux 可使用 sort 命令来对文件内容或者其他命令的输出内容进行排序。常见的场景是按大小排列日志文件、按文件名排序等。 sort 命令常用的参数有: r sort默认排序为升序,如果需要降序,则执行 -r即可。reverse n sort排序默认是当做字符来排序的,所以会遇到 10<2 的情况,使用 n 让其当做 number 来排序 u 去重,排重 t 指定分割符号,有时候需要将内容分割,按照其中部分内容排序 k 指定排序列,一般配合 t 使用,注意,列从 1 开始 o 将输出结果重定向写入文件,写入原文件时不能使用 > 。 example:12345678910111213141516171819202122[root@linux]$ls search2*|sort -rn -k 4 -t '.'search2.stdout.log.20search2.stdout.log.19search2.stdout.log.18search2.stdout.log.17search2.stdout.log.16search2.stdout.log.15search2.stdout.log.14search2.stdout.log.13search2.stdout.log.12search2.stdout.log.11search2.stdout.log.10search2.stdout.log.9search2.stdout.log.8search2.stdout.log.7search2.stdout.log.6search2.stdout.log.5search2.stdout.log.4search2.stdout.log.3search2.stdout.log.2search2.stdout.log.1search2.stdout.log]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>sort</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Chrome 非安全端口限制]]></title>
<url>%2Fchrome-unsafe-port-err%2F</url>
<content type="text"><![CDATA[在 beego 群里,有同学问了一个看似比较的简单的问题。问题是这样的: 一个beego web项目,将项目的端口号从默认的 8080 改为 6666 之后通过 Chrome 浏览器无法访问,但是通过其他浏览器却可以访问。 但是并未在意,认为这可能是chrome浏览器或者系统不小心配置了代理导致的,后来我自己在本机上也实验以下,将应用端口改为 6666 之后,通过 Chrome 浏览器也无法访问。报错信息如下:1234This site can’t be reachedThe webpage at http://localhost:6666/ might be temporarily down or it may have moved permanently to a new web address.ERR_UNSAFE_PORT 报错信息中提到了 ERR_UNSAFE_PORT。 解决方案:1/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --explicitly-allowed-ports=6666,8888 或者换端口,避开Chrome的非安全端口限制。参考资料: https://jazzy.id.au/2012/08/23/why_does_chrome_consider_some_ports_unsafe.html https://support.google.com/chrome/forum/AAAAP1KN0B0l5d-nXEjLMM?hl=en Chrome 非安全端口列表如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263641, // tcpmux7, // echo9, // discard11, // systat13, // daytime15, // netstat17, // qotd19, // chargen20, // ftp data21, // ftp access22, // ssh23, // telnet25, // smtp37, // time42, // name43, // nicname53, // domain77, // priv-rjs79, // finger87, // ttylink95, // supdup101, // hostriame102, // iso-tsap103, // gppitnp104, // acr-nema109, // pop2110, // pop3111, // sunrpc113, // auth115, // sftp117, // uucp-path119, // nntp123, // NTP135, // loc-srv /epmap139, // netbios143, // imap2179, // BGP389, // ldap465, // smtp+ssl512, // print / exec513, // login514, // shell515, // printer526, // tempo530, // courier531, // chat532, // netnews540, // uucp556, // remotefs563, // nntp+ssl587, // stmp?601, // ??636, // ldap+ssl993, // ldap+ssl995, // pop3+ssl2049, // nfs3659, // apple-sasl / PasswordServer4045, // lockd6000, // X116665, // Alternate IRC [Apple addition]6666, // Alternate IRC [Apple addition]6667, // Standard IRC [Apple addition]6668, // Alternate IRC [Apple addition]6669, // Alternate IRC [Apple addition]]]></content>
<categories>
<category>notes</category>
</categories>
<tags>
<tag>chrome</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用upx压缩可执行文件]]></title>
<url>%2Fcompress-executables-with-upx%2F</url>
<content type="text"><![CDATA[记得当初刚接触学习 golang 的时候,当时使用还是 go 1.4版本,和其他语言的入门一样,写了一个简单的 hello world 程序。1234567package mainimport "fmt"func main() { fmt.Println("Hello World!")} 程序非常简单,整个源文件大小才 4KB。然后执行 go build 编译成可执行文件,结果让我非常惊讶的是这段简单的代码生成的可执行文件竟然有4M之多。当然,只有升级到了1.8版本之后,情况稍微好了一些,但是生成的可执行程序依然有1.6M之多。123du -sh hello*1.6M hello4.0K hello.go 虽然现在服务器的硬盘不再是限制,价格也比较低廉,但是网络带宽依然是瓶颈,当可执行文件需要通过网络进行分发的时候,文件的大小的影响就比较明显了。 这个问题可以通过 upx(the Ultimate Packer for eXecutables) 来解决。 地址:https://github.com/upx/upx 压缩效果非常明显,以下 helllo_1 和 hello_2 分别是默认压缩和使用 –brute 参数压缩之后的二进制文件:12345678910upx -o hello_1 helloupx --brute hello_2 hellodu -sh hello*1.6M hello4.0K hello.go576K hello_1456K hello_2 可见,压缩比相当可观,大概压缩了 70% 的大小。 下面是一个正式项目的压缩效果:1234 14M gomessage_v24.2M gomessage_v2.compressed //upx -o gomessage_v2.compressed gomessage_v24.1M gomessage_v2.compressed.best //upx --best -o gomessage_v2.compressed.best gomessage_v23.1M gomessage_v2.compressed.brute //upx --brute -o gomessage_v2.compressed.brute gomessage_v2 可见压缩之后可执行文件还是小了很多,从 14M 降低到了 3~4M 左右。不同的参数代表着压缩程度,压缩的越多,压缩过程所需的时间也相对比较长。]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>go</tag>
<tag>upx</tag>
<tag>compress</tag>
</tags>
</entry>
<entry>
<title><![CDATA[读书清单(备忘)]]></title>
<url>%2Freading-list%2F</url>
<content type="text"><![CDATA[2017读书清单进行中&计划中 《Go并发编程实战》 –郝林 《App 后台开发运维和架构实践》–曾健生 《重构–改善既有代码的设计》 – 中文版 《高性能网站架构实战》 – 刘鑫 《构建高性能Web站点》 – 郭欣 《设计模式之禅》 – 秦小波 《go In Action》(Go语言实战)–中文版 《the way to go》(go语言程序设计)–中文版…. 已读 《Go By Example》– github 《build-web-application-with-golang》–github 《Go语言编程》 – 许式伟 《Go语言最佳实践》– pdf 《深入理解PHP内核》– php-internal.com ^_^ 任务艰巨~~~~]]></content>
<categories>
<category>notes</category>
</categories>
<tags>
<tag>book</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用 & 代替 % 来判断整数奇偶性]]></title>
<url>%2Fuse-bitwise-and-judge-even-odd%2F</url>
<content type="text"><![CDATA[一般情况下,判断整数的奇偶性都会使用取模预算, 性能方面没有测试,但是由于机器可以直接操作二进制,应该会比较快,不过这点性能提升一般情况下对整体性能的影响不大,可以忽略不计。12345if num%2==0 { fmt.Println("even")} else { fmt.Println("odd")} 不过,还有一种方式也是类似,使用 按位与 来判断整数奇偶性:12345if num&1==0 { fmt.Println("even")} else { fmt.Println("odd")} 也能达到相同的效果。 类似的,使用二进制运算也可以进行乘除法的运算:乘法左移:12345//a = a * 4a = a << 2//b = b * 8b = b << 3 除法右移:12345//a = a / 4a = a >> 2//b = b / 8b = b >> 3 这些都是一些二进制运算的技巧,记下备忘。]]></content>
<categories>
<category>notes</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title><![CDATA[阅读 php 源码,使用 vld 扩展查看 opcode]]></title>
<url>%2Fphp-vld%2F</url>
<content type="text"><![CDATA[php 虽然是一门动态语言,但是它也有一个编译的过程,这个过程经过词法分析、语法分析,最终生成 Zend 引擎可以执行的 opcode 指令,类似于 java 的字节码。 要查看一段代码对应的 opcode ,可以使用 vld 扩展 来查看。 安装安装 vld 和安装 php 其他的扩展没有任何区别,下载源码、编译、安装等…123456tar zxf vld-0.14.0.tar.gzcd vld-0.14.0phpize./configuremakemake install 记得要修改 php.ini 启用 vld 扩展:12[vld]extensions=vld.so 使用使用 vld 查看 opcode 也非常简单,主要传递参数-dvld.active=1来激活 vld 扩展即可:12345678910111213141516[root@host ~]# php -dvld.active=1 source.phpFinding entry pointsBranch analysis from position: 0Jump found. (Code = 62) Position 1 = -2filename: /root/source.phpfunction name: (null)number of ops: 3compiled vars: !0 = $aline #* E I O op fetch ext return operands------------------------------------------------------------------------------------- 2 0 E > EXT_STMT 1 ASSIGN !0, 10 3 2 > RETURN 1branch: # 0; line: 2- 3; sop: 0; eop: 2; out1: -2path #1: 0, 参数vld 扩展主要有以下几个参数: vld.active=1 启用 vld 扩展 vld.verbosity=3 显示更详细信息 vld.execute=0 只显示 opcode 而不执行 php 脚本 还有一些其他参数,请参考 深入理解 php 内核#VLD 扩展使用指南 备注如果经常需要查看 opcode , 而每次在命令中传递参数避免麻烦的话,可以将以上 vld 的参数配置在 php.ini 文件中,12345[vld]extensions=vld.sovld.active=1vld.vld.verbosity=3vld.execute=0 从而避免手动使用 php -d 参数来传递 vld 配置。1234php --help... -d foo[=bar] Define INI entry foo with value 'bar'...]]></content>
<categories>
<category>php</category>
</categories>
<tags>
<tag>opcode</tag>
<tag>php</tag>
</tags>
</entry>
<entry>
<title><![CDATA[阅读 php 源码,ctags 配合 vim 实现代码跳转]]></title>
<url>%2Fctags%2F</url>
<content type="text"><![CDATA[在阅读 php 源码的时候,为了方便跳转到相应的函数定义的位置,需要使用 ctags 来生成 tags 文件,再配合 vim 来实现跳转。 安装 ctagsmacOS 上使用 ctags 非常简单,一般都预装了 ctags 。 如果没有安装,可以使用 brew install ctags 来安装。 tags 文件在 php 源码根目录,执行 ctags -R, 就会在当前目录下生成一个名为 tags 的 文件。 12345678910~/codes/php-src(master*) » tail -n 20 tags zval_marked_grey Zend/zend_gc.h /^ uint32_t zval_marked_grey;$/;" m struct:_zend_gc_globalszval_object_property_dump ext/standard/var.c /^static void zval_object_property_dump(zval *zv, zend_ulong index, zend_string *key, int level) \/* {{{ *\/$/;" f file:zval_opt_copy_ctor Zend/zend_variables.h /^#define zval_opt_copy_ctor(/;" dzval_possible_root Zend/zend_gc.h /^ uint32_t zval_possible_root;$/;" m struct:_zend_gc_globalszval_ptr_dtor Zend/zend_execute.c /^#define zval_ptr_dtor(/;" d file:zval_ptr_dtor Zend/zend_execute.c /^#undef zval_ptr_dtor$/;" d file:zval_ptr_dtor Zend/zend_variables.h /^#define zval_ptr_dtor(/;" dzval_ptr_dtor_nogc Zend/zend_variables.h /^#define zval_ptr_dtor_nogc(/;" dzval_ptr_dtor_wrapper Zend/zend_variables.h /^#define zval_ptr_dtor_wrapper /;" d tags 文件的格式非常简单,以tab 分割各项,基本有以下几项内容: 第一列:tag name ,例如 zval_ptr_dtor 第二列:tag 所在的文件,例如 Zend/zend_variables.h 第三列:执行的命令,一般是指在 vi 中执行的正则搜索命令,例如 /^#define zval_ptr_dtor(/;" 第四列:tag 的类型,例如:d , 类型在不同的语言中有不同的含义。在 c 中, d 代表着 macro definitions (宏定义) 具体的各语言的 tag 的类型定义可以使用 ctags --list-kinds 来查看: 12345678910111213141516171819202122232425~/codes/php-src(master*) » ctags --list-kindsC c classes d macro definitions e enumerators (values inside an enumeration) f function definitions g enumeration names l local variables [off] m class, struct, and union members n namespaces p function prototypes [off] s structure names t typedefs u union names v variable definitions x external and forward variable declarations [off]Java c classes e enum constants f fields g enum types i interfaces l local variables [off] m methods p packages 查看 ctags 支持哪些语言可以使用 ctags --list-languages 来查看: 12345678910111213141516171819~/codes/php-src(master*) » ctags --list-languages....CC++FlexFortranHTMLJavaJavaScriptLispLuaMakeMatLabOCamlPascalPerlPHPPython... 大概有 41 种,不过 并没有看到 对 go 的支持,应该是因为 go 语言还是比较年轻的原因。 使用 tags一般情况下,在 tags 文件所在目录使用 vi (vim) 打开源码文件,在文件中使用快捷键 ctrl+] 即可实现跳转,ctrl+t 即可回到跳转之前的位置。 如果无法跳转,提示 E433: No tags file ,则需要手动指定 tags 文件,通过 vim 命令或者直接修改 vim 的配置文件 vimrc 均可: 12345#在~/.vimrc中添加:set tags+=~/codes/php-src/tags#或者在vim中运行命令::set tags+=~/codes/php-src/tags]]></content>
<categories>
<category>php</category>
</categories>
<tags>
<tag>ctags</tag>
</tags>
</entry>
<entry>
<title><![CDATA[golang 匿名 struct 的使用方式]]></title>
<url>%2Fgo-anonymous-struct%2F</url>
<content type="text"><![CDATA[编程中有时候需要一个临时的 struct 来封装数据,而这个 struct 的结构在其它地方又不会被二次复用,可以使用匿名 struct 来实现。 主要有两种方式,如下:第一种方式,通过 var 初始化 123var user struct{Name string;age int}user.Name = "name"user.age = 18 第二种方式,直接初始化 1json.Marshal(struct{Name string;age int}{"name",18}) 或者:1234json.Marshal(struct{ Name string age int}{"name",18})]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>anonymous</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Glide--golang 包管理工具简单教程]]></title>
<url>%2Fglide%2F</url>
<content type="text"><![CDATA[Golang挺好用的,但是目前比较受到诟病的是其没有自带包管理工具(golang社区已经发起了一个dep的项目,不过要正式被golang接受还需要一段时间)。官方没有咋办,那就自己造轮子呗。所以 go的包管理工具就五花八门、层出不穷了。目前有Glide, gopm, godep,gpm,gom等等。。。 这里介绍一个工具,使用比较简单: glide 我已经将其的文档翻译成了中文: glide 中文文档. 翻译的内容放在github上: https://github.com/javasgl/GlideDocs 不想看文章的可以直接去看中文文档,文档写的很清晰,文档中关于glide的用法和原理都有说明。 安装安装就没啥可以说的了,去下载安装即可了(http://glidedocs.readthedocs.io/zh/latest/#glide) 初始化在你已有的go项目的根目录下直接运行:glide init,就会生成一个glide.yaml配置文件,配置信息的详细说明见文档。运行glide init之后,glide自动分析当前项目里已有的依赖包的关系并自动生成了glide.yaml文件来管理这些依赖关系。这个过程需要一段时间来运行,因为它在扫描你的代码在推算依赖关系,请耐心等待。12345678~/codes/xxxxx/xxxx/: glide init[INFO] Generating a YAML configuration file and guessing the dependencies[INFO] Attempting to import from other package managers (use --skip-import to skip)[INFO] Scanning code to look for dependencies[INFO] --> Found reference to github.com/astaxie/beego[INFO] --> Found reference to github.com/stretchr/testify/assert[INFO] --> Found reference to gopkg.in/olivere/elastic.v3.... 运行完成之后,glide.yaml中的内容如下:1234567package: .import:- package: github.com/astaxie/beego- package: github.com/stretchr/testify subpackages: - assert- package: gopkg.in/olivere/elastic.v3 添加依赖运行glide get,类似于go get,不过不同的是,glide get会下载你指定包到vendor/目录中而不是之前的GOPATH之中。关于go的 vendor/目录请自行 google,这是go 1.5版本之后的功能。这个glide get下载包的同时,也会更新glide.yaml中的配置:12~/cods/xxxx/xxxx/:glide get https://github.com/garyburd/redigo.... 完成之后,glide.yaml也更新了:12345678package: .import:- package: github.com/astaxie/beego- package: github.com/stretchr/testify subpackages: - assert- package: gopkg.in/olivere/elastic.v3- package: github.com/grayburd/redigo 更新依赖运行glide up来更新glide.yaml中配置的依赖包。首次运行的时候,会生成一个glide.lock文件,对php的composer熟悉的一下就知道这个锁文件是干嘛的了。这个锁文件是保证再次运行glide up的时候不会去远程获取更新,而是直接使用这个锁文件中定义的依赖包及其版本。在协作开发时,一般这个锁文件可以进行版本控制,这样其他人拿到这个锁文件之后,执行glide up则会获取锁文件中指定的包版本,起到多人协作开发时统一开发环境的问题。123456~/cods/xxxx/xxxx/:glide up[INFO] Downloading dependencies. Please wait...[INFO] --> Fetching github.com/stretchr/testify.[INFO] --> Fetching gopkg.in/olivere/elastic.v3.[INFO] --> Fetching github.com/astaxie/beego..... 其他其他的一些命令主要是帮助管理这些依赖的。详细见 http://glidedocs.readthedocs.io/zh/latest/commands/ 写在最后欢迎大家 star ,提交 issue 或者 fork 提交 pull request 来帮助我改进翻译质量。翻译项目地址: https://github.com/javasgl/GlideDocs]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>glide</tag>
</tags>
</entry>
<entry>
<title><![CDATA[golang中slice越界问题]]></title>
<url>%2Fgo-slice-append%2F</url>
<content type="text"><![CDATA[go中slice由于其类似于一个长度可动态变化的数组而非常方便使用。slice有两个基本属性:cap,len,分别代表容量和已有数据长度。一般声明slice的两种方式如下:1234//定义了一个初始长度为0,容量为2的int 类型的 `slice`slice:=make([]int,0,2)var slice=[]int{0,1} 往slice中添加元素使用append方法:1slice=append(slice,1) 当然有时也可以使用索引值来直接赋值:1slice[0]=1 不过这种方式需要注意的是,索引的值不能大于slice的容量(cap),否则会报出panic: runtime error: index out of range错误。所以一般情况下应该优先使用append的方法给slice添加元素。 同样,对slice或者array进行切片(slice)操作的时候,索引也不能超过当前slice的容量或者array的长度123var slice = []int{0, 1, 2, 3, 4, 5, 6}fmt.Println(slice[:5]) //[0,1,2,3,4]fmt.Println(slice[1:10]) //slice bounds out of range slice的容量扩容规则: 当容量不够时,容量扩充为之前容量的2倍 在调用append方法给slice添加元素的时候,可以一次添加多个或者全部:123456var slice = []int{0,1}var slice2 = []int{2,3}fmt.Println(append(slice,slice2...)) //[0,1,2,3]fmt.Println(append(slice,4,5)) //[0,1,4,5]]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>slice</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mac上使用gdb单步调试golang程序]]></title>
<url>%2Fdebug-golang-with-gdb-on-mac%2F</url>
<content type="text"><![CDATA[单步调试最近在写golang程序的时候,想要想单步调试一下程序,减少手动print log调试代码的痛苦。 找了一些golang单步调试的方案,主要有几下几种方案: 使用 SublimeGDB 插件在Sublime 中调试 使用 Goland IDE 调试,不过可惜Goland还未发布正式版本 使用 gdb 工具调试 每个都尝试了一遍: SublimeGDB 界面不错,但是调试速度太慢,设置一个断点都需要很长时间,故 pass 此方案。 Goland IDE 只是预览版,有使用时间限制,还是等等吧,等 Jetbrains 出正式版后再去试试.(Jetbrains出品必属精品) gdb调试,使用下来发现,目前而言,gdb的方式还是用的比较顺手的。 不过在mac电脑上使用gdb还是遇到了一些问题,不过还好都顺利解决了。 遇到的主要问题有权限问题和 mac系统版本问题 权限问题mac 系统为了安全起见,不允许普通用户直接运行gdb,运行时会出现以下问题:12Unable to find Mach task port for process-id 4263: (os/kern) failure (0x5). (please check gdb is codesigned - see taskgated(8)) 这个问题,网络上已有解决方案,比如对gdb进行签名啥的,嫌麻烦,没有尝试。详细步骤见这里这里直接使用 sudo gdb ..来运行。可以自行设置命令alias。 1alias gdb='sudo gdb' 不过这样就得每次输入系统密码了。不过可以使用 expect 来让命令行自动输入密码(这是后话)。 版本问题开始调试的时候遇到了一下问题:1During startup program terminated with signal ?, Unknown signal. 查询了一下,Mac Sierra 这个版本的系统不支持 gdb 调试。解决方案五花八门。主要进行了一下操作:123brew upgrade gdb touch ~/.gdbinitcat 'set startup-with-shell off' > ~/.gdbinit 这些操作主要是: 将 gdb 从 7.1.2升级到最新的 8.0 给 gdb 写入配置文件 幸运的是,经过这两步的处理,gdb 已经能够在本人机器上正常运行了。 下面记录下一些 常用的gdb操作: command alias description l main.main list main.main 查看 golang main函数入口源码 b 12 breakpoints 12 在12行设置一个断点 d delete 删除所有断点 i b info breakpoints 查看已经设置的断点 r run 开始运行程序 n next 执行下一行,相当于step over s - 进入函数内部,相当于step into i locals info locals 查看局部变量 i args info args 查看参数 p paramName print paramName 打印变量 c - 继续执行,直到下一个断点 whatis paramName - 查看变量类型 up - 返回上一次函数 down - 进入下一层函数 h help help]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
<tag>gdb</tag>
<tag>debug</tag>
</tags>
</entry>
<entry>
<title><![CDATA[压力测试工具ab、webbench、http_load、siege简单使用]]></title>
<url>%2Fpressure-test-tools%2F</url>
<content type="text"><![CDATA[本文简单介绍ab、http_load、webbench、siege四种压力测试工具的使用。 abab 是 apache 服务器自带的一个压力测试工具。安装apache web服务器时就自动安装了。当然,ab也是可以单独安装的,如果你不想安装apache而只是想安装ab,那么可以使用以下方式安装:1[root@yourdream ~]# yum install httpd-tools 安装完成之后即可使用ab进行测试了。1234567[root@yourdream ~]# ab -hUsage: ab [options] [http[s]://]hostname[:port]/pathOptions are: -n requests Number of requests to perform -c concurrency Number of multiple requests to make .... -h Display usage information (this message) 压力测试的时候一般使用到 -n和-c参数来分别指定运行次数和并发数:123456789101112131415161718192021222324252627282930313233343536[root@yourdream ~]# ab -n 1000 -c 100 https://baidu.com/This is ApacheBench, Version 2.3 <$Revision: 655654 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking baidu.com (be patient)Completed 100 requests...Completed 1000 requestsFinished 1000 requests....Concurrency Level: 100Time taken for tests: 3.742 secondsComplete requests: 1000Failed requests: 0Write errors: 0...Requests per second: 267.23 [#/sec] (mean)Time per request: 374.213 [ms] (mean)Time per request: 3.742 [ms] (mean, across all concurrent requests)Transfer rate: 92.50 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 93 260 50.7 269 498Processing: 26 88 45.6 77 362Waiting: 25 62 31.8 50 356Total: 125 348 47.5 342 635Percentage of the requests served within a certain time (ms) 50% 342 .... 99% 534 100% 635 (longest request) 测试结果中,重点关注Requests per second和Time per request 指标,分别是每秒请求数和单个请求耗时。注意这些指标只能作为性能参考,因为会受到各种因素的影响,比如网络环境的影响… http_loadhttp_load非常轻量,程序解压后100kb左右的大小。不过不支持直接测试url,需要先将要测试的url写入文件,把文件作为参数传递给http_load。1234567[root@yourdream ~]# http_loadusage: http_load [-checksum] [-throttle] [-proxy host:port] [-verbose] [-timeout secs] [-sip sip_file] -parallel N | -rate N [-jitter] -fetches N | -seconds N url_fileOne start specifier, either -parallel or -rate, is required.One end specifier, either -fetches or -seconds, is required. 经常需要使用到的参数是-parallel和-fetches,分别表示并发数和总共请求数。12345678910[root@yourdream ~]# http_load -parallel 100 -fetches 1000 a.txt....1000 fetches, 100 max parallel, 55088 bytes, in 1.8685 seconds55.088 mean bytes/connection535.189 fetches/sec, 29482.5 bytes/secmsecs/connect: 31.5979 mean, 48.524 max, 26.395 minmsecs/first-response: 36.9429 mean, 1312.77 max, 5.107 min999 bad byte countsHTTP response codes: code 200 -- 514 测试结果中,重点关注fetches/sec和msecs/connect,分别表示每秒处理请求数和每个连接平均响应时间。 webbenchwebbench 也非常轻量级,和http_load大小差不多。12345678910111213141516[root@yourdream ~]# webbenchwebbench [option]... URL -f|--force Don't wait for reply from server. -r|--reload Send reload request - Pragma: no-cache. -t|--time <sec> Run benchmark for <sec> seconds. Default 30. -p|--proxy <server:port> Use proxy server for request. -c|--clients <n> Run <n> HTTP clients at once. Default one. -9|--http09 Use HTTP/0.9 style requests. -1|--http10 Use HTTP/1.0 protocol. -2|--http11 Use HTTP/1.1 protocol. --get Use GET request method. --head Use HEAD request method. --options Use OPTIONS request method. --trace Use TRACE request method. -?|-h|--help This information. -V|--version Display program version. webbench不支持指定总访问数,支持并发数(-c)和测试时长(-t),不直接支持https测试。123456789[root@yourdream ~]# webbench -c 100 -t 10 http://baidu.com/Webbench - Simple Web Benchmark 1.5Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.Benchmarking: GET http://baidu.com/100 clients, running 10 sec.Speed=14628 pages/min, 2448 bytes/sec.Requests: 86 susceed, 2352 failed. 每秒钟响应请求数:146285 pages/min,每秒钟传输数据量:2448 bytes/sec siege安装可以直接使用yum、apt安装,也自己去下载手动安装。123456789101112131415[root@yourdream ~]# siegeSIEGE 3.0.8Usage: siege [options] siege [options] URL siege -g URLOptions: ... -h, --help HELP, prints this section. ... -c, --concurrent=NUM CONCURRENT users, default is 10 -i, --internet INTERNET user simulation, hits URLs randomly. ... -t, --time=NUMm TIMED testing where "m" is modifier S, M, or H ex: --time=1H, one hour test. ... 经常使用的参数是-c和-t,分别是并发数和测试时长。12345678910111213141516171819202122[root@yourdream ~]# siege -c 100 -t 10S https://baidu.com** SIEGE 3.0.8** Preparing 100 concurrent users for battle.The server is now under siege...HTTP/1.1 200 0.03 secs: 28207 bytes ==> GET /HTTP/1.1 302 0.15 secs: 161 bytes ==> GET /....Lifting the server siege.. done.Transactions: 1512 hitsAvailability: 100.00 %Elapsed time: 9.63 secsData transferred: 20.38 MBResponse time: 0.12 secsTransaction rate: 157.01 trans/secThroughput: 2.12 MB/secConcurrency: 18.14Successful transactions: 1555Failed transactions: 0Longest transaction: 1.34Shortest transaction: 0.02... 测试结果中,重点关注Transaction rate和Concurrency,分别表示每秒处理数和实际并发数。siege可以将测试结果写入日志文件中,方便进行后续图形化工具或者分析工具使用。12 Date & Time, Trans, Elap Time, Data Trans, Resp Time, Trans Rate, Throughput, Concurrent, OKAY, Failed2017-06-05 11:38:02, 1512, 9.63, 20, 0.12, 157.01, 2.08, 18.14, 1555, 0]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>ab</tag>
<tag>webbench</tag>
<tag>http_load</tag>
<tag>siege</tag>
</tags>
</entry>
<entry>
<title><![CDATA[js 回调函数访问上下文 this 对象]]></title>
<url>%2Fjs-pass-this-into-callback-function%2F</url>
<content type="text"><![CDATA[在开发项目之中,使用到js ajax 同服务端进行数据交互。一般图方便直接使用jquery或者axios等已有的库。一般代码如下:12345678//axiosaxios.get('/url').then(function(res){ console.log(res)})//juqery$.get('/url',function(res){ console.log(res)},'json') 那么经常这么使用的肯定会遇到一个场景,就是:在回调函数中如何访问上下文中的this对象?123456function request(){ //this axios.get('/url').then(function(res){ //in callbacl ,how to access this })} 之前的做法是这样的:将this赋值给一个局部变量,然后在回调函数中访问这个变量12345678function request(){ //this var that = this; axios.get('/url').then(function(res){ //in callbacl ,how to access this console.log(that) })} 后来知道了更为优雅的两种方式: 使用 bind函数:123456789101112//axiosfunction request(){ axios.get('/url').then(function(res){ console.log(this); }.bind(this));}//jqueryfunction request(){ $.get('/url',function(res){ console.log(this); }.bind(this),'json');} 使用ES6的箭头函数:123456789101112//axiosfunction request(){ axios.get('/url').then((res)=>{ console.log(this); ));}//juqeryfunction request(){ $.get('/url',(res)=>{ console.log(this); },'json');} 参考: 深入浅出ES6(七):箭头函数 Arrow Functions]]></content>
<categories>
<category>javascript</category>
</categories>
<tags>
<tag>js</tag>
<tag>es6</tag>
</tags>
</entry>
<entry>
<title><![CDATA[mysql unsigned/int字段越界]]></title>
<url>%2Fmysql-bigint-out-of-range%2F</url>
<content type="text"><![CDATA[线上的MySQL错误日志中遇到了以下问题1Data truncation: BIGINT UNSIGNED value is out of range in `(database.tablename.fieldname)` MySQL版本为:1234567mysql> select version();+------------+| version() |+------------+| 5.6.21-log |+------------+1 row in set (0.00 sec) 引起这个错误的原因是sql语句中对unsigned字段进行了递减操作的结果为负数导致的,1update database.tablename set fieldname=fieldname-num 而MySQL的模式是默认的mode:1234567mysql> show variables like 'sql_mode';+---------------+------------------------+| Variable_name | Value |+---------------+------------------------+| sql_mode | NO_ENGINE_SUBSTITUTION |+---------------+------------------------+1 row in set (0.00 sec) 解决方案有3种: 1.程序中做数据合法性检查,先查询出来,递减之后判断结果,结果正常再去插入,不过这样需要2次sql操作,需要保证操作的原子性,不然容易引起数据一致性问题. 2.在同一个session中临时修改MySQL的sql_mode:12SET sql_mode='NO_UNSIGNED_SUBTRACTION';UPDATE database.tablename SET fieldname=fieldname-num NO_UNSIGNED_SUBTRACTION 模式下,对于unsigned字段,如果插入负值,则会讲该字段的值自动设置为 0 . 3.如果unsigned字段能接受负值,则可以使用CAST函数处理:12SET sql_mode='NO_UNSIGNED_SUBTRACTION';UPDATE database.tablename SET fieldname=CAST(fieldname-num AS UNSIGNED); 需要注意的是CAST的表现会因不同的sql_mode而不同,需要合适的sql_mode和CAST配合使用. 参考: mysql out-of-range-and-overflow mysql sql mode mysql no_unsigned_subtraction mode]]></content>
<categories>
<category>mysql</category>
</categories>
<tags>
<tag>mysql</tag>
<tag>database</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux强制卸载设备]]></title>
<url>%2Fumount-device-when-its-buzy%2F</url>
<content type="text"><![CDATA[Linux下因为磁盘出问题而导致了Input/output error,所以准备先把有问题的磁盘卸载掉再重新挂上试试。 执行umount:1234[root@hostname ~]# umount -n /dev/sdc1umount: /home/data: device is busy. (In some cases useful info about processes that use the device is found by lsof(8) or fuser(1)) 显示设备正忙,所以执行umount -nf:123456[root@hostname ~]# umount -nf /dev/sdc1umount2: Device or resource busyumount: /home/data: device is busy. (In some cases useful info about processes that use the device is found by lsof(8) or fuser(1))umount2: Device or resource busy 还是无法卸载。 根据给出的提示,使用losf或者fuser来找出那些进程正在使用该设备:123[root@yourdream ~]# fuser -cu /dev/sdc1/dev/sdc1: 2444c(root) 2458c(root) 3041c(mysql)[root@yourdream ~]# fuser -cu /dev/sdc1 使用c指定挂载的文件系统,u显示使用者的id。可以看出三个进程正在使用,通过ps可以查到分别为redis和mysql正在使用。因此,卸载之前停掉redis和mysql之后再次执行umount就能正常卸载了。]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>umount</tag>
<tag>fuser</tag>
<tag>losf</tag>
</tags>
</entry>
<entry>
<title><![CDATA[I-love-vim]]></title>
<url>%2Fi-love-vim%2F</url>
<content type="text"><![CDATA[个人是一个vi重度使用者,所以尽可能的会希望所使用的工具中都能支持vim来方便操作。 chrome浏览器支持 vim操作: Vimium 扩展 atom 编辑器: vim-mode、vim-mode-plus sublime text: 天然支持,配置中ignored_packages 置为空[]即可 idea 编辑器:vim 插件用来编辑文件内容,还有一个和Vimium相似的插件(名称遗忘,自己未使用),以和Vimium相同的方式操作idea的各种菜单 command line: 大部分天然支持vim编辑模式 (非 windows 系统),自行给vim安装了一些插件,打造成了一个命令行的golang的IDE 目录:scrooloose/nerdtree 目录git状态:Xuyuanp/nerdtree-git-plugin 自动完成:Shougo/neocomplete go相关格式等工具:fatih/vim-go 最后,附上一张命令行IDE效果图:]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>vim</tag>
</tags>
</entry>
<entry>
<title><![CDATA[dbm--python的一个简易K-V数据库]]></title>
<url>%2Fpython-dbm%2F</url>
<content type="text"><![CDATA[在一些小型简单的python程序中,不需要(不想)引入庞大的关系型数据库或者其他大型的非关系型数据库(NoSql)时,dbm模块是一个非常不错的选择。不过在使用的时候需要注意的是,它要求key,value均为字符串类型。可以对其进行一个简单的封装,便于使用。12345678910111213141516171819202122232425#!/usr/bin/env python# -*- coding: utf-8 -*-import dbmfrom models.Config.Config import Configclass DBM(object): def __init__(self): self._db = dbm.open(Config.parse_config('dbm', 'dbfile'), 'c') def set(self, key, value): self._db[key] = str(value) return self def del(self, key): if key in self._db.keys(): del self._db[key] def get(self, key): if key in self._db.keys(): return self._db[key] else: return None def __del__(self): self._db.close() Config 是自己封装的一个简易的配置文件解析的类,负责从ini文件中读取指定的配置 dbm 的 api 非常简单,set、del、keys,其数据结构和字典非常相似,可以在小型项目中来当做简易的K/V数据库来使用。]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>database</tag>
<tag>python</tag>
<tag>dbm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[hexo 命令备忘录]]></title>
<url>%2Fhexo-command-notes%2F</url>
<content type="text"><![CDATA[这里记录个人一些 hexo 常用命令备查: 创建新的文章: hexo new post-title 生成db和静态文件: hexo generate,缩写为 hexo g 删除cache和静态文件:hexo clean 本地开始预览server:hexo server,缩写为 hexo s,默认端口为 4000 部署到远程: hexo deply,缩写为 hexo d 有时候部署到远程后发现blog并没有更新,则可以先clean之后再 deploy:12hexo cleanhexo g -d]]></content>
<categories>
<category>notes</category>
</categories>
<tags>
<tag>hexo</tag>
<tag>blog</tag>
</tags>
</entry>
<entry>
<title><![CDATA[中文正则匹配]]></title>
<url>%2Fregex-match-chinese%2F</url>
<content type="text"><![CDATA[在某些场景下,需要使用正则对文本中的中文进行匹配。之前就在一个python的项目中需要对中文进行相应的匹配。实际需求: 要求匹配非中文内容。中文的unicode编码范围为 u4e00 到 u9fa5。123456789#!/usr/bin/env python# -*- coding: utf-8 -*-import regroup = re.search(ur'([^\u4e00-\u9fa5])','1中文en汉字')if group: print grou.groups() Tips: 1.正则非匹配: ^2.正则范围匹配: [a-b]3.正则捕获: ()4.python2 中 u 表示 unicode的字符串, python3 已经全部使用unicode来表示字符串5.python 中 r 表示 row string,使用原始字符串,不进行转义]]></content>
<categories>
<category>notes</category>
</categories>
<tags>
<tag>python</tag>
<tag>regex</tag>
</tags>
</entry>
<entry>
<title><![CDATA[shell脚本修改环境变量]]></title>
<url>%2Fshell-change-path%2F</url>
<content type="text"><![CDATA[有时候编写go程序的时候需要将当前目录加到系统GOPATH中,不然就得将代码放置在已有的GOPATH之下。所以一般习惯在项目根目录执行创建一个shell脚本来动态的修改GOPTAH。脚本很简单,代码如下:123#/bin/shCURRENT=`pwd`export GOPATH="${GOPATH}:${CURRENT}" 因为是shell脚本,所以就习惯性的执行了如下命令:12sh envecho $GOPATH 期望GOPATH中已经追加上了当前目录,然而事与愿违,并没有什么效果,GOPATH仍然是之前的内容。 原因:使用 sh 命令来执行shell脚本的时候,脚本真正是在sh创建的子shell中执行,所以当sh进程完成的时候并没有修改系统变量,所以通过执行 sh env 来修改系统变量是无效的。子shell和父shell彼此无法使用对方的变量,子shell对环境变量的修改也不会影响父shell。需要使用 source env来执行,source命令执行脚本的时候,是在source当前shell中执行的,并不会创建子shell。 参考: 用source 执行脚本和用sh 执行脚本有什么区别]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Shell</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Alfred 编写 workflow]]></title>
<url>%2Falfred-workflow%2F</url>
<content type="text"><![CDATA[Alfred 的一个亮点功能就是workflow。通过workflow用户可以很容易的扩展alfred的功能,而开源的优秀的workflow也有很多,不过如果找不到自己满意的workflow,可以自己动手编写workflow。自己动手,丰衣足食!之所以重复造轮子的原因的是因为这个datetie-format-converter 转换的时间是按照UTC-GMT时间来算的,所以对于中国来说,属于东八区,所以就写了一个php版本的自己使用。这里只是简单记录下自己开发的一个Unix时间戳转换的workflow的过程,详细教程请自行google或者参考 这篇教程. 一些基本概念workflow可以使用脚本语言来编写,包括但不限于bash、php、python、ruby等。workflow中的triggers、inputs、actions、ouputs等基本概念就不解释了,详细了解的可以自己一个个的试一试,反正总共加起来也不多。 Alfred XML格式数据Alfred中要求ouputs最后输出特定Schema的 XML内容。其实这一点我不太理解为什么,个人一直不喜欢XML格式的数据,重复臃肿、层级难于管理,就数据交换格式而言,个人比较倾向简单清晰易读的JSON,Alfred不支持返回JSON数据结果,不知道是不是由于MacOS的原因,没有深究。Alfred要求输出的内容以下:1234567<items> <item autocomplete = "autocompletex" uid = "123321" arg = "argsx" > <title >title</title> <subtitle >subtitle</subtitle> <icon >icon</icon> </item></items> 根节点为items 其中包括任意多个item节点,每一个item节点代表本次查询结果的一行。 每一个item节点包括若干parameter与childnode,其含义为: uid: 每一个item要有一个独立的uid,不可重复 valid:值为yes 或者 no, 若为no,该行结果不可被选择 autocomplete : 自动补全的值, 使用tab可以令alfred自动补全为 autocompelete属性的值. arg:作为下一个模块的参数传递 该行item的标题,也是主要显示的位置。 该行字标题位置,会被显示为灰色小字 该行图标的文件名,其大小为64X64 pixels 接受参数Alfred可以通过query字段来获取数据的内容。 逻辑处理&格式输出获取query的内容后,使用熟悉的脚本语言进行一些处理后组成转符合上面Schema的XML数据格式直接输出即可。 调试Alfred很人性化的为开发者提供了调试工具,在workflow开发界面的右上角Toggle debuggind mode即可在下方打开一个debug窗口输出一些调试信息,方便开发。 说明以下是开发的一个简单的时间戳转换workflow,代码没有做严格的输入检查,只是简单的将输入的内容通过php的date函数转换成人类可读的Y-m-d H:i:s格式.123456date_default_timezone_set('Asia/Shanghai');require_once ('workflows.php');$query = "{query}";$wf = new Workflows ();$wf->result ( time (), $query, date('Y-m-d H:i:s',$query), '', 'icon.png', 'yes' );echo $wf->toxml(); 其中worklows.php 中就只用到了 输出 XML 的方法,这个方法就是将resutl()中传递的参数组装成特定XML数据。 后续后来研究了下datetime-format-converter的源码,做了简单的修改,已经支持北京时间的转换,其实就是在作者的python代码中时间转换的时候加上了时区的设置,手动将时区设置为Asia/Shanghai即可。123456789101112131415def parse_query_value(query_str): """ Return value for the query string """ try: query_str = str(query_str).strip('"\' ') if query_str == 'now': d = utcnow().shift('Asia/Shanghai') else: # Parse datetime string or timestamp try: d = epoch(float(query_str)).shift("Asia/Shanghai") except ValueError: d = parse(str(query_str)).shift('Asia/Shanghai') except (TypeError, ValueError): d = None return d 将第6、10、12行中时间转换加上了shift('Asia/Shanghai')1d = epoch(float(query_str)) 改为1d = epoch(float(query_str)).shift("Asia/Shanghai") 即可。其实更好的做法是自动检测系统信息推断出用户所使用的时区并转换。 参考资料 http://myg0u.com/python/2015/05/23/tutorial-alfred-workflow.html http://www.deanishe.net/alfred-workflow/ http://www.deanishe.net/alfred-workflow/tutorial.html]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>Alfred</tag>
<tag>workflow</tag>
</tags>
</entry>
<entry>
<title><![CDATA[解决MacOS Sierra 升级后原有的ssh private key无法通过验证]]></title>
<url>%2FMacOS-Sierra-ssh-config%2F</url>
<content type="text"><![CDATA[周末正好有空,就准备把mac pro 升级到最新的sierra版本,毕竟已经落后于apple官方两个比较大的版本了。 升级过程很简单,也很顺利。直接通过appstore上在线更新即可,下载完成之后安装向导步骤完成即可。 直到….我发现公司测试环境502之后,想要登陆到机器上去处理一下的时候,我的ssh key竟然毫无征兆的被deny了!升级之前能登陆服务器,升级之后无法登陆。 问了其他同事,跳板机能正常登陆的,排除了机房出问题的可能性。那就说明这是本地的ssh agent升级后出问题了。 同时,我也试了下github的key是否也不能使用。结果正如我所料:123456ssh -v github.com..........debug1: Skipping ssh-dss key xxxx/xxxx/xxx/ - not in PubkeyAcceptedKeyTypes.....Permission denied (publickey). 更加证实了我的想法: 这是Mac升级后导致的后来 google 发现这个问题很正常,很多人都在升级后遇到了这个问题。同时也暂时采用了临时方案:1234Host "github.com" User "git" IdentityFile "/xxx/xxxx/xxx" PubkeyAcceptedKeyTypes=+ssh-dss 在.ssh/config 中添加了 PubkeyAcceptedKeyTypes=+ssh-dss 配置,让 ssh 接受 dsa 类型的key。 最终方案应该是采用更加安全的 RSA 算法来生成 key,长度为 2048的 RSA。1024长度的RSA已经有被暴力破解成功的记录了,所以建议采用 2048 长度1ssh-keygen -t rsa -b 2048 -C "new key" 当然,采用 ecdsa 更好,不过考虑到Openssl的兼容问题,在ecdsa未完全通用的情况下还是使用RSA比较好。]]></content>
<categories>
<category>os</category>
</categories>
<tags>
<tag>mac</tag>
<tag>ssh</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux zgrep使用笔记]]></title>
<url>%2FLinux-zgrep%2F</url>
<content type="text"><![CDATA[grep 可以用来搜索文件内容,而基于grep的 zgrep这可以用来在压缩包中搜索内容而不需要事先解压文件. 123456789101112131415161718192021222324252627282930313233343536[root@yourdream ~]# cat test.gopackage mainimport ( "fmt")func main(){ fmt.Println("Hello Golang!");}[root@yourdream ~]# tar zcf test.tar.gz test.go[root@yourdream ~]# file test.tar.gztest.tar.gz: gzip compressed data, from Unix, last modified: Fri Mar 17 17:23:09 2017[root@yourdream ~]# zgrep "fmt" test.tar.gzBinary file (standard input) matches[root@yourdream ~]# zgrep -a "fmt" test.tar.gz "fmt" fmt.Println("Hello Golang!");[root@yourdream ~]# tar -tvf test.tar.gz-rw-r--r-- root/root 81 2017-03-17 17:18 test.go[root@yourdream ~]# tar -rvf test.tar.gz test/test/test/a.php[root@yourdream ~]# tar -tvf test.tar.gz-rw-r--r-- root/root 81 2017-03-17 17:18 test.godrwxr-xr-x root/root 0 2017-03-17 17:50 test/-rw-r--r-- root/root 1127 2017-03-17 17:50 test/a.php[root@yourdream ~]# zgrep -aHn "Hello" test.tar.gztest.tar.gz:6: fmt.Println("Hello Golang!");test.tar.gz:16:echo 'Hello php'; 注意需要加上 -a 参数,让其二进制文件当做文本处理 tar -u -r 先压缩包中添加文件 tar -H 显示文件名 tar -n 显示行号]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>zgrep</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iptables使用笔记]]></title>
<url>%2FLinux-iptables%2F</url>
<content type="text"><![CDATA[Linux 下防火墙 iptables 命令使用笔记 查看目前iptables设置 iptables -vnL --line-numbers -v verbose 详细信息 -n 显示数字端口等 -L list ,默认会显示filter表的规则 --line-numbers 显示序号,删除、插入时有用 删除规则iptables -t table -D INPUT ruleNumber -t table name,默认filter表,filter|Nat|Mangle|Raw -D delete INPUT chain name,INPUT|OUTPUT|FORWARD|PREROUTING|POSTROUTING ruleNumber rule number,可以使用--line-numbers参数查看 查看状态/etc/init.d/iptables status 保存修改/etc/init.d/iptables save,规则保存在/etc/sysconfg/iptables文件中 开启|重启|关闭/etc/init.d/iptables start|restart|stop examplesiptables -I INPUT -s 1.1.1.1 -p tcp -dport 6666 -m comment --comment "some comment" -j ACCEPT 向INPUT链中I(插入)一条规则,没有指定ruleNumber则插入到最前面 -s source,来源ip或者hostname -p protocol 协议 tcp,udp,icmp(ping包),all 等 /etc/protocols中protocol的均可以 -dport 目标端口 destination port,注意--dport和-dport -m match extension,启用扩展, comment是注释扩展 -j jump ACCEPT ACCEPT|DROP|REJECT|LOG 详细可参考 iptables详解,文章很详细!]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>iptables</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Go Get安装一些第三方库-网络问题]]></title>
<url>%2Fgo-get-golang-x-packages%2F</url>
<content type="text"><![CDATA[go在go get 一些 package时候的会由于众所周知的原因而无法下载。比如在安装 bee的时候有可能会遇到无法下载 golang.org/x/sys/unix 的问题。1unrecognized import path "golang.org/x/sys/unix" 解决方案:手动从github下载相应的package 12git clone --depth=1 https://github.com/golang/xxx.gitgit clone --depth=1 https://github.com/golang/xxx.git 注:xxx 为对应的需要的库 下载完成后,软链或者复制 到 $GOPATH/src/golang.org/x/ 下即可。1234567golang.org/└── x ├── net ├── sys └── tools4 directories 更为简便的方法:12345mkdir -p $GOPATH/src/golang.org/x/cd !$git clone https://github.com/golang/net.gitgit clone https://github.com/golang/sys.gitgit clone https://github.com/golang/tools.git]]></content>
<categories>
<category>go</category>
</categories>
<tags>
<tag>go</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Atom 调整左侧目录字体大小]]></title>
<url>%2Fatom-set-font-size%2F</url>
<content type="text"><![CDATA[刚刚安装好 atom的时候, 左侧的 tree 目录字体特别小,可以通过一下方式来设置 打开Atom-Stylesheet 找到 .tree-view 1234// style the background color of the tree view.tree-view { // background-color: whitesmoke;} 给 .tree-view 添加 font-size 即可: 123.tree-view { font-size:16px;} 当然,添加其他属性也是可以的,可以自己定制样式,颜色,字体,字号等,CSS语法]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>atom</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Atom设置代理]]></title>
<url>%2Fatom_set_proxy%2F</url>
<content type="text"><![CDATA[由于众所周知的原因,Atom有时候安装插件的时候很慢甚至无法安装。可以通过设置代理来解决。前提是已经有一个可以使用的http代理。参考之前的文章:将socks5转为http代理 atom自带了一个工具 apm(atom package management) 这个工具可以用来设置一些系统配置 可以用apm config list 查看现有的系统配置信息 下面开始设置 http代理: 执行一下命令 123apm config set http-proxy http://host:portapm config set https-proxy http://host:portapm config set strict-ssl false 注意两点: https 配置的也是 http 而不是 https! set strict-ssl false 可以避免ssl 证书错误问题 其实直接修改 ~/.atom/.apmrc 文件也是可行的 具体说明可以查看 这里]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>atom</tag>
<tag>proxy</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Mac上使用Privoxy 将 socks5转换为 http 代理]]></title>
<url>%2Ftransfer_socks5_to_http_proxy%2F</url>
<content type="text"><![CDATA[shadowsocks 挺不错的,但是有些时候需要使用http代理来爬墙。这时候可以使用privoxy来将 socks5 代理转换为 http代理。 配置步骤如下: 首先,确保 shadowsocks 已经正常起来的,默认的本地socks5端口号为 1080,可以使用 netstat 和 lsof 命令查看端口情况。 安装privoxy, mac 使用 brew install privoxy 即可 安装完成后,修改privoxy配置文件 1vim /usr/local/etc/privoxy/config 修改内容如下: 12forward-socks5t / 127.0.0.1:1080 .listen-address 127.0.0.1:8118 listen-address 默认是监听本地8118端口,如果端口没有被占用,可以不用修改 启动privoxy1/usr/local/sbin/privoxy /usr/local/etc/privoxy/config 可以使用 ps aux|grep privoxy和 lsof -i:8118来检查是否成功启动 正常情况下,可以使用http代理了,代理地址http://127.0.0.1:8118 示例例如,可以给 git 设置 http 或 https 代理,设置方式如下:12git config --global http.proxy http://127.0.0.1:8118git config --global https.proxy http://127.0.0.1:8118 取消代理配置:12git config --global --unset http.proxygit config --global --unset https.proxy 和其他 git 的配置一样,不使用命令行而是直接修改相应的 .gitconfig 文件也是可以的。当然,如果不想修改 git 配置,而只是想临时使用一下,可以使用 -c 参数:1git -c https.proxy=http://127.0.0.1:8118 clone --depth=1 https://github.com/xxx/xxx]]></content>
<categories>
<category>tools</category>
</categories>
<tags>
<tag>proxy</tag>
<tag>http</tag>
<tag>socks5</tag>
<tag>privoxy</tag>
<tag>shadowsocks</tag>
</tags>
</entry>
</search>