详解租约机制以及在hbase中的应用

详解租约机制以及在hbase中的应用

为什么需要Lease

分布式系统中为什么需要租约机制,这是因为在分布式系统,为了保证服务的高可用,需要在服务发生故障的时候及时启动另外一个服务实例以替换故障服务。这样就需要在服务端和客户端或者服务端和控制中心维持一个心跳信息,用于服务进程向控制中心汇报当前自己的健康情况,如果控制中心在一段时间收不到服务进程上报的心跳,则会启动新的进程继续对外提供服务。

但是,由于实际网络情况的复杂性,控制中心无法收到心跳时不能准确地判断究竟是服务故障了还是服务进程和控制中心之间的网络发生了故障。这种情况下控制中心冒然地启用新进程有可能会造成“双主”这种情况出现。

为避免上述情况的发生引入了租约机制,此时服务节点持续向控制中心申请短时间租约,控制中心在已派发的租约过期之前,不会启用新服务节点,而服务节点租约过期时若还无法从控制中心申请到新租约,自己中断客户链接。

此外,租约机制还可用于客户端和服务端之间的解藕,避免客户端进程失去响应时,其占用的服务端资源长期得不到释放进而影响到服务端的稳定。

Lease的实现

在实际系统中,如果依赖一个中心结点向外发布lease存在很大的风险,那就是如果该中心结点发生宕机或者网络故障,那么服务节点由于接收不到新的租约那么会导致整个服务集群进入不可用状态。因此,在实际使用中,对外提供lease服务的往往是由多个进程实例组成的另外一套集群,该集群具有高可用性,可以对外提供lease服务,比如zookeeper集群。

HRegionServer的租约Lease管理

租约线程的初始化

在HRegionServer的run主循环里会调用preRegistrationInitialization预先初始化一些线程,包括初始化集群连接信息setupClusterConnection()、healthCheckChore、pauseMonitor、initializeZookeeper以及initializeThreads()。

其中在initializeThreads()中会初始化各类线程,这些线程包括了这台regionServer的lease线程:

this.compactionChecker = new CompactionChecker(this, this.threadWakeFrequency, this); //检查合并请求
this.periodicFlusher = new PeriodicMemstoreFlusher(this.threadWakeFrequency, this);  //周期性地检查memstore的flush请求
this.leases = new Leases(this.threadWakeFrequency); 

Leases类的定义如下,它继承了HasThread这个抽象类,并定义了如下几个主要的成员变量:

public static final int MIN_WAIT_TIME = 100;
private final Map<String, Lease> leases = new ConcurrentHashMap<String, Lease>(); 

protected final int leaseCheckFrequency;
protected volatile boolean stopRequested = false;

其中Map型成员变量leases负责管理该regionserver进程中的lease实例,我们看看lease类都定义了哪些变量:

private final String leaseName;
private final LeaseListener listener;
private int leaseTimeoutPeriod;
private long expirationTime;

leaseTimeoutPeriod是租约时间,expirationTime会在lease被创建时被置位为系统时间与leaseTimeoutPeriod之和,用于周期性地计算该租约已经被使用多长时间,如果租约已经超过了leaseTimeoutPeriod定义的到期时间,则会触发一个expired事件,LeaseListener会监听该事件并调用leaseExpired方法,不同类型的lease都会继承LeaseListener接口并实现自己的leaseExpired方法,如下所示是scan lease对该方法的实现:

@Override
public void leaseExpired() {    //处理租约过期
  RegionScannerHolder rsh = scanners.remove(this.scannerName);
  if (rsh != null) {
   RegionScanner s = rsh.s;
   LOG.info("Scanner " + this.scannerName + " lease expired on region "
     + s.getRegionInfo().getRegionNameAsString());
   try {
    Region region = regionServer.getRegion(s.getRegionInfo().getRegionName());
    if (region != null && region.getCoprocessorHost() != null) {
     region.getCoprocessorHost().preScannerClose(s);
    } 

    s.close();
    if (region != null && region.getCoprocessorHost() != null) {
     region.getCoprocessorHost().postScannerClose(s);
    }
   } catch (IOException e) {
    LOG.error("Closing scanner for "
     + s.getRegionInfo().getRegionNameAsString(), e);
   }
  } else {
   LOG.warn("Scanner " + this.scannerName + " lease expired, but no related" +
    " scanner found, hence no chance to close that related scanner!");
  }
 }

