lucene和elasticsearch笔记

lucene和elasticsearch笔记

目录

简介

全文检索是海量文本数据的快速查询技术;从海量数据中迅速、准确的定位到需要的信息。

倒排索引:索引文件是全文检索的核心,其实现步骤为:

  1. 将源数据按照结构封装成文本对象;
  2. 对文本数据进行分词计算,获取对应字段/属性的分词结果;分词,即将文本字符串按照一定规则切分成具有最小意义的词语。
  3. 将文本对象输出到索引文件的数据部分;
  4. 将分词结果合并输出到索引文件,形成索引指向文本对象的结构。

lucene

lucene是基于java开发的全文检索引擎工具。具有稳定、高性能、内存要求小、支持多种搜索功能的优点。分词器是处理数据文本中,字符串分词计算的实现代码,要根据不同的语言、不同的分词逻辑、不同的环境而使用不同的分词器。lucene自带一些分词器可以使用,但是无法实现满足所有情况,故放开了一个分词的接口Analyzer;实现了该接口就可以在lucene中使用,完成分词。

代码使用:

依赖

<dependency> <!-- 查询相关jar包 -->
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency> <!-- lucene自带智能中文分词器 -->
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-smartcn</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency> <!-- 默认的英文lucene分词器 -->
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency> <!-- lucene核心包 -->
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>6.0.0</version>
</dependency>

测试分词器

编写分词器方法,完成根据分词器实现类和字符串分词并打印分词结果

public void printAnalyzer(Analyzer analyzer,String msg) throws Exception{
    //分词器最终都是处理流数据,先将msg转化成字符串流
    StringReader reader=new StringReader(msg);
    //从分词器实现类中,获取分词的流数据,获取过程就是对字符串
    //已经完成了分词计算,lucene每计算一次字符串,都对应field
    TokenStream tokenStream = analyzer.tokenStream("test", reader);
    //重置对象reset
    tokenStream.reset();
    //获取token流中的字符属性对象
    CharTermAttribute attribute = tokenStream.getAttribute(CharTermAttribute.class);
    //遍历token
    while(tokenStream.incrementToken()){
        System.out.println(attribute.toString());
    }
}
/*
//供测试的几种分词器
Analyzer a1 = new StandardAnalyzer();//标准
Analyzer a2 = new SmartChineseAnalyzer();//智能中文
Analyzer a3 = new SimpleAnalyzer();//简单
Analyzer a4 = new WhitespaceAnalyzer();//空格
*/

测试创建索引

索引的创建,lucene的索引文件由document对象和携带多种属性的分词结果两部分组成。lucene创建索引的步骤是:(索引创建索引后可以通过luke软件查看)

  • ①指定索引文件目录(Directory);
  • ②创建分词器;
  • ③创建输出对象IndexWriter
  • ④封装Document对象;
  • ⑥将document输出到索引文件;

示例代码如下:

@Test
public void createIndex() throws IOException {
    FSDirectory dir = FSDirectory.open(Paths.get("./index"));
    Analyzer analyzer = new IKAnalyzer6x();
    IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(analyzer));
    Document doc1 = new Document();
    Document doc2 = new Document();
    doc1.add(new TextField("title", "娱乐扒呀扒", Store.YES));
    doc1.add(new TextField("content","巴黎圣母院烧了,很遗憾,没有爬上去过", Store.NO));
    doc2.add(new TextField("name","三星固态硬盘华为", Store.YES));
    doc2.add(new DoublePoint("price",2200));
    doc2.add(new StringField("price", "2200", Store.YES));
    doc2.add(new StringField("img","http://image.jt.com/1.jpg", Store.YES));
    writer.addDocument(doc1);
    writer.addDocument(doc2);
    writer.commit();
}

测试搜索索引数据:

lucene可以提供多种的查询功能,每种查询都有对应的实现类,主要有:

  • 词项查询:提供关键字,从某个域中匹配;
  • 多域查询:提供关键字或一句话,先解析为一批词项,在进行匹配;
  • 布尔查询:在多个查询条件之间使用逻辑关系;
  • 范围查询:对具有数字特征的point类型进行查询。
  • 还有前缀查询、模糊查询、通配符查询等等。

