需求
学员购买课程并开课之后会生成一个课表,学员根据课表去上课。目前存在转班的功能,转班的时候会把原来的课表作废掉,然后按照新转的班型去生成新的课表。而这样存在一个问题,新的课表和原来的课表一般会存在相同的课节,转班之后相同课节的观看时长等信息会丢失。现在期望将观看时长、录播件观看进度等存储在学员+课节的维度,在转单之后还能根据课节去匹配原来的观看时长。
方案
在做需求的技术方案调研的时候,发现目前系统学员的观看时长存在很多和听课记录时长对不上的情况。 原有的拉取直播录播观看记录的代码设计复杂且冗余严重,并且在和第三方技术人员沟通中得知录播的实际观看时长应该直接用第三方接口提供的duration,因为观看录播,进入房间之后如果不播放是不会统计实际观看时长的,因此离开时间减进入时间一般不等于duration。而原系统设计上采用了离开时间减进入时间的方式来统计时长,导致时长对不上。
考虑到在原功能上做修改,也会因为数据缺失而导致数据不准确,因此决定重构拉取观看记录和统计时长的功能。
可以先全量拉取数据,然后根据手机号统计时长即可。后面想到了学员存在更换手机号的情况,更换手机号之后,之前的观看记录就对不上了,因为前系统设计的进入直播间录播间的传参是手机号。
当时思维钻了牛角尖,一直想着一条记录拉取回来之后如何计算出正确的统计时长。可以在更换手机号的时候把历史的属于该学员的手机号更换成新的手机号,但这又引出了重新拉取观看记录的时候观看记录匹配不上的问题(第三方的直播观看记录不提供记录唯一标识)。只要变更了手机号就会存在该问题,因此不考虑更改记录的手机号。不变更的基础上如何去统计呢。
后来突然想到,变更手机号学员是少数,能不能先不考虑特殊情况。这么一想,需求一下子就简单了。主要逻辑变成了简单的拉取观看记录回来并存储起来,然后根据手机号统计数据,并将统计结果按照学员+课节的维度存储到一个新的统计表中。每次拉取完定时任务,再根据手机号变更记录表的数据去特殊处理变更手机号的学员的观看时长(根据变更手机号的时间,分时间段去分别统计时长并汇总)。
考虑到如果每条记录都统计一遍观看时长,会存在同一个号码同一个课节需要统计多次。因此在全量拉取观看记录的时候先把统计逻辑去掉,后续再全量统计,同一个号码和课节只统计一次。
具体实现逻辑(在同步库进行)
拉取直播录播观看记录
直播和录播拉取记录所需的参数不一样。录播可以直接按照天来拉取,直播需要按照直播间加时间段拉取。
因此拉取直播需要先根据时间段查询课节,然后根据课节设置的直播间和时间段去拉取数据。
录播的直接按照日期拉取数据。
考虑到效率,拉取的定时任务都采用多线程的实现方式。为了数据的安全,直播录播按照日期去分定时任务,每个定时任务拉取一天的记录。
拉取回来的记录需要判断是否已经存在,录播观看记录有第三方唯一id,使用该id来唯一标识一条记录。而直播不存在该标识,只能按照直播间+进入时间去唯一标识一条记录(同一个号码看同一场直播存在互踢机制,即一个直播间里同一个号码的直播观看记录不会存在时间上的重叠)。
因为不同线程拉取的是不同日期的数据,因此不用担心数据会存在冲突。
重复拉取的录播记录只有duration发生变化的才重新统计观看时长。
直播观看记录重复拉取的不会重新统计观看时长。
统计学员课节观看时长
同一个学员+课节只需要统计一次,因此在遍历观看记录统计数据的时候再次碰到已经统计过的记录直接跳过。采用redis来记录是否已经统计过。该任务也采用多线程的模式,但因为即使不同的日期,也可能存在同一个学员看同一个录播回放的情况,如果同时多个线程判断某学员+课节的统计记录不存在,那么可能创建多条统计记录,造成重复。因此在创建之前需要加上上锁。考虑到不同学员不同课节之间互相不用影响,因而采用redis的setNx方法来进行判断,只允许一个线程进入创建统计记录。其余线程直接结束。
为了进一步降低线程安全问题,上线之后使用单线程或少量多线程定时拉取最近两天的记录,并且将直播和录播的任务执行时间稍微错开。即使出现因为同时创建而统计错误的问题,数据也会在再次观看的时候正确统计。
针对更改手机号的记录特殊统计
查询电话号码变更记录表,按照学员维度将变更时间分成不同的时间段。一个学员按照不同时间段对应不同的号码去查询观看记录并统计到一起更新到统计记录表。因为这个是补偿任务,因此在拉取观看记录的定时任务执行10~20分钟之后再开始补偿。
上线之后的处理
上线之后需要再次拉取最近一到两个月的观看记录。防止同步库数据和正式库数据差导致数据缺失。