solr5項目實戰詳解,分布式緩存,全文檢索


說一下大體思路,電商類網站,由於老項目數據庫設計很不合理,一些查詢涉及的表過多,導致查詢速度異常緩慢,在不修改架構設計和源碼上,做了一下處理。

solr+eh ,使用eh緩存關聯數據,再用solr查詢速度,文章偏向小白文,大神見笑。很多設計不完善,實現功能為主。

一、配置緩存功能

結合我之前博文的eh文章配置,讓項目啟動后自動拉取數據存入緩存。

首先建立一個監聽類,實現項目啟動后調用。

StartupListener.java

public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
   
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
       //緩存數據
    }

公共容器xml配置添加

<bean id="startupListener" class="StartupListener類路徑"></bean>

封裝了EH工具類

EHUtil.java

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class EHUtil {
    
    private static final CacheManager manager = new CacheManager("src/main/resources/ehcache.xml");
    
    public static void setCache(String cacheName,String key,Object value){
        Cache cache = manager.getCache(cacheName);
        Element element = new Element(key,value); 
        cache.put(element);
    }
    
    public static Object getCache(String cacheName,String key){
        Cache cache = manager.getCache(cacheName);
        Element element = cache.get(key);
        if (element != null) {  
            return element.getValue();
        }
        return null;
    }
    
    public static boolean removeCache(String cacheName,String key){
        Cache cache = manager.getCache(cacheName);
        
        return cache.remove(key);
    }
    
    public static int getSize(String cacheName){
        Cache cache = manager.getCache(cacheName);
        
        return cache.getSize();
    }
    
    public static long getMemoryStoreSize(String cacheName){
        Cache cache = manager.getCache(cacheName);
        return cache.getMemoryStoreSize();
    }
}

 定義 getCache 返回類型為Object 如果需要返回javaBean,需要實現序列化

然后在項目Service內調用,保證操作dao層數據修改同步到EH緩存里,具體根據業務編寫。

二、solr檢索配置

創建solr工具類

 SolrUti.java

import java.util.ArrayList;
import java.util.List;

import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;

import com.penuel.mythopoetms.model.SolrCode;

public class SolrUtil {
    
    //填寫你得solr服務器的core地址,例如: http://XXX/solr/db
    private static final String URL = Constants.get("SOLR_URL");
    
    private static HttpSolrClient server = null;
    
    static{
        server = new HttpSolrClient(URL);
    }
    