实现查询的步骤如下:

  • ①指定索引所在目录(Directory);
  • ②根据索引位置创建查询对象:IndexSearcher
  • ③创建查询条件:Query;(需要指定查询关键字和选择查询方式)
  • ④调用查询方法;(查询对象的search方法)
  • ⑤遍历评分结果,从每一个评分结果中获取document的id值;
  • ⑥根据id获取document并返回。

示例代码(使用了词项查询):

@Test
public void termQuery1() throws Exception {
    Path path = Paths.get("./index");
    FSDirectory dir = FSDirectory.open(path);
    IndexReader reader = DirectoryReader.open(dir);
    IndexSearcher search = new IndexSearcher(reader);
    Term term = new Term("name", "三星");
    Query query = new TermQuery(term);
    TopDocs topDocs = search.search(query, 10);
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        int docId = scoreDoc.doc;
        Document doc = search.doc(docId);
        System.out.println("id为:" + docId + "的评分是:" + scoreDoc.score);
        System.out.println("name:" + doc.get("name"));
        System.out.println("price:" + doc.get("price"));
        System.out.println("img:" + doc.get("img"));
        System.out.println("title:" + doc.get("title"));
        System.out.println("content:" + doc.get("content"));
    }
}

elasticsearch

简介

elasticsearch是一个分布式可扩展的实时搜索和分析引擎,是基于lucene开发的,具有以下特点:

  1. 分布式实时文件存储;
  2. 实时分析的分布式搜索引擎;
  3. 可扩展,能处理PB级别的结构化或非结构化数据;

es是面向文档型的数据库,一条数据就是一个文档,用json格式存储;

安装配置

安装:官网下载tar包,解压即可。(注意:需要jdk环境,且es不能以root用户和分组启动,所以需要新建一个用户,并将解压的整个目录拥有者改为新建的用户)
需要修改的配置(elasticsearch.yml):

# 集群名称
cluster.name: my-application
# 节点名称
node.name: node-1
# 设置es会被写入交换空间
bootstrap.memory_lock: false
# 停用bootstrap检测
bootstrap.system_call_filter: false
# ip和端口
network.host: 192.168.0.1
http.port: 9200
# 在7.0版本中,还需要如下配置
cluster.initial_master_nodes: ["node-1"]
# 开启http插件访问权限,若不用插件则不需要
http.cors.enabled: true
http.cors.allow-origin: "*"

另外,启动时可能的报错及处理:
线程数太少:修改/etc/security/limits.d/90-nproc.conf文件,以星号开头的一行的数字改为报错提示的值;
虚拟内存太小:修改或追加文件/etc/sysctl.conf,vm.max_map_count = 655360;保存退出后执行sysctl -p。
启动方式:执行./bin/elasticsearch

可视化插件安装(kibana):解压后只需要修改./conf/kibana.yml即可

header插件:

//允许远程访问
server.host: "0.0.0.0"
elasticsearch.hosts: ["es的访问地址"]

使用概念说明

首先了解4个概念:

  1. 索引(index):是多个分片组在一起的逻辑文件,但对于我们使用而言,索引是elasticsearch存放数据的地方,可以理解为关系型数据库的一个数据库;索引名称必须全小写,不能以下划线开头;
  2. 类型(type):索引下不同的数据类型,可以理解为数据库中的表;一般每个类型都有自己的映射或者结构定义;
  3. 文档(document):存储的数据实体,可以理解为数据库中的一行数据;
  4. 字段(field):字段,组成了文档,可以理解为数据库中的列;

索引的创建原则:类似的数据,类似的文档放在一个索引,即一个索引中的文档应该是很多字段都相同。且索引名称必须全小写,不能以下划线开头。

另外,在7.0中,尽量不在使用类型,否则容易出错。

校验语句

GET /index01/type01/_validate/query?explain
{
  "query": {
    "match": {
      "field01": "关键字"
    }
  }
}

