Elasticsearch顶尖高手系列:高手进阶篇(二)

  • 第59-72节
    • 59_数据建模_关系型与document类型数据模型对比
    • 60_数据建模_通过应用层join实现用户与博客的关联
    • 61_数据建模_通过数据冗余实现用户与博客的关联
    • 62_数据建模_对每个用户发表的博客进行分组top_hits
    • 63_数据建模_对文件系统进行数据建模以及文件搜索
    • 64_数据建模_全局锁实现悲观锁并发控制
    • 65_数据建模_document锁实现悲观锁并发控制
    • 66_数据建模_共享锁和排他锁实现悲观锁并发控制
    • 67_数据建模_nested嵌套查询
    • 68_数据建模_nested嵌套聚合
    • 69_数据建模_父子关系建模
    • 70_数据建模_父子关系查询
    • 71_数据建模_父子关系聚合
    • 72_数据建模_父子关系祖孙三层练习
  • 第73-81节
    • 73_进阶_term vector探查
    • 74_进阶_highlight高亮
    • 75_进阶_search template搜索模板
    • 76_进阶_completion suggest搜索提示
    • 77_进阶_dynamic_templates动态映射模板定制自己的映射策略
    • 78_进阶_geo point地理位置数据类型
    • 79_进阶_酒店o2o搜索案例以及搜索指定区域内的酒店
    • 80_进阶_搜索距离当前位置一定范围内的酒店
    • 81_进阶_统计当前位置每个距离范围内有多少家酒店
  • 第82-92节
    • 82_Java API_client集群自动探查以及汽车零售店案例背景
    • 83_Java API_基于upsert实现汽车最新价格的调整
    • 84_Java API_mget实现多辆汽车的配置与价格对比
    • 85_Java API_基于bulk实现多4S店销售数据批量上传
    • 86_Java API_基于scroll实现月度销售数据批量下载
    • 87_Java API_基于search template实现按品牌分页查询模板
    • 88_Java API_对汽车品牌进行全文检索、精准查询和前缀搜索
    • 89_Java API_对汽车品牌进行多种条件的组合搜索
    • 90_Java API_基于地理位置对周围汽车4S店进行搜索
    • 91_Java API_如何自己尝试API以掌握所有搜索和聚合的语法
    • 92_快速入门篇以及高手进阶篇课程总结,以及后续阶段课程介绍
  • 93-103节
    • 93_es生产集群部署之硬件配置、jvm以及集群规划建议

第59-72节

59_数据建模_关系型与document类型数据模型对比

关系型数据库的数据模型

es的document数据模型

public class Department {private Integer deptId;private String name;private String desc;private List<Employee> employees;}public class Employee {private Integer empId;private String name;private Integer age;private String gender;private Department dept;}

关系型数据库中

department表

dept_id
name
desc

employee表

emp_id
name
age
gender
dept_id

三范式 --> 将每个数据实体拆分为一个独立的数据表,同时使用主外键关联关系将多个数据表关联起来 --> 确保没有任何冗余的数据

一份数据,只会放在一个数据表中 --> dept name,部门名称,就只会放在department表中,不会在employee表中也放一个dept name,如果说你要查看某个员工的部门名称,那么必须通过员工表中的外键,dept_id,找到在部门表中对应的记录,然后找到部门名称

es文档数据模型

{"deptId": "1","name": "研发部门","desc": "负责公司的所有研发项目","employees": [{"empId": "1","name": "张三","age": 28,"gender": "男"},{"empId": "2","name": "王兰","age": 25,"gender": "女"},{"empId": "3","name": "李四","age": 34,"gender": "男"}]
}

es,更加类似于面向对象的数据模型,将所有由关联关系的数据,放在一个doc json类型数据中,整个数据的关系,还有完整的数据,都放在了一起

60_数据建模_通过应用层join实现用户与博客的关联

1、构造用户与博客数据

在构造数据模型的时候,还是将有关联关系的数据,然后分割为不同的实体,类似于关系型数据库中的模型

案例背景:博客网站, 我们会模拟各种用户发表各种博客,然后针对用户和博客之间的关系进行数据建模,同时针对建模好的数据执行各种搜索/聚合的操作

PUT /website/users/1
{"name":     "小鱼儿","email":    "xiaoyuer@sina.com","birthday":      "1980-01-01"
}PUT /website/blogs/1
{"title":    "我的第一篇博客","content":     "这是我的第一篇博客,开通啦!!!""userId":     1
}

一个用户对应多个博客,一对多的关系,做了建模

建模方式,分割实体,类似三范式的方式,用主外键关联关系,将多个实体关联起来

2、搜索小鱼儿发表的所有博客

GET /website/users/_search
{"query": {"term": {"name.keyword": {"value": "小鱼儿"}}}
}
{"took": 91,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 0.2876821,"hits": [{"_index": "website","_type": "users","_id": "1","_score": 0.2876821,"_source": {"name": "小鱼儿","email": "xiaoyuer@sina.com","birthday": "1980-01-01"}}]}
}

比如这里搜索的是,1万个用户的博客,可能第一次搜索,会得到1万个userId

GET /website/blogs/_search
{"query": {"constant_score": {"filter": {"terms": {"userId": [1]}}}}
}

第二次搜索的时候,要放入terms中1万个userId,才能进行搜索,这个时候性能比较差了

{"took": 4,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "website","_type": "blogs","_id": "1","_score": 1,"_source": {"title": "小鱼儿的第一篇博客","content": "大家好,我是小鱼儿,这是我写的第一篇博客!","userId": 1}}]}
}

上面的操作,就属于应用层的join,在应用层先查出一份数据,然后再查出一份数据,进行关联

3、优点和缺点

优点:数据不冗余,维护方便
缺点:应用层join,如果关联数据过多,导致查询过大,性能很差

61_数据建模_通过数据冗余实现用户与博客的关联

1、构造冗余的用户和博客数据

第二种建模方式:用冗余数据,采用文档数据模型,进行数据建模,实现用户和博客的关联

PUT /website/users/1
{"name":     "小鱼儿","email":    "xiaoyuer@sina.com","birthday":      "1980-01-01"
}PUT /website/blogs/1
{"title": "小鱼儿的第一篇博客","content": "大家好,我是小鱼儿。。。","userInfo": {"userId": 1,"username": "小鱼儿"}
}

冗余数据,就是说,将可能会进行搜索的条件和要搜索的数据,放在一个doc中

2、基于冗余用户数据搜索博客

GET /website/blogs/_search
{"query": {"term": {"userInfo.username.keyword": {"value": "小鱼儿"}}}
}

就不需要走应用层的join,先搜一个数据,找到id,再去搜另一份数据

直接走一个有冗余数据的type即可,指定要的搜索条件,即可搜索出自己想要的数据来

3、优点和缺点

优点:性能高,不需要执行两次搜索
缺点:数据冗余,维护成本高 --> 每次如果你的username变化了,同时要更新user type和blog type

一般来说,对于es这种NoSQL类型的数据存储来讲,都是冗余模式…
当然,你要去维护数据的关联关系,也是很有必要的,所以一旦出现冗余数据的修改,必须记得将所有关联的数据全部更新

62_数据建模_对每个用户发表的博客进行分组top_hits

1、构造更多测试数据

PUT /website/users/3
{"name": "黄药师","email": "huangyaoshi@sina.com","birthday": "1970-10-24"
}PUT /website/blogs/3
{"title": "我是黄药师","content": "我是黄药师啊,各位同学们!!!","userInfo": {"userId": 1,"userName": "黄药师"}
}
PUT /website/users/2
{"name": "花无缺","email": "huawuque@sina.com","birthday": "1980-02-02"
}PUT /website/blogs/4
{"title": "花无缺的身世揭秘","content": "大家好,我是花无缺,所以我的身世是。。。","userInfo": {"userId": 2,"userName": "花无缺"}
}

2、对每个用户发表的博客进行分组

比如说,小鱼儿发表的那些博客,花无缺发表了哪些博客,黄药师发表了哪些博客

GET /website/blogs/_search
{"size": 0, "aggs": {"group_by_username": {"terms": {"field": "userInfo.username.keyword"},"aggs": {"top_blogs": {"top_hits": {"_source": {"include": "title"}, "size": 5}}}}}
}

63_数据建模_对文件系统进行数据建模以及文件搜索

数据建模,对类似文件系统这种的有多层级关系的数据进行建模

1、文件系统数据构造

PUT /fs
{"settings": {"analysis": {"analyzer": {"paths": { "tokenizer": "path_hierarchy"}}}}
}

path_hierarchy tokenizer讲解

/a/b/c/d --> path_hierarchy -> /a/b/c/d, /a/b/c, /a/b, /a

fs: filesystem

PUT /fs/_mapping/file
{"properties": {"name": { "type":  "keyword"},"path": { "type":  "keyword","fields": {"tree": { "type":     "text","analyzer": "paths"}}}}
}
PUT /fs/file/1
{"name":     "README.txt", "path":     "/workspace/projects/helloworld", "contents": "这是我的第一个elasticsearch程序"
}

2、对文件系统执行搜索

文件搜索需求:查找一份,内容包括elasticsearch,在/workspace/projects/hellworld这个目录下的文件

GET /fs/file/_search
{"query": {"bool": {"must": [{"match": {"contents": "elasticsearch"}},{"constant_score": {"filter": {"term": {"path": "/workspace/projects/helloworld"}}}}]}}
}
{"took": 2,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1.284885,"hits": [{"_index": "fs","_type": "file","_id": "1","_score": 1.284885,"_source": {"name": "README.txt","path": "/workspace/projects/helloworld","contents": "这是我的第一个elasticsearch程序"}}]}
}

搜索需求2:搜索/workspace目录下,内容包含elasticsearch的所有的文件

/workspace/projects/helloworld doc1
/workspace/projects doc1
/workspace doc1

GET /fs/file/_search
{"query": {"bool": {"must": [{"match": {"contents": "elasticsearch"}},{"constant_score": {"filter": {"term": {"path.tree": "/workspace"}}}}]}}
}

64_数据建模_全局锁实现悲观锁并发控制

课程大纲

1、悲观锁的简要说明

基于version的乐观锁并发控制

在数据建模,结合文件系统建模的这个案例,把悲观锁的并发控制,3种锁粒度,都给大家仔细讲解一下

最粗的一个粒度,全局锁

/workspace/projects/helloworld

如果多个线程,都过来,要并发地给/workspace/projects/helloworld下的README.txt修改文件名

实际上要进行并发的控制,避免出现多线程的并发安全问题,比如多个线程修改,纯并发,先执行的修改操作被后执行的修改操作给覆盖了

get current version

带着这个current version去执行修改,如果一旦发现数据已经被别人给修改了,version号跟之前自己获取的已经不一样了; 那么必须重新获取新的version号再次尝试修改

上来就尝试给这条数据加个锁,然后呢,此时就只有你能执行各种各样的操作了,其他人不能执行操作

第一种锁:全局锁,直接锁掉整个fs index

2、全局锁的上锁实验

PUT /fs/lock/global/_create
{}

fs: 你要上锁的那个index
lock: 就是你指定的一个对这个index上全局锁的一个type
global: 就是你上的全局锁对应的这个doc的id
_create:强制必须是创建,如果/fs/lock/global这个doc已经存在,那么创建失败,报错

利用了doc来进行上锁

/fs/lock/global /index/type/id --> doc{"_index": "fs","_type": "lock","_id": "global","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}

另外一个线程同时尝试上锁

PUT /fs/lock/global/_create
{}
{"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[lock][global]: version conflict, document already exists (current version [1])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "2","index": "fs"}],"type": "version_conflict_engine_exception","reason": "[lock][global]: version conflict, document already exists (current version [1])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "2","index": "fs"},"status": 409
}

如果失败,就再次重复尝试上锁

执行各种操作。。。

POST /fs/file/1/_update
{"doc": {"name": "README1.txt"}
}{"_index": "fs","_type": "file","_id": "1","_version": 2,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0}
}
DELETE /fs/lock/global{"found": true,"_index": "fs","_type": "lock","_id": "global","_version": 2,"result": "deleted","_shards": {"total": 2,"successful": 1,"failed": 0}
}

另外一个线程,因为之前发现上锁失败,反复尝试重新上锁,终于上锁成功了,因为之前获取到全局锁的那个线程已经delete /fs/lock/global全局锁了

