分类 应用 下的文章

MQTT 与 Kafka|物联网消息与流数据集成实践

一、MQTT 如何与 Kafka 一起使用?

  • 技术定位:MQTT(Message Queuing Telemetry Transport)是轻量级消息传输协议,专为受限网络环境下的设备通信设计;Apache Kafka 是分布式流处理平台,旨在处理大规模实时数据流。
  • 协同价值:二者为互补技术,结合使用可构建强大物联网架构,实现设备与平台间的稳定连接、高效数据传输,同时支持高吞吐量数据的实时处理与分析。
  • 适用场景:网联汽车和车联网、智能城市基础设施、工业物联网监控、物流管理等物联网场景。

二、Kafka 和 MQTT 可以解决哪些物联网挑战?

物联网平台架构设计需应对以下核心挑战,MQTT 与 Kafka 的组合可针对性解决:

  1. 连接性和网络弹性:应对网联汽车等场景中网络不稳定、延迟等问题,确保数据稳定传输。
  2. 扩展性:支持设备数量增长,处理不断增加的物联网设备产生的大量数据。
  3. 消息吞吐量:适配物联网设备实时产生的传感器读数、位置信息等海量数据,保障数据有效采集、处理与分发。
  4. 数据存储:提供高效的数据流存储与管理方案,应对物联网设备持续产生的数据。

三、为什么需要在物联网架构中集成 MQTT 与 Kafka?

1. Kafka 在物联网场景中的不足

  • 不可靠的连接:Kafka 客户端需稳定 IP 连接,无法适配移动网络等不稳定环境,易导致通信中断。
  • 客户端复杂性与资源密集性:Kafka 客户端复杂且消耗资源多,不适用于资源受限的小型物联网设备。
  • 主题可扩展性限制:处理大量主题时存在瓶颈,难以适配物联网应用中“多设备+多主题”的场景。

2. MQTT 弥补 Kafka 不足的核心优势

  • 可靠的连接:专为不稳定网络环境设计,保障物联网设备间消息传输可靠性。
  • 轻量级客户端:客户端资源消耗低,适合资源受限的物联网设备。
  • 海量主题扩展:高效处理大量业务主题,可将海量 MQTT 主题汇聚后映射到 Kafka 主题,实现数据汇聚处理。

四、几种可行的 MQTT-Kafka 集成解决方案对比

1. EMQX Kafka 数据集成

  • 核心机制:EMQX(流行 MQTT Broker)通过内置 Kafka 数据集成功能,作为二者桥梁实现无缝通信。
  • 关键特性:支持以生产者(向 Kafka 发消息)、消费者(从 Kafka 收消息)两种角色创建数据桥接;具备双向数据传输能力,架构设计灵活;低延迟、高吞吐量,保障数据桥接高效可靠。
  • 参考文档:可访问 EMQX 文档“集成 Kafka”了解详情。

2. Confluent MQTT 代理

  • 提供方:Kafka 商业运营公司 Confluent。
  • 核心机制:提供 MQTT 协议代理模块,连接 MQTT 客户端与 Kafka Broker,简化客户端发布/订阅 Kafka 主题的流程,抽象直接通信的复杂性,避免多余复制与延迟。
  • 局限性:仅支持 MQTT 3.1.1 版本;MQTT 客户端连接性能可能影响数据吞吐量。

3. 对开源 MQTT Broker 和 Kafka 进行定制开发

  • 实现方式:使用开源 MQTT Broker,自行开发桥接服务——通过 MQTT 客户端从 MQTT Broker 订阅数据,利用 Kafka Producer API 将数据发送到 Kafka。
  • 注意事项:需自行开发与维护桥接服务,同时需考虑可靠性与扩展性问题。

五、使用 EMQX 将 MQTT 数据集成到 Kafka

1. 核心架构优势

EMQX 作为高度可扩展的 MQTT Broker,结合 Kafka 高吞吐量、持久化数据处理能力,为物联网构建完善数据基础设施,支持海量设备连接。
请输入图片描述

2. 关键功能

  • 双向连接:既可以将设备 MQTT 消息批量转发到 Kafka,也能从后端系统订阅 Kafka 消息并下发到物联网客户端。
  • 灵活的主题映射:支持一对一、一对多、多对多等多种 MQTT-Kafka 主题映射方式,同时兼容 MQTT 主题过滤器(通配符)。
  • 多写入模式:Kafka 生产者支持同步/异步写入,可根据场景平衡延迟与吞吐量。
  • 数据处理与监控:提供消息总数、成功/失败交付数、消息速率等实时指标;可结合 SQL 规则,在消息推送至 Kafka 或设备前完成数据提取、过滤、丰富与转换。

六、应用场景示例:MQTT 和 Kafka 赋能网联汽车和车联网

请输入图片描述
MQTT + Kafka 架构在网联汽车与车联网领域应用广泛,核心场景包括:

  1. 车载信息系统和车辆数据分析:实现海量实时车辆数据(传感器读数、GPS 位置、油耗、驾驶行为)的云端接入、流式处理与分析,用于车辆性能监控、预测性维护、车队管理,提升运营效率。
  2. 智能交通管理:获取并处理网联汽车、交通传感器、基础设施等多源交通数据,开发智能交通管理系统,实现实时交通监控、拥堵检测、路线优化、智能交通信号控制。
  3. 远程诊断:支持高吞吐量数据传输,用于车辆远程诊断与故障排除,实现主动维护与快速问题解决。
  4. 能源效率和环境影响:实现网联汽车与智能电网系统、能源管理平台的双向数据交互,包括实时监测能源消耗、实施需求响应机制、优化电动汽车充电策略。
  5. 预测性维护:持续跟踪车辆健康与性能数据,通过高吞吐量实时车载数据收集、异常检测和预测性维护算法,帮助车主及时发现潜在问题并安排维护。

七、结语

MQTT + Kafka 架构适用于需实时数据收集、高扩展性、高可靠性及物联网集成能力的应用场景,可实现数据流畅传输、高效沟通与创新应用(如网联汽车生态系统功能)。二者结合是理想的物联网架构解决方案,能实现物联网设备与云之间的无缝端到端集成,保障双向通信可靠性。

我工作中用MQ的10种场景

前言

在分布式系统开发中,消息队列(MQ)是解决系统解耦、异步通信、流量削峰等问题的核心组件。我在过往的项目开发(电商秒杀、物流调度、支付对账等场景)中,多次用到RabbitMQ、Kafka、RocketMQ等不同类型的MQ,总结出10种高频实用场景,每种场景均包含“业务痛点、MQ解决方案、技术要点、架构优势”,希望能给大家提供参考。

场景一:系统解耦

业务痛点

早期电商系统中,订单创建后需同步调用库存系统、支付系统、物流系统的接口。若其中某一个系统接口超时(如物流系统服务宕机),会导致整个订单流程失败,系统耦合度极高,一处故障影响全局。

MQ解决方案

  1. 订单系统在创建订单后,不再直接调用其他系统接口,而是向MQ发送一条“订单创建成功”的消息(包含订单ID、商品ID、用户ID、订单金额等核心字段);
  2. 库存系统、支付系统、物流系统分别作为消费者,订阅MQ中“订单创建成功”的消息队列;
  3. 各系统接收到消息后,异步执行自身业务逻辑(库存扣减、支付状态同步、物流单生成),执行结果通过MQ反馈给订单系统(如“库存扣减成功”“支付完成”)。

技术要点

  • 消息队列选择:RabbitMQ(支持交换机路由,可按系统类型路由消息);
  • 消息可靠性:开启消息持久化(durable=true)、生产者确认机制(publisher confirm)、消费者手动ACK,避免消息丢失;
  • 代码示例(Java + RabbitMQ):

    // 订单系统生产者发送消息
    @Service
    public class OrderService {
      @Autowired
      private RabbitTemplate rabbitTemplate;
    
      public void createOrder(Order order) {
          // 1. 保存订单到数据库
          orderMapper.insert(order);
          // 2. 发送订单创建消息到MQ
          String message = JSON.toJSONString(order);
          rabbitTemplate.convertAndSend("order_exchange", "order.created", message, new CorrelationData(order.getOrderId()));
          System.out.println("订单创建消息发送成功,订单ID:" + order.getOrderId());
      }
    }
    
    // 库存系统消费者接收消息
    @Component
    public class InventoryConsumer {
      @Autowired
      private InventoryMapper inventoryMapper;
    
      @RabbitListener(queues = "inventory_queue")
      public void handleOrderCreated(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
          try {
              Order order = JSON.parseObject(message, Order.class);
              // 执行库存扣减逻辑
              inventoryMapper.deductStock(order.getProductId(), order.getQuantity());
              // 手动ACK确认消息消费成功
              channel.basicAck(deliveryTag, false);
              System.out.println("库存扣减成功,商品ID:" + order.getProductId());
          } catch (Exception e) {
              // 消费失败,消息重新入队(或进入死信队列)
              channel.basicNack(deliveryTag, false, true);
              e.printStackTrace();
          }
      }
    }

架构优势

  • 解耦:各系统仅依赖MQ消息,无需感知其他系统存在,新增系统(如积分系统)只需订阅消息即可,无需修改订单系统代码;
  • 容错:某一系统故障时,消息暂存MQ,待系统恢复后重新消费,避免流程中断;
  • 可扩展性:各系统可独立扩容(如库存系统压力大时,增加消费者实例),不影响其他系统。

场景二:异步通信

业务痛点

用户在电商平台下单支付后,系统需完成“订单状态更新、发送短信通知、生成积分、同步会员等级”4个操作。若采用同步调用,每个操作耗时100ms,总耗时400ms,用户需等待400ms才能看到支付成功页面,体验较差。

MQ解决方案

  1. 支付系统完成支付后,同步更新订单状态(耗时100ms),随后向MQ发送“支付成功”消息;
  2. 短信服务、积分服务、会员服务作为消费者,异步订阅“支付成功”消息,分别执行短信发送、积分增加、会员等级更新操作;
  3. 用户无需等待后续3个操作完成,支付成功后立即看到结果,后续操作由MQ异步驱动。

技术要点

  • 消息投递模式:采用“扇出交换机(Fanout Exchange)”,让多个消费者同时接收同一条消息;
  • 异步非阻塞:消费者业务逻辑采用异步线程池处理,避免单条消息消费耗时过长阻塞队列;
  • 耗时对比:同步调用总耗时400ms → 异步调用总耗时100ms(仅订单状态更新),用户等待时间缩短75%。

架构优势

  • 提升用户体验:核心流程(支付+订单更新)快速响应,非核心流程异步执行;
  • 提高系统吞吐量:同步调用时系统TPS受限于最慢操作,异步后各操作并行处理,TPS提升3-5倍。

场景三:流量削峰

业务痛点

电商秒杀活动中,每秒请求量可达10000+,但后端库存系统、订单系统的最大处理能力仅为2000 TPS。若直接将请求打向后端,会导致系统过载、数据库连接耗尽,甚至服务宕机。

MQ解决方案

  1. 秒杀活动开始前,提前在MQ中创建“秒杀请求队列”,设置队列最大长度(如10万条,避免消息堆积过多);
  2. 用户秒杀请求先发送至MQ,MQ作为“缓冲器”接收所有请求,后端系统按自身处理能力(2000 TPS)从MQ中拉取请求;
  3. 超过MQ队列长度的请求直接返回“秒杀拥挤,请稍后重试”,避免后端压力过大。

技术要点

  • MQ选择:Kafka(高吞吐量,每秒可处理百万级消息,适合秒杀场景);
  • 流量控制:设置Kafka消费者“每次拉取消息数量”(如每次拉取200条),配合线程池控制并发消费速度;
  • 防重复提交:消息中携带用户ID+商品ID,后端系统通过Redis实现“幂等性校验”,避免重复创建订单;
  • 代码示例(Kafka消费者限流):

    @Service
    public class SeckillConsumer {
      @Autowired
      private SeckillService seckillService;
      @Autowired
      private StringRedisTemplate redisTemplate;
    
      // 配置Kafka消费者,每次拉取200条消息,线程池核心线程数10
      @KafkaListener(topics = "seckill_topic", properties = {
              "max.poll.records=200",
              "fetch.min.bytes=1024",
              "fetch.max.wait.ms=500"
      })
      public void consumeSeckillRequest(ConsumerRecord<String, String> record) {
          String message = record.value();
          SeckillRequest request = JSON.parseObject(message, SeckillRequest.class);
          String userId = request.getUserId();
          String productId = request.getProductId();
          String key = "seckill:order:" + userId + ":" + productId;
    
          // 幂等性校验:判断用户是否已秒杀成功
          if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
              System.out.println("用户" + userId + "已秒杀过商品" + productId,跳过重复请求");
              return;
          }
    
          // 执行秒杀逻辑
          boolean success = seckillService.doSeckill(userId, productId);
          if (success) {
              // 秒杀成功,记录用户秒杀记录(过期时间24小时)
              redisTemplate.opsForValue().set(key, "1", 24, TimeUnit.HOURS);
              System.out.println("用户" + userId + "秒杀商品" + productId + "成功");
          } else {
              System.out.println("用户" + userId + "秒杀商品" + productId + "失败,库存不足");
          }
      }
    }

