【商城】Elasticsearch搜索引擎-01.构建智能搜索系统之查询-实战

本文详细介绍了Elasticsearch的各种查询类型,如精确匹配、模糊查询、范围查询等,并重点讲解了组合查询如bool和dis_max,同时展示了使用OkHttp作为搜索客户端的方法。涵盖了电商搜索场景的典型需求。

我们都知道,Elasticsearch作为搜索引擎被广泛应用在各个领域,尤其是电子商务网站和App的检索,如下几乎囊括的商城大部分搜索使用场景查询定义,其中包括:

基本查询:简单的查询方式
  • terms:目标列包含指定词(不分词)
  • match:会把输入的“短语”进行分解成分词,分词器可自定义
  • bool match:match分解成多个词,并且是and的关系,默认是or的关系
  • match phrase:进行分词,但要指定分词之间词语的个数
  • match phrase prefix:进行分词,分词按前缀匹配
  • multi-match:作用于多个fields的match查询
  • query string:支持lucence查询语法,title:crime10+title:punishment-otitle:cat,用±表示include和exclude,可以用表示权重,例如: field^2表示权重为2,默认权重为1
  • field查询:是queyr查询的简化版本
  • ids查询:field查询的特殊情况,只针对id列
  • prefix查询:类似于term查询,按前缀必须匹配
  • fuzzy like this查询:相似文本的查询,怎么计算相似度呢
  • fuzzy like this field查询:选定title
  • fuzzy查询:模糊查询,可定义词语之间的编辑距离(fuzziness)得到
  • wildcard查询:使用* ?的term查询
  • more like this:like且设定好范围
  • range:针对数值类型范围查询,from to

Filter过滤查询 ,对查询结果过滤
  • term{“year”:2021},过滤结果中year为2021的数据
  • range :过滤结果中指定列在给定范围的数据l
  • exists :查询结果中存在某列的数据
  • missing :查询结果中缺失某列的数据
  • scripts:bool表达式 ,查询结果中满足脚本指定条件的数据
  • type :按类型过滤
  • limit :限定返回一定数量的结果
  • ids :限定返回特殊的id集合
  • not,and,or :多个条件的组合查询

组合查询 :多个条件的组合查询
  • bool:{“must”:{查询A},“should”:{查询B}}
  • boosting:查询权重,positive的分数增高,negative分数降低
  • costant score:恒定分数;
  • indices:实现在多个Index上查询;
  • custom filters score:

另外,针对Elasticsearch搜索场景,此处不选择官方提供的查询客户端(后期会@Deprecated),而是使用 okhttp 作为ES检索查询客户端,可能很多伙计都知道OkHttp是一个高效的HTTP客户端,被业界广泛应用在Http请求场景,它有以下特性:

  • 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接 连接池减少请求延时 透明的
  • GZIP压缩减少响应数据的大小
  • 缓存响应内容,避免一些完全重复的请求

1、定义查询大全

基础BaseQuery定义,以及其他枚举类型定义;

@Getter
public abstract class BaseQuery<Q> implements Serializable {

    private static final long serialVersionUID = -8937265256480681309L;
    /**
     * Field查询权重
     */
    private Float boost;

    public final Q boost(Float boost){
        this.boost = boost;
        //noinspection unchecked
        return (Q)this;
    }
}

 public static enum  ZeroTerms {
    /** zero terms*/
    NONE,
    ALL
   }
  public enum  Operator {
    /**操作方向*/
    OR,
    AND
   }
 public enum  Order {
    /**排序方向*/
    ASC,
    DESC
 }
@Getter
@Builder
public class Sort implements Serializable {
    private static final long serialVersionUID = -5574272690024012413L;
    /**
     * 排序字段
     */
    private String field;
    /**
     * 排序指令
     */
    private Order order;
    /**
     * 脚本排序
     */
    private Script script;
    /**
     * 脚本排序Filed数据类型
     */
    private String type;
}
1.1 id查询
/** 精确查询:文档主键Id列表查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:18
 */
@Getter
@Builder
@EqualsAndHashCode(callSuper = true)
public class IdsQuery extends BaseQuery<IdsQuery> {
    private static final long serialVersionUID = -5818467179482171316L;
    /**
     * ids值列表
     */
    private Set<String> values;

    public IdsQuery addValue(String value){
        if (null == values){
            values = Sets.newHashSet();
        }
        values.add(value);
        return this;
    }

