跳到主要内容

Java SpringCloud 面试题集

总题数: 55道 | 重点领域: 微服务、服务治理、分布式系统 | 难度分布: 高级

本文档整理了 Java Spring Cloud 的完整55道面试题目,涵盖微服务架构、服务治理、分布式系统等各个方面。


面试题目列表

1. 什么是分布式事务的防悬挂,空回滚?

这是Seata TCC模式中处理异常情况的两个重要机制。

空回滚(Empty Rollback)

定义:Try阶段未执行或执行失败,但Cancel方法被调用的情况。

产生原因

  • 网络延迟导致Try请求超时,TM(事务管理器)认为Try失败
  • TM发起全局回滚,调用所有分支的Cancel方法
  • 但实际上Try请求可能还在网络中传输,尚未到达

问题:Cancel方法找不到Try阶段的业务数据,无法执行回滚操作。

解决方案

java
1@TwoPhaseBusinessAction(name = "deduct", commitMethod = "commit", rollbackMethod = "rollback")
2public interface AccountService {
3 boolean deduct(BusinessActionContext context,
4 @BusinessActionContextParameter(paramName = "userId") String userId,
5 @BusinessActionContextParameter(paramName = "amount") int amount);
6
7 boolean commit(BusinessActionContext context);
8 boolean rollback(BusinessActionContext context);
9}
10
11@Service
12public class AccountServiceImpl implements AccountService {
13
14 @Override
15 public boolean rollback(BusinessActionContext context) {
16 String xid = context.getXid();
17
18 // 空回滚检查:判断Try是否执行过
19 TryStatus tryStatus = tryStatusMapper.selectByXid(xid);
20 if (tryStatus == null) {
21 // Try未执行,这是空回滚
22 log.info("空回滚,xid: {}", xid);
23 // 记录Cancel状态,防止后续Try执行(防悬挂)
24 cancelStatusMapper.insert(new CancelStatus(xid));
25 return true; // 空回滚成功
26 }
27
28 // 幂等性检查:是否已经回滚过
29 CancelStatus cancelStatus = cancelStatusMapper.selectByXid(xid);
30 if (cancelStatus != null) {
31 log.info("重复回滚,xid: {}", xid);
32 return true;
33 }
34
35 // 执行实际的回滚逻辑
36 String userId = context.getActionContext("userId", String.class);
37 int amount = context.getActionContext("amount", Integer.class);
38
39 // 解冻金额
40 accountMapper.unfreezeAmount(userId, amount);
41
42 // 记录Cancel状态
43 cancelStatusMapper.insert(new CancelStatus(xid));
44
45 log.info("回滚成功,userId: {}, amount: {}", userId, amount);
46 return true;
47 }
48}

防悬挂(Suspension Prevention)

定义:Cancel方法先于Try方法执行的情况。

产生原因

  • Try请求因网络拥堵延迟到达
  • TM超时后发起全局回滚,Cancel先执行完成
  • 延迟的Try请求最终到达并执行

问题:Try执行后资源被占用(如冻结金额),但永远不会被释放,因为Cancel已经执行完毕。

解决方案

java
1@Override
2public boolean deduct(BusinessActionContext context, String userId, int amount) {
3 String xid = context.getXid();
4
5 // 防悬挂检查:判断Cancel是否已执行
6 CancelStatus cancelStatus = cancelStatusMapper.selectByXid(xid);
7 if (cancelStatus != null) {
8 // Cancel已执行,拒绝执行Try
9 log.warn("防悬挂,Cancel已执行,拒绝Try,xid: {}", xid);
10 return false;
11 }
12
13 // 幂等性检查:是否已经执行过Try
14 TryStatus tryStatus = tryStatusMapper.selectByXid(xid);
15 if (tryStatus != null) {
16 log.info("重复Try,xid: {}", xid);
17 return true;
18 }
19
20 // 执行实际的Try逻辑
21 // 1. 检查账户余额
22 Account account = accountMapper.selectByUserId(userId);
23 if (account.getBalance() < amount) {
24 throw new InsufficientBalanceException("余额不足");
25 }
26
27 // 2. 冻结金额
28 accountMapper.freezeAmount(userId, amount);
29
30 // 3. 记录Try状态
31 tryStatusMapper.insert(new TryStatus(xid, userId, amount));
32
33 log.info("Try成功,userId: {}, amount: {}", userId, amount);
34 return true;
35}

数据库表设计

sql
1-- Try状态表
2CREATE TABLE try_status (
3 id BIGINT PRIMARY KEY AUTO_INCREMENT,
4 xid VARCHAR(128) UNIQUE NOT NULL COMMENT '全局事务ID',
5 user_id VARCHAR(64) NOT NULL,
6 amount INT NOT NULL,
7 create_time DATETIME DEFAULT CURRENT_TIMESTAMP
8);
9
10-- Cancel状态表
11CREATE TABLE cancel_status (
12 id BIGINT PRIMARY KEY AUTO_INCREMENT,
13 xid VARCHAR(128) UNIQUE NOT NULL COMMENT '全局事务ID',
14 create_time DATETIME DEFAULT CURRENT_TIMESTAMP
15);

完整流程图

1正常流程:
2Try → Confirm
3
4记录Try状态 → 提交事务
5
6空回滚流程:
7Try超时/失败 → Cancel被调用
8
9检查Try状态 → 未执行 → 记录Cancel状态 → 返回成功
10
11防悬挂流程:
12Cancel先执行 → 记录Cancel状态
13
14Try延迟到达 → 检查Cancel状态 → 已执行 → 拒绝Try

2. 什么是配置中心?有哪些常见的配置中心?

配置中心定义

配置中心是微服务架构中用于集中管理所有服务配置的基础设施,支持配置的统一管理、动态更新、版本控制和权限管理。

为什么需要配置中心

  1. 配置集中管理

    • 传统方式:配置分散在各个服务的配置文件中
    • 问题:修改配置需要重新打包部署,管理困难
    • 解决:统一管理所有服务的配置
  2. 动态更新

    • 传统方式:修改配置需要重启服务
    • 问题:服务重启影响可用性
    • 解决:配置修改后实时生效,无需重启
  3. 环境隔离

    • 需求:开发、测试、生产环境配置不同
    • 解决:通过namespace或profile实现环境隔离
  4. 版本管理

    • 需求:配置变更历史追溯和回滚
    • 解决:配置版本化管理,支持快速回滚

