solr

何为solr

solr是java搜索引擎Lucene的更高一层封装,通过webapp服务器实现可视化界面,方便使用人员配置、访问和调用

solr架构解析

以下是solrcloud的架构图,单机模式的solr只是在这个基础上的简化:

solrcloud架构.png
  • SolrCloud:由分布在多个物理主机上的solr节点构成一个统一的solr分布式集群
  • Solr节点:物理概念,单个的物理主机,对应一个具体的solr服务器
  • core:物理概念,对应一系列document以及索引,单个solr主机上的core不相同
  • replica(leader):逻辑概念,每个replica都映射到某一个core,一个core可以有多个分布在不同主机上的replica,通过zookeeper进行选举产生leader
  • shard:逻辑概念,由多个replica以及一个leader组成集群,一个shard和同一个core的索引一一对应,也就是说shard的多个replica分布在不同的主机上成为core集群
  • collection:一套完整的倒排索引,一个独立的collection对应一个solrconfig.xml和schema.xml配置文件,可以分为不同的分片(shard),每个shard又由分布在不同主机上的replica组成

单机模式中,一个collection就对应一个core,当然也就没有shard和replica的概念了

solr环境搭建(单机模式)

solr有单机启动模式和集群启动模式,这里仅学习单机模式

启动服务器

在solr的bin文件夹下打开cmd,输入“solr start”即可快速启动solr服务器,默认8983端口,输入localhost:8983/solr/访问这个服务

创建core

core对应数据库中的一张表(或者保存一系列同类数据的容器),其中的元素为document(对应数据库的一行数据),通过命令行方式创建core:

solr create -c solrDemo

建立名为solrDemo的core,在solr界面中就可以查看core

配置分词器

分词器用于将单份document中的数据/文本中的单词进行分隔、整理、语法化并最终生成索引,solr的默认分词器只能对英文分词,中文分词需要配置额外的分词器

  • 配置Lucene默认的中文分词器:

修改solr-7.4.0\server\solr\solrDemo\conf下的managed-schema文件,添加下列配置:

<fieldType name="solr_cnAnalyzer" class="solr.TextField" positionIncrementGap="100">
      <!-- 用于建立索引的分词器,粒度更细 -->
      <analyzer type="index">
        <tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
      </analyzer>
      <!-- 用于搜索的分词器,粒度更粗 -->
      <analyzer type="query">
        <tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
      </analyzer>
    </fieldType>
    
  <field name="solr_cnAnalyzer" type="solr_cnAnalyzer" multiValued="false" indexed="true" stored="true"/>

重启后,在core中的Analysis中测试一下,FieldType选择solr_cnAnalyzer,就可以看到solr可以准确的将输入的查询语句拆分为中文词语

设置字段

为了将数据库/文本中的数据和文本正确映射到core中,需要在core的schema中准确设置与数据库/文本完全对应的字段(field):

  • 设置文本字段:field type选择solr_cnAnalyzer,字段名选择源文件对应的字段名,其他默认
  • 设置数字字段:field type选择ploat或者pint等

特别注意:store和index一定要为true,前者指定数据库字段值存入solr后能够被检索得到,后者指定该字段建立索引

导入数据并创建索引(文本方式)

这里演示通过solrj工具从文本中导入数据到core中

  • 建立实体类:通过实体类为载体将数据映射到core中(类似于orm框架),需要在与字段对应的成员属性上进行@Field注解,便于solr4j工具将属性值映射到core字段:
public class Product {
    @Field
    int id;
    @Field
    float price;
    @Field
    String category;
    @Field
    String name;
    @Field
    String place;
    @Field
    String code;
    
    /*
    getter and setter
    */
}
  • ProductUtil:将文本导入到java程序的工具类:
public class ProductUtil {
    //从文本中读入每一行数据,保存为Product对象
    public static List<Product> file2List(String fileName){
        File file = new File(fileName);
        try {
            List<Product> productList = new ArrayList<>(147940);
            List<String> list = FileUtils.readLines(file, "UTF-8");
            for(String s:list){
                productList.add(line2Product(s));
            }
            return productList;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static Product line2Product(String productLine){
        Product product = new Product();
        String[] fields = productLine.split(",");
        product.setId(Integer.parseInt(fields[0]));
        product.setName(fields[1]);
        product.setCategory(fields[2]);
        product.setPrice(Float.parseFloat(fields[3]));
        product.setPlace(fields[4]);
        product.setCode(fields[5]);
        return product;
    }
}
  • SolrUtil:将实体类Product中保存的数据以document的形式导入到core表中,保存为core字段:
public class SolrUtils {
    private static SolrClient solrClient;
    private static String url;
    static {
        url = "http://127.0.0.1:8983/solr/solrDemo";
        solrClient = new HttpSolrClient.Builder(url).build();//建立solr通讯连接
    }
    //批量录入数据
    public static <T> boolean batchSaveOrUpdate(List<T> entities) throws SolrServerException,IOException{
        DocumentObjectBinder binder = new DocumentObjectBinder();
        int total = entities.size();
        int count = 0;
        for(T t:entities){
            SolrInputDocument document = binder.toSolrInputDocument(t);
            solrClient.add(document);
            System.out.printf("一共有%d条记录需要添加,当前添加到%d条记录"+"\n", total, ++count);
        }
        solrClient.commit();
        return true;
    }
    //录入单个document数据
    public static<T> boolean saveOrUpdate(T entity) throws SolrServerException, IOException{
        DocumentObjectBinder binder = new DocumentObjectBinder();
        SolrInputDocument document = binder.toSolrInputDocument(entity);//将实体类的数据包装为单个document
        solrClient.add(document);
        solrClient.commit();
        return true;
    }
}

完成录入后,通过在core中查询:可以查看一共录入了多少条document数据;数据录入后,solr能够对每一段document进行分词、词法和语法解析,并对每一个词生成索引

导入数据并创建索引(数据库方式)

  • 导入数据库需要准备两个jar包,将jar包放到webapp的lib下即可:
mysql-connector-java-5.1.46.jar
solr-dataimporthandler-7.4.0.jar
  • 配置requestHandler:在当前collection的solrconfig.xml中配置
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
        <str name="config">data-config.xml</str>
    </lst>
</requestHandler>
  • 配置data-config:新建data-config.xml文档,进行如下配置:
<dataConfig>
  <dataSource type="JdbcDataSource" 
              driver="com.mysql.jdbc.Driver"
              url="jdbc:mysql://127.0.0.1:3306/zhihudemo" 
              user="root" 
              password="admin"/>
  <document>
    <entity name="question" 
            query="select id,title,content from question">
            <field column="title" name="question_title"/>
            <field column="content" name="question_content"/>
    </entity>
  </document>
</dataConfig>

其中entity对应数据库实体,如果field不设置的话,solr将查找与column相同名字的solr字段

  • 在core的dataimport中进行导入

solr环境搭建(centos7.4):

centos上solr安装完成后,默认的安装目录为 /opt/solr,配置文件默认存放在 /var/solr 文件夹下面,数据文件(core)在/var/solr/data下面

创建core

进行搜索

通过solrj工具在java工程中搜索文本

分页查询

solr支持分页查询,和关系型数据库类型,通过设置offset和count进行分页查询:

  • SolrUtils加入以下方法:
public static QueryResponse query(String keyWords, int offset, int count) throws SolrServerException, IOException{
        SolrQuery query = new SolrQuery();
        query.setStart(offset);
        query.setRows(count);
        query.setQuery(keyWords);
        return solrClient.query(query);
    }

keywords为搜索关键词,以“field name : keyword”的方式构成

        //获得查询反馈
        QueryResponse queryResponse = SolrUtils.query("name:婴儿", 0, 10);
        //将反馈结果导出为一张document的List
        SolrDocumentList documents = queryResponse.getResults();

        //取出document中的数据
        for(SolrDocument solrDocument:documents)
            Collection<String> fields = solrDocument.getFieldNames();
            for(String s:fields){
                //根据fieldName从document中取出数据
                System.out.print(solrDocument.get(s) + "\t");
            }
            System.out.println();
        }

高亮查询

高亮查询可以将文本中查询到的文字加上类似html的前缀和后缀,便于醒目提示:

  • 在SolrUtils中加入以下方法:
public static void queryHighlight(String keyWords, String fieldName, int offset, int count) throws SolrServerException, IOException{
        SolrQuery query = new SolrQuery();
        query.setQuery(fieldName + ":" + keyWords);
        query.setStart(offset);
        query.setRows(count);
        
        //打开高亮提示,设置需要高亮的字段名和前后缀提示符,以及每个分片最大长度(100)
        query.setHighlight(true);
        query.addHighlightField(fieldName);
        query.setHighlightSimplePre("<span style='color:red'>");
        query.setHighlightSimplePost("</span>");
        query.setHighlightFragsize(100);
        
        //获得查询反馈,将反馈结果以NamedList的形式取出,然后取出高亮查询的结果“highlighting”,遍历显示
        QueryResponse queryResponse = solrClient.query(query);
        NamedList<Object> response = queryResponse.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 = queryResponse.getResults();
        for(SolrDocument result:results){
            System.out.println(result.toString());
        }
    }

删除document

通过id删除某个document:

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

添加单个document

通过SolrInputDocument向solr添加单个document,记得一定要commit:

public boolean newQuestionIndex(int qid, String title, String content){
        SolrInputDocument document = new SolrInputDocument();
        document.setField("id", String.valueOf(qid));
        document.setField(QUESTION_TITLE_FIELD, title);
        document.setField(QUESTION_CONTENT_FIELD, content);
        try {
            UpdateResponse response = solrClient.add(document);
            int status = response.getStatus();
            if(status == 0){
                logger.info("添加问题:“"+title+"”成功 !");
                solrClient.commit();
            }else {
                logger.info("添加问题:“"+title+"”失败 !");
            }
            return response != null && status == 0;
        } catch (Exception e) {
            logger.error("向solr添加document时出错:"+e.getMessage());
            e.printStackTrace();
            return false;
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容