    public static void addDoc(SolrInputDocument doc){
        try {
            UpdateResponse response = server.add(doc);
            // 提交
            server.commit();
//            System.out.println("########## Query Time :" + response.getQTime());
//            System.out.println("########## Elapsed Time :" + response.getElapsedTime());
//            System.out.println("########## Status :" + response.getStatus());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void addDocs(Object key,List<Object> objs){
        List<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
        
        for(Object obj:objs ){
            SolrInputDocument doc = new SolrInputDocument();
            
            doc.addField(SolrCode.id.code, key);
            doc.addField(SolrCode.title.code, obj);
            
            docs.add(doc);
        }
        
        try {
            server.add(docs);
            // 提交
            server.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void byAddDocs(List<SolrInputDocument> docs){
        try {
            server.add(docs);
            // 提交
            server.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        
    
    public static void removeDoc(String key){
        try {
            server.deleteById(key);
            server.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void removeQuery(String key){
        try {
            server.deleteByQuery(key);
            server.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static SolrDocumentList queryPage(ModifiableSolrParams params){
        
        try {
            QueryResponse response = server.query(params);
            SolrDocumentList list = response.getResults();
            
//            System.out.println("########### 總共 : " + list.getNumFound() + "條記錄");
//            System.out.println("########### 總共 : " + response.getQTime()+ "毫秒");
//            
//            for (SolrDocument doc : list) {
//                System.out.println("######### id : " + doc.get("id") + "  title : " + doc.get("title"));
//            }
            
            return list;
        } catch (SolrServerException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static SolrDocument queryById(ModifiableSolrParams params){
        
        try {
            QueryResponse response = server.query(params);
            SolrDocumentList list = response.getResults();
            if(list.size()>0){
                return list.get(0);
            }
        } catch (SolrServerException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static String getIds(SolrDocumentList list){
        StringBuffer sb = new StringBuffer();
        for(SolrDocument doc:list){
            String sss = ((String) doc.get("id")).replaceAll("\\D+", "");
            if(sb.length()==0){
                sb.append(sss);
                continue;
            }
            sb.append(","+sss);
        }
        return sb.toString();
    }
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------

配置完成,現在理順一下項目業務,

例:獲取一個商品列表,商品名和設計師名,兩個字段需要檢索,然后提供根據更新時間排序,並實現分頁。

分析:前面說了,數據庫設計結構為 (A表 B表 AB表)一但這種表多起來, 查詢會很慢。

數據庫設計解決:A: 商品表,B:設計師表,C:分類,D:品牌。 可見設計庫設計的不合理,在不修改原架構上進行處理。

1.建立緩存。把 AB,AC,AD,B,C,D 等表 建立緩存

配置統一標准規則key來主鍵查詢緩存,如(XXX+A表的id)這樣就可以根據A表id直接提取緩存數據。

創建枚舉類

CacheCode.java

public enum CacheCode {
    Cache("myCache"),designer("designerKEY"),brand("brandKEY")
    ,designerI("designerIKEY"),brandI("brandIKEY")
    ,cate("cateKEY"),cateI("cateIKEY");
    
    public String code;  
    private CacheCode(String code){  
        this.code=code;  
    }  
}

創建好后按照規則 啟動拉取數據存入緩存,修改之前的監聽類

 StartupListener.java

import java.util.ArrayList;
import java.util.List;

import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

import com.penuel.mythopoetms.model.Brand;
import com.penuel.mythopoetms.model.CacheCode;
import com.penuel.mythopoetms.model.Category;
import com.penuel.mythopoetms.model.Designer;
import com.penuel.mythopoetms.model.Item;
import com.penuel.mythopoetms.model.ItemBrand;
import com.penuel.mythopoetms.model.ItemCate;
import com.penuel.mythopoetms.model.ItemDesigner;
import com.penuel.mythopoetms.model.ItemOther;
import com.penuel.mythopoetms.service.BrandService;
import com.penuel.mythopoetms.service.CategoryService;
import com.penuel.mythopoetms.service.DesignerService;
import com.penuel.mythopoetms.service.ItemBrandService;
import com.penuel.mythopoetms.service.ItemCateService;
import com.penuel.mythopoetms.service.ItemDesignerService;
import com.penuel.mythopoetms.service.ItemHelpService;
import com.penuel.mythopoetms.service.ItemOtherService;
import com.penuel.mythopoetms.service.ItemService;

@Service
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
    
    @Autowired
    private DesignerService designerService;
    @Autowired
    private BrandService brandService;
    @Autowired
    private ItemDesignerService itemDesignerService;
    @Autowired
    private ItemBrandService itemBrandService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private ItemCateService itemCateService;
    @Autowired
    private ItemService itemService;
    
    
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        try {
            //設計師關聯表
            List<ItemDesigner> idList = itemDesignerService.list();
            if(idList!=null&&idList.size()>0){
                for(ItemDesigner idr:idList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.designerI.code+idr.getItemId(), idr.getDesignerId());
                }
            }
            
            //設計師
            List<Designer> dlist = designerService.listDesigners();
            if(dlist!=null&&dlist.size()>0){
                for(Designer designer:dlist){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.designer.code+designer.getId(), designer);
                }
            }
            
            //品牌關聯表
            List<ItemBrand> ibList = itemBrandService.list();
            if(ibList!=null&&ibList.size()>0){
                for(ItemBrand ib:ibList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.brandI.code+ib.getItemId(), ib.getBrandId());
                }
            }
            
            //品牌
            List<Brand> blist = brandService.listBrands();
            if(blist!=null&&blist.size()>0){
                for(Brand brand:blist){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.brand.code+brand.getId(), brand);
                }
            }
            
            //標簽關聯
            List<ItemCate> icList = itemCateService.list();
            if(icList!=null&&icList.size()>0){
                for(ItemCate ic:icList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.cateI.code+ic.getItemId(), ic.getCateId());
                }
            }
            
            //標簽
            List<Category> cList = categoryService.listCategories();
            if(cList!=null&&cList.size()>0){
                for(Category category:cList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.cate.code+category.getId(), category);
                }
            }    
    }
}

 

Service自行添加編寫,不做描述。

下面開始solr相關配置

2.編寫業務工具類,用於增刪改查 solr 索引,

首先,設計好solr索引庫要存的字段。前面講過:獲取一個商品列表,商品名和設計師名,兩個字段需要檢索,然后提供根據更新時間排序,並實現分頁

所以solr存儲數據的格式應該是這樣的

['ltime:'更新時間',id:'itemId+商品ID','title:'商品名稱','name':'設計師名字']

其中id為 唯一,相同id會覆蓋操作,這里不懂solr配置的可看我前面的文章配置,包括中文分詞

先從建立索引開始入手。同樣建立solr的枚舉類統一key規則

SolrCode.java

public enum SolrCode {
    id("id"),title("title"),ltime("last_modified"),name("name");
    
    
    public String code;  
    private SolrCode(String code){  
        this.code=code;  
    }  
}

 編寫業務類建立索引

ItemHelpService.java

import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import com.penuel.mythopoetms.model.CacheCode;
import com.penuel.mythopoetms.model.Designer;
import com.penuel.mythopoetms.model.Item;
import com.penuel.mythopoetms.model.SolrCode;
import com.penuel.mythopoetms.utils.EHUtil;
import com.penuel.mythopoetms.utils.SolrUtil;

public class ItemHelpService {
    
    
    /**
     * @param date 時間
     * @param strs 多個參數
     * @return
     * @throws ParseException
     */
    public static SolrInputDocument addDocsHelp(Date date,String ...strs) throws ParseException{
        SimpleDateFormat format =  new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); 
        
        SolrInputDocument doc = new SolrInputDocument();
        
        doc.addField(SolrCode.ltime.code,date);
        doc.addField(SolrCode.id.code, strs[0]);
        doc.addField(SolrCode.title.code, strs[1]);
        doc.addField(SolrCode.name.code, strs[2]);
        
        return doc;
    }
    
}

編寫完成后 修改 StartupListener 啟動項目並建立索引

StartupListener.java

import java.util.ArrayList;
import java.util.List;

import org.apache.solr.common.SolrInputDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

import com.penuel.mythopoetms.model.Brand;
import com.penuel.mythopoetms.model.CacheCode;
import com.penuel.mythopoetms.model.Category;
import com.penuel.mythopoetms.model.Designer;
import com.penuel.mythopoetms.model.Item;
import com.penuel.mythopoetms.model.ItemBrand;
import com.penuel.mythopoetms.model.ItemCate;
import com.penuel.mythopoetms.model.ItemDesigner;
import com.penuel.mythopoetms.model.ItemOther;
import com.penuel.mythopoetms.service.BrandService;
import com.penuel.mythopoetms.service.CategoryService;
import com.penuel.mythopoetms.service.DesignerService;
import com.penuel.mythopoetms.service.ItemBrandService;
import com.penuel.mythopoetms.service.ItemCateService;
import com.penuel.mythopoetms.service.ItemDesignerService;
import com.penuel.mythopoetms.service.ItemHelpService;
import com.penuel.mythopoetms.service.ItemOtherService;
import com.penuel.mythopoetms.service.ItemService;

@Service
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
    
    @Autowired
    private DesignerService designerService;
    @Autowired
    private BrandService brandService;
    @Autowired
    private ItemDesignerService itemDesignerService;
    @Autowired
    private ItemBrandService itemBrandService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private ItemCateService itemCateService;
    @Autowired
    private ItemOtherService itemOtherService;
    @Autowired
    private ItemService itemService;
    
    
    
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        try {
            //設計師關聯表
            List<ItemDesigner> idList = itemDesignerService.list();
            if(idList!=null&&idList.size()>0){
                for(ItemDesigner idr:idList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.designerI.code+idr.getItemId(), idr.getDesignerId());
                }
            }
            
            //設計師
            List<Designer> dlist = designerService.listDesigners();
            if(dlist!=null&&dlist.size()>0){
                for(Designer designer:dlist){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.designer.code+designer.getId(), designer);
                }
            }
            
            //品牌關聯表
            List<ItemBrand> ibList = itemBrandService.list();
            if(ibList!=null&&ibList.size()>0){
                for(ItemBrand ib:ibList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.brandI.code+ib.getItemId(), ib.getBrandId());
                }
            }
            
            //品牌
            List<Brand> blist = brandService.listBrands();
            if(blist!=null&&blist.size()>0){
                for(Brand brand:blist){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.brand.code+brand.getId(), brand);
                }
            }
            
            //標簽關聯
            List<ItemCate> icList = itemCateService.list();
            if(icList!=null&&icList.size()>0){
                for(ItemCate ic:icList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.cateI.code+ic.getItemId(), ic.getCateId());
                }
            }
            
            //標簽
            List<Category> cList = categoryService.listCategories();
            if(cList!=null&&cList.size()>0){
                for(Category category:cList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.cate.code+category.getId(), category);
                }
            }
            //商品url
            List<ItemOther> ioList = itemOtherService.list();
            if(ioList!=null&&ioList.size()>0){
                for(ItemOther io:ioList){
                    EHUtil.setCache(CacheCode.Cache.code, CacheCode.otherI.code+io.getItemId(), io.getUrlDetail());
                }
            }
            
