Estágio 8: Estrutura de serviço avançada (Capítulo 6: ElasticSearch3)
- Capítulo 6: `ElasticSearch`
- Mecanismo de pesquisa distribuído 3
- 0. Objetivos de aprendizagem
- 1. Agregação de dados
- 2. Preenchimento automático _
- 3. Sincronização de dados
- 4. Aglomerado
Capítulo Seis:ElasticSearch
Mecanismo de pesquisa distribuído 3
0. Objetivos de aprendizagem
1. Agregação de dados
As agregações nos permitem implementar de forma extremamente convenienteEstatísticas, análise e cálculo de dados. Por exemplo:
- Qual marca de telefones celulares é a mais popular?
- Qual é o preço médio, preço máximo e preço mínimo desses celulares?
- Quais são as vendas mensais desses telefones?
sql
É muito mais conveniente implementar essas funções estatísticas do que o banco de dados , e a velocidade da consulta é muito rápida e os efeitos de pesquisa em tempo real podem ser alcançados.
1.1. Tipos de agregação
Existem três tipos comuns de agregação:
-
Agregação de intervalo :Usado para agrupar documentos e contar o número de cada grupo
TermAggregation
:de acordo comValor do campo do documentoAgrupamento, como agrupamento por valor de marca, agrupamento por paísDate Histogram
:de acordo comescada de dataAgrupamento, como um grupo por semana ou um grupo por mês
Os campos agregados não são segmentados
-
Agregação de métricas : usada paracalcule alguns valores, tais como: valor máximo, valor mínimo, valor médio, etc.
- Média: valor médio
- Máx.: Encontre o valor máximo
- Min: Encontre o valor mínimo
Stats
: Encontre máximo, mínimo, média, soma, etc. ao mesmo tempo
-
Agregação de pipeline :Agregar com base nos resultados de outras agregações
Perceber: Os campos participantes da agregação devem ser
keyword
, ,日期
,数值
布尔类型
;
(Ou seja, os campos que participam da agregação são todos campos que não podem ser segmentados)
1.2. DSL implementa agregação
Agora queremos contar os tipos de marcas de hotéis em todos os dados, na verdade agrupamos os dados de acordo com a marca. Neste momento a agregação pode ser feita com base no nome da marca do hotel, ou seja, Bucket
agregação.
1.2.1.Bucket
Sintaxe de agregação (agregação de bucket)
A sintaxe é a seguinte:
GET /hotel/_search
{
"size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果
"aggs": {
// 定义聚合
"brandAgg": {
//给聚合起个名字,随便起;
"terms": {
// 聚合的类型,按照字段值聚合,所以选择term,代表TermAggregation,按照文档字段值分组
"field": "brand", // 参与聚合的字段
"size": 20 // 希望获取的聚合结果数量
}
}
}
}
acimaO que é importante é o seguinte: Nome da agregação, tipo de agregação, valor do campo ;
"brandAgg": {
//给聚合起个名字,随便起;
"terms": {
// 聚合的类型,按照字段值聚合,所以选择term
"field": "brand", // 参与聚合的字段
O resultado é mostrado abaixo:
1.2.2.Classificação de resultados de agregação
Por padrão, Bucket
a agregação conta Bucket
o número de documentos dentro, indicado por _count
, e segue_count
Classificar em ordem decrescente。
Podemos especificar o atributo order para personalizar o método de classificação da agregação:
GET /hotel/_search
{
"size": 0, // 没置size为0,结果中不包含文档,只包含聚合结果
"aggs": {
// 定义聚合
"brandAgg": {
//给聚合起个名字,随便起;
"terms": {
// 聚合的类型,按照字民值聚合,所以选择term,代表TermAggregation,按照文档字段值分组
"field": "brand", // 参与案合的字段
"order": {
"_count": "asc" // 按照_count升序排列
},
"size": 20 // 希望获取的聚合结果数量;
}
}
}
}
1.2.3.Limitar o escopo de agregação
Por padrão, a agregação de bucket agrega todos os documentos no banco de dados de índice, mas em cenários reais, os usuários inserirão condições de pesquisa, portantoA agregação precisa ser uma agregação dos resultados da pesquisa. EntãoA agregação deve ser qualificada。
pudermosLimitar o escopo dos documentos a serem agregados, basta adicionar query
uma condição:
GET /hotel/_search
{
"query": {
"range": {
"price": {
"lte": 200 // 只对200元以下的文档聚合
}
}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}
Desta vez, o número de marcas agregadas foi significativamente menor:
1.2.4.Metric
Sintaxe de agregação (agregação de métrica)
Acima agrupamos os hotéis de acordo com as marcas, formando baldes. Agora precisamos fazer cálculos sobre os hotéis do intervalo,Obtenha a avaliação do usuário de cada marca , valores equivalentesmin
max
avg
。
Isso requer o uso de Metric
agregação, como agregação de estatísticas: você pode obter resultados como min
, max
e assim por diante.avg
A sintaxe é a seguinte:
GET /hotel/_search
{
"size": 0,
"aggs": {
//定义聚合
"brandAgg": {
//给聚合起个名字,随便起
"terms": {
//terms聚合
"field": "brand",
"size": 20 //希望获取的聚合结果数量
},
"aggs": {
// 是brands聚合的子聚合,也就是分组后对每组分别计算
"score_stats": {
// 聚合名称
"stats": {
// 聚合类型,这里stats可以计算min、max、avg等
"field": "score" // 聚合字段,这里是score
}
}
}
}
}
}
Esta score_stats
agregação é uma subagregação aninhadabrandAgg
na agregação . Porque precisamos calculá-lo separadamente em cada intervalo.
Além disso, também podemos classificar os resultados da agregação, por exemplo, pela pontuação média do hotel em cada intervalo:
1.2.5.resumo
aggs
Representa agregação, e query同级
qual query
é o seu papel neste momento?
- Limitar o escopo dos documentos a serem agregados
Três elementos são necessários para agregação:
- Nome da agregação
- Tipo de agregação
- Campos agregados
As propriedades configuráveis agregadas são:
size
:Especifique o número de resultados de agregaçãoorder
: Especifique o método de classificação dos resultados de agregaçãofield
:Especifique o campo de agregação
1.3. RestAPI implementa agregação
1.3.1.Sintaxe da API
Condições de agregaçãoequery
doençaNo mesmo nível, você precisa usá-lo request.source()
para especificar condições de agregação.
Sintaxe das condições de agregação:
os resultados da agregação também são diferentes dos resultados da consulta e a API também é especial. Mas o mesmo JSON é analisado camada por camada:
1.3.2.Necessidades empresariais
Caso: Definir métodos em IUserService para obter agregação de marcas, cidades e classificações por estrelas.
Requisitos: A marca, cidade e outras informações na página de pesquisa não devem ser codificadas na página, mas obtidas pela agregação dos dados do hotel em a biblioteca de índice:
Análise:
atualmente, a lista de cidades, a lista de estrelas e a lista de marcas na página são codificadas e não serão alteradas com os resultados da pesquisa. Mas quando as condições de pesquisa do usuário mudam, os resultados da pesquisa mudam de acordo.
Por exemplo, se um usuário pesquisar "Oriental Pearl Tower", o hotel pesquisado deverá estar próximo à Oriental Pearl Tower em Xangai. Portanto, a cidade só pode ser Xangai. No momento, a lista de cidades não deve exibir informações como Pequim , Shenzhen e Hangzhou.
Quer dizer,Quais cidades estão incluídas nos resultados da pesquisa, quais cidades devem ser listadas na página; quais marcas estão incluídas nos resultados da pesquisa, quais marcas devem ser listadas na página。
Como posso saber quais marcas estão incluídas nos meus resultados de pesquisa? Como posso saber quais cidades estão incluídas nos meus resultados de pesquisa?
Use a função de agregação e a agregação de bucket para agrupar os documentos nos resultados da pesquisa com base em marcas e cidades, para que você possa saber quais marcas e quais cidades estão incluídas.。
Como os resultados da pesquisa são agregados, a agregação éagregação com escopo,Por outras palavras, as condições de agregação são consistentes com as condições de pesquisa de documentos.。
Olhando para o navegador, você pode descobrir que o front-end realmente emitiu tal solicitação:
os parâmetros da solicitação são exatamente iguais aos parâmetros do documento de pesquisa .
O tipo de valor de retorno é o resultado final a ser exibido na página:
o resultado é uma Map
estrutura:
key
É uma string, cidade, classificação por estrelas, marca, preçovalue
é uma coleção, como os nomes de várias cidades
1.3.3.Realização de negócios
Adicione um método ao cn.itcast.hotel.web
pacote HotelController
com os seguintes requisitos:
- Método de solicitação:
POST
- Caminho da solicitação:
/hotel/filters
- Parâmetros de solicitação:
RequestParams
, consistentes com os parâmetros de busca de documentos - Tipo de valor de retorno:
Map<String, List<String>>
Código:
@PostMapping("filters")
public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
return hotelService.getFilters(params);
}
O método chamado aqui ainda não IHotelService中
foi getFilters
implementado.
Defina o novo método em cn.itcast.hotel.service.IHotelService
:
Map<String, List<String>> filters(RequestParams params);
Implemente este método em cn.itcast.hotel.service.impl.HotelService
:
@Override
public Map<String, List<String>> filters(RequestParams params) {
try {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1.query只限定范围
buildBasicQuery(params, request);
// 2.2.设置size
request.source().size(0);
// 2.3.聚合
buildAggregation(request);
// 3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Map<String, List<String>> result = new HashMap<>();
Aggregations aggregations = response.getAggregations();
// 4.1.根据品牌名称,获取品牌结果
List<String> brandList = getAggByName(aggregations, "brandAgg");
result.put("品牌", brandList);
// 4.2.根据品牌名称,获取品牌结果
List<String> cityList = getAggByName(aggregations, "cityAgg");
result.put("城市", cityList);
// 4.3.根据品牌名称,获取品牌结果
List<String> starList = getAggByName(aggregations, "starAgg");
result.put("星级", starList);
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void buildAggregation(SearchRequest request) {
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms("cityAgg")
.field("city")
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms("starAgg")
.field("starName")
.size(100)
);
}
private List<String> getAggByName(Aggregations aggregations, String aggName) {
// 4.1.根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get(aggName);
// 4.2.获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 4.3.遍历
List<String> brandList = new ArrayList<>();
for (Terms.Bucket bucket : buckets) {
// 4.4.获取key
String key = bucket.getKeyAsString();
brandList.add(key);
}
return brandList;
}
2. Preenchimento automático _
Quando o usuário insere um caractere na caixa de pesquisa, devemos solicitar termos de pesquisa relacionados ao caractere, conforme mostrado na figura:
Esta função que solicita entradas completas com base nas letras inseridas pelo usuário é de preenchimento automático.。
Como precisa ser inferido com base nas letras Pinyin, a função de segmentação de palavras Pinyin é usada.
2.1. Segmentador de palavras Pinyin
Para conseguir o preenchimento baseado em cartas, o documento deve ser segmentado de acordo com o Pinyin. Acontece que elasticsearch
existe . Endereço: https://github.com/medcl/elasticsearch-análise-pinyin
Os materiais pré-curso também fornecem um pacote de instalação para o segmentador de palavras Pinyin:
o método de instalação é o mesmo do segmentador de palavras IK, que é dividido em três etapas:
①Descompacte
②Fazer upload para o diretório elasticsearch
na máquina virtual ③Reiniciar ④Testeplugin
elasticsearch
Para etapas de instalação detalhadas, consulte: Processo de instalação do segmentador de palavras IK .
O uso do teste é o seguinte:
POST /_analyze
{
"text": "如家酒店还不错", #要分词的内容;
"analyzer": "pinyin" #分词器
}
resultado:
2.2. Segmentador de palavras personalizado
O tokenizer Pinyin padrão separará cada caractere chinês em Pinyin, e o que queremos é que cada entrada forme um grupo de Pinyin, então precisamos fazer algumas modificações no tokenizer Pinyin.Personalização personalizada,formaTokenizador personalizado。
elasticsearch
O segmentador de palavras do meio ( analyzer
) consiste em três partes::
character filters
:tokenizer
Processe o texto antes. Por exemplo, exclua caracteres, substitua caracterestokenizer
: Recorte o texto em termos de acordo com certas regras (term
). Por exemplokeyword
, não há segmentação de palavras; eik_smart
tokenizer filter
:tokenizer
processe ainda mais as entradas de saída. Por exemplo, conversão de maiúsculas e minúsculas, processamento de sinônimos, processamento pinyin, etc.
Quando o documento é segmentado, o documento será processado por estas três partes em sequência:
Podemos configurar um analisador personalizado (segmentador de palavras) por meio de configurações ao criar uma biblioteca de índices:
a sintaxe para declarar um tokenizer customizado é a seguinte:
PUT /test //创建名为test的索引库
{
"settings": {
//定义索引库的分词器的
"analysis": {
"analyzer": {
// 自定义分词器
"my_analyzer": {
// 分词器名称
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": {
// 自定义tokenizer filter
"py": {
// 过滤器名称
"type": "pinyin", // 过滤器类型,这里是pinyin
"keep_full_pinyin": false, //解决单个字拼的问题;
"keep_joined_full_pinyin": true, //全拼
"keep_original": true, //保留中文
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
//mappings映射时
"properties": {
"name": {
"type": "text",
"analyzer": "my_analyzer", //name使用自定义的my_analyzer分词器;analyzer在创建索引时使用
"search_analyzer": "ik_smart" //search_analyzer在搜索索引时使用;
}
}
}
}
Teste: (Os resultados incluem pinyin e caracteres chineses)
Resumir:
Como usar o tokenizador Pinyin?
- ① Baixe
pinyin
o segmentador de palavras - ②Descompacte e coloque-o
elasticsearch
noplugin
diretório - ③Reiniciar
Como personalizar o tokenizador?
- ① Ao criar uma biblioteca de índice,
settings
configure-a e pode incluir três partes - ②
character filter
- ③
tokenizer
- ④
filter
O que devo prestar atenção ao usar o segmentador de palavras Pinyin?
- Para evitar a busca por homófonos, use o tokenizer Pinyin ao criar o índice; não use o tokenizer Pinyin ao pesquisar.
2.3.Consulta de preenchimento automático
elasticsearch
A consulta do Sugestor de conclusão é fornecida para implementar a função de conclusão automática. Esta consulta corresponderá e retornará termos que começam com o que o usuário inseriu. Para melhorar a eficiência do preenchimento das consultas, existem algumas restrições quanto aos tipos de campos do documento:
- Os campos participantes da consulta de preenchimento devem ser
completion
do tipo。 - O conteúdo do campo geralmente é uma matriz formada por múltiplas entradas usadas para preenchimento.。
Por exemplo, uma biblioteca de índice como esta:
// 创建索引库
PUT test
{
"mappings": {
"properties": {
"title":{
"type": "completion"
}
}
}
}
Em seguida insira os seguintes dados:
// 示例数据
POST test/_doc
{
"title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
"title": ["SK-II", "PITERA"]
}
POST test/_doc
{
"title": ["Nintendo", "switch"]
}
A instrução DSL de consulta é a seguinte:
// 自动补全查询
GET /test/_search
{
"suggest": {
"title_suggest": {
//随意起的名称;
"text": "s", // 关键字
"completion": {
//自动补全的类型
"field": "title", // 补全查询的字段
"skip_duplicates": true, // 跳过重复的
"size": 10 // 获取前10条结果
}
}
}
}
resumo:
2.4. Implementar o preenchimento automático da caixa de pesquisa de hotéis
Agora, nossa hotel
biblioteca de índice não configurou um segmentador de palavras Pinyin e precisamos modificar a configuração na biblioteca de índice. mas nós sabemosA biblioteca de índices não pode ser modificada, só pode ser excluída e recriada.。
Além disso, precisamos adicionar um campo para preenchimento automático, colocar marca, sugestão, cidade, etc. como um prompt de preenchimento automático.
Então, para resumir, nósAs coisas que precisam ser feitas incluem:
-
Modifique a estrutura do banco de dados de índice de hotéis e configure um segmentador de palavras Pinyin personalizado
-
Modifique o nome e todos os campos da biblioteca de índice e use um segmentador de palavras personalizado
-
A biblioteca de índice adiciona uma nova sugestão de campo, cujo tipo é de conclusão e usa um segmentador de palavras personalizado.
-
Adicione um campo de sugestão à classe HotelDoc, contendo marca e negócio
-
Reimportar dados para o banco de dados do hotel
2.4.1.Modificar estrutura de mapeamento de hotel
código mostrado abaixo:
// 酒店数据索引库
PUT /hotel
{
"settings": {
//定义分词器
"analysis": {
"analyzer": {
"text_anlyzer": {
//全文检索使用的分词器
"tokenizer": "ik_max_word",
"filter": "py"
},
"completion_analyzer": {
//自动补全使用的分词器
"tokenizer": "keyword",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"id":{
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "text_anlyzer", //创建索引时使用的分词器
"search_analyzer": "ik_smart", //搜索时的分词器
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword",
"copy_to": "all"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "text_anlyzer", //创建索引时使用的分词器"text_anlyzer"
"search_analyzer": "ik_smart" //搜索时使用的分词器"ik_smart"
},
"suggestion":{
//自动补全的字段
"type": "completion",
"analyzer": "completion_analyzer" //分词器;
}
}
}
}
2.4.2.Modificar entidade HotelDoc
HotelDoc
É necessário adicionar um campo para preenchimento automático, o conteúdo pode ser marca do hotel, cidade, bairro comercial e outras informações. De acordo com os requisitos dos campos de preenchimento automático, é melhor ser uma matriz desses campos.
Portanto, HotelDoc
adicionamos um suggestion
campo do tipo List<String>
e, em seguida, colocamos informações como brand
, e assim por diante.city
business
código mostrado abaixo:
package cn.itcast.hotel.pojo;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
private Object distance;
private Boolean isAD;
private List<String> suggestion;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
// 组装suggestion
if(this.business.contains("/")){
// business有多个值,需要切割
String[] arr = this.business.split("/"); //数组;
// 添加元素
this.suggestion = new ArrayList<>();
this.suggestion.add(this.brand);
Collections.addAll(this.suggestion, arr); //批量添加,将数组中的元素一个一个的添加进集合;
}else {
this.suggestion = Arrays.asList(this.brand, this.business); //集合
}
}
}
2.4.3. Reimportação
Execute novamente a função de importação de dados escrita anteriormente e você verá que os novos dados do hotel contêm sugestões:
2.4.4.JavaAPI para consultas de preenchimento automático
Anteriormente, aprendemos o DSL para conclusão automática de consultas, mas não aprendemos a API Java correspondente. Aqui está um exemplo:
Os campos da consulta de conclusão acima devem ser escritos como seus
Os resultados do preenchimento automático também são bastante especiais. O código analisado é o seguinte:
2.4.5.Implementar o preenchimento automático da caixa de pesquisa
Olhando para a página front-end, podemos descobrir que quando digitamos na caixa de entrada, o front-end iniciará ajax
uma solicitação:
o valor de retorno é uma coleção de termos preenchidos, do tipoList<String>
1) Adicione uma nova interface cn.itcast.hotel.web
ao pacote HotelController
para receber novas solicitações:
@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
return hotelService.getSuggestions(prefix);
}
2) Adicione métodos ao cn.itcast.hotel.service
pacote :IhotelService
List<String> getSuggestions(String prefix);
3) cn.itcast.hotel.service.impl.HotelService
Implemente o método em:
@Override
public List<String> getSuggestions(String prefix) {
//prefix是前端传过来的参数,是关键字
try {
// 1.准备Request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
request.source().suggest(new SuggestBuilder().addSuggestion(
"suggestions",
SuggestBuilders.completionSuggestion("suggestion") //补全字段
.prefix(prefix) //前段传过来的参数,是关键字
.skipDuplicates(true)
.size(10)
));
// 3.发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Suggest suggest = response.getSuggest();
// 4.1.根据补全查询名称,获取补全结果
CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
// 4.2.获取options
List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
// 4.3.遍历
List<String> list = new ArrayList<>(options.size());
for (CompletionSuggestion.Entry.Option option : options) {
String text = option.getText().toString();
list.add(text);
}
return list;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3. Sincronização de dados
elasticsearch
Os dados do hotel vêm do mysql
banco de dados, portanto mysql
, quando os dados mudam, elasticsearch
eles também devem mudar. Esta é a sincronização de dadoselasticsearch
entremysql
。
3.1. Análise de ideias
Existem três soluções comuns de sincronização de dados:
- Chamada síncrona
- Notificação assíncrona
- monitor
binlog
3.1.1.Chamada síncrona
Opção 1: chamada síncrona
As etapas básicas são as seguintes:
hotel-demo
Fornece uma interface externa para modificarelasticsearch
os dados em- Serviços de gestão hoteleira estão sendo concluídosOperações de banco de dadosDepois disso, chame diretamente
hotel-demo
a interface fornecida,
3.1.2. Notificação assíncrona
Opção 2: notificação assíncrona
O processo é como se segue:
hotel-admin
Apósmysql
adicionar, excluir e modificar dados do banco de dados, envieMQ
uma mensagemhotel-demo
Monitore e conclua a modificação dos dadosMQ
após receber a mensagemelasticsearch
3.1.3.monitorbinlog
Opção 3: binlog
O processo de monitoramento é o seguinte:
- Ative
mysql
abinlog
função mysql
As operações concluídas de adição, exclusão e modificação serão registradasbinlog
emhotel-demo
Com base nocanal
monitoramentobinlog
das alterações,elasticsearch
o conteúdo é atualizado em tempo real
3.1.4.Selecionar
Método 1: chamada síncrona
- Vantagens: simples de implementar, bruto
- Desvantagens: alto grau de acoplamento de negócios
Método 2: notificação assíncrona
- Vantagens: baixo acoplamento, dificuldade média de implementação
- Desvantagens: Confie na confiabilidade do mq
Método 3: Monitoramentobinlog
- Vantagens: Serviços de dissociação completa
- Desvantagens: A abertura
binlog
aumenta a carga do banco de dados e alta complexidade de implementação
3.2. Implementar sincronização de dados
Utilize MQ
implementação mysql
e elasticsearch
sincronização de dados
3.2.1. Ideias
Utilize o projeto disponibilizado no material pré-curso hotel-admin
como um microsserviço para gestão hoteleira. Quando os dados do hotel são adicionados, excluídos ou modificados, as elasticsearch
mesmas operações devem ser realizadas para os dados do centro.。
etapa:
- Importe o projeto fornecido pelos materiais pré-curso
hotel-admin
, inicie e teste os dados do hotelCRUD
- declarar
exchange
(switch),queue
(fila),RoutingKey
- Conclua o envio da mensagem no
hotel-admin
campo adicionar, excluir e modificar negócios em hotel-demo
Conclua o monitoramento de mensagens e atualizeelasticsearch
os dados em- Iniciar e testar a funcionalidade de sincronização de dados
3.2.2.Demonstração de importação
Importe os projetos fornecidos pelos materiais pré-aula hotel-admin
:
após a execução, visitehttp://localhost:8099
Inclui CRUD
funções de hotel:
3.2.3. Declarar switches e filas
MQ
A estrutura é mostrada na figura:
No tutorial, o switch e a fila são declarados hotel-demo
no consumidor;
1)Introduzir dependências
hotel-admin
Dependências hotel-demo
introduzidas em rabbitmq
:
<!--amqp-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1)amqp
endereço configurado
Endereço configurado no .yml
arquivo amqp
:
2)Declarar o nome do switch de fila
Crie uma nova classe no pacote em hotel-admin
e :hotel-demo
cn.itcast.hotel.constatnts
MqConstants
package cn.itcast.hotel.constatnts;
public class MqConstants {
/**
* 交换机
*/
public final static String HOTEL_EXCHANGE = "hotel.topic";
/**
* 监听新增和修改的队列
*/
public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
/**
* 监听删除的队列
*/
public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
/**
* 新增或修改的RoutingKey
*/
public final static String HOTEL_INSERT_KEY = "hotel.insert";
/**
* 删除的RoutingKey
*/
public final static String HOTEL_DELETE_KEY = "hotel.delete";
}
3)Declarar o switch de fila (definir a fila do switch, relacionamento de ligação routingKey)
Em hotel-demo
, cn.itcast.hotel.config
defina a classe de configuração no pacote MqConfig
e declare a fila e o switch:
package cn.itcast.hotel.config;
@Configuration
public class MqConfig {
@Bean
public TopicExchange topicExchange(){
//交换机
return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false); //true代表持久化
}
@Bean
public Queue insertQueue(){
//增加和修改的队列
return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
}
@Bean
public Queue deleteQueue(){
//删除的队列;
return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
}
@Bean
public Binding insertQueueBinding(){
//绑定关系;
//insertQueue()队列绑定到topicExchange()交换机,使用MqConstants.HOTEL_INSERT_KEY这个RoutingKey
return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
}
@Bean
public Binding deleteQueueBinding(){
//绑定关系;
//deleteQueue()队列绑定到topicExchange()交换机,使用MqConstants.HOTEL_DELETE_KEY这个RoutingKey
return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
}
}
3.2.4.Enviar mensagens MQ
Sob o pacote hotel-demo
do generalcn.itcast.hotel.constatnts
Declarar o nome do switch de filaMqConstants
Copie a classe hotel-admin
para cn.itcast.hotel.constatnts
o pacote para que não haja erros ao usar vários nomes; adicione
também as mesmas dependências e configure o mesmo endereço ;3.2.3
amqp
Envie mensagens respectivamente no hotel-admin
negócio de adicionar, excluir e modificar MQ
: ( na classe do pacote hotel-admin
do projeto ):cn.itcast.hotel.web
HotelController
Injete o que é necessário para enviar a mensagem api
:
3.2.5.Receber mensagens MQ
hotel-demo
MQ
O que fazer ao receber uma mensagem inclui:
新增
hotel
Mensagem: de acordo com as informaçõesid
de consulta passadashotel
, adicione um dado ao banco de dados de índice删除
Mensagem: Exclua um dado no banco de dados de índice com base nohotel
passadoid
1) Primeiro adicione e exclua serviços hotel-demo
do cn.itcast.hotel.service
pacoteIHotelService
void deleteById(Long id);
void insertById(Long id);
2) Implementar o negócio do hotel-demo
pacote :cn.itcast.hotel.service.impl
HotelService
@Override
public void deleteById(Long id) {
try {
// 1.准备Request
DeleteRequest request = new DeleteRequest("hotel", id.toString());
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void insertById(Long id) {
try {
// 0.根据id查询酒店数据
Hotel hotel = getById(id);
// 转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 1.准备Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
// 2.准备Json文档
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
// 3.发送请求
client.index(request, RequestOptions.DEFAULT);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3) Escreva um ouvinte e adicione uma nova classe
no hotel-demo
pacote cn.itcast.hotel.mq
:
package cn.itcast.hotel.mq;
@Component
public class HotelListener {
@Autowired
private IHotelService hotelService;
/**
* 监听酒店新增或修改的业务
* @param id 酒店id
*/
@RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
public void listenHotelInsertOrUpdate(Long id){
hotelService.insertById(id);
}
/**
* 监听酒店删除的业务
* @param id 酒店id
*/
@RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
public void listenHotelDelete(Long id){
hotelService.deleteById(id);
}
}
4. Aglomerado
Ao fazer o armazenamento de dados em uma única máquina elasticsearch
, você inevitavelmente enfrentará dois problemas:Problema de armazenamento em massa de dados、Problema de ponto único de falha。
-
Problema de armazenamento em massa de dados: Divida logicamente a biblioteca de índice em N fragmentos (
shard
) e armazene-os em vários nós
-
Problema de ponto único de falha: Faça backup de dados fragmentados em nós diferentes (
replica
)
Conceitos relacionados ao cluster ES :
-
Cluster: um grupo de nós com um nome de cluster comum.
-
Nó : uma instância do Elasticsearch no cluster
-
Fragmentação : O índice pode ser dividido em diferentes partes para armazenamento, chamado fragmentação. Em um ambiente de cluster, diferentes fragmentos de um índice podem ser divididos em nós diferentes
Resolva o problema: A quantidade de dados é muito grande e a capacidade de armazenamento em um único ponto é limitada.
Aqui, dividimos os dados em 3 fatias: shard0, shard1, shard2
-
Fragmento primário (
Primary shard
): relativo à definição de fragmentos de réplica. -
Fragmentos de réplica (
Replica shard
) Cada fragmento primário pode ter uma ou mais réplicas e os dados são iguais aos do fragmento primário.
O backup de dados pode garantir alta disponibilidade, mas se você fizer backup de cada fragmento, o número de nós necessários dobrará e o custo será muito alto!
Para encontrar um equilíbrio entre alta disponibilidade e custo, podemos fazer o seguinte:
- Primeiro, os dados são fragmentados e armazenados em nós diferentes.
- Em seguida, faça backup de cada fragmento e coloque-o no outro nó para concluir o backup mútuo.
Isso pode reduzir bastante o número de nós de serviço necessários. Conforme mostrado na figura, tomamos 3 fragmentos e um backup para cada fragmento como exemplo:
Agora, cada fragmento tem 1 backup, armazenado em 3 nós:
- node0: salva os fragmentos 0 e 1
- node1: salva os fragmentos 0 e 2
- node2: fragmentos salvos 1 e 2
4.1. Construir cluster ES
Documentos de referência para materiais pré-curso :
Capítulo 4:
4.2. Problema de cluster com cérebro dividido
4.2.1. Divisão de responsabilidades do cluster
elasticsearch
Os nós de cluster médios têm responsabilidades diferentes:
Por padrão, qualquer nó no cluster possui as quatro funções acima.
Mas um cluster real deve separar as responsabilidades do cluster:
master节点
: Altos requisitos de CPU, mas poucos requisitos de memóriadata节点
: Altos requisitos para CPU e memóriacoordinating节点
: Altos requisitos de largura de banda de rede e CPU
A separação de tarefas nos permite alocar diferentes hardwares para implantação de acordo com as necessidades dos diferentes nós. E evite interferência mútua entre empresas.
Uma divisão típica es
das responsabilidades do cluster é a seguinte:
4.2.2.problema de cérebro dividido
O cérebro dividido é causado pela desconexão dos nós do cluster.
Por exemplo, em um cluster, o nó mestre perde contato com outros nós:
neste momento, node2
se node3
for considerado node1
inativo, ele reelegerá o mestre:
quando o nó3 for eleito, o cluster continuará prestando serviços ao mundo exterior, node2 e node3 formam seu próprio cluster e node1 forma seu próprio cluster.Os dados dos dois clusters estão fora de sincronia e ocorrem diferenças de dados.。
Quando a rede é restaurada, porque há dois nós mestres no cluster, o status do cluster é inconsistente e ocorre uma situação de divisão cerebral:
Solução para dividir o cérebroSim, obrigatórioO número de votos deve exceder (número de nós elegíveis + 1)/2 para ser eleito líder., portanto, o número de nós elegíveis é preferencialmente um número ímpar. O item de configuração correspondente é Discovery.zen.minimum_master_nodes, que se tornou a configuração padrão após es7.0, portanto, geralmente não ocorrem problemas de cérebro dividido.
Por exemplo: para um cluster formado por 3 nós, os votos devem exceder (3+1)/2, que é 2 votos. node3 recebeu votos de node2 e node3 e foi eleito líder. Node1 tem apenas 1 voto próprio e não foi eleito. Ainda há apenas um nó mestre no cluster e não há cérebro dividido.
4.2.3. Resumo
master eligible
Qual é a função dos nós?
- Participe da eleição do líder do cluster
- O nó mestre pode gerenciar o status do cluster, gerenciar informações de fragmentos e lidar com solicitações para criar e excluir bibliotecas de índice.
data
Qual é a função dos nós?
- CRUD de dados
coordinator
Qual é a função dos nós?
- Rotear solicitações para outros nós
- Combine os resultados da consulta e devolva-os ao usuário
4.3. Armazenamento distribuído em cluster
Quando um novo documento é adicionado, ele deve ser salvo em fragmentos diferentes para garantir o equilíbrio dos dados. Então, coordinating node
como determinar em qual fragmento os dados devem ser armazenados?
4.3.1. Teste de armazenamento fragmentado
Insira três dados:
No teste, você pode ver que os três dados estão em fragmentos diferentes:
resultado:
4.3.2. Princípio do armazenamento fragmentado
elasticsearch
Um algoritmo é usado hash
para calcular em qual fragmento o documento deve ser armazenado:
ilustrar:
_routing
O padrão é documentoid
- O algoritmo está relacionado ao número de fragmentos, portanto, uma vez criada a biblioteca de índice, o número de fragmentos não pode ser modificado !
O processo para adicionar novos documentos é o seguinte:
Interpretação:
- 1) Adicione um
id=1
novo documento - 2) Faça a operação
id
nelehash
. Se o resultado for 2, ele deve ser armazenado emshard-2
- 3)
shard-2
O fragmento primário está nonode3
nó e roteia os dados paranode3
- 4) Salve o documento
- 5) Sincronize a
shard-2
cópia fornecidareplica-2
nonode2
nó - 6) Retorne o resultado para
coordinating-node
o nó
4.4. Consulta distribuída de cluster
elasticsearch
A consulta é dividida em duas etapas:
scatter phase
:estágio de dispersão,coordinating node
(nó coordenador) distribuirá a solicitação para cada fragmentogather phase
:estágio de agregação,coordinating node
resumirdata node
os resultados da pesquisa e processá-los em um conjunto de resultados final retornado ao usuário
resumo:
4.5. Failover de cluster
Os nós no cluster master
monitorarão o status dos nós no cluster. Se um nó estiver inativo, os dados fragmentados do nó inativo serão imediatamente migrados para outros nós para garantir a segurança dos dados. Isso é chamadofailover。
1) Por exemplo, uma estrutura de cluster é mostrada na figura:
Agora, node1
é o nó mestre e os outros dois nós são nós escravos.
2) De repente, node1
ocorre uma falha:
a primeira coisa após o tempo de inatividade é reeleger o mestre. Por exemplo, se você selecionar node2
:
node2
Depois de se tornar o nó mestre, o status de monitoramento do cluster será verificado e será descoberto que: shard-1
, shard-0
não há nó de réplica. node1
Portanto, os dados acima precisam ser migrados para node2
:node3
resumo: