后台索引生产/消费模式

这是种模式在现实生活中的例子很多:

  邮局寄信

  生产者:你,消费者:投递员,任务列表:邮筒

  你写信然后扔到邮筒中去,给任务列表中添加了一个任务。投递员取走有邮筒里的信,消费掉任务列表里的一个任务。

  邮局这样做的好处在于:

  1.解耦 你不必去认识投递员,万一认识的那个投递员不干了,你又要重新认识一个投递员。

  2.支持并发 你不必在某个地点傻等着投递员,同时,投递员也不需挨家挨户的问,哪家需要寄信。

对比邮局寄信的事情,类似博客、论坛等发文章的网站,创建文章索引库也有些类似。

  生产者:创建任务,添加到任务列表中,例如添加一篇随笔。

  消费者:将任务列表中,某一篇随笔添加到索引库中,这样在搜索的时候,才能够搜索出来新发的随笔。

  创建索引库是耗时很长的工作,所以启动有一个消费者线程一直保持对IndexWriter写的状态,有新任务进入的时候对IndexWriter写入,写入完成之后关闭。然后下次while循环扫描的时候判断如果队列汇总没有任务,则sleep5秒钟后再判断,防止不断判断给服务器cpu压力。

IndexManager.cs代码:

  

 public class IndexManager
    {
        //单例
        private IndexManager()
        {
        }
        private static IndexManager instance = new IndexManager();
        public static IndexManager Instance()
        {
            return instance;
        }

        //任务列表
        private List<IndexJob> jobs = new List<IndexJob>(); 
        //启动消费者线程
        public void Start()
        {
            Thread threadIndex = new Thread(Index);
            threadIndex.IsBackground = true;
            threadIndex.Start();
        }
        //创建索引
        private void Index()
        {
            while (true)
            {
                //防止空转造成cpu占用率过高
                if (jobs.Count <= 0)
                {
                    //logger.Debug("没有任务,再睡会!");
                    Thread.Sleep(5 * 1000);
                    continue;
                }

                //为什么每次循环都要打开、关闭索引库。因为关闭索引库以后才会把写入的数据提交到索引库中。也可以每次操作都“提交”(参考Lucene.net文档)

                string indexPath = "c:/cmsindex";
                FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexPath), 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)
        {
            foreach (var job in jobs.ToArray())
            {
                //todo:异常处理
                jobs.Remove(job);// 消费掉
                //因为是自己的网站,所以直接读取数据库,不用webclient了
                //为避免重复索引,所以先删除number=i的记录,再重新添加
                writer.DeleteDocuments(new Term("number", job.Id.ToString()));

                //如果“添加文章”任务再添加,
                if (job.JobType == JobType.Add)
                {
                    BLL.newsrupeng newBll = new BLL.newsrupeng();
                    var art = newBll.GetModel(job.Id);
                    if (art == null)//有可能刚添加就被删除了
                      {
                        continue;
                    }
                    string title = art.title;
                    string body = art.content;//去掉标签                

                      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.NOT_ANALYZED));
                    document.Add(new Field("body", body, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
                    writer.AddDocument(document);
                    //logger.Debug("索引" + job.Id + "完毕");
                }
            }
        }

        //添加任务
        public void AddArticle(int artId)
        {
            IndexJob job = new IndexJob();
            job.Id = artId;
            job.JobType = JobType.Add;
            //logger.Debug(artId+"加入任务列表");
            jobs.Add(job);//把任务加入商品库
        }
        //删除任务
        public void RemoveArticle(int artId)
        {
            IndexJob job = new IndexJob();
            job.JobType = JobType.Remove;
            job.Id = artId;
            //logger.Debug(artId + "加入删除任务列表");
            jobs.Add(job);//把任务加入商品库
        }
    }

    /// <summary>
    /// 索引任务
    /// </summary>
    class IndexJob
    {
        public int Id { get; set; }
        public JobType JobType { get; set; }
    }
    enum JobType { Add, Remove }

 

 

 一个winform例子更佳能够体现这个模式。
  启动后台线程,向文本框中输入值,然后生产,listbox中经过5秒之后,才能显示出来刚刚添加文本。

       

代码:

public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }
        private List<string> jobList = new List<string>();
        //添加任务列表
        private void button1_Click(object sender, EventArgs e)
        {
            if (textBox1.Text!="")
            {
                jobList.Add(textBox1.Text);
                textBox1.Clear();
                textBox1.Focus();
            } 
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Start();
        }

        private void Start()
        {
            Thread thread = new Thread(ProcessDo);
            thread.IsBackground = true;
            thread.Start();
        }
        //工作
        private void ProcessDo()
        {
            while (true)
            {
                if (jobList.Count<=0)
                {
                    Thread.Sleep(5*1000);
                    continue;
                }
                foreach (var s in jobList.ToArray())
                {
                    MyDelegate d = (txt) =>
                    {
                        listBox1.Items.Add(s);
                    };
                    listBox1.Invoke(d, s);
                    jobList.Remove(s);//消费了商品就把商品从“仓库”中移除掉
                }
            }
        }
        
    }
    delegate void MyDelegate(string s);

 

posted on 2012-03-22 10:25  ancient_sir  阅读(245)  评论(0编辑  收藏  举报