Stories

Detail Return Return

行為型模式-協作與交互機制 - Stories Detail

行為型模式聚焦於對象間的行為交互,通過規範對象協作方式提升系統的靈活性與可擴展性。在分佈式系統中,由於多節點異步通信、網絡不可靠性及狀態一致性挑戰,行為型模式需針對分佈式特性進行適應性設計。本文從觀察者、策略、命令、責任鏈、狀態五大核心行為型模式出發,系統解析其在分佈式場景下的演化與實踐。

一、觀察者模式:分佈式事件的發佈 - 訂閲機制

1.1 模式核心與分佈式適配

觀察者模式通過發佈 - 訂閲機制實現對象間的鬆耦合通信,在分佈式系統中演變為跨節點的事件驅動架構(EDA),解決節點間異步通知問題。

1. 基於消息隊列的分佈式觀察者

// 事件定義(跨節點傳輸) 
public class OrderEvent implements Serializable { 
   private String eventId; 
   private Long orderId; 
   private OrderStatus status; 
   private LocalDateTime timestamp; 
   // getters/setters 
} 

// 抽象主題(事件發佈者) 
public interface EventPublisher { 
   void publish(OrderEvent event); 
} 

// 具體發佈者(訂單服務) 
public class OrderService implements EventPublisher { 
   @Autowired 
   private KafkaTemplate<String, OrderEvent> kafkaTemplate; 

   @Override 
   public void publish(OrderEvent event) { 
       // 發佈事件到Kafka主題 
       kafkaTemplate.send("order-events", event.getOrderId().toString(), event); 
   } 
   // 訂單狀態變更時發佈事件 
   public void updateStatus(Long orderId, OrderStatus status) { 
       OrderEvent event = new OrderEvent(UUID.randomUUID().toString(), orderId, status, LocalDateTime.now()); 
       publish(event); 
   } 
} 
// 抽象觀察者(事件訂閲者) 
public interface EventSubscriber { 
   void onEvent(OrderEvent event); 
} 

// 具體觀察者(庫存服務) 
public class InventorySubscriber implements EventSubscriber { 
   @KafkaListener(topics = "order-events") 
   @Override 
   public void onEvent(OrderEvent event) { 
       if (event.getStatus() == OrderStatus.PAID) { 
           // 訂單支付後扣減庫存 
           inventoryService.deduct(event.getOrderId()); 
       } 
   } 
} 

// 具體觀察者(積分服務) 
public class PointSubscriber implements EventSubscriber { 
   @KafkaListener(topics = "order-events") 
   @Override 
   public void onEvent(OrderEvent event) { 
       if (event.getStatus() == OrderStatus.PAID) { 
           // 訂單支付後增加積分 
           pointService.add(event.getOrderId()); 
       } 
   } 
} 

2. 分佈式場景關鍵特性

  • 異步解耦:發佈者無需知道訂閲者存在(如訂單服務不依賴庫存 / 積分服務),降低節點耦合。
  • 可靠性保障:通過消息隊列的持久化(如 Kafka 的日誌存儲)確保事件不丟失,應對節點宕機。
  • 廣播能力:同一事件可被多個訂閲者消費(如訂單支付事件同時通知庫存、積分、物流服務)。

1.2 與傳統觀察者的核心區別

維度 單體觀察者模式 分佈式觀察者模式(事件驅動)
通信方式 內存直接調用 消息隊列異步通信(跨節點)
可靠性 依賴本地調用,失敗直接拋出異常 消息重試機制,支持死信隊列處理失敗事件
訂閲管理 代碼中直接註冊觀察者 通過消息隊列主題動態訂閲(無需代碼變更)

二、策略模式:分佈式場景的動態算法切換

2.1 模式核心與負載均衡策略

策略模式通過封裝不同算法實現動態切換,在分佈式系統中廣泛應用於負載均衡、路由策略、序列化方式選擇等場景。

1. 分佈式負載均衡的策略設計

// 策略接口(負載均衡算法) 
public interface LoadBalanceStrategy { 
   String selectInstance(List<String> instances); 
} 

// 具體策略1:輪詢 
public class RoundRobinStrategy implements LoadBalanceStrategy { 

   private AtomicInteger index = new AtomicInteger(0); 
   
   @Override 
   public String selectInstance(List<String> instances) { 

       if (instances.isEmpty()) return null; 
       int i = index.getAndIncrement() % instances.size(); 
       return instances.get(i); 
   } 
} 
// 具體策略2:權重 
public class WeightedStrategy implements LoadBalanceStrategy { 

