Начало работы с Elasticsearch и освоение его Java API

Elasticsearch

Личный технический блог:www.zhenganwen.top

окрестности

Установить ЕС

Структура проекта ЭС

распаковатьelasticsearch-6.2.1.zip, каталог, полученный после распаковки, ==корневой каталог ES==, и функции каждого каталога следующие:

  • bin, в котором хранятся командные сценарии, такие как запуск ES
  • config, конфигурационный файл для хранения ЭП, содержимое которого считывается при запуске ЭП
    • elasticsearch.yml, конфигурация информации о кластере ES, внешние порты, блокировка памяти, каталог данных, междоменный доступ и другие атрибуты
    • jvm.options, ES написан на Java, этот файл используется для установки параметров, связанных с JVM, таких как максимальная куча, минимальная куча
    • log4j2.properties, ES использует log4j в качестве основы ведения журнала.
  • данные, каталог хранения данных (данные индекса)
  • lib, ES-зависимая библиотека
  • logs, директория для хранения логов
  • модули, каждый функциональный модуль ЭС
  • plugins, каталог хранения расширяемых плагинов ES. Например, вы можете поместить плагин сегментации китайских слов ik в этот каталог, и он будет автоматически загружен при запуске ES.

конфигурация свойства

Все свойства в файле elasticsearch.yml по умолчанию аннотированы.Нам нужно установить некоторые необходимые значения свойств и добавить следующее в конец текста (конфигурация, связанная с кластером, будет подробно объяснена позже):

cluster.name: xuecheng #集群名称,默认为elasticsearch
node.name: xc_node_1   #节点名称,一个ES实例就是一个节点(通常一台机器上只部署一个ES实例)
network.host: 0.0.0.0  #IP绑定,0.0.0.0表示所有IP都可访问到此ES实例
http.port: 9200	       #通过此端口以RESTful的形式访问ES
transport.tcp.port: 9300 #ES集群通信使用的端口
node.master: true	   #此节点是否能够作为主节点
node.data: true  	   #此节点是否存放数据
discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]	#集群其他节点的通信端口,ES启动时会发现这些节点
discovery.zen.minimum_master_nodes: 1	#主节点数量的最少值,此值的计算公式为(master_eligible_nodes/2)+1,即可作为主节点的节点数/2+1
node.ingest: true	   #此节点是否作为协调节点,当索引库具有多个分片并且各分片位于不同节点上时,如果收到查询请求的节点发现要查询的数据在另一个节点的分片上,那么作为协调节点的该节点将会转发此请求并最终响应结果数据
bootstrap.memory_lock: false	#是否锁住ES占用的内存,此项涉及到OS的swap概念,当ES空闲时操作系统可能会把ES占用的内存数据暂时保存到磁盘上,当ES活动起来时再调入内存,如果要求ES时刻保持迅速响应状态则可设置为true,那么ES的运行内存永远不会被交换到磁盘以避免交换过程带来的延时
node.max_local_storage_nodes: 2	#本机上的最大存储节点数,多个ES实例可以共享一个数据目录,这一特性有利于我们在开发环境的一台机器上测试集群机制,但在生产环境下建议设置为1,并且官方也建议一台集群仅部署一个ES实例

path.data: D:\software\es\elasticsearch-6.2.1\data	#ES的数据目录
path.logs: D:\software\es\elasticsearch-6.2.1\logs	#ES的日志目录

http.cors.enabled: true			#是否允许跨域访问,后面通过一个可视化ES管理插件时需要通过js跨域访问此ES
http.cors.allow-origin: /.*/	#设置所有域均可跨域访问此ES

Настройки параметров JVM

Память кучи по умолчанию, которая должна быть выделена для запуска ES, составляет 1 Г. Если память вашего компьютера мала, вы можетеjvm.optionsСреда настроена на 512M:

-Xms512m
-Xmx512

начать ES

двойной щелчок/bin/elasticsearch.batЗапустите скрипт, чтобы запустить ES, и закройте окно командной строки, чтобы закрыть ES.

Доступ после запуска:http://localhost:9200, если вы получите следующий ответ, ES запустится успешно:

{
    name: "xc_node_1",
    cluster_name: "xuecheng",
    cluster_uuid: "93K4AFOVSD-kPF2DdHlcow",
    version: {
        number: "6.2.1",
        build_hash: "7299dc3",
        build_date: "2018-02-07T19:34:26.990113Z",
        build_snapshot: false,
        lucene_version: "7.2.1",
        minimum_wire_compatibility_version: "5.6.0",
        minimum_index_compatibility_version: "5.0.0"
    },
    tagline: "You Know, for Search"
}

Плагин визуализации elasticsearch-head

ES — это поисковая система на уровне продукта, разработанная на основе Lucene, которая инкапсулирует множество внутренних деталей.Благодаря этому подключаемому модулю мы можем визуально просматривать его внутреннее состояние через Интернет.

Этот плагин не нужно размещать в ES/pluginsкаталог, потому что он взаимодействует с ES через JS.

git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start

Браузер открывается:http://localhost:9100и подключитесь к ES через http-порт, предоставленный ES:

image

Быстрый старт ES

Прежде всего, нам нужно понять несколько понятий: индексная библиотека (index), документ (document), поле (поле), мы можем понять это по аналогии с реляционной базой данных:

ES MySQL
индексная библиотека база данныхбаза данных
type Таблица
документдокумент ряд ряд
поле столбец столбец

Но начиная с ES6.x,typeКонцепция постепенно ослабевает, и в ES9 официально ее уберут. Следовательно, мы можем сравнить библиотеку индексов с таблицей. Библиотека индексов используется для хранения серии данных с аналогичной структурой. Хотя несколькоtypeСоздайте эффект «несколько таблиц» в библиотеке индексов, но официальные лица не рекомендуют этого делать, потому что это снизит производительность индекса и поиска, или вам нужно создать другую библиотеку индексов. По аналогии с MySQL в библиотеку помещается только одна таблица.

Указатель существительных и указатель глаголов

Индекс существительного относится к репозиторию индексов, файлу на диске.

Библиотека индексов представляет собой инвертированную индексную таблицу Процесс сохранения данных в ES заключается в том, чтобы сначала сегментировать данные, а затем добавить их в инвертированную индексную таблицу.

image

Если в качестве примера добавить в индексную базу данных «Китайскую Народную Республику» и «Китай вверх и вниз пять тысяч лет», логическая структура инвертированной индексной таблицы выглядит следующим образом:

term doc_id
Китай 1, 2
люди 1
республика 1
вверх и вниз 2
пять 2
тысячелетие 2
doc_id doc
1 Китайская Народная Республика
2 Пять тысяч лет Китая

Этот процесс сегментации данных и установления связи между каждым сегментом и документом называется ==индекс== (глагол).

Postman

