Elasticsearch写入优化

收到工单 用户提工单到 L2 说写入消息堆积了,第一时间检查 Elasticsearch 这边的日志和监控,把一百多个节点的日志都遍历看了一遍,并没有看到任何异常的关键字。再把 Grafana 监控看了一个遍,也没有任何明显异常的指标,只有少数几个 data 节点超出 85% 水位。没办法,只能从写入端的日志去着手了。 刚开始就看到每分钟内多次的 HTTP 429 报错,还有 socket timeout 的报错。从下午排查到晚上,实在 Elasticsearch 这边没有任何异常。客户那边也上会了,问了下:“你们这个重试是怎样的?” 回答是等待十秒重试一次。另外,客户是通过 Bulk API 来批量发送请求的,但是每次 Bulk 发送的大小不固定:小于 2000 直接发送,大于 2000 截断发送(这个肯定是不合理的,后面再讲)。 这个等待重试策略,如果 Elasticsearch 因为写入请求太多导致阻塞,那么简单的等待重试肯定是解决不了任何问题的。当天就推荐用户修改为退避式重试。修改完后的写入端日志里,不再有 429 的报错了,但还是会有 socket timeout 的报错。 结合之前注意到的情况,猜想是请求发送的次数太多了。然而单个 Bulk 的数据量明显不到推荐值,没有被充分利用。官方推荐的 Bulk 大小是 5–15 MB,而目前的情况是,几十条甚至一两条的写入请求也会被直接发送出去。 推荐用户修改 Bulk 单次提交数据量后,仍然没有任何缓解效果,消息队列消费不完,还是经常性出现堆积。 data 节点的配置是 16C64G,检查了写入线程数也就是默认是 16,这个没法改,最大也就改到 17。写入线程队列是 6.x Elasticsearch 的默认值 200,这个改大也不能解决消费慢的问题,只能让更多的线程在排队。 其他方面,不同索引的刷新配置 refresh_interval 设置的 30–60 s,这也是一个合理的值。translog 落盘配置检查过也都是默认值。唯一一处疑点就是 mapping 的 max_field 设置的是 10000,max_depth 设置的是 20,这个不是正常值,但也不会导致写入慢吧?用户反映他们的 mapping 并不复杂,也不存在 20 层嵌套。逻辑上分析也没啥问题——我定义一个瓶子最大能装 1 吨水,也不是说我的瓶子真的要装 1 吨水。 硬着头皮 按理说,云厂商提供云产品服务,只提供服务和运维,调优能调的肯定在售卖给客户时已经是最优配置,不可能让用户买到的实例再去关闭 swap、调最大文件句柄这种东西。 ...

2024-11-16 · 8 min

ElasticSearch中熔断器