            //商品or設計師索引
            //['ltime:'更新時間',id:'itemId+商品ID','title:'商品名稱','name':'設計師名字']
            List<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
            List<Item> iList = itemService.listItems();
            for(Item item:iList){
                Designer dir =(Designer)EHUtil.getCache(CacheCode.Cache.code,CacheCode.designer.code+ 
                        EHUtil.getCache(CacheCode.Cache.code, CacheCode.designerI.code+item.getId()));
                if(dir==null)
                    continue;
                docs.add(ItemHelpService.addDocsHelp(item.getUtime(),"itemId"+item.getId(),item.getName(),
                        dir.getName()));
            }
            SolrUtil.byAddDocs(docs);
        
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Service 主要為數據拉取,可自行編寫。

現在索引建立完成,可以開始進行搜索了。

這里需要中文分詞器,配合solr的查詢功能。方法如下。

ItemHelpService.java

import java.io.IOException;
import java.io.StringReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;

import com.penuel.mythopoetms.model.CacheCode;
import com.penuel.mythopoetms.model.Designer;
import com.penuel.mythopoetms.model.Item;
import com.penuel.mythopoetms.model.SolrCode;
import com.penuel.mythopoetms.utils.EHUtil;
import com.penuel.mythopoetms.utils.SolrUtil;

public class ItemHelpService {
        