   @Override 
   public String selectInstance(List<String> instances) { 
       // 假設實例格式為"ip:port:weight",解析權重並選擇 
       int totalWeight = instances.stream() 
           .mapToInt(inst -> Integer.parseInt(inst.split(":")[2])) 
           .sum(); 
       int random = new Random().nextInt(totalWeight); 
       int current = 0; 
       for (String inst : instances) { 
           int weight = Integer.parseInt(inst.split(":")[2]); 
           current += weight; 
           if (current > random) { 
               return inst; 
           } 
       } 
       return instances.get(0); 
   } 
} 

// 策略上下文(負載均衡器) 
public class LoadBalancer { 
   private LoadBalanceStrategy strategy; 
   // 動態設置策略(如從配置中心獲取) 
   public void setStrategy(LoadBalanceStrategy strategy) { 
       this.strategy = strategy; 
   } 
   public String chooseInstance(String serviceName) { 
       // 從註冊中心獲取服務實例列表 
       List<String> instances = serviceDiscovery.getInstances(serviceName); 
       return strategy.selectInstance(instances); 
   } 
} 

// 使用示例 
public class ServiceConsumer { 

   public void invokeService() { 
       LoadBalancer balancer = new LoadBalancer(); 
       // 從配置動態選擇策略(如"weighted") 
       String strategyType = Config.get("loadbalance.strategy"); 

       balancer.setStrategy(StrategyFactory.create(strategyType)); 

       String instance = balancer.chooseInstance("order-service"); 
       // 調用選中的實例 
   } 
} 

2. 分佈式場景價值

  • 動態適配:根據集羣狀態切換策略(如低負載時用輪詢,高負載時用權重策略)。
  • 灰度發佈:通過策略路由部分流量到新版本實例(如GrayStrategy只將 10% 流量路由到新實例)。

三、命令模式:分佈式任務的異步執行與重試

3.1 模式核心與分佈式命令

命令模式通過封裝請求為對象,實現請求的異步執行、日誌記錄與重試,在分佈式系統中演變為跨節點任務調度機制。

1. 分佈式任務的命令設計

// 命令接口 

public interface DistributedCommand extends Serializable { 

   String getCommandId(); // 唯一命令ID(用於冪等性) 
   CommandResult execute(); // 執行命令 
   CommandResult compensate(); // 補償命令(失敗時執行) 
} 
// 具體命令:訂單支付命令 
public class PaymentCommand implements DistributedCommand { 

   private String commandId; 
   private Long orderId; 
   private BigDecimal amount; 
   @Override 
   public CommandResult execute() { 
       try { 
           // 調用支付服務 
           paymentService.pay(orderId, amount); 
           return CommandResult.success(); 
       } catch (Exception e) { 
           return CommandResult.failure(e.getMessage()); 
       } 
   } 

   @Override 
   public CommandResult compensate() { 

       // 支付失敗時執行退款 
       return paymentService.refund(orderId) ? CommandResult.success() : CommandResult.failure("退款失敗"); 
   } 
   // getters/setters 
} 

// 命令調用者(任務調度器) 
public class CommandScheduler { 

   @Autowired 
   private KafkaTemplate<String, DistributedCommand> kafkaTemplate; 

   @Autowired 
   private CommandRepository repository; // 命令日誌存儲 

   // 發送命令到執行節點 
   public void schedule(DistributedCommand command) { 
       // 保存命令日誌(用於重試和恢復) 
       repository.save(new CommandLog(command.getCommandId(), command, CommandStatus.PENDING)); 
       // 發送到命令主題 
       kafkaTemplate.send("command-topic", command.getCommandId(), command); 
   } 
   // 處理命令結果 
   public void handleResult(String commandId, CommandResult result) { 

       CommandLog log = repository.findById(commandId); 

       if (result.isSuccess()) { 
           log.setStatus(CommandStatus.SUCCESS); 
       } else { 
           log.setStatus(CommandStatus.FAILED); 
           // 失敗重試(最多3次) 
           if (log.getRetryCount() < 3) { 
               log.incrementRetryCount(); 
               kafkaTemplate.send("command-topic", commandId, log.getCommand()); 
           } 
       } 
       repository.update(log); 
   } 
} 

// 命令執行者(工作節點) 
public class CommandWorker { 

   @KafkaListener(topics = "command-topic") 
   public void executeCommand(ConsumerRecord<String, DistributedCommand> record) { 

       DistributedCommand command = record.value(); 
       CommandResult result = command.execute(); 

       // 發送執行結果 
       commandScheduler.handleResult(command.getCommandId(), result); 
   } 
} 

2. 分佈式場景優勢

  • 異步執行:命令通過消息隊列異步發送到工作節點,調用者無需等待執行完成。
  • 故障恢復:命令日誌記錄執行狀態,節點宕機後可從日誌恢復未完成命令。
  • 冪等設計:通過commandId確保重複執行(如消息重試)不會產生副作用。

四、責任鏈模式:分佈式請求的鏈式處理