PUT /fs/lock/global/_create
{}
{"_index": "fs","_type": "lock","_id": "global","_version": 3,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"created": true
}
POST /fs/file/1/_update
{"doc": {"name": "README.txt"}
}
{"_index": "fs","_type": "file","_id": "1","_version": 3,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0}
}
DELETE /fs/lock/global

3、全局锁的优点和缺点

优点:操作非常简单,非常容易使用,成本低
缺点:你直接就把整个index给上锁了,这个时候对index中所有的doc的操作,都会被block住,导致整个系统的并发能力很低

上锁解锁的操作不是频繁,然后每次上锁之后,执行的操作的耗时不会太长,用这种方式,方便

65_数据建模_document锁实现悲观锁并发控制

课程大纲

1、对document level锁,详细的讲解

全局锁,一次性就锁整个index,对这个index的所有增删改操作都会被block住,如果上锁不频繁,还可以,比较简单

细粒度的一个锁,document锁,顾名思义,每次就锁你要操作的,你要执行增删改的那些doc,doc锁了,其他线程就不能对这些doc执行增删改操作了
但是你只是锁了部分doc,其他线程对其他的doc还是可以上锁和执行增删改操作的

document锁,是用脚本进行上锁

POST /fs/lock/1/_update
{"upsert": { "process_id": 123 },"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';""params": {"process_id": 123}
}

/fs/lock,是固定的,就是说fs下的lock type,专门用于进行上锁
/fs/lock/id,比如1,id其实就是你要上锁的那个doc的id,代表了某个doc数据对应的lock(也是一个doc)
_update + upsert:执行upsert操作

params,里面有个process_id,process_id,是你的要执行增删改操作的进程的唯一id,比如说可以在java系统,启动的时候,给你的每个线程都用UUID自动生成一个thread id,你的系统进程启动的时候给整个进程也分配一个UUID。process_id + thread_id就代表了某一个进程下的某个线程的唯一标识。可以自己用UUID生成一个唯一ID

process_id很重要,会在lock中,设置对对应的doc加锁的进程的id,这样其他进程过来的时候,才知道,这条数据已经被别人给锁了

assert false,不是当前进程加锁的话,则抛出异常
ctx.op=‘noop’,不做任何修改

如果该document之前没有被锁,/fs/lock/1之前不存在,也就是doc id=1没有被别人上过锁; upsert的语法,那么执行index操作,创建一个/fs/lock/id这条数据,而且用params中的数据作为这个lock的数据。process_id被设置为123,script不执行。这个时候象征着process_id=123的进程已经锁了一个doc了。

如果document被锁了,就是说/fs/lock/1已经存在了,代表doc id=1已经被某个进程给锁了。那么执行update操作,script,此时会比对process_id,如果相同,就是说,某个进程,之前锁了这个doc,然后这次又过来,就可以直接对这个doc执行操作,说明是该进程之前锁的doc,则不报错,不执行任何操作,返回success; 如果process_id比对不上,说明doc被其他doc给锁了,此时报错

/fs/lock/1
{"process_id": 123
}POST /fs/lock/1/_update
{"upsert": { "process_id": 123 },"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';""params": {"process_id": 123}
}

script:ctx._source.process_id,123
process_id:加锁的upsert请求中带过来额proess_id

如果两个process_id相同,说明是一个进程先加锁,然后又过来尝试加锁,可能是要执行另外一个操作,此时就不会block,对同一个process_id是不会block,ctx.op= ‘noop’,什么都不做,返回一个success

如果说已经有一个进程加了锁了

/fs/lock/1
{"process_id": 123
}POST /fs/lock/1/_update
{"upsert": { "process_id": 123 },"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';""params": {"process_id": 234}
}
"script": "if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';"

ctx._source.process_id:123
process_id: 234

process_id不相等,说明这个doc之前已经被别人上锁了,process_id=123上锁了; process_id=234过来再次尝试上锁,失败,assert false,就会报错

此时遇到报错的process,就应该尝试重新上锁,直到上锁成功

有报错的话,如果有些doc被锁了,那么需要重试

直到所有锁定都成功,执行自己的操作。。。

释放所有的锁

2、上document锁的完整实验过程

scripts/judge-lock.groovy: if ( ctx._source.process_id != process_id ) { assert false }; ctx.op = 'noop';
POST /fs/lock/1/_update
{"upsert": { "process_id": 123 },"script": {"lang": "groovy","file": "judge-lock", "params": {"process_id": 123}}
}{"_index": "fs","_type": "lock","_id": "1","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0}
}
GET /fs/lock/1{"_index": "fs","_type": "lock","_id": "1","_version": 1,"found": true,"_source": {"process_id": 123}
}
POST /fs/lock/1/_update
{"upsert": { "process_id": 234 },"script": {"lang": "groovy","file": "judge-lock", "params": {"process_id": 234}}
}
{"error": {"root_cause": [{"type": "remote_transport_exception","reason": "[4onsTYV][127.0.0.1:9300][indices:data/write/update[s]]"}],"type": "illegal_argument_exception","reason": "failed to execute script","caused_by": {"type": "script_exception","reason": "error evaluating judge-lock","caused_by": {"type": "power_assertion_error","reason": "assert false\n"},"script_stack": [],"script": "","lang": "groovy"}},"status": 400
}
POST /fs/lock/1/_update
{"upsert": { "process_id": 123 },"script": {"lang": "groovy","file": "judge-lock", "params": {"process_id": 123}}
}
{"_index": "fs","_type": "lock","_id": "1","_version": 1,"result": "noop","_shards": {"total": 0,"successful": 0,"failed": 0}
}
POST /fs/file/1/_update
{"doc": {"name": "README1.txt"}
}
{"_index": "fs","_type": "file","_id": "1","_version": 4,"result": "updated","_shards": {"total": 2,"successful": 1,"failed": 0}
}
POST /fs/_refresh GET /fs/lock/_search?scroll=1m
{"query": {"term": {"process_id": 123}}
}{"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACPkFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAj5RY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAAI-YWNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACPnFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAj6BY0b25zVFlWWlRqR3ZJajlfc3BXejJ3","took": 51,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "fs","_type": "lock","_id": "1","_score": 1,"_source": {"process_id": 123}}]}
}PUT /fs/lock/_bulk
{ "delete": { "_id": 1}}{"took": 20,"errors": false,"items": [{"delete": {"found": true,"_index": "fs","_type": "lock","_id": "1","_version": 2,"result": "deleted","_shards": {"total": 2,"successful": 1,"failed": 0},"status": 200}}]
}POST /fs/lock/1/_update
{"upsert": { "process_id": 234 },"script": {"lang": "groovy","file": "judge-lock", "params": {"process_id": 234}}
}

process_id=234上锁就成功了

66_数据建模_共享锁和排他锁实现悲观锁并发控制

课程大纲

1、共享锁和排他锁的说明

共享锁:这份数据是共享的,然后多个线程过来,都可以获取同一个数据的共享锁,然后对这个数据执行读操作
排他锁:是排他的操作,只能一个线程获取排他锁,然后执行增删改操作

读写锁的分离

如果只是要读取数据的话,那么任意个线程都可以同时进来然后读取数据,每个线程都可以上一个共享锁
但是这个时候,如果有线程要过来修改数据,那么会尝试上排他锁,排他锁会跟共享锁互斥,也就是说,如果有人已经上了共享锁了,那么排他锁就不能上,就得等

如果有人在读数据,就不允许别人来修改数据

反之,也是一样的

如果有人在修改数据,就是加了排他锁
那么其他线程过来要修改数据,也会尝试加排他锁,此时会失败,锁冲突,必须等待,同时只能有一个线程修改数据
如果有人过来同时要读取数据,那么会尝试加共享锁,此时会失败,因为共享锁和排他锁是冲突的

如果有在修改数据,就不允许别人来修改数据,也不允许别人来读取数据

2、共享锁和排他锁的实验

第一步:有人在读数据,其他人也能过来读数据

judge-lock-2.groovy: if (ctx._source.lock_type == ‘exclusive’) { assert false }; ctx._source.lock_count++

POST /fs/lock/1/_update
{"upsert": { "lock_type":  "shared","lock_count": 1},"script": {"lang": "groovy","file": "judge-lock-2"}
}POST /fs/lock/1/_update
{"upsert": { "lock_type":  "shared","lock_count": 1},"script": {"lang": "groovy","file": "judge-lock-2"}
}
GET /fs/lock/1{"_index": "fs","_type": "lock","_id": "1","_version": 3,"found": true,"_source": {"lock_type": "shared","lock_count": 3}
}

就给大家模拟了,有人上了共享锁,你还是要上共享锁,直接上就行了,没问题,只是lock_count加1

2、已经有人上了共享锁,然后有人要上排他锁

PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }

排他锁用的不是upsert语法,create语法,要求lock必须不能存在,直接自己是第一个上锁的人,上的是排他锁

{"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[lock][1]: version conflict, document already exists (current version [3])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "3","index": "fs"}],"type": "version_conflict_engine_exception","reason": "[lock][1]: version conflict, document already exists (current version [3])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "3","index": "fs"},"status": 409
}

如果已经有人上了共享锁,明显/fs/lock/1是存在的,create语法去上排他锁,肯定会报错

3、对共享锁进行解锁

POST /fs/lock/1/_update
{"script": {"lang": "groovy","file": "unlock-shared"}
}

连续解锁3次,此时共享锁就彻底没了

每次解锁一个共享锁,就对lock_count先减1,如果减了1之后,是0,那么说明所有的共享锁都解锁完了,此时就就将/fs/lock/1删除,就彻底解锁所有的共享锁

3、上排他锁,再上排他锁

PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }

其他线程

