有个人长的像洋葱,走着走着就哭了…….

Elasticsearch 5.x 字段折叠的使用

199aon3omgg1vjpg.jpg

 在 Elasticsearch 5.x 有一个字段折叠(Field Collapsing,#22337)的功能非常有意思,在这里分享一下,
 
字段折叠是一个很有历史的需求了,可以看这个 issue,编号#256,最初是2010年7月提的issue,也是讨论最多的帖子之一(240+评论),熬了6年才支持的特性,你说牛不牛,哈哈。
 
目测该特性将于5.3发布,尝鲜地址:Elasticsearch-5.3.0-SNAPSHOT,文档地址:search-request-collapse
 
So,什么是字段折叠,可以理解就是按特定字段进行合并去重,比如我们有一个菜谱搜索,我希望按菜谱的“菜系”字段进行折叠,即返回结果每个菜系都返回一个结果,也就是按菜系去重,我搜索关键字“鱼”,要去返回的结果里面各种菜系都有,有湘菜,有粤菜,有中餐,有西餐,别全是湘菜,就是这个意思,通过按特定字段折叠之后,来丰富搜索结果的多样性。
 
说到这里,有人肯定会想到,使用 term agg+ top hits agg 来实现啊,这种组合两种聚和的方式可以实现上面的功能,不过也有一些局限性,比如,不能分页,#4915;结果不够精确(top term+top hits,es 的聚合实现选择了牺牲精度来提高速度);数据量大的情况下,聚合比较慢,影响搜索体验。
 
而新的的字段折叠的方式是怎么实现的的呢,有这些要点:
  1. 折叠+取 inner_hits 分两阶段执行(组合聚合的方式只有一个阶段),所以 top hits 永远是精确的。
  2. 字段折叠只在 top hits 层执行,不需要每次都在完整的结果集上对为每个折叠主键计算实际的 doc values 值,只对 top hits 这小部分数据操作就可以,和 term agg 相比要节省很多内存。
  3. 因为只在 top hits 上进行折叠,所以相比组合聚合的方式,速度要快很多。
  4. 折叠 top docs 不需要使用全局序列(global ordinals)来转换 string,相比 agg 这也节省了很多内存。
  5. 分页成为可能,和常规搜索一样,具有相同的局限,先获取 from+size 的内容,再合并。
  6. search_after 和 scroll 暂未实现,不过具备可行性。
  7.  折叠只影响搜索结果,不影响聚合,搜索结果的 total 是所有的命中纪录数,去重的结果数未知(无法计算)。

 
下面来看看具体的例子,就知道怎么回事了,使用起来很简单。
  • 先准备索引和数据,这里以菜谱为例,name:菜谱名,type 为菜系,rating 为用户的累积平均评分

DELETE recipes
PUT recipes
POST recipes/type/_mapping
{
"properties": {
"name":{
"type": "text"
},
"rating":{
"type": "float"
},"type":{
"type": "keyword"
}
}
}
POST recipes/type/
{
"name":"清蒸鱼头","rating":1,"type":"湘菜"
}

POST recipes/type/
{
"name":"剁椒鱼头","rating":2,"type":"湘菜"
}

POST recipes/type/
{
"name":"红烧鲫鱼","rating":3,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(辣)","rating":3,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(微辣)","rating":4,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(变态辣)","rating":5,"type":"湘菜"
}

POST recipes/type/
{
"name":"广式鲫鱼汤","rating":5,"type":"粤菜"
}

POST recipes/type/
{
"name":"鱼香肉丝","rating":2,"type":"川菜"
}

POST recipes/type/
{
"name":"奶油鲍鱼汤","rating":2,"type":"西菜"

  • 现在我们看看普通的查询效果是怎么样的,搜索关键字带“鱼”的菜,返回3条数据

POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"size": 3
全是湘菜,我的天,最近上火不想吃辣,这个第一页的结果对我来说就是垃圾,如下:
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 0.26742277,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": 0.26742277,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHXO_OA-dG63Txsa",
        "_score": 0.19100356,
        "_source": {
          "name": "红烧鲫鱼",
          "rating": 3,
          "type": "湘菜"
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHWy_OA-dG63TxsZ",
        "_score": 0.19100356,
        "_source": {
          "name": "剁椒鱼头",
          "rating": 2,
          "type": "湘菜"
        }
      }
    ]
  }
}
我们再看看,这次我想加个评分排序,大家都喜欢的是那些,看看有没有喜欢吃的,执行查询:
POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"sort": [
{
"rating": {
"order": "desc"
}
}
],"size": 3
结果稍微好点了,不过3个里面2个是湘菜,还是有点不合适,结果如下:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": null,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        },
        "sort": [
          5
        ]
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYW_OA-dG63Txse",
        "_score": null,
        "_source": {
          "name": "广式鲫鱼汤",
          "rating": 5,
          "type": "粤菜"
        },
        "sort": [
          5
        ]
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHX7_OA-dG63Txsc",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(微辣)",
          "rating": 4,
          "type": "湘菜"
        },
        "sort": [
          4
        ]
      }
    ]
  }
}
现在我知道了,我要看看其他菜系,这家不是还有西餐、广东菜等各种菜系的么,来来,帮我每个菜系来一个菜看看,换 terms agg 先得到唯一的 term 的 bucket,再组合 top_hits agg,返回按评分排序的第一个 top hits,有点复杂,没关系,看下面的查询就知道了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],"aggs": {
"type": {
"terms": {
"field": "type",
"size": 10
},"aggs": {
"rated": {
"top_hits": {
"sort": [{
"rating": {"order": "desc"}
}],
"size": 1
}
}
}
}
},
"size": 0,
"from": 0
看下面的结果,虽然 json 结构有点复杂,不过总算是我们想要的结果了,湘菜、粤菜、川菜、西菜都出来了,每样一个,不重样:
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "type": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "湘菜",
          "doc_count": 6,
          "rated": {
            "hits": {
              "total": 6,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYF_OA-dG63Txsd",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(变态辣)",
                    "rating": 5,
                    "type": "湘菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "川菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYr_OA-dG63Txsf",
                  "_score": null,
                  "_source": {
                    "name": "鱼香肉丝",
                    "rating": 2,
                    "type": "川菜"
                  },
                  "sort": [
                    2
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "粤菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYW_OA-dG63Txse",
                  "_score": null,
                  "_source": {
                    "name": "广式鲫鱼汤",
                    "rating": 5,
                    "type": "粤菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "西菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHY3_OA-dG63Txsg",
                  "_score": null,
                  "_source": {
                    "name": "奶油鲍鱼汤",
                    "rating": 2,
                    "type": "西菜"
                  },
                  "sort": [
                    2
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }
}
上面的实现方法,前面已经说了,可以做,有局限性,那看看新的字段折叠法如何做到呢,查询如下,加一个 collapse 参数,指定对那个字段去重就行了,这里当然对菜系“type”字段进行去重了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type"
},
"size": 3,
"from": 0
}
结果很理想嘛,命中结果还是熟悉的那个味道(和查询结果长的一样嘛),如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRJ_OA-dG63TxpW",
"_score": 0.018980097,
"_source": {
"name": "鲫鱼汤(微辣)",
"rating": 4,
"type": "湘菜"
},
"fields": {
"type": [
"湘菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRk_OA-dG63TxpZ",
"_score": 0.013813315,
"_source": {
"name": "鱼香肉丝",
"rating": 2,
"type": "川菜"
},
"fields": {
"type": [
"川菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRb_OA-dG63TxpY",
"_score": 0.0125863515,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"fields": {
"type": [
"粤菜"
]
}
}
]
}
}
我再试试翻页,把 from 改一下,现在返回了3条数据,from 改成3,新的查询如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRw_OA-dG63Txpa",
"_score": 0.012546891,
"_source": {
"name": "奶油鲍鱼汤",
"rating": 2,
"type": "西菜"
},
"fields": {
"type": [
"西菜"
]
}
}
]
}
}
上面的结果只有一条了,去重之后本来就只有4条数据,上面的工作正常,每个菜系只有一个菜啊,那我不乐意了,帮我每个菜系里面多返回几条,我好选菜啊,加上参数 inner_hits 来控制返回的条数,这里返回2条,按 rating 也排个序,新的查询构造如下:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type",
"inner_hits": {
"name": "top_rated",
"size": 2,
"sort": [
{
"rating": "desc"
}
]
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],
"size": 2,
"from": 0
}
查询结果如下,完美:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": null,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        },
        "fields": {
          "type": [
            "湘菜"
          ]
        },
        "sort": [
          5
        ],
        "inner_hits": {
          "top_rated": {
            "hits": {
              "total": 6,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYF_OA-dG63Txsd",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(变态辣)",
                    "rating": 5,
                    "type": "湘菜"
                  },
                  "sort": [
                    5
                  ]
                },
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHX7_OA-dG63Txsc",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(微辣)",
                    "rating": 4,
                    "type": "湘菜"
                  },
                  "sort": [
                    4
                  ]
                }
              ]
            }
          }
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYW_OA-dG63Txse",
        "_score": null,
        "_source": {
          "name": "广式鲫鱼汤",
          "rating": 5,
          "type": "粤菜"
        },
        "fields": {
          "type": [
            "粤菜"
          ]
        },
        "sort": [
          5
        ],
        "inner_hits": {
          "top_rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYW_OA-dG63Txse",
                  "_score": null,
                  "_source": {
                    "name": "广式鲫鱼汤",
                    "rating": 5,
                    "type": "粤菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        }
      }
    ]
  }
}
好了,字段折叠介绍就到这里。
继续阅读 »
199aon3omgg1vjpg.jpg

 在 Elasticsearch 5.x 有一个字段折叠(Field Collapsing,#22337)的功能非常有意思,在这里分享一下,
 
字段折叠是一个很有历史的需求了,可以看这个 issue,编号#256,最初是2010年7月提的issue,也是讨论最多的帖子之一(240+评论),熬了6年才支持的特性,你说牛不牛,哈哈。
 
目测该特性将于5.3发布,尝鲜地址:Elasticsearch-5.3.0-SNAPSHOT,文档地址:search-request-collapse
 
So,什么是字段折叠,可以理解就是按特定字段进行合并去重,比如我们有一个菜谱搜索,我希望按菜谱的“菜系”字段进行折叠,即返回结果每个菜系都返回一个结果,也就是按菜系去重,我搜索关键字“鱼”,要去返回的结果里面各种菜系都有,有湘菜,有粤菜,有中餐,有西餐,别全是湘菜,就是这个意思,通过按特定字段折叠之后,来丰富搜索结果的多样性。
 
说到这里,有人肯定会想到,使用 term agg+ top hits agg 来实现啊,这种组合两种聚和的方式可以实现上面的功能,不过也有一些局限性,比如,不能分页,#4915;结果不够精确(top term+top hits,es 的聚合实现选择了牺牲精度来提高速度);数据量大的情况下,聚合比较慢,影响搜索体验。
 
而新的的字段折叠的方式是怎么实现的的呢,有这些要点:
  1. 折叠+取 inner_hits 分两阶段执行(组合聚合的方式只有一个阶段),所以 top hits 永远是精确的。
  2. 字段折叠只在 top hits 层执行,不需要每次都在完整的结果集上对为每个折叠主键计算实际的 doc values 值,只对 top hits 这小部分数据操作就可以,和 term agg 相比要节省很多内存。
  3. 因为只在 top hits 上进行折叠,所以相比组合聚合的方式,速度要快很多。
  4. 折叠 top docs 不需要使用全局序列(global ordinals)来转换 string,相比 agg 这也节省了很多内存。
  5. 分页成为可能,和常规搜索一样,具有相同的局限,先获取 from+size 的内容,再合并。
  6. search_after 和 scroll 暂未实现,不过具备可行性。
  7.  折叠只影响搜索结果,不影响聚合,搜索结果的 total 是所有的命中纪录数,去重的结果数未知(无法计算)。

 
下面来看看具体的例子,就知道怎么回事了,使用起来很简单。
  • 先准备索引和数据,这里以菜谱为例,name:菜谱名,type 为菜系,rating 为用户的累积平均评分

DELETE recipes
PUT recipes
POST recipes/type/_mapping
{
"properties": {
"name":{
"type": "text"
},
"rating":{
"type": "float"
},"type":{
"type": "keyword"
}
}
}
POST recipes/type/
{
"name":"清蒸鱼头","rating":1,"type":"湘菜"
}

POST recipes/type/
{
"name":"剁椒鱼头","rating":2,"type":"湘菜"
}

POST recipes/type/
{
"name":"红烧鲫鱼","rating":3,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(辣)","rating":3,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(微辣)","rating":4,"type":"湘菜"
}

POST recipes/type/
{
"name":"鲫鱼汤(变态辣)","rating":5,"type":"湘菜"
}

POST recipes/type/
{
"name":"广式鲫鱼汤","rating":5,"type":"粤菜"
}

POST recipes/type/
{
"name":"鱼香肉丝","rating":2,"type":"川菜"
}

POST recipes/type/
{
"name":"奶油鲍鱼汤","rating":2,"type":"西菜"

  • 现在我们看看普通的查询效果是怎么样的,搜索关键字带“鱼”的菜,返回3条数据

POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"size": 3
全是湘菜,我的天,最近上火不想吃辣,这个第一页的结果对我来说就是垃圾,如下:
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 0.26742277,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": 0.26742277,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHXO_OA-dG63Txsa",
        "_score": 0.19100356,
        "_source": {
          "name": "红烧鲫鱼",
          "rating": 3,
          "type": "湘菜"
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHWy_OA-dG63TxsZ",
        "_score": 0.19100356,
        "_source": {
          "name": "剁椒鱼头",
          "rating": 2,
          "type": "湘菜"
        }
      }
    ]
  }
}
我们再看看,这次我想加个评分排序,大家都喜欢的是那些,看看有没有喜欢吃的,执行查询:
POST recipes/type/_search
{
"query": {"match": {
"name": "鱼"
}},"sort": [
{
"rating": {
"order": "desc"
}
}
],"size": 3
结果稍微好点了,不过3个里面2个是湘菜,还是有点不合适,结果如下:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": null,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        },
        "sort": [
          5
        ]
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYW_OA-dG63Txse",
        "_score": null,
        "_source": {
          "name": "广式鲫鱼汤",
          "rating": 5,
          "type": "粤菜"
        },
        "sort": [
          5
        ]
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHX7_OA-dG63Txsc",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(微辣)",
          "rating": 4,
          "type": "湘菜"
        },
        "sort": [
          4
        ]
      }
    ]
  }
}
现在我知道了,我要看看其他菜系,这家不是还有西餐、广东菜等各种菜系的么,来来,帮我每个菜系来一个菜看看,换 terms agg 先得到唯一的 term 的 bucket,再组合 top_hits agg,返回按评分排序的第一个 top hits,有点复杂,没关系,看下面的查询就知道了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],"aggs": {
"type": {
"terms": {
"field": "type",
"size": 10
},"aggs": {
"rated": {
"top_hits": {
"sort": [{
"rating": {"order": "desc"}
}],
"size": 1
}
}
}
}
},
"size": 0,
"from": 0
看下面的结果,虽然 json 结构有点复杂,不过总算是我们想要的结果了,湘菜、粤菜、川菜、西菜都出来了,每样一个,不重样:
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "type": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "湘菜",
          "doc_count": 6,
          "rated": {
            "hits": {
              "total": 6,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYF_OA-dG63Txsd",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(变态辣)",
                    "rating": 5,
                    "type": "湘菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "川菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYr_OA-dG63Txsf",
                  "_score": null,
                  "_source": {
                    "name": "鱼香肉丝",
                    "rating": 2,
                    "type": "川菜"
                  },
                  "sort": [
                    2
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "粤菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYW_OA-dG63Txse",
                  "_score": null,
                  "_source": {
                    "name": "广式鲫鱼汤",
                    "rating": 5,
                    "type": "粤菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        },
        {
          "key": "西菜",
          "doc_count": 1,
          "rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHY3_OA-dG63Txsg",
                  "_score": null,
                  "_source": {
                    "name": "奶油鲍鱼汤",
                    "rating": 2,
                    "type": "西菜"
                  },
                  "sort": [
                    2
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }
}
上面的实现方法,前面已经说了,可以做,有局限性,那看看新的字段折叠法如何做到呢,查询如下,加一个 collapse 参数,指定对那个字段去重就行了,这里当然对菜系“type”字段进行去重了:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type"
},
"size": 3,
"from": 0
}
结果很理想嘛,命中结果还是熟悉的那个味道(和查询结果长的一样嘛),如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRJ_OA-dG63TxpW",
"_score": 0.018980097,
"_source": {
"name": "鲫鱼汤(微辣)",
"rating": 4,
"type": "湘菜"
},
"fields": {
"type": [
"湘菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRk_OA-dG63TxpZ",
"_score": 0.013813315,
"_source": {
"name": "鱼香肉丝",
"rating": 2,
"type": "川菜"
},
"fields": {
"type": [
"川菜"
]
}
},
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRb_OA-dG63TxpY",
"_score": 0.0125863515,
"_source": {
"name": "广式鲫鱼汤",
"rating": 5,
"type": "粤菜"
},
"fields": {
"type": [
"粤菜"
]
}
}
]
}
}
我再试试翻页,把 from 改一下,现在返回了3条数据,from 改成3,新的查询如下:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 9,
"max_score": null,
"hits": [
{
"_index": "recipes",
"_type": "type",
"_id": "AVoDNlRw_OA-dG63Txpa",
"_score": 0.012546891,
"_source": {
"name": "奶油鲍鱼汤",
"rating": 2,
"type": "西菜"
},
"fields": {
"type": [
"西菜"
]
}
}
]
}
}
上面的结果只有一条了,去重之后本来就只有4条数据,上面的工作正常,每个菜系只有一个菜啊,那我不乐意了,帮我每个菜系里面多返回几条,我好选菜啊,加上参数 inner_hits 来控制返回的条数,这里返回2条,按 rating 也排个序,新的查询构造如下:
GET recipes/type/_search
{
"query": {
"match": {
"name": "鱼"
}
},
"collapse": {
"field": "type",
"inner_hits": {
"name": "top_rated",
"size": 2,
"sort": [
{
"rating": "desc"
}
]
}
},
"sort": [
{
"rating": {
"order": "desc"
}
}
],
"size": 2,
"from": 0
}
查询结果如下,完美:
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": null,
    "hits": [
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYF_OA-dG63Txsd",
        "_score": null,
        "_source": {
          "name": "鲫鱼汤(变态辣)",
          "rating": 5,
          "type": "湘菜"
        },
        "fields": {
          "type": [
            "湘菜"
          ]
        },
        "sort": [
          5
        ],
        "inner_hits": {
          "top_rated": {
            "hits": {
              "total": 6,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYF_OA-dG63Txsd",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(变态辣)",
                    "rating": 5,
                    "type": "湘菜"
                  },
                  "sort": [
                    5
                  ]
                },
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHX7_OA-dG63Txsc",
                  "_score": null,
                  "_source": {
                    "name": "鲫鱼汤(微辣)",
                    "rating": 4,
                    "type": "湘菜"
                  },
                  "sort": [
                    4
                  ]
                }
              ]
            }
          }
        }
      },
      {
        "_index": "recipes",
        "_type": "type",
        "_id": "AVoESHYW_OA-dG63Txse",
        "_score": null,
        "_source": {
          "name": "广式鲫鱼汤",
          "rating": 5,
          "type": "粤菜"
        },
        "fields": {
          "type": [
            "粤菜"
          ]
        },
        "sort": [
          5
        ],
        "inner_hits": {
          "top_rated": {
            "hits": {
              "total": 1,
              "max_score": null,
              "hits": [
                {
                  "_index": "recipes",
                  "_type": "type",
                  "_id": "AVoESHYW_OA-dG63Txse",
                  "_score": null,
                  "_source": {
                    "name": "广式鲫鱼汤",
                    "rating": 5,
                    "type": "粤菜"
                  },
                  "sort": [
                    5
                  ]
                }
              ]
            }
          }
        }
      }
    ]
  }
}
好了,字段折叠介绍就到这里。 收起阅读 »

