Hycz's Blog

Life is a game. Why so serious?

Cassandra 0.8.0 源码分析——Thrift相关

一、引子

这两天在写一个简单的cassandra测试程序,中间参考了test/distributed下的代码,不过其中用到的whirr实在蛋疼,首先这里用到的是whirr0.4版本,跟最新的0.7版本差别太大,其次是没有详细的文档说明如何在服务器端配置能够使用whirr进行集群管理,所以我就自己写了个简单的,把whirr的部分都砍掉了。

写这个简单的测试程序的过程中,出了些开始觉得诡异,后来觉得愚蠢的错误,不过也因此给我理由步进进去看了看源码,比如libthrift-0.6里的源码,还有自动生成的代码。

二、Introduction

首先推荐一个不错的thrift介绍,http://kingxss.iteye.com/blog/1413696

其他介绍的话就不说了,那篇博客里有写,这里就说下自己的感受。Thrift就是定义了一套自己的语法、传输协议,以此提供了与具体语言无关的一系列定义,在需要的时候翻译成相应的语言,同时提供了服务器客户端之间的底层通讯功能,于是可以让使用者专注于实现自己的功能

具体的表现,就是我接触到的Cassandra中thrift的使用,在cassandra.thrift文件中定义好数据结构struct,服务功能service,以及枚举enum、异常exception等部分后,使用thrift根据这个文件中的定义,自动生成大量的代码,在Cassandra中,就是interface/thrift/gen-java下的代码。

三、真的是生成代码?

开始我一直奇怪的就是,为何.thrift文件只有650+行,却生成了Cassandra.java这个有30000+行的庞然大物,而且.thrift文件中没有业务逻辑,仅仅是一些功能的名称。所以我下了个thrift的可执行程序(http://www.apache.org/dyn/closer.cgi?path=/thrift/0.8.0/thrift-0.8.0.exe)自己生成了一次。

首先我写了个简单的定义Hello.thrift,代码如下:

namespace java service.demo   
service Hello{   
 string helloString(1:string para)   
 i32 helloInt(1:i32 para)   
 bool helloBoolean(1:bool para)   
 void helloVoid()   
 string helloNull()   
}

执行命令thrift-0.8.0.exe -gen java Hello.thrift后,查看了一下生成代码的大小,真是吓人,仅仅几行的定义就生成了几千行的代码。

再看看cassandra.thrift,本身就有650+行,执行命令thrift-0.8.0.exe -gen java cassandra.thrift之后,确实生成了在原本项目中interface/thrift/gen-java目录下的代码。

四、生成了什么代码

刚才提到.thrift文件被叫做接口定义(interface definition),使用Thrift IDL(Thrift interface description language)(http://thrift.apache.org/docs/idl/)写成,这种文件一般包括了很多thrift types 和 services。services也是thrift types的一种,不过这里定义的services还需要由服务端实现,由客户端调用

所以,重要的就是理解.thrift文件中的各种项。官网上有2处提到了这些类型,一个是Thrift IDL(http://thrift.apache.org/docs/idl/),一个是Thrift Types(http://thrift.apache.org/docs/types/),我认为前者是完整的描述,后者则是其中常用的一些类型。

先说说下面列举一些常用的Type:

  • Base Types
    就是下面的7种基本类型,不用太多解释:

    • bool: A boolean value (true or false)
    • byte: An 8-bit signed integer
    • i16: A 16-bit signed integer
    • i32: A 32-bit signed integer
    • i64: A 64-bit signed integer
    • double: A 64-bit floating point number
    • string: A text string encoded using UTF-8 encoding
  • Special Types
    只有一种binary类型,当前只是string类型的一种特殊形式。

    • binary: a sequence of unencoded bytes
  • Structs
    定义了一个通用对象,类似于OOP中的Class,但是没有继承。一个Struct有一系列的拥有单独名称标识的强类型域(strongly typed fields)。在生成java代码时,每个Struct将单独生成一个类,由于定义时不会有方法,所以生成的Class代码中只有众多的存取方法。
  • Containers
    多种编程语言中常用的强类型容器,具体有3种:

    • list:元素的有序列表。会被翻译成STL vector,Java ArrayList,native arrays in scripting languages等等。
    • set:不同元素的无序列表。会被翻译成STL set, Java HashSet, set in Python等等。PHP中不支持set,所以会被看成语List相同。
    • map:严格不同的键值对map。可以被翻译成STL map, Java HashMap, PHP associative array, Python/Ruby dictionary等等。

    在实际使用中,容器常常作为field的类型使用。

  • Exceptions
    功能上Exception和Struct是一样的,不过Exception在翻译成目标语言的时候,都会继承相应的Exception类。生成Java代码时,也会生成一个单独的Exception文件。
  • Services
    定义一个service等同于在OOP中定义一个接口(或者纯虚拟抽象类)。Thrift编译器产生完整功能的,实现了这个接口的客户端和服务端stub。
    一个service有一系列命名了的functions组成,function有一系列参数和返回类型。

更加完整的定义还是得去看Thrift IDL,这里不细说了。

五、service生成代码的分析

interface/thrift/gen-java目录下,生成了很多的java文件,其中Cassandra.java是客户端的所在,其他则是一些数据结构的类、异常的类和枚举的类,所以这些闲散的类就没什么看头了,研究一下服务的类Cassandra.java。

我觉得Cassandra.java可以分为4个部分:接口,Client,Processor,辅助类

1、接口部分

接口部分有两个,IfaceAsyncIface,两者都是根据.thrift文件中的”service Cassandra{…}”这一项中的定义生成的,两个接口的不同之处是AsyncIface中的方法都比Iface中的多一个Call back的参数。

2、Client

Client就是实现了上述接口的类,因此同样有2个,一个是Client类,一个是AsyncClient类。Client类分为了4个部分,分别是:实例属性,Factory类,功能方法,sendrecv方法

2.1 实例属性

之所以把实例属性放在其他部分之前说,是由于实例属性体现了这是一个分布式程序,并且影响着其他部分。

实例属性只有3个:iprot_, oprot_, seqid。前2个都是org.apache.thrift.protocol.TProtocol类型的,可以理解为提供了分别设置输入协议对象和输出协议对象的能力,虽然使用时可以设置为一个。seqid则表示这客户端的编号,每次收到的消息都要比较seqid是否相同。

2.2 Factory类

Factory类就是提供了2个public级别的Client的构造器,2种构造器的参数列表略有不同,一个是只传入了一个协议对象,这将会把iprot_和oprot_设为相同对象;另一个则传入了2个协议对象,分别对应iprot_和oprot_

2.3 功能方法

这里的功能方法即为接口Iface中定义的方法,有趣的地方是所有的功能方法都有着统一的形式,即顺序执行send和recv方法。比如下面这个例子

public void add(ByteBuffer key, ColumnParent column_parent, CounterColumn column, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, org.apache.thrift.TException
    {
      send_add(key, column_parent, column, consistency_level);
      recv_add();
    }

2.4 sendrecv方法

这里是真正涉及到了分布式的地方,每一个功能方法都对应着一个send方法和recv方法。send方法写消息,用oprot_写,recv方法收消息,从iprot_中读。比如下面这个例子

    public void send_add(ByteBuffer key, ColumnParent column_parent, CounterColumn column, ConsistencyLevel consistency_level) throws org.apache.thrift.TException
    {
      oprot_.writeMessageBegin(new org.apache.thrift.protocol.TMessage("add", org.apache.thrift.protocol.TMessageType.CALL, ++seqid_));
      add_args args = new add_args();
      args.setKey(key);
      args.setColumn_parent(column_parent);
      args.setColumn(column);
      args.setConsistency_level(consistency_level);
      args.write(oprot_);
      oprot_.writeMessageEnd();
      oprot_.getTransport().flush();
    }

    public void recv_add() throws InvalidRequestException, UnavailableException, TimedOutException, org.apache.thrift.TException
    {
      org.apache.thrift.protocol.TMessage msg = iprot_.readMessageBegin();
      if (msg.type == org.apache.thrift.protocol.TMessageType.EXCEPTION) {
        org.apache.thrift.TApplicationException x = org.apache.thrift.TApplicationException.read(iprot_);
        iprot_.readMessageEnd();
        throw x;
      }
      if (msg.seqid != seqid_) {
        throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.BAD_SEQUENCE_ID, "add failed: out of sequence response");
      }
      add_result result = new add_result();
      result.read(iprot_);
      iprot_.readMessageEnd();
      if (result.ire != null) {
        throw result.ire;
      }
      if (result.ue != null) {
        throw result.ue;
      }
      if (result.te != null) {
        throw result.te;
      }
      return;
    }

这里就涉及到了Thrift中的Message的结构,见下一章。

3、Processor

Processor类实现了org.apache.thrift.TProcessor接口,这个类封装了各种从输入输出流中读写数据的操作,在Thrift架构中在传输之上,应用之下,连接了底层操作和用户功能实现。Processor类大致分为这么几个部分:构造器,process方法,具体功能类

3.1 构造器

构造这个类的实例时,需要传入Iface中定义的方法的具体实现,具体来说,就是一个实现了Iface接口的对象,然后构造器中新建了各个功能类的的实例放入processMap_。具体代码如下:

public static class Processor implements org.apache.thrift.TProcessor {
  private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class.getName());
  public Processor(Iface iface)
  {
    iface_ = iface;
    processMap_.put("login", new login());
    processMap_.put("set_keyspace", new set_keyspace());
    processMap_.put("get", new get());
    processMap_.put("get_slice", new get_slice());
    processMap_.put("get_count", new get_count());
    processMap_.put("multiget_slice", new multiget_slice());
    processMap_.put("multiget_count", new multiget_count());
    processMap_.put("get_range_slices", new get_range_slices());
    processMap_.put("get_indexed_slices", new get_indexed_slices());
    processMap_.put("insert", new insert());
    processMap_.put("add", new add());
    processMap_.put("remove", new remove());
    processMap_.put("remove_counter", new remove_counter());
    processMap_.put("batch_mutate", new batch_mutate());
    processMap_.put("truncate", new truncate());
    processMap_.put("describe_schema_versions", new describe_schema_versions());
    processMap_.put("describe_keyspaces", new describe_keyspaces());
    processMap_.put("describe_cluster_name", new describe_cluster_name());
    processMap_.put("describe_version", new describe_version());
    processMap_.put("describe_ring", new describe_ring());
    processMap_.put("describe_partitioner", new describe_partitioner());
    processMap_.put("describe_snitch", new describe_snitch());
    processMap_.put("describe_keyspace", new describe_keyspace());
    processMap_.put("describe_splits", new describe_splits());
    processMap_.put("system_add_column_family", new system_add_column_family());
    processMap_.put("system_drop_column_family", new system_drop_column_family());
    processMap_.put("system_add_keyspace", new system_add_keyspace());
    processMap_.put("system_drop_keyspace", new system_drop_keyspace());
    processMap_.put("system_update_keyspace", new system_update_keyspace());
    processMap_.put("system_update_column_family", new system_update_column_family());
    processMap_.put("execute_cql_query", new execute_cql_query());
  }
…
private Iface iface_;
protected final HashMap<String,ProcessFunction> processMap_ = new HashMap<String,ProcessFunction>();
…

3.2 process方法

这个process方法是使用Processor类进行处理时的入口,通过读入输入流中的Message,寻找是否有相应的功能类来处理,如果有,则转至相应功能类进行处理,否则,写回一个Exception。具体代码如下:

public boolean process(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException
{
  org.apache.thrift.protocol.TMessage msg = iprot.readMessageBegin();
  ProcessFunction fn = processMap_.get(msg.name);
  if (fn == null) {
    org.apache.thrift.protocol.TProtocolUtil.skip(iprot, org.apache.thrift.protocol.TType.STRUCT);
    iprot.readMessageEnd();
    org.apache.thrift.TApplicationException x = new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'");
    oprot.writeMessageBegin(new org.apache.thrift.protocol.TMessage(msg.name, org.apache.thrift.protocol.TMessageType.EXCEPTION, msg.seqid));
    x.write(oprot);
    oprot.writeMessageEnd();
    oprot.getTransport().flush();
    return true;
  }
  fn.process(msg.seqid, iprot, oprot);
  return true;
}

3.3 具体功能类

对已Iface中定义的每个方法,这里都建立了一个类来与其对应,这样的类都实现了processFunction接口,其中只实现了一个process方法,用来处理输入输出流部分的操作,比如读入输入参数,写回结果,写回异常等等当然,最后还是要使用构造时传入的具体处理功能实现。这里代码比较多,随便贴一个来看看:

    protected static interface ProcessFunction {
      public void process(int seqid, org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException;
    }
…

    private class get_slice implements ProcessFunction {
      public void process(int seqid, org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException
      {
        get_slice_args args = new get_slice_args();
        try {
          args.read(iprot);
        } catch (org.apache.thrift.protocol.TProtocolException e) {
          iprot.readMessageEnd();
          org.apache.thrift.TApplicationException x = new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.PROTOCOL_ERROR, e.getMessage());
          oprot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("get_slice", org.apache.thrift.protocol.TMessageType.EXCEPTION, seqid));
          x.write(oprot);
          oprot.writeMessageEnd();
          oprot.getTransport().flush();
          return;
        }
        iprot.readMessageEnd();
        get_slice_result result = new get_slice_result();
        try {
          result.success = iface_.get_slice(args.key, args.column_parent, args.predicate, args.consistency_level);
        } catch (InvalidRequestException ire) {
          result.ire = ire;
        } catch (UnavailableException ue) {
          result.ue = ue;
        } catch (TimedOutException te) {
          result.te = te;
        } catch (Throwable th) {
          LOGGER.error("Internal error processing get_slice", th);
          org.apache.thrift.TApplicationException x = new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.INTERNAL_ERROR, "Internal error processing get_slice");
          oprot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("get_slice", org.apache.thrift.protocol.TMessageType.EXCEPTION, seqid));
          x.write(oprot);
          oprot.writeMessageEnd();
          oprot.getTransport().flush();
          return;
        }
        oprot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("get_slice", org.apache.thrift.protocol.TMessageType.REPLY, seqid));
        result.write(oprot);
        oprot.writeMessageEnd();
        oprot.getTransport().flush();
      }

    }

4、辅助类

我所称的辅助类其实也跟所有的功能方法有关,每一个功能方法都有2个辅助类,分别是args和result。比如对于add功能,就有add_args和add_result两个辅助类。

args类是根据.thrift文件中定义的方法中的参数列表生成的,主要是对参数列表中的每一项建立各种方法:get、set、isSet、unset等等,还定义了一些特定类型的struct对象和field对象(这里见下一章),这里的代码比较冗长,主要是供Client中的send方法使用。

result类是根据.thrift文件中定义的方法返回值和异常列表生成的,注意这里如果有返回值的话,那么会储存在success属性中,主要是对返回值和异常建立各种方法:get、set、isSet、unset等等,还定义了一些特定类型的struct对象和field对象(这里见下一章),这个类主要供Client中的recv方法使用。

六、Thrift中的Message的结构

既然客户端和服务端之间的通讯的媒介是Message,那么我们就简单看下Message的结构是怎样的。

因为是生成代码,所以Cassandra.java中的代码结构就有迹可循。这一节我们先用一个例子来看一下消息是如何被一点点组织起来,完成后又被一点点读取出来的,这里用get_range_slice功能,然后总结一下Message的通用结构。

    public List<KeySlice> get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level) throws InvalidRequestException, UnavailableException, TimedOutException, org.apache.thrift.TException
    {
      send_get_range_slices(column_parent, predicate, range, consistency_level);
      return recv_get_range_slices();
    }
    public void send_get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level) throws org.apache.thrift.TException
    {
      oprot_.writeMessageBegin(new org.apache.thrift.protocol.TMessage("get_range_slices", org.apache.thrift.protocol.TMessageType.CALL, ++seqid_));
      get_range_slices_args args = new get_range_slices_args();
      args.setColumn_parent(column_parent);
      args.setPredicate(predicate);
      args.setRange(range);
      args.setConsistency_level(consistency_level);
      args.write(oprot_);
      oprot_.writeMessageEnd();
      oprot_.getTransport().flush();
    }

1、Message Begin和End

send方法中的第一句就是写入一个Message Begin。这里实际上给了我们2个信息,一个是这个Begin究竟包括了哪些内容,另一个是如何去写入一个Message Begin。

1.1 Message Begin包括的内容

且不论不同的协议会如何对待传入的参数,我们知道了这个Message Begin是一个org.apache.thrift.protocol.TMessage类型的。这里是这个类的构造器:

public TMessage(String n, byte t, int s) {
name = n;
type = t;
seqid = s;
}
public final String name;
public final byte type;
public final int seqid;

由此可见,一个Message应该包括的内容有3个:name,type,seqid

  • name:根据上面send方法中的设置来看,name被设置为功能名称,如果使用TMessage的无参数构造器的话,name会被设为空字符串””。
  • type:虽然这是一个byte类型的,但是实际上是从TMessageType中选择的,具体含义见源码,不过如果使用TMessage的无参数构造器的话,type会被置为TType.STOP。
    public final class TMessageType {
      public static final byte CALL  = 1;
      public static final byte REPLY = 2;
      public static final byte EXCEPTION = 3;
      public static final byte ONEWAY = 4;
    }
  • seqid:这个就是用来标记客户端的。

1.2 不同协议的write

语句中写的是oprot_.writeMessageBegin,所以注定不同的协议会有不用的写法。先看看0.6版本中有哪些协议:

  • TBinaryProtocol
  • TCompactProtocol
  • TJSONProtocol
  • TSimpleJSONProtocol

这些协议都继承了TProtocol这个抽象类。在源码提供的测试程序中使用的是TBinaryProtocol。

当所有的写入完成后,需要执行writeMessageEnd方法。TBinaryProtocol中这是一个空方法。TJSONProtocol中就不是空方法。

2、args写入

send方法中的第二部分就是把参数写入,这里就借助了args辅助类来执行这个操作,当然,具体执行的之后,实际上还是调用的oprot_中的write方法,具体如下:

    public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
      validate();

      oprot.writeStructBegin(STRUCT_DESC);
      if (this.column_parent != null) {
        oprot.writeFieldBegin(COLUMN_PARENT_FIELD_DESC);
        this.column_parent.write(oprot);
        oprot.writeFieldEnd();
      }
      if (this.predicate != null) {
        oprot.writeFieldBegin(PREDICATE_FIELD_DESC);
        this.predicate.write(oprot);
        oprot.writeFieldEnd();
      }
      if (this.range != null) {
        oprot.writeFieldBegin(RANGE_FIELD_DESC);
        this.range.write(oprot);
        oprot.writeFieldEnd();
      }
      if (this.consistency_level != null) {
        oprot.writeFieldBegin(CONSISTENCY_LEVEL_FIELD_DESC);
        oprot.writeI32(this.consistency_level.getValue());
        oprot.writeFieldEnd();
      }
      oprot.writeFieldStop();
      oprot.writeStructEnd();
    }

从这里已经可以看出Message中的大致结构了,依次是Message级,Struct级,Field级

3、Message结构

Message的结构有三级,依次是Message级,Struct级,Field级。这是Thrift中通用的逻辑结构,不同的协议实现的不同。大致的示意图如下:

Message Begin
Struct Begin
Field Begin
arg
Field End
Struct End
Message End

其中arg是包含特定数据的地方,比如String,Int,Map,List等等,这里的结构就不固定了,arg部分的写入一般是在args类中的write方法里完成的。

在Cassandra.java中,用的是TBinaryProtocol,Message结构中的很多部分没有用到,比如writeMessageEnd方法,writeStructBegin方法,writeStructEnd方法,writeFieldEnd方法都是空方法,而在Message Begin和Field Begin这两处,则写入了一些标识信息,例如Field Begin中就标识了arg中放入的数据的类型,如下:

Message Begin: name, type, seqid
Struct Begin: nothing
Field Begin: field type, field id
arg: blablabla
Field End: nothing
Struct End: nothing
Message End: nothing

TBinaryProtocol类中的具体代码如下:

  public void writeMessageBegin(TMessage message) throws TException {
    if (strictWrite_) {
      int version = VERSION_1 | message.type;
      writeI32(version);
      writeString(message.name);
      writeI32(message.seqid);
    } else {
      writeString(message.name);
      writeByte(message.type);
      writeI32(message.seqid);
    }
  }

  public void writeMessageEnd() {}

  public void writeStructBegin(TStruct struct) {}

  public void writeStructEnd() {}

  public void writeFieldBegin(TField field) throws TException {
    writeByte(field.type);
    writeI16(field.id);
  }

  public void writeFieldEnd() {}

  public void writeFieldStop() throws TException {
    writeByte(TType.STOP);
  }

