ES 深入浅出系列(二)ES的存储结构与CRUD汇总

850 阅读6分钟

ES的数据存储结构

Index

在Es中,它的存储结构不再是类似于MySQL的这种关系型存储结构,更多是以索引Index为维度去进行数据落库。

Es底层对于数据的存储是按照索引去划分存放的,这个索引的概念就有点类似于mysql的数据库。一般在es中,每个index都会被分成5个分片存储数据,每个分片会存储不同的数据内容。

例如下边这张图所示:Book索引一共有1亿条记录,由于我们的es做了集群部署,所以不同的es节点存储的数据不同,因此不同的es服务所存储的数据容量也不一样。

每个分片上必须要有对应的备份数据,而且建议不同的分片数据存放在不同的机器上会安全一些。

图片

通常我们可以部署多台es,然后不同的es服务会承载不同的数据内容存放。例如es服务1,它的分片存储5kw数据,es服务2,它的分片存储另外的5kw数据。

同时不同的es服务内部建议配置自己的从分片,用于做数据备份(避免数据丢失,达成高可用)。

type

type是一种比index还要小粒度的存储规格,类似于index是一个数据库,type是里面的数据表。

图片

通常来说索引内部有多个type,不同的type包含不同的document,document可以类比成mysql中的行记录,document中的field可以类比成mysql表中的具体列。

不过这里要注意,在Es6.x版本中,一个index下可以有多种type,但是在Es7.x版本中,一个index下可以没有Type。

基于Kibana对ES的操作记录

Kibanna的devtools面板上,提供了可视化的es操作控制台,如果需要练习es脚本的话,这个面板是一个非常合适的入口。

图片

创建索引

在创建索引的时候,我们可以使用setting去指定创建的数据的分片和备份数,具体的索引内部结构则是通过使用mappings去指定:

PUT /book
{
  "settings": {
     //指定我们的备份数
    "number_of_replicas": 1,
    //指定分片数
    "number_of_shards": 5
  },
  "mappings": {
    //指定type
    "novel": {
      //指定field
      "properties":{
        "name": {
          "type" : "text",
          //指定使用ik分词器
          "analyzer":"ik_max_word",
          //指定当前的field可以作为被查询的条件
          "index": true,
          // 是否需要额外存储
          "store": false
        },
        "author" : {
          "type":"keyword"
        },
        "wordCount" : {
          "type":"long"
        },
        "onSale":{
          "type":"date",
          //时间的格式化方式
          "format":"yyyy-MM-dd || yyyy-MM-dd HH:mm:ss"
        },
        "descr":{
          "type":"text",
          "analyzer":"ik_max_word"
        }
      }
    }
  }
}

新建文档

在es当中,创建文档有两种方式,分别是指定id式创建文档,非指定id式创建文档。这块的区别可以类比成MySQL中是否指定主键id进行数据插入。

  • 自动生成id

在我们操作es插入记录的时候,如果没有指定id,那么默认id就是自动生成的,这种情况下,我们的插入脚本可以如下案例方式编写:

POST /book/novel
{
  "name":"java-learn",
  "author":"idea",
  "descr":"idea first book",
  "wordCount":100000,
  "onSale":"2023-01-01"
}

生成结果中的_id是一个随机字符串,结果如下:

{
  "_index" : "book",
  "_type" : "novel",
  "_id" : "pWDseYoBcWu8nlR3hooO",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}
  • 非自动生成id

通常在业务系统中,大家更加偏向于手动指定id后再往es重插入数据,此时我们的脚本可以仿照下边这段编写:

POST /book/novel/1001
{
  "name":"java-learn",
  "author":"idea",
  "descr":"idea first book",
  "wordCount":100000,
  "onSale":"2023-01-01"
}

这种方式往es中写入的数据,就可以携带一个指定好的id值了,返回的结果中也是带有我们指定好的id值的。

