大使模式(Ambassador)

19-06-14 jdon
              

目的

在客户端上提供助手服务实例,并从共享资源中卸载常用功能。

说明

远程服务有许多客户端访问它所提供的功能。该服务是遗留应用程序,无法更新。来自用户的大量请求导致连接问题。应实施新的请求频率规则以及延迟检查和客户端日志记录。

简而言之

使用大使模式,我们可以实现从客户端进行频率较低的轮询以及延迟检查和日志记录。

Microsoft文档说明

大使服务可以被视为与客户协同工作的进程外代理。此模式可用于以语言无关的方式卸载常见的客户端连接任务,如监视,日志记录,路由,安全性(如TLS)和弹性模式。它通常与遗留应用程序或其他难以修改的应用程序一起使用,以扩展其网络功能。

程序示例

考虑到上面的例子,我们将以简单的方式模仿功能。

我们有一个由远程服务和大使服务实现的接口:

interface RemoteServiceInterface {

    long doRemoteFunction(int value) throws Exception;
}

表示为单例的远程服务。

public class RemoteService implements RemoteServiceInterface {

        private static final Logger LOGGER = LoggerFactory.getLogger(RemoteService.class);
    private static RemoteService service = null;2

    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;
    }
}

服务大使添加其他功能,如日志记录,延迟检查

public class ServiceAmbassador implements RemoteServiceInterface {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceAmbassador.class);
    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) {
        RemoteService service = RemoteService.getRemoteService();
        long startTime = System.currentTimeMillis();
        long result = service.doRemoteFunction(value);
        long timeTaken = System.currentTimeMillis() - startTime;

        LOGGER.info("Time taken (ms): " + timeTaken);
        return result;
    }

    private long safeCall(int value) {

        int retries = 0;
        long result = -1;

        for (int i = 0; i < RETRIES; i++) {

            if (retries >= RETRIES) {
                return -1;
            }

            if ((result = checkLatency(value)) == -1) {
                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;
    }
}

客户端有一个本地服务大使,用于与远程服务进行交互:

public class Client {

    private ServiceAmbassador serviceAmbassador;

    Client() {
        serviceAmbassador = new ServiceAmbassador();
    }

    long useService(int value) {
        long result = serviceAmbassador.doRemoteFunction(value);
        LOGGER.info("Service result: " + result)
        return result;
    }
}

这里有两个使用该服务的客户端。

Client host1 = new Client();
Client host2 = new Client();
host1.useService(12);
host2.useService(73);

适用场景

大使模式适用于无法修改或极难修改的遗留远程服务。可以在客户端上实现连接功能,从而无需更改远程服务。

  • 大使为远程服务提供本地接口。
  • 大使在客户端提供日志记录、断路、重试和安全保护。

典型用例

  • 控制对其他对象的访问
  • 实现日志记录
  • 实现断路
  • 卸载远程服务任务
  • 促进网络连接