所以Message大致就是这么回事,关于上面的代码还有几个需要解释的地方:

  • 几种type:之前提到过Message的type,这里又出现了Field的type,两者是不同的,如同字面上的意思,Message的type表示了整个Message的类型,而Field的type仅表示了这个Field的类型Message的type是在TMessageType类中定义,Field的type是在TType类中定义,具体如下:
    /**
    * Type constants in the Thrift protocol.
    */
    public final class TType {
      public static final byte STOP   = 0;
      public static final byte VOID   = 1;
      public static final byte BOOL   = 2;
      public static final byte BYTE   = 3;
      public static final byte DOUBLE = 4;
      public static final byte I16    = 6;
      public static final byte I32    = 8;
      public static final byte I64    = 10;
      public static final byte STRING = 11;
      public static final byte STRUCT = 12;
      public static final byte MAP    = 13;
      public static final byte SET    = 14;
      public static final byte LIST   = 15;
      public static final byte ENUM   = 16;
    }

    于是这里就列出了所有arg中可能出现的数据的类型,对于String及String之后的类型,都有专门的writeXXXBegin、writeXXXEnd、readXXXBegin和readXXXEnd方法。

  • 关于Field Stop:在白皮书(http://thrift.apache.org/static/files/thrift-20070401.pdf)中提到,“所有的write方法都有一个对应的read方法,除了writeFieldStop这个例外这是一个特殊的方法,用来标记一个Struct的结束”。

七、Thrift的结构和简单使用(Java版)

首先有两篇介绍Thrift的文章,跟之前的不同是没有讲代码,http://diwakergupta.github.com/thrift-missing-guide/#_versioning_compatibilityhttp://dongxicheng.org/search-engine/thrift-guide/ ,前者是鸟文的,后者是中文的,不过翻译了不少前者的内容。

1、Thrift网络栈

下面这个是我画的很丑的Thrift 网络栈:

/—————————————————————
|        Server                                         |
|        (Single-threaded, event-driven etc) |
|—————————————————————|
|        Processer                                     |
|        (compiler generated)                     |
|—————————————————————|
|        Protocol                                       |
|        (Binary, JSON, Compact etc)           |
|—————————————————————|
|        Transport                                     |
|        (raw TCP, HTTP etc)                     |
|—————————————————————/

之后的内容大部分摘自http://dongxicheng.org/search-engine/thrift-guide/

1.1 Transport

Transport层提供了一个简单的网络读写抽象层。这使得thrift底层的transport从系统其它部分(如:序列化/反序列化)解耦。以下是一些Transport接口提供的方法:

  • open
  • close
  • read
  • write
  • flush

除了以上几个接口,Thrift使用ServerTransport接口接受或者创建原始transport对象。正如名字暗示的那样,ServerTransport用在server端,为到来的连接创建Transport对象。

  • open
  • listen
  • accept
  • close

1.2 Protocol

Protocol抽象层定义了一种将内存中数据结构映射成可传输格式的机制。换句话说,Protocol定义了datatype怎样使用底层的Transport对自己进行编解码。因此,Protocol的实现要给出编码机制并负责对数据进行序列化。

Protocol接口的定义如下:

  • writeMessageBegin(name, type, seq)
  • writeMessageEnd()
  • writeStructBegin(name)
  • writeStructEnd()
  • writeFieldBegin(name, type, id)
  • writeFieldEnd()
  • writeFieldStop()
  • writeMapBegin(ktype, vtype, size)
  • writeMapEnd()
  • writeListBegin(etype, size)
  • writeListEnd()
  • writeSetBegin(etype, size)
  • writeSetEnd()
  • writeBool(bool)
  • writeByte(byte)
  • writeI16(i16)
  • writeI32(i32)
  • writeI64(i64)
  • writeDouble(double)
  • writeString(string)
  • name, type, seq = readMessageBegin()
  • readMessageEnd()
  • name = readStructBegin()
  • readStructEnd()
  • name, type, id = readFieldBegin()
  • readFieldEnd()
  • k, v, size = readMapBegin()
  • readMapEnd()
  • etype, size = readListBegin()
  • readListEnd()
  • etype, size = readSetBegin()
  • readSetEnd()
  • bool = readBool()
  • byte = readByte()
  • i16 = readI16()
  • i32 = readI32()
  • i64 = readI64()
  • double = readDouble()
  • string = readString()

下面是一些对大部分thrift支持的语言均可用的protocol:

  • binary:简单的二进制编码
  • Compact:具体见THRIFT-11
  • Json

1.3 Processor

Processor封装了从输入数据流中读数据和向输出数据流中写数据的操作。读写数据流用Protocol对象表示。Processor的结构体非常简单:

/**
 * A processor is a generic object which operates upon an input stream and
 * writes to some output stream.
 *
 */
public interface TProcessor {
  public boolean process(TProtocol in, TProtocol out)
    throws TException;
}

与服务相关的processor实现由编译器产生。Processor主要工作流程如下:从连接中读取数据(使用输入protocol),将处理授权给handler(由用户实现),最后将结果写到连接上(使用输出protocol)。

1.4 Server

Server将以上所有特性集成在一起:

  1. 创建一个transport对象
  2. 为transport对象创建输入输出protocol
  3. 基于输入输出protocol创建processor
  4. 等待连接请求并将之交给processor处理

2、Client 和 Server的编写

2.1 Client编写

好吧, 这个Client不是指Cassandra.java中的Client,而是指单独写的一个可以通过使用Cassandra.java来对服务器进行操作的客户端。其实只是一个简单的客户端的的话并不需要太多代码,这个是部分代码:

    public Cassandra.Client createClient(InetAddress addr) throws TException
    {
        TTransport transport    = new TSocket(
                                    addr.getHostAddress(),
                                    CLIENT_PORT,
                                    200000);
        transport               = new TFramedTransport(transport);
        TProtocol  protocol     = new TBinaryProtocol(transport);

        Cassandra.Client client = new Cassandra.Client(protocol);
        transport.open();

        return client;
    }

这里先建了个TSocket对象,然后以此为参数建立TTransport对象,再以此TTransport对象建立TProtocol对象,最后以此TProtocol对象建立客户端。

使用这个客户端也很方便,代码如下:

	public void testInsert() throws Exception
    {

        List hosts = controller.getHosts();
        final String keyspace = "TestInsert";
        addKeyspace(keyspace, 1);
        Cassandra.Client client = controller.createClient(hosts.get(0));
        try{
        	client.set_keyspace(keyspace);

	        ByteBuffer key = newKey();

	        insert(client, key, "Standard1", "c1", "v1", 0, ConsistencyLevel.ONE);
	        insert(client, key, "Standard1", "c2", "v2", 0, ConsistencyLevel.ONE);

	        // block until the column is available
	        new Get(client, "Standard1", key).name("c1").value("v1").perform(ConsistencyLevel.ONE);
	        new Get(client, "Standard1", key).name("c2").value("v2").perform(ConsistencyLevel.ONE);

	        List coscs = get_slice(client, key, "Standard1", ConsistencyLevel.ONE);
	        assertColumnEqual("c1", "v1", 0, coscs.get(0).column);
	        assertColumnEqual("c2", "v2", 0, coscs.get(1).column);
		} catch(Exception e){
			e.printStackTrace();
		} finally {
			client.send_system_drop_keyspace(keyspace);
		}
    }

2.2 Server编写

这一部分就不是用户写的了,而是属于Cassandra的一部分,org.apache.cassandra.thrift.CassandraServer就是这个server,这个类实现了Iface中定义的各种功能,其对象将会作为参数传给Processor,由于Processor中已经做了输入输出流的读取,因此CassandraServer类只需要专注于功能实现就行了,其实还是调用其他内部接口,比如下面这个方法:

    public List get_range_slices(ColumnParent column_parent, SlicePredicate predicate, KeyRange range, ConsistencyLevel consistency_level)
    throws InvalidRequestException, UnavailableException, TException, TimedOutException
    {
        logger.debug("range_slice");

        String keyspace = state().getKeyspace();
        state().hasColumnFamilyAccess(column_parent.column_family, Permission.READ);

        CFMetaData metadata = ThriftValidation.validateColumnFamily(keyspace, column_parent.column_family);
        ThriftValidation.validateColumnParent(metadata, column_parent);
        ThriftValidation.validatePredicate(metadata, column_parent, predicate);
        ThriftValidation.validateKeyRange(range);
        ThriftValidation.validateConsistencyLevel(keyspace, consistency_level);

        List rows;
        try
        {
            IPartitioner p = StorageService.getPartitioner();
            AbstractBounds bounds;
            if (range.start_key == null)
            {
                Token.TokenFactory tokenFactory = p.getTokenFactory();
                Token left = tokenFactory.fromString(range.start_token);
                Token right = tokenFactory.fromString(range.end_token);
                bounds = new Range(left, right);
            }
            else
            {
                bounds = new Bounds(p.getToken(range.start_key), p.getToken(range.end_key));
            }
            try
            {
                schedule();
                rows = StorageProxy.getRangeSlice(new RangeSliceCommand(keyspace, column_parent, predicate, bounds, range.count), consistency_level);
            }
            finally
            {
                release();
            }
            assert rows != null;
        }
        catch (TimeoutException e)
        {
        	throw new TimedOutException();
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }        return thriftifyKeySlices(rows, column_parent, predicate);
    }

这里在获得了key的bounds之后,尝试调度此线程获得资源,然后调用了StorageProxy.getRangeSlice方法。

八、总结

这篇水文从Cassandra的生成代码入手,简单介绍了.thrift文件,分析了生成代码的结构,Thrift中的Message结构,最后简单介绍了一下Thrift的结构和如何写代码去使用Thrift。

[转]PaxosLease

原文转自:http://www.cnblogs.com/chen77716/archive/2011/03/21/2130800.html

世上有很多选举算法,但最完美的莫过于Paxos,但把paxos用于master选举与用于value的选举有用众多不同之处,主要一点是二者执 行频率不同。value选举需要频繁、高密度地执行paxos算法,每instance选举一个value。因此,原生的paxos算法无法满足高性能的 要求;另一个问题是,paxos理论上存在活锁,lamprot推荐大家通过选举Leader避免这个问题。

所有的实现都指向一个Leader/Master,但Master的选举又该如何实现?如果继续采用paxos那性能和活锁又该如何解决?这个似乎 是个递归问题,用paxos解决paxos遇到的问题。能实现吗?能,Keyspace的PaxosLease算法给出了答案。

1. Lease

Master选举的过程是这样的:从众多的Node中选择一个作为Master,如果该Master一致alive则无需选举,如果master crash,则其他的node进行选举下一个master。选择正确性的保证是:任何时刻最多只能有一个master。

逻辑上Master更像一把无形的锁,任何一个节点拿到这个锁,都可成为master,所以本质上Master选举是个分布式锁的问题,但完全靠锁 来解决选举问题也是有风险的:如果一个Node拿到了锁,之后crash,那锁会导致无法释放,即死锁。一种可行的方案是给锁加个时间(Lease),拿 到锁的Master只能在Lease有效期内访问锁定的资源,在Lease timeout后,其他Node可以继续竞争锁,这就从根本上避免了死锁。

Master在拿到锁后,如果一直alive,可以向其他node”续租“锁,从而避免频繁的选举活动。关于Lease更多的请参考:http://research.microsoft.com/en-us/um/people/blampson/58-consensus/webpage.html

2. Master选举

Master选举与value选举存在几个不同的地方:

  • 执行频率低(正常情况下,Node失败概率不会太高)
  • 无Paxos的持久化,重启后状态丢失

为了保证选举正确性,还要保证:

  • 保证算法纯正性,不依赖与其他算法

3. PaxosLease

PaxosLease是Kespace开发的基于Paxos、Lease的Master选举算法,算法的角色与Paxos一样,也分Proposer、Acceptor、Learn,各角色的行为也与paxos基本一致,但除下面两个重要的区别:

  • 不要求acceptor持久化数据
  • 不存在Leader

PaxosLease把Paxos的accept阶段重新命名为propose,因此存在prepare、propose2个阶段。

再引入几个符号:

  • T:每个Master的Lease时间(秒)
  • M:全局Lease时间,要确保 M > T
  • ballot number:每次发送的proposal编号

算法过程

  1. proposer启动定时器Timer1,等待T秒便超时
  2. proposer向acceptor发送(ballot number,proposer id,T)
  3. acceptor接收到prepare消息后,判断:
    • 如果msg.ballot_number < local.promisedBallotNumber,则发送拒绝消息
    • 否则,发送accept,包含的内容已经promise的最大编号和T
  4. proposer接收acceptor的response
    • 如果多数派accept,则进入promise阶段
    • 否则,随机等待一段时间,提高编号重启prepare过程
  5. proposer执行promise阶段
    • 如果prepare阶段接收的value不为空,则终止promise
    • 否则,发送(ballot number,proposer id ,T)
  6. acceptor接收到promise请求
    • 如果msg.ballot_number < local.promisedBallotNumber,则回应拒绝消息
    • 否则,启动定时器Timer2,等待T秒超时
  7. proposer接收acceptor的response
    • 如果接收到多数派的回应
      • 删除Timer1
      • 启动extend Timer3,等待时间T1 < T
      • 发送Learn消息,转入8
    • 否则,重启prepare过程
  8.  Learn接收到proposer的learn消息
    • 停止Timer1
    • 重新启动Timer1

因为proposer、acceptor、learn三位一体,所以上述提到的Timer1、Timer2、Timer3其实位于同一Node,同一进程。

  • Timer1:等待时间T,超时便发起prepare请求
  • Timer2:等待时间T,超时便清空本次paxos instance状态,继续下一次
  • Timer3:等待时间T1 < T,超时便执行prepare

Timer1运行于所有的Node,任何一个Node的Timer1超时便发起prepare请求

Timer2仅运行与参与选举过程的Node,如果超期则清空本次选举状态

Timer3仅运行于获得lease的节点,目的是在lease超期之前续租

4. Paxos

移除lease相关部分,上述过程其实就是一次paxos instance,但第5步与原始paxos算法稍有不同:

  • 原始的paxos规定,如果接收的value v不为空,则使用v继续accept阶段,以保证每次选举仅选出一个决议;
  • PaxosLease选举的目的是使其他node接受自己作为master,当接收的value不为空时,自己应该退出选举,而不是继续提交其他Node的value。也就是说,只要当前node知道其他Node可能会优先自己成为master,则退出选举,成就他人。

因为每个节点都始终运行着Timer1,只要超时便开始prepare,因此paxosleas不存在静态死锁问题,但动态死该怎么解决?之前关于 paxos算法的介绍得知,在paxos中存在活锁,也即动态死锁,表现为在prepare阶段2个proposer会转而提出更高编号的 proposal,PaxosLease提供的解决办法就是在失败重新提交proposal时随机等待一段时间,因为各Node等待时间的不一致,只要运 行足够长的时间,活锁总能避免。

传统的Paxos是否可以采取这样的方法避免活锁?答案是否定的,之前说过,value选举的频率高、延迟低,不能容忍提交proposal时等待几秒。而Master选举因为频率低,却可以容忍上述方法带来的性能损失。

因为运行PaxosLease的所有节点都没有持久化,当Node重启时所有的状态都会丢失,这样重新加入的节点可能会扰乱前面的paxos过程, 因此强制Node在重新加入前必须等待M秒,因为M > T,所以只要等待M秒便可以越过本次paxos选举。其实即使等待M秒,也不能从理论上完全保证PaxosLease能正确执行,要搞清楚这个我问题,我 们必须知道新加入的Node是如何扰乱了paxos过程:

假如存在三个Acceptor A、B、C,已经promised的最高编号分别为A =(1),B =(1),C =(1);存在两个proposer P1,P2

  1. 初始状态A=(1)、B=(1)、C=N/A# P1 提交编号(3,v3)到A、B成功完成了prepare、accept阶段,编号3,v1被选为最终决议
  2. B宕机重启,状态丢失回到初始状态、A宕机、C重启,则A=N/A、B =(1)、C=(1)# P2提交编号(2,v2)到B、C也可成功完成preapare、accept阶段,编号2,v2被选为最终决议

存在v2、v3两个最终决议,违反了paxos的安全属性。如果B重启能记住之前的状态B=(3,v3),P2则不会提交成功,从而能保证paxos的安全属性。这个错误发生要几个条件:

  • 节点重启前作为多数派回应了prepaer、accept消息
  • 其他节点也重启,并与当前节点构成最少多数派
  • 之前的instance未结束,又回答了其他proposer的prepare、accept消息

反观M秒的设定,其实等待多少秒并不重要,重要的是重加入后不能立即参加paxos过程。在实际中因为选举的过程非常快,节点重加入的过程也可监控,这些理论上的错误是很难发生的。

Catch Up机制是否适用于PaxosLease?


在讨论paxos的实现时,曾提到如果节点磁盘失败,当重新加入时需要使用catch up,节点参与paxos过程,但不做任何回应,直至一次paxos过程完成。

但这个并不适合于PaxosLease,因为master选举的频率很低,如果等待一次paxos过程完成,节点要等很长的时间才能重新加入。

5. 正确性证明

PaxosLease是由3个精妙配合的Timer来工作的,Paxos算法的正确性是显然的,PaxosLease会不会在引入Timer之后改变其安全假设?请看下面简短证明:

首先引入几个符合:

  • b:ballot number
  • tstart :proposer启动Timer1的时间
  • tnow :proposer在prepare阶段接收到多数派响应空value的时间
  • tacquire :proposer在propose阶段接收到多数派的accept时间
  • A1:在prepare阶段回应proposer的多数派
  • A2:在propose阶段回应proposer的多数派

假如proposer p已经获得了lease,则在tend =tstart +T之内,其他proposer不会获得多数派acceptor响应空value,也即其他proposer不会获得lease。

Part1:不存在任何proposer q,编号b1<b,在[tacquire ,tend ]之间获得lease


假如q也在此期间获得了lease,则存在多数派A4在propose阶段回应了q,令a是A2和A4的公共成员,因为b1<b,a必定先接 受了q的proposal,然后发送回应给p,因为p在propose阶段接收到了空value,说明q的Timer2已经超时,因此q的timer也肯 定超时(b1<b,q的timer启动的更早),因此p、q的lease并无重叠

Part2:不存在任何propose q,编号b1>b,在[tacquire ,tend ]之间获得lease


证明与上类似。

6. 结论

其实PaxosLease的正确性是有Paxos算法保证的,PaxosLease只是在Paxos的基础上限定了一个时间。在时间T之内,任何Node都不能申请lease,因此master宕机后重新选择master的最大时间为T,也即服务不可用的最大时间为T。

T设的过小会减少服务不可用的时间,但会产生更多的内部消息;设的过大内部消息减少,但会导致更长的宕机时间。

PaxosLease是一个节点之间不存在依赖的简洁的选举算法,就像其论文所说每个节点都有两个状态:

  • 我不是master,也不知道谁是master
  • 我是master

正因为如此,PaxosLease的缺点就是无法做到负载均衡,无法按权重选择master。

[转]Paxos算法3-实现探讨

原文转自:http://www.cnblogs.com/chen77716/archive/2011/02/04/2130802.html

前两篇Paxos算法的讨论,让我们对paxos算法的理论形成过程有了大概的了解,但距离其成为一个可执行的算法程序还有很长的路要走,原因是很多的细节和错误未被考虑。Google Chubby的作者说,paxos算法实现起来远没有看起来简单,原因是paxos的容错仅限于server crash这一种情况,但在实际工程实现时要考虑磁盘损坏、文件损坏、Leader身份丢失等诸多的错误。

1. Paxos各角色的职能

在paxos算法中存在Client、Proposer、Proposer Leaer、Acceptor、Learn五种角色,可精简为三种主要角色:proposer、acceptor、learn。角色只是逻辑上存在的,在实际实现中,节点可以身兼多职。

在我们的讨论中,我们先假定没有Proposer Leader这一角色,在不考虑活锁的情况下,如果算法过程正确,那有Leader角色的算法过程肯定也正确。

除了五种角色,还有三个重要的概念:instance、proposal、value,分别代表:每次paxos选举过程、提案、提案的value

当然,还有4个关键过程:

  • (Phase1):prepare
  • (Phase1):prepare ack
  • (Phase2):accept
  • (Phase2):accept ack

对acceptor来说,还蕴含是着promise、accept、reject三个动作。

先上一幅图,更直观地对几种角色的职能加以了解(各角色的具体职能参考Lamport的论文就足够了):

Paxos各角色职责

上图不是非常严格,仅为表现各角色之间的关系。

2. Proposer

在Proposer、Acceptor、Learn中均涉及到proposal的编号,该编号应该有proposer作出改变,对其他的角色是只读的,这就保证了只有一个数据源。当多个proposer同时提交proposal时,必须保证各proposer的编号唯一、且可比较,具体做法之前已经提到。这里还要强调一点的是,仅每个proposer按自己的规则提高编号是不够的,还必须了解“外面”的最大编号是多少,例如,P1、P2、P3(请参考:Paxos算法2#再论编号问题:编号唯一性 )

  • P3的当前编号为初始编号2
  • 当P3提交proposal时发现已经有更大的编号16(16是P2提出的,按规则:5*3+1)
  • P3发起新编号时必须保证new no >16,按照前面的规则必须选择:5*3+2 = 17,而不能仅按自己的规则选择:1*3+2=5

这要求acceptor要在reject消息中给出当前的最大编号,proposer可能出现宕机,重启后继续服务,reject消息会帮助它迅速找到下一个正确编号。但是当多个acceptor回复各自不一的reject消息时,事情就变得复杂起来。

当proposer发送proposal给一个acceptor时,会有三种结果:

  • timeout:超时,未接收到aceptor的response
  • reject:编号不够大,拒绝。并附有当前最大编号
  • promise:接受,并确保不会批准小于此编号的proposal。并附有当前最大编号及对应的value

在判断是否可以进行Phase2时的一个充分条例就是:必须有acceptor的多数派promise了当前的proposal 。

下面分别从Phase1和Phase2讨论proposer的行为:

Phase1-prepare:发送prepare到acceptor


Proposer在本地选择proposal编号,发送给acceptor,会收到几种情况的response:

(a). 没有收到多数派的回应

消息丢失、Server宕机导致没有多数派响应,在可靠消息传输(TCP)下,应该报告宕机导致剩余的Server无法继续提供服务,在实际中一个多数派同时宕机的可能性非常小。

(b). 收到多数派的reject

Acceptor可能会发生任意的错误,比如消息丢失、宕机重启等,会导致每个acceptor看到的最大编号不一致,因而在reject消息中response给proposer的最大编号也不一致,这种情况proposer应该取其最大作为比较对象,重新计算编号后继续Phase1的prepare阶段。

(c). 收到多数派的promise

根据包含的value不同,这些promise又分三种情况:

  • 多数派的value是相同的,说明之前已经达成了最终决议
  • value互不相同,之前并没有达成最终决议
  • 返回的value全部为null

全部为null的情况比较好处理,只要proposer自由决定value即可;多数派达成一致的情况也好处理,选择已经达成决议的value提交即可,value互不相同的情况有两种处理方式:

  • 方案1:自由确定一个value。原因:反正之前没有达成决议,本次提交哪个value应该是没有限制的。
  • 方案2:选择promise编号最大的value。原因:本次选举(instance)已经有提案了,虽未获通过,但以后的提案应该继续之前的,Lamport在paxos simple的论文中选择这种方式。

其实问题的本质是:在一个instance内,一个acceptor是否能accept多个value?约束P2只是要求,如果某个value v已被选出,那之后选出的还应该是v;反过来说,如果value v还没有被多数派accept,也没有限制acceptor只accept一个value。

感觉两种处理方式都可以,只要选择一个value,能在paxos之后的过程中达成一致即可。其实不然,有可能value v已经成为了最终决议,但acceptor不知道,如果这时不选择value v而选其他value,会导致在一次instance内达成两个决议。

会不会存在这样一种情况:A、B、C、D为多数派的promise,A、B、C的proposal编号,value为(1,1),D的为(2,2)?就是说,编号互不一致,但小编号的已经达成了最终决议,而大编号的没有?

设:小编号的proposal为P1,value为v1;大编号的proposal为P2,value为v2

  • 如果P1选出最终决议,那么肯定是完成了phase1、phase2。存在一个acceptor的多数派C1,P1为其最大编号,并且每个acceptor都accept了v1;
  • 当P2执行phase1时,存在多数派C2回应了promise,则C1与C2存在一个公共成员,其最大编号为P1,并且accept了v1
  • 根据规则,P2只能选择v1继续phase2,也就是说v1=v2,无论phase2是否能成功,绝不会在acceptor中遗留下类似(2,2)这样的value

也就是说,只要按照【方案2】选择value就能保证结果的正确性。之所以能有这样的结果,关键还是那个神秘的多数派,这个多数派起了两个至关重要的作用:

  • 在phase1拒绝小编号的proposal
  • 在phase2强迫proposal选择指定的value

而多数派能起作用的原因就是,任何两个多数派至少有一个公共成员,而这个公共成员对后续proposal的行为起着决定性的影响,如果这个多数派拒绝了后续的proposal,这些proposal就会因为无法形成新的多数派而进行不下去。这也是paxos算法的精髓所在吧。

Phase2-accept:发送accept给acceptor


如果一切正常,proposer会选择一个value发送给acceptor,这个过程比较简单

accept也会收到2种回应:


(a). acceptor多数派accept了value

一旦多数派accept了value,那最终决议就已达成,剩下的工作就是交由learn学习并关闭本次选举(instance)。

(b). acceptor多数派reject了value或超时

说明acceptor不可用或提交的编号不够大,继续Phase1的处理。

proposer的处理大概如此,但实际编程时还有几个问题要考虑:

  • 来自acceptor的重复消息
  • 本来超时的消息又突然到了
  • 消息持久化

其他2个问题比较简单,持久化的问题有必要讨论下。

持久化的目的是在proposer server宕机“苏醒”时,可以继续参与paxos过程。

从前面分析可看出,proposer工作的正确性是靠编号的正确性来保证的,编号的正确性是由proposer对编号的初始化写及acceptor的reject一起保证的,所以只要acceptor能正常工作,proposer就无须持久化当前编号。

3. acceptor

acceptor的行为相对简单,就是根据提案的编号决定是否接受proposal,判断编号依赖promise和accept两种消息,因此acceptor必须对接收到的消息做持久化处理。根据之前的讨论也知道,acceptor的持久化也会影响着proposer的正确性。

在acceptor对proposal进行决策的时候,还有个重要的概念没有被详细讨论,即instance。任何对proposal的判断都是基于某个instance,即某次paxos过程,当本次instance宣布结束(选出了最终决议)时,paxos过程就转移到下一个instance。这样会衍生出几个问题:

  1. instance何时被关闭?被谁关闭?
  2. acceptor的行为是否依赖instance的关闭与否?
  3. acceptor的多数派会不会在同一个instance内对两个不同的value同时达成一致?

根据1中对各角色职能的讨论,决议是否被选出是由learn来决定的,当learn得知某个value v已经被多数派accept时,就认为决议被选出,并宣布关闭当前的instance。与2中提到的一样,因为网络原因acceptor可能不会得知instance已被关闭,而会继续对proposer回答关于该instance的问题。也就是说,无论如何acceptor都无法准确得知instance是否关闭,acceptor程序的正确性也就不能依赖instance是否关闭。但acceptor在已经知道instance已被关闭的情况下,在拒绝proposer时能提供更多的信息,比如,可以使proposer选择一个更高的instance重新提交请求。

当然,只要proposer根据2中提到的方式进行提案,就不会发生同一instance中产生两个决议的情况。

4. learn

learn的主要职责是学习决议,但决议必须一个一个按顺序学,不能跳号,比如learn已经知道了100,102号决议,必须同时知道101时才能一起学习。只是简单的等待101号决议的到来显然不是一个好办法,learn还要去主动向acceptor询问101号决议的情况,acceptor会对消息做持久化,做到这一点显然不难。

learn还要周期性地check所接收到的value,一旦意识到决议已经达成,必须关闭对应的instance,并通知acceptor、proposer等(根据需要可以通知任意多的对象)。

learn还存在一个问题是,是选择一个server做learn还是选多个,假如有N个acceptor,M个learn,则会产生N*M的通信量,如果M很大则通信量会非常大,如果M=1,通信量小但会形成单点。折中方案是选择规模相对较小的M,使这些learn通知其他learn。

paxos中的learn相对比较抽象,好理解但难以想象能做什么,原因在于对paxos的应用场景不清晰。一般说来有两种使用paxos的场景:

  • paxos作为单独的服务,比如google的chubby,hadoop的zookeeper
  • paxos作为应用的一部分,比如Keyspace、BerkeleyDB

如果把paxos作为单独的服务,那learn的作用就是达成决议后通知客户端;如果是应用的一部分,learn则会直接执行业务逻辑,比如开始数据复制。

持久化:

learn所依赖的所有信息就是value和instance,这些信息都已在acceptor中进行了持久化,所有learn不需要在对消息做持久化,当learn新加入或重启时要做到的就是能把这些信息通过acceptor再取回来。

错误处理:

learn可能会重启或新加入后会对“之前发生的事情”不清楚,解决办法是:使learn继续监听消息,直至某个instance对应的value达成一致时,learn再向acceptor请求之前所有的instance。

至此,我们进一步讨论了paxos个角色的职责和可能的实现分析,离我们把paxos算法变为一个可执行程序的目标又进了一步,使我们对paxos的实现方式大致心里有底,但还有诸多的问题需要进一步讨论,比如错误处理。虽然文中也提到了一些错误及处理方式,但还没有系统地考虑到所有的错误。

接下来的讨论将重点围绕着分布式环境下的错误处理。

[转]Paxos算法2-算法过程

原文转自:http://www.cnblogs.com/chen77716/archive/2011/01/30/2130803.html

1.编号处理

根据P2c ,proposer在提案前会先咨询acceptor查看其批准的最大的编号和value,再决定提交哪个value。之前我们一直强调更高编号的proposal,而没有说明低编号的proposal该怎么处理。

|——–低编号(L<N)——–|——–当前编号(N)——–|——–高编号(H>N)——–|

P2c 的正确性是由当前编号N而产生了一些更高编号H来保证的,更低编号L在之前某个时刻,可能也是符合P2c 的,但因为网络通信的不可靠,导致L被延迟到与H同时提交,L与H有可能有不同的value,这显然违背了P2c ,解决办法是acceptor不接受任何编号已过期的proposal,更精确的描述为:

P1a : An acceptor can accept a proposal numbered n iff it has not responded to a prepare request having a number greater than n.

显然,acceptor接收到的第一个proposal符合这个条件,也就是说P1a 蕴含了P1。

关于编号问题进一步的讨论请参考下节的【再论编号问题:唯一编号 】。

2. Paxos算法形成

重新整理P2c 和P1a 可以提出Paxos算法,算法分2个阶段:

Phase1:prepare

(a)proposer选择一个proposal编号n,发送给acceptor中的一个多数派

(b)如果acceptor发现n是它已回复的请求中编号最大的,它会回复它已accept的最大的proposal和对应的value(如果有);同时还附有一种承诺:不会批准编号小于n的proposal

Phase2:accept

(a)如果proposer接收到了多数派的回应,它发送一个accept消息到(编号为n,value v的proposal)到acceptor的多数派(可以与prepare的多数派不同)

关键是这个value v是什么,如果acceptor回应中包含了value,则取其编号最大的那个,作为v;如果回应中不包含任何value,则有proposer随意选择一个

(b)acceptor接收到accept消息后check,如果没有比n大的回应比n大的proposal,则accept对应的value;否则拒绝或不回应

感觉算法过程异常地简单,而理解算法是怎么形成却非常困难。再仔细考虑下,这个算法又会产生更多的疑问:

再论编号问题:唯一编号


保证paxos正确运行的一个重要因素就是proposal编号,编号之间要能比较大小/先后,如果是一个proposer很容易做到,如果是多个proposer同时提案,该如何处理?Lamport不关心这个问题,只是要求编号必须是全序的,但我们必须关心。这个问题看似简单,实际还稍微有点棘手,因为这本质上是也是一个分布式的问题。

在Google的Chubby论文中给出了这样一种方法:

假设有n个proposer,每个编号为ir (0<=ir <n),proposol编号的任何值s都应该大于它已知的最大值,并且满足:s %n = ir => s = m*n + ir

proposer已知的最大值来自两部分:proposer自己对编号自增后的值和接收到acceptor的reject后所得到的值

以3个proposer P1、P2、P3为例,开始m=0,编号分别为0,1,2

P1提交的时候发现了P2已经提交,P2编号为1 > P1的0,因此P1重新计算编号:new P1 = 1*3+0 = 4

P3以编号2提交,发现小于P1的4,因此P3重新编号:new P3 = 1*3+2 = 5

整个paxos算法基本上就是围绕着proposal编号在进行:proposer忙于选择更大的编号提交proposal,acceptor则比较提交的proposal的编号是否已是最大,只要编号确定了,所对应的value也就确定了。所以说,在paxos算法中没有什么比proposal的编号更重要。

活锁


当一proposer提交的poposal被拒绝时,可能是因为acceptor promise了更大编号的proposal,因此proposer提高编号继续提交。 如果2个proposer都发现自己的编号过低转而提出更高编号的proposal,会导致死循环,也称为活锁。

Leader选举


活锁的问题在理论上的确存在,Lamport给出的解决办法是选举出一个proposer作leader,所有的proposal都通过leader来提交,当Leader宕机时马上再选举其他的Leader。

Leader之所以能解决这个问题,是因为其可以控制提交的进度,比如果之前的proposal没有结果,之后的proposal就等一等,不着急提高编号再次提交,相当于把一个分布式问题转化为一个单点问题,而单点的健壮性是靠选举机制保证。

问题貌似越来越复杂,因为又需要一个Leader选举算法,但Lamport在fast paxos中认为该问题比较简单,因为Leader选举失败不会对系统造成什么影响,因此这个问题他不想讨论。但是后来他又说,Fischer, Lynch, and Patterson的研究结果表明一个可靠的选举算法必须使用随机或超时(租赁)。

Paxos本来就是选举算法,能否用paxos来选举Leader呢?选举Leader是选举proposal的一部分,在选举leader时再用paxos是不是已经在递归使用paxos?存在称之为PaxosLease的paxos算法简化版可以完成leader的选举,像Keyspace、Libpaxos、Zookeeper、goole chubby等实现中都采用了该算法。关于PaxosLease,之后我们将会详细讨论。

虽然Lamport提到了随机和超时机制,但我个人认为更健壮和优雅的做法还是PaxosLease。

Leader带来的困惑


Leader解决了活锁问题,但引入了一个疑问:

既然有了Leader,那只要在Leader上设置一个Queue,所有的proposal便可以被全局编号,除了Leader是可以选举的,与Paxos算法1 提到的单点MQ非常相似。

那是不是说,只要从多个MQ中选举出一个作为Master就等于实现了paxos算法?现在的MQ本身就支持Master-Master模式,难道饶了一圈,paxos就是双Master模式?

仅从编号来看,的确如此,只要选举出单个Master接收所有proposal,编号问题迎刃而解,实在无须再走acceptor的流程。但paxos算法要求无论发生什么错误,都能保证在每次选举中能选定一个value,并能被learn学习。比如leader、acceptor,learn都可能宕机,之后,还可能“苏醒”,这些过程都要保证算法的正确性。

如果仅有一个Master,宕机时选举的结果根本就无法被learn学习, 也就是说,Leader选举机制更多的是保证异常情况下算法的正确性,虚惊一场,paxos原来不是Master-Master。

在此,我们第一次提到了”learn”这个角色,在value被选择后,learn的工作就是去学习最终决议,学习也是算法的一部分,同样要在任何情况下保证正确性,后续的主要工作将围绕“learn”展开。

Paxos与二段提交


Google的人曾说,其他分布式算法都是paxos的简化形式。

假如leader只提交一个proposal给acceptor的简单情况:

  • 发送prepared给多数派acceptor
  • 接收多数派的响应
  • 发送accept给多数派使其批准对应的value

其实就是一个二段提交问题,整个paxos算法可以看作是多个交叉执行而又相互影响的二段提交算法。

如何选出多个Value


Paxos算法描述的过程是发生在“一次选举”的过程中,这个在前面也提到过,实际Paxos算法执行是一轮接一轮,每轮还有个专有称呼:instance(翻译成中文有点怪),每instance都选出一个唯一的value。

在每instanc中,一个proposal可能会被提交多次才能获得acceptor的批准,一般做法是,如果acceptor没有接受,那proposer就提高编号继续提交。如果acceptor还没有选择(多数派批准)一个value,proposer可以任意提交value,否则就必须提交意见选择的,这个在P2c 中已经说明过。

Paxos中还有一个要提一下的问题是,在prepare阶段提交的是proposal的编号,之后再决定提交哪个value,就是value与编号是分开提交的,这与我们的思维有点不一样。

3. 学习决议

在决议被最终选出后,最重要的事情就是让learn学习决议,学习决议就是决定如何处理决议。

在学习的过程中,遇到的第一个问题就是learn如何知道决议已被选出,简单的做法就是每个批准proposal的acceptor都告诉每个需要学习的learn,但这样的通信量非常大。简单的优化方式就是只告诉一个learn,让这个唯一learn通知其他learn,这样做的好是减少了通信量,但坏处同样明显,会形成单点;当然折中方案是告诉一小部分learn,复杂性是learn之间又会有分布式的问题。

无论如何,有一点是肯定的,就是每个acceptor都要向learn发送批准的消息,如果不是这样的话,learn就无法知道这个value是否是最终决议,因此优化的问题缩减为一个还是多个learn的问题。

能否像proposer的Leader一样为learn也选个Leader?因为每个acceptor都有持久存储,这样做是可以的,但会把系统搞的越来越复杂,之后我们还会详细讨论这个问题。

Learn学习决议时,还有一个重要的问题就是要按顺序学习,之前的选举算法花费很多精力就是为了给所有的proposal全局编号,目的是能被按顺序使用。但learn收到的决议的顺序可能不不一致,有可能先收到10号决议,但9号还未到,这时必须等9号到达,或主动向acceptor去请求9号决议,之后才能学习9号、10号决议。

4. 异常情况、持久存储

在算法执行的过程中会产生很多的异常情况,比如proposer宕机、acceptor在接收proposal后宕机,proposer接收消息后宕机,acceptor在accept后宕机,learn宕机等,甚至还有存储失败等诸多错误。

但无论何种错误必须保证paxos算法的正确性,这就需要proposer、aceptor、learn都做能持久存储,以做到server”醒来“后仍能正确参与paxos处理。

  • propose该存储已提交的最大proposal编号、决议编号(instance id)
  • acceptor储已promise的最大编号;已accept的最大编号和value、决议编号
  • learn存储已学习过的决议和编号

以上就是paxos算法的大概介绍,目的是对paxos算法有粗略了解,知道算法解决什么问题、算法的角色及怎么产生的,还有就是算法执行的过程、核心所在及对容错处理的要求。

但仅根据上面的描述还很难翻译成一个可执行的算法程序,因为还有无限多的问题需要解决:

  • Leader选举算法
  • Leader宕机,但新的Leader还未选出,对系统会有什么影响
  • 更多交叉在一起的错误发生,还能否保证算法的正确性
  • learn到达该怎么学习决议
  • instance no、proposal no是该维护在哪里?
  • 性能

众多问题如雪片般飞来,待这些都逐一解决后才能讨论实现的问题。当然还有一个最重要的问题,paxos算法被证明是正确的,但程序如何能被证明是正确的?

更多的请参考后面的章节。

[转]Paxos算法1-算法形成理论

原文转自http://www.cnblogs.com/chen77716/archive/2011/01/27/2130804.html

Paxos算法的难理解与算法的知名度一样令人敬仰,从我个人的经历而言,难理解的原因并不是该算法高深到大家智商不够,而在于Lamport在表达该算法时过于晦涩且缺乏一个完整的应用场景。如果大师能换种思路表达该算法,大家可能会更容易接受:

  • 首先提出算法适用的场景,给出一个多数读者能理解的案例
  • 其次描述Paxos算法如何解决这个问题
  • 再次给出算法的起源(就是那些希腊城邦的比喻和算法过程)

Lamport首先提出算法的起源,在没有任何辅助场景下,已经让很多人陷于泥潭,在满脑子疑问的前提下,根本无法继续接触算法的具体内容,更无从体会算法的精华。本文将换种表达方法对Paxos算法进行重新描述。

我们所有的描述都假设读者已经熟读了Lamport的paxos-simple一文,因此对各种概念不再解释。

除了Lamport的几篇论文,对Paxos算法描述比较简洁的中文文章是:http://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95,该文翻译的比较到位,但在关键细节上还是存在一些歧义和一些对原文不正确的理解,可能会导致读者对Paxos算法更迷茫,但阅读该文可以快速地对Paxos算法有个大概的了解。

1.应用场景

(1)分布式中的一致性

Paxos算法主要是解决一致性问题,关于“一致性”,在不同的场景有不同的解释:

  • NoSQL领域:一致性更强调“能读到新写入的”,就是读写一致性
  • 数据库领域:一致性强调“所有的数据状态一致”,经过一个事务后,如果事务成功,所有的表数据都按照事务中的SQL进行了操作,该修改的修改,该增加的增加,该删除的删除,不能该修改的修改了,该删除的没删掉;如果事务失败,所有的数据还是在初始状态;
  • 状态机:在状态机中的一致性更强调在每个初始状态一致的状态机上执行一串命令后状态都必须相互一致,也就是顺序一致性。Paxos算法中的一致性指的就是这种情况,接下来我们会对这种场景进一步讨论。

(2)MQ

假如所有系统的Log信息都写入一个MQ Server,然后通过MQ把每条Log指令发异步送到多个Log Server写入文件(写入多个Log Server的原因是对Log文件做备份以防数据丢失),则所有Log Server上的数据肯定是一致的(Log内容及顺序完全相同),因为MQ本身就有排序功能,只要进了Q数据也就有了序,相当于编了全局唯一的号,无论把这些数据写入多少个文件,只要按编号,各文件的内容必定是一致的,但一个MQ Server显然是一个单点,如果宕了会影响整个系统的可用性。

(3)多MQ

要解决MQ单点问题,首选方案是采用多个MQ Server,即使用一个MQ Cluster,客户端可以访问任意MQ Server,不同的客户端可能访问不同MQ Server,不同MQ Server上的数据内容、顺序可能不一致,如果不解决这个问题,每个MQ Server写入Log Server的内容就不一致,这显然不是我们期望的结果。

(4)NoSQL中的数据更新

一般的NoSQL都会通过数据复制的形式保证其可用性,但客户端对多数据进行操作时,可能会有很多对同一数据的操作发送的某一台或几台Server,有可能执行:Insert、Update A、Update B….Update N,就一次Insert连续多次Update,最终复制Server上也必须执行这一的更新操作,如果因为线程池、网络、Server资源等原因导致各复制Server接收到的更新顺序不一致,那边这样的复制数据就失去了意义,如果在金融领域甚至会造成严重的后果。

上面这些不一致问题性正是Paxos算法要解决的,当然这些问题也不是只有Paxos能解决,在没有Paxos之前这些问题也得到了解决,比如通过使用双Master模式的MQ解决MQ单点问题;通过使用Master Server解决NoSQL的复制问题,但这些解决方法都存在一些缺陷,要么难水平扩展,要么影响可用性。当然除了Paxos算法还有其他一些算法也试图解决这类问题,比如:Viewstamped Replication算法。

上面描述的这些场景的共性是希望多Server之间状态一致,也就是一致性,再看中文Wiki开篇提到的:

在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。为保证每个节点执行相同的命令序列,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的指令一致

大家或许会对该描述有更深的理解。

2.Paxos如何解决这类问题

Paxos对这类问题的解决就是试图对各Server上的状态进行全局编号,如果能编号成功,那么所有操作都按照编号顺序执行,一致性就不言而喻。当Cluster中的Server都接收了一些数据,如何进行编号?就是表决,让所有的Server进行表决,看哪个Server上的哪个数据应该排第一,哪个排第二…,只要多数Server同意某个数据该排第几,那就排第几。

很显然,为了给每个数据唯一编号,每次表决只能产生一个数据,否则表决就没有任何意义。Paxos的算法的所有精力都放在如何在一次表决只产生一个数据。再进一步,我们称表决的数据叫Value,Paxos算法的核心和精华就是确保每次表决只产生一个Value。

3.Paxos算法

我们对原文的概念加以补充:

  • promise:Acceptor对proposer承诺,如果没有更大编号的proposal会accept它提交的proposal
  • accept:Acceptor没有发现有比之前的proposal更大编号的proposal,就批准了该proposal
  • chosen:当Acceptor的多数派都accept一个proposal时,该proposal就被最终选择,也称为决议

也就是说,Acceptor对proposer有两个动作:promise和accept

下面的解释也主要围绕着”Only a single value is chosen, “, 再看下条件P1,

P1:An acceptor must accept the first proposal that it receives.

乍一看,这个条件是显然的,因为之前没有任何value,acceptor理所当然地应该accept第一个proposal,但仔细想想,感觉P1这个条件很不严格,到底是一个对问题的简单描述还是一个数学上严格的必要条件?这些疑问归结为2个问题:

(1)这个条件本质上在保证什么?

(2)第二个proposal怎么办?

在后续的算法中看到一个Acceptor是否批准一个Value与它是否是第一个没有任何关系,而与这个Proposal的编号有关。那岂不说明P1没有得到保证?开始我也百思不得其解,后来经过跟朋友交流发现,P1中的”accept”其实是指acceptor对proposer的”promise”,就是语言描述跟算法的步骤描述之间存在歧义,因此我认为对算法问题还是应该采用数学语法而非文字语言。

所以,P1是强调了第一个proposal要被promise,但第二个还未提到,这也是疑问之一。

也很显然的是,单靠P1是无法保证Paxos算法的,因可能无法形成多数派,那接下来的讨论应该是考虑如何弥补P1的缺点,使其可以保证Paxos算法,就是我们希望未来的条件应该说明:

  • 如何解决P1中无法形成多数派的问题
  • 第二个proposal如何选择

于是约束P2出现了:
P2:If a proposal with value v is chosen, then every higher-numbered proposal that is chosen has value v.

P2的出现让人大跌眼镜,P2并没沿着P1的路向下走,也没有解决P1的上述2个不完备,而是从另一个侧面讨论如何保证只能选出一个Value。P1讨论的是该如何选择,P2讨论的是一旦被选出来,之后的选择应该不变,就是P1还在讨论选的问题,P2已经选出来了,中间有个断层,怎么选的没有讨论。

其实从后面Lamport不断对P2增强可以看出,P2里面蕴含着P1(通过proposal编号,第一次之前没有编号,所以选择),P2才真正给出了怎么选择的具体过程,从事后分析看,P1给出了第一个该怎么选,P2给出了所有的该怎么选,条件有点重复。所以,把P1和P2看作是两个独立条件的做法是不准确的,因而中文wiki中提到“如果 P1 和 P2 都能够保证,那么约束2就能够保证 ”,对细微理解有一定的影响。

也不是说P1就没有用,反过来看,P2是个未知问题,而P1是这个未知问题的已知部分,从契约的角度来看,P1就是个不变式,任何对P2的增强都不能过了头以至于无法满足P1这个不变式,也就是说,P1是P2增强的底线。

那还有没有其他的不变式需要遵守?是否在对P2增强的过程中已破坏了这些未知的不变式?这些高难度的问题牵扯到Paxos算法正确性,要看MIT的严格的数学证明,已超出了本文。

另外,中文Wiki对P2的描述是:“P2:一旦一个 value 被批准(chosen),那么之后批准(chosen)的 value 必须和这个 value 一样 。”,原文采用higher-numbered 更能描述未来对proposal进行编号这个事实, 而中文采用“之后,已经完全失去这个意义。

我们暂时按下P1不表,近距离观察一下P2,为了保证每次选出一个value,P2规定在一个Value已经被选出的情况下,如果还有其他的proposer提交value,那之后批准的value应该跟前一个一致,就是在事实上已经选定一个value时,之后的proposer不能提交不同的value把之前的结果打乱。这是一个泛泛的描述,但如果这个描述能得到实现,paxos算法就能得到保证,因此P2也称”safety property”。

接下来的讨论都时基于“If a proposal with value v is chosen”,如何保证“then every higher-numbered proposal that is chosen has value v ”,具体怎么做到“a proposal with value v is chosen”暂且不谈。

P2更多是从思想层面上提出来该如何解决这个问题,但具体的落实工作需要很多细化的步骤,Lamport是通过逐步增强条件的方式进行落实P2,主要从下面几个方面进行:

  • 对整个结果提出要求(P2)
  • 对Acceptor提出要求(P2a
  • 对Proposer提出要求(P2b
  • 对Acceptor与Proposer同时提出要求(P2c

Lamport为什么能把过程划分的如此清楚已经不得而知,但从Lamport发表的文章来看,他对分布式有很深的造诣,也持续了很长的时间,能有如此的结果,与他对分布式的基础与背后的巨大努力有很大关系。但对我们而言,不知过程只知个结果,总感觉知其然不知其所以然。

我们沿着上面的思路继续:

P2a :If a proposal with value v is chosen, then every higher-numbered proposal accepted by any acceptor has value v.

这个条件是在限制acceptor,很显然,如果P2a 得到了满足,满足P2是肯定的,但P2a 的增强破坏了P1不变式的底线,具体参考原文,所以P2a本身没啥意义,转而从proposer端进行增强。

P2b :If a proposal with value v is chosen, then every higher-numbered proposal issued by any proposer has value v.

这个条件是在限制proposer,如果能限制住proposer,对acceptor的限制当然能被满足的。同时,因为限制proposer必须提交value v,也就顺便保证了P1(第一个肯定是value v)

但P2b 是难以实现的,难实现的原因是多个proposer可以提交任意value的proposal,无法限制proposer不能提交某个value,因此需要寻找P2b的等价条件:

P2c :For any v and n, if a proposal with value v and number n is issued, then there is a set S consisting of a majority of acceptors such that either

(a) no acceptor in S has accepted any proposal numbered less than n, or

(b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S.

根据原文,P2c 里面蕴含了P2b ,但由P2c 推导P2b是最难理解的部分。

首先要清楚P2c 要做什么,因为P2b 很难直接实现,P2c 要做的就是解决的P2b问题,也就是要解决“如果value v被选择了,更高编号的提案已经具有value v”,也就是说:

  • R:“For any v and n, if a proposal with value v and number n is issued”是结果,而
  • C:“ then there is a set S consisting… ”是条件

就是要证明如果C成立,那么结果R成立,而原文的表达是“如果R成立,那么存在一个条件R”,容易让人搞混因果关系,再次感叹如果使用数学符号表达这样的歧义肯定会减少很多。

P2c 解决问题的思路是:不是直接尝试去满足P2b ,而是寻找能满足P2b 的一个充分条件,如果能满足这个充分条件,那P2b 的满足是显然的。还要强调一点的是proposer可以提交任意的value,你怎么能限制我提交的必须是value v呢?其实原文中的“For any v and n, if a proposal with value v and number n is issued ”是指“如果一个编号为n的proposal提交value v,并且value v能被acceptor所接受”,要想被接受就不能随便提交一个value,就必须是一个受限制的value,这里讨论的前提是value v是要被接受的。然后我们再看下,是否满足了条件C,结果R就成立。

(a) no acceptor in S has accepted any proposal numbered less than n

如果这个条件成立,那么n是S中第一个proposal,根据P1,必须接受,所以结果R成立

(b) v is the value of the highest-numbered proposal among all proposals numbered less than n accepted by the acceptors in S

这个证明先假设编号为n的proposal具有value X被选择,肯定存在一个结合C,其中的每个acceptor都接受了value X,而集合S中的每个Acceptor都接受了value v,因为S、C都是多数派,所以存在一个公共成员u,既接受了X,又接受了v,为了保证选择的唯一性,必须X=v.

大家可能会发觉该证明有点不太严格,“小于n的最大编号”与n之间还有很多proposal,那些proposal也有一些value,那些value会不会不是v?

这个就会用到原文中的数学归纳法,就是任意的编号m的proposal具有了value v,那么n=m+1是,根据上面也是具有value v的,那么向后递推,任意的n >m都具有value v。中文wiki中的那个归纳证明不需要对m…n-1正推,而对n反证,通过数学归纳正推完全可以得出最终结果。

也就是说,P2c 是P2b 的一个加强,满足P2c 就能满足P2b

我们再近距离观察下P2c ,发现只要在proposer提交提案前,咨询一下acceptor,看他们的最高编号是啥,他们是否选择了某个value v,再根据acceptor的回答进行选择新的编号、value提交,就可以满足P2c。通过编号,可以把(a)和(b)两个条件统一在一起。

其实P2c要表达的思想非常简单:如果前面有value v选出了,那以后就提交这个value v;否则proposer决定提交哪个value,具体做法就是事前咨询,事中决定,事后提交,也就是说可以通过消息传递模型实现。Lamport通过条件、集合、归纳证明等形式表达该问题,而没提这样做的目的,会导致理解很困难。大家可能会比较疑惑,难道自始至终只能选出一个value?其实这里的选出,是指一次选举,而不是整个选举周期,可以多次运行paxos,每次都只选出一个value。

满足P2c从侧面也反映出要想提交一个正确的value v,要对proposer、acceptor同时进行限制,仅限制一方问题是无法解决的。

再回顾下条件之间的递推关系P2c =>P2b =>P2a =>P2,就是说P2c 最终保证了P2,也就是解决了如何做到一个value v被选择之后,被选择的编号更大的proposal都具有value v,P2c不仅保证P2的结果,更提出了“如何选”的问题,就是上面分阶段进行,这就填补了P1与P2之间缺少如何选的断层,还有P1的2个不完备问题从直观上感觉会得到解决,具体的要看算法过程章节。

P1的不完备问题:

P2c也顺便解决了P1的不完备问题,因为proposer提交的value是受acceptor限制的,就不会在一次选举中提交两个不同的value,即使能提交也会因为proposal编号问题有一个会被拒绝,从而能保证能形成多数派。

另一个关于第二个该怎么选的不完备问题,也是显然的了。

再次证明了,P2里面蕴含了P1,P1只是未知问题P2的不变式。

DNF中的生意之道VS现实中的生意之道

这段时间一直在玩dnf,但凡是个游戏,就一定有它的经济系统。尝试着在其中做生意,赚点金币,原先只是想把练控偶师期间做的人偶卖出去,后来在做生意的过程中,觉得这就是一个缩小版的真实世界经济系统,所以可以以小见大,臆测一下真实世界中的生意怎么做。

一、拍卖行的起步

控偶师,就是做人偶的人,人偶可以帮你在地下城中打怪,让玩家的死亡率降低,效率增高,算是半个外挂,所以是有市场的。

有专业就有等级,不可能一个铁匠一开始就能做屠龙刀,同样的道理,控偶师有9级,对应着9个等级的人偶,不同等级的人偶需要的材料不同,有的贵,有的便宜,不用的人偶好卖程度不同,有的好卖,有的不好卖。这些都是废话,因为大家都是这样的。

升控偶师的等级是一件痛苦的事情,对于我这种没什么金币的人,只能做一部分人偶,然后放在拍卖行卖出去,然后才有钱做后面的人偶。然而,这样的过程是亏钱的过程,因为大家都在做,于是低级人偶的价格惨不忍睹,保本都很危险,更多时候是亏本卖,这也算是供过于求的原因。此外,原料的问题也很严重,当升级过程中必须要用的某个材料(其实就是淬炼的冒险家灵魂)正好是某个高级的超热门的人偶(28号)的原料的时候,这个材料必然超贵,但是做出的人偶却绝对不值成本的价值,所以,那一段是超级亏本的阶段。

后来,度过最恶心的飓风博尔赫的阶段之后,后面练的就比较快了,因为人偶可以卖出去了,而且亏得很少,甚至能保本。最终,到达了可以做28号的阶段。

这时一般不会亏了,即使是按照拍卖行的均价去买材料,做出来卖掉也能赚个1000g,虽然少,但是也算有。不过更多时候,我是去刷新拍卖行,看有没有便宜的材料出现,赶紧买下来,这样就能赚的多一点点,这样的行为真的是让人去干机器的活。

于是,我开始思考,利润是从哪里来的。从小到大,我们被教育,利润来自剩余劳动价值,那么看看我做人偶的话,我的剩余劳动价值只有做人偶这个行为了吧。这太抽象了,一点不实际。更为实际的想法是这样:利润来自每个人偶的利润*卖出的数量。

所以,想要提高总利润,要么提高每个人偶的利润,要么提高卖出的数量。

二、拍卖行的种种

这里就不得不说说在这个阶段一直依赖的拍卖行系统。

据我观察,拍卖行一定是跨线的,甚至是跨服务器的,所以,拍卖行成为了一个类似于淘宝的地方,各种个人卖家放东西上去卖,买家可以看到排序后的列表,去买最便宜的东西。因此交易量巨大。卖出的东西只要有人要,那么数量就不是问题,只要你能把价格降到最低,这也就是一金秒压党出现的原因。我也是其中一员,为了尽快把成本收回,总是做10个,放10个,1g秒压别人。

这里也是有技巧的,拍卖行的交易量大,所以价格的波动就大,你可以压别人,别人也可以压你,如果一次放很大数量的东西上去,但是被别人压了价格,那么东西就被套牢在拍卖行中,当然,你可以强制下架,但是代价是10000g,也就是10个28号的利润。所以,这就需要紧紧跟住市场,在价格发生变动时及时应对。具体的做法就是一次放适量的商品出去,即使被压了,也不会导致长时间内无法周转。这样的道理让我想到了窃听风云2里的刘青云,钉住价格波动,一下就能钱生钱,还钱给古天乐。

既然数量可以通过这样的手段达到,剩下的就是单个利润的问题,也就无非是降低成本。制造业的成本是个很有意思的东西, ∑原料成本*原料数量,我们的目标是要降低每个积,有些原料数量大,但是成本低,可能还不如别的一单位的原料贵,有的原料价格贵,但是需要数量少,有的则两者皆大,这边是我们的目标。具体到28号上,也就是要降低淬炼的冒险家灵魂的成本。湖北一区中,8500是其均价,一般价格在这个数字周围上下浮动,有心者可以买到8200甚至更低的价格,这么一算,成本就降低了300*5=1500,这将会转化为纯利润,相比于原先的1000利润,2500的利润相当于提高了150%,还是很可观的。

三、制造业的供应链

这时,我就在想,便宜的原料出自何处,其实不外乎有以下几种出处:拍卖行,自己去制造原料,站在大街上喊收原料,或者有个稳定的供应商。

拍卖行,属于集中管理的资源中心,所有人都可以予取予求,说白了,就是便宜货会出现,但是你要和别人抢。

自己制造,这就是小农经济了,所有的东西,从最底层的原料开始,都由自己制造,繁琐而低效。

站街收材料,这或许会有冤大头卖给你,但是时间成本实在太高。

最后,就是稳定的供应商了,如果有稳定的供应商,你可以和他建立稳定的供货渠道,他做的是各种收集原料,然后卖给你,你做的是从他那买原料,然后做人偶卖出去,分工明确,当规模扩大到一定程度时,利润将相当可怕。

这应该就是供应链了吧,制造业的特点是有原料就一定能做出来,所以一旦一条稳定的从上到下的供应链形成时,一个产业就形成了。供应链的断裂也将带来可怕的利润损失,需要小心维护。

供应链的特点就是分工明确,高效,规模大,对于制造业来说是发展的必然阶段。然后我就想到了这种公司中的销售和采购的作用,他们的身份可能不高,但是作用很大,是他们一点点建立起了整条供应链。

四、摆摊阶段的扩大再生产

回到拍卖行,拍卖行不是无偿使用的,需要收卖价的5%作为手续费。看似5%不多,但是自己卖过东西才懂,举例来说,如果28号53000的成本,卖53000/0.95=55789才不亏本,如果手续费下降到3%的话(也就是花人民币买了拍卖行优惠劵),就可以多出55789*2%=1115的利润,这部分也就成了纯利润。

不过,在我买了拍卖行优惠劵之后,发现了另一个更有意思的现象,那就是摆摊。

摆摊是一种更原始的销售方法,摆摊之于拍卖行就相当于实体店之于淘宝。这里就有一个很有意思的现象,为什么淘宝的东西更便宜,但是还是会有人去实体店买呢?答案很多,可以是质量原因,有可能是因为近,有可能是看到实体心里安心,无论如何,这个现象存在,而且存在于虚拟世界中,那就是:

为什么分明拍卖行更便宜,却有人会去买贵的摆摊的东西呢?

虚拟世界中不存在现实中的很多问题:质量相同,拍卖行比摆摊更近,全都是虚拟的都只能看到属性。那么剩下的就只有一项了,那就是心理原因。

分析这个心理显然超出了本文的能力,但是还是可以略加揣度。

或许有些人就是不知道有拍卖行这个功能。

或许有些人认为从拍卖行买需要更多的鼠标操作次数,拍卖行的操作过程:输入名称,查询,选择,一口价,数量,确定,开邮箱,点邮件,点物品,删除邮件。摆摊的操作过程:点开摊位,点物品,数量,确定。10:4的操作复杂导致了这个现象么?

或者有些人只是被突然出现在眼前的商品激起了购物欲望么?

我不知道,但是这个现象就是存在,而且成为了另一种牟利手段的基础。

摆摊相比于拍卖行,优势在这里:手续费只有1%,可以卖的比拍卖行贵好多。劣势是销售数量少,因为只有周围看得到你的人才能买(相比于拍卖行的多频道,甚至是多服务器的客户人群)。

所以,摆摊还是很有的赚的,甚至我直接从拍卖行买成品再摆摊卖都有的赚。

我当时就是用一点点成本,做出28号,卖比周围都便宜的价格,卖出去之后再收集便宜的原料,再做,周而复始。

这个就类似于现实世界中的扩大再生产了吧。我原本也不是很理解为什么那些大老板会说自己穷,现在想一想,其实是赚来的钱又投入到了生产中去,剩下的资金就很少了。那些生意失败的人应该就是在这个过程中,把资金换成了商品,但是却无法把商品换成资金的悲惨人生,可见对市场敏锐观察的重要性。

五、压价与联盟

摆摊的过程中,自然出现了竞争,竞争的手段有几种:占据优势的地理位置,价格打压。

其实拍卖行的价格打压更为明显,总会有人用更低的价格出货,以求迅速周转,所以拍卖行的价格总是在或多或少地降,除非有人大量收购。这也是符合供求关系的规律的。

摆摊中的压价也是一样,面对更小的客户群体,更少的竞争对手,一样可以用压价的方法让客户买自己的东西不买别人的东西。这里得提一下,虽然他们选择从摆摊这里而不是拍卖行买东西,他们一样会货比三家,这种心理真的很奇怪。

但是摆摊者之间的价格波动却是缓慢的,因为很多人一摆一天,都不去改变价格。有时候也不是不愿意改,而是一点扯了摊位,就会有别人抢你的位置,甚至摆摊者之间会使用建筑学,让一个地图里的摆摊者数量降到最低。

虽然价格波动缓慢,但是波动的幅度却很大,因为利润本身就已经可以满足要求了,少赚点但是能卖出去一样可以接受。

这样的现象在现实中也存在,我就在想,如果卖28号的人之间达成共识会怎样呢?大家形成一个联盟,使用统一的价格出货,就好比现实中的手机市场里,一件东西不同人都卖一个价格。这种联盟的形成可以保护商家的利益,不过在dnf里也行不通,因为一旦如此摆摊位置就会成为最大的不公平因素。

六、商人类型

上面所说的都是我在游戏中作为一名制造者而得到的经验和思考。然而,即使是在游戏中,也存在着不同类型的商人。

制造者,如我上面所说,收集原料,做出成品,卖掉。但是对供求关系很敏感,走的算是薄利多销的路。

奢侈品供应者,就是制造,买卖各种奢侈品的人,在游戏里,表现为各种高强化武器装备,顶级附魔等等,他们的特点是商品数量少,但是利润巨大,交易量少,受供求关系影响不明显。

倒卖者,与现实中的收购公司差不多,凭空玩弄金钱,制造利润,而不是靠自己去做东西

Cassandra 0.8.0流程分析(1)——CassandraDaemon启动流程

一、入口

入口方法是CassandraDaemon中的main方法,里面只有一个CassandraDaemon().activate()的调用。

    public static void main(String[] args)
    {
        new CassandraDaemon().activate();
    }

这里调用的是AbstractCassandraDaemon中的方法。Cassandra虽然是用java写的,但是确实如我从前看到的一篇文章所说的那样,很有C的风格。具体来说,类中的static变量比比皆是,还有就是代码中常用static{}代码块,这个就是用来在第一次调用到这个类时一定会执行到的部分,于是,这就成了C语言中的面向过程编程了。而且由于步进时是不会自动在这些地方停留的,于是,必须手动在这个代码块中设置断点,否则总是直接执行完了,对于我们这种废柴读代码者来说很是难过,不过习惯了也就好了。

二、setup

继续刚才的过程,AbstractCassandraDaemon首先启动了log4j的相关功能,设置了定期检查配置更新。然后正式进入到activate()方法中。

这个方法做了2件事,也就是2步,第一步是初始化daemon,第二步是启动daemon。

初始化的工作其实是调用AbstractCassandraDaemon中的setup()方法。这其实是个非常繁复的过程。注意下面的类的初始化基本都包括了log4j的Logger注册,和Mbean的注册,所以就不单独写了。

1、设置logger

2、检查CLibrary

3、DatabaseDescriptor初始化

全名org.apache.cassandra.config.DatabaseDescriptor。很类似,这个类的初始化也是放在了static中,说的笼统一点,这里就是把各种配置文件读取了,然后存放在相应的变量中。但是如果真的要分的话,还是有2个部分:

第1部分是读取cassadnra.yaml,将其中的配置参数导入到各个变量中,整个过程中还涉及到对参数值的合法性检查,根据设置的类名启动相应的类或建立相应的对象等等。

第2部分是建立各种system tables,这些系统表的配置是直接写在代码中的(hardcode),而不是从配置中读取。具体来说,就是建立了一个名为system的keyspace的MetaData,然后其中包括以下的columnFamily:

  • StatusCf:persistent metadata for the local node,本节点的持久化元数据
  • HintsCf:hinted handoff data,便签式提交数据
  • MigrationsCf:individual schema mutations,单个(?)schema改变
  • SchemaCf:current state of the schema,当前schema状态
  • IndexCf:indexes that have been completed,已完成索引
  • NodeIdCf:nodeId and their metadata,节点ID和相应元数据

相关的代码如下:

            // Hardcoded system tables
            KSMetaData systemMeta = new KSMetaData(Table.SYSTEM_TABLE,
                                                   LocalStrategy.class,
                                                   KSMetaData.optsWithRF(1),
                                                   CFMetaData.StatusCf,
                                                   CFMetaData.HintsCf,
                                                   CFMetaData.MigrationsCf,
                                                   CFMetaData.SchemaCf,
                                                   CFMetaData.IndexCf,
                                                   CFMetaData.NodeIdCf);
            CFMetaData.map(CFMetaData.StatusCf);
            CFMetaData.map(CFMetaData.HintsCf);
            CFMetaData.map(CFMetaData.MigrationsCf);
            CFMetaData.map(CFMetaData.SchemaCf);
            CFMetaData.map(CFMetaData.IndexCf);
            CFMetaData.map(CFMetaData.NodeIdCf);
            tables.put(Table.SYSTEM_TABLE, systemMeta);

从这里也可以看出一个keyspace的元数据需要包括哪些内容(名,副本策略,副本策略参数,ColumnFamily的元数据)。注意这里仅仅是建立了相关的元数据,并没有真正生成相应的keyspace的对象。

这个类的初始化很重要,是整个daemon启动的基础。

4、StorageService初始化

这个类的全名是org.apache.cassandra.service.StorageService,实际上是在DatabaseDescriptor初始化的过程中调用到了这个类的方法, 于是在第一次调用时初始化了。StorageService的初始化包括3个部分。

第一个部分定义了一系列的谓词(VERBS)和谓词阶段(verbStages),如下:

/* All verb handler identifiers */
    public enum Verb
    {
        MUTATION,
        BINARY,
        READ_REPAIR,
        READ,
        REQUEST_RESPONSE, // client-initiated reads and writes
        STREAM_INITIATE, // Deprecated
        STREAM_INITIATE_DONE, // Deprecated
        STREAM_REPLY,
        STREAM_REQUEST,
        RANGE_SLICE,
        BOOTSTRAP_TOKEN,
        TREE_REQUEST,
        TREE_RESPONSE,
        JOIN, // Deprecated
        GOSSIP_DIGEST_SYN,
        GOSSIP_DIGEST_ACK,
        GOSSIP_DIGEST_ACK2,
        DEFINITIONS_ANNOUNCE,
        DEFINITIONS_UPDATE_RESPONSE,
        TRUNCATE,
        SCHEMA_CHECK,
        INDEX_SCAN,
        REPLICATION_FINISHED,
        INTERNAL_RESPONSE, // responses to internal calls
        COUNTER_MUTATION,
        // use as padding for backwards compatability where a previous version needs to validate a verb from the future.
        UNUSED_1,
        UNUSED_2,
        UNUSED_3,
        ;
        // remember to add new verbs at the end, since we serialize by ordinal
    }
    public static final Verb[] VERBS = Verb.values();

    public static final EnumMap<StorageService.Verb, Stage> verbStages = new EnumMap<StorageService.Verb, Stage>(StorageService.Verb.class)
    {{
        put(Verb.MUTATION, Stage.MUTATION);
        put(Verb.BINARY, Stage.MUTATION);
        put(Verb.READ_REPAIR, Stage.MUTATION);
        put(Verb.READ, Stage.READ);
        put(Verb.REQUEST_RESPONSE, Stage.REQUEST_RESPONSE);
        put(Verb.STREAM_REPLY, Stage.MISC); // TODO does this really belong on misc? I've just copied old behavior here
        put(Verb.STREAM_REQUEST, Stage.STREAM);
        put(Verb.RANGE_SLICE, Stage.READ);
        put(Verb.BOOTSTRAP_TOKEN, Stage.MISC);
        put(Verb.TREE_REQUEST, Stage.ANTI_ENTROPY);
        put(Verb.TREE_RESPONSE, Stage.ANTI_ENTROPY);
        put(Verb.GOSSIP_DIGEST_ACK, Stage.GOSSIP);
        put(Verb.GOSSIP_DIGEST_ACK2, Stage.GOSSIP);
        put(Verb.GOSSIP_DIGEST_SYN, Stage.GOSSIP);
        put(Verb.DEFINITIONS_ANNOUNCE, Stage.READ);
        put(Verb.DEFINITIONS_UPDATE_RESPONSE, Stage.READ);
        put(Verb.TRUNCATE, Stage.MUTATION);
        put(Verb.SCHEMA_CHECK, Stage.MIGRATION);
        put(Verb.INDEX_SCAN, Stage.READ);
        put(Verb.REPLICATION_FINISHED, Stage.MISC);
        put(Verb.INTERNAL_RESPONSE, Stage.INTERNAL_RESPONSE);
        put(Verb.COUNTER_MUTATION, Stage.MUTATION);
        put(Verb.UNUSED_1, Stage.INTERNAL_RESPONSE);
        put(Verb.UNUSED_2, Stage.INTERNAL_RESPONSE);
        put(Verb.UNUSED_3, Stage.INTERNAL_RESPONSE);
    }};

第二个部分是建立了2个线程池,一个是用来处理短任务,一个是用来处理非周期性长任务,如下:

    /**
     * This pool is used for periodic short (sub-second) tasks.
     */
     public static final RetryingScheduledThreadPoolExecutor scheduledTasks = new RetryingScheduledThreadPoolExecutor("ScheduledTasks");

    /**
     * This pool is used by tasks that can have longer execution times, and usually are non periodic.
     */
    public static final RetryingScheduledThreadPoolExecutor tasks = new RetryingScheduledThreadPoolExecutor("NonPeriodicTasks");

第三个部分是实例化了一个标记为public static final的StorageService对象。在实例化的过程中,首先注册了谓词处理(register the verb handlers),然后又初始化了一个org.apache.cassandra.streaming.StreamingService(是的,层层调用。。。),不过这个类的初始化仅仅是做了实例化,实例化的过程中除了注册Mbean什么都没做。。。

5、获取网络地址和端口(address and port)

6、ColumnFamilyStore初始化

同样的,这个类的初始化是在第一次调用到这个类时完成的,过程是完成所有static的定义和static块中代码的执行。

带有static的定义有3个,分别是

  • flushSorter :一个用来执行flush任务中的排序阶段的线程池,由于是CPU密集型(CPU-bound),所以会根据处理器数量的线程。
  • flushWriter :在排序之后,由此线程池进行写磁盘,由于是磁盘密集型(disk-bound),所以可以和flushSourter同时运作。注意,flushSorter和flushWriter处理的是Memtable和BinaryMemtable的flush,对于BinaryMemtable的flush,这两个线程池已经足够了,这两个线程池都是private的
  • postFlushExecutor : 不同于上面两个线程池,这个是用来处理live Memtable的flush。live Memtable的flush更复杂一些,需要switchMemtable做额外的2件事(这里只会由switchMemtable去调用submitFlush):第一,将这个Memtable放到memtablesPendingFlush中,直到flush完成,并且它已经被转为SSTableReader,加到了ssTable_中;第二,等到flush完成后,在commitLogUpdater中加入一项标记,markCompacted,调用onMemtableFlush。这允许在多核系统中多个flush同时进行,并且以正确的顺序调用onMemtableFlush,正确的顺序对于replay很重要,否就就要restart,因为当onMemtableFlush被调用时,是假设在给定位置之上的内容都已经被固化到SSTable中了。注意到这个线程池是public的,所以是会被别的类调用的,在这个类中有2处调用,一处是添加已flush的标记到commit log的header中,一处是添加已compact的标记到commitlogUpdater中。

static块中,则从StorageService.tasks线程池中建立一个线程,设置延迟1秒开始,两次执行间隔为1秒,执行的内容是一个org.apache.cassandra.db.MeteredFlusher对象。

7、MeteredFlusher线程的定期运作

首先,先获取非活跃Memtable的大小。

然后就是flush的操作,flush的过程分2部分,设m是管线pipeline中能够存在的最大Memtable数量,第一部分则是将所有已使用内存超过已分配内存的1/m的ColumnFamily进行flush,第二部分则是对剩下的Memtable根据大小进行排序,对超过阀值的进行flush

8、清洗系统表的目录

这一步是对系统表所在的目录做清理,保证作为系统基石的系统表不会出错,可以理解,系统有可能在任何时候down机,于是固化到硬盘上的数据文件也就可能处于各种各样的脏数据状态,这里就是为了纠正这些可能的错误,虽然手段很粗暴。具体来说,就是对每个系统表调用ColumnFamilyStore.scrubDataDirectories方法,代码如下:

        // check the system table to keep user from shooting self in foot by changing partitioner, cluster name, etc.
        // we do a one-off scrub of the system table first; we can't load the list of the rest of the tables,
        // until system table is opened.
        for (CFMetaData cfm : DatabaseDescriptor.getTableMetaData(Table.SYSTEM_TABLE).values())
            ColumnFamilyStore.scrubDataDirectories(Table.SYSTEM_TABLE, cfm.cfName);

在方法的执行过程中,所做的工作是移除相应的ColumnFamily所在目录下的不必要的文件,所谓不必要的文件,包括以下这些:临时文件(temp files),孤儿(orphans,指缺少data file的),零长度文件(zero-length files),已合并sstable(compacted sstables)。至于无法辨认的文件,将会被略过。经过这个方法之后,一系列满足上述描述的Descriptor将会被移除。

在具体的实现中,首先处理掉的是各个temp files和compacted sstables,因为有标记,所以很好清除;其次,是orphans,需要检查data file的标记;再其次,是未完成的cache文件,需要对文件名进行匹配,然后删掉;最后是清理这个ColumnFamily的一级索引,具体的操作是对一个ColumnFamily中的各个Column的Index做一次scrubDataDirectories递归调用,这里有趣的是,在原本该填入column family name的地方,填入的是针对某个column的indexName,虽然我还没有看到那部分的代码,但是从这里可以推测,Cassandra中主索引的实现,是以特殊的ColumnFamily的方式实现的。

9、Table的初始化

全名是org.apache.cassandra.db.Table。这个类的初始化自然也是在第一次调用到它的时候才会发生,有趣的是,这个类的第一次调用发生的位置是有可能不确定的。

第一处出现的地方是在SystemTable的checkHealth()方法中:

            table = Table.open(Table.SYSTEM_TABLE);

checkHealth()会在AbstractCassandraDaemon的setup()方法中被调用,这是真正的起始调用处。所以调用过程其实是这样:AbstractCassandraDaemon.setup()->SystemTable.checkHealth()->Table.open(Table.SYSTEM_TABLE)

第二处出现的地方是在ColumnFamilyStore的all()方法中:

        for (Table table : Table.all())

真正的起始调用处是在MeteredFlusher中的run()方法,调用过程是这样:MeteredFlusher.run()->MeteredFlusher.countFlushingBytes()->ColumnFamilyStore.all()->Table.all()->Table.open(tableName)

由于这两个调用的地方处于不同的线程之中,所以谁先被执行到是不确定的,然而,重要的地方是Table这个类的很多方法是需要在不同线程中进行同步的。也就是说,这些方法同一时间只能有一个线程去访问。open()方法就是其中一个。

有点扯远了,不过,明白Table的用处对于理解它的初始化是有帮助的。这里的static块中的代码没有什么特点,仅仅是检查相关的keyspace的目录有没有建立好,没建立好就建一下。更加重要的是Table中各种变量的存在意义。

如同我之前的文章所说,keyspace其实就是table,在代码中也得到了体现。首先,是2个static的变量,一个是重用读写锁:

    /**
     * accesses to CFS.memtable should acquire this for thread safety.
     * Table.maybeSwitchMemtable should aquire the writeLock; see that method for the full explanation.
     *
     * (Enabling fairness in the RRWL is observed to decrease throughput, so we leave it off.)
     */
    static final ReentrantReadWriteLock switchLock = new ReentrantReadWriteLock();

一个就是用来储存所有Table的实例映射的变量:

    /** Table objects, one per keyspace.  only one instance should ever exist for any given keyspace. */
    private static final Map<String, Table> instances = new NonBlockingHashMap<String, Table>();

其他的非static变量则是一个table应该拥有的属性:name,columnFamilyStores(每个column family都有一个columnFamilyStore),indexLocks,flushTask,replicationStrategy。

无论如何,Table.open()方法都是至关重要的一个入口。其实这个方法做的事情很简单,就是从instances中取得相应名字的table,如果找不到的话,就新建一个。需要注意的是必须保证每个keyspace只有一个table对象,所以在新建table的代码块外面加上了synchronized标记,用来在多线程中同步。

Table的构造器则做是在读取配置之后,建立各个实例属性(也就是上面提到的那些,name,columnFamilyStores,indexLocks,flushTask,replicationStrategy)。

10、系统表健康检查

说是健康检查,实际上也就是打开系统表的一个Column Family,比了比里面的值。

第一步打开系统表:

            table = Table.open(Table.SYSTEM_TABLE);

这一步的结果有两种:1)成功,继续下面的操作;2)发生异常,注释上说的是当更改了partitioner(从OPP改成RP)的时候会发生,系统表的文件还在,却无法读取。

第二步是读取系统表中的一部分(一行两列),如下面的子表(粗体是具体值,带引号的是字符串,不带的是变量,灰色是变量名,通常都是常量,蓝色是说明,下同):

column family: SCHEMA_CF=”Schema
columns: PARTITIONER=”Partioner CLUSTERNAME=”ClusterName
Key: LOCATION_KEY=”L  value  value

具体代码如下:

        SortedSet<ByteBuffer> cols = new TreeSet<ByteBuffer>(BytesType.instance);
        cols.add(PARTITIONER);
        cols.add(CLUSTERNAME);
        QueryFilter filter = QueryFilter.getNamesFilter(decorate(LOCATION_KEY), new QueryPath(STATUS_CF), cols);
        ColumnFamily cf = table.getColumnFamilyStore(STATUS_CF).getColumnFamily(filter);

此处的情况就略为复杂一些,可能发生的结果有三种:

  • 1)cf不为空,读取成功,great;
  • 2)cf为空,于是再次尝试取得名为 STATUS_CF 的系统表中的ColumnFamily,检查其中的SSTable部分:
    • 2.1)如果SSTable部分不为空,那就抛出异常,因为这意味着这个CF的文件存在,但是却读不出来,这种情况是在更改了partitioner(从RP改成了OPP)的时候会发生。如果为空,则进入2.2;
    • 2.2)如果SSTable部分为空,意味着没有找到系统表的这部分文件,没关系,这会被认为是新的节点,于是进入处理新节点的流程;

