接下去我们进行索引建立,本项目索引建立我们使用Lucene.Net。在使用前我们介绍以下Lucene是什么!
Lucene概述
Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。
索引过程:
①获取内容
②建立文档
获取原始内容后,就需要对这些内容进行索引,必须首先将这些内容转换成部件(通常称为文档),以供搜索引擎使用。文档主要包括几个带值的域,比如标题、正文、摘要、作者和链接。
③文档分析
搜索引擎不能直接对文本进行索引:确切地说,必须将文本分割成一系列被称为语汇单元的独立的原子元素。每一个语汇单元大致与语言中的“单词”对应起来。
④文档索引
在索引步骤中,文档被加入到索引列表。
Lucene 的参考链接,想多了解的小伙伴可以点击
借助 Lucene.Net 构建站内搜索引擎
使用Lucene.Net实现全文检索
Lucene.Net+盘古分词器(详细介绍)
在阅读上述内容和文章链接后,相信大家对Lucene是什么有了一定的了解。那么我们再来说说分词,分词我们简单理解是这样的
“今天是个好日子”通过分词中间件,我们能够得到一个集合,集合内容为["今天","是","一个","好日子"]这样的内容,相当于把内容分解成了我们日常理解的词汇。中文分词现在有很多种
庖丁解牛,盘古分词,结巴分词,IK分词等等,大家可以通过百度对分词组件进行了解,这里也不做多的说明。
本项目选用的分词组件是 盘古分词,采用Lucene.Net建立索引
索引建立是基于当前已经存在的20张表
我们是对这些数据建立索引,那我们如何高效的建立索引,当然也是多线程啦!
思路说明
遍历20张表
第一步、得到每张表的个数集合 Dictionary[表名,分页总数]
我们的分页以1000作为基数,即一次取1000条数据
_commodityService.GetTableCount(i) 得到当前表格的个数 (i是商品表索引号)
PageHelper.GetPageNum(A,B) A是集合,B是个数,得到的集合是对当前表的数据每次取1000个,需要分多少页
public static int GetPageNum(int allCount, int pageSize)
{
int PageNum = 0;//任务分页个数
if (allCount % pageSize == 0)
{
PageNum = allCount / pageSize;
}
else
{
PageNum = allCount / pageSize + 1;
}
return PageNum;
}
//分页以1000作为基数
//Dictionary[表名,分页总数]
Dictionary<int, int> tableCountDictionary = new Dictionary<int, int>();
//StaticConst.CategorySheetCount=20
for (int i = 0; i < StaticConst.CategorySheetCount; i++)
{
int pageNum = PageHelper.GetPageNum(_commodityService.GetTableCount(i), StaticConst.PageGetCount); ;
tableCountDictionary.Add(i, pageNum);
}
第二步、得到[表索引,页码]集合
对第一步的Dic字典循环,我们又得到一个新的集合列表,列表的内容是【表索引,分页索引】的集合
集合的例子是:[{0,1},{0,2},{0,3}]
解释,第一张表第一页,第一张表第二页,第一张表第三页这样的集合
public class TableIndexModel
{ /// <summary>
/// 表索引
/// </summary>
public int TableIndex { get; set; }
/// <summary>
/// 分页索引
/// </summary>
public int PageIndex { get; set; }
}
//得到[表索引,页码]集合
List<TableIndexModel> timList = new List<TableIndexModel>();
foreach (var tcd in tableCountDictionary)
{
for (int i = 1; i <= tcd.Value; i++)
{
timList.Add(new TableIndexModel()
{
TableIndex = tcd.Key,
PageIndex = i
});
}
}
第三步、定义每一个线程需要完成的内容
根据第二步骤,我们得到了一个[表索引,页码]的集合,接下去我们开始分配每个线程要完成的任务量
如果我们集合个数是3000,我们对其3000进行分页,最好是将页数定义的多一点,这样每个集合处理的任务量少,耗时少。
执行完上述代码后我们可以得到一个List<[表索引,页码]>的集合,这个集合就是我们最终得到的集合。这个集合的好处如果大家看得懂代码,能够体会到好处,那就是任务分配均匀,每个集合要处理的任务数都是相同的,这样多线程处理的时候就不会有快慢之分。能够快速切换任务和节约执行时间。
/*平均分配任务的业务逻辑为:
每个线程需要处理多少任务=timList.Count 总个数 /分页数
*/
int workPageNum = PageHelper.GetPageNum(timList.Count, threadCount);
//得到[平均分配后的任务列表]
List<List<TableIndexModel>> taskDataList = new List<List<TableIndexModel>>();
for (int i = 1; i <= workPageNum; i++)
{
var list = timList.Skip((i - 1) * threadCount).Take(threadCount).ToList();
taskDataList.Add(list);
}
第四步、多线程处理
如下代码因为是部分贴图,所以可能理解起来较为困难,如果有问题的观众可以直接下载github上面的源码,自己调试看看效果,有问题也可以email给我。
代码的大概意思是,开启20个线程,20个线程处理第三步得到的集合。
- 得到一个随机编码,这是索引存储的Lucene文件夹名称,判断编码是否存在,如果不存在加入编码list集合
- 对当前集合建立索引
- 将当前任务加入List<Task>集合,判断任务集合是否超出20上限,如果超出,等待集合中任务完成。这一步是用来手动限定20个线程数量的。
- 在所有任务结束后,对当前的编码集合进行索引合并。
在索引建立时,如果存在错误,即认定索引建立失败,结束所有的任务
List<Task> taskList = new List<Task>();
var threadNums = Enumerable.Range(1, StaticConst.CategorySheetCount).ToList();
Random random = new Random();
int index = 0, alltakCount = taskDataList.Count;
foreach (var taskData in taskDataList)
{
index++;
var threadCode = CommodityDAL.GetTName(random.Next(1, threadNums.Count));
Task task = taskFactory.StartNew(() =>
{
try
{
LuceneBulid luceneBuild = new LuceneBulid();
if (!PathSuffixList.Any(u => u == threadCode))
{
PathSuffixList.Add(threadCode);
}
//建立索引
luceneBuild.BuildIndex(taskData, threadCode, true);
}
catch (Exception ex)
{
Console.WriteLine($"BuildIndexError\t{ex.Message}");
CTS.Cancel();
}
}, CTS.Token);
taskList.Add(task);
if (taskList.Count > 20)
{
taskList = taskList.Where(t => !t.IsCompleted && !t.IsCanceled && !t.IsFaulted).ToList();
Task.WaitAny(taskList.ToArray());
}
}
taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), MergeAllLuceneIndex));
private void MergeAllLuceneIndex(Task[] obj)
{
try
{
ILuceneBulid builder = new LuceneBulid();
builder.MergeAllLuceneIndex(PathSuffixList.ToArray());
OnTaskComplate(new EventArgs());//任务完成触发事件
}
catch (Exception ex)
{
StringValueEventArgs e = new StringValueEventArgs() { Value = ex.Message };
OnTaskError(e);//每完成一个任务触发事件
Console.WriteLine($"MergeAllLuceneIndex\t{ex.Message}{ex}");
}
}