    public IdsQuery addValues(Collection<String> values){
        if (null == this.values){
            this.values = Sets.newHashSet();
        }
        this.values.addAll(values);
        return this;
    }
}
1.2 Exists(存在)查询
 /** 存在查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:18
 */
@Getter
@Builder
@EqualsAndHashCode(callSuper = true)
public class ExistsQuery extends BaseQuery<ExistsQuery> {
    private static final long serialVersionUID = 1738703878906147701L;
    /**
     * 判断字段
     */
    private String field;
}
1.3 Wildcard(通配符)查询
/* * 通配符模糊匹配查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:19
 */
@Getter
@Builder
public class WildcardQuery extends BaseQuery<WildcardQuery> {
    private static final long serialVersionUID = -2682500922726969971L;
    /**
     * 属性值
     */
    private String field;
    /**
     * 属性value
     */
    private String value;
}
1.4 Range(范围)查询
/** 范围查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:19
 */
@Getter
@Builder
public class RangeQuery extends BaseQuery<RangeQuery> {
    private static final long serialVersionUID = 718814285116379481L;
    /**
     * 属性
     */
    private String field;
    /**
     * 开始值
     */
    private Object from;
    /**
     * 结束值
     */
    private Object to;
    /**
     * 是否包括下边界,默认true
     */
    private Boolean includeLower;
    /**
     * 是否包括上边界,默认true
     */
    private Boolean includeUpper;

    public RangeQuery from(Object from){
        this.from = from;
        return this;
    }

    public RangeQuery to(Object to){
        this.to = to;
        return this;
    }

    public RangeQuery includeLower(Boolean includeLower){
        this.includeLower = includeLower;
        return this;
    }

    public RangeQuery includeUpper(Boolean includeUpper){
        this.includeUpper = includeUpper;
        return this;
    }
}
1.5 Term(精确)查询
/** 精确匹配查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:19
 */
@Getter
@Builder
public class TermQuery extends BaseQuery<TermQuery> {
    private static final long serialVersionUID = -7457986485757144952L;
    /**
     * 匹配字段
     */
    private String field;
    /**
     * 匹配值
     */
    private Object value;
}
1.6 Terms(精确)多查询
* 多属多值精确匹配查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:19
 */

@Getter
@Builder
public class TermsQuery extends BaseQuery<TermsQuery> {
    private static final long serialVersionUID = -6528812410794945781L;
    /**
     * 属性
     */
    private String field;
    /**
     * 值列表
     */
    private List<Object> values;

    public TermsQuery addValue(String value) {
        if (null == values) {
            values = Lists.newArrayList();
        }
        values.add(value);
        return this;
    }

    public TermsQuery addValue(Object value) {
        if (null == values) {
            values = Lists.newArrayList();
        }
        values.add(value);
        return this;
    }

    public TermsQuery addValues(List values) {
        if (null == this.values) {
            this.values = Lists.newArrayList();
        }
        this.values.addAll(values);
        return this;
    }

    public TermsQuery addValues(Collection values) {
        if (null == this.values) {
            this.values = Lists.newArrayList();
        }
        this.values.addAll(values);
        return this;
    }
}
1.7 Match(分词匹配)查询
/** 分词匹配查询 
 * 例如:“中国杭州”->分词为“中国”、"杭州" 
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:18
 */
@Getter
@Builder
public class MatchQuery extends BaseQuery<MatchQuery> {
    private static final long serialVersionUID = 4875762311528902561L;
    /**
     * 匹配字段
     */
    private String field;
    /**
     * 查询类型,默认boolean
     * boolean(布尔):分词器对字符串分词,然后进行相应的子查询
     * phrase(短语):查询字符串,匹配所有分词
     * phrase_prefix(短语前缀):查询字符串,前缀匹配
     */
    private String type;
    /**
     * 查询值(字符串)
     */
    private Object query;
    /**
     * 分词器
     */
    private String analyzer;
    /**
     * 单个字段字符串如何匹配查询条件的分词,默认or
     */
    private Operator operator;
    /**
     * 容忍度:数据不匹配且无法转换时候报错,默认false
     * 设置为true时:忽略报错
     */
    private Boolean lenient;
    /**
     * Stop分词,会将stop words停用词在索引时全部去掉
     * 默认为None:搜索不到停止词
     */
    private ZeroTerms zeroTermsQuery;
    /**
     * 匹配词最大距离,例如:god->good,编辑距离1
     * 模糊度(莱文斯坦距离),默认Auto(一般默认即可)
     */
    private String fuzziness;
    /**
     * 前置精确匹配长度:与fuzziness使用,不能模糊化初始化字符串长度,默认0
     * 比如:拼写错误goosd->goods,设置为3时,goo必须精确匹配
     */
    private Integer prefixLength;
    /**
     * 控制前置扩展成分词的数量,默认50
     */
    private Integer maxExpansions;
    /**
     * field分词后匹配的百分比,例如80%
     */
    private String minimumShouldMatch;
}
1.8 MultiMatch(分词多匹配)查询
/** 分词匹配查询:多字段Match匹配查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:18
 */