此外,这里的QueryFilter对象的建立,ColumnFamily对象的获得,都是一系列比较复杂的过程,这也是后话。

第三步是检查读取到的partitioner和clustername是否与从配置文件中读取的相同,我们需要他们相同!

11、载入Schema

第一步是尝试获得最新的MigrationId。获取的位置依然是System Table,存入UUID类型的变量中,这里假设这个变量是version吧,version下面还会用到,讲起来比较方便。具体位置如下表:

column family: SCHEMA_CF=”Schema
columns: LAST_MIGRATION_KEY=”Last Migration
Key: LAST_MIGRATION_KEY=”Last Migration  value

第二步,判断这个值是否找到,这时,会发生2种情况:1,找不到,表现为version == null。然后此时再查看是否有数据文件存在,如果数据文件还在,那么就是系统无法读取schema,需要用户用CLI重新定义schema,如果没有数据文件存在,那么很好,说明是一个什么表都没建立的空节点;2,找到了这个版本号,那么就会调用org.apache.cassandra.db.DefsTable.loadFromStorage(UUID version)方法。

第三步,接上一步的最后一个分支,是从存储中载入某个版本的keyspace。具体来说,是从System Table的下面DEFINITION_SCHEMA_COLUMN_NAME列中读出一个json串

column family: SCHEMA_CF=”Schema
columns: DEFINITION_SCHEMA_COLUMN_NAME=”Avro/Schema 每个keyspace一个column,name这里是keyspace的name,直接是文本
Key: version  value是Schema的JSON 每个keyspace的定义, 根据左边的schema编码之后的数据,读出后需要用相应的schema解码

