Lucene.Net+Apsara Distributed File System分詞

來源:互聯網
上載者:User

標籤:Lucene   blog   http   io   color   ar   os   使用   sp   

前言

       各位朋友,謝謝大家的支援,由於檔案過大,有考慮到著作權的問題,故沒有提供下載,本人已建立一個搜尋技術交流群:77570783,原始碼已上傳至群共用,需要的朋友,請自行下載!

       首先自問自答幾個問題,以讓各位看官瞭解寫此文的目的

什麼是站內搜尋?與一般搜尋的區別?
很多網站都有搜尋功能,很多都是用SQL語句的Like實現的,但是Like無法做到模糊比對(例如我搜尋“.net學習”,如果有“.net的學習”,Like就無法搜尋到,這明顯不符合需求,但是站內搜尋就能做到),另外Like會造成全盤掃描,會對資料庫造成很大壓力,為什麼不用資料庫全文檢索索引,跟普通SQL一樣,很傻瓜,靈活性不行

為什麼不用百度、google的站內搜尋?
畢竟是別人的東西,用起來肯定會受制於人(哪天你的網站火了,它看你不爽了,就可能被K),主要還是索引的不夠及時,網站新的內容,需要一定時間才能被索引到,並且使用者的體驗也不太好

      最近改造了《動力起航》的站內搜尋的功能,它其實已經有站內搜尋的功能,但是是用like來實現的,改造此功能是本著在儘可能少的修改網站的原始碼的情況下去改造此功能以及此站內搜尋功能可以很好的移植到其他項目的原則來編寫!本文有借鑒其他大神及園友的技術,在此謝謝!

功能簡介

站內搜尋使用的技術
Log4Net  日誌記錄

lucene.Net   全文檢索索引開發包,只能檢索文本資訊

分詞(lucene.Net提供StandardAnalyzer一元分詞,按照單個字進行分詞,一個漢字一個詞)

Apsara Distributed File System分詞   基於詞庫的分詞,可以維護詞庫

具體詳解

      首先我們新增的SearchHelper類需要將其做成一個單例,使用單例是因為:有許多地方需要使用使用,但我們同時又希望只有一個對象去操作,具體代碼如下:

        #region 建立單例        // 定義一個靜態變數來儲存類的執行個體        private static SearchHelper uniqueInstance;        // 定義一個標識確保線程同步        private static readonly object locker = new object();        // 定義私人建構函式,使外界不能建立該類執行個體        private SearchHelper()        { }        /// <summary>        /// 定義公有方法提供一個全域訪問點,同時你也可以定義公有屬性來提供全域訪問點        /// </summary>        /// <returns></returns>        public static SearchHelper GetInstance()        {            // 當第一個線程運行到這裡時,此時會對locker對象 "加鎖",            // 當第二個線程運行該方法時,首先檢測到locker對象為"加鎖"狀態,該線程就會掛起等待第一個線程解鎖            // lock語句運行完之後(即線程運行完之後)會對該對象"解鎖"            lock (locker)            {                // 如果類的執行個體不存在則建立,否則直接返回                if (uniqueInstance == null)                {                    uniqueInstance = new SearchHelper();                }            }            return uniqueInstance;        }        #endregion

        其次,使用Lucene.Net需要將被搜尋的進行索引,然後儲存到索引庫以便被搜尋,我們引入了“生產者,消費者模式”. 生產者就是當我們新增,修改或刪除的時候我們就需要將其在索引庫進行相應的操作,我們將此操作交給另一個線程去處理,這個線程就是我們的消費者,使用“生產者,消費者模式”是因為:索引庫使用前需解鎖操作,使用完成之後必須解鎖,所以只能有一個對象對索引庫進行操作,避免資料混亂,所以要使用生產者,消費者模式

首先我們來看生產者,代碼如下:

        private Queue<IndexJob> jobs = new Queue<IndexJob>();       //任務隊列,儲存生產出來的任務和消費者使用,不使用list避免移除時資料混亂問題        /// <summary>        /// 任務類,包括任務的Id ,操作的類型        /// </summary>        class IndexJob        {            public int Id { get; set; }            public JobType JobType { get; set; }        }        /// <summary>        /// 枚舉,操作類型是增加還是刪除        /// </summary>        enum JobType { Add, Remove }        #region 任務添加        public void AddArticle(int artId)        {            IndexJob job = new IndexJob();            job.Id = artId;            job.JobType = JobType.Add;            logger.Debug(artId + "加入工作清單");            jobs.Enqueue(job);//把任務加入商品庫        }        public void RemoveArticle(int artId)        {            IndexJob job = new IndexJob();            job.JobType = JobType.Remove;            job.Id = artId;            logger.Debug(artId + "加入刪除工作清單");            jobs.Enqueue(job);//把任務加入商品庫        }        #endregion

