O SaaS está mudando o modo como os aplicativos são projetados,
construídos, implementados e
operados. Uma diferença chave entre desenvolver um
aplicativo SaaS e desenvolver um aplicativo
corporativo genérico é que os aplicativos SaaS devem ser
multiarrendatários.
Outros requisitos principais de SaaS,
como segurança, customização, Arquitetura Orientada a Serviços (SOA)
e integração, também afetam a arquitetura do aplicativo SaaS.
Multiarrendamento refere-se à capacidade de um aplicativo de permitir
o alojamento de múltiplos
arrendatários em um único código de base e compartilhar
infraestrutura como
bancos de dados.
Há múltiplas opções de design para
ativar o banco de dados dedicado
a arquitetura de dados de multiarrendatários – para cada
arrendatário, esquema separado de banco de dados compartilhado
para cada arrendatário e esquema de banco de dados de
shard compartilhado para todos os
arrendatários.
Uma estratégia chave com custo reduzido é
ter um banco de dados compartilhado e uma
estratégia de esquema compartilhada para todos os
arrendatários.
Adotar essa abordagem apresenta
um grande desafio à escala de banco de dados conforme o
banco de dados é compartilhado entre todos os
arrendatários suportados pelo aplicativo SaaS. Uma
técnica simples para escalar um banco de dados compartilhado é através
de
sharding de banco de dados baseado no ID do
arrendatário.
Sharding de banco de dados refere-se ao particionamento
horizontal de um banco de dados, sendo que cada
partição individual é chamada de shard. Há alguns softwares
livres, bem como softwares proprietários, como
Hibernate Shards, Apache Slice, SQLAlchemy ORM e dbShards, que
oferecem o recurso de sharding.
Este artigo
abordará o Hibernate, já que ele é a estrutura ORM usada com mais
frequência no espaço do J2EE. Abordaremos
os problemas de escala de banco de dados de aplicativos SaaS usando
Hibernate Shards, que são uma
extensão do Hibernate, e que tornam disponíveis os recursos de
sharding para usuários do Hibernate.
Sharding de banco de dados para aplicativos SaaS
- Os aplicativos SaaS que adotam um banco de dados compartilhado e uma
abordagem de esquema compartilhada devem ser projetados
levando-se em consideração que precisarão ser escalados quando não
puderem mais atender às métricas de desempenho
de linha de base, como, por exemplo, quando muitos usuários tentam
acessar o banco de dados
simultaneamente ou quando o tamanho do banco de dados faz com que
consultas e atualizações levem muito tempo
para serem executadas. Uma forma de escalar um banco de dados
compartilhado é por sharding. É
a forma mais efetiva de escalar um banco de dados, já que as linhas em
um esquema compartilhado são diferenciadas
dos arrendatários – com base no ID do arrendatário. O banco de dados pode
ser particionado facilmente de forma horizontal
com base no ID do arrendatário. Isso torna mais fácil mover dados que
pertencem a cada um dos arrendatários
para partições individuais. - Ao particionar dados de um aplicativo SaaS multiarrendatário,
precisamos considerar
fatores como degradação de desempenho devido ao aumento do número de
usuários simultâneos ou aumento do tamanho do banco de dados devido ao
fornecimento
de múltiplos arrendatários. Este fator pode afetar as características de
desempenho do banco de dados
existente. Isso ajudará a selecionar as técnicas apropriadas de
partição de acordo com o requisito de tamanho do banco de dados de um
arrendatário individual, ou número de usuários de um arrendatário
individual acessando o banco de dados
simultaneamente. O sharding fornece muitas vantagens,
como leituras e gravações mais rápidas no banco de dados,
resposta a procura melhorada, tamanhos menores de tabela e distribuição
de tabelas com base na necessidade. - O sharding de banco de dados do Hibernate pode ser dinamizado para
construção de aplicativos SaaS, adotando outras
opções de arquitetura de dados multiarrendatária, como banco de dados
dedicado para cada arrendatário, ou esquemas
separados de banco de dados compartilhado para cada arrendatário. O
sharding de banco de dados do Hibernate pode extrair o código do
aplicativo
conectando-se a múltiplos bancos de dados baseados em contexto de
arrendatário para fornecer configuração de metadados e
sharding de API – responsável por lidar com inserção de dados e leitura de dados de
múltiplos bancos de dados de acordo com o
contexto do arrendatário.
Implementação de sharding usando Hibernate Shards
O Hibernate Shards é uma estrutura projetada para encapsular e
minimizar essa complexidade
adicionando suporte a particionamento horizontal para o
Hibernate
Core. Ele minimiza a complexidade de implementação
quando se
trabalha com dados de shards.
O objetivo principal do
Hibernate Shards é
ativar o aplicativo para que você possa consultar e
transacionar nos
conjuntos de dados de shards usando a API padrão do
Hibernate Core. Essa abordagem fornece
os seguintes benefícios:
- Fornece soluções não invasivas para suportar sharding de banco de
dados, enquanto os aplicativos existentes
ativados por SaaS são construídos usando o Hibernate - Permite que os aplicativos SaaS existentes que usam Hibernate, mas
que ainda não precisam de sharding, adotem
a solução sem uma refatoração grande se e quando eles atingirem esse
estágio.
O Hibernate Shards fornece:
- As extensões sensíveis a shard de interfaces são fornecidas pelo
Hibernate Core, de modo que o código não
precisa saber que está interagindo com um conjunto de dados de shard. As
extensões sensíveis a shard que atuam como um
mecanismo de sharding são as seguintes:
- org.hibernate.shards.ShardedSessionFactory
- org.hibernate.shards.criteria.ShardedCriteria
- org.hibernate.shards.session.ShardedSession
- org.hibernate.shards.query.ShardedQuery
- Interfaces para implementação de estratégia de sharding específica do aplicativo. O mecanismo de sharding descrito acima usa a estratégia de sharding fornecida pelo aplicativo.
- org.hibernate.shards.strategy.access.ShardAccessStrategy: Com esta estratégia, o Hibernate determina como aplicar operações do banco de dados pelos múltiplos shards. Sempre que uma consulta for executada, essa estratégia é mencionada. Duas implementações padrão, SequentialShardAccessStrategy eParallelShardAccessStrategy, já são fornecidas.
- org.hibernate.shards.strategy.resolution.ShardResolutionStrategy: Esta estratégia é usada para determinar o conjunto de shards no qual um objeto com um certo ID pode residir. A resolução do shard é vinculada à geração de ID para a qual o Hibernate fornece inúmeras estratégias, como geração de UUID no nível do aplicativo e geração distribuída de encadeamento.
- org.hibernate.shards.strategy.selection.ShardSelectionStrategy: Esta estratégia nos permite determinar o shard no qual um novo objeto deve ser criado. Com base na necessidade do aplicativo, podemos decidir pela implementação dessa interface. Embora o Hibernate forneça inicialmente uma implementação pronta para uso com alternância de eventos, precisamos fornecer uma implementação baseada no ID do arrendatário para aplicativos baseados em SaaS.
- Suporte a shards virtuais para facilitar o impacto associado à
resharding.
- Os aplicativos SaaS devem ser projetados levando-se em consideração que
eles podem ter que escalar um banco de dados no
futuro, com fornecimento aumentado de novos arrendatários para o
aplicativo. Quando estava projetando, você pode ter acreditado
que dois bancos de dados eram suficientes, mas quando um conjunto de
dados do aplicativo SaaS cresce além da
capacidade dos bancos de dados originalmente alocados
para o aplicativo, torna-se necessário adicionar bancos de dados. Muitas
vezes, desejamos
redistribuir os dados pelos shards. Resharding é um problema complicado,
e tem o potencial de causar
complicações graves no gerenciamento do seu aplicativo de produção, se
não foi considerado durante o planejamento. - Com shards virtuais, podemos apontar múltiplos shards virtuais para
shards físicos em
um arquivo de configuração do Hibernate. Isso permite a definição de
muitos shards virtuais, que podem mapear
até dois ou três shards físicos apenas. Com o crescimento do projeto, a
configuração pode ser mudada
para adicionar mais shards físicos. - Ao projetar e implementar aplicativos SaaS usando Hibernate Framework
para Persistence,
devemos criar shards virtuais para cada um dos arrendatários, que podem
ser mapeados para um banco de dados físico previsto.
Com o fornecimento de novos arrendatários, podemos criar mais um shard
virtual e mapeá-lo para os mesmos
shards físicos previstos ou mais shards físicos.
Construção de um aplicativo de e-commerce multiarrendatário usando Hibernate Shards
Vamos ver como podemos dinamizar esse conceito ao construir um
aplicativo de e-commerce
multiarrendatário. Vamos presumir que o aplicativo de e-commerce
multiarrendatário está usando estratégia de esquema compartilhado e de
banco de dados compartilhado
para atender os requisitos de dados de multiarrendamento.
Conforme a
quantia de dados aumenta, precisamos
escalar o banco de dados para tratar novos arrendatários. Neste
exemplo, temos dois arrendatários que compartilham um único banco de
dados, e
quando arrendatários adicionais entram, seus dados são movidos para
diferentes shards de banco de dados baseados no
ID do arrendatário.
Esse aplicativo também usa a opção de shard
virtual
para cuidar de requisitos futuros de escalabilidade de banco de
dados. O aplicativo possui
informações de perfil do cliente para arrendatários localizados em
múltiplos locais geográficos
na tabela de cliente.
Configuração:
- Pré-requisitos
A seguir estão as principais bibliotecas e ferramentas usadas para
testar essa amostra:
- hibernate-shards.jar – V3.2.x
- hibernate3.jar – V3.0.0.Beta2
- hsqldb.jar – V1.8.0
- Eclipse IDE – V3.5 (a amostra deve funcionar em versões anteriores sem
qualquer alteração)
- Esquema do banco de dados da tabela de cliente
Tabela 1. Tabela de cliente
Column name | Column type | Remarks |
CUSTOMER_ID | DECIMAL(40,0) | PRIMARY KEY |
TENANT_ID | VARCHAR(50) | |
FIRST_NAME | VARCHAR(50) | |
LAST_NAME | VARCHAR(50) | |
LOCATION | VARCHAR(50) | |
UPDATE_TIME | TIMESTAMP |
Um modelo de objeto Java correspondente à tabela é criado. Um fragmento
dele
é mostrado abaixo:
Listagem 1. Listagem de código de amostra do modelo de objeto de cliente
public class Customer {
private BigInteger customerId;
private String tenantId;
private String firstName;
private String lastName;
private String location;
private Date updateTime;
// Getters and Setters
}
Depois definimos o Customer.hbm.xml correspondente:
Listagem 2. Listagem de código de amostra do arquivo de configuração Customer.hbm.xml
<hibernate-mapping package="com.sample.shards.bean">
<class name="Customer" table="CUSTOMER">
<id name="customerId" column="CUSTOMER_ID" type="big_integer">
<generator class="org.hibernate.shards.id.ShardedUUIDGenerator"/>
</id>
<property name="tenantId" column="TENANT_ID"/>
<property name="firstName" column="FIRST_NAME"/>
<property name="lastName" column="LAST_NAME"/>
<property name="location" column="LOCATION"/>
<property name="updateTime" type="timestamp" column="UPDATE_TIME"/>
</class>
</hibernate-mapping
Em seguida, configuramos os xmls de configuração do Hibernate.
Inicialmente, em um banco de dados compartilhado com
abordagem de esquema compartilhado, um único
hibernate.cfg.xml é configurado.
Conforme escalamos o número de instâncias do banco
de dados, o hibernate.cfg.xml é
adicionado para cada instância de banco de dados
usada no shard.
Esses arquivos
de configuração do Hibernate diferem nos detalhes de
conexão, factory de sessão e
ID de shard. A tabela abaixo mostra o
hibernate0.cfg.xml –
um dos três arquivos de configuração usados no
exemplo.
Listagem 3. Listagem de código de amostra do arquivo de configuração Hibernate0.cfg.xml
<hibernate-configuration>
<session-factory name="HibernateSessionFactory0">
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:mem:shard0</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="hibernate.connection.shard_id">0</property>
<property name="hibernate.shard.enable_cross_shard_relationship_checks">
true
</property>
</session-factory>
</hibernate-configuration>
Como podemos ver acima, a configuração do Hibernate Shards é bem semelhante à do Hibernate. A principal diferença é a adição de propriedades hibernate.connection.shard_id ehibernate.shard.enable_cross_shard_relationship_checks.
Note também que o factory de sessão e o ID de shard devem representar o shard de forma única. O hibernate1.cfg.xml e o hibernate2.cfg.xml são configurados de forma semelhante.
Observe que qualquer banco de dados pode ser usado. No exemplo, usamos um HSQL Database Engine Server para uma instância de dados e HSQL Database Engine In-Memory para outras duas instâncias de banco de dados.
Construção de estratégia de sharding
Agora definimos as estratégias de shard a serem usadas no aplicativo. Criamos uma ShardSelectionStrategy chamada CustomerShardSelectionStrategy, que implementa a ShardSelectionStrategy que decide o shard no qual a linha é restaurada com base no ID do arrendatário.
O ID de shard será mapeado considerando-se o mapa de ID de shard virtual configurado nas últimas etapas. Caso um mapa de ID de shad virtual não seja fornecido (ele é opcional), o mapa de ID de shard físico é usado como padrão.
Listagem 4. Listagem de código de amostra de CustomerShardSelectionStrategy
public class CustomerShardSelectionStrategy implements ShardSelectionStrategy {
public ShardId selectShardIdForNewObject(Object obj) {
if (obj instanceof Customer) {
return getShardId(((Customer) obj).getTenantId());
}
throw new IllegalArgumentException();
}
public ShardId getShardId(String tenantId) {
int shardId = 0;
if(tenantId.equalsIgnoreCase("Tenant1") || tenantId.equalsIgnoreCase("Tenant2"))
shardId = 0;
else if(tenantId.equalsIgnoreCase("Tenant3"))
shardId = 1;
else if(tenantId.equalsIgnoreCase("Tenant4"))
shardId = 2;
else if(tenantId.equalsIgnoreCase("Tenant5"))
shardId = 3;
return new ShardId(shardId);
}
}
Usamos implementações AllShardsShardResolutionStrategy e SequentialShardAccessStrategy fornecidas pelo Hibernate Shards para as outras estratégias. A seguir, construiremos a Factory de Estratégia de Shard com base em implementações de estratégia de shard.
Listagem 5. Construção da Factory de Estratégia de Shard
ShardStrategyFactory buildShardStrategyFactory() {
return new ShardStrategyFactory() {
public ShardStrategy newShardStrategy(List<ShardId> shardIds) {
ShardSelectionStrategy pss = new CustomerShardSelectionStrategy();
ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
ShardAccessStrategy pas = new SequentialShardAccessStrategy();
return new ShardStrategyImpl(pss, prs, pas);
}
};
}
Depois obtemos a ShardSessionFactory passando os arquivos de configuração hibernate*.cfg.xml e customer.hbm.xml, também criamos quatro shards virtuais para requisitos futuros, que são mapeados no momento para três shards físicos.
Como discutido na seção anterior, criamos um mapa de shard virtual para cuidar de requisitos futuros de escala de banco de dados. Como se pode ver abaixo, configuramos os shards virtuais dois e três para mapear o mesmo shard físico dois no momento.
Posteriormente, quando um novo shard físico for adicionado, somente esse mapeamento e o arquivo de configuração do Hibernate precisam ser fornecidos. Nenhuma outra alteração precisará ser feita no restante do código.
Listagem 6. Listagem do código de amostra para criar factory de sessão
public SessionFactory createSessionFactory() {
Configuration config = new Configuration();
config.configure("hibernate0.cfg.xml");
config.addResource("customer.hbm.xml");
List shardConfigs = new ArrayList();
shardConfigs.add(buildShardConfig("hibernate0.cfg.xml"));
shardConfigs.add(buildShardConfig("hibernate1.cfg.xml"));
shardConfigs.add(buildShardConfig("hibernate2.cfg.xml"));
Map<Integer, Integer> virtualShardMap = new HashMap<Integer, Integer>();
virtualShardMap.put(0, 0);
virtualShardMap.put(1, 1);
virtualShardMap.put(2, 2);
virtualShardMap.put(3, 2);
ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
ShardedConfiguration shardedConfig = new ShardedConfiguration(
config,
shardConfigs,
shardStrategyFactory,
virtualShardMap);
return shardedConfig.buildShardedSessionFactory();
}
Realização de operações do banco de dados usando Hibernate
Finalmente, realizamos operações normais no Hibernate usando a instância SessionFactory recuperada.
Listagem 7. Listagem do código de amostra para realizar operações do banco de dados
Session session = sessionFactory.openSession();
try {
session.beginTransaction();
// Add new records to the database
Customer customer = new Customer();
customer.setFirstName("John");
customer.setLastName("Doe");
customer.setLocation("New York City");
customer.setTenantId("Tenant1");
customer.setUpdateTime(new Date());
session.save(customer);
// Similarly add records for other Tenants...
session.getTransaction().commit();
// List the customers from the database
List customers = getData();
for (int i = 0; i < customers.size(); i++) {
Customer customer = (Customer) customers.get(i);
System.out.println(customer.toString());
}
} finally {
session.close();
}
Saída de código
Ao executar o aplicativo, o resultado a seguir é exibido no console.
Listagem 8. Resultado de amostra exibido no console
Customer Id: 8006718453799964144490040897437701, Tenant Id: Tenant3, First Name: James,
Last Name: Bond, Location: London, Update Time: 2010-03-16 09:28:33.526
Customer Id: 13199015312334791773020537226657798, Tenant Id: Tenant4, First Name: Vijay,
Last Name: Raj, Location: Bangalore, Update Time: 2010-03-16 09:28:33.526
Customer Id: 18391312170869619401551033555877895, Tenant Id: Tenant5, First Name: Brett,
Last Name: Lee, Location: Melbourne, Update Time: 2010-03-16 09:28:33.526
Customer Id: 2814421595265136515959544563040259, Tenant Id: Tenant1, First Name: John,
Last Name: Doe, Location: New York City, Update Time: 2010-03-16 09:28:33.401
Customer Id: 2814421595265136515959544568217604, Tenant Id: Tenant2, First Name: Jane,
Last Name: Dane, Location: San Francisco, Update Time: 2010-03-16 09:28:33.526
A tabela de cliente em diferentes bancos de dados terá os dados a seguir.
Tabela 2. Tabela de cliente no banco de dados 1
Customer_ID | Tenant_ID | First_Name | Last_Name | Local | Update_Time |
2814421595265136515959544563040259 | tenant1 | John | Doe | New York City | 2010-03-16 09:24:31.839 |
2814421595265136515959544568217604 | tenant2 | Jane | Dane | San Francisco | 2010-03-16 09:24:31.964 |
Tabela 3. Tabela de cliente no banco de dados 2
Customer_ID | Tenant_ID | First_Name | Last_Name | Local | Update_Time |
8006718453799964144490040897437701 | tenant3 | James | Bond | London | 2010-03-16 09:28:33.526 |
Tabela 4. Tabela de cliente no banco de dados 3
Customer_ID | Tenant_ID | First_Name | Last_Name | Local | Update_Time |
13199015312334791773020537226657798 | tenant4 | Vijay | Raj | Bangalore | 2010-03-16 09:28:33.526 |
18391312170869619401551033555877895 | tenant5 | Brett | Lee | Melbourne | 2010-03-16 09:28:33.526 |
Agora você já pode fazer o download do código de amostra completa.
Limitação atual do Hibernate Shards Framework
Algumas das principais limitações do Hibernate Shards Framework (V3.0.0.Beta2) são as seguintes:
- Alguns dos métodos fornecidos pela ShardedSessionImpl,
ShardedCriteriaImple pela ShardedQueryImpl,
que usamos raramente, ainda não
estão completamente implementados - No momento, a estrutura de sharding não pode ser usada com HQL. Ela
só pode ser usada com SQL. - O Hibernate Shards não fornece suporte a transações distribuídas
dentro de um ambiente
não gerenciado. Se o aplicativo requer transações
distribuídas,
precisamos fazer o plug-in de uma implementação de
gerenciamento
de transação que suporte transações distribuídas. - Atualmente, o Hibernate Shards Framework não suporta gráficos de
objeto de compartilhamento cruzado. - O mecanismo de configuração atual não funciona enquanto estiver
configurando SessionFactory através de JPA
Conclusão
Este artigo forneceu uma visão geral dos requisitos de sharding de banco
de dados de aplicativos
SaaS, bem como conceitos de sharding de banco de dados e
viabilidade de
implementação de sharding usando a estrutura de software
livre do Hibernate Shards.
Tratamos também das limitações atuais do Hibernate
Shard Framework. Este artigo ajudará arquitetos e
desenvolvedores a dinamizarem
a estrutura do Hibernate Shards na camada de dados de
uma forma efetiva, construindo
e projetando, ao mesmo tempo, verdadeiros aplicativos
SaaS multiarrendatários baseados em J2EE.
Recursos
Aprender
-
Consulte a Wikipédia para obter
detalhes sobre Shard
database architecture. -
Consulte o post do blog de julho de 2008 de Rahul Roy
sobre Shard
– A database design. -
Outro portal com informações sobre sharding de
banco de dados está em codeFutures. -
Saiba mais lendo “Java
development 2.0: Sharding with Hibernate Shards.” -
Verifique o item do blog Lifescaler sobre Database
sharding unraveled. -
Para saber mais detalhes sobre a estrutura do Hibernate
Shards,
consulte Hibernate.org. -
Visite o portal MSDN portal para obter mais
informações sobre Multi-tenant
Data Architecture. -
A comunidade
My developerWorks
é um exemplo de uma comunidade geral bem-sucedida que abrange uma ampla
variedade de tópicos.
Obter produtos e tecnologias
-
Inove seu próximo projeto de desenvolvimento de software livre com a versão de teste do software da IBM,
disponível para download ou em DVD.
***
artigo publicado originalmente no developerWorks Brasil, por Chetan Kothari e Arun Viswanathan
Chetan Kothari é o arquiteto principal do Centro de Excelência do Java 2
Platform, Enterprise Edition (J2EE)
da Infosys Technologies Limited, líder global em serviços de
consultoria de negócios
e TI. Ele possui mais de nove anos de experiência no
desenvolvimento da estrutura
do aplicativo J2EE, definindo, projetando e implementando
soluções de TI
de missão crítica em larga escala em uma série de segmentos de
mercado. É formado em
engenharia eletrônica pela Universidade de Nagpur.
Arun Viswanathan trabalha no Centro de Excelência de Java Enterprise no
SETLabs da Infosys Technologies. Ele possui mais de sete anos de
experiência em tecnologias Java e Java EE. Atualmente, está envolvido em
design e consultoria para aplicativos baseados em SaaS.