然后依次经过几个类型的容器,IColumn=>ByteBuffer=>String=>Schema,获得一个Schema对象。最终通过反序列化得到所有keyspace的元数据的集合Collection<KSMetaData>

  • IColumn=>ByteBuffer:直接取IColumn的value
  • ByteBuffer=>String:调用ByteBufferUtil.string(value),获得一个json字符串
  • String=>Schema:调用Schema.parse(s),这个方法来自Avro项目,具体的过程不在次讨论,简单介绍一下Avro中的Schema支持,以下翻译自(http://www.cloudera.com/blog/2009/11/avro-a-new-format-for-data-interchange/):“

    Avro使用JSON来定义一个数据结构的schema。举例来说,一个二维的点可以定义成以下的Avro记录:

    {“type”: “record”, “name”: “Point”,
    “fields”: [
    {“name”: “x”, “type”: “int”},
    {“name”: “y”, “type”: “int”},
    ]
    }

    这个记录的每个实例都被序列化成简单的两个整数,没有额外的每记录(per-record)或每域(per-field)的注解。整数使用可变长的zig-zag编码写下。因此,较小坐标值的点就能仅用两个字节来写下:100个点会需要大概200字节。

    在记录(records)类型和数值(numeric)类型之外,Avro还包括了对数组(arrays),映射(maps),枚举(enums),变量(variable),定长二进制数据(fixed-length binary data)以及字符串 (strings)的支持。Avro还定义了一个容器文件格式(container file format),以提供良好的支持给MapReduce以及其他分析框架。细节见Avro specification.

  • Collection<KSMetaData>:使用第三步开始得到的Schema对象依次反序列化这个ColumnFamily中的其他Column的值,每次生成一个KsDef类的对象,合起来就是所有的keyspace的元数据集合。这个Schema相当于是keyspace元数据的元数据。

第四步,对所有的keyspace:创建keyspace名到columnfamily名的映射<cfm.ksName, cfm.cfName>,然后添加到CFMetaData类的静态变量cfIdMap中;然后将这个keyspace的元数据,连同版本号一起添加到DatabaseDescriptor的相应静态变量中。

12、清洗所有表的目录

类似上述第8步,只是清洗的对象是所有的表,因为他们的元数据已经由上一步获得了,存在了DatabaseDescriptor.tables里。

13、打开所有表

对所有的表执行Table.open(table)方法。代码如下:

        // initialize keyspaces
        for (String table : DatabaseDescriptor.getTables())
        {
            if (logger.isDebugEnabled())
                logger.debug("opening keyspace " + table);
            Table.open(table);
        }

14、启动垃圾收集检查器的定期运作

这里的垃圾收集检查器是指GCInspector类,这个类也用到了Singleton Pattern,只被实例化一次。但是注意这一点:这个类的作用是定期对sun的类进行垃圾收集。实例化时,会在MBeanServer中注册。然后会启动一个线程定期运作。定期运作时,如果当完成一次完整的垃圾收集后仍然使用很多的内存,则会根据需要进行1)降低缓存大小;2)flush最大的Memtable。

