Chamadas Proxied não funcionam como esperado

5

Eu tenho modificado um aplicativo para ter uma separação cliente / servidor mais limpa para permitir divisão de carga e compartilhamento de recursos, etc. Tudo é gravado em uma interface, por isso é fácil adicionar uma camada remota à interface usando um proxy. Tudo funcionou bem. A próxima fase foi adicionar uma camada de cache à interface e, novamente, isso funcionou bem e a velocidade foi melhorada, mas não tanto quanto eu esperava. Na inspeção, ficou muito claro o que estava acontecendo.

Tenho certeza de que esse comportamento foi visto muitas vezes antes e provavelmente há um padrão de design para resolver o problema, mas ele me escapa e não sei ao certo como descrevê-lo.

É mais fácil explicar com um exemplo. Vamos imaginar a interface é

interface IMyCode {
    List<IThing> getLots( List<String> );
    IThing getOne( String id );
}

O método getLots () chama getOne () e preenche a lista antes de retornar.

A interface é implementada no cliente, que é intermediado por proxy para um cliente remoto que, em seguida, chama o servidor remoto que, por sua vez, chama a implementação no servidor. No cliente e nas camadas do servidor, há também um cache.

Então nós temos: -

Client interface
      |
 Client cache
      |
 Remote client
      |
 Remote server
      |
  Server cache
      |
Server interface

Se chamarmos getOne ("A") na interface do cliente, a chamada é passada para o cache do cliente, que é a falha. Isso, então, chama o cliente remoto que passa a chamada para o servidor remoto. Isso, então, chama o cache do servidor, que também falha e, assim, a chamada é finalmente passada para a interface do servidor que, na verdade, obtém o IThing. Por sua vez, o cache do servidor é preenchido e, finalmente, o cache do cliente também.

Se getOne ("A") for chamado novamente na interface do cliente, o cache do cliente terá os dados e será retornado imediatamente. Se um segundo cliente chamado getOne ("B") preenchesse o cache do servidor com "B", assim como seu próprio cache de cliente. Então, quando o primeiro cliente chama getOne ("B"), o cache do cliente falha, mas o cache do servidor tem os dados.

Isso é tudo como seria de se esperar e funciona bem.

Agora vamos chamar getLots (["C", "D"]). Isso funciona como você esperaria chamando getOne () duas vezes, mas há uma sutileza aqui. A chamada para getLots () não pode usar diretamente o cache. Portanto, a sequência é chamar a interface do cliente que, por sua vez, chama o cliente remoto, depois o servidor remoto e, por fim, a interface do servidor. Isso, então, chama getOne () para preencher a lista antes de retornar.

O problema é que as chamadas getOne () estão sendo atendidas no servidor quando, idealmente, devem ser atendidas no cliente. Se você imaginar que o link cliente / servidor é realmente lento, fica claro por que a chamada do cliente é mais eficiente do que a chamada do servidor quando o cache do cliente tiver os dados.

Este exemplo é inventado para ilustrar o ponto. O problema mais geral é que você não pode simplesmente continuar adicionando camadas com proxy a uma interface e esperar que funcione como você imaginaria. Assim que a chamada passar pelo 'proxy', todas as chamadas subseqüentes estarão no lado com proxy, em vez de no lado 'eu'.

Eu falhei em aprender ou não aprendi algo corretamente?

Tudo isso é implementado em Java e eu não usei EJBs.

Parece que o exemplo pode ser confuso. O problema não tem nada a ver com eficiências de cache. Tem mais a ver com uma ilusão criada pelo uso de proxies ou técnicas de AOP em geral.

Quando você tem um objeto cuja classe implementa uma interface, existe uma suposição de que uma chamada nesse objeto pode fazer mais chamadas nesse mesmo objeto. Por exemplo,

public String getInternalString() {
    return InetAddress.getLocalHost().toString();
}

public String getString() {
    return getInternalString();
}

Se você pegar um objeto e chamar getString (), o resultado dependerá de onde o código está sendo executado. Se você adicionar um proxy remoto à classe, o resultado poderá ser diferente para chamadas para getString () e getInternalString () no mesmo objeto. Isso ocorre porque a chamada inicial fica "desprotegida" antes que o método real seja chamado.

Acho isso não apenas confuso, mas me pergunto como posso controlar esse comportamento, especialmente porque o uso do proxy pode ser feito por terceiros.

O conceito é bom, mas a prática certamente não é o que eu esperava.

Eu perdi o ponto em algum lugar?

    
por AndyH 01.11.2011 / 11:55
fonte

2 respostas

1

A relação entre quaisquer dois métodos é desconhecida para uma biblioteca de proxy.

Então o método

public String getString() {
  return getInternalString();
}

uma vez intermediado e implementado no lado do servidor é equivalente a qualquer método

public String getString() {
  //some executable statements along with a return.
}

Mas se você quiser preservar a relação entre os métodos, talvez não queira fazer proxy dos métodos mais avançados

public String getString() {

ou

List<IThing> getLots( List<String> );

cada cliente da biblioteca de proxy codificaria isso separadamente, ou seja, não faz mais parte da interface IMyCode que deve ser suportada pela biblioteca de proxy.

    
por 04.12.2011 / 08:21
fonte
0

Estou falando isso rapidamente antes de uma palestra, então me desculpem se eu estiver um pouco confuso

Acho que o principal problema é que a sua chamada getLots() basicamente entra em um loop de várias chamadas getOne() . Sem ver seu código e sua infraestrutura em detalhes, ficaria tentado a fazer com que a chamada getLots() fizesse exatamente isso, obter muitos objetos em uma única chamada, carregando seus dois caches ao longo do caminho.

Sim, há um custo inicial único para essa chamada, mas depois seus caches estão mais cheios, fazendo com que as chamadas getOne() subseqüentes sejam muito mais rápidas e as chamadas getLots() provavelmente ficarão mais rápidas (dependendo da sua chave de pesquisa alcance). É claro que você precisa pensar em como garantir que as chamadas getLots() pesquisem e retornem de forma eficiente de seus caches, além de percorrer as camadas conforme necessário.

Parece que você mede o desempenho antes e depois de fazer alterações, o que é bom. Você pode usar isso para medir o tamanho mais eficiente das chamadas getLots() que você faz para o servidor (quando não há cache ou cache parcial ou cache completo envolvido).

    
por 01.11.2011 / 13:21
fonte