sql >> Base de Datos >  >> NoSQL >> HBase

Procedimiento:Probar aplicaciones HBase utilizando herramientas populares

Si bien la adopción de Apache HBase para crear aplicaciones de usuario final se ha disparado, muchas de esas aplicaciones (y muchas aplicaciones en general) no se han probado bien. En esta publicación, aprenderá algunas de las formas en que se pueden realizar fácilmente estas pruebas.

Comenzaremos con las pruebas unitarias a través de JUnit, luego pasaremos a usar Mockito y Apache MRUnit, y luego usaremos un miniclúster HBase para las pruebas de integración. (El código base de HBase en sí mismo se prueba a través de un miniclúster, entonces, ¿por qué no aprovecharlo también para las aplicaciones ascendentes?)

Como base para la discusión, supongamos que tiene un objeto de acceso a datos (DAO) de HBase que realiza la siguiente inserción en HBase. La lógica podría ser más complicada, por supuesto, pero a modo de ejemplo, esto hace el trabajo.

public class MyHBaseDAO {

        	public static void insertRecord(HTableInterface table, HBaseTestObj obj)
       	throws Exception {
  	              	Put put = createPut(obj);
  	              	table.put(put);
        	}

        	private static Put createPut(HBaseTestObj obj) {
  	              	Put put = new Put(Bytes.toBytes(obj.getRowKey()));
  	              	put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1"),
             	               	Bytes.toBytes(obj.getData1()));
  	              	put.add(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2"),
             	               	Bytes.toBytes(obj.getData2()));
  	              	return put;
        	}
}

HBaseTestObj es un objeto de datos básico con getters y setters para rowkey, data1 y data2.

El insertRecord hace una inserción en la tabla HBase contra la familia de columnas de CF, con CQ-1 y CQ-2 como calificadores. El método createPut simplemente completa un Put y lo devuelve al método de llamada.

Uso de JUnit

JUnit, que es bien conocido por la mayoría de los desarrolladores de Java en este momento, se aplica fácilmente a muchas aplicaciones HBase. Primero, agregue la dependencia a su pom:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>

Ahora, dentro de la clase de prueba:

public class TestMyHbaseDAOData {
          	@Test
          	public void testCreatePut() throws Exception {
    	  	HBaseTestObj obj = new HBaseTestObj();
    	  	obj.setRowKey("ROWKEY-1");
    	  	obj.setData1("DATA-1");
    	  	obj.setData2("DATA-2");
    	  	Put put = MyHBaseDAO.createPut(obj);
    	  	assertEquals(obj.getRowKey(), Bytes.toString(put.getRow()));
    	  	assertEquals(obj.getData1(), Bytes.toString(put.get(Bytes.toBytes("CF"),
  Bytes.toBytes("CQ-1")).get(0).getValue()));
    	  	assertEquals(obj.getData2(), Bytes.toString(put.get(Bytes.toBytes("CF"),
  Bytes.toBytes("CQ-2")).get(0).getValue()));
          	}
  }

Lo que hizo aquí fue asegurarse de que su método createPut cree, complete y devuelva un objeto Put con los valores esperados.

Uso de Mockito

Entonces, ¿cómo se realiza la prueba unitaria del método insertRecord anterior? Un enfoque muy efectivo es hacerlo con Mockito.

Primero, agrega Mockito como una dependencia a tu pom:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

Luego, en la clase de prueba:

@RunWith(MockitoJUnitRunner.class)
public class TestMyHBaseDAO{
  @Mock 
  private HTableInterface table;
  @Mock
  private HTablePool hTablePool;
  @Captor
  private ArgumentCaptor putCaptor;

  @Test
  public void testInsertRecord() throws Exception {
    //return mock table when getTable is called
    when(hTablePool.getTable("tablename")).thenReturn(table);
    //create test object and make a call to the DAO that needs testing
    HBaseTestObj obj = new HBaseTestObj();
    obj.setRowKey("ROWKEY-1");
    obj.setData1("DATA-1");
    obj.setData2("DATA-2");
    MyHBaseDAO.insertRecord(table, obj);
    verify(table).put(putCaptor.capture());
    Put put = putCaptor.getValue();
  
    assertEquals(Bytes.toString(put.getRow()), obj.getRowKey());
    assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-1")));
    assert(put.has(Bytes.toBytes("CF"), Bytes.toBytes("CQ-2")));
    assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),
Bytes.toBytes("CQ-1")).get(0).getValue()), "DATA-1");
    assertEquals(Bytes.toString(put.get(Bytes.toBytes("CF"),
Bytes.toBytes("CQ-2")).get(0).getValue()), "DATA-2");
  }
}

Aquí ha rellenado HBaseTestObj con "ROWKEY-1", "DATA-1", "DATA-2" como valores. Luego usó la tabla simulada y el DAO para insertar el registro. Capturó el Put que la DAO habría insertado y verificó que la clave de fila, data1 y data2 son lo que esperaba que fueran.

La clave aquí es administrar el grupo de htable y la creación de instancias de htable fuera del DAO. Esto le permite burlarse de ellos limpiamente y probar Puts como se muestra arriba. Del mismo modo, ahora puede expandirse a todas las demás operaciones, como Obtener, Escanear, Eliminar, etc.

Uso de MRUnit

Con las pruebas de unidades de acceso a datos regulares cubiertas, pasemos a los trabajos de MapReduce que van en contra de las tablas de HBase.

Probar trabajos de MR que van en contra de HBase es tan sencillo como probar trabajos normales de MapReduce. MRUnit hace que sea realmente fácil probar trabajos de MapReduce, incluidos los de HBase.

Imagine que tiene un trabajo de MR que escribe en una tabla HBase, "MyTest", que tiene una familia de columnas, "CF". El reductor de tal trabajo podría verse así:

public class MyReducer extends TableReducer<Text, Text, ImmutableBytesWritable> {
   public static final byte[] CF = "CF".getBytes();
   public static final byte[] QUALIFIER = "CQ-1".getBytes();
  public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
     //bunch of processing to extract data to be inserted, in our case, lets say we are simply
     //appending all the records we receive from the mapper for this particular
     //key and insert one record into HBase
     StringBuffer data = new StringBuffer();
     Put put = new Put(Bytes.toBytes(key.toString()));
     for (Text val : values) {
         data = data.append(val);
     }
     put.add(CF, QUALIFIER, Bytes.toBytes(data.toString()));
     //write to HBase
     context.write(new ImmutableBytesWritable(Bytes.toBytes(key.toString())), put);
   }
 }

Ahora, ¿cómo realiza la prueba unitaria del reductor anterior en MRUnit? Primero, agregue MMRUnit como una dependencia a su pom.

<dependency>
   <groupId>org.apache.mrunit</groupId>
   <artifactId>mrunit</artifactId>
   <version>1.0.0 </version>
   <scope>test</scope>
</dependency>

Luego, dentro de la clase de prueba, use el ReduceDriver que proporciona MRUnit como se muestra a continuación:

public class MyReducerTest {
    ReduceDriver<Text, Text, ImmutableBytesWritable, Writable> reduceDriver;
    byte[] CF = "CF".getBytes();
    byte[] QUALIFIER = "CQ-1".getBytes();

    @Before
    public void setUp() {
      MyReducer reducer = new MyReducer();
      reduceDriver = ReduceDriver.newReduceDriver(reducer);
    }
  
   @Test
   public void testHBaseInsert() throws IOException {
      String strKey = "RowKey-1", strValue = "DATA", strValue1 = "DATA1", 
strValue2 = "DATA2";
      List<Text> list = new ArrayList<Text>();
      list.add(new Text(strValue));
      list.add(new Text(strValue1));
      list.add(new Text(strValue2));
      //since in our case all that the reducer is doing is appending the records that the mapper   
      //sends it, we should get the following back
      String expectedOutput = strValue + strValue1 + strValue2;
     //Setup Input, mimic what mapper would have passed
      //to the reducer and run test
      reduceDriver.withInput(new Text(strKey), list);
      //run the reducer and get its output
      List<Pair<ImmutableBytesWritable, Writable>> result = reduceDriver.run();
    
      //extract key from result and verify
      assertEquals(Bytes.toString(result.get(0).getFirst().get()), strKey);
    
      //extract value for CF/QUALIFIER and verify
      Put a = (Put)result.get(0).getSecond();
      String c = Bytes.toString(a.get(CF, QUALIFIER).get(0).getValue());
      assertEquals(expectedOutput,c );
   }

}