代码就一句:

        try
        {
            GCInspector.instance.start();
        }
        catch (Throwable t)
        {
            logger.warn("Unable to start GCInspector (currently only supported on the Sun JVM)");
        }

15、CommitLog恢复

CommitLog的恢复是在server端启动时完成的,作用是处理未完成的操作。

第一步,从DatabaseDescriptor中获得commitlog的位置,然后检查大小,看是否需要恢复(空文件不需要恢复)。如果需要,那么对需要恢复的commitlog进行排序(会有多个commitlog文件),排序依据是修改时间

第二步,使用这些commitlog文件进行恢复。这一步是CommitLog恢复的主要操作所在,过程还是比较复杂,而且似乎不同版本间的这个方法是不一样的,这里的仅对0.8.0。

  1. 获得所有sstable文件的ReplayPosition。
    先来说说ReplayPosition,这个类有用的是两个属性:segment和position。segment在程序里表现为时间点的值,用这个语句生成:System.currentTimeMillis(),segment会在2个地方出现(至少到目前为止,我只看到两个地方),一个是commitlog的文件名中,另一个是sstable4元组(一个SSTable包括Data.db,Filter.db,Index.db和Statistics.db)中的Statistics.db。而position则是文件中的文件指针的位置,用来标记从文件的何处开始读。于是,这里要做的就是从每个ColumnFamily的各个SSTable的Statistics.db文件中读出每个SSTable的ReplayPosition,然后取每个ColumnFamily的各个SSTable的ReplayPosition中的最大值,存入cfposition变量中。具体的文件格式在以后的文章中再说。
  2. 获取cfposition中最小的那个ReplayPosition,存入globalPosition。这里的最小指的是segment最小,也就是时间最早,代表着上一次系统结束前最早操作的那个column family的时间点。这个时间点后面会用到。
  3. 获取commitlog中最小的那个的segment。正如上面所说,commitlog的文件名中就包含了这个segment,所以直接解析即可。
  4. 对每个commitlog,获得执行恢复操作所需要的commitlog文件的位置。判断的依据是比较globalPosition.segment和commitlog.segment(代码中不是这样写的,但是这里这样写方便叙述,包括下面提到的replayPosition为了方便叙述也写成commitlog.replayPosition)的值,会发生三种情况:
    1)globalPosition.segment<commitlog.segment:说明这个commitlog所做的操作还没有执行,所以把commitlog.replayPosition值设为0
    2)globalPosition.segment==commitlog.segment:说明这个commitlog所做的操作正杂执行,所以把commitlog.replayPosition值设为globalPosition.position
    3)globalPosition.segment>commitlog.segment:说明这个commitlog所做的操作已经执行过了,所以把commitlog.replayPosition值设为reader.length(),也就是这个commitlog的末尾。
  5. 如果要恢复,从replayPosition每次读取一项(entry),然后将读取到的每一项反序列化成RowMutation对象
  6. 将得到的RowMutation对象添加到SEDA中MUTATION阶段的线程池中,执行提交。
    futures.add(StageManager.getStage(Stage.MUTATION).submit(runnable));
  7. 最后等所有的更改结束之后flush涉及到的table。

第三步,删除所有的commitlog。

16、启动服务器

第一步,获得各节点的Token,即一系列<token,endpoint>,获取地址在系统表中,具体如下:

column family: STATUS_CF=”LocationInfo
columns: column name是各token值
Key: RING_KEY=”Ring column value是相应的endpoint值

第二步,更新系统中使用到token的地方,一处时tokenMetadata_变量,一处是Gossiper。

第三步,定义了一个ShutdownHook,添加到了Runtime中。

第四步,加入到Token Ring中。从这里开始是真正开始把服务器当做网络中的一个节点,所以各种设置监听。

首先就是启动Gossiper。Gossiper作为Cassandra中很有特色的一个东西,还是比较重要的,其作用是维护集群的状态,通过gossip,每个节点都能知道集群中包含哪些节点,以及这些节点的状态,这使得Cassandra集群中的任何一个节点都可以完成任意key的路由,任意一个节点不可用都不会造成灾难性的后果。Gossiper的详细介绍以后再说,这里仅给出涉及到的部分。

  1. Gossiper采用 Singleton Pattern,仅有一个实例,在实例化的时候,做了两件事:设置了两个时间长度,用作gossip过程中的时间上限,然后将这个Gossiper实例注册为FailureDetector的一个监听器。Gossiper和FailureDetector的羁绊在于IFailureDetectionEventListener接口,其中只有一个方法 convict(InetAddress ep),即标记一个节点死了,FailureDetector实现了”The Phi Accrual Failure Detector”,做出判决,然后由各监听器来执行。
  2. 在启动Gossiper之前,注册了2个预订用户(subscribers):StorageService实例和MigrationManager实例,他们的共同特点是要实现IEndpointStateChangeSubscriber接口,这个接口定义了5个方法,用于某节点a通知它感兴趣的那部分(?interested parties,不知道怎么翻。。)关于任意endpoint的状态改变。这个5个方法分别是:onJoin,onChange,onAlive,onDead,onRemove。
            // have to start the gossip service before we can see any info on other nodes.  this is necessary
            // for bootstrap to get the load info it needs.
            // (we won't be part of the storage ring though until we add a nodeId to our state, below.)
            Gossiper.instance.register(this);
            Gossiper.instance.register(migrationManager);
            Gossiper.instance.start(SystemTable.incrementAndGetGeneration()); // needed for node-ring gathering.

    然后,Gossiper启动,参数是新的版本号。在启动过程中,首先获得所有的seed节点。然后启动本机的心跳状态(HeartBeatState),将本机状态设为alive,并将<netAddress, EndpointState>映射存入endpointStateMap。接下来通知snitches,告诉他们gossip要开始了。最后启动定期运作的线程,定期GossipTask类中的run方法,运作的内容如下(摘自袁大星的文档):

    Cassandra内部有一个Gossiper,每隔一秒运行一次(在Gossiper.java的start方法中),按照以下规则向其他节点发 送同步消息:

    1) 随机取一个当前活着的节点,并向它发送同步请求

    2) 向随机一台不可达的机器发送同步请求

    3) 如果第一步中所选择的节点不是seed,或者当前活着的节点数少于seed数,则向随意一台seed发送同步请求

    第一和第二步好理解,通过第一步可以和当前活着的节点同步状态,以更新本地的状态,通过第二步可以尽早发现不可用的节点重新可用了。

    第三步中的第一个条件,如果第一步中的节点不是seed,则向随意一台seed发送同步请求也比较好理解,因为seed理论上总是有较多的节点状态 信息。

    第三步中第二个条件则有点难理解,当活着的节点数少于seed时,也需要向随机的seed发送同步消息。其实这里是为了避免出现seed孤岛。

    如果没有这个判断,考虑这样一种场景,有4台机器,{A, B, C, D},并且配置了它们都是seed,如果它们同时启动,可能会出现这样的情形:

    A节点起来,发现没有活着的节点,走到第三步,和任意一个种子同步,假设选择了B

    B节点和A完成同步,则认为A活着,它将和A同步,由于A是种子,B将不再和其他种子同步

    C节点起来,发现没有活着的节点,同样走到第三步,和任意一个种子同步,假设这次选择了D

    C节点和D完成同步,认为D活着,则它将和D同步,由于D也是种子,所以C也不再和其他种子同步

    这时就形成了两个孤岛,A和B互相同步,C和D之间互相同步,但是{A,B}和{C,D}之间将不再互相同步,它们也就不知道对方的存在了。

    加入第二个判断后,A和B同步完,发现只有一个节点活着,但是seed有4个,这时会再和任意一个seed通信,从而打破这个孤岛。

  3. MessagingService开始监听本地地址。这是用于消息传递的类。
  4. StorageLoadBalancer开始广播。这个类是这篇文章《Scalable range query processing for large-scale distributed database applications》的实现。用于监视负载信息,必要时会进行负载平衡,运行间隔是5分钟。
  5. 向所有seed节点声明自己的版本号。先用消息进行积极(actively)的声明,最后使用Gossip进行消极(passively)的声明。
  6. 在Gossiper的本地映射中添加应用状态。
            MessagingService.instance().listen(FBUtilities.getLocalAddress());
            StorageLoadBalancer.instance.startBroadcasting();
            MigrationManager.announce(DatabaseDescriptor.getDefsVersion(), DatabaseDescriptor.getSeeds());
            Gossiper.instance.addLocalApplicationState(ApplicationState.RELEASE_VERSION, valueFactory.releaseVersion());
  7. HintedHandOffManager注册Mbean。
  8. 判断是不是AutoBootstrap,然后分别有不同的处理流程。如果不是AutoBootstrap的话,那么就设置一下token就好了。如果是AutoBootstrap的话,事情就大条了,先要用StorageLoadBalancer获得负载信息,然后看看这个节点是不是已经是token ring中一部分,是的话就异常了,有幸进行下去的话,用获得的负载信息来决定本机的token值,然后再用这个token启动。

17、载入mx4j

这是为了能够使用JMX。实现的代码里充斥着reflection。。。

三、start

启动RPCServer。最终是调用了这个方法:

    protected void startServer()
    {
        if (server == null)
        {
            server = new ThriftServer(listenAddr, listenPort);
            server.start();
        }
    }

四、后记

在拖延症的影响下,这篇日志写了2个多月,真是汗颜啊。。。当然,也有第一次看这个代码的原因,往往一个方法要搞懂得看好久,不过,完成了这篇日志,还是很有好处的,了解了许多实现内部的细节,以后应该就能看的快些了。最后一些部分有些流水账,写的太急。还有一些方法的细节部分没有写,准备以后专门用别的日志来写,这篇已经太长太长了。。。

google系列的ipv6地址

拷下来当备忘,以后换系统的时候方便找

#google plus
2404:6800:8005::71 profiles.google.com
2404:6800:8005::65 plusone.google.com
2404:6800:8005::8a plus.google.com
2404:6800:8005::62 talkgadget.google.com
#以下是图片服务器的国内ip,防止用了ipv6之后图片刷不出来
203.208.46.29 picadaweb.google.com
203.208.46.29 lh1.ggpht.com
203.208.46.29 lh2.ggpht.com
203.208.46.29 lh3.ggpht.com
203.208.46.29 lh4.ggpht.com
203.208.46.29 lh5.ggpht.com
203.208.46.29 lh6.ggpht.com
203.208.46.29 lh6.googleusercontent.com
203.208.46.29 lh5.googleusercontent.com
203.208.46.29 lh4.googleusercontent.com
203.208.46.29 lh3.googleusercontent.com
203.208.46.29 lh2.googleusercontent.com
203.208.46.29 lh1.googleusercontent.com
203.208.46.29 plus.google.com
203.208.46.29 talkgadget.google.com

##Google.com Google.com
2404:6800:8005::68 http://www.google.com #主页
2404:6800:8005::c1 m.google.com #Google移动版
2404:6800:8005::54 accounts.google.com #帐户
2404:6800:8005::65 services.google.com #服务申请
2404:6800:8005::65 goto.google.com #跳转
2404:6800:8005::d2 jmt0.google.com
2404:6800:8005::d2 wire.l.google.com

##Google.com.hk 谷歌香港
2404:6800:8005::2e http://www.google.com.hk
2404:6800:8005::2e images.google.com.hk
2404:6800:8005::2e video.google.com.hk
2404:6800:8005::2e maps.google.com.hk
2404:6800:8005::2e news.google.com.hk
2404:6800:8005::2e translate.google.com.hk
2404:6800:8005::2e blogsearch.google.com.hk
2404:6800:8005::2e picasaweb.google.com.hk
2404:6800:8005::2e toolbar.google.com.hk
2404:6800:8005::2e desktop.google.com.hk
2404:6800:8005::2e id.google.com.hk
2404:6800:8005::2e wenda.google.com.hk
2404:6800:8005::67 http://www.googlechinawebmaster.com

##Google.cn 谷歌中国(启用此地址无法正常使用谷歌音乐)
2401:3800:c001::68 translate.google.cn #翻译
2401:3800:c001::68 blogsearch.google.cn #博客搜索
2401:3800:c001::68 pack.google.cn #软件精选(跳转)
2401:3800:c001::68 news.google.cn #新闻(跳转)
2401:3800:c001::68 video.google.cn #视频(跳转)
2404:6800:8005::84 music.googleusercontent.cn

##Google.com.tw Google台湾
2404:6800:8005::2f http://www.google.com.tw #主页
2404:6800:8005::2f picasaweb.google.com.tw #picasaweb

##Google.co.jp Google日本
2a00:1450:8006::30 http://www.google.co.jp

#IPv6:ipv6.google.co.jp
2404:6800:8005::20 http://www.google.com.tr #土耳其
2404:6800:8005::21 http://www.google.com.au #澳大利亚
2404:6800:8005::22 http://www.google.com.vn #越南
2404:6800:8005::23 http://www.google.com.pk #巴基斯坦
2404:6800:8005::24 http://www.google.com.my #马来西亚
2404:6800:8005::25 http://www.google.com.pe
2404:6800:8005::26 http://www.google.co.za
2404:6800:8005::27 http://www.google.co.ve
2404:6800:8005::28 http://www.google.com.ph
2404:6800:8005::29 http://www.google.com.ar
2404:6800:8005::2a http://www.google.co.nz
2404:6800:8005::2b http://www.google.lt
2404:6800:8005::2d http://www.google.com.sg #新加坡
2404:6800:8005::2e http://www.google.com.hk #香港
2404:6800:8005::2f http://www.google.com.tw #台湾
2404:6800:8005::30 http://www.google.co.jp #日本
2404:6800:8005::31 http://www.google.ae
2404:6800:8005::32 http://www.google.co.uk #英国
2404:6800:8005::33 http://www.google.com.gr
2404:6800:8005::34 http://www.google.de
2404:6800:8005::35 http://www.google.co.il
2404:6800:8005::36 http://www.google.fr #法国
2404:6800:8005::38 http://www.google.it #意大利
2404:6800:8005::39 http://www.google.lv
2404:6800:8005::3a http://www.google.ca
2404:6800:8005::3b http://www.google.pl
2404:6800:8005::3c http://www.google.ch
2404:6800:8005::3d http://www.google.ro
2404:6800:8005::3e http://www.google.nl #荷兰
2404:6800:8005::3f http://www.google.com.ru #俄罗斯
2404:6800:8005::40 http://www.google.at #奥地利
2404:6800:8005::42 http://www.google.be
2404:6800:8005::44 http://www.google.co.kr #南韩
2404:6800:8005::45 http://www.google.com.ua
2404:6800:8005::48 http://www.google.fi #芬兰
2404:6800:8005::49 http://www.google.co.in
2404:6800:8005::4a http://www.google.pt
2404:6800:8005::4b http://www.google.com.ly
2404:6800:8005::4c http://www.google.com.br

#Web 网页
2404:6800:8005::68 http://www.google.com #主页
2404:6800:8005::68 encrypted.google.com #主页
2404:6800:8005::68 http://www.l.google.com
2404:6800:8005::62 www0.l.google.com
2404:6800:8005::62 www1.l.google.com
2404:6800:8005::62 www3.l.google.com
2404:6800:8005::62 suggestqueries.google.com #搜索建议
2404:6800:8005::62 suggestqueries.l.google.com #搜索建议
2404:6800:8005::62 clients0.google.com #客户端服务器
2404:6800:8005::62 clients1.google.com #客户端服务器
2404:6800:8005::62 clients2.google.com #客户端服务器
2404:6800:8005::62 clients3.google.com #客户端服务器
2404:6800:8005::62 clients4.google.com #客户端服务器
2404:6800:8005::62 clients.l.google.com
2404:6800:8005::62 clients1.google.com.hk # .com.hk 搜索建议
2404:6800:8005::62 clients-china.l.google.com
2404:6800:8005::62 linkhelp.clients.google.com #

#Images 图片
2404:6800:8005::68 images.google.com #主页
2404:6800:8005::68 images.l.google.com #
2404:6800:8005::62 tbn0.google.com
2404:6800:8005::62 tbn1.google.com
2404:6800:8005::62 tbn2.google.com
2404:6800:8005::62 tbn3.google.com
2404:6800:8005::62 tbn4.google.com
2404:6800:8005::62 tbn5.google.com
2404:6800:8005::62 tbn6.google.com

#Video 视频
2404:6800:8005::62 video.google.com #主页
2404:6800:8005::62 0.gvt0.com
2404:6800:8005::62 1.gvt0.com
2404:6800:8005::62 2.gvt0.com
2404:6800:8005::62 3.gvt0.com
2404:6800:8005::62 4.gvt0.com
2404:6800:8005::62 5.gvt0.com
2404:6800:8005::62 video-stats.video.google.com
2404:6800:8005::74 upload.video.google.com
2404:6800:8005::74 sslvideo-upload.l.google.com
2404:6800:8005::62 vp.video.google.com
2404:6800:8005::62 vp.video.l.google.com
2404:6800:8005::62 qwqy.vp.video.l.google.com
2404:6800:8005::62 nz.vp.video.l.google.com
2404:6800:8005::62 nztdug.vp.video.l.google.com
2404:6800:8005::62 pr.vp.video.l.google.com
2404:6800:8005::62 ug.vp.video.l.google.com
2404:6800:8005::62 vp01.video.l.google.com
2404:6800:8005::62 vp02.video.l.google.com
2404:6800:8005::62 vp03.video.l.google.com
2404:6800:8005::62 vp04.video.l.google.com
2404:6800:8005::62 vp05.video.l.google.com
2404:6800:8005::62 vp06.video.l.google.com
2404:6800:8005::62 vp07.video.l.google.com
2404:6800:8005::62 vp08.video.l.google.com
2404:6800:8005::62 vp09.video.l.google.com
2404:6800:8005::62 vp10.video.l.google.com
2404:6800:8005::62 vp11.video.l.google.com
2404:6800:8005::62 vp12.video.l.google.com
2404:6800:8005::62 vp13.video.l.google.com
2404:6800:8005::62 vp14.video.l.google.com
2404:6800:8005::62 vp15.video.l.google.com
2404:6800:8005::62 vp16.video.l.google.com
2404:6800:8005::62 vp17.video.l.google.com
2404:6800:8005::62 vp18.video.l.google.com
2404:6800:8005::62 vp19.video.l.google.com
2404:6800:8005::62 vp20.video.l.google.com

2401:3800:c001::68 0.gvt0.cn
2401:3800:c001::68 1.gvt0.cn
2401:3800:c001::68 2.gvt0.cn
2401:3800:c001::68 3.gvt0.cn

