[关闭]
@yanbo-ai 2015-11-07T06:40:07.000000Z 字数 3351 阅读 2749

服务启动设计

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

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

有序加载

一种非常简单的方式是:将所有的服务按照依赖层级逐个地加载它们。如下图, 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 来描述整个加载的过程:

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

销毁

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

分组加载实现

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

  1. import com.google.common.util.concurrent.Service;
  2. public interface GroupedServiceBus {
  3. void awaitStarted();
  4. void awaitStopped();
  5. GroupedServiceBus start(Service... services);
  6. GroupedServiceBus then(Service... services);
  7. GroupedServiceBus awaitServiceGroupStarted(Service... services);
  8. }

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

  1. public interface KernelService {
  2. }

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

  1. public class GroupedServiceBusImpl implements GroupedServiceBus {
  2. private final List<ServiceManager> serviceCluster = new ArrayList<>();
  3. private final ServiceManager.Listener listener = new ServiceManager.Listener() {
  4. @Override
  5. public void failure(Service service) {
  6. if (service instanceof KernelService) {
  7. logger.error("KernelService [{}] start failure, system will be exit 1.", service.getClass());
  8. System.exit(1);
  9. } else {
  10. logger.error("Service [{}] start failure.", service.getClass());
  11. }
  12. }
  13. };
  14. @Override
  15. public void awaitStarted() {
  16. if (!serviceCluster.isEmpty()) {
  17. for (final ServiceManager serviceManager : serviceCluster) {
  18. awaitStartedServiceManager(serviceManager);
  19. }
  20. }
  21. }
  22. @Override
  23. public void awaitStopped() {
  24. if (!serviceCluster.isEmpty()) {
  25. for (int i = serviceCluster.size() - 1; i > -1; i--) {
  26. final ServiceManager serviceManager = serviceCluster.get(i);
  27. serviceManager.stopAsync();
  28. serviceManager.awaitStopped();
  29. }
  30. serviceCluster.clear();
  31. }
  32. }
  33. @Override
  34. public GroupedServiceBus start(final Service... services) {
  35. then(services);
  36. return this;
  37. }
  38. @Override
  39. public GroupedServiceBus then(Service... services) {
  40. final ServiceManager serviceManager = new ServiceManager(ImmutableList.copyOf(services));
  41. serviceManager.addListener(listener);
  42. serviceCluster.add(serviceManager);
  43. return this;
  44. }
  45. private void awaitStartedServiceManager(final ServiceManager serviceManager) {
  46. if (!serviceManager.isHealthy()) {
  47. serviceManager.startAsync();
  48. serviceManager.awaitHealthy();
  49. }
  50. }
  51. }

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

总结

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

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注