几百行代码完成百度搜索引擎,真的可以吗?(上)

简介: Hello 大家好,我是鸭血粉丝,大家都叫我阿粉,搜索引擎想必大家一定不会默认,我们项目中经常使用的 ElasticSearch 就是一种搜索引擎,在我们的日志系统中必不可少,ELK 作为一个整体,基本上是运维标配了,另外目前的搜索引擎底层都是基于 Lucene 来实现的。

阿粉最近遇到一个需求,因为数据量没有达到需要使用 ElasticSearch 的级别,也不想单独部署一套集群,所以准备自己基于 Lucene 实现一个简易的搜索服务。下面我们一起来看一下吧。

背景

**Lucene **是一套用于全文检索和搜索的开放源码程序库,由 Apache 软件基金会支持和提供。Lucene 提供了一个简单却强大的应用程序接口,能够做全文索引和搜索。Lucene 是现在最受欢迎的免费 Java 信息检索程序库。

上面的解释是来自维基百科,我们只需要知道 Lucene 可以进行全文索引和搜索就行了,这里的索引是动词,意思是我们可以将文档或者文章或者文件等数据进行索引记录下来,索引过后,我们查询起来就会很快。

索引这个词有的时候是动词,表示我们要索引数据,有的时候是名词,我们需要根据上下文场景来判断。新华字典前面的字母表或者书籍前面的目录本质上都是索引。

接入

引入依赖

首先我们创建一个 SpringBoot 项目,然后在 pom 文件中加入如下内容,我这里使用的 lucene 版本是 7.2.1,

<properties>
    <lucene.version>7.2.1</lucene.version>
</properties>
<!-- Lucene核心库 -->
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-core</artifactId>
 <version>${lucene.version}</version>
</dependency>
<!-- Lucene解析库 -->
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-queryparser</artifactId>
 <version>${lucene.version}</version>
</dependency>
<!-- Lucene附加的分析库 -->
<dependency>
 <groupId>org.apache.lucene</groupId>
 <artifactId>lucene-analyzers-common</artifactId>
 <version>${lucene.version}</version>
</dependency>

索引数据

在使用 Lucene 之前我们需要先索引一些文件,然后再通过关键词查询出来,下面我们来模拟整个过程。为了方便我们这里模拟一些数据,正常的数据应该是从数据库或者文件中加载的,我们的思路是这样的:

  1. 生成多条实体数据;
  2. 将实体数据映射成 Lucene 的文档形式;
  3. 索引文档;
  4. 根据关键词查询文档;

第一步我们先创建一个实体如下:

import lombok.Data;
@Data
public class ArticleModel {
    private String title;
    private String author;
    private String content;
}