PUT /fs/lock/1/_create
{ "lock_type": "exclusive" }
{"error": {"root_cause": [{"type": "version_conflict_engine_exception","reason": "[lock][1]: version conflict, document already exists (current version [7])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "3","index": "fs"}],"type": "version_conflict_engine_exception","reason": "[lock][1]: version conflict, document already exists (current version [7])","index_uuid": "IYbj0OLGQHmMUpLfbhD4Hw","shard": "3","index": "fs"},"status": 409
}

4、上排他锁,上共享锁

POST /fs/lock/1/_update
{"upsert": { "lock_type":  "shared","lock_count": 1},"script": {"lang": "groovy","file": "judge-lock-2"}
}
{"error": {"root_cause": [{"type": "remote_transport_exception","reason": "[4onsTYV][127.0.0.1:9300][indices:data/write/update[s]]"}],"type": "illegal_argument_exception","reason": "failed to execute script","caused_by": {"type": "script_exception","reason": "error evaluating judge-lock-2","caused_by": {"type": "power_assertion_error","reason": "assert false\n"},"script_stack": [],"script": "","lang": "groovy"}},"status": 400
}

5、解锁排他锁

DELETE /fs/lock/1

67_数据建模_nested嵌套查询

1、做一个实验,引出来为什么需要nested object

冗余数据方式的来建模,其实用的就是object类型,我们这里又要引入一种新的object类型,nested object类型

博客,评论,做的这种数据模型

PUT /website/blogs/6
{"title": "花无缺发表的一篇帖子","content":  "我是花无缺,大家要不要考虑一下投资房产和买股票的事情啊。。。","tags":  [ "投资", "理财" ],"comments": [ {"name":    "小鱼儿","comment": "什么股票啊?推荐一下呗","age":     28,"stars":   4,"date":    "2016-09-01"},{"name":    "黄药师","comment": "我喜欢投资房产,风,险大收益也大","age":     31,"stars":   5,"date":    "2016-10-22"}]
}

被年龄是28岁的黄药师评论过的博客,搜索

GET /website/blogs/_search
{"query": {"bool": {"must": [{ "match": { "comments.name": "黄药师" }},{ "match": { "comments.age":  28      }} ]}}
}
{"took": 102,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1.8022683,"hits": [{"_index": "website","_type": "blogs","_id": "6","_score": 1.8022683,"_source": {"title": "花无缺发表的一篇帖子","content": "我是花无缺,大家要不要考虑一下投资房产和买股票的事情啊。。。","tags": ["投资","理财"],"comments": [{"name": "小鱼儿","comment": "什么股票啊?推荐一下呗","age": 28,"stars": 4,"date": "2016-09-01"},{"name": "黄药师","comment": "我喜欢投资房产,风,险大收益也大","age": 31,"stars": 5,"date": "2016-10-22"}]}}]}
}

结果是。。。好像不太对啊???

object类型数据结构的底层存储。。。

{"title":            [ "花无缺", "发表", "一篇", "帖子" ],"content":             [ "我", "是", "花无缺", "大家", "要不要", "考虑", "一下", "投资", "房产", "买", "股票", "事情" ],"tags":             [ "投资", "理财" ],"comments.name":    [ "小鱼儿", "黄药师" ],"comments.comment": [ "什么", "股票", "推荐", "我", "喜欢", "投资", "房产", "风险", "收益", "大" ],"comments.age":     [ 28, 31 ],"comments.stars":   [ 4, 5 ],"comments.date":    [ 2016-09-01, 2016-10-22 ]
}

object类型底层数据结构,会将一个json数组中的数据,进行扁平化

所以,直接命中了这个document,name=黄药师,age=28,正好符合

2、引入nested object类型,来解决object类型底层数据结构导致的问题

修改mapping,将comments的类型从object设置为nested

PUT /website
{"mappings": {"blogs": {"properties": {"comments": {"type": "nested", "properties": {"name":    { "type": "string"  },"comment": { "type": "string"  },"age":     { "type": "short"   },"stars":   { "type": "short"   },"date":    { "type": "date"    }}}}}}
}
{ "comments.name":    [ "小鱼儿" ],"comments.comment": [ "什么", "股票", "推荐" ],"comments.age":     [ 28 ],"comments.stars":   [ 4 ],"comments.date":    [ 2014-09-01 ]
}
{ "comments.name":    [ "黄药师" ],"comments.comment": [ "我", "喜欢", "投资", "房产", "风险", "收益", "大" ],"comments.age":     [ 31 ],"comments.stars":   [ 5 ],"comments.date":    [ 2014-10-22 ]
}
{ "title":            [ "花无缺", "发表", "一篇", "帖子" ],"body":             [ "我", "是", "花无缺", "大家", "要不要", "考虑", "一下", "投资", "房产", "买", "股票", "事情" ],"tags":             [ "投资", "理财" ]
}

再次搜索,成功了。。。

GET /website/blogs/_search
{"query": {"bool": {"must": [{"match": {"title": "花无缺"}},{"nested": {"path": "comments","query": {"bool": {"must": [{"match": {"comments.name": "黄药师"}},{"match": {"comments.age": 28}}]}}}}]}}
}

score_mode:max,min,avg,none,默认是avg

如果搜索命中了多个nested document,如何讲个多个nested document的分数合并为一个分数

68_数据建模_nested嵌套聚合

我们讲解一下基于nested object中的数据进行聚合分析

聚合数据分析的需求1:按照评论日期进行bucket划分,然后拿到每个月的评论的评分的平均值

GET /website/blogs/_search
{"size": 0, "aggs": {"comments_path": {"nested": {"path": "comments"}, "aggs": {"group_by_comments_date": {"date_histogram": {"field": "comments.date","interval": "month","format": "yyyy-MM"},"aggs": {"avg_stars": {"avg": {"field": "comments.stars"}}}}}}}
}
{"took": 52,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 2,"max_score": 0,"hits": []},"aggregations": {"comments_path": {"doc_count": 4,"group_by_comments_date": {"buckets": [{"key_as_string": "2016-08","key": 1470009600000,"doc_count": 1,"avg_stars": {"value": 3}},{"key_as_string": "2016-09","key": 1472688000000,"doc_count": 2,"avg_stars": {"value": 4.5}},{"key_as_string": "2016-10","key": 1475280000000,"doc_count": 1,"avg_stars": {"value": 5}}]}}}
}

ES操作之嵌套查询(nested)与退出嵌套(reverse_nested)操作

GET /website/blogs/_search
{"size": 0,"aggs": {"comments_path": {"nested": {"path": "comments"},"aggs": {"group_by_comments_age": {"histogram": {"field": "comments.age","interval": 10},"aggs": {"reverse_path": {"reverse_nested": {}, "aggs": {"group_by_tags": {"terms": {"field": "tags.keyword"}}}}}}}}}
}
{"took": 5,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 2,"max_score": 0,"hits": []},"aggregations": {"comments_path": {"doc_count": 4,"group_by_comments_age": {"buckets": [{"key": 20,"doc_count": 1,"reverse_path": {"doc_count": 1,"group_by_tags": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "投资","doc_count": 1},{"key": "理财","doc_count": 1}]}}},{"key": 30,"doc_count": 3,"reverse_path": {"doc_count": 2,"group_by_tags": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "大侠","doc_count": 1},{"key": "投资","doc_count": 1},{"key": "理财","doc_count": 1},{"key": "练功","doc_count": 1}]}}}]}}}
}

69_数据建模_父子关系建模

nested object的建模,有个不好的地方,就是采取的是类似冗余数据的方式,将多个数据都放在一起了,维护成本就比较高

parent child建模方式,采取的是类似于关系型数据库的三范式类的建模,多个实体都分割开来,每个实体之间都通过一些关联方式,进行了父子关系的关联,各种数据不需要都放在一起,父doc和子doc分别在进行更新的时候,都不会影响对方

一对多关系的建模,维护起来比较方便,而且我们之前说过,类似关系型数据库的建模方式,应用层join的方式,会导致性能比较差,因为做多次搜索。父子关系的数据模型,不会,性能很好。因为虽然数据实体之间分割开来,但是我们在搜索的时候,由es自动为我们处理底层的关联关系,并且通过一些手段保证搜索性能。

父子关系数据模型,相对于nested数据模型来说,优点是父doc和子doc互相之间不会影响

要点:父子关系元数据映射,用于确保查询时候的高性能,但是有一个限制,就是父子数据必须存在于一个shard中

父子关系数据存在一个shard中,而且还有映射其关联关系的元数据,那么搜索父子关系数据的时候,不用跨分片,一个分片本地自己就搞定了,性能当然高咯

案例背景:研发中心员工管理案例,一个IT公司有多个研发中心,每个研发中心有多个员工

PUT /company
{"mappings": {"rd_center": {},"employee": {"_parent": {"type": "rd_center" }}}
}

父子关系建模的核心,多个type之间有父子关系,用_parent指定父type

POST /company/rd_center/_bulk
{ "index": { "_id": "1" }}
{ "name": "北京研发总部", "city": "北京", "country": "中国" }
{ "index": { "_id": "2" }}
{ "name": "上海研发中心", "city": "上海", "country": "中国" }
{ "index": { "_id": "3" }}
{ "name": "硅谷人工智能实验室", "city": "硅谷", "country": "美国" }

shard路由的时候,id=1的rd_center doc,默认会根据id进行路由,到某一个shard

PUT /company/employee/1?parent=1
{"name":  "张三","birthday":   "1970-10-24","hobby": "爬山"
}

维护父子关系的核心,parent=1,指定了这个数据的父doc的id

此时,parent-child关系,就确保了说,父doc和子doc都是保存在一个shard上的。内部原理还是doc routing,employee和rd_center的数据,都会用parent id作为routing,这样就会到一个shard

就不会根据id=1的employee doc的id进行路由了,而是根据parent=1进行路由,会根据父doc的id进行路由,那么就可以通过底层的路由机制,保证父子数据存在于一个shard中

POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "1" }}
{ "name": "李四", "birthday": "1982-05-16", "hobby": "游泳" }
{ "index": { "_id": 3, "parent": "2" }}
{ "name": "王二", "birthday": "1979-04-01", "hobby": "爬山" }
{ "index": { "_id": 4, "parent": "3" }}
{ "name": "赵五", "birthday": "1987-05-11", "hobby": "骑马" }

70_数据建模_父子关系查询

我们已经建立了父子关系的数据模型之后,就要基于这个模型进行各种搜索和聚合了

1、搜索有1980年以后出生的员工的研发中心

GET /company/rd_center/_search
{"query": {"has_child": {"type": "employee","query": {"range": {"birthday": {"gte": "1980-01-01"}}}}}
}
{"took": 33,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 2,"max_score": 1,"hits": [{"_index": "company","_type": "rd_center","_id": "1","_score": 1,"_source": {"name": "北京研发总部","city": "北京","country": "中国"}},{"_index": "company","_type": "rd_center","_id": "3","_score": 1,"_source": {"name": "硅谷人工智能实验室","city": "硅谷","country": "美国"}}]}
}

2、搜索有名叫张三的员工的研发中心

GET /company/rd_center/_search
{"query": {"has_child": {"type":       "employee","query": {"match": {"name": "张三"}}}}
}
{"took": 2,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "company","_type": "rd_center","_id": "1","_score": 1,"_source": {"name": "北京研发总部","city": "北京","country": "中国"}}]}
}

3、min_children搜索有至少2个以上员工的研发中心

GET /company/rd_center/_search
{"query": {"has_child": {"type":         "employee","min_children": 2, "query": {"match_all": {}}}}
}
{"took": 5,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "company","_type": "rd_center","_id": "1","_score": 1,"_source": {"name": "北京研发总部","city": "北京","country": "中国"}}]}
}

4、搜索在中国的研发中心的员工

GET /company/employee/_search
{"query": {"has_parent": {"parent_type": "rd_center","query": {"term": {"country.keyword": "中国"}}}}
}{"took": 5,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 3,"max_score": 1,"hits": [{"_index": "company","_type": "employee","_id": "3","_score": 1,"_routing": "2","_parent": "2","_source": {"name": "王二","birthday": "1979-04-01","hobby": "爬山"}},{"_index": "company","_type": "employee","_id": "1","_score": 1,"_routing": "1","_parent": "1","_source": {"name": "张三","birthday": "1970-10-24","hobby": "爬山"}},{"_index": "company","_type": "employee","_id": "2","_score": 1,"_routing": "1","_parent": "1","_source": {"name": "李四","birthday": "1982-05-16","hobby": "游泳"}}]}
}

71_数据建模_父子关系聚合

统计每个国家的喜欢每种爱好的员工有多少个

GET /company/rd_center/_search
{"size": 0,"aggs": {"group_by_country": {"terms": {"field": "country.keyword"},"aggs": {"group_by_child_employee": {"children": {"type": "employee"},"aggs": {"group_by_hobby": {"terms": {"field": "hobby.keyword"}}}}}}}
}
{"took": 15,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 3,"max_score": 0,"hits": []},"aggregations": {"group_by_country": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "中国","doc_count": 2,"group_by_child_employee": {"doc_count": 3,"group_by_hobby": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "爬山","doc_count": 2},{"key": "游泳","doc_count": 1}]}}},{"key": "美国","doc_count": 1,"group_by_child_employee": {"doc_count": 1,"group_by_hobby": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 0,"buckets": [{"key": "骑马","doc_count": 1}]}}}]}}
}

72_数据建模_父子关系祖孙三层练习

父子关系,祖孙三层关系的数据建模,搜索

PUT /company
{"mappings": {"country": {},"rd_center": {"_parent": {"type": "country" }},"employee": {"_parent": {"type": "rd_center" }}}
}

country -> rd_center -> employee,祖孙三层数据模型

POST /company/country/_bulk
{ "index": { "_id": "1" }}
{ "name": "中国" }
{ "index": { "_id": "2" }}
{ "name": "美国" }
POST /company/rd_center/_bulk
{ "index": { "_id": "1", "parent": "1" }}
{ "name": "北京研发总部" }
{ "index": { "_id": "2", "parent": "1" }}
{ "name": "上海研发中心" }
{ "index": { "_id": "3", "parent": "2" }}
{ "name": "硅谷人工智能实验室" }
PUT /company/employee/1?parent=1&routing=1
{"name":  "张三","dob":   "1970-10-24","hobby": "爬山"
}

routing参数的讲解,必须跟grandparent相同,否则有问题

country,用的是自己的id去路由; rd_center,parent,用的是country的id去路由; employee,如果也是仅仅指定一个parent,那么用的是rd_center的id去路由,这就导致祖孙三层数据不会在一个shard上

孙子辈儿,要手动指定routing,指定为爷爷辈儿的数据的id

搜索有爬山爱好的员工所在的国家

GET /company/country/_search
{"query": {"has_child": {"type": "rd_center","query": {"has_child": {"type": "employee","query": {"match": {"hobby": "爬山"}}}}}}
}{"took": 10,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "company","_type": "country","_id": "1","_score": 1,"_source": {"name": "中国"}}]}
}

第73-81节

73_进阶_term vector探查

课程大纲

1、term vector介绍

获取document中的某个field内的各个term的统计信息

term information: term frequency in the field, term positions, start and end offsets, term payloads

term statistics: 设置term_statistics=true; total term frequency, 一个term在所有document中出现的频率; document frequency,有多少document包含这个term

