拉呱,无论是当作全文检索工具,还是仅仅当作NOSQL,Elasticsearch的性能,牛的没法说!!!奈何和它相见恨晚
全文检索-像淘宝京东类似的网上商城,当我们在在搜索框搜索某个商品名称时,网络没有问题的话,获取响应的速度,几乎和我们键盘起落的速度是一致的,这足以ES的魅力,亿万级别的搜索,秒秒钟完事
一个正规的项目运行过程中,日志的产出是源源不断的,把日志的文本信息导入到ES,使用它的聚合功能轻松获取我们关系的内容,结合Kibana做图表分析,可视化日志记录,动态分析
稍微解释下, 全文检索可以片面的理解成是从敲代码的人的角度上说的,假如你是用户,你会关心什么全文检索? 用户关系的是搜索结果!那好,对我们敲代码的来说,就是1.拿到用户搜索框输入的关键字送进ES (数据在存进es的时候,es对他们进行了索引)2.ES的程序会对关键字进行分词,拿着这些碎片去ES中的索引库里面的type中的field中匹配比对,那,那么多field,它和谁比对呢? 和标记上text属性的字段比对)
另外要了解的内容1.全文检索只处理文本不处理语义,2.全文检索忽略英文的大小写 3.结果可能不止一个,并且有相关得分
mysql | Elasticsearch |
---|---|
Database 数据库 | indices 索引 |
Table 数据表 | type 类型 |
Row 行 | document 文档 |
Columns 列 | Field 字段 |
其实大多数人都是先接触的关系型数据库,我也是,可能一开始感觉着好别扭,但是再回头来看,好像他的名字比传统的关系型数据库名什么行啊列啊,更合理一些
{% asset_img 1.png 图片 %}
通过图片可以看到: 数据分成三部分,分别存放在三个分片里面,而且每个分片都有自己的副本,这样就算是一台es,它同样是分布式
这部分纯属扯皮了,不说语法,说一下我对它的感觉,首先呢, 为什么学它? 图方便快速呗,大部分情况下,是需要使用它的全文检索功能,但是总得有个下手点吧,不用说一开始都是环境配置一顿整,访问个9200看到版本号,也算是开个头了,然后呢? 先不用想他怎么检索,怎么花里胡哨的检索,我们得知道自己想检索什么!先把数据给它,思路就来了,先去搞数据,下一步自然就是创建新的索引(数据库),循环把我们的数据送进es的索引里面.到这里,也算是完成一半的任务了,下面就是使用人家提供好的api去索引库,检索就好了. 先有个大概的思路.想干什么,怎么干,往下学
下面会有一些关键字 也就是json的 key部分,对我们来说,一般可以见名知意
PUT /xiaowu1
{
"settings": {
"number_of_shards": 1
, "number_of_replicas": 1
}
}
成功的相应:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "xiaowu1"
}
GET /xiaowu1
响应:相关的源信息
{
"xiaowu1": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1552653562807",
"number_of_shards": "1",
"number_of_replicas": "1",
"uuid": "u0tMDD-pQXaHb77cTPorZA",
"version": {
"created": "6020499"
},
"provided_name": "xiaowu1"
}
}
}
}
在玩搜索之前我们要思考如何定义文档的,如文档包括哪些字段,这些字段是否保存,是否索引,是否分词等等, 实际上就像是在描述我们数据库中的一张表,有哪些列,列是什么类型的,是主键不?自增长不?
以后看到"mappings":{} 就像查看关系型数据库中对整张表的定义一样
PUT /xiaowu/_mapping/goods
{
"properties": { ---属性关键字
"name":{ ---name为字段名,可以不止一个,就像数据库中的列,你开心,多少个都行
"type": "text", ---类型常用:integer,long,text,object,date,short,string,float
"index": true, ---是否索引 默认true
"store": false, ---是否存储 默认false
"analyzer": "ik_max_word" ---是否分词
},
"images": {
"type": "keyword",
"index": "false"
},
"age": {
"type": "float"
}
}
}
1 .字符串类型有两种
2 .数值类型
{girl:{name:"张三",age:23}
而是会把它处理成 girl.name和girl.age
5 .store
POST /changwu/item/
{
"title":"小米手机",
"price":2699.00
}
响应:
{
"_index": "changwu",
"_type": "item",
"_id": "Pj6bgWkB3eQnUSvRfoa2",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 6
}
id,是自动生成的当前数据的标识,我们还可以指定
POST /changwu/item/2
GET /索引库名/_search
{
"query": {
"查询类型":{
"查询条件": "条件值"
}
}
}
使用,SpringDataElasticsearch玩复杂查询的时候,免不了会BuildQueryXXX
or 关系
match类型查询,会把查询条件进行分词,然后再查询,词条之间是or关系,按照相关性得分排序
GET /goods/_search
{
"query": {
"match":{
"price": 269900
}
}
}
and关系
很多情况下我们希望更精确的查找,于是我们使用 and关系
GET /xiaowu/_search
{
"query":{
"match":{
"title":{
"query":"米手机",
"operator":"and"
}
}
}
}
这样他在分词的时候,米--手机同时都匹配上才会显示出结果!
假设有这样一种情况,用户给定的条件分词后,有五个词,但是其中的四个是在描述他想要搜索的内容,如果使用or,毫无疑问,一大堆杂七杂八的东西被查询出来,如果使用and,es很可能把那个目标文档排除,那该怎么办呢?看下面!
GET /xiaowu/_search
{
"query":{
"match":{
"title":{
"query":"米手机",
"minimum_should_match":"75%"
}
}
}
}
意思是,用户输入的词条满足75%的匹配程,我就认为是匹配上了, ---用户输入的检索条件,被分解为三个词,3*0.75=2.25 也就是说,这三个词,至少有两个是匹配上的,es就认为匹配成功
GET /xiaowu/_search
{
"query":{
"multi_match":{
"query":"米",
"fields":["title"]
}
}
}
他的fields接受一个字段数组
GET /xiaowu/_search
{
"query": {
"term": {
"price": {
"value": "1888"
}
}
}
}
默认情况下,elasicsearch在搜索的结果在,会把文档保存在_source里面的所有字段都返回,如果我们想获取里面的部分结果,添加_soure过滤
GET /xiaowu/_search
{
"_source": ["过滤字段"],
"query": {
"term": {
"price": {
"value": "1888"
}
}
}
}
解读查询结果:
{
"took": 1, ----花费时长,单位毫秒
"timed_out": false, ----是否超时
"_shards": { --- 分片信息
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": { --- 命中的结果
"total": 1, --- 结果总数
"max_score": 4.765019, ---相关性的最高得分
"hits": [ --- 搜索结果的对象数组
{
"_index": "goods", --- 索引
"_type": "docs", --- 类型
"_id": "180", --- 唯一id
"_score": 4.765019, ---文档得分
"_source": { --- 文档的数据源,(前面说过,结果都在_source里面)
"id": 180,
...
}
GET _search
{
"query": {
"match": {
"title": "小米2手机"
}
}
}
POST /xiaowu/goods/
{
"title":"es666",
"hehe":true
}
查询结果:
{
"_index": "xiaowu",
"_type": "goods",
"_id": "Qz69gWkB3eQnUSvRC4ah",
"_score": 1,
"_source": {
"title": "es666",
"hehe": true
}
查看映射
{
"xiaowu": {
"mappings": {
"goods": {
"properties": {
"age": {
"type": "float"
},
"hehe": {
"type": "boolean"
},
"images": {
"type": "keyword",
"index": false
},
"name": {
"type": "text",
"store": true,
"analyzer": "ik_max_word"
},
"price": {
"type": "float"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
这样看,其实我们在上面配置的映射关系就没有必要了,Elasticsearch会智能化的推断出字段的type,比如true它推断为boolean, 但是有问题的是title 它推断为 text ,title.keyword是 keyword类型
把上面的POST转变成PUT就是修改
语法: DELETE /索引库名/类型名/id
看得懂,会使用Elasticsearch的高级玩法很重要,这关系着,能不能理解如何使用它的原生api进行高级查询
GET /xiaowu/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "米"
}
}
],
"must_not": [
{
"match": {
"title": "大"
}
}
],
"should": [
{
"match": {
"price": "999"
}
}
]
}
}
}
GET /xiaowu/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10,
"lte": 20
}
}
}
}
关键字 | 含义 |
---|---|
gte | 大于等于 |
lte | 小于等于 |
gt | 大于 |
lt | 小于 |
模糊查询很人性化,它允许用户在编辑的条件有拼写错误,但是出现偏差的编辑差距不得超过2
GET /xiaowu/_search
{
"query": {
"fuzzy": {
"title": {
"value": "appla",
"fuzziness": 1
}
}
}
}
1.有条件查询进行过滤
查询就会影响文档的相关性得分,假如我们只是想在现有的查询基础上,根据需求过滤下一些结果,却不想影响文档的相关性得分的话filter就派上用场了
GET /changwu/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小米手机" }},
"filter":{
"range":{"price":{"gt":2000.00,"lt":3800.00}}
}
}
}
}
filter中还可以在bool组合过滤
如果一次查询只有过滤,没有查询条件,不希望进行评分,我们可以使用constant_score
GET /changwu/_search
{
"query":{
"constant_score": {
"filter": {
"range":{"price":{"gt":2000.00,"lt":3000.00}}
}
}
}
排序sort和query是有先后顺序的,先query,再排序
GET /changwu/_search
{
"query": {
"match": {
"title": "手机"
}
},
"sort": [
{
"price": {
"order": "asc"
}
}
]
}
看上面的sort后面的条件,是个数组,因此我们可以写条件,按多个字段进行排序
当然Elasticsearah里面分桶的方式很多
常用的度量方法:
es中进行过滤,排序,聚合的字段,不能被分词!!!!*
GET /cars/_search
{
"size": 0,
"aggs": {
"popular_brand": {
"terms": {
"field": "color"
},
"aggs": {
"priceAvg": {
"avg": {
"field": "price"
}
}
}
}
}
}
一般都是先聚为桶,然后在桶的基础上进行度量
{% asset_img 2.png 图片 %}
这是原来画的图,JEST直接放弃了,让我们自己拼接json串,简直只有难受
终于到编码阶段,这部分相比前面的原生api看起来就好受多了,Spring一整合,啥东西都简单的只剩下两件事,1,写个配置文件,2.用它的方法,玩他的注解--, 当然我现在回顾学习的整个过程,最重要的是还是那句话,不要忘记了自己的需求,不然学着学着容易迷失,不知道自己想干啥,对于Elasticsearch吧先觉的它啥都能干,又觉得它啥也干不了,这是个很尴尬的事情! 那,我用它做全文检索,我就得去搞明白 知道下面那几件事
下面挨个做这几件事!
坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
配置文件(java和它的交互走的是tcp协议)
spring:
application:
name: search-service
data:
elasticsearch:
cluster-nodes: 192.168.43.150:9300
cluster-name: elasticsearch
启动类
实体类在java中就像是接盘侠,啥样的东东,它都有能给接下来,看完了下面的注解,就知道了如何把数据存进es认识的实体类,准备把他们存在索引库
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
@Document(indexName="changwu",type = "item",shards = 1)
public class Item {
@Id
@Field(type=FieldType.Long)
Long id;
@Field(type = FieldType.Text,analyzer = "ik_smart")
String title; //标题
@Field(type=FieldType.Keyword,index = true)
String category;// 分类
@Field(type=FieldType.Keyword)
String brand; // 品牌
@Field(type=FieldType.Double)
Double price; // 价格
@Field(type=FieldType.Keyword,index = false)
String images; // 图片地址
}
另外不要忘记了它的智能推断,如果我们不在字段上添加@Field,他根据值的类型进行推断
比如: 下面三个都会被推断为long类型,
private Long cid3;//
private Date createTime;//
private List<Long> price;//
索引库的创建放到text里面就ok了
创建索引,添加映射,删除索引
template.createIndex(Goods.class);
template.putMapping(Goods.class);
template.deleteIndex()
SpringData的强大之后就是,我们不再去写dao层了,她会通过反射给我们写好,有点Mybatis里面的通用mapper的意思,但是它更强大,---你给方法名它自动的生成方法
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> {}
使用 repository点一下,findXX saveXXX, deleteXXX全出来了,不再细说
如果可以看懂原生的语法,那么对他的使用就不多说了...就是无脑使用
到了这,就知道了如何把通过java代码,对索引库里面的数据进行简单的增删改查
真正使用es的时候,Repository里面的方法可以满足大部分的功能,但是聚合,过滤的话,只能使用原生的API
假设我们有下面的需求: 前端把用户需要搜索的信息收集起来了--全文检索
/**
* 全文检索
*/
@Test
public void textQuery(){
//创建查询构建器
NativeSearchQueryBuilder QueryBuilder = new NativeSearchQueryBuilder();
/**
* 给查询构造器添加条件
* 1. 它仍然需要的是一个 QueryBuilder ,通过QueryBuilders里面的静态方法创建,间接继承了QueryBuild
* 2. 可也看到,基本上所有常用的条件查询都有了, bool , 词条term , match , 模糊查询 fuzzy
* 3. 我们也可以抽出来一个方法, 方法里使用bool查询, 他支持先添加查询,紧接着过滤, 还记不记得那个 match{},filter:{}
* 3.1 注意区分开结果过滤_source 和 filter
*
* matchQuery @Param field
* matchQuery @Param field的值
* */
QueryBuilder.withQuery(QueryBuilders.matchQuery("title","小米"));
/**
* 结果过滤
* @Param : SourceFilter ,但是它是和接口,于是我用它唯一的实现类
*/
QueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"title","price"},null));
/**
* 排序
*/
QueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
/**
* 分页
* 原生的api使用下面两个字段控制
* "from": 0,
* "size":10
* 但是注意,他是的第一页是0
* @Param : Pageable 他是个接口,我们使用它的实现类 PageRequest(静态方法of)
*/
QueryBuilder.withPageable(PageRequest.of(1,10));
Page<Goods> result = repository.search(QueryBuilder.build()); //它仍然需要的是一个QueryBuilder , 通过构造器.build()构建出来
//解析结果
long elements = result.getTotalElements();
int totalPages = result.getTotalPages();
List<Goods> content = result.getContent();
}
/**
* 创建基本的查询条件
* 创建布尔查询,
* 一部分当作查询条件(must)
* 一部分当作过滤条件(filter)
* @param searchRequest
* @return QueryBuilder 给QueryBuild.withQuery()使用
*/
private QueryBuilder buildBasicQuery(SearchRequest searchRequest) {
//创建布尔查询
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
//查询条件
queryBuilder.must(QueryBuilders.matchQuery("字段名",字段值));
if(过滤条件){
/*
*把处理好的字段,传给过滤器 过滤
*@param name The name of the field
*@param value The value of the term
*/
queryBuilder.filter(QueryBuilders.termQuery(字段名,字段值));
}
return queryBuilder;
}
{% asset_img 3.png 图片 %}
/**
* 聚合查询
*/
@Test
public void textAgg(){
//同样少不了 查询构造器
NativeSearchQueryBuilder QueryBuilder = new NativeSearchQueryBuilder();
/**
* 添加聚合add, 可以聚合多次
* @Param AbstractAggregationBuilder 它间接继承与 AggregationBuilder 我们下面的工具类
*
* AggregationBuilders下面基本上涵盖了我们所有的聚合方式
*/
QueryBuilder.addAggregation(AggregationBuilders.terms("popular_brand").field("brand"));
/**
* 推荐使用和这个,支持聚合查询,并返回带聚合的结果
* @Param :SearchQuery
* @Param :Class<T>
* @Return:
*/
AggregatedPage<Goods> result = template.queryForPage(QueryBuilder.build(), Goods.class);
/**
* 解析聚合
*
*/
Aggregations aggregations = result.getAggregations();
// 获取指定名称 聚合
//Aggregations agg = aggregations.get("popular_brand");
StringTerms agg = aggregations.get("popular_brand");
/**
* 问题来了, 不存在 agg.getBuckets()
* 原因看上面的图,是 Aggregations是个顶级接口,
*/
List<StringTerms.Bucket> buckets = agg.getBuckets();
//遍历buckets
for (StringTerms.Bucket bucket : buckets) {
System.out.println(bucket.getKeyAsString());
System.out.println(bucket.getDocCount());
}
}
原文:https://www.cnblogs.com/ZhuChangwu/p/11150374.html