Básicamente, después de un montón de procesamiento en MyReducer, verificó que:

  • El resultado es el esperado.
  • El Put que se inserta en HBase tiene "RowKey-1" como clave de fila.
  • “DATADATA1DATA2” es el valor para la familia de columnas CF y el calificador de columna CQ.

También puede probar Mappers que obtienen datos de HBase de manera similar usando MapperDriver, o probar trabajos de MR que leen de HBase, procesan datos y escriben en HDFS.

Uso de un miniclúster de HBase

Ahora veremos cómo realizar las pruebas de integración. HBase se envía con HBaseTestingUtility, lo que facilita la escritura de pruebas de integración con un miniclúster de HBase. Para extraer las bibliotecas correctas, se requieren las siguientes dependencias en su pom:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase</artifactId>
    <version>0.94.2-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>
        
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>2.0.0-cdh4.2.0</version>
    <scope>test</scope>
</dependency>

Ahora, veamos cómo ejecutar una prueba de integración para el inserto MyDAO descrito en la introducción:

public class MyHBaseIntegrationTest {
private static HBaseTestingUtility utility;
byte[] CF = "CF".getBytes();
byte[] QUALIFIER = "CQ-1".getBytes();

@Before
public void setup() throws Exception {
	utility = new HBaseTestingUtility();
	utility.startMiniCluster();
}

@Test
    public void testInsert() throws Exception {
   	 HTableInterface table = utility.createTable(Bytes.toBytes("MyTest"),
   			 Bytes.toBytes("CF"));
   	 HBaseTestObj obj = new HBaseTestObj();
   	 obj.setRowKey("ROWKEY-1");
   	 obj.setData1("DATA-1");
   	 obj.setData2("DATA-2");
   	 MyHBaseDAO.insertRecord(table, obj);
   	 Get get1 = new Get(Bytes.toBytes(obj.getRowKey()));
   	 get1.addColumn(CF, CQ1);
   	 Result result1 = table.get(get1);
   	 assertEquals(Bytes.toString(result1.getRow()), obj.getRowKey());
   	 assertEquals(Bytes.toString(result1.value()), obj.getData1());
   	 Get get2 = new Get(Bytes.toBytes(obj.getRowKey()));
   	 get2.addColumn(CF, CQ2);
   	 Result result2 = table.get(get2);
   	 assertEquals(Bytes.toString(result2.getRow()), obj.getRowKey());
   	 assertEquals(Bytes.toString(result2.value()), obj.getData2());
    }}

Aquí creó un miniclúster de HBase y lo inició. Luego creó una tabla llamada "MyTest" con una familia de columnas, "CF". Insertó un registro usando el DAO que necesitaba probar, hizo un Obtener de la misma tabla y verificó que el DAO insertó los registros correctamente.

Se podría hacer lo mismo para casos de uso mucho más complicados junto con los trabajos de MR como los que se muestran arriba. También puede acceder a los miniclústeres de HDFS y ZooKeeper creados al crear el de HBase, ejecutar un trabajo de MR, enviarlo a HBase y verificar los registros insertados.

Solo una nota rápida de precaución:iniciar un miniclúster tarda de 20 a 30 segundos y no se puede hacer en Windows sin Cygwin. Sin embargo, debido a que solo deben ejecutarse periódicamente, el tiempo de ejecución más largo debería ser aceptable.

Puede encontrar código de muestra para los ejemplos anteriores en https://github.com/sitaula/HBaseTest. ¡Feliz prueba!