客户端的scan请求是分解成多次RPC请求发到服务端的,分解的次数是scan的总数据量与客户端setCache两者的比值。每个scan请求发到服务端后会租用一个scanner,用于当前的scan结束后,后续的scan可以直接复用已有的资源,但是为防止scanner长期占用服务端资源,通过租约管理,关闭不再使用的scanner。

OK,回到前面的Leases类,看看它是如何管理regionServer进程中的各个lease的,这部分逻辑在它覆写的run方法中:

public void run() {
  long toWait = leaseCheckFrequency;
  Lease nextLease = null;
  long nextLeaseDelay = Long.MAX_VALUE; 

  while (!stopRequested || (stopRequested && !leases.isEmpty()) ) { 

   //睡眠一段时间 

   nextLease = null;
   nextLeaseDelay = Long.MAX_VALUE;
   for (Iterator<Map.Entry<String, Lease>> it = leases.entrySet().iterator(); it.hasNext();) {
    Map.Entry<String, Lease> entry = it.next();
    Lease lease = entry.getValue();
    long thisLeaseDelay = lease.getDelay(TimeUnit.MILLISECONDS);
    if ( thisLeaseDelay > 0) {
     if (nextLease == null || thisLeaseDelay < nextLeaseDelay) {
      nextLease = lease;
      nextLeaseDelay = thisLeaseDelay;
     }
    } else {
     // A lease expired. Run the expired code before removing from map
     // since its presence in map is used to see if lease exists still.
     if (lease.getListener() == null) {
      LOG.error("lease listener is null for lease " + lease.getLeaseName());
     } else {
      lease.getListener().leaseExpired();
     }
     it.remove();
    }
   }
  }
  close();
 }

我们省略掉一些异常处理,在while的循环周期中会逐一便利map中管理的lease,计算每个lease的thisLeaseDelay以检查改lease是否已经过期。判断lease是否过期的方法很简单,就是取出当前时间与lease中定义的expirationTime做差,如果差值小于0,则说明该租约已经到期,则调用lease中定义的leaseExpired方法,这与上面我们讲过的关联上了。其中thisLeaseDelay决定了下一次的lease检查在多久之后发生,thisLeaseDelay的计算依据是选择选取所有未过期lease中leaseDelay最短的,通过thisLeaseDelay计算toWait时间,用于决定前面的睡眠时间。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

时间: 2017-02-17

【Java】BigDecimal实现加减乘除运算代码

java.math.BigDecimal BigDecimal一共有4个够造方法,让我先来看看其中的两种用法: 第一种:BigDecimal(double val) Translates a double into a BigDecimal. 第二种:BigDecimal(String val) Translates the String repre sentation of a BigDecimal into a BigDecimal. 使用BigDecimal要用String来够造,要做一个

Java 图片与byte数组互相转换实例

实例如下: //图片到byte数组 public byte[] image2byte(String path){ byte[] data = null; FileImageInputStream input = null; try { input = new FileImageInputStream(new File(path)); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buf = new byte[

JavaScript中无法通过div.style.left获取值的解决方法

