Posts Tagged ‘elasticsearch’

Elasticsearch 性能优化-移除FST堆内存

July 29th, 2020

单节点es能存储和处理的数据量主要受3方面限制:cpu、内存、磁盘

es是java程序,内存受限于JVM的堆内存,给es进程的堆内存又不能超过32G(受限于指针压缩,超过32g指针压缩失败,内存浪费)。所有堆内存大小的限制是es处理能力的主要限制

分析es堆内存,发现es堆内存使用占比最多的是FST。

何为FST呢?

倒排索引以分词后的词为主键进行组织,每个词后面对应的都是存在该关键字的文档id(这些id使用增量编码压缩,将大数变小数,按字节存储。所以如果id有一定的公共前缀,可以增加压缩比),term查询首先就是找到词,然后再根据文档id找到文档记录。如果我们每次查找都去找倒排索引,由于倒排索引存储在磁盘上,那就需要遍历磁盘上倒排索引记录,会进行多次磁盘IO,严重影响查询性能。

联想:如果索引中查询频繁的字段值有公共前缀,那在分词表是不是就比较接近,比较容易分配到同一个block中,可以加快查询速度

而FST相当于是倒排索引的一个二级缓存索引树,在生成倒排索引之后,根据分词表,将原先的分词表划分为多个block,每个block包含25-48个词,将每个block里的词的公共前缀取出来作为作为1个节点,其叶子节点对应是block的首地址,形成一个前缀树,放在堆内存中,永不回收,这就是FST。(可以联想到如果节点上的段很多,这部分占用的堆内存将会很多)

在term查找的时候,先根据term关键字遍历堆内存中的FST,找到对应term关键字匹配的前缀节点,找到该前缀节点下的block首地址。再读取本地磁盘上的分词表,将对应的block读取到内存,找到term关键词对应的文档id,然后根据文档id找到磁盘上的原始文档数据

在refresh和merge生成新的段时,会在磁盘生成一系列的段文件,其中有一个.tip文件就是FST文件,生成tip文件之后,lucene会将每个字段的FST(es里会对索引结构里的每个设置为index的字段创建倒排索引和FST)数据解析处理,永驻在堆内存里,直到段被删除。

优化方案:
1、扩大FST中每个block包含的词的数量,block变少,FST中占有的内存也会下降–会降低查询性能

2、将FST从堆内存移到堆外内存,交给MMAP管理,nmap属于page cache,会被回收,在大量读写下,会频繁被回收,频繁从磁盘读tip文件,性能反而大大下降

3、将FST从堆内存移到堆内存,新建一个cache来管理,使用LRU策略,进行缓存更新。
每次根据key去cache中找到FST,返回FST对象,拷贝加载到对内内存,进行查找。

4、将FST从堆内存移到堆内存,新建一个cache来管理,使用LRU策略,进行更新
每次根据key去cache中找到FST,返回FST对象地址,在cache中直接进行查找。

Elasticsearch 对于分片的处理

July 24th, 2020

elk在我们的生产环境中有广泛的应用,最近发现elk查询速度缓慢,最后查阅了大量资料,发现分片数量对查询性能有巨大影响,但是由于是线上系统,所以对于数据的处理比较谨慎。

1. 创建共享目录
在三个节点上创建共享目录/mnt/backup/,使三个节点的es用户都可以读写该目录。(推荐使用nfs创建共享目录)

2. 修改ES配置
在elasticsearch.yml中加入一行: path.repo: [“/home/backup/”],修改后重启es集群。

3. 创建备份仓库

PUT /_snapshot/fs_backup
{
  "type": "fs",
  "settings": {
    "location": "/home/app/elk_data",
    "compress": true
  }
}

PUT /_snapshot/fs_backup/snapshot_indicator_value?wait_for_completion=true
{
  "indices": "xxxxxx",
  "ignore_unavailable": true,
  "include_global_state": true
}

由于这个是一个耗时的操作,所以我们可以通过 GET _cat/tasks?detailed 查看备份状态。备份完毕以后,我们使用reindex进行分片,yyyy必须是事先创建好

{
...
  "settings": {
    "number_of_shards": 5,
    "number_of_replicas": 1
  }
...
}

 

POST _reindex?slices=auto&refresh
{
  "source": {
    "index": "xxxx"
  },
  "dest": {
    "index" : "yyyy"
  }
}

这个reindex也是一个耗时操作,需要等待执行完毕,执行完毕以后,使用alias别名,访问xxxx即可,通过迁移,发现api耗时降低一半,用户体验明显变好了。

DELETE xxxx
GET /_aliases
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "yyyy",
        "alias": "xxxx"
      }
    }
  ]
}

参考:

https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html

 

Elasticsearch 学习

February 27th, 2020

elasticsearch 的层次结构和关系型数据库不一致,分为Index/【Type】/Document三个层级,对标Mysql就是:

ELK DB
Index Database
Type(已废弃) Table
Document Row
Column Filed
Schema Mapping
SQL DSL

在7.0以前,一个index会设置多个types,目前type已经废除,7.0后只允许创建一个type –> _doc

但是Document中es允许不同的结构,但是最好保持相同,这样有利于提高搜索效率。当我们插入一条数据后,如果没有指定_id的话,es会随机分配一个_id,如果强加指定的话,会按照该id存储Document。但是如果频繁对数据进行修改,随着插入数据的变多,自定义的_id会出现冲突的问题,可能在后期sharding出现查询缓慢的问题,需要额外注意。

