Web網站數據」實時」更新設計

 

  請注意這個實時打上了雙引號,沒有絕對的實時,只是時間的顆粒不同罷了(1ms,1s,1m)。html

服務器數據有更新能夠快速通知客戶端。Web 基於取得模式,而服務器創建大量的和客戶端鏈接來提供數據實時更新反而拉低服務器的使用效能。ajax

 請下載DEMO  點擊下載


 

1、現有方案概括有兩類。

  1. 服務器真實推送 - 基於瀏覽器外部控件數據實時更新。

    IE  ActiveX(flash)控件,還有其餘瀏覽器好比Firefox插件。這種基於瀏覽器外部插件的,因爲移植性差。主要要在一些瀏覽器安全上獲得應用。好比在線支付(支付寶),自動登錄(QQ)。和一些內網控制(電網內部控制管理)。 Flash 其實也是瀏覽器的一種插件後臺經過創建Socket 來與客戶端實時數據更新。這點比較有優點的是Flash 插件幾乎每臺機器上都有安裝,移植性沒有問題。但一樣對防火牆穿透能力差,並且須要消耗服務器大量的。數據庫

  1. 基於XMLHttpRequest 定時取的解決方案.

    Ajax 經過定時去詢問服務器是否有數據更新,彷佛是一個通用的解決方案,如:1元xx 類的網站,由於搶購模式須要實現更新商品的剩餘份數。好比要獲取服務器當前參與人數,獲取最新購買人數,發送的私信,好友消息,每一種類型數據都設定一個時間如:1秒到數據庫取一次數據。而大多數請求的連接是無效的,並且過多的請求會致使瀏覽器無響應。 api

 


 

2、爲何咱們不能二者結合,選擇折中的方案呢?

   客戶端腳本須要一個能夠告知的程序,告訴咱們服務器中有數據更新了,而後執行本身註冊好的程序到服務器取對應的數據。瀏覽器

咱們只須要服務器通知提供這樣的數據結構:緩存

{
    //用戶消息更新時間
    msg: '20141192003261234',
    //網站購買記錄更新時間
    buy: '20141192003534567'
}

 

獲取客戶端獲取當前的數據後 和本身當前瀏覽器中存儲的用戶消息更新時間進行對比。若是msg更新時間與服務器給的時間徹底一致,咱們就沒有必要到服務器中去用戶我的消息了,反之服務器中消息更新,開始執行咱們預約的程序來獲取服務器,用戶收到的消息。顯示給用戶,而且設置一下當前消息的更新時間。安全

咱們的須要的就這麼簡單——須要一個通知程序通知咱們服務器

 


3、咱們須要作什麼,會遇到那些問題。

    讓服務器通知客戶端程序數據更新顯然不是很划算的事情咱們上面討論過了,因此咱們須要在客戶端設置一個循環往復的定時程序到服務器裏面去取註冊好的類型(用戶消息,網站成交數量,購買人次,商品剩餘數量)的更新的時間,經過對於註冊對於數據類型的時間來判斷要不要執行咱們註冊好的方法。數據結構

  問題1:定時程序必須有序執行Ajax方法(前一個ajax完畢後才能發送第二次Ajax),服務器獲取數據是一個耗時操做。

       Ajax異步遞歸。app

  問題2:並非每個頁面都須要知道數據的變更狀況

       不一樣的頁面註冊不一樣的監聽信息。

  問題3:如何動態開始和阻止定時程序,而且保持程序只能運行一個定時實例。

      設置互斥量.

  問題4:服務器如何存儲這些數據的更新狀態(公共的:成交量,個體的:用戶消息)。

      須要一個存儲介質,存儲對應類型信息的更新時間

  問題5:如何在異常中恢復。確保定時程序能正常運行。

      Try ... Catch...  $.ajax error.

 