常见配置中心对比

1. Spring Cloud Config

特点

  • Spring官方提供,与Spring生态无缝集成
  • 基于Git存储,天然支持版本管理
  • 需要手动刷新配置(通过/actuator/refresh)

配置示例

yaml
1# Config Server配置
2spring:
3 application:
4 name: config-server
5 cloud:
6 config:
7 server:
8 git:
9 uri: https://github.com/your-repo/config-repo
10 search-paths: config/{application}
11 username: your-username
12 password: your-password
13 default-label: master
14 clone-on-start: true # 启动时克隆仓库
15 force-pull: true # 强制拉取最新配置
16
17# Config Client配置
18spring:
19 application:
20 name: user-service
21 cloud:
22 config:
23 uri: http://localhost:8888
24 profile: dev
25 label: master
26 fail-fast: true # 连接失败快速失败
27 retry:
28 max-attempts: 6
29 initial-interval: 1000

动态刷新

java
1@RestController
2@RefreshScope // 支持配置刷新
3public class ConfigController {
4
5 @Value("${custom.config}")
6 private String config;
7
8 @GetMapping("/config")
9 public String getConfig() {
10 return config;
11 }
12}
13
14// 刷新配置
15// POST http://localhost:8080/actuator/refresh

2. Nacos Config

特点

  • 阿里巴巴开源,功能强大
  • 支持配置和服务发现
  • 自动推送配置变更,无需手动刷新
  • 提供Web控制台

配置示例

yaml
1spring:
2 application:
3 name: user-service
4 cloud:
5 nacos:
6 config:
7 server-addr: localhost:8848
8 namespace: dev # 命名空间,实现环境隔离
9 group: DEFAULT_GROUP # 配置分组
10 file-extension: yaml
11 refresh-enabled: true # 自动刷新
12 extension-configs:
13 - data-id: common.yaml
14 group: DEFAULT_GROUP
15 refresh: true

使用示例

java
1@RestController
2@RefreshScope
3public class ConfigController {
4
5 @NacosValue(value = "${config.info}", autoRefreshed = true)
6 private String configInfo;
7
8 @GetMapping("/config")
9 public String getConfig() {
10 return configInfo;
11 }
12}

3. Apollo

特点

  • 携程开源,企业级配置中心
  • 功能最完善,支持灰度发布
  • 提供完善的权限管理和审计功能
  • 客户端配置缓存,容灾能力强

配置示例

properties
1# app.properties
2app.id=user-service
3apollo.meta=http://localhost:8080
4apollo.bootstrap.enabled=true
5apollo.bootstrap.namespaces=application,database
java
1@Configuration
2public class ApolloConfig {
3
4 @ApolloConfig
5 private Config config;
6
7 @ApolloConfigChangeListener
8 private void onChange(ConfigChangeEvent changeEvent) {
9 for (String key : changeEvent.changedKeys()) {
10 ConfigChange change = changeEvent.getChange(key);
11 log.info("配置变更 - key: {}, oldValue: {}, newValue: {}",
12 key, change.getOldValue(), change.getNewValue());
13 }
14 }
15}

4. Consul

特点

  • HashiCorp开源,Go语言编写
  • 支持服务发现和配置管理
  • 支持多数据中心
  • 提供健康检查功能

对比表格

特性Spring Cloud ConfigNacosApolloConsul
开源公司Spring阿里巴巴携程HashiCorp
配置存储GitMySQLMySQLKV存储
配置界面
动态刷新手动自动自动支持
版本管理Git内置内置有限
权限管理简单完善
灰度发布不支持支持支持不支持
多环境ProfileNamespaceNamespace文件夹
客户端缓存
配置回滚Git回滚支持支持不支持
学习成本
社区活跃度

选择建议

  • 小型项目:Spring Cloud Config(简单易用)
  • 中型项目:Nacos(功能均衡,国内支持好)
  • 大型企业:Apollo(功能最完善,权限管理强)
  • 多语言环境:Consul(跨语言支持好)

3. 你知道 Nacos 配置中心的实现原理吗?

Nacos配置中心架构

1┌─────────────────────────────────────────────┐
2│ Nacos Client │
3│ ┌──────────────────────────────────────┐ │
4│ │ ConfigService (配置服务接口) │ │
5│ ├──────────────────────────────────────┤ │
6│ │ ClientWorker (长轮询工作线程) │ │
7│ ├──────────────────────────────────────┤ │
8│ │ LocalConfigInfoProcessor (本地缓存) │ │
9│ └──────────────────────────────────────┘ │
10└─────────────────────────────────────────────┘
11 ↓ HTTP Long Polling
12┌─────────────────────────────────────────────┐
13│ Nacos Server │
14│ ┌──────────────────────────────────────┐ │
15│ │ ConfigController (配置接口) │ │
16│ ├──────────────────────────────────────┤ │
17│ │ LongPollingService (长轮询服务) │ │
18│ ├──────────────────────────────────────┤ │
19│ │ ConfigCacheService (配置缓存) │ │
20│ ├──────────────────────────────────────┤ │
21│ │ DumpService (配置持久化) │ │
22│ └──────────────────────────────────────┘ │
23└─────────────────────────────────────────────┘
24
25 ┌──────────────┐
26 │ MySQL │
27 └──────────────┘

核心实现原理

1. 配置存储(三级缓存)

java
1// 第一级:内存缓存(ConcurrentHashMap)
2private final ConcurrentHashMap<String, CacheItem> cache = new ConcurrentHashMap<>();
3
4public class CacheItem {
5 private String content; // 配置内容
6 private String md5; // 配置MD5值
7 private long lastModified; // 最后修改时间
8}
9
10// 第二级:本地文件缓存(容灾)
11public class LocalConfigInfoProcessor {
12 private static final String LOCAL_SNAPSHOT_PATH =
13 System.getProperty("user.home") + "/nacos/config/";
14
15 public void saveSnapshot(String dataId, String group, String content) {
16 String file = LOCAL_SNAPSHOT_PATH + dataId + "_" + group;
17 Files.write(Paths.get(file), content.getBytes());
18 }
19
20 public String getFailover(String dataId, String group) {
21 String file = LOCAL_SNAPSHOT_PATH + dataId + "_" + group;
22 return Files.readString(Paths.get(file));
23 }
24}
25
26// 第三级:MySQL持久化
27@Mapper
28public interface ConfigInfoMapper {
29 @Select("SELECT content, md5 FROM config_info " +
30 "WHERE data_id = #{dataId} AND group_id = #{group}")
31 ConfigInfo selectConfig(@Param("dataId") String dataId,
32 @Param("group") String group);
33}

