在项目中会遇到这样记录文章或者其他内容阅读量的需求,最常规的方案就是每一次读取内容的时候把内容表中阅读数的值+1。这个方案非常简单而且也很容易实现,但是这个方案有几个问题:
以上第一个问题我们可以给代码加同步锁解决,但是会导致代码执行效率太低;第二个问题把数据库改成读写分离,不过为了解决这个问题而读写分离显得没有必要。
因此如果你的站点流量很小的话直接使用以上方案没有问题,但是如果流量增大的话那么就得另谋他路了。
首先针对第一个和第二个问题,都是由于频繁的读写数据库造成的。所以我们得想办法把读写频率降低。
降低用户阅读内容的频率显然不是我们希望的,但是我们可以降低用户访问数据库的频率。通过在用户和数据库之间架设缓存机制来降低数据库读频率。
然后是怎么降低写数据库的频率呢?因为实际上我们写数据库只是去更新表中的一个阅读数字段。而且这个字段对实时性和准确性的要求并不是很高。所以在此考虑将内容的id
作为key
,阅读数新增的值作为value
存在缓存数据库中。每五分钟从缓存中取出value
并且更新数据库:
int value = redis.get("article_1");
update article set num = num + value where id = 1;
redis.set("article_1", redis.get("article_1")-value);
以上伪代码的意思是先从redis中取出id为1的文章新增的阅读数,更新数据库中文章的阅读数,更新redis中该文章的值,为了避免在更新过程中redis中又有了新的值放进来,所以减去value
而不是直接置为0。
而且我们将这个任务放到定时任务中去,每五分钟执行一次,这样文章的读写频率都降低了。接下来是同一个人在短时间内多次阅读同一篇文章的问题。
在这里我们定义同一个人在五分钟内阅读了同一篇文章的话阅读数只能算一次。那么我们怎么去定义这是同一个人呢?在一般系统中我们都会给用户一个uid
来作为用户的唯一标识,所以首先可以通过这个用户id
来确定是同一个人。但是在很多场景下用户都是没有登陆的,那么我们就没办法获取到我们赋予用户的uid
了,这个时候有几种解决方案:
第一种是在用户打开app或者网站首页的时候,如果用户没有登陆的话,就给用户注册一个游客的身份,将游客id作为此时用户的uid
。
第二种方案使用设备码来标识用户,虽然现在设备码越来越难获取,但是还是有很多方法能拿到用户设备的唯一标识符的。
第三种方案直接如果用户没有登陆的话直接使用用户的ip
地址作为用户id唯一标识用户。
当然可能还有其他方案,欢迎评论留言告知。
拿到uid
之后,将uid
+内容id
作为key
直接存进缓存数据库,设置失效时间为5分钟。
每一次给这个文章设置阅读数加1的时候都去查询缓存中是否存在这个key
。
if (!redis.get(uid+article_id)) {
redis.inc(article_id,1);
redis.set(uid+article_id, 5*60);
}
以上就是阅读数的大致实现思路,由于会用到定时任务,因此下一次会将Spring Boot
与Quartz
的整合方案公布一下。如果思路有什么问题也欢迎批评指正。