端口 9300 组件间通信,9200 http界面端口,5601 kibana界面
基本操作
es不支持修改索引信息
### 创建索引shopping,es中的索引类似于关系数据库中的库
curl -X PUT http://localhost:9200/shopping
### 查看索引
curl -X GET http://localhost:9200/shopping
### 查看所有索引
curl -X GET http://localhost:9200/_cat/indices?v=true
### 向索引添加文档,es中文档即为具体的数据,指定id为1001,不指定id则由es自动生成
curl -X POST -H "Content-Type: application/json" \ # 因为操作是幂等性的,也可以使用 PUT
-d '{"name":"张三","age":18}' \
http://localhost:9200/shopping/_create/1001 # 此处的 _create 可以换成 _doc,但是使用 _create 时语义更加明确
### 查看文档数据
curl -X GET http://localhost:9200/shopping/_doc/1001?pretty=true
### 返回所有文档数据
curl -X GET http://localhost:9200/shopping/_search?pretty=true
### 完全覆盖更新,具有幂等性,可使用PUT
curl -X PUT -H "Content-Type: application/json" \
-d '{"name":"xxx", "age":33}' \
http://localhost:9200/shopping/_doc/1001
### 局部更新,不具有幂等性
curl -X POST -H "Content-Type: application/json" \
-d '{"doc":{"name":"李四"}}' \
http://localhost:9200/shopping/_update/1001
### 删除文档
curl -X DELETE http://localhost:9200/shopping/_doc/1001
更新文档
# 2 种更新方法
### 1. 发送部分文档,多人同时修改可能覆盖,可通过版本号进行控制
### 使用 upsert 可以在文档不存在时创建文档
curl -XPOST 'http://localhost:9200/get-together/_update/xxx'
-H 'Content-Type: application/json'
-d '{
"doc": {
"age": 20
},
"upsert": {
"name": "John Doe",
"age": 18
}
}'
### 2. 使用Painless脚本更新
curl -XPOST 'http://localhost:9200/get-together/_update/xxx'
-H 'Content-Type: application/json'
-d '{
"script": {
"source": "ctx._source.age += params.age_gap",
"params": {
"age_gap": 5
}
}
}'
打开关闭文档
### 关闭索引,索引保存在磁盘上,关闭索引可以减少内存占用
curl -XPOST 'http://localhost:9200/get-together/_close'
### 打开索引
curl -XPOST 'http://localhost:9200/get-together/_open'
使用 match 进行匹配时默认使用分词匹配倒排索引,只要部分字词匹配目标字段即匹配成功。如:使用“小华”进行匹配时即可匹配“小米”又可以匹配“华为”。
### 创建索引
curl -XPUT 'http://localhost:9200/accounts'
### 导入 json 数据
curl -XPOST 'http://localhost:9200/accounts/_bulk' \
-H 'Content-Type: application/json' \
--data-binary @D:\\data\\es_accounts.json
###
curl -XGET 'http://localhost:9200/accounts'
###
curl -XGET 'http://localhost:9200/_cat/indices?v'
###
curl -XGET 'http://localhost:9200/accounts/_search?q=gender:M'
### 匹配查询,分页查询,指定返回字段,排序
curl -XGET -H "Content-Type: application/json" 'http://localhost:9200/accounts/_search' \
-d '{
"query": {
"match": {
"gender": "M"
}
},
"from": 0,
"size": 5,
"_source": ["age", "firstname"],
"sort": {
"age": {
"order": "desc"
}
}
}'
### 组合查询
# must表示与,should表示或
curl -XGET -H "Content-Type: application/json" "http://localhost:9200/accounts/_search" \
-d '{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"gender": "F"
}
}
],
"filter": {
"range": {
"age": {
"gte": 20,
"lte": 40
}
}
}
}
},
"highlight": {
"fields": {
"firstname": {}
}
}
}'
### 聚合操作
# 通过 age 字段进行聚合;指定size为0,表示不返回文档,只返回聚合结果
curl -XGET -H "Content-Type: application/json" "http://localhost:9200/accounts/_search" \
-d '{
"aggs": {
"age_group": {
"terms": {
"field": "age"
}
}
},
"size": 0
}'
### 平均值
curl -XGET -H "Content-Type: application/json" "http://localhost:9200/accounts/_search" \
-d '{
"aggs": {
"age_avg": {
"avg": {
"field": "age"
}
}
},
"size": 0
}'
别名
### 查看所有别名
curl -XGET 'http://localhost:9200/_aliases'
### 查看别名
curl -XGET 'http://localhost:9200/users/_alias/'
### 创建别名
curl -XPOST 'http://localhost:9200/_aliases'
-H 'Content-Type: application/json'
-d '{
"actions": [
{
"add": {
"index": "users",
"alias": "all_users"
}
}
]
}'
### 删除别名
curl -XPOST 'http://localhost:9200/_aliases'
-H 'Content-Type: application/json'
-d '{
"actions": [
{
"remove": {
"index": "users",
"alias": "all_users"
}
}
]
}'
并发修改:多个请求同时修改数据,一般使用加锁解决问题。es常用乐观锁
###
curl -XPUT 'http://localhost:9200/users'
###
curl -H 'Content-Type: application/json' -XPUT 'http://localhost:9200/users/_create/1' -d '{
"name": "zhangsan",
"age": 18
}'
# 创建请求的返回如下,当数据被修改后version 和 seq_no 会改变
{
"_index": "users",
"_id": "1",
"_version": 1, # 早期使用,从1开始,每次修改时增加
"result": "created",
"_shards": {
},
"_seq_no": 0, # 推荐,从0开始,每次修改时增加
"_primary_term": 1 # 表示文档所在主分片的编号
}
### 查询数据
curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/users/_doc/1'
# 响应
{
"_index": "users",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "zhangsan",
"age": 18
}
}
### 通过请求参数 version 设置version,当不符合时修改失败,version是早期使用的参数
curl -H 'Content-Type: application/json' -XPOST 'http://localhost:9200/users/_update/1?version=1' -d '{
"age": 19
}'
### 现在推荐使用 if_seq_no if_primary_term 指定版本
curl -H 'Content-Type: application/json' -XPOST 'http://localhost:9200/users/_update/1?if_seq_no=0&if_primary_term=1' -d '{
"age": 19
}'
还可以使用外部字段进行版本控制
指定操作索引的范围
curl -XGET 'http://localhost:9200/_search' -d ''
curl -XGET 'http://localhost:9200/_all/_search' -d ''
curl -XGET 'http://localhost:9200/*/_search' -d ''
curl -XGET 'http://localhost:9200/get-together/_search' -d ''
curl -XGET 'http://localhost:9200/get-together, other/_search' -d ''
curl -XGET 'http://localhost:9200/+get-to*, -get-together/_search' -d ''
JAVA 操作 ES
package org.example.dir01;
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.bulk.CreateOperation;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Main {
private static RestClientTransport transport = null;
private static ElasticsearchClient client = null;
private static ElasticsearchAsyncClient asyncClient = null;
private static String INDEX_NAME = "index_test_1";
public static void main(String[] args) throws Exception {
init();
// operateIndex();
// operateIndexLambda();
// operateDocument();
transport.close();
}
private static void init() throws IOException {
RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200, "http")).build();
transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
client = new ElasticsearchClient(transport); // 进行同步操作
asyncClient = new ElasticsearchAsyncClient(transport); // 进行异步操作
}
private static void operateIndex() throws Exception {
// 拿到索引客户端对象
final ElasticsearchIndicesClient indices = client.indices();
// 判断索引是否存在
ExistsRequest existsRequest = new ExistsRequest.Builder().index(INDEX_NAME).build();
boolean isExists = indices.exists(existsRequest).value();
if (isExists) {
System.out.println("索引已存在:" + INDEX_NAME);
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest.Builder().index(INDEX_NAME).build();
boolean deleted = indices.delete(deleteIndexRequest).acknowledged();
if (!deleted) {
System.out.println("删除已存在索引失败:" + INDEX_NAME);
throw new RuntimeException("删除已存在索引失败:" + INDEX_NAME);
}
System.out.println("删除已存在索引成功:" + INDEX_NAME);
}
// 创建索引
CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder().index(INDEX_NAME).build();
CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
System.out.println("创建索引的响应:createIndexResponse = " + createIndexResponse);
// String index = createIndexResponse.index();
// System.out.println("创建索引成功:" + index);
GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index(INDEX_NAME).build();
GetIndexResponse getIndexResponse = indices.get(getIndexRequest);
System.out.println("查询的响应结果:getIndexResponse = " + getIndexResponse);
IndexState indexState = getIndexResponse.get(INDEX_NAME);
System.out.println("创建索引的结果:indexState = " + indexState);
}
private static void operateIndexLambda() throws Exception {
// 拿到索引客户端对象
final ElasticsearchIndicesClient indices = client.indices();
// 判断索引是否存在
boolean isExists = indices.exists(req -> req.index(INDEX_NAME)).value();
if (isExists) {
System.out.println("索引已存在:" + INDEX_NAME);
boolean deleted = indices.delete(req -> req.index(INDEX_NAME)).acknowledged();
if (!deleted) {
System.out.println("删除已存在索引失败:" + INDEX_NAME);
throw new RuntimeException("删除已存在索引失败:" + INDEX_NAME);
}
System.out.println("删除已存在索引成功:" + INDEX_NAME);
}
// 创建索引
CreateIndexResponse createIndexResponse = indices.create(req -> req.index(INDEX_NAME));
System.out.println("创建索引的响应:createIndexResponse = " + createIndexResponse);
GetIndexResponse getIndexResponse = indices.get(req -> req.index(INDEX_NAME));
System.out.println("查询的响应结果:getIndexResponse = " + getIndexResponse);
IndexState indexState = getIndexResponse.get(INDEX_NAME);
System.out.println("创建索引的结果:indexState = " + indexState);
}
private static void operateDocument() throws Exception {
// 增加文档
User user = new User("1001", "张三", 18);
CreateRequest<User> createRequest = new CreateRequest.Builder<User>()
.index(INDEX_NAME)
.id("1001")
.document(user)
.build();
CreateResponse createResponse = client.create(createRequest);
// 批量添加
List<BulkOperation> bulkOperationList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
CreateOperation<User> createOperation = new CreateOperation.Builder<User>()
.index(INDEX_NAME)
.id("200" + i)
.document(new User("100" + i, "张三" + i, 18 + i))
.build();
BulkOperation bulkOperation = new BulkOperation.Builder()
.create(createOperation)
.build();
bulkOperationList.add(bulkOperation);
}
BulkRequest bulkRequest = new BulkRequest.Builder()
.operations(bulkOperationList)
.build();
BulkResponse bulkResponse = client.bulk(bulkRequest);
System.out.println("批量创建响应:bulkResponse = " + bulkResponse);
// 文档删除
DeleteRequest deleteRequest = new DeleteRequest.Builder()
.index(INDEX_NAME)
.id("1001")
.build();
DeleteResponse deleteResponse = client.delete(deleteRequest);
System.out.println("删除文档的响应:deleteResponse = " + deleteResponse);
}
private static void operateDocumentLambda() throws Exception {
// 增加文档
Result result = client.create(
req -> req.index(INDEX_NAME)
.id("1001")
.document(new User("1001", "张三", 18))
).result();
System.out.println("创建结果:result = " + result);
// 批量添加
List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
userList.add(new User("100" + i, "张三" + i, 18 + i));
}
BulkResponse bulkResponse = client.bulk(
req -> {
userList.forEach(user -> {
req.operations(
builder -> builder.create(
d -> d.index(INDEX_NAME)
.id(user.getId())
.document(user)
)
);
});
return req;
}
);
System.out.println("批量创建响应:bulkResponse = " + bulkResponse);
// 文档删除
DeleteResponse deleteResponse = client.delete(
req -> req.index(INDEX_NAME).id("1001")
);
System.out.println("删除文档的响应:deleteResponse = " + deleteResponse);
}
private static void queryDocument() throws Exception {
MatchQuery matchQuery = new MatchQuery.Builder()
.field("age").query(19)
.build();
Query query = new Query.Builder()
.match(matchQuery)
.build();
SearchRequest searchRequest = new SearchRequest.Builder()
.query(query)
.build();
SearchResponse<User> userSearchResponse = client.search(searchRequest, User.class);
System.out.println("userSearchResponse = " + userSearchResponse);
}
private static void queryDocumentLambda() throws Exception {
SearchResponse<User> userSearchResponse = client.search(req -> {
req.query(
q -> q.match(
m -> m.field("age").query(19)
)
);
return req;
}, User.class);
System.out.println("userSearchResponse = " + userSearchResponse);
}
// 异步与同步最大的区别就是多线程
private static void asyncOpt() {
asyncClient.indices().create(req -> req.index("newindex")).whenComplete((resp, err) -> {
System.out.println("回调方法,在另外一个线程中执行");
if (err != null) {
System.out.println("创建索引失败:" + err.getMessage());
return;
}
System.out.println("创建索引成功:" + resp.index());
});
System.out.println("主线程执行");
}
}
映射
类似关系数据库中的表结构,
### mapping 创建映射,可在通过GET查看
curl -XPUT -H "Content-Type: application/json" "http://localhost:9200/users/_mapping"
-d '{
"properties": {
"name": {
"type": "text", # text类型会被分词,可以通过部分关键字搜索到
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"gender": {
"type": "keyword", # keyword类型不会被分词
"index": true # 建立索引
},
"tel": {
"type": "keyword",
"index": false # 不能够被搜索
},
"address": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}'
集群
集群名称默认 elasticsearch,通过指定名字确认、加入集群。节点的名称在启动时随机生成,可通过配置文件指定。通过配置elasticsearch.yml 文件配置集群。
cluster.name: my-application # 所有节点必须相同
node.name: node-1 # 每个节点必须唯一
node.master: true
node.data: true
network.host: 192.168.0.1
http.port: 9200
transport.tcp.port: 9301 # 集群间通信端口
# 配置集群
discovery.seed_hosts: ["localhost:9301","localhost:9302","localhost:9303"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
# 设置允许跨域
http.cors.enabled: true
http.cors.allow-origin: "*"
查看集群
curl -XGET http://localhost:1001/_cluster/health
curl -XGET http://localhost:1001/_cat/nodes
进阶
分片:一个索引存储的数据量可能超出了单个节点的限制,es提供将索引划分为多份的能力,每一份称为一个分片。分片数量可以在创建索引时指定。每个分片本身也是一个完善且独立的“索引”,其可放置在集群任何一个节点上。
- 分片允许水平分割、拓展内容容量
- 分片上可进行分布式的、并行的操作,进而提高吞吐量
副本(Replicas):节点可能宕机,为此将一个分片复制多份并放在不同节点上。
- 副本提高了可用性
- 副本提高了搜索的并行度
集群中有一个master负责不同机器间的调度,但数据的具体操作不由master决定。扩容时分片会自动在节点间重新分配。
### 创建一个索引,有3个分片,1个副本
curl -H 'Content-Type: application/json' -XPUT 'http://localhost:9200/users'
-d '{
"settings": {
"numbers_of_shards": 3,
"numbers_of_replicas": 1
}
}'
分片数量在索引创建时确定,但副本数可以动态修改
curl -H 'Content-Type: application/json' -XPUT 'http://localhost:9200/users/_settings'
-d '{
"settings": {
"numbers_of_replicas": 2
}
}'
写:存储数据时通过计算数据hash并对分片取余而确定路由。
读:用户可以访问任何一个有主分片或副本的节点读取数据,这个节点称为协调节点。一般采用轮询操作。
写数据
默认等待副本完成同步后再返回成功响应,可通过请求参数设置异步完成副本复制。
consistency: one(只需主分片完成) all(所有分片完成写入) quorum(默认数量完成写入)
timeout: 30s
读操作
更新文档
- 客户端发送请求
- 经过路由确定处理请求的主分片
- 查询数据并做更新
- 同步到副本
写入延迟
持久化与日志
ES选择先写入内存,再写入日志。因为es有分词等复杂操作,如果先写日志再写内存,当宕机时会有大量无效日志。
写入内存的数据无法被搜索,必须在被refresh到磁盘缓存后才能被读取。
数据以segment的格式进行存储,是不可修改的。通过追加的方式完成增删改,需要定期合并磁盘中的段。
文档分析
分析工作主要包括:将一块文本分为适合倒排索引的独立词条,将词条统一化为标准格式以提高“可搜索性”。
分析器主要有3个功能:
- 字符过滤器,替换HTML元素、&等特殊字符
- 将字符串分为单个词条
- Token过滤器,可能改变Token,如将token小写,去除a an 等无用token,或增加词条
### 测试分词器
curl -XGET 'http://localhost:9200/users/_analyze?pretty' -d '{
"analyzer": "standard", # ik_max_word ik_smart
"text": "这里输入需要进行分词测试的语句"
}'
为了支持中文需要手动下载ik分词器并放入es的plugin文件夹中,ik下的config目录中可以自定义词典文件。
ES 优化
硬件
es的索引和文档都是存储在本地磁盘上,其性能严重依赖磁盘:
- 使用SSD
- 使用 RAID0,条带化技术可加快磁盘IO,无需镜像或奇偶校验、es自带此功能
- 使用多块磁盘,在配置文件中配置多个 path.data 允许使用数据条带技术
- 不使用远程挂载存储
分片
es可通过分片和索引加速加大访问并行度,因为访问时的路由策略,索引创建后无法修改分片数。如果分片数过大也有代价:
- 一个分片底层就是一个Lucene索引,会消耗一定的文件句柄、内存、cpu运转
- 每个索引请求都要命中一个索引分片,如果多个分片在同一个节点上竞争使用相同资源就很糟糕
- 计算相关的的词项统计是基于分片的,如果有多个分片而每个都只有很少的数据,会导致低相关度
一般遵循以下配置:
- 控制每个分片占用的硬盘容量不超过ES的最大VM的堆空间设置(一般设置不超过32G,参考下文的VM设置原则),因此,如果索引的总容量在500G左右,那分片大小在16个左右即可;当然,最好同时考虑原则2。
- 考虑一下node数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以,一般都设置分片数不超过节点数的3倍。
- 主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:
节点数 <= 主分片数*(副本数+1)
写入速度优化
默认设置兼顾可靠性、实时性、写入速度等。实际使用时要根据要求进行偏向化设置。针对搜索性能要求不高,但写入要求较高的场景,尽可能选择写优化场景。
- 加大 Translog Flush,用于降低 IOops、Writeblock
- 增加 Index Refresh 间隔,用于减少 Segment Merge 次数
- 调整 Bulk 线程池和队列
- 优化节点间任务分布
- 优化底层Luece索引,降低CPU和IO
- 对于大量数据使用批量数据操作 Bulk API
题
es集群的选举流程
- Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPc来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要pi血g通)这两部分
- 对所有可以成为master的节点 (node,master:tnue) 根据nodeld字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
- 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
- master节点的职责主要包括集群、节点和索引的管理,不负贵文档级别的管理;data节点可以关闭hp功能。
- 网络问题:集群间的网络延迟导致一些节点访问不到master,认为master挂掉了从而选举出新的master,并对master上的分片和副本标红,分配新的主分片
- 节点负载:主节点的角色既为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
- 内存回收:data节点上的ES进程占用的内存较大,引发VM的大规模内存回收,造成Es进程失去响应。
集群“脑裂”解决
- 减少误判:
discovery.zen.ping timeout节点状态的响应时间
,默认为3s,可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如6s,discovery.zen-ping_timeout:.6),可适当减少误判。 - 选举触发:
discovery.zen.minimum_master_nodes:1
该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(2)+1,n为主节点个数(即有资格成为主节点的节点个数)
- 角色份离:即master节点与data节点分离,限制角色
- 主节点配置为:node.master:true node.data:false
- 从节点配置为:node.master:false node..data:tue
es中删除和更新文档的流程
- 删除与更新也是写操作,但是es中文档是不可变的
- 删除操作:磁盘生每个段有一个相应的.del文件,接收删除请求后在del文件中将数据标记为删除,查询时依旧能够匹配但是在结果中会被过滤掉。当合并段时del文件中的数据不会被合并
- 更新操作:生成新文档时为其生成一个版本号,执行更新时旧版本文档在del文件中标记为删除,新版本文档被索引到一个新的段。旧文档依旧能匹配查询,但会在结果中过滤掉
ES 文档评分机制
Lucene和ES的得分机制基于词频和逆文档词频公式,简称 TF-IDF 公式。
ES 实现
https://mp.weixin.qq.com/s/WviiVaYJpRDx8AVGxGWO8Q
https://mp.weixin.qq.com/s/wnOZl68GlEw34Ms9obtshw
- 词条:索引中最小存储和查询单元
- 词典:词条的集合,一般使用B+或HashMap存储
- 倒排索引:通过词典查询数据id,再通过id查询数据
标签:http,9200,索引,ElasticSearch,curl,节点,localhost From: https://www.cnblogs.com/zhh567/p/17445382.html