架构优势

  • 流量缓冲:MQ承接瞬时高峰流量,避免后端系统被“冲垮”;
  • 削峰填谷:将秒杀1小时内的高峰流量,分散到后续2-3小时内处理(若库存未售罄),充分利用后端资源;
  • 保护核心系统:仅让符合处理能力的请求进入后端,确保系统稳定运行。

场景四:数据同步

业务痛点

电商系统中,用户在APP端修改个人信息(昵称、手机号、地址)后,需同步更新到“用户中心数据库”“搜索服务索引库”“推荐系统用户画像库”3个数据源。若采用代码中逐个调用接口同步,存在“同步失败数据不一致”“新增数据源需改代码”等问题。

MQ解决方案

  1. 用户中心系统在用户信息修改后,向MQ发送“用户信息更新”消息(包含用户ID、修改字段、新值);
  2. 搜索服务、推荐服务分别订阅该消息,接收到消息后:

    • 搜索服务:更新Elasticsearch中的用户索引;
    • 推荐服务:更新Redis或MySQL中的用户画像数据;
  3. 所有数据源通过MQ消息异步同步,确保数据最终一致性。

技术要点

  • 消息格式:采用“增量更新”格式,仅携带修改的字段(如{userId:123, nickname:"新昵称"}),减少消息体积;
  • 数据一致性:设置消息重试机制(如重试3次),重试失败后进入死信队列,人工介入处理;
  • 同步延迟:通过监控平台统计消息从发送到消费的延迟(目标<1s),确保各数据源同步及时。

架构优势

  • 数据一致性:避免手动同步导致的数据源不一致问题;
  • 扩展性:新增数据源(如数据分析平台)只需订阅消息,无需修改用户中心代码;
  • 可追溯:MQ记录所有同步消息,便于排查数据不一致原因(如某条消息消费失败)。

场景五:事务消息

业务痛点

电商“下单扣库存”场景中,存在“订单创建成功但库存扣减失败”的问题:若先创建订单,再扣库存,库存扣减失败会导致订单无库存却存在;若先扣库存,再创建订单,订单创建失败会导致库存被扣却无订单,出现数据不一致。

MQ解决方案

采用RocketMQ的“事务消息”机制,实现“订单创建”与“库存扣减”的原子性操作,流程如下:

  1. 订单系统发送“半事务消息”到MQ(此时消息处于“待确认”状态,消费者无法消费);
  2. MQ收到半事务消息后,向订单系统返回“消息发送成功”;
  3. 订单系统执行本地事务(创建订单+扣减库存,在同一个数据库事务中):

    • 若本地事务执行成功,向MQ发送“确认消息”,MQ将半事务消息标记为“可消费”,库存系统(或其他消费者)可消费消息;
    • 若本地事务执行失败,向MQ发送“回滚消息”,MQ删除半事务消息,避免消息被消费;
  4. 若订单系统因网络问题未及时发送确认/回滚消息,MQ会定时向订单系统发起“事务状态回查”,订单系统查询本地事务状态后,补发送确认/回滚消息。

技术要点

  • MQ选择:RocketMQ(原生支持事务消息,RabbitMQ需通过自定义逻辑实现);
  • 本地事务:订单创建与库存扣减需在同一个数据库事务中,确保两者同时成功或同时失败;
  • 回查机制:设置RocketMQ事务回查次数(如3次),回查间隔(如10s),避免无限回查;
  • 代码示例(RocketMQ事务消息):

    // 1. 订单系统发送半事务消息
    @Service
    public class OrderTransactionService {
      @Autowired
      private RocketMQTemplate rocketMQTemplate;
      @Autowired
      private OrderMapper orderMapper;
      @Autowired
      private InventoryMapper inventoryMapper;
    
      public void createOrderWithTransaction(Order order) {
          // 构造半事务消息
          String message = JSON.toJSONString(order);
          Message<String> msg = MessageBuilder.withPayload(message)
                  .setHeader(RocketMQHeaders.TRANSACTION_ID, UUID.randomUUID().toString())
                  .build();
    
          // 发送半事务消息,指定事务监听器
          rocketMQTemplate.sendMessageInTransaction(
                  "order_transaction_topic",  // 事务消息主题
                  "order_tag",                // 消息标签
                  msg,                        // 消息内容
                  order                       // 本地事务参数(传递给事务监听器)
          );
      }
    }
    
    // 2. 事务监听器(执行本地事务+回查事务状态)
    @Component
    @RocketMQTransactionListener(txProducerGroup = "order_producer_group")
    public class OrderTransactionListener implements RocketMQLocalTransactionListener {
      @Autowired
      private OrderMapper orderMapper;
      @Autowired
      private InventoryMapper inventoryMapper;
      @Autowired
      private OrderStatusMapper orderStatusMapper;
    
      // 执行本地事务
      @Override
      public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
          Order order = (Order) arg;
          Connection connection = null;
          try {
              // 获取数据库连接,开启手动事务
              connection = DruidDataSourceFactory.createDataSource().getConnection();
              connection.setAutoCommit(false);
    
              // 执行订单创建
              orderMapper.insert(order);
              // 执行库存扣减
              int rows = inventoryMapper.deductStock(order.getProductId(), order.getQuantity());
              if (rows == 0) {
                  // 库存不足,回滚本地事务
                  connection.rollback();
                  return RocketMQLocalTransactionState.ROLLBACK;
              }
    
              // 提交本地事务
              connection.commit();
              // 记录订单事务状态(用于回查)
              orderStatusMapper.insert(order.getOrderId(), "SUCCESS");
              return RocketMQLocalTransactionState.COMMIT;
          } catch (Exception e) {
              // 本地事务执行异常,回滚
              try {
                  if (connection != null) connection.rollback();
              } catch (SQLException ex) {
                  ex.printStackTrace();
              }
              e.printStackTrace();
              return RocketMQLocalTransactionState.ROLLBACK;
          } finally {
              try {
                  if (connection != null) connection.close();
              } catch (SQLException e) {
                  e.printStackTrace();
              }
          }
      }
    
      // 事务状态回查
      @Override
      public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
          String message = new String((byte[]) msg.getPayload());
          Order order = JSON.parseObject(message, Order.class);
          // 查询本地订单事务状态
          String status = orderStatusMapper.select(order.getOrderId());
          if ("SUCCESS".equals(status)) {
              return RocketMQLocalTransactionState.COMMIT;
          } else if ("FAIL".equals(status)) {
              return RocketMQLocalTransactionState.ROLLBACK;
          } else {
              // 状态未知,继续回查(最多回查3次)
              return RocketMQLocalTransactionState.UNKNOWN;
          }
      }
    }

架构优势

  • 事务一致性:确保“订单创建”与“库存扣减”等操作原子性,避免数据不一致;
  • 可靠性:通过回查机制解决网络异常导致的事务状态未知问题;
  • 低耦合:相比分布式事务(如Seata),无需引入额外框架,依赖MQ原生能力即可实现。

场景六:日志收集

业务痛点

分布式系统中,服务部署在多台服务器上(如订单服务部署5台、支付服务部署3台),日志分散在各服务器的本地文件中。当系统出现故障时,运维人员需逐台登录服务器查看日志,定位问题效率极低(如查找某条订单的日志,需打开5台服务器的日志文件搜索)。

MQ解决方案

采用“MQ + ELK”架构(Kafka + Elasticsearch + Logstash + Kibana),实现日志集中收集与分析:

  1. 各服务通过日志框架(如Logback、Log4j2)将日志输出到本地文件,同时通过“MQ日志Appender”将日志发送到Kafka队列;
  2. Logstash作为消费者,从Kafka中拉取日志,对日志进行清洗(如提取字段、过滤无用日志)、结构化处理(如转为JSON格式);
  3. Logstash将处理后的日志写入Elasticsearch(搜索引擎),构建日志索引;
  4. 运维人员通过Kibana(可视化工具)查询Elasticsearch中的日志,支持按“服务名、时间、日志级别、订单ID”等多维度搜索,快速定位问题。

技术要点

  • MQ选择:Kafka(高吞吐量,适合日志等大量非实时数据传输);
  • 日志结构化:Logstash配置过滤规则,将非结构化日志(如“2024-05-20 10:30:00 [INFO] OrderService: create order success, orderId=123”)转为JSON格式({"time":"2024-05-20 10:30:00","level":"INFO","service":"OrderService","content":"create order success","orderId":"123"});
  • 日志分片:Kafka按“服务名”分区(如订单服务日志写入order_service分区,支付服务日志写入pay_service分区),便于后续按服务查询;
  • 配置示例(Logback对接Kafka):

    <!-- Logback配置文件:logback-spring.xml -->
    <configuration>
      <!-- 1. 输出日志到控制台 -->
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
          <encoder>
              <pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] %logger{50} - %msg%n</pattern>
          </encoder>
      </appender>
    
      <!-- 2. 输出日志到Kafka -->
      <appender name="KAFKA" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
          <!-- Kafka地址(Logstash监听地址) -->
          <destination>192.168.1.100:9500</destination>
          <encoder class="net.logstash.logback.encoder.LogstashEncoder">
              <!-- 自定义日志字段 -->
              <customFields>{"service":"order_service","env":"prod"}</customFields>
              <fieldNames>
                  <timestamp>time</timestamp>
                  <message>content</message>
                  <logger>logger</logger>
                  <thread>thread</thread>
                  <level>level</level>
              </fieldNames>
          </encoder>
      </appender>
    
      <!-- 3. 根日志配置,同时输出到控制台和Kafka -->
      <root level="INFO">
          <appender-ref ref="CONSOLE" />
          <appender-ref ref="KAFKA" />
      </root>
    </configuration>

架构优势

  • 集中化:所有服务日志统一存储到Elasticsearch,无需逐台查看服务器;
  • 高效查询:支持多维度搜索(如“orderId=123”“level=ERROR”),定位问题时间从小时级缩短到分钟级;
  • 可分析:通过Kibana生成日志统计报表(如各服务ERROR日志数量趋势、请求耗时分布),提前发现系统隐患。

场景七:延迟消息

业务痛点

电商订单创建后,若用户未在30分钟内支付,需自动取消订单并释放库存。早期实现方式是“定时任务每隔1分钟扫描未支付订单”,存在两个问题:一是延迟(最快1分钟后取消,最慢2分钟),二是压力大(订单量多时,扫描SQL执行耗时久,占用数据库资源)。

MQ解决方案

采用MQ的“延迟消息”功能,实现订单30分钟未支付自动取消:

  1. 订单系统创建未支付订单后,向MQ发送一条“延迟30分钟”的消息(消息内容包含订单ID);
  2. MQ接收到延迟消息后,暂存消息,不立即投递;
  3. 30分钟后,MQ将消息投递到“订单取消队列”;
  4. 订单系统作为消费者,接收到消息后,查询订单支付状态:

    • 若未支付:取消订单,释放库存;
    • 若已支付:忽略消息(通过幂等性校验避免重复取消)。

