1.solr嵌入式使用

本篇用来简易搭建solr EmbeddedSolrServer嵌入式实例,即将solr嵌入到应用系统中。应用系统使用SpringBoot搭建。
并同时在嵌入式solr下考虑如何使用自定义过滤插件、自定义排序插件。

1、准备

jdk:jdk8
solr版本:4.7.2
SpringBoot版本:2.5.0

2、创建solr core

2.1、创建SOLE_HOME

SOLR_HOME目录:D:\work\workspace\learn\learn_solr4\solr\multicore

2.2、创建core目录

在SOLR_HOME目录下创建两个core:book、buyrecord

-${SOLR_HOME}
|---book    可售书索引引擎,这是目录
|---buyrecord   购买记录索引引擎,这是目录

2.3、创建solr.xml内容

在D:\work\workspace\learn\learn_solr4\solr文件夹下创建solr.xml

<?xml version="1.0" encoding="UTF-8" ?>
<solr persistent="true">

  <cores adminPath="/admin/cores">
    <core name="book" instanceDir="book">
        <property name="solr.data.dir" value="/opt/learn/solr/index2/book" />
    </core>
    <core name="buyrecord" instanceDir="buyrecord">
        <property name="solr.data.dir" value="/opt/learn/solr/index2/buyrecord" />
    </core>
  </cores>
        
</solr>

注:
该配置文件定义两个core,并指定core的索引文件目录solr.data.dir

2.4、拷贝索引配置文件

从solr实例中拷贝索引配置到core,book、buyrecord中
从solr-4.7.2\example\solr\collection1下conf文件夹拷贝到multicore/book、multicore/buyrecord下

2.5、配置索引字段

以book这个core为例,配置索引字段
${SOLR_HOME}/book/conf/schema.xml
配置fields、主键uniqueKey

<fields>

    <!-- 书id -->
    <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <!-- 书名 -->
    <field name="name" type="string" indexed="true" stored="true"/>
    <!-- 门店id -->
    <field name="shopid" type="string" indexed="true" stored="true"/>
    <!-- 价格,base_price基本价格、shop_price门店价格,pin_price拼团价格 -->
    <dynamicField name="*_price" type="tdouble" indexed="true"  stored="true"/>
    <!-- 是否参与拼团,0-不参与拼团,1-参与拼团 -->
    <field name="isPintuan" type="tint" indexed="true" stored="true"/>
  </fields>

  <uniqueKey>id</uniqueKey>

3、创建SpringBoot应用

创建SpringBoot web应用。

4、应用中嵌入solr引擎

4.1、pom.xml添加solr依赖

        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-core</artifactId>
            <version>4.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>4.7.2</version>
        </dependency>

4.2、添加solr引擎容器Bean

@Component
public class SolrServerContainer {

    private CoreContainer coreContainer;

    public static final String SOLE_HOME="/work/workspace/learn/learn_solr4/solr/multicore";

    public static final String SOLR_CONFIG ="/work/workspace/learn/learn_solr4/solr/solr.xml";

    @PostConstruct
    public void init(){
        File f = new File(SOLR_CONFIG);
        coreContainer = CoreContainer.createAndLoad(SOLE_HOME,f);
    }

    public EmbeddedSolrServer getSolrServer(String core){
        return new EmbeddedSolrServer(coreContainer,core);
    }

    public CoreContainer getCoreContainer() {
        return coreContainer;
    }

    public void setCoreContainer(CoreContainer coreContainer) {
        this.coreContainer = coreContainer;
    }
}

至此,solr就嵌入到应用系统中了。
后面就看下嵌入式solr的使用

5、嵌入式solr使用

5.1、类似原生http访问

像原生http://127.0.0.1:8080/solr/book/select?q=:这种http访问示例
(1)查看下solr原生web拦截器
solr原生web应用是添加solr web拦截器,拦截/solr路径
web.xml

<filter>
    <filter-name>SolrRequestFilter</filter-name>
    <filter-class>org.apache.solr.servlet.SolrDispatchFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>SolrRequestFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

再看下SolrDispatchFilter源码

public class SolrDispatchFilter extends BaseSolrFilter {
    public void init(FilterConfig config) throws ServletException {
        log.info("SolrDispatchFilter.init()");

        try {
            this.pathPrefix = config.getInitParameter("path-prefix");
            this.cores = this.createCoreContainer();
            log.info("user.dir=" + System.getProperty("user.dir"));
        } catch (Throwable var3) {
            log.error("Could not start Solr. Check solr/home property and the logs");
            SolrCore.log(var3);
            if (var3 instanceof Error) {
                throw (Error)var3;
            }
        }

        log.info("SolrDispatchFilter.init() done");
    }
    
    ...
}

solr原生的SolrDispatchFilter,在初始化时init()指定了pathPrefix和cores。

(2)那么我们就可以类似定义拦截器,继承SolrDispatchFilter

@WebFilter(filterName = "mySolrDispatchFilter",urlPatterns = "/solr/*")
public class MySolrDispatchFilter extends SolrDispatchFilter {

