16

写在前面
从初次了解elastic产品到正式投入使用,拖拖拉拉的也有小半年了,刚接触的时候看到一些帖子都是安装教程,后来看到一些都是深入教程,此篇文章较居中一点,总结了我在踩的一些坑和记录一些周边插件的使用方式、方法,便于自己后续回顾,也希望能给新用户一些引导,少走一些弯路;核心其实是想表达一下对rockybean和KennyW的爱,这期间非常感谢两位的协助,在非工作日深夜排查问题多次,正文多处采用二位给予的讲解,万分感谢。

ElasticSearch简介

  • 基于Lucene构建的分布式,RESTful搜索和分析引擎;
  • 实时搜索、分析,稳定,可靠,快速;
  • JAVA编写,开源,使用JSON开源通过HTTP来索引数据;

项目介绍

  • 历史:
    由前同事工作交接,原有一套ES1.7集群,但是经常jvm跑满导致集群不可用,没有ES调优经验,甚至没有使用经验,从零了解ES,过度版本ES5.3~5.6;
  • 背景:
    数据源主要是Nginx访问日志,由于Nginx是集群,相应的日志分布落在每台机器上,当对整体日志做一些数据分析、故障排查等,因数据分散且量很大,通过脚本已经不能做分析处理了,为了对Nginx日志做全文搜索、分析,实时监控等,所以踏上ELK之路;
  • 数据量:
    每天6-7T,Docs在100亿左右

架构图

图片描述

第一部分:日志收集(Nginx + Rsyslog)

  • 采用Nginx内置的syslog模块,每台机器启用本地的rsyslog,通过UDP方式传输本地的514端口(Rsyslog),然后Rsyslog在将数据转发至Kafka;
  • 选型比较:日志收集有很多种方式,如flume、filebeat、lua脚本等,但是这些组件都需要额外安装客户端,而rsyslog在linux都集成了。简单对比过Rsyslog和其他区别,首先在Nginx中的syslog对于一个更改相对灵活,JSON日志与落地日志毫无干系(目前是两份格式:人肉读取的本地日志格式,机器读取JSON网络传输格式),日志输出直接通过网络传输走(网络消耗很低)不受本地磁盘影响,对不同的server_name或location等可以灵活的修改,总之控制权在Nginx手里,Nginx的维护同学就可以自定义源数据格式,管理成本相对较低;
  • Rsyslog配置(双打Kafka)
    现有的版本是0.8,而刚开始测试的用logstash5.x需要kafka0.10(最终hangout替换logstash),所以新搭建了一组新的集群,Rsyslog向两个Kafka集群分别写数据,配置如下

    Module (load="imudp")
    Module (load="omkafka")
    Input (type="imudp" port="514")
    Module (load="mmsequence")
    $MaxMessageSize 4k
    
    local5.none /var/log/messages
    local5.none @log.domain.com:514
    set $!newmsg = replace($msg,'\\x','\\u00')
    
    template(name="kafka_topic" type="string" string="%programname%")
    template(name="kafka_msg" type="string" string="%!newmsg%")
    if ($syslogfacility-text == 'local5' and $syslogseverity-text == 'info') then{
    
    action(type="omkafka" topic="kafka_topic" partitions.auto="on"
    dynatopic="on" dynatopic.cachesize="1000"
    confParam=["compression.codec=snappy"]
    #kafka broker addr
    broker=["10.10.10.1:9092","10.10.10.2:9092",]
    template="kafka_msg"
    errorfile="/var/log/omkafka/log_kafka_failures.log")
    
    action(type="omkafka" topic="kafka_topic" partitions.auto="on"
    dynatopic="on" dynatopic.cachesize="1000"
    confParam=["compression.codec=snappy"]
    #kafka broker addr
    broker=["20.20.20.1:9092","20.20.20.2:9092",]
    template="kafka_msg"
    errorfile="/var/log/omkafka/log_kafka_failures.log")
    
    stop
    }
    
  • 配置Nginx JSON格式日志

    
    log_format json_format  '{"@timestamp":"$time_iso8601",'
            '"cookie_id":"$cookie_id",' #内部cookie_id
            '"client_ip":"$remote_addr",'
            '"remote_user":"$remote_user",'
            '"request_method":"$request_method",'
            '"domain":"$host",'
            '"user_agent":"$http_user_agent",'
            '"xff":"$http_x_forwarded_for",'
            '"upstream_addr":"$upstream_addr",'
            '"upstream_response_time":"$upstream_response_time",'
            '"request_time":"$request_time",'
            '"size":"$body_bytes_sent",'
            '"idc_tag":"tjtx",'
            '"cluster":"$host_pass",'
            '"status":"$status",'
            '"upstream_status":"$upstream_status",'
            '"host":"$hostname",'
            '"via":"$http_via",'
            '"protocol":"$scheme",'
            '"request_uri":"$request_uri",'
            '"http_referer":"$http_referer"}';
  • Nginx内置syslog模块配置,并且引用刚刚定义的json日志格式

    access_log syslog:local5:info:127.0.0.1:514:nginx_aggregation_log json_format;
    #nginx_aggregation_log   这是自定义的Topic

    NginxSyslog模块介绍
    图片描述

    注:

    1) UDP传输虽快,但是以太网(Ethernet)数据帧的长度必须在46-1500字节之间,UDP不能像TCP重组数据包,去除IP和UDP的数据包,最终可使用只剩1472字节。如果传输大于这个长度的消息,并不会想UDP本身一样直接丢弃,只是会损坏源数据格式,截断超过限制字节以外的数据;
    2) 对于Nginx日志来说,只要不保留POST数据,基本一条消息不会超过限制字节,我在NginxSyslog介绍中没看到支持TCP,用lua脚本实现的TCP方式传输,但是看了很多帖子都不建议在Nginx中用TCP日志传输。就是因为TCP传输可靠,但像网络抖动、传输异常,可能会不停的重试多次或等待,直接影响这条请求,也直接影响到了用户;
    3) 消息超过了UDP传输限制怎么办,我这目前是保留一条消息的重要字段,如上述的json_format的格式,将 request_uri、http_referer等可能会较大的字段放到最后,如果真的发现消息不完整,直接丢弃http_referer,取request_uri问号前的内容;(在logstash或hangout中filters实现,具体配置详见下文Hangout-filters)