Elastic Stack 5.2.0 发布

本周三,Elastic 发布了 ElasticStack 的全新版本5.2.0,包含了很多激动人心的特性,让我们 一起来看看吧,
(Elastic Stack 包括 Elasticsearch、Logstash、Kibana、Beats):
 Elasticsearch 5.2.0 主要亮点:
  1. 新增数字和日期范围字段类型,非常方便对范围类型进行交并集的查询,比如你的数据是日历类型的数据,每天都有一些会议信息,会议的开始和结束时间都不同,你想看本周那天有空,可以使用 range fields 很方便的进行查询。了解更多
  2. 新增分片分配解释 API,会告知分片失败的具体原因,如分片损坏、磁盘写满还是配置错误,进行快速运维,以前只能根据经验到处寻找可能是什么问题,费事费力,了解更多
  3. 对 Keyword 类型标准化,在5.0将 string 类型拆成了 text 和 keyword两种类型,text 支持分词,keyword 则不分词默认支持 doc values,不过有时候你还需要对 keyword 类型进行一些标准化处理,如都转成小写,现在可以使用 normalizers 参数来指定标准化的 filter。
  4. Terms 聚合的分区,terms 聚合默认返回10个 term,以前如果你需要返回全部的 term 列表是不可能的任务(基于内存压力),现在你可以通过将请求分区,以多次请求的方式来取回这些数据,了解更多
  5. 字段折叠,在搜索时可以按某个字段的值进行折叠,在每个折叠的值内进行排序选取 topN,了解更多

 