@Getter
@Builder
public class MultiMatchQuery extends BaseQuery<MultiMatchQuery> {
    private static final long serialVersionUID = -734174780787419253L;
    /**
     * 查询多字段
     */
    private List<String> fields;
    /**
     * 查询值
     */
    private String query;
    /**
     * 查询类型:默认best_fields
     * best_fields:匹配【从指定字段】查询每个字段评分,求最高评分
     * most_fields:匹配【从指定字段】查询每个字段评分,求平均分
     * cross_fields:把query拆分成分词,然后在各个字段执行匹配查询,默认情况下,只要有一个字段匹配就返回文档
     * phrase:在每个字段上执行查询,返回最高评分,类似best_fields
     * phrase_prefix:在每个字段上执行前缀查询,返回最高评分,类似best_fields
     */
    private String type;
    /**
     * 平局参数:将其他field匹配查询语句的评分也考虑其中
     * dis_max:查询值会使用单个最佳匹配语句_score作为整体评分
     * 提供了dis_max和bool之间的折中选择,评分方式如下:
     * 1、获得最佳匹配语句的评分:_score
     * 2、将其他匹配语句的评分结果与tie_breaker相乘
     * 3、对以上评分求和并做规范化
     * score=best_field.scoreBoot+other_field.scoreBoot*tie_breaker
     */
    private Float tieBreaker;
    /**
     * 分词器
     */
    private String analyzer;
    /**
     * 字段匹配分词间操作,默认OR
     */
    private Operator operator;
    /**
     * 溢出(移动)值:分词间移动间隔
     * 例如:quick for匹配quick brown for,slop设置为1时匹配
     */
    private Integer slop;
    /**
     * 容忍度:数据不匹配且无法转换时候报错,默认false
     * 设置为true时:忽略报错
     */
    private Boolean lenient;
    /**
     * Stop分词,会将stop words停用词在索引时全部去掉
     * 默认为None:搜索不到停止词
     */
    private ZeroTerms zeroTermsQuery;
    /**
     * 匹配词最大距离,例如:god->good,编辑距离1
     * 模糊度(莱文斯坦距离),默认Auto(一般默认即可)
     */
    private String fuzziness;
    /**
     * 前置精确匹配长度:与fuzziness使用,不能模糊化初始化字符串长度,默认0
     * 比如:拼写错误goosd->goods,设置为3时,goo必须精确匹配
     */
    private Integer prefixLength;
    /**
     * 控制前置扩展成分词的数量,默认50
     */
    private Integer maxExpansions;
    /**
     * field分词后匹配的百分比,例如80%
     */
    private String minimumShouldMatch;

    public MultiMatchQuery addFields(List<String> fields) {
        if (null == this.fields) {
            this.fields = Lists.newArrayList();
        }
        this.fields.addAll(fields);
        return this;
    }

    public MultiMatchQuery addField(String field) {
        if (null == this.fields) {
            this.fields = Lists.newArrayList();
        }
        this.fields.add(field);
        return this;
    }
}
1.9 Nested(嵌套)高级查询
/** 嵌套(nested)查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:18
 */
@Getter
@Builder
public class NestedQuery extends BaseQuery<NestedQuery> {
    private static final long serialVersionUID = 2504790838597063911L;

    private String path;
    /**
     * 查询
     */
    private BaseQuery query;
    /**
     * 匹配子对象的分数相关性分数
     */
    private ScoreMode scoreMode;
}
1.10 Script高级查询
/* * script查询
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:19
 */
@Getter
@Builder
public class ScriptQuery extends BaseQuery<ScriptQuery> {
    private static final long serialVersionUID = -3995950246151932374L;

    private Script script;
   @Getter
   @Builder
   public static class Script implements Serializable {

    private static final long serialVersionUID = -9222431184650362263L;

    /**
     * 脚本语言,默认是painless
     */
    private String lang;
    /**
     * 脚本实现的代码
     */
    private String source;
    /**
     * 自定义参数
     */
    private Map<String, Object> params;