Postman — это клиентский инструмент HTTP, который может легко отправлять различные формы запросов RESTful.

Далее будет использоваться Postman для тестирования ES RESTful API.Корневой URL-адрес запроса:http://localhost:9200

Управление библиотекой индексов

Создайте библиотеку индексов

Создайте индексную библиотеку с именем «xc_course» для хранения данных курса Xuecheng Online (образовательная платформа):

  • PUT /xc_course
{
	"settings":{
		"number_of_shards":1,		//索引库分片数量
		"number_of_replicas":0		//每个分片的副本数,关于分片、集群等在后文详细介绍
	}
}

image

Удачно ли творение? мы можем пройтиelasticsearch-headДля просмотра обновите localhost:9100:

image

удалить библиотеку индексов

DELET /xc_course

Просмотр информации об индексе

GET /xc_course

Управление сопоставлением

Создать карту

Отображение может быть аналогично определению структуры таблицы MySQL, например, какие поля и какие поля.

Формат запроса на создание сопоставления: POST /имя_индекса/имя_типа/_mapping.

Не сказатьtypeОно ослабло? Почему здесь указано имя типа? Поскольку понятие типа официально удалено в ES9, перед этим требуется переходный период, поэтому мы можем указать бессмысленное имя типа, например «doc»:

POST /xc_course/doc/_mapping

{
	"properties":{
		"name":{
			"type":"text"
		},
		"description":{
			"type":"text"
		},
		"price":{
			"type":"double"
		}
	}
}

Отображение представлений (аналоговая структура таблицы представлений)

GET /xc_course/doc/_mapping

{
    "xc_course": {
        "mappings": {
            "doc": {
                "properties": {
                    "description": {
                        "type": "text"
                    },
                    "name": {
                        "type": "text"
                    },
                    "price": {
                        "type": "double"
                    }
                }
            }
        }
    }
}

Его также можно посмотреть через плагин head:

image

управление документами

Добавить документы

PUT /index/type/id

Если вы не укажете идентификатор, ES автоматически сгенерирует его для нас:

PUT /xc_course/doc

{
    "name" : "Bootstrap开发框架",
    "description" : "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
    "price" : 99.9
}

Ответ выглядит следующим образом:

{
    "_index": "xc_course",
    "_type": "doc",
    "_id": "Hib0QmoB7xBOMrejqjF3",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 1,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

Запросить документы по id

GET /index/type/id

Итак, мы получаем идентификатор, сгенерированный данными, которые мы только что добавили в запрос:

GET /xc_course/doc/Hib0QmoB7xBOMrejqjF3

{
    "_index": "xc_course",
    "_type": "doc",
    "_id": "Hib0QmoB7xBOMrejqjF3",
    "_version": 1,
    "found": true,
    "_source": {
        "name": "Bootstrap开发框架",
        "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
        "price": 99.9
    }
}

Запросить все документы

GET /index/type/_search

{
    "took": 64,				//此次查询花费时间
    "timed_out": false,		
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {			   
        "total": 1,
        "max_score": 1,
        "hits": [			//查询匹配的文档集合
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "Hib0QmoB7xBOMrejqjF3",
                "_score": 1,
                "_source": {
                    "name": "Bootstrap开发框架",
                    "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
                    "price": 99.9
                }
            }
        ]
    }
}

IK китайский токенизатор

ES по умолчанию не поддерживает сегментацию китайских слов, то есть для добавленных китайских данных ES будет рассматривать каждое слово как термин (термин), что не способствует поиску на китайском языке.

Протестируйте результат сегментации китайских слов по умолчанию в ES:

POST /_analyze

Вы обнаружите, что фиксированный API ES будет приведен_префикс, например_mapping,_search,_analyze

{
	"text":"中华人民共和国"
}

Результаты сегментации слов следующие:

{
    "tokens": [
        {
            "token": "中",
            "start_offset": 0,
            "end_offset": 1,
            "type": "<IDEOGRAPHIC>",
            "position": 0
        },
        {
            "token": "华",
            "start_offset": 1,
            "end_offset": 2,
            "type": "<IDEOGRAPHIC>",
            "position": 1
        },
        {
            "token": "人",
            "start_offset": 2,
            "end_offset": 3,
            "type": "<IDEOGRAPHIC>",
            "position": 2
        },
        {
            "token": "民",
            "start_offset": 3,
            "end_offset": 4,
            "type": "<IDEOGRAPHIC>",
            "position": 3
        },
        {
            "token": "共",
            "start_offset": 4,
            "end_offset": 5,
            "type": "<IDEOGRAPHIC>",
            "position": 4
        },
        {
            "token": "和",
            "start_offset": 5,
            "end_offset": 6,
            "type": "<IDEOGRAPHIC>",
            "position": 5
        },
        {
            "token": "国",
            "start_offset": 6,
            "end_offset": 7,
            "type": "<IDEOGRAPHIC>",
            "position": 6
        }
    ]
}

скачатьik-6.4.0и распаковать в ES/plugins/каталог и переименуйте распакованный каталог в ==ik==, ==перезапустите ES==, и плагин будет загружен автоматически.

После перезапуска ES проверьте эффект сегментации слов:

POST http://localhost:9200/_analyze

{
	"text":"中华人民共和国",
	"analyzer":"ik_max_word"	//设置分词器为ik分词器,否则还是会采用默认分词器,可选ik_max_word和ik_smart
}

Стратегия сегментации слов ik_max_word заключается в разделении как можно большего количества терминов, то есть в мелкозернистой сегментации слов:

{
    "tokens": [
        {
            "token": "中华人民共和国",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "中华人民",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "中华",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "华人",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "人民共和国",
            "start_offset": 2,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "人民",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 5
        },
        {
            "token": "共和国",
            "start_offset": 4,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 6
        },
        {
            "token": "共和",
            "start_offset": 4,
            "end_offset": 6,
            "type": "CN_WORD",
            "position": 7
        },
        {
            "token": "国",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 8
        }
    ]
}

А ik_smart — это гранулированная сегментация слов (настройка"analyzer" : "ik_smart"):

