Linguagem de consulta de objeto para cache distribuído

Autor: Iqbal Khan

NCache permite criar um cache distribuído escalável na camada intermediária para que você possa reduzir viagens caras ao banco de dados e melhorar muito o desempenho de seu aplicativo. Ele também melhora a escalabilidade do aplicativo porque você pode localizar dados usados ​​com frequência nesse cache altamente escalável, em vez de um único servidor de banco de dados que não pode ser dimensionado muito bem.

Seu aplicativo normalmente usa um cache como uma Hashtable onde tudo é armazenado com base em uma chave e você deve ter essa chave para buscar um item. Isso é como ter um banco de dados relacional onde você só pode usar a chave primária para encontrar dados. Isso funciona bem em muitas situações, mas em um aplicativo complexo da vida real, seu aplicativo geralmente precisa encontrar dados com base em atributos diferentes da chave primária. E, como você está mantendo muitos dados no cache, seria muito útil se você pudesse pesquisar o cache dessa maneira também. NCache fornece exatamente essa facilidade.

Neste artigo, vou discutir como NCache a consulta de objetos funciona.

Chaves de retorno de pesquisa de cache

NCache fornece uma linguagem de consulta de objetos (OQL) para permitir que você pesquise o cache. Você precisa fazer chamadas de API e especificar uma pesquisa com base nessa OQL para buscar uma coleção de objetos do cache. Aqui está o que você precisa usar em seu aplicativo .NET para consultar o cache.

public class Program
{
    public static void Main(string[] args)
    {
        NCache.InitializeCache("myReplicatedCache");
        String query = "SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100";
        // Fetch the keys matching this search criteria
        ICollection keys = NCache.Cache.Search(query);
        if (keys.Count > 0)
        {
            IEnumerator ie = keys.GetEnumerator();
            while (ie.MoveNext())
            {
                String key = (String)ie.Current;
                Product prod = (Product)NCache.Cache.Get(key);

                HandleProduct(prod);
                Console.WriteLine("ProductID: {0}", prod.ProductID);
            }
        }
        NCache.Cache.Dispose();
    }
} 

O código acima pesquisa o cache e retorna uma coleção de chaves. Em seguida, ele itera sobre todas as chaves retornadas e busca individualmente os itens em cache correspondentes do cache. O benefício dessa abordagem é que a consulta não retorna automaticamente muitos dados e, em vez disso, apenas retorna chaves. Em seguida, o aplicativo cliente pode determinar quais chaves deseja buscar. A desvantagem dessa abordagem é que, se você for buscar a maioria dos itens do cache de qualquer maneira, acabará fazendo muitas viagens ao cache. E, se o cache for distribuído, isso pode acabar se tornando caro. Se esse for o caso, você poderá realizar uma pesquisa que retorne as chaves e os itens juntos.

Pesquisa de cache retornando itens

Como você já viu, um simples Cache.Search(...) retorna uma coleção de chaves. No entanto, se você pretende buscar todos ou a maioria dos itens em cache associados a essas chaves, Cache.Search(...) não é uma maneira muito eficiente de pesquisar o cache. A razão é que você primeiro fará uma chamada para fazer a pesquisa. Em seguida, você fará várias chamadas para buscar os itens associados a cada chave. Isso pode se tornar uma operação muito cara. Nessas situações, é melhor buscar todas as chaves e itens em uma chamada. Abaixo está o exemplo fazendo exatamente isso.

public class Program
{
    public static void Main(string[] args)
    {
        NCache.InitializeCache("myReplicatedCache");
        String query = "SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100";
        // Fetch the keys matching this search criteria
        IDictionary dict = NCache.Cache.SearchEntries(query);
        if (dict.Count > 0)
        {
            IDictionaryEnumerator ide = dict.GetEnumerator();
            while (ide.MoveNext())
            {
                String key = (String)ide.Key;
                Product prod = (Product)ide.Value;
                HandleProduct(prod);
                Console.WriteLine("Key = {0}, ProductID: {1}",
                key, prod.ProductID);
            }
        }
        NCache.Cache.Dispose();
    }
}

O código acima pesquisa o cache e retorna um dicionário contendo chaves e valores. Dessa forma, todos os dados baseados nos critérios de pesquisa são buscados em uma chamada para NCache. Essa é uma maneira muito mais eficiente de buscar todos os dados do cache do que fazer Cache.Search().

Indexando atributos pesquisáveis

Por favor, note que NCache requer que todos os atributos pesquisáveis ​​sejam indexados. Isso porque sem indexação, NCache teria que percorrer todo o cache para encontrar os itens que o usuário está procurando. E essa é uma operação muito cara com potencial de desacelerar todo o cache e desfazer a principal razão pela qual as pessoas NCache, ou seja, para aumentar o desempenho e a escalabilidade de seus aplicativos.

NCache fornece seu próprio mecanismo de indexação. Você pode identificar os objetos em seus assemblies .NET que deseja indexar. Então, quando você adiciona dados a NCache, ele verifica se você está adicionando esses objetos. E, se estiver, ele usa o .NET Reflection para extrair dados dos atributos indexados e cria seu índice interno. Então, quando você consulta o cache com Cache.Search() ou Cache.SearchEntries(), NCache usa esses índices para localizar rapidamente os objetos desejados e os retorna para você.

Indexando atributos de objeto antes de iniciar o cache
Figura 1: Atributos de objeto de indexação antes de iniciar o cache

Observe que sempre que você especifica índices em atributos de objeto, isso adiciona um pouco ao tempo de processamento das operações Adicionar, Inserir e Remover. No entanto, a operação Get não é afetada.

Consultando diferentes topologias de cache

