ElasticSearch原理与实践

1.数据结构

名称

描述

与关系型数据库对应

Index(索引)

就有类似特性的文档集合,是一个存储方案,必须全小写

数据库

Type(l类型)

是索引的内部逻辑分区,对应的是不同业务的存储逻辑

Document(文档)

是Lucene索引和搜索的原子单位,基于json表示

一条记录

filed(域)

一个文档中有多个域,域可以有多个值,域代表了相同的数据类型

mapping(映射)

域与数据类型的映射关系

 

2.服务架构

名称

描述

cluster(集群)

不同的实例组成的集群,根据统一的cluster.name进行连接认证

node(节点)

一个服务实例

shard(数据分片)

一个index存在多个分片,副本永远不会分配到相同的主节点上

Replica(数据分片副本)

一个主分片默认配置一个副本(可设置多个),解决了高可用和分担读压力

3.单节点内部架构

节点之间通过Transport模块互相通信,Discovery模块互相发现

GateWay模块持久化存储

RestfulApi层,脚本编译执行层、索引模块、查询模块

4.应用场景

全文搜索检索引擎(Stack OverFlow,GitHub)

实时日志检索系统(ElasticSearch+LogStash,Kibana)

实时在线分析系统(OlAP)

实时推荐系统

5.倒排索引

通过单词导向文档id(建立映射关系),通过增加一个索引(例如B树),索引到指定列上,指向文档的id

单词词典

记录所有稳当的单词,记录单词与倒排列表的关系,通过B+树或者哈希拉链法实现

倒排列表

记录了单词对应的文档详情,主要有一下几项:

词频TF:该单词在文档出现的次数,用于评分

位置(Position):记录单词在文档中的分词位置,用于语句搜索

偏移(Offset):记录单词的开始与结束位置,用于实现高亮显示

 

 

6.索引文档过程

根据文档id确定shard编号,转发请求到shard所在的节点(all,one,quorun模式)

在节点上,先将文档内容写入内存,同时记录translog日志,当内存满了再写入文件中

检索时,先查主分片和分片副本,查不到再查translog

7.优缺点

优点:

    P2P对等架构,发现故障时自动分配其他节点工作,系统易管理,易伸缩

    先写文件系统缓存,再定期刷写到磁盘,写入性能高

    字段缓存,过滤器缓存,查询性能高

    内存熔断机制保证系统稳定

    系统不依赖其他服务,自带分布式协调器,稳定性高

    插件式框架,易于开发者自定义插件,可扩展性高

    基于translog recovery和NodeFaultDetection,容错性高

缺点:

    权限认证、报警系统收费,未开源

    没有实现事物,不适合需要事物支持的场景

    集群庞大后,异常恢复时间久,网络请求风暴

    对于多维度统计计算支持较差

 

8.学习方向(转载)

(1)偏算法方向:搜索算法/研发工程师。工作内容往往是在"使用"和"二次开发"两方面跟ES打交道,需要知道ES一些机制、DSL写法、索引构建和某些外围插件的开发和使用。如果是偏算法,那么会对分词、查询改写、召回和精排等模块进行优化。其中需要java开发能力,比如写个服务接口,提供自己实现的某算法能力。如果是偏研发,那么可能涉及ES二次开发,需要做一些跟倒排索引构建、跳表查找加速、索引压缩等相关的底层开发,需要对搜索引擎的研发有一定的了解,ES以及底层的lucene都是java开发,自然而然也是用Java。

(2)偏ops方向:ES系统/运维工程师。往往是java后台工程师会往这个方向靠,ES集群的管理、维护和升级,以及最重要的集群性能优化,都是上面搜索算法/研发工程师不具备的能力。虽然ES开箱即用,但离用得好还差个ES系统/运维工程师,而这也是很多公司缺的,因为这几年有意识往这个方向靠的java工程师不多,往往是被动得维护ES才变成了ES OPS专家。所以ES线下沙龙涉及系统优化,大家都是以经验分享,没有形成体系。

(3)偏数据流方向:日志系统研发工程师。10个ES技术分享,假设有2个分享搜索、3个分享ops,其它都是分享ELK系统搭建。虽然有搜索业务的公司很多,但有日志管理需求的公司更多,其中ELK又那么好用,所以能不能用java开发ELK系统也是强需求,能不能从0到1搭一套日志管理系统,拿出手分量也够。

9.分词Analyzer

分词器是将全文本转化为一系列单词(term/token)的过程,可以使用ES内置分词器或者第三方的分词器,除了在数据写入时转换词条,匹配查询语句时也会对查询语句进行分词分析

由三部分组成:

Character Filter:原始文本处理,去掉特殊符号与HTML格式

Tokenizer:按照规则切分单词

TokenFilters:对切分单词加工、小写、删除停顿词,增加同义词

10.API

查询语句

释义

/_search

集群上所有索引

/index1/_search

index1

/index1,index2/_search

index1和index2

/index*/_search

index开头的所有索引

使用URI查询

get查询,查询叫cessi的姓名索引

http://ip:port/kibana/_search?q=first_name:cessi

post查询,body格式为

{

    "query":{

        "query_string"{

            "default_field":"first_name",

            "query": "cessi AND haha"

        }

    }

}

 