4.1 模式核心與 API 網關過濾鏈

責任鏈模式通過多個處理器依次處理請求,在分佈式系統中廣泛應用於 API 網關的請求過濾、分佈式事務的階段處理等場景。

1. API 網關的責任鏈設計

// 處理器接口 
public interface GatewayFilter { 

   void doFilter(GatewayRequest request, GatewayResponse response, GatewayFilterChain chain); 
} 

// 具體過濾器1:認證過濾 
public class AuthFilter implements GatewayFilter { 

   @Override 
   public void doFilter(GatewayRequest request, GatewayResponse response, GatewayFilterChain chain) { 

       String token = request.getHeader("Authorization"); 

       if (token == null || !authService.validate(token)) { 
           response.setStatusCode(401); 
           response.setBody("Unauthorized"); 
           return; // 終止鏈 
       } 
       chain.doFilter(request, response); // 繼續下一個過濾器 
   } 
} 

// 具體過濾器2:限流過濾 
public class RateLimitFilter implements GatewayFilter { 

   private RedisTemplate<String, Integer> redisTemplate; 
   
   @Override 
   public void doFilter(GatewayRequest request, GatewayResponse response, GatewayFilterChain chain) { 

       String clientIp = request.getClientIp(); 
       String key = "rate_limit:" + clientIp; 
       // 使用Redis實現限流 
       Long count = redisTemplate.opsForValue().increment(key, 1); 

       if (count == 1) { 
           redisTemplate.expire(key, 1, TimeUnit.MINUTES); 
       } 
       if (count > 100) { // 每分鐘最多100次請求 
           response.setStatusCode(429); 
           response.setBody("Too Many Requests"); 
           return; 
       } 
       chain.doFilter(request, response); 
   } 
} 

// 過濾器鏈 
public class DefaultGatewayFilterChain implements GatewayFilterChain { 

   private List<GatewayFilter> filters; 
   private int index = 0; 

   public DefaultGatewayFilterChain(List<GatewayFilter> filters) { 
       this.filters = filters; 
   } 

   @Override 
   public void doFilter(GatewayRequest request, GatewayResponse response) { 

       if (index < filters.size()) { 
           GatewayFilter filter = filters.get(index++); 
           filter.doFilter(request, response, this); 
       } 
   } 
} 

// API網關 
public class ApiGateway { 

   public GatewayResponse handleRequest(GatewayRequest request) { 

       GatewayResponse response = new GatewayResponse(); 
       // 構建過濾器鏈(認證→限流→路由) 
       List<GatewayFilter> filters = Arrays.asList( 
           new AuthFilter(), 
           new RateLimitFilter(), 
           new RouteFilter() 
       ); 

       new DefaultGatewayFilterChain(filters).doFilter(request, response); 

       return response; 
   } 
} 

2. 分佈式場景價值

  • 橫切邏輯複用:認證、限流等邏輯集中在網關,無需每個服務重複實現。
  • 動態擴展:通過配置中心動態增刪過濾器(如臨時添加維護模式過濾器)。

五、狀態模式:分佈式系統的狀態機管理

5.1 模式核心與訂單狀態流轉

狀態模式通過封裝對象狀態及其轉換邏輯,在分佈式系統中用於管理跨節點的狀態一致性(如訂單狀態、工作流狀態)。

1. 分佈式訂單狀態機

// 狀態接口 
public interface OrderState { 

   void pay(OrderContext context); // 支付操作 
   void ship(OrderContext context); // 發貨操作 
   void complete(OrderContext context); // 完成操作 
   OrderStatus getStatus(); // 獲取當前狀態 

} 

// 具體狀態:待支付 
public class PendingState implements OrderState { 

   @Override 
   public void pay(OrderContext context) { 

       // 狀態轉換:待支付→已支付 
       context.setState(new PaidState()); 

       // 發佈狀態變更事件(通知其他節點) 
       context.publishEvent(OrderStatus.PAID); 

   } 

   @Override 
   public void ship(OrderContext context) { 

       throw new IllegalStateException("未支付訂單不能發貨"); 
   } 

   @Override 
   public void complete(OrderContext context) { 

       throw new IllegalStateException("未支付訂單不能完成"); 
   } 

   @Override 
   public OrderStatus getStatus() { return OrderStatus.PENDING; } 

} 
// 具體狀態:已支付(其他狀態略) 
public class PaidState implements OrderState { /* 實現發貨等操作 */ } 
// 上下文:訂單狀態管理器 

public class OrderContext { 

   private OrderState currentState; 
   private Long orderId; 
   private EventPublisher eventPublisher; 
   public OrderContext(Long orderId) { 
       this.orderId = orderId; 

       this.currentState = new PendingState(); // 初始狀態 

   } 

