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;
}