常用中间件面试汇总:Mysql、Mq、Redis、操作系统、Nacos、Es、Mybatis
- Mysql
- 消息队列
-
- 1、为什么要使用消息队列
-
- [1.1 kafka、rocketMQ、rabbitMQ的区别与联系](#1.1 kafka、rocketMQ、rabbitMQ的区别与联系)
- 2、kafa消息队列
-
- [2.1 Kafka的架构是怎么样的?](#2.1 Kafka的架构是怎么样的?)
- [2.2 Kafka 消息的发送过程](#2.2 Kafka 消息的发送过程)
- [2.3 Kafka 消息丢失问题](#2.3 Kafka 消息丢失问题)
- [2.4 Kafka 消息重复消费问题](#2.4 Kafka 消息重复消费问题)
- [2.5 Kafka 消息顺序消费问题](#2.5 Kafka 消息顺序消费问题)
- [2.6 Kafka 的事务消息](#2.6 Kafka 的事务消息)
- [2.7 Kafka 的重平衡问题](#2.7 Kafka 的重平衡问题)
- [2.8 Kafka 为什么这么快?](#2.8 Kafka 为什么这么快?)
- 3、RocketMQ消息队列
-
- [3.1 RocketMQ的架构是怎么样的?](#3.1 RocketMQ的架构是怎么样的?)
- [3.2 RocketMQ消息的发送过程](#3.2 RocketMQ消息的发送过程)
- [3.3 RocketMQ 消息丢失问题](#3.3 RocketMQ 消息丢失问题)
- [3.4 RocketMQ 消息重复消费问题](#3.4 RocketMQ 消息重复消费问题)
- [3.5 RocketMQ 消息顺序消费问题](#3.5 RocketMQ 消息顺序消费问题)
- [3.6 RocketMQ 的事务消息](#3.6 RocketMQ 的事务消息)
- [3.7 RocketMQ 的重平衡问题](#3.7 RocketMQ 的重平衡问题)
- [3.8 RocketMQ 如何实现延时消息?](#3.8 RocketMQ 如何实现延时消息?)
- [4、 RabbitMQ消息队列](#4、 RabbitMQ消息队列)
-
- [4.1 RabbitMQ的架构是怎么样的?](#4.1 RabbitMQ的架构是怎么样的?)
- [4.2 RabbitMQ消息的发送过程](#4.2 RabbitMQ消息的发送过程)
- [4.3 RabbitMQ 消息丢失问题](#4.3 RabbitMQ 消息丢失问题)
- [4.4 RabbitMQ 消息重复消费问题](#4.4 RabbitMQ 消息重复消费问题)
- [4.5 RabbitMQ 消息顺序消费问题](#4.5 RabbitMQ 消息顺序消费问题)
- [4.6 RabbitMQ 的事务消息](#4.6 RabbitMQ 的事务消息)
- [4.7 RabbitMQ 如何实现延时消息](#4.7 RabbitMQ 如何实现延时消息)
- Redis
- 操作系统
- Nacos
- Es
-
- [1、为什么要使用ElasticSearch?和传统关系数据库(如 MySQL)有什么不同?](#1、为什么要使用ElasticSearch?和传统关系数据库(如 MySQL)有什么不同?)
- [2、Elasticsearch 整体架构?如何优化 ElasticSearch 搜索性能?](#2、Elasticsearch 整体架构?如何优化 ElasticSearch 搜索性能?)
- 3、什么是ElasticSearch的深度分页问题?如何解决?
- Mybatis
Mysql
1、InnoDB存储引擎
【Mysql】事务隔离级别、索引原理、/redolog/undolog/binlog区别、主从复制原理
InnoDB和MyISAM是MySQL中比较常用的两个执行引擎,MySQL 在 5.5 之前版本默认存储引擎是 MyISAM,5.5 之后版本默认存储引擎是 InnoDB,MyISAM适合查询以及插入为主的应用,InnoDB适合频繁修改以及涉及到安全性较高的应用。
他们主要有以下区别:
1、InnoDB支持事务 ,MyISAM不支持
2、InnoDB 是聚集索引 ,MyISAM 是非聚集索引。
3、InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。
2、MySQL中的事务隔离级别?
【Mysql】事务隔离级别、索引原理、/redolog/undolog/binlog区别、主从复制原理
mysql设立了4种隔离级别。隔离级别越高,数据库的并发性能就越差。由于脏写是一个严重的问题,因此这4种隔离级别都解决了脏写问题。
1、读未提交(Read Uncommited):可以看到其他未提交事务执行的结果。解决了脏写问题。
2、读已提交(Read Commited):一个事务只能看见已提交事务执行的结果。解决了脏写、脏读问题。
3、可重复读(Repeatable read):可重复读是mysql默认的隔离级别。一个事务读取前后的数据保证一致,不受其他并发事务修改的影响。解决了脏写、脏读、不可重复读的问题,但还是会有幻读的问题。
4、串行化(Serializable):一个事务读取数据期间,禁止其他事务插入、更新、删除操作。解决了脏写、脏读、不可重复读、幻读的问题。
数据库的读写问题,除了采用加锁的方式解决,还可以通过MVCC的方式解决。
所谓的MVCC,就是生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo日志构建)。查询语句只能读到在生成ReadView之前已提交事务做的修改,不影响其他事务进行写操作,因此可以解决读写问题。相比于加锁的方式解决读写问题,可以大幅提高并发性能。
在Innodb中,通过MVCC解决脏读和不可重复读,通过MVCC+间隙锁解决幻读。
所谓的MySQL事务的2阶段提交,其实是在更新过程中,保证binlog和redolog一致性的一种手段。过程是:
● Prepare 阶段
○ 这个阶段 SQL 已经成功执行并生成 redolog并写入磁盘,处于prepare阶段
● BinLog持久化
○ binlog 提交,将 binlog 内存日志写入磁盘
● Commit
○ 在执行引擎内部执行事务操作,写入redolog,处于Commit阶段
3、mysql索引原理
【Mysql】事务隔离级别、索引原理、/redolog/undolog/binlog区别、主从复制原理
索引的概念:
索引是帮助mysql高效获取数据的数据结构,因此索引是数据结构。
聚簇索引就是按照每张表的主键构造一个B+树,B+树的叶子节点中记录着表中一行记录的所有值。只要找到这个叶子节点也就得到了这条记录的所有值。
什么是回表,怎么减少回表的次数?
通过非主键查询时,会先通过非聚簇索引查到主键的值,之后,还需要再通过主键的值再进行一次查询才能得到我们要查询的数据。而这个过程就叫做回表。
索引失效问题怎么排查?
在排查索引失效的时候,第一步一定是找到要分析的SQL语句,然后通过explain查看他的执行计划。主要关注key、type和extra这几个字段。一般来说,比较理想的走索引的话,应该是以下几种情况:
1、首先看一下key,表示查询优化器选择使用的索引。key一定要有值,不能是NULL。
2、其次看一下type,表示查询时所使用的索引类型。阿里巴巴开发手册要求,type至少应该是range级别、一般要达到ref级别、最好是const级别。all和index都是不满足条件的。
- 使用非索引字段查询:all
- 不符合最左前缀匹配的索引查询:index
- 使用索引进行范围查询:range
- 使用非唯一索引进行查询:ref
- 使用唯一键索引做唯一查询:const
3、最后看一下extra,表示其他额外的信息。如果是NULL,或者using index,using index condition都是可以的。
需要知道的是,到底要不要走索引,走哪个索引,是MySQL的优化器决定的,他会根据预估的成本来做一个决定。那么,有以下这么几种情况可能会导致没走索引:
1、没有正确创建索引:当查询语句中的where条件中的字段,没有创建索引,或者不符合最左前缀匹配的话,就是没有正确的创建索引。
2、索引区分度不高:如果索引的区分度不够高,那么可能会不走索引,因为这种情况下走索引的效率并不高。
3、表太小:当表中的数据很小,优化器认为扫全表的成本也不高的时候,也可能不走索引
4、查询语句中,索引字段因为用到了函数、类型不一致、使用了OR、LIKE、不等于、not null等导致了索引失效
4、binlog、redolog和undolog区别?
【Mysql】事务隔离级别、索引原理、/redolog/undolog/binlog区别、主从复制原理
什么是binlog、redolog和undolog?
InnoDb存储引擎在更新数据时,并不会直接刷新到磁盘上,这样效率和速度都很低。而是会顺序IO写入到redolog日志中,该事务就算提交成功了。redolog日志保证了事务的持久性,数据库崩溃恢复时,可读取redolog日志,保证事务的原子性和一致性。
undolog日志主要有2个作用:
作用1:回滚数据时逻辑上地将数据回滚到事务开始前,实际磁盘页上的数据是进行了变更的。
作用2:InnoDb存储引擎中的MVCC是通过undo日志实现的。
binlog记录了数据库所有的DDL和DML等数据库更新事件的语句。它以事件形式记录并保存在二进制文件中,通过这些信息,可以再现数据更新操作的全过程。
binlog、redolog和undolog区别?
redo日志针对的是事务写过程中,在保证数据持久性的基础上,提升了事务提交的速度,保证了事务的持久性 。
undo日志针对的是事务需要回滚时,保证了事务的数据一致性。
redo log让innodb存储引擎拥有崩溃恢复能力,持久化保障的的是单台机器。
binlog保证了mysql集群架构的数据一致性,持久化保障的是多台机器。
5、sql调优经验?
1、大部分可以通过添加联合索引来优化,需要保证最左前缀匹配。
2、只查询部分字段的话,优先考虑使用覆盖索引,减少一次回表操作。
3、某些慢查询耗时很长,可以考虑使用主键或唯一键为游标,将一次查询改为2次或者多次查询来进行优化。解决深分页查询也是类似的原理,子查询先查出符合条件的主键id,然后再利用主键索引来做范围查询得到分页结果。
深分页查询典型case:
sql
SELECT c1, c2, cn... FROM table WHERE name = "jack" LIMIT 1000000,10
优化后:
sql
SELECT c1, c2, cn...
FROM table
WHERE name = "jack"
AND id >= (SELECT id FROM table WHERE name = "jack" ORDER BY id LIMIT 1000000, 1)
ORDER BY id
LIMIT 10
4、查询条件中带有in关键字的话,如果in中的类型是有限的且比较少,可以考虑并行查询,利用索引提升效率。
5、多多基本上没有多表join查询的情况,确实存在的话,一般会通过mysql dts同步到es表中,通过es进行宽表查询。
6、数据库锁
什么时候加的是行锁,什么时候加的是间隙锁?
1、对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,而不会锁定间隙。
2、对于其他搜索条件,InnoDB锁定扫描的索引范围,使用gap lock或next-key lock来阻塞其他事务插入范围覆盖的间隙。
已经有一个表级别的S锁和X锁了,为什么还需要意向锁呢?
意向锁是数据库管理系统中用于实现锁协议的一种锁机制,旨在处理不同锁粒度(如行锁和表锁)之间的并发性问题。
比如事务A加了一个行锁,事务B想添加一个表级别的X锁,那事务B怎么知道能否加锁成功呢?不可能一行一行的去判断是否加了行锁。因此这里就需要意向锁了,事务A加行锁的同时会加一个表级别的意向排他锁,待事务A完成之后,才能加锁成功。
消息队列
1、为什么要使用消息队列
使用消息队列的主要目的:解耦、异步、削峰填谷。
1.1 kafka、rocketMQ、rabbitMQ的区别与联系
1、架构不同。kafka的物理分区是分区partition,rocketMQ和rabbitMQ的物理分区是队列Queue。
- kafka架构中topic是逻辑分区,分区partition是物理分区,同一个partition里的数据是有序的。kafka集群是partition维度,同一个topic下的不同partiton分布在不同的broker中。
- rocketMQ架构中topic是逻辑分区,队列Queue是物理分区,同一个队列里的数据是有序的。rocketMQ集群是broker维度,一个主broker,可以有多个从broker,通过主从复制提升集群可用性。
- rabbitMQ架构是根据交换器路由消息到指定队列Queue中,而rocketMQ不需要交换器。
2、事务消息不同。kafka的事务保证的是消息处理的原子性,rocketMQ和rabbitMQ的事务可以保证本地事务和消息处理的原子性。
- Kafka的事务消息可以确保一组生产或消费操作要么全部成功,要么全部失败,以保证消息处理的原子性。
- rocketMQ在发送事务消息时,首先向Broker发送一条"half消息"(即半消息),半消息将被存储在Broker端的事务消息日志中,但是这个消息还不能被消费者消费。在半消息发送成功后,应用程序通过执行本地事务来确定是否要提交该事务消息。
- RabbitMQ的事务机制,允许生产者将一组操作打包成一个原子事务单元,要么全部执行成功,要么全部失败。事务机制是同步的,提交一个事务之后会阻塞在那儿,相比之下rocketMQ的事务消息性能更好。 confirm机制是异步的,发送一个消息之后就可以发送下一个消息,RabbitMQ 接收了之后会异步回调confirm接口通知这个消息接收到了。一般在生产者这块避免数据丢失,建议使用用 confirm 机制,不要使用事务机制。
3、延迟队列的实现区别?
- kafka的发送消息时可以设置延时参数。Kafka维护了一个定时器管理器,定期检查消息的延时时间是否到期。当消息的延时时间到期后,Kafka将消息推送给对应的消费者进行消费。
- 在RocketMQ 5.0中,采用了一种新的实现方式:基于时间轮的定时消息。时间轮是一种高效的定时器算法,能够处理大量的定时任务,并且能够在O(1)时间内找到下一个即将要执行的任务。
- rabbitMQ基于死信队列的方式,消息先会投递到一个正常队列,在TTL过期后进入死信队列。但是基于插件的这种方式,消息并不会立即进入队列,而是先把他们保存在一个基于Erlang开发的Mnesia数据库中,然后通过一个定时器去查询需要被投递的消息,再把他们投递到x-delayed-message交换机中。
4、顺序消费的实现区别?
- kafka的物理分区是partition,所以同一个partition的消息是有序的。实现顺序消费,一种方式是在一个topic中,只创建一个partition。另一种方式是发送消息的时候指定partition。
- rocketMQ的物理分区是队列,所以同一个队列的消息是有序的。实现顺序消费,要求是生产者发送消息时指定队列,要求消费者使用有序消费模式MessageListenerOrderly。为了保证同一个队列中的有序消息可以被顺序消费,一共需要加3把锁。这三把锁分别是:1)先锁定Broker上的MessageQueue,确保消息只会投递到唯一的消费者;2)消费者对本地的MessageQueue加锁,确保只有一个线程能处理这个消息队列;3)对存储消息的ProcessQueue加锁(broker中的topic加锁),确保在重平衡的过程中不会出现消息的重复消费。
- rabbitMQ的物理分区是队列,所以同一个队列的消息是有序的。实现顺序消费,需要根据路由键将消息路由到指定队列,队列对应多个消费者。通过 basicQos(1) 保证每个消费者一次只处理一条消息,做到顺序消费的同时,保证消费能力。
2、kafa消息队列
2.1 Kafka的架构是怎么样的?
Kafka 的整体架构比较简单,是显式分布式架构,主要由 Producer(生产者)、broker(Kafka集群)和 consumer(消费者) 组成。
每个broker中可以有多个topic,每个topic可以有多个partition。
topic是承载消息的逻辑容器,partition是topic的物理分区,是一个有序不变的消息序列。
每个partition可以有多个副本,只有 Leader partition才能处理生产者和消费者的请求,而 Follower partition只是 Leader 分区的备份,用于提供数据的冗余备份和容错能力。
2.2 Kafka 消息的发送过程
使用Kafka发送消息时,一般有两种方式,分别是同步发送(producer.send(msg).get() )及异步发送(producer.send(msg, callback))。
同步发送的时候,可以在发送消息后,通过get方法等待消息结果:producer.send(record).get(); ,这种情况能够准确的拿到消息最终的发送结果,要么是成功,要么是失败。
而异步发送,是采用了callback的方式进行回调的,可以大大的提升消息的吞吐量,也可以根据回调来判断消息是否发送成功。
1、生产者通过主线程(Main)发送消息到消息累加器(RecordAccumulator)中。
2、当满足指定条件时,消息累加器(RecordAccumulator) 将缓冲区中的消息组织成一个批次(batch),然后通过发送线程(Sender)一次性发送给 Broker。
3、为了保证数据的可靠性,Kafka 使用了消息复制机制(In-Sync Replicas(同步副本))。Leader Broker 接收到消息后,会将消息复制到其他副本(Partition Follower)。
4、Producer依赖ACK才能知道消息是否投递成功,一共有3种情况
4.1 request.required.acks = 0:表示 Producer 不等待来自 Leader 的 ACK 确认,直接发送下一条消息。
4.2 request.required.acks = 1:表示 Producer 等待来自 Leader 的 ACK 确认,当收到确认后才发送下一条消息。
4.3 request.required.acks = -1:Leader会把消息复制到集群中的所有ISR(In-Sync Replicas,同步副本),要等待所有ISR的ACK确认后,再向Producer发送ACK消息,然后Producer再继续发下一条消息。
2.3 Kafka 消息丢失问题
Kafka如何保证消息不丢失?
1、生产者
生产者通过ack来确认是否发送消息成功,其中ack有3种确认模式。
第1种是不需要等待leader分区ack确认,直接发送下一条消息;
第2种是等待leader分区ack确认写入后,再发送下一条消息;
第3种是等待leader和所有副本分区都ack确认写入后,再发送下一条消息。
2、broker
通过持久化存储和ISR副本复制机制来保证消息不丢失。
3、消费者
可以使用手动提交偏移量的方式。避免拉取了消息以后,业务逻辑没处理完,提交偏移量后消费者挂了的问题。
为什么Kafka没办法100%保证消息不丢失?
如果broker集群和生产者都先后挂了,则无法100%保证消息不丢失。
但是可以做一些机制来保证,比如引入本地消息表等,保证在Kafka Broker没有保存消息成功时,可以重新投递消息。
2.4 Kafka 消息重复消费问题
在Kafka中,每个消费者都必须加入至少一个消费者组。同一个消费者组内的消费者可以共享消费者的负载。因此,如果一个消息被消费组中的任何一个消费者消费了,那么其他消费者就不会再收到这个消息了。
消费者可以通过手动提交消费位移来控制消息的消费情况。通过手动提交位移,消费者可以跟踪自己已经消费的消息,确保不会重复消费同一消息。
2.5 Kafka 消息顺序消费问题
Kafka的消息是存储在指定的topic中的某个partition中的。同一个partition中的消息是有序的,但是跨partition,或者跨topic的消息就是无序的了。
基于此,想要实现消息的顺序消费,可以有以下2个办法:
1、在一个topic中,只创建一个partition,这样这个topic下的消息都会按照顺序保存在同一个partition中,这就保证了消息的顺序消费。
2、发送消息的时候指定partition,如果一个topic下有多个partition,那么我们可以把需要保证顺序的消息都发送到同一个partition中,这样也能做到顺序消费。
2.6 Kafka 的事务消息
在Kafka中,事务消息可以确保一组生产或消费操作要么全部成功,要么全部失败,以保证消息处理的原子性。也就是说,他的作用是保证一组消息要么都成功,要么都失败。
只不过,通常在分布式系统中,通常说的事务消息,如RocketMQ的事务消息保证的是本地事务和发MQ能作为一个原子性,即要么一起成功,要么一起失败。
所以,Kafka的事务消息只保证他自己的消息发送的原子性。而RocketMQ的事务消息是保证本地事务和发消息的原子性。
kafka消息的事务提交和回滚,也是遵循了一个2阶段提交。
2.7 Kafka 的重平衡问题
重平衡的 3 个触发条件:
● 消费者组成员数量发生变化。(新消费者的加入或者退出)
● 订阅主题(Topic)数量发生变化。
● 订阅主题的分区(Partition)数发生变化。
当Kafka 集群要触发重平衡机制时,大致的步骤如下:
1、暂停消费:在重平衡开始之前,Kafka 会暂停所有消费者的拉取操作,以确保不会出现重平衡期间的消息丢失或重复消费。
2、计算分区分配方案:Kafka 集群会根据当前消费者组的消费者数量和主题分区数量,计算出每个消费者应该分配的分区列表,以实现分区的负载均衡。
3、通知消费者:一旦分区分配方案确定,Kafka 集群会将分配方案发送给每个消费者,告诉它们需要消费的分区列表,并请求它们重新加入消费者组。
4、重新分配分区:在消费者重新加入消费者组后,Kafka 集群会将分区分配方案应用到实际的分区分配中,重新分配主题分区给各个消费者。
5、恢复消费:最后,Kafka 会恢复所有消费者的拉取操作,允许它们消费分配给自己的分区。
MQ的重平衡会带来哪些问题?
1、STW:在重平衡过程中,消费者可能会短暂停止消费,等待新的分区/队列分配,导致吞吐下降。
2、重复消费:当消费者重新分配队列/分区时,可能会重新拉取到未提交的消息,导致消息被多次消费。
3、消息堆积:所有消费者都会暂停,导致短时间内消息积压。
什么是Kafka的渐进式重平衡?
在 Kafka 2.4.0 引入了渐进式重平衡。减少所有分区都停止消费的情况,而只有其中部分分区需要重新分配而已。
2.8 Kafka 为什么这么快?
1、消息发送
1.1 批量发送:Kafka 通过将多个消息打包成一个批次,减少了网络传输和磁盘写入的次数,从而提高了消息的吞吐量和传输效率。
1.2 异步发送:生产者可以异步发送消息,不必等待每个消息的确认,这大大提高了消息发送的效率。
1.3 并行发送:通过将数据分布在不同的分区(Partitions)中,生产者可以并行发送消息,从而提高了吞吐量。
2、消息存储
2.1 磁盘顺序写入:Kafka把消息存储在磁盘上,且以顺序的方式写入数据。顺序写入比随机写入速度快很多,因为它减少了磁头寻道时间。避免了随机读写带来的性能损耗,提高了磁盘的使用效率。
2.2 页缓存:Kafka 将其数据存储在磁盘中,但在访问数据时,它会先将数据加载到操作系统的页缓存中,并在页缓存中保留一份副本,从而实现快速的数据访问。
2.3 分区和副本:Kafka 采用分区和副本的机制,可以将数据分散到多个节点上进行处理,从而实现了分布式的高可用性和负载均衡。
3、消息消费
3.1 消费者群组:通过消费者群组可以实现消息的负载均衡和容错处理。
3.2 并行消费:不同的消费者可以独立地消费不同的分区,实现消费的并行处理。
3.3 批量拉取:Kafka支持批量拉取消息,可以一次性拉取多个消息进行消费。减少网络消耗,提升性能
3、RocketMQ消息队列
【RocketMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息
3.1 RocketMQ的架构是怎么样的?
RocketMQ中有这样几个角色:NameServer、Broker、Producer和Consumer。
NameServer:NameServer是RocketMQ的路由和寻址中心,它维护了Broker和Topic的路由信息,提供了Producer和Consumer与正确的Broker建立连接的能力。NameServer还负责监控Broker的状态,并提供自动发现和故障恢复的功能。
Broker:Broker是RocketMQ的核心组件,负责存储、传输和路由消息。它接收Producer发送的消息,并将其存储在内部存储中。并且还负责处理Consumer的订阅请求,将消息推送给订阅了相应Topic的Consumer。
3.2 RocketMQ消息的发送过程
RocketMQ默认使用集群消费模式:
任意一条消息只需要被集群内的任意一个消费者处理即可。集群模式下,每一条消息都只会被分发到一台机器上处理。但是不保证每一次失败重投的消息路由到同一台机器上。
3.3 RocketMQ 消息丢失问题
RocketMQ如何保证消息不丢失?
1、生产者
生产者支持同步发送和异步发送,都可以根据ack的结果来确认是否发送成功。
如果想要保证消息不丢失,可以将消息保存机制修改为同步刷盘,这样,Broker会在同步请求中把数据保存在磁盘上,确保保存成功后再返回确认结果给生产者。
2、broker
为了保证消息不丢失,RocketMQ肯定要通过集群方式进行部署,Broker 通常采用一主多从部署方式。
并且需要采用主从同步的方式做数据复制,即Master在将数据同步到Slave节点后,再返回给生产者确认结果。
3、consumer
消费者需要确保在消息拉取并消费成功之后再给Broker返回ACK。
RocketMQ和Kafka一样,只能最大限度的保证消息不丢失,但是没办法做到100%保证不丢失。原理也类似,如果broker集群和生产者先后发生崩溃后,可能导致消息丢失。
3.4 RocketMQ 消息重复消费问题
RocketMQ如果重复消费了,可能是什么原因导致的?
1、Consumer返回给Broker消费失败(常见)
2、Consumer消费处理超时了(常见)
3、消息发重了(常见)
3.5 RocketMQ 消息顺序消费问题
和Kafka只支持同一个Partition内消息的顺序性一样,RocketMQ中也提供了基于队列的顺序消费。即同一个队列内的消息可以做到有序,但是不同队列内的消息是无序的!
1、生产者
生产者发送消息时需要指定队列,通常可以使用取模法进行路由。
2、消费者
RocketMQ的MessageListener回调函数提供了两种消费模式,有序消费模式MessageListenerOrderly和并发消费模式MessageListenerConcurrently。所以,想要实现顺序消费,需要使用MessageListenerOrderly模式接收消息。
为了保证同一个队列中的有序消息可以被顺序消费,一共需要加3把锁:
1、先锁定Broker上的MessageQueue,确保消息只会投递到唯一的消费者
2、消费者对本地的MessageQueue加锁,确保只有一个线程能处理这个消息队列
3、对存储消息的ProcessQueue加锁(broker中的topic加锁),确保在重平衡的过程中不会出现消息的重复消费。
RocketMQ顺序消费的缺点?
1、并发能力受限
为了保证顺序,通常会把消息按照 key 分配到固定队列或分区。并且要求同一个 key 的消息串行消费。这就导致消息无法并发消费,那么处理速度就会大大下降,尤其是当某个消息处理慢的话,会拖慢整个队列的速度。
而kafka消息在topic的基础上又加了一层分区的概念,基于分区维度进行顺序消费,可以提升并行消费的能力。
3.6 RocketMQ 的事务消息
在发送事务消息时,首先向RocketMQ Broker发送一条"half消息"(即半消息),半消息将被存储在Broker端的事务消息日志中,但是这个消息还不能被消费者消费。
接下来,在半消息发送成功后,应用程序通过执行本地事务来确定是否要提交该事务消息。
如果本地事务执行成功,就会通知RocketMQ Broker提交该事务消息,使得该消息可以被消费者消费;否则,就会通知RocketMQ Broker回滚该事务消息,该消息将被删除,从而保证消息不会被消费者消费。
3.7 RocketMQ 的重平衡问题
和Kafka不同的是,RocketMQ他只有个定时重平衡的机制,他会自动的每 20s 进行一次重平衡检查,如果发现有消费者新增或离开时,会触发重新分配队列。
由于 RocketMQ 的消费者是通过 异步拉取然后再放到本地队列处理消息的,即使重平衡发生,每个消费者仍然可以继续消费它当前的队列中的消息,只要重平衡的时间足够短,就可以完全消除STW的发生,因为这段时间本地队列中消息还是在正常处理的。一旦重平衡好了,拉取的时候拉取新的队列的消息就行了。
还有就是RocketMQ 在消费者重平衡时是通过默认就是通过局部调整来完成的。当消费者变化时,只有受影响的消费者会重新分配消息队列,其他消费者不受影响。 (类似kafka的渐进式重平衡,但是RocketMQ默认就是这样的)
3.8 RocketMQ 如何实现延时消息?
5.0之前是基于延迟等级的方式实现延时消息,基本原理如下:
1、生产者发送消息时需要设置延迟时间级别:message.setDelayTimeLevel(3)。
2、Broker 收到消息后,会将消息写入 CommitLog。如果延迟时间大于0,会将消息存储在一个Topic队列中:SCHEDULE_TOPIC_XXXX中。同时会保存原消息对应的topic和队列的信息。SCHEDULE_TOPIC_XXXX中有18个消息队列,分别存储18个延迟等级对应的消息。
RocketMQ 在启动时,会从broker.conf中获取18个等级对应的时间,延迟等级和时间的关系会存在放到DelayLevelTable中。
3、RocketMQ启动后创建一个有 18 个核心线程的定时线程池,并开启18个定时任务,每隔100ms,从TopicSCHEDULE_TOPIC_XXXX判断18个队列里的第一个消息是否可以被投放,如果可以投放,则在投放到原本的目标Topic中。判断逻辑:存入时间+延迟时间 > 当前时间。
在RocketMQ 5.0中,采用了一种新的实现方式:基于时间轮的定时消息。时间轮是一种高效的定时器算法,能够处理大量的定时任务,并且能够在O(1)时间内找到下一个即将要执行的任务,因此能够提高消息的投递性能。
4、 RabbitMQ消息队列
【RabbitMQ】架构原理、消息丢失、重复消费、顺序消费、事务消息
4.1 RabbitMQ的架构是怎么样的?
1、Producer(生产者):生产者是消息的发送方,负责将消息发布到RabbitMQ的交换器(Exchange)。
2、VHost:是RabbitMQ中虚拟主机的概念,类似于操作系统中的命名空间,用于将RabbitMQ的资源进行隔离和分组。每个VHost拥有自己的交换器、队列、绑定和权限设置。
3、Exchange(交换器):交换器是消息的接收和路由中心,它接收来自生产者的消息,并将消息路由到一个或多个与之绑定的队列(Queue)中。
4、Queue(队列):队列是消息的存储和消费地,它保存着未被消费的消息,等待消费者(Consumer)从队列中获取并处理消息。
5、Consumer(消费者):消费者是消息的接收方,负责从队列中获取消息,并进行处理和消费。
4.2 RabbitMQ消息的发送过程
rabbitMQ一共有6种工作模式(消息分发方式),分别是简单模式、工作队列模式、发布订阅模式、路由模式、主题模式以及RPC模式。
其中最常用的是路由模式,路由模式用于实现根据消息的路由键(Routing Key)将消息路由到不同的队列中。
4.3 RabbitMQ 消息丢失问题
1、如何保障消息一定能发送到RabbitMQ?
RabbitMQ的消息最终是存储在Queue上的,而在Queue之前还要经过Exchange,那么这个过程中就有两个地方可能导致消息丢失。第一个是Producer到Exchange的过程,第二个是Exchange到Queue的过程。
上面两个可能丢失的过程,都可以利用confirm机制,注册回调来监听是否成功。
Publisher Confirm是一种机制,用于确保消息已经被Exchange成功接收和处理。一旦消息成功到达Exchange并被处理,RabbitMQ会向消息生产者发送确认信号(ACK)。
Publisher Returns机制与Publisher Confirms类似,但用于处理在消息无法路由到任何队列时的情况。当RabbitMQ在无法路由消息时将消息返回给消息生产者,但是如果能正确路由,则不会返回消息。
2、队列和交换机的持久化
在声明队列时,可以通过设置durable参数为true来创建一个持久化队列。持久化队列会在RabbitMQ服务器重启后保留,确保队列的元数据不会丢失。
在声明交换机时,也可以通过设置durable参数为true来创建一个持久化交换机。持久化交换机会在RabbitMQ服务器重启后保留,以确保交换机的元数据不会丢失。
3、持久化消息
生产者发送的消息可以通过设置消息的deliveryMode为2来创建持久化消息。持久化消息在发送到持久化队列后,将在服务器重启后保留,以确保消息不会丢失。
4、消费者确认机制
在RabbitMQ中,消费者处理消息成功后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息,这样才能确保消息不会丢失。如果消费者在处理消息中出现了异常,那么就会返回nack回执,MQ收到回执之后就会重新投递一次消息,如果消费者一直都没有返回ACK/NACK的话,那么他也会在尝试重新投递。
5、无法做到100%不丢
虽然我们通过发送者端进行异步回调、MQ进行持久化、消费者做确认机制,但是也没办法保证100%不丢,因为MQ的持久化过程其实是异步的。即使我们开了持久化,也有可能在内存暂存成功后,异步持久化之前宕机了,那么这个消息就会丢失。
4.4 RabbitMQ 消息重复消费问题
RabbitMQ的消息消费是有确认机制的,正常情况下,消费者在消费消息成功后,会发送一个确认消息,消息队列接收到之后,就会将该消息从消息队列中删除,下次也就不会再投递了。
但是如果存在网络延迟的问题,导致确认消息没有发送到消息队列,导致消息重投了,是有可能的。所以,当我们使用MQ的时候,消费者端自己也需要做好幂等控制来防止消息被重复消费。
4.5 RabbitMQ 消息顺序消费问题
根据路由键将消息路由到指定队列,队列对应多个消费者。通过 basicQos(1) 保证每个消费者一次只处理一条消息,做到顺序消费的同时,保证消费能力。
4.6 RabbitMQ 的事务消息
因为事务机制是同步的,提交一个事务之后会阻塞在那儿,但是 confirm机制是异步的,发送一个消息之后就可以发送下一个消息,RabbitMQ 接收了之后会异步回调confirm接口通知这个消息接收到了。一般在生产者这块避免数据丢失,建议使用用 confirm 机制,不要使用事务机制。
4.7 RabbitMQ 如何实现延时消息
1、死信队列
给一个消息设定TTL,但是并不消费这个消息,等他过期,过期后就会进入到死信队列,然后我们再监听死信队列的消息消费就行了。
2、RabbitMQ插件
基于死信队列的方式,是消息先会投递到一个正常队列,在TTL过期后进入死信队列。
但是基于插件的这种方式,消息并不会立即进入队列,而是先把他们保存在一个基于Erlang开发的Mnesia数据库中,然后通过一个定时器去查询需要被投递的消息,再把他们投递到x-delayed-message交换机中。
Redis
1、持久化机制
1、RDB
RDB是将Redis的内存中的数据定期保存到磁盘上,以防止数据在Redis进程异常退出或服务器断电等情况下丢失。
2、AOF
AOF是将Redis的所有写操作追加到AOF文件(Append Only File)的末尾,从而记录了Redis服务器运行期间所有修改操作的详细记录。
3、混合持久化
AOF和RDB各自有优缺点,为了让用户能够同时拥有上述两种持久化的优点, Redis 4.0 推出了 RDB-AOF 混合持久化。
4、RDB的写回策略
定期触发:Redis通过配置文件中 save 参数定义了 RDB 的自动保存条件。如果300秒内至少有10个键发生变化,则保存快照。
5、AOF的写回策略
AOF有三种数据写回策略,分别是Always,Everysec和No。
6、不能完全保证数据不丢失
即使是在AOF的always策略下,也不能保证100%不丢失数据的。
即使Redis请求立即将数据同步到磁盘,操作系统的I/O缓冲区可能会导致实际写入磁盘的操作延迟发生。如果在写入缓冲区之后,没写磁盘前,机器挂了,那么数据就丢了。
2、过期策略
Redis默认同时开启定期删除和惰性删除两种过期策略。
1、定期删除
Redis 默认每隔 100ms 就随机抽取一些设置了过期时间的 key,并检查其是否过期,如果过期才删除。
定期删除的缺点是可能导致访问延迟:当大量键同时过期并在访问时触发删除操作时,可能会导致读写操作的延迟。原因是因为Redis的主动过期定时任务也是在Redis的单线程模型中的主线程中执行的,也就是说如果出现了一批key同时过期,就需要删除大量的Key。那么因为命令执行是单线程的,所以这时候后面来的业务操作请求,就需要等这个删除命令执行完才可以处理业务请求。
2、惰性删除
当一个 key 过期时,不会立即从内存中删除,而是在访问这个 key 的时候才会触发删除操作。
3、数据结构
Redis为什么这么快:
1、基于内存
2、单线程模型
3、多路复用I/O模型
4、高效的数据结构
Redis 中支持了多种数据类型,其中比较常用的有五种:
字符串(String)
哈希(Hash)
列表(List)
集合(Set)
有序集合(Sorted Set)
另外,Redis中还支持一些高级的数据类型,如:Streams、Bitmap、Geospatial。
1、字符串
Redis本身是通过C语言实现的,但是他并没有直接使用C语言中的字符数组的方式来实现字符串,而是自己实现了一个SDS(Simple Dynamic Strings),即简单动态字符串。
C语言中,字符串是通过字符数组实现的,底层是开辟了一块连续的空间,依次存放字符串中的每一个字符。为了表示字符串的结束,他会在字符数组的最后一个字符处记录\0。
redis主要做了2点改进:
1、在字符串中增加一个表示字符串现有长度的len字段:这样在获取长度的时候就不依赖\0了,直接返回len的值就行了。
2、在字符串中增加一个表示分配给该字符数组的总长度的alloc字段:这样在做追加操作的时候,只需要判断新追加的部分的len加上已有的len是否大于alloc,如果超过就重新再申请新空间,如果没超过,就直接进行追加就行了。
2、Zset
Redis中的ZSet在实现中主要有2类:分别是ziplist(压缩列表)和skiplist(跳跃表)。
当ZSet的元素数量比较少时,Redis会采用ZipList(ListPack)来存储ZSet的数据。ZipList(ListPack)是一种紧凑的列表结构,它通过连续存储元素来节约内存空间。
当ZSet的元素数量增多时,Redis会自动将ZipList(ListPack)转换为SkipList,以保持元素的有序性和支持范围查询操作。
ZSet能支持范围查询,这是因为它的核心数据结构设计采用了跳表,而它又能O(1)的复杂度获取元素权重,这是因为它同时采用了哈希表进行索引。zset的数据结构中包含了两个成员,分别是哈希表dict和跳表zsl。
4、缓存最佳实践
【Redis】热Key/大Key问题、缓存击穿、缓存穿透、缓存雪崩、缓存与数据库一致性问题
1、解决缓存击穿、缓存穿透、缓存雪崩问题
缓存击穿:是指当某一key的缓存过期时,大并发量的请求同时访问此key,瞬间击穿缓存服务器,直接访问数据库,让数据库处于负载的情况。解决方式有2个:一个是访问数据库之前加互斥锁,保证获取锁的线程才能访问数据库。第二个是异步任务定时更新缓存,在缓存失效前进行更新。
缓存穿透:是指缓存服务器中没有缓存数据,数据库中也没有符合条件的数据,导致业务系统每次都绕过缓存服务器查询下游的数据库,缓存失去了其应有的作用。解决方式有2个:一个是缓存空值,缓存中查到的是空值而不是null,说明之前已经查过了,就不会再查数据库了。第二个是使用布隆过滤器(Bloom Filter),可以判断出哪些值是一定不存在的。
缓存雪崩:是指当大量缓存同时过期或缓存服务宕机,所有请求的都直接访问数据库,造成数据库高负载,影响性能,甚至数据库宕机。解决方式可以把不同的key过期时间设置成不同的, 并且通过定时刷新的方式更新过期时间。
2、读缓存的方案
第一步:查询缓存,如果缓存中有值,则直接返回
第二步:查询数据库
第三步:把数据库的查询结果更新到缓存中
3、更新缓存的方案
首先不要先写数据库,然后直接去更新缓存。
将数据库数据序列化成字符串等操作更复杂,不如直接删缓存。并且在并发场景下,会导致写写并发时数据不一致的问题。
其次不要先删缓存,再写数据库。
先删缓存再写数据库,会导致读写并发时数据不一致的问题。
先写数据库,再删缓存。
如果第二步删缓存失败了,会导致数据库和缓存不一致的问题。可以尝试重试,缓存删除失败的概率比较低。生产上一般使用这种方案就足够了。
可靠性更高的是使用缓存双删策略。
4、热key问题
针对热key问题,常见的有2种解决思路:
1)多级缓存:可以添加本地缓存。
2)热key拆分:将热key拆分成多节点,不同的客户端访问不同的内容。
5、大key问题
1)大key拆分。并行查询后再进行汇总。
2)转移存放在数据库中。
5、事务机制
Redis中是支持事务的,他的事务主要目的是保证多个命令执行的原子性,即要在一个原子操作中执行,不会被打断。
需要注意的是,Redis的事务是不支持回滚的。从 Redis 2.6.5 开始,服务器将会在累积命令的过程中检测到错误。然后,在执行 EXEC 期间会拒绝执行事务,并返回一个错误,同时丢弃该事务。如果事务执行过程中发生错误,Redis会继续执行剩余的命令而不是回滚整个事务。
Redis的事务和Lua之间有哪些区别?
1、前后依赖
在 Redis 的事务中,事务内的命令都是独立执行的,并且在没有执行EXEC命令之前,命令是没有被真正执行的,所以后续命令是不会也不能依赖于前一个命令的结果的。
在Lua 脚本中是可以依赖前一个命令的结果的,Lua 脚本中的多个命令是依次执行的,我们可以利用前一个命令的结果进行后续的处理。
2、原子性保证
不管是Redis的事务还是Lua,都没办法回滚。Redis的事务在执行过程中,如果有某一个命令失败了,是不影响后续命令的执行的,而Lua脚本中,如果执行过程中某个命令执行失败了,是会影响后续命令执行的。
3、交互次数
在Redis的事务执行时,每一条命令都需要和Redis服务器进行一次网络交互。
而Lua脚本则不需要,只需要一次性的把整个脚本提交给Redis即可。网络交互比事务要少。
4、流程编排
借助Lua脚本,可以实现非常丰富的各种分支流程控制,以及各种运算相关操作。而Redis的事务本身是不支持这些操作的。
6、分布式锁
用SETNX实现分布式锁的优缺点?
优点:实现简单、性能较高。
缺点:
1)锁无法续期:如果加锁方在加锁后的执行时间较长,而锁的超时时间设置的较短,可能导致锁被误释放。
2)不支持可重入:可以借助redisson封装的能力实现可重入锁。
3)无法避免死锁:如果加锁方在加锁后未能及时解锁(也未设置超时时间),且该客户端崩溃,可能导致死锁。
Redisson实现分布式锁的原理?
为了避免锁超时,Redisson中引入了看门狗的机制,在Redisson实例被关闭前,不断的延长锁的有效期。
自动续租:当一个Redisson客户端实例获取到一个分布式锁时,如果没有指定锁的超时时间,Watchdog会基于Netty的时间轮启动一个后台任务,定期向Redis发送命令,重新设置锁的过期时间,通常是锁的租约时间的1/3。。
续期时长:默认情况下,每10s钟做一次续期,续期时长是30s。
什么是RedLock,他解决了什么问题?
RedLock是Redis的作者提出的一个多节点分布式锁算法,旨在解决使用单节点Redis分布式锁可能存在的单点故障问题。RedLock是通过引入多个Redis节点来解决单点故障的问题。
在进行加锁操作时,RedLock会向每个Redis节点发送相同的命令请求,每个节点都会去竞争锁,如果至少在大多数节点上成功获取了锁,那么就认为加锁成功。反之,如果大多数节点上没有成功获取锁,则加锁失败。这样就可以避免因为某个Redis节点故障导致加锁失败的情况发生。
操作系统
1、BIO、NIO、AIO的区别?
首先要清楚用户程序(进程)要进行IO操作,一般有4个步骤,以读IO举例:
1、用户进程通过read系统调用,向内核发送读请求。
2、内核向硬件发送读指令,并等待读就绪。
3、DMA把将要读取的数据复制到指定的内核缓存区中。
4、内核将数据从内核缓存区拷贝到用户进程空间中。
耗时的地方主要有2个:一个是等待内核数据就绪(步骤2和3),另一个是将数据从内核拷贝到用户进程中(步骤4)。
BIO:
同步阻塞IO模型,从系统调用recv到将数据从内核复制到用户空间并返回,在这段时间内进程始终阻塞。即处理每一个请求,用户进程启动的线程既要等待内核数据就绪,又要等待数据从内核拷贝到用户进程。
适用场景:连接数少且固定(如几百以内),如管理后台、内部管理系统。
NIO:
同步非阻塞IO模型,调用recv之前会先调用select或poll,这两个系统调用都可以在内核准备好数据(网络数据已经到达内核了)时告知用户进程,它准备好了,这时候再调用recv时是一定有数据的。
此时用户进程再启动一个线程去进行处理,这样线程只需要将数据从内核拷贝到用户进程即可。
一个线程管理成千上万个连接(Selector单线程轮询),只在数据就绪时才分配线程,资源利用率高。
适用场景:高并发、连接数多、短连接的场景,如Web服务器(Tomcat的NIO模式)、网关、RPC框架。
AIO:
调用aio_read令内核把数据准备好,并且复制到用户进程空间后执行事先指定好的函数。这样用户的进程既不需要等待内核数据就绪,也不需要等待数据从内核拷贝到用户进程。
适用场景:高并发、长连接、IO密集型的应用,如文件服务器、图片处理服务。
1、OSI的七层网络模型有哪些?
OSI(Open System Interconnection,开放式系统互联)七层模型是计算机网络中一种通信协议的分类方式,分为以下七个层次:
1、物理层(Physical Layer):主要规定传输介质的传输方式,包括电信号、电压、光脉冲等。该层的主要协议是物理媒介相关协议,如RS232、以太网等。
2、数据链路层(Data Link Layer):在物理层上建立数据链路,对数据进行分帧、差错校验等处理,确保数据可靠地传输。该层的主要协议有点对点协议PPP(Point-to-Point Protocol)、以太网协议等。
3、网络层(Network Layer):主要解决数据在网络中的传输问题,包括寻址、路由选择等。该层的主要协议有IP协议、网关协议(ARP)、路由协议(RIP、OSPF、BGP等)等。
4、传输层(Transport Layer):提供端到端的可靠传输服务,包括数据传输控制。该层的主要协议有TCP协议、UDP协议等。
5、会话层(Session Layer):提供会话管理功能,负责建立、维护和结束会话。该层主要实现了不同计算机之间的会话控制。
6、表示层(Presentation Layer):负责数据格式的转换,确保应用层数据的格式一致。该层主要实现了数据格式的转换和数据加密解密等功能,如JPEG、MPEG等。
7、应用层(Application Layer):提供应用程序之间的交互,包括文件传输、电子邮件、远程登录等。该层的主要协议有HTTP、FTP、DNS、TELNET等。
Nacos
1、Nacos是AP的还是CP的?
Nacos在单个集群中同时支持AP和CP两种模式,可以根据具体的使用场景进行选择。
默认情况下是AP模式,可以通过修改nacos的配置文件来切换AP/CP。之所以这么设计是因为Nacos目前在业内主要有两种应用,分别是注册中心和配置中心。
对于注册中心来说,他要提供服务的注册和发现能力,如果使用一个强一致性算法,那么就会对可用性造成一定的影响。而注册中心一旦可用性不能满足了,那么就会影响所有服务的互相调用。而如果一致性没办法做到强一致性的话,最多是可能某个服务不在了,但是还会调用过去,理论上来说会失败,然后重试也是可以接受的。
对于配置中心来说,他的主要职责就是提供统一的配置,一致性是他的一个重点考量,即使损失一点可用性(晚一点推送)也是可以接受的,但是不同的机器接收到配置不一样,这个是不能接受的。
2、Nacos的服务注册和服务发现的过程是怎么样的?
1、服务注册:
服务注册发生在服务实例(如订单服务)启动时,通过Nacos客户端向Nacos Server 发送注册请求,包含服务名、IP、端口、集群名、元数据等信息。nacos Server将实例信息存入持久化存储(如内嵌数据库Derby或外置MySQL),并同步至集群 其他节点(基于Raft协议保证一致性)。
注册后,实例定期(默认5秒)向Nacos发送心跳包,维持健康状态。若15秒内无心跳,实例被标记为不健康;30秒未收到则删除实例。
2、服务发现:
服务发现指的是通过nacos来发现对应的服务都有哪些提供者。
服务发现有两种方式,一种是主动查询,首次调用前,消费者客户端向 Nacos Server 发起一次查询,获取目标服务的全量实例列表。结果缓存到本地,并订阅该服务的变更事件。后续调用时,直接读取本地缓存,根据负载均衡策略(如随机、轮询、权重)选择一个实例发起调用。
为了保证本地缓存的实时性,客户端默认每隔 10 秒主动向 Nacos Server 拉取最新服务列表,覆盖旧缓存。
另外,当 Nacos Server 检测到服务实例变化(如注册、下线、健康状态变更),会通过 UDP 或 gRPC 主动推送变更事件到订阅的客户端。客户端收到推送后立即更新本地缓存,无需等待定时拉取,实现秒级更新。
Es
1、为什么要使用ElasticSearch?和传统关系数据库(如 MySQL)有什么不同?
Elasticsearch 的核心功能是全文搜索。它对数据进行索引时会自动建立全文搜索索引,使其在搜索大量文本数据时表现优异。
MySQL 虽然也提供了基本的全文搜索功能,但其主要设计目标是处理结构化数据的存储和查询,对全文搜索的支持不如 Elasticsearch 那样强大。
ES的典型使用场景:
1、搜索引擎:电商网站的商品搜索、站内搜索、模糊查询、全文检索服务。
2、非关系型数据库:业务宽表(数据库字段太多,查询太慢,索引没有办法再做优化)
,数据库做统计查询。
3、大数据近实时分析引擎。
4、日志分析。
2、Elasticsearch 整体架构?如何优化 ElasticSearch 搜索性能?
整体架构:
1、集群中中包含多个节点,每个节点中可以有多个分片,通过分片来提高写入吞吐量。
2、通过分片的副本来提高查询吞吐量或实现高可用性。
如何优化 ElasticSearch 搜索性能?
1、合理设置分片数量:
虽然更多的分片可以提高写入吞吐量,因为可以并行写入多个分片。但是,查询大量分片可能会降低查询性能,因为每个分片都需要单独处理查询。而且分片数量过多可能会增加集群的管理开销和降低查询效率。有一个很好的经验法则:确保对于节点上已配置的每个 GB,将分片数量保持在 20 以下。
2、正确设置索引:
为每个字段指定正确的数据类型(如 text, keyword, date, integer 等),这是因为不同的数据类型有不同的存储和索引方式。需要注意的是:text 类型用于全文搜索,它会被分析(analyzed),即分解为单个词项。keyword 类型用于精确值匹配,过滤,排序和聚合。它不会被分析。
根据需要选择合适的分析器(Analyzer),对于 text 类型的字段,可以指定分析器来定义文本如何被分割和索引。对于不需要全文搜索的字段,使用 keyword 类型以避免分析开销。
3、避免深度分页:
避免深度分页,对于需要处理大量数据的情况,考虑使用 search_after。
4、使用 term 而非 match 查询文本字段:
一般来说,term 查询比 match 查询快,因为 term 查询不需要对查询内容进行分词。term 查询直接查找精确的值,索引定位效率高。
3、什么是ElasticSearch的深度分页问题?如何解决?
ES的检索机制决定了,当进行分页查询时,Elasticsearch需要先找到并处理所有位于当前页之前的记录。例如,如果你请求第10000页的数据,并且每页显示10条记录,系统需要先处理前9990条记录,然后才能获取到你请求的那10条记录。这意味着,随着页码的增加,数据库需要处理的数据量急剧增加,导致查询效率降低。
这就是ES的深度分页的问题,深度分页需要数据库在内存中维护大量的数据,并对这些数据进行排序和处理,这会消耗大量的CPU和内存资源。随着分页深度的增加,查询响应时间会显著增加。在某些情况下,这可能导致查询超时或者系统负载过重。
search_after 是 Elasticsearch 中用于实现深度分页的一种机制。与传统的分页方法(使用 from 和 size 参数)不同,search_after 允许你基于上一次查询的结果来获取下一批数据,这在处理大量数据时特别有效。在第一次查询时,需要定义一个排序规则,不需要指定 search_after 参数。在后续的查询中,使用上一次查询结果中最后一条记录的排序值。
缺点是一方面需要有一个全局唯一的字段用来排序,另外虽然一次分页查询时不需要处理先前页面中的数据,但实际需要依赖上一个页面中的查询结果。
Mybatis
1、mybatis的缓存机制是怎样的?
Mybatis的缓存机制有两种:一级缓存和二级缓存。
一级缓存,默认情况下是开启的,是SqlSession 级别的缓存。同一个sql会话中相同语句的查询可以使用缓存,不同sql会话中的查询无法使用到缓存。
二级缓存,默认是不开启的,是namespace级别的缓存,即表级别的缓存。不同sql会话中的查询可以使用到缓存。但是尽量不要使用,因为多表查询的时候,二级缓存就不太适用了,优先使用第三方缓存,比如redis,在业务层来使用缓存。
2、使用mybatis如何实现分页?
MyBatis中可以通过两种方式来实现分页:基于物理分页和基于逻辑分页。
所谓物理分页,指的是最终执行的SQL中进行分页,即SQL语句中带limit,这样SQL语句执行之后返回的内容就是分页后的结果。所谓逻辑分页,就是在SQL语句中不进行分页,照常全部查询,在查询到的结果集中,再进行分页。
1、在SQL中添加limit语句:物理分页
2、基于PageHelper分页插件实现分页:物理分页
PageHelper会在执行器的query方法执行之前,会从ThreadLocal中再获取分页参数信息,页码和页大小,然后执行分页算法,计算需要返回的数据块的起始位置和大小。最后,PageHelper会通过修改SQL语句的方式,在SQL后面动态拼接上limit语句,限定查询的数据范围,从而实现物理分页的效果。并且在查询结束后再清除ThreadLocal中的分页参数。
3、基于RowBounds实现分页:逻辑分页
RowBounds是基于逻辑分页的。在查询的时候,会先将所有符合条件的记录返回,然后再在内存中进行分页,分页的方式是根据RowBounds中指定的offset和limit进行数据保留,即抛弃掉不需要的数据再返回。尽量避免使用rowBounds来做分页。