    /**
     * @param date 時間
     * @param strs 多個參數
     * @return
     * @throws ParseException
     */
    public static SolrInputDocument addDocsHelp(Date date,String ...strs) throws ParseException{
        SimpleDateFormat format =  new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); 
        
        SolrInputDocument doc = new SolrInputDocument();
        
        doc.addField(SolrCode.ltime.code,date);
        doc.addField(SolrCode.id.code, strs[0]);
        doc.addField(SolrCode.title.code, strs[1]);
        doc.addField(SolrCode.name.code, strs[2]);
        
        return doc;
    }
    
    /**
     * @param key  商品名
     * @param key2  設計師
     * @param sort 排序
     * @param start 起始位
     * @param rows 顯示頁數
     * @return
     */
    public static String[] queryCount(String key,String key2,String sort,int start,int rows){
        ModifiableSolrParams params = new ModifiableSolrParams();
        
        if(StringUtils.isBlank(key)){
            if(StringUtils.isBlank(key2))
                params.set("q", "*:*");
            else
                params.set("q", "name:"+key2+"*");
        }else{
            if(key.length()<2)
                params.set("q", "title:"+key+"*");
            else
                params.set("q", "title:"+key);
            
            params.set("fq", toDismantle(key,key2));
        }
        
        params.set("sort", sort);
        params.set("start", start);
        params.set("rows", rows);
        
        String [] str = new String[2];
        SolrDocumentList list = SolrUtil.queryPage(params);
        
        str[0]=SolrUtil.getIds(list);
        str[1]=Long.toString(list.getNumFound());
        
        return str;
    }
    
