SpringBoot
概述
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
Spring 最核心的思想就是不重新造轮子,开箱即用,提高开发效率。
Spring 提供的核心功能主要是 IoC 和 AOP。
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
Spring 和 SpringMVC 的问题在于:需要配置大量的参数。 Spring Boot 通过一个自动配置和启动的项来目解决这个问题。为了更快的构建产品就绪应用程序,Spring Boot 提供了一些非功能性特征。
Spring Boot 有哪些优点?
Spring Boot 主要有如下优点:
- 内置servlet容器,不需要在服务器部署 tomcat。只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目
- SpringBoot提供了starter,把常用库聚合在一起,简化复杂的环境配置,快速搭建spring应用环境
- 可以快速创建独立运行的spring项目,集成主流框架
- 准生产环境的运行应用监控
Spring Boot 的核心注解是哪个?
Spring Boot 的核心注解是 @SpringBootApplication,它是一个复合注解,主要组合了以下三个注解:
@SpringBootConfiguration:Spring Boot 配置注解,是 Spring Boot 项目的基石,也是 Spring Boot 项目的核心。@EnableAutoConfiguration:Spring Boot 自动配置注解,开启自动配置功能,可以根据项目中引入的依赖自动配置项目。@ComponentScan:Spring Boot 组件扫描注解,用于扫描项目中的组件,并将其注册到 Spring 容器中。
Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。
Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。
普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
Spring Boot 中如何解决跨域问题 ?
跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋。
因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。
这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}jdk8 和 jdk17 有什么区别?
jdk8兼容性最佳,但是不再接收新特性更新。
核心特性:
Lambda 表达式:简化匿名函数,如 list.forEach(e -> System.out.println(e))。
Stream API:函数式操作集合(过滤、映射、归约),如 list.stream().filter(e -> e > 10).collect(Collectors.toList())。
接口默认方法:允许接口定义具体实现(default 关键字)。
日期时间 API:引入 java.time 包(LocalDate、LocalDateTime 等)替代旧版 Date。
方法引用:简化函数调用,如 System.out::println。
jdk17新特性丰富,性能和安全性显著提升。
核心特性:
- 模式匹配,instanceof,密封类/接口,switch表达式增强,record类
性能优化:
jdk8默认垃圾回收器为parlleGC(吞吐量优先)
jdk17默认GC是G1 GC,性能显著提升。
jdk 8引入了Lambda表达式和stream api
jdk21 引入虚拟线程,jdk24提供了更多的AI支持。
分库分表是什么?怎么保证数据的一致性?
在大型项目中,分库分表是应对高并发和海量数据的常见手段,但数据一致性是其核心挑战。
以下结合实践经验,分场景说明解决方案:
一、分库分表场景与一致性挑战
1. 为何需要分库分表?
水平分库:按业务或用户ID拆分数据库(如订单库、用户库),提升并发能力。
水平分表:将大表按规则拆分(如按时间、哈希),避免单表数据量过大。
垂直分表:按字段拆分表(如将不常用字段单独存储),优化查询效率。
2. 一致性挑战
跨库事务:传统数据库事务(ACID)无法跨数据库生效。
最终一致性延迟:异步同步数据可能导致短时间内数据不一致。
分布式ID冲突:分库后自增ID失效,需保证全局唯一ID。
二、数据一致性解决方案
1. 强一致性(实时同步)
适用场景:核心交易(如支付、库存扣减)。
方案:
2PC/3PC协议(两阶段/三阶段提交)
原理:引入协调者(Coordinator),所有参与者预提交后统一确认。
问题:性能差,存在阻塞风险。
实现:开源框架如
Seata的XA模式。
TCC补偿事务
原理:Try(预留资源)→ Confirm(提交)→ Cancel(回滚)。
示例:扣库存时先冻结,支付成功后正式扣减,失败则解冻。
实现:
Seata的TCC模式、ByteTCC。
2. 最终一致性(异步同步)
适用场景:非核心业务(如订单状态通知、统计数据)。
方案:
基于MQ的可靠消息模式
流程:
业务操作后发送消息到MQ(如RocketMQ、Kafka)。
消费方处理消息并更新数据。
失败时通过重试或人工补偿。
关键:消息幂等性(避免重复消费)。
订阅binlog日志
流程:
通过Canal等工具订阅数据库binlog。
解析binlog并同步到其他库。
实现:阿里开源的
Canal、Maxwell。
3. 分布式ID生成
方案:
UUID:简单但无序,适合非主键场景。
数据库号段模式:批量获取ID段(如每次从DB取1000个),减少DB压力。
雪花算法(Snowflake):生成64位ID(时间戳+机器ID+序列号),需解决时钟回拨问题。
Redis原子操作:利用INCR命令生成递增ID。
4. 补偿机制
对账系统:定期比对各库数据,发现差异时人工干预。
重试策略:消息失败时自动重试(如MQ的死信队列)。
幂等设计:所有接口支持重试幂等(如通过唯一ID去重)。
三、分库分表工具推荐
ShardingSphere(Java)
支持分库分表、读写分离,提供分布式事务解决方案。
兼容Spring Boot,可无缝集成现有项目。
MyCat(MySQL中间件)
- 基于MySQL协议,透明代理分库分表逻辑。
Vitess(Google开源)
- 支持MySQL和PostgreSQL,适合超大规模分布式数据库。
四、实践建议
优先避免跨库事务:通过合理的库表设计(如按用户ID分库)减少事务。
权衡一致性级别:非关键业务优先选择最终一致性,牺牲部分实时性换取性能。
监控与告警:实时监控数据同步延迟,设置阈值触发告警。
测试与演练:模拟数据不一致场景,验证补偿机制的有效性。
五、示例代码(基于ShardingSphere实现分库分表)
# application.yml 配置分片规则
spring:
shardingsphere:
rules:
sharding:
tables:
# 订单表按用户ID分库,按订单ID分表
t_order:
actual-data-nodes: db$->{0..1}.t_order$->{0..2}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-inline
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
sharding-algorithms:
db-inline:
type: INLINE
props:
algorithm-expression: db$->{user_id % 2}
table-inline:
type: INLINE
props:
algorithm-expression: t_order$->{order_id % 3}其实可以用更通俗的方式来理解这些方案,就像处理生活中的各种麻烦事一样。
一、为啥要分库分表?一致性难在哪?
分库分表就像分家:
数据库数据太多、访问太频繁时,就像一大家子人挤在小房子里太拥挤,所以按业务分成“订单家”“用户家”(水平分库),或者把大表拆成几张小表(水平分表),让每个“家”的数据少一点,访问更快。
一致性的麻烦:
原来在一个库里改数据,就像一家人说话好商量;分库后跨库改数据,就像不同“家”之间传话,容易出错。比如同时改订单库和库存库的数据,可能一个改了一个没改,导致数据对不上。
二、数据一致性怎么解决?分情况看!
1. 必须实时一致的场景(比如花钱买东西)
方案1:两阶段确认(像组队买奶茶)
比如你和朋友一起点奶茶,先问所有人“要不要买”(预提交),等大家都同意了,再统一付钱(正式提交)。如果有人反悔,就都不买。
技术里叫2PC/3PC,用Seata框架实现,但缺点是如果中间有人“卡住”(网络问题),所有人都得等,效率低。
方案2:先占坑后确认(TCC模式,像订酒店)
订酒店时先冻结房费(Try),入住时扣钱(Confirm),取消就解冻(Cancel)。对应到代码里,比如扣库存时先冻结数量,支付成功再正式扣减,失败就还原。
2. 可以晚点一致的场景(比如统计数据)
方案1:发消息通知(像发群公告)
比如下单后,给“库存群”“物流群”发消息说“订单已创建”,各群自己处理。如果消息没收到,就重试或者人工检查。
关键:每个群处理消息时,要保证不管收到多少次,结果都一样(幂等性),比如用订单号做标记,避免重复处理。
方案2:“偷瞄”数据库记录(订阅binlog)
就像偷偷看别人记的账本(binlog),发现哪里改了,就跟着自己改。比如Canal工具能“监听”MySQL的修改记录,自动同步到其他库。
3. 分库后ID怎么不重复?(像给每个人发唯一编号)
UUID:生成一串很长的随机码,简单但没规律,适合不重要的ID。
找数据库“批发”ID:比如一次从数据库拿1000个ID(号段模式),用完再拿,减少频繁找数据库的麻烦。
雪花算法:按“时间+机器号+序列号”生成ID,像按顺序发号码牌,缺点是如果机器时钟调回过去,可能生成重复ID。
用Redis计数:每次要ID时,让Redis的数字加1,适合简单场景。
4. 万一数据错了怎么办?(事后补救)
定期对账:像月底对账一样,定期检查各库数据是否一致,错了就人工修正。
失败重试:消息没发成功?多试几次!比如MQ里的“死信队列”,专门存失败的消息,过会儿再发。
防重复操作:所有接口都加个“唯一订单号”,不管点多少次提交,结果都一样。
三、分库分表工具推荐(像请搬家公司帮忙)
ShardingSphere(Java用):帮你自动拆分数据库和表,还能处理分布式事务,和Spring Boot搭配很方便。
MyCat:像个翻译官,把你的数据库操作转换成多个库的操作,对开发人员透明。
Vitess(Google出品):适合超大规模数据,支持MySQL和PostgreSQL,不过用起来比较复杂。
四、实战小技巧(避坑指南)
能不分库就不分:先尽量通过优化单库单表解决问题,分库分表是最后手段。
非核心业务别强求实时一致:比如统计报表晚几分钟更新没关系,优先保证系统不卡。
盯紧“同步延迟”:就像盯着快递物流一样,实时监控数据同步有没有卡住,设置警报。
提前模拟故障:比如故意断开一个库,看看系统能不能自动补偿数据,避免真出事时抓瞎。
总结
分库分表的数据一致性,就像管一群孩子:
重要的事(支付)必须当场管到位(强一致性,2PC/TCC);
不重要的事(通知)可以事后打招呼(最终一致性,MQ+重试);
最后一定要有兜底(对账+补偿),不然出了错更麻烦。