{
    "tokens": [
        {
            "token": "中华人民共和国",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}

пользовательский тезаурус

Токенизатор ik предоставляет только лексикон распространенных китайских фраз, но не может идентифицировать популярные сетевые фразы в режиме реального времени, поэтому иногда, чтобы повысить точность сегментации слов, нам необходимо самостоятельно расширить лексикон.

Во-первых, мы проверяем эффект сегментации слов ik на онлайн-словарь «синий и тонкий гриб»:

PUT /_analyze

{
    "text":"蓝瘦香菇",
    "analyzer":"ik_smart"
}

Причастия следующие:

{
    "tokens": [
        {
            "token": "蓝",
            "start_offset": 0,
            "end_offset": 1,
            "type": "CN_CHAR",
            "position": 0
        },
        {
            "token": "瘦",
            "start_offset": 1,
            "end_offset": 2,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "香菇",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 2
        }
    ]
}

наши в ЕС/plugins/ik/configДобавьте пользовательский файл тезауруса в каталогmy.dicИ добавить строчку "синий тонкий гриб" (формат файла словаря - по одной строчке на каждый термин), а в файл конфигурации ik/plugins/ik/config/IKAnalyzer.cfg.xmlВведите этот пользовательский словарь в:

<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">my.dic</entry>

==Перезапустите ES==, токенизатор ik будет использовать наш новый добавленный термин в качестве критерия токенизации:

{
    "tokens": [
        {
            "token": "蓝瘦香菇",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}

карта

новое поле

PUT /xc_course/doc/_mapping

{
    "properties":{
        "create_time":{
            "type":"date"
        }
    }
}

GET /xc_course/doc/_mapping

{
    "xc_course": {
        "mappings": {
            "doc": {
                "properties": {
                    "create_time": {
                        "type": "date"
                    },
                    "description": {
                        "type": "text"
                    },
                    "name": {
                        "type": "text"
                    },
                    "price": {
                        "type": "double"
                    }
                }
            }
        }
    }
}

Существующие сопоставления могут добавлять новые поля, но не могут изменять определение существующих полей!

PUT /xc_course/doc/_mapping

{
    "properties":{
        "price":{
            "type":"integer"
        }
    }
}

Ошибка: Определенная цена не может быть изменена с двойного типа на целочисленный:

{
    "error": {
        "root_cause": [
            {
                "type": "illegal_argument_exception",
                "reason": "mapper [price] cannot be changed from type [double] to [integer]"
            }
        ],
        "type": "illegal_argument_exception",
        "reason": "mapper [price] cannot be changed from type [double] to [integer]"
    },
    "status": 400
}

Если вам необходимо изменить определение поля (включая тип, токенизатор, индексировать ли и т. д.), удалите только библиотеку индексов, чтобы перестроить индекс и определить каждое поле, а затем перенести данные. Таким образом, определение отображения следует учитывать при создании библиотеки индексов, поскольку только поля могут быть расширены, но не переопределены.

Часто используемые типы сопоставления — тип

Основные типы данных ES6.2 следующие:

image

keyword

Поля этого типа не токенизированы, а содержимое поля представлено в виде неделимой фразы. Например, основные товарные знаки и торговые марки могут использовать этот тип. И это точное совпадение, когда запрашивается содержимое этого поля, например, когда тип является ключевым словомbrandПоиск по полю «Huawei» не найдет документы со значением поля «Huawei Honor».

date

Поля, тип которых — дата, также могут указывать дополнительныеformat,как

{
    "properties":{
        "create_time":{	
            "type":"date",
            "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"	
        }
    }
}

добавленный документcreate_timeЗначением поля может быть дата+время или только дата

Числовой тип

image

1. Старайтесь выбирать тип с небольшой областью действия, чтобы повысить эффективность поиска. 2. Попробуйте использовать масштабный коэффициент для чисел с плавающей запятой, таких как поле цены, единица измерения - юани, мы устанавливаем масштабный коэффициент равным 100, который будет храниться в ES как == точки == хранилище, отображение выглядит следующим образом:

"price": {
    "type": "scaled_float",       
    "scaling_factor": 100
}

Поскольку коэффициент масштабирования равен 100, если мы введем цену 23,45, то ES будет хранить 23,45 умножить на 100 в ES. Если введенная цена 23,456, ES умножит 23,456 на 100 и возьмет число, близкое к исходному значению, что даст 2346.

Преимущество использования коэффициента масштабирования заключается в том, что целые числа легче сжимать, чем числа с плавающей запятой, что экономит место на диске.

Создавать ли индекс - index

Значение индекса по умолчанию равно true, то есть требуется сегментация слов, а инвертированный индекс (связь между терминами и документами) устанавливается в соответствии с терминами, полученными в результате сегментации. Данные некоторых полей не имеют смысла, например, url картинки курса используется только для отображения картинки, а для индексации не требуется сегментация слов, поэтому его можно установить в false:

PUT /xc_course/doc/_mapping

{
    "properties":{
        "pic":{
            "type":"text"
            "index":"false"
        }
    }
}

Токенизатор индекса и токенизатор поиска

Токенизатор индекса - анализатор

Токенизатор используемый при добавлении данных в индексную библиотеку, рекомендуется использовать ik_max_word, например "Китайская Народная Республика", если используется ik_smart, то вся "Китайская Народная Республика" будет храниться как терм (этот пункт ) в таблице инвертированного индекса, тогда поиск по «Республике» не найдет эти данные (полное совпадение).

Токенизатор поиска — search_analyzer

Токенизатор поиска — это токенизатор, используемый для ввода пользовательского поиска для токенизации.

Рекомендуется использовать ik_smart, например, при поиске «Китайская Народная Республика», содержание «Гималайская Республика» не должно отображаться.

Будь то дополнительное хранилище - магазин

Независимо от того, хранится ли он вне источника, после индексации каждого документа исходный документ будет сохранен в ES и сохранен в_sourceСредний, обычно не нужно устанавливать хранить верно, потому что в_sourceУже есть исходный документ в формате .

Комплексный бой

Создайте сопоставление коллекций курсов:

  1. Сначала удалите сопоставленный индекс

    DELET /xc_course

  2. Добавить индекс

    PUT /xc_course

  3. Создать карту

    PUT /xc_course/doc/_mapping

    {
        "properties": {
            "name": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            },
            "description": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            },
            "price": {
                "type": "scaled_float",
                "scaling_factor": 100
            },
            "studypattern": {
                "type": "keyword"
            },
            "pic": {
                "type": "text",
                "index": false
            },
            "timestamp": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
            }
        }
    }
    
  4. Добавить документы

    POST /xc_course/doc

    {
        "name": "Java核心技术",
        "description": "深入浅出讲解Java核心技术以及相关原理",
        "price": 99.9,
        "studypattern": "20101",
        "pic": "http://xxx.xxx.xxx/skllsdfsdflsdfk.img",
        "timestamp": "2019-4-1 13:16:00"
    }
    
  5. Получить Java

    GET http://localhost:9200/xc_course/doc/_search?q=name:java

  6. Режим обучения извлечению

    GET http://localhost:9200/xc_course/doc/_search?q=studypattern:20101

Управление индексами и Java-клиент

Начиная с этой главы, мы реализуем поддерживающий код Java для каждого RESTful API ES. В конце концов, хотя внешний интерфейс может получить доступ к ES через HTTP, для управления и настройки ES по-прежнему требуется серверная часть в качестве хаба.

Java-клиент, предоставляемый ES — RestClient

Официально рекомендуется RestClient, который включает в себя два типа: клиент REST низкого уровня Java и клиент REST высокого уровня Java. ES предоставляет Java High Level REST Client после 6.0.Официально два клиента рекомендуют использовать Java High Level REST Client, но он все еще находится в стадии совершенства, и некоторые функции еще недоступны (если он имеет неподдерживаемые функции, используйте Java Low Level REST Client). .).