2. 配置推送(HTTP长轮询)

客户端实现

java
1public class ClientWorker {
2 // 长轮询超时时间:30秒
3 private static final long TIMEOUT = 30000L;
4
5 // 每个配置一个监听任务
6 private final ConcurrentHashMap<String, CacheData> cacheMap = new ConcurrentHashMap<>();
7
8 public ClientWorker() {
9 // 启动长轮询线程
10 executor.scheduleWithFixedDelay(new LongPollingRunnable(), 0, 10, TimeUnit.MILLISECONDS);
11 }
12
13 class LongPollingRunnable implements Runnable {
14 @Override
15 public void run() {
16 try {
17 // 1. 检查本地配置
18 List<CacheData> cacheDatas = checkLocalConfig();
19
20 // 2. 组装监听的配置列表
21 StringBuilder sb = new StringBuilder();
22 for (CacheData cacheData : cacheDatas) {
23 sb.append(cacheData.dataId).append(WORD_SEPARATOR);
24 sb.append(cacheData.group).append(WORD_SEPARATOR);
25 sb.append(cacheData.md5).append(LINE_SEPARATOR);
26 }
27
28 // 3. 发起长轮询请求
29 HttpResult result = agent.httpPost(
30 "/v1/cs/configs/listener",
31 null,
32 sb.toString(),
33 TIMEOUT
34 );
35
36 // 4. 处理变更的配置
37 if (result.code == HttpStatus.OK) {
38 String content = result.content;
39 if (StringUtils.isNotEmpty(content)) {
40 // 解析变更的配置
41 String[] changedConfigs = content.split(LINE_SEPARATOR);
42 for (String config : changedConfigs) {
43 String[] parts = config.split(WORD_SEPARATOR);
44 String dataId = parts[0];
45 String group = parts[1];
46
47 // 拉取最新配置
48 String newContent = getServerConfig(dataId, group);
49 CacheData cacheData = cacheMap.get(groupKey(dataId, group));
50 cacheData.setContent(newContent);
51
52 // 通知监听器
53 cacheData.checkListenerMd5();
54
55 // 保存到本地快照
56 LocalConfigInfoProcessor.saveSnapshot(dataId, group, newContent);
57 }
58 }
59 }
60 } catch (Exception e) {
61 log.error("长轮询异常", e);
62 }
63 }
64 }
65
66 // 获取服务端配置
67 private String getServerConfig(String dataId, String group) {
68 HttpResult result = agent.httpGet(
69 "/v1/cs/configs",
70 Arrays.asList("dataId", dataId, "group", group),
71 null,
72 3000
73 );
74 return result.content;
75 }
76}

服务端实现

java
1@RestController
2@RequestMapping("/v1/cs/configs")
3public class ConfigController {
4
5 @Autowired
6 private LongPollingService longPollingService;
7
8 // 长轮询监听配置变更
9 @PostMapping("/listener")
10 public void listener(HttpServletRequest request, HttpServletResponse response) {
11 // 1. 解析客户端监听的配置列表
12 String probeModify = request.getParameter("Listening-Configs");
13 Map<String, String> clientMd5Map = MD5Util.getClientMd5Map(probeModify);
14
15 // 2. 检查配置是否变更
16 List<String> changedConfigs = new ArrayList<>();
17 for (Map.Entry<String, String> entry : clientMd5Map.entrySet()) {
18 String groupKey = entry.getKey();
19 String clientMd5 = entry.getValue();
20
21 // 从缓存获取服务端MD5
22 CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
23 if (cacheItem != null && !clientMd5.equals(cacheItem.getMd5())) {
24 // 配置已变更
25 changedConfigs.add(groupKey);
26 }
27 }
28
29 // 3. 如果有配置变更,立即返回
30 if (!changedConfigs.isEmpty()) {
31 response.getWriter().write(StringUtils.join(changedConfigs, LINE_SEPARATOR));
32 return;
33 }
34
35 // 4. 没有变更,挂起请求(长轮询)
36 String ip = RequestUtil.getRemoteIp(request);
37 AsyncContext asyncContext = request.startAsync();
38 asyncContext.setTimeout(29500L); // 29.5秒超时
39
40 // 注册监听器
41 ConfigExecutor.executeLongPolling(new ClientLongPolling(
42 asyncContext,
43 clientMd5Map,
44 ip
45 ));
46 }
47
48 // 发布配置
49 @PostMapping
50 public Result publishConfig(@RequestParam String dataId,
51 @RequestParam String group,
52 @RequestParam String content) {
53 // 1. 持久化到数据库
54 configInfoMapper.insertOrUpdate(dataId, group, content);
55
56 // 2. 更新内存缓存
57 String md5 = MD5Util.getMD5(content);
58 ConfigCacheService.updateMd5(groupKey(dataId, group), md5);
59
60 // 3. 通知所有监听该配置的客户端
61 longPollingService.notifyClients(dataId, group);
62
63 return Result.success();
64 }
65}
66
67// 长轮询服务
68@Service
69public class LongPollingService {
70 // 所有挂起的长轮询请求
71 private final Queue<ClientLongPolling> allSubs = new ConcurrentLinkedQueue<>();
72
73 // 客户端长轮询任务
74 class ClientLongPolling implements Runnable {
75 final AsyncContext asyncContext;
76 final Map<String, String> clientMd5Map;
77 final long createTime;
78
79 @Override
80 public void run() {
81 // 超时后返回空响应
82 asyncContext.getResponse().setStatus(HttpStatus.OK);
83 asyncContext.complete();
84 allSubs.remove(this);
85 }
86 }
87
88 // 通知客户端配置变更
89 public void notifyClients(String dataId, String group) {
90 String groupKey = groupKey(dataId, group);
91
92 for (ClientLongPolling client : allSubs) {
93 if (client.clientMd5Map.containsKey(groupKey)) {
94 try {
95 // 唤醒挂起的请求,返回变更通知
96 HttpServletResponse response =
97 (HttpServletResponse) client.asyncContext.getResponse();
98 response.getWriter().write(groupKey);
99 client.asyncContext.complete();
100 allSubs.remove(client);
101 } catch (Exception e) {
102 log.error("通知客户端失败", e);
103 }
104 }
105 }
106 }
107}

