ElasticSearch

倒排索引

ES所谓的倒排索引,是通过分词策略,比如文章形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。

有了倒排索引,就能实现O(1)时间复杂度的效率检索文章了,极大的提高了检索效率

倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。

lucene从4+版本后开始大量使用的数据结构是FST。FST有两个优点:

1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;

2)查询速度快。O(len(str))的查询时间复杂度。

这个数据的格式大致如下:假设有一个id字段标识了每个帖子数据,然后title字段是帖子的标题,content字段是帖子的内容。

idtitlecontent
1Java好用吗?Java是非常非常好的一门语言。。。。
2大家一起来学Java我这儿有一些很好的Java学习资源,比如说。。。
3一次Java面试经验去年这个时候,我学了Java,今年开始了面试。。。

把上述的几行数据放到搜索引擎里,这个倒排索引的数据大致看起来如下:

关键词     id

Java         [1, 2, 3]

语言         [1]

面试         [3]

资源         [2]    

那么你要搜索包含“Java”关键词的帖子,直接扫描这个倒排索引,在倒排索引里找到“Java”这个关键词对应的那些数据的id就好了。然后你可以根据这几个id找到对应的数据就可以了,这个就是倒排索引的数据格式以及搜索的方式,上面这种利用倒排索引查找数据的方式,也被称之为全文检索

ElasticSearch2.x版本查询

springboot1.5.18对应 elasticsearch2.4.6版本
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
				.add(QueryBuilders.matchPhraseQuery("name", searchContent),
						ScoreFunctionBuilders.weightFactorFunction(1000))
				.add(QueryBuilders.matchPhraseQuery("description", searchContent),
						ScoreFunctionBuilders.weightFactorFunction(500))
				.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);

		// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
				.withPageable(pageable)
				.withQuery(functionScoreQueryBuilder).build();

在更高的ES5.x、6.x 中 QueryBuilders.functionScoreQuery()已取消。

    @Resource(name = "elasticsearchClient")
    private RestHighLevelClient client;


public SearchUserGameResult searchUserGameList(String childId, Integer month, Integer abilityItem, Integer signStatus, Integer starStatus, Integer previousStatus, Integer pageNo, Integer pageSize) {
        //http://xxxx/_doc/1

        SearchUserGameResult result = null;

        SearchRequest searchRequest = new SearchRequest(INDEX_NAME).types(DOC_TYPE);

        List<String> list = Lists.newArrayList();

        // 定义包含的字段
        String[] includeFields = new String[]{"id", "xxx", "xxx", "xxx", "xx"};
        // 不包含的字段
        /// String[] excludeFields = new String[]{};

//        QueryBuilder queryBuilder = QueryBuilders.boolQuery()
//                .filter(QueryBuilders.fuzzyQuery("content", "的"))
//                .filter(QueryBuilders.termQuery("x", "1"))
//                .filter(QueryBuilders.termQuery("x", "5"))
//                .filter(QueryBuilders.termQuery("x", "1"))
//                .filter(QueryBuilders.termQuery("status", "1"))
//                .mustNot(QueryBuilders.existsQuery("rootId"))
//                ;


//        TermsAggregationBuilder builder = AggregationBuilders.terms("cnt").field("topicId")
//                .size(5);

        QueryBuilder queryBuilder = QueryBuilders.boolQuery()
                .filter(QueryBuilders.termQuery("childId", childId))
                .filter(QueryBuilders.termQuery("month", month));

        if (null != abilityItem) {
            ((BoolQueryBuilder) queryBuilder).filter(QueryBuilders.termQuery("abilityItem", abilityItem));
        }


        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /// searchSourceBuilder.aggregation(builder);
        searchSourceBuilder.fetchSource(includeFields, null)
                ///    .sort(new FieldSortBuilder("createAt").order(SortOrder.DESC))
                .from((pageNo - 1) * pageSize).size(pageSize);

        searchSourceBuilder.query(queryBuilder);

        searchRequest.source(searchSourceBuilder);
        logger.info("[xxx]request:\n" + JSONObject.parseObject(searchSourceBuilder.toString()));
        SearchResponse response;
        try {
            response = client.search(searchRequest, RequestOptions.DEFAULT);
            logger.info("[xx]response==" + JSONObject.toJSONString(response));

            SearchHits hits = response.getHits();
            Integer totalHits = (int) hits.getTotalHits();

            for (SearchHit hit : hits) {
                String json = hit.getSourceAsString();
             

                list.add(userGame);
            }

            
        } catch (Exception e) {
            logger.error("[xxx] exception e:{}", e);
        }

        logger.info("[xxxx]result:{}", JSONObject.toJSONString(result));
        return result;
    }

 