#Map 地图
2404:6800:8005::68 maps.google.com #主页
2404:6800:8005::68 local.google.com
2404:6800:8005::68 ditu.google.com #中国版(镜像)
2404:6800:8005::68 maps.l.google.com
2404:6800:8005::62 maps-api-ssl.google.com
2404:6800:8005::62 map.google.com
2404:6800:8005::62 kh.google.com
2404:6800:8005::62 kh.l.google.com
2404:6800:8005::62 khmdb.google.com
2404:6800:8005::62 khm.google.com #
2404:6800:8005::62 khm.l.google.com
2404:6800:8005::62 khm0.google.com #Satellite View
2404:6800:8005::62 khm1.google.com #Satellite View
2404:6800:8005::62 khm2.google.com #Satellite View
2404:6800:8005::62 khm3.google.com #Satellite View
2404:6800:8005::62 cbk0.google.com #Street View
2404:6800:8005::62 cbk1.google.com #Street View
2404:6800:8005::62 cbk2.google.com #Street View
2404:6800:8005::62 cbk3.google.com #Street View
2404:6800:8005::62 mw0.google.com
2404:6800:8005::62 mw1.google.com
2404:6800:8005::62 mw2.google.com
2404:6800:8005::62 mw3.google.com
2404:6800:8005::62 mw-small.l.google.com
2404:6800:8005::62 mt.l.google.com
2404:6800:8005::62 mt0.google.com
2404:6800:8005::62 mt1.google.com
2404:6800:8005::62 mt2.google.com
2404:6800:8005::62 mt3.google.com
2404:6800:8005::62 mlt0.google.com
2404:6800:8005::62 mlt1.google.com
2404:6800:8005::62 mlt2.google.com
2404:6800:8005::62 mlt3.google.com
2404:6800:8005::62 gg.google.com
2404:6800:8005::62 csi.l.google.com
2404:6800:8005::62 id.google.com
2404:6800:8005::62 id.l.google.com
2401:3800:c001::68 id.google.cn
2401:3800:c001::68 ditu.google.cn
2401:3800:c001::68 mt0.google.cn
2401:3800:c001::68 mt1.google.cn
2401:3800:c001::68 mt2.google.cn
2401:3800:c001::68 mt3.google.cn
2401:3800:c001::68 maps.gstatic.cn

#News 资讯
2404:6800:8005::68 news.google.com #主页
2404:6800:8005::68 news.l.google.com
2404:6800:8005::62 nt0.ggpht.com
2404:6800:8005::62 nt1.ggpht.com
2404:6800:8005::62 nt2.ggpht.com
2404:6800:8005::62 nt3.ggpht.com
2404:6800:8005::62 nt4.ggpht.com
2404:6800:8005::62 nt5.ggpht.com

#Gmail 邮箱
2404:6800:8005::11 mail.google.com #主页
2404:6800:8005::53 googlemail.l.google.com
2404:6800:8005::11 googlemail.l.google.com
2404:6800:8005::12 googlemail.l.google.com
2404:6800:8005::13 googlemail.l.google.com
2404:6800:8005::bd chatenabled.mail.google.com #Gmail中Gtalk聊天服务
2404:6800:8005::62 talk.gmail.com #Gmail中Gtalk聊天服务
2404:6800:8005::62 gmail.google.com #
2404:6800:8005::62 gmail.l.google.com #
2404:6800:8005::62 http://www.gmail.com #Gmail主页
2404:6800:8005::62 gmail.com #Gmail主页
2404:6800:8005::62 pop.gmail.com #pop服务
2404:6800:8005::62 smtp.gmail.com #smtp服务
2404:6800:8005::62 smtp1.google.com
2404:6800:8005::62 smtp2.google.com
2404:6800:8005::62 smtp3.google.com
2404:6800:8005::62 smtp4.google.com
2404:6800:8005::62 smtp5.google.com
2404:6800:8005::62 smtp-out.google.com
2404:6800:8005::62 smtp-out2.google.com
2404:6800:8005::62 smtp-out3.google.com
2404:6800:8005::62 imap.google.com #
2404:6800:8005::62 gmail-pop.l.google.com
2404:6800:8005::62 gmail-smtp.l.google.com
2404:6800:8005::62 gmail-smtp-in.l.google.com
2404:6800:8005::62 gmr-smtp-in.l.google.com

#Books 图书
2404:6800:8005::62 books.google.com #主页
2404:6800:8005::62 bks0.books.google.com
2404:6800:8005::62 bks1.books.google.com
2404:6800:8005::62 bks2.books.google.com
2404:6800:8005::62 bks3.books.google.com
2404:6800:8005::62 bks4.books.google.com
2404:6800:8005::62 bks5.books.google.com
2404:6800:8005::62 bks6.books.google.com
2404:6800:8005::62 bks7.books.google.com
2404:6800:8005::62 bks8.books.google.com
2404:6800:8005::62 bks9.books.google.com

#Finance 财经
2404:6800:8005::62 finance.google.com

#Translate 翻译
2404:6800:8005::62 translate.google.com
2401:3800:c001::68 translate.google.cn

#Trends 趋势
2404:6800:8005::63 trends.google.com

#Directory 网页目录
2404:6800:8005::8a directory.google.com
2404:6800:8005::8a dir.google.com #Google网页目录

#Blog 博客搜索
2404:6800:8005::63 blogsearch.google.com
2401:3800:c001::68 blogsearch.google.cn

#Calendar 日历
2404:6800:8005::64 calendar.google.com

#Photo/Picasa 照片/网络相册
2404:6800:8005::5d photos.google.com
2404:6800:8005::63 picasa.google.com
2404:6800:8005::be picasaweb.google.com
2404:6800:8005::62 lh0.ggpht.com
2404:6800:8005::62 lh1.ggpht.com
2404:6800:8005::62 lh2.ggpht.com
2404:6800:8005::62 lh3.ggpht.com
2404:6800:8005::62 lh4.ggpht.com
2404:6800:8005::62 lh5.ggpht.com
2404:6800:8005::62 lh6.ggpht.com
2404:6800:8005::62 lh7.ggpht.com
2404:6800:8005::62 lh8.ggpht.com
2404:6800:8005::62 lh9.ggpht.com
2404:6800:8005::62 lh6.google.com

#Docs 文档
2404:6800:8005::64 docs.google.com
2404:6800:8005::65 docs0.google.com
2404:6800:8005::65 docs1.google.com
2404:6800:8005::65 docs2.google.com
2404:6800:8005::65 docs3.google.com
2404:6800:8005::65 docs4.google.com
2404:6800:8005::65 docs5.google.com
2404:6800:8005::65 docs6.google.com
2404:6800:8005::65 docs7.google.com
2404:6800:8005::65 docs8.google.com
2404:6800:8005::65 docs9.google.com
2404:6800:8005::62 spreadsheet.google.com
2404:6800:8005::62 spreadsheets.google.com
2404:6800:8005::62 spreadsheets0.google.com
2404:6800:8005::62 spreadsheets1.google.com
2404:6800:8005::62 spreadsheets2.google.com
2404:6800:8005::62 spreadsheets3.google.com
2404:6800:8005::62 spreadsheets4.google.com
2404:6800:8005::62 spreadsheets5.google.com
2404:6800:8005::62 spreadsheets6.google.com
2404:6800:8005::62 spreadsheets7.google.com
2404:6800:8005::62 spreadsheets8.google.com
2404:6800:8005::62 spreadsheets9.google.com
2404:6800:8005::62 spreadsheets.l.google.com
2404:6800:8005::62 spreadsheets-china.l.google.com
2404:6800:8005::62 writely.google.com
2404:6800:8005::62 writely.l.google.com
2404:6800:8005::62 writely-com.l.google.com
2404:6800:8005::62 writely-china.l.google.com

#Reader 阅读器
2404:6800:8005::68 reader.google.com
2404:6800:8005::68 www2.l.google.com

#Group 论坛
2404:6800:8005::62 groups.google.com
2404:6800:8005::62 groups.l.google.com
2404:6800:8005::89 *.googlegroups.com
2404:6800:8005::89 blob-s-docs.googlegroups.com
2404:6800:8005::89 2503061233288453901-a-1802744773732722657-s-sites.googlegroups.com

#Scholar 学术搜索
2404:6800:8005::62 scholar.google.com
2404:6800:8005::62 scholar.l.google.com

#Tools 工具
2404:6800:8005::62 tools.google.com
2404:6800:8005::62 tools.l.google.com

#Code 代码
2404:6800:8005::64 code.google.com #主页
2404:6800:8005::64 code.l.google.com #
2404:6800:8005::52 *.googlecode.com #
2404:6800:8005::52 chromium.googlecode.com #
2404:6800:8005::52 searchforchrome.googlecode.com #
2404:6800:8005::52 android-scripting.googlecode.com #Android Scripting Environment
2404:6800:8005::52 earth-api-samples.googlecode.com #
2404:6800:8005::52 gmaps-samples-flash.googlecode.com #
2404:6800:8005::52 google-code-feed-gadget.googlecode.com
2404:6800:8005::52 china-addthis.googlecode.com #
2404:6800:8005::52 get-flash-videos.googlecode.com #get-flash-videos
2404:6800:8005::52 youplayer.googlecode.com #YouPlayer
2404:6800:8005::52 cclive.googlecode.com #ccLive

#Labs 实验室
2404:6800:8005::65 labs.google.com
2404:6800:8005::62 http://www.googlelabs.com
2404:6800:8005::62 browsersize.googlelabs.com #Browser Size
2404:6800:8005::62 citytours.googlelabs.com #City Tours
2404:6800:8005::62 fastflip.googlelabs.com #Fast Flip
2404:6800:8005::62 followfinder.googlelabs.com #Follow Finder
2404:6800:8005::62 image-swirl.googlelabs.com #Image Swirl
2404:6800:8005::62 listen.googlelabs.com #Google Listen
2404:6800:8005::62 livingstories.googlelabs.com #Living Stories
2404:6800:8005::62 newstimeline.googlelabs.com #Google News Timeline
2404:6800:8005::62 relatedlinks.googlelabs.com #Related Links
2404:6800:8005::62 scriptconv.googlelabs.com #Script Converter
2404:6800:8005::62 similar-images.googlelabs.com #Similar Images
2404:6800:8005::62 storegadget.googlelabs.com #Google Checkout Store Gadget
2404:6800:8005::62 tables.googlelabs.com #Fusion Tables
2404:6800:8005::62 appspot.l.google.com

#Knol 在线百科全书
2404:6800:8005::65 knol.google.com

#SketchUp 3D建模工具
2404:6800:8005::62 sketchup.google.com

#Pack 软件精选
2404:6800:8005::68 pack.google.com
2404:6800:8005::68 cache.pack.google.com
2401:3800:c001::68 pack.google.cn

#Blogger 博客服务
2404:6800:8005::bf http://www.blogger.com
2404:6800:8005::bf blogger.com
2404:6800:8005::bf buttons.blogger.com
2404:6800:8005::bf beta.blogger.com
2404:6800:8005::bf draft.blogger.com #Blogger 测试区
2404:6800:8005::bf status.blogger.com #Blogger 状态
2404:6800:8005::bf help.blogger.com #支持中心
2404:6800:8005::bf buzz.blogger.com #Blogger Buzz博客(英文)
2404:6800:8005::bf photos1.blogger.com
2404:6800:8005::bf bp0.blogger.com
2404:6800:8005::bf bp1.blogger.com
2404:6800:8005::bf bp2.blogger.com
2404:6800:8005::bf bloggerphotos.l.google.com
2404:6800:8005::62 blogger.google.com
2404:6800:8005::62 www2.blogger.com
2404:6800:8005::62 blogger.l.google.com
2404:6800:8005::62 http://www.blogblog.com
2404:6800:8005::62 www1.blogblog.com
2404:6800:8005::62 www2.blogblog.com
2404:6800:8005::62 img.blogblog.com
2404:6800:8005::62 img1.blogblog.com
2404:6800:8005::62 img2.blogblog.com
2404:6800:8005::62 img.blshe.com

#Blogspot 博客服务
2404:6800:8005::62 http://www.blogspot.com #主页
2404:6800:8005::62 blogsofnote.blogspot.com #留言博客(英文版本)
2404:6800:8005::62 knownissues.blogspot.com #已知问题
2404:6800:8005::62 1.bp.blogspot.com #
2404:6800:8005::62 2.bp.blogspot.com #
2404:6800:8005::62 3.bp.blogspot.com #
2404:6800:8005::62 4.bp.blogspot.com #
2404:6800:8005::62 bloggertemplatespreview.blogspot.com #模板编辑器的实时预览功能

#Google 官方博客群
2404:6800:8005::62 adwordsapi.blogspot.com
2404:6800:8005::62 adsense-zhs.blogspot.com
2404:6800:8005::62 android-developers.blogspot.com
2404:6800:8005::62 apacdeveloper.blogspot.com #Google Asia Pacific Developer Blog
2404:6800:8005::62 booksearch.blogspot.com #Inside Google Books
2404:6800:8005::62 chrome.blogspot.com
2404:6800:8005::62 doubleclickpublishersapi.blogspot.com
2404:6800:8005::62 emeadev.blogspot.com #Google Europe, Middle East & Africa Developer Blog
2404:6800:8005::62 gearsblog.blogspot.com
2404:6800:8005::62 google-code-featured.blogspot.com #Featured Projects on Google Code
2404:6800:8005::62 google-entertainment-it.blogspot.com
2404:6800:8005::62 google-opensource.blogspot.com
2404:6800:8005::62 googleajaxsearchapi.blogspot.com
2404:6800:8005::62 googleappengine.blogspot.com
2404:6800:8005::62 googleappsdeveloper.blogspot.com
2404:6800:8005::62 googleblog.blogspot.com #Official Google Blog
2404:6800:8005::62 googlecheckout.blogspot.com
2404:6800:8005::62 googlecheckoutapi.blogspot.com
2404:6800:8005::62 googlechinablog.blogspot.com
2404:6800:8005::62 googlechromereleases.blogspot.com #Google Chrome Releases
2404:6800:8005::62 googlecode.blogspot.com
2404:6800:8005::62 googlecustomsearch.blogspot.com #Google Custom Search Blog
2404:6800:8005::62 googleenterprise.blogspot.com
2404:6800:8005::62 googlegeodevelopers.blogspot.com #Google Geo Developers Blog
2404:6800:8005::62 googlemashupeditor.blogspot.com
2404:6800:8005::62 googlemobile.blogspot.com
2404:6800:8005::62 googleresearch.blogspot.com
2404:6800:8005::62 googletalk.blogspot.com
2404:6800:8005::62 googlewebmaster-cn.blogspot.com
2404:6800:8005::62 googlewebmastercentral.blogspot.com
2404:6800:8005::62 googlewebtoolkit.blogspot.com
2404:6800:8005::62 golangblog.blogspot.com
2404:6800:8005::62 gmailblog.blogspot.com
2404:6800:8005::62 igoogledeveloper.blogspot.com #iGoogle Developer Blog
2404:6800:8005::62 webmproject.blogspot.com
2404:6800:8005::62 youtube-global.blogspot.com #YouTube Blog
#BlogSpot 上的其他常用博客
2404:6800:8005::62 googlesystem.blogspot.com #Google Operating System
2404:6800:8005::62 chinafreenet.blogspot.com #中国自由网
2404:6800:8005::62 gregmankiw.blogspot.com #GREG MANKIW’S BLOG
2404:6800:8005::62 xiangeliushui.blogspot.com #年华似水,岁月如歌
2404:6800:8005::62 chinagfw.blogspot.com #GFW Blog
2404:6800:8005::62 wallpapers-arena.blogspot.com #Wallpapers Arena
2404:6800:8005::62 ggq.blogspot.com #GG圈
2404:6800:8005::62 whiteappleer.blogspot.com #WA+ER
2404:6800:8005::62 rain-reader.blogspot.com #Nostalgia: Those Who Remain
2404:6800:8005::62 unityteam1.blogspot.com #生活圈 BLOG
2404:6800:8005::62 ipv6-or-no-ipv6.blogspot.com #IPv6 Related Stuff
2404:6800:8005::62 gysj.blogspot.com #
2404:6800:8005::62 szncu.blogspot.com #
#2404:6800:8005::62 *.blogspot.com #可以添加你自己的博客地址到这里

#Checkout 买家
2404:6800:8005::73 checkout.google.com

#Orkut 网络社区(尚未部署至 ipv6)
2404:6800:8005::69 help.orkut.com
2404:6800:8005::62 officialorkutblog.blogspot.com
2404:6800:8003::79 blog.orkut.com
2404:6800:8003::79 en.blog.orkut.com

#Sites 协作平台
2404:6800:8005::65 sites.google.com
2404:6800:8005::62 gsamplemaps.googlepages.com

#Google Apps 企业应用套件
2404:6800:8005::62 apps.google.com #主页
2404:6800:8003::79 ghs.google.com
2404:6800:8003::79 ghs46.google.com #GHS 双栈入口!
2404:6800:8003::79 ghs.l.google.com
2404:6800:8003::79 ghs46.l.google.com
2404:6800:8003::79 blog.opensocial.org
2404:6800:8003::79 govecn.org
2404:6800:8003::79 http://www.govecn.org
2404:6800:8003::79 1984talk.org
2404:6800:8003::79 http://www.1984talk.org
#2404:6800:8003::79 ghs.google.com #可以添加你 GApps 域名的博客地址 / GSites 地址到这里

#Mashups/App Engine GAE
2404:6800:8005::67 googlemashups.com #Google Mashup Editor
2404:6800:8005::68 http://www.googlemashups.com
2404:6800:8005::62 googlemashups.l.google.com
2404:6800:8005::63 *.googlemashups.com
2404:6800:8005::64 appengine.google.com #主页
2404:6800:8005::62 appspot.l.google.com #
2404:6800:8005::8d *.appspot.com
2404:6800:8005::8d productideas.appspot.com #Google 汇问
2404:6800:8005::8d wave-api.appspot.com #Google Wave API
2404:6800:8005::8d wave-skynet.appspot.com #SkyNet
2404:6800:8005::8d cactus-wave.appspot.com #
2404:6800:8005::8d storegadgetwizard.appspot.com #Google Checkout Store Gadget
2404:6800:8005::8d moderator.appspot.com #Google Moderator
2404:6800:8005::8d haiticrisis.appspot.com #Google Person Finder: Haiti Earthquake
2404:6800:8005::8d mytracks.appspot.com #My Tracks for Android
2404:6800:8005::8d reader2twitter.appspot.com #Reader2Tweet
2404:6800:8005::8d twitese.appspot.com
2404:6800:8005::8d gfw.appspot.com
2404:6800:8005::8d go2china9.appspot.com
2404:6800:8005::8d mirrorrr.appspot.com
2404:6800:8005::8d mirrornt.appspot.com
2404:6800:8005::8d soproxy.appspot.com
2404:6800:8005::8d so-proxy.appspot.com
2404:6800:8005::8d go-west.appspot.com
2404:6800:8005::8d proxytea.appspot.com
2404:6800:8005::8d sivanproxy.appspot.com
2404:6800:8005::8d proxybay.appspot.com
2404:6800:8005::8d ipgoto.appspot.com
2404:6800:8005::8d meme2028.appspot.com
2404:6800:8005::8d autoproxy2pac.appspot.com

#Google APIs 开发接口服务
2404:6800:8005::62 chart.apis.google.com #Google 图表 API
2404:6800:8005::5f *.googleapis.com
2404:6800:8005::5f translate.googleapis.com #Google 翻译 API
2404:6800:8005::5f ajax.googleapis.com #Ajax API
2404:6800:8005::5f googleapis-ajax.google.com
2404:6800:8005::5f googleapis-ajax.l.google.com
2404:6800:8005::5f commondatastorage.googleapis.com #

#Google Hosted 托管服务
2404:6800:8005::84 http://www.googlehosted.com
2404:6800:8005::84 music.googleusercontent.com #音乐播放器 专辑封面 等
2404:6800:8005::84 googlehosted.l.google.com
2404:6800:8005::62 base.googlehosted.com
2404:6800:8005::62 base0.googlehosted.com
2404:6800:8005::62 base1.googlehosted.com
2404:6800:8005::62 base2.googlehosted.com
2404:6800:8005::62 base3.googlehosted.com
2404:6800:8005::62 base4.googlehosted.com
2404:6800:8005::62 base5.googlehosted.com

#GoogleUserContent 用户自定义的Google服务
2404:6800:8005::84 http://www.googleusercontent.com #
2404:6800:8005::84 clients1.googleusercontent.com #
2404:6800:8005::84 clients2.googleusercontent.com #
2404:6800:8005::84 webcache.googleusercontent.com #网页快照
2404:6800:8005::84 lh0.googleusercontent.com #
2404:6800:8005::84 lh1.googleusercontent.com #
2404:6800:8005::84 lh2.googleusercontent.com #
2404:6800:8005::84 lh3.googleusercontent.com #
2404:6800:8005::62 lh3.googleusercontent.com #谷歌音乐(大陆)
2404:6800:8005::62 lh4.googleusercontent.com #谷歌音乐(大陆)
2404:6800:8005::62 lh5.googleusercontent.com #谷歌音乐(大陆)
2404:6800:8005::62 lh6.googleusercontent.com #谷歌音乐(大陆)
2404:6800:8005::84 s2.googleusercontent.com #
2404:6800:8005::84 wave.googleusercontent.com #Wave
2404:6800:8005::84 blogger.googleusercontent.com #Blogger
2404:6800:8005::84 translate.googleusercontent.com #翻译
2404:6800:8005::84 music-onebox.googleusercontent.com #音乐歌曲CD封面图片
2404:6800:8005::84 spreadsheets-opensocial.googleusercontent.com #表格
2404:6800:8005::84 www-opensocial.googleusercontent.com #
2404:6800:8005::84 www-gm-opensocial.googleusercontent.com #Gmail?
2404:6800:8005::84 www-opensocial-sandbox.googleusercontent.com #SandBox
2404:6800:8005::84 www-open-opensocial.googleusercontent.com #
2404:6800:8005::84 1-open-opensocial.googleusercontent.com #
2404:6800:8005::84 www-focus-opensocial.googleusercontent.com #缩略图
2404:6800:8005::84 images0-focus-opensocial.googleusercontent.com #缩略图
2404:6800:8005::84 images1-focus-opensocial.googleusercontent.com #缩略图
2404:6800:8005::84 images2-focus-opensocial.googleusercontent.com #缩略图
2404:6800:8005::84 doc-00-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-08-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-10-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-14-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-0c-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-0g-7o-docs.googleusercontent.com #
2404:6800:8005::84 doc-0s-7o-docs.googleusercontent.com #
2404:6800:8005::84 www-focus-opensocial.googleusercontent.com #
2404:6800:8005::84 0-focus-opensocial.googleusercontent.com #
2404:6800:8005::84 1-focus-opensocial.googleusercontent.com #
2404:6800:8005::84 2-focus-opensocial.googleusercontent.com #
2404:6800:8005::84 3-focus-opensocial.googleusercontent.com #
2404:6800:8005::84 www-open-opensocial.googleusercontent.com #
2404:6800:8005::84 0-open-opensocial.googleusercontent.com #
2404:6800:8005::84 1-open-opensocial.googleusercontent.com #
2404:6800:8005::84 2-open-opensocial.googleusercontent.com #
2404:6800:8005::84 3-open-opensocial.googleusercontent.com #
2404:6800:8005::84 www-wave-opensocial.googleusercontent.com #Wave
2404:6800:8005::84 0-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 1-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 2-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 3-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 4-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 5-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 6-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 7-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 8-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 9-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 10-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 11-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 12-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 13-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 14-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 15-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 16-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 17-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 18-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 19-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 20-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 21-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 22-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 23-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 24-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 25-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 26-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 27-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 28-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 29-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 30-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 31-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 32-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 33-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 34-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 35-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 36-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 37-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 38-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 39-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 40-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 1927502848-wave-opensocial.googleusercontent.com #
2404:6800:8005::84 la5dhjn62ripv179lf7outfl68h6dc3c-a-wave-opensocial.googleusercontent.com
2404:6800:8005::84 3hdrrlnlknhi77nrmsjnjr152ueo3soc-a-calendar-opensocial.googleusercontent.com
2404:6800:8005::84 eds9earadhd329tuipi6kfc947ts928j-a-sites-opensocial.googleusercontent.com
2404:6800:8005::84 sp5ovcebgtpf6rg65f53gdnvqtt3a58n-a-sites-opensocial.googleusercontent.com

#Chrome 谷歌浏览器
2404:6800:8005::64 chrome.google.com
2404:6800:8005::65 browsersync.google.com
2404:6800:8005::65 browsersync.l.google.com
2404:6800:8005::63 toolbarqueries.google.com #PageRank 查询(工具栏显示)
2404:6800:8005::63 toolbarqueries.clients.google.com
2404:6800:8005::63 toolbarqueries.l.google.com

#Chromium Chromium 项目
2404:6800:8005::65 chromium.org #跳转至 www
2404:6800:8003::79 http://www.chromium.org
2404:6800:8003::79 dev.chromium.org
2404:6800:8003::79 blog.chromium.org
2404:6800:8005::62 build.chromium.org

#Chromium OS Chromium 操作系统
2404:6800:8003::79 goto.ext.google.com
2404:6800:8005::8d welcome-cros.appspot.com #Chromium 主菜单

#SafeBrowing 安全浏览
2404:6800:8005::62 sb.google.com #安全浏览检测 API
2001:4860:8010::88 sb.l.google.com
2404:6800:8005::62 sb-ssl.google.com
2001:4860:8010::be sb-ssl.google.com
2001:4860:8010::5b sb-ssl.google.com
2001:4860:8010::5d sb-ssl.google.com
2001:4860:8010::88 sb-ssl.google.com
2404:6800:8005::62 sb-ssl.l.google.com
2404:6800:8005::62 safebrowsing.clients.google.com #安全浏览警告页面
2404:6800:8005::62 safebrowsing-cache.google.com #安全浏览警告数据(分块加载)
2001:4860:4001:402::17 safebrowsing.cache.l.google.com

