java 大厂面试总结
一: reids
1:redis 数据持久化
RDB:指定的时间间隔内保存数据快照
1、编辑 redis.conf 注:使用whereis redis命令查看redis安装在哪个位置,然后进入redis安装目录的etc目录下,编辑redis.conf。通过备份的方式来实现
缺点: 因为是特定条件下进行一次持久化(每隔一段时间),就可能会导致一旦redis崩溃,再次回复时,可能会导致部分数据丢失。
注:如果设置的备份时间间隔较短,比较耗服务器性能,如果设置的备份时间间隔较长,又可能会导致数据恢复时部分数据丢失。
2:AOF持久化方案
先把命令追加到操作日志的尾部,保存所有的历史操作。
1、相比于RDB持久化方案的优点:
(1)数据非常完整,故障恢复丢失数据少
(2)可对历史操作进行处理
将redis.conf 配置文件中的appendonly 参数改为yes 后,则redis开始启动AOF数据持久化模式
3、RDB模式和AOF模式的恢复
2: redis 的缓冲穿透与 雪崩的解决方式
缓冲穿透:
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,
如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义
解决方法:
2.1:
,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
2.2:
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓冲雪崩
缓存雪崩是指缓存大量失效,导致大量的请求都直接向数据库获取数据,造成数据库的压力。缓存大量失效的原因可能是缓存服务器宏机,或者大量Redis的键设置的过期时间相同。
Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键删除。Redis对过期键的策略+持久化
如果缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。
解决方案
对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况,非常好解决:
解决方法:1、在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:
事发前:实现Redis的高可用(主从架构+Sentinel(哨兵) 或者Redis Cluster(集群)),尽量避免Redis挂掉这种情况发生。
事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)
事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
3: redis 的缓冲击穿
缓冲击穿:
系统中存在以下两个问题时需要引起注意:
当前key是一个热点key(例如一个秒杀活动),并发量非常大。
重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃
解决方案:
1:分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2:设置永不过期时间
从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
2种方案对比:
分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
“永远不过期”:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
4:redis 的并发竞争问题如何解决
redis 的并发竞争问题是什么
多个客户端同时并发写入一个key,可能本来先写入到的数据 后到了,导致数据版本错了
或许 多个客户端同时获取一个key,修改值后在写回去,只要顺序错了,数据就错了
这里的并发指的是多个redis的client同时set key引起的并发问题。
解决方法
1: 分布式锁加 时间戳 ,加一把分布式锁,大家去抢锁,抢到锁就进行set 操作, 加锁的目的是吧一些并行读写的方式改为了串行读写
2:利用消息队里 在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。把Redis.set操作放在队列中使其串行化,必须的一个一个执行。
5: redis 的缓冲失效策略与主键失效机制
惰性过期
1:访问key 的时候判断key 是否过期,过期则清除,对内存不够友好,对cpu友好
定期过期
隔一段 时间去扫描一次,会扫描一定数量的key,并清除过期的key
6:redis 的事务以及分布式锁
7:redis 怎么实现消息的已读与未读
比如说 消息聊天列表里,每一个好友 聊天群都设定为一个模块,只要存在用户在上个时间点之后没有看过的消息就提示用户有新消息
使用hash 存储用户每个模块上次看过的时间
使用使用sortedset存储每个模块的每个信息产生的时间
8:redis 的事务机制
9: redis 的同步机制
10:简述 redis 的事务实现
11: redis 的 缓存与数据库双写一致
二:mq
1: 如何解决重复消费
2:mq 数据丢失怎么解决(数据丢失分为 生产者丢失数据,消费者丢失数据,mq 本身丢失数据)
2.1: 生产者丢失数据
2.2: 消费者丢失数据
2.3 :mq 丢失数据
3: 如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。
4: 如何保证mq 的高可用的
5: 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
6: 那如何保证消息的顺序性?
二.2 kafka 面试
1:为什么使用 Kafka
1:使用场景 ①异步处理 ②应用解耦 ③流量削峰 ④日志处理 ⑤消息通讯等 2:性能好 ①顺序读写 ②零拷贝 ③分区 ④批量发送 ⑤数据压缩 3:优势 1:吞吐量大 2: 分布式 3:大数据日志采集用的比较多
2:介绍下 Kafka 的各个组件
broker:kafka集群中的一个节点
topic:主题是kafka的逻辑上的队列
partition:一个topic可以包含一个或多个partition,每个partition的消息数据都是单独存储的,offset也是单独维护的,partition内部消息有序
partition复制因子:一个topic的所有分区会分布到各个broker上,允许设置复制因子使分区可以在其他节点上留存备份,在主分区所在broker宕机时,可以作为新的主分区继续提供服务
consumer group:一个topic可以有消费者组消费消息,kafka为每个消费者组单独管理每个分区的消费偏移量offset,消费者组间是广播模式,对于一个消费者组内是负载均衡,即一条消息可以被多个消费者组消费,只能被一个消费者组内的其中一个消费者消费;消费者组内的每个成员负责一定数量的分区,当消费者组内的消费者发生变动时,会触发分区的重平衡
pull消费模型:消费者向负责分区主动拉取消息,分区的消费偏移量通过一个默认的topic来记录,也可以显示指定
pull模型的优点:
消费速度可以由消费者自由控制
逐条消费或者批量消费可以由消费者自由控制
3:如何保证写入 Kafka 的数据不丢失
producer : 发送消息主要有两类API可以调用,分别是producer.send(msg)和producer.send(msg,callback)。
- producer.send(msg):俗称“发后即忘”,不管消息有没有成功写入到broker端,生产者都不会收到任何通知,那么到消息因为某些原因(如:网络异常,消息体过大等)并未被broker接受时,就产生了消息丢失。
// sendAPI KafkaProducer producer = new KafkaProducer<String, String>(props); String message = "test message"; producer.send(new ProducerRecord<String, String>("test-topic", message));
- producer.send(msg,callback):带有回调函数,通过callback的回调来处理broker端的响应结果,如果未成功发送,那么就可以做响应的处理工作,如进行重试,记录日志等。
-
// send带回调API KafkaProducer producer = new KafkaProducer<String, String>(props); String message = "test message"; producer.send(new ProducerRecord<String, String>("test-topic", message), new Callback() { @Override public void onCompletion(RecordMetadata recordMetadata, Exception e) { // 具体的逻辑判断 if (Objects.nonNull(e)) { System.out.println("发送异常"); } } });
在kafka中,对于某些异常,生产者捕获到异常会,会进行异常重试,重试的次数是由retries参数来控制的,因此为了保证消费的可靠性,还需要将这个参数的值设置为大于0的值,一般可设置为3~5。
复制因子:创建topic的时候指定复制因子大于1时,一个分区被分配到一个broker上,同时会在其他broker上维护一个分区副本; isr列表:分区及其副本分别为leader和follower,leader对外提供读写服务,follower会向leader发送同步请求,拉取最新的数据,如果follower和leader的消息差距保持在一定范围之内,
那么这个follower在isr列表内;当分区leader所在broker宕机,会从isr列表中选举一个follower作为新的leader提供服务 通过kafka的acks参数可以控制消息的发送行为,acks可选的值有0、1、all;
当设置为0时,生产者消息发送成功即为成功,不关心是否写入到磁盘及后续操作;
当设置为1时,消息发送到分区leader后写入磁盘即为成功;
当设置为all时,消息发送到分区leader并写入磁盘后,同步给isr列表中的所有分区副本后即为成功
4:如何保证从 Kafka 消费的数据不丢失
在kafka中,每一条消息就是自己offset偏移量,消费者每次消费完消息后,都会提交自己消费的位移 可以理解为数组的下标
对于consumer端来说,有两种提交消费位移的方式,分别是自动提交和手动提交。
自动提交:设置为enable.auto.commit为true,表示开启自动提交,自动提交会在每次调用poll时,提交上次poll时的消费位移,每次poll时,都是提交上次的offset的位移,如果是在单线程的情况,
不会出现消费丢失的情况,但是对应多线程的应用来说,就有可能出现消费丢失的情况,例如我们每次poll到的数据都先放在一个队列中,用一个专门的线程来处理队列中的数据,但是我们poll的时候,
上次提交的位移还没有完成消费,消费端出现了宕机,这个时候消费端重启后,就会出现消息丢失的情况。
Properties props = new Properties(); // 开启自动提交 props.put("enable.auto.commit", "true"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("test-topic")); while (true) { ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); }
- 手动提交,使用commitSync() 和 commitASync()API来进行手动提交,手动提交,可以让我们根据自己的实际消费情况来设置什么时间点进行提交位移,将位移提交交给用户自己,合理设置位移提交点可以保证消费的消费不丢失
-
while (true) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1)); process(records); // 处理消息 try { // 提交消费位移 consumer.commitSync(); } catch (CommitFailedException e) { System.out.println("处理提交失败异常"); // 处理提交失败异常 } }
消息不丢失配置的设置
在producer端使用,不要使用producer.send(msg)的API,要使用producer.seng(msg,callback)带有回调方法的API,来进行消息发送会掉确认。
producer端设置acks=all,表示消息全部提交到ISR集合中的全部分区,才算消息提交成功。
producer端设置retries > 0,此参数,表示当生产者发送出现异常(如:broker出现网络抖动,导致超时)producer端进行重试的次数。
broker端,unclean.leader.election.enable = false,表示不允许,非ISR集合中的分区,进行leader选举,因为如果一个follower分区,消息落后于leader分区太远,当这个follower成为leader分区后,就会存在消息丢失。
broker端,replication.factor >= 3,表示副本的数量,消息进行多余的冗余备份,可以防止因为broker端异常,导致的消息丢失。
broker端,min.insync.replicas > 1,这个参数用来控制消息需要最小写入的副本数。
consumer端,将自动提交改为手动提交,确认消息消费完成后,在进行提交。
8:Kafka 怎么保证消息的顺序消费
producer:
Kafka如何保证单partition有序?
producer发消息到队列时,通过加锁保证有序。
先后两条消息发送时,前一条消息发送失败,后一条消息发送成功,然后失败的消息重试后发送成功,造成乱序。为了解决这个问题
Kafka引入了Producer ID(即PID)和Sequence Number。
对于每个PID,该Producer发送消息的每个<Topic, Partition>都对应一个单调递增的Sequence Number。
同样,Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每Commit一条消息时将其对应序号递增。
对于接收的每条消息,如果其序号比Broker维护的序号大一,则Broker会接受它,否则将其丢弃
如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息
如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息
发送失败后会重试,这样可以保证每个消息都被发送到broker
消费者从 partition 中取出来数据的时候,也一定是有顺序的
comsumer:
kafka的消费组的组员最多增加到和partition数量一致,多了不起作用,我们一般将消费组里组员的个数设置为和partition的数量相同
我们都知道Kafka是分布式多partition的,它会将一个topic中的消息尽可能均匀的分发到每个partition上。那么问题就来了,这样怎么保证同一个topic消息的顺序呢?
kafka可以通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程,以保证数据有序。
也就是说生产者在写消息的时候,可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者端创建多个内存队列,具有相同 key 的数据都路由到同一个内存 队列;然后每个线程分别消费一个内存队列即可,这样就能保证顺序性。
9:Kafka 怎么避免重复消费
kafka 不可能 避免消息重复消费 只能大概率避免 牺牲性能的前提下 避免方式 利用幂等性 1:引入单独去重机制 每个消息加一个 分布式id,在消费端,我们可以保存最近的1000条消息id到redis或mysql表中,配置max.poll.records的值小于,落库的时候幂等性 更新
10:kafka 消息堆叠是怎么处理
1:一般产生的原因 可能是 重复消费了,不要自动提交,设置消费者超时稍微大一点
3:kafka-manage 登录这个查看,加大服务数量
6:零拷贝技术使用哪个方法实现
所谓零拷贝,就是在数据操作时,不需要将数据从一个内存位置拷贝到另外一个内存位置,这样可以减少一次内存拷贝的损耗,从而节省了 CPU 时钟周期和内存带宽
7:Java 中也有类似的零拷贝技术,是哪个方法
11
10:什么是 HighWatermark 和 LEO
HW标记了一个特殊的offset
11:什么是 ISR,为什么需要引入 ISR
分区中的所有副本统称为AR(Assigned Repllicas) 所有与leader副本保持一定程度同步的副本(包括Leader)组成ISR(In-Sync Replicas 不丢消息的机制
12:介绍一下kafka 的consumerGroup
在Kafka中,多个Consumer可以组成一个Consumer Group,
一个Consumer只能属于一个Consumer Group。
Consumer Group保证其订阅的Topic的每个分区只被分配给此Consumer Group中的一个消费者处理。
如果不同Consumer Group订阅了同一Topic,Consumer Group彼此之间不会干扰。
这样,如果要实现一个消息可以被多个消费者同时消费(“广播”)的效果,则将每个消费者放人单独的一个Consumer Group;
如果要实现一个消息只被一个消费者消费(“独占”)的效果,则将所有的Consumer放入一个Consumer Group中。在Kafka官网的介绍中,将Consumer Group称为“逻辑上的订阅者
13: kafka为什么快
1:吞吐量大
14:简述kafka 的rebalance 机制
Rebalance 本质上是一种协议,规定了一个 Consumer Group 下的所有 consumer 如何达成一致,来分配订阅 Topic 的每个分区。
例如:某 Group 下有 20 个 consumer 实例,它订阅了一个具有 100 个 partition 的 Topic 。正常情况下,kafka 会为每个 Consumer 平均的分配 5 个分区。这个分配的过程就是 Rebalance
触发rebalance 的前提: 1:consumer 数量发生变化 ,订阅 Topic 的分区数发生变化。订阅的 Topic 个数发生变化。
在 Rebalance 的过程中 consumer group 下的所有消费者实例都会停止工作,等待 Rebalance 过程完成。
步骤
1:Join 加入组,
2:sync 平均消费
三:线程问题
1: 线程池,怎么设置核心线程池
2:如果有50 个任务,队列是100,有两个核心线程池,最大线程池是4 个,这时候会运行几个线程
3: 线程池的拒绝策略
4:callback 线程返回值是什么
5: 比如有10 个线程,7个线程运行完毕,怎么让退出线程
四:sql 问题
1:什么是回表操作,使用索引查询时如何尽量的避免回表操作
一种是主键索引 聚簇索引:
主键索引就是按照主键字段进行构成的索引组织表,通常我们在按照主键索引查询数据时,是直接就能返回所需要的记录的,因为主键索引的叶子节点上记录了该主键索引字段对应的行记录,
非聚簇索引:
我们只能在它的叶子节点上先查找它对应的主键,然后再去主键索引上查找它的行记录,这种按照:辅助索引-->主键索引--->row记录的过程,我们就称之为回表操作。
回表就是先通过数据库索引扫描出数据所在的行,再通过行主键id取出索引中未提供的数据,即基于非主键索引的查询需要多扫描一棵索引树。
详细回答: 我们都知道Mysql索引对数据库的查询有很重要的位置,通过通过索引很快的查找到需要的数据,通过Mysql的索引分为两种:一种是主键索引,一种是辅助索引(辅助索引),
主键索引就是按照主键字段进行构成的索引组织表,通常我们在按照主键索引查询数据时,是直接就能返回所需要的记录的,因为主键索引的叶子节点上记录了该主键索引字段对应的行记录,但是如果我们要是通过辅助索引查找数据时,我们只能在它的叶子节点上先查找它对应的主键,然后再去主键索引上查找它的行记录,这种按照:辅助索引-->主键索引--->row记录的过程,我们就称之为回表操作。
避免方式:
- 能尽量用主键索引查询就能查询结果的的都用主键索引进行查询
- 不能直接用主键索引查询的,我们最好建立联合索引,其实联合索引只是减少了查询过程中回表的次数,但有时也能完全消除操作,具体还是要看select 后面所要返回的字段信息,
- 建立联合索引的其中的一个技术是用到了索引覆盖(索引覆盖其实也是减少查询过程中回表的次数),通过我们在执行计划中的Extra字段中会有'use index'信息出现
2:mysql 的索引有哪些 结构
4:什么情况下使用 hash 树,以及b+tree 树
5:联合索引,最左匹配原则
这里关于最左匹配原则用如下一张表来表示: 其中(a,b,c)是构造的联合索引
![](https://img2022.cnblogs.com/blog/1001363/202202/1001363-20220223145308830-965990612.png)
6: 什么情况下明明创建了索引,当是执行的时候并没有通过索引
7: 数据的事务级别这块
8:什么是索引下堆
https://www.cnblogs.com/zmc60/p/14938740.html
9:分库分表后怎么关联查询,这块是怎么做到分布式事务一致性的问题
分布式事务一致性:
两阶段提交
三阶段提交
五: 事务问题
一:spring 的事务是怎么执行处理的,多个service 层的话,事务会同时回滚吗?如果会回滚怎么保证 不想被回滚的service 层不被回滚, 为什么会回滚
事务特性(4种):
原子性 (atomicity):强调事务的不可分割.
一致性 (consistency):事务的执行的前后数据的完整性保持一致.
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(durability) :事务一旦结束,数据就持久到数据库
如果不考虑隔离性引发安全性问题: 脏读 :一个事务读到了另一个事务的未提交的数据 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致. 虚幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致. 解决读问题: 设置事务隔离级别(5种) DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. 未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生 已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生 可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生. 串行化的 (serializable) :避免以上所有读问题. Mysql 默认:可重复读 Oracle 默认:读已提交 read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。 repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。 serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。
二: spring 事务的传播方式
* 保证同一个事务中 propagion_required: 支持当前事务,如果不存在 就新建一个(默认) propagion_supports: 支持当前事务,如果不存在,就不使用事务 propagion_mandatory: 支持当前事务,如果不存在,抛出异常 * 保证没有在同一个事务中 propagion_requires_new: 如果有事务存在,挂起当前事务,创建一个新的事务 propagion_not_supported: 以非事务方式运行,如果有事务存在,挂起当前事务 propagion_never: 以非事务方式运行,如果有事务存在,抛出异常 propagion_nested: 如果当前事务存在,则嵌套事务执行
数据库是怎么保证在断电的情况下,保持消息一致性的
六:微服务
1: 微服务的时候怎么进行服务之间的调用,用什么技术来处理的。
使用Feign A服务调用 b 服务的service层,B服务提供接口,使用feign走restful的调用形式
2: gateway 的流程
1:当我们的请求到达了gatewat 网关 ,网关先利用断言,来判定这次请求是不是符合某个路由规则,如果符合了就按这个路由贵州给他路由到指定地方,去这些指定地方就要经过一系列的filter 来指定
难点就是 如何制定路由规则,这些断言如何判断失败
3: apollo
apollo 是如何实现配置回滚的
1:apollo 没吃发布都会在release 库里面生成一个版本,每次回退的是 覆盖表的版本,回退到上一个版本
七: 分布式
7.1:分布式事务
7.1.1:什么是两阶段提交什么是三阶段提交
cap 原理:
Consistency:一致性,这个和数据库ACID的一致性类似,但这里关注的所有数据节点上的数据一致性和正确性,而数据库的ACID关注的是在在一个事务内,对数据的一些约束。系统在执行过某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读取到最新值。
Availability:可用性,每一个操作总是能够在一定时间内返回结果。需要注意“一定时间”和“返回结果”。“一定时间”是指,系统结果必须在给定时间内返回。“返回结果”是指系统返回操作成功或失败的结果。
Partition Tolerance:分区容忍性,是否可以对数据进行分区。这是考虑到性能和可伸缩性
两阶段提交:
两个概念:
事务协调者: 通常一个系统只有一个
事务参与者: 一般包含多个 可以理解为数据库副本的个数
1:请求阶段
请求阶段 事务协调者会通知事务参与者准备, 提交或者取消事务,然后进入表决过程
在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)
2:提交阶段
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
参与者在接收到协调者发来的消息后将执行响应的操作
两阶段提交的缺点:
1.同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。
当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2.单点故障。由于协调者的重要性,一旦协调者发生故障。
参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3.数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。
而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象
两阶段提交无法解决的问题:
当协调者出错,同时参与者也出错时,两阶段无法保证事务执行的完整性。
考虑协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。
那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。 三阶段提交:
7.2:分布式锁
7.3:分布式调度
1:分布式的事务怎么实现
2:什么是分布式事务,保证了什么:
就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分 布式事务就是为了保证不同数据库的数据一致性。
cap 原则与定理
base 理论:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性
解决方案:
1: 基于XA 协议的两阶段提交
XA 是一个分布式 事务协议,大致分为两个部分,事务管理器以及本地资源管理器
本地资源管理器: 右数据实现,比如Oracle ,DB2这些商业数据库都实现了XA 接口。
事务管理器:作为了全局的调度者,负责各个本地资源的提交与回滚。
实现思路:
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往 并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主 库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
2:tcc编程模式
3:seata 框架
https://www.cnblogs.com/workstation-nigoudongma/p/9546801.html
7.2: 分布式锁
7.3: 分布式调度
7.3.1:布式调度,我4 个定时器,有两个任务,怎么只让一个服务进行定时
8:mybatist
1:mybatist 中 resultType 与resultMap 的区别
八 :数据结构的问题
1: hashMap 1.7 与1.8 之后的区别,1.8 是什么时候扩容 以及底层实现的逻辑
说下hashMap 的底层实现
Hash Map采用的是散列表来记录数据,可以把散列表想象成table[]数组,每个下标下标保存一个链表的head,用链表解决hash值冲突的问题,每次添加调用put(key , value),先得到key的hash值然后用hash & (table的长度)来得到这个Node<key,value>应该存放的位置index,为什么不同hash % (table的长度),这里就不详细说明了,得到index后就会查看这个table[index]存不存在数据,如果不存在直接把table[index]指向Node<key,value>,如果存在就会有哈希冲突,就会遍历这个table[index]指向的链表,如果已经存在这个key值就会更新该Node<key, value>中的value值,如果不存在就添加到table[index]指向链表的尾节点。
HashMap 多线程操作导致死循环问题,这是1.7 的问题,
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复制链表过程如下: 以下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,
1.8 已经解决了HashMap 多线程导致死循环问题 ,解决的方法是 通过尾插法解决的,没有进行什么锁操作。
2:concurrentHashMap 如何实现线程安全与hashTable 的区别
3: 创建线程安全的hashMap 方式有几种
1. 使用Collections.synchronizedMap(Map)创建线程安全的map集合;
2:hashTable
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
可以看到,Hashtable是用在synchronized修饰方法。
这个和synchronizedMap(Map)一样,效率不高。
https://blog.csdn.net/qq_45036591/article/details/105470901
3:concurrentHashMap
实现方式其实 与 hashMap 是一样的只是加入了锁机制
1.7 与1.8 也不一样
1.7 实现使用 ReentrantLock 与volatile 来解决线程安全
1.8 实现使用 了 CAS + synchronized 来保证并发安全性
九: springboot 的面试
1:springboot aplication 的运行机制
2: 条件注解,用到过没
你在项目中都遇到过哪些问题怎么解决的了
1:
问题:并发注册问题
我们在做完功能之后,对首页上所有功能做了并发性能测试,结果测出在做注册功能时会碰到同一个用户名或者手机号在并发量高的情况下会出现多次注册的问题。
我们的需求要求每个手机号或用户名只能被注册一次,在用户注册填写用户名手机号时会通过ajax去后台异步校验是否有重复记录,在并发情况下,会出现多个注册用户同时去后台校验一个未被注册的用户名时,都会成功通过,从而导致注册的用户名或者手机号重复。
解决方案:利用redis来解决,用户输入用户名去后台校验一旦成功,首先去redis中查看是否有这条用户名,如果有,则视为此用户名被别的用户抢占了,如果没,就放进去,然后返回校验成功。在别的用户去redis找这条数据只能排队去查时都能查到,视为用户名被抢占了,返回用户名校验不通过。
2:大量的使用了缓存,那么就存在缓存的过期时间控制以及缓存击穿以及缓存雪崩等问题
十: jvm 的这块
1: jvm 如何调优
十一: 锁机制这块的面试
1: aqs 的实现方式,由aqs 实现的锁有哪些
2 : reentrantLock 上锁释放锁
3: juc 下的锁
4:cas 自旋的优化方式
// send带回调API KafkaProducer producer = new KafkaProducer<String, String>(props); String message = "test message"; producer.send(new ProducerRecord<String, String>("test-topic", message), new Callback() { @Override public void onCompletion(RecordMetadata recordMetadata, Exception e) { // 具体的逻辑判断 if (Objects.nonNull(e)) { System.out.println("发送异常"); } } });
消费者端创建多个内存队列,具有相同 key 的数据都路由到同一个内存 队列;然后每个线程分别消费一个内存队列即可,这样就能保证顺序性。
一种是主键索引