4、代碼實現

       客戶端定時程序:

       

 //客戶端監聽對象
    var listener = {
      tid: 0,
      keys: "",
      //任務存儲對象
      taskType: {},
      //註冊一個任務
      appendTaskType: function (key, type) {
        if (typeof (type) == "function" && typeof (key) == "string" && /^[a-z0-9]+$/.test(key)) {
          //添加一個任務
          this.taskType[key] = {
            //任務執行函數
            fun: type,
            //變化量
            ts: ''
          };
          var a = [];
          for (var k in this.taskType) { a.push(k); }
          this.keys = a.join('.');
        }
      },
      //開始運行監聽
      start: function () {
        //若是定時器正在運行則返回
        if (this.tid != 0)
          return;
        fn();
        //私有定時執行方法
        function fn() {
          $.getJSON("/api/listener", { keys: listener.keys }, function (d) {
            //獲取定時器全部的註冊類型
            for (var key in listener.taskType) {
              //獲取註冊類型對象
              var O = listener.taskType[key];
              //判斷當前對象是否存在和當前的值是否和以前的變化值同樣,是否真正執行
              if (d[key] && d[key] != O.ts) {
                //更改現有狀態
                O.ts = d[key];
                //執行註冊函數
                O.fun(O);
              }
            }
            //設置ID
            listener.tid = setTimeout(fn, 1000);
          })
        }
      },
      //關閉監聽
      stop: function () {
        //清除定時器
        clearTimeout(this.tid);
        //歸零
        this.tid = 0;
      }
    }
    //註冊對頁面的監聽
    listener.appendTaskType("msg", function () {
      $.getJSON("/api/getlist", { key: 'msg' }, function (data) {
        var b = $("#msgList");
        b.hide();
        b.empty();
        var html = '';
        for (var i = 0; i < data.length; i++) {
          html += "<li>【" + data[i].user + "】說:" + data[i].msg + "</li>"
        }
        b.html(html);
        b.slideDown(300);
      })
    });
    //註冊對購買記錄的監聽
    listener.appendTaskType("buy", function () {
      $.getJSON("/api/getlist", { key: 'buy' }, function (data) {
        var b = $("#buyList");
        b.hide();
        b.empty();
        var html = '';
        for (var i = 0; i < data.length; i++) {
          html += "<li>【" + data[i].user + "】購買了:" + data[i].msg + "</li>"
        }
        b.html(html);
        b.slideDown(300);
      })
    })
    //開始執行監放任務
    listener.start();

 

服務器代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers {
    public class ApiController : Controller {
        //
        // GET: /Api/
        /// <summary>
        /// 根據緩存Key獲取當前緩存最後更新的標示
        /// </summary>
        /// <param name="cacheKey">緩存Key</param>
        /// <returns>最後更新的時間</returns>
        public string getlast(string cacheKey) {
            List<data> ls = HttpRuntime.Cache.Get(cacheKey) as List<data>;
            if (ls != null)
                return ls.First().time;
            return "0";
        }
        /// <summary>
        /// 服務器定時方法,這個方法是檢測,數據有沒有進行更新
        /// </summary>
        /// <returns></returns>
        public ContentResult Listener(string keys) {
            if (string.IsNullOrWhiteSpace(keys))
                return Content("{}");
            System.Text.StringBuilder builder = new System.Text.StringBuilder("{");
            //客戶端須要知道用戶信息是否變更
            if (keys.Contains("msg")) {
                builder.AppendFormat("\"msg\":\"{0}\",", getlast("msg"));
            }
            //客戶端須要知道購買列表是否變更
            if (keys.Contains("buy")) {
                builder.AppendFormat("\"buy\":\"{0}\",", getlast("buy"));
            }
            //類推各類監聽.......

            //移除最後「,」
            if (builder.Length > 1) {
                builder.Remove(builder.Length - 1, 1);
            }
            builder.Append("}");
            return Content(builder.ToString());
        }
        public ContentResult msg(data ms) {
            InsertCache(ms, "msg");
            return Content("ok");
        }
        public ContentResult buy(data buy) {
            InsertCache(buy, "buy");
            return Content("ok");
        }
        private void InsertCache(data d, string cacheKey) {
            //使用時間設置最後更新量
            d.time = DateTime.Now.ToString("yyyyMMddhhmmssffff");
            //獲取存儲的值
            List<data> ls = HttpRuntime.Cache.Get(cacheKey) as List<data>;
            //判斷是否爲空
            if (ls == null) {
                ls = new List<data>();
                HttpRuntime.Cache.Insert(cacheKey, ls);
            }
            //添加到集合
            ls.Insert(0, d);
            //移除大於這個數
            if (ls.Count > 10)
                ls.RemoveRange(10, ls.Count - 10);
        }
        public JsonResult GetList(string key) {
            if (string.IsNullOrEmpty(key))
                return Json(null, JsonRequestBehavior.AllowGet);
            return Json(HttpRuntime.Cache.Get(key) as List<data>, JsonRequestBehavior.AllowGet);
        }
        public JsonResult GetCache() {

            return Json(new {
                p1 = HttpRuntime.Cache.EffectivePercentagePhysicalMemoryLimit,
                p2 = HttpRuntime.Cache.EffectivePrivateBytesLimit
            },
                JsonRequestBehavior.AllowGet);
        }
    }
    //定義一個數據存儲介質
    public class data {
        public string user { get; set; }
        public string msg { get; set; }
        public string time { get; set; }
    }
}

 

 請下載DEMO  點擊下載

相關文章
相關標籤/搜索