代码改变世界

在asp.net中实现观察者模式,或有更好的方法(续)

2008-05-06 02:00  横刀天笑  阅读(2918)  评论(15编辑  收藏  举报
 写完这个Post后本来想把完整代码实现传上来,后来看到不少园友提出异议,看了大家的留言后我也一直在思索:我为什么这样做?当初我是怎样想到这个解决方案的?我在几个解决方案之间做了取舍了么?我这样做是不是矫枉过正了?经过这些思考有了现在的这个Post

首先我进一步谈一下需求:

这是一个Web Application,有很多客户端向服务器端提交数据(客户端是C++的,以http-post方式向服务器端提交二进制数据,服务器端解析这个二进制包,数据提交很频繁),管理员可以进入监视页面浏览这些数据,数据要即时的,客户端发来一条,管理员屏幕上要马上可以看到,允许多个管理员同时监视即时数据,所有管理员看到的数据都是一样的(目前是这样的,也许以后对管理员要分角色,各角色管理员看到的信息将不同)

由于数据提交非常频繁,客户要求不允许频繁的数据库操作,所以我将数据保存在一个IList的缓存里面,当这个IList的大小超过了我在配置文件里定义的大小的时候就将数据批量插入到数据库。

下面我将以我当初思考的思路为主线描述:

第一个版本:

//在程序里我写了一个静态类,这个静态类保存整个程序中共享的一些数据,相当于原来的//Application对象,但是静态成员是编译期类型检查的
public static ApplicationData
{
    
//这个队列用来保存客户端传递过来的数据,当队列达到一定长度的时候同步到数据库
    public static Queue<DataHead> OperateDataList = new Queue<DataHead>();
//这个List也是保存客户端传递过来的数据的,但它是为监视准备数据的,
//当一个监视页面的请求到来的时候将这个List的数据Response过去,然后Clear这个//List
    public static IList<DataHead> MonitorDataList = new List<DataHead>();
}
public class ReciveDataHandler : IHttpHandler
{
    
//……
    Public void ProcessRequest(HttpContext context)
    {
        
//解析从客户端传递过来的数据
        DataHead data = GetData(context);
        OperateDataList.Add(data);
        If(OperateDataList.Count 
> BufferSize)
        {
            
//将数据写入到数据库
            AddToBase();
}
        MonitorDataList.Add(data);
}
}
//监视页面从这里获取数据
public class MonitorHandler : IHttpHandler
{
    
//……
    Public void ProcessRequest(HttpContext context)
    {
        If(MonitorDataList.Count 
> 0)
        {
            
//将MonitorDataList里的数据Response出去
            OutPut();
            MonitorDataList.Clear();
}
}
}

 

说实话,我当初做出这个的时候觉得一点问题都没有,开始的时候客户测试也没有发现任何问题,终于有一天客户和我同时测试部署在同一IIS的时候,问题出现了:只有一个监视页面有数据。看到这个后我还百思不得其解,顺着程序的执行流程一步一步走下去,没有找出任何错误。后来做了下日志,原来MonitorDataList是一个全局共享的,一个在监视把数据Clear了后别人就无法获取数据了。不知道有没有人这样做过:有时候忘记了自己正在做一个web程序,而web程序是一个并发的,对一些共享资源的访问有着微妙的问题,如果没有记住这点,按照程序流程的执行步骤是找不出任何问题的。

怎么办?再一看这不是事件订阅所描述的场景么?所以就有了上一篇PostSolution。不过那个方案受到不少人质疑,其中金色海洋提出这样的方法:

Public class ReciveData : IHttpHandler
{
    
//……….
    
//将客户端传递过来的数据存入数据库
}

Public 
class MonitorHandler : IHttpHandler
{
    
//………
    
//为null的时候说明该管理员第一次监视
    If(Session[“id”] == null)
    {
        
//根据时间从服务器取出数据
        
//并将取出数据的最后一个id保存在session中
        Session[“id”] = id;
}
//不为null则说明该管理员已经开始监视了
Else
{
    
//根据session里保存的最后一个id,取出大于那个id的数据
    Session[“id”] = currentId;
}
}

 

看似这个方案不错,我尝试着将我的程序修改为这样,但是我将上面的代码编写完,我发现我不可以再进行下去了:上面的方案满足不了我的需求,客户明确要求了客户端提交的数据要先缓存然后缓存超过配置大小(这个大小还需要可以在配置文件里面配置,以便可以经过测试找出一个最合理的值),而这种Session记录的方案是依靠数据库来保存数据,这个Session[“id”]就相当于一个游标,这个游标指向的是数据库,那好,我们将Session[“id”]指向缓存数据,但是请注意缓存随时可能超过设置大小而被同步到数据库并被清空。

经过一番思考后我还是回到我自己的Solution上,不过我又有了新的看法了。不是要将数据先缓存么?看看这个缓存,实际上她也是个观察者,至于她执行怎样的缓存策略是她的事情,如是我又有了一个新类:

//这里的代码接上篇Post
using System;
using System.Collections.Generic;
using System.Text;
namespace ForyourSoft.NetTraffic.Framework
{
    
public sealed class DataBase
    {
        
private IList<string> _buffer = new List<string>();
        
private static DataBase _instance = null;
        
public static void Subscribe()
        {
            
if (_instance == null)
                _instance 
= new DataBase(Monitor.Current);
        }
        
public DataBase(Monitor monitor)
        {
     monitor.OnMessage 
+= new EventHandler<Monitor.MessageEventArgs>(monitor_OnMessage);
        }
        
void monitor_OnMessage(object sender, Monitor.MessageEventArgs e)
        {
            _buffer.Add(e.Message);
            
if (_buffer.Count >= Config.BufferSize)
            {
                
//将数据添加到数据库
            }
        }
    }
}

 

PS:由于系统中我们只需要这样唯一一个订阅者,所以我将其实现为一个单件,在Application_Start的时候调用DataBase.Subscribe()

现在系统是这样的结构:


 

可以设想以后还会有更多的订阅者。果然,昨天客户要求在下一个版本中管理员分角色,各个角色看到的数据不同的,只有超级管理员才可以监视所有数据,OMG,呵呵,不过还好,我只需要添加几个订阅者就可以轻松搞定。

后记:也许是我的文章标题没有起好,也许很多人得到模式恐惧症,提到模式总是要来考察一下你的case,不是那种Enterprise级别的用了pattern就是过火了。其实这篇文章的内容里没有一点模式的气息,只是用.netEvent实现观察者模式的思想,我想如果合适,今天模式的投资,明天你会有收获的。