使用elasticsearch搜索1000万条数据

Published On July 17, 2017

category project | tags elasticsearch 搜索


ES是一个目前非常火的基于lecene的开源搜索引擎。lucene是一个比较底层的搜索引擎库(library),ES是基于lucene的一个服务(server)。用户不需要编程就可以使用它提供的强大搜索功能。

安装

安装java8。

使用rpm包的方式安装elasticsearch和kibana。虽然kibana不是必须的,但在kibana的dev tools里可以很方便地请求elasticsearch的接口,不过kibana对中文输入法支持很烂。

安装smart-cn中文分词插件/usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-smartcn。需要重启elasticsearch,否则创建索引的时候会提示找不到smartcn分词器。

默认只能从本地访问ES,如果需要从其他电脑访问,需要修改一下ES的配置,参见Cannot start Elasticsearch with non loopback address

transport.host: localhost                                                                                          
network.host: 0.0.0.0

创建索引

在浏览器中访问kibana: http://192.168.3.220:5601 在dev tools页面的console中执行:

PUT /toutiao
{
  "settings": {
    "number_of_replicas": 0,
    "analysis": {
      "analyzer": {
        "keywords_analyzer": {
          "type": "custom",
          "tokenizer": "comma",
          "filter": ["trim"]
        },
        "url_analyzer": {
          "type": "standard",
          "stopwords": [
            "http",
            "https"
          ]
        }
      },
      "tokenizer": {
        "comma": {
          "pattern": ",",
          "type": "pattern"
        }
      }
    }
  },
  "mappings": {
    "article1000": {
      "properties": {
        "keywords": {
          "type": "text",
          "analyzer": "keywords_analyzer"
        },
        "publish_time": {
          "type": "date",
          "ignore_malformed": true,
          "format": "yyyy-MM-dd HH:mm:ss"
        },
        "source": {
          "type": "keyword"
        },
        "title": {
          "type": "text",
          "analyzer": "smartcn"
        },
        "url": {
          "type": "text",
          "analyzer": "url_analyzer"
        }
      }
    }
  }
}

mapping解释

执行

GET toutiao
返回toutiao这个index的信息,包含了创建索引时的所有配置。

有几个概念需要明确,toutiao是索引(index),article1000是类型(type),类型是文档(document)的集合,索引包含类型。同一个索引里的不同类型代表不同的集合,它们通常具有相同(或相似)的字段集合,在lucene里面共用同一个表。

all types in Elasticsearch ultimately share the same mapping

一个索引包含多个分片(shards),每个分片使用一个lucene存储。

ES中的mapping定义了一个文档集合(即type)中各个字段的类型和所使用的索引类型,类似于mysql的schema,即表结构。 ES支持四种核心的数据类型:string,number,Boolean和date。不同的数据类型使用的索引也是不同的,如果不指定mapping,对于第一次出现的某个字段,ES会根据字段的类型自动选择相应的索引。比如,对于string类型的字段,会使用standard解析器进行全文索引。

了解ES的索引对于使用ES至关重要。

索引大致上可以分为精确值索引和全文索引。 精确值索引,比如用户id,日期等。对这些字段进行搜索与mysql的=查询类似,要么匹配要么不匹配,所以Foo和foo是不同的。

全文索引通常在自然语言文本上,比如标题。搜索结果具有相关性。这种索引使用倒排表作为数据结构,而一般数据库的索引通常使用B树。 在插入文档的时候,需要先对这样的字段(full-text field)分词、标准化,然后插入倒排表。这个过程叫做分析,由分析器(analyzer)完成。搜索的时候,对搜索字符串使用同样的分析方法,然后查询倒排表。

the same analysis process is applied both to the document at index time, and to the query string at search time so that the terms in the query match the terms in the inverted index.

ES自带了很多分析器,还支持自定义分析器。分析器由char_filter、tokenizer和filter三部分构成,可以分别进行定制。

下面对各个字段的mapping进行解释。

title是我们要进行全文搜索的字段,使用smartcn进行分词。

publish_time在json里是用字符串表示的,格式为yyyy-MM-dd HH:mm:ss。 需要显示告诉ES将该字段保存为日期类型,因为有的为NULL,建索引的时候会报错,设置ignore_malformed表示当数据格式不符合要求的时候忽略这个字段。

source是来源的名称,不需要分词,直接使用keyword这种字符串类型。等价于ES的老版本里的

{
  "source": {
      "type": "keyword",
      "index": "not_analyzed"
  }
}

keywords是由,分隔的关键字,自定义分析器的tokenizer