自动选举

- **master-slave**: elasticsearch集群一旦建立起来以后,会选举出一个master,其他都为slave节点。 但是具体操作的时候,每个节点都提供写和读的操作。就是说,你不论往哪个节点中做写操作,这个数据也会分配到集群上的所有节点中。

- **replicate**: 这里有某个节点挂掉的情况,如果是slave节点挂掉了,那么首先关心,数据会不会丢呢?不会。如果你开启了replicate,那么这个数据一定在别的机器上是有备份的。别的节点上的备份分片会自动升格为这份分片数据的主分片。这里要注意的是这里会有一小段时间的yellow状态时间。

- **防止脑裂(discovery.zen.minimum_master_nodes)**: 设置参数: discovery.zen.minimum_master_nodes 为3(超过一半的节点数),那么当两个机房的连接断了之后,就会以大于等于3的机房的master为主,另外一个机房的节点就停止服务了。

- **负载均航(RESTful API)** :对于自动服务这里不难看出,如果把节点直接暴露在外面,不管怎么切换master,必然会有单节点问题。所以一般我们会在可提供服务的节点前面加一个负载均衡。

脑裂恢复

集群脑裂发生后,需要的是恢复。此时,重启集群是十分危险的。当elasticsearch 集群启动时, 会选出一个主节点( 一般是启动的第一个节点被选为主) 。 由于索引的两份拷贝已经不一样了, elasticsearch 会认为选出来的主保留的分片是“主拷贝”并将这份拷贝推送给集群中的其他节点。 这很严重。 让我们设想下你是用的是 node 客户端并且一个节点保留了索引中的正确数据。 但如果是另外的一个节点先启动并被选为主, 它会将一份过期的索引数据推送给另一个节点, 覆盖它, 导致丢失了有效数据。

所以怎么从脑裂中恢复?

第一个 :建议是给所有数据重新索引。

第二个 :如果脑裂发生了, 要十分小心的重启你的集群。 停掉所有节点并决定哪一个节点第一个启动。

(1)如果需要, 单独启动每个节点并分析它保存的数据;

(2) 如果不是有效的, 关掉它, 并删除它数据目录的内容( 删前先做个备份) ;

(3)如果你找到了你想要保存数据的节点, 启动它并且检查日志确保它被选为主节点;

Elasticsearch是如何实现Master选举的?

  1. 对所有可以成为master(node.master: true)的节点根据nodeId排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
  2. 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举。
  3. 对于brain split问题,需要把候选master节点最小值(discovery.zen.minimum_master_nodes)设置为可以成为master节点数n/2+1(quorum )

下面以一个图(图片来自于elasticsearch官网)来说明:

假设之前选举了A节点为master,两个switch之间突然断线了,这样就分词了两部分。CDE和AB,因为 minimumMasterNodes的数目为3(集群中5个节点都可以成为master,3=5/2+1),因此cde会可以进行选举假设C成为master。AB两个节点因为少于3所以无法选举,只能一直寻求加入集群,要么线路连通加入到CDE中要么就一直处于寻找集群状态,这样就保证了集群不分裂。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值