Зависимости следующие:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.2.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.2.1</version>
</dependency>

Spring интегрирует ES

полагаться

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.2.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.2.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
</dependency>	

конфигурационный файл

application.yml:

server:
  port: ${port:40100}
spring:
  application:
    name: xc-service-search
xuecheng:	#自定义属性项
  elasticsearch:
    host-list: ${eshostlist:127.0.0.1:9200} #多个节点中间用逗号分隔

стартовый класс

@SpringBootApplication
public class SearchApplication {
    public static void main(String[] args){
        SpringApplication.run(SearchApplication.class, args);
    }
}

Класс конфигурации ES

package com.xuecheng.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ElasticsearchConfig class
 *
 * @author : zaw
 * @date : 2019/4/22
 */
@Configuration
public class ElasticsearchConfig {

    @Value("${xuecheng.elasticsearch.host-list}")
    private String hostList;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(RestClient.builder(getHttpHostList(hostList)));
    }

    private HttpHost[] getHttpHostList(String hostList) {
        String[] hosts = hostList.split(",");
        HttpHost[] httpHostArr = new HttpHost[hosts.length];
        for (int i = 0; i < hosts.length; i++) {
            String[] items = hosts[i].split(":");
            httpHostArr[i] = new HttpHost(items[0], Integer.parseInt(items[1]), "http");
        }
        return httpHostArr;
    }

    // rest low level client
    @Bean
    public RestClient restClient() {
        return RestClient.builder(getHttpHostList(hostList)).build();
    }
}

тестовый класс

package com.xuecheng.search;

import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

/**
 * TestES class
 *
 * @author : zaw
 * @date : 2019/4/22
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestESRestClient {

    @Autowired
    RestHighLevelClient restHighLevelClient;    //ES连接对象

    @Autowired
    RestClient restClient;
}

ES клиентский API

Сначала мы удаляем созданную ранее библиотеку индексов:

DELETE /xc_course

Затем просмотрите форму RESTful для создания библиотеки индексов:

PUT /xc_course

{
	"settings":{
		"index":{
			"number_of_shards":1,
			"number_of_replicas":0
		}
	}
}

Создайте библиотеку индексов

@Test
public void testCreateIndex() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("xc_course");
    /**
         * {
         * 	"settings":{
         * 		"index":{
         * 			"number_of_shards":1,
         * 			"number_of_replicas":0
         *       }
         *    }
         * }
         */
    request.settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0));
    IndicesClient indicesClient = restHighLevelClient.indices();    //通过ES连接对象获取索引库管理对象
    CreateIndexResponse response = indicesClient.create(request);
    System.out.println(response.isAcknowledged());  //操作是否成功
}

По сравнению с формой RESTful запрос инициируется через метод CreateIndexRequest, а в третьей строке указывается имя создаваемой индексной библиотеки через конструктор (соответствует URI/xc_course), строка 14 создает тело запроса (вы найдетеsettingsметод очень похож на формат запроса JSON).

Для работы с библиотекой индексов вам необходимо использоватьIndicesClientобъект.

удалить библиотеку индексов

@Test
public void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("xc_course");
    IndicesClient indicesClient = restHighLevelClient.indices();
    DeleteIndexResponse response = indicesClient.delete(request);
    System.out.println(response.isAcknowledged());
}

Укажите сопоставление при создании библиотеки индексов.

@Test
public void testCreateIndexWithMapping() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("xc_course");
    request.settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0));
    request.mapping("doc", "{\n" +
                    "    \"properties\": {\n" +
                    "        \"name\": {\n" +
                    "            \"type\": \"text\",\n" +
                    "            \"analyzer\": \"ik_max_word\",\n" +
                    "            \"search_analyzer\": \"ik_smart\"\n" +
                    "        },\n" +
                    "        \"price\": {\n" +
                    "            \"type\": \"scaled_float\",\n" +
                    "            \"scaling_factor\": 100\n" +
                    "        },\n" +
                    "        \"timestamp\": {\n" +
                    "            \"type\": \"date\",\n" +
                    "            \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd\"\n" +
                    "        }\n" +
                    "    }\n" +
                    "}", XContentType.JSON);
    IndicesClient indicesClient = restHighLevelClient.indices();
    CreateIndexResponse response = indicesClient.create(request);
    System.out.println(response.isAcknowledged());
}

Добавить документы

Процесс добавления документов называется «индексированием» (глагол). Нужно использоватьIndexRequestОбъект индексируется.

public static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Test
public void testAddDocument() throws IOException {
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("name", "Java核心技术");
    jsonMap.put("price", 66.6);
    jsonMap.put("timestamp", FORMAT.format(new Date(System.currentTimeMillis())));
    IndexRequest request = new IndexRequest("xc_course", "doc");
    request.source(jsonMap);
    IndexResponse response = restHighLevelClient.index(request);
    System.out.println(response);
}

Результат ответа содержит идентификатор документа, сгенерированный ES для нас, Здесь идентификатор, который я получил от теста,fHh6RWoBduPBueXKl_tz

Запросить документы по id

@Test
public void testFindById() throws IOException {
    GetRequest request = new GetRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    GetResponse response = restHighLevelClient.get(request);
    System.out.println(response);
}

Обновить документ на основе идентификатора

Обновление документов ЭП двумя способами: полная замена и частичное обновление

Полная замена: ES сначала запросит документ на основе идентификатора и удалит его, а затем вставит идентификатор в качестве идентификатора нового документа.

Частичное обновление: будет обновлено только соответствующее поле.

Полная замена:

POST /index/type/id

Частичное обновление:

POST /index/type/_update

Java-клиент обеспечивает частичные обновления, то есть обновляются только отправленные поля, а значения остальных полей остаются неизменными.

@Test
public void testUpdateDoc() throws IOException {
    UpdateRequest request = new UpdateRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    Map<String, Object> docMap = new HashMap<>();
    docMap.put("name", "Spring核心技术");
    docMap.put("price", 99.8);
    docMap.put("timestamp", FORMAT.format(new Date(System.currentTimeMillis())));
    request.doc(docMap);
    UpdateResponse response = restHighLevelClient.update(request);
    System.out.println(response);
    testFindById();
}

удалить документ по id

@Test
public void testDeleteDoc() throws IOException {
    DeleteRequest request = new DeleteRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    DeleteResponse response = restHighLevelClient.delete(request);
    System.out.println(response);
}

Управление поиском