    public Script addParam(String key, Object value) {
        if (null == params) {
            params = Maps.newHashMap();
        }
        params.put(key, value);
        return this;
    }
  }
}

2、组合查询

2.1 bool 组合查询

评分为所有子查询评分之和;

/** 组合查询,组合第2部分定义各种组合
 * @author <a href="mailto:974064580@163.com>lee possible</a>
 * @date 2021-02-06 17:17
 */
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class CombineQuery extends BaseQuery<BoolQuery> {
    private static final long serialVersionUID = -3096588829428648257L;
    /**
     * 文档必须匹配查询内容
     */
    public List<BaseQuery> must;
    /**
     * 文档必须不允许匹配查询内容
     */
    public List<BaseQuery> mustNot;
    /**
     * 文档过滤匹配内容,并不影响文档匹配得分
     */
    public List<BaseQuery> filter;
    /**
     * 文档匹配上可以增加相关得分,相当于or
     */
    public List<BaseQuery> should;
    /**
     * 分词后匹配的百分比,例如80%
     */
    private String minimumShouldMatch;

    public BoolQuery addMust(BaseQuery query) {
        if (must == null) {
            must = Lists.newArrayList();
        }
        must.add(query);
        return this;
    }

    public BoolQuery addMustNot(BaseQuery query) {
        if (mustNot == null) {
            mustNot = Lists.newArrayList();
        }
        mustNot.add(query);
        return this;
    }

    public BoolQuery addFilter(BaseQuery query) {
        if (filter == null) {
            filter = Lists.newArrayList();
        }
        filter.add(query);
        return this;
    }

    private BoolQuery addShould(BaseQuery query) {
        if (should == null) {
            should = Lists.newArrayList();
        }
        should.add(query);
        return this;
    }
}
2.2 dix_max 组合查询

评分为与高权重子查询的打分高度相关;

@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class DisMaxQuery extends BaseQuery<DisMaxQuery> {
    private static final long serialVersionUID = -3096588829428648257L;
    /**
     * 平局参数:将其他field匹配查询语句的评分也考虑其中
     */
    private Float tieBreaker;
    /**
     * 文档必须匹配查询内容
     */
    public List<BaseQuery> must;
    /**
     * 文档必须不允许匹配查询内容
     */
    public List<BaseQuery> mustNot;
    /**
     * 文档过滤匹配内容,并不影响文档匹配得分
     */
    public List<BaseQuery> filter;
    /**
     * 文档匹配上可以增加相关得分,相当于or
     */
    public List<BaseQuery> should;
    /**
     * 一个文档分词后匹配的百分比,例如:80%,
     */
    private String minimumShouldMatch;

    public DisMaxQuery addMust(BaseQuery query) {
        if (must == null) {
            must = Lists.newArrayList();
        }
        must.add(query);
        return this;
    }

    public DisMaxQuery addMustNot(BaseQuery query) {
        if (mustNot == null) {
            mustNot = Lists.newArrayList();
        }
        mustNot.add(query);
        return this;
    }

    public DisMaxQuery addFilter(BaseQuery query) {
        if (filter == null) {
            filter = Lists.newArrayList();
        }
        filter.add(query);
        return this;
    }

    private DisMaxQuery addShould(BaseQuery query) {
        if (should == null) {
            should = Lists.newArrayList();
        }
        should.add(query);
        return this;
    }
}

3、OkHttp使用

3.1 构建OkHttp客户端
 /**
     * 构建OkHttp客户端
     */
     private static AtomicReference<OkHttpClient> INSTANCE = new AtomicReference<>();
    public OkHttpClient getClient(){
        for (; ;){
            //Http连接对象
            OkHttpClient singleton = INSTANCE.get();
            if (null != singleton){
                return singleton;
            }
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            //定义属性-自行处理
            HttpPoolProperties pool = properties.getHttpPool();
            if (Objects.nonNull(pool.getCallTimeout())){
                builder.callTimeout(pool.getCallTimeout(), TimeUnit.MILLISECONDS);
            }
            if (Objects.nonNull(pool.getReadTimeout())){
                builder.readTimeout(pool.getReadTimeout(), TimeUnit.MILLISECONDS);
            }
            if (Objects.nonNull(pool.getWriteTimeout())){
                builder.writeTimeout(pool.getWriteTimeout(), TimeUnit.MILLISECONDS);
            }
            if (Objects.nonNull(pool.getConnectTimeout())){
                builder.connectTimeout(pool.getConnectTimeout(), TimeUnit.MILLISECONDS);
            }
            builder.connectionPool(new ConnectionPool(pool.getMaxIdle(), pool.getKeepAlive(), TimeUnit.MILLISECONDS));
            singleton = builder.build();
            if (INSTANCE.compareAndSet(null, singleton)){
                return singleton;
            }
        }
    }
