在软件开发中,通常的做法是将一些基础,简单的服务组合在一起而形成一个具有某一功能的特定服务。这种搭积木的结构,或者说自下而上的组合更有利于程序的资源隔离以及维护与拓展。高层的服务依赖底层服务提供业务计算,低层的服务提供诸如数据存储,网络传输等基础操作。
这些低层的服务就如现实世界中的城市基础设施,没有道路车辆就无处行驶,没有发电厂提供的电力所有的电力设备就无法工作。所以,在程序世界构建的时候,这些基础服务必须预先完成加载(初始化)。当低层接口与高层接口变得比较多时,它们的关系错综复杂。所以,合理地管理它们变得非常有必要!下面我们将讨论几种常见的模式:
有序加载
一种非常简单的方式是:将所有的服务按照依赖层级逐个地加载它们。如下图, 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 2
在 ServiceGroup 1
加载完成之后才开始加载。
服务 A, B, D 之间没有依赖,所以它们的加载可以是无序的。当 ServiceGroup 1
加载完成之后,ServiceGroup 2
在加载之前就已经加载了必要的服务。这时候不会出现 ServiceNotFound
的问题。下面的代码使用了 DSL 来描述整个加载的过程:
GroupedServiceBus serviceBus = ...
serviceBus.start(serviceA, serviceB, serviceD)
.then(serviceC, serviceE)
.awaitStarted();
销毁
销毁的顺序必须是加载顺序的反序。这样你才能保证不回出现必要服务的丢失。
分组加载实现
使用 Gauva 的 Service 作为服务的基础接口,下面给出了一个分组加载的简单实现。
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 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
的所有服务都可以并行加载。
总结
这三种模式没有哪一种是绝对正确跟优秀的。在程序设计中,除了要考虑性能,稳定性等同时还要避免陷入过度设计的陷阱。你的选择需要适应你的环境!