Подготовьте среду

Чтобы данные были доступны для поиска, мы воссоздаем сопоставление и добавим некоторые тестовые данные.

Создать карту

DELETE /xc_course

PUT /xc_course

{
    "settings":{
        "number_of_shards":1,
        "number_of_replicas":0
    }
}

PUT /xc_course/doc/_mapping

{
    "properties": {
        "name": {
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_smart"
        },
        "description": {
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_smart"
        },
        "studymodel":{
			"type":"keyword"	//授课模式,值为数据字典代号
		},
        "pic": {
            "type": "text",
            "index": false
        },
        "price": {
            "type": "float"
        },
        "timestamp": {
            "type": "date",
            "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
        }
    }
}

Добавить тестовые данные

PUT /xc_course/doc/1

{
    "name": "Bootstrap开发",
    "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
    "studymodel": "201002",
    "price": 38.6,
    "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "timestamp": "2018-04-25 19:11:35"
}

PUT /xc_course/doc/2

{
    "name": "java编程基础",
    "description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",
    "studymodel": "201001",
    "price": 68.6,
    "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "timestamp": "2018-03-25 19:11:35"
}

PUT /xc_course/doc/3

{
    "name": "spring开发基础",
    "description": "spring 在java领域非常流行,java程序员都在用。",
    "studymodel": "201001",
    "price": 88.6,
    "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "timestamp": "2018-02-24 19:11:35"
}

простой поиск

  • Искать все документы в указанном индексном репозитории

    GET /xc_course/_search

  • Искать все документы указанного типа

    GET /xc_course/doc/_search

DSL-поиск

DSL (Domain Specific Language) — это метод поиска на основе json, предложенный ES.При поиске передаются определенные данные в формате json для выполнения различных требований поиска.

DSL является более мощным, чем метод поиска URI, рекомендуется использовать метод DSL для завершения поиска в проекте.

Метод поиска DSL отправляется с использованием POST, а URI начинается с_searchend (поиск в диапазоне индекса или типа) и определите условия поиска в теле запроса JSON.

Запросить все документы — matchAllQuery

POST /xc_course/doc/_search

{
	"query":{
		"match_all":{}
	},
	"_source":["name","studymodel"]
}

queryиспользуется для определения критериев поиска,_sourceИспользуется для указания того, какие поля должны быть включены в возвращаемый набор результатов. Это полезно, когда сам документ содержит большой объем данных, но мы хотим получить данные только нескольких определенных полей (что может отфильтровать ненужные поля и повысить эффективность передачи).

Описание результата:

  • take, время, затраченное на эту операцию, в миллисекундах
  • time_out, время ожидания запроса истекло (время ожидания истекает, когда ES недоступен или произошел сбой сети)
  • _shard, какие осколки искались в этой операции
  • хиты, результат попадания
  • hits.total, количество подходящих документов
  • hits.hits, набор хитов
  • hits.max_score, наивысшая оценка каждого документа в hits.hits, оценка документа — это релевантность запроса.
  • _source, исходные данные документа
{
    "took": 57,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "studymodel": "201002",
                    "name": "Bootstrap开发"
                }
            },
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "2",
                "_score": 1,
                "_source": {
                    "studymodel": "201001",
                    "name": "java编程基础"
                }
            },
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "3",
                "_score": 1,
                "_source": {
                    "studymodel": "201001",
                    "name": "spring开发基础"
                }
            }
        ]
    }
}

Реализация кода Java:

@Test
public void testMatchAll() throws IOException {
    // POST     /xc_course/doc
    SearchRequest request = new SearchRequest("xc_course");    //DSL搜索请求对象
    request.types("doc");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();    //DSL请求体构造对象
    /**
         * {
         * 	"from":2,"size":1,
         * 	"query":{
         * 		"match_all":{
         *
         *                }* 	},
         * 	"_source":["name","studymodel"]
         * }
         */
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    //参数1:要返回哪些字段   参数2:不要返回哪些字段    两者通常指定其一
    searchSourceBuilder.fetchSource(new String[]{"name", "studymodel"}, null);
    //将请求体设置到请求对象中
    request.source(searchSourceBuilder);
    //发起DSL请求
    SearchResponse response = restHighLevelClient.search(request);
    System.out.println(response);
}

Основной API DSL

  • новый SearchRequest(index), указывает индексную библиотеку для поиска
  • searchRequest.type(type), указывает тип для поиска
  • SearchSourceBuilder, создает тело запроса DSL.
  • searchSourceBuilder.query(queryBuilder), создайте тело запроса“query”:{}часть содержания
  • QueryBuilders, статический фабричный класс, простой в создании queryBuilder, напримерsearchSourceBuilder.query(QueryBuilders.matchAllQuery())эквивалентно построению“query”:{ "match_all":{} }
  • searchRequest.source(), установите построенное тело запроса в объект запроса

Пейджинговый запрос

PUT http://localhost:9200/xc_course/doc/_search

{
	"from":0,"size":1,
	"query":{
		"match_all":{
			
		}
	},
	"_source":["name","studymodel"]
}

вfromЗначение - это смещение набора результатов, в то время какsizeпосле старта из позиции смещенияsizeРезультаты.

{
    "took": 80,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "studymodel": "201002",
                    "name": "Bootstrap开发"
                }
            }
        ]
    }
}

Здесь, хотяhits.totalравен 3, но возвращается только первая запись. Поэтому нам нужно использовать формулу при подкачке:from = (page-1)*size

Реализация кода Java

@Test
public void testPaginating() throws IOException {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    int page = 1, size = 1;
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.from((page - 1) * size);
    searchSourceBuilder.size(size);
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchSourceBuilder.fetchSource(new String[]{"name", "studymodel"}, null);

    request.source(searchSourceBuilder);
    SearchResponse response = restHighLevelClient.search(request);
    System.out.println(response);
}

Извлечь документы в результирующем наборе

SearchResponse response = restHighLevelClient.search(request);
SearchHits hits = response.getHits();				//hits
if (hits != null) {
    SearchHit[] results = hits.getHits();			//hits.hits
    for (SearchHit result : results) {
        System.out.println(result.getSourceAsMap()); //hits.hits._source
    }
}

Сопоставление терминов — termQuery

Сопоставление терминов является ==точным соответствием==, и только когда указанный нами термин существует в таблице инвертированного индекса, будет возвращен набор документов, связанный с термином.

Если название курса поиска содержитjavaвременная документация

{
	"from":0,"size":1,
	"query":{
		"term":{ "name":"java" }
	},
	"_source":["name","studymodel"]
}

Результат выглядит следующим образом:

"hits": {
    "total": 1,
    "max_score": 0.9331132,
    "hits": [
        {
            "_index": "xc_course",
            "_type": "doc",
            "_id": "2",
            "_score": 0.9331132,
            "_source": {
                "studymodel": "201001",
                "name": "java编程基础"
            }
        }
    ]
}

