A limpeza de dados é justificada? Engenheiro off-line ET2 de conversão de grupo de verificação multidimensional
Método 1: Use RandomAccessFile para baixar o hash do arquivo e remover duplicatas
public class App { public static void main( String[] args ) throws Exception { //Prepara hash HashMap<String, Integer> map = new HashMap<>(); //Lê o arquivo RandomAccessFile raf = new RandomAccessFile("D:\ \bgdata\\bgdata01\\events.csv", "rw"); //Pular a primeira linha raf.readLine(); //Ler uma linha de dados String line=""; //Loop para ler dados while ( (line =raf.readLine())!=null){ //Os dados lidos são separados por vírgulas e a primeira String eventid=line.split(",")[0]; //Determina se o mapa contém id if (mapa. }else { map.put(eventid,mapa.get(eventid)+1); //Caso contrário, é a primeira vez que obtemos o valor do id e o configuramos como 1 map.put(eventid,1); } } //Fecha o arquivo de stream raf.close(); //Sai e verifica o tamanho do mapa para ver se há duplicatas System.out.println (map.size()+"=============="); } }
Desvantagens: Os dados do arquivo usados desta vez foram 3 milhões e a leitura foi muito lenta. Demorou cerca de 30 minutos o(╥﹏╥)o
Recomenda-se usar o seguinte método
Método 2 usa Mapreduce para baixar arquivos
2.1Configurar arquivo pom
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>org.apache.hadoop </groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId> hadoop-hdfs</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <versão>2.6.0</versão> </dependency>
2.2 Configurar a classe RcMapper
classe pública RcMapper estende Mapper<LongWritable,Text ,Text, IntWritable> { IntWritable one= new IntWritable(1); @Override mapa vazio protegido (chave LongWritable, valor de texto, contexto de contexto) lança IOException, InterruptedException { String eventid=value.toString().split(",")[0]; context.write(novo Texto(eventid),um); } }
2.3 Configurar a classe RcReduce
classe pública RcReduce estende Redutor<Texto, IntWritable,Texto,IntWritable>{ @Override protegido void reduzir(chave de texto, valores Iterable<IntWritable>, contexto de contexto) lança IOException, InterruptedException { int count=0; for (IntWritable it: valores) { contagem+=1; } context.write(chave,new IntWritable(contagem)); } }
2.4 Configurar a classe RcCountDriver
public class RcCountDriver { public static void main(String[] args) throws Exception { //Instancia o objeto de trabalho Job job = Job.getInstance(new Configuration()); //Obtém o objeto por meio de reflexão job.setJarByClass(RcCountDriver.class ); // Reflexão para obter o objeto RcMapper //Definir o Text.class correspondente job.setMapperClass(RcMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); // Reflexão para obter o objeto RcReduce // Defina o Text.class correspondente job.setReducerClass(RcReduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // Obtenha o caminho do arquivo de destino FileInputFormat.addInputPath(job,new Path("file:///D:\\bgdata\\bgdata01\\events.csv")); // Marque o endereço do arquivo de download FileOutputFormat.setOutputPath (job ,new Path("file:///d:/calres/cal01")); / ** *O trabalho é executado através de job.waitForCompletion(true), * true significa que o progresso da execução e outras informações serão saída para o usuário em tempo hábil, * Se for falso, apenas espere o trabalho terminar */ job.waitForCompletion(true); } }
Basta clicar para testar! !
Método 3. Use Mapreduce para remover duplicatas
3.1 Configurar a classe CfMapper
public class CfMapper estende Mapper<LongWritable, Text,Text, IntWritable> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //O valor obtido é separado por espaços String[] infos = value . toString().split("\t"); //Operação se o segundo dado não for um if (!infos[1].equals("1")){ //Obter o primeiro valor, o segundo Remover espaços de valores diferentes de 1 context.write(new Text(infos[0]),new IntWritable(Integer.parseInt(infos [ 1].trim()))); } } }
3.2 Configurar a classe CfCombiner
classe pública CfCombiner estende Redutor<Texto, IntWritable,Texto,IntWritable> { @Override protected void reduz (chave de texto, valores Iterable<IntWritable>, contexto de contexto) lança IOException, InterruptedException { //配置kv键值对 context.write(chave ,valores.iterator().next()); } }
3.3 Configurar a classe CfCountDriver
public class CfCountDriver { public static void main(String[] args) throws Exception { //Instancia o objeto de trabalho Job job = Job.getInstance(new Configuration()); //Obtém o objeto por meio de reflexão job.setJarByClass(CfCountDriver.class ); // Reflexão para obter o objeto CfMapper //Definir o Text.class correspondente job.setMapperClass(CfMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); // Reflexão para obter o objeto CfCombiner // Defina o Text.class correspondente // job.setCombinerClass(CfCombiner.class); job.setReducerClass(CfCombiner.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // Obtém o caminho do arquivo de destino FileInputFormat.addInputPath(job,new Path("file:///d:/calres/cal01/a")); // Marca o endereço do arquivo de download FileOutputFormat .setOutputPath(job,new Path("file:///d:/calres/ca102/")); /** * O trabalho é executado através de job.waitForCompletion(true), * true significa que o progresso da execução e outros as informações serão enviadas a tempo Para o usuário, * se for falso, apenas espere o trabalho terminar */ job.waitForCompletion(true); } }
implementação de código kafka de configuração ideal (otimização: modo bridge)
1 Importar arquivo kafka pom
canall extrai dados do banco de dados em tempo real
2Configurar yml
Envie manualmente o monitor de serialização de dados lidos desde o início
server: port: 8999 spring: application: name: userinterest kafka: bootstrap-servers: 192.168.64.210:9092 #acks=0: Independentemente de sucesso ou falha, envie apenas uma vez. Nenhuma confirmação é necessária #acks=1: ou seja, você só precisa confirmar que o líder recebeu a mensagem #acks=all ou -1: ISR + Líder têm certeza de receber o consumidor: #Se deseja confirmar automaticamente o deslocamento offset enable-auto-commit: false #Earliest: Sem registro de envio, o consumo começa desde o início #latest: Sem registro de envio, o consumo começa a partir da próxima mensagem mais recente auto-offset-reset: mais antigo #Key método de codificação e decodificação key-deserializer: org.apache.kafka.common.serialization.StringDeserializer #valor método de codificação e decodificação value-deserializer: org.apache.kafka.common.serialization.StringDeserializer #Configurar o ouvinte listener: #Quando o valor de enable.auto.commit for definido como falso, o valor entrará em vigor ; quando for verdadeiro, não terá efeito. # manual_immediate: Você precisa chamar manualmente Acknowledgment.acknowledge() para enviar a confirmação -modo: manual_imediato
3. Configurar modelo de modo bridge
3.1 Classe de interface em modo Bridge
public interface FillHbaseData<T> extends FillData<Put,T> { List<Put> fillData(List<T> lis); } / ** * Converte formatos de dados de acordo com diferentes dados do usuário e modelos de classe de entidade de entrada Interface * @param < T> */ public interface StringToEntity<T> { List<T> change(String line); }
3.2 Classe de implementação do modo Bridge
# EventAttendessFillDataImpl类 public class EventAttendessFillDataImpl implements FillHbaseData<EventAttendees> { @Override public List<Put> fillData(List<EventAttendees> lst) { List<Put> puts = new ArrayList<>(); lst.stream().forEach(ea->{ Put put = new Put((ea.getEventid() + ea.getUserid() + ea.getAnswer()).getBytes()); put.addColumn("base" .getBytes(),"eventid".getBytes(),ea.getAnswer().getBytes()); put.addColumn("base".getBytes(),"userid".getBytes(),ea.getAnswer(). getBytes()); put.addColumn("base".getBytes(),"resposta".getBytes(),ea.getAnswer(). getBytes()); coloca.add(colocar); }); opções de venda de retorno; } } # EventFillDataImpl类 public class EventFillDataImpl implements FillHbaseData<Events> { @Override public List<Put> fillData(List<Events> lis) { List<Put> puts = new ArrayList<>(); lis.stream().forEach( event -> { Put put = new Put(event.getEventid().getBytes()); put.addColumn("base".getBytes(),"userid".getBytes(),event .getUserid().getBytes()); put.addColumn("base".getBytes(),"starttime".getBytes(),event.getStarttime().getBytes()); put.addColumn("base". put.addColumn("base".getBytes(),"zip".getBytes(),event.getZip().getBytes()); put.addColumn("base".getBytes(),"estado".getBytes(),event.getState().getBytes()); put.addColumn("base".getBytes(),"país".getBytes(),event.getCountry().getBytes()); put.addColumn("base".getBytes(),"lat".getBytes(),event.getLat().getBytes()); put.addColumn("base".getBytes(),"lng".getBytes(),event.getLng().getBytes()); coloca. getBytes()); }); retornar nulo; } } # EventAttendeesChangeImpl类 public class EventAttendeesChangeImpl implements StringToEntity<EventAttendees> { / ** * Os dados entram como eventid sim talvez convidado não * ex:123,112233,34343,234234,45454,112233,23232,234234,3434343,34343 * Converta os dados formato Para 123 112233 sim, 123 34343 sim, 123 234234 talvez... * @param line * @return */ @Override public List<EventAttendees> change(String line) { String[] infos = line.split(" ," , -1); List<EventAttendees> eas = new ArrayList<>(); //Primeiro conte todas as pessoas que responderam sim if (infos[1].trim().equals("")&&infos[1]! = nulo){ Arrays.asList(infos[1].dividir(" ")).stream().forEach( sim->{ EventAttendees ea = EventAttendees.builder() .eventid(infos[0]).userid(yes).answer("yes") .build(); eas.add(ea); }); } //先计算所有回答maybe的人 if (infos[2].trim().equals("")&&infos[2]!=null){ Arrays.asList(infos[2].split(" ")) .stream().forEach( talvez->{ EventAttendees ea = EventAttendees.builder() .eventid(infos[0]).userid(talvez).answer("talvez") .build(); eas.add(ea); }); } //Primeiro calcule todas as pessoas que responderam ao convite if (infos[3].trim().equals("")&&infos[3]!=null){ Arrays.asList( infos [3].split(" ")).stream().forEach( convidado->{ EventAttendees ea = EventAttendees.builder() .eventid(infos[0]).userid(convidado).resposta("convidada") . build(); eas.add(ea); }); } //Primeiro calcule todas as pessoas que responderam não if (infos[4].trim().equals("")&&infos[4]!=null) { Arrays.asList(infos[4].split(" ")).stream().forEach( no->{ EventAttendees ea = EventAttendees.builder() .eventid(infos[0]).userid(no).answer( "não") .build(); eas.add(ea); }); } retornar eas; } } # EventsChangeImpl类 public class EventsChangeImpl implements StringToEntity<Events> { @Override public List<Events> change(String line) { String[] infos = line.split(",", -1); List<Eventos> events=new ArrayList<>(); Eventos evento = Events.builder().eventid(infos[0]).userid(infos[1]).starttime(infos[2]) .city(infos[3]).state(infos[4]). zip (infos[5]).país(infos[6]) .lat(infos[7]).lng(infos[8]).build(); events.add(evento); retornar eventos; } } # UserFriendsChangeImpl类 /** * 将将123123, 123435 435455 345345 => 123123, 123435 123123,435455 123123, 345345 */ classe pública @Override public List<UserFriends> change(String linha) { String[] infos = linha.split(","); List<UserFriends> ufs = new ArrayList<>(); Arrays.asList((infos[1]).split(" ")).stream().forEach( fid->{ UserFriends uf = UserFriends.builder().userid(infos[0]).friendid(fid). construir(); ufs.add(uf); } ); retornar ufs; } }
3.3 Classe abstrata do modo Bridge
/** * #3.3.1 AbstractDataChanage抽象类 * 桥梁模式中的抽象角色 */ public abstract class AbstractDataChanage<E,T> implements DataChanage<T> { protected FillData<E,T> fillData; protected StringToEntity<T> stringToEntity; public AbstractDataChanage(FillData<E, T> fillData, StringToEntity<T> stringToEntity) { this.fillData = fillData; this.stringToEntity = stringToEntity; } @Override public abstract List<T> change(String line); public abstract void fill(ConsumerRecord<String, /** * #3.3.2 Interface DataChanage * Interface de conversão de dados Os dados kafka são convertidos em formatos de dados comuns * (se houver vários bancos de dados redis hbase oracle, uma interface de preenchimento precisa ser escrita) * @param <T> * / interface pública List<T> change(String line); } # 3.3.3 Classe DataChangeFillHbaseDatabase classe pública DataChangeFillHbaseDatabase<T> estende AbstractDataChanage<Put,T> { public DataChangeFillHbaseDatabase( FillData<Put,T> fillData, StringToEntity<T > stringToEntity) { super(fillData,stringToEntity); } @Override public List<T> change(String line){ return stringToEntity.change(line); } @Override public void fill(ConsumerRecord<String,String> record){ //Leia o ConsumerRecord obtido pelo kafka e converta-o em um string List< Put> puts = fillData.fillData(change(record.value())); //Preenche a coleção no banco de dados hbase correspondente } } # 3.3.4 Interface FillData public interface FillData<T,E> { List< T> fillData(Lista<E> lst); }
3.4 Modo ponte
Fábrica abstrata a, fábrica abc, 3 produtos, fábrica b, fábrica abc, 3 produtos
4. Escreva classes de entidade
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class EventAttendees { private String eventid; string privada ID do usuário; resposta de string privada; } @Data @AllArgsConstructor @NoArgsConstructor @Builder public class Events { private String eventid; string privada ID do usuário; horário de início da string privada; cidade privada de String; estado de String privado; zip de string privada; país String privado; string privada lat; string privada lng; } @Dados @AllArgsConstructor @NoArgsConstructor @Builder public class UserFriends { private String userid; private String amigo; }
5. Gravar classe de configuração
@Configuration public class HbaseConfig { @Bean public org.apache.hadoop.conf.Configuration hbaseConfiguration(){ org.apache.hadoop.conf.Configuration cfg= HBaseConfiguration.create(); cfg.set(HConstants.ZOOKEEPER_QUORUM,"192.168.64.210:2181"); return cfg; } @Bean @Scope("prototype") public Connection getConnection() { Connection connection=null; tente { conexão = ConnectionFactory.createConnection(hbaseConfiguration()); } catch (IOException e) { e.printStackTrace(); } retornar conexão; } @Bean public Supplier<Conexão> hbaseConSupplier(){ return ()->{ return getConnection(); }; } }
5.1 Resolver duplicação de dados hbase
hbase subjacente kv k é a chave de linha e a chave de linha é nome de usuário + amigo 5.2 A quantidade de dados é muito grande? Mais de 30w, dezenas de g, 3000w de dados, cerca de vários g, sub-banco de dados, tabela, particionamento vertical 10g função hbase função de pré-partição partição oracle partição hash intervalo de partição lista partição
5.2 Escrevendo classes de serviço
#5.2.1 /** * Aceita o conjunto de dados List<Put> convertido para preencher o banco de dados Hbase */ @Component public class HbaseWriter { @Resource private Connection hbaseConnection; public void write(List<Put> puts,String tableName ){ try { Table table = hbaseConnection.getTable(TableName.valueOf(tableName)); table.put(puts); } catch (IOException e) { e.printStackTrace(); } } } # 5.2.2 @Component classe pública KafkaReader { @KafkaListener (groupId = "cm",tópicos = {"events_raw"}) public void readEventToHbase(ConsumerRecord<String ,String > record, Acknowledgment ack){ AbstractDataChanage<Put,Events> eventsHandler = new DataChangeFillHbaseDatabase<Events>( new EventFillDataImpl(), new EventsChangeImpl() ); eventsHandler.fill(registro); ack.acknowledge(); } @KafkaListener(groupId = "cm",topics = {"event_attendees_raw"}) public void readEventToHbase1(ConsumerRecord<String ,String > record, Acknowledgment ack){ AbstractDataChanage<Put, new EventAttendeesChangeImpl() ) ; eventsHandler.fill(registro); ack.acknowledge(); } @KafkaListener(groupId = "cm",topics = {"user_friends_raw"}) public void readEventToHbase2(ConsumerRecord<String ,String > record, confirmação de reconhecimento){ AbstractDataChanage<Put, UserFriends> eventsHandler = new DataChangeFillHbaseDatabase<UserFriends>( new UserFriendsFillDataImpl (), novo UserFriendsChangeImpl() ) ; eventsHandler.fill(registro); ack.acknowledge(); } }
6 Crie cluster de colunas no banco de dados hbase
#Calcular automaticamente a divisão com base no número necessário de regiões e algoritmo de divisão criar 'userfriends','base',{ NUMREGIONS => 3, SPLITALGO =>'HexStringSplit' } ============ == ============================================== == ====== NUMREGIONS descrição: o tamanho padrão do HFile do hbase Os dados de origem são Hive: Número recomendado de partições ≈ tamanho do HDFS/10G * 10 * 1.2 HexStringSplit , UniformSplit, DecimalStringSplit Descrição: UniformSplit (pequena ocupação de espaço, prefixo de chave de linha completamente aleatório •••••••): um agregado que divide uniformemente o espaço de chaves possíveis. Isso é recomendado quando as chaves são bytes aleatórios aproximadamente consistentes (como hashes). As linhas são valores de bytes brutos no intervalo 00 => FF, preenchidos à direita com 0s para manter a mesma ordem memcmp(). Este é um algoritmo natural para um ambiente byte[] e economiza espaço, mas não é necessariamente o mais simples em termos de legibilidade. HexStringSplit (ocupa muito espaço, rowkey é uma string hexadecimal como prefixo •••••••): HexStringSplit é um RegionSplitter.SplitAlgorithm típico para selecionar o limite da região. O formato dos limites da região HexStringSplit é uma representação ASCII de uma soma de verificação MD5 ou qualquer outro valor hexadecimal distribuído uniformemente. Row é um valor longo codificado em hexadecimal no intervalo "00000000" => "FFFFFFFF", preenchido à esquerda com 0s para que permaneça lexicograficamente na mesma ordem do binário. Como esse algoritmo de divisão usa strings hexadecimais como chaves, é fácil de ler e escrever no shell, mas ocupa mais espaço e pode não ser intuitivo. DecimalStringSplit: rowkey é uma string decimal como um prefixo ===================================== === ============================== criar 'eventAttendees','base' cat events.csv.COMPLETED | head -2 cd /opt/data/attendees cat event_attendees_raw |head -2 criar 'eventos' 'base'
6.1 comando de inicialização hbase
#hbaseStart start-hbase.sh ou inicie o script! ! ! ! O script é o seguinte #Endereço do navegador http://192.168.64.210: 60010 /master-status