24、观察者模式在微服务中的应用:配置中心变更通知、服务状态变更通知
观察者模式,说白了就是“发布-订阅”那一套。一个对象状态变了,通知一堆依赖它的对象。这个模式在单体应用里用得挺多,但到了微服务架构下,它的价值才真正被放大。
我个人习惯把观察者模式看作是微服务之间的“消息桥梁”。你想想看,微服务那么多,彼此之间如果硬编码调用,那耦合度得多高?改一个服务,可能牵连一片。观察者模式正好解决了这个问题——解耦。
配置中心变更通知
先说说配置中心。我记得有一次线上事故,就是因为改了数据库连接池的配置,但服务没感知到,结果连接池爆了。从那以后,我对配置的动态刷新特别敏感。
配置中心变更通知,是观察者模式在微服务里最经典的应用之一。比如我们用 Nacos 或 Apollo,配置一改,所有订阅了这个配置的服务都能收到通知。
来看一个简化的例子。假设我们有一个配置中心服务,它维护着一堆配置项。其他服务订阅了这些配置项的变化。
// 配置中心(被观察者)
public class ConfigCenter {
private Map<String, String> configs = new ConcurrentHashMap<>();
private List<ConfigObserver> observers = new CopyOnWriteArrayList<>();
public void registerObserver(ConfigObserver observer) {
observers.add(observer);
}
public void removeObserver(ConfigObserver observer) {
observers.remove(observer);
}
public void updateConfig(String key, String value) {
configs.put(key, value);
notifyObservers(key, value);
}
private void notifyObservers(String key, String value) {
for (ConfigObserver observer : observers) {
observer.onConfigChanged(key, value);
}
}
}
// 观察者接口
public interface ConfigObserver {
void onConfigChanged(String key, String value);
}
// 具体服务(观察者)
public class OrderService implements ConfigObserver {
@Override
public void onConfigChanged(String key, String value) {
if ("order.timeout".equals(key)) {
System.out.println("订单超时时间更新为: " + value);
// 刷新本地缓存
}
}
}
这个模式的好处很明显:配置中心不需要知道谁订阅了它,服务也不需要轮询配置中心。配置一变,通知自动推过来。
核心要点:观察者模式让配置变更的通知从“拉”变成了“推”。服务不再需要每隔几秒去查一次配置有没有变,而是等着配置中心主动通知它。这大大降低了网络开销和延迟。
服务状态变更通知
另一个常见场景是服务状态变更通知。微服务架构里,服务实例可能随时上下线。比如某个服务挂了,或者新启动了一个实例。这时候,依赖它的其他服务需要知道这个变化。
我曾经在一个电商项目里遇到过这种情况:订单服务依赖库存服务。库存服务的一个实例挂了,但订单服务不知道,还在往那个实例发请求,结果大量请求超时。后来我们用观察者模式做了服务状态变更通知,问题就解决了。
来看一个服务注册中心的例子:
// 服务注册中心(被观察者)
public class ServiceRegistry {
private Map<String, List<ServiceInstance>> services = new ConcurrentHashMap<>();
private List<ServiceObserver> observers = new CopyOnWriteArrayList<>();
public void registerObserver(ServiceObserver observer) {
observers.add(observer);
}
public void registerService(String serviceName, ServiceInstance instance) {
services.computeIfAbsent(serviceName, k -> new CopyOnWriteArrayList<>()).add(instance);
notifyObservers(serviceName, "UP", instance);
}
public void unregisterService(String serviceName, ServiceInstance instance) {
List<ServiceInstance> instances = services.get(serviceName);
if (instances != null) {
instances.remove(instance);
notifyObservers(serviceName, "DOWN", instance);
}
}
private void notifyObservers(String serviceName, String status, ServiceInstance instance) {
for (ServiceObserver observer : observers) {
observer.onServiceChanged(serviceName, status, instance);
}
}
}
// 观察者接口
public interface ServiceObserver {
void onServiceChanged(String serviceName, String status, ServiceInstance instance);
}
// 网关服务(观察者)
public class GatewayService implements ServiceObserver {
@Override
public void onServiceChanged(String serviceName, String status, ServiceInstance instance) {
if ("DOWN".equals(status)) {
System.out.println("服务 " + serviceName + " 实例 " + instance.getAddress() + " 已下线,更新路由表");
// 从路由表中移除该实例
} else if ("UP".equals(status)) {
System.out.println("服务 " + serviceName + " 实例 " + instance.getAddress() + " 已上线,添加路由");
// 添加到路由表
}
}
}
避坑指南:我曾经在实现服务状态通知时,忘了处理观察者的异常。结果一个观察者抛异常,导致整个通知链断了。后来我改成了异步通知,每个观察者独立执行,互不影响。这一点很重要,尤其是通知链比较长的时候。
观察者模式在微服务中的架构图
下面这张图展示了观察者模式在微服务中的整体结构。配置中心和服务注册中心作为被观察者,多个微服务作为观察者,订阅各自感兴趣的事件。
实现时的几个关键点
观察者模式在微服务里实现起来并不复杂,但有几个坑需要注意。
- 异步通知:观察者的处理逻辑不能阻塞被观察者。我建议用线程池或者消息队列来做异步通知。这样即使某个观察者处理慢了,也不会影响其他观察者。
- 异常隔离:每个观察者应该独立捕获异常。一个观察者挂了,不能影响整个通知链。我曾经就踩过这个坑,后来加了个 try-catch 就解决了。
- 去重和幂等:同一个通知可能因为网络重试等原因被发送多次。观察者需要做好幂等处理,避免重复执行。
- 观察者注册与注销:服务启动时注册,关闭时注销。这个生命周期管理要做好,否则会出现“幽灵观察者”——服务已经挂了,但注册中心还保留着它的订阅信息。
注意:观察者模式虽然好用,但不要滥用。如果观察者太多,通知链太长,性能会下降。我见过一个项目,一个配置变更通知下去,触发了上百个观察者,结果配置中心自己先扛不住了。合理的做法是控制观察者的数量,或者用消息队列做缓冲。
总结
观察者模式在微服务里的应用,说白了就是让服务之间“松耦合”地通信。配置中心变更通知和服务状态变更通知是两个最典型的场景。你想想看,如果没有这个模式,服务之间就得硬编码调用,或者轮询,那得多麻烦。
我个人觉得,观察者模式是微服务架构里不可或缺的一环。它让系统更灵活,更容易扩展。当然,实现的时候要注意异步、异常隔离、幂等这些细节。嗯,这些细节往往决定了系统的稳定性。
一句话总结:观察者模式让微服务之间的通知从“主动拉”变成了“被动推”,解耦的同时也提升了系统的响应速度和稳定性。