下面是消費者,消費者我們單獨一個線程來進行任務的處理:

        /// <summary>        /// 索引任務線程        /// </summary>        private void IndexOn()        {            logger.Debug("索引任務線程啟動");            while (true)            {                if (jobs.Count <= 0)                {                    Thread.Sleep(5 * 1000);                    continue;                }                //建立索引目錄                if (!System.IO.Directory.Exists(IndexDic))                {                    System.IO.Directory.CreateDirectory(IndexDic);                }                FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NativeFSLockFactory());                bool isUpdate = IndexReader.IndexExists(directory);                logger.Debug("索引庫存在狀態" + isUpdate);                if (isUpdate)                {                    //如果索引目錄被鎖定(比如索引過程中程式異常退出),則首先解鎖                    if (IndexWriter.IsLocked(directory))                    {                        logger.Debug("開始解鎖索引庫");                        IndexWriter.Unlock(directory);                        logger.Debug("解鎖索引庫完成");                    }                }                IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);                ProcessJobs(writer);                writer.Close();                directory.Close();//不要忘了Close,否則索引結果搜不到                logger.Debug("全部索引完畢");            }        }        private void ProcessJobs(IndexWriter writer)        {            while (jobs.Count != 0)            {                IndexJob job = jobs.Dequeue();                writer.DeleteDocuments(new Term("number", job.Id.ToString()));                //如果“添加文章”任務再添加,                if (job.JobType == JobType.Add)                {                    BLL.article bll = new BLL.article();                    Model.article art = bll.GetArticleModel(job.Id);                    if (art == null)//有可能剛添加就被刪除了                    {                        continue;                    }                                        string channel_id = art.channel_id.ToString();                    string title = art.title;                    DateTime time = art.add_time;                    string content = Utils.DropHTML(art.content.ToString());                    string Addtime = art.add_time.ToString("yyyy-MM-dd");                    Document document = new Document();                    //只有對需要全文檢索索引的欄位才ANALYZED                    document.Add(new Field("number", job.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));                    document.Add(new Field("title", title, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));                    document.Add(new Field("channel_id", channel_id, Field.Store.YES, Field.Index.NOT_ANALYZED));                    document.Add(new Field("Addtime", Addtime, Field.Store.YES, Field.Index.NOT_ANALYZED));                    document.Add(new Field("content", content, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));                    writer.AddDocument(document);                    logger.Debug("索引" + job.Id + "完畢");                }            }        }         #endregion

 

