最近由于我的网站的文章浏览量计数在Chrome浏览器下有BUG,因此打算从新实现这个功能。前端
本来的实现很简单,每次点击文章详情页的时候,前端会发送一个GET请求articles/id
获取一篇文章详情。这个时候,会把这篇文章的浏览量+1,再存进数据库里。redis
这个实现本来能够实现这个功能,可是后来我才发现,我犯了一个很致命的错误:在GET请求的业务逻辑里进行了数据的写操做!sql
原则来说,GET请求应该具备幂等性,即短期内同时两个如出一辙的GET请求,返回的结果也应该是同样的。而我本来的实现就破坏了GET请求的幂等性。数据库
刚好,在Chrome浏览器里,个人文章详情页会发送两次GET请求。这疑似Chrome浏览器和nuxt服务端渲染之间的一个BUG,目前尚未定位到具体缘由。后端
但不管如何,后端应该是能够避免这样的BUG,即便某用户短期内请求两次或者屡次,也应该只增长一次浏览量计数。浏览器
因为最近在学习高并发方面的知识,因此这里也考虑一下,若是一个高并发的文章浏览量计数系统,应该如何设计?缓存
先来理一下需求。多线程
若是要知足高并发,那首先考虑用异步和缓存。因此考虑使用多线程加Redis的解决方案。并发
请求流程:异步
PUT
请求/articles/{id:\\d+}/view
。200
响应。200
响应后,当即把当前文章的浏览量+1,知足需求3。后端主要逻辑:
后端的主要思路是暂时把增长的浏览量(假设某篇文章为n)放进Redis里,而后每隔一段时间刷新到Mysql数据库和ElasticSearch存储里,让这篇文章的浏览量在现有的基础上加n,而后把Redis这篇文章的浏览量清零。
isViewd:articleId:ip
。若是有,就说明以前浏览过,就什么也不作,直接返回。若是没有,就加上这个key。时间能够设置为1小时过时,防止占用过多内存。这里使用Redis的string类型。viewCount:articleId
,value为缓存的浏览量。完成后当前线程任务就结束了。这里使用Redis的string类型。这些key应该没有过时时间。若是并发量特别大,能够考虑不把浏览量存在数据库里,而仅存在Redis里,这样能够获得近乎实时的浏览量存储,并且需求8排序也是实时的(使用zset),但这样可能会耗费大量的内存资源。
虽然最后权衡了并发量和复杂性,个人我的网站的文章浏览逻辑并无彻底按照上述设计思路来作,但上述思路是我对一个高并发文章浏览量计数系统设计的思考,之后若是有机会能够写一个开源的版本。
可能实现起来会更复杂,根据并发量的不一样,代码也会有一些差异,以上思路仅供参考。