面试遇到的问题
文章目录
- Q1:@SpringBootApplication 这个注解包含哪三个注解,作用是什么
- **1. `@SpringBootApplication`**(最核心注解)
- **2. `@EnableAutoConfiguration`**
- **3. `@ComponentScan`**
- **总结**
- Q2:@Controller @Component @Service 这三个注解,有什么区别
- **1. `@Component`**
- **2. `@Service`**
- **3. `@Controller`**
- **关键区别总结**
- **为什么需要不同的注解?**
- **常见问题**
- Q3:线上的机器 CPU的占用率很高,我想知道是哪个哪个方法导致的,怎么查看
- 如何排查线上机器CPU占用率高的问题
- 1. 快速确认Java进程CPU使用情况
- 2. 使用JDK工具分析Java进程
- 方法一:使用top命令定位高CPU线程
- 方法二:使用jstack抓取线程堆栈
- 方法三:使用jstat查看GC情况(排除GC问题)
- 3. 使用Arthas工具实时诊断(推荐)
- 4. 生成火焰图精确定位
- 5. 常见高CPU原因
- 6. 解决方案
- 注意事项
- Q4:现在的redis有三个节点,这三个节点都是一主,一从,也就是一共6台机器。现在我要再加两个节点,这两个节点也是一主一从。怎么扩容?
- 当前架构
- 扩容步骤
- 1. 准备新节点
- 2. 加入新主节点
- 3. 加入新从节点
- 4. 重新分配哈希槽(关键步骤)
- 5. 平衡集群(可选)
- 6. 验证集群状态
- 注意事项
- 扩容后验证
- Q5:Dubbo+nacos。我现在A去调用B服务,发现超时,如何排查问题
- 一、基础网络与连通性检查
- 二、Dubbo配置检查
- 三、服务端性能排查
- 四、Nacos注册中心问题排查
- 五、高级诊断工具使用
- 六、常见问题解决方案
- 七、系统性优化建议
- Q6:Redis 大key问题怎么解决
- 一、什么是 Redis 大 Key 问题?
- 二、大 Key 的判定标准
- 三、检测大 Key 的方法
- 1. 使用 redis-cli 扫描
- 2. 使用 MEMORY USAGE 命令
- 3. 使用 Redis RDB 工具分析
- 4. 线上实时监控(阿里云/腾讯云等提供的监控指标)
- 四、大 Key 的解决方案
- 1. 数据拆分(最推荐方案)
- 2. 使用压缩(适用于文本数据)
- 3. 数据分片(Sharding)
- 4. 过期时间管理
- 5. 数据结构优化
- 五、预防大 Key 的最佳实践
- 六、特殊场景处理
- 1. 已存在大 Key 的迁移方案
- 2. 热点大 Key 处理
- Q7:Mybaits-plus 如何将一个字符串查出来之后直接转换成一个对象,不要使用 JsonUtil.toBean
- 方法一:使用 TypeHandler(推荐)
- 1. 创建自定义 TypeHandler
- 2. 在实体类中使用
- 方法二:使用 MyBatis-Plus 的自动结果映射
- 方法三:使用 MyBatis-Plus 的 Wrapper 查询
- 方法四:使用 MyBatis 的 @ConstructorArgs 注解(适用于构造函数注入)
- 注意事项
- Q8:使用Arthas排查时间段内最耗时的三个方法
- 1. 启动Arthas并附加到目标JVM
- 2. 使用profiler命令进行采样分析(推荐)
- 3. 使用monitor命令监控方法耗时
- 4. 使用trace命令追踪方法调用链
- 5. 使用dashboard查看实时热点
- 6. 组合使用time tunnel记录和重放(高级)
- 结果解读技巧
- 注意事项
- Q9:completableFuture 有哪些常用方法?
- **1. 创建异步任务**
- **2. 结果处理(链式调用)**
- **(1)同步处理结果**
- **(2)异步处理结果**
- **3. 组合多个 Future**
- **(1)依赖前序任务**
- **(2)聚合多个任务**
- **4. 异常处理**
- **5. 主动控制**
- **6. 状态检查**
- **典型场景示例**
- **1. 链式异步调用**
- **2. 并行任务聚合**
- **3. 超时控制**
- **注意事项**
Q1:@SpringBootApplication 这个注解包含哪三个注解,作用是什么
1. @SpringBootApplication
(最核心注解)
作用:这是一个组合注解,包含以下三个关键注解的功能:
@SpringBootConfiguration
:标记该类为 Spring Boot 的配置类(基于@Configuration
)。@EnableAutoConfiguration
:启用 Spring Boot 的自动配置机制(根据依赖自动配置 Bean)。@ComponentScan
:自动扫描当前包及其子包下的@Component
、@Service
、@Controller
等注解的类,并注册为 Bean。
示例:
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
2. @EnableAutoConfiguration
作用:单独启用 Spring Boot 的自动配置(通常不需要单独使用,因为 @SpringBootApplication
已包含)。
- 根据项目的依赖(如
spring-boot-starter-web
、spring-boot-starter-data-jpa
)自动配置 Spring 应用。 - 例如:引入
spring-boot-starter-web
后,会自动配置 Tomcat 和 Spring MVC。
示例:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication { /* ... */ }
3. @ComponentScan
作用:指定 Spring 扫描 Bean 的包路径(默认扫描启动类所在包及其子包)。
- 可通过
basePackages
参数自定义扫描路径。 - 通常不需要显式添加,除非需要覆盖默认扫描规则。
示例:
@SpringBootApplication
@ComponentScan(basePackages = {"com.example", "org.utils"})
public class MyApplication { /* ... */ }
总结
注解 | 作用 | 是否必须 |
---|---|---|
@SpringBootApplication | 组合注解(含配置、自动配置、组件扫描) | 是(启动类通常只需此注解) |
@EnableAutoConfiguration | 启用自动配置 | 已包含在 @SpringBootApplication 中 |
@ComponentScan | 定义组件扫描范围 | 默认已包含,需自定义路径时才显式使用 |
实际开发中,启动类只需一个 @SpringBootApplication
即可,其他注解仅在特殊场景下单独使用。
Q2:@Controller @Component @Service 这三个注解,有什么区别
在 Spring 框架中,@Component
、@Service
和 @Controller
都是用于标识 Spring 管理的 Bean 的注解,但它们有不同的语义和用途。以下是它们的核心区别:
1. @Component
-
作用:最基础的注解,标记一个类为 Spring 的 组件(Bean),由 Spring 容器管理。
-
用途:适用于任何需要被 Spring 管理的类(通用型)。
-
示例:
@Component public class MyUtility {// 工具类或通用组件 }
-
衍生注解:
@Service
、@Controller
、@Repository
都是@Component
的特化形式(功能相同,但语义更明确)。
2. @Service
-
作用:标记一个类为 业务逻辑层(Service 层) 的组件。
-
用途:用于实现业务逻辑(如计算、事务管理等),通常被
@Controller
或@RestController
调用。 -
与
@Component
的区别:- 功能完全相同,但
@Service
的语义更清晰(表明这是业务逻辑类)。 - Spring 本身不强制区别,但团队协作时推荐按分层使用。
- 功能完全相同,但
-
示例:
@Service public class UserService {public void createUser(User user) { /* 业务逻辑 */ } }
3. @Controller
-
作用:标记一个类为 表现层(Web 层)控制器,处理 HTTP 请求。
-
用途:用于 Spring MVC 或 Spring WebFlux,定义路由和请求处理逻辑。
-
与
@Component
的区别:@Controller
是@Component
的特化,专用于 Web 层。- 结合
@RequestMapping
、@GetMapping
等注解定义端点。
-
示例:
@Controller public class UserController {@GetMapping("/users")public String listUsers(Model model) { /* 返回视图或数据 */ } }
关键区别总结
注解 | 层级 | 用途 | 是否可互换 |
---|---|---|---|
@Component | 通用 | 任何需要 Spring 管理的类 | 是(但语义不明确) |
@Service | 业务层 | 业务逻辑处理 | 是(但推荐按分层使用) |
@Controller | 表现层 | 处理 HTTP 请求 | 否(专用于 Web 层) |
为什么需要不同的注解?
- 代码可读性:通过注解名称直接明确类的职责(如
@Service
表示业务逻辑)。 - AOP 增强:未来可能针对特定注解实现切面(如
@Transactional
通常用于@Service
)。 - 框架扩展:Spring 可能对特定注解提供额外功能(如
@Controller
支持 Web 请求绑定)。
常见问题
Q:如果混用会怎样?
A:功能上无影响(Spring 均会注册为 Bean),但会降低代码可维护性。例如:
- 用
@Component
替代@Controller
:Web 请求仍能工作,但团队可能困惑为何不用@Controller
。 - 用
@Service
替代@Repository
:事务管理可能正常,但数据库异常转换等特性可能丢失。
最佳实践:严格按分层使用对应注解!
Q3:线上的机器 CPU的占用率很高,我想知道是哪个哪个方法导致的,怎么查看
如何排查线上机器CPU占用率高的问题
当线上机器CPU占用率高时,你需要系统性地排查是哪个Java方法导致的。以下是详细的排查步骤:
1. 快速确认Java进程CPU使用情况
top -c
或
htop
查看哪个Java进程CPU占用率高,记下PID(进程ID)
2. 使用JDK工具分析Java进程
方法一:使用top命令定位高CPU线程
top -H -p [PID] # 查看该进程下所有线程的CPU使用情况
记下高CPU占用的线程ID(TID),将其转换为16进制:
printf "%x\n" [TID]
方法二:使用jstack抓取线程堆栈
jstack [PID] > thread_dump.log
然后在thread_dump.log中搜索之前转换的16进制线程ID,找到对应的线程堆栈
方法三:使用jstat查看GC情况(排除GC问题)
jstat -gcutil [PID] 1000 5 # 每秒一次,共5次
3. 使用Arthas工具实时诊断(推荐)
# 下载并启动Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar [PID]# 常用命令
dashboard # 整体监控面板
thread -n 3 # 查看最忙的3个线程
thread [TID] # 查看指定线程堆栈
profiler start # 开始采样
profiler stop # 停止采样并生成火焰图
4. 生成火焰图精确定位
# 使用async-profiler(需安装)
./profiler.sh -d 30 -f /tmp/flamegraph.html [PID]
火焰图可以直观显示CPU时间消耗在哪些方法上
5. 常见高CPU原因
- 死循环:检查线程堆栈中是否有明显的循环调用
- 频繁GC:通过jstat确认是否GC导致
- 锁竞争:大量线程处于BLOCKED状态
- 算法效率低:某些方法执行时间过长
- 第三方库问题:如日志组件、网络通信等
6. 解决方案
- 优化热点代码(火焰图显示最耗CPU的部分)
- 调整JVM参数(特别是堆大小和GC相关)
- 修复死循环或锁竞争问题
- 对高负载接口进行限流或降级
- 考虑水平扩展或服务拆分
注意事项
- 生产环境操作要谨慎,避免影响线上服务
- 尽量在低峰期进行诊断
- 保留现场数据(线程dump、GC日志等)供后续分析
- 考虑使用APM工具(如SkyWalking、Pinpoint)长期监控
通过以上步骤,你应该能精确定位到导致CPU高的具体Java方法,然后针对性地进行优化。
Q4:现在的redis有三个节点,这三个节点都是一主,一从,也就是一共6台机器。现在我要再加两个节点,这两个节点也是一主一从。怎么扩容?
当前架构
- 现有集群:3个主节点 + 3个从节点(共6台机器)
- 计划扩容:新增2个主节点 + 2个从节点(共4台机器,总计10台)
扩容步骤
1. 准备新节点
- 在新服务器上安装相同版本的Redis
- 配置文件保持与现有集群一致(特别是集群相关参数)
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
2. 加入新主节点
# 在任意一台现有Redis节点上执行(假设新节点IP为192.168.1.101)
redis-cli --cluster add-node 192.168.1.101:6379 现有集群任意节点IP:端口
3. 加入新从节点
# 先加入集群作为主节点
redis-cli --cluster add-node 192.168.1.102:6379 现有集群任意节点IP:端口# 然后将其设置为某个主节点的从节点
redis-cli -h 192.168.1.102 -p 6379
> CLUSTER REPLICATE <目标主节点ID> # 主节点ID可通过CLUSTER NODES命令查看
4. 重新分配哈希槽(关键步骤)
# 启动重新分片
redis-cli --cluster reshard 现有集群任意节点IP:端口# 系统会交互式询问:
1. 要移动多少个槽位?(建议:16384/5≈3276个槽位/新节点)
2. 接收槽位的目标节点ID(输入新主节点ID)
3. 从哪些节点转移槽位?(可输入"all"从所有现有节点平均转移)
4. 确认分片方案
5. 平衡集群(可选)
# 自动平衡各节点槽位数量
redis-cli --cluster rebalance --cluster-use-empty-masters 集群任意节点IP:端口
6. 验证集群状态
redis-cli --cluster check 集群任意节点IP:端口
注意事项
-
业务低峰期操作:数据迁移会影响性能
-
监控迁移进度:
redis-cli --cluster info 集群任意节点IP:端口
-
客户端更新:确保客户端支持集群动态发现,或更新客户端节点列表
-
备份数据:操作前备份集群数据
-
槽位迁移:迁移过程中相关键不可用,建议设置:
redis-cli --cluster set-timeout 集群任意节点IP:端口 60000 # 设置超时为60秒
-
从节点添加:确保每个主节点都有至少一个从节点
扩容后验证
-
检查集群状态:
redis-cli CLUSTER INFO
-
测试数据读写:
redis-cli -c -p 6379 SET test_key "hello" redis-cli -c -p 6379 GET test_key
-
检查各节点负载是否均衡
通过以上步骤,你可以安全地将Redis集群从3主3从扩容到5主5从架构。
Q5:Dubbo+nacos。我现在A去调用B服务,发现超时,如何排查问题
一、基础网络与连通性检查
-
网络连通性测试
- 使用
ping
和telnet
检查服务提供者网络连通性 - 执行
telnet B服务IP 20880
确认Dubbo端口是否可达 - 使用
traceroute
分析网络路径是否存在异常
- 使用
-
容器网络配置(如使用Docker)
- 检查Docker网络模式(建议使用host模式或确保自定义网桥配置正确)
- 确认Nacos注册的IP是否为容器实际可访问IP
二、Dubbo配置检查
-
超时时间设置
-
检查Dubbo消费者端的
timeout
配置是否合理 -
建议基于业务P99响应时间设置,留有20%缓冲
-
示例配置:
<dubbo:reference interface="com.example.BService" timeout="3000" retries="0"/>
-
-
重试机制
- 非幂等操作必须设置
retries=0
防止重复请求 - 幂等操作可适当设置重试次数
- 非幂等操作必须设置
-
负载均衡策略
-
检查当前负载均衡策略(建议使用
leastactive
或random
) -
配置示例:
<dubbo:reference loadbalance="leastactive"/>
-
三、服务端性能排查
-
B服务性能分析
- 检查B服务CPU、内存使用情况
- 使用
jstack
分析线程堆栈,查找阻塞线程 - 监控GC日志,排除GC停顿导致的超时
-
数据库与外部依赖
- 检查是否存在慢SQL或数据库连接池问题
- 确认外部服务调用是否超时
-
Dubbo线程模型
-
检查Dubbo服务提供者的线程池状态
-
调整线程池参数:
<dubbo:protocol threads="200"/>
-
四、Nacos注册中心问题排查
-
Nacos健康状态
- 检查Nacos Server是否正常运行
- 查看Nacos日志是否有502等错误
- 确认磁盘IO性能(历史曾因磁盘问题导致心跳失败)
-
服务注册信息
- 检查Nacos控制台确认B服务是否正常注册
- 确认注册的IP和端口是否正确(特别注意Docker环境)
-
客户端TIME_WAIT问题
- 检查是否存在大量TIME_WAIT连接
- 升级Nacos客户端版本(旧版本存在HTTP连接不重用问题)
五、高级诊断工具使用
-
Arthas诊断
# 监控Dubbo调用 watch com.alibaba.dubbo.rpc.filter.ConsumerContextFilter invoke '{params,returnObj,throwExp}' -x 3# 分析最忙线程 thread -n 3
-
分布式追踪
- 集成SkyWalking或Zipkin追踪跨服务调用链
- 定位具体超时的环节
-
网络抓包分析
tcpdump -i any port 20880 -w dubbo.pcap
六、常见问题解决方案
-
Docker环境特殊问题
-
确保Nacos注册的是宿主机可访问IP(而非容器内部IP)
-
尝试改用host网络模式:
docker run --network=host ...
-
-
GC导致的超时
-
添加JVM参数收集GC日志:
-XX:+PrintGCDetails -Xloggc:/path/to/gc.log
-
对于Docker环境,显式设置GC线程数:
-XX:ParallelGCThreads=4 -XX:ConcGCThreads=4
-
-
Nacos客户端优化
-
开启本地缓存加载:
namingLoadCacheAtStart=true
-
调整心跳间隔:
nacos.client.beat.interval=5000
-
七、系统性优化建议
-
异步化改造
-
将耗时操作改为异步执行:
CompletableFuture.runAsync(() -> {// 耗时操作 });
-
-
熔断降级
-
集成Sentinel实现熔断降级
-
配置示例:
@DubboReference(parameters = {"blockHandlerClass", "MyBlockHandler"}) private BService bService;
-
-
监控告警
- 部署Dubbo Admin控制台监控QPS和响应时间
- 设置超时率告警(如单接口超时率>5%)
通过以上步骤的系统性排查,通常可以定位到Dubbo+Nacos环境下服务调用超时的根本原因。建议按照从简到繁的顺序进行排查,先确认网络和基础配置,再深入分析性能问题。
Q6:Redis 大key问题怎么解决
一、什么是 Redis 大 Key 问题?
大 Key 是指存储在 Redis 中单个 Key 对应的 Value 过大(通常以 KB 或 MB 为单位),导致 Redis 性能下降或稳定性问题的现象。具体表现包括:
- 内存占用高:单个 Key 占用大量内存
- 操作阻塞:对大 Key 的操作耗时过长,阻塞 Redis 单线程
- 网络负载大:传输大 Key 消耗过多带宽
- 持久化问题:AOF 重写或 RDB 保存时处理大 Key 效率低
二、大 Key 的判定标准
数据类型 | 大 Key 标准 |
---|---|
String | Value > 10KB |
Hash/Set/ZSet | 元素数量 > 5,000 |
List | 元素数量 > 10,000 |
Stream | 条目数 > 5,000 |
三、检测大 Key 的方法
1. 使用 redis-cli 扫描
# 扫描整个实例
redis-cli --bigkeys# 采样扫描(更快但可能遗漏)
redis-cli --bigkeys -i 0.1 # 每100ms扫描一次
2. 使用 MEMORY USAGE 命令
redis-cli MEMORY USAGE your_key_name
3. 使用 Redis RDB 工具分析
rdb -c memory dump.rdb --bytes 10240 > large_keys.csv
4. 线上实时监控(阿里云/腾讯云等提供的监控指标)
四、大 Key 的解决方案
1. 数据拆分(最推荐方案)
String 类型:
# 原始大 Key
SET user:1000:profile "非常大的JSON数据..."# 拆分为多个小 Key
SET user:1000:profile:basic "{基础信息}"
SET user:1000:profile:contact "{联系方式}"
SET user:1000:profile:history "{历史记录}"
Hash 类型:
# 原始大 Hash
HSET product:1000 field1 val1 field2 val2 ... field5000 val5000# 按字段前缀拆分
HSET product:1000:part1 field1 val1 ... field1000 val1000
HSET product:1000:part2 field1001 val1001 ... field2000 val2000
2. 使用压缩(适用于文本数据)
# 写入时压缩
SET user:1000:profile.gz "压缩后的数据"# 读取时解压(需客户端处理)
3. 数据分片(Sharding)
// 客户端分片示例
public String getShardedData(String key, int shard) {String shardKey = key + ":" + (shard % 10);return redis.get(shardKey);
}
4. 过期时间管理
# 对大 Key 设置合理的过期时间
EXPIRE large_key 3600
5. 数据结构优化
原结构 | 问题 | 优化方案 |
---|---|---|
大 String | 更新成本高 | 改用 Hash 分字段存储 |
大 List | 随机访问慢 | 改用 ZSet 分页查询 |
大 Set | 交集计算慢 | 拆分多个 Set 并行计算 |
五、预防大 Key 的最佳实践
-
设计阶段:
- 预估 Value 大小,提前设计拆分方案
- 避免使用 Redis 存储文件等二进制大数据
-
开发阶段:
- 代码审查时检查 Redis 使用方式
- 实现自动检测大 Key 的监控脚本
-
运维阶段:
- 定期扫描大 Key(如每周一次)
- 设置内存告警阈值(如单 Key > 1MB 告警)
-
监控体系:
# Prometheus + Grafana 监控示例 redis_memory_usage_bytes{key="large_key"} > 1048576
六、特殊场景处理
1. 已存在大 Key 的迁移方案
# 使用 SCAN + DUMP/RESTORE 渐进式迁移
redis-cli --scan --pattern "large_*" | while read key; doredis-cli --pipe << EOFDUMP "$key" | head -c 102400 | redis-cli -x RESTORE "$key:part1" 0DUMP "$key" | tail -c +102401 | redis-cli -x RESTORE "$key:part2" 0DEL "$key"
EOF
done
2. 热点大 Key 处理
- 增加本地缓存(如 Caffeine)
- 实现多级缓存策略
- 考虑读写分离架构
通过以上方法,可以有效解决和预防 Redis 大 Key 问题,保障 Redis 的高性能运行。
Q7:Mybaits-plus 如何将一个字符串查出来之后直接转换成一个对象,不要使用 JsonUtil.toBean
方法一:使用 TypeHandler(推荐)
1. 创建自定义 TypeHandler
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(YourObject.class)
public class JsonToObjectTypeHandler extends BaseTypeHandler<YourObject> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, YourObject parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, JSON.toJSONString(parameter));}@Overridepublic YourObject getNullableResult(ResultSet rs, String columnName) throws SQLException {String json = rs.getString(columnName);return parseJsonToObject(json);}@Overridepublic YourObject getNullableResult(ResultSet rs, int columnIndex) throws SQLException {String json = rs.getString(columnIndex);return parseJsonToObject(json);}@Overridepublic YourObject getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {String json = cs.getString(columnIndex);return parseJsonToObject(json);}private YourObject parseJsonToObject(String json) {if (StringUtils.isBlank(json)) {return null;}return JSON.parseObject(json, YourObject.class);}
}
2. 在实体类中使用
@TableName("your_table")
public class YourEntity {@TableField(value = "json_column", typeHandler = JsonToObjectTypeHandler.class)private YourObject yourObject;// getter/setter
}
方法二:使用 MyBatis-Plus 的自动结果映射
public interface YourMapper extends BaseMapper<YourEntity> {@Select("SELECT json_column FROM your_table WHERE id = #{id}")@Results({@Result(property = "yourObject", column = "json_column", javaType = YourObject.class, typeHandler = JsonToObjectTypeHandler.class)})YourEntity selectWithObject(@Param("id") Long id);
}
方法三:使用 MyBatis-Plus 的 Wrapper 查询
// 查询时自动转换
YourEntity entity = yourService.getOne(Wrappers.<YourEntity>lambdaQuery().eq(YourEntity::getId, id).select(YourEntity::getJsonColumn) // 假设getJsonColumn返回YourObject类型
);
方法四:使用 MyBatis 的 @ConstructorArgs 注解(适用于构造函数注入)
public interface YourMapper extends BaseMapper<YourEntity> {@Select("SELECT json_column FROM your_table WHERE id = #{id}")@ConstructorArgs({@Arg(column = "json_column", javaType = YourObject.class, typeHandler = JsonToObjectTypeHandler.class)})YourEntity selectWithConstructor(@Param("id") Long id);
}
注意事项
- 性能考虑:频繁的 JSON 解析会影响性能,对于大量数据查询建议在数据库层面处理
- 空值处理:确保你的 TypeHandler 正确处理 null 值
- 复杂对象:如果对象结构复杂,确保有适当的默认构造函数和 setter 方法
- 版本兼容:不同版本的 MyBatis-Plus 对 TypeHandler 的支持可能略有不同
以上方法都能实现从数据库字符串到 Java 对象的自动转换,避免了手动调用 JsonUtil.toBean
的步骤。
Q8:使用Arthas排查时间段内最耗时的三个方法
1. 启动Arthas并附加到目标JVM
# 下载并启动Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar# 选择目标Java进程(输入数字编号)
2. 使用profiler命令进行采样分析(推荐)
# 启动采样(默认CPU热点分析)
profiler start -d 60 # 采样60秒# 停止采样并生成火焰图
profiler stop --format html # 生成HTML格式报告
火焰图会直观显示最耗时的调用栈,顶部最宽的部分就是最耗时的代码路径。
3. 使用monitor命令监控方法耗时
# 监控特定类的方法(统计周期10秒,统计3次)
monitor -c 10 -n 3 com.example.YourClass *
输出示例:
method[com.example.YourClass.method1] cnt[1023] avg[12.34ms] max[45.67ms] min[8.90ms]
method[com.example.YourClass.method2] cnt[456] avg[23.45ms] max[67.89ms] min[15.67ms]
4. 使用trace命令追踪方法调用链
# 追踪特定方法(耗时超过10ms的调用)
trace com.example.YourClass yourMethod '#cost > 10' -n 3
5. 使用dashboard查看实时热点
dashboard # 查看实时线程CPU占用
按Q
退出dashboard视图后,可以查看线程统计信息。
6. 组合使用time tunnel记录和重放(高级)
# 开始记录方法调用
tt -t com.example.YourClass *
# 一段时间后停止记录
tt -l # 列出记录
tt -i 1004 -w 'target.method(args)' # 分析特定调用
结果解读技巧
- 火焰图:最顶层的宽条表示最耗时的代码路径
- monitor输出:关注
avg
和max
耗时高的方法 - trace结果:查找
#cost
值最大的调用链 - 线程状态:在dashboard中关注
RUNNABLE
状态的线程
注意事项
- 生产环境谨慎使用,采样会影响性能(通常<5%)
- 对于短时间方法调用,适当增加采样时间(建议至少30秒)
- 结合
-n
参数限制输出条目数,避免信息过载 - 分析完成后及时退出Arthas(
stop
命令)
通过以上方法,你可以准确找出指定时间段内最耗时的三个Java方法,并获取它们的调用上下文和性能指标。
Q9:completableFuture 有哪些常用方法?
CompletableFuture
是 Java 8 引入的异步编程工具,提供了丰富的链式调用和组合操作。以下是其 常用方法分类详解,附带示例代码和典型场景:
1. 创建异步任务
方法 | 说明 | 示例 |
---|---|---|
runAsync(Runnable) | 无返回值的异步任务 | CompletableFuture.runAsync(() -> System.out.println("Task running")) |
supplyAsync(Supplier) | 有返回值的异步任务 | CompletableFuture.supplyAsync(() -> "Result") |
指定线程池 | 默认用 ForkJoinPool.commonPool() ,可自定义 | supplyAsync(() -> "Result", Executors.newFixedThreadPool(10)) |
2. 结果处理(链式调用)
(1)同步处理结果
方法 | 说明 | 示例 |
---|---|---|
thenApply(Function) | 对结果同步转换 | future.thenApply(s -> s + " processed") |
thenAccept(Consumer) | 消费结果(无返回值) | future.thenAccept(System.out::println) |
thenRun(Runnable) | 结果完成后执行操作 | future.thenRun(() -> System.out.println("Done")) |
(2)异步处理结果
方法 | 说明 | 示例 |
---|---|---|
thenApplyAsync(Function) | 异步转换结果 | future.thenApplyAsync(s -> s + " async") |
thenAcceptAsync(Consumer) | 异步消费结果 | future.thenAcceptAsync(System.out::println) |
3. 组合多个 Future
(1)依赖前序任务
方法 | 说明 | 示例 |
---|---|---|
thenCompose(Function) | 扁平化嵌套 Future | futureA.thenCompose(resultA -> futureB(resultA)) |
handle(BiFunction) | 处理结果或异常 | future.handle((res, ex) -> ex != null ? "fallback" : res) |
(2)聚合多个任务
方法 | 说明 | 示例 |
---|---|---|
thenCombine(CompletionStage, BiFunction) | 合并两个任务结果 | futureA.thenCombine(futureB, (a, b) -> a + b) |
allOf(CompletableFuture...) | 所有任务完成后触发 | CompletableFuture.allOf(futures).thenRun(...) |
anyOf(CompletableFuture...) | 任意任务完成后触发 | CompletableFuture.anyOf(futures).thenAccept(...) |
4. 异常处理
方法 | 说明 | 示例 |
---|---|---|
exceptionally(Function) | 捕获异常并返回默认值 | future.exceptionally(ex -> "Error: " + ex.getMessage()) |
whenComplete(BiConsumer) | 无论成功/失败都执行 | future.whenComplete((res, ex) -> { if (ex != null) log.error(ex); }) |
5. 主动控制
方法 | 说明 | 示例 |
---|---|---|
complete(T value) | 手动完成任务 | future.complete("Manual result") |
completeExceptionally(Throwable) | 手动失败任务 | future.completeExceptionally(new RuntimeException()) |
cancel(boolean mayInterrupt) | 取消任务 | future.cancel(true) |
6. 状态检查
方法 | 说明 | 示例 |
---|---|---|
isDone() | 任务是否完成(成功/失败/取消) | if (future.isDone()) { ... } |
isCompletedExceptionally() | 是否因异常完成 | if (future.isCompletedExceptionally()) { ... } |
get() / get(long, TimeUnit) | 阻塞获取结果(需处理异常) | String result = future.get(5, TimeUnit.SECONDS) |
join() | 类似 get() ,但不抛受检异常 | String result = future.join() |
典型场景示例
1. 链式异步调用
CompletableFuture.supplyAsync(() -> fetchUserData()).thenApplyAsync(user -> processData(user)).thenAcceptAsync(result -> sendResult(result)).exceptionally(ex -> {System.err.println("Error: " + ex);return null;});
2. 并行任务聚合
CompletableFuture<String> futureA = fetchDataA();
CompletableFuture<String> futureB = fetchDataB();futureA.thenCombine(futureB, (a, b) -> a + " & " + b).thenAccept(System.out::println);
3. 超时控制
future.completeOnTimeout("Timeout Fallback", 2, TimeUnit.SECONDS).thenAccept(System.out::println);
注意事项
- 线程池管理:避免无限制使用默认线程池(尤其在大量任务时)。
- 异常传播:
thenApply
不会处理前序任务的异常,需配合exceptionally
或handle
。 - 阻塞风险:
get()
会阻塞线程,异步场景优先使用回调(如thenAccept
)。
如果需要更复杂的组合逻辑(如重试机制),可以结合 RetryUtils 或 Spring 的 @Async
扩展。