相关链接:
Download Elasticsearch 5.2.0
Elasticsearch 5.2.0 release notes
Elasticsearch 5.2.0 breaking changes
X-Pack 5.2.0 release notes
 
Logstash 5.2.0 主要亮点:
  1. 新的监控 UI,现在 X-Pack 也能监控 logstash 了,X-Pack 基础免费版就包括,如下图:
    MonitoringUI-rel-blog.png
    Screen_Shot_2017-01-09_at_16.51_.49_.png
  2. 更多的监控 API,新增3个类型的统计信息:cgroup、持久化队列和 output新增持续时间字段。
  3. 离线插件管理,在很多没有公网的部署环境,都需要离线安装,以前的离线安装不够完善,尽管大部分插件没问题,但是还是存在个别插件的依赖链下载不完整的问题,为了解决这个问题,我们基本上重新设计了整个工作流程使用了新的方式来打包插件和他所有的依赖,了解更多

 
相关连接:
Download Logstash 5.2.0
Logstash 5.2.0 release notes
 
Kibana 5.2.0 主要亮点:
  1. 支持 Elasticsearch Tribe 节点,在“admin”集群的基础上,引入了新的“data”集群,“data”集群可以理解成 Kibana 后面的数据来源,可以是 tribe 节点,而“admin”集群是存放 kibana 可视化信息“。kibana”索引的地方,默认 data 和 admin 集群都是在同一个集群,且不能是 tribe 节点,目前还有一些细节正在处理,如 console 还不能很好的工作。
  2. 增加新的可视化类型:热点图(heatmap),可以很方便的按区间和按时间来显示数据的范围分布,如下图:
    Heatmap_Linechart.png
  3. 开始进行国际化的支持,感谢 IBM 团队的努力,目前已经提供了基础的框架支持,虽然是万里长征的第一大步,但也是非常激动人心的。
  4. 地图服务的改进,Elastic 自己的 Tile 地图服务已经上线几个月了,我们现在能提供10个级别的缩放了,X-Pack 基础用户可以达到12个级别的缩放,并且我们正在尝试18个级别的缩放,并且从5.2开始,我们能让这些级别动态调整,不用发布新的 Kibana。
  5. 监控容器中的 Elasticsearch,现在我们可以监控容器里面的 Elasticsearch 实例的运行情况了,CPU 利用率、GC、堆栈使用情况等,如下图:
    monitoring-elasticsearch-in-containers.png

 