Embora o comportamento de pesquisa da perspectiva do aplicativo cliente seja o mesmo, independentemente de quais topologias de armazenamento em cache você está usando, os componentes internos da pesquisa variam de topologia para topologia. Por exemplo, se você estiver fazendo uma pesquisa em um cache replicado, sua pesquisa será conduzida inteiramente no servidor de cache de onde você iniciou essa pesquisa. Isso ocorre porque todo o cache está disponível lá. Aqui está como uma consulta é executada em um cache replicado.

A consulta é executada localmente em um servidor
Figura 2: a consulta é executada localmente em um servidor

No entanto, se você tiver uma topologia de cache particionada ou de réplica particionada, nem todos os dados residirão em um único nó de cache no cluster. Nessa situação, o servidor de cache onde a consulta é iniciada envia a consulta para todos os outros servidores do cluster e também a executa localmente. A consulta então é executada em todos os servidores em paralelo e seus resultados são retornados de todos os nós para este nó do servidor de origem. Este nó servidor então combina todos os resultados (faz uma "união") e os devolve ao cliente. Abaixo está um diagrama mostrando tudo isso.

A consulta é executada em paralelo em todos os nós do servidor
Figura 3: Execuções de consulta em paralelo em todos os nós do servidor

Seus assemblies .NET

In NCache, a indexação é feita nos nós do servidor. No entanto, NCache usa o .NET Reflection no cliente para extrair os valores dos atributos do objeto e os envia ao servidor. Portanto, seus assemblies .NET que contêm a definição de objeto são necessários apenas no lado do cliente em que seu aplicativo reside. Esteja você executando no modo InProc ou OutProc, seus assemblies precisam estar em um diretório onde seu aplicativo cliente possa acessá-los.

Além disso, NCache também suporta linguagem de consulta de objeto para clientes Java.

Sintaxe da linguagem de consulta

NCache suporta linguagem semelhante a SQL chamada Object Query Language (OQL). Esta linguagem tem a seguinte sintaxe em NCache.

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID > 100;

SELECT NCacheQuerySample.Business.Product WHERE (this.ProductID != 100 AND this.ProductID <= 200);

SELECT NCacheQuerySample.Business.Product WHERE (this.ProductID == 150 OR this.ProductID == 160);

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID IN (1, 4, 7, 10);

SELECT NCacheQuerySample.Business.Product WHERE this.ProductID NOT IN (1, 4, 7, 10);

SELECT NCacheQuerySample.Business.Employee WHERE this.HiringDate > DateTime.now;

SELECT NCacheQuerySample.Business.Employee WHERE this.HiringDate > DateTime ('01/01/2007');

SELECT NCacheQuerySample.Business.Employee WHERE this.Hired = true;

Você pode combinar várias expressões com AND e OR e usando parênteses aninhados. A gramática de sintaxe da linguagem de consulta completa é especificada abaixo.

<Query>                 ::= SELECT <ObjectType> 
                          | SELECT <ObjectType> WHERE <Expression>

<Expression>            ::= <OrExpr>

<OrExpr>                ::= <OrExpr> 'OR' <AndExpr>
                          | <AndExpr>

<AndExpr>               ::= <AndExpr> 'AND' <UnaryExpr>
                          | <UnaryExpr>

<UnaryExpr>             ::= 'NOT'  <CompareExpr>
                          | <CompareExpr>

<CompareExpr>           ::= <Atrrib> '='  <Value>
                          | <Atrrib> '!=' <Value>
                          | <Atrrib> '==' <Value>
                          | <Atrrib> '<>' <Value>
                          | <Atrrib> '<'  <Value>
                          | <Atrrib> '>'  <Value>
                          | <Atrrib> '<=' <Value>
                          | <Atrrib> '>=' <Value>
                          | <Atrrib> 'IN' <InList>
                          | <Atrrib> 'NOT' 'IN' <InList>
                          | '(' <Expression> ')'

<Atrrib>		::= <ObjectValue>                                                    

<Value>                 ::= '-' <NumLiteral> 
                          | <NumLiteral> 
                          | <StrLiteral>
                          | 'true'
                          | 'false'
                          | <Date>

<Date>                  ::= 'DateTime' '.' 'now'
                          | 'DateTime' '(' StringLiteral ')'

<StrLiteral>		::= StringLiteral
			  | 'null'  

<NumLiteral>            ::= IntegerLiteral 
                          | RealLiteral

<ObjectType>            ::= '*' 
                          | <Property>

<Property>		::= <Property> '.' Identifier
			  | Identifier

<ObjectValue>           ::= Keyword '.' Identifier              

<InList>                ::= '(' <ListType> ')'

<ListType>              ::= <NumLiteralList>
                          | <StrLiteralList>
                          | <DateList>

<NumLiteralList>        ::=  <NumLiteral> ',' <NumLiteralList>
                          | <NumLiteral>

<StrLiteralList>        ::= <StrLiteral> ',' <StrLiteralList>
                          | <StrLiteral>

<DateList>              ::= <Date> ',' <DateList>
                          | <Date> 

Conclusão

Como você viu, NCache torna muito simples consultar o cache distribuído. Este é um recurso poderoso que permite que você use seu cache de maneira mais significativa e encontre coisas nele com mais facilidade. Confira.


Autor: Iqbal Khan trabalha para Alachisoft , uma empresa de software líder que fornece soluções de cache distribuído .NET e Java, mapeamento O/R e otimização de armazenamento do SharePoint. Você pode alcançá-lo em iqbal@alachisoft.com.

© Copyright Alachisoft 2002 - . Todos os direitos reservados. NCache é uma marca registrada da Diyatech Corp.