#Toolbar 工具栏
2404:6800:8005::62 toolbar.google.com

#Desktop 桌面
2404:6800:8005::62 desktop.google.com
2404:6800:8005::62 desktop.l.google.com

#Google Earth Google地球
2404:6800:8005::65 earth.google.com

#Google Mars Google火星地图
2404:6800:8005::65 mars.google.com

#Download 下载
2404:6800:8005::5b dl.google.com
2404:6800:8005::5d dl.l.google.com
2404:6800:8005::88 dl-ssl.google.com

#Sandbox 沙盒
2404:6800:8005::51 sandbox.google.com

#Wave 波浪
2404:6800:8005::76 wave.google.com
2404:6800:8005::76 www4.l.google.com
2404:6800:8005::76 wave0.google.com
2404:6800:8005::76 wave1.google.com
2404:6800:8005::62 googlewave.com

#WiFi
2404:6800:8005::7b wifi.google.com
2404:6800:8005::62 wifi.l.google.com

#GTalk 聊天
2404:6800:8005::62 talk.l.google.com
2404:6800:8005::62 default.talk.google.com
2404:6800:8005::62 talkgadget.google.com
2404:6800:8005::62 rtmp0.google.com
2404:6800:8005::62 users.talk.google.com

#Buzz
2404:6800:8005::62 buzz.google.com

#Answers/Guru/WenDa 问答社区(国际版已停止运营)
2404:6800:8005::66 answers.google.com
2404:6800:8005::62 guru.google.com
2404:6800:8005::62 guru.google.co.th #泰国
2404:6800:8005::2e wenda.google.com.hk

#Fusion RSS 聚合指南
2404:6800:8005::62 fusion.google.com

#iGoogle Modules iGoogle 小工具
2404:6800:8005::62 gmodules.com
2404:6800:8005::62 http://www.gmodules.com
2404:6800:8005::62 http://www.ig.gmodules.com
2404:6800:8005::62 ig.gmodules.com
2404:6800:8005::62 ads.gmodules.com
2404:6800:8005::62 p.gmodules.com
2404:6800:8005::62 1.ig.gmodules.com
2404:6800:8005::62 2.ig.gmodules.com
2404:6800:8005::62 3.ig.gmodules.com
2404:6800:8005::62 4.ig.gmodules.com
2404:6800:8005::62 5.ig.gmodules.com
2404:6800:8005::62 6.ig.gmodules.com
2404:6800:8005::62 maps.gmodules.com
2404:6800:8005::62 img0.gmodules.com
2404:6800:8005::62 img1.gmodules.com
2404:6800:8005::62 img2.gmodules.com
2404:6800:8005::62 img3.gmodules.com
2404:6800:8005::62 skins.gmodules.com
2404:6800:8005::62 friendconnect.gmodules.com
2404:6800:8005::62 mc8tdi0ripmbpds25eboaupdulritrp6.friendconnect.gmodules.com
2404:6800:8005::62 r1rk9np7bpcsfoeekl0khkd2juj27q3o.friendconnect.gmodules.com
2404:6800:8005::62 r1rk9np7bpcsfoeekl0khkd2juj27q3o.a.friendconnect.gmodules.com

#GStatic Google 静态文件存储
2404:6800:8005::62 http://www.gstatic.com
2404:6800:8005::62 csi.gstatic.com
2404:6800:8005::62 maps.gstatic.com
2404:6800:8005::78 ssl.gstatic.com
2404:6800:8005::62 t0.gstatic.com
2404:6800:8005::62 t1.gstatic.com
2404:6800:8005::62 t2.gstatic.com
2404:6800:8005::62 t3.gstatic.com
2404:6800:8005::62 t4.gstatic.com
2404:6800:8005::62 mt0.gstatic.com

##Google其他服务
#YouTube
2404:6800:8005::65 http://www.youtube.com
2404:6800:8005::65 http://www.youtube-nocookie.com
2404:6800:8005::64 youtube-ui-china.l.google.com
2404:6800:8005::64 m.youtube.com
2404:6800:8005::64 tw.youtube.com
2404:6800:8005::65 youtu.be
2404:6800:8005::64 gdata.youtube.com
2404:6800:8005::64 help.youtube.com
2404:6800:8005::64 upload.youtube.com
2404:6800:8005::64 insight.youtube.com
2404:6800:8005::64 img.youtube.com
2404:6800:8005::64 s2.youtube.com
2404:6800:8005::64 youtube.com
2404:6800:8003::79 apiblog.youtube.com #YouTube API 开发博客
2404:6800:8005::64 clients1.youtube.com
2001:4860:4001:402::15 static.cache.l.google.com
2404:6800:8005::76 ytimg.l.google.com
2404:6800:8005::76 i.ytimg.com
2404:6800:8005::76 i1.ytimg.com
2404:6800:8005::76 i2.ytimg.com
2404:6800:8005::76 i3.ytimg.com
2404:6800:8005::76 i4.ytimg.com
2404:6800:8005::76 d.yimg.com
2404:6800:8005::76 s.ytimg.com

2404:6800:4001::10 v1.lscache1.c.youtube.com
2404:6800:4001::10 v1.lscache2.c.youtube.com
2404:6800:4001::10 v1.lscache3.c.youtube.com
2404:6800:4001::10 v1.lscache4.c.youtube.com
2404:6800:4001:1::10 v1.lscache5.c.youtube.com
2404:6800:4001::10 v1.lscache6.c.youtube.com
2404:6800:4001::10 v1.lscache7.c.youtube.com
2404:6800:4001::10 v1.lscache8.c.youtube.com
2404:6800:4001:1::13 v2.lscache1.c.youtube.com
2404:6800:4001:1::13 v2.lscache2.c.youtube.com
2404:6800:4001::13 v2.lscache3.c.youtube.com
2404:6800:4001:1::13 v2.lscache4.c.youtube.com
2404:6800:4001::13 v2.lscache5.c.youtube.com
2404:6800:4001::13 v2.lscache6.c.youtube.com
2404:6800:4001::13 v2.lscache7.c.youtube.com
2404:6800:4001::13 v2.lscache8.c.youtube.com
2404:6800:4001::16 v3.lscache1.c.youtube.com
2404:6800:4001::16 v3.lscache2.c.youtube.com
2404:6800:4001::16 v3.lscache3.c.youtube.com
2404:6800:4001::16 v3.lscache4.c.youtube.com
2404:6800:4001::16 v3.lscache5.c.youtube.com
2404:6800:4001::16 v3.lscache6.c.youtube.com
2404:6800:4001:1::16 v3.lscache7.c.youtube.com
2404:6800:4001::16 v3.lscache8.c.youtube.com
2404:6800:4001::19 v4.lscache1.c.youtube.com
2404:6800:4001:1::19 v4.lscache2.c.youtube.com
2404:6800:4001::19 v4.lscache3.c.youtube.com
2404:6800:4001::19 v4.lscache4.c.youtube.com
2404:6800:4001::19 v4.lscache5.c.youtube.com
2404:6800:4001::19 v4.lscache6.c.youtube.com
2404:6800:4001::19 v4.lscache7.c.youtube.com
2404:6800:4001::19 v4.lscache8.c.youtube.com
2404:6800:4001::1c v5.lscache1.c.youtube.com
2404:6800:4001:1::1c v5.lscache2.c.youtube.com
2404:6800:4001::1c v5.lscache3.c.youtube.com
2404:6800:4001:1::1c v5.lscache4.c.youtube.com
2404:6800:4001::1c v5.lscache5.c.youtube.com
2404:6800:4001::1c v5.lscache6.c.youtube.com
2404:6800:4001::1c v5.lscache7.c.youtube.com
2404:6800:4001::1c v5.lscache8.c.youtube.com
2404:6800:4001::1f v6.lscache1.c.youtube.com
2404:6800:4001::1f v6.lscache2.c.youtube.com
2404:6800:4001::1f v6.lscache3.c.youtube.com
2404:6800:4001::1f v6.lscache4.c.youtube.com
2404:6800:4001::1f v6.lscache5.c.youtube.com
2404:6800:4001::1f v6.lscache6.c.youtube.com
2404:6800:4001::1f v6.lscache7.c.youtube.com
2404:6800:4001:1::1f v6.lscache8.c.youtube.com
2404:6800:4001::22 v7.lscache1.c.youtube.com
2404:6800:4001::22 v7.lscache2.c.youtube.com
2404:6800:4001::22 v7.lscache3.c.youtube.com
2404:6800:4001:1::22 v7.lscache4.c.youtube.com
2404:6800:4001::22 v7.lscache5.c.youtube.com
2404:6800:4001:1::22 v7.lscache6.c.youtube.com
2404:6800:4001::22 v7.lscache7.c.youtube.com
2404:6800:4001::22 v7.lscache8.c.youtube.com
2404:6800:4001::25 v8.lscache1.c.youtube.com
2404:6800:4001::25 v8.lscache2.c.youtube.com
2404:6800:4001:1::25 v8.lscache3.c.youtube.com
2404:6800:4001::25 v8.lscache4.c.youtube.com
2404:6800:4001:1::25 v8.lscache5.c.youtube.com
2404:6800:4001::25 v8.lscache6.c.youtube.com
2404:6800:4001::25 v8.lscache7.c.youtube.com
2404:6800:4001:1::25 v8.lscache8.c.youtube.com
2404:6800:4001::11 v9.lscache1.c.youtube.com
2404:6800:4001::11 v9.lscache2.c.youtube.com
2404:6800:4001::11 v9.lscache3.c.youtube.com
2404:6800:4001:1::11 v9.lscache4.c.youtube.com
2404:6800:4001:1::11 v9.lscache5.c.youtube.com
2404:6800:4001::11 v9.lscache6.c.youtube.com
2404:6800:4001::11 v9.lscache7.c.youtube.com
2404:6800:4001::11 v9.lscache8.c.youtube.com
2404:6800:4001::14 v10.lscache1.c.youtube.com
2404:6800:4001::14 v10.lscache2.c.youtube.com
2404:6800:4001::14 v10.lscache3.c.youtube.com
2404:6800:4001::14 v10.lscache4.c.youtube.com
2404:6800:4001::14 v10.lscache5.c.youtube.com
2404:6800:4001::14 v10.lscache6.c.youtube.com
2404:6800:4001:1::14 v10.lscache7.c.youtube.com
2404:6800:4001::14 v10.lscache8.c.youtube.com
2404:6800:4001::17 v11.lscache1.c.youtube.com
2404:6800:4001::17 v11.lscache2.c.youtube.com
2404:6800:4001::17 v11.lscache3.c.youtube.com
2404:6800:4001::17 v11.lscache4.c.youtube.com
2404:6800:4001::17 v11.lscache5.c.youtube.com
2404:6800:4001::17 v11.lscache6.c.youtube.com
2404:6800:4001::17 v11.lscache7.c.youtube.com
2404:6800:4001::17 v11.lscache8.c.youtube.com
2404:6800:4001::1a v12.lscache1.c.youtube.com
2404:6800:4001:1::1a v12.lscache2.c.youtube.com
2404:6800:4001::1a v12.lscache3.c.youtube.com
2404:6800:4001::1a v12.lscache4.c.youtube.com
2404:6800:4001::1a v12.lscache5.c.youtube.com
2404:6800:4001::1a v12.lscache6.c.youtube.com
2404:6800:4001::1a v12.lscache7.c.youtube.com
2404:6800:4001::1a v12.lscache8.c.youtube.com
2404:6800:4001::1d v13.lscache1.c.youtube.com
2404:6800:4001::1d v13.lscache2.c.youtube.com
2404:6800:4001::1d v13.lscache3.c.youtube.com
2404:6800:4001::1d v13.lscache4.c.youtube.com
2404:6800:4001:1::1d v13.lscache5.c.youtube.com
2404:6800:4001::1d v13.lscache6.c.youtube.com
2404:6800:4001::1d v13.lscache7.c.youtube.com
2404:6800:4001::1d v13.lscache8.c.youtube.com
2404:6800:4001::20 v14.lscache1.c.youtube.com
2404:6800:4001::20 v14.lscache2.c.youtube.com
2404:6800:4001::20 v14.lscache3.c.youtube.com
2404:6800:4001::20 v14.lscache4.c.youtube.com
2404:6800:4001::20 v14.lscache5.c.youtube.com
2404:6800:4001::20 v14.lscache6.c.youtube.com
2404:6800:4001::20 v14.lscache7.c.youtube.com
2404:6800:4001:1::20 v14.lscache8.c.youtube.com
2404:6800:4001::23 v15.lscache1.c.youtube.com
2404:6800:4001::23 v15.lscache2.c.youtube.com
2404:6800:4001::23 v15.lscache3.c.youtube.com
2404:6800:4001:1::23 v15.lscache4.c.youtube.com
2404:6800:4001::23 v15.lscache5.c.youtube.com
2404:6800:4001:1::23 v15.lscache6.c.youtube.com
2404:6800:4001::23 v15.lscache7.c.youtube.com
2404:6800:4001::23 v15.lscache8.c.youtube.com
2404:6800:4001::26 v16.lscache1.c.youtube.com
2404:6800:4001::26 v16.lscache2.c.youtube.com
2404:6800:4001:1::26 v16.lscache3.c.youtube.com
2404:6800:4001::26 v16.lscache4.c.youtube.com
2404:6800:4001::26 v16.lscache5.c.youtube.com
2404:6800:4001::26 v16.lscache6.c.youtube.com
2404:6800:4001::26 v16.lscache7.c.youtube.com
2404:6800:4001::26 v16.lscache8.c.youtube.com
2404:6800:4001::12 v17.lscache1.c.youtube.com
2404:6800:4001::12 v17.lscache2.c.youtube.com
2404:6800:4001::12 v17.lscache3.c.youtube.com
2404:6800:4001::12 v17.lscache4.c.youtube.com
2404:6800:4001::12 v17.lscache5.c.youtube.com
2404:6800:4001::12 v17.lscache6.c.youtube.com
2404:6800:4001::12 v17.lscache7.c.youtube.com
2404:6800:4001::12 v17.lscache8.c.youtube.com
2404:6800:4001:1::15 v18.lscache1.c.youtube.com
2404:6800:4001:1::15 v18.lscache2.c.youtube.com
2404:6800:4001::15 v18.lscache3.c.youtube.com
2404:6800:4001::15 v18.lscache4.c.youtube.com
2404:6800:4001::15 v18.lscache5.c.youtube.com
2404:6800:4001::15 v18.lscache6.c.youtube.com
2404:6800:4001:1::15 v18.lscache7.c.youtube.com
2404:6800:4001::15 v18.lscache8.c.youtube.com
2404:6800:4001:1::18 v19.lscache1.c.youtube.com
2404:6800:4001::18 v19.lscache2.c.youtube.com
2404:6800:4001:1::18 v19.lscache3.c.youtube.com
2404:6800:4001:1::18 v19.lscache4.c.youtube.com
2404:6800:4001::18 v19.lscache5.c.youtube.com
2404:6800:4001::18 v19.lscache6.c.youtube.com
2404:6800:4001::18 v19.lscache7.c.youtube.com
2404:6800:4001::18 v19.lscache8.c.youtube.com
2404:6800:4001::1b v20.lscache1.c.youtube.com
2404:6800:4001::1b v20.lscache2.c.youtube.com
2404:6800:4001::1b v20.lscache3.c.youtube.com
2404:6800:4001::1b v20.lscache4.c.youtube.com
2404:6800:4001:1::1b v20.lscache5.c.youtube.com
2404:6800:4001::1b v20.lscache6.c.youtube.com
2404:6800:4001::1b v20.lscache7.c.youtube.com
2404:6800:4001::1b v20.lscache8.c.youtube.com
2404:6800:4001::1e v21.lscache1.c.youtube.com
2404:6800:4001::1e v21.lscache2.c.youtube.com
2404:6800:4001:1::1e v21.lscache3.c.youtube.com
2404:6800:4001::1e v21.lscache4.c.youtube.com
2404:6800:4001::1e v21.lscache5.c.youtube.com
2404:6800:4001:1::1e v21.lscache6.c.youtube.com
2404:6800:4001::1e v21.lscache7.c.youtube.com
2404:6800:4001::1e v21.lscache8.c.youtube.com
2404:6800:4001::21 v22.lscache1.c.youtube.com
2404:6800:4001::21 v22.lscache2.c.youtube.com
2404:6800:4001::21 v22.lscache3.c.youtube.com
2404:6800:4001::21 v22.lscache4.c.youtube.com
2404:6800:4001:1::21 v22.lscache5.c.youtube.com
2404:6800:4001::21 v22.lscache6.c.youtube.com
2404:6800:4001::21 v22.lscache7.c.youtube.com
2404:6800:4001::21 v22.lscache8.c.youtube.com
2404:6800:4001:1::24 v23.lscache1.c.youtube.com
2404:6800:4001::24 v23.lscache2.c.youtube.com
2404:6800:4001::24 v23.lscache3.c.youtube.com
2404:6800:4001::24 v23.lscache4.c.youtube.com
2404:6800:4001::24 v23.lscache5.c.youtube.com
2404:6800:4001::24 v23.lscache6.c.youtube.com
2404:6800:4001::24 v23.lscache7.c.youtube.com
2404:6800:4001::24 v23.lscache8.c.youtube.com
2404:6800:4001::27 v24.lscache1.c.youtube.com
2404:6800:4001::27 v24.lscache2.c.youtube.com
2404:6800:4001::27 v24.lscache3.c.youtube.com
2404:6800:4001::27 v24.lscache4.c.youtube.com
2404:6800:4001:1::27 v24.lscache5.c.youtube.com
2404:6800:4001::27 v24.lscache6.c.youtube.com
2404:6800:4001::27 v24.lscache7.c.youtube.com
2404:6800:4001:1::27 v24.lscache8.c.youtube.com

#Android Google手机操作系统
2404:6800:8005::62 http://www.android.com
2404:6800:8005::62 android.com
2404:6800:8003::79 developer.android.com
2404:6800:8005::62 source.android.com

#The Go Programming Language Go 编程语言
2404:6800:8005::62 golang.org
2404:6800:8005::62 http://www.golang.org
2404:6800:8003::79 blog.golang.org

#Analytics 分析(Google所提供的网站流量统计服务)
2404:6800:8005::61 http://www.google-analytics.com
2404:6800:8005::61 *.google-analytics.com
2404:6800:8005::61 ssl.google-analytics.com

#DoubleClick 曾经世界最大的网络广告服务商,06年底被Google并购,现AdSense服务指向域名
2001:4860:b006::94 ad.doubleclick.net
2001:4860:b006::95 ad-g.doubleclick.net
2001:4860:b006::95 ad-apac.doubleclick.net
2404:6800:8005::9a googleads.g.doubleclick.net
2404:6800:8005::9b feedads.g.doubleclick.net
2404:6800:8005::90 fls.uk.doubleclick.net
2404:6800:8005::8e *.au.doubleclick.net
2404:6800:8005::8f *.de.doubleclick.net
2404:6800:8005::90 *.uk.doubleclick.net
2404:6800:8005::90 *.fr.doubleclick.net
2404:6800:8005::92 *.jp.doubleclick.net

#FeedBurner
2404:6800:8005::62 feedburner.google.com
2404:6800:8005::62 http://www.feedburner.com
2404:6800:8005::62 feeds.feedburner.com
2404:6800:8005::62 feeds2.feedburner.com
2404:6800:8005::76 feedproxy.google.com #Feed 跳转代理

#GoogleSyndication Google广告服务 AdWord(Google广告词,对关键字进行右侧付费推广),原AdSense服务指向域名
2404:6800:8005::62 http://www.googlesyndication.com
2404:6800:8005::62 pagead2.googlesyndication.com
2404:6800:8005::62 buttons.googlesyndication.com
2404:6800:8005::62 domains.googlesyndication.com
2404:6800:8005::98 tpc.googlesyndication.com

#GoogleSyndication Google广告服务 AdWord
2404:6800:8005::70 adwords.google.com
2404:6800:8005::41 adwords.google.sk

#GoogleADServices Google广告服务 AdSense
2404:6800:8005::60 http://www.googleadservices.com
2404:6800:8005::a4 pagead2.googleadservices.com
2404:6800:8005::a7 partner.googleadservices.com

#Panoramio
2001:4860:8010::8d http://www.panoramio.com
2001:4860:8010::8d static.panoramio.com

2404:6800:8005::62 pagead.google.com
2404:6800:8005::62 pagead.l.google.com
2404:6800:8005::62 pagead2.google.com

#Goo.gl Google短网址服务
2404:6800:8005::62 goo.gl
2404:6800:8005::b8 service.urchin.com

#Google.org Google 公益
2404:6800:8003::79 blog.google.org

[转]使用Apache Avro

源地址:http://www.infoq.com/cn/articles/ApacheAvro

作者 Boris Lublinsky 译者 王恒涛 发布于 2011年3月11日

Avro[1]是最近加入到Apache的Hadoop家族的项目之一。为支持数据密集型应用,它定义了一种数据格式并在多种编程语言中支持这种格式。

Avro提供的功能类似于其他编组系统,如Thrift、Protocol Buffers等。而Avro的主要不同之处在于[2]:

  • “动态类型:Avro无需生成代码。数据总是伴以模式定义,这样就可以在不生成代码、静态数据类型的情况下对数据进行所有处理。这样有利于构建通用的数据处理系统和语言。
  • 无标记数据:由于在读取数据时有模式定义,这就大大减少了数据编辑所需的类型信息,从而减少序列化空间。
  • 不用手动分配的字段ID:当数据模式发生变化,处理数据时总是同时提供新旧模式,差异就可以用字段名来做符号化的分析。”

由于性能高、基本代码少和产出数据量精简等特点,Avro周围展开了众多活动——许多NoSQL实现,包括Hadoop、Cssandra等,都把Avro整合到它们的客户端API和储存功能中;已经有人对Avro与其他流行序列化框架做了Benchmark测试并得到结果[3],但是,目前尚无可供人们学习使用Avro的代码示例[4]。

在这篇文章中我将试着描述我使用Avro的经验,特别是:

  • 如何建立组件化Avro模式,使用组件搭建整体模式,分别保存在多个文件中
  • 在Avro中实现继承
  • 在Avro中实现多态
  • Avro文档的向后兼容性。

组件化Apache Avro模式

如Avro规范所述[5]Avro文档模式定义成JSON文件。在当前Avro实现中,模式类需要一个文件(或字符串)来表示内部模式。同XML模式不一样,Avro当前版本不支持向模式文档中导入(一个或多个)子模式,这往往迫使开发者编写非常复杂的模式定义[6],并大大复杂化了模式的重用。下面的代码示例给出了一个有趣的拆分和组合模式文件的例子。它基于模式类提供的一个toString()方法,该方法返回一个JSON字符串以表示给定的模式定义。用这种办法,我提供了一个简单AvroUtils,能够自动完成上述功能:

package com.navteq.avro.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.avro.Schema;

public class AvroUtils {

        private static Map<String, Schema> schemas = new HashMap<String, Schema>();

        private AvroUtils(){}

        public static void addSchema(String name, Schema schema){
               schemas.put(name, schema);

        }

        public static Schema getSchema(String name){
               return schemas.get(name);

        }

        public static String resolveSchema(String sc){

                String result = sc;
                for(Map.Entry<String, Schema> entry : schemas.entrySet())
                      result = replace(result, entry.getKey(),
                                        entry.getValue().toString());
                return result;

        }

        static String replace(String str, String pattern, String replace) {

                int s = 0;
                int e = 0;
                StringBuffer result = new StringBuffer();
                while ((e = str.indexOf(pattern, s)) >= 0) {
                result.append(str.substring(s, e));
                result.append(replace);
                s = e+pattern.length();

        }
        result.append(str.substring(s));
        return result.toString();

}

public static Schema parseSchema(String schemaString){

        String completeSchema = resolveSchema(schemaString);
        Schema schema = Schema.parse(completeSchema);
        String name = schema.getFullName();
        schemas.put(name, schema);
        return schema;

}

public static Schema parseSchema(InputStream in)throws IOException {

    StringBuffer out = new StringBuffer();
    byte[] b = new byte[4096];
    for (int n; (n = in.read(b)) != -1;) {
     out.append(new String(b, 0, n));
    }
    return parseSchema(out.toString());

}

public static Schema parseSchema(File file)throws IOException {

        FileInputStream fis = new FileInputStream(file);
        return parseSchema(fis);
    }
}

清单1 AvroUtils类

这个简单实现基于全局(静态)模式注册表,它由完全限定的模式名及与之对应的对象构成。对于每一个要解析的新模式,该实现在注册表中搜索已保存的完全限定模式名,并且在给定的模式中做字符串替换。模式字符串被解析之后,它的全名和模式名都存储在注册表中。

下面是一个简单的测试,展示如何使用这个类:

package com.navteq.avro.common;

import java.io.File;

import org.junit.Test;

public class AvroUtilsTest {

       private static final String schemaDescription =
         "{ \n" +
            " \"namespace\": \"com.navteq.avro\", \n" +
            " \"name\": \"FacebookUser\", \n" +
            " \"type\": \"record\",\n" +
            " \"fields\": [\n" +
                     " {\"name\": \"name\", \"type\": [\"string\", \"null\"] },\n" +
                     " {\"name\": \"num_likes\", \"type\": \"int\"},\n" +
                     " {\"name\": \"num_photos\", \"type\": \"int\"},\n" +
            " {\"name\": \"num_groups\", \"type\": \"int\"} ]\n" +
         "}";