   // 委託狀態操作 
   public void pay() { currentState.pay(this); } 

   public void ship() { currentState.ship(this); } 

   public void complete() { currentState.complete(this); } 

   // 發佈狀態事件(跨節點同步) 
   public void publishEvent(OrderStatus status) { 

       eventPublisher.publish(new OrderEvent(orderId, status)); 

   } 

   // 設置狀態(僅允許狀態內部調用) 
   void setState(OrderState state) { this.currentState = state; } 

} 

// 使用示例 
public class OrderController { 

   @PostMapping("/orders/{id}/pay") 
   public void payOrder(@PathVariable Long id) { 
       OrderContext context = orderContextManager.get(id); 
       context.pay(); // 狀態轉換由狀態機管理 
   } 
} 

2. 分佈式場景挑戰與解決

  • 狀態一致性:通過事件發佈同步狀態變更(如訂單服務狀態變更後,通知庫存服務)。
  • 併發安全:狀態轉換加分佈式鎖(如 Redis 鎖),防止併發操作導致狀態錯亂。

六、面試高頻問題深度解析

6.1 基礎概念類問題

Q:分佈式環境下的觀察者模式與傳統觀察者模式有何本質區別?如何保證事件不丟失?

A:

  • 本質區別

    傳統觀察者是進程內同步調用(如內存中的發佈 - 訂閲),分佈式觀察者基於消息隊列異步通信,跨節點、跨進程。

  • 不丟失保證
  1. 消息持久化:Kafka 將消息寫入磁盤,確保 broker 宕機後數據不丟失。
  2. 確認機制:消費者處理完成後發送 ACK(如 Kafka 的 offset 提交),未確認則重試。
  3. 死信隊列:多次重試失敗的事件進入死信隊列,人工干預處理。

Q:策略模式在分佈式負載均衡中的應用?如何動態切換負載均衡策略?

A:

  • 應用場景

    策略模式封裝輪詢、權重、IP 哈希等負載均衡算法,LoadBalancer通過setStrategy()動態選擇算法。

  • 動態切換
  1. 配置中心(如 Nacos)存儲策略類型(如weighted)。
  2. 客户端監聽配置變更,調用setStrategy()更新策略。
  3. 示例:流量高峯時切換到權重策略(優先調度到高配節點),低谷時用輪詢策略。

6.2 實戰設計類問題

Q:如何用責任鏈模式設計一個支持多租户的 API 網關權限系統?

A:

  1. 過濾器鏈設計
  • TenantFilter:解析請求中的租户 ID,驗證租户合法性。
  • AuthFilter:驗證租户內用户的令牌有效性。
  • PermissionFilter:檢查用户是否有權限訪問當前接口(結合租户 + 用户角色)。
  • RateLimitFilter:按租户限制 API 調用頻率。
  1. 執行邏輯

    網關接收請求後,依次執行過濾器鏈,任何過濾器失敗則返回對應錯誤(如 401/403)。

  2. 租户隔離

    每個過濾器通過request.getTenantId()獲取租户上下文,確保權限校驗在租户維度隔離。

Q:分佈式狀態機如何保證跨節點的狀態一致性?

A:

  1. 狀態事件同步:狀態變更時發佈全局事件(如OrderStatusChangedEvent),所有節點消費事件更新本地狀態。
  2. 分佈式鎖:狀態轉換操作加鎖(如 Redis 鎖),防止併發修改導致狀態衝突。
  3. 狀態校驗:節點啓動時從事件日誌重建狀態(如從 Kafka 消費歷史事件恢復最新狀態)。

總結:行為型模式的分佈式設計原則

核心選型策略

分佈式場景 推薦模式 核心解決問題
跨節點異步通知 觀察者模式(消息隊列) 節點解耦,事件驅動協作
動態算法切換(負載均衡等) 策略模式 算法與使用分離,支持動態適配
分佈式任務調度與重試 命令模式 任務異步執行,支持補償與冪等性
網關過濾、請求處理鏈 責任鏈模式 橫切邏輯複用,動態擴展過濾器
跨節點狀態流轉(訂單等) 狀態模式 狀態轉換邏輯封裝,保證狀態一致性

分佈式適配要點

  1. 異步優先:儘量採用異步通信(消息隊列)減少節點阻塞,應對網絡延遲。
  2. 冪等設計:所有跨節點操作需保證冪等性(如命令 ID、事件 ID),防止重試導致副作用。
  3. 容錯機制:結合重試、超時控制、熔斷降級,應對網絡分區與節點故障。

通過掌握行為型模式在分佈式系統中的適配邏輯,不僅能在面試中清晰解析跨節點協作問題,更能在實際架構中設計鬆耦合、高可用的分佈式系統,體現高級程序員對複雜系統的設計能力。

Add a new Comments

Some HTML is okay.