<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    關(guān)于數(shù)據(jù)同步的一些思考與改進(jìn)

    共 8162字,需瀏覽 17分鐘

     ·

    2021-05-29 15:42

    公眾號關(guān)注 “GitHub今日熱榜
    設(shè)為 “星標(biāo)”,帶你挖掘更多開發(fā)神器!






    背景


    閑的沒事,自己寫了個小網(wǎng)站,搭建在自己國外的VPS上,VPS內(nèi)存極小(512M),而且還要跑點(diǎn)別的(你懂的),內(nèi)存更緊張巴巴. 改造之前小網(wǎng)站用到了時髦Redis,Rabbmitmq,Mysql,那時候阿里云的學(xué)生主機(jī)內(nèi)存富足,裝這么多中間件壓力不大,可到了這樣的小內(nèi)存VPS上,一切都變得水土不服,索性啥中間件都不要了,數(shù)據(jù)庫也不要了.


    沒了數(shù)據(jù)庫,網(wǎng)站的數(shù)據(jù)從哪里來?存在哪里? 文本形式持久化到本地磁盤?


    國外的VPS不比國內(nèi),可能哪天說不能訪問就不能訪問了,VPS的磁盤存儲顯然不踏實(shí).


    同事給我建議了萬能的Github,聽過Github托管代碼,托管靜態(tài)頁面,托管女裝大佬,但托管網(wǎng)站數(shù)據(jù)倒是第一次聽說,于是我對網(wǎng)站架構(gòu)進(jìn)行了重新設(shè)計.


    Plan1 數(shù)據(jù)的同步



    小網(wǎng)站數(shù)據(jù)不多,10M左右,所有數(shù)據(jù)直接加載到內(nèi)存中服務(wù)器也不會吃力,網(wǎng)站啟動,自動從Github Clone數(shù)據(jù),并定期把內(nèi)存中的數(shù)據(jù)序列化后Push到Github.


    可以看到,整個過程中,好像沒有磁盤啥事了,在我的眼里,Github就是一塊延時略高的磁盤(其實(shí)延時也還好,國外的Github訪問速度飛快).


    Plan2 同步的頻率


    磁盤的讀取速度和內(nèi)存無法比,何況遠(yuǎn)程的Github,那么如果減少數(shù)據(jù)從內(nèi)存到Github的同步開銷呢?顯然就是減少同步的頻率.


    一小時同步一次,應(yīng)該夠了.


    但如果我的網(wǎng)站在這一小時掛了boom,而數(shù)據(jù)還沒來得及同步,那上次一同步到網(wǎng)站掛掉這個時間段內(nèi)的數(shù)據(jù)不就沒了嗎?細(xì)思極恐!


    Plan3 多多不益善


    既然一小時一次不安全,那就一分鐘同步一次!


    其實(shí)這樣也是有問題的,小網(wǎng)站一般都是無人問津,如果以較高的頻率進(jìn)行數(shù)據(jù)同步,可以說絕大多數(shù)(用互聯(lián)網(wǎng)的所法是百分之N個9)的數(shù)據(jù)同步都是沒意義的,同時還增大了數(shù)據(jù)的同步開銷,沒準(zhǔn)Github還會把我的賬號給封了.


    Plan4 內(nèi)存數(shù)據(jù)變更立即觸發(fā)數(shù)據(jù)同步


    在我的網(wǎng)站中,有統(tǒng)一的數(shù)據(jù)訪問層,只要數(shù)據(jù)訪問層中的insert,update,delete處加入數(shù)據(jù)同步事件,即可實(shí)現(xiàn)一旦更新立即同步.


    這樣是數(shù)據(jù)是安全了,可是一次訪問請求往往伴隨著多次數(shù)據(jù)更新,每更新一次同步一次,可能是最腦殘的做法吧.


    Question


    數(shù)據(jù)更改一次同步一次不合理,同步頻率太低數(shù)據(jù)不安全,頻率太高多數(shù)同步?jīng)]有意義,到底該怎樣呢?


    局部性原理


    在揭開我的設(shè)計方案前,我們先來過一下CPU訪問存儲器時所遵守的局部性原理.


    在計算機(jī)存儲介質(zhì)這個金字塔中,越靠近金字塔頂端,空間越小,但是讀取數(shù)據(jù)越快;越靠近金字塔底端,空間越大,但訪問速度也越慢.


    正式因?yàn)檫@樣,所以每次自下而上的數(shù)據(jù)數(shù)據(jù)流大小逐層遞增, 交換頻率逐層遞減,如何在時間與空間上取到平衡點(diǎn)是關(guān)鍵.


    于是有了空間局部性原理和時間局部性原理,力求讓計算機(jī)的數(shù)據(jù)流動更高效.



    空間局部性


    如果一條數(shù)據(jù)被訪問,那么與它臨近的數(shù)據(jù)也可能要被用到. 比如數(shù)組,你訪問了索引1上的數(shù)據(jù),那么1附近的數(shù)據(jù)當(dāng)然很有可能被訪問,所以這個時候干脆把1附近的數(shù)據(jù)也往上加載一個層級.


    時間局部性


    如果一條數(shù)據(jù)項(xiàng)正在被訪問,那么在近期它很可能還會被再次訪問,所以這個時候干脆就把它留在當(dāng)前層級,先不急著回收掉.


    而網(wǎng)站的數(shù)據(jù)的更新也是具有時間局部性的,像我這樣并冷門的網(wǎng)站,基本沒人訪問,但是一旦訪問了,立即就要進(jìn)行點(diǎn)擊量的更新,站點(diǎn)響應(yīng)速度的記錄,沒準(zhǔn)又會有評論留言,然后要通知管理員進(jìn)行留言審核.這大概就是不鳴則已,一鳴驚人,一次訪問短期內(nèi)往往立即觸發(fā)一連串的數(shù)據(jù)更新,我認(rèn)為這也是一種時間局部性.


    所以,在數(shù)據(jù)同步上,我設(shè)計了如下方案.


    • 另起一個線程作為定時任務(wù),主要負(fù)責(zé)定時數(shù)據(jù)同步

    • 正常情況下,每小時與Github進(jìn)行數(shù)據(jù)同步.

    • 一旦網(wǎng)站數(shù)據(jù)被更新,檢查剩余同步時間是否大于30秒.
      ** 如果大于三十秒,強(qiáng)行把計時器剩余時間設(shè)置為30秒.
      ** 如果小于三十秒,不做操作.

    • 計時器時間走完,立即同步數(shù)據(jù)到Github.


    原本文章說到這里就可以結(jié)束了,但程序員注定愛代碼愛過文字,又恰好我天生愛造輪子,我從令牌桶得到靈感設(shè)計了一個乞丐版沙漏計時器,可以用于任何定時任務(wù)的執(zhí)行,班門弄斧,歡迎提出改進(jìn)意見.


    public class BlogsTimer
    {
        private static Stack<int> _upFunnel; //沙漏上部分
        private static Stack<int> _downFunnel; //沙漏下部分
        private static readonly List<Action> TimerEvents; //定時執(zhí)行的事件
        private static bool _timerSwitch; //沙漏開關(guān)
        private static readonly int Speed; //每秒消費(fèi)令牌數(shù)量
        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); //起一個線程消費(fèi)桶里的令牌
            _timerThread.Start();
            LunchEvents(); // 觸發(fā)事件
        }
        public static void Stop()
        
    {
            _timerSwitch = false;
        }

        //給沙漏注冊定時執(zhí)行事件
        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) //當(dāng)前沙漏中剩余令牌小于設(shè)置中秒數(shù),則返回不加速
                    return;
                while (_upFunnel.Count > accelerateSeconds && _upFunnel.Count > 1) //令牌數(shù)大于秒數(shù),則釋放出多余令牌
                {
                    _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; //旋轉(zhuǎn)沙漏
                        _downFunnel = _upFunnel;
                        _upFunnel = tempStack;
                    }
                }
                Thread.Sleep(Speed);
            }
        }
    }




    出處:cnblogs.com/CoderAyu/p/11839520.html










    關(guān)注GitHub今日熱榜,專注挖掘好用的開發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開發(fā)者成長!









    點(diǎn)個在看,你最好看


    瀏覽 36
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    91久久婷婷亚洲精品成人 | 青娱乐亚洲精品视频 | 日韩欧美一级操逼大片 | 高清一区在线观看视频 | 激情五月成人网 |