3. 配置更新流程

11. 客户端启动
2
32. 拉取配置并保存到本地缓存
4
53. 启动长轮询线程
6
74. 发送长轮询请求(携带配置MD5)
8
95. 服务端检查MD5是否变化
10 ├─ 已变化 → 立即返回变更通知
11 └─ 未变化 → 挂起请求29.5秒
12
136. 配置发生变更
14
157. 服务端通知所有监听的客户端
16
178. 客户端收到通知,拉取最新配置
18
199. 更新本地缓存
20
2110. 触发@RefreshScope刷新Bean
22
2311. 继续下一轮长轮询

4. 容灾机制

java
1public class ConfigService {
2
3 public String getConfig(String dataId, String group) {
4 try {
5 // 1. 优先从内存缓存获取
6 CacheData cacheData = cacheMap.get(groupKey(dataId, group));
7 if (cacheData != null) {
8 return cacheData.getContent();
9 }
10
11 // 2. 从服务端获取
12 String content = getServerConfig(dataId, group);
13 if (content != null) {
14 // 保存到本地快照
15 LocalConfigInfoProcessor.saveSnapshot(dataId, group, content);
16 return content;
17 }
18 } catch (Exception e) {
19 log.warn("从服务端获取配置失败,使用本地快照", e);
20 }
21
22 // 3. 服务端不可用,使用本地快照(容灾)
23 return LocalConfigInfoProcessor.getFailover(dataId, group);
24 }
25}

性能优化

  1. 批量长轮询:一个HTTP请求监听多个配置
  2. MD5校验:只传输MD5,减少网络开销
  3. 本地缓存:减少服务端压力
  4. 异步通知:配置变更异步通知,不阻塞主流程

4. 为什么需要服务注册发现?

服务注册发现的必要性

在微服务架构中,服务实例的数量和地址是动态变化的,传统的硬编码服务地址的方式已经无法满足需求。

传统方式的问题

java
1// 硬编码服务地址
2@RestController
3public class OrderController {
4
5 @GetMapping("/order/{id}")
6 public Order getOrder(@PathVariable Long id) {
7 // 硬编码用户服务地址
8 String userServiceUrl = "http://192.168.1.100:8080";
9 User user = restTemplate.getForObject(
10 userServiceUrl + "/user/" + userId,
11 User.class
12 );
13 // 问题:
14 // 1. 服务地址变更需要修改代码
15 // 2. 无法实现负载均衡
16 // 3. 无法感知服务健康状态
17 // 4. 服务扩容需要手动配置
18 }
19}

服务注册发现解决的问题

1. 动态服务管理

java
1// 服务提供者自动注册
2@SpringBootApplication
3@EnableDiscoveryClient
4public class UserServiceApplication {
5 public static void main(String[] args) {
6 SpringApplication.run(UserServiceApplication.class, args);
7 }
8}
9
10// application.yml
11spring:
12 application:
13 name: user-service
14 cloud:
15 nacos:
16 discovery:
17 server-addr: localhost:8848
18 ip: 192.168.1.100
19 port: 8080
20 weight: 1 # 权重
21 metadata:
22 version: v1.0
23 region: beijing

2. 自动服务发现和负载均衡

java
1@RestController
2public class OrderController {
3
4 @Autowired
5 private RestTemplate restTemplate; // 已配置@LoadBalanced
6
7 @Autowired
8 private DiscoveryClient discoveryClient;
9
10 @GetMapping("/order/{id}")
11 public Order getOrder(@PathVariable Long id) {
12 // 方式1:使用服务名调用(自动负载均衡)
13 User user = restTemplate.getForObject(
14 "http://user-service/user/" + userId,
15 User.class
16 );
17
18 // 方式2:手动获取服务实例
19 List<ServiceInstance> instances =
20 discoveryClient.getInstances("user-service");
21 if (!instances.isEmpty()) {
22 ServiceInstance instance = instances.get(0);
23 String url = "http://" + instance.getHost() + ":"
24 + instance.getPort() + "/user/" + userId;
25 user = restTemplate.getForObject(url, User.class);
26 }
27
28 return order;
29 }
30}
31
32// RestTemplate配置
33@Configuration
34public class RestTemplateConfig {
35
36 @Bean
37 @LoadBalanced // 开启负载均衡
38 public RestTemplate restTemplate() {
39 return new RestTemplate();
40 }
41}

3. 健康检查和故障隔离

java
1// Nacos健康检查配置
2spring:
3 cloud:
4 nacos:
5 discovery:
6 heart-beat-interval: 5000 # 心跳间隔5
7 heart-beat-timeout: 15000 # 15秒未收到心跳标记不健康
8 ip-delete-timeout: 30000 # 30秒未收到心跳删除实例
9
10// 自定义健康检查
11@Component
12public class CustomHealthIndicator implements HealthIndicator {
13
14 @Override
15 public Health health() {
16 // 检查数据库连接
17 if (checkDatabase()) {
18 return Health.up()
19 .withDetail("database", "available")
20 .build();
21 }
22 return Health.down()
23 .withDetail("database", "unavailable")
24 .build();
25 }
26}

4. 服务元数据管理

java
1// 注册时携带元数据
2@Configuration
3public class NacosConfig {
4
5 @Bean
6 public NacosDiscoveryProperties nacosProperties() {
7 NacosDiscoveryProperties properties = new NacosDiscoveryProperties();
8
9 Map<String, String> metadata = new HashMap<>();
10 metadata.put("version", "v1.0");
11 metadata.put("region", "beijing");
12 metadata.put("env", "prod");
13
14 properties.setMetadata(metadata);
15 return properties;
16 }
17}
18
19// 根据元数据过滤服务
20@Service
21public class UserServiceClient {
22
23 @Autowired
24 private NacosDiscoveryClient discoveryClient;
25
26 public List<ServiceInstance> getServicesByVersion(String version) {
27 List<ServiceInstance> instances =
28 discoveryClient.getInstances("user-service");
29
30 return instances.stream()
31 .filter(instance ->
32 version.equals(instance.getMetadata().get("version")))
33 .collect(Collectors.toList());
34 }
35}