第二部分-存储中间件(Kafka)

  • Kafka性能很强,顺序写入磁盘,高吞吐量的分布式发布订阅消息系统
  • Kafka一直不是瓶颈,也没太多深入优化,Topic数据保留了12小时,副本为1
  • 针对不同的Topic,对Partition的数量有稍微改变,目前是5台服务器,之前简单测过增加Partition的性能,从8、16、32、64增加来看,明显情况就是Partition增加,CPU使用也会随之增加,因为kafka本身不是瓶颈,其他明显问题也未遇到;
  • 目前这边最大的Topic是一天近5T数据,64Partition没有任何问题,部分小的Topic都是16Partitio,Kafka整个集群的CPU空闲都在80%以上,内存、IO均无压力,后续也考虑缩减机器。这边的kafka团队有个建议值,供大家参考:【每天数据规模小于50G选择4分区、50G且小于100G选择8分区、大于100G且小于500G选择16分区、大于500G,选择24分区】
  • Kafka监控插件:kafka-monitorkafka-manager

    注:
    目前我们这kakfa集群是kafka_2.10-0.8.1.1版本,但是logstash5.x对kafka有版本要求>0.10版本。后来采用hangout,更换了几个jar包解决了此问题

第三部分-数据搬运工(Hangout

  • 模仿logstash做的一个应用,功能没有logstash多,但是基本使用都有了,java编写,性能可以翻好几倍,用到的功能就是从kafka订阅消息,做一些简单的过滤,然后写入ES;目前hangout部署到2台服务器上,每个进程开8G内存,CPU在60-70左右;

    inputs:
       - Kafka:
        topic: 
            nginx_aggregation_log: 32
        codec: 
            json
        consumer_settings:
            group.id: es-nginx_aggregation_log
            zookeeper.connect: "10.10.10.1:2181,10.10.10.2:2181"
            auto.commit.interval.ms: "20000"
            socket.receive.buffer.bytes: "1048576"
            fetch.message.max.bytes: "1048576"
            num.consumer.fetchers: "1"
    filters:
       - Filters:
       if:
            - '<#if message??>true</#if>' 
            #如果不是完整的JSON,会出现message,则走此逻辑
       filters:
           - Grok:
               match:
                 - '(?<msg>{"@timestamp":.*"request_uri":([^\?]+)\?)'
                 #正则匹配@timestamp开始到request_uri后边的第一个?截止
           - Gsub:
               fields:
                   msg: ['$','"}']
                   #补全符号,完整新的JSON格式
           - Json:
               field: msg
               remove_fields: ['message'] 
               #干掉错误的数据
    - Convert:
        fields:
            request_time:
                to: float
                remove_if_fail: true
            upstream_response_time:
                to: float
                remove_if_fail: true
            size:
                to: integer
                remove_if_fail: true
    - GeoIP2:
        source: client_ip
        database: '/opt/soft/hangout/etc/other/GeoLite2-City.mmdb'
        - Json:
             field: geoip
    - Remove:
            fields:
                - msg
    - Add:
           fields:
              request_url: '<#assign a=request_uri?split("?")>${a[0]}'
              #request_uri这个term的cardinality很高,所以?前用于聚合,原有的用于搜索
           if:
             - '<#if request_uri??>true</#if>'
    outputs:
    - Elasticsearch:
    cluster: es-nginx
    timezone: "Asia/Shanghai"
    hosts: "10.10.10.1:9300,10.10.10.2:9300"
    index: 'hangout-nginx_aggregation_log-%{+YYYY.MM.dd}'
    
  • Hangout进程管理工具(supervisord
    主要是守护hangout进程,在web界面对hangout进行启、停、重启等操作
  • topic: nginx_aggregation_log: 32,无论是logstash还是hangout都有这个概念,这个32代表需要建立多少子线程去kafka读取数据,数量最好与Partition相等,如果少于Partition,会一个线程同时去2个Partition读取消息,若大于Partition则会有不工作的进程

第四部分-Elasticsearch(后面简称ES)

  • 硬件环境

    CPU:32C,内存:128G ,硬盘:STAT 6T * 12,网卡:万兆
    
  • 软件环境:

    【系统】: Centos7 内核3.10
    【JDK】: 1.8.0_66/31G (据说此版本JDK有BUG,请安装最新JDK)
    【系统参数修改1】: vm.swappiness=1 [降低对硬盘的缓存]
    【系统参数修改2】: vm.max_map_count=262144 [Elasticsearch针对各种文件使用NioFS和MMapFS的混合。以便有足够的虚拟内存可用于mmapped文件] 
  • ES配置文件

    cluster.name: es-nginx
    node.name: 10.10.10.1 
    
    #为后期冷热数据使用
    node.attr.rack_id: hdd 
    path.data: /data 
    path.logs: /opt/logs/elasticsearch/
    network.host: 0.0.0.0 
    http.port: 9200
    
    #设置新节点被启动时能够发现的主节点列表
    discovery.zen.ping.unicast.hosts: ["10.10.10.1","10.10.10.2","10.10.10.3"]
    
    #防止脑裂(n/2+1)
    discovery.zen.minimum_master_nodes: 2
    node.master: true
    node.data: false
  • ES跳入的第一坑:node.master与node.data同时服务

    刚刚开始测试ES的第一个版本是ES5.3,先搞了3台机器,每个机器一个节点,配置是master和data共同提供服务,高可用架构集群搭建完成,但是写入性能特别差,cpu使用在20-30%,少量io.wait,下图是当时3w左右的性能图图片描述当时觉得既然ES硬件很空闲一定是logstash出问题了,查看logstash确实有很严重的Full GC,开始从2台服务器扩至4台服务器,后来发现无果,期间各种调整ES的shard的数量都没效果,又怀疑kafka性能,从2、4、6、8...64分区依旧无果。当时这个坑可爬了一段时间,后来在Google的游荡中无意中看到帖子说,不要将master和data都启用,然后我照着做了改变,master单点,data两台,问题搞定,效果图找不到了,起码翻倍是有的;

    [Master除了网卡,其他没什么消耗]
    
    
  • template

    因shard数量、字段类型、其他设置等都是都是在创建时生成,所以要提前创建好相应的模板,便于规范管理和引用,下面针对shard和aliases做的一些设置,如下:

    {
          "template": "agg-nginx-*",
          "aliases": {  
            "agg-nginx": {}  
          },
          "settings": {
            "number_of_shards": 4,
            "number_of_replicas": 1,
            "index.routing.allocation.include.rack_id": "ssd"
          }

通过上述配置PUT到 _template/ur_name下在分片上的定义已经成功,但是像agg-nginx-和test-agg-test-这样的2个索引名字,即使你创建了另一个"template": "agg-nginx-test-*"的模板依旧都匹配第一个,当然换名字最简单有效,在template的order的是专门解决这个问题的。默认创建"order": "0",值越高优先级越高,所以在想要先匹配的将order值调高即可

  • mapping

    ES的mapping非常类似于静态语言中的数据类型:声明一个变量为int类型的变量, 以后这个变量都只能存储int类型的数据。同样的, 一个number类型的mapping字段只能存储number类型的数据。同语言的数据类型相比,mapping还有一些其他的含义,mapping不仅告诉ES一个field中是什么类型的值, 它还告诉ES如何索引数据以及数据是否能被搜索到

    下列是一个删减版的mapping
    "mappings": {
        "ngx_log": {
           "_all": {
            "enabled": false
          },
          "properties": {
            "@timestamp": {
              "type": "date"
            },
            "client_ip": {
              "type": "ip"
            },
            "domain": {
              "type": "keyword"
            },
            "geoip": {
              "properties": {
                "city_name": {
                  "type": "keyword"
                },
                "country_name": {
                  "type": "keyword"
                },
                "latitude": {
                  "type": "float"
                },
                "location": {
                  "type": "geo_point"
                },
                "longitude": {
                  "type": "float"
                },
              }
            },
    
            "request_time": {
              "type": "float"
            },
            "request_url": {
              "type": "keyword"
            },
            "status": {
              "type": "keyword"
      ype": "keyword"
            },
            
          }
        }
      }
  1. _all字段
    该_all字段是一个特殊的catch-all字段,它将所有其他字段的值连接成一个大字符串,使用空格作为分隔符,然后对其进行分析和索引,但不存储。也就是说它能被查询,但不能被取回显示。因为Nginx每个Key对应的value都是提前定义好的,所以不用全文查询,不需要开启_all字段,另外也节省了一半的存储空间

  2. 默认的text类型
    图片描述上边这英文有点多,其实简单理解就是不分词,你就最好别用text了,而且Text类型也会相应的多占用空间,依照上述,数据主要是日志分析,每条数据的格式已经很明确,主要用于日志分析,所以不需要分词。像一些所有引擎的业务更适合需要分词;

  3. 比如说像这个字段,get_ip中location这个字段类型默认text,但是如果不指定geo_point类型,根本无法使用地图功能,类型的指定是很重要的
  4. 向request_time这样的数据类型需要做计算,比如说平均值、和、大于、小于等等的,默认的text也能使用,但是效率远远小于float类型
  5. 字段类型有很多种,什么IP啊、DATE啊等等,根据相应的需要去官网查看详解吧,mapping-types介绍
  • shard & replicas

    [摘取部分苏若年博客内容]
    1)分片算法:
    shard = hash(routing) % number_of_primary_shards
    routing值是一个任意字符串,它默认是_id但也可以自定义,这个routing字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是0到number_of_primary_shards - 1,这个数字就是特定文档所在的分片。

    这也解释了为什么主切片的数量只能在创建索引时定义且不能修改:如果主切片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。

    所有的文档API(get、index、delete、bulk、update、mget)都接收一个routing参数,它用来自定义文档到分片的映射。自定义路由值可以确保所有相关文档.比如用户的文章,按照用户账号路由,就可以实现属于同一用户的文档被保存在同一分片上。


    2)分片与副本交互:

    新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上,下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤:

    1、客户端给Node 1发送新建、索引或删除请求。

    2、节点使用文档的_id确定文档属于分片0。它转发请求到Node 3,分片0位于这个节点上。

    3、Node 3在主分片上执行请求,如果成功,它转发请求到相应的位于Node 1和Node 2的复制节点上。当所有的复制节点报告成功,Node 3报告成功到请求的节点,请求的节点再报告给客户端。

    客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。你的修改生效了。

    一个索引要分多少片?什么时候该扩容?
    取决于硬件和你对响应速度的要求,一般来说一个shard的数据量控制在1、2千万的级别,速度都还好,过亿会比较缓慢。 但是任何事物都有两面,shard划分比较小,必然数量就比较多。 在用户做1、2天数据搜索的时候可能还好,如果搜更长时间的数据,一次搜索需要并行搜索的shard就比较多。如果节点数量有限,就会比较吃力,需要扩充更多的节点

  • routing

    据说是优化之王道,经常拿城市举的例子,比如说我想看下网站的北京pv是多少,如果按照默认hash逻辑,一定要全shard扫描,然后聚合结果,但是如果提前设置好routing,比如说指定城市字段做hash计算,routing值一样的放到特定几个分片,这样查起来也不需要全shard扫了;这样弊端就是会造成shard大小不均,所以routing优化需要花一些功夫来观察、测试;目前kibana还不支持routing查询,所以目前在kibana上还没有使用routing,这是优化的重点所以先记录下来。后续我的想法是,像nginx日志的域名的字段都是英文字母,针对首字母做下routing,当想看某一个域名时不在全盘扫,查询优化会有明显效果,后续有结果在与大家分享;
    另外hangout开始对routing的支持,后来在GitHub提了一个小issue,很快就加上了,点个赞;

第五部分-Kibana图形展示

Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看、交互存放在Elasticsearch索引里的数据,使用各种不同的图表、表格、地图等kibana能够很轻易地展示高级数据分析与可视化。
先介绍下几块功能:

  • X-pack可以免费使用部分功能,最强就是monitoring功能了,基本重要指标都收集了
  • Dev_tools的代码补全神器,类似于IDE;还有强劲的可视化Search Profiler,便于直接定位问题点
  • 其他就是常用的Discover、Visualize、Dashboard、Timelion搜索和绘图功能了,没有找到合适的帖子,直接吧迷妹儿的帖子放过来吧,主要介绍了如何优雅的使用kibana的搜索框
  • 还有就是Elastic官方提供了一个Demo,便于大家做图形展示的时候参考和借鉴

(╯﹏╰)吐槽一下,用SF写到现在,Chrome都快没法用了,打完字一会才显示出来

  • 直接上几张最终成果图吧

  • [QPS展示]
    QPS展示
  • [QPS环比]
    图片描述
  • [异常状态码趋势]
    图片描述
  • [慢请求QPS]
    图片描述
  • [状态码比例]
    状态码比例
  • [Domin与URL TOP]
    图片描述
  • 再来一张深夜[热图],北京和上海的孩子总是不碎觉
    图片描述
  • 上图中环比图是Timelion完成的,其他都是Visualize的功能
    Timelion语法:

    .es(index=index1,timefield=@timestamp).label('Today').title(QPS).color(#1E90FF), 
    .es(offset=-24h,index=index2,timefield=@timestamp).label('Yesterday').lines(fill=1,width=0.5).color(gray)
  • kibana认证问题
    由于x-pack试用版将认证功能被阉割掉了,直接暴露内容太风险,所以利用Nignx插件auth_basic的功能,做了一个简单验证模块

最后调优

核心问题:查询慢、查询15Min数据都超时

  • 数据量:
    每天数据在6-7T,Docs在100亿左右,其中一个大的索引在4-5T
  • ES升级
    版本升级会带来一些新特性,以及一些bug修复,当然也会引发新问题,我就踩到了新版本的坑;
    ES5.x升级到5.5是滚动升级,相对升级步骤简单,但是由于数据太大,动辄几十T,一台台完成升级,却是一个漫长的过程。另外,1.x升级据说很痛苦,有兴趣的去看看,连接放在这里reindex-upgrade
    简述下5.x升级的过程
    1)关掉分片自动分配:"cluster.routing.allocation.enable": "none"
    2)强制刷新,尽可能将缓存中的数据,写入磁盘: _flush/synced
    3)停止一个节点,开始升级及升级插件,然后启动
    4)打开分片自动分配:"cluster.routing.allocation.enable" :"all"
    5)等待集群恢复至绿色,继续下一台
  • 扩容
    扩容很简单,没什么可说的,除了配置文件中node.name不一样,其他都一样;
    最初扩容只是将data节点从3台扩至20台,写入没问题,1-2k的数据,单机性能写入在30-40k;

  • mapping字段优化:

    主要像上述的mapping介绍,做类型优化,不分词的keyword,数学计算的改成整型or浮点等

    status这个字段的类型,value一般都是200、301、302、404、502,最多估计也就几百个,像这样的字段就不适合做long,long类型的索引是为范围查找优化的,用的是二叉树这样的索引,适合值范围比较大的字段,比如body_size,可能最大值和最小值相差很多,而用keyword索引是倒排,只用存放一个所有status的词典,所以keyword更适合

  • shard数量调整:

    开始主分片数量是每台机器1个,副本1(20shard x 1replicas),每个shard已经达到将近200G,看这个量级已经超过官方建议值30~50G之间(具体要根据实际情况测试为准)。于是开始将数量翻倍40shard * 1replicas,调整后查询并没有明显改善,对写入没有什么改变,继续double,依旧没效而且分片越多,写入的时候消耗的CPU就越高

    如果过度分配shard,就增大了Lucene在合并分片查询结果时的复杂度,从而增大了耗时,在新建索引时,更是一笔大的开销。
    一般来说,刚开始的时候尽量少分片为佳, 只有到一个分片上数据太多,单次查询太慢在考虑加分片。
    加分片也有负面作用,提高了并发量,随之开销就会越大,更多的shard一般也伴随更多的segment文件。如果说节点数量没变,每个节点上会有更多的小文件,搜索的时候并发量是高了,前提是在磁盘io和cpu都还能应付的过来的时候,速度才会快

  • 拆分索引
    更改shard已经得不到显著效果,于是从拆索引下手。数据都是Nginx日志,从源头拆分没什么好的方法,于是从hangout这层开始做处理,很简单就是将域名的第一个字母取出来,写入相应的索引时候带过去,例如:nginx-log-{首字母},这样一拆,一下创建26个索引(shard20 * 1replicas),CPU立马load 30+,负载直接上来,然后ES数据还跟不上,拒绝了很多内容,如下图,最终还是无果而告终此方案
    图片描述

    上述的索引拆分是比较傻瓜式,首先已知的问题就是可能A开头的域名很大,其他很小就是很不均匀

  • Pcle-SSD测试
    经过上面的折腾,已经没有什么突破点,于是借用了8台SSD的机器,修改ES配置node.attr.rack_id: ssd,将数据写入SSD,全程io无压力,写入一个一天4-5T的索引,CPU在50%左右,但是查询依旧不给力,经过无数shard调整依旧不给力,IO一直没空闲,最终结论就是没有将ssd的性能发挥出来,还是未解决根本问题
  • kibana BUG
    之前没有ES相关经验,能想到的办法、关注点都没有什么在突破的地方了,在束手无策的时候,又非常有幸认识了KennyW,再次感谢KennyW的支持;问题根本原因是kibana5.4-5.5这两个版本在查询问题,为了节省空间,我关掉了_all这个字段,如果搜索框不输入内容的时候,则会补充*查询所有字段,生成的bool条件就非常多,引发kibana的缺陷,引用:
    图片描述
    当时的版本KennyW给的知道是临时修复方法如下:图片描述
    详细问题请看这里ES 5.4+ 引起的Kibana性能问题,Kibana5.5.2后已经修复此问题,所以最终版本还是升级到了5.6,也是目前的最新版
  • 回归HDD
    经过SSD的折腾,查到了kibanaBug问题,其实SSD并未发现其他的性能亮点,经过与KennyW的交流,他们自己有做过在SSD和多块HDD的盘对比,写入量并无明显提升,所以在磁盘并不是真正的瓶颈,查询方面SSD明显提高。但是SSD的高额付出换来那么几秒钟的意义不大。相对,对一些公司的搜索业务,数据量级小,还有像一些监控业务,要求实时性非常高,对SSD是很好的选择,速度快,容量不需要太多,也比较经济实惠

    [KennyW经验指导]
    当做复杂计算,bucket很多的时候主要消耗的是CPU和内存,磁盘并不是瓶颈,hdd和ssd在我们实际用下来,感觉在大数据场景分别不大,ssd优势在大量的随机磁盘io,ES本身做了很多优化,让数据变成顺序的磁盘访问,而且搜索过的数据块,ES都能利用文件系统缓存加速,所以即使使用hdd,可能第一次搜索磁盘访问会带来额外的几秒耗时,但多次执行同一个搜索时,后面几次几乎没什么磁盘io开销,速度会有明显提升

  • RAID0的重要性
    当所有的集群全部更改回hdd的集群时候,发现一个之前没太关注的指标,每块盘的利用率。刚开始解决的时候我们的机器都是裸盘直接挂上,path.data:data1,data2,data...这样挂载,但是在5.x版本发现这样磁盘问题(CMD:iostat -x 1)图片描述这样看来很奇怪,为什么这块盘这么忙,多观察一会发现,一会又发现其他的盘又很忙,然后df查看磁盘使用率,扎心了
    图片描述挂了这么多盘,真实写入连一半的盘都不到,既然这样,没得说的了,直接上raid0吧
  • 收尾
    做到目前这些调整,现在一天小于1T的索引任何查询都没有问题,查询在10s左右返回所有数据(上图kibana展示),但是那个一天4-5T的索引还是有问题,一查询IO就跑满,到30s直接超时了,先说下IO跑满的问题吧,问题是request_uri台过于分散,聚合出现问题如图:15分钟就出现了2000多万不同值,如果长时间计算,不可想象的恐怖
    图片描述

    【KennyW指导】
    request_uri 会产生大量的磁盘IO。 ES做terms聚合的时候,为了节省内存,不会将所有term的内容直接读出来做bucket,因为有些term的内容可能很长,会比较耗费内存。 所以他会借助一种叫做oridinals的数据结构, 这种数据结构类似这样
    1 abc
    2 efg
    3 hfa
    .............

    一个该字段所有不同值的顺序列表。 做分桶聚合的时候,只需要拿这个顺序数字做key就可以了,等聚合出结果,再通过这个key查ordinals表,得到实际的key值填充结果。 但是这个ordinals是随着segment 文件生成的,每个segment文件的ordinals顺序可能不一样。 因此在聚合的时候,需要一个全局的global ordinals。 这个数据结构是在聚合的时候,实时生成后缓存在内存里的。 如果某个字段的不同值非常多,计算价值非常的昂贵,那么这个构造过程就非常的缓慢,除了大量的磁盘IO消耗,还会有很大的内存消耗。

    下图是关掉这个有问题的visualize前后对比图,虽然不快,但是latency降了很多图片描述
    后来对uri的?后边的参数全部丢弃,像这样的问题只能减少然后做聚合使用,原有数据做搜索使用,但是由于数据太大,做复杂计算还是会超时30s,这种情况只能是降低team的cardinality,或者加分片、加机器或者拆索引了,所以对kibana的超时时间做了一点调整,还有一些周边小的修改如下
    1)kibana默认的30s超时改成2min,kibana.yml中修改
    elasticsearch.requestTimeout: 120000
    2)kibana默认地图使用高德,kibana.yml中新增
    tilemap.url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
    3)结合cerebro插件,高效管理索引

后续

到这里基本的一期项目算是结束,虽然部分查询并没有迅速返回,但是基本数据都可以展示,后续会关注几个点继续深入优化和调整,有结果在与大家分享

  • routing优化查询
  • curator管理过期索引
  • 比较大index索引做拆分
  • 增加client节点,减少集群影响
  • 冷热数据分离(node.attr.rack)、定期对冷数据force_merge,优化查询速度

总结

首先在这里还是先要感谢rockybean和KennyW的大力支持。
对自己的总结就是,对ES经验太少,踩了很多不必要的坑,另外就是没有好好统一阅读下官网文档,这样极其影响效率与进度,好多时候也会束手无策,不知从何下手。
第一次写技术帖,而且时间稍紧,有哪些地方写的不好或敏感烦请指出,我会及时更正。希望这篇文章,能给予像我这样的小白用户少踩一点坑,多一点爱。


zhangxing
56 声望6 粉丝

运维~