Elasticsearch Service 提供了多种官方的熔断器(circuit breaker),用于防止内存使用过高导致 ES 集群因为 OutOfMemoryError 而出现问题。Elasticsearch 设置有各种类型的子熔断器,负责特定请求处理的内存限制。此外,还有一个父熔断器,用于限制所有子熔断器上使用的内存总量。 Circuit breaker settings 断路器设置 Elasticsearch contains multiple circuit breakers used to prevent operations from causing an OutOfMemoryError. Each breaker specifies a limit for how much memory it can use. Additionally, there is a parent-level breaker that specifies the total amount of memory that can be used across all breakers. Elasticsearch 包含多个断路器,用于防止操作导致 OutOfMemoryError。每个断路器都指定了其可以使用的内存量的限制。此外,还有一个父级断路器,用于指定可在所有断路器中使用的内存总量。 Except where noted otherwise, these settings can be dynamically updated on a live cluster with the cluster-update-settings API. 除非另有说明,否则可以使用 cluster-update-settings API 在实时集群上动态更新这些设置。 For information about circuit breaker errors, see Circuit breaker errors. 有关断路器错误的信息,请参阅断路器错误。 下面提到的静态和动态的意思是,这个配置参数的属性。 动态设置(Dynamic Settings): 可以使用集群更新设置 API 在运行中的集群上进行配置和更新。 在未启动或已关闭的节点上,也可以通过 elasticsearch.yml 文件进行本地配置。 静态设置(Static Settings): 只能在未启动或已关闭的节点上通过 elasticsearch.yml 文件进行配置。 必须在集群中的每个相关节点上进行设置。 主要用于配置静态的集群设置和节点设置。 静态设置的配置只在节点启动时生效,不会受到集群运行时的影响。 Parent circuit breaker 父断路器 The parent-level breaker can be configured with the following settings: 可以使用以下设置配置父级断路器: indices.breaker.total.use_real_memory (Static) Determines whether the parent breaker should take real memory usage into account (true) or only consider the amount that is reserved by child circuit breakers (false). Defaults to true. (静态)确定父断路器是应考虑实际内存使用情况(true)还是仅考虑子断路器保留的内存量(false)。默认值为 true。 indices.breaker.total.limit (Dynamic) Starting limit for overall parent breaker. Defaults to 70% of JVM heap if indices.breaker.total.use_real_memory is false. If indices.breaker.total.use_real_memory is true, defaults to 95% of the JVM heap. (动态)整体父断路器的起始限制。如果 indices.breaker.total.use_real_memory 为 false,默认为 JVM 堆的 70%;如果为 true,默认为 JVM 堆的 95%。 Field data circuit breaker 字段数据断路器 The field data circuit breaker estimates the heap memory required to load a field into the field data cache. If loading the field would cause the cache to exceed a predefined memory limit, the circuit breaker stops the operation and returns an error. 字段数据断路器估计将字段加载到字段数据缓存中所需的堆内存。如果加载该字段会导致缓存超出预定义的内存限制,则断路器将停止操作并返回错误。 ...

2023-11-20 · 5 min

Elasticsearch稳定性介绍

Elasticsearch 的团队致力于不断改进 Elasticsearch 和 Apache Lucene,以保护您的数据。与任何分布式系统一样,Elasticsearch 非常复杂,每个部分都可能遇到需要正确处理的边缘情况。我们将讨论在面对硬件和软件故障时,为改进 Elasticsearch 在健壮性和弹性方面所做的持续努力。 提升 Elasticsearch 的弹性:https://www.elastic.co/cn/videos/improving-elasticsearch-resiliency Elasticsearch 和弹性:https://www.elastic.co/cn/elasticon/conf/2016/sf/elasticsearch-and-resiliency 参考资料:https://www.elastic.co/guide/en/elasticsearch/resiliency/current/index.html 本文不是 Elasticsearch 的 Release docs,只关注与 Elasticsearch 在弹性/稳定性方面的进展。 V5.0.0 使用两阶段提交进行集群状态发布 Elasticsearch 中的主节点会持续监控集群节点,并在某个节点无法及时响应其 PING 请求时将其从集群中移除。如果主节点剩下的节点过少,它将主动放弃主节点的角色,然后开始新的主节点选举过程。当网络分区导致主节点失去许多从节点时,在检测到节点丢失并且主节点下行之前,有一个很短的时间窗口。在这个时间窗口内,主节点可能会错误地接受和确认集群状态变更。为了避免这种情况,我们在集群状态发布中引入了一个新的阶段,其中提议的集群状态会发送给所有节点,但尚未提交。只有在足够多的节点主动确认更改后,更改才会被 committed,并向节点发送 commit 消息#13062。A master node in Elasticsearch continuously monitors the cluster nodes and removes any node from the cluster that doesn’t respond to its pings in a timely fashion. If the master is left with too few nodes, it will step down and a new master election will start. When a network partition causes a master node to lose many followers, there is a short window in time until the node loss is detected and the master steps down. During that window, the master may erroneously accept and acknowledge cluster state changes. To avoid this, we introduce a new phase to cluster state publishing where the proposed cluster state is sent to all nodes but is not yet committed. Only once enough nodes actively acknowledge the change, it is committed and commit messages are sent to the nodes. See #13062。一句话总结:局部断网导致主节点与其他节点失联时,在其他节点检测到,并且投票出新的主节点前,有一个很短的时间窗口,在这个时间窗口内,主节点可能会错误地接受变更。改进:引入了一个新的状态来解决这个问题,想修改集群的状态,需要足够多的节点主动确认后才可以。 V5.0.0 使索引创建能够灵活应对索引关闭和整个集群崩溃 恢复索引需要一定的分片副本(2 除外)可用于分配主分片副本的仲裁。这意味着如果集群在足够的分片被分配#9126之前就宕机了,将无法分配一个主分片。如果一个索引在足够的分片副本启动之前就被关闭了,重启索引也会出现同样的问题,无法重新打开索引#15281。分配 ID#14739通过跟踪集群中已分配的分片副本来解决这个问题。这使得在仅有一个分片副本的情况下也可以安全地恢复一个索引。分配 ID 还可以区分索引已创建但尚未启动任何分片的情况。如果在索引被关闭之前,没有启动任何分片,那么在重新打开索引时将会分配一个全新的分片。Recovering an index requires a quorum (with an exception for 2) of shard copies to be available to allocate a primary. This means that a primary cannot be assigned if the cluster dies before enough shards have been allocated (#9126). The same happens if an index is closed before enough shard copies were started, making it impossible to reopen the index (#15281). Allocation IDs (#14739) solve this issue by tracking allocated shard copies in the cluster. This makes it possible to safely recover an index in the presence of a single shard copy. Allocation IDs can also distinguish the situation where an index has been created but none of the shards have been started. If such an index was inadvertently closed before at least one shard could be started, a fresh shard will be allocated upon reopening the index。一句话总结:恢复一个索引,需要一定数量的(a quorum:至少一半)分片来投票选出能够用来恢复的分片,(分片数为 2 除外,根本没法投,谁可用就用谁),这样如果集群还没有足够多的分片就宕机了,那么这个主分片就没法恢复了。另外,如果一个索引在足够的分片副本启动之前就被关闭了,重启索引也会出现同样的问题。改进:引入了分配 ID 的概念。 ...