服务注册发现流程

1服务启动
2
31. 读取配置(服务名、IP、端口)
4
52. 向注册中心注册服务实例
6
73. 定期发送心跳(保持注册状态)
8
94. 拉取服务列表并缓存
10
115. 监听服务变更
12
136. 服务调用时从本地缓存获取实例
14
157. 通过负载均衡算法选择实例
16
178. 发起HTTP/RPC调用
18
199. 服务下线时注销注册

核心优势总结

  1. 解耦:服务消费者无需知道提供者的具体地址
  2. 弹性:支持服务动态扩缩容
  3. 容错:自动剔除不健康实例
  4. 负载均衡:自动分配流量
  5. 可观测:实时查看服务状态

5. 为什么需要在微服务中使用链路追踪?Spring Cloud 可以选择哪些微服务链路追踪方案?

链路追踪的必要性

在微服务架构中,一个用户请求可能经过多个服务,如何快速定位问题和分析性能瓶颈成为关键挑战。

微服务调用链路示例

1用户请求 → API网关 → 订单服务 → 用户服务 → 数据库
2
3 库存服务 → Redis
4
5 支付服务 → 第三方支付API

没有链路追踪的问题

java
1// 订单服务日志
22024-01-01 10:00:01 [order-service] 创建订单开始, orderId=123
32024-01-01 10:00:05 [order-service] 创建订单完成, orderId=123, 耗时=4s
4
5// 用户服务日志
62024-01-01 10:00:02 [user-service] 查询用户, userId=456
72024-01-01 10:00:03 [user-service] 查询完成, userId=456
8
9// 问题:
10// 1. 无法关联同一个请求的所有日志
11// 2. 不知道整个请求链路的调用顺序
12// 3. 无法定位哪个服务是瓶颈
13// 4. 排查问题需要查看多个服务的日志

链路追踪解决方案

1. Sleuth + Zipkin(经典方案)

依赖配置

xml
1<!-- Sleuth -->
2<dependency>
3 <groupId>org.springframework.cloud</groupId>
4 <artifactId>spring-cloud-starter-sleuth</artifactId>
5</dependency>
6
7<!-- Zipkin -->
8<dependency>
9 <groupId>org.springframework.cloud</groupId>
10 <artifactId>spring-cloud-sleuth-zipkin</artifactId>
11</dependency>

配置

yaml
1spring:
2 application:
3 name: order-service
4 zipkin:
5 base-url: http://localhost:9411 # Zipkin服务器地址
6 sender:
7 type: web # 发送方式:web/kafka/rabbitmq
8 sleuth:
9 sampler:
10 probability: 1.0 # 采样率:1.0表示100%采样
11 web:
12 client:
13 enabled: true
14 feign:
15 enabled: true
16 messaging:
17 enabled: true

自动生成TraceId和SpanId

java
1@RestController
2@Slf4j
3public class OrderController {
4
5 @Autowired
6 private RestTemplate restTemplate;
7
8 @GetMapping("/order/{id}")
9 public Order createOrder(@PathVariable Long id) {
10 // Sleuth自动在日志中添加TraceId和SpanId
11 log.info("开始创建订单");
12 // 输出:[order-service,a1b2c3d4,e5f6g7h8,true] 开始创建订单
13 // [应用名,TraceId,SpanId,是否导出到Zipkin]
14
15 // 调用用户服务(自动传递TraceId)
16 User user = restTemplate.getForObject(
17 "http://user-service/user/" + userId,
18 User.class
19 );
20
21 // 调用库存服务
22 Stock stock = restTemplate.getForObject(
23 "http://stock-service/stock/deduct",
24 Stock.class
25 );
26
27 log.info("订单创建完成");
28 return order;
29 }
30}

TraceId传递原理

java
1// Sleuth拦截器自动在HTTP Header中添加TraceId
2public class TraceFilter implements Filter {
3
4 @Override
5 public void doFilter(ServletRequest request, ServletResponse response,
6 FilterChain chain) {
7 HttpServletRequest httpRequest = (HttpServletRequest) request;
8
9 // 从请求头获取或生成TraceId
10 String traceId = httpRequest.getHeader("X-B3-TraceId");
11 if (traceId == null) {
12 traceId = generateTraceId();
13 }
14
15 // 存入ThreadLocal
16 TraceContext.setTraceId(traceId);
17
18 try {
19 chain.doFilter(request, response);
20 } finally {
21 TraceContext.clear();
22 }
23 }
24}
25
26// RestTemplate拦截器自动传递TraceId
27public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor {
28
29 @Override
30 public ClientHttpResponse intercept(HttpRequest request, byte[] body,
31 ClientHttpRequestExecution execution) {
32 // 从ThreadLocal获取TraceId
33 String traceId = TraceContext.getTraceId();
34
35 // 添加到请求头
36 request.getHeaders().add("X-B3-TraceId", traceId);
37 request.getHeaders().add("X-B3-SpanId", generateSpanId());
38
39 return execution.execute(request, body);
40 }
41}

Zipkin UI查看链路

1访问 http://localhost:9411
2
3链路追踪展示:
4┌─────────────────────────────────────────────────┐
5│ TraceId: a1b2c3d4e5f6g7h8 │
6│ 总耗时: 450ms │
7├─────────────────────────────────────────────────┤
8│ order-service [========] 450ms │
9│ ├─ user-service [===] 100ms │
10│ ├─ stock-service [====] 150ms │
11│ └─ payment-service [=====] 200ms │
12└─────────────────────────────────────────────────┘

2. SkyWalking(推荐方案)

特点

  • 无侵入:通过Java Agent实现
  • 功能强大:支持多种中间件
  • 中文文档:国内使用广泛

使用方式