相关连接:
Download Kibana 5.2.0
Kibana 5.2.0 release notes
 
Beats 5.2.0 主要亮点:
  1. 新增 Beat:Heartbeat,一个新的正式的官方beat 成员,用于可用性监控,和所有的 beat 一样,轻量级,Heartbeat 可以用于很多场景,比如安全,你不希望暴露某个端口时,使用 Heartbeat,当你发现该端口对外开启了,就可以触发通知,或者服务/网站可用性检测,服务down 了可以及时感知,目前支持:ICMP、TCP 和 HTTP 类型的监控,目前 Heartbeat还处于 beat 阶段,暂不推荐用于生产环境。
  2. Metricbeat 可跟踪网络连接,从5.2开始,Metricbeat 导出了 linux 系统的应用程序的网络连接信息,每个进程打开的 tcp 套接字,本地及远程的 ip 都包含在内,基于它,你可以进行如下的图分析:
    connections-2.png
  3. 收集Prometheus导出的指标,从5.2开始,监控系统普罗米修斯的收集模块导出的数据可以提供给 Metricbeat 然后索引进 Elasticsearch。

 
相关连接:
Download Beats 5.2.0
Beats 5.2.0 release notes
 
Elastic Stack 下载链接:https://www.elastic.co/downloads
Bug 反馈:http://github.com/elastic

以后有版本的更新消息都会在这里发布一份中文版,欢迎大家关注。
继续阅读 »
本周三,Elastic 发布了 ElasticStack 的全新版本5.2.0,包含了很多激动人心的特性,让我们 一起来看看吧,
(Elastic Stack 包括 Elasticsearch、Logstash、Kibana、Beats):
 Elasticsearch 5.2.0 主要亮点:
  1. 新增数字和日期范围字段类型,非常方便对范围类型进行交并集的查询,比如你的数据是日历类型的数据,每天都有一些会议信息,会议的开始和结束时间都不同,你想看本周那天有空,可以使用 range fields 很方便的进行查询。了解更多
  2. 新增分片分配解释 API,会告知分片失败的具体原因,如分片损坏、磁盘写满还是配置错误,进行快速运维,以前只能根据经验到处寻找可能是什么问题,费事费力,了解更多
  3. 对 Keyword 类型标准化,在5.0将 string 类型拆成了 text 和 keyword两种类型,text 支持分词,keyword 则不分词默认支持 doc values,不过有时候你还需要对 keyword 类型进行一些标准化处理,如都转成小写,现在可以使用 normalizers 参数来指定标准化的 filter。
  4. Terms 聚合的分区,terms 聚合默认返回10个 term,以前如果你需要返回全部的 term 列表是不可能的任务(基于内存压力),现在你可以通过将请求分区,以多次请求的方式来取回这些数据,了解更多
  5. 字段折叠,在搜索时可以按某个字段的值进行折叠,在每个折叠的值内进行排序选取 topN,了解更多

 
相关链接:
Download Elasticsearch 5.2.0
Elasticsearch 5.2.0 release notes
Elasticsearch 5.2.0 breaking changes
X-Pack 5.2.0 release notes
 
Logstash 5.2.0 主要亮点:
  1. 新的监控 UI,现在 X-Pack 也能监控 logstash 了,X-Pack 基础免费版就包括,如下图:
    MonitoringUI-rel-blog.png
    Screen_Shot_2017-01-09_at_16.51_.49_.png
  2. 更多的监控 API,新增3个类型的统计信息:cgroup、持久化队列和 output新增持续时间字段。
  3. 离线插件管理,在很多没有公网的部署环境,都需要离线安装,以前的离线安装不够完善,尽管大部分插件没问题,但是还是存在个别插件的依赖链下载不完整的问题,为了解决这个问题,我们基本上重新设计了整个工作流程使用了新的方式来打包插件和他所有的依赖,了解更多

 
