Elasticsearch顶尖高手系列:高手进阶篇(二)
作者:互联网
Elasticsearch顶尖高手系列:高手进阶篇(二)
- 第59-72节
- 59_数据建模_关系型与document类型数据模型对比
- 60_数据建模_通过应用层join实现用户与博客的关联
- 61_数据建模_通过数据冗余实现用户与博客的关联
- 62_数据建模_对每个用户发表的博客进行分组top_hits
- 63_数据建模_对文件系统进行数据建模以及文件搜索
- 64_数据建模_全局锁实现悲观锁并发控制
- 65_数据建模_document锁实现悲观锁并发控制
- 66_数据建模_共享锁和排他锁实现悲观锁并发控制
- 67_数据建模_nested嵌套查询
- 68_数据建模_nested嵌套聚合
- 69_数据建模_父子关系建模
- 70_数据建模_父子关系查询
- 71_数据建模_父子关系聚合
- 72_数据建模_父子关系祖孙三层练习
- 第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节
第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": "中国"
}
}
]
}
}
第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
标签:顶尖高手,name,lock,进阶篇,Elasticsearch,doc,type,id,es 来源: https://blog.csdn.net/hd20086996/article/details/111581836