SPB-设计机制-全文检索

来自站长百科
跳转至: 导航、​ 搜索

导航: 上一页

全文检索在SpaceBuilder中有两个重要的作用:

  1. 所有模糊搜索都采用全文检索实现,减轻数据库的负担;
  2. 实现信息的挖掘,例如:资讯的相关资讯,博客文章的相关文章;

SpaceBuilder的全文检索功能以Lucene.Net为核心。Lucene.Net是Apache Lucene的c#语言版本,Apache Lucene是一个开源的搜索引擎,提供一组解读,过滤,分析文件,编排和使用索引的API,它的强大之处除了高效和简单外,最重要的是使使用者可以随时根据自己需要扩展其功能。

一、索引文件同步机制

由于Lucene只能操作索引文件,而索引文件必须保证与数据库数据同步。如何保证Lucene中索引文件与数据库保持同步是一个必须要解决的问题。

Spacebuilder097.jpg

SpaceBuilder采用如下方式进行解决

  • 建立spb_ItemsForIndex表,临时保存需要更新到索引文件的数据;
  • 编写相应的ExtensionModules,当对象发生变化时记录到spb_ItemsForIndex表中;

例如,SpaceBuilder.LuceneSearch.PrepareForUserIndexModule:

public class PrepareForUserIndexModule : IGlobalModule
    {
        #region IGlobalModule Members
        public void Init(GlobalEventManager gem, System.Xml.XmlNode node)
        {
            gem.AfterUserChange += new UserEventHandler(gem_PostUserUpdate);
        }
        #endregion

        void gem_PostUserUpdate(User user, GlobalEventArgs e)
        {
            if ((user != null) && (e.State == ObjectState.Create || e.State == ObjectState.Update
 || e.State == ObjectState.Delete))
            {
                ItemForIndex item = new ItemForIndex();
                item.ItemID = user.UserID;
                if (user.UserType == UserTypes.PersonUser)
                    item.SearchType = FullTextSearchTypes.PersonUser;
                else if (user.UserType == UserTypes.CompanyUser)
                    item.SearchType = FullTextSearchTypes.CompanyUser;

                item.DataAction = e.State;
                
                SearchDataProvider.Instance().CreateUpdateDeleteItemForIndex(item, DataProviderAction.Create);
            }
        }
    }

当添加、修改、删除用户时将自动记录到spb_ItemsForIndex

  • 编写相应的Task,使spb_ItemsForIndex数据定期更新到Lucene索引文件中:
  public class PersonUserIndexTask : ITask
    {
        /// <summary>
        /// 每次更新索引的最大数目
        /// </summary>
        private int count = 1000;

        #region ITask Members

        public void Execute(System.Xml.XmlNode node)
        {
            XmlAttribute countNode = node.Attributes["count"];
            if (countNode != null)
            {
                try
                {
                    count = int.Parse(countNode.Value);
                }
                catch
                {
                    count = 1000;
                }
            }
            SearchDataProvider dp = SearchDataProvider.Instance();
            IList<ItemForIndex> itemsForIndex = dp.GetItemsForIndex(FullTextSearchTypes.PersonUser, count);

            IList<ItemForIndex> createItems = new List<ItemForIndex>();
            IList<ItemForIndex> updateItems = new List<ItemForIndex>();
            IList<ItemForIndex> deleteItems = new List<ItemForIndex>();

            foreach (ItemForIndex item in itemsForIndex)
            {
                if (item.DataAction == ObjectState.Create)
                    createItems.Add(item);
                else if (item.DataAction == ObjectState.Update)
                    updateItems.Add(item);
                else if (item.DataAction == ObjectState.Delete)
                    deleteItems.Add(item);
            }

#region Insert
            IList<PersonUser> createUsers = new List<PersonUser>();
            foreach (ItemForIndex item in createItems)
            {
                createUsers.Add((PersonUser)Users.GetUser(item.ItemID));
            }

            if (PersonUserIndexServer.Insert(createUsers))
            {
                dp.DeleteItemsForIndex(createItems);
            }
            else
            {
                foreach (ItemForIndex item in createItems)
                {
                    item.DataAction = ObjectState.Update;
                }
                dp.UpdateItemsForIndex(createItems);
            }
            IList<PersonUser> updateUsers = new List<PersonUser>();
            foreach (ItemForIndex item in updateItems)
            {
                updateUsers.Add((PersonUser)Users.GetUser(item.ItemID));
            }

            if (PersonUserIndexServer.Update(updateUsers))
            {
                dp.DeleteItemsForIndex(updateItems);
            }
            int[] deleteUids = new int[deleteItems.Count];
            for (int i = 0; i < deleteItems.Count; i++)
            {
                deleteUids[i] = deleteItems[i].ItemID;
            }
            if (PersonUserIndexServer.Delete(deleteUids))
            {
                dp.DeleteItemsForIndex(deleteItems);
            }
            
        }
        #endregion
    }

二、使用全文检索

  • 索引文件格式
  • 索引文件配置
<LuceneSearch globalIndexDirectory="~/IndexFiles" mergeFactor="10" minMergeDocs="100" />
  1. globalIndexDirectory表示所有索引文件的根目录,可以是虚拟路径(~/IndexFiles/),也可以是物理路径(e:\SpaceBuilderIndex\ 或者 \\192.168.0.1\SpaceBuilderIndex\)。
  2. )mergeFactor表示合并因子,这个参数决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如,如果合并因子的值是 10,那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且,如果磁盘上的索引块的隔数达到 10 的话,这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10,如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲,为这个参数赋一个比较大的值会得到比较好的索引效果。
  3. minMergeDocs表示最小合并文档数,这个参数也会影响索引的性能。它决定了内存中的文档数至少达到多少才能将它们写回磁盘。这个参数的默认值是,如果你有足够的内存,那么将这个值尽量设的比较大一些将会显著的提高索引性能。
  4. 索引文件格式目前SpaceBuilder内置了两部分索引文件:一部分是针对个人用户的索引,另一部分是Post(包括:博客文章、图片、文件、网摘、资讯等)。如果新加的应用可以直接使用Post索引,则可以省去建立索引文件格式的工作量,只需确定好Post索引的对应关系。否则,需要建立新加应用的索引文件格式。
  5. 开发新加应用的ExtensionModules及Task;
  6. 开发新加应用的FullTextQuery(查询条件)及Search(查询器);例如:SpaceBuilder.File.FileThreadFullTextQuery及SpaceBuilder.LuceneSearch.FileSearch。


参考资料[ ]