为了更精确的记录用户上一次播放的进度,我采用的方案是:前端每隔10秒就发起一次请求,将播放记录写入数据库。这涉及到大量的数据库写操作,在并发较高的情况下,会给数据库带来非常大的压力。该怎么解决呢?
1. 功能背景
-
网站课程以录播视频为主,为提升学习体验,需要实现视频续播功能。
-
用户断开连接或切换设备后,仍然可以从上一次播放位置继续学习。
-
产品要求:
-
续播时间误差控制在 20 秒以内。
-
支持多设备切换续播。
-
2. 目前方案
前端在播放视频时,每隔 10秒通过心跳请求将当前播放进度(currentTime
)发送到服务端,服务端将数据写入数据库。
3. 方案改进
播放进度记录较为频繁,写数据库的压力太大。
3.1 改进方案
当写数据库并发较高时,不再直接写到数据库。而是先将数据缓存到Redis,然后定期将缓存中的数据批量写入数据库。
由于Redis是内存操作,写的效率也非常高,这样每次请求的处理速度大大提高,响应时间大大缩短,并发能力肯定有很大的提升。
而且由于数据都缓存到Redis了,积累一些数据后再批量写入数据库,这样数据库的写频率、写次数都大大减少,对数据库压力小了非常多!
变化最大的有两点:
-
提交播放进度后,如果是更新播放进度则不写数据库,而是写缓存(覆盖之前旧数据)
-
需要一个定时任务,定期将缓存数据写入数据库
3.2 优缺点
3.2.1 优点
-
写缓存速度快,响应时间大大减少
-
降低数据库的写频率和写次数,大大减轻数据库压力
3.2.2 缺点
-
实现相对复杂
-
依赖Redis可靠性
-
不支持事务和复杂业务
3.2.3 场景
-
写频率较高、写业务相对简单的场景
3.3 持久化思路
3.3.1 一般思路
提交播放进度后,如果是更新播放进度则不写数据库,而是写缓存(覆盖之前旧数据)。
所有一定有一个步骤就是持久化缓存数据到数据库。一般采用的是定时任务持久化。
3.3.2 存在问题
但是定时任务的持久化方式在播放进度记录业务中存在一些问题,主要就是时效性问题。我的产品要求视频续播的时间误差不能超过20秒。
-
假如定时任务间隔较短,例如15秒一次,对数据库的更新频率太高,压力太大
-
假如定时任务间隔较长,例如2分钟一次,更新频率较低,续播误差可能超过2分钟,不满足需求
注意:如果产品对于时间误差要求不高,定时任务处理是最简单,最可靠的一种方案,推荐使用。
3.3.3 问题分析
那么问题来了,有什么办法能够在不增加数据库压力的情况下,保证时间误差较低吗?
假如一个视频时长为20分钟,我们从头播放至15分钟关闭,每隔10秒提交一次播放进度,大概需要提交90次请求。
但是下一次我们再次打开该视频续播的时候,肯定是从最后一次提交的播放进度来续播。也就是说续播进度之前的N次播放进度都是没有意义的,都会被覆盖。
既然如此,我们完全没有必要定期把这些播放进度写到数据库,只需要将用户最后一次提交的播放进度写入数据库即可。
但问题来了,我们怎么知道哪一次提交是最后一次提交呢?
只要用户一直在提交记录,Redis中的播放进度就会一直变化。如果Redis中的播放进度不变,肯定是停止了播放,是最后一次提交。
因此,我们只要能判断Redis中的播放进度是否变化即可。怎么判断呢?
3.3.4 持久化思路
每当前端提交播放记录时,我们可以设置一个延迟任务并保存这次提交的进度。等待15秒后(因为前端每10秒提交一次,15秒就是等待下一次提交),检查Redis中的缓存的进度与任务中的进度是否一致。
-
不一致:说明持续在提交,无需处理
-
一致:说明是最后一次提交,更新视频播放进度到数据库中