java面试宝典
2024-07-10 10:44:00 1 举报
AI智能生成
Java是一种广泛使用的面向对象编程语言,它具有简单、跨平台、多线程、动态等特点。在Java中,类是创建对象的模板,对象是类的实例。Java还提供了封装、继承、多态等面向对象编程特性,以及丰富的API库,如Java Collections Framework、Java I/O、Java Swing等。在Java编程中,关键字如public、private、protected、final等用于定义访问权限和类、变量、方法的特性。此外,Java还支持异常处理、反射、注解等高级特性。文件类型方面,Java源文件通常以.java为扩展名,编译后的字节码文件以.class为扩展名。Java程序可以在各种操作系统和平台上运行,只要安装了Java虚拟机(JVM)。
作者其他创作
大纲/内容
分布式/微服务
1.CAP理论,BASE理论
CAP
Consistency (一致性)
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。
对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability (可用性)
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance (分区容错性)
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。
BASE
基本可用
响应时间上的损失: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理用户请求的时间变为 3 s。
系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
软状态
数据同步允许一定的延迟
最终一致性
系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时
2.负载均衡算法、类型
算法
1、轮询法
2、随机法
3、源地址哈希法
4、加权轮询法
5、加权随机法
6、最小连接数法
类型
DNS 方式实现负载均衡
硬件负载均衡:F5 和 A10
软件负载均衡
Nginx
七层负载均衡,支持 HTTP、E-mail 协议,同时也支持 4 层负载均衡
HAproxy
支持七层规则的,性能也很不错。OpenStack 默认使用的负载均衡软件就是HAproxy
LVS
运行在内核态,性能是软件负载均衡中最高的,严格来说工作在三层,所以更通用一些,适用各种应用服务
3.分布式架构下,Session 共享有什么方案
1、采用无状态服务,抛弃session
2、存入cookie(有安全风险)
3、服务器之间进行 Session 同步,这样可以保证每个服务器上都有全部的 Session 信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;
4、 IP 绑定策略
5、使用 Redis 存储
实现了 Session 共享;
可以水平扩展(增加 Redis 服务器);
服务器重启 Session 不丢失(不过也要注意 Session 在 Redis 中的刷新/失效机制);
不仅可以跨服务器 Session 共享,甚至可以跨平台(例如网页端和 APP 端)。
4.简述你对RPC、RMI的理解
RPC
RMI
5.分布式id生成方案
uuid
如何生成
当前日期和时间 时间戳
时钟序列。 计数器
全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
优点
代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽 略)
缺点
每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增。
UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append 操作,需要进 行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性 能下降比较大,还会增加读 取磁盘次数
UUID长度过长,不适用于存储,耗费数据库性能。
ID无一定业务含义,可读性差。
有信息安全问题,有可能泄露mac地址
数据库自增序列
单机模式
优点
实现简单,依靠数据库即可,成本小
ID数字化,单调自增,满足数据库存储和查询性能
具有一定的业务可读性
缺点
强依赖DB,存在单点问题,如果数据库宕机,则业务不可用
DB生成ID性能有限,单点数据库压力大,无法扛高并发场景
信息安全问题,比如暴露订单量,url查询改一下id查到别人的订单
数据库高可用
优点
解决了ID生成的单点问题,同时平衡了负载。
缺点
系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
数据库压力大:每次获取一个ID都必须读写一次数据库。
主从同步的时候:电商下单->支付insert master db select数据 ,因为数据同步延迟导致 查不到这个数 据。加cache(不是最好的解决方式)数据要求比较严谨的话查master主库。
Leaf-segment
基于redis、mongodb、zk等中间件生成
雪花算法
优点
每个毫秒值包含的ID值很多,不够可以变动位数来增加,性能佳(依赖workId的实现)
时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的
能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高
缺点
强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨, 都会抛异常处 理,阻止ID生成,这可能导致服务不可用。
6.分布式锁解决方案
数据库
利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间
Zookeeper分布式锁
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临 时节点就会自动删除掉,其他客户端自动获取锁。临时顺序节点解决惊群效应
Redis分布式锁
setNX,单线程处理网络请求,不需要考虑并发安全性
7.分布式事务解决方案
XA规范
分布式事务规范,定义了分布式事务模型
四个角色
事务管理器(协调者TM)
资源管理器(参与者RM)
应用程序AP
通信资源管理器CRM
全局事务
一个横跨多个数据库的事务,要么全部提交、要么全部回滚
JTA事务时java对XA规范的实现,对应JDBC的单库事务
协议
两阶段协议
第一阶段( prepare ) :每个参与者执行本地事务但不提交,进入 ready 状态,并通知协调者已经准备就绪。
第二阶段( commit ) 当协调者确认每个参与者都 ready 后,通知参与者进行 commit 操作;如果有参与者 fail ,则发送 rollback 命令,各参与者做回滚。
问题
单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
不确定性:当协事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
三阶段协议
第一阶段:CanCommit阶段,协调者询问事务参与者,是否有能力完成此次事务。
如果都返回yes,则进入第二阶段
有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求
第二阶段:PreCommit阶段,此时协调者会向所有的参与者发送PreCommit请求,参与者收到后开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。
第三阶段:DoCommit阶段, 在阶段二中如果所有的参与者节点都返回了Ack,那么协调者就会从“预提交状态”转变为“提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。 相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。
TCC(补偿事务):Try、Confirm、Cancel
Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作既回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。
TCC模型对业务的侵入性较强,改造的难度较大,每个操作都需要有 try 、 confirm 、 cancel 三个接口实现
confirm 和 cancel 接口还必须实现幂等性。
消息队列的事务消息
发送prepare消息到消息中间件
发送成功后,执行本地事务
如果事务执行成功,则commit,消息中间件将消息下发至消费端(commit前,消息不会被消费)
如果事务执行失败,则回滚,消息中间件将这条prepare消息删除
消费端接收到消息进行消费,如果消费失败,则不断重试
8.如何实现接口的幂等性
唯一id。每次操作,都根据操作和内容生成唯一的id,在执行之前先判断id是否存在,如果不存在则执行后续操作,并且保存到数据库或者redis等。
服务端提供发送token的接口,业务调用接口前先获取token,然后调用业务接口请求时,把token携带过去,务器判断token是否存在redis中,存在表示第一次请求,可以继续执行业务,执行业务完成后,最后需要把redis中的token删除
建去重表。将业务中有唯一标识的字段保存到去重表,如果表中存在,则表示已经处理过了
版本控制。增加版本号,当版本号符合时,才能更新数据
状态控制。例如订单有状态已支付 未支付 支付中 支付失败,当处于未支付的时候才允许修改为支付中等
9.简述ZAB 协议
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。
ZAB 协议包括两种基本的模式:崩溃恢复和消息广播。
当整个 zookeeper 集群刚刚启动或者 Leader 服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有进程(服务器)进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步,当集群中超过半数机器与该 Leader服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。
10.zk的数据模型和节点类型
1、PERSISTENT-持久节点
除非手动删除,否则节点一直存在于 Zookeeper 上 2、EPHEMERAL-临时节点
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。
3、PERSISTENT_SEQUENTIAL-持久顺序节点
基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
4、EPHEMERAL_SEQUENTIAL-临时顺序节点
基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
11.简述zk的命名服务、配置管理、集群管理
12.讲下Zookeeper watch机制
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变 包含四种1、客户端注册 watcher2、服务端处理 watcher3、客户端回调 watcher
13.zk和eureka的区别
zk选举机制
选票格式vote=(myid,ZXID) 如果zxid大的(数据新)优先成为leader负责写数据,如果zxid一样推荐myid大的成为leader
14.Spring Cloud和Dubbo的区别
底层协议
springcloud基于http协议
dubbo基于Tcp协议
注册中心
Spring Cloud 使用的 eureka、nacos
dubbo推荐使用zookeeper
模型定义
dubbo 将一个接口定义为一个服务
SpringCloud 则是将一个应用定义为一个服务
15.什么是Hystrix?简述实现机制
分布式容错框架
阻止故障的连锁反应,实现熔断
快速失败,实现优雅降级
提供实时的监控和告警
资源隔离
线程隔离:Hystrix会给每一个Command分配一个单独的线程池,这样在进行单个服务调用的时候,就可以在独立的线程池里面进行,而不会对其他线程池造成影响
信号量隔离:客户端需向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩的目的。
熔断和降级:调用服务失败后快速失败
熔断是为了防止异常不扩散,保证系统的稳定性
降级:编写好调用失败的补救逻辑,然后对服务直接停止运行,这样这些接口就无法正常调用,但又不至于直接报错,只是服务水平下降
总结
通过HystrixCommand 或者HystrixObservableCommand 将所有的外部系统(或者称为依赖)包装起来,整个包装对象是单独运行在一个线程之中(这是典型的命令模式)。
超时请求应该超过你定义的阈值
为每个依赖关系维护一个小的线程池(或信号量); 如果它变满了,那么依赖关系的请求将立即被拒绝,而不是排队等待。
统计成功,失败(由客户端抛出的异常),超时和线程拒绝。
打开断路器可以在一段时间内停止对特定服务的所有请求,如果服务的错误百分比通过阈值,手动或自动的关闭断路器。
当请求被拒绝、连接超时或者断路器打开,直接执行fallback逻辑。
近乎实时监控指标和配置变化。
16.springcloud核心组件及其作用
“xxx系统”包括xx子系统、xx子系统、xx子系统、xx子 系统、xx子系统等。项目是基于 Spring Boot2.x快速集成开发,选择Spring Cloud进 行分布式微服务架构,主体采用Spring + SpringMVC + Mybatis 框架,使用nacos作为注册 和发现中心,使用gateway作为网关,使用rabbitMQ作为队列实现,使用Fegin进行微服务 之间的通信,使用Redis作为缓存服务,使用xxljob作为定时任务框架,集成Prometheus监 控。项目采用前后台分离的开 发模式,前端使用ant-design-pro-vue框架,集成Echats实现 了数据大屏,后端通过Swagger2框架生成接口描述文档, 并通过ElasticSearch进行日志收集 查询。
17.Dubbo 的整体架构设计及分层
五个角色
注册中心registry:服务注册与发现
服务提供者provider:暴露服务
服务消费者consumer:调用远程服务
监控中心monitor:统计服务的调用次数和调用时间
容器container:服务允许容器
调用流程
1:container容器负责启动、加载、运行provider
2:provider在启动时,向regisitry中心注册自己提供的服务
3:consumer在启动时,向regisitry中心订阅自己所需的服务
4:regisitry返回服务提供者列表给consumer,如果有变更,registry将基于长连接推送变更数据给consumer
5:consumer调用provider服务,基于负载均衡算法进行调用
6:consumer调用provider的统计,基于短链接定时每分钟一次统计到monitor
分层
接口服务层( Service):面向开发者,业务代码、接口、实现等
配置层( Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心
服务代理层( Proxy):对生产者和消费者、dubbo都会产生一个代理类封装调用细节,业务层对远程调用无感
服务注册层( Registry) : 封装服务地址的注册和发现, 以服务 URL 为中心
路由层( Cluster) : 封装多个提供者的路由和负载均衡, 并桥接注册中心
监控层( Monitor) : RPC 调用次数和调用时间监控
远程调用层( Protocal):封装 RPC 调用
信息交换层( Exchange): 封装请求响应模式, 同步转异步
网络传输层( Transport):抽象 mina 和 netty 为统一接口,统一网络传输接口
数据序列化层( Serialize) : 数据传输的序列化和反序列化
es倒排索引
netty
粘包(发送的数据有时候粘在一起多了) 有概率问题
主流第三种 前两种有局限性 --自定义客户端服务端编解码器 放入长度和字符串
拆包(按照字节码拆的时候数据不全)服务端转发 --客户端服务端解码器不一致 有概率问题
zookeeper面试题总结
是什么
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务
是Google的Chubby一个开源的实现(Chubby是不开源的),它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。
最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户 。
能做什么
服务注册中心
最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心,服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据
命名服务
配置中心
分布式锁
术语
Subtopic
Subtopic
架构
ZooKeeper分为服务器端(Server) 和客户端(Client),客户端可以连接到整个 ZooKeeper服务的任意服务器上(除非 leaderServes 参数被显式设置, leader 不允许接受客户端连接)
客户端使用并维护一个 TCP 连接,通过这个连接发送请求、接受响应、获取观察的事件以及发送心跳。如果这个 TCP 连接中断,客户端将自动尝试连接到另外的 ZooKeeper服务器。客户端第一次连接到 ZooKeeper服务时,接受这个连接的 ZooKeeper服务器会为这个客户端建立一个会话。当这个客户端连接到另外的服务器时,这个会话会被新的服务器重新建立。
上图中每一个Server代表一个安装Zookeeper服务的机器,即是整个提供Zookeeper服务的集群(或者是由伪集群组成)
组成ZooKeeper服务的服务器必须彼此了解。它们维护一个内存中的状态图像,以及持久存储中的事务日志和快照, 只要大多数服务器可用,ZooKeeper服务就可用
ZooKeeper 启动时,将从实例中选举一个 leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据。每个Server 在内存中存储了一份数据
Zookeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;
Zab协议包含两个阶段:leader election阶段和Atomic Brodcast阶段。
leader election
集群中将选举出一个leader,其他的机器则称为follower,所有的写操作都被传送给leader,并通过brodcast将所有的更新告诉给follower。
当leader崩溃或者leader失去大多数的follower时,需要重新选举出一个新的leader,让所有的服务器都恢复到一个正确的状态
当leader被选举出来,且大多数服务器完成了 和leader的状态同步后,leadder election 的过程就结束了,就将会进入到Atomic brodcast的过程。
Atomic Brodcast同步leader和follower之间的信息,保证leader和follower具有形同的系统状态
运行原理
核心原理
ZAB协议
是什么?
机制保证了各个Server之间的同步
Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。
能做什么?
恢复模式(选主)
广播模式(同步)
原则
Zab协议要求每个 Leader 都要经历三个阶段:发现,同步,广播。
运行原理是什么?
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式
当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。
状态同步保证了leader和Server具有相同的系统状态。
如何做到分布式一致性?
递增的事务id号(zxid)来标识事务
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。
所有的提议(proposal)都在被提出的时候加 上了zxid。
实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一 个新的epoch,标识当前属于那个leader的统治时期。
低32位用于递增计数。
epoch
可以理解为皇帝的年号,当新的皇帝leader产生后,将有一个新的epoch年号。
Server的三种状态
LOOKING
当前server不知道谁是leader,正在搜寻
LEADING
当前server即为选举出来的leader
FOLLOWING
leader已经选举出来,当前server与之同步
zookeeper为什么要这么设计?
zookeeper的目标是什么?
提供高性能、高可用、顺序一致性的分布式协调服务,保证数据最终一致性
高性能
简单的数据模型
采用树形结组织数据节点
缺点
所以zk不能存储大量的数据,没法分布式存储
每个节点最大为1M
全量数据节点,都存储在内存中
Follower和Observer直接处理非事务请求
高可用
构建集群
默认采用了Quorums这种方式选举,过半可用
默认采用了Quorums这种方式来防止"脑裂"现象。
Zookeeper的写也遵循quorum机制
epoch最大的就是新leader,处理脑裂问题
顺序一致性
每个事务请求,都会转发给leader处理,会分配全局唯一的事务递增id(zxid:64位: epoch _+ 自增id)
通过提议投票方式
保证事务提交的可靠性提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据
Zookeeper中的角色有哪些?
系统模型
领导者leader
leader提供读服务和写服务
leader负责进行投票的发起和决议
更新系统状态
跟随者follower
follower服务器为客户提供读服务
参与leader选举过程
参与写操作“过半写成功”策略
观察者observer
observer服务器为客户提供读服务
不参与选举过程
不参与写操作“过半写成功”策略
目的
用于在不影响写性能的前提下提升集群的读性能
学习者learner
进行follower、observer和leader服务器的数据同步
事务性会话请求的转发和proposal提议投票等功能
客户端client
服务请求发起方
Zookeeper节点ZNode和相关属性有哪些?
Znode节点类型有哪些?
持久的(persistent)
客户端和服务器端断开连接后,创建的节点不删除(默认)。
短暂的(ephemeral)
客户端和服务器端断开连接后,创建的节点自己删除。
Znode有四种形式
持久化目录节点(PERSISTENT)
客户端与Zookeeper断开连接后,该节点依旧存在持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
临时目录节点(EPHEMERAL)
客户端与Zookeeper断开连接后,该节点被删除
临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)
客户端与Zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号「注意」:创建ZNode时设置顺序标识,ZNode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。
持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)
客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号:
常见问题?
为什么zk集群节点数要求为奇数?
节省资源
3个节点(3/2=1.5),即最少2个几点,容忍1个几点宕机
4个节点(4/2=2),即最少3个几点,容忍1个节点宕机
集群1与集群2都有 允许1个节点宕机 的容错能力,但是集群2比集群1多了1个节点。在相同容错能力的情况下,本着节约资源的原则,zookeeper集群的节点数维持奇数个更好一些。
防止脑裂
默认采用了Quorums(法定人数)这种方式来防止"脑裂"现象。
epoch(年号)最大的就是新leader,处理脑裂问题
先比较zxid,再比较myid,谁最大谁是leader
节点奇数,过半选主,过半成功
zk不适应的场景
zk是cp模型
zookeeper 的 CP 模型不适合注册中心
服务发现场景没有必要做强一致性
zk的不可用导致服务注册发现有问题
如何避免
客户端本地缓存一份服务列表
zookeeper 的性能不适合注册中心
在大规模服务集群场景中,zookeeper 的性能也是瓶颈。
zookeeper 所有的写操作都是 leader 处理的,在大规模服务注册写请求时,压力巨大,而且 leader 是单点,无法水平扩展。
还有所有服务于 zookeeper 的长连接也是很重的负担。
zookeeper 对每一个写请求,都会写一个事务日志,同时会定期将内存数据镜像dump到磁盘,保持数据一致性和持久性。这个动作会降低性能,而且对于注册中心来讲,是不需要的。
总结
其实,这并不算是 zookeeper 的问题,是人家本来就不适合做注册中心,非要用他的话,肯定一堆问题。
zookeeper 的特长是做分布式协调服务,例如 kafka、hbase、flink、hadoop 等大项目都在用 zookeeper,用的挺好的,因为是用对了地方。例如可以看下:kafka 中 zookeeper 具体是做什么的?
并发编程
1.线程的生命周期及状态
创建
新创建了一个线程对象
就绪
线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行
就绪状态的线程获取了CPU,执行程序代码。
阻塞
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
等待阻塞
运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中
其他阻塞
运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法
死亡
线程执行完了或者因异常退出了run方法,该线程结束生命周期。
2.sleep()、wait()、join()、yield()的区别
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。---使用sleep之后
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
3.sleep和wait
1、sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu 的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而 是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程 序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出 interruptexception异常返回,这点和wait是一样的。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。
6、sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。
4.yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
5.join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
分支主题
3.对线程安全的理解
不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获 得正确的结果,我们就说这个对象是线程安全的
堆
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈(线程安全的)
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
4.Thread和Runnable
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会newThread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
class Thread implements Runnable
5.对守护线程的理解
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程的作用
GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景
1.为其它线程提供服务支持的情况
2.在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用
注意点
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。
6.ThreadLocal的原理和使用场景
每一个 Thread 对象均含有一个 ThreadLocalMap 类型的成员变量 threadLocals ,它存储本线程中所有ThreadLocal对象及其对应的值。ThreadLocalMap 由一个个 Entry 对象构成。Entry 继承自 WeakReference<ThreadLocal<?>> ,一个 Entry 由 ThreadLocal 对象和 Object 构成。由此可见, Entry 的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
分支主题
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景 隔离类之间的传递,可以构造一个utils 维护一个threadlocal 统一从这里取值。
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的 connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种 隔离
解决问题 : 记录日志区分手机端调用还是pc端调用 使用threadlocal 单独变量实现,避免了所有代码的修改传递参数 抽出来了一个公用的工具类 ,记录日志完成之后,在进行threadlocal的删除 --以及其他系统的调用实现
手机端都是dubbo调用--Thread.currentThread().getName().startsWith("DubboServerHandler"); --慢慢的区分其他系统和本系统的调用 使用threadlocal
7.ThreadLocal内存泄露原因,如何避免
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本
key 使用强引用
当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。
8.并发、并行、串行的区别
串行
串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行
9.并发的三大特性
原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
关键字: synchronized
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
关键字: volatile、synchronized、final
有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
关键字: volatile、synchronized
10.为什么使用线程池,参数解释
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
4、参数
corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
maxinumPoolSize 代表的是最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
keepAliveTime 、 unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime 来设置空闲时间
workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程
ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
Handler 任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这是也就拒绝
AbortPolicy直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;(即创建次任务的线程)
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
11.简述线程池处理流程
线程池处理流程
分支主题
分支主题
分支主题
12.线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?
(1)存放任务
(2)线程开销比较大
13.线程池中线程复用原理
线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
同一个线程可以从阻塞队列中不断获取新任务来执行
让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,并不是每次执行任务都会调用 Thread.start() 来创建新线程
14.同步工具类
CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作
countDown()
count数减1
await()
线程阻塞直到count等于0
应用场景
开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段。
应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
确保一个计算不会执行,直到所需要的资源被初始化。
CyclicBarrier
CyclicBarrier(int ),参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
当cyclicBarrier的count数等于0的时候,阻塞的线程都继续执行
应用场景
多线程计算
Semaphore(信号量控制)
可以控制同时访问的线程个数,它维护了一组"许可证"。
acquire()
表示阻塞并获取许可
release()
表示释放许可
应用场景
可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接
CountDownLatch和CyclicBarrier的区别
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置
CountDownLatch.await一般阻塞主线程,而CyclicBarrierton一般阻塞工作线程
CountDownLatch主要用于描述一个或者多个线程等待其他线程执行完毕
CyclicBarrier主要用于描述多个线程之间相互等待
Exchanger
Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
15.锁
synchronized
一个重量级的可重入锁
监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因
作用方式
作用于代码块
作用于实例方法
持有的是当前对象实例的锁
作用于静态方法
持有的是静态对象的锁
底层实现
作用于方法(显式同步)
JVM可以从方法区中的方法表结构中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。
作用于代码块(隐式同步)
编译时,在代码块的前后加上monitorenter和monitorexit
为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令
ObjectMontior
waitSet
EntryList
owner
count
线程中断与synchronized
线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用
等待唤醒机制与synchronized
在使用notify/notifyAll和wait这3个方法时,必须处于synchronized代码块或者synchronized方法中
notify/notifyAll和wait方法都依赖于monitor
java对象头
Mark Word
对象的hashCode
CG年代
锁信息(偏向锁,轻量级锁,重量级锁)
偏向锁,cas更新偏向锁标志位,当第二个过来的时候,如果偏向锁存在就升级轻量锁
适用于单线程适用锁的情况,如果线程争用激烈,那么应该禁用偏向锁。
轻量级锁:java对其做了规定,默认每个线程最多执行10次(该值),超过10次后改为重量锁
轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似)
重量级锁:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权
重量级锁:适用于竞争激烈的情况
GC标志
指向monitor的指针
Class Metadata Address
指向对象实例的指针
分支主题
没有拿到锁的就在外边等待循环( 自旋)
锁的力度
放到内部代码块只是锁里面的一段代码
类锁(两个静态方法 方法虽然不同,加载同一个类对象上)项目中不建议大量用system.out.print 因为他是同步单利的
不建议使用system.out.printLn
Lock
AbstractQueuedSynchronizer(AQS)
Doug Lea大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类
同步状态管理、线程的排队、等待与唤醒等
模板方法模式
继承同步器并且重写指定的方法
应用
CountDownLatch
共享
读写锁ReentrantReadWriteLock
ReadLock
共享锁
悲观读:读的时候不可以获得写锁
WriteLock
独占锁
适用于读取数据的频率远远大于写数据的频率的场合
可重入锁ReentrantLock
非公平锁NonfairSync
随机的获取
公平锁FairSync
谁等的时间最长,谁就先获取锁
如果是当前持有锁的线程,可重入
响应中断
限时等待
StampedLock
乐观读
比ReentrantReadWriteLock性能高
代码复杂,发现写入,再读一次
悲观读
不可重入
jdk1.8
独占
state为线程获取独占锁的次数,占用锁state+1,释放state-1
线程独占,可重入
共享
state为一定初始数字的计数器,占用锁state-1,释放state+1
/** * 共享资源变量 * 使用CAS更改状态,volatile保证线程可见性,高并发场景下, * 即被一个线程修改后,状态会立马让其他线程可见。 */
volatile int waitStatus;
允许多个线程持有
CLH队列
头结点head尾节点tail
双向链表
将新节点的prev置为tail再尝试CAS
防止prev一瞬间的null值
释放head,next变为head
遍历节点从tail向前遍历
防止遍历过程中节点变动
FIFO先进先出
CAS(比较与交换算法)
16.定时线程
ScheduledThreadPoolExecutor
Timer
17.线程间如何通信?
volatile关键字
修饰内存可见性,最简单
CountDownLatch
基于AQS框架,维护一个线程间共享变量state
wait/notify机制
共享变量的synchronized或Lock同步机制
ReentrantLock结合Condition
LockSupport
CyclicBarrier
等待N个线程都达到某个状态后继续运行
信号量semaphore
线程间同步
一个线程完成操作后就通过信号量通知其它线程
springmvc、springBoot
1.Spring Boot、Spring MVC 和 Spring 有什么区别
spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等
springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端
springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用
2.SpringMVC 工作流程
1、用户发送请求至前端控制器 DispatcherServlet。
2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3、处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、DispatcherServlet 调用 HandlerAdapter 处理器适配器。
5、HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)
6、Controller 执行完成返回 ModelAndView。
7、HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。
8、DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。
9、ViewReslover 解析后返回具体 View
10、DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)
11、DispatcherServlet 响应用户。
3.Spring MVC的主要组件
1、HandlerMapping
initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行,这就是HandlerMapping需要做的事。
2、HandlerAdapter
initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
3、HandlerExceptionResolver
initHandlerExceptionResolvers(context), 其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
4、ViewResolver
initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
5、RequestToViewNameTranslator
initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
6、LocaleResolver
initLocaleResolver(context), 解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
7、ThemeResolver
initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
8、MultipartResolver
initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
9、FlashMapManager
initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。
4.Spring Boot 自动配置原理
自动装配原理图
@Import + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到METAINF/spring.factories下
使用Spring spi扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
5.如何理解 Spring Boot 中的 Starter
使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的beanstarter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot--starter,springboot-starter-redis
6.什么是嵌入式服务器?为什么要使用嵌入式服务器
节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载springmvc
Spring
1.如何实现一个IOC容器
1、配置文件配置包扫描路径
2、递归包扫描获取.class文件
3、反射、确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入
5.拓展:解决循环依赖(本地map缓存);方便拓展,引入后置处理器等等
2.spring是什么?
轻量级的开源的J2EE框架
IOC
DI
AOP
日志
3.谈谈你对AOP的理解
相对于OOP(面向对象)来说
AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
对目标类方法进行加强的方式
继承
缺点:需要直到要增强的方法的类才能继承
装饰者模式
缺点:需要有接口,这个接口下除了要增强的方法外别的方法也要实现
动态代理模式
JDK
需要接口但可以指定增强方法,不需要实现全部方法
Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例
参数列表:和目标对象相同的类加载器;目标类所有的接口;增强类(可以自定义增强类并实现InvlcationHandler接口并重写其内部方法,也可以在此处直接定义匿名内部类)
此处的InvocationHandler可以是一个匿名内部类,实现内部的invoke方法,在该方法中定义增强逻辑该方法的参数:代理类对象;要被增强的方法(在将目标类所有接口传进去时就已经知道所有的代理方法了);方法需要的参数。
细节
proxyPerson.run(); // 执行这个方法 invoke都会执行一遍 执行的内容就是针对该方法的增强
返回值 return 谁调用返回给谁 返回的内容就是最终值而不是需要增强的方法的返回值
通过if-else判断方法名来增强指定方法
CGLIB
不需要接口,可以指定增强方法,但代码量比较大
CGLIB原理
该代理模式是用拦截器和过滤器的方式进行继承,对目标类的方法进行增强
4.谈谈你对IOC的理解
ioc容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。
通俗的讲:比如你去洗脚城,一般是你自己点小姐姐,如果是洗脚城安排小姐姐,那么就是控制反转了,你把点小姐姐的权利给了洗脚城
5.BeanFactory和ApplicationContext有什么区别?
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能
继承MessageSource,因此支持国际化
统一的资源文件访问方式
提供在监听器中注册bean的事件
同时加载多个配置文件
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。 只能生产bean
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
相同点:他们都可以生产bean
6.描述一下Spring Bean的生命周期?
1、解析类得到BeanDefinition
2、如果有多个构造方法,则要推断构造方法
3、确定好构造方法后,进行实例化得到一个对象
4、对对象中的加了@Autowired注解的属性进行属性填充
5、回调Aware方法,比如BeanNameAware,BeanFactoryAware
6、调用BeanPostProcessor(后置处理器)的初始化前的方法
7、调用初始化方法
8、调用BeanPostProcessor(后置处理器)的初始化后的方法,在这里会进行AOP
9、如果当前创建的bean是单例的则会把bean放入单例池
10、使用bean
11、Spring容器关闭时调用DisposableBean中destory()方法
7.解释下Spring支持的几种bean的作用域。
singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype(原型):为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象
request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
application:bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket:bean被定义为在websocket的生命周期中复用一个单例对象。
8.Spring框架中的单例Bean是线程安全的么?
无状态的情况
controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
有状态的情况
Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring是如何保证事务获取同一个Connection的)
拓展:不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。
9.Spring 框架中都用到了哪些设计模式?
简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是 在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
工厂方法
实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调 用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点
spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没 有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
适配器模式:
Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替 controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC 的扩展了
装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有 Decorator。
动态代理:
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理 对象。SpringAOP就是以这种方式织入切面的。
观察者模式
spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
策略模式
Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
模板方法:父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。
refresh方法
10.Spring事务的实现方式和原理以及隔离级别?
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,@Transactional注解就是申明式的。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
spring事务隔离级别就是数据库的隔离级别
read uncommitted(未提交读)
read committed(提交读、不可重复读)
repeatable read(可重复读)
serializable(可串行化)
11.spring事务传播机制
多个事务方法相互调用时,事务如何在这些方法间传播
REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
12.spring事务什么时候会失效?
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了
1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!
2、方法不是public的
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可 以开启 AspectJ 代理模式。
3、数据库不支持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)
13.什么是bean的自动装配,有哪些方式?
@Autowired自动装配bean,可以在字段、setter方法、构造函数上使用。
装配的方式
no – 缺省情况下,自动配置是通过“ref”属性手动设定 。
手动装配:以value或ref的方式明确指定属性值都是手动装配。 需要通过‘ref’属性来连接bean。
byName-根据bean的属性名称进行自动装配。
Cutomer的属性名称是person,Spring会将bean id为person的bean通过setter方法进行自动装 配。
<bean id="cutomer" class="com.xxx.xxx.Cutomer" autowire="byName"/>
<bean id="person" class="com.xxx.xxx.Person"/>
byType-根据bean的类型进行自动装配。
constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形同,则进行自动装配,否则导致异常
autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。
14.如和解决循环依赖
循环依赖
类型
构造器的循环依赖
出现在Bean实例化过程中,即调用对象构造方法实例化时
无法解决
属性的循环依赖
出现在Bean填充属性过程中
理论依据
Java的引用传递,当我们获取到对象的引用时,对象的field或属性是可以延后设置的(构造器必须是在获取引用之前)
如何检测是否存在循环依赖
Bean在创建的时候可以给该Bean加标记,如果递归调用回来发现,Bean正在创建中的话,即说明了循环依赖了
singletonsCurrentlyInCreation/** 正在创建中的bean**/
Spring解决方案
三级缓存----不管是不是循环依赖都有三级缓存
singletonFactories:单例对象工厂的缓存 解耦--三级缓存
/** 三级缓存 该map用户缓存 key为 beanName value 为ObjectFactory(包装为早期对象) 有可能此类为aop类*/
earlySingletonObjects:提前曝光的单例对象缓存 纯净的bean 没有初始化--二级缓存
/** 二级缓存 ,用户缓存我们的key为beanName value是我们的早期对象(对象属性还没有来得及进行赋值) */
singletonObjects:单例对象缓存 完整的bean --一级缓存
/** 一级缓存 这个就是我们大名鼎鼎的单例缓存池 用于保存我们所有的单实例bean */
先尝试从单例对象缓存获取Bean
isSingletonCurrentlyInCreation():判断当前单例bean是否正在创建中
allowEarlyReference:是否允许从singletonFactories中通过getObject拿到对象
单例缓存中未取到,且Bean正在创建中,尝试从提前曝光的单例对象缓存中获取Bean
提前曝光的缓存中未取到,且允许singletonFactories通过getObject()获取,就从对象工厂缓存获取
单例对象被创建,未完成初始化,提前曝光,从singletonFactories中移除,并放入earlySingletonObjects中
最后删除二级和三级缓存
为什么二级缓存
为什么三级缓存
为什么Spring不能解决构造器的循环依赖?
从流程图应该不难看出来,在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
15.BeanFactory和factoryBean有什么区别?
beanfactory spring顶层核心接口使用简单工厂模式,负责生产bean
factoryBean 一个接口,获取bean取到的是重写的getObject方法的bean(可以定义任意实体bean),没有实现这个接口 获取的就是当前bean的实体。
实现了这个 使用getbean 取到的是tank 没有实现这个取得是car
加了美元$修饰 就可以取到car了
使用场景,集成mybatis,使用这个返回动态代理
java基础
面向对象
封装
javaBean的属性私有,提供getset对外方法
orm框架,只需要引入mybatis,不需要理解技术细节
继承
多态
继承
方法的重写
子类的实现指向父类的引用
JDK、JRE、JVM区别和联系
jdk
包含jre,java开发包
jre
包含jvm,java环境
jvm
java虚拟机
==和equals
==
比较的是栈中的地址,基本类型直接相等
equals
不重写,和【==】一样比较的是栈中的地址
String
重写了equles方法,比较的是堆中对象的值
源码:使用循环,比较每一个字符是否相等
String a = "123";会放进常量池中
例子
Integer
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
对于对象引用类型:==比较的是对象的内存地址。
对于基本数据类型:==比较的是值。如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer 对象,而是直接引用常量池中的Integer对象
这里a==b会自动装箱成为以对象 a与b不是一个对象所以报错 a==c是对的会自动拆箱 b==c 也是拆箱与基本类型对比
分支主题
final
修饰类:表示类不可被继承 典型的String
修饰方法:表示方法不可被子类覆盖,但是可以重载
修饰变量:表示变量一旦被赋值就不可以更改它的值
修饰成员变量
如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值
如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值
修饰局部变量
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次)
修饰基本类型数据
如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改
修饰引用类型数据
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的。
为什么局部内部类和匿名内部类只能访问局部final变量?
String、StringBuffer、StringBulider
String
final修饰的
每次操作都会产生新的对象
String值不可以变但是引用可以变
StringBuffer
线程安全,每个方法synchronized修饰的
每次都是在原对象上操作的
StringBulider
线程不安全
每次都是在原对象上操作的
重载和重写的区别
重写
发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。(里氏代换原则)
重载(@Override)
发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
接口和抽象类的区别
基础区别
抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法(1.8以后可以有默认方法)
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的
抽象类只能继承一个,接口可以实现多个
设计目的
接口:对类的行为进行约束。如SpringMVC架构中的service层
抽象类:代码复用。如:设计模式中的模板模式 --简单来说可以抽取一些共有业务逻辑同时可以覆盖抽象类方法
使用场景
接口:当你关注一个操作的时候,用接口;抽象成本低;like a
抽象类:当你关注一个事物的本质的时候,用抽象类;抽象成本高;is a
java io
字节流
reader
writer
字符流
inputstream
outputstream
NIO
nio存在的问题
使用NIO != 高性能,当连接数<1000,并发程度不高或者局域网环境下NIO并没有显著的性能优势。
NIO并没有完全屏蔽平台差异,它仍然是基于各个操作系统的I/O系统实现的,差异仍然存在。使用NIO做网络编程构建事件驱动模型并不容易,陷阱重重。
推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。
nio的优势
事件驱动模型
避免多线程
单线程处理多任务
非阻塞I/O,I/O读写不再阻塞
基于block的传输,通常比基于流的传输更高效
更高级的IO函数,Zero Copy
I/O多路复用大大提高了Java网络应用的可伸缩性和实用性
List和Set
List
有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素
Set
无序,不可重复,最多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素
static class Node<K,V> implements Map.Entry<K,V> { 用的是map
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
add方法
hashCode与equals
hashCode
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有hashCode() 函数。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
equals
参考3.0
ArrayList、LinkedList区别
ArrayList
动态数组
Object[] elementData-----arraylist
连续内存存储
适合下标访问(随机访问)
扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象)
分支主题
删除慢 会重新复制一份list 所以效率慢
多线程场景下如何使用 ArrayList?
分支主题
LinkedList
基于链表
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
} 链表list
可以存储在分散的内存中
适合做数据插入及删除操作,不适合查询:需要逐一遍历
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。
删除快
CopyOnWriteArrayList 并发编程安全的list 写多读少不适合造成系统卡顿 每次写都会复制一份
HashMapTable区别及实现
HashMap
线程不安全
存储node未使用树
超过8之后转换为树状
大于8转换为树,小于6转换为链表 数组长度大于64
分支主题
HashMap允许key和value为null 转红黑树的时候数组长度要达到64链表长度达到8
分支主题
底层实现:数组+链表实现
jdk8开始链表高度到8、数组长度超过容量64,链表转变为红黑树,元素以内部类Node节点存在 高度变为6转换为链表
计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
key为null,存在下标0的位置
数组扩容
1.8以下,并发时会有扩容成环的问题
map初始化
Hash表默认初始容量16(注解说必须说为2的整数次幂)方便位运算,效率更高,散列更均匀避免hash碰撞
DEFAULT_INITIAL_CAPACITY = 1 << 4;
最大Hash表容量 2的30次方
MAXIMUM_CAPACITY = 1 << 30;
默认加载因子
DEFAULT_LOAD_FACTOR = 0.75f
多少个槽位有值/hashmap数组大小
HashTable
线程安全,每个方法都加了synchronized
1.8以后一般都用ConcurrentHashMap
HashTable不允许key和value为null
ConcurrentHashMap原理
1.7
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构--使用手动锁加减锁
元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segmentget方法无需加锁,volatile保证
1.8
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性查找,替换,赋值操作都使用CASCompare-And-Swap
1.初始化使用cas锁进行实现
2.put第一步取值判断槽,如果槽位为空,则cas放置值
3.put是否在扩容,改变当前的节点 详见超链接
4.put上面两种都不是添加synczied进行枷锁,锁的是header头对象效率会高(1.7锁的是大段效率肯定低),构建链表
锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容读操作无锁:Node的val和next使用volatile修饰,读写线程对该变量互相可见数组用volatile修饰,保证扩容时被读线程感知
对比1.7锁的大段,效率更高
分支主题
什么是字节码?采用字节码的好处是什么?
编译后,后缀为.class的文件
好处是基于jvm,一处编译,处处运行
Java类加载器
bootstrap ClassLoader
BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
ExtClassLoader
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类
AppClassLoader
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器
继承ClassLoader实现自定义类加载器
双亲委托模型
双亲委派模型图 appclassloader的parent是extclassloader 的parent bootstrapclassloader 有一个属性parent向上指向
向上查找 查找缓存有就直接加载,没有向下查找类加载路径 没有继续向下加载 一直加载到发起的类加载起
好处
主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String
同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类
tomcat打破了双亲委派,自己加载自己响应加载器下的
Java中的异常体系
异常体系UML类图
Java中的所有异常都来自顶级父类Throwable。
Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
全局统一异常处理
1、编写GloabExceptionHandler
2、类上增加注解@ControllerAdvice
3、方法上增加注解@ExceptionHandler(Exception.class)
4.异常类型
运行时异常
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
非运行时异常(编译的时候就出现问题编译不过)
IOException、FileNotFoundExcetion 和SQLException
GC如何判断对象可以被回收
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收
引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。
可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GCRoots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象
GC Roots的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
垃圾回收算法
复制算法
标记清除算法
标记整理算法
分支主题
垃圾回收器
GC流程
类加载过程 加载-》验证-》准备-》解析-》初始化-》使用-》卸载
分支主题
运行时内存
https://www.processon.com/view/605d94157d9c08555e5cf41e
MQ
1.简述RabbitMQ的架构设计
Broker:rabbitmq的服务节点
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式。
Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。
Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ就可以指定如何正确的路由到队列了。
信道:信道是建立在Connection 之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接着可以创建一个AMQP 信道(Channel) ,每个信道都会被指派一个唯一的D。RabbitMQ 处理的每条AMQP 指令都是通过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,允许所有的连接通过多条光线束进行传输和接收。
2.RabbitMQ如何确保消息发送 ? 消息接收?
分支主题
解决公司一个问题:房子掉淘宝池后,有一个监听器要处理房子一系列通知状态信息,要延迟20秒发送,批处理错误数据几千条的时候 使用多线程处理,导致队列阻塞,--发送到mq 延迟20秒发送到系统 处理了线程阻塞问题
3.RabbitMQ事务消息
4.RabbitMQ死信队列、延时队列
5.RabbitMQ镜像队列机制
6.简述kafka架构设计
7.kafka怎么处理消息顺序、重复发送、重复消费、消息丢失
8.Kafka在什么情况下会出现消息丢失及解决方案
9.Kafka是pull?push?优劣势分析
10.Kafka中zk的作用
11.简述kafka的rebalance机制
12.Kafka的性能好在什么地方
Mysql
1.索引的基本原理
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
1. 把创建了索引的列的内容进行排序
2. 对排序结果生成倒排表
3. 在倒排表内容上拼上数据地址链
4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
mysql小表驱动大表查询快
left join 、 in和exit优化 、
2.mysql聚簇和非聚簇索引的区别
聚簇索引(数据和索引放在了一起)
将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
非聚簇索引
叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。
MyISM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
3.mysql索引的数据结构,各自优劣
B+树(层级低 对比b数进行了优化只有叶子节点存数据,这样存储数据更多)
默认索引数据结构--- b树非叶子节点存数据 导致非叶子节点存储数据量小。b+树存储的数据多 16k可以存储很多
b+数非叶子节点存在指针链接 方便分页查询
B+Tree索引
认识 B-Tree (B树)
3阶B树示例
每个节点最多有m个孩子;
除根节点和叶子节点外,其他每个节点至少有Ceil(m/2)个孩子;
若根节点不是子节点,则至少有2个孩子;
所有叶子节点都在同一层,且不包含其他关键字信息;
每个非终端节点包含n个关键字信息,m/2 <= n <= m,n=孩子个数-1;
每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
看图好理解
认识B+Tree(B+树)
B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
4阶B+树示例
有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
除根节点和子节点外,每个节点至少有 (m+1)/2个孩子。
所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
B+Tree 与 B-Tree 的几点不同
非叶子节点只存储键值信息;
所有叶子节点之间都有一个链指针;
数据记录都存放在叶子节点中;
B+Tree的优势(相对B-Tree)
单一节点存储更多的元素,使得查询的IO次数更少。
所有查询都要查找到叶子节点,查询性能稳定。
所有叶子节点形成有序链表,便于范围查询。
MyISAM 和 InnoDB 引擎索引都采用B+索引
MyISAM 主键索引与辅助索引
MyISAM索引图示(索引和数据存放位置分离)
MyISAM引擎的索引文件和数据文件是分离的。这样的索引称为"非聚簇索引"。
MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。
MyISAM的主索引与辅助索引区别并不大,只是主键索引不能有重复的关键字。
InnoDB主键索引与辅助索引
InnoDB主键索引图示
InnoDB辅助(非主键)索引图示
辅助索引检索过程(以上图name字段为例)
先在辅助索引上检索name,到达其叶子节点获取对应的主键;
②再使用主键在主索引上再进行对应的检索操作。
InnoDB的数据文件就是主键索引文件(索引和数据存放在一起),这种索引被称为“聚簇索引”,一个表只能有一个聚簇索引。
InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行)。
数据库索引的原理,为什么要用 B+树,为什么不用二叉树?
当从算法逻辑上讲,二叉树的查找速度和比较次数是最小的;
但是由于数据库索引是存储在磁盘上的,所以必须考虑磁盘IO的问题,磁盘IO是比较耗时的操作;
当数据量比较大的时候,索引的大小可能有几个G,是不可能全部加载到内存中的;
做法是逐一加载每一个磁盘页,这里的磁盘页对应着索引树的节点;
索引树的高度(层级)就是需要的磁盘IO次数;
在相同数据量的情况下,B+树的高度是小于二叉树的,数据量越大差距越明显。
hash
哈希碰撞
1 搜索二叉树:每个节点有两个子节点,数据量的增大必然导致高度的快速增加,显然这个不适合作为大量数据存储的基础结构。
4.索引设计的原则
1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列
2. 基数较小的表,索引效果较差,没有必要在此列建立索引
非黑即白 如男女
3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
4.不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
5. 定义有外键的数据列一定要建立索引。
6. 更新频繁字段不适合创建索引
7. 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
8. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
9. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
10. 对于定义为text、image和bit的数据类型的列不要建立索引。
5.什么是最左前缀原则
abc联合索引必须有a,要想走c,必须有b 查询条件中 --创建索引的时候是先比较a 再比较b 所以最左匹配 orderby 也会走最左匹配原则
6.锁的类型有哪些
共享锁(Share Lock)
共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题
排他锁(eXclusive Lock)
排他锁又称写锁,简称X锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取。避免了出现脏数据和脏读的问题。
表锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;
特点: 粒度大,加锁简单,容易冲突;
行锁
行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问;
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;
记录锁(Record Lock)
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。
精准条件命中,并且命中的条件字段是唯一索引加了记录锁之后,数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。
页锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
间隙锁(Gap Lock)
属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。
范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复
读)的事务级别中。
触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。
比如表里面的数据ID 为 1,4,5,7,10 ,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区间 (-n代表负无穷大,n代表正无穷大)
临建锁(Next-Key Lock)
也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁 会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一 个区间也会锁住 触发条件:范围查询并命中,查询命中了索引。 结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之 后,在范围区间内数据不允许被修改和插 入。
意向共享锁
当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。
意向排他锁
当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。
什么时候会加锁
SELECT ... 语句正常情况下为快照读,不加锁;
SELECT ... LOCK IN SHARE MODE 语句为当前读,加 S 锁;
SELECT ... FOR UPDATE 语句为当前读,加 X 锁;
常见的 DML 语句(如 INSERT、DELETE、UPDATE)为当前读,加 X 锁;
常见的 DDL 语句(如 ALTER、CREATE 等)加表级锁,且这些语句为隐式提交,不能回滚;
7.InnoDB存储引擎的锁的算法
Record lock:单个行记录上的锁
Gap lock:间隙锁,锁定一个范围,不包括记录本身
Next-key lock:record+gap 锁定一个范围,包含记录本身
8.关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
mysql可以开启慢查询统计、连接池也可以
优化
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
9.事务的基本特性和隔离级别
事务基本特性ACID
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
隔离性有4个隔离级别
read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
read commit 读已提交,两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已经提交的事务。用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
发生同一个事物里,同一条数据两次读取值不一样
repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
幻读和脏读的区别
10.ACID靠什么保证的
A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C一致性由其他三大特性保证、程序代码要保证业务上的一致性
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log恢复
InnoDB redo log 写盘,InnoDB 事务进入 prepare 状态。 如果前面 prepare 成功,binlog 写盘,再继续将事务日志持久化到 binlog,如果持久化成功,那么 InnoDB 事务则进入 commit 状态(在 redo log 里面写一个 commit 记录)redolog的刷盘会在系统空闲时进行
11.什么是MVCC
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链 --解决幻读
12.分表后非sharding_key的查询怎么处理,分表后的排序
1. 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过user_id去查询。
2. 宽表,对数据实时性要求不是很高的场景,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
3. 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。
13.mysql主从同步原理
Mysql的主从复制中主要有三个线程: master(binlog dump thread)、slave(I/O thread 、SQL thread) ,Master一条线程和Slave中的两条线程。
主节点 binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
主节点 log dump 线程,当 binlog 有变动时,log dump 线程读取其内容并发送给从节点。
从节点 I/O线程接收 binlog 内容,并将其写入到 relay log 文件中。
从节点的SQL 线程读取 relay log 文件内容对数据更新进行重放,最终保证主从数据库的一致性。
注:主从节点使用 binglog 文件 + position 偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发生宕机重启,则会自动从 position 的位置发起同步。
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。
14.简述MyISAM和InnoDB的区别
MyISAM
不支持事务,但是每次查询都是原子的;
支持表级锁,即每次操作是对整个表加锁;
存储表的总行数;
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。
InnoDb
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;
不存储总行数;
15.简述mysql中索引类型及对数据库的性能的影响
索引类型
普通索引:允许被索引的数据列包含重复的值。
唯一索引:可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。
联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。
覆盖索引(Covering Index)
select 的数据列只用从索引中就能够获取到,不必根据索引再次读取数据文件。查询列要被所建的索引覆盖。
判断标准
使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为 using index。
全文索引:通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
性能影响
索引可以极大的提高数据的查询速度
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变
16.mysql执行计划怎么看
EXPLAIN SELECT * from A where X=? and Y=?
1。id :是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为NULL最后执行。
2。selectType 表示查询中每个select子句的类型
SIMPLE: 表示此查询不包含 UNION 查询或子查询
PRIMARY: 表示此查询是最外层的查询(包含子查询)
SUBQUERY: 子查询中的第一个 SELECT
UNION: 表示此查询是 UNION 的第二或随后的查询
DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询
UNION RESULT, UNION 的结果
DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)
3.table:表示该语句查询的表
4.type:优化sql的重要字段,也是我们判断sql性能和优化程度重要指标。他的取值类型范围:
const:通过索引一次命中,匹配一行数据
system: 表中只有一行记录,相当于系统表;
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配
ref: 非唯一性索引扫描,返回匹配某个值的所有
range: 只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>;
index: 只遍历索引树;
ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。 那么基本就是随着表的数量增多,执行效率越慢。
5.possible_keys:它表示Mysql在执行该sql语句的时候,可能用到的索引信息,仅仅是可能,实际不一定会用到。
6.key:此字段是 mysql 在当前查询时所真正使用到的索引。 他是possible_keys的子集
7.key_len:表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用,这也是我们优化sql时,评估索引的重要指标
9.rows:mysql 查询优化器根据统计信息,估算该sql返回结果集需要扫描读取的行数,这个值相关重要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明要优化空间越大
10.filtered:返回结果的行占需要读到的行(rows列的值)的百分比,就是百分比越高,说明需要查询到数据越准确, 百分比越小,说明查询到的数据量大,而结果集很少
11.extra
using filesort :表示 mysql 对结果集进行外部排序,不能通过索引顺序达到排序效果。一般有using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。
using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。
using temporary:查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高,建议优化。
using where :sql使用了where过滤,效率较高。
执行流程图
索引覆盖
mabatis #{}和${}的区别是什么
#{}是预编译处理、是占位符, ${}是字符串替换、是拼接符。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 来赋值;
Mybatis 在处理${}时, 就是把${}替换成变量的值,调用 Statement 来赋值;
#{} 的变量替换是在DBMS 中、变量替换后,#{} 对应的变量自动加上单引号
${} 的变量替换是在 DBMS 外、变量替换后,${} 对应的变量不会加上单引号
使用#{}可以有效的防止 SQL 注入, 提高系统安全性。
redis
1.RDB 和 AOF 机制
RDB:Redis DataBase
优点
1、整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
AOF:Append Only File
优点
1、数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。
2、通过 append 模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redischeck-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的
缺点
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
3、运行效率没有RDB高
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
2.Redis的过期键的删除策略
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
3.Redis线程模型、单线程快的原因
单线程快的原因
纯内存操作
核心是基于非阻塞的IO多路复用机制
单线程反而避免了多线程的频繁上下文切换带来的性能问题
4.简述Redis事务实现
5.redis集群方案
哨兵模式
sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
哨兵通常需要 3 个实例,来保证自己的健壮性。
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
Redis Cluster
方案说明
通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
每份数据分片会存储在多个互为主从的多节点上
数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
同一分片多个节点间的数据不保持强一致性
读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
扩容时需要需要把旧节点的数据迁移一部分到新节点
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
优点
无中心架构,支持动态扩容,对业务透明
具备Sentinel的监控和自动Failover(故障转移)能力
客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
高性能,客户端直连redis服务,免去了proxy代理的损耗
缺点
运维也很复杂,数据迁移需要人工干预
只能使用0号数据库
不支持批量操作(pipeline管道操作)
分布式逻辑和存储模块耦合等
Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行
Redis Sharding
优点
优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强
缺点
由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化
Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool
6.redis 主从复制的核心原理
全量复制
主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的
主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗
部分复制
1. 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
2. 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
3. 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:
如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制
通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
7.缓存雪崩、缓存穿透、缓存击穿
缓存雪崩
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
缓存预热
互斥锁
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
缓存穿透
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
缓存击穿
设置热点数据永远不过期。
加互斥锁
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
分布式锁
setnx(set if not exists)
秒杀
分支主题
redis淘汰策略
1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6、no-enviction(驱逐):禁止驱逐数据
0 条评论
下一页