技术要点

  • MQ选择:

    • RabbitMQ:通过“死信队列(DLX)+ 消息过期时间(TTL)”实现延迟消息;
    • RocketMQ:原生支持延迟消息,提供1s、5s、10s、30s、1min、5min等预设延迟级别,也可自定义延迟时间;
  • 幂等性:消费者处理消息时,必须先查询订单状态,避免已支付订单被取消;
  • 消息可靠性:开启消息持久化,避免MQ宕机导致延迟消息丢失;
  • 代码示例(RabbitMQ延迟消息):

    // 1. 配置RabbitMQ延迟队列(死信队列)
    @Configuration
    public class RabbitMQDelayConfig {
      // 普通队列(接收延迟消息,消息过期后转发到死信队列)
      @Bean
      public Queue orderDelayQueue() {
          Map<String, Object> args = new HashMap<>();
          // 设置消息过期时间(30分钟=1800000ms)
          args.put("x-message-ttl", 1800000);
          // 设置死信交换机
          args.put("x-dead-letter-exchange", "order_dead_exchange");
          // 设置死信路由键
          args.put("x-dead-letter-routing-key", "order.dead.cancel");
          return QueueBuilder.durable("order_delay_queue").withArguments(args).build();
      }
    
      // 死信交换机
      @Bean
      public DirectExchange orderDeadExchange() {
          return ExchangeBuilder.directExchange("order_dead_exchange").durable(true).build();
      }
    
      // 死信队列(实际处理订单取消的队列)
      @Bean
      public Queue orderDeadQueue() {
          return QueueBuilder.durable("order_dead_queue").build();
      }
    
      // 绑定死信交换机与死信队列
      @Bean
      public Binding deadExchangeBindingDeadQueue() {
          return BindingBuilder.bind(orderDeadQueue())
                  .to(orderDeadExchange())
                  .with("order.dead.cancel");
      }
    
      // 绑定普通交换机与普通延迟队列
      @Bean
      public DirectExchange orderDelayExchange() {
          return ExchangeBuilder.directExchange("order_delay_exchange").durable(true).build();
      }
    
      @Bean
      public Binding delayExchangeBindingDelayQueue() {
          return BindingBuilder.bind(orderDelayQueue())
                  .to(orderDelayExchange())
                  .with("order.delay.create");
      }
    }
    
    // 2. 订单系统发送延迟消息
    @Service
    public class OrderDelayService {
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private OrderMapper orderMapper;
    
      public void createUnpaidOrder(Order order) {
          // 1. 保存未支付订单
          order.setStatus("UNPAID");
          orderMapper.insert(order);
          // 2. 发送30分钟延迟消息
          String message = JSON.toJSONString(order.getOrderId());
          rabbitTemplate.convertAndSend("order_delay_exchange", "order.delay.create", message);
          System.out.println("未支付订单创建成功,订单ID:" + order.getOrderId() + ",延迟30分钟后检查支付状态");
      }
    }
    
    // 3. 订单系统消费死信队列消息,取消未支付订单
    @Component
    public class OrderCancelConsumer {
      @Autowired
      private OrderMapper orderMapper;
      @Autowired
      private InventoryMapper inventoryMapper;
    
      @RabbitListener(queues = "order_dead_queue")
      public void handleOrderCancel(String orderId) {
          // 查询订单当前状态
          Order order = orderMapper.selectById(orderId);
          if (order == null) {
              System.out.println("订单不存在,订单ID:" + orderId);
              return;
          }
          // 若未支付,取消订单并释放库存
          if ("UNPAID".equals(order.getStatus())) {
              order.setStatus("CANCELLED");
              orderMapper.updateById(order);
              // 释放库存
              inventoryMapper.restoreStock(order.getProductId(), order.getQuantity());
              System.out.println("订单30分钟未支付已取消,订单ID:" + orderId + ",库存已释放");
          } else {
              // 已支付,忽略消息
              System.out.println("订单已支付,无需取消,订单ID:" + orderId);
          }
      }
    }

架构优势

  • 精准延迟:消息延迟时间可精确到秒级(RocketMQ),避免定时任务的扫描延迟;
  • 低压力:无需定时扫描数据库,减少数据库查询压力(订单量10万时,定时任务需扫描10万条记录,延迟消息仅需处理未支付的订单);
  • 可扩展:支持其他延迟场景(如“订单支付后24小时未发货提醒”“会员到期前3天通知”),只需发送对应延迟级别的消息。

场景八:广播通知

业务痛点

分布式系统中,配置中心(如Nacos、Apollo)更新某一配置(如数据库连接池大小、接口超时时间)后,需通知所有依赖该配置的服务(如订单服务、支付服务、库存服务)重新加载配置。若采用“配置中心逐个调用服务接口”的方式,服务数量多时(如20个),调用效率低且易出现调用失败。

MQ解决方案

  1. 配置中心更新配置后,向MQ发送一条“配置更新”广播消息(包含配置Key、新值、版本号);
  2. 所有依赖该配置的服务作为消费者,订阅MQ的“配置更新”广播队列;
  3. 各服务接收到消息后,检查消息中的配置Key是否为自身依赖的配置:

    • 若是:重新加载配置(如更新数据库连接池、接口超时时间),并记录配置版本号;
    • 若否:忽略消息。

技术要点

  • MQ投递模式:采用“扇出交换机(Fanout Exchange)”或“主题交换机(Topic Exchange)”,实现消息广播;

    • 扇出交换机:所有绑定该交换机的队列都会收到消息,适合全量广播;
    • 主题交换机:通过通配符(如“config.order.#”“config.pay.#”)实现按服务类型广播;
  • 配置版本号:消息中携带配置版本号,服务端对比本地版本号,避免重复加载(如配置中心重试发送消息时);
  • 代码示例(RabbitMQ扇出交换机广播):

    // 1. 配置扇出交换机与消费者队列
    @Configuration
    public class ConfigBroadcastConfig {
      // 扇出交换机(配置更新广播)
      @Bean
      public FanoutExchange configFanoutExchange() {
          return ExchangeBuilder.fanoutExchange("config_broadcast_exchange").durable(true).build();
      }
    
      // 订单服务队列(绑定扇出交换机)
      @Bean
      public Queue orderConfigQueue() {
          return QueueBuilder.durable("order_config_queue").build();
      }
    
      // 支付服务队列(绑定扇出交换机)
      @Bean
      public Queue payConfigQueue() {
          return QueueBuilder.durable("pay_config_queue").build();
      }
    
      // 库存服务队列(绑定扇出交换机)
      @Bean
      public Queue inventoryConfigQueue() {
          return QueueBuilder.durable("inventory_config_queue").build();
      }
    
      // 绑定交换机与各队列
      @Bean
      public Binding orderConfigBinding() {
          return BindingBuilder.bind(orderConfigQueue()).to(configFanoutExchange());
      }
    
      @Bean
      public Binding payConfigBinding() {
          return BindingBuilder.bind(payConfigQueue()).to(configFanoutExchange());
      }
    
      @Bean
      public Binding inventoryConfigBinding() {
          return BindingBuilder.bind(inventoryConfigQueue()).to(configFanoutExchange());
      }
    }
    
    // 2. 配置中心发送广播消息
    @Service
    public class ConfigCenterService {
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private ConfigMapper configMapper;
    
      public void updateConfig(String configKey, String newValue) {
          // 1. 更新配置中心数据库
          Config config = configMapper.selectByKey(configKey);
          if (config == null) {
              config = new Config();
              config.setConfigKey(configKey);
              config.setConfigValue(newValue);
              config.setVersion(1);
              configMapper.insert(config);
          } else {
              config.setConfigValue(newValue);
              config.setVersion(config.getVersion() + 1);
              configMapper.updateById(config);
          }
    
          // 2. 发送配置更新广播消息
          ConfigUpdateMessage message = new ConfigUpdateMessage();
          message.setConfigKey(configKey);
          message.setNewValue(newValue);
          message.setVersion(config.getVersion());
          rabbitTemplate.convertAndSend("config_broadcast_exchange", "", JSON.toJSONString(message));
          System.out.println("配置更新广播消息发送成功,配置Key:" + configKey + ",版本号:" + config.getVersion());
      }
    }
    
    // 3. 订单服务消费广播消息,重新加载配置
    @Component
    public class OrderConfigConsumer {
      @Autowired
      private ConfigService configService;
      // 本地缓存配置版本号
      private Map<String, Integer> localConfigVersion = new ConcurrentHashMap<>();
    
      @RabbitListener(queues = "order_config_queue")
      public void handleConfigUpdate(String message) {
          ConfigUpdateMessage updateMessage = JSON.parseObject(message, ConfigUpdateMessage.class);
          String configKey = updateMessage.getConfigKey();
          int newVersion = updateMessage.getVersion();
          String newValue = updateMessage.getNewValue();
    
          // 对比本地版本号,避免重复加载
          Integer localVersion = localConfigVersion.getOrDefault(configKey, 0);
          if (newVersion <= localVersion) {
              System.out.println("配置版本号未更新,忽略消息,配置Key:" + configKey);
              return;
          }
    
          // 重新加载配置(以数据库连接池为例)
          if ("db.pool.maxActive".equals(configKey)) {
              configService.updateDbPoolMaxActive(Integer.parseInt(newValue));
              localConfigVersion.put(configKey, newVersion);
              System.out.println("订单服务更新数据库连接池配置,maxActive:" + newValue + ",版本号:" + newVersion);
          } else if ("api.timeout.order".equals(configKey)) {
              configService.updateOrderApiTimeout(Integer.parseInt(newValue));
              localConfigVersion.put(configKey, newVersion);
              System.out.println("订单服务更新接口超时配置,timeout:" + newValue + "ms,版本号:" + newVersion);
          }
          // 其他配置Key的处理逻辑...
      }
    }

架构优势

  • 高效广播:一次发送消息,所有服务同时接收,避免逐个调用的效率问题;
  • 低耦合:配置中心无需知道有多少服务依赖配置,新增服务只需绑定队列即可;
  • 可靠通知:通过MQ消息重试机制,确保所有服务都能收到配置更新消息(即使服务暂时宕机,恢复后也能消费)。

场景九:限流熔断

业务痛点

分布式系统中,服务间存在依赖关系(如订单服务依赖支付服务)。当支付服务因故障响应缓慢(如接口超时从100ms变为5000ms)或返回错误(如500错误)时,订单服务会因等待支付服务响应而产生大量线程阻塞,进而导致订单服务线程池耗尽,无法处理新请求,引发“级联故障”。

MQ解决方案

采用“MQ + 限流熔断”组合方案,保护订单服务不被依赖服务的故障影响:

  1. 订单服务调用支付服务前,先检查“支付服务健康状态”(通过熔断器统计调用成功率、超时率);
  2. 若支付服务健康(成功率>90%,超时率<5%):直接调用支付服务接口;
  3. 若支付服务不健康(成功率<80%,超时率>10%):触发熔断,订单服务不直接调用支付服务,而是向MQ发送“支付请求”消息,同时返回“支付处理中,请稍后查询结果”给用户;
  4. 支付服务恢复健康后,从MQ中拉取“支付请求”消息,异步处理支付,处理完成后通过MQ反馈结果给订单服务;
  5. 订单服务接收到支付结果后,更新订单状态,并通过短信/APP推送通知用户。

技术要点

  • 熔断器选择:Sentinel(阿里开源,支持限流、熔断、降级)或Resilience4j(轻量级,适合微服务);
  • MQ角色:熔断时作为“请求缓冲池”,暂存支付请求,避免订单服务线程阻塞;
  • 健康检查:熔断器定时统计支付服务调用指标(成功率、超时率、错误率),动态调整熔断状态(闭合→半开→打开);
  • 代码示例(Sentinel熔断 + RabbitMQ缓冲):

    // 1. 配置Sentinel熔断器(支付服务调用熔断规则)
    @Configuration
    public class SentinelConfig {
      @PostConstruct
      public void initFlowRules() {
          // 熔断规则:支付服务调用接口
          DegradeRule rule = new DegradeRule();
          rule.setResource("payServiceCall");  // 资源名(支付服务调用接口)
          rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);  // 按异常比例熔断
          rule.setCount(0.1);  // 异常比例阈值(10%)
          rule.setTimeWindow(60);  // 熔断时间窗口(60秒)
          rule.setMinRequestAmount(10);  // 最小请求数(10次请求后才开始统计)
          DegradeRuleManager.loadRules(Collections.singletonList(rule));
    
          // 限流规则:订单服务接口限流(1000 TPS)
          FlowRule flowRule = new FlowRule();
          flowRule.setResource("createOrderApi");
          flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
          flowRule.setCount(1000);
          FlowRuleManager.loadRules(Collections.singletonList(flowRule));
      }
    }
    
    // 2. 订单服务调用支付服务(带熔断与MQ缓冲)
    @Service
    public class OrderPayService {
      @Autowired
      private PayServiceFeignClient payServiceFeignClient;  // Feign调用支付服务
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private OrderMapper orderMapper;
    
      // 订单创建接口(带限流)
      @SentinelResource(value = "createOrderApi", blockHandler = "createOrderBlockHandler")
      public String createOrder(Order order) {
          // 保存订单
          orderMapper.insert(order);
          // 调用支付服务(带熔断)
          String payResult = callPayServiceWithDegrade(order);
          return payResult;
      }
    
      // 支付服务调用(带熔断)
      @SentinelResource(value = "payServiceCall", fallback = "payServiceFallback")
      private String callPayServiceWithDegrade(Order order) {
          // 直接调用支付服务Feign接口
          PayRequest request = new PayRequest();
          request.setOrderId(order.getOrderId());
          request.setAmount(order.getAmount());
          PayResponse response = payServiceFeignClient.pay(request);
          if ("SUCCESS".equals(response.getCode())) {
              order.setStatus("PAID");
              orderMapper.updateById(order);
              return "订单创建成功,已支付";
          } else {
              return "订单创建成功,支付失败:" + response.getMsg();
          }
      }
    
      // 支付服务熔断降级方法(触发熔断时调用)
      private String payServiceFallback(Order order, Throwable e) {
          // 向MQ发送支付请求消息,异步处理
          PayRequestMessage message = new PayRequestMessage();
          message.setOrderId(order.getOrderId());
          message.setAmount(order.getAmount());
          message.setUserId(order.getUserId());
          rabbitTemplate.convertAndSend("pay_request_queue", JSON.toJSONString(message));
          // 更新订单状态为“支付处理中”
          order.setStatus("PAYING");
          orderMapper.updateById(order);
          // 返回用户提示
          return "订单创建成功,支付处理中,请稍后在【我的订单】中查询结果";
      }
    
      // 订单接口限流降级方法
      public String createOrderBlockHandler(Order order, BlockException e) {
          return "当前订单创建人数过多,请稍后重试";
      }
    }
    
    // 3. 支付服务恢复后,消费MQ消息处理支付
    @Component
    public class PayRequestConsumer {
      @Autowired
      private PayService payService;
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private OrderFeignClient orderFeignClient;
    
      @RabbitListener(queues = "pay_request_queue")
      public void handlePayRequest(String message) {
          PayRequestMessage requestMessage = JSON.parseObject(message, PayRequestMessage.class);
          String orderId = requestMessage.getOrderId();
          BigDecimal amount = requestMessage.getAmount();
    
          try {
              // 处理支付逻辑
              PayResult result = payService.processPay(orderId, amount);
              // 发送支付结果消息给订单服务
              PayResultMessage resultMessage = new PayResultMessage();
              resultMessage.setOrderId(orderId);
              resultMessage.setPayStatus(result.isSuccess() ? "PAID" : "PAY_FAILED");
              resultMessage.setPayTime(new Date());
              rabbitTemplate.convertAndSend("pay_result_queue", JSON.toJSONString(resultMessage));
              System.out.println("支付请求处理完成,订单ID:" + orderId + ",支付状态:" + resultMessage.getPayStatus());
          } catch (Exception e) {
              // 支付处理失败,消息重新入队(重试3次后进入死信队列)
              throw new AmqpRejectAndDontRequeueException("支付处理失败,订单ID:" + orderId, e);
          }
      }
    }
    
    // 4. 订单服务消费支付结果消息,更新订单状态
    @Component
    public class PayResultConsumer {
      @Autowired
      private OrderMapper orderMapper;
      @Autowired
      private SmsService smsService;
    
      @RabbitListener(queues = "pay_result_queue")
      public void handlePayResult(String message) {
          PayResultMessage resultMessage = JSON.parseObject(message, PayResultMessage.class);
          String orderId = resultMessage.getOrderId();
          String payStatus = resultMessage.getPayStatus();
    
          // 更新订单状态
          Order order = orderMapper.selectById(orderId);
          if (order == null) {
              System.out.println("订单不存在,订单ID:" + orderId);
              return;
          }
          order.setStatus(payStatus);
          order.setPayTime(resultMessage.getPayTime());
          orderMapper.updateById(order);
    
          // 发送支付结果短信通知
          String smsContent = "您的订单(ID:" + orderId + ")" + 
                  ("PAID".equals(payStatus) ? "已支付成功" : "支付失败,请重新尝试") + 
                  ",详情可在APP【我的订单】中查看。";
          smsService.sendSms(order.getUserId(), smsContent);
          System.out.println("订单状态更新完成,订单ID:" + orderId + ",支付状态:" + payStatus);
      }
    }

