搜索
什么是搜索, 计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。
常见的全网搜索引擎,像百度、谷歌这样的。但是除此以外,搜索技术在垂直领域也有广泛的使用,比如淘宝、京东搜索商品,万芳、知网搜索期刊,csdn中搜索问题贴。也都是基于海量数据的搜索。
如何处理搜索
用传统关系性数据库
弊端:
1、 对于传统的关系性数据库对于关键词的查询,只能逐字逐行的匹配,性能非常差。
2、匹配方式不合理,比如搜索“小密手机” ,如果用like进行匹配, 根本匹配不到。但是考虑使用者的用户体验的话,除了完全匹配的记录,还应该显示一部分近似匹配的记录,至少应该匹配到“手机”。
专业全文索引是怎么处理的
全文搜索引擎目前主流的索引技术就是倒排索引的方式。
传统的保存数据的方式都是
记录→单词
而倒排索引的保存数据的方式是
单词→记录
例如
搜索“红海行动”
但是数据库中保存的数据如图:
那么搜索引擎是如何能将两者匹配上的呢?
基于分词技术构建倒排索引:
首先每个记录保存数据时,都不会直接存入数据库。系统先会对数据进行分词,然后以倒排索引结构保存。如下:
然后等到用户搜索的时候,会把搜索的关键词也进行分词,会把“红海行动”分词分成:红海和行动两个词。
这样的话,先用红海进行匹配,得到id=1和id=2的记录编号,再用行动匹配可以迅速定位id为1,3的记录。
那么全文索引通常,还会根据匹配程度进行打分,显然1号记录能匹配的次数更多。所以显示的时候以评分进行排序的话,1号记录会排到最前面。而2、3号记录也可以匹配到。
全文检索工具elasticsearch
lucene与elasticsearch
咱们之前讲的处理分词,构建倒排索引,等等,都是这个叫lucene的做的。那么能不能说这个lucene就是搜索引擎呢?
还不能。lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。
好比lucene是类似于jdk,而搜索引擎软件就是tomcat 的。
目前市面上流行的搜索引擎软件,主流的就两款,elasticsearch和solr,这两款都是基于lucene的搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作,修改、添加、保存、查询等等都十分类似。就好像都是支持sql语言的两种数据库软件。只要学会其中一个另一个很容易上手。
从实际企业使用情况来看,elasticSearch的市场份额逐步在取代solr,国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的
elasticSearch的使用场景
- 为用户提供按关键字查询的全文搜索功能。
- 著名的ELK框架(ElasticSearch,Logstash,Kibana),实现企业海量日志的处理分析的解决方案。大数据领域的重要一份子。
elasticSearch的安装
elasticsearch的基本概念
cluster |
整个elasticsearch 默认就是集群状态,整个集群是一份完整、互备的数据。 |
node |
集群中的一个节点,一般只一个进程就是一个node |
shard |
分片,即使是一个节点中的数据也会通过hash算法,分成多个片存放,默认是5片。 |
index |
相当于rdbms的database, 对于用户来说是一个逻辑数据库,虽然物理上会被分多个shard存放,也可能存放在多个node中。 |
type |
类似于rdbms的table,但是与其说像table,其实更像面向对象中的class , 同一Json的格式的数据集合。 |
document |
类似于rdbms的 row、面向对象里的object |
field |
相当于字段、属性 |
利用kibana学习 elasticsearch restful api (DSL)
执行bin目录下的kibana程序:
cd /opt/kibana-5.6.4-linux-x86_64/bin
./kibana
es中保存的数据结构
public class Movie {
String id;
String name;
Double doubanScore;
List<Actor> actorList;
}
public class Actor{
String id;
String name;
}
这两个对象如果放在关系型数据库保存,会被拆成2张表,但是elasticsearch是用一个json来表示一个document。
所以他保存到es中应该是:
{ “id”:”1”, “name”:”operation red sea”, “doubanScore”:”8.5”, “actorList”:[ {“id”:”1”,”name”:”zhangyi”}, {“id”:”2”,”name”:”haiqing”}, {“id”:”3”,”name”:”zhanghanyu”} ] } |
对数据的操作增删改查
查看es中有哪些索引
GET /_cat/indices?v |
es 中会默认存在一个名为.kibana的索引
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open .kibana sBDZ-v6YQMWx9GaQOmSQQg 1 1 1 0 3.2kb 3.2kb
表头的含义
health |
green(集群完整) yellow(单点正常、集群不完整) red(单点不正常) |
status |
是否能使用 |
index |
索引名 |
uuid |
索引统一编号 |
pri |
主节点几个 |
rep |
从节点几个 |
docs.count |
文档数 |
docs.deleted |
文档被删了多少 |
store.size |
整体占空间大小 |
pri.store.size |
主节点占 |
增加一个索引
PUT /movie_index |
{ "acknowledged": true, "shards_acknowledged": true, "index": "movie_index" }
删除一个索引
ES 是不删除也不修改任何数据
DELETE /movie_index |
{ "acknowledged": true }
新增文档
格式 :PUT /index/type/id
PUT /movie_index/movie/1 { "id":1, "name":"operation red sea", "doubanScore":8.5, "actorList":[ {"id":1,"name":"zhang yi"}, {"id":2,"name":"hai qing"}, {"id":3,"name":"zhang han yu"} ] } PUT /movie_index/movie/2 { "id":2, "name":"operation meigong river", "doubanScore":8.0, "actorList":[ {"id":3,"name":"zhang han yu"} ] } PUT /movie_index/movie/3 { "id":3, "name":"incident red sea", "doubanScore":5.0, "actorList":[ {"id":4,"name":"zhang chen"} ] } |
如果之前没建过index或者type,es 会自动创建。
直接用id查找
GET movie_index/movie/1 |
修改—整体替换
和新增没有区别
PUT /movie_index/movie/3 { "id":"3", "name":"incident red sea", "doubanScore":"5.0", "actorList":[ {"id":"1","name":"zhang chen"} ] } |
修改—某个字段
POST movie_index/movie/3/_update { "doc": { "doubanScore":"7.0" } } |
修改—某个字段和 修改—整体替换二者选一,否则:
删除一个document
DELETE movie_index/movie/3 |
{ "found": true, "_index": "movie_index", "_type": "movie", "_id": "3", "_version": 18, "result": "deleted", "_shards": { "total": 2, "successful": 1, "failed": 0 } }
搜索type全部数据
GET movie_index/movie/_search |
结果
{ "took": 2, //耗费时间 毫秒 "timed_out": false, //是否超时 "_shards": { "total": 5, //发送给全部5个分片 "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 3, //命中3条数据 "max_score": 1, //最大评分 "hits": [ // 结果 { "_index": "movie_index", "_type": "movie", "_id": 2, "_score": 1, "_source": { "id": "2", "name": "operation meigong river", "doubanScore": 8.0, "actorList": [ { "id": "1", "name": "zhang han yu" } ] } 。。。。。。。。 。。。。。。。。 } |
按条件查询(全部)
GET movie_index/movie/_search { "query":{ "match_all": {} } } |
按分词查询
GET movie_index/movie/_search { "query":{ "match": {"name":"red"} } } |
注意结果的评分
按分词子属性查询
GET movie_index/movie/_search { "query":{ "match": {"actorList.name":"zhang"} } } |
结果:
{ "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 2, "max_score": 1, "hits": [ { "_index": "movie_index", "_type": "movie", "_id": "2", "_score": 1, "_source": { "id": 2, "name": "operation meigong river", "doubanScore": 8, "actorList": [ { "id": 3, "name": "zhang han yu" } ] } }, { "_index": "movie_index", "_type": "movie", "_id": "1", "_score": 1, "_source": { "id": 1, "name": "operation red sea", "doubanScore": 8.5, "actorList": [ { "id": 1, "name": "zhang yi" }, { "id": 2, "name": "hai qing" }, { "id": 3, "name": "zhang han yu" } ] } } ] } }
match phrase
GET movie_index/movie/_search { "query":{ "match_phrase": {"name":"operation red"} } } |
按短语查询,不再利用分词技术,直接用短语在原始数据中匹配
我就不发结果了,太长的博客也不好看。
fuzzy查询
GET movie_index/movie/_search { "query":{ "fuzzy": {"name":"rad"} } } |
校正匹配分词,当一个单词都无法准确匹配,es通过一种算法对非常接近的单词也给与一定的评分,能够查询出来,但是消耗更多的性能。
过滤--查询后过滤
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red"}
},
"post_filter":{
"term": {
"actorList.id": 3
}
}
}
————————————————
版权声明:本文为CSDN博主「我是廖志伟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/java_wxid/article/details/86543331过滤--查询后过滤
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red"}
},
"post_filter":{
"term": {
"actorList.id": 3
}
}
}
先查询后过滤效率慢,好比,我先从全国所有人中先过滤其他省份的留下广东的,再查询比先查询全国所有人再过滤广东的
过滤--查询前过滤(推荐)
GET movie_index/movie/_search
{
"query":{
"bool":{
"filter":[ {"term": { "actorList.id": "1" }},
{"term": { "actorList.id": "3" }}
],
"must":{"match":{"name":"red"}}
}
}
}
过滤--按范围过滤
GET movie_index/movie/_search
{
"query": {
"bool": {
"filter": {
"range": {
"doubanScore": {"gte": 8}
}
}
}
}
}
关于范围操作符:
gt |
大于 |
lt |
小于 |
gte |
大于等于 |
lte |
小于等于 |
排序
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
}
, "sort": [
{
"doubanScore": {
"order": "desc"
}
}
]
}
这个先按名称后按red sea排序
分页查询
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"from": 1,
"size": 1
}
指定查询的字段
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"_source": ["name", "doubanScore"]
}
太多了,但我坚持,希望尽最大努力把我知道的全部写出来。
高亮
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
},
"highlight": {
"fields": {"name":{} }
}
}
聚合
取出每个演员共参演了多少部电影
GET movie_index/movie/_search
{
"aggs": {
"groupby_actor": {
"terms": {
"field": "actorList.name.keyword"
}
}
}
}
每个演员参演电影的平均分是多少,并按评分排序
GET movie_index/movie/_search
{
"aggs": {
"groupby_actor_id": {
"terms": {
"field": "actorList.name.keyword" ,
"order": {
"avg_score": "desc"
}
},
"aggs": {
"avg_score":{
"avg": {
"field": "doubanScore"
}
}
}
}
}
}
关于mapping的类型
之前说type可以理解为table,那每个字段的数据类型是如何定义的呢
查看看mapping
GET movie_index/_mapping/movie |
{ "movie_index": { "mappings": { "movie": { "properties": { "actorList": { "properties": { "id": { "type": "long" }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } }, "doubanScore": { "type": "float" }, "id": { "type": "long" }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } } }
实际上每个type中的字段是什么数据类型,由mapping定义。
但是如果没有设定mapping系统会自动,根据一条数据的格式来推断出应该的数据格式。
true/false → boolean
1020 → long
20.1 → double
“2017-02-01” → date
“hello world” → text +keyword
默认只有text会进行分词,keyword是不会分词的字符串。
mapping除了自动定义,还可以手动定义,但是只能对新加的、没有数据的字段进行定义。一旦有了数据就无法再做修改了。
注意:虽然每个Field的数据放在不同的type下,但是同一个名字的Field在一个index下只能有一种mapping定义。
中文分词
elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。
分词器下载网址:https://github.com/medcl/elasticsearch-analysis-ik
安装
下载好的zip包,请解压后放到 /usr/share/elasticsearch/plugins/
cp /opt/elasticsearch-analysis-ik-5.6.4.zip elastic
unzip elastic
rm elastic
然后重启es
service elasticsearch stop
service elasticsearch start
测试使用
使用默认
GET movie_index/_analyze
{
"text": "我是中国人"
}
请观察结果
使用分词器
GET movie_index/_analyze
{ "analyzer": "ik_smart",
"text": "我是中国人"
}
请观察结果
另外一个分词器
ik_max_word
GET movie_index/_analyze
{ "analyzer": "ik_max_word",
"text": "我是中国人"
}
请观察结果
能够看出不同的分词器,分词有明显的区别,所以以后定义一个type不能再使用默认的mapping了,要手工建立mapping, 因为要选择分词器。
基于中文分词搭建索引
1、建立mapping
PUT movie_chn
{
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text"
, "analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
插入数据
PUT /movie_chn/movie/1
{ "id":1,
"name":"红海行动",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"张译"},
{"id":2,"name":"海清"},
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn/movie/2
{
"id":2,
"name":"湄公河行动",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"张涵予"}
]
}
PUT /movie_chn/movie/3
{
"id":3,
"name":"红海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"张晨"}
]
}
查询测试
GET /movie_chn/movie/_search
{
"query": {
"match": {
"name": "红海战役"
}
}
}
GET /movie_chn/movie/_search
{
"query": {
"term": {
"actorList.name": "张译"
}
}
}
自定义词库
修改/usr/share/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://192.168.67.163/fenci/myword.txt</entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
按照标红的路径利用nginx发布静态资源
在nginx.conf中配置
server {
listen 80;
server_name 192.168.67.163;
location /fenci/ {
root es;
}
}
并且在/usr/local/nginx/下建/es/fenci/目录,目录下加myword.txt
myword.txt中编写关键词,每一行代表一个词。
然后重启es服务器,重启nginx。
在kibana中测试分词效果
更新完成后,es只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:
POST movies_index_chn/_update_by_query?conflicts=proceed
有点长了,分章节吧,第三章继续写。