我的网站数据同步的一些思考与改进

背景

闲的没事,本身写了个小网站,搭建在本身国外的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