-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
509 lines (302 loc) · 220 KB
/
atom.xml
File metadata and controls
509 lines (302 loc) · 220 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Welcome to my-blog</title>
<icon>https://www.gravatar.com/avatar/e3033211f497dbeb0d82dedb049dddbd</icon>
<subtitle>关于学习 关于成长</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://gpp-note.top/"/>
<updated>2020-04-25T13:11:34.693Z</updated>
<id>http://gpp-note.top/</id>
<author>
<name>Guopp</name>
<email>2474986040@qq.com</email>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>React</title>
<link href="http://gpp-note.top/articles/2020-04-25-React.html"/>
<id>http://gpp-note.top/articles/2020-04-25-React.html</id>
<published>2020-04-25T13:10:53.000Z</published>
<updated>2020-04-25T13:11:34.693Z</updated>
<content type="html"><![CDATA[<h3 id="React"><a href="#React" class="headerlink" title="React"></a>React</h3><h4 id="JSX简介"><a href="#JSX简介" class="headerlink" title="JSX简介"></a>JSX简介</h4><p><code>const element = <h1>Hello, world!</h1>;</code><br>这里的标签语法既不是字符串,也不是html;</p><p>是一个JavaScript的语法扩展。<br>建议在React中配合使用JSX,JSX可以很好的描述UI应该呈现出它应有的交互的本质形式。</p><p>JSX可以生成React元素。</p><h5 id="为什么使用JSX?"><a href="#为什么使用JSX?" class="headerlink" title="为什么使用JSX?"></a>为什么使用JSX?</h5><p>React认为渲染逻辑本质上与其他UI逻辑内在耦合。</p><p>React并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。</p><h5 id="在JSX中嵌入表达式"><a href="#在JSX中嵌入表达式" class="headerlink" title="在JSX中嵌入表达式"></a>在JSX中嵌入表达式</h5><p>在JSX语法中,可以在大括号内放置任何有效的JavaScript表达式。</p><h5 id="JSX也是一个表达式"><a href="#JSX也是一个表达式" class="headerlink" title="JSX也是一个表达式"></a>JSX也是一个表达式</h5><p>在编译之后,JSX表达式会被转为普通JavaScript函数调用,并对其取值后得到JavaScript对象。</p><h5 id="JSX特定属性"><a href="#JSX特定属性" class="headerlink" title="JSX特定属性"></a>JSX特定属性</h5><ul><li>可以使用引号,来将属性值指定为字符串字面量</li><li>可以使用大括号,来在属性值中插入一个JavaScript表达式<br> 在属性中嵌入JavaScript表达式时,不要在大括号外面加上引号。只能使用引号或大括号中的一个,对于同一属性不能同时使用这两种符号。</li></ul><h5 id="使用JSX指定子元素"><a href="#使用JSX指定子元素" class="headerlink" title="使用JSX指定子元素"></a>使用JSX指定子元素</h5><p>假如一个标签里面没有内容,可以使用/>来闭合标签。</p>]]></content>
<summary type="html">
<h3 id="React"><a href="#React" class="headerlink" title="React"></a>React</h3><h4 id="JSX简介"><a href="#JSX简介" class="headerlink" title="JSX
</summary>
<category term="React" scheme="http://gpp-note.top/categories/React/"/>
<category term="Front" scheme="http://gpp-note.top/tags/Front/"/>
</entry>
<entry>
<title>gitSubmodule</title>
<link href="http://gpp-note.top/articles/2020-04-25-gitSubmodule.html"/>
<id>http://gpp-note.top/articles/2020-04-25-gitSubmodule.html</id>
<published>2020-04-25T13:01:07.000Z</published>
<updated>2020-04-25T13:07:16.443Z</updated>
<content type="html"><![CDATA[<h4 id="1-使用场景"><a href="#1-使用场景" class="headerlink" title="1. 使用场景"></a>1. 使用场景</h4><p>基于公司的项目会越来越多,常常需要提取一个公共的类库提供给多个项目使用,但是这个library怎么和git在一起方便管理呢?<br>我们需要解决下面几个问题:</p><a id="more"></a><p>如何在git项目中导入library库?<br>library库在其他的项目中被修改了可以更新到远程的代码库中?<br>其他项目如何获取到library库最新的提交?<br>如何在clone的时候能够自动导入library库?<br>解决以上问题,可以考虑使用git的 Submodule来解决。</p><h4 id="2-什么是Submodule"><a href="#2-什么是Submodule" class="headerlink" title="2. 什么是Submodule?"></a>2. 什么是Submodule?</h4><figure class="highlight plain"><figcaption><span>Submodule``` 是一个很好的多项目使用共同类库的工具,他允许类库项目做为repository,子项目做为一个单独的git项目存在父项目中,子项目可以有自己的独立的commit,push,pull。而父项目以Submodule的形式包含子项目,父项目可以指定子项目header,父项目中会的提交信息包含Submodule的信息,再clone父项目的时候可以把Submodule初始化。</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">在项目中使用Submodule</span><br><span class="line"></span><br><span class="line">使用git命令可以直接添加Submodule:</span><br><span class="line"></span><br><span class="line">```git submodule add git@github.com:jjz/pod-library.git pod-library</span><br></pre></td></tr></table></figure><p>使用 git status命令可以看到</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line"> On branch master</span><br><span class="line"> Changes to be committed:</span><br><span class="line"> </span><br><span class="line"> new file: .gitmodules</span><br><span class="line"> new file: pod-library</span><br></pre></td></tr></table></figure><p>可以看到多了两个需要提交的文件:.gitmodules和 pod-library<br>.gitmodules 内容包含Submodule的主要信息,指定reposirory,指定路径:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[submodule "pod-library"]</span><br><span class="line"> path = pod-library</span><br><span class="line"> url = git@github.com:jjz/pod-library.git</span><br></pre></td></tr></table></figure><p>可以看到记录了子项目的目录和子项目的git地址信息。<br>pod-libray内容只保护子项目的commit id,就能指定到对于的git header上,例如:</p><figure class="highlight plain"><figcaption><span>commit 4ac42d2f8b9ba0c2f0f2f2ec87ddbd529275fea5```</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">4ac42d2f8b9ba0c2f0f2f2ec87ddbd529275fea5就是子项目的commit id,父项目的git并不会记录Submodule的文件变动,它是按照commit git指定Submodule的git header。</span><br><span class="line"></span><br><span class="line">另外,这两个文件都需要提交到父项目的git中。</span><br><span class="line"></span><br><span class="line">还可以这样使用命令添加Submodule</span><br></pre></td></tr></table></figure><p>git add .gitmodules pod-ibrary<br>git commit -m “pod-library submodule”<br>git submodule init<br>修改Submodule</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">首先需要确认有对Submodule的commit权限。</span><br><span class="line">进入Submodule目录里面:</span><br><span class="line">```cd pod-library/</span><br></pre></td></tr></table></figure><p>修改其中的一个文件看下文件的可以用git status查看变动:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git status</span><br><span class="line">modified: pod-library/UseAFHTTP.h</span><br></pre></td></tr></table></figure><p>提交Submodule的更改内容:</p><figure class="highlight plain"><figcaption><span>commit -a -m'test submodule'```</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">然后push 到远程服务器:</span><br><span class="line"></span><br><span class="line">```git push</span><br></pre></td></tr></table></figure><p>然后再回到父目录,提交Submodule在父项目中的变动:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cd ..</span><br><span class="line">git status</span><br><span class="line">on branch master</span><br></pre></td></tr></table></figure><p>modified: pod-library (new commits)<br>可以看到pod-library中已经变更为Submodule最新的commit id:</p><figure class="highlight plain"><figcaption><span>commit 330417cf3fc1d2c42092b20506b0d296d90d0b5f```</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">需要把Submodule的变动信息推送到父项目的远程服务器</span><br></pre></td></tr></table></figure><p>git commit -m’update submodule’<br>git push</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">这样就把子模块的变更信息以及子模块的变更信息提交到远程服务器了,从远程服务器上更新下来的内容就是最新提交的内容了。</span><br><span class="line"></span><br><span class="line">更新Submodule</span><br><span class="line"></span><br><span class="line">更新Submodule有两种方式:</span><br><span class="line">在父项目的目录下直接运行</span><br></pre></td></tr></table></figure><p>git submodule foreach git pull</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">在Submodule的目录下面更新</span><br><span class="line"></span><br><span class="line">>cd pod-library</span><br><span class="line">git pull</span><br><span class="line">可以看到在Submodule的目录中,使用git和单独的一个项目是一样的,注意更新Submodule的时候如果有新的commit id产生,需要在父项目产生一个新的提交,pod-libray文件中的 Subproject commit会变为最新的commit id。</span><br><span class="line"></span><br><span class="line">```clone Submodule</span><br></pre></td></tr></table></figure><p>clone Submodule有两种方式 一种是采用递归的方式clone整个项目,一种是clone父项目,再更新子项目。<br>采用递归参数 –recursive</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone git@github.com:jjz/pod-project.git --recursive</span><br></pre></td></tr></table></figure><p>输出结果:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">loning into 'pod-project'...</span><br><span class="line">remote: Counting objects: 57, done.</span><br><span class="line">remote: Compressing objects: 100% (45/45), done.</span><br><span class="line">remote: Total 57 (delta 13), reused 49 (delta 8), pack-reused 0</span><br><span class="line">Receiving objects: 100% (57/57), 18.79 KiB | 0 bytes/s, done.</span><br><span class="line">Resolving deltas: 100% (13/13), done.</span><br><span class="line">Checking connectivity... done.</span><br><span class="line">Submodule 'pod-library' (git@github.com:jjz/pod-library.git) registered for path 'pod-library'</span><br><span class="line">Cloning into 'pod-library'...</span><br><span class="line">remote: Counting objects: 34, done.</span><br><span class="line">remote: Compressing objects: 100% (25/25), done.</span><br><span class="line">remote: Total 34 (delta 8), reused 30 (delta 7), pack-reused 0</span><br><span class="line">Receiving objects: 100% (34/34), 12.95 KiB | 0 bytes/s, done.</span><br><span class="line">Resolving deltas: 100% (8/8), done.</span><br><span class="line">Checking connectivity... done.</span><br><span class="line">Submodule path 'pod-library': checked out '330417cf3fc1d2c</span><br><span class="line"></span><br><span class="line">42092b20506b0d296d90d0b5f'</span><br></pre></td></tr></table></figure><p>可以看到init Submodule 会自动被clone下来</p><p>2.第二种方法先clone父项目,再初始化Submodule</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git clone git@github.com:jjz/pod-project.git</span><br><span class="line">cd pod-project</span><br><span class="line">git submodule init</span><br></pre></td></tr></table></figure><p>输出:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Submodule 'pod-library' (git@github.com:jjz/pod-library.git) </span><br><span class="line">registered for path 'pod-library'</span><br></pre></td></tr></table></figure><p>更新Submodule:</p><figure class="highlight plain"><figcaption><span>submodule update```</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">运行结果:</span><br></pre></td></tr></table></figure><p> Cloning into ‘pod-library’…<br> remote: Counting objects: 34, done.<br> remote: Compressing objects: 100% (25/25), done.<br> remote: Total 34 (delta 8), reused 30 (delta 7), pack-reused 0<br> Receiving objects: 100% (34/34), 12.95 KiB | 0 bytes/s, done.<br> Resolving deltas: 100% (8/8), done.<br> Checking connectivity… done.<br> Submodule path ‘pod-library’: checked out ‘330417cf3fc1d2c42092b20506b0d296d90d0b5f’</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">删除Submodule</span><br><span class="line"></span><br><span class="line">git 并不支持直接删除Submodule需要手动删除对应的文件:</span><br></pre></td></tr></table></figure><p>cd pod-project<br>git rm –cached pod-library<br>rm -rf pod-library<br>rm .gitmodules<br>更改git的配置文件config:<br>vim .git/config</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">可以看到Submodule的配置信息:</span><br></pre></td></tr></table></figure><p>[submodule “pod-library”]<br> url = <a href="mailto:git@github.com">git@github.com</a>:jjz/pod-library.git`<br>``<br>删除submodule相关的内容,然后提交到远程服务器:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit -a -m 'remove pod-library submodule'</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h4 id="1-使用场景"><a href="#1-使用场景" class="headerlink" title="1. 使用场景"></a>1. 使用场景</h4><p>基于公司的项目会越来越多,常常需要提取一个公共的类库提供给多个项目使用,但是这个library怎么和git在一起方便管理呢?<br>我们需要解决下面几个问题:</p>
</summary>
<category term="Git" scheme="http://gpp-note.top/categories/Git/"/>
<category term="Git" scheme="http://gpp-note.top/tags/Git/"/>
</entry>
<entry>
<title>Rabbitmq</title>
<link href="http://gpp-note.top/articles/2020-03-10-rabbitmq.html"/>
<id>http://gpp-note.top/articles/2020-03-10-rabbitmq.html</id>
<published>2020-03-10T08:28:45.000Z</published>
<updated>2020-03-12T08:57:44.057Z</updated>
<content type="html"><![CDATA[<h4 id="1-RabbitMQ介绍"><a href="#1-RabbitMQ介绍" class="headerlink" title="1. RabbitMQ介绍"></a>1. RabbitMQ介绍</h4><p>MQ:<br> 消息队列;<br>RabbitMQ:<br> MQ的框架消息队列系统,实现消息的发送和接收,一种消息代理和队列的服务器;<br>AMQP(Advanced Message Queuing Protocol):<br> 高级消息队列协议。提供统一消息服务的应用层标准高级消息队列,是应用层协议的一个开放标准,为面向消息的中间件设计;<br>AMQP的主要特征是:<br> 面向消息、队列、路由、安全性、可靠。</p><a id="more"></a><h4 id="2-工作模式"><a href="#2-工作模式" class="headerlink" title="2. 工作模式"></a>2. 工作模式</h4><ul><li>消息:<br> 包括两个部分:有效载荷(你想要传输的数据)和标签(描述有效载荷,并决定了谁将获取消息);</li><li>生产者(消费者):<br> 发送消息到消息队列;</li><li>消费者:<br> 从消息队列接收消息;</li><li>队列:<br> 存储着即将被消费的消息, 在创建队列时,要指定队列名称;</li><li>交换机交换机和队列:<br> 持久化:将交换机和队列的数据保存在磁盘上,服务器重启或宕机之后仍然存在;<br> 非持久化:将交换机和队列的数据保存在内存上,服务器重启或宕机后不存在(性能高于持久化);</li></ul><p>根据需求来确定是否需要持久化,上图可以看出,针对每一种交换机有不同的type。<br>交换机的类型:</p><ul><li>默认交换机</li><li>直连交换机(direct)</li><li>主题交换机(topic)</li><li>头交换机(headers)</li><li>扇形交换机(fanout)</li></ul><p>消息确认:</p><ul><li>自动确认:消息发送给应用后立即删除;</li><li>显示确认:待应用发送一个确认回执后再删除消息;</li></ul><h4 id="3-队列模型"><a href="#3-队列模型" class="headerlink" title="3. 队列模型"></a>3. 队列模型</h4><ol><li>点对点消息队列模型:1对1模型</li></ol><p>特点:</p><ul><li>消息一旦被消息,消息队列就被删除;</li><li>生产者和消费者不存在时间上的依赖性;</li><li>消费者接收成功后,需要向队列应答成功;</li></ul><ol start="2"><li>发布订阅消息模型:n对n模型<br>特点:</li></ol><ul><li>每个消息可以有多个消费者,发布的消息可以被多个消费者消费;</li><li>发布者和订阅者有时间上的依赖关系;</li><li>针对每个主题的订阅者,必须创建一个订阅者之后,才能消费发布者的消息;</li><li>为了消费消息,订阅者必须保持订阅状态; </li></ul><h4 id="4-消息队列问题"><a href="#4-消息队列问题" class="headerlink" title="4. 消息队列问题"></a>4. 消息队列问题</h4><ol><li>消息不丢失问题<br>消费者挂掉消息丢失–消费者可以通过AMQP 的basic.ack 命令显式地向RabbitMQ 发送一个确认;<br>或者在订阅到队列的时候就将auto_ack 参数设置为true 。<br>当设置了auto_ack=true 时,一旦消费者接收消息,RabbitMQ 会自动视其确认了消息。<br>而当auto_ack=false时,需要消费者通过确认命令告诉RabbitMQ 它已经正确地接收了消息,同时RabbitMQ才能安全地把消息从队列中删除;</li></ol><p>Rabbitmq自己挂掉消息丢失–RabbitMQ 确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件。<br>当发布一条持久性消息到持久交换器上时, Rabbit 会在消息提交到日志文件后才发送响应;</p><ol start="2"><li>异步处理</li></ol><ul><li>引用应用场景:用户注册信息后,写入数据库,需要发送注册短信和注册邮件。</li><li>传统串行方式:300ms,1s可处理3次请求;</li><li>传统并行方式:200ms,1s可处理5次请求;</li><li>消息队列:105ms,1s可处理9次请求;</li></ul><ol start="3"><li>应用解耦<br>在项目启动之初是很难预测未来会遇到什么困难的,消息中间件在处理过程中插入了一个隐含的,基于数据的接口层,两边都实现这个接口,这样就允许独立的修改或者扩展两边的处理过程,只要两边遵守相同的接口约束即可。</li></ol><ul><li>传统模式:两个系统耦合,相互依赖,一个系统出现问题,则都会失败;</li><li>消息队列:发货系统发货后,发货系统完成持久化处理,将消息写入消息队列,返回发货成功,订单系统订阅发货的消息,获取发货信息,订单系统根据信息,进行更新操作。发货系统在发货的时候不用关心后续操作了,如果订单系统不能正常使用。也不影响正常发货,实现订单系统与发货系统的应用解耦;</li></ul><ol start="4"><li><p>流量削峰<br>对于一些类似淘宝双11这种秒杀活动,访问量剧增,瞬时间流量过大,服务器可能承受不了那么大的流量压力,导致服务宕机,使用消息中间件采用队列的形式,将压力转移到消息队列上,可以减少突发访问压力,不会因为突发的超时负荷要求而崩溃,起到缓冲作用。</p></li><li><p>消息队列积压</p></li></ol><ul><li>ready准备状态,如果消息数比较多,认为是消费者的处理能力不足,通过增加消费者来可以解决;</li><li>unacked未确认状态是因为,消费者取走消息后没有及时做消息确认,没有收到ack的应答,对于开启手动确认机制的,不进行ack则消息会一直以unacked状态留在队列中;<br>或者是消费者处理能力不足。生产者投放消息的速度较快,当消费者按照prefetch_count设置的值取走相应数量的消息时,这些消息都会暂时处于unacked状态;</li></ul><p>当积压很多的时候会导致程序无法消费数据,这时候监控系统就会提示我们了,然后运维或开发自己发现后,就要迅速解决积压问题(有一次提交代码的时候有一个值没有处理好,导致监控飙升)</p><p>解决办法:</p><ul><li>增加消费者的处理能;</li><li>考虑使用队列最大长度限制;</li><li>给消息设置年龄,超时就丢弃;</li><li>默认情况下,rabbitmq消费者为单线程串行消费,设置并发消费两个关键属性concurrentConsumers和prefetchCountoncurrentConsumers设置的是对每个listener在初始化的时候设置的并发消费者的个数,prefetchCount是每次一次性从broker里面取的待消费的消息的个数;</li><li>建立新的queue,消费者同时订阅新旧queue,采用订阅模式;</li><li>生产者端缓存数据,在mq被消费完后再发送到mq,打破发送循环条件,设置合适的qos值,当qos值被用光,而新的ack没有被mq接收时,就可以跳出发送循环,去接收新的消息;消费者主动block接收进程,消费者感受到接收消息过快时主动block,利用block和unblock方法调节接收速率,当接收线程被block时,跳出发送循环。</li><li>新建一个topic,partition是原来的10倍;然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue;接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据;等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息(目前采用过这种方法来应对紧急消息堆积情况);</li></ul><ol start="6"><li>消息的事务支持<br>即是原子性的,一次处理的多个消息应该在一个事务支持范围内,一个消息失败则即回滚到消息队列中去;</li><li>消息的持久化<br>启用消息持久化后,消息队列宕机重启后,消息可以从持久化存储恢复,消息不丢失,可以继续消费处理;</li><li>消息队列的高可用性<br>遇到宕机、重启等情况,消息队列可能无法服务,所以需要消息队列的高可用的支持,可以采用Rabbitmq的镜像集群模式;</li></ol>]]></content>
<summary type="html">
<h4 id="1-RabbitMQ介绍"><a href="#1-RabbitMQ介绍" class="headerlink" title="1. RabbitMQ介绍"></a>1. RabbitMQ介绍</h4><p>MQ:<br> 消息队列;<br>RabbitMQ:<br> MQ的框架消息队列系统,实现消息的发送和接收,一种消息代理和队列的服务器;<br>AMQP(Advanced Message Queuing Protocol):<br> 高级消息队列协议。提供统一消息服务的应用层标准高级消息队列,是应用层协议的一个开放标准,为面向消息的中间件设计;<br>AMQP的主要特征是:<br> 面向消息、队列、路由、安全性、可靠。</p>
</summary>
<category term="Rabbitmq" scheme="http://gpp-note.top/categories/Rabbitmq/"/>
<category term="Rabbitmq" scheme="http://gpp-note.top/tags/Rabbitmq/"/>
</entry>
<entry>
<title>Docker</title>
<link href="http://gpp-note.top/articles/2020-03-10-docker.html"/>
<id>http://gpp-note.top/articles/2020-03-10-docker.html</id>
<published>2020-03-10T08:27:35.000Z</published>
<updated>2020-03-12T08:57:54.128Z</updated>
<content type="html"><![CDATA[<p>最近空闲的时候在看一本书《Docker技术入门与实战》讲的关于Docker的使用,还蛮详细的,现在针对每个章节简单记录一下,好记性不如烂笔头。</p><h4 id="1-初始容器与Docker"><a href="#1-初始容器与Docker" class="headerlink" title="1. 初始容器与Docker"></a>1. 初始容器与Docker</h4><h5 id="Docker是什么"><a href="#Docker是什么" class="headerlink" title="Docker是什么"></a>Docker是什么</h5><p>Docker是基于go语言实现的开源容器项目,一个开源的引擎。</p><p>Docker的目标是:<code>Build,Ship and Run Any App,Anywhere</code>,简单说就是“通过对应用的封装Packaging、分发Distribution、部署Deployment、运行Runtime生命周期进行管理,达到应用组件—<code>一次封装、到处运行的目的</code>”。</p><p>Docker提供高效、敏捷、可移植的、自给自足和轻量级的容器方案,并支持部署到本地环境和多种主流平台。为应用的开发、运行和部署提供了‘一站式’的实用解决方案。<br>Docker提供了各种容器管理工具,用户无需关注底层的操作,可以更简单的管理和使用容器,通过引用分层文件系统构建和高效的镜像机制,降低了迁移难度,极大的提高了用户体验,用户操作Docker容器就像操作应用自身一样简单。<br>Docker容器理解为一种轻量级的沙盒,每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。</p><a id="more"></a><h5 id="Docker在开发和运维中的优势"><a href="#Docker在开发和运维中的优势" class="headerlink" title="Docker在开发和运维中的优势"></a>Docker在开发和运维中的优势</h5><ul><li>更快速的交付和部署<br>使用Docker快速构建一套开发环境,快速创建和删除容器,实现快速迭代,大量节约开发、测试和部署的时间。</li><li>更高效的资源利用<br>Docker容器的运行不需要额外的虚拟化管理程序支持,属于内核级的虚拟化,可以实现更高的性能,同时对资源的额外需求很低。</li><li>更轻松的迁移和扩展<br>可以在任意容器运行,包括物理机、虚拟机、公有云、私有云、个人电脑或服务器等,支持主流的操作系统发行版本,这种兼容性让用户可以在不同平台之间轻松的迁移应用。</li><li>更简单的更新管理<br>Dockerfile文件,只需要修改参数就可以实现大量的更新工作,并且修改都以增量的方式被分发和更新,从而实现自动化高效的容器管理。</li></ul><h5 id="Docker与虚拟机比较"><a href="#Docker与虚拟机比较" class="headerlink" title="Docker与虚拟机比较"></a>Docker与虚拟机比较</h5><h4 id="3-核心概念与安装配置"><a href="#3-核心概念与安装配置" class="headerlink" title="3. 核心概念与安装配置"></a>3. 核心概念与安装配置</h4><h5 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h5><p>Docker三大核心:镜像Image、容器Container、仓库Repository</p><ul><li><p>镜像Image<br>类似于虚拟机镜像,一个只读的模板,创建Docker容器的基础。</p></li><li><p>容器Container<br>类似于一个轻量级的沙箱,Docker利用容器运行和隔离应用。<br>容器是从镜像创建的应用运行示例;<br>可以将其启动、开始、停止、删除;<br>容器是彼此相互隔离、互不可见的;<br>可看做一个简易版的Linux环境以及应用在其中的应用程序打包而成的盒子;<br>镜像自身是只读的,容器从镜像启动的时候,会在镜像的最上层创建一个可写层;</p></li><li><p>仓库Repository<br>类似于代码仓库,集中存放镜像文件的场所;<br>分为公开仓库和私有仓库;</p></li></ul><h5 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h5><p>balabalabala……,根据不同的环境进行安装,网上也有很多介绍。</p><h4 id="4-使用Docker镜像"><a href="#4-使用Docker镜像" class="headerlink" title="4. 使用Docker镜像"></a>4. 使用Docker镜像</h4><h5 id="获取镜像"><a href="#获取镜像" class="headerlink" title="获取镜像"></a>获取镜像</h5><p>镜像是运行容器的前提,描述一个镜像需要包括“名称+标签”信息。</p><p>可以直接从Docker Hu镜像源直接来下载镜像:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">docker pull NAME[:TAG] //name是镜像仓库的名称,tag是镜像的标签</span><br><span class="line">// 如果不显示指定tag,则会默认latest标签。</span><br><span class="line">-a , --all-tags=true | false :是否获取仓库中所有镜像,默认为否。</span><br></pre></td></tr></table></figure><h5 id="查看镜像信息"><a href="#查看镜像信息" class="headerlink" title="查看镜像信息"></a>查看镜像信息</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker images 列出本地主机上已有镜像的基本信息</span><br><span class="line">来自哪个仓库、标签信息、镜像ID(唯一标识镜像)、创建时间、镜像大小(体积越小越优秀)</span><br></pre></td></tr></table></figure><h5 id="使用inspect命令查看详细信息"><a href="#使用inspect命令查看详细信息" class="headerlink" title="使用inspect命令查看详细信息"></a>使用inspect命令查看详细信息</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker inspect NAME[:TAG]</span><br></pre></td></tr></table></figure><h5 id="使用history命令查看镜像历史"><a href="#使用history命令查看镜像历史" class="headerlink" title="使用history命令查看镜像历史"></a>使用history命令查看镜像历史</h5><h5 id="搜寻镜像"><a href="#搜寻镜像" class="headerlink" title="搜寻镜像"></a>搜寻镜像</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker search 可以搜寻远端仓库中共享的镜像,默认官方仓库中的。</span><br></pre></td></tr></table></figure><h5 id="删除镜像"><a href="#删除镜像" class="headerlink" title="删除镜像"></a>删除镜像</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker rmi IMAGE [IMAGE...]</span><br></pre></td></tr></table></figure><p>使用标签删除镜像:删除镜像多个标签中的指定标签,不影响镜像文件,当还剩下一个标签的时候,删除则会导致彻底删除镜像;<br>使用镜像ID删除镜像:会先尝试删除指向该镜像的标签,然后删除该镜像文件本身,但当该镜像创建的容器存在时,镜像文件默认是无法被删除的,虽然可以-f强制删除,但正确做法是先删除依赖该镜像的所有容器,再来删除镜像。</p><h5 id="创建镜像"><a href="#创建镜像" class="headerlink" title="创建镜像"></a>创建镜像</h5><p>方法三种:基于已有镜像的容器创建、基于本地模板导入、基于Dockerfile创建。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">//基于已有镜像的容器创建</span><br><span class="line">docker commit [options] container [repository[:tag]]</span><br><span class="line"></span><br><span class="line">// 就有本地模板导入</span><br><span class="line">docker import [options] file | url | - [repository[:tag]]</span><br></pre></td></tr></table></figure><h5 id="存出和载入镜像"><a href="#存出和载入镜像" class="headerlink" title="存出和载入镜像"></a>存出和载入镜像</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// 存出镜像 导出镜像到本地文件</span><br><span class="line">docker save</span><br><span class="line"></span><br><span class="line">// 载入镜像 将导出的tar文件再导入到本地镜像库</span><br><span class="line">docker load</span><br></pre></td></tr></table></figure><h5 id="上传镜像"><a href="#上传镜像" class="headerlink" title="上传镜像"></a>上传镜像</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 上传镜像到仓库</span><br><span class="line">docker push Name[:tag]</span><br></pre></td></tr></table></figure><h4 id="5-操作Docker容器"><a href="#5-操作Docker容器" class="headerlink" title="5. 操作Docker容器"></a>5. 操作Docker容器</h4><h5 id="创建容器"><a href="#创建容器" class="headerlink" title="创建容器"></a>创建容器</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">docker create //创建的容器处于停止状态</span><br><span class="line"></span><br><span class="line">//启动容器</span><br><span class="line">docker start af</span><br><span class="line"></span><br><span class="line">//先终止再启动</span><br><span class="line">docker restart</span><br><span class="line"></span><br><span class="line">// 新建并启动容器 如果不存在的镜像,会先进行下载,</span><br><span class="line">// 利用镜像创建一个容器启动</span><br><span class="line">docker run === docker create + docker start</span><br><span class="line"></span><br><span class="line">//获取容器的输出信息</span><br><span class="line">docker logs</span><br><span class="line"></span><br><span class="line">//终止容器</span><br><span class="line">docker stop</span><br><span class="line"></span><br><span class="line">//强制终止容器</span><br><span class="line">docker kill</span><br><span class="line"></span><br><span class="line">//查看所有容器的ID</span><br><span class="line">docker ps -qa</span><br><span class="line"></span><br><span class="line">04-2 进入容器</span><br><span class="line">docker exec</span><br><span class="line">04-3 删除容器</span><br><span class="line">docker rm //删除处于终止或退出状态的容器 正在运行的容器加-f</span><br><span class="line">04-4 导入和导出容器</span><br><span class="line">// 导出容器</span><br><span class="line">docker export</span><br><span class="line"></span><br><span class="line">// 导入容器</span><br><span class="line">docker import</span><br></pre></td></tr></table></figure><h4 id="6-访问Docker仓库"><a href="#6-访问Docker仓库" class="headerlink" title="6. 访问Docker仓库"></a>6. 访问Docker仓库</h4><p>仓库和注册服务器区别:</p><ul><li>注册服务器是存放仓库的具体服务器;</li><li>一个注册服务上可以有多个仓库;</li><li>每个仓库下可以有多个镜像;</li><li>仓库看做一个具体的项目或目录;</li></ul><p>此处省略若干字,哈哈哈,主要是搭建仓库的,可以找资源搭建一下。</p><h5 id="Docker数据管理"><a href="#Docker数据管理" class="headerlink" title="Docker数据管理"></a>Docker数据管理</h5><p>容器中管理数据方式:</p><ul><li>数据卷Data Volumes:容器内数据直接映射到本地主机环境;</li><li>数据卷容器Data Volumes Containers:使用特定容器维护数据卷;</li></ul><h5 id="数据卷"><a href="#数据卷" class="headerlink" title="数据卷"></a>数据卷</h5><p>数据卷是一个可供容器使用的特殊目录,将主机操作系统目录直接映射进容器,特性:</p><ul><li>数据卷可以在容器之间共享和重用,容器间传递数据将变得高效方便;</li><li>对数据卷内的数据的修改会立马生效,无论是容器内操作还是本地操作;</li><li>对数据卷的更新不会影响镜像,解耦了应用和数据;</li><li>卷会一直存在,直到没有容器使用,可以安全卸载;</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个数据卷</span><br><span class="line">// -v标记可以在容器内创建一个数据卷</span><br><span class="line">docker run -d -P --name web -v /webapp training/webapp </span><br><span class="line">python app.py</span><br><span class="line"></span><br><span class="line">// training/webapp镜像 web容器 数据卷挂在到/webapp目录上</span><br><span class="line">// 挂在一个本地的已有目录到容器中作为数据卷</span><br><span class="line">docker run -d -P --name web -v /src/webapp:/opt/webapp </span><br><span class="line">training/webapp python app.py</span><br></pre></td></tr></table></figure><h5 id="数据卷容器"><a href="#数据卷容器" class="headerlink" title="数据卷容器"></a>数据卷容器</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">多个容器之间共享一些持续更新的数据,就要使用数据卷容器</span><br><span class="line">// 创建一个数据卷容器dbdata,并在其中创建一个数据卷</span><br><span class="line">// 挂在/dbdatadocker run it -v /dbdata --name dbdata ubuntu</span><br><span class="line">// 使用--volumes-from来挂在dbdata容器中的书卷</span><br><span class="line">docker run -it --volumes-from --name db1 ubuntu</span><br></pre></td></tr></table></figure><h5 id="利用数据卷容器来迁移数据"><a href="#利用数据卷容器来迁移数据" class="headerlink" title="利用数据卷容器来迁移数据"></a>利用数据卷容器来迁移数据</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// 备份 </span><br><span class="line">// 利用ubuntu镜像创建容器worker,并挂在到dbdata容器的容器卷</span><br><span class="line">// -v $(pwd):/backup参数挂在本地的当前目录到worker容器的/backup目录</span><br><span class="line">// worker启动后,tar cvf /backup/backup.tar /dbdata命令将/dbdata</span><br><span class="line">// 下内容备份为容器内的/backup/backup.tar</span><br><span class="line">docker run --volumes-from dbdata -v $(pwd):/backup </span><br><span class="line">--name worker ubuntu tar cvf /backup/backup.tar /dbdata</span><br><span class="line"></span><br><span class="line">// 恢复</span><br><span class="line">docker run -v /dbdata --name dbdata2 ubuntu /bin/bash</span><br></pre></td></tr></table></figure><h4 id="7-端口映射与容器互联"><a href="#7-端口映射与容器互联" class="headerlink" title="7. 端口映射与容器互联"></a>7. 端口映射与容器互联</h4><p>Docker提供了两个很方便的功能来满足服务访问的基本需求:</p><ul><li>允许映射容器内应用的服务端口到本地宿主主机;</li><li>互联机制实现多个容器间通过容器名来快速访问;</li></ul><h5 id="端口映射实现访问容器"><a href="#端口映射实现访问容器" class="headerlink" title="端口映射实现访问容器"></a>端口映射实现访问容器</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">// 查看映射端口号配置</span><br><span class="line">docker port</span><br><span class="line"></span><br><span class="line">// 映射到所有接口地址</span><br><span class="line">docker run -d -p 5000:5000 training/webapp python app.py</span><br><span class="line"></span><br><span class="line">// 映射到指定地址的指定端口号</span><br><span class="line">docker run -d -p 127.0.0.1:5000:5000 training/webapp</span><br><span class="line"> python app.py</span><br><span class="line"> </span><br><span class="line"> // 映射到指定地址的任意端口号</span><br><span class="line"> docker run -d -p 127.0.0.1::5000 training/webapp python </span><br><span class="line"> app.py</span><br></pre></td></tr></table></figure><h5 id="互联机制实现便捷互访"><a href="#互联机制实现便捷互访" class="headerlink" title="互联机制实现便捷互访"></a>互联机制实现便捷互访</h5><p>容器的互联是一种让多个容器中应用进行快速交互的方式。会在源和接收容器之间创建连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// db容器和web容器建立互联关系,</span><br><span class="line">// --link name:alias</span><br><span class="line">docker run -d -P --name web --link db:db </span><br><span class="line">training/webapppython app.py</span><br></pre></td></tr></table></figure><h4 id="8-使用Dockerfile创建镜像"><a href="#8-使用Dockerfile创建镜像" class="headerlink" title="8. 使用Dockerfile创建镜像"></a>8. 使用Dockerfile创建镜像</h4><p>Dockerfile是一个文本格式的配置文件,可以用它快速创建自定义的镜像。</p><h5 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h5><p>分为四个部分:<br> 基础镜像信息、维护者信息、镜像操作指令、容器启动时执行指令</p><h5 id="创建镜像-1"><a href="#创建镜像-1" class="headerlink" title="创建镜像"></a>创建镜像</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker build</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 指定Dockerfile所在路径/tmp/docker_builder </span><br><span class="line">// 生成镜像标签build_repo/first_image</span><br><span class="line">docker build -t build_repo/first_image /tmp/docker_builder</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>最近空闲的时候在看一本书《Docker技术入门与实战》讲的关于Docker的使用,还蛮详细的,现在针对每个章节简单记录一下,好记性不如烂笔头。</p>
<h4 id="1-初始容器与Docker"><a href="#1-初始容器与Docker" class="headerlink" title="1. 初始容器与Docker"></a>1. 初始容器与Docker</h4><h5 id="Docker是什么"><a href="#Docker是什么" class="headerlink" title="Docker是什么"></a>Docker是什么</h5><p>Docker是基于go语言实现的开源容器项目,一个开源的引擎。</p>
<p>Docker的目标是:<code>Build,Ship and Run Any App,Anywhere</code>,简单说就是“通过对应用的封装Packaging、分发Distribution、部署Deployment、运行Runtime生命周期进行管理,达到应用组件—<code>一次封装、到处运行的目的</code>”。</p>
<p>Docker提供高效、敏捷、可移植的、自给自足和轻量级的容器方案,并支持部署到本地环境和多种主流平台。为应用的开发、运行和部署提供了‘一站式’的实用解决方案。<br>Docker提供了各种容器管理工具,用户无需关注底层的操作,可以更简单的管理和使用容器,通过引用分层文件系统构建和高效的镜像机制,降低了迁移难度,极大的提高了用户体验,用户操作Docker容器就像操作应用自身一样简单。<br>Docker容器理解为一种轻量级的沙盒,每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。</p>
</summary>
<category term="Docker" scheme="http://gpp-note.top/categories/Docker/"/>
<category term="Docker" scheme="http://gpp-note.top/tags/Docker/"/>
</entry>
<entry>
<title>English</title>
<link href="http://gpp-note.top/articles/2020-02-22-interview-english.html"/>
<id>http://gpp-note.top/articles/2020-02-22-interview-english.html</id>
<published>2020-02-22T14:52:40.000Z</published>
<updated>2020-02-24T07:39:27.425Z</updated>
<content type="html"><![CDATA[<a id="more"></a><p>Thank you for giving me this opportunity for this interview</p><p>My name is guopp , and from zhoukou, a city in HeNan provixe<br>I am 26 years old, born inb Henan Provice.</p><p>I think I’m a good team player in project teams.<br>and so on<br>during my college yearss</p><p>there’s all thank you<br>Team player 善于团队协作<br>motivated 积极的 [ˈməʊtɪveɪtɪd]<br>Helpfulness and caring.<br>Adaptability and sense of humor.<br> friendliness.<br> I like people who possess the “can do” spirit.</p><p>I gained many skills and experiences</p><blockquote><p>what is your greatest strength?<br>I feel that my strengest asset is my ability to stick to things to get them done.<br>what is your greatest weakness?<br>I am such a perfectionist that i will not stop until a job is well done</p></blockquote><blockquote><p>Do you think you are introverted or extroverted?(introverted[ˈɪntrəvɜːtɪd])<br>i am quite outgoing, i think<br>I wouldn’t call myself introverted though sometimes I’m reserved and enjoy staying all by myself,often and often I like sharing activities with others.</p></blockquote><blockquote><p>How do you get along with others?<br>I keep close contacts with my friends.(contacts[ˈkɒntækt]),We often get together and talk with each other</p></blockquote><blockquote><p>Which schools have you attended?<br>i graduated from Shanghai Second Polytechnic University(Polytechnic[ˌpɒliˈteknɪk])in 2017(two thousand and seventeen)</p></blockquote><blockquote><p>what’s your major in university<br>software engineering</p></blockquote><blockquote><p>Have you received any certificates ?</p></blockquote><blockquote><p>Have you got any experience in advertisting</p></blockquote><blockquote><p>I am someone who can adapt to any situation</p></blockquote><blockquote><p>If hired , when could you start work?<br>We’ll notify you as soon as possible, when can you start working if we decide to use you ?<br>I’d like to begin to work any time you want<br>when I accepted, i will do my best for the company </p></blockquote><blockquote><p>what do you look for in the job ?<br>what advantages are you seeking from this change of job ?<br>I am hoping to get an offer of a better position, if opportunity knocks, i will take it.</p></blockquote><blockquote><p>what do you want to get from your new job?<br>what do you expect from this job?<br>what is important to you in your job?</p></blockquote><blockquote><p>What’s your marital status ?<br>what’s your plan for the future?<br>what’s your long-range career objective?<br>Do you have a clear career path to follow?<br>What are you long-term goals set for youself?<br>There is no hurry to get married, In order to achieve this goal, I just want to work step by step.<br>I wish to move up to higher positions with acquisition of more experience in the future.<br>I plan to continue working for a long time, My career is the most important to me.<br>I hope to command a skill, to set a solid foundation and to build my interpersonal network.</p></blockquote><blockquote><p>Any other questions ?<br>May I ask about the salary?<br>Do you mind if I ask abount the wages?<br>Are there any additional year-end bonuses?<br>What starting salary would you expect here?<br>The salary I should require would be 21000 a month </p></blockquote><blockquote><p>Could you pleasse tell me something about the wages?<br>I’d like to have the salary which matches my ability<br>I should require a commencing salary of 21000 Yuan a month.<br>Based on my skills and experience, I am looking for 21000 yuan a month.</p></blockquote><p>Good afternoon,my dear manager !</p><p>My name is wang guipeng,I’m very happy and excited for having a face to face interview in your company.</p><p> I hope i can make a good performance today.Now I would like to introduce myself briefly.</p><p>I am 29 years old,born in Anhui province,graduated from the north university of china.</p><p>My major is Software Engneering,and i got my bachelor degree after my graduation in the year of 2013.</p><p>I hava about 5 years work experience,I worked for my first company as a technical support engineer,and I worked for my second company as a function/automation/perfomance test engineer.</p><p>During my tenure, I participated in the skills competition organized by Huawei and won the first place.</p><p>In my daily life,I’m fond of hiking,party,writing blogs.</p><p>I think I’m a good team player and I’m a person of great honesty to others.Also I am able to work under great pressure.</p><p>all right.thank you for giving me the chance!</p><p>After graduation, I spent two companies<br>The first company is to be a Java programmer<br>In life, my personality sunlight, cheerful, like to listen to music, read a book;<br>Work is responsible earnestly, positive initiative, can bear hardships and stand hard work, self-motivated, diligently study, can constantly improve their ability and comprehensive quality.</p>]]></content>
<summary type="html">
Welcome to my-blog!!!
</summary>
<category term="Interview" scheme="http://gpp-note.top/categories/Interview/"/>
<category term="Interview" scheme="http://gpp-note.top/tags/Interview/"/>
</entry>
<entry>
<title>Rabbitmq</title>
<link href="http://gpp-note.top/articles/2020-02-16-interview-rabbitmq.html"/>
<id>http://gpp-note.top/articles/2020-02-16-interview-rabbitmq.html</id>
<published>2020-02-16T14:02:20.000Z</published>
<updated>2020-02-18T14:07:24.510Z</updated>
<content type="html"><![CDATA[<a id="more"></a><h4 id="1-Rabbitmq"><a href="#1-Rabbitmq" class="headerlink" title="1. Rabbitmq"></a>1. Rabbitmq</h4><p>采用AMQP高级消息队列协议的一种消息队列技术,<br>最大的特点就是消费并不需要确保提供方存在,实现了服务之间的高度解耦。</p><p>解耦、异步、削峰</p><h5 id="1-1-解耦"><a href="#1-1-解耦" class="headerlink" title="1.1 解耦"></a>1.1 解耦</h5><p>传统模式:<br>缺点:系统间耦合性太强</p><p>中间件模式:<br>优点:将消息写入消息列表,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。</p><h5 id="1-2-异步"><a href="#1-2-异步" class="headerlink" title="1.2 异步"></a>1.2 异步</h5><p>传统模式:<br>缺点:一些非必要的业务逻辑以同步的方式运行,太耗费时间。</p><p>中间件模式:<br>优点:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度;</p><h5 id="1-3-削峰"><a href="#1-3-削峰" class="headerlink" title="1.3 削峰"></a>1.3 削峰</h5><p>传统模式:<br>缺点:并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常;</p><p>中间件模式:<br>优点:系统A慢慢的按照数据库能处理的并发量,从消息队列中慢慢拉取消息。在生产中,这个短暂的高峰期积压是允许的。</p><h4 id="2-为什么要使用rabbitmq"><a href="#2-为什么要使用rabbitmq" class="headerlink" title="2. 为什么要使用rabbitmq"></a>2. 为什么要使用rabbitmq</h4><ul><li>在分布式系统下具备异步、削峰、负载均衡等一系列高级功能;</li><li>拥有持久化的机制,进程消息、队列中的消息也可以保存下来;</li><li>实现消费者和生产者之间的解耦;</li><li>对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作;</li><li>可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单</li></ul><h4 id="3-使用rabbitmq的场景"><a href="#3-使用rabbitmq的场景" class="headerlink" title="3. 使用rabbitmq的场景"></a>3. 使用rabbitmq的场景</h4><ul><li>服务间异步通信</li><li>顺序消费</li><li>定时任务</li><li>请求削峰</li></ul><h4 id="4-如何确保消息正确的发送至rabbitmq?如何确保消息接收方消费了消息?"><a href="#4-如何确保消息正确的发送至rabbitmq?如何确保消息接收方消费了消息?" class="headerlink" title="4. 如何确保消息正确的发送至rabbitmq?如何确保消息接收方消费了消息?"></a>4. 如何确保消息正确的发送至rabbitmq?如何确保消息接收方消费了消息?</h4><h5 id="发送方确认模式"><a href="#发送方确认模式" class="headerlink" title="发送方确认模式"></a>发送方确认模式</h5><p>将信道设置成confirm模式(发送方确认模式),则所有在信道上发布的消息都会被指派一个唯一的ID。<br>一旦消息被投递到目的队列后,或者消息被写入磁盘后(可持久化的消息),信道会发送一个确认给生产者(包含消息唯一ID)。<br>如果Rabbitmq发生内部错误从而导致消息丢失,会发送一条nack(not ack nowledged,未确认)消息。<br>发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息达到生产者应用程序,生产者应用程序的回调方法就会被触发来处理确认消息。</p><h5 id="接收方确认机制"><a href="#接收方确认机制" class="headerlink" title="接收方确认机制"></a>接收方确认机制</h5><p>消费者接收每一条消息后都必须进行确认(消息接收和确认是两个不同操作)。只有消费者确认了消息,Rabbitmq才能安全的把消息从队列中删除。</p><p>这里并没有用到超时机制,Rabbitmq仅通过Consumer的连接中断来确认是否需要重新发送消息。也就是说,只要连接不中断,Rabbitmq给了Consumer足够长的时间来处理消息。保证数据的最终一致性;</p><p>下面几种特殊情况:</p><ul><li>如果消费者接收到消息,在确认之前断开了连接或取消订阅,Rabbitmq会认为消息没有被分发,然后重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)。</li><li>如果消费者接收到消息却没有确认消息,连接也未断开,则Rabbitmq认为消费者繁忙,将不会给消费者分发更多的消息。</li></ul><h4 id="5-如何避免消息重复投递或重复消费?"><a href="#5-如何避免消息重复投递或重复消费?" class="headerlink" title="5. 如何避免消息重复投递或重复消费?"></a>5. 如何避免消息重复投递或重复消费?</h4><p>在消息生产时,MQ内部针对每条生产者发送的消息生成一个inner-msg-id,作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;<br>在消息消费时,要求消息体中必须要有一个bixId(对于同一业务全局的唯一,如支付ID、订单ID、帖子ID等)作为去重的依据,避免同一条消息被重复消费。</p><h4 id="6-消息基于什么传输"><a href="#6-消息基于什么传输" class="headerlink" title="6. 消息基于什么传输"></a>6. 消息基于什么传输</h4><p>由于TCP连接的创建和销毁开销较大,且并发受系统资源限制,会造成性能瓶颈。<br>Rabbitmq使用信道的方式来传输数据。信道是建立在真实的TCP连接内的虚拟连接,且每条TCP连接上的信道数量没有限制。</p><h4 id="7-消息如何分发"><a href="#7-消息如何分发" class="headerlink" title="7. 消息如何分发"></a>7. 消息如何分发</h4><p>若该队列至少有一个消费者订阅,消息将以循环(round-robin)的方式发送给消费者。每条消息只会分发给一个订阅的消息者(前提是消费者能够正常处理消息并进行确认)。通过路由可实现多消费的功能。</p><h4 id="8-消息怎么路由"><a href="#8-消息怎么路由" class="headerlink" title="8. 消息怎么路由"></a>8. 消息怎么路由</h4><p>消息提供方 -》 路由 -》 一到多个队列消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。通过队列路由键,可以把队列绑定到交换器上。消息到达交换器后,Rabbitmq会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则);</p><p>常见的交换器有三种:</p><ul><li>fanout:如果交换器收到消息,就会广播到所有绑定的队列上;</li><li>direct:如果路由键完全匹配,消息就被投递到相应的队列;</li><li>topic:可以使来自不同源头的消息能够达到同一个队列。使用topic交换器时,可以使用通配符;</li></ul><h4 id="9-如何确保消息不丢失?"><a href="#9-如何确保消息不丢失?" class="headerlink" title="9. 如何确保消息不丢失?"></a>9. 如何确保消息不丢失?</h4><p>消息持久化,当前前提是队列必须持久化</p><p>Rabbitmq确保持久性消息能从服务器重启中恢复的方式是,将它们写入磁盘上的一个持久化日志文件,当发布一条持久化消息到持久交换器上时,Rabbitmq会在消息提交到日志文件后才发送响应。一旦消费者从持久队列中消费了一条持久化消息,Rabbitmq会在持久化日志中把这条消息标记为等待垃圾收集。如果持久化消息在被消费之前Rabbitmq重启,那么Rabbit会自动重建交换器和队列(以及绑定),并重新发布持久化日志文件中的消息到合适的队列。</p><h4 id="10-使用Rabbitmq有什么好处"><a href="#10-使用Rabbitmq有什么好处" class="headerlink" title="10. 使用Rabbitmq有什么好处"></a>10. 使用Rabbitmq有什么好处</h4><ul><li>服务间高度解耦</li><li>异步通信性能高</li><li>流量削峰</li></ul><h4 id="11-Rabbitmq的集群"><a href="#11-Rabbitmq的集群" class="headerlink" title="11. Rabbitmq的集群"></a>11. Rabbitmq的集群</h4><p>镜像集群模式<br>你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。<br>好处在于,你任何一个机器宕机了,没事,别的机器都可以用。<br>坏处在于,第一,这个性能开销也太大了吧,消息同步所有机器,导致网络带宽压力和消耗很重;第二,这么玩,没有扩展性可言了,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue。</p><h4 id="12-mq的缺点"><a href="#12-mq的缺点" class="headerlink" title="12. mq的缺点"></a>12. mq的缺点</h4><ul><li>系统可用性降低<br>系统引入的外部依赖较多,就容易挂掉,本来你就是A,系统调用BCD三个系统的接口就好了,人ABCD四个系统好好的,没问题,如果加个mq进来,如果mq挂了,整套系统就崩溃了。</li><li>系统复杂性提高<br>加个mq,如何保证消息没有重复消费?如何处理消息丢失情况?如何保证消息传递的顺序性?</li><li>一致性问题<br>A系统处理完了直接返回成功了,人都以为这个请求成功了;但问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,数据就不一致了。</li></ul><p>所以消息队列实际是一种非常复杂的架构,引入有很多好处,但也得针对它带来的坏处做各种额外的技术方案和架构来规避掉。</p><h4 id="13-消息队列如何选型"><a href="#13-消息队列如何选型" class="headerlink" title="13. 消息队列如何选型"></a>13. 消息队列如何选型</h4>]]></content>
<summary type="html">
Welcome to my-blog!!!
</summary>
<category term="Interview" scheme="http://gpp-note.top/categories/Interview/"/>
<category term="Interview" scheme="http://gpp-note.top/tags/Interview/"/>
</entry>
<entry>
<title>ES6</title>
<link href="http://gpp-note.top/articles/2020-02-12-interview-ES6.html"/>
<id>http://gpp-note.top/articles/2020-02-12-interview-ES6.html</id>
<published>2020-02-12T09:40:14.000Z</published>
<updated>2020-02-15T13:21:01.355Z</updated>
<content type="html"><![CDATA[<a id="more"></a><h4 id="1-let-var的区别"><a href="#1-let-var的区别" class="headerlink" title="1. let var的区别"></a>1. let var的区别</h4><p>let声明的变量</p><ul><li>不能重复声明</li><li>作用域为该语句所在的代码块内</li><li>不存在变量提升(会报错)</li><li>存在暂时性死域(只能先声明再使用)</li></ul><p>var声明的变量</p><ul><li>能重复声明</li><li>作用域为该语句所在的函数内</li><li>存在变量提升(变量可以在声明之前使用,值为undefined);</li></ul><h4 id="2-WeakMap和Map的区别?"><a href="#2-WeakMap和Map的区别?" class="headerlink" title="2. WeakMap和Map的区别?"></a>2. WeakMap和Map的区别?</h4><p>WeakMap和Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。<br>最大的好处是可以避免内存泄漏。一个仅被WeakMap作为key而引用的对象,会被垃圾回收期回收掉。<br>WeakMap有和Map类似的set(key, value), get(key), has(key), delete(key)和clear()方法,没有任何与迭代有关的属性和方法。</p><h4 id="3-ES6模块加载"><a href="#3-ES6模块加载" class="headerlink" title="3. ES6模块加载"></a>3. ES6模块加载</h4><p>ES6 实现了模块功能,将文件当作独立的模块,一个文件一个模块。<br>每个模块可以导出自己的API成员,也可以导入其他模块或者模块中特定的API</p><p>ES6 模块的设计思想:是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。</p><p>CommonJS和AMD模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。<br>所以编译期会检查对导入模块的API或成员的引用</p><p>模块有两个主要特征:</p><ul><li>为创建内部作用域而调用了一个包装函数</li><li>包装函数的返回值至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包</li></ul><h4 id="4-require、exports、module-exports、import、export"><a href="#4-require、exports、module-exports、import、export" class="headerlink" title="4. require、exports、module.exports、import、export"></a>4. require、exports、module.exports、import、export</h4><p>使用范围:</p><ul><li>require:node 和 es6 都支持的引入</li><li>module.exports / exports:只有 node 支持的导出</li><li>export / import:只有es6 支持的导出引入</li></ul><p>在node模块里是遵循CommonJS规范的,执行模块文件时,会同时生成一个module对象和一个exports对象,<br>module对象又有一个exports属性,他们初始化时指向同一块{}内存区域,<br>注意是同一块,即exports对象是module.exports的引用:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">// test.js</span><br><span class="line">var module.exports = {};</span><br><span class="line">var exports = module.exports;</span><br><span class="line">console.log(module.exports); // {}</span><br><span class="line">console.log(exports); // {}</span><br><span class="line"></span><br><span class="line">exports.name = 2;</span><br><span class="line">console.log(module.exports); // {name: 2}</span><br><span class="line">console.log(exports); // {name: 2}</span><br><span class="line"></span><br><span class="line">var exports = {name: 3};</span><br><span class="line">console.log(module.exports); // {name: 2}</span><br><span class="line">console.log(exports); // {name: 3}</span><br></pre></td></tr></table></figure><p>可以看出:其实require导出的内容是module.exports指向的内存块内容,并不是exports的。<br>区分他们之间的区别就是exports只是module.exports的引用,辅助后者添加内容用的。<br>当其中一个指向另一块内存时,两者便没什么关系了。</p><h4 id="5-CommonJS中require-exports和ES6中import-export区别"><a href="#5-CommonJS中require-exports和ES6中import-export区别" class="headerlink" title="5. CommonJS中require/exports和ES6中import/export区别"></a>5. CommonJS中require/exports和ES6中import/export区别</h4><p>CommonJS模块的重要特性是加载时执行,及脚本代码在require的时候,就会全部执行。<br>一旦出现某个模块被“循环加载”就只输出已经执行的部分,还没有执行的部分是不输出的。</p><p>ES6模块的动态引用,如果使用import从一个模块加载变量,那些变量不会缓存,而是成为<br>一个指向被加载模块的引用。需要开发者自己保证,真正取值的时候能够取到值。</p><p>import/export最终都是编译为require/exports来执行的</p><p>CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。</p><p>export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。</p><h4 id="6-几种常见模块化规范的简介"><a href="#6-几种常见模块化规范的简介" class="headerlink" title="6. 几种常见模块化规范的简介"></a>6. 几种常见模块化规范的简介</h4><p>CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的。</p><p>AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难。</p><p>CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Nodejs中运行。</p><p>ES6在语言标椎的层面上,实现了模块功能,而且实现的相当简单,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。</p><h4 id="7-模块化开发是怎么做的?"><a href="#7-模块化开发是怎么做的?" class="headerlink" title="7. 模块化开发是怎么做的?"></a>7. 模块化开发是怎么做的?</h4><p>使用命名空间</p><h4 id="8-ES5的defineProperty"><a href="#8-ES5的defineProperty" class="headerlink" title="8. ES5的defineProperty"></a>8. ES5的defineProperty</h4><p>Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,<br>并返回这个对象。</p><h4 id="9-map、each、some、forEach"><a href="#9-map、each、some、forEach" class="headerlink" title="9. map、each、some、forEach"></a>9. map、each、some、forEach</h4><ul><li><p>map<br>map遍历数组,克隆原数组,对新数组进行操作并返回一个新的数组,滥用map会造成内存浪费;<br>有return;</p></li><li><p>each<br>返回原来的数组,不会创建一个新的数组;<br>无return;</p></li><li><p>every<br>当内部return false时跳出整个循环;<br>有一项不满足元素,则整个表达式返回false,剩余元素不再执行;</p></li><li><p>some<br>当内部return true时跳出整个循环;<br>有一个元素满足条件,则表达式返回true,剩余元素不再执行;</p></li><li><p>foreach<br>没有返回值;<br>对原本数组进行操作;</p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">var array = [1,2,3,4,5]; </span><br><span class="line">var res = _.forEach(array, function (item,index,input) { </span><br><span class="line"> input[index] = item*10; </span><br><span class="line">}) </span><br><span class="line">console.log(res);//=>undefined</span><br><span class="line">console.log(array);//=>[10,20,30,40,50]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">var res = _.map(array, function (item,index,input) { </span><br><span class="line"> input[index] = item*10; </span><br><span class="line">}) </span><br><span class="line">console.log(res);//=>[10,20,30,40,50]</span><br><span class="line">console.log(array);//=>[1,2,3,4,5]</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
Welcome to my-blog!!!
</summary>
<category term="Interview" scheme="http://gpp-note.top/categories/Interview/"/>
<category term="Interview" scheme="http://gpp-note.top/tags/Interview/"/>
</entry>
<entry>
<title>linux</title>
<link href="http://gpp-note.top/articles/2020-02-11-linux.html"/>
<id>http://gpp-note.top/articles/2020-02-11-linux.html</id>
<published>2020-02-11T09:58:08.000Z</published>
<updated>2020-02-11T10:20:40.701Z</updated>
<content type="html"><![CDATA[<h4 id="1-常用操作及概念"><a href="#1-常用操作及概念" class="headerlink" title="1. 常用操作及概念"></a>1. 常用操作及概念</h4><p>Tab:命令和文件名补全;</p><p>Ctrl + C:中断正在运行的程序;</p><p>Ctrl + D:结束键盘输入</p><a id="more"></a><p>man<br>man date</p><p>info:将文档分成一个个页面,每个页面可以进行跳转;</p><p>doc:/usr/share/doc 存放着软件的一整套说明文件;</p><p>who:查看有没有其它用户在线;</p><p>sync:为了加快磁盘文件的读写速度,位于内存中的文件数据不会立即<br>同步到磁盘上,因此关机之前需要先进行sync同步操作;</p><ul><li><p>修改权限<br> r:4,w:2,x:1<br> chmod [-R] xyz dirname/filename</p></li><li><p>默认权限<br> 文件默认:666,-rw-rw-rw-<br> 目录默认:777,drwxrwxrwx</p></li><li><p>获取文件内容<br> cat:取得文件内容<br> tac:是cat反向操作,从最后一行开始打印<br> more:一页一页查看文件内容<br> less:向前翻页<br> head:head [-n number] filename 取得文件前几行<br> tail:取得文件最后几行<br> od:以字符或十六进制的形式显示二进制文件</p></li><li><p>指令与文件搜索<br> which:指令搜索 which [-a] command<br> whereis:文件搜索,速度比较快,因为它只搜索几个特定的目录<br> locate:文件搜索,可以用正则或关键字搜索<br> find:文件搜索,可以使用文件的属性和权限进行搜索</p></li><li><p>正则表达式<br> grep<br> grep -n ‘the’ regular_express.txt</p><p> last<br> last -n 5</p><p> ps:查看某个时间点的进程信息<br> ps -l:查看自己的进程<br> ps aux:查看系统所有进程</p><p> top:实时查看进程信息<br> top -d 2:两秒钟刷新一次</p><p> netstat:查看占用端口的进程<br> netstat -anp | grep port</p></li></ul><h4 id="2-目录配置"><a href="#2-目录配置" class="headerlink" title="2. 目录配置"></a>2. 目录配置</h4><p>/ : root,根目录;<br>/usr(unix software resource): 所有系统默认软件都会安装到这个目录;<br>/var(variable):存放系统或程序运行过程中的数据文件;</p><h4 id="3-进程"><a href="#3-进程" class="headerlink" title="3. 进程"></a>3. 进程</h4><ul><li><p>孤儿进程<br>一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。<br>孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。<br>由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。</p></li><li><p>僵尸进程<br>一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。<br>僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。<br>系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。<br>要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。</p></li></ul>]]></content>
<summary type="html">
<h4 id="1-常用操作及概念"><a href="#1-常用操作及概念" class="headerlink" title="1. 常用操作及概念"></a>1. 常用操作及概念</h4><p>Tab:命令和文件名补全;</p>
<p>Ctrl + C:中断正在运行的程序;</p>
<p>Ctrl + D:结束键盘输入</p>
</summary>
<category term="linux" scheme="http://gpp-note.top/categories/linux/"/>
<category term="linux" scheme="http://gpp-note.top/tags/linux/"/>
</entry>
<entry>
<title>数据库系统原理</title>
<link href="http://gpp-note.top/articles/2020-02-11-dataBase.html"/>
<id>http://gpp-note.top/articles/2020-02-11-dataBase.html</id>
<published>2020-02-11T09:57:18.000Z</published>
<updated>2020-02-11T10:16:12.984Z</updated>
<content type="html"><![CDATA[<h4 id="1-事务"><a href="#1-事务" class="headerlink" title="1. 事务"></a>1. 事务</h4><p>能满足ACID特性的一组操作,可以通过commit提交一个事务,也可以用Rollback进行回滚。</p><a id="more"></a><h5 id="ACID"><a href="#ACID" class="headerlink" title="ACID"></a>ACID</h5><ul><li>A原子性(Atomicity)<br>事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚;<br>回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。</li><li>C一致性(Consistency)<br>数据库在事务执行前后都保持一致性状态。<br>在一致性状态下,所有事务对一个数据的读取结果都是相同的。</li><li>I隔离性(Isolationn)<br>一个事务所做的修改在最终提交以前,对其它事务是不可见的。</li><li>D持久性(Durability)<br>一旦事务提交,所做的修改将会蝾螈保存到数据库中。<br>即使系统发生崩溃,事务执行的结果也不能丢失。<br>使用重组日志来保证持久性。</li></ul><h5 id="事务的ACID特性"><a href="#事务的ACID特性" class="headerlink" title="事务的ACID特性"></a>事务的ACID特性</h5><p>只有满足一致性,事务的执行结果才是正确的;<br>在无并发的情况下,事务串行执行,隔离性一定能够满足,此时只要能满足原子性,一定能满足一致性;<br>在并发情况下,多个事务并行执行,事务不仅要满足原子性,还要满足隔离性,才能满足一致性;<br>事务满足持久化是为了能应对数据库崩溃的情况;</p><h5 id="Autocommit"><a href="#Autocommit" class="headerlink" title="Autocommit"></a>Autocommit</h5><p>mysql默认采用自动提交模式。即不显示使用start transaction语句来开始一个事务,则每个查询都会被当做一个事务自动提交。</p><h4 id="2-并发一致性问题"><a href="#2-并发一致性问题" class="headerlink" title="2. 并发一致性问题"></a>2. 并发一致性问题</h4><p>在并发环境下,事务的隔离性很难保证,因为会出现很多并发一致性的问题。<br>产生并发不一致性问题的主要原因是破坏了事务的隔离性。</p><h5 id="解决办法是"><a href="#解决办法是" class="headerlink" title="解决办法是"></a>解决办法是</h5><p>通过并发控制来保证隔离性。<br>并发控制可以通过封锁来实现,但是封锁操作需要用户自己来控制,相当复杂。<br>数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。</p><h5 id="并发一致性问题"><a href="#并发一致性问题" class="headerlink" title="并发一致性问题"></a>并发一致性问题</h5><ul><li><p>丢失修改;<br>T1和T2两个事务都对同一个数据修改,T1先修改,T2后修改,T2的修改覆盖了T1。</p></li><li><p>读脏数据;<br>T1修改一个数据,T2随后读取这个数据,如果T1撤销了这次修改,则T2读取的数据是脏数据。</p></li><li><p>不可重复读;<br>T2读取一个数据,T1修改该数据,如果T2再次读取,则和第一次结果不同。</p></li><li><p>幻影读;<br>T1读取某个范围的数据,T2在这个范围内插入新的数据,T1再次读取这个范围的数据,此时读取的结果和第一次不同。</p></li></ul><h4 id="3-封锁粒度"><a href="#3-封锁粒度" class="headerlink" title="3. 封锁粒度"></a>3. 封锁粒度</h4><p>mysql提供了两种封锁粒度:行级锁和表级锁。</p><p>应该尽量只锁定需要修改的那部分数据,而不是所有的资源。<br>锁定的数据量越少,发生锁争用的可能性就越小,系统的并发程度就越高。</p><p>加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、及检查锁状态)都会增加系统开销,因此锁粒度越小,系统开销越大。</p><p>在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。</p><h5 id="封锁类型"><a href="#封锁类型" class="headerlink" title="封锁类型"></a>封锁类型</h5><ul><li>读写锁<br>排它锁(Exclusive):简写X锁,又称写锁;<br>共享锁(Shared):简写S锁,又称读锁;</li></ul><p>一个事务对数据对象A加了X锁,就可以对A进行读取和更新。加锁期间其它事务不能对A加任何锁;<br>一个事务对数据对象A加了S锁,就可以对A进行读取操作,但不能更新。加锁期间其它事务对A加S锁,但不能加X锁;</p><p>锁的兼容关系:</p><ul><li>X S<br>X × ×<br>S × √</li></ul><ul><li>意向锁<br>使用意向锁Intention Locks可以更容易的支持多粒度封锁。</li></ul><p>在存在行级锁和表级锁的情况下,事务T想要对表A加X锁,就需要检测是否有其他事务对A或A中任意一行加了锁,需要对A每一行都检测一次,比较耗时。</p><p>意向锁在原来的x/s锁之上引入了IX/IS,IX/IS都是表锁,表示一个事务想要在表中的某个数据行了加X锁或S锁,规定:<br>一个事务在获得某个数据行对象的S锁之前,必须先获得表的IS锁或更强的锁;<br>一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁;</p><p>引入意向锁,事务T对表A加X锁,只需要先检测是否有其他事务对表A加了X/IX/S/IS锁,如果加了,表示有其它事务正在使用这个表或表中某一行的锁,因此事务T加X锁失败。</p><p>兼容关系:<br>任意IX/IS之间都是兼容的,因为他们只是表示想要对表加锁,而不是真正的锁;<br>S锁只与S锁和IS锁兼容,即事务T想要对数据进行加S锁,其它事务可以已经获得对表或表中行的S锁;</p><ul><li>X IX S IS<br>X × × × ×<br>IX × √ × √<br>S × × √ √<br>IS × √ √ √</li></ul><h5 id="封锁协议"><a href="#封锁协议" class="headerlink" title="封锁协议"></a>封锁协议</h5><p>1、三级封锁协议</p><ul><li>一级封锁协议<br>事务A要修改数据A时必须加X锁,直到T结束才释放锁</li></ul><p>可以解决丢失修改的问题,因为不可能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。</p><ul><li>二级封锁协议<br>在一级的基础上,要求读取数据A时必须加S锁,读取完马上释放S锁。</li></ul><p>可以解决读脏数据问题,因为一个事务对数据A进行修改,根据1级封锁协议,会加X锁,那么就不能再加S锁了,也就是不会读入数据。</p><ul><li>三级封锁协议<br>在二级的基础上,要求读取数据A时必须加S锁,知道事务结束才能释放S锁。</li></ul><p>可以解决不可重复读的问题,因为读A时,其它事务不能对A加X锁,从而避免了在读的期间数据发生改变。</p><p>2、两段锁协议<br>加锁和解锁分两个阶段进行。</p><p>可串行化调度:通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。</p><p>事务遵循两段锁协议是保证可串行化调度的充分条件。<br>但不是必要条件</p><h5 id="mysql隐式与显示锁定"><a href="#mysql隐式与显示锁定" class="headerlink" title="mysql隐式与显示锁定"></a>mysql隐式与显示锁定</h5><p>mysql的InnoDB存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,且所有的锁都在同一时刻释放,这被称为隐式锁定。</p><h4 id="4-隔离级别"><a href="#4-隔离级别" class="headerlink" title="4. 隔离级别"></a>4. 隔离级别</h4><h5 id="未提交读(Read-Uncommited)"><a href="#未提交读(Read-Uncommited)" class="headerlink" title="未提交读(Read Uncommited)"></a>未提交读(Read Uncommited)</h5><p>事务中的修改,即使没有提交,对其它事务也是可见的。</p><h5 id="提交读(Read-commited)"><a href="#提交读(Read-commited)" class="headerlink" title="提交读(Read commited)"></a>提交读(Read commited)</h5><p>一个事务只能读取已经提交的事务所做的修改。<br>即一个事务所做的修改在提交之前对其它事务是不可见的。</p><h5 id="可重复读(Repeatable-read)"><a href="#可重复读(Repeatable-read)" class="headerlink" title="可重复读(Repeatable read)"></a>可重复读(Repeatable read)</h5><p>保证在同一个事务中多次获取同样数据的结果是一样的。</p><h5 id="可串行化(Serializable)"><a href="#可串行化(Serializable)" class="headerlink" title="可串行化(Serializable)"></a>可串行化(Serializable)</h5><p>强制事务串行执行。<br>需要加锁实现,而其它隔离级别通常不需要。</p><p>隔离级别 脏读 不可重复读 幻影读<br>未提交读 √ √ √<br>提交读 × √ √<br>可重复读 × × √<br>可串行化 × × ×</p><h4 id="5-多版本并发控制"><a href="#5-多版本并发控制" class="headerlink" title="5. 多版本并发控制"></a>5. 多版本并发控制</h4><p>多版本并发控制(Multi-Version Concurrency Control,MVCC)<br>是Mysql的InnoDB存储引擎实现隔离级别的一种具体方式,<br>用于实现提交读和可重复读这两种隔离级别。<br>而未提交读隔离级别总是读取最新的数据行,无需使用MVCC。<br>可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现。</p><h5 id="版本号"><a href="#版本号" class="headerlink" title="版本号"></a>版本号</h5><p>系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。<br>事务版本号:事务开始时的系统版本号。</p><h5 id="隐藏的列"><a href="#隐藏的列" class="headerlink" title="隐藏的列"></a>隐藏的列</h5><p>MVCC在每行记录后面都保存着两个隐藏的列,用来存储两个版本号:</p><h5 id="创建版本号"><a href="#创建版本号" class="headerlink" title="创建版本号"></a>创建版本号</h5><p>指示创建一个数据行的快照时的系统版本号;<br>删除版本号:<br>如果该快照的删除版本号大于当前事务版本号 表示该快照有效,否则表示该快照已经被删除了。</p><h5 id="Undo日志"><a href="#Undo日志" class="headerlink" title="Undo日志"></a>Undo日志</h5><p>MVCC使用到的快照存储在Undo日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。</p><h5 id="实现过程"><a href="#实现过程" class="headerlink" title="实现过程"></a>实现过程</h5><p>当开始一个事务,该事务的版本号肯定大于当前所有数据行快照的创建版本号。<br>数据行快照的创建版本号是创建数据行快照时的系统版本号,系统版本号随着创建事务而递增,因此新创建一个事务时,这个事务的系统版本号比之前的系统版本号都打,也就是比所有数据行快照的创建版本号都大。</p><ul><li>select</li><li>insert</li><li>delete</li><li>update</li></ul><h5 id="快照读与当前读"><a href="#快照读与当前读" class="headerlink" title="快照读与当前读"></a>快照读与当前读</h5><p>快照读<br>使用MVCC读取的是快照中的数据,这样可以减少加锁带来的开销;</p><p>当前读<br>读取的是最新的数据,需要加锁。</p><h4 id="6-Net-Key-Locks"><a href="#6-Net-Key-Locks" class="headerlink" title="6. Net-Key Locks"></a>6. Net-Key Locks</h4><p>Next-Key Locks是mysql的InnoDB存储引擎的一种锁的实现。</p><p>MVCC不能解决幻影读的问题,Next-Key Locks就是为解决这个问题。<br>在可重复读的隔离级别下,使用MVCC+Next-key Locks可以解决幻影读问题。</p><p>Record Locks<br>锁定一个记录上的索引,而不是记录本身。<br>如果表没有设置索引,InnoDB会自动在主键上创建隐藏的聚簇索引,因此Record Locks依然可以使用</p><p>Gap Locks<br>锁定索引之间的间隙,但不包含索引本身</p><p>Next-Key Locks<br>是Record Locks和Gap Locks的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。</p><h4 id="7-关系数据库设计理论"><a href="#7-关系数据库设计理论" class="headerlink" title="7. 关系数据库设计理论"></a>7. 关系数据库设计理论</h4><h5 id="函数依赖"><a href="#函数依赖" class="headerlink" title="函数依赖"></a>函数依赖</h5><p>记A->B表示A函数觉得B,也可以说B函数依赖于A。</p><p>如果 {A1,A2,… ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。</p><p>对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。</p><p>对于 A->B,B->C,则 A->C 是一个传递函数依赖。</p><h5 id="异常"><a href="#异常" class="headerlink" title="异常"></a>异常</h5><p>不符合范式的关系,会产生很多异常:<br> 冗余数据<br> 修改异常<br> 删除异常<br> 插入异常</p><h5 id="范式"><a href="#范式" class="headerlink" title="范式"></a>范式</h5><p>范式理论是为了解决以上四种异常。<br>高级别范式的依赖于低级别的范式</p><ul><li><p>第一范式(1NF)<br>属性不可分</p></li><li><p>第二范式(2NF)<br>每个非主属性完全函数依赖于键码。<br>可以通过分解来满足。</p></li><li><p>第三范式(3NF)<br>非主属性不传递函数依赖于键码。</p></li></ul><h4 id="8-ER图"><a href="#8-ER图" class="headerlink" title="8. ER图"></a>8. ER图</h4><p>Entity-Relationship</p><p>包括:实体、属性、联系</p><p>用来进行关系型数据库系统的概念设计。</p><h5 id="实体的三种联系"><a href="#实体的三种联系" class="headerlink" title="实体的三种联系"></a>实体的三种联系</h5><ul><li><p>一对一:<br>画两个带箭头的线段</p></li><li><p>一对多:<br>A到B是一对多,则画指向B的箭头</p></li><li><p>多对多:<br>两个不带箭头的线段</p></li></ul><h5 id="表示出现多次的关系"><a href="#表示出现多次的关系" class="headerlink" title="表示出现多次的关系"></a>表示出现多次的关系</h5><p>一个实体在联系出现几次,就要用几条线连接。</p><h5 id="联系的多向性"><a href="#联系的多向性" class="headerlink" title="联系的多向性"></a>联系的多向性</h5><p>三元关系</p><h5 id="表示子类"><a href="#表示子类" class="headerlink" title="表示子类"></a>表示子类</h5><p>用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,与父类和子类有关系的连到父类上。</p>]]></content>
<summary type="html">
<h4 id="1-事务"><a href="#1-事务" class="headerlink" title="1. 事务"></a>1. 事务</h4><p>能满足ACID特性的一组操作,可以通过commit提交一个事务,也可以用Rollback进行回滚。</p>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Database" scheme="http://gpp-note.top/tags/Database/"/>
</entry>
<entry>
<title>Mysql</title>
<link href="http://gpp-note.top/articles/2020-02-11-dataBase-Mysql.html"/>
<id>http://gpp-note.top/articles/2020-02-11-dataBase-Mysql.html</id>
<published>2020-02-11T09:56:42.000Z</published>
<updated>2020-02-11T10:42:46.918Z</updated>
<content type="html"><![CDATA[<h4 id="1-索引"><a href="#1-索引" class="headerlink" title="1. 索引"></a>1. 索引</h4><h5 id="B-Tree原理"><a href="#B-Tree原理" class="headerlink" title="B+ Tree原理"></a>B+ Tree原理</h5><ol><li>数据结构<br>B Tree (Balance Tree,平衡树):查找树,且所有叶子节点位于同一层。<br>B+ Tree是基于B Tree和叶子节点顺序访问指针进行实现的,它具有B Tree的平衡性,通过顺序访问指针提高区间查询性能。</li></ol><p>一个节点中的key从左到右非递减排列,key左右是key(i)、key(i+1),且不为null,则该指针指向节点的所有key大于等于key(i)且小于等于key(i+1)。</p><a id="more"></a><ol start="2"><li>操作</li></ol><h4 id="2-Mysql索引"><a href="#2-Mysql索引" class="headerlink" title="2. Mysql索引"></a>2. Mysql索引</h4><p>索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同的存储引擎具有不同的索引类型和实现。</p><h5 id="B-Tree索引"><a href="#B-Tree索引" class="headerlink" title="B+ Tree索引"></a>B+ Tree索引</h5><p>大多数Mysql存储引擎的默认索引类型。</p><p>不需要进行全表扫描,只需要对树进行搜索即可,查找速度快很多。<br>B+Tree的有序性,所以除了用于查找,还可以用于排序和分组。</p><p>可以指定多个列作为索引列,多个索引列共同组成键。<br>适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。<br>如果不是按照索引列的顺序进行查找,则无法使用索引。</p><p>InnoDB的B+Tree索引分为主索引和辅助索引。<br>主索引的叶子节点data记录着完整的数据记录,这种记录称为聚簇索引。<br>因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。</p><p>辅助索引的叶子节点的data域记录着主键的值,因此在使用辅助索引进行查找时,<br>需要先查找到主键值,然后再到主索引中进行查找。</p><h5 id="哈希索引"><a href="#哈希索引" class="headerlink" title="哈希索引"></a>哈希索引</h5><p>哈希索引能以O(1)时间进行查找,但失去了有序性。<br>无法用于排序与分组<br>只支持精确查找,无法用于部分查找和范围查找</p><p>InnoDB存储引擎有一个特殊的功能叫“自适应哈希索引”<br>当某个索引值被使用的非常频繁时,会在B+Tree索引之上再创建一个哈希索引,<br>这样就让B+Tree索引具有哈希索引的一些优点,如:快速的哈希查找。</p><h5 id="全文索引"><a href="#全文索引" class="headerlink" title="全文索引"></a>全文索引</h5><p>MyISAM存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。</p><p>查找条件使用Match Against,而不是普通的Where</p><p>全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。</p><p>InnoDB存储引擎在Mysql5.6.4版本中也开始支持全文索引。</p><h5 id="空间数据索引"><a href="#空间数据索引" class="headerlink" title="空间数据索引"></a>空间数据索引</h5><p>MyISAM存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。<br>空间数据索引会从所有维度来索引数据,可以有效的使用任意维度来进行组合查询。<br>必须使用GIS相关的函数来维护数据。</p><h4 id="3-索引优化"><a href="#3-索引优化" class="headerlink" title="3. 索引优化"></a>3. 索引优化</h4><ol><li>独立的列<br>在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">// 不能使用 actor_id 列的索引</span><br><span class="line">SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;</span><br></pre></td></tr></table></figure><ol start="2"><li>多列索引<br>在需要使用多列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 最好把 actor_id 和 film_id 设置为多列索引</span><br><span class="line">SELECT film_id, actor_ id FROM sakila.film_actor</span><br><span class="line">WHERE actor_id = 1 AND film_id = 1;</span><br></pre></td></tr></table></figure><ol start="3"><li>索引列的顺序<br>让选择项最强的索引列放在前面</li></ol><p>索引的选择性是指:<br> 不重复的索引值和记录总数的比值。<br> 最大值为1,此时每个记录都有唯一的索引与其对应。<br> 选择性越高,每个记录的区分度越高,查询效率也越高。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">// customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面</span><br><span class="line">SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,</span><br><span class="line">COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,</span><br><span class="line">COUNT(*)</span><br><span class="line">FROM payment;</span><br><span class="line"></span><br><span class="line">staff_id_selectivity: 0.0001</span><br><span class="line">customer_id_selectivity: 0.0373</span><br><span class="line"> COUNT(*): 16049</span><br></pre></td></tr></table></figure><ol start="4"><li><p>前缀索引<br>对于Blob、Text和Varcher类型的列,必须使用前缀索引,只索引开始的部分字符。<br>前缀长度的选取需要根据索引选择性来确定。</p></li><li><p>覆盖索引<br>索引包含所有需要查询的字段的值。</p></li></ol><p>优点:</p><ul><li>索引通常远小于数据行的大小,只读取索引能大大减少数据访问量;</li><li>一些存储引擎在内存中只缓存索引,而数据依赖于操作系统来缓存,因此,只访问索引可以不使用系统调用;</li><li>对于InnoDB引擎,若辅助索引能够覆盖查询,则无需访问主索引;</li></ul><h5 id="索引的优点"><a href="#索引的优点" class="headerlink" title="索引的优点"></a>索引的优点</h5><ul><li>大大减少了服务器需要扫描的数据行数;</li><li>帮助服务器避免进行排序和分组,以及比曼创建临时表;(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表);</li><li>将随机I/O变为顺序I/O(B+Tree索引是有序的,会将相邻的数据都存储在一起);</li></ul><h5 id="索引的使用条件"><a href="#索引的使用条件" class="headerlink" title="索引的使用条件"></a>索引的使用条件</h5><ul><li>对于非常小的表,大部分情况下简单的全表扫描比建立索引更高效;</li><li>对于中到大型的表,索引非常有效;</li><li>对于特大型的表,建立和维护索引的代价将会随之增长,这时需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条一条记录的匹配,如:使用分区技术。</li></ul><h4 id="4-查询性能优化"><a href="#4-查询性能优化" class="headerlink" title="4. 查询性能优化"></a>4. 查询性能优化</h4><p>使用Explain进行分析<br>Explain用来分析select查询,通过分析explain结果来优化查询语句。</p><h5 id="重要字段"><a href="#重要字段" class="headerlink" title="重要字段"></a>重要字段</h5><ul><li>select_type:查询类型,有简单查询、联合查询、子查询等;</li><li>key:使用的索引;</li><li>rows:扫描的行数;</li></ul><h5 id="优化数据访问"><a href="#优化数据访问" class="headerlink" title="优化数据访问"></a>优化数据访问</h5><ol><li>减少请求的数据量</li></ol><ul><li>只返回必要的列;</li><li>只返回必要的行;</li><li>缓存重复查询的数据:使用缓存可以避免在数据库中进行查询;</li></ul><ol start="2"><li>减少服务器端扫描的行数<br>最有效的方式是使用索引来覆盖查询</li></ol><h5 id="重构查询方式"><a href="#重构查询方式" class="headerlink" title="重构查询方式"></a>重构查询方式</h5><ol><li><p>切分大查询<br>一个大查询如果一次执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但是重要的查询</p></li><li><p>分解大连接查询<br>将一个大连接查询分解成对每一个表进行一次单表查询,然后再应用程序中进行关联<br>好处:</p></li></ol><ul><li>让缓存更高效;<br> 对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。<br> 而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。</li><li>分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询;</li><li>减少锁竞争;</li><li>在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩;</li><li>查询本身效率也可能会有所提升。</li></ul><h4 id="5-存储引擎"><a href="#5-存储引擎" class="headerlink" title="5. 存储引擎"></a>5. 存储引擎</h4><h5 id="InnoDB"><a href="#InnoDB" class="headerlink" title="InnoDB"></a>InnoDB</h5><p>是Mysql默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。</p><p>实现了四个标准的隔离级别,默认级别是可重复读(Repeatable Read),<br>在可重复读的隔离级别下,通过多版本并发控制(MVCC)+间隙锁(Next-Key Locking)防止幻影读。</p><p>主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。</p><p>内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、<br>能够加速插入操作的插入缓冲区等。</p><p>支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,<br>而在读写混合场景中,停止写入可能也意味着停止读取。</p><h5 id="MyISAM"><a href="#MyISAM" class="headerlink" title="MyISAM"></a>MyISAM</h5><p>设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。</p><p>提供了大量的特性,包括压缩表、空间数据索引等。</p><p>不支持事务。<br>不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。<br>但在表有读取操作的同时,也可以往表中插入新的记录,成为并发插入(Concurrent insert)</p><p>可以手动或自动执行检查和修复操作,但和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作非常慢。</p><p>如果指定了Delay_key_write选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓存区,<br>只有在清理键缓存区时或关闭表的时候才会将对应的索引快写入磁盘。这种方式可以极大提升写入性能,但在数据库或主机崩溃时会造成索引损坏,需要执行修复操作。</p><h5 id="比较"><a href="#比较" class="headerlink" title="比较"></a>比较</h5><ul><li>事务:InnoDB是事务型,可以使用Commit和Rollback语句;</li><li>并发:MyISAM只支持表级锁,而InnoDB还支持行级锁;</li><li>外键:InnoDB支持外键;</li><li>备份:InnoDB支持在线热备份;</li><li>崩溃恢复:MyISAM崩溃后发生损坏的概率比InnoDB高很多,而且恢复的速度也更慢;</li><li>其他特性:MyISAM支持压缩表和空间数据索引;</li></ul><h4 id="6-数据类型"><a href="#6-数据类型" class="headerlink" title="6. 数据类型"></a>6. 数据类型</h4><h5 id="整型"><a href="#整型" class="headerlink" title="整型"></a>整型</h5><p>TinyInt:8位存储空间<br>SmalInt:16位<br>MediumInt:24位<br>Int:32位<br>BigInt:64位<br>一般越小的列越好。<br>Int(11)中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。</p><h5 id="浮点数"><a href="#浮点数" class="headerlink" title="浮点数"></a>浮点数</h5><p>Float和Double浮点类型;<br>Decimal:高精度的小数类型;<br>CPU原生支持浮点运算,但不支持Decimal类型的计算,因此Decimal的计算比浮点类型需要更高的代价。<br>Float、Double、Decimal都可以指定列宽。<br>Decimal(18,9)表示总共18位,取9位存储小数部分,剩余9位存储整数部分。</p><h5 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h5><p>主要有Char和Varchar两种类型,一种定长,一种变长。<br>Varchar:变长类型,节省空间,只需要存储必要的内容。<br>但在执行Update时可能会使行变得比原来长,当超出一个页能够容纳的大小时,就要执行额 外的操作。MyISAM会将行拆成不同的片段存储,而InnoDB则需要分裂页来使行放进页内。</p><p>在进行检索和存储时,会保留Varchar末尾的空格,而会删除Char末尾的空格。</p><h5 id="时间和日期"><a href="#时间和日期" class="headerlink" title="时间和日期"></a>时间和日期</h5><p>Datetime和Timestamp</p><ul><li><p>Datetime:<br> 能够保存从1000年到9999年的日期和时间,精度为秒,使用8字节的存储空间。<br> 它与时区无关。<br> 默认情况下,Mysql以一种可排序的、无歧义的格式显示Datetime值,是ANSI标椎定义的日期和时间表示法。</p></li><li><p>Timestamp:<br> 和UNIX时间戳相同,保存从1970年1月1日午夜以来的秒数,使用4个字节,只能表示从1970到2038年。<br> 它和时区有关,即一个时间戳在不同的时区所代表的具体时间是不同的。<br> Mysql提供了From_unixtime()函数把Unix时间戳转化为日期,Unix_Timestamp<br> ()函数把日期转换为UNIX时间戳。<br> 默认情况下,如果插入时没有指定Timestamp列的值,会将这个值设置为当前时间。<br> 应该尽量使用Timestamp,比Datetime空间效率更高。</p></li></ul><h4 id="7-切分"><a href="#7-切分" class="headerlink" title="7. 切分"></a>7. 切分</h4><h5 id="水平切分"><a href="#水平切分" class="headerlink" title="水平切分"></a>水平切分</h5><p>水平切分:Sharding<br>是将同一个表中的记录拆分到多个结构相同的表中。</p><p>当一个表中的数据不断增多时,Sharding是必然的选择,它可以将数据分布到集群的不同节点上,<br>从而缓存单个数据库的压力。</p><h5 id="垂直切分"><a href="#垂直切分" class="headerlink" title="垂直切分"></a>垂直切分</h5><p>垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,<br>也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。</p><p>在数据库的层面,使用垂直切分将按数据库中表的密集程度部署到不同的库中。</p><h5 id="Shaeding策略"><a href="#Shaeding策略" class="headerlink" title="Shaeding策略"></a>Shaeding策略</h5><p>哈希取模:hash(key)%N<br>范围:可以是ID范围也可以是时间范围<br>映射表:使用单独的一个数据库来存储映射关系</p><h5 id="Sharding存在的问题"><a href="#Sharding存在的问题" class="headerlink" title="Sharding存在的问题"></a>Sharding存在的问题</h5><ol><li>事务问题<br> 使用分布式事务来解决,比如XA接口</li><li>连接<br> 可以将原来的连接分解成多个单表查询,然后在用户程序中进行连接。</li><li>ID唯一性<br> 使用全局唯一ID(GUID)<br> 为每个分片指定一个ID范围<br> 分布式ID生成器</li></ol><h4 id="8-复制"><a href="#8-复制" class="headerlink" title="8. 复制"></a>8. 复制</h4><h5 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h5><p>主要涉及三个线程:binlog线程、I/O线程和Sql线程<br>binlog线程:<br>负责将主服务器上的数据更改写入二进制日志(Binary log)中<br>I/O线程:<br>负责从主服务器上读取二进制日志,并写入从服务器的中继日志(Relay log)<br>Sql线程:<br>负责读取中继日志,解析出主服务器已经执行的数据更改,并在从服务器中重放(Replay)。</p><h5 id="读写分离"><a href="#读写分离" class="headerlink" title="读写分离"></a>读写分离</h5><p>主服务器处理写操作以及实时性要求比较高的读操作,而从服务器处理读操作。<br>读写分离能够提高性能的原因在于:<br>主从服务器负责各自的读和写,极大程度缓解了锁的争用;<br>从服务器可以使用MyISAM,提升查询性能及节约系统开销;<br>增加冗余,提高可用性;</p><p>读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器</p>]]></content>
<summary type="html">
<h4 id="1-索引"><a href="#1-索引" class="headerlink" title="1. 索引"></a>1. 索引</h4><h5 id="B-Tree原理"><a href="#B-Tree原理" class="headerlink" title="B+ Tree原理"></a>B+ Tree原理</h5><ol>
<li>数据结构<br>B Tree (Balance Tree,平衡树):查找树,且所有叶子节点位于同一层。<br>B+ Tree是基于B Tree和叶子节点顺序访问指针进行实现的,它具有B Tree的平衡性,通过顺序访问指针提高区间查询性能。</li>
</ol>
<p>一个节点中的key从左到右非递减排列,key左右是key(i)、key(i+1),且不为null,则该指针指向节点的所有key大于等于key(i)且小于等于key(i+1)。</p>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Mysql" scheme="http://gpp-note.top/tags/Mysql/"/>
</entry>
<entry>
<title>Http-Net</title>
<link href="http://gpp-note.top/articles/2020-02-11-Http-Net.html"/>
<id>http://gpp-note.top/articles/2020-02-11-Http-Net.html</id>
<published>2020-02-11T09:48:35.000Z</published>
<updated>2020-02-11T10:20:40.701Z</updated>
<content type="html"><![CDATA[<h4 id="1-主机之间的通信方式"><a href="#1-主机之间的通信方式" class="headerlink" title="1. 主机之间的通信方式"></a>1. 主机之间的通信方式</h4><p>客户-服务器(C/S):<br> 客户是服务的请求方,服务器是服务的提供方。</p><p>对等(P2P):<br> 不区分客户和服务器</p><a id="more"></a><h4 id="2-计算机网络体系结构"><a href="#2-计算机网络体系结构" class="headerlink" title="2. 计算机网络体系结构"></a>2. 计算机网络体系结构</h4><p>OSI:应用层、表示层、会话层、运输层、网络层、数据链路层、物理层<br>五层协议:应用层、运输层、网络层、数据链路层、物理层<br>TCP/IP:应用层、运输车、网际层、网络接口层</p><ul><li>应用层 :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等协议。数据单位为报文。</li><li>传输层 :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。</li><li>网络层 :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。</li><li>数据链路层 :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。</li><li>物理层 :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。</li><li>表示层 :数据压缩、加密以及数据描述,这使得应用程序不必关心在各台主机中数据内部格式不同的问题。</li><li>会话层 :建立及管理会话。</li></ul><h4 id="3-HTTP"><a href="#3-HTTP" class="headerlink" title="3. HTTP"></a>3. HTTP</h4><ul><li><p>URI<br>包括URL和URN</p></li><li><p>请求和响应报文</p></li><li><p>HTTP方法<br>客户端发送的请求报文第一行为请求行,包括了方法字段</p></li><li><p>HTTP状态码<br>1XX信息:<br> 到目前为止都很正常,客户端可以继续发送请求或忽略这个响应</p></li></ul><p>2XX成功:<br> 200 ok<br> 204 No Content:<br> 请求已成功处理,但返回的响应报文不包含实体的主体部分。<br> 一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。<br> 206 Partial Content:<br> 表示客户端进行了范围请求,响应报文包含由Content-Range指定范围的实体内容。</p><p>3XX重定向<br> 301 Moved Permanently:永久性重定向<br> 302 Found:临时性重定向<br> 303 See Other:和302有相同的功能,但303明确要求客户端应该采用GET方法获取资源<br> 304 Not Modified:如果请求报文首部包含一些条件,如:If-Match,如果不满足,服务器会返回304状态码<br> 307 Temporary Redirect:临时重定向,要求浏览器不会把重定向请求的POST改成get方法。</p><p>4XX客户端错误<br> 400 Bad Request:请求报文中存在语法错误<br> 401 Unauthorized:该状态码表示发送的请求需要有认证信息,如果之前已进行过一次请求,则表示用户认证失败。<br> 403 Forbidden:请求被拒绝<br> 404 Not Found</p><p>5XX服务器错误<br> 500 Internal Server Error:服务器正在执行请求时发生错误<br> 503 Service Unavailable:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。</p><ul><li>连接管理<br>1、短连接与长连接<br>长连接只需要建立一次TCP连接就能进行多次HTTP通信<br>从Http/1.1 开始默认是长连接,如果断开连接,需要由客户端或服务端提出断开,使用Connection:close<br>在Http/1.1之前默认是短连接,如果需要使用长连接,则使用Connection:Keep-Alive</li></ul><p>2、流水线<br>默认情况下,Http请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。<br>由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。</p><p>流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。</p><p>3、Cookie<br>Http协议是无状态的,主要是为了让Http协议尽可能简单,使得它能够处理大量事务。<br>http1.1引入Cookie来保存状态信息</p><p>Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,<br>用于告知服务端两个请求是否来自同一浏览器。</p><p>Cookie会带来额外的性能开销,已渐渐被淘汰,新的浏览器API已经允许开发者直接将数据存储到本地。</p><p>用途:<br> 会话状态管理(如用户登录、购物车、游戏分数或其它需要记录的信息)<br> 个性化设置(如用户自定义设置、主题)<br> 浏览器行为跟踪(如跟踪分析用户行为)</p><p>创建过程:<br> 服务器发送的响应报文包含Set-Cookie首部字段,客户端得到响应报文后把Cookie内容保存到浏览器中<br> 客户端之后对同一个服务器发送请求时,会从浏览器中取出Cookie信息,并通过Cookie请求首部字段发送服务器</p><p>分类<br> 会话期 Cookie:浏览器关闭之后会自动删除,也就是说它仅在会话期内有效<br> 持久性 Cookie:指定过期时间Expires或有效期max-age之后就成为了持久性的Cookie</p><p>作用域:<br> Domain标识指定了哪些主机可以接受Cookie,如果不指定,默认当前文档的主机(不包含子域名)<br> 如果指定了domain,则一般包含子域名。</p><p>4、Session<br>利用Session存储在服务端,存储在服务器的信息更加安全<br>可以存储在服务器上的文件、数据库或者内存中,也可以将Session存储在Redis</p><p>过程:<br> 用户进行登录时,用户提交包含用户名和密码的表单,放入Http请求报文中<br> 服务器验证该用户名和密码,正确,存储到Redis<br> 服务器返回响应报文的Set-Cookie首部字段包含了这个SessionId,客户端收到响应报文后存储Cookie值到浏览器<br> 客户端之后对同一个服务器请求时会包含该Cookie值,服务器收到之后提取出SessionId,从Redis取出用户信息,继续之前业务操作</p><p>5、Cookie与Session选择<br>Cookie只能存储ASCII码字符串,而Session则可以存储任何类型的数据,考虑数据复杂性时首选Session<br>Cookie存储在浏览器中,容易被恶意查看,如非要将隐私数据存在Cookie,可以将Cookie进行加密,然后再服务器解密<br>大型网站,用户所有的信息都存储到Session中,开销大,不建议</p><p>6、缓存<br>优点:<br> 缓解服务器压力<br> 降低客户端获取资源的延迟:<br> 缓存通常位于内存中,读取缓存的速度更快。<br> 并且缓存服务器在地理位置上也有可能比源服务器来的近</p><p>实现方法:<br> 让代理服务器进行缓存<br> 让客户端浏览器进行缓存</p><p>Cache-Control<br>Http/1.1通过Cache-Control首部字段来控制缓存</p><p>禁止进行缓存:<br> no-store指令规定不能对请求或相应的任何一部分进行缓存(cache-control:no-store)</p><p>强制确认缓存:<br> no-cache规定缓存服务器需要先向源服务器验证缓存资源的有效性,<br> 只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。<br> Cache-Control: no-cache</p><p>私有缓存和公共缓存:<br> private规定将资源作为私有缓存,只能被单独用户使用,一般存储在用户浏览器中<br> Cache-Control: private</p><p>public规定将资源作为公共缓存,可以被多个用户使用,一般存储在代理服务器中<br>Cache-Control: public</p><p>缓存过期机制<br>max-age出现在请求报文,并且缓存资源的缓存时间小于该指令指定的时间,就能接受该缓存<br>max-age出现在响应报文,表示缓存资源在缓存服务器中保存的时间<br>Cache-Control: max-age=31536000</p><p>expires首部字段也可以用于告知缓存服务器该资源什么时候过期<br>Expires:Wed, 04 Jul 2012 08:23:05 GMT</p><p>在Http/1.1中,会优先处理max-age指令<br>在Http/1.0中,max-age指令会被忽略掉</p><p>缓存验证</p><p>7、通信数据转发</p><p>代理<br>代理服务器接受客户端的请求,并且转发给其它服务器</p><p>目的:<br> 缓存<br> 负载均衡<br> 网络访问控制<br> 访问日志记录</p><p>分为正向代理和反向代理:<br> 用户察觉得到正向代理的存在<br> 而反向代理一般位于内部网络中,用户察觉不到</p><p>网关:<br>与代理服务器不同的是,网关服务器将HTTP转化为其它协议进行通信,从而请求其它非Http服务器的服务。</p><p>隧道:<br>使用SSL等加密手段,在客户端和服务器之间建立一条安全的通信线路。</p><p>8、HTTPS<br>Http有以下安全问题:<br> 使用明文进行通信,内容可能被窃听;<br> 不验证通信方的身份,通信方的身份有可能遭遇伪装;<br> 无法证明报文的完整性,报文有可能遭篡改;</p><p>HTTPS并不是新协议,而是让HTTP先和SSL(Secure Sockets Layer)通信,<br>再由SSL和TCP通信,也就是说HTTPS使用了隧道进行通信。</p><p>通过使用SSL,HTTPS具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)</p><p>缺点:<br> 需要进行加密解密等过程,速度慢<br> 需要支付证书授权的高额费用</p><p>9、HTTP/2.0</p><p>HTTP/1.X缺陷:<br> 客户端需要使用多个连接才能实现并发和缩短延迟;<br> 不会压缩请求和响应首部,从而导致不必要的网络流量;<br> 不支持有效的资源优先级,致使底层TCP连接的利用率低下;</p><p>二进制分帧层:<br> HTTP/2.0将报文分成Headers帧和Data帧,它们都是二进制格式的,在通信过程中,只会有<br> 一个TCP连接存在,它承载了任意数量的双向数据流(Stream)<br> 一个数据流Stream:都有一个唯一标识符和可选的优先级消息,用于承载双向消息;<br> 消息Message:是与逻辑请求或响应对应的完成的一系列帧。</p><p>服务端推送:<br>HTTP/2.0在客户端请求一个资源时,会把相关的资源一起发送给客户端端,客户端不需要再次发起请求了。</p><p>首部压缩:<br> HTTP/1.1首部带有大量信息,而且每次都要重复发送;<br> HTTP/2.0要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。</p><p>10、HTTP/1.1新特性<br> 默认长连接<br> 支持流水线<br> 支持同时打开多个TCP连接<br> 支持虚拟主机<br> 新增状态码100<br> 支持分块传输编码<br> 新增缓存处理指令max-age</p><h4 id="4-GET和POST"><a href="#4-GET和POST" class="headerlink" title="4. GET和POST"></a>4. GET和POST</h4><ul><li><p>作用<br> get用户获取资源;<br> post用于传输实体主体</p></li><li><p>参数<br> get参数是以查询字符串出现在URL中,url只支持ASCII码,所以Get的参数中如果存在中文字符需要进行编码<br> post存储在实体主体中</p></li><li><p>安全<br> 安全的HTTP方法不会改变服务器状态,即它只是可读的。</p><p> get是安全的,post不是,因为post的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,<br> 上传成功之后,服务器可能是把这个数据存储到数据库中,因此状态发生了改变。</p><p> 安全方法:get、Head、Options<br> 不安全:post、put、delete</p></li><li><p>幂等性<br> 幂等的HTTP方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。<br> 幂等方法不应该具有副作用</p><p> 所有的安全方法也都是幂等的。</p><p> 在正确实现条件下,get、head、put和delete方法都是幂等的,post不是。</p></li><li><p>可缓存<br> 如果要对响应进行缓存,需要满足:<br> 请求报文的http方法本身是可缓存的,包括Get和Head,但Put和Delete不可缓存,post多数情况下不可缓存;<br> 响应报文的状态码是可缓存的;<br> 响应报文的Cache-Control首部字段没有指定不进行缓存;</p></li></ul>]]></content>
<summary type="html">
<h4 id="1-主机之间的通信方式"><a href="#1-主机之间的通信方式" class="headerlink" title="1. 主机之间的通信方式"></a>1. 主机之间的通信方式</h4><p>客户-服务器(C/S):<br> 客户是服务的请求方,服务器是服务的提供方。</p>
<p>对等(P2P):<br> 不区分客户和服务器</p>
</summary>
<category term="计算机网络" scheme="http://gpp-note.top/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
<category term="Net" scheme="http://gpp-note.top/tags/Net/"/>
</entry>
<entry>
<title>Tcp</title>
<link href="http://gpp-note.top/articles/2020-02-11-Http-Tcp.html"/>
<id>http://gpp-note.top/articles/2020-02-11-Http-Tcp.html</id>
<published>2020-02-11T09:34:15.000Z</published>
<updated>2020-02-11T10:20:40.701Z</updated>
<content type="html"><![CDATA[<h4 id="1-三次握手过程"><a href="#1-三次握手过程" class="headerlink" title="1. 三次握手过程"></a>1. 三次握手过程</h4><ul><li>B处于Listen(监听)状态,等待来自A的连接请求;</li><li>A向B发送连接请求报文Syn;</li><li>B收到A发来的Syn,如果同意建立连接,则向A发送连接确认报文Syn Ack;</li><li>A收到Syn Ack后,还要向B发出确认报文Ack;</li><li>B收到Ack后,建立连接;</li></ul><a id="more"></a><p>接收到Syn后,所有的报文都存在Ack字段</p><h5 id="三次握手的原因"><a href="#三次握手的原因" class="headerlink" title="三次握手的原因"></a>三次握手的原因</h5><p>第三次握手是为了防止失效的链接请求到服务器,让服务器错误打开连接。</p><p>客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务发回的连接确认。<br>客户端等待一个超时重传时间之后,就会重新请求连接。<br>这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。<br>如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。</p><h4 id="2-四次挥手过程"><a href="#2-四次挥手过程" class="headerlink" title="2. 四次挥手过程"></a>2. 四次挥手过程</h4><ul><li>A发送连接释放报文FIN</li><li>B收到FIN后发出确认ACK,此时TCP属于半关闭状态,B能向A发送数据但是A不能向B发送数据</li><li>当B不再需要连接时,发送连接释放报文FIN</li><li>A收到FIN后发出确认ACK,进入Time-wait状态,等待2倍的MSL(最大报文存活时间)后释放连接</li><li>B收到ACK后释放连接</li></ul><h5 id="四次挥手的原因"><a href="#四次挥手的原因" class="headerlink" title="四次挥手的原因"></a>四次挥手的原因</h5><p>客户端发送了FIN连接释放报文之后,服务端收到了这个报文,进入Close-wait状态<br>这个状态是为了让服务器端发送还未传送完毕的数据,<br>传送完毕之后,服务器会发送FIN连接释放报文。</p><h4 id="Time-wait"><a href="#Time-wait" class="headerlink" title="Time-wait"></a>Time-wait</h4><p>客户端接收到服务器端的FIN报文之后进入此状态,而不是直接Closed状态,<br>还需要等待一个时间计时器设置的时间2MSL,原因是:</p><ul><li><p>确保最后一个确认报文ACK能够到达。<br> 如果服务器端没收到客户端发送来的确认报文ACK,那么需要重新发送连接释放请求FIN报文,<br>客户端等待一段时间就是为了处理这种情况的发生。</p></li><li><p>等待一段时间是为了让本次连接持续时间内产生的所有报文都从网络中消失,<br> 使得下一个新的连接不会出现旧的连接请求报文Syn,从而错误打开连接。</p></li></ul>]]></content>
<summary type="html">
<h4 id="1-三次握手过程"><a href="#1-三次握手过程" class="headerlink" title="1. 三次握手过程"></a>1. 三次握手过程</h4><ul>
<li>B处于Listen(监听)状态,等待来自A的连接请求;</li>
<li>A向B发送连接请求报文Syn;</li>
<li>B收到A发来的Syn,如果同意建立连接,则向A发送连接确认报文Syn Ack;</li>
<li>A收到Syn Ack后,还要向B发出确认报文Ack;</li>
<li>B收到Ack后,建立连接;</li>
</ul>
</summary>
<category term="计算机网络" scheme="http://gpp-note.top/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
<category term="Net" scheme="http://gpp-note.top/tags/Net/"/>
</entry>
<entry>
<title>ES6之class</title>
<link href="http://gpp-note.top/articles/2020-02-11-ES-class.html"/>
<id>http://gpp-note.top/articles/2020-02-11-ES-class.html</id>
<published>2020-02-11T09:31:07.000Z</published>
<updated>2020-02-11T10:20:40.701Z</updated>
<content type="html"><![CDATA[<h4 id="1-class"><a href="#1-class" class="headerlink" title="1. class"></a>1. class</h4><p>constructor定义构造方法,this关键字代表实例对象。<br>类的一般方法都定义在类的prototype属性上面。<br>类的实例上面调用方法,其实就是调用原型上的方法。<br>类的内部所有定义的方法,都是不可枚举的。<br>类的静态方法只能用类类调用,不能用类的实例调用。<br>如果在实例上调用静态方法,会抛出错误,表示不存在该方法。<br>父类的静态方法,可以被子类继承。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">class Point {</span><br><span class="line"> constructor(){</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line"> toString(){</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line"> toValue(){</span><br><span class="line"> // ...</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 等同于</span><br><span class="line">Point.prototype = {</span><br><span class="line"> toString(){},</span><br><span class="line"> toValue(){}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h4 id="2-Constructor方法"><a href="#2-Constructor方法" class="headerlink" title="2. Constructor方法"></a>2. Constructor方法</h4><p>类的默认方法,通过new命令生成对象实例时,自动调用该方法。<br>默认返回实例对象this</p><h4 id="3-类的实例对象"><a href="#3-类的实例对象" class="headerlink" title="3. 类的实例对象"></a>3. 类的实例对象</h4><p>生成类的实例对象的写法,也是使用new命令</p><p>class不存在变量提升,因此先使用,后定义会报错</p><h4 id="4-this指向"><a href="#4-this指向" class="headerlink" title="4. this指向"></a>4. this指向</h4><p>类的方法内部的this,默认指向类的实例。<br>如果静态方法包含this,指的是类,不是实例</p><h4 id="5-Name属性"><a href="#5-Name属性" class="headerlink" title="5. Name属性"></a>5. Name属性</h4><p>返回紧跟在class关键字后面的类名</p><h4 id="6-class的继承"><a href="#6-class的继承" class="headerlink" title="6. class的继承"></a>6. class的继承</h4><p>通过extends关键字实现继承<br>子类必须在constructor方法中调用super方法,否则新建实例报错<br>子类没有自己的this对象,而是继承父类的this对象,并对其加工。<br>不调用super方法,子类就得不到this对象</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">class ColorPoint extends Point {</span><br><span class="line"> constructor(x, y, color) {</span><br><span class="line"> super(x, y); // 调用父类的constructor(x, y)</span><br><span class="line"> this.color = color;</span><br><span class="line"> }</span><br><span class="line"> toString() {</span><br><span class="line"> return this.color + ' ' + super.toString(); // 调用父类的toString()</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="7-类的prototype属性和-proto-属性"><a href="#7-类的prototype属性和-proto-属性" class="headerlink" title="7. 类的prototype属性和_proto_属性"></a>7. 类的prototype属性和_proto_属性</h4><p>class同时有prototype属性和_proto_属性,因此同时存在两条继承链。<br>子类的_proto_属性,表示构造函数的继承,总是指向父类<br>子类的prototype属性的_proto_属性,总是指向父类的prototype属性</p><h4 id="8-Object-getPrototypeOf"><a href="#8-Object-getPrototypeOf" class="headerlink" title="8. Object.getPrototypeOf()"></a>8. Object.getPrototypeOf()</h4><p>用来从子类上获取父类,判断一个类是否继承了另一个类</p><h4 id="9-super关键字"><a href="#9-super关键字" class="headerlink" title="9. super关键字"></a>9. super关键字</h4><p>可以当函数,也可以当对象使用<br>必须显式指定作为函数使用还是作为对象<br>作函数时:代表父类的构造函数,且super()只能用在子类的构造函数中,用在其他地方报错。<br>作为对象时:在普通方法中,指向父类的原型对象;在静态方法中,指向父类。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">class A {</span><br><span class="line"> p() {</span><br><span class="line"> return 2;</span><br><span class="line"> }</span><br><span class="line"> static m() {</span><br><span class="line"> console.log("父类的m方法被调用")</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> class B extends A {</span><br><span class="line"> constructor() {</span><br><span class="line"> super();</span><br><span class="line"> console.log(super.p()); // 2 super.p()在普通方法中,指向A.prototype</span><br><span class="line"> }</span><br><span class="line"> static show() {</span><br><span class="line"> super.m(); // 在静态方法中,相当于A.m()</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> let b = new B();</span><br><span class="line"> B.show(); //父类的m方法被调用</span><br></pre></td></tr></table></figure><h4 id="10-实例的-proto-属性"><a href="#10-实例的-proto-属性" class="headerlink" title="10. 实例的_proto_属性"></a>10. 实例的_proto_属性</h4><p>子类实例的_proto_属性的_proto_属性,指向父类实例的_proto_属性。</p><p>Class的取值函数getter和存值函数setter</p><h4 id="11-Class的静态方法"><a href="#11-Class的静态方法" class="headerlink" title="11. Class的静态方法"></a>11. Class的静态方法</h4><p>在一个方法前,加static关键字<br>静态方法不会被实例继承,而是通过类来调用。<br>静态方法包含this,指向的是类,而不是实例。<br>静态方法可以和非静态方法重名。<br>父类的非静态方法可以被子类继承。</p><h4 id="12-Class的静态属性和实例属性"><a href="#12-Class的静态属性和实例属性" class="headerlink" title="12. Class的静态属性和实例属性"></a>12. Class的静态属性和实例属性</h4><p>静态属性指的是Class本身的属性,即class.propname,而不是定义在实例对象this上的属性。</p><h4 id="13-new-target属性"><a href="#13-new-target属性" class="headerlink" title="13. new.target属性"></a>13. new.target属性</h4><p>在构造函数中,返回new命令作用于的那个构造函数,如果构造函数不是通过new调用的,<br>new.target会返回undefined,<br>可以确定该构造函数是怎么调用的。</p>]]></content>
<summary type="html">
<h4 id="1-class"><a href="#1-class" class="headerlink" title="1. class"></a>1. class</h4><p>constructor定义构造方法,this关键字代表实例对象。<br>类的一般方法都定义在类的prototype属性上面。<br>类的实例上面调用方法,其实就是调用原型上的方法。<br>类的内部所有定义的方法,都是不可枚举的。<br>类的静态方法只能用类类调用,不能用类的实例调用。<br>如果在实例上调用静态方法,会抛出错误,表示不存在该方法。<br>父类的静态方法,可以被子类继承。</p>
</summary>
<category term="ES6" scheme="http://gpp-note.top/categories/ES6/"/>
<category term="ES6" scheme="http://gpp-note.top/tags/ES6/"/>
</entry>
<entry>
<title>ES6之asyncAwait</title>
<link href="http://gpp-note.top/articles/2020-02-11-ES-asyncAwait.html"/>
<id>http://gpp-note.top/articles/2020-02-11-ES-asyncAwait.html</id>
<published>2020-02-11T09:30:10.000Z</published>
<updated>2020-02-11T10:20:40.700Z</updated>
<content type="html"><![CDATA[<h4 id="1-async"><a href="#1-async" class="headerlink" title="1. async"></a>1. async</h4><p>async函数返回一个Promise对象,可以使用then方法添加回调函数。<br>当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。</p><a id="more"></a><p>async函数返回的Promise对象,必须等内部所有的await命令后面的Promise对象执行完,<br>才会发生状态改变,除非遇到return语句或抛出错误。<br>即只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。</p><p>async是Generator函数和自动执行器,包装在一个函数里。</p><h4 id="2-await"><a href="#2-await" class="headerlink" title="2. await"></a>2. await</h4><p>await ecpression:会造成异步函数停止执行并且等待Promise的解决后再恢复执行。<br>若 ecpression是Promise对象,则返回 ecpression的[[PromiseValue]]值,<br>否则,直接返回 ecpression</p><h4 id="3-异步Generator函数"><a href="#3-异步Generator函数" class="headerlink" title="3. 异步Generator函数"></a>3. 异步Generator函数</h4><p>异步Generator函数就是async函数与Generator函数的结合</p>]]></content>
<summary type="html">
<h4 id="1-async"><a href="#1-async" class="headerlink" title="1. async"></a>1. async</h4><p>async函数返回一个Promise对象,可以使用then方法添加回调函数。<br>当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。</p>
</summary>
<category term="ES6" scheme="http://gpp-note.top/categories/ES6/"/>
<category term="ES6" scheme="http://gpp-note.top/tags/ES6/"/>
</entry>
<entry>
<title>ES6之Promise</title>
<link href="http://gpp-note.top/articles/2020-02-11-ES-Promise.html"/>
<id>http://gpp-note.top/articles/2020-02-11-ES-Promise.html</id>
<published>2020-02-11T09:27:32.000Z</published>
<updated>2020-02-11T10:20:40.694Z</updated>
<content type="html"><![CDATA[<h4 id="1-Promise对象的特点"><a href="#1-Promise对象的特点" class="headerlink" title="1. Promise对象的特点"></a>1. Promise对象的特点</h4><p>三种状态:</p><ul><li>Pending</li><li>Resolved(Fulfilled)</li><li>Rejected<br>一旦状态改变,不会再变,任何时候都可以得到这个结果</li></ul><p>状态只有两种改变:</p><ul><li>Pending到Resolver</li><li>Pending到Rejected<br>如果改变已经发生,对Promise对象添加回调函数,也会立即得到这个结果<br>但事件Event不同,如果错过了,再去监听,是得不到结果的<br>状态一改变,即调用Promise对象的then方法</li></ul><a id="more"></a><h5 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h5><p>Promise一旦新建就会立即执行,无法中途取消<br>如果不设置回调函数,Promise内部抛出的错误,不会反应到外部<br>当处于Pending状态时,无法得知目前进展到哪个阶段</p><h4 id="2-Promise-all"><a href="#2-Promise-all" class="headerlink" title="2. Promise.all()"></a>2. Promise.all()</h4><pre><code>当在可迭代参数中所有Promise倍resolve,或任意一Promise被reject时,返回一个新的promise</code></pre><h4 id="3-Promise-race"><a href="#3-Promise-race" class="headerlink" title="3. Promise.race()"></a>3. Promise.race()</h4><p>返回一个新的异步的promise,只要有一个完成或失败,新的promise就会立刻完成或失败,<br>并得到那个promise对象的返回值或错误原因</p><p>参数为空,返回的蝾螈都是pending状态</p><h4 id="4-Promise-resolve"><a href="#4-Promise-resolve" class="headerlink" title="4. Promise.resolve()"></a>4. Promise.resolve()</h4><p>返回一个Promise对象</p><h4 id="5-Promise-reject"><a href="#5-Promise-reject" class="headerlink" title="5. Promise.reject()"></a>5. Promise.reject()</h4><p>返回一个新的Promise实例,状态为rejected,因此回调函数会立即执行</p>]]></content>
<summary type="html">
<h4 id="1-Promise对象的特点"><a href="#1-Promise对象的特点" class="headerlink" title="1. Promise对象的特点"></a>1. Promise对象的特点</h4><p>三种状态:</p>
<ul>
<li>Pending</li>
<li>Resolved(Fulfilled)</li>
<li>Rejected<br>一旦状态改变,不会再变,任何时候都可以得到这个结果</li>
</ul>
<p>状态只有两种改变:</p>
<ul>
<li>Pending到Resolver</li>
<li>Pending到Rejected<br>如果改变已经发生,对Promise对象添加回调函数,也会立即得到这个结果<br>但事件Event不同,如果错过了,再去监听,是得不到结果的<br>状态一改变,即调用Promise对象的then方法</li>
</ul>
</summary>
<category term="ES6" scheme="http://gpp-note.top/categories/ES6/"/>
<category term="ES6" scheme="http://gpp-note.top/tags/ES6/"/>
</entry>
<entry>
<title>ES6之SetMap</title>
<link href="http://gpp-note.top/articles/2020-02-11-ES-SetMap.html"/>
<id>http://gpp-note.top/articles/2020-02-11-ES-SetMap.html</id>
<published>2020-02-11T09:25:50.000Z</published>
<updated>2020-02-11T10:20:40.694Z</updated>
<content type="html"><![CDATA[<h4 id="1-Set"><a href="#1-Set" class="headerlink" title="1. Set"></a>1. Set</h4><p>本身是一个构造函数,生成set数据结构。<br>接受一个数组作为参数,用来初始化。<br>去除数组的重复成员。<br>遍历顺序是插入顺序。<br>没有键名,只有键值。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const set = new Set([1, 2, 3, 4, 4]);</span><br><span class="line">[...set]</span><br><span class="line">// [1, 2, 3, 4]</span><br></pre></td></tr></table></figure><h4 id="2-WeakSet"><a href="#2-WeakSet" class="headerlink" title="2. WeakSet"></a>2. WeakSet</h4><p>构造函数,可以使用new命令,创建WeakSet数据结构<br>接收数组或类似数组的对象作为参数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const a = [[1, 2], [3, 4]];</span><br><span class="line">const ws = new WeakSet(a);</span><br><span class="line">// WeakSet {[1, 2], [3, 4]}</span><br></pre></td></tr></table></figure><p>WeakSet的成员只能是对象,而不能是其它类型的值;<br>WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,<br>即:如果其它对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用内存,<br>不考虑该对象还存在于WeakSet之中。<br>WeakSet没有size属性,不可遍历。</p><h4 id="3-Map"><a href="#3-Map" class="headerlink" title="3. Map"></a>3. Map</h4><p>键值对的集合(Hash结构)<br>map键是对象类型的,内存地址相同才相同<br>map键是简单类型的,两个值严格相等视为一个键,0和-0是同一个<br>map键将NaN和其自身视为同一个键<br>遍历顺序是插入顺利</p><h4 id="4-WeakMap"><a href="#4-WeakMap" class="headerlink" title="4. WeakMap"></a>4. WeakMap</h4><p>WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名<br>WeakMap的键名所指向的对象,不计入垃圾回收机制</p><p>WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。<br>因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。<br>即一旦不再需要,WeakMap里面的键名对象和对应的键值就会自动消失,不用手动删除引用。</p><p>WeakMap有助于防止内存泄漏</p>]]></content>
<summary type="html">
<h4 id="1-Set"><a href="#1-Set" class="headerlink" title="1. Set"></a>1. Set</h4><p>本身是一个构造函数,生成set数据结构。<br>接受一个数组作为参数,用来初始化。<br>去除数组的重复成员。<br>遍历顺序是插入顺序。<br>没有键名,只有键值。</p>
</summary>
<category term="ES6" scheme="http://gpp-note.top/categories/ES6/"/>
<category term="ES6" scheme="http://gpp-note.top/tags/ES6/"/>
</entry>
<entry>
<title>Redis</title>
<link href="http://gpp-note.top/articles/2020-02-11-dataBase-Redis.html"/>
<id>http://gpp-note.top/articles/2020-02-11-dataBase-Redis.html</id>
<published>2020-02-11T09:22:39.000Z</published>
<updated>2020-04-25T13:12:46.023Z</updated>
<content type="html"><![CDATA[<h2 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h2><p>一个开源的,内存中的数据结构存储系统;<br>用作数据库、缓存和消息中间件;</p><a id="more"></a><h4 id="1-介绍"><a href="#1-介绍" class="headerlink" title="1. 介绍"></a>1. 介绍</h4><p>Remote Dictionary Server,数据结构服务器。<br>完全开源、非关系型、高性能的key-value存储系统。<br>速度非常快的非关系型(Nosql)内存键值数据库,可以存储键和五种不同类型的值之间的映射。</p><p>Redis支持很多特性。如:将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片扩展写性能。<br>键的类型只能为字符串,值有五种类型:字符串、列表、集合、散列表、有序集合(string、list、set、zset和hash)。</p><h4 id="2-Redis的特点"><a href="#2-Redis的特点" class="headerlink" title="2. Redis的特点"></a>2. Redis的特点</h4><p>redis是单线程的,操作是安全的;<br>redis支持数据的持久化,可将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用;<br>redis支持key-value,并提供list、set、zset、hash等数据结构的存储;<br>redis支持数据的备份,即master-slave模式的数据备份;<br>redis单个value的最大限制1GB;</p><h4 id="3-五种数据类型介绍及使用场景"><a href="#3-五种数据类型介绍及使用场景" class="headerlink" title="3. 五种数据类型介绍及使用场景"></a>3. 五种数据类型介绍及使用场景</h4><p>应用场景:<br> 会话缓存;<br> 消息队列,比如支付;<br> 活动排行榜或计数;<br> 发布,订阅消息(消息通知);<br> 商品列表,评论列表等;</p><p>计数器<br>可以对string进行自增自减运算,从而实现计数器功能。<br>redis这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。</p><p>缓存<br>将热点数据放到内存中,设置内存的最大使用量,及淘汰策略来保证缓存的命中率。</p><p>查找表<br>如DNS记录就很适合使用Redis进行存储。</p><p>查找表和缓存类似,也是利用redis快速查找特性。<br>但查找表的内容不能失效,而缓存的内容可以失效,缓存不作为可靠的数据来源。</p><p>消息队列<br>list是一个双向链表,可以通过lpush和rpop写入和读取消息。</p><p>会话缓存<br>可以使用Redis来统一存储多台应用服务器的会话消息。<br>当应用服务器不再存储用户的会话消息,也就是不再具有状态,<br>一个用户可以请求任意一个应用服务器,从而容易实现高可用性及可伸缩性。</p><p>分布式锁实现<br>在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。</p><p>可以使用Redis自带的setnx命令实现分布式锁,除此之外,还可以使用RedLock分布式锁实现。</p><p>其它<br>set可以实现交集、并集等操作,从而实现共同好友的功能<br>zset可以有序性的操作,从而实现排行榜等功能。</p><p>1) String<br> key-value,可以包含任何数据,一个键最大能存储512MB。<br> 比如图片或者序列化的对象。</p><pre><code>set:设置存储在指定键中的值;set key value *get:获取存储在指定键中的值;get keydel:删除存储在指定键中的值;del key使用场景: incr 自增 ;eg:生成id; decr 减少;eg:库存; 计数器缓存; 缓存–过期时间设置,模拟session;使用案例:</code></pre><p>2) List<br> redis列表,按照插入顺序可以添加一个元素到列表的头部或尾部。</p><pre><code>rpush:将给定值推入列表的右端;rpush key value *lrange:获取列表在指定范围上的所有值;lrange key start stoplindex:获取列表在指定范围上的单个元素;lindex key indexlpop key使用场景: 多任务调度队列; 利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好; 微博发表文章,使关注的人都能看到(使用列表做一个消息队列); 生产者消费者模型(多线程);</code></pre><p>3)Set<br> string类型的无序集合。集合是通过哈希表实现的,增删查复杂度都是O(1)</p><pre><code>sadd:将给定元素添加到集合;sadd key valuesmembers:返回集合包含的所有元素;smembers keysismember:检查指定元素是否存在于集合中; srem key value使用场景: 微博关注数。 利用交集、并集、差集等操作,可以计算共同爱好,全部的喜好,自己独有的喜好等功能。</code></pre><p>4)hash<br> 键值对集合。是string类型的field和value的映射表,适用于存储对象。</p><pre><code>hset:散列里面关联起指定的键值对;hset key field valuehget:获取指定散列键的值;hgetall:散列包含的所有键值对;hgetall keyhdel:如果给定键存在于散列中,移出这个键;hdel key field使用场景: 购物车 单点登录,用这种数据结构存储用户信息,一cookie作为key,设置30分钟为缓存过期时间, 能很好地模拟类似session效果。</code></pre><p>5)zset<br> 集合,不允许重复的成员。每个元素都会关联一个double类型的分数。<br> redis通过分数来为集合中成员进行从小到大的排序。<br> zset的成员是唯一的,但分数却可以重复。</p><pre><code>zadd:将一个带有给定分值的成员添加到有序集合里面;zrange:根据元素在有序排列中所处的位置,从有序集合里面获取多个元素;zrangebyscore:获取有序集合在给定分值范围内的所有元素;zrem:如果指定成员存在于有序集合中,那么移出这个成员使用场景 排行榜,取Top N操作; 延时任务 范围查找</code></pre><h4 id="4-持久化"><a href="#4-持久化" class="headerlink" title="4. 持久化"></a>4. 持久化</h4><p>Redis是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。</p><p>两种方式:<br>1)rdb快照:<br> 将某个时间点的所有数据都存放到硬盘上;<br> 可以将快照复制到其它服务器,从而创建具有相同数据的服务器副本;<br> 如果系统发生故障,将会丢失最后一次创建快照之后的数据;<br> 如果数据量很大,保存快照的时间会很长;</p><pre><code>默认redis是会以快照的形式将数据持久化到磁盘(一个二进制文件,dump.rdb,这个文件名字可以指定),在配置文件(redis.conf)中的格式是:saveN M表示在N秒之内,redis至少发生M次修改则redis抓快照到磁盘。保存 900 1:900秒内如果超过1个key被修改,则启动快照保存;保存300 10:300秒内如果超过10个key被修改,则启动快照保存;保存60 10000:60秒内如果超过10000个重点被修改,则启动快照保存;</code></pre><p>2)aof持久化<br> 将写命令添加到AOF文件(Append Only File)的末尾。<br> 使用AOF持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。<br> 这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓存区,<br> 然后由操作系统决定什么时候同步磁盘,有以下选项:<br> * always 每个写命令都同步<br> * everysec 每秒同步一次<br> * no 让操作系统来决定何时同步</p><pre><code>always:选项会严重降低服务器的性能;everysec:选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且redis每秒执行一次同步对服务器性能几乎没有任何影响;no:选项并不能给服务器性能带来多大的提升,而且会增加系统崩溃时数据丢失的数量;随着服务器请求的增多,AOF文件会越来越大。Redis提供了一种将AOF重写的特性,能够去除AOF文件中的冗余写命令。使用aof持久时,服务将每个收到的写命令通过写函数追加到文件中(appendonly.aof)aof持久化存储方式参数说明: appendonly yes:开启aof持久化存储方式; appendfsync always:收到写命令后就立即写入磁盘,效率最差,效果最好; appendfsync everysec:每秒写入磁盘一次,效率与效果居中; appendfsync no:完全依赖操作系统,效果最佳,效果没法保证;</code></pre><h4 id="5-redis性能、并发"><a href="#5-redis性能、并发" class="headerlink" title="5. redis性能、并发"></a>5. redis性能、并发</h4><p>性能:<br> 在碰到需要执行耗时特别久,且结果不频繁变动的sql,特别适合将运行结果放入魂村,<br>这样后面的请求就去缓存中读取,使得请求能够迅速响应。</p><p>并发:<br> 在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。<br> 这个时候,需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。</p><h4 id="6-优点"><a href="#6-优点" class="headerlink" title="6. 优点"></a>6. 优点</h4><p>1)性能极高。<br> redis能读的速度是110000次/s,写的速度是81000次/s;<br>2)丰富的数据类型。<br> redis支持二进制案例的strings、lists、hashes、sets及ordered sets数据类型操作;<br>3)原子。<br> redis的所有操作都是原子性的,意思是要么成功要么失败完全不执行。<br> 单个操作都是原子性的,多个操作也支持事务,即原子性,通过Multi和Exec指令包起来。<br>4)丰富的特性。<br> redis支持publish、subscribe,通知,key过期等特性。</p><h4 id="7-缺点"><a href="#7-缺点" class="headerlink" title="7. 缺点"></a>7. 缺点</h4><p>1)缓存和数据库双写一致性问题<br>2)缓存雪崩问题<br>3)缓存击穿问题<br>4)缓存并发竞争问题</p><h4 id="8-单线程的redis为什么这么快?"><a href="#8-单线程的redis为什么这么快?" class="headerlink" title="8. 单线程的redis为什么这么快?"></a>8. 单线程的redis为什么这么快?</h4><p>redis是单线程工作模型<br>1)纯内存操作<br>2)单线程操作,避免了频繁的上下文切换<br>3)采用非阻塞的I/o多路复用机制</p><h4 id="9-redis的过期策略及内存淘汰机制"><a href="#9-redis的过期策略及内存淘汰机制" class="headerlink" title="9. redis的过期策略及内存淘汰机制"></a>9. redis的过期策略及内存淘汰机制</h4><p>eg:redis只能存5G数据,可是写了10G,那会删除5G的数据。怎么删除?<br> 你的数据设置了过期时间,但是时间到了,内存占用率还是比较高,原因是什么?</p><pre><code>--redis:采用的是定期删除+惰性删除策略。</code></pre><p>为什么不用定时删除策略?<br> 定时删除,用一个定时器来负责监视key,过期则自动删除。<br> 虽然内存即使释放,但是十分消耗CPU资源。<br> 在大并发情况下,CPU要将时间应用在处理请求,而不是删除key,因此没有用这一策略。</p><p>定期删除+惰性删除是如何工作的?<br> 定期删除,redis默认每个100ms检查,是否有过期的key,有则删除。<br> 需要说明的是,redis不是每个100ms将所有key检查一次,而是随机抽取检查。<br> 因此,如果只采用定期删除策略,会导致很多key到期时间没有删除。</p><pre><code>所以,惰性删除是在获取某个key的时候,redis会检查一遍,这个key如果设置了过期时间,那么是否过期了?如果过期了,此时就会删除。</code></pre><p>采用定期删除+惰性删除就没有其他问题了吗?<br> 如果定期删除没有删除key,然后你也没及时请求key,也就是说惰性删除也没生效。<br> 这样,redis的内存会越来越高,那么就应该采用内存淘汰机制。</p><h4 id="10-redis和数据库双写一致性的问题"><a href="#10-redis和数据库双写一致性的问题" class="headerlink" title="10. redis和数据库双写一致性的问题"></a>10. redis和数据库双写一致性的问题</h4><p>一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。<br>数据库和缓存双写,就必然会存在不一致的问题。<br>如果对数据有强一致性要求,不能放缓存。<br>我们所做的一切,只能保证最终一致性。另外,从根本来上说,只能说降低不一致发生的概率,无法完全避免。<br>因此,有强一致性的要求的数据,不能放缓存。</p><p>首先,采用正确更新策略,先更新数据库,再删除缓存,其次,因为可能存在删除缓存失败的问题,提供了一个补偿措施即可<br>如消息队列。</p><h4 id="11-如何应对缓存穿透和缓存雪崩问题"><a href="#11-如何应对缓存穿透和缓存雪崩问题" class="headerlink" title="11. 如何应对缓存穿透和缓存雪崩问题"></a>11. 如何应对缓存穿透和缓存雪崩问题</h4><ul><li><p>缓存穿透:即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。<br>解决方案:<br> 1)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试;<br> 2)采用异步更新策略,无论key是否取到值,都直接返回。</p><pre><code>value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需做环迅预热。</code></pre><p> 3)提供一个能迅速判断请求是否有效的拦截机制</p></li><li><p>缓存雪崩:即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。<br>解决方案:<br> 1)给缓存的失效时间,加上一个随机值,避免集体失效;<br> 2)使用互斥锁,但是该方案吞吐量明显下降了;<br> 3)双缓存。</p></li></ul><h4 id="12-如何解决redis的并发竞争key问题"><a href="#12-如何解决redis的并发竞争key问题" class="headerlink" title="12. 如何解决redis的并发竞争key问题"></a>12. 如何解决redis的并发竞争key问题</h4><p>即:同时有多个子系统去set一个key,需要注意什么?<br>1)如果对这个key操作,不要求顺序<br> 这种情况,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。<br>2)如果对这个key操作,要求顺序<br> 假设有一个key1,系统A需要将key1设置为value1,系统B需要将key1设置为valueB,系统C将key1设置为valueC,<br> 期望按照key1的value值按照valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个<br> 时间戳,<br> 系统A key1 {valueA 3:00}<br> 系统B key1 {valueB 3:05}<br> 系统C key1 {valueC 3:10}<br> 那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05},接下来系统A抢到锁,发现自己的valueA的时间戳<br> 早于缓存时间戳,那就不做set操作。</p><pre><code>其他方法,比如利用队列,将set方法变成串行访问也可以。</code></pre><h4 id="13-数据结构"><a href="#13-数据结构" class="headerlink" title="13. 数据结构"></a>13. 数据结构</h4><h5 id="字典"><a href="#字典" class="headerlink" title="字典"></a>字典</h5><p>distht:散列表结构,使用拉链法解决哈希冲突</p><p>Redis的字典dict中包含两个哈希表dictht,这是为了方便进行rehash操作。<br>在扩容时,将其中一个dictht上的键值对rehash到另一个dictht上,完成之后释放空间并交换两个dictht的角色。</p><p>rehash操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的rehash操作给服务器带来的过大的负担。</p><p>渐进式rehash通过记录dict的rehashidx完成,从0开始,然后每执行一次rehash都会递增。</p><p>在rehash期间,每次对字典执行添加、删除、查找或更新操作时,都会执行一次渐进式rehash。</p><p>采用渐进式rehash会导致字典中的数据分散在两个dictht上,因此对字典的查找操作也需要到对应的dictht去执行。</p><h5 id="跳跃表"><a href="#跳跃表" class="headerlink" title="跳跃表"></a>跳跃表</h5><p>是有序集合的底层实现之一。</p><p>跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。<br>在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。</p><p>与红黑树等平衡树相比,跳跃表的优点:<br>插入速度非常快速,因为不需要进行旋转灯操作来维护平衡性;<br>更容易实现;<br>支持无锁操作;</p><h4 id="14-Redis与Memcached"><a href="#14-Redis与Memcached" class="headerlink" title="14. Redis与Memcached"></a>14. Redis与Memcached</h4><p>两者都是非关系型内存键值数据库,主要有以下不同:</p><h5 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h5><p>Memcached仅支持字符串类型,redis支持五种不同的数据类型,可以灵活的解决问题。</p><h5 id="数据持久化"><a href="#数据持久化" class="headerlink" title="数据持久化"></a>数据持久化</h5><p>Redis支持两种持久化策略:RDB快照和AOF日志,而Memcached不支持持久化。</p><h5 id="分布式"><a href="#分布式" class="headerlink" title="分布式"></a>分布式</h5><p>Memcached不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,<br>这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。</p><p>redis cluster实现了分布式的支持。</p><h5 id="内存管理机制"><a href="#内存管理机制" class="headerlink" title="内存管理机制"></a>内存管理机制</h5><p>在redis中,并不是所有数据都一直存储在内存中,可以将一些很久没用的value交换到磁盘,<br>而Memcached的数据则会一直在内存中。</p><p>Memcached将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。<br>但这种方式会使得内存的利用率不高。</p><h4 id="15-键的过期时间"><a href="#15-键的过期时间" class="headerlink" title="15. 键的过期时间"></a>15. 键的过期时间</h4><p>Redis可以为每个键设置过期时间,当键过期时,会自动删除该键。</p><p>对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。</p><h4 id="16-数据淘汰策略"><a href="#16-数据淘汰策略" class="headerlink" title="16. 数据淘汰策略"></a>16. 数据淘汰策略</h4><p>可以设置内存的最大使用量,当内存使用量超出时,会施行数据淘汰策略。</p><p>Redis有6种淘汰策略:</p><ul><li>volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰</li><li>volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰</li><li>volatile-random 从已设置过期时间的数据集中任意选择数据淘汰</li><li>allkeys-lru 从所有数据集中挑选最近最少使用的数据淘汰</li><li>allkeys-random 从所有数据集中任意选择数据进行淘汰</li><li>noeviction 禁止驱逐数据</li></ul><p>作为内存数据库,出于对性能和内存消耗的考虑,redis的淘汰算法实际上并非针对所有的key,<br>而是抽样一小部分并且从中选出被淘汰的key。</p><p>使用Redis缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。<br>可以将内存最大使用量设置为热点数据占用的内存量,然后启用allkeys-lru淘汰策略,<br>将最近最少使用的数据淘汰。</p><p>Redis4.0引入了volatile-lfu和allkeys-lfu淘汰策略,LFU策略通过统计访问频率,将访问频率最少的键值对淘汰。</p><h4 id="17-事务"><a href="#17-事务" class="headerlink" title="17. 事务"></a>17. 事务</h4><p>一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。</p><p>事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式成为流水线,<br>可以减少客户端与服务器之间的网络通信次数从而提升性能。</p><p>redis最简单的事务实现方式是使用MULTI和EXEC命令将事务操作包围起来。</p><h4 id="19-事件"><a href="#19-事件" class="headerlink" title="19. 事件"></a>19. 事件</h4><p>redis服务器是一个事件驱动程序。</p><h5 id="文件事件"><a href="#文件事件" class="headerlink" title="文件事件"></a>文件事件</h5><p>服务器通过套接字与客户端或其它服务器进行通信,文件事件就是对套接字操作的抽象。<br>Redis基于Reactor模式开发了自己的网络事件处理器,使用I/O多路复用程序来同时监听多个套接字,<br>并将到达的事件传送给文件事件分派器,分排器会根据套接字产生的事件类型调用相应的事件处理器。</p><h5 id="时间事件"><a href="#时间事件" class="headerlink" title="时间事件"></a>时间事件</h5><p>服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。<br>时间事件分为:</p><ul><li>定时事件:是让一段程序在指定的时间之内执行一次;</li><li>周期性事件:是让一段程序每隔指定时间就执行一次;</li></ul><p>Redis将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,<br>并调用相应的事件处理器。</p><h5 id="事件的调度与执行"><a href="#事件的调度与执行" class="headerlink" title="事件的调度与执行"></a>事件的调度与执行</h5><p>服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但不能一直监听,否则时间事件无法在规定的时间内执行,<br>因此监听时间应该根据距离现在最近的时间事件来决定。</p><h4 id="19-复制"><a href="#19-复制" class="headerlink" title="19. 复制"></a>19. 复制</h4><p>通过使用slaveof host port命令来让一个服务器成为另一个服务器的从服务器。</p><p>一个从服务器只能有一个主服务器,并且不支持主主复制。</p><h5 id="连接过程"><a href="#连接过程" class="headerlink" title="连接过程"></a>连接过程</h5><ul><li>主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。<br>快照文件发送完毕后,开始向从服务器发送存储在缓冲区中的写命令。</li><li>从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;</li><li>主服务器每执行一次写命令,就向从服务器发送相同的命令</li></ul><h5 id="主从链"><a href="#主从链" class="headerlink" title="主从链"></a>主从链</h5><p>随着负载不断上升,主服务器可能无法很快的更新所有从服务器,或重新连接和同步从服务器将导致系统超载。<br>为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。<br>中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。</p><h4 id="20-Sentinel"><a href="#20-Sentinel" class="headerlink" title="20. Sentinel"></a>20. Sentinel</h4><p>Sentinel哨兵:可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。</p><h4 id="21-分片"><a href="#21-分片" class="headerlink" title="21. 分片"></a>21. 分片</h4><p>分片是将数据划分成多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得<br>线性级别的性能提升。</p><p>根据分片的位置,可以分为三种分片方式:</p><ul><li>客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点;</li><li>代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上;</li><li>服务器分片:Redis Cluster;</li></ul><h4 id="22-redis"><a href="#22-redis" class="headerlink" title="22. redis"></a>22. redis</h4><p>1)setnx setex set的区别:</p><ul><li><p>set key value<br> 将字符串值value关联到key;<br> 如果key已经持有其他值,set就覆盖旧值,无视类型;</p></li><li><p>setex key seconds value<br> 将值value关联到key,并将key的生存时间设为seconds(秒为单位);<br> 如果key已经存在,setex命令将覆写旧值;</p><p> 设置成功返回OK<br> 当seconds参数不合法时,返回一个错误;</p></li><li><p>setnx key value<br> 将key的值设为value,当且仅当key不存在;<br> 若给定的key已经存在,则setnx不做任何动作;<br> setnx是<code>set if not exists</code>简写</p><p> 设置成功,返回1<br> 设置失败,返回0</p></li><li><p>getset key value<br> 将给定的key的值设为value,并返回key的旧值<br> 当key存在但不是字符串类型时,返回一个错误</p><p> 返回给定的key的旧值<br> 当key没有旧值时,也即是,key不存在时,返回null</p></li></ul><p>用redis实现分布式锁,setnx加锁,del释放锁,设置过期时间,到了时间,del释放锁。</p><p>存在的问题:</p><ul><li>setnx和expire不是原子操作。一旦redis宕机,expire没有设置成功,锁就无法释放。只有一个请求的setnx可以成功,任何一个请求的expire都可以成功。请求比较密集,过期时间一直刷新,导致锁一直有效。</li><li>超时后,删除其他线程的锁。在线程A执行过程中,锁已释放,A还未在执行业务,但是还未删除锁。线程B获取锁执行业务,线程A执行完,A误删B的锁。</li><li>多个线程并发获取锁、释放锁。同一时间有线程A、B在访问同一代码块。</li></ul><p>解决:</p><ul><li>Redis2.6.12以上版本,可以用set获取锁。set可以实现setnx和expire,这个是原子操作。</li><li>Lua删除锁,Lua是原子操作。</li><li>让获取锁的线程开启一个守护线程,给线程还没执行完,又快要过期的锁续航。即:线程A还没执行完,守护线程每当快过期时,延时expire时间。当线程A执行完,显示关闭守护线程。如果中间宕机,锁超过超时,守护线程也不在了,自动伞释放锁。</li></ul><h4 id="23-Redis管道(Pipelining):"><a href="#23-Redis管道(Pipelining):" class="headerlink" title="23. Redis管道(Pipelining):"></a>23. Redis管道(Pipelining):</h4><p>Redis是一种基于客户端-服务端模型及请求/响应协议的TCP服务。</p><p>一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令一起发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。可以降低RTT时间从而提升性能。<br>使用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存所有命令的处理结果,打包的命令越多,缓存消耗内存也越多。所以不是打包命令越多越好。</p><p>RTT(Round Trip Time):请求从客户端到服务器的时间、命令排队的时间、命令真正执行的时间、结果从服务器到客户端的时间。</p><p>使用客户端对redis进行一次操作时,客户端将请求传送给服务器,服务器处理完毕后,再将响应回复给客户端,花费一个网络数据包来回的时间。<br>如果连续执行多条指令,那就会花费多个网络数据包来回的时间。</p><p>pipeline和事务是两个完全不同的概念,pipeline只是表达“交互”中操作的传递的方向性,pipeline也可以在事务中运行,也可以不在。</p><h4 id="24-Redis集群:"><a href="#24-Redis集群:" class="headerlink" title="24. Redis集群:"></a>24. Redis集群:</h4><p>集群:就是通过添加服务器的数量,提供相同的服务,从而让服务器达到一个稳定、高效的状态;</p><p>使用的必要性(为什么要学习redis集群):</p><ul><li>单个redis存在不稳定性。当redis服务宕机了,就没有可用的服务了;</li><li>单个redis的读写能力是有限的;</li><li>redis集群是为了强化redis的读写能力;</li></ul><p>如何学习redis集群:</p><ul><li>redis集群中,每一个redis称之为一个节点;</li><li>redis集群中,有两种类型的节点:主节点master和从节点slave;</li><li>redis集群,是基于redis主从复制实现的;</li></ul><ol><li><p>三种模式<br>主从模式<br>Sentinel模式<br>Cluster模式</p></li><li><p>主从模式<br>主数据库master和从数据库slave。<br>有多个redis节点;</p></li></ol><p>特点:</p><ul><li>主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库;</li><li>从数据库是只读的,并且接收主数据库同步过来的数据;</li><li>一个master可以拥有多个slave,一个slave只能对应一个master;</li><li>slave挂了不影响其他slave的读和master的读和写,重新启动后会将数据从master同步过来;</li><li>master挂了以后,不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务;</li><li>master挂了以后,不会在slave节点中重新选一个master;</li></ul><p>工作机制:<br>当slave启动后,主动向master发送sync命令。master接收到sync命令后在后台保存快照(RDB持久化)和缓存保存快照这段命令,然后将保存的快照文件和缓存命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。<br>复制初始化后,master每次接收到写命令都会同步发送给slave,保证主从数据一致性。</p><p>安全设置:<br>当master节点设置密码后,客户端访问master需要密码,启动slave需要密码,在配置文件中配置即可,客户端访问slave不需要密码。</p><p>缺点:<br>master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">查看集群状态:info replication</span><br><span class="line">config get dir</span><br><span class="line">config get dbfilename</span><br></pre></td></tr></table></figure><ol start="3"><li>Sentinel模式<br>Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务:</li></ol><p>监控(Monitoring):<br> Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。<br> * Sentinel可以监控任意多个Master和该Master下的Slaves(即主从模式)<br> * 同一个哨兵下的、不同主从模型,彼此之间相互独立;<br> * Sentinel会不断的检查Master和Slaves是否正常;</p><p>提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。</p><p>自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会进行选举,将其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。</p><p>特点:</p><ul><li><p>sentinel模式是建立在主从模式的基础上,如果只有一个Redis节点,sentinel就没有任何意义</p></li><li><p>当master挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master</p></li><li><p>当master重新启动后,它将不再是master而是做为slave接收新的master的同步数据</p></li><li><p>sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群</p></li><li><p>多sentinel配置的时候,sentinel之间也会自动监控</p></li><li><p>当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中,不需要担心</p></li><li><p>一个sentinel或sentinel集群可以管理多个主从Redis,多个sentinel也可以监控同一个redis</p></li><li><p>sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了</p></li></ul><p>工作机制:</p><ul><li><p>每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令 </p></li><li><p>如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。 </p></li><li><p>如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率确认master的确进入了主观下线状态</p></li><li><p>当有足够数量的sentinel(大于等于配置文件指定的值)在指定的时间范围内确认master的确进入了主观下线状态, 则master会被标记为客观下线 </p></li><li><p>在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令 </p></li><li><p>当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次 </p></li><li><p>若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除;<br>若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除</p></li></ul><p>当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。</p><ol start="3"><li>Cluster模式<br>sentinel模式基本可以满足一般生产的需求,具备高可用性。<br>但是当数据量过大到一台服务器存放不下的情况下,主从模式就不能满足需求了,需要对存储的数据进行分片,将数据存储到多个Redis实例中。<br>Cluster模式就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。</li></ol><p>Cluster可以说是sentinel和主从模式的结合体,通过Cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容。</p><p>使用集群,只需要将redis配置文件中的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行,新增节点非常方便。</p><p>特点:</p><ul><li><p>多个redis节点网络互联,数据共享</p></li><li><p>所有的节点都是一主一从(也可以是一主多从),其中从不提供服务,仅作为备用</p></li><li><p>不支持同时处理多个key(如MSET/MGET),因为redis需要把key均匀分布在各个节点上,<br>并发量很高的情况下同时创建key-value会降低性能并导致不可预测的行为</p></li><li><p>支持在线增加、删除节点</p></li><li><p>客户端可以连接任何一个主节点进行读写</p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">登录集群:redis-cli -c -h 127.0.0.1 -p 6379 -a 123456</span><br><span class="line">查看集群信息:cluster info</span><br><span class="line">列出节点信息:cluster nodes</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h2 id="Redis"><a href="#Redis" class="headerlink" title="Redis"></a>Redis</h2><p>一个开源的,内存中的数据结构存储系统;<br>用作数据库、缓存和消息中间件;</p>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Redis" scheme="http://gpp-note.top/tags/Redis/"/>
</entry>
<entry>
<title>Proto3</title>
<link href="http://gpp-note.top/articles/2020-02-11-proto3.html"/>
<id>http://gpp-note.top/articles/2020-02-11-proto3.html</id>
<published>2020-02-11T09:16:52.000Z</published>
<updated>2020-02-11T10:20:40.708Z</updated>
<content type="html"><![CDATA[<h4 id="Protobuf(proto3)"><a href="#Protobuf(proto3)" class="headerlink" title="Protobuf(proto3)"></a>Protobuf(proto3)</h4><p>protocol buffer是google的语言中立的、平台中立的,可扩展机制的,用于序列化结构化数据。<br>对比XML,更小、刚快、更简单。<br>简单的设计协议,通过自带工具转换成对应的语言代码,<br>协议是二进制协议,设计时只需要描述各个类的关系,简单明了。</p><p>定义消息类型<br>syntax = “proto3”; // 指定使用proto3的语法,否则默认是proto2,该定义必须是文件的第一个非空的非注释行</p><p>// HelloWorldRequest消息定义了三个字段(名称/值对),对应着消息内容<br>// 每个字段都由字段限制、字段类型、字段名和编号四部分组成<br>message HelloWorldRequest {<br> string name = 1;<br> int32 age = 2;<br>}</p><a id="more"></a><h4 id="1-指定字段类型"><a href="#1-指定字段类型" class="headerlink" title="1. 指定字段类型"></a>1. 指定字段类型</h4><pre><code>doublefloatint32int64booleanstringbyteuint64sint32sint64fixed32fixed64sfixed32sfixed64</code></pre><h4 id="2-赋予编号-数字标识符"><a href="#2-赋予编号-数字标识符" class="headerlink" title="2. 赋予编号(数字标识符)"></a>2. 赋予编号(数字标识符)</h4><p>消息中的每一个字段都有一个独一无二的数值类型的编号,用来在消息的二进制格式中识别各个字段,一旦开始使用就不能再改变。</p><p>1-15使用一个字节编码<br>16-2047使用两个字节编码<br>所以将编号1-15留给频繁使用的字段<br>要为将来有可能添加的、频繁出现的标识号预留一些标识号<br>最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。<br>不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报错</p><h4 id="3-指定字段限制"><a href="#3-指定字段限制" class="headerlink" title="3. 指定字段限制"></a>3. 指定字段限制</h4><p>required:必须赋值的字段<br>optional:可有可无的字段<br>repeated:可重复的字段(变长字段),重复使用任意次数(包括0次)</p><h4 id="4-添加更多消息类型"><a href="#4-添加更多消息类型" class="headerlink" title="4. 添加更多消息类型"></a>4. 添加更多消息类型</h4><p>一个.proto文件可以定义多个消息类型</p><h4 id="5-添加注释"><a href="#5-添加注释" class="headerlink" title="5. 添加注释"></a>5. 添加注释</h4><p>//</p><h4 id="6-预留字段"><a href="#6-预留字段" class="headerlink" title="6. 预留字段"></a>6. 预留字段</h4><p>当你在某次更新消息中屏蔽或者删除了一个字段的话,未来的使用着可能在他们的更新中重用这个标签数字来标记他们自己的字段。然后当他们加载旧的消息的时候就会出现很多问题,包括数据冲突,隐藏的bug等等。指定这个字段的标签数字(或者名字,名字可能在序列化为JSON的时候可能冲突)标记为reserved来保证他们不会再次被使用。如果以后的人试用的话protobuf编译器会提示出错。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">message Foo {</span><br><span class="line"> reserved 2, 15, 9 to 11;</span><br><span class="line"> reserved "foo", "bar";</span><br><span class="line">}</span><br><span class="line">// 一个reserved字段不能机油标签数字又有名字</span><br></pre></td></tr></table></figure><h4 id="7-默认值"><a href="#7-默认值" class="headerlink" title="7. 默认值"></a>7. 默认值</h4><pre><code>string:空字符串字节:空字节布尔:false数字类型:0枚举:第一个定义的枚举值,且该值必须为0重复字段:空</code></pre><h4 id="8-枚举"><a href="#8-枚举" class="headerlink" title="8. 枚举"></a>8. 枚举</h4><p>Corpus枚举第一个常量映射为0,每个枚举定义必须包含一个映射到0的常量作为第一个元素<br>设置可选参数allow_alias为true,可以在枚举结构中使用别名(两个值元素值相同)<br>枚举器常量必须在32位正数范围内<br>不建议使用负值</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">message SearchRequest {</span><br><span class="line"> string query = 1;</span><br><span class="line"> int32 page_number = 2;</span><br><span class="line"> int32 result_per_page = 3;</span><br><span class="line"> enum Corpus { </span><br><span class="line"> UNIVERSAL = 0;</span><br><span class="line"> WEB = 1;</span><br><span class="line"> IMAGES = 2;</span><br><span class="line"> LOCAL = 3;</span><br><span class="line"> NEWS = 4;</span><br><span class="line"> PRODUCTS = 5;</span><br><span class="line"> VIDEO = 6;</span><br><span class="line"> }</span><br><span class="line"> Corpus corpus = 4;</span><br><span class="line">}</span><br><span class="line">enum EnumAllowingAlias {</span><br><span class="line"> option allow_alias = true;</span><br><span class="line"> UNKNOWN = 0;</span><br><span class="line"> STARTED = 1;</span><br><span class="line"> RUNNING = 1;</span><br><span class="line">}</span><br><span class="line">enum EnumNotAllowingAlias {</span><br><span class="line"> UNKNOWN = 0;</span><br><span class="line"> STARTED = 1;</span><br><span class="line"> // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.</span><br><span class="line">}</span><br><span class="line">``` </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#### 9. 使用其他消息类型</span><br><span class="line">使用一个消息的定义作为另一个消息的字段类型</span><br></pre></td></tr></table></figure><p>message SearchResponse {<br> repeated Result results = 1;<br>}</p><p>message Result {<br> string url = 1;<br> string title = 2;<br> repeated string snippets = 3;<br>}</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#### 10. 导入定义</span><br><span class="line">```import 'other_protos.proto';</span><br></pre></td></tr></table></figure><h4 id="11-嵌套类型"><a href="#11-嵌套类型" class="headerlink" title="11. 嵌套类型"></a>11. 嵌套类型</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">message SearchResponse {</span><br><span class="line"> message Result {</span><br><span class="line"> string url = 1;</span><br><span class="line"> string title = 2;</span><br><span class="line"> repeated string snippets = 3;</span><br><span class="line"> }</span><br><span class="line"> repeated Result results = 1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 在另一个消息中使用Result定义</span><br><span class="line">message SomeOtherMessage {</span><br><span class="line"> SearchResponse.Result result = 1;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="12-更新消息类型"><a href="#12-更新消息类型" class="headerlink" title="12. 更新消息类型"></a>12. 更新消息类型</h4><ul><li>不要更改任何现有字段的字段编号</li><li>添加新字段,必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的兼容性</li><li>在原有消息中,不能移出已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用</li><li>int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性</li><li>optional和repeated限定符也是互相兼容</li></ul><h4 id="13-任意消息类型"><a href="#13-任意消息类型" class="headerlink" title="13. 任意消息类型"></a>13. 任意消息类型</h4><p>any类型不需要在.proto文件中定义就可以直接使用的消息类型,使用前import google/protobuf/any.proto文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">import "google/protobuf/any.proto";</span><br><span class="line"></span><br><span class="line">message ErrorStatus {</span><br><span class="line"> string message = 1;</span><br><span class="line"> repeated google.protobuf.Any details = 2;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="14-其中一个类型"><a href="#14-其中一个类型" class="headerlink" title="14. 其中一个类型"></a>14. 其中一个类型</h4><p>Oneof</p><ul><li>一个包含许多字段的消息,且最多只同时设置一个字段,可以使用oneof强制执行并节省内存</li><li>oneof字段只有最后被设置的字段才有效,即后面的set操作会覆盖前面的set操作</li><li>oneof不可以是repeated</li><li>反射api可以用在oneof字段</li><li>添加或删除oneof字段,如果检测到oneof字段的返回值是None/Not_Set,意味着oneof没有被设置或设置了一个不同的oneof版本,但无法区分这两种情况(向后兼容)</li><li>删除或添加字段到oneof,在消息序列化或解析后会丢失一些消息,一些字段会被清空</li><li>删除一个字段然后重新添加,在消息序列化或解析后会清除当前设置的oneof字段</li><li>分割或合并字段,同普通的删除字段操作</li></ul><h1 id="Maps(表映射)"><a href="#Maps(表映射)" class="headerlink" title="Maps(表映射)"></a>Maps(表映射)</h1><ul><li>map<key_type, value_type> map_field = N;</li><li>key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型</li><li>Map的字段不可以是重复的(repeated)</li><li>线性顺序和map值的的迭代顺序是未定义的,所以不能期待map的元素是有序的</li><li>maps可以通过key来排序,数值类型的key通过比较数值进行排序</li><li>线性解析或者合并的时候,如果出现重复的key值,最后一个key将被使用。从文本格式来解析map,如果出现重复key值则解析失败</li><li>向后兼容:在线性上是等价的,即使paotocol buffers没有实现maps数据结构也不会影响数据的处理</li></ul><h4 id="15-包"><a href="#15-包" class="headerlink" title="15. 包"></a>15. 包</h4><p>可以向.proto文件添加package可选说明符,以防止协议消息类型之间的名称冲突</p>]]></content>
<summary type="html">
<h4 id="Protobuf(proto3)"><a href="#Protobuf(proto3)" class="headerlink" title="Protobuf(proto3)"></a>Protobuf(proto3)</h4><p>protocol buffer是google的语言中立的、平台中立的,可扩展机制的,用于序列化结构化数据。<br>对比XML,更小、刚快、更简单。<br>简单的设计协议,通过自带工具转换成对应的语言代码,<br>协议是二进制协议,设计时只需要描述各个类的关系,简单明了。</p>
<p>定义消息类型<br>syntax = “proto3”; // 指定使用proto3的语法,否则默认是proto2,该定义必须是文件的第一个非空的非注释行</p>
<p>// HelloWorldRequest消息定义了三个字段(名称/值对),对应着消息内容<br>// 每个字段都由字段限制、字段类型、字段名和编号四部分组成<br>message HelloWorldRequest {<br> string name = 1;<br> int32 age = 2;<br>}</p>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Proto3" scheme="http://gpp-note.top/tags/Proto3/"/>
</entry>
<entry>
<title>Mongodb</title>
<link href="http://gpp-note.top/articles/2020-02-11-dataBase-Mongodb.html"/>
<id>http://gpp-note.top/articles/2020-02-11-dataBase-Mongodb.html</id>
<published>2020-02-11T09:11:24.000Z</published>
<updated>2020-04-25T13:10:24.882Z</updated>
<content type="html"><![CDATA[<h4 id="1-创建索引"><a href="#1-创建索引" class="headerlink" title="1. 创建索引"></a>1. 创建索引</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#### 2. 查询索引</span><br><span class="line">```db.user.getIndexes({})</span><br></pre></td></tr></table></figure><a id="more"></a><h4 id="3-索引类型"><a href="#3-索引类型" class="headerlink" title="3. 索引类型"></a>3. 索引类型</h4><ul><li><p>默认索引<br> 强制唯一的,不可删除。</p></li><li><p>唯一索引</p></li><li><p>组合索引<br> 多个键组合,键后面的数字表明了索引的方向,1表示升序,-1表示降序。</p></li></ul><h4 id="4-查询数据库主从库"><a href="#4-查询数据库主从库" class="headerlink" title="4. 查询数据库主从库"></a>4. 查询数据库主从库</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">查询结果:</span><br></pre></td></tr></table></figure><p>{<br> “setName” : “数据库名”,<br> “setVersion” : 25,<br> “ismaster” : false, // 所在库是否为主库<br> “secondary” : true, // 所在库是否为从库<br> “hosts” : [ // 数据库hosts<br> “<strong>***</strong>:27017”,<br> “<strong>***</strong>:27017”,<br> “<strong>***</strong>:27017”<br> ],<br> “primary” : “<strong>***</strong>:27017”, // 主库<br> “me” : “<strong>**</strong>:27017”,<br> “maxBsonObjectSize” : 16777216,<br> “maxMessageSizeBytes” : 48000000,<br> “maxWriteBatchSize” : 1000,<br> “localTime” : ISODate(“2020-02-11T09:18:17.240Z”),<br> “maxWireVersion” : 3,<br> “minWireVersion” : 0,<br> “ok” : 1.0<br>}</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#### 5. 获取ObjectId的时间</span><br><span class="line">``` ObjectId('5d01777700de3b32269aefe4').getTimestamp()</span><br></pre></td></tr></table></figure><p>结果:<code>ISODate("2019-06-12T22:06:47Z")</code></p><p>无论是MongoDB还是Mysql,都存在着主键的定义。</p><p>Mongodb,主键名为“_id”</p><p>Mysql,主键的指定是在Mysql插入数据时指明PRIMARY KEY来定义的。</p><ol><li>数据库的平均插入速率:Mongodb指定_id插入 > Mysql不指定主键插入 > Mysql指定主键插入 > MongoDB指定_id插入;</li><li>MongoDB在指定_id与不指定_id插入时速度相差很大,而Mysql的差别却小很多。</li></ol><p>分析:</p><ol><li>在指定_id或主键时,两种数据库在插入时要对索引值进行处理,并查找数据库中是否存在相同的键值,这会减慢插入的速率;</li><li>在Mongodb中,指定索引插入比不指定慢很多,因为Mongodb里每一条数据的_id是唯一的,不指定时,其_id是系统自动计算生成的。Mongodb通过计算机特征值、时间、进程ID与随机数来确保生成的id是唯一的。而在指定id插入时,Mongodb每插一条数据,都需要检查此id可不可用,当数据库中数据条数太多的时候,这一步的查询开销会拖慢整个数据库的插入速度;</li><li>Mongodb会充分使用系统内存作为缓存,插入时,Mongodb会尽可能的在内存块写不进去数据之后,再将数据持久化保存到硬盘上。</li></ol><p>结论:</p><ol><li>相比Mysql,Mongodb数据库更适合那些读作业较重的任务模型。Mongodb能充分利用机器的内存资源。如果机器的内存资源丰富的话,Mongodb的查询效率会快很多;</li><li>推荐采用不带id的插入方式,然后对相关的字段作索引来查询;</li><li>Mongodb适合那些对数据库具体数据格式不明确或数据库数据格式经常变化的需求模型,而且对开发者十分友好;</li><li>Mongodb自带一个分布式文件系统,很方便部署到服务器机群上。Mongodb里有一个shard的概念,就是方便了服务器分片使用的。每增加一台shard,Mongodb的插入性能会以接近倍数的方式增长,磁盘容量也很可以很方便扩充;</li><li>Mongodb还自带了对map-reduce运算框架的支持,这也很方便进行数据的统计;</li></ol><h4 id="Mongodb的索引"><a href="#Mongodb的索引" class="headerlink" title="Mongodb的索引"></a>Mongodb的索引</h4><ol><li>单键索引</li><li>复合索引</li><li>多键索引</li><li>哈希索引</li></ol><h5 id="Mongodb索引的管理"><a href="#Mongodb索引的管理" class="headerlink" title="Mongodb索引的管理"></a>Mongodb索引的管理</h5><p>索引的本质是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据的物理地址,索引可以大大加快查询的速度,这是因为使用索引后可以不再扫描全表来定位某行的数据,而是通过索引表找到该行数据的物理地址,然后通过地址来访问相应的数据。</p><p>索引可以加快数据检索、排序、分组的速度,减少I/O,但是索引也不是越多越好,因为索引本身也是数据表,需要占用存储空间,同时索引需要数据库进行维护,当我们对索引列的值进行增删改查操作时,数据库需要更新索引表,这会增加数据库的压力。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个索引</span><br><span class="line">db.tables.createIndex({age: -1})</span><br><span class="line">// 查看所有索引</span><br><span class="line">db.tables.getIndexes()</span><br><span class="line">// 删除特定的索引</span><br><span class="line">db.tables.dropIndex({age: -1})</span><br><span class="line">// 删除所有的索引</span><br><span class="line">db.tables.dropIndexes()</span><br></pre></td></tr></table></figure><h5 id="常用的索引"><a href="#常用的索引" class="headerlink" title="常用的索引"></a>常用的索引</h5><ol><li><p>唯一索引<br>强制要求索引字段没有重复值</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.tables.createIndex({name: 1}, {unique: true})</span><br></pre></td></tr></table></figure></li><li><p>局部索引<br>只对collection的一部分添加索引。<br>创建索引的时候,根据过滤条件判断是否为document添加索引,对于没有添加索引的文档查找时采用的是全表扫描,对添加了索引的文档查找时使用索引。</p></li><li><p>稀疏索引<br>在有索引字段的document上添加索引。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.tables.createIndex({name: 1}, { sparse: true})</span><br></pre></td></tr></table></figure></li><li><p>TTL索引<br>TTL indexes:特殊的单键索引,用于设置document的过期时间,过期自动删除。<br>TTL索引只能设置在date类型字段上,过期时间为字段值+expireAfterSeconds;</p></li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">//添加测试数据</span><br><span class="line">db.logs.insertMany([</span><br><span class="line"> {_id:1,createtime:new Date(),msg:"log1"},</span><br><span class="line"> {_id:2,createtime:new Date(),msg:"log2"},</span><br><span class="line"> {_id:3,createtime:new Date(),msg:"log3"},</span><br><span class="line"> {_id:4,createtime:new Date(),msg:"log4"}</span><br><span class="line"> ])</span><br><span class="line"> //在createtime字段添加TTL索引,过期时间是120s</span><br><span class="line"> db.logs.createIndex({createtime:1}, { expireAfterSeconds: 120 })</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">//logs中的document在创建后的120s后过期,会被mongoDB自动删除</span><br></pre></td></tr></table></figure><p>redis:</p><p>缓存更新:一种机制,怎么样保证缓存中的key是实时有效的,以及及时的更新数据资源。</p><p>解决办法:</p><ul><li>缓存服务器自带的缓存失效策略</li><li>自定义:定时去清理过期的缓存;当用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到数据并更新缓存;</li></ul><p>缓存降级:<br>发生场景:<br> 当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算等)。<br>解决办法:<br> 在进行降级之前要对系统进行梳理,看看系统是不是可以丢车保帅;从而梳理出哪些必须誓死保护,哪些可以降级;比如可以参考日志级别设置预案:<br> * 一般:比如有些服务偶尔因为网络抖动或服务正在上线而超时,可以自动降级;<br> * 警告:有些服务在一段时间内成功率有波动,可以自动降级或人工降级,并发送告警;<br> * 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问突然猛增到系统能承受的最大阈值,此时可以根据情况自动降级或者人工降级;<br> * 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。</p>]]></content>
<summary type="html">
<h4 id="1-创建索引"><a href="#1-创建索引" class="headerlink" title="1. 创建索引"></a>1. 创建索引</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#### 2. 查询索引</span><br><span class="line">&#96;&#96;&#96;db.user.getIndexes(&#123;&#125;)</span><br></pre></td></tr></table></figure>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Mongodb" scheme="http://gpp-note.top/tags/Mongodb/"/>
</entry>
<entry>
<title>Knex</title>
<link href="http://gpp-note.top/articles/2020-02-11-dataBase-knex.html"/>
<id>http://gpp-note.top/articles/2020-02-11-dataBase-knex.html</id>
<published>2020-02-11T09:08:37.000Z</published>
<updated>2020-02-11T10:14:51.589Z</updated>
<content type="html"><![CDATA[<h4 id="1-Knexjs"><a href="#1-Knexjs" class="headerlink" title="1. Knexjs"></a>1. Knexjs</h4><p>设计的“包含电池”SQL查询构建器,设计灵活,便于携带,并且使用起来非常有趣。<br>它具有传统的节点样式回调以及用于清洁异步流控制的承诺接口,流接口,全功能查询和模式构建器,事务支持(带保存点),连接池以及不同查询客户和方言之间的标准化响应。</p><a id="more"></a><h4 id="2-支持"><a href="#2-支持" class="headerlink" title="2. 支持"></a>2. 支持</h4><p>主要目标环境是NodeJS,安装该Knex库。</p><h4 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h4><figure class="highlight plain"><figcaption><span>install knex --save```</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#### 3. 初始数据库</span><br><span class="line">该knex模块本身是一个为Knex提供配置对象的函数,它接受一些参数。该client参数是必须的,并且确定哪个客户端适配器将与该库一起使用。</span><br><span class="line">mysql数据库初始化:</span><br></pre></td></tr></table></figure><p>var knex = require(‘knex’)({<br> client: ‘mysql’,<br> connection: {<br> host : ‘127.0.0.1’,<br> user : ‘your_database_user’,<br> password : ‘your_database_password’,<br> database : ‘myapp_test’<br> }<br>});</p><pre><code>#### 4. knex查询构造器</code></pre>]]></content>
<summary type="html">
<h4 id="1-Knexjs"><a href="#1-Knexjs" class="headerlink" title="1. Knexjs"></a>1. Knexjs</h4><p>设计的“包含电池”SQL查询构建器,设计灵活,便于携带,并且使用起来非常有趣。<br>它具有传统的节点样式回调以及用于清洁异步流控制的承诺接口,流接口,全功能查询和模式构建器,事务支持(带保存点),连接池以及不同查询客户和方言之间的标准化响应。</p>
</summary>
<category term="Database" scheme="http://gpp-note.top/categories/Database/"/>
<category term="Knex" scheme="http://gpp-note.top/tags/Knex/"/>
</entry>
</feed>