架构优势

  • 熔断保护:依赖服务故障时,触发熔断,避免订单服务线程阻塞,保障核心订单创建功能可用;
  • 缓冲请求:MQ暂存支付请求,待依赖服务恢复后异步处理,不丢失用户请求;
  • 限流保护:通过Sentinel限流,避免订单服务被瞬时高峰流量冲垮,保障系统稳定。

场景十:分布式事务最终一致性

业务痛点

跨银行转账场景中,用户从A银行转账1000元到B银行,需完成“A银行扣减1000元”和“B银行增加1000元”两个操作。由于A银行和B银行是不同的系统(不同数据库、不同服务),无法使用传统的数据库事务保证原子性,存在“A银行扣钱成功但B银行加钱失败”的风险,导致数据不一致。

MQ解决方案

采用“可靠消息最终一致性”方案,实现跨系统事务的最终一致性,流程如下:

  1. A银行系统执行本地事务(扣减1000元):

    • 若扣减失败:直接返回转账失败给用户,流程结束;
    • 若扣减成功:向MQ发送一条“转账发起”消息(包含转账ID、A银行账户、B银行账户、金额1000元);
  2. MQ收到消息后,向A银行返回“消息发送成功”确认;
  3. B银行系统订阅“转账发起”消息,执行本地事务(增加1000元):

    • 若增加成功:向MQ发送“转账成功”确认消息,MQ删除“转账发起”消息;
    • 若增加失败:不发送确认消息,MQ会定时重新投递“转账发起”消息(重试机制);
  4. A银行系统订阅“转账成功”消息,更新本地转账状态为“成功”;
  5. 若B银行多次重试仍失败(如B银行系统长时间宕机),MQ将“转账发起”消息转入死信队列,A银行系统监控到死信消息后,执行“回滚本地事务”(退还1000元给用户),并通过人工介入处理B银行问题。

技术要点

  • 消息可靠性:开启MQ消息持久化、生产者确认、消费者手动ACK,确保消息不丢失;
  • 重试机制:设置MQ消息重试次数(如5次),重试间隔(如10s、30s、1min、5min、10min),避免频繁重试加重B银行系统负担;
  • 死信处理:死信队列需关联监控告警(如短信通知运维人员),确保异常情况及时被发现;
  • 幂等性:B银行系统处理“转账发起”消息时,需通过“转账ID”做幂等性校验(如查询是否已处理过该转账ID),避免重复加钱;
  • 代码示例(核心流程):

    // 1. A银行系统执行本地事务并发送消息
    @Service
    public class ABankTransferService {
      @Autowired
      private ABankAccountMapper accountMapper;
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private TransferRecordMapper transferRecordMapper;
    
      @Transactional
      public String transferToBBank(String fromAccount, String toBBankAccount, BigDecimal amount) {
          // 1. 生成唯一转账ID
          String transferId = UUID.randomUUID().toString();
          // 2. 扣减A银行账户余额(本地事务)
          int rows = accountMapper.deductBalance(fromAccount, amount);
          if (rows == 0) {
              throw new RuntimeException("A银行账户余额不足,转账失败");
          }
          // 3. 记录转账记录(状态:处理中)
          TransferRecord record = new TransferRecord();
          record.setTransferId(transferId);
          record.setFromAccount(fromAccount);
          record.setToAccount(toBBankAccount);
          record.setAmount(amount);
          record.setStatus("PROCESSING");
          record.setCreateTime(new Date());
          transferRecordMapper.insert(record);
          // 4. 发送转账发起消息(开启生产者确认)
          try {
              TransferMessage message = new TransferMessage();
              message.setTransferId(transferId);
              message.setFromAccount(fromAccount);
              message.setToBBankAccount(toBBankAccount);
              message.setAmount(amount);
              // 同步等待生产者确认
              rabbitTemplate.invoke(new RabbitOperations.Callback<String>() {
                  @Override
                  public String doInRabbit(RabbitOperations operations) throws AmqpException {
                      operations.convertAndSend("transfer_exchange", "transfer.to.bbank", JSON.toJSONString(message),
                              new CorrelationData(transferId));
                      return null;
                  }
              });
              System.out.println("A银行转账发起,转账ID:" + transferId + ",已扣减余额:" + amount);
              return "转账处理中,转账ID:" + transferId;
          } catch (Exception e) {
              // 消息发送失败,回滚本地事务(Spring事务管理自动回滚)
              throw new RuntimeException("转账消息发送失败,已回滚A银行余额", e);
          }
      }
    
      // 5. 消费B银行转账成功消息,更新转账状态
      @RabbitListener(queues = "abank_transfer_success_queue")
      public void handleTransferSuccess(String message) {
          TransferSuccessMessage successMessage = JSON.parseObject(message, TransferSuccessMessage.class);
          String transferId = successMessage.getTransferId();
          // 更新转账记录状态为成功
          TransferRecord record = transferRecordMapper.selectByTransferId(transferId);
          if (record != null && "PROCESSING".equals(record.getStatus())) {
              record.setStatus("SUCCESS");
              record.setCompleteTime(new Date());
              transferRecordMapper.updateById(record);
              System.out.println("A银行转账成功,转账ID:" + transferId);
          }
      }
    
      // 6. 消费死信消息,回滚本地事务
      @RabbitListener(queues = "transfer_dead_queue")
      public void handleTransferDeadLetter(String message) {
          TransferMessage transferMessage = JSON.parseObject(message, TransferMessage.class);
          String transferId = transferMessage.getTransferId();
          String fromAccount = transferMessage.getFromAccount();
          BigDecimal amount = transferMessage.getAmount();
          // 回滚A银行余额
          accountMapper.restoreBalance(fromAccount, amount);
          // 更新转账记录状态为失败
          TransferRecord record = transferRecordMapper.selectByTransferId(transferId);
          if (record != null) {
              record.setStatus("FAIL");
              record.setFailReason("B银行处理失败,已回滚");
              transferRecordMapper.updateById(record);
          }
          // 发送告警短信给运维人员
          String alarmContent = "转账ID:" + transferId + " 进入死信队列,已回滚A银行余额 " + amount + ",请检查B银行系统";
          SmsUtils.sendSms("13800138000", alarmContent);
          System.out.println("转账失败已回滚,转账ID:" + transferId + ",告警已发送");
      }
    }
    
    // 2. B银行系统消费转账发起消息,执行本地事务
    @Component
    public class BBankTransferConsumer {
      @Autowired
      private BBankAccountMapper accountMapper;
      @Autowired
      private RabbitTemplate rabbitTemplate;
      @Autowired
      private BBankTransferRecordMapper transferRecordMapper;
    
      @RabbitListener(queues = "bbank_transfer_queue")
      public void handleTransferRequest(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
          TransferMessage transferMessage = JSON.parseObject(message, TransferMessage.class);
          String transferId = transferMessage.getTransferId();
          String toAccount = transferMessage.getToBBankAccount();
          BigDecimal amount = transferMessage.getAmount();
    
          try {
              // 1. 幂等性校验:判断是否已处理过该转账ID
              BBankTransferRecord record = transferRecordMapper.selectByTransferId(transferId);
              if (record != null && "SUCCESS".equals(record.getStatus())) {
                  // 已处理过,手动ACK
                  channel.basicAck(deliveryTag, false);
                  System.out.println("B银行已处理该转账,转账ID:" + transferId + ",忽略消息");
                  return;
              }
    
              // 2. 执行本地事务:增加B银行账户余额
              accountMapper.increaseBalance(toAccount, amount);
              // 3. 记录B银行转账记录
              if (record == null) {
                  record = new BBankTransferRecord();
                  record.setTransferId(transferId);
                  record.setToAccount(toAccount);
                  record.setAmount(amount);
                  record.setStatus("SUCCESS");
                  record.setCreateTime(new Date());
                  transferRecordMapper.insert(record);
              } else {
                  record.setStatus("SUCCESS");
                  transferRecordMapper.updateById(record);
              }
    
              // 4. 发送转账成功消息给A银行
              TransferSuccessMessage successMessage = new TransferSuccessMessage();
              successMessage.setTransferId(transferId);
              rabbitTemplate.convertAndSend("transfer_exchange", "transfer.success.to.abank", JSON.toJSONString(successMessage));
    
              // 5. 手动ACK确认消息消费成功
              channel.basicAck(deliveryTag, false);
              System.out.println("B银行转账处理成功,转账ID:" + transferId + ",已增加余额:" + amount);
          } catch (Exception e) {
              // 6. 处理失败,判断是否重试(重试5次后进入死信队列)
              Integer retryCount = (Integer) channel.getMessageProperties().getHeaders().get("retry-count");
              if (retryCount == null) retryCount = 0;
              if (retryCount < 5) {
                  // 重新入队,重试次数+1
                  channel.getMessageProperties().getHeaders().put("retry-count", retryCount + 1);
                  channel.basicNack(deliveryTag, false, true);
                  System.out.println("B银行转账处理失败,重试次数:" + (retryCount + 1) + ",转账ID:" + transferId);
              } else {
                  // 重试5次失败,进入死信队列
                  channel.basicNack(deliveryTag, false, false);
                  System.out.println("B银行转账处理失败,重试5次后进入死信队列,转账ID:" + transferId);
              }
              e.printStackTrace();
          }
      }
    }