{
  "_index" : "book",
  "_type" : "novel",
  "_id" : "1001",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

修改文档

  • 覆盖式修改

这种方式去覆盖修改文档的话,会将整个记录给覆盖了,导致文档只剩下了name字段。


//需要在url的末尾指定好id值
PUT /book/novel/1001
{
  //直接覆盖修改了原先的文档
  "name":"java-dev"
}
  • doc修改方式

这种方式只会修改部分的字段值,传递的参数如下所示:

POST /book/novel/1001/_update
{
  "doc":{
    "name":"doc-java-2"
  }
}

删除文档

删除的操作就非常简单了,只需要在url中携带相应id即可:

DELETE /book/novel/omDseYoBcWu8nlR3AorZ

其删除结果如下所示:

{
  "_index" : "book",
  "_type" : "novel",
  "_id" : "omDseYoBcWu8nlR3AorZ",
  "_version" : 2,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    // 这里会告诉你是否删除成功
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 9,
  "_primary_term" : 1
}

如果你在测试完这些api之后,希望查看下es的具体数据,那么其实是可以通过kibana面板的数据展示来查看的:

图片

基于Java如何对ES进行CRUD操作

其实在Java中,我们也可以实现对ES的CRUD操作,不过首先我们需要引入相关的两份核心依赖配置。

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

对于ES的操作,更多是建议大家使用RestHighLevel工具包中的API访问,首先我们需要构建一个es的客户端工具类:

public class EsClient {

    public static RestHighLevelClient getClient() {
        HttpHost httpHost = new HttpHost("es服务器的ip地址",9200);
        RestClientBuilder clientBuilder = RestClient.builder(httpHost);
        RestHighLevelClient client = new RestHighLevelClient(clientBuilder);
        return client;
    }
}

接着,我列了一份关于使用这个EsClient完成CRUD的代码案例,供大家参考:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.junit.Test;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author idea
 * @Date: Created in 21:01 2023/9/9
 * @Description
 */
public class EsClientTest {

    private RestHighLevelClient client = EsClient.getClient();
    private String index = "person";
    private String type = "man";
    private ObjectMapper objectMapper = new ObjectMapper();

    @Test
    public void createIndex() throws IOException {
        //1.准备索引的setting配置
        Settings.Builder setting = Settings.builder()
                .put("number_of_shards", 3)
                .put("number_of_replicas", 1);

        //2.准备索引的mappings设置
        XContentBuilder mappings = JsonXContent.contentBuilder()
                .startObject()
                .startObject("properties")
                .startObject("name")
                .field("type", "text")
                .endObject()
                .startObject("age")
                .field("type", "integer")
                .endObject()
                .startObject("birthday")
                .field("type", "date")
                .field("format", "yyyy-mm-dd")
                .endObject()
                .startObject("tel")
                .field("type","long")
                .endObject()
                .endObject()
                .endObject();
        //3.通过highLevelClient去连接es,并且执行创建索引
        CreateIndexRequest request = new CreateIndexRequest(index)
                .settings(setting)
                .mapping(type,mappings);
        //创建索引
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

    @Test
    public void checkExistIndex() throws IOException {
        //准备request对象
        GetIndexRequest getIndexRequest = new GetIndexRequest();
        getIndexRequest.indices(index);
        //通过client对象去发送请求给到es
        boolean exist = client.indices().exists(getIndexRequest,RequestOptions.DEFAULT);
        System.out.println(exist);
        getIndexRequest.indices("test");
        boolean existTestIndex = client.indices().exists(getIndexRequest,RequestOptions.DEFAULT);
        System.out.println(existTestIndex);
    }

    @Test
    public void testDeleteIndex() throws IOException {
        DeleteIndexRequest deleteRequest = new DeleteIndexRequest();
        deleteRequest.indices(index);
        AcknowledgedResponse response = client.indices().delete(deleteRequest,RequestOptions.DEFAULT);
        System.out.println(response);
    }

    @Test
    public void testAddDocument() throws IOException {
        for (int i=2;i<100;i++) {
            PersonPO personPO = new PersonPO((long) i,"idea",18,new Date(),186781273L);
            String json = objectMapper.writeValueAsString(personPO);
            IndexRequest indexRequest = new IndexRequest(index,type,personPO.getId().toString());
            indexRequest.source(json, XContentType.JSON);
            IndexResponse indexResponse = client.index(indexRequest,RequestOptions.DEFAULT);
            System.out.println(indexResponse.getResult().toString());
        }
    }

    @Test
    public void testBatchInsert() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        for (int i=1000;i<1010;i++) {
            PersonPO personPO = new PersonPO((long) i,"idea",18,new Date(),186781273L);
            String json = objectMapper.writeValueAsString(personPO);
            IndexRequest indexRequest = new IndexRequest(index,type,personPO.getId().toString());
            indexRequest.source(json,XContentType.JSON);
            //这里面可以塞入其他类型的request对象,例如deleterequest,updaterequest,从而实现批量操作功能
            bulkRequest.add(indexRequest);
//            bulkRequest.add(new IndexRequest(index,type,personPO.getId().toString()).source(json,XContentType.JSON));
        }
        BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(response.toString());
    }

    @Test
    public void testUpdateDocument() throws IOException {
        Map<String,Object> doc = new HashMap<>();
        doc.put("name","张大三");
        String docId = "1";
        UpdateRequest request = new UpdateRequest(index,type,docId);
        request.doc(doc);
        UpdateResponse update = client.update(request,RequestOptions.DEFAULT);
        System.out.println(update.getResult().toString());
    }

    @Test
    public void testDeleteDocument() throws IOException {
        String docId = "1";
        DeleteRequest deleteRequest = new DeleteRequest(index,type,docId);
        System.out.println(client.delete(deleteRequest).getResult().toString());
    }

    @Test
    public void testConnectEs() {
        RestHighLevelClient restHighLevelClient = EsClient.getClient();
        System.out.println(restHighLevelClient);
    }
}

这段代码中有提到一个叫做Person的对象,该对象是一个映射了person索引的man类型下的一种document结构体。

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @Author idea
 * @Date: Created in 09:33 2023/9/10
 * @Description
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PersonPO {

    @JsonIgnore
    private Long id;
    private String name;
    private Integer age;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
    private Long tel;

}