    @Autowired
    private SolrServerContainer solrServerContainer;

    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("MySolrDispatchFilter init");
        this.cores = solrServerContainer.getCoreContainer();
        this.pathPrefix = "/solr";
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        super.doFilter(request, response, chain);
    }
}

并且在SpringBoot启动类中添加注解
@ServletComponentScan

(3)启动SpringBoot应用

(4)向book solr引擎中插入数据
通过solrj客户端插入数据

  @Test
    public void testInsert() throws IOException, SolrServerException {
        String url = "http://127.0.0.1:8080/solr/book";
        SolrServer solrServer = new HttpSolrServer(url);
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id","3333");
        doc.addField("name","node.js入门与精通");
        doc.addField("shopid","s001");
        doc.addField("base_price",Double.valueOf(72));
        doc.addField("shop_price",Double.valueOf(77));
        doc.addField("pin_price",Double.valueOf(74));
        doc.addField("isPintuan","1");

        solrServer.add(doc);
        solrServer.commit();

    }

通过solrj共向book引擎中插入3条数据。

(5)查看solr索引
http://127.0.0.1:8080/solr/book/select?q=:
访问结果如下:

image.png

5.2、应用内EmbeddedSolrServer访问

通过EmbeddedSolrServer访问应用内引擎
测试用例使用junit测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class SolrServerContainerTest {

    @Autowired
    private SolrServerContainer solrServerContainer;

    @Test
    public void test() throws SolrServerException {
        //获取嵌入式solr
        EmbeddedSolrServer solrServer = solrServerContainer.getSolrServer("book");
        SolrQuery request = new SolrQuery();
        request.add("q","*:*");
        //查询
        QueryResponse response = solrServer.query(request);
        System.out.println(response);
    }
}

6、自定义过滤插件

6.1、模拟这样的场景

书籍有如下几种价格:
基本价:base_price
门店价格:shop_price
拼团价格:pin_price

当某个书籍支持拼团时,isPintuan=1时,取书的价格pin_price>shop_price>base_price
当某个书籍不支持拼团时,isPintuan=0时,取书的价格shop_price>base_price

现在我要取价格在[70~100]之间的书,该如何从索引引擎中获取合适的书籍?

这里定义个自定义过滤插件实现

6.2、定义自定义过滤插件QParserPlugin

(1)定义PriceFilterQParserPlugin,继承QParserPlugin
实现createParser方法,需要QParser对象。

public class PriceFilterQParserPlugin extends QParserPlugin {
    @Override
    public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
        String min = localParams.get("min");
        String max = localParams.get("max");

        final PriceFilterQuery priceFilterQuery = new PriceFilterQuery(Double.parseDouble(min),Double.parseDouble(max));
        return new QParser(qstr, localParams, params, req) {
            @Override
            public Query parse() throws SyntaxError {
                return priceFilterQuery;
            }
        };
    }

    @Override
    public void init(NamedList namedList) {

    }
}

(2)定义PriceFilterQuery

public class PriceFilterQuery extends ExtendedQueryBase implements PostFilter {
    private double min;
    private double max;

    public PriceFilterQuery(double min, double max) {
        this.min = min;
        this.max = max;
    }

    @Override
    public DelegatingCollector getFilterCollector(IndexSearcher indexSearcher) {
        return new DelegatingCollector(){
            AtomicReader reader;
            @Override
            public void collect(int doc) throws IOException {
                double price = 0.0;
                //取基本价
                double base_price = SolrFieldDataUtil.getDouble(reader,doc,"base_price");
                //取门店价
                double shop_price = SolrFieldDataUtil.getDouble(reader,doc,"shop_price");
                //取拼团价
                double pin_price = SolrFieldDataUtil.getDouble(reader,doc,"pin_price");

                //取是否支持门店价
                int isPintuan = SolrFieldDataUtil.getInt(reader,doc,"isPintuan");

                if(isPintuan == 1 && pin_price > 0){
                    price = pin_price;
                }else if(shop_price > 0){
                    price = shop_price;
                }else{
                    price = base_price;
                }

                if(price >= min && price <= max){
                    super.collect(doc);
                }
            }

            @Override
            public void setNextReader(AtomicReaderContext context) throws IOException {
                super.setNextReader(context);
                this.reader = context.reader();
            }
        };
    }

    @Override
    public int hashCode() {
        return System.identityHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return this == obj;
    }

    @Override
    public void setCache(boolean cache) {

    }

    @Override
    public boolean getCache() {
        ##这里必须返回false
        ##solr源码中在只有返回false时,才会执行该插件
        return false;
    }

    @Override
    public int getCost() {
        return Math.max(super.getCost(), 100);
    }
}

(3)定义取solr字段值工具类

public class SolrFieldDataUtil {
   private SolrFieldDataUtil() {
   }

   public static String getString(AtomicReader reader, int doc, String fieldName) throws IOException {
      BinaryDocValues values = FieldCache.DEFAULT.getTerms(reader, fieldName, false);
       BytesRef byteRef = new BytesRef();
       values.get(doc, byteRef);
       return byteRef.utf8ToString();
   }