我们再写一个工具类,用来索引数据,代码如下:

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LuceneIndexUtil {
    private static String INDEX_PATH = "/opt/lucene/demo";
    private static IndexWriter writer;
    public static LuceneIndexUtil getInstance() {
        return SingletonHolder.luceneUtil;
    }
    private static class SingletonHolder {
        public final static LuceneIndexUtil luceneUtil = new LuceneIndexUtil();
    }
    private LuceneIndexUtil() {
        this.initLuceneUtil();
    }
    private void initLuceneUtil() {
        try {
            Directory dir = FSDirectory.open(Paths.get(INDEX_PATH));
            Analyzer analyzer = new StandardAnalyzer();
            IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
            writer = new IndexWriter(dir, iwc);
        } catch (IOException e) {
            log.error("create luceneUtil error");
            if (null != writer) {
                try {
                    writer.close();
                } catch (IOException ioException) {
                    ioException.printStackTrace();
                } finally {
                    writer = null;
                }
            }
        }
    }
    /**
     * 索引单个文档
     *
     * @param doc 文档信息
     * @throws IOException IO 异常
     */
    public void addDoc(Document doc) throws IOException {
        if (null != doc) {
            writer.addDocument(doc);
            writer.commit();
            writer.close();
        }
    }
    /**
     * 索引单个实体
     *
     * @param model 单个实体
     * @throws IOException IO 异常
     */
    public void addModelDoc(Object model) throws IOException {
        Document document = new Document();
        List<Field> fields = luceneField(model.getClass());
        fields.forEach(document::add);
        writer.addDocument(document);
        writer.commit();
        writer.close();
    }
    /**
     * 索引实体列表
     *
     * @param objects 实例列表
     * @throws IOException IO 异常
     */
    public void addModelDocs(List<?> objects) throws IOException {
        if (CollectionUtils.isNotEmpty(objects)) {
            List<Document> docs = new ArrayList<>();
            objects.forEach(o -> {
                Document document = new Document();
                List<Field> fields = luceneField(o);
                fields.forEach(document::add);
                docs.add(document);
            });
            writer.addDocuments(docs);
        }
    }
    /**
     * 清除所有文档
     *
     * @throws IOException IO 异常
     */
    public void delAllDocs() throws IOException {
        writer.deleteAll();
    }
    /**
     * 索引文档列表
     *
     * @param docs 文档列表
     * @throws IOException IO 异常
     */
    public void addDocs(List<Document> docs) throws IOException {
        if (CollectionUtils.isNotEmpty(docs)) {
            long startTime = System.currentTimeMillis();
            writer.addDocuments(docs);
            writer.commit();
            log.info("共索引{}个 Document,共耗时{} 毫秒", docs.size(), (System.currentTimeMillis() - startTime));
        } else {
            log.warn("索引列表为空");
        }
    }
    /**
     * 根据实体 class 对象获取字段类型,进行 lucene Field 字段映射
     *
     * @param modelObj 实体 modelObj 对象
     * @return 字段映射列表
     */
    public List<Field> luceneField(Object modelObj) {
        Map<String, Object> classFields = ReflectionUtils.getClassFields(modelObj.getClass());
        Map<String, Object> classFieldsValues = ReflectionUtils.getClassFieldsValues(modelObj);
        List<Field> fields = new ArrayList<>();
        for (String key : classFields.keySet()) {
            Field field;
            String dataType = StringUtils.substringAfterLast(classFields.get(key).toString(), ".");
            switch (dataType) {
                case "Integer":
                    field = new IntPoint(key, (Integer) classFieldsValues.get(key));
                    break;
                case "Long":
                    field = new LongPoint(key, (Long) classFieldsValues.get(key));
                    break;
                case "Float":
                    field = new FloatPoint(key, (Float) classFieldsValues.get(key));
                    break;
                case "Double":
                    field = new DoublePoint(key, (Double) classFieldsValues.get(key));
                    break;
                case "String":
                    String string = (String) classFieldsValues.get(key);
                    if (StringUtils.isNotBlank(string)) {
                        if (string.length() <= 1024) {
                            field = new StringField(key, (String) classFieldsValues.get(key), Field.Store.YES);
                        } else {
                            field = new TextField(key, (String) classFieldsValues.get(key), Field.Store.NO);
                        }
                    } else {
                        field = new StringField(key, StringUtils.EMPTY, Field.Store.NO);
                    }
                    break;
                default:
                    field = new TextField(key, JsonUtils.obj2Json(classFieldsValues.get(key)), Field.Store.YES);
                    break;
            }
            fields.add(field);
        }
        return fields;
    }
    public void close() {
        if (null != writer) {
            try {
                writer.close();
            } catch (IOException e) {
                log.error("close writer error");
            }
            writer = null;
        }
    }
    public void commit() throws IOException {
        if (null != writer) {
            writer.commit();
            writer.close();
        }
    }
}

有了工具类,我们再写一个 demo 来进行数据的索引

import java.util.ArrayList;
import java.util.List;
/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author Silence<br>
 * <b>Date:</b>2020-10-17 21:08<br>
 * <b>Desc:</b>无<br>
 */
