好久没有写文章了,这篇介绍一个工作中遇到的功能和相关设计,其中涉及到的几个点还是值得深入考虑下,简单做个记录。
业务场景
实际是一个数据统计的需求,比如我们在使用微博的时候,会记录有多少人评论,发布多少评论,收到多少评论等交互信息。与之类似,这个需求详细描述的话有如下5个指标要提供。
1、用户发布的内容数;
2、用户内容得到的分享数
3、用户分享的内容数;
4、用户给他人的评论数;
5、用户内容获得的评论数;
概要设计
问题一
用户发布的内容数
首先,第一个用户发布的内容数,这个其实只要在用户发布的时候做记录就可以了。这个是一种基础的方案,另外有一种场景是,这个发布的内容需要审核。内容只记录有效内容,这个场景下,有三种解决方法:1)如果有消息组件,在完成审核之后推送消息来统计。2)没有消息组件或者不完善可以用借口回调做统计。3)比较笨拙的方法,固定时间后主动调用接口获得统计数据。但是会有问题,审核如果是人工的,往往不会是固定周期完成,因此不推荐。操作其实就是分享之后给authorId 对应的pub_count+1。
问题二
用户内容获得的分享数
这里俩问题,计数的是内容还是分享的人次,是否需要去重。这里我们看下,首先有三个关键参数,内容ID,分享者,内容作者,即contentId
,sharedBy
,authorId
。用户内容获得的分享数,假设去重,就用 sharedBy + contentId 判断是否已经分享,否则设置标签并给对应的authorId的recv_shared_count + 1。当然这里如果是记录分享人次,则用authorId + sharedBy去重,效果就是只要用户A分享过B的内容,就算一人次计数,且以后再分享都不算。
问题三
用户分享的内容数
这个比较简单,用sharedBy + contentId做去重,然后按结果给对应的sharedBy的shared_count+1
问题四
用户给他人的评论数
这个是比较复杂的一个功能,有很多的情况。首先,评论一般是在一个内容下的,回复内容算作一级评论,当然还有回复评论,会有多级的评论回复。如果是统计人次,还有个场景就是A发内容,B评论A,C评论B的评论,这种情况到底算给谁的评论,这里我们默认也算作给A的评论,因为都是在A的内容下。还有一种比较特殊就是,在别人的内容下自己回复自己的评论是不是要计数,虽然这个情况比较少,其实也可以加但可能会有hash key 放大的风险,因为又增加了一个维度的统计。
这个还是内容的评论数,不是用户评论了多少人,还是以内容来判断的。首先,发布评论之后,有几个参数:内容id,评论者,内容作者,即contentId
,commenter
,authorId
,首先用authorId和commenter过滤自己给自己的评论。其次给对应的commenter的comment_count + 1
问题五
用户内容获得评论数
这个还是以内容计数的,在评论的时候会有三个参数,评论者,内容作者,内容id,即commenter
,authorId
,contentId
,首先过滤自己给自己内容评论(他人内容下自己回复自己评论不过滤),最后给对应的authorId的recv_comment_count+1。
技术选型和设计
具体要使用哪种技术,其实是和业务紧密相关的。首先我们可以看到类似exist和incr的操作,直观的是使用Redis,但Redis涉及到集群维护以及持久化的问题,如果有成熟的环境可以直接来用就方便了。持久化除了依赖Redis当然也可以自己维护,比如用任务刷到数据库等等。其次可以考虑MySQL持久化,如唯一键等特性,当然也可以自己维护逻辑关系。其他比如Pegasus等也可以使用。
聪明的同学已经看到给别人评论和收到评论,以及分享别人内容和获得分享是有关联的。的确设计的时候这两种情况是在一起考虑的。以下是整体的设计方案和伪代码。
1、结构
pub_count; //发布内容数
shared_count;//分享内容数
comment_count;//发布评论数
recv_comment_count;//内容获得评论数
recv_shared_count;//内容获得分享数
}
2、业务伪代码
incrPubCount(authorId){
incr(authorId+pub_count);
}
incrSharedCount(sharedBy,authorId,contentId){
if(exist(sharedBy+contentId) || sharedBy == authorId){
return;
}else{
set(sharedBy+contentId);
incr(sharedBy+shared_count);
incr(authorId+recv_shared_count);
}
}
//具体按人次还是按内容依据情况修改。
incrCommentCount(commenter,authorId,contentId){
if(authorId == commenter){
return;
}else{
incr(commenter+comment_count);
incr(authorId+recv_comment_count);
}
}
总结
以上就是回顾的所有内容,某些方面还是有待完善,比如数据量非常大的时候的存储选型;某些异常的时候状态可能不一致的问题,如:A分享B,A分享计数增加,但B获得分享计数增加失败的情况。这种问题其实非常典型,类似银行转账的,只是这个是两边都增加。也因为业务不需要这么严谨,而且主要是每个具体操作比较简单,可以看做是原子的,依赖服务失败几率比较小,失败后再重试或者回滚成功的可能也比较小,因此没有再做更复杂的容错方案。感兴趣的朋友可以再深入了解下一致性相关内容。
还有一个是这个需求统计相关,可以用异步流程实现,如spring的async注解,以后有时间单独开篇文章介绍下。
以上,感谢阅读。
网友评论