3.2 OkHttp客户端调用
 /**
     * OkHttp 请求封装
     * @param request 请求
     * @return 响应结果
     * @throws IOException 异常
     */
    public HttpResponse call(Request request)throws IOException {
        long startTime = System.currentTimeMillis();
        try {
            Response response = client().newCall(request).execute();
            HttpResponse resp;
            try {
                resp = new HttpResponse();
                resp.setUrl(request.url());
                resp.setCode(response.code());
                resp.setBody(null == response.body() ? null : Objects.requireNonNull(response.body()).string());
                long cost = (System.currentTimeMillis() - startTime) / 1000;
                if (cost > REQUEST_TIMEOUT) {
                    log.error("[ES-Client]执行Http耗时太长,节点:{},耗时:{}", request.url().host(), cost);
                    throw new RuntimeException("ES执行操作耗时太长,请检查!");
                }
            } catch (Exception e) {
                log.error(String.format("请求[ES-Client]发生异常,节点:%s,%d", request.url().host(), request.url().port()), e);
                throw e;
            } finally {
                if (null != response) {
                    response.close();
                }
            }
            return resp;
        } catch (ConnectException e) {
            //踢出lost节点
            httpTargetPool.disconnect(request.url().host());
            throw e;
        }
    }

本节仅叙述Elasticsearch的各种查询定义,以及查询客户端的选择,后续将逐步展开,逐步带大家探索如何构建搜索引擎系统。

继“Java开发微信朋友圈PC版系统-架构1.0”之后,debug这段时间日撸夜撸,终于赶在春节放假前给诸位带来了这一系统的架构2.0版本,特此分享给诸位进行学习,以掌握、巩固更多的技术栈以及项目和产品开发经验,同时也为即将到来的金三银四跳槽季做准备! 言归正传,下面仍然以问答的方式介绍下本门课程的相关内容! (1)问题一:这是一门什么样的课程? 很明显,本门课程是建立在架构1.0,即 第1门课程 的基础上发布的,包含了架构1.0的内容,即它仍然是一门项目、产品实战课,基于Spring Boot2.X + 分布式中间件开发的一款类似“新浪微博”、“QQ空间”、“微信朋友圈”PC版的互联网社交软件,包含完整的门户网前端 以及 后台系统管理端,可以说是一套相当完整的系统! (2)问题二:架构2.0融入了哪些新技术以及各自有什么作用? 本课程对应着系统架构2.0,即第2阶段,主要目标:基于架构1.0,优化系统的整体性能,实现一个真正的互联网社交产品;其中,可以学习到的技术干货非常多,包括:系统架构设计、Spring Boot2.X、缓存Redis、多线程并发编程、消息中间件RabbitMQ、全文搜索引擎Elastic Search、前后端消息实时通知WebSocket、分布式任务调度中间件Elastic Job、Http Restful编程、Http通信OKHttp3、分布式全局唯一ID、雪花算法SnowFlake、注册中心ZooKeeper、Shiro+Redis 集群Session共享、敏感词自动过滤、Java8 等等; A.  基于Elastic Search实现首页列表数据的初始化加载、首页全文检索;B.  基于缓存Redis缓存首页朋友圈“是否已点赞、收藏、关注、评论、转发”等统计数据;整合Shiro实现集群部署模式下Session共享;C.  多线程并发编程并发处理系统产生的废弃图片、文件数据;D.  基于Elastic Job切片作业调度分布式多线程清理系统产生的废弃图片;E.  基于RabbitMQ解耦同步调用的服务模块,实现服务模块之间异步通信;F.  基于WebSocket实现系统后端 与 首页前端 当前登录用户实时消息通知;G.  基于OKHttp3、Restful风格的Rest API实现ES文档、分词数据存储与检索;H.  分布式全局唯一ID 雪花算法SnowFlake实现朋友圈图片的唯一命名;I.  ZooKeeper充当Elastic Job创建的系统作业的注册中心;J.  为塑造一个健康的网络环境,对用户发的朋友圈、评论、回复内容进行敏感词过滤;K.  大量优雅的Java8  Lambda编程、Stream编程;  (3)问题三:系统运行起来有效果图看吗?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广漂一枚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值