2023-11-16 · 15 min

Docker Compose 运行 Elastic Stack 8.X

材料准备 如题,使用 Docker Compose 运行 Elastic Stack 8.X,五个月前还在前司做 7.9 版本的 ELK 迁移,如今已经是前司了,当时总共不到 20TB,最近问前同事数据量已经到 40TB 了,而且根据她发我的截图来看,三千多个索引,副本分片却只有两千多,不操心了,事不关己高高挂起,反正现在负责 ELK 的是新来的陌生人。今天看已经发到 8.10 版本了,来装一个感受下新特性。 官方 Docker Compose 官方安装指引文档 额外操作参考博客 version: "2.2" services: setup: image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs user: "0" command: > bash -c ' if [ x${ELASTIC_PASSWORD} == x ]; then echo "Set the ELASTIC_PASSWORD environment variable in the .env file"; exit 1; elif [ x${KIBANA_PASSWORD} == x ]; then echo "Set the KIBANA_PASSWORD environment variable in the .env file"; exit 1; fi; if [ ! -f config/certs/ca.zip ]; then echo "Creating CA"; bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip; unzip config/certs/ca.zip -d config/certs; fi; if [ ! -f config/certs/certs.zip ]; then echo "Creating certs"; echo -ne \ "instances:\n"\ " - name: es01\n"\ " dns:\n"\ " - es01\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ " - name: es02\n"\ " dns:\n"\ " - es02\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ " - name: es03\n"\ " dns:\n"\ " - es03\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ > config/certs/instances.yml; bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key; unzip config/certs/certs.zip -d config/certs; fi; echo "Setting file permissions"; chown -R root:root config/certs; find . -type d -exec chmod 750 \{\} \;; find . -type f -exec chmod 640 \{\} \;; echo "Waiting for Elasticsearch availability"; until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done; echo "Setting kibana_system password"; until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; echo "All done!"; ' healthcheck: test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"] interval: 1s timeout: 5s retries: 120 es01: depends_on: setup: condition: service_healthy image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata01:/usr/share/elasticsearch/data ports: - ${ES_PORT}:9200 environment: - node.name=es01 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es02,es03 - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es01/es01.key - xpack.security.http.ssl.certificate=certs/es01/es01.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es01/es01.key - xpack.security.transport.ssl.certificate=certs/es01/es01.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: [ "CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'" ] interval: 10s timeout: 10s retries: 120 es02: depends_on: - es01 image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata02:/usr/share/elasticsearch/data environment: - node.name=es02 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es01,es03 - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es02/es02.key - xpack.security.http.ssl.certificate=certs/es02/es02.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es02/es02.key - xpack.security.transport.ssl.certificate=certs/es02/es02.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: [ "CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'" ] interval: 10s timeout: 10s retries: 120 es03: depends_on: - es02 image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata03:/usr/share/elasticsearch/data environment: - node.name=es03 - cluster.name=${CLUSTER_NAME} - cluster.initial_master_nodes=es01,es02,es03 - discovery.seed_hosts=es01,es02 - bootstrap.memory_lock=true - xpack.security.enabled=true - xpack.security.http.ssl.enabled=true - xpack.security.http.ssl.key=certs/es03/es03.key - xpack.security.http.ssl.certificate=certs/es03/es03.crt - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.enabled=true - xpack.security.transport.ssl.key=certs/es03/es03.key - xpack.security.transport.ssl.certificate=certs/es03/es03.crt - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt - xpack.security.transport.ssl.verification_mode=certificate - xpack.license.self_generated.type=${LICENSE} mem_limit: ${MEM_LIMIT} ulimits: memlock: soft: -1 hard: -1 healthcheck: test: [ "CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'" ] interval: 10s timeout: 10s retries: 120 kibana: depends_on: es01: condition: service_healthy es02: condition: service_healthy es03: condition: service_healthy image: docker.elastic.co/kibana/kibana:${STACK_VERSION} volumes: - certs:/usr/share/kibana/config/certs - kibanadata:/usr/share/kibana/data ports: - ${KIBANA_PORT}:5601 environment: - SERVERNAME=kibana - ELASTICSEARCH_HOSTS=https://es01:9200 - ELASTICSEARCH_USERNAME=kibana_system - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD} - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt mem_limit: ${MEM_LIMIT} healthcheck: test: [ "CMD-SHELL", "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'" ] interval: 10s timeout: 10s retries: 120 volumes: certs: driver: local esdata01: driver: local esdata02: driver: local esdata03: driver: local kibanadata: driver: local 步骤分析 来欣赏下官方文档里面都干了些啥吧 创建了一个卷用来存储各种证书 判断 Elasticsearch 的密码和 Kibana 的密码是否设置(在 .env 文件中设置环境变量的值,包括密码,端口) 自签了一个 CA 然后给每个节点签发了证书,不了解这个流程的,自行谷歌 在 YAML 中运行 bash,使用 bash 执行命令为每个节点生成配置文件,又是 YAML 格式的,俄罗斯套娃。 ...