field statistics: document count,有多少document包含这个field; sum of document frequency,一个field中所有term的df之和; sum of total term frequency,一个field中的所有term的tf之和

GET /twitter/tweet/1/_termvectors
GET /twitter/tweet/1/_termvectors?fields=text

term statistics和field statistics并不精准,不会被考虑有的doc可能被删除了

我告诉大家,其实很少用,用的时候,一般来说,就是你需要对一些数据做探查的时候。比如说,你想要看到某个term,某个词条,大话西游,这个词条,在多少个document中出现了。或者说某个field,film_desc,电影的说明信息,有多少个doc包含了这个说明信息。

2、index-iime term vector实验

term vector,涉及了很多的term和field相关的统计信息,有两种方式可以采集到这个统计信息

(1)index-time,你在mapping里配置一下,然后建立索引的时候,就直接给你生成这些term和field的统计信息了
(2)query-time,你之前没有生成过任何的Term vector信息,然后在查看term vector的时候,直接就可以看到了,会on the fly,现场计算出各种统计信息,然后返回给你

这一讲,不会手敲任何命令,直接copy我做好的命令,因为这一讲的重点,不是掌握什么搜索或者聚合的语法,而是说,掌握,如何采集term vector信息,然后如何看懂term vector信息,你能掌握利用term vector进行数据探查

PUT /my_index
{"mappings": {"my_type": {"properties": {"text": {"type": "text","term_vector": "with_positions_offsets_payloads","store" : true,"analyzer" : "fulltext_analyzer"},"fullname": {"type": "text","analyzer" : "fulltext_analyzer"}}}},"settings" : {"index" : {"number_of_shards" : 1,"number_of_replicas" : 0},"analysis": {"analyzer": {"fulltext_analyzer": {"type": "custom","tokenizer": "whitespace","filter": ["lowercase","type_as_payload"]}}}}
}
PUT /my_index/my_type/1
{"fullname" : "Leo Li","text" : "hello test test test "
}PUT /my_index/my_type/2
{"fullname" : "Leo Li","text" : "other hello test ..."
}
GET /my_index/my_type/1/_termvectors
{"fields" : ["text"],"offsets" : true,"payloads" : true,"positions" : true,"term_statistics" : true,"field_statistics" : true
}
{"_index": "my_index","_type": "my_type","_id": "1","_version": 1,"found": true,"took": 10,"term_vectors": {"text": {"field_statistics": {"sum_doc_freq": 6,"doc_count": 2,"sum_ttf": 8},"terms": {"hello": {"doc_freq": 2,"ttf": 2,"term_freq": 1,"tokens": [{"position": 0,"start_offset": 0,"end_offset": 5,"payload": "d29yZA=="}]},"test": {"doc_freq": 2,"ttf": 4,"term_freq": 3,"tokens": [{"position": 1,"start_offset": 6,"end_offset": 10,"payload": "d29yZA=="},{"position": 2,"start_offset": 11,"end_offset": 15,"payload": "d29yZA=="},{"position": 3,"start_offset": 16,"end_offset": 20,"payload": "d29yZA=="}]}}}}
}

3、query-time term vector实验

GET /my_index/my_type/1/_termvectors
{"fields" : ["fullname"],"offsets" : true,"positions" : true,"term_statistics" : true,"field_statistics" : true
}

一般来说,如果条件允许,你就用query time的term vector就可以了,你要探查什么数据,现场去探查一下就好了

4、手动指定doc的term vector

GET /my_index/my_type/_termvectors
{"doc" : {"fullname" : "Leo Li","text" : "hello test test test"},"fields" : ["text"],"offsets" : true,"payloads" : true,"positions" : true,"term_statistics" : true,"field_statistics" : true
}

手动指定一个doc,实际上不是要指定doc,而是要指定你想要安插的词条,hello test,那么就可以放在一个field中

将这些term分词,然后对每个term,都去计算它在现有的所有doc中的一些统计信息

这个挺有用的,可以让你手动指定要探查的term的数据情况,你就可以指定探查“大话西游”这个词条的统计信息

5、手动指定analyzer来生成term vector

GET /my_index/my_type/_termvectors
{"doc" : {"fullname" : "Leo Li","text" : "hello test test test"},"fields" : ["text"],"offsets" : true,"payloads" : true,"positions" : true,"term_statistics" : true,"field_statistics" : true,"per_field_analyzer" : {"text": "standard"}
}

6、terms filter

GET /my_index/my_type/_termvectors
{"doc" : {"fullname" : "Leo Li","text" : "hello test test test"},"fields" : ["text"],"offsets" : true,"payloads" : true,"positions" : true,"term_statistics" : true,"field_statistics" : true,"filter" : {"max_num_terms" : 3,"min_term_freq" : 1,"min_doc_freq" : 1}
}

这个就是说,根据term统计信息,过滤出你想要看到的term vector统计结果
也挺有用的,比如你探查数据把,可以过滤掉一些出现频率过低的term,就不考虑了

7、multi term vector

GET _mtermvectors
{"docs": [{"_index": "my_index","_type": "my_type","_id": "2","term_statistics": true},{"_index": "my_index","_type": "my_type","_id": "1","fields": ["text"]}]
}
GET /my_index/_mtermvectors
{"docs": [{"_type": "test","_id": "2","fields": ["text"],"term_statistics": true},{"_type": "test","_id": "1"}]
}GET /my_index/my_type/_mtermvectors
{"docs": [{"_id": "2","fields": ["text"],"term_statistics": true},{"_id": "1"}]
}GET /_mtermvectors
{"docs": [{"_index": "my_index","_type": "my_type","doc" : {"fullname" : "Leo Li","text" : "hello test test test"}},{"_index": "my_index","_type": "my_type","doc" : {"fullname" : "Leo Li","text" : "other hello test ..."}}]
}

74_进阶_highlight高亮

课程大纲

1、一个最基本的高亮例子

PUT /blog_website
{"mappings": {"blogs": {"properties": {"title": {"type": "text","analyzer": "ik_max_word"},"content": {"type": "text","analyzer": "ik_max_word"}}}}
}
PUT /blog_website/blogs/1
{"title": "我的第一篇博客","content": "大家好,这是我写的第一篇博客,特别喜欢这个博客网站!!!"
}
GET /blog_website/blogs/_search
{"query": {"match": {"title": "博客"}},"highlight": {"fields": {"title": {}}}
}
{"took": 103,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 0.28582606,"hits": [{"_index": "blog_website","_type": "blogs","_id": "1","_score": 0.28582606,"_source": {"title": "我的第一篇博客","content": "大家好,这是我写的第一篇博客,特别喜欢这个博客网站!!!"},"highlight": {"title": ["我的第一篇<em>博客</em>"]}}]}
}

表现,会变成红色,所以说你的指定的field中,如果包含了那个搜索词的话,就会在那个field的文本中,对搜索词进行红色的高亮显示

GET /blog_website/blogs/_search
{"query": {"bool": {"should": [{"match": {"title": "博客"}},{"match": {"content": "博客"}}]}},"highlight": {"fields": {"title": {},"content": {}}}
}

highlight中的field,必须跟query中的field一一对齐的

2、三种highlight介绍

2.1 plain highlight,lucene highlight,默认

2.2 posting highlight,index_options=offsets

(1)性能比plain highlight要高,因为不需要重新对高亮文本进行分词
(2)对磁盘的消耗更少
(3)将文本切割为句子,并且对句子进行高亮,效果更好

PUT /blog_website
{"mappings": {"blogs": {"properties": {"title": {"type": "text","analyzer": "ik_max_word"},"content": {"type": "text","analyzer": "ik_max_word","index_options": "offsets"}}}}
}
PUT /blog_website/blogs/1
{"title": "我的第一篇博客","content": "大家好,这是我写的第一篇博客,特别喜欢这个博客网站!!!"
}
GET /blog_website/blogs/_search
{"query": {"match": {"content": "博客"}},"highlight": {"fields": {"content": {}}}
}

2.3 fast vector highlight

index-time term vector设置在mapping中,就会用fast verctor highlight

(1)对大field而言(大于1mb),性能更高

PUT /blog_website
{"mappings": {"blogs": {"properties": {"title": {"type": "text","analyzer": "ik_max_word"},"content": {"type": "text","analyzer": "ik_max_word","term_vector" : "with_positions_offsets"}}}}
}

强制使用某种highlighter,比如对于开启了term vector的field而言,可以强制使用plain highlight

GET /blog_website/blogs/_search
{"query": {"match": {"content": "博客"}},"highlight": {"fields": {"content": {"type": "plain"}}}
}

总结一下,其实可以根据你的实际情况去考虑,一般情况下,用plain highlight也就足够了,不需要做其他额外的设置
如果对高亮的性能要求很高,可以尝试启用posting highlight
如果field的值特别大,超过了1M,那么可以用fast vector highlight

3、设置高亮html标签,默认是标签

GET /blog_website/blogs/_search
{"query": {"match": {"content": "博客"}},"highlight": {"pre_tags": ["<tag1>"],"post_tags": ["</tag2>"], "fields": {"content": {"type": "plain"}}}
}

4、高亮片段fragment的设置

GET /_search
{"query" : {"match": { "user": "kimchy" }},"highlight" : {"fields" : {"content" : {"fragment_size" : 150, "number_of_fragments" : 3, "no_match_size": 150 }}}
}

fragment_size: 你一个Field的值,比如有长度是1万,但是你不可能在页面上显示这么长啊。。。设置要显示出来的fragment文本判断的长度,默认是100
number_of_fragments:你可能你的高亮的fragment文本片段有多个片段,你可以指定就显示几个片段

75_进阶_search template搜索模板

课程大纲

搜索模板,search template,高级功能,就可以将我们的一些搜索进行模板化,然后的话,每次执行这个搜索,就直接调用模板,给传入一些参数就可以了

越高级的功能,越少使用,可能只有在你真的遇到特别合适的场景的时候,才会去使用某个高级功能。但是,这些高级功能你是否掌握,其实就是普通的es开发人员,和es高手之间的一个区别。高手,一般来说,会把一个技术掌握的特别好,特别全面,特别深入,也许他平时用不到这个技术,但是当真的遇到一定的场景的时候,高手可以基于自己的深厚的技术储备,立即反应过来,找到一个合适的解决方案。

如果是一个普通的技术人员,一般只会学习跟自己当前工作相关的一些知识和技术,只要求自己掌握的技术可以解决工作目前遇到的问题就可以了,就满足了,就会止步不前了,然后就不会更加深入的去学习一个技术。但是,当你的项目真正遇到问题的时候,遇到了一些难题,你之前的那一点技术储备已经没法去应付这些更加困难的问题了,此时,普通的技术人员就会扎耳挠腮,没有任何办法。

高手,对技术是很有追求,能够精通很多自己遇到过的技术,但是也许自己学的很多东西,自己都没用过,但是不要紧,这是你的一种技术储备。

1、search template入门

GET /blog_website/blogs/_search/template
{"inline" : {"query": { "match" : { "{{field}}" : "{{value}}" } }},"params" : {"field" : "title","value" : "博客"}
}
GET /blog_website/blogs/_search
{"query": { "match" : { "title" : "博客" } }
}

search template:"{{field}}" : “{{value}}”

2、toJson

GET /blog_website/blogs/_search/template
{"inline": "{\"query\": {\"match\": {{#toJson}}matchCondition{{/toJson}}}}","params": {"matchCondition": {"title": "博客"}}
}
GET /blog_website/blogs/_search
{"query": { "match" : { "title" : "博客" } }
}

3、join

GET /blog_website/blogs/_search/template
{"inline": {"query": {"match": {"title": "{{#join delimiter=' '}}titles{{/join delimiter=' '}}"}}},"params": {"titles": ["博客", "网站"]}
}

博客,网站

GET /blog_website/blogs/_search
{"query": { "match" : { "title" : "博客 网站" } }
}

4、default value

POST /blog_website/blogs/1/_update
{"doc": {"views": 5}
}
GET /blog_website/blogs/_search/template
{"inline": {"query": {"range": {"views": {"gte": "{{start}}","lte": "{{end}}{{^end}}20{{/end}}"}}}},"params": {"start": 1,"end": 10}
}
GET /blog_website/blogs/_search
{"query": {"range": {"views": {"gte": 1,"lte": 10}}}
}
GET /blog_website/blogs/_search/template
{"inline": {"query": {"range": {"views": {"gte": "{{start}}","lte": "{{end}}{{^end}}20{{/end}}"}}}},"params": {"start": 1}
}
GET /blog_website/blogs/_search
{"query": {"range": {"views": {"gte": 1,"lte": 20}}}
}