以上我們就把索引庫建立完畢了,接下來就是進行搜尋了,搜尋操作裡麵包括對搜尋關鍵詞進行分詞,其次是搜尋內容搜尋詞高亮顯示,下面就是搜尋的代碼:
        #region 從索引搜尋結果        /// <summary>        /// 從索引搜尋結果        /// </summary>        public List<Model.article> SearchIndex(string Words, int PageSize, int PageIndex, out int _totalcount)        {            _totalcount = 0;            Dictionary<string, string> dic = new Dictionary<string, string>();            BooleanQuery bQuery = new BooleanQuery();            string title = string.Empty;            string content = string.Empty;            title = GetKeyWordsSplitBySpace(Words);            QueryParser parse = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "title", new PanGuAnalyzer());            Query query = parse.Parse(title);            parse.SetDefaultOperator(QueryParser.Operator.AND);            bQuery.Add(query, BooleanClause.Occur.SHOULD);            dic.Add("title", Words);            content = GetKeyWordsSplitBySpace(Words);            QueryParser parseC = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "content", new PanGuAnalyzer());            Query queryC = parseC.Parse(content);            parseC.SetDefaultOperator(QueryParser.Operator.AND);            bQuery.Add(queryC, BooleanClause.Occur.SHOULD);            dic.Add("content", Words);            if (bQuery != null && bQuery.GetClauses().Length > 0)            {                return GetSearchResult(bQuery, dic, PageSize, PageIndex, out _totalcount);            }            return null;        }        /// <summary>        /// 擷取        /// </summary>        /// <param name="bQuery"></param>        private List<Model.article> GetSearchResult(BooleanQuery bQuery, Dictionary<string, string> dicKeywords, int PageSize, int PageIndex, out int totalCount)        {            List<Model.article> list = new List<Model.article>();            FSDirectory directory = FSDirectory.Open(new DirectoryInfo(IndexDic), new NoLockFactory());            IndexReader reader = IndexReader.Open(directory, true);            IndexSearcher searcher = new IndexSearcher(reader);            TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);            Sort sort = new Sort(new SortField("Addtime", SortField.DOC, true));            searcher.Search(bQuery, null, collector);            totalCount = collector.GetTotalHits();//返回總條數            TopDocs docs = searcher.Search(bQuery, (Filter)null, PageSize * PageIndex, sort);            if (docs != null && docs.totalHits > 0)            {                for (int i = 0; i < docs.totalHits; i++)                {                    if (i >= (PageIndex - 1) * PageSize && i < PageIndex * PageSize)                    {                        Document doc = searcher.Doc(docs.scoreDocs[i].doc);                        Model.article model = new Model.article()                        {                            id = int.Parse(doc.Get("number").ToString()),                            title = doc.Get("title").ToString(),                            content = doc.Get("content").ToString(),                            add_time = DateTime.Parse(doc.Get("Addtime").ToString()),                            channel_id = int.Parse(doc.Get("channel_id").ToString())                        };                        list.Add(SetHighlighter(dicKeywords, model));                    }                }            }            return list;        }        /// <summary>        /// 設定關鍵字高亮        /// </summary>        /// <param name="dicKeywords">關鍵字列表</param>        /// <param name="model">返回的資料模型</param>        /// <returns></returns>        private Model.article SetHighlighter(Dictionary<string, string> dicKeywords, Model.article model)        {            SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");            Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new Segment());            highlighter.FragmentSize = 250;            string strTitle = string.Empty;            string strContent = string.Empty;            dicKeywords.TryGetValue("title", out strTitle);            dicKeywords.TryGetValue("content", out strContent);            if (!string.IsNullOrEmpty(strTitle))            {                string title = model.title;                model.title = highlighter.GetBestFragment(strTitle, model.title);                if (string.IsNullOrEmpty(model.title))                {                    model.title = title;                }            }            if (!string.IsNullOrEmpty(strContent))            {                string content = model.content;                model.content = highlighter.GetBestFragment(strContent, model.content);                if (string.IsNullOrEmpty(model.content))                {                    model.content = content;                }            }            return model;        }        /// <summary>        /// 處理關鍵字為索引格式        /// </summary>        /// <param name="keywords"></param>        /// <returns></returns>        private string GetKeyWordsSplitBySpace(string keywords)        {            PanGuTokenizer ktTokenizer = new PanGuTokenizer();            StringBuilder result = new StringBuilder();            ICollection<WordInfo> words = ktTokenizer.SegmentToWordInfos(keywords);            foreach (WordInfo word in words)            {                if (word == null)                {                    continue;                }                result.AppendFormat("{0}^{1}.0 ", word.Word, (int)Math.Pow(3, word.Rank));            }            return result.ToString().Trim();        }         #endregion

以上我們的站內搜尋的SearchHelper類就建立好了,下面來講講如何使用,此類提供以下幾個方法對外使用:

在Global裡面啟動消費者線程:

        protected void Application_Start(object sender, EventArgs e)        {            //啟動索引庫的掃描線程(生產者)            SearchHelper.GetInstance().CustomerStart();        }

在需被搜尋的新增或修改處添加下面方法:

SearchHelper.GetInstance().AddArticle(model.id);

在需被搜尋的刪除處添加下面方法:

SearchHelper.GetInstance().RemoveArticle(model.id);

搜尋的時候使用下面的方法即可:

public List<Model.article> SearchIndex(string Words, int PageSize, int PageIndex, out int _totalcount)
 結束語

       以上就是整個站內搜尋的全部代碼,SearchHelper協助類:http://files.cnblogs.com/beimeng/SearchHelper.rar

       本來想直接提供改造了《動力起航》的原始碼,這樣就可以直接看到效果了,一方面由於檔案過大,另一方面不知道是不是會侵權,所有沒有提供下載.如果有需要的朋友可以留下郵箱我將發給你,但僅供學習交流之用,誤用做商業用途,以上如果有侵權等問題還請及時告知我,以便我及時更正!

       很榮幸此文能上最多推薦,多謝大家的支援,由於索要改造了《動力起航》的原始碼的園友很多,一一發給大家有點麻煩,在考慮是否放到網盤提供大家下載是不是更方便一些,但是不知道這樣會不會有侵權之嫌啊,求各位給個建議,如果可以我就上傳網盤了,不行的話就只能一個一個發給大家了!

       最好如果覺得好的話!請給個推薦啊~~~~親!!!!

Lucene.Net+Apsara Distributed File System分詞

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.