но если указать"term"за{ "name":"java编程" }не могу найти:

"hits": {
    "total": 0,
    "max_score": null,
    "hits": []
}

Поскольку «основа программирования на Java» будет разделена на три термина «java», «программирование» и «базовый» во время индексации и добавлена ​​в таблицу инвертированного индекса, поэтому на этот раз нет термина «программирование на Java». .

termЗапрос является точным совпадением,term.nameне будетsearch_analyzerПричастие, а в целом и термины в таблице инвертированного индекса совпадают.

Точное совпадение по идентификатору — termsQuery

Запрос документов с идентификатором 1 и 3

POST http://localhost:9200/xc_course/doc/_search

{
	"query":{
		"ids":{
			"values":["1","3"]
		}
	}
}

Java-реализация

@Test
public void testQueryByIds(){
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    List<String> ids = Arrays.asList(new String[]{"1", "3"});
    sourceBuilder.query(QueryBuilders.termsQuery("_id", ids));

    printResult(request, sourceBuilder);
}

private void printResult(SearchRequest request,SearchSourceBuilder sourceBuilder) {
    request.source(sourceBuilder);
    SearchResponse response = null;
    try {
        response = restHighLevelClient.search(request);
    } catch (IOException e) {
        e.printStackTrace();
    }
    SearchHits hits = response.getHits();
    if (hits != null) {
        SearchHit[] results = hits.getHits();
        for (SearchHit result : results) {
            System.out.println(result.getSourceAsMap());
        }
    }
}

==Большая яма==

Точное сопоставление на основе идентификатора также является типом запроса термина, но вызываемый APItermsQuery("_id", ids), Обратите внимание, чтоtermsQueryвместоtermQuery.

Полнотекстовый поиск — matchQuery

Введенные ключевые слова будутsearch_analyzerУказанный токенизатор разделяет слова, а затем ищет набор документов в таблице инвертированного индекса в соответствии с полученным термином. Будет найден набор документов, связанный с каждым термином. Например, если вы отметите «основы начальной загрузки», вы найдете « основы программирования на Java":

POST

{
	"query":{
		"match":{
			"name":"bootstrap基础"
		}
	}
}

Потому что "основы бутстрапа" будут разделены на два термина "бутстрап" и "базовый", а термин "базовый" связан с документом "основы программирования на Java".

@Test
public void testMatchQuery() {

    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("name", "bootstrap基础"));

    printResult(request, sourceBuilder);
}

operator

Приведенный выше запрос эквивалентен:

{
    "query": {
        "match": {
            "name": {
                "query": "bootstrap基础",
                "operator": "or"
            }
        }
    }
}

То есть объединение результатов запроса каждого термина после сегментации ключевого слова поиска.

operatorВозможное значениеor,and, соответствующие объединению и взятию пересечения соответственно.

Следующий запрос будет иметь только один результат (название курса содержит как «java», так и «basic», только «основы программирования на Java»):

{
    "query": {
        "match": {
            "name": {
                "query": "java基础",
                "operator": "and"
            }
        }
    }
}

Java-код

@Test
public void testMatchQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("name", "java基础").operator(Operator.AND));

    printResult(request, sourceBuilder);
}

minimum_should_match

использовано вышеoperator = orУказывает, что при совпадении одного слова будет начисляться оценка Как добиться, если три слова совпадают как минимум с двумя словами?

использоватьminimum_should_matchВы можете указать долю совпадающих слов в документе, например, искомое предложение выглядит следующим образом:

{
    "query": {
        "match": {
            "name": {
                "query": "spring开发框架",
                "minimum_should_match":"80%"
            }
        }
    }
}

«весенняя структура разработки» будет разделена на три слова: весна, разработка, структура.

настраивать"minimum_should_match":"80%"Указывает, что доля соответствия трех слов в документе составляет 80%, то есть 3*0,8=2,4, округленное до 2, таблица Совпадение считается успешным, если в документе присутствует не менее двух слов.

@Test
public void testMatchQuery3() {
    SearchRequest request = new SearchRequest("xc_course");
request.types("doc");

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("name", "spring开发指南").minimumShouldMatch("70%")); //3*0.7 -> 2

printResult(request, sourceBuilder);
}

Многодоменный поиск — multiMatchQuery

узнал свышеtermQueryиmatchQueryОдновременно может быть сопоставлено только одно Поле, узнайте в этом разделеmultiQuery, вы можете сопоставить сразу несколько полей (то есть расширить область поиска, которая всегда была вnameполе).

Например, чтобы получить документы, содержащие «spring» или «css» в названии или описании курса:

{
    "query": {
        "multi_match": {
            "query": "spring css",
            "minimum_should_match": "50%",
            "fields": [
                "name",
                "description"
            ]
        }
    },
    "_source":["name","description"]
}

Ява:

@Test
public void testMultiMatchQuery() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.
                        multiMatchQuery("spring css","name","description").
                        minimumShouldMatch("50%")); 

    printResult(request, sourceBuilder);
}

увеличить веса

Обратите внимание на указанные выше оценки документов:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "3",
        "_score": 1.3339276,
        "_source": {
            "name": "spring开发基础",
            "description": "spring 在java领域非常流行,java程序员都在用。"
        }
    },
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 0.69607234,
        "_source": {
            "name": "Bootstrap开发",
            "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。"
        }
    }
]

вы найдете документацию3серединаspringКоличество появлений термина в документе составляет более высокую долю от общего количества терминов в документе, поэтому оценка (_score) выше. Тогда мы догадываемся, мы в документе?1добавить еще несколько к описанию курсаcssможет улучшить его_scoreШерстяная ткань?

Итак, давайте обновим документ 1:

@Test
public void testUpdateDoc() throws IOException {
    UpdateRequest request = new UpdateRequest("xc_course", "doc", "1");
    Map<String, Object> docMap = new HashMap<>();
    docMap.put("description", "Bootstrap是由Twitter推出的一个css前台页面开发框架,是一个非常流行的css开发框架,此框架集成了多种css页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助css开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。");
    request.doc(docMap);
    UpdateResponse response = restHighLevelClient.update(request);
    System.out.println(response);
    testFindById();
}

Запросите еще раз и обнаружите, что оценка документа 1 действительно стала выше:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 1.575484,
        "_source": {
            "name": "Bootstrap开发",
            "description": "Bootstrap是由Twitter推出的一个css前台页面开发框架,是一个非常流行的css开发框架,此框架集成了多种css页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助css开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。"
        }
    },
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "3",
        "_score": 1.346281,
        "_source": {
            "name": "spring开发基础",
            "description": "spring 在java领域非常流行,java程序员都在用。"
        }
    }
]

