-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path201903.txt
518 lines (445 loc) · 60.8 KB
/
201903.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
REP: 复用、发布等同原则
软件复用的最小粒度应等同于其发布的最小粒度。直白地说,就是要复用一段代码就把它抽成组件。该原则指导我们组件拆分的粒度。
CCP:共同闭包原则
为了相同目的而同时修改的类,应该放在同一个组件中。CCP 原则是 SRP 原则在组件层面的描述。该原则指导我们组件拆分的粒度。
对大部分应用程序而言,可维护性的重要性远远大于可复用性,由同一个原因引起的代码修改,最好在同一个组件中,如果分散在多个组件中,那么开发、提交、部署的成本都会上升。
CRP:共同复用原则
不要强迫一个组件依赖它不需要的东西。CRP 原则是 ISP 原则在组件层面的描述。该原则指导我们组件拆分的粒度。
相信你一定有这种经历,集成了组件A,但组件A依赖了组件B、C。即使组件B、C 你完全用不到,也不得不集成进来。这是因为你只用到了组件A的部分能力,组件A中额外的能力带来了额外的依赖。如果遵循共同复用原则,你需要把A拆分,只保留你要用的部分。
REP、CCP、CRP 三个原则之间存在彼此竞争的关系,REP 和 CCP 是黏合性原则,它们会让组件变得更大,而 CRP 原则是排除性原则,它会让组件变小。遵守REP、CCP 而忽略 CRP ,就会依赖了太多没有用到的组件和类,而这些组件或类的变动会导致你自己的组件进行太多不必要的发布;遵守 REP 、CRP 而忽略 CCP,因为组件拆分的太细了,一个需求变更可能要改n个组件,带来的成本也是巨大的。
优秀的架构师应该能在上述三角形张力区域中定位一个最适合目前研发团队状态的位置,例如在项目早期,CCP比REP更重要,随着项目的发展,这个最合适的位置也要不停调整。
----------------------------------------------------------------------------------------
协程是属于线程的,又称微线程,纤程,英文名Coroutine。举个例子,在执行函数A时,我希望随时中断去执行函数B,然后中断B的执行,切换回来执行A。这就是协程的作用,由调用者自由切换。这个切换过程并不是等同于函数调用,因为它没有调用语句。执行方式与多线程类似,但是协程只有一个线程执行。
协程的优点是执行效率非常高,因为协程的切换由程序自身控制,不需要切换线程,即没有切换线程的开销。同时,由于只有一个线程,不存在冲突问题,不需要依赖锁(加锁与释放锁存在很多资源消耗)。
协程主要的使用场景在于处理IO密集型程序,解决效率问题,不适用于CPU密集型程序的处理。然而实际场景中这两种场景非常多,如果要充分发挥CPU利用率,可以结合多进程+协程的方式。后续我们会讲到结合点。
根据wikipedia的定义,协程是一个无优先级的子程序调度组件,允许子程序在特定的地方挂起恢复。所以理论上,只要内存足够,一个线程中可以有任意多个协程,但同一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。协程是为了充分发挥异步调用的优势,异步操作则是为了避免IO操作阻塞线程。
----------------------------------------------------------------------------------------
桥接模式的优点:
- 由于抽象与实现分离,所以扩展能力强;
- 其实现细节对客户透明;
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
桥接模式的结构:可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
桥接模式包含以下主要角色:
1. 抽象化角色Abstraction: 定义抽象类,并包含一个对实现化对象的引用;
2. 扩展抽象化角色Refined Abstraction:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法;
3. 实现化角色Implementor:定义实现化角色的接口,供扩展抽象化角色调用;
4. 具体实现化角色Concrete Implementor:给出实现化角色接口的具体实现。
桥接模式的应用场景:
1. 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2. 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
3. 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时
桥接模式的扩展:在软件开发中,有时桥接模式可与适配器模式联合使用。当桥接模式的实现化角色的接口与现有类的接口不一致时,可以在两者中间定义一个适配器将两者连接起来。
Kubernetes 集群管理系统需要具备便捷的集群生命周期管理能力,完成集群的创建、升级和工作节点的管理。在大规模场景下,集群变更的可控性直接关系到集群的稳定性,因此管理系统可监控、可灰度、可回滚的能力是系统设计的重点之一。
除此之外,超大规模集群中,节点数量已经达到 10K 量级,节点硬件故障、组件异常等问题会常态出现。面向大规模集群的管理系统在设计之初就需要充分考虑这些异常场景,并能够从这些异常场景中自恢复。
外观模式是迪米特法则的典型应用,主要优点如下:
1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类;
2. 对客户端屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易;
3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象;
外观模式的主要缺点如下:
1. 不能很好地限制客户使用子系统类;
2. 增加新的子系统可能需要修改外观类或客户端的原代码,违背了开闭原则;
外观模式的扩展:
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了开闭原则。如果引入抽象外观类,则在一定程度上解决了该问题。
享元模式的优点:相同对象只要保存一份,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
享元模式的缺点:
1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性;
2. 读取享元模式的外部状态会使得运行时间稍微变长。
享元模式的主要角色有如下。
1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2. 具体享元角色(ConcreteFlyweight):实现抽象享元角色中所规定的接口。
3. 非享元角色(UnsharableFlyweight):是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
4. 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
享元模式的扩展:在享元模式的结构图中包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式。
1. 单纯享元模式:这种享元模式中的所有具体的享元类都是可以共享的,不存在非共享的具体享元类;
2. 复合享元模式:这种享元模式中的有些享元是由一些单纯享元对象组合而成的,它们就是复合享元对象,虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享。
组合模式Composite定义:又叫做部分-整体模式,是一种将对象组合成树状的层次结构的模式,用来表示"部分-整体"的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式的优点:
1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,简化了客户端代码;
2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足开闭原则;
组合模式的缺点:
1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2. 不容易限制容器中的构件;
3. 不容易用继承的方法来增加构件的新功能;
组合模式包含以下主要角色。
1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
3. 树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
组合模式分为透明式的组合模式和安全式的组合模式。
1. 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但缺点是:树叶构件本来没有add,remove,getChild方法,却要实现它们(空实现或者抛异常),这样会带来一些安全性问题。
2. 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,失去了透明性。
组合模式的扩展:如果对组合模式中的树叶节点和树枝节点进行抽象,也就是树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了。
----------------------------------------------------
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF 设计模式中最为庞大的一类,它包含以下 11 种模式。
模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式,
----------------------------------------------------
使用过程分解之后的代码,已经比以前的代码更清晰、更容易维护了。不过,还有两个问题值得关注:
1. 领域知识被割裂肢解:什么叫被肢解?目前为止做的都是过程化拆解,导致没有一个聚合领域知识的地方。每个Use Case的代码只关心自己的处理流程,知识没有沉淀。
相同的业务逻辑会在Use Case中被重复实现,导致代码重复度高,即时有复用,最多也就是抽取一个util,代码对业务语义的表达能力很弱,从而影响代码的可读性和可理解性。
2. 代码的业务表达能力缺少:试想下,在过程式的代码中,所做的事情无外乎就是取数据--做计算--存数据,在这种情况下,要如何通过代码显性化的表达我们的业务呢?说实话,很难做到,因为我们缺失了模型,以及模型之间的关系。脱离模型的业务表达,是缺少韵律和灵魂的。
自上而下的结构化分解+自下而上的面向对象分析;
----------------------------------------------------------
模板方法(TemplateMethod)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变算法结构的情况下重定义该算法的特殊步骤。它是一种类行为模式。
模板方法的优点:
1. 封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展;
2. 它在父类中提取了公共的部分代码,便于代码复用;
3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则;
模板方法的缺点:
1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更抽象;
2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它增加了代码阅读的难度;
模板方法模式包含以下主要角色。
(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
1. 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
2. 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
模板方法模式的扩展:在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用钩子方法,可以使得子类控制父类的行为。
----------------------------------------------------------
Cannot reduce the visibility of the inherited method.
策略模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下:
1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句;
2. 策略模式提供了一些列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码;
3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的策略;
4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法;
5. 策略模式把算法的使用放在环境类中,而算法的实现移到具体策略类中,实现了两者的分离;
策略模式的缺点:
1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类;
2. 策略模式造成很多的策略类;
策略模式的扩展:在使用一个策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类,将大大减少客户端的工作复杂度。
命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加和管理。
命令模式的优点:
1. 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦;
2. 增加或删除命令非常方便。采用命令模式增加或删除命令不会影响其他类,满足开闭原则,对扩展比较灵活;
3. 可以实现宏命令。命令模式可以和组合模式结合,将多个命令装配称一个组合命令,即宏命令;
4. 方便实现Undo和Redo操作。命令模式可以和备忘录模式结合,实现命令的撤销与恢复。
命令模式的缺点:可能产生大量命令类。因为针对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
命令模式的扩展:在软件开发中,有时将命令模式与组合模式联合使用,构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令。
责任链:也叫职责链,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链是一种对象行为型模式,其优点如下:
1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
责任链的缺点:
1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
责任链的扩展:存在以下两种情况:
1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理;把责任推给下家;
2. 不纯的职责链:允许出现某一个具体处理者对象承担了请求的一部分责任又将剩余的责任传给下家的情况,且一个请求可以最终不被任何处理者所处理。
------------------------------------------------------
状态模式:State,对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式是一种对象行为型模式,优点如下:
1. 状态模式与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足单一职责原则;
2. 减少对象间的互相依赖。将不同钻沟通引入独立的对象中会使得状态转换变得更加明确,且减少对象间的互相依赖;
3. 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换;
缺点:
1. 状态模式的使用必然会增加系统的类与对象的个数;
2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
状态模式的扩展:在有些情况下,可能多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享。分析:共享状态模式的不同之处是在环境类中增加了一个HashMap来保存相关状态,当需要某种状态时可以从中获取。
------------------------------------------------------
观察者模式:Observer,指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称做发布订阅模式、模型-视图模式,它是对象行为型模式;
优点:
1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系;
2. 目标与观察者之间建立了一套触发机制。
缺点:
1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用;
2. 当观察者对象很多时,通知的发布会 花费很多时间,影响程序的效率;
观察者模式的扩展:通过java.util.Observable类和java.util.Observer接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1. Observable类
Observable类是抽象目标类,它有一个Vector向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的3个方法。
void addObserver(Observer o):用于将新的观察者对象添加到向量中。
void notifyObservers(Object arg):调用向量中的所有观察者对象的update方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
void setChange():用来设置一个boolean类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers()才会通知观察者。
2. Observer 接口
Observer接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用void update(Observable o,Object arg) 方法,进行相应的工作。
------------------------------------------------------
中介者模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫做调停模式,它是迪米特法则的典型应用。
中介者模式是对象行为型模式,优点如下:
1. 降低了对象之间的耦合性,使得对象易于独立地被复用;
2. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展;
缺点:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
中介者模式的扩展:在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
1. 不定义中介者接口,把具体中介者对象实现为单例;
2. 同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用;
------------------------------------------------------
迭代器模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式。
优点:
1. 访问一个聚合对象的内容而无须暴露它的内部表示;
2. 遍历任务交由迭代器完成,这简化了聚合类;
3. 它支持不同方式遍历聚合对象,甚至可以自定义迭代器的子类以支持新的遍历;
4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码;
5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点:增加了类的个数,这在一定程度上增加了系统的复杂性。
迭代器模式的扩展:迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问。
------------------------------------------------------
访问者模式:Vistor将作用于某种数据结构中的各元素的操作分离出来封装称独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新操作,为数据结构中的每个元素提供多种访问方式。它对数据的操作与数据结构进行分离,是行为型模式中最复杂的一种模式。
访问者模式是对象行为型模式,优点如下:
1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新功能;
2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度;
3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构;
4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每个访问者的功能都比较单一;
缺点:
1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则;
2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性;
3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
访问者模式的扩展:Visitor是使用频率较高的设计模式,常常和以下两种设计模式联用。
1. 与迭代器模式联用。因为访问者模式中的对象结构是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器;
2. 同组合模式联用。因为访问者中的元素对象可能是叶子对象或者组合对象,如果元素对象包含组合对象,就必须用组合模式。
------------------------------------------------------
备忘录Memento:在不破坏封装性的前提下,获取对象的内部状态,并在该对象之外保存这个状态,以便以后需要时能将该对象恢复到原先保存的状态。又叫做快照模式。
备忘录模式是对象行为型模式,优点:
1. 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史状态;
2. 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能访问这些状态信息;
3. 简化了发起人角色,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则;
缺点:资源消耗大,如果要保存的内部状态信息过多或者太过频繁,将会占用较大的内存资源。
备忘录模式的扩展:在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类
------------------------------------------------------
解释器模式:Interpreter给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
解释器模式是一种类行为型模式,优点如下:
1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法;
2. 容易实现。在语法树中的每个表达式节点类都是类似的,所以实现其文法较为容易;
缺点:
1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程麻烦;
2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护;
3. 可应用的场景较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
解释器模式的结构与组合模式类似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
解释器模式的扩展:在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
------------------------------------------------------
在软件系统中,类不是孤立存在的,类与类之间存在各种关系。根据类与类之间的耦合度从弱到强排列,有依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系等6种。
怎么实现分布式缓存?
1. 雪崩问题防范;
2. Cache和DB的一致性;
3. 缓存中间件的选择;
4. 缓存穿透问题防范;
5. 高可用性,无单点故障;
6. 横向扩展性,数据分片策略,扩容/缩容策略;
7. Cache的主从之间的延时问题;
8. 相关的监控机制,检测方案,健康检查机制;
9. 相关的回滚方案;
10. 相关的降级方案;
11. 淘汰策略;
12. 热点数据的不平衡问题;
13. 缓存击穿问题防范;
14. 多层次;这样某一层挂了,还有另一层可以撑着,等待缓存的修复,例如分布式缓存因为某种原因挂了,因为持久化的原因/同步机制的原因/内存过大的原因等,修复需要一段时间,至少本地缓存可以扛一阵,不至于一下子就击穿数据库。而且对于特别热的数据,热到集中式的缓存处理不过来,网卡也打满的情况,由于本地缓存不需要远程调用,也是分布在应用层的,可以缓解这种问题。
缓存的更新模式:
1. Cache Aside模式;
- 读取失效:cache数据没有命中,查询DB,成功后把数据写入缓存;
- 读取命中:读取cache数据;
- 更新:把数据更新到DB,失效缓存;
为什么更新不直接写缓存?为了防止高并发的数据不一致;
2. Read/Write Through模式;
- 缓存代理了DB读取、写入的逻辑,可以把缓存看作唯一的存储;
3. Write Behind Caching(Write Back)模式;
- 这种模式下所有的操作都走缓存,缓存里的数据再通过异步的方式同步到数据库里。所以系统的写性能大大提升了。
缓存的三大矛盾问题:
1. 缓存实时性和一致性问题:当有写入后怎么处理;
2. 缓存的穿透问题:当缓存没有读到数据怎么处理;
3. 缓存对数据库高并发访问:都来访问数据库怎么处理;
自底向上推导应用逻辑架构
自顶向下构建架构
升层思考问题,升维思考手段/方法。不过这张图中每个问题到底严重到什么程度,还没有给出量化,不过我们在工作中,我们是要量化这个严重程度,而且要放在时间轴上来进行量化,因为有些问题当前可能并不严重,但是数月后可能会变成大问题。
Nmon:开源性能监控工具,用于监控Linux系统的资源消耗信息,并能把结果输出到文件中,然后通过nmon_analyser工具产生数据文件与图形化结果。
不是swap空间占用多就一定性能下降,真正影响性能是swap in和out的频率,频率越高,对系统的性能影响越大。
使用vmstat监控swap in和swap out。
Swap Used高,可能的情况:Swap和GC同时发生会导致GC时间变长,可以通过减少堆大小,或者增加物理内存解决。加入GC的时候,有堆的一部分内存被交换到swap,GC时内存空间不足,就需要把内存中堆的另外一部分换到swap,Linux对swap的回收是滞后的,就会看到大量swap占用。
常见的监控指标:
1. 通用监控指标:
1.1 CPU
1.2 内存
1.3 硬盘:磁盘IO速度、传输、读写比率
1.4 文件系统
1.5 NFS
1.6 高耗进程(资源占用率比较高的进程)
1.7 虚拟内存
1.8 Swap Space交换空间
1.9 负载
1.10 文件句柄数
2. 数据库监控指标:
2.1 缓存命中
2.2 索引
2.3 慢SQL
2.4 数据库线程数
2.5 连接池
3. 中间件监控指标:
3.1 消息累积
3.2 消息追踪
3.3 消息调用链
3.4 负载均衡
3.5 缓存
4. 网络:
4.1 吞吐量
4.2 吞吐率
5. 应用:
5.1 JVM
5.2 日志
5.3 GC频率
5.4 调用链
5.5 慢请求
AOP使用场景:
1. Authentication 权限检查
2. Caching 缓存
3. Context passing 内容传递
4. Error Handling 错误处理
5. Lazy loading 延迟加载
6. Debugging 调试
7. logging, tracing, profiling, monitoring 日志记录,跟踪,优化,校准
8. Performance optimization 性能优化,效率检查
9. Persistence 持久化
10. Resource pooling 资源池
11. Synchronization 同步
12. Transaction 事务管理
Spring Boot Features:
1. Create stand-alone Spring applications;
2. Embed Tomcat, Jetty or Undertow directly(no need to deploy WAR files);
3. Provide opinionated 'starter' dependencies to simplify your build configuration;
4. Automatically configure Spring and 3rd party libraries whenever possible;
5. Provide production-ready features such as metrics, health checks and externalized configuration;
6. Absolutely no code generation and no requirement for XML configuration;
Spring Boot除了自动配置,相比传统Spring有什么其他的区别:
为Spring生态系统的开发提供一种更简洁的方式,提供了很多非功能性特性,例如:嵌入式Servlet容器,Security,统计,健康检查,外部化配置等等,主要体现几点:
1.Spring Boot可以建立独立的Spring应用程序;
2.内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了;
3.无需再像Spring那样搞一堆繁琐的xml文件的配置;
4.可以自动配置Spring。SpringBoot将原有的XML配置改为Java配置,将bean注入改为使用注解注入的方式(@Autowire),并将多个xml、properties配置浓缩在一个appliaction.yml配置文件中。
5.提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
6.整合常用依赖(开发库,例如spring-webmvc、jackson-json、validation-api和tomcat等),提供的POM可以简化Maven的配置。当我们引入核心依赖时,SpringBoot会自引入其他依赖。
HashMap和Hashtable的区别:
1. Hashtable是线程安全的,效率比较低;
2. Hashtable的Key和Value都不能为Null;HashMap的Key和Value都可以为Null;
3. Hashtable是JDK1.0出现的,HashMap是JDK1.2出现的;
4. Hashtable默认的初始大小为11,之后每次扩容,容量为原来的2N+1;
5. HashMap默认的初始大小为16,之后每次扩容,容量为原来的两倍;
6. Hashtable在计算元素的位置时需要进行取模运算,取模运算比较耗时;
7. HashMap为了提高计算效率,将哈希表的大小固定为1的幂次,在取模时不需要做除法,只需要位运算,比除法高效很多;
8. HashMap继承自AbstractMap,而Hashtable继承自Dictionary类。但都同时实现了Map, Cloneable, Serializable三个接口。
Object的hashCode()方法重写了,equals方法是不是也需要修改?
不需要。Object类有两个方法equals(),hashCode(),这两个方法都是用来比较两个对象是否相等的,如果两个对象相等(equals),那么必须拥有相同的hashCode。但两个对象的hashCode相等,并不一定相等(equals)。
重写equals()方法就必须重写hashCode(),但重写hashCode()不一定重写equals()。
SQL优化的常见方法:
1. 查询条件不用函数,避免全表扫描;
2. 减少不必要的表连接;
3. 有些数据操作的业务逻辑可以放在应用层实现;
4. 尽量避免使用游标,游标的效率比较差;
5. 不要把SQL写得太复杂;
6. 不要循环执行查询;
7. 用exists代替in;
8. 表关联不要太多,可以存储部分冗余数据;
9. 查询尽量用索引;
10. inner关联的表可以先查出来,再去关联left join的表;
11. 可以进行表关联数据拆分,即先查出核心数据,再通过核心数据查其他数据,这样会快很多;
12. 参考SQL执行顺序,进行优化;explain
13. 使用数据仓库的形式,建立单独的表存储数据,根据时间戳定期更新数据。将多表关联的数据集中抽取到一张表,查询时单表查询,提高了查询效率;
14. 对查询进行优化,尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引;
15. 尽量避免在where子句中对字段进行null值判断,否则引擎会放弃索引直接全表扫描;用特殊值代替null值;
16. 尽量避免在where子句中使用!=或<>,否则引擎会放弃索引直接全表扫描;
17. SQL索引的顺序,字段的顺序;
什么对象会从新生代晋升到老年代:
1. Eden区满,进行MinorGC,当Eden和Survivor区中仍然存活的对象无法放入到Survivor中,则通过分配担保机制提前转移到老年代中;
2. 大对象无法在新生代直接分配,会直接在老年代分配;-XX:PretenureSizeThreshold对象的大小大于此值会直接在老年代分配,此参数只对Serial及ParNew有效;
3. 长期存活的对象进入老年代;-XX:MaxTenuringThreshold,如果对象在Eden出生并在第一次发生MinorGC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话Age=Age+1。
4. 动态对象年龄判定。JVM并不总是要求对象的年龄达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同年龄的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
多个数据库连接池的集合,不同数据库连接池属性自适配(DBCP, C3P0, Druid, HikariCP);
两阶段事务提交采用的是X/Open组织所定义的DTP(Distributed Transaction Processing)模型,通过抽象出来的AP,TM,RM的概念可以保证事务的强一致性。其中TM和RM采用XA协议进行双向通信。与传统的本地事务相比,XA事务增加了prepare阶段,数据库除了被动接收提交指令外,还可以反向通知调用方事务是否可以被提交。因此TM可以收集所有分支事务的prepare结果,最后进行原子的提交,保证事务的强一致性。
Java通过定义JTA接口实现了XA的模型,JTA接口里的ResourceManager需要数据库厂商提供XA的驱动实现,而TransactionManager则需要事务管理器的厂商实现,传统的事务管理器需要同应用服务器绑定,因此使用的成本很高。 而嵌入式的事务管器可以以jar包的形式提供服务,同ShardingSphere集成后,可保证分片后跨库事务强一致性。
通常,只有使用了事务管理器厂商所提供的XA事务连接池,才能支持XA事务。ShardingSphere整合XA事务时,分离了XA事务管理和连接池管理,这样接入XA时,可以做到对业务的零侵入。
ShardingSphere的柔性事务已通过第三方SPI实现Saga事务,Saga引擎使用Servicecomb-Saga。
数据库范式:设计关心数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯科德范式(BCNF)、第四范式(4NF)、第五范式(5NF,又称完美范式)。
规范化:一个低一级的关系模式通过模式分解可以转化为若干个高一级范式的关系模式的集合,这个过程叫做规范化。
MongoDB的优点:
1. 性能优越:快速。在适量级的内存的MongoDB的性能是非常迅速的,它将热数据存储在物理内存中,使得热数据的读写变得十分快。
2. 高扩展:第三方支持丰富(与其他NoSQL相比,MongoDB也具有的优势);
3. 自身的Failover机制;
4. 弱一致性(最终一致性),更能保证用户的访问速度;
5. 文档结构的存储方式,能够更便捷的获取数据:Json存储格式;
6. 支持大容量的存储,内容GridFS;
7. 内置Sharding;
MongoDB的缺点:主要是没有事务机制;
1. MongoDB不支持事务操作(最主要的缺点);
2. MongoDB占用空间过大;
3. MongoDB没有如MySQL成熟的维护工具,对于开发和运维是比较大的挑战;
4. 不支持join操作;不支持group功能,可以通过map-reduce来进行弥补。
MongoDB各个角色的作用:
1. 配置服务器:是一个独立的mongod进程,保存集群和分片的元数据,即各分片包含了哪些数据的信息。最先开始建立,启用日志功能。像启动普通的mongod一样启动配置服务器,指定configsvr选项。不需要太多的空间和资源,配置服务器的1KB空间相当于真实数据的200MB.保存的只是数据的分布表。当服务不可用,则变成只读,无法分块、迁移数据;
2. 路由服务器:即mongos,起到一个路由的功能,供程序连接。本身不保存数据,在启动时从配置服务器加载集群信息,开启mongos进程需要配置服务器的地址,指定configdb选项;
3. 分片服务器:是一个独立的mongod进程,保存数据信息。可以是一个副本集也可以是单独的一台服务器。
分页技术:程序虚拟地址空间产生的地址叫做虚拟地址。这个虚拟地址经过页表的映射后得到程序在内存的真实物理地址。页表作为虚拟地址到页框的映射表,页框是内存的一段地址,是刚好容纳程序一个页面的地址段。当访问页表项的有效位为0时,即内存中不存在需要的页面时,就会发生缺页中断,就需要进行页面置换。
现在讨论MMU中运行机制,先解释TLB,TLB是分页技术中的高速缓存技术,缓存部分页表项,命中率高可以大大提高效率。虚拟地址一开始会被分成两部分,高位为虚拟页号,低位为偏移量。虚拟页号作为关键字在TLB中搜索是否命中页号,如果存在此页号,则可以去除页框号和低位偏移量形成真实物理地址。否则,发生TLB访问失效,此时需要访问内存中的页表,如果页表中存在此页号,则取出页框号。若没有,则发生缺页中断,陷入内核,需要执行磁盘IO,在磁盘中寻找所需页面调入内存。新调入的页面会置换页表和TLB中的一个页面。
内存地址的编号有上限。地址空间的范围和地址总线(Address Bus)的位数直接相关。CPU通过地址总线来向内存说明想要存取数据的地址。以英特尔32位的80386型CPU为例,这款CPU有32个针脚可以传输地址信息。每个针脚对应了一位。如果针脚上是高电压,那么这一位为1.如果是低电压,那么这一位是0。32位的电压高低信息通过地址总线传到内存的32个针脚,内存就能把电压高低信息转化成32位的二进制数,从而知道CPU想要的是哪个位置的数据。用十六进制表示,32位地址空间就是从0x00000000到0xFFFFFFFF。
内存的存储单元采用了随机读取存储器(RAM, Random Access Memory)。所谓的“随机读取”,是指存储器的读取时间和数据所在位置无关。与之相对,很多存储器的读取时间和数据所在位置有关。就拿磁带来说,我们想听其中的一首歌,必须转动带子。如果那首歌是第一首,那么立即就可以播放。如果那首歌恰巧是最后一首,我们快进到可以播放的位置就需要花很长时间。我们已经知道,进程需要调用内存中不同位置的数据。如果数据读取时间和位置相关的话,计算机就很难把控进程的运行时间。因此,随机读取的特性是内存成为主存储器的关键因素。
内存提供的存储空间,除了能满足内核的运行需求,还通常能支持运行中的进程。即使进程所需空间超过内存空间,内存空间也可以通过少量拓展来弥补。换句话说,内存的存储能力,和计算机运行状态的数据总量相当。内存的缺点是不能持久地保存数据。一旦断电,内存中的数据就会消失。因此,计算机即使有了内存这样一个主存储器,还是需要硬盘这样的外部存储器来提供持久的储存空间。
尽管进程和内存的地址非常紧密,但进程并不能直接访问内存。在Linux下,进程不能直接读写内存中地址为0x1位置的数据。进程中能访问的地址,只能是虚拟内存地址(Virtual Memory Address)。操作系统会把虚拟内存地址翻译成真实的内存地址。这种内存管理方式,称为虚拟内存。
每个进程都有自己的一套虚拟内存地址,用来给自己的进程空间编号。进程空间的数据同样以字节为单位,依次增加。从功能上说,虚拟内存地址和物理内存地址类似,都是为数据提供位置索引。进程的虚拟内存地址相互独立。因此,两个进程空间可以有相同的虚拟内存地址,如0x10001000。虚拟内存地址和物理内存地址又有一定的对应关系。对进程某个虚拟内存地址的操作,会被CPU翻译成对某个具体内存地址的操作。
本质上说,虚拟内存地址剥夺了应用程序自由访问物理内存地址的权利。进程对物理内存的访问,必须经过操作系统的审查。因此,掌握着内存对应关系的操作系统,也掌握了应用程序访问内存的闸门。借助虚拟内存地址,操作系统可以保障进程空间的独立性。只要操作系统把两个进程的进程空间对应到不同的内存区域,就让两个进程空间成为“老死不相往来”的两个小王国。两个进程就不可能相互篡改对方的数据,进程出错的可能性就大为减少。
另一方面,有了虚拟内存地址,内存共享也变得简单。操作系统可以把同一物理内存区域对应到多个进程空间。这样,不需要任何的数据复制,多个进程就可以看到相同的数据。内核和共享库的映射,就是通过这种方式进行的。每个进程空间中,最初一部分的虚拟内存地址,都对应到物理内存中预留给内核的空间。这样,所有的进程就可以共享同一套内核空间。共享库的情况也是类似。对于任何一个共享库,计算机只需要往物理内存中加载一次,就可以通过操作对应关系,来让多个进程共同使用。IPO中的共享内存,也有赖于虚拟内存地址。
记录对应关系最简单的办法,就是把对应关系记录在一张表中。为了让翻译速度足够地快,这个表必须加载在内存中。不过,这种记录方式惊人地浪费。如果1GB物理内存的每个字节都有一个对应记录的话,那么光是对应关系就要远远超过内存的空间。由于对应关系的条目众多,搜索到一个对应关系所需的时间也很长。这样的话,会陷入瘫痪。
因此,Linux采用了分页(paging)的方式来记录对应关系。所谓的分页,就是以更大尺寸的单位页(page)来管理内存。在Linux中,通常每页大小为4KB。Linux把物理内存和进程空间都分割成页。
内存分页,可以极大地减少所要记录的内存对应关系。我们已经看到,以字节为单位的对应记录实在太多。如果把物理内存和进程空间的地址都分成页,内核只需要记录页的对应关系,相关的工作量就会大为减少。由于每页的大小是每个字节的4000倍。因此,内存中的总页数只是总字节数的四千分之一。对应关系也缩减为原始策略的四千分之一。分页让虚拟内存地址的设计有了实现的可能。
无论是虚拟页还是物理页,一页之内的地址都是连续的。这样的话,一个虚拟页和一个物理页对应起来,页内的数据就可以按顺序一一对应。这意味着,虚拟内存地址和物理内存地址的末尾部分应该完全相同。大多数情况下,每一页有4096个字节。由于4096是2的12次方,所以地址最后12位的对应关系天然成立。我们把地址的这一部分称为偏移量(offset)。偏移量实际上表达了该字节在页内的位置。地址的前一部分则是页编号。操作系统只需要记录页编号的对应关系。
内存分页制度的关键,在于管理进程空间页和物理页的对应关系。操作系统把对应关系记录在分页表(page table)中。这种对应关系让上层的抽象内存和下层的物理内存分离,从而让Linux能灵活地进行内存管理。由于每个进程会有一套虚拟内存地址,那么每个进程都会有一个分页表。为了保证查询速度,分页表也会保存在内存中。分页表有很多种实现方式,最简单的一种分页表就是把所有的对应关系记录到同一个线性列表中。
这种单一的连续分页表,需要给每一个虚拟页预留一条记录的位置。但对于任何一个应用进程,其进程空间真正用到的地址都相当有限。我们还记得,进程空间会有栈和堆。进程空间为栈和堆的增长预留了地址,但栈和堆很少会占满进程空间。这意味着,如果使用连续分页表,很多条目都没有真正用到。因此,Linux中的分页表,采用了多层的数据结构。多层的分页表能够减少所需的空间。
我们来看一个简化的分页设计,用以说明Linux的多层分页表。我们把地址分为了页编号和偏移量两部分,用单层的分页表记录页编号部分的对应关系。对于多层分页表来说,会进一步分割页编号为两个或更多的部分,然后用两层或更多层的分页表来记录其对应关系。
多层分页表就好像把完整的电话号码分成区号。如果每个区号没有使用,只需要把此区号标记为空即可。同样,一级分页表中0x01记录为空,说明了以0x01开头的虚拟地址段都没有使用。相应的,二级表就不需要存在。正是通过这一手段,多层分页表占据的空间要比单层分页表少了很多。
多层分页表还有一个优势。单层分页表必须存在于连续的内存空间。而多层分页表的二级表,可以散布于内存的不同位置。这样,操作系统就可以利用零碎空间来存储分页表。还需要注意的是,这里简化了分页表的很多细节。最新Linux系统中的分页表多达3层,管理的内存地址也要长很多。不过,多层分页表的基本原理都是相同的。
可以在MongoDB记录中设置任何属性的索引,来实现更快的排序。
如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。
Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。
Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。
Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。
GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。
MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
MongoDB的优势:
1. MongoDB适合那些对数据库具体数据格式不明确或者数据库数据格式经常变化的需求模型,而且对开发者十分友好。
2. MongoDB官方就自带一个分布式文件系统,可以很方便地部署到服务器机群上。MongoDB里有一个Shard的概念,就是方便为了服务器分片使用的。每增加一台Shard,MongoDB的插入性能也会以接近倍数的方式增长,磁盘容量也很可以很方便地扩充。
3. MongoDB还自带了对map-reduce运算框架的支持,这也很方便进行数据的统计。
用tcpdump输出tcp/ip数据的格式如下:(The general format of a TCP protocol line is:)
src > dst: Flags [tcpflags], seq data-seqno, ack ackno, win window, urg urgent, options [opts], length len
1. src and dst are the source and destination IP addresses and ports.
2. tcpflags are some combination of S(SYN), F(FIN), P(PUSH), R(RST), U(URG), W(ECN CWR), E(ECN-Echo) or .(ACK), or none if no flags are set.
3. data-seqno describes the portion of sequence space covered by the data in this packet.
4. ackno is sequence number of the next data exptected the other direction on this connection.
5. window is the number of bytes of receive buffer space available the other direction on this connection.
6. urg indicates there is urgent data in the packet.
7. opts are TCP options(e.g., mss 1024).
8. len is the length of payload data.
------------------------------------------------------------------
MongoDB用B树做索引的原因:
B-树和B+树最重要的一个区别就是:B+树只有叶子节点存放数据,其余节点用来索引,而B-树是每个索引节点都会有Data域。这就决定了B+树更适合用来存储外部数据,也就是所谓的磁盘数据。
从MySQL(InnoDB)的角度来看,B+树是用来充当索引的,一般来说索引非常大,尤其是关系型数据库这种数据量大的索引能达到亿级别,所以为了减少内存的占用,索引也会被存储在磁盘上。
那么MySQL如何衡量查询效率呢?磁盘IO次数,B-树(B类树)的特点就是每层节点数目非常多,层数很少,目的就是为了减少磁盘IO次数,当查询数据的时候,最好的情况就是很快找到目标索引,然后读取数据,使用B+树就能很好的完成这个目的,但是B-树的每个节点都有data域(指针),这无疑增大了节点大小,即增加了磁盘IO次数(磁盘IO一次读出的数据量大小是固定的,单个数据变大,每次读出的就少,IO次数增多,一次IO非常耗时),而B+树除了叶子节点其他节点并不存储数据,节点小,磁盘IO次数就少。这是优点之一。
另外一个优点:B+树所有的Data域在叶子节点,一般来说都会进行一个优化,就是将所有的叶子节点用指针串起来。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问/遍历了。
数据库采用B树的主要原因是B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者效率太低)。
至于MongoDB为什么使用B树而不是B+树,可以从设计角度考虑,它并不是关系型数据库,而是以Json格式作为存储的NoSQL,目的就是高性能、高可用、易扩展。首先它摆脱了关系模型,上面所述的优点二就没那么强烈了,其次MySQL由于使用B+树,数据都在叶子节点上,每次查询都需要访问叶子节点,而MongoDB使用B树,所有节点都有Data域,只要找到指定索引就可以进行访问,无疑单次查询平均快于MySQL。
------------------------------------------------------------------
Consul的可靠性:
Consul是一个服务网格(微服务间的TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个分布式的、高度可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。
Consul is a service networking solution to connect and secure services across any runtime platform and public or private cloud.
Consul is a service networking tool that allows you to discover services and secure network traffic.
1. Consul-Kubernetes Deployments: Use Consul service discovery and service mesh features with Kubernetes.
2. Secure Service Communication: Secure and observe communication between your services without modifying theire code.
3. Dynamic Load Balanceing: Automate load balancer configuration with Consul and HAProxy, Nginx, or F5.
Consul是一个服务管理软件。支持多数据中心,分布式,高可用的服务发现和配置共享。采用Raft算法,用来保证服务的高可用。
Consul是由HashiCorp基于GO语言开发的支持多数据中心分布式高可用的服务发现和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。