Khái niệm cơ bản về Elaticsearch

Elaticsearch là một công cụ tìm kiếm có api json Rest, sử dụng Lucene và được viết bằng Java. Một mô tả về tất cả các ưu điểm của động cơ này có sẵn tại trang web chính thức. Trong phần tiếp theo, chúng tôi sẽ gọi Elaticsearch là ES.

Các công cụ tương tự được sử dụng cho các tìm kiếm phức tạp trong cơ sở dữ liệu tài liệu. Ví dụ: tìm kiếm có tính đến hình thái của ngôn ngữ hoặc tìm kiếm theo tọa độ địa lý.

Trong bài viết này, tôi sẽ nói về những điều cơ bản của ES bằng cách sử dụng ví dụ về lập chỉ mục các bài đăng trên blog. Tôi sẽ chỉ cho bạn cách lọc, sắp xếp và tìm kiếm tài liệu.

Để không phụ thuộc vào hệ điều hành, mình sẽ thực hiện mọi yêu cầu tới ES bằng CURL. Ngoài ra còn có một plugin cho google chrome có tên là ý nghĩa.

Văn bản chứa các liên kết đến tài liệu và các nguồn khác. Cuối cùng có các liên kết để truy cập nhanh vào tài liệu. Định nghĩa của các thuật ngữ không quen thuộc có thể được tìm thấy trong bảng chú giải thuật ngữ.

Cài đặt ES

Để làm được điều này, trước tiên chúng ta cần có Java. Nhà phát triển Đề nghị cài đặt các phiên bản Java mới hơn bản cập nhật Java 8 20 hoặc bản cập nhật Java 7 55.

Bản phân phối ES có sẵn tại trang web dành cho nhà phát triển. Sau khi giải nén kho lưu trữ, bạn cần chạy bin/elasticsearch. Cũng có sẵn gói cho apt và yum. có hình ảnh chính thức cho docker. Thông tin thêm về cài đặt.

Sau khi cài đặt và khởi chạy, hãy kiểm tra chức năng:

# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200

curl -X GET $ES_URL

Chúng tôi sẽ nhận được một cái gì đó như thế này:

{
  "name" : "Heimdall",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.2.1",
    "build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
    "build_timestamp" : "2016-03-09T09:38:54Z",
    "build_snapshot" : false,
    "lucene_version" : "5.4.1"
  },
  "tagline" : "You Know, for Search"
}

Lập chỉ mục

Hãy thêm một bài viết vào ES:

# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.

curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
  "title": "Веселые котята",
  "content": "<p>Смешная история про котят<p>",
  "tags": [
    "котята",
    "смешная история"
  ],
  "published_at": "2014-09-12T20:44:42+00:00"
}'

phản hồi của máy chủ:

{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}

ES được tạo tự động chỉ số blog và Loại bưu kiện. Chúng ta có thể rút ra một sự tương tự có điều kiện: chỉ mục là cơ sở dữ liệu và loại là bảng trong cơ sở dữ liệu này. Mỗi loại có sơ đồ riêng - lập bản đồ, giống như một bảng quan hệ. Ánh xạ được tạo tự động khi tài liệu được lập chỉ mục:

# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"

Trong phản hồi của máy chủ, tôi đã thêm giá trị của các trường của tài liệu được lập chỉ mục vào phần nhận xét:

{
  "blog" : {
    "mappings" : {
      "post" : {
        "properties" : {
          /* "content": "<p>Смешная история про котят<p>", */ 
          "content" : {
            "type" : "string"
          },
          /* "published_at": "2014-09-12T20:44:42+00:00" */
          "published_at" : {
            "type" : "date",
            "format" : "strict_date_optional_time||epoch_millis"
          },
          /* "tags": ["котята", "смешная история"] */
          "tags" : {
            "type" : "string"
          },
          /*  "title": "Веселые котята" */
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

Điều đáng chú ý là ES không phân biệt giữa một giá trị đơn lẻ và một mảng giá trị. Ví dụ: trường tiêu đề chỉ chứa tiêu đề và trường thẻ chứa một mảng các chuỗi, mặc dù chúng được biểu diễn theo cùng một cách trong ánh xạ.
Chúng ta sẽ nói nhiều hơn về việc lập bản đồ sau.

yêu cầu

Truy xuất tài liệu theo id của nó:

# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята",
    "content" : "<p>Смешная история про котят<p>",
    "tags" : [ "котята", "смешная история" ],
    "published_at" : "2014-09-12T20:44:42+00:00"
  }
}

Các khóa mới xuất hiện trong phản hồi: _version и _source. Nói chung, tất cả các phím bắt đầu bằng _ được xếp vào loại chính thức.

Ключ _version hiển thị phiên bản tài liệu. Nó là cần thiết để cơ chế khóa lạc quan hoạt động. Ví dụ: chúng tôi muốn thay đổi tài liệu có phiên bản 1. Chúng tôi gửi tài liệu đã thay đổi và cho biết rằng đây là bản chỉnh sửa của tài liệu có phiên bản 1. Nếu ai đó khác cũng đã chỉnh sửa tài liệu có phiên bản 1 và gửi các thay đổi trước chúng tôi, thì ES sẽ không chấp nhận những thay đổi của chúng tôi, bởi vì nó lưu trữ tài liệu với phiên bản 2.

Ключ _source chứa tài liệu mà chúng tôi đã lập chỉ mục. ES không sử dụng giá trị này cho các hoạt động tìm kiếm vì Các chỉ mục được sử dụng để tìm kiếm. Để tiết kiệm dung lượng, ES lưu trữ tài liệu nguồn nén. Nếu chúng tôi chỉ cần id chứ không cần toàn bộ tài liệu nguồn thì chúng tôi có thể vô hiệu hóa bộ nhớ nguồn.

Nếu chúng tôi không cần thêm thông tin, chúng tôi chỉ có thể lấy nội dung của _source:

curl -XGET "$ES_URL/blog/post/1/_source?pretty"
{
  "title" : "Веселые котята",
  "content" : "<p>Смешная история про котят<p>",
  "tags" : [ "котята", "смешная история" ],
  "published_at" : "2014-09-12T20:44:42+00:00"
}

Bạn cũng có thể chỉ chọn một số trường nhất định:

# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"
{
  "_index" : "blog",
  "_type" : "post",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "title" : "Веселые котята"
  }
}

Hãy lập chỉ mục thêm một vài bài đăng và chạy các truy vấn phức tạp hơn.

curl -XPUT "$ES_URL/blog/post/2" -d'
{
  "title": "Веселые щенки",
  "content": "<p>Смешная история про щенков<p>",
  "tags": [
    "щенки",
    "смешная история"
  ],
  "published_at": "2014-08-12T20:44:42+00:00"
}'
curl -XPUT "$ES_URL/blog/post/3" -d'
{
  "title": "Как у меня появился котенок",
  "content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
  "tags": [
    "котята"
  ],
  "published_at": "2014-07-21T20:44:42+00:00"
}'

Sắp xếp

# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "size": 1,
  "_source": ["title", "published_at"],
  "sort": [{"published_at": "desc"}]
}'
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : null,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : null,
      "_source" : {
        "title" : "Веселые котята",
        "published_at" : "2014-09-12T20:44:42+00:00"
      },
      "sort" : [ 1410554682000 ]
    } ]
  }
}

Chúng tôi đã chọn bài viết cuối cùng. size hạn chế số lượng văn bản được ban hành. total hiển thị tổng số tài liệu phù hợp với yêu cầu. sort ở đầu ra chứa một mảng các số nguyên mà việc sắp xếp được thực hiện. Những thứ kia. ngày đã được chuyển đổi thành một số nguyên. Thông tin thêm về việc sắp xếp có thể được tìm thấy trong tài liệu.

Bộ lọc và truy vấn

ES vì phiên bản 2 không phân biệt giữa bộ lọc và truy vấn khái niệm ngữ cảnh được giới thiệu.
Ngữ cảnh truy vấn khác với ngữ cảnh bộ lọc ở chỗ truy vấn tạo ra _score và không được lưu vào bộ đệm. Tôi sẽ cho bạn biết _score là gì sau.

Lọc theo ngày

Chúng tôi sử dụng yêu cầu phạm vi trong bối cảnh của bộ lọc:

# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "filter": {
    "range": {
      "published_at": { "gte": "2014-09-01" }
    }
  }
}'

Lọc theo thẻ

Chúng tôi sử dụng truy vấn thuật ngữ để tìm kiếm id tài liệu có chứa một từ nhất định:

# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": [
    "title",
    "tags"
  ],
  "filter": {
    "term": {
      "tags": "котята"
    }
  }
}'
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 1.0,
      "_source" : {
        "title" : "Веселые котята",
        "tags" : [ "котята", "смешная история" ]
      }
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 1.0,
      "_source" : {
        "title" : "Как у меня появился котенок",
        "tags" : [ "котята" ]
      }
    } ]
  }
}

Tìm kiếm toàn văn

Ba trong số các tài liệu của chúng tôi chứa nội dung sau trong trường nội dung:

  • <p>Смешная история про котят<p>
  • <p>Смешная история про щенков<p>
  • <p>Душераздирающая история про бедного котенка с улицы<p>

Chúng tôi sử dụng truy vấn khớp để tìm kiếm id tài liệu có chứa một từ nhất định:

# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
  "_source": false,
  "query": {
    "match": {
      "content": "история"
    }
  }
}'
{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 0.11506981,
    "hits" : [ {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "2",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "1",
      "_score" : 0.11506981
    }, {
      "_index" : "blog",
      "_type" : "post",
      "_id" : "3",
      "_score" : 0.095891505
    } ]
  }
}

Tuy nhiên, nếu tìm kiếm “truyện” trong trường nội dung, chúng ta sẽ không tìm thấy gì, bởi vì Mục lục chỉ chứa các từ gốc chứ không chứa gốc của chúng. Để thực hiện tìm kiếm chất lượng cao, bạn cần định cấu hình máy phân tích.

Lĩnh vực _score trình diễn sự liên quan. Nếu yêu cầu được thực thi trong ngữ cảnh bộ lọc thì giá trị _score sẽ luôn bằng 1, nghĩa là khớp hoàn toàn với bộ lọc.

Máy phân tích

Máy phân tích cần thiết để chuyển đổi văn bản nguồn thành một tập hợp các mã thông báo.
Máy phân tích bao gồm một mã thông báo và một số tùy chọn Bộ lọc mã thông báo. Tokenizer có thể được đặt trước bởi một số Bộ lọc Char. Trình tạo mã thông báo chia chuỗi nguồn thành các mã thông báo, chẳng hạn như dấu cách và ký tự dấu chấm câu. TokenFilter có thể thay đổi mã thông báo, xóa hoặc thêm mã thông báo mới, ví dụ: chỉ để lại gốc của từ, xóa giới từ, thêm từ đồng nghĩa. CharFilter - thay đổi toàn bộ chuỗi nguồn, ví dụ: cắt bỏ các thẻ html.

ES có một số máy phân tích tiêu chuẩn. Ví dụ, một máy phân tích người Nga.

Hãy sử dụng api và hãy xem các máy phân tích tiêu chuẩn và tiếng Nga biến đổi chuỗi “Những câu chuyện vui về mèo con” như thế nào:

# используем анализатор standard       
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "веселые",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истории",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "про",
    "start_offset" : 16,
    "end_offset" : 19,
    "type" : "<ALPHANUM>",
    "position" : 2
  }, {
    "token" : "котят",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}
# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"
{
  "tokens" : [ {
    "token" : "весел",
    "start_offset" : 0,
    "end_offset" : 7,
    "type" : "<ALPHANUM>",
    "position" : 0
  }, {
    "token" : "истор",
    "start_offset" : 8,
    "end_offset" : 15,
    "type" : "<ALPHANUM>",
    "position" : 1
  }, {
    "token" : "кот",
    "start_offset" : 20,
    "end_offset" : 25,
    "type" : "<ALPHANUM>",
    "position" : 3
  } ]
}

Máy phân tích tiêu chuẩn chia chuỗi bằng dấu cách và chuyển mọi thứ thành chữ thường, máy phân tích tiếng Nga loại bỏ những từ không quan trọng, chuyển nó thành chữ thường và để lại gốc của từ.

Hãy cùng xem máy phân tích Nga sử dụng Tokenizer, TokenFilters, CharFilters nào:

{
  "filter": {
    "russian_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "russian_keywords": {
      "type":       "keyword_marker",
      "keywords":   []
    },
    "russian_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "russian": {
      "tokenizer":  "standard",
      /* TokenFilters */
      "filter": [
        "lowercase",
        "russian_stop",
        "russian_keywords",
        "russian_stemmer"
      ]
      /* CharFilters отсутствуют */
    }
  }
}

Hãy mô tả máy phân tích của chúng tôi dựa trên tiếng Nga, nó sẽ cắt bỏ các thẻ html. Hãy gọi nó là mặc định, bởi vì máy phân tích có tên này sẽ được sử dụng theo mặc định.

{
  "filter": {
    "ru_stop": {
      "type":       "stop",
      "stopwords":  "_russian_"
    },
    "ru_stemmer": {
      "type":       "stemmer",
      "language":   "russian"
    }
  },
  "analyzer": {
    "default": {
      /* добавляем удаление html тегов */
      "char_filter": ["html_strip"],
      "tokenizer":  "standard",
      "filter": [
        "lowercase",
        "ru_stop",
        "ru_stemmer"
      ]
    }
  }
}

Đầu tiên, tất cả các thẻ HTML sẽ bị xóa khỏi chuỗi nguồn, sau đó tiêu chuẩn mã thông báo sẽ chia nó thành các mã thông báo, các mã thông báo kết quả sẽ chuyển sang chữ thường, các từ không quan trọng sẽ bị xóa và các mã thông báo còn lại sẽ vẫn là gốc của từ.

Tạo chỉ mục

Ở trên chúng tôi đã mô tả bộ phân tích mặc định. Nó sẽ áp dụng cho tất cả các trường chuỗi. Bài đăng của chúng tôi chứa một loạt các thẻ, vì vậy các thẻ này cũng sẽ được máy phân tích xử lý. Bởi vì Chúng tôi đang tìm kiếm các bài đăng khớp chính xác với một thẻ, sau đó chúng tôi cần tắt phân tích cho trường thẻ.

Hãy tạo một chỉ mục blog2 bằng bộ phân tích và ánh xạ, trong đó tính năng phân tích trường thẻ bị tắt:

curl -XPOST "$ES_URL/blog2" -d'
{
  "settings": {
    "analysis": {
      "filter": {
        "ru_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "ru_stemmer": {
          "type": "stemmer",
          "language": "russian"
        }
      },
      "analyzer": {
        "default": {
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "ru_stop",
            "ru_stemmer"
          ]
        }
      }
    }
  },
  "mappings": {
    "post": {
      "properties": {
        "content": {
          "type": "string"
        },
        "published_at": {
          "type": "date"
        },
        "tags": {
          "type": "string",
          "index": "not_analyzed"
        },
        "title": {
          "type": "string"
        }
      }
    }
  }
}'

Hãy thêm 3 bài viết tương tự vào chỉ mục này (blog2). Tôi sẽ bỏ qua quá trình này bởi vì... nó tương tự như việc thêm tài liệu vào chỉ mục blog.

Tìm kiếm toàn văn có hỗ trợ biểu thức

Chúng ta hãy xem xét một loại yêu cầu khác:

# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "истории",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

Bởi vì Chúng tôi đang sử dụng một máy phân tích có gốc tiếng Nga, khi đó yêu cầu này sẽ trả về tất cả tài liệu, mặc dù chúng chỉ chứa từ 'lịch sử'.

Yêu cầu có thể chứa các ký tự đặc biệt, ví dụ:

""fried eggs" +(eggplant | potato) -frittata"

Cú pháp yêu cầu:

+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount
# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
  "query": {
    "simple_query_string": {
      "query": "-щенки",
      "fields": [
        "title^3",
        "tags^2",
        "content"
      ]
    }
  }
}'

# получим 2 поста про котиков

tài liệu tham khảo

PS

Nếu bạn quan tâm đến các bài học như vậy, có ý tưởng cho các bài viết mới hoặc có đề xuất hợp tác, tôi sẽ rất vui khi nhận được tin nhắn qua tin nhắn cá nhân hoặc qua email m.kuzmin+habr@darkleaf.ru.

Nguồn: www.habr.com