分布式搜索引擎

Elasticsearch是一款强大的开源搜索引擎,支持分布式和Restful接口。文章介绍了其倒排索引的概念,对比了与MySQL的正向索引,详细阐述了Elasticsearch的安装、索引库和文档操作,以及数据同步的方法。此外,还提到了Elasticsearch在集群环境下的工作原理和数据分布策略。
摘要由CSDN通过智能技术生成

目录

介绍

倒排索引

安装

索引库操作

创建索引库和映射

查询索引库

修改索引库

删除索引库

文档操作

新增文档

查询文档

删除文档

修改文档

RestAPI

查询与检索

DSL查询语法

搜索结果处理

数据聚合

自动补全

数据同步

ES集群


介绍

elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容,例如:在GitHub搜索代码,在电商网站搜索商品,在百度搜索答案,在打车软件搜索附近的车。elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域;而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。 elasticsearch具备下列优势:

  • 支持分布式,可水平扩展
  • 提供Restful接口,可被任何语言调用

倒排索引

在mysql正向索引中根据文本搜索,只能模糊查询全表扫描数据,效率低下;

倒排索引则根据词条进行搜索,每个词条都建立了索引而不是字段:

- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。 文档数据会被序列化为json格式后存储在elasticsearch中。

  • 索引(index):相同类型的文档的集合
  • 映射(mapping):索引中文档的字段约束信息,类似表的结构约束 

 

Mysql:擅长事务类型操作,可以确保数据的安全和一致性

Elasticsearch:擅长海量数据的搜索、分析、计算

 

安装

部署单点es,在浏览器输入地址访问:http://10.0.0.31:5601,即可看到结果

#为了让es和kibana容器互联。先创建一个网络
docker network create es-net
#加载镜像
docker load -i es.tar
docker load -i kibana.tar
#创建容器
docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \  #非集群模式
    -v es-data:/usr/share/elasticsearch/data \  #挂载逻辑卷,绑定es的数据目录
    -v es-plugins:/usr/share/elasticsearch/plugins \  #挂载逻辑卷,绑定es的插件目录
    --privileged \  #授予逻辑卷访问权
    --network es-net \  #加入一个名为es-net的网络中
    -p 9200:9200 \  #端口映射配置
    -p 9300:9300 \
elasticsearch:7.12.1

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \ #设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
--network=es-net \  #加入一个名为es-net的网络中,与elasticsearch在同一个网络中
-p 5601:5601  \
kibana:7.12.1

#离线安装ik插件
#查看elasticsearch的数据卷目录
docker volume inspect es-plugins
#解压缩分词器安装包,上传到es容器的插件数据卷中
/var/lib/docker/volumes/es-plugins/_data
#在分词器config目录下IKAnalyzer.cfg.xml配置文件配置自己的扩展字典和停用词词典
#重启容器
docker restart es
docker restart kibana

部署es集群可以直接使用docker-compose来完成,不过要求Linux虚拟机至少有4G的内存空间

#docker-compose文件,启动命令docker-compose up
version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es01
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data02:/usr/share/elasticsearch/data
    networks:
      - elastic
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic

volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local

networks:
  elastic:
    driver: bridge

索引库操作

索引库就类似数据库表,mapping映射就类似表的结构。我们要向es中存储数据,必须先创建“库”和“表”。

常见的mapping属性包括:

- type:字段数据类型,常见的简单类型有:
  - 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
  - 数值:long、integer、short、byte、double、float、
  - 布尔:boolean
  - 日期:date
  - 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段

创建索引库和映射

PUT /heima
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "falsae"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          }
        }
      },
      // ... 略
    }
  }
}

查询索引库

修改索引库

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。

删除索引库

文档操作

新增文档

查询文档

删除文档

修改文档

 全量修改:根据指定的id删除文档,新增一个相同id的文档,如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
增量修改:修改文档中的部分字段

PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

RestAPI

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。其中的Java Rest Client又包括两种:Java Low Level Rest Client, Java High Level Rest Client

在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。分为三步:

1、引入es的RestHighLevelClient依赖

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

2、因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

3、初始化RestHighLevelClient

 @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://10.0.0.31:9200")
        ));
    }
@AfterEach
    void tearDown() throws IOException {
        client.close();
    }

4、创建索引库

 5、判断索引库是否存在

@Test
void testExistsHotelIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

6、操作文档

文档操作的基本步骤:

- 初始化RestHighLevelClient
- 创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
- 准备参数(Index、Update、Bulk时需要)
- 发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
- 解析结果(Get时需要)

新增

查询