相关连接:
Download Logstash 5.2.0
Logstash 5.2.0 release notes
 
Kibana 5.2.0 主要亮点:
  1. 支持 Elasticsearch Tribe 节点,在“admin”集群的基础上,引入了新的“data”集群,“data”集群可以理解成 Kibana 后面的数据来源,可以是 tribe 节点,而“admin”集群是存放 kibana 可视化信息“。kibana”索引的地方,默认 data 和 admin 集群都是在同一个集群,且不能是 tribe 节点,目前还有一些细节正在处理,如 console 还不能很好的工作。
  2. 增加新的可视化类型:热点图(heatmap),可以很方便的按区间和按时间来显示数据的范围分布,如下图:
    Heatmap_Linechart.png
  3. 开始进行国际化的支持,感谢 IBM 团队的努力,目前已经提供了基础的框架支持,虽然是万里长征的第一大步,但也是非常激动人心的。
  4. 地图服务的改进,Elastic 自己的 Tile 地图服务已经上线几个月了,我们现在能提供10个级别的缩放了,X-Pack 基础用户可以达到12个级别的缩放,并且我们正在尝试18个级别的缩放,并且从5.2开始,我们能让这些级别动态调整,不用发布新的 Kibana。
  5. 监控容器中的 Elasticsearch,现在我们可以监控容器里面的 Elasticsearch 实例的运行情况了,CPU 利用率、GC、堆栈使用情况等,如下图:
    monitoring-elasticsearch-in-containers.png

 
相关连接:
Download Kibana 5.2.0
Kibana 5.2.0 release notes
 
Beats 5.2.0 主要亮点:
  1. 新增 Beat:Heartbeat,一个新的正式的官方beat 成员,用于可用性监控,和所有的 beat 一样,轻量级,Heartbeat 可以用于很多场景,比如安全,你不希望暴露某个端口时,使用 Heartbeat,当你发现该端口对外开启了,就可以触发通知,或者服务/网站可用性检测,服务down 了可以及时感知,目前支持:ICMP、TCP 和 HTTP 类型的监控,目前 Heartbeat还处于 beat 阶段,暂不推荐用于生产环境。
  2. Metricbeat 可跟踪网络连接,从5.2开始,Metricbeat 导出了 linux 系统的应用程序的网络连接信息,每个进程打开的 tcp 套接字,本地及远程的 ip 都包含在内,基于它,你可以进行如下的图分析:
    connections-2.png
  3. 收集Prometheus导出的指标,从5.2开始,监控系统普罗米修斯的收集模块导出的数据可以提供给 Metricbeat 然后索引进 Elasticsearch。

 
相关连接:
Download Beats 5.2.0
Beats 5.2.0 release notes
 
Elastic Stack 下载链接:https://www.elastic.co/downloads
Bug 反馈:http://github.com/elastic

以后有版本的更新消息都会在这里发布一份中文版,欢迎大家关注。 收起阅读 »

发现的一个不错的elasticsearch 官方文档的翻译文档

这几天一直在学习elasticsearch,很多地方不是太明白,于是去官方网站上查看说明文档,发现真的很不方便查看,一是由于文档布局排版不好,查看不同的API还得必须跳到不同页面上,最重要一点是需要翻墙才能看文档,很是烦恼,今天忽然在一个网站上发现了一个把elasticsearch官方2.3.3的java elasticsearch 文档翻译了,而且我看了一下,还是不错的,于是分享给大家,果断收藏啊:
Elasticsearch 2.3.3 JAVA api说明文档
继续阅读 »
这几天一直在学习elasticsearch,很多地方不是太明白,于是去官方网站上查看说明文档,发现真的很不方便查看,一是由于文档布局排版不好,查看不同的API还得必须跳到不同页面上,最重要一点是需要翻墙才能看文档,很是烦恼,今天忽然在一个网站上发现了一个把elasticsearch官方2.3.3的java elasticsearch 文档翻译了,而且我看了一下,还是不错的,于是分享给大家,果断收藏啊:
Elasticsearch 2.3.3 JAVA api说明文档 收起阅读 »

Elasticsearch 安全加固 101

images.jpeg

 最近 MongoDB 的安全事件闹得沸沸扬扬,应该不少人都听说了吧,事情大概是,因为 MongoDB 默认的安全设置造成了数据外泄并且被黑客勒索才能找回数据,想了解的,这里有几个链接:
http://www.jianshu.com/p/48d17a69e190
http://mt.sohu.com/20170107/n478047698.shtml​ 
http://bbs.tianya.cn/post-itinfo-503786-1.shtml​ 
 
安全从来不是等到出事才要注意的事情,可以说安全是第一重要的事情,不管你是公司的CTO、技术总监、运维总监、架构师还是一线工程师,都应该有安全意识,好了,废话不多说了,Elasticsearch 的用户现在越来越多了,有些已经成为公司的基础服务,所以数据的安全非常重要,今天主要给大家介绍 Elasticsearch 围绕安全方面的的几点使用事项:
 
下载安装
 
     请使用正规渠道下载 Elasticsearch,首选官方网站,下载完成,记得要验证下载文件的 sha1值和官网下载的提供的sha1值进行对比,避免下载过程中被人拦截破坏文件,甚至注入恶意代码。
不要随便安装第三方的插件,插件有可能引入安全漏洞甚至本身自带后门,需谨慎使用。
    链接君:https://www.elastic.co/downloads 
 
 
使用最新的 Elasticsearch
 
    请关注 Elastic 网站,及时更新升级 Elasticsearch 的最新版本,Elasticsearch 每次版本发布都会优化和改进一部分功能,尤其是安全漏洞的补丁,仔细阅读 Elasticsearch 的更新记录,Elasticsearch 的版本遵照 语义化版本 ,所以小版本间应该是能够无缝升级的,建议及时本地测试和线上更新,升级前,记得 snapshot 做好备份。
    链接君:https://www.elastic.co/downloads
 
 
修改默认的 Elasticsearch 集群名称
 
     Elasticsearch 默认的集群名称是 elasticsearch,请在生成环境上一定要修改成其他的名称,并且不同的环境和不同的集群要保证不相同,监控集群节点情况,如果有未知节点加入,一定要及时预警。
    文档君:https://www.elastic.co/guide/e ... .name
 
 不要暴露 Elasticsearch 在公网上
 
      Elasticsearch 默认端口是9200,绑定的是本机127.0.0.1的这个 ip,这个默认参数其实很安全,但是有很多人想要绑定其他的 lan 口或者公网的 ip,可以修改相应参数,记住,修改有风险,如果确实需要将 Elasticsearch 暴露在公网环境,请修改特定的端口绑定IP,不要直接修改参数: network.host,而是要分别修改:http.port 来绑定 HTTP 协议9200 端口的 IP(RESTful 接口调用),修改:transport.tcp.port 对应的 TCP 9300 端口的 IP(集群内通信),如果你不需要 http 端口,你干脆禁用掉,另外还需要在 Elasticsearch 之上加上成熟的安全防护措施(注意是成熟的!),在这里提供几种方案:
  1. 9200的 HTTP 接口之上加上 Nginx 来提供 Http Basic-Auth 的基本的身份认证,辅助 SSL 证书进行传输层的加密,Nginx 进一步限制可接受 Verb 请求类型及可被操作的索引前缀。
  2. 使用 Elastic 的 X-Pack 插件,同样提供了 Http Basic-Auth 和 SSL 传输层的加密,X-Pack 还能提供内外 Elasticsearch 集群节点间的流量加密,避免旁路攻击。

 
     文档君:https://www.elastic.co/guide/e ... tings 
 
