1

在软件开发中,通常的做法是将一些基础,简单的服务组合在一起而形成一个具有某一功能的特定服务。这种搭积木的结构,或者说自下而上的组合更有利于程序的资源隔离以及维护与拓展。高层的服务依赖底层服务提供业务计算,低层的服务提供诸如数据存储,网络传输等基础操作。

这些低层的服务就如现实世界中的城市基础设施,没有道路车辆就无处行驶,没有发电厂提供的电力所有的电力设备就无法工作。所以,在程序世界构建的时候,这些基础服务必须预先完成加载(初始化)。当低层接口与高层接口变得比较多时,它们的关系错综复杂。所以,合理地管理它们变得非常有必要!下面我们将讨论几种常见的模式:

有序加载

一种非常简单的方式是:将所有的服务按照依赖层级逐个地加载它们。如下图, C 分别依赖了 A 与 B 。在程序启动的时候可以按照 A -> B -> C 的方式依次加载它们。

这种方式能够有效地解决服务依赖,在服务不是特别复杂,依赖的链不是很长的情况下它会是一个很不错的选择。但是这种方式会将整个服务启动的时候变得很长,因为它们的加载是逐个进行的。因此在大型的服务中这种方式是不被推荐的。

按需加载

另一种方式是按需加载,在服务被使用(调用)时,再去加载它所依赖的服务。如下图,有 2 个高层服务 C 与 E,它们分别依赖了基础服务 A,B 与 B,D。

当访问 C 服务的时候,会按需加载 C <-> B <-> A。C 去尝试找到并加载 B,B 也会尝试找到并加载 A。这是一个有返回的过程,所有使用符号 <-> 表示。同理,E 服务的加载过程也是按照 E <-> B <-> D。

这种模式最大的优点是能做到按需加载,当高层服务被使用的时候才会去加载依赖的服务。在容器技术中也称之为 依赖注入。为避免重复加载的过程,实际上我们会把这些服务放在一个服务容器里面,服务的加载会有容器处理,我们要做的仅仅是从一个容器里面找到它们。例如上图中 C, E 都依赖了 B。

大多的应用服务都会选择按需加载的模式,它不仅能避免有序加载的不能同时加载多个服务的问题,按需加载实际上因为不会加载那些无访问的服务,所以能有效地节省一部分资源。

但这就是终极方案吗?显然不是!下面我们来了解第三种模式。

分组加载

分组加载是将服务按照依赖的层级划分称不同的 ServiceGroup。ServiceGroup 之间是按照顺序加载的,但 ServiceGroup 内的服务是并行加载的。这种方式能够快速地将所有的服务一次性加载。与按需加载不同,分组加载可以在应用启动成功之时就可以立即服务;同时对于按序加载能过做到更加快速地启动服务。如下图,ServiceGroup 1 中的 A, B, 'C' 是并行加载的,ServiceGroup 2ServiceGroup 1 加载完成之后才开始加载。

服务 A, B, D 之间没有依赖,所以它们的加载可以是无序的。当 ServiceGroup 1 加载完成之后,ServiceGroup 2 在加载之前就已经加载了必要的服务。这时候不会出现 ServiceNotFound 的问题。下面的代码使用了 DSL 来描述整个加载的过程:

GroupedServiceBus serviceBus = ...
serviceBus.start(serviceA, serviceB, serviceD)
          .then(serviceC, serviceE)
          .awaitStarted();

销毁

销毁的顺序必须是加载顺序的反序。这样你才能保证不回出现必要服务的丢失。

分组加载实现

使用 Gauva 的 Service 作为服务的基础接口,下面给出了一个分组加载的简单实现。
Current Version

import com.google.common.util.concurrent.Service;

public interface GroupedServiceBus {
    void awaitStarted();

    void awaitStopped();

    GroupedServiceBus start(Service... services);

    GroupedServiceBus then(Service... services);

    GroupedServiceBus awaitServiceGroupStarted(Service... services);

}

使用 DSL 的方式设计 API 能过让它变得更加容易使用跟理解。

public interface KernelService {

}

可以使用 KernelService 来指定哪些服务是必须正确加载的,当 KernelService 加载失败应用并不能提供一个正确的服务,这时你也许可以将整个程序退出。


public class GroupedServiceBusImpl implements GroupedServiceBus {

    private final List<ServiceManager> serviceCluster = new ArrayList<>();
    private final ServiceManager.Listener listener = new ServiceManager.Listener() {
        @Override
        public void failure(Service service) {
            if (service instanceof KernelService) {
                logger.error("KernelService [{}] start failure, system will be exit 1.", service.getClass());
                System.exit(1);
            } else {
                logger.error("Service [{}] start failure.", service.getClass());
            }
        }
    };

    @Override
    public void awaitStarted() {
        if (!serviceCluster.isEmpty()) {
            for (final ServiceManager serviceManager : serviceCluster) {
                awaitStartedServiceManager(serviceManager);
            }
        }
    }

    @Override
    public void awaitStopped() {
        if (!serviceCluster.isEmpty()) {
            for (int i = serviceCluster.size() - 1; i > -1; i--) {
                final ServiceManager serviceManager = serviceCluster.get(i);
                serviceManager.stopAsync();
                serviceManager.awaitStopped();
            }
            serviceCluster.clear();
        }
    }

    @Override
    public GroupedServiceBus start(final Service... services) {
        then(services);
        return this;
    }

    @Override
    public GroupedServiceBus then(Service... services) {
        final ServiceManager serviceManager = new ServiceManager(ImmutableList.copyOf(services));
        serviceManager.addListener(listener);
        serviceCluster.add(serviceManager);
        return this;
    }

    private void awaitStartedServiceManager(final ServiceManager serviceManager) {
        if (!serviceManager.isHealthy()) {
            serviceManager.startAsync();
            serviceManager.awaitHealthy();
        }
    }
}

上面的 serviceCluster 是一个 ServiceManager 的集合,在这里 ServiceManager 就是我们上面讲到的 ServiceGroup。在 ServiceGroup 的所有服务都可以并行加载。

总结

这三种模式没有哪一种是绝对正确跟优秀的。在程序设计中,除了要考虑性能,稳定性等同时还要避免陷入过度设计的陷阱。你的选择需要适应你的环境!


艾彦波
804 声望25 粉丝