使用Elasticsearch记录日志踩过的坑经验分享

使用Elasticsearch记录日志踩过的坑经验分享

961发表于2019-11-30

之前有一个asp.net core项目采用的是Elasticsearch记录日志,使用的是NLog框架的ES target。

Nlog.Config.xml:

<!--ElasticSearch保存日志信息-->
<target name="ElasticSearch" xsi:type="ElasticSearch" ConnectionStringName="ElasticSearchServerAddress"
		index="lanhu-req-${date:format=yyyy.MM.dd}" documentType="doc" includeAllProperties="true"
  layout="[${date:format=yyyy-MM-dd HH\:mm\:ss}][${level}] ${logger} ${message}" requireAuth="true" username="admin" password="admin">
  <!--<field name="AppKey" layout="${event-context:item=AppKey}"  />-->
  <!--<field name="aspnet-Request-IP" layout="${aspnet-Request-IP}"/>-->
  <field name="MachineName" layout="${machinename}" />
  <field name="Time" layout="${longdate}" />
  <field name="level" layout="${level:uppercase=true}" />
  <field name="logger" layout=" ${logger}" />
  <field name="message" layout=" ${message}" />
  <field name="exception" layout=" ${exception:format=toString}" />
  <field name="processid" layout=" ${processid}" />
  <field name="threadname" layout=" ${threadname}" />
  <field name="stacktrace" layout=" ${stacktrace}" />
  <!--<field name="activityid" layout=" ${activityid}" />-->
  <!--<field name="aspnet-request-ip" layout=" ${aspnet-request-ip}" />
  <field name="aspnet-traceidentifier" layout=" ${aspnet-traceidentifier}" />-->
  <!--<field name="Properties" layout=" ${machinename} ${longdate} ${level:uppercase=true} ${logger} ${message} ${exception}|${processid}|${stacktrace}|${threadname}" />-->
  <!--<field name="Properties" layout="${exception}" />-->
</target>


我主要是从以下几方面来优化的。

一、es进程崩溃,服务自动关闭

1、服务自动关闭

经过排查发现有一个奇怪的现象,服务总是在每天的固定时间关闭。

经验告诉我们es自动关闭,一般都是内存不够了,OMM异常。后来查了一下服务器有定时任务,这个定时任务比较耗资源。

查看了一下es有日志,果然有报内存异常。查看es的jvm参数设置(jvm.options),是官方默认值(1g):

from clipboard

我把内存加到2G,加了内存后,这个问题暂时消失一段时间。


2、使用Kibana查询时间段长的索引,超时,然后进程崩溃。

查日志,又是OOM异常。为了这次把内存改为4G,这时服务器总内存为15G。


二、es内存持续增加问题

使用systemctl status es查看服务占用Memory一直在涨,担心会把内存耗尽。

++++++++++++++++++++++++++++++

es采用mmap的方式将索引文件映射到内存中,随着检索的次数增加,越来越多的数据被操作系统读入到内存中。这部分内存位于系统中,但是又不归es管理,也就是和jvm配置的堆内存大小无关。结果就是导致es总的内存不断增长。
解决该问题的版本是修改es的存储方式,配置参数index.store.type由mmap改成niofs。

niofs的性能也很好,对检索的性能影响并不大,并且能够有效的控制内存增长。可以认为这是一种按需读取数据的方式,读取的数据主要在系统的缓存中,这个内存是会被回收掉的,es的内存并不会疯涨。

在elasticsearch.yml增加以下设置:

index.store.type: niofs

from clipboard

关于es内存有一个32G现象:

Elasticsearch 默认安装后设置的堆内存是 1 GB。 对于任何一个业务部署来说, 这个设置都太小了。
比如机器有 64G 内存,那么我们是不是设置的越大越好呢?
其实不是的。
主要 Elasticsearch 底层使用 Lucene。Lucene 被设计为可以利用操作系统底层机制来缓存内存数据结构。 Lucene 的段是分别存储到单个文件中的。因为段是不可变的,这些文件也都不会变化,这是对缓存友好的,同时操作系统也会把这些段文件缓存起来,以便更快的访问。
如果你把所有的内存都分配给 Elasticsearch 的堆内存,那将不会有剩余的内存交给 Lucene。 这将严重地影响全文检索的性能。
标准的建议是把 50% 的可用内存作为 Elasticsearch 的堆内存,保留剩下的 50%。当然它也不会被浪费,Lucene 会很乐意利用起余下的内存。
同时了解过 ES 的同学都听说过「不要超过 32G」的说法吧。
其实主要原因是 :JVM 在内存小于 32 GB 的时候会采用一个内存对象指针压缩技术。
在 Java 中,所有的对象都分配在堆上,并通过一个指针进行引用。 普通对象指针(OOP)指向这些对象,通常为 CPU 字长 的大小:32 位或 64 位,取决于你的处理器。指针引用的就是这个 OOP 值的字节位置。
对于 32 位的系统,意味着堆内存大小最大为 4 GB。对于 64 位的系统, 可以使用更大的内存,但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。更糟糕的是, 更大的指针在主内存和各级缓存(例如 LLC,L1 等)之间移动数据的时候,会占用更多的带宽.
所以最终我们都会采用 31 G 设置
-Xms 31g
-Xmx 31g
假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene

三、cpu占用高

有一天服务器从15G增加到30G,es配置什么都没动,但是发现服务器特别卡,经过排查是cpu占用特别大,而且是es进程。尝试万能的重启,但是不管用。

经过各种努力和尝试,都无果,后来把es的jvm参数设置(jvm.options)从原来的4G,改为8G,问题解决了!

看到来如果服务器物理内存增加了,jvm内存也要相应的增加。


四、占用空间大的问题

由于项目需要把每次请求的原始信息记录下来,方便以后问题排查和追责。数据存储压力就比较大,一天光日志就1G多,500G的硬盘要不了多久就会满了。

通过以下方式来减少存储:

1、减少副本数

2、修改Index Templates优化日志索引结构

NLog默认的es的target生成的mapping是下图左边这这样的。


from clipboard

可以看到默认在每个字段上都加了一个嵌套字段名为keyword的类型为keyword,其实大多数都是用不上的。

from clipboard

一般我们只需要保留像时间和日志级别level,方便按时间或日志级别筛选。

优化后的mapping如上面图右边所示。

对于日志存储每天生成索引的,每个索引字段都是一样的,每次都要指定索引字段有点麻烦,es可以用 Index Templates来解决。

我们日志索引是一天一个,为了让将来自动创建的索引生效,注意:这个模板是对当前已生成的索引无效的。

我们需要利用Index template来创建一个模板,并带上索引Mapping,使用下面命令创建一个模板:

PUT /_template/tpl_lanhu-req
{
    "template": "lanhu-req*",
    "settings": {
            "refresh_interval": "30s",
            "number_of_shards": "5",
            "number_of_replicas": "1",
            "index.mapper.dynamic": false
    },
    "mappings": {
        "doc": {
            "properties": {
                "@timestamp": {
                    "type": "date"
                },
                "MachineName": {
                    "type": "text"
                },
                "Time": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type": "keyword",
                            "ignore_above": 256
                        }
                    }
                },
                "exception": {
                    "type": "text"
                },
                "level": {
                    "type": "text",
                    "fields": {
                        "keyword": {
                            "type": "keyword",
                            "ignore_above": 256
                        }
                    }
                },
                "logger": {
                    "type": "text"
                },
                "message": {
                    "type": "text"
                },
                "processid": {
                    "type": "text"
                },
                "stacktrace": {
                    "type": "text"
                }
            }
        }
    }
}

from clipboard

返回:"acknowledged" : true  表示成功。

template字段值需要与程序日志配置的名称模式匹配,“lanhu-req*"表示以lanhu-req开头,我们程序日志也就是这样。

from clipboard

经过上面的优化,存储基本上减少了一半。


ps:++++++++++++++++++++++++

查询模板:

GET /_template/tpl_lanhu-req?pretty


from clipboard


删除模板:

DELETE /_template/tpl_lanhu-req

ps++++++++++++++++++++++

优化成果=》占用内存基本上稳定,查询一周的日志都不会崩,而且服务运行了45天了没有崩过。

from clipboard






小编蓝狐