禁用批量删除索引
 
Elasticsearch 支持通过_all(全部)和通配符(*)来批量删除索引,在生产环境,这个有点危险,你可以通过设置: action.destructive_requires_name: true 来禁用它。

安全使用动态脚本

     Elasticsearch 的脚本很方便的可以对数据进行操作,不过如果你暂时没有用上,还请禁用它(Elasticsearch 在1.2.x 以后已经默认禁用了),如果你已经在使用动态脚本,比如 Groovy,它不是沙盒机制的脚本引擎,启用 inline 或 store 类型的groovy 有安全风险,请限制脚本的接触方,比如通过模板的方式来限制脚本的调用,只需要执行特定预先定义好的脚本,对调用参数进行过滤和参数值的检测,做好验证,同时各种日志都必须要保留好,方便进行日志分析,异常的调用和请求一定要有发现和预警机制。
      Elasticsearch 默认启用了  Java Security Manager ,但还请正确配置其白名单。
      使用 Groovy 或者JavaScript 等脚本的用户,尽快迁移到 Painless 脚本,Painless 更快更安全。
      文档君:https://www.elastic.co/guide/e ... .html
 
给 Elasticsearch 服务器加固
 
     服务器加固是一个必备流程,不管上面运行的是什么服务;
     首先,请开启防火墙,请设置防火墙规则,只开启必备的端口,完成之后,使用扫描工具扫描服务器,检查端口开发情况;
     如果可能,不要用密码的方法来远程登录服务器,使用公私钥的方式来 ssh 登录服务器,如果只能使用密码,请妥善保管好你的用户名和密码,禁用 root 用户,不用使用弱密码。
     关注 Java 最新的漏洞,使用安全的 JVM 运行时。
     服务器及时更新最新的软件,使用安全的 repo 软件源,绑定软件源的 host和 ip,避免 dns 污染造成的攻击,关注服务器软件漏洞,及时打上补丁。
     收集系统日志和安装相应的入侵检测软件,及时发现服务器是否有异常行为。
 
不要以 root 身份运行 Elasticsearch

    如果你的运维人员打算以 root 身份来运行某个服务,这个运维人员一定是一个不合格的运维人员,记住一定不要以 root 身份来运行 Elasticsearch,另外,要不和其他的服务公用相同的用户,然后还要保证该用户的权限要最小化。
     范例君:
sudo -u es-user ES_JAVA_OPTS="-Xms1024m -Xmx1024m"  /opt/elasticsearch/bin/elasticsearc

 
正确设置 Elasticsearch 的数据目录
 
     请确保 Elasticsearch 的目录分配了合理的读写权限,避免使用共享文件系统,确保只有 elasticsearch 的启动用户才能访问,同理,日志目录也一样需要正确配置,避免泄露敏感信息。
     文档君:https://www.elastic.co/guide/e ... tings
 
定期对 Elasticsearch 进行备份
 
     使用 Elasticsearch 提供的备份还原机制,定期对 Elasticsearch 的数据进行快照备份,以备不时之需。
     文档君:https://www.elastic.co/guide/e ... .html
 
加上监控和预警
 
     Elasticsearch 提供了很好的默认参数,对参数方面还做了正确性检测,bootstrap 启动检查,不准确的参数,直接不允许 Elasticsearch 启动,以至于有很多人抱怨,怎么现在部署到线上默认就需要做这么多设置才能使用呢,是的,以前启动就默认绑定了所有的网卡,集群见自动发现和相连,现在需要手动绑定局域网网卡,默认只是绑定的本机127.0.0.1的 ip,对上线来说麻烦了一点,做了这些检查也就是为了保证数据安全,以前很多的公网都能直接访问的 Elasticsearch 实例,都是因为默认设置就绑定了公网 ip,但是这些还不够,作为用户,你还需要收集各种日志和系统监控信息,及时主动掌握服务健康和资源使用情况,发现异常情况要及时处理,这里提供一些方案:
  1. 使用开源的 Elastic Stack 收集这些日志,可以使用 Filebeat 收集日志,Metricbeat收集系统监控信息,存进 Elasticsearch,一旦发现异常的波动,使用 Watcher 来进行预警,通过邮件或者 webhook 调用短信、微信或者电话。
  2. 使用其他厂商的安全监控产品。
  3. 使用托管的 Elasticsearch 云的产品,如 Elastic Cloud等等。


是的,把安全这个事情考虑进去之后,很多事情都要比没考虑要变得更加复杂和麻烦,千里之堤毁于蚁穴,一个不起眼的忽视就有可能造成全部数据的丢失和泄露,出来混迟早是要还的,安全问题千万不能忽视。
 
以上几点建议举例针对 linux 平台,其他平台思路基本上一样,仅供参考,安全是一个包含很多方方面面的学科,抛砖引玉,希望大家有用。 

最后,Elastic 非常关心我们的产品安全,如果您发现有任何安全方面的问题,还请在这里上报:
https://www.elastic.co/community/security
企业用户需要 X-Pack 及 Elastic 官方技术支持,请访问下面的链接:
https://www.elastic.co/cn/contact
继续阅读 »
images.jpeg

 最近 MongoDB 的安全事件闹得沸沸扬扬,应该不少人都听说了吧,事情大概是,因为 MongoDB 默认的安全设置造成了数据外泄并且被黑客勒索才能找回数据,想了解的,这里有几个链接:
http://www.jianshu.com/p/48d17a69e190
http://mt.sohu.com/20170107/n478047698.shtml​ 
http://bbs.tianya.cn/post-itinfo-503786-1.shtml​ 
 
安全从来不是等到出事才要注意的事情,可以说安全是第一重要的事情,不管你是公司的CTO、技术总监、运维总监、架构师还是一线工程师,都应该有安全意识,好了,废话不多说了,Elasticsearch 的用户现在越来越多了,有些已经成为公司的基础服务,所以数据的安全非常重要,今天主要给大家介绍 Elasticsearch 围绕安全方面的的几点使用事项:
 
下载安装
 
     请使用正规渠道下载 Elasticsearch,首选官方网站,下载完成,记得要验证下载文件的 sha1值和官网下载的提供的sha1值进行对比,避免下载过程中被人拦截破坏文件,甚至注入恶意代码。
不要随便安装第三方的插件,插件有可能引入安全漏洞甚至本身自带后门,需谨慎使用。
    链接君:https://www.elastic.co/downloads 
 
 
使用最新的 Elasticsearch
 
    请关注 Elastic 网站,及时更新升级 Elasticsearch 的最新版本,Elasticsearch 每次版本发布都会优化和改进一部分功能,尤其是安全漏洞的补丁,仔细阅读 Elasticsearch 的更新记录,Elasticsearch 的版本遵照 语义化版本 ,所以小版本间应该是能够无缝升级的,建议及时本地测试和线上更新,升级前,记得 snapshot 做好备份。
    链接君:https://www.elastic.co/downloads
 
 
修改默认的 Elasticsearch 集群名称
 
     Elasticsearch 默认的集群名称是 elasticsearch,请在生成环境上一定要修改成其他的名称,并且不同的环境和不同的集群要保证不相同,监控集群节点情况,如果有未知节点加入,一定要及时预警。
    文档君:https://www.elastic.co/guide/e ... .name
 
 不要暴露 Elasticsearch 在公网上
 
      Elasticsearch 默认端口是9200,绑定的是本机127.0.0.1的这个 ip,这个默认参数其实很安全,但是有很多人想要绑定其他的 lan 口或者公网的 ip,可以修改相应参数,记住,修改有风险,如果确实需要将 Elasticsearch 暴露在公网环境,请修改特定的端口绑定IP,不要直接修改参数: network.host,而是要分别修改:http.port 来绑定 HTTP 协议9200 端口的 IP(RESTful 接口调用),修改:transport.tcp.port 对应的 TCP 9300 端口的 IP(集群内通信),如果你不需要 http 端口,你干脆禁用掉,另外还需要在 Elasticsearch 之上加上成熟的安全防护措施(注意是成熟的!),在这里提供几种方案:
  1. 9200的 HTTP 接口之上加上 Nginx 来提供 Http Basic-Auth 的基本的身份认证,辅助 SSL 证书进行传输层的加密,Nginx 进一步限制可接受 Verb 请求类型及可被操作的索引前缀。
  2. 使用 Elastic 的 X-Pack 插件,同样提供了 Http Basic-Auth 和 SSL 传输层的加密,X-Pack 还能提供内外 Elasticsearch 集群节点间的流量加密,避免旁路攻击。

 
     文档君:https://www.elastic.co/guide/e ... tings 
 
禁用批量删除索引
 
Elasticsearch 支持通过_all(全部)和通配符(*)来批量删除索引,在生产环境,这个有点危险,你可以通过设置: action.destructive_requires_name: true 来禁用它。

安全使用动态脚本

     Elasticsearch 的脚本很方便的可以对数据进行操作,不过如果你暂时没有用上,还请禁用它(Elasticsearch 在1.2.x 以后已经默认禁用了),如果你已经在使用动态脚本,比如 Groovy,它不是沙盒机制的脚本引擎,启用 inline 或 store 类型的groovy 有安全风险,请限制脚本的接触方,比如通过模板的方式来限制脚本的调用,只需要执行特定预先定义好的脚本,对调用参数进行过滤和参数值的检测,做好验证,同时各种日志都必须要保留好,方便进行日志分析,异常的调用和请求一定要有发现和预警机制。
      Elasticsearch 默认启用了  Java Security Manager ,但还请正确配置其白名单。
      使用 Groovy 或者JavaScript 等脚本的用户,尽快迁移到 Painless 脚本,Painless 更快更安全。
      文档君:https://www.elastic.co/guide/e ... .html
 
给 Elasticsearch 服务器加固
 
     服务器加固是一个必备流程,不管上面运行的是什么服务;
     首先,请开启防火墙,请设置防火墙规则,只开启必备的端口,完成之后,使用扫描工具扫描服务器,检查端口开发情况;
     如果可能,不要用密码的方法来远程登录服务器,使用公私钥的方式来 ssh 登录服务器,如果只能使用密码,请妥善保管好你的用户名和密码,禁用 root 用户,不用使用弱密码。
     关注 Java 最新的漏洞,使用安全的 JVM 运行时。
     服务器及时更新最新的软件,使用安全的 repo 软件源,绑定软件源的 host和 ip,避免 dns 污染造成的攻击,关注服务器软件漏洞,及时打上补丁。
     收集系统日志和安装相应的入侵检测软件,及时发现服务器是否有异常行为。
 
不要以 root 身份运行 Elasticsearch

    如果你的运维人员打算以 root 身份来运行某个服务,这个运维人员一定是一个不合格的运维人员,记住一定不要以 root 身份来运行 Elasticsearch,另外,要不和其他的服务公用相同的用户,然后还要保证该用户的权限要最小化。
     范例君:
sudo -u es-user ES_JAVA_OPTS="-Xms1024m -Xmx1024m"  /opt/elasticsearch/bin/elasticsearc

 
正确设置 Elasticsearch 的数据目录
 
     请确保 Elasticsearch 的目录分配了合理的读写权限,避免使用共享文件系统,确保只有 elasticsearch 的启动用户才能访问,同理,日志目录也一样需要正确配置,避免泄露敏感信息。
     文档君:https://www.elastic.co/guide/e ... tings
 
定期对 Elasticsearch 进行备份
 
     使用 Elasticsearch 提供的备份还原机制,定期对 Elasticsearch 的数据进行快照备份,以备不时之需。
     文档君:https://www.elastic.co/guide/e ... .html
 
加上监控和预警
 
     Elasticsearch 提供了很好的默认参数,对参数方面还做了正确性检测,bootstrap 启动检查,不准确的参数,直接不允许 Elasticsearch 启动,以至于有很多人抱怨,怎么现在部署到线上默认就需要做这么多设置才能使用呢,是的,以前启动就默认绑定了所有的网卡,集群见自动发现和相连,现在需要手动绑定局域网网卡,默认只是绑定的本机127.0.0.1的 ip,对上线来说麻烦了一点,做了这些检查也就是为了保证数据安全,以前很多的公网都能直接访问的 Elasticsearch 实例,都是因为默认设置就绑定了公网 ip,但是这些还不够,作为用户,你还需要收集各种日志和系统监控信息,及时主动掌握服务健康和资源使用情况,发现异常情况要及时处理,这里提供一些方案:
  1. 使用开源的 Elastic Stack 收集这些日志,可以使用 Filebeat 收集日志,Metricbeat收集系统监控信息,存进 Elasticsearch,一旦发现异常的波动,使用 Watcher 来进行预警,通过邮件或者 webhook 调用短信、微信或者电话。
  2. 使用其他厂商的安全监控产品。
  3. 使用托管的 Elasticsearch 云的产品,如 Elastic Cloud等等。


是的,把安全这个事情考虑进去之后,很多事情都要比没考虑要变得更加复杂和麻烦,千里之堤毁于蚁穴,一个不起眼的忽视就有可能造成全部数据的丢失和泄露,出来混迟早是要还的,安全问题千万不能忽视。
 
以上几点建议举例针对 linux 平台,其他平台思路基本上一样,仅供参考,安全是一个包含很多方方面面的学科,抛砖引玉,希望大家有用。 