增删改

//创建/替换文档(也可以建立索引)(当索引为提前手动创建的时,不能使用该方式)
PUT /index01/type01/1
{
    "field01" : "内容1",
    "field02" : "内容2"
}
//创建文档
curl -XPOST /index01/_create/1
或者:curl -XPUT /index01/_doc/1
或者:curl -XPUT /index01/_doc/
//修改文档部分内容(其实实现原理是查询出来进行全量替换,然后更新版本)
POST /index01/type01/1/_update
{
    "doc":{
        "field01":"新内容"
    }
}
//删除文档(不会立即删除,而是有版本号控制,当有足够的版本数是,会删除最老的)
DELETE /index01/type01/1

GET /_cat/indices?v  //查看索引
get /index01  //查看索引
DELETE /index01  //删除索引

查询示例

简单查询

GET /index01/type01/_search
{
  "query": { ## 查询字段
    "match": {
      "field01": "关键字"
    }
    ## "match_all": {} ##查询所有字段
  },
  "_source": [ ## 结果要展示的字段
    "field01",
    "field02"
  ]
  "sort": [ ## 排序
    {
      "field02": "desc"
    }
  ]
  "from": 0, ## 分页,开始是0
  "size": 1  ## 分页大小
  "highlight": { ## 在结果中高亮显示匹配的关键字
    "fields": {
      "field01": {}
    }
  }
}

match查询

//会使用分词器拆分关键字,根据要求查询,然后会按相关性排序
GET /index01/type01/_search
{
  "query": {
    "match": {
      ## "field01": "关键字1 关键字2" ## 默认or查询
      "field01": {
        "query": "关键字1 关键字2",
        "operator": "and" ## 改为必须全部匹配
        ## "minimum_should_match": "50%" ## 设置只要有一般匹配即可
      }
    }
  }
}

multi_match查询

//在多个字段中查询,只要有一个字段有匹配就返回
GET /index01/type01/_search
{
  "query": {
    "multi_match": {
      "query": "关键字",
      # "type": "cross_fields",
      # "operator": "and"
      "fields": [
        "field01",
        "field02"
      ]
    }
  }
}

match_phrase查询

term查询与match的区别是term不分词,类似于 =,terms类似于 in。具体用法类似。

//会使用分词器拆分关键字,但必须全部包含才会出现
GET /index01/type01/_search
{
  "query": {
    "match_phrase": {
      ## "field01": "关键字1 关键字2" ## 默认必须全部匹配
      "field01": {
        "query": "关键字1 关键字2",
        "slop": 1 ## 设置匹配的关键词个数
      }
    }
  }
}

组合与范围查询

在query下用bool,bool下包括must(=),must_not(!=),should(or),filter(过滤);

GET /index01/type01/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "field01": "关键字"
        }
      },
      "filter": {
        "range": { #用于查询数值、时间区间
          "field02": {
            "gt": 400, #说明:gt、get、lt、lte
            "lt": 700  #分别代表:>、>=、<、<=
          }
        }
      }
    }
  }
}

其他查询

filter查询:是过滤精确查询,会缓存结果,不会进行相关性排序;
boost :设置字段权重,默认是1,(可以在创建索引时指定,也可以在组合查询时设置),例如:

{
    "query": {
        "bool": {
            "should": [
                {
                    "match": {
                        "title": {
                            "query": "编程思想",
                            "boost": 2
                        }
                    }
                },
                {
                	"match": {
                        "content": {
                            "query": "编程思想",
                            "boost": 1
                        }
                    }
                }
            ]
        }
    }
}

prefix前缀查询:性能差,扫描所有倒排索引
通配符查询:使用星号,性能差,扫描所有倒排索引
正则搜索:性能差
fuzziness: 纠错查询

字段数据类型

字符型:string;
数字型:long、integer、short、byte、double、float;
日期型:date;
布尔型:boolean;
二进制型:binary;
对象类型:object,适用于单个json对象;
嵌套类型:nested,适用于json数组;
数组类型:不需要指定,只要用类似于[“xxx1”,“xxx2”]的形式使用即可;
经纬度类型:geo_point(使用示例:{ “lat”: 41.12,“lon”: -71.34});
ipv4类型:ip;

