Este exemplo mostra como encadear vários jobs MapReduce sequencialmente no MaxCompute. Um único job MapReduce não pode invocar outro job em tempo de execução. Portanto, quando a lógica de processamento exige uma execução iterativa — em que cada iteração depende da saída da anterior —, é necessário orquestrar vários jobs a partir de um método main do lado do cliente. Este exemplo usa um contador para controlar o loop de iteração.
Como funciona
O programa executa dois tipos de jobs em sequência:
Job de inicialização (
InitMapper): Grava um valor inicial de contador (2) na tabela de saída.Jobs de decremento (
DecreaseMapper): Executam em loop. Cada iteração lê a saída anterior de uma tabela de recursos, decrementa o valor em 1 e defina um contador. O loop termina quando o contador atinge0.
A transferência de dados entre jobs usa dois mecanismos:
Tabela de recursos (
multijobs_res_table): Transfere o valor numérico entre os jobs.Contador (
multijobs.value): Indica ao métodomainse a iteração deve continuar.
O método main é o único orquestrador: ele submete os jobs em ordem e verifica a conclusão entre cada etapa.
Pré-requisitos
Antes de começar, certifique-se de ter:
Concluído a configuração do ambiente descrita em Primeiros passos
Compilado o pacote
mapreduce-examples.jare o colocado no diretóriobin\data\resourcesdo caminho de instalação do cliente MaxCompute
Preparar tabelas e recursos
-
Crie as tabelas de teste:
CREATE TABLE mr_empty (key STRING, value STRING); CREATE TABLE mr_multijobs_out (value BIGINT); -
Registre os recursos usados pelo job:
add table mr_multijobs_out as multijobs_res_table -f; -- Omit -f when adding the JAR for the first time. add jar data\resources\mapreduce-examples.jar -f;O comando registra a tabela
mr_multijobs_outcomomultijobs_res_tablepara que oDecreaseMapperpossa ler a saída do job anterior como uma tabela de recursos. O comando também registra o JAR para que o cliente MaxCompute localize a classe em tempo de execução.
Execute MultiJobs
Execute o seguinte comando no cliente MaxCompute:
jar -resources mapreduce-examples.jar,multijobs_res_table -classpath data\resources\mapreduce-examples.jar \
com.aliyun.odps.mapred.open.example.MultiJobs mr_multijobs_out;
Parâmetros do comando:
|
Parâmetro |
Valor |
Descrição |
|
|
|
Recursos disponíveis para o job: o pacote JAR e a tabela de recursos que contém a saída do job anterior |
|
|
|
Caminho local do JAR que o cliente usa para localizar a classe principal |
|
Classe principal |
|
Ponto de entrada do programa |
|
Argumento |
|
Nome da tabela de saída passado para o método |
Resultado esperado
Quando o job termina, a tabela mr_multijobs_out contém um registro:
+------------+
| value |
+------------+
| 0 |
+------------+
Código de exemplo
Para configurar as dependências do Project Object Model (POM), consulte a seção Precauções no guia Primeiros passos.
package com.aliyun.odps.mapred.open.example;
import java.io.IOException;
import java.util.Iterator;
import com.aliyun.odps.data.Record;
import com.aliyun.odps.data.TableInfo;
import com.aliyun.odps.mapred.JobClient;
import com.aliyun.odps.mapred.MapperBase;
import com.aliyun.odps.mapred.RunningJob;
import com.aliyun.odps.mapred.TaskContext;
import com.aliyun.odps.mapred.conf.JobConf;
import com.aliyun.odps.mapred.utils.InputUtils;
import com.aliyun.odps.mapred.utils.OutputUtils;
import com.aliyun.odps.mapred.utils.SchemaUtils;
/**
* MultiJobs
*
* Running multiple job
*
**/
public class MultiJobs {
public static class InitMapper extends MapperBase {
@Override
public void setup(TaskContext context) throws IOException {
Record record = context.createOutputRecord();
long v = context.getJobConf().getLong("multijobs.value", 2);
record.set(0, v);
context.write(record);
}
}
public static class DecreaseMapper extends MapperBase {
@Override
public void cleanup(TaskContext context) throws IOException {
/** Obtain the variable values that are defined in the main function from JobConf. */
long expect = context.getJobConf().getLong("multijobs.expect.value", -1);
long v = -1;
int count = 0;
/** Read the data from the output table of the previous job. */
Iterator<Record> iter = context.readResourceTable("multijobs_res_table");
while (iter.hasNext()) {
Record r = iter.next();
v = (Long) r.get(0);
if (expect != v) {
throw new IOException("expect: " + expect + ", but: " + v);
}
count++;
}
if (count != 1) {
throw new IOException("res_table should have 1 record, but: " + count);
}
Record record = context.createOutputRecord();
v--;
record.set(0, v);
context.write(record);
/** Set the counter. The counter value can be obtained in the main function after the job is completed. */
context.getCounter("multijobs", "value").setValue(v);
}
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: TestMultiJobs <table>");
System.exit(1);
}
String tbl = args[0];
long iterCount = 2;
System.err.println("Start to run init job.");
JobConf initJob = new JobConf();
initJob.setLong("multijobs.value", iterCount);
initJob.setMapperClass(InitMapper.class);
InputUtils.addTable(TableInfo.builder().tableName("mr_empty").build(), initJob);
OutputUtils.addTable(TableInfo.builder().tableName(tbl).build(), initJob);
initJob.setMapOutputKeySchema(SchemaUtils.fromString("key:string"));
initJob.setMapOutputValueSchema(SchemaUtils.fromString("value:string"));
/** Explicitly set the number of reducers to 0 for map-only jobs. */
initJob.setNumReduceTasks(0);
JobClient.runJob(initJob);
while (true) {
System.err.println("Start to run iter job, count: " + iterCount);
JobConf decJob = new JobConf();
decJob.setLong("multijobs.expect.value", iterCount);
decJob.setMapperClass(DecreaseMapper.class);
InputUtils.addTable(TableInfo.builder().tableName("mr_empty").build(), decJob);
OutputUtils.addTable(TableInfo.builder().tableName(tbl).build(), decJob);
/** Explicitly set the number of reducers to 0 for map-only jobs. */
decJob.setNumReduceTasks(0);
RunningJob rJob = JobClient.runJob(decJob);
iterCount--;
/** If the specified number of iterations is reached, exit the loop. */
if (rJob.getCounters().findCounter("multijobs", "value").getValue() == 0) {
break;
}
}
if (iterCount != 0) {
throw new IOException("Job failed.");
}
}
}
Análise do código
InitMapper
Executa apenas uma vez no método setup(). Lê o valor inicial do contador (multijobs.value, padrão 2) do JobConf e o grava na tabela de saída. Este é um job apenas de mapeamento (map-only) — a chamada setNumReduceTasks(0) impede que o framework inicie qualquer redutor.
DecreaseMapper
Executa no método cleanup() ao final de cada iteração. Ele executa as seguintes ações:
Lê o valor esperado (
multijobs.expect.value) doJobConf.Lê o único registro da tabela
multijobs_res_tableusandocontext.readResourceTable(). Lança umaIOExceptionse a contagem de registros não for exatamente 1 ou se o valor não corresponder ao valor esperado.Decrementa o valor em 1, grava-o na tabela de saída e defina o contador (
multijobs.value) com o novo valor.
Método main
Controla a sequência de jobs:
Submete o job de inicialização e aguarda sua conclusão.
Entra em um loop que submete os jobs de decremento um de cada vez, aguardando a conclusão de cada um antes de prosseguir.
Após cada job de decremento, verifica o contador
multijobs.value. Se for igual a0, encerra o loop.Verifica se
iterCounté0após o loop; caso contrário, lança umaIOException, sinalizando falha no job.