       private static final String schemaDescriptionExt =
         " { \n" +
             " \"namespace\": \"com.navteq.avro\", \n" +
             " \"name\": \"FacebookSpecialUser\", \n" +
             " \"type\": \"record\",\n" +
             " \"fields\": [\n" +
                      " {\"name\": \"user\", \"type\": com.navteq.avro.FacebookUser },\n" +
                      " {\"name\": \"specialData\", \"type\": \"int\"} ]\n" +
          "}";

       @Test
       public void testParseSchema() throws Exception{

               AvroUtils.parseSchema(schemaDescription);
               Schema extended = AvroUtils.parseSchema(schemaDescriptionExt);
               System.out.println(extended.toString(true));
       }
}

清单2 AvroUtils测试

在这个测试中,第一个模式的完全限定名是com.navteq.avro.FacebookUser,替换正常运行并打印出以下结果:

{
  "type" : "record",
  "name" : "FacebookSpecialUser",
  "namespace" : "com.navteq.avro",
  "fields" : [ {
    "name" : "user",
    "type" : {
      "type" : "record",
      "name" : "FacebookUser",
      "fields" : [ {
        "name" : "name",
        "type" : [ "string", "null" ]
      }, {
        "name" : "num_likes",
        "type" : "int"
      }, {
        "name" : "num_photos",
        "type" : "int"
      }, {
        "name" : "num_groups",
        "type" : "int"
      } ]
    }
  }, {
    "name" : "specialData",
    "type" : "int"
  } ]
}

清单3 AvroUtilsTest的执行结果

使用Apache Avro实现继承

一种常见的定义数据的方法是通过继承——使用现有的数据定义并添加参数。虽然技术上Avro不支持继承[7],但要是实现一个类继承的结构非常简单。

如果我们有一个基类的定义——FacebookUser,如下:

{
"namespace": "com.navteq.avro",
"name": "FacebookUser",
"type": "record",
"fields": [
  {"name": "name", "type": ["string", "null"] },
  {"name": "num_likes", "type": "int"},
  {"name": "num_photos", "type": "int"},
  {"name": "num_groups", "type": "int"} ]
}

清单4 Facebook用户记录的定义

要创建一个FacebookSpecialUser定义非常简单,它大概是这样的:

{
    "namespace": "com.navteq.avro",
  "name": "FacebookSpecialUser",
  "type": "record",
  "fields": [
    {"name": "user", "type": com.navteq.avro.FacebookUser },
      {"name": "specialData", "type": "int"}
    ]
}

清单5 Facebook特殊的用户记录的定义

一个特殊的用户定义包含两个字段——Facebook的用户类型的用户和一个int类型的数据字段。

特殊Facebook用户的简单测试类如下:

package com.navteq.avro.inheritance;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.util.Utf8;
import org.junit.Before;
import org.junit.Test;

import com.navteq.avro.common.AvroUtils;

public class TestSimpleInheritance {

        private Schema schema;
        private Schema subSchema;

        @Before
        public void setUp() throws Exception {

                subSchema = AvroUtils.parseSchema(new File("resources/facebookUser.avro"));
                schema = AvroUtils.parseSchema(new File("resources/FacebookSpecialUser.avro"));

        }

        @Test
        public void testSimpleInheritance() throws Exception{
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                GenericDatumWriter writer =
                            new GenericDatumWriter(schema);
                Encoder encoder = new BinaryEncoder(outputStream);

                GenericRecord subRecord1 = new GenericData.Record(subSchema);
                subRecord1.put("name", new Utf8("Doctor Who"));
                subRecord1.put("num_likes", 1);
                subRecord1.put("num_photos", 0);
                subRecord1.put("num_groups", 423);
                GenericRecord record1 = new GenericData.Record(schema);
                record1.put("user", subRecord1);
                record1.put("specialData", 1);

                writer.write(record1, encoder);

                GenericRecord subRecord2 = new GenericData.Record(subSchema);
                subRecord2.put("name", new org.apache.avro.util.Utf8("Doctor WhoWho"));
                subRecord2.put("num_likes", 2);
                subRecord2.put("num_photos", 0);
                subRecord2.put("num_groups", 424);
                GenericRecord record2 = new GenericData.Record(schema);
                record2.put("user", subRecord2);
                record2.put("specialData", 2);

                writer.write(record2, encoder);

                encoder.flush();

                ByteArrayInputStream inputStream =
                        new ByteArrayInputStream(outputStream.toByteArray());
                Decoder decoder = DecoderFactory.defaultFactory().
                        createBinaryDecoder(inputStream, null);
                GenericDatumReader reader =
                        new GenericDatumReader(schema);
                while(true){
                        try{
                              GenericRecord result = reader.read(null, decoder);
                              System.out.println(result);
                        }
                        catch(EOFException eof){
                                break;
                        }
                        catch(Exception ex){
                                ex.printStackTrace();
                        }
                }
        }
}[8]

清单6 一个特殊的Facebook用户的测试类

运行这个测试类产生预期的结果:

{"user": {"name": "Doctor Who", "num_likes": 1, "num_photos": 0,
"num_groups": 423}, "specialData": 1}
{"user": {"name": "Doctor WhoWho", "num_likes": 2, "num_photos": 0,
"num_groups": 424}, "specialData": 2}

清单7 Facebook特殊用户的测试结果

如果唯一需要的是有包含基础数据和其他参数的记录,此代码工作正常,但它不提供多态性——读取相同记录时,没办法知道到底读的是哪个类型的记录。

使用ApacheAvro实现多态性

与谷歌protocol buffers不同[9],Avro不支持可选参数[10],上述继承的实现不适应于多态性的实现——这是由于必须具备特殊的数据参数。幸运的是,Avro支持联合体,允许省略某些记录的参数。下面的定义可用于创建一个多态的纪录。对于基准纪录,我将使用清单4中描述的例子。为了扩展我们将使用以下两个定义:

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUserExtension1",
   "type": "record",
   "fields": [
      {"name": "specialData1", "type": "int"}
     ]
}

清单8 首条扩展记录的定义

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUserExtension2",
   "type": "record",
   "fields": [
      {"name": "specialData2", "type": "int"}
     ]
}

清单9 第二条扩展记录的定义

有了以上两个定义一个多态记录可以定义如下:

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUser",
   "type": "record",
   "fields": [
      {"name": "type", "type": "string" },
      {"name": "user", "type": com.navteq.avro.FacebookUser },
        {"name": "extension1", "type":
            [com.navteq.avro.FacebookSpecialUserExtension1, "null"]},
        {"name": "extension2", "type":
            [com.navteq.avro.FacebookSpecialUserExtension2, "null"]}
      ]
}

清单10 Facebook特殊用户的多态定义

这里扩展1和扩展2都是可选的且二者皆可。为了使处理更简单,我添加了一个类型字段,可以用来明确定义的记录类型。

下面给出一个更好的多态记录的定义:

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUser1",
   "type": "record",
   "fields": [
      {"name": "type", "type": "string" },
      {"name": "user", "type": com.navteq.avro.FacebookUser },
        {"name": "extension", "type":
            [com.navteq.avro.FacebookSpecialUserExtension1,
            com.navteq.avro.FacebookSpecialUserExtension2,
            "null"]}
      ]
}

清单11 Facebook特殊用户的改进多态定义

下面给出一个多态Facebook特殊用户的简单测试类:

package com.navteq.avro.inheritance;
package com.navteq.avro.inheritance;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.JsonDecoder;
import org.apache.avro.io.JsonEncoder;
import org.apache.avro.util.Utf8;
import org.junit.Before;
import org.junit.Test;

import com.navteq.avro.common.AvroUtils;

public class TestInheritance {

        private Schema FBUser;
        private Schema base;
        private Schema ext1;
        private Schema ext2;

        @Before
        public void setUp() throws Exception {

                 base = AvroUtils.parseSchema(new File("resources/facebookUser.avro"));
                 ext1 = AvroUtils.parseSchema(
                         new File("resources/FacebookSpecialUserExtension1.avro"));
                 ext2 = AvroUtils.parseSchema(
                         new File("resources/FacebookSpecialUserExtension2.avro"));
                 FBUser = AvroUtils.parseSchema(new File("resources/FacebooklUserInheritance.avro"));
}

        @Test
        public void testInheritanceBinary() throws Exception{
                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                 GenericDatumWriter writer =
                         new GenericDatumWriter(FBUser);
                 Encoder encoder = new BinaryEncoder(outputStream);

                 GenericRecord baseRecord = new GenericData.Record(base);
                 baseRecord.put("name", new Utf8("Doctor Who"));
                 baseRecord.put("num_likes", 1);
                 baseRecord.put("num_photos", 0);
                 baseRecord.put("num_groups", 423);
                 GenericRecord FBrecord = new GenericData.Record(FBUser);
                 FBrecord.put("type", "base");
                 FBrecord.put("user", baseRecord);

                 writer.write(FBrecord, encoder);

                 baseRecord = new GenericData.Record(base);
                 baseRecord.put("name", new Utf8("Doctor WhoWho"));
                 baseRecord.put("num_likes", 1);
                 baseRecord.put("num_photos", 0);
                 baseRecord.put("num_groups", 423);
                 GenericRecord extRecord = new GenericData.Record(ext1);
                 extRecord.put("specialData1", 1);
                 FBrecord = new GenericData.Record(FBUser);
                 FBrecord.put("type", "extension1");
                 FBrecord.put("user", baseRecord);
                 FBrecord.put("extension", extRecord);

                 writer.write(FBrecord, encoder);

                 baseRecord = new GenericData.Record(base);
                 baseRecord.put("name", new org.apache.avro.util.Utf8("Doctor WhoWhoWho"));
                 baseRecord.put("num_likes", 2);
                 baseRecord.put("num_photos", 0);
                 baseRecord.put("num_groups", 424);
                 extRecord = new GenericData.Record(ext2);
                 extRecord.put("specialData2", 2);
                 FBrecord = new GenericData.Record(FBUser);
                 FBrecord.put("type", "extension2");
                 FBrecord.put("user", baseRecord);
                 FBrecord.put("extension", extRecord);

                 writer.write(FBrecord, encoder);

                 encoder.flush();                 byte[] data = outputStream.toByteArray();
                 ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
                 Decoder decoder =
                        DecoderFactory.defaultFactory().createBinaryDecoder(inputStream, null);
                 GenericDatumReader reader =
                        new GenericDatumReader(FBUser);
                 while(true){
                        try{
                               GenericRecord result = reader.read(null, decoder);
                               System.out.println(result);
                        }
                        catch(EOFException eof){
                               break;
                        }
                        catch(Exception ex){
                               ex.printStackTrace();
                        }
                 }
        }
}

清单12 一条多态Facebook用户记录的测试类

运行这个测试类产生的预期结果:

{"type": "base", "user": {"name": "Doctor Who", "num_likes": 1, "num_photos":
0, "num_groups": 423}, "extension": null}
{"type": "extension1", "user": {"name": "Doctor WhoWho", "num_likes": 1,
"num_photos": 0, "num_groups": 423}, "extension": {"specialData1": 1}}
{"type": "extension2", "user": {"name": "Doctor WhoWhoWho", "num_likes": 2,
"num_photos": 0, "num_groups": 424}, "extension": {"specialData2": 2}}

清单13 多态Facebook用户记录测试的执行结果

使用ApacheAvro的向后兼容性

XML的优势之一就是当模式定义使用可选参数扩展时具备向后兼容性。我们介绍一个第三扩展记录的定义来测试Avro的这个特性:

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUserExtension3",
   "type": "record",
   "fields": [
      {"name": "specialData3", "type": "int"}
   ]
}

清单14 第三扩展记录的定义

多态记录的变更定义如下:

{
     "namespace": "com.navteq.avro",
   "name": "FacebookSpecialUser11",
   "type": "record",
   "fields": [
     {"name": "type", "type": "string" },
     {"name": "user", "type": com.navteq.avro.FacebookUser },
       {"name": "extension", "type":
          [com.navteq.avro.FacebookSpecialUserExtension1,
          com.navteq.avro.FacebookSpecialUserExtension2,
          com.navteq.avro.FacebookSpecialUserExtension3,
          "null"]}
     ]
}

清单15 Facebook特殊用户的改进多态定义

为了能读取清单15中记录定义中的记录,清单12中的代码在修改后(但仍然用清单11中的记录定义来写数据)生成下列结果:

{"type": "base", "user": {"name": "Doctor Who", "num_likes": 1, "num_photos":
0, "num_groups": 423}, "extension": {"specialData3": 10}}
java.lang.ArrayIndexOutOfBoundsException
      at java.lang.System.arraycopy(Native Method)
      at org.apache.avro.io.BinaryDecoder.doReadBytes(BinaryDecoder.java:331)       at org.apache.avro.io.BinaryDecoder.readString(BinaryDecoder.java:265)       at org.apache.avro.io.ValidatingDecoder.readString(ValidatingDecoder.java:99)       at org.apache.avro.generic.GenericDatumReader.readString(GenericDatumReader.java:318)       at org.apache.avro.generic.GenericDatumReader.readString(GenericDatumReader.java:312)       at org.apache.avro.generic.GenericDatumReader.read(GenericDatumReader.java:120)       at org.apache.avro.generic.GenericDatumReader.readRecord(GenericDatumReader.java:142)       at org.apache.avro.generic.GenericDatumReader.read(GenericDatumReader.java:114)       at org.apache.avro.generic.GenericDatumReader.read(GenericDatumReader.java:105)       at com.navteq.avro.inheritance.TestInheritance.testInheritanceBinary(TestInheritance.java:119)       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)       at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)       at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)       at java.lang.reflect.Method.invoke(Unknown Source)

清单16 多态Facebook用户记录对扩展定义测试的执行结果

虽然Avro提供了一个能够解决这个问题的API——GenericDatumReader<GenericRecord>构造函数可以使用两个参数——分别用来写记录与读记录的模式,但这不总是解决向后兼容问题的一定可行的方法,因为它必须要记住用来写每条记录的所有模式。

一个更合适的解决方案是:从二进制编码器/解码器(它建立记录的二进制表象)切换到JSON编码器/解码器。在这种情况下代码有效,并产生以下结果:

{"type": "base", "user": {"name": "Doctor Who", "num_likes": 1, "num_photos":
0, "num_groups": 423}, "extension": null}
{"type": "extension1", "user": {"name": "Doctor WhoWho", "num_likes": 1,
"num_photos": 0, "num_groups": 423}, "extension": {"specialData1": 1}}
{"type": "extension2", "user": {"name": "Doctor WhoWhoWho", "num_likes": 2,
"num_photos": 0, "num_groups": 424}, "extension": {"specialData2": 2}}

清单17 应用JSON编码多态Facebook用户记录对扩展定义测试的执行结果

通过JSON的编码器,实际的数据转换成JSON:

{"type":"base","user":{"name":{"string":"Doctor
Who"},"num_likes":1,"num_photos":0,"num_groups":423},"extension":null}
{"type":"extension1","user":{"name":{"string":"Doctor
WhoWho"},"num_likes":1,"num_photos":0,"num_groups":423},"extension":{"FacebookSpecialUserExtension1":{"specialData1":1}}}
{"type":"extension2","user":{"name":{"string":"Doctor
WhoWhoWho"},"num_likes":2,"num_photos":0,"num_groups":424},"extension":{"FacebookSpecialUserExtension2":{"specialData2":2}}}

清单18 JSON编码下所转换的数据

还有一个需要考虑的问题,在我的测试中,同样的数据在二进制编码下产生的Avro记录的大小为89字节,而在JSON编码下产生了473字节。

结论

当前实现的Avro不直接支持模式的组件化或模式组件重用,但像本文中描述的一个简单的框架能够为这些特性提供支持。尽管Avro不直接支持多态性,文中利用适当的模式设计可以简单地实现多态数据模式。至于真正意义上向后兼容性问题,只有使用JSON编码的时候Avro才支持[11]。最后一点和Avro的特性没有多大关系,更多的是来自JSON。最后一点严重限制了Avro适用性(如果向后兼容性是必须的),使其使用范围局限为一种高级的JSON编组和处理API。

除了一般的(这里所用到的)Avro方法,也可以使用一个特定的Avro。这时候,可通过(Avro)生产特定的记录而非普通的记录。尽管有些说法指出[12]Avro的特定应用能够获得性能提升,以我使用当前Avro版本(1.4.1)的经验来看,两者有着同样的性能表现。


[1] http://hadoop.apache.org/avro/

[2] http://avro.apache.org/docs/1.4.1/

[3] http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

[4] 我在Avro编组Avro Map Reduce发现的几篇

[5] http://avro.apache.org/docs/current/spec.html

[6] 很有趣,Avro IDL支持子IDL

[7] 与明确支持类型定义中的基类型的XML不同

[8] 关于上面的代码需要指出的一点是,模式解析是在构造函数中完成的,原因在于构造解析是Avro实现中最昂贵的操作。

[9] http://code.google.com/p/protobuf/

[10] Avro支持“Null”,这不同于可选参数,在Avro中“Null”表示某个属性没有值

[11] 或者如果有旧版本的模式

[12] http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

查看英文原文:Using Apache Avro


感谢马国耀对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

[转]Java Annotation入门

Java Annotation入门

作者:cleverpig

版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
原 文:[http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html]http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html[/url]
关键字:Java,annotation,标注

摘要:
本 文针对java初学者或者annotation初次使用者全面地说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简 单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义annotation生成javabean映射xml文件) 还需要进一步的研究和探讨。涉及到深入annotation的内容,作者将在后文《Java Annotation高级应用》中谈到。

同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,

一、为什么使用Annotation:

在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另 外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利 而且减少了错误。

二、Annotation工作方式:

在5.0 版之前的Java平台已经具有了一些ad hoc annotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个 javadoc tag也是一个ad hoc annotation用来说明一个方法已过时。从Java5.0版发布以来,5.0平台提供了一个正式的annotation功能:允许开发者定义、使用 自己的annoatation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotaion 的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt)组成。
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取。
当然annotation在某种程度上使javadoc tag更加完整。一般情况下,如果这个标记对java文档产生影响或者用于生成java文档的话,它应该作为一个javadoc tag;否则将作为一个annotation。

三、Annotation使用方法:

1。类型声明方式:
通常,应用程序并不是必须定义annotation类型,但是定义annotation类型并非难事。Annotation类型声明于一般的接口声明极为类似,区别只在于它在interface关键字前面使用“@”符号。
annotation 类型的每个方法声明定义了一个annotation类型成员,但方法声明不必有参数或者异常声明;方法返回值的类型被限制在以下的范围: primitives、String、Class、enums、annotation和前面类型的数组;方法可以有默认值。

下面是一个简单的annotation类型声明:
清单1:

    /**
     * Describes the Request-For-Enhancement(RFE) that led
     * to the presence of the annotated API element.
     */
    public @interface RequestForEnhancement {
        int    id();
        String synopsis();
        String engineer() default "[unassigned]";
        String date();    default "[unimplemented]";
    }

代码中只定义了一个annotation类型RequestForEnhancement。

2。修饰方法的annotation声明方式:
annotation 是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。 annotations由“@+annotation类型+带有括号的成员-值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。

A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:

    @RequestForEnhancement(
        id       = 2868724,
        synopsis = "Enable time-travel",
        engineer = "Mr. Peabody",
        date     = "4/1/3007"
    )
    public static void travelThroughTime(Date destination) { ... }

B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:

    /**
     * Indicates that the specification of the annotated API element
     * is preliminary and subject to change.
     */
    public @interface Preliminary { }

作为上面没有成员的annotation类型声明的简写方式:
清单4:

    @Preliminary public class TimeTravel { ... }

C:如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:

    /**
     * Associates a copyright notice with the annotated API element.
     */
    public @interface Copyright {
        String value();
    }

更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:

    @Copyright("2002 Yoyodyne Propulsion Systems")
    public class OscillationOverthruster { ... }

3。一个使用实例:
结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:

    import java.lang.annotation.*;

    /**
     * Indicates that the annotated method is a test method.
     * This annotation should be used only on parameterless static methods.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test { }

值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。

在 上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的 annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的 annotation只能用于修饰方法声明。

下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:

    public class Foo {
        @Test public static void m1() { }
        public static void m2() { }
        @Test public static void m3() {
            throw new RuntimeException("Boom");
        }
        public static void m4() { }
        @Test public static void m5() { }
        public static void m6() { }
        @Test public static void m7() {
            throw new RuntimeException("Crash");
        }
        public static void m8() { }
    }

Here is the testing tool:

    import java.lang.reflect.*;

    public class RunTests {
       public static void main(String[] args) throws Exception {
          int passed = 0, failed = 0;
          for (Method m : Class.forName(args[0]).getMethods()) {
             if (m.isAnnotationPresent(Test.class)) {
                try {
                   m.invoke(null);
                   passed++;
                } catch (Throwable ex) {
                   System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                   failed++;
                }
             }
          }
          System.out.printf("Passed: %d, Failed %d%n", passed, failed);
       }
    }

这 个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法被 annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行 通过/失败的方法数量。
下面文字表示了如何运行这个基于annotation的测试工具:

清单9:

    $ java RunTests Foo
    Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
    Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
    Passed: 2, Failed 2

四、Annotation分类:

根据annotation的使用方法和用途主要分为以下几类:

1。内建Annotation——Java5.0版在java语法中经常用到的内建Annotation:
@Deprecated用于修饰已经过时的方法;
@Override用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings用于通知java编译器禁止特定的编译警告。

下面代码展示了内建Annotation类型的用法:
清单10:

package com.bjinfotech.practice.annotation;

/**
 * 演示如何使用java5内建的annotation
 * 参考资料:
 * http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
 * http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
 * http://mindprod.com/jgloss/annotations.html
 * @author cleverpig
 *
 */
import java.util.List;

public class UsingBuiltInAnnotation {
        //食物类
        class Food{}
        //干草类
        class Hay extends Food{}
        //动物类
        class Animal{
                Food getFood(){
                        return null;
                }
                //使用Annotation声明Deprecated方法
                @Deprecated
                void deprecatedMethod(){
                }
        }
        //马类-继承动物类
        class Horse extends Animal{
                //使用Annotation声明覆盖方法
                @Override
                Hay getFood(){
                        return new Hay();
                }
                //使用Annotation声明禁止警告
                @SuppressWarnings({"deprecation","unchecked"})
                void callDeprecatedMethod(List horseGroup){
                        Animal an=new Animal();
                        an.deprecatedMethod();
                        horseGroup.add(an);
                }
        }
}

2。开发者自定义Annotation:由开发者自定义Annotation类型。
下面是一个使用annotation进行方法测试的sample:

AnnotationDefineForTestFunction类型定义如下:
清单11:

package com.bjinfotech.practice.annotation;

import java.lang.annotation.*;
/**
 * 定义annotation
 * @author cleverpig
 *
 */
//加载在VM中,在运行时进行映射
@Retention(RetentionPolicy.RUNTIME)
//限定此annotation只能标示方法
@Target(ElementType.METHOD)
public @interface AnnotationDefineForTestFunction{}

测试annotation的代码如下:

清单12:

package com.bjinfotech.practice.annotation;

import java.lang.reflect.*;/**
 * 一个实例程序应用前面定义的Annotation:AnnotationDefineForTestFunction
 * @author cleverpig
 *
 */
public class UsingAnnotation {
        @AnnotationDefineForTestFunction public static void method01(){}

        public static void method02(){}

        @AnnotationDefineForTestFunction public static void method03(){
                throw new RuntimeException("method03");
        }

        public static void method04(){
                throw new RuntimeException("method04");
        }

        public static void main(String[] argv) throws Exception{
                int passed = 0, failed = 0;
                //被检测的类名
                String className="com.bjinfotech.practice.annotation.UsingAnnotation";
                //逐个检查此类的方法,当其方法使用annotation声明时调用此方法
            for (Method m : Class.forName(className).getMethods()) {
               if (m.isAnnotationPresent(AnnotationDefineForTestFunction.class)) {
                  try {
                     m.invoke(null);
                     passed++;
                  } catch (Throwable ex) {
                     System.out.printf("测试 %s 失败: %s %n", m, ex.getCause());
                     failed++;
                  }
               }
            }
            System.out.printf("测试结果: 通过: %d, 失败: %d%n", passed, failed);
        }
}

3。使用第三方开发的Annotation类型
这也是开发人员所常常用到的一种方式。比如我们在使用Hibernate3.0时就可以利用Annotation生成数据表映射配置文件,而不必使用Xdoclet。

五、总结:

1。 前面的文字说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的 annotation应用(例如使用自定义annotation生成javabean映射xml文件)还需要进一步的研究和探讨。

2。同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,因为编译时的annotation要使用annotation processing tool。

涉及以上2方面的深入内容,作者将在后文《Java Annotation高级应用》中谈到。

六、参考资源:
·Matrix-Java开发者社区:http://www.matrix.org.cn
·http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·作者的Blog:http://blog.matrix.org.cn/page/cleverpig