bash
1# 下载SkyWalking Agent
2wget https://archive.apache.org/dist/skywalking/8.9.0/apache-skywalking-apm-8.9.0.tar.gz
3
4# 启动应用时添加agent
5java -javaagent:/path/to/skywalking-agent.jar \
6 -Dskywalking.agent.service_name=order-service \
7 -Dskywalking.collector.backend_service=localhost:11800 \
8 -jar order-service.jar

自定义追踪

java
1@Component
2public class CustomTracer {
3
4 @Autowired
5 private Tracer tracer;
6
7 public void customTrace() {
8 // 创建自定义Span
9 AbstractSpan span = tracer.createLocalSpan("custom-operation");
10 span.tag("userId", "123");
11 span.tag("orderId", "456");
12
13 try {
14 // 业务逻辑
15 doSomething();
16 } catch (Exception e) {
17 span.errorOccurred();
18 span.log(e);
19 } finally {
20 tracer.stopSpan();
21 }
22 }
23}

3. Jaeger(云原生方案)

特点

  • CNCF项目
  • 高性能
  • 支持OpenTracing标准

配置

yaml
1opentracing:
2 jaeger:
3 http-sender:
4 url: http://localhost:14268/api/traces
5 probabilistic-sampler:
6 sampling-rate: 1.0
7 log-spans: true

方案对比

特性Sleuth+ZipkinSkyWalkingJaeger
侵入性低(依赖)无(Agent)
性能开销
功能基础强大强大
UI简单丰富丰富
中文文档一般完善一般
学习成本
社区活跃活跃活跃

链路追踪的核心价值

  1. 快速定位问题

    • 哪个服务出错
    • 哪个接口慢
    • 调用链路是否正常
  2. 性能分析

    • 各服务耗时占比
    • 识别性能瓶颈
    • 优化调用链路
  3. 依赖分析

    • 服务依赖关系
    • 调用频率统计
    • 影响范围评估
  4. 容量规划

    • 服务调用量统计
    • 资源使用情况
    • 扩容决策依据

6. Spring Cloud 的优缺点有哪些?

Spring Cloud优点详解

1. 生态完善

1Spring Cloud生态组件:
2┌─────────────────────────────────────────┐
3│ 服务治理层 │
4│ - Eureka/Nacos (服务注册发现) │
5│ - Ribbon/LoadBalancer (负载均衡) │
6│ - Feign/OpenFeign (服务调用) │
7├─────────────────────────────────────────┤
8│ 容错保护层 │
9│ - Hystrix/Sentinel (熔断降级) │
10│ - Resilience4j (容错库) │
11├─────────────────────────────────────────┤
12│ 网关层 │
13│ - Gateway/Zuul (API网关) │
14├─────────────────────────────────────────┤
15│ 配置管理层 │
16│ - Config/Nacos Config (配置中心) │
17│ - Bus (消息总线) │
18├─────────────────────────────────────────┤
19│ 监控追踪层 │
20│ - Sleuth (链路追踪) │
21│ - Zipkin (追踪展示) │
22│ - Admin (服务监控) │
23├─────────────────────────────────────────┤
24│ 安全层 │
25│ - Security (安全框架) │
26│ - OAuth2 (认证授权) │
27└─────────────────────────────────────────┘

2. 开箱即用

java
1// 只需添加依赖和注解即可使用
2@SpringBootApplication
3@EnableDiscoveryClient // 服务注册
4@EnableFeignClients // 服务调用
5@EnableCircuitBreaker // 熔断降级
6public class Application {
7 public static void main(String[] args) {
8 SpringApplication.run(Application.class, args);
9 }
10}
11
12// 声明式服务调用
13@FeignClient("user-service")
14public interface UserClient {
15 @GetMapping("/user/{id}")
16 User getUser(@PathVariable Long id);
17}
18
19// 自动负载均衡
20@Autowired
21private UserClient userClient;
22
23public User getUser(Long id) {
24 return userClient.getUser(id); // 自动负载均衡
25}

3. 与Spring Boot无缝集成

yaml
1# 统一的配置方式
2spring:
3 application:
4 name: order-service
5 cloud:
6 nacos:
7 discovery:
8 server-addr: localhost:8848
9 sentinel:
10 transport:
11 dashboard: localhost:8080

4. 社区活跃,文档完善

  • GitHub Star数高
  • 问题响应快
  • 中文文档丰富
  • 大量实践案例

Spring Cloud缺点详解

1. 版本兼容问题

xml
1<!-- 版本依赖复杂 -->
2<properties>
3 <spring-boot.version>2.6.13</spring-boot.version>
4 <spring-cloud.version>2021.0.5</spring-cloud.version>
5</properties>
6
7<!-- 版本对应关系 -->
8Spring Boot 2.6.x → Spring Cloud 2021.0.x
9Spring Boot 2.7.x → Spring Cloud 2021.0.x
10Spring Boot 3.0.x → Spring Cloud 2022.0.x
11
12<!-- 组件版本也需要匹配 -->
13<dependency>
14 <groupId>com.alibaba.cloud</groupId>
15 <artifactId>spring-cloud-alibaba-dependencies</artifactId>
16 <version>2021.0.4.0</version>
17</dependency>

问题

  • 升级困难,需要整体升级
  • 不同版本API可能不兼容
  • 需要严格的版本管理

2. 性能开销

java
1// HTTP通信开销
2@FeignClient("user-service")
3public interface UserClient {
4 @GetMapping("/user/{id}")
5 User getUser(@PathVariable Long id);
6}
7
8// 每次调用都需要:
9// 1. HTTP连接建立
10// 2. JSON序列化/反序列化
11// 3. 网络传输
12// 4. 负载均衡计算
13
14// 对比RPC(如Dubbo):
15// 1. 长连接复用
16// 2. 二进制序列化(更快)
17// 3. 直接TCP通信
18// 4. 性能更高

性能对比

1HTTP REST (Spring Cloud): ~5-10ms
2RPC (Dubbo): ~1-2ms
3本地方法调用: ~0.01ms

3. 学习成本高

1需要掌握的知识:
21. Spring Boot基础
32. Spring Cloud各组件
43. 微服务架构理念
54. 分布式系统知识
65. 容器化部署(Docker/K8s)
76. 监控运维工具
8
9学习路径长,上手周期约2-3个月

4. 运维复杂

