研究之dubbo-registry模块

来源:互联网 发布:js string长度 编辑:程序博客网 时间:2024/05/21 16:54
  dubbo-registry注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。 
   registry模块顶层接口为RegistryService和NotifyListener以及一个工厂接口RegistryFactory。 
   RegistryService接口包含4个方法。 
   void register(URL url); 注册服务 
   void unregister(URL url); 取消注册 
   void subscribe(URL url, NotifyListener listener); 订阅服务(推) 
   void unsubscribe(URL url, NotifyListener listener);取消订阅 
   List<URL> lookup(URL url); 订阅服务(拉) 
   
  Registry模块使用经典的消费者生成者模式,dubbo消费者订阅服务,dubbo服务者注册服务。 
  AbstractRegistry为RegistryService接口实现的一个抽象类,提供一些默认实现。 
 
Java代码  收藏代码
  1. public AbstractRegistry(URL url) {  
  2.     setUrl(url);  
  3.     // 启动文件保存定时器  
  4.     syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);  
  5.     String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");  
  6.     File file = null;  
  7.     if (ConfigUtils.isNotEmpty(filename)) {  
  8.         file = new File(filename);  
  9.         if(! file.exists() && file.getParentFile() != null && ! file.getParentFile().exists()){  
  10.             if(! file.getParentFile().mkdirs()){  
  11.                 throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");  
  12.             }  
  13.         }  
  14.     }  
  15.     this.file = file;  
  16.     loadProperties();  
  17.     notify(url.getBackupUrls());  
  18. }  

   可以看出AbstractRegistry类在构建的时候,会去本地磁盘文件读取文件,转成properties对象。文件默认路径是user.home/.dubbo/dubbo-registry-host.cache 
   读取完之后会把这个url存到本地缓存文件里面去,但是该方法并非递增修改,而且是全部重写文件。 
 
Java代码  收藏代码
  1. public void doSaveProperties(long version) {  
  2.      if(version < lastCacheChanged.get()){  
  3.          return;  
  4.      }  
  5.      if (file == null) {  
  6.          return;  
  7.      }  
  8.      Properties newProperties = new Properties();  
  9.      // 保存之前先读取一遍,防止多个注册中心之间冲突  
  10.      InputStream in = null;  
  11.      try {  
  12.          if (file.exists()) {  
  13.              in = new FileInputStream(file);  
  14.              newProperties.load(in);  
  15.          }  
  16.      } catch (Throwable e) {  
  17.          logger.warn("Failed to load registry store file, cause: " + e.getMessage(), e);  
  18.      } finally {  
  19.          if (in != null) {  
  20.              try {  
  21.                  in.close();  
  22.              } catch (IOException e) {  
  23.                  logger.warn(e.getMessage(), e);  
  24.              }  
  25.          }  
  26.      }       
  27.   // 保存  
  28.      try {  
  29. newProperties.putAll(properties);  
  30.          File lockfile = new File(file.getAbsolutePath() + ".lock");  
  31.          if (!lockfile.exists()) {  
  32.             lockfile.createNewFile();  
  33.          }  
  34.          RandomAccessFile raf = new RandomAccessFile(lockfile, "rw");  
  35.          try {  
  36.              FileChannel channel = raf.getChannel();  
  37.              try {  
  38.                  FileLock lock = channel.tryLock();  
  39.                 if (lock == null) {  
  40.                      throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");  
  41.                  }  
  42.                 // 保存  
  43.                  try {  
  44.                     if (! file.exists()) {  
  45.                          file.createNewFile();  
  46.                      }  
  47.                      FileOutputStream outputFile = new FileOutputStream(file);    
  48.                      try {  
  49.                          newProperties.store(outputFile, "Dubbo Registry Cache");  
  50.                      } finally {  
  51.                         outputFile.close();  
  52.                      }  
  53.                  } finally {  
  54.                     lock.release();  
  55.                  }  
  56.              } finally {  
  57.                  channel.close();  
  58.              }  
  59.          } finally {  
  60.              raf.close();  
  61.          }  
  62.      } catch (Throwable e) {  
  63.          if (version < lastCacheChanged.get()) {  
  64.              return;  
  65.          } else {  
  66.              registryCacheExecutor.execute(new SaveProperties(lastCacheChanged.incrementAndGet()));  
  67.          }  
  68.          logger.warn("Failed to save registry store file, cause: " + e.getMessage(), e);  
  69.      }  
  70.  }  

  如果是两个不同的注册中心,不能使用一个缓存文件。 
  值得注意一点的是,多个dubbo服务公用一个cache文件的时候,如果一起启动,可能会出现Failed to load registry store File 这类异常信息,这是因为当时该文件其他服务占用了。这种情况开发环境比较多,可以无视掉。因为会重复去注册的,下面会说明,但是尽量还是手动指定注册缓存文件比较好。尽量不使用同一个缓存文件。 
 