架构优势

  • 最终一致性:通过MQ消息重试与死信处理,确保“扣钱”与“加钱”两个操作最终同时成功或同时失败,避免数据不一致;
  • 可靠性:消息不丢失,即使跨系统、跨网络,也能通过重试机制保障事务推进;
  • 低耦合:A银行与B银行仅通过MQ消息交互,无需感知对方系统细节,便于维护与扩展。

最后说一句

MQ的核心价值在于“解耦、异步、削峰”,但并非所有场景都适合用MQ——若业务逻辑简单、无跨系统交互、对实时性要求极高(如毫秒级响应),直接调用接口可能更高效。在实际项目中,需结合业务痛点、技术成本、维护难度综合判断是否使用MQ,以及选择哪种MQ(RabbitMQ/Kafka/RocketMQ)。希望以上10种场景能给大家带来启发,避免在项目中滥用或误用MQ。

管理心得,不间断记录

兼职管理

1、响应时间 微信响应时间 超过5分钟 2次 中止合作
2、培养能力 经验、工具
3、收益高于预期
4、工程师和质量

关于裁员

裁员名单是根据个人在公司工作期间对代码的贡献来制定和排名的。

高效工作总结:

0、必胜信心
1、结果导向
2、完成时间
3、物理专注

公司管理思路:

互相投资、互相信任,非传统主雇关系
1、培养人,把能力传授给下面的人
2、放权
3、分享机制

小团队项目组基本管理:

1、放手,新问题主动处理、老问题交给下属处理,借此制定标准处理问题流程化;
2、榜样,有困难主动承担、有问题主动冲锋;
3、让功让利,向上让功、向下让利;

温和派领导人管理思路:

1、立功,对公司有贡献;
2、握权,能够影响员工收入组成40%、考核评分、项目奖金;
3、守信,不轻易承诺、一旦承诺必须兑现;
4、立标杆,培养典型员工进步并树立标杆;

团队凝聚力

1、身先士卒,带动员工积极性;
2、知人善任,用人长处;
3、勇于背锅;
4、尊重、感谢,公约机制;

提高员工工作效率

1、责任到人;
2、限定时间;
3、确定标准;
4、重复一次;
5、检查机制;
6、验收考核;

老版轻松

X成就感
V想明白,布置下去
1、会算账,不算公司赚多少钱、算员工能赚多少钱;
2、会画饼,公司壮大时候做到位置、发展前景;
3、会激发,你是专家我不行、顶梁柱;

软件架构师应该知道的97件事之概括

架构师是一种神秘的职位,据说每个架构师都有密不可传的方法,当然我们不信,更多的是只可意会不可言传。就是说了我们也不会懂,因为还每到“火候”。所能做的就是,当我们到这种火候的时候我们能想起来曾经有过架构师这么说过,然后我们就可以更自信的向前大步走....

1、客户需求重于个人简历

要想拥有漂亮的个人简历:我们常常向客户推荐技术、手段,甚至方法论来解决问题,使用时髦的编程技巧和流行的范式,有时候根本不是寻求解决问题的最佳方案。

积累一批满意的客户,选择切合实际的技术解决他们的难题,让他们乐于推荐你才是最好的履历。

2、简化根本复杂性,消除偶发复杂性

根本复杂性指的是与生俱来的、无法避免的困难。比如,协调全国的空中交通就是一个“天生的”复杂问题,必须实时跟踪每架飞机的位置。

偶发复杂性:是人们解决根本复杂性的过程中衍生的。

架构师的责任在于解决问题的根本复杂性,同时避免引入偶发复杂性。

怎么做?尽量选择源自实际项目的框架,警惕那些象牙塔里面的产品。

3、关键问题可能不是出在技术上

简单的项目也会翻船,而且这不是个别情况。大多数项目是人完成的,人才是项目成败与否的基础。

如果团队里有人工作方式不正确,拖项目后腿怎么办?有一种非常古老但很完善的技术可以帮助你解决问题。它可能是人类历史上最重要的技术创新,这就是对话。

有几个简单的对话技巧可以显著改善对话效果:

不要把对话当成对抗。

不要带着情绪与人沟通。

尝试通过沟通设置共同的目标。

4、以沟通为中心,坚持简明清晰的表达方式和开明的领导风格

沟通必须简明清晰,没有人愿意阅读冗长的架构决策文档,架构师言简意赅的表达观点是项目成功的必要条件。

架构师往往忽略了自己也是领导者。作为领导者我们必须获得同伴的尊敬才能顺利开展工作。所有的成员都希望有明确的沟通和开明的领导。只能这样才能改善沟通效果,建立团结健康的工作环境。

5、架构决定性能

大家似乎理解,事实并未如此,有些架构师认为简单的更换底层架构就足以解决应用的性能问题,他们很可能轻信了“经测试产品性能超出竞争对手25%”,比如从4ms到3ms,这1ms放在一个性能极低的架构里几乎可以忽略不计。

归根结底,所有产品和架构必须遵循分布式计算和物理学的基本原理:运行应用和产品的计算机性能有限,通过物理连接和逻辑协议实现的通信必然有延时。因此,应该承认架构才是影响应用性能和可伸缩性的决定因素。性能参数是无法简单的通过更换软件,或者“调优”底层软件架构来改善的,我们必须在架构的设计和重新设计上投入更多的精力。

6、分析客户需求背后的意义

顾客和最终用户通常提出的所谓需求,只是他们心目中可行的解决方案,并不是问题唯一的解决途径,当了解顾客的需求背后的意义,我们可以为顾客解决真正的问题并可能降低难度。

7、起立发言

许多架构师都是从技术岗位上成长起来的,他们擅长和机器打交道,然而架构师更需要与人打交道,无论劝说开发人员接受具体的设计模式,还是向管理层解释购买中间件的利弊,沟通都是达成目标的核心技能。

有经验的架构师会很重视推销自己的想法也明白有效沟通的重要性,其中一个简单又实用的技巧是在2人以上的场合发表意见时请“站起来”,起立发言非常重要尤其是当其他人坐着的时候。

当你站起来的时候无形中添加一种权威和自信,自然就控制住了场面,听众不会随意打断你的发言,这些都会让你的发言效果大为改观,你会发现站立可以更好的用双手和肢体语言。在10人以上的场合,起立发言方便你与每位听众保持视线接触。眼神交流、肢体语言等表达方式在沟通中的作用不可小觑。起立发言还可以让你更好的控制语气、语调、语速和嗓门,让你的声音传的更远。当你讲到重点内容时,注意放慢语速。发声技巧也能显著改善沟通效果。

比沟通事半功倍,起立发言是最简单、有效的方法。

8、故障终究会发生

硬件会出错,于是我们增加冗余资源来提升系统的可靠性,同时也增加了至少有一台设备出错的概率。

软件会出错,增加额外的监控程序也会出错。于是我们又为自动化增加监控,结果是更多的软件,导致更高的故障率。类似的如:三里岛核电站泄漏事故

既然必然会出错就需要事先设计好防范故障的模型,以应对威胁系统安全的意外情况。

9、我们常常忽略自己在谈判

我们都面临过削减预算的要求,如果资金运转促襟见肘,技术方案只能委屈求全。

比如如下情景:

“我们真的需要这东西吗?”项目投资人发难道。

尽管真的需要,我们通常也不能这么回答,因为此时我们是在谈判,此时我们需要认清自己的角色,不能把自己当成工程师,而且投资人明白他在和你谈判,我们应该这样回答"真的需要吗"这类问题:

“单台服务器每天至少崩溃3次,没有第二台我们甚至无法跟董事会演示,事实上我们需要4台服务器,构成2组,这样在需要时断开一组而不必被迫关闭系统。即使有一台出现意外,也不影响系统正常运行”

10、量化需求

速度快不能算作需求,响应灵敏和可扩展也不能算需求,因为我们无法客观地判断是否满足了这样的条件。

正确的描述需求应该像这样:“必须在1500ms内响应用户的输入。在正常负载下,平均响应时间控制在750ms-1250ms之间。由于用户无法识别500ms以内的响应,所以我们没必要将响应时间降到这个范围一下。”

11、一行代码比500行架构说明更有价值

架构说明书(specifications)很重要,因为它描述了构建系统的模式,但是静下心来全面彻底地理解架构——即从宏观上把握组件之间的交互,又着眼于组件内部的代码细节——也很重要。

架构师往往容易被抽象的架构所吸引,沉迷于设计过程。事实上仅有架构说明书是远远不够的。软件项目的最终目标是建立生产体系,架构师必须时刻关注这个目标,牢记设计只是达到目标的手段,而不是目标。

如果亲自开发,应该珍视自己花在写代码上的时间,千万别听信这会分散架构师精力的说法。这样既能拓展你的宏观视野,也能丰富你的微观世界。

12、放之四海皆准的解决方案

架构师应该坚持培养和训练“情景意识”——因为我们遇到的问题千差万别,不存在放之四海皆准的解决方案。

“情景模式”:调查有经验的架构师处理复杂问题的方式。有经验的架构师和设计师的答案如出一辙:只须使用常识....【一个】比“常识”更贴切的说法是“情景意识”——在给定情景下对合理性的把握。架构师通过学习和实践,不断积累的案例和经验,建立足够的情景意识。他们通常需要十年的磨练,才能解决系统层次的问题。

13、提前关注性能问题

商业用户的需求主要表现卫队功能的要求。系统的非功能特性则由架构师负责,包括:性能表现、灵活性、持续正常工作时间、技术支持资源等。但是对非功能特性的初始测试往往被拖到开发周期的最后阶段,有时还由开发团队来操刀,这样的错误屡见不鲜。

在项目周期的最后阶段才关注性能问题,会导致我们错失大量历史信息,这些信息包含性能变化的细节。如果性能是架构设计的重要指标,就应该尽早展开性能测试。在采用敏捷方法开发的项目中,如果有2周为一个迭代周期,我认为性能测试的开始时间最迟不能晚于第三次迭代。

坚持技术测试是需要耐心和毅力的,无论是搭建合适的测试环境,采集适当的数据集,还是编写必要的测试用例,都需要投入大量的时间。

14、架构设计要平衡兼顾多方需求

平衡兼顾各方的要求和项目的技术需求

CEO需要控制成本

运营部门要求软件易于管理

二次开发人员要求软件代码容易学习方便维护

业务部门:旅行合同义务、创造收益、树立客户口碑、控制成本,创造有价值的技术资产

技术部门:确保软件的功能

15、草率提交任务是不负责任的行为

傍晚时候,团队正在完成本次迭代的收尾工作,一切按部就班、有条不紊。只有约翰赶着赴约有些急躁,他仓促写完自己的代码,编译、检入,然后匆匆离开。几分钟后红灯亮起(许多采用敏捷开发方法的软件公司(例如ThouthtWorks)在每个团队成员的桌上放置一盏3色灯,用来表示当前的集成状态,黄色正在集成,绿色集成成功,红色集成失败),构建失败。约翰没来得及执行自动测试就草率地提交了任务,连累大家无法继续工作。正常的工作秩序全被打乱了。

这个时候架构师就该发挥作用了,营造一种团队文化,以维护流程通畅为重,以浪费他人时间为耻。要做到这一点,务必在系统内实现完善的自动测试功能,纠正开发人员的行为。

沉下心来改变系统的生产效率,缩短流程避免各行其是,才能缩短开发时间。总之一定要杜绝一切草率提交任务的念头。

16、不要在一棵树上吊死

负责构建系统的人似乎无法接受这样的事实:没有哪种数据类型、消息格式、消息传送机制,甚至主流的架构组件、策略、观点用来能够用来解决所有的业务问题,毕竟当大家都希望摆脱业务需求不断滋生的意外和烦恼。

才用多钟表现方式、多钟传输方式不是为了消遣。应当认识到,通过分解系统的非功能参数,可以为客户提供多样化的解决方案。

17、业务目标至上

在商业化的背景下开发企业应用,架构师必须成为业务部门和技术部门沟通的桥梁,周旋调解,兼顾双方利益,同时业务目标来驱动项目 开发。业务目标和实际的开发条件应该成为架构师主持制定决策的参照系统。

在启动一个软件项目之前,应当制定计划,明确投资回报的预期。架构师必须把握这个预期,并预估该项目的商业价值,避免做出错误的技术决策,造成经费超支。

用业务目标驱动项目开发,才能保证软件开发团队的长远利益。

18、先确保解决方案简单可用,再考虑通用性和复用性