10.实践(转载)

pom文件配置

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.4.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.4.0</version>
</dependency>

yml配置

es:
  host: xx.xx.xx.xx
  port: 9200
  scheme: http

工具类

package xyz.wongs.weathertop.base.service;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import sun.rmi.runtime.Log;
import xyz.wongs.weathertop.base.entiy.ElasticEntity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Slf4j
@Component
public class BaseElasticService {

    @Autowired
    RestHighLevelClient restHighLevelClient;

    /**
     * @date 2019/10/17 17:30
     * @param idxName   索引名称
     * @param idxSQL    索引描述
     * @return void
     * @throws
     * @since
     */
    public void createIndex(String idxName,String idxSQL){
        try {
            if (!this.indexExist(idxName)) {
                log.error(" idxName={} 已经存在,idxSql={}",idxName,idxSQL);
                return;
            }
            CreateIndexRequest request = new CreateIndexRequest(idxName);
            buildSetting(request);
            request.mapping(idxSQL, XContentType.JSON);
//            request.settings() 手工指定Setting
            CreateIndexResponse res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            if (!res.isAcknowledged()) {
                throw new RuntimeException("初始化失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    /** 断某个index是否存在
     * @author WCNGS@QQ.COM
     * @See
     * @date 2019/10/17 17:27
     * @param idxName index名
     * @return boolean
     * @throws
     * @since
     */
    public boolean indexExist(String idxName) throws Exception {
        GetIndexRequest request = new GetIndexRequest(idxName);
        request.local(false);
        request.humanReadable(true);
        request.includeDefaults(false);
        request.indicesOptions(IndicesOptions.lenientExpandOpen());
        return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }

    /** 断某个index是否存在
     * @date 2019/10/17 17:27
     * @param idxName index名
     * @return boolean
     * @throws
     * @since
     */
    public boolean isExistsIndex(String idxName) throws Exception {
        return restHighLevelClient.indices().exists(new GetIndexRequest(idxName),RequestOptions.DEFAULT);
    }

    /** 设置分片
     * @date 2019/10/17 19:27
     * @param request
     * @return void
     * @throws
     * @since
     */
    public void buildSetting(CreateIndexRequest request){
        request.settings(Settings.builder().put("index.number_of_shards",3)
                .put("index.number_of_replicas",2));
    }
    /**
     * @author WCNGS@QQ.COM
     * @See
     * @date 2019/10/17 17:27
     * @param idxName index
     * @param entity    对象
     * @return void
     * @throws
     * @since
     */
    public void insertOrUpdateOne(String idxName, ElasticEntity entity) {
        IndexRequest request = new IndexRequest(idxName);
        log.error("Data : id={},entity={}",entity.getId(),JSON.toJSONString(entity.getData()));
        request.id(entity.getId());
        request.source(entity.getData(), XContentType.JSON);
//        request.source(JSON.toJSONString(entity.getData()), XContentType.JSON);
        try {
            restHighLevelClient.index(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /** 批量插入数据
     * @date 2019/10/17 17:26
     * @param idxName index
     * @param list 带插入列表
     * @return void
     * @throws
     * @since
     */
    public void insertBatch(String idxName, List<ElasticEntity> list) {
        BulkRequest request = new BulkRequest();
        list.forEach(item -> request.add(new IndexRequest(idxName).id(item.getId())
                .source(item.getData(), XContentType.JSON)));
        try {
            restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** 批量删除
     * @date 2019/10/17 17:14
     * @param idxName index
     * @param idList    待删除列表
     * @return void
     * @throws
     * @since
     */
    public <T> void deleteBatch(String idxName, Collection<T> idList) {
        BulkRequest request = new BulkRequest();
        idList.forEach(item -> request.add(new DeleteRequest(idxName, item.toString())));
        try {
            restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @date 2019/10/17 17:14
     * @param idxName index
     * @param builder   查询参数
     * @param c 结果类对象
     * @return java.util.List<T>
     * @throws
     * @since
     */
    public <T> List<T> search(String idxName, SearchSourceBuilder builder, Class<T> c) {
        SearchRequest request = new SearchRequest(idxName);
        request.source(builder);
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = response.getHits().getHits();
            List<T> res = new ArrayList<>(hits.length);
            for (SearchHit hit : hits) {
                res.add(JSON.parseObject(hit.getSourceAsString(), c));
            }
            return res;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** 删除index
     * @date 2019/10/17 17:13
     * @param idxName
     * @return void
     * @throws
     * @since
     */
    public void deleteIndex(String idxName) {
        try {
            if (!this.indexExist(idxName)) {
                log.error(" idxName={} 已经存在",idxName);
                return;
            }
            restHighLevelClient.indices().delete(new DeleteIndexRequest(idxName), RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * @date 2019/10/17 17:13
     * @param idxName
     * @param builder
     * @return void
     * @throws
     * @since
     */
    public void deleteByQuery(String idxName, QueryBuilder builder) {

        DeleteByQueryRequest request = new DeleteByQueryRequest(idxName);
        request.setQuery(builder);
        //设置批量操作数量,最大为10000
        request.setBatchSize(10000);
        request.setConflicts("proceed");
        try {
            restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值