2023-09-28 · 5 min

处理 Kafka 故障全流程

今天中午散步回来,美女同事跟我说,最近的日志没有进来,我登上 Kibana 一看,我靠,近两天的日志全没有(部分索引有,部分索引量变少了,部分索引完全没有新日志进来)。怎么办?看日志呗,毕竟咱不能像美女同事一样把这个情况告诉别人然后摆烂吧,本文记录处理的全流程。 ...

2023-02-27 · 4 min

Elasticsearch 跨集群迁移方案对比

elasticsearch-dump、logstash、reindex、snapshot 方式进行数据迁移,实际上这几种工具大体上可以分为以下几类: scroll query + bulk:批量读取旧集群的数据然后再批量写入新集群,elasticsearch-dump、logstash、reindex 都是采用这种方式。 snapshot:直接把旧集群的底层的文件进行备份,在新的集群中恢复出来,相比较 scroll query + bulk 的方式,snapshot 的方式迁移速度最快。 从源 ES 集群通过备份 API 创建数据快照,然后在目标 ES 集群中进行恢复,无网络互通要求、迁移速度快、运维配置简单、适用于数据量大,接受离线数据迁移的场景。Snapshot and restore 模块允许创建单个索引或者整个集群的快照到远程仓库。所以首先需要创建一个存储快照的地方,存储方案可以选择一个 NFS 的共享存储,或者对象存储。 ...

