以下ELK系列文章参考自http://www.tianyiqingci.com/
总目录:
Monitor API
ElasticSearch聚合分析API
Elasticsearch信息检索API
ElasticSearch索引管理API
附录
Monitor API
Cluster health
查看集群健康状态接口。
http://localhost:9200/_cluster/health?pretty
{
"cluster_name" : "my.es.cluster",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 2,
"number_of_data_nodes" : 2,
"active_primary_shards" : 8,
"active_shards" : 16,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
Status:集群的健康状态
- Green:健康。所有分片(shards)与所有副本(replicas)都可用
- Yellow:亚健康。表示所有的分片可用(集群可以正常处理任何请求),但是副本不完全可用
- Red:集群不可用。有一或多个分片不可用。
number_of_nodes:总节点数
number_of_data_nodes:总数据节点数
active_primary_shards:分片数
active_shards:分片/副本总数
relocating_shards:正在迁移中的分片数
initializing_shards:正在初始化的分片数
unassigned_shards:未分配到节点上的分片数
number_of_pending_tasks:挂起的任务数
active_shards_percent_as_number:可用分片百分比
cluster stats
查看集群各种统计指标数据的接口。
http://localhost:9200/_cluster/stats?human&pretty
{
"timestamp" : 1460968416164,
"cluster_name" : "my.es.cluster",
"status" : "green",
"indices" : {
"count" : 3,
"shards" : {
"total" : 16,
"primaries" : 8,
"replication" : 1.0,
"index" : {
"shards" : {
"min" : 2,
"max" : 12,
"avg" : 5.333333333333333
},
"primaries" : {
"min" : 1,
"max" : 6,
"avg" : 2.6666666666666665
},
"replication" : {
"min" : 1.0,
"max" : 1.0,
"avg" : 1.0
}
}
......
Pending tasks
查看集群挂起的任务的接口。
http://localhost:9200/_cluster/pending_tasks?human&pretty
{
"tasks" : [ ]
}
Nodes stats
查看集群中各节点的统计指标数据的接口。
http://localhost:9200/_nodes/stats?pretty
{
"cluster_name" : "my.es.cluster",
"nodes" : {
"m708WABKT4e_I_trKEEV1w" : {
"timestamp" : 1460969249469,
"name" : "node-1",
"transport_address" : "localhost:9300",
"host" : "localhost",
……………………
会将集群下所有node统计指标全列出来
ElasticSearch聚合分析API
ES高级功能API – 聚合(Aggregations),聚合功能为ES注入了统计分析的血统,使用户在面对大数据提取统计指标时变得游刃有余。同样的工作,你在hadoop中可能需要写mapreduce或hive,在mongo中你必须得用大段的mapreduce脚本,而在ES中仅仅调用一个API就能实现了。
关于Aggregations
Aggregations的部分特性类似于SQL语言中的group by,avg,sum等函数。但Aggregations API还提供了更加复杂的统计分析接口。
掌握Aggregations需要理解两个概念:
- 桶(Buckets):符合条件的文档的集合,相当于SQL中的group by。比如,在users表中,按“地区”聚合,一个人将被分到北京桶或上海桶或其他桶里;按“性别”聚合,一个人将被分到男桶或女桶
- 指标(Metrics):基于Buckets的基础上进行统计分析,相当于SQL中的count,avg,sum等。比如,按“地区”聚合,计算每个地区的人数,平均年龄等
对照一条SQL来加深我们的理解:
SELECT COUNT(color) FROM table GROUP BY color
GROUP BY相当于做分桶的工作,COUNT是统计指标。
下面介绍一些常用的Aggregations API。
Metrics
AVG
求均值。
GET /company/employee/_search
{
"aggs" : {
"avg_grade" : { "avg" : { "field" : "grade" } }
}
}
执行结果
{
"aggregations": {
"avg_grade": {"value": 75}
}
}
其他的简单统计API,如valuecount, max,min,sum作用与SQL中类似,就不一一解释了。
Cardinality
cardinality的作用是先执行类似SQL中的distinct操作,然后再统计排重后集合长度。得到的结果是一个近似值,因为考虑到在大量分片中排重的性能损耗Cardinality算法并不会load所有的数据。
{
"aggs" : {
"author_count" : {
"cardinality" : {"field" : "author"}
}
}
}
Stats
返回聚合分析后所有有关stat的指标。具体哪些是stat指标是ES定义的,共有5项。
{
"aggs" : {
"grades_stats" : { "stats" : { "field" : "grade" } }
}
}
执行结果
{
"aggregations": {
"grades_stats": {
"count": 6,
"min": 60,
"max": 98,
"avg": 78.5,
"sum": 471
}
}
}
Extended Stats
返回聚合分析后所有指标,比Stats多三个统计结果:平方和、方差、标准差
{
"aggs" : {
"grades_stats" : { "extended_stats" : { "field" : "grade" } }
}
}
执行结果
{
"aggregations": {
"grade_stats": {
"count": 9,
"min": 72,
"max": 99,
"avg": 86,
"sum": 774,
# 平方和
"sum_of_squares": 67028,
# 方差
"variance": 51.55555555555556,
# 标准差
"std_deviation": 7.180219742846005,
#平均加/减两个标准差的区间,用于可视化你的数据方差
"std_deviation_bounds": {
"upper": 100.36043948569201,
"lower": 71.63956051430799
}
}
}
}
Percentiles
百分位法统计,举例,运维人员记录了每次启动系统所需要的时间,或者,网站记录了每次用户访问的页面加载时间,然后对这些时间数据进行百分位法统计。我们在测试报告中经常会看到类似的统计数据
{
"aggs" : {
"load_time_outlier" : {
"percentiles" : {"field" : "load_time"}
}
}
}
结果是
{
"aggregations": {
"load_time_outlier": {
"values" : {
"1.0": 15,
"5.0": 20,
"25.0": 23,
"50.0": 25,
"75.0": 29,
"95.0": 60,
"99.0": 150
}
}
}
}
加载时间在15ms内的占1%,20ms内的占5%,等等。
我们还可以指定百分位的指标,比如只想统计95%、99%、99.9%的加载时间
{
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9]
}
}
}
}
Percentile Ranks
Percentile API中,返回结果values中的key是固定的0-100间的值,而Percentile Ranks返回值中的value才是固定的,同样也是0到100。例如,我想知道加载时间是15ms与30ms的数据,在所有记录中处于什么水平,以这种方式反映数据在集合中的排名情况。
{
"aggs" : {
"load_time_outlier" : {
"percentile_ranks" : {
"field" : "load_time",
"values" : [15, 30]
}
}
}
}
执行结果
{
"aggregations": {
"load_time_outlier": {
"values" : {
"15": 92,
"30": 100
}
}
}
}
Bucket
Filter
先过滤后聚合,类似SQL中的where,也有点象group by后加having。比如
{
"aggs" : {
"red_products" : {
"filter" : { "term": { "color": "red" } },
"aggs" : {
"avg_price" : { "avg" : { "field" : "price" } }
}
}
}
}
只统计红色衣服的均价。
Range
反映数据的分布情况,比如我想知道小于50,50到100,大于100的数据的个数。
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"ranges" : [
{ "to" : 50 },
{ "from" : 50, "to" : 100 },
{ "from" : 100 }
]
}
}
}
}
执行结果
{
"aggregations": {
"price_ranges" : {
"buckets": [
{"to": 50, "doc_count": 2},
{"from": 50, "to": 100, "doc_count": 4},
{"from": 100, "doc_count": 4}
]
}
}
}
Missing
我们想找出price字段值为空的文档的个数。
{
"aggs" : {
"products_without_a_price" : {
"missing" : { "field" : "price" }
}
}
}
执行结果
{
"aggs" : {
"products_without_a_price" : {
"doc_count" : 10
}
}
}
Terms
针对某个字段排重后统计个数。
{
"aggs" : {
"genders" : {
"terms" : { "field" : "gender" }
}
}
}
执行结果
{
"aggregations" : {
"genders" : {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets" : [
{"key" : "male","doc_count" : 10},
{"key" : "female","doc_count" : 10},
]
}
}
}
Date Range
针对日期型数据做分布统计。
{
"aggs": {
"range": {
"date_range": {
"field": "date",
"format": "MM-yyy",
"ranges": [
{ "to": "now-10M/M" },
{ "from": "now-10M/M" }
]
}
}
}
}
这里的format参数是指定返回值的日期格式。
执行结果
{
"aggregations": {
"range": {
"buckets": [
{"to": 1.3437792E+12, "to_as_string": "08-2012","doc_count": 7},
{"from": 1.3437792E+12, "from_as_string": "08-2012","doc_count": 2}
]
}
}
}
Global Aggregation
指定聚合的作用域与查询的作用域没有关联。因此返回结果中query命中的文档,与聚合的的统计结果是没有关系的。
{
"query" : {
"match" : { "title" : "shirt" }
},
"aggs" : {
"all_products" : {
"global" : {},
"aggs" : {
"avg_price" : { "avg" : { "field" : "price" } }
}
}
}
}
Histogram
跟range类似,不过Histogram不需要你指定统计区间,只需要提供一个间隔区间的值。好象不太好理解,看个例子就全明白了。
比如,以50元为一个区间,统计每个区间内的价格分布
{
"aggs" : {
"prices" : {
"histogram" : {
"field" : "price",
"interval" : 50
}
}
}
}
执行结果
{
"aggregations": {
"prices" : {
"buckets": [
{"key": 0, "doc_count": 2},
{"key": 50, "doc_count": 4},
{"key": 100, "doc_count": 0},
{"key": 150, "doc_count": 3}
]
}
}
}
由于最高的价格没超过200元,因此最后的结果自动分为小于50,50到100,100到150,大于150共四个区间的值。
100到150区间的文档数为0个,我们想在返回结果中自动过滤该值,或者过滤偏小的值,可以添加一个参数”min_doc_count”,比如
{
"aggs" : {
"prices" : {
"histogram" : {
"field" : "price",
"interval" : 50,
"min_doc_count" : 1
}
}
}
}
返回结果会自动将你设定的值以下的统计结果过滤出去。
Date Histogram
使用方法与Histogram类似,只是聚合的间隔区间是针对时间类型的字段。
{
"aggs" : {
"articles_over_time" : {
"date_histogram" : {
"field" : "date",
"interval" : "1M",
"format" : "yyyy-MM-dd"
}
}
}
}
执行结果
{
"aggregations": {
"articles_over_time": {
"buckets": [
{"key_as_string": "2013-02-02","key": 1328140800000, "doc_count": 1},
{"key_as_string": "2013-03-02","key": 1330646400000, "doc_count": 2},
...
]
}
}
}
IPv4 range
由于ES是一个企业级的搜索和分析的解决方案,在做大量数据统计分析时比如用户访问行为数据,会采集用户的IP地址,类似这样的数据(还有地理位置数据等),ES也提供了最直接的统计接口。
{
"aggs" : {
"ip_ranges" : {
"ip_range" : {
"field" : "ip",
"ranges" : [
{ "to" : "10.0.0.5" },
{ "from" : "10.0.0.5" }
]
}
}
}
}
执行结果
{
"aggregations": {
"ip_ranges": {
"buckets" : [
{"to": 167772165, "to_as_string": "10.0.0.5","doc_count": 4},
{"from": 167772165,"from_as_string": "10.0.0.5","doc_count": 6}
]
}
}
}
Return only aggregation results
在统计分析时我们有时候并不需要知道命中了哪些文档,只需要将统计的结果返回给我们。因此我们可以在request body中添加配置参数size。
curl -XGET 'http://localhost:9200/twitter/tweet/_search' -d '{
"size": 0,
"aggregations": {
"my_agg": {
"terms": {"field": "text"}
}
}
}
'
聚合缓存
ES中经常使用到的聚合结果集可以被缓存起来,以便更快速的系统响应。这些缓存的结果集和你掠过缓存直接查询的结果是一样的。因为,第一次聚合的条件与结果缓存起来后,ES会判断你后续使用的聚合条件,如果聚合条件不变,并且检索的数据块未增更新,ES会自动返回缓存的结果。
注意聚合结果的缓存只针对size=0的请求(参考3.10章节),还有在聚合请求中使用了动态参数的比如Date Range中的now(参考3.5章节),ES同样不会缓存结果,因为聚合条件是动态的,即使缓存了结果也没用了。
Elasticsearch信息检索API
前言
想要更好的理解ES不应当把它的存储功能与搜索功能割裂开学习,只有正确的索引文档才能使之被正确的检索。
Elasticsearch真正强大之处在于可以从混乱的数据中找出有意义的信息。
ES与其他nosql数据库的差别之一,ES接收非结构化数据但数据本身必须是结构化的JSON文档,不能保存无结构的二进制数据。这是由ES搜索引擎的基因决定的。
ES搜索中你要知道的
倒排索引(Reverted Index)
ES使用倒排索引结构来做快速的全文搜索。倒排索引由在文档中出现的唯一的单词列表,以及对于每个单词在文档中的位置组成。
从网上扒了个很好的例子,假设我们有三句话:
T[0] = "it is what it is"
T[1] = "what is it"
T[2] = "it is a banana"
常规索引是指通过位置找到相应的单词,比如:T[0]的第一个单词是it,可以记为 (0,0) : “it”,再如 (2,1) : “is”。
倒排索引则是反过来,通过单词获取位置,比如:”it” 这个单词出现的位置有 (0,0) (0,3) (1,2) (2,0),这样可以记为 “it” :{(0,0) (0,3) (1,2) (2,0)}。通过对上述三句话建立倒排索引可以得到:
"a" : {(2,2)}
"banana" : {(2,3)}
"is" : {(0,1) (0,4) (1,1) (2,1)}
"it" : {(0,0) (0,3) (1,2) (2,0)}
"what" : {(0,2) (1,0)}
通过构建好的倒排索引,使得我们可以很方便的实现对语句的检索,比如: 需要检索包含”what” “is” “it”三个单词的语句,忽略倒排表中的第二位(单词在每句中的位置),可以得到 {0 1}∩{ 0 1 2}∩{0 1 2} = {0 1},因此我们断定T[0]和T[1]满足条件。在检索 “what is it”这个词组的时候还需要考虑到单词的具体位置,因此我们只能够获取到 T[1] 满足条件{(1,0) (1,1) (1,2)}。
上面的分析可以告诉我们,单词或语句的检索在构建好倒排索引之后可以转化成一个集合求解的问题,而不用逐行逐字的扫描,这使得检索效率得到大大地提高,这也就是为什么倒排索引在搜索领域如此重要的原因。
分析与分词(Analyzer)
将一段文本转换为一组唯一的标准化的标记(token),以便创建倒排索引,然后查询倒排索引。
Analyzer的工作分三部分:
- 字符过滤器(Character filters):分词前整理字符串,做一些类似去掉HTML标签. 把&符号转换成单词 And 之类的操作;
- 分词解析器(Tokenizer):分词解析器将会把一个字符串拆分成单独的terms。 一个简单的分词器可能会使用空白和标点符号(punctuation), 来进行分词解析;
- 解析过滤器(Token filters):进一步进行terms 整理。变成小写,删除介词,语义连接词: ‘A, and , the And,添加同义词
例如,
The quick brown fox jumped over the lazy dog
字符过滤:The quick brown fox jumped over the lazy dog
解析:[the, quick, brown, fox, jump, over, the, lazy, dog]
解析过滤器:[quick, fast, brown, fox, foxes, foxing, foxed, jump, jumps, leap, lazy, dog, dogs]
实践中需要注意的是,查询时只能查找在倒排索引中出现的词,所以确保在文档索引的时候以及字符串查询的时候使用同一个分析器。
相似性算法(Similarity algorithm)
默认情况下,ES查询结果是按相关性倒序返回的。查询结果中有一项_score值,存储一个浮点型的数字,就是相关性评分。
在ES中应用相似性算法较典型的有:
- Fuzzy query(模糊匹配)是看与原始检索term的相似程度,允许查询时匹配拼写错误的单词, 音标表过滤器能在索引时用于发音相似的匹配;
- Terms query(词条匹配)是看找到的terms 百分比值
相似性算法包含:
- Inverse document frequency:在当前index库中出现的越多,相关性越低
- Term frequency/inverse document frequency(TF/IDF):一种用于资讯检索与资讯探勘的常用加权技术。如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
- Term frequency:在一个field中出现越频繁得分越高
- Field-length norm:匹配百分比越高得分越高。如,中华人民共和国 < 人民共和国 < 人民。
一个独立的查询中,可能是一个term proximity(近似度,如“人民”与“人民共和国”) 和 term similarity(相似度,如“人民”与“人们”) 的结合体。
在ES查询中你附加yes/no 从句越多(bool 子句),_score数值越高。
简易检索(Query String)
把参数直接附加在URL后面进行信息的检索。适合用作curl 测试。
# 查指定index,字段user为kimchy的
curl -XGET 'http://localhost:9200/twitter/_search?q=user:kimchy'
# 查指定index下指定type
curl -XGET 'http://localhost:9200/twitter/tweet,user/_search?q=user:kimchy'
# 查指定多个index下指定type
curl -XGET 'http://localhost:9200/kimchy,elasticsearch/tweet/_search?q=tag:wow'
# 查全局下指定type
curl -XGET 'http://localhost:9200/_all/tweet/_search?q=tag:wow'
# 查全局
curl -XGET 'http://localhost:9200/_search?q=tag:wow'
# 计数
curl -XGET 'http://localhost:9200/twitter/tweet/_count?q=user:kimchy'
结构化检索(Query DSL)
为方便理解,我会把结构化过滤(Filter DSL)也穿插在这个章节中进行介绍。
简易检索是一种非常有效的命令行查询。但是,如果你想要善用搜索,你必须使用结构化检索查询(request body search)API。
DSL,面向领域专用语言,近似人类语言的查询语言,让不熟悉编程的人也能使用查询功能。使用结构化查询,你需要传递query参数来构成一个查询子句,例如
GET _search {
"query": {
"bool": {
"must":[
{"match": {"title":"search"}},
{"match":{"content": "Elasticsearch"}}
]
},
"filter": [
{"term":{"status": "published"}},
{"range": {"date": {"gte": "2016-03-18"}}}
]
}
}
我们可以看到,request body是由一条条简单的查询子句合并而成。合并的方式有两种:叶子子句与复合子句。
request body还可以添加查询语句和过滤语句,他们可以放在各自的上下文中。上面的例子是一条带过滤的查询语句,你也可以照着样子写一个带查询的过滤语句。
下面我们再来认识一下其他一些重要的搜索API。
Match All
空查询,即匹配所有的文档
# 匹配全部
{"match_all":{}}
#无须计算相似度分值,手动指定_score 返回值
{"match_all":{"boost": {"boost": 1.2}}
Term/terms
精确过滤/包含。term只在倒排查询里精确地查找特定短语,而不会匹配短语的其它变形,如Kitty或KITTY。这里的过滤不是指等于。
执行下面的命令
{
"term": {"user" :"kitty"}
}
返回文档中有可能会包含
{ “user” : [“kitty”, hello”] }
因为term 过滤器的工作原理是检查倒排索引中所有具有短语的文档。而上面的文档在倒倒索引中与kitty建立了关联,因此它可以作为结果被返回。
GET /school/students/_search
{
"query": {
"filtered": {
"filter": {
"term": {
"user": "kitty"
}
}
}
}
}
这里要注意,如果你期望用term精确过滤字符型字段时,需要在mapping时将该字段置为not_analyzed。否则会出现下面这种情况,文档
{ “user” : “hello.kitty” }
在执行
{
"term": {"user" :"hello.kitty"}
}
后无法返回结果,原因是在索引过程中分析器自动将hello.kitty分成了hello与kitty两个term,而且中间的分隔符.号被过滤掉了。
Range
范围过滤
{"range" :
{"age" : { "gte" : 10, "lte" : 20, "boost" : 2.0 }}
}
对应逻辑运算符有:
- gte 大于等于
- gt 大于
- lte 小于等于
- lt 小于
注意,range过滤也对字符串有效,但相比数字或日期的效率要慢得多,比如,
{"range" :
{ "name" : { "gte" : "g", "lte" :"r" }
}
因为ES会在从g到r范围内的每个短语执行term过滤。
Exists
过滤是否包含指定字段的文档
{ "exists" :
{ "field" : "user" }
}
以下都是满足条件的:
{ “user”: “jane” }
{ “user”: “” }
{ “user”: “-” }
{ “user”: [“jane”] }
{ “user”: [“jane”, null ] }
Prefix and Wildcard
前缀与通配符查询,效果相当于SQL脚本中的like。
{ "regexp":
{ "name.first": "s.*y" }
}
星号代表多个字符序列,问号代表单个字符。
两个查询器性能都很差。ES中不允许使用?*开始的通配符(如,?itty)。
Regexp
正则查询。跟通配符查询功能类似,但是正则查询支持更灵活定制细化查询,这一点与通配符的泛化是不一样的,而且正则查询天生支持使用强大的正则表达式的来准确匹配一个或几个term。
{ "regexp":
{ "name.first": "s.*y" }
}
使用尽量精确的正则来匹配terms,避免过重的迭代扫描。
需要注意的是,使用正则查询的字段最好是不分词的,因为分词的字段可能会导致边界问题,从而使查询失败,得不到任何结果,这一点和Wildcard效果是一样的。
Fuzzy
模糊查询。在相似性算法一节中有点到过,如果在输入关键字时你的单词拼写有问题或打了一个同音字,却搜不出结果来,这个搜索引擎的体验会非常差。搜索引擎就应该把可能的结果,通过相关性分数将匹配结果呈现给我们。
{
"fuzzy": {
"user": {
"value": "ki",
"boost": 1.0,
"fuzziness": auto,
"prefix_length": 0,
"max_expansions": 50
}
}
}
其中,fuzziness默认值为auto,没事别改它;prefix_length代表从第N位开始匹配。
IDS
这里的IDs就是文档在倒排索引中的DocIDs,即根据文档ID精确检索。
{ "ids" :
{ "type" : "my_type", "values" : ["1", "4", "100"] }
}
注意使用该查询器时需要指定index与type,除非你的集群里只有一组index与type。
Constant Score
得分过滤,设定相关性分值,使返回的文档相似度能满足用户要求。
{ "constant_score" :
{ "filter" : { "term" : { "user" : "kimchy"} }, "boost" : 1.2 }
}
Bool
布尔过滤。由一个或者多个boolean类型的从句嵌套组成复合查询。
该过滤器的子句类型有:
- Must必须匹配;
- Must_not必须不匹配;
- Should至少有一个分句匹配,与OR 相同
Bool条件匹配的越多,该条记录的得分越高。但是, filter的匹配与否,对则结果集相似度分值不产生影响。
# minimum_should_match:1,should条件列表中至少满足一项
{
"bool": {
"must": {
"term": {"user": "kimchy"}
},
"filter": {
"term": {"tag": "tech"}
},
"must_not": {
"range": {"age": {"from": 10,"to": 20}}
},
"should": [
{
"term": {"tag": "wow"}
},
{
"term": {"tag": "elasticsearch"}
}
],
"minimum_should_match": 1,
"boost": 1
}
}
Dis Max
dis_max(Disjuction Max,Disjuction是”OR”的意思)与bool过滤中的should有点象,就是返回匹配了任何从句的文档,但dis_max有个更高级的功能,就是tie_breaker,打破平局。
{
"dis_max": {
"tie_breaker": 0.7,
"queries": [
{
"term": {
"name": "kitty"
}
},
{
"term": {
"age": 35
}
}
]
}
}
如果没有tie_breaker,那么返回两个文档
{“name”:”kitty”, “age”:33}
{“name”:”kitty”, “age”:35}
且两个文档得分一致,因为name与age只要满足一项就能匹配上,加上tie_breaker后,会将age的匹配度也算上。因此最终的得分,第二个文档会高于第一个。
Indices
同时查询多个index
{
"indices": {
"indices": [
"index1",
"index2"
],
"query": {
"term": {
"tag": "wow"
}
},
"no_match_query": {
"term": {
"tag": "kow"
}
}
}
}
From/Size
ES中的翻页
{
"from": 0,
"size": 10,
"query": {
"term": {
"user": "kimchy"
}
}
}
Sort
ES中的排序
{
"sort": [
{"post_date": {"order": "asc"}},
"user",
{"name": "desc"},
{"age": "desc"},
"_score"
],
"query": {
"term": {"user": "kimchy"}
}
}
Source
指定返回字段
# 不返回字段
{
"_source": false,
"query": {
"term": {
"user": "kimchy"
}
}
}
# 指定返回字段
{
"_source": {
"include": [
"field1", "field2"
],
"exclude": ["field3" ],
"query" : { "term" : { "user" : "kimchy" }
}
}
优化你的检索
先记住一个事,尽量多的使用过滤器。
假设你使用term去匹配user字段,过滤器的内部逻辑是:
- term 过滤器在倒排索引中进行匹配
- 为匹配过的文档创建一个由1和0构成的字节集,匹配的文档得到1字节,不匹配为0;
- 将这个字节集放入内存以备后续请求使用,后续请求将跳过步骤1和2。
执行查询时filter会早于query执行,这样内存中的字节集会传给query以跳过已经被排除的文档。过滤语句的作用就是为query缩小匹配范围。
而query语句不仅要查找匹配的文档,还要计算每个文档的相关性,所以一般来讲query语句要比过滤语句更耗时,并且查询结果不能缓存。
ElasticSearch索引管理API
前言
ES的可定制化做得非常丰富灵活,只要能熟悉它强大的API库,它能几乎满足你关于垂直搜索领域的所有特殊的需求。今天这篇文章将介绍ES索引管理与配置的API,内容来源自官网的Indices API(https://www.elastic.co/guide/en/elasticsearch/reference/2.2/indices.html),讲实话ES官网的文档写得不是很友好,给我感觉好象是有多位作者各自编辑且互相间没什么呼应很容易让人看蒙圈。讲方法库的文章又总是很枯燥的,但Indices API在ES众多API库中起核心支配的作用,所以我把它放在API的第一篇,在翻译文档的同时我会将自己的理解尽量用大白话讲出来。
Elasticsearch官方API遵循restful标准,支持接口有get,post,put,delete,head等等。Get是请求指定的页面信息,并返回实体主体;Post是请求服务器接受所指定的文档更新所标识的URI;Put向服务器传送的数据取代指定的文档的内容;Delete是请求服务器删除指定的页面;Head只请求页面的首部。
为了偷懒,文章中会直接复制很多官网的源码。而官方文档中在贴出大块代码时经常会采用简写的样式,会让初学者很难理解,我先举个例子,下面是一个完整的请求:
curl -XGET 'localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}'
简写curl请求:
GET /_count
{
"query": {
"match_all": {}
}
}
Index索引管理
创建Index
使用默认设置创建索引
curl -XPUT 'http://localhost:9200/twitter/'
默认shards数量为5
默认replicas数量为1
确保索引被创建在适当数量的分片上以及指定若干数量的副本
curl -XPUT 'http://localhost:9200/twitter/' -d '
index :
number_of_shards : 3
number_of_replicas : 2
'
在创建索引后,shards的数量不可修改,replicas的数量可以修改
删除Index
curl -XDELETE 'http://localhost:9200/twitter/'
删除多个
curl -XDELETE 'http://localhost:9200/index_one,index_two/'
curl -XDELETE 'http://localhost:9200/index_*/'
curl -XDELETE 'http://localhost:9200/_all/'
获取Index信息
根据指定index的名称或别名查询
curl -XGET 'http://localhost:9200/twitter/'
根据指定index的名称或别名返回指定的数据
curl -XGET 'http://localhost:9200/twitter/_settings,_mappings ,_warmers,_aliase'
指定返回的数据包括:
_settings当前库的配置信息(分片shards或副本replicas等);_mappings当前库的结构;_warmers用于查询优化的加热器;_aliase别名。
查询Indices是否存在
curl -XHEAD -i 'http://localhost:9200/twitter'
返回404表示不存在,200表示存在。
开启/关闭Index
curl -XPOST 'localhost:9200/my_index/_close'
curl -XPOST 'localhost:9200/my_index/_open'
Index被关闭后不能做增删改查操作。
Mapping管理
Mapping主要用于描述ES的库表结构,mapping不仅告诉ES一个type中有哪些field,还描述field是什么类型,它还告诉ES如何索引数据以及数据是否能被搜索到。
Lucene中没有mapping,你可以将mapping理解为一个JSON文档到Lucene扁平化数据的映射器。
很多初学者会忽视mapping的作用,比如同一个index下两个type中都有createdate字段,一个存日期型“20160328”,一个是数值型20160328。在检索“20160328”时两种类型的文档都会被过滤出来,好象没定义mapping也可以啊。这其实是一个陷阱,当数值型的那个createdate值变成时间戳如1459129963726,这时候无论是过滤还是排序都有问题,Lucene是不关心字段是日期型还是数值型,它的排序规则是根据遇到的第一个createdate的类型决定的。
规避陷阱的办法就是真正的理解ES中type的含义,type的确是一张非结构化的数据表,但别把不相关的数据都扔到一个type中。原则上确保index下各个type中,同名的字段用相同的mapping,如果再谨慎一点,字段名称也请定义再明确再详细一些。
创建Mapping
可以通过创建mapping来创建index,或向已有index添加type,或向已有type添加field
创建一个名称为twitter的index,同时创建twitter下名称为tweet的type,以及在这个type下创建一个string类型的field名叫message。在一个请求中同时创建库表
PUT twitter
{
"mappings": {
"tweet": {
"properties": {
"message": {
"type": "string"
}
}
}
}
}
再往表tweet下添加一个user_name字段
PUT twitter/_mapping/tweet
{
"properties": {
"user_name": {
"type": "string"
}
}
}
在twitter下创建名为user的type
PUT twitter/_mapping/user
{
"properties": {
"name": {
"type": "string"
}
}
}
ES还支持同时对多个index进行mapping操作,指定的index名称用逗号,分隔
获取mapping信息
获取指定index信息,或index下指定type信息
curl -XGET 'http://localhost:9200/_mapping/twitter'
curl -XGET 'http://localhost:9200/twitter/_mapping/tweet'
获取多个index或多个type信息
curl -XGET 'http://localhost:9200/_mapping/twitter,kimchy'
curl -XGET 'http://localhost:9200/_all/_mapping/tweet,book'
获取全库信息
curl -XGET 'http://localhost:9200/_all/_mapping'
curl -XGET 'http://localhost:9200/_mapping'
获取field字段信息,假设有下面一个库表结构
{
"twitter": {
"tweet": {
"properties": {
"username": { "type": "string" },
"age": { "type": "number" }
}
}
}
}
查username字段信息
curl -XGET 'http://localhost:9200/twitter/_mapping/tweet/field/username'
查多个字段信息
curl -XGET 'http://localhost:9200/twitter,kimchy/_mapping/field/username,age'
curl -XGET 'http://localhost:9200/_all/_mapping/tweet,book/field/message,username'
curl -XGET 'http://localhost:9200/_all/_mapping/tw*/field/*age'
查询type是否存在
curl -XHEAD -i 'http://localhost:9200/twitter/tweet'
返回404表示不存在,200表示存在。
Alias别名管理
同mapping一样,新手上路时很容易忽略aliases功能,老司机们对aliases的好是如饮甘饴。
先说一个重要的事,在实际应用中尽量用别名而不是索引原名。养成好习惯现在就把代码中的索引名换掉。下面说一个场景,你会意识到这事很重要:
你们线上最忙碌的一个index,叫它idx_AAA好了,有一天必须要改其中一个字段的mapping,直接改现有的idx_AAA数据?不行。复制AAA到idx_BBB上,然后将代码中配置中所有idx_AAA改为idx_BBB,然后走上线流程,发升级公告,重启搜索服务?小公司的老板可能会忍你,你看马云会不会。马老板拎刀去了,你才开始流泪太晚了。你如果一开始就给idx_AAA建个别名,你的应用上全都用别名,有天要改mapping,可以但别动idx_AAA,复制AAA到idx_BBB上改mapping,改完后,执行脚本:
POST /_aliases
{
"actions": [
{ "remove": { "index": "idx_AAA", "alias": "my_alias" }},
{ "add":&amp;amp;amp;nbsp;&amp;amp;amp;nbsp;&amp;amp;amp;nbsp; { "index": " idx_BBB", "alias": "my_alias" }}
]
}
无缝切换有没有,马总再也不用担心你的mapping了。
创建aliases关联
关联一个别名
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "add" : { "index" : "test1", "alias" : "alias1" } }
]
}'
可以一个index对应多个aliases,或一个aliases对应多个index
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "add" : { "index" : "test1", "alias" : "alias1" } },
{ "add" : { "index" : "test2", "alias" : "alias1" } }
]
}'
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "add" : { "indices" : ["test1", "test2"], "alias" : "alias1" } }
]
}'
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "add" : { "index" : "test*", "alias" : "all_test_indices" } }
]
}'
ES还提供了根据过滤器filter建立alias的功能,下面的例子,即先user=”kimchy”过滤出结果index,然后再为结果index创建aliases关联
curl -XPOST 'http://localhost:9200/_aliases' -d '{
"actions" : [
{
"add" : {
"index" : "test1",
"alias" : "alias2",
"filter" : { "term" : { "user" : "kimchy" } }
}
}
]
}'
ES还可以将视图创建在指定的分片shard中,这样做的目的是为了减少检索时的寻片工作,在执行查询时指定检索的shard路由地址,将避免shard by shard的加载与过滤
# 查与存都在同一个分片
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{
"add" : {
"index" : "test",
"alias" : "alias1",
"routing" : "1"
}
}
]
}'
# 查与存在不同分片
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{
"add" : {
"index" : "test",
"alias" : "alias2",
"search_routing" : "1,2",
"index_routing" : "2"
}
}
]
}'
#指定路由检索
curl -XGET 'http://localhost:9200/alias2/_search?q=user:kimchy&amp;amp;amp;routing=2,3'
删除aliases关联
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "remove" : { "index" : "test1", "alias" : "alias1" } }
]
}'
可以在一个请求中同时做创建与删除操作
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "remove" : { "index" : "test1", "alias" : "alias1" } },
{ "add" : { "index" : "test1", "alias" : "alias2" } }
]
}'
Setting索引设置
我们可以通过多种方式来自定义ES的索引行为,但对于大部分应用来说,只需要搞清楚两个配置就行了number_of_shards与number_of_replicas。ES已经提供了优化好的默认配置,除非你明白这些配置的意义以及为什么这么做,不然就别改这些配置
修改index设置
Index创建后无法修改分片数量,但可以修改副本数量
curl -XPOST 'localhost:9200/myindex/_close'
curl -XPUT 'localhost:9200/myindex/_settings' -d '
{
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 4
}
}'
curl -XPOST 'localhost:9200/myindex/_open'
需要注意的是,修改设置前应该先关闭index服务,修改完成后再启动
修改index的分词方式
curl -XPOST 'localhost:9200/myindex/_close'
curl -XPUT 'localhost:9200/myindex/_settings' -d '{
"analysis" : {
"analyzer":{
"content":{
"type":"custom",
"tokenizer":"whitespace"
}
}
}
}'
curl -XPOST 'localhost:9200/myindex/_open'
获取设置信息
curl -XGET 'http://localhost:9200/twitter/_settings'
同时获取多个
curl -XGET 'http://localhost:9200/twitter,kimchy/_settings'
curl -XGET 'http://localhost:9200/_all/_settings'
curl -XGET 'http://localhost:9200/2013-*/_settings'
指定过滤条件获取
curl -XGET 'http://localhost:9200/2013-*/_settings/name=index.number_*'
分析器
这是索引设置的第三个重要的配置项,几年前我从开始接触ES后的很长一段时间里认为ES里的Analyze就是分词器,后来才意识到应该是分析器,分析器中包含了分词器,过滤器。ES官方内置了一些分析器,最重要的就是standard。可惜内置的分析器都是针对西方语系定制的。之后我们会讲到中文的Analyze以及如何定制我们需要的Analyze。Analyze的作用就是将你的文本数据转化成倒排索引。
测试分词
不指定index测试分词效果
curl -XGET 'localhost:9200/_analyze' -d '
{
"analyzer" : "standard",
"text" : "this is a test"
}'
可以同时为多个文本进行分词
curl -XGET 'localhost:9200/_analyze' -d '
{
"analyzer" : "standard",
"text" : ["this is a test", "the second text"]
}'
可以指定一个index下默认的分词器进行测试
curl -XGET 'localhost:9200/twitter/_analyze' -d '
{
"text" : "this is a test"
}'
可以指定index下指定分词器进行分词
curl -XGET 'localhost:9200/twitter/_analyze' -d '
{
"analyzer" : "whitespace",
"text : "this is a test"
}'
可以基于指定field的分词器进行分词
curl -XGET 'localhost:9200/twitter/_analyze' -d '
{
"field" : "username",
"text" : "this is a test"
}'
也可以在一个URL中带上分词参数
curl -XGET 'localhost:9200/_analyze?tokenizer=keyword&amp;amp;filters=lowercase&amp;amp;text=this+is+a+test'
分词器Explain
与关系型数据库一样,ES也拥有explain功能,在get _analyze请求中将explain参数设置为true,可以输出分词器的详细信息,explain本身并不提供任何调优的建议,但它能提供重要的信息帮助你做出调优决策。2.x后新特性。
curl -XGET 'localhost:9200/twitter/_analyze' -d '
{
"tokenizer" : "standard",
"token_filters" : ["snowball"],
"text" : "detailed output",
"explain" : true,
"attributes" : ["keyword"]
}'
Index模板
索引模板用来预先配置索引。通常用在索引日志数据的场景,比如,你将日志数据索引在一个以日期结尾的索引上,以后每天,一个新的配置好的索引会自动创建好。
创建index模板
Index模板的作用就是它的字面意义,定义好了template,创建index时可以应用这些template,或者,创建好模板后应用到指定的index上。Template中可以包含setting,aliases与mapping配置。
下面的例子就是创建一个模板,将这个模板中包含的一系列配置应用到名称是“te”开头的index上。
curl -XPUT localhost:9200/_template/template_1 -d '
{
"template" : "te*",
"settings" : {
"number_of_shards" : 1
},
"aliases" : {
"alias1" : {},
"alias2" : {
"filter" : {
"term" : {"user" : "kimchy" }
},
"routing" : "kimchy"
},
"{index}-alias" : {}
},
"mappings" : {
"type1" : {
"_source" : { "enabled" : false }
}
}
}
'
同时应用多个模板,应用过程中用order指定每个模板的执行顺序,如果后一个模板中包含的配置信息与前一个模板有重复,就将覆盖前一模板重复的配置。
curl -XPUT localhost:9200/_template/template_1 -d '
{
"template" : "*",
"order" : 0,
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"type1" : {
"_source" : { "enabled" : false }
}
}
}
'
curl -XPUT localhost:9200/_template/template_2 -d '
{
"template" : "te*",
"order" : 1,
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"type1" : {
"_source" : { "enabled" : true }
}
}
}
'
删除index模板
curl -XDELETE localhost:9200/_template/template_1
获取template信息
curl -XGET localhost:9200/_template/template_1
查询template是否存在
curl -XHEAD -i localhost:9200/_template/template_1
Warmers管理
预热器,原理是将数据段提前加载到内存中,以提升检索性能。该功能主要用于聚合与排序操作。
2.x版本中被deprecated了,即将在3.0版本中被移除,不多作介绍了
https://github.com/elastic/elasticsearch/issues/15607
Replica配置
很奇怪官网上关于副本配置的章节只有对shadow replicas(影子副本)的描述,先留白等后面碰到相关的要点再补充到这里。
影子副本
官网原文“This functionality is experimental and may be changed or removed completely in a future release.”- 这是个实验性功能,在更高版本中可能会被修改或者完全删除。
这个章节已经够枯燥了,对于具有不确定性的功能,我就偷个懒不浪费时间了,感兴趣的朋友可以去官网(https://www.elastic.co/guide/en/elasticsearch/reference/2.2/_node_level_settings_related_to_shadow_replicas.html)
题外话,如何甄别与接受开源项目中这种不稳定的功能,应当成为技术人员身上的一种能力,且不说每个人的精力是有限的,高风险的功能对于你生产集群的维护与升级带来的烦恼和损失才是难以估算的。
Index监控
后面两个章节的阅读对象不仅是开发人员,还有运维人员。
Indices状态
获取索引状态
# 查看所有
curl localhost:9200/_stats
# 查看一个/多个索引
curl localhost:9200/index1,index2/_stats
指定返回的状态信息
curl 'localhost:9200/_stats/merge,refresh'
curl 'localhost:9200/my_index/_stats/indexing?types=type1,type2
curl 'localhost:9200/_stats/search?groups=group1,group2
Indices段(segments)
一种衡量数据文件空间大小的逻辑单位,例如Oracle的最小结构单位是block,然后是extent,再就是segment,然后才是表空间和数据库。ES中最小逻辑单元的就是segment,从底到顶顺序是这样的,segment -> shard -> inverted index -> index。inverted index看着很陌生,但说到中文名就很响亮了 – 倒排索引,在索引设置的章节中也提到过它。
查看段信息
# 查看指定索引的段
curl -XGET 'http://localhost:9200/test/_segments'
# 查看多个索引的段
curl -XGET 'http://localhost:9200/test1,test2/_segments'
# 查看全部索引的段
curl -XGET 'http://localhost:9200/_segments'
Indices恢复
获取索引恢复信息
curl -XGET http://localhost:9200/index1,index2/_recovery
获取集群恢复信息
curl -XGET http://localhost:9200/_recovery?pretty&amp;human
Indices分片存储
提供Index 分片存储信息:分片在哪个节点上, 最近的状况, 是否有异常发生
# 查看指定索引
curl -XGET 'http://localhost:9200/test/_shard_stores'
# 查看多个索引
curl -XGET 'http://localhost:9200/test1,test2/_shard_stores'
# 查看全局
curl -XGET 'http://localhost:9200/_shard_stores'
Status管理
清除缓存
# 清除指定索引
curl -XPOST 'http://localhost:9200/twitter/_cache/clear'
# 清除多个索引
curl -XPOST 'http://localhost:9200/kimchy,elasticsearch/_cache/clear'
# 清除全局索引
curl -XPOST 'http://localhost:9200/_cache/clear'
强制刷新
如果你的集群从采集到检索的过滤设置是非实时的,那么有时候又有实时性的需求,就可以调用强制刷新的接口。
# 刷新指定索引
curl -XPOST 'http://localhost:9200/twitter/_refresh'
# 刷新多个索引
curl -XPOST 'http://localhost:9200/kimchy,elasticsearch/_refresh'
# 刷新所有索引
curl -XPOST 'http://localhost:9200/_refresh'
强制合并
为减少段(segment)的数量,ES允许你将多个索引进行强制合并
# 合并指定索引
curl -XPOST 'http://localhost:9200/twitter/_forcemerge'
# 合并多个索引
curl -XPOST 'http://localhost:9200/kimchy,elasticsearch/_forcemerge'
# 合并所有索引
curl -XPOST 'http://localhost:9200/_forcemerge'
该接口还提供几个参数来定制你的合并需求,如max_num_segments是你想最终合并为几个segments,如果值为1,那么合并后数据全在一个段中;only_expunge_deletes,是否清除过期数据,大部分分布式数据库中,用户提交一个删除数据的请求,用户以为指定的数据已经消失了,事实上数据库是不会立即执行删除操作的,而只是在该条数据上打上了标签而已,再通过其他的策略定期或统一清除。
清空缓存
清空索引的缓冲数据以及事务日志。
# 清空指定索引
POST /twitter/_flush
# 清空多个索引
POST /kimchy,elasticsearch/_flush
# 清空所有索引
POST /_flush
升级版本
给指定索引升级版本,这里的版本升级是针对Lucene的,有时候你用旧的Lucene版本创建的索引,新版本的Lucene的reader无法解析,所以开发过程中要注意:
统一测试环境与线上的Lucene版本,其实就是ES版本;
开发人员之间的版本统一;
Solr迁移ES前对各种类型的数据有个全面的测试与评估;
慎用本接口
否则,重建索引的代价ESer们应该会懂
curl -XPOST 'http://localhost:9200/twitter/_upgrade'
写在最后,如果你打算在你的企业中构建一个搜索引擎,有两个要点,一是吃透ES的技术细节,二是弄清楚你的业务需求,然而很多技术人员会经常犯这样的错,过于关注第一点,而忽略第二点。在我看来,第二点来得更为重要。平台与解决方案是达到最终目的手段,在前行的过程中不要忘记为什么而出发。
附录
以下是几个ELK的管理脚本,供参考
https://github.com/imperialwicket/elasticsearch-logstash-index-mgmt
elasticsearch-backup-index.sh
#!/bin/bash
#
# elasticsearch-backup-index.sh
#
# Push logstash index from yesterday to s3 with an accompanying restore script.
# http://logstash.net
# http://www.elasticsearch.org
# https://github.com/s3tools/s3cmd | http://s3tools.org/s3cmd
#
# Inspiration:
# http://tech.superhappykittymeow.com/?p=296
#
# Must run on an elasticsearch node, and expects to find the index on this node.
usage()
{
cat << EOF
elasticsearch-backup-index.sh
Create a restorable backup of an elasticsearch index (assumes Logstash format
indexes), and upload it to an existing S3 bucket. The default backs up an
index from yesterday. Note that this script itself does not restart
elasticsearch - the restore script that is generated for each backup will
restart elasticsearch after restoring an archived index.
USAGE: ./elasticsearch-backup-index.sh -b S3_BUCKET -i INDEX_DIRECTORY [OPTIONS]
OPTIONS:
-h Show this message
-b S3 path for backups (Required)
-g Consistent index name (default: logstash)
-i Elasticsearch index directory (Required)
-d Backup a specific date (format: YYYY.mm.dd)
-c Command for s3cmd (default: s3cmd put)
-t Temporary directory for archiving (default: /tmp)
-p Persist local backups, by default backups are not kept locally
-s Shards (default: 5)
-r Replicas (default: 0)
-e Elasticsearch URL (default: http://localhost:9200)
-n How nice tar must be (default: 19)
-u Restart command for elastic search (default 'service elasticsearch restart')
EXAMPLES:
./elasticsearch-backup-index.sh -b "s3://someBucket" \
-i "/usr/local/elasticsearch/data/node/0/indices"
This uses http://localhost:9200 to connect to elasticsearch and backs up
the index from yesterday (based on system time, be careful with timezones)
./elasticsearch-backup-index.sh -b "s3://bucket" -i "/mnt/es/data/node/0/indices" \
-d "2013.05.21" -c "/usr/local/bin/s3cmd put" -t "/mnt/es/backups" \
-g my_index -u "service es restart" -e "http://127.0.0.1:9200" -p
Connect to elasticsearch using 127.0.0.1 instead of localhost, backup the
index "my_index" from 2013.05.21 instead of yesterday, use the s3cmd in /usr/local/bin
explicitly, store the archive and restore script in /mnt/es/backups (and
persist them) and use 'service es restart' to restart elastic search.
EOF
}
if [ "$USER" != 'root' ] && [ "$LOGNAME" != 'root' ]; then
# I don't want to troubleshoot the permissions of others
echo "This script must be run as root."
exit 1
fi
# Defaults
S3CMD="s3cmd put"
TMP_DIR="/tmp"
SHARDS=5
REPLICAS=0
ELASTICSEARCH="http://localhost:9200"
NICE=19
RESTART="service elasticsearch restart"
# Validate shard/replica values
RE_D="^[0-9]+$"
while getopts ":b:i:d:c:g:t:p:s:r:e:n:u:h" flag
do
case "$flag" in
h)
usage
exit 0
;;
b)
S3_BASE=$OPTARG
;;
i)
INDEX_DIR=$OPTARG
;;
d)
DATE=$OPTARG
;;
c)
S3CMD=$OPTARG
;;
g)
INAME=$OPTARG
;;
t)
TMP_DIR=$OPTARG
;;
p)
PERSIST=1
;;
s)
if [[ $OPTARG =~ $RE_D ]]; then
SHARDS=$OPTARG
else
ERROR="${ERROR}Shards must be an integer.\n"
fi
;;
r)
if [[ $OPTARG =~ $RE_D ]]; then
REPLICAS=$OPTARG
else
ERROR="${ERROR}Replicas must be an integer.\n"
fi
;;
e)
ELASTICSEARCH=$OPTARG
;;
n)
if [[ $OPTARG =~ $RE_D ]]; then
NICE=$OPTARG
fi
# If nice is not an integer, just use default
;;
u)
RESTART=$OPTARG
;;
?)
usage
exit 1
;;
esac
done
# We need an S3 base path
if [ -z "$S3_BASE" ]; then
ERROR="${ERROR}Please provide an s3 bucket and path with -b.\n"
fi
# We need an elasticsearch index directory
if [ -z "INDEX_DIR" ]; then
ERROR="${ERROR}Please provide an Elasticsearch index directory with -i.\n"
fi
# If we have errors, show the errors with usage data and exit.
if [ -n "$ERROR" ]; then
echo -e $ERROR
usage
exit 1
fi
if [ -z "$INAME" ]; then
INAME="logstash"
fi
# Default logstash index naming is hardcoded, as are YYYY-mm container directories.
if [ -n "$DATE" ]; then
INDEX="$INAME-$DATE"
YEARMONTH=${DATE//\./-}
YEARMONTH=${YEARMONTH:0:7}
else
INDEX=`date --date='yesterday' +"$INAME-%Y.%m.%d"`
YEARMONTH=`date --date='yesterday' +"%Y-%m"`
fi
S3_TARGET="$S3_BASE/$YEARMONTH"
# Make sure there is an index
if ! [ -d $INDEX_DIR/$INDEX ]; then
echo "The index $INDEX_DIR/$INDEX does not appear to exist."
exit 1
fi
# Get metadata from elasticsearch
INDEX_MAPPING=`curl -s -XGET "$ELASTICSEARCH/$INDEX/_mapping"`
SETTINGS="{\"settings\":{\"number_of_shards\":$SHARDS,\"number_of_replicas\":$REPLICAS},\"mappings\":$INDEX_MAPPING}"
# Make the tmp directory if it does not already exist.
if ! [ -d $TMP_DIR ]; then
mkdir -p $TMP_DIR
fi
# Tar and gzip the index dirextory.
cd $INDEX_DIR
nice -n $NICE tar czf $TMP_DIR/$INDEX.tgz $INDEX
cd - > /dev/null
# Create a restore script for elasticsearch
cat << EOF >> $TMP_DIR/${INDEX}-restore.sh
#!/bin/bash
#
# ${INDEX}-restore.sh - restores elasticsearch index: $INDEX to elasticsearch
# instance at $ELASTICSEARCH. This script expects to run in the same
# directory as the $INDEX.tgz file.
# Make sure this index does not exist already
TEST=\`curl -XGET "$ELASTICSEARCH/$INDEX/_status" 2> /dev/null | grep error\`
if [ -z "\$TEST" ]; then
echo "Index: $INDEX already exists on this elasticsearch node."
exit 1
fi
# Extract index files
DOWNLOAD_DIR=\`pwd\`
cd $INDEX_DIR
if [ -f \$DOWNLOAD_DIR/$INDEX.tgz ]; then
# If we have the archive, create the new index in ES
curl -XPUT '$ELASTICSEARCH/$INDEX/' -d '$SETTINGS' > /dev/null 2>&1
# Extract the archive in to the INDEX_DIR
tar xzf \$DOWNLOAD_DIR/$INDEX.tgz
# Restart elasticsearch to allow it to open the new dir and file data
$RESTART
exit 0
else
echo "Unable to locate archive file \$DOWNLOAD_DIR/$INDEX.tgz."
exit 1
fi
EOF
# Put archive and restore script in s3.
$S3CMD $TMP_DIR/$INDEX.tgz $S3_TARGET/$INDEX.tgz
$S3CMD $TMP_DIR/$INDEX-restore.sh $S3_TARGET/$INDEX-restore.sh
# cleanup tmp files
if [ -z $PERSIST ]; then
rm $TMP_DIR/$INDEX.tgz
rm $TMP_DIR/$INDEX-restore.sh
fi
exit 0
elasticsearch-close-old-indices.sh
#!/bin/bash
# elasticsearch-close-old-indices.sh
#
# Close logstash format indices from elasticsearch maintaining only a
# specified number.
# http://logstash.net
# http://www.elasticsearch.org
#
# Inspiration:
# http://tech.superhappykittymeow.com/?p=296
#
# Must have access to the specified elasticsearch node.
usage()
{
cat << EOF
elasticsearch-close-old-indices.sh
Compares the current list of indices to a configured value and closes any
indices surpassing that value. Sort is lexicographical; the first n of a 'sort
-r' list are kept, all others are closed.
USAGE: ./elasticsearch-close-old-indices.sh [OPTIONS]
OPTIONS:
-h Show this message
-i Indices to keep open (default: 14)
-e Elasticsearch URL (default: http://localhost:9200)
-g Consistent index name (default: logstash)
-o Output actions to a specified file
EXAMPLES:
./elasticsearch-close-old-indices.sh
Connect to http://localhost:9200 and get a list of indices matching
'logstash'. Keep the top lexicographical 14 indices, close any others.
./elasticsearch-close-old-indices.sh -e "http://es.example.com:9200" \
-i 28 -g my-logs -o /mnt/es/logfile.log
Connect to http://es.example.com:9200 and get a list of indices matching
'my-logs'. Keep the top 28 indices, close any others. When using a custom
index naming scheme be sure that a 'sort -r' places the indices you want to
keep at the top of the list. Output index closes to /mnt/es/logfile.log.
EOF
}
# Defaults
ELASTICSEARCH="http://localhost:9200"
KEEP=14
GREP="logstash"
LOGFILE=/dev/null
# Validate numeric values
RE_D="^[0-9]+$"
while getopts ":i:e:g:o:h" flag
do
case "$flag" in
h)
usage
exit 0
;;
i)
if [[ $OPTARG =~ $RE_D ]]; then
KEEP=$OPTARG
else
ERROR="${ERROR}Indexes to keep must be an integer.\n"
fi
;;
e)
ELASTICSEARCH=$OPTARG
;;
g)
GREP=$OPTARG
;;
o)
LOGFILE=$OPTARG
;;
?)
usage
exit 1
;;
esac
done
# If we have errors, show the errors with usage data and exit.
if [ -n "$ERROR" ]; then
echo -e $ERROR
usage
exit 1
fi
# Get the indices from elasticsearch
INDICES_TEXT=`curl -s "$ELASTICSEARCH/_cat/indices?v" | awk '/'$GREP'/{match($0, /[:blank]*('$GREP'.[^ ]+)[:blank]*/, m); print m[1];}' | sort -r`
if [ -z "$INDICES_TEXT" ]; then
echo "No indices returned containing '$GREP' from $ELASTICSEARCH."
exit 1
fi
# If we are logging, make sure we have a logfile TODO - handle errors here
touch $LOGFILE
# Close indices
declare -a INDEX=($INDICES_TEXT)
if [ ${#INDEX[@]} -gt $KEEP ]; then
for index in ${INDEX[@]:$KEEP};do
# We don't want to accidentally close everything
if [ -n "$index" ]; then
echo -n `date "+[%Y-%m-%d %H:%M] "`" Closing index: $index." >> $LOGFILE
curl -s -XPOST "$ELASTICSEARCH/$index/_flush" >> $LOGFILE
curl -s -XPOST "$ELASTICSEARCH/$index/_close" >> $LOGFILE
echo "." >> $LOGFILE
fi
done
fi
exit 0
elasticsearch-remove-expired-indices.sh
#!/usr/bin/env bash
#
# Delete logstash format indices from elasticsearch maintaining only a
# specified number.
#
# Inspiration:
# https://github.com/imperialwicket/elasticsearch-logstash-index-mgmt/blob/master/elasticsearch-remove-old-indices.sh
#
# Must have access to the specified elasticsearch node.
usage()
{
cat << EOF
elasticsearch-remove-expired-indices.sh
Delete all indices older than a date.
USAGE: ./elasticsearch-remove-expired-indices.sh [OPTIONS]
OPTIONS:
-h Show this message
-d Expiration date (YYYY-MM-dd) from when we should start deleting the indices (default: 3 months ago)
-e Elasticsearch URL (default: http://localhost:9200)
-g Consistent index name (default: logstash)
-o Output actions to a specified file
EXAMPLES:
./elasticsearch-remove-old-indices.sh
Connect to http://localhost:9200 and get a list of indices matching
'logstash'. Keep the indices from less than 3 months, delete any others.
./elasticsearch-remove-old-indices.sh -e "http://es.example.com:9200" \
-d 1991-04-25 -g my-logs -o /mnt/es/logfile.log
Connect to http://es.example.com:9200 and get a list of indices matching
'my-logs'. Keep the indices created after the 25 april 1991, delete any others.
Output index deletes to /mnt/es/logfile.log.
EOF
}
# Defaults
ELASTICSEARCH="http://localhost:9200"
DATE=$(date --date="3 months ago" +"%Y%m%d")
INDEX_NAME="logstash"
LOGFILE=/dev/null
# Validate numeric values
RE_DATE="^[0-9]{4}-((0[0-9])|(1[0-2]))-(([0-2][0-9])|(3[0-1]))+$"
while getopts ":d:e:g:o:h" flag
do
case "$flag" in
h)
usage
exit 0
;;
d)
if [[ $OPTARG =~ $RE_DATE ]]; then
DATE=$OPTARG
else
ERROR="${ERROR}Expiration date must be YYYY-MM-dd.\n"
fi
;;
e)
ELASTICSEARCH=$OPTARG
;;
g)
INDEX_NAME=$OPTARG
;;
o)
LOGFILE=$OPTARG
;;
?)
usage
exit 1
;;
esac
done
# If we have errors, show the errors with usage data and exit.
if [ -n "$ERROR" ]; then
echo -e $ERROR
usage
exit 1
fi
# Get the indices from elasticsearch
INDICES_TEXT=`curl -s "$ELASTICSEARCH/_cat/indices?v" | awk '/'$INDEX_NAME'/{match($0, /[:blank]*('$INDEX_NAME'.[^ ]+)[:blank]*/, m); print m[1];}' | sort -r`
if [ -z "$INDICES_TEXT" ]; then
echo "No indices returned containing '$GREP' from $ELASTICSEARCH."
exit 1
fi
# If we are logging, make sure we have a logfile TODO - handle errors here
if [ -n "$LOGFILE" ] && ! [ -e $LOGFILE ]; then
touch $LOGFILE
fi
# Delete indices
declare -a INDEX=($INDICES_TEXT)
for index in ${INDEX[@]};do
# We don't want to accidentally delete everything
if [ -n "$index" ]; then
INDEX_DATE=$(echo $index | sed -n 's/.*\([0-9]\{4\}\.[0-9]\{2\}\.[0-9]\{2\}\).*/\1/p'| sed 's/\./-/g')
if [ $(date -d $DATE +"%Y%m%d") -ge $(date -d $INDEX_DATE +"%Y%m%d") ]; then
echo `date "+[%Y-%m-%d %H:%M] "`" Deleting index: $index." >> $LOGFILE
curl -s -XDELETE "$ELASTICSEARCH/$index/" >> $LOGFILE
fi
fi
done
exit 0
elasticsearch-remove-old-indices.sh
#!/bin/bash
# elasticsearch-remove-old-indices.sh
#
# Delete logstash format indices from elasticsearch maintaining only a
# specified number.
# http://logstash.net
# http://www.elasticsearch.org
#
# Inspiration:
# http://tech.superhappykittymeow.com/?p=296
#
# Must have access to the specified elasticsearch node.
usage()
{
cat << EOF
elasticsearch-remove-old-indices.sh
Compares the current list of indices to a configured value and deletes any
indices surpassing that value. Sort is lexicographical; the first n of a 'sort
-r' list are kept, all others are deleted.
USAGE: ./elasticsearch-remove-old-indices.sh [OPTIONS]
OPTIONS:
-h Show this message
-i Indices to keep (default: 14)
-e Elasticsearch URL (default: http://localhost:9200)
-g Consistent index name (default: logstash)
-o Output actions to a specified file
EXAMPLES:
./elasticsearch-remove-old-indices.sh
Connect to http://localhost:9200 and get a list of indices matching
'logstash'. Keep the top lexicographical 14 indices, delete any others.
./elasticsearch-remove-old-indices.sh -e "http://es.example.com:9200" \
-i 28 -g my-logs -o /mnt/es/logfile.log
Connect to http://es.example.com:9200 and get a list of indices matching
'my-logs'. Keep the top 28 indices, delete any others. When using a custom
index naming scheme be sure that a 'sort -r' places the indices you want to
keep at the top of the list. Output index deletes to /mnt/es/logfile.log.
EOF
}
# Defaults
ELASTICSEARCH="http://localhost:9200"
KEEP=14
GREP="logstash"
LOGFILE=/dev/null
# Validate numeric values
RE_D="^[0-9]+$"
while getopts ":i:e:g:o:h" flag
do
case "$flag" in
h)
usage
exit 0
;;
i)
if [[ $OPTARG =~ $RE_D ]]; then
KEEP=$OPTARG
else
ERROR="${ERROR}Indexes to keep must be an integer.\n"
fi
;;
e)
ELASTICSEARCH=$OPTARG
;;
g)
GREP=$OPTARG
;;
o)
LOGFILE=$OPTARG
;;
?)
usage
exit 1
;;
esac
done
# If we have errors, show the errors with usage data and exit.
if [ -n "$ERROR" ]; then
echo -e $ERROR
usage
exit 1
fi
# Get the indices from elasticsearch
INDICES_TEXT=`curl -s "$ELASTICSEARCH/_cat/indices?v" | awk '/'$GREP'/{match($0, /[:blank]*('$GREP'.[^ ]+)[:blank]*/, m); print m[1];}' | sort -r`
if [ -z "$INDICES_TEXT" ]; then
echo "No indices returned containing '$GREP' from $ELASTICSEARCH."
exit 1
fi
# If we are logging, make sure we have a logfile TODO - handle errors here
touch $LOGFILE
# Delete indices
declare -a INDEX=($INDICES_TEXT)
if [ ${#INDEX[@]} -gt $KEEP ]; then
for index in ${INDEX[@]:$KEEP};do
# We don't want to accidentally delete everything
if [ -n "$index" ]; then
echo `date "+[%Y-%m-%d %H:%M] "`" Deleting index: $index." >> $LOGFILE
curl -s -XDELETE "$ELASTICSEARCH/$index/" >> $LOGFILE
fi
done
fi
exit 0
elasticsearch-restore-index.sh
#!/bin/bash
#
# elasticsearch-restore-index.sh
#
# Retrieve a specified logstash index from s3 and restore with an accompanying
# restore script.
# http://logstash.net
# http://www.elasticsearch.org
# https://github.com/s3tools/s3cmd | http://s3tools.org/s3cmd
#
# Inspiration:
# http://tech.superhappykittymeow.com/?p=296
#
# Must run on an elasticsearch node with data, the restore script restarts
# elasticsearch.
usage()
{
cat << EOF
elasticsearch-restore-index.sh
USAGE: ./elasticsearch-restore-index.sh -b S3_BUCKET [OPTIONS]
OPTIONS:
-h Show this message
-b S3 path for backups (Required)
-i Elasticsearch index directory (Required)
-d Date to retrieve (Required, format: YYYY.mm.dd)
-t Temporary directory for download and extract (default: /tmp)
-c Command for s3cmd (default: s3cmd get)
-e Elasticsearch URL (default: http://localhost:9200)
-n How nice tar must be (default: 19)
EXAMPLES:
./elasticsearch-restore-index.sh -b "s3://someBucket" -i /mnt/es/data/nodes/0/indices \
-d "2013.05.01"
Get the backup and restore script for the 2013.05.01 index from this s3
bucket and restore the index to the provided elasticsearch index directory.
EOF
}
if [ "$USER" != 'root' ] && [ "$LOGNAME" != 'root' ]; then
# I don't want to troubleshoot the permissions of others
echo "This script must be run as root."
exit 1
fi
# Defaults
S3CMD="s3cmd get"
ELASTICSEARCH="http://localhost:9200"
NICE=19
TMP_DIR="/tmp"
while getopts ":b:i:t:d:c:e:n:h" flag
do
case "$flag" in
h)
usage
exit 0
;;
b)
S3_BASE=$OPTARG
;;
i)
INDEX_DIR=$OPTARG
;;
t)
TMP_DIR=$OPTARG
;;
d)
DATE=$OPTARG
;;
c)
S3CMD=$OPTARG
;;
e)
ELASTICSEARCH=$OPTARG
;;
n)
if [[ $OPTARG =~ $RE_D ]]; then
NICE=$OPTARG
fi
# If nice is not an integer, just use default
;;
?)
usage
exit 1
;;
esac
done
# We need an S3 base path
if [ -z "$S3_BASE" ]; then
ERROR="${ERROR}Please provide an s3 bucket and path with -b.\n"
fi
# We need an elasticsearch index directory
if [ -z "INDEX_DIR" ]; then
ERROR="${ERROR}Please provide an Elasticsearch index directory with -i.\n"
fi
# We need a date to restore
if [ -z "$DATE" ]; then
ERROR="${ERROR}Please provide a date for restoration with -d.\n"
fi
# If we have errors, show the errors with usage data and exit.
if [ -n "$ERROR" ]; then
echo -e $ERROR
usage
exit 1
fi
# Default logstash index naming is hardcoded, as are YYYY-mm container directories.
INDEX="logstash-$DATE"
YEARMONTH=${DATE//\./-}
YEARMONTH=${YEARMONTH:0:7}
S3_TARGET="$S3_BASE/$YEARMONTH"
# Get archive and execute the restore script. TODO check file existence first
$S3CMD $S3_TARGET/$INDEX.tgz $TMP_DIR/$INDEX.tgz
$S3CMD $S3_TARGET/$INDEX-restore.sh $TMP_DIR/$INDEX-restore.sh
if [ -f $TMP_DIR/$INDEX-restore.sh ]; then
chmod 750 $TMP_DIR/$INDEX-restore.sh
$TMP_DIR/$INDEX-restore.sh
# cleanup tmp files
rm $TMP_DIR/$INDEX.tgz
rm $TMP_DIR/$INDEX-restore.sh
else
echo "Unable to find restore script, does that backup exist?"
exit 1
fi
exit 0
es-backup-index.sh
#!/bin/bash
# by Serge D 2015 [email protected]
# This is a wrapper script for daily run
# i.e. you can run it by cron as follows
## m h dom mon dow command
# 11 4 * * * /opt/es/es-backup-index.sh >> /var/log/elasticsearch/esindexbackup.log
# Assuming you have the scripts inside '/opt/es/' folder. Or adjust the path to your taste.
#
# Set your system realities here
S3URL="s3://elasticsearch-backups"
ESDATA="/mnt/disk2/es/data/elasticsearch/nodes/0/indices/"
DAYS=7
# Read through all the available ES indices and generate a list of unique index names
# then proceed on all the indices
for i in `ls -1 $ESDATA | sed -r -e 's/-+[0-9]{4}\.[0-9]{2}\.[0-9]{2}$//' | uniq` ; do
echo -n " *** Daily index backup for index name '$i' begin: "
date
/opt/es/elasticsearch-backup-index.sh -b $S3URL -i $ESDATA -g $i
echo -n " *** Close indices for index name '$i' which are > $DAYS days old : "
date
/opt/es/elasticsearch-close-old-indices.sh -i $DAYS -g $i
echo -n " *** Delete indices for index name '$i' which are > $DAYS days old : "
date
/opt/es/elasticsearch-remove-old-indices.sh -i $DAYS -g $i
echo " ==== Done for index name '$i' ==== "
echo " "
done
echo -n " ******* FINISHED : "
date