乐趣区

关于后端:从每秒6000写请求谈起

背景

每一个片子的幕后,都保留了你的观看记录,具体的记着你观看了几次,跳过了那些时长,据说依据这些数据能够剖析出你喜爱哪个日本明星,以此来做定向推送 ……

尽管看起来很简略的一个性能,其实波及到的数据量十分大,极限状况下为你的用户数 * 视频数的乘积。

那么在只有两个网站服务器,一台 sqlserver 的状况下,该如何面对这样不算大数据量的写申请呢?为什么说是写申请呢?因为用户观看视频的每一秒你都须要记录下来,例如:视频的第十秒用户观看了。要想把这个性能搞定,首先须要定义几个事件:

  1. 记录用户观看视频状况的数据定义
  2. 和客户端交互的数据协定
  3. 数据库中记录的数据格式
  4. 如何解决服务器写的压力(毕竟单台服务器申请数还是比拟大)

解决方案

用户观看视频进度定义

对于一个视频来说,如果有 1 个小时的时长,这 3600 秒对应着 3600 个是否曾经观看的状态,对于观看状态来说,只有观看和未观看两种状态,所以一个 bit 足以,一个字节(byte)有 8 个 bit,所以一个 byte 能够示意 8 秒的观看状态,以此为根底,进制越高,同样数量的字符示意的状态就越多。

客户端每次上传新的数据,须要和服务端曾经存在的数据做位运算,例如:01000 示意第二秒观看了,客户端新上传:00011 示意第 4,5 秒都观看了,对于用户而言这个视频第 2,4,5 秒都看过,尽管只是一个简略的运算,然而量大的时候,对 cpu 的耗费不容小觑。

 第一字节    第二字节
  0 1 2 3 4 5 6 7  0 1 2 3 4 5 6 7 
bit:1 0 0 0 1 0 0 0  0 1 0 0 0 0 0 0
二进制:0x88    0x40
字符串:8840
和客户端交互协定

用户观看视频的进度实时信息,只有客户端晓得,客户端须要上传用户的观看进度数据,和服务端交互的进制能够抉择通用性比拟强的 16 进制,当然你抉择 100 进制也无所谓,只有单方能同时反对,并且能失常解析即可

数据库数据格式

每种数据库反对的数据类型有差别,所以这里不在过多叙述,当然无论什么格局,占用空间越少越好,但也要依据业务的计算量来综合思考。

解决问题

cpu 性能问题

毕竟要把用户每次最新的观看数据和老数据做合并工作,在用户量大的状况下不容小觑。在综合了各种条件之后,最终采纳 10 进制来做合并工作,客户端上传上来 16 进制数据,而后转化为十进制,而后和观看记录(10 进制)做合并运算,这部分 cpu 省略不了,具体转化程序为:

 // 须要新加的数据
        ConcurrentQueue<UserVideoInfo> AddQueue = new ConcurrentQueue<UserVideoInfo>();

// 把 16 进制的字符串依照两位 宰割成十进制数组
        protected List<int> ConvertToProgressArray(string progressString)
        {if (string.IsNullOrWhiteSpace(progressString))
            {return null;}
            // 验证是否为 2 的倍数长度
            if (progressString.Length % 2 != 0)
            {return null;}
            var proStrSpan = progressString.AsSpan();
            List<int> ret = new List<int>();
          
            int i = 0;
            while (i < proStrSpan.Length)
            {ret.Add(int.Parse(proStrSpan.Slice(i, 2).ToString(), System.Globalization.NumberStyles.HexNumber)); ;
                i = i + 2;
            }
            return ret;
        }
客户端申请数量问题

如果同时一万用户在同时观看视频,上传数据工夫距离为 2 秒,意味着每秒有 5000 申请。因为这个业务只是一个用户 log 型业务,何为 log 型,就是说能够容忍一部分数据失落,针对这个数据状态,客户端能够先在本地做缓冲记录,没有必要一秒上传一次记录,例如当初约定的客户端 30 秒上传一次记录,如果用户关掉客户端,下次启动的时候会从新上传未胜利的记录。

数据库压力

如果每次申请都独自更新数据库,依照第二条的计算每秒高达 5000 次 update 申请。用户观看每次视频都加载内存中缓存,仔细分析这种业务,因为是 log 型数据,所以每次你申请没有必要都去更新数据库,而是先更新了缓存,而后定时去更新数据库。

因为数据量的问题,所有的更新操作都会发送到一个工作队列,队列的执行者会依据配置批量更新数据库,这样比单条更新数据库性能要高很多,其实这种计划在很多 log 型的业务中都有应用,批量更新对数据库的压力要小很多, 代码相似以下

public async Task<int> AddUserVideoData(UserVideoInfo data, DBProcessEnum processType = DBProcessEnum.Update)
        {if(processType== DBProcessEnum.Add)
            {AddQueue.Enqueue(data);
            }
           
            return 1;
        }

 void MulProcessData()
        {
            // 每次更新的条数
            int maxNumber = 50;
            List<UserVideoInfo> data = new List<UserVideoInfo>();
            while (true)
            {if (data == null)
                {data = new List<UserVideoInfo>();
                }
                try
                {if (!AddQueue.Any() && !UpdateQueue.Any())
                    {System.Threading.Thread.Sleep(500);
                    }                   
                    else
                    {
                        // 先解决 须要更新的
                        data.Clear();
                        while (data.Count <= maxNumber && AddQueue.Any())
                        {if (!AddQueue.TryDequeue(out UserVideoInfo value))
                            {continue;}
                            // 判断是否有反复对象
                            if (data.Any(s => s.UserId == value.UserId && s.VideoId == value.VideoId))
                            {var exsitItem = data.First(s => s.UserId == value.UserId && s.VideoId == value.VideoId);
                                exsitItem = value;
                            }
                            else
                            {data.Add(value);
                            }

                        }
                        if (data != null && data.Any())
                        {var ret = UserVideoProgressProxy.Add(data);
                        }
                        
                    }
                }
                catch (Exception err)
                {}}
        }

写在最初

其实这种高 IO 的操作用 sqlserver 这种关系型数据库反而不好,Nosql 在这种简略高 IO 的情境下要很多,改天能够改为 redis 试一试,预计会比 sqlserver 要好很多。

更多精彩文章

  • 分布式大并发系列
  • 架构设计系列
  • 趣学算法和数据结构系列
  • 设计模式系列

退出移动版