Esta postagem do blog foi publicada no Hortonworks.com antes da fusão com a Cloudera. Alguns links, recursos ou referências podem não ser mais precisos.
Para este post, fazemos um mergulho técnico profundo em uma das principais áreas do HBase. Especificamente, veremos como o Apache HBase distribui a carga pelas regiões e gerencia a divisão de regiões. O HBase armazena linhas de dados em tabelas. As tabelas são divididas em blocos de linhas chamados “regiões”. Essas regiões são distribuídas pelo cluster, hospedadas e disponibilizadas aos processos clientes pelo processo RegionServer. Uma região é um intervalo contínuo dentro do espaço de chaves, o que significa que todas as linhas na tabela que classificam entre a chave inicial e a chave final da região são armazenadas na mesma região. As regiões não são sobrepostas, ou seja, uma única chave de linha pertence a exatamente uma região a qualquer momento. Uma região é atendida apenas por um único servidor de região a qualquer momento, e é assim que o HBase garante uma consistência forte em uma única linha#. Juntamente com o -ROOT- e .META. regiões, as regiões de uma tabela formam efetivamente uma árvore B de 3 níveis com a finalidade de localizar uma linha dentro de uma tabela.
Uma Região, por sua vez, consiste em muitas “Lojas”, que correspondem a famílias de colunas. Um armazenamento contém um memstore e zero ou mais arquivos de armazenamento. Os dados para cada família de colunas são armazenados e acessados separadamente.
Uma tabela geralmente consiste em muitas regiões, que por sua vez são hospedadas por muitos servidores de região. Assim, as regiões são o mecanismo físico usado para distribuir a carga de gravação e consulta entre os servidores da região. Quando uma tabela é criada pela primeira vez, o HBase, por padrão, alocará apenas uma região para a tabela. Isso significa que, inicialmente, todas as solicitações irão para um único servidor de região, independentemente do número de servidores de região. Esta é a principal razão pela qual as fases iniciais de carregamento de dados em uma tabela vazia não podem utilizar toda a capacidade do cluster.
Pré-divisão
A razão pela qual o HBase cria apenas uma região para a tabela é que ele não pode saber como criar os pontos de divisão dentro do espaço de chave de linha. Tomar tais decisões é altamente baseado na distribuição das chaves em seus dados. Em vez de dar um palpite e deixar você lidar com as consequências, o HBase fornece ferramentas para gerenciar isso do cliente. Com um processo chamado pré-divisão, você pode criar uma tabela com várias regiões fornecendo os pontos de divisão no momento da criação da tabela. Como a pré-divisão garantirá que a carga inicial seja distribuída de maneira mais uniforme em todo o cluster, você deve sempre considerar usá-la se conhecer sua distribuição de chaves de antemão. No entanto, a pré-divisão também tem o risco de criar regiões que não distribuem verdadeiramente a carga de maneira uniforme devido à distorção de dados ou na presença de linhas muito quentes ou grandes. Se o conjunto inicial de pontos de divisão de região for mal escolhido, você poderá acabar com uma distribuição de carga heterogênea, o que, por sua vez, limitará o desempenho de seus clusters.
Não há uma resposta curta para o número ideal de regiões para uma determinada carga, mas você pode começar com um múltiplo menor do número de servidores de região como número de divisões e deixar a divisão automatizada cuidar do resto.
Um problema com a pré-divisão é calcular os pontos de divisão da tabela. Você pode usar o utilitário RegionSplitter. RegionSplitter cria os pontos de divisão, usando um SplitAlgorithm conectável. HexStringSplit e UniformSplit são dois algoritmos predefinidos. O primeiro pode ser usado se as chaves de linha tiverem um prefixo para strings hexadecimais (como se você estiver usando hashes como prefixos). O último divide o espaço de chave uniformemente assumindo que são arrays de bytes aleatórios. Você também pode implementar seu SplitAlgorithm personalizado e usá-lo no utilitário RegionSplitter.
$ hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f f1
onde -c 10, especifica o número solicitado de regiões como 10 e -f especifica as famílias de colunas que você deseja na tabela, separadas por “:”. A ferramenta criará uma tabela chamada “test_table” com 10 regiões:
13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,,1358563771069.acc1ad1b7962564fc3a43e5907e8db33.', STARTKEY => '', ENDKEY => '19999999', ENCODED => acc1ad1b7962564fc3a43e5907e8db33,} 13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,19999999,1358563771096.37ec12df6bd0078f5573565af415c91b.', STARTKEY => '19999999', ENDKEY => '33333332', ENCODED => 37ec12df6bd0078f5573565af415c91b,} ...
Se você tiver pontos de divisão em mãos, também poderá usar o shell HBase, para criar a tabela com os pontos de divisão desejados.
hbase(main):015:0> create 'test_table', 'f1', SPLITS=> ['a', 'b', 'c']
ou
$ echo -e "anbnc" >/tmp/splits hbase(main):015:0> create 'test_table', 'f1', SPLITSFILE=>'/tmp/splits'
Para uma distribuição de carga ideal, você deve pensar em seu modelo de dados e distribuição de chaves para escolher o algoritmo de divisão ou pontos de divisão corretos. Independentemente do método escolhido para criar a tabela com um número pré-determinado de regiões, agora você pode começar a carregar os dados na tabela e ver que a carga está distribuída em todo o cluster. Você pode permitir que a divisão automatizada assuma o controle assim que a ingestão de dados for iniciada e monitorar continuamente o número total de regiões da tabela.
Divisão automática
Independentemente de a pré-divisão ser usada ou não, quando uma região atinge um determinado limite, ela é automaticamente dividida em duas regiões. Se você estiver usando o HBase 0.94 (que vem com o HDP-1.2), poderá configurar quando o HBase decide dividir uma região e como ele calcula os pontos de divisão por meio da API RegionSplitPolicy conectável. Há algumas políticas de divisão de região predefinidas:ConstantSizeRegionSplitPolicy, IncreaseToUpperBoundRegionSplitPolicy e KeyPrefixRegionSplitPolicy.
A primeira é a política de divisão padrão e única para versões do HBase anteriores a 0,94. Ele divide as regiões quando o tamanho total dos dados de um dos armazenamentos (correspondente a uma família de colunas) na região fica maior que o configurado “hbase.hregion.max.filesize”, que tem um valor padrão de 10 GB. Essa política de divisão é ideal nos casos em que você já fez a pré-divisão e está interessado em obter um número menor de regiões por servidor de região.
A política de divisão padrão para o HBase 0.94 e o tronco é o IncreaseToUpperBoundRegionSplitPolicy, que faz uma divisão mais agressiva com base no número de regiões hospedadas no mesmo servidor de região. A política de divisão usa o tamanho máximo do arquivo de armazenamento com base em Min (R^2 * “hbase.hregion.memstore.flush.size”, “hbase.hregion.max.filesize”), onde R é o número de regiões do mesmo tabela hospedada no mesmo servidor de região. Assim, por exemplo, com o tamanho padrão de liberação do memstore de 128 MB e o tamanho máximo de armazenamento padrão de 10 GB, a primeira região no servidor da região será dividida logo após a primeira liberação em 128 MB. À medida que o número de regiões hospedadas no servidor da região aumenta, ele usará tamanhos de divisão crescentes:512 MB, 1152 MB, 2 GB, 3,2 GB, 4,6 GB, 6,2 GB etc. .hregion.max.filesize”, nesse ponto, o tamanho de divisão de 10 GB será usado a partir de então. Para ambos os algoritmos, independentemente de quando ocorre a divisão, o ponto de divisão usado é a chave de linha que corresponde ao ponto médio no “índice de bloco” para o maior arquivo de armazenamento no maior armazenamento.
KeyPrefixRegionSplitPolicy é uma adição curiosa ao arsenal do HBase. Você pode configurar o comprimento do prefixo para suas chaves de linha para agrupá-las, e essa política de divisão garante que as regiões não sejam divididas no meio de um grupo de linhas com o mesmo prefixo. Se você tiver definido prefixos para suas chaves, poderá usar essa política de divisão para garantir que as linhas com o mesmo prefixo de chave de linha sempre terminem na mesma região. Esse agrupamento de registros às vezes é chamado de “Grupos de Entidades” ou “Grupos de Linhas”. Esse é um recurso importante ao considerar o uso do recurso “transações locais” (link alternativo) no design do aplicativo.
Você pode configurar a política de divisão padrão a ser usada definindo a configuração “hbase.regionserver.region.split.policy” ou configurando o descritor de tabela. Para suas almas corajosas, você também pode implementar sua própria política de divisão personalizada e conectá-la no momento da criação da tabela ou modificando uma tabela existente:
HTableDescriptor tableDesc = new HTableDescriptor("example-table"); tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, AwesomeSplitPolicy.class.getName()); //add columns etc admin.createTable(tableDesc);
Se você estiver fazendo uma pré-divisão e quiser gerenciar manualmente as divisões de região, também poderá desabilitar as divisões de região, definindo “hbase.hregion.max.filesize” como um número alto e definindo a política de divisão como ConstantSizeRegionSplitPolicy. No entanto, você deve usar um valor de proteção de 100 GB, para que as regiões não cresçam além dos recursos de um servidor regional. Você pode considerar desabilitar a divisão automatizada e contar com o conjunto inicial de regiões da pré-divisão, por exemplo, se estiver usando hashes uniformes para seus prefixos de chave e garantir que a leitura/gravação seja carregada em cada região, bem como seu tamanho é uniforme em todas as regiões da tabela.
Divisões forçadas
O HBase também permite que os clientes forcem a divisão de uma tabela online do lado do cliente. Por exemplo, o shell HBase pode ser usado para dividir todas as regiões da tabela ou dividir uma região, opcionalmente fornecendo um ponto de divisão.
hbase(main):024:0> split 'b07d0034cbe72cb040ae9cf66300a10c', 'b' 0 row(s) in 0.1620 seconds
Com o monitoramento cuidadoso da distribuição de carga do HBase, se você perceber que algumas regiões estão recebendo cargas desiguais, considere dividir manualmente essas regiões para equilibrar a carga e melhorar a taxa de transferência. Outra razão pela qual você pode querer fazer divisões manuais é quando você vê que as divisões iniciais para a região se tornam subótimas e você desativou as divisões automatizadas. Isso pode acontecer, por exemplo, se a distribuição de dados mudar ao longo do tempo.
Como as divisões de região são implementadas
À medida que as solicitações de gravação são tratadas pelo servidor da região, elas se acumulam em um sistema de armazenamento na memória chamado “memstore”. Quando o memstore é preenchido, seu conteúdo é gravado no disco como arquivos de armazenamento adicionais. Esse evento é chamado de “limpeza do memstore”. À medida que os arquivos de armazenamento se acumulam, o RegionServer os “compacta” em arquivos combinados maiores. Após a conclusão de cada limpeza ou compactação, uma solicitação de divisão de região é enfileirada se RegionSplitPolicy decidir que a região deve ser dividida em duas. Como todos os arquivos de dados no HBase são imutáveis, quando ocorre uma divisão, as regiões filhas recém-criadas não reescrevem todos os dados em novos arquivos. Em vez disso, eles criarão pequenos arquivos semelhantes a links simbólicos, denominados arquivos de referência, que apontam para a parte superior ou inferior do arquivo de armazenamento pai de acordo com o ponto de divisão. O arquivo de referência será usado como um arquivo de dados normal, mas apenas metade dos registros. A região só pode ser dividida se não houver mais referências aos arquivos de dados imutáveis da região pai. Esses arquivos de referência são limpos gradualmente por compactações, para que a região pare de se referir a seus arquivos pais e possa ser dividida ainda mais.
Embora a divisão da região seja uma decisão local tomada no RegionServer, o próprio processo de divisão deve ser coordenado com muitos atores. O RegionServer notifica o Master antes e depois da divisão, atualiza o .META. table para que os clientes possam descobrir as novas regiões filhas e reorganiza a estrutura de diretórios e os arquivos de dados no HDFS. Split é um processo multitarefa. Para habilitar a reversão em caso de erro, o RegionServer mantém um diário na memória sobre o estado de execução. As etapas executadas pelo RegionServer para executar a divisão são ilustradas na Figura 1. Cada etapa é rotulada com seu número de etapa. As ações dos RegionServers ou Master são mostradas em vermelho, enquanto as ações dos clientes são mostradas em verde.
1. RegionServer decide localmente dividir a região e prepara a divisão. Como primeiro passo, ele cria um znode no zookeeper em /hbase/region-in-transition/region-name no estado SPLITTING.
2. O Mestre aprende sobre este znode, pois ele tem um watcher para o znode da região-em-transição pai.
3. RegionServer cria um subdiretório chamado “.splits” no diretório de região do pai no HDFS.
4. RegionServer fecha a região pai, força uma limpeza do cache e marca a região como offline em suas estruturas de dados locais. Nesse ponto, as solicitações do cliente que chegam à região pai lançarão NotServingRegionException. O cliente tentará novamente com algum recuo.
5. RegionServer cria os diretórios de região no diretório .splits, para as regiões filhas A e B, e cria as estruturas de dados necessárias. Em seguida, ele divide os arquivos de armazenamento, no sentido de criar dois arquivos de referência por arquivo de armazenamento na região pai. Esses arquivos de referência apontarão para os arquivos das regiões pai.
6. RegionServer cria o diretório de região real no HDFS e move os arquivos de referência para cada filha.
7. RegionServer envia uma solicitação Put para o .META. tabela e define o pai como offline no .META. tabela e adiciona informações sobre regiões filhas. Neste ponto, não haverá entradas individuais em .META. para as filhas. Os clientes verão que a região pai está dividida se digitalizarem .META., mas não saberão sobre as filhas até que apareçam em .META.. Além disso, se isso colocar em .META. for bem-sucedido, o pai será efetivamente dividido. Se o RegionServer falhar antes que esse RPC seja bem-sucedido, o mestre e o próximo servidor de região que abrir a região limparão o estado sujo sobre a divisão de região. Após o .META. atualização, no entanto, a divisão de região será avançada pelo Mestre.
8. RegionServer abre filhas em paralelo para aceitar gravações.
9. RegionServer adiciona as filhas A e B a .META. juntamente com informações de que hospeda as regiões. Após esse ponto, os clientes podem descobrir as novas regiões e emitir solicitações para a nova região. Os clientes armazenam em cache o .META. entradas localmente, mas quando fizerem solicitações ao servidor da região ou .META., seus caches serão invalidados e eles aprenderão sobre as novas regiões a partir de .META..
10. O RegionServer atualiza o znode /hbase/region-in-transition/region-name no zookeeper para o estado SPLIT, para que o mestre possa aprender sobre isso. O balanceador pode reatribuir livremente as regiões filhas a outros servidores de região, se assim o desejar.
11. Após a divisão, o meta e o HDFS ainda conterão referências à região pai. Essas referências serão removidas quando as compactações nas regiões filhas reescreverem os arquivos de dados. As tarefas de coleta de lixo no mestre verificam periodicamente se as regiões filhas ainda se referem aos arquivos pais. Caso contrário, a região pai será removida.
Mesclas de região
Ao contrário da divisão de regiões, o HBase neste momento não fornece ferramentas utilizáveis para mesclar regiões. Embora existam ferramentas HMerge e Merge, elas não são muito adequadas para uso geral. Atualmente, não há suporte para tabelas online e funcionalidade de mesclagem automática. No entanto, com problemas como OnlineMerge, mesclagens automáticas de regiões iniciadas pelo mestre, bloqueios de leitura/gravação baseados em ZK para operações de tabela, estamos trabalhando para estabilizar as divisões de região e permitir um melhor suporte para mesclagens de região. Fique atento!
Conclusão
Como você pode ver, o HBase nos bastidores faz muita manutenção para gerenciar divisões de regiões e fazer fragmentação automatizada por regiões. No entanto, o HBase também fornece as ferramentas necessárias para o gerenciamento de regiões, para que você possa gerenciar o processo de divisão. Você também pode controlar precisamente quando e como as divisões de região estão acontecendo por meio de uma RegionSplitPolicy.
O número de regiões em uma tabela e como essas regiões são divididas são fatores cruciais para entender e ajustar sua carga de cluster HBase. Se você puder estimar sua distribuição de chaves, deverá criar a tabela com pré-divisão para obter o melhor desempenho de carregamento inicial. Você pode começar com um número múltiplo menor de servidores de região como ponto de partida para o número inicial de regiões e deixar que a divisão automatizada assuma o controle. Se você não puder estimar corretamente os pontos de divisão iniciais, é melhor apenas criar a tabela com uma região e iniciar algum carregamento inicial com divisão automatizada e usar o IncreaseToUpperBoundRegionSplitPolicy. No entanto, lembre-se de que o número total de regiões se estabilizará ao longo do tempo e o conjunto atual de pontos de divisão da região será determinado a partir dos dados que a tabela recebeu até agora. Você pode querer monitorar a distribuição de carga entre as regiões o tempo todo e, se a distribuição de carga mudar ao longo do tempo, use a divisão manual ou defina tamanhos de divisão de região mais agressivos. Por fim, você pode experimentar o próximo recurso de mesclagem online e contribuir com seu caso de uso.