{
  "keywords_analyzer": {
    "type": "custom",
    "tokenizer": "comma",
    "filter": ["trim"]
  },
  "tokenizer": {
    "comma": {
      "pattern": ",",
      "type": "pattern"
    }
  }
}
可以使用_analyze接口对分析器进行测试。"体育,篮球,NBA,"将会被分成"体育","篮球","NBA"三个关键词:
GET /toutiao/_analyze
{
    "text": "体育,篮球,NBA,",
    "analyzer": "keywords_analyzer"
}

url的前缀都是http或https,在standard分析器的基础上过滤掉http和https这两个stopword,对http://toutiao.com/group/4252133775/分词的结果是"toutiao.com","group","4252133775"。

{
  "url_analyzer": {
    "type": "standard",
    "stopwords": [
      "http",
      "https"
    ]
  }
}
除了这些字段外,ES会将所有字段的字符串形式拼接成一个大的字符串(空格分隔),作为_all字段。如果搜索的时候没有指定字段,就对这个特殊的字段进行搜索。

导入数据

下载数据

百度网盘下载csv文件:article1000.csv,数据是从mysql导出的,内容是今日头条的新闻。

Mysql对应的表结构请看上一篇文章——使用全文索引将你的mysql打造成一个搜索引擎

从csv导入ES

yanxurui/csv2es下载python脚本,安装依赖后执行:

python csv2es.py --es-host 192.168.3.220:9200 --bulk-size 1000 --index toutiao --type article1000 --id-field id /Volumes/Elements/article1000.csv
大概需要1个小时,ES会占用了7.5G的存储空间。

在kibana中执行

GET /toutiao/article1000/_count
可以看到总共有9999999条数据,因为其中有一条数据格式有问题导入失败了。

搜索

全文搜索

搜索标题中包含库里的所有记录

GET /toutiao/article1000/_search?pretty
{
    "query": {
        "match": {
            "title": "库里" 
        }
    }
}
took是以毫秒为单位时间,耗时不到100ms。ES的一大优势就是做任何事情都是实时的(real-time)。 hits是搜索结果的集合,默认返回前10条。 total表示匹配的总数,对于分页很有用。

执行5次该搜索并与上一篇文章中使用MySQL的全文索引作比较: performance

分页

查询第1001到1020条结果:

GET /toutiao/article1000/_search?pretty
{
    "query": {
        "match": {
            "title": "库里"
        }
    },
    "from": 1000,
    "size": 20
}

精确值搜索

对source字段搜索是精确匹配的,如果使用match语句搜索:

GET /toutiao/article1000/_search?pretty
{
    "query": {
        "match": {
            "source": "虎扑"
        }
    }
}
此时它是一个scoring query,搜索结果带有相关度,对于精确值的字段这个值是相同的,没有意义。 scoring query应该用在全文搜索的时候,比如title字段。

正确的做法是采用filter query

GET /toutiao/article1000/_search?pretty
{
    "query": {
        "bool": {
            "filter": {
              "term": {"source": "虎扑"}
            }
        }
    }
}

等价于

GET /toutiao/article1000/_search?pretty
{
    "query": {
        "term": {
            "source": "虎扑"
        }
    }
}

多字段搜索

下面是一个比较复杂的搜索,对多个字段指定了过滤条件:

  1. title包含“我喜欢你”
  2. keywords字段为null
  3. url为mp.weixin.qq.com或source为"美文欣赏”|”美文美图”的结果得分更高,在最终的结果里排序里更靠前
  4. publish_time为2016年3月1日以后
GET /toutiao/article1000/_search?pretty
{
    "query": {
      "bool": {
        "must": [
          {"match": {"title": "我喜欢你"}}
        ],
        "must_not": [
          {"exists": {"field": "keywords"}}
        ], 
        "should": [
          { "match": { "url": "mp.weixin.qq.com"}},
          { "terms": {"source": ["美文欣赏","美文美图"]}}
        ],
        "filter": {
          "range": { "publish_time": { "gte": "2016-03-01 00:00:00" }} 
        }
    }
  },
  "size": 100
}

扩展

ES不止是一个搜索引擎,它也是一个真正的nosql数据库,通过集群可以支持PB级别的数据量。与传统的rdb相比,nosql最大的特点是面向json文档的,更进一步也就是schema-less,即记录或文档并不需要具有统一的属性集合,并且一个字段不要求单一值,可以是一个对象(kv字典,比如多种联系方式)或数组(比如多个标签)。

与其他nosql相比,比如mongodb,ES在查询的时候不会扫描所有的文档,查询都是通过索引进行的,因为所有字段都会建索引。与mongodb相比,虽然elasticsearch时schema-less的,但同一个字段类型必须相同,因为同一个index底层的lucene用的是具有统一格式的表。


qq email facebook github
© 2018 - Xurui Yan. All rights reserved
Built using pelican