Тогда у нас есть такое бизнес-требование: появление spring или css в курсе — это однозначно курс, более связанный с spring или css, но появление описания курса — не обязательно. Таким образом, мы хотим увеличить вес элементов ключевых слов, которые появляются в курсе, мы можем сделать это (вnameдобавить поле^символ и укажите вес, по умолчанию 1):

{
    "query": {
        "multi_match": {
            "query": "spring css",
            "minimum_should_match": "50%",
            "fields": [
                "name^10",
                "description"
            ]
        }
    },
    "_source":["name","description"]
}

Ява:

@Test
public void testMultiMatchQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("spring css", "name", "description").minimumShouldMatch("50%");
    multiMatchQueryBuilder.field("name", 10);
    sourceBuilder.query(multiMatchQueryBuilder);

    printResult(request, sourceBuilder);
}

Логический запрос - boolQuery

Логический запрос соответствует запросу Lucene BooleanQuery, который реализует == Объединяет несколько запросов == .

три параметра

  • must: документ должен соответствовать условиям запроса, включенным в must, что эквивалентно «И».
  • should: документ должен соответствовать одному или нескольким условиям запроса, включенным в должен, что эквивалентно «ИЛИ».
  • must_not: документ не может соответствовать условию запроса, включенному в must_not, что эквивалентно «НЕ».

Например, если название курса запроса содержит «spring» == и ==, название курса или описание курса связано с «средой разработки»:

{
    "query": {
        "bool":{
            "must":[
                {
                    "term":{
                        "name":"spring"
                    }
                },
                {
                    "multi_match":{
                        "query":"开发框架",
                        "fields":["name","description"]
                    }
                }
            ]
        }
    },
    "_source":["name"]
}

Java

@Test
public void testBoolQuery() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();	//query
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();		//query.bool

    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "spring");
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("开发框架", "name", "description");

    boolQueryBuilder.must(termQueryBuilder);		//query.bool.must
    boolQueryBuilder.must(multiMatchQueryBuilder);	
    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}

условия, которые должны быть соблюденыmustсередина(boolQueryBuilder.must(条件)), условия, которые необходимо исключить, помещаются вmust_not, нужно только выполнить одно из условий, чтобы поставитьshouldсередина.

Запросите курсы, название которых должно содержать слово «разработка», но не слово «java», а также слово «spring» или «boostrap»:

{
    "query": {
       "bool":{
       		"must":[
				{
					"term":{
						"name":"开发"
					}
				}
       		],
       		"must_not":[
				{
					"term":{
						"name":"java"
					}
				}
       		],
       		"should":[
				{
					"term":{
						"name":"bootstrap"
					}
				},
				{
					"term":{
						"name":"spring"
					}
				}
       		]
       }
    },
    "_source":["name"]
}

Конечно, реальный проект не будет задавать такие условия.termQuery, факт может быть любым из предыдущихQuery.

Java

@Test
public void testBoolQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    boolQueryBuilder.must(QueryBuilders.termQuery("name","开发"));
    boolQueryBuilder.mustNot(QueryBuilders.termQuery("name", "java"));
    boolQueryBuilder.should(QueryBuilders.termQuery("name","spring"));
    boolQueryBuilder.should(QueryBuilders.termQuery("name","bootstrap"));

    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}

фильтр - фильтр

Фильтрация предназначена для фильтрации результатов поиска. Фильтр == в основном оценивает, соответствует ли документ, а не вычисляет и оценивает оценку соответствия документа ==, поэтому эффективность фильтра == выше, чем у запроса, и это удобен для кеширования == , рекомендуется максимально использовать фильтры для реализации запросов или использовать фильтры и запросы вместе.

Фильтры можно использовать только в логических запросах.

Найдите полный текст «spring framework» и отфильтруйте кодовое название режима обучения, отличное от «201001», и стоимость курса не находится в диапазоне от 10 до 100.

{
    "query": {
        "bool": {
            "must": [
                {
                    "multi_match": {
                        "query": "spring框架",
                        "fields": [
                            "name",
                            "description"
                        ]
                    }
                }
            ],
            "filter": [
                {
                    "term": {
                        "studymodel": "201001"
                    }
                },
                {
                    "range": {
                        "price": {
                            "gte": "10",
                            "lte": "100"
                        }
                    }
                }
            ]
        }
    }
}

Java

@Test
public void testBoolQuery3() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    boolQueryBuilder.must(QueryBuilders.multiMatchQuery("spring框架", "name", "description"));
    boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(10).lte(100));

    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}

Сортировать

Запросите курсы, цена которых находится в диапазоне от 10 до 100, и отсортируйте их по возрастанию цены. Если цена одинаковая, отсортируйте их по убыванию по отметке времени.

{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "price": {
                            "gte": "10",
                            "lte": "100"
                        }
                    }
                }
            ]
        }
    },
    "sort": [
        {
            "price": "asc"
        },
        {
            "timestamp": "desc"
        }
    ],
    "_source": [
        "name",
        "price",
        "timestamp"
    ]
}

Java

@Test
public void testSort() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(10).lte(100));

    sourceBuilder.sort("price", SortOrder.ASC);
    sourceBuilder.sort("timestamp", SortOrder.DESC);

    sourceBuilder.query(boolQueryBuilder);
    printResult(request, sourceBuilder);
}

выделять

{
    "query": {
        "bool": {
            "filter": [
                {
                    "multi_match": {
                        "query": "bootstrap",
                        "fields": [
                            "name",
                            "description"
                        ]
                    }
                }
            ]
        }
    },
    "highlight":{
        "pre_tags":["<tag>"],
        "post_tags":["</tag>"],
        "fields":{
            "name":{},
            "description":{}
        }
    }
}

результат:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 0,
        "_source": {
            "name": "Bootstrap开发",
            "description": "Bootstrap是由Twitter推出的一个css前台页面开发框架,是一个非常流行的css开发框架,此框架集成了多种css页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助css开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
            "studymodel": "201002",
            "price": 38.6,
            "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
            "timestamp": "2018-04-25 19:11:35"
        },
        "highlight": {
            "name": [
                "<tag>Bootstrap</tag>开发"
            ],
            "description": [
                "<tag>Bootstrap</tag>是由Twitter推出的一个css前台页面开发框架,是一个非常流行的css开发框架,此框架集成了多种css页面效果。"
            ]
        }
    }
]

hitsКаждый результат в наборе результатов дает исходный документ_sourceКроме того, соответствующие выделенные результаты также приведеныhighlight

Java