   public static double getDouble(AtomicReader reader, int doc, String fieldName) throws IOException {
        FieldCache.Doubles values = FieldCache.DEFAULT.getDoubles(reader, fieldName, false);
       return values.get(doc);
   }

   public static int getInt(AtomicReader reader, int doc, String fieldName) throws IOException {
        FieldCache.Ints values = FieldCache.DEFAULT.getInts(reader, fieldName, false);
       return values.get(doc);
   }
}

6.3、配置自定义过滤器

在book core的solrconfig.xml根节点下配置

<queryParser name="priceFilter" class="com.learn.solr.query.function.filter.PriceFilterQParserPlugin"/>

6.4、启动SpringBoot,访问solr

http://127.0.0.1:8080/solr/book/select?q=:&fq={!priceFilter min=70 max=100}

此时会报错

java.lang.IllegalArgumentException: Invalid character found in the request target [/solr/book/select?q=*:*&fq={!priceFilter%20min=70%20max=99}]. The valid characters are defined in RFC 7230 and RFC 3986

这是由于tomcat8及以后版本,支持的RFC 7230 and RFC 3986规范定义{}为特殊字符,不可使用。

6.5、解决特殊字符问题

在SpringBoot启动类中,添加如下代码,对这几个特殊字符继续可使用。

@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> connector.setProperty("relaxedQueryChars", "|{}[]"));
    return factory;
}

6.6、再次启动SpringBoot,访问solr

访问结果:
http://127.0.0.1:8080/solr/book/select?q=:&fq={!priceFilter min=70 max=100}

这里fq={!priceFilter min=70 max=100}就是使用价格过滤插件


image.png

7、自定义排序插件

7.1、场景

同自定义过滤插件场景,根据价格排序,需要考虑到基本价、门店价、拼团价

7.2、自定义获取价格插件

public class PriceValueSourceParser extends ValueSourceParser {
    @Override
    public ValueSource parse(FunctionQParser functionQParser) throws SyntaxError {
        return new PriceValueSource();
    }

    private class PriceValueSource extends ValueSource{

        @Override
        public FunctionValues getValues(Map map, AtomicReaderContext atomicReaderContext) throws IOException {
            final AtomicReader reader = atomicReaderContext.reader();
            return new FunctionValues() {
                @Override
                public short shortVal(int doc) {
                    return (short) doubleVal(doc);
                }

                @Override
                public float floatVal(int doc) {
                    return (float) doubleVal(doc);
                }

                @Override
                public int intVal(int doc) {
                    return (int)doubleVal(doc);
                }

                @Override
                public long longVal(int doc) {
                    return (long)doubleVal(doc);
                }

                @Override
                public double doubleVal(int doc) {
                    double price = 0.0;
                    //取价格说明,优先级:pin_price > shop_price > base_price,且pin_price必须在isPintuan=1是有效
                    try {
                        double base_price = SolrFieldDataUtil.getDouble(reader,doc,"base_price");
                        double shop_price = SolrFieldDataUtil.getDouble(reader,doc,"shop_price");
                        double pin_price = SolrFieldDataUtil.getDouble(reader,doc,"pin_price");

                        int isPintuan = SolrFieldDataUtil.getInt(reader,doc,"isPintuan");

                        if(isPintuan == 1 && pin_price > 0){
                            price = pin_price;
                        }else if(shop_price > 0){
                            price = shop_price;
                        }else{
                            price = base_price;
                        }

                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    return price;

                }

                @Override
                public String toString(int i) {
                    return "";
                }
            };
        }

        @Override
        public boolean equals(Object o) {
            return this == o;
        }

        @Override
        public int hashCode() {
            return 0;
        }

        @Override
        public String description() {
            return "PriceValueSource";
        }
    }
}

7.3、配置插件

在book core的solrconfig.xml根节点下配置

<valueSourceParser name="priceValue" class="com.learn.solr.query.function.value.PriceValueSourceParser"/>

7.4、重启SpringBoot应用

http://127.0.0.1:8080/solr/book/select?q=:&fl=id,name,shopid,base_price,shop_price,pin_price,isPintuan,price:priceValue()&sort=priceValue() asc
按照价格升序排序,同时fl字段中也把实际价格打印出来

image.png

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

推荐阅读更多精彩内容

  • Solr的安装与配置 多数搜索引擎应用都必须具有某种搜索功能,而搜索功能往往大量的消耗资源导致应用程序运行缓慢。为...
    TyCoding阅读 5,251评论 1 5
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,797评论 6 342
  • Solr简介 Solr是什么 Solr是一个基于全文检索的企业级应用服务器。 全文检索:可以输入一段文字,通过分词...
    深拥_66e2阅读 301评论 0 0
  • Solr Full Import全量导入 所谓全量索引一般指的是每次从数据库中读取需要导入的全部数据,然后提交到S...
    Vekaco阅读 3,443评论 1 2
  • solr 数据同步,全量、增量方式 DIH全量同步(全表数据)(一般做第一次数据同步) 首先创建对应的数据库表 s...
    逐暗者阅读 14,599评论 1 26