public class Demo {
    public static void main(String[] args) {
        LuceneIndexUtil luceneUtil = LuceneIndexUtil.getInstance();
        List<ArticleModel> articles = new ArrayList<>();
        try {
            //索引数据
            ArticleModel article1 = new ArticleModel();
            article1.setTitle("Java 极客技术");
            article1.setAuthor("鸭血粉丝");
            article1.setContent("这是一篇给大家介绍 Lucene 的技术文章,必定点赞评论转发!!!");
            ArticleModel article2 = new ArticleModel();
            article2.setTitle("极客技术");
            article2.setAuthor("鸭血粉丝");
            article2.setContent("此处省略两千字...");
            ArticleModel article3 = new ArticleModel();
            article3.setTitle("Java 极客技术");
            article3.setAuthor("鸭血粉丝");
            article3.setContent("最后邀请你加入我们的知识星球,Today is big day!");
            articles.add(article1);
            articles.add(article2);
            articles.add(article3);
            luceneUtil.addModelDocs(articles);
            luceneUtil.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

上面的 content 内容可以自行进行替换,阿粉这边避免凑字数的嫌疑就不贴了。

展示

运行结束过后,我们用过 Lucene 的可视化工具 luke 来查看下索引的数据内容,下载过后解压我们可以看到有.bat 和 .sh 两个脚本,根据自己的系统进行运行就好了。阿粉这边是 mac 用的是 sh 脚本运行,运行后打开设置的索引目录即可。

luke 在 GitHub 上面有,但是下载很慢,阿粉这边通过国外服务器下载下来,并上传的百度网盘中了,公众号回复【luke】获取。31.jpg

相关文章
|
12天前
|
人工智能 缓存 搜索推荐
百度/Bing/Google搜索引擎使用技巧
本文分享了百度、Bing和Google三大搜索引擎的实用技巧,涵盖精确匹配、排除关键词、站内及文件类型搜索等,如使用双引号进行精确搜索“人工智能应用”,排除特定词如“人工智能 -游戏”,以及在特定网站如“site:baidu.com 人工智能”内查找内容等,帮助提高搜索效率和准确性。
百度/Bing/Google搜索引擎使用技巧
|
搜索推荐 定位技术 SEO
自建网站写博客,怎么被百度等搜索引擎搜到?
自从本站openGPS.cn增加博客板块,也有一段时间了,通过这段时间的学习积累,意识到自建网站发布博客,中间要经历的几件事情: 1,百度搜索,360搜索,搜狗搜索等国内搜索引擎好久搜不到自己的网站。
1971 0
|
运维 搜索推荐 数据可视化
几百行代码完成百度搜索引擎,真的可以吗?(下)
Hello 大家好,我是鸭血粉丝,大家都叫我阿粉,搜索引擎想必大家一定不会默认,我们项目中经常使用的 ElasticSearch 就是一种搜索引擎,在我们的日志系统中必不可少,ELK 作为一个整体,基本上是运维标配了,另外目前的搜索引擎底层都是基于 Lucene 来实现的。
几百行代码完成百度搜索引擎,真的可以吗?(下)
|
搜索推荐 SEO
怎么发帖可以让搜索引擎(百度)尽快收录?
帖子的内容需要注意的要点: 1、内容中需要带有站点的名称,并把这个站点的名称作为文字链接,连向你的主域名。如:SEO十万个为什么 。这个概念叫:Anchor Text,中文:链接锚文本
189 0
|
搜索推荐
百度网盘搜索引擎
百度网盘搜索 百度网盘搜索是国内老牌的百度网盘搜索引擎,也是百度网盘,百度云盘搜索。 百度网盘搜索是基于百度云搜索,最大的百度网盘资源搜索中心,千万级数据量,让您一网打尽所有的百度网盘资源. http://pan.codedq.net/福利!福利!百度网盘搜索功能全新上线!支持百度网盘搜索功能。搜片,搜资料,一网打尽! 喜欢的朋友可以添加收藏! http://pan.codedq.net/
9278 1
|
自然语言处理 搜索推荐 算法
海量数据搜索---demo展示百度、谷歌搜索引擎的实现
百度、谷歌等网站之所以能很快在海量数据中找到需要的数据,得益于其搜索引擎,本文将介绍搜索引擎的基本知识及中文分词的方法,并通过demo演示如何进行数据检索。
|
数据采集 搜索推荐 前端开发
fbh
|
Web App开发 编解码 搜索推荐
禁止百度,神马,搜狗等搜索引擎转码
百度 第一种,HTTP Response中显式声明Cache-control为no-transform。 第二种,meta标签中显式声明Cache-control为no-tranform,格式为: 神马 第一种. HTTP Response中显式声明Cache-control为no-siteapp。
fbh
2321 0
|
运维 负载均衡 搜索推荐
支撑百度搜索引擎99.995%可靠名字服务架构设计
百度搜索引擎是全球最大的中文搜索引擎,致力于向人们提供"简单,可依赖"的信息获取方式。百度网页搜索部架构师郑然为我们分享支撑百度搜索引擎的可靠名字服务架构设计。
3571 0
下一篇
无影云桌面