✍个人博客:Pandaconda-CSDN博客
📣专栏地址:https://blog.csdn.net/newin2020/category_12903849.html
📚专栏简介:在这个专栏中,我将会分享后端开发面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
1. 简述分布式事务及其常见解决方案
概念
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上的事务。简单来说,就是一次大的操作由多个小操作组成,这些小操作分布在不同的服务器上,且它们必须保证要么全部成功,要么全部失败。例如电商系统中,创建订单、扣减库存、更新用户积分等操作可能分布在不同服务中,需要保证这些操作在一个事务中完成。
常见解决方案
-
两阶段提交(2PC)
-
原理:分为准备阶段和提交阶段。协调者向所有参与者发送准备请求,参与者执行事务操作并反馈是否可以提交;如果所有参与者都反馈可以提交,协调者再发送提交请求,参与者正式提交事务;若有参与者反馈不能提交,协调者则发送回滚请求。
-
优点:实现简单,保证强一致性。
-
缺点:存在单点故障问题(协调者故障会导致整个事务无法完成),性能较低,因为存在较长的阻塞时间。
-
-
三阶段提交(3PC)
-
原理:在两阶段提交基础上增加了一个预询问阶段,缓解了部分阻塞问题。协调者先询问参与者是否有能力处理事务,得到肯定答复后再进入准备阶段和提交阶段。
-
优点:减少了参与者的阻塞时间,一定程度上提高了系统的并发性能。
-
缺点:仍然没有完全解决单点故障问题,且实现复杂度较高。
-
-
TCC(Try - Confirm - Cancel)
-
原理:将事务操作分为三个步骤。Try 阶段进行资源的预留和检查;Confirm 阶段对 Try 阶段预留的资源进行确认提交;Cancel 阶段如果 Try 阶段失败或其他原因需要回滚,对预留的资源进行释放。
-
优点:无需数据库的事务支持,适用于复杂业务场景,性能较高。
-
缺点:开发成本高,需要开发者自己实现 Try、Confirm 和 Cancel 逻辑。
-
-
消息最终一致性方案
-
原理:基于消息队列实现。事务操作分为业务操作和消息发送,业务操作完成后发送消息到消息队列,其他服务从队列中消费消息并执行相应操作。如果消息消费失败,通过重试机制保证最终一致性。
-
优点:实现简单,对业务侵入性小,性能较高。
-
缺点:只能保证最终一致性,在一段时间内可能存在数据不一致的情况。
-
2. 在 Node.js 中,如何优化 Express 应用的性能?
路由优化
- 路由模块化:将不同功能的路由拆分成独立的模块,便于管理和维护,同时也能提高代码的可读性。例如:
// routes/user.js
const express = require('express');
const router = express.Router();router.get('/', (req, res) => {res.send('User list');
});module.exports = router;// app.js
const express = require('express');
const app = express();
const userRoutes = require('./routes/user');app.use('/users', userRoutes);app.listen(3000, () => {console.log('Server is running on port 3000');
});
- 路由顺序优化:将常用的路由放在前面,减少不必要的路由匹配,提高路由匹配效率。
- 中间件优化
- 减少不必要的中间件:移除项目中未使用或不必要的中间件,避免增加额外的处理开销。
- 异步中间件优化:对于异步中间件,使用 async/await 或 .then() 方法正确处理异步操作,避免阻塞事件循环。
app.use(async (req, res, next) => {try {// 异步操作const data = await someAsyncFunction();req.data = data;next();} catch (error) {next(error);}
});
- 缓存机制
- 使用内存缓存:对于一些不经常变化的数据,可以使用内存缓存(如 node - cache)来减少数据库查询次数。
const NodeCache = require('node - cache');
const myCache = new NodeCache();app.get('/data', (req, res) => {const cachedData = myCache.get('data');if (cachedData) {res.send(cachedData);} else {// 从数据库获取数据const data = getDataFromDatabase();myCache.set('data', data);res.send(data);}
});
- CDN 缓存:对于静态资源(如 CSS、JavaScript、图片等),使用 CDN 进行缓存和分发,减轻服务器压力,提高资源加载速度。
- 数据库优化
- 数据库连接池:使用数据库连接池(如 mysql - pool 或 mongoose 的连接池),减少数据库连接的创建和销毁开销,提高数据库访问性能。
- 索引优化:在数据库中合理创建索引,加快查询速度。例如,在经常用于查询条件的字段上创建索引。
3. 在 Spring 框架中,如何实现 AOP(面向切面编程)?
在 Spring 框架中,实现 AOP 主要有以下步骤:
- 添加依赖
如果使用 Maven,在 pom.xml 中添加 Spring AOP 和 AspectJ 的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring - boot - starter - aop</artifactId>
</dependency>
- 定义切面类
使用 @Aspect 注解将一个类标记为切面类,在切面类中定义通知(Advice)和切点(Pointcut)。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {// 定义切点@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}// 前置通知@Before("serviceMethods()")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("Before method: " + joinPoint.getSignature().getName());}// 后置通知@After("serviceMethods()")public void afterAdvice(JoinPoint joinPoint) {System.out.println("After method: " + joinPoint.getSignature().getName());}
}
- 配置 AOP
如果使用 Spring Boot,默认会自动配置 AOP。如果是传统的 Spring 项目,需要在配置文件中开启 AOP 自动代理:
<aop:aspectj - autoproxy/>
或者使用 Java 配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy
public class AppConfig {// 其他配置
}
- 定义目标类
创建一个普通的服务类作为目标类,AOP 会对该类的方法进行增强。
import org.springframework.stereotype.Service;@Service
public class UserService {public void addUser() {System.out.println("Adding user...");}
}
- 测试 AOP
在 Spring 应用中使用目标类,AOP 会自动应用切面逻辑。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application implements CommandLineRunner {@Autowiredprivate UserService userService;public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Overridepublic void run(String... args) throws Exception {userService.addUser();}
}