对话 Kibana 之父:如果需要,你应该自己动手编写工具
转载:http://www.infoq.com/cn/news/2 ... nTool
在 Elastic 中国开发者大会 2016上,ELK 正式宣布更名为“Elastic Stack”,Elastic公司称其开源项目累计已经有8000万次下载。Elastic Stack 最新版本为5.0,从此,Elastic公司会对Elasticsearch、Kibana、Logstash、Beats、X-Pack进行统一规划以同版本号码发布。会上,Kibana 的原作者 Rashid Khan 进行了题为《Kibana 5.0: The Window into the Elastic Stack》。 PPT下载:http://elasticsearch.cn/article/122
早在2001年,Rashid 就接触了运维工作,他的第一份工作是在摩根大通集团做网络运维管理分析员。2012年,Rashid 在美国一家媒体公司担任架构工程师,并且研发了 Kibana 的初始版本,那时他的目的是专门设计界面以适配 Logstash,如今 Kibana 已经逐渐演变成了 Elasticsearch 的分析平台。运维出身的他是在怎样的情况下开始了 Kibana 开发,Kibana 走到今天经历了什么,将来 Kibana 的发展会是怎样的?InfoQ 对 Rashid 进行了采访,以下文章来自于采访稿件的整理。
作为运维人员,我亟须优化日志搜索
开始的时候做运维工作遇到很多问题,on call待命,甚至在凌晨2点被叫醒;这种工作状态让我感到很厌烦。往往,在日志中可以发现问题所在,但是需要花费好久时间才能找到。
于是,我寻找有哪些开源软件可以做基本的日志搜索,然后发现了Logstash和与之配合使用的Elasticsearch。经过测试,我发现Elasticsearch速度很快并且提供我所需要的功能;然后我就开始编写一套非常简单的interface作为补充展示,大概花费了我几天的时间。这就是第一版Kibana的诞生过程,当时是采用PHP编写的,功能是可以展示日志并配有搜索入口,目的是把这个工具可以交付给我的boss,使得他无需我的参与便可以使用这个interface。需要提一句的是,Elasticsearch 对于Web编程很友好,并且日志数据按照日期排列。
在全职投入 Kibana 为 Elastic 公司工作之前,我一直从事运维工作并且我非常喜欢运维工作。因为这段实践经验帮助我体会到了运维人的问题和困难,这让我知道了需要创造一个什么样的工具。我依然认为运维是一个非常有挑战的工作,让所有的东西都正常地运转起来。
编程吧,动手创造自己的工具
的确,我是运维人员,但是我还自己动手开发工具。这在美国越来越普遍了,因为大家意识到,如果你可以编写代码,你的工作会轻松很多,代码可以代替你进行重复工作。通过代码实现自动化是正确的途径,没有人愿意不停地做同样的事情。
编写Kibana是因为我当时没有发现一个适合我的工具,于是我决定自己动手。第一版Kibana是用PHP写的,第二版是用Ruby,第三版之后是用JavaScript。我不害怕学习心得语言,因为学语言并不难,Ruby或者JavaScript的语言掌握仅仅是简单的熟悉语法,并没有接触到实际项目中复杂的事情。而重写Kibana的工作也并不复杂,因为其实Elasticsearch做的工作最重。
“哪种编程语言最好?”说实话,其实这个问题的讨论对我而言并不重要。重要的是,为你的工作选择恰当的语言。PHP在我心中仍然有一席之地,我认为它依然是一个好的语言,可能很多人有异议,但是我认为它简单易上手、稳定变化慢,相关工具也很容易上手。Node.js相对来说,比较复杂;Node社区也意识到这个问题,并且正在改进。比如说,当时我选择了Ruby重写Kibana,是因为Logstash是用JRuby写的,Elasticsearch 使用Java写的(JRuby可以理解为Ruby可以跑在JVM里面)。当时想把 Kibana 的 Ruby那个版本是因为想放到Logstash中,但是没有成功。所以,接下来我们研发了Kibana 3 。
在开发Kibana之前,我用过Graphite,但是为什么依然不满足呢?首先,Graphite很棒,所有关于数字、指标、时间序列的事情。但是那个时候,我需要的是一个可以做日志搜索的东西,需要有一个Dashboard可以给出一个图片。我非常希望从日志中获得信息并且把它们和预定的指标绑定在一起,实际上这些幕后工作都是Elasticsearch做的,并且速度真的快很多。此外需要考虑到扩展性,Graphite对于它适合的大小还算可以,即使超过了极限,更多的数据对应着更多的CPU消耗;但是想扩展很多的话,就很困难了,这一点上Graphite还有很多可以提升的空间,Elastic Stack就可以很轻松地实现。
不过,我依然很喜欢Graphite,我也依然认为这是一个有需求的工具,并且它其实是可以和Elasticsearch、Kibana结合在一起使用的。Architecture dependent的问题困扰了很多人, 比如32bit和64bit两者之间,即便是传输一个文件也不能工作,这是一个非常可怕的事情。Graphite 解决了这个问题,并且界面很美,功能强大 。Kibana也解决了很多相似的问题, 尤其是加上了Elasticsearch的配合,解决了许多我在做运维工作时总是非常想做的工作。
从来没有犹豫过是否开源
12岁的时候就开始接触开源项目了,所以在写Kibana的时候从来没有犹豫过要不要把它开源。
开始的时候我们只是把需求写在纸上,然后一条条做。放到Github之后,看到下载量不断上升我们感到很吃惊。我们没有想到,原来这么多人都面临这同样的问题,没有想到大家对这样的一个开源项目如此需要。开源的目的就是为了能帮助人们,最初我也曾疑惑有可能根本没有人用;然后发现越来越多的人在讨论、使用它。现在Elastic Stack是一个开源整体,把个人的事业career放在服务其他人的开源项目上,并能收获到好的反馈,这让我们感到很开心、很欣慰。
当时的小愿望,现在的大公司
Kibana第一版存在仅仅几周。是因为我开始使用Ruby进行重写,这大概花费了两周的时间。因为Logstash使用Ruby写的(即便当时我并不会Ruby),而我的目的就是让Kibana适配Logstash和Elasticsearch,让三者在一起可以协作获得更多的信息。当时我的想法就是让三个工具可以无缝衔接起来好似一个工具一样,有趣的是,这仅仅是当时我自己的一个愿望,后来Elasticsearch的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。
我现在依然是on call的。在 Elastic 公司,我们有on call轮班制。其实这是与用户交流的机会,这是我们 Elastic 每一个开发者都非常珍视的机会。在对用户支持的过程中,我们可以更清晰地了解用户的需求和真实使用情况;还有一些其他方式,比如会议、沙龙、见面会等,任何可以帮助我们与社区连接的。在我看来,在用户发生问题时,你在他身边并且帮助修复问题:没有比这个更好的工作方式。所以,on call不是折磨,是机会。
Kibana的下一步:数据挖掘、角色报表
1、数据挖掘,精益求精
最开始在做日志分析的那个时候,坦率地讲,我并没有关联到了Data mining。因为那时只是想先把问题弄清楚。但是在把所有的问题都解决完(这些并不难,只是花时间而已),实现了最初我们想要的Kibana之后,运维的工作量就大大减少了。
一切都运转得很顺利之后,我们开始思考怎样能把事情做得越来越好,尽量少地产生问题。我们可以获得数据,并且发现了一些问题发生的规律:问题的发生节点,比如说往往半夜三点、发布新版本之后;问题的发生频率,哪些问题非常热门,我们需要把对应的工作放在CDN上;问题的优化处理,发生问题之后如何最快地回滚。机器学习很强有力,而且对于运维人员而言,越少的红色提示越幸福。但是目前我的考虑是,能做到提前预警就已经很棒了。
基于这些思考,我们认为需要开始进行数据挖掘的工作了,这样才把事情做得越来越好,才能更大程度地帮助公司用户。在五六年前,很少会有人想到运维团队可以给出商业业务的指导意见,但是现在这开始越来越普遍。
2、接下来,Dashboard不会只有public一种
此前Kibana的Dashboard是完全公开的,没有角色区分。我知道一些其他的工具提供了包边权限区分的功能。最初的时候,只是想保持事情的简单化,Kibana并没有考虑到把Dashboard做成基于角色的,我们考虑更多的是产品易用性、功能,而没有打算触及安全模块。对于我们自己而言,这并不是过去那些年优先级第一的事项。最开始Kibana的主要矛盾是怎样把内容展现出来,打造Elasticsearch的良好用户界面,所以那个时候是界面是对所有用户可见的。而权限的控制我们是在Elasticsearch上面实现的,搜索、索引、集群操作添加是在Elasticsearch,也就是说我们首先Elasticsearch中实现数据层的安全。
接下来,我们考虑怎样把安全性延展到Kibana中,对不同角色进行区分化的搜索展示。(此前,有一个插件可以满足在Kibana中进行 Elasticsearch 用户的控制。代码是开源的,任何公司都可以编写自己的安全模块,并且我们也乐意帮助他们)在决定做一件事情之后我们就希望把一件事情做得非常好,而不是半途而废。
Kibana in Elastic Stack 5.0
研发情况
研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。Elastic Stack5.0 的所有功能大概花费了9个月的研发时间。在决策哪些功能需要研发时,我们有几周的考虑时间,还会参考社区中的反馈。然后我们还会给开发者一些自主空间,我们试着避免总是给某些人下发某些任务。我们认为最好主意并不来自与管理层或者经理,而是来自于那些与用户交流频繁的软件工程师,花费很多时间做客户支持的同事。会面通常是远程的,因为我们是个分布式的公司,公司成员分布于30多个国家,一共470多人。Kibana部分的研发、测试和运营人员一共有20多人。
如果有两个程序员所做的事情是一样的话,没有关系这不是重复劳动,反而可以让产品更加优化,两个人可以互相讨论加速功能研发;否则的话产品会被程序员打上过强的个人烙印,最终让产品交付给客户的是整个团队。
会一直并且只是配合Elasticsearch
是的,我们没有其他 datasource 的计划,因为我们大部分的使用case是要有时间序列的。
最开始融合在一起的是 Elasticsearch 和 Logstash,然后 Kibana 参与进来。在这个融合的过程中,遇到任何冲突或者改变,我们的评判标准都是怎样对用户而言更友好。举例说明,安全问题最佳的解决是在数据层,搜索非常占用内存,使用ES可以做很复杂的事情,在旧版本的 Kibana 中,可以使用 Elasticsearch 的 API,但是这拖缓了速度,并且用户可能会做一些危险的事情。在 Kibana 和 Elasticsearch 融合之后,我再也没有那样做了,对于一些重的内存需求工作不会在UI层(Kibana)而是会放到数据层(ES),用最少的内存,让尽可能多的计算脱离 JVM heap ,放入socket breaker,让我们管理更简洁干净并做到在UI可追踪。
Kibana的美学
Kibana最初的设计师只是我一个人,现在当然我们有了自己的很优秀的设计师,这是很被看重的部分,没有外包出去。因为我们需要和设计团队频繁地交流,不断地给予反馈,和工程团队。这是我们公司文化的一个重要部分。
你想一想这是运维人员需要终日面对的工具,没有人愿意一直看着丑的东西;此外,也希望Kibana可以让运维人员的boss们感到惊艳,我们希望可以帮助使用者产生非常美的工作。
写在最后
在采访结束时,InfoQ问Rashid是否可以给广大读者一些建议,Rashid想了想说:
继续阅读 »
在 Elastic 中国开发者大会 2016上,ELK 正式宣布更名为“Elastic Stack”,Elastic公司称其开源项目累计已经有8000万次下载。Elastic Stack 最新版本为5.0,从此,Elastic公司会对Elasticsearch、Kibana、Logstash、Beats、X-Pack进行统一规划以同版本号码发布。会上,Kibana 的原作者 Rashid Khan 进行了题为《Kibana 5.0: The Window into the Elastic Stack》。 PPT下载:http://elasticsearch.cn/article/122
早在2001年,Rashid 就接触了运维工作,他的第一份工作是在摩根大通集团做网络运维管理分析员。2012年,Rashid 在美国一家媒体公司担任架构工程师,并且研发了 Kibana 的初始版本,那时他的目的是专门设计界面以适配 Logstash,如今 Kibana 已经逐渐演变成了 Elasticsearch 的分析平台。运维出身的他是在怎样的情况下开始了 Kibana 开发,Kibana 走到今天经历了什么,将来 Kibana 的发展会是怎样的?InfoQ 对 Rashid 进行了采访,以下文章来自于采访稿件的整理。
作为运维人员,我亟须优化日志搜索
开始的时候做运维工作遇到很多问题,on call待命,甚至在凌晨2点被叫醒;这种工作状态让我感到很厌烦。往往,在日志中可以发现问题所在,但是需要花费好久时间才能找到。
于是,我寻找有哪些开源软件可以做基本的日志搜索,然后发现了Logstash和与之配合使用的Elasticsearch。经过测试,我发现Elasticsearch速度很快并且提供我所需要的功能;然后我就开始编写一套非常简单的interface作为补充展示,大概花费了我几天的时间。这就是第一版Kibana的诞生过程,当时是采用PHP编写的,功能是可以展示日志并配有搜索入口,目的是把这个工具可以交付给我的boss,使得他无需我的参与便可以使用这个interface。需要提一句的是,Elasticsearch 对于Web编程很友好,并且日志数据按照日期排列。
在全职投入 Kibana 为 Elastic 公司工作之前,我一直从事运维工作并且我非常喜欢运维工作。因为这段实践经验帮助我体会到了运维人的问题和困难,这让我知道了需要创造一个什么样的工具。我依然认为运维是一个非常有挑战的工作,让所有的东西都正常地运转起来。
编程吧,动手创造自己的工具
的确,我是运维人员,但是我还自己动手开发工具。这在美国越来越普遍了,因为大家意识到,如果你可以编写代码,你的工作会轻松很多,代码可以代替你进行重复工作。通过代码实现自动化是正确的途径,没有人愿意不停地做同样的事情。
编写Kibana是因为我当时没有发现一个适合我的工具,于是我决定自己动手。第一版Kibana是用PHP写的,第二版是用Ruby,第三版之后是用JavaScript。我不害怕学习心得语言,因为学语言并不难,Ruby或者JavaScript的语言掌握仅仅是简单的熟悉语法,并没有接触到实际项目中复杂的事情。而重写Kibana的工作也并不复杂,因为其实Elasticsearch做的工作最重。
“哪种编程语言最好?”说实话,其实这个问题的讨论对我而言并不重要。重要的是,为你的工作选择恰当的语言。PHP在我心中仍然有一席之地,我认为它依然是一个好的语言,可能很多人有异议,但是我认为它简单易上手、稳定变化慢,相关工具也很容易上手。Node.js相对来说,比较复杂;Node社区也意识到这个问题,并且正在改进。比如说,当时我选择了Ruby重写Kibana,是因为Logstash是用JRuby写的,Elasticsearch 使用Java写的(JRuby可以理解为Ruby可以跑在JVM里面)。当时想把 Kibana 的 Ruby那个版本是因为想放到Logstash中,但是没有成功。所以,接下来我们研发了Kibana 3 。
在开发Kibana之前,我用过Graphite,但是为什么依然不满足呢?首先,Graphite很棒,所有关于数字、指标、时间序列的事情。但是那个时候,我需要的是一个可以做日志搜索的东西,需要有一个Dashboard可以给出一个图片。我非常希望从日志中获得信息并且把它们和预定的指标绑定在一起,实际上这些幕后工作都是Elasticsearch做的,并且速度真的快很多。此外需要考虑到扩展性,Graphite对于它适合的大小还算可以,即使超过了极限,更多的数据对应着更多的CPU消耗;但是想扩展很多的话,就很困难了,这一点上Graphite还有很多可以提升的空间,Elastic Stack就可以很轻松地实现。
不过,我依然很喜欢Graphite,我也依然认为这是一个有需求的工具,并且它其实是可以和Elasticsearch、Kibana结合在一起使用的。Architecture dependent的问题困扰了很多人, 比如32bit和64bit两者之间,即便是传输一个文件也不能工作,这是一个非常可怕的事情。Graphite 解决了这个问题,并且界面很美,功能强大 。Kibana也解决了很多相似的问题, 尤其是加上了Elasticsearch的配合,解决了许多我在做运维工作时总是非常想做的工作。
从来没有犹豫过是否开源
12岁的时候就开始接触开源项目了,所以在写Kibana的时候从来没有犹豫过要不要把它开源。
开始的时候我们只是把需求写在纸上,然后一条条做。放到Github之后,看到下载量不断上升我们感到很吃惊。我们没有想到,原来这么多人都面临这同样的问题,没有想到大家对这样的一个开源项目如此需要。开源的目的就是为了能帮助人们,最初我也曾疑惑有可能根本没有人用;然后发现越来越多的人在讨论、使用它。现在Elastic Stack是一个开源整体,把个人的事业career放在服务其他人的开源项目上,并能收获到好的反馈,这让我们感到很开心、很欣慰。
当时的小愿望,现在的大公司
Kibana第一版存在仅仅几周。是因为我开始使用Ruby进行重写,这大概花费了两周的时间。因为Logstash使用Ruby写的(即便当时我并不会Ruby),而我的目的就是让Kibana适配Logstash和Elasticsearch,让三者在一起可以协作获得更多的信息。当时我的想法就是让三个工具可以无缝衔接起来好似一个工具一样,有趣的是,这仅仅是当时我自己的一个愿望,后来Elasticsearch的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。
我现在依然是on call的。在 Elastic 公司,我们有on call轮班制。其实这是与用户交流的机会,这是我们 Elastic 每一个开发者都非常珍视的机会。在对用户支持的过程中,我们可以更清晰地了解用户的需求和真实使用情况;还有一些其他方式,比如会议、沙龙、见面会等,任何可以帮助我们与社区连接的。在我看来,在用户发生问题时,你在他身边并且帮助修复问题:没有比这个更好的工作方式。所以,on call不是折磨,是机会。
Kibana的下一步:数据挖掘、角色报表
1、数据挖掘,精益求精
最开始在做日志分析的那个时候,坦率地讲,我并没有关联到了Data mining。因为那时只是想先把问题弄清楚。但是在把所有的问题都解决完(这些并不难,只是花时间而已),实现了最初我们想要的Kibana之后,运维的工作量就大大减少了。
一切都运转得很顺利之后,我们开始思考怎样能把事情做得越来越好,尽量少地产生问题。我们可以获得数据,并且发现了一些问题发生的规律:问题的发生节点,比如说往往半夜三点、发布新版本之后;问题的发生频率,哪些问题非常热门,我们需要把对应的工作放在CDN上;问题的优化处理,发生问题之后如何最快地回滚。机器学习很强有力,而且对于运维人员而言,越少的红色提示越幸福。但是目前我的考虑是,能做到提前预警就已经很棒了。
基于这些思考,我们认为需要开始进行数据挖掘的工作了,这样才把事情做得越来越好,才能更大程度地帮助公司用户。在五六年前,很少会有人想到运维团队可以给出商业业务的指导意见,但是现在这开始越来越普遍。
2、接下来,Dashboard不会只有public一种
此前Kibana的Dashboard是完全公开的,没有角色区分。我知道一些其他的工具提供了包边权限区分的功能。最初的时候,只是想保持事情的简单化,Kibana并没有考虑到把Dashboard做成基于角色的,我们考虑更多的是产品易用性、功能,而没有打算触及安全模块。对于我们自己而言,这并不是过去那些年优先级第一的事项。最开始Kibana的主要矛盾是怎样把内容展现出来,打造Elasticsearch的良好用户界面,所以那个时候是界面是对所有用户可见的。而权限的控制我们是在Elasticsearch上面实现的,搜索、索引、集群操作添加是在Elasticsearch,也就是说我们首先Elasticsearch中实现数据层的安全。
接下来,我们考虑怎样把安全性延展到Kibana中,对不同角色进行区分化的搜索展示。(此前,有一个插件可以满足在Kibana中进行 Elasticsearch 用户的控制。代码是开源的,任何公司都可以编写自己的安全模块,并且我们也乐意帮助他们)在决定做一件事情之后我们就希望把一件事情做得非常好,而不是半途而废。
Kibana in Elastic Stack 5.0
研发情况
研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。Elastic Stack5.0 的所有功能大概花费了9个月的研发时间。在决策哪些功能需要研发时,我们有几周的考虑时间,还会参考社区中的反馈。然后我们还会给开发者一些自主空间,我们试着避免总是给某些人下发某些任务。我们认为最好主意并不来自与管理层或者经理,而是来自于那些与用户交流频繁的软件工程师,花费很多时间做客户支持的同事。会面通常是远程的,因为我们是个分布式的公司,公司成员分布于30多个国家,一共470多人。Kibana部分的研发、测试和运营人员一共有20多人。
如果有两个程序员所做的事情是一样的话,没有关系这不是重复劳动,反而可以让产品更加优化,两个人可以互相讨论加速功能研发;否则的话产品会被程序员打上过强的个人烙印,最终让产品交付给客户的是整个团队。
会一直并且只是配合Elasticsearch
是的,我们没有其他 datasource 的计划,因为我们大部分的使用case是要有时间序列的。
最开始融合在一起的是 Elasticsearch 和 Logstash,然后 Kibana 参与进来。在这个融合的过程中,遇到任何冲突或者改变,我们的评判标准都是怎样对用户而言更友好。举例说明,安全问题最佳的解决是在数据层,搜索非常占用内存,使用ES可以做很复杂的事情,在旧版本的 Kibana 中,可以使用 Elasticsearch 的 API,但是这拖缓了速度,并且用户可能会做一些危险的事情。在 Kibana 和 Elasticsearch 融合之后,我再也没有那样做了,对于一些重的内存需求工作不会在UI层(Kibana)而是会放到数据层(ES),用最少的内存,让尽可能多的计算脱离 JVM heap ,放入socket breaker,让我们管理更简洁干净并做到在UI可追踪。
Kibana的美学
Kibana最初的设计师只是我一个人,现在当然我们有了自己的很优秀的设计师,这是很被看重的部分,没有外包出去。因为我们需要和设计团队频繁地交流,不断地给予反馈,和工程团队。这是我们公司文化的一个重要部分。
你想一想这是运维人员需要终日面对的工具,没有人愿意一直看着丑的东西;此外,也希望Kibana可以让运维人员的boss们感到惊艳,我们希望可以帮助使用者产生非常美的工作。
写在最后
在采访结束时,InfoQ问Rashid是否可以给广大读者一些建议,Rashid想了想说:
如果你有一个想法,把它code出来,build起来。不要等其他人的开源代码,有可能你会等到,但是有可能你永远等不到。在你写出来之后,你没准会收获惊喜。
转载:http://www.infoq.com/cn/news/2 ... nTool
在 Elastic 中国开发者大会 2016上,ELK 正式宣布更名为“Elastic Stack”,Elastic公司称其开源项目累计已经有8000万次下载。Elastic Stack 最新版本为5.0,从此,Elastic公司会对Elasticsearch、Kibana、Logstash、Beats、X-Pack进行统一规划以同版本号码发布。会上,Kibana 的原作者 Rashid Khan 进行了题为《Kibana 5.0: The Window into the Elastic Stack》。 PPT下载:http://elasticsearch.cn/article/122
早在2001年,Rashid 就接触了运维工作,他的第一份工作是在摩根大通集团做网络运维管理分析员。2012年,Rashid 在美国一家媒体公司担任架构工程师,并且研发了 Kibana 的初始版本,那时他的目的是专门设计界面以适配 Logstash,如今 Kibana 已经逐渐演变成了 Elasticsearch 的分析平台。运维出身的他是在怎样的情况下开始了 Kibana 开发,Kibana 走到今天经历了什么,将来 Kibana 的发展会是怎样的?InfoQ 对 Rashid 进行了采访,以下文章来自于采访稿件的整理。
作为运维人员,我亟须优化日志搜索
开始的时候做运维工作遇到很多问题,on call待命,甚至在凌晨2点被叫醒;这种工作状态让我感到很厌烦。往往,在日志中可以发现问题所在,但是需要花费好久时间才能找到。
于是,我寻找有哪些开源软件可以做基本的日志搜索,然后发现了Logstash和与之配合使用的Elasticsearch。经过测试,我发现Elasticsearch速度很快并且提供我所需要的功能;然后我就开始编写一套非常简单的interface作为补充展示,大概花费了我几天的时间。这就是第一版Kibana的诞生过程,当时是采用PHP编写的,功能是可以展示日志并配有搜索入口,目的是把这个工具可以交付给我的boss,使得他无需我的参与便可以使用这个interface。需要提一句的是,Elasticsearch 对于Web编程很友好,并且日志数据按照日期排列。
在全职投入 Kibana 为 Elastic 公司工作之前,我一直从事运维工作并且我非常喜欢运维工作。因为这段实践经验帮助我体会到了运维人的问题和困难,这让我知道了需要创造一个什么样的工具。我依然认为运维是一个非常有挑战的工作,让所有的东西都正常地运转起来。
编程吧,动手创造自己的工具
的确,我是运维人员,但是我还自己动手开发工具。这在美国越来越普遍了,因为大家意识到,如果你可以编写代码,你的工作会轻松很多,代码可以代替你进行重复工作。通过代码实现自动化是正确的途径,没有人愿意不停地做同样的事情。
编写Kibana是因为我当时没有发现一个适合我的工具,于是我决定自己动手。第一版Kibana是用PHP写的,第二版是用Ruby,第三版之后是用JavaScript。我不害怕学习心得语言,因为学语言并不难,Ruby或者JavaScript的语言掌握仅仅是简单的熟悉语法,并没有接触到实际项目中复杂的事情。而重写Kibana的工作也并不复杂,因为其实Elasticsearch做的工作最重。
“哪种编程语言最好?”说实话,其实这个问题的讨论对我而言并不重要。重要的是,为你的工作选择恰当的语言。PHP在我心中仍然有一席之地,我认为它依然是一个好的语言,可能很多人有异议,但是我认为它简单易上手、稳定变化慢,相关工具也很容易上手。Node.js相对来说,比较复杂;Node社区也意识到这个问题,并且正在改进。比如说,当时我选择了Ruby重写Kibana,是因为Logstash是用JRuby写的,Elasticsearch 使用Java写的(JRuby可以理解为Ruby可以跑在JVM里面)。当时想把 Kibana 的 Ruby那个版本是因为想放到Logstash中,但是没有成功。所以,接下来我们研发了Kibana 3 。
在开发Kibana之前,我用过Graphite,但是为什么依然不满足呢?首先,Graphite很棒,所有关于数字、指标、时间序列的事情。但是那个时候,我需要的是一个可以做日志搜索的东西,需要有一个Dashboard可以给出一个图片。我非常希望从日志中获得信息并且把它们和预定的指标绑定在一起,实际上这些幕后工作都是Elasticsearch做的,并且速度真的快很多。此外需要考虑到扩展性,Graphite对于它适合的大小还算可以,即使超过了极限,更多的数据对应着更多的CPU消耗;但是想扩展很多的话,就很困难了,这一点上Graphite还有很多可以提升的空间,Elastic Stack就可以很轻松地实现。
不过,我依然很喜欢Graphite,我也依然认为这是一个有需求的工具,并且它其实是可以和Elasticsearch、Kibana结合在一起使用的。Architecture dependent的问题困扰了很多人, 比如32bit和64bit两者之间,即便是传输一个文件也不能工作,这是一个非常可怕的事情。Graphite 解决了这个问题,并且界面很美,功能强大 。Kibana也解决了很多相似的问题, 尤其是加上了Elasticsearch的配合,解决了许多我在做运维工作时总是非常想做的工作。
从来没有犹豫过是否开源
12岁的时候就开始接触开源项目了,所以在写Kibana的时候从来没有犹豫过要不要把它开源。
开始的时候我们只是把需求写在纸上,然后一条条做。放到Github之后,看到下载量不断上升我们感到很吃惊。我们没有想到,原来这么多人都面临这同样的问题,没有想到大家对这样的一个开源项目如此需要。开源的目的就是为了能帮助人们,最初我也曾疑惑有可能根本没有人用;然后发现越来越多的人在讨论、使用它。现在Elastic Stack是一个开源整体,把个人的事业career放在服务其他人的开源项目上,并能收获到好的反馈,这让我们感到很开心、很欣慰。
当时的小愿望,现在的大公司
Kibana第一版存在仅仅几周。是因为我开始使用Ruby进行重写,这大概花费了两周的时间。因为Logstash使用Ruby写的(即便当时我并不会Ruby),而我的目的就是让Kibana适配Logstash和Elasticsearch,让三者在一起可以协作获得更多的信息。当时我的想法就是让三个工具可以无缝衔接起来好似一个工具一样,有趣的是,这仅仅是当时我自己的一个愿望,后来Elasticsearch的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。
我现在依然是on call的。在 Elastic 公司,我们有on call轮班制。其实这是与用户交流的机会,这是我们 Elastic 每一个开发者都非常珍视的机会。在对用户支持的过程中,我们可以更清晰地了解用户的需求和真实使用情况;还有一些其他方式,比如会议、沙龙、见面会等,任何可以帮助我们与社区连接的。在我看来,在用户发生问题时,你在他身边并且帮助修复问题:没有比这个更好的工作方式。所以,on call不是折磨,是机会。
Kibana的下一步:数据挖掘、角色报表
1、数据挖掘,精益求精
最开始在做日志分析的那个时候,坦率地讲,我并没有关联到了Data mining。因为那时只是想先把问题弄清楚。但是在把所有的问题都解决完(这些并不难,只是花时间而已),实现了最初我们想要的Kibana之后,运维的工作量就大大减少了。
一切都运转得很顺利之后,我们开始思考怎样能把事情做得越来越好,尽量少地产生问题。我们可以获得数据,并且发现了一些问题发生的规律:问题的发生节点,比如说往往半夜三点、发布新版本之后;问题的发生频率,哪些问题非常热门,我们需要把对应的工作放在CDN上;问题的优化处理,发生问题之后如何最快地回滚。机器学习很强有力,而且对于运维人员而言,越少的红色提示越幸福。但是目前我的考虑是,能做到提前预警就已经很棒了。
基于这些思考,我们认为需要开始进行数据挖掘的工作了,这样才把事情做得越来越好,才能更大程度地帮助公司用户。在五六年前,很少会有人想到运维团队可以给出商业业务的指导意见,但是现在这开始越来越普遍。
2、接下来,Dashboard不会只有public一种
此前Kibana的Dashboard是完全公开的,没有角色区分。我知道一些其他的工具提供了包边权限区分的功能。最初的时候,只是想保持事情的简单化,Kibana并没有考虑到把Dashboard做成基于角色的,我们考虑更多的是产品易用性、功能,而没有打算触及安全模块。对于我们自己而言,这并不是过去那些年优先级第一的事项。最开始Kibana的主要矛盾是怎样把内容展现出来,打造Elasticsearch的良好用户界面,所以那个时候是界面是对所有用户可见的。而权限的控制我们是在Elasticsearch上面实现的,搜索、索引、集群操作添加是在Elasticsearch,也就是说我们首先Elasticsearch中实现数据层的安全。
接下来,我们考虑怎样把安全性延展到Kibana中,对不同角色进行区分化的搜索展示。(此前,有一个插件可以满足在Kibana中进行 Elasticsearch 用户的控制。代码是开源的,任何公司都可以编写自己的安全模块,并且我们也乐意帮助他们)在决定做一件事情之后我们就希望把一件事情做得非常好,而不是半途而废。
Kibana in Elastic Stack 5.0
研发情况
研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。Elastic Stack5.0 的所有功能大概花费了9个月的研发时间。在决策哪些功能需要研发时,我们有几周的考虑时间,还会参考社区中的反馈。然后我们还会给开发者一些自主空间,我们试着避免总是给某些人下发某些任务。我们认为最好主意并不来自与管理层或者经理,而是来自于那些与用户交流频繁的软件工程师,花费很多时间做客户支持的同事。会面通常是远程的,因为我们是个分布式的公司,公司成员分布于30多个国家,一共470多人。Kibana部分的研发、测试和运营人员一共有20多人。
如果有两个程序员所做的事情是一样的话,没有关系这不是重复劳动,反而可以让产品更加优化,两个人可以互相讨论加速功能研发;否则的话产品会被程序员打上过强的个人烙印,最终让产品交付给客户的是整个团队。
会一直并且只是配合Elasticsearch
是的,我们没有其他 datasource 的计划,因为我们大部分的使用case是要有时间序列的。
最开始融合在一起的是 Elasticsearch 和 Logstash,然后 Kibana 参与进来。在这个融合的过程中,遇到任何冲突或者改变,我们的评判标准都是怎样对用户而言更友好。举例说明,安全问题最佳的解决是在数据层,搜索非常占用内存,使用ES可以做很复杂的事情,在旧版本的 Kibana 中,可以使用 Elasticsearch 的 API,但是这拖缓了速度,并且用户可能会做一些危险的事情。在 Kibana 和 Elasticsearch 融合之后,我再也没有那样做了,对于一些重的内存需求工作不会在UI层(Kibana)而是会放到数据层(ES),用最少的内存,让尽可能多的计算脱离 JVM heap ,放入socket breaker,让我们管理更简洁干净并做到在UI可追踪。
Kibana的美学
Kibana最初的设计师只是我一个人,现在当然我们有了自己的很优秀的设计师,这是很被看重的部分,没有外包出去。因为我们需要和设计团队频繁地交流,不断地给予反馈,和工程团队。这是我们公司文化的一个重要部分。
你想一想这是运维人员需要终日面对的工具,没有人愿意一直看着丑的东西;此外,也希望Kibana可以让运维人员的boss们感到惊艳,我们希望可以帮助使用者产生非常美的工作。
写在最后
在采访结束时,InfoQ问Rashid是否可以给广大读者一些建议,Rashid想了想说:
收起阅读 »
在 Elastic 中国开发者大会 2016上,ELK 正式宣布更名为“Elastic Stack”,Elastic公司称其开源项目累计已经有8000万次下载。Elastic Stack 最新版本为5.0,从此,Elastic公司会对Elasticsearch、Kibana、Logstash、Beats、X-Pack进行统一规划以同版本号码发布。会上,Kibana 的原作者 Rashid Khan 进行了题为《Kibana 5.0: The Window into the Elastic Stack》。 PPT下载:http://elasticsearch.cn/article/122
早在2001年,Rashid 就接触了运维工作,他的第一份工作是在摩根大通集团做网络运维管理分析员。2012年,Rashid 在美国一家媒体公司担任架构工程师,并且研发了 Kibana 的初始版本,那时他的目的是专门设计界面以适配 Logstash,如今 Kibana 已经逐渐演变成了 Elasticsearch 的分析平台。运维出身的他是在怎样的情况下开始了 Kibana 开发,Kibana 走到今天经历了什么,将来 Kibana 的发展会是怎样的?InfoQ 对 Rashid 进行了采访,以下文章来自于采访稿件的整理。
作为运维人员,我亟须优化日志搜索
开始的时候做运维工作遇到很多问题,on call待命,甚至在凌晨2点被叫醒;这种工作状态让我感到很厌烦。往往,在日志中可以发现问题所在,但是需要花费好久时间才能找到。
于是,我寻找有哪些开源软件可以做基本的日志搜索,然后发现了Logstash和与之配合使用的Elasticsearch。经过测试,我发现Elasticsearch速度很快并且提供我所需要的功能;然后我就开始编写一套非常简单的interface作为补充展示,大概花费了我几天的时间。这就是第一版Kibana的诞生过程,当时是采用PHP编写的,功能是可以展示日志并配有搜索入口,目的是把这个工具可以交付给我的boss,使得他无需我的参与便可以使用这个interface。需要提一句的是,Elasticsearch 对于Web编程很友好,并且日志数据按照日期排列。
在全职投入 Kibana 为 Elastic 公司工作之前,我一直从事运维工作并且我非常喜欢运维工作。因为这段实践经验帮助我体会到了运维人的问题和困难,这让我知道了需要创造一个什么样的工具。我依然认为运维是一个非常有挑战的工作,让所有的东西都正常地运转起来。
编程吧,动手创造自己的工具
的确,我是运维人员,但是我还自己动手开发工具。这在美国越来越普遍了,因为大家意识到,如果你可以编写代码,你的工作会轻松很多,代码可以代替你进行重复工作。通过代码实现自动化是正确的途径,没有人愿意不停地做同样的事情。
编写Kibana是因为我当时没有发现一个适合我的工具,于是我决定自己动手。第一版Kibana是用PHP写的,第二版是用Ruby,第三版之后是用JavaScript。我不害怕学习心得语言,因为学语言并不难,Ruby或者JavaScript的语言掌握仅仅是简单的熟悉语法,并没有接触到实际项目中复杂的事情。而重写Kibana的工作也并不复杂,因为其实Elasticsearch做的工作最重。
“哪种编程语言最好?”说实话,其实这个问题的讨论对我而言并不重要。重要的是,为你的工作选择恰当的语言。PHP在我心中仍然有一席之地,我认为它依然是一个好的语言,可能很多人有异议,但是我认为它简单易上手、稳定变化慢,相关工具也很容易上手。Node.js相对来说,比较复杂;Node社区也意识到这个问题,并且正在改进。比如说,当时我选择了Ruby重写Kibana,是因为Logstash是用JRuby写的,Elasticsearch 使用Java写的(JRuby可以理解为Ruby可以跑在JVM里面)。当时想把 Kibana 的 Ruby那个版本是因为想放到Logstash中,但是没有成功。所以,接下来我们研发了Kibana 3 。
在开发Kibana之前,我用过Graphite,但是为什么依然不满足呢?首先,Graphite很棒,所有关于数字、指标、时间序列的事情。但是那个时候,我需要的是一个可以做日志搜索的东西,需要有一个Dashboard可以给出一个图片。我非常希望从日志中获得信息并且把它们和预定的指标绑定在一起,实际上这些幕后工作都是Elasticsearch做的,并且速度真的快很多。此外需要考虑到扩展性,Graphite对于它适合的大小还算可以,即使超过了极限,更多的数据对应着更多的CPU消耗;但是想扩展很多的话,就很困难了,这一点上Graphite还有很多可以提升的空间,Elastic Stack就可以很轻松地实现。
不过,我依然很喜欢Graphite,我也依然认为这是一个有需求的工具,并且它其实是可以和Elasticsearch、Kibana结合在一起使用的。Architecture dependent的问题困扰了很多人, 比如32bit和64bit两者之间,即便是传输一个文件也不能工作,这是一个非常可怕的事情。Graphite 解决了这个问题,并且界面很美,功能强大 。Kibana也解决了很多相似的问题, 尤其是加上了Elasticsearch的配合,解决了许多我在做运维工作时总是非常想做的工作。
从来没有犹豫过是否开源
12岁的时候就开始接触开源项目了,所以在写Kibana的时候从来没有犹豫过要不要把它开源。
开始的时候我们只是把需求写在纸上,然后一条条做。放到Github之后,看到下载量不断上升我们感到很吃惊。我们没有想到,原来这么多人都面临这同样的问题,没有想到大家对这样的一个开源项目如此需要。开源的目的就是为了能帮助人们,最初我也曾疑惑有可能根本没有人用;然后发现越来越多的人在讨论、使用它。现在Elastic Stack是一个开源整体,把个人的事业career放在服务其他人的开源项目上,并能收获到好的反馈,这让我们感到很开心、很欣慰。
当时的小愿望,现在的大公司
Kibana第一版存在仅仅几周。是因为我开始使用Ruby进行重写,这大概花费了两周的时间。因为Logstash使用Ruby写的(即便当时我并不会Ruby),而我的目的就是让Kibana适配Logstash和Elasticsearch,让三者在一起可以协作获得更多的信息。当时我的想法就是让三个工具可以无缝衔接起来好似一个工具一样,有趣的是,这仅仅是当时我自己的一个愿望,后来Elasticsearch的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。
我现在依然是on call的。在 Elastic 公司,我们有on call轮班制。其实这是与用户交流的机会,这是我们 Elastic 每一个开发者都非常珍视的机会。在对用户支持的过程中,我们可以更清晰地了解用户的需求和真实使用情况;还有一些其他方式,比如会议、沙龙、见面会等,任何可以帮助我们与社区连接的。在我看来,在用户发生问题时,你在他身边并且帮助修复问题:没有比这个更好的工作方式。所以,on call不是折磨,是机会。
Kibana的下一步:数据挖掘、角色报表
1、数据挖掘,精益求精
最开始在做日志分析的那个时候,坦率地讲,我并没有关联到了Data mining。因为那时只是想先把问题弄清楚。但是在把所有的问题都解决完(这些并不难,只是花时间而已),实现了最初我们想要的Kibana之后,运维的工作量就大大减少了。
一切都运转得很顺利之后,我们开始思考怎样能把事情做得越来越好,尽量少地产生问题。我们可以获得数据,并且发现了一些问题发生的规律:问题的发生节点,比如说往往半夜三点、发布新版本之后;问题的发生频率,哪些问题非常热门,我们需要把对应的工作放在CDN上;问题的优化处理,发生问题之后如何最快地回滚。机器学习很强有力,而且对于运维人员而言,越少的红色提示越幸福。但是目前我的考虑是,能做到提前预警就已经很棒了。
基于这些思考,我们认为需要开始进行数据挖掘的工作了,这样才把事情做得越来越好,才能更大程度地帮助公司用户。在五六年前,很少会有人想到运维团队可以给出商业业务的指导意见,但是现在这开始越来越普遍。
2、接下来,Dashboard不会只有public一种
此前Kibana的Dashboard是完全公开的,没有角色区分。我知道一些其他的工具提供了包边权限区分的功能。最初的时候,只是想保持事情的简单化,Kibana并没有考虑到把Dashboard做成基于角色的,我们考虑更多的是产品易用性、功能,而没有打算触及安全模块。对于我们自己而言,这并不是过去那些年优先级第一的事项。最开始Kibana的主要矛盾是怎样把内容展现出来,打造Elasticsearch的良好用户界面,所以那个时候是界面是对所有用户可见的。而权限的控制我们是在Elasticsearch上面实现的,搜索、索引、集群操作添加是在Elasticsearch,也就是说我们首先Elasticsearch中实现数据层的安全。
接下来,我们考虑怎样把安全性延展到Kibana中,对不同角色进行区分化的搜索展示。(此前,有一个插件可以满足在Kibana中进行 Elasticsearch 用户的控制。代码是开源的,任何公司都可以编写自己的安全模块,并且我们也乐意帮助他们)在决定做一件事情之后我们就希望把一件事情做得非常好,而不是半途而废。
Kibana in Elastic Stack 5.0
研发情况
研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。Elastic Stack5.0 的所有功能大概花费了9个月的研发时间。在决策哪些功能需要研发时,我们有几周的考虑时间,还会参考社区中的反馈。然后我们还会给开发者一些自主空间,我们试着避免总是给某些人下发某些任务。我们认为最好主意并不来自与管理层或者经理,而是来自于那些与用户交流频繁的软件工程师,花费很多时间做客户支持的同事。会面通常是远程的,因为我们是个分布式的公司,公司成员分布于30多个国家,一共470多人。Kibana部分的研发、测试和运营人员一共有20多人。
如果有两个程序员所做的事情是一样的话,没有关系这不是重复劳动,反而可以让产品更加优化,两个人可以互相讨论加速功能研发;否则的话产品会被程序员打上过强的个人烙印,最终让产品交付给客户的是整个团队。
会一直并且只是配合Elasticsearch
是的,我们没有其他 datasource 的计划,因为我们大部分的使用case是要有时间序列的。
最开始融合在一起的是 Elasticsearch 和 Logstash,然后 Kibana 参与进来。在这个融合的过程中,遇到任何冲突或者改变,我们的评判标准都是怎样对用户而言更友好。举例说明,安全问题最佳的解决是在数据层,搜索非常占用内存,使用ES可以做很复杂的事情,在旧版本的 Kibana 中,可以使用 Elasticsearch 的 API,但是这拖缓了速度,并且用户可能会做一些危险的事情。在 Kibana 和 Elasticsearch 融合之后,我再也没有那样做了,对于一些重的内存需求工作不会在UI层(Kibana)而是会放到数据层(ES),用最少的内存,让尽可能多的计算脱离 JVM heap ,放入socket breaker,让我们管理更简洁干净并做到在UI可追踪。
Kibana的美学
Kibana最初的设计师只是我一个人,现在当然我们有了自己的很优秀的设计师,这是很被看重的部分,没有外包出去。因为我们需要和设计团队频繁地交流,不断地给予反馈,和工程团队。这是我们公司文化的一个重要部分。
你想一想这是运维人员需要终日面对的工具,没有人愿意一直看着丑的东西;此外,也希望Kibana可以让运维人员的boss们感到惊艳,我们希望可以帮助使用者产生非常美的工作。
写在最后
在采访结束时,InfoQ问Rashid是否可以给广大读者一些建议,Rashid想了想说:
如果你有一个想法,把它code出来,build起来。不要等其他人的开源代码,有可能你会等到,但是有可能你永远等不到。在你写出来之后,你没准会收获惊喜。
收起阅读 »
medcl 发表于 : 2017-01-11 11:45
评论 (2)
Elasticsearch 2.x mapping tips
elasticsearch 2.x mapping tips
作者:杨振涛 首发于:Elasticsearch 中文社区 日期:2017-1-10
如果把elasticsearch中的mapping类比为关系型数据库中的schema的话,那么我们可能重点强调了两者之间的共性,而忽略了elasticsearch里mapping很不相同的部分 —— 这恰恰是实践中最容易被坑的地方。这里总结了几点实践中的小心得,希望对你所有帮助。
mapping 基础
创建索引库index
查看指定索引库的mapping:
PS: 这时你获得的结果为空,因为刚建的库,没有mapping信息。
创建索引类型type并指定mapping :
更新mapping (只能增加字段,不能删除字段,也不能修改字段类型,或者说无法增加一个不同类型的同名字段):
增加属性 score:
更新成功会返回:
删除mapping :
2.4版本开始ES已经不支持mapping的删除了。
tip1 dynamic 模式
动态mapping是ES的一个重要特性,这个配置的可选值及含义如下:
tip2 主要数据类型及注意事项
分词和不分词的值都需要,中英文都需要 ,
长度截取,超长过滤 ,
大小写问题(不分词时索引数据不会转小写,搜索都会转小写)
analyzer: analyzed, not_analyzed, no(表示该属性不能用来做搜索和聚合)
properties : .raw, .en/.cn
tip3 id设置
在不设置id的情况下,默认的ES会给一个类似HASH串的随机ID;如果业务上需要且可以保证索引数据的唯一性,也可以使用业务ID作为索引ID,好处就是可以根据业务ID轻松地GET到索引数据,而无需维护索引ID和业务ID的关系。
同时,设置mapping的时候也可以指定ID的生成策略,比如UUID:
tip4 index和type规划
index的别名这个特性就不再强调了,不管是否用到,第一时间设置别名是最佳实践! schema 比较相似的type,放在同一个index里;schema差异非常大的type,建议放在不同的index里;原因是跟搜索引擎的segment以及lucene有关,本质上同一个index里的type底层是同样的存储结构,差异越大意味着type a的属性在type b里大部分都是空值,那么最终会得到一个非常稀疏的矩阵,影响计算效率并浪费存储空间。
关于滚动index的问题,对于日志类的搜索应用,按天或其他维度做滚动index是非常好必要的,这样可以更好地区分冷热数据。比如:
如果只需要查询最近3天的数据,那么只需要对3天前的index remove alias即可,然后每天循环滚动。一个细节是,对于这种场景下的索引,写入的时候必须使用原始的index name,而不能使用alias;查询的时候则使用alias。
另一个问题,就是index容量的规划,副本数直接决定需要多少冗余空间;另外,索引数据本身也会有膨胀的现象,尤其是基于中文的全文搜索应用,term集可能会比较大。比如有10000个docs,占用100MB空间时,并不能简单认为100000个docs就占用约1GB。
tip5 测试分词器
如果使用的是基于词典的分词器,比如IK这类,那么线上系统可能会需要按需添加自定义词,或者同义词等,技术上我们可以暴露该类功能给搜索引擎运营人员使用。所以,需要提供一个测试分词器的接口,方便对比和验证。ES默认就提供这样的REST接口的。
按指定分词器分词指定文本:
按指定索引库的属性测试分词效果:
以上关于 mapping 的几点心得,并非金科玉律,需要根据不同的业务需求场景来区别分析和应对。如果你有更多心得,欢迎回复本文分享。
关于作者:
杨振涛,vivo移动互联网 搜索架构师,关注实时搜索,搜索广告,以及大数据的存储、索引、搜索和可视化。
继续阅读 »
作者:杨振涛 首发于:Elasticsearch 中文社区 日期:2017-1-10
如果把elasticsearch中的mapping类比为关系型数据库中的schema的话,那么我们可能重点强调了两者之间的共性,而忽略了elasticsearch里mapping很不相同的部分 —— 这恰恰是实践中最容易被坑的地方。这里总结了几点实践中的小心得,希望对你所有帮助。
mapping 基础
创建索引库index
curl -XPOST "http://192.168.9.19:9200/vivo_vimc"
查看指定索引库的mapping:
curl -XGET "http://192.168.9.19:9200/vivo_ ... ot%3B
PS: 这时你获得的结果为空,因为刚建的库,没有mapping信息。
创建索引类型type并指定mapping :
curl -XPOST http://192.168.9.19:9200/vivo_vmic/apps/_mapping -d '{
"apps" : {
"properties" : {
"appName" : {
"type" : "string",
"index" : "not_analyzed",
"fields" :{
"cn": {
"type" : "string",
"index" : "analyzed",
"analyzer": "ik"
},
"en": {
"type" : "string",
}
},
"store":"yes"
},
"status" : {
"type" : "boolean"
},
"type" : {
"type" : "integer"
},
"onsaleDate" : {
"type" : "date"
},
}
}
}'
更新mapping (只能增加字段,不能删除字段,也不能修改字段类型,或者说无法增加一个不同类型的同名字段):
增加属性 score:
curl -XPOST "http://192.168.9.19:9200/vivo_ ... ot%3B -d '{
"apps": {
"properties": {
"score":{
"type":"float"
}
}
}
}'
更新成功会返回:
{
"acknowledged" : true
}
删除mapping :
2.4版本开始ES已经不支持mapping的删除了。
tip1 dynamic 模式
动态mapping是ES的一个重要特性,这个配置的可选值及含义如下:
- true :支持动态扩展,新增数据有新的属性时,自动添加,索引成功
- false :不支持动态扩展,新增数据有新的属性时,直接忽略,索引成功
- strict: 不支持动态扩展,新增数据有新的属性时,会报错,索引失败
tip2 主要数据类型及注意事项
- string
分词和不分词的值都需要,中英文都需要 ,
长度截取,超长过滤 ,
大小写问题(不分词时索引数据不会转小写,搜索都会转小写)
analyzer: analyzed, not_analyzed, no(表示该属性不能用来做搜索和聚合)
properties : .raw, .en/.cn
- date : 如果不明确指定,那么默认的date格式是:"strict_date_optional_time||epoch_millis",这是官网的表述,意思是可以是一个字符串类型的输入,也可以是数值类型的输入,前者可以是日期或者日期加上时间,后者则是毫秒数。关于时区信息:不管业务上是否需要时区信息,我们建议依然保存,以防万一。另外,data类型在明确指定 format 参数时,也有很多坑,对于format: epoch_second, epools_millis ,如果你想用来排序,那么为了性能,我们强烈建议你使用 epoc_second,差距很大哟,你可以亲自做一个对比测试。
- long, integer, short, byte, double ,float 希望此类字段参与搜索和聚合的话,就不能设置not_analyzed。
- boolean, binaryboolean类型比较特殊,在ES里面只定义了false类的值( false, "false", "off", "no", "0", "" , 0, 0.0 ),其他所有都认为是true。实践中,我们建议优先使用 0(编程和性能友好),其次使用 true(兼容json默认的类型)。
- ipv4 type:ip 日志分析等最常用的数据类型,注意这里的是ipv4,ipv6目前暂不支持(ES 2.x);赋值时其实传递的是字符串,但ES内部其实保存的是一个long类型。
- geo type:geo_point , type:geo_shape LBS服务的必选数据类型,但不建议完全依赖此特性,业务层面要尽可能地缩小范围,或者在使用围栏类功能时,只要业务容忍,使用正方形代替圆形。
- 数组,对象,内嵌将一个复杂对象放在一个属性中,其中数组最常用。
- completion主要是用来做自动完成和拼写纠错的。
tip3 id设置
在不设置id的情况下,默认的ES会给一个类似HASH串的随机ID;如果业务上需要且可以保证索引数据的唯一性,也可以使用业务ID作为索引ID,好处就是可以根据业务ID轻松地GET到索引数据,而无需维护索引ID和业务ID的关系。
同时,设置mapping的时候也可以指定ID的生成策略,比如UUID:
curl -s -XPUT http://192.168.9.19:9200/vivo_vimc -d '
{
"mappings": {
"apps": {
"_id": {
"path": "uuid"
},
"properties": {
"cnName": {
"type": "string",
"index": "analyzed"
}
}
}
}
}'
tip4 index和type规划
index的别名这个特性就不再强调了,不管是否用到,第一时间设置别名是最佳实践! schema 比较相似的type,放在同一个index里;schema差异非常大的type,建议放在不同的index里;原因是跟搜索引擎的segment以及lucene有关,本质上同一个index里的type底层是同样的存储结构,差异越大意味着type a的属性在type b里大部分都是空值,那么最终会得到一个非常稀疏的矩阵,影响计算效率并浪费存储空间。
关于滚动index的问题,对于日志类的搜索应用,按天或其他维度做滚动index是非常好必要的,这样可以更好地区分冷热数据。比如:
index alias
vivo_appstore_log_20160108
vivo_appstore_log_20160109 vivo_appstore_log
vivo_appstore_log_20160110 vivo_appstore_log
vivo_appstore_log_20160111 vivo_appstore_log
...
如果只需要查询最近3天的数据,那么只需要对3天前的index remove alias即可,然后每天循环滚动。一个细节是,对于这种场景下的索引,写入的时候必须使用原始的index name,而不能使用alias;查询的时候则使用alias。
另一个问题,就是index容量的规划,副本数直接决定需要多少冗余空间;另外,索引数据本身也会有膨胀的现象,尤其是基于中文的全文搜索应用,term集可能会比较大。比如有10000个docs,占用100MB空间时,并不能简单认为100000个docs就占用约1GB。
tip5 测试分词器
如果使用的是基于词典的分词器,比如IK这类,那么线上系统可能会需要按需添加自定义词,或者同义词等,技术上我们可以暴露该类功能给搜索引擎运营人员使用。所以,需要提供一个测试分词器的接口,方便对比和验证。ES默认就提供这样的REST接口的。
按指定分词器分词指定文本:
GET /vivo_vimc/apps/_analyze?text=Hello, vivo 移动互联网&analyzer=ik
按指定索引库的属性测试分词效果:
GET /vivo_vimc/apps/_analyze
{
"field": "appName",
"text": "Pokemon Go"
}
以上关于 mapping 的几点心得,并非金科玉律,需要根据不同的业务需求场景来区别分析和应对。如果你有更多心得,欢迎回复本文分享。
关于作者:
杨振涛,vivo移动互联网 搜索架构师,关注实时搜索,搜索广告,以及大数据的存储、索引、搜索和可视化。
elasticsearch 2.x mapping tips
作者:杨振涛 首发于:Elasticsearch 中文社区 日期:2017-1-10
如果把elasticsearch中的mapping类比为关系型数据库中的schema的话,那么我们可能重点强调了两者之间的共性,而忽略了elasticsearch里mapping很不相同的部分 —— 这恰恰是实践中最容易被坑的地方。这里总结了几点实践中的小心得,希望对你所有帮助。
mapping 基础
创建索引库index
查看指定索引库的mapping:
PS: 这时你获得的结果为空,因为刚建的库,没有mapping信息。
创建索引类型type并指定mapping :
更新mapping (只能增加字段,不能删除字段,也不能修改字段类型,或者说无法增加一个不同类型的同名字段):
增加属性 score:
更新成功会返回:
删除mapping :
2.4版本开始ES已经不支持mapping的删除了。
tip1 dynamic 模式
动态mapping是ES的一个重要特性,这个配置的可选值及含义如下:
tip2 主要数据类型及注意事项
分词和不分词的值都需要,中英文都需要 ,
长度截取,超长过滤 ,
大小写问题(不分词时索引数据不会转小写,搜索都会转小写)
analyzer: analyzed, not_analyzed, no(表示该属性不能用来做搜索和聚合)
properties : .raw, .en/.cn
tip3 id设置
在不设置id的情况下,默认的ES会给一个类似HASH串的随机ID;如果业务上需要且可以保证索引数据的唯一性,也可以使用业务ID作为索引ID,好处就是可以根据业务ID轻松地GET到索引数据,而无需维护索引ID和业务ID的关系。
同时,设置mapping的时候也可以指定ID的生成策略,比如UUID:
tip4 index和type规划
index的别名这个特性就不再强调了,不管是否用到,第一时间设置别名是最佳实践! schema 比较相似的type,放在同一个index里;schema差异非常大的type,建议放在不同的index里;原因是跟搜索引擎的segment以及lucene有关,本质上同一个index里的type底层是同样的存储结构,差异越大意味着type a的属性在type b里大部分都是空值,那么最终会得到一个非常稀疏的矩阵,影响计算效率并浪费存储空间。
关于滚动index的问题,对于日志类的搜索应用,按天或其他维度做滚动index是非常好必要的,这样可以更好地区分冷热数据。比如:
如果只需要查询最近3天的数据,那么只需要对3天前的index remove alias即可,然后每天循环滚动。一个细节是,对于这种场景下的索引,写入的时候必须使用原始的index name,而不能使用alias;查询的时候则使用alias。
另一个问题,就是index容量的规划,副本数直接决定需要多少冗余空间;另外,索引数据本身也会有膨胀的现象,尤其是基于中文的全文搜索应用,term集可能会比较大。比如有10000个docs,占用100MB空间时,并不能简单认为100000个docs就占用约1GB。
tip5 测试分词器
如果使用的是基于词典的分词器,比如IK这类,那么线上系统可能会需要按需添加自定义词,或者同义词等,技术上我们可以暴露该类功能给搜索引擎运营人员使用。所以,需要提供一个测试分词器的接口,方便对比和验证。ES默认就提供这样的REST接口的。
按指定分词器分词指定文本:
按指定索引库的属性测试分词效果:
以上关于 mapping 的几点心得,并非金科玉律,需要根据不同的业务需求场景来区别分析和应对。如果你有更多心得,欢迎回复本文分享。
关于作者:
杨振涛,vivo移动互联网 搜索架构师,关注实时搜索,搜索广告,以及大数据的存储、索引、搜索和可视化。 收起阅读 »
作者:杨振涛 首发于:Elasticsearch 中文社区 日期:2017-1-10
如果把elasticsearch中的mapping类比为关系型数据库中的schema的话,那么我们可能重点强调了两者之间的共性,而忽略了elasticsearch里mapping很不相同的部分 —— 这恰恰是实践中最容易被坑的地方。这里总结了几点实践中的小心得,希望对你所有帮助。
mapping 基础
创建索引库index
curl -XPOST "http://192.168.9.19:9200/vivo_vimc"
查看指定索引库的mapping:
curl -XGET "http://192.168.9.19:9200/vivo_ ... ot%3B
PS: 这时你获得的结果为空,因为刚建的库,没有mapping信息。
创建索引类型type并指定mapping :
curl -XPOST http://192.168.9.19:9200/vivo_vmic/apps/_mapping -d '{
"apps" : {
"properties" : {
"appName" : {
"type" : "string",
"index" : "not_analyzed",
"fields" :{
"cn": {
"type" : "string",
"index" : "analyzed",
"analyzer": "ik"
},
"en": {
"type" : "string",
}
},
"store":"yes"
},
"status" : {
"type" : "boolean"
},
"type" : {
"type" : "integer"
},
"onsaleDate" : {
"type" : "date"
},
}
}
}'
更新mapping (只能增加字段,不能删除字段,也不能修改字段类型,或者说无法增加一个不同类型的同名字段):
增加属性 score:
curl -XPOST "http://192.168.9.19:9200/vivo_ ... ot%3B -d '{
"apps": {
"properties": {
"score":{
"type":"float"
}
}
}
}'
更新成功会返回:
{
"acknowledged" : true
}
删除mapping :
2.4版本开始ES已经不支持mapping的删除了。
tip1 dynamic 模式
动态mapping是ES的一个重要特性,这个配置的可选值及含义如下:
- true :支持动态扩展,新增数据有新的属性时,自动添加,索引成功
- false :不支持动态扩展,新增数据有新的属性时,直接忽略,索引成功
- strict: 不支持动态扩展,新增数据有新的属性时,会报错,索引失败
tip2 主要数据类型及注意事项
- string
分词和不分词的值都需要,中英文都需要 ,
长度截取,超长过滤 ,
大小写问题(不分词时索引数据不会转小写,搜索都会转小写)
analyzer: analyzed, not_analyzed, no(表示该属性不能用来做搜索和聚合)
properties : .raw, .en/.cn
- date : 如果不明确指定,那么默认的date格式是:"strict_date_optional_time||epoch_millis",这是官网的表述,意思是可以是一个字符串类型的输入,也可以是数值类型的输入,前者可以是日期或者日期加上时间,后者则是毫秒数。关于时区信息:不管业务上是否需要时区信息,我们建议依然保存,以防万一。另外,data类型在明确指定 format 参数时,也有很多坑,对于format: epoch_second, epools_millis ,如果你想用来排序,那么为了性能,我们强烈建议你使用 epoc_second,差距很大哟,你可以亲自做一个对比测试。
- long, integer, short, byte, double ,float 希望此类字段参与搜索和聚合的话,就不能设置not_analyzed。
- boolean, binaryboolean类型比较特殊,在ES里面只定义了false类的值( false, "false", "off", "no", "0", "" , 0, 0.0 ),其他所有都认为是true。实践中,我们建议优先使用 0(编程和性能友好),其次使用 true(兼容json默认的类型)。
- ipv4 type:ip 日志分析等最常用的数据类型,注意这里的是ipv4,ipv6目前暂不支持(ES 2.x);赋值时其实传递的是字符串,但ES内部其实保存的是一个long类型。
- geo type:geo_point , type:geo_shape LBS服务的必选数据类型,但不建议完全依赖此特性,业务层面要尽可能地缩小范围,或者在使用围栏类功能时,只要业务容忍,使用正方形代替圆形。
- 数组,对象,内嵌将一个复杂对象放在一个属性中,其中数组最常用。
- completion主要是用来做自动完成和拼写纠错的。
tip3 id设置
在不设置id的情况下,默认的ES会给一个类似HASH串的随机ID;如果业务上需要且可以保证索引数据的唯一性,也可以使用业务ID作为索引ID,好处就是可以根据业务ID轻松地GET到索引数据,而无需维护索引ID和业务ID的关系。
同时,设置mapping的时候也可以指定ID的生成策略,比如UUID:
curl -s -XPUT http://192.168.9.19:9200/vivo_vimc -d '
{
"mappings": {
"apps": {
"_id": {
"path": "uuid"
},
"properties": {
"cnName": {
"type": "string",
"index": "analyzed"
}
}
}
}
}'
tip4 index和type规划
index的别名这个特性就不再强调了,不管是否用到,第一时间设置别名是最佳实践! schema 比较相似的type,放在同一个index里;schema差异非常大的type,建议放在不同的index里;原因是跟搜索引擎的segment以及lucene有关,本质上同一个index里的type底层是同样的存储结构,差异越大意味着type a的属性在type b里大部分都是空值,那么最终会得到一个非常稀疏的矩阵,影响计算效率并浪费存储空间。
关于滚动index的问题,对于日志类的搜索应用,按天或其他维度做滚动index是非常好必要的,这样可以更好地区分冷热数据。比如:
index alias
vivo_appstore_log_20160108
vivo_appstore_log_20160109 vivo_appstore_log
vivo_appstore_log_20160110 vivo_appstore_log
vivo_appstore_log_20160111 vivo_appstore_log
...
如果只需要查询最近3天的数据,那么只需要对3天前的index remove alias即可,然后每天循环滚动。一个细节是,对于这种场景下的索引,写入的时候必须使用原始的index name,而不能使用alias;查询的时候则使用alias。
另一个问题,就是index容量的规划,副本数直接决定需要多少冗余空间;另外,索引数据本身也会有膨胀的现象,尤其是基于中文的全文搜索应用,term集可能会比较大。比如有10000个docs,占用100MB空间时,并不能简单认为100000个docs就占用约1GB。
tip5 测试分词器
如果使用的是基于词典的分词器,比如IK这类,那么线上系统可能会需要按需添加自定义词,或者同义词等,技术上我们可以暴露该类功能给搜索引擎运营人员使用。所以,需要提供一个测试分词器的接口,方便对比和验证。ES默认就提供这样的REST接口的。
按指定分词器分词指定文本:
GET /vivo_vimc/apps/_analyze?text=Hello, vivo 移动互联网&analyzer=ik
按指定索引库的属性测试分词效果:
GET /vivo_vimc/apps/_analyze
{
"field": "appName",
"text": "Pokemon Go"
}
以上关于 mapping 的几点心得,并非金科玉律,需要根据不同的业务需求场景来区别分析和应对。如果你有更多心得,欢迎回复本文分享。
关于作者:
杨振涛,vivo移动互联网 搜索架构师,关注实时搜索,搜索广告,以及大数据的存储、索引、搜索和可视化。 收起阅读 »
《Elasticsearch 权威指南》中文版
在几十位社区同学的共同努力下,《Elasticsearch 权威指南》的翻译工作接近尾声,
在线访问链接如下:
http://es-guide-preview.elasticsearch.cn
晚点会放到 elastic.co 官网上,大家学习 Elasticsearch 又多了一份好的资料,大家在访问的过程,如果发现有问题(翻译的各种 bug,翻译有误,不合理,不通顺,标点,格式等等),欢迎前往 https://github.com/elasticsear ... guide 提交 Issue,同时也欢迎直接提交 pull request 来改进本书。
同时也希望更多的志愿者加入我们一起进行翻译,后续我们会继续翻译其他的手册,另外有很多同学自己已经在翻译部分内容,也欢迎加入我们一起,有兴趣的同学加入我们翻译的QQ群:109764489 ,一起为 Elastic 的中文资料贡献力量。
最后,再次感谢以下本书的志愿者:
薛杰,骆朗,彭秋源,魏喆,饶琛琳, 风虎,路小磊,michealzh,nodexy,sdlyjzh,落英流离, sunyonggang,Singham,烧碱,龙翔,陈思,陈华, 追风侃侃,Geolem,卷发,kfypmqqw,袁伟强,yichao, 小彬,leo,tangmisi,Alex,baifan,Evan,fanyer, wwb,瑞星,刘碧琴,walker,songgl, 吕兵,东,杜宁,秦东亮,biyuhao,刘刚, yumo,王秀文,zcola,gitqh,blackoon,David,韩炳辰, 韩陆,echolihao,Xargin,abel-sun,卞顺强, bsll,冬狼,王琦。
在几十位社区同学的共同努力下,《Elasticsearch 权威指南》的翻译工作接近尾声,
在线访问链接如下:
http://es-guide-preview.elasticsearch.cn
晚点会放到 elastic.co 官网上,大家学习 Elasticsearch 又多了一份好的资料,大家在访问的过程,如果发现有问题(翻译的各种 bug,翻译有误,不合理,不通顺,标点,格式等等),欢迎前往 https://github.com/elasticsear ... guide 提交 Issue,同时也欢迎直接提交 pull request 来改进本书。
同时也希望更多的志愿者加入我们一起进行翻译,后续我们会继续翻译其他的手册,另外有很多同学自己已经在翻译部分内容,也欢迎加入我们一起,有兴趣的同学加入我们翻译的QQ群:109764489 ,一起为 Elastic 的中文资料贡献力量。
最后,再次感谢以下本书的志愿者:
薛杰,骆朗,彭秋源,魏喆,饶琛琳, 风虎,路小磊,michealzh,nodexy,sdlyjzh,落英流离, sunyonggang,Singham,烧碱,龙翔,陈思,陈华, 追风侃侃,Geolem,卷发,kfypmqqw,袁伟强,yichao, 小彬,leo,tangmisi,Alex,baifan,Evan,fanyer, wwb,瑞星,刘碧琴,walker,songgl, 吕兵,东,杜宁,秦东亮,biyuhao,刘刚, yumo,王秀文,zcola,gitqh,blackoon,David,韩炳辰, 韩陆,echolihao,Xargin,abel-sun,卞顺强, bsll,冬狼,王琦。
收起阅读 »
Elastic{ON}DevChina2016 资料下载
本月10号在北京的第一次 Elastic 中国开发者大会的资料现在已经可以下载了,没有去现场的或者去现场的还想回顾的都可以去看看,欢迎大家踊跃交流讨论!
https://pan.baidu.com/s/1slbCZ9b
继续阅读 »
https://pan.baidu.com/s/1slbCZ9b
本月10号在北京的第一次 Elastic 中国开发者大会的资料现在已经可以下载了,没有去现场的或者去现场的还想回顾的都可以去看看,欢迎大家踊跃交流讨论!
https://pan.baidu.com/s/1slbCZ9b
收起阅读 »
https://pan.baidu.com/s/1slbCZ9b
收起阅读 »
medcl 发表于 : 2016-12-28 16:52
评论 (1)
无外网环境10分钟快速集成 elasticsearch-head
一台连上外网的机器
1.下载对应的 node 安装包
2.下载 Elasticsearch-head 安装包(顺便解压)
3.cmd 输入下面命令安装taobao提供的镜像(原因你懂的)
4.cmd cd 到你解压的 elasticsearch-head 目录下
5.install 后,head 目录下会自动生成一个 node_modules 目录,里面为相关的依赖
到此,本地的准备工作都已完成。
可以把两个安装包上传到服务器。
建议:elasticsearch-head 重新压缩后上传
/etc/profile 添加(改成你的路径)
在 elastic/config/elasticsearch.yml 添加(否则完成后网页中会显示未连接)
之后(后台启动加 &)
在网页中输入你的http://localhost:9100
继续阅读 »
1.下载对应的 node 安装包
2.下载 Elasticsearch-head 安装包(顺便解压)
3.cmd 输入下面命令安装taobao提供的镜像(原因你懂的)
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
4.cmd cd 到你解压的 elasticsearch-head 目录下
$ cnpm install
这时你会发现多了一个 node_modules 目录5.install 后,head 目录下会自动生成一个 node_modules 目录,里面为相关的依赖
到此,本地的准备工作都已完成。
可以把两个安装包上传到服务器。
建议:elasticsearch-head 重新压缩后上传
/etc/profile 添加(改成你的路径)
export NODE_HOME=/usr/local/elastic/node/node-v6.9.2-linux-x64
export PATH=$NODE_HOME/bin:$PATH
记得$ source /etc/profile
在 elastic/config/elasticsearch.yml 添加(否则完成后网页中会显示未连接)
http.cors.enabled: true
http.cors.allow-origin: "*"
之后(后台启动加 &)
./grunt server
在网页中输入你的http://localhost:9100
一台连上外网的机器
1.下载对应的 node 安装包
2.下载 Elasticsearch-head 安装包(顺便解压)
3.cmd 输入下面命令安装taobao提供的镜像(原因你懂的)
4.cmd cd 到你解压的 elasticsearch-head 目录下
5.install 后,head 目录下会自动生成一个 node_modules 目录,里面为相关的依赖
到此,本地的准备工作都已完成。
可以把两个安装包上传到服务器。
建议:elasticsearch-head 重新压缩后上传
/etc/profile 添加(改成你的路径)
在 elastic/config/elasticsearch.yml 添加(否则完成后网页中会显示未连接)
之后(后台启动加 &)
在网页中输入你的http://localhost:9100 收起阅读 »
1.下载对应的 node 安装包
2.下载 Elasticsearch-head 安装包(顺便解压)
3.cmd 输入下面命令安装taobao提供的镜像(原因你懂的)
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
4.cmd cd 到你解压的 elasticsearch-head 目录下
$ cnpm install
这时你会发现多了一个 node_modules 目录5.install 后,head 目录下会自动生成一个 node_modules 目录,里面为相关的依赖
到此,本地的准备工作都已完成。
可以把两个安装包上传到服务器。
建议:elasticsearch-head 重新压缩后上传
/etc/profile 添加(改成你的路径)
export NODE_HOME=/usr/local/elastic/node/node-v6.9.2-linux-x64
export PATH=$NODE_HOME/bin:$PATH
记得$ source /etc/profile
在 elastic/config/elasticsearch.yml 添加(否则完成后网页中会显示未连接)
http.cors.enabled: true
http.cors.allow-origin: "*"
之后(后台启动加 &)
./grunt server
在网页中输入你的http://localhost:9100 收起阅读 »
Day 14: Elasticsearch 5 入坑指南
尝鲜
10月26日,Elasticsearch5.0.0 GA终于放出,携程ES Ops团队也在第一时间在DEV和UAT环境分别进行了2.4.0 至5.0.0的升级和测试。升级完成后,除了部分Query不向前兼容(主要是Filtered Query),需要在应用端做一些修改以外,未发现其他问题。通过监控系统看对比升级前后的主要系统指标,在同等索引量的情况下,CPU使用率有明显下降 ( 30% - 50%左右) ,相信性能方面5.0应该是有较大提升的。
在测试环境稳定运行了2周以后,我们决定选定一个生产集群进行升级,考验新版本在更为复杂的用户环境下的表现。 出于对业务影响最小化的考虑,用于日志分析的集群被圈定为升级目标。该集群也是携程十几个集群中规模最大的一个,共有120个数据结点运行于70台物理机上,总数据量接近1PB。
升级前需要做一些准备工作,下载官方的Migration Helper插件,检查集群设置和索引的兼容性。对于不兼容的配置项,MH会详尽列出,其中标注为红色部分为为升级前必须修改项。1.x版本创建的索引,是无法直接升级到5的,需要先在2.x集群里做一次reindex 。 MH提供了不兼容索引扫描功能,对于找到的不兼容索引,可以直接在UI上发起reindex操作,等待结束即可。 如果是用于业务搜索集群,数据可能比较重要,建议升级前做一个Snapshot,万一升级过程出现意外,可以回退版本从备份里快速恢复数据。我们的日志集群数据量极大,也没有对数据100%不丢的要求,因此升级前没有做Snapshot。 做完所有的准备工作后,预先通知所有用户集群升级的时间以及可能产生的影响,选定了周五深夜用户低峰期,开始正式升级工作。
首先通过Ansible将新版本批量部署到所有结点并统一配置,紧接着对原有集群做了Full Stop,校验所有的ES已经停下后,开始Full Start。整个过程比较顺利,所有结点正常启动,数据恢复完成后,集群重新回到正常服务状态。
周末两天运行,未发现有任何的异样,CPU利用率也降了不少,看起来很靠谱……直到周一
踏坑
周一早上,随着用户访问量高峰来临,马上浮现出一个诡异的现象: 索引速率遇到了瓶颈,数据开始在前置的消息队列(Kafka)里堆积。 从监控数据看,尽管所有的数据结点CPU消耗都比上周同期低,磁盘IO也很低,但索引速率却低了很多。反复对比查看升级前后各类监控指标后,终于发现一个可疑点,所有结点的网络流量比升级前高了好几倍! 在集群架构上,我们是单独架设了几台client node做为数据写入和分发的入口,现在这几个node的网络流量已经饱和,成为数据写入的瓶颈。一开始,怀疑是否2.4启用了tcp压缩,而5.0取消了,但翻查官方文档后发现transport.tcp.compress在2.4和5.0里默认都是关闭的! 这时候只有两个解决办法了,要么启用tcp压缩,要么扩容client node。 先考虑了一下tcp压缩的方案,快速扒了一下ES源码,在transport.TcpTransport这个类里,sendRequest和sendResponse两个方法会根据transport.tcp.compress设置来决定发送的消息是否要经过压缩,而在messageReceived方法则会读取消息头部的状态信息,探测消息是否经过压缩以及压缩的方法,而后决定是否需要解压,以及采用的解压方式。 这样看起来,ES是允许tcp压缩和不压缩的结点之间通讯的,那么只对client node启用压缩应该就可以了。测试环境测试过后,验证了想法的可行性。于是对生产的client node开启tcp压缩,同时在数据发送端(hangout的ES output)也启用tcp压缩,重启client node后入口网络流量降到和之前2.4差不多的程度,问题得到规避。 针对这个问题在Github上提交了issue https://github.com/elastic/ela ... 21612, 但未得到官方合理的解释。
解决好这个问题,另外一个问题来了,很多执行大量历史数据搜索的用户反映出不了结果。 从监控数据看,这类查询的搜索耗时非常久,直到网关300秒超时(查询api前置的nginx代理)。我们之前对集群设置过Global Search timeout为60s,用来保护集群资源过多被超高代价的查询消耗,在2.4版本是有效果的,现在看来不起作用了。手动测试了一下,这个参数果然失效! 于是向官方报告了第2个问题:https://github.com/elastic/ela ... 21595 。 这个问题很快被官方确认为Bug,修复也很快加入到了5.0.2。 为了规避这个问题,我们只好临时修改了一下Kibana以及第三方API访问要经过的nginx proxy,默认为所有的search request加入一个超时选项。此后,问题有一些缓解,但仍然发现用户查询大范围历史数据时,部分用于存储历史数据的结点响应很慢。
我们的集群是做了冷热分离的结构的,热节点主要承担写入和存放过去24小时数据,冷结点没有写入,查询频率也低,所以为了最大化利用硬件资源,一台物理机上跑了3个实例,这样一台128GB内存的机器可以存放下近30TB的索引。查看冷结点的监控数据,看到用户查询期间磁盘的read IO非常高,直接将磁盘IO Util%撑到100%,并且可持续数小时,同时search thread pool有大量的active thread处于无法完成状态,search queue不断攀升直至饱和、开始reject。 表象上看search thread似乎一直在尝试从磁盘大量读取数据,一次search甚至可以持续几十分钟至一个小时,耗尽了所有的搜索线程,导致拒绝后续的搜索服务。 于是Github上报了第3个issue: https://github.com/elastic/ela ... 21611 这个问题找到解决办法之前,我们只能通过反复重启有问题的冷结点来缓解。 和官方讨论过程中,得知5.0在Lucene文件访问方式上有一个比较大的改动,2.4使用mmapfs读取索引文件的部分,而5.0以后改为用mmapfs读取索引文件的全部。怀疑问题和这个变动有关,尝试将所有索引文件的设置改为NIOFS后,问题迎刃而解。 搜索性能一下回到了2.4时代,再也没出现搜索线程超长时间执行的问题。之后找时间复现了这个问题,并抓取了线程栈,看到长时间执行的搜索线程一直在做Global Ordinal的构造工作。 至于为何会这样,还不清楚。 从官方给出的信息看,底层索引文件的访问模式是没有变化的,仅仅是将文件读取方式全部改成了mmapfs,理论上应该性能更好,但是看起来在我们这种一台机器跑多个ES实例,所有分配的heap为系统缓存3倍的极端用例下,大范围的数据搜索可能造成过高的磁盘读IO,集群性能指数级下降。
以上问题前后耗了4天才完全规避掉,支持团队连续熬夜后集群总算回复到平稳状态。然而好景不长,运行一段时间以后,数据结点出现疑似内存泄漏现象。结点总数据没怎么增加、甚至还有减少的情况下,heap使用率一只呈攀升趋势,Old GC无法回收内存。这个问题对用户影响较小,通过监控我们可以及时发现内存即将用尽的结点,做一次重启很快就恢复了。 为排查根源,我们对一个有问题的结点做了dump,通过MAT工具分析,看到meta data相关的一个alias对象被实例化了有6600万次之多! 在Github上提交了第四个issue: https://github.com/elastic/ela ... 22013,不多久被确认为已知问题https://github.com/elastic/ela ... 21284 ,在5.0.1已经修复。
最后还存在一个master node内存泄漏的问题,这个问题在2.4.0时代就存在了,升级到5.0.0以后依然没有修复。由于我们的master node和data node是分离的,所以这个问题比较容易通过监控发现,解决方式也很简单和迅速,重启master node即可,对用户完全无影响。之后不久,5.0.2版本正式发布,release notes里提到了对这个问题的修复 https://github.com/elastic/ela ... 21578 。
上周周末我们将集群rolling upgrade到了5.0.2,global search timeout失效和两个内存泄漏的问题从根源上解决掉了。 网络流量增大的问题依然存在,仍然需要通过启用client结点的transport.tcp.compress规避。 冷结点搜索性能的问题没看到有提及,估计没解决,安全起见,还是保持索引的文件系统为NIOFS。升级完成运行一段时间后,可以肯定,5.0.2已经比较稳定。
心得
升到5.0.2后,对于其中一组数据结点这两天特意加了点索引负载,通过监控数据将v5.0.2与2.4.0做实际运行环境的索引吞吐量对比。
在近似的CPU使用率和load情况下,5.0.2能够支撑更大的吞吐量。另外5.0带来的Instant aggregation功能,对于跨多个索引的时序类型数据的聚合也可以有效Cache了,在使用Kibana的时候提速感觉非常明显。
升级过程虽然遇到很多波折,但由于集群架构上做了角色分离(client,master,data)和冷热分离,因而Bug引起的故障比较容易被限定在一个较小的范围而不至于影响所有的功能和所有的用户。 故障点定位更加容易,规避措施也更容易实施。 部分规避措施实施过程中甚至对用户是完全无影响的,比如: 重启内存泄漏的master node)。详尽的监控为问题的发现和诊断提供了有力的支持。
Elasticsearch是非常复杂的系统,官方的测试无法覆盖所有的用例场景和数据规模,一些极端的应用场景可能触发某个深藏的Bug或者缺陷而陷入困境。 因此对于稳定性要求极高的应用,最好还是采用经过长时间考验的版本,比如v2.4.2。
继续阅读 »
10月26日,Elasticsearch5.0.0 GA终于放出,携程ES Ops团队也在第一时间在DEV和UAT环境分别进行了2.4.0 至5.0.0的升级和测试。升级完成后,除了部分Query不向前兼容(主要是Filtered Query),需要在应用端做一些修改以外,未发现其他问题。通过监控系统看对比升级前后的主要系统指标,在同等索引量的情况下,CPU使用率有明显下降 ( 30% - 50%左右) ,相信性能方面5.0应该是有较大提升的。
在测试环境稳定运行了2周以后,我们决定选定一个生产集群进行升级,考验新版本在更为复杂的用户环境下的表现。 出于对业务影响最小化的考虑,用于日志分析的集群被圈定为升级目标。该集群也是携程十几个集群中规模最大的一个,共有120个数据结点运行于70台物理机上,总数据量接近1PB。
升级前需要做一些准备工作,下载官方的Migration Helper插件,检查集群设置和索引的兼容性。对于不兼容的配置项,MH会详尽列出,其中标注为红色部分为为升级前必须修改项。1.x版本创建的索引,是无法直接升级到5的,需要先在2.x集群里做一次reindex 。 MH提供了不兼容索引扫描功能,对于找到的不兼容索引,可以直接在UI上发起reindex操作,等待结束即可。 如果是用于业务搜索集群,数据可能比较重要,建议升级前做一个Snapshot,万一升级过程出现意外,可以回退版本从备份里快速恢复数据。我们的日志集群数据量极大,也没有对数据100%不丢的要求,因此升级前没有做Snapshot。 做完所有的准备工作后,预先通知所有用户集群升级的时间以及可能产生的影响,选定了周五深夜用户低峰期,开始正式升级工作。
首先通过Ansible将新版本批量部署到所有结点并统一配置,紧接着对原有集群做了Full Stop,校验所有的ES已经停下后,开始Full Start。整个过程比较顺利,所有结点正常启动,数据恢复完成后,集群重新回到正常服务状态。
周末两天运行,未发现有任何的异样,CPU利用率也降了不少,看起来很靠谱……直到周一
踏坑
周一早上,随着用户访问量高峰来临,马上浮现出一个诡异的现象: 索引速率遇到了瓶颈,数据开始在前置的消息队列(Kafka)里堆积。 从监控数据看,尽管所有的数据结点CPU消耗都比上周同期低,磁盘IO也很低,但索引速率却低了很多。反复对比查看升级前后各类监控指标后,终于发现一个可疑点,所有结点的网络流量比升级前高了好几倍! 在集群架构上,我们是单独架设了几台client node做为数据写入和分发的入口,现在这几个node的网络流量已经饱和,成为数据写入的瓶颈。一开始,怀疑是否2.4启用了tcp压缩,而5.0取消了,但翻查官方文档后发现transport.tcp.compress在2.4和5.0里默认都是关闭的! 这时候只有两个解决办法了,要么启用tcp压缩,要么扩容client node。 先考虑了一下tcp压缩的方案,快速扒了一下ES源码,在transport.TcpTransport这个类里,sendRequest和sendResponse两个方法会根据transport.tcp.compress设置来决定发送的消息是否要经过压缩,而在messageReceived方法则会读取消息头部的状态信息,探测消息是否经过压缩以及压缩的方法,而后决定是否需要解压,以及采用的解压方式。 这样看起来,ES是允许tcp压缩和不压缩的结点之间通讯的,那么只对client node启用压缩应该就可以了。测试环境测试过后,验证了想法的可行性。于是对生产的client node开启tcp压缩,同时在数据发送端(hangout的ES output)也启用tcp压缩,重启client node后入口网络流量降到和之前2.4差不多的程度,问题得到规避。 针对这个问题在Github上提交了issue https://github.com/elastic/ela ... 21612, 但未得到官方合理的解释。
解决好这个问题,另外一个问题来了,很多执行大量历史数据搜索的用户反映出不了结果。 从监控数据看,这类查询的搜索耗时非常久,直到网关300秒超时(查询api前置的nginx代理)。我们之前对集群设置过Global Search timeout为60s,用来保护集群资源过多被超高代价的查询消耗,在2.4版本是有效果的,现在看来不起作用了。手动测试了一下,这个参数果然失效! 于是向官方报告了第2个问题:https://github.com/elastic/ela ... 21595 。 这个问题很快被官方确认为Bug,修复也很快加入到了5.0.2。 为了规避这个问题,我们只好临时修改了一下Kibana以及第三方API访问要经过的nginx proxy,默认为所有的search request加入一个超时选项。此后,问题有一些缓解,但仍然发现用户查询大范围历史数据时,部分用于存储历史数据的结点响应很慢。
我们的集群是做了冷热分离的结构的,热节点主要承担写入和存放过去24小时数据,冷结点没有写入,查询频率也低,所以为了最大化利用硬件资源,一台物理机上跑了3个实例,这样一台128GB内存的机器可以存放下近30TB的索引。查看冷结点的监控数据,看到用户查询期间磁盘的read IO非常高,直接将磁盘IO Util%撑到100%,并且可持续数小时,同时search thread pool有大量的active thread处于无法完成状态,search queue不断攀升直至饱和、开始reject。 表象上看search thread似乎一直在尝试从磁盘大量读取数据,一次search甚至可以持续几十分钟至一个小时,耗尽了所有的搜索线程,导致拒绝后续的搜索服务。 于是Github上报了第3个issue: https://github.com/elastic/ela ... 21611 这个问题找到解决办法之前,我们只能通过反复重启有问题的冷结点来缓解。 和官方讨论过程中,得知5.0在Lucene文件访问方式上有一个比较大的改动,2.4使用mmapfs读取索引文件的部分,而5.0以后改为用mmapfs读取索引文件的全部。怀疑问题和这个变动有关,尝试将所有索引文件的设置改为NIOFS后,问题迎刃而解。 搜索性能一下回到了2.4时代,再也没出现搜索线程超长时间执行的问题。之后找时间复现了这个问题,并抓取了线程栈,看到长时间执行的搜索线程一直在做Global Ordinal的构造工作。 至于为何会这样,还不清楚。 从官方给出的信息看,底层索引文件的访问模式是没有变化的,仅仅是将文件读取方式全部改成了mmapfs,理论上应该性能更好,但是看起来在我们这种一台机器跑多个ES实例,所有分配的heap为系统缓存3倍的极端用例下,大范围的数据搜索可能造成过高的磁盘读IO,集群性能指数级下降。
以上问题前后耗了4天才完全规避掉,支持团队连续熬夜后集群总算回复到平稳状态。然而好景不长,运行一段时间以后,数据结点出现疑似内存泄漏现象。结点总数据没怎么增加、甚至还有减少的情况下,heap使用率一只呈攀升趋势,Old GC无法回收内存。这个问题对用户影响较小,通过监控我们可以及时发现内存即将用尽的结点,做一次重启很快就恢复了。 为排查根源,我们对一个有问题的结点做了dump,通过MAT工具分析,看到meta data相关的一个alias对象被实例化了有6600万次之多! 在Github上提交了第四个issue: https://github.com/elastic/ela ... 22013,不多久被确认为已知问题https://github.com/elastic/ela ... 21284 ,在5.0.1已经修复。
最后还存在一个master node内存泄漏的问题,这个问题在2.4.0时代就存在了,升级到5.0.0以后依然没有修复。由于我们的master node和data node是分离的,所以这个问题比较容易通过监控发现,解决方式也很简单和迅速,重启master node即可,对用户完全无影响。之后不久,5.0.2版本正式发布,release notes里提到了对这个问题的修复 https://github.com/elastic/ela ... 21578 。
上周周末我们将集群rolling upgrade到了5.0.2,global search timeout失效和两个内存泄漏的问题从根源上解决掉了。 网络流量增大的问题依然存在,仍然需要通过启用client结点的transport.tcp.compress规避。 冷结点搜索性能的问题没看到有提及,估计没解决,安全起见,还是保持索引的文件系统为NIOFS。升级完成运行一段时间后,可以肯定,5.0.2已经比较稳定。
心得
升到5.0.2后,对于其中一组数据结点这两天特意加了点索引负载,通过监控数据将v5.0.2与2.4.0做实际运行环境的索引吞吐量对比。
在近似的CPU使用率和load情况下,5.0.2能够支撑更大的吞吐量。另外5.0带来的Instant aggregation功能,对于跨多个索引的时序类型数据的聚合也可以有效Cache了,在使用Kibana的时候提速感觉非常明显。
升级过程虽然遇到很多波折,但由于集群架构上做了角色分离(client,master,data)和冷热分离,因而Bug引起的故障比较容易被限定在一个较小的范围而不至于影响所有的功能和所有的用户。 故障点定位更加容易,规避措施也更容易实施。 部分规避措施实施过程中甚至对用户是完全无影响的,比如: 重启内存泄漏的master node)。详尽的监控为问题的发现和诊断提供了有力的支持。
Elasticsearch是非常复杂的系统,官方的测试无法覆盖所有的用例场景和数据规模,一些极端的应用场景可能触发某个深藏的Bug或者缺陷而陷入困境。 因此对于稳定性要求极高的应用,最好还是采用经过长时间考验的版本,比如v2.4.2。
尝鲜
10月26日,Elasticsearch5.0.0 GA终于放出,携程ES Ops团队也在第一时间在DEV和UAT环境分别进行了2.4.0 至5.0.0的升级和测试。升级完成后,除了部分Query不向前兼容(主要是Filtered Query),需要在应用端做一些修改以外,未发现其他问题。通过监控系统看对比升级前后的主要系统指标,在同等索引量的情况下,CPU使用率有明显下降 ( 30% - 50%左右) ,相信性能方面5.0应该是有较大提升的。
在测试环境稳定运行了2周以后,我们决定选定一个生产集群进行升级,考验新版本在更为复杂的用户环境下的表现。 出于对业务影响最小化的考虑,用于日志分析的集群被圈定为升级目标。该集群也是携程十几个集群中规模最大的一个,共有120个数据结点运行于70台物理机上,总数据量接近1PB。
升级前需要做一些准备工作,下载官方的Migration Helper插件,检查集群设置和索引的兼容性。对于不兼容的配置项,MH会详尽列出,其中标注为红色部分为为升级前必须修改项。1.x版本创建的索引,是无法直接升级到5的,需要先在2.x集群里做一次reindex 。 MH提供了不兼容索引扫描功能,对于找到的不兼容索引,可以直接在UI上发起reindex操作,等待结束即可。 如果是用于业务搜索集群,数据可能比较重要,建议升级前做一个Snapshot,万一升级过程出现意外,可以回退版本从备份里快速恢复数据。我们的日志集群数据量极大,也没有对数据100%不丢的要求,因此升级前没有做Snapshot。 做完所有的准备工作后,预先通知所有用户集群升级的时间以及可能产生的影响,选定了周五深夜用户低峰期,开始正式升级工作。
首先通过Ansible将新版本批量部署到所有结点并统一配置,紧接着对原有集群做了Full Stop,校验所有的ES已经停下后,开始Full Start。整个过程比较顺利,所有结点正常启动,数据恢复完成后,集群重新回到正常服务状态。
周末两天运行,未发现有任何的异样,CPU利用率也降了不少,看起来很靠谱……直到周一
踏坑
周一早上,随着用户访问量高峰来临,马上浮现出一个诡异的现象: 索引速率遇到了瓶颈,数据开始在前置的消息队列(Kafka)里堆积。 从监控数据看,尽管所有的数据结点CPU消耗都比上周同期低,磁盘IO也很低,但索引速率却低了很多。反复对比查看升级前后各类监控指标后,终于发现一个可疑点,所有结点的网络流量比升级前高了好几倍! 在集群架构上,我们是单独架设了几台client node做为数据写入和分发的入口,现在这几个node的网络流量已经饱和,成为数据写入的瓶颈。一开始,怀疑是否2.4启用了tcp压缩,而5.0取消了,但翻查官方文档后发现transport.tcp.compress在2.4和5.0里默认都是关闭的! 这时候只有两个解决办法了,要么启用tcp压缩,要么扩容client node。 先考虑了一下tcp压缩的方案,快速扒了一下ES源码,在transport.TcpTransport这个类里,sendRequest和sendResponse两个方法会根据transport.tcp.compress设置来决定发送的消息是否要经过压缩,而在messageReceived方法则会读取消息头部的状态信息,探测消息是否经过压缩以及压缩的方法,而后决定是否需要解压,以及采用的解压方式。 这样看起来,ES是允许tcp压缩和不压缩的结点之间通讯的,那么只对client node启用压缩应该就可以了。测试环境测试过后,验证了想法的可行性。于是对生产的client node开启tcp压缩,同时在数据发送端(hangout的ES output)也启用tcp压缩,重启client node后入口网络流量降到和之前2.4差不多的程度,问题得到规避。 针对这个问题在Github上提交了issue https://github.com/elastic/ela ... 21612, 但未得到官方合理的解释。
解决好这个问题,另外一个问题来了,很多执行大量历史数据搜索的用户反映出不了结果。 从监控数据看,这类查询的搜索耗时非常久,直到网关300秒超时(查询api前置的nginx代理)。我们之前对集群设置过Global Search timeout为60s,用来保护集群资源过多被超高代价的查询消耗,在2.4版本是有效果的,现在看来不起作用了。手动测试了一下,这个参数果然失效! 于是向官方报告了第2个问题:https://github.com/elastic/ela ... 21595 。 这个问题很快被官方确认为Bug,修复也很快加入到了5.0.2。 为了规避这个问题,我们只好临时修改了一下Kibana以及第三方API访问要经过的nginx proxy,默认为所有的search request加入一个超时选项。此后,问题有一些缓解,但仍然发现用户查询大范围历史数据时,部分用于存储历史数据的结点响应很慢。
我们的集群是做了冷热分离的结构的,热节点主要承担写入和存放过去24小时数据,冷结点没有写入,查询频率也低,所以为了最大化利用硬件资源,一台物理机上跑了3个实例,这样一台128GB内存的机器可以存放下近30TB的索引。查看冷结点的监控数据,看到用户查询期间磁盘的read IO非常高,直接将磁盘IO Util%撑到100%,并且可持续数小时,同时search thread pool有大量的active thread处于无法完成状态,search queue不断攀升直至饱和、开始reject。 表象上看search thread似乎一直在尝试从磁盘大量读取数据,一次search甚至可以持续几十分钟至一个小时,耗尽了所有的搜索线程,导致拒绝后续的搜索服务。 于是Github上报了第3个issue: https://github.com/elastic/ela ... 21611 这个问题找到解决办法之前,我们只能通过反复重启有问题的冷结点来缓解。 和官方讨论过程中,得知5.0在Lucene文件访问方式上有一个比较大的改动,2.4使用mmapfs读取索引文件的部分,而5.0以后改为用mmapfs读取索引文件的全部。怀疑问题和这个变动有关,尝试将所有索引文件的设置改为NIOFS后,问题迎刃而解。 搜索性能一下回到了2.4时代,再也没出现搜索线程超长时间执行的问题。之后找时间复现了这个问题,并抓取了线程栈,看到长时间执行的搜索线程一直在做Global Ordinal的构造工作。 至于为何会这样,还不清楚。 从官方给出的信息看,底层索引文件的访问模式是没有变化的,仅仅是将文件读取方式全部改成了mmapfs,理论上应该性能更好,但是看起来在我们这种一台机器跑多个ES实例,所有分配的heap为系统缓存3倍的极端用例下,大范围的数据搜索可能造成过高的磁盘读IO,集群性能指数级下降。
以上问题前后耗了4天才完全规避掉,支持团队连续熬夜后集群总算回复到平稳状态。然而好景不长,运行一段时间以后,数据结点出现疑似内存泄漏现象。结点总数据没怎么增加、甚至还有减少的情况下,heap使用率一只呈攀升趋势,Old GC无法回收内存。这个问题对用户影响较小,通过监控我们可以及时发现内存即将用尽的结点,做一次重启很快就恢复了。 为排查根源,我们对一个有问题的结点做了dump,通过MAT工具分析,看到meta data相关的一个alias对象被实例化了有6600万次之多! 在Github上提交了第四个issue: https://github.com/elastic/ela ... 22013,不多久被确认为已知问题https://github.com/elastic/ela ... 21284 ,在5.0.1已经修复。
最后还存在一个master node内存泄漏的问题,这个问题在2.4.0时代就存在了,升级到5.0.0以后依然没有修复。由于我们的master node和data node是分离的,所以这个问题比较容易通过监控发现,解决方式也很简单和迅速,重启master node即可,对用户完全无影响。之后不久,5.0.2版本正式发布,release notes里提到了对这个问题的修复 https://github.com/elastic/ela ... 21578 。
上周周末我们将集群rolling upgrade到了5.0.2,global search timeout失效和两个内存泄漏的问题从根源上解决掉了。 网络流量增大的问题依然存在,仍然需要通过启用client结点的transport.tcp.compress规避。 冷结点搜索性能的问题没看到有提及,估计没解决,安全起见,还是保持索引的文件系统为NIOFS。升级完成运行一段时间后,可以肯定,5.0.2已经比较稳定。
心得
升到5.0.2后,对于其中一组数据结点这两天特意加了点索引负载,通过监控数据将v5.0.2与2.4.0做实际运行环境的索引吞吐量对比。
在近似的CPU使用率和load情况下,5.0.2能够支撑更大的吞吐量。另外5.0带来的Instant aggregation功能,对于跨多个索引的时序类型数据的聚合也可以有效Cache了,在使用Kibana的时候提速感觉非常明显。
升级过程虽然遇到很多波折,但由于集群架构上做了角色分离(client,master,data)和冷热分离,因而Bug引起的故障比较容易被限定在一个较小的范围而不至于影响所有的功能和所有的用户。 故障点定位更加容易,规避措施也更容易实施。 部分规避措施实施过程中甚至对用户是完全无影响的,比如: 重启内存泄漏的master node)。详尽的监控为问题的发现和诊断提供了有力的支持。
Elasticsearch是非常复杂的系统,官方的测试无法覆盖所有的用例场景和数据规模,一些极端的应用场景可能触发某个深藏的Bug或者缺陷而陷入困境。 因此对于稳定性要求极高的应用,最好还是采用经过长时间考验的版本,比如v2.4.2。 收起阅读 »
10月26日,Elasticsearch5.0.0 GA终于放出,携程ES Ops团队也在第一时间在DEV和UAT环境分别进行了2.4.0 至5.0.0的升级和测试。升级完成后,除了部分Query不向前兼容(主要是Filtered Query),需要在应用端做一些修改以外,未发现其他问题。通过监控系统看对比升级前后的主要系统指标,在同等索引量的情况下,CPU使用率有明显下降 ( 30% - 50%左右) ,相信性能方面5.0应该是有较大提升的。
在测试环境稳定运行了2周以后,我们决定选定一个生产集群进行升级,考验新版本在更为复杂的用户环境下的表现。 出于对业务影响最小化的考虑,用于日志分析的集群被圈定为升级目标。该集群也是携程十几个集群中规模最大的一个,共有120个数据结点运行于70台物理机上,总数据量接近1PB。
升级前需要做一些准备工作,下载官方的Migration Helper插件,检查集群设置和索引的兼容性。对于不兼容的配置项,MH会详尽列出,其中标注为红色部分为为升级前必须修改项。1.x版本创建的索引,是无法直接升级到5的,需要先在2.x集群里做一次reindex 。 MH提供了不兼容索引扫描功能,对于找到的不兼容索引,可以直接在UI上发起reindex操作,等待结束即可。 如果是用于业务搜索集群,数据可能比较重要,建议升级前做一个Snapshot,万一升级过程出现意外,可以回退版本从备份里快速恢复数据。我们的日志集群数据量极大,也没有对数据100%不丢的要求,因此升级前没有做Snapshot。 做完所有的准备工作后,预先通知所有用户集群升级的时间以及可能产生的影响,选定了周五深夜用户低峰期,开始正式升级工作。
首先通过Ansible将新版本批量部署到所有结点并统一配置,紧接着对原有集群做了Full Stop,校验所有的ES已经停下后,开始Full Start。整个过程比较顺利,所有结点正常启动,数据恢复完成后,集群重新回到正常服务状态。
周末两天运行,未发现有任何的异样,CPU利用率也降了不少,看起来很靠谱……直到周一
踏坑
周一早上,随着用户访问量高峰来临,马上浮现出一个诡异的现象: 索引速率遇到了瓶颈,数据开始在前置的消息队列(Kafka)里堆积。 从监控数据看,尽管所有的数据结点CPU消耗都比上周同期低,磁盘IO也很低,但索引速率却低了很多。反复对比查看升级前后各类监控指标后,终于发现一个可疑点,所有结点的网络流量比升级前高了好几倍! 在集群架构上,我们是单独架设了几台client node做为数据写入和分发的入口,现在这几个node的网络流量已经饱和,成为数据写入的瓶颈。一开始,怀疑是否2.4启用了tcp压缩,而5.0取消了,但翻查官方文档后发现transport.tcp.compress在2.4和5.0里默认都是关闭的! 这时候只有两个解决办法了,要么启用tcp压缩,要么扩容client node。 先考虑了一下tcp压缩的方案,快速扒了一下ES源码,在transport.TcpTransport这个类里,sendRequest和sendResponse两个方法会根据transport.tcp.compress设置来决定发送的消息是否要经过压缩,而在messageReceived方法则会读取消息头部的状态信息,探测消息是否经过压缩以及压缩的方法,而后决定是否需要解压,以及采用的解压方式。 这样看起来,ES是允许tcp压缩和不压缩的结点之间通讯的,那么只对client node启用压缩应该就可以了。测试环境测试过后,验证了想法的可行性。于是对生产的client node开启tcp压缩,同时在数据发送端(hangout的ES output)也启用tcp压缩,重启client node后入口网络流量降到和之前2.4差不多的程度,问题得到规避。 针对这个问题在Github上提交了issue https://github.com/elastic/ela ... 21612, 但未得到官方合理的解释。
解决好这个问题,另外一个问题来了,很多执行大量历史数据搜索的用户反映出不了结果。 从监控数据看,这类查询的搜索耗时非常久,直到网关300秒超时(查询api前置的nginx代理)。我们之前对集群设置过Global Search timeout为60s,用来保护集群资源过多被超高代价的查询消耗,在2.4版本是有效果的,现在看来不起作用了。手动测试了一下,这个参数果然失效! 于是向官方报告了第2个问题:https://github.com/elastic/ela ... 21595 。 这个问题很快被官方确认为Bug,修复也很快加入到了5.0.2。 为了规避这个问题,我们只好临时修改了一下Kibana以及第三方API访问要经过的nginx proxy,默认为所有的search request加入一个超时选项。此后,问题有一些缓解,但仍然发现用户查询大范围历史数据时,部分用于存储历史数据的结点响应很慢。
我们的集群是做了冷热分离的结构的,热节点主要承担写入和存放过去24小时数据,冷结点没有写入,查询频率也低,所以为了最大化利用硬件资源,一台物理机上跑了3个实例,这样一台128GB内存的机器可以存放下近30TB的索引。查看冷结点的监控数据,看到用户查询期间磁盘的read IO非常高,直接将磁盘IO Util%撑到100%,并且可持续数小时,同时search thread pool有大量的active thread处于无法完成状态,search queue不断攀升直至饱和、开始reject。 表象上看search thread似乎一直在尝试从磁盘大量读取数据,一次search甚至可以持续几十分钟至一个小时,耗尽了所有的搜索线程,导致拒绝后续的搜索服务。 于是Github上报了第3个issue: https://github.com/elastic/ela ... 21611 这个问题找到解决办法之前,我们只能通过反复重启有问题的冷结点来缓解。 和官方讨论过程中,得知5.0在Lucene文件访问方式上有一个比较大的改动,2.4使用mmapfs读取索引文件的部分,而5.0以后改为用mmapfs读取索引文件的全部。怀疑问题和这个变动有关,尝试将所有索引文件的设置改为NIOFS后,问题迎刃而解。 搜索性能一下回到了2.4时代,再也没出现搜索线程超长时间执行的问题。之后找时间复现了这个问题,并抓取了线程栈,看到长时间执行的搜索线程一直在做Global Ordinal的构造工作。 至于为何会这样,还不清楚。 从官方给出的信息看,底层索引文件的访问模式是没有变化的,仅仅是将文件读取方式全部改成了mmapfs,理论上应该性能更好,但是看起来在我们这种一台机器跑多个ES实例,所有分配的heap为系统缓存3倍的极端用例下,大范围的数据搜索可能造成过高的磁盘读IO,集群性能指数级下降。
以上问题前后耗了4天才完全规避掉,支持团队连续熬夜后集群总算回复到平稳状态。然而好景不长,运行一段时间以后,数据结点出现疑似内存泄漏现象。结点总数据没怎么增加、甚至还有减少的情况下,heap使用率一只呈攀升趋势,Old GC无法回收内存。这个问题对用户影响较小,通过监控我们可以及时发现内存即将用尽的结点,做一次重启很快就恢复了。 为排查根源,我们对一个有问题的结点做了dump,通过MAT工具分析,看到meta data相关的一个alias对象被实例化了有6600万次之多! 在Github上提交了第四个issue: https://github.com/elastic/ela ... 22013,不多久被确认为已知问题https://github.com/elastic/ela ... 21284 ,在5.0.1已经修复。
最后还存在一个master node内存泄漏的问题,这个问题在2.4.0时代就存在了,升级到5.0.0以后依然没有修复。由于我们的master node和data node是分离的,所以这个问题比较容易通过监控发现,解决方式也很简单和迅速,重启master node即可,对用户完全无影响。之后不久,5.0.2版本正式发布,release notes里提到了对这个问题的修复 https://github.com/elastic/ela ... 21578 。
上周周末我们将集群rolling upgrade到了5.0.2,global search timeout失效和两个内存泄漏的问题从根源上解决掉了。 网络流量增大的问题依然存在,仍然需要通过启用client结点的transport.tcp.compress规避。 冷结点搜索性能的问题没看到有提及,估计没解决,安全起见,还是保持索引的文件系统为NIOFS。升级完成运行一段时间后,可以肯定,5.0.2已经比较稳定。
心得
升到5.0.2后,对于其中一组数据结点这两天特意加了点索引负载,通过监控数据将v5.0.2与2.4.0做实际运行环境的索引吞吐量对比。
在近似的CPU使用率和load情况下,5.0.2能够支撑更大的吞吐量。另外5.0带来的Instant aggregation功能,对于跨多个索引的时序类型数据的聚合也可以有效Cache了,在使用Kibana的时候提速感觉非常明显。
升级过程虽然遇到很多波折,但由于集群架构上做了角色分离(client,master,data)和冷热分离,因而Bug引起的故障比较容易被限定在一个较小的范围而不至于影响所有的功能和所有的用户。 故障点定位更加容易,规避措施也更容易实施。 部分规避措施实施过程中甚至对用户是完全无影响的,比如: 重启内存泄漏的master node)。详尽的监控为问题的发现和诊断提供了有力的支持。
Elasticsearch是非常复杂的系统,官方的测试无法覆盖所有的用例场景和数据规模,一些极端的应用场景可能触发某个深藏的Bug或者缺陷而陷入困境。 因此对于稳定性要求极高的应用,最好还是采用经过长时间考验的版本,比如v2.4.2。 收起阅读 »
可定制的 elasticsearch 数据导入工具 ——mysql_2_elasticsearch
A customizable importer from mysql to elasticsearch.
可定制的 elasticsearch 数据导入工具 ——基于 elasticsearch 的 JS API
【github 项目地址】
https://github.com/parksben/mysql_2_elasticsearch
【主要功能】
1. 完全使用 JS 实现数据从 MySQL 到 elasticsearch 的迁移;
2. 可批量导入多张 MySQL 表;
3. 可自定义的数据迁移规则(数据表/字段关系、字段过滤、使用正则进行数据处理);
4. 可自定义的异步分片导入方式,数据导入效率更高。
【一键安装】
【快速开始(简单用例)】
【最佳实现(完整用例)】
【注意事项及参考】
1. elasticsearch数据导入前请先配置好数据的 mapping;
2. "host_config" 更多参数设置详见 [es官方API文档] https://www.elastic.co/guide/e ... .html
3. mysql 表的自增 id 将自动替换为 "表名+_id" 的格式,如:"users_id";
4. 如出现数据缺失情况,请注意查看 elasticsearch 终端进程或日志,找出未成功导入的数据,通过设置 exception_handler 参数处理它。
继续阅读 »
可定制的 elasticsearch 数据导入工具 ——基于 elasticsearch 的 JS API
【github 项目地址】
https://github.com/parksben/mysql_2_elasticsearch
【主要功能】
1. 完全使用 JS 实现数据从 MySQL 到 elasticsearch 的迁移;
2. 可批量导入多张 MySQL 表;
3. 可自定义的数据迁移规则(数据表/字段关系、字段过滤、使用正则进行数据处理);
4. 可自定义的异步分片导入方式,数据导入效率更高。
【一键安装】
npm install mysql_2_elasticsearch
【快速开始(简单用例)】
var esMysqlRiver = require('mysql_2_elasticsearch');
var river_config = {
mysql: {
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'users',
port: 3306
},
elasticsearch: {
host_config: { // es客户端的配置参数
host: 'localhost:9200',
// log: 'trace'
},
index: 'myIndex'
},
riverMap: {
'users => users': {} // 将数据表 users 导入到 es 类型: /myIndex/users
}
};
/*
** 以下代码内容:
** 通过 esMysqlRiver 方法进行数据传输,方法的回调参数(一个JSON对象) obj 包含此次数据传输的结果
** 其中:
** 1. obj.total => 需要传输的数据表数量
** 2. obj.success => 传输成功的数据表数量
** 3. obj.failed => 传输失败的数据表数量
** 4. obj.result => 本次数据传输的结论
*/
esMysqlRiver(river_config, function(obj) {
/* 将传输结果打印到终端 */
console.log('\n---------------------------------');
console.log('总传送:' + obj.total + '项');
console.log('成功:' + obj.success + '项');
console.log('失败:' + obj.failed + '项');
if (obj.result == 'success') {
console.log('\n结论:全部数据传送完成!');
} else {
console.log('\n结论:传送未成功...');
}
console.log('---------------------------------');
console.log('\n(使用 Ctrl + C 退出进程)');
/* 将传输结果打印到终端 */
});
【最佳实现(完整用例)】
var esMysqlRiver = require('mysql_2_elasticsearch');
/*
** mysql_2_elasticsearch 的相关参数配置(详情见注释)
*/
var river_config = {
/* [必需] MySQL数据库的相关参数(根据实际情况进行修改) */
mysql: {
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'users',
port: 3306
},
/* [必需] es 相关参数(根据实际情况进行修改) */
elasticsearch: {
host_config: { // [必需] host_config 即 es客户端的配置参数,详细配置参考 es官方文档
host: 'localhost:9200',
log: 'trace',
// Other options...
},
index: 'myIndex', // [必需] es 索引名
chunkSize: 8000, // [非必需] 单分片最大数据量,默认为 5000 (条数据)
timeout: '2m' // [非必需] 单次分片请求的超时时间,默认为 1m
//(注意:此 timeout 并非es客户端请求的timeout,后者请在 host_config 中设置)
},
/* [必需] 数据传送的规则 */
riverMap: {
'users => users': { // [必需] 'a => b' 表示将 mysql数据库中名为 'a' 的 table 的所有数据 输送到 es中名为 'b' 的 type 中去
filter_out: [ // [非必需] 需要过滤的字段名,即 filter_out 中的设置的所有字段将不会被导入 elasticsearch 的数据中
'password',
'age'
],
exception_handler: { // [非必需] 异常处理器,使用JS正则表达式处理异常数据,避免 es 入库时由于类型不合法造成数据缺失
'birthday': [ // [示例] 对 users 表的 birthday 字段的异常数据进行处理
{
match: /NaN/gi, // [示例] 正则条件(此例匹配字段值为 "NaN" 的情况)
writeAs: null // [示例] 将 "NaN" 重写为 null
},
{
match: /(\d{4})年/gi, // [示例] 正则表达式(此例匹配字段值为形如 "2016年" 的情况)
writeAs: '$1.1' // [示例] 将 "2015年" 样式的数据重写为 "2016.1" 样式的数据
}
]
}
},
// Other fields' options...
}
};
/*
** 将传输结果打印到终端
*/
esMysqlRiver(river_config, function(obj) {
console.log('\n---------------------------------');
console.log('总传送:' + obj.total + '项');
console.log('成功:' + obj.success + '项');
console.log('失败:' + obj.failed + '项');
if (obj.result == 'success') {
console.log('\n结论:全部数据传送完成!');
} else {
console.log('\n结论:传送未成功...');
}
console.log('---------------------------------');
console.log('\n(使用 Ctrl + C 退出进程)');
});
【注意事项及参考】
1. elasticsearch数据导入前请先配置好数据的 mapping;
2. "host_config" 更多参数设置详见 [es官方API文档] https://www.elastic.co/guide/e ... .html
3. mysql 表的自增 id 将自动替换为 "表名+_id" 的格式,如:"users_id";
4. 如出现数据缺失情况,请注意查看 elasticsearch 终端进程或日志,找出未成功导入的数据,通过设置 exception_handler 参数处理它。
A customizable importer from mysql to elasticsearch.
可定制的 elasticsearch 数据导入工具 ——基于 elasticsearch 的 JS API
【github 项目地址】
https://github.com/parksben/mysql_2_elasticsearch
【主要功能】
1. 完全使用 JS 实现数据从 MySQL 到 elasticsearch 的迁移;
2. 可批量导入多张 MySQL 表;
3. 可自定义的数据迁移规则(数据表/字段关系、字段过滤、使用正则进行数据处理);
4. 可自定义的异步分片导入方式,数据导入效率更高。
【一键安装】
【快速开始(简单用例)】
【最佳实现(完整用例)】
【注意事项及参考】
1. elasticsearch数据导入前请先配置好数据的 mapping;
2. "host_config" 更多参数设置详见 [es官方API文档] https://www.elastic.co/guide/e ... .html
3. mysql 表的自增 id 将自动替换为 "表名+_id" 的格式,如:"users_id";
4. 如出现数据缺失情况,请注意查看 elasticsearch 终端进程或日志,找出未成功导入的数据,通过设置 exception_handler 参数处理它。 收起阅读 »
可定制的 elasticsearch 数据导入工具 ——基于 elasticsearch 的 JS API
【github 项目地址】
https://github.com/parksben/mysql_2_elasticsearch
【主要功能】
1. 完全使用 JS 实现数据从 MySQL 到 elasticsearch 的迁移;
2. 可批量导入多张 MySQL 表;
3. 可自定义的数据迁移规则(数据表/字段关系、字段过滤、使用正则进行数据处理);
4. 可自定义的异步分片导入方式,数据导入效率更高。
【一键安装】
npm install mysql_2_elasticsearch
【快速开始(简单用例)】
var esMysqlRiver = require('mysql_2_elasticsearch');
var river_config = {
mysql: {
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'users',
port: 3306
},
elasticsearch: {
host_config: { // es客户端的配置参数
host: 'localhost:9200',
// log: 'trace'
},
index: 'myIndex'
},
riverMap: {
'users => users': {} // 将数据表 users 导入到 es 类型: /myIndex/users
}
};
/*
** 以下代码内容:
** 通过 esMysqlRiver 方法进行数据传输,方法的回调参数(一个JSON对象) obj 包含此次数据传输的结果
** 其中:
** 1. obj.total => 需要传输的数据表数量
** 2. obj.success => 传输成功的数据表数量
** 3. obj.failed => 传输失败的数据表数量
** 4. obj.result => 本次数据传输的结论
*/
esMysqlRiver(river_config, function(obj) {
/* 将传输结果打印到终端 */
console.log('\n---------------------------------');
console.log('总传送:' + obj.total + '项');
console.log('成功:' + obj.success + '项');
console.log('失败:' + obj.failed + '项');
if (obj.result == 'success') {
console.log('\n结论:全部数据传送完成!');
} else {
console.log('\n结论:传送未成功...');
}
console.log('---------------------------------');
console.log('\n(使用 Ctrl + C 退出进程)');
/* 将传输结果打印到终端 */
});
【最佳实现(完整用例)】
var esMysqlRiver = require('mysql_2_elasticsearch');
/*
** mysql_2_elasticsearch 的相关参数配置(详情见注释)
*/
var river_config = {
/* [必需] MySQL数据库的相关参数(根据实际情况进行修改) */
mysql: {
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'users',
port: 3306
},
/* [必需] es 相关参数(根据实际情况进行修改) */
elasticsearch: {
host_config: { // [必需] host_config 即 es客户端的配置参数,详细配置参考 es官方文档
host: 'localhost:9200',
log: 'trace',
// Other options...
},
index: 'myIndex', // [必需] es 索引名
chunkSize: 8000, // [非必需] 单分片最大数据量,默认为 5000 (条数据)
timeout: '2m' // [非必需] 单次分片请求的超时时间,默认为 1m
//(注意:此 timeout 并非es客户端请求的timeout,后者请在 host_config 中设置)
},
/* [必需] 数据传送的规则 */
riverMap: {
'users => users': { // [必需] 'a => b' 表示将 mysql数据库中名为 'a' 的 table 的所有数据 输送到 es中名为 'b' 的 type 中去
filter_out: [ // [非必需] 需要过滤的字段名,即 filter_out 中的设置的所有字段将不会被导入 elasticsearch 的数据中
'password',
'age'
],
exception_handler: { // [非必需] 异常处理器,使用JS正则表达式处理异常数据,避免 es 入库时由于类型不合法造成数据缺失
'birthday': [ // [示例] 对 users 表的 birthday 字段的异常数据进行处理
{
match: /NaN/gi, // [示例] 正则条件(此例匹配字段值为 "NaN" 的情况)
writeAs: null // [示例] 将 "NaN" 重写为 null
},
{
match: /(\d{4})年/gi, // [示例] 正则表达式(此例匹配字段值为形如 "2016年" 的情况)
writeAs: '$1.1' // [示例] 将 "2015年" 样式的数据重写为 "2016.1" 样式的数据
}
]
}
},
// Other fields' options...
}
};
/*
** 将传输结果打印到终端
*/
esMysqlRiver(river_config, function(obj) {
console.log('\n---------------------------------');
console.log('总传送:' + obj.total + '项');
console.log('成功:' + obj.success + '项');
console.log('失败:' + obj.failed + '项');
if (obj.result == 'success') {
console.log('\n结论:全部数据传送完成!');
} else {
console.log('\n结论:传送未成功...');
}
console.log('---------------------------------');
console.log('\n(使用 Ctrl + C 退出进程)');
});
【注意事项及参考】
1. elasticsearch数据导入前请先配置好数据的 mapping;
2. "host_config" 更多参数设置详见 [es官方API文档] https://www.elastic.co/guide/e ... .html
3. mysql 表的自增 id 将自动替换为 "表名+_id" 的格式,如:"users_id";
4. 如出现数据缺失情况,请注意查看 elasticsearch 终端进程或日志,找出未成功导入的数据,通过设置 exception_handler 参数处理它。 收起阅读 »
parksben 发表于 : 2016-12-13 23:55
评论 (7)
Day6:《记一次es性能调优》
一.前言
应medcl写es文章的时候,其实这段时间es研究的不多,感觉没什么新东西可写。
考虑只有这次调优心得可与大家分享,文笔有限,见谅!
二.背景
先交代一下背景,调优的项目是某电商类搜索项目,流量来自于前端的app和h5。
搜索主要是根据用户的地理位置和关键字等条件搜索附近的商家和商品。
商品数据大概在5000w左右,商品更新很频繁,更新量大概是每天2000w条左右,(因商家经常会促销、或者调上下架状态、改价格等)查询也相当频繁。
集群有2个集群,一个主一个备,用于有问题的时候随时切换。主集群有8个节点,配置是32核,
32g内存的docker的机器。给es jvm分配20g内存,jdk 版本是1.7,gc 是使用parnew/cms gc。
这个项目我是后期加入的,来的时候项目已上线。由于参与进来的时候es跑的也还是比较稳定,所以也一直
没调过es的参数。程序,参数基本上也就保持上线的时候那个样子。
es上线的时候是用的1.5版本,后期没升过级。
三.问题
项目大概跑了一年多,时间来到大概16年的9月份。搜索请求响应时间开始出现几秒才完成的情况,
我就被拉过来调优了。通过我们自己内部的调用方法监控,tp99和avg这些值还好,维持在200ms以下。max 最大有5,6s的情况,而且次数有点多。
这里没怎么折腾,很快定位就是es gc导致的。翻了一下es gc日志,就是cms remark这个阶段时间特别长,
而且 这个阶段是stop the world的。
四.解决
为什么remark阶段这么长时间? 直接上结论,就是一次cms 周期内,并发标记后到remark这个期间jvm 堆内存对象
变化很大。说白了对应我们的场景就是一大波 es bulk操作。对应Bigdesk观察,几秒的卡顿基本都出现在一大波 es bulk操作之后。
这里解释一下,引用网上文章的说法:
remark如果耗时较长,通常原因是在cms gc已经结束了concurrent-mark步骤后,旧生代的引用关系仍然发生了很多的变化,旧生代的引用关系发生变化的原因主要是:
* 在这个间隔时间段内,新生代晋升到旧生代的对象比较多;
* 在这个间隔时间段内,新生代没怎么发生ygc,但创建出来的对象又比较多,这种通常就只能是新生代比较大的原因;
原文地址:
http://hellojava.info/?tag=cms-gc-remark
调整一:
加cms gc 的 线程
直接从根源入手,你remark 慢,我就让你跑快点。
因为我们是32 核的cpu ,cpu 利用率用bigdesk观察还是很低的,5%左右。这么低,那就加点线程呗。
-XX:ParallelGCThreads= N
-XX:ParallelCMSThreads= M
调整这2个参数都可以,它们的关系:ParallelCMSThreads = (ParallelGCThreads + 3)/4)
调整后情况缓解了一些,remark还是有3,4秒的情况。
调整二:
关于这点是我们自己的问题。一次bulk 操作就写1条数据。
是的,你没有看错,我们这边的工程师就是这么干的。
一年以前提过这里最好是能合并写,但现在还是这个样子。
这里有一些业务上的原因,合并写会导致一些字段值不准确。
合并写暂时没办法,只能调整es 了。(这里说明一下,其实合并写应该是本次优化比较有效果的办法,可惜这招不让我用。)
通过bigdesk观察,bulk线程池有reject的情况。
但就增加bulk线程池的消费线程,加快数据的消费速度,减少对象驻留在jvm 的时间。
调整后情况没有明显的好转,
但肯定有用,能优化一点是一点嘛。
调整三:
再次从gc入手, -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark
这个是网上找的办法:
为了减少第二次暂停的时间,开启并行remark: -XX:+CMSParallelRemarkEnabled。 如果remark还是过长的话,可以开启-XX:+CMSScavengeBeforeRemark选项,强制 remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。调整后情况也没有特别的好转。
以上都是从减小单次cms gc的开销的方向去解决问题,然后我就换了个方向,降低cms gc发生的次数,让它少发生或者不发生。
调整四:
这里调整了一共5个参数,
Xmn10g ==> 8g
CMSInitiatingOccupancyFraction=70 ==>80
index.cache.filter.max_size 2g==>1g
index.cache.filter.expire 2m==>40s
index.refresh_interval 20s==>30s
前2个参数没什么好说的,提高cms gc 被触发的条件,降低cms gc 触发几率。
后3个参数是本文的重点,这里大概讲下es 对于filter cache的管理。
这部分是通过阅读源码分析出来的,涉及代码还挺多,有点复杂,还有很大一部分还是lucene的代码。
这里就不贴大段代码了。
es 对于 filter cache管理是内部维护了一个map的结构(实际是利用com.google.common.cache实现的),关键是这个map 的key 是个它自己定义的类
叫 FilterCacheKey,它override了equals方法
public FilterCacheKey(Object readerKey, Object filterKey) {
this.readerKey = readerKey;
this.filterKey = filterKey;
}
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
FilterCacheKey that = (FilterCacheKey) o;
return (readerKey().equals(that.readerKey()) && filterKey.equals(that.filterKey));
}
从这里可以看出,filter cache 能否被再次利用到就跟readerKey和filterKey 有关。
filterkey如果你build 查询语句的时候什么都没设置的话,就是filter对象本身。
举个例子,TermFilter,如果term一样,那前后2次查询filterKey是一致的。
关键是这个readerKey是否一致呢?这个readerKey其实就是lucene 的 indexReader,如果前后2次查询有数据更新并且
index.refresh_interval 这个参数设置的比较小,es 会去刷新indexReader,那么很有可能readerKey不一致。
对应我们的场景,数据更新频繁,而且index.refresh_interval 比较小,那么缓存利用率就不太高。
后一次查询的filter cache 会重新put 到上面提到的map里,然后这个index.cache.filter.max_size 2g
就迅速占满(我们程序代码里很多地方使用到了filter),配多大都有可能占满。那这个filter cache什么时候被移除呢,index.cache.filter.expire 2m管移除这个事,当然应该还有size满了移除的策略。
这就造成了缓存没什么用,还占这么大地方,还占那么久。
然后这个filter cache就很可能跑到 old gen去了。
那么就让它占少点,不干活就快点走:
index.cache.filter.max_size 2g==>1g
index.cache.filter.expire 2m==>40s
index.refresh_interval 20s==>30s
这些调整完毕,重启es ,通过bigdesk ,gc a线图好看多了,cms gc 基本没了,monitor gc 也顺畅多了。
五.总结和备注
总结:
1.优化这个事,我认为是业务上的优化要比技术上优化有效的多
2.日志和监控是你最好的朋友,仔细的看,你总会看出什么
3.es 缓存要利用好,还是需要好好去设计,回头考虑单独写一篇。
备注:
因为这次优化后,我就离开了原来的公司,没有了原来的环境。所以本文
部分参数和数字可能不准确,我仅凭记忆完成。
一.前言
应medcl写es文章的时候,其实这段时间es研究的不多,感觉没什么新东西可写。
考虑只有这次调优心得可与大家分享,文笔有限,见谅!
二.背景
先交代一下背景,调优的项目是某电商类搜索项目,流量来自于前端的app和h5。
搜索主要是根据用户的地理位置和关键字等条件搜索附近的商家和商品。
商品数据大概在5000w左右,商品更新很频繁,更新量大概是每天2000w条左右,(因商家经常会促销、或者调上下架状态、改价格等)查询也相当频繁。
集群有2个集群,一个主一个备,用于有问题的时候随时切换。主集群有8个节点,配置是32核,
32g内存的docker的机器。给es jvm分配20g内存,jdk 版本是1.7,gc 是使用parnew/cms gc。
这个项目我是后期加入的,来的时候项目已上线。由于参与进来的时候es跑的也还是比较稳定,所以也一直
没调过es的参数。程序,参数基本上也就保持上线的时候那个样子。
es上线的时候是用的1.5版本,后期没升过级。
三.问题
项目大概跑了一年多,时间来到大概16年的9月份。搜索请求响应时间开始出现几秒才完成的情况,
我就被拉过来调优了。通过我们自己内部的调用方法监控,tp99和avg这些值还好,维持在200ms以下。max 最大有5,6s的情况,而且次数有点多。
这里没怎么折腾,很快定位就是es gc导致的。翻了一下es gc日志,就是cms remark这个阶段时间特别长,
而且 这个阶段是stop the world的。
四.解决
为什么remark阶段这么长时间? 直接上结论,就是一次cms 周期内,并发标记后到remark这个期间jvm 堆内存对象
变化很大。说白了对应我们的场景就是一大波 es bulk操作。对应Bigdesk观察,几秒的卡顿基本都出现在一大波 es bulk操作之后。
这里解释一下,引用网上文章的说法:
remark如果耗时较长,通常原因是在cms gc已经结束了concurrent-mark步骤后,旧生代的引用关系仍然发生了很多的变化,旧生代的引用关系发生变化的原因主要是:
* 在这个间隔时间段内,新生代晋升到旧生代的对象比较多;
* 在这个间隔时间段内,新生代没怎么发生ygc,但创建出来的对象又比较多,这种通常就只能是新生代比较大的原因;
原文地址:
http://hellojava.info/?tag=cms-gc-remark
调整一:
加cms gc 的 线程
直接从根源入手,你remark 慢,我就让你跑快点。
因为我们是32 核的cpu ,cpu 利用率用bigdesk观察还是很低的,5%左右。这么低,那就加点线程呗。
-XX:ParallelGCThreads= N
-XX:ParallelCMSThreads= M
调整这2个参数都可以,它们的关系:ParallelCMSThreads = (ParallelGCThreads + 3)/4)
调整后情况缓解了一些,remark还是有3,4秒的情况。
调整二:
关于这点是我们自己的问题。一次bulk 操作就写1条数据。
是的,你没有看错,我们这边的工程师就是这么干的。
一年以前提过这里最好是能合并写,但现在还是这个样子。
这里有一些业务上的原因,合并写会导致一些字段值不准确。
合并写暂时没办法,只能调整es 了。(这里说明一下,其实合并写应该是本次优化比较有效果的办法,可惜这招不让我用。)
通过bigdesk观察,bulk线程池有reject的情况。
但就增加bulk线程池的消费线程,加快数据的消费速度,减少对象驻留在jvm 的时间。
调整后情况没有明显的好转,
但肯定有用,能优化一点是一点嘛。
调整三:
再次从gc入手, -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark
这个是网上找的办法:
为了减少第二次暂停的时间,开启并行remark: -XX:+CMSParallelRemarkEnabled。 如果remark还是过长的话,可以开启-XX:+CMSScavengeBeforeRemark选项,强制 remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。调整后情况也没有特别的好转。
以上都是从减小单次cms gc的开销的方向去解决问题,然后我就换了个方向,降低cms gc发生的次数,让它少发生或者不发生。
调整四:
这里调整了一共5个参数,
Xmn10g ==> 8g
CMSInitiatingOccupancyFraction=70 ==>80
index.cache.filter.max_size 2g==>1g
index.cache.filter.expire 2m==>40s
index.refresh_interval 20s==>30s
前2个参数没什么好说的,提高cms gc 被触发的条件,降低cms gc 触发几率。
后3个参数是本文的重点,这里大概讲下es 对于filter cache的管理。
这部分是通过阅读源码分析出来的,涉及代码还挺多,有点复杂,还有很大一部分还是lucene的代码。
这里就不贴大段代码了。
es 对于 filter cache管理是内部维护了一个map的结构(实际是利用com.google.common.cache实现的),关键是这个map 的key 是个它自己定义的类
叫 FilterCacheKey,它override了equals方法
public FilterCacheKey(Object readerKey, Object filterKey) {
this.readerKey = readerKey;
this.filterKey = filterKey;
}
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
FilterCacheKey that = (FilterCacheKey) o;
return (readerKey().equals(that.readerKey()) && filterKey.equals(that.filterKey));
}
从这里可以看出,filter cache 能否被再次利用到就跟readerKey和filterKey 有关。
filterkey如果你build 查询语句的时候什么都没设置的话,就是filter对象本身。
举个例子,TermFilter,如果term一样,那前后2次查询filterKey是一致的。
关键是这个readerKey是否一致呢?这个readerKey其实就是lucene 的 indexReader,如果前后2次查询有数据更新并且
index.refresh_interval 这个参数设置的比较小,es 会去刷新indexReader,那么很有可能readerKey不一致。
对应我们的场景,数据更新频繁,而且index.refresh_interval 比较小,那么缓存利用率就不太高。
后一次查询的filter cache 会重新put 到上面提到的map里,然后这个index.cache.filter.max_size 2g
就迅速占满(我们程序代码里很多地方使用到了filter),配多大都有可能占满。那这个filter cache什么时候被移除呢,index.cache.filter.expire 2m管移除这个事,当然应该还有size满了移除的策略。
这就造成了缓存没什么用,还占这么大地方,还占那么久。
然后这个filter cache就很可能跑到 old gen去了。
那么就让它占少点,不干活就快点走:
index.cache.filter.max_size 2g==>1g
index.cache.filter.expire 2m==>40s
index.refresh_interval 20s==>30s
这些调整完毕,重启es ,通过bigdesk ,gc a线图好看多了,cms gc 基本没了,monitor gc 也顺畅多了。
五.总结和备注
总结:
1.优化这个事,我认为是业务上的优化要比技术上优化有效的多
2.日志和监控是你最好的朋友,仔细的看,你总会看出什么
3.es 缓存要利用好,还是需要好好去设计,回头考虑单独写一篇。
备注:
因为这次优化后,我就离开了原来的公司,没有了原来的环境。所以本文
部分参数和数字可能不准确,我仅凭记忆完成。
收起阅读 »
求一个Kibana5能用的高德URL
kibana到5.0后可以直接在kibana.yml上面使用tilemap.url进行配置,
但是之前使用的url貌似都不行,有伙伴能提供个K5能用的url吗?
但是之前使用的url貌似都不行,有伙伴能提供个K5能用的url吗?
kibana到5.0后可以直接在kibana.yml上面使用tilemap.url进行配置,
但是之前使用的url貌似都不行,有伙伴能提供个K5能用的url吗?
但是之前使用的url貌似都不行,有伙伴能提供个K5能用的url吗?
Yid1201 发表于 : 2016-12-06 15:41
评论 (0)
Day5: 《PacketBeat奇妙的OOM小记》
Beats这个项目的确很好用,几行命令下来,一个成型的Agent就出来了。使用者只需要关注采集什么数据就好,后续的事情libbeat基本都处理完了。不过值得吐槽的是,Beat太散了,管理起来东一个西一个的,产品化的时候对客户说,我们要在机器上放n个Agent不知道客户会是什么样的表情。
不过轻量级、已部署的特点还是极大的吸引了我,于是就有了后面的事情了。
PacketBeat不明原因的OOM
某天我把PacketBeat放到了我的服务器上面,这台服务器上面有个MongoDB,MongoDB主要是拿来存放ES的元数据的。ES2.x的时候并没有很好的元数据管理,为了能让ES的索引分配的比较均匀,并且有元数据辅助查询,设计好一个元数据管理的仓库是必要的。然后我打开了对MongoDB的抓包功能,恩,一切都很好,接着我打开了日志管理页面,看到了一条一条的MongoDB的包被抓回来,解码,然后塞到了ES。可是第二天一看,咦??Packet跪了?不是吧,ElasticSearch做的产品这么不稳定么。我不信。
然后我又启动了第二次,紧接着熟练的top了一下,观察了PacketBeat半个多小时,在被观察的这段时间里面,PacketBeat的表现非常的正常,看不出有什么异样。好吧,那上一次的OOM可能只是个意外,Windows也经常蓝屏嘛,OOM一次也正常。结果第二天我再次打开终端,发现这货居然又OOM了!!
好吧好吧,我感觉我已经踩到Bug了,拿了开源社区这么多东西,总得贡献一下的,好吧,提个Issue去 https://github.com/elastic/beats/issues/2867
真相只有一个
微信群里面聊起这个奇妙的OOM,Medcl大神问是不是因为采集了ES的日志,(我的这台服务器和日志服务器有关系)然后导致滚雪球把PacketBeat给滚死了。咦?说不定真的是这个原因耶!但是看了看PacketBeat,我并没有抓ES的包,而且假如我采集了ES的包,应该一下就OOM掉了,不应该等那么久。不过这么一说,却仿佛打开了新世界的大门
我把这台服务器在日志服务器中的角色重新梳理了下,终于发现了这次OOM的原因了。。
由于2.X的ES没有比较好的元数据信息,所以当日志送到LogServer的时候,我做了些额外的操作,让LogServer持久化到ES一定量的时候就会往Mongo写一下元数据信息(当然也有其他服务会往里面做CRUD啦),开始的时候访问Mongo的次数其实是很少的,假设按1W来算。那么问题来了,由于我们的PacketBeat抓了Mongo的包,那么LogServer往ES的CRUD操作都会被PacketBeat给抓走,然后再送回给LogServer
那么一个隐藏的滚雪球事件就产生了,刚开始的那段时间,Mongo被抓包的次数只有1W,然后就往LogServer多送了1W条日志,不。。应该多很多,毕竟网络包嘛,然后就导致LogServer因为要管理元数据的频率开始逐渐地提高,逐渐提高CRUD的频率后抓包的内容也越来越多,紧接着到这发生到LogServer的频率也越来越高。。。。。每次PacketBeat崩掉的时候,都送了80W左右的日志量出去,然后它就OOM掉了(因为我那台机器就只剩下2G的空闲内存给它用,被系统给干掉了)。。我居然发现了这样的场景
结论
使用PacketBeat的时候,记得要留意一下有没这种反馈型滚雪球的情况,多发生在自己的日志服务器上面。当然那种直接抓ES的就没什么好说了,估计启动了之后没多久就崩溃掉了
继续阅读 »
不过轻量级、已部署的特点还是极大的吸引了我,于是就有了后面的事情了。
PacketBeat不明原因的OOM
某天我把PacketBeat放到了我的服务器上面,这台服务器上面有个MongoDB,MongoDB主要是拿来存放ES的元数据的。ES2.x的时候并没有很好的元数据管理,为了能让ES的索引分配的比较均匀,并且有元数据辅助查询,设计好一个元数据管理的仓库是必要的。然后我打开了对MongoDB的抓包功能,恩,一切都很好,接着我打开了日志管理页面,看到了一条一条的MongoDB的包被抓回来,解码,然后塞到了ES。可是第二天一看,咦??Packet跪了?不是吧,ElasticSearch做的产品这么不稳定么。我不信。
然后我又启动了第二次,紧接着熟练的top了一下,观察了PacketBeat半个多小时,在被观察的这段时间里面,PacketBeat的表现非常的正常,看不出有什么异样。好吧,那上一次的OOM可能只是个意外,Windows也经常蓝屏嘛,OOM一次也正常。结果第二天我再次打开终端,发现这货居然又OOM了!!
好吧好吧,我感觉我已经踩到Bug了,拿了开源社区这么多东西,总得贡献一下的,好吧,提个Issue去 https://github.com/elastic/beats/issues/2867
真相只有一个
微信群里面聊起这个奇妙的OOM,Medcl大神问是不是因为采集了ES的日志,(我的这台服务器和日志服务器有关系)然后导致滚雪球把PacketBeat给滚死了。咦?说不定真的是这个原因耶!但是看了看PacketBeat,我并没有抓ES的包,而且假如我采集了ES的包,应该一下就OOM掉了,不应该等那么久。不过这么一说,却仿佛打开了新世界的大门
我把这台服务器在日志服务器中的角色重新梳理了下,终于发现了这次OOM的原因了。。
由于2.X的ES没有比较好的元数据信息,所以当日志送到LogServer的时候,我做了些额外的操作,让LogServer持久化到ES一定量的时候就会往Mongo写一下元数据信息(当然也有其他服务会往里面做CRUD啦),开始的时候访问Mongo的次数其实是很少的,假设按1W来算。那么问题来了,由于我们的PacketBeat抓了Mongo的包,那么LogServer往ES的CRUD操作都会被PacketBeat给抓走,然后再送回给LogServer
那么一个隐藏的滚雪球事件就产生了,刚开始的那段时间,Mongo被抓包的次数只有1W,然后就往LogServer多送了1W条日志,不。。应该多很多,毕竟网络包嘛,然后就导致LogServer因为要管理元数据的频率开始逐渐地提高,逐渐提高CRUD的频率后抓包的内容也越来越多,紧接着到这发生到LogServer的频率也越来越高。。。。。每次PacketBeat崩掉的时候,都送了80W左右的日志量出去,然后它就OOM掉了(因为我那台机器就只剩下2G的空闲内存给它用,被系统给干掉了)。。我居然发现了这样的场景
结论
使用PacketBeat的时候,记得要留意一下有没这种反馈型滚雪球的情况,多发生在自己的日志服务器上面。当然那种直接抓ES的就没什么好说了,估计启动了之后没多久就崩溃掉了
Beats这个项目的确很好用,几行命令下来,一个成型的Agent就出来了。使用者只需要关注采集什么数据就好,后续的事情libbeat基本都处理完了。不过值得吐槽的是,Beat太散了,管理起来东一个西一个的,产品化的时候对客户说,我们要在机器上放n个Agent不知道客户会是什么样的表情。
不过轻量级、已部署的特点还是极大的吸引了我,于是就有了后面的事情了。
PacketBeat不明原因的OOM
某天我把PacketBeat放到了我的服务器上面,这台服务器上面有个MongoDB,MongoDB主要是拿来存放ES的元数据的。ES2.x的时候并没有很好的元数据管理,为了能让ES的索引分配的比较均匀,并且有元数据辅助查询,设计好一个元数据管理的仓库是必要的。然后我打开了对MongoDB的抓包功能,恩,一切都很好,接着我打开了日志管理页面,看到了一条一条的MongoDB的包被抓回来,解码,然后塞到了ES。可是第二天一看,咦??Packet跪了?不是吧,ElasticSearch做的产品这么不稳定么。我不信。
然后我又启动了第二次,紧接着熟练的top了一下,观察了PacketBeat半个多小时,在被观察的这段时间里面,PacketBeat的表现非常的正常,看不出有什么异样。好吧,那上一次的OOM可能只是个意外,Windows也经常蓝屏嘛,OOM一次也正常。结果第二天我再次打开终端,发现这货居然又OOM了!!
好吧好吧,我感觉我已经踩到Bug了,拿了开源社区这么多东西,总得贡献一下的,好吧,提个Issue去 https://github.com/elastic/beats/issues/2867
真相只有一个
微信群里面聊起这个奇妙的OOM,Medcl大神问是不是因为采集了ES的日志,(我的这台服务器和日志服务器有关系)然后导致滚雪球把PacketBeat给滚死了。咦?说不定真的是这个原因耶!但是看了看PacketBeat,我并没有抓ES的包,而且假如我采集了ES的包,应该一下就OOM掉了,不应该等那么久。不过这么一说,却仿佛打开了新世界的大门
我把这台服务器在日志服务器中的角色重新梳理了下,终于发现了这次OOM的原因了。。
由于2.X的ES没有比较好的元数据信息,所以当日志送到LogServer的时候,我做了些额外的操作,让LogServer持久化到ES一定量的时候就会往Mongo写一下元数据信息(当然也有其他服务会往里面做CRUD啦),开始的时候访问Mongo的次数其实是很少的,假设按1W来算。那么问题来了,由于我们的PacketBeat抓了Mongo的包,那么LogServer往ES的CRUD操作都会被PacketBeat给抓走,然后再送回给LogServer
那么一个隐藏的滚雪球事件就产生了,刚开始的那段时间,Mongo被抓包的次数只有1W,然后就往LogServer多送了1W条日志,不。。应该多很多,毕竟网络包嘛,然后就导致LogServer因为要管理元数据的频率开始逐渐地提高,逐渐提高CRUD的频率后抓包的内容也越来越多,紧接着到这发生到LogServer的频率也越来越高。。。。。每次PacketBeat崩掉的时候,都送了80W左右的日志量出去,然后它就OOM掉了(因为我那台机器就只剩下2G的空闲内存给它用,被系统给干掉了)。。我居然发现了这样的场景
结论
使用PacketBeat的时候,记得要留意一下有没这种反馈型滚雪球的情况,多发生在自己的日志服务器上面。当然那种直接抓ES的就没什么好说了,估计启动了之后没多久就崩溃掉了 收起阅读 »
不过轻量级、已部署的特点还是极大的吸引了我,于是就有了后面的事情了。
PacketBeat不明原因的OOM
某天我把PacketBeat放到了我的服务器上面,这台服务器上面有个MongoDB,MongoDB主要是拿来存放ES的元数据的。ES2.x的时候并没有很好的元数据管理,为了能让ES的索引分配的比较均匀,并且有元数据辅助查询,设计好一个元数据管理的仓库是必要的。然后我打开了对MongoDB的抓包功能,恩,一切都很好,接着我打开了日志管理页面,看到了一条一条的MongoDB的包被抓回来,解码,然后塞到了ES。可是第二天一看,咦??Packet跪了?不是吧,ElasticSearch做的产品这么不稳定么。我不信。
然后我又启动了第二次,紧接着熟练的top了一下,观察了PacketBeat半个多小时,在被观察的这段时间里面,PacketBeat的表现非常的正常,看不出有什么异样。好吧,那上一次的OOM可能只是个意外,Windows也经常蓝屏嘛,OOM一次也正常。结果第二天我再次打开终端,发现这货居然又OOM了!!
好吧好吧,我感觉我已经踩到Bug了,拿了开源社区这么多东西,总得贡献一下的,好吧,提个Issue去 https://github.com/elastic/beats/issues/2867
真相只有一个
微信群里面聊起这个奇妙的OOM,Medcl大神问是不是因为采集了ES的日志,(我的这台服务器和日志服务器有关系)然后导致滚雪球把PacketBeat给滚死了。咦?说不定真的是这个原因耶!但是看了看PacketBeat,我并没有抓ES的包,而且假如我采集了ES的包,应该一下就OOM掉了,不应该等那么久。不过这么一说,却仿佛打开了新世界的大门
我把这台服务器在日志服务器中的角色重新梳理了下,终于发现了这次OOM的原因了。。
由于2.X的ES没有比较好的元数据信息,所以当日志送到LogServer的时候,我做了些额外的操作,让LogServer持久化到ES一定量的时候就会往Mongo写一下元数据信息(当然也有其他服务会往里面做CRUD啦),开始的时候访问Mongo的次数其实是很少的,假设按1W来算。那么问题来了,由于我们的PacketBeat抓了Mongo的包,那么LogServer往ES的CRUD操作都会被PacketBeat给抓走,然后再送回给LogServer
那么一个隐藏的滚雪球事件就产生了,刚开始的那段时间,Mongo被抓包的次数只有1W,然后就往LogServer多送了1W条日志,不。。应该多很多,毕竟网络包嘛,然后就导致LogServer因为要管理元数据的频率开始逐渐地提高,逐渐提高CRUD的频率后抓包的内容也越来越多,紧接着到这发生到LogServer的频率也越来越高。。。。。每次PacketBeat崩掉的时候,都送了80W左右的日志量出去,然后它就OOM掉了(因为我那台机器就只剩下2G的空闲内存给它用,被系统给干掉了)。。我居然发现了这样的场景
结论
使用PacketBeat的时候,记得要留意一下有没这种反馈型滚雪球的情况,多发生在自己的日志服务器上面。当然那种直接抓ES的就没什么好说了,估计启动了之后没多久就崩溃掉了 收起阅读 »
ES5.0.0 安装记录
创建用户:adduser elasticsearch
可查看创建结果:
##########/etc/passwd
##########/etc/shadow
##########/etc/group
配置环境变量
修改文件:/home/elasticsearch/.profile
追加内容:
export JAVA_HOME=/home/elasticsearch/java/jdk1.8.0_73
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$CLASSPATH
export PATH
配置elasticsearch5.0.0
tar -xf elasticsearch-5.0.0.tar.gz -C /home/elasticsearch/
cd /home/elasticsearch/
ln -sv elasticsearch-5.0.0 elasticsearch
mkdir -pv /esdata/elasticsearch/{data,logs}
chown -R elasticsearch.elasticsearch /esdata/elasticsearch
修改ES配置文件
/home/elasticsearch/elasticsearch-5.0.0/config/elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: "*"
path.data: /esdata/elasticsearch/data
path.logs: /esdata/elasticsearch/logs
network.host: 192.168.25.57
http.port: 8201
transport.tcp.port: 8301
bootstrap.memory_lock: true
/home/elasticsearch/elasticsearch-5.0.0/config/jvm.options
-Xms8g
-Xmx8g
修改系统参数
/etc/security/limits.conf
elasticsearch soft nproc 65536
elasticsearch hard nproc 65536
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
elasticsearch - memlock unlimited
/etc/sysctl.conf
vm.max_map_count = 262144
加载更新:sysctl -p
启动ES服务
su - elasticsearch -c "/home/elasticsearch/elasticsearch/bin/elasticsearch &"
创建用户:adduser elasticsearch
可查看创建结果:
##########/etc/passwd
##########/etc/shadow
##########/etc/group
配置环境变量
修改文件:/home/elasticsearch/.profile
追加内容:
export JAVA_HOME=/home/elasticsearch/java/jdk1.8.0_73
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$CLASSPATH
export PATH
配置elasticsearch5.0.0
tar -xf elasticsearch-5.0.0.tar.gz -C /home/elasticsearch/
cd /home/elasticsearch/
ln -sv elasticsearch-5.0.0 elasticsearch
mkdir -pv /esdata/elasticsearch/{data,logs}
chown -R elasticsearch.elasticsearch /esdata/elasticsearch
修改ES配置文件
/home/elasticsearch/elasticsearch-5.0.0/config/elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: "*"
path.data: /esdata/elasticsearch/data
path.logs: /esdata/elasticsearch/logs
network.host: 192.168.25.57
http.port: 8201
transport.tcp.port: 8301
bootstrap.memory_lock: true
/home/elasticsearch/elasticsearch-5.0.0/config/jvm.options
-Xms8g
-Xmx8g
修改系统参数
/etc/security/limits.conf
elasticsearch soft nproc 65536
elasticsearch hard nproc 65536
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
elasticsearch - memlock unlimited
/etc/sysctl.conf
vm.max_map_count = 262144
加载更新:sysctl -p
启动ES服务
su - elasticsearch -c "/home/elasticsearch/elasticsearch/bin/elasticsearch &"
收起阅读 »
sunping 发表于 : 2016-12-05 09:42
评论 (1)
Day4: 《将sql转换为es的DSL》
es现在几乎已经是开源搜索引擎的事实标准了,搭建简易,使用方便。不过在很多公司里(包括我司的部分部门),并不是把它当搜索引擎来用,而是当db来用。因为本身查询/搜索原理的区别,使es在千万或者亿级的数据中进行逻辑筛选相对高效。例如一些wms、工单查询系统,单表几十个甚至上百个字段,如果在数据库里为每种类型的查询都建立合适的索引,成本比较高,更不用说索引建多了还会影响到插入速度,后期的索引优化也是比较麻烦的问题。
不过如果把es当db来使的话,始终会有一个绕不过去的坎。就是es的DSL。让所有业务开发去学习dsl的话也不是不可以,但DSL真的有点反人类(不要打我)。简单的a and b或者a or b还比较容易写,如果我要的是a and (b and (c or d) and e)的查询逻辑,那我觉得谁写都会晕。即使是用官方或者第三方提供的client,如果需求多种多样的话,想要灵活地实现`需求=>DSL`的过程还是比较痛苦。
对于业务开发来说,当然是sql更平易近人(毕竟写了这么多年CRUD)。所以还有一种歪门邪道的流派,直接把sql转成DSL。要做sql和DSL转换的工作,需要进行sql的解析,先不要怵,这个年代找一个靠谱的sql parser还是比较容易的。比如阿里开源的druid连接池里的sql模块:
https://github.com/alibaba/dru ... d/sql
因为笔者的实现是用的下面这个golang版的parser:
https://github.com/xwb1989/sqlparser
所以用这个来举例吧~
这个是其作者从youtube/vitness里提取并进行改进的一个parser,我们能用到的是一部分子集功能,只需要解析select类的sql。
先举个简单的sql的例子:
sql的select语句在被解析之后生成一个Select的结构体,如果我们不关心使用者需要的字段的话,可以先把SelectExprs/Distinct/Comments/Lock里的内容忽略掉。如果不是分组统计类的需求,也可以先把GroupBy/Having忽略掉。这里我们关心的就剩下From、Where、OrderBy和Limit。
From对应的TableExprs实际上可以认为是简单的字符串,这里的值其实就是`x_order`。
OrderBy实际上是一个元素为
的数组。
Limit也很简单,
其实就是俩数字。
那么剩下的就是这个Where结构了。where会被解析为AST(`https://en.wikipedia.org/wiki/Abstract_syntax_tree`),中文是抽象语法树。在不说子查询之类的情况下,这个AST也不会太复杂,毕竟where后面的情况比起编译原理里的程序语言来说情况还是要少得多的。以上述的sql为例,这里解析出来的Where结构是这样的:
只有一个节点,一个ComparisonExpr表达式,这个ComparisonExpr,中文比较表达式,指代的就是我们sql里的`user_id = 1`。实际上我们可以认为这个"比较表达式"即是所有复杂AST的叶子节点。叶子结点在AST遍历的时候一般也就是递归的终点。因为这里的查询比较简单,整棵AST只有一个节点,即根节点和叶子节点都是这个ComparisonExpr。
再来一个复杂点的例子。
稍微有一些二叉树的样子了吧。把这棵简单的树画出来:
回到文章开头的那个复杂的例子:
看着真够麻烦的,我们把这棵树画出来:
这样看着就直观多了。我们有了AST的结构,那要怎么对应到es的查询DSL呢?少安毋躁。
我们知道es的bool query是可以进行嵌套的,所以实际上我们可以同样可以构造出树形结构的bool query。这里把bool嵌套must和bool嵌套should简化一下,写成boolmust和boolshould:
例如a and (b and c)
我们把query内部的第一个boolmust当作根节点,内部嵌套的a和另一个boolmust当作它的两个子节点,然后b和c又是这个boolmust的子节点。可以看出来,实际上这棵树和AST的节点可以一一对应。
再回到文章开头的例子,a and (b and (c or d) and e):
和前文中ast来做个简单的结构对比~
和前文中sql的where解析后的AST树也是完全匹配的。思路来了,只要对sql解析生成的AST进行递归,即可得到这棵树。当然了,这里还可以进行一些优化,如果子节点的类型和父
节点的类型一致,例如都是and表达式或者都是or表达式,我们可以在生成dsl的时候将其作为并列的节点进行合并,这里不再赘述。
在递归中有这么几种情况:
这样问题就变成了如何处理AST的叶子节点。前面提到了叶子节点实际上就是Comparison Expression。只要简单进行一些对应即可,下面是我们的项目里的一些对应关系,仅供参考:
最后再附上demo
https://github.com/cch123/elasticsql
继续阅读 »
不过如果把es当db来使的话,始终会有一个绕不过去的坎。就是es的DSL。让所有业务开发去学习dsl的话也不是不可以,但DSL真的有点反人类(不要打我)。简单的a and b或者a or b还比较容易写,如果我要的是a and (b and (c or d) and e)的查询逻辑,那我觉得谁写都会晕。即使是用官方或者第三方提供的client,如果需求多种多样的话,想要灵活地实现`需求=>DSL`的过程还是比较痛苦。
对于业务开发来说,当然是sql更平易近人(毕竟写了这么多年CRUD)。所以还有一种歪门邪道的流派,直接把sql转成DSL。要做sql和DSL转换的工作,需要进行sql的解析,先不要怵,这个年代找一个靠谱的sql parser还是比较容易的。比如阿里开源的druid连接池里的sql模块:
https://github.com/alibaba/dru ... d/sql
因为笔者的实现是用的下面这个golang版的parser:
https://github.com/xwb1989/sqlparser
所以用这个来举例吧~
这个是其作者从youtube/vitness里提取并进行改进的一个parser,我们能用到的是一部分子集功能,只需要解析select类的sql。
先举个简单的sql的例子:
select * from x_order where userId = 1 order by id desc limit 10,1;
解析之后会变成golang的一个struct,来看看具体的定义:
&sqlparser.Select{
Comments:sqlparser.Comments(nil),
Distinct:"",
SelectExprs:sqlparser.SelectExprs{(*sqlparser.StarExpr)(0xc42000aee0)},
From:sqlparser.TableExprs{(*sqlparser.AliasedTableExpr)(0xc420016930)},
Where:(*sqlparser.Where)(0xc42000afa0),
GroupBy:sqlparser.GroupBy(nil),
Having:(*sqlparser.Where)(nil),
OrderBy:sqlparser.OrderBy{(*sqlparser.Order)(0xc42000af20)},
Limit:(*sqlparser.Limit)(0xc42000af80),
Lock:""
}
sql的select语句在被解析之后生成一个Select的结构体,如果我们不关心使用者需要的字段的话,可以先把SelectExprs/Distinct/Comments/Lock里的内容忽略掉。如果不是分组统计类的需求,也可以先把GroupBy/Having忽略掉。这里我们关心的就剩下From、Where、OrderBy和Limit。
From对应的TableExprs实际上可以认为是简单的字符串,这里的值其实就是`x_order`。
OrderBy实际上是一个元素为
type Order struct {
Expr ValExpr
Direction string
}\
的数组。
Limit也很简单,
type Limit struct {
Offset, Rowcount ValExpr
}
其实就是俩数字。
那么剩下的就是这个Where结构了。where会被解析为AST(`https://en.wikipedia.org/wiki/Abstract_syntax_tree`),中文是抽象语法树。在不说子查询之类的情况下,这个AST也不会太复杂,毕竟where后面的情况比起编译原理里的程序语言来说情况还是要少得多的。以上述的sql为例,这里解析出来的Where结构是这样的:
&sqlparser.Where{
Type:"where",
Expr:(*sqlparser.ComparisonExpr)(0xc420016a50)
}
只有一个节点,一个ComparisonExpr表达式,这个ComparisonExpr,中文比较表达式,指代的就是我们sql里的`user_id = 1`。实际上我们可以认为这个"比较表达式"即是所有复杂AST的叶子节点。叶子结点在AST遍历的时候一般也就是递归的终点。因为这里的查询比较简单,整棵AST只有一个节点,即根节点和叶子节点都是这个ComparisonExpr。
再来一个复杂点的例子。
select * from users where user_id = 1 and product_id =2
=>
&sqlparser.Where{
Type:"where",
Expr:(*sqlparser.AndExpr)(0xc42000b020)
}
AndExpr有Left和Right两个成员,分别是:
Left:
&sqlparser.ComparisonExpr{
Operator:"=",
Left:(*sqlparser.ColName)(0xc4200709c0),
Right:sqlparser.NumVal{0x31}
}
Right:
&sqlparser.ComparisonExpr{
Operator:"=",
Left:(*sqlparser.ColName)(0xc420070a50),
Right:sqlparser.NumVal{0x32}
}
稍微有一些二叉树的样子了吧。把这棵简单的树画出来:
回到文章开头的那个复杂的例子:
a and (b and (c or d) and e)
=>
select * from user_history where user_id = 1 and (product_id = 2 and (star_num = 4 or star_num = 5) and banned = 1)
看着真够麻烦的,我们把这棵树画出来:
这样看着就直观多了。我们有了AST的结构,那要怎么对应到es的查询DSL呢?少安毋躁。
我们知道es的bool query是可以进行嵌套的,所以实际上我们可以同样可以构造出树形结构的bool query。这里把bool嵌套must和bool嵌套should简化一下,写成boolmust和boolshould:
例如a and (b and c)
query {
boolmust {
a,
boolmust {
b,
c
}
}
}
我们把query内部的第一个boolmust当作根节点,内部嵌套的a和另一个boolmust当作它的两个子节点,然后b和c又是这个boolmust的子节点。可以看出来,实际上这棵树和AST的节点可以一一对应。
再回到文章开头的例子,a and (b and (c or d) and e):
query {
boolmust {
a,
boolmust {
b,
boolshould {
c,
d
},
e
}
}
}
和前文中ast来做个简单的结构对比~
和前文中sql的where解析后的AST树也是完全匹配的。思路来了,只要对sql解析生成的AST进行递归,即可得到这棵树。当然了,这里还可以进行一些优化,如果子节点的类型和父
节点的类型一致,例如都是and表达式或者都是or表达式,我们可以在生成dsl的时候将其作为并列的节点进行合并,这里不再赘述。
在递归中有这么几种情况:
AndExpr => bool must [{left}, {right}]
OrExpr => bool should [{left}, {right}]
ComparisonExpr => 一般是叶子节点
ParenBoolExpr => 指代括号表达式,其实内部是上述三种节点的某一种,所以直接取出内部节点按上述方法来处理
这样问题就变成了如何处理AST的叶子节点。前面提到了叶子节点实际上就是Comparison Expression。只要简单进行一些对应即可,下面是我们的项目里的一些对应关系,仅供参考:
最后再附上demo
https://github.com/cch123/elasticsql
es现在几乎已经是开源搜索引擎的事实标准了,搭建简易,使用方便。不过在很多公司里(包括我司的部分部门),并不是把它当搜索引擎来用,而是当db来用。因为本身查询/搜索原理的区别,使es在千万或者亿级的数据中进行逻辑筛选相对高效。例如一些wms、工单查询系统,单表几十个甚至上百个字段,如果在数据库里为每种类型的查询都建立合适的索引,成本比较高,更不用说索引建多了还会影响到插入速度,后期的索引优化也是比较麻烦的问题。
不过如果把es当db来使的话,始终会有一个绕不过去的坎。就是es的DSL。让所有业务开发去学习dsl的话也不是不可以,但DSL真的有点反人类(不要打我)。简单的a and b或者a or b还比较容易写,如果我要的是a and (b and (c or d) and e)的查询逻辑,那我觉得谁写都会晕。即使是用官方或者第三方提供的client,如果需求多种多样的话,想要灵活地实现`需求=>DSL`的过程还是比较痛苦。
对于业务开发来说,当然是sql更平易近人(毕竟写了这么多年CRUD)。所以还有一种歪门邪道的流派,直接把sql转成DSL。要做sql和DSL转换的工作,需要进行sql的解析,先不要怵,这个年代找一个靠谱的sql parser还是比较容易的。比如阿里开源的druid连接池里的sql模块:
https://github.com/alibaba/dru ... d/sql
因为笔者的实现是用的下面这个golang版的parser:
https://github.com/xwb1989/sqlparser
所以用这个来举例吧~
这个是其作者从youtube/vitness里提取并进行改进的一个parser,我们能用到的是一部分子集功能,只需要解析select类的sql。
先举个简单的sql的例子:
sql的select语句在被解析之后生成一个Select的结构体,如果我们不关心使用者需要的字段的话,可以先把SelectExprs/Distinct/Comments/Lock里的内容忽略掉。如果不是分组统计类的需求,也可以先把GroupBy/Having忽略掉。这里我们关心的就剩下From、Where、OrderBy和Limit。
From对应的TableExprs实际上可以认为是简单的字符串,这里的值其实就是`x_order`。
OrderBy实际上是一个元素为
的数组。
Limit也很简单,
其实就是俩数字。
那么剩下的就是这个Where结构了。where会被解析为AST(`https://en.wikipedia.org/wiki/Abstract_syntax_tree`),中文是抽象语法树。在不说子查询之类的情况下,这个AST也不会太复杂,毕竟where后面的情况比起编译原理里的程序语言来说情况还是要少得多的。以上述的sql为例,这里解析出来的Where结构是这样的:
只有一个节点,一个ComparisonExpr表达式,这个ComparisonExpr,中文比较表达式,指代的就是我们sql里的`user_id = 1`。实际上我们可以认为这个"比较表达式"即是所有复杂AST的叶子节点。叶子结点在AST遍历的时候一般也就是递归的终点。因为这里的查询比较简单,整棵AST只有一个节点,即根节点和叶子节点都是这个ComparisonExpr。
再来一个复杂点的例子。
稍微有一些二叉树的样子了吧。把这棵简单的树画出来:
回到文章开头的那个复杂的例子:
看着真够麻烦的,我们把这棵树画出来:
这样看着就直观多了。我们有了AST的结构,那要怎么对应到es的查询DSL呢?少安毋躁。
我们知道es的bool query是可以进行嵌套的,所以实际上我们可以同样可以构造出树形结构的bool query。这里把bool嵌套must和bool嵌套should简化一下,写成boolmust和boolshould:
例如a and (b and c)
我们把query内部的第一个boolmust当作根节点,内部嵌套的a和另一个boolmust当作它的两个子节点,然后b和c又是这个boolmust的子节点。可以看出来,实际上这棵树和AST的节点可以一一对应。
再回到文章开头的例子,a and (b and (c or d) and e):
和前文中ast来做个简单的结构对比~
和前文中sql的where解析后的AST树也是完全匹配的。思路来了,只要对sql解析生成的AST进行递归,即可得到这棵树。当然了,这里还可以进行一些优化,如果子节点的类型和父
节点的类型一致,例如都是and表达式或者都是or表达式,我们可以在生成dsl的时候将其作为并列的节点进行合并,这里不再赘述。
在递归中有这么几种情况:
这样问题就变成了如何处理AST的叶子节点。前面提到了叶子节点实际上就是Comparison Expression。只要简单进行一些对应即可,下面是我们的项目里的一些对应关系,仅供参考:
最后再附上demo
https://github.com/cch123/elasticsql 收起阅读 »
不过如果把es当db来使的话,始终会有一个绕不过去的坎。就是es的DSL。让所有业务开发去学习dsl的话也不是不可以,但DSL真的有点反人类(不要打我)。简单的a and b或者a or b还比较容易写,如果我要的是a and (b and (c or d) and e)的查询逻辑,那我觉得谁写都会晕。即使是用官方或者第三方提供的client,如果需求多种多样的话,想要灵活地实现`需求=>DSL`的过程还是比较痛苦。
对于业务开发来说,当然是sql更平易近人(毕竟写了这么多年CRUD)。所以还有一种歪门邪道的流派,直接把sql转成DSL。要做sql和DSL转换的工作,需要进行sql的解析,先不要怵,这个年代找一个靠谱的sql parser还是比较容易的。比如阿里开源的druid连接池里的sql模块:
https://github.com/alibaba/dru ... d/sql
因为笔者的实现是用的下面这个golang版的parser:
https://github.com/xwb1989/sqlparser
所以用这个来举例吧~
这个是其作者从youtube/vitness里提取并进行改进的一个parser,我们能用到的是一部分子集功能,只需要解析select类的sql。
先举个简单的sql的例子:
select * from x_order where userId = 1 order by id desc limit 10,1;
解析之后会变成golang的一个struct,来看看具体的定义:
&sqlparser.Select{
Comments:sqlparser.Comments(nil),
Distinct:"",
SelectExprs:sqlparser.SelectExprs{(*sqlparser.StarExpr)(0xc42000aee0)},
From:sqlparser.TableExprs{(*sqlparser.AliasedTableExpr)(0xc420016930)},
Where:(*sqlparser.Where)(0xc42000afa0),
GroupBy:sqlparser.GroupBy(nil),
Having:(*sqlparser.Where)(nil),
OrderBy:sqlparser.OrderBy{(*sqlparser.Order)(0xc42000af20)},
Limit:(*sqlparser.Limit)(0xc42000af80),
Lock:""
}
sql的select语句在被解析之后生成一个Select的结构体,如果我们不关心使用者需要的字段的话,可以先把SelectExprs/Distinct/Comments/Lock里的内容忽略掉。如果不是分组统计类的需求,也可以先把GroupBy/Having忽略掉。这里我们关心的就剩下From、Where、OrderBy和Limit。
From对应的TableExprs实际上可以认为是简单的字符串,这里的值其实就是`x_order`。
OrderBy实际上是一个元素为
type Order struct {
Expr ValExpr
Direction string
}\
的数组。
Limit也很简单,
type Limit struct {
Offset, Rowcount ValExpr
}
其实就是俩数字。
那么剩下的就是这个Where结构了。where会被解析为AST(`https://en.wikipedia.org/wiki/Abstract_syntax_tree`),中文是抽象语法树。在不说子查询之类的情况下,这个AST也不会太复杂,毕竟where后面的情况比起编译原理里的程序语言来说情况还是要少得多的。以上述的sql为例,这里解析出来的Where结构是这样的:
&sqlparser.Where{
Type:"where",
Expr:(*sqlparser.ComparisonExpr)(0xc420016a50)
}
只有一个节点,一个ComparisonExpr表达式,这个ComparisonExpr,中文比较表达式,指代的就是我们sql里的`user_id = 1`。实际上我们可以认为这个"比较表达式"即是所有复杂AST的叶子节点。叶子结点在AST遍历的时候一般也就是递归的终点。因为这里的查询比较简单,整棵AST只有一个节点,即根节点和叶子节点都是这个ComparisonExpr。
再来一个复杂点的例子。
select * from users where user_id = 1 and product_id =2
=>
&sqlparser.Where{
Type:"where",
Expr:(*sqlparser.AndExpr)(0xc42000b020)
}
AndExpr有Left和Right两个成员,分别是:
Left:
&sqlparser.ComparisonExpr{
Operator:"=",
Left:(*sqlparser.ColName)(0xc4200709c0),
Right:sqlparser.NumVal{0x31}
}
Right:
&sqlparser.ComparisonExpr{
Operator:"=",
Left:(*sqlparser.ColName)(0xc420070a50),
Right:sqlparser.NumVal{0x32}
}
稍微有一些二叉树的样子了吧。把这棵简单的树画出来:
回到文章开头的那个复杂的例子:
a and (b and (c or d) and e)
=>
select * from user_history where user_id = 1 and (product_id = 2 and (star_num = 4 or star_num = 5) and banned = 1)
看着真够麻烦的,我们把这棵树画出来:
这样看着就直观多了。我们有了AST的结构,那要怎么对应到es的查询DSL呢?少安毋躁。
我们知道es的bool query是可以进行嵌套的,所以实际上我们可以同样可以构造出树形结构的bool query。这里把bool嵌套must和bool嵌套should简化一下,写成boolmust和boolshould:
例如a and (b and c)
query {
boolmust {
a,
boolmust {
b,
c
}
}
}
我们把query内部的第一个boolmust当作根节点,内部嵌套的a和另一个boolmust当作它的两个子节点,然后b和c又是这个boolmust的子节点。可以看出来,实际上这棵树和AST的节点可以一一对应。
再回到文章开头的例子,a and (b and (c or d) and e):
query {
boolmust {
a,
boolmust {
b,
boolshould {
c,
d
},
e
}
}
}
和前文中ast来做个简单的结构对比~
和前文中sql的where解析后的AST树也是完全匹配的。思路来了,只要对sql解析生成的AST进行递归,即可得到这棵树。当然了,这里还可以进行一些优化,如果子节点的类型和父
节点的类型一致,例如都是and表达式或者都是or表达式,我们可以在生成dsl的时候将其作为并列的节点进行合并,这里不再赘述。
在递归中有这么几种情况:
AndExpr => bool must [{left}, {right}]
OrExpr => bool should [{left}, {right}]
ComparisonExpr => 一般是叶子节点
ParenBoolExpr => 指代括号表达式,其实内部是上述三种节点的某一种,所以直接取出内部节点按上述方法来处理
这样问题就变成了如何处理AST的叶子节点。前面提到了叶子节点实际上就是Comparison Expression。只要简单进行一些对应即可,下面是我们的项目里的一些对应关系,仅供参考:
最后再附上demo
https://github.com/cch123/elasticsql 收起阅读 »
Day3: 《创建一个你自己的 Beat》
Elastic Advent 第三篇, 手头上事情实在太多,这两天正在进行权威指南翻译的冲刺阶段,临时填下坑,翻译官网的一篇文章吧(原文:https://www.elastic.co/blog/build-your-own-beat),Advent 规则很自由的,没说不能翻译文章啊,嘿嘿嘿,另外号召大家踊跃报名,大家一起玩才有意思。
活动地址:http://elasticsearch.cn/article/107
言归正传!
Beat 是一个开源的用来构建轻量级数据汇集的平台,可用于将各种类型的数据发送至Elasticsearch 与 Logstash。我们有 Packetbeat 用于监控局域网内服务器之间的网络流量信息,有 Filebeat 收集服务器上的日志信息,还有新推出的 Metricbeat 可以定期获取外部系统的监控指标信息,除此以外,你还可以非常方便的基于 libbeat 框架来构建你属于自己的专属 Beat,目前 beas 社区已经有超过25个 Community Beats 了。
Elastic 还提供一个 Beat generator(Beat 生成器)来帮你快速构建属于你自己的 Beat。通过这篇博客你将会看到如何通过 Beat 生成器来快速创建一个你自己的 Beat。今天我们创建的是一个叫做 lsbeat 的 Beat,lsbeat 非常类似 Unix 系统下的命令行 ls,我们用 lsbeat 来索引目录和文件信息。本篇文章环境基于 Unix 系统,如果你是 Windows 或是其它系统,相关操作可能需要根据实际情况进行调整。
第一步 – 配置 Golang 环境
Beats 是用 Golang写的,显然,要创建和开发一个 beat,Golang 环境必不可少,关于这方面的文章很多,建议查看这篇 Golang 的安装向导: install Golang。当前 Beats 需要的最低版本是 Golang 1.6。另外请确保正确设置了你的 $GOPATH 环境变量。
另外 Golang Glide 被用来进行包的依赖管理,所以也需要确保正确安装,最低版本是 Glide 0.10.0,安装说明点这里。
让我们先来看看 lsbeat 将会用到的一段代码吧,这是一个简单的 golang 程序,通过命令行接收一个目录参数,然后列出该目录下的文件和子目录信息。
后面我们将使用到这段代码和 listDir 函数。
第二步 – 生成项目
要生成一个你自己的 Beat,就要用到 beat-generator 了,首先你必须安装 cookiecutter。安装的详细说明看这里。安装好 cookiecutter 之后,我们要给自己的 Beat 起一个好听的名字,最好是小写的英文字母,我们今天这个例子就叫 lsbeat 吧。
生成项目模板之前,我们需要下载 Beats generator 包文件,就在 beats 仓库。安装好 GoLang 之后,你就可以很方便的使用 go get 命令来下载 Beats generator 包文件了。 当你执行下面的这个命令后,所有的源码文件都会下载到 $GOPATH/src 目录。
在 GOPATH 下创建一个以你自己github账号名称命名的目录,并切换过去,然后执行 cookiecutter 命令并指向 Beat Generator 源码路径。
Cookiecutter 接下来会问你几个问题,比如项目名称,我们输入:lsbeat;你的 github 用户名,输入你自己的 github 账户;还有两个关于beat和beat_path应该会自动识别,默认回车就好;最后的问题,你可以输入你的姓和名。
现在应该已经创建好了一个名为 lsbeat 的目录,并且里面应该会生成一些文件,让我们一起来看一下吧,结构如下:
我们刚刚已经生成好了一个原始的 Beat 模板了,但是你还需要获取相关的依赖和设置好 git 仓库。
首先,你需要拉取依赖的相关包信息,我们的这个例子是 lsbeat,我们先做一些的基本的配置,回头再看看详细看看其它的模板和配置文件,只需要执行 make setup 就可以自动获取依赖。
当你创建好了自己的 Beat 之后,记得上传到 github 仓库,并和社区进行分享哦,如下:
要 push lsbeat 到你的 git 仓库,只需要执行如下命令:
恭喜你,现在你已经完成了一个 Beat ,并且发布了第一个版本到了 Github,不过里面还没有什么具体内容,现在让我们进一步看看里面的代码吧。
第四步 – 配置
执行过上面一系列命令之后,项目里将会自动创建名为 lsbeat.yml 和 lsbeat.template.json 的配置文件。所有的基本配置项都已经生成在了里面。
Period 参数包含在每一个生成的 Beats 里面,它表示 lsbeat 将会每隔 1 秒钟轮询一次,这里我们修改 period 时间间隔为 10 秒。还可以在修改 etc/ 目录下面的 beat.yml 文件,这里新增一个 path 参数表示我们具体要监听哪个目录。
参数添加好了之后,我们只需要运行 make update 命令就能让这些修改应用到配置文件lsbeat.yml。
修改完 yml 文件,记得修改 config/config.go文件,添加一个path 参数。
同时我们修改 period 默认时间间隔为 10 秒,默认监听的是当前目录 (.) 。.
第五步 – 添加代码
每一个 Beat 需要实现 Beater 接口,里面定义了 Run() 和 Stop() 函数。.
我们可以定义一个名为 Lsbeat 的结构体,然后用这个对象实现 Beater 接口。然后添加字段 lastIndexTime 来保存最后运行的时间戳信息。
另外,每个 Beat 还需要实现 New() 方法来接收 Beat 配置信息和返回 Lsbeat 的具体实例。
在我们的 lsbeat 例子中,我们要做的就是扩展默认的 Run() 函数来导出指定目录的文件和子目录信息。
在修改 Run() 函数之前,我们先在 lsbeat.go 增加 listDir() 函数,就是我们前面最开始测试的那段代码,用于收集目录和文件信息的简单例子稍微修改一下。另外我们还要生成以下字段信息:
第一次运行的时候我们将索引所有的文件和目录信息,然后我们再定期检查是否有新文件被创建或者修改,再索引这些新创建的文件和目录。每次定期检查的时间戳都会保存在 lasIndexTime 变量,完整代码如下:
记住在最开始需要导入 “io/ioutil” 包。
现在,让我们看看如何在 Run() 函数里面调用 listDir() 函数,并且保存时间戳到 lasIndexTime 变量。
函数 Stop() 用来中断 run 的循环执行,保持默认生成的就行。
到这里,编码部分基本就完成了。我们接下来添加新字段到 mapping 中,修改文件 etc/fields.yml。.
重新应用新的配置。
$ make update
字段 file_name 将使用 nGram 分词,我们还需要在文件 lsbeat.template.json 的 “settings” 节点添加一个自定义的 analyzer。
第六步 – 编译和运行
现在我们可以编译和运行了,只需要执行 make 命令就可以编译出可执行文件 lsbeat (lsbeat.exe on windows) 。
$ make
修改 lsbeat.yml 文件,设置需要监听的目录,如: “/Users/ec2-user/go”,记住是全路径。
同时确保你的 elasticsearch 和 kibana 正常运行。现在运行一下 lsbeat 命令看看会发生什么事情吧。
$ ./lsbeat
打开Kibana,通过调用 _cat 接口我们看看的索引是不是创建了。
可以看到创建了一个名为 lsbeat-2016.06.03 的索引,并且看到已经有了一些文档了。现在对 filename 字段查询一下,由于使用的是 nGram 分词,支持模糊匹配,我们使用 lsbe 关键字搜一下。
大功告成! 恭喜你,你已经完成了第一个属于你自己的 beat。
继续阅读 »
活动地址:http://elasticsearch.cn/article/107
言归正传!
Beat 是一个开源的用来构建轻量级数据汇集的平台,可用于将各种类型的数据发送至Elasticsearch 与 Logstash。我们有 Packetbeat 用于监控局域网内服务器之间的网络流量信息,有 Filebeat 收集服务器上的日志信息,还有新推出的 Metricbeat 可以定期获取外部系统的监控指标信息,除此以外,你还可以非常方便的基于 libbeat 框架来构建你属于自己的专属 Beat,目前 beas 社区已经有超过25个 Community Beats 了。
Elastic 还提供一个 Beat generator(Beat 生成器)来帮你快速构建属于你自己的 Beat。通过这篇博客你将会看到如何通过 Beat 生成器来快速创建一个你自己的 Beat。今天我们创建的是一个叫做 lsbeat 的 Beat,lsbeat 非常类似 Unix 系统下的命令行 ls,我们用 lsbeat 来索引目录和文件信息。本篇文章环境基于 Unix 系统,如果你是 Windows 或是其它系统,相关操作可能需要根据实际情况进行调整。
第一步 – 配置 Golang 环境
Beats 是用 Golang写的,显然,要创建和开发一个 beat,Golang 环境必不可少,关于这方面的文章很多,建议查看这篇 Golang 的安装向导: install Golang。当前 Beats 需要的最低版本是 Golang 1.6。另外请确保正确设置了你的 $GOPATH 环境变量。
另外 Golang Glide 被用来进行包的依赖管理,所以也需要确保正确安装,最低版本是 Glide 0.10.0,安装说明点这里。
让我们先来看看 lsbeat 将会用到的一段代码吧,这是一个简单的 golang 程序,通过命令行接收一个目录参数,然后列出该目录下的文件和子目录信息。
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
//apply run path "." without argument.
if len(os.Args) == 1 {
listDir(".")
} else {
listDir(os.Args[1])
}
}
func listDir(dirFile string) {
files, _ := ioutil.ReadDir(dirFile)
for _, f := range files {
t := f.ModTime()
fmt.Println(f.Name(), dirFile+"/"+f.Name(), f.IsDir(), t, f.Size())
if f.IsDir() {
listDir(dirFile + "/" + f.Name())
}
}
}
后面我们将使用到这段代码和 listDir 函数。
第二步 – 生成项目
要生成一个你自己的 Beat,就要用到 beat-generator 了,首先你必须安装 cookiecutter。安装的详细说明看这里。安装好 cookiecutter 之后,我们要给自己的 Beat 起一个好听的名字,最好是小写的英文字母,我们今天这个例子就叫 lsbeat 吧。
生成项目模板之前,我们需要下载 Beats generator 包文件,就在 beats 仓库。安装好 GoLang 之后,你就可以很方便的使用 go get 命令来下载 Beats generator 包文件了。 当你执行下面的这个命令后,所有的源码文件都会下载到 $GOPATH/src 目录。
$ go get github.com/elastic/beats
在 GOPATH 下创建一个以你自己github账号名称命名的目录,并切换过去,然后执行 cookiecutter 命令并指向 Beat Generator 源码路径。
$ cd $GOPATH/src/github.com/{user}
$ cookiecutter $GOPATH/src/github.com/elastic/beats/generate/beat
Cookiecutter 接下来会问你几个问题,比如项目名称,我们输入:lsbeat;你的 github 用户名,输入你自己的 github 账户;还有两个关于beat和beat_path应该会自动识别,默认回车就好;最后的问题,你可以输入你的姓和名。
project_name [Examplebeat]: lsbeat
github_name [your-github-name]: {username}
beat [lsbeat]:
beat_path [github.com/{github id}]:
full_name [Firstname Lastname]: {Full Name}
现在应该已经创建好了一个名为 lsbeat 的目录,并且里面应该会生成一些文件,让我们一起来看一下吧,结构如下:
$ cd lsbeat
$ tree
.
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── beater
│ └── lsbeat.go
├── config
│ ├── config.go
│ └── config_test.go
├── dev-tools
│ └── packer
│ ├── Makefile
│ ├── beats
│ │ └── lsbeat.yml
│ └── version.yml
├── docs
│ └── index.asciidoc
├── etc
│ ├── beat.yml
│ └── fields.yml
├── glide.yaml
├── lsbeat.template.json
├── main.go
├── main_test.go
└── tests
└── system
├── config
│ └── lsbeat.yml.j2
├── lsbeat.py
├── requirements.txt
└── test_base.py
我们刚刚已经生成好了一个原始的 Beat 模板了,但是你还需要获取相关的依赖和设置好 git 仓库。
首先,你需要拉取依赖的相关包信息,我们的这个例子是 lsbeat,我们先做一些的基本的配置,回头再看看详细看看其它的模板和配置文件,只需要执行 make setup 就可以自动获取依赖。
$ make setup
当你创建好了自己的 Beat 之后,记得上传到 github 仓库,并和社区进行分享哦,如下:
要 push lsbeat 到你的 git 仓库,只需要执行如下命令:
$ git remote add origin git@github.com:{username}/lsbeat.git
$ git push -u origin master
恭喜你,现在你已经完成了一个 Beat ,并且发布了第一个版本到了 Github,不过里面还没有什么具体内容,现在让我们进一步看看里面的代码吧。
第四步 – 配置
执行过上面一系列命令之后,项目里将会自动创建名为 lsbeat.yml 和 lsbeat.template.json 的配置文件。所有的基本配置项都已经生成在了里面。
lsbeat.yml
lsbeat:
# Defines how often an event is sent to the output
period: 1s
Period 参数包含在每一个生成的 Beats 里面,它表示 lsbeat 将会每隔 1 秒钟轮询一次,这里我们修改 period 时间间隔为 10 秒。还可以在修改 etc/ 目录下面的 beat.yml 文件,这里新增一个 path 参数表示我们具体要监听哪个目录。
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "."
参数添加好了之后,我们只需要运行 make update 命令就能让这些修改应用到配置文件lsbeat.yml。
$ make update
$ cat lsbeat.yml
################### Lsbeat Configuration Example #########################
############################# Lsbeat ######################################
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "."
###############################################################################
修改完 yml 文件,记得修改 config/config.go文件,添加一个path 参数。
package config
import "time"
type Config struct {
Period time.Duration `config:"period"`
Path string `config:"path"`
}
var DefaultConfig = Config{
Period: 10 * time.Second,
Path: ".",
}
同时我们修改 period 默认时间间隔为 10 秒,默认监听的是当前目录 (.) 。.
第五步 – 添加代码
每一个 Beat 需要实现 Beater 接口,里面定义了 Run() 和 Stop() 函数。.
我们可以定义一个名为 Lsbeat 的结构体,然后用这个对象实现 Beater 接口。然后添加字段 lastIndexTime 来保存最后运行的时间戳信息。
type Lsbeat struct {
done chan struct{}
config config.Config
client publisher.Client
lastIndexTime time.Time
...
}
另外,每个 Beat 还需要实现 New() 方法来接收 Beat 配置信息和返回 Lsbeat 的具体实例。
func New(b *beat.Beat, cfg *common.Config) (beat.Beater, error) {
config := config.DefaultConfig
if err := cfg.Unpack(&config); err != nil {
return nil, fmt.Errorf("Error reading config file: %v", err)
}
ls := &Lsbeat{
done: make(chan struct{}),
config: config,
}
return ls, nil
}
在我们的 lsbeat 例子中,我们要做的就是扩展默认的 Run() 函数来导出指定目录的文件和子目录信息。
在修改 Run() 函数之前,我们先在 lsbeat.go 增加 listDir() 函数,就是我们前面最开始测试的那段代码,用于收集目录和文件信息的简单例子稍微修改一下。另外我们还要生成以下字段信息:
"@timestamp": common.Time(time.Now()),
"type": beatname,
"modtime": common.Time(t),
"filename": f.Name(),
"path": dirFile + "/" + f.Name(),
"directory": f.IsDir(),
"filesize": f.Size(),
第一次运行的时候我们将索引所有的文件和目录信息,然后我们再定期检查是否有新文件被创建或者修改,再索引这些新创建的文件和目录。每次定期检查的时间戳都会保存在 lasIndexTime 变量,完整代码如下:
func (bt *Lsbeat) listDir(dirFile string, beatname string, init bool) {
files, _ := ioutil.ReadDir(dirFile)
for _, f := range files {
t := f.ModTime()
event := common.MapStr{
"@timestamp": common.Time(time.Now()),
"type": beatname,
"modtime": common.Time(t),
"filename": f.Name(),
"path": dirFile + "/" + f.Name(),
"directory": f.IsDir(),
"filesize": f.Size(),
}
if init {
// index all files and directories on init
bt.client.PublishEvent(event)
} else {
// Index only changed files since last run.
if t.After(bt.lastIndexTime) {
bt.client.PublishEvent(event)
}
}
if f.IsDir() {
bt.listDir(dirFile+"/"+f.Name(), beatname, init)
}
}
}
记住在最开始需要导入 “io/ioutil” 包。
import (
"fmt"
"io/ioutil"
"time"
)
现在,让我们看看如何在 Run() 函数里面调用 listDir() 函数,并且保存时间戳到 lasIndexTime 变量。
func (bt *Lsbeat) Run(b *beat.Beat) error {
logp.Info("lsbeat is running! Hit CTRL-C to stop it.")
bt.client = b.Publisher.Connect()
ticker := time.NewTicker(bt.config.Period)
counter := 1
for {
select {
case <-bt.done:
return nil
case <-ticker.C:
}
bt.listDir(bt.config.Path, b.Name, true) // call lsDir
bt.lastIndexTime = time.Now() // mark Timestamp
logp.Info("Event sent")
counter++
}
}
函数 Stop() 用来中断 run 的循环执行,保持默认生成的就行。
func (bt *Lsbeat) Stop() {
bt.client.Close()
close(bt.done)
}
到这里,编码部分基本就完成了。我们接下来添加新字段到 mapping 中,修改文件 etc/fields.yml。.
- key: lsbeat
title: LS Beat
description:
fields:
- name: counter
type: integer
required: true
description: >
PLEASE UPDATE DOCUMENTATION
#new fiels added lsbeat
- name: modtime
type: date
- name: filename
type: text
- name: path
- name: directory
type: boolean
- name: filesize
type: long
重新应用新的配置。
$ make update
字段 file_name 将使用 nGram 分词,我们还需要在文件 lsbeat.template.json 的 “settings” 节点添加一个自定义的 analyzer。
{
"mappings": {
...
},
"order": 0,
"settings": {
"index.refresh_interval": "5s",
"analysis": {
"analyzer": {
"ls_ngram_analyzer": {
"tokenizer": "ls_ngram_tokenizer"
}
},
"tokenizer": {
"ls_ngram_tokenizer": {
"type": "ngram",
"min_gram": "2",
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"template": "lsbeat-*"
}
第六步 – 编译和运行
现在我们可以编译和运行了,只需要执行 make 命令就可以编译出可执行文件 lsbeat (lsbeat.exe on windows) 。
$ make
修改 lsbeat.yml 文件,设置需要监听的目录,如: “/Users/ec2-user/go”,记住是全路径。
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "/Users/ec2-user/go"
同时确保你的 elasticsearch 和 kibana 正常运行。现在运行一下 lsbeat 命令看看会发生什么事情吧。
$ ./lsbeat
打开Kibana,通过调用 _cat 接口我们看看的索引是不是创建了。
可以看到创建了一个名为 lsbeat-2016.06.03 的索引,并且看到已经有了一些文档了。现在对 filename 字段查询一下,由于使用的是 nGram 分词,支持模糊匹配,我们使用 lsbe 关键字搜一下。
大功告成! 恭喜你,你已经完成了第一个属于你自己的 beat。
Elastic Advent 第三篇, 手头上事情实在太多,这两天正在进行权威指南翻译的冲刺阶段,临时填下坑,翻译官网的一篇文章吧(原文:https://www.elastic.co/blog/build-your-own-beat),Advent 规则很自由的,没说不能翻译文章啊,嘿嘿嘿,另外号召大家踊跃报名,大家一起玩才有意思。
活动地址:http://elasticsearch.cn/article/107
言归正传!
Beat 是一个开源的用来构建轻量级数据汇集的平台,可用于将各种类型的数据发送至Elasticsearch 与 Logstash。我们有 Packetbeat 用于监控局域网内服务器之间的网络流量信息,有 Filebeat 收集服务器上的日志信息,还有新推出的 Metricbeat 可以定期获取外部系统的监控指标信息,除此以外,你还可以非常方便的基于 libbeat 框架来构建你属于自己的专属 Beat,目前 beas 社区已经有超过25个 Community Beats 了。
Elastic 还提供一个 Beat generator(Beat 生成器)来帮你快速构建属于你自己的 Beat。通过这篇博客你将会看到如何通过 Beat 生成器来快速创建一个你自己的 Beat。今天我们创建的是一个叫做 lsbeat 的 Beat,lsbeat 非常类似 Unix 系统下的命令行 ls,我们用 lsbeat 来索引目录和文件信息。本篇文章环境基于 Unix 系统,如果你是 Windows 或是其它系统,相关操作可能需要根据实际情况进行调整。
第一步 – 配置 Golang 环境
Beats 是用 Golang写的,显然,要创建和开发一个 beat,Golang 环境必不可少,关于这方面的文章很多,建议查看这篇 Golang 的安装向导: install Golang。当前 Beats 需要的最低版本是 Golang 1.6。另外请确保正确设置了你的 $GOPATH 环境变量。
另外 Golang Glide 被用来进行包的依赖管理,所以也需要确保正确安装,最低版本是 Glide 0.10.0,安装说明点这里。
让我们先来看看 lsbeat 将会用到的一段代码吧,这是一个简单的 golang 程序,通过命令行接收一个目录参数,然后列出该目录下的文件和子目录信息。
后面我们将使用到这段代码和 listDir 函数。
第二步 – 生成项目
要生成一个你自己的 Beat,就要用到 beat-generator 了,首先你必须安装 cookiecutter。安装的详细说明看这里。安装好 cookiecutter 之后,我们要给自己的 Beat 起一个好听的名字,最好是小写的英文字母,我们今天这个例子就叫 lsbeat 吧。
生成项目模板之前,我们需要下载 Beats generator 包文件,就在 beats 仓库。安装好 GoLang 之后,你就可以很方便的使用 go get 命令来下载 Beats generator 包文件了。 当你执行下面的这个命令后,所有的源码文件都会下载到 $GOPATH/src 目录。
在 GOPATH 下创建一个以你自己github账号名称命名的目录,并切换过去,然后执行 cookiecutter 命令并指向 Beat Generator 源码路径。
Cookiecutter 接下来会问你几个问题,比如项目名称,我们输入:lsbeat;你的 github 用户名,输入你自己的 github 账户;还有两个关于beat和beat_path应该会自动识别,默认回车就好;最后的问题,你可以输入你的姓和名。
现在应该已经创建好了一个名为 lsbeat 的目录,并且里面应该会生成一些文件,让我们一起来看一下吧,结构如下:
我们刚刚已经生成好了一个原始的 Beat 模板了,但是你还需要获取相关的依赖和设置好 git 仓库。
首先,你需要拉取依赖的相关包信息,我们的这个例子是 lsbeat,我们先做一些的基本的配置,回头再看看详细看看其它的模板和配置文件,只需要执行 make setup 就可以自动获取依赖。
当你创建好了自己的 Beat 之后,记得上传到 github 仓库,并和社区进行分享哦,如下:
要 push lsbeat 到你的 git 仓库,只需要执行如下命令:
恭喜你,现在你已经完成了一个 Beat ,并且发布了第一个版本到了 Github,不过里面还没有什么具体内容,现在让我们进一步看看里面的代码吧。
第四步 – 配置
执行过上面一系列命令之后,项目里将会自动创建名为 lsbeat.yml 和 lsbeat.template.json 的配置文件。所有的基本配置项都已经生成在了里面。
Period 参数包含在每一个生成的 Beats 里面,它表示 lsbeat 将会每隔 1 秒钟轮询一次,这里我们修改 period 时间间隔为 10 秒。还可以在修改 etc/ 目录下面的 beat.yml 文件,这里新增一个 path 参数表示我们具体要监听哪个目录。
参数添加好了之后,我们只需要运行 make update 命令就能让这些修改应用到配置文件lsbeat.yml。
修改完 yml 文件,记得修改 config/config.go文件,添加一个path 参数。
同时我们修改 period 默认时间间隔为 10 秒,默认监听的是当前目录 (.) 。.
第五步 – 添加代码
每一个 Beat 需要实现 Beater 接口,里面定义了 Run() 和 Stop() 函数。.
我们可以定义一个名为 Lsbeat 的结构体,然后用这个对象实现 Beater 接口。然后添加字段 lastIndexTime 来保存最后运行的时间戳信息。
另外,每个 Beat 还需要实现 New() 方法来接收 Beat 配置信息和返回 Lsbeat 的具体实例。
在我们的 lsbeat 例子中,我们要做的就是扩展默认的 Run() 函数来导出指定目录的文件和子目录信息。
在修改 Run() 函数之前,我们先在 lsbeat.go 增加 listDir() 函数,就是我们前面最开始测试的那段代码,用于收集目录和文件信息的简单例子稍微修改一下。另外我们还要生成以下字段信息:
第一次运行的时候我们将索引所有的文件和目录信息,然后我们再定期检查是否有新文件被创建或者修改,再索引这些新创建的文件和目录。每次定期检查的时间戳都会保存在 lasIndexTime 变量,完整代码如下:
记住在最开始需要导入 “io/ioutil” 包。
现在,让我们看看如何在 Run() 函数里面调用 listDir() 函数,并且保存时间戳到 lasIndexTime 变量。
函数 Stop() 用来中断 run 的循环执行,保持默认生成的就行。
到这里,编码部分基本就完成了。我们接下来添加新字段到 mapping 中,修改文件 etc/fields.yml。.
重新应用新的配置。
$ make update
字段 file_name 将使用 nGram 分词,我们还需要在文件 lsbeat.template.json 的 “settings” 节点添加一个自定义的 analyzer。
第六步 – 编译和运行
现在我们可以编译和运行了,只需要执行 make 命令就可以编译出可执行文件 lsbeat (lsbeat.exe on windows) 。
$ make
修改 lsbeat.yml 文件,设置需要监听的目录,如: “/Users/ec2-user/go”,记住是全路径。
同时确保你的 elasticsearch 和 kibana 正常运行。现在运行一下 lsbeat 命令看看会发生什么事情吧。
$ ./lsbeat
打开Kibana,通过调用 _cat 接口我们看看的索引是不是创建了。
可以看到创建了一个名为 lsbeat-2016.06.03 的索引,并且看到已经有了一些文档了。现在对 filename 字段查询一下,由于使用的是 nGram 分词,支持模糊匹配,我们使用 lsbe 关键字搜一下。
大功告成! 恭喜你,你已经完成了第一个属于你自己的 beat。 收起阅读 »
活动地址:http://elasticsearch.cn/article/107
言归正传!
Beat 是一个开源的用来构建轻量级数据汇集的平台,可用于将各种类型的数据发送至Elasticsearch 与 Logstash。我们有 Packetbeat 用于监控局域网内服务器之间的网络流量信息,有 Filebeat 收集服务器上的日志信息,还有新推出的 Metricbeat 可以定期获取外部系统的监控指标信息,除此以外,你还可以非常方便的基于 libbeat 框架来构建你属于自己的专属 Beat,目前 beas 社区已经有超过25个 Community Beats 了。
Elastic 还提供一个 Beat generator(Beat 生成器)来帮你快速构建属于你自己的 Beat。通过这篇博客你将会看到如何通过 Beat 生成器来快速创建一个你自己的 Beat。今天我们创建的是一个叫做 lsbeat 的 Beat,lsbeat 非常类似 Unix 系统下的命令行 ls,我们用 lsbeat 来索引目录和文件信息。本篇文章环境基于 Unix 系统,如果你是 Windows 或是其它系统,相关操作可能需要根据实际情况进行调整。
第一步 – 配置 Golang 环境
Beats 是用 Golang写的,显然,要创建和开发一个 beat,Golang 环境必不可少,关于这方面的文章很多,建议查看这篇 Golang 的安装向导: install Golang。当前 Beats 需要的最低版本是 Golang 1.6。另外请确保正确设置了你的 $GOPATH 环境变量。
另外 Golang Glide 被用来进行包的依赖管理,所以也需要确保正确安装,最低版本是 Glide 0.10.0,安装说明点这里。
让我们先来看看 lsbeat 将会用到的一段代码吧,这是一个简单的 golang 程序,通过命令行接收一个目录参数,然后列出该目录下的文件和子目录信息。
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
//apply run path "." without argument.
if len(os.Args) == 1 {
listDir(".")
} else {
listDir(os.Args[1])
}
}
func listDir(dirFile string) {
files, _ := ioutil.ReadDir(dirFile)
for _, f := range files {
t := f.ModTime()
fmt.Println(f.Name(), dirFile+"/"+f.Name(), f.IsDir(), t, f.Size())
if f.IsDir() {
listDir(dirFile + "/" + f.Name())
}
}
}
后面我们将使用到这段代码和 listDir 函数。
第二步 – 生成项目
要生成一个你自己的 Beat,就要用到 beat-generator 了,首先你必须安装 cookiecutter。安装的详细说明看这里。安装好 cookiecutter 之后,我们要给自己的 Beat 起一个好听的名字,最好是小写的英文字母,我们今天这个例子就叫 lsbeat 吧。
生成项目模板之前,我们需要下载 Beats generator 包文件,就在 beats 仓库。安装好 GoLang 之后,你就可以很方便的使用 go get 命令来下载 Beats generator 包文件了。 当你执行下面的这个命令后,所有的源码文件都会下载到 $GOPATH/src 目录。
$ go get github.com/elastic/beats
在 GOPATH 下创建一个以你自己github账号名称命名的目录,并切换过去,然后执行 cookiecutter 命令并指向 Beat Generator 源码路径。
$ cd $GOPATH/src/github.com/{user}
$ cookiecutter $GOPATH/src/github.com/elastic/beats/generate/beat
Cookiecutter 接下来会问你几个问题,比如项目名称,我们输入:lsbeat;你的 github 用户名,输入你自己的 github 账户;还有两个关于beat和beat_path应该会自动识别,默认回车就好;最后的问题,你可以输入你的姓和名。
project_name [Examplebeat]: lsbeat
github_name [your-github-name]: {username}
beat [lsbeat]:
beat_path [github.com/{github id}]:
full_name [Firstname Lastname]: {Full Name}
现在应该已经创建好了一个名为 lsbeat 的目录,并且里面应该会生成一些文件,让我们一起来看一下吧,结构如下:
$ cd lsbeat
$ tree
.
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── beater
│ └── lsbeat.go
├── config
│ ├── config.go
│ └── config_test.go
├── dev-tools
│ └── packer
│ ├── Makefile
│ ├── beats
│ │ └── lsbeat.yml
│ └── version.yml
├── docs
│ └── index.asciidoc
├── etc
│ ├── beat.yml
│ └── fields.yml
├── glide.yaml
├── lsbeat.template.json
├── main.go
├── main_test.go
└── tests
└── system
├── config
│ └── lsbeat.yml.j2
├── lsbeat.py
├── requirements.txt
└── test_base.py
我们刚刚已经生成好了一个原始的 Beat 模板了,但是你还需要获取相关的依赖和设置好 git 仓库。
首先,你需要拉取依赖的相关包信息,我们的这个例子是 lsbeat,我们先做一些的基本的配置,回头再看看详细看看其它的模板和配置文件,只需要执行 make setup 就可以自动获取依赖。
$ make setup
当你创建好了自己的 Beat 之后,记得上传到 github 仓库,并和社区进行分享哦,如下:
要 push lsbeat 到你的 git 仓库,只需要执行如下命令:
$ git remote add origin git@github.com:{username}/lsbeat.git
$ git push -u origin master
恭喜你,现在你已经完成了一个 Beat ,并且发布了第一个版本到了 Github,不过里面还没有什么具体内容,现在让我们进一步看看里面的代码吧。
第四步 – 配置
执行过上面一系列命令之后,项目里将会自动创建名为 lsbeat.yml 和 lsbeat.template.json 的配置文件。所有的基本配置项都已经生成在了里面。
lsbeat.yml
lsbeat:
# Defines how often an event is sent to the output
period: 1s
Period 参数包含在每一个生成的 Beats 里面,它表示 lsbeat 将会每隔 1 秒钟轮询一次,这里我们修改 period 时间间隔为 10 秒。还可以在修改 etc/ 目录下面的 beat.yml 文件,这里新增一个 path 参数表示我们具体要监听哪个目录。
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "."
参数添加好了之后,我们只需要运行 make update 命令就能让这些修改应用到配置文件lsbeat.yml。
$ make update
$ cat lsbeat.yml
################### Lsbeat Configuration Example #########################
############################# Lsbeat ######################################
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "."
###############################################################################
修改完 yml 文件,记得修改 config/config.go文件,添加一个path 参数。
package config
import "time"
type Config struct {
Period time.Duration `config:"period"`
Path string `config:"path"`
}
var DefaultConfig = Config{
Period: 10 * time.Second,
Path: ".",
}
同时我们修改 period 默认时间间隔为 10 秒,默认监听的是当前目录 (.) 。.
第五步 – 添加代码
每一个 Beat 需要实现 Beater 接口,里面定义了 Run() 和 Stop() 函数。.
我们可以定义一个名为 Lsbeat 的结构体,然后用这个对象实现 Beater 接口。然后添加字段 lastIndexTime 来保存最后运行的时间戳信息。
type Lsbeat struct {
done chan struct{}
config config.Config
client publisher.Client
lastIndexTime time.Time
...
}
另外,每个 Beat 还需要实现 New() 方法来接收 Beat 配置信息和返回 Lsbeat 的具体实例。
func New(b *beat.Beat, cfg *common.Config) (beat.Beater, error) {
config := config.DefaultConfig
if err := cfg.Unpack(&config); err != nil {
return nil, fmt.Errorf("Error reading config file: %v", err)
}
ls := &Lsbeat{
done: make(chan struct{}),
config: config,
}
return ls, nil
}
在我们的 lsbeat 例子中,我们要做的就是扩展默认的 Run() 函数来导出指定目录的文件和子目录信息。
在修改 Run() 函数之前,我们先在 lsbeat.go 增加 listDir() 函数,就是我们前面最开始测试的那段代码,用于收集目录和文件信息的简单例子稍微修改一下。另外我们还要生成以下字段信息:
"@timestamp": common.Time(time.Now()),
"type": beatname,
"modtime": common.Time(t),
"filename": f.Name(),
"path": dirFile + "/" + f.Name(),
"directory": f.IsDir(),
"filesize": f.Size(),
第一次运行的时候我们将索引所有的文件和目录信息,然后我们再定期检查是否有新文件被创建或者修改,再索引这些新创建的文件和目录。每次定期检查的时间戳都会保存在 lasIndexTime 变量,完整代码如下:
func (bt *Lsbeat) listDir(dirFile string, beatname string, init bool) {
files, _ := ioutil.ReadDir(dirFile)
for _, f := range files {
t := f.ModTime()
event := common.MapStr{
"@timestamp": common.Time(time.Now()),
"type": beatname,
"modtime": common.Time(t),
"filename": f.Name(),
"path": dirFile + "/" + f.Name(),
"directory": f.IsDir(),
"filesize": f.Size(),
}
if init {
// index all files and directories on init
bt.client.PublishEvent(event)
} else {
// Index only changed files since last run.
if t.After(bt.lastIndexTime) {
bt.client.PublishEvent(event)
}
}
if f.IsDir() {
bt.listDir(dirFile+"/"+f.Name(), beatname, init)
}
}
}
记住在最开始需要导入 “io/ioutil” 包。
import (
"fmt"
"io/ioutil"
"time"
)
现在,让我们看看如何在 Run() 函数里面调用 listDir() 函数,并且保存时间戳到 lasIndexTime 变量。
func (bt *Lsbeat) Run(b *beat.Beat) error {
logp.Info("lsbeat is running! Hit CTRL-C to stop it.")
bt.client = b.Publisher.Connect()
ticker := time.NewTicker(bt.config.Period)
counter := 1
for {
select {
case <-bt.done:
return nil
case <-ticker.C:
}
bt.listDir(bt.config.Path, b.Name, true) // call lsDir
bt.lastIndexTime = time.Now() // mark Timestamp
logp.Info("Event sent")
counter++
}
}
函数 Stop() 用来中断 run 的循环执行,保持默认生成的就行。
func (bt *Lsbeat) Stop() {
bt.client.Close()
close(bt.done)
}
到这里,编码部分基本就完成了。我们接下来添加新字段到 mapping 中,修改文件 etc/fields.yml。.
- key: lsbeat
title: LS Beat
description:
fields:
- name: counter
type: integer
required: true
description: >
PLEASE UPDATE DOCUMENTATION
#new fiels added lsbeat
- name: modtime
type: date
- name: filename
type: text
- name: path
- name: directory
type: boolean
- name: filesize
type: long
重新应用新的配置。
$ make update
字段 file_name 将使用 nGram 分词,我们还需要在文件 lsbeat.template.json 的 “settings” 节点添加一个自定义的 analyzer。
{
"mappings": {
...
},
"order": 0,
"settings": {
"index.refresh_interval": "5s",
"analysis": {
"analyzer": {
"ls_ngram_analyzer": {
"tokenizer": "ls_ngram_tokenizer"
}
},
"tokenizer": {
"ls_ngram_tokenizer": {
"type": "ngram",
"min_gram": "2",
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"template": "lsbeat-*"
}
第六步 – 编译和运行
现在我们可以编译和运行了,只需要执行 make 命令就可以编译出可执行文件 lsbeat (lsbeat.exe on windows) 。
$ make
修改 lsbeat.yml 文件,设置需要监听的目录,如: “/Users/ec2-user/go”,记住是全路径。
lsbeat:
# Defines how often an event is sent to the output
period: 10s
path: "/Users/ec2-user/go"
同时确保你的 elasticsearch 和 kibana 正常运行。现在运行一下 lsbeat 命令看看会发生什么事情吧。
$ ./lsbeat
打开Kibana,通过调用 _cat 接口我们看看的索引是不是创建了。
可以看到创建了一个名为 lsbeat-2016.06.03 的索引,并且看到已经有了一些文档了。现在对 filename 字段查询一下,由于使用的是 nGram 分词,支持模糊匹配,我们使用 lsbe 关键字搜一下。
大功告成! 恭喜你,你已经完成了第一个属于你自己的 beat。 收起阅读 »
开源一组ES工具包,请大家帮忙宣传下,谢谢
https://github.com/DataSays/wES/
wES 是一组开源的Java ElasticSearch客户端和工具; 简洁但是很勥 :)
wES = Java Retrofit2/OkHttp版本的客户端(不依赖Json类库,高度可定制) + 工具包 + spring-boot demo + 常用的ElasticSearch环境Dockerfile
wES 模块
wES 分割成许多模块, 可以按需选择.
wUtil: 一些有用的帮助类和工具类.
wES-client: 一个基于Retrofit2/OkHttp的Java客户端, 她是基于官方的ElasticSearch Rest API规范生成的. 她包含两种访问ElasticSearch的实现: OkHttp3版本的和Retrofit2版本. 而且她只依赖okhttp3/retrofit2,并且可以通过实现org.datasays.wes.core.IConvert接口支持任意一种Java Json类库. 你可以按照你的想法使用她.
wES-toolkit: 一些使用wES-client和ElasticSearch的工具包. 她包含了一套基于Gson的标准IConvert实现及封装代码库.
wES-demo: 一个Spring-boot + Vue.js的web应用, 用于展示一些通用的ElasticSearch使用场景.
wES-docker: 一些构建ElasticSearch开发/生产环境的常用Dockerfiles和shell脚本.
继续阅读 »
wES 是一组开源的Java ElasticSearch客户端和工具; 简洁但是很勥 :)
wES = Java Retrofit2/OkHttp版本的客户端(不依赖Json类库,高度可定制) + 工具包 + spring-boot demo + 常用的ElasticSearch环境Dockerfile
wES 模块
wES 分割成许多模块, 可以按需选择.
wUtil: 一些有用的帮助类和工具类.
wES-client: 一个基于Retrofit2/OkHttp的Java客户端, 她是基于官方的ElasticSearch Rest API规范生成的. 她包含两种访问ElasticSearch的实现: OkHttp3版本的和Retrofit2版本. 而且她只依赖okhttp3/retrofit2,并且可以通过实现org.datasays.wes.core.IConvert接口支持任意一种Java Json类库. 你可以按照你的想法使用她.
wES-toolkit: 一些使用wES-client和ElasticSearch的工具包. 她包含了一套基于Gson的标准IConvert实现及封装代码库.
wES-demo: 一个Spring-boot + Vue.js的web应用, 用于展示一些通用的ElasticSearch使用场景.
wES-docker: 一些构建ElasticSearch开发/生产环境的常用Dockerfiles和shell脚本.
https://github.com/DataSays/wES/
wES 是一组开源的Java ElasticSearch客户端和工具; 简洁但是很勥 :)
wES = Java Retrofit2/OkHttp版本的客户端(不依赖Json类库,高度可定制) + 工具包 + spring-boot demo + 常用的ElasticSearch环境Dockerfile
wES 模块
wES 分割成许多模块, 可以按需选择.
wUtil: 一些有用的帮助类和工具类.
wES-client: 一个基于Retrofit2/OkHttp的Java客户端, 她是基于官方的ElasticSearch Rest API规范生成的. 她包含两种访问ElasticSearch的实现: OkHttp3版本的和Retrofit2版本. 而且她只依赖okhttp3/retrofit2,并且可以通过实现org.datasays.wes.core.IConvert接口支持任意一种Java Json类库. 你可以按照你的想法使用她.
wES-toolkit: 一些使用wES-client和ElasticSearch的工具包. 她包含了一套基于Gson的标准IConvert实现及封装代码库.
wES-demo: 一个Spring-boot + Vue.js的web应用, 用于展示一些通用的ElasticSearch使用场景.
wES-docker: 一些构建ElasticSearch开发/生产环境的常用Dockerfiles和shell脚本.
收起阅读 »
wES 是一组开源的Java ElasticSearch客户端和工具; 简洁但是很勥 :)
wES = Java Retrofit2/OkHttp版本的客户端(不依赖Json类库,高度可定制) + 工具包 + spring-boot demo + 常用的ElasticSearch环境Dockerfile
wES 模块
wES 分割成许多模块, 可以按需选择.
wUtil: 一些有用的帮助类和工具类.
wES-client: 一个基于Retrofit2/OkHttp的Java客户端, 她是基于官方的ElasticSearch Rest API规范生成的. 她包含两种访问ElasticSearch的实现: OkHttp3版本的和Retrofit2版本. 而且她只依赖okhttp3/retrofit2,并且可以通过实现org.datasays.wes.core.IConvert接口支持任意一种Java Json类库. 你可以按照你的想法使用她.
wES-toolkit: 一些使用wES-client和ElasticSearch的工具包. 她包含了一套基于Gson的标准IConvert实现及封装代码库.
wES-demo: 一个Spring-boot + Vue.js的web应用, 用于展示一些通用的ElasticSearch使用场景.
wES-docker: 一些构建ElasticSearch开发/生产环境的常用Dockerfiles和shell脚本.
收起阅读 »