//添加数据类型示例:
PUT index01
{"mappings": {"properties": {"field01": {"type": "geo_point"}}}}
//使用该数据类型:
PUT index01/_doc/1
{"location": "41.12,-71.34"}

IK分词器

将IK分词器压缩包解压到elasticsearch/plugins下并重命名为analysis-ik(名称在插件包根目录的properties文件中有),然后重启ES即可。

//使用分词器分词并返回分词结果
POST /_analyze
{
  # "analyzer": "ik_max_word", # 指定使用ik分词器,若不指定则使用默认的
  "text": "*"
}
//使用ik分词器
//创建索引
curl -XPUT /index
//创建映射,并在建索引和查询时使用不同的分词器
curl -XPOST /index/_mapping
{
    "properties": {
        "content": {
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_smart"
        }
    }
}
//使用ik分词器(只指定索引,没有type)
curl -XPOST /index/_create/1
{"content": "java编程思想"}

集群

集群的结构:

  1. 主节点:维护当前集群所有的元数据,例如索引分片数量,副本数量,分片大小,分片存储位置,副本存储位置。客户端无论哪种方式连接,可以获取主节点的元数据信息,从而才能操作集群,使用索引、文档、搜索等功能。
  2. 数据节点:只对当前的集群中索引的分片进行管理,听从于主节点的命令,只负责数据的读写工作;
  3. 负载均衡节点:只做当前集群连接的高并发客户端的分流工作;
  4. 协调器节点:将不同角色不同功能的节点整合到一起,形成集群

搭建集群:
只需要修改配置文件elasticsearch.yml的配置文件即可;该配置文件中默认具有master、data、负载均衡这三个角色,协调器则需要手动配置;要搭建集群,在上面单个节点的配置的基础上,要保证同一个集群中的集群名字一致,另外,还需要以下配置:

# 设置节点间交互的tcp端口,默认是9300,可以不设置
# transport.tcp.port: 9300
# node.master: true  # 主节点角色
# node.data: true  # 数据节点角色
# node.ingest: true  # 负载均衡器角色
# 配置协调器节点列表, 7.0没有了
discovery.zen.ping.unicast.hosts: ["ip", "ip:port"]
# 配置最小master个数避免脑裂, 7.0没有了
discovery.zen.minimum_master_nodes: 2
# 在7.0版本中集群的配置改为以下两条:(启动后会自动计算最小的master个数,不会有脑裂)
# 配置尝试发现已有的集群列表,不配置则不发现
discovery.seed_hosts: ["host1", "host2"]
# 刚启动时需要的最小的主节点数量
cluster.initial_master_nodes: ["node-1, node-2"]
# 设置索引的分片数,默认为5 
# index.number_of_shards: 5  
# 设置索引的副本数,默认为1:  
# index.number_of_replicas: 1  

脑裂:ES集群中,多个master间若发生延时断开,会造成不同master同时管理的情况;所以需要配置过半的最小master个数(n/2 + 1),使得集群中最终只有一个master集体有效管理集群。

ES启动的选举逻辑:节点启动->连接协调器->获取整个集群在线节点->将现役master放到本节点activeMaster的list中->若离list为空则继续获取,否则下一步->若list大小小于最小master个数则继续获取,否则将id最小的节点暂时定为master->若依然没有选举成功,则重新执行一遍。

索引数据的分片和副本分片:默认是5个分片,一个副本。同时,在集群数据节点足够的情况下,ES会自动根据数量计算分片的分布,以尽可能保证合理的位置。所以在数据节点足够的情况下要高性能则是扩大分片数量,要高可用则提高副本数量。另外,对于已经生成的索引数据是无法修改分片和副本的,但是修改之后生成的索引则会按新的配置计算。

上一篇:全文搜索引擎 Elasticsearch 入门:集群搭建


下一篇:elasticsearch--知识点