许多用来实现基础设施的代码,包括组建、框架、类库、基础服务,普遍存在一个问题,它们设计一向强调通用性而不考虑具体应用。

如果存在多个可实施方案难以取舍,“先简单后通用”原则可以成为最终的评判标准。

虽然很多架构师重视通用性,但这样做是有前提条件的。并非所有人都需要通用性,愿意为它掏钱。

19、架构师应该亲力亲为

称职的架构师应该通过示范领导团队,架构师通常都取得过不错的业绩,有份出彩的简历,容易获得业务人员和技术人员的青睐。但除非他能展示自己的实践能力,否则很难赢得团队的尊重,团队成员将无法从他身上学到东西,大家甚至难以在他的领导下做好本职工作。

20、持续集成

架构师(无论是应用架构师还是企业架构师)都应该在项目中鼓励推广持续集成的方法和工具。

现在持续集成已经取代了“尽早构建,经常构建”(build early and often)的提法,它确保当前的开发不会出现意外,是一种降低风险的技巧。

构建应用程序是持续集成最主要的内容,它通常是自动执行的,可以设置在夜里执行,或者当源代码改变时自动触发。当然你也可以选择手动构建。

21、避免进度调整失误

导致项目失败的原因很多,最常见的是中途临时调整进度。

改变计划回答带来以下问题:

仓促的进度会导致拙略的设计、蹩脚的文档,可能引发质量问题,导致用户拒绝签收

仓促完成代码导致bug增多,测试不充分增加测试中可能出现的问题

以上问题均能引发产品质量问题,而解决产品质量的问题代价更高。最后的结果是成本不降反升,通常项目就是这样失败的。

22、取舍的艺术

架构师应该明白鱼和熊掌不可兼得的道理。如果2个性能本身就是冲突的,满足一项就会导致另一项失败或者整体失败。

23、打造数据库堡垒

创建牢固的数据模型要从第一天开始

牢固的数据模型既可以保障当前的数据的安全,又为今后提供可扩展性。要保障数据安全就必须隔离来自应用层的bug(在不断变化的应用层中这些bug无处不在,不会因为你的勤奋而消失),必须严格遵守引用完整性规则,尽量使用域约束规则,还要选择恰当的键,即保证数据的完整性,又遵守约束规则。要实现可扩展性,就必须正确地将数据标准化。以便今后在数据模型上添加架构层:千万不要偷懒走捷径。

为了妥善保护数据库必须拒绝无效数据,阻止无意义的关系。在定义键、外键、域约束时,应该采取简洁的容易被理解和验证的名称,使他们含义不言自明。数据模型中的域规则也要做到物理化和持久化,避免他们在应用逻辑发生改变时被删除。

为了充分发挥关系型数据库的作用——让它真正的成为应用的一部分,而不仅仅是存放数据的库房——必须从开始构建数据库时,就深刻理解业务需求。随着产品的演变,数据层也会发生变化。

24、重视不确定性

当你面临2中选择的时候应该仔细考虑设计中的不确定性。

迫于压力,人们常常为了决策而决策。这时可以借鉴期权思想(指在期货交易中,权利的受让可以在将来的某个约定的时间,根据当时的情况决定是否行使权利,即推迟做决定时间),当你在不同的系统开发路线之间举棋不定时,不要急于做出决策。推迟决策,直到掌握更详实的信息,以便做出更可靠的决策。但也别太迟,要赶在这些信息失效前利用他们。

25、不要轻易放过不起眼的问题

问题出现时,虽然个别团队成员会发现一些端倪,但往往由于大多数人认识不到其严重性,这些问题不是被忽略就是被搁置,知道变得难以解决。

注意造成的原因和哪些方法克服这些消极因素。

26、让大家学会复用

有这样一种观点,认为设计优良的框架、细致考虑并精巧实现的架构自然会被人们重复利用。

但也是在满足下列条件下擦可能被复用:

大家都知道它的存在

大家知道如何使用它们

大家认识到利用已有资源好过自己动手

27、架构里面没有大写“I"

英文单词架构(architecture)里面有字母”i"但不是大写的“I”。它代表的不是那个喜欢唤起别人关注,喜欢凌驾于众人之上的“I"(自我)。

自我可能是我们这最大的敌人。

如何避免犯错误?

需求不会撒谎。

重视团队合作。

检查你的工作。

自我反省。

28、使用”一千英尺高“的视图

用来了解正在开发的软件质量如何

在架构图中,系统是由若干个小方框组成的,方框之间的连线代表着各种含义:

依赖关系、数据流、共享资源等。

这种图好比从飞机上俯瞰地面上的风景,我们称之为“三万英尺高”的视图。另一种典型的视图是源代码,好比占在地面上看大地。两种视图都无法冲分钟展现软件的质量:前者太抽象,而后者细节太多,以至于我们看不清整个架构。很显然我们需要一个介于2着之间的视图——“一千英尺高”的视图。

"一千英尺高“的视图提供的信息来自恰当的层次,囊括大量数据和多钟度量标准,例如方法数,类扇出数和圈复杂度。具体的视图与特定的质量属性密切相关。

一旦我们绘制出合适的视图,判断软件质量就更客观了。

29、先尝试后决策

创建一个应用需要作出很多决策。有些决策设计挑选框架和函数,而另一些则需要选定特定的设计模式。

架构师应该持续关注那些马上要制定的决策,架构师可以在决策之前,要求几个开发人员商量解决方案,比较不同解决方案的优点和弊端。

对同一个问题尝试2种或者2种以上的解决方案,可能是代价最低的选择。事后发现方案不合适或者是没人发现方案不合适都是糟糕的情况。

30、掌握业务领域知识

高水平的软件架构师不仅要懂技术,还要掌握问题空间对应的业务领域的知识。缺乏业务领域知识的架构师不能顺利地解决问题,无法把握业务目标和业务需求,也就难以设计有效的架构来满足需求。
31、程序设计师一种设计

程序设计属于设计范畴而不是生产范畴。

软件的生产则是自动化的,由编译器、构建工具和测试代码共同完成。

如果把编写代码看成设计行为,而不是生产行为,我们就能采用一些已经被证明有效的管理方式。这些方法过去用于管理不可预测性的创新工作,比如研发新车、新药、新的电脑游戏。我们指的是敏捷的产品管理方法和精益生产方法,比如SCRUM。

32、让开发人员自己做主

多数架构师都是从开发人员干起的。以前作为开发人员你很少有机会仔细观察整个系统是怎样组合在一起的,而作为架构师这是你工作的重点。

如果想出色的完成工作,是不可能有空闲去干预开发人员的。

33、时间改变一切

选择值得投入精力的工作

简单原则,回顾以前或者更早的项目时,几乎都会惊诧自己当初的做法,如果有机会再做一次,我们一定会以更简单点的方法来完成。这就是时间的作用。

别跟以前的工作过不去,你现在看重的设计思路,可能2-3年后就会被自己否定。

34、设立软件架构专业为时尚早

设计软件架构师一门手艺,从业者无意要通过实践和训练才能在这个领域获得成功。

35、控制项目规模

估算与准确的科学计算相差甚远,所以产品特性实现起来常常比预期要困难。

缩小和控制项目规模策略:

抓住真正需求。分而治之,设置优先级,尽快交付。

敏捷方法的倡导者提倡开发”最简单有用的东西“,越复杂的架构越难以实现。缩小项目规模通常会降低架构的复杂性,这是架构师提高成功几率最有效的途径。

36、架构师不是演员,是管家

架构师接受新项目,都渴望证明自己的价值,这是人之常情。

炫耀和作秀与指挥开发项目背道而驰。

架构师的职责和管家类似,承担着管理他人资产的责任。

架构师要满足不同领域的客户需求,而这些领域的专业知识通常是架构师所不具备的。

37、软件架构的道德责任

软件世界的道德范畴边界并不清晰,尽管有些行为无疑是不道德的,比如侵犯他人的公民权利等,但还有些行为的道德意义不被察觉,比如浪费别人的时间。

架构师的每项决策(例如设置必填项和规定流程),都限制了用户可以做什么不能做什么。这比法律容易的多,并且还找不到法院受理他们的诉讼。

我们可以从倍增效应的角度来看待软件的影响。软件问题所造成的损失将以成不可估量的倍数出现,尤其是给人心理上的。

假设架构师要实现一个新功能,简单的设计要1天完成复杂的设计要1周的时间,但简单的设计要强迫用户输入大量的数据,这个过程常常会丢失数据,耽搁工作,让人非常沮丧。从长远看浪费别人的时间将远远超过你省下的时间。

损人利己是不道德的行为哪怕程度很轻。

38、摩天大厦不可伸缩

土木工程不只是设计建筑这么简单,真正的难题在于规划整个施工过程,确保建筑物拔地而起,包括从奠基到竣工的所有工作。其中有很多经验值得我们借鉴,尤其是对于部署大型集成化软件系统(包括所有企业应用和web应用)。如果把软件比喻成土木工程,那么传统的大爆炸式软件部署方式就好比把备齐的建筑材料一股脑仍上天,指望他们瞬间拼成一座大厦一样那么可笑。

相反,无论是开发新项目,还是替换已有的系统,都应该逐个部署系统组件。2个优点:首先,隐藏在代码中的技术风险是部署软件时无法回避的问题,其次这种方法迫使我们设计清晰的组件间接口。

有些是不能借鉴的,尤其是在建筑工程中屡试不爽的”瀑布式“施工方法。毕竟摩天大厦不需要可伸缩性。

应用软件只要具备了用户要求的功能便可发布,不用等到十全十美。事实上,产品越早发布公司的净收益就越高。这样既可增加商业利润又可以改变架构品质,这样实用的技巧实在不多。

39、混合开发的时代已经来临

混合编程:在一套软件系统中同时采用多种核心编程语言

现在可以采用基于文本协议(text-based protocols)了。这些新技术以特定格式的文本作为载体,便于所有人编写和理解,为混合开发提供了前所未有的可能性。

架构师把若干个强大的开发工具组合起来使用,以往的标准是挑选合适的编程语言,现在则演变成挑选合适的编程范式。

选择多了并不总是好事,但至少好过以往软件架构非此即彼的窘境。

40、性能至上

性能指标和其他指标一样重要。

有些设计师把性能放到最后考虑。

我们通常把系统响应用户输入的时间作为衡量性能的标准。

生产率通常用来描述构架系统的效率,也属于性能范畴,其重要性在于直接影响项目的成本和进度。

系统的人机交互性能直接关系到用户是否愿意掏钱。包括:响应时间,是否直观,操作步骤是否简单。

合格的说明书除了注明系统每秒钟的响应次数,还要测量典型的任务时间。

非交互性组件的性能同样影响着系统的表现。

在考虑系统的实现方法和运维策略时,架构师和设计师应该密切的关注系统的性能表现。

41、留意架构图里面的空白区域

软件系统由相互依赖的程序组成,我们把装配这些程序的方法及程序之间的关系成为架构。

假设某个箭头表示”使用HTTP协议,发送SOAP-XML格式的同步请求/响应消息“,由于架构图里面的空间有限,写不了这么多内容,所以通常用简单的注释表示。从技术角度出发可以简写成”XML over HTTP",如果从业务角度出发,有可能写成“查询库存单元”。不同的程序看似通过箭头直接联系,其实不然,矩形之间的空白区域充满 着各种软件和硬件。

应该理解每个箭头包含的静态信息和动态信息。

42、学习软件专业的行话

每个专业都有行话,同行之间讲行话方便交流,清晰、简洁、高效的方式与同行进行沟通,是软件架构师应具备的能力。架构师必须掌握基本的架构模式和设计模式,学会辨别不同模式,并借助他们和同行及开发人员进行交流。

架构和设计模式可以分为四大类:企业架构模式、应用架构模式、集成模式、设计模式。

企业架构模式定义架构的全局框架结构。

应用架构模式指出了全局架构下的子系统及局部应用的设计方法。

设计模式研究架构中各个组件的构造方法。

除以上4种外,架构师还应该了解和提防各种反模式。

反模式:影响软件开发中的常见错误:需求分析麻痹症、委员会设计、蘑菇管理、死亡征途。

43、具体情境决定一切

”分享设计架构的理念让我觉得很滑稽,因为我认为压根就不存在设计理念“

“毕竟,没有理念本身也是一种理念”

最重要的设计经验是具体情境决定一切,根据它设计尽量简单的解决方案。换句话说,架构决策只有在情境需要时,才能牺牲尽量简单的原则。

脱离了具体的应用场景,孤立地比较技术的优劣是毫无意义的事。

44、侏儒、精灵、巫师和国王

