Java SpringCloud 面试题集
总题数: 55道 | 重点领域: 微服务、服务治理、分布式系统 | 难度分布: 高级
本文档整理了 Java Spring Cloud 的完整55道面试题目,涵盖微服务架构、服务治理、分布式系统等各个方面。
面试题目列表
1. 什么是分布式事务的防悬挂,空回滚?
这是Seata TCC模式中处理异常情况的两个重要机制。
空回滚(Empty Rollback):
定义:Try阶段未执行或执行失败,但Cancel方法被调用的情况。
产生原因:
- 网络延迟导致Try请求超时,TM(事务管理器)认为Try失败
- TM发起全局回滚,调用所有分支的Cancel方法
- 但实际上Try请求可能还在网络中传输,尚未到达
问题:Cancel方法找不到Try阶段的业务数据,无法执行回滚操作。
解决方案:
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}1011@Service12public class AccountServiceImpl implements AccountService {13 14 @Override15 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已经执行完毕。
解决方案:
1@Override2public 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已执行,拒绝执行Try9 log.warn("防悬挂,Cancel已执行,拒绝Try,xid: {}", xid);10 return false;11 }12 13 // 幂等性检查:是否已经执行过Try14 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}数据库表设计:
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_TIMESTAMP8);910-- 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_TIMESTAMP15);完整流程图:
1正常流程:2Try → Confirm3 ↓4记录Try状态 → 提交事务56空回滚流程:7Try超时/失败 → Cancel被调用8 ↓9检查Try状态 → 未执行 → 记录Cancel状态 → 返回成功1011防悬挂流程:12Cancel先执行 → 记录Cancel状态13 ↓14Try延迟到达 → 检查Cancel状态 → 已执行 → 拒绝Try2. 什么是配置中心?有哪些常见的配置中心?
配置中心定义:
配置中心是微服务架构中用于集中管理所有服务配置的基础设施,支持配置的统一管理、动态更新、版本控制和权限管理。
为什么需要配置中心:
-
配置集中管理:
- 传统方式:配置分散在各个服务的配置文件中
- 问题:修改配置需要重新打包部署,管理困难
- 解决:统一管理所有服务的配置
-
动态更新:
- 传统方式:修改配置需要重启服务
- 问题:服务重启影响可用性
- 解决:配置修改后实时生效,无需重启
-
环境隔离:
- 需求:开发、测试、生产环境配置不同
- 解决:通过namespace或profile实现环境隔离
-
版本管理:
- 需求:配置变更历史追溯和回滚
- 解决:配置版本化管理,支持快速回滚
常见配置中心对比:
1. Spring Cloud Config:
特点:
- Spring官方提供,与Spring生态无缝集成
- 基于Git存储,天然支持版本管理
- 需要手动刷新配置(通过/actuator/refresh)
配置示例:
1# Config Server配置2spring:3 application:4 name: config-server5 cloud:6 config:7 server:8 git:9 uri: https://github.com/your-repo/config-repo10 search-paths: config/{application}11 username: your-username12 password: your-password13 default-label: master14 clone-on-start: true # 启动时克隆仓库15 force-pull: true # 强制拉取最新配置1617# Config Client配置18spring:19 application:20 name: user-service21 cloud:22 config:23 uri: http://localhost:888824 profile: dev25 label: master26 fail-fast: true # 连接失败快速失败27 retry:28 max-attempts: 629 initial-interval: 1000动态刷新:
1@RestController2@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}1314// 刷新配置15// POST http://localhost:8080/actuator/refresh2. Nacos Config:
特点:
- 阿里巴巴开源,功能强大
- 支持配置和服务发现
- 自动推送配置变更,无需手动刷新
- 提供Web控制台
配置示例:
1spring:2 application:3 name: user-service4 cloud:5 nacos:6 config:7 server-addr: localhost:88488 namespace: dev # 命名空间,实现环境隔离9 group: DEFAULT_GROUP # 配置分组10 file-extension: yaml11 refresh-enabled: true # 自动刷新12 extension-configs:13 - data-id: common.yaml14 group: DEFAULT_GROUP15 refresh: true使用示例:
1@RestController2@RefreshScope3public 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:
特点:
- 携程开源,企业级配置中心
- 功能最完善,支持灰度发布
- 提供完善的权限管理和审计功能
- 客户端配置缓存,容灾能力强
配置示例:
1# app.properties2app.id=user-service3apollo.meta=http://localhost:80804apollo.bootstrap.enabled=true5apollo.bootstrap.namespaces=application,database1@Configuration2public class ApolloConfig {3 4 @ApolloConfig5 private Config config;6 7 @ApolloConfigChangeListener8 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 Config | Nacos | Apollo | Consul |
|---|---|---|---|---|
| 开源公司 | Spring | 阿里巴巴 | 携程 | HashiCorp |
| 配置存储 | Git | MySQL | MySQL | KV存储 |
| 配置界面 | 无 | 有 | 有 | 有 |
| 动态刷新 | 手动 | 自动 | 自动 | 支持 |
| 版本管理 | Git | 内置 | 内置 | 有限 |
| 权限管理 | 无 | 简单 | 完善 | 有 |
| 灰度发布 | 不支持 | 支持 | 支持 | 不支持 |
| 多环境 | Profile | Namespace | Namespace | 文件夹 |
| 客户端缓存 | 无 | 有 | 有 | 有 |
| 配置回滚 | 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 Polling12┌─────────────────────────────────────────────┐13│ Nacos Server │14│ ┌──────────────────────────────────────┐ │15│ │ ConfigController (配置接口) │ │16│ ├──────────────────────────────────────┤ │17│ │ LongPollingService (长轮询服务) │ │18│ ├──────────────────────────────────────┤ │19│ │ ConfigCacheService (配置缓存) │ │20│ ├──────────────────────────────────────┤ │21│ │ DumpService (配置持久化) │ │22│ └──────────────────────────────────────┘ │23└─────────────────────────────────────────────┘24 ↓25 ┌──────────────┐26 │ MySQL │27 └──────────────┘核心实现原理:
1. 配置存储(三级缓存):
1// 第一级:内存缓存(ConcurrentHashMap)2private final ConcurrentHashMap<String, CacheItem> cache = new ConcurrentHashMap<>();34public class CacheItem {5 private String content; // 配置内容6 private String md5; // 配置MD5值7 private long lastModified; // 最后修改时间8}910// 第二级:本地文件缓存(容灾)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}2526// 第三级:MySQL持久化27@Mapper28public 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长轮询):
客户端实现:
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 @Override15 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 TIMEOUT34 );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 300073 );74 return result.content;75 }76}服务端实现:
1@RestController2@RequestMapping("/v1/cs/configs")3public class ConfigController {4 5 @Autowired6 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 // 从缓存获取服务端MD522 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 ip45 ));46 }47 48 // 发布配置49 @PostMapping50 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}6667// 长轮询服务68@Service69public 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 @Override80 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刷新Bean22 ↓2311. 继续下一轮长轮询4. 容灾机制:
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}性能优化:
- 批量长轮询:一个HTTP请求监听多个配置
- MD5校验:只传输MD5,减少网络开销
- 本地缓存:减少服务端压力
- 异步通知:配置变更异步通知,不阻塞主流程
4. 为什么需要服务注册发现?
服务注册发现的必要性:
在微服务架构中,服务实例的数量和地址是动态变化的,传统的硬编码服务地址的方式已经无法满足需求。
传统方式的问题:
1// 硬编码服务地址2@RestController3public 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.class12 );13 // 问题:14 // 1. 服务地址变更需要修改代码15 // 2. 无法实现负载均衡16 // 3. 无法感知服务健康状态17 // 4. 服务扩容需要手动配置18 }19}服务注册发现解决的问题:
1. 动态服务管理:
1// 服务提供者自动注册2@SpringBootApplication3@EnableDiscoveryClient4public class UserServiceApplication {5 public static void main(String[] args) {6 SpringApplication.run(UserServiceApplication.class, args);7 }8}910// application.yml11spring:12 application:13 name: user-service14 cloud:15 nacos:16 discovery:17 server-addr: localhost:884818 ip: 192.168.1.10019 port: 808020 weight: 1 # 权重21 metadata:22 version: v1.023 region: beijing2. 自动服务发现和负载均衡:
1@RestController2public class OrderController {3 4 @Autowired5 private RestTemplate restTemplate; // 已配置@LoadBalanced6 7 @Autowired8 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.class16 );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}3132// RestTemplate配置33@Configuration34public class RestTemplateConfig {35 36 @Bean37 @LoadBalanced // 开启负载均衡38 public RestTemplate restTemplate() {39 return new RestTemplate();40 }41}3. 健康检查和故障隔离:
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秒未收到心跳删除实例910// 自定义健康检查11@Component12public class CustomHealthIndicator implements HealthIndicator {13 14 @Override15 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. 服务元数据管理:
1// 注册时携带元数据2@Configuration3public class NacosConfig {4 5 @Bean6 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}1819// 根据元数据过滤服务20@Service21public class UserServiceClient {22 23 @Autowired24 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. 服务下线时注销注册核心优势总结:
- 解耦:服务消费者无需知道提供者的具体地址
- 弹性:支持服务动态扩缩容
- 容错:自动剔除不健康实例
- 负载均衡:自动分配流量
- 可观测:实时查看服务状态
5. 为什么需要在微服务中使用链路追踪?Spring Cloud 可以选择哪些微服务链路追踪方案?
链路追踪的必要性:
在微服务架构中,一个用户请求可能经过多个服务,如何快速定位问题和分析性能瓶颈成为关键挑战。
微服务调用链路示例:
1用户请求 → API网关 → 订单服务 → 用户服务 → 数据库2 ↓3 库存服务 → Redis4 ↓5 支付服务 → 第三方支付API没有链路追踪的问题:
1// 订单服务日志22024-01-01 10:00:01 [order-service] 创建订单开始, orderId=12332024-01-01 10:00:05 [order-service] 创建订单完成, orderId=123, 耗时=4s45// 用户服务日志 62024-01-01 10:00:02 [user-service] 查询用户, userId=45672024-01-01 10:00:03 [user-service] 查询完成, userId=45689// 问题:10// 1. 无法关联同一个请求的所有日志11// 2. 不知道整个请求链路的调用顺序12// 3. 无法定位哪个服务是瓶颈13// 4. 排查问题需要查看多个服务的日志链路追踪解决方案:
1. Sleuth + Zipkin(经典方案):
依赖配置:
1<!-- Sleuth -->2<dependency>3 <groupId>org.springframework.cloud</groupId>4 <artifactId>spring-cloud-starter-sleuth</artifactId>5</dependency>67<!-- Zipkin -->8<dependency>9 <groupId>org.springframework.cloud</groupId>10 <artifactId>spring-cloud-sleuth-zipkin</artifactId>11</dependency>配置:
1spring:2 application:3 name: order-service4 zipkin:5 base-url: http://localhost:9411 # Zipkin服务器地址6 sender:7 type: web # 发送方式:web/kafka/rabbitmq8 sleuth:9 sampler:10 probability: 1.0 # 采样率:1.0表示100%采样11 web:12 client:13 enabled: true14 feign:15 enabled: true16 messaging:17 enabled: true自动生成TraceId和SpanId:
1@RestController2@Slf4j3public class OrderController {4 5 @Autowired6 private RestTemplate restTemplate;7 8 @GetMapping("/order/{id}")9 public Order createOrder(@PathVariable Long id) {10 // Sleuth自动在日志中添加TraceId和SpanId11 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.class19 );20 21 // 调用库存服务22 Stock stock = restTemplate.getForObject(23 "http://stock-service/stock/deduct",24 Stock.class25 );26 27 log.info("订单创建完成");28 return order;29 }30}TraceId传递原理:
1// Sleuth拦截器自动在HTTP Header中添加TraceId2public class TraceFilter implements Filter {3 4 @Override5 public void doFilter(ServletRequest request, ServletResponse response, 6 FilterChain chain) {7 HttpServletRequest httpRequest = (HttpServletRequest) request;8 9 // 从请求头获取或生成TraceId10 String traceId = httpRequest.getHeader("X-B3-TraceId");11 if (traceId == null) {12 traceId = generateTraceId();13 }14 15 // 存入ThreadLocal16 TraceContext.setTraceId(traceId);17 18 try {19 chain.doFilter(request, response);20 } finally {21 TraceContext.clear();22 }23 }24}2526// RestTemplate拦截器自动传递TraceId27public class TraceRestTemplateInterceptor implements ClientHttpRequestInterceptor {28 29 @Override30 public ClientHttpResponse intercept(HttpRequest request, byte[] body,31 ClientHttpRequestExecution execution) {32 // 从ThreadLocal获取TraceId33 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:941123链路追踪展示: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实现
- 功能强大:支持多种中间件
- 中文文档:国内使用广泛
使用方式:
1# 下载SkyWalking Agent2wget https://archive.apache.org/dist/skywalking/8.9.0/apache-skywalking-apm-8.9.0.tar.gz34# 启动应用时添加agent5java -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自定义追踪:
1@Component2public class CustomTracer {3 4 @Autowired5 private Tracer tracer;6 7 public void customTrace() {8 // 创建自定义Span9 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标准
配置:
1opentracing:2 jaeger:3 http-sender:4 url: http://localhost:14268/api/traces5 probabilistic-sampler:6 sampling-rate: 1.07 log-spans: true方案对比:
| 特性 | Sleuth+Zipkin | SkyWalking | Jaeger |
|---|---|---|---|
| 侵入性 | 低(依赖) | 无(Agent) | 低 |
| 性能开销 | 中 | 低 | 低 |
| 功能 | 基础 | 强大 | 强大 |
| UI | 简单 | 丰富 | 丰富 |
| 中文文档 | 一般 | 完善 | 一般 |
| 学习成本 | 低 | 中 | 中 |
| 社区 | 活跃 | 活跃 | 活跃 |
链路追踪的核心价值:
-
快速定位问题:
- 哪个服务出错
- 哪个接口慢
- 调用链路是否正常
-
性能分析:
- 各服务耗时占比
- 识别性能瓶颈
- 优化调用链路
-
依赖分析:
- 服务依赖关系
- 调用频率统计
- 影响范围评估
-
容量规划:
- 服务调用量统计
- 资源使用情况
- 扩容决策依据
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. 开箱即用:
1// 只需添加依赖和注解即可使用2@SpringBootApplication3@EnableDiscoveryClient // 服务注册4@EnableFeignClients // 服务调用5@EnableCircuitBreaker // 熔断降级6public class Application {7 public static void main(String[] args) {8 SpringApplication.run(Application.class, args);9 }10}1112// 声明式服务调用13@FeignClient("user-service")14public interface UserClient {15 @GetMapping("/user/{id}")16 User getUser(@PathVariable Long id);17}1819// 自动负载均衡20@Autowired21private UserClient userClient;2223public User getUser(Long id) {24 return userClient.getUser(id); // 自动负载均衡25}3. 与Spring Boot无缝集成:
1# 统一的配置方式2spring:3 application:4 name: order-service5 cloud:6 nacos:7 discovery:8 server-addr: localhost:88489 sentinel:10 transport:11 dashboard: localhost:80804. 社区活跃,文档完善:
- GitHub Star数高
- 问题响应快
- 中文文档丰富
- 大量实践案例
Spring Cloud缺点详解:
1. 版本兼容问题:
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>67<!-- 版本对应关系 -->8Spring Boot 2.6.x → Spring Cloud 2021.0.x9Spring Boot 2.7.x → Spring Cloud 2021.0.x10Spring Boot 3.0.x → Spring Cloud 2022.0.x1112<!-- 组件版本也需要匹配 -->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. 性能开销:
1// HTTP通信开销2@FeignClient("user-service")3public interface UserClient {4 @GetMapping("/user/{id}")5 User getUser(@PathVariable Long id);6}78// 每次调用都需要:9// 1. HTTP连接建立10// 2. JSON序列化/反序列化11// 3. 网络传输12// 4. 负载均衡计算1314// 对比RPC(如Dubbo):15// 1. 长连接复用16// 2. 二进制序列化(更快)17// 3. 直接TCP通信18// 4. 性能更高性能对比:
1HTTP REST (Spring Cloud): ~5-10ms2RPC (Dubbo): ~1-2ms3本地方法调用: ~0.01ms3. 学习成本高:
1需要掌握的知识:21. Spring Boot基础32. Spring Cloud各组件43. 微服务架构理念54. 分布式系统知识65. 容器化部署(Docker/K8s)76. 监控运维工具89学习路径长,上手周期约2-3个月4. 运维复杂:
1运维挑战:21. 服务数量多(可能几十上百个)32. 配置管理复杂43. 日志分散,排查困难54. 监控指标多65. 部署流程复杂76. 故障定位困难89需要完善的运维体系:10- CI/CD流水线11- 日志收集(ELK)12- 监控告警(Prometheus+Grafana)13- 链路追踪(SkyWalking)14- 配置中心(Nacos)15- 服务网格(Istio)适用场景:
适合使用Spring Cloud的场景:
- 中大型互联网项目
- 业务复杂,需要拆分服务
- 团队规模较大(大于20人)
- 需要快速迭代
- 对性能要求不是极致
- 团队熟悉Spring生态
不适合使用Spring Cloud的场景:
- 小型项目(单体更合适)
- 对性能要求极高(考虑Dubbo)
- 团队规模小(小于5人)
- 运维能力不足
- 业务简单,不需要拆分
最佳实践建议:
-
版本管理:
- 使用BOM统一管理版本
- 定期升级,但不追新
- 建立版本兼容性测试
-
性能优化:
- 合理设置超时时间
- 使用连接池
- 开启HTTP/2
- 考虑gRPC替代REST
-
降低复杂度:
- 从核心组件开始
- 渐进式引入
- 做好文档和培训
- 建立最佳实践规范
-
运维自动化:
- 容器化部署
- 自动化监控
- 统一日志收集
- 完善的告警机制
7. Spring Boot 和 Spring Cloud 之间的区别?
Spring Boot:快速开发单体应用的框架,简化配置
Spring Cloud:微服务架构解决方案,提供服务治理能力
关系:Spring Cloud基于Spring Boot构建
8. Spring Cloud 由什么组成?
核心组件:
- 注册中心:Eureka、Nacos、Consul
- 服务调用:Feign、Ribbon、LoadBalancer
- 熔断降级:Hystrix、Sentinel、Resilience4j
- 服务网关:Zuul、Gateway
- 配置中心:Config、Nacos Config
- 链路追踪: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 的实现原理说一下?
核心机制:
- 服务注册:服务启动时向Eureka Server注册
- 心跳续约:每30秒发送心跳,90秒未收到则剔除
- 服务发现:客户端从注册中心获取服务列表,本地缓存
- 自我保护:网络故障时不剔除服务
23. Spring Cloud 如何实现服务注册?
注解:@EnableDiscoveryClient或@EnableEurekaClient
配置:在application.yml中配置注册中心地址
自动注册:应用启动后自动向注册中心注册
24. Consul 是什么?
Consul:HashiCorp开源的服务网格解决方案,提供服务发现、配置管理、健康检查。
特点:支持CP和AP模式、多数据中心、强一致性
25. Eureka、Zookeeper、Nacos、Consul 的区别?
| 特性 | Eureka | Zookeeper | Nacos | Consul |
|---|---|---|---|---|
| CAP | AP | CP | AP/CP | CP/AP |
| 健康检查 | 心跳 | 长连接 | 心跳/HTTP | TCP/HTTP |
| 配置中心 | 不支持 | 支持 | 支持 | 支持 |
| 语言 | Java | Java | Java | Go |
26. Nacos 中的 Namespace 是什么?
Namespace:命名空间,用于实现环境隔离(dev/test/prod)
作用:不同环境的服务和配置相互隔离,互不影响
27. 为什么需要负载均衡?
原因:
- 提高系统处理能力
- 避免单点故障
- 合理分配流量
- 提升系统可用性
28. Spring Cloud 负载均衡的实现方式有哪些?
客户端负载均衡:Ribbon、LoadBalancer
服务端负载均衡:Nginx、Gateway
29. 负载均衡算法有哪些?
常见算法:
- 轮询:依次分配
- 随机:随机选择
- 加权轮询:根据权重分配
- 最少连接:选择连接数最少的
- 一致性Hash:根据IP Hash分配
30. HTTP 与 RPC 之间的区别?
| 特性 | HTTP | RPC |
|---|---|---|
| 协议 | HTTP协议 | 自定义协议 |
| 性能 | 较低 | 高 |
| 跨语言 | 支持 | 部分支持 |
| 复杂度 | 简单 | 复杂 |
| 适用场景 | 对外API | 内部服务 |
31. 什么是 Feign?
Feign:Netflix开源的声明式HTTP客户端,简化服务间调用。
特点:声明式、集成Ribbon负载均衡、支持多种编解码器
32. Feign 是如何实现负载均衡的?
实现:Feign集成了Ribbon,通过Ribbon实现客户端负载均衡。
流程:Feign调用 → Ribbon选择服务实例 → 发起HTTP请求
33. Feign 为什么第一次调用耗时很长?
原因:
- Ribbon需要从注册中心拉取服务列表
- Ribbon延迟加载,首次请求才初始化
解决:配置饥饿加载(eager-load)
34. Feign 和 OpenFeign 的区别?
Feign:Netflix开源,已停更
OpenFeign:Spring Cloud官方维护,支持Spring MVC注解
推荐:使用OpenFeign
35. Feign 和 Dubbo 的区别?
| 特性 | Feign | Dubbo |
|---|---|---|
| 协议 | HTTP | RPC |
| 性能 | 低 | 高 |
| 跨语言 | 支持 | 有限 |
| 服务治理 | 依赖Spring Cloud | 内置 |
| 适用 | 微服务 | 分布式 |
36. 什么是熔断器?为什么需要熔断器?
熔断器:当服务失败达到阈值时,自动断开服务调用,防止雪崩。
作用:防止服务雪崩、快速失败、保护系统资源
37. 什么是 Hystrix?
Hystrix:Netflix开源的容错管理库,提供熔断、降级、隔离、限流功能。
状态:已停更,推荐使用Sentinel或Resilience4j
38. Hystrix 什么是服务雪崩?
服务雪崩:一个服务失败导致上游服务级联失败,最终整个系统崩溃。
防止:熔断、降级、限流、超时控制
39. 什么是服务降级?
服务降级:当服务压力过大或不可用时,返回默认结果或简化结果。
场景:服务异常、超时、高并发
40. 什么是服务熔断?
服务熔断:当服务失败率达到阈值时,熔断器打开,直接返回错误。
状态:关闭、打开、半开
41. 什么是服务限流?
服务限流:限制服务的并发访问量,防止系统过载。
算法:计数器、滑动窗口、漏桶、令牌桶
42. Sentinel 是怎么实现限流的?
实现:基于滑动窗口算法,统计请求数量,超过阈值则拒绝。
特点:实时监控、多维度限流、控制台配置
43. Sentinel 与 Hystrix 的区别是什么?
| 特性 | Sentinel | Hystrix |
|---|---|---|
| 维护状态 | 活跃 | 停更 |
| 限流 | 支持 | 不支持 |
| 控制台 | 有 | 无 |
| 实时指标 | 支持 | 不支持 |
| 规则配置 | 动态 | 静态 |
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 作为网关?
原因:
- Spring官方维护,持续更新
- 基于WebFlux,性能高
- 支持响应式编程
- 配置灵活,扩展性强
52. Spring Cloud 说说什么是 API 网关?它有什么作用?
API网关:系统对外的统一入口,负责路由转发和请求聚合。
作用:路由、认证、限流、熔断、监控、协议转换
53. Dubbo 和 Spring Cloud Gateway 有什么区别?
Dubbo:RPC框架,用于服务间调用
Gateway:API网关,用于请求路由和转发
区别:不同层次,不同作用,可以结合使用
54. 什么是令牌桶算法?工作原理是什么?使用它有哪些优点和注意事项?
原理:按固定速率往桶中放令牌,请求需要获取令牌才能执行。
优点:允许突发流量、平滑限流、简单高效
注意:需要合理设置桶容量和令牌生成速率
55. Spring Cloud 有哪些核心组件?
核心组件:
- 注册中心:Eureka、Nacos、Consul
- 服务调用:Feign、OpenFeign、Ribbon
- 熔断降级:Hystrix、Sentinel、Resilience4j
- 服务网关:Gateway、Zuul
- 配置中心:Config、Nacos Config
- 链路追踪:Sleuth、Zipkin
- 消息总线:Bus
学习指南
核心要点:
- 微服务架构设计原则
- Spring Cloud 核心组件
- 服务治理和注册发现
- 分布式系统设计模式
学习路径建议:
- 掌握微服务架构的基本概念
- 熟悉Spring Cloud的核心组件
- 理解服务治理和负载均衡
- 学习分布式事务和链路追踪
评论区 / Comments