搜索引擎技术

前言

1、Lucene

  • 以连接数据库为类比,Lucene 就相当于 JDBC,是基本的用法。

  • Lucene 官网

  • Lucene 教程

  • Lucene 和 like 的区别

    • 相关度
      • 不同相关度的结果 lucene 都会查询出来,但是使用 like,就做不到这一点了。
    • 性能
      • 数据量小的时候,like 也会有很好的表现,但是数据量一大,like 的表现就差很多了。
  • 分词器

    • 指的是搜索引擎如何使用关键字进行匹配,如关键字:护眼带光源。如果使用 like,那么 %护眼带光源%,匹配出来的结果就是要么全匹配,要不都不匹配。
    • 而使用分词器,就会把这个关键字分为 护眼,带,光源 3 个关键字,这样就可以找到不同相关程度的结果了。
  • 高亮显示

    • 查询结果里会高亮标记关键字。
    • 运行结果是 html 代码,为了正常显示,复制到一个 html 文件里,打开就可以看到效果了。
  • 分页查询是很常见的需求,比如要查询第 10 页,每页 10 条数据。

    • 第一种是把 100 条数据查出来,然后取最后 10 条。优点是快,缺点是对内存消耗大。
    • 第二种是把第 90 条查询出来,然后基于这一条,通过 searchAfter 方法查询 10 条数据。优点是内存消耗小,缺点是比第一种更慢。
  • 索引删除和更新

    • 索引建立好了之后,还是需要维护的,比如新增,删除和维护。
    • 索引里的数据,其实就是一个一个的 Document 对象,索引删除和更新那么就是删除和更新这些 Documen 对象。