架构师好比国王,应该熟悉各种人的性格特点,招聘不同性格的人加入自己的团队。

安排任务时应该时刻考虑所有开发人员的性格特点。为不同性格的团队成员安排合适的任务,如果大家有机会磨合、相互适应,就能轻松化解决各种难题。

45、向建筑师学习

建筑师名言

建筑师社会性的表演,是上演人类历史的剧院。

要想成为伟大的建筑师,优雅丰富的心灵远比聪明才智重要。

建筑师自诩上帝的助手,甚至觊觎上帝的宝座。(上帝:客户)

天底下没有完美的建筑。

建筑师首先应该是伟大的雕塑家,或者伟大的画家,否则他不过是个建筑工人。

46、避免重复

你的开发人员在重复无需思考的工作吗?代码里面反复出现某些相似的片段?某些代码是复制粘贴后稍加修改而成的,如果出现这些情况,说明团队工作效率不高。

软件开发的真理:复制时魔鬼重复性的工作拖累开发的进度。

消灭重复的内容是你的责任,为此,应该重新研究框架,创造更完善的抽象机制,请专门制作工具的程序员(toolsmith)帮你完成切面框架(aspect framework),使用代码生成器。要想消灭重复内容必须有人采取行动。

47、欢迎来到现实世界

工程师偏爱精确,整天和0和1打交道的工程师更是如此。

现实世界不是二进制的。顾客有可能撤销确认过的订单,支票有可能跳票,信件可能丢失,付款时间可能延迟,许下承诺还可能失信。

这些已经够糟了,可分布式系统又带来了新的不一致性。服务有可能失效,状态有可能在毫无征兆的情况下改变,事务处理可能得不到保证。

分布式系统不但是松耦合、异步、并发的,而且不遵守传统的事物语义。

48、仔细观察别试图控制一切

妄想掌控一切的架构师只能设计出紧耦合的、脆弱的解决方案,这一套已经行不通了。我们必须启动必要的辅助机制。

我们已经进入分布式、松耦合系统的时代。构建松耦合的系统多少有些麻烦,我们希望系统足够灵活,别因为一点点小的改动就支离破碎。

随着系统的配置越来越灵活,当前的系统配置包含了更多的信息,为了便于理解,必须从中提取模型。比如,搞清楚哪个组件负责向逻辑信道发送消息,哪个组件负责接收消息,就应该把组件间的通信关系用图标模型记录下来。

与模型驱动架构不同,你先构建出灵活的架构,然后从实际的系统状态中提取模型。

49、架构师好比两面神

罗马神话里面,两面神是司守门户和万物始末之神。他有两张面孔,凝视2个不同的方向。

两面神兼顾前后,过去与未来。架构师在不同的对象之间架起桥梁,比如梦想和现实、过去的成功和未来的方向、业务目标与开发i限制。

我们应该以两面神为榜样,工作上严格把关,综合考虑新情况与老经验,在成熟技术上不断创新,既满足当前的业务需求,又兼顾未来的额发展规划。

50、架构师当聚焦于边界和接口

“哪些应该在一起,哪些应该分开”

51、助力开发团队

架构师可以施加约束,也有机会成为推动者。

要确保开发人员拥有他们所需的工具。

要确保开发人员拥有所需的技能,确保他们能够获得必须的培训。

同时,也要尽可能的参与到开发人员的选拔中。

只要不违背软件设计的总体目标就让开发人员自己做决策。

最后,保护开发人员,不要让他们卷入到不那么重要的工作中。

52、记录决策理由

在软件开发社区,对于文档尤其是关于软件自身设计的文档的价值,争论颇多。分歧一般集中于两处,一处是“详细的前期设计”的有效价值,另一处则是使设计文档化和不断变化的代码库保持同步的难易程度。

记录软件架构决策理由的文档,长期有用,无需为之付出过多维护精力,具有很高的投资回报价值。

根据项目的不同灵活选择合适的文档格式来记录架构决策的方方面面,格式可以是文本、维基、或博客形式的速记备忘录,也可以使用较正式的模板。

比如这些文档:

可以作为和开发人员沟通的工具,说明应遵循的重要架构原则

当开发人员对架构背后的逻辑提出质疑时,使团队能够就事论事

向经理和利益相关者说明这样构建软件的确切原因

把项目移交给下任架构师

好处是:

它逼着你说出理由时,有助于确保基础是扎实稳固的、

如果相关条件发生变化,需要对决策重新评估,它可以作为一个起点

53、挑战假设,尤其是你自己的

延迟判决法则:“臆断是事情搞砸的根源”

软件架构师的最佳实践表明,应该记录下每个决策背后的理由,当这一决策包含权衡(性能vs.可维护性,成本vs.上市时间等)时尤须如此。

有些小道消息:

开源软件不可靠

位图索引带来的麻烦比好处多....

确保这些假设清楚明确,对后继者和未来的重新评估来说非常重要。更重要的是拿出证据。

不要忽略相关这个词语。

事实和假设是构建软件的两大支柱。务必确保软件的基石坚实可靠。

54、分享知识经验

从所有的失败的经验中,我们可以学到很多东西。在像软件开发这般年轻的行业中。为了持续发展传播经验和知识至关重要。每个团队在自己的小世界小角落里所学到的东西,可能会在全球产生影响力。

55、模式病

人们记录和发现模式,避免后来人重新发明车轮。

对于软件架构师而言,设计模式是极有价值的可用工具之一。使用模式,能够创造更易沟通更易理解的通用解决方案。模式与良好设计息息相关,如果发现自己试图把最喜欢的模式硬套在不适用的问题空间上,那么你也许是”模式病“患者。

不要让一展模式功底的欲望遮掩了务实真知。

56、不要滥用架构隐喻

架构师喜欢使用隐喻(metaphor)。对那些通常比较抽象、复杂和变化的移动的目标,隐喻提供了良好的具体媒介。找到实物作为正要构建的东西的隐喻,会更容易沟通和讨论架构全局。

滥用架构隐喻经常会出现问题,让架构师不知所措,比如:

业务领域的客户开始越来越喜欢系统隐喻,这时,系统还在构想中,在这种情况下所有各方共享的是最乐观的可能解读,但其中并没有包括任何必要的约束。

开发团队认为隐喻比实际业务更重要。由于团队耽溺于隐喻,你不得不开始修正那些古怪的决策。

所交付的系统包含了许多遗留名称,从早已老旧过时,有待重新鉴定隐喻,到多次重构和重复挖掘的概念。

57、关注应用程序的支持和维护

应用程序的支持和维护永远不是事后才考虑的事情。由于应用程序80%的声明周期都在维护上。

开发人员和支持人员拥有的技能不同,就像开发/测试环境和生产环境有截然不同的目的一样。

系统管理员会遇到的问题:

*系统管理员不能重新提交请求消息来重现问题。

*一旦投入使用,就没有调试器可以用了。

....

就会出现以下症状:

*大多数问题都需要开发人员参与。

*支持团队讨厌新的应用程序。

....

为了确保应用程序脱离开发人员之手后能成功运行,应该做到:

*理解开发人员和支持人员确实具有不同的技能

*在项目中尽早的引入支持负责人。

...

当系统管理员很开心的时候大家都会很开的。

58、有舍才有得

有时,接受某种约束或放弃某个特性,可带来更好的架构,这种架构在构建和运维上都会更加简单,而且成本更低,往往能产生富有创造性和创新性的结果。

59、先考虑原则、公理和类比,再考虑个人意见和口味

如果是单凭个人经验、意见和口味创建的架构是没有考虑原则、公理、和类比所带来的好处的。

这样做,架构文档化会更容易,从描述架构所遵循的原则开始即可。

原则清晰的架构能够把架构师解放出来,使其免于全面复审而忙得不可开交。

原则和公理确保了架构的一致性。

60、从“可行走骨架”开始开发应用

为了实现、验证和不断发展应用架构,一个非常有用的策略,便是从Alistair Cockburn所谓的“可行走骨架”开始。“可行走骨架”是对系统的最简单实现(a minimal implementation),它贯穿头尾,将所有的主要构件组件连接起来。从可工作的最小系统开始训练全部的通信路径,可以带来“正朝着正确的方向前进”的信心。

对于大型系统通常是由多名开发人员共同构成,对于大型系统而言更多的协调工作是必不可少的。

从“可行走骨架“开始,保持系统一直运行可用,递增式地进行培育,使其逐步增长。

61、数据是核心

软件开发人员最初将软件理解为命令、函数和算法构成的系统。

如果稍稍后退站远一点,计算机只不过是能访问与操作一堆数据的时髦工具。

代码在计算机中运行时,底层数据的状态不断发生变化。

举例而言,如果想了解Unix操作系统,通过源码逐行挖掘是不大可能奏效的,但是如果你读过一本unix内部数据结构的书,便可更好的了解unix底层是如何运行的。从概念上开看,数据比代码更加精炼,也更好理解。

即使对于最复杂的系统,通过这种面向数据的视角,即通过底层信息的结构整体开看待系统,也可以将之缩减为细节的有形集合。然后降低其复杂性。

数据在大多数问题中处于核心地位,业务领域问题由数据蔓延到代码中。

诚然,软件架构中的许多问题确实和数据相关。

从设计角度来看,大多数系统的关键问题,就是要在正确的时间从系统获取正确的数据。

62、确保简单的问题有简单的解

软件架构师解决了许多非常困难的问题,但也会解决一些相对容易的问题,对于简单的问题,不要使用复杂的解决方案。

为复杂问题付出的直接成本可能看上去很小,但是其潜在成本要大得多。为问题的解决方案付出的代价不仅仅只是在实现和维护上。

架构师会从主观的判断或潜在不确定性需求出发,产生调整解决方案的强烈冲动。但记住一点:试图猜测未来的需求时,错的概率是50%,错的非常离谱的概率是49%。

63、架构师首先是开发人员

不管解决方案多么优秀,决定实现能否成功的最重要因素之一是让开发人员愿意接下任务。

虽然不是工作的一部分,架构师还是会经常去处理一些比较复杂的任务。这样做的目的有两个:第一,这是一种乐趣,而且还能帮我们做到宝刀不老;第二,这有助于向开发人员表明,我不是碰到麻烦时会干吹烟卷却束手无策的架构师。

64、根据投资回报率进行决策(ROI)

我们对项目所做的每一个决策——无论是技术、过程,还是与人相关——都可以看做一种投资形式。

回报率(rate of return),也成投资回报率(Return On Investment,ROI),是衡量投资是否成功的标准之一。

将架构决策视为投资,并将相关的回报率也一并考虑在内。在判断每个决策选项是否务实或恰当时,这种方法很有用。

65、一切软件系统都是遗留系统

即使系统十分前沿,采用了最新的技术开发而成,但对接手它的下一个人而言,它也会是遗留系统(legacy)。必须面对这种情况!在今天,软件很快便会过时,这已经成为软件的天然属性。如果软件能够存活下来哪怕只有数月的时间,都必须承认一点:负责维护工作的开发人员肯定要对软件进行缺陷修复,这是不可避免的。这引出如下几个问题。

*清晰性:各个组件和类的角色一定要清楚

*可测行:系统易于验证吗?

*正确性:结果和设计或期望的一致吗?

*跟踪性:可容易修复紧急缺陷

66、起码要有2个可选的解决方案

对于某一个问题,如果只考虑了一个解决方案,那你就有麻烦了,在给定的约束条件下,寻找问题的最佳方案,期望第一个解决方案即满足全部的需求和约束,几乎是不可能的。

这种问题即使有——也绝少是真地由于缺乏可选方案而造成的,它更可能是架构师缺乏特定问题域的经验所致。

67、理解变化的影响

好的架构师能够将复杂性降到最低程度,他在解决方案中给出的抽象,应该能够为更高的层次提供坚实基础,同时,还应该能足够务实地应付未来的变化。

优秀的架构师能够深刻理解变化带来的影响,这种影响不仅限于彼此隔离的软件模块之间,而且包括人与人之间,以及系统与系统之间。

变化有多种不同的表现形式:

*功能需求的变化

*可扩展性需求的变化

*系统接口被修改

。。。。

幸运的是许多工具和技术可以用以减轻变化的影响:

*进行小规模的增量渐变

*构建可重复运行的测试用例

*跟踪好依赖关系

*降低复杂性

*自动化重复任务

68、你不能不了解硬件

对于许多软件架构师,硬件容量规划问题是一个超出其舒适区的主题,但它的确是架构师工作的重要组成部分。

如果没有硬件方面的专业知识,预估待开发系统的硬件配置是非常容易出错的。比起采购超出实际所需的硬件,为容量规划留出预算是更为经济的。

