-
Notifications
You must be signed in to change notification settings - Fork 1
/
201810.txt
620 lines (576 loc) · 35.5 KB
/
201810.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
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
ForkJoin
ForkJoinTask
ForkJoinPool
ForkJoinTask的一个子类: CountedCompleter: 直译为计数的完成器
工作线程: ForkJointWorkerThread
CountedCompleter是一个特殊的ForkJoinTask,它会触发完成动作时,检查有没有挂起action,若没有则执行一个完成动作。
public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
...
}
最左前缀匹配原则:MySQL优化器会合适的调整顺序,使其尽量走索引。
为什么要使用联合索引?
1. 减少开销;
2. 覆盖索引;
3. 效率高;
对于explain的两种type类型: index和ref。
index: 这种类型表示MySQL会对整个索引进行扫描。要想用到这种类型的索引,对这个索引并无特别要求,只要是索引,或者某个联合索引的一部分,MySQL都可能会采用index类型的方式扫描。但是缺点是效率不高,MySQL会从索引中的第一个数据查找到最后一个数据,直到找到符合条件的某个索引。
ref:这种类型表示MySQL会根据特点的算法快速查找某个符合条件的索引,而不是对索引中的所有数据进行扫描。要想实现这种查找,索引是有要求的,要实现这种能快速查找的算法,索引就要满足特定的数据结构。简单说,索引字段的数据必须是有序的,才能实现这种类型的查找,才能利用到索引。
MySQL查询优化器会判断纠正SQL该以什么样的顺序执行效率最高,最后才生成真正的执行计划。
Dubbo的服务请求失败怎么处理:在集群调用失败时,Dubbo提供了多种容错方案,缺省为failover重试。
1. 可以自行扩展集群容错策略。(扩展接口: org.apache.dubbo.rpc.cluster.Cluster)
2. Failover Cluster: 失败自动切换,当出现失败,重试其他服务器。通常用于读操作,但重试会带来更长延迟。可通过retries=2来设置重试次数(不含第一次);
3. Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,如新增记录。
4. Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
5. Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
6. Forking Cluster: 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks=2来设置最大并行数。
7. Broadcast Cluster: 广播所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或者日志等本地资源信息。
synchronized和lock有什么区别:
1. synchronized是Java语言内置关键字。Lock是接口,通过这个接口的实现可以实现同步访问;
2. synchronized无需手动释放锁;Lock需要手动释放锁。
3. synchronized既可以加在方法上也可以加载特定代码块上,括号表示需要锁的对象。
4. 性能:资源竞争激励的情况下,lock性能会比synchronize好,竞争不激励的情况下,synchronize比lock性能好,synchronize会根据锁的竞争情况,从偏向锁-->轻量级锁-->重量级锁升级,而且编程更简单。
5. 锁的机制:synchronized是在JVM层面实现的,系统会监控锁的释放与否。Lock是JDK代码实现的,需要手动释放,在finally块中释放。可以采用非阻塞方式获取锁。
6. 公平锁:synchronized是非公平锁,Lock支持公平锁和非公平锁;
7. 可中断锁:ReentrantLock提供了lockInterruptibly()的功能,可以中断争夺锁的操作,抢锁的时候会检查是否被中断,中断直接抛出异常,退出抢锁。而synchronized只有抢锁的过程,不可干预,直到抢锁以后,才可以编码控制锁的释放;
8. 快速反馈:ReentrantLock提供了tryLock()和tryLock(timeout)功能,不等待或者限定时间等待获取锁,更灵活,可以避免死锁的发生。
9. 读写锁:ReentrantReadWriteLock类实现了读写锁的功能;而synchronized是独占锁;
10. Condition: ReentrantLock提供了比Sync更精准的线程调度工具,Condition,一个lock可以有多个Condition,比如在生产消费的业务下,一个锁通过控制生产Condition和消费Condition精准控制。
TCP通过维护一个拥塞窗口来进行拥塞控制,拥塞控制的原则是:只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入到网络中的数据包数。
TCP拥塞控制算法出现了几种不同的思路:
1. 基于丢包的拥塞控制:将丢包视为出现拥塞,采取缓慢探测的方式,逐渐增大拥塞窗口,当出现丢包时,将拥塞窗口减小,如Reno,Cubic等。
2. 基于时延的拥塞控制:将时延增加视为出现拥塞,延时增加时增大拥塞窗口,延时减小时减小拥塞窗口,如Vegas,FastTCP等。
3. 基于链路容量的拥塞控制:实时测量网络带宽和时延,认为网络上报文总量大于带宽时延乘积时出现了拥塞,如BBR。
4. 基于学习的拥塞控制:没有特定的拥塞信号,而是借助评价函数,基于训练数据,使用机器学习的方法形成一个控制策略,如Remy。
滑动窗口计数有很多使用场景,比如限流防止系统雪崩。相比较计数实现,滑动窗口实现会更加平滑,能自动消除毛刺。滑动窗口原理是在每次有访问进来时,先判断前N个单位时间内的总访问量是否超过设置的阈值,并对当前时间片上的请求数加一操作。但由于访问量的不可预见性,会发生单位时间内的前半段大量请求涌入,而后半段则拒绝所有请求的情况(通常,需要可以将单位时间切的足够小来缓解);其次,很难确定这个阈值设置在多少比较合适,只能通过经验或者模拟(如压测)来进行估计,即使是压测也很难估计的准确。集群部署中每台机器的硬件参数不同,可能导致需要对每台机器的阈值设置的都不尽相同。同一台机子在不同的时间点的系统压力也不一样(比如晚上还有一些任务,或其他的一些业务操作的影响),能够承受的最大阈值也不尽相同,无法考虑的周全。
滑动窗口模式适用于对某一资源的保护的需求上,如对DB的保护,对某一服务的调用的控制上。
Redis的命令: set <key> <value> nx px 30000; 这个命令的含义是将一个value设置到一个key中,如果不存在将会赋值并且设置超时时间为30。
-------------Redis的计数器实现限流控制---------------------------------------------
Lua脚本:
local val = redis.call('incr', KEYS[1])
local ttl = redis.call('ttl', KEYS[1])
redis.log(redis.LOG_NOTICE, 'incr '..KEYS[1]..' '..val)
if val == 1 then
redis.call('expire', KEYS[1], tonumber(ARGV[1]))
else
if ttl == -1 then
redis.call('expire', KEYS[1], tonumber(ARGV[1]))
end
end
if val > tonumber(ARGV[2]) then
return 0
end
return 1
对传入的key做incr操作,如果key首次生成,设置超时时间ARGV[1];
ttl是为防止某些key在未设置超时时间并长时间已经存在的情况下做的保护的判断;
判断当前key的val是否超过限制次数ARGV[2]。
Redis客户端:
public long limit(String key) {
return redisClient.eval(key, expire, reqCount);
}
加载lua脚本,并对外提供Redis调用的接口
注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
String value() default "";
}
AOP拦截:
@Before("@annotation(com.x.y.z.RateLimit)")
public void before(JoinPoint jp) throws Throwable {
Signature sig = jp.getSignature();
if (! (sig instanceof MethodSignature)) {
throw new IllegArgumentException("annotation must use in method");
}
MethodSignature msig = (MethodSignature)sig;
String methodName = jp.getTarget().getClass().getName() + "." + msig.getName();
String limitKey = Md5Utils.encrypt(methodName);
if (rateLimiter.limit(limitKey) != 1) {
throw new OverLimitException("Over Limit!!");
}
}
统一异常处理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OverLimitException.class)
@ResponseBody
public String overLimitExceptionHandler(OverLimitException ex) {
return ex.getMessage();
}
}
----------------------------------------------------------
redis实现滑动窗口:
1. 可以基于Redis的zset数据结构:每次当请求进来时,value固定为UUID,而score也为当前时间戳。因为score可以用来计算当前时间戳之内有多少个请求数量。而zset数据结构也提供了range方法轻易获取2个时间戳内有多少请求。另外定时或者每次调用时,清理score低于特定时间戳的记录。代码如下:
public boolean limitFlow() {
long currentTime = System.currentTimeMillis();
if (redisTemplate.hasKey("limit")) {
Integer count = redisTemplate.opsForZSet().rangeByScore("limit", currentTime - intervalTime, currentTime);
//清理老的,避免zset过大
redisTemplate.opsForZset.removeRangeByScore("limit", 0, currentTime - intervalTime);
if (count != null && count > 100) {
return false;
}
}
redisTemplate.opsForZSet().add("limit", UUID.randomUUID().toString(), currentTime);
return true;
}
2. 基于Redis的令牌桶算法: 可以采用List。一个定时任务往:redisTemplate.opsForList().rightPush("key", UUID);具体的限流函数:redisTemplate.opsForList().leftPop("key");
Spring Bean的生命周期(共12个步骤):
1. Bean实例化
2. Bean属性注入
3. 调用BeanNameAware#setBeanName(String name)
4. 调用BeanFactoryAware#setBeanFactory(BeanFactory beanFactory)
5. 调用ApplicationContextAware#setApplicationContext(ApplicationContext applicationContext)
6. 调用BeanPostProcessor#postProcessBeforeInitialization(Object bean, String beanName)
(6). 调用注解@PostConstruct的自定义初始化方法
7. 调用InitializingBean#afterPropertiesSet()
8. 调用自定义初始化方法: initMethod
9. 调用BeanPostProcessor#postProcessAfterInitialization(Object bean, String beanName)
10. 正常业务使用Bean
(10). 调用注解@PreDestroy的自定义销毁函数
11. 调用DisposableBean#destroy()
12. 调用自定义销毁函数: destroyMethod
务必注意: 使用配置(init-method)或者@Bean注解中的initMethod的初始化方法和使用注解@PostConstruct的初始化方法的执行顺序,一个在InitializingBean#afterProperties()之前,一个在之后。同样,也要注意自定义销毁方法。
真正完整的Bean生命周期如下:
1. Bean实例化
2. Bean属性注入
3. 调用BeanNameAware#setBeanName()
4. 调用BeanClassLoaderAware#setBeanClassLoader()
5. 调用BeanFactoryAware#setBeanFactory()
6. 调用EnvironmentAware#setEnvironment()
7. 调用EmbeddedValueResolverAware#setEmbeddedValueResolver()
8. 调用ResourceLoaderAware#setResourceLoader()
9. 调用ApplicationEventPublisherAware#setApplicationEventPublisher()
10. 调用MessageSourceAware#setMessageSource()
11. 调用ApplicationContextAware#setApplicationContext
12. 如果是WebApplication,则调用ServletContextAware#setServletContext()
13. 调用BeanPostProcessor#postProcessBeforeInitialization
14. 调用注解@PostConstruct的自定义初始化方法
15. 调用InitializingBean#afterPropertiesSet
16. 调用自定义初始化方法(在配置文件或者@Bean中)
17. 调用BeanPostProcessor#postProcessAfterInitialization
18. 正常业务使用Bean
19. 调用@PreDestroy的自定义销毁函数
20. 调用DisposableBean#destroy
21. 调用自定义销毁函数
Spring的生命周期有四个阶段,以及每个阶段的扩展点。
实例化,属性赋值对应构造方法和setter方法注入。
初始化和销毁是用户能自定义扩展的两个阶段
实例化: Instantiation
属性赋值:Populate
初始化: Initialization
销毁: Destruction
/* Subinterface of BeanPostProcessor that adds a before-instantiation callback, and a callback after instantiation but before explicit properties are set or autowiring occurs.
Typically used to suppress default instantiation for specific target beans, for examples to create proxies with special TargetSources(pooling targets, lazily initializing targets, etc), or to implement additional injection strategies such as field injection.
NOTE: This interface is a special purpose interface, mainly for internal use within the framework. It is recommended to implement the plain BeanPostProcessor interface as far as possible, or to derive from InstantiationAwareBeanPostProcessorAdapter in order to be shielded from extensions to this interface.
*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;
boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;
PropertyValues postProcessPropertyValues(PropertyValues, pvs, PropertyDescriptor[] pds,
Object bean, String beanName) throws BeansException;
}
Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware可以拿到BeanName,以此类推。调用时机需要注意:所有的Aware方法都是在初始化阶段之前调用的!
--------------------------------------------------------------------------------------
EasyTransaction:
A distributed transaction solution unified the usage of TCC, SAGA, FMT(seata/fescar AutoCompensation), reliable message, compensate and so on.
柔性事务,分布式事务,TCC,SAGA,可靠消息,最大努力交付消息,事务消息,补偿,全局事务,soft transaction, distribute transaction, compensation, 自动补偿。
EasyTransaction可一站式解决分布式SOA(包括微服务)的事务问题。
EasyTransaction意在解决对于每个分布式事务场景中都自行重复设计中间状态、幂等实现及重试逻辑的状况。
采纳EasyTransaction后能解决现有已发现的所有分布式事务场景,减少设计开发工作量,提高开发效率,并统一保证事务实现的可靠性。
特性如下:
1. 引入jar即用,无需独立部署协调者,无需独立部署ZK(使用extensionsuite-database-starter时);
2. 一个框架包括多种事务形态,一个框架搞定所有类型的事务;
3. 多种事务形态可混合使用;
4. 高性能,大多数业务系统瓶颈在业务数据库,若不启用框架的幂等功能,对业务数据库的额外消耗仅为25字节;
5. 框架自带幂等实现及调用错乱次序处理,大幅减轻业务开发工作量,但启用的同时会在业务数据库增加一条幂等控制行;
6. 业务代码可实现完全无侵入;
7. 支持嵌套事务;
8. 分布式事务ID可关联业务ID,业务类型,APPID,便于监控各个业务的分布式事务执行情况;
9. 整合Seata的AT模式,改造行锁使其存储到本地,改造集中式TC为ET的分布式协调。
--------------------------------------------------------------------------------------
ByteTCC:
ByteTCC is a distributed transaction manager based on the TCC(Try/Confirm/Cancel) mechanism. It is compatible with JTA specification.
Features:
- support declarative transaction management.
- support normal transaction, TCC transaction, compensating service transaction.
- support distributed transaction scenarios. e.g. multi-database, cross-applications and cross-servers transaction.
- support long live transaction.
- support dubbo framework.
- support Spring Cloud.
- provide solutions for service idempotence in framework layer.
--------------------------------------------------------------------------------------
Hmily: 高性能异步分布式事务TCC框架。
Modules:
- hmily-admin
- hmily-annotation
- hmily-apache-dubbo
- hmily-common
- hmily-core
- hmily-dashboard
- hmily-dubbo
- hmily-motan
- hmily-springcloud
- hmily-spring-boot-starter
Features:
- All Spring version are supported and Seamlessintegration.
- Provides support for the spring-cloud, dubbo, motan RPC framework.
- Provides integration of the spring boot starter.
- Support Nested transaction.
- Local transaction storage support: redis, mongodb, zookeeper, file, mysql.
- Transaction log serialization support: java, hessian, kryo, protostuff
- Spi extension: Users can customize the storage of serialization and transaction logs
Hmily为什么这么高性能?
1. 采用disruptor进行事务日志的异步读写(disruptor是一个无锁,无GC的并发编程框架)
2. 异步支持confirm/cancel方法;
3. ThreadLocal缓存的使用
4. GuavaCache的使用
--------------------------------------------------------------------------------------
TCC-Transaction:
--------------------------------------------------------------------------------------
TX-LCN:定位于一款事务协调性框架,框架本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
TC-LCN主要有两个模块:Tx-Client(TC)和Tx-Manager(TM)。TC作为微服务下的依赖,TM是独立的服务。
TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制方。
事务发起方或者参与方都由TxClient端来控制。
LCN事务模式:
1. 原理介绍:LCN模式是通过代理Connection的方式实现对本地事务的操作,然后再由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
2. 模式特点:
- 该模式对代码的嵌入式为低;
- 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块;
- 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障;
- 该模式缺陷在于代理的连接需要随事务发起方一同释放连接,增加了连接占用的时间;
TCC模式:
1. 原理介绍:TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
2. 模式特点:
- 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作;
- 该模式对有无本地事务控制都可以支持使用面广;
- 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高;
TXC模式:
1. 原理介绍:TXC模式来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存支持的SQL快照信息和创建锁。当需要回滚的时候就采用这些记录回滚数据库,目前锁实现依赖Redis分布式锁控制。
2. 模式特点:
- 同样对代码的嵌入性低;
- 仅限于对支持SQL方式的模块支持;
- 该模式由于每次执行SQL前需要先查询影响数据,因此相比LCN模式消耗资源与时间更多;
- 该模式不好占用数据库的连接资源;
--------------------------------------------------------------------------------------
TCC-Transaction, hmily, ByteTCC, EasyTransaction, tx-lcn比对:
从关注度来看:
1. TCC-Transaction: Github 4.2K Stars
2. Hmily: Github 2.6K Stars
3. ByteTCC: Github 2.1K Stars
4. EasyTransaction: Github 1.9K Stars
5. tx-lcn: Github 3K Stars
从幂等性支持:
1. EasyTransaction: 支持, 业务可选择启用(框架支持情况下影响性能)
2. ByteTCC: provide solutions for service idempotence in framework layer.
3. Hmily: 不支持;
4. TCC-Transaction: 不支持;
5. tx-lcn: 不支持;
从嵌套调用支持:
1. EasyTransaction: 支持嵌套事务
2. ByteTCC: 不支持
3. Hmily: Support Nested transaction;
4. TCC-Transaction: 不支持;
5. tx-lcn: 不支持;
从RPC框架支持:
1. EasyTransaction: Dubbo, Spring Cloud ribbon/eureka,支持可扩展
2. ByteTCC: Dubbo, Spring Cloud
3. Hmily: Dubbo, SpringCloud, Motan
4. TCC-Transaction: 不和底层使用的RPC框架耦合,即使用dubbo, thrift, web service, http都可。但提供tcc-tansaction-dubbo.jar方便dubbo框架的集成。
5. tx-lcn: Dubbo, Spring Cloud。支持可扩展。
从事务日志存储方式:
1. EasyTransaction: 支持关系数据库, Redis。支持可扩展
2. ByteTCC: File,最新版本支持MongoDB
3. Hmily: Redis, MongoDB, Zookeeper, MySQL, File
4. TCC-Transaction: FileSystemTransactionRepository, SpringJdbcTransactionRepository, RedisTransactionRepository, ZookeeperTransactionRepository.
5. tx-lcn: MySQL。
从性能角度:
1. EasyTransaction: 第三方测试,优于很多框架
2. ByteTCC: 一般
3. Hmily: 非常高
4. TCC-Transaction: 比较低
5. tx-lcn: 比较低
事务崩溃恢复支持情况:
1. EasyTransaction: 支持;有后台线程负责crash恢复,其根据"在执行分布式调用前写入的WriteAheadLog获取可能已经调用的业务"以及"跟随业务一起提交的一条框架记录以确认的业务最终提交状态"来进行最终的crash具体操作(如TCC的Confirm或者Rollback)
2. ByteTCC: 支持。
3. Hmily: ?
4. TCC-Transaction: 定期异步恢复事务,有相关参数可供设置。
5. tx-lcn: 超时后自动补偿机制会自动修复在分布式事务时间后系统存在的可修复异常。
--------------------------------------------------------------------------------------
Kubernetes-Ingress: 管理对集群中的服务(通常是HTTP)的外部访问的API对象。Ingress可以提供负载均衡、SSL终端和基于名称的虚拟主机。
分布式事务常见解决方案:
1. TCC方案
2. 2PC
3. 3PC
4. 本地消息表
5. 可靠消息最终一致性
6. 最大努力通知
7. saga事务
CQRS:命令查询责任分离是一种分离写入(命令)和读取(查询)的方式。可以专门有一个数据库用来管理写入部分。查询和读部分(也称为视图或者投影)虽然源自写入部分,但是由另外一个或多个数据库进行专门管理。大多数情况下,读部分的查询是异步计算的,这意味着两部分都不是严格一致的。
CQRS背后的思想之一是:必须承认单单依靠一个数据库几乎不可能同时有效管理读取和写入两种操作。为了侧重读操作和写操作,可以选择不同的软件供应商,对应用的数据库进行调整等措施。例如,Apache Cassandra在保存/写入数据方面是有效的,而Elasticsearch非常适合搜索的。使用CQRS实际上是一种利用解决方案优势而不是依赖唯一单一数据库的方法。
类实例化的顺序:
1. 父类静态成员和静态代码块,按在代码中出现的顺序依次执行;
2. 子类静态成员和静态代码块,按在代码中出现的顺序依次执行;
3. 父类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
4. 父类构造方法;
5. 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
6. 子类构造方法;
结论:对象初始化顺序:先静态,再实例成员,最后构造方法;每个都是先父类再子类。
/* The Void class is an uninstantiable placeholder class to hold a reference to the Class object representing the Java keyword void.
*/
public final class Void {
// The Class object representing the pseudo-type corresponding to the keyword void.
@SuppressWarnings("unchecked")
public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
// The Void class cannot be instantiated.
private Void() {}
}
/* A recursive resultless ForkJoinTask. This class establishes conventions to parameterize resultless actions as Void ForkJoinTasks. Because null is the only valid value of type Void, methods such as join always return null upon completion.
*/
public abstract class RecursiveAction extends ForkJoinTask<Void> {
private static final long serialVersionUID = 5232453952276485070L;
protected abstract void compute();
public final Void getRawResult() { return null; }
protected final void setRawResult(Void mustBeNull) {}
protected final boolean exec() {
compute();
return true;
}
}
切记:算法中的除以2, 4, 8...,要使用无符号右移 >>> 2的幂次
Eureka有什么问题:
1. 安全性: 服务注册中心的安全性需要考虑,应该对服务注册和发现的请求进行鉴权,来确保服务的安全性。Eureka天生不支持,需要额外扩展。
2. 新注册服务的快速发现问题: Eureka因为缓存设计的原因,使得服务注册之后,最迟需要120秒才能倍发现。
3. Eureka默认注册的是hostname,对于docker环境来说不适用,需要设置eureka.instance.prefer-ip-address=true。
4. Eureka只是一个AP。为了保证高可用性,分布式部署时,由于数据同步比较慢,导致一致性比较差。
5. 不支持事件通知:只支持拉模式,不支持推送模式。
6. 集群部署,并且注册实例很多时,Eureka服务端之间的信息同步会占用大量网络资源。
/* An array of object references in which elements may be updated atomically. See the JUC.atomic package specification for description of the properties of atomic variables.
*/
public class AtomicReferenceArray<E> implements java.io.Serializable {
private static final long serialVersionUID = -6209656149925076980L;
private static final Unsafe unsafe;
private static final int base;
private static final int shift;
private static final long arrayFieldOffset;
private final Object[] array; // must have exact type Object[]
static {
try {
unsafe = Unsafe.getUnsafe();
arrayFieldOffset = unsafe.objectFieldOffset(
AtomicReferenceArray.class.getDeclaredFiled("array"));
base = unsafe.arrayBaseOffset(Object[].class);
int scale = unsafe.arrayIndexScale(Object[].class);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
shift = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
private static long byteOffset(int i) {
return ((long)i << shift) + base;
}
public AtomicReferenceArray(int length) {
array = new Object[length];
}
public AtomicReferenceArray(E[] array) {
this.array = Arrays.copyOf(array, array.length, Object[].class);
}
public final int length() {
return array.length;
}
public final E get(int i) {
return getRaw(checkedByteOffset(i));
}
@SuppressWarnings("unchecked")
private E getRaw(long offset) {
return (E) unsafe.getObjectVolatile(array, offset);
}
public final void set(int i, E newValue) {
unsafe.putObjectVolatile(array, checkedByteOffset(i), newValue);
}
public final void lazySet(int i, E newValue) {
unsafe.putOrderedObject(array, checkedByteOffset(i), newValue);
}
@SuppressWarnings("unchecked")
public final E getAndSet(int i, E newValue) {
return (E) unsafe.getAndSetObject(array, checkedByteOffset(i), newValue);
}
/**
* Atomically sets the element at position {@code i} to the given
* updated value if the current value {@code ==} the expected value.
*
* @param i the index
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int i, E expect, E update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
private boolean compareAndSetRaw(long offset, E expect, E update) {
return unsafe.compareAndSwapObject(array, offset, expect, update);
}
/**
* Atomically sets the element at position {@code i} to the given
* updated value if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param i the index
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weakCompareAndSet(int i, E expect, E update) {
return compareAndSet(i, expect, update);
}
/**
* Atomically updates the element at index {@code i} with the results
* of applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param i the index
* @param updateFunction a side-effect-free function
* @return the previous value
* @since 1.8
*/
public final E getAndUpdate(int i, UnaryOperator<E> updateFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = updateFunction.apply(prev);
} while (!compareAndSetRaw(offset, prev, next));
return prev;
}
/**
* Atomically updates the element at index {@code i} with the results
* of applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param i the index
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final E updateAndGet(int i, UnaryOperator<E> updateFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = updateFunction.apply(prev);
} while (!compareAndSetRaw(offset, prev, next));
return next;
}
/**
* Atomically updates the element at index {@code i} with the
* results of applying the given function to the current and
* given values, returning the previous value. The function should
* be side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function is
* applied with the current value at index {@code i} as its first
* argument, and the given update as the second argument.
*
* @param i the index
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final E getAndAccumulate(int i, E x,
BinaryOperator<E> accumulatorFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSetRaw(offset, prev, next));
return prev;
}
/**
* Atomically updates the element at index {@code i} with the
* results of applying the given function to the current and
* given values, returning the updated value. The function should
* be side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function is
* applied with the current value at index {@code i} as its first
* argument, and the given update as the second argument.
*
* @param i the index
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final E accumulateAndGet(int i, E x,
BinaryOperator<E> accumulatorFunction) {
long offset = checkedByteOffset(i);
E prev, next;
do {
prev = getRaw(offset);
next = accumulatorFunction.apply(prev, x);
} while (!compareAndSetRaw(offset, prev, next));
return next;
}
/**
* Returns the String representation of the current values of array.
* @return the String representation of the current values of array
*/
public String toString() {
int iMax = array.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(getRaw(byteOffset(i)));
if (i == iMax)
return b.append(']').toString();
b.append(',').append(' ');
}
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException,
java.io.InvalidObjectException {
// Note: This must be changed if any additional fields are defined
Object a = s.readFields().get("array", null);
if (a == null || !a.getClass().isArray())
throw new java.io.InvalidObjectException("Not array type");
if (a.getClass() != Object[].class)
a = Arrays.copyOf((Object[])a, Array.getLength(a), Object[].class);
unsafe.putObjectVolatile(this, arrayFieldOffset, a);
}
}
跨语言的微服务:
1. K8S
2. Istio
3. gRPC
4. 不同语言对注册中心/熔断/发现/配置中心的适配
5. Service Mesh, SideCar
6. Motan
解决跨语言调用的思路无非两种:
1. 寻找一个通用的协议;
2. 使用agent完成协议的适配;
微服务架构的缺点:
1. 微服务强调了服务大小,但实际上这并没有一个统一的标准:业务逻辑应该按照什么规则划分为微服务,这本身就是一个经验工程。有些开发者主张 10-100 行代码就应该建立一个微服务。虽然建立小型服务是微服务架构崇尚的,但要记住,微服务是达到目的的手段,而不是目标。微服务的目标是充分分解应用程序,以促进敏捷开发和持续集成部署。
2. 微服务的分布式特点带来的复杂性:开发人员需要基于 RPC 或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。
3. 分区的数据库体系和分布式事务:更新多个业务实体的业务交易相当普遍,不同服务可能拥有不同的数据库。CAP 原理的约束,使得我们不得不放弃传统的强一致性,而转而追求最终一致性,这个对开发人员来说是一个挑战。
4. 测试挑战:传统的单体WEB应用只需测试单一的 REST API 即可,而对微服务进行测试,需要启动它依赖的所有其他服务。这种复杂性不可低估。
5. 跨多个服务的更改:比如在传统单体应用中,若有 A、B、C 三个服务需要更改,A 依赖 B,B 依赖 C。我们只需更改相应的模块,然后一次性部署即可。但是在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。我们需要先更新 C,然后更新 B,最后更新 A。
6. 部署复杂:微服务由不同的大量服务构成。每种服务可能拥有自己的配置、应用实例数量以及基础服务地址。这里就需要不同的配置、部署、扩展和监控组件。此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址。因此,成功部署微服务应用需要开发人员有更好地部署策略和高度自动化的水平。
总的来说(问题和挑战):API Gateway、服务间调用、服务发现、服务容错、服务部署、数据调用以及测试。