BroadLeafプロジェクトの検索機能の改善

17293 ワード

Solrについて
Broadleafプロジェクトでは、商品の検索に組み込まれたSolrサーバーが使われています。これは配置ファイルから分かります。
  • プロジェクトのホームページ: http://www.broadleafcommerce.com/
  • ウェブサイトの例: http://demo.broadleafcommerce.org/
  • ウェブサイトのソースコードの例: https://github.com/BroadleafCommerce/DemoSite
  • 例えばウェブサイトのソースコードのappication Contact.xmlファイルから、sorに関する構成が見られます。
        <bean id="solrEmbedded" class="java.lang.String">    
             <constructor-arg value="solrhome"/>     
        </bean>    
        <bean id="blSearchService" class="org.broadleafcommerce.core.search.service.solr.SolrSearchServiceImpl"> 
                <constructor-arg name="solrServer" ref="${solr.source}" />
                <constructor-arg name="reindexServer" ref="${solr.source.reindex}" />     
        </bean>      
        <bean id="rebuildIndexJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
                 <property name="targetObject" ref="blSearchService" />         
                 <property name="targetMethod" value="rebuildIndex" />     
        </bean>
        <bean id="rebuildIndexTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
                 <property name="jobDetail" ref="rebuildIndexJobDetail" />         
                 <property name="startDelay" value="${solr.index.start.delay}" />         
                 <property name="repeatInterval" value="${solr.index.repeat.interval}" />     
        </bean>     
        <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">         
            <property name="triggers">             
                <list>                 
                    <ref bean="rebuildIndexTrigger" />                 
                    <!--<ref bean="purgeCartTrigger" />-->                 
                    <!--<ref bean="purgeCustomerTrigger" />-->             
                </list>         
            </property>     
        </bean>
    資源配置ファイルはcomon.propertiesにあります。
    web.defaultPageSize=15 
    web.maxPageSize=100 
    solr.source=solrEmbedded 
    solr.source.reindex=solrEmbedded 
    solr.index.start.delay=5000 
    solr.index.repeat.interval=3600000
    上から見ると、使用されているSolrは組み込みサービスであり、Solrプロファイル(schema.xmlとsorceonfig.xml)は、 https://github.com/BroadleafCommerce/DemoSite/tree/master/site/src/main/resources 目次の下
    ソースコードSolrSearch ServiceImpl.javaから見ると、2つのSolrサービスが起動され、それぞれprimryとreindexの2つのsorcereに対応し、primryは照会に用い、reindexはインデックスの再構築に用いられる。
    検索エンジンの改良
    目標を改善する
    この記事は組み込み式Solrサービスを独立して実行するSolrサービスに変更するだけで、さらにSolCloudクラスタに変更することもできます。
  • 単独で検索エンジンサーバを構築する
  • は、増分更新インデックス
  • をサポートしています。
  • マニュアル再構成インデックス
  • をサポートします。
    設計の考え方
  • .元のシステムに組み込まれた検索エンジンが独立して展開されている検索エンジンを修正します。インストール方法は以下の通りです。
  • .元システムのSolSearch ServiceImplクラスを拡張し、インデックスを追加する方法を追加します。
  • .元システムで商品のservice類を修正し(ここで呼び出したのはLLCatalogServiceImplです。このクラスは新しく追加されました)、saveProductメソッドに検索エンジンにインデックスを追加する方法です。
  • .元システムにおけるsor関連のプロファイルを修正する。
  • .元のシステムにおけるインデックスの再構成のタイミングタスクを修正して、インデックスの手動再構成をサポートします。
  • 実現方法
    1、独立運行のソロサーバを構築する
    参考にしてもいいです。アプリSolr紹介とインストール
    ポイントはsor/homeの定義と、このディレクトリの下で2つのディレクトリを作成し、それぞれprimryとreindexであり、2つのディレクトリの下の構成ファイルは同じで、sour/homeディレクトリ構造は以下の通りである。
    ➜  solrhome-solr  tree -L 3 
    . 
    ├── primary │   
        └── conf │       
        ├── schema.xml │       
        └── solrconfig.xml 
    ├── reindex │   
        └── conf │       
        ├── schema.xml │       
        └── solrconfig.xml 
        └── solr.xml 
     4 directories, 5 files
    sour.xmlファイルの内容は以下の通りです。
    <?xml version="1.0" encoding="UTF-8" ?> 
    <solr persistent="true">   
        <cores defaultCoreName="primary" adminPath="/admin/cores">     
            <core instanceDir="reindex" name="reindex"/>     
            <core instanceDir="primary" name="primary"/>   
        </cores> 
    </solr>
    schema.xmlの内容はもとの基本と同じです。行を追加しただけです。
    <field name="_version_" type="long" indexed="true" stored="true" multiValued="false"/>
    ソロfig.xmlの内容は以下の通りです。
    <?xml version="1.0" encoding="UTF-8" ?> 
    <config>   
        <luceneMatchVersion>4.4</luceneMatchVersion>   
        <directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.StandardDirectoryFactory}"/>
           
        <schemaFactory class="ClassicIndexSchemaFactory"/>   
        
        <updateHandler class="solr.DirectUpdateHandler2">     
            <autoCommit>         
                <maxDocs>2</maxDocs>         
                <maxTime>3000</maxTime>     
            </autoCommit>   
        </updateHandler>  
        
        <requestHandler name="/get" class="solr.RealTimeGetHandler">     
            <lst name="defaults">       
                <str name="omitHeader">true</str>     
            </lst>   
        </requestHandler>   
        
        <requestHandler name="/replication" class="solr.ReplicationHandler" startup="lazy" />    
        
        <requestDispatcher handleSelect="true" >     
            <requestParsers enableRemoteStreaming="false" multipartUploadLimitInKB="2048" formdataUploadLimitInKB="2048" />     
            <httpCaching never304="true" />  
        </requestDispatcher>   
        
        <requestHandler name="standard" class="solr.StandardRequestHandler" default="true" />   
        <requestHandler name="/analysis/field" startup="lazy" class="solr.FieldAnalysisRequestHandler" />   
        <requestHandler name="/update" class="solr.UpdateRequestHandler"  />   
        <requestHandler name="/update/csv" class="solr.CSVRequestHandler" startup="lazy" />   
        <requestHandler name="/update/json" class="solr.JsonUpdateRequestHandler" startup="lazy" />   
        <requestHandler name="/admin/" class="org.apache.solr.handler.admin.AdminHandlers" />   
        <requestHandler name="/admin/ping" class="solr.PingRequestHandler">     
            <lst name="invariants">       
                <str name="q">solrpingquery</str>     
            </lst>     
            
            <lst name="defaults">       
                <str name="echoParams">all</str>     
            </lst>   
        </requestHandler>   
        
        <queryResponseWriter name="json" class="solr.JSONResponseWriter">         
            <str name="content-type">text/plain; charset=UTF-8</str>   
        </queryResponseWriter>  
         
         
        <!-- config for the admin interface -->    
        <admin>     
            <defaultQuery>solr</defaultQuery>   
         </admin> 
    </config>
    2、SolrSearch ServiceImpl類を拡張する
    coreモジュールでorg.broadleafcommere.co.re.search.service.sorr.ExtSolrSearch Serviceインターフェースを作成します。このインターフェースは以下のように定義されています。
    package org.broadleafcommerce.core.search.service.solr; 
    import java.io.IOException; 
    import org.broadleafcommerce.common.exception.ServiceException; 
    import org.broadleafcommerce.core.catalog.domain.Product; 
    import org.broadleafcommerce.core.search.service.SearchService; 
    
    public interface ExtSolrSearchService extends SearchService {     
            public void addProductIndex(Product product) throws ServiceException,     
            IOException; 
            
    }
    そして、実際のクラスorg.broadleafcommerce.co.search.service.sorr.ExtSolrSearch ServiceImplを作成し、この実装クラスの定義は以下の通りである。
    package org.broadleafcommerce.core.search.service.solr; 
    import java.io.IOException; 
    import java.util.ArrayList; 
    import java.util.Collections; 
    import java.util.Comparator; 
    import java.util.List; 
    import javax.annotation.Resource; 
    import javax.xml.parsers.ParserConfigurationException; 
    import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 
    import org.apache.solr.client.solrj.SolrServer; 
    import org.apache.solr.client.solrj.SolrServerException; 
    import org.apache.solr.client.solrj.response.QueryResponse; 
    import org.apache.solr.common.SolrDocument; 
    import org.apache.solr.common.SolrDocumentList; 
    import org.apache.solr.common.SolrInputDocument; 
    import org.broadleafcommerce.common.exception.ServiceException; 
    import org.broadleafcommerce.common.locale.domain.Locale; 
    import org.broadleafcommerce.common.util.StopWatch; 
    import org.broadleafcommerce.common.util.TransactionUtils; 
    import org.broadleafcommerce.core.catalog.domain.Product; 
    import org.broadleafcommerce.core.search.domain.Field; 
    import org.springframework.transaction.TransactionDefinition; 
    import org.springframework.transaction.TransactionStatus; 
    import org.xml.sax.SAXException; 
    
    public class ExtSolrSearchServiceImpl extends SolrSearchServiceImpl implements         
            ExtSolrSearchService {     
            
        private static final Log LOG = LogFactory             
                        .getLog(ExtSolrSearchServiceImpl.class);     
                    
        @Resource(name = "blSolrIndexService")     
        protected SolrIndexServiceImpl solrIndexServiceImpl;     
        
        public ExtSolrSearchServiceImpl(SolrServer solrServer,
                 SolrServer reindexServer) {
              super(solrServer, reindexServer);
         }
         public ExtSolrSearchServiceImpl(SolrServer solrServer) {
             super(solrServer);
         }
         public ExtSolrSearchServiceImpl(String solrServer, String reindexServer)
                 throws IOException, ParserConfigurationException, SAXException {
             super(solrServer, reindexServer);
         }
         public ExtSolrSearchServiceImpl(String solrServer) throws IOException,
                 ParserConfigurationException, SAXException {
             super(solrServer);
         }
         public void addProductIndex(Product product) throws ServiceException,
                 IOException {
             TransactionStatus status = TransactionUtils.createTransaction(
                     "saveProduct", TransactionDefinition.PROPAGATION_REQUIRED,
                     solrIndexServiceImpl.transactionManager, true);
             StopWatch s = new StopWatch();
             try {
                  List<Field> fields = fieldDao.readAllProductFields();
                  List<Locale> locales = solrIndexServiceImpl.getAllLocales();
                  SolrInputDocument document = solrIndexServiceImpl.buildDocument(
                                       product, fields, locales);
                  if (LOG.isTraceEnabled()) {
                      LOG.trace(document);
                  } 
                  SolrContext.getServer().add(document);
                  SolrContext.getServer().commit();
                  TransactionUtils.finalizeTransaction(status,
                               solrIndexServiceImpl.transactionManager, false);
                  } catch (SolrServerException e) {
                   TransactionUtils.finalizeTransaction(status,
                          solrIndexServiceImpl.transactionManager, true); 
                   throw new ServiceException("Could not rebuild index", e);
                  } catch (IOException e) {
                   TransactionUtils.finalizeTransaction(status,
                          solrIndexServiceImpl.transactionManager, true);
                   throw new ServiceException("Could not rebuild index", e); 
                  } catch (RuntimeException e) {
                    TransactionUtils.finalizeTransaction(status,
                          solrIndexServiceImpl.transactionManager, true);
                   throw e;
                  }
             LOG.info(String.format("Finished adding index in %s", s.toLapString()));
         }    
          protected List<Product> getProducts(QueryResponse response) {
             final List<Long> productIds = new ArrayList<Long>();
             SolrDocumentList docs = response.getResults();
             for (SolrDocument doc : docs) {
                 productIds                     
                          .add((Long) doc.getFieldValue(shs.getProductIdFieldName()));
             }
             /**          * TODO                    */ 
             List<Product> products = productDao.readProductsByIds(productIds);
             // We have to sort the products list by the order of the productIds list
             // to maintain sortability in the UI
             if (products != null) { 
                Collections.sort(products, new Comparator<Product>() {
                     public int compare(Product o1, Product o2) {
                         return new Integer(productIds.indexOf(o1.getId()))
                                 .compareTo(productIds.indexOf(o2.getId())); 
                    }
                  });
             }
             return products;
         }
      }
    3、ソロ関連のプロファイルを修正する
    a.webモジュールの中/werc/mail/webapp/WEB-INF/aplication Contront.xmlの以下のコードを削除する:
    <bean id="blSearchService" class="org.broadleafcommerce.core.search.service.solr.SolrSearchServiceImpl">
         <constructor-arg name="solrServer" ref="${solr.source}" />
         <constructor-arg name="reindexServer" ref="${solr.source.reindex}" />
     </bean>
    b.webモジュールにおけるweb/src/main/resoures/runtime-properties/common.propertiesの以下のコードを削除する:
    solr.source=solrEmbedded 
    solr.source.reindex=solrEmbedded
    c.coreモジュールでcore/src/main/resource/appication Controtext.xmlは次のコードを追加します。
    <bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer"> 
        <constructor-arg value="${solr.url}" /> 
    </bean>
    
    <bean id="solrReindexServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer"> 
        <constructor-arg value="${solr.url.reindex}" /> 
    </bean> 
    
    <bean id="blSearchService"     class="org.broadleafcommerce.core.search.service.solr.ExtSolrSearchServiceImpl">     
        <constructor-arg name="solrServer" ref="${solr.source}" />     
        <constructor-arg name="reindexServer" ref="${solr.source.reindex}" /> 
    </bean>
    d.coreモジュールにcore/src/main/resource/runtime-properties/common-sharred.propertiesに下記のコードを追加します。
    solr.url= 
    solr.url.reindex=http://localhost:8080/solr/reindex
    solr.source=solrServer
    solr.source.reindex=solrReindexServer
    4、LLCatalog ServiceImpl類を修正する
    下記のコードを追加します。
    @Resource(name = "blSearchService") 
    private ExtSolrSearchService extSolrSearchService;
    このクラスのsaveProductを修正する方法は以下の通りです。
    @Override 
    @Transactional("blTransactionManager") 
    public Product saveProduct(Product product) {
         Product dbProduct = catalogService.saveProduct(product);     
         try {         
             
             extSolrSearchService.addProductIndex(dbProduct);     
              
             } catch (ServiceException e) {
                 e.printStackTrace();
                 throw new RuntimeException(e);     
             } catch (IOException e) {         
                 e.printStackTrace();         
                 throw new RuntimeException(e);     
             }     
             return dbProduct;
      }
    5、タイミングタスクを修正する
    a.webシステムを起動する時、データベースの中の商品を調べて、索引を再構築します。この機能は、appication Contect.xmlでタイミングタスクが定義されていますので、このタイミングタスクのキャンセルを推奨します。以下のコードを削除します。
    <bean id="rebuildIndexJobDetail"     class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
         <property name="targetObject" ref="blSearchService" />     
         <property name="targetMethod" value="rebuildIndex" /> 
    </bean> 
    
    <bean id="rebuildIndexTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">     
        <property name="jobDetail" ref="rebuildIndexJobDetail" />     
        <property name="startDelay" value="${solr.index.start.delay}" />     
        <property name="repeatInterval" value="${solr.index.repeat.interval}" /> 
    </bean>
    
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
         <property name="triggers">
                  <list>             
                      <ref bean="rebuildIndexTrigger" />         
                  </list>     
         </property> 
    </bean>
    b.mainメソッドを作成し、jarパッケージにし、shellスクリプトを作成し、インデックスを手動で再構築したり、タイミングタスクを設定したりします。このクラスはblSearch Serviceという名前のbeanを取得して、beanのrebuildIndex方法を呼び出します。主なコードは以下の通りです。
    @Resource(name = "blSearchService") 
    private SearchService extSolrSearchService;
     public void doRebuild(){ 
         extSolrSearchService.rebuildIndex(); 
     }