微服务架构核心概念业务示例
一、什么是微服务
概念
微服务是一种架构风格,将单体应用拆分为一组小型、独立部署的服务,每个服务围绕特定业务能力构建,通过轻量级通信机制(通常是 HTTP/消息队列)协作。
与单体架构对比
| 对比项 | 单体架构 | 微服务架构 |
|---|---|---|
| 部署单元 | 整个应用一起打包部署 | 每个服务独立部署 |
| 数据库 | 所有模块共享一个数据库 | 每个服务有自己的数据库 |
| 通信方式 | 方法调用(进程内) | HTTP/MQ(跨进程) |
| 扩展方式 | 整体扩容 | 按需扩容单个服务 |
| 故障影响 | 一个模块崩溃,整体不可用 | 一个服务故障,其他不受影响 |
| 团队协作 | 所有人改同一个代码仓库 | 各团队负责自己的服务 |
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、服务间通信:OpenFeign
概念
OpenFeign 是 Spring Cloud 提供的声明式 HTTP 客户端。你只需要定义一个接口并加上注解,Feign 自动帮你完成 HTTP 请求的构造、发送和响应解析。
与手动 HTTP 调用的对比
手动 RestTemplate 方式(繁琐):
Stringurl="http://xxx-order/api/xxx/order/result/receive";HttpHeadersheaders=newHttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<OmsOperationDto>entity=newHttpEntity<>(dto,headers);ResponseEntity<RestControllerResult>response=restTemplate.postForEntity(url,entity,RestControllerResult.class);Feign 声明式方式(简洁):
RestControllerResult<Boolean>result=xxxOrderFeign.noticeOms(dto);通用代码示例
1. 定义 Feign 接口
/** * 远程服务调用接口. * 声明式定义:只需写接口 + 注解,无需实现类. */@FeignClient(value="${service.name.user-service}/api",// 服务名(从配置读取)fallbackFactory=UserFeignFallbackFactory.class// 降级处理)publicinterfaceUserFeign{@GetMapping("/user/info/{userId}")RestControllerResult<UserDto>getUserInfo(@PathVariable("userId")IntegeruserId);@PostMapping("/user/batch-query")RestControllerResult<List<UserDto>>batchQuery(@RequestBodyList<Integer>userIds);}2. Feign 降级工厂(Fallback Factory)
/** * Feign 降级工厂. * 当远程服务不可用时执行降级逻辑,避免级联故障. */@ComponentpublicclassUserFeignFallbackFactoryimplementsFallbackFactory<UserFeign>{@OverridepublicUserFeigncreate(Throwablecause){returnnewUserFeign(){@OverridepublicRestControllerResult<UserDto>getUserInfo(IntegeruserId){log.warn("调用用户服务降级, userId={}, cause={}",userId,cause.getMessage());returnRestControllerResult.fail("用户服务暂不可用");}@OverridepublicRestControllerResult<List<UserDto>>batchQuery(List<Integer>userIds){log.warn("批量查询用户降级, cause={}",cause.getMessage());returnRestControllerResult.fail("用户服务暂不可用");}};}}3. 业务代码中调用
@ServicepublicclassOrderServiceImpl{@ResourceprivateUserFeignuserFeign;publicOrderDetailVogetOrderDetail(IntegerorderId){Orderorder=orderRepository.findById(orderId);// 像调用本地方法一样调用远程服务RestControllerResult<UserDto>userResult=userFeign.getUserInfo(order.getUserId());if(Boolean.TRUE.equals(userResult.getSuccess())){// 组装返回数据OrderDetailVovo=newOrderDetailVo();vo.setOrder(order);vo.setUser(userResult.getData());returnvo;}returnnull;}}Feign 的核心配置
feign:sentinel:enabled:true# 开启熔断client:config:default:connectTimeout:60000# 连接超时 60sreadTimeout:60000# 读超时 60sloggerLevel:basic# 日志级别三、服务注册与发现
概念
微服务数量众多,每个服务可能有多个实例。服务注册与发现解决的问题是:调用方如何知道目标服务的真实地址?
┌──────────┐ 注册 ┌──────────────────┐ 发现 ┌──────────┐ │ 服务A │──────→│ 注册中心 │←──────│ 服务B │ │ 实例1 │ │ (Nacos/Eureka) │ │ │ │ 实例2 │ │ │ │ │ │ 实例3 │ │ 服务A → [实例列表]│ │ │ └──────────┘ └──────────────────┘ └──────────┘工作流程
- 服务A 启动后向注册中心注册自己的地址
- 服务B 需要调用服务A 时,先从注册中心获取服务A 的实例列表
- 通过负载均衡策略选择一个实例发起调用
- 注册中心通过心跳检测剔除不健康实例
实现方式
请求链路:Feign → API Gateway → 目标服务,网关负责服务路由和负载均衡。
四、服务熔断与降级
概念
当远程服务出现故障(超时、异常),如果不做处理,调用方会一直等待,最终拖垮自身。熔断器就像电路保险丝,当故障达到阈值时"断开",直接走降级逻辑。
三种状态
┌──────────────────────────────────────────┐ │ │ ▼ │ ┌──────────┐ 故障率达标 ┌──────────┐ │ │ CLOSED │ ────────────→ │ OPEN │ │ 探测成功 │ (正常) │ │ (熔断) │ │ └──────────┘ └─────┬────┘ │ ▲ │ 等待超时 │ │ ▼ │ │ ┌──────────┐ │ │ 探测失败 │HALF-OPEN │─────────┘ └────────────────── │ (半开) │ └──────────┘- CLOSED:正常调用,统计失败率
- OPEN:直接走降级逻辑,不再调用远程服务
- HALF-OPEN:放行少量请求探测,成功则恢复,失败则继续熔断
通用代码示例
/** * 带降级的远程调用示例. * 降级逻辑保证主流程不受下游故障影响. */@Service@Slf4jpublicclassNotificationService{@ResourceprivateNotificationFeignnotificationFeign;/** * 发送通知(不影响主业务). * 即使通知服务挂了,主业务继续执行. */publicvoidsendNotification(Stringmessage){try{RestControllerResult<Boolean>result=notificationFeign.send(message);if(!Boolean.TRUE.equals(result.getSuccess())){log.warn("发送通知失败: {}",result.getMessage());}}catch(Exceptione){// 通知失败不影响主流程log.warn("通知服务调用异常",e);}}}五、消息队列(MQ)
概念
消息队列是服务内部异步解耦的核心组件。生产者发送消息到队列,消费者从队列拉取消息处理,两者时间上解耦。
与事件中心的定位区别
┌──────────────────────────────────────────────────────────┐ │ 同一个服务/系统内部 │ │ │ │ 业务方法 ──MQ──→ 异步处理方法 │ │ (同一个代码仓库,同一个团队维护) │ │ │ │ 用途:解耦耗时操作、削峰填谷、保证顺序 │ └──────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────┐ │ 跨系统/跨团队 │ │ │ │ a服务 ──事件中心──→ b服务 │ │ (不同代码仓库,不同团队维护) │ │ │ │ 用途:系统间通知、发布/订阅 │ └──────────────────────────────────────────────────────────┘通用代码示例
生产者
/** * MQ 消息生产者. * 发送消息到指定队列. */@Component@Slf4jpublicclassOrderMqSender{@JshRabbitProducer(queue="${mq.queue.order-process}",exchange="${mq.exchange.order}")privateJshRabbitTemplaterabbitTemplate;publicvoidsendOrderProcess(IntegerorderId){if(orderId!=null){rabbitTemplate.send(orderId);log.info("发送订单处理消息, orderId={}",orderId);}}}消费者
/** * MQ 消息消费者. * 通过 @Profile 控制是否启用. */@Profile("order-process-consumer")// 本地不想消费时可去掉此 profile@Component@Slf4jpublicclassOrderMqConsumer{@ResourceprivateOrderProcessServiceorderProcessService;@JshRabbitConsumer(queue="${mq.queue.order-process}")publicvoidconsume(IntegerorderId){log.info("消费订单处理消息, orderId={}",orderId);try{orderProcessService.process(orderId);}catch(Exceptione){log.error("订单处理失败, orderId={}",orderId,e);throwe;// 抛出异常触发 MQ 重试}}}MQ 的作用:
- 异步化:不阻塞主业务事务
- 重试:消费失败自动重试
- 削峰:高并发时消息排队处理
六、分布式事务与数据一致性
问题
微服务各自有独立数据库,一个业务操作可能跨多个服务,无法使用传统的数据库事务保证一致性。
方案:本地消息表(最终一致性)
┌─────────────────────────────────────────────────┐ │ 同一个数据库事务 │ │ │ │ ① 执行业务逻辑(更新发货单状态等) │ │ ② 插入消息记录(a_send_b_record_log) │ │ ③ 事务提交 │ └───────────────────────┬─────────────────────────┘ │ ▼ 事务提交后 ┌─────────────────────────────────────────────────┐ │ ④ 发送 MQ(携带 logId) │ │ ⑤ 消费者读取消息记录,推送事件中心 │ │ ⑥ 成功/失败更新消息状态 │ │ ⑦ 失败可重试(运维接口/定时任务) │ └─────────────────────────────────────────────────┘通用代码示例
/** * 本地消息表实现最终一致性. */@ServicepublicclassOrderService{@ResourceprivateOrderRepositoryorderRepository;@ResourceprivateMessageLogRepositorymessageLogRepository;@Transactional(rollbackFor=Exception.class)publicvoidcreateOrder(OrderDtodto){// ① 业务操作Orderorder=newOrder();order.setOrderCode(dto.getOrderCode());order.setStatus("CREATED");orderRepository.save(order);// ② 同一事务内写消息记录MessageLogmsgLog=newMessageLog();msgLog.setBizKey(order.getOrderCode());msgLog.setEventType("order.created");msgLog.setPayload(JSON.toJSONString(order));msgLog.setStatus(0);messageLogRepository.save(msgLog);// ③ 注册事务提交后的回调TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){@OverridepublicvoidafterCommit(){mqSender.send(msgLog.getId());}});}}为什么不用分布式事务(2PC/Seata)?
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低(锁等待) | 高 | 金融转账 |
| Seata AT | 强一致 | 中 | 中 | 短事务、同步场景 |
| 本地消息表 | 最终一致 | 高 | 低 | 通知类、可接受短暂延迟 |
| TCC | 最终一致 | 高 | 高 | 库存扣减等 |
示例需求是"通知系统b",属于通知类场景,不需要b的即时响应,用本地消息表 + 最终一致性是最合适的方案。
七、API 网关
概念
API 网关是微服务的统一入口,所有外部请求先经过网关,由网关路由到具体服务。
┌─────────────────┐ 外部请求 ──────────→│ API Gateway │ │ │ │ 路由规则: │ │ /a-order → A │──→ a 服务 │ /b → B │──→ b服务 │ /order → C │──→ 订单服务 └─────────────────┘网关的职责
| 职责 | 说明 |
|---|---|
| 路由转发 | 根据 URL 前缀将请求转发到对应服务 |
| 负载均衡 | 同一服务多实例时分发请求 |
| 鉴权认证 | 统一校验 JWT Token |
| 限流熔断 | 保护后端服务不被打爆 |
| 协议转换 | HTTPS → HTTP,外部加密内部明文 |
八、配置中心
概念
微服务众多,每个服务有各自的配置(数据库、MQ、第三方地址等)。配置中心提供统一管理和动态刷新。
通过配置实现多环境切换
# 本地开发eventcenter:event:route-event-to-oms:url:https://xxx.net/.../send# 测试环境# 生产环境(配置中心覆盖)eventcenter:event:route-event-to-oms:url:https://xxx/.../send# 生产环境代码中通过@Value注入,无需修改代码即可切换环境:
@Value("${eventcenter.event.route-event-to-oms.url}")privateStringrouteEventToOmsEventUrl;九、服务间调用的安全认证
JWT Token 传递
微服务间调用需要传递身份信息。项目通过 Feign 请求拦截器自动带上 JWT Token:
用户请求 → 网关(验证JWT)→ a服务 → Feign调用b服务(携带JWT)通用示例:Feign 请求拦截器
/** * Feign 请求拦截器. * 自动将当前请求的 JWT Token 传递给下游服务. */@ConfigurationpublicclassFeignInterceptorConfig{@BeanpublicRequestInterceptorrequestInterceptor(){returnrequestTemplate->{// 从当前请求上下文获取 TokenServletRequestAttributesattributes=(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attributes!=null){Stringtoken=attributes.getRequest().getHeader("Authorization");if(token!=null){requestTemplate.header("Authorization",token);}}};}}