删除

    @Test
    void testDeleteDocumentById() throws IOException {
        // 1.准备Request      // DELETE /hotel/_doc/{id}
        DeleteRequest request = new DeleteRequest("hotel", "61083");
        // 2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

 更新

批量导入:批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送

@Test
    void testBulkRequest() throws IOException {
        // 查询所有的酒店数据
        List<Hotel> list = hotelService.list();

        // 1.准备Request
        BulkRequest request = new BulkRequest();
        // 2.准备参数
        for (Hotel hotel : list) {
            // 2.1.转为HotelDoc
            HotelDoc hotelDoc = new HotelDoc(hotel);
            // 2.2.转json
            String json = JSON.toJSONString(hotelDoc);
            // 2.3.添加请求
            request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
        }

        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

查询与检索

除了存储,elasticsearch最擅长的还是搜索和数据分析,Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:

  • 查询所有:查询出所有数据,一般测试用。例如:match_all
  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:match_query,multi_match_query
  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如: ids, range, term
  • 地理(geo)查询:根据经纬度查询。例如: geo_distance, geo_bounding_box
  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如: bool, function_score
GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
}

DSL查询语法

// 查询所有
GET /indexName/_search
{
  "query": {
    "match_all": {
    }
  }
}

全文检索查询 

精确查询 

 

地理坐标查询 

算分函数查询

布尔查询

搜索结果处理

搜索的结果可以按照用户指定的方式去处理或展示

  • 排序:es默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序
  • 分页:默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了,elasticsearch中通过修改from、size参数来控制要返回的分页结果
  • 高亮:高亮查询必须使用全文检索查询,并且要有搜索关键字,将来才可以对关键字高亮

 

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 990, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}
#elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条

深度分页问题,ES提供了两种解决方案 :

  1. search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
  2. scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。

高亮:

数据聚合

数据聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。参加聚合的字段必须是keyword、日期、数值、布尔类型。

  • 桶(Bucket)聚合:用来对文档做分组

  - TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组
  - Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

  - Avg:求平均值
  - Max:求最大值
  - Min:求最小值
  - Stats:同时求max、min、avg、sum等

  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

 

自动补全

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

声明自定义分词器的语法如下:

PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { // 自定义分词器
        "my_analyzer": {  // 分词器名称
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": { // 自定义tokenizer filter
        "py": { // 过滤器名称
          "type": "pinyin", // 过滤器类型,这里是pinyin
		  "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"//拼音分词器适合在创建倒排索引的时候使用,但不能在搜索的时候使用
      }
    }
  }
}

 

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

 

查询的dsl语句及api如下:

// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "s", // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

 

 

数据同步

elasticsearch中的数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearch与mysql之间的数据同步

 

 代码示例:

//生产者
  @PutMapping()
    public void updateById(@RequestBody Hotel hotel){
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id不能为空");
        }
        hotelService.updateById(hotel);

        // 发送MQ消息
        rabbitTemplate.convertAndSend(HotelMqConstants.EXCHANGE_NAME, HotelMqConstants.INSERT_KEY, hotel.getId());
    }
//消费者
 @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = HotelMqConstants.INSERT_QUEUE_NAME),
            exchange = @Exchange(name = HotelMqConstants.EXCHANGE_NAME, type = ExchangeTypes.TOPIC),
            key = HotelMqConstants.INSERT_KEY
    ))
    public void listenHotelInsert(Long hotelId){
        // 新增
        hotelService.saveById(hotelId);
    }

ES集群

单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储问题、单点故障问题。

  • 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
  • 单点故障问题:将分片数据在不同节点备份(replica )

集群(cluster):一组拥有共同的 cluster name 的 节点。

节点 :集群中的一个 Elasticearch 实例

分片(shard):索引可以被拆分为不同的部分进行存储,称为分片。在集群环境下,一个索引的不同分片可以拆分到不同的节点中

主分片(Primary shard):相对于副本分片的定义。

副本分片(Replica shard)每个主分片可以有一个或者多个副本,数据和主分片一样。

 

每个索引库的分片数量、副本数量都是在创建索引库时指定的,并且分片数量一旦设置以后无法修改。语法如下: 

PUT /itcast
{
  "settings": {
    "number_of_shards": 3, // 分片数量
    "number_of_replicas": 1 // 副本数量
  },
  "mappings": {
    "properties": {
      // mapping映射定义 ...
    }
  }
}

 集群节点有不同的职责划分,默认情况下,集群中的任何一个节点都同时具备上述四种角色,但是真实的集群一定要将集群职责分离:

  • master节点:对CPU要求高,但是内存要求低
  • data节点:对CPU和内存要求都高
  • coordinating节点:对网络带宽、CPU要求高

职责分离可以让我们根据不同节点的需求分配不同的硬件去部署。而且避免业务之间的互相干扰。

 

 

默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其它候选节点会选举一个成为主节点。当主节点与其他节点网络故障时,可能发生脑裂问题。 为了避免脑裂,需要要求选票超过 ( eligible节点数量 + 1 )/ 2 才能当选为主,因此eligible节点数量最好是奇数。对应配置项是discovery.zen.minimum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题

 ES集群的分布式存储:

当新增文档时,应该保存到不同分片,保证数据均衡,那么coordinating node如何确定数据该存储到哪个分片呢? elasticsearch会通过hash算法来计算文档应该存储到哪个分片(_routing默认是文档的id,算法与分片数量有关,因此索引库一旦创建,分片数量不能修改!):

 

 ES集群的分布式查询:


elasticsearch的查询分成两个阶段:

  • scatter phase:分散阶段,coordinating node会把请求分发到每一个分片
  • gather phase:聚集阶段,coordinating node汇总data node的搜索结果,并处理为最终结果集返回给用户

 ES集群的故障转移:

集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其它节点,确保数据安全,这个叫做故障转移。master宕机后,EligibleMaster选举为新的主节点。 

 

 node1发生故障后的第一件事,就是重新选主,选中了node2,node2成为主节点后,会检测集群监控状态,发现:shard-1、shard-0没有副本节点。因此需要将node1上的数据迁移到node2、node3:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值