最后,Elastic 非常关心我们的产品安全,如果您发现有任何安全方面的问题,还请在这里上报:
https://www.elastic.co/community/security
企业用户需要 X-Pack 及 Elastic 官方技术支持,请访问下面的链接:
https://www.elastic.co/cn/contact 收起阅读 »

招聘

招聘需求:
 
技术总监1名:
1、 日志分析产品研发经验优先,8年以上工作经验
2、 精通elasticsearch、Redis、MongoDB、Neo4J等NoSQL系统,熟悉elasticsearch集群管理和性能调优,有在实际项目中使用搜索引擎工具、内存数据库、文档数据库、图数据库的经验
3、 熟悉Hadoop、Spark、Spark Streaming、Storm、HBase、ZooKeeper等大数据生态系统组件;熟悉Kafka、ZeroMQ等消息系统组件
4、 精通日志处理工具集ELK,flume,heka等
5、 熟悉Linux环境,熟练使用Python和Shell进行脚本编程
6、 精通JAVA语言,熟悉主流开源框架Struts2、Spring、Hibernate,SpringMVC的运行机制及使用。熟练掌握和使用J2EE开发中常见面向对象设计模式(Singleton,Proxy,Factory等)
7、 有系统运维管理平台项目开发及管理经验优先
8、 提供优厚待遇及期权,薪水50万以上
9、 金融行业背景优先考虑
 
 
技术专家2名:
1、熟悉Hadoop生态圈相关技术,如Hbase,Hive,Zookeeper,flume,kafka,storm
2、了解spark运行机制,研读spark部分内核源码;了解ELK日志分析平台(elasticsearch、logstash、kibana)使用
3、 熟悉Linux系统的基本操作和集群环境的搭建和部署
4、 熟悉JavaEE的开发,掌握JavaEE流行框架的使用,如:Struts2、Spring、Hibernate、SpringMVC等
5、熟悉主流数据库Oracle、DB2、MySQL、Mariadb的使用及性能优化;会使用NoSql数据库(如redis)解决开发过程中遇到的业务问题
6、提供优厚待遇和期权,薪水30万以上
7、金融行业优先考虑
如有意向请联系郝女士   联系电话:13520834022      邮箱:haojinjin16@163.com
招聘单位:洋大数据信息技术(北京)有限公司
继续阅读 »
招聘需求:
 
技术总监1名:
1、 日志分析产品研发经验优先,8年以上工作经验
2、 精通elasticsearch、Redis、MongoDB、Neo4J等NoSQL系统,熟悉elasticsearch集群管理和性能调优,有在实际项目中使用搜索引擎工具、内存数据库、文档数据库、图数据库的经验
3、 熟悉Hadoop、Spark、Spark Streaming、Storm、HBase、ZooKeeper等大数据生态系统组件;熟悉Kafka、ZeroMQ等消息系统组件
4、 精通日志处理工具集ELK,flume,heka等
5、 熟悉Linux环境,熟练使用Python和Shell进行脚本编程
6、 精通JAVA语言,熟悉主流开源框架Struts2、Spring、Hibernate,SpringMVC的运行机制及使用。熟练掌握和使用J2EE开发中常见面向对象设计模式(Singleton,Proxy,Factory等)
7、 有系统运维管理平台项目开发及管理经验优先
8、 提供优厚待遇及期权,薪水50万以上
9、 金融行业背景优先考虑
 
 
技术专家2名:
1、熟悉Hadoop生态圈相关技术,如Hbase,Hive,Zookeeper,flume,kafka,storm
2、了解spark运行机制,研读spark部分内核源码;了解ELK日志分析平台(elasticsearch、logstash、kibana)使用
3、 熟悉Linux系统的基本操作和集群环境的搭建和部署
4、 熟悉JavaEE的开发,掌握JavaEE流行框架的使用,如:Struts2、Spring、Hibernate、SpringMVC等
5、熟悉主流数据库Oracle、DB2、MySQL、Mariadb的使用及性能优化;会使用NoSql数据库(如redis)解决开发过程中遇到的业务问题
6、提供优厚待遇和期权,薪水30万以上
7、金融行业优先考虑
如有意向请联系郝女士   联系电话:13520834022      邮箱:haojinjin16@163.com
招聘单位:洋大数据信息技术(北京)有限公司 收起阅读 »

对话 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 

IMG_4857.gif


早在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的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。

elastic-logo-H-full-color.jpg


我现在依然是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

Snip20170111_11.png


研发情况

研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。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起来。不要等其他人的开源代码,有可能你会等到,但是有可能你永远等不到。在你写出来之后,你没准会收获惊喜。


 
1.png

 
继续阅读 »
转载: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 

IMG_4857.gif


早在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的人联系我问要不要合并在成为同一家公司,我们才发现彼此的看法竟然不谋而合。

elastic-logo-H-full-color.jpg


我现在依然是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

Snip20170111_11.png


研发情况

研发出新功能的第一版本通常很快,但是需要不断的测试确保所有运转正常。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起来。不要等其他人的开源代码,有可能你会等到,但是有可能你永远等不到。在你写出来之后,你没准会收获惊喜。


 
1.png

  收起阅读 »

Elasticsearch 2.x mapping tips

elasticsearch 2.x mapping tips

作者:杨振涛  首发于: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
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 权威指南》中文版

es-guide.gif

 在几十位社区同学的共同努力下,《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,冬狼,王琦。
 
继续阅读 »
es-guide.gif

 在几十位社区同学的共同努力下,《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,冬狼,王琦。
  收起阅读 »

2017年学习内容

  • 重新看lucene源码
  • 看es源码
  • 对比lucene和es
  • 基于lucene实现自己的搜索框架

  • 重新看lucene源码
  • 看es源码
  • 对比lucene和es
  • 基于lucene实现自己的搜索框架

Elastic{ON}DevChina2016 资料下载

本月10号在北京的第一次 Elastic 中国开发者大会的资料现在已经可以下载了,没有去现场的或者去现场的还想回顾的都可以去看看,欢迎大家踊跃交流讨论!
 
https://pan.baidu.com/s/1slbCZ9b

 
继续阅读 »
本月10号在北京的第一次 Elastic 中国开发者大会的资料现在已经可以下载了,没有去现场的或者去现场的还想回顾的都可以去看看,欢迎大家踊跃交流讨论!
 
https://pan.baidu.com/s/1slbCZ9b

  收起阅读 »

无外网环境10分钟快速集成 elasticsearch-head

一台连上外网的机器
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提供的镜像(原因你懂的)
$ 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做实际运行环境的索引吞吐量对比。

2.4_.png


5.0_.png

 
在近似的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做实际运行环境的索引吞吐量对比。

2.4_.png


5.0_.png

 
在近似的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. 可自定义的异步分片导入方式,数据导入效率更高。


【一键安装】
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. 可自定义的异步分片导入方式,数据导入效率更高。


【一键安装】
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 参数处理它。 收起阅读 »

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吗?
kibana到5.0后可以直接在kibana.yml上面使用tilemap.url进行配置,
但是之前使用的url貌似都不行,有伙伴能提供个K5能用的url吗?