程序员过关斩将--从每秒6000写请求谈起

源码共读

共 2475字,需浏览 5分钟

 ·

2020-03-09 23:23



作者丨菜v菜来源丨架构师修行之路

菜菜哥,紧急求助呀

2b2e01d16d98cafe8e14fb6fb79f4d6c.webp805e9c5df6d1bcf2d9b36038c180f65e.webp

怎么回事?产品经理砍你了?

5125fe9c5592db565a6db4e4abf562c2.webp8ea9d477dcc60603185e45fbf65e38dc.webp

没有,只是写了个新项目,上线就被压垮了

2b2e01d16d98cafe8e14fb6fb79f4d6c.webp805e9c5df6d1bcf2d9b36038c180f65e.webp

什么功能,这么强悍?

5125fe9c5592db565a6db4e4abf562c2.webp8ea9d477dcc60603185e45fbf65e38dc.webp

一个记录用户观看视频进度信息的功能

2b2e01d16d98cafe8e14fb6fb79f4d6c.webp805e9c5df6d1bcf2d9b36038c180f65e.webp

那如果用户基数大,确实是需要注意的,那我给你分析一下哈

5125fe9c5592db565a6db4e4abf562c2.webp8ea9d477dcc60603185e45fbf65e38dc.webp7d3485771eb0c0be95e23b25a0b18627.webp背景
3c2dead7716b76152f61c8fd067bee15.webp每一个片子的幕后,都保留了你的观看记录,详细的记着你观看了几次,跳过了那些时长 ,据说根据这些数据可以分析出你喜欢哪个日本明星,以此来做定向推送......d9ae5666f036558dd649b87b3db7f29d.webp虽然看起来很简单的一个功能,其实涉及到的数据量非常大,极限情况下为你的用户数*视频数的乘积。
那么在只有两个网站服务器,一台sqlserver的情况下,该如何面对这样不算大数据量的写请求呢?为什么说是写请求呢?因为用户观看视频的每一秒你都需要记录下来,例如:视频的第十秒用户观看了。要想把这个功能搞定,首先需要定义几个事情:1. 记录用户观看视频情况的数据定义2. 和客户端交互的数据协议3. 数据库中记录的数据格式4. 如何解决服务器写的压力(毕竟单台服务器请求数还是比较大)
7d3485771eb0c0be95e23b25a0b18627.webp解决方案

用户观看视频进度定义

3c2dead7716b76152f61c8fd067bee15.webp对于一个视频来说,假如有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

和客户端交互协议

3c2dead7716b76152f61c8fd067bee15.webp用户观看视频的进度实时信息,只有客户端知道,客户端需要上传用户的观看进度数据,和服务端交互的进制可以选择通用性比较强的16进制,当然你选择100进制也无所谓,只要双方能同时支持,并且能正常解析即可

数据库数据格式

3c2dead7716b76152f61c8fd067bee15.webp每种数据库支持的数据类型有差异,所以这里不在过多叙述,当然无论什么格式,占用空间越少越好,但也要根据业务的计算量来综合考虑。7d3485771eb0c0be95e23b25a0b18627.webp解决问题

cpu性能问题

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

//把16进制的字符串按照两位 分割成十进制数组
        protected List<intConvertToProgressArray(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;
        }

客户端请求数量问题

3c2dead7716b76152f61c8fd067bee15.webp如果同时一万用户在同时观看视频,上传数据时间间隔为2秒,意味着每秒有5000请求。由于这个业务只是一个用户log型业务,何为log型,就是说可以容忍一部分数据丢失,针对这个数据形态,客户端可以先在本地做缓冲记录,没有必要一秒上传一次记录,例如现在约定的客户端30秒上传一次记录,如果用户关掉客户端,下次启动的时候会重新上传未成功的记录。

数据库压力

3c2dead7716b76152f61c8fd067bee15.webp如果每次请求都单独更新数据库,按照第二条的计算每秒高达5000次update请求。用户观看每次视频都加载内存中缓存,仔细分析这种业务,由于是log型数据,所以每次你请求没有必要都去更新数据库,而是先更新了缓存,然后定时去更新数据库。
由于数据量的问题,所有的更新操作都会发送到一个任务队列,队列的执行者会根据配置批量更新数据库,这样比单条更新数据库性能要高很多,其实这种方案在很多log型的业务中都有使用,批量更新对数据库的压力要小很多,代码类似以下
public async Task<intAddUserVideoData(UserVideoInfo data, DBProcessEnum processType = DBProcessEnum.Update)
        
{
            if(processType== DBProcessEnum.Add)
            {
                AddQueue.Enqueue(data);
            }

            return 1;
        }

 void MulProcessData()
        
{
            //每次更新的条数
            int maxNumber = 50;
            List data = new List();
            while (true)
            {
                if (data == null)
                {
                    data = new List();
                }
                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)
                {

                }


            }
        }

7d3485771eb0c0be95e23b25a0b18627.webp写在最后
3c2dead7716b76152f61c8fd067bee15.webp其实这种高IO的操作用sqlserver这种关系型数据库反而不好,Nosql在这种简单高IO的情境下要很多,改天可以改为redis试一试,估计会比sqlserver要好很多。

0f76187b62b8a382f808ef6caa2d5b70.webp

近期精彩内容推荐:  

bcd0f60e6c02e1212644a01ead9b53a6.webp 有个程序媛上司是什么体验

bcd0f60e6c02e1212644a01ead9b53a6.webp 一个天才程序员的黑帮大佬人生

bcd0f60e6c02e1212644a01ead9b53a6.webp 200行Python代码做一个换脸程序

bcd0f60e6c02e1212644a01ead9b53a6.webp 在 IntelliJ IDEA 中使用 Git,太方便了!




7d843899962f202f3102be79cd0c8ec0.webp

在看点这里a5647cf69d27a99af70b6aaea263f5bb.webp好文分享给更多人↓↓

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报