价值30K-----高级JAVA面试大纲
2024-02-02 21:37:26 0 举报
AI智能生成
这里有你想要的一切
作者其他创作
大纲/内容
高级JAVA面试大纲详解
概念:分布式系统, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)
可用性(A):集群部分节点故障,集群能响应客户端读写请求。(具备高可用性,响应要求及时)
分区容忍性(P):分区备份,可多节点提供服务,提高系统容错。系统如不能在时限内达成数据一致,在C和A间做选择。
CP:zookeeper,Consul,Hbase,Nacos(要配置,持久节点),Kafka(replication.factor = 3、min.insync.replicas = 3、acks = all)
AP:eureka,redis,Nacos(默认,临时节点),Kafka(replication.factor = 3、min.insync.replicas = 3、acks = 1)
CA:关系型数据库(mysql关系型数据库)、Kafka(Kafka的开发人员申明Kafka是CA系统,因为运行在一个数据中心,网络分区问题基本不会发生,如果发生了,就有AP和CP之分)
CAP原则
BASE对CAP延伸,无法强一致性(Strong Consistency),达到最终一致性(Eventual Consitency)。
1,基本可用(Basically Available):分布式系统出现不可预知故障,允许损失部分节点,部分时间段,部分业务模块可用性2,软状态( Soft State):允许系统数据存在中间状态,不会影响系统整体可用性。3,最终一致( Eventual Consistency):强调所有数据变更,一段时间同步(重试,负载均衡,分区同步,主从复制,消息同步),最终一致。
BASE大型,高可用,可扩展,分布式系统,牺牲ACID强一致性,获得可用性,允许数据在一段时间内不一致,终达到一致状态。
Base理论
1协调者向参与者发commit 2参与者执行事务,释放占有资源 3参与者给协调者commit结果
参与者回复正常执行
1,协调者向参与者发rollback 2,参与者收到事务回滚后,执行rollbacak,释放占用资源3参与者给协调者返rollback结果
1或多执行失败
等待超时
第一阶段,协调者收集参与者事务执行情况
1,协调者向参与者发送事务执行指令,等待参与者反馈事务执行结果 2,参与者收到请求,执行事务不提交,记录事务日志 3,参与者将事务执行情况给协调者,阻塞等待,协调者后续指令
实现
优点- 实现简单
缺点- 1,同步阻塞,机器出现性能,网络故障:执行中,参与节点都事务阻塞- 2,协调者,单点故障:协调者发生故障,参与者一直阻塞- 3,丢失消息,导致数据不一致:发Commit时,局部网络异常,只有部分参与者收请求- 4,过于保守:无完善容错机制,任意节点失败,都会导致整个事务失败
二阶段提交(2PC)
优点-3PC,协调者(Coordinator)和参与者(Participant)设置超时预估值,中断机制,2PC协调者才有超时机制。- 加了事务询问过程,设置中断逻辑,减少了阻塞范围
缺点- 未解决数据不一致问题
三阶段提交, 协调者,参与者引入超时机制 ,把第一个阶段拆分成两步:询问,后发布执行指令,后提交事务执行,最后提交事务。分别为:cancommit,precommit,do_commit
三阶段提交(3PC)
执行流程
Paxos为分布式强一致性提供理论基础,Paxos协议理解困难,实现复杂
优缺点
Paxos 协议是解决分布式系统,多节点对值(提案)达成一致(决议)通信协议。能处理少数节点离线情况,多数节点仍能够达成一致。
Paxos
ZAB
Raft
一致性协议
阻塞,效率低,依赖分配者,存在消息丢失,没有容错,试错方案
font color=\"#c40c0c\
TCC 方案(常用)- 用于保证数据正确性分布式事务场景,全成功,或者全回滚,如资金转账等- TCC 依赖回滚和补偿代码,适用场景较少
本地消息表- 依赖数据库消息表,无法支持高并发场景
MQ事务消息方案(常用)- 只保证发送端与MQ间事务性,不保证消费端事务- 消费端事务执行失败,1,不断重试直到成功,2,通知发送端回滚 3,人工补偿- 支持高并发,大多数公司选择,rocketmq解决方案
最大努力通知方案- 最大努力通知服务消费 MQ ,写入数据库中记录下来- 最大努力通知系统B,反复 N 次,最后还是不行就放弃。
分布式事务
Netty
分布式协调框架,Apache Hadoop子项目,解决分布式应用遇到数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置管理等。存储少量数据到内存数据库,两个核心概念:文件系统数据结构+监听通知机制。
什么是zookeeper?
客户端与zookeeper断开连接,节点依旧存在,不手动删除节点,永远存在
PERSISTENT(持久化目录节点)
客户端与zookeeper断开连接,节点依旧存在,Zookeeper给该节点名称,进行顺序编号
PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)
客户端与zookeeper断开连接后,节点被删除
EPHEMERAL(临时目录节点)
客户端与zookeeper断开连接,节点被删除,只是Zookeeper给该节点名称,进行顺序编号
EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点)
Container节点
默认禁用,配置 zookeeper.extendedTypesEnabled=true 开启,不稳定
TTL 节点
zookeeper的znode(目录节点)有哪些?
客户端注册,监听任意节点,目录节点及递归子目录节点。对节点监听,节点被删除,被修改时,客户端将被通知对目录监听,目录有子节点被创建,删除,客户端将被通知对目录递归子节点进行监听,目录下面任意子节点,有目录结构变化(子节点被创建,或被删除)根节点有数据变化时,客户端将被通知。注意:通知都是一次性,对节点,目录及递归子集进行监听,一旦触发,对应监听被移除。每节点下面事件只被触发一次。
zookeeper的watch监听通知机制
1,处理事务请求和非事务请求2,集群内部各服务器调度者
Leader
1,处理客户端非事务请求,转发事务给Leader2,参与事务请求Proposal(提案)投票3,参与Leader选举投票
Follower
1,处理客户端非事务请求,转发事务请求给Leader2,不参与leader选举,投票。写操作。3,数据没有持久化到硬盘。Observer只会把数据加载到内存。
Observer
服务器角色
LOOKING:寻找 Leader 状态,需进入选举流程LEADING:领导者状态FOLLOWING:跟随者状态,Leader 已选举出来,当前节点角色 followerOBSERVER:观察者状态,当前节点角色是 observer
Zookeeper 节点状态
ZooKeeper 状态变化生成 ZXID(ZooKeeper 事务 id)形式标记。ZXID 是64 位数字, Leader 统一分配,全局唯一,不断递增。ZXID 展示了所有ZooKeeper 变更顺序。每次变更有唯一 zxid, zxid1 小于 zxid2 说明 zxid1 在 zxid2 之前发生。值越大,数据越新,选举算法中权重越大
事务ID(zxid)
服务器ID(myid)
投票次数,同一轮投票过程,逻辑时钟值相同。投完,数据会增加,与接收到其它服务器返回投票信息数值相比,不同值,做出不同判断
逻辑时钟(epoch logicalclock)
选举相关的概念
集群初始化启动时 Leader 选举
集群运行期间 Leader 故障
leader选举
基于Zookeeper的实现方式:数据存储:将数据(配置信息)存储到Zookeeper节点数据获取:启动初始化节点,从Zookeeper数据节点读取数据,节点上注册一个数据变更Watcher数据变更:变更数据时,更新Zookeeper对应节点数据,Zookeeper会将数据变更通知,通过watch事件,发各客户端,接到通知后,变更数据设计模式Push 模式Pull 模式发布与订阅
分布式配置中心
注册中心1、服务提供方(user-service,N个实例)zk中创建临时节点,比如/user-service/1,...,/user-service/n2、服务调用方(order-serivce)监听/user-service,有变化,新增或删除实例,order-serivce会收到通知,重新获取数据,并重新设置监听
分布式注册中心
临时有序节点特性分布式锁,属于公平锁。1、请求进来,直接在/lock节点下创建临时有序节点,比如/order_id2、判断是不是/lock节点下面最小节点a.是,获取锁b.否,对前面节点进行监听(watch)3、获得锁,处理完任务,释放锁,delete节点,后继第一节点会得到通知,判断。临时有序节点好处,避免锁释放后,引发多个节点并发竞争锁(羊群效应),缓解服务器压力。
分布式互斥锁(公平锁)
获取锁原理:1、客户端 create() 持久节点临时有序子节点,返回带序号节点名称;2、客户端getChildren() 获取持久节点子节点列表;3、判断自己在子节点顺序,是第一个子节点(序号最小),获取到了锁;否,判断自己获取节点类型,是读节点,判断是否有比自己小的写节点,没有,则获读锁,有则通过 exist() 向比自己小最后写节点,注册Watcher监听;如果是写节点,那么通过 exist() 接口向比自己小最后节点注册一个Watcher监听;4、等Watcher通知,客户端创建的是读节点,则获取到了读锁;否在继续进行第二步
分布式共享锁(读锁)
分布式队列
集群选举
原理将启动服务注册到zk注册中心上,采用临时节点。从zk节点上获取最新服务节点信息,本地使用负载均衡算法,分配服务器
负载均衡
zookeeper的经典使用场景?
不是越多越好,选举和事务提交前询问会更耗时。如果是3、4台服务器哪个更好?3台更好,只能支持宕机1台服务,4台增加选举耗时。将其中1台换成Observer不一样,不参与投票,又提供服务。
集群使用几台服务器好?
Leader 收到客户端请求后,将请求封成个事务,给事务分配全局递增唯一 ID,称为事务ID(ZXID),ZAB 协议需保证事务顺序,必须将事务按照 ZXID 后排序后处理,通过消息队列实现, Leader 和 Follwer 间还有消息队列,用来解耦他们之间耦合,解除同步阻塞zookeeper集群中为保证进程能够有序顺序执行, Leader 服务器接受写请求,Follower 服务器接受到客户端写请求,转发到Leader 服务器进行处理,Follower只能处理读请求ZAB协议规定,事务在一台机器上被处理(commit)成功,所有机器上都被处理成功
消息广播
ZAB 定义 2 个原则:丢弃在 Leader 提出/复制,没有提交事务。确保 Leader 提交事务最终会被所有服务器提交
崩溃恢复
崩溃恢复后,需在正式工作前(接收客户端请求),Leader 确认事务是否已被过半 Follwer 提交,完成数据同步。为保持数据一致。Follwer 服务器成功同步后,Leader 将这些服务器加到可用服务器列表中。Zookeeper数据同步为四类1,直接差异化同步(DIFF同步)2,先回滚再差异化同步(TRUNC+DIFF同步)3,仅回滚同步(TRUNC同步)4,全量同步(SNAP同步)数据同步前,Leader服务器会完成数据同步初始化:peerLastZxid:从learner(follower或observer)注册发送ACKEPOCH消息中提取* lastZxid(Learner服务器最后处理ZXID)minCommittedLog:Leader服务器Proposal缓存队列committedLog中最小ZXIDmaxCommittedLog:Leader服务器Proposal缓存队列committedLog中最大ZXID直接差异化同步(DIFF同步)场景:peerLastZxid介于minCommittedLog和maxCommittedLog间先回滚,再差异化同步(TRUNC+DIFF同步)场景:新Leader服务器发现Learner服务器含自己没有事务记录,就需要Learner服务器进行事务回滚--回滚到Leader服务器同步,也是最接近peerLastZxid的ZXID仅回滚(TRUNC同步)场景:peerLastZxid 大于 maxCommittedLog全量同步(SNAP同步)场景一:peerLastZxid 小于 minCommittedLog场景二:Leader服务器上没有Proposal缓存队列,peerLastZxid不等于lastProcessZxid
数据同步
ZAB协议
zookeeper采用了全局递增事务Id来标识,所有proposal(提议)都在被提出时加上zxid,zxid是一个64位数字,高32位epoch(时期; 纪元; 世; 新时代)用来标识leader周期,有新的leader产生出来,epoch会自增,低32位用来递增计数。当新生proposal时,会依据数据库两阶段过程,先会向其他server发出事务执行请求,超过半数机器都能执行,且能够成功,那么就会开始执行。
zookeeper是如何保证事务的顺序一致性的?
水平扩容了:全部重启:关闭所有Zookeeper服务,修改配置后启动。不影响之前客户端会话。逐个重启:过半存活原则下,一台重启不影响整个集群对外提供服务。比较常用的方式。3.5版本开始支持动态扩容。
集群支持动态添加机器吗?
相同点:1,都存在类似于Leader角色,负责协调多Follower进程运行2,Leader等待超过半数Follower正确反馈后,才会提交提案3,存在Leader周期的属性,ZAB协议epoch,Paxos中Ballot不同点:应用场景不同,ZAB构建高可用分布式数据主备系统,Paxos构建分布式一致性状态机系统。
ZAB和Paxos算法的联系与区别?
ZooKeeper分布式锁:(1)优点:ZooKeeper分布式锁(如InterProcessMutex),有效的解决分布式系统资源竞争,不可重入问题,使用起来也较为简单。(2)缺点:ZooKeeper实现分布式锁,性能并不太高。为啥呢?创建锁和释放锁,要动态创建、销毁临时节点来。ZK中创建和删除节点,只能通过Leader服务器来执行,Leader还需要将数据同步到所有Follower机器上,这样频繁网络通信,性能短板,非常突出。
ZooKeeper分布式锁的优点和缺点?
zookeeper
dubbo
分库分表
ShardingSphere
专为Apache ZooKeeper设计,用于实现一致性的状态机复制服务。
设计初衷
强调顺序一致性,更新在有副本上,以相同顺序执行。两个关键阶段:崩溃恢复和消息广播。1,领导者选举过程与数据同步相结合,保证新领导者拥有最新事务状态。2,简化了Paxos的部分复杂性,并针对ZooKeeper的服务特性进行了优化。
特点
ZAB为Apache ZooKeeper专门设计,高度契合ZooKeeper的需求,确保了数据的强一致性。强调顺序一致性,保证所有更新操作在所有服务器上按全局一致的顺序进行。针对崩溃恢复进行了优化,能够快速重新选举并同步集群状态。
优点
主要应用于ZooKeeper这样的协调服务中,通用性不如Raft和Paxos广泛。对于非ZooKeeper应用场景,需要更多的定制化工作来适应具体需求。
缺点
主要用于构建高可用的服务协调系统(如ZooKeeper),处理分布式锁、配置管理等任务。
使用场景
设计目标:易于理解和实现,是Paxos的一种更具体且工程化的变体。
明确强领导者概念,每任期只有一个领导者,处理客请求,管理日志复制。详细定义领导人选举、日志复制、安全状态转换内,所有操作流程。成员变更(如集群扩容或缩容)清晰的操作指导。对于Paxos,Raft通过更直观的设计降低实现难度,提高可理解性。
设计目标是易于理解与实现,相对于Paxos更直观清晰,适合教学和工程实践。明确定义了领导者选举机制和日志复制流程,简化了分布式共识过程。提供了详细的操作步骤和清晰的状态转换图,有利于开发者准确地实现算法。
虽然相比Paxos更易于理解和实现,但在某些复杂场景下可能略显保守,性能上的优势并不总是明显。在节点数量较多的情况下,其网络通信开销可能会增加。
广泛应用于分布式存储系统、数据库、云原生应用以及各种要求强一致性的分布式系统中。
raft
最早的实用分布式共识算法之一,由Leslie Lamport提出
专注解决“拜占庭将军”问题,网络不稳定情况下,达成一致决策。基本思想通过提案、投票、接受来达到一致性。Paxos没有规定领导者角色和选举机制,抽象描述提议和批准过程,直接实现,需要更多工程化努力。虽然理论基础强大,因其表述相对抽象,实际实现起来相对复杂,不如Raft直观易懂。
是分布式系统中最基础且经过严格理论证明的一致性算法,具有非常高的容错性和稳定性。理论上可以解决任何分布式一致性问题,提供了强大的理论支撑。
Paxos的原始描述较为抽象和复杂,直接从学术论文中理解并实现起来相对困难。对于实际工程应用,需要开发人员深入理解并根据具体场景进行适当的优化和简化
常用于构建大型分布式数据库、分布式文件系统以及其他要求极高一致性和容错性的系统底层技术。
paxos
一致性算法
分布式框架
BrokerKafka集群包含一个或多个服务器,这种服务器被称为broker。broker端不维护数据的消费状态,提升了性能。直接使用磁盘进行存储,线性读写,速度快:避免了数据在JVM内存和系统内存之间的复制,减少耗性能的创建对象和垃圾回收
Producer消息生产者,负责发布消息到Kafka broker
Consumer消息消费者,向Kafka broker读取消息的客户端,consumer从broker拉取(pull)数据并进行处理
Topic每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic,属于逻辑上的概念
PartitionParition是物理上的概念,每个Topic包含一个或多个Partition
Consumer Group每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)
概念
消息分发策略?1、指定具体partition2、没有指定partition但有key的情况下,将key的hash值与topic的partition数进行取余得到partition3、没有指定partition且无key的情况下,会随机选择一个分区并一直使用,直到设定的时间到期后(metadata.max.age.ms,默认10分钟),会重新随机另外一个分区
acks0:生产者不会等待任何来自服务器响应1(默认):leader写日志成功,才会返回成功-1或者all:所有ISR的副本全部写日志成功才返回成功
retries重试次数(默认0),生产者从服务器收到的错误有可能是临时性的错误(比如分区找不到首领),需要重试,重试间隔默认100ms,可以通过retry.backoff.ms来设置
buffer.memory生产端用来缓存消息的缓冲区大小,默认32M生产者发送消息不是直接发到broker,而是放在缓冲区队列里面,再由后台线程不断从队列中取出消息发送,发送成功后会触发callback。如果缓冲区不足,会先阻塞一段时间(max.block.ms),再抛出异常
batch.size当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里,满了就发送。默认16K,该值过小的话,会降低吞吐量,过大的话,会带来较大的内存压力
linger.ms该参数指定了生产者在发送批次消息之前等待更多消息加入批次的时间。KafkaProducer会在批次填满或linger.ms达到上限时把批次发送出去。默认0,表示不等待,这种情况会导致批次消息过少就被发送了
max.block.ms默认60秒,KafkaProducer.send() and KafkaProducer.partitionsFor() 方法最大的阻塞时间,当buffer.memory满了或者集群的Metadata不可用时,这两个方法会被阻塞。
request.timeout.msProducer向Broker发送请求以后,等待响应的最长时间,默认30秒
重要参数
发送并忘记调用send()方法发送消息,但并不关心它是否正常到达。font color=\"#0b38d9\
同步发送调用send()方法发送消息,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功。font color=\"#0b38d9\
异步发送调用send()方法发送消息,并指定一个回调函数,服务器在返回响应时调用该函数
消息发送方式
至少一次(AtLeastOnce)默认,可以保证数据不丢失,但是不能保证数据不重复
最多一次(AtMostOnce)可以保证数据不重复,但是不能保证数据不丢失
精确一次(Exactly Once)不会漏传输也不会重复传输
数据的传递性语义
生产端
消费者消费原理?1、一个partition里面的消息,只能被同一consumer group中的某一个consumer来消费2、想要多个consumer来消费同一个partition,那这多个consumer必须要在不同的consumer group中3、如果consumer多于partition,则多出的consumer处于空闲浪费状态
消费者分区分配策略?1、Range(范围)默认:分区数/消费者数,多的按顺序分摊到前面几个消费者上面2、RoundRobin(轮询):轮询分区策略是把所有partition和所有consumer线程都列出来,然后按照hashcode进行排序。最后通过轮询算法分配partition给消费线程。3、Stricky(粘性):它主要有两个目的(分区的分配尽可能的均匀分区的分配,尽可能和上次分配保持相同),初始采用轮询,后面变化。
consumer和partition的数量建议?1、最好partition和consumer数量一致,或者partition是consumer的整数倍2、consumer大于partition,会导致consumer浪费3、consumer远小于partition,会导致吞吐量降低consumer从多个partition读取数据,不保证数据间的顺序性,kafka只保证在一个partition上数据是有序的
什么是rebalance机制?什么时候会触发该机制?Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer 如何达成一致,来分配订阅 Topic 的每个分区。1、同一个consumer group内新增了消费者2、消费者离开当前所属的consumer group,比如主动停机或者宕机3、topic新增了分区(也就是分区数量发生了变化)
什么是coordinator?coordinator又是如何协调consumer group消费的(rebalance过程)?coordinator是用来统筹管理consumer group消费的1、确定coordinator:broker集群会将集群中负载最小的broker定为coordinator2、获取coordinator:consumer会向集群中任意一个broker发送一个GroupCoordinatorRequest来获取coordinator3、确定consumer leader:所有的consumer向coordinator发送joinGroup请求,coordinator会从中挑选一个来担任consumer leader角色(如果没有leader,那么第一个请求的就是Leader;如果leader退出了消费组,则随机选举一个)4、确定分区分配策略:每个consumer都可以有自己的分区分配策略,在上述joinGroup的过程中,consumer也会将自己的分区分配策略发给coordinator,由coordinator组成一个侯选集,然后每个consumer都从中选举一个自己支持的策略并投票,最终票数多的为最终策略5、确定消费partition方案:leader选举出来后,会根据最终策略,产生一个消费partition的方案,然后向coordinator发送一个SyncGroupRequest请求,将方案同步给coordinator6、同步方案给消费者:coordinator拿到方案后,将方案同步给其它consumer,除非发生rebalance,否则consumer只会消费对应的PartitionRebalance期间,整个消费组无法消费消息
消费端
Controller(控制器)在Kafka集群中会有一个broker会被选举为控制器(KafkaController),它负责管理整个集群中所有分区和副本的状态1、监听broker相关的变化2、监听topic相关的变化3、从Zookeeper中读取获取当前所有与topic、partition以及broker有关的信息并进行相应的管理4、更新集群的元数据信息,同步到其他普通的broker节点中
Leaderpartition中的Leader副本,提供读写服务,并且同步数据给Follower副本
Followerpartition中的Follower副本,不提供读写服务,用于备份Leader副本中的数据,与Leader是一主多从关系
AR(Assigned Repllicas)partition中的所有副本集合
ISR(In-Sync Replicas)与leader副本保持一定程度同步(replica.lag.time.max.ms时间内有同步)的副本集合(ISR中的第一个副本就是Leader副本),ISR是AR的子集Leader副本负责维护和跟踪ISR集合中所有的follower副本的滞后状态,当follower副本落后太多或者失效时,leader副本会吧它从ISR集合中剔除
OSR(Out-Sync Relipcas)与leader副本同步滞后过多的副本集合,AR = ISR + OSR
LEO(Log End Offset)标识当前日志文件中下一条待写入的消息的offset。LEO 的大小相当于当前日志分区中最后一条消息的offset值加1。
HW(High Watermark)俗称高水位,它标识了一个消息偏移量(offset),消费者只能拉取到这个offset之前的消息。HW为ISR中所有副本的最小LEO
LW(Low Watermark)俗称低水位,AR集合中最小的Log Start Offset
Controller选举优先到zookeeper上面创建/controller临时节点的broker为Controller
Partition副本Leader选举Controller会从ISR列表(参数unclean.leader.election.enable=false的前提下)里挑第一个broker作为Leader(第一个broker最先放进ISR列表,可能是同步数据最多的副本),如果参数unclean.leader.election.enable=true,代表在ISR列表里所有副本都挂了的时候可以在ISR列表以外的副本中选Leader
Kafka数据同步方式?Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制,而是半同步复制。Kafka使用这种ISR的方式有效的权衡了数据可靠性和性能之间关系
Kafka如何保存消费端的消费位置?Kafka是通过offset来保存消费者的位置的。在kafka中,提供了一个consumer_offsets_* 的一个topic,把offset信息写入到这个topic中。offset不跨分区,只保证同一分区的offset的顺序性
服务端
Kafka为什么速度这么快?1、利用 Partition实现数据分区存储,提高并发处理效率2、利用页缓存技术,提高IO效率3、顺序写,在刷盘时,将数据追加到文件的末尾,性能不比写内存差多少4、零拷贝(读):减少了从os cache拷贝到应用缓存和从应用缓存拷贝到socket缓存。Socket缓存中仅仅会拷贝一个描述符过去,不会拷贝数据到Socket缓存5、消息批量发送,提高吞吐量6、数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP、Snappy、LZ4格式对消息集合进行压缩操作系统本身有一层缓存,叫做page cache,是在内存里的缓存,我们也可以称之为os cache,意思就是操作系统自己管理的缓存。
Kafka如何快速读取指定offset的消息?1、Partition数据是分段(Segment)存储,每个分段都包含log、index、timeindex三个文件。2、offset根据font color=\"#0b38d9\
Kafka如何保证消息不丢失?1、设置 ack=-1,保证ISR所有副本全部写日志成功才返回;producer.type=sync,设置成同步模式2、设置 replication.factor>=3,并且 min.insync.replicas>=2,保证ISR中副本集至少大于等于23、设置 unclean.leader.election.enable=false,关闭跟不上Leader的follower副本的选举4、设置 enable.auto.commit=false,消费者改为手动提交第1点是生产端,第2、3点是服务端,第4点为消费端-- min.insync.replicas:ISR里面的最小副本数-- replication.factor:这个参数用来表示分区的副本数
Kafka 是怎么去实现负载均衡的?1、生产者:消息分发策略2、消费者:分区分配策略
为什么Kafka不支持读写分离?1、读写分离(主写从读)会存在数据一致性和延时问题2、Kafka本身采取了分区的策略,天然提高了并发效率,不需要进一步采用读写分离来提升效率
Kafka 分区数可以增加或减少吗?为什么?1、支持分区增加,增加后会重新rebalance就好了2、不支持分区减少,因为会丢失上面的消息
Kafka消息是采用Pull模式,还是Push模式?1、Push:当消息生产速率远大于消费速率,消费者容易崩溃2、Pull:可以根据自己的消费能力拉取数据,但是如果没有消息时,会不断轮询,可以在消费端设置轮询间隔
Kafka的分区数是不是越多越好? -- 不是1、生产者缓存占用内存会更多(一个分区会有一个batch.size的开销)2、消费者线程数也会更多(一个分区需要一个线程来处理)3、服务端的很多组件在内存中维护分区级别缓存,比如controller,FetcherManager等,因此分区数越多,这种缓存的成本就越大4、文件句柄的开销会更多(.index和.log文件数量会更多)
谈谈你对Kafka生产者幂等性的了解?Kafka精确一次性(Exactly-once)保障之一为了实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。PID。新的Producer在初始化时,分配一个PID,这个PID用户是不可见的。Sequence Numbler。对PID,Producer发送数据对应从0单调递增Sequence NumberBroker端,在缓存保存这seq number,对接收消息,如序号比Broker缓存中序号大于1,则接受,否,丢弃,这样就可以实现消息重复提交。只能保证单个Producer
Kafka如何保证消息的有序性?单分区可以保证有序性,多分区不可以1、将max.in.flight.requests.per.connection设置为1,消息队列只允许一个请求,消息失败,第一时间发送,不会乱序,会降低网络吞吐量。2、开启生产者幂等性设置(Exactly-once)
Kafka缺点?由于是批量发送,数据并非真正的实时;对于mqtt协议不支持;不支持物联网传感数据直接接入;支持同分区内消息有序,无法实现全局消息有序;监控不完善,需安装插件;依赖zookeeper进行元数据管理;3.0版本去除
常见问题汇总
Kafka
Broker-- RocketMQ集群包含一个或多个服务器,服务器被称Broker。Broker是存储和转发消息, 单机大约能承受 10 万 QPS 请求。-- RocketMQ集群Broker保存总数据一部分,可实现横向扩展。为提升可靠性(防止数据丢失), Broker 可有自己副本(slave)-- 默认情况下,读写都发生在 master。slaveReadEnable=true 的情况下,slave 也可以参与读负载。只有 Brokerld=1的 slave 才参与读负载注:Broker 会向所有的Name Server注册
Master-- 主节点,配置来设置,不会改变,Master挂了后,从节点不会选举变成主节点注:4.5以后,基于dledger技术可以进行选举,推举一个slave成master。
Slave-- 从节点,用于数据备份,配置来设置,4.5前,不会改变,Master挂了后,从节点不会选举变成主节点
NameServer-- NameServer RocketMQ路由中心-- Producer 和 Consumer通过NameServer拿到Topic相关信息-- NameServer间不做数据通信,每个NameServer 节点都保存着全量路由信息
Producer-- Producer 跟 Name Server 任意节点(随机)建立长连接,定期从Name Server拉取 Topic 路由信息-- 根据Topic路由信息与指定Broker 建立TCP长连接-- 发送逻辑一致Producer 可组成一个 Group-- RocketMQ生产者支持批量发送,List要自己传进去-- Producer写数据只能操作 master 节点
Consumer-- Consumer 跟 Name Server 任意节点(随机)建立长连接,定期从 Name Server 拉取 Topic 路由信息-- 根据Topic路由信息与指定的Master和Slave 建立 TCP长连接-- 消费逻辑一致 Consumer可组成一个Group-- 在Brokerld=1 slaveReadEnable=true 的情况下,slave 也可以参与读负载
Topic在 RocketMQ中,Topic是一个逻辑概念,消息不是按 Topic 划分存储的
Message Queue类似Kafka里面的Partition分片概念,topic的数据是分片存储
同步发送消息发送后,producer等到broker回应后,才能继续发送下一个消息
异步发送发出数据后,不等borker响应,发送下数据包注:有一个异步发送回调接口(SendCallback)来处理返回信息
font color=\"#e0124f\
顺序发送(OrderProducer)顺序发送比较复杂,支持局部顺序消息,要保证发送端,服务端,消费端顺序保持一致
发送方式
消息的存储结构是怎么样的?-- CommitLog:存储消息元数据。消息顺序存到CommitLog文件。CommitLog多个文件组成,每个文件固定大小1G。以第条消息偏移量为文件名。-- ComsumeQueue:存储消息CommitLog的索引。记录当前MessageQueue被存到了哪一条CommitLog。-- IndexFile:为消息查询提供了一种通过key或时间区间,来查询消息的方法,这种通过IndexFile来查找消息的方法,不影响发送与消费消息主流程
刷盘机制是怎么样的?-- 同步刷盘,消息写入内存PAGECACHE后,立刻通知线程刷盘, 然后等待刷盘完成,执行完成后唤醒等待线程,返回消息写成功状态-- 异步刷盘,消息可能写入内存PAGECACHE就返回消息成功,何时刷盘,操作系统自己决定注:配置方式,Broker配置文件里的flushDiskType(SYNC_FLUSH、ASYNC_FLUSH)
消息主从复制是怎么样的?-- 同步复制,Master和Slave都写消息成功后,才反馈给客户端写成功状态-- 异步复制,只要master写消息成功,就反馈给客户端写成功。再异步将消息复制给Slave节点。注:配置方式,Broker配置文件里的brokerRole(ASYNC_MASTER、 SYNC_MASTER、SLAVE)
集群消费(Clustering)相同Consumer Group每Consumer实例协作 分摊消息
广播消费(Broadcasting)相同Consumer Group的每Consumer实例接收全量的消息
消息模式
拉取式消费(pull)Consumer主动从Broker服务器拉消息
推动式消费(push)Broker收到数据,主动推送给消费端,实时性比较高,底层同样是pull
消费形式
NameServer 作为路由中心到底怎么工作的呢?-- 节点在启动时,根据配置NameServer列表,与每个NameServer建立TCP长连接,并注册自己信息-- Broker(包含master和slave)每30s发送心跳信息,表示自己存活-- NameServer每10s检查Broker 最近一次心跳时间,发现Broker超过120s都没发送心跳,认为Broker 挂掉,将其从路由信息里移除。
NameServer 之间是互相不通信的,也没有主从之分,它们是怎么保持一致性的?-- 服务注册,Broker都会向所有NameServer进行注册,并且每隔30s发送心跳-- 服务剔除,Broker正常关闭(连接断开),Netty通道关闭,监听器监听到连接断开事件;Broker异常关闭,NameServer每10s检查,某Broker120s都没有发送心跳,可剔除-- 路由发现,生产者在发送第一条消息时,根据从NameServer 获取路由信息,消费者一般订阅固定Topic,启动时候就要获取 Broker 信息注:-- NameServer 不会主动推送服务信息给客户端(指生产端和消费端),-- 客户端不会发送心跳到Nameserver-- 客户端每隔30s到NameServer拉取最新信息,缓存在本地-- NameServer 是AP,只保证最终一致性,Zookeeper是CP
如果作为路由中心的 NameServer 全部挂掉了,而且暂时没有恢复呢?-- 客户端会缓存路由信息在本地,不完全依赖于 NameServer
如果 Broker 刚挂,怎么处理数据?-- 重试-- 把无法连接Broker 隔离掉,不再连接-- 优先选择延迟小节点,就能避免连接到容易挂 Broker 了
路由端(NameServer)
RocketMQ磁盘保存文件慢吗?-- 顺序写,保证了消息存储的速度。高性能磁盘顺序写速度可以达到600MB/s-- 零拷贝,减少了2次数据在内存中的复制(内核空间到用户空间及用户空间到内核socket)-- 页缓存,利用页缓存技术,提高IO效率注:-- RocketMQ采用的是mmap技术,Kafka采用的是sendfile技术-- 关于零拷贝,JAVA的NIO中提供了两种实现方式,mmap和sendfile,其中mmap适合比较小的文件(1.5~2G),而sendfile适合传递比较大的文件
RocketMQ延时消息是怎么实现的?1、producer 要将一个延迟消息发送到某个 Topic 中2、Broker 判断这是一个延迟消息后,将其通过临时存储进行暂存3、Broker 内部通过一个延迟服务(delay service)检查消息是否到期,将到期的消息投递到目标 Topic 中4、消费者消费目标 topic 中的延迟投递的消息注:对外非商业的版本,延时设置有18个级别:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
RocketMQ事务消息实现原理?即怎么保证数据一致性?注:half消息对消费者是不可见的,服务端和broker间的通信
RocketMQ如何保证消息不丢失?1、同步发送(等待返回结果)+重试机制,尽可能减小消息丢失的可能性(或者生产者使用事务消息机制)2、Broker配置,分区容错性,同步刷盘+Dledger主从架构 + 多个master节点3、消费者不用异步消费 + 消费消息重试机制
RocketMQ如何保证消息顺序消费?1、发送端:同步发送(保证消息发送顺序) + 自定义投放策略(保证消息存储在同个MessageQueue)2、消费端:使用有序消费模式MessageListenerOrderly,MessageListenerOrderly是通过加分布式锁和本地锁,保证同时只有一条线程,去消费一个队列上数据。
消息消费失败,消费端处理机制?1,消费消息出现异常,RocketMQ默认多次重试。重试次数和重试间隔时间客户端配置,通过consumer的retryTimesWhenSendFailed设置重试次数,通过consumeMessageBatchMaxSize与consumeConcurrentlyMaxSpan等参数调整消费批次大小,并发度2,若干次重试仍无法成功消费,RocketMQ可将消息发延时队列,后再尝试消费,自定义延时消息或者死信队列来实现。3,最大重试次数后仍未正确处理,RocketMQ将其存入死信队列。监听死信队列来排查问题、修复错误,并对死信进行特殊处理。4,对至少被消费一次的场景,RocketMQ提供事务消息支持。当事务消息二次确认失败,服务端会进行回查,根据业务结果决定是回滚,再次投递。5,在某些高级使用场景下,消费者可手动控制消息ACK(确认收到并处理完成)或NACK(拒绝接收或处理失败)。如NACK,认为该消息未被正确处理,并触发重试机制。
RocketMQ
为什么使用消息中间件?-- 解耦、异步、削峰引入消息队列会存在什么问题?-- 1,系统实际可用性降低(业务系统正常,但消息中间件挂了,或者堆积不消费)-- 2,系统复杂性提高(1,消息丢失,2,重复消费、3,顺序消费、4,数据一致性问题等)Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?-- Kafka:高可用(分布式),高吞吐(10W级),低延时(ms级别),消息0丢失,功能简单(适合大数据计算及数据采集)-- RocketMQ:高可用(分布式),高吞吐(10W级),低延时(ms级别),消息0丢失,MQ功能完善(适合业务需求)-- RabbitMQ:高可用(主从),高吞吐(1W级),超低延时(微秒级别),消息基本不丢失,MQ功能完善-- ActiveMQ:高可用(主从),高吞吐(1W级),低延时(ms级别),消息较低丢失,MQ功能完善
如何保证消息不丢失? -- Kafka1、设置 ack=-1,保证ISR所有副本全部写日志成功才返回;producer.type=sync,设置成同步模式,retries=MAX无限重试2、设置 replication.factor>=3,并且 min.insync.replicas>=2,保证ISR中副本集至少大于等于23、设置 unclean.leader.election.enable=false,关闭跟不上Leader的follower副本的选举4、设置 enable.auto.commit=false,消费者改为手动提交第1点是生产端,第2、3点是服务端,第4点为消费端-- min.insync.replicas:ISR里面的最小副本数-- replication.factor:这个参数用来表示分区的副本数
如何保证消息不被重复消费(幂等性)?1、消费者消费完消息,但还未提交到kafka更新offset,宕机了,导致offset未变化2、通过数据库唯一约束或者redis唯一键来判断
如何保证消息的顺序性? -- Kafka-- 一个消费者开多个线程来处理一个Partition里面的顺序消息时,会出现顺序错乱的问题解决方案:写N个queue,相同的Key的数据放到同一个queue,然后开N个线程,每个线程分别消费其中一个queue。这样就保证了同一个key的消息是顺序消费的,而且也兼顾了吞吐量
有几百万消息持续积压几小时,说说怎么解决? -- Kafka扩容1、先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。2、新建一个 topic,partition 是原来的 10 倍3、写一个临时分发的程序,将积压的消息转发到新的topic里面来4、部署10倍的 consumer 来消费新的topic5、消费完后,切换成原有的架构注意保证消息幂等性mq 写满了丢失数据怎么处理?如消息积压,上面扩容方案没有来得及实施,mq就满了,这时只能丢失部分数据,后面再从业务系统中找出数据后写入mq
常见问题
消息中间件
基于内存运行非关系型(NoSql)数据库。
什么是redis?
1,丰富数据类型;2,进程内与跨进程;3,单机与分布式;4,功能丰富:持久化机制、过期策略;5,支持多种编程语言;6,高可用,集群;
redis特性?
redis是单线程的吗?- 单线程「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」一个线程(主线程)来完成- Redis 启动时,会创建3个BIO线程「1,关闭文件(BIO_CLOSE_FILE)、2,AOF 刷盘(BIO_AOF_FSYNC)、3,释放内存(BIO_LAZY_FREE)」。- Redis6.0后,创建3个线程,协助主线程处理写请求(默认,也可以通过修改配置,读请求用多线程IO)//读请求使用io多线程io-threads-do-reads yes // io-threads N,表示启用 N-1 个 I/O 多线程(主线程也算一个 I/O 线程)io-threads 4
String 字符串
Hash 哈希
List 列表
String 类型无序集合,最大存储数量 2^32-1(40 亿左右)。sadd myset a b c d e f g应用场景:1 抽奖:随机获取元素spop mykey2 点赞、签到、打卡这条微博的 ID 是 t1001,用户 ID 是 u3001。用 like:t1001 来维护 t1001 这条微博的所有点赞用户。点赞了这条微博:sadd like:t1001 u3001取消点赞:srem like:t1001 u3001是否点赞:sismember like:t1001 u3001点赞的所有用户:smembers like:t1001点赞数:scard like:t1001比关系型数据库简单许多。3 商品标签用 tags:i5001 来维护商品所有的标签。sadd tags:i5001 画面清晰细腻sadd tags:i5001 真彩清晰显示屏sadd tags:i5001 流畅至极4 商品筛选获取差集sdiff set1 set2获取交集(intersection )sinter set1 set2获取并集sunion set1 set2筛选商品,苹果的,iOS 的,屏幕在 60-624 之间的,屏幕材质是 LCD 屏幕sinter brand:apple brand:ios screensize:60-624 screentype:lcd5 用户关注、推荐模型
Set 集合
sorted set,有序的 set,每个元素有个 score。score 相同时,按照 key 的 ASCII 码排序。zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python应用场景:1 排行榜id 为 6001 新闻点击数加 1zincrby hotNews:20190926 1 6001今天点击最多的 15 条zrevrange hotNews:20190926 0 15 withscores
子主题
ZSet 有序集合
redis支持的数据类型?
redis线程模型
自动触发
手动触发
RDB触发规则
bgsave流程
fork()
主进程fork()子进程后,内核把主进程中内存页的权限设为read-only,子进程地址空间指向主进程。共享了主进程内存,其中某个进程写内存时(是主进程写,子进程rdb文件持久化工作,不参与客户端请求),CPU硬件检测到内存页read-only,触发页异常中断(page-fault),内核中断例程。内核会把触发的异常页复制一份(仅复制异常页,所修改数据页,不是内存中全部数据),主子进程各自持有独立同样的数据
CopyOnWrite
RDB(Redis DataBase)
1、resp协议格式数据2、* 后面数字,代表命令有多少参数3、$ 号后面的数字,代表参数几个字符4、带过期时间set命令,记录key过期时间戳
AOF文件格式
开启策略
重写策略
bgRewriteAof流程
AOF(Append Only File)
redis持久化
定时过期(主动)-- 设key过期时间,同时,为该key创建定时器,让定时器在key过期时间来临前,对key进行删除对内存友好、对cpu不友好(新增定时器,消耗cpu资源)
定期过期-- 每隔一段时间(默认100ms),扫描expires 字典(默认20)key,并清除其中已过期的 key该策略是折中方案span style=\
过期策略
LRU是Least Recently Used,最近很少用,可理解成最久没有用1、链表满,把链表尾部数据丢弃掉,新加的缓存,直接加到链表头。2、链表缓存被命中,直接把数据移到链头,原头节点缓存就向表尾移动。
LRU策略建议使用All-keys Lru
LFU策略
随机策略
TTL策略
noeviction策略(默认)
1、Redis维护大小16候选池,第一次随机选取采用数据,把数据放到候选池中,候选池中数据根据key空闲进行排序。2、第二次后取数据时,大于候选池内最小空闲时间key,放进候选池。3、候选池数据满后,空闲时间最大key,就会被挤出候选池。执行淘汰时,从候选池,选取空闲时间最大key进行淘汰。
Redis的LRU
淘汰策略
redis内存回收
主从数据是如何同步的?1、slave连上master后(stocket长连接),会发送一个PSYNC命令给master请求复制数据2、master通过bgsave来生成rdb文件,并在开始生成文件的时候,将后面的写命令都缓存到buffer中3、rdb文件生成好后,master将文件send到slave4、slave收到rdb文件后,先清空自己的旧文件,再将新的rdb内容加载到内存中5、master发送buffer,slave执行buffer中的写命令6、master通过stocket长连接,将写命令持续发到slave中,保持数据一致
主从架构
服务下线判断1、Sentinel 默认以每秒钟 1 次的频率向 Redis 服务节点发送 PING 命令2、如果在down-after-milliseconds 内都没有收到有效回复,Sentinel 会将该服务器标记为下线(主观下线)3、Sentinel 节点会继续询问其他的 Sentinel 节点,确认这个节点是否下线,如果多数 Sentinel 节点都认为 master 下线,master 才真正确认被下线(客观下线),这个时候就需要重新选举 master。
Sentinel 集群的 Leader 选举(Raft算法)1、master 客观下线触发选举2、每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel(先到先得)的leader3、超过一半的sentinel选举某sentinel作为leader注:sentinel是没有主从是分的,只是在客观下线后,有个leader选举,leader来处理故障转移
故障转移1、选出 Sentinel Leader 之后,由 Sentinel Leader 从所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器2、让其它从服务器改为从新的主服务器复制数据3、将原主服务器也改为从新的主服务器复制数据
这么多从节点,选谁成为新的主节点?1、如果与哨兵断开连接时长的比较久,超过了某个阈值,就直接失去了选举权2、如果拥有选举权,那就看谁的优先级高(replica-priority 100),数值越小优先级越高3、优先级相同,就看谁从 master 中复制的数据最多(复制偏移量最大),选最多的那个4、复制数量也相同,就选择进程 id 最小的那个
哨兵架构
数据怎么相对均匀地分片?1、hash取模:节点变化后,需要对所有数据进行重新分布2、一致性hash:解决节点变化后,全部数据都要重新分布的问题。引入虚拟节点,解决数据分布不均匀问题3、hash slot:对 key 用 CRC16 算法计算再%16384,得到一个 slot的值注意:key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系
怎么让相关的数据落到同一个节点上?-- 在 key 里面加入{hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算如:127.0.0.1:7293> set a{qs}a 1
客户端连接到哪一台服务器?访问的数据不在当前节点上,怎么办?-- 客户端操作的key不在当前连接的Redis服务器上,会返回一个MOVED,且含有Key所有服务器地址,重定向连接新服务器即可如:127.0.0.1:7291> set qs 1(error) MOVED 13724 127.0.0.1:7293Jedis 等客户端会在本地维护一份 slot——node 的映射关系,大部分时候不需要重定向,所以叫做 smart jedis(需要客户端支持)。
新增或下线了 Master 节点,数据怎么迁移(重新分配)?-- 需要把原有的 slot分配给新的节点负责,并且把相关的数据迁移过来如:redis-cli --cluster add-node 127.0.0.1:7291 127.0.0.1:7297redis-cli --cluster reshard 127.0.0.1:7291
Redis集群选举原理分析当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:1、slave发现自己的master变为FAIL2、将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack4、尝试failover的slave收集master返回的FAILOVER_AUTH_ACK5、slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)6、slave广播Pong消息通知其他集群节点
生产环境部署情况?1、集群部署,3主3从,32G 内存+ 8 核 CPU + 1T 磁盘,redis只占用10g2、用户数据,商品数据,库存数据
集群架构
redis高可用架构
为什么要用缓存?-- 高性能(响应,存储,容错,稳定,如果访问多,变动小,可以放缓存)-- 高并发(内存,支持高并发)用了缓存之后会有什么不良后果?-- 缓存雪崩、缓存穿透、缓存击穿、缓存与数据库双写不一致
Redis为什么会这么快?1、纯内存操作2、数据接入单线程(避免创建,销毁线程开销、避免上下文切换、避免多线程资源竞争,引发锁问题)3、IO多路复用注意:-- 官方数据Redis 的 QPS 可以达到 10 万左右(每秒请求数)。-- cpu不会成为redis瓶颈,redis瓶颈最有可能是机器内存,网络带宽。
如何保证缓存与数据库双写时的数据一致性?-- 先更新数据库,再删除缓存,再配合删除缓存,重试机制(mq或者binlog)(短暂数据不一致)1、先更新数据库,再更新缓存(多写高并发,无法保证顺序,存在数据库缓存数据不一致情况;存在浪费性能)2、先删除缓存,再更新数据库(读写高并发时,存在数据库,缓存数据不一致)3、先删除缓存,再更新数据库,最后,延时删除缓存(存在延长时间不好确认情况)
什么是缓存穿透?--缓存、数据库均不存在解决方案是什么?-- 缓存空对象,返回空对象,设过期时间(浪费内存,存储空对象,导致数据库与缓存数据不一致)-- 布隆过滤器(无法删除元素,会影响key)注:布隆过滤器,判断不存在,一定不存在,判断存在,不一定存在
什么是缓存击穿?-- 单一热点key,缓存数据失效解决方案是什么?-- 设key永不过期-- 用分布式锁查key,失效,对key进行加锁,只允许一个查询拿到锁,查询数据库(其它自旋等待),结果查询出来存储到缓存,释放锁,其它查询,从缓存获取
什么是缓存雪崩?-- 大量缓存数据同时失效解决方案是什么?--1, 设key永不过期-- 2,将key过期时间打乱-- 3,redis前,加本地缓存-- 4,限流组件,限制每秒请求数,未通过请求走降级-- 5,高可用集群
redis
数据存储方式使用B树
monogdb
index(索引)es 存储数据基本单位是索引,类似于表
document(文档)document就相当于表中一条记录
field(列)field相当于表字段
mapping(映射)表结构
shard(分片)类似于kafka中partition分片,shard最好在同机房,减少网络带宽消耗
replica(副本)类似于kafka中的follow副本
primary(主副本)类似于kafka中的leader副本
node(节点)类似于kafka中的节点服务器broker
master类似于kafka中的leader,选举出来的,master选举采用Raft算法
IK分词器
HanL分词器
中文分词器
什么是倒排索引?倒排索引是如何查找到数据的?-- 通过value 找 key
es 读取数据的过程?1、读请求任意节点,节点成coordinating node(协调节点)2、协调节点对doc id 路由(hash),转到对应node(随机轮询算法,将请求转发primary shard或replica shard)3、node 处理读请求,结果返协调节点4、协调节点将数据返给客户端注意:primary shard负责读写,replica shard负责读
es 搜索数据过程?1、请求任意节点,节点成coordinating node(协调节点)2、协调节点广播到所有数据节点,节点分片处理请求3、分片查询结果(文档ID,节点信息,分片信息)放队列,返给协调节点4、协调节点将结果汇总,排序5、根据文档ID,去各个分片拉取实际文档数据,返回给客户端
es 写数据底层原理?-- master选举采用Raft算法
es 性能优化方案?-- 加filesystem cache大小-- 数据预热,热点数据预热filesystem cache中-- 冷热分离,冷数据写一个索引,热数据写另一个索引,让热点数据保留filesystem cache中
ES(ElasticSearch)
存储中间件
1 先删除缓存2 更新数据库3 休眠一会(比如1秒),再删除缓存。缺点: 休眠时间不确定,过短导致第二次缓存删除后,另一个读到旧数据,将旧数据写入到缓存中
延时双删策略
1 写,更新数据库2 缓存删除失败3 把删除失败key放到消息队列4 消费消息队列消息,获取要删除的key5 重试删除缓存操作
删除缓存重试机制
1 读取缓存中是否有数据2 缓存有相关数据value,则返回3 缓存无相关数据,从数据库读取数据,放缓存,再返回4 有更新数据,先更新数据库,再删缓存5 为保证删成功,用binlog异步删除6 主从数据库,binglog取从库7 一主多从,从库采集binlog,消费端收到所有binlog数据,才删缓存,为简单,收一次更新log,删除一次缓存Canal+RocketMQ同步MySQL到Redis/ES
读取biglog异步删除缓存
Redis与MySQL双写一致性如何保证?
Ngnix缓存
本地缓存只读、数据小、高频率访问数据。如秒杀商品数据。优点:访问速度快缺点:1 占JVM空间,不能存储大量数据2 本地缓存更新复杂度高3 应用重启后,缓存数据丢失,缓存雪崩,数据库造成巨大压力4 多应用节点,无法共享缓存数据
本地缓存
1 热点数据大量访问,对系统造成各种网络开销,影响系统性能2 集中缓存雪崩,缓存被击穿,造成数据库的压力增大,造成数据库挂机状态,进而造成服务宕机
Redis缓存
为什么要用三级缓存架构?
秒杀超卖 解决方案?
分布式
服务提供者启动时会将自己的服务信息(如接口名、版本号、IP地址、端口号等)注册到注册中心。服务消费者在启动时或需要时向注册中心订阅所需的服务,从注册中心获取可用的服务提供者列表。
服务注册中心 (Registry)
提供者实现了服务接口并对外暴露服务,通过Dubbo框架将其服务注册到注册中心。
服务提供者 (Provider)
消费者引用服务接口并通过注册中心发现和调用远程服务,Dubbo会根据配置创建代理对象,隐藏了网络通信和协议转换等细节。
服务消费者 (Consumer)
监控中心用于收集统计信息,如服务调用次数、耗时等,便于运维人员对系统性能进行监控和优化。
配置层允许开发者以集中化的方式管理服务的元数据信息,包括接口定义、服务版本、路由规则、负载均衡策略等。
配置管理
核心组件
提供者启动时将服务信息发布到注册中心,消费者则订阅相应的服务,注册中心负责通知消费者的变更情况。
服务发布与订阅
消费者通过本地代理对象发起远程服务调用,Dubbo基于指定的协议(如dubbo、rmi、http等)进行远程通讯。
服务调用
在消费者端,Dubbo支持多种负载均衡策略,如随机、轮询、最少活跃调用数、一致性Hash等,确保请求能均匀地分发给多个服务提供者实例。
如果服务提供者发生故障,Dubbo能够通过不同的容错模式(如快速失败、失败安全、失败自动恢复等)保证服务调用的健壮性。
容错与失败恢复
Dubbo采用SPI(Service Provider Interface)扩展机制,使得开发者可以自定义过滤器、适配器、序列化方式等多种功能模块。
扩展机制
运行机制
支持TCP、HTTP等不同网络传输协议。
传输协议
支持Hessian2、JSON、Java原生序列化等序列化方式,实现跨语言调用的可能性。
数据编解码
网络通信
通过集群层的支持,Dubbo能够处理多实例的路由和负载均衡,实现服务的高可用。
集群容错
集群与高可用
原理
问题:请简述Dubbo的架构及各组件的功能。 答案:Dubbo包含服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry,如Zookeeper、Nacos等)、监控中心(Monitor)和容器(Container)。Provider发布服务,Consumer通过Registry订阅并调用服务;Monitor负责收集统计信息,进行运维监控;Container为服务运行提供环境支持。
架构组件
问题:如何实现服务的注册与发现? 答案:服务提供者启动时,向注册中心注册服务信息,接口名、版本、地址等;服务消费者从注册中心订阅所需服务,并根据注册中心返回的信息,找到可用服务提供者。
服务注册与发现
问题:描述一次Dubbo服务调用完整流程。 答案:Consumer通过代理对象,发起远程方法调用;根据负载均衡策略,选择一个Provider节点;用指定协议(如dubbo、rmi、http等)将请求发送给选定Provider;Provider收到请求后,执行业务逻辑并返回结果;Consumer接收到响应,反序列化得到返回值。
服务调用过程
问题:列举几种Dubbo的负载均衡策略并解释其工作原理。 答案:Dubbo支持多种负载均衡策略,例如:轮询(Round Robin):按顺序依次分配请求到提供者。随机(Random):随机选择一个提供者进行调用。最少活跃调用数(Least Active):优先分配给当前活跃调用数最少提供者。一致性Hash(Consistent Hash):基于Key做一致性哈希,相同Key请求分发至同一提供者。
负载均衡策略
问题:简述Dubbo提供的集群容错模式。 答案:Dubbo提供了多种容错模式,包括:快速失败(Failfast):调用失败立即报错,用于非核心,且快速响应服务。失败重试(Retry):出现异常时多次重试,直到成功或达最大重试次数。失败安全(Failsafe):无论调用成功与否不抛出异常,用写入日志等操作。并行调用(Forking):一次发起所有提供者调用,第一个返回成功,返回结果。广播调用(Broadcast):向所有提供者,发起调用,不关心结果。
集群容错机制
问题:Dubbo如何实现自定义扩展点? 答案:Dubbo用SPI(Service Provider Interface)机制,可自定义Filter、Invoker、Protocol等扩展点,只要实现对应接口,在META-INF/services目录下放置配置文件
问题:Dubbo支持哪些配置方式? 答案:Dubbo支持XML和注解,也支持API编程式配置和服务治理规则,动态推送更新。
配置方式
问题:Dubbo如何实现服务的监控? 答案:Dubbo内置监控统计功能,可统计服务调用次数、耗时等信息。可对接第三方监控系统,如Zipkin、Prometheus等,以实现更全面的监控和性能分析。
监控与运维
面试
Dubbo
1启动 Spring Boot 应用
初始化 SpringApplication 类实例,并设置其基础属性如主配置类等。
2创建 SpringApplication 实例
加载系统属性和命令行参数,合并成应用的运行环境。
3初始化环境 (Environment)
从多个来源读取配置,包括:application.properties 或 application.yml 文件、Profile特定的配置文件、命令行参数、系统属性等。
4加载外部配置
注册并初始化 ApplicationContextInitializer 和 ApplicationListener 实现类。
5 监听器与初始化器准备
从META-INF/spring.factories中加载并注册所有的ApplicationContextInitializer实现。执行所有自定义的初始化器来进一步定制上下文。使用ClassPathScanningCandidateComponentProvider进行组件扫描,找出需要管理的bean定义。
6 资源加载与Bean定义
根据当前环境和已有的Bean定义情况,自动检测并配置合适的Spring Beans。检查spring-boot-autoconfigure模块下的META-INF/spring.factories中的EnableAutoConfiguration条目,按需启用相应的自动配置类
7自动配置 (Auto-Configuration)
创建 ApplicationContext 的子类(通常是 AnnotationConfigServletWebServerApplicationContext),然后刷新上下文以完成Bean的创建和初始化过程。在此过程中,会执行@PostConstruct注解的方法,以及初始化所有的单例Bean。
8创建并刷新 ApplicationContext
如果是web应用,Spring Boot将根据配置自动启动嵌入式的Tomcat、Jetty或Undertow服务器。
9启动嵌入式服务器
调用ApplicationRunner和CommandLineRunner接口的实现类,这些类在应用完全启动后可以执行一些初始化逻辑。
10 启动完成后处理
加载流程
Spring Boot的核心特性是其智能的自动配置机制。当Spring Boot启动时,会根据项目中引入的依赖库和类路径中的Bean定义,自动配置相关组件并初始化它们。这意味着无需大量的XML配置或注解,就可以快速搭建出一个具备基本功能的Spring应用。
自动配置
Spring Boot提供了一系列预定义的起步依赖(Starters),这些依赖模块以group ID org.springframework.boot 开头,并且通常命名为 spring-boot-starter-xxx。通过引入合适的starter,可以一次性导入一组兼容且功能相关的依赖包,如数据库连接、Web服务、安全性等。
启动依赖
Spring Boot内嵌了Tomcat、Jetty或Undertow等Servlet容器,使得开发人员不再需要单独部署服务器环境,只需运行Spring Boot应用的主类即可启动一个独立运行的Web应用程序。
嵌入式容器
Spring Boot Command Line Interface (CLI) 提供了一种命令行工具,可以通过脚本编写快速创建和运行Spring Boot应用,特别适合快速原型开发。
springBoot Cli
Spring Boot Actuator包含一系列生产就绪的监控和管理端点,可以用来查看应用信息、健康状况、审计日志、JVM指标等,对运维管理和故障排查极其有用。
Actuator端点
Spring Boot支持通过application.properties或application.yml文件进行集中化的配置管理,同时提供了属性绑定和外部化配置等功能,便于在不同环境中切换配置。
简化配置
Spring Boot提供了标准的父级POM(Maven Project Object Model),简化了Maven项目的构建配置,统一了版本管理和依赖管理。
starter parent POM
通过使用@SpringBootApplication注解标记的Java类作为应用的入口点,该注解包含了@Configuration、@EnableAutoConfiguration和@ComponentScan三个注解的功能,使得整个Spring Boot应用能够被正确地引导和自动配置。
主类(Spring Boot Application Class)
Spring Boot为HTTP客户端请求提供了RestTemplate以及响应式编程模型下的WebClient,方便微服务之间的通信调用。
RestTemplate和WebClient
组件
基础
Spring Boot
服务注册服务提供者(Nacos Client)Rest请求,向Nacos Server注册自己服务
服务心跳Nacos Client维护定时心跳(默认5秒)通知Nacos Server,更新Nacos Server服务实例时间为当前时间临时节点适用,非临时节点不上报
服务健康检查临时节点(默认):Nacos Client每5秒上报健康,Nacos Server开启定时任务(5秒)检查注册服务实例健康情况,超过15秒未更新,就设成不健康状态(消费者看不到该实例),超30秒就剔除服务非临时节点:Nacos Server主动探测(TCP),每20秒探测一次,探测不到,认为服务异常,不会剔除服务。
服务发现服务调用者(Nacos Client)调用服务时,发送REST请求Nacos Server,获取注册服务清单,缓存在Nacos Client本地,在Nacos Client本地开启定时任务(默认30秒)拉取服务端最新注册表信息,更新到本地缓存。Nacos Server发现服务异常,通过UDP方式,推送给服务调用者
服务数据同步(一致性)cp模式:采用raft协议进行同步ap模式:进行异步批量同步,1,实例数达到1000条 2,当前时间跟上次同步大于2秒,进行同步
优点:1、就算其中一台机器宕机了,其他几台机器照样可以接收请求缺点:1、机器正好注册,没同步,宕机,新注册服务,客户端还是调用不了
AP架构(默认)1、集群节点间,数据同步是异步同步2、服务列表信息,不会持久化到本地,只会存在内存中临时节点
优点:1、节点间数据一致,客户端获取数据都是一样缺点:1、一台机器宕机,集群会进行选举,选举过程,服务对外不可用2、宕机半数以上,所有服务都不可用,无法产生master3、机器是偶数台,防止脑裂,导致整个服务不可用
CP架构1、raft协议,保证集群节点间数据一致性,节点之间选举2、数据会持久化到本地,防止数据丢失非临时节点
Nacos的AP和CP,针对注册中心来说,配置中心存储在mysql,不存AP和CP
Nacos是AP架构还是CP架构?
Nacos的服务注册表结构是怎样的?1、Nacosfont color=\"#e74f4c\
Nacos如何支撑阿里内部数十万服务注册压力?Nacos接收到注册请求,不立即写数据,将服务注册任务放入阻塞队列,立即响应给客户端。用线程池读取阻塞队列中任务,异步完成实例更新,提高并发写能力。
Nacos如何避免并发读写冲突问题?Nacos更新实例列表,采用CopyOnWrite技术,将旧实例列表拷贝一份,更新拷贝实例列表,覆盖旧实例列表。1、复制旧注册表List实例数据,形成新注册表List2、新增,修改,删除操作在新List完成3、操作完成后,用新List覆盖旧List读写锁是遵循 写写互斥、读写互斥、读读不互斥 原则,而copyOnWrite 则是写写互斥、读写不互斥、读读不互斥原则。
Nacos中的保护阈值的作用是什么?1、保护阈值:健康实例数/总实例数,0-1间浮点数2、Nacos返回健康实例。健康实例占比低时,请求让剩余健康实例处理,导致压力过大,可能宕机,造成雪崩。保护阈值,为防止出现这种情况,设置的。3、到保护阈值以下,Nacos返回所有实例给消费者(健康和不健康),部分请求会请求到不健康实例上,牺牲部分请求,避免服务不可用
Nacos与Eureka的区别有哪些?接口方式:对外暴露Rest API,实现服务注册、发现等功能实例类型:Nacos的实例有永久和临时实例;Eureka只支持临时实例健康检测:Nacos对临时实例,用5秒检测定时上报心跳模式检测,永久实例,用TCP(20秒)主动请求来检测;Eureka 心跳模式服务发现:Nacos支持 client 定时拉取 和 server UDP 订阅推送两种模式;Eureka定时拉取模式
Nacos 服务注册与发现
Nacos 配置中心
LoadBalancer 客户端负载均衡器
Ribbon 客户端负载均衡
OpenFeign 声明式服务调用
固定窗口算法
滑动窗口算法
漏桶算法
令牌桶算法
限流算法
sentinel基于信号量隔离
Hystrix基于信号量、线程池
隔离策略
基于响应时间、失败比率
基于失败比例
熔断降级策略
滑动窗口
滑动窗口(RxJava)
实现方式
支持多种数据源
配置规则
多个扩展点
插件形式
扩展性
支持
基于注解支持
不支持
限流
支持慢启动,匀速器模式
支持模式
系统负载保护
可配置规则,秒级监控,机器发现
不完善
控制台
框架适配
Sentinel 对比 Hystrix?
Sentinel 限流降级熔断
Seata 微服务分布式事务
Spring Cloud Gateway 是一个基于Spring框架和Project Reactor构建的微服务API网关。它作为Spring Cloud生态体系的一部分,承担着客户端请求路由、过滤以及与后端服务交互的核心作用
背景框架
Spring Cloud Gateway提供了一种声明式的路由配置方式,允许用户根据路径、HTTP方法、Header、Query参数等规则来定义路由规则。每个路由都会关联一个或多个过滤器,这些过滤器会按照一定的顺序对经过该路由的请求进行处理
路由机制
过滤器是Gateway实现功能扩展的关键组件,包括预处理器(Pre-filter)、路由过滤器(Route Filter)、后处理器(Post-filter)等类型。过滤器链是在请求到达网关时执行的一系列操作,如身份验证、限流、熔断、日志记录、请求转换等。内置了多种过滤器,同时也支持自定义过滤器以满足特定业务需求。
过滤链接
Spring Cloud Gateway基于Reactor项目实现了异步非阻塞的网络通信模型,能够有效提高系统在高并发场景下的性能表现。
异步非阻塞IO
Gateway可以通过集成服务注册中心(例如Eureka、Consul),自动获取并更新服务实例列表,从而实现动态路由和负载均衡。当客户端请求到达时,Gateway通过匹配预先定义的路由规则找到目标服务,并转发至可用的服务实例。
动态路由和服务发现
客户端发起请求到Spring Cloud Gateway。Gateway首先根据路由配置找到匹配的路由及其对应的过滤器链。然后依次执行过滤器链中的各个过滤器,每个过滤器都可以决定是否继续传递请求给下一个过滤器或者直接返回响应给客户端。最终,经过所有过滤器处理后的请求被发送到下游服务,待收到响应后再经由过滤器链逆序执行后处理逻辑,并将最终响应返回给客户端。
请求处理流程
Spring Cloud Gateway可以整合OAuth2、JWT等认证机制,在网关层实现安全性控制。同时,它可以与Zipkin、Sleuth等工具集成,实现全链路跟踪和分布式监控,方便问题定位和性能分析。
安全性和监控
基于Spring 5和Project Reactor构建,支持异步非阻塞IO模型,提高了系统的吞吐量和响应速度
性能优化
提供了丰富的过滤器机制,用户可以根据需求自定义各种预处理、路由和后处理逻辑。内置了限流、熔断、重试等高级特性。
功能强大
与Spring Boot、Spring Cloud生态完美融合,配置更加简洁灵活,并且可以无缝对接如Eureka、Consul等服务发现组件。
集成度高
利用Route Predicate Factory和Filter Factory的组合,能够实现更细粒度的路由控制
高性能路由
相对而言,学习曲线稍陡峭,对于不熟悉Reactor编程模型的开发者来说可能需要更多时间去适应
对于较早版本的Spring Cloud环境,Gateway可能是更新的技术,社区支持和相关文档在初期可能不如Zuul成熟。
gateway网关
作为Netflix开源的一部分,Zuul较早应用于生产环境,历经多代迭代,相对成熟稳定。
成熟稳定
基于Servlet API构建,对于大多数Java开发者来说较为亲切,学习成本相对较低。
易于上手
提供了一些预定义的过滤器,比如身份验证、安全、监控等功能。
预先封装的功能
由于Zuul基于传统的同步阻塞I/O模型,因此在高并发场景下可能存在性能瓶颈。
性能瓶颈
虽然也支持自定义过滤器,但在复杂路由策略和高级功能的支持上相比Gateway略显不足。
扩展性限制
随着Netflix停止对Zuul 2.x的开发,社区支持力度逐渐减弱,而Spring Cloud Gateway已成为Spring Cloud官方推荐的API网关解决方案。
未来维护情况
zuul
子对比zuul
Gateway 统一网关
Skywalking 链路追踪组件
Spring Security OAuth2 微服务安全
Spring Cloud Alibaba
服务注册服务提供者(Eureka Client)HTTP请求,向Eureka Server注册自己服务
服务续约与剔除Eureka Client默认每隔30秒心跳请求Eureka Server,Eureka Server90秒还未收到请求,该实例从注册表中剔除
服务自我保护机制Eureka Server (默认15分钟)丢失(font color=\"#0b38d9\
服务发现1、Eureka Client启动时从Eureka Server获取注册表信息,缓存在本地2、Eureka Client每隔30秒去Eureka Server拉取最近注册表变化,保存在本地注册表中
Eureka为什么能够抗住高并发?font color=\"#0b38d9\
Eureka是如何避免并发读写冲突的? -- 多级缓存Eureka Server为了避免读写内存并发冲突,用多级缓存机制来进一步提升服务请求响应速度。拉取注册表:1、从ReadOnlyCacheMap查缓存注册表2、无,从ReadWriteCacheMap里缓存注册表3、无,从内存中获取实际注册表数据注册表变更:1、内存中更新变更注册表数据,过期掉ReadWriteCacheMap2、默认30秒,各服务注册表,读ReadOnlyCacheMap3、30秒后,Eureka Server后台线程发现ReadWriteCacheMap清空,清空ReadOnlyCacheMap缓存4、服务拉取注册表,会从内存中获取最新的数据了,同时填充各个缓存
Eureka服务注册中心不保证数据一致性(保存最终一致性),属于AP,数据同步存在延时,全部对外提供服务,让客户端必须定时间发送心跳到Eureka Server,属集中式心跳机制集群中每Eureka实例是对等。每Eureka实例都包含全部服务注册表
Eureka存在哪些缺点?1、超大规模集群时,存在单机内存不足情况2、超大规模集群时,服务注册中心无法保证数据一致性3、Eureka Client每30秒Eureka Server发送一次心跳请求,Eureka Server压力太大
Eureka和ZooKeeper都可以提供服务注册与发现的功能,请说说两个的区别?1、ZooKeeper CP,Eureka AP2、ZooKeeper选举期间,注册服务瘫痪,服务最终会恢复,选举期间不可用3、Eureka各节点平等关系,只要有一台Eureka可保证服务可用,查询到数据不是最新4、ZooKeeper有Leader和Follower角色,Eureka各节点平等5、ZooKeeper用过半数存活原则,Eureka采用自我保护机制,解决高可用问题
Eureka如何保证高可用与数据一致?1、Eureka通过集群保证高可用,AP保证最终一致性2、Eureka区分正常客户端,服务端发起数据同步请求,对于服务端发起数据同步请求,Eureka不再进行其他同步操作,避免数据同步,出现死循环3、Eureka用版本号(时间戳)来区分新旧数据4、客户端定时拉取数据,会检测数据与本地数据冲突,会重新发起注册请求
Eureka 服务注册与发现
Consul注册中心架构设计1、每个服务的机器部署Consul Agent2、在多台机器上部署Consul Server,核心服务注册中心,要求部署3~5台机器,保证高可用3、Consul Agent收集服务信息,发给Consul Server,还会对服务请求,检查健康4、Consul Agent转发给Consul Server,查询其他服务所在机器,方便直接调用服务5、Consul Server集群,自动选举出一台机器作leader,对外提供服务,其他Consul Server是follower,leader自动同步数据给follower
Consul通过Raft协议实现数据一致性的?1、Leader有最新数据,只有Leader对外提供服务2、Leader收到注册信息后,将数据同步到大部分follower后才算成功3、Leader服务宕机后,其它follower有最新数据,重新选举出Leader,对外提供服务
Consul 是如何进行leader选举的?1、Create Session:参与选举应用,创建Session,Session存活状态,关联到健康检查2、Acquire KV:多应用Session去锁定一个KV,只能有一个锁定住,锁定成功应用,就是leader。
Consul如何通过Agent实现分布式健康检查?每机器上Consul Agent不断发送请求,检查服务健康,服务宕机,通知Consul Server。可大幅度减小Server端压力。
Consul服务注册与发现
Ribbon 客户端负载均衡
Fegin 声明式服务调用
Hystrix 实现服务限流,降级,熔断
Zuul 统一网关详解,服务路由,过滤器使用
Config 分布式配置中心
Sleuth 分布式链路跟踪
Spring Cloud
微服务
架构
命令
Docker run流程图
Docker底层原理
Docker 挂载
Docker 网络
Dockerfile
Docker Compose
Docker
Nginx
k8s
Jenkins
相关工具
二叉数
红黑树
B树
B+树
hash表
数据结构
Explain执行计划字段列
mysql为什么采用B+树而不是B树?1、B+树更少层高(单一节点存储更多元素),减少IO次数2、叶子节点存储数据,非叶子节点不存储数据,使用缓存页,一次可以加载更多数据 3、叶子节点有序链表(mysql对B+树再次优化,双向有序链表),可高效地支持范围查找
mysql为什么不使用哈希索引、平衡二叉树索引、B树索引?1、hash索引支持等值查询,不适用范围查询2、平衡二叉树插入,更新操作,维护平衡代价太大,层级太高,IO次数多3、B+树相对于B树更具有优势
聚集索引表中只有一个,非聚集索引可有多个
聚集索引是物理上连续存在,非聚集索引逻辑上连续,并非物理上
聚集索引是索引组织形式,索引键值逻辑顺序决定表数据行的物理存储顺序,非聚集索引是普通索引,对数据列创建相应索引,不影响整表物理存储顺序
二叉树的数据结构来描述的,聚簇索引叶节点就是数据节点。非聚簇索引的叶节点仍然索引节点,有一个指针指向对应数据块
聚集索引非聚集索引比较
查询速度上来说,聚集优于非聚集
插入数据来说,非聚集要比聚集快
聚集索引非聚集索引优缺点
什么是聚簇索引、非聚簇索引、联合索引、索引覆盖、索引下推?1、索引覆盖:在索引树中拿到结果,不回表查询聚簇索引情况(减少回表次数,减少IO)2、索引下推:在使用联合索引,通过减少回表的次数,来提高数据库的查询效率
索引
表锁(MYISAM与INNODB支持)优点:表锁开销小,加锁快,无死锁,缺点:锁定粒度大,发生锁冲突概率最高,并发低
行锁(InnoDB默认)优点:锁定粒度最小,发生锁冲突的概率最低,并发度也最高,缺点:行锁开销大,加锁慢;会死锁;-- InnoDB是基于索引构建行锁-- 索引未生效,查询条件未建立索引,行锁变表锁
页锁(BDB支持)开销和加锁时间,界于表锁和行锁间:会死锁;锁定粒度界于表锁和行锁间,并发度一般
按操作的粒度分类
Record Lock(记录锁)锁住一条索引记录例:10,20
Gap Lock(间隙锁) -- 解决幻读问题注:生在事务隔离级别为RR(Repeatable Read)的情况下,RC才会有幻读的问题font color=\"#1808f5\
非唯一索引等值查询
非唯一索引范围查询
Next-key Lock(临键锁)-- 解决当前读的幻读问题InnoDB默认,Record Lock + Gap Lock注:只发生在事务隔离级别为RR(Repeatable Read)的情况下,所以RC才会有幻读的问题左开右闭 ,除了最后一个锁区间,是全开区间font color=\"#1808f5\
行锁的算法
读锁(共享锁,Share Lock)若事务T对数据对象A加上读锁,则事务T只能读A;其他事务只能再对A加读锁,不能加写锁,直到事务T释放A上的读锁
写锁(排它锁,Exclusive Lock)若事务T对数据对象A加上写锁,只允许事务T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁
按操作的类型分类
悲观锁-- 一种用来解决 读-写冲突 和 写-写冲突 的加锁并发控制(利用数据库锁机制)-- 可以解决脏读,幻读,不可重复读,更新丢失的问题
乐观锁-- 是一种用来解决 写-写冲突 无锁并发控制-- 方式一:使用数据版本(version)实现-- 方式二:使用时间戳(timestamp)实现-- 无法解决脏读,幻读,不可重复读,可以解决更新丢失问题
MVCC + 悲观锁MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁MVCC解决读写冲突,乐观锁解决写写冲突
MVCC(多版本并发控制)-- 解决 读-写冲突 无锁并发控制-- 可以解决脏读,幻读(RR级别),不可重复读事务问题,不可以解决更新丢失问题
按锁思想分类
3个隐式字段(DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR)
undo日志版本链
Read View(一致性视图)
MVCC
数据库锁
可重复读(REPEATABLE-READ)含义:对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改与锁关系:读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。问题:幻读InnDB默认隔离级别,通过MVCC来实现可重复读,在RR级别下,通过MVCC解决快照读的幻读问题,通过临间锁(next-key-lock)解决当前读的幻读问题
可串⾏化(SERIALIZABLE)含义:所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰与锁关系:该级别锁定整个范围的键,并一直持有锁,直到事务完成问题:性能差
隔离级别
事务对数据进⾏修改未提交,另⼀个事务访问数据,还没有提交,另⼀个事务读到是“脏数据“
脏读(DirtyReads)
事务读取数据时,另⼀个事务也访问了该数据,第⼀个事务修改数据后,第⼆个事务修改数据。第⼀个事务内修改丢失,丢失修改。
更新丢失(LostUpdate)或脏写
不可重复读(Unrepeatableread)
幻读与不可重复读类似。事务(T1)读取⼏⾏数据,事务(T2)插⼊⼀些数据。后查询中,事务(T1)发现不存在的记录,称为幻读。注:不可重复读和幻读区别: 不可重复读重点font color=\"#e74f4c\
幻读(Phantom read)
并发事务处理带来的问题
数据库事务
mysql如何实现读写分离?基于主从复制架构,主库,多个从库,只是写主库,然后主库自动把数据给同步到从库上去。
mysql 主从复制原理的是啥?1、主库写表操作后,写入binlog日志文件(需要打开bin-log功能)2、从库(IO线程)从主库拉取binlog日志(主库操作是IO线程-binlog dump线程完成的),将内容写relay log的末端,读到binlog文件名和位置记录master-info文件中,下一次读取时候,知道从哪里开始读取。主库返回信息,除日志内容外,有binlog文件名称和binlog位置。3、从库SQL线程,检测到relay日志有新增后,将新增内容执行,写入从库中
如何解决主从库同步延时问题?1、半同步复制:主库必须要等到数据同步到从库(至少一个)relay log才算成功2、并行复制:从库多SQL线程同时读取relay,写入从库中3、分库:将主库分成多个库,减少单个库并发量
mysql读写分离
为什么要分库分表?分库:减少数据库的并发压力和减少磁盘使用率分表:是为了提高SQL执行效率
sharding-jdbc优点:不用部署,运维成本低,不需要代理层二次转发,性能很高缺点:需要各个系统都重新升级版本再发布,各系统需要耦合sharding-jdbc 依赖中小公司使用
mycat优点:对于各个项目是透明的,升级之类,自己中间件那里搞就行了缺点:需要部署,自己运维一套中间件,运维成本高大公司使用
有哪些分库分表中间件?各自优缺点?
垂直拆分把一个有很多字段的表给拆分成多个表,或者是多个库上去。一般来说,访问频率高的字段放一张表,访问频率低的字段放一张表
水平拆分把表数据多个库多个表里去,每库的表结构都一样,每个库表放的数据是不同,所有库表数据加起来,就是全部数据
水平拆分 -- range按照range 来分,每个库一段连续的数据,按时间范围来。优点:扩容简单,只要预备好,给每个月都准备个库就可以,到一个新的月份,写新库了。缺点:容易产生热点问题,大部分请求,访问最新的数据
水平拆分 -- hash按字段hash 散列,较为常用。优点:分摊每个库的数据量和请求压力缺点:扩容起来比较麻烦,会有一个数据迁移过程,之前数据重新计算hash 值,再分配不同的库或表
如何对数据库进行分库分表?
不建议使用在线上系统
停机迁移方案
双写迁移方案1、写表地方,老库和新库(通过中间件)都写2、同步历史数据,不允许覆盖新库的数据,除非旧库的数据比新库更新3、程序切换成,分库分表最新代码
如何将未分库分表的系统切换成分库分表的系统?
分库分表如何动态扩容
利用单台数据库自增主键可适用:并发不高,数据量大不适用:并发高,数据量大
利用Redis生成ID-- 使用Redis集群(如5台),每台步长一样,使用原子操作INCR来增加优点:性能高,单机顺序递增缺点:不能再扩展,5台已经够使用了
Twitter的snowflake算法生成一个64位long型ID-- 1 bit 恒为0,保证生成ID为正整数-- 41 bit 作为日期毫秒数 - 41位的长度可用69年-- 10 bit 作为机器编号 (5个bit是数据中心,5个bit的机器ID) - 10位的长度支持部署1024个节点-- 12 bit 作为毫秒内序列号。12位的计数顺序号支持每个节点每毫秒4096ID序号优点:分布式,性能高,单机递增缺点:分布式环境,可能由于服务器时间不一致,导致生成ID不是全局递增;时钟回拨,服务器上时间突然倒退回时间注:时钟回拨解决方案1、回拨时间间隔小时,不生成 ID,循环等待到时间点到达2、提前机器ID,序列号留出拓展位置0,出现时钟回拨时,拓展位置1,可保证生成ID唯一性。
分库分表的主键ID如何生成?
1 InnoDB支持事务,MyISAM不支持,InnoDB每条SQL都默认封装成事务,自动提交,会影响速度,多条SQL语言begin和commit间,组成个事务;2 InnoDB支持外键,MyISAM不支持。对包含外键InnoDB表,转MYISAM会失败;3 聚集索引 VS 非聚集索引InnoDB是聚集索引,用B+作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),有主键,主键索引效率高。辅助索引需要两次查询,先查询到主键,再通过主键查询到数据。主键不应过大,主键大,其他索引都很大4 InnoDB支持表、行(默认)级锁,MyISAM支持表级锁5 InnoDB表必须有唯一索引(如主键)(自己找/生产一个隐藏列Row_id充当主键),Myisam可没有
MyISAM与InnoDB 的区别?
mysql的查询SQL执行过程?1、发查询语句给服务器。2、检查缓存存在该查询,存在,返回缓存中存在结果。不存在就进行下一步。3、服务器进行SQL解析、语法检测和预处理,由优化器生成执行计划。4、Mysql执行器执行计划,调用存储引擎接口进行查询。5、服务器将查询结果返回客户端。
InnoDB更新语句执行流程?-- undo log:保证事务的一致性(事务回滚,MVCC),随机读写(说不定要回滚)-- redo log:重做日志,保证事务持久性,顺序写-- bin log:归档日志,顺序写注:执行顺序 undoLog -> redoLog -> binLog -> redoLog
mysql
数据库
设计要点:1 按钮及时设为不可用,禁止用户重复提交2 用本地缓存,缓存一些商品信息,等变动不大数据3 将库存信息,提前刷到Redis缓存,在Redis缓存,进行库存锁定,扣减操作4 允许场景下,使用异步模式,等缓存都落库后,再回调返回结果5 如果允许,增加类似答题校验,等验证措施,来减少并发6 注意缓存雪崩,缓存击穿,缓存穿透等问题7 根据库存数量,设置允许通过请求数量,比如扩大几倍。其它手段:1 业务隔离:把秒杀做成一种促销,需要报名报名,归纳,整合用户数据2 系统隔离:秒杀模块,单独部署3 数据隔离:启动单独Mysql支持
秒杀系统设计
秒杀超卖,少卖,及解决方案Lua + Redis + MQ1、先将库存数据,预热到Redis2、用Lua脚本,依靠redis批量操作命令行原子性的特性,做库存扣减(判断库存充足,扣减操作,属于原子操作),成功返回订单号3、Lua脚本将信息发MQ(失败,重试,重试不成功,得持久化,再做补偿处理,保证订单系统,可以拿到相关信息)4、订单系统MQ信息,创建订单5、客户端,通过轮询订单,来获取订单数据注:用户未支付,取消订单,要对Redis库存进行添加
1 接入层,垂直切分,根据红包ID,发红包、抢红包、拆红包、查详情等都部署在同台机器上处理。2 请求进行排队,到数据库时是串行,不涉及抢锁问题3 为防止队列太长,过载导致队列被降级,直接打到数据库,数据库前面加上个缓存,用CAS自增,控制并发,太高并发,直接返回失败。4 红包冷热数据分离,按时间分表。
高并发的红包系统
1 预先分片,如十台机器,每台先分一千个ID,一号机从0开始,二号从1000开始等等。2 类似雪花算法,每个机器有个id,然后基于时间算一个id,再加上一个递增id。如美团的方案。缺点时间戳不能回拨,回拨可能出现问题。
分布式ID
1 固定窗口计数器:按照时间段划分窗口,有请求就+1,最为简单的算法,这个算法有时会让通过请求量,允许为限制的两倍。2 滑动窗口计数器:通过将窗口再细分,并且按照时间“滑动”解决突破限制问题,时间区间的精度越高,算法所需的空间容量就越大。3 漏桶:请求类似水滴,先放桶里,服务提供方,按照固定速率,从桶里面取出请求执行。缺陷,短时间内有大量的突发请求,服务器没有任何负载,每个请求,队列中等待一段时间,才能被响应。4 令牌桶:往桶里面发放令牌,每个请求拿走一个令牌,有令牌的请求。多余的令牌会直接丢弃。令牌桶算法,将请求平均分布到时间区间,能够承受范围内的突发请求,较为广泛的一种限流算法。
分布式限流
架构设计问题
JVM 同步规范:监视器m解锁,对后续加锁同步 volatile变量v写入,后续线程,对v读同步启动线程与线程操作同步。属性写,默认值(0, false,null)对其线程同步。(创建对象前,对象属性默认值,程序启动时,默认值,初始化来创建) T1 操作,T2 发现T1 结束( T2 通过 T1.isAlive() 或 T1.join() 来判断 T1 是否已终结) T1 中断 T2,T1 中断,其他线程发现 T2 被中断,同步(抛出 InterruptedException 异常,调用 Thread.interrupted 或 Thread.isInterrupted )
Synchronization Order
Happens-before Order
线程 a 进 synchronized 前, synchronized 对共享变量操作,后续持同监视器锁(monitor)(读锁,共享锁)线程 b 可见。synchronized 保障变量可见性:线程获取监视器锁,才 synchronized 代码块,进入代码块,从主存中重新读取共享变量值,退出代码块时,线程写缓冲区中数据,刷到主内存。Java对象可作为锁, synchronized 实现同步基础:普通方法,锁实例对象(synchronized method)font color=\"#e74f4c\
synchronized
对象在堆中内存布局分: 对象头,实例数据,对齐填充Hotspot虚拟机对象头:Mark Word、Klass Pointer。Klass Point对象指向其类元数据指针,Mark Word存储对象运行时标志数据,是实现轻量级锁和偏向锁的关键。JVM 用2个字(jvm 字等于位数)存储对象头(对象是数组则会分配3个字,多1个字记录数组长度)Mark Word在默认存储着对象HashCode、分代年龄、font color=\"#e74f4c\
对象头
monitor
同步代码块public void syncTask(){ //同步代码库 synchronized (this){ doSomething.... } }对同步代码块,实现是monitorenter和monitorexit指令,其中monitorenter指向同步代码块开始位置,monitorexit指明同步代码块结束位置。执行到moniterenter指令,获取monitor。monitorenter // 进入同步方法........ // 执行逻辑monitorexit // 退出同步方法块同步方法public synchronized void syncTask(){ doSomething...... }方法同步是隐式,通过字节码指令来控制,在方法调用和返回操作中。JVM从方法常量池的方法表结构(method_info Structure) 的 ACC_SYNCHRONIZED 访问标志,是否同步方法。当方法调用时,指令检查方法 ACC_SYNCHRONIZED 访问标志,设置了,去获取monitor
底层
没有对资源锁定,所有的线程能访问,修改同个资源,只有一个线程修改成功。无锁特点修改操作会在循环内进行,线程不断尝试修改共享资源。没有冲突修改成功退出,否则循环尝试。多线程修改同个值,必定有个线程修改成功,而其修改失败线程不断重试,直到修改成功。CAS原理,应用是无锁的实现
无锁
大多数情况,锁总是同线程多次获得,不存在多线程竞争,出现偏向锁。在一个线程执行同步代码块,提高性能 。线程访问同步代码块,获取锁时,Mark Word存储锁偏向线程ID。线程进入和退出同步块,不通过CAS加锁和解锁,检测Mark Word存储指向当前线程的偏向锁。偏向锁在无多线程竞争,减少不必要轻量级锁执行,轻量级锁获取,释放依赖多次CAS原子指令,偏向锁只要在置换ThreadID时,依赖一次CAS原子指令遇到其他线程尝试竞争偏向锁时,持有偏向锁线程才会释放锁,偏向锁撤销,需等待全局安全点(时间点上没有字节码正在执行),先暂停拥有偏向锁线程,判断锁对象,处于被锁定状态。撤销偏向锁,恢复到无锁(标志位为“01”)轻量级锁(标志位为“00”)的状态。
偏向锁
偏向锁,被另外线程所访问,偏向锁升级为轻量级锁,其他线程自旋的形式尝试获取锁,不会阻塞,提高性能。进同步块时,同步对象锁状态无锁状态(锁标志位“01”状态,是否为偏向锁“0”),虚拟机将线程栈帧建立一个锁记录(Lock Record)空间,存储锁对象Mark Word拷贝,拷贝对象头Mark Word到锁记录。拷贝后,虚拟机用CAS操作,尝试将对象Mark Word更新指向Lock Record指针,将Lock Record里的owner指针,指向对象Mark Word。更新动作成功,线程就拥有对象的锁,对象Mark Word的锁标志设“00”,对象处于轻量级锁定状态。更新操作失败,虚拟机先检查对象Mark Word指向当前线程栈帧,是,线程已有对象的锁,可进同步块继续执行,否则,多个线程竞争锁。当只有一个等待线程,自旋进行等待。自旋超过一定的次数,一个线程持有锁,一个自旋,第三个来时,轻量级锁升级重量级锁。
轻量级锁
升级为重量级锁,锁标志的状态值“10”,Mark Word存储指向重量级锁指针,等待锁的线程,进入阻塞状态。
重量级锁
偏向锁: 只有一个线程进入临界区
轻量级锁: 多个线程交替进入临界区
重量级锁: 多个线程同时进入临界区
理解思路情况一:只有Thread1会进入临界区;情况二:Thread#1和Thread#2交替进入临界区;情况三:Thread#1和Thread#2同时进入临界区。Thread#1进临界区,JVM将lockObject对象头Mark Word锁标志设为“01”,用CAS把Thread#1线程ID记录Mark Word中,偏向模式。所谓“偏向”,锁会偏向Thread#1,没有线程进入临界区,Thread#1出入临界区,无需同步操作Thread#2尝试进入临界区。Thread#2尝试进入,Thread#1已退出临界区,lockObject处未锁定状态,偏向锁上发生竞争(对应情况二),会撤销偏向,Mark Word中不再存放偏向线程ID,而是存放hashCode和GC分代年龄,锁标识位变为“01”(表示未锁定),Thread#2获取lockObject轻量级锁。Thread#1和Thread#2交替进临界区,偏向锁无法满足需求,需要膨胀到轻量级锁。一直是Thread#1和Thread#2交替进临界区,没有问题,轻量锁hold住。轻量级锁上发生竞争,出现“Thread#1和Thread#2同时进入临界区”,轻量级锁hold不住。 (根本原因轻量级锁没有足够空间存储,若不膨胀为重量级锁,所有等待轻量锁的线程,只能自旋,可能会损失很多CPU时间)
自旋失败:当多个线程尝试获取同一个已被轻量级锁定的对象时,除了持有锁的线程外,其他线程会首先通过自旋CAS操作尝试获取锁。如果自旋多次后(即自旋循环达到一定次数),依然无法获得锁,那么表明当前存在较高的锁竞争。在这种情况下,为了防止过多CPU资源被无效地消耗在自旋上,JVM会选择将轻量级锁升级为重量级锁,这样等待锁的线程就会进入阻塞状态,并由操作系统进行调度
锁竞争加剧:当系统检测到有多个线程频繁争夺同一把轻量级锁时,意味着出现了较为激烈的锁竞争状况,继续维持轻量级锁可能导致性能恶化。因此,为了避免这种过度的线程上下文切换和CPU利用率过低的问题,JVM会将轻量级锁升级为重量级锁,让后续线程排队等候,从而保证更公平的锁分配机制。
锁膨胀条件满足:在特定条件下,例如锁从栈上逸出(即将锁对象作为方法返回值或者保存到其他不受控制的地方),使得轻量级锁的安全性模型失效,这时也会触发锁升级过程。
轻量级锁升级为重量级锁原因
锁升级
锁消除
锁粗化
适用锁
锁优化
锁的分类
以单例模式的 double check 检查问题, 看一下如下单例的写法:public class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { // 1. 第一次检查 synchronized (Singleton.class) { // 2 if (instance == null) { // 3. 第二次检查 instance = new Singleton(); // 4 } } } return instance; }}复制这种写法是有问题的, 假设有两个线程 a 和 b 调用 getInstance() 方法假设 a 先运行, 首先到步骤 4 处。instance = new Singleton() 这句代码首先会申请一段空间,然后将各个属性初始化为零值(0/null),执行构造方法中的属性赋值[1],将这个对象的引用赋值给 instance[2]。在这个过程中,[1] 和 [2] 可能会发生重排序。此时,线程 b 刚刚进来执行到步骤 1,就有可能会看到 instance 不为 null,然后线程 b 也就不会等待监视器锁,而是直接返回 instance。问题是这个 instance 可能还没执行完构造方法(线程 a 此时还在 4 这一步),所以线程 b 拿到的 instance 是不完整的,它里面的属性值可能是初始化的零值(0/false/null),而不是线程 a 在构造方法中指定的值。想解决这个问题, 使用 volatile 关键字修饰 instance 即可另外: 如果对象所有的属性都被 final 关键字修饰, 那么不需要加 volatile 也可以保证不发生重排序。
不能保证重排序
synchronize
javaLock 方法
ReentrantLock 基本用法可重入性原理公平锁与非公平锁的区别和设置Condition 对象的使用await() 方法signal() 和 signalAll() 方法
ReentrantLock
读写锁的基本原理ReadLock(读锁)和 WriteLock(写锁)提高并发性能的场景分析
读写锁 - ReentrantReadWriteLock
正确锁定和解锁的顺序避免死锁策略锁定模式的选择与应用使用 try-with-resources 结构自动释放锁资源
Lock 实例与最佳实践
用 volatile 关键字整型state,表示同步状态。独占模式,值代表当前持有锁线程数( ReentrantLock 重入次数),共享模式下,可同时获取到锁线程数。线程尝试获取或释放锁,通过CAS修改状态变量。
状态管理
CLH 队列
独占模式:ReentrantLock,线程tryAcquire()尝试获取锁时,成功state+1返回true,否则,线程构造成Node,加入队列,进入park()阻塞状态。锁线程释放锁(tryRelease()state-1),后续节点可能成头节点,会唤醒后继节点再次尝试。
共享模式:Semaphore或CountDownLatch,多线程可同时获到“锁”,state值可被多线程累加,递减。
同步过程
用Unsafe类的park()和unpark()实现线程阻塞和唤醒,提供底层操作系统级别线程挂起和恢复执行能力。
阻塞与唤醒机制
AQS上构建工具类,如ReentrantLock还支持锁降级,Condition对象,满足特定条件时,才继续执行,有各自等待队列,依赖AQS核心逻辑
锁降级与条件队列
Node 类:线程节点结构详解双向链表(CLH 队列)模型内部状态管理,原子操作类(如 getState()、compareAndSetState())
AQS 核心数据结构
独占模式(Exclusive Mode)acquire(int) 方法实现tryAcquire(int) 抽象方法定义线程阻塞与唤醒逻辑共享模式(Shared Mode)acquireShared(int) 与 releaseShared(int)tryAcquireShared(int) 和 tryReleaseShared(int)
基础方法与机制
公平锁的获取与释放流程非公平锁的特点与性能比较
AQS 的公平与非公平策略
ReentrantLock 实现分析ReentrantReadWriteLock 读写锁原理Semaphore 信号量机制CountDownLatch 倒计数器CyclicBarrier 循环栅栏
基于 AQS 的并发工具类
继承 AQS 创建自定义同步器定义和重写关键方法,实现不同同步逻辑
自定义同步组件
自旋锁与适应性自旋锁的升级与降级(例如 ReentrantLock)超时与中断支持
AQS 的高级特性
比 synchronized 关键字优化同步策略,以少上下文切换和锁竞争
AQS 性能调优
使用 AQS 解决实际并发问题示例分布式系统中 AQS 的应用扩展
实战应用与案例分析
死锁预防与处理线程饥饿场景,解决方案锁定顺序,对并发控制的影响
疑难解答与最佳实践
AQS (AbstractQueuedSynchronizer) 框架
自旋锁与自适应自旋锁粗化与锁消除轻量级锁与偏向锁的工作机制
锁优化技术
处理 InterruptedException锁竞争激烈时的监控与诊断
Lock 错误处理与异常控制
使用 Lock 解决生产者-消费者问题多线程环境中确保数据一致性分布式系统中的 Lock 实现及其挑战
实战案例分析
Lock 与 synchronized 关键字的比较根据应用场景选择合适的锁实现进一步学习和探索的方向
总结与对比
lock
内存可见性上文提到过进入 synchronized 时,使得本地缓存失效,synchronized 块中对共享变量的读取必须从主内存读取;退出 synchronized 时,会将进入 synchronized 块之前和 synchronized块中的写操作刷入到主存中。volatile 有类似的语义,读一个 volatile 变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读取最新值,写一个 volatile 属性会立即刷入到主内存。所以,volatile 读和 monitorenter 有相同的语义,volatile 写和 monitorexit 有相同的语义。
禁止重排序volatile 的禁止重排序并不局限于两个 volatile 的属性操作不能重排序,而且是 volatile 属性操作和它周围的普通属性的操作也不能重排序。根据 volatile 的内存可见性和禁止重排序,那么我们不难得出一个推论:线程 a 如果写入一个 volatile 变量,此时线程 b 再读取这个变量,那么此时对于线程 a 可见的所有属性对于线程 b都是可见的(这里想想为何ReentrantLock只用了一个volatile的state属性)
内存屏障四大分类:(Load 代表读取指令,Store代表写入指令)内存屏障类型 使用场景 描述LoadLoad 屏障 Load1; LoadLoad; Load2 在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore 屏障 Store1; StoreStore; Store2 在Store2写入执行前,保证Store1的写入操作对其它处理器可见LoadStore 屏障 Load1; LoadStore; Store2 在Store2被写入前,保证Load1要读取的数据被读取完毕。StoreLoad 屏障 Store1; StoreLoad; Load2 在Load2读取操作执行前,保证Store1的写入对所有处理器可见。为了实现volatile的内存语义,Java内存模型采取以下的保守策略:在每个 volatile 写操作的前面插入一个 StoreStore 屏障在每个 volatile 写操作的后面插入一个 StoreLoad 屏障在每个 volatile 读操作的后面插入一个 LoadLoad 屏障在每个 volatile 读操作的后面插入一个 LoadStore 屏障
语义实现
volatile 修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值。volatile 属性的读写操作都是无锁的,它不能替代 synchronized,因为它没有提供原子性和互斥性。因为无锁,不需要花费时间在获取锁和释放锁上,所以说它是低成本的。volatile 只能作用于属性,我们用 volatile 修饰属性,这样 compilers 就不会对这个属性做指令重排序。volatile 提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile 属性不会被线程缓存,始终从主存中读取。volatile 提供了 happens-before 保证,对 volatile 变量 v 的写入 happens-before 所有其他线程后续对 v 的读操作。另 volatile 可以使得 long 和 double 的赋值是原子的
使用总结
valatile(保存内存可见性,指令重排)
指令重排序(编译器优化指令)
线程栈,内存可见性
原子性
背景
并发使代码可能会产生各种各样结果, Java 编程语言规范一些基本规则,约束下来实现 JVM,开发者要按照规则写代码,并发代码才能准确预测执行结果
final
锁
corePoolSize(核心线程数)maximumPoolSize(最大线程数)
核心参数
workQueue(工作队列类型,如LinkedBlockingQueue、SynchronousQueue等)
队列和任务处理
keepAliveTime(非核心线程空闲存活时间)TimeUnit(keepAliveTime的时间单位)
线程生命周期管理
ThreadFactory(用于自定义线程创建逻辑的工厂接口)
线程工厂
RejectedExecutionHandler(当队列满且无法添加新线程时的拒绝策略)
拒绝策略
ThreadPoolExecutor构造方法参数
newFixedThreadPool 创建固定大小的线程池
newCachedThreadPool 动态调整线程数量的线程池
newSingleThreadExecutor 只有一个工作线程的线程池
newScheduledThreadPool 支持定时及周期性任务执行的线程池
线程池创建工具类 Executors
线程池常用方法
默认行为:异常会被UncaughtExceptionHandler捕获,若没有设置则默认打印堆栈跟踪信息可通过自定义RejectedExecutionHandler处理因线程池饱和导致的任务拒绝异常
任务抛出异常的处理
异常传播至Future.get()
线程池异常处理
Future接口及其实现类get()方法获取任务结果与捕获可能的异常使用Future实现异步回调Future.get配合CountDownLatch、CompletableFuture等进行结果合并或异步编程
异步回调与Future
线程池配置优化异常处理策略设计结合Future实现优雅的任务完成通知机制
最佳实践
ThreadPoolCreationException:线程池初始化失败,检查参数是否合理ThreadPoolExecutor相关运行时异常处理回调函数中处理并发异常的技巧
常见问题与解决方案
线程池
线程的基本概念 线程生命周期 线程状态转换
创建和启动线程 2.2.1 继承Thread类创建线程 2.2.2 实现Runnable接口创建线程 2.2.3 使用Executor框架创建线程(Callable与Future)
线程中断机制 2.5.1 interrupt()方法与isInterrupted()方法 2.5.2 InterruptedException处理
线程基础
synchronized关键字 3.1.1 对象锁 3.1.2 类锁 3.1.3 synchronized代码块与方法
锁优化机制 3.2.1 轻量级锁 3.2.2 偏向锁 3.2.3 重量级锁
内存可见性与volatile关键字 3.3.1 volatile变量的内存语义 3.3.2 happens-before原则
同步机制
Lock接口与实现 4.1.1 ReentrantLock 4.1.2 Condition对象 4.1.3 公平锁与非公平锁
ReadWriteLock接口与ReentrantReadWriteLock
StampedLock
高级同步机制
wait()、notify()与notifyAll()方法
BlockingQueue及其实现 5.2.1 ArrayBlockingQueue 5.2.2 LinkedBlockingQueue 5.2.3 SynchronousQueue
同步辅助类 5.3.1 CountDownLatch 5.3.2 CyclicBarrier 5.3.3 Semaphore 5.3.4 Phaser
线程间通信
内存屏障与指令重排序JMM的主要特性volatile与有序性保证final字段与构造函数初始化安全性
内存模型
8.1 ConcurrentHashMap 8.2 CopyOnWriteArrayList与CopyOnWriteArraySet 8.3 ConcurrentLinkedQueue与其他并发队列 8.4 并发集合的安全迭代器
并发容器与数据结构
生产者-消费者模式 9.2 读写锁的应用场景 9.3 双重检查锁定(Double Checked Locking) 9.4 安全发布与不可变对象 9.5 线程局部变量(ThreadLocal)的应用
并发设计模式与最佳实践
性能调优
多线程环境下资源竞争问题解决 11.2 利用线程池提高系统吞吐量 11.3 并发容器的实际运用
实战案例
并发
JDBC 驱动程序加载
抽象工场模式
例如:java.lang.StringBuilder 或 java.nio.ByteBuffer
建造者模式
java.util.Calendar.getInstance() 或 JDBC 中的 DriverManager.getConnection()
工厂方法模式
例如:clone() 方法
原型模式
java.lang.Runtime.getRuntime()
单例模式
创建型(5)
例如:Java I/O 包中的 InputStreamReader 和 OutputStreamWriter
适配器模式
例如:Java AWT/Swing 中的颜色和颜色UI delegate
桥接模式
例如:文件系统目录与文件的关系
组合模式
例如:Java IO 流的装饰链
装饰模式
例如:Java Swing 中的 JFrame 对 GUI 元素的封装
外观模式
例如:数据库连接池中的复用对象
享元模式
例如:Java RMI、动态代理机制 java.lang.reflect.Proxy
代理模式
结构型(7)
例如:Servlet 过滤器链
责任链模式
例如:java.awt.event.ActionListener 在 GUI 编程中的应用
命令模式
正则表达式API是JDK中与解释器模式理念相契合的一个部分:Pattern和Matcher类:java.util.regex.Pattern 类可以视为解释器模式中的“语法分析器”,它根据给定的正则表达式字符串构建一个内部数据结构(类似于抽象语法树),这个数据结构代表了正则表达式的文法规则。java.util.regex.Matcher 类扮演了“解释器”的角色,它可以接受输入字符序列,并依据Pattern对象定义的规则进行匹配解析,找到符合规则的子串。
解释器模式
例如:java.util.Iterator 接口及其实现类
迭代器模式
比如Swing组件之间的事件调度
中介者模式
某些API的设计中可以看到类似备忘录模式的思想。例如:java.util.Date 类虽然不是严格意义上的备忘录模式实现,但在早些版本的Java中(如Java 7及更早),clone() 方法可以用于创建当前日期对象的一个副本,这个副本可以看作是当前时刻的“备忘录”,可以在需要时恢复或比较不同时间点的状态。在Java Swing或JavaFX等图形用户界面库中,当处理复杂的可编辑组件时,可能会用到备忘录模式的概念,用来撤销/重做操作序列(通过维护一系列状态的快照)。
备忘录模式
java.util.Observable 类和 java.util.Observer 接口
观察者模式
根据对象状态改变其行为,如JDBC驱动管理的状态转换
状态模式
java.util.Comparator
策略模式
javax.servlet.http.HttpServlet
模板方法模式
在Java Compiler API中,javax.lang.model.element.Element 和 javax.lang.model.element.ElementVisitor 接口之间存在一定的访问者模式特点。Element 是一个接口,代表了编译器处理过程中遇到的各种程序元素,如类、接口、方法等,形成了一个对象结构。ElementVisitor 是一个访客接口,定义了一系列可以作用于不同类型的 Element 的方法,允许用户根据不同的元素类型执行相应的操作。
访问者模式
行为型(11)
设计模式
类加载子系统根据给定全限定名类名(如:java.lang.Object)装载class文件到运行时数据区的方法区
运行时数据区(JMM内存模型)
执行引擎执行classes指令
本地库接口本地方法库(native libraries)交互,其它编程语言交互接口。
JVM构成图
类的生命周期?加载->验证->准备->解析->初始化->使用->卸载
核心类加载器(BootstrapClassLoader)加载支撑JVM运行于JRE的lib下核心类库
扩展类加载器(ExtensionClassLoader)加载支撑JVM运行于JRE下ext的JAR类包
应用加载器(AppClassLoader)加载ClassPath路径下类包,加载自己的类
自定义加载器(继承ClassLoader)加载自定义路径类包
类加载器有哪些?
如何创建一个自定义加载器?1、继承java.lang.ClassLoader2、重写findClass方法(打破双亲委派机制,重写loadClass();不想打破双亲委派机制findClass())
什么是双亲委派机制?类加载时,向上委派,向下加载。
为什么要设计双亲委派机制?1,沙箱安全:自己写java.lang.String.class类不会被加载,可防止核心API被篡改2,避免类重复加载:父已加载,子无需加载,加载唯一
什么是全盘负责委托机制?ClassLoder装载时,类所依赖引用类由这个ClassLoder载入,除非显示使用另外一个ClassLoder
类加载机制
Eden(默认8/10)
Survivor0(默认1/10)
Survivor1(默认1/10)
年轻代(默认1/3)
老年代(默认2/3)
堆(共享)
局部变量表
操作数栈
动态链接
方法出口
虚拟机栈(私有)
本地方法栈(私有)
程序计数器(私有)
常量
类信息
方法区(元空间)(共享)
Java 堆和栈区别?1、内存区域不同2、可见性不同(堆共享、栈私有)3、存放数据不同(堆存放对象,静态变量,字符串常量池。栈存放常量、变量、类信息)
JVM内存模型
-Xmx3072m:JVM最大堆内存3G
-Xms3072m:JVM初始堆3G。与-Xmx相同,避免垃圾回收完成后,JVM重新分配内存
-Xmn2048:设年轻代大小2G
-Xss256K:每个线程栈大小,默认1m,值越小,JVM开启线程越多
-XX:SurvivorRatio=4:设置Eden区与Survivor区比值,默认是8,1个Survivor区占年轻代1/10
-XX:MetaspaceSize: 指定元空间触发Fullgc初始阈值(元空间无固定初始大小), 字节为单位,默认是21M
建议JVM MetaspaceSize和MaxMetaspaceSize同值,比初始值大,8G物理内存机器,两值设256M。
让对象在新生代里分配和回收,别让太多对象频繁进老年代,避免频繁老年代垃圾回收,给系统充足内存,避免新生代频繁进行垃圾回收。
-XX:MaxMetaspaceSize: 设元空间最大值, 默认是-1,设计上不限制, 限于本地内存
JVM参数设置
Minor GC 新生代收集堆中新生代垃圾收集动作,Minor GC频繁,速度比较快年轻代Eden区分配满触发MinorGC 标记-复制算法。
Major GC 老年代收集堆中老年代垃圾收集动作老年代对象较稳定,MajorGC不会频繁只有CMS收集器才有单独MajorGC
Mixed GC 混合收集整个新生代,部分老年代垃圾收集动作。只有G1收集器会有这种行为
Full GC 整堆收集整个Java堆和方法区垃圾收集Full GC 前先Minor GC
GC类型
大对象直接进入老年代避免Eden和两个Survivor间大量内存拷贝(新生代用标记-复制算法收集内存)。大对象是指需要大量连续内存空间的对象,如:大数组和长字符串
长期存活的对象将进入老年代虚拟机为对象年龄设计数器,对象经过1次Minor GC,进入Survivor区,每次GC对象年龄加1,到阀值(默认15)进老年区。可以通过参数 -XX:MaxTenuringThreshold 来设置
对象动态年龄判断Survivor相同年龄对象总和大于Survivor一半,大于等于的对象可进老年代。font color=\"#0b38d9\
对象晋升老年代
程序代码普遍存在引用赋值,强引用关系还存在,不会回收掉被引用的对象
强引用不可达时回收
有用,非必须对象。被软引用关联的对象,内存溢出前,会把这些对象列进回收范围,进行二次回收,这次回收仍无足够的内存, 才会抛出内存溢出异常。
软引用内存不足时回收
非必须对象,比软引用弱,弱引用关联的对象,只能生存到下次垃圾收集。下次垃圾收集器开始,都会回收掉被弱引用关联的对象
弱引用垃圾收集器工作时回收
“幽灵引用”或者“幻影引用”,对象是否有虚引用,不会生存时间构成影响,无法通过虚引用取得对象实例。对象被收集器回收时收到一个系统通知
虚引用垃圾收集器工作时回收
对象引用类型
哈希码
GC分代年龄
锁状态标志
线程持有锁
偏向线程Id
偏向时间戳
对象自身运行时数据(mark word)
类型指针
时数组,还有记录数组长度数据
实例数据
对象填充
对象内存布局
句柄访问好处,reference存储稳定句柄地址,对象移动(垃圾收集标记,复制,移动对象的行为)只会改变句柄实例数据指针,reference本身不被修改
句柄访问
直接指针访问速度更快,节省一次指针定位时间开销,对象访问Java中非常频繁,这类开销积少成多,是可观的执行成本。HotSpot虚拟机用直接指针来进行对象访问
直接指针HotSpot虚拟机主要使用直接指针来进行对象访问
对象访问
对象不会逃逸线程外,可将对象在栈上分配,对象占用的内存随栈帧出栈而销毁,这样一来,垃圾收集压力降低很多
栈上分配
线程同步是耗时过程,逃逸分析能够确定变量不会逃逸出线程,无法被其他线程访问,变量读写不会有竞争, 同步措施可安全地消除掉。
同步消除
一个数据是基本数据类型,不可拆分,称之为标量。把Java对象拆散,将其成员变量恢复为原始类型来访问,过程称为标量替换。假如逃逸分析能够证明对象不会被方法外部访问,且可以被拆散,可不创建对象,直接创建若干成员变量代替,让对象成员变量在栈上分配和读写
标量替换
逃逸分析
对象
引用计数器法为对象创建引用计数,新增引用+1,释放 -1,计数为 0 就可回收。缺点不能解决循环引用问题
可达性分析算法GC Roots 向下搜索,搜索过路径称为引用链。对象到 GC Roots 无引用链,对象可回收
可作为GC Root根的对象虚拟机栈(本地变量)引用对象本地方法栈引用对象静态变量引用对象
对象内存回收算法
分代收集理论对象存活时间不同,将内存分几块,Java堆分新生代(1/3)和老年代(2/3),由业务场景,选择分代收集器,各年代内存分配,回收触发机制
标记 - 清除标记出需要回收对象,标记后,统一回收掉被标记对象。优点:实现简单,不需要对象进行移动。缺点:标记、清除效率低,产生大量不连续内存碎片,内存利用率相对不高,加大触发垃圾回收的频率。适用于老年代,CMS用标记清除
标记 - 复制可用内存,分2等份,每次只用一块。这块用完,将存活对象,复制到另一块,再把用过内存清理掉。优点:顺序分配内存,实现简单、运行高效,不考虑内存碎片。缺点:实际可用内存为一半,对象存活率高时,会频繁进行复制。适用于年轻代
标记 - 整理标记,让存活对象向一端移动,清理边界以外内存优点:解决标记-清理算法存在大量内存碎片问题。缺点:需要局部对象移动,降低了效率。适用于老年代
垃圾收集理论及算法
单线程收集器,收集时,要stop the world,用复制算法。和CMS搭配
Serial收集器(标记 - 复制)
Serial收集器对应老年代版本,单线程收集器,用标记整理算法
Serial Old收集器(标记-整理)
Serial收集器多线程版本,要stop the world,复制算法。和CMS搭配
ParNew收集器(标记 -复制)可以搭配Serial Old和CMS
分代模型的新生代收集器,并发多线程复制算法收集器,目标达到可控的吞吐量。如虚拟机运行100分钟,垃圾回收1分钟,吞吐量99%。JDK8默认
Parallel收集器(标记 -复制)JDK8 新生代 默认,侧重吞吐量
Parallel Scavenge收集器老年代版本,多线程,标记-整理算法
Parallel Old收集器(标记-整理)JDK8 老年代 默认,侧重吞吐量
暂停所有其他线程(STW),记录gc roots直接能引用对象,速度很快
初始标记(STW)
GC Roots关联对象,遍历引用链过程, 耗时长,无需停顿用户线程, 可与垃圾收集线程并发运行
并发标记
修正并发标记期间,用户程序运行,标记变动部分,会STW
重新标记(STW)
开启用户线程,GC线程,对未标记区域做清扫。新增对象会被标记为黑色不做任何处理(见下面三色标记算法详解)
并发清除
重置GC过程中标记数据
并发重置
CMS收集器(标记-清除)老年代用 侧重低停顿
初始标记(initialmark,STW)
GC Roots关联对象,遍历引用链过程, 耗时长,无需停顿用户线程, 可与垃圾收集线程并发运行
并发标记(ConcurrentMarking)
最终标记(Remark,STW)
复制算法,将region存活对象复制另一个region中,不会像CMS很多内存碎片,需整理一次,G1用复制算法回收,几乎不会太多内存碎片。
筛选回收(Cleanup,STW)
G1收集器(标记-复制)JDK9默认
Serial回收器内存小(约100m),单核心,停顿时间无要求
Parallel吞吐量有要求,4G以下可用parallel
CMS/G1低停顿,内存4-8G可用ParNew+CMS,8G上可用G1
ZGC内存较大,几百G用ZGC
如何选择垃圾收集器?
垃圾收集器
jpsJVM Process Status Tool,显示系统内HotSpot虚拟机进程
jstatJVM statistics Monitoring 监视虚拟机运行时状态信息,显示出虚拟机进程中类装载、内存、垃圾收集、JIT编译等数据。
jmapJVM Memory Map 生成heap dump文件
jhatJVM Heap Analysis Tool 命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置微型HTTP/HTML服务器,生成dump分析结果,可在浏览器中查看
jstack生成java虚拟机当前线程快照。
jinfoJVM Configuration info 实时查看和调整虚拟机运行参数
JVM常用调优命令
不会立即生效,不可控,不建议使用
System.gc()
大对象空间分配,长期存活对象,进老年代,老年代放不下,会触发Full GC
老年代空间不足
方法区由永久代实现,永久代空间不足 Full GC
永久代空间不足
老年代空间担保失败
cms并发标记和并发清理期间,有新垃圾产生,老年代没有足够空间.
Cocurrent mode failure
什么时候会触发FullGC?
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代,oom前几次垃圾回收?
原理,都是多线程操作,都有存在stw过程,存在并发标记,清理的过程,CMS多了重置操作,G1
流程,初始标记(stw)->并发标记->重新标记->收集垃圾->重置 G1 初始标记stw->并发标记->最终标记->复制移动
优点:回收效率高,cpu利用率,stw时间短,
你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
jvm内存太小,所需内存太大,创建对象时,分配空间就会抛出这个异常1、可适当调整-Xms和-Xmx两个jvm参数2、提高请求执行速度,垃圾回收越早越好
java堆内存溢出(OutOfMemoryError.heap)
加载的类太多或者引用第三方JAR。设置-XX:MetaspaceSize(初始空间大小)和-XX:MaxMetaspaceSize(最大空间),触发FULL GC
元空间内存溢出(OutOfMemoryError: Metaspace)
-XX:MaxPermSize=128m-XXermSize=128m
永久代内存溢出(OutOfMemoryError:PermGenspace)
1、调高-Xss(线程栈大小)2、减少递归调用
栈内存溢出(StackOverflowError)
1、NIO框架用ByteBuffer中的allocateDirect()时,及时clear内存2、-XX:MaxDirectMemorySize=128m
直接内存内存溢出(OutOfMemoryError: Direct buffer memory)
Java 进程98%时间垃圾回收,恢复不到2%堆空间,最后连续5个(编译时常量)垃圾回收如此。1、使用 -Xmx 增加堆大小2、使用 -XX:-UseGCOverheadLimit 取消 GC 开销限制3、修复应用程序中的内存泄漏
GC 开销超过限制(OutOfMemoryError:GCoverheadlimitexceeded)
一个线程空间大小是有限制的,线程空间满后,出现异常。增加线程栈大小。-Xss2m
线程栈满(Stack size too small)
1、减少线程数量。2、线程数量不能减少情况,设置-Xss减小单个线程大小。以便能生产更多的线程。
系统内存被占满(OutOfMemoryError: unable to create new native thread)
内存溢出(out of memory)的场景及解决方案?
线上CPU使用比较高,如何快速定位问题?1、定位进程,top命令查看CPU占比最高的进程PID18932、定位线程,top -Hp 1893命令查看CPU占比最高的线程PID45193、转16进制,printf %x 4519命令将线程B转换成16进制11a74、定位代码,jstack 1893 |grep -A 200 11a7命令查看栈信息,定位到具体的代码行
OOM会导致JVM直接退出吗?为什么?不会,线程抛OOM后,线程持有对象,所占资源都会被释放掉(gc),不影响其它线程工作线程都退出了(只有守护线程),JVM才会退出注:内存泄露不会马上导致异常,内存泄露堆积后,最终可能会导致内存溢出。
jvm
集合
自由主题
0 条评论
回复 删除
下一页