2、Solr

  • Solr 是基于 Lucene 进行了封装,相当 Mybatis,方便开发人员配置,访问和调用。

  • 而且 Solr 被做成了 webapp 形式,以 Tomcat 的应用的方式启动,提供了可视化的配置界面。

  • Solr 官网

  • 启动服务器

    1
    2
    3
    4
    5
    6
    7
    8
    # 启动服务器,会占用端口 8983
    $ solr.cmd start

    # 关闭
    $ solr.cmd stop -all

    # 访问服务器
    http://127.0.0.1:8983/solr/#/
  • 创建 Core,如果说 Solr 相当于一个数据库的话,那么 Core 就相当于一张表。

    1
    2
    3
    4
    5
    # 创建 Core
    $ solr.cmd create -c new_core

    # 删除 Core
    $ solr.cmd delete -c new_core
  • 中文分词,默认情况下是没有中文分词的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 修改 /conf/managed-schema 配置文件

    <schema name="default-config" version="1.6">
    <!-- 增加如下代码 -->
    <fieldType name="text_ik" class="solr.TextField">
    <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
    </fieldType>
    <field name="text_ik" type="text_ik" indexed="true" stored="true" multiValued="false" />
    </schema>
  • 设置字段

    • Solr 中的 Core 就相当于表,那么就要为这个表设置字段,用于存放数据。
    • id 字段是默认就有的,无需自己创建。
  • 创建索引

    • Solr 支持通过各种各样的语言(如 php、javascript、c#)把数据加入到索引里。
    • 第三方工具 SolrJ,使用 Java 语言来把数据加入到索引里。
  • 分页查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static QueryResponse query(String keywords,int startOfPage, int numberOfPage) throws SolrServerException, IOException {
    SolrQuery query = new SolrQuery();
    query.setStart(startOfPage);
    query.setRows(numberOfPage);

    query.setQuery(keywords);
    QueryResponse rsp = client.query(query);
    return rsp;
    }
  • 高亮显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public static SolrClient client;

    public static void queryHighlight(String keywords) throws SolrServerException, IOException {
    SolrQuery q = new SolrQuery();
    // 开始页数
    q.setStart(0);
    // 每页显示条数
    q.setRows(10);
    // 设置查询关键字
    q.setQuery(keywords);
    // 开启高亮
    q.setHighlight(true);
    // 高亮字段
    q.addHighlightField("name");
    // 高亮单词的前缀
    q.setHighlightSimplePre("<span style='color:red'>");
    // 高亮单词的后缀
    q.setHighlightSimplePost("</span>");
    // 摘要最长 100 个字符
    q.setHighlightFragsize(100);
    // 查询
    QueryResponse query = client.query(q);

    // 获取高亮字段 name 相应结果
    NamedList<Object> response = query.getResponse();
    NamedList<?> highlighting = (NamedList<?>) response.get("highlighting");
    for (int i = 0; i < highlighting.size(); i++) {
    System.out.println(highlighting.getName(i) + ":" + highlighting.getVal(i));
    }

    // 获取查询结果
    SolrDocumentList results = query.getResults();
    for (SolrDocument result : results) {
    System.out.println(result.toString());
    }
    }
  • 更新删除索引

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static SolrClient client;

    public static boolean deleteById(String id) {
    try {
    client.deleteById(id);
    client.commit();
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

3、ElasticSearch

3.1 Kibana

  • Kibana 是在 ElasticSearch 有了相当多的数据之后,进行分析这些数据用的工具。

  • Kibana 里面有一个叫做 Dev Tools 的,可以很方便地以 Restful 风格向 ElasticSearch 服务器提交请求。

  • 索引管理,索引相当于就是一个数据库服务器上的某个数据库,所以索引也可以看成是 Elastic Search 里的某个数据库。

    1
    2
    3
    4
    5
    6
    7
    8
    # 增加索引
    PUT /how2java?pretty

    # 删除
    DELETE /how2java?pretty

    # 查询
    GET /_cat/indices?v
  • 文档管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 增加文档
    PUT /how2java/product/1?pretty
    {
    "name": "蜡烛"
    }

    # 删除
    DELETE /how2java/product/1?pretty

    # 修改
    POST /how2java/product/1/_update?pretty
    {
    "doc": { "name": "蓝色蜡烛" }
    }

    PUT /how2java/product/1?pretty
    {
    "name": "红色蜡烛"
    }

    # 获取
    GET /how2java/product/1?pretty
  • 批量导入数据

    1
    2
    3
    4
    5
    6
    7
    # 批量导入两条数据,使用这种方式能够插入的上限较小
    POST _bulk
    {"index":{"_index":"how2java","_type":"product","_id":10001}}
    {"code":"540785126782","price":398,"name":"房屋卫士自流平美缝剂瓷砖地砖专用双组份真瓷胶防水填缝剂镏金色","place":"上海","category":"品质建材"}
    {"index":{"_index":"how2java","_type":"product","_id":10002}}
    {"code":"24727352473","price":21.799999237060547,"name":"艾瑞泽手工大号小号调温热熔胶枪玻璃胶枪硅胶条热溶胶棒20W-100W","place":"山东青岛","category":"品质建材"}
    {"index":{"_index":"how2java","_type":"product","_id":10003}}
  • curl 批量导入,curl 是一个工具,可以模拟浏览器向服务器提交数据。

    1
    2
    # 执行导入
    $ curl -H "Content-Type: application/json" -XPOST "localhost:9200/how2java/product/_bulk?refresh" --data-binary "@products.json"
  • 查询操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    # 查询所有
    GET /how2java/_search
    {
    "query": { "match_all": {} }
    }

    # id 倒排序
    GET /how2java/_search
    {
    "query": { "match_all": {} },
    "sort": [
    { "_id": "desc" }
    ]
    }

    # 只返回部分字段
    GET /how2java/_search
    {
    "query": { "match_all": {} },
    "_source": ["name","price"]
    }

    # 条件查询
    GET /how2java/_search
    {
    "query": { "match": { "name": "时尚连衣裙" } }
    }

    # 分页查询
    GET /how2java/_search
    {
    "query": { "match_all": {} },
    "from": 1,
    "size": 3,
    "sort": { "_id": { "order": "desc" } }
    }
  • 聚合操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 聚合操作
    GET /how2java/_search
    {
    "size": 0,
    "aggs": {
    "group_by_place": {
    "terms": {
    "field": "place.keyword",
    "size": 3
    }
    }
    }
    }

    # 相当于 sql 语句
    select count(*), place from product group by place limit 0, 3

3.2 Java API

  • 通过 Java api 实现对 Elastic Search 的管理。

  • 索引管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private static void createIndex(String indexName) throws IOException {
    CreateIndexRequest request = new CreateIndexRequest(indexName);
    client.indices().create(request);
    System.out.println("创建了索引:"+indexName);
    }

    private static void deleteIndex(String indexName) throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest(indexName);
    client.indices().delete(request);
    System.out.println("删除了索引:"+indexName);

    }
  • 文档管理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    private static void addDocument(Product product) throws IOException {
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("name", product.getName());
    IndexRequest indexRequest = new IndexRequest(indexName, "product", String.valueOf(product.getId()))
    .source(jsonMap);
    client.index(indexRequest);
    System.out.println("已经向 ElasticSearch 服务器增加产品:"+product);
    }

    private static void deleteDocument(int id) throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest (indexName,"product", String.valueOf(id));
    client.delete(deleteRequest);
    System.out.println("已经从 ElasticSearch 服务器上删除 id="+id+" 的文档");
    }

    private static void updateDocument(Product product) throws IOException {

    UpdateRequest updateRequest = new UpdateRequest (indexName, "product", String.valueOf(product.getId()))
    .doc("name",product.getName());

    client.update(updateRequest);
    System.out.println("已经在 ElasticSearch 服务器修改产品为:"+product);

    }

    private static void getDocument(int id) throws IOException {
    GetRequest request = new GetRequest(
    indexName,
    "product",
    String.valueOf(id));

    GetResponse response = client.get(request);

    if(!response.isExists()){
    System.out.println("检查到服务器上 "+"id="+id+ "的文档不存在");
    }
    else{
    String source = response.getSourceAsString();
    System.out.print("获取到服务器上 "+"id="+id+ "的文档内容是:");

    System.out.println(source);

    }
    }
  • 批量操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private static void batchInsert(List<Product> products) throws IOException {
    BulkRequest request = new BulkRequest();

    for (Product product : products) {
    Map<String,Object> m = product.toMap();
    IndexRequest indexRequest= new IndexRequest(indexName, "product", String.valueOf(product.getId())).source(m);
    request.add(indexRequest);
    }

    client.bulk(request);
    System.out.println("批量插入完成");
    }
  • 查询操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    private static SearchHits search(String keyword, int start, int count) throws IOException {
    SearchRequest searchRequest = new SearchRequest(indexName);

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 关键字匹配
    MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("name",keyword );
    // 模糊匹配
    matchQueryBuilder.fuzziness(Fuzziness.AUTO);
    sourceBuilder.query(matchQueryBuilder);
    // 第几页
    sourceBuilder.from(start);
    // 第几条
    sourceBuilder.size(count);

    searchRequest.source(sourceBuilder);
    // 匹配度从高到低
    sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));

    SearchResponse searchResponse = client.search(searchRequest);

    SearchHits hits = searchResponse.getHits();
    return hits;
    }

3.3 Spring Boot 支持

  • Spring Boot 有一个 spring data 组件,可以用来连接各种数据源。

  • 用来连接 elasticsearch 的是 spring-data-elasticsearch。

文章目录
  1. 1. 前言
  2. 2. 1、Lucene
  3. 3. 2、Solr
  4. 4. 3、ElasticSearch
    1. 4.1. 3.1 Kibana
    2. 4.2. 3.2 Java API
    3. 4.3. 3.3 Spring Boot 支持
隐藏目录