2023-01-09 · 9 min

删除 ES 的 security-7 索引后的处理步骤

搭建了一套新环境,为了测试 Kibana 配置 LDAP,并且对 ES 中超大的索引进行导出存档。 在搭建的过程中发现,Kibana 连接 ES 失败,网上查找资料后发现解决方案,发现只要删除 .security-7 这个索引就能够清除之前设置的密码,重新设置。.security-7 索引中应该包含了用户登录等一些信息。利用 curl 命令执行删除索引的操作时,误删了另外一个测试环境的该索引,删除后,一切认证相关的功能全都失效,Kibana 无法登录,查找资料进行恢复。 ...

2023-01-07 · 1 min

Logstash 用日志时间代替时间戳

最近工作中遇到的一个问题,网络组把网络设备的“陈年”老日志传到 ELK,这样的问题就是日志的时间是过去的,但是 Logstash 在生成时间戳然后输入到 ES 时,默认的是当前的时间戳。于是需求就是将日志中的时间代替它生成的时间戳,开搞! ...

2022-12-29 · 2 min

使用 Log-pilot 收集 k8s 中的容器日志

容器时代越来越多的传统应用将会逐渐容器化,而日志又是应用的一个关键环节,那么在应用容器化过程中,如何方便快捷高效地来自动发现和采集应用的日志,如何与日志存储系统协同来高效存储和搜索应用日志。本文将主要跟大家分享下如何通过Log-Pilot来采集容器的标准输出日志和容器内文件日志。 ...

2022-12-11 · 3 min

Elastalert2 安装配置流程 - 代替 Elastalert

之前已经有 elastalert 的安装配置文档了,虽然经过了测试但是发现部分内置命令无法正常运行,并且不支持新版本 Kibana,代码仓库长时间没有更新,于是换用新版本的 elastalert2。 Requirements Elasticsearch 7.x 或 8.x,或 OpenSearch 1.x 或 2.x ISO8601 或 Unix timestamped 数据 Python 3.10,Require OpenSSL 1.1.1 或更新版本 pip 根据官方文档描述,需要升级 Python 环境到 3.10,OpenSSL 到 1.1.1。事实上在 Python 3.7 之后的版本,依赖的 OpenSSL 必须要是 1.1 或 1.0.2 之后的版本。 开始安装 Python 3.10 和 OpenSSL sudo yum -y groupinstall "Development tools" sudo yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel libffi-devel gdbm-devel db4-devel libpcap-devel xz-devel make sudo yum install zlib* -y sudo yum install -y gcc gcc-c++ python-devel wget sudo yum install -y zlib zlib-dev openssl-devel sqlite-devel bzip2-devel libffi libffi-devel gcc gcc-c++ wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz ./config --prefix=/usr/local/openssl shared zlib sudo make && make install mv /usr/bin/openssl /usr/bin/openssl.bak mv /usr/include/openssl /usr/include/openssl.bak ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl ln -s /usr/local/openssl/include/openssl /usr/include/openssl echo /usr/local/openssl/lib >> /etc/ld.so.conf ldconfig -v openssl version wget https://www.python.org/ftp/python/3.10.4/Python-3.10.4.tgz sudo ./configure --prefix=/usr/local/python3 --with-ssl=/usr/local/openssl sudo make && sudo make install && sudo make clean 安装完成可能会出现 WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available. Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.")) - skipping 修改完善 需要修改 vim /tmp/softwarebak/Python-3.10.4/Modules/Setup,在末尾加入以下内容: _socket socketmodule.c # Socket module helper for SSL support; you must comment out the other # socket line above, and possibly edit the SSL variable: SSL=/usr/local/openssl _ssl _ssl.c \ -DUSE_SSL -I$(SSL)/include -I$(SSL)/include/openssl \ -L$(SSL)/lib -lssl -lcrypto 重新编译安装 Python 安装 elastalert2 具体流程,请看官方文档

2022-10-13 · 1 min