设计模式与分布式系统:服务注册中的观察者、负载均衡中的策略、熔断器中的状态
分布式系统,说白了就是一堆服务凑在一起干活。但服务多了,问题就来了——怎么找到对方?怎么分配任务?怎么防止雪崩?
我这些年做分布式系统,发现一个有意思的现象:很多看似复杂的问题,用经典设计模式去套,反而豁然开朗。今天咱们就聊聊三个最典型的场景:服务注册、负载均衡、熔断器。它们分别对应观察者模式、策略模式、状态模式。
一、服务注册与发现:观察者模式
先说说服务注册。你想想看,一个微服务系统里,可能有几十上百个服务实例。它们随时可能启动、停止、扩容、缩容。如果让服务之间硬编码地址,那维护成本就太高了。
观察者模式在这里扮演的角色,其实就是「发布-订阅」的机制。服务注册中心是主题(Subject),各个服务实例是观察者(Observer)。
我记得有一次做电商系统,订单服务需要调用库存服务。一开始我们用的是轮询方式,每隔几秒去查一次注册中心。结果呢?服务下线了,订单还在往那边发请求,一堆报错。
后来改成观察者模式,注册中心主动推送变更事件。代码大概长这样:
// 注册中心 - 主题
public class ServiceRegistry {
private Map<String, List<ServiceObserver>> observers = new ConcurrentHashMap<>();
public void registerObserver(String serviceName, ServiceObserver observer) {
observers.computeIfAbsent(serviceName, k -> new CopyOnWriteArrayList<>()).add(observer);
}
public void removeObserver(String serviceName, ServiceObserver observer) {
List<ServiceObserver> list = observers.get(serviceName);
if (list != null) {
list.remove(observer);
}
}
public void notifyObservers(String serviceName, ServiceEvent event) {
List<ServiceObserver> list = observers.get(serviceName);
if (list != null) {
for (ServiceObserver observer : list) {
observer.onServiceChanged(serviceName, event);
}
}
}
// 服务实例上下线时调用
public void instanceOnline(String serviceName, ServiceInstance instance) {
// 保存实例信息...
notifyObservers(serviceName, new ServiceEvent(EventType.ONLINE, instance));
}
}
// 观察者接口
public interface ServiceObserver {
void onServiceChanged(String serviceName, ServiceEvent event);
}
// 调用方 - 具体观察者
public class OrderService implements ServiceObserver {
private List<ServiceInstance> inventoryInstances = new ArrayList<>();
@Override
public void onServiceChanged(String serviceName, ServiceEvent event) {
if ("inventory-service".equals(serviceName)) {
if (event.getType() == EventType.ONLINE) {
inventoryInstances.add(event.getInstance());
System.out.println("库存服务上线: " + event.getInstance().getAddress());
} else if (event.getType() == EventType.OFFLINE) {
inventoryInstances.remove(event.getInstance());
System.out.println("库存服务下线: " + event.getInstance().getAddress());
}
}
}
}
二、负载均衡:策略模式
服务找到了,接下来就是怎么分配请求。负载均衡算法五花八门:轮询、随机、加权、最少连接、一致性哈希……
策略模式在这里简直是天作之合。每种算法就是一个策略,客户端根据场景自由切换。我习惯把策略定义成接口,然后每种算法一个实现类。
// 策略接口
public interface LoadBalanceStrategy {
ServiceInstance select(List<ServiceInstance> instances, String requestKey);
}
// 轮询策略
public class RoundRobinStrategy implements LoadBalanceStrategy {
private AtomicInteger index = new AtomicInteger(0);
@Override
public ServiceInstance select(List<ServiceInstance> instances, String requestKey) {
int idx = Math.abs(index.getAndIncrement() % instances.size());
return instances.get(idx);
}
}
// 一致性哈希策略
public class ConsistentHashStrategy implements LoadBalanceStrategy {
private static final int VIRTUAL_NODES = 160;
@Override
public ServiceInstance select(List<ServiceInstance> instances, String requestKey) {
TreeMap<Integer, ServiceInstance> ring = new TreeMap<>();
for (ServiceInstance instance : instances) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
int hash = (instance.getId() + "#" + i).hashCode();
ring.put(hash, instance);
}
}
int hash = requestKey.hashCode();
SortedMap<Integer, ServiceInstance> tailMap = ring.tailMap(hash);
Integer key = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
return ring.get(key);
}
}
// 上下文 - 客户端
public class LoadBalancer {
private LoadBalanceStrategy strategy;
public LoadBalancer(LoadBalanceStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(LoadBalanceStrategy strategy) {
this.strategy = strategy;
}
public ServiceInstance select(List<ServiceInstance> instances, String requestKey) {
if (instances == null || instances.isEmpty()) {
throw new RuntimeException("没有可用的服务实例");
}
return strategy.select(instances, requestKey);
}
}
实际项目中,我一般会根据业务场景动态切换策略。比如普通请求用轮询,带用户ID的请求用一致性哈希保证会话粘性,高并发时用最少连接策略。
三、熔断器:状态模式
熔断器这东西,说白了就是防止雪崩的保险丝。当某个服务频繁超时或报错,熔断器就打开,直接返回降级结果,不再发起真实调用。
熔断器有三个状态:关闭、打开、半开。状态之间的转换逻辑,用状态模式来管理再合适不过了。
// 状态接口
public interface CircuitBreakerState {
boolean allowRequest();
void onSuccess();
void onFailure();
CircuitBreakerState nextState();
}
// 关闭状态
public class ClosedState implements CircuitBreakerState {
private int failureCount = 0;
private int threshold;
private long lastFailureTime;
public ClosedState(int threshold) {
this.threshold = threshold;
}
@Override
public boolean allowRequest() {
return true; // 关闭状态允许所有请求
}
@Override
public void onSuccess() {
failureCount = 0; // 成功一次就重置失败计数
}
@Override
public void onFailure() {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= threshold) {
System.out.println("失败次数达到阈值,熔断器打开");
}
}
@Override
public CircuitBreakerState nextState() {
if (failureCount >= threshold) {
return new OpenState(lastFailureTime);
}
return this;
}
}
// 打开状态
public class OpenState implements CircuitBreakerState {
private long openTime;
private static final long TIMEOUT = 5000; // 5秒后尝试半开
public OpenState(long openTime) {
this.openTime = openTime;
}
@Override
public boolean allowRequest() {
if (System.currentTimeMillis() - openTime > TIMEOUT) {
System.out.println("熔断超时,尝试半开");
return true; // 放行一个请求试探
}
return false; // 直接拒绝
}
@Override
public void onSuccess() {
// 半开状态下成功,会切换到关闭状态
}
@Override
public void onFailure() {
// 半开状态下失败,回到打开状态
}
@Override
public CircuitBreakerState nextState() {
// 这里由上下文根据请求结果决定
return this;
}
}
// 熔断器上下文
public class CircuitBreaker {
private CircuitBreakerState state;
public CircuitBreaker(int threshold) {
this.state = new ClosedState(threshold);
}
public boolean allowRequest() {
return state.allowRequest();
}
public void recordSuccess() {
state.onSuccess();
if (state instanceof OpenState) {
state = new ClosedState(5); // 半开成功,回到关闭
}
}
public void recordFailure() {
state.onFailure();
state = state.nextState();
}
}
我曾经在一个支付系统里用过这个模式。当时第三方支付接口经常超时,如果不加熔断,整个订单服务都会被拖垮。用了状态模式后,每个状态的行为一目了然,后来加了个「手动半开」的功能,扩展起来也很方便。
四、三者如何协同工作
这三个模式在分布式系统里不是孤立的。我画了一张图,帮你理清它们的关系:
你看,服务注册中心用观察者模式把实例列表推送给负载均衡器,负载均衡器用策略模式选出一个实例,最后熔断器用状态模式保护调用过程。三个模式各司其职,配合得天衣无缝。
好了,今天就聊到这儿。这三个模式在分布式系统里的应用,说白了就是「怎么找到服务、怎么分配请求、怎么保护系统」。你想想看,是不是这个理?
公众号:蓝海资料掘金营,微信deep3321