5、conditional

es的config/scripts目录下,预先保存这个复杂的模板,后缀名是.mustache,文件名是conditonal

{"query": {"bool": {"must": {"match": {"line": "{{text}}" }},"filter": {{{#line_no}} "range": {"line_no": {{{#start}} "gte": "{{start}}" {{#end}},{{/end}} {{/start}} {{#end}} "lte": "{{end}}" {{/end}} }}{{/line_no}} }}}
}
GET /my_index/my_type/_search {"took": 4,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "my_index","_type": "my_type","_id": "1","_score": 1,"_source": {"line": "我的博客","line_no": 5}}]}
}
GET /my_index/my_type/_search/template
{"file": "conditional","params": {"text": "博客","line_no": true,"start": 1,"end": 10}
}

6、保存search template

config/scripts,.mustache

提供一个思路

比如说,一般在大型的团队中,可能不同的人,都会想要执行一些类似的搜索操作
这个时候,有一些负责底层运维的一些同学,就可以基于search template,封装一些模板出来,然后是放在各个es进程的scripts目录下的
其他的团队,其实就不用各个团队自己反复手写复杂的通用的查询语句了,直接调用某个搜索模板,传入一些参数就好了

76_进阶_completion suggest搜索提示

课程大纲

suggest,completion suggest,自动完成,搜索推荐,搜索提示 --> 自动完成,auto completion

auto completion

比如说我们在百度,搜索,你现在搜索“大话西游” -->
百度,自动给你提示,“大话西游电影”,“大话西游小说”, “大话西游手游”

不用你把所有你想要输入的文本都输入完,搜索引擎会自动提示你可能是你想要搜索的那个文本

PUT /news_website
{"mappings": {"news" : {"properties" : {"title" : {"type": "text","analyzer": "ik_max_word","fields": {"suggest" : {"type" : "completion","analyzer": "ik_max_word"}}},"content": {"type": "text","analyzer": "ik_max_word"}}}}
}

completion,es实现的时候,是非常高性能的,会构建不是倒排索引,也不是正拍索引,就是纯的用于进行前缀搜索的一种特殊的数据结构,而且会全部放在内存中,所以auto completion进行的前缀搜索提示,性能是非常高的

大话西游

PUT /news_website/news/1
{"title": "大话西游电影","content": "大话西游的电影时隔20年即将在2017年4月重映"
}
PUT /news_website/news/2
{"title": "大话西游小说","content": "某知名网络小说作家已经完成了大话西游同名小说的出版"
}
PUT /news_website/news/3
{"title": "大话西游手游","content": "网易游戏近日出品了大话西游经典IP的手游,正在火爆内测中"
}
GET /news_website/news/_search
{"suggest": {"my-suggest" : {"prefix" : "大话西游","completion" : {"field" : "title.suggest"}}}
}
{"took": 6,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 0,"max_score": 0,"hits": []},"suggest": {"my-suggest": [{"text": "大话西游","offset": 0,"length": 4,"options": [{"text": "大话西游小说","_index": "news_website","_type": "news","_id": "2","_score": 1,"_source": {"title": "大话西游小说","content": "某知名网络小说作家已经完成了大话西游同名小说的出版"}},{"text": "大话西游手游","_index": "news_website","_type": "news","_id": "3","_score": 1,"_source": {"title": "大话西游手游","content": "网易游戏近日出品了大话西游经典IP的手游,正在火爆内测中"}},{"text": "大话西游电影","_index": "news_website","_type": "news","_id": "1","_score": 1,"_source": {"title": "大话西游电影","content": "大话西游的电影时隔20年即将在2017年4月重映"}}]}]}
}
GET /news_website/news/_search
{"query": {"match": {"content": "大话西游电影"}}
}
{"took": 9,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 3,"max_score": 1.3495269,"hits": [{"_index": "news_website","_type": "news","_id": "1","_score": 1.3495269,"_source": {"title": "大话西游电影","content": "大话西游的电影时隔20年即将在2017年4月重映"}},{"_index": "news_website","_type": "news","_id": "3","_score": 1.217097,"_source": {"title": "大话西游手游","content": "网易游戏近日出品了大话西游经典IP的手游,正在火爆内测中"}},{"_index": "news_website","_type": "news","_id": "2","_score": 1.1299736,"_source": {"title": "大话西游小说","content": "某知名网络小说作家已经完成了大话西游同名小说的出版"}}]}
}

77_进阶_dynamic_templates动态映射模板定制自己的映射策略

课程大纲

高级的用法

比如说,我们本来没有某个type,或者没有某个field,但是希望在插入数据的时候,es自动为我们做一个识别,动态映射出这个type的mapping,包括每个field的数据类型,一般用的动态映射,dynamic mapping

这里有个问题,如果说,我们其实对dynamic mapping有一些自己独特的需求,比如说,es默认来说,如经过识别到一个数字,field: 10,默认是搞成这个field的数据类型是long,再比如说,如果我们弄了一个field : “10”,默认就是text,还会带一个keyword的内置field。我们没法改变。

但是我们现在就是希望动态映射的时候,根据我们的需求去映射,而不是让es自己按照默认的规则去玩儿

dyanmic mapping template,动态映射模板

我们自己预先定义一个模板,然后插入数据的时候,相关的field,如果能够根据我们预先定义的规则,匹配上某个我们预定义的模板,那么就会根据我们的模板来进行mapping,决定这个Field的数据类型

0、默认的动态映射的效果咋样

DELETE /my_index
PUT /my_index/my_type/1
{"test_string": "hello world","test_number": 10
}

es的自动的默认的,动态映射是咋样的。。。

GET /my_index/_mapping/my_type