@Test
public void testHighlight() throws IOException {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    boolQueryBuilder.filter(QueryBuilders.multiMatchQuery("bootstrap","name","description"));

    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<tag>");
    highlightBuilder.postTags("</tag>");
    highlightBuilder.field("name").field("description");

    sourceBuilder.query(boolQueryBuilder);
    sourceBuilder.highlighter(highlightBuilder);
    request.source(sourceBuilder);

    SearchResponse response = restHighLevelClient.search(request);
    SearchHits hits = response.getHits();       //hits
    if (hits != null) {
        SearchHit[] results = hits.getHits();   //hits.hits
        if (results != null) {
            for (SearchHit result : results) {
                Map<String, Object> source = result.getSourceAsMap();   //_source
                String name = (String) source.get("name");
                Map<String, HighlightField> highlightFields = result.getHighlightFields();  //highlight
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    Text[] fragments = highlightField.getFragments();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Text text : fragments) {
                        stringBuilder.append(text.string());
                    }
                    name = stringBuilder.toString();
                }
                System.out.println(name);

                String description = (String) source.get("description");
                HighlightField highlightField2 = highlightFields.get("description");
                if (highlightField2 != null) {
                    Text[] fragments = highlightField2.getFragments();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Text text : fragments) {
                        stringBuilder.append(text.string());
                    }
                    description = stringBuilder.toString();
                }
                System.out.println(description);
            }
        }
    }
}

Более сложный для понимания APIHighlightFieldsиhighlightField.getFragments(), нам нужно сравнить структуру ответа JSO, чтобы понять по аналогии.

image

мы можем пройтиhighlightFields.get()получитьhighlight.nameиhighlight.descriptionсоответствующийhighlightField,но почемуhightField.getFragmentвозвращаетText[]вместоTextШерстяная ткань. Мы предполагаем, что ES делит документ на несколько сегментов в соответствии с предложением и выделяет и возвращает только сегмент, в котором появляется элемент ключевого слова, поэтому мы извлекаем css и проверяем его, и это правда:

image

Поэтому вам нужно обратить внимание на возвращенныйhighlightМожет не содержать все исходное содержимое поля

image

Управление кластером

ЭС обычно работает в кластерном режиме, что позволяет не только улучшить поисковые возможности ЭС, но и возможность обработки поиска больших данных, а также повысить отказоустойчивость и высокую доступность системы.ЭС может реализовать поиск ПБ-уровня. данные.

На следующем рисунке схематично представлена ​​структура кластера ES:

image

Понятия, связанные с кластером

узел

Кластер ES состоит из нескольких серверов, каждый из которых является узлом Node (для этого сервиса развернут только один процесс ES).

Фрагментация

При наличии большого количества документов, в связи с ограничениями памяти и жесткого диска, а также для повышения производительности обработки, отказоустойчивости и высокой доступности ЭП, мы делим индекс на несколько шардов (что можно сравнить с разделами в MySQL, таблица разделена на несколько файлов), и каждый сегмент может быть размещен на отдельном сервере, чтобы несколько серверов могли совместно предоставлять услуги индексирования и поиска для внешнего мира.

Когда приходит поисковый запрос, он будет запрошен из каждого сегмента, и, наконец, запрошенные данные будут объединены и возвращены пользователю.

копировать

Чтобы улучшить высокую доступность ES и повысить пропускную способность поиска, мы скопируем один или несколько шардов и будем хранить их на других серверах, чтобы даже в случае отказа текущего сервера сервер с репликой мог по-прежнему предоставлять услуги в обычном режиме. .

главный узел

В кластере будет один или несколько главных узлов.Роль главного узла заключается в управлении кластером, например добавлении узлов, удалении узлов и т. д. После того, как главный узел умрет, ES повторно выберет главный узел.

Переадресация узла

Каждый узел знает информацию о других узлах, мы можем инициировать запрос к любому v, и узел, получивший запрос, перешлет его другим узлам для запроса данных.

Три роли узлов

главный узел

Главный узел в основном используется для управления кластером и его индексации, например для добавления узлов, выделения сегментов, добавления и удаления индексов и т. д.

узел данных

Осколки данных хранятся на узле данных, который отвечает за операции индексирования и поиска.

клиентский узел

Клиентский узел существует только как клиент запроса, а клиент также действует как балансировщик нагрузки.Клиентский узел не хранит данные, а просто сбалансированно пересылает запрос другим узлам.

настроить

Доступны на/config/elasticsearch.ymlНастройте функцию узла в:

  • node.master: # Разрешен ли главный узел
  • node.data: # Позволяет хранить данные в виде узлов данных
  • node.ingest: # Разрешено ли стать узлом-координатором (пересылка запросов, когда данные не находятся на текущем экземпляре ES)

Четыре комбинации:

  • master=true, data=true: как главный узел, так и узел данных
  • master=false, data=true: только узлы данных
  • master=true, data=false: только главный узел, данные не сохраняются
  • master=false, data=false: это не главный узел и не узел данных.В этом случае для приема может быть установлено значение true, чтобы указать, что это клиент.

Создайте кластер

Далее давайте создадим кластер из 2 узлов и настроим 2 сегмента для индекса, по одной копии для каждого сегмента.

распаковатьelasticsearch-6.2.1.zipдва дляes-1,es-2

Файл конфигурации elasticsearch.yml

Узел 1:

cluster.name: xuecheng
node.name: xc_node_1
network.host: 0.0.0.0
http.port: 9200
transport.tcp.port: 9300
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]
discovery.zen.minimum_master_nodes: 1
node.ingest: true
bootstrap.memory_lock: false
node.max_local_storage_nodes: 2

path.data: D:\software\es\cluster\es-1\data
path.logs: D:\software\es\cluster\es-1\logs

http.cors.enabled: true
http.cors.allow-origin: /.*/

Узел 2:

cluster.name: xuecheng
node.name: xc_node_2
network.host: 0.0.0.0
http.port: 9201
transport.tcp.port: 9301
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]
discovery.zen.minimum_master_nodes: 1
node.ingest: true
bootstrap.memory_lock: false
node.max_local_storage_nodes: 2

path.data: D:\software\es\cluster\es-2\data
path.logs: D:\software\es\cluster\es-2\logs

http.cors.enabled: true
http.cors.allow-origin: /.*/

тестовый осколок

Чтобы создать индекс, индекс делится на два сегмента, каждый из которых имеет копию:

PUT http://localhost:9200/xc_course

{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 1
    }
}

пройти черезheadПлагин проверяет статус индекса:

image

Тестовая репликация master-slave

ввод данных

POST http://localhost:9200/xc_course/doc

{
	"name":"java编程基础"
}

Оба узла имеют данные:

image

image

работоспособность кластера

Просмотрите состояние кластера Elasticsearch, посетив GET /_cluster/health.

Используйте три цвета, чтобы показать состояние здоровья: зеленый, желтый или красный.

  • зеленый: все основные сегменты и реплики запущены и работают.
  • желтый: все основные осколки работают правильно, но некоторые осколки реплик не работают должным образом.
  • красный: основной осколок не работает должным образом.