1运维挑战:
21. 服务数量多(可能几十上百个)
32. 配置管理复杂
43. 日志分散,排查困难
54. 监控指标多
65. 部署流程复杂
76. 故障定位困难
8
9需要完善的运维体系:
10- CI/CD流水线
11- 日志收集(ELK)
12- 监控告警(Prometheus+Grafana)
13- 链路追踪(SkyWalking)
14- 配置中心(Nacos)
15- 服务网格(Istio)

适用场景

适合使用Spring Cloud的场景

  1. 中大型互联网项目
  2. 业务复杂,需要拆分服务
  3. 团队规模较大(大于20人)
  4. 需要快速迭代
  5. 对性能要求不是极致
  6. 团队熟悉Spring生态

不适合使用Spring Cloud的场景

  1. 小型项目(单体更合适)
  2. 对性能要求极高(考虑Dubbo)
  3. 团队规模小(小于5人)
  4. 运维能力不足
  5. 业务简单,不需要拆分

最佳实践建议

  1. 版本管理

    • 使用BOM统一管理版本
    • 定期升级,但不追新
    • 建立版本兼容性测试
  2. 性能优化

    • 合理设置超时时间
    • 使用连接池
    • 开启HTTP/2
    • 考虑gRPC替代REST
  3. 降低复杂度

    • 从核心组件开始
    • 渐进式引入
    • 做好文档和培训
    • 建立最佳实践规范
  4. 运维自动化

    • 容器化部署
    • 自动化监控
    • 统一日志收集
    • 完善的告警机制

7. Spring Boot 和 Spring Cloud 之间的区别?

Spring Boot:快速开发单体应用的框架,简化配置

Spring Cloud:微服务架构解决方案,提供服务治理能力

关系:Spring Cloud基于Spring Boot构建

8. Spring Cloud 由什么组成?

核心组件

  1. 注册中心:Eureka、Nacos、Consul
  2. 服务调用:Feign、Ribbon、LoadBalancer
  3. 熔断降级:Hystrix、Sentinel、Resilience4j
  4. 服务网关:Zuul、Gateway
  5. 配置中心:Config、Nacos Config
  6. 链路追踪:Sleuth、Zipkin

9. 你是怎么理解微服务的?

定义:将单一应用拆分成一组小型服务,每个服务独立运行,通过轻量级通信交互。

特征:服务独立、单一职责、去中心化、容错设计

10. 单体应用、SOA、微服务架构有什么区别?

单体应用:所有功能在一个应用中,部署简单但扩展困难

SOA:通过ESB进行服务编排,服务粒度较大,重量级协议

微服务:服务细粒度拆分,轻量级通信,独立数据库,去中心化

11. Spring Cloud Config 是什么?

定义:Spring Cloud提供的集中化配置管理工具,支持配置的版本管理和动态刷新。

功能:集中管理配置、环境隔离、基于Git的版本管理、配置动态刷新

12. 什么情况下需要使用分布式事务,有哪些方案?

场景:跨服务数据一致性、跨数据库操作、消息队列可靠性

方案:2PC、TCC、Saga、本地消息表、最大努力通知、Seata

13. 你们的服务是怎么做日志收集的?

方案:ELK栈(Elasticsearch + Logstash + Kibana)或EFK(Fluentd替代Logstash)

流程:应用输出日志 → Filebeat收集 → Logstash处理 → Elasticsearch存储 → Kibana展示

14. 说一下你对于 DDD 的了解?

DDD(领域驱动设计):一种软件设计方法论,强调以业务领域为中心进行设计。

核心概念:领域、实体、值对象、聚合、限界上下文、领域事件

15. 什么是 Seata?

Seata:阿里巴巴开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。

支持模式:AT、TCC、Saga、XA

16. Seata 支持哪些模式的分布式事务?

AT模式:自动的两阶段提交,无侵入,自动生成补偿SQL

TCC模式:Try-Confirm-Cancel,需要手动编写三个方法

Saga模式:长事务解决方案,正向流程+补偿流程

XA模式:传统XA协议,强一致性

17. 了解 Seata 的实现原理吗?

架构:TC(事务协调器)+ TM(事务管理器)+ RM(资源管理器)

流程:TM开启全局事务 → RM注册分支事务 → 执行业务 → TM提交/回滚 → TC协调RM提交/回滚

18. Seata 的事务回滚是怎么实现的?

AT模式:通过undo_log表记录前后镜像,回滚时执行反向SQL

TCC模式:调用Cancel方法执行补偿逻辑

19. 分布式和微服务有什么区别?

分布式:是一种系统架构,强调系统的分布式部署

微服务:是一种架构风格,强调服务的细粒度拆分和独立性

关系:微服务一定是分布式,但分布式不一定是微服务

20. Spring Cloud 有哪些注册中心?

注册中心:Eureka、Nacos、Consul、Zookeeper

21. 什么是 Eureka?

Eureka:Netflix开源的服务注册与发现组件,采用AP模式(可用性+分区容错)

组成:Eureka Server(注册中心)+ Eureka Client(服务实例)

22. Eureka 的实现原理说一下?

核心机制

  1. 服务注册:服务启动时向Eureka Server注册
  2. 心跳续约:每30秒发送心跳,90秒未收到则剔除
  3. 服务发现:客户端从注册中心获取服务列表,本地缓存
  4. 自我保护:网络故障时不剔除服务

23. Spring Cloud 如何实现服务注册?

注解@EnableDiscoveryClient@EnableEurekaClient

配置:在application.yml中配置注册中心地址

自动注册:应用启动后自动向注册中心注册

24. Consul 是什么?

Consul:HashiCorp开源的服务网格解决方案,提供服务发现、配置管理、健康检查。

特点:支持CP和AP模式、多数据中心、强一致性

25. Eureka、Zookeeper、Nacos、Consul 的区别?

特性EurekaZookeeperNacosConsul
CAPAPCPAP/CPCP/AP
健康检查心跳长连接心跳/HTTPTCP/HTTP
配置中心不支持支持支持支持
语言JavaJavaJavaGo

26. Nacos 中的 Namespace 是什么?

Namespace:命名空间,用于实现环境隔离(dev/test/prod)

作用:不同环境的服务和配置相互隔离,互不影响

27. 为什么需要负载均衡?

原因

  1. 提高系统处理能力
  2. 避免单点故障
  3. 合理分配流量
  4. 提升系统可用性