Java代码  收藏代码
  1. <dubbo:registry address="${zookeeper.address}" file=".cache/dubbo-registry-car.cache" />  

这样指定的话,缓存文件会在当前工程目录下。 
RegistryService接口的所有方法参数都有URL对象,说明dubbo服务最终是转成URL来注册的。通过URL来表示服务的全部信息。 
FailbackRegistry是AbstractRegistry类的子类,通过模式设计模式扩展,重写了AbstractRegistry类一些实现,并新增模板方法,交个子类去实现。 
Java代码  收藏代码
  1. public FailbackRegistry(URL url) {  
  2.       super(url);  
  3.       int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);  
  4.       this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {  
  5.           public void run() {  
  6.               // 检测并连接注册中心  
  7.               try {  
  8.                   retry();  
  9.               } catch (Throwable t) { // 防御性容错  
  10.                   logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);  
  11.               }  
  12.           }  
  13.       }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);  
  14.   }  

  FailbackRegistry构建的时候通过ScheduledExecutorService来延迟执行连接注册中心,默认延迟周期为5秒。 
  其中retry()方法从set集合里面取出注册失败的url,然后不断去重新注册。如果注册中心当机了,但是只要注册中心重新启动之后,dubbo还是会去重新注册的。 
 
Java代码  收藏代码
  1. @Override  
  2.  public void register(URL url) {  
  3.      super.register(url);  
  4.      failedRegistered.remove(url);  
  5.      failedUnregistered.remove(url);  
  6.      try {  
  7.          // 向服务器端发送注册请求  
  8.          doRegister(url);  
  9.      } catch (Exception e) {  
  10.          Throwable t = e;  
  11.   
  12.          // 如果开启了启动时检测,则直接抛出异常  
  13.          boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)  
  14.                  && url.getParameter(Constants.CHECK_KEY, true)  
  15.                  && ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());  
  16.          boolean skipFailback = t instanceof SkipFailbackWrapperException;  
  17.          if (check || skipFailback) {  
  18.              if(skipFailback) {  
  19.                  t = t.getCause();  
  20.              }  
  21.              throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);  
  22.          } else {  
  23.              logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);  
  24.          }  
  25.   
  26.          // 将失败的注册请求记录到失败列表,定时重试  
  27.          failedRegistered.add(url);  
  28.      }  
  29.  }  

  register方法把具体的注册实现交给子类的doRegister实现。注册失败的都会记录到失败的set集合里面去,值得注意一个check 配置,当设置<dubbo:registry check="false" />时,记录失败注册和订阅请求,后台定时重试。 
  zookeeper注册中心是dubbo推荐使用的注册中心,本身zookeeper的基于是Paxos,一个分布式一致性算法。 
  
 
  流程: 
1.服务提供者启动时向/dubbo/com.foo.BarService/providers目录下写入URL 
2.服务消费者启动时订阅/dubbo/com.foo.BarService/providers目录下的URL向/dubbo/com.foo.BarService/consumers目录下写入自己的URL 
3.监控中心启动时订阅/dubbo/com.foo.BarService目录下的所有提供者和消费者URL 
  dubbo如何通过注册中心来实现服务治理,则下篇文章博文单独讲解。