Modo de cópia na gravação: cópia na gravação

Tópico: Modo Copy-on-Write: COW que não é uma estratégia de atraso

No último artigo sobre o qual falamos quando a classe String em Java implementa o método replace (), ela não altera o conteúdo da matriz value [] na string original, mas cria uma nova string. É frequentemente usado para resolver o problema de modificar objetos imutáveis. Se você pensar sobre esse método em profundidade, descobrirá que ele é essencialmente um método Copy-on-Write . Os chamadosCopiar na gravação, Frequentemente abreviado como COW ou CoW, como o nome indica é copiar na gravação .

As operações de gravação de objetos imutáveis ​​geralmente são resolvidas usando o método Copy-on-Write. Obviamente, o campo de aplicação Copy-on-Write não se limita ao modo Immutability. Abaixo, apresentamos brevemente o campo de aplicação Copy-on-Write, para que você tenha uma compreensão mais abrangente.

Campos de aplicativo do modo Copiar na gravação

Introduzidos CopyOnWriteArrayListe CopyOnWriteArraySetesses dois contêineres Copy-on-Write, a idéia de design por trás deles é Copy-on-Write: a operação de leitura alcançada pelos dois contêineres do Copy-on-Write é livre de bloqueios, porque não há bloqueio, portanto, o desempenho da operação de leitura Ao extremo.

Além do campo de Java, o Copy-on-Write também é amplamente utilizado no campo de sistemas operacionais.

A cópia na gravação está no campo dos sistemas operacionais. A API para criar processos nos sistemas operacionais do tipo Unix é fork (). A função tradicional fork () criará uma cópia completa do processo pai. Por exemplo, o espaço de endereço do processo pai agora usa 1G de memória e o processo filho fork () É necessário copiar o espaço de endereço (ocupando 1G de memória) de todo o processo do processo pai para o processo filho, que consome muito tempo. A função fork () no Linux é muito mais inteligente: quando fork () um processo filho, o espaço de endereço de todo o processo não é copiado, mas os processos pai e filho compartilham o mesmo espaço de endereço; usado apenas no processo pai ou no processo filho O espaço de endereço é copiado apenas quando a gravação é necessária, para que os processos pai e filho tenham seus próprios espaços de endereço.

Essencialmente, o espaço de endereço e os dados dos processos pai e filho são isolados.Usar Copiar na gravação é mais uma estratégia de atraso . Somente quando é realmente necessário copiar, ele é copiado, não copiado antecipadamente. Ao mesmo tempo, o Copy-on-Write também suporta cópias sob demanda, portanto, o Copy-on-Write pode melhorar o desempenho no campo do sistema operacional. Em comparação, o contêiner Copy-on-Write fornecido pelo Java copia o contêiner inteiro ao mesmo tempo da modificação, portanto, ao mesmo tempo em que melhora o desempenho das operações de leitura, isso custa à cópia da memória. Aqui você encontrará que o mesmo é o aplicativo Copy-on-Write, cenas diferentes, o impacto no desempenho é diferente.

No campo dos sistemas operacionais, além do uso de Copy-on-Write no processo de criação, muitos sistemas de arquivos também são usados, como Btrfs (Sistema de Arquivos em Árvore B), aufs (sistema de arquivos de unificação multicamada avançado), etc.

Além do campo Java e do sistema operacional que mencionamos acima, o Copy-on-Write também pode ser visto em muitos outros campos: O design da imagem do contêiner do Docker é Copy-on-Write e até o design por trás do sistema de gerenciamento de origem distribuído Git Os pensamentos têm cópia na gravação ...

No entanto, a maior área de aplicação do Copy-on-Write ainda está em programação funcional . A base da programação funcional é a imutabilidade; portanto, todas as operações de modificação na programação funcional exigem que o Copy-on-Write seja resolvido. Você pode ter dúvidas: "Todas as alterações de dados precisam ser copiadas. O desempenho se tornará um gargalo?" Sua preocupação é justificada. A razão pela qual a programação funcional não surgiu nos primeiros anos e o desempenho definitivamente prejudicou. . Mas com a melhoria do desempenho do hardware, os problemas de desempenho gradualmente se tornaram aceitáveis. Além disso, o Copy-on-Write está longe de ser tão estúpido quanto o CopyOnWriteArrayList em Java: toda a matriz é copiada uma vez. A cópia na gravação também pode ser copiada sob demanda.Se você estiver interessado, pode consultar o livro Estruturas de dados puramente funcionais, que descreve a implementação de várias estruturas de dados invariantes.

Os dois contêineres CopyOnWriteArrayList e CopyOnWriteArraySet copiarão toda a matriz quando forem modificados; portanto, se o contêiner for modificado com frequência ou a própria matriz for muito grande, isso não será recomendado. Por outro lado, se houver muito poucas modificações, o número de matrizes não é grande e o desempenho da leitura é exigente, use o efeito do contêiner Copy-on-Write é muito bom . Familiarize-se com um caso.

Case

Como uma estrutura RPC, o provedor de serviços é implantado com várias instâncias distribuídas, portanto, quando o cliente do serviço chama RPC, ele seleciona uma instância de serviço para chamar.Este processo de seleção está essencialmente fazendo o balanceamento de carga e fazendo A premissa do balanceamento de carga é que o cliente deve ter todas as informações de roteamento. Por exemplo, na figura abaixo, o provedor do serviço A possui 3 instâncias, 192.168.1.1, 192.168.1.2 e 192.168.1.3 Antes que o cliente chame o serviço de destino A, a primeira coisa a ser feita é o balanceamento de carga, que é desse Selecione uma das três instâncias e envie a solicitação para a instância de destino selecionada por meio do RPC.
Insira a descrição da imagem aqui
Uma tarefa principal da estrutura RPC é manter o relacionamento de roteamento de serviços.Podemos simplificar o relacionamento de roteamento de serviços na tabela de roteamento mostrada na figura abaixo. Quando o provedor de serviços fica online ou offline, ele precisa atualizar a tabela de roteamento do cliente.
Insira a descrição da imagem aqui
Primeiro, analisamos como usar o programa para alcançar. Cada chamada RPC precisa calcular o IP e o número da porta do serviço de destino através do balanceador de carga, e o balanceador de carga precisa obter todas as informações de roteamento da interface através da tabela de roteamento, ou seja, cada chamada RPC precisa acessar a tabela de roteamento , portanto O requisito de desempenho para acessar a tabela de roteamento é muito alto. No entanto, a tabela de roteamento não possui altos requisitos de consistência de dados.A tabela de roteamento de um provedor de serviços, de on-line a feedback para o cliente, mesmo que tenha 5 segundos, geralmente é aceitável (5 segundos para o Ena Para uma CPU com segundos como um ciclo de clock, são mais de 10.000 anos, portanto, a tabela de roteamento não possui altos requisitos de consistência). E a tabela de roteamento é um problema típico de ler mais escrever menos classe, a quantidade de operações de gravação em comparação com as operações de leitura, pode ser descrita como uma gota no oceano, lamentável.

Através da análise acima, você encontrará algumas palavras-chave:Requisitos de alto desempenho de leitura, leia mais e escreva menos, consistência fraca. Juntando tudo, o que você acha? CopyOnWriteArrayListE CopyOnWriteArraySetnaturalmente adequado para este cenário ah. Portanto, no código de exemplo a seguir, dentro da classe RouteTable, usamos ConcurrentHashMap<String, CopyOnWriteArraySet<Router>>essa estrutura de dados para descrever a tabela de roteamento, ConcurrentHashMap Key é o nome da interface, Value é a coleção de rotas, essa coleção de rotas que usamos CopyOnWriteArraySet.

Em seguida, vamos pensar em como projetar o roteador.O provedor de serviços atualizará as informações de roteamento toda vez que ficar online ou offline.Neste momento, você tem duas opções. Uma é identificar um bit de status atualizando o roteador, se você fizer isso, todos os locais que acessam o bit de status precisam acessá-lo de forma síncrona, o que afeta o desempenho. A outra é usar o modo Imutabilidade: cada vez que você fica online ou offline, um novo objeto Roteador é criado ou o objeto Roteador correspondente é excluído. Como a frequência de online e offline é muito baixa, a última é a melhor escolha . Se você fica on-line e off-line com frequência, pode usar o modo Imutabilidade, um campo booleano indica se deve ficar off-line, pode usar a API de atualização de campo atômico para operá-lo;

O código de implementação do roteador é mostrado abaixo, que é uma implementação típica do modo Imutabilidade.O que você precisa prestar atenção é que reescrevemos o método equals para que os métodos add () e remove () de CopyOnWriteArraySet possam funcionar corretamente.

//路由信息
public final class Router{

  private final String  ip;
  private final Integer port;
  private final String  iface;
  
  //构造函数
  public Router(String ip, Integer port, String iface){
    this.ip = ip;
    this.port = port;
    this.iface = iface;
  }
  
  //重写equals方法
  public boolean equals(Object obj){
    if (obj instanceof Router) {
      Router r = (Router)obj;
      return iface.equals(r.iface) &&
             ip.equals(r.ip) &&
             port.equals(r.port);
    }
    return false;
  }
  public int hashCode() {
    //省略hashCode相关代码
  }
}

//路由表信息
public class RouterTable {

  //Key:接口名
  //Value:路由集合
  ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> 
                   rt = new ConcurrentHashMap<>();
                   
  //根据接口名获取路由表
  public Set<Router> get(String iface){
    return rt.get(iface);
  }
  
  //删除路由
  public void remove(Router router) {
    Set<Router> set=rt.get(router.iface);
    if (set != null) {
      set.remove(router);
    }
  }
  //增加路由
  public void add(Router router) {
    Set<Router> set = rt.computeIfAbsent(route.iface, r -> 
        new CopyOnWriteArraySet<>());
    set.add(router);
  }
}

Sumário

Atualmente, o Copy-on-Write não é bem conhecido no campo da programação simultânea em Java. Muitas pessoas o ignoraram inadvertidamente, mas na verdade o Copy-on-Write é a solução simultânea mais simples. É tão simples que os tipos de dados básicos em Java, como String, Inteiro, Longo, etc., são todos baseados no esquema Copy-on-Write.

O Copy-on-Write é uma solução técnica muito versátil e possui uma ampla gama de aplicações em muitos campos. No entanto, também possui desvantagens, ou seja, consome memória. Cada modificação requer a cópia de um novo objeto. Felizmente, com a maturidade dos algoritmos de coleta automática de lixo (GC) e o desenvolvimento de hardware, esse consumo de memória foi gradualmente aceitável. Também. Portanto, no trabalho real, se houver muito poucas operações de gravação, você poderá tentar usar o Copy-on-Write, o efeito ainda será bom.

Publicado 138 artigos originais · Gosto 3 · Visitante 7209

Acho que você gosta

Origin blog.csdn.net/weixin_43719015/article/details/105691664
Recomendado
Clasificación