目录
1. 微服务的优缺点分别是什么?说下你在项目开发中碰到的坑
优点
缺点
2. Dubbo 服务调用超时问题怎么解决?
3. 可以简单的概述下Netty高性能吗?
4. 使⽤过哪些jdk命令,并说明各⾃的作⽤是什么
1 jps
2 jstat
3 jinfo
4 jmap
6 jstack
7 jconsole
5. Jvm进程占用cpu过高问题排查
6. 是否了解类加载器双亲委派模型机制和破坏双亲委派模型?
7. 如何保证缓存与数据库双写时的数据一致性?
8. redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
9. Redis的cluster集群模式为什么不设计成读写分离?
10. Jdk提供的线程池有哪些,它们有什么区别,你用了哪些
11. 请说出同步线程及线程调度相关的方法?
12. 简述下AQS 原理,AQS有几种资源共享方式?
13. 分布式事务有哪几种实现方式
14. 介绍一下Atomic 原子类
15. JUC 包中的原子类是哪4类?
16. 能不能简单介绍一下AtomicInteger类的原理
17. spring 用到了那些设计模式,具体说明下?
1.简单工厂
2.工厂方法
3.单例模式
4.适配器模式
5.装饰器模式
6.代理模式
7.观察者模式
8.策略模式
9.模版方法模式
18. 可以简单讲讲JAVA 后台线程它的定义、优先级、如何设置、example、生命周期。
19. Elasticsearch对于大数据量(上亿量级)的聚合如何实现?
20. 在并发情况下,Elasticsearch如果保证读写一致?
21. 详细描述一下Elasticsearch更新和删除文档的过程
22. 介绍下Kafka,什么情况会导致kafka运行变慢?
23. Kafka 的设计是什么样的呢?消费者如何消费数据?
24. MySQL的主从复制延迟的原因及解决办法
25. 分库与分表带来的分布式困境与应对之策?
26. Hash索引和B+树所有有什么区别或者说优劣呢?
27. 你能说一下分布式Dubbo的架构设计分层画像吗?
28. Dubbo的注册中心集群挂掉,发布者和订阅者之间还能通信么?
29. Dubbo的集群容错方案有哪些?
30. Dubbo集群的负载均衡有哪些策略?
31. 远程调用设计应当包含的组件?
32. rcp 远程调用信息维度有哪些
33. 高内聚低耦合插件实现思路是什么
34. 高并发下容错策略如何选择(b)
35. Dubbo框架源码最重要的设计原则是什么?从架构设计角度谈一 下你对这个设计原则的理解。
36. 什么是SPI?请简单描述一下SPI要解决的问题。
37. 简述Dubbo的Wrapper机制
38. 为什么需要服务治理?
39. 负载均衡不应该支持的特性包括(A,B,C,D)
40. @EnableConfigurationProperties注解对于Starter的定义很 重要,请谈一上你对这个注解的认识。
41. 什么是 Netflix Feign?它的优点是什么?
42. 以下针对高可用保障说法正确的是(a,b,c,d)
43. 缓存数据仅为上下文生命周期则选择那种缓存策略(a)
44. Alibaba Seata分布式事务处理框架无法保证以下哪些场景的数据一致性(AC)
45. 有关分布式问题追踪说明正确的是(a,b,d)
46. 解决协议前后兼容的方式为(a)
47. 有线上解决OOM的经验吗?什么场景下发生的?怎么解决的?
48. 消息中间件如何选型的?
49. 容器化的底层实现原理是什么?解决了什么问题。
50. 请说一下SOA与微服务的区别?
51. 服务端限流的策略有哪些方式?
52. 微服务优点是什么?
53. 解决跨语言传输序列化产品有哪些
54. 以下说法正确的是(b,c,d)
55. A/B测试环境需要支撑的技术包含哪些?
56. 以下属于分布式事务处理模型的有(ABCD)
57. 解释一下分布式BASE理论是什么?
58. 以下关于分布式事务处理XA模型的其优、缺点正确的是(BD)
59. Alibaba Seata分布式事务处理框架无法保证以下哪些场景的数 据一致性(AC)
60. Seata能保障哪些极端情况下的数据一致性(BCD)
61. 针对Seata的AT和TCC说法正确的是(ABC)
62. Seata都有哪些组件(BCD)
63. Seata 的 Saga模式描述正确的是(ACD)
64. Seata 的 Saga模式描述错误的是(BCD)
65. 属于 Seata 的Saga服务的最佳实践的是(ABC)
66. 以下关于Saga概述描述正确的是(BCD)
67. Saga事务保证事务的特性说法正确的是(ABC)
68. 以下关于TCC和Saga描述正确的是(D)
69. 以下关于Saga的实现方式说法正确的是(ABCD)
70. 以下关于Saga的优势描述正确的是(ABC)
71. 简述一下redis的持久化。
72. 需要分布式系统的原因有哪些?
73. 什么是分布式核心系统三要素?
74. 通常情况下,导致系统可用性降低的原因有哪些?
75. 高可用架构如何量化(ABCD)
76. 微服务都有哪些高可用设计的手段?
77. 高性能架构设计手段有(ACD)。
78. 以下关于分布式锁及其设计的描述正确的是(BCD)。
79. 对高可用9999是什么?(ABD)
80. 下方属于 CP 的是?(AC)
81. 客户端限流手段都有哪些?(ABC)
82. 符合七层负载均衡设计的有哪些(CD)
83. 客服端负载均衡的几种算法(ABCDE)
84. 对注册中心说法正确的是__ ACD__(多选)
85. 容错保护本质是为了什么__ ABD__(多选)
86. 解决map的并发问题方案
87. ConcurrentHashMap使用原理
88. jvm内存结构,各个部分的特点?
1. PC寄存器:
2. 方法区:
3. 堆:
4. 栈:
89. 什么是 CAS
90. 什么是 Java 的内 存 模 型 ,Java中各个线 程是怎么彼此看到对方的变量的 ?
91. 请谈谈volatile有什么特 点 ,为什么它 能保证变量对所有线程的可见性 ?
92. 既 然 volatile 能 够 保 证 线 程 间 的 变 量 可 见 性 , 是 不 是 就 意 味着 基 于 volatile 变 量 的 运 算 就 是 并 发 安 全 的 ?
93. 请对比下volatile对Synchronized 的异同 。
94. 请谈谈ThreadLocal是怎么解决并发安全的?
95. 当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
96. 简述synchronized和java.util.concurrent.locks.Lock的异同?
97. 什么是ThreadLocal?
98. 并发与并行的区别是什么?
99. Synchronized的 原 理 是 什 么 ?
100. 什么是可重入性,为什么说Synchronized是可重入锁?
101. JVM对Java的原生锁做了哪些优化?
102. 为什么说Synchronized是非公平锁?
103. 什么是锁消除和锁粗化?
104. 乐观锁一定就是好的吗?
105. 跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?
106. ReentrantLock是如何实现可重入性的?
107. 请谈谈ReadWriteLock和StampedLock?
108. Java中的线程池是如何实现的?
109. 创建线程池的几个核心构造参数?
110. 如何在Java线程池中提交线程?
111. 在java中wait和sleep方法的不同?
112. 请说说Hystrix 的设计原则?
113. Hystrix是如何实现资源隔离的?
114. 在Nacos作为注册中心时,服务A调用服务B,如果此时服务B挂了会出现什么情况?(A、C)
115. Eureka 源码中 InstanceInfo 类中具有两个状态属性,是哪两个,你了解过它们吗?
116. Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中获取注册表信息,这个过程称为服务发现。请谈一下你对这个获取过程的认识。
117. 请简单描述一下 Reactor模型在 Netty 中的应用。
118. 请简单谈一下你对 Spring Boot 启动过程的了解。
119. 请简单谈一下你对 Spring Boot 自动配置的理解。
120. 对于 Spring Boot 来说,最为重要的注解应该就是@SpringBootApplication 了,请谈一下你对这个注解的认识。
121. META-INF/spring.factory 配置文件对于 Spring Boot 的自动配置很重要,为什么?
122. Dubbo的主要应用场景?
123. Dubbo服务注册与发现的流程?
124. Dubbo和SpringCloud中的 OpenFeign组件的区别?
125. Dubbo框架源码最重要的设计原则是什么?从架构设计角度谈一下你对这个设计原则的理解。
126. 为什么Dubbo使用URL,而不使用JSON,使用URL的好处是什么?
127. JDK的SPI机制存在什么问题?
128. Dubbo支持哪些协议,每种协议的应用场景,优缺点?
129. 对于 zk 的节点类型,谈一下你的认识?
130. 对于 zk 来说 watcher 机制非常重要,watcher 机制的工作原理是怎样的?谈一下你的认识。
131. 对于 zk 官方给出了四种最典型的应用场景,配置维护就是之一。什么是配置维护?请谈一下你的看法。
132. 使用 zk 可以实现 Master 选举,实现原理是什么?请谈一下你的看法。
133. zk 可以实现分布式锁,Redis 也可以实现。由它们实现的分布式锁有什么区别?请谈一下你的认识。
134. zk客户端维护着会话超时管理,请谈一下你对此的认识。
135. zk是 CP的,zk集群在数据同步或 leader选举时是不对外提供服务的,那岂不是用户体验非常不好?请谈一下你对此的看法。
136. zk Client在连接 zk时会发生连接丢失事件,什么是连接丢失?请谈一下你的认识。
137. zk Client在连接 zk时会发生会话转移事件,什么是会话转移?请谈一下你的认识。
138. zk Client在连接 zk时会发生会话失效事件,什么是会话失效?请谈一下你的认识。
139. zk中的会话空闲超时管理采用的是分桶策略。请谈一下你对分桶策略的认识。
140. zk中的会话空闲超时管理采用的是分桶策略。什么是会话桶?里面存放的是什么?从源码角度请谈一下你的认识。
141. zk 中的会话空闲超时管理采用的是分桶策略。会话桶中的会话会发生换桶,什么时候会进行换桶?如何换桶呢?从源码角度请谈一下你的认识。
142. zk 中的会话空闲超时管理采用的是分桶策略。该分桶策略中会话空闲超时判断发生在哪里?超时发生后的处理发生在哪里?都做了哪些处理呢?请谈一下你的认识。
143. 简述Netty中 Client端 bootstrap 中定义的 ChannelInitializer处理器的创建、添加时机,及添加到哪个 channel的 pipeline中了?
144. Netty的ChannelInboundHandler中都包含了哪一类的方法?
145. NioEventLoopGroup的本质是什么,其包含了什么重要的变量?
146. 一个 Server同时监听了多个 port 的意义是什么呢?
147. 请简述 Channel实例在创建过程中都完成了哪些重要任务。
148. NioEventLoop中有一个成员变量 wakenUp,其是一个原子布尔类型。这个变量的值对于selector选择源码的阅读很重要。它的值代表什么意义?
149. 在 ChannelInitializer类上为什么需要添加@Sharable?
150. 对于 ChannelInitializer处理器实例的创建、删除,都与一个initMap 有成员变量相关。请简述这个 initMap在ChannelInitializer 处理器中的作用。
151. 简述一下 ChannelInboundHandlerAdapter 与 SimpleChannelInboundHandler处理器的区别及应用场景。
152. ChannelPipline 与 ChannelHandlerContext 都具有fireChannelRead()方法,请简述一下它们的区别。
153. 简述消息在 inboundHandler、outboundHandler中的传递顺序,及发生异常后,异常信息在 inboundHandler、outboundHandler 中的传递顺序。
154. Spring Boot 中有一个注解@ComponentScan,请谈一下你对这个注解的认识。
155. Spring Boot 中有一个注解@EnableAutoConfiguration,请谈一下你对这个注解的认识。
156. 你了解 Spring Boot 官方给出的 Starter 工程的命名规范吗?具体是什么?
157. 我们知道@Configuration 所注解的类称为配置类,其中的@Bean 方法可以创建相应实例。请问这些@Bean 方法实例的创建顺序什么?
158. 你曾定义过 Starter 吗?简单说一下定义的大体步骤。
159. 请说说聚集(簇)索引和非聚集(簇)索引的区别?
160. 在MySQL中,为什么二叉树、红黑树、哈希表不适合作为索引数据结构?
161. 为什么MySQL推荐使用整型的自增主键?
162. 为什么非主键索引结构叶子节点存储的是主键值?
163. MySQL的binlog有几种录入格式?分别有什么区别?
164. Redis如何提高主从复制的速度和连接的稳定性?
165. 为什么Redis的Mater节点不建议写快照内存?
166. 调用 BGREWRITEAOF 重写 AOF 文件的过程中会发生哪些现象?
167. Redis 如何做内存优化?
168. Redis 回收进程如何工作的?
169. 为什么 Redis 需要把所有数据放到内存中?
170. Redis 集群方案什么情况下会导致整个集群不可用?
171. Redis 有哪些适合的场景?
(1)、会话缓存(Session Cache)
(2)、全页缓存(FPC)
(3)、队列
(4)、排行榜/计数器
(5)、发布/订阅
172. Redis 集群会有写操作丢失吗?为什么?
173. Redis 持久化数据和缓存怎么做扩容?
174. #{}和${}的区别是什么?
175. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
176. Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
177. 简述 Mybatis 的插件运行原理,以及如何编写一个插件?
178. 请简述一下MyBatis的一级、二级缓存?
179. 请详细说下JVM的内存结构以及分区?
180. 请详细说下堆区中Eden,survival (from+ to),老年代,各自的特点?
181. 请详细说下GC有几种判定方法及内容?
182. 请说下JVM中常见的垃圾回收算法?
复制算法(Copying):
标记-清除算法(Mark-Sweep)
标记-整理算法(Mark-Compact)
183. Minor GC 与Full GC 分别在什么时候发生?
184. 为什么要使用并发编程(并发编程的优点)?
185. 并发编程有什么缺点?
186. 什么是上下文切换?
187. Java 中用到的线程调度算法是什么?
188. 什么是自旋?
189. 多线程中 synchronized 锁升级的原理是什么?
190. volatile 变量和 atomic 变量有什么不同?
191. 什么是不可变对象,它对写并发应用有什么帮助?
192. 死锁与活锁的区别,死锁与饥饿的区别?
193. 多线程锁的升级原理是什么?
194. ApplicationContext通常的实现是什么?
195. Bean 工厂和 Application contexts 有什么区别?
196. 什么是Spring beans?
197. 一个Spring Bean定义 包含什么?
198. 请解释Spring框架中bean的生命周期?
199. 为什么Nginx性能这么高?
200. Nginx是如何处理一个HTTP请求的呢?
201. 在Nginx中,如何用未定义的服务器名称来阻止处理请求?
202. Nginx负载均衡策略有那些?
203. Nginx服务器上的Master和Worker进程分别是什么?
进阶面试题
• 每一个服务足够内聚,代码容易理解
• 开发效率提高,一个服务只做一件事
• 微服务能够被小团队单独开发
• 微服务是松耦合的,是有功能意义的服务
• 可以用不同的语言开发,面向接口编程
• 易于与第三方集成
• 微服务只是业务逻辑的代码,不会和HTML,CSS或者其他界面组合
开发中,两种开发模式,前后端分离,全栈工程师
• 可以灵活搭配,连接公共库/连接独立库
• 分布式系统的负责性
• 多服务运维难度,随着服务的增加,运维的压力也在增大
• 系统部署依赖
• 服务间通信成本
• 数据一致性
• 系统集成测试
• 性能监控
消费者调用服务超时会引起服务降级的发生,即从发出调用请求到 获取到提供者的响应结果这个时间超出了设定的时限。默认服务调用超时时限为 1 秒。可以 在消费者端与提供者端设置超时时限来解决。总的来说还是要设计好业务代码来减少调用时长,设置准确RPC调用的超时时间才能更好的解决这个问题。
在 IO 编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术进行处理。IO 多路复用技术通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O 多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
与 Socket 类和 ServerSocket 类相对应,NIO 也提供了 SocketChannel 和ServerSocketChannel两种不同的套接字通道实现。
jps:Java Virtual Machine Process Status Tool
查看Java进程 ,相当于Linux下的ps命令,只不过它只列出Java进程。
jstat:JVM Statistics Monitoring Tool
jstat可以查看Java程序运⾏时相关信息,可以通过它查看堆信息的相关情况
jinfo:Java Configuration Info
jinfo可以⽤来查看正在运⾏的java程序的扩展参数,甚⾄⽀持运⾏时,修改部分参数
jmap:Memory Map
jmap⽤来查看堆内存使⽤状况,⼀般结合jhat使⽤。
jstack:Java Stack Trace,jstack是java虚拟机⾃带的⼀种堆栈跟踪⼯具。jstack⽤于⽣成java虚拟
机当前时刻的线程快照。线程快照是当前java虚拟机内每⼀条线程正在执⾏的⽅法堆栈的集合,⽣成线
程快照的主要⽬的是定位线程出现⻓时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的⻓
时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调⽤堆栈,就可以知道没有响应的线程
到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃⽣成core⽂件,jstack⼯具可以⽤来获
得core⽂件的java stack和native stack的信息,从⽽可以轻松地知道java程序是如何崩溃和在程序何处
发⽣问题。另外,jstack⼯具还可以附属到正在运⾏的java程序中,看到当时运⾏的java程序的java
stack和native stack的信息, 如果现在运⾏的java程序呈现hung的状态,jstack是⾮常有⽤的。
Jconsole:Java Monitoring and Management Console,Java 5引⼊,⼀个内置 Java 性能分析器,
可以从命令⾏或在 GUI shell 中运⾏。您可以轻松地使⽤ JConsole来监控 Java 应⽤程序性能和跟踪
Java 中的代码。
首先,登录上对应的机器,通过top命令找到占用CPU过高的进程ID,也就是PID,
ps -ef | grep xxx
找到对应的服务之后,可以直接查看服务打印的日志,没有发现任何异常,所以只能通过jdk提供的JVM工具来排查问题。
先通过jdk自带的工具jstack保存一下JVM进程对应的栈信息,具体的命令是:
然后通过top命令找到占用CPU较多时间的线程
找到目标线程的PID为xx,然后将PID转换为16进制,可以使用printf命令
转化结果为:5622
然后在之前保存的JVM进程的栈信息的文件中找到nid=0x5622的线程的栈信息
通过线程的栈信息,我们可以找到该线程在执行的代码,然后通过排查这段代码找出问题所在。
双亲委派模型机制:皇子的例子
破坏双亲委派模型:JDK1.0的时候写好了一些类和类加载器,但是JDK1.2的时候才出现了双亲委派模型。比如说DriverManager去加载Driver的时候,就是破坏了双亲委派模型。
DriverManager相当于是皇上去处理
Driver实现类(第三方提供)相当于皇子去处理
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求
redis 提供 6种数据淘汰策略:
1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).
5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使
用吧!
而且绝大多数情况下,并没有读写分离的必要性。读写分离的意义在于存在大量低重要性的负载存在时,抢占更重要的写请求的资源。但是对于缓存来说,读和写是一体的,读和写出问题,造成的结果都是缓存不可用,很难说有什么读请求会比写请求更重要。
而同时,读写分离带来的主从延迟等问题,又会提升设计上的复杂性。所以大部分redis的应用场景,并不会选择读写分离。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
1)wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;注意:java5通过Lock接口提供了显示的锁机制,Lock接口中定义了加锁(lock()方法)和解锁(unLock()方法),增强了多线程编程的灵活性及对线程的协调
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设
置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制
AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、
CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某
一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
1、基于可靠消息服务(基于可靠消息中间件);
2、最大努力尝试(基于消息中间件);
3、TX-LCN(对LCN的实现);
4、X/Open DTP模型(XA规范,基于两阶段提交);
5、阿里DTS(基于TCC);
6、华为ServiceComb(对SAGA模式的实现);
7、阿里GTS(开源产品为Fescar,对XA协议改进后的实现)
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不
会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类
基本类型
使用原子的方式更新基本类型
AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean :布尔型原子类
数组类型
使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整形数组原子类
AtomicLongArray:长整形数组原子类
AtomicReferenceArray :引用类型数组原子类
引用类型
AtomicReference:引用类型原子类
AtomicStampedRerence:原子更新引用类型里的字段原子类
AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型
AtomicIntegerFieldUpdater:原子更新整形字段的更新器AtomicLongFieldUpdater:原子更新长整形字段的更新器
AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原
子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
实现方式:
BeanFactory。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
实质:
由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
实现原理:
bean容器的启动阶段:
• 读取bean的xml配置文件,将bean元素分别转换成一个BeanDefinition对象。
• 然后通过BeanDefinitionRegistry将这些bean注册到beanFactory中,保存在它的一个ConcurrentHashMap中。
• 将BeanDefinition注册到了beanFactory之后,在这里Spring为我们提供了一个扩展的切口,允许我们通过实现接口BeanFactoryPostProcessor 在此处来插入我们定义的代码。典型的例子就是:PropertyPlaceholderConfigurer,我们一般在配置数据库的dataSource时使用到的占位符的值,就是它注入进去的。
容器中bean的实例化阶段:
实例化阶段主要是通过反射或者CGLIB对bean进行实例化,在这个阶段Spring又给我们暴露了很多的扩展点:
• 各种的Aware接口 ,比如 BeanFactoryAware,对于实现了这些Aware接口的bean,在实例化bean时Spring会帮我们注入对应的BeanFactory的实例。
• BeanPostProcessor接口 ,实现了BeanPostProcessor接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
• InitializingBean接口 ,实现了InitializingBean接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
• DisposableBean接口 ,实现了BeanPostProcessor接口的bean,在该bean死亡时Spring会帮我们调用接口中的方法。
设计意义:
松耦合。 可以将原来硬编码的依赖,通过Spring这个beanFactory这个工厂来注入依赖,也就是说原来只有依赖方和被依赖方,现在我们引入了第三方——spring这个beanFactory,由它来解决bean之间的依赖问题,达到了松耦合的效果.
bean的额外处理。 通过Spring接口的暴露,在实例化bean的阶段我们可以进行一些额外的处理,这些额外的处理只需要让bean实现对应的接口即可,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean。[非常重要]
实现方式:
FactoryBean接口。
实现原理:
实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。
Spring依赖注入Bean实例默认是单例的。
Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。
总结
单例模式定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
实现方式:
SpringMVC中的适配器HandlerAdatper。
实现原理:
HandlerAdatper根据Handler规则执行不同的Handler。
实现过程:
DispatcherServlet根据HandlerMapping返回的handler,向HandlerAdatper发起请求,处理Handler。
HandlerAdapter根据规则找到对应的Handler并让其执行,执行完毕后Handler会向HandlerAdapter返回一个ModelAndView,最后由HandlerAdapter向DispatchServelet返回一个ModelAndView。
实现意义:
HandlerAdatper使得Handler的扩展变得容易,只需要增加一个新的Handler和一个对应的HandlerAdapter即可。
因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。
实现方式:
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
实质:
动态地给一个对象添加一些额外的职责。
就增加功能来说,Decorator模式相比生成子类更为灵活。
实现方式:
AOP底层,就是动态代理模式的实现。
动态代理:
在内存中构建的,不需要手动编写代理类
静态代理:
需要手工编写代理类,代理类引用被代理对象。
实现原理:
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。
织入:把切面应用到目标对象并创建新的代理对象的过程。
实现方式:
spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
具体实现:
事件机制的实现需要三个部分,事件源,事件,事件监听器
ApplicationEvent抽象类[事件]
继承自jdk的EventObject,所有的事件都需要继承ApplicationEvent,并且通过构造器参数source得到事件源。
实现方式:
Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
Resource 接口介绍
source 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。
Resource 接口主要提供了如下几个方法:
• getInputStream(): 定位并打开资源,返回资源对应的输入流。每次调用都返回新的输入流。调用者必须负责关闭输入流。
• exists(): 返回 Resource 所指向的资源是否存在。
• isOpen(): 返回资源文件是否打开,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏。
• getDescription(): 返回资源的描述信息,通常用于资源处理出错时输出该信息,通常是全限定文件名或实际 URL。
• getFile: 返回资源对应的 File 对象。
• getURL: 返回资源对应的 URL 对象。
最后两个方法通常无须使用,仅在通过简单方式访问无法实现时,Resource 提供传统的资源访问的功能。
Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。
Spring 为 Resource 接口提供了如下实现类:
• UrlResource: 访问网络资源的实现类。
• ClassPathResource: 访问类加载路径里资源的实现类。
• FileSystemResource: 访问文件系统里资源的实现类。
• ServletContextResource: 访问相对于 ServletContext 路径里的资源的实现类.
• InputStreamResource: 访问输入流资源的实现类。
• ByteArrayResource: 访问字节数组资源的实现类。
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
经典模板方法定义:
父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。
最大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。
所以父类模板方法中有两类方法:
共同的方法: 所有子类都会用到的代码
不同的方法: 子类要覆盖的方法,分为两种:
• 抽象方法:父类中的是抽象方法,子类必须覆盖
• 钩子方法:父类中是一个空方法,子类继承了默认也是空的
注:为什么叫钩子,子类可以通过这个钩子(方法),控制父类,因为这个钩子实际是父类的方法(空方法)!
Spring模板方法模式实质:
是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。
1. 定义:守护线程--也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公共服务,在没有用户线程可服务时会自动离开。
1. 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
1. 设置:通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
1. 在 Daemon 线程中产生的新线程也是 Daemon 的。
1. 线程则是 JVM 级别的,以 Tomcat 为例,如果你在 Web 应用中启动一个线程,这个线程的生命周期并不会和 Web 应用程序保持同步。也就是说,即使你停止了 Web 应用,这个线程依旧是活跃的。
1. example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
1. 生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退出了;如果还有一个或以上的非守护线程则 JVM 不会退出。
Elasticsearch 提供的首个近似聚合是cardinality 度量。它提供一个字段的基数,即该字段的distinct或者unique值的数目。它是基于HLL算法的。HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。其特点是:可配置的精度,用来控制内存的使用(更精确 = 更多内存);小的数据集精度是非常高的;我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关 。
可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication为async时,也可以通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本。
删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;
磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
答:Kafka是一种高吞吐量、分布式、基于发布/订阅的消息系统,最初由LinkedIn公司开发,使用Scala语言编写,目前是Apache的开源项目。broker:Kafka服务器,负责消息存储和转发topic:消息类别,Kafka按照topic来分类消息partition:topic的分区,一个topic可以包含多个partition,topic消息保存在各个partition上offset:消息在日志中的位置,可以理解是消息在partition上的偏移量,也是代表该消息的唯一序号Producer:消息生产者Consumer:消息消费者ConsumerGroup:消费者分组,每个Consumer必须属于一个groupZookeeper:保存着集群broker、topic、partition等meta数据;另外,还负责broker故障发现,partitionleader选举,负载均衡等功能
cpu性能瓶颈磁盘读写瓶颈网络瓶颈
答:Kafka 将消息以 topic 为单位进行归纳
将向 Kafka topic 发布消息的程序成为 producers.
将预订 topics 并消费消息的程序成为 consumer.
Kafka 以集群的方式运行,可以由一个或多个服务组成,每个服务叫做一个 broker.
producers 通过网络将消息发送到 Kafka 集群,集群向消费者提供消息
消费者每次消费数据的时候,消费者都会记录消费的物理偏移量(offset)的位置等到下次消费时,他会接着上次位置继续消费。
延迟原因:
一个服务器开放N个链接给客户端来连接的,这样有会有大并发的更新操作, 但是从服务器的里面读取binlog 的线程仅有一个, 当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从延迟。
解决办法:
实际上主从同步延迟根本没有什么一招制敌的办法, 因为所有的SQL必须都要在从服务器里面执行一遍,但是主服务器如果不断的有更新操作源源不断的写入, 那么一旦有延迟产生, 那么延迟加重的可能性就会原来越大。 当然我们可以做一些缓解的措施。
a. 我们知道因为主服务器要负责更新操作, 他对安全性的要求比从服务器高, 所有有些设置可以修改,比如syncbinlog=1,innodbflushlogattrxcommit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲syncbinlog设置为0或者关闭binlog,innodbflushlog,innodbflushlogattrx_commit 也 可以设置为0来提高sql的执行效率 这个能很大程度上提高效率。另外就是使用比主库更好的硬件设备作为slave。
b. 就是把,一台从服务器当度作为备份使用, 而不提供查询, 那边他的负载下来了, 执行relay log 里面的SQL效率自然就高了。
c. 增加从服务器喽,这个目的还是分散读的压力, 从而降低服务器负载。
1. 分布式事务问题
• 由于表分布在不同库中,不可避免会带来跨库事务问题。一般可使用"XA协议"和"两阶段提交"处理,但是这种方式性能较差,代码开发量也比较大。
• 通常做法是做到最终一致性的方案,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。
1. 分页、排序、函数计算的坑
• 分页、排序问题:
• 需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户,过程比较复杂。
1. 跨库join问题
• 两个表的数据join条件连接的数据位于不同的数据库中,这时就不能使用join的查询方式。
• 解决:
• (1)字段冗余。对a,b表需要join查询的字段,在a表中添加b表的冗余字段。
• (2)数据同步。定时同步表数据,使得需要join的两表位于同一数据库。
• (3)全局表。使用比较广泛,数据量不大,变动较少的表可以存储在所有的数据库中,减少关联查询。
• (4)ER绑定表。
• (5)系统层组装。
1. 分布式主键ID问题。
• 一个表分库后,如果使用数据库自带的自增主键,插入数据时每个表按照自己的规律自增,避免不了会主键重复的问题。
• 解决:
• (1)数据库全局表。
• 在一个库中单独创建一张表用于维护数据库主键,这张表记录了全局主键的唯一名称name、起始值startvalue、当前值curvalue和步长increatement。
• 当插入操作需要获得全局 ID 时,先利用行锁 for update锁行 ,取到当前值,步长。
• 更新表的curvalue=curvalue+increatement。
• 系统批量获取到一段ID号段[curvalue,curvalue+increatement-1]。将(id=cur_value)作为本次插入的主键值。下次insert时,自动加1,直至使用increatement次,ID号段用光。下次插入再去数据库中获取一段区间的ID号段。(id自动加1可以采用redis incr或者原子类自增的方式)。
• 通过这样使用批量获取的方式可以降低数据库的写压力,每次获取一段区间的 ID 号段,用完之后再去数据库获取,可以减轻数据库的压力。
• (2)redis incr命令
• 利用Redis 的int自增方式,结合全局表的方式,可以大大减少数据库的压力。
• (3)UUID (不建议):UUID比较长占用存储空间、UUID的无无序性每次插入时会引起索引树中数据的频繁变动和页分裂。
• (4)snowflflake算法
• (5)可以使用分库分表中间件,比如Mycat提供的分布式主键解决方案。
•
答:首先要知道Hash索引和B+树索引的底层实现原理:
hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。
B+树底层实现是多路平衡查找树,对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
那么可以看出他们有以下的不同:
hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询。因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。
hash索引不支持使用索引进行排序,原理同上。
hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是因为hash函数的不可预测,AAAA和AAAAB的索引没有相关性。
hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询。
hash索引虽然在等值查询上较快,但是不稳定,性能不可预测。当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低。
因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度。而不需要使用hash索引。
百万面试题--
Dubbo框架设计一共划分了10个层:
Service 服务接口层:该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应 的接口和实现。
config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以ServiceProxy 为中心,扩展接口为 ProxyFactory
registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为RegistryFactory, Registry, RegistryService
cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为MonitorFactory, Monitor, MonitorService
protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
可以的,启动dubbo时,消费者会从zookeeper拉取注册的生产者的地址接口等数据,缓存在本地。每 次调用时,按照本地存储的地址进行调用。
FailoverCluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更 长延迟。
FailfastCluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增 记录。FailsafeCluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
FailbackCluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。ForkingCluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作, 但需要浪费更多服务资源。可通过forks=”2″来设置最大并行数。
BroadcastCluster:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供 者更新缓存或日志等本地资源信
Dubbo提供了常见的集群策略实现,并预扩展点予以自行实现。
A、 random :随机算法,是 Dubbo 默认的负载均衡算法。存在服务堆积问题。
B、 roundrobin :轮询算法。按照设定好的权重依次进行调度。
C、 leastactive :最少活跃度调度算法。即被调度的次数越少,其优选级就越高,被调度到的机率就越 高。
D、 consistenthash :一致性 hash 算法。对于相同参数的请求,其会被路由到相同的提供者。
动态代理
集群容错
负载均衡
通信组建
接口与方法
参数
版本
分组
高内聚指框架本身不可分割的核心能力,其他能力可以通过插件式OSGI开放方式调用
a failover
b failfast
c failsafe
d failback
解析:Dubbo中的容错策略:
Failover
故障转移策略。当消费者调用提供者集群中的某个服务器失败时,其会自动尝试着调用其它服务器。而重试的次数是通过retries属性指定的。
Failfast
快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe
失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。该策略通常用于执行相对不太重要的服务。
Failback
失败自动恢复策略。消费者调用提供者失败后,Dubbo会记录下该失败请求,然后会定时发起重试请求,而定时任务执行的次数仍是通过配置文件中的retries指定的。该策略通常用于实时性要求不太高的服务。
Forking
并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
Broadcast
广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
在高并发情况下,只要在失败的时候快速响应,那么将减少请求响应时间,从而增大吞吐量。如果一味地让所有请求都成功,是达不到高并发的。容错不是不允许犯错,而是出错后怎么处理。
Dubbo在设计时具有两大设计原则:
“微内核+插件”的设计模式。内核只负责组装插件(扩展点),Dubbo的功能都是由插件实现的,也就 是 Dubbo 的所有功能点都可被用户自定义扩展类所替换。Dubbo的高扩展性、开放性在这里被充分体 现。
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。简单来说就是,在 Dubbo中,所有重要资源都是以URL的形式来描述的。
SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制。其主要是解决面向抽象编 程中上层对下层接口实现类的依赖问题,可以实现这两层间的解耦合。
Wrapper机制,即扩展类的包装机制。就是对扩展类中的SPI接口方法进行增强,进行包装,是AOP思 想的体现,是Wrapper设计模式的应用。一个SPI可以包含多个Wrapper,即可以通过多个Wrapper对 同一个扩展类进行增强,增强出不现的功能。Wrapper机制不是通过注解实现的,而是通过一套 Wrapper规范实现的。
服务治理是主要针对分布式服务框架的微服务,处理服务调用之间的关系、服务发布和发现、故障监控 与处理,服务的参数配置、服务降级和熔断、服务使用率监控等。
原因:
过多的服务URL配置困难
负载均衡分配节点压力过大的情况下也需要部署集群
服务依赖混乱,启动顺序不清晰
过多服务导致性能指标分析难度较大,需要监控
故障定位与排查难度较大
A、多协议接入
B、同城容灾
C、流量分发
D、会话保持
解析:负载均衡应该具备单一指责,仅仅属于服务选择,如果是负载均衡平台,那就需要具备以上模块.
@EnableConfigurationProperties注解在Starter定义时主要用于读取application.yml配置文件中相关 的属性,并封装到指定类型的实例中,以备Starter中的核心业务实例使用。
具体来说,它就是开启了对@ConfigurationProperties注解的Bean的自动注册,注解到Spring容器 中。这种Bean有两种注册方式:在配置类使用@Bean方法注册,或直接使用该注解的value属性进行注 册。若在配置类中使用@Bean注册,则需要在配置类中定义一个@Bean方法,该方法的返回值为“使用
@ConfigurationProperties注解标注”的类。若直接使用该注解的value属性进行注册,则需要将这个“使 用@ConfigurationProperties注解标注”的类作为value属性值出现即可。
Feign 是受到 Retrofit, JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序。 Feign 的第一个目标 是将约束分母的复杂性统一到 http apis,而不考虑其稳定性。
使用 Netflix Feign 可以使调用变得更加轻松和清洁。如果 Netflix Ribbon 依赖关系也在类路径中,那么 Feign 默认也会负责负 载平衡。
a 提供两地三中心来保证异地区域的高可用
b 提高智能调度的策略来加速业务流量消耗
c 限流策略应该越靠近用户侧越消耗资源越少
d 熔断不属于高可用保障的环节
解析:熔断也是一种高可用的保证手段,限流,降级,熔断。都是为了保证核心业务的高可用
a threadcache
b lru
c memcache
d redis
解析:上下文生命周期表示在同一线程内,lru属于缓存淘汰策略算法。
A、数据源全部节点(当前全部和无新节点)不可恢复性故障。
B、服务调用timeout
C、业务中事务的二阶段SeataServer与业务侧永不可达。
D、当业务服务出现不可恢复性部分宕机或者可恢复性全部节点宕机情况
解析:B、服务调用timeout和D、当业务服务出现不可恢复性部分宕机或者可恢复性全部节点宕机情况两种情况下Seata框架可以通过重试等操作保证数据的一致性。AC两种情况下无法保证数据一致性。
a 追踪服务与服务之间的性能问题
b 追踪服务与服务之间的调度异常问题
c 追踪服务中的dump数据
d 分析服务与服务之间调度的合理性
解析:dump数据不属于链路追踪的任务范畴
a attachment
b 新增字段
c 增加新的版本
d 协议体兼容
Dubbo协议采用固定长度的消息头(16字节)和不定长度的消息体来进行数据传输,Dubbo 协议没有预留扩展字段,没法新增标识,要解决协议前后兼容的问题,只有透过在消息体的attachment 中修改协议版本号version的方式进行实现。Dubbo协议消息体如下:
编辑
透过上图,我们看到Dubbo协议消息体被特定的序列化类型序列化后,每个部分都是一个byte[]或byte:
o 如果是请求包,则每个部分依次为:Dubbo Version、Service name、Service version、Method name、Method Parameter types、Method arguments、Attachments
o 如果是响应包,则每个部分依次为:返回值类型(0表示异常,1是正常响应,2是返回空值)、返回值。
通过jvm的内存监控,或者是异常日志的监控,来发现OOM。
解决方案,需要综合日志,以及当时的dump文件,分析dump文件查看当时的内存占用情况。进而分析具体的导致溢出的代码。
有侧重于业务的,有侧重与吞吐量的,需要综合考虑。RabbitMQ在业务量不是很大,并发在几万左右的业务使用非常合适,上手快,管理台非常直观好用。kafka一般在日志或者大数据领域使用比较多,功能比较单一。业务领域使用RocketMQ的也比较多,对于一些业务量在十万并发以上的业务,建议使用,功能也是比较丰富的。
容器化底层是通过Namespace、Cgroups以及联合文件系统来实现的,把相关的程序运行文件也都打包了起来。主要解决了将程序的运行环境也打包在一起的问题,解决了传统的程序运维部署的老大难问题,使我们的程序在开发、测试以及上线环境,无缝运行。
SOA的提出是在企业计算领域,就是要将紧耦合的系统,划分为面向业务的,粗粒度,松耦合,无状态的服务。服务发布出来供其他服务调用,一组互相依赖的服务就构成了SOA架构下的系统。
微服务是指开发一个单个 小型的但有业务功能的服务,每个服务都有自己的处理和轻量通讯机制,可以部署在单个或多个服务器上。
微服务也指一种种松耦合的、有一定的有界上下文的面向服务架构。也就是说,如果每个服务都要同时修改,那么它们就不是微服务,因为它们紧耦合在一起;如果你需要掌握一个服务太多的上下文场景使用条件,那么它就是一个有上下文边界的服务,这个定义来自DDD领域驱动设计。
连接限流
信号量限流
线程池限流
参数限流
每个微服务都很小,这样能聚焦一个指定的业务功能或业务需求。
微服务能够被小团队单独开发,这个小团队是2到5人的开发人员组成。
微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。
微服务能使用不同的语言开发。
微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如Jenkins, Hudson, bamboo 。
一个团队的新成员能够更快投入生产。
微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值。
微服务允许你利用融合最新技术。
json
xml
protobuf
thrift
a restful 属于新品种协议
b rcp 紧适合方法级别的远程调用
c http 非常适合跨语言的协议调用
d soap 适合小流量环境下的跨平台远程调用
解析:restful其实就是一种http请求,不能算是新品种协议。SOAP 是一种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息。
配置热开关,
路由导流
独立物理环境
从点到面的配置下推方式
A、XA模型
B、AT模型
C、TCC模型
D、Saga模型
解析:AT,Saga是Seata框架提供的模型。Seata同时也提供了TCC模型。
BA: Basic Availability 基本业务可用性(支持分区失败)。
S: Soft state 柔性状态(状态允许有短时间不同步,异步)
E: Eventual consistency 最终一致性(最终数据是一致的,但不是实时一致)
A、不能保证事务的ACID特性
B、与本地事务相比,XA 协议分布式事务处理的系统开销相当大
C、不支持 XA 协议的资源可以参与XA分布式事务处理
D、XA模型保证数据的强一致性,因此广泛用于可靠性要求高的金融领域
解析:A、XA模型能够保证数据的强一致性,严格事务的ACID特性;B、与本地事务相比,XA 协议需要 协调的资源多、耗时长,因此XA协议的事务处理需要的系统开销相当大C、XA事务处理模型需要所有参 与事务处理的资源实现XA协议。
A、数据源全部节点(当前全部和无新节点)不可恢复性故障。
B、服务调用timeout
C、业务中事务的二阶段SeataServer与业务侧永不可达。
D、当业务服务出现不可恢复性部分宕机或者可恢复性全部节点宕机情况
解析:B、服务调用timeout和D、当业务服务出现不可恢复性部分宕机或者可恢复性全部节点宕机情况 两种情况下Seata框架可以通过重试等操作保证数据的一致性。AC两种情况下无法保证数据一致性。
A、SeataServer(TM)存储故障
B、被调用服务宕机,网络问题等服务不可达或可恢复性重试情况
C、SeataServer不可恢复性部分或全部计算节点宕机
D、A调用B(B上线发布、重启、故障、timeout),保证A进入的流量的一致性
解析:A选型SeataServer(TM)存储故障时Seata框架无法正常运行,因此,Seata不能保证该情况下 的数据一致性。 BCD情况下Seata都可保证数据一致性。
A、AT模式基于支持本地ACID事务的关系型数据库
B、TCC要求每个分支事务都具备两阶段提交模型
C、AT和TCC都支持关系型数据库
D、TCC不需要底层数据资源的事务支持
解析:Seata 的AT属于XA的一个变种,是需要本地数据库的事务支持的。
A、事务注册器
B、事务协调器
C、事务管理器
D、资源管理器
A、saga可以适用于业务流程多的场景
B、saga能保证数据隔离性
C、saga一阶段提交本地事务,性能高
D、saga跟TCC一样,也是一种补偿事务
解析:Seata 的saga模式并不能保证数据隔离性。
A、Saga模式中,业务流程中每个参与者都提交本地事务
B、Saga模式的第一阶段和第二阶段都可以有程序自己完成
C、Saga不能适用于业务流程很多的场景
D、Saga模式需要提供prepare行为,commit行为,rollback行为
解析:Seata的Saga模型适用于长业务事务。
A、服务设计时需要允许空补偿
B、防止补偿服务比原服务先执行
C、原始服务和补偿服务需要保证幂等操作
D、Saga事务优先保证隔离性
解析:Seata的Saga并不能保证隔离性。
A、Saga和TCC一样,是一种补偿事务,需要进行try阶段。
B、Saga事务协调器负责按照顺序执行事务链中的分支事务,分支事务执行完毕,即释放资源。
C、Saga对业务有侵入
D、Saga是Seata提供的长事务解决方案
解析:Saga 和 TCC 一样,也是一种补偿事务,但它没有 try 阶段,而是把分布式事务看作一组本地事务构成的事务链。
A、Saga保证原子性:Saga协调器可以协调事务链中的本地事务要么全部提交,要么全部回滚
B、Saga保证一致性:Saga事务可以实现最终一致性
C、Saga保证持久性:基于本地事务,保证事务的持久性
D、Saga保证隔离性:Saga保证事务隔离性,本地事务提交后变更对其他事务不可见
解析:Saga 不保证事务隔离性,本地事务提交后变更就对其他事务可见了。
A、Saga是完美补偿,补偿操作会彻底清理之前的原始事务操作,用户是感知不到事务取消之前的状态 信息的
B、TCC是不完美补偿,补偿操作会留下之前原始事务操作的痕迹,需要考虑对业务上的影响
C、Saga的事务可以更好的支持异步化
D、Saga事务和TCC事务对业务实现要求防止资源悬挂
解析:D,防止资源悬挂,网络异常导致事务的正向操作指令晚于补偿操作指令到达,则要丢弃本次正
常操作,否则会出现资源悬挂问题。
A、通过状态图来定义服务调用的流程并生成json状态语言定义文件
B、状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
C、状态图json由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将 事务回滚
D、异常发生时是否进行补偿也可由用户自定义决定
A、一阶段提交本地事务,无锁,高性能
B、事件驱动架构,参与者可异步执行,高吞吐
C、补偿事务易于实现
D、保证事务隔离性
解析:ABC都属于Saga的特性,但是它不能保证事务的隔离性。
答:RDB持久化,该机制可以在指定的时间间隔内生成数据集的时间点快照。AOF持久化,记录服务器 执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令还原数据集。AOF文件中的命令全 部以Redis协议的格式来保存,新命令会被追加到文件末尾。Redis还可以再后台对AOF文件进行重写, 是的AOF文件的体积不会超过保存数据集状态所需的实际大小。
答:升级单机处理能力的性价比越来越低,单机处理能力存在瓶颈,处于稳定性和可用性的考虑。
答:高可用,高并发,高性能。
答:1. 网络故障,2. 系统配置错误,3. 中间件出现故障,比如缓存不可用。4. 高访问量,服务响应 慢,没有设置超时时间,导致主线程堵死。5. 大流量下,流量负载不均,引起个别服务负载过高,出现 雪崩效应。
A、一段时间(比如一年)的停机时间占比
B、一段时间(比如一年)的停机影响请求量占比(停机影响请求量/总请求量)
C、某功能或者服务的失败次数与总的请求次数占比
D、SLA p99 或者 p995
解析:SLA 服务等级协议,p99 1.403 表示过去的10秒内最慢的1%请求的平均延时为1.403秒。p95 过 去的10秒内最慢的5%的请求平均延时。
1年 = 365天 = 8760小时
99.9 = 8760 * (100-99.9)% = 8760 * 0.001 = 8.76小时
99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟
99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟
答:无状态化设计,幂等设计,服务保护设计,异步设计,其中服务保护设计有限流,降级,熔断。
A、⾼效的⽹络 IO 与线程模型运⽤
B、服务间调用设置尽可能长的超时时间
C、对于热点访问数据合理使用缓存
D、使用MQ将同步架构转换为异步架构
A、在CAP理论中分布式锁本质上是AP模型
B、在CAP理论中Redis集群属于AP模型,主从切换可能丢失锁信息,不适合做为分布式锁的解决方案
C、有较多的客户端频繁的申请加锁、释放锁,对于 Zookeeper集群的压力会非常大,所以Zookeeper 不适合用来实现分布式锁
D、在CAP理论中支持CP模型的Etcd,在性能和易用性上均超过Zookeeper,在数据一致性保证方面强 于Redis集群,并且Etcd还有很多适合实现分布式锁的特性,适合做分布式锁解决方案
解析:分布式锁一般会选用CP模型。
A、具有故障自动恢复能力的可用性
B、具有较高的可用性,一年出问题时间不超过52.56分钟
C、系统出问题的时间在99.99分钟以内
D、系统能够在99.99%的时间正常工作不出问题
解析:
1年 = 365天 = 8760小时
99.9 = 8760 * (100-99.9)% = 8760 * 0.001 = 8.76小时
99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟
99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟
A、zookeeper
B、kafka
C、consul
D、mysql
E、eureka
解析:我们在搭建mysql 的时候,集群保障的是高可用,一般是一种AP模型。
A、并发限流
B、随机限流
C、熔断器
D、限速器
E、网关限流
解析:RateLimiter限速器属于服务端限流手段。
A、A10
B、F5
C、LVS
D、Nginx
解析:lvs和nginx可用于7层负载均衡。
A、本地优先
B、并发数均衡
C、延迟优先
D、一致性哈希
E、权重优先
A、注册中心不能因为自身的原因破坏服务之间本身的可连通性。
B、注册中心只有cp类型。要求数据强一致性。
C、注册中心与服务调用链路是弱依赖的。只有服务发布扩容时才依赖。
D、注册中心可以不是cp类型。不一定要求数据强一致。
解析:eureka就是一种AP模型的注册中心。
A、保护服务器,保证最大限度的服务能力。
B、保护服务器,防止服务器崩溃
C、保证服务的可扩展性。
D、其他服务出现故障时,主要业务能正常提供服务
解析:容错保护并不能增强服务的扩展性。
新增面试题--
HashMap不是线程安全的;Hashtable线程安全,但效率低,因为是Hashtable是使用synchronized的,所有线程竞争同⼀把锁;而ConcurrentHashMap不不仅线程安全⽽而且效率⾼高,因为它包含一个segment数组,将数据分段存储,给每⼀段数据配⼀把锁,也就是所谓的锁分段技术。
1、工作机制(分片思想):它引入了⼀个“分段锁”的概念,具体可以理解为把⼀个大的Map拆分成N个⼩的segment,根据key.hashCode()来决定把key放到哪个HashTable中。可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
2、应用:当读>写时使用,适合做缓存,在程序启动时初始化,之后可以被多个线程访问;
3、hash冲突:
1、简介:HashMap中调⽤用hashCode()⽅方法来计算hashCode。由于在Java中两个不同的对象可能有⼀样的hashCode,所以不同的键可能有一hashCode,从而导致冲突的产生。
2、hash冲突解决:使用平衡树来代替链表,当同一hash中的元素数量超过特定的值便会由链表切换到平衡树
4、无锁读:ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是无锁读和加锁写,并且利用了分段锁(不是在所有的entry上加锁,而是在⼀一部分entry上加锁);
a. 每个线程拥有一个pc寄存器;
b. 指向下一条指令的地址。
a. 保存装载的类的元信息:类型的常量池,字段、方法信息,方法字节码;jdk6时,String等常量信息置于方法区,jdk7移到了堆中;
b. 通常和永久区(Perm)关联在⼀一起;
a. 应用系统对象都保存在java堆中;
b. 所有线程共享java堆;
c. 对分代GC来说,堆也是分代的;
a. 线程私有;
b. 栈由一系列帧组成(因此java栈也叫做帧栈);
c. 帧保存一个方法的局部变量(局部变量表)、操作数栈、常量池指针;
d. 每一次方法调⽤用创建一个帧,并压栈。
CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:它包含 3 个参数CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧的),N 表示新值。当且仅当 V 值等于 E 值时,才会将 V 的值设为 N,如果 V 值和 E 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS 返回当前 V 的真实值。
CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
Java 的 内 存 模 型 定 义 了 程 序 中 各 个 变 量 的 访 问 规 则 , 即 在 虚 拟 机 中 将变 量 存 储 到 内 存 和 从 内 存 中 取 出 这 样 的 底 层 细 节 。
此 处 的 变 量 包 括 实 例 字 段 、 静 态 字 段 和 构 成 数 组 对 象 的 元 素 , 但 是 不 包括 局 部 变 量 和 方 法 参 数 , 因 为 这 些 是 线 程 私 有 的 , 不 会 被 共 享 , 所 以 不存 在 竞 争 问 题 。
Java 中 各 个 线 程 是 怎 么 彼 此 看 到 对 方 的 变 量 的 呢 ? Java 中 定 义 了 主 内
存 与 工 作 内 存 的 概 念 :
所 有 的 变 量 都 存 储 在 主 内 存 , 每 条 线 程 还 有 自 己 的 工 作 内 存 , 保 存 了 被该 线 程 使 用 到 的 变 量 的 主 内 存 副 本 拷 贝 。
线 程 对 变 量 的 所 有 操 作 ( 读 取 、 赋 值 ) 都 必 须 在 工 作 内 存 中 进 行 , 不 能直 接 读 写 主 内 存 的 变 量 。 不 同 的 线 程 之 间 也 无 法 直 接 访 问 对 方 工 作 内 存的 变 量 , 线 程 间 变 量 值 的 传 递 需 要 通 过 主 内 存 。
关 键 字 volatile 是 Java 虚 拟 机 提 供 的 最 轻 量 级 的 同 步 机 制 。 当 一 个变 量 被 定 义 成 volatile 之 后 , 具 备 两 种 特 性 :
91.1保 证 此 变 量 对 所 有 线 程 的 可 见 性 。 当 一 条 线 程 修 改 了 这 个 变 量 的 值 , 新值 对 于 其 他 线 程 是 可 以 立 即 得 知 的 。 而 普 通 变 量 做 不 到 这 一 点 。
91.2禁 止 指 令 重 排 序 优 化 。 普 通 变 量 仅 仅 能 保 证 在 该 方 法 执 行 过 程 中 , 得 到正 确 结 果 , 但 是 不 保 证 程 序 代 码 的 执 行 顺 序 。
显然不是的 。 基 于 volatile 变 量 的 运 算 在 并 发 下 不 一 定 是 安 全 的 。volatile 变量在各个线程的工作内存 ,不存在一致性问题( 各个线程的工作内存中volatile变量 ,每次使用前都要刷新到主内 存 )。但是Java里面的运算并非原子操作 ,导致volatile变量的运算在并发下一样是不安全的 。
Synchronized既能保证可见性 ,又能保证原子性 ,而volatile只能保证可见性 ,无法保证原子性 。
ThreadLocal 和 Synchonized 都用于解决多线程并发访问 ,防止任务在共享资源上 产生冲突 。 但是ThreadLocal 与Synchronized 有本质区别 。
Synchronized 用于实现同步机制 ,是利用锁的机制使变量或代码块在某一时该只能被一个线程访问 ,是一种 “以时间换空间”的方式 。 而ThreadLocal 为每 一 个线程都提供了变量的副 本 ,使得每个线程在某一时间访问到的并不是同一个对 象 ,根除了对变量的共享 ,是一种 “以空间换时间”的方式 。
ThreadLocal这是Java提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。为每一个线程维护变量的副本,把共享数据的可见范围限制在同一个线程之内,其实现原理是,在类中有一个,用于存储每一个线程的变量的副本。
•其他方法前是否加了synchronized关键字,如果没加,则能。
•如果这个方法内部调用了wait,则可以进入其他synchronized方法。
•如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
•如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
主要相同点:Lock能完成synchronized所实现的所有功能。
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是privatestatic属性。
并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行)。
并行:单位时间内,多个任务同时执行。
Synchronized是由JVM实现的一种实现互斥同步的一种方式,如果你查看被Synchronized修饰过的程序块编译后的字节码,会发现,被Synchronized修饰过的程序块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令。
这两个指令是什么意思呢?
在虚拟机执行到monitorenter指令时,首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+1;当执行monitorexit指令时将锁计数器-1;当计数器为0时,锁就被释放了。
如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。Java中Synchronize通过在对象头设置标记,达到了获取锁和释放锁的目的。
可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。比如下面的伪代码,一个类中的同步方法调用另一个同步方法,假如Synchronized不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。
对Synchronized来说,可重入性是显而易见的,刚才提到,在执行monitorenter指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器+1,其实本质上就通过这种方式实现了可重入性。
在Java6之前,Monitor的实现完全依赖底层操作系统的互斥锁来
实现,也就是我们刚才在问题二中所阐述的获取/释放锁的逻辑。由于Java层面的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代JDK中做了大量的优化。
一种优化是使用自旋锁,即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作,避免了用户态到内核态的切换。
现在的JDK中还提供了三种不同的Monitor实现,也就是三种不同的锁:
偏向锁
轻量级锁
重量级锁
这三种锁使得JDK得以优化Synchronized的运行,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这就是锁的升级、降级。
当没有竞争出现时,默认会使用偏向锁。JVM会利用CAS操作,在对象头上的MarkWord部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,因为在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。
如果有另一线程试图锁定某个被偏斜过的对象,JVM就撤销偏斜锁,切换到轻量级锁实现。轻量级锁依赖CAS操作MarkWord来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。
锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。主要根据逃逸分析。
程序员怎么会在明知道不存在数据竞争的情况下使用同步呢?
很多不是程序员自己加入的。锁粗化:原则上,同步块的作用范围要尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。
锁粗化就是增大锁的作用域。
乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它也有缺点:
1.乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。
2.长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。
3.ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。
其实,锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。Synchronized通过在对象头中设置标记实现了这一目的,是一种JVM原生的锁实现方式,而ReentrantLock以及所有的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,并保证每个线程都能拥有对该int的可见性和原子修改,其本质是基于所谓的AQS框架。其实,锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。Synchronized通过在对象头中设置标记实现了这一目的,是一种JVM原生的锁实现方式,而ReentrantLock以及所有的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,并保证每个线程都能拥有对该int的可见性和原子修改,其本质是基于所谓的AQS框架。
ReentrantLock内部自定义了同步器Sync(Sync既实现了AQS,又实现了AOS,而AOS提供了一种互斥锁持有的方式),其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样,一样就可重入了。
虽然ReentrantLock和Synchronized简单实用,但是行为上有一定局限性,要么不占,要么独占。实际应用场景中,有时候不需要大量竞争的写操作,而是以并发读取为主,为了进一步优化并发操作的粒度,Java提供了读写锁。
读写锁基于的原理是多个读操作不需要互斥,如果读锁试图锁定时,写锁是被某个线程持有,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到有争议的数据。ReadWriteLock代表了一对锁,下面是一个基于读写锁实现的数据结构,当数据量较大,并发读多、并发写少的时候,能够比纯同步版本凸显出优势:读写锁看起来比Synchronized的粒度似乎细一些,但在实际应用中,其表现也并不尽如人意,主要还是因为相对比较大的开销。所以,JDK在后期引入了StampedLock,在提供类似读写锁的同时,还支持优化读模式。优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate方法确认是否进入了写模式,如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁。
在Java中,所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类Worker,它基于AQS实现,存放在线程池的HashSet
Java中的线程池的创建其实非常灵活,我们可以通过配置不同的参数,创建出行为不同的线程池,这几个参数包括:
corePoolSize:线程池的核心线程数。
maximumPoolSize:线程池允许的最大线程数。
keepAliveTime:超过核心线程数时闲置线程的存活时间。
workQueue:任务执行前保存任务的队列,保存由execute方法提交的Runnable任务。
线程池最常用的提交任务的方法有两种:
1.execute():ExecutorService.execute方法接收一个Runable实例,它用来执行一个任务:
2.submit():ExecutorService.submit()方法返回的是Future对象。可以用isDone()来查询Future是否已经完成,当任务完成时,它具有一个结果,可以调用get()来获取结果。也可以不用isDone()进行检查就直接调用get(),在这种情况下,get()将阻塞,直至结果准备就绪。
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
新增微服务相关面试题--
1. 对有依赖关系的应用服务在调⽤时出现的调⽤延迟和调⽤失败进⾏控制和容错保护。
1. 在复杂的分布式系统中,阻⽌服务故障而带来雪崩效应。
1. 提供快速失败和快速恢复的⽀持。
1. 提供 fallback(兜底方案) 优雅降级的⽀持。
1. ⽀持监控、报警等操作。
资源隔离:把对某一个依赖服务的所有调⽤请求,全部隔离在同一份资源池内,不会去用其它资源了。哪怕对这个依赖服务,比如说订单服务,现在订单的请求为100,但是线程池内就 10 个线程,最多就只会用这 10 个线程去执行,不会存在,对订单服务的请求,因为接口调用延时,将 tomcat 内部所有的线程资源全部耗尽。
Hystrix 实现资源隔离,主要有两种技术:
1. 线程池
1. 信号量
线程池隔离技术:并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保
tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。线程池隔离技术,是用Hystrix 自己的线程去执行调用;
信号量隔离技术:是直接让 tomcat线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
适⽤场景:
线程池技术:适合绝大多数场景,比如说我们对依赖服务的⽹络请求的调⽤和访问、需要
对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
信号量技术:适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务
逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普
通限流就可以了,因为不需要去捕获 timeout 类似的问题。
A. 服务A感知延迟
B. 服务A能够立马感知到服务B已下线
C. 服务B仍然可以被服务A调用
D. 服务B不能被服务A调用
解析:这道题考察的是Nacos的心跳检测机制。当服务B不健康时,对于Nacos和服务A来说是在B服务出现不健康后15s才知道。服务A还是会去调服务B,但是发现调不通,此时会对服务B做降级处理。
分别是 status 与 overriddenStatus。下面我依次谈一下我对它们的了解。
status 就是当前 Instance 的工作状态,其初值为 UP,表示可以正常提供服务。
overriddenStatus 用于记录外部对当前 instance 修改的状态,初值为 UNKNOWN。这个状态仅在 Server 端是有意义的。其意义就是通过修改 Server 端 instanceInfo 的 overriddenStatus的值来达到修改 Server 端对应 instanceInfo 的 status 的值的目的。 若要深入了解 overriddenStatus 的意义,就需要了解其被修改的过程。当用户通过Actuator 的 service-registry 监控终端向某个 Client 提交 POST 状态修改请求后,该请求会被Client 接收并直接再以 PUT 请求的方式提交给 Server。Server 在接收到这个请求后,会从注册表中找到该 Client 对应的 InstanceInfo,修改其 overriddenStatus,、status 为指定状态。注意,用户请求提交到 Client 后,Client 并未修改当前 Client 的 InstanceInfo 的任何状态。即 Client端的该 InstanceInfo 的状态一直为 UP 状态。
那么这个 overriddenStatus 有什么用呢?当 Server 端注册表中某 instanceInfo 的 status为 UP 时,其是可以被其它 instance 服务发现的。相反,若其 status 为非 UP,其它 instance是无法发现和使用该 instance 提供的服务的。Server 端 InstanceInfo 的 status 是可以通过用户提交状态修改请求修改 overriddenStatus 而达到修改 status 的目的的。
例如,一旦将某 InstanceInfo 的 overriddenStatus 修改为了 OUT_OF_SERVICE,则其 Server端的 status 也就成了 OUT_OF_SERVICE,此时 Consumer 是无法调用到该InstanceInfo 对应的Provider的服务的。不过需要注意,若直接从浏览器访问该Provider,其是可以正常给出响应结果的,并且,此时查看 Provider中的 InstanceInfo的status,仍为UP。
Eureka Client 从 Eureka Server 中获取注册表分为两种情况,一种是将 Server 中所有注
册信息全部下载到当前客户端本地并进行缓存,这种称为全量获取;一种是仅获取在 Server
中发生变更的注册信息到本地,然后根据变更修改本地缓存中的注册信息,这种称为增量获
取。当 Client 在启动时第一次下载就属于全量获取,而后期每 30 秒从 Server 下载一次的定
时下载属于增量下载。无论是哪种情况,Client 都是通过 Jersey 框架向 Server 发送了一个
GET 请求。只不过是,不同的获取方式,提交请求时携带的参数是不同的。
在 Netty-Server 中一般使用的是 Reactor的多线程池模型,而Netty-Client中一般使用的是 Reactor单线程池模型。具体来说,NioEventLoopGroup充当着线程池。每一个 NioEventLoopGroup 中都包含了多个NioEventLoop,而每个 NioEventLoop 又绑定着一个线程。
一个 NioEventLoop可以处理多个 Channel中的 IO操作,而其只有一个线程。所以对于这个线程资源的使用,就存在了竞争。此时为每一个 NioEventLoop都绑定了一个多跑复用器Selector,由 Selector来决定当前 NioEventLoop的线程为哪些 Channel服务。
这就是 Reactor模型在 Netty中的应用。
Spring Boot 的启动从大的方面来说需要经过以下两大阶段:加载 Spring Boot 配置文
件 application.yml,与完成自动配置。
而自动配置又包含以下两个大的步骤:加载自动配置相关的类,与扫描并注册自定义的
组件类。
加载 Spring Boot 配置文件是在启动类的 run()方法执行时加载的。其大体要经历运行环
境准备、为环境准备过程添加监听、发布环境准备事件等 6 步。
自 动 配 置 则 是 由 组 合 注 解 @SpringBootApplication 所 包 含 的 子 注 解@EnableAutoConfiguration 完成。其不仅加载了 META-INF/spring.factories 中内置的自动配置相关类,还完成了自定义类的加载与注册。若存在第三方 Starter,则其会将该 Starter 中META-INF/spring.factories 中的自动配置相关类加载并装配。
Spring Boot 与 SSM 传统开发相比,其最大的特点是自动配置,不用再在 xml 文件中做大量的配置了。自动配置的实现主要是通过自动配置类来完成的,自动配置类存在于两类位置:一个是 Spring Boot 框架中内置的,一个是从外部引入的 Starter 中。
具体来说,自动配置类的作用就是根据条件创建核心业务类的实例到 Spring 容器中,以备该 Starter 的引用工程类中注入。当然,自动配置类还有一个作用:若创建核心业务类时需要获取配置文件中的相关属性值,其也会将这些属性值封装为一个属性实例,以备核心业务类使用。当然,自动配置类需要在 META-INF/spring.factories 中注册。
所以自动配置其实就是能够自动获取配置文件中的属性并创建相应核心业务类实例。
Spring Boot 中的@SpringBootApplication 是一个组合注解。除了基本的元注解外,其
还组合了三个很重要的注解:
@SpringBootConfiguration:其等价于@Configuration,表示当前类为一个配置类。
@ComponentScan:该注解用于配置应用中 Bean 的扫描指令,但其并没有进行真正的
扫描。
@EnableAutoConfiguration:这是核心注解,其开启了自动配置。对内置自动配置类的加载及对自定义 Bean 的扫描都是由该注解完成的。
无论是Spring Boot内置的META-INF/spring.factory配置文件,还是导入Starter依赖中的 META-INF/spring.factory配置文件,对于Spring Boot自动配置来说很重要是因为,其包含了一个 key-value 对,key为EnableAutoConfiguration的全限定性类名,而value则为当前应用中所有可用的自动配置类。所有可用的自动配置类都在这里声明,所以该文件对于Spring Boot 自动配置来说很重要。
透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
Provider(提供者)绑定指定端口并启动服务•指供者连接注册中心,并发本机IP、端口、应用信息和提供服务信息发送至注册中心存储
Consumer(消费者),连接注册中心,并发送应用信息、所求服务信息至注册中心•注册中心根据消费者所求服务信息匹配对应的提供者列表发送至Consumer应用缓存。
Consumer在发起远程调用时基于缓存的消费者列表择其一发起调用。
Provider状态变更会实时通知注册中心、在由注册中心实时推送至Consumer
设计的原因:
Consumer与Provider解偶,双方都可以横向增减节点数。
注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉后,将自动切换到另一台去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间内也不会影响服务的调用服务提供者无状态,任意一台宕掉后,不影响使用
最大的区别:Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC通信。
而SpringCloud OpenFeign是基于Http协议+Rest接口调用远程过程的通信,相对来说,Http请求会有更大的报文,占的带宽也会更多。但是REST相比RPC更为灵活。
Dubbo在设计时具有两大设计原则:
“微内核+插件”的设计模式。内核只负责组装插件(扩展点),Dubbo的功能都是由插件实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展类所替换。Dubbo的高扩展性、开放性在这里被充分体现。
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。简单来说就是,在Dubbo中,所有重要资源都是以URL的形式来描述的。
关于这个问题,官方是没有相关说明的,下面我谈两点我个人的看法:
首先,Dubbo是将URL作为公共契约出现的,即希望所有扩展点都要遵守的约定。既然是约定,那么可以这样约定,也可以那样约定。只要统一就行。所以,在Dubbo创建之初,也许当时若采用了JSON作为这个约定也是未偿不可的。
其次,单从JSON与URL相比而言,都是一种简洁的数据存储格式。但在简洁的同时,URL与Dubbo应用场景的契合度更高些。因为Dubbo中URL的所有应用场景都与通信有关,都会涉及到通信协议、通信主机、端口号、业务接口等信息。其语义性要强于JSON,且对于这些数据就无需再给出相应的key了,会使传输的数据量更小。
JDK的SPI机制将所有配置文件中的实现类全部实例化,无论是否能够用到,浪费了宝贵的系统资源。
dubbo 协议 :
Dubbo 默认传输协议
连接个数:单连接
连接方式:长连接
协议:TCP
传输方式:NIO 异步传输
适用范围:传入传出参数数据包较小(建议小于 100K),消费者比提供者个数多,单一 消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
rmi:采用JDK标准的rmi协议实现,传输参数和返回参数对象需要实现Serializable接口,使用java标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议TCP。多个短连接,TCP协议传输,同步传输,适用常规的远程服务调用和rmi互操作。在依赖低版本的Common-Collections包,java序列化存在安全漏洞;
webservice:基于WebService的远程调用协议,集成CXF实现,提供和原生WebService的互操作。多个短连接,基于HTTP传输,同步传输,适用系统集成和跨语言调用;
http:基于Http表单提交的远程调用协议,使用Spring的HttpInvoke实现。多个短连接,传输协议HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器JS调用;
hessian:集成Hessian服务,基于HTTP通讯,采用Servlet暴露服务,Dubbo内嵌Jetty作为服务器时默认实现,提供与Hession服务互操作。多个短连接,同步HTTP传输,Hessian序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件;
memcache:基于memcached实现的RPC协议
redis:基于redis实现的RPC协议
每个 znode 根据节点类型的不同,具有不同的生命周期与特性。
• 持久节点:节点被创建后会一直保存在 zk 中,直到将其删除。
• 持久顺序节点:一个父节点可以为它的第一级子节点维护一份顺序,用于记录每个子节
点创建的先后顺序。其在创建子节点时,会在子节点名称后添加数字后辍,作为该子节
点的完整节点名。序号由 10 位数字组成,由这些子节点的父节点维护,从 0 开始计数。
• 临时节点:临时节点的生命周期与客户端的会话绑定在一起,会话消失则该节点就会被
自动清理。
• 临时顺序节点:添加了创建序号的临时节点。
当客户端想要监听 zk 中某节点的状态变化时,需要向该节点注册 watcher 监听。其
首先会在客户端创建一个 watcher 对象,并为其添加相应的回调。当 zk 中对应的节点发生
了相应的 watcher 事件后,zk 会向客户端发送事件通知,触发该 watcher 回调的执行。执行
完毕,该 watcher 对象销毁。若要再次监听,则需再次注册。
分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。
若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改这些服务器中的配置文件。如果我们集群服务器比较少,那么这些修改还不是太麻烦,但如果集群服务器特别多,比如某些大型互联网公司的 Hadoop 集群有数千台服务器,那么纯手工的更改这些配置文件几乎就是一件不可能完成的任务。即使使用大量人力进行修改可行,但过多的人员参与,出错的概率大大提升,对于集群所形成的危险是很大的。
zk 可以通过一种发布订阅模式对集群中的配置文件进行统一管理,这就是配置维护。
其作用与 Spring Cloud Config 的作用相同。
使用 zk 实现 Master 选举的原理是,集群中所有主机都向 zk 中创建相同路径下的某持久节点注册子节点列表变更 watcher 监听,并在该节点下持久相同名称的临时节点,谁创建成功谁就是 Master。
当 Master 宕机,该临时节点消失,此时会触发其他主机 watcher 回调的执行。watcher回调会重新抢注该节点下的临时节点,谁注册成功谁就是 Master。即可以实现 Master 宕机后的自动重新选举。
使用 zk 实现的分布式锁是 CP 的分布式锁。因为 zk 是 CP 的。在某客户端向 zk 集群中的某节点写入数据后,会等待超过半数的其它节点完成同步后,才会响应该客户端。 使用 Redis 实现的分布式锁是 AP 的分布式锁。因为 Redis 是 AP 的。在某客户端向 Redis集群中的某节点写入数据后,会立即响应该客户端,之后在 Redis 集群中会以异步的方式来同步数据。
对于 AP 的分布式锁,需要注意可能会出现的问题:一个客户端 a 在 Redis 集群的某节点 A 写入数据后,另一个节点 B 在还未同步时,另一个客户端 b 从 B 节点读取数据,没有发现 a 写入的数据。此时可能会出现问题。所以,如果某共享资源要求必须严格按照锁机制进行访问,那么就使用 zk 实现的 CP 锁。
zk 客户端维护着会话超时管理,主要管理的超时有两类:读超时与连接超时。当客户端长时间没有收到服务端的请求响应或心跳响应时,会发生读超时;当客户端发出连接请求后,长时间没有收到服务端的连接成功 ACK,此时发生连接超时。无论哪类超时,都会抛出 SessionTimeoutException异常。但若是读超时,则会发出 Ping连接请求,Ping失败则会关闭连接;若是连接超时,则会再次连接,直到重连策略不满足,关闭连接。
对于 zk 的 CP,其实对于客户端来说,一般是感知不到的。因为当客户端连接 zk 集群时,若集群恰好由于数据同步或 leader 选举而不对外提供服务,那么,客户端的此次连接是失败的。所以,其就会尝试着按照重试策略再连接。只要不超时就会一直连。而 zk 集群中的数据同步与leader 选举是很快的,在客户端重试连接过程中已经完成。此时客户端再连就会连接成功。所以,对于客户端来说,zk的 CP是感知不到的。
因为网络抖动等原因导致客户端长时间收不到服务端的心跳回复,客户端就会引发连接丢失。连接丢失会引发客户端自动从 zk地址列表中逐个尝试重新连接,直到重连成功,或按照指定的重试策略终止。
当发生连接丢失后,客户端又以原来的 sessionId重新连接上了 zk服务器。若重连上的服务器不是原来的服务器,那么客户端就需要更新本地 zk 对象中的相关信息。这就是会话转移,即回话从一个服务端转移到了另一个服务器。
服务端会由于长时间没有收到某客户端的心跳,即该客户端会话在服务端出现长时间的空闲状态时,服务器会认为该客户端已经挂了,然后会将该会话从服务器中删除。不过,在空闲超时时间范围内,该客户端又重新连接上了服务器,此时服务器并不会删除该会话,且sessionId仍是原来的。
若客户端连接丢失后,在会话空闲超时范围内没有连接上服务器,则服务器会将该会话从服务器中删除。
由于客户端的重连与服务端的会话删除是两个独立运行于不同主机的进程,所以客户端的重连与服务端的会话删除没有关系。
若在服务端将某客户端的会话删除后,而该客户端仍使用原来的 sessionId又重新连接上了服务器。那么这个会话是失效的,因为服务端根本就没有该会话的信息。此时服务端会向客户端发送关闭该连接的响应。这也是客户端知道其与服务端连接失效的途径。
分桶策略是一种查找空闲超时会话的方式,是将空闲超时时间相近的会话放到同一个会话桶中来进行管理,以减少管理的复杂度。在检查超时时,只需要检查桶中剩下的会话即可,因为在该桶的时间范围内没有超时的会话已经被移出了桶,而桶中存在的会话就是超时的会话。
zk 中的会话空闲超时管理中分桶策略中存在会话桶的概念,从源码角度来说,会话桶就是一个实例,其中维护着一个 Set集合,Set集合中存放的是会话实例。
当 zk 集群启动后,其会将时间按照固定长度划分为若干段,每一段生成一个会话桶。这些会话桶被存放在一个 Map 集合中,Map 的 value为会话桶,key 为这个会话桶的标识。这个标识就是这个会话桶对应时间段的最大边界值,即是一个时间。
当有客户端会话连接 Server成功后,Server首先会根据会话连接成功的时间及设置的空闲超时时长计算出其空闲超时时间点,然后会根据该时间点计算出该会话所对应的会话桶标识,然后根据标识找到会话桶,将该会话对象放入到该会话桶中。
zk 中的会话空闲超时管理中分桶策略中存在会话桶的概念,从源码角度来说,会话发生换桶说明会话没有发生空闲过期,只有当会话与服务器发生了交互时才不会过期。所以,当某会话与服务器发生了一次交互时,就会马上判断这个会话是否需要换桶。
如何判断是否需要换桶呢?首先会重新计算会话的空闲超时时间点,然后根据这个时间点再计算出其应该对应的会话桶标识 expireTime。再然后比较 expireTime与当前会话所在的会话桶标识 tickTime(包含在会话实例中)的大小关系。若 expireTime等于 tickTime,则说明当前会话不需要换桶;若expireTime大于tickTime,则说明该换桶了;不可能出现expireTime小于 tickTime的情况,因为时间值只会增大。
如何进行换桶呢?从会话当前所在桶中删除当前会话,然后更新当前会话所在的会话桶标识 tickTime,然后再根据这个 tickTime从会话 Map 中查找响应的会话桶。若存在,则直接将会话放入桶中。若没有,则创建一个,并放入到会话 Map 中,然后再将会话放入桶中。
zk 中的会话空闲超时管理中分桶策略中存在会话桶的概念。当服务器启动时会创建并启动一个不会停止的线程,专门用于查找过期的会话桶。然后将过期的会话桶从会话桶Map 中删除,并关闭该会话桶中的所有会话。
在 Client端 bootstrap中定义的 ChannelInitializer处理器的创建、添加时机,及添加位置如下:
• 创建时机:在 Client启动时被创建
• 添加位置:被添加到 Channel的 pipeline中,Client端没有parentChannel与childChannel的区分
• 添加时机:在 Client启动时被添加
ChannelInboundHandler中包含了像 channelRead()、channelRegistered()、channelActive()等回调方法,即由其它事件所触发的发法。
NioEventLoopGroup是一个线程池,是一个线程池 Executor,其还封装着一个总的 executor,这个 executor绑定着一个 threadFactory,同时NioEventLoopGroup还封装着一个用于存放 eventLoop 的数组。
这个总的 executor为其所包含的每一个 eventLoop创建了一个子executor,然后这个总的 executor所绑定的 threadFactory 会为每个子executor创建一个线程,用于完成相关任务。
一个 Server监听多个 port可以创建多个 parentChannel,而多个parentChannel一般会绑定多个 EventLoop,从而使 EventLoop的负载均衡了。
不过,一般情况下,并不会使 Client自行指定要连接的 Server的 port,因为这样仍无法真正实现对 parentGroup的 EventLoop 的的负载均衡。一般会使 Client访问一个路由服务器,例如 Nginx,让路由服务器根据负载均衡策略,路由到相同 Server的不同 Port上。这样,就实现了对 parentGroup中 EventLoop的负载均衡。
在创建 Channel过程中完成了以下几个重要任务:
• 生成了 Channel的 id
• 创建了真正的数据传输对象 Unsafe
• 创建了与 Channel相绑定的 ChannelPipeline
• 为 Channel创建了其配置类 ChannelConfig
NioEventLoop中有一个成员变量 wakenUp,其是一个原子布尔类型。其取值意义为:
• true:表示当前 eventLoop 所绑定的线程处于非阻塞状态,即唤醒状态
• false:表示当前 eventLoop 所绑定的线程即将被阻塞
@Sharable注解添加到一个处理器类上表示该处理器是共享的,可以被多次添加到同一个 ChannelPipeline 中,也可以被添加到多个 ChannelPipeline中。
服务端启动类中定义的 ChannelInitializer实例是在 Server启动时创建的,然后每过来一个 Client连接,就会将其添加到一个 childChannel 的pipeline中。即一个 ChannelInitializer处理器实例被添加到了多个不同的pipeline中。这也就是为什么需要在ChannelInitializer类上添加@Sharable注解的原因。
ChannelInitializer 处理器是一个共享处理器,为了减少内存的使用,在其中定义了一个成员变量 initMap。它是一个 Set集合,其中仅存放着一个元素:当前 ChannelInitializer处理器构成的节点 ctx。
虽然 ChannelInitializer处理器实例是共享的,但处理器构成的节点实例却是多例的。所以,对于 ChannelInitializer处理器的删除,其仅仅就是从 pipeline中删除了该节点 ctx,然后从这个共享的 initMap中删除了其存放的节点 ctx。但 ChannelInitializer处理器实例并没有被删除。
一个 childChannel会封装一个 ChannelPipeline,而一个 pipeline中就会有一个“由 ChannelInitializer处理器构成的” ctx实例注册。但这个ChannelInitializer 处理器实例却是共享的。
若我们使用 ChannelInboundHandlerAdapter,则需要我们自己释放 msg,而使用 SimpleChannelInboundHandler,则系统会自动释放。所以,使用哪个类作为处理器的父类,关键要看是否有需要释放的消息。
一般情况下,若 channelRead()中从对端接收到的 msg(或其封装实例)需要通过writeAndFlush()等方法发送给对端,则该 msg 不能释放,所以需要使用ChannelInboundHandlerAdapter由我们自行控制 msg的释放。当然,若根本就不需要从对端读取数据,则直接使用ChannelInboundHandlerAdapter。若使用SimpleChannelInboundHandler还需要重写 channelRead0()方法。
ChannelPipline 中的 fireChannelRead()方法会从 head 节点的channelRead()方法开始触发 pipeline中节点的 channelRead()方法;而ChannelHandlerContext中的 fireChannelRead()方法则是触发当前节点的后面节点的 channelRead()方法。
消息在 inboundHandler中 channelRead()方法中的传递顺序为,从 head节点开始逐个向后传递,直到传递给 tail 节点将该消息释放。
消息在outboundHandler中write()方法中的传递顺序为,从tail节点开始逐个向前传递,直
到传递到 head 节点,然后调用 unsafe的 write()方法完成底层写操作。
若发生异常,异常信息会从当前发生异常的节点开始调用exceptionCaught()方法,并向后面节点传递,无论后面节点是inboundHandler 还是 outboundHandler,最后传递到 tail 节点的exceptionCaught()方法,将异常消息释放。
当然,前述的向后传递或向前传递的前提是,必须要在节点方法中调用传递到下一个节点的方法,否则是无法传递的。
Spring Boot 中的注解@ComponentScan 是@SpringBootApplication 注解的一个组成注解,用于配置使用@Configuration 定义的组件的扫描指令,并且支持同时使用 Spring 配置文件中的
不过,对于这点,Spring Boot1.x 版本中仅会扫描当前标注类所在包的子孙包,不会扫描标注类所在的包。但 Spring Boot2.x 版本中默认会扫描当前注解所标注类所在的包及其子孙包。
对于一个 Spring Boot 工程,与自动配置相关的 Bean 均是由 Spring 容器管理的。而这些Bean 的类型根据创建者的不同可以分为两种:一种是由程序员自定义的组件类,例如我们自己定义的处理器类、Service 类等;另一种是框架本身已经定义好的自动配置相关类。
自定义类由@ComponentScan 指定的扫描指令进行扫描,而框架自身的自动配置相关类在 一 个 配 置 文 件 中 存 放 , 将 来 会 被 加 载 到 内 存 。 这 两 种 类 型 的 类 都 会 由 注解@EnableAutoConfiguration 交给 Spring 容器来管理。
首先我想谈一下我对@Enable 开头的这一类注解的认识,然后我再谈一下对 Spring Boot 中的注解@ EnableAutoConfiguration 的认识。
首先,@Enable 开头这一类注解一般用于开启某一项功能,是为了简化代码的导入,即使用了该类注解,就会自动导入某些类。所以该类注解是组合注解,一般都会组合一个@Import 注解,用于导入指定的多个类。@EnableXxx 的功能主要体现在这些被导入的类上,而被导入的类一般有三种:配置类、可以实现动态选择的选择器,与可以完成动态注解的注册器。
然后,Spring Boot 中的注解@ EnableAutoConfiguration 是@SpringBootApplication注解的一个组成注解,该注解用于完成自动配置相关的自定义类及内置类的加载。其本身也是一个组合注解。除了元注解外,还组合了@Import 与@AutoConfigurationPackage 两个注解。具体分工如下:
• @Import:用于加载 Spring Boot 中内置的及导入 starter 中 META-INF/spring.factory 配置中的自动配置类。
• @AutoConfigurationPackage:用于扫描、加载并注册自定义的组件类。
Spring Boot 官方给出的 Starter 工程的命名需要遵循如下规范:
• Spring 官方定义的Starter 格式为:spring-boot-starter-{name},如 spring-boot-starter-web。
• 非官方 Starter 命名格式为:{name}-spring-boot-starter,如 dubbo-spring-boot-starter。
@Configuration 所注解的类中的@Bean 方法实例的创建顺序即这些@Bean 方法的执行顺序。首先可以为这些方法的执行添加执行条件,例如使用以@ConditionOn 开头的条件注解。在这些条件都满足的情况下,这些方法的执行顺序即为其在@Configuration 注解类中的定义顺序,先定义者先执行,其对应实例先创建。
对于自定义 Starter,其工程命名格式为{name}-spring-boot-starterConfiguration。工程需要导入配置处理器依赖。然工程中需要定义如下的类与配置:
• 定义核心业务类,这是该 Starter 存在的意义。
• 定义自动配置类,其完成对核心业务类的实例化。
• 若核心业务类中需要从配置文件获取配置数据,还需要定义一个用于封装配置文件中相关属性的类。
• 定义 META-INF/spring.factories 配置文件,用于对自动配置类进行注册。
聚集(簇)索引:不是一种单独的索引类型,而是一种数据存储方式。如InnoDB的聚集(簇)索引,其实就是把索引和数据保存在一个文件中。
非聚集(簇)索引:不是聚集(簇)索引的就是非聚集(簇)索引,如MyIASM引擎下,索引文件和数据文件是分开存储的。
二叉树:在自增主键下,二叉树结构会形成斜树,查询效率并没有提高。
红黑树:红黑树也就二叉平衡树,同数据体量下,红黑树的查询效率会比二叉树高,但是企业级开发中我们知道数据量是很多的,这时候红黑树的高度也就很高,查询也会慢下来。
哈希表:查询效率非常快,需要解决哈希碰撞问题,同时这种数据结构不适用于范围查询。
1.B+Tree叶子节点是自增顺序排列的,在插入的过程中可以尽量减少页分裂,即使要进行分裂,也只会分裂很少一部分。除此之外,也能减少数据的移动,让每次数据插入都是插入到最后。
2.使用整型的好处是占用的空间小,哪怕是BigInt类型的也就只占8字节,相对于字符串类型来说,不仅节省了存储空间,在主键比较大小的时候也快,字符串类型的需要一个一个字符比较过去,直到比出大小为止。
1.如果非主键索引也存数据的话,在更新数据时,主键索引下的数据和非主键索引下的数据存在数据一致性问题。
2.因为主键索引已经存了完整数据,非主键索引不存完整数据可以节省存储空间。
有三种格式,分别是satement,row和mixed。
1、statement模式下每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
2、row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动, 基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。
3、mixed ,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。
让Master和Slave部署在同一个局域网内。
save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务。
AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,
所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个
用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户
的所有信息存储到一张散列表里面.
当一个客户端运行了新的命令,添加了新的数据。Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 Redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 Redis 的性能。在内存越来越便宜的今天,Redis 将会越来越受欢迎。
如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。
最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。
除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。
Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。
如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(SortedSet)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构 。所以, 我们要从 排序集 合中获取 到排名 最靠前的 10 个用 户–我们 称之为“user_scores”,我们只需要像下面一样执行即可:
当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你
需要这样执行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到。
最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非
常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至
用 Redis 的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。
Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
#{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性。
Dao 接口,就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement,举例:
com.mybatis.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为
com.mybatis.mappers.StudentDao 下面 id = findStudentById 的
MappedStatement。在 Mybatis 中,每一个
标签,都会被解析为一个 MappedStatement 对象。
Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao接 口 生 成 代 理 proxy 对 象 , 代 理 对 象 proxy 会 拦 截 接 口 方 法 , 转 而 执 行MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
第一种是使用
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
Mybatis 仅 可 以 编 写 针 对 ParameterHandler 、 ResultSetHandler 、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。实现 Mybatis 的 Interceptor 接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,还需要在配置文件中配置你编写的插件。
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储 ,不 同在 于其 存 储作 用域 为 Mapper(Namespace), 并且 可自 定义 存 储源 ,如Ehcache。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:
3 ) 对 于 缓 存 数 据 更 新 机 制 , 当 某 一 个 作 用 域 ( 一 级 缓 存 Session/ 二 级 缓 存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
JVM 分为堆区和栈区,还有方法区,初始化的对象放在堆里面,引用放在栈里面,class类信息常量池(static常量和static变量)等放在方法区
方法区:主要是存储类信息,常量池(static常量和static变量),编译后的代码(字节码)等数据
堆:初始化的对象,成员变量 (非static的变量),所有的对象实例和数组都要在堆上分配
栈:栈的结构是栈帧组成的,调用一个方法就压入一帧,帧上面存储局部变量表,操作数栈,方法出口等信息,局部变量表存放的是8大基础类型加上一个应用类型,所以还是一个指向地址的指针
本地方法栈:主要为Native方法服务
程序计数器:记录当前线程执行的行号
堆里面分为新生代和老生代(java8取消了永久代,采用了Metaspace),新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to,当经过一次或者多次GC之后,存活下来的对象会被移动到老年区,当JVM内存不够用的时候,会触发Full GC,清理JVM老年区。
当新生区满了之后会触发YGC,先把存活的对象放到其中一个Survice 区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎 片,因此一般会把Eden 进行完全的清理,然后整理内存。那么下次GC 的时候, 就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下, 就会使用老年代的担保,直接放到老年代里面。因为JVM 认为,一般大对象的存 活时间一般比较久远。
引用计数法:指的是如果某个地方引用了这个对象就+1,如果失效了就-1,当为0就会回收但是JVM没有用这种方式,因为无法判定相互循环引用(A引用B,B引用A)的情况。
引用链法: 通过一种GC ROOT的对象(方法区中静态变量引用的对象等-static变量)来判断,如果有一条链能够到达GC ROOT就说明,不能到达GC ROOT就说明可以回收
针对新生代,比如user0,user1,user2是刚创建的对象在伊甸园区,发现被循环引用,然后就进入s0区,这时user0不再被继续引用了,user1,user2就会被复制到s1区,然后删除s0中的对象.user4又被创建且引用次数多,则进入s1区.
优缺点:效率高,但是浪费空间.适合新生代.
算法分为“标记”和“清除”两个阶段:首先扫描所有对象标记出需要回收的对象,在标记完成后扫描回收所有被标记的对象,所以需要扫描两遍。回收效率略低,如果大部分对象是朝生夕死,那么回收效率降低,因为需要大量标记对象和回收对象,对比复制回收效率要低。
它的主要问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
回收的时候如果需要回收的对象越多,需要做的标记和清除的工作越多,所以标记清除算法适用于老年代。
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。标记整理算法虽然没有内存碎片,但是效率偏低。
我们看到标记整理与标记清除算法的区别主要在于对象的移动。对象移动不单单会加重系统担,同时需要全程暂停用户线程才能进行,同时所有引用对象的地方都需要更新(直接指针需要调整)。
所以看到,老年代采用的标记整理算法与标记清除算法,各有优点,各有缺点。
新生代内存不够用时候发生MGC 也叫YGC,JVM 内存不够的时候发生FGC。
1.充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。
2.方便进行业务拆分,提升系统并发能力和性能:在特殊的业务场景下,先天的就适合于并发编程。现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。
并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如内存泄漏、上下文切换、线程安全、死锁等问题。
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。
有两种调度模型:分时调度模型和抢占式调度模型。
分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量,那么 count++ 操作就不是原子性的。
而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
只有满足如下状态,一个对象才是不可变的:
• 它的状态不能在创建后再被修改;
• 所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this引用的逸出)。
不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
Java 中导致饥饿的原因:
1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait方法),因为其他线程总是被持续地获得唤醒。
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
• FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
• ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
• WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean
Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中
Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单例,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单例。
一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。
• Spring容器 从XML 文件中读取bean的定义,并实例化bean。
• Spring根据bean的定义填充所有的属性。
• 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
• 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
• 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
• 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
• 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
• 如果bean实现了 DisposableBean,它将调用destroy()方法。
因为他的事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决
Nginx是一个高性能Web服务器,能够同时处理大量的并发请求,它结合多进程机制和异步机制,异步机制使用的是异步非阻塞的范式。
多进程机制:
服务器每当收到一个客户端时,就有服务器主进程生成一个子进程出来和客户端建立连接,直到连接断开,子进程也就结束。
异步非阻塞机制:
每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。
当某个工作进程接收到客户端的请求后,调用IO进行处理,如果不能立即得到结果,就去处理其他请求,而客户端在此期间也无需等待响应,可以去处理其他事情。
当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起,当前处理的事物去响应客户端请求。
Server {
listen 80;
server_name "";
return 444;
}
服务器名称保留为一个空字符串,它将在没有“主机”头字段的情况下匹配请求,而一个特殊的Nginx的非标准代码444返回,从而终止连接。
轮询:默认方式
weight:权重方式
ip_hash:依据ip分配方式
least_conn:最少连接方式
fair(第三方):响应时间方式
url_hash(第三方):依据URL分配方式
Master进程:读取及评估配置和维持 ;
Worker进程:处理请求。