69、现在走捷径,将来付利息

长远看来,系统维护将比项目初期的开发消耗更多的资源。

测试:使用测试先行的设计,进行单元测试

碰到架构问题或设计缺陷,作为架构师,一定要坚持成本还很低廉时就动手。搁置越久,为之付出的利息也就越高。

70、不要追求完美,足够好就行

软件设计师,尤其是架构师,在评估针对某个问题的解决方案时,会倾向于考虑它是否优雅完美。有些瑕疵只须经由一些调整或迭代重构便可消除。

建议:不要屈服于企图使设计或实现达到完美的诱惑!把目的设定在“足够好”就行,当已经达成目标时就停下来。

71、小心“好主意”

“好主意”会杀死项目。有时候杀伤力会很快见效,但更常见的症状是:因屡屡错过的里程碑和不断攀升的缺陷数量,项目苟延残喘,最终不治身亡。

“好主意”:那种诱人2的、不用想都知道的、外表无辜、以为不可能会产生伤害的那种“好”注意。

“好主意”真正的邪恶之处在于它的“好”。

如果出现下列关键词,要小心了:

*如果....会更酷。

*“嘿,他们刚刚发布了YYY框架的XXX版本。我们应该马上升级”。

72、内容为王

我见过无数这样的设计,它们大都永无止境地强调需求、设计、开发、安全及可维护性,但从未关注系统的真正要点——数据。

73、对商业方,架构师要避免愤世嫉俗

雇主希望偶们能够解决问题,倾听和了解雇主的业务,是我们必须掌握的最为关键的技能。

成为优秀架构师的秘诀之中有一个关键要素,那便是:要对工作满怀激情,但不要是那种带着愤怒和火气的“激情”。

74、拉伸关键维度,发现设计中的不足

应用程序的设计轮廓,最初是基于特定的业务需求、所选择的技术或现有的技术、性能要求、预期的数据量,以及构建、部署和运营上可用的财务资源。无论采用什么样的解决方案,都要求能满足或超越当前环境下的要求,成功运行起来,否则这就不称其为解决方案了。

拉伸解决方案的关键维度,看看哪些方面会遭到破坏。

75、架构师要以自己的编程能力为依托

在架构上,架构师都期望能够创建出精巧的设计和完美的抽象,来优雅的解决手头的问题。如果能再项目中多安插些技术那就更让人有成就感了。但是最终要实现设计的不是架构师。如果开发人员需要去实现那些架构上的“杂技”,那将会对整个项目造成巨大影响

为项目设计架构时,对实现的每个设计元素所需要的工作量,要做到心中有数;如果以前曾开发过其中某种设计元素,那么估算所需的工作量将会容易得多。

不要在设计里面使用自己没有亲自实现过的模式,不要使用自己没有用之写过代码的框架,不要使用自己没有亲自配置过的服务器。

76、命名要恰如其分

如果都不知道一个东西应该叫什么,那你肯定不知道它究竟是什么。如果你不知道它究竟是什么,那么你肯定不能坐下来为它编写代码。

77、稳定的问题才能产生高质量的解决方案

最好的架构师不是要去解决难题,而是围绕难题开展工作。架构师要能够将四处弥漫的软件问题圈起来,并画出其中的各种边界,确保对问题由稳定的、完整的认识。

这些问题应该具有以下特性:

*内聚性:问题块在概念上市统一的,其中所有的任务、数据和特征都是相关的。

*能够很好地和其他块分隔开:这些块在概念上进行了规范化处理;他们很少重叠或者根本不重叠。

78、天道酬勤

架构师常常被描述为一种注重创造力与问题解决能力的职业。除了创造力外架构师还应当具备另一项同样重要的特质——勤奋,勤奋指很多方面,但归根结底是指具备坚强的毅力,并且对系统的每项任务和每个架构的目标都能投入足够精力。

勤奋经常和平凡携手同行。

勤奋还意味着要求架构师必须真正做好那些看似简单的任务,坚守承诺。

79、对决策负责

由于软件架构师比组织中的其他人更有影响力,所以他们必须对自己做出的决策负责。

研究表明有超过三分之二的软件项目要么彻底失败了,要么未能成功交付(项目延期、预算超支、客户不满意)。究其根本原因,有许多事由于架构师做出了不当的决策,或者无法最终执行正确的架构决策。

如何做出有效的架构决策:

首先,对决策的过程有充分认识

第二,定期对架构决策进行复审

第三,要贯彻架构决策

最后,可以将一些决策委托给响应问题域的专家,不必出自其自身。

80、弃聪明,求质朴

智力超群、足智多谋对任何人来说都是值得称道的品质。对架构师来说,这些素质尤其被看重。

然而,聪明也包含某些额外的隐含意义。它也暗指能快速想出脱身之计的能力,但这种本领最终要依靠一些小伎俩,骗局或掉包计。

聪明的设计僵硬难改,其细节会对全局产生太多牵扯,往往会一触即毁。

81、精心选择有效的技术,绝不轻易放弃

作为软件设计开发的老手,每个架构师通常都有一些让自己屡屡取胜的武器用来武装自己。这些技术由于种种原因深受架构师青睐,排在其首选解决方案的前几位。这并不是说某种技术一旦入围就可以永久罔替。

选择新技术虽然有风险,但其价值能为你带来质的飞跃。

考虑技术未来的前景。

软件架构师工作很大一部分,是要选择用以攻克难题的合适技术。

82、客户的客户才是你的客户

在软件会议需求会议上,要这样想象:你的客户并不是你的客户。实际上真的不难做到,因为,事实便是如此。如果你的客户的客户赢了,那么你也赢了。

如果你在编写一个电子商务应用程序,那么应该考虑的应当是那些会在那个网站上购物的人的需求。如果你的客户有意无意的忽视他们的客户所看重的重要事项——那么请放弃这个项目。

需求收集会议不是项目实现讨论会议。

83、事物发展总会出人意料

事物的发展总会出人意料。在设计时,人们很容易陷入误区,投入太多的时间在设计上。

设计中的这些微小变化累积起来,很快就需要对设计进行一次较大的变更。

设计是一个不断发现的过程。

84、选择彼此间可协调工作的框架

软件框架是系统的基础,在选择时,不仅要考虑每个框架自身的质量和特性,也要关注共同构成系统的各个框架之间是否能和谐相处。

如果框架间有重叠,则要确保了解候选框架在逻辑域或关注层面上的重叠程度。

为了尽量减少框架间互有重叠的概率,要根据系统需求的应用场合,选择精专强大的框架。

系统应该由多个相互独立的框架构成,其中每个框架都精专于各自的领域,但同时又非常简洁、包容、灵活。

85、着重强调项目的商业价值

对于非软件业从业人员,软件架构显得虚无缥缈,架构项目在争取资金时会遭遇到困难。

大众心理学表明,大部分人会相信亲眼所见的东西。而拥有预算决定权的人,几乎都是受商业价值驱使的。

下列五部,会成功将架构提案打造成经典的商业项目:

1、形成价值描述(提高 生产力,改进效率的能力)

2、建立量化度量标准

3、回过头来关联传统商业的衡量方式

4、知道该在哪里停止

5、寻找恰当的时机

86、不仅仅只控制代码,也要控制数据

源代码的控制和持续集成,是管理应用程序构建过程和部署过程的优秀工具。数据方案和数据内容常常会根据源代码的变化而变化,他们也是这一过程的重要组成部分,因此也要对之进行类似的控制。

数据库的变化不应该影响构建活动的连续性。要把数据库也作为一个构建单元包含在内,做到一次性构建整个应用程序。数据迁移不应该作为一种补救措施。

87、偿还技术债务

对任何已投入实用的项目(也就是说,有客户在实用产品),无论是要修复缺陷,还是要添加新功能,总有必须修改产品的时候。在那点上,会面临2个可能选择:花合适时间“一次做对”,或者取巧走“捷径”,争取尽快推出修改过的产品。

作为架构师必须决定怎么做才有意义。

88、不要急于求解

架构师多半是开发人员出身。开发人员的主要职责是解决编程问题。相比架构问题编程问题的范围更狭窄。很多编程问题是很小但比较棘手的算法问题。

架构师和开发人员因此学会了迅速进入问题解决模式,但有时候没有解决方案才是最好的而解决方案。许多问题根本不需要解决,看似问题是因为我们只关注表面症状。

学会审视问题本身。

看看能否改变问题。

我们必须戒除“问题解决迷恋症”。

89、打造上手的系统

我们十工具的制造者。我们制造的系统,一定要能够帮助人们——通常是其他人——做事,否则就是去了存在的意义,我们也将无法从中获取报酬。

用户体验应该是“上手的"。

90、找到并留住富有激情的问题解决者

组建一支汇聚优秀开发人员的团队,是确保软件项目成功非常重要的事情之一。一旦建成便竭力维护。

提放批评过度。批评过度,可能会扼杀开发人员的创造力,降低其生产力。更糟糕的是,可能会在团队内部造成纷争。

以正确的方式经营开发团队,其重要性不言而喻。

91、软件并非真实存在

软件工程经常被拿去和完善的传统学科——如土木工程——进行对比。这些类比有个问题,和这些传统实践制造的有形产品不同,软件并非真实的存在。

业务和软件都是活生生、会变化的实体。业务需求可能会因新近收购的业务伙伴和营销战略而迅速变化。

业务需求很可能会发生变化,因为我们构建的产品是柔韧的。

记住,需求文档不是蓝图,软件并非真实存在。我们创造的虚拟物比实体物更易于改变。

92、学习新语言

架构师必须让别人能理解你。

业务人员专业用语和程序员专业用语是有差别的。架构师需要掌握各个沟通团队的语言。

93、没有永不过时的解决方案

今天的解决方案会成为明天的问题。

今天做出的选择,在未来很大程度上是错误的。

“分析瘫痪”是今天架构师们碰到的问题之一,此问题最大的归因,是试图去猜测对未来而言最好的技术。

94、用户接受度问题

人们并不总是满心欢喜地接受新系统或系统的重大升级。这种情势,可能会对项目的成功构成威胁。

架构师的目标,是要去了解和衡量用户的接受度问题带来的威胁,并朝着能减轻这些威胁的方向开展工作。

“项目拥戴者”可以帮助消除用户接受度问题。这个职位的最佳人选,是能代表用户或利益相关方的人。

95、清汤的重要提示

清汤是一种非常清澈的肉汤,上品清汤非常清澈,需要不断重复的精炼浓缩才能烹出。

设计架构也应当不断精炼浓缩。

软件中许多漏洞的需求和缺陷,可最终追溯到含糊笼统的语言描述上。拒绝模凌两可的废话。概念需要在加上“总是、永远、不管任何情况下”仍然成立。

96、对最终用户而言,界面就是系统

糟糕的用户界面埋没了太多的好产品,最终用户通过界面访问系统。

架构师应该确保,随着架构变化而变的用户界面也要能够反应用户的期望。

不仅要让最常用的交互易用,而且要使最终用户能够从中获得回报。

97、优秀的软件不是构建出来的,而是培育出来的

在一开始就要设计庞大的系统几乎毫无好处可言。

设计尽可能小的系统,帮助成功交付,并推动它向宏伟的远景目标不断演化。

粗略的从后面的十几个里面提取出以下几个方面:

数据:程序的核心就是数据,数据的结构表现出数据的形态,数据可能会随着架构更新,数据库也要更新。

文档:文档是为了避免反复思考而存在的,类似于代码注释,文档是对工程的注释。尤其是对架构师的每一个有可能产生分歧的决策,必须用文档标明决策原因。

推迟决定(也有其他翻译):是软件开发模型的策略,这样有助于收集更多有关决定的信息。

团队:团队需要的不是单一的一类人,但需要的一起能合理分配工作并完成工作的一类人。找到好的团队成员一定尽心维护。

用户:客户至上,客户体验是架构师最好的简历,标志着软件的成功与否。

沟通:和业务部门沟通,需要学习业务部门的语言,其中涉及到一些专业词汇,避免沟通障碍。和客户沟通明白需求,和程序员沟通....

选择技术:如何选择使用的技术,要考虑的事项(如:架构师本人的技术,对于软件的性能,不同架构一起融合等)

性能:相对于功能而言,性能经常被忽略。

——————

作者:H100
来源:CSDN
原文:https://blog.csdn.net/u014449046/article/details/49390571
版权声明:本文为博主原创文章,转载请附上博文链接!

/* * @Author: your name * @Date: 2016-09-06 00:00:00 * @LastEditTime: 2020-03-17 18:29:35 * @LastEditors: Please set LastEditors * @Description: In User Settings Edit * @FilePath: \htdocs\usr\themes\default\footer.php */