至于移除type的原因主要是因为Lucene导致的,具体查看 https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html

分片可以分为主分片(primary key)和复制分片(replica shard)

主分片
每个文档都被分派到索引下的某个主分片内。
当索引创建完成时,主分片的数量就固定了。(这里存在一个问题,随着数量的增加,可能会出现不够的情况,最好保持一个shard<=30GB)
主分片的大小理论上是无限制的。
所有的写操作只能在主分片上完成后才能复制到其他分片上,写操作包括新建,索引,更新,删除。

复制分片
复制分片是主分片的副本,用以防止硬件故障导致的数据丢失。
复制分片可以提供读操作,比如搜索或从别的shared取回文档。
复制分片可以支持横向拓展。

Document 元数据,其中的_source是原JSON文件,_id 就是es中唯一标识的一个字段。

{
  "_index" : "movies",
  "_type" : "_doc",
  "_id" : "1163",
  "_version" : 1,
  "_seq_no" : 875,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "@version" : "1",
    "year" : 1994,
    "title" : "Mina Tannenbaum",
    "genre" : [
      "Drama"
    ],
    "id" : "1163"
  }
}

Document settings

{
  "movies" : {
    "settings" : {
      "index" : {
        "creation_date" : "1582618004949",
        "number_of_shards" : "1",
        "number_of_replicas" : "0",
        "uuid" : "JvyjYwMeTk6OmxhSRo-ocA",
        "version" : {
          "created" : "7060099"
        },
        "provided_name" : "movies"
      }
    }
  }
}

Document mappings

{
  "movies" : {
    "mappings" : {
      "properties" : {
        "@version" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        ......
    }
  }
}

从这个里面看到@version 字段 既可以全文匹配,也可以根据关键词匹配。

另外一个非常重要的功能就是 analyzer ,analyzer有现成的也有手写的,总的规则就是:

{
    "settings": {
        "analysis": {
            "char_filter": { ... custom character filters ... },//字符过滤器
            "tokenizer": { ... custom tokenizers ... },//分词器
            "filter": { ... custom token filters ... }, //词单元过滤器
            "analyzer":    { ...    custom analyzers      ... }
        }
    }
}

一个分词就是先对文档进行过滤(char_filter),然后进行分词(tokenizer),然后再对分词后的词进行过滤(filter),我们可以将这几个部分组装在analyzer中,然后放入setting,最后在mapping中引用my_analyzer即可。

{
    "settings": {
        "analysis": {
            "char_filter": {
                "&amp;amp;_to_and": {
                    "type": "mapping",
                    "mappings": [ "&amp;amp;=&amp;gt; and "]
            }},
            "filter": {
                "my_stopwords": {
                    "type": "stop",
                    "stopwords": [ "the", "a" ]
            }},
            "analyzer": {
                "my_analyzer": {
                    "type": "custom",
                    "char_filter": [ "html_strip", "&amp;amp;_to_and" ],
                    "tokenizer": "standard",
                    "filter": [ "lowercase", "my_stopwords" ]
            }}
}}}

CRUD就不用说了,除了可以使用Postman进行接口调试,也可以使用Kibana的Dev Tool

 

https://www.elastic.co/guide/index.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html

elasticsearch 配置遇到的问题

October 7th, 2017

为了存储海量数据,以便进一步进行数据分析,调研了一段时间,elasticsearch是个不错的选择。
elasticsearch 是一个用于搜索领域的分布式数据库,基于jdk为jdk1.8.0_73以上。不同于mysql之类的关系型数据库,elasticsearch基于RESTful web接口 需要使用POST/GET/DELETE/PUT来处理数据。我采用elasticsearch-py接口对数据库进行CRUD。不过在启动的时候,发现如下问题:

[2017-12-27T21:07:13,695][INFO ][o.e.t.TransportService   ] [node-1] publish_address {192.168.228.134:9300}, bound_addresses {[::]:9300}
[2017-12-27T21:07:14,005][INFO ][o.e.b.BootstrapChecks    ] [node-1] bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[2017-12-27T21:07:14,155][INFO ][o.e.n.Node               ] [node-1] stopping ...
[2017-12-27T21:07:14,468][INFO ][o.e.n.Node               ] [node-1] stopped
[2017-12-27T21:07:14,482][INFO ][o.e.n.Node               ] [node-1] closing ...
[2017-12-27T21:07:14,764][INFO ][o.e.n.Node               ] [node-1] closed

这个时候需要扩大虚拟内存堆:sysctl -w vm.max_map_count=262144

另外安装elasticsearch-head在5.x版本后需要借助nodejs服务,这一块配置安装比较繁琐,先要配置nodejs/npm/grunt。

npm安装:

curl https://npmjs.org/install.sh | sh
sh install.sh

如果出现 npm cannot be installed without Node.js. Install Node.js first, and then try again. 则需要 安装Node.js

apt-get install nodejs 如果node.js版本过低,则需要升级

# 第一步:首先安装 n 模块:
npm install -g n
# 第二步:升级node.js到最新稳定版
n stable

node 环境安装完毕后安装 elasticsearch-head 所需模块:

git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start

配置完 elasticsearch-head,要在./config/elasticsearch.yml中打开注释,然后重新启动es。

http.port: 9200
# 跨域
http.cors.enabled: true
http.cors.allow-origin: "*"

 

参考:

https://my.oschina.net/kittyMan/blog/387512?p=1
http://orchome.com/489