在实际开发过程中,如果使用sql表现出局限性,我们就要尝试使用别的技术方案来解决问题,比如elasticsearch。
接下来我们一起看一下es的常用功能和使用方法。
首先是安装与启动
这部分是很简单方便的,首先从官网下载es的安装包。我们这个示例使用的是6.8.8版本,
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.8.8.tar.gz
解压之后就可以直接用命令启动了。
$ tar -xvf elasticsearch-6.8.8.tar.gz
$ ./elasticsearch-6.8.8/bin/elasticsearch &
因为es是java写的,所以在启动的时候要确保环境中安装了jdk。
启动好之后,我们可以直接请求es的restful接口,来查看es的启动状态
$ curl http://localhost:9200
正常的返回结果:
{
"name" : "demo-1",
"cluster_name" : "demo",
"cluster_uuid" : "gs1uLoXyQseqMF0DB47_Cg",
"version" : {
"number" : "6.8.8",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "2f4c224",
"build_date" : "2020-03-18T23:22:18.622755Z",
"build_snapshot" : false,
"lucene_version" : "7.7.2",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
接下来我们创建索引
绿颜色的这几个地方,都是我们自定义的名称,可以随意修改,其他的都是系统关键字是固定的写法。

$ curl -XPUT 192.168.60.89:9602/goods/ -H "Content-Type: application/json" —header "Accept: */*" -d '{
"mappings":{
"list":{
"properties" : {
"goodsId" : {
"type" : "keyword",
"index": false
},
"name" : {
"type" : "text",
"index": true
},
"price" : {
"type" : "keyword",
"index": true
},
"createTime" : {
"type" : "keyword",
"index": true
}
}
}
}
}'
标注1这里写的是goods,表示创建一个叫goods的索引,
标注2这里写的是list,表示goods索引里有个索引类型叫list。
需要注意的是,从6.0以后,es的一个索引只能有一个类型,所以我们只要按这个固定的模式创建就行了。另外,最新的7.X版本,里面已经去掉索引类型了,所以如果是使用最新版的,就不要加这个索引类型list。
标注3这里表示goodsId字段的类型是keyword,是一种不需要分词的字符串类型,不分词的意思是,值插入的时候是什么样,就存成什么样。
标注4这里表示name字段的类型是text,是一种需要分词的字符串类型。需要分词的意思是,值在插入的时候,需要额外从文本中分析出一个个词条进行存储。
当然还有其他的类型,比如Integer 、boolean等等,都是不需要进行分词的。
有了索引,我们就可以往里边插数据了。
插数据
还是一样的,绿色的部分都输入我们自定义的数据,

$ curl -XPOST 192.168.60.89:9602/goods/list/10000/_create -H "Content-Type: application/json" —header "Accept: */*" -d '
{"goodsId":"10000","name":"测试苹果","price":"9999","createTime":"2020-11-11 11:11:11"}'
标注1的地方是_create关键字,表示创建一个新文档,
标注2这个地方就是文档的内容,里边的字段名称和创建索引时设置的字段名称需要保持一致。
整个命令表示往goods索引的list类型里插入一个id是10000的新文档,
插入成功会返回这样一段提示,其中successful=1表示成功。
{"_index":"goods","_type":"list","_id":"10000","_version":1,"result":"created","_shards":{"total":3,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}
修改数据
也是一样的,绿色部分是我们自定义的数据,

curl -XPOST 192.168.60.89:9602/goods/_doc/20000/_update -H "Content-Type: application/json" —header "Accept: */*" -d '
{"doc":{"goodsId":"20000","name":"测试苹果22","price":"2222","createTime":"2020-11-11 11:11:11"}}
'
标注1 2 3的这三个关键字,都是固定的写法。
这个命令表示修改goods索引里id为20000的这个文档,
修改成功返回的也是successful=1
{"_index":"goods","_type":"_doc","_id":"20000","_version":2,"result":"updated","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}
批量插入或者修改数据
我们在往es里导入数据的时候,就可以用这种批量插入的方式。

curl -XPOST 192.168.60.89:9602/goods/list/_bulk?pretty -H "Content-Type: application/json" —header "Accept: */*" -d '
{"index":{"_id":"1"}}
{"goodsId":"1","name":"苹果Apple iPhone 12","price":"9999","createTime":"2020-11-11 11:11:11"}
{"index":{"_id":"2"}}
{"goodsId":"2","name":"苹果Apple iPhone 11","price":"7777","createTime":"2019-11-11 12:11:11"}
{"index":{"_id":"3"}}
{"goodsId":"3","name":"苹果烟台红富士 新鲜水果","price":"25","createTime":"2020-11-11 13:11:11"}
{"index":{"_id":"4"}}
{"goodsId":"4","name":"Apple 苹果 iPhone 11","price":"7777","createTime":"2019-11-11 14:11:11"}
{"index":{"_id":"5"}}
{"goodsId":"5","name":"新疆阿克苏苹果 超甜 12元一斤","price":"12","createTime":"2020-11-11 15:11:11"}
{"index":{"_id":"6"}}
{"goodsId":"6","name":"苹果Apple iPhone 4","price":"400","createTime":"2008-11-11 16:11:11"}
{"index":{"_id":"7"}}
{"goodsId":"7","name":"烟台红富士苹果 大果 4粒装 3斤","price":"23","createTime":"2020-11-11 17:11:11"}'
绿色的地方和之前的插入是一样的,
标注1这里从create改成_bulk关键字,
标注2这里需要给每个文档都加一个固定的index id,用于传入文档的id,
标注3是文档的内容。执行这个命令时es会判断,如果系统里没有这个文档就插入,已经有这个文档就进行更新。
删除数据
删除文档就更简单了,只要设置索引和文档id就可以删除了。
curl -XDELETE 192.168.60.89:9602/goods/_doc/10000?pretty
查找操作
接下来是我们用的最多的查找操作。
查数据有三种类型,精确查找,全文搜索,聚合查找。
首先是精确查找:

curl -XGET 192.168.60.89:9602/goods/list/_search?pretty -H "Content-Type: application/json" —header "Accept: */*" -d '
{
"query": {
"bool": {
"must": [
{ "term": { "_id": "20000"}}
]
}
}
}'
绿色部分也是输入我们自定义的名称。
我们先看标注5,term关键字和sql中的等于判断一样,用于精确比较,这里就是id等于20000的意思,
标注4的must,和sql中的and一样,表示标注5这里多个条件之间的关系,标注4也可以改成must____not或者should或者should____not,大家可以根据字面意思去理解,我们这个示例中的must再结合标注5的这个条件,表示查找必须满足id=20000这个条件的数据。
另外标注1 2 3这三个也是固定的写法。
查找成功返回的结果:

我们主要关注这4点:
标注1,表示查询耗费的时间,单位是毫秒
标注2,表示这个索引有5个主分片
标注3,是对查到的每一条数据打分的分数,匹配的越精确分数就越高
标注4,这里就是文档的具体内容
我们注意到标注3的分数字段,打分是因为es对查找的文档进行了相关度分析,在这个示例中,因为我们是用id来进行精确查找的,所以是不需要进行相关度分析,这种场景下我们可以使用filter进行查找。
filter的用法和query的用法基本没有区别:

只是在标注2这里,多加了一层filter过滤。
我们看到filter的返回结果,基本上和query返回的结果是一样的:

只是现在标注1的score字段变成0,因为filter不会执行相关度分析,所以也就不会对结果打分。
另外还有一点区别是,filter还会对结果进行缓存,有了这两点区别,filter整体查询速度要比query快,但是也正是因为filter不会对结果打分,会导致filter不能对结果进行相关度排序,所以我们在实际使用时,可以根据需要进行选择。
query和filter的区别:

全文搜索
接下来是全文搜索,格式和精确查找的query用法是一样的:

只不过把标注2这里由之前的term变成match关键字。
标注1这里的from和size和sql里的limit作用一样,用来对结果进行分页。
这个示例表示搜索两条数据,并且name里同时包含苹果 和 12关键字。
返回结果的格式也是一样的,因为是全文搜索,所以会对文档进行相关度分析对结果进行打分,这里第一条是1.3分,第二条是0.8分。

我看到返回的数据里,还是有一个是手机,有一个是水果,
现在如果想把包含水果的数据给过滤掉,可以再加一个must_not的判断,把包含斤 盒 克这种水果的键字的记录给过滤掉:

curl -XGET 192.168.60.89:9602/goods/list/_search?pretty -H "Content-Type: application/json" —header "Accept: */*" -d '
{
"from": 0,
"size": 100,
"query": {
"bool": {
"must": [
{ "match": { "name": "苹果"}},
{ "match": { "name": "12"}}
],
"must_not": [
{ "match": { "name": "斤 盒 克 "}}
]
}
}
}'
这样就可以只获取到一条苹果手机的数据:

boosting搜索
现在如果用户搜的是“苹果 4”,我们认为用户搜手机和搜水果的可能性都存在,这时我们可以使用boosting查询来对结果进行权重设置。
boosting的格式我们可以拿前边的全文搜索的格式做为一个参照。

首先我们看标注2,从原来的bool关键字改成boosting关键字
标注3从原来的must变成positive关键字,表示正向的条件,作用和must是一样的,就是必须满足positive里的条件
标注4从原来的must__not变成negative关键字,表示负向的条件
整个命令的意思是,搜索的结果必须满足positive里的条件,同时,如果和negative里的条件也匹配了,那么这个文档的得分就要打折,打折比例通过标注5这里的negative_boost参数来设置的,0.2表示打两折,最后,返回的文档得分不低于标注1这里min_score设置的分数。
我们看到返回的结果里,有苹果4手机和也有4粒包装的水果,并且是按照各自的权重比例打分的,而且返回来的都是得分大于0.15的文档。

聚合查找
聚合查找的用法也很简单,一般是这样使用的:

我们看下边标注1这里,使用price字段进行聚合,也就是group by price字段
标注2是给聚合返回的结果起的名字
标注3表示只对名称包含苹果关键字的文档进行聚合
这是返回的结果:

标注1是我们给聚合返回起的名字,标注2和标注3这里是返回的结果,就是价格是12的有1条记录,价格是2222的有5条记录。
网友评论