我的網站數據同步的一些思考與改進

背景

閒的沒事,本身寫了個小網站,搭建在本身國外的VPS上,VPS內存極小(512M),並且還要跑點別的(你懂的),內存更緊張巴巴. 改造以前小網站用到了時髦的Redis,Rabbmitmq,Mysql,那時候阿里雲的學生主機內存富足,裝這麼多中間件壓力不大,可到了這樣的小內存VPS上,一切都變得水土不服,索性啥中間件都不要了,數據庫也不要了.
沒了數據庫,網站的數據從哪裏來?存在哪裏? 文本形式持久化到本地磁盤?
國外的VPS不比國內,可能哪天說不能訪問就不能訪問了,VPS的磁盤存儲顯然不踏實.
同事給我建議了萬能的Github,聽過Github託管代碼📜,託管靜態頁面🔮,託管女裝大佬💃,但託管網站數據卻是第一次據說,因而我對網站架構進行了從新設計.html

Plan1 數據的同步

users

小網站數據很少,10M左右,全部數據直接加載到內存中服務器也不會吃力,網站啓動,自動從Github Clone數據,並按期把內存中的數據序列化後Push到Github.
能夠看到,整個過程當中,好像沒有磁盤啥事了,在個人眼裏,Github就是一塊延時略高的磁盤(其實延時也還好,國外的Github訪問速度飛快).git

Plan2 同步的頻率

磁盤的讀取速度和內存沒法比,況且遠程的Github,那麼若是減小數據從內存到Github的同步開銷呢?顯然就是減小同步的頻率.
一小時同步一次,應該夠了.
但若是個人網站在這一小時掛了boom🌋,而數據還沒來得及同步,那上次一同步到網站掛掉這個時間段內的數據不就沒了嗎?細思極恐😱!程序員

Plan3 多多不益善

既然一小時一次不安全,那就一分鐘同步一次!
其實這樣也是有問題的,小網站通常都是無人問津,若是以較高的頻率進行數據同步,能夠說絕大多數(用互聯網的所法是百分之N個9)的數據同步都是沒意義的,同時還增大了數據的同步開銷,沒準Github還會把個人帳號給封了.github

Plan4 內存數據變動當即觸發數據同步

在個人網站中,有統一的數據訪問層,只要數據訪問層中的insert,update,delete處加入數據同步事件,便可實現一旦更新當即同步.
這樣是數據是安全了,但是一次訪問請求每每伴隨着屢次數據更新,每更新一次同步一次,多是最腦殘🙈的作法吧.sql

Question

數據更改一次同步一次不合理,同步頻率過低數據不安全,頻率過高多數同步沒有意義,到底該怎樣呢?數據庫

局部性原理

在揭開個人設計方案前,咱們先來過一下CPU訪問存儲器時所遵照的局部性原理.c#

在計算機存儲介質這個金字塔中,越靠近金字塔頂端,空間越小,可是讀取數據越快;越靠近金字塔底端,空間越大,但訪問速度也越慢.
正式由於這樣,因此每次自下而上的數據數據流大小逐層遞增, 交換頻率逐層遞減,如何在時間與空間上取到平衡點是關鍵.
因而有了空間局部性原理和時間局部性原理,力求讓計算機的數據流動更高效.數組

users

空間局部性

若是一條數據被訪問,那麼與它臨近的數據也可能要被用到. 好比數組,你訪問了索引1上的數據,那麼1附近的數據固然頗有可能被訪問,因此這個時候乾脆把1附近的數據也往上加載一個層級.安全

時間局部性

若是一條數據項正在被訪問,那麼在近期它極可能還會被再次訪問,因此這個時候乾脆就把它留在當前層級,先不急着回收掉.服務器

而網站的數據的更新也是具備時間局部性的,像我這樣並冷門的網站,基本沒人訪問,可是一旦訪問了,當即就要進行點擊量的更新,站點響應速度的記錄,沒準又會有評論留言,而後要通知管理員進行留言審覈.這大概就是不鳴則已,一舉成名,一次訪問短時間內每每當即觸發一連串的數據更新,我認爲這也是一種時間局部性.

因此,在數據同步上,我設計了以下方案.

  • 另起一個線程做爲定時任務,主要負責定時數據同步
  • 正常狀況下,每小時與Github進行數據同步.
  • 一旦網站數據被更新,檢查剩餘同步時間是否大於30秒.
    ** 若是大於三十秒,強行把計時器剩餘時間設置爲30秒.
    ** 若是小於三十秒,不作操做.
  • 計時器時間走完,當即同步數據到Github.

定時沙漏⏳

本來文章說到這裏就能夠結束了,但程序員註定愛代碼愛過文字,又剛好我天生愛造輪子,我從令牌桶獲得靈感設計了一個乞丐版沙漏計時器,能夠用於任何定時任務的執行,班門弄斧,歡迎提出改進意見.

Show time

public class BlogsTimer
{
    private static Stack<int> _upFunnel;  //沙漏上部分
    private static Stack<int> _downFunnel;  //沙漏下部分
    private static readonly List<Action> TimerEvents;  //定時執行的事件
    private static bool _timerSwitch;  //沙漏開關
    private static readonly int Speed;  //每秒消費令牌數量
    private static Thread _timerThread;
    private static readonly object TimerLock;
    static BlogsTimer() {
        _upFunnel = new Stack<int>();
        _downFunnel = new Stack<int>();
        Speed = 1 * 1000;
        TimerEvents = new List<Action>();
        TimerLock = new object();
    }
    //計時器開始
    public static void Start(TimeSpan timeSpan) {
        lock (TimerLock)
        {
            _upFunnel.Clear();
            _downFunnel.Clear();
            for (var i = 0; i < timeSpan.TotalSeconds; i++)
            {
                _upFunnel.Push(i);
            }
        }
        _timerSwitch = true;
        _timerThread = new Thread(Consume); //起一個線程消費桶裏的令牌
        _timerThread.Start();
        LunchEvents(); // 觸發事件
    }
    public static void Stop() {
        _timerSwitch = false;
    }

    //給沙漏註冊定時執行事件
    public static void Register(Action timeEvent) {
        TimerEvents.Add(timeEvent);
        timeEvent.Invoke();
    }

    //把沙漏加速到指定的時間
    public static void AccelerateTo(TimeSpan timeSpan) {
        var accelerateSeconds = timeSpan.TotalSeconds;
        lock (TimerLock)
        {
            if (_upFunnel.Count < accelerateSeconds) //當前沙漏中剩餘令牌小於設置中秒數,則返回不加速
                return;
            while (_upFunnel.Count > accelerateSeconds && _upFunnel.Count > 1)  //令牌數大於秒數,則釋放出多餘令牌
            {
                _downFunnel.Push(_upFunnel.Pop());
            }
        }
    }
    private static void LunchEvents() {
        TimerEvents.ForEach(a => a.Invoke());
    }
    private static void Consume() {
        while (_timerSwitch)
        {
            lock (TimerLock)
            {
                if (_upFunnel.TryPop(out var item))
                {
                    _downFunnel.Push(item);
                }
                else
                {
                    LunchEvents();
                    var tempStack = _downFunnel;  //旋轉沙漏
                    _downFunnel = _upFunnel;
                    _upFunnel = tempStack;
                }
            }
            Thread.Sleep(Speed);
        }
    }
}

源碼地址: https://github.com/liuzhenyulive/iBlogs/blob/master/Src/iBlogs.Site.Core/Common/iBlogsTimer.cs
演示地址: https://www.iblogs.site

 

出處:http://www.javashuo.com/article/p-pdjyeoqs-dt.html

相關文章
相關標籤/搜索