一.问题总结: 样式必须直接写在元素内部才能通过div.style.left直接获取属性值(也就是必须是内联样式才行),定义在css中的样式不能通过这种方式获取. 让元素移动到200停止 setTimeout ( function () { var div = document.getElementById("div4"); //var left = parseInt(div.style.left) + 5; var left = div.offsetLeft + 5; div.sty

Java使用SFTP上传文件到服务器的简单使用

最近用到SFTP上传文件查找了一些资料后自己做了一点总结,方便以后的查询.具体代码如下所示: /** * 将文件上传到服务器 * * @param filePath * 文件路径 * @param channelSftp * channelSftp对象 * @return */ public static boolean uploadFile(String filePath, ChannelSftp channelSftp) { OutputStream outstream = null; In

java 线程同步详细介绍及实例代码

java 线程同步 概要: 为了加快代码的运行速度,我们采用了多线程的方法.并行的执行确实让代码变得更加高效,但随之而来的问题是,有很多个线程在程序中同时运行,如果它们同时的去修改一个对象,很可能会造成讹误的情况,这个时候我们需要用一种同步的机制来管理这些线程. (一)竞争条件 记得操作系统中,让我印象很深的有一张图.上面画的是一块块进程,在这些进程里面分了几个线程,所有这些线程齐刷刷统一的指向进程的资源.Java中也是如此,资源会在线程间共享而不是每个线程都有一份独立的资源.在这种共享的情况下

JavaScript实现的鼠标响应颜色渐变效果完整实例

本文实例讲述了JavaScript实现的鼠标响应颜色渐变效果.分享给大家供大家参考,具体如下: 运行效果图如下: 完整代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/199

java中@ModelAttribute注解的作用

问题描述:当我们使用表单传值得时候,如果定义了,name,address,age.等属性,那么通过表单传过去的值就只有这3个属性,但是我们需要的name,pass,address,age等四个属性.那么在提交表单并赋值的时候就会出现,未得到的值为null的情况.如下图: 获取的pass为null 那么我们需要用@ModelAttribute注解来解决他: 实现代码如下: @ModelAttribute public void getUsers(@RequestParam(value="name&

java 中@Deprecated 注解的实例详解

java 中@Deprecated 注解的实例详解 1 简介 Deprecated 同 SuppressWarnings 一样,都是 J2SE 5.0 中定义在Java.lang包中的标准 Annotation 之一,其可以标注在类.字段和方法上,其作用为:不鼓励程序员使用被 @Deprecated 注释的程序元素,因为被 @Deprecated 注释的元素很危险(例如,现阶段 JDK 提供的带有 @Deprecated 注释的元素在以后的 JDK 版本中可能被删除)或存在更好的选择.在使用不被

Java中自定义注解介绍与使用场景详解

注解的概念及分类 1.首先我们来看一下什么是注解: 注解就是某种注解类型的一个实例,我们可以用它在某个类上进行标注,这样编译器在编译我们的文件时,会根据我们自己设定的方法来编译类. 2.注解的分类 注解大体上分为三种:标记注解,一般注解,元注解,@Override用于标识,该方法是继承自超类的.这样,当超类的方法修改后,实现类就可以直接看到了.而@Deprecated注解,则是标识当前方法或者类已经不推荐使用,如果用户还是要使用,会生成编译的警告. 本文主要介绍的是关于Java自定义注解,下面话

Java中自定义注解类及使用实例解析

这篇文章主要介绍了Java中自定义注解类并使用过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在Java框架中,经常会使用注解,而且还可以省很多事,来了解下自定义注解. 注解是一种能被添加到java代码中的元数据,类.方法.变量.参数和包都可以用注解来修饰.注解对于它所修饰的代码并没有直接的影响 先写一个自己的注解类 @Documented //会被javadoc命令识别 @Retention(RetentionPolicy.RUNTI

浅谈Spring中@Import注解的作用和使用

@Import用来导入@Configuration注解的配置类.声明@Bean注解的bean方法.导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类. @Import注解的作用 查看Import注解源码 /** * Indicates one or more {@link Configuration @Configuration} classes to import. * * <p>Provides functionality eq

谈谈Java中自定义注解及使用场景

Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅.本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP. 一. 什么是注解(Annotation) Java注解是什么,以下是引用自维基百科的内容 Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据. Java语言中的类.方法.变量.参数和包等都可以被标注.和Javadoc不同,Java标注可以通过反

全面了解Java中Native关键字的作用

初次遇见 native是在 java.lang.Object 源码中的一个hashCode方法: public native int hashCode(); 为什么有个native呢?这是我所要学习的地方.所以下面想要总结下native. 一.认识 native 即 JNI,Java Native Interface 凡是一种语言,都希望是纯.比如解决某一个方案都喜欢就单单这个语言来写即可.Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Ja

java 中MyBatis注解映射的实例详解

java  中MyBatis注解映射的实例详解 1.普通映射 @Select("select * from mybatis_Student where id=#{id}") public Student getStudent(int id); @Insert("insert into mybatis_Student (name, age, remark, pic,grade_id,address_id) values (#{name},#{age},#{remark}, #{

Java中volatile关键字的作用与用法详解

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile 关键字作用是,使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存. volatile 2个使用场景: 1.可见性:Java提供了volatile关键字来保证可见性. 当一个共享变量被volatile修饰时,它会保证修

Java 中的注解详解及示例代码

在Java中,注解(Annotation)引入始于Java5,用来描述Java代码的元信息,通常情况下注解不会直接影响代码的执行,尽管有些注解可以用来做到影响代码执行. 注解可以做什么 Java中的注解通常扮演以下角色 编译器指令 构建时指令 运行时指令 其中 Java内置了三种编译器指令,本文后面部分会重点介绍 Java注解可以应用在构建时,即当你构建你的项目时.构建过程包括生成源码,编译源码,生成xml文件,打包编译的源码和文件到JAR包等.软件的构建通常使用诸如Apache Ant和Mav