28. Spring Cloud 负载均衡的实现方式有哪些?

客户端负载均衡:Ribbon、LoadBalancer

服务端负载均衡:Nginx、Gateway

29. 负载均衡算法有哪些?

常见算法

  1. 轮询:依次分配
  2. 随机:随机选择
  3. 加权轮询:根据权重分配
  4. 最少连接:选择连接数最少的
  5. 一致性Hash:根据IP Hash分配

30. HTTP 与 RPC 之间的区别?

特性HTTPRPC
协议HTTP协议自定义协议
性能较低
跨语言支持部分支持
复杂度简单复杂
适用场景对外API内部服务

31. 什么是 Feign?

Feign:Netflix开源的声明式HTTP客户端,简化服务间调用。

特点:声明式、集成Ribbon负载均衡、支持多种编解码器

32. Feign 是如何实现负载均衡的?

实现:Feign集成了Ribbon,通过Ribbon实现客户端负载均衡。

流程:Feign调用 → Ribbon选择服务实例 → 发起HTTP请求

33. Feign 为什么第一次调用耗时很长?

原因

  1. Ribbon需要从注册中心拉取服务列表
  2. Ribbon延迟加载,首次请求才初始化

解决:配置饥饿加载(eager-load)

34. Feign 和 OpenFeign 的区别?

Feign:Netflix开源,已停更

OpenFeign:Spring Cloud官方维护,支持Spring MVC注解

推荐:使用OpenFeign

35. Feign 和 Dubbo 的区别?

特性FeignDubbo
协议HTTPRPC
性能
跨语言支持有限
服务治理依赖Spring Cloud内置
适用微服务分布式

36. 什么是熔断器?为什么需要熔断器?

熔断器:当服务失败达到阈值时,自动断开服务调用,防止雪崩。

作用:防止服务雪崩、快速失败、保护系统资源

37. 什么是 Hystrix?

Hystrix:Netflix开源的容错管理库,提供熔断、降级、隔离、限流功能。

状态:已停更,推荐使用Sentinel或Resilience4j

38. Hystrix 什么是服务雪崩?

服务雪崩:一个服务失败导致上游服务级联失败,最终整个系统崩溃。

防止:熔断、降级、限流、超时控制

39. 什么是服务降级?

服务降级:当服务压力过大或不可用时,返回默认结果或简化结果。

场景:服务异常、超时、高并发

40. 什么是服务熔断?

服务熔断:当服务失败率达到阈值时,熔断器打开,直接返回错误。

状态:关闭、打开、半开

41. 什么是服务限流?

服务限流:限制服务的并发访问量,防止系统过载。

算法:计数器、滑动窗口、漏桶、令牌桶

42. Sentinel 是怎么实现限流的?

实现:基于滑动窗口算法,统计请求数量,超过阈值则拒绝。

特点:实时监控、多维度限流、控制台配置

43. Sentinel 与 Hystrix 的区别是什么?

特性SentinelHystrix
维护状态活跃停更
限流支持不支持
控制台
实时指标支持不支持
规则配置动态静态

44. Sentinel 是怎么实现集群限流的?

实现:通过Token Server统一分配令牌,各节点向Token Server申请令牌。

流程:请求 → 申请令牌 → Token Server分配 → 执行或拒绝

45. 什么是服务网格?

服务网格(Service Mesh):专用的基础设施层,处理服务间通信。

代表:Istio、Linkerd

特点:无侵入、服务治理、可观测性

46. 什么是灰度发布、金丝雀部署以及蓝绿部署?

蓝绿部署:同时运行两个版本,切换流量,问题可快速回滚

金丝雀部署:小流量验证新版本,逐步扩大流量

灰度发布:按用户特征分配流量,部分用户使用新版本

47. 什么是微服务网关?为什么需要服务网关?

微服务网关:系统的入口,路由转发请求到后端服务。

作用:路由转发、负载均衡、身份认证、限流熔断、日志监控

48. Spring Cloud 可以选择哪些 API 网关?

网关组件:Spring Cloud Gateway、Zuul、Kong、Nginx

推荐:Spring Cloud Gateway(官方维护,性能好)

49. 什么是 Spring Cloud Zuul?

Zuul:Netflix开源的API网关,基于Servlet实现。

状态:已停更,不推荐使用

50. 什么是 Spring Cloud Gateway?

Gateway:Spring Cloud官方网关,基于WebFlux实现,支持响应式。

特点:高性能、非阻塞、支持谓词和过滤器

51. 你项目里为什么选择 Gateway 作为网关?

原因

  1. Spring官方维护,持续更新
  2. 基于WebFlux,性能高
  3. 支持响应式编程
  4. 配置灵活,扩展性强

52. Spring Cloud 说说什么是 API 网关?它有什么作用?

API网关:系统对外的统一入口,负责路由转发和请求聚合。

作用:路由、认证、限流、熔断、监控、协议转换

53. Dubbo 和 Spring Cloud Gateway 有什么区别?

Dubbo:RPC框架,用于服务间调用

Gateway:API网关,用于请求路由和转发

区别:不同层次,不同作用,可以结合使用

54. 什么是令牌桶算法?工作原理是什么?使用它有哪些优点和注意事项?

原理:按固定速率往桶中放令牌,请求需要获取令牌才能执行。

优点:允许突发流量、平滑限流、简单高效

注意:需要合理设置桶容量和令牌生成速率

55. Spring Cloud 有哪些核心组件?

核心组件

  1. 注册中心:Eureka、Nacos、Consul
  2. 服务调用:Feign、OpenFeign、Ribbon
  3. 熔断降级:Hystrix、Sentinel、Resilience4j
  4. 服务网关:Gateway、Zuul
  5. 配置中心:Config、Nacos Config
  6. 链路追踪:Sleuth、Zipkin
  7. 消息总线:Bus

学习指南

核心要点:

  • 微服务架构设计原则
  • Spring Cloud 核心组件
  • 服务治理和注册发现
  • 分布式系统设计模式

学习路径建议:

  1. 掌握微服务架构的基本概念
  2. 熟悉Spring Cloud的核心组件
  3. 理解服务治理和负载均衡
  4. 学习分布式事务和链路追踪
forum

评论区 / Comments