    /**
     * @param text 商品名
     * @param text2 設計師名
     * @return
     */
    public static String[] toDismantle(String text,String text2){
        List<String> list = new ArrayList<String>();
        if(text.length()<2){
            list.add("title:"+text);
        }else{
            StringReader sr=new StringReader(text);  
            IKSegmenter ik=new IKSegmenter(sr, true);  
            Lexeme lex=null;  
            ModifiableSolrParams params = new ModifiableSolrParams();
            params.set("q", "title:"+text);
            try {
                while((lex=ik.next())!=null){  
                    list.add("title:"+lex.getLexemeText()+"*");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }  
        }
        if(StringUtils.isNotBlank(text2)){
            list.add("name:"+text2+"*");
        }
        String[] arr = (String[])list.toArray(new String[list.size()]);
        return arr;
    }
}

 

這里可能看不太懂,

現講一下 queryCount方法

key=商品名稱,key2=設計師名稱,sort=指定字段排序,start=起始位,rows=搜索數量

key默認值為 空串即"", key2 默認值為 空串即 ""

toDismantle方法 

主要針對 key 值 做中文分詞(IK2012版本),設計師不需要分詞,因為設計師需要單字匹配,但是商品不可以,舉一個例子 就會明白

例如,

查詢: ("q","title:男士灰色")

不做分詞處理的話,數據就會是這樣的

輸出 "男士灰色XXXX","女士灰色XXXX"

因為 不做分詞處理。灰色為同性詞,很多不相關的數據會摻雜進來。

做分詞處理的效果:

查詢: ("q","title:男士灰色")

分詞處理並添加條件 ("fq",["title:男士*",title:灰色*])

數據就會過濾掉 不相關的詞,比如"女士"等,

還有一處為查詢字段長度判斷

if(key.length()<2)
      params.set("q", "title:"+key+"*");

如果用戶搜索單個詞。

例如

查詢("q","title:男")

solr的分詞器 會自動匹配 "男" 的索引 而不是模糊查詢 所以要在后邊加上* 來匹配模糊查詢。

sort值為排序用,書寫規則為 "字段 desc" 或者 "字段 asc"

start,rows。 最常見的分頁功能,

start 值為:(當前頁- 1) * 顯示數量

rows 值為:顯示數量

 

queryCount方法,是一個數組,

[0] 儲存的是 查詢出來的所有商品ID

[1]儲存的是 商品條數

得到這些數據  

,使用 sql select *  from A表 where id in (數組[0])  來獲取商品列表數據。

然后遍歷商品列表,根據其ID值 自動匹配 緩存里存儲的 B表 C表 D表 等等數據。

 

最終效率從最開始的 3秒 到 現在的首次查詢僅0.3秒左右,之后,平均查詢占0.02秒。

 

講到這里基本上是結束了,集體索引的更新操作和緩存操作基本一樣,保持緩存 索引和 數據庫同步即可。

配置方面可查詢之前文章

EH集群:http://www.cnblogs.com/mangyang/p/5481713.html

solr配置:http://www.cnblogs.com/mangyang/p/5500852.html

solr5中文分詞:http://www.cnblogs.com/mangyang/p/5502773.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM