elasticsearch 集群配置和使用

es 集群

这一篇是上一篇的补充;上一篇地址

集群配置

单机安装前面我讲过了,这里我就直接上配置文件了有不清楚的看上篇

cluster.name: whjes
node.name: node3
node.master: true
node.data: true
path.data: /usr/local/es/elasticsearch-7.6.1/data
path.logs: /usr/local/es/elasticsearch-7.6.1/log
#network.host: 0.0.0.0
network.bind_host: 内网ip
network.publish_host: 外网ip3
http.port: 9200
discovery.seed_hosts: ["外网ip1", "外网ip2", "外网ip3"]
discovery.zen.minimum_master_nodes: 2
cluster.initial_master_nodes: ["node1", "node2", "node3"]
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
http.cors.enabled: true
http.cors.allow-origin: "*"

上面的配置比单机配置多了那些?
node.master: 指定当前节点是否可以为主节点; node.data: 指定当前是否可以为数据节点;当不设置是默认为true;
network.bind_host: 如果是云服务器那么这里配置内网ip,如果是本地那么使用network.host 即可,
network.publish_host: 这个的配置参数同上个参数;
discovery.seed_hosts:这个参数由之前一个ip ,变成所有ip的集合;
discovery.zen.minimum_master_nodes: 选举主节点的参数(这个后面我会详细的讲)
cluster.initial_master_nodes:这个填写所有可以成为主节点的节点名集合,单机的情况只能填写自己;


es架构原理

**1.elasticsearch 的节点 **
1).Master 节点: 当es 启动的时候会根据 discovery.seed_hosts: [“IP1”, “IP2”, “IP3”] 这项配置跟你所有的节点建立连接;然后根据cluster.initial_master_nodes: [“node1”, “node2”, “node3”] 这项配置从中选出一个 master 节点;
Master 节点主要负责:创建和删除索引,维护元数据,管理各个节点的状态;
2).Data数据节点:在集群中有N个数据节点;用来存储检索删除添加等一些列的对数据的操作;集群中压力基本都在数据节点中;所以在配置的时候也需要酌情考虑;
2.分片和副本策略
1).分片: 就是分布在不同服务节点的索引数据,es会自动管理分片,发现不均匀就会自动迁移,一个索引有多个分片组成,分片在不同的服务节点
2).副本: 为了es分片进行容错;当一个节点的分片出现了问题这个时候就需要副本来替代掉;这样不会影响使用;es 创建索引时默认创建一个分片,每一个分片包含一个主分片和一个副分片;
分片数可以自行设定;每个分片都会有一个主分片和若干个副本分片组
设置分片和副本如下

//分片三个分本分片两个
PUT /test
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  }
}

可以安装一个工具查看分片的情况: elasticsearch-head
**安装elasticsearch-head **
1.第一步安装node环境

# 安装node 的最简单方式就是使用yum 但是又centos版本找不仓库如果可以找到使用下面的方式
yum install -y nodejs
# 上面找不到那么你只能按照下面的方式
#1.下载安装包
wget https://npm.taobao.org/mirrors/node/v8.0.0/node-v8.0.0-linux-x64.tar.gz
#2.解压文件 并指定放在 /usr/local/es的目录下
tar -zxvf node-v8.0.0-linux-x64.tar.gz -C /usr/local/es
#3.进入编辑 etc/profile 下配置环境
vi /etc/profile
# 在文件下添加 export NODE_HOME=/usr/local/es/node-v8.0.0-linux-x64 export PATH=:$PATH:$NODE_HOME/bin 保存 NODE_HOME 是你的安装路径
# 重新刷新文件
source /etc/profile
# 测试安装是否成功查看版本号
node -v 

2.准备个安装包 elasticsearch-head-compile-after.tar.gz 放在上一篇中记录的 /usr/local/es 目录下然后进行解压配置

# 解压
tar -zxvf elasticsearch-head-compile-after.tar.gz -C /usr/local/es
# 进入插件的目录下打开Gruntfile.js 这个文件
cd /elasticsearch-head
vim GruntFile.js

找到代码中的93行左右如下代码:hostname: ‘192.168.100.100’, 修改为:自己的ip 或者域名(云服务使用内网ip)

connect: {
    server: {
        options: {
            hostname: '192.168.100.100',
            port: 9100,
            base: '.',
            keepalive: true
        }
    }
}

在修改app.js

# 修改上面的参数后保存进入_site 下找到app.js
cd _site
vim app.js
#在Vim中输入「:4354」,定位到第4354行,修改 http://localhost:9200为http://es的ip:9200或者es的域名(云服务外网ip)
cd /usr/local/es/elasticsearch‐head/node_modules/grunt/bin/
#启动命令
nohup ./grunt server >/dev/null 2>&1 &

启动好后使用http://ip:9100访问;这个插件没有设置登录页面那么复杂,这个插件vue 开发一般在使用的时候的环境可以是本地查看一下,或者把这个插件改造一下打包通过nginx 代理直接使用不需要这么麻烦,都是可以的,自行研究;
通过安装好了这个插件就可以清楚的看到创建的test的索引的分片情况如下
在这里插入图片描述
由图的node1 ,node2,node3三个节点且node2为主节点
分别由0,1,2三个分片;在node1节点上0是主分片,其他为副本分片,在node2节点上1是主分片,其他1为副本分片;同理的node3;
3.集群的检索原理和写入原理
1).写入原理按照上面的test索引写入;并且把node1 为master 节点其他为 DataNode系欸但
在这里插入图片描述
1.选择任意一个DataNode发送请求,例如:node2。此时,node2就成为一个
coordinating node(协调节点)
2.计算得到文档要写入的分片
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的 _id
3.coordinating node会进行路由,将请求转发给对应的primary shard所在的
DataNode
4.node1节点上的Primary Shard处理请求,写入数据到索引库中,并将数据同步到
Replica shard
5.Primary Shard和Replica Shard都保存好了文档,返回client
2).检索的原理在这里插入图片描述
1.客户端发送一个请求,会选择一个DataNode 节点作为coordinating node (协调节点);
2.协调节点会将,查询的请求广播到每一个数据节点进行查询;
3.每一个数据节点的分片进行查询后的结果;返回给协调节点;
4.协调节点进行汇总;并排序后返回给客户端
elasticsearch 准实时搜索的实现
1). 当数据写入es 分片时,首先会先写入缓存中,然后会通过一个内存buffer 生成一个segment ,并刷到文件系统缓存中,这时候就会被检索到;这个refresh 每秒执行一次(所以叫准实时)
2).在数据刷到缓存的同时会记录一个translog 的日志,当refresh出现错误的时候会根据日志来恢复数据;当数据别提交到磁盘的时候那么就会同时清理这个日志;
3).flush 默认每个三十分钟执行一次将缓存的数据刷到磁盘
4).随着数据的怎加可能会出现很多的segment;这个时候es 会触发segment 的合并;这样会减少在检索的时候IO的开销;而且这个阶段会真正的物理删除;(删除此前执行过的delete 的数据)
根据如上的解释得到下面的如图
在这里插入图片描述

集群的问题

生产集群中,如果配置少了discovery.zen.minimum_master_nodes 这项参数可能会出现脑裂的问题,那什么是脑裂呢?
故名思意脑裂就是大脑出现了分裂,也就是出现了多个大脑,出现两个或者多个master节点;当出现两个master 节点的时候可能会出现一下问题:
数据检索时数据的完整性,创建和删除索引的操作等都无法保证
一般什么情况会出现脑裂的问题呢?
当集群通信出现异常的时候可能会将集群分成两个部分比如上面的图为例node1 和node2,node3分成两个部分node1 独立,node2,node3成一部分;由于部分一有master 节点不会变化(node1 master 节点),但由于通信的问题被分成的第二部分master节点,这时就需要在node2 和node3 中选取出一个节点作为master 节点(比如选举node2 作为master );这时候就会出现两个master节点;当发送一个检索请求时,当请求在node1 上那么node2是无感知这个节点的所以会出现检索数据不完整;
通过discovery.zen.minimum_master_nodes 这个参数配置就可以解决脑裂的问题但是配置多少呢?假设可以为master 的节点集群有N台机器那么参数就是N/2+1 这样就可以避免脑裂的问题但是集群的最小数量那么也就固定了3台;因为master 节点根据这个配置最小是需要2台来进行选举;

通过api 直接使用es

1.向项目中导入mvn

<!-- es 客户端操作类的导入-->
  		<dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.6.1</version>
        </dependency>
        <!-- json 数据类型的操作-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <!--用于书写测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

2.介绍几个关键类—— 封装请求的数据类型
IndexRequest :添加数据封装类
UpdateRequest: 修改的类
GetRequest: 通过id查找的分装类
DeleteRequest: 删除
SearchRequest : 组合查询的类
SearchScrollRequesst: 使用scrollId分页查询类

3. 示例展示——这里只有关键的代码
1).初始化连接池

	// 根据自己的集群填写ip
    private RestHighLevelClient restHighLevelClient;
    public EsCarServiceImpl() {
        RestClientBuilder restClientBuilder= RestClient.builder(
                new HttpHost("Ip1",9200),
                new HttpHost("Ip2",9200),
                new HttpHost("Ip3",9200)
        );
        restHighLevelClient=new RestHighLevelClient(restClientBuilder);
    }

2).通过IndexRequest 向es 里添加数据

//CAR为的定义的es 文档名
    public MsgReturn add(Car car) throws  Exception {
    	//封装数据
        IndexRequest indexRequest=new IndexRequest(CAR);
        indexRequest.id(String.valueOf(car.getId()));
        String JSON =JSONObject.toJSONString(car);
        indexRequest.source(JSON, XContentType.JSON);
        //发送请求
        restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        return  MsgReturn.getsussess();
    }

3).通过GetRequest 查找数据

    public MsgReturn findbyId(String id) throws IOException {
    	//CAR 文档名 ,id就是kibana 语句里根据id查询的id
        GetRequest getRequest=new GetRequest(CAR, id);
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        String sourceAsString = getResponse.getSourceAsString();
        Car car=JSONObject.parseObject(sourceAsString,Car.class);
        return MsgReturn.getsussess(car);
    }

4). 通过UpdateRequest修改数据

public MsgReturn update(Car car) throws Exception {
       
        GetRequest getRequest=new GetRequest(CAR, String.valueOf(car.getId()) );
        boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
         //首先判断有没有值
         if(exists){
             UpdateRequest updateRequest = new UpdateRequest(CAR, String.valueOf(car.getId()));
             String json=JSONObject.toJSONString(car);
             updateRequest.doc(json, XContentType.JSON);
             UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
             if(update.getResult().toString().equals("UPDATE")){
                 return MsgReturn.getsussess();
             }else if(update.getResult().toString().equals("NOOP")){
                  return MsgReturn.getsussess("没有任何的修改");
             }
         }
        return MsgReturn.geterror();
    }

5).通过DeleteRequest 删除数据

    public MsgReturn deletebyId(String id) throws IOException {
        GetRequest getRequest=new GetRequest(CAR, id);
        boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
        if(exists){
            DeleteRequest deleteRequest = new DeleteRequest(CAR, id);
            DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            String flag=delete.getResult().toString();
            if("DELETED".equals(flag)){
                return  MsgReturn.getsussess("删除id 为:"+id+"的数据");
            }

        }
        return MsgReturn.geterror();
    }

6).根据SearchRequest 和SearchScrollRequest 检索高亮分页聚合等功能

public MsgReturn highlight_aggs_search_scroll(String keywords, String scrollId) throws Exception {
        List<Car> cars=new ArrayList();
        List<Car_Aggs> aggs=new ArrayList();
        Map<String,Object> map =new HashMap();
        SearchResponse search =null;
        if(scrollId==null){
            //当一次查询的时候走这个分支
            SearchRequest searchRequest = new SearchRequest(CAR);
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            boolQueryBuilder.must(QueryBuilders.matchQuery("brand", keywords));
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keywords,"remark" ));
            searchSourceBuilder.query(boolQueryBuilder);
            // 设置高亮
            HighlightBuilder highlightBuilder=new HighlightBuilder();
            highlightBuilder.field("brand");
            highlightBuilder.field("remark");
            highlightBuilder.preTags("<em>");
            highlightBuilder.postTags("</em>");
            searchSourceBuilder.highlighter(highlightBuilder);
            //创建一个分组 根据分组聚合
            TermsAggregationBuilder field = AggregationBuilders.terms("group_by_brand").field("brand");
            field.subAggregation(AggregationBuilders.avg("group_by_brand_avg").field("price"));
            field.subAggregation(AggregationBuilders.sum("group_by_brand_sum").field("price"));
            searchSourceBuilder.aggregation(field);
            //这里设置每页数据我这里数据不多我就设置1个
            searchSourceBuilder.size(1);
            searchRequest.source(searchSourceBuilder);
            // 设置scrollId 有效时间
            searchRequest.scroll(TimeValue.timeValueMillis(5));
            //发送请求
             search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //聚合的数据
            Terms terms= search.getAggregations().get("group_by_brand");
            List<? extends Terms.Bucket> buckets = terms.getBuckets();
            for(Terms.Bucket bucket:buckets){
                String keyAsString = bucket.getKeyAsString();
                Avg avg=bucket.getAggregations().get("group_by_brand_avg");
                Sum sum=bucket.getAggregations().get("group_by_brand_sum");
//                String docCount = String.valueOf(bucket.getDocCount());
                Car_Aggs car_aggs=new Car_Aggs(keyAsString, sum.getValue(), avg.getValue());
                aggs.add(car_aggs);
            }
            map.put("car_aggs",aggs );
        }
        else{
            SearchScrollRequest searchScrollRequest=new SearchScrollRequest(scrollId);
             searchScrollRequest.scroll(TimeValue.timeValueMillis(5));
             search=restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);

        }
        // 接收查询到的数据
       String scrollId1 = search.getScrollId();
        // 查询到的数据
        SearchHit[] hits = search.getHits().getHits();
        for(SearchHit searchHit:hits){
            Car car=JSONObject.parseObject(searchHit.getSourceAsString(),Car.class );
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            HighlightField brand = highlightFields.get("brand");
            HighlightField remark = highlightFields.get("remark");
            String brandtxt=this.get(brand);
            String remarktxt=this.get(remark);
            car.setRemark(remarktxt);
            car.setBrand(brandtxt);
            cars.add(car);
        }

        map.put("cars", cars);
        map.put("scrollId", scrollId1);
        return  MsgReturn.getsussess(map);

    }
        //定义一个内部的方法对数据进行处理
    public  String get(HighlightField field){
         if(field!=null){
             Text[] fragments = field.fragments();
             StringBuilder builder=new StringBuilder();
             for(Text text:fragments){
                 builder.append(text);
             }
             return   builder.toString();
         }
         return null;
    }

还有一种分页使用from ,size 的方式我这里不写自行操作一下都不难;但是通过上面这个示例6,可以看到方法中的重复代码量多可以相对提取一下,比如上面的get方法 提取出重复操作的代码;比如在我们设置的高亮的时候也可以设置一个方法调用方法直接设置;这种方式可以使主线的方法更明了;

spring boot中如何使用

在spring boot 中有几种使用es的方式,在开发中类似于redistemplate 这种模板类,在es 中也有elasticsearchTemplate 模板类,但是我不推荐使用;虽然他封装了很多的类,个人感觉他的方法不够明了,所以在springboot 中我不推荐使用自动配置的es 方式;还是使用官方推荐得Java High Level REST Client;这个跟上面使用得方式是一样得就是配置方面可能有些不一样,为了维护得方便肯定是需要把配置类中连接池中得配置信息写在配置文件中,上代码:
1.pom.xml 文件信息
只对操作es 的关键配置贴出来了;这里为什么我去掉了elasticsearch和elasticsearch-rest-client;这里有个坑就是我们引入的jar包都是7.6.1配套的但是这两个可能不是,启动的时候报错找不到某个类,所以我去除从新引用这两个jar包并指定版本;

 <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.6.1</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</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.6.1</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.6.1</version>
        </dependency>

2.application.yml 配置信息
这个配置自己定义的下面有个配置类读取这个配置信息

es:
  node-uirs: Ip1,Ip2,Ip3
  port: 9200

3.配置类

@Configuration
public class ElastincsearchConfig {
    @Value("${es.port}")
    private  int prot;
    @Value("${es.node-uirs}")
    private  String hosts;
     @Bean
    public RestHighLevelClient restHighLevelClient(){
         String[] split = hosts.split(",");
         HttpHost [] httpHosts=new HttpHost[split.length];
         int i=0;
         for(String host:split){
             httpHosts[i]=new HttpHost(host,prot);
           i++;
         }
         RestClientBuilder builder = RestClient.builder(httpHosts);
         RestHighLevelClient restHighLevelClient=new RestHighLevelClient(builder);

        return restHighLevelClient;
    }
}

剩下如何去使用我想我这里也不用说了;如果想要案例的可以私信我

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值