第三章、框架,服务器,中间件等问题
3.1)springboot,spring ,springMVC,mybatis等框架
1、spring的常用注解
@Configuration 注解为例,它用来标记类可以当做一个 bean 的定义,被 Spring IOC 容器使用。另一个例子是@Bean 注解,它表示此方法将要返回一个对象,作为一个 bean 注册进 Spring 应用上下文。
@Autowired:该注解应用于依赖注入
2 、springboot常用注解
@SpringBootApplication
这个注解是springboot启动类上的一个注解,是一个组合注解,它的主要作用就是标记说明这个类是springboot的主配置类,springboot可以运行这个类里面的main()方法来启动程序此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。
@RestController
相当于是@Controller和@ResponseBody的组合注解。返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面
@Field
使用 @Field 注解时,Spring Boot 会根据注解中指定的属性名,在请求参数中查找同名的参数值,并将其自动转换为该属性的类型,然后赋值给该属性。需要注意的是,@Field 注解只能用于处理表单数据和 URL 查询参数,对于 JSON 数据,需要使用 @RequestBody 注解或其他相关注解进行处理。
public class User {
private String name;
private int age;
// getter 和 setter 方法省略
}
@RestController
public class UserController {
@PostMapping("/user")
public void createUser(@Field("name") String name, @Field("age") int age) {
User user = new User();
user.setName(name);
user.setAge(age);
// 保存用户信息到数据库
}
}
3、定时任务SpringTask
我们使用定时任务主要是处理一些定时或延迟的工作,配置好时间,让程序在指定的时间或者指定的频率去执行,使用Spring Task,Spring 3.0后提供Spring Task实现任务调度,支持按日历调度,相比Quartz功能稍简单,但是在开发基本够用,支持注解编程方式。
SpringBoot中的Schedule : 通过@EnableScheduling+@Scheduled最实现定时任务,底层使用的是Spring Task
4、mybatis里面怎么实现分页
MyBatis 实现分页主要有两种常见方式:
手动分页:在 SQL 里用
LIMIT
和OFFSET
(不同数据库语法有别)控制,如SELECT * FROM table LIMIT #{offset}, #{pageSize}
,Java 代码算好偏移量传入。分页插件:用 PageHelper 等,先添加依赖,再配置拦截器,之后在查询前调用
PageHelper.startPage(pageNum, pageSize)
开启分页,查询后结果会封装到PageInfo
。
5、说一下springboot自动装配
Spring Boot 自动装配是 Spring Boot 的核心特性之一,极大简化了 Spring 应用开发配置:
原理:基于条件注解(如
@ConditionalOnClass
、@ConditionalOnMissingBean
等),Spring Boot 会在类路径中查找特定类或 Bean,根据查找结果决定是否加载对应的自动配置类。启动流程:启动类的
@SpringBootApplication
注解包含@EnableAutoConfiguration
,该注解会触发自动装配机制。自动配置类:位于
spring-boot-autoconfigure
包,每个自动配置类针对特定功能,会根据条件创建相应的 Bean。配置文件覆盖:若默认配置不满足需求,可在
application.properties
或application.yml
里修改配置,覆盖自动装配的默认值。
6.1、springbean的生命周期
第一阶段有bean定义,先在配置文件里面用<bean>标签定义bean。
第二阶段bean的初始化,通过构造器或工厂方法创建 Bean 实例再通过反射机制为 Bean 的属性设置值,创建后使用初始化方法(init-method)进行初始化。再将 Bean 实 例 传 递 给 Bean 后 置 处 理 器
第三阶段bean的运行期,此时Bean已经被创建准备好可以使用了。
第四阶段bean的销毁,当容器关闭时, 调用 Bean 的销毁方法(destroy-method)进行销毁
6.2、bean初始化经历了什么过程
在 Spring 框架里,Bean 初始化主要经历以下过程:
实例化
通过构造函数或工厂方法创建 Bean 对象实例,就像用new
关键字创建对象一样。
属性注入
为实例化后的 Bean 对象设置属性值,比如使用@Autowired
注解注入依赖的其他 Bean。
实现 Aware 接口(可选)
如果 Bean 实现了Aware
系列接口,如BeanNameAware
、BeanFactoryAware
等,Spring 会调用相应的set
方法,将相关信息传递给 Bean。
BeanPostProcessor 前置处理
在初始化方法调用之前,Spring 会调用已注册的BeanPostProcessor
的postProcessBeforeInitialization
方法,对 Bean 进行前置处理。
初始化方法调用
若 Bean 定义了初始化方法(如通过@PostConstruct
注解、init-method
属性指定),Spring 会调用这些方法。
BeanPostProcessor 后置处理
初始化方法调用之后,Spring 会调用BeanPostProcessor
的postProcessAfterInitialization
方法,对 Bean 进行后置处理。
就绪可用
经过上述步骤,Bean 完成初始化,可在应用中使用。
7、mybatis防止SQL注入
MyBatis 防止 SQL 注入主要有以下方法:
使用预编译语句(PreparedStatement):MyBatis 默认采用预编译,使用
#{}
占位符。如SELECT * FROM users WHERE username = #{username}
,MyBatis 会将参数当作一个字符串,避免 SQL 注入风险。** 避免使用:{}` 是直接替换,易引发注入。只有在动态表名、列名等必要场景才用,且要严格校验用户输入。
严格输入校验:对用户输入数据进行长度、类型等校验,过滤非法字符。
使用插件和工具:借助一些代码扫描工具来检测潜在注入风险。
8、spring的AOP功能怎么用呢
加依赖:Maven 项目在
pom.xml
里添加spring-boot-starter-aop
依赖。建切面类:用
@Aspect
和@Component
注解,写通知方法,如@Before
前置通知,定义切入点表达式指定要拦截的方法。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
// 定义切入点和前置通知
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
System.out.println("Before method execution");
}
}
写业务类:正常写业务逻辑方法。
启用 AOP:Spring Boot 默认开启,不用额外配置。
测试:调用业务方法,就能触发切面通知。
8.1、还有项目中哪里使用了AOP
在项目里,AOP 常应用于以下场景:
日志记录:在方法执行前后记录日志,如记录方法调用时间、参数和返回值,便于问题排查和系统监控。
事务管理:通过 AOP 可以在方法执行前后自动开启和提交或回滚事务,简化事务处理代码。
权限验证:在调用敏感方法前进行权限检查,若用户无权限则阻止方法执行。
性能监控:统计方法的执行时间,找出性能瓶颈方法。
缓存处理:在方法调用前先检查缓存,若有则直接返回缓存结果,无则执行方法并将结果存入缓存。
分享
9、spring的设计模式
Spring 框架广泛使用了多种设计模式,以下是主要的几种:
单例模式
Spring 默认以单例模式创建 Bean 实例,即一个 Bean 定义在一个 Spring 容器中只有一个实例,可减少对象创建开销,节省系统资源。
场景:当系统中某个类只需要一个实例,且该实例被多个组件共享时使用。如 Spring 里的配置类、数据库连接池管理类等,避免重复创建对象浪费资源,保证全局唯一性。
工厂模式
Spring 通过
BeanFactory
和ApplicationContext
作为工厂,根据配置信息创建和管理 Bean 对象,开发者只需通过名称或类型从工厂获取所需 Bean。
场景:对象创建过程复杂,或需根据不同条件创建不同类型对象时。Spring 的
BeanFactory
和ApplicationContext
作为工厂,根据配置信息创建和管理 Bean,开发者只需从工厂获取,无需关心创建细节。
代理模式
AOP 功能基于代理模式实现。当有切面逻辑时,Spring 会为目标对象创建代理对象,在不修改目标对象代码的情况下增强其功能,有 JDK 动态代理和 CGLIB 代理两种方式。
场景:需要在不修改原有对象代码情况下增强其功能。如 AOP 中,对日志记录、事务管理、权限验证等功能,Spring 通过代理模式为目标对象创建代理,在方法前后添加额外逻辑。
模板方法模式
像
JdbcTemplate
等,封装了通用的操作流程,将可变部分留给开发者实现,减少代码重复,保证操作流程一致性。
场景:有一些通用操作流程,但部分步骤具体实现不同。像
JdbcTemplate
封装了 JDBC 操作通用步骤,将 SQL 执行、结果处理等可变部分留给开发者实现,减少代码重复。
观察者模式
应用于事件机制,
ApplicationEvent
和ApplicationListener
分别代表事件和监听器,事件发布时,相应监听器会做出响应。
场景:一个对象状态改变需要通知其他多个对象做出响应。Spring 的事件机制中,当
ApplicationEvent
发布时,ApplicationListener
能接收事件并执行相应逻辑,实现模块间解耦。
10、mybatis里xml文件返回数据类型
mybatis里xml文件想返回list〈map〉数据类型/或者list〈bean〉,select标签里需要怎么写
返回 List<Map>
若想让select
标签返回List<Map>
,只需把resultType
设为map
,MyBatis 会把每行查询结果存成一个Map
,键是列名,值是对应列的值,多个Map
组成List
。示例如下:
<select id="getUserListAsMap" resultType="map">
SELECT id, username, email FROM users
</select>
在 Java 代码中调用该方法,返回的就是List<Map<String, Object>>
类型。
返回 List<Bean>
若要返回List<Bean>
,要把resultType
设为 Bean 的全限定类名,或者使用resultMap
来做结果映射。
使用 resultType
<select id="getUserListAsBean" resultType="com.example.entity.User">
SELECT id, username, email FROM users
</select>
这里com.example.entity.User
是自定义的 Java Bean 类,MyBatis 会自动把查询结果映射到User
对象属性上,前提是列名和属性名一致。
使用 resultMap
<resultMap id="UserResultMap" type="com.example.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</resultMap>
<select id="getUserListWithResultMap" resultMap="UserResultMap">
SELECT id, username, email FROM users
</select>
resultMap
能更灵活地处理列名和属性名不一致的情况,以及复杂的映射关系。
11、bean单例的优点是什么
Bean 单例模式在 Spring 框架中有诸多优点:
节省资源:避免重复创建相同功能的对象,减少内存占用和系统开销,尤其对资源消耗大的对象(如数据库连接池)效果明显。
提高性能:单例对象只需创建一次,后续直接使用,无需重复初始化,加快系统响应速度。
数据共享:单例 Bean 可在不同组件间共享数据和状态,便于实现全局管理,如全局配置信息管理。
方便管理:单例模式使 Bean 管理更集中,Spring 容器能统一控制单例 Bean 的生命周期和依赖关系。
12、时间格式化除了用相关的插件,spring自带的怎么处理。
在 Spring 中,不借助插件进行时间格式化可通过以下两种常见方式:
方式一:使用 @DateTimeFormat
注解
在实体类的日期字段上使用该注解指定日期格式,它主要用于处理入参时的日期格式化。
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class Event {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date eventDate;
public Date getEventDate() {
return eventDate;
}
public void setEventDate(Date eventDate) {
this.eventDate = eventDate;
}
}
这样,当接收前端传来符合 yyyy-MM-dd
格式的日期字符串时,Spring 能自动将其转换为 Date
类型。
方式二:自定义 Converter
或 Formatter
自定义
Converter
:实现Converter<String, Date>
接口,将字符串转换为日期类型。
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateConverter implements Converter<String, Date> {
private static final String DATE_FORMAT = "yyyy-MM-dd";
@Override
public Date convert(String source) {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
try {
return sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}
然后在配置类中注册这个转换器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Bean
@Override
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = super.mvcConversionService();
conversionService.addConverter(new StringToDateConverter());
return conversionService;
}
}
自定义
Formatter
:实现Formatter<Date>
接口,实现parse
和print
方法,可在配置类中注册,功能与Converter
类似,只是应用场景稍有不同。
13、catch 抛到最后action层的时候,怎么处理,除了人工处之外,spring提供了什么处理方式。
在 Spring 中,除了人工处理异常,还提供了以下几种处理抛到 Action 层(通常指 Controller 层)异常的方式:
@ExceptionHandler 注解
在 Controller 类中使用@ExceptionHandler
注解定义异常处理方法,可处理该 Controller 内抛出的指定类型异常。
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
// 处理特定异常
@ExceptionHandler(NullPointerException.class)
public String handleNullPointerException(NullPointerException e) {
return "Caught NullPointerException: " + e.getMessage();
}
}
@ControllerAdvice 注解
结合@ExceptionHandler
,@ControllerAdvice
可创建全局异常处理器,处理所有 Controller 抛出的异常。
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleAllExceptions(Exception e) {
return "Global Exception: " + e.g
HandlerExceptionResolver 接口
自定义类实现HandlerExceptionResolver
接口,重写resolveException
方法,能灵活控制异常处理逻辑,在 Spring 配置中注册该处理器。
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 处理异常逻辑
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", ex.getMessage());
mav.setViewName("error");
return mav;
}
}
14、对事物委托的管理。事务的提交管理有哪些
在 Spring 中,事务委托管理主要涉及事务的开启、提交、回滚等操作,提交管理方式主要分为编程式事务管理和声明式事务管理:
编程式事务管理
开发者在代码中手动控制事务的开启、提交和回滚,灵活性高,但代码侵入性强。
使用
TransactionTemplate
:TransactionTemplate
是 Spring 提供的用于编程式事务管理的工具类。通过
TransactionTemplate
的execute
方法执行事务操作,在回调函数中定义业务逻辑,若执行成功则自动提交事务,出现异常则自动回滚。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transferMoney() {
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
try {
// 业务逻辑
// 操作成功,提交事务
return null;
} catch (Exception e) {
// 出现异常,回滚事务
status.setRollbackOnly();
return null;
}
}
});
}
}
声明式事务管理
基于 AOP 实现,通过注解或 XML 配置声明事务,减少代码侵入性,开发更方便。
使用
@Transactional
注解:在服务层方法上添加
@Transactional
注解,Spring 会自动为该方法创建事务。默认情况下,方法正常执行完毕则自动提交事务,若抛出
RuntimeException
及其子类异常则自动回滚事务。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void transferMoney() {
// 业务逻辑
}
}
在配置类上添加 @EnableTransactionManagement
注解开启声明式事务支持。
15、springboot启动有哪三种方式
Spring Boot 常见的启动方式主要有以下三种:
1. 使用 main
方法启动
原理:这是最常规的启动方式。Spring Boot 应用会有一个包含
main
方法的主类,该类使用@SpringBootApplication
注解。@SpringBootApplication
是一个组合注解,包含了自动配置、组件扫描等功能。main
方法中调用SpringApplication.run()
方法来启动 Spring Boot 应用。示例代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
使用场景:开发和调试阶段,直接在 IDE 中运行
main
方法即可启动应用,方便快捷。
2. 使用 Maven 或 Gradle 插件启动
Maven
原理:Maven 的
spring-boot-maven-plugin
插件提供了spring-boot:run
目标,它会自动下载依赖并启动 Spring Boot 应用。操作步骤:在项目根目录下,打开命令行工具,执行以下命令:
mvn spring-boot:run
Gradle
原理:Gradle 的
spring-boot-gradle-plugin
插件提供了bootRun
任务,用于启动 Spring Boot 应用。操作步骤:在项目根目录下,打开命令行工具,执行以下命令:
gradle bootRun
使用场景:适用于持续集成环境或者需要在命令行中快速启动应用的场景。
3. 打包成可执行 JAR 或 WAR 文件启动
打包成可执行 JAR
原理:使用 Maven 或 Gradle 插件将项目打包成一个包含所有依赖的可执行 JAR 文件。JAR 文件中包含了应用的代码、依赖库以及嵌入式服务器(如 Tomcat、Jetty 等)。
操作步骤:
Maven:执行
mvn clean package
命令,在target
目录下会生成一个可执行的 JAR 文件。Gradle:执行
gradle build
命令,在build/libs
目录下会生成可执行 JAR 文件。启动应用:在命令行中执行
java -jar your-application.jar
命令启动应用。
打包成 WAR 文件
原理:如果需要将应用部署到外部服务器(如 Apache Tomcat),可以将项目打包成 WAR 文件。
操作步骤:
Maven:将
packaging
设置为war
,并排除嵌入式服务器依赖,执行mvn clean package
命令生成 WAR 文件。Gradle:配置
war
插件,排除嵌入式服务器依赖,执行gradle war
命令生成 WAR 文件。然后将 WAR 文件部署到外部服务器中启动。
使用场景:适合生产环境部署,可执行 JAR 方便独立部署,WAR 文件适合部署到现有的服务器环境中。
3.2)SpringCloud 各组件Nacos、Gateway、 OpenFeign、Nginx 等
1、SpringCloud 微服务框架,用过哪些组件
Eureka组件用来注册服务管理服务,还有Feign用来做服务调用,还有现在比较新的nacos.我自己就用过eureka和nacos。
其主要涉及的组件包括:
Hystix:熔断器
Eureka:注册中心
Zuul:服务网关
Ribbon:负载均衡
Feign:服务调用
sentinel:服务限流
2、Hystix熔断器,服务降级和服务熔断
服务降级: 微服务架构中在服务器压力大的时候,对一些边缘服务进行降级(例如返回一个提前准备好的错误处理信息),让核心业务享受更多资源,能够正常运行。
雪崩效应: 微服务之间的数据交互是通过远程调用来完成的。服务A调用B,B调用服务C,当服务C的调用响应时间过长或者服务C不可用,对C的调用越来越多,然后服务C崩溃了,但是链路调用还在,对服务B的调用也在持续增多,然后服务B崩溃,随之A也崩溃,就是雪崩效应。
服务熔断: 应对微服务雪崩效应的保护机制,类似保险丝。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断的实现
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
服务熔断和服务降级的区别
①服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
②服务熔断一般是自我熔断恢复,服务降级相当于人工控制
③服务熔断是一个框架层次的处理,服务降级是业务层次的处理
3、Eureka
Eureka是spring cloud的服务注册和发现的组件
首先导入eureka的包依赖,然后在springboot的启动类加上Eureka的启动注解@EnableDiscoveryClient让这个服务能被发现 ,最后在配置文件properties里加上eureka相关的配置,比如端口配置。这样服务就可以被注册到Eureka的服务中心。我们可以通过eureka的ui去看服务的启动情况。
4、Nacos和Eureka的区别
两个用法一样,但是Eureka已经停止维护了,Nacos是更新版本的用来做服务注册和发现的组件。它的功能更完善,发送心跳次数更频繁,监测服务心跳更准确。
5、Gateway
Gateway是一个网关技术,他的作用是用来过滤一些不满足条件的请求,客户端发送到 Spring Cloud Gateway 的请求需要通过一定的匹配条件,才会发送到真正的服务节点。
具体使用是:引入Gateway相关依赖包以后,通过在yml配置文件里定义filters(过滤器)来设置过滤条件对请求进行拦截和修改,通过 Predicate (断言)来实现 Route 路由的匹配。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。
6、OpenFeign
OpenFegin 是用来进行远程服务调用的组件,服务被注册到服务中心后,通过使用OpenFeign,我们可以像调用本地方法一样来调用远程服务。
先导入OpenFeign的依赖,在启动类上使用@EnableFeignClients这样就会对有@FeignClient注解的包进行扫描,接下来在调用端新建被调用端的接口映射,这个映射上要加@FeignClient注解。
7、nginx的负载均衡高可用
我理解的nginx是用来做反向代理的:本来客户端可以直接通过HTTP协议访问应用服务器,用了nginx后客户端所有请求都到了nginx,然后nginx发送请求到服务器,并将从服务器上得到的结果返回给客户端。我们通过配置来管理这些请求应该具体发送到哪些地址。
先找liun中安装nginx服务器然后启动,在conf文件里配置监听的服务端口,地址。配置转发的地址。
为了解决负载均衡服务器的宕机,需要建立一个备份机。主服务器和备份机上都运行高可用(High Availability)监控程序,通过传送诸如“I am alive”这样的信息来监控对方的运行状况。当备份机不能在一定的时间内收到这样的信息时,它就接管主服务器的服务IP并继续提供负载均衡服务;当备份管理器又从主管理器收到“I am alive”这样的信息时,它就释放服务IP地址,这样的主服务器就开始再次提供负载均衡服务。
8、zookeeper注册中心有什么可以替换的吗
可以替换 Zookeeper 作为注册中心的有:
Eureka:由 Netflix 开源,是 Spring Cloud 早期默认注册中心,提供服务注册与发现功能,采用 AP 架构,更注重服务的可用性。
Consul:HashiCorp 公司产品,具备分布式、高可用等特点,除服务注册发现,还有健康检查、KV 存储等功能,采用 CP 架构,强调数据一致性。
Nacos:阿里巴巴开源,提供服务注册发现、配置管理功能,支持 AP 和 CP 两种模式,可根据需求灵活切换,易于上手和维护。
3.3)RabbitMQ消息队列
如何使用 MQ 解决分布式事务?
如果是传统的集中式架构在注册时,我们会做出如下操作:
1.收集用户录入信息,保存到数据库
2.向用户的手机或邮箱发送验证码等等…
实现这个功能非常简单:开启一个本地事务,往本地数据库中插入一条用户数据,发送验证码,提交事物。但是在分布式架构中,用户和发送验证码是两个独立的服务,它们都有各自的数据库,那么就不能通过本地事务保证操作的原子性。这时我们就需要用到 MQ(消息队列)来为我们实现这个需求。在用户进行注册操作的时候,我们为该操作创建一条消息,当用户信息保存成功时,把这条消息发送到消息队列。验证码系统会监听消息,一旦接受到消息,就会给该用户发送验证码。
1、RabbitMQ消息队列是什么有什么用
RabbitMQ是什么: 是消息队列,主要目的是管理消息的传输和提升效率,整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。mq有三大优点:解耦,异步,削峰.
生产者和消费者 : 生产消息的一方(邮件投递者),消费消息的一方(邮件收件人)
消息:
由消息头和 消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routingkey(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)
Exchange(交换器):
在 RabbitMQ 中,消息并不是直接被投递到消息队列中的,中间还必须经过 Exchange(交换器) 这一层,Exchange(交换器) 会把我们的消息分配到对应的消息队列中。
Exchange(交换器) 有 4 种类型,不同的类型对应着不同的路由策略:direct(默认),fanout, topic, 和 headers。
BindingKey和RoutingKey
BindingKey (绑定)就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。
生产者将消息发给交换器的时候,一般会指定一个 RoutingKey(路由键),用来指定这个消息的路由规则,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候。
Queue(消息队列)
Queue(消息队列) 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Broker(消息中间件的服务节点)
一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。
2、重复消费问题
例如:前端重复提交
用户注册,用户创建商品等操作,前端都会提交一些数据给后台服务,后台需要根据用户提交的数据在数据库中创建记录。如果用户不小心多点了几次,后端收到了好几次提交,这时就会在数据库中重复创建了多条记录。这就是接口没有幂等性带来的 bug。
例如:消息重复消费
在使用消息中间件来处理消息队列,且手动 ack 确认消息被正常消费时。如果消费者突然断开连接,那么已经执行了一半的消息会重新放回队列。
当消息被其他消费者重新消费时,如果没有幂等性,就会导致消息重复消费时结果异常,如数据库重复数据,数据库数据冲突,资源重复等。
1、消息去重
消息去重是一种常用的解决重复消费问题的方法。可以通过给每条消息设置唯一的消息 ID,并将已消费的消息 ID 进行保存,当接收到重复的消息时,先判断该消息 ID 是否已经消费过,若已消费过则直接丢弃该消息。为了保证消息 ID 的唯一性,可以使用 UUID 等方式生成
2、幂等性处理
在消息消费者端,可以通过实现幂等性处理来解决重复消费问题当接收到重复的消息时,先判断该消息是否已经被处理过,若已经处理过则直接跳过该消息。幂等性处理可以通过在数据库中添加唯一索引、使用乐观锁等方式来实现。
3、消费者限流
消费者限流是一种控制消费速度的方法,可以有效地减少重复消费的可能性。在 RabbitMQ 中,可以通过设置消费者的 prefetchcount 消费速度。
3、幂等性问题的思考和总结,防重、幂等
幂等性问题的思考和总结,防重、幂等
幂等性和幂等性接口:
幂等性:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。幂等性接口是指可以使用相同参数重复执行,并能获得相同结果的接口。
防重和幂等的区别:
防重设计主要为了避免产生重复数据,对接口返回没有太多要求。
而幂等设计除了避免产生重复数据之外,还要求每次请求都返回一样的结果。
可以使用 写入 Redis 来保证,因为 Redis 的 key 和 value 就是天然支持幂等的。当然还有使用 数据库插入法 ,基于数据库的唯一键来保证重复数据不会被插入多条。
4、消息不丢失
①生产者到rabbitMQ:保障投递者投递消息可靠
1、Confirm模式来实现异步发布确认,即确认消息是否成功,如果发送确认失败,则重新发送消息,如果补偿也不成功,则需要把丢失的消息存储到数据库(缓存中),推送人工处理。
2、事务机制和 Confirm 机制,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。
②rabbit本身可靠性
1、把消息持久化下来
2、服务集群搭建
③可能消费真正消费时消息丢失
1、把自动确认机制改为手动确认,保障消息真正消费完再进行消息的确认删除
2、basicAck 机制、死信队列、消息补偿机制
5、高可用解决方案
RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。
单机模式
Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式。
普通集群模式
意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。
镜像集群模式
这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了
这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据
6、什么是死信队列,导致的死信的几种原因
当消息在一个队列中变成死信之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX(Dead-Letter-Exchange)死信交换器,绑定 DLX 的队列就称之为死信队列。
死信的几种原因
①消息被拒(Basic.Reject /Basic.Nack) 且 requeue = false。
②消息 TTL 过期。
③队列满了,无法再添加
7、什么是延迟队列?RabbitMQ 怎么实现延迟队列
延迟队列指的是存储对应的延迟消息,消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
实现延迟消息,一般有两种方式:
通过 RabbitMQ 本身队列的特性来实现,需要使用 RabbitMQ 的死信交换机(Exchange)和消息的存活时间 TTL(Time To Live)。在 RabbitMQ 3.5.7 及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时,插件依赖 Erlang/OPT 18.0 及以上。
8、什么是优先级队列?
RabbitMQ 自 V3.5.0 有优先级队列实现,优先级高的队列会先被消费。可以通过x-max-priority参数来实现优先级队列。不过,当消费速度大于生产速度且 Broker 没有堆积的情况下,优先级显得没有意义。
9、RabbitMQ 有哪些工作模式?
①简单模式:一个生产者一个消费者绑定一个队列
②work 工作模式:一个生产者多个消费者消费同一个队列
③pub/sub 发布订阅模式:一个生产者,一个交换机,多个队列,多个消息消费者。
④Routing 路由模式:一个生产者,一个交换机,多个队列,多个消费者。
⑤Topic 主题模式:一个消息生产者,一个交换机,多个队列,多个消息消费者。此时的自己唯一的Routekey,不是一个确定值,像我们熟悉的正则表达式对应的匹配规则增加topic 通配符 * #
10、RabbitMQ 消息怎么传输?
RabbitMQ 使用信道的方式来传输数据。信道(Channel)是生产者、消费者与 RabbitMQ 通信的渠道,信道是建立在 TCP 链接上的虚拟链接。 RabbitMQ 在一条 TCP 链接上建立成百上千个信道来达到多个线程处理,这个 TCP 被多个线程共享,每个信道在 RabbitMQ 都有唯一的 ID,保证了信道私有性,每个信道对应一个线程使用。
11、如何保证 RabbitMQ 消息的顺序性?
拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者),就是多一些 queue (消息队列)而已,确实是麻烦点;或者就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
12、如何解决消息队列的延时以及过期失效问题?
RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
13、MQ在项目中如何应用的?
在项目中主要是完成系统之间通信,并且将系统之间的调用进行解耦。例如在添加、修改商品信息后,需要将商品信息同步到索引库、同步缓存中的数据以及生成静态页面一系列操作。在此场景下就可以使用mq。一旦后台对商品信息进行修改后,就向mq发送一条消息,然后通过mq将消息发送给消息的消费端,消费端接收到消息可以进行相应的业务处理。
应用场景:详情页静态化、商品上下架申请、调价申请等。
14、rabbitMQ手动确认是同步还是异步?
RabbitMQ 手动确认可以是同步也可以是异步。同步确认通过channel.basicAck
在消息处理完直接调用;异步确认借助ConfirmCallback
和ReturnCallback
,在消息确认时回调处理。
15、mq消息持久化的时候消息丢失了怎么办
若 MQ 消息持久化时丢失,可采取以下措施:
生产者端:开启事务机制或发送方确认模式(publisher confirms),确保消息成功抵达 MQ,若未确认则重发。
MQ 端:保证队列和消息都持久化配置正确;避免 MQ 异常,做好集群和高可用部署,如 RabbitMQ 的镜像队列。
消费者端:使用手动确认机制,处理完消息再确认,避免处理前因异常确认导致消息丢失;若消费失败,可根据情况重试或放入死信队列分析。
3.4)XXL-JOB,MongoDB,Docker 等
1、定时任务,XXL-JOB
定时任务:在固定时间点执行,或者周期性的去执行某个任务,比如:每天晚上24点做数据汇总,定时发送短信等。
XXL-JOB是一个分布式任务调度平台,有个ADMIN的界面可以管理任务,对任务状态进行修改、启动/停止/终止任务,即时生效。他有两个模块一个是调度中心一个是执行器。
调度中心负责管理调度信息,出调度请求,执行器接收调度中心的请求并执行任务。如果有多个执行器可以采用轮询,随机,故障转移等“路由策略”进行调度。
具体使用的话,把调度中心的jar包上传到服务器,把自带的数据库文件导入生成生成数据库和表,修改一下配置文件然后用命令启动。这时候我们就可以访问调度中心的UI界面了。然后配置执行器在项目里先引入核心的jar包,修改配置文件里的调度中心地址,就可以使用@xxljo注解,调度中心就会显示我们的任务了。
故障转移(FAILOVER)的”路由策略”,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。
2.1、MongoDB
为什么用MongoDB不用MySQL
MongoDB是面向文档的非关系型数据库,索引使用B树的数据结构,数据以文件的形式存储在磁盘上。并且MongoDB会将频繁访问的数据缓存在内存中,以提高查询和更新的速度。
MongoDB采用面向文档的存储方式,每个文档都是一个BSON对象,可以包含多个字段,每个字段都有自己的数据类型。
适合存储大数据。它可以处理海量数据,并且可以自动分割数据到多个节点上。
内存映射
MongoDB使用内存映射技术,将磁盘上的数据映射到内存中,这样可以避免频繁的磁盘访问,提高读取速度。此外,MongoDB还使用了写时复制技术,每个客户端都有一个独立的写副本,这也加快了写入速度。
分布式架构
MongoDB是一种分布式数据库,它可以将数据分布在多个节点上。这样可以减轻单个节点的负载,提高整个系统的处理能力。此外,MongoDB还支持副本集和分片技术,可以提高系统的可用性和可扩展性。
MongoDB怎么使用
连接到MongoDBJava连接Mongodb数据库文章
创建数据库和集合
# 创建集合
collection = db["history"]
# 创建索引以加快查询速度
collection.create_index("timestamp")
插入历史消息数据
# 插入历史消息
message = {
"timestamp": "2022-01-01 10:00:00",
"user": "Alice",
"content": "Hello, World!"
}
collection.insert_one(message)
查询历史消息,查询操作来检索这些数据
# 查询历史消息
query = {"user": "Alice"}
messages = collection.find(query)
for message in messages:
print(message)
更新历史消息
# 更新历史消息
query = {"user": "Alice"}
new_values = {"$set": {"content": "Hi, World!"}}
collection.update_many(query, new_values)
删除历史消息
# 删除历史消息
query = {"user": "Alice"}
collection.delete_many(query)
2.2、springboot整合mangoDB
以下是 Spring Boot 整合 MongoDB 的简单步骤:
1. 添加依赖
在pom.xml
中添加 Spring Data MongoDB 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2. 配置连接信息
在application.properties
或application.yml
中配置 MongoDB 连接信息。以application.properties
为例:
spring.data.mongodb.uri=mongodb://localhost:27017/your_database_name
3. 创建实体类
创建与 MongoDB 文档对应的 Java 实体类,使用@Document
注解指定集合名称。
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "users")
public class User {
@Id
private String id;
private String name;
private int age;
// 构造方法、Getter和Setter方法
}
创建继承自MongoRepository
的接口,用于对 MongoDB 进行数据操作。
import org.springframework.data.mongodb.repository.MongoRepository;
public interface UserRepository extends MongoRepository<User, String> {
}
在服务类或控制器中注入UserRepository
并使用它进行数据操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User saveUser(User user) {
return userRepository.save(user);
}
}
通过以上步骤,就完成了 Spring Boot 与 MongoDB 的基本整合。
3、Docker介绍
Docker 是一个用于开发、部署和运行应用程序的开源平台,以下是其简单介绍:
核心概念
镜像(Image):轻量级、独立的可执行软件包,包含运行应用所需的一切,如代码、运行时环境、库和配置文件等。
容器(Container):镜像的运行实例,相互隔离,每个容器有自己独立的文件系统、进程空间等,可高效运行应用。
仓库(Repository):集中存储和分发镜像的地方,分为公共仓库(如 Docker Hub)和私有仓库。
主要优势
轻量级和高效:与传统虚拟机相比,容器共享操作系统内核,启动快、资源占用少。
环境一致性:确保应用在开发、测试和生产环境中的一致性,避免 “在我机器上能运行” 的问题。
可移植性:可在不同操作系统和云平台上运行,方便迁移和部署。
隔离性:容器之间相互隔离,一个容器的故障不会影响其他容器。
应用场景
微服务架构:每个微服务可打包成独立容器,便于开发、部署和扩展。
持续集成与持续部署(CI/CD):加速应用的构建、测试和部署流程。
4、ElasticSearch
ElasticSearch 是一个开源的分布式搜索和分析引擎,以下为简单介绍:
核心特点
分布式:数据可分散存储在多个节点,具备水平扩展能力,能处理 PB 级数据。
全文搜索:提供强大的全文搜索功能,可对大量文本数据进行快速、精准检索。
实时性:数据写入后可近乎实时被搜索到,响应速度快。
多数据类型支持:支持结构化、非结构化、地理空间等多种数据类型的处理。
主要组件
索引(Index):类似数据库中的数据库,是文档的集合。
类型(Type):早期版本用于逻辑分组文档,7.x 开始逐步弃用。
文档(Document):索引中存储的基本数据单元,以 JSON 格式表示。
分片(Shard):索引可拆分为多个分片,分布在不同节点,提高性能和可用性。
副本(Replica):分片的复制,增强数据冗余和系统容错能力。
应用场景
搜索引擎:构建网站、电商平台等的搜索功能。
日志分析:收集、存储和分析系统日志,帮助排查问题。
数据可视化:结合 Kibana 进行数据可视化展示,辅助决策。
RocketMQ 面试常见问题及答案:
基础概念类
1. 请简要介绍一下 RocketMQ 是什么?
RocketMQ 是一款开源的分布式消息中间件,由阿里巴巴开发并捐赠给 Apache 基金会。它具有高吞吐量、高可用性、可伸缩性和容错性等特点,广泛应用于异步通信、解耦、流量削峰填谷等场景。RocketMQ 采用了分布式架构,由 NameServer、Broker、Producer、Consumer 等组件构成,支持多种消息模型,如同步消息、异步消息、单向消息和顺序消息等。
2. RocketMQ 有哪些核心组件,它们的作用分别是什么?
NameServer:作为 RocketMQ 的命名服务,负责存储 Broker 的元数据信息,如 Broker 的地址、Topic 的路由信息等。它是一个无状态的节点,多个 NameServer 之间相互独立,Broker 会定期向 NameServer 注册自己的信息。
Broker:负责消息的存储、转发和管理。它接收 Producer 发送的消息,并将其存储在磁盘上,同时为 Consumer 提供消息拉取服务。Broker 可以分为 Master 和 Slave 节点,Master 节点支持读写操作,Slave 节点主要用于数据备份和读操作。
Producer:消息的生产者,负责创建和发送消息到 Broker。它可以根据业务需求选择不同的消息发送方式,如同步发送、异步发送和单向发送。
Consumer:消息的消费者,负责从 Broker 拉取消息并进行处理。Consumer 可以分为 PushConsumer 和 PullConsumer,PushConsumer 由 Broker 主动推送消息,PullConsumer 由 Consumer 主动拉取消息。
3. 说说 RocketMQ 的消息模型有哪些?
同步消息:Producer 发送消息后,会等待 Broker 返回发送结果,只有收到成功响应后才会继续执行后续操作。这种方式可靠性高,但会影响发送性能。
异步消息:Producer 发送消息后,不会等待 Broker 的响应,而是继续执行后续操作。当 Broker 处理完消息后,会回调 Producer 的回调函数,告知发送结果。这种方式可以提高发送性能,适用于对响应时间要求不高的场景。
单向消息:Producer 发送消息后,不等待 Broker 的响应,也不关心消息是否发送成功。这种方式发送速度最快,但可靠性最低,适用于对消息丢失不敏感的场景。
顺序消息:保证消息的顺序性,分为全局顺序消息和分区顺序消息。全局顺序消息是指所有消息按照严格的顺序发送和消费;分区顺序消息是指在一个分区内的消息按照顺序发送和消费。
性能与调优类
1. 如何提高 RocketMQ 的消息发送性能?
批量发送消息:将多个小消息合并成一个大消息进行发送,减少网络开销。
异步发送消息:使用异步发送方式,避免等待 Broker 的响应,提高发送效率。
合理配置 Producer 参数:如调整发送线程池大小、重试次数等。
水平扩展 Broker 集群:增加 Broker 节点数量,提高消息处理能力。
2. 当 RocketMQ 的消费出现积压时,应该如何处理?
增加 Consumer 实例:通过水平扩展 Consumer 的数量,提高消息消费速度。
提高单个 Consumer 的消费能力:优化 Consumer 的业务逻辑,减少处理时间;调整 Consumer 的拉取参数,如批量拉取数量。
检查消息生产速度:如果消息生产速度过快,可能需要对业务进行限流,避免持续积压。
分析消息积压原因:查看是否存在消费异常,如代码逻辑错误、数据库连接问题等,并及时修复。
高可用与容错类
1. RocketMQ 是如何保证高可用性的?
Broker 主从复制:Broker 采用主从架构,Master 节点负责读写操作,Slave 节点实时从 Master 节点同步数据。当 Master 节点出现故障时,可以将 Slave 节点切换为 Master 节点,保证服务的可用性。
NameServer 集群:多个 NameServer 节点相互独立,Broker 会定期向所有 NameServer 注册自己的信息。当某个 NameServer 节点出现故障时,其他 NameServer 节点仍然可以正常提供服务。
消息重试机制:Producer 在发送消息失败时,会进行重试;Consumer 在消费消息失败时,也会进行重试,确保消息不会丢失。
2. 请描述一下 RocketMQ 的故障转移机制。
当 Broker 的 Master 节点出现故障时,RocketMQ 会自动进行故障转移。具体过程如下:
检测故障:Broker 会定期向 NameServer 发送心跳信息,NameServer 会根据心跳信息判断 Broker 的健康状态。当发现某个 Broker 的 Master 节点长时间没有发送心跳信息时,NameServer 会认为该节点出现故障。
切换节点:NameServer 会将故障节点的信息从路由表中移除,并通知 Producer 和 Consumer。Producer 和 Consumer 会根据新的路由信息,将消息发送到其他可用的 Broker 节点或从其他可用的 Broker 节点拉取消息。
数据恢复:当故障节点恢复后,会自动从其他 Broker 节点同步数据,保证数据的一致性。
运维与监控类
1. 如何监控 RocketMQ 的运行状态?
RocketMQ 自带监控工具:RocketMQ 提供了一些命令行工具和管理控制台,可以查看 Broker 的状态、Topic 的信息、消息的堆积情况等。
第三方监控系统:可以使用 Prometheus、Grafana 等第三方监控系统,通过配置相应的监控指标,对 RocketMQ 进行实时监控和可视化展示。
日志监控:通过查看 RocketMQ 的日志文件,及时发现系统中的异常信息和错误日志。
2. 在生产环境中,如何进行 RocketMQ 的容量规划?
预估消息量:根据业务需求和历史数据,预估系统的消息生产和消费速率,以及消息的存储周期。
选择合适的硬件配置:根据预估的消息量,选择合适的服务器硬件配置,包括 CPU、内存、磁盘等。
规划 Broker 集群规模:根据消息量和性能要求,确定 Broker 节点的数量和分布,以及 Master 和 Slave 节点的比例。
考虑网络带宽:确保网络带宽能够满足消息的传输需求,避免因网络瓶颈影响系统性能。
Elasticsearch 面试常见问题及答案
基础概念
1.Elasticsearch 的倒排索引是什么,有什么优势?
倒排索引是一种索引结构,与正向索引相反。在正向索引中,通过文档 ID 查找文档内容;而倒排索引以词为中心,记录每个词在哪些文档中出现以及出现的位置等信息。优势在于能极大提高搜索效率,比如在海量文本中搜索特定关键词,倒排索引可快速定位包含该关键词的文档,避免全量文档扫描。
2.Elasticsearch 中的分片和副本是什么,有什么作用?
分片是将一个索引拆分成多个较小的部分,分布在不同节点存储,可提升索引的存储和处理能力,实现水平扩展。副本是分片的拷贝,用于数据冗余和高可用性,当主分片所在节点故障时,副本分片可提供服务,同时也能分担读请求,提高系统整体的读性能。
索引与搜索
1.如何在 Elasticsearch 中创建索引并设置合适的映射?
使用PUT请求创建索引,例如PUT /my_index。设置映射时,指定每个字段的数据类型、分词器等属性。对于字符串类型,若要进行全文搜索,需选择合适分词器,如中文可选用ik_max_word分词器。示例创建索引及映射的请求:
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "float"
}
}
}
}
2.解释一下 Elasticsearch 中的 Query DSL,如何使用它进行复杂查询?
Query DSL(Domain Specific Language)是 Elasticsearch 用于构建查询的特定语法。通过它可以组合各种查询条件,如match查询用于全文搜索,term查询用于精确匹配,bool查询用于组合多个查询条件。例如,查询标题包含 “苹果” 且价格大于 100 的文档:
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "苹果"
}
},
{
"range": {
"price": {
"gt": 100
}
}
}
]
}
}
}
集群管理
1.如何监控 Elasticsearch 集群的健康状态?
使用_cluster/health API,发送GET请求,如GET /_cluster/health。返回结果包含集群状态(green、yellow、red),green表示所有主分片和副本分片都正常;yellow表示所有主分片可用,但部分副本分片不可用;red表示存在主分片不可用,集群部分功能不可用。还可查看分片数量、节点数量等信息,实时监控集群健康。
2.在 Elasticsearch 集群中,如何添加或删除节点?
添加节点时,确保新节点安装了相同版本的 Elasticsearch,配置好elasticsearch.yml文件,主要是集群名称和节点名称,以及与其他节点通信的网络地址等参数。启动新节点后,它会自动发现集群并加入。删除节点时,先将该节点上的分片迁移到其他节点,可使用_cluster/reroute API 手动迁移,或等待集群自动平衡。节点分片迁移完成后,停止该节点服务即可从集群中删除。
性能优化
1.Elasticsearch 写入性能优化有哪些方法?
批量写入数据,使用bulk API 减少请求次数;合理设置索引的refresh_interval参数,增大该值可减少索引刷新频率,提高写入性能,但会增加数据可见延迟;调整index.translog.durability参数,设置为async可提高写入性能,但可能存在数据丢失风险;优化硬件配置,使用高速磁盘,提高 I/O 性能。
2.如何避免 Elasticsearch 索引碎片化?
避免频繁创建和删除索引,可通过delete_by_query代替直接删除索引;定期对索引进行优化合并,使用_forcemerge API,设置合适的max_num_segments参数,将多个小的段合并成大段,减少索引碎片化;合理规划索引的分片数量,避免分片过多或过少。
MongoDB 面试常见问题及答案
基础概念
1.MongoDB 是什么,与传统关系型数据库有何区别?
MongoDB 是一个面向文档的非关系型数据库,以 BSON(Binary JSON)格式存储数据。与传统关系型数据库相比,区别在于:
数据模型:关系型数据库使用表格结构,数据以行和列存储;MongoDB 采用文档模型,数据以文档形式存储,文档类似 JSON 对象,具有灵活的结构,不同文档的字段可以不同。
查询语言:关系型数据库使用 SQL 查询;MongoDB 有自己的查询语法,更适合处理非结构化和半结构化数据。
事务支持:关系型数据库通常支持强事务,能保证 ACID 特性;MongoDB 在副本集和分片集群中,对于单文档操作具有原子性,多文档事务支持在一定版本后逐步完善,但与传统关系型数据库的事务支持仍有差异。
2.MongoDB 中的文档、集合和数据库分别是什么概念?
文档:是 MongoDB 中数据的基本单元,由键值对组成,类似 JSON 对象,是一种自包含的数据结构。例如{"name": "John", "age": 30, "city": "New York"}就是一个文档。
集合:是一组文档的容器,相当于关系型数据库中的表。集合中的文档不需要有相同的结构,可根据业务需求灵活存储不同格式的文档。
数据库:是集合的逻辑分组,一个 MongoDB 实例可以包含多个数据库,每个数据库都有自己的权限控制和数据存储。
数据操作
1.如何在 MongoDB 中插入、更新和删除文档?
插入文档:使用insertOne或insertMany方法。insertOne用于插入单个文档,例如:
db.users.insertOne({ "name": "Alice", "age": 25 });
insertMany用于插入多个文档,如:
db.users.insertMany([
{ "name": "Bob", "age": 30 },
{ "name": "Charlie", "age": 35 }
]);
更新文档:使用updateOne、updateMany或replaceOne方法。updateOne用于更新单个文档,例如将name为Alice的文档的age更新为 26:
db.users.updateOne(
{ "name": "Alice" },
{ $set: { "age": 26 } }
);
updateMany用于更新多个匹配的文档,replaceOne则是替换整个文档。
删除文档:使用deleteOne或deleteMany方法。deleteOne删除单个匹配的文档,如删除name为Bob的文档:
db.users.deleteOne({ "name": "Bob" });
deleteMany删除所有匹配的文档,如删除age大于 30 的所有文档:
db.users.deleteMany({ "age": { $gt: 30 } });
2.如何在 MongoDB 中进行复杂查询,例如条件查询、排序和分页?
条件查询:使用find方法并结合查询操作符。例如查询age大于 25 的所有文档:
db.users.find({ "age": { $gt: 25 } });
常见的查询操作符还有$lt(小于)、$gte(大于等于)、$lte(小于等于)、$ne(不等于)、$in(在指定数组内)等。
排序:使用sort方法,1 表示升序,-1 表示降序。例如按age升序排序:
db.users.find().sort({ "age": 1 });
分页:使用skip和limit方法。skip用于跳过指定数量的文档,limit用于限制返回的文档数量。例如查询第 11 到 20 条文档(假设每页 10 条):
db.users.find().skip(10).limit(10);
索引优化
1.MongoDB 索引有哪些类型,如何创建索引?
MongoDB 索引类型包括:
单字段索引:基于单个字段创建的索引,可加速对该字段的查询。
复合索引:基于多个字段创建的索引,复合索引中字段的顺序很重要,会影响查询性能。
多键索引:用于对数组字段创建索引,可对数组中的每个元素建立索引。
文本索引:用于全文搜索,可对字符串字段进行高效的文本搜索。
创建索引使用createIndex方法,例如为users集合的name字段创建单字段索引:
db.users.createIndex({ "name": 1 });
为name和age字段创建复合索引:
db.users.createIndex({ "name": 1, "age": 1 });
2.如何分析 MongoDB 查询的性能,优化查询?
使用explain方法分析查询性能,它会返回查询执行计划,包括查询使用的索引、扫描的文档数量、查询耗时等信息。例如:
db.users.find({ "age": { $gt: 25 } }).explain("executionStats");
根据explain的结果优化查询:
创建合适的索引:如果查询没有使用索引或使用了低效索引,根据查询条件创建或调整索引。
避免全表扫描:尽量使用索引覆盖查询,减少扫描的文档数量。
优化查询语句:简化复杂的查询逻辑,避免不必要的操作符和子查询。
集群管理
1.MongoDB 副本集是什么,有什么作用?
MongoDB 副本集是由多个 MongoDB 实例组成的集群,其中一个是主节点(Primary),其余是从节点(Secondary)。主节点负责处理所有写操作,从节点复制主节点的数据。作用包括:
数据冗余:从节点复制主节点的数据,当主节点故障时,从节点可以接替成为新的主节点,保证数据的可用性和持久性。
读扩展:读操作可以分发到从节点,提高系统的读性能,尤其是在高并发读的场景下。
2.MongoDB 分片集群是如何工作的,在什么情况下需要使用分片集群?
MongoDB 分片集群由分片(Shard)、配置服务器(Config Server)和路由服务器(MongoS)组成。分片用于存储数据,配置服务器存储集群的元数据(如数据分布信息),路由服务器负责接收客户端请求并将其路由到正确的分片。
当数据量过大,单个 MongoDB 实例无法存储所有数据,或者读写负载过高,单个实例无法承受时,需要使用分片集群。分片集群通过将数据分散存储在多个分片上,实现水平扩展,提高存储和处理能力。例如电商系统中,随着订单数据的不断增长,使用分片集群可以有效管理海量数据和应对高并发读写。
评论区