Java 中的“大使”模式:简化远程资源管理
“大使”设计模式的意图
Java 中的“大使”模式有助于将监控、日志记录和路由等常见功能从共享资源转移到辅助服务实例中,从而提高分布式系统的性能和可维护性。
结合实际案例详细解释“大使”模式
实际例子
想象一家繁忙的酒店,客人经常要求预订餐厅、购买活动门票或安排交通。为了避免每个客人单独联系这些服务,酒店提供了一位礼宾员。礼宾员代表客人处理这些任务,确保预订顺利进行、门票准时预订以及交通安排高效。
在这个比喻中,客人代表客户服务,外部提供商(餐厅、票务供应商、交通工具)代表远程服务,而礼宾员代表大使服务。这种设置允许客人专注于享受住宿,而礼宾员管理外部交互的复杂性,提供无缝且增强的体验。
用简单的话来说
使用“大使”模式,我们可以实现减少客户端的频繁轮询,以及延迟检查和日志记录。
Microsoft 文档指出
大使服务可以被认为是一个与客户端位于同一位置的进程外代理。这种模式对于卸载常见的客户端连接任务非常有用,例如监控、日志记录、路由、安全(如 TLS)和弹性模式,并且能够以与语言无关的方式实现。它经常与遗留应用程序或难以修改的其他应用程序一起使用,以扩展其网络功能。它还可以使专门的团队实现这些功能。
Java 中“大使”模式的编程示例
在本示例中,我们演示了如何在 Java 中的“大使”模式中实现延迟检查、日志记录和重试机制,以提高系统可靠性。
一个远程服务有多个客户端访问它提供的功能。该服务是一个遗留应用程序,无法更新。来自用户的大量请求会导致连接问题。应该实施新的请求频率规则,以及延迟检查和客户端日志记录。
考虑到上述介绍,我们将在这个示例中模仿该功能。我们有一个由远程服务和大使服务实现的接口。
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
}
远程服务表示为单例。
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
private RemoteService() {
}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e);
}
return waitTime >= 200 ? value * 10 : -1;
}
}
服务大使添加了额外的功能,如日志记录和延迟检查。
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
ServiceAmbassador() {
}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
var startTime = System.currentTimeMillis();
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return FAILURE;
}
if ((result = checkLatency(value)) == FAILURE) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
}
return result;
}
}
客户端拥有一个本地服务大使,用于与远程服务交互。
@Slf4j
public class Client {
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result);
return result;
}
}
这里有两个使用该服务的客户端。
public class App {
public static void main(String[] args) {
var host1 = new Client();
var host2 = new Client();
host1.useService(12);
host2.useService(73);
}
}
以下是运行示例的输出
Time taken(ms):111
Service result:120
Time taken(ms):931
Failed to reach remote:(1)
Time taken(ms):665
Failed to reach remote:(2)
Time taken(ms):538
Failed to reach remote:(3)
Service result:-1
何时在 Java 中使用“大使”模式
- “大使”模式对于 Java 中的云原生和微服务架构特别有用。它有助于监控、日志记录和保护服务间通信,使其成为分布式系统的理想选择。
- 遗留系统集成:通过处理必要的但非核心功能,促进与较新服务的通信。
- 性能提升:可用于缓存结果或压缩数据,以提高通信效率。
典型的用例包括
- 控制对另一个对象的访问
- 实现日志记录
- 实现断路器
- 卸载远程服务任务
- 促进网络连接
“大使”模式的优缺点
优点
- 关注点分离:将跨领域关注点从服务逻辑中分离出来,从而产生更干净、更易维护的代码。
- 可重用基础设施逻辑:“大使”模式允许在多个服务中重用相同的逻辑(例如,日志记录、监控)。
- 提高安全性:集中管理安全功能,如 SSL 终止或身份验证,降低了配置错误的风险。
- 灵活度:无需修改服务代码,就可以更轻松地更新或替换基础设施问题。
权衡
- 增加复杂度:在架构中添加了另一层,这会使系统设计和调试变得复杂。
- 潜在的性能开销:额外的网络跳跃可能会引入延迟和开销,尤其是在没有优化的情况下。
- 部署开销:需要额外的资源和管理来部署和扩展大使服务。
Java 中“大使”模式的实际应用
- 服务网格实现:在 Istio 或 Linkerd 这样的服务网格架构中,“大使”模式通常用作侧车代理,处理服务间通信。这包括诸如服务发现、路由、负载平衡、遥测(指标和跟踪)和安全(身份验证和授权)等任务。
- API 网关:API 网关可以使用“大使”模式来封装常见的功能,如速率限制、缓存、请求整形和身份验证。这使后端服务能够专注于其核心业务逻辑,而无需承担这些跨领域关注点。
- 日志记录和监控:大使可以聚合来自各种服务的日志和指标,并将它们转发到 Prometheus 或 ELK Stack(Elasticsearch、Logstash、Kibana)等集中式监控工具。这简化了每个服务的日志记录和监控设置,并提供系统运行状况的统一视图。
- 安全性:大使可以管理安全相关功能,如 SSL/TLS 终止、身份验证和加密。这确保了跨服务的一致安全实践,并降低了由于配置错误而导致安全漏洞的可能性。
- 弹性:大使可以实现弹性模式,如断路器、重试和超时。例如,Netflix 的 Hystrix 库可以在大使中使用,以防止微服务生态系统中的级联故障。
- 数据库代理:大使可以充当数据库连接的代理,提供连接池、副本读写分离和查询缓存等功能。这从应用程序服务中卸载了大量的复杂性。
- 遗留系统集成:在现代微服务需要与遗留系统通信的场景中,大使可以充当中间人,翻译协议、处理必要的转换以及实施现代安全实践,从而简化集成过程。
- 网络优化:对于跨不同地理位置或云区域部署的服务,大使可以通过压缩数据、批量请求,甚至实施智能路由来优化通信,从而降低延迟和成本。
- 微服务的 Kubernetes 原生 API 网关
相关的 Java 设计模式
- 断路器:经常与它一起使用,通过停止对无响应服务的调用来管理容错。
- 装饰器:装饰器模式用于动态地向对象添加功能,而大使模式用于将功能卸载到单独的对象。
- 代理:与代理模式有相似之处,但大使模式特别关注卸载辅助功能。
- 侧车:在容器化应用程序的上下文中使用的类似模式,其中一个侧车容器为主要应用程序容器提供额外的功能。