精华内容
下载资源
问答
  • SOA中间件 - 服务自动注册与发现

    千次阅读 2016-07-13 20:59:21
    时序图:

    时序图:


    展开全文
  • RegistryDirectory,基于注册中心的服务发现,本文将重点探讨Dubbo是如何实现服务的自动注册与发现。从上篇文章,得知在消息消费者在创建服务调用器(Invoker)【消费者在初始时】时需要根据不同的协议,例如dubbo、...

       RegistryDirectory,基于注册中心的服务发现,本文将重点探讨Dubbo是如何实现服务的自动注册与发现。从上篇文章,得知在消息消费者在创建服务调用器(Invoker)【消费者在初始时】时需要根据不同的协议,例如dubbo、registry(从注册中心获取服务提供者)来构建,其调用的方法为Protocol#refer,基于注册中心发现服务提供者的实现协议为RegistryProtocol。

       RegistryProtocol#refer ----> doRefer方法。

       RegistryProtocol#doRefer

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {    // @1
            RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);   // @2
            directory.setRegistry(registry);
            directory.setProtocol(protocol);   // @3
            // all attributes of REFER_KEY
            Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());   // @4
            URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);  // @5
            if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
                    && url.getParameter(Constants.REGISTER_KEY, true)) {
                registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                        Constants.CHECK_KEY, String.valueOf(false)));
            }   // @6
            directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
                    Constants.PROVIDERS_CATEGORY
                            + "," + Constants.CONFIGURATORS_CATEGORY
                            + "," + Constants.ROUTERS_CATEGORY));     // @7
    
            Invoker invoker = cluster.join(directory);    // @8
            ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);     // @9
            return invoker;
        }
    

       代码@1:参数详解

    • Cluster cluster:集群策略。
    • Registry registry:注册中心实现类。
    • Class type:引用服务名,dubbo:reference interface。
    • URL url:注册中心URL。

       代码@2:构建RegistryDirectory对象,基于注册中心动态发现服务提供者(服务提供者新增或减少),本节重点会剖析该类的实现细节。
       代码@3:为RegistryDirectory设置注册中心、协议。
       代码@4:获取服务消费者的配置属性。
       代码@5:构建消费者URL,例如:

    consumer://192.168.56.1/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9892&qos.port=33333&side=consumer&timestamp=1528380277185
    

       代码@6:向注册中心消息消费者:

    consumer://192.168.56.1/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9892&qos.port=33333&side=consumer&timestamp=1528380277185
    

       相比第5步的URL,增加了category=consumers、check=false,其中category表示在注册中心的命名空间,这里代表消费端。该步骤的作用就是向注册中心为服务增加一个消息消费者,其生成的效果如下:【以zookeeper为例】。
    这里写图片描述
       代码@7:为消息消费者添加category=providers,configurators,routers属性后,然后向注册中心订阅该URL,关注该服务下的providers,configurators,routers发生变化时通知RegistryDirectory,以便及时发现服务提供者、配置、路由规则的变化。

    consumer://192.168.56.1/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9892&qos.port=33333&side=consumer&timestamp=1528380277185
    

       其订阅关系调用的入口为:RegistryDirectory#subscribe方法,是接下来需要重点分析的重点。
       代码@8:根据Directory,利用集群策略返回集群Invoker。
       代码@9:缓存服务消费者、服务提供者对应关系。

       从这里发现,服务的注册与发现与RegistryDirectory联系非常紧密,接下来让我们来详细分析RegistryDirectory的实现细节。

    1、RegistryDirectory类图
    这里写图片描述

    • private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
      集群策略,默认为failover。
    • private static final RouterFactory routerFactory = ExtensionLoader.getExtensionLoader (RouterFactory.class).getAdaptiveExtension()路由工厂,可以通过监控中心或治理中心配置。
    • private static final ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension();配置实现工厂类。
    • private final String serviceKey; 服务key,默认为服务接口名。com.alibaba.dubbo.registry.RegistryService,注册中心在Dubbo中也是使用服务暴露。
    • private final Class< T > serviceType;服务提供者接口类,例如interface com.alibaba.dubbo.demo.DemoService
    • private final Map< String, String> queryMap:服务消费者URL中的所有属性。
    • private final URL directoryUrl;注册中心URL,只保留消息消费者URL查询属性,也就是queryMap。
    • private final String[] serviceMethods:引用服务提供者方法数组。
    • private final boolean multiGroup:是否引用多个服务组。
    • private Protocol protocol:协议。
    • private Registry registry:注册中心实现者。
    • private volatile List< Configurator> configurators;配置信息。
    • private volatile Map< String, Invoker< T>> urlInvokerMap; 服务URL对应的Invoker(服务提供者调用器)。
    • private volatile Map< String, List< Invoker< T>>> methodInvokerMap; methodName : List< Invoker< T >>,
      dubbo:method 对应的Invoker缓存表。
    • private volatile Set< URL > cachedInvokerUrls; 当前缓存的所有URL提供者URL。

    2、RegistryDirectory 构造方法详解

    public RegistryDirectory(Class<T> serviceType, URL url) {    // @1
            super(url);
            if (serviceType == null)
                throw new IllegalArgumentException("service type is null.");
            if (url.getServiceKey() == null || url.getServiceKey().length() == 0)
                throw new IllegalArgumentException("registry serviceKey is null.");
            this.serviceType = serviceType;  
            this.serviceKey = url.getServiceKey();     // @2
            this.queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));  // @3
            this.overrideDirectoryUrl = this.directoryUrl = url.setPath(url.getServiceInterface()).clearParameters().addParameters(queryMap).removeParameter(Constants.MONITOR_KEY); //@4
            String group = directoryUrl.getParameter(Constants.GROUP_KEY, "");
            this.multiGroup = group != null && ("*".equals(group) || group.contains(","));
            String methods = queryMap.get(Constants.METHODS_KEY);
            this.serviceMethods = methods == null ? null : Constants.COMMA_SPLIT_PATTERN.split(methods);   // @5
        }
    

       代码@1:参数描述,serviceType:消费者引用的服务< dubbo:reference interface="" …/>;URL url:注册中心的URL,例如:

    zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.0&pid=5552&qos.port=33333&refer=application%3Ddemo-consumer%26check%3Dfalse%26dubbo%3D2.0.0%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D5552%26qos.port%3D33333%26register.ip%3D192.168.56.1%26side%3Dconsumer%26timestamp%3D1528379076123&timestamp=1528379076179
    

       代码@2:获取注册中心URL的serviceKey:com.alibaba.dubbo.registry.RegistryService。
       代码@3:获取注册中心URL消费提供者的所有配置参数:从url属性的refer。
       代码@4:初始化haulovverrideDirecotryUrl、directoryUrl:注册中心的URL,移除监控中心以及其他属性值,只保留消息消费者的配置属性。
       代码@5:获取服务消费者单独配置的方法名dubbo:method。

    3、RegistryDirectory#subscribe

    public void subscribe(URL url) {
         setConsumerUrl(url);   // @1
         registry.subscribe(url, this); // @2
    }
    

       代码@1:设置RegistryDirectory的consumerUrl为消费者URL。
       代码@2:调用注册中心订阅消息消息消费者URL,首先看一下接口Registry#subscribe的接口声明:
    RegistryService:void subscribe(URL url, NotifyListener listener); 这里传入的NotifyListener为RegistryDirectory,其注册中心的subscribe方法暂时不深入去跟踪,不过根据上面URL上面的特点,应该能猜出如下实现关键点:

    consumer://192.168.56.1/com.alibaba.dubbo.demo.DemoService?application=demo-consumer&category=providers,configurators,routers&check=false&dubbo=2.0.0&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=9892&qos.port=33333&side=consumer&timestamp=1528380277185
    
    • 根据消息消费者URL,获取服务名。
    • 根据category=providers、configurators、routers,分别在该服务名下的providers目录、configurators目录、routers目录建立事件监听,监听该目录下节点的创建、更新、删除事件,然后一旦事件触发,将回调RegistryDirectory#void notify(List< URL> urls)。

    4、RegistryDirectory#notify
       首先该方法是在注册中心providers、configurators、routers目录下的节点发生变化后,通知RegistryDirectory,已便更新最新信息,实现”动态“发现机制。

       RegistryDirectory#notify

    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
    for (URL url : urls) {
         String protocol = url.getProtocol();    // @1 
         String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);   // @2
         if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) {   // @3
               routerUrls.add(url);
          } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {   // @4
               configuratorUrls.add(url);
         } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {    // @5
               invokerUrls.add(url);
         } else {
              logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + 
                    NetUtils.getLocalHost());
         }
    }
    

       Step1:根据通知的URL的前缀,分别添加到:invokerUrls(提供者url)、routerUrls(路由信息)、configuratorUrls (配置url)。
       代码@1:从url中获取协议字段,例如condition://、route://、script://、override://等。
       代码@2:获取url的category,在注册中心的命令空间,例如:providers、configurators、routers。
       代码@3:如果category等于routers或协议等于route,则添加到routerUrls中。
       代码@4:如果category等于configurators或协议等于override,则添加到configuratorUrls中。
       代码@5:如果category等于providers,则表示服务提供者url,加入到invokerUrls中。

       RegistryDirectory#notify

    // configurators
    if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
        this.configurators = toConfigurators(configuratorUrls);
    }
    

       Step2:将configuratorUrls转换为配置对象List< Configurator> configurators,该方法将在《源码分析Dubbo配置规则实现细节》一文中详细讲解。

       RegistryDirectory#notify

    // routers
    if (routerUrls != null && !routerUrls.isEmpty()) {
          List<Router> routers = toRouters(routerUrls);
          if (routers != null) { // null - do nothing
                setRouters(routers);
          }
    }
    

       Step3:将routerUrls路由URL转换为Router对象,该部分内容将在《源码分析Dubbo路由机制实现细节》一文中详细分析。

       RegistryDirectory#notify

    // providers
    refreshInvoker(invokerUrls);
    

       Step4:根据回调通知刷新服务提供者集合。

    5、RegistryDirectory#refreshInvoker

       RegistryDirectory#refreshInvoker

    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
             && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
         this.forbidden = true; // Forbid to access
         this.methodInvokerMap = null; // Set the method invoker map to null
         destroyAllInvokers(); // Close all invokers
    } 
    

       Step1:如果invokerUrls不为空并且长度为1,并且协议为empty,表示该服务的所有服务提供者都下线了。需要销毁当前所有的服务提供者Invoker。

       RegistryDirectory#refreshInvoker

    this.forbidden = false; // Allow to access
    Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
    if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
            invokerUrls.addAll(this.cachedInvokerUrls);
    } else {
          this.cachedInvokerUrls = new HashSet<URL>();
          this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
    }
    if (invokerUrls.isEmpty()) {
          return;
    }
    

       Step2: 如果invokerUrls为空,并且已缓存的invokerUrls不为空,将缓存中的invoker url复制到invokerUrls中,这里可以说明如果providers目录未发送变化,invokerUrls则为空,表示使用上次缓存的服务提供者URL对应的invoker;如果invokerUrls不为空,则用iinvokerUrls中的值替换原缓存的invokerUrls,这里说明,如果providers发生变化,invokerUrls中会包含此时注册中心所有的服务提供者。如果invokerUrls为空,则无需处理,结束本次更新服务提供者Invoker操作。

       RegistryDirectory#refreshInvoker

    Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
    Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
    

       Step3:将invokerUrls转换为对应的Invoke,然后根据服务级的url:invoker映射关系创建method:List< Invoker>映射关系,将在下文相信分析。

       RegistryDirectory#refreshInvoker

    this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
    this.urlInvokerMap = newUrlInvokerMap;
    try {
            destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
    } catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
    }
    

       Step4:如果支持multiGroup机制,则合并methodInvoker,将在下文分析,然后根据toInvokers、toMethodInvokers刷新当前最新的服务提供者信息。

    6、RegistryDirectory#toInvokers

       RegistryDirectory#toInvokers

    String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
    for (URL providerUrl : urls) {
        // ...
    }
    

       Step1:获取消息消费者URL中的协议类型,< dubbo:reference protocol="" …/>属性值,然后遍历所有的Invoker Url(服务提供者URL)。

       RegistryDirectory#toInvokers

    if (queryProtocols != null && queryProtocols.length() > 0) {
           boolean accept = false;
           String[] acceptProtocols = queryProtocols.split(",");
           for (String acceptProtocol : acceptProtocols) {
                if (providerUrl.getProtocol().equals(acceptProtocol)) {
                      accept = true;
                      break;
                }
           }
          if (!accept) {
                continue;
          }
    }
    

       Step2: 从这一步开始,代码都包裹在for(URL providerUrl : urls)中,一个一个处理提供者URL。如果dubbo:referecnce标签的protocol不为空,则需要对服务提供者URL进行过滤,匹配其协议与protocol属性相同的服务,如果不匹配,则跳过后续处理逻辑,接着处理下一个服务提供者URL。

       RegistryDirectory#toInvokers

    if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
          continue;
    }
    

       Step3:如果协议为empty,跳过,处理下一个服务提供者URL。

       RegistryDirectory#toInvokers

    if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
           logger.error(new IllegalStateException("Unsupported protocol " + providerUrl.getProtocol() + " in notified url: " + providerUrl + " from registry " + getUrl().getAddress() + " to 
                     consumer " + NetUtils.getLocalHost()
                            + ", supported protocol: " + ExtensionLoader.getExtensionLoader(Protocol.class).getSupportedExtensions()));
       continue;
    }
    

       Step4:验证服务提供者协议,如果不支持,则跳过。

       RegistryDirectory#toInvokers

    URL url = mergeUrl(providerUrl);
    

       Step5:合并URL中的属性,其具体实现细节如下:

    1. 消费端属性覆盖生产者端属性(配置属性消费者端优先生产者端属性),其具体实现方法:ClusterUtils.mergeUrl(providerUrl, queryMap),其中queryMap为消费端属性。
      a、首先移除只在服务提供者端生效的属性(线程池相关):threadname、default.threadname、threadpool、default.threadpool、corethreads、default.corethreads、threads、default.threads、queues、default.queues、alive、default.alive、transporter、default.transporter,服务提供者URL中的这些属性来源于dubbo:protocol、dubbo:provider。
      b、用消费端配置属性覆盖服务端属性。
      c、如下属性以服务端优先:dubbo(dubbo信息)、version(版本)、group(服务组)、methods(服务方法)、timestamp(时间戳)。
      d、合并服务端,消费端Filter,其配置属性(reference.filter),返回结果为:provider#reference.filter,
      consumer#reference.filter。
      e、合并服务端,消费端Listener,其配置属性(invoker.listener),返回结果为:provider#invoker.listener,consumer#invoker.listener。
    2. 合并configuratorUrls 中的属性,我们现在应该知道,dubbo可以在监控中心或管理端(dubbo-admin)覆盖覆盖服务提供者的属性,其使用协议为override,该部分的实现逻辑见:《源码分析Dubbo配置规则机制(override协议)》
    3. 为服务提供者URL增加check=false,默认只有在服务调用时才检查服务提供者是否可用。
    4. 重新复制overrideDirectoryUrl,providerUrl在进过第一步参数合并后(包含override协议覆盖后的属性)赋值给overrideDirectoryUrl。
    String key = url.toFullString(); // The parameter urls are sorted
    if (keys.contains(key)) { // Repeated url
          continue;
    }
    keys.add(key);
    

       Step6:获取url所有属性构成的key,该key也是RegistryDirectory中Map<String, Invoker> urlInvokerMap;中的key。

       RegistryDirectory#toInvokers

    Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
    Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
    if (invoker == null) { // Not in the cache, refer again
       try {
             boolean enabled = true;
             if (url.hasParameter(Constants.DISABLED_KEY)) {
                   enabled = !url.getParameter(Constants.DISABLED_KEY, false);
              } else {
                   enabled = url.getParameter(Constants.ENABLED_KEY, true);
               }
               if (enabled) {
                    invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
               }
         } catch (Throwable t) {
                   logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
          }
          if (invoker != null) { // Put new invoker in cache
                newUrlInvokerMap.put(key, invoker);
          }
    } else {
           newUrlInvokerMap.put(key, invoker);
    }
    

       Step7:如果localUrlInvokerMap中未包含invoker并且该provider状态为启用,则创建该URL对应的Invoker,并添加到newUrlInvokerMap中。toInvokers运行结束后,回到refreshInvoker方法中继续往下执行,根据 最新的服务提供者映射关系Map< String,Invoker>,构建Map< String,List< Invoker>>,其中键为methodName。然后更新RegistryDirectory的urlInvokerMap、methodInvokerMap属性,并销毁老的Invoker对象,完成一次路由发现过程。

       上面整个过程完成了一次动态服务提供者发现流程,下面再分析一下RegistryDirectory的另外一个重要方法,doList,再重复一遍RegistryDirectory的作用,服务提供者目录服务,在集群Invoker的实现中,内部持有一个Direcotry对象,在进行服务调用之前,首先先从众多的Invoker中选择一个来执行,那众多的Invoker从哪来呢?其来源于集群Invoker中会调用Direcotry的public List< Invoker< T>> list(Invocation invocation),首先将调用AbstractDirectory#list方法,然后再内部调用doList方法,doList方法有其子类实现。

    7、RegistryDirectory#doList(Invocation invocation) 方法详解

       RegistryDirectory#doList

    if (forbidden) {
          // 1. No service provider 2. Service providers are disabled
          throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
                "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +  NetUtils.getLocalHost()
                      + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
    }
    

       Step1:如果禁止访问(如果没有服务提供者,或服务提供者被禁用),则抛出没有提供者异常。

       RegistryDirectory#doList

    Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
    if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
        String methodName = RpcUtils.getMethodName(invocation);
        Object[] args = RpcUtils.getArguments(invocation);
        if (args != null && args.length > 0 && args[0] != null
                 && (args[0] instanceof String || args[0].getClass().isEnum())) {
              invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter
         }
        if (invokers == null) {
              invokers = localMethodInvokerMap.get(methodName);
        }
        if (invokers == null) {
              invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
         }
         if (invokers == null) {
             Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
             if (iterator.hasNext()) {
                  invokers = iterator.next();
              }
         }
    }
    return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
    

       Step2:根据方法名称,从Map< String,List< Invoker>>这个集合中找到合适的List< Invoker>,如果方法名未命中,则返回所有的Invoker,localMethodInvokerMap中方法名,主要是dubbo:service的子标签dubbo:method,最终返回invokers。

       本文详细介绍了服务消费者基于注册中心的服务发现机制,其中对routers(路由)与configurators(override协议)并未详细展开,下节先重点分析configurators与routers(路由)实现细节。

       总结一下服务注册与发现机制:
       基于注册 中心的事件通知(订阅与发布),一切支持事件订阅与发布的框架都可以作为Dubbo注册中心的选型。

    1. 服务提供者在暴露服务时,会向注册中心注册自己,具体就是在${service interface}/providers目录下添加 一个节点(临时),服务提供者需要与注册中心保持长连接,一旦连接断掉(重试连接)会话信息失效后,注册中心会认为该服务提供者不可用(提供者节点会被删除)。

    2. 消费者在启动时,首先也会向注册中心注册自己,具体在${interface interface}/consumers目录下创建一个节点。

    3. 消费者订阅${service interface}/ [ providers、configurators、routers ]三个目录,这些目录下的节点删除、新增事件都胡通知消费者,根据通知,重构服务调用器(Invoker)。

       以上就是Dubbo服务注册与动态发现机制的原理与实现细节。


    欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
    1、源码分析RocketMQ专栏(40篇+)
    2、源码分析Sentinel专栏(12篇+)
    3、源码分析Dubbo专栏(28篇+)
    4、源码分析Mybatis专栏
    5、源码分析Netty专栏(18篇+)
    6、源码分析JUC专栏
    7、源码分析Elasticjob专栏
    8、Elasticsearch专栏(20篇+)
    9、源码分析MyCat专栏

    展开全文
  • 前言 我个人觉得,中间件的部署使用是非常难记忆...它主要实现了两个功能,服务注册与发现与自身的负载均衡的集群。 我们可以把他理解为一个没有界面的应用程序,因为没有界面,所以想启动Consul就只能使用命令行了;
  • node中间件,支持服务自动发现,自动注册服务间通信,自动推选master服务,master服务crash后自动重新推选可用的服务作为master服务,支持added,removed,elected等事件监听。 Requirements requirements ...
  • Startup 类:注册服务和使用中间件 Startup类默认生成了两个方法,在这个类中主要负责注册服务和使用中间件。 让我们先来看一下Startup类的源码 Startup类的源码 在下面的源码中有ConfigureServices和Configure...

    笔记内容来源于微软 MVP 杨旭老师 solenovex 的视频

    Startup 类:注册服务和使用中间件

    Startup类默认生成了两个方法,在这个类中主要负责注册服务和使用中间件。

    让我们先来看一下Startup类的源码

    Startup类的源码

    在下面的源码中有ConfigureServices和Configure两个方法。

    public void ConfigureServices(IServiceCollection services){}
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }

     

    1.ConfigureServices方法

    ConfigureServices方法是用来注册服务的,Configure方法是用来使用中间件的。

    注册服务就是依赖注入,向Ioc容器中注入类型的对象,注入后就可以直接使用该类型注入实例,而无需再次实例化,减少了代码的耦合。

    具体内容可以自行百度依赖注入/控制反转。

    2.Configure方法

    Configure方法配置了Http请求处理的管道,每个Http请求到达之后,会按照Configure方法中的组件来决定如何处理这些请求。

    其中的app.Run方法以及匿名函数,就是不管什么请求到达这里,都会返回 context.Response.WriteAsync("Hello World!") 响应。

    注册服务演示

    我们先创建一个IWelcomeService接口以及实现该接口的WelcomeService类

    public interface IWelcomeService
    {
        string GetMessage();
    }
    public class WelcomeService : IWelcomeService
    {
        public string GetMessage()
        {
            return "Hello from IWelcomeService";
        }
    }

     

    然后在Startup类Configure方法中使用该接口类型对象,调用接口的GetMessage方法输出

    事实上,运行后我们会发现直接使用是不行的,因为该类型刚还有在ConfigureServices方法中注册。

    下面我们就在ConfigureServices方法中注册接口和实现类。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IWelcomeService, WelcomeService>();
    }

     

    运行后的结果

    Hello from IWelcomeService

    注册服务的几种形式

    注册服务时,我们使用的是AddSingleton方法,这是以单例的形式依赖注入对象,单例就是指该类型只有一个实例,具体含义和用法可以自行百度。

    还有其他几种形式,如AddTransient方法,每次请求都会生成一个实例,AddScoped方法,每次Http请求都会生成一个实例。

    管道和中间件

    Configure方法配置了Http请求处理的管道。

    假设有个Http请求到达了我们的Web应用,有一个POST请求,那么我们的Web应用就需要处理这个请求。中间件就是负责处理请求的,他将决定如何处理这些请求。

    中间件其实就是个对象,每个中间件的角色和功能都不一样,并且局限在特定的领域内。每个中间件都很小,Web应用会用到很多中间件。

    一个简单的中间件管道示例

    假设一个POST请求Product,首先通过Logger中间件,Logger可以查看到很多的信息,记录下请求的信息,也可以拒绝请求。

    然后转送到下个中间件,比如授权中间件。授权中间件会先找一个特定的cookie的值或者token,如果找到了就转发到下一个中间件。

    下一个中间件可以是路由中间件。首先查看请求的URL,然后找到可以响应该请求的方法,就可以返回一个JSON/HTML,然后原路返回。

    Startup类被调用的逻辑

    搞清了Startup类中的方法的作用和能做什么后,我们来看一看Startup类是如何被调用的。

    上一篇博文提到了Program类的CreateWebHostBuilder方法中调用了  UseStartup<Startup>() ,就是这个方法制定了Startup类为启动类,实例化Starup类得到实例,并调用类中的两个方法。

    首先会调用ConfigureServices方法,注册服务。除了预先注册好的服务,自己写的类和一些没注册内置类都需要在该方法中注册。

    然后调用Configure方法,该方法只会调用一次。在该方法中,我们使用实现了IApplicationBuilder接口的类型的对象,来配置中间件。

    中间件的Use方法

    默认的Run方法并不建议使用,项目中常常使用Use方法。除此之外还有内置的很多USeXxx方法。如UseWelcomePage方法就会调用欢迎页。这些方法的参数往往可以传递一个对象来对其进行配置。

    还有直接使用Use方法更底层,其参数是一个Func<ReuquestDelegate,ReuquestDelegate>,Func的参数是ReuquestDelegate,

    其返回类型也是ReuquestDelegate。ReuquestDelegate类型就是一段可执行的代码,例如下方代码,返回了一个异步Task,Task返回了httpContext对象。

    app.Use(next => 
    {
        return async httpContext =>
        {
            if (httpContext.Request.Path.StartsWithSegments("/first"))
            {
                await httpContext.Response.WriteAsync("First!");
            }
            else
            {
                await next(httpContext);
            }
        };
    });

    异常页面的中间件

    下方代码首先判断当前环境是不是开发环境,如果是,当出现错误时就会使用开发者异常页面中间件。

    因为开发者需要详细的错误信息,但是这些信息不能直接暴露,否则会被黑客轻易利用,因此只能在开发者环境下才需要调用开发者异常页面中间件。

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    判断环境和自定义环境

    前面讲到可以判断运行环境,事实上环境是可以自定义的,

    转载于:https://www.cnblogs.com/errornull/p/10545772.html

    展开全文
  • 聊聊微服务的服务注册与发现

    千次阅读 2018-06-27 17:32:49
    摘要: 一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用。如果没有想清楚前面的功能,再高的可用性和性能都是浮云。最后,安全也同样重要。下面将从 服务注册、...

    摘要: 一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用。如果没有想清楚前面的功能,再高的可用性和性能都是浮云。最后,安全也同样重要。下面将从 服务注册、服务发现、容灾和高可用三个大方面来回答这些问题的主流做法。

    引言

    聊起微服务的服务注册与发现,很多人立马就会脱口而出 zk、etcd、consul、eureka 这些组件,进而聊到 CAP 如何取舍,性能如何,高可用和容灾是怎么实现的。

    在这之前,站在组件使用者的角度,我想先问这么几个问题:

    • 注册的 IP 和端口怎么确定 ?
    • 实现服务治理还需要注册哪些信息 ?
    • 如何进行优雅的服务注册与服务下线 ?
    • 注册服务的健康检查是如何做的 ?
    • 当服务有节点退出或新的节点加入时,订阅者能不能及时收到通知 ?
    • 我能方便地查看某个应用发布和订阅了哪些服务,以及所订阅的服务有哪些节点吗 ?

    看完这些问题后,您也许会发现,对于服务注册与发现,首先应该关注的是服务注册发现本身的功能,然后才是性能和高可用。

    一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用。如果没有想清楚前面的功能,再高的可用性和性能都是浮云。最后,安全也同样重要。

    • 服务端的性能如何 ?
    • 服务发现的容灾策略是怎样的 ?
    • 当我的应用和服务发现中心的网络连接出现问题时,会对我的调用产生什么影响 ?
    • 服务注册中心某台机器宕机或者全部宕机时,会对我的调用产生什么影响 ?
    • 服务注册和发现的链路安全吗,有没有做好权限控制 ?

    下面将从 服务注册、服务发现、容灾和高可用三个大方面来回答这些问题的主流做法。

    最后会介绍一下 ANS(Alibaba Naming Service) , ANS 综合了这些解决方案中的优点,并在 EDAS(阿里巴巴企业级分布式应用服务) 中输出,目前完全免费!

    服务注册

    注册的 IP 和端口怎么确定 ?

    IP 如何确定

    主流的 IP 获取有这几种方法。

    • 最简单粗暴的方式,手动配置需要注册的IP。当然这种方式基本无法在生产环境使用,因为微服务基本都是支持水平扩容多机部署的,在配置中写死 IP 地址的方式无法支持一份代码水平扩容,会给运维带来极大的成本。
    • 通过遍历网卡的方式去获取,找到第一个不为本地环回地址的 IP 地址。绝大多数情况下,这个方式比较好用,dubbo 等框架采用的就是这种方法。
    • 在一些网络规划比较好的标准化机房中,我们还可以通过手动指定网卡名,即 interfaceName 的方式来指定使用哪一块网卡所对应的 IP 地址进行注册。
    • 当上述三种方式都不能有效解决问题的时候,有一个方法就是直接与服务注册中心建立 socket 连接,然后通过socket.getLocalAddress() 这种方式来获取本机的 IP。

    端口如何确定

    端口的获取,没有标准化的方案。

    • 如果是 RPC 应用,启动的时候都有一个配置来指定服务监听的端口, 注册的时候直接使用配置项的端口值。
    • 传统的 WEB 容器所提供的 HTTP 的应用,同样也存在一个配置文件来配置容器的监听端口,注册时候直接使用配置项的端口值。
    • 特别的,在 Java 应用的 Spring Boot 框架中,可以通过 EmbeddedServletContainerInitializedEvent. getEmbeddedServletContainer().getPort()来获取。(Spring Boot 版本为 1.x)。

    实现服务治理还需要注册哪些信息 ?

    简单地将 IP 和 port 信息注册上去,可以满足基本的服务调用的需求,但是在业务发展到一定程度的时候,我们还会有这些需求:

    • 想知道某个 HTTP 服务是否开启了 TLS。
    • 对相同服务下的不同节点设置不同的权重,进行流量调度。
    • 将服务分成预发环境和生产环境,方便进行AB Test功能。
    • 不同机房的服务注册时加上机房的标签,以实现同机房优先的路由规则。

    这些高级功能的实现,本质上是依赖于客户端调用时候的负载均衡策略和调用策略,但是如果服务元数据没有注册上来,也只能是巧妇难为无米之炊。一个良好的服务注册中心在设计最初就应该支持这些扩展字段。

    如何进行优雅的服务注册与服务下线 ?

    优雅发布

    虽然服务注册一般发生在服务的启动阶段,但是细分的话,服务注册应该在服务已经完全启动成功,并准备对外提供服务之后才能进行注册。

    • 有些 RPC 框架自身提供了方法来判断服务是否已经启动完成,如 Thrift ,我们可以通过 Server.isServing() 来判断。
    • 有一些 RPC 框架本身没有提供服务是否启动完成的方式,这时我们可以通过检测端口是否已经处于监听状态来判断。
    • 而对于 HTTP 服务,服务是否启动完毕也可以通过端口是否处于监听状态来判断。
    • 特别的,在 Java 应用的 Spring Boot 框架中,可以通过事件通知的形式来通知容器已经启动完毕, EmbeddedServletContainerInitializedEvent 事件来通知容器已经启动完成 (Spring Boot 版本为 1.x)。

    优雅下线

    绝大多数的服务注册中心都提供了健康检查功能,在应用停止后会自动摘除服务所对应的节点。但是我们也不能完全依赖此功能,应用应该在停止时主动调用服务注册中心的服务下线接口。

    • 在 Java 应用中,通用的服务下线接口调用一般使用 JVM Shutdown Hook 的方式来实现。
    • 特别的,在 Java 应用中的 Spring 框架中,可以通过 Spring Bean LifeCycle 来实现应用停止时主动调用服务下线接口。
    • 当然上述两种方式还不够优雅,因为不能确保不出现 kill -9 这种粗暴的停止方式,而且应用调用服务下线接口也是尝试去调用,对于网络不通等异常场景并没有做异常处理。因此,调用客户端仍应该做好负载均衡与 failover 的处理。
    • 更优雅的方式,先将即将停止的应用所对应的权重调成 0,此时上游将不再调用此应用。这时候的停止应用的操作对服务订阅者完全没有影响,当然这种场景需要订阅者实现按权重的负载均衡和运维部署工具深度结合。

    服务的健康检查是如何做的 ?

    健康检查分为客户端心跳和服务端主动探测两种方式。

    • 客户端心跳
    • 客户端每隔一定时间主动发送“心跳”的方式来向服务端表明自己的服务状态正常,心跳可以是 TCP 的形式,也可以是 HTTP 的形式。
    • 也可以通过维持客户端和服务端的一个 socket 长连接自己实现一个客户端心跳的方式。
    • ZooKeeper 并没有主动的发送心跳,而是依赖了组件本身提供的临时节点的特性,通过 ZooKeeper 连接的 session 来维持临时节点。

    但是客户端心跳中,长连接的维持和客户端的主动心跳都只是表明链路上的正常,不一定是服务状态正常。

    服务端主动调用服务进行健康检查是一个较为准确的方式,返回结果成功表明服务状态确实正常。

    • 服务端主动探测
    • 服务端调用服务发布者某个 HTTP 接口来完成健康检查。
    • 对于没有提供 HTTP 服务的 RPC 应用,服务端调用服务发布者的接口来完成健康检查。
    • 可以通过执行某个脚本的形式来进行综合检查。

    服务端主动探测也存在问题。服务注册中心主动调用 RPC 服务的某个接口无法做到通用性;在很多场景下服务注册中心到服务发布者的网络是不通的,服务端无法主动发起健康检查。

    所以如何取舍,还是需要根据实际情况来决定,根据不同的场景,选择不同的策略。

    服务发现

    怎么找到服务发现服务端的地址?

    • 在应用的配置文件中指定服务注册中心的地址,类似于 zookeeper 和 eureka。
    • 指定一个地址服务器的地址,然后通过这个地址服务器来获取服务注册中心的地址,地址服务器返回的结果会随着服务注册中心的扩缩容及时更新。

    当服务有节点退出或新的节点加入时,订阅者如何及时收到通知 ?

    很经典的 Push 和 Pull 问题。

    Push 的经典实现有两种,基于 socket 长连接的 notify,典型的实现如 zookeeper;另一种为 HTTP 连接所使用 Long Polling。

    但是基于 socket 长连接的 notify 和基于 HTTP 协议的 Long Polling 都会存在notify消息丢失的问题。

    所以通过 Pull 的方式定时轮询也必不可少,时间间隔的选择也很关键,频率越高服务注册中心所承受的压力也越大。需要结合服务端的性能和业务的规模进行权衡。

    还有一种方式,真实的 Push,客户端开启一个 UDP server,服务注册中心通过 UDP 的方式进行数据推送,当然这个也受限于网络的连通性。

    我能方便地查看我发布和订阅了哪些服务,订阅的服务有哪些节点吗 ?

    • 一个好的产品,用户使用体验和运维体验必须是优雅的,如果查看本机发布和订阅的服务,只能通过查看日志,甚至是 jmap 的方式来获取,显然体验非常糟糕。
    • 服务注册中心应该提供了丰富的接口,支持根据应用名、IP、订阅服务名、发布服务名,来进行多层次的组合查询。
    • 同时,客户端的内存里,同样也应该保留服务发布与订阅的各种信息,并提供方式供人方便地查询。
    • 比如在 Java 中的 Spring Boot 的应用,可以结合 actuator endpoint,通过 HTTP 的方式来提供本机服务查询功能,查询此应用发布的服务,以及订阅的服务及各服务的对应节点。

    容灾和高可用

    性能如何

    当服务节点数越来越多时,服务注册中心的性能会成为瓶颈,这时候就需要通过水平扩容来提升服务注册中心集群的性能。

    • 对于那些采用了类 Paxos 协议的强一致性的组件,如ZooKeeper,由于每次写操作需要过半的节点确认。水平扩容不能提升整个集群的写性能,只能提升整个集群的读性能。
    • 而对于采用最终一致性的组件来说,水平扩容可以同时提升整个集群的写性能和读性能。

    客户端容灾策略

    1. 首先,本地内存缓存,当运行时与服务注册中心的连接丢失或服务注册中心完全宕机,仍能正常地调用服务。
    2. 然后,本地缓存文件,当应用与服务注册中心发生网络分区或服务注册中心完全宕机后,应用进行了重启操作,内存里没有数据,此时应用可以通过读取本地缓存文件的数据来获取到最后一次订阅到的内容。
    3. 最后,本地容灾文件夹。正常的情况下,容灾文件夹内是没有内容的。当服务端完全宕机且长时间不能恢复,同时服务提供者又发生了很大的变更时,可以通过在容灾文件夹内添加文件的方式来开启本地容灾。此时客户端会忽略原有的本地缓存文件,只从本地容灾文件中读取配置。

    服务端容灾与高可用

    • 当有新节点加入集群时,节点启动后能自动添加到地址服务器中,并通过地址服务器找到其他节点,自动从其他节点同步数据,以达到数据的最终一致性。
    • 当某个节点宕机时,此服务注册中心节点的信息会自动地址服务器中摘除,客户端能及时感知到此节点已下线。

    服务端的无状态性保证了服务的容灾和高可用可以做的很薄。

    服务端安全是如何做的 ?

    链路安全,对于使用 HTTP 连接的服务注册中心,保护链路安全的最好方式是使用 HTTPS。而使用 TCP 连接的服务注册中心来说,由于应用层协议一般使用的是私有协议,不一定存在现成的 TLS 支持方案。

    在业务安全方面,应该在每一次的发布、订阅、心跳,都带上鉴权的信息就行验签和鉴权,确保业务信息的安全性。

    Alibaba Naming Service

    ANS (Alibaba Naming Service) 是阿里巴巴中间件团队将多年业务实践沉淀打磨的开源产品。在服务注册与发现方面,ANS 综合了上述解决方案中的优点,是最适合云原生应用的服务注册与发现组件。 
    ANS 服务已经在 EDAS(阿里巴巴企业级分布式应用服务) 上线,目前已经提供 Spring Cloud Ans Starter 方便 Spring Cloud 用户直接使用一个安全的可靠的商业版服务注册与发现功能。ANS 能完美地支持 Eureka 的特性,而且目前完全免费!更多信息参见 EDAS 帮助文档

    原文链接

    展开全文
  • 文章目录(1)前言(2) Alibaba Nacos基本介绍(3)基本使用(4)Nacos的高可用部署(5)Nacos实现原理分析(6) 注册中心的原理 ...为了解决这类的问题,就需要引入服务注册中心.主要有以下功能: 1、服务地址的管理。
  • 使用用eureka做redisProxy服务注册与发现中心使得分布式扩展部署更加 简单,配置更加简洁。 Features 框架特性 支持redis通信协议,原有的程序无须改变。 使用eureka,ribbin组件,使得redisProxy自动注册发现与负载 ...
  • NodeJs服务注册与服务发现实现

    千次阅读 2019-01-09 12:38:04
    前言 由于作者才刚开始学习NodeJs,水平实在有限,本文更像是一篇学习笔记,适合同刚开始学习...服务注册与服务发现就是这其中一种机制,大概的流程为: 其中: 注册中心:zookeeper 服务提供者:NodeJs应用服...
  • 虽然Spring Cloud Alibaba还没能纳入Spring Cloud的主版本管理中,但是凭借阿里中间件团队的背景,还是得到不少团队的支持;同时,由于Spring Cloud Alibaba中的几项主要功能都直指Netflix OSS中的重要组件,而后者...
  • 微服务 微服务按照我个人的理解就是将众多的功能拆分成一个个子服务,其中以现在...关于服务注册与发现,这里有一篇文章讲的特别好,也是我写这边博文的原因,我们在做微服务的时候,很多时候,不能说仅仅停留在用...
  • 使用Nacos实现服务注册与发现

    千次阅读 2019-01-18 10:05:46
    虽然Spring Cloud Alibaba还没能纳入Spring Cloud的主版本管理中,但是凭借阿里中间件团队的背景,还是得到不少团队的支持;同时,由于Spring Cloud Alibaba中的几项主要功能都直指Netflix OSS中的重要组件,而后者...
  • 内容提要:《可伸缩服务架构:框架与中间件》以高可用服务架构为主题,侧重于讲解高可用架构设计的核心要点:可伸缩和可扩展,从应用层、数据库、缓存、消息队列、大数据查询系统、分布式定时任务调度系统、微服务等...
  • 导读:本文主要介绍如何使用 Golang 生态中的微服务框架 Go-Micro(v2) 集成 Nacos 进行服务注册与发现。(Go-Micro 目前已经是 v3 版本,但由于某些原因项...
  • 导读:本文主要介绍如何使用 Golang 生态中的微服务框架 Go-Micro(v2) 集成 Nacos 进行服务注册与发现。(Go-Micro 目前已经是 v3 版本,但由于某些原因项目已经更名为 nitro 具体原因大家可以去 github 中查看。) ...
  • Nacos服务注册与发现、配置管理

    千次阅读 2020-07-05 23:10:59
    启动后可访问:http://localhost:8848/nacos 账号密码都是nacos 二、服务注册与发现 1. maven项目添加依赖 <dependency> <groupId>...
  • 分布式系统之服务中间件dubbox

    千次阅读 2016-11-27 16:57:33
    随着互联网的发展,网站应用的规模不断的扩大,常规的垂直应用架构已无法应对,分布式服务架构势在必行,在分布式服务架构中,资源调度和治理中心(SOA)是关键.而Dubbox就是资源调度和治理中心的管理工具. Dubbox是当当网...
  • 本文涉及到的内容: (1)架构中引入服务框架的概念 (2)服务框架的设计实现 ...本文思路来源于《大型网站系统Java中间件实践》一书。 1. 架构中引入服务框架的概念在最初的网站中,业务都是直接访
  • 中间件系列】Nacos注册中心妙用

    千次阅读 2020-06-15 12:26:34
    ​ 不知道有没有考虑过这样一个问题,为什么要注册中心呢?以及注册中心该如何的选择呢?还是默认的哪个是最新的,哪个用的人多,就选用哪个呢? ​ 服务之间的互相调用时,需要服务端开启服务,客户端进行对接。...
  • 二.SpringCloudAlibaba极简入门-服务注册与发现Nacos

    千次阅读 多人点赞 2020-04-16 23:05:07
    比如在服务注册与发现、客户端负载均衡等方面都做了很好的抽象,而上层应用方面依赖的都是这些抽象接口,而非针对某个具体中间件的实现。所以,在Spring Cloud中,我们可以很方便的去切换服务治理方面的中间件
  • 今天要给大家介绍的阿里巴巴中间件团队出品的Nacos来作为新一代的服务管理中间件。 首先学习Nacos之前,我们应该看看Nacos的官网,对它有一个初步的认识。 1. Nacos 官网 (https://nacos.io) 2 Nacos简介 ...
  • Eureka服务注册中心---SpringCloud

    万次阅读 2021-01-15 15:43:32
    Eureka是基于REST的服务,用于定位服务,以实现云端中间件层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用...
  • 服务中间件Dubbo的来历

    千次阅读 2016-09-02 08:51:19
    随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,需要一个治理系统确保架构有条不紊的演进。 单一应用架构  当网站流量很小时,只需一个...
  • 复杂简单并存 到底是复杂好还是简单好,这是一个没有答案的问题,也是一个哲学问题。见仁见智啦。 事物整体肯定是向复杂化方向发展,但是向人们呈现时应尽量简单化。用一句话来说就是:功能复杂化,使用简单化...
  • 消息中间件NSQ深入实践

    千次阅读 2017-11-21 09:05:00
    消息中间件NSQ深入实践 1. 介绍 最近在研究一些消息中间件,常用的MQ如RabbitMQ、ActiveMQ、Kafka等。NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,由bitly公司开源出来的一款简单...
  • Django中间件

    千次阅读 2016-07-26 15:39:18
    这类功能可以用Django的中间件框架来实现,该框架由切入到Django的request/response处理过程中的钩子集合组成。这个轻量级低层次的plug-in系统,能用于全面的修改Django的输入和输出。 每个中间件组件都用
  • 1.3 Spring Cloud 与中间件 1.3.1 什么是中间件 近年来,越来越多的领域已经离不开计算机、网络技术以及通用技术了。并且随着计算机技术的迅猛发展,更多的软件被要求在很多不同的网络协议、不同的硬件生产厂商以及...
  • 工具与中间件: Docker

    2019-10-12 10:37:55
    ustc docker mirror的优势之一就是不需要注册,是真正的公共服务。 [https://lug.ustc.edu.cn/wiki/mirrors/help/docker](https://lug.ustc.edu.cn/wiki/mirrors/help/docker) 编辑该文件: vi /etc/docker/...
  • 分布式中间件实践之路

    千次阅读 多人点赞 2018-11-06 11:49:02
    本课程是作者从事中间件研发的经验总结,来自实践,服务于实践,课程主要包括分布式缓存、分布式锁、分布式消息队列三大部分内容,涉及 Redis、Etcd、Kafka、RocketMQ 等众多主流开源软件的使用方案。 课程注重理论...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,772
精华内容 13,508
关键字:

服务注册与发现中间件