ElasticSearch学习笔记
Elasticsearch是一个基于Apache Lucene的全文检索和分析引擎,可以扩容到上百台服务器,处理PB级结构化/非结构化数据。
ES的应用场景举例:
- 支持在线搜索、自动完成(搜索建议)功能
- 作为ELK栈的一部分,收集、聚合、分析日志/事务数据
- 海量数据的即席分析
Elasticsearch 尽可能地屏蔽了分布式系统的复杂性,它在后台自动执行的操作包括:
- 分配文档到不同的容器或分片中,文档可以储存在一个或多个节点中
- 按集群节点来均衡分配这些分片,从而对索引和搜索过程进行负载均衡
- 复制每个分片以支持数据冗余,从而防止硬件故障导致的数据丢失
- 将集群中任一节点的请求路由到存有相关数据的节点
- 集群扩容时无缝整合新节点,重新分配分片以便从离群节点恢复
ES是一个准实时(Near Realtime)的搜索平台,从你开始索引一个文档,到该文档可以被搜索,有个较小的延迟,通常秒级。
唯一的依赖是JDK,请预先安装好JDK8(建议1.8.0_131+),然后:
1 2 3 4 5 6 7 8 9 10 11 |
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.3.tar.gz tar xzf elasticsearch-6.2.3.tar.gz mv elasticsearch-6.2.3 6.2.3 # 启动ES,-d以守护进程方式运行,默认端口9200 ./elasticsearch -d # 指定集群和节点名称 ./elasticsearch -Ecluster.name=es.gmem.cc -Enode.name=es-10.gmem.cc # 停止ES kill -SIGTERM $ES_PID |
所有节点、客户端都应该使用一样的JDK版本。
ES的Docker镜像基于CentOS:7。相关镜像的列表参考:https://www.docker.elastic.co/。
镜像分为三种风格:basic包含基本的X-Pack特性,自动激活免费License;platinum包含全部X-Pack特性,默认30天试用;oss不支持X-Pack,仅仅包含ES。
执行下面的命令拉取镜像:
1 2 3 |
docker pull docker.elastic.co/elasticsearch/elasticsearch:6.2.3 docker pull docker.elastic.co/elasticsearch/elasticsearch-platinum:6.2.3 docker pull docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.3 |
参考如下命令部署容器:
1 |
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.2.3 |
内核参数vm.max_map_count至少要增大到262144: sysctl -w vm.max_map_count=262144
参考kubernetes-elasticsearch-cluster搭建集群。注意点:
- ES的Pod需要超级用户权限运行init容器,避免设置某些VM选项。因此你需要以--allow-privileged选项运行kubelet
- ES_JAVA_OPTS的默认值为-Xms256m -Xmx256m,非常小。你可以按需调整
- 数据节点Pod默认在一个emptyDir中存储数据,请根据实际情况修改
- PROCESSORS的默认值为1,如果需要调整,请设置resources.limits.cpu、livenessProbe
- 支持1.9.3+版本的K8S
从下面的仓库下载K8S资源定义文件:
1 2 |
git clone https://github.com/pires/kubernetes-elasticsearch-cluster.git es cd es |
1 2 3 4 5 6 7 |
docker pull quay.io/pires/docker-elasticsearch-kubernetes:6.2.2_1 docker tag quay.io/pires/docker-elasticsearch-kubernetes:6.2.2_1 docker.gmem.cc/elasticsearch-kubernetes:6.2.2 docker push docker.gmem.cc/elasticsearch-kubernetes:6.2.2 docker pull busybox:1.27.2 docker tag busybox:1.27.2 docker.gmem.cc/busybox:1.27.2 docker push docker.gmem.cc/busybox:1.27.2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Master节点服务 kubectl create -f es-discovery-svc.yaml # Data节点服务 kubectl create -f es-svc.yaml # Master节点的Deployment,默认3个Replica,init容器调用sysctl kubectl create -f es-master.yaml # 等待所有Master节点就绪 kubectl -n dev rollout status -f es-master.yaml # Client节点的Deployment,默认2个Replica,init容器调用sysctl kubectl create -f es-client.yaml # 等待所有Client节点就绪 kubectl rollout status -f es-client.yaml # 数据节点,使用本地目录 kubectl create -f es-data.yaml kubectl rollout status -f es-data.yaml # 基于SS的数据节点(推荐) kubectl create -f stateful/es-data-svc.yaml kubectl create -f stateful/es-data-stateful.yaml |
资源规格定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
apiVersion: v1 kind: Service metadata: name: elasticsearch-discovery namespace: dev labels: component: elasticsearch role: master spec: selector: component: elasticsearch role: master ports: - name: transport port: 9300 protocol: TCP |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
apiVersion: v1 kind: Service metadata: name: elasticsearch namespace: dev labels: component: elasticsearch role: client spec: selector: component: elasticsearch role: client ports: - name: http port: 9200 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-master namespace: dev labels: component: elasticsearch role: master spec: replicas: 3 template: metadata: labels: component: elasticsearch role: master spec: initContainers: - name: init-sysctl image: docker.gmem.cc/busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-master image: docker.gmem.cc/elasticsearch-kubernetes:6.2.2 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: gmemes - name: NUMBER_OF_MASTERS value: "2" - name: NODE_MASTER value: "true" - name: NODE_INGEST value: "false" - name: NODE_DATA value: "false" - name: HTTP_ENABLE value: "false" - name: ES_JAVA_OPTS # ES要求堆最大最小值一样 value: -Xms256m -Xmx256m - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 1 ports: - containerPort: 9300 name: transport livenessProbe: tcpSocket: port: transport volumeMounts: - name: storage mountPath: /data volumes: # Pod第一次调度到节点上创建一个空白目录,除非重新调度到其它节点,不会重新创建 - emptyDir: medium: "" name: "storage" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
apiVersion: apps/v1beta1 kind: Deployment metadata: name: es-client namespace: dev labels: component: elasticsearch role: client spec: replicas: 2 template: metadata: labels: component: elasticsearch role: client spec: initContainers: - name: init-sysctl image: docker.gmem.cc/busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-client image: docker.gmem.cc/elasticsearch-kubernetes:6.2.2 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: gmemes - name: NODE_MASTER value: "false" - name: NODE_DATA value: "false" - name: HTTP_ENABLE value: "true" - name: ES_JAVA_OPTS value: -Xms256m -Xmx256m - name: NETWORK_HOST value: _site_,_lo_ - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 1 ports: - containerPort: 9200 name: http - containerPort: 9300 name: transport livenessProbe: tcpSocket: port: transport readinessProbe: httpGet: path: /_cluster/health port: http initialDelaySeconds: 20 timeoutSeconds: 5 volumeMounts: - name: storage mountPath: /data volumes: - emptyDir: medium: "" name: storage |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
apiVersion: v1 kind: Service metadata: name: elasticsearch-data namespace: dev labels: component: elasticsearch role: data spec: ports: - port: 9300 name: transport clusterIP: None selector: component: elasticsearch role: data |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: es-data namespace: dev labels: component: elasticsearch role: data spec: serviceName: elasticsearch-data replicas: 5 template: metadata: labels: component: elasticsearch role: data spec: affinity: podAntiAffinity: # 不得存在role=data的其它pod preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: role operator: In values: - data # 当前节点上 topologyKey: kubernetes.io/hostname initContainers: - name: init-sysctl image: docker.gmem.cc/busybox:1.27.2 command: - sysctl - -w - vm.max_map_count=262144 securityContext: privileged: true containers: - name: es-data image: docker.gmem.cc/elasticsearch-kubernetes:6.2.2 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: CLUSTER_NAME value: gmemes - name: NODE_MASTER value: "false" - name: NODE_INGEST value: "false" - name: HTTP_ENABLE value: "false" - name: ES_JAVA_OPTS value: -Xms256m -Xmx256m - name: PROCESSORS valueFrom: resourceFieldRef: resource: limits.cpu resources: limits: cpu: 1 ports: - containerPort: 9300 name: transport livenessProbe: tcpSocket: port: transport initialDelaySeconds: 20 periodSeconds: 10 volumeMounts: - name: es-data-pvc mountPath: /data volumeClaimTemplates: - metadata: name: es-data-pvc namespace: dev spec: storageClassName: rook-block accessModes: [ ReadWriteOnce ] resources: requests: storage: 4Gi |
1 2 3 4 5 |
helm repo add gmem https://chartmuseum.gmem.cc helm install --name=es --namespace=kube-system gmem/elasticsearch # 检查集群健康状态 curl http://es-elasticsearch.kube-system.svc.k8s.gmem.cc:9200/_cat/health?v |
一系列节点的集合,它们在整体上持有完整的数据集,提供联合的索引(Federated indexing),并在整体上对外提供搜索功能。
每个集群以名字来识别,默认名字elasticsearch,每个节点只能属于单个集群。如果部署多套集群,注意确保集群名字不重复。
节点是集群中的单个服务器。节点的唯一标识也是名称,默认是节点启动时随机生成的UUID。
当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。
用户可以将请求发送到 集群中的任何节点 ,包括主节点。每个节点都知道任意文档所处的位置,并且能够将请求直接转发到存储所需文档的节点。
索引是一系列具有相近特性的文档的集合。例如客户数据可以对应一个索引,商品目录可以对应另一个索引。ES中的索引类似于RDBMS中的表。索引的唯一标识是名称,名称必须全小写。
当索引作为动词使用时,表示将一个文档存储到索引(名词)中,是其支持全文检索的过程。
Mapping用于描述数据的每个字段如何存储。ES自动生成一个_all字段,其类型为字符串,属于全文字段。ES会根据文档内容进行猜测,动态产生一个映射。
Elasticsearch 支持 如下简单域类型:
- 字符串:string
- 整数:byte, short, integer, long
- 浮点数:float, double
- 布尔型:boolean
- 日期:date
当你索引一个包含新字段的文档时,ES自动进行动态映射。JSON类型到上述类型的转换比较直白,除了要注意2018-04-03这样的字符串会被自动解析为date类型。
除了上述的简单标量类型外,JSON中的null、数组、对象,都是被ES支持的。
空字段不会被索引,包括:null、null、[ null ]
Lucene不理解嵌套对象,Lucene文档由一组键值对的列表构成。为了支持复杂类型的处理,ES必须对文档进行扁平化。
多值域以数组形式表示,数组的元素类型必须相同。尽管提取文档时,数组元素顺序不会丢失,但是索引是以无序的多值域形式进行的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "followers": [ { "age": 35, "name": "Mary White"}, { "age": 26, "name": "Alex Jones"}, { "age": 19, "name": "Lisa Smith"} ] } # 被扁平化为 { # 扁平化后,age和name之间的关系丢失 # 如果要查询是否有名为alex的26岁的follower,需要使用嵌套对象 "followers.age": [19, 26, 35], "followers.name": [alex, jones, lisa, smith, mary, white] } |
类似的,为了让ES有效的索引嵌套对象,同样需要扁平化:
1 2 3 4 5 |
{ "tweet": [elasticsearch, flexible, very], "user.id": [@johnsmith], "user.gender": [male] }? |
Analysis是处理全文字段,使其可以被搜索的过程。分析包含下面的步骤:
- 首先,将一块文本分成适合于倒排索引的独立的词条
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”
分析器负责执行上面的工作。 分析器实际上是将三个功能封装到了一个包里:
- 字符过滤器:首先,字符串按顺序通过每个字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成and
- 分词器:其次,字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条
- Token过滤器:最后,词条按顺序通过每个Token过滤器 。这个过程可能会:
- 改变词条,例如小写化?
- 删除词条,例如像 a, and, the这样的无用词
- 增加词条,例如像像 jump 和 leap 这种同义词
ES提供了开箱即用的字符过滤器、分词器、Token过滤器,它们可以自由的组合成分析器,满足不同应用场景。
分析器 | 说明 |
标准分析器 | 默认的分析器,根据Unicode联盟定义的单词边界来话分文本,删除绝大部分标点符号,最后小写化 |
简单分析器 | 在任何非字母的地方拆分词条 |
空格分析器 | 以空格为界拆分词条 |
语言分析器 |
针对特定语言,例如:
|
- 当索引一个文档时,它的全文域被分析成词条以用来创建倒排索引
- 当在全文字段上执行搜索时,查询字符串也需要类似的分析过程
当查询一个精确值字段时,分析器不会介入。
当ES在你的文档中检测到一个新的字符串域 ,它会自动设置其为一个全文字符串域,使用标准分析器对它进行分析。某些情况下你可能需要改变此默认行为:
- 你想使用一个不同的分析器,适用于你的数据使用的语言
- 你想要一个字符串域就是一个精确值,不需要分析
可以通过自定义映射来满足上述需求。
总体来说,ES中的字段可以分为精确值、全文两个大类。
精确值包括日期、数字,字符串也可以表示精确值。精确值是大小写敏感的。精确值很容易查询,它要么匹配,要么不匹配查询条件。
查询全文字段则要复杂的多,通常不会对全文字段进行精确匹配查询。全文字段匹配查询条件时有个相关度的概念,表现为分数(Scoure)。搜索引擎应该能够识别缩写、词根、同义词,并给出适当的相关度。
对全文检索的支持依赖于分析,在分析之后需要创建倒排索引。
ES使用一种称为倒排索引的结构来支持快速的全文检索。
倒排索引的结构类似于RDBMS的位图索引,对于索引中出现的任何不重复的词的标准模式,生成包含该此的文档列表。标准模式提取了词干、同义词。
默认情况下,查询返回结果是按相关性倒序排列的。每个文档都有相关性评分,用一个正浮点数字段 _score 来表示 。 _score 的评分越高,相关性越高。
查询语句会为每个文档生成一个 _score 字段。评分的计算方式取决于查询类型。不同的查询语句用于不同的目的:fuzzy 查询会计算与关键词的拼写相似程度。terms 查询会计算找到的内容与关键词组成部分匹配的百分比。
通常我们说的 relevance 是我们用来计算全文本字段的值相对于全文本检索词相似程度的算法。ES的相似度算法被定义为检索词频率/反向文档频率, 包括以下内容:
- 检索词频率:检索词在该字段出现的频率?出现频率越高,相关性也越高
- 反向文档频率:每个检索词在索引中出现的频率?频率越高,相关性越低
- 字段长度准则:字段本身的长度是多少?长度越长,相关性越低
Type用于对索引进行分区/分类,允许你在一个索引里存储不同类型的文档。从6.0开始Type被弃用。
可被索引的、最小的信息单元。以JSON形式表示。?
一个索引的数据量可以超过硬盘的物理容量限制,ES使用分片来突破此限制。每个分片都是独立的、完整功能的“子索引”
创建索引时,你可以指定分片的数量。但是,分片如何分配给节点,分片中的文档如何被聚合以响应查询,完全由ES管理,对用户透明。
每个分片可以创建0-N个副本,这样可以避免单点故障。
ES的API通过JSON over RESTful HTTP暴露。除非特别强调,所有API都遵守本节描述的约定。
大部分支持index参数的API,都能够跨越多个索引执行。你可以用以下形式指定多个索引:
1 2 3 4 5 6 7 8 |
# 枚举 test1,test2,test3 # 所有索引 _all # 使用通配符 test* # 排除索引 -test3 |
所有支持多索引的API,均识别以下URL参数:
参数 | 说明 |
ignore_unavailable | 是否忽视不存在或者关闭的索引,取值true/false |
allow_no_indices | 是否允许没有任何匹配的索引 |
expand_wildcards | 通配符如何展开,open仅仅展开匹配打开的索引,其它取值all,close |
指定索引名时,你可以提供一些特殊符号,以匹配时间序列索引集中的一个范围,这样可以避免全集群扫描过滤。
几乎所有支持index参数的API,均可以指定如下格式的索引名:
1 2 3 4 5 |
<static_name{date_math_expr{date_format|time_zone}}> # static_name 索引名中固定的部分 # date_math_expr 动态计算为时间点的表达式 # date_format 日期展示格式,默认YYYY.MM.dd # time_zone 时区,默认UTC |
示例:
表达式 | ?说明 | 解析为 |
<logstash-{now/d}> | 截断到日起点 | logstash-2018.04.12 |
<logstash-{now/M}> | 截断到月起点? | logstash-2018.04.01 |
<logstash-{now/M{YYYY.MM}}> | 截断到月,格式化为年月 | logstash-2018.04 |
<logstash-{now/M-1M{YYYY.MM}}> | 截断到月,减一月 | logstash-2018.03 |
<logstash-{now/d{YYYY.MM.dd|+8:00}}>? | 使用东八区格式化 | logstash-2018.04.12 |
运算符: +1d添加一天、 -1d减少一天、 /d向下截断到最近一天、 /h向下截断到最近一小时。例如 now-1h/d表示当前时间的毫秒数减去1小时,然后向下截断为UTC当日零时。
日期字段:y年、M月、w周、d日、h时、m分、s秒
选项 | 说明 |
pretty=true | 格式化打印,默认打印为JSON格式 |
format=yaml | 打印为YAML格式 |
human=false | 是否以人类易读方式输出数字 |
filter_path |
用于减少服务器返回的响应长度,该参数为逗号分隔的、响应字段过滤器。例如: filter_path=took,hits.hits._id,hits.hits._score? |
flat_settings | 影响_settings查询的输出格式 |
error_trace | 设置为true,则查询出错时返回结果包含调用栈信息,便于诊断 |
source | 使用不支持非POST请求体的HTTP客户端库时,使用此参数传递请求体内容 |
REST参数(使用HTTP时对应URL参数)使用小写+下划线的风格。
类型 | 说明 |
布尔 | 取值true或者false,不支持其它取值 |
数字 |
支持原生JSON数字类型 支持单位:k、m、g、t、p |
时间 | 支持单位:d、h、m、s、ms、micros、nanos |
字节数 | 支持单位:b、kb、mb、gb、tb、pb |
距离 | 支持单位:km、m、cm、mm |
某些查询/API支持非精确的“模糊查询”,你可以指定fuzziness参数。
当针对text或keyword字段进行模糊查询时,“模糊”被解释为编辑距离(Levenshtein Edit Distance)——为了让一个字符串变为另一个,所需改变的字符个数。
某些库不支持非POST请求的请求体,此时你可以将请求体作为source查询参数传递。
需要同时传递参数source_content_type来指定内容类型,例如application/json。
必须通过请求头Content-Type来设置请求体格式,大部分API支持 JSON, YAML, CBOR,SMILE这些格式。批量/多搜索API支持NDJSON,JSON,SMILE。
1 2 3 4 5 |
curl http://localhost:9200/_cat/health?v # status取值含义: # green 一切正常,集群功能完整 # yellow 所有数据可用,但是某些副本分片(Replica)没有分配。集群功能完整 # red 某些数据不可用,存在没有运行的主分片 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// curl http://es-elasticsearch.kube-system.svc.k8s.gmem.cc:9200/_nodes/os?pretty { "_nodes" : { "total" : 12, "successful" : 12, "failed" : 0 }, "cluster_name" : "es", "nodes" : { "CmHIMj5aReqUPXL4yglVjQ" : { "name" : "es-client-695458dd5c-4dt26", "transport_address" : "172.27.208.114:9300", "host" : "172.27.208.114", "ip" : "172.27.208.114", "version" : "6.2.4", "build_hash" : "ccec39f", "roles" : [ "ingest" ], "os" : { "refresh_interval_in_millis" : 1000, "name" : "Linux", "arch" : "amd64", "version" : "4.15.18-041518-generic", // 可用的CPU数量 "available_processors" : 4, // ES可用的CPU数量 "allocated_processors" : 1 } } } } |
1 2 3 4 |
# PUT /index_name/_settings { "number_of_replicas" : 2 } |
默认副本份数是1,要为将来创建的索引修改副本份数,执行:
1 2 3 4 5 6 7 8 9 10 |
# 未来创建的以fluentd开头的索引,副本份数为0 curl -XPUT "localhost:9200/_template/logstash_template" -H 'Content-Type: application/json' -d' { "index_patterns": ["fluentd*"], "settings": { "number_of_replicas": 0 } } ' |
你可以使用 analyze API 来看文本是如何被分析的:
1 2 3 4 5 |
# GET /_analyze { "analyzer": "standard", "text": "Text to analyze" } |
使用_mapping API可以获取1-N个索引的1-N个字段的映射信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# 获取索引gb的tweet(文档)类型的映射信息 # GET /gb/_mapping/tweet { "gb": { "mappings": { "tweet": { # 字段列表 "properties": { "date": { "type": "date", "format": "strict_date_optional_time||epoch_millis" }, "name": { "type": "string" }, "tweet": { "type": "string" }, "user_id": { "type": "long" } } } } } } # 具有内嵌对象的文档的映射形式 { "gb": { "tweet": { "properties": { "tweet": { "type": "string" }, # 内嵌文档 "user": { "type": "object", "properties": { "id": { "type": "string" }, # 内嵌文档 "name": { "type": "object", "properties": { "first": { "type": "string" }, "last": { "type": "string" } } } } } } } } } |
_validate 可以用来验证查询是否合法:
1 2 3 4 5 6 7 8 9 10 |
# GET /gb/tweet/_validate/query # 显示查询不合法的原因 # GET /gb/tweet/_validate/query?explain { "query": { "tweet" : { "match" : "really powerful" } } } |
1 2 3 4 5 6 7 |
curl -X PUT http://localhost:9200/media?pretty { "acknowledged" : true, "shards_acknowledged" : true, "index" : "media" } |
尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射 ,特别是字符串域。自定义映射允许你执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
指定字段映射时,最重要的属性是type,对于非string字段,通常仅仅需要设置type。
首次创建一个索引时,你可以指定自定义映射,以后,你可以使用_mapping API创建新字段的映射,或者修改现有字段的映射。注意一个限制:不能把字段从analyzed修改为not_analyzed。
首次创建索引时指定映射的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# PUT /gb { "mappings": { "tweet" : { "properties" : { "tweet" : { "type" : "string", # analyzed 设置为全文字段;not_analyzed 设置为精确值字段;no 不索引此字段,不支持检索 "index" : "analyzed", # 分析器,默认standard "analyzer": "english" }, "date" : { "type" : "date" }, "name" : { "type" : "string" }, "user_id" : { "type" : "long" } } } } } |
修改某个文档类型的某个字段的映射的例子:
1 2 3 4 5 6 7 8 9 |
# PUT /gb/_mapping/tweet { "properties" : { "tag" : { "type" : "string", "index": "not_analyzed" } } } |
1 2 3 4 |
curl http://localhost:9200/_cat/indices?v # health status index uuid pri rep docs.count docs.deleted store.size pri.store.size # yellow open media EGuJhl4oSMy-FAhgK0bPJQ 5 1 0 0 1.1kb 1.1kb # 由于没有额外的Replica,存在单点风险,因此yellow 包含5个分片,副本份数1 |
插入一个文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
curl -X PUT 'localhost:9200/customer/_doc/1?pretty' -H 'Content-Type: application/json' -d'{"name":"Alex"}' { "_index" : "customer", # 文档所在的索引 "_type" : "_doc", # 文档的类型,不同规格的文档可以共享类似的Schema,这些文档放在同一个索引中,以type区分 "_id" : "1", # 文档标识符,从URL参数获得 "_version" : 1, # 文档版本号 "result" : "created", "_shards" : { # 索引操作的复制处理情况 "total" : 2, # 有多少分片(包括主分片、复制分片)执行了索引操作 # 成功完成索引操作的分片个数,至少为1才意味着索引操作成功。默认情况下仅主分片成功后就会返回 "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 } |
使用相同URL再次PUT,则对标识符为1的文档进行替换(Reindex)操作。
插入文档时,可以不显式指定ID,ES会自动随机生成一个: POST /customer/_doc?pretty ...
如果索引文档时,目标索引不存在,则:
- 自动创建索引,设置选项action.auto_create_index=false,可以禁用
- 自动创建一个类型映射,设置选项index.mapper.dynamic=false,可以禁用
每个被索引的文档都具有对应的版本号,此版本号作为响应的一部分返回。默认情况下,索引从1开始,每次更新(即使没有做任何改变)、删除操作后增1。
请求可以直接指定版本号,这种情况下ES自动进行乐观并发控制:PUT index_name/_doc/1?version=2。乐观并发控制的典型应用场景是读后更新。
版本化是完全实时的,不受检索操作的准实时性影响。如果不提供version参数,则ES不对操作进行版本检查。
版本号可以存放在ES外部,要启用此特性,设置version_type=external。这种情况下,请求参数中的版本号会和当前被索引的文档中的版本号进行比较,如果请求中的版本号大则新文档被存储、索引。
指定 op_type=create则强制进行创建操作,如果同ID的文档已经存在,则报错。
默认情况下,文档ID的哈希值决定了它被存放到哪个分片上。你可以使用请求参数 routing,其值作为哈希函数的入参。
根据路由的结果,索引操作在相应的主分片(所在的节点)上执行。当主分片的索引操作完成后,更新操作复制到可用的从分片。
为了提升可靠性,ES允许配置为必须等待一定数量的分片的写操作完成,在此之前,请求必须等待、重试,或者超时。默认情况下只需要等待主分片,即index.write.wait_for_active_shards=1。你也可以针对请求来设置wait_for_active_shards参数。设置为all则需要等待所有分片操作完成。
执行索引操作时,主分片可能处于不可用状态,默认情况下,ES会等待1分钟,此超时时间可以通过请求参数timeout定制。
除了插入/替换文档之外,我们还可以进行更新操作。注意,实际上ES是不支持In-place更新的,它仅仅是把更新信息merge到原文档中,然后替换掉原文档。示例:
1 2 3 4 5 6 7 8 |
curl -XPOST 'localhost:9200/customer/_doc/1/_update?pretty' -H 'Content-Type: application/json' -d' { # 可以指定一个需要merge from的文档 "doc": { "name": "Wong", "age": 30 } # 也可以指定一段脚本 "script" : "ctx._source.age += 1" } ' |
这类API允许基于ID来取得JSON格式的文档:
1 2 3 4 5 6 7 8 9 10 11 12 |
curl http://localhost:9200/customer/_doc/1?pretty { "_index" : "customer", "_type" : "_doc", "_id" : "1", "_version" : 4, "found" : true, "_source" : { # 文档的完整JSON "name" : "Alex" } } |
你也可以使用HEAD方法来检查目标文档是否存在:HEAD customer/_doc/id
默认情况下get操作是实时的,不受到索引刷新率的影响。
如果所请求的文档已经更新,但是尚未刷新,则此API会即席的发起一次刷新操作。设置请求参数realtime=false禁用此自动刷新行为。
默认情况下此API获取源文档的所有内容,即_source字段。设置_source=false则不返回源文档的任何内容。
如果需要返回某些字段,使用_source_include请求参数;如果需要排除某些字段,使用_source_exclude字段,示例:
1 2 3 4 |
# 可以使用通配符 _source_include=*.id&_source_exclude=entities # 如果仅仅使用include,可以直接简写为_source _source=*.id,retweeted |
要仅仅返回响应文档的_source字段,使用请求:customer/_doc/1/_source
如果索引文档时使用routing来指定路由,则提取文档时必须传入相同的routing参数。
使用参数preference,可以指定在什么分片上提取文档。默认值是随机选取分片。取值_primary则仅仅在主分片上提取,取值_local则尽可能在本地分配的分片上提取。
分片副本份数越多,则操作的性能越好。
使用参数refresh=true,可以在提取之前刷新相关的分片。使用此参数时要注意对系统性能的潜在影响。
传递version参数,则仅在当前文档版本号匹配时,才返回。
使用mget API可以同时提取多个文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# GET /_mget { "docs" : [ { "_index" : "website", "_type" : "blog", "_id" : 2 }, { "_index" : "website", "_type" : "pageviews", "_id" : 1, "_source": "views" } ] } # 其它变体 # GET /index_name/_mget # 如果索引、类型都相同,则仅指定一个ids数组即可 # GET /index_name/type/_mget { "ids" : [ "2", "1" ] } |
返回值包含一个docs数组
根据ID来删除一个已被索引的文档:
1 |
curl -X DELETE 'localhost:9200/customer/_doc/1?pretty' |
版本化、路由、分布式、刷新、超时类似于索引文档。
可以将匹配查询条件的文档删除:
1 2 3 4 5 6 7 8 9 |
curl -X POST "/twitter/_delete_by_query" -H 'Content-Type: application/json' -d' { "query": { "match": { "message": "some message" } } } ' |
此API会获取其被调用时的索引的快照,并基于此快照中匹配文档的内部版本号,执行删除操作。
注意:由于内部版本化不支持版本号0, 因此version=0的文档无法被_delete_by_query删除。
1 2 3 4 5 |
curl -X DELETE 'localhost:9200/customer?pretty' { "acknowledged" : true } |
ES提供了批量操作的API,可以把多个CRUD操作组合在一起执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 插入两个文档 curl -X POST 'localhost:9200/customer/_doc/_bulk?pretty' -H 'Content-Type: application/json' -d' {"index":{"_id":"1"}} {"name": "John Doe" } # Source Document {"index":{"_id":"2"}} {"name": "Jane Doe" } ' # 更新一个文档,删除一个文档 curl -XPOST 'localhost:9200/customer/_doc/_bulk?pretty' -H 'Content-Type: application/json' -d' {"update":{"_id":"1"}} {"doc": { "name": "John Doe becomes Jane Doe" } } {"delete":{"_id":"2"}} ' # 注意删除操作不需要指定的Source Document |
如果批处理中的某个操作失败,则它会继续处理。最终,所有操作的执行结果会返回给调用者。
准备数据:
1 2 3 |
wget https://raw.githubusercontent.com/elastic/elasticsearch/master/docs/src/test/resources/accounts.json # 从文件读取批处理的输入 curl -H "Content-Type: application/json" -X POST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@accounts.json" |
当执行搜索时,请求会广播给索引的全部分片,并以RR算法选择分片的Replica。使用routing参数可以强制在匹配哈希值的分片上执行搜索,你可以为routing指定逗号分隔的多个值。
除了默认的RR轮询算法以外,ES还支持自适应Replica选择,自动选取最适当的Replica。选取准则包括:
- 根据协调(coordinating)节点向数据节点转发请求的响应时间
- 在数据节点上执行请求所消耗的时间
- 数据节点的搜索线程池大小
要启用该特性,设置集群选项:
1 2 3 4 5 6 |
# PUT /_cluster/settings { "transient": { "cluster.routing.use_adaptive_replica_selection": true } } |
除了在每个请求中设置超时之外,ES还支持全局性的搜索超时search.default_search_timeout,此设置没有默认值,设置为-1可以取消先前设置的值。
搜索可以通过标准的任务取消机制来取消。默认情况下ES仅仅在段边界(segment boundaries)来检查请求是否已经被取消,因此取消操作可能由于大段而延迟。要降低取消操作的响应时间,可以设置search.low_level_cancellation=true,但是要注意此设置会导致更加频繁的检查。
默认情况下ES不限制搜索请求牵涉到的分片数量,你可以设置软限制 action.search.shard_count.limit 来拒绝命中太多分片的请求。
参数max_concurrent_shard_requests可以限制搜索请求最多同时在多少个分片上执行,可以防止单个搜索请求消耗整个集群的资源。 此参数的默认值取决于集群中数据节点的数量,最多256。
检索API支持两种传递查询参数的方式:通过URL参数、通过请求体。
要检索特定索引上,任何类型的文档,使用:/index_name/_search
要检索特定索引上,特定类型的文档,使用:/index_name/type1,type2.../_search
要检索多个索引上,具有特定标签的,使用:/index1,index2/_search?q=tag:tag1
要检索任何的索引,使用:/_all/_search
示例:
1 2 3 4 5 6 |
# q=* 匹配所有文档,不指定field默认使用_all字段,此字段是String类型 # q=field:value 仅field字段匹配value的文档 # sort=account_number:asc 根据账号升序排列结果 curl -XGET 'localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty' |
query参数传递Query DSL,示例:
1 2 3 4 5 6 7 8 9 |
curl -XGET 'localhost:9200/bank/_search?pretty' -H 'Content-Type: application/json' -d' { "from" : 0, "size" : 10, "query": { "match_all": {} }, "sort": [ { "account_number": "asc" } ] } ' |
可用参数:
参数 | 说明 |
timeout | 搜索超时,默认无 |
from | 分页,起始偏移量,默认0 |
size | 分页,抓取结果数量,默认10 |
search_type |
搜索类型,取值dfs_query_then_fetch、query_then_fetch,默认query_then_fetch 只能作为URL参数传递 |
request_cache |
true/false,是否启用搜索结果(仅针对size为0的请求,亦即聚合/建议请求)的缓存 只能作为URL参数传递 |
terminate_after |
每个分片最多收集的文档数量,如果超过限制查询立即终止,响应中的terminated_early设置为true 设置此参数为1,可以实现快速检查是否存在匹配(exists) |
batched_reduce_size | 在协调节点(coordinating node)上,每次Reduce分片结果的数量。可以防止单个请求占用太多的内存 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
{ "took" : 10, # 执行搜索消耗的时间 "timed_out" : false, # 搜索是否超时 "_shards" : { # 多少分片参与到搜索 "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { # 搜索结果 "total" : 1000, # 匹配的总数 "max_score" : null, # 最大分数 "hits" : [ # 实际匹配文档的数组 { "_index" : "bank", "_type" : "_doc", "_id" : "0", "_score" : null, # 分数表示文档匹配查询的程度,值越大,匹配程度越高 "_source" : { "account_number" : 0, "balance" : 16623, "firstname" : "Bradshaw", "lastname" : "Mckenzie", "age" : 29, "gender" : "F", "address" : "244 Columbus Place", "employer" : "Euron", "email" : "bradshawmckenzie@euron.com", "city" : "Hobucken", "state" : "CO" }, "sort" : [ 0 ] }, { "_index" : "bank", "_type" : "_doc", "_id" : "1", "_score" : null, "_source" : { "account_number" : 1, "balance" : 39225, "firstname" : "Amber", "lastname" : "Duke", "age" : 32, "gender" : "M", "address" : "880 Holmes Lane", "employer" : "Pyrami", "email" : "amberduke@pyrami.com", "city" : "Brogan", "state" : "IL" }, "sort" : [ 1 ] }, ... ] } } |
一旦搜索结果返回,ES就不会在服务器端存留任何资源,例如游标。这个特性和关系型数据库不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 返回所有文档 { "query": { "match_all": {} } } # 返回第一个匹配的文档,size默认为10 { "query": { "match_all": {} }, "size": 1 } # 分页,返回11-20个文档 { "query": { "match_all": {} }, "from": 10, "size": 10 } |
默认情况下,返回的结果是按照相关性(评分)进行排序的——最相关的文档排在最前,默认按照_score字段降序排序。
你也可以定制排序方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 根据balance升序排列 { "query": { "match_all": {} }, "sort": { "balance": { "order": "desc" } } } # 多值字段排序 { "query" : { "term" : { "country" : "china" } }, "sort" : [ {"population" : {"order" : "asc", "mode" : "avg"}} ] } |
ES支持根据数组字段、多值字段进行排序,此时可以设置sort.mode字段:
sort.mode | 说明 |
min | 取数组中的最小值参与排序 |
max | 取数组中的最大值参与排序 |
sum | 取数组元素总和参与排序 |
avg | 取数组元素平均值参与排序 |
median | 取中位数参与排序 |
ES支持依据文档中的嵌套字段进行排序,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "query" : { "term" : { "product" : "chocolate" } }, "sort" : [ { # 根据嵌套字段排序 "offer.price" : { # 取嵌套字段平均值 "mode" : "avg", # 升序排列 "order" : "asc", # 嵌套字段信息 "nested": { # 导航路径 "path": "offer", # 过滤条件 "filter": { "term" : { "offer.color" : "blue" } } } } } ] } |
使用missing指定可以指定当某个文档没有参与排序的字段时该怎么办,默认值为_last,可以取值_first或者自定义一个用于排序的数值:
1 2 3 4 5 |
{ "sort" : [ { "price" : {"missing" : "_last"} } ] } |
ES支持根据二维平面上的距离值来排序,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "sort" : [ { "_geo_distance" : { # 支持多种形式的坐标: "pin.location" : { "lat" : 40, "lon" : -70 }, "pin.location" : "40,-70", "pin.location" : [[-70, 40], [-71, 42]], "pin.location" : [-70, 40], # 排序的字段: 计算距离时的中心点 "order" : "asc", # 升降序 "unit" : "km", "mode" : "min", # 如果排序字段中包含多个Geo点,如何处理 "distance_type" : "arc" # 可以取值plane,速度快但是长距离、近极地时不准确 } } ] } |
排序算法可以由自定义的脚本提供:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "sort" : { "_script" : { "type" : "number", "script" : { "lang": "painless", "source": "doc['field_name'].value * params.factor", "params" : { "factor" : 1.1 } }, "order" : "asc" } } |
当使用排序时,默认不会计算匹配分数,要改变此行为设置track_scores = true
当执行排序时,排序相关的字段被载入内存。每个分片都需要具有足够的内存来容纳这些字段:
- 对于参与排序的字符串类型,不应该被analyzed/tokenized
- 对于参与排序的数字类型,尽可能使用更短的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 不返回任何字段 { "_source": false } # 返回account_number、balance两个字段 { "query": { "match_all": {} }, "_source": ["account_number", "balance"] } # 使用通配符指定字段 { "_source": [ "obj1.*", "obj2.*" ] } # 指定需要包含、排除的字段 { "_source": { "includes": [ "obj1.*", "obj2.*" ], "excludes": [ "*.description" ] } } |
允许根据现有字段,进行计算,衍生出新的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "query" : { "match_all": {} }, "script_fields" : { "test2" : { # 使用doc更快,内存消耗高(因为目标字段的terms被载入内存)。仅能返回简单的值(不能返回JSON文档) "script" : { "lang": "painless", "source": "doc['my_field_name'].value * params.factor", "params" : { "factor" : 2.0 } }, # 使用_source非常慢,因为整个文档需要载入并解析 "script" : "params['_source']['my_field_name']" } } } |
通常的规则是,使用 查询(query)语句来进行全文搜索或者其它任何需要影响相关性得分的搜索。除此以外的情况都使用过滤(filters)。
本节以Query DSL语法说明如何指定查询条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 查询语句典型结构 { QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } # 针对特定字段的查询语句结构 { QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# 全部匹配 "query": {} "query": { "match_all": {} } # 账号为20 { "query": { "match": { "account_number": 20 } } } # 地址中包含mill字样 { "query": { "match": { "address": "mill" } } } # 地址中包含mill或者lane字样 { "query": { "match": { "address": "mill lane" } } } # 地址中包含"mill lane"这个短语 { "query": { "match_phrase": { "address": "mill lane" } } } # 在多个字段上进行匹配 { "multi_match": { "query": "full text search", "fields": [ "title", "body" ] } } # 查询落在指定区间的时间、数字 { "range": { "age": { "gte": 20, "lt": 30 } } } # 精确匹配查询 { "term": { "age": 26 }} { "term": { "date": "2014-09-01" }} { "term": { "public": true }} { "term": { "tag": "full_text" }} # 精确匹配查询(多值,匹配任意一个即可) { "terms": { "tag": [ "search", "full_text", "nosql" ] }} # 存在性查询 { # 存在title字段 "exists": { "field": "title" } # 不存在title字段 "missing": { "field": "title" } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
{ "query": { "bool": { # 与:地址中同时包含mill和lane "must": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } } { "query": { "bool": { # 或:地址中包含mill或lane "should": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } } { "query": { "bool": { # 非:地址中不得包含mill或lane "must_not": [ { "match": { "address": "mill" } }, { "match": { "address": "lane" } } ] } } } { "query": { # 返回年龄为40,且不住在爱达荷州的顾客账户 "bool": { "must": [ { "match": { "age": "40" } } ], "must_not": [ { "match": { "state": "ID" } } ] } } } |
过滤查询(Filtering queries)只是简单的检查包含或者排除,这就使得计算起来非常快。过滤查询不进行评分,结果可以被缓存。
相反,评分查询(scoring queries)不仅仅要找出 匹配的文档,还要计算每个匹配文档的相关性,计算相关性使得它们比不评分查询费力的多。同时,查询结果并不缓存。
由于倒排索引(inverted index),一个简单的评分查询在匹配少量文档时可能与一个涵盖百万文档的filter表现的一样好,甚至会更好。但是在一般情况下,一个filter 会比一个评分的query性能更优异,并且每次都表现的很稳定。
注意filter可以放在不同位置,仅仅其引用的查询条件不影响评分,而不是整个查询不支持评分。过滤查询示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "query": { "bool": { # 返回地址中包含lane(默认大小写不敏感),且余额在2w到3w之间的账户 "must": { "match": { "address": "lane" } }, "filter": { "range": { # 下面的条件不影响评分 "balance": { "gte": 20000, "lte": 30000 } } } } } } |
ES支持修改搜索结果,为匹配搜索的字段添加HTML标签,以便高亮显示:
1 2 3 4 5 6 7 8 |
{ "highlight": { "fields" : { # 默认情况下,about字段中匹配搜索条件的部分会被<em>标签包围 "about" : {} } } } |
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ # 不返回聚合前的结果集,我们仅仅关注聚合 "size": 0, "aggs": { # 聚合结果的键 "group_by_state": { "terms": { # 根据state字段进行分组 "field": "state.keyword" }, # 聚合函数默认是统计总数 } } } # 类似于SQL:SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC |
执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{ "took" : 28, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, # 聚合前的结果集 "hits" : { "total" : 1000, "max_score" : 0.0, "hits" : [ ] }, # 聚合 "aggregations" : { # 键 "group_by_state" : { "doc_count_error_upper_bound" : 20, "sum_other_doc_count" : 770, "buckets" : [ { "key" : "ID", "doc_count" : 27 }, { "key" : "TX", "doc_count" : 27 } ... ] } } } |
你可以指定聚合函数、排序方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "size": 0, "aggs": { "group_by_state": { "terms": { "field": "state.keyword", # 根据平均余额降序排列 "order": { "average_balance": "desc" } }, # 聚合函数:对账户余额取平均值 "aggs": { "average_balance": { # 字段名 "avg": { "field": "balance" } } } } } } |
ES提供了很适当的默认配置,需要很少的定制化。大部分配置项都可以在运行时更新。
ES的配置文件主要有三个:
- elasticsearch.yml 主配置文件
- jvm.options 使用的JVM参数
- log4j2.properties 日志配置
这些配置文件位于conf目录,具体位置和你使用的安装方式有关。可以通过环境变量定制:
1 |
ES_PATH_CONF=/path/to/my/config ./bin/elasticsearch |
主配置文件基于YML格式,支持通过${VAR_NAME}来引用环境变量,例如:
1 2 |
node.name: ${HOSTNAME} network.host: ${ES_NETWORK_HOST} |
主要的配置项如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
path: # 日志存放目录 logs: /var/log/elasticsearch # 数据存放目录 data: /var/data/elasticsearch # 可以指定多个存储位置 data: - /mnt/elasticsearch_1 - /mnt/elasticsearch_2 - /mnt/elasticsearch_3 # 当前节点所属集群名称 cluster.name: logging-prod # 当前节点的名称 node.name: ${HOSTNAME} # 绑定的监听地址 network.host: 10.0.0.1 # 节点的相互发现 # ES实现了所谓Zen Discovery,用于节点发现、Master选举 # 不进行任何配置的情况下,ES会扫描localhost的9300-9305来寻找相同服务器上运行的节点 # 在实际集群中,你需要指定当前节点需要连接的其它节点 discovery.zen.ping.unicast.hosts: - 192.168.1.10:9300 - 192.168.1.11 - seeds.mydomain.com # 节点列表可以不指定端口,默认使用transport.profiles.default.port # 如果transport.profiles.default.port没有配置则使用transport.tcp.port # 为了防止数据丢失,需要配置每个有资格成为Master的节点能够看到的,其它有资格成为Master的节点的最小数量 # 如果不设置此选项,在网络分区的情况下,集群会分裂为两个独立的小集群,即脑裂。脑裂会导致数据丢失 # 为了防止脑裂,需要设置此选项为 (master_eligible_nodes / 2) + 1 discovery.zen.minimum_master_nodes: 2 zen: # 故障发现,默认1秒执行一次,超时30秒,超时3次则剔除节点。可能导致索引重新分配 ping_interval: 1 ping_timeout: 30s ping_retries: 3 |
默认情况下,ES使用固定大小的1G堆内存。关于JVM参数配置的建议包括:
- 设置堆的最小值和最大值相同
- 堆越大,ES越可以缓存更多的东西。但是大堆意味着更长的GC停顿
- Xmx不要设置超过50%的物理内存。为内核和系统缓存留下空间
ES使用Java安全管理器。JVM默认无限期的缓存DNS解析记录,如果你依赖于动态解析的DNS,则需要配置安全管理器:
1 2 |
networkaddress.cache.ttl=<timeout>、 networkaddress.cache.negative.ttl=<timeout> |
Swap可能导致JVM堆甚至可执行页被交换到磁盘中,对性能有非常不利的影响。Swap可能导致GC从毫秒级变为分钟级完成、导致节点响应异常缓慢甚至脱离集群。因此,宁愿让OS把节点杀掉也不要启用Swap。
你可以执行下面的命令来禁用所有交换文件:
1 |
sudo swapoff -a |
注释掉/etc/fstab中的相关行,可以永久禁用Swap。
设置systctl参数vm.swappiness为1,可以减小Linux内核进行Swap的倾向,让它在通常情况下不会Swap。
或者,你可以利用Linux的mlockall,将ES进程的地址空间锁定在内存中,配置ES:
1 |
bootstrap.memory_lock: true |
ES需要使用大量的文件描述符/文件句柄。确保运行ES的用户可以打开65536或者更多的文件描述符。
ES默认使用一个mmapfs目录来存储其索引,OS的mmap计数默认值很低,可能导致OOM异常。执行下面命令修改:
1 2 3 |
sysctl -w vm.max_map_count=262144 # 一个进程可以拥有的VMA(虚拟内存区域)的数量,虚拟内存区域是一个连续的虚拟地址空间区域 # 在进程的生命周期中,每当程序尝试在内存中映射文件,链接到共享内存段,或者分配堆空间的时候,这些区域将被创建 |
ES需要创建很多线程来完成不同的操作,你最少要保证ES能够创建4096个线程:ulimit -u 4096或者设置nprocs
对于Gold/Platinum类型的License,如果你启用了安全,则必须同时启用TLS。
你可以使用外部提供的CA证书,或者通过下面的命令生成:
1 |
bin/x-pack/certutil ca |
可以使用下面的命令生成p12格式的私钥、证书:
1 |
bin/x-pack/certutil cert --ca elastic-stack-ca.p12 |
默认情况下,此证书不包含SAN字段,因此可以所有节点共享。如果需要更加严格的身份验证,可以传入--name, --dns --ip参数。
你可以可以使用外部提供的证书。
把密钥拷贝到节点的config/certs目录下
添加以下ES配置项:
1 2 3 4 |
xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.keystore.path: certs/elastic-certificates.p12 xpack.security.transport.ssl.truststore.path: certs/elastic-certificates.p12 |
验证模式默认certificate,不检查SAN,如果需要更加严格的身份验证,设置为full。
重启服务后,集群节点间的TLS通信OK,你可以使用Gold/Platinum的License了。
1 2 3 |
curl -u elastic:$PSWD 'http://es-elasticsearch.kube-system.svc.k8s.gmem.cc:9200/_xpack/license' \ -H "Content-Type: application/json" -d @eslic.json # license文件路径 |
登陆到客户端节点,执行下面的命令,可以随机生成elastic、kibana、logstash_system的新密码:
1 |
bin/x-pack/setup-passwords auto |
或者,你也可以交互式的设置密码:
1 |
bin/x-pack/setup-passwords interactive |
添加ES配置项:
1 2 3 4 5 6 |
xpack.security.authc: anonymous: # 匿名用户的名称 username: anonymous # 授予的角色,逗号分隔 roles: transport_client |
角色 | 说明 |
ingest_admin | 可以访问所有索引模板、ingest流水线配置 |
kibana_dashboard_only_user | 可以访问Kibana仪表盘,只读访问.kibana索引 |
kibana_system | 可以读写Kibana索引,检查集群可用性,管理索引模板。可以读.monitoring-*索引,读写.reporting-*索引 |
kibana_user | Kibana用户的最小权限,可以访问Kibana索引,监控集群状态 |
logstash_admin | 可以访问.logstash*索引,以管理配置 |
logstash_system | Logstash系统级用户,可以发送监控数据给ES |
monitoring_user | 支持X-pack monitoring |
remote_monitoring_agent | 支持写入监控数据到ES |
superuser | 超级用户 |
transport_client |
支持访问Node Liveness API、Cluster State API,例如/_cluster/state/version |
watcher_admin | 读写.watches索引 |
watcher_user | 只读.watches索引 |
启用身份验证后,如果没有修改内置用户密码,则kibana登陆界面会提示:Login is currently disabled. Administrators should consult the Kibana logs for more details.
你需要更改kibana的配置项elasticsearch.password,如果在Docker中运行Kibana,也可以修改环境变量ELASTICSEARCH_PASSWORD。然后使用setup-passwords命令更改内置用户密码。
建议把节点分为三类:
- Master节点,仅仅负责集群管理,不存储数据,不提供HTTP API
- Client节点,和客户端通信,不存储数据,提供HTTP API
- Data节点,仅仅负责存储、索引数据,不提供HTTP API
对于超过32GB的堆,无法使用压缩普通对象指针(Compressed Ordinary Object Pointer),指针大小变为64bit。这导致50GB的堆,能存储的对象数量和30GB的堆差不多。
堆的最大、最小值应该设置为一致。
低水位:默认情况下,节点磁盘用量超过85%后,ES不会分发新的分片到该节点。即便如此,已有的分片仍然可能继续增大。
高水位:默认情况下,节点磁盘用量超过90%后,ES会停止写入,并且将该节点上的分片重现分配给磁盘空闲的其它节点。
副本:默认情况下使用1个副本,这意味着两倍的磁盘空间
分片:更大的分片,在存储上越高效。但是节点失败导致数据迁移的成本也越高。
索引是存储文档并让其可检索的过程。文档必须落盘,才能被搜索。
默认的落盘间隔是一秒,由参数refresh_interval指定。如果将此参数改为半分钟甚至更大,则能极大的增加ES的吞吐量:
1 2 3 4 5 6 |
PUT fluentd-2018.12.26/_settings { "index" : { "refresh_interval" : "10s" } } |
每次落盘,ES都会创建一个新的段(segment )。
设置更多的分片,插入数据的并发度更高:
1 2 3 4 5 6 7 8 9 |
PUT /_template/logstash_template { "index_patterns": ["fluentd*"], "settings": { "number_of_shards" : 12, "number_of_replicas": 0, "refresh_interval" : "10s" } } |
字段数据(Field Data )反转倒排索引。如果你需要知道某个字段包含哪些值,则ES需要反转字段的倒排索引,并产生字段数据。
字段数据存放在堆中,不加任何限制可能充满整个堆。参数indices.fielddata.cache.size用于控制字段数据的内存用量,可以指定百分比或者绝对值。
如果写入量非常大,则需要保证内存中的索引缓冲足够大,对应的参数是indices.memory.index_buffer_size。可以设置高达512MB/分片。
ES 6.x的查询缓存使用LRU算法清除,其内存用量通过indices.queries.cache.size配置。
批量请求比针对单个文档的请求性能要好很多。批次的最佳大小需要基准测试才能得出。
可以使用多个客户端并发的进行批量请求,单线程往往不能用尽ES的吞吐能力。
交换分区会导致严重的性能下降。
在进行IO操作时,需要使用文件系统缓存。你应该保证有一半的ES节点内存用于文件系统缓存。
[…] 参考:ElasticSearch学习笔记 […]