{"my_index": {"mappings": {"my_type": {"properties": {"test_number": {"type": "long"},"test_string": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}}
}

这个就是es的默认的动态映射规则,可能就不是我们想要的。。。

我们比如说,现在想要的效果是啥。。。

test_number,如果是个数字,我们希望默认就是integer类型的
test_string,如果是字符串,我们希望默认是个text,这个没问题,但是内置的field名字,叫做raw,不叫座keyword,类型还是keyword,保留500个字符

1、根据类型匹配映射模板

动态映射模板,有两种方式,第一种,是根据新加入的field的默认的数据类型,来进行匹配,匹配上某个预定义的模板;第二种,是根据新加入的field的名字,去匹配预定义的名字,或者去匹配一个预定义的通配符,然后匹配上某个预定义的模板

PUT my_index
{"mappings": {"my_type": {"dynamic_templates": [{"integers": {"match_mapping_type": "long","mapping": {"type": "integer"}}},{"strings": {"match_mapping_type": "string","mapping": {"type": "text","fields": {"raw": {"type": "keyword","ignore_above": 500}}}}}]}}
}
PUT /my_index/my_type/1
{"test_long": 1,"test_string": "hello world"
}
{"my_index": {"mappings": {"my_type": {"dynamic_templates": [{"integers": {"match_mapping_type": "long","mapping": {"type": "integer"}}},{"strings": {"match_mapping_type": "string","mapping": {"fields": {"raw": {"ignore_above": 500,"type": "keyword"}},"type": "text"}}}],"properties": {"test_number": {"type": "integer"},"test_string": {"type": "text","fields": {"raw": {"type": "keyword","ignore_above": 500}}}}}}}

2、根据字段名配映射模板

PUT /my_index
{"mappings": {"my_type": {"dynamic_templates": [{"string_as_integer": {"match_mapping_type": "string","match": "long_*","unmatch": "*_text","mapping": {"type": "integer"}}}]}}
}

举个例子,field : “10”,把类似这种field,弄成long型

{"my_index": {"mappings": {"my_type": {"dynamic_templates": [{"string_as_integer": {"match": "long_*","unmatch": "*_text","match_mapping_type": "string","mapping": {"type": "integer"}}}],"properties": {"long_field": {"type": "integer"},"long_field_text": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}}
}

场景,有些时候,dynamic mapping + template,每天有一堆日志,每天有一堆数据

这些数据,每天的数据都放一个新的type中,每天的数据都会哗哗的往新的tye中写入,此时你就可以定义一个模板,搞一个脚本,每天都预先生成一个新type的模板,里面讲你的各个Field都匹配到一个你预定义的模板中去,就好了

78_进阶_geo point地理位置数据类型

课程大纲

这一讲开始,后面会跟着几讲内容,将地理位置相关的知识给大家讲解一下

主要是es支持基于地理位置的搜索,和聚合分析的

举个例子,比如说,我们后面就会给大家演示一下,你现在如果说做了一个酒店o2o app,让你的用户在任何地方,都可以根据当前所在的位置,找到自己身边的符合条件的一些酒店,那么此时就完全可以使用es来实现,非常合适

我现在在上海某个大厦附近,我要搜索到距离我2公里以内的5星级的带游泳池的一个酒店s,用es就完全可以实现类似这样的基于地理位置的搜索引擎

1、建立geo_point类型的mapping

第一个地理位置的数据类型,就是geo_point,geo_point,说白了,就是一个地理位置坐标点,包含了一个经度,一个维度,经纬度,就可以唯一定位一个地球上的坐标

PUT /my_index
{"mappings": {"my_type": {"properties": {"location": {"type": "geo_point"}}}}
}

2、写入geo_point的3种方法

PUT my_index/my_type/1
{"text": "Geo-point as an object","location": { "lat": 41.12,"lon": -71.34}
}

latitude:维度
longitude:经度

我们这里就不用去关心,这些坐标到底代表什么地方,其实都是我自己随便写的,只要能够作为课程,给大家演示清楚就可以了,自己去找一些提供地理位置的一些公司,供应商,api,百度地图,也是提供各个地方的经纬度的

不建议用下面两种语法

PUT my_index/my_type/2
{"text": "Geo-point as a string","location": "41.12,-71.34"
}PUT my_index/my_type/4
{"text": "Geo-point as an array","location": [ -71.34, 41.12 ]
}

3、根据地理位置进行查询

最最简单的,根据地理位置查询一些点,比如说,下面geo_bounding_box查询,查询某个矩形的地理位置范围内的坐标点

GET /my_index/my_type/_search
{"query": {"geo_bounding_box": {"location": {"top_left": {"lat": 42,"lon": -72},"bottom_right": {"lat": 40,"lon": -74}}}}
}
{"took": 81,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 1,"hits": [{"_index": "my_index","_type": "my_type","_id": "1","_score": 1,"_source": {"location": {"lat": 41.12,"lon": -71.34}}}]}
}

比如41.12,-71.34就是一个酒店,然后我们现在搜索的是从42,-72(代表了大厦A)和40,-74(代表了马路B)作为矩形的范围,在这个范围内的酒店,是什么

79_进阶_酒店o2o搜索案例以及搜索指定区域内的酒店

稍微真实点的案例,酒店o2o app作为一个背景,用各种各样的方式,去搜索你当前所在的地理位置附近的酒店

搜索指定区域范围内的酒店,比如说,我们可以在搜索的时候,指定两个地点,就要在东方明珠大厦和上海路组成的矩阵的范围内,搜索我想要的酒店

PUT /hotel_app
{"mappings": {"hotels": {"properties": {"pin": {"properties": {"location": {"type": "geo_point"}}}}}}
}
PUT /hotel_app/hotels/1
{"name": "喜来登大酒店","pin" : {"location" : {"lat" : 40.12,"lon" : -71.34}}
}
GET /hotel_app/hotels/_search
{"query": {"bool": {"must": [{"match_all": {}}],"filter": {"geo_bounding_box": {"pin.location": {"top_left" : {"lat" : 40.73,"lon" : -74.1},"bottom_right" : {"lat" : 40.01,"lon" : -71.12}}}}}}
}
GET /hotel_app/hotels/_search
{"query": {"bool": {"must": [{"match_all": {}}],"filter": {"geo_polygon": {"pin.location": {"points": [{"lat" : 40.73, "lon" : -74.1},{"lat" : 40.01, "lon" : -71.12},{"lat" : 50.56, "lon" : -90.58}]}}}}}
}

我们现在要指定东方明珠大厦,上海路,上海博物馆,这三个地区组成的多边形的范围内,我要搜索这里面的酒店

80_进阶_搜索距离当前位置一定范围内的酒店

酒店o2o app,作为案例背景

比如说,现在用户,所在的位置,是个地理位置的坐标,我是知道我的坐标的,app是知道的,android,地理位置api,都可以拿到当前手机app的经纬度

我说,我现在就要搜索出,举例我200m,或者1公里内的酒店

重要!!!!

我们之前出去玩儿,都会用一些酒店o2o app,典型的代表,很多旅游app,一般来说,我们怎么搜索,到了一个地方,就会搜索说,我这里举例几百米,2公里内的酒店,搜索一下

上节课讲解的,其实也很重要,一般来说,发生在我们在去旅游之前,会现在旅游app上搜索一个区域内的酒店,比如说,指定了西安火车站、西安博物馆,拿指定的几个地方的地理位置,组成一个多边形区域范围,去搜索这个区域内的酒店

承认,一些案例,当然不可能说达到讲解真实的复杂的大型的项目的效果来的那么好,光是学知识,学技术而言,有一些案例就非常不错了

后面,会讲解真正的企业级的大型的搜索引擎,真实复杂业务的数据分析系统的项目

GET /hotel_app/hotels/_search
{"query": {"bool": {"must": [{"match_all": {}}],"filter": {"geo_distance": {"distance": "200km","pin.location": {"lat": 40,"lon": -70}}}}}
}

81_进阶_统计当前位置每个距离范围内有多少家酒店

最后一个知识点,基于地理位置进行聚合分析

我的需求就是,统计一下,举例我当前坐标的几个范围内的酒店的数量,比如说举例我0100m有几个酒店,100m300m有几个酒店,300m以上有几个酒店

一般来说,做酒店app,一般来说,我们是不是会有一个地图,用户可以在地图上直接查看和搜索酒店,此时就可以显示出来举例你当前的位置,几个举例范围内,有多少家酒店,让用户知道,心里清楚,用户体验就比较好

GET /hotel_app/hotels/_search
{"size": 0,"aggs": {"agg_by_distance_range": {"geo_distance": {"field": "pin.location","origin": {"lat": 40,"lon": -70},"unit": "mi", "ranges": [{"to": 100},{"from": 100,"to": 300},{"from": 300}]}}}
}
{"took": 5,"timed_out": false,"_shards": {"total": 5,"successful": 5,"failed": 0},"hits": {"total": 1,"max_score": 0,"hits": []},"aggregations": {"agg_by_distance_range": {"buckets": [{"key": "*-100.0","from": 0,"to": 100,"doc_count": 1},{"key": "100.0-300.0","from": 100,"to": 300,"doc_count": 0},{"key": "300.0-*","from": 300,"doc_count": 0}]}}
}

m (metres) but it can also accept: m (miles), km (kilometers)

sloppy_arc (the default), arc (most accurate) and plane (fastest)

第82-92节

82_Java API_client集群自动探查以及汽车零售店案例背景

课程大纲

快速入门篇,讲解过了一些基本的java api,包括了document增删改查,基本的搜索,基本的聚合

高手进阶篇,必须将java api这块深入讲解一下,介绍一些最常用的,最核心的一些api的使用,用一个模拟现实的案例背景,让大家在学习的时候更加贴近业务

话说在前面,我们是不可能将所有的java api用视频全部录制一遍的,因为api太多了。。。。

我们之前讲解各种功能,各种知识点,花了那么多的时间,哪儿些才是最最关键的,知识,原理,功能,es restful api,最次最次,哪怕是搞php,搞python的人也可以来学习

如果说,现在要将所有所有的api全部用java api实现一遍和讲解,太耗费时间了,几乎不可能接受

采取的粗略,将核心的java api语法,还有最最常用的那些api都给大家上课演示了

然后最后一讲,会告诉大家,在掌握了之前那些课程讲解的各种知识点之后,如果要用java api去实现和开发,应该怎么自己去探索和掌握

java api,api的学习,实际上是最最简单的,纯用,没什么难度,技术难度,你掌握了课上讲解的这些api之后,自己应该就可以举一反三,后面自己去探索和尝试出自己要用的各种功能对应的java api是什么。。。

1、client集群自动探查

默认情况下,是根据我们手动指定的所有节点,依次轮询这些节点,来发送各种请求的,如下面的代码,我们可以手动为client指定多个节点

TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost1"), 9300)).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost2"), 9300)).addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost3"), 9300));

但是问题是,如果我们有成百上千个节点呢?难道也要这样手动添加吗?

es client提供了一种集群节点自动探查的功能,打开这个自动探查机制以后,es client会根据我们手动指定的几个节点连接过去,然后通过集群状态自动获取当前集群中的所有data node,然后用这份完整的列表更新自己内部要发送请求的node list。默认每隔5秒钟,就会更新一次node list。

但是注意,es cilent是不会将Master node纳入node list的,因为要避免给master node发送搜索等请求。

这样的话,我们其实直接就指定几个master node,或者1个node就好了,client会自动去探查集群的所有节点,而且每隔5秒还会自动刷新。非常棒。

Settings settings = Settings.builder()
.put(“client.transport.sniff”, true).build();
TransportClient client = new PreBuiltTransportClient(settings);

使用上述的settings配置,将client.transport.sniff设置为true即可打开集群节点自动探查功能

在实际的生产环境中,都是这么玩儿的。。。

2、汽车零售案例背景

简单来说,会涉及到三个数据,汽车信息,汽车销售记录,汽车4S店信息

83_Java API_基于upsert实现汽车最新价格的调整

课程大纲

做一个汽车零售数据的mapping,我们要做的第一份数据,其实汽车信息

PUT /car_shop
{"mappings": {"cars": {"properties": {"brand": {"type": "text","analyzer": "ik_max_word","fields": {"raw": {"type": "keyword"}}},"name": {"type": "text","analyzer": "ik_max_word","fields": {"raw": {"type": "keyword"}}}}}}
}

首先的话呢,第一次调整宝马320这个汽车的售价,我们希望将售价设置为32万,用一个upsert语法,如果这个汽车的信息之前不存在,那么就insert,如果存在,那么就update

IndexRequest indexRequest = new IndexRequest("car_shop", "cars", "1").source(jsonBuilder().startObject().field("brand", "宝马").field("name", "宝马320").field("price", 320000).field("produce_date", "2017-01-01").endObject());UpdateRequest updateRequest = new UpdateRequest("car_shop", "cars", "1").doc(jsonBuilder().startObject().field("price", 320000).endObject()).upsert(indexRequest);       client.update(updateRequest).get();
IndexRequest indexRequest = new IndexRequest("car_shop", "cars", "1").source(jsonBuilder().startObject().field("brand", "宝马").field("name", "宝马320").field("price", 310000).field("produce_date", "2017-01-01").endObject());
UpdateRequest updateRequest = new UpdateRequest("car_shop", "cars", "1").doc(jsonBuilder().startObject().field("price", 310000).endObject()).upsert(indexRequest);
client.update(updateRequest).get();

84_Java API_mget实现多辆汽车的配置与价格对比

课程大纲

场景,一般来说,我们都可以在一些汽车网站上,或者在混合销售多个品牌的汽车4S店的内部,都可以在系统里调出来多个汽车的信息,放在网页上,进行对比

mget,一次性将多个document的数据查询出来,放在一起显示,多个汽车的型号,一次性拿出了多辆汽车的信息

PUT /car_shop/cars/2
{"brand": "奔驰","name": "奔驰C200","price": 350000,"produce_date": "2017-01-05"
}MultiGetResponse multiGetItemResponses = client.prepareMultiGet().add("car_shop", "cars", "1")           .add("car_shop", "cars", "2")        .get();for (MultiGetItemResponse itemResponse : multiGetItemResponses) { GetResponse response = itemResponse.getResponse();if (response.isExists()) {                      String json = response.getSourceAsString(); }
}

85_Java API_基于bulk实现多4S店销售数据批量上传

课程大纲

业务场景:有一个汽车销售公司,拥有很多家4S店,这些4S店的数据,都会在一段时间内陆续传递过来,汽车的销售数据,现在希望能够在内存中缓存比如1000条销售数据,然后一次性批量上传到es中去

PUT /car_shop/sales/1
{"brand": "宝马","name": "宝马320","price": 320000,"produce_date": "2017-01-01","sale_price": 300000,"sale_date": "2017-01-21"
}PUT /car_shop/sales/2
{"brand": "宝马","name": "宝马320","price": 320000,"produce_date": "2017-01-01","sale_price": 300000,"sale_date": "2017-01-21"
}
BulkRequestBuilder bulkRequest = client.prepareBulk();bulkRequest.add(client.prepareIndex("car_shop", "sales", "3").setSource(jsonBuilder().startObject().field("brand", "奔驰").field("name", "奔驰C200").field("price", 350000).field("produce_date", "2017-01-05").field("sale_price", 340000).field("sale_date", "2017-02-03").endObject()));bulkRequest.add(client.prepareUpdate("car_shop", "sales", "1").setDoc(jsonBuilder()               .startObject().field("sale_price", "290000").endObject()));bulkRequest.add(client.prepareDelete("car_shop", "sales", "2"));BulkResponse bulkResponse = bulkRequest.get();if (bulkResponse.hasFailures()) {}

86_Java API_基于scroll实现月度销售数据批量下载

课程大纲

比如说,现在要下载大批量的数据,从es,放到excel中,我们说,月度,或者年度,销售记录,很多,比如几千条,几万条,几十万条

其实就要用到我们之前讲解的es scroll api,对大量数据批量的获取和处理

PUT /car_shop/sales/4
{"brand": "宝马","name": "宝马320","price": 320000,"produce_date": "2017-01-01","sale_price": 280000,"sale_date": "2017-01-25"
}

就是要看宝马的销售记录

2条数据,做一个演示,每个批次下载一条宝马的销售记录,分2个批次给它下载完

SearchResponse scrollResp = client.prepareSearch("car_shop").addTypes("sales").setScroll(new TimeValue(60000)).setQuery(termQuery("brand.raw", "宝马")).setSize(1).get(); do {for (SearchHit hit : scrollResp.getHits().getHits()) {}scrollResp = client.prepareSearchScroll(scrollResp.getScrollId()).setScroll(new TimeValue(60000)).execute().actionGet();
} while(scrollResp.getHits().getHits().length != 0);

87_Java API_基于search template实现按品牌分页查询模板

课程大纲

搜索模板的功能,java api怎么去调用一个搜索模板

page_query_by_brand.mustache{"from": {{from}},"size": {{size}},"query": {"match": {"brand.keyword": "{{brand}}" }}
}SearchResponse sr = new SearchTemplateRequestBuilder(client).setScript("page_query_by_brand")                 .setScriptType(ScriptService.ScriptType.FILE) .setScriptParams(template_params)             .setRequest(new SearchRequest())              .get()                                        .getResponse();

88_Java API_对汽车品牌进行全文检索、精准查询和前缀搜索

课程大纲

PUT /car_shop/cars/5
{"brand": "华晨宝马","name": "宝马318","price": 270000,"produce_date": "2017-01-20"
}SearchResponse response = client.prepareSearch("car_shop").setTypes("cars").setQuery(QueryBuilders.matchQuery("brand", "宝马"))                .get();SearchResponse response = client.prepareSearch("car_shop").setTypes("cars").setQuery(QueryBuilders.multiMatchQuery("宝马", "brand", "name"))                .get();SearchResponse response = client.prepareSearch("car_shop").setTypes("cars").setQuery(QueryBuilders.commonTermsQuery("name", "宝马320"))                .get();SearchResponse response = client.prepareSearch("car_shop").setTypes("cars").setQuery(QueryBuilders.prefixQuery("name", "宝"))                .get();

89_Java API_对汽车品牌进行多种条件的组合搜索

课程大纲

QueryBuilder qb = boolQuery().must(matchQuery("brand", "宝马"))    .mustNot(termQuery("name.raw", "宝马318")) .should(termQuery("produce_date", "2017-01-02"))  .filter(rangeQuery("price").gte("280000").lt("350000"));SearchResponse response = client.prepareSearch("car_shop").setTypes("cars").setQuery(qb)                .get();

90_Java API_基于地理位置对周围汽车4S店进行搜索

课程大纲

<dependency><groupId>org.locationtech.spatial4j</groupId><artifactId>spatial4j</artifactId><version>0.6</version>
</dependency><dependency><groupId>com.vividsolutions</groupId><artifactId>jts</artifactId><version>1.13</version>                         <exclusions><exclusion><groupId>xerces</groupId><artifactId>xercesImpl</artifactId></exclusion></exclusions>
</dependency>

比如我们有很多的4s店,然后呢给了用户一个app,在某个地方的时候,可以根据当前的地理位置搜索一下,自己附近的4s店

POST /car_shop/_mapping/shops
{"properties": {"pin": {"properties": {"location": {"type": "geo_point"}}}}
}PUT /car_shop/shops/1
{"name": "上海至全宝马4S店","pin" : {"location" : {"lat" : 40.12,"lon" : -71.34}}
}

第一个需求:搜索两个坐标点组成的一个区域

QueryBuilder qb = geoBoundingBoxQuery("pin.location").setCorners(40.73, -74.1, 40.01, -71.12);

第二个需求:指定一个区域,由三个坐标点,组成,比如上海大厦,东方明珠塔,上海火车站

List<GeoPoint> points = new ArrayList<>();
points.add(new GeoPoint(40.73, -74.1));
points.add(new GeoPoint(40.01, -71.12));
points.add(new GeoPoint(50.56, -90.58));QueryBuilder qb = geoPolygonQuery("pin.location", points);

第三个需求:搜索距离当前位置在200公里内的4s店

QueryBuilder qb = geoDistanceQuery("pin.location").point(40, -70).distance(200, DistanceUnit.KILOMETERS);   SearchResponse response = client.prepareSearch("car_shop").setTypes("shops").setQuery(qb)                .get();

91_Java API_如何自己尝试API以掌握所有搜索和聚合的语法

课程大纲

1、自己要什么query,自己去用QueryBuilders去尝试,disMaxQuery
2、自己的某种query,有一些特殊的参数,tieBreaker,自己拿着query去尝试,点一下,看ide的自动提示,自己扫一眼,不就知道有哪些query,哪些参数了
3、如果不是query,是聚合,也是一样的,你就拿AggregationBuilders,点一下,看一下ide的自动提示,我们讲过的各种语法,都有了
4、包括各种聚合的参数,也是一样的,找到对应的AggregationBuilder之后,自己点一下,找需要的参数就可以了
5、自己不断尝试,就o了,组装了一个搜索,或者聚合,自己试一下,测一下

如果你实在找不到,搞不定,就QQ来找我,当然别自己一开始就跑来找我,先自己努力研究一下

有些人说,可以上官网,官网api也没这么全

92_快速入门篇以及高手进阶篇课程总结,以及后续阶段课程介绍

课程大纲

1、快速入门:能了解最最基本的es的一些使用
2、分布式原理:了解es的基本原理
3、分布式文档系统:基本精通es的document相关的一些操作和开发
4、初识搜索引擎:掌握es最核心,最基本的一些搜索的技术
5、索引管理:掌握了基本的es的索引相关的操作
6、内核原理探秘:深入理解的es的底层的原理
7、Java API初步使用:掌握最基础的java api的使用

开始把es用起来,可以玩儿起来,掌握了一些基本的知识,自己在公司做一些最最简单的小项目,也ok

1、深度探秘搜索技术:彻底深入的了解各种高级搜索技术,精通搜索底层原理
2、彻底掌握IK中文分词器:彻底掌握,连源码的修改都讲过了,怎么基于mysql热加载你的词库
3、深入聚合数据分析:彻底深入的掌握了各种各样的数据聚合分析的功能
4、数据建模实战:对模拟真实世界的有复杂关系的数据模型,讲解了建模、搜索和聚合
5、elasticsearch高手进阶:高级功能,搜索模板,term vector,地理位置的搜索和聚合
6、java api:核心的java api的现场演示,如何自己去摸索所有的java api的使用

你做一些小型的项目,数据量不大,简单在自己公司部署几个节点的es,3个节点

玩儿各种各样的搜索,聚合,中文分词,有关联和层次关系的数据如何建模,document如何管理和操作,索引的基本管理,es的核心原理,java api的系统的使用,高级的功能,基于地理位置的应用的开发

你都能搞定了

你只能做es的小型项目,或者大型项目,但是数据量大不了

============================================================================================================================

两个篇章

1、运维优化:生产环境的大规模集群的部署、运维管理、监控、安全管理、升级、性能优化、索引管理,大型es集群的运维知识,包括海量数据场景下的性能的调优,还有一个大数据场景的应用系统的设计,范式

搞java的,了解什么es的运维。。。。

你要是搞java的,结果不了解es运维,你也别做es的大型项目,大数据场景下的,你根本就不了解集群,不了解大数据集群环境下的一些特殊的配置,安全,监控,es全景图,概览

你要是搞java,基于es集群,大数据量做开发,你不了解上面这些东西,你碰到了问题,就抓瞎

你如果真是搞java的,最自己的技术有追求,希望自己出去找工作,技术牛逼一些,不要给自己设限制,开发,不要了解运维。如果你是个java架构师,你连es集群相关的知识都不懂,你碰到了问题,你的项目遇到了一些的报错,你都搞不定,你还当什么架构师,或者项目经理

如果你对技术有追求,就好好学一下

2、项目实战:各种各样的案例,作为背景,模拟现实,来用业务驱动课程的讲解,和动手的实战,更好的理解、吸收、刺激你的对技术如何运用的灵感

大型门户网站的搜索引擎系统:安全模拟真实大型搜索引擎系统的架构,特殊的点,降级,防止雪崩,缓存,架构怎么拆分,复杂的搜索引擎怎么构建,讲解

大型电商网站的数据分析系统:完全用真实的复杂的电商的业务场景,去开发一整套完整的涵盖数十个分析模块的es数据分析系统

后面两个篇章才是真正的拔高的地方,如果你对自己技术有追求,想把技术学好一些,建议,前面两个篇章,至少看个2遍,彻底掌握;后面2个篇章可以到时候好好看看

93-103节

93_es生产集群部署之硬件配置、jvm以及集群规划建议

我们之前一直是在windows环境下去启动一个单节点的es进程,然后去学习和练习各种es的高阶的搜索技术,聚合技术

主要针对的是es的开发,你可以认为是,如果你是一个java工程师的话,然后你们公司已经有人去维护和搭建一个es集群了,那么你直接掌握之前的内容,就足够了

你就可以建立需要的索引,写入数据,搜索,聚合

很多同学,还是需要自己去规划、设计和搭建一个es集群出来,先,然后才能基于es去做一些开发这样的

肯定不是在windows操作系统上去搞了,肯定是在linux集群上面去部署咯

规划一下,比如你需要几台机器,物理机,还是虚拟机呢?每台机器要多大的资源,多少G内存,多少个cpu,多大的磁盘空间,等等,然后对生产环境的机器相关的配置有没有什么特殊的要求,对于es来说。你需要处理多大的数据量?需要多大的集群才能支撑的住?

这块规划好之后,就是搞到一堆你需要的机器,符合你的需求和要求的机器,机器弄好之后,就可以开始在上面部署和玩儿你的es了

是这样的,我们的课程,肯定是只能用虚拟机去演练和模拟,在机器的配置上,能够在课程现场支撑的数据量上,是绝对达不到生产环境的

尽量保证,给大家用虚拟机去还原和模拟一些场景,然后讲解各种相关的技术知识

一般来说,如果我们刚开始用es,都是先在自己的笔记本电脑上,或者是几个虚拟机组成的小集群上,安装一个es,然后开始学习和试用其中的功能。但是如果我们要将es部署到生产环境中,那么是由很多额外的事情要做的。需要考虑我们部署的机器的内存、CPU、磁盘、JVM等各种资源和配置。

  • 1、内存

es是很吃内存的,es吃的主要不是你的jvm的内存,一般来说es用jvm heap(堆内存)还是用的比较少的,主要吃的是你的机器的内存

es底层基于lucene,lucene是基于磁盘文件来读写和保存你的索引数据的,倒排索引,正排索引,lucene的特点就是会基于os filesystem cache,会尽量将频繁访问的磁盘文件的数据在操作系统的内存中进行缓存,然后尽量提升磁盘文件读写的性能

很多同学都问我说,es的性能感觉不太理想,es的性能80%取决于说,你的机器上,除了分配给jvm heap内存以外,还剩下多少内存,剩下的内存会留给es的磁盘索引文件做缓存,如果os cache能够缓存更多的磁盘文件的数据,索引文件的数据,索引读写的性能都会高很多,特别是检索

但是如果你的大量的索引文件在os cache中放不下,还是停留在磁盘上,那么搜索、聚合的时候大量的都是读写磁盘文件,性能当然低了,一个数量级,ms级,s级

问我,es上千万数据的搜索,要耗费10s,大量读写磁盘了

如果在es生产环境中,哪种资源最容易耗尽,那么就是内存了。排序和聚合都会耗费掉很多内存,所以给es进程分配足够的jvm heap内存是很重要的。除了给jvm heap分配内存,还需要给予足够的内存给os filesystem cache。因为lucene用的数据结构都是给予磁盘的格式,es是通过os cache来进行高性能的磁盘文件读写的。

关于机器的内存相关的知识,后面会有很深入的讲解,这里先简单提一下,一般而言,除非说你的数据量很小,比如就是一些os系统,各种信息管理系统,要做一个内部的检索引擎,几万,几十万,几百万的数据量,对机器的资源配置要求还是蛮低的。一般而言,如果你的数据量过亿,几亿,几十亿。那么其实建议你的每台机器都给64G的内存的量。

如果一个机器有64G的内存,那么是比较理想的状态,但是32GB和16GB的内存也是ok的。具体的内存数量还是根据数据量来决定。但是如果一个机器的内存数量小于8G,那么就不太适合生产环境了,因为我们可能就需要很多小内存的机器来搭建集群。而大于64G的机器也不是很有必要。

笔记本电脑,24G内存(16G+8G),双核,虚拟机4台2核4G

  • 2、CPU

大多数的es集群对于cpu的要求都会比较低一些,因此一台机器有多少个cpu core其实对生产环境的es集群部署相对来说没有那么的重要了,至少没有内存来的重要。当然,肯定是要用多核处理器的,一般来说2个cpu core~8个cpu core都是可以的。

此外,如果要选择是较少的cpu core但是cpu性能很高,还是较多的cpu core但是cpu性能较为一般,那么肯定是选择性能较为一般但是更多的cpu core。因为更多的cpu core可以提供更强的并发处理能力,远比单个cpu性能高带来的效果更加明显。

  • 3、磁盘

对于es的生产环境来说,磁盘是非常重要的,尤其是对那些大量写入的es集群,比如互联网公司将每天的实时日志数据以高并发的速度写入es集群。在服务器上,磁盘是最慢的那个资源,所以对于大量写入的es集群来说,会很容易因为磁盘的读写性能造成整个集群的性能瓶颈。

如果我们能够使用SSD固态硬盘,而不是机械硬盘,那么当然是最好的,SSD的性能比机械硬盘可以高很多倍,可以让es的读写性能都高很多倍。所以,如果公司出的起钱大量使用固态硬盘,那么当然是最好的。

连我的笔记本电脑,都是有100多G的SSD啊,还有1T的机械硬盘

如果我们在用SSD硬盘的化,那么需要检查我们的I/O scheduler,需要正确的配置IO scheduler。当我们将数据写入磁盘时,IO scheduler会决定什么时候数据才会真正的写入磁盘,而不是停留在os cache内存缓冲中。大
机器上,默认的IO scheduler是cfq,也就是completely fair queuing。

这个scheduler会给每个进程都分配一些时间分片,time slice,然后会优化每个进程的数据如何写入磁盘中,优化的思路主要 是根据磁盘的物理布局来决定如何将数据写入磁盘,进而提升写入磁盘的性能。这是针对机械硬盘做出的优化,因为机械硬盘是一种旋转存储介质,是通过机械旋转磁盘+磁头进行磁盘读写的机制。

但是scheduler的这种默认的执行机制,对于SSD来说是不太高效的,因为SSD跟机械硬盘是不一样的,SSD不涉及到机械磁盘旋转和磁头读取这种传统的读写机制。对于SSD来说,应该用deadline/noop scheduler。deadline scheduler会基于写操作被pending了多长时间来进行写磁盘优化,而noop scheduler就是一个简单的FIFO队列先进先出的机制。

调整io scheduler可以带来很大的性能提升,甚至可以达到数百倍。

如果我们没有办法使用SSD,只能使用机械硬盘,那么至少得尽量正确读写速度最快的磁盘,比如高性能的服务器磁盘。

此外,使用RAID 0也是一种提升磁盘读写速度的高效的方式,无论是对于机械硬盘,还是SSD,都一样。RAID 0也被称之为条带式存储机制,striping,在RAID各种级别中性能是最高的。RAID 0的基本原理,是把连续的数据分散存储到多个磁盘上进行读写,也就是对数据进行条带式存储。这样系统的磁盘读写请求就可以被分散到多个磁盘上并行执行。但是没有必要使用镜像或者RAID的其他模式,因为我们不需要通过RAID来实现数据高可用存储,es的replica副本机制本身已经实现了数据高可用存储。

最后,我们要避免跟网络相关的存储模式,network-attached storage,NAS,比如基于网络的分布式存储模式。虽然很多供应商都说他们的NAS解决方案性能非常高,而且比本地存储的可靠性更高。但是实际上用起来会有很多性能和可靠性上的风险,一般因为网络传输会造成较高的延时,同时还有单点故障的风险。

  • 4、网络

对于es这种分布式系统来说,快速而且可靠的网络是非常的重要的。因为高速网络通信可以让es的节点间通信达到低延时的效果,高带宽可以让shard的移动和恢复,以及分配等操作更加的快速。现代的数据中心的网络对于大多数的集群来说,性能都足够高了。比如千兆网卡,这都是可以的。

但是要避免一个集群横跨多个数据中心,比如异地多机房部署一个集群,因为那样的话跨机房,跨地域的传输会导致网络通信和数据传输性能较差。es集群是一种p2p模式的分布式系统架构,不是master-slave主从分布式系统。在es集群中,所有的node都是相等的,任意两个node间的互相通信都是很频繁和正常的。因此如果部署在异地多机房,那么可能会导致node间频繁跨地域进行通信,通信延时会非常高,甚至造成集群运行频繁不正常。

就跟NAS存储模式一样,很多供应商都说跨地域的多数据中心是非常可靠的,而且低延时的。一般来说,可能的确是这样,但是一旦发生了网络故障,那么集群就完了。通常来说,跨地域多机房部署一个es集群带来的效益,远远低于维护这样的集群所带来的额外成本。

  • 5、自建集群 vs 云部署

现在一般很容易就可以拿到高性能的机器来部署集群:很多高性能的机器可以有上百G的内存资源,还有几十个cpu core。但是同时我们也可以再云供应商上,比如阿里云,租用大量的小资源的虚拟机。那么对于自己购买昂贵高性能服务器自建集群,以及租用云机器来部署,该选择哪种方案呢?

你是自己购买5台,比如说,8核64G的物理机,搭建es集群

或者是,比如说,上阿里云,或者其他的云服务,购买了2核4G,16台,虚拟机,搭建es集群

你上阿里云,也可以买到大资源量的虚拟机,4/8/16核64G

一般来说,对于es集群而言,是建议拥有少数机器,但是每个机器的资源都非常多,尽量避免拥有大量的少资源的虚拟机。因为对于运维和管理来说,管理5个物理机组成的es集群,远远比管理100个虚拟机组成的es集群要简单的多。

同时即使是自建集群,也要尽量避免那种超大资源量的超级服务器,因为那样可能造成资源无法完全利用,然后在一个物理机上部署多个es节点,这会导致我们的集群管理更加的复杂。

  • 6、JVM

对于最新的es版本,一般多建议用最新的jvm版本,除非es明确说明要用哪个jdk版本。es和lucene都是一种满足特殊需求的软件,lucene的单元测试和集成测试中,经常会发现jvm自身的一些bug。这些bug涵盖的范围很广,因此尽量用最新的jvm版本,bug会少一些。

就目前es 5.x版本而言,建议用jdk 8,而不是jdk 7,同时jdk 6已经不再被支持了。

如果我们用java编写es应用程序,而且在使用transport client或者node client,要确保运行我们的应用程序的jvm版本跟es服务器运行的jvm版本是一样的。在es中,有些java的本地序列化机制都被使用了,比如ip地址,异常信息,等等。而jvm可能在不同的minor版本之间修改序列化格式,所以如果client和server的jvm版本不一致,可能有序列化的问题。

同时官方推荐,绝对不要随便调整jvm的设置。虽然jvm有几百个配置选项,而且我们可以手动调优jvm的几乎方方面面。同时遇到一个性能场景的时候,每个人都会第一时间想到去调优jvm,但是es官方还是推荐我们不要随便调节jvm参数。因为es是一个非常复杂的分布式软件系统,而且es的默认jvm配置都是基于真实业务场景中长期的实践得到的。随便调节jvm配置反而有可能导致集群性能变得更加差,以及出现一些未知的问题。反而是很多情况下,将自定义的jvm配置全部删除,性能是保持的最好的。

  • 7、容量规划

在规划你的es集群的时候,一般要规划你需要多少台服务器,每台服务器要有多少资源,能够支撑你预计的多大的数据量。但是这个东西其实不是一概而论的,要视具体的读写场景,包括你执行多么复杂的操作,读写QPS来决定的。不过一般而言,根据讲师的实际经验,对于很多的中小型公司,都是建议es集群承载的数据量在10亿规模以内。用最合理的技术做最合理的事情。

这里可以给出几个在国内es非常适合的几个场景,es是做搜索的,当然可以做某个系统的搜索引擎。比如网站或者app的搜索引擎,或者是某些软件系统的搜索引擎,此外es还可以用来做数据分析。那么针对这几个不同的场景,都可以给出具体建议。比如做网站或者app的搜索引擎,一般数据量会相对来说大一些,但是通常而言,一个网站或者app的内容都是有限的,不会无限膨胀,通常数据量从百万级到亿级不等,因此用于搜索的数据都放在es中是合理的。

然后一些软件系统或者特殊项目的搜索引擎,根据项目情况不同,数据量也是从百万量级到几十亿,甚至几百亿,或者每日增量几亿,都有可能,那么此时就要根据具体的业务场景来决定了。如果数据量特别大,日增量都几亿规模,那么其实建议不要将每天全量的数据都写入es中,es也不适合这种无限规模膨胀的场景。es是很耗费内存的,无限膨胀的数据量,会导致我们无法提供足够的资源来支撑这么大的数据量。可以考虑是不是就将部分热数据,比如最近几天的数据,放到es中做高频高性能搜索,然后将大量的很少访问的冷数据放大数据系统做离线批量处理,比如hadoop系统里面。

比如说,你预计一下,你的数据量有多大,需要多少台机器,每台机器要多少资源,来支撑,可以达到多大的性能

数据量 -> 性能,10亿 -> 1s

es达到ms级的化,你必须要有足够的os cache去缓存几乎大部分的索引数据

10亿,每条数据是多大,比如多少个字节,1k -> 100G

5台,64G,8核,300G -> 100G总数据量,300G,一般要分给es jvm heap,150G -> 100G,100G落地到磁盘文件加入很多es自己的信息,100G -> 200G

200G落地磁盘的数据,物理内存剩余的只有150G,可能还有一些操作系统,还有其他的损耗100G

200G落地磁盘的数据,100G物理内存可以用来做os cache,50%的概率是基于os cache做磁盘索引文件的读写,几秒,很正常啦。。。

根据我们的实践经验而言,一般来说,除非是你的机器的内存资源,完全可以容纳所有的落地的磁盘文件的os cache,ms,否则的话,如果不是的话,会大量走磁盘,几秒

同时如果数据量在10亿以内的规模,那么一般而言,如果提供5台以上的机器,每台机器的配置到8核64G的配置,一般而言都能hold住。当然,这个也看具体的使用场景,如果你读写特别频繁,或者查询特别复杂,那么可能还需要更多的机器资源。如果你要承载更大的数据量,那么就相应的提供更多的机器和资源。

要提升你的es的性能,最重要的,还是说规划合理的数据量,物理内存资源大小,os cache

Elasticsearch顶尖高手系列:高手进阶篇(二)相关推荐

  1. Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验)

    Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验) 参考文章: (1)Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验) (2)https://www.cnblogs.co ...

  2. nas安装emby_威联通QNAP系统入门进阶 篇二:宅家新姿势—威联通NAS安装套件版Emby搭建家庭影音服务器...

    威联通QNAP系统入门&进阶 篇二:宅家新姿势-威联通NAS安装套件版Emby搭建家庭影音服务器 2020-02-04 19:38:54 123点赞 1466收藏 123评论 你是AMD Ye ...

  3. Android日志[进阶篇]二-分析堆栈轨迹(调试和外部堆栈)

    Android日志[进阶篇]一-使用 Logcat 写入和查看日志 Android日志[进阶篇]二-分析堆栈轨迹(调试和外部堆栈) Android日志[进阶篇]三-Logcat命令行工具 Androi ...

  4. Mysql高手系列 - 第8篇:详解排序和分页(order by limit),及存在的坑

    这是Mysql系列第8篇. 环境:mysql5.7.25,cmd命令中进行演示. 代码中被[]包含的表示可选,|符号分开的表示可选其一. 本章内容 详解排序查询 详解limit limit存在的坑 分 ...

  5. [安全攻防进阶篇] 二.如何学好逆向分析、逆向路线推荐及吕布传游戏逆向案例

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  6. VMware 从菜鸟到高手系列之基础篇

    整个实验环境所需要的虚拟机机器列表,包括机器名称.IP和所安装所需软件如下表: 服务器角色 操作系统 机器名 IP地址 所需安装介质 Connection Server Windows Server ...

  7. 《Genesis-3D游戏引擎系列教程-进阶篇》6:动画

    2019独角兽企业重金招聘Python工程师标准>>> 注:本系列进阶教程仅针对引擎编辑器:v1.2.2及以下版本 脚本控制与分割动画 资源下载   工程文件 经过一些编辑器基本操作 ...

  8. 剖析Elasticsearch集群系列第三篇 近实时搜索、深层分页问题和搜索相关性权衡之道...

    http://www.infoq.com/cn/articles/anatomy-of-an-elasticsearch-cluster-part03?utm_source=infoq&utm ...

  9. 《Genesis-3D游戏引擎系列教程-进阶篇》10:动画融合

    2019独角兽企业重金招聘Python工程师标准>>> 注:本系列进阶教程仅针对引擎编辑器:v1.2.2及以下版本 地形高度图的导入 素材资源   工程文件 Genesis-3D编辑 ...

  10. Java学习路线(进阶篇二)

    文章目录 前言 一.常用API 1.Object(祖宗类) 2.Objects(工具类-提供一些方法完成一些功能) 3.StringBuilder(不可变的字符串类) 4.Math(基本数字运算的方法 ...

最新文章

  1. 事件计划自动关闭_首部L3级自动驾驶国际法规之ALKS功能解读
  2. 【翻译转载】【官方教程】Asp.Net MVC4入门指南(2):添加一个控制器
  3. window7自带的分区
  4. UnityEngine.UI.dll 路径
  5. HBase shell 命令。
  6. 两个异常:a circular reference was detected while serializing及 Maximum length exceeded
  7. kubernetes 应用快速入门
  8. mysql主备(centos6.4)
  9. 【解决】打印机只打印第一页纸的问题
  10. 在framework和hal添加log
  11. 二进制文件(.bin)查看
  12. PHP开发环境搭建:PHP集成环境XAMPP 的安装与配置
  13. centos7安裝搜狗輸入法_centos7安装搜狗输入法
  14. 02读书笔记:《编码》-隐匿在计算机软硬件背后的语言(12-14章)
  15. 编程英语:常见代码错误 error 语句学习(1)
  16. SC16IS752调试
  17. 《学习opencv》第四章1,2题(第二题详解,最切题)
  18. 网盾极风云:五分钟搞懂HTTP和HTTPS
  19. 图片的放大ZommJS
  20. spring-依赖注入(DI)

热门文章

  1. Win11系统svchost.exe一直在下载怎么办?
  2. stm32晶振配置不一致导致 Invalid Rom Table 至芯片锁死解决方案
  3. 计算机电子表格today函数,EXCEL函数教育之今天(today)与现在(now)
  4. 腾讯/京东/网易校招笔试刷人超70%,这份笔试自救指南请收好
  5. ubuntu镜像源的配置
  6. 即将开幕!阿里云飞天技术峰会邀您一同探秘云原生最佳实践
  7. ppt怎么加注解文字_ppt幻灯片怎么给图片添加注释?
  8. 基于python mediapipe的视频或者图片更换背景
  9. 手机怎样和宽带连接无线路由器设置路由器连接服务器,手机如何设置路由器?...
  10. STC15W201S串口蓝牙通信PWM控制RGB彩灯