Java基础
HashMap尾插法的优点(1.8尾插, 1.7头插)
1. 减少冲突引起的频繁扩容:尾插法有助于减少链表的长度,从而减少冲突引起的性能下降。
2. 更适合并发操作:尾插法在进行并发操作时更有利,因为它减少了多个线程在同一个桶的链表头部争夺锁的可能性,提高了并发性能。
3. 保持插入顺序:尾插法有助于保持元素插入的顺序,这对于某些应用来说可能是重要的特性。
spring
spring 生命周期
1.实例化bean(构造一个半成平对象)
2.设置对象属性
3.初始化
- 检查Aware相关接口,并设置依赖
- BeanPostProcessor before() 前置处理
- InitializingBean# afterPropertiesSet
- init-method()
- BeanPostProcessor# after() 后置处理
4.使用
5.销毁
spring三级缓存
第一级缓存:singletonObjects,用于保存实例化,属性注入和初始化都已经完成的Bean实例。
第二级缓存: earlySingletonObjects,已经实例化完成的Bean,还没有进行属性注入。也就是半成品Bean实例。
第三级缓存: singletonFactories,Bean创建的工厂,以便后续Bean需要实现AOP创建代理对象。
Autowired和Resource区别
1.来源不同:
@Autowired 是Spring框架的一部分,用于实现依赖注入的功能,
@Resource 属于javax.annotation.Resource
包
2.依赖查找顺序不同:
@Resource 先按名称匹配,再按类型进行匹配
@Autowired先按类型进行匹配,再按名称进行匹配
3.注入方式不同
@Autowired 支持构造方法注入、属性注入和Setter注入,
@Resource 仅支持属性注入和Setter注入。
4.参数支持不同
5.作用位置不同
6.默认设计模式不同
@Autowired 默认为单例(Singleton)
@Resource 默认为原型(Prototype)
SpringBoot
SpringBoot 自动装配
1.SpringBoot的jar包下有一个spring.factories的配置文件,其中一个配置想是实现自动装配功能的类的全限定名配置。
2.SpringBoot项目的启动类注解SpringBootApplication是一个复合注解,他包含了EnableAutoConfiguration这个注解,这个注解会通过Import注解导入AutoConfigurationImportSelector.class,这个类中就实现加载spring.factories配置文件的能力,从中读取到了自动配置类,并加载到spring容器中。
微服务
Spring Cloud和Dubbo有什么区别
1 定位不同: springCloud微服务架构下的一站式解决方案;Dubbo主要用于服务的调用和治理。
2 生态环境不同: springCloud依靠spring平台,更完善;Dubbo相对匮乏。
3 调用方式不同: springCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。
简单来说: Spring Cloud是品牌机,Dubbo是组装机。
Eureka自我保护机制
Eureka自我保护机制是Eureka的一个重要特性,它的目的是防止Eureka Server因为短暂的网络问题或者故障而将服务实例错误地剔除出服务列表
默认情况下客户端每隔30s向注册中心发一次心跳,证明自己还活着,如果Eureka服务端超过90s没有收到某一个客户端的心跳,就任务该客户端宕机,那么新的请求就不在分发给这个客户端。但这个时候,Eureka服务端不会直接剔除该客户端实例,如果15分钟内一直没有修复,Eureka就会踢动剔除机制,把坏掉的服务剔除。
什么时候启动自我保护模式:eureka.server.renewalPercentThreshold设定了一个阈值,表示存活的微服务实例占总的注册实例的比例。低于这个比例时,会出发自我保护机制。
服务雪崩
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩
雪崩问题的解决思路
(这3种方式解决因服务故障导致的服务雪崩)
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
舱壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
(这种方式是解决因瞬间高并发流量引起的服务雪崩)
流量控制:限制业务访问的QPS,避免服务因流量的突增而故障
服务熔断
当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断。
服务降级
服务降级是指当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,
从而释放服务器资源以保证核心业务正常运作或高效运作说白了,就是尽可能的把系统资源让给优先级高的服务。
服务降级使用场景
当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,可以将一些不重要或不紧急 的服务或任务进行服务的 延迟使用 或 暂停使用
熔断VS降级
相同点:
1.目标一致 都是从可用性和可靠性出发,为了防止系统崩溃;
2.用户体验类似 最终都让用户体验到的是某些功能暂时不可用;
不同点:
触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
锁
死锁产生的条件:
互斥条件:资源在同一时间只能被一个进程使用,其他进程必须等待。
请求和保持条件:一个进程已经占有了资源,但仍请求其他资源,同时保持已获得的资源。
不可剥夺条件:进程已获得的资源在未使用完之前不能被剥夺,只能在使用完时由进程自行释放。
循环等待条件:存在一个进程链,其中每个进程都等待下一个进程所需的资源,形成环路。
解决死锁的方法:
破坏“请求和保持”条件:让进程一次性申请所有所需资源,或者在申请新资源时释放已获得的资源。
破坏“不可剥夺”条件:允许进程在资源未使用完时被剥夺,或者允许资源被抢占。
破坏“循环等待”条件:通过资源编号确保资源申请的顺序性,避免进程链的形成。
死锁的检测:通过进程和资源的监控,及时发现死锁并采取措施。
死锁的解除:抢占资源或终止(撤销)死锁进程,以打破死锁状态。
线程安全的特性
原子性: 是指某些操作时不可分割的,必须连续完成,Java种保证原子性就会用到锁或者CAS操作等
可见性:可见性是指一个线程对共享变量的修改,另外一个线程能够立刻看到;Java方面提供了两个关键字来保证多线程情况下共享变量的可见性方案
有序性:有序性是指程序在执行的时候,程序的代码执行顺序和语句的顺序是一致的。在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序;重排序不会影响单线程的执行结果,但是在并发情况下,可能会出现诡异的BUG。Java 中可以使用 volatile 来保证顺序性,synchronized 和 Lock 也可以来保证有序性,和保证原子性的方式一样,通过同一段时间只能一个线程访问来实现的。
锁升级机制
1)当对锁象初始化后,还未有任何线程来竞争,此时为无锁状态。其中锁标志位
为01,偏向锁标志位
为0
2)当有一个线程来竞争锁,锁对象第一次被线程获取,锁标志位
依然为01,偏向锁标志位
会被置为1,此时锁进入偏向模式。同时,使用CAS操作将此获取锁对象的线程ID设置到锁对象的Mark Word中,持有偏向锁,下次再可直接进入。
3)此时,线程B尝试获取锁,发现锁处于偏向模式,但MarkWord中存储的不是本线程ID。那么线程B使用CAS操作尝试获取锁,这时锁是有可能获取成功的,因为上一个持有偏向锁的线程不会主动释放偏向锁。如果线程B获取锁成功,则会将MarkWord中的线程ID设置为本线程的ID。但若线程B获取锁失败,则会执行下述操作。
4)偏向锁抢占失败,表明锁对象存在竞争,则会先撤销偏向模式,偏向锁标志位
重新被置为0,准备升级轻量级锁。首先将在当前线程的帧栈中开辟一块锁记录空间(Lock Record),用于存储锁对象当前的Mark Word拷贝。然后,使用CAS操作尝试把锁对象的Mark Word更新为指向帧栈中Lock Record的指针,CAS操作成功,则代表获取到锁,同时将锁标志位
设置为00,进入轻量级锁模式。若CAS操作失败,则进入下述操作。
5)刚一出现CAS竞争轻量级锁失败时,不会立即膨胀为重量级锁,而是采用CAS自旋的方式,不断重试,尝试抢锁。JDK1.6中,默认开启自旋,自旋10次,可通过-XX:PreBlockSpin更改自旋次数。JDK1.6对于只能指定固定次数的自旋进行了优化,采用了自适应的自旋,重试机制更加智能。
6)只有通过自旋依然获取不到锁的情况,表明锁竞争较为激烈,不再适合额外的CAS操作消耗CPU资源,则直接膨胀为重量级锁,锁标志位
设置为10。在此状态下,所有等待锁的线程都必须进入阻塞状态。
MySQL/MyBatis
SQL查询语句规范:
select <目标列名序列>
[into <存放结果新表或视图>]
From <数据源>
[where <检索条件表达式>]
[group by <分组依据列>]
[having <对分组结果提取条件>]
[Order by <排序依据列> [asc|desc]]
-- 案例:
select name, COUNT(name)
from t_test
WHERE score > 80
GROUP BY `name`
HAVING COUNT(name) > 2
order by name asc;
MySql的SQL语句执行流程
SQL语句->查询缓存->解析器->优化器->执行器
Innodb数据结构:
InnoDB使用B+树数据结构存储表和索引
SQL优化:
MyBatis的执行流程
1、加载核心配置文件,封装为Configuration对象
2、加载mapper配置
3、构建回话工厂:SqlSessionFactory
4、创建会话对象:SqlSession
5、Executor执行器读取映射信息MappedStatement
6、ParameterHandler处理参数映射
7、ResultHandler处理结果映射
Mysql性能优化思路
MySQL的查询分析器
通过explain 我们可以知道以下信息:
1.表的读取顺序
2.数据读取操作的类型
3.哪些索引可以使用,哪些索引实际使用了
4.表之间的引用
5.每张表有多少行被优化器查询等信息
id列
select_type列
table列
type列:关联类型或访问类型,
优劣:system > const > eq_ref > ref > range > index > ALL
NULL:mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。
possible_keys列:
这一列显示查询可能使用哪些索引来查找
key列:
这一列显示mysql实际采用哪个索引来优化对该表的访问
key_len列
这一列显示了mysql在索引里使用的字节数(可以由此计算使用了索引中的哪些列)
ref列:
这一列显示了在key列记录的索引中,表查找值所用到的列或常量
rows列:
这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数
Extra列
JVM
JVM调优工具
JMAP、JSTAT和JSTACK
比如:使用JMap和JStack,排查CPU使用率100%的问题
事务
Spring的事务传播机制
1.required(Spring默认的传播机制。)
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务
2.supports
,则加入该事务;否则,以非事务的方式运行
3.mandatory
表示当前方法必须在一个事务中被调用,否则将抛出异常
4.requires_new
表示当前方法必须开启一个新事务运行,如果当前存在事务,则挂起该事务
5.not_supported
表示当前方法不应该在事务中运行,如果存在事务,则挂起该事务
6.never
表示当前方法不应该在事务中运行,如果存在事务,则抛出异常
7.nested
表示当前方法必须在一个嵌套事务中运行,如果当前存在事务,则在该事务内开启一个嵌套事务;如果当前没有事务,则创建一个新事务
操作系统/协议
什么是TCP
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
什么是TCP粘包拆包
粘包:当多个数据包被粘合在一起一次性发送时,接收端可能无法正确区分每个数据包的边界,导致接收到的数据出现错位或混乱。1
拆包:当一个数据包过大,超过底层缓冲区大小或TCP等待时长时,TCP协议会将该数据包拆分成多个小包进行发送,以确保每个小包都能在缓冲区大小限制内发送出去
TCP粘包拆包产生原因:
操作系统在发送TCP数据时,会使用一个底层缓冲区。如果发送的数据量较小,不足以填满缓冲区,TCP可能会将多个小数据包合并成一个发送,造成粘包。
反之,如果数据量较大,超过了缓冲区大小,TCP会将其拆分成多个小包发送,造成拆包
粘包拆包解决方案
1.固定长度:为每个数据包设定固定的长度,如果数据不足,通过补充空格或其他方式补全至指定长度。(缺点:增加数据的体积)
2.特殊分隔符:在每个数据包的末尾添加一个固定的分隔符,这样即使数据包被拆分,也可以通过找到分隔符来重新组合数据包。
3.消息长度头部:将消息分为头部和消息体两部分,头部中保存有整个消息的长度信息,只有当读取到足够长度的消息后,才认为接收到了一个完整的消息
实现零拷贝的几种方式
sendfile、mmap、Direct IO、splice
架构设计
如何设计一个高并发系统
从3个层面解决这个问题:
1、性能
2、扩展性
3、可用性
性能方面
1.要考虑数据库的性能,会涉及到数据库的调优,比如索引的使用,数据库事务和锁,以上是开发细节方面的考量。当一台数据库不足以支撑时流量高峰时,会考虑队DB进行分库分表和读写分离的处理,比如设计一主多从,一主库负责较少的写请求,多从库负责较多的写请求。当单表数据量过大时没需要进行分表设计,比如,根据不同的id存入不同的表。
2.要考虑JVM调优:比如使用更高级的垃圾回收机制
3.增加缓存的使用,比如最常使用的redis,缓存数据库查询出的热点数据,避免每次请求直接打到数据库中,提高访问处理速度,这其中会涉及到缓存的双写一直性处理。
4.对于一部分消息,可以记性异步处理,比如使用MQ,使用MQ的话就会设计到MQ消息的可靠5.对于数据库连接等,必须考虑使用数据库连接池,避免高频率的创建、关闭数据库连接。同理,单个进程内,也需要使用数据库连接池来避免频繁的创建关闭线程。
6.如果资金充足,也可以考虑硬件上的提升
扩展性方面
1.系统应该要能便捷的自动扩容缩容,比如使用docker进行容器化部署,使用k8s进行容器编排。
2.在系统的扩展性方面就需要考虑到系统的负责均衡,比如常见的负载均衡算法:随机,轮训,加权的轮训和随机等
高可用性
1.对于多数系统,流量峰值不是一层不变的,也许会在某一段时间内突然产生大量的请求,如果我的系统出现流量高峰,超出了某个节点的处理能力,导致该节点停止运行,那么为了避免因为该节点引起雪崩问题等,就需要就行熔断降级,比如SpringCloud的hystrix组件,进行服务熔断,当调用已经挂掉的微服务时,不再发出请求,而是直接调用预先写好的错误处理方法。
2.对于有预感的流量高峰,在不能对系统进行扩展时,可以采用服务降级的策略,比如只保留系统的主要业务,非主要业务可以调用预先设定的降级逻辑,降低这些业务对系统资源的占用。